@smartledger/bsv 9.8.0 → 9.9.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 +94 -0
- package/README.md +19 -19
- package/bsv-gdaf.min.js +59 -59
- package/bsv-smartcontract.min.js +1 -1
- package/bsv.bundle.js +59 -59
- package/bsv.d.ts +340 -0
- package/bsv.min.js +59 -59
- package/docs/AUDIT_SCOPE.md +6 -6
- package/docs/BRC220_BATCH_LEAF_AMENDMENT.md +16 -6
- package/docs/BRC220_ENCODING_AMENDMENT.md +30 -91
- package/docs/BRC220_PLAN.md +39 -34
- 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/notaryhash/certificate.js +524 -79
- package/lib/notaryhash/index.js +100 -71
- package/lib/notaryhash/merkle.js +94 -0
- package/lib/notaryhash/script.js +8 -1
- package/lib/notaryhash/suites.js +17 -14
- package/package.json +1 -1
- package/tools/gen-brc220-batch-vector.js +7 -3
- package/version.js +1 -1
package/lib/notaryhash/index.js
CHANGED
|
@@ -22,6 +22,10 @@ var Merkle = require('./merkle')
|
|
|
22
22
|
* saying which check failed is unactionable — a bad signature and an unmined transaction
|
|
23
23
|
* are different problems with different fixes.
|
|
24
24
|
*
|
|
25
|
+
* Certificates are in the reference implementation's format — see
|
|
26
|
+
* lib/notaryhash/certificate.js. Every check below normalises first, so a certificate
|
|
27
|
+
* written by 8.3.0–9.8.0 still verifies.
|
|
28
|
+
*
|
|
25
29
|
* NOTE this library never fetches a block header. The spec is explicit that the verifier
|
|
26
30
|
* "trusts only a block header, obtained from any source it chooses", and choosing that
|
|
27
31
|
* source is the caller's decision, not ours: a single provider is a single point of
|
|
@@ -52,12 +56,8 @@ NotaryHash.registerSuite = function (algorithm, suite) {
|
|
|
52
56
|
NotaryHash.verifySignature = function (certificate) {
|
|
53
57
|
try {
|
|
54
58
|
if (!certificate || typeof certificate !== 'object') return false
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
Buffer.from(certificate.payloadHash, 'hex'),
|
|
58
|
-
Buffer.from(certificate.signature, 'hex'),
|
|
59
|
-
Buffer.from(certificate.publicKey, 'hex')
|
|
60
|
-
)
|
|
59
|
+
var f = Certificate.toProofInput(certificate)
|
|
60
|
+
return Suites.verify(f.algorithm, f.payloadHash, f.signature, f.publicKey)
|
|
61
61
|
} catch (e) {
|
|
62
62
|
return false
|
|
63
63
|
}
|
|
@@ -115,36 +115,38 @@ NotaryHash.recordFromRawTx = function (rawTx) {
|
|
|
115
115
|
NotaryHash.recordMatchesCertificate = function (record, certificate) {
|
|
116
116
|
try {
|
|
117
117
|
if (!record || !certificate) return false
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
var c = Certificate.normalize(certificate)
|
|
119
|
+
var recordIsBatch = record.mode === NotaryScript.MODE.BATCH
|
|
120
|
+
var certIsBatch = !!(c.anchor && c.anchor.type === Certificate.ANCHOR_TYPE.BATCH)
|
|
121
|
+
|
|
122
|
+
if (recordIsBatch || certIsBatch) {
|
|
123
|
+
// Both sides must agree it is a batch: a batch certificate pointed at a single
|
|
124
|
+
// proof's record, or the reverse, is a mismatch however the fields line up.
|
|
125
|
+
if (!recordIsBatch || !certIsBatch || !c.merkle) return false
|
|
126
|
+
if (!record.merkleRoot.equals(Certificate.decodeBytes(c.merkle.root, 'hex', 'merkle.root'))) {
|
|
123
127
|
return false
|
|
124
128
|
}
|
|
125
|
-
// leafCount is compared because the inclusion proof
|
|
126
|
-
// wrong one
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
// on-chain u32be is the authoritative value, so it is checked here rather than
|
|
130
|
-
// assumed to be implied.
|
|
131
|
-
return record.leafCount === certificate.merkle.leafCount
|
|
129
|
+
// leafCount is compared because the inclusion proof cannot be relied on to catch a
|
|
130
|
+
// wrong one: for most indices the fold is identical across neighbouring counts.
|
|
131
|
+
// The on-chain u32be is authoritative.
|
|
132
|
+
return record.leafCount === c.merkle.leafCount
|
|
132
133
|
}
|
|
133
134
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (record.
|
|
137
|
-
if (record.proofHash.toString('hex') !== String(certificate.proofHash).toLowerCase()) return false
|
|
135
|
+
var expected = c.mode === Certificate.MODE.FULL ? NotaryScript.MODE.FULL
|
|
136
|
+
: c.mode === Certificate.MODE.HYBRID ? NotaryScript.MODE.HYBRID : -1
|
|
137
|
+
if (record.mode !== expected) return false
|
|
138
138
|
|
|
139
|
-
var
|
|
140
|
-
|
|
139
|
+
var f = Certificate.toProofInput(c)
|
|
140
|
+
if (record.algorithm !== f.algorithm) return false
|
|
141
|
+
if (record.hashAlgorithm !== f.hashAlgorithm) return false
|
|
142
|
+
if (!record.payloadHash.equals(f.payloadHash)) return false
|
|
143
|
+
if (!record.proofHash.equals(Certificate.decodeBytes(c.proofHash, 'hex', 'proofHash'))) return false
|
|
141
144
|
|
|
142
145
|
if (record.mode === NotaryScript.MODE.HYBRID) {
|
|
143
|
-
return record.publicKeyHash.equals(Hash.sha256(
|
|
144
|
-
record.signatureHash.equals(Hash.sha256(
|
|
146
|
+
return record.publicKeyHash.equals(Hash.sha256(f.publicKey)) &&
|
|
147
|
+
record.signatureHash.equals(Hash.sha256(f.signature))
|
|
145
148
|
}
|
|
146
|
-
|
|
147
|
-
return record.publicKey.equals(certPub) && record.signature.equals(certSig)
|
|
149
|
+
return record.publicKey.equals(f.publicKey) && record.signature.equals(f.signature)
|
|
148
150
|
} catch (e) {
|
|
149
151
|
return false
|
|
150
152
|
}
|
|
@@ -171,7 +173,8 @@ NotaryHash.verifyAnchorSPV = function (certificate, opts) {
|
|
|
171
173
|
opts = opts || {}
|
|
172
174
|
|
|
173
175
|
try {
|
|
174
|
-
var
|
|
176
|
+
var c = Certificate.normalize(certificate)
|
|
177
|
+
var spv = c && c.spv
|
|
175
178
|
if (!spv) {
|
|
176
179
|
return { valid: false, errors: ['certificate has no SPV envelope'] }
|
|
177
180
|
}
|
|
@@ -184,14 +187,14 @@ NotaryHash.verifyAnchorSPV = function (certificate, opts) {
|
|
|
184
187
|
}
|
|
185
188
|
|
|
186
189
|
var txid = NotaryHash.txidFromRawTx(spv.rawTx)
|
|
187
|
-
if (txid !== String(
|
|
190
|
+
if (!c.anchor || txid !== String(c.anchor.txid).toLowerCase()) {
|
|
188
191
|
errors.push('rawTx does not hash to anchor.txid')
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
var record = NotaryHash.recordFromRawTx(spv.rawTx)
|
|
192
195
|
if (!record) {
|
|
193
196
|
errors.push('no NotaryHash record found in rawTx')
|
|
194
|
-
} else if (!NotaryHash.recordMatchesCertificate(record,
|
|
197
|
+
} else if (!NotaryHash.recordMatchesCertificate(record, c)) {
|
|
195
198
|
errors.push('on-chain record does not match the certificate')
|
|
196
199
|
}
|
|
197
200
|
|
|
@@ -209,6 +212,15 @@ NotaryHash.verifyAnchorSPV = function (certificate, opts) {
|
|
|
209
212
|
: 'block header failed proof-of-work validation')
|
|
210
213
|
}
|
|
211
214
|
|
|
215
|
+
// The spec anchors the certificate in "the block header for spv.blockHash". A header
|
|
216
|
+
// the proof folds to, that is not that block, proves inclusion somewhere else — and the
|
|
217
|
+
// certificate's own statement of where would go unchecked. The reference compares
|
|
218
|
+
// the header's hash against the envelope; so does this.
|
|
219
|
+
if (spv.blockHash !== undefined &&
|
|
220
|
+
String(spv.blockHash).toLowerCase() !== String(inclusion.blockHash).toLowerCase()) {
|
|
221
|
+
errors.push('the supplied header is not the block the SPV envelope names')
|
|
222
|
+
}
|
|
223
|
+
|
|
212
224
|
return { valid: errors.length === 0, errors: errors }
|
|
213
225
|
} catch (e) {
|
|
214
226
|
return { valid: false, errors: ['anchor verification error: ' + e.message] }
|
|
@@ -223,54 +235,57 @@ NotaryHash.verifyAnchorSPV = function (certificate, opts) {
|
|
|
223
235
|
* NOT the Bitcoin tree in lib/spv — see lib/notaryhash/merkle.js for why the difference
|
|
224
236
|
* matters and why reusing the other one would be silently wrong.
|
|
225
237
|
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
238
|
+
* The leaf data is the certificate's proofHash. The spec does not state what a leaf
|
|
239
|
+
* contains; the reference implementation uses proofHash, and a batch it built verifies
|
|
240
|
+
* here leaf for leaf — see test/notaryhash/reference_certs.js.
|
|
241
|
+
*
|
|
242
|
+
* The path is folded by the sides it carries, as the reference folds it. `leafIndex` is
|
|
243
|
+
* not used to re-derive them: the reference does not, and a verifier that refused
|
|
244
|
+
* certificates the reference accepts would not be a BRC-220 verifier.
|
|
232
245
|
*
|
|
233
246
|
* @param {Object} certificate
|
|
234
247
|
* @returns {Object} { valid, errors }
|
|
235
248
|
*/
|
|
236
249
|
NotaryHash.verifyBatchInclusion = function (certificate) {
|
|
237
250
|
try {
|
|
238
|
-
|
|
239
|
-
|
|
251
|
+
var c = Certificate.normalize(certificate)
|
|
252
|
+
if (!c || !c.anchor || c.anchor.type !== Certificate.ANCHOR_TYPE.BATCH) {
|
|
253
|
+
return { valid: false, errors: ['certificate is not batch-anchored (anchor.type is not "batch")'] }
|
|
240
254
|
}
|
|
241
|
-
|
|
242
|
-
|
|
255
|
+
if (!c.merkle) return { valid: false, errors: ['batch certificate has no merkle proof'] }
|
|
256
|
+
return merkleFolds(c, 'merkle inclusion proof does not fold to the batch root')
|
|
257
|
+
} catch (e) {
|
|
258
|
+
return { valid: false, errors: ['batch inclusion error: ' + e.message] }
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Does the certificate's merkle proof fold its proofHash to the root it states?
|
|
264
|
+
*
|
|
265
|
+
* The same question for a batch anchor and for a proof carried on a direct one; only
|
|
266
|
+
* what a failure means differs, so the caller supplies the message.
|
|
267
|
+
*/
|
|
268
|
+
function merkleFolds (c, failure) {
|
|
269
|
+
try {
|
|
270
|
+
var m = c.merkle
|
|
243
271
|
|
|
244
272
|
// THE LEAF IS proofHash, NOT canonicalBytes.
|
|
245
273
|
//
|
|
246
|
-
// BRC-220
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
// batches with the exported NotaryHash.Merkle must use proofHash as the leaf datum.
|
|
260
|
-
//
|
|
261
|
-
// Reasoning and the proposed spec text: docs/BRC220_BATCH_LEAF_AMENDMENT.md.
|
|
262
|
-
// Enforced by test/notaryhash/batch_leaf.js, which checks that a canonicalBytes-leaf
|
|
263
|
-
// tree is rejected rather than trusting the comment.
|
|
264
|
-
var leafData = Buffer.from(certificate.proofHash, 'hex')
|
|
265
|
-
var path = (m.path || []).map(function (node) {
|
|
266
|
-
return Buffer.isBuffer(node) ? node : Buffer.from(node, 'hex')
|
|
267
|
-
})
|
|
268
|
-
var root = Buffer.isBuffer(m.root) ? m.root : Buffer.from(String(m.root), 'hex')
|
|
269
|
-
|
|
270
|
-
var included = Merkle.verifyInclusion(leafData, m.leafIndex, m.leafCount, path, root)
|
|
271
|
-
return included
|
|
274
|
+
// BRC-220 writes the batch tree as `leaf = SHA256(0x00 ‖ d)` without binding `d`, and
|
|
275
|
+
// the two readings — proofHash or canonicalBytes — are equally sound and produce
|
|
276
|
+
// different roots. proofHash is what the reference implementation's batcher uses
|
|
277
|
+
// (`leaves = batch.map(e => e.proofHash)`), and bsv-blockchain/BRCs#246 proposes
|
|
278
|
+
// stating it in the spec. test/notaryhash/batch_leaf.js checks that a
|
|
279
|
+
// canonicalBytes-leaf tree is rejected rather than trusting this comment.
|
|
280
|
+
var leafData = Certificate.decodeBytes(c.proofHash, 'hex', 'proofHash')
|
|
281
|
+
var root = Certificate.decodeBytes(m.root, 'hex', 'merkle.root')
|
|
282
|
+
|
|
283
|
+
// Folded by side, as the reference folds it. The sides are what the certificate
|
|
284
|
+
// carries; a legacy bare-hash path had them derived from leafIndex and leafCount when
|
|
285
|
+
// it was normalised.
|
|
286
|
+
return Merkle.verifyAuditPath(leafData, m.path, root)
|
|
272
287
|
? { valid: true, errors: [] }
|
|
273
|
-
: { valid: false, errors: [
|
|
288
|
+
: { valid: false, errors: [failure] }
|
|
274
289
|
} catch (e) {
|
|
275
290
|
return { valid: false, errors: ['batch inclusion error: ' + e.message] }
|
|
276
291
|
}
|
|
@@ -290,7 +305,8 @@ NotaryHash.verifyBatchInclusion = function (certificate) {
|
|
|
290
305
|
* @param {String|Buffer} [opts.header] - an independently obtained block header
|
|
291
306
|
* @param {Boolean} [opts.skipAnchor] - check 1 and 2 only; the result is NOT a valid
|
|
292
307
|
* certificate, and `valid` will be false. For offline triage.
|
|
293
|
-
* @returns {Object} { valid, signature, proofIntegrity, anchor, shape,
|
|
308
|
+
* @returns {Object} { valid, signature, proofIntegrity, anchor, batchInclusion, shape,
|
|
309
|
+
* legacy, errors } — `legacy` is true when the certificate was written by 8.3.0–9.8.0
|
|
294
310
|
*/
|
|
295
311
|
NotaryHash.verify = function (certificate, opts) {
|
|
296
312
|
opts = opts || {}
|
|
@@ -304,6 +320,11 @@ NotaryHash.verify = function (certificate, opts) {
|
|
|
304
320
|
errors: []
|
|
305
321
|
}
|
|
306
322
|
|
|
323
|
+
// Legacy certificates are translated once, here, and the translation is reported so a
|
|
324
|
+
// caller holding one knows to re-issue it in the current format.
|
|
325
|
+
report.legacy = Certificate.isLegacy(certificate)
|
|
326
|
+
certificate = Certificate.normalize(certificate)
|
|
327
|
+
|
|
307
328
|
report.shape = Certificate.validateShape(certificate)
|
|
308
329
|
if (report.shape.length) {
|
|
309
330
|
report.errors = report.shape.slice()
|
|
@@ -328,9 +349,17 @@ NotaryHash.verify = function (certificate, opts) {
|
|
|
328
349
|
// A batched certificate has a fourth thing to prove: that this proof is actually one
|
|
329
350
|
// of the ones the on-chain root commits to. Without it, any certificate could point at
|
|
330
351
|
// any batch anchor and the anchor check alone would not notice.
|
|
352
|
+
//
|
|
353
|
+
// A merkle proof is checked whenever one is present, not only on a batch anchor, as the
|
|
354
|
+
// reference checks it: a certificate whose path does not fold to its stated root fails
|
|
355
|
+
// even when nothing else relies on it. On a direct anchor that is the whole of it — the
|
|
356
|
+
// record is compared directly, so a proof that folds adds nothing and is accepted.
|
|
331
357
|
report.batchInclusion = true
|
|
332
|
-
|
|
333
|
-
|
|
358
|
+
var batchAnchored = !!(certificate.anchor && certificate.anchor.type === Certificate.ANCHOR_TYPE.BATCH)
|
|
359
|
+
if (batchAnchored || certificate.merkle !== undefined) {
|
|
360
|
+
var batch = batchAnchored
|
|
361
|
+
? NotaryHash.verifyBatchInclusion(certificate)
|
|
362
|
+
: merkleFolds(certificate, 'merkle proof does not fold to its stated root')
|
|
334
363
|
report.batchInclusion = batch.valid
|
|
335
364
|
batch.errors.forEach(function (e) { report.errors.push(e) })
|
|
336
365
|
}
|
package/lib/notaryhash/merkle.js
CHANGED
|
@@ -214,4 +214,98 @@ Merkle.verifyInclusion = function (leafData, index, leafCount, path, expectedRoo
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Which side of the running hash each audit-path sibling sits on, leaf upward.
|
|
219
|
+
*
|
|
220
|
+
* Derived from the index and the tree size exactly as RFC 6962 derives the tree's shape,
|
|
221
|
+
* so it lines up entry for entry with Merkle.path(). Used to give the bare hashes that
|
|
222
|
+
* 8.3.0–9.8.0 wrote the sides the reference format carries.
|
|
223
|
+
*
|
|
224
|
+
* @param {Number} index
|
|
225
|
+
* @param {Number} leafCount
|
|
226
|
+
* @returns {Array<String>} 'left' | 'right' per sibling
|
|
227
|
+
*/
|
|
228
|
+
Merkle.pathSides = function (index, leafCount) {
|
|
229
|
+
$.checkArgument(Number.isInteger(leafCount) && leafCount > 0, 'leafCount must be a positive integer')
|
|
230
|
+
$.checkArgument(Number.isInteger(index) && index >= 0 && index < leafCount,
|
|
231
|
+
'index must be within the tree')
|
|
232
|
+
if (leafCount === 1) return []
|
|
233
|
+
var k = Merkle.largestPowerOfTwoBelow(leafCount)
|
|
234
|
+
return index < k
|
|
235
|
+
? Merkle.pathSides(index, k).concat(['right'])
|
|
236
|
+
: Merkle.pathSides(index - k, leafCount - k).concat(['left'])
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The audit path in the form BRC-220 certificates carry it: `{ hash, side }` per sibling,
|
|
241
|
+
* leaf upward, where `side` is the sibling's position relative to the running hash.
|
|
242
|
+
*
|
|
243
|
+
* This is the reference implementation's shape, and folding it needs no index or tree
|
|
244
|
+
* size — see rootFromPath(). Merkle.path() returns the same hashes without sides.
|
|
245
|
+
*
|
|
246
|
+
* @param {Array<Buffer>} leaves - leaf DATA (for BRC-220, each proof's proofHash)
|
|
247
|
+
* @param {Number} index
|
|
248
|
+
* @returns {Array<{hash: Buffer, side: String}>}
|
|
249
|
+
*/
|
|
250
|
+
Merkle.auditPath = function (leaves, index) {
|
|
251
|
+
var sides = Merkle.pathSides(index, leaves.length)
|
|
252
|
+
return Merkle.path(leaves, index).map(function (hash, i) {
|
|
253
|
+
return { hash: hash, side: sides[i] }
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function nodeHash (value, i) {
|
|
258
|
+
var buf = Buffer.isBuffer(value) ? value : null
|
|
259
|
+
if (!buf) {
|
|
260
|
+
if (typeof value !== 'string') throw new Error('audit path node ' + i + ' has no hash')
|
|
261
|
+
var clean = value.slice(0, 2) === '0x' ? value.slice(2) : value
|
|
262
|
+
if (!/^[0-9a-fA-F]*$/.test(clean)) throw new Error('audit path node ' + i + ' is not hex')
|
|
263
|
+
buf = Buffer.from(clean, 'hex')
|
|
264
|
+
}
|
|
265
|
+
if (buf.length !== 32) throw new Error('audit path node ' + i + ' is not 32 bytes')
|
|
266
|
+
return buf
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Fold a `{ hash, side }` audit path back to a root, as the reference implementation
|
|
271
|
+
* does: a 'left' sibling is hashed before the running value, a 'right' one after.
|
|
272
|
+
*
|
|
273
|
+
* @param {Buffer} leafData - the leaf DATA, hashed here
|
|
274
|
+
* @param {Array<{hash: Buffer|String, side: String}>} path
|
|
275
|
+
* @returns {Buffer} the computed root
|
|
276
|
+
*/
|
|
277
|
+
Merkle.rootFromPath = function (leafData, path) {
|
|
278
|
+
$.checkArgument(Buffer.isBuffer(leafData), 'leafData must be a Buffer')
|
|
279
|
+
$.checkArgument(Array.isArray(path), 'path must be an array')
|
|
280
|
+
var acc = Merkle.hashLeaf(leafData)
|
|
281
|
+
for (var i = 0; i < path.length; i++) {
|
|
282
|
+
var node = path[i]
|
|
283
|
+
if (!node || typeof node !== 'object') throw new Error('audit path node ' + i + ' is not an object')
|
|
284
|
+
var hash = nodeHash(node.hash, i)
|
|
285
|
+
if (node.side === 'left') acc = Merkle.hashNode(hash, acc)
|
|
286
|
+
else if (node.side === 'right') acc = Merkle.hashNode(acc, hash)
|
|
287
|
+
else throw new Error('audit path node ' + i + ' has side ' + JSON.stringify(node.side))
|
|
288
|
+
}
|
|
289
|
+
return acc
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Does a `{ hash, side }` audit path fold this leaf to the expected root?
|
|
294
|
+
*
|
|
295
|
+
* Strict boolean, false on any malformed input rather than throwing.
|
|
296
|
+
*
|
|
297
|
+
* @param {Buffer} leafData
|
|
298
|
+
* @param {Array<{hash: Buffer|String, side: String}>} path
|
|
299
|
+
* @param {Buffer} expectedRoot
|
|
300
|
+
* @returns {Boolean}
|
|
301
|
+
*/
|
|
302
|
+
Merkle.verifyAuditPath = function (leafData, path, expectedRoot) {
|
|
303
|
+
try {
|
|
304
|
+
if (!Buffer.isBuffer(expectedRoot)) return false
|
|
305
|
+
return Merkle.rootFromPath(leafData, path).equals(expectedRoot)
|
|
306
|
+
} catch (e) {
|
|
307
|
+
return false
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
217
311
|
module.exports = Merkle
|
package/lib/notaryhash/script.js
CHANGED
|
@@ -35,6 +35,10 @@ NotaryScript.MODE = {
|
|
|
35
35
|
BATCH: 2
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// The certificate's spelling of the same modes, so a record can be built straight from a
|
|
39
|
+
// certificate's `mode` field. The on-chain byte is the number either way.
|
|
40
|
+
var MODE_BY_NAME = { full: NotaryScript.MODE.FULL, hybrid: NotaryScript.MODE.HYBRID, batch: NotaryScript.MODE.BATCH }
|
|
41
|
+
|
|
38
42
|
function u8 (n) { return Buffer.from([n]) }
|
|
39
43
|
|
|
40
44
|
function u32be (n) {
|
|
@@ -103,7 +107,10 @@ function readBuf (chunk, name) {
|
|
|
103
107
|
NotaryScript.build = function (record) {
|
|
104
108
|
$.checkArgument(record && typeof record === 'object', 'record is required')
|
|
105
109
|
|
|
106
|
-
var mode = record.mode
|
|
110
|
+
var mode = typeof record.mode === 'string' &&
|
|
111
|
+
Object.prototype.hasOwnProperty.call(MODE_BY_NAME, record.mode)
|
|
112
|
+
? MODE_BY_NAME[record.mode]
|
|
113
|
+
: record.mode
|
|
107
114
|
$.checkArgument(mode === NotaryScript.MODE.FULL ||
|
|
108
115
|
mode === NotaryScript.MODE.HYBRID ||
|
|
109
116
|
mode === NotaryScript.MODE.BATCH,
|
package/lib/notaryhash/suites.js
CHANGED
|
@@ -4,7 +4,6 @@ var BN = require('../crypto/bn')
|
|
|
4
4
|
var ECDSA = require('../crypto/ecdsa')
|
|
5
5
|
var Signature = require('../crypto/signature')
|
|
6
6
|
var PublicKey = require('../publickey')
|
|
7
|
-
var Point = require('../crypto/point')
|
|
8
7
|
var $ = require('../util/preconditions')
|
|
9
8
|
|
|
10
9
|
/**
|
|
@@ -105,14 +104,21 @@ Suites.verify = function (algorithm, payloadHash, signature, publicKey) {
|
|
|
105
104
|
* schemes apply their own internal hashing"). There is no second hash, and no Bitcoin
|
|
106
105
|
* sighash — this is a detached signature over a digest.
|
|
107
106
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* certificate's `encoding` field may say so; see docs/BRC220_ENCODING_AMENDMENT.md for
|
|
111
|
-
* why raw is what new implementations should emit.
|
|
107
|
+
* What it accepts is what the reference implementation accepts, because a verifier that
|
|
108
|
+
* refuses certificates the reference issued is not a BRC-220 verifier:
|
|
112
109
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
110
|
+
* - a signature of 64 bytes, `r || s`, each a 32-byte big-endian integer, OR DER,
|
|
111
|
+
* told apart by the bytes themselves (DER opens with 0x30 and is not 64 bytes). The
|
|
112
|
+
* certificate's `encoding` field says how the bytes are WRITTEN — hex or base64 — and
|
|
113
|
+
* nothing about which of these they are;
|
|
114
|
+
* - a 33-byte compressed or 65-byte uncompressed public key;
|
|
115
|
+
* - a high-S signature.
|
|
116
|
+
*
|
|
117
|
+
* That last one reverses what 8.3.0–9.8.0 did, which was to reject high-S. The reference
|
|
118
|
+
* accepts it deliberately — "we attest to whatever valid signature the signer produced"
|
|
119
|
+
* — and the reasoning holds: proofHash covers the exact signature bytes, so the malleated
|
|
120
|
+
* form of a signature yields a DIFFERENT certificate, not a forgery of this one. Nothing
|
|
121
|
+
* that verifies under the low-S form stops verifying.
|
|
116
122
|
*/
|
|
117
123
|
Suites.register('ECDSA-secp256k1', {
|
|
118
124
|
verify: function (payloadHash, signature, publicKey) {
|
|
@@ -124,19 +130,16 @@ Suites.register('ECDSA-secp256k1', {
|
|
|
124
130
|
BN.fromBuffer(signature.slice(0, 32)),
|
|
125
131
|
BN.fromBuffer(signature.slice(32, 64))
|
|
126
132
|
)
|
|
127
|
-
} else {
|
|
128
|
-
// DER, for the legacy `encoding: "der"` case.
|
|
133
|
+
} else if (signature[0] === 0x30) {
|
|
129
134
|
try {
|
|
130
135
|
sig = Signature.fromDER(signature)
|
|
131
136
|
} catch (e) {
|
|
132
137
|
return false
|
|
133
138
|
}
|
|
139
|
+
} else {
|
|
140
|
+
return false
|
|
134
141
|
}
|
|
135
142
|
|
|
136
|
-
// Low-S, enforced rather than normalised.
|
|
137
|
-
var halfOrder = Point.getN().div(new BN(2))
|
|
138
|
-
if (sig.s.gt(halfOrder)) return false
|
|
139
|
-
|
|
140
143
|
var pubkey
|
|
141
144
|
try {
|
|
142
145
|
pubkey = PublicKey.fromBuffer(publicKey)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartledger/bsv",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.9.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",
|
|
@@ -69,7 +69,7 @@ function derive (label, i) {
|
|
|
69
69
|
return Hash.sha256(Buffer.from('BRC-220/batch-vector/' + label + '/' + i, 'utf8'))
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
//
|
|
72
|
+
// 64-byte r‖s, low-S — the form the reference implementation signs with. Not DER.
|
|
73
73
|
function signRaw (payloadHash, privkey) {
|
|
74
74
|
// set() rather than assigning fields directly: it derives `pubkey` from `privkey`,
|
|
75
75
|
// which sign() requires. RFC 6979 makes this deterministic, so the vector reproduces.
|
|
@@ -137,7 +137,8 @@ for (var i = 0; i < LEAF_COUNT; i++) {
|
|
|
137
137
|
payloadPreimage: 'BRC-220/batch-vector/payload/' + i,
|
|
138
138
|
algorithm: ALGORITHM,
|
|
139
139
|
hashAlgorithm: HASH_ALGORITHM,
|
|
140
|
-
|
|
140
|
+
// How publicKey and signature are written, as in a certificate. Not a byte format.
|
|
141
|
+
encoding: 'hex',
|
|
141
142
|
payloadHash: payloadHash.toString('hex'),
|
|
142
143
|
publicKey: publicKey.toString('hex'),
|
|
143
144
|
signature: signature.toString('hex'),
|
|
@@ -156,7 +157,10 @@ var leaves = proofs.map(function (p) { return Buffer.from(p.proofHash, 'hex') })
|
|
|
156
157
|
var root = Merkle.root(leaves)
|
|
157
158
|
|
|
158
159
|
proofs.forEach(function (p, idx) {
|
|
159
|
-
|
|
160
|
+
// { hash, side } per sibling, leaf upward — the form certificates carry.
|
|
161
|
+
p.path = Merkle.auditPath(leaves, idx).map(function (n) {
|
|
162
|
+
return { hash: n.hash.toString('hex'), side: n.side }
|
|
163
|
+
})
|
|
160
164
|
})
|
|
161
165
|
|
|
162
166
|
var vector = {
|
package/version.js
CHANGED