From 73373601578ecb73dbc2daa7b444ace2ba49dae3 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 15 May 2023 11:17:41 +0200 Subject: [PATCH 01/12] txscript: enable OP_CAT --- txscript/engine.go | 3 +-- txscript/opcode.go | 41 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index 1458728f72..cf15b47eff 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -335,8 +335,6 @@ func (vm *Engine) isBranchExecuting() bool { // conditional). func isOpcodeDisabled(opcode byte) bool { switch opcode { - case OP_CAT: - return true case OP_SUBSTR: return true case OP_LEFT: @@ -698,6 +696,7 @@ func (vm *Engine) verifyWitnessProgram(witness wire.TxWitness) error { // An op success op code has been found, however if // the policy flag forbidding them is active, then // we'll return an error. + // TODO: add flag for discourage OP_CAT. if vm.hasFlag(ScriptVerifyDiscourageOpSuccess) { errStr := fmt.Sprintf("script contains " + "OP_SUCCESS op code") diff --git a/txscript/opcode.go b/txscript/opcode.go index 4918b991c5..812a5d05d5 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -445,7 +445,7 @@ var opcodeArray = [256]opcode{ OP_TUCK: {OP_TUCK, "OP_TUCK", 1, opcodeTuck}, // Splice opcodes. - OP_CAT: {OP_CAT, "OP_CAT", 1, opcodeDisabled}, + OP_CAT: {OP_CAT, "OP_CAT", 1, opcodeCat}, OP_SUBSTR: {OP_SUBSTR, "OP_SUBSTR", 1, opcodeDisabled}, OP_LEFT: {OP_LEFT, "OP_LEFT", 1, opcodeDisabled}, OP_RIGHT: {OP_RIGHT, "OP_RIGHT", 1, opcodeDisabled}, @@ -619,7 +619,6 @@ var opcodeOnelineRepls = map[string]string{ var successOpcodes = map[byte]struct{}{ OP_RESERVED: {}, // 80 OP_VER: {}, // 98 - OP_CAT: {}, // 126 OP_SUBSTR: {}, // 127 OP_LEFT: {}, // 128 OP_RIGHT: {}, // 129 @@ -1944,6 +1943,44 @@ func opcodeHash256(op *opcode, data []byte, vm *Engine) error { return nil } +// opcodeCat concatenates the top two stack items, leaving the result on the +// stack. +// +// Stack transformation: [...x1 x2] -> [... x1|x2] +func opcodeCat(op *opcode, data []byte, vm *Engine) error { + // This op code can only be used if tapsript execution is active. + // Before the soft fork, this opcode was disabled. + if vm.taprootCtx == nil { + return opcodeDisabled(op, data, vm) + } + + x2, err := vm.dstack.PopByteArray() + if err != nil { + return err + } + + x1, err := vm.dstack.PopByteArray() + if err != nil { + return err + } + + var buf bytes.Buffer + buf.Write(x1) + buf.Write(x2) + + c := buf.Bytes() + + // Ensure result is within the max allowed size. + if len(c) > MaxScriptElementSize { + str := fmt.Sprintf("element size %d exceeds max allowed size %d", + len(c), MaxScriptElementSize) + return scriptError(ErrElementTooBig, str) + } + + vm.dstack.PushByteArray(c) + return nil +} + // opcodeCodeSeparator stores the current script offset as the most recently // seen OP_CODESEPARATOR which is used during signature checking. // From 4087db20c2a67aa76f8ef33d939979e77a04d605 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 5 Jan 2024 15:18:07 +0100 Subject: [PATCH 02/12] txscript/engine_test: add TestOpcodeCat --- txscript/engine_test.go | 227 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) diff --git a/txscript/engine_test.go b/txscript/engine_test.go index c88d27a60e..5a9a825c89 100644 --- a/txscript/engine_test.go +++ b/txscript/engine_test.go @@ -6,10 +6,14 @@ package txscript import ( + "bytes" + "crypto/sha256" "testing" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/require" ) // TestBadPC sets the pc to a deliberately bad result then confirms that Step @@ -426,3 +430,226 @@ func TestCheckSignatureEncoding(t *testing.T) { } } } + +// TestOpcodeCat tests that scripts with OP_CAT are executed correctly. +func TestOpcodeCat(t *testing.T) { + t.Parallel() + + tests := []struct { + script *ScriptBuilder + expErr ErrorCode + expStack [][]byte + nonTaproot bool + }{ + + // No elements to cat. + { + script: NewScriptBuilder(). + AddOp(OP_CAT), + expErr: ErrInvalidStackOperation, + expStack: [][]byte{}, + }, + + // Only a single element to cat. + { + script: NewScriptBuilder(). + AddData([]byte{0xaa}). + AddOp(OP_CAT), + expErr: ErrInvalidStackOperation, + expStack: [][]byte{ + {0xaa}, + }, + }, + + // Normal cat. + { + script: NewScriptBuilder(). + AddData([]byte{0xaa}). + AddData([]byte{0xbb}). + AddOp(OP_CAT), + expErr: -1, + expStack: [][]byte{ + {0xaa, 0xbb}, + }, + }, + + // Disabled in non-taproot context. + { + script: NewScriptBuilder(). + AddData([]byte{0xaa}). + AddData([]byte{0xbb}). + AddOp(OP_CAT), + expErr: ErrDisabledOpcode, + expStack: [][]byte{ + {0xaa}, {0xbb}, + }, + nonTaproot: true, + }, + + // Cat with empty element. + { + script: NewScriptBuilder(). + AddData([]byte{0xaa}). + AddData([]byte{}). + AddOp(OP_CAT), + expErr: -1, + expStack: [][]byte{ + {0xaa}, + }, + }, + + // Cat with empty element. + { + script: NewScriptBuilder(). + AddData([]byte{}). + AddData([]byte{0xbb}). + AddOp(OP_CAT), + expErr: -1, + expStack: [][]byte{ + {0xbb}, + }, + }, + + // Cat elements of different lengths. + { + script: NewScriptBuilder(). + AddData([]byte{0xaa, 0xbb, 0xcc}). + AddData([]byte{0xdd}). + AddOp(OP_CAT), + expErr: -1, + expStack: [][]byte{ + {0xaa, 0xbb, 0xcc, 0xdd}, + }, + }, + + // Cat up to max element size. + { + script: NewScriptBuilder(). + AddData( + bytes.Repeat( + []byte{0xaa}, MaxScriptElementSize-1, + ), + ). + AddData([]byte{0xdd}). + AddOp(OP_CAT), + expErr: -1, + expStack: [][]byte{ + append( + bytes.Repeat([]byte{0xaa}, MaxScriptElementSize-1), + 0xdd, + ), + }, + }, + + // Failing to when result exceeds max element size. + { + script: NewScriptBuilder(). + AddData( + bytes.Repeat( + []byte{0xaa}, MaxScriptElementSize-1, + ), + ). + AddData([]byte{0xdd, 0xee}). + AddOp(OP_CAT), + expErr: ErrElementTooBig, + expStack: [][]byte{ + bytes.Repeat([]byte{0xaa}, MaxScriptElementSize-1), + []byte{0xdd, 0xee}, + }, + }, + } + + for _, test := range tests { + tx := wire.NewMsgTx(2) + tx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Index: 0, + }, + }) + + script, err := test.script.Script() + if err != nil { + t.Error(err) + } + + // Assmble the script into a taproot output key. + tapScriptTree := AssembleTaprootScriptTree( + NewBaseTapLeaf(script), + ) + privKey, err := btcec.NewPrivateKey() + if err != nil { + t.Error(err) + continue + } + + inputKey := privKey.PubKey() + ctrlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock(inputKey) + tapScriptRootHash := tapScriptTree.RootNode.TapHash() + inputTapKey := ComputeTaprootOutputKey( + inputKey, tapScriptRootHash[:], + ) + + inputScript, err := PayToTaprootScript(inputTapKey) + if err != nil { + t.Error(err) + continue + + } + + cbBytes, err := ctrlBlock.ToBytes() + if err != nil { + t.Error(err) + continue + } + tx.TxIn[0].Witness = wire.TxWitness{script, cbBytes} + + // As a special case, if we are testing non-taproot spends, we + // recreate the pkscript as a P2WSH. + if test.nonTaproot { + scriptHash := sha256.Sum256(script) + inputScript, err = payToWitnessScriptHashScript(scriptHash[:]) + if err != nil { + t.Error(err) + continue + } + tx.TxIn[0].Witness = wire.TxWitness{script} + } + + prevOut := &wire.TxOut{ + Value: 1e8, + PkScript: inputScript, + } + prevOutFetcher := NewCannedPrevOutputFetcher( + prevOut.PkScript, prevOut.Value, + ) + + sigHashes := NewTxSigHashes(tx, prevOutFetcher) + + // We'll record the final stack, since we want to check it + // against what we expect. + var finalStack [][]byte + cb := func(step *StepInfo) error { + finalStack = step.Stack + return nil + } + + // TODO: test discourage script flags if added. + vm, err := NewDebugEngine( + prevOut.PkScript, tx, 0, StandardVerifyFlags, nil, + sigHashes, prevOut.Value, nil, cb, + ) + if err != nil { + t.Errorf("Failed to create script: %v", err) + continue + } + + err = vm.Execute() + if (test.expErr != -1 || err != nil) && !IsErrorCode(err, test.expErr) { + t.Errorf("Expected error %v, got %v", test.expErr, err) + continue + } + + // Check expected stack. + require.Equal(t, test.expStack, finalStack) + } +} From ae324e23656b811efd58441b46b586f22de22799 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 23 Feb 2024 09:33:10 +0100 Subject: [PATCH 03/12] make sure OP_CAT is invalid in legacy script, even if not executed --- txscript/engine.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index cf15b47eff..5d77915c07 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -333,8 +333,14 @@ func (vm *Engine) isBranchExecuting() bool { // isOpcodeDisabled returns whether or not the opcode is disabled and thus is // always bad to see in the instruction stream (even if turned off by a // conditional). -func isOpcodeDisabled(opcode byte) bool { +func isOpcodeDisabled(opcode byte, tapscript bool) bool { switch opcode { + case OP_CAT: + // CAT is re-enabled for tapscript. + if tapscript { + return false + } + return true case OP_SUBSTR: return true case OP_LEFT: @@ -453,7 +459,7 @@ func checkMinimalDataPush(op *opcode, data []byte) error { // tested in this case. func (vm *Engine) executeOpcode(op *opcode, data []byte) error { // Disabled opcodes are fail on program counter. - if isOpcodeDisabled(op.value) { + if isOpcodeDisabled(op.value, vm.taprootCtx != nil) { str := fmt.Sprintf("attempt to execute disabled opcode %s", op.name) return scriptError(ErrDisabledOpcode, str) } From 824e5e26d53b30ac9c75ddeee1ed3e57e99c3d8f Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 23 Feb 2024 09:38:23 +0100 Subject: [PATCH 04/12] prealloc buffer size --- txscript/opcode.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/txscript/opcode.go b/txscript/opcode.go index 812a5d05d5..a3f8b7aeed 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -1964,19 +1964,19 @@ func opcodeCat(op *opcode, data []byte, vm *Engine) error { return err } - var buf bytes.Buffer - buf.Write(x1) - buf.Write(x2) - - c := buf.Bytes() + n := len(x1) + len(x2) // Ensure result is within the max allowed size. - if len(c) > MaxScriptElementSize { + if n > MaxScriptElementSize { str := fmt.Sprintf("element size %d exceeds max allowed size %d", - len(c), MaxScriptElementSize) + n, MaxScriptElementSize) return scriptError(ErrElementTooBig, str) } + c := make([]byte, n) + copy(c[:], x1[:]) + copy(c[len(x1):], x2[:]) + vm.dstack.PushByteArray(c) return nil } From 0c252101c4c9879fdd682311a8b05c09314608a2 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 23 Feb 2024 10:13:51 +0100 Subject: [PATCH 05/12] txscript: make hasFlag helper method on ScriptFlags --- txscript/engine.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/txscript/engine.go b/txscript/engine.go index 5d77915c07..b516c32a30 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -116,6 +116,10 @@ const ( ScriptVerifyDiscourageUpgradeablePubkeyType ) +func (f ScriptFlags) hasFlag(flag ScriptFlags) bool { + return f&flag == flag +} + const ( // MaxStackSize is the maximum combined height of stack and alt stack // during execution. @@ -316,7 +320,7 @@ type StepInfo struct { // hasFlag returns whether the script engine instance has the passed flag set. func (vm *Engine) hasFlag(flag ScriptFlags) bool { - return vm.flags&flag == flag + return vm.flags.hasFlag(flag) } // isBranchExecuting returns whether or not the current conditional branch is From dcbb36c301803d8028a7f80c1852380235b61b75 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 23 Feb 2024 10:15:02 +0100 Subject: [PATCH 06/12] txscript/script: move success flag checks to ScriptHasOpSuccess --- txscript/engine.go | 15 +++++---------- txscript/script.go | 21 +++++++++++++++++---- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index b516c32a30..c879ad2b3b 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -702,17 +702,12 @@ func (vm *Engine) verifyWitnessProgram(witness wire.TxWitness) error { // check to see if OP_SUCCESS op codes are found in the // script. If so, then we'll return here early as we // skip proper validation. - if ScriptHasOpSuccess(witnessScript) { - // An op success op code has been found, however if - // the policy flag forbidding them is active, then - // we'll return an error. - // TODO: add flag for discourage OP_CAT. - if vm.hasFlag(ScriptVerifyDiscourageOpSuccess) { - errStr := fmt.Sprintf("script contains " + - "OP_SUCCESS op code") - return scriptError(ErrDiscourageOpSuccess, errStr) - } + suc, err := ScriptHasOpSuccess(witnessScript, vm.flags) + if err != nil { + return err + } + if suc { // Otherwise, the script passes scott free. vm.taprootCtx.mustSucceed = true return nil diff --git a/txscript/script.go b/txscript/script.go index 13d6c42711..77df803e60 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -528,7 +528,7 @@ func IsUnspendable(pkScript []byte) bool { // ScriptHasOpSuccess returns true if any op codes in the script contain an // OP_SUCCESS op code. -func ScriptHasOpSuccess(witnessScript []byte) bool { +func ScriptHasOpSuccess(witnessScript []byte, flags ScriptFlags) (bool, error) { // First, create a new script tokenizer so we can run through all the // elements. tokenizer := MakeScriptTokenizer(0, witnessScript) @@ -536,10 +536,23 @@ func ScriptHasOpSuccess(witnessScript []byte) bool { // Run through all the op codes, returning true if we find anything // that is marked as a new op success. for tokenizer.Next() { - if _, ok := successOpcodes[tokenizer.Opcode()]; ok { - return true + op := tokenizer.Opcode() + + if _, ok := successOpcodes[op]; ok { + // An op success op code has been found, however if the + // policy flag forbidding them is active, then we'll + // return an error. + if flags.hasFlag(ScriptVerifyDiscourageOpSuccess) { + errStr := fmt.Sprintf("script contains " + + "OP_SUCCESS op code") + return true, scriptError(ErrDiscourageOpSuccess, + errStr) + } + + return true, nil } + } - return false + return false, nil } From 1c1467c6e0088a9155abdd20cb6dbe0c3b55205d Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 23 Feb 2024 10:20:59 +0100 Subject: [PATCH 07/12] txscript: add flags for OP_CAT activation and discourage --- txscript/engine.go | 8 ++++++++ txscript/script.go | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/txscript/engine.go b/txscript/engine.go index c879ad2b3b..0aad66e9f9 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -114,6 +114,14 @@ const ( // ScriptVerifyDiscourageUpgradeablePubkeyType defines if unknown // public key versions (during tapscript execution) is non-standard. ScriptVerifyDiscourageUpgradeablePubkeyType + + // ScriptVerifyOpCat defines whether to verify an encounted OP_CAT + // opcode in tapscript, or fall back to OP_SUCCESS behavior. + ScriptVerifyOpCat + + // ScriptVerifyDiscourageOpCat defines whether or not to consider usage + // of OP_CAT during tapscript execution as non-standard. + ScriptVerifyDiscourageOpCat ) func (f ScriptFlags) hasFlag(flag ScriptFlags) bool { diff --git a/txscript/script.go b/txscript/script.go index 77df803e60..16f2cff602 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -538,6 +538,24 @@ func ScriptHasOpSuccess(witnessScript []byte, flags ScriptFlags) (bool, error) { for tokenizer.Next() { op := tokenizer.Opcode() + // OP_CAT is considered a success opcode if it is not + // activated. + if op == OP_CAT { + switch { + // If OP_CAT is discouraged, it doesn't matter if it is + // active or not. + case flags.hasFlag(ScriptVerifyDiscourageOpCat): + errStr := fmt.Sprintf("script contains " + + "discouraged OP_CAT op code") + return true, scriptError(ErrDiscourageOpSuccess, + errStr) + + // If not activated it has success behavior. + case !flags.hasFlag(ScriptVerifyOpCat): + return true, nil + } + } + if _, ok := successOpcodes[op]; ok { // An op success op code has been found, however if the // policy flag forbidding them is active, then we'll From e5a86946114f8091cfa3795377d6139553bece70 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 26 Feb 2024 15:32:45 +0100 Subject: [PATCH 08/12] add flag tests --- txscript/engine_test.go | 217 +++++++++++++++++++++++++++------------- 1 file changed, 147 insertions(+), 70 deletions(-) diff --git a/txscript/engine_test.go b/txscript/engine_test.go index 5a9a825c89..8865512ef2 100644 --- a/txscript/engine_test.go +++ b/txscript/engine_test.go @@ -435,39 +435,44 @@ func TestCheckSignatureEncoding(t *testing.T) { func TestOpcodeCat(t *testing.T) { t.Parallel() + // Define helper constants for non-error cases. + const ( + NO_ERROR = -1 + SUCCESS = -2 + ) + tests := []struct { - script *ScriptBuilder + flags ScriptFlags expErr ErrorCode + startStack [][]byte expStack [][]byte nonTaproot bool }{ // No elements to cat. { - script: NewScriptBuilder(). - AddOp(OP_CAT), - expErr: ErrInvalidStackOperation, - expStack: [][]byte{}, + startStack: [][]byte{}, + flags: ScriptVerifyOpCat, + expErr: ErrInvalidStackOperation, }, // Only a single element to cat. { - script: NewScriptBuilder(). - AddData([]byte{0xaa}). - AddOp(OP_CAT), - expErr: ErrInvalidStackOperation, - expStack: [][]byte{ + startStack: [][]byte{ {0xaa}, }, + flags: ScriptVerifyOpCat, + expErr: ErrInvalidStackOperation, }, // Normal cat. { - script: NewScriptBuilder(). - AddData([]byte{0xaa}). - AddData([]byte{0xbb}). - AddOp(OP_CAT), - expErr: -1, + startStack: [][]byte{ + {0xaa}, + {0xbb}, + }, + flags: ScriptVerifyOpCat, + expErr: NO_ERROR, expStack: [][]byte{ {0xaa, 0xbb}, }, @@ -475,24 +480,23 @@ func TestOpcodeCat(t *testing.T) { // Disabled in non-taproot context. { - script: NewScriptBuilder(). - AddData([]byte{0xaa}). - AddData([]byte{0xbb}). - AddOp(OP_CAT), - expErr: ErrDisabledOpcode, - expStack: [][]byte{ - {0xaa}, {0xbb}, + startStack: [][]byte{ + {0xaa}, + {0xbb}, }, + flags: ScriptVerifyOpCat, + expErr: ErrDisabledOpcode, nonTaproot: true, }, // Cat with empty element. { - script: NewScriptBuilder(). - AddData([]byte{0xaa}). - AddData([]byte{}). - AddOp(OP_CAT), - expErr: -1, + startStack: [][]byte{ + {0xaa}, + {}, + }, + flags: ScriptVerifyOpCat, + expErr: NO_ERROR, expStack: [][]byte{ {0xaa}, }, @@ -500,11 +504,12 @@ func TestOpcodeCat(t *testing.T) { // Cat with empty element. { - script: NewScriptBuilder(). - AddData([]byte{}). - AddData([]byte{0xbb}). - AddOp(OP_CAT), - expErr: -1, + startStack: [][]byte{ + {}, + {0xbb}, + }, + flags: ScriptVerifyOpCat, + expErr: NO_ERROR, expStack: [][]byte{ {0xbb}, }, @@ -512,11 +517,13 @@ func TestOpcodeCat(t *testing.T) { // Cat elements of different lengths. { - script: NewScriptBuilder(). - AddData([]byte{0xaa, 0xbb, 0xcc}). - AddData([]byte{0xdd}). - AddOp(OP_CAT), - expErr: -1, + startStack: [][]byte{ + {0xaa, 0xbb, 0xcc}, + {0xdd}, + }, + + flags: ScriptVerifyOpCat, + expErr: NO_ERROR, expStack: [][]byte{ {0xaa, 0xbb, 0xcc, 0xdd}, }, @@ -524,15 +531,14 @@ func TestOpcodeCat(t *testing.T) { // Cat up to max element size. { - script: NewScriptBuilder(). - AddData( - bytes.Repeat( - []byte{0xaa}, MaxScriptElementSize-1, - ), - ). - AddData([]byte{0xdd}). - AddOp(OP_CAT), - expErr: -1, + startStack: [][]byte{ + bytes.Repeat( + []byte{0xaa}, MaxScriptElementSize-1, + ), + {0xdd}, + }, + flags: ScriptVerifyOpCat, + expErr: NO_ERROR, expStack: [][]byte{ append( bytes.Repeat([]byte{0xaa}, MaxScriptElementSize-1), @@ -543,19 +549,61 @@ func TestOpcodeCat(t *testing.T) { // Failing to when result exceeds max element size. { - script: NewScriptBuilder(). - AddData( - bytes.Repeat( - []byte{0xaa}, MaxScriptElementSize-1, - ), - ). - AddData([]byte{0xdd, 0xee}). - AddOp(OP_CAT), + startStack: [][]byte{ + bytes.Repeat( + []byte{0xaa}, MaxScriptElementSize-1, + ), + {0xdd, 0xee}, + }, + flags: ScriptVerifyOpCat, expErr: ErrElementTooBig, - expStack: [][]byte{ - bytes.Repeat([]byte{0xaa}, MaxScriptElementSize-1), - []byte{0xdd, 0xee}, + }, + + // ======== Flag tests ========= + + // Discourage CAT. + { + startStack: [][]byte{ + {0xaa}, {0xbb}, + }, + flags: ScriptVerifyDiscourageOpCat, + expErr: ErrDiscourageOpSuccess, + }, + + // Discourage CAT when CAT is active. + { + startStack: [][]byte{ + {0xaa}, {0xbb}, + }, + flags: ScriptVerifyDiscourageOpCat | ScriptVerifyOpCat, + expErr: ErrDiscourageOpSuccess, + }, + + // Valid CAT but CAT is not active. It should behave as + // OP_SUCCESS. + { + startStack: [][]byte{ + {0xaa}, {0xbb}, + }, + expErr: SUCCESS, + }, + + // Invalid CAT when CAT is not active. It should behave as + // OP_SUCCESS. + { + startStack: [][]byte{ + {0xaa}, + }, + expErr: SUCCESS, + }, + + // Invalid CAT when CAT is active. + { + startStack: [][]byte{ + {0xaa}, }, + flags: ScriptVerifyOpCat, + expErr: ErrInvalidStackOperation, }, } @@ -567,7 +615,13 @@ func TestOpcodeCat(t *testing.T) { }, }) - script, err := test.script.Script() + // OP_CAT will be our one and only script opcode apart from + // making sure the script is valid. + script, err := NewScriptBuilder(). + AddOp(OP_CAT). + AddOp(OP_DROP). + AddInt64(1). + Script() if err != nil { t.Error(err) } @@ -601,7 +655,14 @@ func TestOpcodeCat(t *testing.T) { t.Error(err) continue } - tx.TxIn[0].Witness = wire.TxWitness{script, cbBytes} + + w := wire.TxWitness{} + for _, e := range test.startStack { + w = append(w, e) + } + + w = append(w, script, cbBytes) + tx.TxIn[0].Witness = w // As a special case, if we are testing non-taproot spends, we // recreate the pkscript as a P2WSH. @@ -625,17 +686,26 @@ func TestOpcodeCat(t *testing.T) { sigHashes := NewTxSigHashes(tx, prevOutFetcher) - // We'll record the final stack, since we want to check it - // against what we expect. - var finalStack [][]byte + // We'll record the stack after the CAT operation, since we + // want to check it against what we expect. + finalStack := [][]byte{} cb := func(step *StepInfo) error { + if step.ScriptIndex != 2 { + return nil + } + + // Our script has OP_CAT as first opcode. + if step.OpcodeIndex != 1 { + return nil + } + finalStack = step.Stack return nil } - // TODO: test discourage script flags if added. + flags := StandardVerifyFlags | test.flags vm, err := NewDebugEngine( - prevOut.PkScript, tx, 0, StandardVerifyFlags, nil, + prevOut.PkScript, tx, 0, flags, nil, sigHashes, prevOut.Value, nil, cb, ) if err != nil { @@ -644,12 +714,19 @@ func TestOpcodeCat(t *testing.T) { } err = vm.Execute() - if (test.expErr != -1 || err != nil) && !IsErrorCode(err, test.expErr) { - t.Errorf("Expected error %v, got %v", test.expErr, err) - continue + if err != nil { + if !IsErrorCode(err, test.expErr) { + t.Errorf("Expected error %v, got %v", test.expErr, err) + } + } else { + // Check expected stack if we didn't expect error + // during execution. We skip this check for the SUCCESS + // case, as stack is not altered at all. + if test.expErr == NO_ERROR { + require.Equal(t, test.expStack, finalStack) + } else if test.expErr != SUCCESS { + t.Errorf("Expected error %v, got %v", test.expErr, err) + } } - - // Check expected stack. - require.Equal(t, test.expStack, finalStack) } } From 1305932885f3737149dbbbcca46a728f267f704f Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 7 Mar 2024 15:34:08 +0100 Subject: [PATCH 09/12] txscript reference test: add OP_CAT tests to script_tests.json --- txscript/data/script_tests.json | 338 ++++++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) diff --git a/txscript/data/script_tests.json b/txscript/data/script_tests.json index bd3b4e3125..5d6705e451 100644 --- a/txscript/data/script_tests.json +++ b/txscript/data/script_tests.json @@ -2513,6 +2513,344 @@ ["4294967296", "CHECKSEQUENCEVERIFY", "CHECKSEQUENCEVERIFY", "UNSATISFIED_LOCKTIME", "CSV fails if stack top bit 1 << 31 is not set, and tx version < 2"], +["OP_CAT tests"], +[ + [ + "CAT", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT", + "OK", + "TAPSCRIPT (CAT) Test of OP_CAT flag by calling CAT on an empty stack. This does not error because no OP_CAT flag is set so CAT is OP_SUCCESS" +], +[ + [ + "CAT", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "INVALID_STACK_OPERATION", + "TAPSCRIPT Test of OP_CAT flag by calling CAT on an empty stack. This throws an error because OP_CAT flag is set so CAT is executed" +], +[ + [ + "aa", + "bb", + "CAT", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT Test of OP_CAT flag by calling CAT on two elements. OP_CAT flag is set so CAT is executed." +], +[ + [ + "78a11a1260c1101260", + "78a11a1260", + "c1101260", + "CAT EQUAL", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT CATs 78a11a1260 and c1101260 together and checks it is EQUAL to stack element 78a11a1260c1101260" +], +[ + [ + "78a11a1260c1101260", + "78a11a1260", + "c1101260", + "CAT EQUAL", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT", + "OK", + "TAPSCRIPT Test of OP_CAT flag, CATs 78a11a1260 and c1101260 together and checks it is EQUAL to stack element 78a11a1260c1101260. No OP_CAT set so CAT should be OP_SUCCESS." +], +[ + [ + "", + "78a11a1260", + "c1101260", + "CAT EQUAL", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "EVAL_FALSE", + "TAPSCRIPT CATs 78a11a1260 and c1101260 together and checks it is EQUAL to the empty stack element" +], +[ + [ + "51", + "78a11a1260c1101260", + "78a11a1260", + "c1101260", + "CAT EQUALVERIFY", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT CATS 78a11a1260 and c1101260 together and checks it is EQUALVERIFY to stack element 78a11a1260c1101260" +], +[ + [ + "51", + "c110126078a11a1260", + "78a11a1260", + "c1101260", + "CAT EQUALVERIFY", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "EQUALVERIFY", + "TAPSCRIPT CATs 78a11a1260 and c1101260 together and checks it is EQUALVERIFY to stack element c110126078a11a1260" +], +[ + [ + "aa", + "bb", + "CAT 0x4c 0x02 0xaabb EQUAL", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT CATs aa and bb together and checks EQUAL to aabb" +], +[ + [ + "eeffeeff", + "aa", + "bbcc", + "CAT CAT DUP DROP 0x4c 0x06 0xeeffeeffaabbcc EQUAL", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT CATs aa and bbcc and eeffeff together and checks EQUAL to eeffeeffaabbcc" +], +[ + [ + "c24f2c1e363e09a5dd56f0", + "89a0385490a11b6dc6740f3513", + "CAT 0x4c 0x18 0xc24f2c1e363e09a5dd56f089a0385490a11b6dc6740f3513 EQUAL", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT Tests CAT on different sized random stack elements and compares the result." +], +[ + [ + "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93", + "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93", + "CAT", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT Tests CAT on two hash outputs" +], +[ + [ + "51", + "bbbb", + "01", + "IF CAT ELSE DROP ENDIF", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT Tests CAT inside of an IF ELSE conditional (true IF)" +], +[ + [ + "51", + "", + "IF CAT ELSE DROP ENDIF", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "CLEANSTACK", + "TAPSCRIPT Tests CAT inside of an IF ELSE conditional (false IF)" +], +[ + [ + "1a1a", + "DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT Runs DUP CAT seven times on 1a1a" +], +[ + [ + "1a1a1a1a1a1a1a", + "DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "INVALID_STACK_OPERATION", + "TAPSCRIPT Runs DUP CAT seven times on 1a1a1a1a1a1a1a triggering a stack size error as the result is larger than max stack element size" +], +[ + [ + "1ffe1234567890", + "00", + "HASH256 DUP SHA1 CAT DUP CAT TOALTSTACK HASH256 DUP CAT TOALTSTACK FROMALTSTACK", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT Tests CAT with a melange of other opcodes including FROMALTSTACK. " +], +[ + [ + "CAT", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "INVALID_STACK_OPERATION", + "TAPSCRIPT ([], CAT) Tests CAT fails on empty stack" +], +[ + [ + "09ca7009ca7009ca7009ca7009ca70", + "CAT", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "INVALID_STACK_OPERATION", + "TAPSCRIPT ([09ca7009ca7009ca7009ca7009ca70], CAT) Tests CAT fails on a stack of only one element" +], +[ + [ + "", + "09ca7009ca7009ca7009ca7009ca70", + "CAT", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT (['', 09ca7009ca7009ca7009ca7009ca70], CAT) Tests CAT succeeds when one of the two values to concatenate is of size zero" +], +[ + [ + "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93", + "0102030405060708", + "CAT", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT ([512 byte element, 09ca7009ca7009ca7009ca7009ca70], CAT) Tests edge case where concatenated value is exactly max stack element size (520 bytes)" +], +[ + [ + "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b930102030405060708", + "01", + "CAT", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "INVALID_STACK_OPERATION", + "TAPSCRIPT ([520 byte element, 01], CAT) Tests edge case where concatenated value is one byte larger than max stack element size (520 bytes)" +], +[ + [ + "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b930102030405060708", + "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b930102030405060708", + "CAT", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "INVALID_STACK_OPERATION", + "TAPSCRIPT ([520 byte element, 520 byte element], CAT) Tests case where each element to concatenate is exactly max stack element size (520 bytes)" +], +[ + [ + "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b930102030405060708", + "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93010203040506070809", + "CAT", + "", + 0.00000001 + ], + "", + "0x51 0x20 ", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "PUSH_SIZE", + "TAPSCRIPT ([520 byte element, 521 byte element], CAT) Tests edge case where one of the elements to concatenate is one byte larger than max stack element size (520 bytes)" +], + ["MINIMALIF tests"], ["MINIMALIF is not applied to non-segwit scripts"], ["1", "IF 1 ENDIF", "P2SH,WITNESS,MINIMALIF", "OK"], From dc83526586e76438c65fcf185305423ebf14c1d7 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Wed, 3 Apr 2024 09:59:44 +0200 Subject: [PATCH 10/12] f json --- txscript/data/script_tests.json | 152 ++++++++++++++++---------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/txscript/data/script_tests.json b/txscript/data/script_tests.json index 5d6705e451..6585bc43eb 100644 --- a/txscript/data/script_tests.json +++ b/txscript/data/script_tests.json @@ -2516,24 +2516,24 @@ ["OP_CAT tests"], [ [ - "CAT", - "", + "#SCRIPT# CAT", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT", "OK", "TAPSCRIPT (CAT) Test of OP_CAT flag by calling CAT on an empty stack. This does not error because no OP_CAT flag is set so CAT is OP_SUCCESS" ], [ [ - "CAT", - "", + "#SCRIPT# CAT", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "INVALID_STACK_OPERATION", "TAPSCRIPT Test of OP_CAT flag by calling CAT on an empty stack. This throws an error because OP_CAT flag is set so CAT is executed" @@ -2542,12 +2542,12 @@ [ "aa", "bb", - "CAT", - "", + "#SCRIPT# CAT", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT Test of OP_CAT flag by calling CAT on two elements. OP_CAT flag is set so CAT is executed." @@ -2557,12 +2557,12 @@ "78a11a1260c1101260", "78a11a1260", "c1101260", - "CAT EQUAL", - "", + "#SCRIPT# CAT EQUAL", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT CATs 78a11a1260 and c1101260 together and checks it is EQUAL to stack element 78a11a1260c1101260" @@ -2572,12 +2572,12 @@ "78a11a1260c1101260", "78a11a1260", "c1101260", - "CAT EQUAL", - "", + "#SCRIPT# CAT EQUAL", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT", "OK", "TAPSCRIPT Test of OP_CAT flag, CATs 78a11a1260 and c1101260 together and checks it is EQUAL to stack element 78a11a1260c1101260. No OP_CAT set so CAT should be OP_SUCCESS." @@ -2587,12 +2587,12 @@ "", "78a11a1260", "c1101260", - "CAT EQUAL", - "", + "#SCRIPT# CAT EQUAL", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "EVAL_FALSE", "TAPSCRIPT CATs 78a11a1260 and c1101260 together and checks it is EQUAL to the empty stack element" @@ -2603,12 +2603,12 @@ "78a11a1260c1101260", "78a11a1260", "c1101260", - "CAT EQUALVERIFY", - "", + "#SCRIPT# CAT EQUALVERIFY", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT CATS 78a11a1260 and c1101260 together and checks it is EQUALVERIFY to stack element 78a11a1260c1101260" @@ -2619,12 +2619,12 @@ "c110126078a11a1260", "78a11a1260", "c1101260", - "CAT EQUALVERIFY", - "", + "#SCRIPT# CAT EQUALVERIFY", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "EQUALVERIFY", "TAPSCRIPT CATs 78a11a1260 and c1101260 together and checks it is EQUALVERIFY to stack element c110126078a11a1260" @@ -2633,12 +2633,12 @@ [ "aa", "bb", - "CAT 0x4c 0x02 0xaabb EQUAL", - "", + "#SCRIPT# CAT 0x4c 0x02 0xaabb EQUAL", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT CATs aa and bb together and checks EQUAL to aabb" @@ -2647,13 +2647,13 @@ [ "eeffeeff", "aa", - "bbcc", - "CAT CAT DUP DROP 0x4c 0x06 0xeeffeeffaabbcc EQUAL", - "", + "bbff", + "#SCRIPT# CAT CAT DUP DROP 0x4c 0x07 0xeeffeeffaabbff EQUAL", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT CATs aa and bbcc and eeffeff together and checks EQUAL to eeffeeffaabbcc" @@ -2662,12 +2662,12 @@ [ "c24f2c1e363e09a5dd56f0", "89a0385490a11b6dc6740f3513", - "CAT 0x4c 0x18 0xc24f2c1e363e09a5dd56f089a0385490a11b6dc6740f3513 EQUAL", - "", + "#SCRIPT# CAT 0x4c 0x18 0xc24f2c1e363e09a5dd56f089a0385490a11b6dc6740f3513 EQUAL", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT Tests CAT on different sized random stack elements and compares the result." @@ -2676,12 +2676,12 @@ [ "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93", "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93", - "CAT", - "", + "#SCRIPT# CAT", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT Tests CAT on two hash outputs" @@ -2691,12 +2691,12 @@ "51", "bbbb", "01", - "IF CAT ELSE DROP ENDIF", - "", + "#SCRIPT# IF CAT ELSE DROP ENDIF", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT Tests CAT inside of an IF ELSE conditional (true IF)" @@ -2705,12 +2705,12 @@ [ "51", "", - "IF CAT ELSE DROP ENDIF", - "", + "#SCRIPT# IF CAT ELSE DROP ENDIF", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "CLEANSTACK", "TAPSCRIPT Tests CAT inside of an IF ELSE conditional (false IF)" @@ -2718,12 +2718,12 @@ [ [ "1a1a", - "DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT", - "", + "#SCRIPT# DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT Runs DUP CAT seven times on 1a1a" @@ -2731,38 +2731,38 @@ [ [ "1a1a1a1a1a1a1a", - "DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT", - "", + "#SCRIPT# DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", - "INVALID_STACK_OPERATION", + "PUSH_SIZE", "TAPSCRIPT Runs DUP CAT seven times on 1a1a1a1a1a1a1a triggering a stack size error as the result is larger than max stack element size" ], [ [ "1ffe1234567890", "00", - "HASH256 DUP SHA1 CAT DUP CAT TOALTSTACK HASH256 DUP CAT TOALTSTACK FROMALTSTACK", - "", + "#SCRIPT# HASH256 DUP SHA1 CAT DUP CAT TOALTSTACK HASH256 DUP CAT TOALTSTACK FROMALTSTACK", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT Tests CAT with a melange of other opcodes including FROMALTSTACK. " ], [ [ - "CAT", - "", + "#SCRIPT# CAT", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "INVALID_STACK_OPERATION", "TAPSCRIPT ([], CAT) Tests CAT fails on empty stack" @@ -2770,12 +2770,12 @@ [ [ "09ca7009ca7009ca7009ca7009ca70", - "CAT", - "", + "#SCRIPT# CAT", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "INVALID_STACK_OPERATION", "TAPSCRIPT ([09ca7009ca7009ca7009ca7009ca70], CAT) Tests CAT fails on a stack of only one element" @@ -2784,12 +2784,12 @@ [ "", "09ca7009ca7009ca7009ca7009ca70", - "CAT", - "", + "#SCRIPT# CAT", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT (['', 09ca7009ca7009ca7009ca7009ca70], CAT) Tests CAT succeeds when one of the two values to concatenate is of size zero" @@ -2798,12 +2798,12 @@ [ "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93", "0102030405060708", - "CAT", - "", + "#SCRIPT# CAT", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT ([512 byte element, 09ca7009ca7009ca7009ca7009ca70], CAT) Tests edge case where concatenated value is exactly max stack element size (520 bytes)" @@ -2812,40 +2812,40 @@ [ "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b930102030405060708", "01", - "CAT", - "", + "#SCRIPT# CAT", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", - "INVALID_STACK_OPERATION", + "PUSH_SIZE", "TAPSCRIPT ([520 byte element, 01], CAT) Tests edge case where concatenated value is one byte larger than max stack element size (520 bytes)" ], [ [ "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b930102030405060708", "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b930102030405060708", - "CAT", - "", + "#SCRIPT# CAT", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", - "INVALID_STACK_OPERATION", + "PUSH_SIZE", "TAPSCRIPT ([520 byte element, 520 byte element], CAT) Tests case where each element to concatenate is exactly max stack element size (520 bytes)" ], [ [ "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b930102030405060708", "f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93f821125522f9490bcd108cdd0effbb002d45c6e66e6b48aeb51c865743796b93010203040506070809", - "CAT", - "", + "#SCRIPT# CAT", + "#CONTROLBLOCK#", 0.00000001 ], "", - "0x51 0x20 ", + "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "PUSH_SIZE", "TAPSCRIPT ([520 byte element, 521 byte element], CAT) Tests edge case where one of the elements to concatenate is one byte larger than max stack element size (520 bytes)" From 6d7c4a1288529414485df7b6f44c00340107ee23 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 7 Mar 2024 15:34:50 +0100 Subject: [PATCH 11/12] WIP: impl OP_CAT reference tests --- txscript/reference_test.go | 89 ++++++++++++++++++++++++++++++++------ txscript/standard_test.go | 2 +- 2 files changed, 76 insertions(+), 15 deletions(-) diff --git a/txscript/reference_test.go b/txscript/reference_test.go index 16f06c4f70..ba6e66a733 100644 --- a/txscript/reference_test.go +++ b/txscript/reference_test.go @@ -17,6 +17,8 @@ import ( "strings" "testing" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" @@ -59,20 +61,67 @@ func parseHex(tok string) ([]byte, error) { return hex.DecodeString(tok[2:]) } +var testPrivKey, _ = btcec.NewPrivateKey() + // parseWitnessStack parses a json array of witness items encoded as hex into a // slice of witness elements. -func parseWitnessStack(elements []interface{}) ([][]byte, error) { - witness := make([][]byte, len(elements)) +func parseWitnessStack(elements []interface{}) ([][]byte, + *IndexedTapScriptTree, error) { + + const ( + autogenScript = "" + autogenControlblock = "" + ) + + var ( + witness = make([][]byte, len(elements)) + tree *IndexedTapScriptTree + pkScript []byte + ) for i, e := range elements { - witElement, err := hex.DecodeString(e.(string)) - if err != nil { - return nil, err - } + s := e.(string) + switch { + case strings.HasPrefix(s, autogenScript): + scriptStr := strings.TrimPrefix(s, autogenScript) + script, err := parseShortForm(scriptStr, nil) + if err != nil { + return nil, nil, err + } + + pkScript = script + witness[i] = script + + case strings.HasPrefix(s, autogenControlblock): + if len(pkScript) == 0 { + return nil, nil, fmt.Errorf("tap script not set") + } + + leaf := NewBaseTapLeaf(pkScript) + tree = AssembleTaprootScriptTree(leaf) + + inputKey := testPrivKey.PubKey() + ctrlBlock := tree.LeafMerkleProofs[0].ToControlBlock( + inputKey, + ) - witness[i] = witElement + ctrlBlockBytes, err := ctrlBlock.ToBytes() + if err != nil { + return nil, nil, err + } + + witness[i] = ctrlBlockBytes + + default: + witElement, err := hex.DecodeString(s) + if err != nil { + return nil, nil, err + } + + witness[i] = witElement + } } - return witness, nil + return witness, tree, nil } // shortFormOps holds a map of opcode names to values for use in short form @@ -90,7 +139,7 @@ var shortFormOps map[string]byte // 0x14 is OP_DATA_20) // - Single quoted strings are pushed as data // - Anything else is an error -func parseShortForm(script string) ([]byte, error) { +func parseShortForm(script string, tree *IndexedTapScriptTree) ([]byte, error) { // Only create the short form opcode map once. if shortFormOps == nil { ops := make(map[string]byte) @@ -142,6 +191,15 @@ func parseShortForm(script string) ([]byte, error) { builder.AddFullData([]byte(tok[1 : len(tok)-1])) } else if opcode, ok := shortFormOps[tok]; ok { builder.AddOp(opcode) + } else if tok == "" { + inputKey := testPrivKey.PubKey() + tapScriptRootHash := tree.RootNode.TapHash() + tapKey := ComputeTaprootOutputKey( + inputKey, tapScriptRootHash[:], + ) + + pubKeyBytes := schnorr.SerializePubKey(tapKey) + builder.script = append(builder.script, pubKeyBytes...) } else { return nil, fmt.Errorf("bad token %q", tok) } @@ -196,6 +254,8 @@ func parseScriptFlags(flagStr string) (ScriptFlags, error) { flags |= ScriptVerifyWitnessPubKeyType case "TAPROOT": flags |= ScriptVerifyTaproot + case "OP_CAT": + flags |= ScriptVerifyOpCat default: return flags, fmt.Errorf("invalid flag: %s", flag) } @@ -358,6 +418,7 @@ func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) { var ( witness wire.TxWitness inputAmt btcutil.Amount + tapTree *IndexedTapScriptTree ) // When the first field of the test data is a slice it contains @@ -371,7 +432,7 @@ func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) { // all but the last element in order to parse the // witness stack. strWitnesses := witnessData[:len(witnessData)-1] - witness, err = parseWitnessStack(strWitnesses) + witness, tapTree, err = parseWitnessStack(strWitnesses) if err != nil { t.Errorf("%s: can't parse witness; %v", name, err) continue @@ -392,7 +453,7 @@ func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) { t.Errorf("%s: signature script is not a string", name) continue } - scriptSig, err := parseShortForm(scriptSigStr) + scriptSig, err := parseShortForm(scriptSigStr, tapTree) if err != nil { t.Errorf("%s: can't parse signature script: %v", name, err) @@ -405,7 +466,7 @@ func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) { t.Errorf("%s: public key script is not a string", name) continue } - scriptPubKey, err := parseShortForm(scriptPubKeyStr) + scriptPubKey, err := parseShortForm(scriptPubKeyStr, tapTree) if err != nil { t.Errorf("%s: can't parse public key script: %v", name, err) @@ -624,7 +685,7 @@ testloop: continue testloop } - script, err := parseShortForm(oscript) + script, err := parseShortForm(oscript, nil) if err != nil { t.Errorf("bad test (%dth input script doesn't "+ "parse %v) %d: %v", j, err, i, test) @@ -781,7 +842,7 @@ testloop: continue } - script, err := parseShortForm(oscript) + script, err := parseShortForm(oscript, nil) if err != nil { t.Errorf("bad test (%dth input script doesn't "+ "parse %v) %d: %v", j, err, i, test) diff --git a/txscript/standard_test.go b/txscript/standard_test.go index 4993a65260..29d2988c86 100644 --- a/txscript/standard_test.go +++ b/txscript/standard_test.go @@ -21,7 +21,7 @@ import ( // tests as a helper since the only way it can fail is if there is an error in // the test source code. func mustParseShortForm(script string) []byte { - s, err := parseShortForm(script) + s, err := parseShortForm(script, nil) if err != nil { panic("invalid short form script in test source: err " + err.Error() + ", script: " + script) From 4ffc4019ba0e37463649a37a1485dfe294f1675e Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Wed, 3 Apr 2024 10:03:41 +0200 Subject: [PATCH 12/12] f ref test --- txscript/reference_test.go | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/txscript/reference_test.go b/txscript/reference_test.go index ba6e66a733..263e115486 100644 --- a/txscript/reference_test.go +++ b/txscript/reference_test.go @@ -63,16 +63,19 @@ func parseHex(tok string) ([]byte, error) { var testPrivKey, _ = btcec.NewPrivateKey() +// Tags that are used to know how to parse the various elements of the +// reference test json. +const ( + tagScript = "#SCRIPT#" + tagControlblock = "#CONTROLBLOCK#" + tagTaprootOutput = "#TAPROOTOUTPUT#" +) + // parseWitnessStack parses a json array of witness items encoded as hex into a // slice of witness elements. func parseWitnessStack(elements []interface{}) ([][]byte, *IndexedTapScriptTree, error) { - const ( - autogenScript = "" - autogenControlblock = "" - ) - var ( witness = make([][]byte, len(elements)) tree *IndexedTapScriptTree @@ -81,8 +84,11 @@ func parseWitnessStack(elements []interface{}) ([][]byte, for i, e := range elements { s := e.(string) switch { - case strings.HasPrefix(s, autogenScript): - scriptStr := strings.TrimPrefix(s, autogenScript) + + // In case we encounter a script tag, we'll intepret the + // element as a script. + case strings.HasPrefix(s, tagScript): + scriptStr := strings.TrimPrefix(s, tagScript) script, err := parseShortForm(scriptStr, nil) if err != nil { return nil, nil, err @@ -91,7 +97,8 @@ func parseWitnessStack(elements []interface{}) ([][]byte, pkScript = script witness[i] = script - case strings.HasPrefix(s, autogenControlblock): + // We will create a control block from the current tap script. + case strings.HasPrefix(s, tagControlblock): if len(pkScript) == 0 { return nil, nil, fmt.Errorf("tap script not set") } @@ -191,7 +198,11 @@ func parseShortForm(script string, tree *IndexedTapScriptTree) ([]byte, error) { builder.AddFullData([]byte(tok[1 : len(tok)-1])) } else if opcode, ok := shortFormOps[tok]; ok { builder.AddOp(opcode) - } else if tok == "" { + } else if tok == tagTaprootOutput { + // Use the current tap tree to create a taproot output. + if tree == nil { + return nil, fmt.Errorf("no taptree set") + } inputKey := testPrivKey.PubKey() tapScriptRootHash := tree.RootNode.TapHash() tapKey := ComputeTaprootOutputKey(