1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
//! consensus test vector extractor and parser
//!
//! Extracts and parses test vectors from consensus's test data directory.
//! Handles specification's specific JSON formats and converts them to our test format.
use serde_json::Value;
use std::fs;
use std::path::PathBuf;
/// Extract transaction test vectors from specification's tx_valid.json
///
/// Reference format: Array of arrays with format:
/// [[[prevout hash, prevout index, prevout scriptPubKey, amount?], ...], serializedTransaction, flags]
pub fn extract_core_transaction_vectors(core_path: &str) -> Result<(), Box<dyn std::error::Error>> {
let tx_valid_path = PathBuf::from(core_path).join("src/test/data/tx_valid.json");
let tx_invalid_path = PathBuf::from(core_path).join("src/test/data/tx_invalid.json");
if tx_valid_path.exists() {
let content = fs::read_to_string(&tx_valid_path)?;
let json: Value = serde_json::from_str(&content)?;
if let Value::Array(cases) = json {
println!("Found {} valid transaction test cases", cases.len());
// Parse each test case
for (i, case) in cases.iter().enumerate() {
if let Value::Array(test_case) = case {
// Skip string-only entries (comments)
if test_case.len() >= 2 && test_case[0].is_string() && test_case[1].is_string()
{
// Format: [[prevouts], serializedTx, flags]
if test_case.len() >= 3 {
if let Value::Array(prevouts) = &test_case[0] {
if let Value::String(tx_hex) = &test_case[1] {
// Parse flags (can be string or array)
let flags = parse_flags(&test_case[2]);
// Store or process this test vector
// (Implementation would store in our test vector format)
}
}
}
}
}
}
}
}
if tx_invalid_path.exists() {
let content = fs::read_to_string(&tx_invalid_path)?;
let json: Value = serde_json::from_str(&content)?;
if let Value::Array(cases) = json {
println!("Found {} invalid transaction test cases", cases.len());
}
}
Ok(())
}
/// Parse script test vectors from specification's script_tests.json
///
/// Reference format: [scriptSig_string, scriptPubKey_string, flags_string, expected_result, description]
pub fn extract_core_script_vectors(core_path: &str) -> Result<(), Box<dyn std::error::Error>> {
let script_tests_path = PathBuf::from(core_path).join("src/test/data/script_tests.json");
if script_tests_path.exists() {
let content = fs::read_to_string(&script_tests_path)?;
let json: Value = serde_json::from_str(&content)?;
if let Value::Array(cases) = json {
println!("Found {} script test cases", cases.len());
for (i, case) in cases.iter().enumerate() {
if let Value::Array(test_case) = case {
// Skip string-only entries (comments/format descriptions)
if test_case.len() >= 4 && test_case[0].is_string() {
// Format: [scriptSig, scriptPubKey, flags, expected, description]
if let Value::String(script_sig_str) = &test_case[0] {
if let Value::String(script_pubkey_str) = &test_case[1] {
if let Value::String(flags_str) = &test_case[2] {
// Parse flags (e.g., "P2SH,STRICTENC" -> 0x01 | 0x02)
let flags = parse_flag_string(flags_str);
// Parse expected result
let expected = test_case
.get(3)
.and_then(|v| v.as_str())
.map(|s| s == "OK")
.unwrap_or(true);
// Store or process this test vector
}
}
}
}
}
}
}
}
Ok(())
}
/// Parse flags from specification's flag string format
///
/// Consensus uses comma-separated flag names like "P2SH,STRICTENC,DERSIG"
fn parse_flag_string(flags_str: &str) -> u32 {
let mut flags = 0u32;
for flag_name in flags_str.split(',') {
let flag_name = flag_name.trim();
match flag_name {
"P2SH" => flags |= 0x01,
"STRICTENC" => flags |= 0x02,
"DERSIG" => flags |= 0x04,
"LOW_S" => flags |= 0x08,
"NULLDUMMY" => flags |= 0x10,
"SIGPUSHONLY" => flags |= 0x20,
"MINIMALDATA" => flags |= 0x40,
"DISCOURAGE_UPGRADABLE_NOPS" => flags |= 0x80,
"CLEANSTACK" => flags |= 0x100,
"CHECKLOCKTIMEVERIFY" => flags |= 0x200,
"CHECKSEQUENCEVERIFY" => flags |= 0x400,
"WITNESS" => flags |= 0x800,
"DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM" => flags |= 0x1000,
"MINIMALIF" => flags |= 0x2000,
"TAPROOT" => flags |= 0x4000,
"NONE" => flags |= 0,
_ => {
// Unknown flag - log but continue
eprintln!("Unknown flag: {flag_name}");
}
}
}
flags
}
/// Parse flags from JSON value (can be string or number)
fn parse_flags(value: &Value) -> u32 {
match value {
Value::String(s) => parse_flag_string(s),
Value::Number(n) => n.as_u64().unwrap_or(0) as u32,
_ => 0,
}
}
/// Convert reference script string to bytes
///
/// Consensus uses human-readable script format (e.g., "1 2 EQUAL")
/// This needs to be converted to bytecode for our tests.
fn script_string_to_bytes(script_str: &str) -> Vec<u8> {
// This is a simplified conversion - actual implementation would need
// full script parser to handle opcodes, push operations, etc.
let mut bytes = Vec::new();
// Split by whitespace and parse tokens
for token in script_str.split_whitespace() {
// Handle opcodes
match token {
"OP_1" | "1" => bytes.push(blvm_consensus::opcodes::OP_1),
"OP_2" | "2" => bytes.push(blvm_consensus::opcodes::OP_2),
"OP_DUP" | "DUP" => bytes.push(blvm_consensus::opcodes::OP_DUP),
"OP_EQUAL" | "EQUAL" => bytes.push(blvm_consensus::opcodes::OP_EQUAL),
"OP_EQUALVERIFY" | "EQUALVERIFY" => bytes.push(blvm_consensus::opcodes::OP_EQUALVERIFY),
"OP_HASH160" | "HASH160" => bytes.push(blvm_consensus::opcodes::OP_HASH160),
"OP_CHECKSIG" | "CHECKSIG" => bytes.push(blvm_consensus::opcodes::OP_CHECKSIG),
_ => {
// Handle hex literals (0x...)
if token.starts_with("0x") {
if let Ok(byte) = u8::from_str_radix(&token[2..], 16) {
bytes.push(byte);
}
} else if let Ok(num) = token.parse::<u8>() {
// Direct number
bytes.push(num);
}
}
}
}
bytes
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_flag_string() {
assert_eq!(parse_flag_string("P2SH"), 0x01);
assert_eq!(parse_flag_string("P2SH,STRICTENC"), 0x01 | 0x02);
assert_eq!(
parse_flag_string("P2SH,STRICTENC,DERSIG"),
0x01 | 0x02 | 0x04
);
assert_eq!(parse_flag_string("WITNESS,TAPROOT"), 0x800 | 0x4000);
}
#[test]
fn test_script_string_to_bytes() {
let bytes = script_string_to_bytes("1 2 EQUAL");
assert!(bytes.len() >= 3);
assert_eq!(bytes[0], blvm_consensus::opcodes::OP_1); // OP_1
assert_eq!(bytes[1], blvm_consensus::opcodes::OP_2); // OP_2
assert_eq!(bytes[2], blvm_consensus::opcodes::OP_EQUAL); // OP_EQUAL
}
#[test]
fn test_extract_core_vectors() {
// Test extraction from reference repository
let core_path = "/home/user/src/bitcoin";
if std::path::Path::new(core_path).exists() {
let result = extract_core_transaction_vectors(core_path);
assert!(result.is_ok());
let result = extract_core_script_vectors(core_path);
assert!(result.is_ok());
}
}
}