@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.
@@ -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
- return Suites.verify(
56
- certificate.algorithm,
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
- if (record.mode !== certificate.mode) return false
119
-
120
- if (record.mode === NotaryScript.MODE.BATCH) {
121
- if (!certificate.merkle) return false
122
- if (record.merkleRoot.toString('hex') !== String(certificate.merkle.root).toLowerCase()) {
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 CANNOT be relied on to catch a
126
- // wrong one. RFC 6962 derives the tree shape from the count, but for most indices
127
- // the fold is identical across neighbouring counts — measured on an 8-leaf tree,
128
- // only indices 6 and 7 fold differently when the count is claimed as 7. The
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
- if (record.algorithm !== certificate.algorithm) return false
135
- if (record.hashAlgorithm !== certificate.hashAlgorithm) return false
136
- if (record.payloadHash.toString('hex') !== String(certificate.payloadHash).toLowerCase()) return false
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 certPub = Buffer.from(certificate.publicKey, 'hex')
140
- var certSig = Buffer.from(certificate.signature, 'hex')
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(certPub)) &&
144
- record.signatureHash.equals(Hash.sha256(certSig))
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 spv = certificate && certificate.spv
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(certificate.anchor.txid).toLowerCase()) {
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, certificate)) {
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
- * ASSUMPTION, flagged rather than buried: the leaf data is the certificate's proofHash.
227
- * The spec says a batch "anchors many proofs" under one root and that the certificate's
228
- * merkle proof must fold to it, but does not state what a leaf contains. proofHash is the
229
- * only per-proof 32-byte value that identifies the proof, so it is the reading taken
230
- * here — and it needs confirming against the reference implementation before this is
231
- * relied on. See docs/BRC220_PLAN.md.
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
- if (!certificate || certificate.mode !== NotaryScript.MODE.BATCH) {
239
- return { valid: false, errors: ['certificate is not in batch mode'] }
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
- var m = certificate.merkle
242
- if (!m) return { valid: false, errors: ['batch certificate has no merkle proof'] }
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 §On-chain record says the batch tree is RFC 6962 and writes
247
- // `leaf = SHA256(0x00 ‖ d)` — but that is RFC 6962's own generic notation for the
248
- // construction, and the spec never binds `d` to a value. `canonicalBytes` appears
249
- // once in the whole document, in the proofHash definition, and nowhere in the batch
250
- // text.
251
- //
252
- // So this is a choice the implementation had to make, and it is:
253
- //
254
- // leaf = SHA-256(0x00 ‖ proofHash) = SHA-256(0x00 ‖ SHA-256(canonicalBytes))
255
- //
256
- // The other reading — d = canonicalBytes — is equally sound cryptographically and
257
- // produces a DIFFERENT root. A certificate built that way fails inclusion here with
258
- // no error naming the cause: the fold simply does not reach the root. Anyone building
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: ['merkle inclusion proof does not fold to the batch root'] }
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, errors }
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
- if (certificate.mode === NotaryScript.MODE.BATCH) {
333
- var batch = NotaryHash.verifyBatchInclusion(certificate)
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
  }
@@ -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
@@ -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,
@@ -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
- * Signatures are 64 raw bytes, `r || s`, each a 32-byte big-endian integer. DER is
109
- * accepted as a length-discriminated fallback for Bitcoin-native signers, because the
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
- * Low-S is REQUIRED. A high-S signature is rejected rather than normalised: normalising
114
- * changes the signature bytes, and those bytes are inside `proofHash`, so accepting both
115
- * forms would mean two valid certificates exist for one signing act.
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.8.0",
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
- // Raw 64-byte r‖s, low-S, as `encoding: "raw"` requires. Not DER.
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
- encoding: 'raw',
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
- p.path = Merkle.path(leaves, idx).map(function (n) { return n.toString('hex') })
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
@@ -1,3 +1,3 @@
1
1
  'use strict'
2
2
  // GENERATED by scripts/sync-version.js on `npm version` — do not edit.
3
- module.exports = '9.8.0'
3
+ module.exports = '9.9.0'