Skip to content

Commit

Permalink
multi: support P2TR addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
guggero committed Mar 8, 2022
1 parent ce64697 commit 443813e
Show file tree
Hide file tree
Showing 36 changed files with 822 additions and 201 deletions.
5 changes: 4 additions & 1 deletion cmd/lncli/commands.go
Expand Up @@ -139,7 +139,8 @@ var newAddressCommand = cli.Command{
Description: `
Generate a wallet new address. Address-types has to be one of:
- p2wkh: Pay to witness key hash
- np2wkh: Pay to nested witness key hash`,
- np2wkh: Pay to nested witness key hash
- p2tr: Pay to taproot pubkey`,
Action: actionDecorator(newAddress),
}

Expand All @@ -161,6 +162,8 @@ func newAddress(ctx *cli.Context) error {
addrType = lnrpc.AddressType_WITNESS_PUBKEY_HASH
case "np2wkh":
addrType = lnrpc.AddressType_NESTED_PUBKEY_HASH
case "p2tr":
addrType = lnrpc.AddressType_TAPROOT_PUBKEY
default:
return fmt.Errorf("invalid address type %v, support address type "+
"are: p2wkh and np2wkh", stringAddrType)
Expand Down
9 changes: 8 additions & 1 deletion config_builder.go
Expand Up @@ -1112,7 +1112,14 @@ func importWatchOnlyAccounts(wallet *wallet.Wallet,

for _, scope := range scopes {
addrSchema := waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0084]
if scope.Scope.Purpose == waddrmgr.KeyScopeBIP0049Plus.Purpose {

// We want witness pubkey hash by default, except for BIP49
// where we want mixed and BIP86 where we want taproot address
// formats.
switch scope.Scope.Purpose {
case waddrmgr.KeyScopeBIP0049Plus.Purpose,
waddrmgr.KeyScopeBIP0086.Purpose:

addrSchema = waddrmgr.ScopeAddrMap[scope.Scope]
}

Expand Down
32 changes: 20 additions & 12 deletions contractcourt/breacharbiter.go
Expand Up @@ -1096,11 +1096,15 @@ func (bo *breachedOutput) SignDesc() *input.SignDescriptor {
// sign descriptor. The method then returns the witness computed by invoking
// this function on the first and subsequent calls.
func (bo *breachedOutput) CraftInputScript(signer input.Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes, txinIdx int) (*input.Script, error) {
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (*input.Script, error) {

// First, we ensure that the witness generation function has been
// initialized for this breached output.
bo.witnessFunc = bo.witnessType.WitnessGenerator(signer, bo.SignDesc())
signDesc := bo.SignDesc()
signDesc.PrevOutputFetcher = prevOutputFetcher
bo.witnessFunc = bo.witnessType.WitnessGenerator(signer, signDesc)

// Now that we have ensured that the witness generation function has
// been initialized, we can proceed to execute it and generate the
Expand Down Expand Up @@ -1397,8 +1401,8 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64,

// Compute the total amount contained in the inputs.
var totalAmt btcutil.Amount
for _, input := range inputs {
totalAmt += btcutil.Amount(input.SignDesc().Output.Value)
for _, inp := range inputs {
totalAmt += btcutil.Amount(inp.SignDesc().Output.Value)
}

// We'll actually attempt to target inclusion within the next two
Expand All @@ -1424,11 +1428,15 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64,

// Next, we add all of the spendable outputs as inputs to the
// transaction.
for _, input := range inputs {
prevOutputFetcher := txscript.NewMultiPrevOutFetcher(nil)
for _, inp := range inputs {
txn.AddTxIn(&wire.TxIn{
PreviousOutPoint: *input.OutPoint(),
Sequence: input.BlocksToMaturity(),
PreviousOutPoint: *inp.OutPoint(),
Sequence: inp.BlocksToMaturity(),
})
prevOutputFetcher.AddPrevOut(
*inp.OutPoint(), inp.RequiredTxOut(),
)
}

// Before signing the transaction, check to ensure that it meets some
Expand All @@ -1440,7 +1448,7 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64,

// Create a sighash cache to improve the performance of hashing and
// signing SigHashAll inputs.
hashCache := txscript.NewTxSigHashesV0Only(txn)
hashCache := txscript.NewTxSigHashes(txn, prevOutputFetcher)

// Create a closure that encapsulates the process of initializing a
// particular output's witness generation function, computing the
Expand All @@ -1452,7 +1460,7 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64,
// transaction using the SpendableOutput's witness generation
// function.
inputScript, err := so.CraftInputScript(
b.cfg.Signer, txn, hashCache, idx,
b.cfg.Signer, txn, hashCache, prevOutputFetcher, idx,
)
if err != nil {
return err
Expand All @@ -1467,8 +1475,8 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64,

// Finally, generate a witness for each output and attach it to the
// transaction.
for i, input := range inputs {
if err := addWitness(i, input); err != nil {
for i, inp := range inputs {
if err := addWitness(i, inp); err != nil {
return nil, err
}
}
Expand Down Expand Up @@ -1648,7 +1656,7 @@ func (ret *retributionInfo) Encode(w io.Writer) error {
return nil
}

// Dencode deserializes a retribution from the passed byte stream.
// Decode deserializes a retribution from the passed byte stream.
func (ret *retributionInfo) Decode(r io.Reader) error {
var scratch [32]byte

Expand Down
31 changes: 24 additions & 7 deletions input/input.go
Expand Up @@ -42,6 +42,7 @@ type Input interface {
// also nested p2sh outputs.
CraftInputScript(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (*Script, error)

// BlocksToMaturity returns the relative timelock, as a number of
Expand Down Expand Up @@ -221,9 +222,13 @@ func NewCsvInputWithCltv(outpoint *wire.OutPoint, witnessType WitnessType,
// txIndex within the passed transaction. The input scripts generated by this
// method support spending p2wkh, p2wsh, and also nested p2sh outputs.
func (bi *BaseInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes, txinIdx int) (*Script, error) {
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
error) {

witnessFunc := bi.witnessType.WitnessGenerator(signer, bi.SignDesc())
signDesc := bi.SignDesc()
signDesc.PrevOutputFetcher = prevOutputFetcher
witnessFunc := bi.witnessType.WitnessGenerator(signer, signDesc)

return witnessFunc(txn, hashCache, txinIdx)
}
Expand Down Expand Up @@ -260,11 +265,14 @@ func MakeHtlcSucceedInput(outpoint *wire.OutPoint,
// txIndex within the passed transaction. The input scripts generated by this
// method support spending p2wkh, p2wsh, and also nested p2sh outputs.
func (h *HtlcSucceedInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes, txinIdx int) (*Script, error) {
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
error) {

desc := h.signDesc
desc.SigHashes = hashCache
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher

witness, err := SenderHtlcSpendRedeem(
signer, &desc, txn, h.preimage,
Expand All @@ -291,7 +299,9 @@ type HtlcSecondLevelAnchorInput struct {
// createWitness creates a witness allowing the passed transaction to
// spend the input.
createWitness func(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes, txinIdx int) (wire.TxWitness, error)
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error)
}

// RequiredTxOut returns the tx out needed to be present on the sweep tx for
Expand All @@ -313,9 +323,12 @@ func (i *HtlcSecondLevelAnchorInput) RequiredLockTime() (uint32, bool) {
// method support spending p2wkh, p2wsh, and also nested p2sh outputs.
func (i *HtlcSecondLevelAnchorInput) CraftInputScript(signer Signer,
txn *wire.MsgTx, hashCache *txscript.TxSigHashes,
txinIdx int) (*Script, error) {
prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
error) {

witness, err := i.createWitness(signer, txn, hashCache, txinIdx)
witness, err := i.createWitness(
signer, txn, hashCache, prevOutputFetcher, txinIdx,
)
if err != nil {
return nil, err
}
Expand All @@ -335,11 +348,13 @@ func MakeHtlcSecondLevelTimeoutAnchorInput(signedTx *wire.MsgTx,
// 2nd timeout transaction.
createWitness := func(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error) {

desc := signDetails.SignDesc
desc.SigHashes = txscript.NewTxSigHashesV0Only(txn)
desc.SigHashes = txscript.NewTxSigHashes(txn, prevOutputFetcher)
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher

return SenderHtlcSpendTimeout(
signDetails.PeerSig, signDetails.SigHashType, signer,
Expand Down Expand Up @@ -373,11 +388,13 @@ func MakeHtlcSecondLevelSuccessAnchorInput(signedTx *wire.MsgTx,
// success transaction.
createWitness := func(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error) {

desc := signDetails.SignDesc
desc.SigHashes = hashCache
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher

return ReceiverHtlcSpendRedeem(
signDetails.PeerSig, signDetails.SigHashType,
Expand Down
22 changes: 22 additions & 0 deletions input/script_utils.go
Expand Up @@ -1572,3 +1572,25 @@ func NewTxSigHashesV0Only(tx *wire.MsgTx) *txscript.TxSigHashes {
nilFetcher := txscript.NewCannedPrevOutputFetcher(nil, 0)
return txscript.NewTxSigHashes(tx, nilFetcher)
}

// MultiPrevOutFetcher returns a txscript.MultiPrevOutFetcher for the given set
// of inputs.
func MultiPrevOutFetcher(inputs []Input) (*txscript.MultiPrevOutFetcher, error) {
fetcher := txscript.NewMultiPrevOutFetcher(nil)
for _, inp := range inputs {
op := inp.OutPoint()
desc := inp.SignDesc()

if op == nil {
return nil, fmt.Errorf("missing input outpoint")
}

if desc == nil || desc.Output == nil {
return nil, fmt.Errorf("missing input utxo information")
}

fetcher.AddPrevOut(*op, desc.Output)
}

return fetcher, nil
}
5 changes: 5 additions & 0 deletions input/signdescriptor.go
Expand Up @@ -74,6 +74,11 @@ type SignDescriptor struct {
// generating the final sighash for signing.
SigHashes *txscript.TxSigHashes

// PrevOutputFetcher is an interface that can return the output
// information on all UTXOs that are being spent in this transaction.
// This MUST be set when spending Taproot outputs.
PrevOutputFetcher txscript.PrevOutputFetcher

// InputIndex is the target input within the transaction that should be
// signed.
InputIndex int
Expand Down
6 changes: 6 additions & 0 deletions input/size.go
Expand Up @@ -519,6 +519,12 @@ const (
// - witness_script_length: 1 byte
// - witness_script (anchor_script)
AnchorWitnessSize = 1 + 1 + 73 + 1 + AnchorScriptSize

// TaprootKeyPathWitnessSize 66 bytes
// - NumberOfWitnessElements: 1 byte
// - sigLength: 1 byte
// - sig: 64 bytes
TaprootKeyPathWitnessSize = 1 + 1 + 64
)

// EstimateCommitTxWeight estimate commitment transaction weight depending on
Expand Down
21 changes: 21 additions & 0 deletions input/witnessgen.go
Expand Up @@ -175,6 +175,10 @@ const (
// and CLTV locktime as part of the script enforced lease commitment
// type.
LeaseHtlcAcceptedSuccessSecondLevel StandardWitnessType = 20

TaprootPubkeySpend StandardWitnessType = 21

TaprootScriptSpend StandardWitnessType = 22
)

// String returns a human readable version of the target WitnessType.
Expand Down Expand Up @@ -245,6 +249,12 @@ func (wt StandardWitnessType) String() string {
case LeaseHtlcAcceptedSuccessSecondLevel:
return "LeaseHtlcAcceptedSuccessSecondLevel"

case TaprootPubkeySpend:
return "TaprootPubkeySpend"

case TaprootScriptSpend:
return "TaprootScriptSpend"

default:
return fmt.Sprintf("Unknown WitnessType: %v", uint32(wt))
}
Expand Down Expand Up @@ -388,6 +398,10 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer,

case WitnessKeyHash:
fallthrough
case TaprootPubkeySpend:
fallthrough
case TaprootScriptSpend:
fallthrough
case NestedWitnessKeyHash:
return signer.ComputeInputScript(tx, desc)

Expand Down Expand Up @@ -494,6 +508,13 @@ func (wt StandardWitnessType) SizeUpperBound() (int, bool, error) {
// The revocation output of a second level output of an HTLC.
case HtlcSecondLevelRevoke:
return ToLocalPenaltyWitnessSize, false, nil

case TaprootPubkeySpend:
return TaprootKeyPathWitnessSize, false, nil

case TaprootScriptSpend:
return 0, false, fmt.Errorf("taproot script send not " +
"implemented yet")
}

return 0, false, fmt.Errorf("unexpected witness type: %v", wt)
Expand Down
28 changes: 19 additions & 9 deletions lnrpc/lightning.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions lnrpc/lightning.proto
Expand Up @@ -1102,12 +1102,15 @@ message ListUnspentResponse {
- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)
- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)
- `p2tr`: Pay to taproot pubkey (`TAPROOT_PUBKEY` = 4)
*/
enum AddressType {
WITNESS_PUBKEY_HASH = 0;
NESTED_PUBKEY_HASH = 1;
UNUSED_WITNESS_PUBKEY_HASH = 2;
UNUSED_NESTED_PUBKEY_HASH = 3;
TAPROOT_PUBKEY = 4;
UNUSED_TAPROOT_PUBKEY = 5;
}

message NewAddressRequest {
Expand Down

0 comments on commit 443813e

Please sign in to comment.