Skip to content

Commit

Permalink
multi: support sighash for vpsbt inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
GeorgeTsagk committed Jan 31, 2024
1 parent eb3ed93 commit 993d41e
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 39 deletions.
4 changes: 3 additions & 1 deletion proof/append_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,9 @@ func signAssetTransfer(t testing.TB, prevProof *Proof, newAsset *asset.Asset,
*prevID: &prevProof.Asset,
}

virtualTx, _, err := tapscript.VirtualTx(newAsset, inputs)
virtualTx, _, err := tapscript.VirtualTx(
newAsset, inputs, txscript.SigHashAll,
)
require.NoError(t, err)
newWitness := genTaprootKeySpend(
t, *senderPrivKey, virtualTx, &prevProof.Asset, 0,
Expand Down
3 changes: 2 additions & 1 deletion tapscript/mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tapscript
import (
"fmt"

"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/taproot-assets/asset"
)
Expand Down Expand Up @@ -31,7 +32,7 @@ func BuildGenesisTx(newAsset *asset.Asset) (*wire.MsgTx,

// Now, create the virtual transaction that represents this asset
// minting.
virtualTx, _, err := VirtualTx(newAsset, nil)
virtualTx, _, err := VirtualTx(newAsset, nil, txscript.SigHashAll)
if err != nil {
return nil, nil, err
}
Expand Down
22 changes: 15 additions & 7 deletions tapscript/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -648,16 +648,20 @@ func SignVirtualTransaction(vPkt *tappsbt.VPacket, signer Signer,
prevAssets[input.PrevID] = input.Asset()
}

// Create a Taproot Asset virtual transaction representing the asset
// transfer.
virtualTx, _, err := VirtualTx(newAsset, prevAssets)
if err != nil {
return err
}

for idx := range inputs {
input := inputs[idx]

// Create a Taproot Asset virtual transaction representing the
// asset transfer. We provide this input's sighash flag and
// index in order to construct a virtual transaction that
// commits to the desired parts of the asset transfer.
virtualTx, _, err := VirtualTx(
newAsset, prevAssets, input.SighashType,
)
if err != nil {
return err
}

// For each input asset leaf, we need to produce a witness.
// Update the input of the virtual TX, generate a witness, and
// attach it to the copy of the new Asset.
Expand All @@ -666,6 +670,10 @@ func SignVirtualTransaction(vPkt *tappsbt.VPacket, signer Signer,
virtualTxCopy, input.Asset(), uint32(idx), nil,
)

fmt.Printf("SGHSH: signing %s:%x (%v)\n",
virtualTxCopy.TxIn[0].PreviousOutPoint.String()[:12],
virtualTxCopy.TxOut[0].PkScript[:6],
input.SighashType)
// Sign the virtual transaction based on the input script
// information (key spend or script spend).
newWitness, err := CreateTaprootSignature(
Expand Down
43 changes: 39 additions & 4 deletions tapscript/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,28 @@ func virtualTxIn(newAsset *asset.Asset, prevAssets commitment.InputSet) (

// virtualTxOut computes the single output of a Taproot Asset virtual
// transaction based on whether an asset has a split commitment or not.
func virtualTxOut(txAsset *asset.Asset) (*wire.TxOut, error) {
func virtualTxOut(txAsset *asset.Asset, sighashFlag txscript.SigHashType) (
*wire.TxOut, error) {

// If the SigHashNone flag is used, we don't need to commit to any
// output, so we just create an empty tree.
if sighashFlag == txscript.SigHashNone {
outputTree := mssmt.NewCompactedTree(mssmt.NewDefaultStore())

treeRoot, err := outputTree.Root(context.Background())
if err != nil {
return nil, err
}

rootKey := treeRoot.NodeHash()
pkScript, err := asset.ComputeTaprootScript(rootKey[:])
if err != nil {
return nil, err
}

return wire.NewTxOut(int64(txAsset.Amount), pkScript), nil
}

// If we have any asset splits, then we'll indirectly commit to all of
// them through the SplitCommitmentRoot.
if txAsset.SplitCommitmentRoot != nil {
Expand Down Expand Up @@ -180,12 +201,16 @@ func virtualTxOut(txAsset *asset.Asset) (*wire.TxOut, error) {
if err != nil {
return nil, err
}

return wire.NewTxOut(int64(txAsset.Amount), pkScript), nil
}

// VirtualTx constructs the virtual transaction that enables the movement of an
// asset representing an asset state transition.
func VirtualTx(newAsset *asset.Asset, prevAssets commitment.InputSet) (
// asset representing an asset state transition. This virtual transaction may be
// different for each input of the asset transfer, as different inputs may
// commit to different parts of the state transition.
func VirtualTx(newAsset *asset.Asset, prevAssets commitment.InputSet,
sighashFlag txscript.SigHashType) (
*wire.MsgTx, mssmt.Tree, error) {

var (
Expand All @@ -194,6 +219,16 @@ func VirtualTx(newAsset *asset.Asset, prevAssets commitment.InputSet) (
err error
)

// Check that we support the provided sighash flag.
switch sighashFlag {
case txscript.SigHashDefault:
case txscript.SigHashAll:
case txscript.SigHashNone:
default:
return nil, nil, fmt.Errorf("unsupported sighash flag: %v",
sighashFlag)
}

// We'll start by mapping all inputs into a MS-SMT.
if newAsset.NeedsGenesisWitnessForGroup() ||
newAsset.HasGenesisWitnessForGroup() {
Expand All @@ -207,7 +242,7 @@ func VirtualTx(newAsset *asset.Asset, prevAssets commitment.InputSet) (
}

// Then we'll map all asset outputs into a single UTXO.
txOut, err := virtualTxOut(newAsset)
txOut, err := virtualTxOut(newAsset, sighashFlag)
if err != nil {
return nil, nil, err
}
Expand Down
91 changes: 67 additions & 24 deletions vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,9 @@ func (vm *Engine) validateWitnessV0(virtualTx *wire.MsgTx, inputIdx uint32,

// validateStateTransition attempts to validate a normal state transition where
// an asset (normal or collectible) is fully consumed without splits. This is
// done by verifying each input has a valid witness generated over the virtual
// transaction representing the state transition.
func (vm *Engine) validateStateTransition(virtualTx *wire.MsgTx) error {
// done by verifying that every input has a valid witness generated over its
// respective virtual transaction representing the state transition.
func (vm *Engine) validateStateTransition() error {
switch {
case len(vm.newAsset.PrevWitnesses) == 0:
return fmt.Errorf("%w: prev witness zero", ErrNoInputs)
Expand All @@ -288,6 +288,69 @@ func (vm *Engine) validateStateTransition(virtualTx *wire.MsgTx) error {
spew.Sdump(witness.PrevID))
}

// Assume the default flag of SigHashAll.
sighashFlag := txscript.SigHashAll

const (
leafVersionEven = byte(txscript.BaseLeafVersion)
leafVersionOdd = byte(txscript.BaseLeafVersion | 1)
)

for _, txWitness := range witness.TxWitness {
witnessLen := len(txWitness)

// Discard control blocks.
if witnessLen >= 33 && witnessLen <= 129 {
if txWitness[0] == leafVersionEven ||
txWitness[0] == leafVersionOdd {
continue
}
}

// If signature has no sighash flag, use the default.
if witnessLen == 64 {
break
}

// Otherwise, use the sighash flag from the witness.
if witnessLen == 65 {
sighashFlag = txscript.SigHashType(
txWitness[len(txWitness)-1],
)
break
}
}

// Now that we know we're not dealing with a genesis state
// transition, we'll map our set of asset inputs and outputs to
// the 1-input 1-output virtual transaction.
virtualTx, inputTree, err := tapscript.VirtualTx(
vm.newAsset, vm.prevAssets, sighashFlag,
)
if err != nil {
return err
}

// If we are commiting to the outputs, we need to also perform
// an inflation check.
if sighashFlag != txscript.SigHashNone {
// Enforce that assets aren't being inflated.
treeRoot, err := inputTree.Root(context.Background())
if err != nil {
return err
}
if treeRoot.NodeSum() !=
uint64(virtualTx.TxOut[0].Value) {

return newErrKind(ErrAmountMismatch)
}
}

fmt.Printf("SGHSH: validating %s:%x (%v)\n",
virtualTx.TxIn[0].PreviousOutPoint.String()[:12],
virtualTx.TxOut[0].PkScript[:6],
sighashFlag)

switch prevAsset.ScriptVersion {
case asset.ScriptV0:
err := vm.validateWitnessV0(
Expand Down Expand Up @@ -345,25 +408,5 @@ func (vm *Engine) Execute() error {
}
}

// Now that we know we're not dealing with a genesis state transition,
// we'll map our set of asset inputs and outputs to the 1-input 1-output
// virtual transaction.
virtualTx, inputTree, err := tapscript.VirtualTx(
vm.newAsset, vm.prevAssets,
)
if err != nil {
return err
}

// Enforce that assets aren't being inflated.
treeRoot, err := inputTree.Root(context.Background())
if err != nil {
return err
}
if treeRoot.NodeSum() != uint64(virtualTx.TxOut[0].Value) {
return newErrKind(ErrAmountMismatch)
}

// Finally, we'll validate the asset witness.
return vm.validateStateTransition(virtualTx)
return vm.validateStateTransition()
}
11 changes: 9 additions & 2 deletions vm/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ func collectibleStateTransition(t *testing.T) (*asset.Asset,
}}

inputs := commitment.InputSet{*prevID: genesisAsset}
virtualTx, _, err := tapscript.VirtualTx(newAsset, inputs)
virtualTx, _, err := tapscript.VirtualTx(
newAsset, inputs, txscript.SigHashAll,
)
require.NoError(t, err)
newWitness := genTaprootKeySpend(
t, *privKey, virtualTx, genesisAsset, 0,
Expand Down Expand Up @@ -259,7 +261,8 @@ func normalStateTransition(t *testing.T) (*asset.Asset, commitment.SplitSet,
*prevID1: genesisAsset1,
*prevID2: genesisAsset2,
}
virtualTx, _, err := tapscript.VirtualTx(newAsset, inputs)
virtualTx, _, err := tapscript.VirtualTx(
newAsset, inputs, txscript.SigHashAll)
require.NoError(t, err)
newWitness := genTaprootKeySpend(
t, *privKey1, virtualTx, genesisAsset1, 0,
Expand Down Expand Up @@ -319,6 +322,7 @@ func splitStateTransition(t *testing.T) (*asset.Asset, commitment.SplitSet,

virtualTx, _, err := tapscript.VirtualTx(
splitCommitment.RootAsset, splitCommitment.PrevAssets,
txscript.SigHashAll,
)
require.NoError(t, err)
newWitness := genTaprootKeySpend(
Expand Down Expand Up @@ -379,6 +383,7 @@ func splitFullValueStateTransition(validRootLocator,

virtualTx, _, err := tapscript.VirtualTx(
splitCommitment.RootAsset, splitCommitment.PrevAssets,
txscript.SigHashAll,
)
require.NoError(t, err)
newWitness := genTaprootKeySpend(
Expand Down Expand Up @@ -427,6 +432,7 @@ func splitCollectibleStateTransition(validRoot bool) stateTransitionFunc {

virtualTx, _, err := tapscript.VirtualTx(
splitCommitment.RootAsset, splitCommitment.PrevAssets,
txscript.SigHashAll,
)
require.NoError(t, err)
newWitness := genTaprootKeySpend(
Expand Down Expand Up @@ -505,6 +511,7 @@ func scriptTreeSpendStateTransition(t *testing.T, useHashLock,

virtualTx, _, err := tapscript.VirtualTx(
splitCommitment.RootAsset, splitCommitment.PrevAssets,
txscript.SigHashAll,
)
require.NoError(t, err)
newWitness := genTaprootScriptSpend(
Expand Down

0 comments on commit 993d41e

Please sign in to comment.