@smartledger/bsv 7.12.0 → 7.13.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.
@@ -58,6 +58,44 @@ async function generateIssuerKeys(opts) {
58
58
  }
59
59
  }
60
60
 
61
+ // The five verification relationships DID Core defines. They exist to be DISTINCT:
62
+ // `authentication` proves who you are right now, `assertionMethod` makes a claim that
63
+ // outlives the session. Keeping them apart is what stops a low-friction login signature
64
+ // from being redeemed as a signed statement.
65
+ //
66
+ // This module used to cross-list every key into both, which erased the distinction and
67
+ // left a caller unable to express "this key may log in but may not make assertions".
68
+ var VERIFICATION_RELATIONSHIPS = [
69
+ 'authentication',
70
+ 'assertionMethod',
71
+ 'keyAgreement',
72
+ 'capabilityInvocation',
73
+ 'capabilityDelegation'
74
+ ]
75
+
76
+ // Preserves the historical output when a key does not say otherwise.
77
+ var DEFAULT_RELATIONSHIPS = ['authentication', 'assertionMethod']
78
+
79
+ /**
80
+ * Resolve the verification relationships for a key descriptor.
81
+ *
82
+ * @param {object} key - `{ kid, jwk, relationships? }`
83
+ * @returns {Array<string>}
84
+ */
85
+ function relationshipsFor (key) {
86
+ var rels = key.relationships || DEFAULT_RELATIONSHIPS
87
+ if (!Array.isArray(rels) || rels.length === 0) {
88
+ throw new Error('relationships must be a non-empty array of DID Core verification relationships')
89
+ }
90
+ rels.forEach(function (rel) {
91
+ if (VERIFICATION_RELATIONSHIPS.indexOf(rel) === -1) {
92
+ throw new Error('unknown verification relationship: ' + rel +
93
+ ' (expected one of ' + VERIFICATION_RELATIONSHIPS.join(', ') + ')')
94
+ }
95
+ })
96
+ return rels
97
+ }
98
+
61
99
  // Build did:web documents (did.json and jwks.json)
62
100
  function buildDidWebDocuments(params) {
63
101
  if (!params.domain) {
@@ -68,6 +106,7 @@ function buildDidWebDocuments(params) {
68
106
  var did = 'did:web:' + domain.replace(/:/g, '%3A')
69
107
 
70
108
  var verificationMethods = []
109
+ var methodRelationships = []
71
110
  var publicKeys = []
72
111
 
73
112
  // Add P-256 key if provided
@@ -79,6 +118,7 @@ function buildDidWebDocuments(params) {
79
118
  publicKeyJwk: params.p256.jwk
80
119
  }
81
120
  verificationMethods.push(p256Method)
121
+ methodRelationships.push(relationshipsFor(params.p256))
82
122
  publicKeys.push(params.p256.jwk)
83
123
  }
84
124
 
@@ -91,6 +131,7 @@ function buildDidWebDocuments(params) {
91
131
  publicKeyJwk: params.k1.jwk
92
132
  }
93
133
  verificationMethods.push(k1Method)
134
+ methodRelationships.push(relationshipsFor(params.k1))
94
135
  publicKeys.push(params.k1.jwk)
95
136
  }
96
137
 
@@ -105,11 +146,19 @@ function buildDidWebDocuments(params) {
105
146
  'https://w3id.org/security/suites/jws-2020/v1'
106
147
  ],
107
148
  id: did,
108
- verificationMethod: verificationMethods,
109
- authentication: verificationMethods.map(function(vm) { return vm.id }),
110
- assertionMethod: verificationMethods.map(function(vm) { return vm.id })
149
+ verificationMethod: verificationMethods
111
150
  }
112
151
 
152
+ // Scope each key to the relationships it was given. Omitting `relationships`
153
+ // reproduces the previous output exactly (every key in both authentication and
154
+ // assertionMethod), so existing callers are unaffected.
155
+ VERIFICATION_RELATIONSHIPS.forEach(function (rel) {
156
+ var ids = verificationMethods
157
+ .filter(function (vm, i) { return methodRelationships[i].indexOf(rel) !== -1 })
158
+ .map(function (vm) { return vm.id })
159
+ if (ids.length) didDocument[rel] = ids
160
+ })
161
+
113
162
  if (params.controllerName) {
114
163
  didDocument.controller = params.controllerName
115
164
  }
@@ -144,6 +193,11 @@ function rotateIssuerKey(params) {
144
193
  publicKeyJwk: params.newKey.jwk
145
194
  }
146
195
 
196
+ // Which relationships this rotation actually replaces. Defaults to both, which is
197
+ // the historical behaviour.
198
+ var rotating = relationshipsFor(params.newKey)
199
+ var current = params.currentDocument || null
200
+
147
201
  // Build updated DID Document with new key as primary
148
202
  var didDocument = {
149
203
  '@context': [
@@ -151,13 +205,38 @@ function rotateIssuerKey(params) {
151
205
  'https://w3id.org/security/suites/jws-2020/v1'
152
206
  ],
153
207
  id: did,
154
- verificationMethod: [newMethod],
155
- authentication: [newMethod.id],
156
- assertionMethod: [newMethod.id],
157
- rotationInfo: {
158
- rotatedAt: new Date().toISOString(),
159
- gracePeriodDays: keepOldForDays
208
+ verificationMethod: [newMethod]
209
+ }
210
+
211
+ // A rotation must not silently reach beyond the relationship being rotated.
212
+ // Replacing a compromised `authentication` key used to rewrite `assertionMethod`
213
+ // too, which invalidates every statement the issuer has ever signed. Relationships
214
+ // outside `rotating` are carried across from `currentDocument` untouched; without a
215
+ // `currentDocument` there is nothing to carry, and the result matches the old output.
216
+ VERIFICATION_RELATIONSHIPS.forEach(function (rel) {
217
+ if (rotating.indexOf(rel) !== -1) {
218
+ didDocument[rel] = [newMethod.id]
219
+ } else if (current && Array.isArray(current[rel]) && current[rel].length) {
220
+ didDocument[rel] = current[rel].slice()
160
221
  }
222
+ })
223
+
224
+ // Keep the verification methods those carried-over relationships still point at,
225
+ // otherwise the document references key ids it does not define. The rotated-out key
226
+ // drops out here automatically: nothing references it any more.
227
+ if (current && Array.isArray(current.verificationMethod)) {
228
+ var referenced = {}
229
+ VERIFICATION_RELATIONSHIPS.forEach(function (rel) {
230
+ (didDocument[rel] || []).forEach(function (id) { referenced[id] = true })
231
+ })
232
+ current.verificationMethod.forEach(function (vm) {
233
+ if (referenced[vm.id] && vm.id !== newMethod.id) didDocument.verificationMethod.push(vm)
234
+ })
235
+ }
236
+
237
+ didDocument.rotationInfo = {
238
+ rotatedAt: new Date().toISOString(),
239
+ gracePeriodDays: keepOldForDays
161
240
  }
162
241
 
163
242
  var jwks = {
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
 
3
3
  var _ = require('../util/_')
4
- var bs58 = require('bs58')
5
4
  var buffer = require('buffer')
6
5
 
7
6
  /**
@@ -11,6 +10,71 @@ var buffer = require('buffer')
11
10
  */
12
11
  var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'.split('')
13
12
 
13
+ // Reverse lookup for decoding. Built from ALPHABET so the two cannot disagree.
14
+ var ALPHABET_MAP = {}
15
+ for (var _i = 0; _i < ALPHABET.length; _i++) ALPHABET_MAP[ALPHABET[_i]] = _i
16
+
17
+ /**
18
+ * Base58 encode/decode, replacing the `bs58` dependency (which also pulled `base-x`).
19
+ *
20
+ * This is the standard big-number-in-base-58 conversion: repeatedly multiply the
21
+ * accumulator by 256 (encode) or 58 (decode) and carry. Two details are where
22
+ * implementations usually go wrong, and both are exercised by the differential test in
23
+ * test/encoding/base58.js:
24
+ *
25
+ * - Leading zero bytes are NOT part of the number — they are significant and each one
26
+ * maps to a literal '1'. Dropping them silently truncates an address.
27
+ * - The carry loops must run to completion before the next digit, or long inputs lose
28
+ * the high-order bytes.
29
+ *
30
+ * These functions encode and decode addresses and WIF private keys, so they were
31
+ * verified by differential test against bs58 across 20,000 random inputs — including
32
+ * every leading-zero-count from 0 to 8 — before the dependency was removed.
33
+ */
34
+ function base58encode (buf) {
35
+ if (buf.length === 0) return ''
36
+ var digits = [0]
37
+ var i, j, carry
38
+ for (i = 0; i < buf.length; i++) {
39
+ for (j = 0, carry = buf[i]; j < digits.length; j++) {
40
+ carry += digits[j] << 8
41
+ digits[j] = carry % 58
42
+ carry = (carry / 58) | 0
43
+ }
44
+ while (carry > 0) {
45
+ digits.push(carry % 58)
46
+ carry = (carry / 58) | 0
47
+ }
48
+ }
49
+ var out = ''
50
+ // Each leading zero byte is a significant '1', not part of the number.
51
+ for (i = 0; buf[i] === 0 && i < buf.length - 1; i++) out += ALPHABET[0]
52
+ for (i = digits.length - 1; i >= 0; i--) out += ALPHABET[digits[i]]
53
+ return out
54
+ }
55
+
56
+ function base58decode (str) {
57
+ if (str.length === 0) return Buffer.alloc(0)
58
+ var bytes = [0]
59
+ var i, j, carry
60
+ for (i = 0; i < str.length; i++) {
61
+ var value = ALPHABET_MAP[str[i]]
62
+ if (value === undefined) throw new Error('Non-base58 character')
63
+ for (j = 0, carry = value; j < bytes.length; j++) {
64
+ carry += bytes[j] * 58
65
+ bytes[j] = carry & 0xff
66
+ carry >>= 8
67
+ }
68
+ while (carry > 0) {
69
+ bytes.push(carry & 0xff)
70
+ carry >>= 8
71
+ }
72
+ }
73
+ // Mirror of the encode side: each leading '1' is a zero byte.
74
+ for (i = 0; str[i] === ALPHABET[0] && i < str.length - 1; i++) bytes.push(0)
75
+ return Buffer.from(bytes.reverse())
76
+ }
77
+
14
78
  /**
15
79
  * A Base58 object can encode/decoded Base 58, which is used primarily for
16
80
  * string-formatted Bitcoin addresses and private keys. Addresses and private
@@ -54,7 +118,7 @@ Base58.encode = function (buf) {
54
118
  if (!buffer.Buffer.isBuffer(buf)) {
55
119
  throw new Error('Input should be a buffer')
56
120
  }
57
- return bs58.encode(buf)
121
+ return base58encode(buf)
58
122
  }
59
123
 
60
124
  /**
@@ -67,7 +131,7 @@ Base58.decode = function (str) {
67
131
  if (typeof str !== 'string') {
68
132
  throw new Error('Input should be a string')
69
133
  }
70
- return Buffer.from(bs58.decode(str))
134
+ return base58decode(str)
71
135
  }
72
136
 
73
137
  Base58.prototype.fromBuffer = function (buf) {
@@ -2,7 +2,6 @@
2
2
 
3
3
  var HDPrivateKey = require('../hdprivatekey')
4
4
  var BN = require('../crypto/bn')
5
- var unorm = require('unorm')
6
5
  var _ = require('../util/_')
7
6
 
8
7
  var pbkdf2 = require('./pbkdf2')
@@ -16,6 +15,21 @@ var Random = require('../crypto/random')
16
15
 
17
16
  var $ = require('../util/preconditions')
18
17
 
18
+ /**
19
+ * NFKD-normalise a mnemonic or passphrase, as BIP-39 requires before PBKDF2.
20
+ *
21
+ * Replaces the `unorm` dependency: `String.prototype.normalize` has been in the language
22
+ * since ES6 and this package requires Node >= 20.19, so the shim was redundant.
23
+ *
24
+ * Non-strings return '' so that `Mnemonic.isValid(garbage)` answers false instead of
25
+ * throwing. unorm was inconsistent about this — `[]` and `{}` returned '' while `['a']`
26
+ * and a Buffer threw — and a validity predicate that fails closed for every non-string
27
+ * is both the safer answer and the coherent one.
28
+ */
29
+ function nfkd (str) {
30
+ return typeof str === 'string' ? str.normalize('NFKD') : ''
31
+ }
32
+
19
33
  /**
20
34
  * This is an immutable class that represents a BIP39 Mnemonic code.
21
35
  * See BIP39 specification for more info: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
@@ -52,7 +66,7 @@ var Mnemonic = function (data, wordlist) {
52
66
  if (Buffer.isBuffer(data)) {
53
67
  seed = data
54
68
  } else if (_.isString(data)) {
55
- phrase = unorm.nfkd(data)
69
+ phrase = nfkd(data)
56
70
  } else if (_.isNumber(data)) {
57
71
  ent = data
58
72
  } else if (data) {
@@ -145,7 +159,7 @@ Mnemonic.Words = require('./words')
145
159
  * @returns {boolean}
146
160
  */
147
161
  Mnemonic.isValid = function (mnemonic, wordlist) {
148
- mnemonic = unorm.nfkd(mnemonic)
162
+ mnemonic = nfkd(mnemonic)
149
163
  wordlist = wordlist || Mnemonic._getDictionary(mnemonic)
150
164
 
151
165
  if (!wordlist) {
@@ -179,7 +193,7 @@ Mnemonic.isValid = function (mnemonic, wordlist) {
179
193
  * @returns {boolean}
180
194
  */
181
195
  Mnemonic._belongsToWordlist = function (mnemonic, wordlist) {
182
- var words = unorm.nfkd(mnemonic).split(' ')
196
+ var words = nfkd(mnemonic).split(' ')
183
197
  for (var i = 0; i < words.length; i++) {
184
198
  var ind = wordlist.indexOf(words[i])
185
199
  if (ind < 0) return false
@@ -217,7 +231,7 @@ Mnemonic._getDictionary = function (mnemonic) {
217
231
  */
218
232
  Mnemonic.prototype.toSeed = function (passphrase) {
219
233
  passphrase = passphrase || ''
220
- return pbkdf2(unorm.nfkd(this.phrase), unorm.nfkd('mnemonic' + passphrase), 2048, 64)
234
+ return pbkdf2(nfkd(this.phrase), nfkd('mnemonic' + passphrase), 2048, 64)
221
235
  }
222
236
 
223
237
  /**
@@ -1,2 +1,35 @@
1
- if (process.browser) module.exports = require('./pbkdf2.browser')
2
- else module.exports = require('./pbkdf2.node')
1
+ 'use strict'
2
+
3
+ // Pick the PBKDF2 backend by CAPABILITY, not by `process.browser`. Same reasoning as
4
+ // lib/crypto/hash.js: that flag is undefined in React Native, Deno, Workers and Bun, so
5
+ // all of them took the node branch and required('crypto') — failing outright, or
6
+ // resolving to a partial shim.
7
+ //
8
+ // The node variant needs `createHmac('sha512')`, so that is what is probed, and the
9
+ // result is checked against RFC 4231 test case 1 rather than merely existing. A shim
10
+ // that returns a wrong or stubbed HMAC would otherwise silently corrupt every seed
11
+ // derived from a mnemonic — and unlike a crash, that failure is invisible until funds
12
+ // are missing. The pure-JS fallback is audited and always safe.
13
+
14
+ var RFC4231_CASE1 = '87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854'
15
+
16
+ function nodeHmacIsUsable () {
17
+ var crypto
18
+ try {
19
+ crypto = require('crypto')
20
+ } catch (e) {
21
+ return false
22
+ }
23
+ if (!crypto || typeof crypto.createHmac !== 'function') return false
24
+
25
+ try {
26
+ var mac = crypto.createHmac('sha512', Buffer.alloc(20, 0x0b))
27
+ .update('Hi There')
28
+ .digest('hex')
29
+ return mac === RFC4231_CASE1
30
+ } catch (e) {
31
+ return false
32
+ }
33
+ }
34
+
35
+ module.exports = nodeHmacIsUsable() ? require('./pbkdf2.node') : require('./pbkdf2.browser')
@@ -8,7 +8,26 @@ var BN = require('../crypto/bn')
8
8
  var Hash = require('../crypto/hash')
9
9
  var Signature = require('../crypto/signature')
10
10
  var PublicKey = require('../publickey')
11
- var cloneDeep = require('clone-deep')
11
+ /**
12
+ * Copy a script stack for the step listener, so a listener that mutates what it is
13
+ * handed cannot corrupt the running evaluation.
14
+ *
15
+ * Replaces the `clone-deep` dependency. The stack is an array of Buffers — every one of
16
+ * the 32 push sites in this file pushes a Buffer — so a shallow map with `Buffer.from`
17
+ * reproduces `cloneDeep(stack, true)` exactly for this input: same bytes, new backing
18
+ * memory, `Buffer.isBuffer` still true, empty buffers preserved.
19
+ *
20
+ * `structuredClone` is NOT a substitute: it returns Uint8Array for a Buffer input, which
21
+ * would silently change the type handed to every existing stepListener.
22
+ *
23
+ * Anything non-Buffer is passed through by reference. Nothing puts such a value on the
24
+ * stack today; if something ever does it will be a primitive, where sharing is harmless.
25
+ */
26
+ function cloneStack (stack) {
27
+ return stack.map(function (item) {
28
+ return Buffer.isBuffer(item) ? Buffer.from(item) : item
29
+ })
30
+ }
12
31
 
13
32
  /**
14
33
  * Bitcoin transactions contain scripts. Each input has a script called the
@@ -653,7 +672,7 @@ Interpreter.prototype.evaluate = function () {
653
672
  Interpreter.prototype._callbackStep = function (thisStep) {
654
673
  if (typeof this.stepListener === 'function') {
655
674
  try {
656
- this.stepListener(thisStep, cloneDeep(this.stack, true), cloneDeep(this.altstack, true))
675
+ this.stepListener(thisStep, cloneStack(this.stack), cloneStack(this.altstack))
657
676
  } catch (err) {
658
677
  console.log(`Error in Step callback:${err}`)
659
678
  }
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  var _ = require('../../util/_')
4
- var inherits = require('inherits')
4
+ var inherits = require('../../util/inherits')
5
5
  var Input = require('./input')
6
6
  var Output = require('../output')
7
7
  var $ = require('../../util/preconditions')
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  var _ = require('../../util/_')
4
- var inherits = require('inherits')
4
+ var inherits = require('../../util/inherits')
5
5
  var Input = require('./input')
6
6
  var Output = require('../output')
7
7
  var $ = require('../../util/preconditions')
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- var inherits = require('inherits')
3
+ var inherits = require('../../util/inherits')
4
4
 
5
5
  var $ = require('../../util/preconditions')
6
6
 
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- var inherits = require('inherits')
3
+ var inherits = require('../../util/inherits')
4
4
 
5
5
  var $ = require('../../util/preconditions')
6
6
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  var _ = require('../util/_')
4
4
  var $ = require('../util/preconditions')
5
- var inherits = require('inherits')
5
+ var inherits = require('../util/inherits')
6
6
  var JSUtil = require('../util/js')
7
7
 
8
8
  var PublicKey = require('../publickey')
@@ -0,0 +1,37 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Set up prototypal inheritance between two constructors.
5
+ *
6
+ * Replaces the `inherits` dependency, whose modern branch this reproduces exactly:
7
+ * `ctor.super_` is assigned and `ctor.prototype` is REPLACED with an object whose
8
+ * prototype is `superCtor.prototype`.
9
+ *
10
+ * Two details are load-bearing and must not be "simplified" into
11
+ * `Object.setPrototypeOf(ctor.prototype, superCtor.prototype)`:
12
+ *
13
+ * - `constructor` is redefined as non-enumerable. A plain assignment would make it
14
+ * enumerable, so it would start showing up in `for…in` and in anything that walks
15
+ * own+inherited keys — this library's `toObject`/`toJSON` paths among them.
16
+ * - `ctor.super_` is part of the observable surface; `TransactionSignature` and the
17
+ * four Input subclasses are public classes, so anything reading `.super_` today
18
+ * keeps working.
19
+ *
20
+ * The package also carried an `Object.create`-less fallback for pre-ES5 browsers. That
21
+ * branch is unreachable on Node >= 20.19 and in every browser this library targets.
22
+ *
23
+ * @param {Function} ctor - the subclass constructor
24
+ * @param {Function} superCtor - the superclass constructor
25
+ */
26
+ module.exports = function inherits (ctor, superCtor) {
27
+ if (!superCtor) return
28
+ ctor.super_ = superCtor
29
+ ctor.prototype = Object.create(superCtor.prototype, {
30
+ constructor: {
31
+ value: ctor,
32
+ enumerable: false,
33
+ writable: true,
34
+ configurable: true
35
+ }
36
+ })
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartledger/bsv",
3
- "version": "7.12.0",
3
+ "version": "7.13.0",
4
4
  "description": "🚀 Complete Bitcoin SV development framework with legally-recognizable DID:web + W3C VC-JWT toolkit, Legal Token Protocol (LTP), Global Digital Attestation Framework (GDAF), StatusList2021 revocation, and 16 flexible loading options. Standards-based credentials with ES256/ES256K support, on-chain BSV anchoring, and comprehensive Bitcoin SV API. Perfect for legal tokens, verifiable credentials, DeFi, smart contracts, and secure Bitcoin applications.",
5
5
  "author": "SmartLedger Technology <hello@smartledger.technology> (https://smartledger.technology)",
6
6
  "homepage": "https://github.com/codenlighten/smartledger-bsv#readme",
@@ -231,12 +231,7 @@
231
231
  "@noble/curves": "2.2.0",
232
232
  "@noble/hashes": "2.2.0",
233
233
  "bn.js": "=4.12.5",
234
- "bs58": "=4.0.1",
235
- "clone-deep": "^4.0.1",
236
- "hash.js": "^1.1.7",
237
- "inherits": "2.0.4",
238
- "secrets.js-grempe": "^2.0.0",
239
- "unorm": "1.6.0"
234
+ "secrets.js-grempe": "^2.0.0"
240
235
  },
241
236
  "devDependencies": {
242
237
  "assert": "^2.1.0",
@@ -277,7 +272,8 @@
277
272
  "before",
278
273
  "beforeEach",
279
274
  "describe",
280
- "it"
275
+ "it",
276
+ "globalThis"
281
277
  ],
282
278
  "ignore": [
283
279
  "dist/**",
@@ -0,0 +1,126 @@
1
+ 'use strict'
2
+
3
+ /* global describe, it */
4
+
5
+ // The hashing and PBKDF2 backends are chosen by CAPABILITY, not by `process.browser`.
6
+ //
7
+ // `process.browser` is a Browserify-era convention, undefined in React Native, Deno,
8
+ // Cloudflare Workers and Bun. Every one of those took the node branch and
9
+ // require('crypto')'d, which fails outright in React Native or — worse — resolves to a
10
+ // partial shim. rn-nodeify and several RN starter templates register exactly such a
11
+ // shim, so this is a reported failure mode, not a hypothetical one.
12
+ //
13
+ // Each case runs in a child process because it has to intercept module resolution
14
+ // before lib/ is loaded, which cannot be undone safely in-process.
15
+
16
+ require('chai').should()
17
+ var path = require('path')
18
+ var spawnSync = require('child_process').spawnSync
19
+
20
+ var ROOT = path.resolve(__dirname, '../..')
21
+ var SHA256_ABC = 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
22
+
23
+ // Run `body` with Module._load intercepted by `stub`, and print the result of `report`.
24
+ function inChild (stub, report) {
25
+ var src = [
26
+ 'var Module = require("module"), orig = Module._load',
27
+ 'var real = orig.call(Module, "crypto", module, false)',
28
+ 'Module._load = function (r) {',
29
+ ' if (r === "crypto") { ' + stub + ' }',
30
+ ' return orig.apply(this, arguments)',
31
+ '}',
32
+ // Only the pure-JS backend is loaded for comparison. hash.node.js require()s
33
+ // 'crypto' at module scope, so it is not even loadable when the stub removes it —
34
+ // which is precisely why hash.js must not reach for it unconditionally.
35
+ 'var HASH = require(' + JSON.stringify(path.join(ROOT, 'lib/crypto/hash.js')) + ')',
36
+ 'var PURE = require(' + JSON.stringify(path.join(ROOT, 'lib/crypto/hash.browser.js')) + ')',
37
+ 'console.log(' + report + ')'
38
+ ].join('\n')
39
+ var out = spawnSync(process.execPath, ['-e', src], { encoding: 'utf8' })
40
+ if (out.status !== 0) throw new Error('child failed: ' + out.stderr)
41
+ return out.stdout.trim()
42
+ }
43
+
44
+ describe('crypto backend selection', function () {
45
+ this.timeout(30000)
46
+
47
+ it('uses node crypto when it is present and correct', function () {
48
+ // Not the pure-JS module => the node backend was selected.
49
+ inChild('return real', '(HASH !== PURE) + " " + HASH.sha256(Buffer.from("abc")).toString("hex")')
50
+ .should.equal('true ' + SHA256_ABC)
51
+ })
52
+
53
+ it('falls back to the pure-JS backend when there is no crypto module', function () {
54
+ inChild('var e = new Error("no crypto"); e.code = "MODULE_NOT_FOUND"; throw e',
55
+ '(HASH === PURE) + " " + HASH.sha256(Buffer.from("abc")).toString("hex")')
56
+ .should.equal('true ' + SHA256_ABC)
57
+ })
58
+
59
+ // The important one. A shim that EXISTS but computes the wrong digest must be
60
+ // rejected — trusting it would silently corrupt every hash in the library, and a
61
+ // wrong hash is invisible until it matters.
62
+ it('rejects a crypto shim that returns incorrect digests', function () {
63
+ inChild(
64
+ 'return { createHash: function () { return { update: function () { return this }, ' +
65
+ 'digest: function () { return "deadbeef" } } } }',
66
+ '(HASH === PURE) + " " + HASH.sha256(Buffer.from("abc")).toString("hex")')
67
+ .should.equal('true ' + SHA256_ABC)
68
+ })
69
+
70
+ // OpenSSL 3 moved ripemd160 to the legacy provider, so some Node builds have sha256
71
+ // but throw on ripemd160 — and ripemd160 is what addresses are built from.
72
+ it('rejects a crypto build without ripemd160', function () {
73
+ inChild(
74
+ 'return Object.assign({}, real, { createHash: function (a) { ' +
75
+ 'if (a === "ripemd160") throw new Error("Digest method not supported"); ' +
76
+ 'return real.createHash(a) } })',
77
+ '(HASH === PURE) + " " + HASH.sha256ripemd160(Buffer.from("abc")).toString("hex").slice(0, 16)')
78
+ .should.equal('true bb1be98c142444d7')
79
+ })
80
+
81
+ it('does not consult process.browser', function () {
82
+ var src = [
83
+ 'process.browser = undefined',
84
+ 'var a = require(' + JSON.stringify(path.join(ROOT, 'lib/crypto/hash.js')) + ')',
85
+ 'console.log(a.sha256(Buffer.from("abc")).toString("hex"))'
86
+ ].join('\n')
87
+ var withoutFlag = spawnSync(process.execPath, ['-e', src], { encoding: 'utf8' })
88
+ var withFlag = spawnSync(process.execPath, ['-e', 'process.browser = true\n' + src.split('\n').slice(1).join('\n')], { encoding: 'utf8' })
89
+ withoutFlag.stdout.trim().should.equal(SHA256_ABC)
90
+ withFlag.stdout.trim().should.equal(SHA256_ABC)
91
+ })
92
+
93
+ describe('pbkdf2', function () {
94
+ // BIP-39 English test vector; the seed must not depend on which backend ran.
95
+ var PHRASE = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
96
+ var SEED = 'c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04'
97
+
98
+ function seedInChild (stub) {
99
+ var src = [
100
+ 'var Module = require("module"), orig = Module._load',
101
+ 'var real = orig.call(Module, "crypto", module, false)',
102
+ 'Module._load = function (r) { if (r === "crypto") { ' + stub + ' } return orig.apply(this, arguments) }',
103
+ 'var bsv = require(' + JSON.stringify(ROOT) + ')',
104
+ 'console.log(new bsv.Mnemonic(' + JSON.stringify(PHRASE) + ').toSeed("TREZOR").toString("hex"))'
105
+ ].join('\n')
106
+ var out = spawnSync(process.execPath, ['-e', src], { encoding: 'utf8' })
107
+ if (out.status !== 0) throw new Error('child failed: ' + out.stderr)
108
+ return out.stdout.trim()
109
+ }
110
+
111
+ it('derives the BIP-39 vector with node crypto', function () {
112
+ seedInChild('return real').should.equal(SEED)
113
+ })
114
+
115
+ it('derives the same seed with no crypto module', function () {
116
+ seedInChild('var e = new Error("no crypto"); e.code = "MODULE_NOT_FOUND"; throw e').should.equal(SEED)
117
+ })
118
+
119
+ it('rejects a shim whose HMAC is wrong and still derives the right seed', function () {
120
+ seedInChild(
121
+ 'return Object.assign({}, real, { createHmac: function () { return { ' +
122
+ 'update: function () { return this }, digest: function () { return Buffer.alloc(64) } } } })'
123
+ ).should.equal(SEED)
124
+ })
125
+ })
126
+ })