From 6ecc72e5e63dd148b7260bca351caf701558b62c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Sun, 20 Feb 2022 16:05:14 -0800 Subject: [PATCH] txscript: move sighash computations to new file --- txscript/script.go | 265 ----------------------------------------- txscript/sighash.go | 280 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 280 insertions(+), 265 deletions(-) create mode 100644 txscript/sighash.go diff --git a/txscript/script.go b/txscript/script.go index d6ae2f5e..e2d70824 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -7,12 +7,10 @@ package txscript import ( "bytes" - "encoding/binary" "fmt" "strings" "time" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" ) @@ -298,269 +296,6 @@ func removeOpcodeByData(script []byte, dataToRemove []byte) []byte { return result } -// calcWitnessSignatureHashRaw computes the sighash digest of a transaction's -// segwit input using the new, optimized digest calculation algorithm defined -// in BIP0143: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki. -// This function makes use of pre-calculated sighash fragments stored within -// the passed HashCache to eliminate duplicate hashing computations when -// calculating the final digest, reducing the complexity from O(N^2) to O(N). -// Additionally, signatures now cover the input value of the referenced unspent -// output. This allows offline, or hardware wallets to compute the exact amount -// being spent, in addition to the final transaction fee. In the case the -// wallet if fed an invalid input amount, the real sighash will differ causing -// the produced signature to be invalid. -func calcWitnessSignatureHashRaw(scriptSig []byte, sigHashes *TxSigHashes, - hashType SigHashType, tx *wire.MsgTx, idx int, amt int64) ([]byte, error) { - - // As a sanity check, ensure the passed input index for the transaction - // is valid. - if idx > len(tx.TxIn)-1 { - return nil, fmt.Errorf("idx %d but %d txins", idx, len(tx.TxIn)) - } - - // We'll utilize this buffer throughout to incrementally calculate - // the signature hash for this transaction. - var sigHash bytes.Buffer - - // First write out, then encode the transaction's version number. - var bVersion [4]byte - binary.LittleEndian.PutUint32(bVersion[:], uint32(tx.Version)) - sigHash.Write(bVersion[:]) - - // Next write out the possibly pre-calculated hashes for the sequence - // numbers of all inputs, and the hashes of the previous outs for all - // outputs. - var zeroHash chainhash.Hash - - // If anyone can pay isn't active, then we can use the cached - // hashPrevOuts, otherwise we just write zeroes for the prev outs. - if hashType&SigHashAnyOneCanPay == 0 { - sigHash.Write(sigHashes.HashPrevOuts[:]) - } else { - sigHash.Write(zeroHash[:]) - } - - // If the sighash isn't anyone can pay, single, or none, the use the - // cached hash sequences, otherwise write all zeroes for the - // hashSequence. - if hashType&SigHashAnyOneCanPay == 0 && - hashType&sigHashMask != SigHashSingle && - hashType&sigHashMask != SigHashNone { - sigHash.Write(sigHashes.HashSequence[:]) - } else { - sigHash.Write(zeroHash[:]) - } - - txIn := tx.TxIn[idx] - - // Next, write the outpoint being spent. - sigHash.Write(txIn.PreviousOutPoint.Hash[:]) - var bIndex [4]byte - binary.LittleEndian.PutUint32(bIndex[:], txIn.PreviousOutPoint.Index) - sigHash.Write(bIndex[:]) - - if isWitnessPubKeyHashScript(scriptSig) { - // The script code for a p2wkh is a length prefix varint for - // the next 25 bytes, followed by a re-creation of the original - // p2pkh pk script. - sigHash.Write([]byte{0x19}) - sigHash.Write([]byte{OP_DUP}) - sigHash.Write([]byte{OP_HASH160}) - sigHash.Write([]byte{OP_DATA_20}) - sigHash.Write(extractWitnessPubKeyHash(scriptSig)) - sigHash.Write([]byte{OP_EQUALVERIFY}) - sigHash.Write([]byte{OP_CHECKSIG}) - } else { - // For p2wsh outputs, and future outputs, the script code is - // the original script, with all code separators removed, - // serialized with a var int length prefix. - wire.WriteVarBytes(&sigHash, 0, scriptSig) - } - - // Next, add the input amount, and sequence number of the input being - // signed. - var bAmount [8]byte - binary.LittleEndian.PutUint64(bAmount[:], uint64(amt)) - sigHash.Write(bAmount[:]) - var bSequence [4]byte - binary.LittleEndian.PutUint32(bSequence[:], txIn.Sequence) - sigHash.Write(bSequence[:]) - - // If the current signature mode isn't single, or none, then we can - // re-use the pre-generated hashoutputs sighash fragment. Otherwise, - // we'll serialize and add only the target output index to the signature - // pre-image. - if hashType&SigHashSingle != SigHashSingle && - hashType&SigHashNone != SigHashNone { - sigHash.Write(sigHashes.HashOutputs[:]) - } else if hashType&sigHashMask == SigHashSingle && idx < len(tx.TxOut) { - var b bytes.Buffer - wire.WriteTxOut(&b, 0, 0, tx.TxOut[idx]) - sigHash.Write(chainhash.DoubleHashB(b.Bytes())) - } else { - sigHash.Write(zeroHash[:]) - } - - // Finally, write out the transaction's locktime, and the sig hash - // type. - var bLockTime [4]byte - binary.LittleEndian.PutUint32(bLockTime[:], tx.LockTime) - sigHash.Write(bLockTime[:]) - var bHashType [4]byte - binary.LittleEndian.PutUint32(bHashType[:], uint32(hashType)) - sigHash.Write(bHashType[:]) - - return chainhash.DoubleHashB(sigHash.Bytes()), nil -} - -// CalcWitnessSigHash computes the sighash digest for the specified input of -// the target transaction observing the desired sig hash type. -func CalcWitnessSigHash(script []byte, sigHashes *TxSigHashes, hType SigHashType, - tx *wire.MsgTx, idx int, amt int64) ([]byte, error) { - - const scriptVersion = 0 - if err := checkScriptParses(scriptVersion, script); err != nil { - return nil, err - } - - return calcWitnessSignatureHashRaw(script, sigHashes, hType, tx, idx, amt) -} - -// shallowCopyTx creates a shallow copy of the transaction for use when -// calculating the signature hash. It is used over the Copy method on the -// transaction itself since that is a deep copy and therefore does more work and -// allocates much more space than needed. -func shallowCopyTx(tx *wire.MsgTx) wire.MsgTx { - // As an additional memory optimization, use contiguous backing arrays - // for the copied inputs and outputs and point the final slice of - // pointers into the contiguous arrays. This avoids a lot of small - // allocations. - txCopy := wire.MsgTx{ - Version: tx.Version, - TxIn: make([]*wire.TxIn, len(tx.TxIn)), - TxOut: make([]*wire.TxOut, len(tx.TxOut)), - LockTime: tx.LockTime, - } - txIns := make([]wire.TxIn, len(tx.TxIn)) - for i, oldTxIn := range tx.TxIn { - txIns[i] = *oldTxIn - txCopy.TxIn[i] = &txIns[i] - } - txOuts := make([]wire.TxOut, len(tx.TxOut)) - for i, oldTxOut := range tx.TxOut { - txOuts[i] = *oldTxOut - txCopy.TxOut[i] = &txOuts[i] - } - return txCopy -} - -// CalcSignatureHash will, given a script and hash type for the current script -// engine instance, calculate the signature hash to be used for signing and -// verification. -// -// NOTE: This function is only valid for version 0 scripts. Since the function -// does not accept a script version, the results are undefined for other script -// versions. -func CalcSignatureHash(script []byte, hashType SigHashType, tx *wire.MsgTx, idx int) ([]byte, error) { - const scriptVersion = 0 - if err := checkScriptParses(scriptVersion, script); err != nil { - return nil, err - } - - return calcSignatureHash(script, hashType, tx, idx), nil -} - -// calcSignatureHash computes the signature hash for the specified input of the -// target transaction observing the desired signature hash type. -func calcSignatureHash(sigScript []byte, hashType SigHashType, tx *wire.MsgTx, idx int) []byte { - // The SigHashSingle signature type signs only the corresponding input - // and output (the output with the same index number as the input). - // - // Since transactions can have more inputs than outputs, this means it - // is improper to use SigHashSingle on input indices that don't have a - // corresponding output. - // - // A bug in the original Satoshi client implementation means specifying - // an index that is out of range results in a signature hash of 1 (as a - // uint256 little endian). The original intent appeared to be to - // indicate failure, but unfortunately, it was never checked and thus is - // treated as the actual signature hash. This buggy behavior is now - // part of the consensus and a hard fork would be required to fix it. - // - // Due to this, care must be taken by software that creates transactions - // which make use of SigHashSingle because it can lead to an extremely - // dangerous situation where the invalid inputs will end up signing a - // hash of 1. This in turn presents an opportunity for attackers to - // cleverly construct transactions which can steal those coins provided - // they can reuse signatures. - if hashType&sigHashMask == SigHashSingle && idx >= len(tx.TxOut) { - var hash chainhash.Hash - hash[0] = 0x01 - return hash[:] - } - - // Remove all instances of OP_CODESEPARATOR from the script. - sigScript = removeOpcodeRaw(sigScript, OP_CODESEPARATOR) - - // Make a shallow copy of the transaction, zeroing out the script for - // all inputs that are not currently being processed. - txCopy := shallowCopyTx(tx) - for i := range txCopy.TxIn { - if i == idx { - txCopy.TxIn[idx].SignatureScript = sigScript - } else { - txCopy.TxIn[i].SignatureScript = nil - } - } - - switch hashType & sigHashMask { - case SigHashNone: - txCopy.TxOut = txCopy.TxOut[0:0] // Empty slice. - for i := range txCopy.TxIn { - if i != idx { - txCopy.TxIn[i].Sequence = 0 - } - } - - case SigHashSingle: - // Resize output array to up to and including requested index. - txCopy.TxOut = txCopy.TxOut[:idx+1] - - // All but current output get zeroed out. - for i := 0; i < idx; i++ { - txCopy.TxOut[i].Value = -1 - txCopy.TxOut[i].PkScript = nil - } - - // Sequence on all other inputs is 0, too. - for i := range txCopy.TxIn { - if i != idx { - txCopy.TxIn[i].Sequence = 0 - } - } - - default: - // Consensus treats undefined hashtypes like normal SigHashAll - // for purposes of hash generation. - fallthrough - case SigHashOld: - fallthrough - case SigHashAll: - // Nothing special here. - } - if hashType&SigHashAnyOneCanPay != 0 { - txCopy.TxIn = txCopy.TxIn[idx : idx+1] - } - - // The final hash is the double sha256 of both the serialized modified - // transaction and the hash type (encoded as a 4-byte little-endian - // value) appended. - wbuf := bytes.NewBuffer(make([]byte, 0, txCopy.SerializeSizeStripped()+4)) - txCopy.SerializeNoWitness(wbuf) - binary.Write(wbuf, binary.LittleEndian, hashType) - return chainhash.DoubleHashB(wbuf.Bytes()) -} - // asSmallInt returns the passed opcode, which must be true according to // isSmallInt(), as an integer. func asSmallInt(op byte) int { diff --git a/txscript/sighash.go b/txscript/sighash.go new file mode 100644 index 00000000..1cee804b --- /dev/null +++ b/txscript/sighash.go @@ -0,0 +1,280 @@ +// Copyright (c) 2013-2017 The btcsuite developers +// Copyright (c) 2015-2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package txscript + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +// shallowCopyTx creates a shallow copy of the transaction for use when +// calculating the signature hash. It is used over the Copy method on the +// transaction itself since that is a deep copy and therefore does more work and +// allocates much more space than needed. +func shallowCopyTx(tx *wire.MsgTx) wire.MsgTx { + // As an additional memory optimization, use contiguous backing arrays + // for the copied inputs and outputs and point the final slice of + // pointers into the contiguous arrays. This avoids a lot of small + // allocations. + txCopy := wire.MsgTx{ + Version: tx.Version, + TxIn: make([]*wire.TxIn, len(tx.TxIn)), + TxOut: make([]*wire.TxOut, len(tx.TxOut)), + LockTime: tx.LockTime, + } + txIns := make([]wire.TxIn, len(tx.TxIn)) + for i, oldTxIn := range tx.TxIn { + txIns[i] = *oldTxIn + txCopy.TxIn[i] = &txIns[i] + } + txOuts := make([]wire.TxOut, len(tx.TxOut)) + for i, oldTxOut := range tx.TxOut { + txOuts[i] = *oldTxOut + txCopy.TxOut[i] = &txOuts[i] + } + return txCopy +} + +// CalcSignatureHash will, given a script and hash type for the current script +// engine instance, calculate the signature hash to be used for signing and +// verification. +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. +func CalcSignatureHash(script []byte, hashType SigHashType, tx *wire.MsgTx, idx int) ([]byte, error) { + const scriptVersion = 0 + if err := checkScriptParses(scriptVersion, script); err != nil { + return nil, err + } + + return calcSignatureHash(script, hashType, tx, idx), nil +} + +// calcSignatureHash computes the signature hash for the specified input of the +// target transaction observing the desired signature hash type. +func calcSignatureHash(sigScript []byte, hashType SigHashType, tx *wire.MsgTx, idx int) []byte { + // The SigHashSingle signature type signs only the corresponding input + // and output (the output with the same index number as the input). + // + // Since transactions can have more inputs than outputs, this means it + // is improper to use SigHashSingle on input indices that don't have a + // corresponding output. + // + // A bug in the original Satoshi client implementation means specifying + // an index that is out of range results in a signature hash of 1 (as a + // uint256 little endian). The original intent appeared to be to + // indicate failure, but unfortunately, it was never checked and thus is + // treated as the actual signature hash. This buggy behavior is now + // part of the consensus and a hard fork would be required to fix it. + // + // Due to this, care must be taken by software that creates transactions + // which make use of SigHashSingle because it can lead to an extremely + // dangerous situation where the invalid inputs will end up signing a + // hash of 1. This in turn presents an opportunity for attackers to + // cleverly construct transactions which can steal those coins provided + // they can reuse signatures. + if hashType&sigHashMask == SigHashSingle && idx >= len(tx.TxOut) { + var hash chainhash.Hash + hash[0] = 0x01 + return hash[:] + } + + // Remove all instances of OP_CODESEPARATOR from the script. + sigScript = removeOpcodeRaw(sigScript, OP_CODESEPARATOR) + + // Make a shallow copy of the transaction, zeroing out the script for + // all inputs that are not currently being processed. + txCopy := shallowCopyTx(tx) + for i := range txCopy.TxIn { + if i == idx { + txCopy.TxIn[idx].SignatureScript = sigScript + } else { + txCopy.TxIn[i].SignatureScript = nil + } + } + + switch hashType & sigHashMask { + case SigHashNone: + txCopy.TxOut = txCopy.TxOut[0:0] // Empty slice. + for i := range txCopy.TxIn { + if i != idx { + txCopy.TxIn[i].Sequence = 0 + } + } + + case SigHashSingle: + // Resize output array to up to and including requested index. + txCopy.TxOut = txCopy.TxOut[:idx+1] + + // All but current output get zeroed out. + for i := 0; i < idx; i++ { + txCopy.TxOut[i].Value = -1 + txCopy.TxOut[i].PkScript = nil + } + + // Sequence on all other inputs is 0, too. + for i := range txCopy.TxIn { + if i != idx { + txCopy.TxIn[i].Sequence = 0 + } + } + + default: + // Consensus treats undefined hashtypes like normal SigHashAll + // for purposes of hash generation. + fallthrough + case SigHashOld: + fallthrough + case SigHashAll: + // Nothing special here. + } + if hashType&SigHashAnyOneCanPay != 0 { + txCopy.TxIn = txCopy.TxIn[idx : idx+1] + } + + // The final hash is the double sha256 of both the serialized modified + // transaction and the hash type (encoded as a 4-byte little-endian + // value) appended. + wbuf := bytes.NewBuffer(make([]byte, 0, txCopy.SerializeSizeStripped()+4)) + txCopy.SerializeNoWitness(wbuf) + binary.Write(wbuf, binary.LittleEndian, hashType) + return chainhash.DoubleHashB(wbuf.Bytes()) +} + +// calcWitnessSignatureHashRaw computes the sighash digest of a transaction's +// segwit input using the new, optimized digest calculation algorithm defined +// in BIP0143: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki. +// This function makes use of pre-calculated sighash fragments stored within +// the passed HashCache to eliminate duplicate hashing computations when +// calculating the final digest, reducing the complexity from O(N^2) to O(N). +// Additionally, signatures now cover the input value of the referenced unspent +// output. This allows offline, or hardware wallets to compute the exact amount +// being spent, in addition to the final transaction fee. In the case the +// wallet if fed an invalid input amount, the real sighash will differ causing +// the produced signature to be invalid. +func calcWitnessSignatureHashRaw(subScript []byte, sigHashes *TxSigHashes, + hashType SigHashType, tx *wire.MsgTx, idx int, amt int64) ([]byte, error) { + + // As a sanity check, ensure the passed input index for the transaction + // is valid. + // + // TODO(roasbeef): check needs to be lifted elsewhere? + if idx > len(tx.TxIn)-1 { + return nil, fmt.Errorf("idx %d but %d txins", idx, len(tx.TxIn)) + } + + // We'll utilize this buffer throughout to incrementally calculate + // the signature hash for this transaction. + var sigHash bytes.Buffer + + // First write out, then encode the transaction's version number. + var bVersion [4]byte + binary.LittleEndian.PutUint32(bVersion[:], uint32(tx.Version)) + sigHash.Write(bVersion[:]) + + // Next write out the possibly pre-calculated hashes for the sequence + // numbers of all inputs, and the hashes of the previous outs for all + // outputs. + var zeroHash chainhash.Hash + + // If anyone can pay isn't active, then we can use the cached + // hashPrevOuts, otherwise we just write zeroes for the prev outs. + if hashType&SigHashAnyOneCanPay == 0 { + sigHash.Write(sigHashes.HashPrevOuts[:]) + } else { + sigHash.Write(zeroHash[:]) + } + + // If the sighash isn't anyone can pay, single, or none, the use the + // cached hash sequences, otherwise write all zeroes for the + // hashSequence. + if hashType&SigHashAnyOneCanPay == 0 && + hashType&sigHashMask != SigHashSingle && + hashType&sigHashMask != SigHashNone { + sigHash.Write(sigHashes.HashSequence[:]) + } else { + sigHash.Write(zeroHash[:]) + } + + txIn := tx.TxIn[idx] + + // Next, write the outpoint being spent. + sigHash.Write(txIn.PreviousOutPoint.Hash[:]) + var bIndex [4]byte + binary.LittleEndian.PutUint32(bIndex[:], txIn.PreviousOutPoint.Index) + sigHash.Write(bIndex[:]) + + if isWitnessPubKeyHashScript(subScript) { + // The script code for a p2wkh is a length prefix varint for + // the next 25 bytes, followed by a re-creation of the original + // p2pkh pk script. + sigHash.Write([]byte{0x19}) + sigHash.Write([]byte{OP_DUP}) + sigHash.Write([]byte{OP_HASH160}) + sigHash.Write([]byte{OP_DATA_20}) + sigHash.Write(extractWitnessPubKeyHash(subScript)) + sigHash.Write([]byte{OP_EQUALVERIFY}) + sigHash.Write([]byte{OP_CHECKSIG}) + } else { + // For p2wsh outputs, and future outputs, the script code is + // the original script, with all code separators removed, + // serialized with a var int length prefix. + wire.WriteVarBytes(&sigHash, 0, subScript) + } + + // Next, add the input amount, and sequence number of the input being + // signed. + var bAmount [8]byte + binary.LittleEndian.PutUint64(bAmount[:], uint64(amt)) + sigHash.Write(bAmount[:]) + var bSequence [4]byte + binary.LittleEndian.PutUint32(bSequence[:], txIn.Sequence) + sigHash.Write(bSequence[:]) + + // If the current signature mode isn't single, or none, then we can + // re-use the pre-generated hashoutputs sighash fragment. Otherwise, + // we'll serialize and add only the target output index to the signature + // pre-image. + if hashType&sigHashMask != SigHashSingle && + hashType&sigHashMask != SigHashNone { + sigHash.Write(sigHashes.HashOutputs[:]) + } else if hashType&sigHashMask == SigHashSingle && idx < len(tx.TxOut) { + var b bytes.Buffer + wire.WriteTxOut(&b, 0, 0, tx.TxOut[idx]) + sigHash.Write(chainhash.DoubleHashB(b.Bytes())) + } else { + sigHash.Write(zeroHash[:]) + } + + // Finally, write out the transaction's locktime, and the sig hash + // type. + var bLockTime [4]byte + binary.LittleEndian.PutUint32(bLockTime[:], tx.LockTime) + sigHash.Write(bLockTime[:]) + var bHashType [4]byte + binary.LittleEndian.PutUint32(bHashType[:], uint32(hashType)) + sigHash.Write(bHashType[:]) + + return chainhash.DoubleHashB(sigHash.Bytes()), nil +} + +// CalcWitnessSigHash computes the sighash digest for the specified input of +// the target transaction observing the desired sig hash type. +func CalcWitnessSigHash(script []byte, sigHashes *TxSigHashes, hType SigHashType, + tx *wire.MsgTx, idx int, amt int64) ([]byte, error) { + + const scriptVersion = 0 + if err := checkScriptParses(scriptVersion, script); err != nil { + return nil, err + } + + return calcWitnessSignatureHashRaw(script, sigHashes, hType, tx, idx, amt) +}