@smartledger/bsv 8.1.0 → 8.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +166 -0
- package/README.md +38 -38
- package/bsv-ecies.min.js +1 -1
- package/bsv-gdaf.min.js +62 -62
- package/bsv-ltp.min.js +48 -48
- package/bsv-smartcontract.min.js +1 -1
- package/bsv.bundle.js +62 -62
- package/bsv.min.js +62 -62
- package/docs/AUDIT_SCOPE.md +8 -8
- package/docs/BRC220_ENCODING_AMENDMENT.md +100 -0
- package/docs/BRC220_PLAN.md +224 -0
- package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
- package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
- package/docs/getting-started/INSTALLATION.md +23 -23
- package/docs/getting-started/QUICK_START.md +7 -7
- package/docs/migration/FROM_BSV_1_5_6.md +5 -5
- package/index.js +5 -0
- package/index.mjs +1 -0
- package/lib/gdaf/attestation-signer.js +54 -2
- package/lib/gdaf/attestation-verifier.js +41 -25
- package/lib/gdaf/zk-prover.js +143 -46
- package/lib/notaryhash/certificate.js +282 -0
- package/lib/notaryhash/encoding.js +150 -0
- package/lib/notaryhash/index.js +334 -0
- package/lib/notaryhash/merkle.js +209 -0
- package/lib/notaryhash/script.js +261 -0
- package/lib/notaryhash/suites.js +156 -0
- package/lib/util/jcs.js +75 -0
- package/package.json +9 -6
- package/test/gdaf/canonicalization.js +106 -0
- package/test/gdaf/zk_prover.js +204 -0
- package/test/notaryhash/certificate.js +249 -0
- package/test/notaryhash/encoding.js +186 -0
- package/test/notaryhash/merkle.js +181 -0
- package/test/notaryhash/script.js +270 -0
- package/test/notaryhash/verify.js +339 -0
- package/tools/minimal_reproduction.js +119 -0
- package/tools/opcode_map.js +342 -0
- package/tools/server.js +97 -0
- package/tools/simple_real_tx.js +136 -0
- package/tools/sv-sighash-harness.js +123 -0
- package/tools/sv-sighash-report.js +71 -0
- package/tools/sv-tx-harness.js +172 -0
- package/tools/sv-tx-report.js +43 -0
- package/tools/sv-vector-harness.js +304 -0
- package/tools/sv-vector-report.js +88 -0
- package/version.js +1 -1
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BSV Opcode Mapping and Interpreter Configuration Tool
|
|
5
|
+
*
|
|
6
|
+
* Maps all opcodes with their enabled/disabled status and required flags
|
|
7
|
+
* Provides helper functions for script execution with proper flags
|
|
8
|
+
*
|
|
9
|
+
* Usage: node opcode_map.js
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const bsv = require('./index.js');
|
|
13
|
+
const { Opcode, Script } = bsv;
|
|
14
|
+
const Interpreter = bsv.Script.Interpreter;
|
|
15
|
+
|
|
16
|
+
// Create reverse opcode mapping
|
|
17
|
+
const opcodeMap = {};
|
|
18
|
+
for (let key in Opcode.map) {
|
|
19
|
+
const code = Opcode.map[key];
|
|
20
|
+
opcodeMap[code] = key;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Define opcode categories and their enablement requirements
|
|
24
|
+
const OPCODE_CATEGORIES = {
|
|
25
|
+
// Always enabled opcodes
|
|
26
|
+
CORE: {
|
|
27
|
+
description: 'Core opcodes - always enabled',
|
|
28
|
+
flag: null,
|
|
29
|
+
opcodes: [
|
|
30
|
+
'OP_0', 'OP_1NEGATE', 'OP_1', 'OP_2', 'OP_3', 'OP_4', 'OP_5', 'OP_6', 'OP_7', 'OP_8', 'OP_9', 'OP_10',
|
|
31
|
+
'OP_11', 'OP_12', 'OP_13', 'OP_14', 'OP_15', 'OP_16', 'OP_NOP', 'OP_IF', 'OP_NOTIF', 'OP_ELSE', 'OP_ENDIF',
|
|
32
|
+
'OP_VERIFY', 'OP_RETURN', 'OP_TOALTSTACK', 'OP_FROMALTSTACK', 'OP_2DROP', 'OP_2DUP', 'OP_3DUP', 'OP_2OVER',
|
|
33
|
+
'OP_2ROT', 'OP_2SWAP', 'OP_IFDUP', 'OP_DEPTH', 'OP_DROP', 'OP_DUP', 'OP_NIP', 'OP_OVER', 'OP_PICK', 'OP_ROLL',
|
|
34
|
+
'OP_ROT', 'OP_SWAP', 'OP_TUCK', 'OP_SIZE', 'OP_EQUAL', 'OP_EQUALVERIFY', 'OP_1ADD', 'OP_1SUB', 'OP_NEGATE',
|
|
35
|
+
'OP_ABS', 'OP_NOT', 'OP_0NOTEQUAL', 'OP_ADD', 'OP_SUB', 'OP_BOOLAND', 'OP_BOOLOR', 'OP_NUMEQUAL',
|
|
36
|
+
'OP_NUMEQUALVERIFY', 'OP_NUMNOTEQUAL', 'OP_LESSTHAN', 'OP_GREATERTHAN', 'OP_LESSTHANOREQUAL',
|
|
37
|
+
'OP_GREATERTHANOREQUAL', 'OP_MIN', 'OP_MAX', 'OP_WITHIN', 'OP_RIPEMD160', 'OP_SHA1', 'OP_SHA256',
|
|
38
|
+
'OP_HASH160', 'OP_HASH256', 'OP_CODESEPARATOR', 'OP_CHECKSIG', 'OP_CHECKSIGVERIFY', 'OP_CHECKMULTISIG',
|
|
39
|
+
'OP_CHECKMULTISIGVERIFY', 'OP_NOP1', 'OP_PUSHDATA1', 'OP_PUSHDATA2', 'OP_PUSHDATA4'
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
// Monolith opcodes (May 2018 upgrade)
|
|
44
|
+
MONOLITH: {
|
|
45
|
+
description: 'Monolith opcodes - require SCRIPT_ENABLE_MONOLITH_OPCODES',
|
|
46
|
+
flag: 'SCRIPT_ENABLE_MONOLITH_OPCODES',
|
|
47
|
+
flagValue: (1 << 18),
|
|
48
|
+
opcodes: [
|
|
49
|
+
'OP_CAT', 'OP_SPLIT', 'OP_NUM2BIN', 'OP_BIN2NUM', 'OP_AND', 'OP_OR', 'OP_XOR', 'OP_DIV', 'OP_MOD'
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// Magnetic opcodes (future upgrade)
|
|
54
|
+
MAGNETIC: {
|
|
55
|
+
description: 'Magnetic opcodes - require SCRIPT_ENABLE_MAGNETIC_OPCODES',
|
|
56
|
+
flag: 'SCRIPT_ENABLE_MAGNETIC_OPCODES',
|
|
57
|
+
flagValue: (1 << 19),
|
|
58
|
+
opcodes: [
|
|
59
|
+
'OP_INVERT', 'OP_MUL', 'OP_LSHIFT', 'OP_RSHIFT'
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// Always disabled opcodes
|
|
64
|
+
DISABLED: {
|
|
65
|
+
description: 'Permanently disabled opcodes',
|
|
66
|
+
flag: null,
|
|
67
|
+
opcodes: [
|
|
68
|
+
'OP_RESERVED', 'OP_VER', 'OP_VERIF', 'OP_VERNOTIF', 'OP_RESERVED1', 'OP_RESERVED2', 'OP_2MUL', 'OP_2DIV'
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// NOP opcodes
|
|
73
|
+
NOPS: {
|
|
74
|
+
description: 'NOP opcodes - may be discouraged with SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS',
|
|
75
|
+
flag: 'SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS',
|
|
76
|
+
flagValue: (1 << 7),
|
|
77
|
+
opcodes: [
|
|
78
|
+
'OP_NOP2', 'OP_NOP3', 'OP_NOP4', 'OP_NOP5', 'OP_NOP6', 'OP_NOP7', 'OP_NOP8', 'OP_NOP9', 'OP_NOP10'
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get opcode information including enablement status
|
|
85
|
+
*/
|
|
86
|
+
function getOpcodeInfo(opcodeValue) {
|
|
87
|
+
const opcodeName = opcodeMap[opcodeValue];
|
|
88
|
+
if (!opcodeName) {
|
|
89
|
+
return {
|
|
90
|
+
value: opcodeValue,
|
|
91
|
+
name: `UNKNOWN_${opcodeValue}`,
|
|
92
|
+
enabled: false,
|
|
93
|
+
category: 'UNKNOWN',
|
|
94
|
+
requiresFlag: null
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Find which category this opcode belongs to
|
|
99
|
+
for (const [category, info] of Object.entries(OPCODE_CATEGORIES)) {
|
|
100
|
+
if (info.opcodes.includes(opcodeName)) {
|
|
101
|
+
return {
|
|
102
|
+
value: opcodeValue,
|
|
103
|
+
hex: `0x${opcodeValue.toString(16).padStart(2, '0')}`,
|
|
104
|
+
name: opcodeName,
|
|
105
|
+
enabled: category !== 'DISABLED',
|
|
106
|
+
category: category,
|
|
107
|
+
requiresFlag: info.flag,
|
|
108
|
+
flagValue: info.flagValue,
|
|
109
|
+
description: info.description
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Default to core if not found in any category
|
|
115
|
+
return {
|
|
116
|
+
value: opcodeValue,
|
|
117
|
+
hex: `0x${opcodeValue.toString(16).padStart(2, '0')}`,
|
|
118
|
+
name: opcodeName,
|
|
119
|
+
enabled: true,
|
|
120
|
+
category: 'CORE',
|
|
121
|
+
requiresFlag: null,
|
|
122
|
+
description: 'Core opcode - always enabled'
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get recommended interpreter flags for different use cases
|
|
128
|
+
*/
|
|
129
|
+
function getInterpreterFlags() {
|
|
130
|
+
return {
|
|
131
|
+
// Minimal flags for basic scripts
|
|
132
|
+
BASIC: Interpreter.SCRIPT_VERIFY_P2SH,
|
|
133
|
+
|
|
134
|
+
// Standard flags for most scripts
|
|
135
|
+
STANDARD: Interpreter.SCRIPT_VERIFY_P2SH |
|
|
136
|
+
Interpreter.SCRIPT_VERIFY_STRICTENC |
|
|
137
|
+
Interpreter.SCRIPT_VERIFY_MINIMALDATA,
|
|
138
|
+
|
|
139
|
+
// Modern BSV with monolith opcodes (recommended for new scripts)
|
|
140
|
+
MODERN: Interpreter.SCRIPT_VERIFY_P2SH |
|
|
141
|
+
Interpreter.SCRIPT_VERIFY_STRICTENC |
|
|
142
|
+
Interpreter.SCRIPT_VERIFY_MINIMALDATA |
|
|
143
|
+
Interpreter.SCRIPT_VERIFY_SIGPUSHONLY |
|
|
144
|
+
Interpreter.SCRIPT_VERIFY_CLEANSTACK |
|
|
145
|
+
Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID |
|
|
146
|
+
Interpreter.SCRIPT_ENABLE_MONOLITH_OPCODES,
|
|
147
|
+
|
|
148
|
+
// Full BSV with all opcodes enabled
|
|
149
|
+
FULL: Interpreter.SCRIPT_VERIFY_P2SH |
|
|
150
|
+
Interpreter.SCRIPT_VERIFY_STRICTENC |
|
|
151
|
+
Interpreter.SCRIPT_VERIFY_MINIMALDATA |
|
|
152
|
+
Interpreter.SCRIPT_VERIFY_SIGPUSHONLY |
|
|
153
|
+
Interpreter.SCRIPT_VERIFY_CLEANSTACK |
|
|
154
|
+
Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID |
|
|
155
|
+
Interpreter.SCRIPT_ENABLE_MONOLITH_OPCODES |
|
|
156
|
+
Interpreter.SCRIPT_ENABLE_MAGNETIC_OPCODES,
|
|
157
|
+
|
|
158
|
+
// Genesis rules (most permissive)
|
|
159
|
+
GENESIS: Interpreter.SCRIPT_VERIFY_P2SH |
|
|
160
|
+
Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID |
|
|
161
|
+
Interpreter.SCRIPT_ENABLE_MONOLITH_OPCODES |
|
|
162
|
+
Interpreter.SCRIPT_ENABLE_MAGNETIC_OPCODES
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Analyze a script and determine required flags
|
|
168
|
+
*/
|
|
169
|
+
function analyzeScriptRequirements(script) {
|
|
170
|
+
let requiredFlags = Interpreter.SCRIPT_VERIFY_P2SH; // Base requirement
|
|
171
|
+
const usedOpcodes = [];
|
|
172
|
+
const issues = [];
|
|
173
|
+
|
|
174
|
+
// Parse script
|
|
175
|
+
let chunks;
|
|
176
|
+
if (typeof script === 'string') {
|
|
177
|
+
// ASM format
|
|
178
|
+
chunks = script.split(' ').map(token => {
|
|
179
|
+
if (token.startsWith('OP_')) {
|
|
180
|
+
const opcodeValue = Object.keys(opcodeMap).find(key => opcodeMap[key] === token);
|
|
181
|
+
return { opcodenum: parseInt(opcodeValue) };
|
|
182
|
+
}
|
|
183
|
+
return { buf: Buffer.from(token, 'hex') };
|
|
184
|
+
});
|
|
185
|
+
} else if (script instanceof Script) {
|
|
186
|
+
chunks = script.chunks;
|
|
187
|
+
} else {
|
|
188
|
+
throw new Error('Invalid script format');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Analyze each chunk
|
|
192
|
+
chunks.forEach((chunk, index) => {
|
|
193
|
+
if (chunk.opcodenum !== undefined) {
|
|
194
|
+
const info = getOpcodeInfo(chunk.opcodenum);
|
|
195
|
+
usedOpcodes.push(info);
|
|
196
|
+
|
|
197
|
+
if (!info.enabled) {
|
|
198
|
+
issues.push(`Opcode ${info.name} at position ${index} is permanently disabled`);
|
|
199
|
+
} else if (info.requiresFlag) {
|
|
200
|
+
if (info.flag === 'SCRIPT_ENABLE_MONOLITH_OPCODES') {
|
|
201
|
+
requiredFlags |= Interpreter.SCRIPT_ENABLE_MONOLITH_OPCODES;
|
|
202
|
+
} else if (info.flag === 'SCRIPT_ENABLE_MAGNETIC_OPCODES') {
|
|
203
|
+
requiredFlags |= Interpreter.SCRIPT_ENABLE_MAGNETIC_OPCODES;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Add FORKID if using any signature opcodes
|
|
210
|
+
const hasSignatureOpcodes = usedOpcodes.some(op =>
|
|
211
|
+
op.name === 'OP_CHECKSIG' || op.name === 'OP_CHECKSIGVERIFY' ||
|
|
212
|
+
op.name === 'OP_CHECKMULTISIG' || op.name === 'OP_CHECKMULTISIGVERIFY'
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
if (hasSignatureOpcodes) {
|
|
216
|
+
requiredFlags |= Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
requiredFlags,
|
|
221
|
+
usedOpcodes,
|
|
222
|
+
issues,
|
|
223
|
+
recommendations: {
|
|
224
|
+
basic: requiredFlags,
|
|
225
|
+
safe: requiredFlags | Interpreter.SCRIPT_VERIFY_STRICTENC | Interpreter.SCRIPT_VERIFY_MINIMALDATA,
|
|
226
|
+
strict: requiredFlags | Interpreter.SCRIPT_VERIFY_STRICTENC | Interpreter.SCRIPT_VERIFY_MINIMALDATA |
|
|
227
|
+
Interpreter.SCRIPT_VERIFY_SIGPUSHONLY | Interpreter.SCRIPT_VERIFY_CLEANSTACK
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Print opcode analysis
|
|
234
|
+
*/
|
|
235
|
+
function printOpcodeAnalysis() {
|
|
236
|
+
console.log('🔧 BSV Opcode Mapping and Interpreter Configuration');
|
|
237
|
+
console.log('═'.repeat(80));
|
|
238
|
+
|
|
239
|
+
// Show interpreter flags
|
|
240
|
+
console.log('\n⚙️ Available Interpreter Flags:');
|
|
241
|
+
const flagNames = Object.keys(Interpreter).filter(key => key.startsWith('SCRIPT_'));
|
|
242
|
+
flagNames.forEach(flagName => {
|
|
243
|
+
const value = Interpreter[flagName];
|
|
244
|
+
console.log(` ${flagName.padEnd(40)}: 0x${value.toString(16).padStart(8, '0').toUpperCase()}`);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Show recommended flag combinations
|
|
248
|
+
console.log('\n🎯 Recommended Flag Combinations:');
|
|
249
|
+
const flags = getInterpreterFlags();
|
|
250
|
+
Object.entries(flags).forEach(([name, value]) => {
|
|
251
|
+
console.log(` ${name.padEnd(10)}: 0x${value.toString(16).padStart(8, '0').toUpperCase()}`);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Show opcode categories
|
|
255
|
+
console.log('\n📋 Opcode Categories:');
|
|
256
|
+
Object.entries(OPCODE_CATEGORIES).forEach(([category, info]) => {
|
|
257
|
+
console.log(`\n ${category} - ${info.description}`);
|
|
258
|
+
if (info.flag) {
|
|
259
|
+
console.log(` Required flag: ${info.flag} (0x${info.flagValue.toString(16).toUpperCase()})`);
|
|
260
|
+
}
|
|
261
|
+
console.log(` Opcodes: ${info.opcodes.length}`);
|
|
262
|
+
|
|
263
|
+
// Show first few opcodes
|
|
264
|
+
const sample = info.opcodes.slice(0, 5);
|
|
265
|
+
console.log(` Sample: ${sample.join(', ')}${info.opcodes.length > 5 ? '...' : ''}`);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Show total counts
|
|
269
|
+
const totalOpcodes = Object.keys(opcodeMap).length;
|
|
270
|
+
const enabledOpcodes = Object.keys(opcodeMap).filter(code => {
|
|
271
|
+
const info = getOpcodeInfo(parseInt(code));
|
|
272
|
+
return info.enabled;
|
|
273
|
+
}).length;
|
|
274
|
+
|
|
275
|
+
console.log(`\n📊 Summary:`);
|
|
276
|
+
console.log(` Total opcodes defined: ${totalOpcodes}`);
|
|
277
|
+
console.log(` Enabled opcodes: ${enabledOpcodes}`);
|
|
278
|
+
console.log(` Disabled opcodes: ${totalOpcodes - enabledOpcodes}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Create a properly configured interpreter
|
|
283
|
+
*/
|
|
284
|
+
function createInterpreter(flagLevel = 'MODERN') {
|
|
285
|
+
const flags = getInterpreterFlags();
|
|
286
|
+
return {
|
|
287
|
+
interpreter: new Interpreter(),
|
|
288
|
+
flags: flags[flagLevel.toUpperCase()] || flags.MODERN,
|
|
289
|
+
|
|
290
|
+
verify: function(scriptSig, scriptPubkey, tx, nin, satoshisBN) {
|
|
291
|
+
return this.interpreter.verify(
|
|
292
|
+
scriptSig,
|
|
293
|
+
scriptPubkey,
|
|
294
|
+
tx,
|
|
295
|
+
nin,
|
|
296
|
+
this.flags,
|
|
297
|
+
satoshisBN
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// CLI execution
|
|
304
|
+
if (require.main === module) {
|
|
305
|
+
printOpcodeAnalysis();
|
|
306
|
+
|
|
307
|
+
// Test with sample nLockTime script
|
|
308
|
+
console.log('\n🧪 Testing nLockTime Script Requirements:');
|
|
309
|
+
const sampleScript = 'OP_OVER OP_HASH256 a7a9bc2c7389fecf7d5279e0909bcaac1b0f7d0ee81713adfb7e97fad95322ad OP_EQUALVERIFY OP_OVER 6a OP_SPLIT OP_NIP 04 OP_SPLIT OP_NIP OP_BIN2NUM e803 OP_NUMEQUALVERIFY 03433a1466b09a27e0067efa9c58ec12ae5e26071a413cfe918b49656c75d7fb2d OP_CHECKSIGVERIFY';
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const analysis = analyzeScriptRequirements(sampleScript);
|
|
313
|
+
console.log(` Required flags: 0x${analysis.requiredFlags.toString(16).toUpperCase()}`);
|
|
314
|
+
console.log(` Used opcodes: ${analysis.usedOpcodes.length}`);
|
|
315
|
+
|
|
316
|
+
if (analysis.issues.length > 0) {
|
|
317
|
+
console.log(` Issues:`);
|
|
318
|
+
analysis.issues.forEach(issue => console.log(` ⚠️ ${issue}`));
|
|
319
|
+
} else {
|
|
320
|
+
console.log(` ✅ No issues found`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
console.log(` Recommendations:`);
|
|
324
|
+
console.log(` Basic: 0x${analysis.recommendations.basic.toString(16).toUpperCase()}`);
|
|
325
|
+
console.log(` Safe: 0x${analysis.recommendations.safe.toString(16).toUpperCase()}`);
|
|
326
|
+
console.log(` Strict: 0x${analysis.recommendations.strict.toString(16).toUpperCase()}`);
|
|
327
|
+
|
|
328
|
+
} catch (error) {
|
|
329
|
+
console.log(` ❌ Error analyzing script: ${error.message}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Export functions and data
|
|
334
|
+
module.exports = {
|
|
335
|
+
opcodeMap,
|
|
336
|
+
OPCODE_CATEGORIES,
|
|
337
|
+
getOpcodeInfo,
|
|
338
|
+
getInterpreterFlags,
|
|
339
|
+
analyzeScriptRequirements,
|
|
340
|
+
createInterpreter,
|
|
341
|
+
printOpcodeAnalysis
|
|
342
|
+
};
|
package/tools/server.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const http = require('http')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
5
|
+
const port = 8080
|
|
6
|
+
|
|
7
|
+
// MIME types for different file extensions
|
|
8
|
+
const mimeTypes = {
|
|
9
|
+
'.html': 'text/html',
|
|
10
|
+
'.js': 'application/javascript',
|
|
11
|
+
'.css': 'text/css',
|
|
12
|
+
'.json': 'application/json',
|
|
13
|
+
'.png': 'image/png',
|
|
14
|
+
'.jpg': 'image/jpg',
|
|
15
|
+
'.gif': 'image/gif',
|
|
16
|
+
'.ico': 'image/x-icon',
|
|
17
|
+
'.svg': 'image/svg+xml',
|
|
18
|
+
'.woff': 'font/woff',
|
|
19
|
+
'.woff2': 'font/woff2'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const server = http.createServer((req, res) => {
|
|
23
|
+
console.log(`Request for ${req.url}`)
|
|
24
|
+
|
|
25
|
+
// Parse URL and remove query string
|
|
26
|
+
let filePath = req.url.split('?')[0]
|
|
27
|
+
|
|
28
|
+
// Default to index.html if no specific file requested
|
|
29
|
+
if (filePath === '/') {
|
|
30
|
+
filePath = '/test.html'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Build full file path
|
|
34
|
+
const fullPath = path.join(__dirname, filePath)
|
|
35
|
+
|
|
36
|
+
// Get file extension
|
|
37
|
+
const extname = path.extname(fullPath)
|
|
38
|
+
const contentType = mimeTypes[extname] || 'application/octet-stream'
|
|
39
|
+
|
|
40
|
+
// Check if file exists
|
|
41
|
+
fs.readFile(fullPath, (err, content) => {
|
|
42
|
+
if (err) {
|
|
43
|
+
if (err.code === 'ENOENT') {
|
|
44
|
+
// File not found
|
|
45
|
+
res.writeHead(404, { 'Content-Type': 'text/html' })
|
|
46
|
+
res.end(`
|
|
47
|
+
<html>
|
|
48
|
+
<body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
|
|
49
|
+
<h1>404 - File Not Found</h1>
|
|
50
|
+
<p>The requested file <strong>${req.url}</strong> was not found.</p>
|
|
51
|
+
<p><a href="/test.html">Go to BSV Security Test</a></p>
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|
|
54
|
+
`)
|
|
55
|
+
} else {
|
|
56
|
+
// Server error
|
|
57
|
+
res.writeHead(500)
|
|
58
|
+
res.end(`Server Error: ${err.code}`)
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// Success - serve the file
|
|
62
|
+
res.writeHead(200, {
|
|
63
|
+
'Content-Type': contentType,
|
|
64
|
+
'Cache-Control': 'no-cache' // Prevent caching for development
|
|
65
|
+
})
|
|
66
|
+
res.end(content)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
server.listen(port, () => {
|
|
72
|
+
console.log(`
|
|
73
|
+
🚀 SmartLedger BSV Test Server Started!
|
|
74
|
+
|
|
75
|
+
📍 Server running at: http://localhost:${port}
|
|
76
|
+
🧪 Test page: http://localhost:${port}/test.html
|
|
77
|
+
📁 Serving files from: ${__dirname}
|
|
78
|
+
|
|
79
|
+
📊 Available files:
|
|
80
|
+
- test.html (Main test page)
|
|
81
|
+
- bsv.min.js (${Math.round(fs.statSync('bsv.min.js').size / 1024)} KB)
|
|
82
|
+
- bsv-message.min.js (${Math.round(fs.statSync('bsv-message.min.js').size / 1024)} KB)
|
|
83
|
+
- bsv-ecies.min.js (${Math.round(fs.statSync('bsv-ecies.min.js').size / 1024)} KB)
|
|
84
|
+
- bsv-mnemonic.min.js (${Math.round(fs.statSync('bsv-mnemonic.min.js').size / 1024)} KB)
|
|
85
|
+
|
|
86
|
+
Press Ctrl+C to stop the server
|
|
87
|
+
`)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// Handle server shutdown gracefully
|
|
91
|
+
process.on('SIGINT', () => {
|
|
92
|
+
console.log('\n👋 Shutting down server...')
|
|
93
|
+
server.close(() => {
|
|
94
|
+
console.log('✅ Server stopped')
|
|
95
|
+
process.exit(0)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Simple Real BSV Transaction Test
|
|
5
|
+
*
|
|
6
|
+
* Uses only real UTXOs to create and broadcast a transaction
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const bsv = require('./index.js');
|
|
10
|
+
const https = require('https');
|
|
11
|
+
|
|
12
|
+
console.log('🚀 Simple Real BSV Transaction Test');
|
|
13
|
+
console.log('===================================\n');
|
|
14
|
+
|
|
15
|
+
const privateKey = new bsv.PrivateKey('L1aW4aubDFB7yfras2S1mN3bqg9nwySY8nkoLmJebSLD5BWv3ENZ');
|
|
16
|
+
const address = privateKey.toAddress().toString();
|
|
17
|
+
|
|
18
|
+
console.log(`Address: ${address}`);
|
|
19
|
+
console.log(`Private Key: ${privateKey.toString()}\n`);
|
|
20
|
+
|
|
21
|
+
// Simple API call
|
|
22
|
+
function makeRequest(url) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
https.get(url, (res) => {
|
|
25
|
+
let data = '';
|
|
26
|
+
res.on('data', chunk => data += chunk);
|
|
27
|
+
res.on('end', () => {
|
|
28
|
+
try {
|
|
29
|
+
resolve(JSON.parse(data));
|
|
30
|
+
} catch (error) {
|
|
31
|
+
reject(new Error(`Failed to parse response: ${error.message}`));
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}).on('error', reject);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function createSimpleTransaction() {
|
|
39
|
+
try {
|
|
40
|
+
// Get real UTXO
|
|
41
|
+
console.log('🔍 Fetching real UTXO...');
|
|
42
|
+
const utxos = await makeRequest(`https://api.whatsonchain.com/v1/bsv/main/address/${address}/unspent`);
|
|
43
|
+
|
|
44
|
+
if (utxos.length === 0) {
|
|
45
|
+
throw new Error('No UTXOs found');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const utxo = utxos[0]; // Use the first UTXO
|
|
49
|
+
console.log(`Found UTXO: ${utxo.tx_hash}:${utxo.tx_pos} = ${utxo.value} sats`);
|
|
50
|
+
|
|
51
|
+
// Create P2PKH script for the UTXO
|
|
52
|
+
const addr = bsv.Address.fromString(address);
|
|
53
|
+
const p2pkhScript = bsv.Script.buildPublicKeyHashOut(address);
|
|
54
|
+
|
|
55
|
+
// Format UTXO for BSV transaction
|
|
56
|
+
const formattedUTXO = {
|
|
57
|
+
txid: utxo.tx_hash,
|
|
58
|
+
vout: utxo.tx_pos,
|
|
59
|
+
address: address,
|
|
60
|
+
satoshis: utxo.value,
|
|
61
|
+
script: p2pkhScript.toHex()
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
console.log(`UTXO Script: ${p2pkhScript.toHex()}`);
|
|
65
|
+
|
|
66
|
+
// Create transaction - send smaller amount since we now have the result of previous tx
|
|
67
|
+
const sendAmount = Math.min(500, Math.floor(utxo.value * 0.8)); // Send 80% or 500 sats, whichever is smaller
|
|
68
|
+
const feePerKb = 10; // 10 sats per KB
|
|
69
|
+
|
|
70
|
+
// Estimate transaction size (typical P2PKH transaction is ~225 bytes)
|
|
71
|
+
const estimatedSize = 225;
|
|
72
|
+
const fee = Math.ceil((estimatedSize * feePerKb) / 1000);
|
|
73
|
+
const changeAmount = utxo.value - sendAmount - fee;
|
|
74
|
+
|
|
75
|
+
if (changeAmount < 0) {
|
|
76
|
+
throw new Error(`Insufficient funds. Need ${sendAmount + fee}, have ${utxo.value}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log(`\n💸 Creating transaction:`);
|
|
80
|
+
console.log(`Send: ${sendAmount} sats`);
|
|
81
|
+
console.log(`Fee rate: ${feePerKb} sats/KB`);
|
|
82
|
+
console.log(`Estimated fee: ${fee} sats`);
|
|
83
|
+
console.log(`Change: ${changeAmount} sats`);
|
|
84
|
+
|
|
85
|
+
const transaction = new bsv.Transaction()
|
|
86
|
+
.from(formattedUTXO)
|
|
87
|
+
.to(address, sendAmount) // Send to ourselves
|
|
88
|
+
.change(address) // Change back to ourselves
|
|
89
|
+
.feePerKb(feePerKb) // Set fee rate
|
|
90
|
+
.sign(privateKey);
|
|
91
|
+
|
|
92
|
+
console.log(`\n✅ Transaction created successfully!`);
|
|
93
|
+
console.log(`Transaction ID: ${transaction.id}`);
|
|
94
|
+
console.log(`Transaction size: ${transaction.toBuffer().length} bytes`);
|
|
95
|
+
|
|
96
|
+
// Validation
|
|
97
|
+
console.log(`\n🔍 Validation:`);
|
|
98
|
+
const isValid = transaction.verify();
|
|
99
|
+
console.log(`Basic BSV validation: ${isValid ? '✅ VALID' : '❌ INVALID'}`);
|
|
100
|
+
|
|
101
|
+
if (isValid) {
|
|
102
|
+
console.log(`Raw transaction: ${transaction.toString()}`);
|
|
103
|
+
|
|
104
|
+
// Test broadcast
|
|
105
|
+
if (process.argv.includes('--broadcast')) {
|
|
106
|
+
console.log('\n📡 Broadcasting transaction...');
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const response = await fetch('https://api.whatsonchain.com/v1/bsv/main/tx/raw', {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: { 'Content-Type': 'application/json' },
|
|
112
|
+
body: JSON.stringify({ txhex: transaction.toString() })
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (response.ok) {
|
|
116
|
+
const result = await response.text();
|
|
117
|
+
console.log(`✅ Transaction broadcast successful!`);
|
|
118
|
+
console.log(`Transaction ID: ${result}`);
|
|
119
|
+
} else {
|
|
120
|
+
const error = await response.text();
|
|
121
|
+
console.log(`❌ Broadcast failed: ${response.status} "${error}"`);
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.log(`❌ Broadcast failed: ${error.message}`);
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
console.log('\n💡 To broadcast: node simple_real_tx.js --broadcast');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.log(`❌ Error: ${error.message}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
createSimpleTransaction();
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared harness for the SV Node transaction digest vectors in
|
|
5
|
+
* test/data/bitcoin-sv/sighash.json.
|
|
6
|
+
*
|
|
7
|
+
* Used by tools/sv-sighash-report.js (which reports) and
|
|
8
|
+
* test/consensus/sv-sighash-vectors.js (which ratchets), so the progress
|
|
9
|
+
* report and the regression gate cannot disagree about what passes.
|
|
10
|
+
*
|
|
11
|
+
* Row layout, from sighash_tests.cpp in the node:
|
|
12
|
+
*
|
|
13
|
+
* [ raw_transaction, script, input_index, hashType,
|
|
14
|
+
* signature_hash (regular), signature_hash (no forkid) ]
|
|
15
|
+
*
|
|
16
|
+
* Two columns pin the routing in SignatureHash() rather than one branch of it.
|
|
17
|
+
* The node takes BIP143 only when forkid is enabled AND requested AND the
|
|
18
|
+
* signature has not asked for the original algorithm:
|
|
19
|
+
*
|
|
20
|
+
* if(enabledSighashForkid && sigHashType.hasForkId() && !sigHashType.hasChronicle())
|
|
21
|
+
* return SignatureHashBIP143(...);
|
|
22
|
+
* return SignatureHashOriginal(...);
|
|
23
|
+
*
|
|
24
|
+
* Note where the Chronicle bit is NOT consulted: the node does not ask whether
|
|
25
|
+
* Chronicle is enabled before honouring it. That gating lives in
|
|
26
|
+
* CheckSignatureEncoding, which rejects a signature carrying the bit outside
|
|
27
|
+
* Chronicle as SCRIPT_ERR_ILLEGAL_CHRONICLE — so such a signature never
|
|
28
|
+
* reaches a digest at all. For a row whose hash type sets 0x20 both columns
|
|
29
|
+
* are therefore identical, the original algorithm being taken either way, and
|
|
30
|
+
* that is what makes carrying both columns worth the trouble.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const bsv = require('..')
|
|
34
|
+
const Script = bsv.Script
|
|
35
|
+
const BN = bsv.crypto.BN
|
|
36
|
+
const Transaction = bsv.Transaction
|
|
37
|
+
const Signature = bsv.crypto.Signature
|
|
38
|
+
const Interpreter = bsv.Script.Interpreter
|
|
39
|
+
const sighash = Transaction.Sighash
|
|
40
|
+
|
|
41
|
+
const rawVectors = require('../test/data/bitcoin-sv/sighash.json')
|
|
42
|
+
|
|
43
|
+
const FORKID = Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID
|
|
44
|
+
const zeroBN = BN.Zero
|
|
45
|
+
|
|
46
|
+
/** The first row of the corpus names the columns rather than carrying data. */
|
|
47
|
+
function rows () {
|
|
48
|
+
return rawVectors.slice(1).filter(v => Array.isArray(v) && v.length >= 6)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Which digest the node produces for this hash type, under each flag setting.
|
|
53
|
+
* Named rather than inferred, so a routing regression reports as one.
|
|
54
|
+
*/
|
|
55
|
+
function algorithm (nhashtype, forkidEnabled) {
|
|
56
|
+
const t = nhashtype >>> 0
|
|
57
|
+
const bip143 = forkidEnabled &&
|
|
58
|
+
(t & Signature.SIGHASH_FORKID) !== 0 &&
|
|
59
|
+
(t & Signature.SIGHASH_CHRONICLE) === 0
|
|
60
|
+
return bip143 ? 'BIP143' : 'OTDA'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Run every vector. Each result is
|
|
65
|
+
*
|
|
66
|
+
* { index, hashType, chronicleBit, algorithm, passed, reason }
|
|
67
|
+
*
|
|
68
|
+
* where `algorithm` is the one the node's routing selects with forkid enabled.
|
|
69
|
+
*/
|
|
70
|
+
function runAll () {
|
|
71
|
+
return rows().map(function (vector, i) {
|
|
72
|
+
const nhashtype = vector[3]
|
|
73
|
+
const chronicleBit = ((nhashtype >>> 0) & Signature.SIGHASH_CHRONICLE) !== 0
|
|
74
|
+
|
|
75
|
+
const base = {
|
|
76
|
+
index: i + 1,
|
|
77
|
+
hashType: nhashtype,
|
|
78
|
+
chronicleBit,
|
|
79
|
+
algorithm: algorithm(nhashtype, true)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// A fresh Script per call: the original algorithm removes code separators
|
|
83
|
+
// in place, so sharing one would let the first call change what the second
|
|
84
|
+
// is given.
|
|
85
|
+
function digest (flags) {
|
|
86
|
+
const tx = new Transaction(Buffer.from(vector[0], 'hex'))
|
|
87
|
+
return sighash.sighash(tx, nhashtype, vector[2],
|
|
88
|
+
Script(Buffer.from(vector[1], 'hex')), zeroBN, flags).toString('hex')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let regular, noForkId
|
|
92
|
+
try {
|
|
93
|
+
regular = digest(FORKID)
|
|
94
|
+
noForkId = digest(0)
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return Object.assign(base, {
|
|
97
|
+
passed: false,
|
|
98
|
+
reason: 'threw: ' + String(e.message).slice(0, 70)
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (regular !== vector[4]) {
|
|
103
|
+
return Object.assign(base, {
|
|
104
|
+
passed: false,
|
|
105
|
+
reason: 'regular column: ' + regular + ' != ' + vector[4]
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
if (noForkId !== vector[5]) {
|
|
109
|
+
return Object.assign(base, {
|
|
110
|
+
passed: false,
|
|
111
|
+
reason: 'no-forkid column: ' + noForkId + ' != ' + vector[5]
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
return Object.assign(base, { passed: true, reason: null })
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Rows whose two columns must agree, because the Chronicle bit is set. */
|
|
119
|
+
function chronicleRows () {
|
|
120
|
+
return rows().filter(v => ((v[3] >>> 0) & Signature.SIGHASH_CHRONICLE) !== 0)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { runAll, rows, chronicleRows, algorithm }
|