diff --git a/common/l1tx.go b/common/l1tx.go index 07fffbc..80a12b5 100644 --- a/common/l1tx.go +++ b/common/l1tx.go @@ -6,12 +6,15 @@ import ( "math/big" ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/iden3/go-iden3-crypto/babyjub" ) const ( // L1TxBytesLen is the length of the byte array that represents the L1Tx L1TxBytesLen = 72 + // L1CoordinatorTxBytesLen is the length of the byte array that represents the L1CoordinatorTx + L1CoordinatorTxBytesLen = 101 ) // L1Tx is a struct that represents a L1 tx @@ -136,7 +139,7 @@ func (tx *L1Tx) Tx() *Tx { } // Bytes encodes a L1Tx into []byte -func (tx *L1Tx) Bytes(nLevels int) ([]byte, error) { +func (tx *L1Tx) Bytes() ([]byte, error) { var b [L1TxBytesLen]byte copy(b[0:20], tx.FromEthAddr.Bytes()) pkComp := tx.FromBJJ.Compress() @@ -165,6 +168,21 @@ func (tx *L1Tx) Bytes(nLevels int) ([]byte, error) { return b[:], nil } +// BytesCoordinatorTx encodes a L1CoordinatorTx into []byte +func (tx *L1Tx) BytesCoordinatorTx(compressedSignatureBytes []byte) ([]byte, error) { + var b [L1CoordinatorTxBytesLen]byte + v := compressedSignatureBytes[64] + s := compressedSignatureBytes[32:64] + r := compressedSignatureBytes[0:32] + b[0] = v + copy(b[1:33], s) + copy(b[33:65], r) + pkComp := tx.FromBJJ.Compress() + copy(b[65:97], pkComp[:]) + copy(b[97:101], tx.TokenID.Bytes()) + return b[:], nil +} + // L1TxFromBytes decodes a L1Tx from []byte func L1TxFromBytes(b []byte) (*L1Tx, error) { if len(b) != L1TxBytesLen { @@ -202,3 +220,52 @@ func L1TxFromBytes(b []byte) (*L1Tx, error) { return tx, nil } + +// L1TxFromCoordinatorBytes decodes a L1Tx from []byte +func L1TxFromCoordinatorBytes(b []byte) (*L1Tx, error) { + if len(b) != L1CoordinatorTxBytesLen { + return nil, fmt.Errorf("Can not parse L1CoordinatorTx bytes, expected length %d, current: %d", 101, len(b)) + } + + bytesMessage1 := []byte("\x19Ethereum Signed Message:\n98") + bytesMessage2 := []byte("I authorize this babyjubjub key for hermez rollup account creation") + + tx := &L1Tx{} + var err error + // Ethereum adds 27 to v + v := b[0] - byte(27) //nolint:gomnd + s := b[1:33] + r := b[33:65] + + pkCompB := b[65:97] + var pkComp babyjub.PublicKeyComp + copy(pkComp[:], pkCompB) + tx.FromBJJ, err = pkComp.Decompress() + if err != nil { + return nil, err + } + tx.TokenID, err = TokenIDFromBytes(b[97:101]) + if err != nil { + return nil, err + } + + var data []byte + data = append(data, bytesMessage1...) + data = append(data, bytesMessage2...) + data = append(data, pkCompB...) + var signature []byte + signature = append(signature, r[:]...) + signature = append(signature, s[:]...) + signature = append(signature, v) + hash := crypto.Keccak256(data) + pubKeyBytes, err := crypto.Ecrecover(hash, signature) + if err != nil { + return nil, err + } + pubKey, err := crypto.UnmarshalPubkey(pubKeyBytes) + if err != nil { + return nil, err + } + tx.FromEthAddr = crypto.PubkeyToAddress(*pubKey) + return tx, nil +} diff --git a/common/l1tx_test.go b/common/l1tx_test.go index d91b8bd..91025fe 100644 --- a/common/l1tx_test.go +++ b/common/l1tx_test.go @@ -1,10 +1,14 @@ package common import ( + "crypto/ecdsa" + "encoding/hex" + "log" "math/big" "testing" ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/iden3/go-iden3-crypto/babyjub" "github.com/iden3/go-iden3-crypto/utils" "github.com/stretchr/testify/assert" @@ -53,7 +57,7 @@ func TestL1TxByteParsers(t *testing.T) { expected, err := utils.HexDecode("c58d29fa6e86e4fae04ddced660d45bcf3cb237056ca90f80d7c374ae7485e9bcc47d4ac399460948da6aeeb899311097925a72c0000000000020002000100000005000000000003") require.Nil(t, err) - encodedData, err := l1Tx.Bytes(32) + encodedData, err := l1Tx.Bytes() require.Nil(t, err) assert.Equal(t, expected, encodedData) @@ -61,7 +65,7 @@ func TestL1TxByteParsers(t *testing.T) { require.Nil(t, err) assert.Equal(t, l1Tx, decodedData) - encodedData2, err := decodedData.Bytes(32) + encodedData2, err := decodedData.Bytes() require.Nil(t, err) assert.Equal(t, encodedData, encodedData2) @@ -73,3 +77,80 @@ func TestL1TxByteParsers(t *testing.T) { _, err = L1TxFromBytes(nil) require.NotNil(t, err) } + +func TestL1CoordinatorTxByteParsers(t *testing.T) { + privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19") + require.Nil(t, err) + + publicKey := privateKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + log.Fatal("error casting public key to ECDSA") + } + publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA) + pubKey, err := crypto.UnmarshalPubkey(publicKeyBytes) + require.Nil(t, err) + fromEthAddr := crypto.PubkeyToAddress(*pubKey) + var pkComp babyjub.PublicKeyComp + err = pkComp.UnmarshalText([]byte("0x56ca90f80d7c374ae7485e9bcc47d4ac399460948da6aeeb899311097925a72c")) + require.Nil(t, err) + pk, err := pkComp.Decompress() + require.Nil(t, err) + bytesMessage1 := []byte("\x19Ethereum Signed Message:\n98") + bytesMessage2 := []byte("I authorize this babyjubjub key for hermez rollup account creation") + + babyjub := pk.Compress() + var data []byte + data = append(data, bytesMessage1...) + data = append(data, bytesMessage2...) + data = append(data, babyjub[:]...) + hash := crypto.Keccak256Hash(data) + signature, err := crypto.Sign(hash.Bytes(), privateKey) + require.Nil(t, err) + // Ethereum adds 27 to v + v := int(signature[64]) + signature[64] = byte(v + 27) + + l1Tx := &L1Tx{ + TokenID: 231, + FromBJJ: pk, + FromEthAddr: fromEthAddr, + } + + bytesCoordinatorL1, err := l1Tx.BytesCoordinatorTx(signature) + require.Nil(t, err) + l1txDecoded, err := L1TxFromCoordinatorBytes(bytesCoordinatorL1) + require.Nil(t, err) + assert.Equal(t, l1Tx, l1txDecoded) +} + +func TestL1CoordinatorTxByteParsersCompatibility(t *testing.T) { + var signature []byte + r, err := hex.DecodeString("da71e5eb097e115405d84d1e7b464009b434b32c014a2df502d1f065ced8bc3b") + require.Nil(t, err) + s, err := hex.DecodeString("186d7122ff7f654cfed3156719774898d573900c86599a885a706dbdffe5ea8c") + require.Nil(t, err) + v, err := hex.DecodeString("1b") + require.Nil(t, err) + + signature = append(signature, r[:]...) + signature = append(signature, s[:]...) + signature = append(signature, v[:]...) + + var pkComp babyjub.PublicKeyComp + err = pkComp.UnmarshalText([]byte("0xa2c2807ee39c3b3378738cff85a46a9465bb8fcf44ea597c33da9719be7c259c")) + require.Nil(t, err) + // Data from the compatibility test + expected := "1b186d7122ff7f654cfed3156719774898d573900c86599a885a706dbdffe5ea8cda71e5eb097e115405d84d1e7b464009b434b32c014a2df502d1f065ced8bc3ba2c2807ee39c3b3378738cff85a46a9465bb8fcf44ea597c33da9719be7c259c000000e7" + pk, err := pkComp.Decompress() + require.Nil(t, err) + + l1Tx := &L1Tx{ + TokenID: 231, + FromBJJ: pk, + } + + encodeData, err := l1Tx.BytesCoordinatorTx(signature) + require.Nil(t, err) + assert.Equal(t, expected, hex.EncodeToString(encodeData)) +} diff --git a/common/l2tx.go b/common/l2tx.go index 82d7f35..dd125b7 100644 --- a/common/l2tx.go +++ b/common/l2tx.go @@ -118,3 +118,52 @@ func L2TxsToPoolL2Txs(txs []*L2Tx) []*PoolL2Tx { } return r } + +// Bytes encodes a L2Tx into []byte +func (tx *L2Tx) Bytes(nLevels int) ([]byte, error) { + fromIdxNumBytes := nLevels / 8 //nolint:gomnd + toIdxNumBytes := nLevels / 8 //nolint:gomnd + var b []byte + fromIdxBytes, err := tx.FromIdx.Bytes() + if err != nil { + return nil, err + } + b = append(b, fromIdxBytes[6-fromIdxNumBytes:]...) + toIdxBytes, err := tx.ToIdx.Bytes() + if err != nil { + return nil, err + } + b = append(b, toIdxBytes[6-toIdxNumBytes:]...) + amountFloat16, err := NewFloat16(tx.Amount) + if err != nil { + return nil, err + } + b = append(b, amountFloat16.Bytes()...) + b = append(b, byte(tx.Fee)) + return b[:], nil +} + +// L2TxFromBytes decodes a L1Tx from []byte +func L2TxFromBytes(b []byte, nLevels int) (*L2Tx, error) { + fromIdxNumByte := nLevels / 8 //nolint:gomnd + toIdxNumByte := fromIdxNumByte + nLevels/8 //nolint:gomnd + amountLenBytes := 2 + amountNumByte := toIdxNumByte + amountLenBytes + tx := &L2Tx{} + var err error + var paddedFromIdxBytes [6]byte + copy(paddedFromIdxBytes[6-len(b[0:fromIdxNumByte]):], b[0:fromIdxNumByte]) + tx.FromIdx, err = IdxFromBytes(paddedFromIdxBytes[:]) + if err != nil { + return nil, err + } + var paddedToIdxBytes [6]byte + copy(paddedToIdxBytes[6-len(b[fromIdxNumByte:toIdxNumByte]):6], b[fromIdxNumByte:toIdxNumByte]) + tx.ToIdx, err = IdxFromBytes(paddedToIdxBytes[:]) + if err != nil { + return nil, err + } + tx.Amount = Float16FromBytes(b[toIdxNumByte:amountNumByte]).BigInt() + tx.Fee = FeeSelector(b[amountNumByte]) + return tx, nil +} diff --git a/common/l2tx_test.go b/common/l2tx_test.go index 747a95a..af406ef 100644 --- a/common/l2tx_test.go +++ b/common/l2tx_test.go @@ -1,10 +1,12 @@ package common import ( + "encoding/hex" "math/big" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewL2Tx(t *testing.T) { @@ -18,3 +20,23 @@ func TestNewL2Tx(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "0x020000000156660000000090", l2Tx.TxID.String()) } + +func TestL2TxByteParsers(t *testing.T) { + amount := new(big.Int) + amount.SetString("79000000", 10) + l2Tx := &L2Tx{ + ToIdx: 256, + Amount: amount, + FromIdx: 257, + Fee: 201, + } + // Data from the compatibility test + expected := "00000101000001002b16c9" + encodedData, err := l2Tx.Bytes(32) + require.Nil(t, err) + assert.Equal(t, expected, hex.EncodeToString(encodedData)) + + decodedData, err := L2TxFromBytes(encodedData, 32) + require.Nil(t, err) + assert.Equal(t, l2Tx, decodedData) +}