Skip to content

Commit

Permalink
address: check sender tree against address
Browse files Browse the repository at this point in the history
  • Loading branch information
jharveyb committed Jul 15, 2022
1 parent c8fb675 commit 64a71db
Show file tree
Hide file tree
Showing 2 changed files with 333 additions and 2 deletions.
94 changes: 93 additions & 1 deletion address/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@ import (
"strings"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil/bech32"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/taro/asset"
"github.com/lightninglabs/taro/commitment"
"github.com/lightninglabs/taro/vm"
"github.com/lightningnetwork/lnd/tlv"
)

Expand Down Expand Up @@ -49,10 +55,22 @@ var (
ErrUnsupportedAssetType = errors.New(
"address: unsupported asset type",
)

// ErrMissingInputAsset is an error returned when we attempt to spend to a
// Taro address from an input that does not contain the matching asset.
ErrMissingInputAsset = errors.New(
"address: Input does not contain requested asset",
)

// ErrInsufficientInputAsset is an error returned when we attempt to spend
// to a Taro address from an input that contains insufficient asset funds.
ErrInsufficientInputAsset = errors.New(
"address: Input asset value is insufficient",
)
)

// Highest version of Taro script supported.
const (
// Highest version of Taro script supported.
TaroScriptVersion uint8 = 0
)

Expand Down Expand Up @@ -127,6 +145,42 @@ func New(id asset.ID, familyKey *btcec.PublicKey, scriptKey btcec.PublicKey,
return &payload, nil
}

// isValidInput verifies that the Taro commitment of the input contains an
// asset that could be spent to the given Taro address.
func isValidInput(input *commitment.TaroCommitment, address AddressTaro,
inputScriptKey btcec.PublicKey, net *ChainParams) (*asset.Asset, error) {

// The input and address networks must match.
if !IsForNet(address.Hrp, net) {
return nil, ErrMismatchedHRP
}

// The top-level Taro tree must have a non-empty asset tree at the leaf
// specified in the address.
inputCommitments := input.Commitments()
assetCommitment, ok := inputCommitments[address.TaroCommitmentKey()]
if !ok {
return nil, ErrMissingInputAsset
}

// The asset tree must have a non-empty Asset at the location
// specified by the sender's script key.
assetCommitmentKey := asset.AssetCommitmentKey(address.ID,
&inputScriptKey, address.FamilyKey)
// Wrong?
inputAsset, _ := assetCommitment.AssetProof(assetCommitmentKey)
if inputAsset == nil {
return nil, ErrMissingInputAsset
}

// For Normal assets, we also check that the input asset amount is at least
// as large as the amount specified in the address.
if inputAsset.Type == asset.Normal && inputAsset.Amount < address.Amount {
return nil, ErrInsufficientInputAsset
}
return inputAsset, nil
}

// Copy returns a deep copy of an Address.
func (a AddressTaro) Copy() *AddressTaro {
addressCopy := a
Expand All @@ -139,6 +193,44 @@ func (a AddressTaro) Copy() *AddressTaro {
return &addressCopy
}

// TaroCommitmentKey is the key that maps to the root commitment for the asset
// family specified by a Taro address.
func (a *AddressTaro) TaroCommitmentKey() [32]byte {
return asset.TaroCommitmentKey(a.ID, a.FamilyKey)
}

// PayToAddrScript constructs a P2TR script that embeds a Taro commitment
// by tweaking the receiver key by a Tapscript tree that contains the Taro
// commitment root. The Taro commitment must be reconstructed by the receiver,
// and they also need to Tapscript sibling hash used here if present.
func PayToAddrScript(internalKey btcec.PublicKey, sibling *chainhash.Hash,
commitment commitment.TaroCommitment) ([]byte, error) {
tapscriptRoot := commitment.TapscriptRoot(sibling)
outputKey := txscript.ComputeTaprootOutputKey(&internalKey,
tapscriptRoot[:])
return txscript.NewScriptBuilder().
AddOp(txscript.OP_1).
AddData(schnorr.SerializePubKey(outputKey)).
Script()
}

// signVirtualKeySpend generates a signature over a Taro virtual transaction,
// where the input asset was spendable via the key path. This signature is
// the witness for the output asset or split commitment.
func signVirtualKeySpend(privKey btcec.PrivateKey, virtualTx wire.MsgTx,
input asset.Asset, idx uint32) (*wire.TxWitness, error) {
sigHash, err := vm.InputKeySpendSigHash(&virtualTx, &input, idx)
if err != nil {
return nil, err
}
taprootPrivKey := txscript.TweakTaprootPrivKey(&privKey, nil)
sig, err := schnorr.Sign(taprootPrivKey, sigHash)
if err != nil {
return nil, err
}
return &wire.TxWitness{sig.Serialize()}, nil
}

// EncodeRecords determines the non-nil records to include when encoding an
// address at runtime.
func (a AddressTaro) EncodeRecords() []tlv.Record {
Expand Down
Loading

0 comments on commit 64a71db

Please sign in to comment.