Skip to content

Commit

Permalink
mod+waddrmgr: add taproot script address type
Browse files Browse the repository at this point in the history
  • Loading branch information
guggero committed Feb 21, 2022
1 parent 2d5ee75 commit 6a78190
Show file tree
Hide file tree
Showing 6 changed files with 443 additions and 26 deletions.
3 changes: 3 additions & 0 deletions go.mod
Expand Up @@ -21,6 +21,7 @@ require (
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf
github.com/lightninglabs/neutrino v0.13.2
github.com/lightningnetwork/lnd/ticker v1.0.0
github.com/lightningnetwork/lnd/tlv v1.0.2
github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc
Expand All @@ -36,6 +37,8 @@ replace github.com/btcsuite/btcd/btcec/v2 => github.com/Roasbeef/btcd/btcec/v2 v

replace github.com/btcsuite/btcwallet/wallet/txauthor => ./wallet/txauthor

replace github.com/lightningnetwork/lnd/tlv => github.com/guggero/lnd/tlv v0.0.0-20220221153005-553bce541120

// The old version of ginko that's used in btcd imports an ancient version of
// gopkg.in/fsnotify.v1 that isn't go mod compatible. We fix that import error
// by replacing ginko (which is only a test library anyway) with a more recent
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -57,6 +57,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/guggero/lnd/tlv v0.0.0-20220221153005-553bce541120 h1:QwR6EGCvOBeHe0AigubF03eBwL/DazWgfzweEJd1JxQ=
github.com/guggero/lnd/tlv v0.0.0-20220221153005-553bce541120/go.mod h1:1MC5EfYK+3VS/JTVNyvCAy8BOQicaAorj5CfrTRVDYQ=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
Expand Down
122 changes: 98 additions & 24 deletions waddrmgr/address.go
Expand Up @@ -55,6 +55,10 @@ const (

// TaprootPubKey represents a p2tr (pay-to-taproot) address type.
TaprootPubKey

// TaprootScript represents a p2tr (pay-to-taproot) address type that
// commits to a script and not just a single key.
TaprootScript
)

const (
Expand Down Expand Up @@ -794,38 +798,108 @@ var _ ManagedScriptAddress = (*witnessScriptAddress)(nil)

// newWitnessScriptAddress initializes and returns a new
// pay-to-witness-script-hash address.
func newWitnessScriptAddress(m *ScopedKeyManager, account uint32, scriptHash,
func newWitnessScriptAddress(m *ScopedKeyManager, account uint32, scriptIdent,
scriptEncrypted []byte, witnessVersion byte,
isSecretScript bool) (*witnessScriptAddress, error) {

var (
address btcutil.Address
err error
)
isSecretScript bool) (ManagedScriptAddress, error) {

switch witnessVersion {
case 0x00:
address, err = btcutil.NewAddressWitnessScriptHash(
scriptHash, m.rootManager.chainParams,
case witnessVersionV0:
address, err := btcutil.NewAddressWitnessScriptHash(
scriptIdent, m.rootManager.chainParams,
)
if err != nil {
return nil, err
}

case 0x01:
address, err = btcutil.NewAddressTaproot(
scriptHash, m.rootManager.chainParams,
return &witnessScriptAddress{
baseScriptAddress: baseScriptAddress{
manager: m,
account: account,
scriptEncrypted: scriptEncrypted,
},
address: address,
witnessVersion: witnessVersion,
isSecretScript: isSecretScript,
}, nil

case witnessVersionV1:
address, err := btcutil.NewAddressTaproot(
scriptIdent, m.rootManager.chainParams,
)
if err != nil {
return nil, err
}

// Lift the x-only coordinate of the tweaked public key.
tweakedPubKey, err := schnorr.ParsePubKey(scriptIdent)
if err != nil {
return nil, fmt.Errorf("error lifting public key from "+
"script ident: %v", err)
}

return &taprootScriptAddress{
witnessScriptAddress: witnessScriptAddress{
baseScriptAddress: baseScriptAddress{
manager: m,
account: account,
scriptEncrypted: scriptEncrypted,
},
address: address,
witnessVersion: witnessVersion,
isSecretScript: isSecretScript,
},
TweakedPubKey: tweakedPubKey,
}, nil

default:
return nil, fmt.Errorf("unsupported witness version %d",
witnessVersion)
}
}

// taprootScriptAddress represents a pay-to-taproot address that commits to a
// script.
type taprootScriptAddress struct {
witnessScriptAddress

TweakedPubKey *btcec.PublicKey
}

// AddrType returns the address type of the managed address. This can be used
// to quickly discern the address type without further processing
//
// This is part of the ManagedAddress interface implementation.
func (a *taprootScriptAddress) AddrType() AddressType {
return TaprootScript
}

// Address returns the btcutil.Address which represents the managed address.
// This will be a pay-to-witness-script-hash address.
//
// This is part of the ManagedAddress interface implementation.
func (a *taprootScriptAddress) Address() btcutil.Address {
return a.address
}

// AddrHash returns the script hash for the address.
//
// This is part of the ManagedAddress interface implementation.
func (a *taprootScriptAddress) AddrHash() []byte {
return schnorr.SerializePubKey(a.TweakedPubKey)
}

// TaprootScript returns the internal key and the full list of tap leaves used
// to construct this taproot address.
func (a *taprootScriptAddress) TaprootScript() (*btcec.PublicKey,
[]txscript.TapLeaf, error) {

// Need to decrypt our internal script first. We need to be unlocked for
// this.
script, err := a.Script()
if err != nil {
return nil, err
return nil, nil, err
}

return &witnessScriptAddress{
baseScriptAddress: baseScriptAddress{
manager: m,
account: account,
scriptEncrypted: scriptEncrypted,
},
address: address,
witnessVersion: witnessVersion,
isSecretScript: isSecretScript,
}, nil
// Decode the additional TLV encoded data.
return tlvDecodeTaprootTaprootScript(script)
}
30 changes: 28 additions & 2 deletions waddrmgr/scoped_manager.go
Expand Up @@ -7,6 +7,7 @@ import (
"sync"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg"
Expand Down Expand Up @@ -2093,6 +2094,31 @@ func (s *ScopedKeyManager) ImportWitnessScript(ns walletdb.ReadWriteBucket,
)
}

// ImportTaprootScript imports a user-provided taproot script into the address
// manager. The imported script will act as a pay-to-taproot address.
//
// TODO(guggero): Add version that allows knowing only a partial script, would
// need to store the inclusion proof alongside the partially revealed script.
func (s *ScopedKeyManager) ImportTaprootScript(ns walletdb.ReadWriteBucket,
internalKey *btcec.PublicKey, leaves []txscript.TapLeaf, bs *BlockStamp,
witnessVersion byte, isSecretScript bool) (ManagedScriptAddress,
error) {

tree := txscript.AssembleTaprootScriptTree(leaves...)
rootHash := tree.RootNode.TapHash()
taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:])

script, err := tlvEncodeTaprootScript(internalKey, leaves)
if err != nil {
return nil, fmt.Errorf("error encoding taproot script: %v", err)
}

return s.importScriptAddress(
ns, schnorr.SerializePubKey(taprootKey), script, bs,
TaprootScript, witnessVersion, isSecretScript,
)
}

// importScriptAddress imports a new pay-to-script or pay-to-witness-script
// address.
func (s *ScopedKeyManager) importScriptAddress(ns walletdb.ReadWriteBucket,
Expand Down Expand Up @@ -2161,7 +2187,7 @@ func (s *ScopedKeyManager) importScriptAddress(ns walletdb.ReadWriteBucket,
// Save the new imported address to the db and update start block (if
// needed) in a single transaction.
switch addrType {
case WitnessScript:
case WitnessScript, TaprootScript:
err = putWitnessScriptAddress(
ns, &s.scope, scriptIdent, ImportedAddrAccount, ssNone,
witnessVersion, isSecretScript, encryptedHash,
Expand Down Expand Up @@ -2199,7 +2225,7 @@ func (s *ScopedKeyManager) importScriptAddress(ns walletdb.ReadWriteBucket,
// should not be cleared out from under the caller.
var managedAddr ManagedScriptAddress
switch addrType {
case WitnessScript:
case WitnessScript, TaprootScript:
managedAddr, err = newWitnessScriptAddress(
s, ImportedAddrAccount, scriptIdent, encryptedScript,
witnessVersion, isSecretScript,
Expand Down

0 comments on commit 6a78190

Please sign in to comment.