@smartledger/bsv 7.12.0 → 8.0.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/README.md +38 -38
  3. package/bsv-didweb.min.js +6 -6
  4. package/bsv-gdaf.min.js +66 -66
  5. package/bsv-ltp.min.js +46 -46
  6. package/bsv-mnemonic.min.js +8 -8
  7. package/bsv-smartcontract.min.js +1 -1
  8. package/bsv.bundle.js +66 -66
  9. package/bsv.min.js +66 -66
  10. package/docs/AUDIT_SCOPE.md +29 -21
  11. package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
  12. package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
  13. package/docs/getting-started/INSTALLATION.md +23 -23
  14. package/docs/getting-started/QUICK_START.md +7 -7
  15. package/docs/migration/FROM_BSV_1_5_6.md +5 -5
  16. package/index.js +10 -2
  17. package/lib/crypto/bn.js +22 -2
  18. package/lib/crypto/hash.browser.js +13 -5
  19. package/lib/crypto/hash.js +58 -2
  20. package/lib/crypto/random.js +72 -15
  21. package/lib/didweb/index.js +88 -9
  22. package/lib/encoding/base58.js +67 -3
  23. package/lib/mnemonic/mnemonic.js +19 -5
  24. package/lib/mnemonic/pbkdf2.js +35 -2
  25. package/lib/script/interpreter.js +420 -58
  26. package/lib/transaction/input/multisig.js +1 -1
  27. package/lib/transaction/input/multisigscripthash.js +1 -1
  28. package/lib/transaction/input/publickey.js +1 -1
  29. package/lib/transaction/input/publickeyhash.js +1 -1
  30. package/lib/transaction/sighash.js +18 -6
  31. package/lib/transaction/signature.js +1 -1
  32. package/lib/util/inherits.js +37 -0
  33. package/package.json +7 -8
  34. package/test/consensus/base58-vectors.js +120 -0
  35. package/test/consensus/sv-script-vectors.js +81 -0
  36. package/test/consensus/sv-sighash-vectors.js +51 -0
  37. package/test/consensus/sv-tx-vectors.js +48 -0
  38. package/test/crypto/backend_selection.js +126 -0
  39. package/test/crypto/random.js +73 -0
  40. package/test/data/bitcoin-sv/README.md +170 -0
  41. package/test/data/bitcoin-sv/base58_encode_decode.json +14 -0
  42. package/test/data/bitcoin-sv/base58_keys_invalid.json +152 -0
  43. package/test/data/bitcoin-sv/base58_keys_valid.json +452 -0
  44. package/test/data/bitcoin-sv/script_tests.json +2591 -0
  45. package/test/data/bitcoin-sv/sighash.json +1003 -0
  46. package/test/data/bitcoin-sv/tx_invalid.json +285 -0
  47. package/test/data/bitcoin-sv/tx_valid.json +367 -0
  48. package/test/didweb/relationships.js +144 -0
  49. package/test/regressions.js +35 -0
  50. package/test/script/chronicle.js +170 -25
  51. package/test/script/defaults.js +93 -0
  52. package/test/script/genesis_limits.js +62 -6
  53. package/test/script/interpreter.js +24 -8
  54. package/test/transaction/sighash.js +21 -1
  55. package/test/transaction/unspentoutput.js +6 -1
  56. package/version.js +1 -1
@@ -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
 
@@ -168,15 +168,27 @@ var sighashPreimage = function sighashPreimage (transaction, sighashType, inputN
168
168
  // only took effect when FORKID was absent could never select OTDA in practice, and
169
169
  // the flag would mean nothing.
170
170
  //
171
- // Gated on SCRIPT_ENABLE_CHRONICLE, which is off by default: before the upgrade the
172
- // 0x20 bit means nothing, so BIP-143 signatures already exist whose type byte happens
173
- // to set it. Honouring it unconditionally would reinterpret those as OTDA.
171
+ // Routed on the bit alone, not on whether Chronicle is enabled, which is what
172
+ // the node does:
173
+ //
174
+ // if(enabledSighashForkid && sigHashType.hasForkId() && !sigHashType.hasChronicle())
175
+ // return SignatureHashBIP143(...);
176
+ // return SignatureHashOriginal(...);
177
+ //
178
+ // The concern that made this gated is real — before the upgrade the 0x20 bit
179
+ // meant nothing, so BIP-143 signatures exist whose type byte happens to set
180
+ // it, and reinterpreting those as OTDA would be a disaster. The node answers
181
+ // it in a different place: CheckSignatureEncoding rejects a signature
182
+ // carrying the bit outside Chronicle as SCRIPT_ERR_ILLEGAL_CHRONICLE, so
183
+ // such a signature never reaches this function. Gating here instead made 260
184
+ // of the node's 1000 digest vectors disagree, because the digest for a given
185
+ // transaction and hash type then depends on a flag the node does not consult.
174
186
  //
175
187
  // Note the sighash type byte is committed INSIDE the preimage either way, so setting
176
188
  // this bit changes the digest even where it does not change the algorithm.
177
- if ((sighashType & Signature.SIGHASH_CHRONICLE) && (flags & Interpreter.SCRIPT_ENABLE_CHRONICLE)) {
178
- // fall through to the original algorithm
179
- } else if ((sighashType & Signature.SIGHASH_FORKID) && (flags & Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID)) {
189
+ if ((sighashType & Signature.SIGHASH_FORKID) &&
190
+ !(sighashType & Signature.SIGHASH_CHRONICLE) &&
191
+ (flags & Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID)) {
180
192
  return sighashPreimageForForkId(txcopy, sighashType, inputNumber, subscript, satoshisBN)
181
193
  }
182
194
 
@@ -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": "8.0.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",
@@ -60,6 +60,9 @@
60
60
  "lint": "standard",
61
61
  "check:load-order": "node scripts/check-address-load-order.js",
62
62
  "conformance": "node conformance/verify.js",
63
+ "vectors:sv": "node tools/sv-vector-report.js",
64
+ "vectors:sv-tx": "node tools/sv-tx-report.js",
65
+ "vectors:sv-sighash": "node tools/sv-sighash-report.js",
63
66
  "conformance:generate": "node conformance/generate.js",
64
67
  "lint:ratchet": "node scripts/lint-ratchet.js",
65
68
  "lint:ratchet:update": "node scripts/lint-ratchet.js --update",
@@ -231,12 +234,7 @@
231
234
  "@noble/curves": "2.2.0",
232
235
  "@noble/hashes": "2.2.0",
233
236
  "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"
237
+ "secrets.js-grempe": "^2.0.0"
240
238
  },
241
239
  "devDependencies": {
242
240
  "assert": "^2.1.0",
@@ -277,7 +275,8 @@
277
275
  "before",
278
276
  "beforeEach",
279
277
  "describe",
280
- "it"
278
+ "it",
279
+ "globalThis"
281
280
  ],
282
281
  "ignore": [
283
282
  "dist/**",
@@ -0,0 +1,120 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Base58 and Base58Check vectors from SV Node v1.2.0.
5
+ *
6
+ * `base58_encode_decode.json` had no counterpart in the inherited bitcoind
7
+ * corpus and nothing else here exercises it. It is small, but it is the only
8
+ * place the raw codec is pinned against the node in both directions, and it
9
+ * carries the two cases an implementation gets wrong: the empty input, and
10
+ * leading zero bytes, which base58 encodes as leading '1's rather than as part
11
+ * of a number.
12
+ *
13
+ * The key vectors are content-identical to the bitcoind copies that
14
+ * test/address.js and test/privatekey.js already use — same 50 rows, different
15
+ * whitespace. They are run here against the SV copies regardless, so that the
16
+ * corpus under test/data/bitcoin-sv is the one this library is measured
17
+ * against. That directory is the declared specification, and re-copying it
18
+ * from a newer node tag should move these tests with it.
19
+ */
20
+
21
+ require('chai').should()
22
+ const bsv = require('../..')
23
+ const Base58 = bsv.encoding.Base58
24
+ const Base58Check = bsv.encoding.Base58Check
25
+ const Address = bsv.Address
26
+ const PrivateKey = bsv.PrivateKey
27
+
28
+ const encodeDecode = require('../data/bitcoin-sv/base58_encode_decode.json')
29
+ const keysValid = require('../data/bitcoin-sv/base58_keys_valid.json')
30
+ const keysInvalid = require('../data/bitcoin-sv/base58_keys_invalid.json')
31
+
32
+ describe('base58 vectors (SV Node v1.2.0)', function () {
33
+ describe('the raw codec', function () {
34
+ it('encodes every vector to the recorded string', function () {
35
+ encodeDecode.forEach(function (v, i) {
36
+ Base58.encode(Buffer.from(v[0], 'hex'))
37
+ .should.equal(v[1], 'encoding row ' + i + ' (' + v[0] + ')')
38
+ })
39
+ })
40
+
41
+ it('decodes every vector back to the recorded bytes', function () {
42
+ encodeDecode.forEach(function (v, i) {
43
+ Base58.decode(v[1]).toString('hex')
44
+ .should.equal(v[0], 'decoding row ' + i + ' (' + JSON.stringify(v[1]) + ')')
45
+ })
46
+ })
47
+
48
+ it('keeps leading zero bytes as leading ones', function () {
49
+ // Ten zero bytes encode as ten '1's. An implementation that treats the
50
+ // input purely as a number loses them, and every address starting 1
51
+ // depends on this.
52
+ Base58.encode(Buffer.alloc(10)).should.equal('1111111111')
53
+ Base58.decode('1111111111').should.deep.equal(Buffer.alloc(10))
54
+ })
55
+
56
+ it('round-trips the empty input', function () {
57
+ Base58.encode(Buffer.alloc(0)).should.equal('')
58
+ Base58.decode('').length.should.equal(0)
59
+ })
60
+ })
61
+
62
+ describe('keys and addresses', function () {
63
+ it('accepts every valid key vector', function () {
64
+ let addresses = 0
65
+ let privkeys = 0
66
+ keysValid.forEach(function (v, i) {
67
+ const str = v[0]
68
+ const meta = v[2]
69
+ if (meta.isPrivkey) {
70
+ const key = PrivateKey.fromWIF(str)
71
+ key.toWIF().should.equal(str, 'WIF round trip at ' + i)
72
+ privkeys++
73
+ } else {
74
+ const network = meta.isTestnet ? 'testnet' : 'livenet'
75
+ const address = Address.fromString(str, network)
76
+ address.toString().should.equal(str, 'address round trip at ' + i)
77
+ address.hashBuffer.toString('hex')
78
+ .should.equal(v[1], 'hash at ' + i)
79
+ address.type.should.equal(
80
+ meta.addrType === 'script' ? Address.PayToScriptHash : Address.PayToPublicKeyHash,
81
+ 'type at ' + i)
82
+ addresses++
83
+ }
84
+ })
85
+ // Both kinds must actually have been reached.
86
+ addresses.should.be.above(0)
87
+ privkeys.should.be.above(0)
88
+ })
89
+
90
+ it('rejects every invalid key vector, as neither address nor key', function () {
91
+ // The claim these vectors make is that the string is not a valid address
92
+ // and not a valid WIF — not that it fails Base58Check. Several of them
93
+ // carry a perfectly good checksum and are simply the wrong length or
94
+ // version, so the checksum layer accepts them and the layer above is
95
+ // what has to refuse. Asserting the stronger thing would be asserting
96
+ // something the corpus does not say.
97
+ let checksumOk = 0
98
+ keysInvalid.forEach(function (v, i) {
99
+ const str = v[0]
100
+ Address.isValid(str).should.equal(false, 'Address accepted row ' + i)
101
+
102
+ let isKey = true
103
+ try {
104
+ PrivateKey.fromWIF(str)
105
+ } catch (e) {
106
+ isKey = false
107
+ }
108
+ isKey.should.equal(false, 'PrivateKey accepted row ' + i)
109
+
110
+ try {
111
+ Base58Check.decode(str)
112
+ checksumOk++
113
+ } catch (e) { /* a bad checksum is one way to be invalid, not the only one */ }
114
+ })
115
+ // Guards the reasoning above: if none of them had a valid checksum, this
116
+ // test would be weaker than it looks and Base58Check could be asserted.
117
+ checksumOk.should.be.above(0)
118
+ })
119
+ })
120
+ })
@@ -0,0 +1,81 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Consensus ratchet over the SV Node script vectors.
5
+ *
6
+ * A report tells you where you are; this stops you going backwards. Both read
7
+ * the same harness, so the progress figure and the gate cannot disagree.
8
+ *
9
+ * False accepts are held at zero outright rather than through an allowlist,
10
+ * because accepting a script the network rejects is the direction that can
11
+ * cost money. False rejects are held at zero too — the corpus passes
12
+ * completely, so there is nothing to exempt and no list to maintain.
13
+ */
14
+
15
+ require('chai').should()
16
+ const harness = require('../../tools/sv-vector-harness')
17
+
18
+ describe('SV Node script vectors', function () {
19
+ this.timeout(120000)
20
+
21
+ let results = null
22
+
23
+ before(function () {
24
+ results = harness.runAll()
25
+ })
26
+
27
+ it('runs the whole corpus', function () {
28
+ results.length.should.equal(1483)
29
+ })
30
+
31
+ it('uses no flag the harness does not recognise', function () {
32
+ // An unrecognised flag means vectors run under the wrong rules, and any
33
+ // pass among them means nothing.
34
+ const unknown = new Set()
35
+ results.forEach(r => r.unknownFlags.forEach(f => unknown.add(f)))
36
+ Array.from(unknown).should.deep.equal([])
37
+ })
38
+
39
+ it('accepts nothing the node rejects', function () {
40
+ const accepts = results.filter(r => r.direction === 'accept')
41
+ const detail = accepts.map(r =>
42
+ '\n ' + r.id + ' ' + r.reason + '\n ' + harness.describe(r.row)).join('')
43
+ accepts.length.should.equal(0, 'false accepts must stay at zero:' + detail)
44
+ })
45
+
46
+ it('rejects nothing the node accepts', function () {
47
+ const rejects = results.filter(r => r.direction === 'reject')
48
+ const detail = rejects.map(r =>
49
+ '\n ' + r.id + ' ' + r.reason + '\n ' + harness.describe(r.row)).join('')
50
+ rejects.length.should.equal(0, 'false rejects must stay at zero:' + detail)
51
+ })
52
+
53
+ it('fails for the reason the node gives', function () {
54
+ // Matching the outcome is not the same as matching the reason, and the gap
55
+ // hid three real bugs — see ERROR_CODE_ALIASES in the harness. Each left
56
+ // the script failing, so the two tests above stayed green throughout.
57
+ const wrong = results.filter(r => r.codeMatches === false)
58
+ const detail = wrong.map(r =>
59
+ '\n ' + r.id + ' expected ' + r.expectedCode + ', reported ' + r.gotCode +
60
+ '\n ' + harness.describe(r.row)).join('')
61
+ wrong.length.should.equal(0,
62
+ wrong.length + ' vector(s) fail for the wrong reason:' + detail)
63
+ })
64
+
65
+ it('compares a code on every vector the node rejects', function () {
66
+ // Guards the test above: if `comparable` ever narrowed, it would pass by
67
+ // checking nothing.
68
+ const rejected = results.filter(r => r.row.expected !== 'OK' && r.passed)
69
+ rejected.length.should.equal(600)
70
+ rejected.every(r => r.codeMatches !== null).should.equal(true)
71
+ })
72
+
73
+ it('uses every alias it grants', function () {
74
+ // The ratchet's other direction. An alias no vector produces any more is
75
+ // an exemption outliving its reason, and should be deleted rather than
76
+ // left to cover something later.
77
+ const produced = new Set(results.filter(r => r.gotCode).map(r => r.gotCode))
78
+ const unused = Object.keys(harness.ERROR_CODE_ALIASES).filter(a => !produced.has(a))
79
+ unused.should.deep.equal([], 'alias(es) no vector produces; remove them')
80
+ })
81
+ })
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Transaction digest vectors from SV Node v1.2.0 — the regression gate.
5
+ *
6
+ * The work is in tools/sv-sighash-harness.js, which npm run vectors:sv-sighash
7
+ * also uses, so the progress report and this gate cannot disagree about what
8
+ * passes. That file documents the corpus layout and the node's routing.
9
+ *
10
+ * Unlike the script and transaction vectors, this one carries no known-failure
11
+ * list. It has never had a failing row and there is no reason it should: a
12
+ * digest is a pure function of a transaction and a hash type, with no era to
13
+ * negotiate.
14
+ */
15
+
16
+ require('chai').should()
17
+ const harness = require('../../tools/sv-sighash-harness')
18
+
19
+ describe('SV Node sighash vectors', function () {
20
+ this.timeout(120000)
21
+
22
+ const results = harness.runAll()
23
+
24
+ it('covers the whole corpus', function () {
25
+ results.length.should.equal(1000)
26
+ })
27
+
28
+ it('matches the node on both digest columns', function () {
29
+ const failures = results.filter(r => !r.passed)
30
+ .map(r => '#' + r.index + ' ' + r.reason)
31
+ failures.slice(0, 8).should.deep.equal([],
32
+ failures.length + ' of ' + results.length + ' rows disagree with the node')
33
+ })
34
+
35
+ it('routes every Chronicle-bit row to the original digest', function () {
36
+ // Stated separately so a routing regression reads as one rather than as
37
+ // hundreds of unexplained hash mismatches.
38
+ const withBit = harness.chronicleRows()
39
+ withBit.length.should.equal(511)
40
+ withBit.forEach(v => v[4].should.equal(v[5]))
41
+ results.filter(r => r.chronicleBit)
42
+ .forEach(r => r.algorithm.should.equal('OTDA'))
43
+ })
44
+
45
+ it('still exercises both algorithms', function () {
46
+ // Guards the test above: if the routing collapsed to OTDA everywhere it
47
+ // would pass, and this is what would notice.
48
+ results.filter(r => r.algorithm === 'BIP143').length.should.be.above(0)
49
+ results.filter(r => r.algorithm === 'OTDA').length.should.be.above(0)
50
+ })
51
+ })
@@ -0,0 +1,48 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Consensus ratchet over the SV Node transaction vectors.
5
+ *
6
+ * These reach a level the script corpus cannot. A script vector evaluates one
7
+ * unlocking script against one locking script; these deserialise a whole
8
+ * transaction, resolve each input against the output it claims to spend, and
9
+ * require every one to verify, with locktimes and sequence numbers in play.
10
+ *
11
+ * That level earned itself immediately: it found that Genesis reverts
12
+ * OP_CHECKLOCKTIMEVERIFY and OP_CHECKSEQUENCEVERIFY to upgradable NOPs, which
13
+ * this library did not implement and no script vector can reach — the
14
+ * behaviour only appears once a real nLockTime and sequence number exist.
15
+ */
16
+
17
+ require('chai').should()
18
+ const harness = require('../../tools/sv-tx-harness')
19
+
20
+ describe('SV Node transaction vectors', function () {
21
+ this.timeout(120000)
22
+
23
+ let results = null
24
+
25
+ before(function () {
26
+ results = harness.runAll()
27
+ })
28
+
29
+ it('runs both files', function () {
30
+ results.length.should.equal(161)
31
+ results.filter(r => r.expected).length.should.equal(93)
32
+ results.filter(r => !r.expected).length.should.equal(68)
33
+ })
34
+
35
+ it('accepts no transaction the node rejects', function () {
36
+ const accepts = results.filter(r => r.direction === 'accept')
37
+ const detail = accepts.map(r =>
38
+ '\n ' + r.id + ' ' + r.reason + '\n ' + harness.describe(r)).join('')
39
+ accepts.length.should.equal(0, 'false accepts must stay at zero:' + detail)
40
+ })
41
+
42
+ it('rejects no transaction the node accepts', function () {
43
+ const rejects = results.filter(r => r.direction === 'reject')
44
+ const detail = rejects.map(r =>
45
+ '\n ' + r.id + ' ' + r.reason + '\n ' + harness.describe(r)).join('')
46
+ rejects.length.should.equal(0, 'false rejects must stay at zero:' + detail)
47
+ })
48
+ })
@@ -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
+ })