diff --git a/.gitignore b/.gitignore index d8ce91b..07dc441 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,7 @@ _testmain.go *.coverprofile .DS_Store + +# IDE specific +.idea +*.iml diff --git a/data/encoder.go b/data/encoder.go index bca11fd..b7e90fc 100644 --- a/data/encoder.go +++ b/data/encoder.go @@ -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) { @@ -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) @@ -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 diff --git a/data/format.go b/data/format.go index 8e9ba5f..9f48124 100644 --- a/data/format.go +++ b/data/format.go @@ -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_MULTISIGN HashPrefix = 0x534D5400 // 'SMT' inner transaction to multi-sign + HP_VALIDATION HashPrefix = 0x56414C00 // 'VAL' validation for signing + HP_PROPOSAL HashPrefix = 0x50525000 // 'PRP' proposal for signing // Node Types NT_UNKNOWN NodeType = 0 diff --git a/data/interface.go b/data/interface.go index 98561cb..3d85545 100644 --- a/data/interface.go +++ b/data/interface.go @@ -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 diff --git a/data/ledgerentry.go b/data/ledgerentry.go index 0343a27..1fc9373 100644 --- a/data/ledgerentry.go +++ b/data/ledgerentry.go @@ -133,12 +133,16 @@ type Escrow struct { DestinationNode *NodeIndex `json:",omitempty"` } -type SignerEntry struct { +type SignerEntryItem struct { 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"` @@ -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 } } diff --git a/data/result.go b/data/result.go index 228995c..37a6de6 100644 --- a/data/result.go +++ b/data/result.go @@ -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."}, diff --git a/data/signing.go b/data/signing.go index e63dd02..33c5726 100644 --- a/data/signing.go +++ b/data/signing.go @@ -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 @@ -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 +} diff --git a/data/transaction.go b/data/transaction.go index ab8b93f..ca9fdf5 100644 --- a/data/transaction.go +++ b/data/transaction.go @@ -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 { @@ -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_MULTISIGN } +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 } diff --git a/go.sum b/go.sum index 51e88ff..6590d55 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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=