-
Notifications
You must be signed in to change notification settings - Fork 0
/
author.go
412 lines (368 loc) · 14.3 KB
/
author.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
// Copyright (c) 2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
// Package txauthor provides transaction creation code for wallets.
package txauthor
import (
"fmt"
"math"
"github.com/kaotisk-hund/cjdcoind/btcutil/er"
"github.com/kaotisk-hund/cjdcoind/txscript/params"
"github.com/kaotisk-hund/cjdcoind/txscript/scriptbuilder"
"github.com/kaotisk-hund/cjdcoind/wire/constants"
"github.com/kaotisk-hund/cjdcoind/btcutil"
"github.com/kaotisk-hund/cjdcoind/chaincfg"
"github.com/kaotisk-hund/cjdcoind/cjdcoinwallet/wallet/txrules"
"github.com/kaotisk-hund/cjdcoind/txscript"
"github.com/kaotisk-hund/cjdcoind/wire"
h "github.com/kaotisk-hund/cjdcoind/cjdcoinwallet/internal/helpers"
"github.com/kaotisk-hund/cjdcoind/cjdcoinwallet/wallet/internal/txsizes"
)
// InputSource provides transaction inputs referencing spendable outputs to
// construct a transaction outputting some target amount. If the target amount
// can not be satisified, this can be signaled by returning a total amount less
// than the target or by returning a more detailed error implementing
// InputSourceError.
type InputSource func(target btcutil.Amount) (btcutil.Amount, []*wire.TxIn, []wire.TxInAdditional, er.R)
// InputSourceError describes the failure to provide enough input value from
// unspent transaction outputs to meet a target amount. A typed error is used
// so input sources can provide their own implementations describing the reason
// for the error, for example, due to spendable policies or locked coins rather
// than the wallet not having enough available input value.
var InputSourceError = er.NewErrorType("txauthor.InputSourceError")
// ImpossbleTransactionError is the default implementation of InputSourceError.
var ImpossibleTxError = InputSourceError.Code("ImpossibleTxError")
// AuthoredTx holds the state of a newly-created transaction and the change
// output (if one was added).
type AuthoredTx struct {
Tx *wire.MsgTx
TotalInput btcutil.Amount
ChangeIndex int // negative if no change
}
// ChangeSource provides P2PKH change output scripts for transaction creation.
type ChangeSource func() ([]byte, er.R)
// NewUnsignedTransaction creates an unsigned transaction paying to one or more
// non-change outputs. An appropriate transaction fee is included based on the
// transaction size.
//
// Transaction inputs are chosen from repeated calls to fetchInputs with
// increasing targets amounts.
//
// If any remaining output value can be returned to the wallet via a change
// output without violating mempool dust rules, a P2WPKH change output is
// appended to the transaction outputs. Since the change output may not be
// necessary, fetchChange is called zero or one times to generate this script.
// This function must return a P2WPKH script or smaller, otherwise fee estimation
// will be incorrect.
//
// If successful, the transaction, total input value spent, and all previous
// output scripts are returned. If the input source was unable to provide
// enough input value to pay for every output any any necessary fees, an
// InputSourceError is returned.
//
// BUGS: Fee estimation may be off when redeeming non-compressed P2PKH outputs.
// TODO(cjd): Fee estimation will be off when redeeming segwit multisigs, we need the redeem script...
func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb btcutil.Amount,
fetchInputs InputSource, fetchChange ChangeSource, partialOk bool) (*AuthoredTx, er.R) {
targetAmount := h.SumOutputValues(outputs)
estimatedSize := txsizes.EstimateVirtualSize(0, 1, 0, outputs, true)
targetFee := txrules.FeeForSerializeSize(relayFeePerKb, estimatedSize)
// If one of the outputs has a value of zero, this means we want to sweep everything
// except for fees to that output.
var sweepTo *wire.TxOut
for _, out := range outputs {
if out.Value == 0 {
sweepTo = out
}
}
for {
synthTargetAmount := targetAmount + targetFee
if sweepTo != nil {
synthTargetAmount = btcutil.Amount(math.MaxInt64)
}
inputAmount, inputs, inputAdditionals, err := fetchInputs(synthTargetAmount)
if err != nil {
return nil, err
}
if inputAmount < targetAmount+targetFee {
if partialOk && len(outputs) == 1 {
targetAmount = 0
sweepTo = outputs[0]
} else {
return nil, ImpossibleTxError.New(fmt.Sprintf("paying [%s] "+
"with fee of [%s], [%s] is immediately available from [%d] inputs",
targetAmount.String(), targetFee.String(), inputAmount.String(), len(inputs)), nil)
}
}
// We count the types of inputs, which we'll use to estimate
// the vsize of the transaction.
var nested, p2wpkh, p2pkh int
for _, add := range inputAdditionals {
switch {
// If this is a p2sh output, we assume this is a
// nested P2WKH.
case txscript.IsPayToScriptHash(add.PkScript):
nested++
case txscript.IsPayToWitnessPubKeyHash(add.PkScript):
p2wpkh++
default:
p2pkh++
}
}
maxSignedSize := txsizes.EstimateVirtualSize(p2pkh, p2wpkh,
nested, outputs, true)
maxRequiredFee := txrules.FeeForSerializeSize(relayFeePerKb, maxSignedSize)
remainingAmount := inputAmount - targetAmount
if remainingAmount < maxRequiredFee {
targetFee = maxRequiredFee
continue
}
if sweepTo != nil {
sweep := remainingAmount - maxRequiredFee
sweepTo.Value = int64(sweep)
targetAmount += sweep
}
unsignedTransaction := &wire.MsgTx{
Version: constants.TxVersion,
TxIn: inputs,
TxOut: outputs,
LockTime: 0,
Additional: inputAdditionals,
}
changeIndex := -1
changeAmount := inputAmount - targetAmount - maxRequiredFee
if changeAmount != 0 && !txrules.IsDustAmount(changeAmount,
txsizes.P2WPKHPkScriptSize, txrules.DefaultRelayFeePerKb) {
changeScript, err := fetchChange()
if err != nil {
return nil, err
}
// if len(changeScript) > txsizes.P2WPKHPkScriptSize {
// return nil, er.New("fee estimation requires change " +
// "scripts no larger than P2WPKH output scripts")
// }
change := wire.NewTxOut(int64(changeAmount), changeScript)
l := len(outputs)
unsignedTransaction.TxOut = append(outputs[:l:l], change)
changeIndex = l
}
return &AuthoredTx{
Tx: unsignedTransaction,
TotalInput: inputAmount,
ChangeIndex: changeIndex,
}, nil
}
}
// RandomizeOutputPosition randomizes the position of a transaction's output by
// swapping it with a random output. The new index is returned. This should be
// done before signing.
func RandomizeOutputPosition(outputs []*wire.TxOut, index int) int {
r := cprng.Int31n(int32(len(outputs)))
outputs[r], outputs[index] = outputs[index], outputs[r]
return int(r)
}
// RandomizeChangePosition randomizes the position of an authored transaction's
// change output. This should be done before signing.
func (tx *AuthoredTx) RandomizeChangePosition() {
tx.ChangeIndex = RandomizeOutputPosition(tx.Tx.TxOut, tx.ChangeIndex)
}
// SecretsSource provides private keys and redeem scripts necessary for
// constructing transaction input signatures. Secrets are looked up by the
// corresponding Address for the previous output script. Addresses for lookup
// are created using the source's blockchain parameters and means a single
// SecretsSource can only manage secrets for a single chain.
//
// TODO: Rewrite this interface to look up private keys and redeem scripts for
// pubkeys, pubkey hashes, script hashes, etc. as separate interface methods.
// This would remove the ChainParams requirement of the interface and could
// avoid unnecessary conversions from previous output scripts to Addresses.
// This can not be done without modifications to the txscript package.
type SecretsSource interface {
txscript.KeyDB
txscript.ScriptDB
ChainParams() *chaincfg.Params
}
// AddAllInputScripts modifies transaction a transaction by adding inputs
// scripts for each input. Previous output scripts being redeemed by each input
// are passed in prevPkScripts and the slice length must match the number of
// inputs. Private keys and redeem scripts are looked up using a SecretsSource
// based on the previous output script.
func AddAllInputScripts(tx *wire.MsgTx, secrets SecretsSource) er.R {
hashCache := txscript.NewTxSigHashes(tx)
chainParams := secrets.ChainParams()
if len(tx.TxIn) != len(tx.Additional) {
return er.New("tx.TxIn and tx.Additional slices must have equal length")
}
for i := range tx.TxIn {
if len(tx.Additional[i].PkScript) == 0 {
if len(tx.TxIn[i].SignatureScript) > 0 {
// This input is already fully signed, we'll leave it alone
continue
}
return er.Errorf("Input number [%d] of transaction [%s] has no PkScript "+
"nor SignatureScript, cannot make transaction", i, tx.TxHash())
}
if err := SignInputScript(
tx, i, params.SigHashAll, hashCache, secrets, secrets, chainParams); err != nil {
return err
}
}
return nil
}
func SignInputScript(
tx *wire.MsgTx,
inputNum int,
sigHashType params.SigHashType,
hashCache *txscript.TxSigHashes,
kdb txscript.KeyDB,
sdb txscript.ScriptDB,
chainParams *chaincfg.Params,
) er.R {
pkScript := tx.Additional[inputNum].PkScript
amt := tx.Additional[inputNum].Value
if len(pkScript) == 0 {
return er.New("Cannot sign transaction because it does not contain additional data")
}
if txscript.IsPayToScriptHash(pkScript) {
err := spendNestedWitnessPubKeyHash(tx.TxIn[inputNum], pkScript,
amt, chainParams, kdb,
tx, hashCache, inputNum, sigHashType)
if err != nil {
return err
}
} else if txscript.IsPayToWitnessPubKeyHash(pkScript) {
err := spendWitnessKeyHash(tx.TxIn[inputNum], pkScript,
amt, chainParams, kdb,
tx, hashCache, inputNum, sigHashType)
if err != nil {
return err
}
} else {
sigScript := tx.TxIn[inputNum].SignatureScript
script, err := txscript.SignTxOutput(
chainParams, tx, inputNum, pkScript, sigHashType, kdb, sdb, sigScript)
if err != nil {
return err
}
tx.TxIn[inputNum].SignatureScript = script
}
return nil
}
// spendWitnessKeyHash generates, and sets a valid witness for spending the
// passed pkScript with the specified input amount. The input amount *must*
// correspond to the output value of the previous pkScript, or else verification
// will fail since the new sighash digest algorithm defined in BIP0143 includes
// the input value in the sighash.
func spendWitnessKeyHash(txIn *wire.TxIn, pkScript []byte,
inputValueP *int64, chainParams *chaincfg.Params, secrets txscript.KeyDB,
tx *wire.MsgTx, hashCache *txscript.TxSigHashes, idx int,
hashType params.SigHashType) er.R {
if inputValueP == nil {
return er.New("Unable to sign transaction because input amount is unknown")
}
inputValue := *inputValueP
// First obtain the key pair associated with this p2wkh address.
_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript,
chainParams)
if err != nil {
return err
}
privKey, compressed, err := secrets.GetKey(addrs[0])
if err != nil {
return err
}
pubKey := privKey.PubKey()
// Once we have the key pair, generate a p2wkh address type, respecting
// the compression type of the generated key.
var pubKeyHash []byte
if compressed {
pubKeyHash = btcutil.Hash160(pubKey.SerializeCompressed())
} else {
pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed())
}
p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, chainParams)
if err != nil {
return err
}
// With the concrete address type, we can now generate the
// corresponding witness program to be used to generate a valid witness
// which will allow us to spend this output.
witnessProgram, err := txscript.PayToAddrScript(p2wkhAddr)
if err != nil {
return err
}
witnessScript, err := txscript.WitnessSignature(tx, hashCache, idx,
inputValue, witnessProgram, hashType, privKey, true)
if err != nil {
return err
}
txIn.Witness = witnessScript
return nil
}
// spendNestedWitnessPubKey generates both a sigScript, and valid witness for
// spending the passed pkScript with the specified input amount. The generated
// sigScript is the version 0 p2wkh witness program corresponding to the queried
// key. The witness stack is identical to that of one which spends a regular
// p2wkh output. The input amount *must* correspond to the output value of the
// previous pkScript, or else verification will fail since the new sighash
// digest algorithm defined in BIP0143 includes the input value in the sighash.
func spendNestedWitnessPubKeyHash(txIn *wire.TxIn, pkScript []byte,
inputValueP *int64, chainParams *chaincfg.Params, secrets txscript.KeyDB,
tx *wire.MsgTx, hashCache *txscript.TxSigHashes, idx int,
hashType params.SigHashType) er.R {
if inputValueP == nil {
return er.New("Unable to sign transaction because input amount is unknown")
}
inputValue := *inputValueP
// First we need to obtain the key pair related to this p2sh output.
_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript,
chainParams)
if err != nil {
return err
}
privKey, compressed, err := secrets.GetKey(addrs[0])
if err != nil {
return err
}
pubKey := privKey.PubKey()
var pubKeyHash []byte
if compressed {
pubKeyHash = btcutil.Hash160(pubKey.SerializeCompressed())
} else {
pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed())
}
// Next, we'll generate a valid sigScript that'll allow us to spend
// the p2sh output. The sigScript will contain only a single push of
// the p2wkh witness program corresponding to the matching public key
// of this address.
p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, chainParams)
if err != nil {
return err
}
witnessProgram, err := txscript.PayToAddrScript(p2wkhAddr)
if err != nil {
return err
}
bldr := scriptbuilder.NewScriptBuilder()
bldr.AddData(witnessProgram)
sigScript, err := bldr.Script()
if err != nil {
return err
}
txIn.SignatureScript = sigScript
// With the sigScript in place, we'll next generate the proper witness
// that'll allow us to spend the p2wkh output.
witnessScript, err := txscript.WitnessSignature(tx, hashCache, idx,
inputValue, witnessProgram, hashType, privKey, compressed)
if err != nil {
return err
}
txIn.Witness = witnessScript
return nil
}
// AddAllInputScripts modifies an authored transaction by adding inputs scripts
// for each input of an authored transaction. Private keys and redeem scripts
// are looked up using a SecretsSource based on the previous output script.
func (tx *AuthoredTx) AddAllInputScripts(secrets SecretsSource) er.R {
return AddAllInputScripts(tx.Tx, secrets)
}