Add TxCompressedData & V2 with some extra parsers for common

This commit is contained in:
arnaucube
2020-08-21 15:01:01 +02:00
parent 3f63cd7cd0
commit a28c81f4ae
12 changed files with 275 additions and 76 deletions

View File

@@ -23,39 +23,47 @@ type Account struct {
EthAddr eth.Address
}
func (a *Account) String() string {
buf := bytes.NewBufferString("")
fmt.Fprintf(buf, "PublicKey: %s..., ", a.PublicKey.String()[:10])
fmt.Fprintf(buf, "EthAddr: %s..., ", a.EthAddr.String()[:10])
fmt.Fprintf(buf, "TokenID: %v, ", a.TokenID)
fmt.Fprintf(buf, "Nonce: %d, ", a.Nonce)
fmt.Fprintf(buf, "Balance: %s, ", a.Balance.String())
return buf.String()
}
// Bytes returns the bytes representing the Account, in a way that each BigInt is represented by 32 bytes, in spite of the BigInt could be represented in less bytes (due a small big.Int), so in this way each BigInt is always 32 bytes and can be automatically parsed from a byte array.
func (l *Account) Bytes() ([32 * NLEAFELEMS]byte, error) {
func (a *Account) Bytes() ([32 * NLEAFELEMS]byte, error) {
var b [32 * NLEAFELEMS]byte
if l.Nonce > 0xffffffffff {
if a.Nonce > 0xffffffffff {
return b, fmt.Errorf("%s Nonce", ErrNumOverflow)
}
if len(l.Balance.Bytes()) > 24 {
if len(a.Balance.Bytes()) > 24 {
return b, fmt.Errorf("%s Balance", ErrNumOverflow)
}
var tokenIDBytes [4]byte
binary.LittleEndian.PutUint32(tokenIDBytes[:], uint32(l.TokenID))
var nonceBytes [8]byte
binary.LittleEndian.PutUint64(nonceBytes[:], l.Nonce)
binary.LittleEndian.PutUint64(nonceBytes[:], a.Nonce)
copy(b[0:4], tokenIDBytes[:])
copy(b[0:4], a.TokenID.Bytes())
copy(b[4:9], nonceBytes[:])
if babyjub.PointCoordSign(l.PublicKey.X) {
if babyjub.PointCoordSign(a.PublicKey.X) {
b[10] = 1
}
copy(b[32:64], SwapEndianness(l.Balance.Bytes())) // SwapEndianness, as big.Int uses BigEndian
copy(b[64:96], SwapEndianness(l.PublicKey.Y.Bytes()))
copy(b[96:116], l.EthAddr.Bytes())
copy(b[32:64], SwapEndianness(a.Balance.Bytes())) // SwapEndianness, as big.Int uses BigEndian
copy(b[64:96], SwapEndianness(a.PublicKey.Y.Bytes()))
copy(b[96:116], a.EthAddr.Bytes())
return b, nil
}
// BigInts returns the [5]*big.Int, where each *big.Int is inside the Finite Field
func (l *Account) BigInts() ([NLEAFELEMS]*big.Int, error) {
func (a *Account) BigInts() ([NLEAFELEMS]*big.Int, error) {
e := [NLEAFELEMS]*big.Int{}
b, err := l.Bytes()
b, err := a.Bytes()
if err != nil {
return e, err
}
@@ -69,10 +77,10 @@ func (l *Account) BigInts() ([NLEAFELEMS]*big.Int, error) {
}
// HashValue returns the value of the Account, which is the Poseidon hash of its *big.Int representation
func (l *Account) HashValue() (*big.Int, error) {
func (a *Account) HashValue() (*big.Int, error) {
b0 := big.NewInt(0)
toHash := []*big.Int{b0, b0, b0, b0, b0, b0}
lBI, err := l.BigInts()
lBI, err := a.BigInts()
if err != nil {
return nil, err
}
@@ -122,12 +130,12 @@ func AccountFromBytes(b [32 * NLEAFELEMS]byte) (*Account, error) {
return nil, ErrNotInFF
}
l := Account{
a := Account{
TokenID: TokenID(tokenID),
Nonce: nonce,
Balance: balance,
PublicKey: &publicKey,
EthAddr: ethAddr,
}
return &l, nil
return &a, nil
}

View File

@@ -8,5 +8,8 @@ var ErrNotInFF = errors.New("BigInt not inside the Finite Field")
// ErrNumOverflow is used when a given value overflows the maximum capacity of the parameter
var ErrNumOverflow = errors.New("Value overflows the type")
// ErrNonceOverflow is used when a given nonce overflows the maximum capacity of the Nonce (2**40-1)
var ErrNonceOverflow = errors.New("Nonce overflow, max value: 2**40 -1")
// ErrBatchQueueEmpty is used when the coordinator.BatchQueue.Pop() is called and has no elements
var ErrBatchQueueEmpty = errors.New("BatchQueue empty")

View File

@@ -14,7 +14,7 @@ type L2Tx struct {
ToIdx Idx `meddler:"to_idx"`
Amount *big.Int `meddler:"amount,bigint"`
Fee FeeSelector `meddler:"fee"`
Nonce uint64 `meddler:"nonce"`
Nonce Nonce `meddler:"nonce"`
// Extra metadata, may be uninitialized
Type TxType `meddler:"-"` // optional, descrives which kind of tx it's
}

View File

@@ -1,13 +1,38 @@
package common
import (
"encoding/binary"
"fmt"
"math/big"
"time"
eth "github.com/ethereum/go-ethereum/common"
"github.com/hermeznetwork/hermez-node/utils"
"github.com/iden3/go-iden3-crypto/babyjub"
)
// Nonce represents the nonce value in a uint64, which has the method Bytes that returns a byte array of length 5 (40 bits).
type Nonce uint64
// Bytes returns a byte array of length 5 representing the Nonce
func (n Nonce) Bytes() ([5]byte, error) {
if n >= 1099511627776 { // 2**40bits
return [5]byte{}, ErrNonceOverflow
}
var nonceBytes [8]byte
binary.LittleEndian.PutUint64(nonceBytes[:], uint64(n))
var b [5]byte
copy(b[:], nonceBytes[:5])
return b, nil
}
func NonceFromBytes(b [5]byte) (Nonce, error) {
var nonceBytes [8]byte
copy(nonceBytes[:], b[:5])
nonce := binary.LittleEndian.Uint64(nonceBytes[:])
return Nonce(nonce), nil
}
// PoolL2Tx is a struct that represents a L2Tx sent by an account to the coordinator hat is waiting to be forged
type PoolL2Tx struct {
// Stored in DB: mandatory fileds
@@ -19,7 +44,7 @@ type PoolL2Tx struct {
TokenID TokenID `meddler:"token_id"`
Amount *big.Int `meddler:"amount,bigint"` // TODO: change to float16
Fee FeeSelector `meddler:"fee"`
Nonce uint64 `meddler:"nonce"` // effective 48 bits used
Nonce Nonce `meddler:"nonce"` // effective 40 bits used
State PoolL2TxState `meddler:"state"`
Signature babyjub.Signature `meddler:"signature"` // tx signature
Timestamp time.Time `meddler:"timestamp,utctime"` // time when added to the tx pool
@@ -40,6 +65,86 @@ type PoolL2Tx struct {
RqTxCompressedData []byte `meddler:"-"` // 253 bits, optional for atomic txs
}
// TxCompressedData spec:
// [ 32 bits ] signatureConstant // 4 bytes: [0:4]
// [ 16 bits ] chainId // 2 bytes: [4:6]
// [ 48 bits ] fromIdx // 6 bytes: [6:12]
// [ 48 bits ] toIdx // 6 bytes: [12:18]
// [ 16 bits ] amountFloat16 // 2 bytes: [18:20]
// [ 32 bits ] tokenID // 4 bytes: [20:24]
// [ 40 bits ] nonce // 5 bytes: [24:29]
// [ 8 bits ] userFee // 1 byte: [29:30]
// [ 1 bits ] toBjjSign // 1 byte: [30:31]
// Total bits compressed data: 241 bits // 31 bytes in *big.Int representation
func (tx *PoolL2Tx) TxCompressedData() (*big.Int, error) {
// sigconstant
sc, ok := new(big.Int).SetString("3322668559", 10)
if !ok {
return nil, fmt.Errorf("error parsing SignatureConstant")
}
amountFloat16, err := utils.NewFloat16(tx.Amount)
if err != nil {
return nil, err
}
var b [31]byte
copy(b[:4], SwapEndianness(sc.Bytes()))
copy(b[4:6], []byte{1, 0, 0, 0}) // LittleEndian representation of uint32(1) for Ethereum
copy(b[6:12], tx.FromIdx.Bytes())
copy(b[12:18], tx.ToIdx.Bytes())
copy(b[18:20], amountFloat16.Bytes())
copy(b[20:24], tx.TokenID.Bytes())
nonceBytes, err := tx.Nonce.Bytes()
if err != nil {
return nil, err
}
copy(b[24:29], nonceBytes[:])
b[29] = byte(tx.Fee)
toBjjSign := byte(0)
if babyjub.PointCoordSign(tx.ToBJJ.X) {
toBjjSign = byte(1)
}
b[30] = toBjjSign
bi := new(big.Int).SetBytes(SwapEndianness(b[:]))
return bi, nil
}
// TxCompressedDataV2 spec:
// [ 48 bits ] fromIdx // 6 bytes: [0:6]
// [ 48 bits ] toIdx // 6 bytes: [6:12]
// [ 16 bits ] amountFloat16 // 2 bytes: [12:14]
// [ 32 bits ] tokenID // 4 bytes: [14:18]
// [ 40 bits ] nonce // 5 bytes: [18:23]
// [ 8 bits ] userFee // 1 byte: [23:24]
// [ 1 bits ] toBjjSign // 1 byte: [24:25]
// Total bits compressed data: 193 bits // 25 bytes in *big.Int representation
func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) {
amountFloat16, err := utils.NewFloat16(tx.Amount)
if err != nil {
return nil, err
}
var b [25]byte
copy(b[0:6], tx.FromIdx.Bytes())
copy(b[6:12], tx.ToIdx.Bytes())
copy(b[12:14], amountFloat16.Bytes())
copy(b[14:18], tx.TokenID.Bytes())
nonceBytes, err := tx.Nonce.Bytes()
if err != nil {
return nil, err
}
copy(b[18:23], nonceBytes[:])
b[23] = byte(tx.Fee)
toBjjSign := byte(0)
if babyjub.PointCoordSign(tx.ToBJJ.X) {
toBjjSign = byte(1)
}
b[24] = toBjjSign
bi := new(big.Int).SetBytes(SwapEndianness(b[:]))
return bi, nil
}
func (tx *PoolL2Tx) Tx() *Tx {
return &Tx{
TxID: tx.TxID,

75
common/pooll2tx_test.go Normal file
View File

@@ -0,0 +1,75 @@
package common
import (
"encoding/hex"
"math/big"
"testing"
"github.com/iden3/go-iden3-crypto/babyjub"
"github.com/stretchr/testify/assert"
)
func TestNonceParser(t *testing.T) {
n := Nonce(1)
nBytes, err := n.Bytes()
assert.Nil(t, err)
assert.Equal(t, 5, len(nBytes))
assert.Equal(t, "0100000000", hex.EncodeToString(nBytes[:]))
n2, err := NonceFromBytes(nBytes)
assert.Nil(t, err)
assert.Equal(t, n, n2)
// value before overflow
n = Nonce(1099511627775)
nBytes, err = n.Bytes()
assert.Nil(t, err)
assert.Equal(t, 5, len(nBytes))
assert.Equal(t, "ffffffffff", hex.EncodeToString(nBytes[:]))
n2, err = NonceFromBytes(nBytes)
assert.Nil(t, err)
assert.Equal(t, n, n2)
// expect value overflow
n = Nonce(1099511627776)
nBytes, err = n.Bytes()
assert.NotNil(t, err)
assert.Equal(t, ErrNonceOverflow, err)
_, err = NonceFromBytes(nBytes)
assert.Nil(t, err)
}
func TestTxCompressedData(t *testing.T) {
var sk babyjub.PrivateKey
_, err := hex.Decode(sk[:], []byte("0001020304050607080900010203040506070809000102030405060708090001"))
assert.Nil(t, err)
tx := PoolL2Tx{
FromIdx: 2,
ToIdx: 3,
Amount: big.NewInt(4),
TokenID: 5,
Nonce: 6,
ToBJJ: sk.Public(),
}
txCompressedData, err := tx.TxCompressedData()
assert.Nil(t, err)
// test vector value generated from javascript implementation
assert.Equal(t, "1766847064778421992193717128424891165872736891548909569553540449389241871", txCompressedData.String())
assert.Equal(t, "10000000000060000000500040000000000030000000000020001c60be60f", hex.EncodeToString(txCompressedData.Bytes())[1:])
tx = PoolL2Tx{
FromIdx: 7,
ToIdx: 8,
Amount: big.NewInt(9),
TokenID: 10,
Nonce: 11,
Fee: 12,
ToBJJ: sk.Public(),
}
txCompressedData, err = tx.TxCompressedDataV2()
assert.Nil(t, err)
// test vector value generated from javascript implementation
assert.Equal(t, "6571340879233176732837827812956721483162819083004853354503", txCompressedData.String())
assert.Equal(t, "10c000000000b0000000a0009000000000008000000000007", hex.EncodeToString(txCompressedData.Bytes())[1:])
}

View File

@@ -1,6 +1,7 @@
package common
import (
"encoding/binary"
"time"
eth "github.com/ethereum/go-ethereum/common"
@@ -26,3 +27,10 @@ type TokenInfo struct {
// TokenID is the unique identifier of the token, as set in the smart contract
type TokenID uint32 // current implementation supports up to 2^32 tokens
// Bytes returns a byte array of length 4 representing the TokenID
func (t TokenID) Bytes() []byte {
var tokenIDBytes [4]byte
binary.LittleEndian.PutUint32(tokenIDBytes[:], uint32(t))
return tokenIDBytes[:]
}

View File

@@ -66,7 +66,7 @@ type Tx struct {
ToIdx Idx // ToIdx is ignored in L1Tx/Deposit, but used in the L1Tx/DepositTransfer
TokenID TokenID
Amount *big.Int
Nonce uint64 // effective 48 bits used
Nonce Nonce // effective 40 bits used
Fee FeeSelector
Type TxType // optional, descrives which kind of tx it's
BatchNum BatchNum // batchNum in which this tx was forged. Presence indicates "forged" state.