@smartledger/bsv 9.8.0 → 9.10.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 +129 -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 +345 -0
- package/bsv.min.js +59 -59
- package/docs/AUDIT_SCOPE.md +6 -6
- package/docs/BRC220_BATCH_LEAF_AMENDMENT.md +23 -8
- package/docs/BRC220_CERTIFICATE_FIELDS_AMENDMENT.md +246 -0
- package/docs/BRC220_ENCODING_AMENDMENT.md +40 -100
- 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 +528 -79
- package/lib/notaryhash/index.js +110 -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
|
}
|
|
@@ -163,6 +165,8 @@ NotaryHash.recordMatchesCertificate = function (record, certificate) {
|
|
|
163
165
|
* @param {Object} certificate - must carry an `spv` envelope
|
|
164
166
|
* @param {Object} opts
|
|
165
167
|
* @param {String|Buffer|BlockHeader} opts.header - independently obtained
|
|
168
|
+
* @param {Number} [opts.height] - the height the header was obtained at. A header's 80
|
|
169
|
+
* bytes do not carry it, so it is checked against spv.blockHeight only when supplied.
|
|
166
170
|
* @param {Boolean} [opts.requirePow=true] - pass false only for test fixtures
|
|
167
171
|
* @returns {Object} { valid, errors }
|
|
168
172
|
*/
|
|
@@ -171,7 +175,8 @@ NotaryHash.verifyAnchorSPV = function (certificate, opts) {
|
|
|
171
175
|
opts = opts || {}
|
|
172
176
|
|
|
173
177
|
try {
|
|
174
|
-
var
|
|
178
|
+
var c = Certificate.normalize(certificate)
|
|
179
|
+
var spv = c && c.spv
|
|
175
180
|
if (!spv) {
|
|
176
181
|
return { valid: false, errors: ['certificate has no SPV envelope'] }
|
|
177
182
|
}
|
|
@@ -184,14 +189,14 @@ NotaryHash.verifyAnchorSPV = function (certificate, opts) {
|
|
|
184
189
|
}
|
|
185
190
|
|
|
186
191
|
var txid = NotaryHash.txidFromRawTx(spv.rawTx)
|
|
187
|
-
if (txid !== String(
|
|
192
|
+
if (!c.anchor || txid !== String(c.anchor.txid).toLowerCase()) {
|
|
188
193
|
errors.push('rawTx does not hash to anchor.txid')
|
|
189
194
|
}
|
|
190
195
|
|
|
191
196
|
var record = NotaryHash.recordFromRawTx(spv.rawTx)
|
|
192
197
|
if (!record) {
|
|
193
198
|
errors.push('no NotaryHash record found in rawTx')
|
|
194
|
-
} else if (!NotaryHash.recordMatchesCertificate(record,
|
|
199
|
+
} else if (!NotaryHash.recordMatchesCertificate(record, c)) {
|
|
195
200
|
errors.push('on-chain record does not match the certificate')
|
|
196
201
|
}
|
|
197
202
|
|
|
@@ -209,6 +214,22 @@ NotaryHash.verifyAnchorSPV = function (certificate, opts) {
|
|
|
209
214
|
: 'block header failed proof-of-work validation')
|
|
210
215
|
}
|
|
211
216
|
|
|
217
|
+
// The spec anchors the certificate in "the block header for spv.blockHash". A header
|
|
218
|
+
// the proof folds to, that is not that block, proves inclusion somewhere else — and the
|
|
219
|
+
// certificate's own statement of where would go unchecked. The reference compares
|
|
220
|
+
// the header's hash against the envelope; so does this.
|
|
221
|
+
if (spv.blockHash !== undefined &&
|
|
222
|
+
String(spv.blockHash).toLowerCase() !== String(inclusion.blockHash).toLowerCase()) {
|
|
223
|
+
errors.push('the supplied header is not the block the SPV envelope names')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// And "at spv.blockHeight". A header does not say what height it is at — that comes
|
|
227
|
+
// from wherever the caller obtained it — so a caller that knows passes it. It cannot be
|
|
228
|
+
// required without breaking every caller that holds only the 80 bytes.
|
|
229
|
+
if (opts.height !== undefined && opts.height !== spv.blockHeight) {
|
|
230
|
+
errors.push('the supplied header is not at the height the SPV envelope names')
|
|
231
|
+
}
|
|
232
|
+
|
|
212
233
|
return { valid: errors.length === 0, errors: errors }
|
|
213
234
|
} catch (e) {
|
|
214
235
|
return { valid: false, errors: ['anchor verification error: ' + e.message] }
|
|
@@ -223,54 +244,57 @@ NotaryHash.verifyAnchorSPV = function (certificate, opts) {
|
|
|
223
244
|
* NOT the Bitcoin tree in lib/spv — see lib/notaryhash/merkle.js for why the difference
|
|
224
245
|
* matters and why reusing the other one would be silently wrong.
|
|
225
246
|
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
247
|
+
* The leaf data is the certificate's proofHash. The spec does not state what a leaf
|
|
248
|
+
* contains; the reference implementation uses proofHash, and a batch it built verifies
|
|
249
|
+
* here leaf for leaf — see test/notaryhash/reference_certs.js.
|
|
250
|
+
*
|
|
251
|
+
* The path is folded by the sides it carries, as the reference folds it. `leafIndex` is
|
|
252
|
+
* not used to re-derive them: the reference does not, and a verifier that refused
|
|
253
|
+
* certificates the reference accepts would not be a BRC-220 verifier.
|
|
232
254
|
*
|
|
233
255
|
* @param {Object} certificate
|
|
234
256
|
* @returns {Object} { valid, errors }
|
|
235
257
|
*/
|
|
236
258
|
NotaryHash.verifyBatchInclusion = function (certificate) {
|
|
237
259
|
try {
|
|
238
|
-
|
|
239
|
-
|
|
260
|
+
var c = Certificate.normalize(certificate)
|
|
261
|
+
if (!c || !c.anchor || c.anchor.type !== Certificate.ANCHOR_TYPE.BATCH) {
|
|
262
|
+
return { valid: false, errors: ['certificate is not batch-anchored (anchor.type is not "batch")'] }
|
|
240
263
|
}
|
|
241
|
-
|
|
242
|
-
|
|
264
|
+
if (!c.merkle) return { valid: false, errors: ['batch certificate has no merkle proof'] }
|
|
265
|
+
return merkleFolds(c, 'merkle inclusion proof does not fold to the batch root')
|
|
266
|
+
} catch (e) {
|
|
267
|
+
return { valid: false, errors: ['batch inclusion error: ' + e.message] }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Does the certificate's merkle proof fold its proofHash to the root it states?
|
|
273
|
+
*
|
|
274
|
+
* The same question for a batch anchor and for a proof carried on a direct one; only
|
|
275
|
+
* what a failure means differs, so the caller supplies the message.
|
|
276
|
+
*/
|
|
277
|
+
function merkleFolds (c, failure) {
|
|
278
|
+
try {
|
|
279
|
+
var m = c.merkle
|
|
243
280
|
|
|
244
281
|
// THE LEAF IS proofHash, NOT canonicalBytes.
|
|
245
282
|
//
|
|
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
|
|
283
|
+
// BRC-220 writes the batch tree as `leaf = SHA256(0x00 ‖ d)` without binding `d`, and
|
|
284
|
+
// the two readings — proofHash or canonicalBytes — are equally sound and produce
|
|
285
|
+
// different roots. proofHash is what the reference implementation's batcher uses
|
|
286
|
+
// (`leaves = batch.map(e => e.proofHash)`), and bsv-blockchain/BRCs#246 proposes
|
|
287
|
+
// stating it in the spec. test/notaryhash/batch_leaf.js checks that a
|
|
288
|
+
// canonicalBytes-leaf tree is rejected rather than trusting this comment.
|
|
289
|
+
var leafData = Certificate.decodeBytes(c.proofHash, 'hex', 'proofHash')
|
|
290
|
+
var root = Certificate.decodeBytes(m.root, 'hex', 'merkle.root')
|
|
291
|
+
|
|
292
|
+
// Folded by side, as the reference folds it. The sides are what the certificate
|
|
293
|
+
// carries; a legacy bare-hash path had them derived from leafIndex and leafCount when
|
|
294
|
+
// it was normalised.
|
|
295
|
+
return Merkle.verifyAuditPath(leafData, m.path, root)
|
|
272
296
|
? { valid: true, errors: [] }
|
|
273
|
-
: { valid: false, errors: [
|
|
297
|
+
: { valid: false, errors: [failure] }
|
|
274
298
|
} catch (e) {
|
|
275
299
|
return { valid: false, errors: ['batch inclusion error: ' + e.message] }
|
|
276
300
|
}
|
|
@@ -288,9 +312,11 @@ NotaryHash.verifyBatchInclusion = function (certificate) {
|
|
|
288
312
|
* @param {Object} certificate
|
|
289
313
|
* @param {Object} [opts]
|
|
290
314
|
* @param {String|Buffer} [opts.header] - an independently obtained block header
|
|
315
|
+
* @param {Number} [opts.height] - the height it was obtained at; see verifyAnchorSPV
|
|
291
316
|
* @param {Boolean} [opts.skipAnchor] - check 1 and 2 only; the result is NOT a valid
|
|
292
317
|
* certificate, and `valid` will be false. For offline triage.
|
|
293
|
-
* @returns {Object} { valid, signature, proofIntegrity, anchor, shape,
|
|
318
|
+
* @returns {Object} { valid, signature, proofIntegrity, anchor, batchInclusion, shape,
|
|
319
|
+
* legacy, errors } — `legacy` is true when the certificate was written by 8.3.0–9.8.0
|
|
294
320
|
*/
|
|
295
321
|
NotaryHash.verify = function (certificate, opts) {
|
|
296
322
|
opts = opts || {}
|
|
@@ -304,6 +330,11 @@ NotaryHash.verify = function (certificate, opts) {
|
|
|
304
330
|
errors: []
|
|
305
331
|
}
|
|
306
332
|
|
|
333
|
+
// Legacy certificates are translated once, here, and the translation is reported so a
|
|
334
|
+
// caller holding one knows to re-issue it in the current format.
|
|
335
|
+
report.legacy = Certificate.isLegacy(certificate)
|
|
336
|
+
certificate = Certificate.normalize(certificate)
|
|
337
|
+
|
|
307
338
|
report.shape = Certificate.validateShape(certificate)
|
|
308
339
|
if (report.shape.length) {
|
|
309
340
|
report.errors = report.shape.slice()
|
|
@@ -328,9 +359,17 @@ NotaryHash.verify = function (certificate, opts) {
|
|
|
328
359
|
// A batched certificate has a fourth thing to prove: that this proof is actually one
|
|
329
360
|
// of the ones the on-chain root commits to. Without it, any certificate could point at
|
|
330
361
|
// any batch anchor and the anchor check alone would not notice.
|
|
362
|
+
//
|
|
363
|
+
// A merkle proof is checked whenever one is present, not only on a batch anchor, as the
|
|
364
|
+
// reference checks it: a certificate whose path does not fold to its stated root fails
|
|
365
|
+
// even when nothing else relies on it. On a direct anchor that is the whole of it — the
|
|
366
|
+
// record is compared directly, so a proof that folds adds nothing and is accepted.
|
|
331
367
|
report.batchInclusion = true
|
|
332
|
-
|
|
333
|
-
|
|
368
|
+
var batchAnchored = !!(certificate.anchor && certificate.anchor.type === Certificate.ANCHOR_TYPE.BATCH)
|
|
369
|
+
if (batchAnchored || certificate.merkle !== undefined) {
|
|
370
|
+
var batch = batchAnchored
|
|
371
|
+
? NotaryHash.verifyBatchInclusion(certificate)
|
|
372
|
+
: merkleFolds(certificate, 'merkle proof does not fold to its stated root')
|
|
334
373
|
report.batchInclusion = batch.valid
|
|
335
374
|
batch.errors.forEach(function (e) { report.errors.push(e) })
|
|
336
375
|
}
|
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.10.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