-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1980 from halseth/script-callback
txscript/engine: add execution StepCallback
- Loading branch information
Showing
2 changed files
with
276 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
// Copyright (c) 2013-2023 The btcsuite developers | ||
// Use of this source code is governed by an ISC | ||
// license that can be found in the LICENSE file. | ||
|
||
package txscript | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/btcsuite/btcd/btcec/v2" | ||
"github.com/btcsuite/btcd/btcec/v2/schnorr" | ||
"github.com/btcsuite/btcd/wire" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
// TestDebugEngine checks that the StepCallbck called during debug script | ||
// execution contains the expected data. | ||
func TestDebugEngine(t *testing.T) { | ||
t.Parallel() | ||
|
||
// We'll generate a private key and a signature for the tx. | ||
privKey, err := btcec.NewPrivateKey() | ||
require.NoError(t, err) | ||
|
||
internalKey := privKey.PubKey() | ||
|
||
// We use a simple script that will utilize both the stack and alt | ||
// stack in order to test the step callback, and wrap it in a taproot | ||
// witness script. | ||
builder := NewScriptBuilder() | ||
builder.AddData([]byte{0xab}) | ||
builder.AddOp(OP_TOALTSTACK) | ||
builder.AddData(schnorr.SerializePubKey(internalKey)) | ||
builder.AddOp(OP_CHECKSIG) | ||
builder.AddOp(OP_VERIFY) | ||
builder.AddOp(OP_1) | ||
pkScript, err := builder.Script() | ||
require.NoError(t, err) | ||
|
||
tapLeaf := NewBaseTapLeaf(pkScript) | ||
tapScriptTree := AssembleTaprootScriptTree(tapLeaf) | ||
|
||
ctrlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock( | ||
internalKey, | ||
) | ||
|
||
tapScriptRootHash := tapScriptTree.RootNode.TapHash() | ||
outputKey := ComputeTaprootOutputKey( | ||
internalKey, tapScriptRootHash[:], | ||
) | ||
p2trScript, err := PayToTaprootScript(outputKey) | ||
require.NoError(t, err) | ||
|
||
testTx := wire.NewMsgTx(2) | ||
testTx.AddTxIn(&wire.TxIn{ | ||
PreviousOutPoint: wire.OutPoint{ | ||
Index: 1, | ||
}, | ||
}) | ||
txOut := &wire.TxOut{ | ||
Value: 1e8, PkScript: p2trScript, | ||
} | ||
testTx.AddTxOut(txOut) | ||
|
||
prevFetcher := NewCannedPrevOutputFetcher( | ||
txOut.PkScript, txOut.Value, | ||
) | ||
sigHashes := NewTxSigHashes(testTx, prevFetcher) | ||
|
||
sig, err := RawTxInTapscriptSignature( | ||
testTx, sigHashes, 0, txOut.Value, | ||
txOut.PkScript, tapLeaf, | ||
SigHashDefault, privKey, | ||
) | ||
require.NoError(t, err) | ||
|
||
// Now that we have the sig, we'll make a valid witness | ||
// including the control block. | ||
ctrlBlockBytes, err := ctrlBlock.ToBytes() | ||
require.NoError(t, err) | ||
txCopy := testTx.Copy() | ||
txCopy.TxIn[0].Witness = wire.TxWitness{ | ||
sig, pkScript, ctrlBlockBytes, | ||
} | ||
|
||
expCallback := []StepInfo{ | ||
// First callback is looking at the OP_1 witness version. | ||
{ | ||
ScriptIndex: 1, | ||
OpcodeIndex: 0, | ||
Stack: [][]byte{}, | ||
AltStack: [][]byte{}, | ||
}, | ||
// The OP_1 witness version is pushed to stack, | ||
{ | ||
ScriptIndex: 1, | ||
OpcodeIndex: 1, | ||
Stack: [][]byte{{0x01}}, | ||
AltStack: [][]byte{}, | ||
}, | ||
// Then the taproot script is being executed, starting with | ||
// only the signature on the stacks. | ||
{ | ||
ScriptIndex: 2, | ||
OpcodeIndex: 0, | ||
Stack: [][]byte{sig}, | ||
AltStack: [][]byte{}, | ||
}, | ||
// 0xab is pushed to the stack. | ||
{ | ||
ScriptIndex: 2, | ||
OpcodeIndex: 1, | ||
Stack: [][]byte{sig, {0xab}}, | ||
AltStack: [][]byte{}, | ||
}, | ||
// 0xab is moved to the alt stack. | ||
{ | ||
ScriptIndex: 2, | ||
OpcodeIndex: 2, | ||
Stack: [][]byte{sig}, | ||
AltStack: [][]byte{{0xab}}, | ||
}, | ||
// The public key is pushed to the stack. | ||
{ | ||
ScriptIndex: 2, | ||
OpcodeIndex: 3, | ||
Stack: [][]byte{ | ||
sig, | ||
schnorr.SerializePubKey(internalKey), | ||
}, | ||
AltStack: [][]byte{{0xab}}, | ||
}, | ||
// OP_CHECKSIG is executed, resulting in 0x01 on the stack. | ||
{ | ||
ScriptIndex: 2, | ||
OpcodeIndex: 4, | ||
Stack: [][]byte{ | ||
{0x01}, | ||
}, | ||
AltStack: [][]byte{{0xab}}, | ||
}, | ||
// OP_VERIFY pops and checks the top stack element. | ||
{ | ||
ScriptIndex: 2, | ||
OpcodeIndex: 5, | ||
Stack: [][]byte{}, | ||
AltStack: [][]byte{{0xab}}, | ||
}, | ||
// A single OP_1 push completes the script execution (note that | ||
// the alt stack is cleared when the script is "done"). | ||
{ | ||
ScriptIndex: 2, | ||
OpcodeIndex: 6, | ||
Stack: [][]byte{{0x01}}, | ||
AltStack: [][]byte{}, | ||
}, | ||
} | ||
|
||
stepIndex := 0 | ||
callback := func(s *StepInfo) error { | ||
require.Less( | ||
t, stepIndex, len(expCallback), "unexpected callback", | ||
) | ||
|
||
require.Equal(t, &expCallback[stepIndex], s) | ||
stepIndex++ | ||
return nil | ||
} | ||
|
||
// Run the debug engine. | ||
vm, err := NewDebugEngine( | ||
txOut.PkScript, txCopy, 0, StandardVerifyFlags, | ||
nil, sigHashes, txOut.Value, prevFetcher, | ||
callback, | ||
) | ||
require.NoError(t, err) | ||
require.NoError(t, vm.Execute()) | ||
} |