f12dfe2c45
Tis commit makes the btcwallet signer implementation use signDesc.HashType instead of SigHashAll when signing transactions. This will allow the creator of the transaction to specify the sighash policy when creating the accompanying sign descriptior.
273 lines
8.1 KiB
Go
273 lines
8.1 KiB
Go
package btcwallet
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/go-errors/errors"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/roasbeef/btcd/btcec"
|
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
|
"github.com/roasbeef/btcd/txscript"
|
|
"github.com/roasbeef/btcd/wire"
|
|
"github.com/roasbeef/btcutil"
|
|
"github.com/roasbeef/btcwallet/waddrmgr"
|
|
base "github.com/roasbeef/btcwallet/wallet"
|
|
)
|
|
|
|
// FetchInputInfo queries for the WalletController's knowledge of the passed
|
|
// outpoint. If the base wallet determines this output is under its control,
|
|
// then the original txout should be returned. Otherwise, a non-nil error value
|
|
// of ErrNotMine should be returned instead.
|
|
//
|
|
// This is a part of the WalletController interface.
|
|
func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*wire.TxOut, error) {
|
|
var (
|
|
err error
|
|
output *wire.TxOut
|
|
)
|
|
|
|
// First check to see if the output is already within the utxo cache.
|
|
// If so we can return directly saving a disk access.
|
|
b.cacheMtx.RLock()
|
|
if output, ok := b.utxoCache[*prevOut]; ok {
|
|
b.cacheMtx.RUnlock()
|
|
return output, nil
|
|
}
|
|
b.cacheMtx.RUnlock()
|
|
|
|
// Otherwse, we manually look up the output within the tx store.
|
|
txid := &prevOut.Hash
|
|
txDetail, err := base.UnstableAPI(b.wallet).TxDetails(txid)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if txDetail == nil {
|
|
return nil, lnwallet.ErrNotMine
|
|
}
|
|
|
|
output = txDetail.TxRecord.MsgTx.TxOut[prevOut.Index]
|
|
|
|
b.cacheMtx.Lock()
|
|
b.utxoCache[*prevOut] = output
|
|
b.cacheMtx.Unlock()
|
|
|
|
return output, nil
|
|
}
|
|
|
|
// fetchOutputKey attempts to fetch the managed address corresponding to the
|
|
// passed output script. This function is used to look up the proper key which
|
|
// should be used to sign a specified input.
|
|
func (b *BtcWallet) fetchOutputAddr(script []byte) (waddrmgr.ManagedAddress, error) {
|
|
_, addrs, _, err := txscript.ExtractPkScriptAddrs(script, b.netParams)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If the case of a multi-sig output, several address may be extracted.
|
|
// Therefore, we simply select the key for the first address we know
|
|
// of.
|
|
for _, addr := range addrs {
|
|
addr, err := b.wallet.AddressInfo(addr)
|
|
if err == nil {
|
|
return addr, nil
|
|
}
|
|
}
|
|
|
|
// TODO(roasbeef): use the errors.wrap package
|
|
return nil, fmt.Errorf("address not found")
|
|
}
|
|
|
|
// fetchPrivKey attempts to retrieve the raw private key corresponding to the
|
|
// passed public key.
|
|
// TODO(roasbeef): alternatively can extract all the data pushes within the
|
|
// script, then attempt to match keys one by one
|
|
func (b *BtcWallet) fetchPrivKey(pub *btcec.PublicKey) (*btcec.PrivateKey, error) {
|
|
hash160 := btcutil.Hash160(pub.SerializeCompressed())
|
|
addr, err := btcutil.NewAddressWitnessPubKeyHash(hash160, b.netParams)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return b.wallet.PrivKeyForAddress(addr)
|
|
}
|
|
|
|
// maybeTweakPrivKey examines the single and double tweak parameters on the
|
|
// passed sign descriptor and may perform a mapping on the passed private key
|
|
// in order to utilize the tweaks, if populated.
|
|
func maybeTweakPrivKey(signDesc *lnwallet.SignDescriptor,
|
|
privKey *btcec.PrivateKey) (*btcec.PrivateKey, error) {
|
|
|
|
var retPriv *btcec.PrivateKey
|
|
switch {
|
|
|
|
case signDesc.SingleTweak != nil:
|
|
retPriv = lnwallet.TweakPrivKey(privKey,
|
|
signDesc.SingleTweak)
|
|
|
|
case signDesc.DoubleTweak != nil:
|
|
retPriv = lnwallet.DeriveRevocationPrivKey(privKey,
|
|
signDesc.DoubleTweak)
|
|
|
|
default:
|
|
retPriv = privKey
|
|
}
|
|
|
|
return retPriv, nil
|
|
}
|
|
|
|
// SignOutputRaw generates a signature for the passed transaction according to
|
|
// the data within the passed SignDescriptor.
|
|
//
|
|
// This is a part of the WalletController interface.
|
|
func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx,
|
|
signDesc *lnwallet.SignDescriptor) ([]byte, error) {
|
|
witnessScript := signDesc.WitnessScript
|
|
|
|
// First attempt to fetch the private key which corresponds to the
|
|
// specified public key.
|
|
privKey, err := b.fetchPrivKey(signDesc.PubKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If a tweak (single or double) is specified, then we'll need to use
|
|
// this tweak to derive the final private key to be used for signing
|
|
// this output.
|
|
privKey, err = maybeTweakPrivKey(signDesc, privKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO(roasbeef): generate sighash midstate if not present?
|
|
|
|
amt := signDesc.Output.Value
|
|
sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes,
|
|
signDesc.InputIndex, amt, witnessScript, signDesc.HashType,
|
|
privKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Chop off the sighash flag at the end of the signature.
|
|
return sig[:len(sig)-1], nil
|
|
}
|
|
|
|
// ComputeInputScript generates a complete InputIndex for the passed
|
|
// transaction with the signature as defined within the passed SignDescriptor.
|
|
// This method is capable of generating the proper input script for both
|
|
// regular p2wkh output and p2wkh outputs nested within a regular p2sh output.
|
|
//
|
|
// This is a part of the WalletController interface.
|
|
func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx,
|
|
signDesc *lnwallet.SignDescriptor) (*lnwallet.InputScript, error) {
|
|
|
|
outputScript := signDesc.Output.PkScript
|
|
walletAddr, err := b.fetchOutputAddr(outputScript)
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
|
|
pka := walletAddr.(waddrmgr.ManagedPubKeyAddress)
|
|
privKey, err := pka.PrivKey()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var witnessProgram []byte
|
|
inputScript := &lnwallet.InputScript{}
|
|
|
|
switch {
|
|
|
|
// If we're spending p2wkh output nested within a p2sh output, then
|
|
// we'll need to attach a sigScript in addition to witness data.
|
|
case pka.IsNestedWitness():
|
|
pubKey := privKey.PubKey()
|
|
pubKeyHash := btcutil.Hash160(pubKey.SerializeCompressed())
|
|
|
|
// 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,
|
|
b.netParams)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
witnessProgram, err = txscript.PayToAddrScript(p2wkhAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bldr := txscript.NewScriptBuilder()
|
|
bldr.AddData(witnessProgram)
|
|
sigScript, err := bldr.Script()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
inputScript.ScriptSig = sigScript
|
|
|
|
// Otherwise, this is a regular p2wkh output, so we include the
|
|
// witness program itself as the subscript to generate the proper
|
|
// sighash digest. As part of the new sighash digest algorithm, the
|
|
// p2wkh witness program will be expanded into a regular p2kh
|
|
// script.
|
|
default:
|
|
witnessProgram = outputScript
|
|
}
|
|
|
|
// If a tweak (single or double) is specified, then we'll need to use
|
|
// this tweak to derive the final private key to be used for signing
|
|
// this output.
|
|
privKey, err = maybeTweakPrivKey(signDesc, privKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Generate a valid witness stack for the input.
|
|
// TODO(roasbeef): adhere to passed HashType
|
|
witnessScript, err := txscript.WitnessSignature(tx, signDesc.SigHashes,
|
|
signDesc.InputIndex, signDesc.Output.Value, witnessProgram,
|
|
signDesc.HashType, privKey, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
inputScript.Witness = witnessScript
|
|
|
|
return inputScript, nil
|
|
}
|
|
|
|
// A compile time check to ensure that BtcWallet implements the Signer
|
|
// interface.
|
|
var _ lnwallet.Signer = (*BtcWallet)(nil)
|
|
|
|
// SignMessage attempts to sign a target message with the private key that
|
|
// corresponds to the passed public key. If the target private key is unable to
|
|
// be found, then an error will be returned. The actual digest signed is the
|
|
// double SHA-256 of the passed message.
|
|
//
|
|
// NOTE: This is a part of the MessageSigner interface.
|
|
func (b *BtcWallet) SignMessage(pubKey *btcec.PublicKey,
|
|
msg []byte) (*btcec.Signature, error) {
|
|
|
|
// First attempt to fetch the private key which corresponds to the
|
|
// specified public key.
|
|
privKey, err := b.fetchPrivKey(pubKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Double hash and sign the data.
|
|
msgDigest := chainhash.DoubleHashB(msg)
|
|
sign, err := privKey.Sign(msgDigest)
|
|
if err != nil {
|
|
return nil, errors.Errorf("unable sign the message: %v", err)
|
|
}
|
|
|
|
return sign, nil
|
|
}
|
|
|
|
// A compile time check to ensure that BtcWallet implements the MessageSigner
|
|
// interface.
|
|
var _ lnwallet.MessageSigner = (*BtcWallet)(nil)
|