From fec324666a17da914d23a3063866ada7835c8caf Mon Sep 17 00:00:00 2001 From: Mostafa Date: Sat, 24 Aug 2024 02:20:29 +0800 Subject: [PATCH 1/2] fix(tx): decode BLS validator signature --- crypto/address.go | 3 +- crypto/address_test.go | 98 +++++++++++++++++++--------- crypto/ed25519/private_key_test.go | 4 ++ types/tx/errors.go | 9 ++- types/tx/tx.go | 100 ++++++++++++++++++----------- types/tx/tx_test.go | 99 +++++++++++++++++++++++++--- util/testsuite/testsuite.go | 11 ++-- 7 files changed, 243 insertions(+), 81 deletions(-) diff --git a/crypto/address.go b/crypto/address.go index 6806cf157..fd26b6e46 100644 --- a/crypto/address.go +++ b/crypto/address.go @@ -164,7 +164,8 @@ func (addr Address) IsTreasuryAddress() bool { func (addr Address) IsAccountAddress() bool { return addr.Type() == AddressTypeTreasury || - addr.Type() == AddressTypeBLSAccount || addr.Type() == AddressTypeEd25519Account + addr.Type() == AddressTypeBLSAccount || + addr.Type() == AddressTypeEd25519Account } func (addr Address) IsValidatorAddress() bool { diff --git a/crypto/address_test.go b/crypto/address_test.go index 6bc28b3af..21733f8a0 100644 --- a/crypto/address_test.go +++ b/crypto/address_test.go @@ -3,7 +3,6 @@ package crypto_test import ( "bytes" "encoding/hex" - "fmt" "io" "strings" "testing" @@ -15,109 +14,145 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAddressKeyType(t *testing.T) { - ts := testsuite.NewTestSuite(t) - - pub, _ := ts.RandBLSKeyPair() - accAddr := pub.AccountAddress() - valAddr := pub.ValidatorAddress() +func TestTreasuryAddressType(t *testing.T) { treasury := crypto.TreasuryAddress - assert.True(t, accAddr.IsAccountAddress()) - assert.False(t, accAddr.IsValidatorAddress()) - assert.False(t, accAddr.IsTreasuryAddress()) - assert.False(t, valAddr.IsAccountAddress()) - assert.True(t, valAddr.IsValidatorAddress()) assert.False(t, treasury.IsValidatorAddress()) assert.True(t, treasury.IsAccountAddress()) assert.True(t, treasury.IsTreasuryAddress()) - assert.NotEqual(t, accAddr, valAddr) } -func TestString(t *testing.T) { - ts := testsuite.NewTestSuite(t) +func TestAddressType(t *testing.T) { + tests := []struct { + address string + account bool + validator bool + }{ + {address: "pc1p0hrct7eflrpw4ccrttxzs4qud2axex4dcdzdfr", account: false, validator: true}, + {address: "pc1zzqkzzu4vyddss052as6c37qrdcfptegquw826x", account: true, validator: false}, + {address: "pc1rspm7ps49gar9ft5g0tkl6lhxs8ygeakq87quh3", account: true, validator: false}, + } + + for _, test := range tests { + addr, _ := crypto.AddressFromString(test.address) + + assert.Equal(t, test.account, addr.IsAccountAddress()) + assert.Equal(t, test.validator, addr.IsValidatorAddress()) + } +} - a, _ := crypto.AddressFromString("pc1p0hrct7eflrpw4ccrttxzs4qud2axex4dcdzdfr") - fmt.Println(a.String()) +func TestShortString(t *testing.T) { + ts := testsuite.NewTestSuite(t) addr1 := ts.RandAccAddress() assert.Contains(t, addr1.String(), addr1.ShortString()) } -func TestToString(t *testing.T) { +func TestFromString(t *testing.T) { tests := []struct { - encoded string - err error - result *crypto.Address + encoded string + err error + bytes []byte + addrType crypto.AddressType }{ { "000000000000000000000000000000000000000000", nil, - &crypto.Address{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + crypto.AddressTypeTreasury, }, { "", bech32m.InvalidLengthError(0), nil, + 0, }, { "not_proper_encoded", bech32m.InvalidSeparatorIndexError(-1), nil, + 0, }, { "pc1ioiooi", bech32m.NonCharsetCharError(105), nil, + 0, }, { "pc19p72rf", bech32m.InvalidLengthError(0), nil, + 0, }, { "qc1z0hrct7eflrpw4ccrttxzs4qud2axex4dh8zz75", crypto.InvalidHRPError("qc"), nil, + 0, }, { "pc1p0hrct7eflrpw4ccrttxzs4qud2axex4dg8xaf5", bech32m.InvalidChecksumError{Expected: "cdzdfr", Actual: "g8xaf5"}, nil, + 0, }, { "pc1p0hrct7eflrpw4ccrttxzs4qud2axexs2dhdk8", crypto.InvalidLengthError(20), nil, + 0, }, { "pc1y0hrct7eflrpw4ccrttxzs4qud2axex4dksmred", crypto.InvalidAddressTypeError(4), nil, + 0, }, { "PC1P0HRCT7EFLRPW4CCRTTXZS4QUD2AXEX4DCDZDFR", // UPPERCASE nil, - &crypto.Address{ - 0x1, 0x7d, 0xc7, 0x85, 0xfb, 0x29, 0xf8, 0xc2, 0xea, 0xe3, - 0x3, 0x5a, 0xcc, 0x28, 0x54, 0x1c, 0x6a, 0xba, 0x6c, 0x9a, 0xad, + []byte{ + 0x01, 0x7d, 0xc7, 0x85, 0xfb, 0x29, 0xf8, 0xc2, 0xea, 0xe3, + 0x03, 0x5a, 0xcc, 0x28, 0x54, 0x1c, 0x6a, 0xba, 0x6c, 0x9a, 0xad, }, + crypto.AddressTypeValidator, }, { "pc1p0hrct7eflrpw4ccrttxzs4qud2axex4dcdzdfr", nil, - &crypto.Address{ - 0x1, 0x7d, 0xc7, 0x85, 0xfb, 0x29, 0xf8, 0xc2, 0xea, 0xe3, - 0x3, 0x5a, 0xcc, 0x28, 0x54, 0x1c, 0x6a, 0xba, 0x6c, 0x9a, 0xad, + []byte{ + 0x01, 0x7d, 0xc7, 0x85, 0xfb, 0x29, 0xf8, 0xc2, 0xea, 0xe3, + 0x03, 0x5a, 0xcc, 0x28, 0x54, 0x1c, 0x6a, 0xba, 0x6c, 0x9a, 0xad, + }, + crypto.AddressTypeValidator, + }, + { + "pc1zzqkzzu4vyddss052as6c37qrdcfptegquw826x", + nil, + []byte{ + 0x02, 0x10, 0x2c, 0x21, 0x72, 0xac, 0x23, 0x5b, 0x08, 0x3e, 0x8a, + 0xec, 0x35, 0x88, 0xf8, 0x03, 0x6e, 0x12, 0x15, 0xe5, 0x00, }, + crypto.AddressTypeBLSAccount, + }, + { + "pc1rspm7ps49gar9ft5g0tkl6lhxs8ygeakq87quh3", + nil, + []byte{ + 0x03, 0x80, 0x77, 0xe0, 0xc2, 0xa5, 0x47, 0x46, 0x54, 0xae, + 0x88, 0x7a, 0xed, 0xfd, 0x7e, 0xe6, 0x81, 0xc8, 0x8c, 0xf6, 0xc0, + }, + crypto.AddressTypeEd25519Account, }, } for no, test := range tests { addr, err := crypto.AddressFromString(test.encoded) if test.err == nil { assert.NoError(t, err, "test %v: unexpected error", no) - assert.Equal(t, *test.result, addr, "test %v: invalid result", no) + assert.Equal(t, test.bytes, addr.Bytes(), "test %v: invalid result", no) assert.Equal(t, strings.ToLower(test.encoded), addr.String(), "test %v: invalid encode", no) + assert.Equal(t, test.addrType, addr.Type(), "test %v: invalid type", no) } else { assert.ErrorIs(t, err, test.err, "test %v: invalid error", no) } @@ -165,6 +200,11 @@ func TestAddressEncoding(t *testing.T) { "02000102030405060708090a0b0c0d0e0f00010203", nil, }, + { + 21, + "03000102030405060708090a0b0c0d0e0f00010203", + nil, + }, } for no, test := range tests { data, _ := hex.DecodeString(test.hex) diff --git a/crypto/ed25519/private_key_test.go b/crypto/ed25519/private_key_test.go index d8a4fac2f..29da3fc3e 100644 --- a/crypto/ed25519/private_key_test.go +++ b/crypto/ed25519/private_key_test.go @@ -1,6 +1,7 @@ package ed25519_test import ( + "fmt" "strings" "testing" @@ -19,6 +20,9 @@ func TestPrivateKeyEqualsTo(t *testing.T) { assert.True(t, prv1.EqualsTo(prv1)) assert.False(t, prv1.EqualsTo(prv2)) assert.False(t, prv1.EqualsTo(prv3)) + + fmt.Printf("%x\n", prv1.PublicKey().Bytes()) + fmt.Println(prv1.Sign([]byte{1}).String()) } func TestPrivateKeyFromString(t *testing.T) { diff --git a/types/tx/errors.go b/types/tx/errors.go index c5e195efb..a05263e11 100644 --- a/types/tx/errors.go +++ b/types/tx/errors.go @@ -1,6 +1,13 @@ package tx -import "github.com/pactus-project/pactus/types/tx/payload" +import ( + "errors" + + "github.com/pactus-project/pactus/types/tx/payload" +) + +// ErrInvalidSigner is returned when the signer address is not valid. +var ErrInvalidSigner = errors.New("invalid signer address") // BasicCheckError is returned when the basic check on the transaction fails. type BasicCheckError struct { diff --git a/types/tx/tx.go b/types/tx/tx.go index 4ad30874a..1e5f87456 100644 --- a/types/tx/tx.go +++ b/types/tx/tx.go @@ -251,18 +251,37 @@ func (tx *Tx) UnmarshalCBOR(bs []byte) error { // SerializeSize returns the number of bytes it would take to serialize the transaction. func (tx *Tx) SerializeSize() int { - n := 3 + // one byte version, flag, payload type - 4 + // for tx.LockTime + n := 7 + // flag (1) + version (1) + payload type (1) + lock_time (4) encoding.VarIntSerializeSize(uint64(tx.Fee())) + encoding.VarStringSerializeSize(tx.Memo()) if tx.Payload() != nil { n += tx.Payload().SerializeSize() } if tx.data.Signature != nil { - n += bls.SignatureSize + switch tx.data.Payload.Signer().Type() { + case crypto.AddressTypeValidator, + crypto.AddressTypeBLSAccount: + n += bls.SignatureSize + + case crypto.AddressTypeEd25519Account: + n += ed25519.SignatureSize + + case crypto.AddressTypeTreasury: + n += 0 + } } if tx.data.PublicKey != nil { - n += bls.PublicKeySize + switch tx.data.Payload.Signer().Type() { + case crypto.AddressTypeValidator, + crypto.AddressTypeBLSAccount: + n += bls.PublicKeySize + + case crypto.AddressTypeEd25519Account: + n += ed25519.PublicKeySize + + case crypto.AddressTypeTreasury: + n += 0 + } } return n @@ -365,59 +384,68 @@ func (tx *Tx) Decode(r io.Reader) error { return nil } - err = tx.setSignatureWithSignerType(r) + // It is a signed transaction, Decode signatory. + sig, err := tx.decodeSignature(r) if err != nil { return err } + tx.data.Signature = sig if !tx.IsPublicKeyStriped() { - switch tx.data.Payload.Signer().Type() { - case crypto.AddressTypeBLSAccount: - pub := new(bls.PublicKey) - err = pub.Decode(r) - if err != nil { - return err - } - tx.data.PublicKey = pub - case crypto.AddressTypeEd25519Account: - pub := new(ed25519.PublicKey) - err = pub.Decode(r) - if err != nil { - return err - } - tx.data.PublicKey = pub - case crypto.AddressTypeValidator: - case crypto.AddressTypeTreasury: - return fmt.Errorf("payload signer type %v not supported", tx.Payload().Signer().Type()) + pub, err := tx.decodePublicKey(r) + if err != nil { + return err } + tx.data.PublicKey = pub } return nil } -func (tx *Tx) setSignatureWithSignerType(r io.Reader) error { +func (tx *Tx) decodeSignature(r io.Reader) (crypto.Signature, error) { switch tx.data.Payload.Signer().Type() { - case crypto.AddressTypeBLSAccount: + case crypto.AddressTypeValidator, + crypto.AddressTypeBLSAccount: sig := new(bls.Signature) err := sig.Decode(r) - if err != nil { - return err - } - tx.data.Signature = sig + + return sig, err case crypto.AddressTypeEd25519Account: sig := new(ed25519.Signature) err := sig.Decode(r) - if err != nil { - return err - } - tx.data.Signature = sig - case crypto.AddressTypeValidator: + + return sig, err + case crypto.AddressTypeTreasury: - return fmt.Errorf("payload signer type %v not supported", tx.Payload().Signer().Type()) + return nil, ErrInvalidSigner + + default: + return nil, ErrInvalidSigner } +} - return nil +func (tx *Tx) decodePublicKey(r io.Reader) (crypto.PublicKey, error) { + switch tx.data.Payload.Signer().Type() { + case crypto.AddressTypeValidator, + crypto.AddressTypeBLSAccount: + pub := new(bls.PublicKey) + err := pub.Decode(r) + + return pub, err + + case crypto.AddressTypeEd25519Account: + pub := new(ed25519.PublicKey) + err := pub.Decode(r) + + return pub, err + + case crypto.AddressTypeTreasury: + return nil, ErrInvalidSigner + + default: + return nil, ErrInvalidSigner + } } func (tx *Tx) String() string { diff --git a/types/tx/tx_test.go b/types/tx/tx_test.go index 6dd3b4a26..b3893b775 100644 --- a/types/tx/tx_test.go +++ b/types/tx/tx_test.go @@ -7,8 +7,11 @@ import ( "testing" "github.com/fxamacker/cbor/v2" + "github.com/pactus-project/pactus/crypto" "github.com/pactus-project/pactus/crypto/bls" + "github.com/pactus-project/pactus/crypto/ed25519" "github.com/pactus-project/pactus/crypto/hash" + "github.com/pactus-project/pactus/types/amount" "github.com/pactus-project/pactus/types/tx" "github.com/pactus-project/pactus/types/tx/payload" "github.com/pactus-project/pactus/util" @@ -38,11 +41,6 @@ func TestEncodingTx(t *testing.T) { trx3 := ts.GenerateTestUnbondTx() trx4 := ts.GenerateTestWithdrawTx() trx5 := ts.GenerateTestSortitionTx() - assert.True(t, trx1.IsTransferTx()) - assert.True(t, trx2.IsBondTx()) - assert.True(t, trx3.IsUnbondTx()) - assert.True(t, trx4.IsWithdrawTx()) - assert.True(t, trx5.IsSortitionTx()) tests := []*tx.Tx{trx1, trx2, trx3, trx4, trx5} for _, trx := range tests { @@ -113,7 +111,7 @@ func TestBasicCheck(t *testing.T) { t.Run("Invalid payload, Should returns error", func(t *testing.T) { invAddr := ts.RandAccAddress() - invAddr[0] = 3 + invAddr[0] = 4 trx := tx.NewTransferTx(ts.RandHeight(), ts.RandAccAddress(), invAddr, 1e9, ts.RandAmount()) err := trx.BasicCheck() @@ -337,22 +335,22 @@ func TestInvalidSignature(t *testing.T) { }) } -func TestSignBytes(t *testing.T) { +func TestSignBytesBLS(t *testing.T) { d, _ := hex.DecodeString( "00" + // Flags "01" + // Version "01020304" + // LockTime "01" + // Fee - "00" + // Memo + "0474657374" + // Memo "01" + // PayloadType "013333333333333333333333333333333333333333" + // Sender "012222222222222222222222222222222222222222" + // Receiver - "01" + // Amount + "02" + // Amount "b53d79e156e9417e010fa21f2b2a96bee6be46fcd233295d2f697cdb9e782b6112ac01c80d0d9d64c2320664c77fa2a6" + // Signature "8d82fa4fcac04a3b565267685e90db1b01420285d2f8295683c138c092c209479983ba1591370778846681b7b558e061" + // PublicKey "1776208c0718006311c84b4a113335c70d1f5c7c5dd93a5625c4af51c48847abd0b590c055306162d2a03ca1cbf7bcc1") - h, _ := hash.FromString("1a8cedbb2ffce29df63210f112afb1c0295b27e2162323bfc774068f0573388e") + h, _ := hash.FromString("084f69979757cecb58d0a37bdd10eebee912ed29f923adb93f09d6bde2b94d5f") trx, err := tx.FromBytes(d) assert.NoError(t, err) assert.Equal(t, len(d), trx.SerializeSize()) @@ -362,6 +360,39 @@ func TestSignBytes(t *testing.T) { assert.Equal(t, h, trx.ID()) assert.Equal(t, hash.CalcHash(sb), trx.ID()) assert.Equal(t, uint32(0x04030201), trx.LockTime()) + assert.Equal(t, "test", trx.Memo()) + assert.Equal(t, amount.Amount(1), trx.Fee()) + assert.Equal(t, amount.Amount(2), trx.Payload().Value()) +} + +func TestSignBytesEd25519(t *testing.T) { + d, _ := hex.DecodeString( + "00" + // Flags + "01" + // Version + "01020304" + // LockTime + "01" + // Fee + "0474657374" + // Memo + "01" + // PayloadType + "033333333333333333333333333333333333333333" + // Sender + "032222222222222222222222222222222222222222" + // Receiver + "02" + // Amount + "4ed287f380291202f36a6a7516d602f1a6eaf789d092dd4050c0907ce79f49db" + // Signature + "6e70c21c82411803815db09713eab426297210a6793658d6bd9ed116ef2c0aac" + // PublicKey + "0aacf0da469a4a47dfb968a321ad7d6b919fdc37d2d2834c69cef90692730902") + + h, _ := hash.FromString("e5a0e1fb4ee6f26a867dd3c091fc9fdfcbd25a5caff8cf13a4485a716501150d") + trx, err := tx.FromBytes(d) + assert.NoError(t, err) + assert.Equal(t, len(d), trx.SerializeSize()) + + sb := d[1 : len(d)-ed25519.PublicKeySize-ed25519.SignatureSize] + assert.Equal(t, sb, trx.SignBytes()) + assert.Equal(t, h, trx.ID()) + assert.Equal(t, hash.CalcHash(sb), trx.ID()) + assert.Equal(t, uint32(0x04030201), trx.LockTime()) + assert.Equal(t, "test", trx.Memo()) + assert.Equal(t, amount.Amount(1), trx.Fee()) + assert.Equal(t, amount.Amount(2), trx.Payload().Value()) } func TestStripPublicKey(t *testing.T) { @@ -401,3 +432,51 @@ func TestFlagNotSigned(t *testing.T) { trx.SetSignature(nil) assert.False(t, trx.IsSigned(), "FlagNotSigned should not be set when the signature is set to nil") } + +func TestInvalidSignerSignature(t *testing.T) { + ts := testsuite.NewTestSuite(t) + + trx := tx.NewTransferTx(ts.RandHeight(), crypto.TreasuryAddress, ts.RandAccAddress(), + ts.RandAmount(), ts.RandAmount()) + trx.SetSignature(ts.RandBLSSignature()) + + bytes, _ := trx.Bytes() + _, err := tx.FromBytes(bytes) + assert.ErrorIs(t, err, tx.ErrInvalidSigner) +} + +func TestInvalidSignerPublicKey(t *testing.T) { + ts := testsuite.NewTestSuite(t) + + trx := tx.NewTransferTx(ts.RandHeight(), crypto.TreasuryAddress, ts.RandAccAddress(), + ts.RandAmount(), ts.RandAmount()) + pub, _ := ts.RandBLSKeyPair() + trx.SetSignature(ts.RandBLSSignature()) + trx.SetPublicKey(pub) + + bytes, _ := trx.Bytes() + _, err := tx.FromBytes(bytes) + assert.ErrorIs(t, err, tx.ErrInvalidSigner) +} + +func TestIsFreeTx(t *testing.T) { + ts := testsuite.NewTestSuite(t) + + trx1 := ts.GenerateTestTransferTx() + trx2 := ts.GenerateTestBondTx() + trx3 := ts.GenerateTestUnbondTx() + trx4 := ts.GenerateTestWithdrawTx() + trx5 := ts.GenerateTestSortitionTx() + + assert.True(t, trx1.IsTransferTx()) + assert.True(t, trx2.IsBondTx()) + assert.True(t, trx3.IsUnbondTx()) + assert.True(t, trx4.IsWithdrawTx()) + assert.True(t, trx5.IsSortitionTx()) + + assert.False(t, trx1.IsFreeTx()) + assert.False(t, trx2.IsFreeTx()) + assert.True(t, trx3.IsFreeTx()) + assert.False(t, trx4.IsFreeTx()) + assert.True(t, trx5.IsFreeTx()) +} diff --git a/util/testsuite/testsuite.go b/util/testsuite/testsuite.go index 3ab7d7bfc..1b8237161 100644 --- a/util/testsuite/testsuite.go +++ b/util/testsuite/testsuite.go @@ -280,9 +280,12 @@ func (ts *TestSuite) RandHash() hash.Hash { // RandAccAddress generates a random account address for testing purposes. func (ts *TestSuite) RandAccAddress() crypto.Address { - addr := crypto.NewAddress(crypto.AddressTypeBLSAccount, ts.RandBytes(20)) + isBLSAddress := ts.RandBool() + if isBLSAddress { + return crypto.NewAddress(crypto.AddressTypeBLSAccount, ts.RandBytes(20)) + } - return addr + return crypto.NewAddress(crypto.AddressTypeEd25519Account, ts.RandBytes(20)) } // RandValAddress generates a random validator address for testing purposes. @@ -320,11 +323,11 @@ func (ts *TestSuite) RandPeerID() peer.ID { // GenerateTestAccount generates an account for testing purposes. func (ts *TestSuite) GenerateTestAccount(number int32) (*account.Account, crypto.Address) { - _, prv := ts.RandBLSKeyPair() + addr := ts.RandAccAddress() acc := account.NewAccount(number) acc.AddToBalance(ts.RandAmount()) - return acc, prv.PublicKeyNative().AccountAddress() + return acc, addr } // GenerateTestValidator generates a validator for testing purposes. From 02a7265abdac00dac428a5f40163bf0694c8cacc Mon Sep 17 00:00:00 2001 From: Mostafa Date: Sat, 24 Aug 2024 02:24:35 +0800 Subject: [PATCH 2/2] chore: remove unnecessary printf --- crypto/ed25519/private_key_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crypto/ed25519/private_key_test.go b/crypto/ed25519/private_key_test.go index 29da3fc3e..d8a4fac2f 100644 --- a/crypto/ed25519/private_key_test.go +++ b/crypto/ed25519/private_key_test.go @@ -1,7 +1,6 @@ package ed25519_test import ( - "fmt" "strings" "testing" @@ -20,9 +19,6 @@ func TestPrivateKeyEqualsTo(t *testing.T) { assert.True(t, prv1.EqualsTo(prv1)) assert.False(t, prv1.EqualsTo(prv2)) assert.False(t, prv1.EqualsTo(prv3)) - - fmt.Printf("%x\n", prv1.PublicKey().Bytes()) - fmt.Println(prv1.Sign([]byte{1}).String()) } func TestPrivateKeyFromString(t *testing.T) {