github.com/decred/dcrlnd@v0.7.6/lnwallet/dcrwallet/signer.go (about)

     1  package dcrwallet
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"decred.org/dcrwallet/v4/errors"
     8  	base "decred.org/dcrwallet/v4/wallet"
     9  	"github.com/decred/dcrd/chaincfg/chainhash"
    10  	"github.com/decred/dcrd/dcrec"
    11  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    12  	"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
    13  	"github.com/decred/dcrd/dcrutil/v4"
    14  	"github.com/decred/dcrd/txscript/v4"
    15  	"github.com/decred/dcrd/txscript/v4/sign"
    16  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    17  	"github.com/decred/dcrd/txscript/v4/stdscript"
    18  	"github.com/decred/dcrd/wire"
    19  	"github.com/decred/dcrlnd/btcwalletcompat"
    20  	"github.com/decred/dcrlnd/input"
    21  	"github.com/decred/dcrlnd/internal/psbt"
    22  	"github.com/decred/dcrlnd/keychain"
    23  	"github.com/decred/dcrlnd/lnwallet"
    24  	"github.com/decred/dcrlnd/lnwallet/chainfee"
    25  )
    26  
    27  // FetchInputInfo queries for the WalletController's knowledge of the passed
    28  // outpoint. If the base wallet determines this output is under its control,
    29  // then the original txout should be returned. Otherwise, a non-nil error value
    30  // of ErrNotMine should be returned instead.
    31  //
    32  // This is a part of the WalletController interface.
    33  func (b *DcrWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo, error) {
    34  	// We manually look up the output within the tx store.
    35  	txid := &prevOut.Hash
    36  	txDetail, err := base.UnstableAPI(b.wallet).TxDetails(context.TODO(), txid)
    37  	if err != nil {
    38  		if errors.Is(err, errors.NotExist) {
    39  			return nil, lnwallet.ErrNotMine
    40  		}
    41  		return nil, err
    42  	} else if txDetail == nil {
    43  		return nil, lnwallet.ErrNotMine
    44  	}
    45  
    46  	numOutputs := uint32(len(txDetail.TxRecord.MsgTx.TxOut))
    47  	if prevOut.Index >= numOutputs {
    48  		return nil, fmt.Errorf("invalid output index %v for "+
    49  			"transaction with %v outputs", prevOut.Index, numOutputs)
    50  
    51  	}
    52  
    53  	// With the output retrieved, we'll make an additional check to ensure
    54  	// we actually have control of this output. We do this because the check
    55  	// above only guarantees that the transaction is somehow relevant to us,
    56  	// like in the event of us being the sender of the transaction.
    57  	output := txDetail.TxRecord.MsgTx.TxOut[prevOut.Index]
    58  	if _, err := b.fetchOutputAddr(output.Version, output.PkScript); err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	// Then, we'll populate all of the information required by the struct.
    63  	addressType := lnwallet.UnknownAddressType
    64  	scriptClass := stdscript.DetermineScriptType(output.Version, output.PkScript)
    65  	switch scriptClass {
    66  	case stdscript.STPubKeyHashEcdsaSecp256k1:
    67  		addressType = lnwallet.PubKeyHash
    68  	}
    69  
    70  	// Determine the number of confirmations the output currently has.
    71  	_, currentHeight := b.wallet.MainChainTip(context.TODO())
    72  	confs := int64(0)
    73  	if txDetail.Block.Height != -1 {
    74  		confs = int64(currentHeight - txDetail.Block.Height)
    75  
    76  	}
    77  
    78  	return &lnwallet.Utxo{
    79  		AddressType: addressType,
    80  		Value: dcrutil.Amount(
    81  			txDetail.TxRecord.MsgTx.TxOut[prevOut.Index].Value,
    82  		),
    83  		PkScript:      output.PkScript,
    84  		Confirmations: confs,
    85  		OutPoint:      *prevOut,
    86  		PrevTx:        &txDetail.MsgTx,
    87  	}, nil
    88  
    89  }
    90  
    91  // fetchOutputAddr attempts to fetch the managed address corresponding to the
    92  // passed output script. This function is used to look up the proper key which
    93  // should be used to sign a specified input.
    94  func (b *DcrWallet) fetchOutputAddr(scriptVersion uint16, script []byte) (base.KnownAddress, error) {
    95  	_, addrs := stdscript.ExtractAddrs(scriptVersion, script,
    96  		b.netParams)
    97  
    98  	// If the case of a multi-sig output, several address may be extracted.
    99  	// Therefore, we simply select the key for the first address we know
   100  	// of.
   101  	for _, addr := range addrs {
   102  		addr, err := b.wallet.KnownAddress(context.TODO(), addr)
   103  		if err == nil {
   104  			return addr, nil
   105  		}
   106  	}
   107  
   108  	return nil, lnwallet.ErrNotMine
   109  }
   110  
   111  // maybeTweakPrivKey examines the single and double tweak parameters on the
   112  // passed sign descriptor and may perform a mapping on the passed private key
   113  // in order to utilize the tweaks, if populated.
   114  func maybeTweakPrivKey(signDesc *input.SignDescriptor,
   115  	privKey *secp256k1.PrivateKey) (*secp256k1.PrivateKey, error) {
   116  
   117  	var retPriv *secp256k1.PrivateKey
   118  	switch {
   119  
   120  	case signDesc.SingleTweak != nil:
   121  		retPriv = input.TweakPrivKey(privKey,
   122  			signDesc.SingleTweak)
   123  
   124  	case signDesc.DoubleTweak != nil:
   125  		retPriv = input.DeriveRevocationPrivKey(privKey,
   126  			signDesc.DoubleTweak)
   127  
   128  	default:
   129  		retPriv = privKey
   130  	}
   131  
   132  	return retPriv, nil
   133  }
   134  
   135  // SignOutputRaw generates a signature for the passed transaction according to
   136  // the data within the passed input.SignDescriptor.
   137  //
   138  // This is a part of the WalletController interface.
   139  func (b *DcrWallet) SignOutputRaw(tx *wire.MsgTx,
   140  	signDesc *input.SignDescriptor) (input.Signature, error) {
   141  
   142  	witnessScript := signDesc.WitnessScript
   143  
   144  	// First attempt to fetch the private key which corresponds to the
   145  	// specified public key.
   146  	privKey, err := b.DerivePrivKey(signDesc.KeyDesc)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	// If a tweak (single or double) is specified, then we'll need to use
   152  	// this tweak to derive the final private key to be used for signing
   153  	// this output.
   154  	privKey, err = maybeTweakPrivKey(signDesc, privKey)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	// TODO(roasbeef): generate sighash midstate if not present?
   160  	// TODO(decred): use cached prefix hash in signDesc.sigHashes
   161  	sig, err := sign.RawTxInSignature(
   162  		tx, signDesc.InputIndex,
   163  		witnessScript, signDesc.HashType, privKey.Serialize(),
   164  		dcrec.STEcdsaSecp256k1,
   165  	)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	// Chop off the sighash flag at the end of the signature.
   171  	return ecdsa.ParseDERSignature(sig[:len(sig)-1])
   172  }
   173  
   174  // p2pkhSigScriptToWitness converts a raw sigScript that signs a p2pkh output
   175  // into a list of individual data pushes, amenable to be used a pseudo-witness
   176  // stack.
   177  func p2pkhSigScriptToWitness(scriptVersion uint16, sigScript []byte) ([][]byte, error) {
   178  	data := make([][]byte, 0, 2)
   179  	tokenizer := txscript.MakeScriptTokenizer(scriptVersion, sigScript)
   180  	for tokenizer.Next() && len(data) < 2 {
   181  		if tokenizer.Data() == nil {
   182  			return nil, errors.New("expected pushed data in p2pkh " +
   183  				"sigScript but found none")
   184  		}
   185  		data = append(data, tokenizer.Data())
   186  	}
   187  	if err := tokenizer.Err(); err != nil {
   188  		return nil, err
   189  
   190  	}
   191  	if len(data) != 2 {
   192  		return nil, fmt.Errorf("expected p2pkh sigScript to have 2 "+
   193  			"data pushes but found %d", len(data))
   194  	}
   195  	return data, nil
   196  
   197  }
   198  
   199  // ComputeInputScript generates a complete InputScript for the passed
   200  // transaction with the signature as defined within the passed input.SignDescriptor.
   201  // This method is capable of generating the proper input script only for
   202  // regular p2pkh outputs.
   203  //
   204  // This is a part of the WalletController interface.
   205  func (b *DcrWallet) ComputeInputScript(tx *wire.MsgTx,
   206  	signDesc *input.SignDescriptor) (*input.Script, error) {
   207  
   208  	outputScript := signDesc.Output.PkScript
   209  	outputScriptVer := signDesc.Output.Version
   210  	walletAddr, err := b.fetchOutputAddr(outputScriptVer, outputScript)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  
   215  	// Fetch the private key for the given wallet address.
   216  	addr, err := stdaddr.DecodeAddress(walletAddr.String(), b.netParams)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  	privKeyWifStr, err := b.wallet.DumpWIFPrivateKey(context.TODO(), addr)
   221  	if err != nil {
   222  		return nil, fmt.Errorf("invalid wif string for address: %v", err)
   223  	}
   224  	privKeyWif, err := dcrutil.DecodeWIF(privKeyWifStr, b.netParams.PrivateKeyID)
   225  	if err != nil {
   226  		return nil, fmt.Errorf("error decoding wif string for address: %v", err)
   227  	}
   228  	if privKeyWif.DSA() != dcrec.STEcdsaSecp256k1 {
   229  		return nil, fmt.Errorf("private key returned is not secp256k1")
   230  	}
   231  	privKeyBytes := privKeyWif.PrivKey()
   232  	privKey := secp256k1.PrivKeyFromBytes(privKeyBytes)
   233  
   234  	// If a tweak (single or double) is specified, then we'll need to use
   235  	// this tweak to derive the final private key to be used for signing
   236  	// this output.
   237  	privKey, err = maybeTweakPrivKey(signDesc, privKey)
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	// Generate a valid witness stack for the input.
   243  	// TODO(roasbeef): adhere to passed HashType
   244  	sigScript, err := sign.SignatureScript(tx, signDesc.InputIndex,
   245  		outputScript, signDesc.HashType, privKey.Serialize(),
   246  		dcrec.STEcdsaSecp256k1, true)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	witness, err := p2pkhSigScriptToWitness(outputScriptVer, sigScript)
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	return &input.Script{Witness: witness}, nil
   257  }
   258  
   259  // A compile time check to ensure that DcrWallet implements the input.Signer
   260  // interface.
   261  var _ input.Signer = (*DcrWallet)(nil)
   262  
   263  // SignMessage attempts to sign a target message with the private key that
   264  // corresponds to the passed public key. If the target private key is unable to
   265  // be found, then an error will be returned. The actual digest signed is the
   266  // chainhash (blake256r14) of the passed message.
   267  //
   268  // NOTE: This is a part of the Messageinput.Signer interface.
   269  func (b *DcrWallet) SignMessage(keyDesc keychain.KeyLocator,
   270  	msg []byte, double bool) (*ecdsa.Signature, error) {
   271  
   272  	// First attempt to fetch the private key which corresponds to the
   273  	// specified public key.
   274  	privKey, err := b.DerivePrivKey(keychain.KeyDescriptor{KeyLocator: keyDesc})
   275  	if err != nil {
   276  		return nil, err
   277  	}
   278  
   279  	// Double hash and sign the data.
   280  	var digest []byte
   281  	if double {
   282  		return nil, fmt.Errorf("dcrlnd does not do doubleHash signing")
   283  	} else {
   284  		digest = chainhash.HashB(msg)
   285  	}
   286  	sign := ecdsa.Sign(privKey, digest)
   287  
   288  	return sign, nil
   289  }
   290  
   291  // FundPsbt currently does nothing.
   292  func (b *DcrWallet) FundPsbt(_ *psbt.Packet, _ int32,
   293  	_ chainfee.AtomPerKByte, _ string) (int32, error) {
   294  
   295  	return 0, fmt.Errorf("FundPSBT not supported")
   296  }
   297  
   298  // FinalizePsbt currently does nothing.
   299  func (b *DcrWallet) FinalizePsbt(_ *psbt.Packet) error {
   300  	return fmt.Errorf("FinalizePsbt not supported")
   301  }
   302  
   303  // SignPsbt does nothing.
   304  func (b *DcrWallet) SignPsbt(*psbt.Packet) error {
   305  	return fmt.Errorf("SignPsbt not supported")
   306  }
   307  
   308  // AddressInfo returns info about a wallet address.
   309  func (b *DcrWallet) AddressInfo(
   310  	stdaddr.Address) (btcwalletcompat.ManagedAddress, error) {
   311  
   312  	return nil, fmt.Errorf("AddressInfo not supported")
   313  }