OSDN Git Service

055782763787294e98166b0d7a3633189139782e
[bytom/bytom.git] / blockchain / txbuilder / witness.go
1 package txbuilder
2
3 import (
4         "bytes"
5         "context"
6         "encoding/json"
7
8         "github.com/bytom/crypto/ed25519/chainkd"
9         "github.com/bytom/crypto/sha3pool"
10         chainjson "github.com/bytom/encoding/json"
11         "github.com/bytom/errors"
12         "github.com/bytom/protocol/bc"
13         "github.com/bytom/protocol/vm"
14         "github.com/bytom/protocol/vm/vmutil"
15 )
16
17 // SignFunc is the function passed into Sign that produces
18 // a signature for a given xpub, derivation path, and hash.
19 type SignFunc func(context.Context, chainkd.XPub, [][]byte, [32]byte, string) ([]byte, error)
20
21 // materializeWitnesses takes a filled in Template and "materializes"
22 // each witness component, turning it into a vector of arguments for
23 // the tx's input witness, creating a fully-signed transaction.
24 func materializeWitnesses(txTemplate *Template) error {
25         msg := txTemplate.Transaction
26
27         if msg == nil {
28                 return errors.Wrap(ErrMissingRawTx)
29         }
30
31         if len(txTemplate.SigningInstructions) > len(msg.Inputs) {
32                 return errors.Wrap(ErrBadInstructionCount)
33         }
34
35         for i, sigInst := range txTemplate.SigningInstructions {
36                 if msg.Inputs[sigInst.Position] == nil {
37                         return errors.WithDetailf(ErrBadTxInputIdx, "signing instruction %d references missing tx input %d", i, sigInst.Position)
38                 }
39
40                 var witness [][]byte
41                 for j, sw := range sigInst.SignatureWitnesses {
42                         err := sw.materialize(txTemplate, sigInst.Position, &witness)
43                         if err != nil {
44                                 return errors.WithDetailf(err, "error in witness component %d of input %d", j, i)
45                         }
46                 }
47
48                 msg.SetInputArguments(sigInst.Position, witness)
49         }
50
51         return nil
52 }
53
54 type (
55         signatureWitness struct {
56                 // Quorum is the number of signatures required.
57                 Quorum int `json:"quorum"`
58
59                 // Keys are the identities of the keys to sign with.
60                 Keys []keyID `json:"keys"`
61
62                 // Program is the predicate part of the signature program, whose hash is what gets
63                 // signed. If empty, it is computed during Sign from the outputs
64                 // and the current input of the transaction.
65                 Program chainjson.HexBytes `json:"program"`
66
67                 // Sigs are signatures of Program made from each of the Keys
68                 // during Sign.
69                 Sigs []chainjson.HexBytes `json:"signatures"`
70         }
71
72         keyID struct {
73                 XPub           chainkd.XPub         `json:"xpub"`
74                 DerivationPath []chainjson.HexBytes `json:"derivation_path"`
75         }
76 )
77
78 var ErrEmptyProgram = errors.New("empty signature program")
79
80 // Sign populates sw.Sigs with as many signatures of the predicate in
81 // sw.Program as it can from the overlapping set of keys in sw.Keys
82 // and xpubs.
83 //
84 // If sw.Program is empty, it is populated with an _inferred_ predicate:
85 // a program committing to aspects of the current
86 // transaction. Specifically, the program commits to:
87 //  - the mintime and maxtime of the transaction (if non-zero)
88 //  - the outputID and (if non-empty) reference data of the current input
89 //  - the assetID, amount, control program, and (if non-empty) reference data of each output.
90 func (sw *signatureWitness) sign(ctx context.Context, tpl *Template, index uint32, xpubs []chainkd.XPub, auth string, signFn SignFunc) error {
91         // Compute the predicate to sign. This is either a
92         // txsighash program if tpl.AllowAdditional is false (i.e., the tx is complete
93         // and no further changes are allowed) or a program enforcing
94         // constraints derived from the existing outputs and current input.
95         if len(sw.Program) == 0 {
96                 sw.Program = buildSigProgram(tpl, tpl.SigningInstructions[index].Position)
97                 if len(sw.Program) == 0 {
98                         return ErrEmptyProgram
99                 }
100         }
101         if len(sw.Sigs) < len(sw.Keys) {
102                 // Each key in sw.Keys may produce a signature in sw.Sigs. Make
103                 // sure there are enough slots in sw.Sigs and that we preserve any
104                 // sigs already present.
105                 newSigs := make([]chainjson.HexBytes, len(sw.Keys))
106                 copy(newSigs, sw.Sigs)
107                 sw.Sigs = newSigs
108         }
109         var h [32]byte
110         sha3pool.Sum256(h[:], sw.Program)
111         for i, keyID := range sw.Keys {
112                 if len(sw.Sigs[i]) > 0 {
113                         // Already have a signature for this key
114                         continue
115                 }
116                 if !contains(xpubs, keyID.XPub) {
117                         continue
118                 }
119                 path := make([]([]byte), len(keyID.DerivationPath))
120                 for i, p := range keyID.DerivationPath {
121                         path[i] = p
122                 }
123                 sigBytes, err := signFn(ctx, keyID.XPub, path, h, auth)
124                 if err != nil {
125                         return errors.WithDetailf(err, "computing signature %d", i)
126                 }
127                 sw.Sigs[i] = sigBytes
128         }
129         return nil
130 }
131
132 func contains(list []chainkd.XPub, key chainkd.XPub) bool {
133         for _, k := range list {
134                 if bytes.Equal(k[:], key[:]) {
135                         return true
136                 }
137         }
138         return false
139 }
140
141 func buildSigProgram(tpl *Template, index uint32) []byte {
142         if !tpl.AllowAdditional {
143                 h := tpl.Hash(index)
144                 builder := vmutil.NewBuilder()
145                 builder.AddData(h.Bytes())
146                 builder.AddOp(vm.OP_TXSIGHASH).AddOp(vm.OP_EQUAL)
147                 prog, _ := builder.Build() // error is impossible
148                 return prog
149         }
150         constraints := make([]constraint, 0, 3+len(tpl.Transaction.Outputs))
151         constraints = append(constraints, &timeConstraint{
152                 minTimeMS: tpl.Transaction.MinTime,
153                 maxTimeMS: tpl.Transaction.MaxTime,
154         })
155         id := tpl.Transaction.Tx.InputIDs[index]
156         if sp, err := tpl.Transaction.Tx.Spend(id); err == nil {
157                 constraints = append(constraints, outputIDConstraint(*sp.SpentOutputId))
158         }
159
160         // Commitment to the tx-level refdata is conditional on it being
161         // non-empty. Commitment to the input-level refdata is
162         // unconditional. Rationale: no one should be able to change "my"
163         // reference data; anyone should be able to set tx refdata but, once
164         // set, it should be immutable.
165         if len(tpl.Transaction.ReferenceData) > 0 {
166                 constraints = append(constraints, refdataConstraint{tpl.Transaction.ReferenceData, true})
167         }
168         constraints = append(constraints, refdataConstraint{tpl.Transaction.Inputs[index].ReferenceData, false})
169
170         for i, out := range tpl.Transaction.Outputs {
171                 c := &payConstraint{
172                         Index:       i,
173                         AssetAmount: out.AssetAmount,
174                         Program:     out.ControlProgram,
175                 }
176                 if len(out.ReferenceData) > 0 {
177                         var b32 [32]byte
178                         sha3pool.Sum256(b32[:], out.ReferenceData)
179                         h := bc.NewHash(b32)
180                         c.RefDataHash = &h
181                 }
182                 constraints = append(constraints, c)
183         }
184         var program []byte
185         for i, c := range constraints {
186                 program = append(program, c.code()...)
187                 if i < len(constraints)-1 { // leave the final bool on top of the stack
188                         program = append(program, byte(vm.OP_VERIFY))
189                 }
190         }
191         return program
192 }
193
194 func (sw signatureWitness) materialize(tpl *Template, index uint32, args *[][]byte) error {
195         // This is the value of N for the CHECKPREDICATE call. The code
196         // assumes that everything already in the arg list before this call
197         // to Materialize is input to the signature program, so N is
198         // len(*args).
199         *args = append(*args, vm.Int64Bytes(int64(len(*args))))
200
201         var nsigs int
202         for i := 0; i < len(sw.Sigs) && nsigs < sw.Quorum; i++ {
203                 if len(sw.Sigs[i]) > 0 {
204                         *args = append(*args, sw.Sigs[i])
205                         nsigs++
206                 }
207         }
208         *args = append(*args, sw.Program)
209         return nil
210 }
211
212 func (sw signatureWitness) MarshalJSON() ([]byte, error) {
213         obj := struct {
214                 Type   string               `json:"type"`
215                 Quorum int                  `json:"quorum"`
216                 Keys   []keyID              `json:"keys"`
217                 Sigs   []chainjson.HexBytes `json:"signatures"`
218         }{
219                 Type:   "signature",
220                 Quorum: sw.Quorum,
221                 Keys:   sw.Keys,
222                 Sigs:   sw.Sigs,
223         }
224         return json.Marshal(obj)
225 }
226
227 // AddWitnessKeys adds a signatureWitness with the given quorum and
228 // list of keys derived by applying the derivation path to each of the
229 // xpubs.
230 func (si *SigningInstruction) AddWitnessKeys(xpubs []chainkd.XPub, path [][]byte, quorum int) {
231         hexPath := make([]chainjson.HexBytes, 0, len(path))
232         for _, p := range path {
233                 hexPath = append(hexPath, p)
234         }
235
236         keyIDs := make([]keyID, 0, len(xpubs))
237         for _, xpub := range xpubs {
238                 keyIDs = append(keyIDs, keyID{xpub, hexPath})
239         }
240
241         sw := &signatureWitness{
242                 Quorum: quorum,
243                 Keys:   keyIDs,
244         }
245         si.SignatureWitnesses = append(si.SignatureWitnesses, sw)
246 }