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"
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)
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
28 return errors.Wrap(ErrMissingRawTx)
31 if len(txTemplate.SigningInstructions) > len(msg.Inputs) {
32 return errors.Wrap(ErrBadInstructionCount)
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)
41 for j, sw := range sigInst.SignatureWitnesses {
42 err := sw.materialize(txTemplate, sigInst.Position, &witness)
44 return errors.WithDetailf(err, "error in witness component %d of input %d", j, i)
48 msg.SetInputArguments(sigInst.Position, witness)
55 signatureWitness struct {
56 // Quorum is the number of signatures required.
57 Quorum int `json:"quorum"`
59 // Keys are the identities of the keys to sign with.
60 Keys []keyID `json:"keys"`
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"`
67 // Sigs are signatures of Program made from each of the Keys
69 Sigs []chainjson.HexBytes `json:"signatures"`
73 XPub chainkd.XPub `json:"xpub"`
74 DerivationPath []chainjson.HexBytes `json:"derivation_path"`
78 var ErrEmptyProgram = errors.New("empty signature program")
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
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
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)
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
116 if !contains(xpubs, keyID.XPub) {
119 path := make([]([]byte), len(keyID.DerivationPath))
120 for i, p := range keyID.DerivationPath {
123 sigBytes, err := signFn(ctx, keyID.XPub, path, h, auth)
125 return errors.WithDetailf(err, "computing signature %d", i)
127 sw.Sigs[i] = sigBytes
132 func contains(list []chainkd.XPub, key chainkd.XPub) bool {
133 for _, k := range list {
134 if bytes.Equal(k[:], key[:]) {
141 func buildSigProgram(tpl *Template, index uint32) []byte {
142 if !tpl.AllowAdditional {
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
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,
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))
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})
168 constraints = append(constraints, refdataConstraint{tpl.Transaction.Inputs[index].ReferenceData, false})
170 for i, out := range tpl.Transaction.Outputs {
173 AssetAmount: out.AssetAmount,
174 Program: out.ControlProgram,
176 if len(out.ReferenceData) > 0 {
178 sha3pool.Sum256(b32[:], out.ReferenceData)
182 constraints = append(constraints, c)
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))
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
199 *args = append(*args, vm.Int64Bytes(int64(len(*args))))
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])
208 *args = append(*args, sw.Program)
212 func (sw signatureWitness) MarshalJSON() ([]byte, error) {
214 Type string `json:"type"`
215 Quorum int `json:"quorum"`
216 Keys []keyID `json:"keys"`
217 Sigs []chainjson.HexBytes `json:"signatures"`
224 return json.Marshal(obj)
227 // AddWitnessKeys adds a signatureWitness with the given quorum and
228 // list of keys derived by applying the derivation path to each of the
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)
236 keyIDs := make([]keyID, 0, len(xpubs))
237 for _, xpub := range xpubs {
238 keyIDs = append(keyIDs, keyID{xpub, hexPath})
241 sw := &signatureWitness{
245 si.SignatureWitnesses = append(si.SignatureWitnesses, sw)