Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pallas signer stake delegation #464

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion keys/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
93 changes: 80 additions & 13 deletions keys/signer_pallas.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"encoding/json"
"errors"
"fmt"
"math"
"strconv"

"github.com/coinbase/kryptology/pkg/signatures/schnorr/mina"
Expand Down Expand Up @@ -126,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"`
Expand All @@ -136,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)
Expand Down Expand Up @@ -192,7 +206,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 {
Expand Down Expand Up @@ -224,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
}
145 changes: 137 additions & 8 deletions keys/signer_pallas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (s *ts) TestParseSigningPayload() {
validUntil = "78"
memo = "memo"
signingPayloadWithPayment = SigningPayload{
Payment: &PayloadFields{
Payment: &PayloadFieldsPayment{
To: toAddress,
From: fromAddress,
Fee: "12",
Expand All @@ -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",
Expand All @@ -101,7 +101,7 @@ func (s *ts) TestParseSigningPayload() {
},
}
signingPayloadWithPaymentAndInvalidFromPublicKey = SigningPayload{
Payment: &PayloadFields{
Payment: &PayloadFieldsPayment{
To: toAddress,
From: "InvalidFrom",
Fee: "12",
Expand All @@ -111,7 +111,7 @@ func (s *ts) TestParseSigningPayload() {
},
}
signingPayloadWithPaymentAndInvalidToPublicKey = SigningPayload{
Payment: &PayloadFields{
Payment: &PayloadFieldsPayment{
To: "InvalidTo",
From: fromAddress,
Fee: "12",
Expand All @@ -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() {
Expand Down Expand Up @@ -154,7 +194,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"},
Expand All @@ -172,7 +212,7 @@ func (s *ts) TestParseSigningPayload() {
FeeToken: 1,
FeePayerPk: fromPublicKey,
Nonce: 56,
ValidUntil: 0,
ValidUntil: 4294967295,
Memo: "",
Tag: [3]bool{false, false, false},
SourcePk: fromPublicKey,
Expand All @@ -188,7 +228,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,
Expand Down Expand Up @@ -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) {
Expand Down