Skip to content

Commit a77e99c

Browse files
committed
Refactor and expand script verification tests
Refactor script verification tests to use stateful object references (transaction, script pubkey, transaction output, precomputed tx data) instead of inline hex params. Expand coverage from a single file to 11 dedicated files, one per output type (P2PKH, P2SH multisig, CLTV, CSV, P2SH-P2WPKH, P2SH-P2WSH, P2WPKH, P2WSH, P2TR keypath, P2TR scriptpath), each testing valid, corrupted, and flag-sensitivity variants. Adapt dependency tracker to detect refs nested inside array-valued params. Document the new/changed methods in handler-spec.md.
1 parent ed8beec commit a77e99c

15 files changed

+3435
-183
lines changed

docs/handler-spec.md

Lines changed: 170 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,59 @@ Many operations return objects (contexts, blocks, chains, etc.) that must persis
102102
The conformance tests are organized into suites, each testing a specific aspect of the Bitcoin Kernel bindings. Test files are located in [`../testdata/`](../testdata/).
103103

104104
### Script Verification Success Cases
105-
**File:** [`script_verify_success.json`](../testdata/script_verify_success.json)
106105

107106
Test cases where the script verification operation executes successfully and returns a boolean result (true for valid scripts, false for invalid scripts).
108107

108+
#### Script Verification — P2PKH
109+
**File:** [`script_verify_p2pkh.json`](../testdata/script_verify_p2pkh.json)
110+
111+
Verifies a real mainnet P2PKH output against three variants of the spending transaction: a valid signature (passes with no flags and with all pre-taproot flags), a corrupted signature (always fails), and a non-DER signature (passes without `btck_ScriptVerificationFlags_DERSIG`, fails when `btck_ScriptVerificationFlags_DERSIG` is set).
112+
113+
#### Script Verification — P2SH Multisig
114+
**File:** [`script_verify_p2sh_multisig.json`](../testdata/script_verify_p2sh_multisig.json)
115+
116+
Verifies a real mainnet P2SH 2-of-3 multisig output against three spending transaction variants: valid signatures (passes with `btck_ScriptVerificationFlags_P2SH` and with all pre-taproot flags), a corrupted signature (fails with `btck_ScriptVerificationFlags_P2SH` but passes without it), and a non-null dummy stack element (passes with `btck_ScriptVerificationFlags_P2SH` alone, fails when `btck_ScriptVerificationFlags_NULLDUMMY` is also set).
117+
118+
#### Script Verification — CLTV
119+
**File:** [`script_verify_cltv.json`](../testdata/script_verify_cltv.json)
120+
121+
Verifies a P2SH output containing `OP_CHECKLOCKTIMEVERIFY` locked to block 100. The transaction with `locktime=100` passes with `btck_ScriptVerificationFlags_P2SH` + `btck_ScriptVerificationFlags_CHECKLOCKTIMEVERIFY` and with all pre-taproot flags. The transaction with `locktime=50` fails when `btck_ScriptVerificationFlags_CHECKLOCKTIMEVERIFY` is enforced but passes when only `btck_ScriptVerificationFlags_P2SH` is set.
122+
123+
#### Script Verification — CSV
124+
**File:** [`script_verify_csv.json`](../testdata/script_verify_csv.json)
125+
126+
Verifies a P2SH output containing `OP_CHECKSEQUENCEVERIFY` locked to sequence 10. The transaction with `sequence=10` passes with `btck_ScriptVerificationFlags_P2SH` + `btck_ScriptVerificationFlags_CHECKSEQUENCEVERIFY` and with all pre-taproot flags. The transaction with `sequence=5` fails when `btck_ScriptVerificationFlags_CHECKSEQUENCEVERIFY` is enforced but passes when only `btck_ScriptVerificationFlags_P2SH` is set.
127+
128+
#### Script Verification — P2SH-P2WPKH
129+
**File:** [`script_verify_p2sh_p2wpkh.json`](../testdata/script_verify_p2sh_p2wpkh.json)
130+
131+
Verifies a real mainnet P2SH-wrapped P2WPKH output against two spending transaction variants: a valid witness signature (passes with `btck_ScriptVerificationFlags_P2SH` + `btck_ScriptVerificationFlags_WITNESS` and with all pre-taproot flags) and a corrupted witness signature (fails with `btck_ScriptVerificationFlags_WITNESS` enforced, passes with `btck_ScriptVerificationFlags_P2SH` only).
132+
133+
#### Script Verification — P2SH-P2WSH
134+
**File:** [`script_verify_p2sh_p2wsh.json`](../testdata/script_verify_p2sh_p2wsh.json)
135+
136+
Verifies a real mainnet P2SH-wrapped P2WSH output against two spending transaction variants: a valid 2-of-3 multisig witness (passes with `btck_ScriptVerificationFlags_P2SH` + `btck_ScriptVerificationFlags_WITNESS` and with all pre-taproot flags) and a corrupted witness signature (fails with `btck_ScriptVerificationFlags_WITNESS` enforced, passes with `btck_ScriptVerificationFlags_P2SH` only).
137+
138+
#### Script Verification — P2WPKH
139+
**File:** [`script_verify_p2wpkh.json`](../testdata/script_verify_p2wpkh.json)
140+
141+
Verifies a real mainnet native P2WPKH output using the same transaction with two different `amount` values: the correct amount (5003 satoshis) passes with `btck_ScriptVerificationFlags_P2SH` + `btck_ScriptVerificationFlags_WITNESS` and with all pre-taproot flags; an incorrect amount (5002 satoshis) causes the witness commitment check to fail when `btck_ScriptVerificationFlags_WITNESS` is enforced, but passes with `btck_ScriptVerificationFlags_P2SH` only.
142+
143+
#### Script Verification — P2WSH
144+
**File:** [`script_verify_p2wsh.json`](../testdata/script_verify_p2wsh.json)
145+
146+
Verifies a real mainnet native P2WSH output at input index 1 of a two-input transaction. A valid HTLC-style witness script passes with `btck_ScriptVerificationFlags_P2SH` + `btck_ScriptVerificationFlags_WITNESS` and with all pre-taproot flags. A transaction with a corrupted witness signature fails with `btck_ScriptVerificationFlags_WITNESS` enforced, but passes with `btck_ScriptVerificationFlags_P2SH` only.
147+
148+
#### Script Verification — P2TR Key-Path
149+
**File:** [`script_verify_p2tr_keypath.json`](../testdata/script_verify_p2tr_keypath.json)
150+
151+
Verifies a real mainnet P2TR key-path spend. Requires one spent output to build precomputed transaction data for Taproot. A valid Schnorr signature passes with `btck_ScriptVerificationFlags_P2SH` + `btck_ScriptVerificationFlags_WITNESS` + `btck_ScriptVerificationFlags_TAPROOT` and with all flags. A corrupted Schnorr signature fails when `btck_ScriptVerificationFlags_TAPROOT` is enforced but passes with `btck_ScriptVerificationFlags_P2SH` + `btck_ScriptVerificationFlags_WITNESS` only.
152+
153+
#### Script Verification — P2TR Script-Path
154+
**File:** [`script_verify_p2tr_scriptpath.json`](../testdata/script_verify_p2tr_scriptpath.json)
155+
156+
Verifies a real mainnet P2TR script-path spend at input index 1 of a two-input transaction. Requires two spent outputs (one per input) to build precomputed transaction data for Taproot. A valid script-path witness passes with `btck_ScriptVerificationFlags_P2SH` + `btck_ScriptVerificationFlags_WITNESS` + `btck_ScriptVerificationFlags_TAPROOT` and with all flags. A corrupted signature fails when `btck_ScriptVerificationFlags_TAPROOT` is enforced but passes with `btck_ScriptVerificationFlags_P2SH` + `btck_ScriptVerificationFlags_WITNESS` only.
157+
109158
### Script Verification Error Cases
110159
**File:** [`script_verify_errors.json`](../testdata/script_verify_errors.json)
111160

@@ -276,31 +325,142 @@ Gets the block hash from a block tree entry.
276325

277326
---
278327

279-
### Script Verification
328+
### Script Pubkey Operations
329+
330+
#### `btck_script_pubkey_create`
331+
332+
Creates a script pubkey object from hex-encoded data.
333+
334+
**Parameters:**
335+
- `script_pubkey` (string, required): Hex-encoded script pubkey data
336+
337+
**Result:** Reference type - Object containing the reference name from the request `ref` field (e.g., `{"ref": "$script_pubkey"}`)
338+
339+
**Error:** `{}` when operation fails (C API returned null)
340+
341+
---
342+
343+
#### `btck_script_pubkey_destroy`
344+
345+
Destroys a script pubkey and frees associated resources.
346+
347+
**Parameters:**
348+
- `script_pubkey` (reference, required): Script pubkey reference to destroy
349+
350+
**Result:** `null` (void operation)
351+
352+
**Error:** `null` (cannot return error)
353+
354+
---
280355

281356
#### `btck_script_pubkey_verify`
282357

283358
Verifies a script pubkey against spending conditions.
284359

285360
**Parameters:**
286-
- `script_pubkey` (string): Hex-encoded script pubkey to be spent
287-
- `amount` (number): Amount of the script pubkey's associated output. May be zero if the witness flag is not set
288-
- `tx_to` (string): Hex-encoded transaction spending the script_pubkey
289-
- `input_index` (number): Index of the input in tx_to spending the script_pubkey
290-
- `flags` (array of strings): Script verification flags controlling validation constraints. Valid flags include:
361+
- `script_pubkey` (reference, required): Reference to a ScriptPubkey from `btck_script_pubkey_create`
362+
- `amount` (number, required): Amount of the script pubkey's associated output. May be zero if the witness flag is not set
363+
- `tx_to` (reference, required): Reference to a Transaction from `btck_transaction_create`
364+
- `precomputed_txdata` (reference, optional): Reference to PrecomputedTransactionData from `btck_precomputed_transaction_data_create`. Required when the taproot flag is set
365+
- `input_index` (number, required): Index of the input in tx_to spending the script_pubkey
366+
- `flags` (array of strings, required): Script verification flags controlling validation constraints. Valid flags include:
367+
- `btck_ScriptVerificationFlags_NONE`
291368
- `btck_ScriptVerificationFlags_P2SH`
292369
- `btck_ScriptVerificationFlags_DERSIG`
293370
- `btck_ScriptVerificationFlags_NULLDUMMY`
294371
- `btck_ScriptVerificationFlags_CHECKLOCKTIMEVERIFY`
295372
- `btck_ScriptVerificationFlags_CHECKSEQUENCEVERIFY`
296373
- `btck_ScriptVerificationFlags_WITNESS`
297374
- `btck_ScriptVerificationFlags_TAPROOT`
298-
- `spent_outputs` (array of objects): Array of outputs spent by the transaction. May be empty if the taproot flag is not set. Each object contains:
299-
- `script_pubkey` (string): Hex-encoded script pubkey of the spent output
300-
- `amount` (number): Amount in satoshis of the spent output
301375

302376
**Result:** Boolean - true if script is valid, false if invalid
303377

304378
**Error:** On error, returns error code with type `btck_ScriptVerifyStatus` and member can be one of:
305379
- `ERROR_INVALID_FLAGS_COMBINATION` - Invalid or inconsistent verification flags were provided. This occurs when the supplied `script_verify_flags` combination violates internal consistency rules.
306380
- `ERROR_SPENT_OUTPUTS_REQUIRED` - Spent outputs are required but were not provided (e.g., for Taproot verification).
381+
382+
---
383+
384+
### Transaction Operations
385+
386+
#### `btck_transaction_create`
387+
388+
Creates a transaction object from raw hex-encoded transaction data.
389+
390+
**Parameters:**
391+
- `raw_transaction` (string, required): Hex-encoded raw transaction data
392+
393+
**Result:** Reference type - Object containing the reference name from the request `ref` field (e.g., `{"ref": "$transaction"}`)
394+
395+
**Error:** `{}` when operation fails (C API returned null, e.g., invalid transaction bytes)
396+
397+
---
398+
399+
#### `btck_transaction_destroy`
400+
401+
Destroys a transaction and frees associated resources.
402+
403+
**Parameters:**
404+
- `transaction` (reference, required): Transaction reference to destroy
405+
406+
**Result:** `null` (void operation)
407+
408+
**Error:** `null` (cannot return error)
409+
410+
---
411+
412+
### Transaction Output Operations
413+
414+
#### `btck_transaction_output_create`
415+
416+
Creates a transaction output from a script pubkey reference and amount.
417+
418+
**Parameters:**
419+
- `script_pubkey` (reference, required): Reference to a ScriptPubkey from `btck_script_pubkey_create`
420+
- `amount` (number, required): Amount in satoshis
421+
422+
**Result:** Reference type - Object containing the reference name from the request `ref` field (e.g., `{"ref": "$transaction_output"}`)
423+
424+
**Error:** `null` (cannot return error)
425+
426+
---
427+
428+
#### `btck_transaction_output_destroy`
429+
430+
Destroys a transaction output and frees associated resources.
431+
432+
**Parameters:**
433+
- `transaction_output` (reference, required): Transaction output reference to destroy
434+
435+
**Result:** `null` (void operation)
436+
437+
**Error:** `null` (cannot return error)
438+
439+
---
440+
441+
### Precomputed Transaction Data Operations
442+
443+
#### `btck_precomputed_transaction_data_create`
444+
445+
Creates precomputed transaction data for script verification. Precomputed data is reusable when verifying multiple inputs of the same transaction.
446+
447+
**Parameters:**
448+
- `tx_to` (reference, required): Reference to a Transaction from `btck_transaction_create`
449+
- `spent_outputs` (array of references, optional): Array of references to TransactionOutput objects from `btck_transaction_output_create`. Required when `btck_ScriptVerificationFlags_TAPROOT` is set
450+
451+
**Result:** Reference type - Object containing the reference name from the request `ref` field (e.g., `{"ref": "$precomputed_txdata"}`)
452+
453+
**Error:** `{}` when operation fails (C API returned null)
454+
455+
---
456+
457+
#### `btck_precomputed_transaction_data_destroy`
458+
459+
Destroys precomputed transaction data and frees associated resources.
460+
461+
**Parameters:**
462+
- `precomputed_txdata` (reference, required): Precomputed transaction data reference to destroy
463+
464+
**Result:** `null` (void operation)
465+
466+
**Error:** `null` (cannot return error)

runner/dependency_tracker.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ func (dt *DependencyTracker) testUsesStatefulRefs(testIndex int, allTests []Test
125125
}
126126

127127
// extractRefsFromParams extracts all reference names from params JSON.
128-
// Searches for ref objects with structure {"ref": "..."} at the first level of params.
128+
// Searches for ref objects with structure {"ref": "..."} at the top level of params,
129+
// and also inside array values one level deep.
129130
func extractRefsFromParams(params json.RawMessage) []string {
130131
var refs []string
131132

@@ -137,6 +138,16 @@ func extractRefsFromParams(params json.RawMessage) []string {
137138
for _, value := range paramsMap {
138139
if ref, ok := ParseRefObject(value); ok {
139140
refs = append(refs, ref)
141+
continue
142+
}
143+
// Check if value is an array and scan its elements for ref objects
144+
var arr []json.RawMessage
145+
if err := json.Unmarshal(value, &arr); err == nil {
146+
for _, elem := range arr {
147+
if ref, ok := ParseRefObject(elem); ok {
148+
refs = append(refs, ref)
149+
}
150+
}
140151
}
141152
}
142153
return refs

runner/dependency_tracker_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,51 @@ import (
66
"testing"
77
)
88

9+
func TestExtractRefsFromParams(t *testing.T) {
10+
tests := []struct {
11+
description string
12+
params string
13+
wantRefs []string
14+
}{
15+
{
16+
description: "direct ref at top level",
17+
params: `{"input": {"ref": "$ref_a"}}`,
18+
wantRefs: []string{"$ref_a"},
19+
},
20+
{
21+
description: "refs inside array param",
22+
params: `{"items": [{"ref": "$ref_a"}, {"ref": "$ref_b"}]}`,
23+
wantRefs: []string{"$ref_a", "$ref_b"},
24+
},
25+
{
26+
description: "mixed direct and array refs",
27+
params: `{"direct": {"ref": "$ref_a"}, "items": [{"ref": "$ref_b"}]}`,
28+
wantRefs: []string{"$ref_a", "$ref_b"},
29+
},
30+
{
31+
description: "deeply nested ref in array is not extracted",
32+
params: `{"direct": {"ref": "$ref_a"}, "items": [{"nested": {"ref": "$ref_b"}}]}`,
33+
wantRefs: []string{"$ref_a"},
34+
},
35+
{
36+
description: "non-ref values in array are ignored",
37+
params: `{"items": [{"ref": "$ref_a"}, "plain_string", 42]}`,
38+
wantRefs: []string{"$ref_a"},
39+
},
40+
}
41+
42+
for _, tt := range tests {
43+
t.Run(tt.description, func(t *testing.T) {
44+
got := extractRefsFromParams([]byte(tt.params))
45+
slices.Sort(got)
46+
slices.Sort(tt.wantRefs)
47+
if !slices.Equal(got, tt.wantRefs) {
48+
t.Errorf("extractRefsFromParams(%s) = %v, want %v", tt.params, got, tt.wantRefs)
49+
}
50+
})
51+
}
52+
}
53+
954
func TestDependencyTracker_BuildDependencyChains(t *testing.T) {
1055
// Create test cases to verify dependency chain building
1156
testsJSON := `[
@@ -43,6 +88,14 @@ func TestDependencyTracker_BuildDependencyChains(t *testing.T) {
4388
"params": {"first": {"ref": "$ref_b"}, "second": {"ref": "$ref_c"}}
4489
},
4590
"expected_response": {}
91+
},
92+
{
93+
"request": {
94+
"id": "test4",
95+
"method": "use_array",
96+
"params": {"items": [{"ref": "$ref_a"}, {"ref": "$ref_c"}]}
97+
},
98+
"expected_response": {}
4699
}
47100
]`
48101

@@ -86,6 +139,11 @@ func TestDependencyTracker_BuildDependencyChains(t *testing.T) {
86139
wantDepChain: []int{0, 1, 2},
87140
description: "test3 depends on test1 (which depends on test0) and test2",
88141
},
142+
{
143+
testIdx: 4,
144+
wantDepChain: []int{0, 2},
145+
description: "test4 depends on test0 and test2 via refs nested in an array param",
146+
},
89147
}
90148

91149
for _, tt := range tests {

0 commit comments

Comments
 (0)