Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

Add transactions multi-signing. #76

Merged
merged 3 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ _testmain.go
*.coverprofile

.DS_Store

# IDE specific
.idea
*.iml
17 changes: 12 additions & 5 deletions data/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,20 @@ import (
)

func Raw(h Hashable) (Hash256, []byte, error) {
return raw(h, h.Prefix(), false)
return raw(h, h.Prefix(), nil, false)
}

func NodeId(h Hashable) (Hash256, error) {
nodeid, _, err := raw(h, h.Prefix(), false)
nodeid, _, err := raw(h, h.Prefix(), nil, false)
return nodeid, err
}

func SigningHash(s Signable) (Hash256, []byte, error) {
return raw(s, s.SigningPrefix(), true)
return raw(s, s.SigningPrefix(), nil, true)
}

func MultiSigningHash(s MultiSignable, account Account) (Hash256, []byte, error) {
return raw(s, s.MultiSigningPrefix(), account.Bytes(), true)
}

func Node(h Storer) (Hash256, []byte, error) {
Expand All @@ -30,14 +34,14 @@ func Node(h Storer) (Hash256, []byte, error) {
return zero256, nil, err
}
}
key, value, err := raw(h, h.Prefix(), true)
key, value, err := raw(h, h.Prefix(), nil, true)
if err != nil {
return zero256, nil, err
}
return key, append(header.Bytes(), value...), nil
}

func raw(value interface{}, prefix HashPrefix, ignoreSigningFields bool) (Hash256, []byte, error) {
func raw(value interface{}, prefix HashPrefix, suffix []byte, ignoreSigningFields bool) (Hash256, []byte, error) {
buf := new(bytes.Buffer)
hasher := sha512.New()
multi := io.MultiWriter(buf, hasher)
Expand All @@ -47,6 +51,9 @@ func raw(value interface{}, prefix HashPrefix, ignoreSigningFields bool) (Hash25
if err := writeRaw(multi, value, ignoreSigningFields); err != nil {
return zero256, nil, err
}
if err := write(hasher, suffix); err != nil {
return zero256, nil, err
}
var hash Hash256
copy(hash[:], hasher.Sum(nil))
return hash, buf.Bytes(), nil
Expand Down
17 changes: 9 additions & 8 deletions data/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ type LedgerNamespace uint16

const (
// Hash Prefixes
HP_TRANSACTION_ID HashPrefix = 0x54584E00 // 'TXN' transaction
HP_TRANSACTION_NODE HashPrefix = 0x534E4400 // 'SND' transaction plus metadata (probably should have been TND!)
HP_LEAF_NODE HashPrefix = 0x4D4C4E00 // 'MLN' account state
HP_INNER_NODE HashPrefix = 0x4D494E00 // 'MIN' inner node in tree
HP_LEDGER_MASTER HashPrefix = 0x4C575200 // 'LWR' ledger master data for signing (probably should have been LGR!)
HP_TRANSACTION_SIGN HashPrefix = 0x53545800 // 'STX' inner transaction to sign
HP_VALIDATION HashPrefix = 0x56414C00 // 'VAL' validation for signing
HP_PROPOSAL HashPrefix = 0x50525000 // 'PRP' proposal for signing
HP_TRANSACTION_ID HashPrefix = 0x54584E00 // 'TXN' transaction
HP_TRANSACTION_NODE HashPrefix = 0x534E4400 // 'SND' transaction plus metadata (probably should have been TND!)
HP_LEAF_NODE HashPrefix = 0x4D4C4E00 // 'MLN' account state
HP_INNER_NODE HashPrefix = 0x4D494E00 // 'MIN' inner node in tree
HP_LEDGER_MASTER HashPrefix = 0x4C575200 // 'LWR' ledger master data for signing (probably should have been LGR!)
HP_TRANSACTION_SIGN HashPrefix = 0x53545800 // 'STX' inner transaction to sign
HP_TRANSACTION_MILTISIGN HashPrefix = 0x534D5400 // 'SMT' inner transaction to multi-sign
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean to split to MILTI_SIGN?

Copy link
Contributor

@donovanhide donovanhide Sep 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/MILTISIGN/MULTISIGN/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, fixed.

HP_VALIDATION HashPrefix = 0x56414C00 // 'VAL' validation for signing
HP_PROPOSAL HashPrefix = 0x50525000 // 'PRP' proposal for signing

// Node Types
NT_UNKNOWN NodeType = 0
Expand Down
9 changes: 9 additions & 0 deletions data/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ type Signable interface {
GetSignature() *VariableLength
}

type MultiSignable interface {
Hashable
InitialiseForSigning()
MultiSigningPrefix() HashPrefix
GetPublicKey() *PublicKey
GetSignature() *VariableLength
SetSigners([]Signer)
}

type Router interface {
Hashable
SuppressionId() Hash256
Expand Down
8 changes: 6 additions & 2 deletions data/ledgerentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,16 @@ type Escrow struct {
DestinationNode *NodeIndex `json:",omitempty"`
}

type SignerEntry struct {
type SignerEntryItem struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you got an example tx or ledge entry from mainnet which demonstrates this structure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a ledger entry and that example transaction is a Payment and does not modify the ledger entry. You need to find a SignerListSet transaction which changes the ledger entry to confirm that this change is necessary. I suspect it isn't, but happy to be proved wrong :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, that was the proof for the Signer and SignerItem.
And here is the proof for the SignerListSet: https://testnet.xrpl.org/transactions/05AADAF31482E06241870D93D32FDC5E59414D7A9D60754D1C1DEC5ACFDF3860/raw

Account *Account `json:",omitempty"`
SignerWeight *uint16 `json:",omitempty"`
WalletLocator *Hash256 `json:",omitempty"`
}

type SignerEntry struct {
SignerEntry SignerEntryItem
}

type SignerList struct {
leBase
Flags *LedgerEntryFlag `json:",omitempty"`
Expand Down Expand Up @@ -233,7 +237,7 @@ func (s *Escrow) Affects(account Account) bool {
}
func (s *SignerList) Affects(account Account) bool {
for _, entry := range s.SignerEntries {
if entry.Account != nil && entry.Account.Equals(account) {
if entry.SignerEntry.Account != nil && entry.SignerEntry.Account.Equals(account) {
return true
}
}
Expand Down
12 changes: 7 additions & 5 deletions data/memo.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package data

type MemoItem struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the previous embedded struct, it was convenient if you read the transaction, but if you create a transaction and you need to define the memo you would need to copy the embedded struct definition every time you do it.

Copy link
Contributor

@donovanhide donovanhide Sep 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been doing this:

memos := make(data.Memos, 1)
memos[0].Memo.MemoData = data.VariableLength(quoteData)

Not particularly elegant, but does your change break this code?
Irrespective of this, it's a change being slipped into a PR for another problem, so best to break it out into a separate issue/PR.
After that happy to merge.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proposed changes might be a breaking change depending on the way the memo is initialized. If it's initialized in the way you do - not breaching, but if in the way where you provide the embedded struct, it's breaking.

That change is removed from that PR and will be moved to another.

Copy link
Contributor Author

@dzmitryhil dzmitryhil Sep 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR: #77

MemoType VariableLength
MemoData VariableLength
MemoFormat VariableLength
}

type Memo struct {
Memo struct {
MemoType VariableLength
MemoData VariableLength
MemoFormat VariableLength
}
Memo MemoItem
}

type Memos []Memo
1 change: 1 addition & 0 deletions data/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ var resultNames = map[TransactionResult]struct {
temCANNOT_PREAUTH_SELF: {"temCANNOT_PREAUTH_SELF", "Malformed: An account may not preauthorize itself."},
temSEQ_AND_TICKET: {"temSEQ_AND_TICKET", "Transaction contains a TicketSequence and a non-zero Sequence"},
temBAD_NFTOKEN_TRANSFER_FEE: {"temBAD_NFTOKEN_TRANSFER_FEE", "Malformed: The NFToken transfer fee must be between 1 and 5000, inclusive."},
temBAD_WEIGHT: {"temBAD_WEIGHT", "The SignerListSet transaction includes a SignerWeight that is invalid, for example a zero or negative value."},

terRETRY: {"terRETRY", "Retry transaction."},
terFUNDS_SPENT: {"terFUNDS_SPENT", "Can't set password, password set funds already spent."},
Expand Down
34 changes: 33 additions & 1 deletion data/signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func Sign(s Signable, key crypto.Key, sequence *uint32) error {
if err != nil {
return err
}
*s.GetSignature() = VariableLength(sig)
*s.GetSignature() = sig
hash, _, err = Raw(s)
if err != nil {
return err
Expand All @@ -29,3 +29,35 @@ func CheckSignature(s Signable) (bool, error) {
}
return crypto.Verify(s.GetPublicKey().Bytes(), hash.Bytes(), msg, s.GetSignature().Bytes())
}

func MultiSign(s MultiSignable, key crypto.Key, sequence *uint32, account Account) error {
s.InitialiseForSigning()
hash, msg, err := MultiSigningHash(s, account)
if err != nil {
return err
}
//
msg = append(s.MultiSigningPrefix().Bytes(), msg...)
msg = append(msg, account.Bytes()...)

sig, err := crypto.Sign(key.Private(sequence), hash.Bytes(), msg)
if err != nil {
return err
}
*s.GetSignature() = sig
// copy pub key only after the signing
copy(s.GetPublicKey().Bytes(), key.Public(sequence))

return nil
}

func SetSigners(s MultiSignable, signers ...Signer) error {
s.SetSigners(signers)

hash, _, err := Raw(s)
if err != nil {
return err
}
copy(s.GetHash().Bytes(), hash.Bytes())
return nil
}
12 changes: 9 additions & 3 deletions data/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ type TxBase struct {
Hash Hash256 `json:"hash"`
}

type Signer struct {
type SignerItem struct {
Account Account
TxnSignature *VariableLength
SigningPubKey *PublicKey
TxnSignature *VariableLength `json:",omitempty"`
SigningPubKey *PublicKey `json:",omitempty"`
}

type Signer struct {
Signer SignerItem
}

type Payment struct {
Expand Down Expand Up @@ -258,6 +262,8 @@ func (t *TxBase) Prefix() HashPrefix { return HP_TRANSACTION_ID
func (t *TxBase) GetPublicKey() *PublicKey { return t.SigningPubKey }
func (t *TxBase) GetSignature() *VariableLength { return t.TxnSignature }
func (t *TxBase) SigningPrefix() HashPrefix { return HP_TRANSACTION_SIGN }
func (t *TxBase) MultiSigningPrefix() HashPrefix { return HP_TRANSACTION_MILTISIGN }
func (t *TxBase) SetSigners(signers []Signer) { t.Signers = signers }
func (t *TxBase) PathSet() PathSet { return PathSet(nil) }
func (t *TxBase) GetHash() *Hash256 { return &t.Hash }

Expand Down
8 changes: 2 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ github.com/bits-and-blooms/bitset v1.2.1 h1:M+/hrU9xlMp7t4TyTDQW97d3tRPVuKFC6zBE
github.com/bits-and-blooms/bitset v1.2.1/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0 h1:MSskdM4/xJYcFzy0altH/C/xHopifpWzHUi1JeVI34Q=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
Expand Down Expand Up @@ -71,21 +71,17 @@ golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
9 changes: 5 additions & 4 deletions websockets/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ type Syncer interface {
}

type CommandError struct {
Name string `json:"error"`
Code int `json:"error_code"`
Message string `json:"error_message"`
Name string `json:"error"`
Code int `json:"error_code"`
Message string `json:"error_message"`
Exception string `json:"error_exception"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a new response field from somewhere? Docs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I've not found it in the spec. It was found in the responses from the testnet.

You can try that code snippet (with updated code):

func main() {
	host := "wss://s.altnet.rippletest.net:51233/"
	remote, err := websockets.NewRemote(host)
	panicIfErr(err)
	amount, err := data.NewAmount("100000") // 0.1 XRP tokens
	tx := data.Payment{
		Amount: *amount,
		TxBase: data.TxBase{
			TransactionType: data.PAYMENT,
		},
	}
	// submit the transaction
	_, err = remote.Submit(&tx)
	fmt.Printf("%s\n", err)
}

func panicIfErr(err error) {
	if err != nil {
		panic(err)
	}
}

The output:

invalidTransaction 0  gFID: uncommon type out of range 0

Raw response.

{"error":"invalidTransaction","error_exception":"gFID: uncommon type out of range 0","id":1,"request":{"command":"submit","id":1,"tx_blob":"12000024000000006140000000000186A06880000000000000008114000000000000000000000000000000000000000083140000000000000000000000000000000000000000"},"status":"error","type":"response"}

The output if you run it from the master:

invalidTransaction 0 

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in a separate PR too. Just in case anyone needs to check the commit history to easily see where changes have been added.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed from that PR. And will be in a different PR.

}

type Command struct {
Expand Down Expand Up @@ -48,7 +49,7 @@ func (c *Command) IncrementId() {
}

func (e *CommandError) Error() string {
return fmt.Sprintf("%s %d %s", e.Name, e.Code, e.Message)
return fmt.Sprintf("%s %d %s %s", e.Name, e.Code, e.Message, e.Exception)
}

func newCommand(command string) *Command {
Expand Down