diff --git a/common/pooll2tx.go b/common/pooll2tx.go index 721691c..1a139e4 100644 --- a/common/pooll2tx.go +++ b/common/pooll2tx.go @@ -9,6 +9,7 @@ import ( eth "github.com/ethereum/go-ethereum/common" "github.com/hermeznetwork/hermez-node/utils" "github.com/iden3/go-iden3-crypto/babyjub" + "github.com/iden3/go-iden3-crypto/poseidon" ) // Nonce represents the nonce value in a uint64, which has the method Bytes that returns a byte array of length 5 (40 bits). @@ -46,7 +47,7 @@ type PoolL2Tx struct { Fee FeeSelector `meddler:"fee"` Nonce Nonce `meddler:"nonce"` // effective 40 bits used State PoolL2TxState `meddler:"state"` - Signature babyjub.Signature `meddler:"signature"` // tx signature + Signature *babyjub.Signature `meddler:"signature"` // tx signature Timestamp time.Time `meddler:"timestamp,utctime"` // time when added to the tx pool // Stored in DB: optional fileds, may be uninitialized BatchNum BatchNum `meddler:"batch_num,zeroisnull"` // batchNum in which this tx was forged. Presence indicates "forged" state. @@ -145,6 +146,31 @@ func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) { return bi, nil } +// HashToSign returns the computed Poseidon hash from the *PoolL2Tx that will be signed by the sender. +func (tx *PoolL2Tx) HashToSign() (*big.Int, error) { + toCompressedData, err := tx.TxCompressedData() + if err != nil { + return nil, err + } + toEthAddr := EthAddrToBigInt(tx.ToEthAddr) + toBjjAy := tx.ToBJJ.Y + rqTxCompressedDataV2, err := tx.TxCompressedDataV2() + if err != nil { + return nil, err + } + + return poseidon.Hash([]*big.Int{toCompressedData, toEthAddr, toBjjAy, rqTxCompressedDataV2, EthAddrToBigInt(tx.RqToEthAddr), tx.RqToBJJ.Y}) +} + +// VerifySignature returns true if the signature verification is correct for the given PublicKey +func (tx *PoolL2Tx) VerifySignature(pk *babyjub.PublicKey) bool { + h, err := tx.HashToSign() + if err != nil { + return false + } + return pk.VerifyPoseidon(h, tx.Signature) +} + func (tx *PoolL2Tx) Tx() *Tx { return &Tx{ TxID: tx.TxID, diff --git a/common/pooll2tx_test.go b/common/pooll2tx_test.go index d555539..818bc43 100644 --- a/common/pooll2tx_test.go +++ b/common/pooll2tx_test.go @@ -5,6 +5,7 @@ import ( "math/big" "testing" + ethCommon "github.com/ethereum/go-ethereum/common" "github.com/iden3/go-iden3-crypto/babyjub" "github.com/stretchr/testify/assert" ) @@ -73,3 +74,49 @@ func TestTxCompressedData(t *testing.T) { assert.Equal(t, "10c000000000b0000000a0009000000000008000000000007", hex.EncodeToString(txCompressedData.Bytes())[1:]) } + +func TestHashToSign(t *testing.T) { + var sk babyjub.PrivateKey + _, err := hex.Decode(sk[:], []byte("0001020304050607080900010203040506070809000102030405060708090001")) + assert.Nil(t, err) + ethAddr := ethCommon.HexToAddress("0xc58d29fA6e86E4FAe04DDcEd660d45BCf3Cb2370") + + tx := PoolL2Tx{ + FromIdx: 2, + ToIdx: 3, + Amount: big.NewInt(4), + TokenID: 5, + Nonce: 6, + ToBJJ: sk.Public(), + RqToEthAddr: ethAddr, + RqToBJJ: sk.Public(), + } + toSign, err := tx.HashToSign() + assert.Nil(t, err) + assert.Equal(t, "14526446928649310956370997581245770629723313742905751117262272426489782809503", toSign.String()) +} + +func TestVerifyTxSignature(t *testing.T) { + var sk babyjub.PrivateKey + _, err := hex.Decode(sk[:], []byte("0001020304050607080900010203040506070809000102030405060708090001")) + assert.Nil(t, err) + ethAddr := ethCommon.HexToAddress("0xc58d29fA6e86E4FAe04DDcEd660d45BCf3Cb2370") + + tx := PoolL2Tx{ + FromIdx: 2, + ToIdx: 3, + Amount: big.NewInt(4), + TokenID: 5, + Nonce: 6, + ToBJJ: sk.Public(), + RqToEthAddr: ethAddr, + RqToBJJ: sk.Public(), + } + toSign, err := tx.HashToSign() + assert.Nil(t, err) + assert.Equal(t, "14526446928649310956370997581245770629723313742905751117262272426489782809503", toSign.String()) + + sig := sk.SignPoseidon(toSign) + tx.Signature = sig + assert.True(t, tx.VerifySignature(sk.Public())) +} diff --git a/common/utils.go b/common/utils.go index c27d78a..9dad220 100644 --- a/common/utils.go +++ b/common/utils.go @@ -1,5 +1,11 @@ package common +import ( + "math/big" + + ethCommon "github.com/ethereum/go-ethereum/common" +) + // SwapEndianness swaps the order of the bytes in the slice. func SwapEndianness(b []byte) []byte { o := make([]byte, len(b)) @@ -8,3 +14,8 @@ func SwapEndianness(b []byte) []byte { } return o } + +// EthAddrToBigInt returns a *big.Int from a given ethereum common.Address. +func EthAddrToBigInt(a ethCommon.Address) *big.Int { + return new(big.Int).SetBytes(a.Bytes()) +} diff --git a/db/l2db/l2db_test.go b/db/l2db/l2db_test.go index 582debd..3e3cda7 100644 --- a/db/l2db/l2db_test.go +++ b/db/l2db/l2db_test.go @@ -149,7 +149,7 @@ func genTxs(n int) []*common.PoolL2Tx { Fee: 99, Nonce: 28, State: state, - Signature: *privK.SignPoseidon(big.NewInt(674238462)), + Signature: privK.SignPoseidon(big.NewInt(674238462)), Timestamp: time.Now().UTC(), } if i%2 == 0 { // Optional parameters: rq