BIP0143+txscript: add segwit sighash, signing, and HashCache integration
This commit implements most of BIP0143 by adding logic to implement the new sighash calculation, signing, and additionally introduces the HashCache optimization which eliminates the O(N^2) computational complexity for the SIGHASH_ALL sighash type. The HashCache struct is the equivalent to the existing SigCache struct, but for caching the reusable midstate for transactions which are spending segwitty outputs.
This commit is contained in:
parent
0db14c740b
commit
98cae74275
9 changed files with 618 additions and 11 deletions
|
@ -704,6 +704,68 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
|
|||
vm.astack.verifyMinimalData = true
|
||||
}
|
||||
|
||||
// Check to see if we should execute in witness verification mode
|
||||
// according to the set flags. We check both the pkScript, and sigScript
|
||||
// here since in the case of nested p2sh, the scriptSig will be a valid
|
||||
// witness program. For nested p2sh, all the bytes after the first data
|
||||
// push should *exactly* match the witness program template.
|
||||
if vm.hasFlag(ScriptVerifyWitness) {
|
||||
// If witness evaluation is enabled, then P2SH MUST also be
|
||||
// active.
|
||||
if !vm.hasFlag(ScriptBip16) {
|
||||
errStr := "P2SH must be enabled to do witness verification"
|
||||
return nil, scriptError(ErrInvalidFlags, errStr)
|
||||
}
|
||||
|
||||
var witProgram []byte
|
||||
|
||||
switch {
|
||||
case isWitnessProgram(vm.scripts[1]):
|
||||
// The scriptSig must be *empty* for all native witness
|
||||
// programs, otherwise we introduce malleability.
|
||||
if len(scriptSig) != 0 {
|
||||
errStr := "native witness program cannot " +
|
||||
"also have a signature script"
|
||||
return nil, scriptError(ErrWitnessMalleated, errStr)
|
||||
}
|
||||
|
||||
witProgram = scriptPubKey
|
||||
case len(tx.TxIn[txIdx].Witness) != 0 && vm.bip16:
|
||||
// The sigScript MUST be *exactly* a single canonical
|
||||
// data push of the witness program, otherwise we
|
||||
// reintroduce malleability.
|
||||
dataPush := vm.scripts[0][0]
|
||||
if len(vm.scripts[0]) == 1 && canonicalPush(dataPush) &&
|
||||
IsWitnessProgram(dataPush.data) {
|
||||
|
||||
witProgram = dataPush.data
|
||||
} else {
|
||||
errStr := "signature script for witness " +
|
||||
"nested p2sh is not canonical"
|
||||
return nil, scriptError(ErrWitnessMalleatedP2SH, errStr)
|
||||
}
|
||||
}
|
||||
|
||||
if witProgram != nil {
|
||||
var err error
|
||||
vm.witnessVersion, vm.witnessProgram, err = ExtractWitnessProgramInfo(witProgram)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vm.witness = true
|
||||
} else {
|
||||
// If we didn't find a witness program in either the
|
||||
// pkScript or as a datapush within the sigScript, then
|
||||
// there MUST NOT be any witness data associated with
|
||||
// the input being validated.
|
||||
if !vm.witness && len(tx.TxIn[txIdx].Witness) != 0 {
|
||||
errStr := "non-witness inputs cannot have a witness"
|
||||
return nil, scriptError(ErrWitnessUnexpected, errStr)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
vm.tx = *tx
|
||||
vm.txIdx = txIdx
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ func ExampleSignTxOutput() {
|
|||
// contains a single output that pays to address in the amount of 1 BTC.
|
||||
originTx := wire.NewMsgTx(wire.TxVersion)
|
||||
prevOut := wire.NewOutPoint(&chainhash.Hash{}, ^uint32(0))
|
||||
txIn := wire.NewTxIn(prevOut, []byte{txscript.OP_0, txscript.OP_0})
|
||||
txIn := wire.NewTxIn(prevOut, []byte{txscript.OP_0, txscript.OP_0}, nil)
|
||||
originTx.AddTxIn(txIn)
|
||||
pkScript, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
|
@ -121,7 +121,7 @@ func ExampleSignTxOutput() {
|
|||
// signature script at this point since it hasn't been created or signed
|
||||
// yet, hence nil is provided for it.
|
||||
prevOut = wire.NewOutPoint(&originTxHash, 0)
|
||||
txIn = wire.NewTxIn(prevOut, nil)
|
||||
txIn = wire.NewTxIn(prevOut, nil, nil)
|
||||
redeemTx.AddTxIn(txIn)
|
||||
|
||||
// Ordinarily this would contain that actual destination of the funds,
|
||||
|
@ -166,7 +166,7 @@ func ExampleSignTxOutput() {
|
|||
txscript.ScriptStrictMultiSig |
|
||||
txscript.ScriptDiscourageUpgradableNops
|
||||
vm, err := txscript.NewEngine(originTx.TxOut[0].PkScript, redeemTx, 0,
|
||||
flags, nil)
|
||||
flags, nil, nil, -1)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
|
|
89
txscript/hashcache.go
Normal file
89
txscript/hashcache.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
// 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 txscript
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// TxSigHashes houses the partial set of sighashes introduced within BIP0143.
|
||||
// This partial set of sighashes may be re-used within each input across a
|
||||
// transaction when validating all inputs. As a result, validation complexity
|
||||
// for SigHashAll can be reduced by a polynomial factor.
|
||||
type TxSigHashes struct {
|
||||
HashPrevOuts chainhash.Hash
|
||||
HashSequence chainhash.Hash
|
||||
HashOutputs chainhash.Hash
|
||||
}
|
||||
|
||||
// NewTxSigHashes computes, and returns the cached sighashes of the given
|
||||
// transaction.
|
||||
func NewTxSigHashes(tx *wire.MsgTx) *TxSigHashes {
|
||||
return &TxSigHashes{
|
||||
HashPrevOuts: calcHashPrevOuts(tx),
|
||||
HashSequence: calcHashSequence(tx),
|
||||
HashOutputs: calcHashOutputs(tx),
|
||||
}
|
||||
}
|
||||
|
||||
// HashCache houses a set of partial sighashes keyed by txid. The set of partial
|
||||
// sighashes are those introduced within BIP0143 by the new more efficient
|
||||
// sighash digest calculation algorithm. Using this threadsafe shared cache,
|
||||
// multiple goroutines can safely re-use the pre-computed partial sighashes
|
||||
// speeding up validation time amongst all inputs found within a block.
|
||||
type HashCache struct {
|
||||
sigHashes map[chainhash.Hash]*TxSigHashes
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewHashCache returns a new instance of the HashCache given a maximum number
|
||||
// of entries which may exist within it at anytime.
|
||||
func NewHashCache(maxSize uint) *HashCache {
|
||||
return &HashCache{
|
||||
sigHashes: make(map[chainhash.Hash]*TxSigHashes, maxSize),
|
||||
}
|
||||
}
|
||||
|
||||
// AddSigHashes computes, then adds the partial sighashes for the passed
|
||||
// transaction.
|
||||
func (h *HashCache) AddSigHashes(tx *wire.MsgTx) {
|
||||
h.Lock()
|
||||
h.sigHashes[tx.TxHash()] = NewTxSigHashes(tx)
|
||||
h.Unlock()
|
||||
}
|
||||
|
||||
// ContainsHashes returns true if the partial sighashes for the passed
|
||||
// transaction currently exist within the HashCache, and false otherwise.
|
||||
func (h *HashCache) ContainsHashes(txid *chainhash.Hash) bool {
|
||||
h.RLock()
|
||||
_, found := h.sigHashes[*txid]
|
||||
h.RUnlock()
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
// GetSigHashes possibly returns the previously cached partial sighashes for
|
||||
// the passed transaction. This function also returns an additional boolean
|
||||
// value indicating if the sighashes for the passed transaction were found to
|
||||
// be present within the HashCache.
|
||||
func (h *HashCache) GetSigHashes(txid *chainhash.Hash) (*TxSigHashes, bool) {
|
||||
h.RLock()
|
||||
item, found := h.sigHashes[*txid]
|
||||
h.RUnlock()
|
||||
|
||||
return item, found
|
||||
}
|
||||
|
||||
// PurgeSigHashes removes all partial sighashes from the HashCache belonging to
|
||||
// the passed transaction.
|
||||
func (h *HashCache) PurgeSigHashes(txid *chainhash.Hash) {
|
||||
h.Lock()
|
||||
delete(h.sigHashes, *txid)
|
||||
h.Unlock()
|
||||
}
|
182
txscript/hashcache_test.go
Normal file
182
txscript/hashcache_test.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
// Copyright (c) 2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package txscript
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// genTestTx creates a random transaction for uses within test cases.
|
||||
func genTestTx() (*wire.MsgTx, error) {
|
||||
tx := wire.NewMsgTx(2)
|
||||
tx.Version = rand.Int31()
|
||||
|
||||
numTxins := rand.Intn(11)
|
||||
for i := 0; i < numTxins; i++ {
|
||||
randTxIn := wire.TxIn{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Index: uint32(rand.Int31()),
|
||||
},
|
||||
Sequence: uint32(rand.Int31()),
|
||||
}
|
||||
_, err := rand.Read(randTxIn.PreviousOutPoint.Hash[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx.TxIn = append(tx.TxIn, &randTxIn)
|
||||
}
|
||||
|
||||
numTxouts := rand.Intn(11)
|
||||
for i := 0; i < numTxouts; i++ {
|
||||
randTxOut := wire.TxOut{
|
||||
Value: rand.Int63(),
|
||||
PkScript: make([]byte, rand.Intn(30)),
|
||||
}
|
||||
if _, err := rand.Read(randTxOut.PkScript); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tx.TxOut = append(tx.TxOut, &randTxOut)
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
// TestHashCacheAddContainsHashes tests that after items have been added to the
|
||||
// hash cache, the ContainsHashes method returns true for all the items
|
||||
// inserted. Conversely, ContainsHashes should return false for any items
|
||||
// _not_ in the hash cache.
|
||||
func TestHashCacheAddContainsHashes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rand.Seed(time.Now().Unix())
|
||||
|
||||
cache := NewHashCache(10)
|
||||
|
||||
var err error
|
||||
|
||||
// First, well generate 10 random transactions for use within our
|
||||
// tests.
|
||||
const numTxns = 10
|
||||
txns := make([]*wire.MsgTx, numTxns)
|
||||
for i := 0; i < numTxns; i++ {
|
||||
txns[i], err = genTestTx()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate test tx: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// With the transactions generated, we'll add each of them to the hash
|
||||
// cache.
|
||||
for _, tx := range txns {
|
||||
cache.AddSigHashes(tx)
|
||||
}
|
||||
|
||||
// Next, we'll ensure that each of the transactions inserted into the
|
||||
// cache are properly located by the ContainsHashes method.
|
||||
for _, tx := range txns {
|
||||
txid := tx.TxHash()
|
||||
if ok := cache.ContainsHashes(&txid); !ok {
|
||||
t.Fatalf("txid %v not found in cache but should be: ",
|
||||
txid)
|
||||
}
|
||||
}
|
||||
|
||||
randTx, err := genTestTx()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate tx: %v", err)
|
||||
}
|
||||
|
||||
// Finally, we'll assert that a transaction that wasn't added to the
|
||||
// cache won't be reported as being present by the ContainsHashes
|
||||
// method.
|
||||
randTxid := randTx.TxHash()
|
||||
if ok := cache.ContainsHashes(&randTxid); ok {
|
||||
t.Fatalf("txid %v wasn't inserted into cache but was found",
|
||||
randTxid)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHashCacheAddGet tests that the sighahes for a particular transaction
|
||||
// care properly retrieved by the GetSigHashes function.
|
||||
func TestHashCacheAddGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rand.Seed(time.Now().Unix())
|
||||
|
||||
cache := NewHashCache(10)
|
||||
|
||||
// To start, we'll generate a random transaction and compute the set of
|
||||
// sighashes for the transaction.
|
||||
randTx, err := genTestTx()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate tx: %v", err)
|
||||
}
|
||||
sigHashes := NewTxSigHashes(randTx)
|
||||
|
||||
// Next, add the transaction to the hash cache.
|
||||
cache.AddSigHashes(randTx)
|
||||
|
||||
// The transaction inserted into the cache above should be found.
|
||||
txid := randTx.TxHash()
|
||||
cacheHashes, ok := cache.GetSigHashes(&txid)
|
||||
if !ok {
|
||||
t.Fatalf("tx %v wasn't found in cache", txid)
|
||||
}
|
||||
|
||||
// Finally, the sighashes retrieved should exactly match the sighash
|
||||
// originally inserted into the cache.
|
||||
if *sigHashes != *cacheHashes {
|
||||
t.Fatalf("sighashes don't match: expected %v, got %v",
|
||||
spew.Sdump(sigHashes), spew.Sdump(cacheHashes))
|
||||
}
|
||||
}
|
||||
|
||||
// TestHashCachePurge tests that items are able to be properly removed from the
|
||||
// hash cache.
|
||||
func TestHashCachePurge(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rand.Seed(time.Now().Unix())
|
||||
|
||||
cache := NewHashCache(10)
|
||||
|
||||
var err error
|
||||
|
||||
// First we'll start by inserting numTxns transactions into the hash cache.
|
||||
const numTxns = 10
|
||||
txns := make([]*wire.MsgTx, numTxns)
|
||||
for i := 0; i < numTxns; i++ {
|
||||
txns[i], err = genTestTx()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate test tx: %v", err)
|
||||
}
|
||||
}
|
||||
for _, tx := range txns {
|
||||
cache.AddSigHashes(tx)
|
||||
}
|
||||
|
||||
// Once all the transactions have been inserted, we'll purge them from
|
||||
// the hash cache.
|
||||
for _, tx := range txns {
|
||||
txid := tx.TxHash()
|
||||
cache.PurgeSigHashes(&txid)
|
||||
}
|
||||
|
||||
// At this point, non of the transaction inserted into the hash cache
|
||||
// should be found within the ache.
|
||||
for _, tx := range txns {
|
||||
txid := tx.TxHash()
|
||||
if ok := cache.ContainsHashes(&txid); ok {
|
||||
t.Fatalf("tx %v found in cache but should have "+
|
||||
"been purged: ", txid)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2050,7 +2050,23 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
|||
subScript = removeOpcodeByData(subScript, fullSigBytes)
|
||||
|
||||
// Generate the signature hash based on the signature hash type.
|
||||
hash := calcSignatureHash(subScript, hashType, &vm.tx, vm.txIdx)
|
||||
var hash []byte
|
||||
if vm.witness {
|
||||
var sigHashes *TxSigHashes
|
||||
if vm.hashCache != nil {
|
||||
sigHashes = vm.hashCache
|
||||
} else {
|
||||
sigHashes = NewTxSigHashes(&vm.tx)
|
||||
}
|
||||
|
||||
hash, err = calcWitnessSignatureHash(subScript, sigHashes, hashType,
|
||||
&vm.tx, vm.txIdx, vm.inputAmount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
hash = calcSignatureHash(subScript, hashType, &vm.tx, vm.txIdx)
|
||||
}
|
||||
|
||||
pubKey, err := btcec.ParsePubKey(pkBytes, btcec.S256())
|
||||
if err != nil {
|
||||
|
@ -2301,7 +2317,23 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
|||
}
|
||||
|
||||
// Generate the signature hash based on the signature hash type.
|
||||
hash := calcSignatureHash(script, hashType, &vm.tx, vm.txIdx)
|
||||
var hash []byte
|
||||
if vm.witness {
|
||||
var sigHashes *TxSigHashes
|
||||
if vm.hashCache != nil {
|
||||
sigHashes = vm.hashCache
|
||||
} else {
|
||||
sigHashes = NewTxSigHashes(&vm.tx)
|
||||
}
|
||||
|
||||
hash, err = calcWitnessSignatureHash(script, sigHashes, hashType,
|
||||
&vm.tx, vm.txIdx, vm.inputAmount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
hash = calcSignatureHash(script, hashType, &vm.tx, vm.txIdx)
|
||||
}
|
||||
|
||||
var valid bool
|
||||
if vm.sigCache != nil {
|
||||
|
|
|
@ -278,6 +278,190 @@ func removeOpcodeByData(pkscript []parsedOpcode, data []byte) []parsedOpcode {
|
|||
|
||||
}
|
||||
|
||||
// calcHashPrevOuts calculates a single hash of all the previous outputs
|
||||
// (txid:index) referenced within the passed transaction. This calculated hash
|
||||
// can be re-used when validating all inputs spending segwit outputs, with a
|
||||
// signature hash type of SigHashAll. This allows validation to re-use previous
|
||||
// hashing computation, reducing the complexity of validating SigHashAll inputs
|
||||
// from O(N^2) to O(N).
|
||||
func calcHashPrevOuts(tx *wire.MsgTx) chainhash.Hash {
|
||||
var b bytes.Buffer
|
||||
for _, in := range tx.TxIn {
|
||||
// First write out the 32-byte transaction ID one of whose
|
||||
// outputs are being referenced by this input.
|
||||
b.Write(in.PreviousOutPoint.Hash[:])
|
||||
|
||||
// Next, we'll encode the index of the referenced output as a
|
||||
// little endian integer.
|
||||
var buf [4]byte
|
||||
binary.LittleEndian.PutUint32(buf[:], in.PreviousOutPoint.Index)
|
||||
b.Write(buf[:])
|
||||
}
|
||||
|
||||
return chainhash.DoubleHashH(b.Bytes())
|
||||
}
|
||||
|
||||
// calcHashSequence computes an aggregated hash of each of the sequence numbers
|
||||
// within the inputs of the passed transaction. This single hash can be re-used
|
||||
// when validating all inputs spending segwit outputs, which include signatures
|
||||
// using the SigHashAll sighash type. This allows validation to re-use previous
|
||||
// hashing computation, reducing the complexity of validating SigHashAll inputs
|
||||
// from O(N^2) to O(N).
|
||||
func calcHashSequence(tx *wire.MsgTx) chainhash.Hash {
|
||||
var b bytes.Buffer
|
||||
for _, in := range tx.TxIn {
|
||||
var buf [4]byte
|
||||
binary.LittleEndian.PutUint32(buf[:], in.Sequence)
|
||||
b.Write(buf[:])
|
||||
}
|
||||
|
||||
return chainhash.DoubleHashH(b.Bytes())
|
||||
}
|
||||
|
||||
// calcHashOutputs computes a hash digest of all outputs created by the
|
||||
// transaction encoded using the wire format. This single hash can be re-used
|
||||
// when validating all inputs spending witness programs, which include
|
||||
// signatures using the SigHashAll sighash type. This allows computation to be
|
||||
// cached, reducing the total hashing complexity from O(N^2) to O(N).
|
||||
func calcHashOutputs(tx *wire.MsgTx) chainhash.Hash {
|
||||
var b bytes.Buffer
|
||||
for _, out := range tx.TxOut {
|
||||
wire.WriteTxOut(&b, 0, 0, out)
|
||||
}
|
||||
|
||||
return chainhash.DoubleHashH(b.Bytes())
|
||||
}
|
||||
|
||||
// calcWitnessSignatureHash 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 calcWitnessSignatureHash(subScript []parsedOpcode, 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 isWitnessPubKeyHash(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(subScript[1].data)
|
||||
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.
|
||||
rawScript, _ := unparseScript(subScript)
|
||||
wire.WriteVarBytes(&sigHash, 0, rawScript)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
parsedScript, err := parseScript(script)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse output script: %v", err)
|
||||
}
|
||||
|
||||
return calcWitnessSignatureHash(parsedScript, sigHashes, hType, tx, idx,
|
||||
amt)
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -367,8 +551,8 @@ func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.Msg
|
|||
// 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.SerializeSize()+4))
|
||||
txCopy.Serialize(wbuf)
|
||||
wbuf := bytes.NewBuffer(make([]byte, 0, txCopy.SerializeSizeStripped()+4))
|
||||
txCopy.SerializeNoWitness(wbuf)
|
||||
binary.Write(wbuf, binary.LittleEndian, hashType)
|
||||
return chainhash.DoubleHashB(wbuf.Bytes())
|
||||
}
|
||||
|
|
|
@ -14,6 +14,61 @@ import (
|
|||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
// RawTxInWitnessSignature returns the serialized ECDA signature for the input
|
||||
// idx of the given transaction, with the hashType appended to it. This
|
||||
// function is identical to RawTxInSignature, however the signature generated
|
||||
// signs a new sighash digest defined in BIP0143.
|
||||
func RawTxInWitnessSignature(tx *wire.MsgTx, sigHashes *TxSigHashes, idx int,
|
||||
amt int64, subScript []byte, hashType SigHashType,
|
||||
key *btcec.PrivateKey) ([]byte, error) {
|
||||
|
||||
parsedScript, err := parseScript(subScript)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse output script: %v", err)
|
||||
}
|
||||
|
||||
hash, err := calcWitnessSignatureHash(parsedScript, sigHashes, hashType, tx,
|
||||
idx, amt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := key.Sign(hash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot sign tx input: %s", err)
|
||||
}
|
||||
|
||||
return append(signature.Serialize(), byte(hashType)), nil
|
||||
}
|
||||
|
||||
// WitnessSignature creates an input witness stack for tx to spend BTC sent
|
||||
// from a previous output to the owner of privKey using the p2wkh script
|
||||
// template. The passed transaction must contain all the inputs and outputs as
|
||||
// dictated by the passed hashType. The signature generated observes the new
|
||||
// transaction digest algorithm defined within BIP0143.
|
||||
func WitnessSignature(tx *wire.MsgTx, sigHashes *TxSigHashes, idx int, amt int64,
|
||||
subscript []byte, hashType SigHashType, privKey *btcec.PrivateKey,
|
||||
compress bool) (wire.TxWitness, error) {
|
||||
|
||||
sig, err := RawTxInWitnessSignature(tx, sigHashes, idx, amt, subscript,
|
||||
hashType, privKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pk := (*btcec.PublicKey)(&privKey.PublicKey)
|
||||
var pkData []byte
|
||||
if compress {
|
||||
pkData = pk.SerializeCompressed()
|
||||
} else {
|
||||
pkData = pk.SerializeUncompressed()
|
||||
}
|
||||
|
||||
// A witness script is actually a stack, so we return an array of byte
|
||||
// slices here, rather than a single byte slice.
|
||||
return wire.TxWitness{sig, pkData}, nil
|
||||
}
|
||||
|
||||
// RawTxInSignature returns the serialized ECDSA signature for the input idx of
|
||||
// the given transaction, with hashType appended to it.
|
||||
func RawTxInSignature(tx *wire.MsgTx, idx int, subScript []byte,
|
||||
|
|
|
@ -236,7 +236,7 @@ func BenchmarkReadTxOut(b *testing.B) {
|
|||
func BenchmarkWriteTxOut(b *testing.B) {
|
||||
txOut := blockOne.Transactions[0].TxOut[0]
|
||||
for i := 0; i < b.N; i++ {
|
||||
writeTxOut(ioutil.Discard, 0, 0, txOut)
|
||||
WriteTxOut(ioutil.Discard, 0, 0, txOut)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -707,7 +707,7 @@ func (msg *MsgTx) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) error
|
|||
}
|
||||
|
||||
for _, to := range msg.TxOut {
|
||||
err = writeTxOut(w, pver, msg.Version, to)
|
||||
err = WriteTxOut(w, pver, msg.Version, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -981,9 +981,12 @@ func readTxOut(r io.Reader, pver uint32, version int32, to *TxOut) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// writeTxOut encodes to into the bitcoin protocol encoding for a transaction
|
||||
// WriteTxOut encodes to into the bitcoin protocol encoding for a transaction
|
||||
// output (TxOut) to w.
|
||||
func writeTxOut(w io.Writer, pver uint32, version int32, to *TxOut) error {
|
||||
//
|
||||
// NOTE: This function is exported in order to allow txscript to compute the
|
||||
// new sighashes for witness transactions (BIP0143).
|
||||
func WriteTxOut(w io.Writer, pver uint32, version int32, to *TxOut) error {
|
||||
err := binarySerializer.PutUint64(w, littleEndian, uint64(to.Value))
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
Loading…
Reference in a new issue