lnd.xprv/lnwallet/btcwallet/signer.go
Johan T. Halseth f12dfe2c45
lnwallet/btcwallet: Use signDesc.HashType when signing
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.
2017-11-06 14:31:03 +01:00

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)