@smartledger/bsv 9.15.0 → 9.16.1
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 +155 -0
- package/README.md +19 -19
- package/bsv-gdaf.min.js +55 -55
- package/bsv-ltp.min.js +36 -36
- package/bsv-smartcontract.min.js +1 -1
- package/bsv.bundle.js +55 -55
- package/bsv.min.js +55 -55
- package/docs/AUDIT_SCOPE.md +6 -6
- package/docs/BRC220_BATCH_LEAF_AMENDMENT.md +16 -4
- package/docs/BRC220_CERTIFICATE_FIELDS_AMENDMENT.md +11 -4
- package/docs/BRC220_PLAN.md +5 -2
- package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
- package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
- package/docs/audit-rfq/cure53.txt +1 -1
- package/docs/audit-rfq/ncc-group.txt +1 -1
- package/docs/audit-rfq/trail-of-bits.txt +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/lib/script/interpreter.js +91 -49
- package/package.json +9 -5
- package/tools/gen-blind-spot-vectors.js +422 -0
- package/tools/minimal_reproduction.js +1 -1
- package/tools/opcode_map.js +11 -4
- package/tools/simple_real_tx.js +1 -1
- package/tools/sv-vector-coverage.js +174 -0
- package/tools/sv-vector-harness.js +8 -4
- package/version.js +1 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What can this corpus NOT tell us?
|
|
5
|
+
*
|
|
6
|
+
* A 1483/1483 score says we agree with the corpus. It says nothing about any rule the corpus
|
|
7
|
+
* never asks about, and the percentage cannot distinguish the two. This reports the difference.
|
|
8
|
+
*
|
|
9
|
+
* The idea is the bsv-scale-protocol session's: tally the verdicts a corpus expects, diff
|
|
10
|
+
* against the verdicts the engine can emit, and name what is left over. Its Rust version is
|
|
11
|
+
* `verdict coverage`; this is the JavaScript one.
|
|
12
|
+
*
|
|
13
|
+
* It reports three axes, because verdict coverage alone would NOT have caught either defect
|
|
14
|
+
* this library shipped:
|
|
15
|
+
*
|
|
16
|
+
* 1. VERDICTS — codes the engine can emit that no row expects. A guard that is present,
|
|
17
|
+
* reachable, and never once exercised by the file we certify ourselves against.
|
|
18
|
+
*
|
|
19
|
+
* 2. FLAG PAIRS — the axis that actually mattered. `SIG_HASHTYPE` IS exercised, by 7 rows, so
|
|
20
|
+
* verdict coverage calls it covered. But no row sets LOW_S at the same time, and LOW_S is
|
|
21
|
+
* what masked it. A check can only be shadowed by another check that is also on, so the
|
|
22
|
+
* unit of coverage for masking is the PAIR, not the flag.
|
|
23
|
+
*
|
|
24
|
+
* 3. TRANSACTION VERSION — every row here carries version 1, so no row can reach Chronicle's
|
|
25
|
+
* malleability relaxations, which apply only above 1. A single column, constant across the
|
|
26
|
+
* whole corpus, silently removed seven rules from the test.
|
|
27
|
+
*
|
|
28
|
+
* Exits 0 always: this measures the corpus, it does not judge the engine.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const fs = require('fs')
|
|
32
|
+
const path = require('path')
|
|
33
|
+
const harness = require('./sv-vector-harness')
|
|
34
|
+
|
|
35
|
+
const rows = require('../test/data/bitcoin-sv/script_tests.json')
|
|
36
|
+
|
|
37
|
+
// Flags BSV mainnet requires today. A low row count on one of these is worse than a low count
|
|
38
|
+
// on a policy flag, which presence/absence alone cannot say.
|
|
39
|
+
const MANDATORY_ON_MAINNET = ['SIGHASH_FORKID', 'STRICTENC', 'UTXO_AFTER_GENESIS']
|
|
40
|
+
|
|
41
|
+
// Worse than untested: the row format has no name for these, so the case cannot be written as a
|
|
42
|
+
// row at all and no amount of adding vectors to this file will cover it. Found by the
|
|
43
|
+
// bsv-scale-protocol session, checking for a bare CHRONICLE token and finding none.
|
|
44
|
+
const CANNOT_EXPRESS = [
|
|
45
|
+
{
|
|
46
|
+
flag: 'CHRONICLE (block era, 1<<20)',
|
|
47
|
+
why: 'no row names it — 42 rows carry UTXO_AFTER_CHRONICLE, the OUTPUT era, and none ' +
|
|
48
|
+
'carries the block era. ILLEGAL_CHRONICLE gates on the block era, so that verdict ' +
|
|
49
|
+
'is unreachable from this file by construction. It needs a hand-built vector.'
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
/** Every SCRIPT_ERR_* this engine can produce, read from the source that produces them. */
|
|
54
|
+
function emittableVerdicts () {
|
|
55
|
+
const files = ['../lib/script/interpreter.js', '../lib/script/script.js']
|
|
56
|
+
const found = new Set()
|
|
57
|
+
for (const f of files) {
|
|
58
|
+
const src = fs.readFileSync(path.join(__dirname, f), 'utf8')
|
|
59
|
+
for (const m of src.matchAll(/SCRIPT_ERR_([A-Z0-9_]+)/g)) found.add(m[1])
|
|
60
|
+
}
|
|
61
|
+
return found
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function main () {
|
|
65
|
+
const parsed = []
|
|
66
|
+
for (const raw of rows) {
|
|
67
|
+
if (!Array.isArray(raw) || raw.length === 1) continue
|
|
68
|
+
const row = harness.parseRow(raw)
|
|
69
|
+
if (row) parsed.push(row)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---- 1. verdicts ----
|
|
73
|
+
const expected = new Set()
|
|
74
|
+
for (const r of parsed) if (r.expected && r.expected !== 'OK') expected.add(r.expected)
|
|
75
|
+
const emittable = emittableVerdicts()
|
|
76
|
+
const aliases = harness.ERROR_CODE_ALIASES
|
|
77
|
+
const unexercised = [...emittable].filter((v) => {
|
|
78
|
+
if (expected.has(v)) return false
|
|
79
|
+
// A narrower name is exercised when the node's broader name is.
|
|
80
|
+
return !(aliases[v] && expected.has(aliases[v]))
|
|
81
|
+
}).sort()
|
|
82
|
+
|
|
83
|
+
// ---- 2. flag pairs ----
|
|
84
|
+
// Names come from the rows themselves, not from a lookup table: the corpus states its own
|
|
85
|
+
// flags, and a table would only reintroduce the mismatch that made the shared vectors give
|
|
86
|
+
// two answers earlier today.
|
|
87
|
+
const setCount = {}
|
|
88
|
+
const pairCount = {}
|
|
89
|
+
for (const r of parsed) {
|
|
90
|
+
const on = String(r.flagStr).split(',').map((s) => s.trim()).filter(Boolean)
|
|
91
|
+
for (const a of on) {
|
|
92
|
+
setCount[a] = (setCount[a] || 0) + 1
|
|
93
|
+
for (const b of on) if (a < b) pairCount[a + '+' + b] = (pairCount[a + '+' + b] || 0) + 1
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Flags that can mask another check by returning before it.
|
|
97
|
+
const MASKERS = ['LOW_S', 'DERSIG', 'STRICTENC', 'MINIMALDATA', 'SIGPUSHONLY', 'CLEANSTACK']
|
|
98
|
+
// A pair can be absent for two very different reasons, and they call for different fixes.
|
|
99
|
+
// The bsv-scale-protocol session made this point: with LOW_S in one row of 1483, most
|
|
100
|
+
// "untested pairs" are really reporting the scarcity of a single flag, and reading them as
|
|
101
|
+
// 13 independent gaps invites 13 vectors when adding rows that set LOW_S at all is cheaper
|
|
102
|
+
// and covers most of them.
|
|
103
|
+
const SCARCE = 5
|
|
104
|
+
const missingPairs = []
|
|
105
|
+
const scarcePairs = []
|
|
106
|
+
const neverSet = []
|
|
107
|
+
for (const a of MASKERS) {
|
|
108
|
+
if (!setCount[a]) { neverSet.push(a); continue }
|
|
109
|
+
for (const b of MASKERS) {
|
|
110
|
+
if (a >= b || !setCount[b]) continue
|
|
111
|
+
if (pairCount[a + '+' + b]) continue
|
|
112
|
+
const entry = `${a} (${setCount[a]}) + ${b} (${setCount[b]})`
|
|
113
|
+
if (setCount[a] <= SCARCE || setCount[b] <= SCARCE) scarcePairs.push(entry)
|
|
114
|
+
else missingPairs.push(entry)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---- 3. transaction version ----
|
|
119
|
+
const byVersion = {}
|
|
120
|
+
for (const r of parsed) byVersion[r.version] = (byVersion[r.version] || 0) + 1
|
|
121
|
+
|
|
122
|
+
const out = []
|
|
123
|
+
out.push('SV Node script corpus — what it cannot tell us')
|
|
124
|
+
out.push('')
|
|
125
|
+
out.push(`rows : ${parsed.length}`)
|
|
126
|
+
out.push(`verdicts this engine emits: ${emittable.size}`)
|
|
127
|
+
out.push(`verdicts the corpus asks : ${expected.size}`)
|
|
128
|
+
out.push('')
|
|
129
|
+
out.push(`--- ${unexercised.length} rules no row in this corpus exercises ---`)
|
|
130
|
+
out.push('agreement here says nothing about them')
|
|
131
|
+
for (const v of unexercised) out.push(' ' + v)
|
|
132
|
+
out.push('')
|
|
133
|
+
out.push('--- how often each flag is set at all ---')
|
|
134
|
+
out.push('a flag that is mandatory in production and set in a handful of rows is a standing')
|
|
135
|
+
out.push('risk, not a gap to be closed once')
|
|
136
|
+
for (const f of Object.keys(setCount).sort((x, y) => setCount[x] - setCount[y])) {
|
|
137
|
+
const pct = ((setCount[f] / parsed.length) * 100).toFixed(1)
|
|
138
|
+
const note = MANDATORY_ON_MAINNET.includes(f) ? ' <- mandatory on BSV mainnet' : ''
|
|
139
|
+
out.push(` ${String(setCount[f]).padStart(5)} ${pct.padStart(5)}% ${f}${note}`)
|
|
140
|
+
}
|
|
141
|
+
out.push('')
|
|
142
|
+
if (neverSet.length) {
|
|
143
|
+
out.push('--- masking flags no row sets at all ---')
|
|
144
|
+
out.push('every pair containing one of these would read as a gap; they are excluded above')
|
|
145
|
+
for (const f of neverSet) out.push(' ' + f)
|
|
146
|
+
out.push('')
|
|
147
|
+
}
|
|
148
|
+
out.push('--- pairs never set together, though BOTH flags are common ---')
|
|
149
|
+
out.push('a check can only be shadowed by another check that is also on, so masking is')
|
|
150
|
+
out.push('covered per PAIR, not per flag. These are the real gaps.')
|
|
151
|
+
if (missingPairs.length === 0) out.push(' (none)')
|
|
152
|
+
for (const p of missingPairs) out.push(' ' + p)
|
|
153
|
+
out.push('')
|
|
154
|
+
out.push(`--- pairs absent because one flag is scarce (<= ${SCARCE} rows) ---`)
|
|
155
|
+
out.push('adding rows that set the scarce flag at all is cheaper than one vector per pair')
|
|
156
|
+
for (const p of scarcePairs) out.push(' ' + p)
|
|
157
|
+
out.push('')
|
|
158
|
+
out.push('--- rules this corpus format cannot express ---')
|
|
159
|
+
for (const f of CANNOT_EXPRESS) {
|
|
160
|
+
out.push(` ${f.flag}: ${f.why}`)
|
|
161
|
+
}
|
|
162
|
+
out.push('')
|
|
163
|
+
out.push('--- transaction version ---')
|
|
164
|
+
for (const v of Object.keys(byVersion).sort()) {
|
|
165
|
+
out.push(` version ${v}: ${byVersion[v]} rows`)
|
|
166
|
+
}
|
|
167
|
+
if (Object.keys(byVersion).length === 1) {
|
|
168
|
+
out.push(' ONE version only. Every rule gated on the transaction version is untested,')
|
|
169
|
+
out.push(' which on BSV means all seven of Chronicle\'s malleability relaxations.')
|
|
170
|
+
}
|
|
171
|
+
console.log(out.join('\n'))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
main()
|
|
@@ -30,10 +30,14 @@ const BufferWriter = bsv.encoding.BufferWriter
|
|
|
30
30
|
|
|
31
31
|
const rawVectors = require('../test/data/bitcoin-sv/script_tests.json')
|
|
32
32
|
|
|
33
|
-
// Mapped by name rather than by value
|
|
34
|
-
// MAGNETIC
|
|
35
|
-
// SCRIPT_GENESIS and SCRIPT_UTXO_AFTER_GENESIS
|
|
36
|
-
//
|
|
33
|
+
// Mapped by name rather than by value, and the reason is no longer the one this comment used
|
|
34
|
+
// to give. It said this library puts MONOLITH and MAGNETIC on 1<<18 and 1<<19, colliding with
|
|
35
|
+
// the node's SCRIPT_GENESIS and SCRIPT_UTXO_AFTER_GENESIS. That collision was real once and was
|
|
36
|
+
// removed: they moved to 1<<11 and 1<<12, which the node leaves unassigned. Bits 18 and 19 now
|
|
37
|
+
// mean the same thing on both sides.
|
|
38
|
+
//
|
|
39
|
+
// Name mapping is still right, for a better reason: the node has no MONOLITH or MAGNETIC flag
|
|
40
|
+
// at all, so there is no value to map to. A row names a rule; only the name is portable.
|
|
37
41
|
const FLAG_MAP = {
|
|
38
42
|
NONE: 'SCRIPT_VERIFY_NONE',
|
|
39
43
|
P2SH: 'SCRIPT_VERIFY_P2SH',
|
package/version.js
CHANGED