1 // Package txauthor provides transaction creation code for wallets.
2 package txauthor
3 4 import (
5 "errors"
6 "github.com/p9c/p9/pkg/amt"
7 "github.com/p9c/p9/pkg/chaincfg"
8 9 "github.com/p9c/p9/pkg/txrules"
10 "github.com/p9c/p9/pkg/txscript"
11 "github.com/p9c/p9/pkg/txsizes"
12 h "github.com/p9c/p9/pkg/util/helpers"
13 "github.com/p9c/p9/pkg/wire"
14 )
15 16 type (
17 // InputSource provides transaction inputs referencing spendable outputs to construct a transaction outputting some
18 // target amount. If the target amount can not be satisified, this can be signaled by returning a total amount less
19 // than the target or by returning a more detailed error implementing InputSourceError.
20 InputSource func(target amt.Amount) (
21 total amt.Amount, inputs []*wire.TxIn,
22 inputValues []amt.Amount, scripts [][]byte, e error,
23 )
24 // InputSourceError describes the failure to provide enough input value from unspent transaction outputs to meet a
25 // target amount. A typed error is used so input sources can provide their own implementations describing the reason
26 // for the error, for example, due to spendable policies or locked coins rather than the wallet not having enough
27 // available input value.
28 InputSourceError interface {
29 error
30 InputSourceError()
31 }
32 // Default implementation of InputSourceError.
33 insufficientFundsError struct{}
34 // AuthoredTx holds the state of a newly-created transaction and the change output (if one was added).
35 AuthoredTx struct {
36 Tx *wire.MsgTx
37 PrevScripts [][]byte
38 PrevInputValues []amt.Amount
39 TotalInput amt.Amount
40 ChangeIndex int // negative if no change
41 }
42 // ChangeSource provides P2PKH change output scripts for transaction creation.
43 ChangeSource func() ([]byte, error)
44 // SecretsSource provides private keys and redeem scripts necessary for constructing transaction input signatures.
45 // Secrets are looked up by the corresponding Address for the previous output script. Addresses for lookup are
46 // created using the source's blockchain parameters and means a single SecretsSource can only manage secrets for a
47 // single chain.
48 //
49 // TODO: Rewrite this interface to look up private keys and redeem scripts for pubkeys, pubkey hashes, script
50 // hashes, etc. as separate interface methods.
51 //
52 // This would remove the ChainParams requirement of the interface and could avoid unnecessary conversions from
53 // previous output scripts to Addresses. This can not be done without modifications to the txscript package.
54 SecretsSource interface {
55 txscript.KeyDB
56 txscript.ScriptDB
57 ChainParams() *chaincfg.Params
58 }
59 )
60 61 func (insufficientFundsError) InputSourceError() {
62 }
63 func (insufficientFundsError) Error() string {
64 return "insufficient funds available to construct transaction"
65 }
66 67 // NewUnsignedTransaction creates an unsigned transaction paying to one or more non-change outputs. An appropriate
68 // transaction fee is included based on the transaction size.
69 //
70 // Transaction inputs are chosen from repeated calls to fetchInputs with increasing targets amounts.
71 //
72 // If any remaining output value can be returned to the wallet via a change output without violating mempool dust rules,
73 // a P2WPKH change output is appended to the transaction outputs. Since the change output may not be necessary,
74 // fetchChange is called zero or one times to generate this script. This function must return a P2WPKH script or
75 // smaller, otherwise fee estimation will be incorrect.
76 //
77 // If successful, the transaction, total input value spent, and all previous output scripts are returned. If the input
78 // source was unable to provide enough input value to pay for every output any any necessary fees, an InputSourceError
79 // is returned.
80 //
81 // BUGS: Fee estimation may be off when redeeming non-compressed P2PKH outputs.
82 func NewUnsignedTransaction(
83 outputs []*wire.TxOut, relayFeePerKb amt.Amount,
84 fetchInputs InputSource, fetchChange ChangeSource,
85 ) (*AuthoredTx, error) {
86 targetAmount := h.SumOutputValues(outputs)
87 estimatedSize := txsizes.EstimateVirtualSize(1, 0, 0, outputs, true)
88 targetFee := txrules.FeeForSerializeSize(relayFeePerKb, estimatedSize)
89 for {
90 inputAmount, inputs, inputValues, scripts, e := fetchInputs(targetAmount + targetFee)
91 if e != nil {
92 return nil, e
93 }
94 if inputAmount < targetAmount+targetFee {
95 return nil, insufficientFundsError{}
96 }
97 // We count the types of inputs, which we'll use to estimate the vsize of the transaction.
98 var nested, p2wpkh, p2pkh int
99 for _, /*pkScript*/ _ = range scripts {
100 switch {
101 // // If this is a p2sh output, we assume this is a nested P2WKH.
102 // case txscript.IsPayToScriptHash(pkScript):
103 // nested++
104 // case txscript.IsPayToWitnessPubKeyHash(pkScript):
105 // p2wpkh++
106 default:
107 p2pkh++
108 }
109 }
110 maxSignedSize := txsizes.EstimateVirtualSize(
111 p2pkh, p2wpkh,
112 nested, outputs, true,
113 )
114 maxRequiredFee := txrules.FeeForSerializeSize(relayFeePerKb, maxSignedSize)
115 remainingAmount := inputAmount - targetAmount
116 if remainingAmount < maxRequiredFee {
117 targetFee = maxRequiredFee
118 continue
119 }
120 unsignedTransaction := &wire.MsgTx{
121 Version: wire.TxVersion,
122 TxIn: inputs,
123 TxOut: outputs,
124 LockTime: 0,
125 }
126 changeIndex := -1
127 changeAmount := inputAmount - targetAmount - maxRequiredFee
128 if changeAmount != 0 && !txrules.IsDustAmount(
129 changeAmount,
130 txsizes.P2PKHPkScriptSize, relayFeePerKb,
131 ) {
132 changeScript, e := fetchChange()
133 if e != nil {
134 return nil, e
135 }
136 if len(changeScript) > txsizes.P2PKHPkScriptSize {
137 return nil, errors.New(
138 "fee estimation requires change " +
139 "scripts no larger than P2WPKH output scripts",
140 )
141 }
142 change := wire.NewTxOut(int64(changeAmount), changeScript)
143 l := len(outputs)
144 unsignedTransaction.TxOut = append(outputs[:l:l], change)
145 changeIndex = l
146 }
147 return &AuthoredTx{
148 Tx: unsignedTransaction,
149 PrevScripts: scripts,
150 PrevInputValues: inputValues,
151 TotalInput: inputAmount,
152 ChangeIndex: changeIndex,
153 }, nil
154 }
155 }
156 157 // RandomizeOutputPosition randomizes the position of a transaction's output by swapping it with a random output. The
158 // new index is returned. This should be done before signing.
159 func RandomizeOutputPosition(outputs []*wire.TxOut, index int) int {
160 r := cprng.Int31n(int32(len(outputs)))
161 outputs[r], outputs[index] = outputs[index], outputs[r]
162 return int(r)
163 }
164 165 // RandomizeChangePosition randomizes the position of an authored transaction's change output. This should be done
166 // before signing.
167 func (tx *AuthoredTx) RandomizeChangePosition() {
168 tx.ChangeIndex = RandomizeOutputPosition(tx.Tx.TxOut, tx.ChangeIndex)
169 }
170 171 // AddAllInputScripts modifies transaction a transaction by adding inputs
172 // scripts for each input. Previous output scripts being redeemed by each input
173 // are passed in prevPkScripts and the slice length must match the number of
174 // inputs. Private keys and redeem scripts are looked up using a SecretsSource
175 // based on the previous output script.
176 func AddAllInputScripts(
177 tx *wire.MsgTx, prevPkScripts [][]byte, inputValues []amt.Amount,
178 secrets SecretsSource,
179 ) (e error) {
180 inputs := tx.TxIn
181 // hashCache := txscript.NewTxSigHashes(tx)
182 chainParams := secrets.ChainParams()
183 if len(inputs) != len(prevPkScripts) {
184 return errors.New(
185 "tx.TxIn and prevPkScripts slices must " +
186 "have equal length",
187 )
188 }
189 for i := range inputs {
190 pkScript := prevPkScripts[i]
191 switch {
192 // // If this is a p2sh output, who's script hash pre-image is a witness program,
193 // // then we'll need to use a modified signing function which generates both the
194 // // sigScript, and the witness script.
195 // case txscript.IsPayToScriptHash(pkScript):
196 // e := spendNestedWitnessPubKeyHash(inputs[i], pkScript,
197 // int64(inputValues[i]), chainParams, secrets,
198 // tx, hashCache, i)
199 // if e != nil {
200 // // return e
201 // }
202 // case txscript.IsPayToWitnessPubKeyHash(pkScript):
203 // e := spendWitnessKeyHash(inputs[i], pkScript,
204 // int64(inputValues[i]), chainParams, secrets,
205 // tx, hashCache, i)
206 // if e != nil {
207 // // return e
208 // }
209 default:
210 sigScript := inputs[i].SignatureScript
211 var script []byte
212 script, e = txscript.SignTxOutput(
213 chainParams, tx, i,
214 pkScript, txscript.SigHashAll, secrets, secrets,
215 sigScript,
216 )
217 if e != nil {
218 return e
219 }
220 inputs[i].SignatureScript = script
221 }
222 }
223 return nil
224 }
225 226 // spendWitnessKeyHash generates, and sets a valid witness for spending the
227 // // passed pkScript with the specified input amount. The input amount *must*
228 // // correspond to the output value of the previous pkScript, or else verification
229 // // will fail since the new sighash digest algorithm defined in BIP0143 includes
230 // // the input value in the sighash.
231 // func spendWitnessKeyHash(txIn *wire.TxIn, pkScript []byte,
232 // inputValue int64, chainParams *chaincfg.Params, secrets SecretsSource,
233 // tx *wire.MsgTx, hashCache *txscript.TxSigHashes, idx int) (e error) {
234 // // First obtain the key pair associated with this p2wkh address.
235 // _, addrs, _, e = txscript.ExtractPkScriptAddrs(pkScript,
236 // chainParams)
237 // if e != nil {
238 // // return e
239 // }
240 // privKey, compressed, e := secrets.GetKey(addrs[0])
241 // if e != nil {
242 // // return e
243 // }
244 // pubKey := privKey.PubKey()
245 // // Once we have the key pair, generate a p2wkh address type, respecting the compression type of the generated key.
246 // var pubKeyHash []byte
247 // if compressed {
248 // pubKeyHash = btcaddr.Hash160(pubKey.SerializeCompressed())
249 // } else {
250 // pubKeyHash = btcaddr.Hash160(pubKey.SerializeUncompressed())
251 // }
252 // p2wkhAddr, e := util.NewAddressWitnessPubKeyHash(pubKeyHash, chainParams)
253 // if e != nil {
254 // // return e
255 // }
256 // // With the concrete address type, we can now generate the corresponding witness
257 // // program to be used to generate a valid witness which will allow us to spend
258 // // this output.
259 // witnessProgram, e := txscript.PayToAddrScript(p2wkhAddr)
260 // if e != nil {
261 // // return e
262 // }
263 // witnessScript, e := txscript.WitnessSignature(tx, hashCache, idx,
264 // inputValue, witnessProgram, txscript.SigHashAll, privKey, true)
265 // if e != nil {
266 // // return e
267 // }
268 // txIn.Witness = witnessScript
269 // return nil
270 // }
271 272 // spendNestedWitnessPubKey generates both a sigScript, and valid witness for
273 // // spending the passed pkScript with the specified input amount. The generated
274 // // sigScript is the version 0 p2wkh witness program corresponding to the queried
275 // // key. The witness stack is identical to that of one which spends a regular
276 // // p2wkh output. The input amount *must* correspond to the output value of the
277 // // previous pkScript, or else verification will fail since the new sighash
278 // // digest algorithm defined in BIP0143 includes the input value in the sighash.
279 // func spendNestedWitnessPubKeyHash(txIn *wire.TxIn, pkScript []byte,
280 // inputValue int64, chainParams *chaincfg.Params, secrets SecretsSource,
281 // tx *wire.MsgTx, hashCache *txscript.TxSigHashes, idx int) (e error) {
282 // // First we need to obtain the key pair related to this p2sh output.
283 // _, addrs, _, e = txscript.ExtractPkScriptAddrs(pkScript,
284 // chainParams)
285 // if e != nil {
286 // // return e
287 // }
288 // privKey, compressed, e := secrets.GetKey(addrs[0])
289 // if e != nil {
290 // // return e
291 // }
292 // pubKey := privKey.PubKey()
293 // var pubKeyHash []byte
294 // if compressed {
295 // pubKeyHash = btcaddr.Hash160(pubKey.SerializeCompressed())
296 // } else {
297 // pubKeyHash = btcaddr.Hash160(pubKey.SerializeUncompressed())
298 // }
299 // // Next, we'll generate a valid sigScript that'll allow us to spend the p2sh
300 // // output. The sigScript will contain only a single push of the p2wkh witness
301 // // program corresponding to the matching public key of this address.
302 // p2wkhAddr, e := util.NewAddressWitnessPubKeyHash(pubKeyHash, chainParams)
303 // if e != nil {
304 // // return e
305 // }
306 // witnessProgram, e := txscript.PayToAddrScript(p2wkhAddr)
307 // if e != nil {
308 // // return e
309 // }
310 // bldr := txscript.NewScriptBuilder()
311 // bldr.AddData(witnessProgram)
312 // sigScript, e := bldr.Script()
313 // if e != nil {
314 // // return e
315 // }
316 // txIn.SignatureScript = sigScript
317 // // With the sigScript in place, we'll next generate the proper witness that'll
318 // // allow us to spend the p2wkh output.
319 // witnessScript, e := txscript.WitnessSignature(tx, hashCache, idx,
320 // inputValue, witnessProgram, txscript.SigHashAll, privKey, compressed)
321 // if e != nil {
322 // // return e
323 // }
324 // txIn.Witness = witnessScript
325 // return nil
326 // }
327 328 // AddAllInputScripts modifies an authored transaction by adding inputs scripts for each input of an authored
329 // transaction. Private keys and redeem scripts are looked up using a SecretsSource based on the previous output script.
330 func (tx *AuthoredTx) AddAllInputScripts(secrets SecretsSource) (e error) {
331 return AddAllInputScripts(tx.Tx, tx.PrevScripts, tx.PrevInputValues, secrets)
332 }
333