@smartledger/bsv 9.19.0 → 9.21.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 +133 -0
- package/README.md +19 -19
- package/bsv-gdaf.min.js +22 -22
- package/bsv-smartcontract.min.js +1 -1
- package/bsv.bundle.js +24 -24
- package/bsv.min.js +24 -24
- package/docs/AUDIT_SCOPE.md +7 -7
- package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
- package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
- package/docs/audit-rfq/cure53.txt +2 -2
- package/docs/audit-rfq/ncc-group.txt +2 -2
- package/docs/audit-rfq/trail-of-bits.txt +2 -2
- 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/gdaf/zk-prover.js +80 -10
- package/lib/notaryhash/certificate.js +31 -0
- package/lib/notaryhash/index.js +93 -3
- package/package.json +1 -1
- package/version.js +1 -1
package/lib/notaryhash/index.js
CHANGED
|
@@ -126,12 +126,40 @@ NotaryHash.txidFromRawTx = function (rawTx) {
|
|
|
126
126
|
/**
|
|
127
127
|
* Find and parse the NotaryHash record in a raw transaction.
|
|
128
128
|
*
|
|
129
|
+
* **Pass `vout` whenever you are verifying a certificate.** Called with one argument this
|
|
130
|
+
* scans every output and returns the first record it finds, which is useful for inspecting
|
|
131
|
+
* a transaction you know nothing about — and wrong for verification, because it makes the
|
|
132
|
+
* certificate's `anchor.vout` decorative: the field can name a payment output, or one past
|
|
133
|
+
* the end, and a record is still found somewhere else in the transaction. `anchor.vout` is
|
|
134
|
+
* covered by no signature, so the scanning form hands an editable field the appearance of
|
|
135
|
+
* having been checked.
|
|
136
|
+
*
|
|
137
|
+
* `verifyAnchorSPV` passes `anchor.vout` for exactly this reason. The one-argument form is
|
|
138
|
+
* kept for inspection and for callers that predate the parameter.
|
|
139
|
+
*
|
|
129
140
|
* @param {Buffer|String} rawTx
|
|
141
|
+
* @param {Number} [vout] read only this output; omit to scan (inspection only)
|
|
130
142
|
* @returns {Object|null} the parsed record, or null if there is none
|
|
131
143
|
*/
|
|
132
|
-
NotaryHash.recordFromRawTx = function (rawTx) {
|
|
144
|
+
NotaryHash.recordFromRawTx = function (rawTx, vout) {
|
|
133
145
|
try {
|
|
134
146
|
var tx = new Transaction(Buffer.isBuffer(rawTx) ? rawTx.toString('hex') : rawTx)
|
|
147
|
+
|
|
148
|
+
// With a vout, read THAT output and no other.
|
|
149
|
+
//
|
|
150
|
+
// Scanning every output means the certificate's `anchor.vout` is decorative: it can name
|
|
151
|
+
// any index, or one past the end, and the record is still found somewhere else in the
|
|
152
|
+
// transaction. `anchor.vout` is covered by no signature, so that made it an editable
|
|
153
|
+
// field that looked authoritative. A caller verifying an anchor must read the output the
|
|
154
|
+
// certificate points at.
|
|
155
|
+
if (vout !== undefined && vout !== null) {
|
|
156
|
+
if (!Number.isInteger(vout) || vout < 0 || vout >= tx.outputs.length) {
|
|
157
|
+
return null
|
|
158
|
+
}
|
|
159
|
+
var at = tx.outputs[vout].script
|
|
160
|
+
return NotaryScript.isNotaryHash(at) ? NotaryScript.parse(at) : null
|
|
161
|
+
}
|
|
162
|
+
|
|
135
163
|
for (var i = 0; i < tx.outputs.length; i++) {
|
|
136
164
|
var script = tx.outputs[i].script
|
|
137
165
|
if (NotaryScript.isNotaryHash(script)) {
|
|
@@ -252,9 +280,9 @@ NotaryHash.verifyAnchorSPV = function (certificate, opts) {
|
|
|
252
280
|
errors.push('rawTx does not hash to anchor.txid')
|
|
253
281
|
}
|
|
254
282
|
|
|
255
|
-
var record = NotaryHash.recordFromRawTx(spv.rawTx)
|
|
283
|
+
var record = NotaryHash.recordFromRawTx(spv.rawTx, c.anchor && c.anchor.vout)
|
|
256
284
|
if (!record) {
|
|
257
|
-
errors.push('no NotaryHash record
|
|
285
|
+
errors.push('no NotaryHash record at anchor.vout in rawTx')
|
|
258
286
|
} else if (!NotaryHash.recordMatchesCertificate(record, c)) {
|
|
259
287
|
errors.push('on-chain record does not match the certificate')
|
|
260
288
|
}
|
|
@@ -349,6 +377,68 @@ NotaryHash.verifyAnchorSPV = function (certificate, opts) {
|
|
|
349
377
|
}
|
|
350
378
|
}
|
|
351
379
|
|
|
380
|
+
// The anchor's own labels must agree with what was actually proved.
|
|
381
|
+
//
|
|
382
|
+
// `anchor.blockTime` and `anchor.blockHeight` are covered by no signature and were
|
|
383
|
+
// checked by nothing, so an edited certificate verified while reporting a different time
|
|
384
|
+
// or height than the header and proof establish. A reader shown "anchored at
|
|
385
|
+
// 2026-06-23T10:06:04Z in block 954784" has no reason to doubt either number.
|
|
386
|
+
//
|
|
387
|
+
// A null is not a claim: a certificate issued before confirmation legitimately carries
|
|
388
|
+
// nulls, and only a stated value is checked.
|
|
389
|
+
var headerBuf = Buffer.isBuffer(opts.header)
|
|
390
|
+
? opts.header
|
|
391
|
+
: Buffer.from(String(opts.header).replace(/^0x/, ''), 'hex')
|
|
392
|
+
if (headerBuf.length === 80 && c.anchor) {
|
|
393
|
+
if (c.anchor.blockTime !== undefined && c.anchor.blockTime !== null) {
|
|
394
|
+
var headerTime = headerBuf.readUInt32LE(68)
|
|
395
|
+
if (Number(c.anchor.blockTime) !== headerTime) {
|
|
396
|
+
errors.push('anchor.blockTime ' + c.anchor.blockTime +
|
|
397
|
+
' is not the time in the block header (' + headerTime + ')')
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (c.anchor.blockHeight !== undefined && c.anchor.blockHeight !== null &&
|
|
401
|
+
spv.blockHeight !== undefined && spv.blockHeight !== null &&
|
|
402
|
+
Number(c.anchor.blockHeight) !== Number(spv.blockHeight)) {
|
|
403
|
+
errors.push('anchor.blockHeight ' + c.anchor.blockHeight +
|
|
404
|
+
' does not equal spv.blockHeight ' + spv.blockHeight)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// anchor.network, when the caller says which chain it expects.
|
|
409
|
+
//
|
|
410
|
+
// The last field in a certificate that no signature covers. BRC-220 calls it descriptive
|
|
411
|
+
// — the headers decide the chain, not the label — so accepting any value is conformant,
|
|
412
|
+
// and this stays OPT-IN for that reason: a testnet caller must not start failing because
|
|
413
|
+
// a default appeared. But a relabelled certificate still renders as a verified record on
|
|
414
|
+
// a page, and a mainnet certificate relabelled "bsv-testnet" verified here, as did one
|
|
415
|
+
// relabelled "not-a-chain".
|
|
416
|
+
//
|
|
417
|
+
// Be clear about what this is worth: it compares a label against the CALLER'S OWN
|
|
418
|
+
// setting. It does not prove which chain the header source serves. A caller who needs
|
|
419
|
+
// that must get its headers from a source it trusts for the chain it means — which is
|
|
420
|
+
// the same reason this library never fetches a header.
|
|
421
|
+
if (opts.network !== undefined && opts.network !== null &&
|
|
422
|
+
c.anchor && c.anchor.network !== undefined && c.anchor.network !== null &&
|
|
423
|
+
String(c.anchor.network) !== String(opts.network)) {
|
|
424
|
+
errors.push('anchor.network is "' + c.anchor.network + '" but the verifier expects "' +
|
|
425
|
+
opts.network + '"')
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// A TSC index must be representable by the path it comes with: a path of n nodes
|
|
429
|
+
// addresses at most 2^n leaves, so a larger index describes a tree the path cannot
|
|
430
|
+
// belong to. Unchecked, the index was another unsigned field that verified while naming
|
|
431
|
+
// an impossible position.
|
|
432
|
+
var mp = spv.merkleProof
|
|
433
|
+
if (mp && Array.isArray(mp.nodes) && mp.index !== undefined && mp.index !== null) {
|
|
434
|
+
var capacity = Math.pow(2, mp.nodes.length)
|
|
435
|
+
if (!Number.isInteger(Number(mp.index)) || Number(mp.index) < 0 ||
|
|
436
|
+
Number(mp.index) >= capacity) {
|
|
437
|
+
errors.push('spv.merkleProof.index ' + mp.index + ' does not fit a path of ' +
|
|
438
|
+
mp.nodes.length + ' nodes, which addresses at most ' + capacity + ' leaves')
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
352
442
|
return { valid: errors.length === 0, errors: errors }
|
|
353
443
|
} catch (e) {
|
|
354
444
|
return { valid: false, errors: ['anchor verification error: ' + e.message] }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartledger/bsv",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.21.0",
|
|
4
4
|
"description": "Bitcoin SV library with an interpreter-verified script engine: OP_PUSH_TX covenants, BIP-143 preimage tooling, and consensus flags that match what miners actually enforce. Also ships DID:web / W3C VC-JWT credentials and the Legal Token Protocol.",
|
|
5
5
|
"author": "SmartLedger Technology <hello@smartledger.technology> (https://smartledger.technology)",
|
|
6
6
|
"homepage": "https://github.com/codenlighten/smartledger-bsv#readme",
|
package/version.js
CHANGED