From d592e22e0e6ad0ecb05288195c2d562ea7ddf6d7 Mon Sep 17 00:00:00 2001 From: Yves-Stan Le Cornec Date: Mon, 5 Dec 2022 14:17:52 +0100 Subject: [PATCH 1/4] Fix default value for validUntil field in signer_pallas.go When the `validUntil` field is not specified in a transaction, `mina` will use the greatest possible one. --- keys/signer_pallas.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/keys/signer_pallas.go b/keys/signer_pallas.go index 1e4bc141..2cecd781 100644 --- a/keys/signer_pallas.go +++ b/keys/signer_pallas.go @@ -18,6 +18,7 @@ import ( "encoding/json" "errors" "fmt" + "math" "strconv" "github.com/coinbase/kryptology/pkg/signatures/schnorr/mina" @@ -192,7 +193,7 @@ func constructTransaction(p *PayloadFields) (*mina.Transaction, error) { return nil, fmt.Errorf("failed to parse uint for nonce: %w", err) } - validUntil := uint64(0) + validUntil := uint64(math.MaxUint64) if p.ValidUntil != nil { validUntil, err = strconv.ParseUint(*p.ValidUntil, 10, 32) if err != nil { From eecbaf55cd1c06d211c470096ed3917dfc4ffa82 Mon Sep 17 00:00:00 2001 From: Yves-Stan Le Cornec Date: Mon, 5 Dec 2022 14:20:47 +0100 Subject: [PATCH 2/4] Handle stake delegation transaction in signer_pallas.go --- keys/errors.go | 2 +- keys/signer_pallas.go | 90 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 79 insertions(+), 13 deletions(-) diff --git a/keys/errors.go b/keys/errors.go index 29a0328b..1404ff8d 100644 --- a/keys/errors.go +++ b/keys/errors.go @@ -44,7 +44,7 @@ var ( ) ErrVerifyFailed = errors.New("verify: verify returned false") - ErrPaymentNotFound = errors.New("payment not found in signingPayload") + ErrEmptyTransaction = errors.New("payment or stakeDelegation field not found in signingPayload") ) // Err takes an error as an argument and returns diff --git a/keys/signer_pallas.go b/keys/signer_pallas.go index 2cecd781..36c9fc61 100644 --- a/keys/signer_pallas.go +++ b/keys/signer_pallas.go @@ -127,7 +127,7 @@ func (s *SignerPallas) Verify(signature *types.Signature) error { return nil } -type PayloadFields struct { +type PayloadFieldsPayment struct { To string `json:"to"` From string `json:"from"` Fee string `json:"fee"` @@ -137,33 +137,46 @@ type PayloadFields struct { Memo *string `json:"memo,omitempty"` } +type PayloadFieldsDelegation struct { + Delegator string `json:"delegator"` + NewDelegate string `json:"new_delegate"` + Fee string `json:"fee"` + Nonce string `json:"nonce"` + ValidUntil *string `json:"valid_until,omitempty"` + Memo *string `json:"memo,omitempty"` +} + +// Exactly one of the fields should be present. type SigningPayload struct { - Payment *PayloadFields `json:"payment"` + Payment *PayloadFieldsPayment `json:"payment"` + StakeDelegation *PayloadFieldsDelegation `json:"stakeDelegation"` } func ParseSigningPayload(rawPayload *types.SigningPayload) (*mina.Transaction, error) { var signingPayload SigningPayload - var payloadFields PayloadFields - err := json.Unmarshal(rawPayload.Bytes, &signingPayload) if err != nil { return nil, fmt.Errorf("failed to unmarshal payload: %w", err) } - + var transaction *mina.Transaction if signingPayload.Payment != nil { - payloadFields = *signingPayload.Payment + transaction, err = constructPaymentTransaction(signingPayload.Payment) + if err != nil { + return nil, fmt.Errorf("failed to construct payment transaction: %w", err) + } + } else if signingPayload.StakeDelegation != nil { + transaction, err = constructDelegationTransaction(signingPayload.StakeDelegation) + if err != nil { + return nil, fmt.Errorf("failed to construct delegation transaction: %w", err) + } } else { - return nil, ErrPaymentNotFound + return nil, ErrEmptyTransaction } - transaction, err := constructTransaction(&payloadFields) - if err != nil { - return nil, fmt.Errorf("failed to construct transaction: %w", err) - } return transaction, nil } -func constructTransaction(p *PayloadFields) (*mina.Transaction, error) { +func constructPaymentTransaction(p *PayloadFieldsPayment) (*mina.Transaction, error) { var fromPublicKey mina.PublicKey if err := fromPublicKey.ParseAddress(p.From); err != nil { return nil, fmt.Errorf("failed to parse \"from\" address: %w", err) @@ -225,3 +238,56 @@ func constructTransaction(p *PayloadFields) (*mina.Transaction, error) { return txn, nil } + +func constructDelegationTransaction(p *PayloadFieldsDelegation) (*mina.Transaction, error) { + var delegatorPublicKey mina.PublicKey + if err := delegatorPublicKey.ParseAddress(p.Delegator); err != nil { + return nil, fmt.Errorf("failed to parse \"delegator\" address: %w", err) + } + + var newDelegatePublicKey mina.PublicKey + if err := newDelegatePublicKey.ParseAddress(p.NewDelegate); err != nil { + return nil, fmt.Errorf("failed to parse \"new_delegate\" address: %w", err) + } + + fee, err := strconv.ParseUint(p.Fee, 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse uint for fee: %w", err) + } + + nonce, err := strconv.ParseUint(p.Nonce, 10, 32) + if err != nil { + return nil, fmt.Errorf("failed to parse uint for nonce: %w", err) + } + + validUntil := uint64(math.MaxUint64) + if p.ValidUntil != nil { + validUntil, err = strconv.ParseUint(*p.ValidUntil, 10, 32) + if err != nil { + return nil, fmt.Errorf("failed to parse uint for valid until memo: %w", err) + } + } + + memo := "" + if p.Memo != nil { + memo = *p.Memo + } + + txn := &mina.Transaction{ + Fee: fee, + FeeToken: 1, + FeePayerPk: &delegatorPublicKey, + Nonce: uint32(nonce), + ValidUntil: uint32(validUntil), + Memo: memo, + Tag: [3]bool{false, false, true}, + SourcePk: &delegatorPublicKey, + ReceiverPk: &newDelegatePublicKey, + TokenId: 1, + Amount: 0, + Locked: false, + NetworkId: mina.TestNet, + } + + return txn, nil +} From 4dc957ecd54202521a6b016db543ce08c446a5e6 Mon Sep 17 00:00:00 2001 From: Yves-Stan Le Cornec Date: Tue, 13 Dec 2022 11:38:20 +0100 Subject: [PATCH 3/4] Update pallas signer tests after adding support for stake delegation --- keys/signer_pallas_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/keys/signer_pallas_test.go b/keys/signer_pallas_test.go index 788ba811..4d2d60f4 100644 --- a/keys/signer_pallas_test.go +++ b/keys/signer_pallas_test.go @@ -78,7 +78,7 @@ func (s *ts) TestParseSigningPayload() { validUntil = "78" memo = "memo" signingPayloadWithPayment = SigningPayload{ - Payment: &PayloadFields{ + Payment: &PayloadFieldsPayment{ To: toAddress, From: fromAddress, Fee: "12", @@ -88,9 +88,9 @@ func (s *ts) TestParseSigningPayload() { Memo: &memo, }, } - signingPayloadWithNoPayment = SigningPayload{} + signingPayloadWithNoPaymentOrDelegation = SigningPayload{} signingPayloadWithPaymentAndNullValidUntilAndNullMemo = SigningPayload{ - Payment: &PayloadFields{ + Payment: &PayloadFieldsPayment{ To: toAddress, From: fromAddress, Fee: "12", @@ -101,7 +101,7 @@ func (s *ts) TestParseSigningPayload() { }, } signingPayloadWithPaymentAndInvalidFromPublicKey = SigningPayload{ - Payment: &PayloadFields{ + Payment: &PayloadFieldsPayment{ To: toAddress, From: "InvalidFrom", Fee: "12", @@ -111,7 +111,7 @@ func (s *ts) TestParseSigningPayload() { }, } signingPayloadWithPaymentAndInvalidToPublicKey = SigningPayload{ - Payment: &PayloadFields{ + Payment: &PayloadFieldsPayment{ To: "InvalidTo", From: fromAddress, Fee: "12", @@ -154,7 +154,7 @@ func (s *ts) TestParseSigningPayload() { s.Require().Equal(expectedTransactionBinary, transactionBinary) }) - s.Run("failed to parse when payment exists with null valid_until and memo", func() { + s.Run("successful to parse when payment exists with null valid_until and memo", func() { payloadBinary, _ := json.Marshal(signingPayloadWithPaymentAndNullValidUntilAndNullMemo) payload := &types.SigningPayload{ AccountIdentifier: &types.AccountIdentifier{Address: "test"}, @@ -172,7 +172,7 @@ func (s *ts) TestParseSigningPayload() { FeeToken: 1, FeePayerPk: fromPublicKey, Nonce: 56, - ValidUntil: 0, + ValidUntil: 4294967295, Memo: "", Tag: [3]bool{false, false, false}, SourcePk: fromPublicKey, @@ -188,7 +188,7 @@ func (s *ts) TestParseSigningPayload() { }) s.Run("failed to parse when payment or stake delegation does not exist", func() { - payloadBinary, _ := json.Marshal(signingPayloadWithNoPayment) + payloadBinary, _ := json.Marshal(signingPayloadWithNoPaymentOrDelegation) payload := &types.SigningPayload{ AccountIdentifier: &types.AccountIdentifier{Address: "test"}, Bytes: payloadBinary, From 0e6a446f945c5192fe32ada9b2f5975d85bea719 Mon Sep 17 00:00:00 2001 From: Yves-Stan Le Cornec Date: Tue, 13 Dec 2022 11:40:29 +0100 Subject: [PATCH 4/4] Add tests for stake delegation transactions in pallas signer --- keys/signer_pallas_test.go | 129 +++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/keys/signer_pallas_test.go b/keys/signer_pallas_test.go index 4d2d60f4..ca5a7373 100644 --- a/keys/signer_pallas_test.go +++ b/keys/signer_pallas_test.go @@ -120,6 +120,46 @@ func (s *ts) TestParseSigningPayload() { Memo: &memo, }, } + signingPayloadWithDelegation = SigningPayload{ + StakeDelegation: &PayloadFieldsDelegation{ + NewDelegate: toAddress, + Delegator: fromAddress, + Fee: "12", + Nonce: "56", + ValidUntil: &validUntil, + Memo: &memo, + }, + } + signingPayloadWithDelegationAndNullValidUntilAndNullMemo = SigningPayload{ + StakeDelegation: &PayloadFieldsDelegation{ + NewDelegate: toAddress, + Delegator: fromAddress, + Fee: "12", + Nonce: "56", + ValidUntil: nil, + Memo: nil, + }, + } + signingPayloadWithDelegationAndInvalidFromPublicKey = SigningPayload{ + StakeDelegation: &PayloadFieldsDelegation{ + NewDelegate: toAddress, + Delegator: "InvalidFrom", + Fee: "12", + Nonce: "56", + ValidUntil: &validUntil, + Memo: &memo, + }, + } + signingPayloadWithDelegationAndInvalidToPublicKey = SigningPayload{ + StakeDelegation: &PayloadFieldsDelegation{ + NewDelegate: "InvalidTo", + Delegator: fromAddress, + Fee: "12", + Nonce: "56", + ValidUntil: &validUntil, + Memo: &memo, + }, + } ) s.Run("successful to parse when payment exists", func() { @@ -234,6 +274,95 @@ func (s *ts) TestParseSigningPayload() { s.Require().Error(err) s.Require().Nil(transactionBinary) }) + + s.Run("successful to parse when stake delegation exists", func() { + payloadBinary, _ := json.Marshal(signingPayloadWithDelegation) + payload := &types.SigningPayload{ + AccountIdentifier: &types.AccountIdentifier{Address: "test"}, + Bytes: payloadBinary, + SignatureType: types.SchnorrPoseidon, + } + transaction, err := ParseSigningPayload(payload) + s.Require().NoError(err) + transactionBinary, err := transaction.MarshalBinary() + s.Require().NoError(err) + + expectedTransaction := mina.Transaction{ + Fee: 12, + FeeToken: 1, + FeePayerPk: fromPublicKey, + Nonce: 56, + ValidUntil: 78, + Memo: "memo", + Tag: [3]bool{false, false, true}, + SourcePk: fromPublicKey, + ReceiverPk: toPublicKey, + TokenId: 1, + Amount: 0, + Locked: false, + NetworkId: mina.TestNet, + } + expectedTransactionBinary, err := expectedTransaction.MarshalBinary() + s.Require().NoError(err) + s.Require().Equal(expectedTransactionBinary, transactionBinary) + }) + + s.Run("successful to parse when delegation exists with null valid_until and memo", func() { + payloadBinary, _ := json.Marshal(signingPayloadWithDelegationAndNullValidUntilAndNullMemo) + payload := &types.SigningPayload{ + AccountIdentifier: &types.AccountIdentifier{Address: "test"}, + Bytes: payloadBinary, + SignatureType: types.SchnorrPoseidon, + } + + transaction, err := ParseSigningPayload(payload) + s.Require().NoError(err) + transactionBinary, err := transaction.MarshalBinary() + s.Require().NoError(err) + + expectedTransaction := mina.Transaction{ + Fee: 12, + FeeToken: 1, + FeePayerPk: fromPublicKey, + Nonce: 56, + ValidUntil: 4294967295, + Memo: "", + Tag: [3]bool{false, false, true}, + SourcePk: fromPublicKey, + ReceiverPk: toPublicKey, + TokenId: 1, + Amount: 0, + Locked: false, + NetworkId: mina.TestNet, + } + expectedTransactionBinary, err := expectedTransaction.MarshalBinary() + s.Require().NoError(err) + s.Require().Equal(expectedTransactionBinary, transactionBinary) + }) + + s.Run("failed to parse when from public key in delegation is invalid", func() { + payloadBinary, _ := json.Marshal(signingPayloadWithDelegationAndInvalidFromPublicKey) + payload := &types.SigningPayload{ + AccountIdentifier: &types.AccountIdentifier{Address: "test"}, + Bytes: payloadBinary, + SignatureType: types.SchnorrPoseidon, + } + transactionBinary, err := ParseSigningPayload(payload) + s.Require().Error(err) + s.Require().Nil(transactionBinary) + }) + + s.Run("failed to parse when to public key in delegation is invalid", func() { + payloadBinary, _ := json.Marshal(signingPayloadWithDelegationAndInvalidToPublicKey) + payload := &types.SigningPayload{ + AccountIdentifier: &types.AccountIdentifier{Address: "test"}, + Bytes: payloadBinary, + SignatureType: types.SchnorrPoseidon, + } + transactionBinary, err := ParseSigningPayload(payload) + s.Require().Error(err) + s.Require().Nil(transactionBinary) + }) } func TestSignPallas(t *testing.T) {