7 log "github.com/sirupsen/logrus"
9 "github.com/vapor/crypto/ed25519/chainkd"
10 "github.com/vapor/crypto/sha3pool"
11 chainjson "github.com/vapor/encoding/json"
12 "github.com/vapor/errors"
13 "github.com/vapor/protocol/vm"
17 // SignatureWitness is a sign struct
18 SignatureWitness struct {
19 // Quorum is the number of signatures required.
20 Quorum int `json:"quorum"`
22 // Keys are the identities of the keys to sign with.
23 Keys []keyID `json:"keys"`
25 // Program is the predicate part of the signature program, whose hash is what gets
26 // signed. If empty, it is computed during Sign from the outputs
27 // and the current input of the transaction.
28 Program chainjson.HexBytes `json:"program"`
30 // Sigs are signatures of Program made from each of the Keys
32 Sigs []chainjson.HexBytes `json:"signatures"`
36 XPub chainkd.XPub `json:"xpub"`
37 DerivationPath []chainjson.HexBytes `json:"derivation_path"`
41 // ErrEmptyProgram is a type of error
42 var ErrEmptyProgram = errors.New("empty signature program")
44 // Sign populates sw.Sigs with as many signatures of the predicate in
45 // sw.Program as it can from the overlapping set of keys in sw.Keys.
47 // If sw.Program is empty, it is populated with an _inferred_ predicate:
48 // a program committing to aspects of the current
49 // transaction. Specifically, the program commits to:
50 // - the mintime and maxtime of the transaction (if non-zero)
51 // - the outputID of the current input
52 // - the assetID, amount, control program of each output.
53 func (sw *SignatureWitness) sign(ctx context.Context, tpl *Template, index uint32, auth string, signFn SignFunc) error {
54 // Compute the predicate to sign. This is either a
55 // txsighash program if tpl.AllowAdditional is false (i.e., the tx is complete
56 // and no further changes are allowed) or a program enforcing
57 // constraints derived from the existing outputs and current input.
58 if len(sw.Program) == 0 {
60 sw.Program, err = buildSigProgram(tpl, tpl.SigningInstructions[index].Position)
64 if len(sw.Program) == 0 {
65 return ErrEmptyProgram
68 if len(sw.Sigs) < len(sw.Keys) {
69 // Each key in sw.Keys may produce a signature in sw.Sigs. Make
70 // sure there are enough slots in sw.Sigs and that we preserve any
71 // sigs already present.
72 newSigs := make([]chainjson.HexBytes, len(sw.Keys))
73 copy(newSigs, sw.Sigs)
77 sha3pool.Sum256(h[:], sw.Program)
78 for i, keyID := range sw.Keys {
79 if len(sw.Sigs[i]) > 0 {
80 // Already have a signature for this key
83 path := make([][]byte, len(keyID.DerivationPath))
84 for i, p := range keyID.DerivationPath {
87 sigBytes, err := signFn(ctx, keyID.XPub, path, h, auth)
89 log.WithField("err", err).Warningf("computing signature %d", i)
93 // This break is ordered to avoid signing transaction successfully only once for a multiple-sign account
94 // that consist of different keys by the same password. Exit immediately when the signature is success,
95 // it means that only one signature will be successful in the loop for this multiple-sign account.
102 func (sw *SignatureWitness) Sign(tpl *Template, index uint32, xprv chainkd.XPrv) error {
103 // Compute the predicate to sign. This is either a
104 // txsighash program if tpl.AllowAdditional is false (i.e., the tx is complete
105 // and no further changes are allowed) or a program enforcing
106 // constraints derived from the existing outputs and current input.
107 if len(sw.Program) == 0 {
109 sw.Program, err = buildSigProgram(tpl, tpl.SigningInstructions[index].Position)
113 if len(sw.Program) == 0 {
114 return ErrEmptyProgram
117 if len(sw.Sigs) < len(sw.Keys) {
118 // Each key in sw.Keys may produce a signature in sw.Sigs. Make
119 // sure there are enough slots in sw.Sigs and that we preserve any
120 // sigs already present.
121 newSigs := make([]chainjson.HexBytes, len(sw.Keys))
122 copy(newSigs, sw.Sigs)
126 sha3pool.Sum256(h[:], sw.Program)
127 for i, keyID := range sw.Keys {
128 if len(sw.Sigs[i]) > 0 {
129 // Already have a signature for this key
132 path := make([][]byte, len(keyID.DerivationPath))
133 for i, p := range keyID.DerivationPath {
136 if keyID.XPub.String() != xprv.XPub().String() {
140 sigBytes := xprv.Sign(h[:])
142 // This break is ordered to avoid signing transaction successfully only once for a multiple-sign account
143 // that consist of different keys by the same password. Exit immediately when the signature is success,
144 // it means that only one signature will be successful in the loop for this multiple-sign account.
145 sw.Sigs[i] = sigBytes
151 func (sw SignatureWitness) Materialize(args *[][]byte) error {
152 // This is the value of N for the CHECKPREDICATE call. The code
153 // assumes that everything already in the arg list before this call
154 // to Materialize is input to the signature program, so N is
156 *args = append(*args, vm.Int64Bytes(int64(len(*args))))
159 for i := 0; i < len(sw.Sigs) && nsigs < sw.Quorum; i++ {
160 if len(sw.Sigs[i]) > 0 {
161 *args = append(*args, sw.Sigs[i])
165 *args = append(*args, sw.Program)
169 // MarshalJSON convert struct to json
170 func (sw SignatureWitness) MarshalJSON() ([]byte, error) {
172 Type string `json:"type"`
173 Quorum int `json:"quorum"`
174 Keys []keyID `json:"keys"`
175 Sigs []chainjson.HexBytes `json:"signatures"`
182 return json.Marshal(obj)