@smartledger/bsv 6.2.2 → 7.0.1

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.
@@ -89,8 +89,38 @@ var Mnemonic = function (data, wordlist) {
89
89
  })
90
90
  }
91
91
 
92
- Mnemonic.fromRandom = function (wordlist = Mnemonic.Words.ENGLISH) {
93
- return new Mnemonic(wordlist)
92
+ /**
93
+ * Generate a random Mnemonic with the given wordlist and entropy.
94
+ *
95
+ * A number in either argument position is the entropy in bits (128, 160, 192,
96
+ * 224 or 256 — higher = more words); an array is the wordlist. So every form
97
+ * below works AND honours the requested strength:
98
+ * fromRandom() -> ENGLISH, 128-bit (12 words)
99
+ * fromRandom(wordlist) -> wordlist, 128-bit
100
+ * fromRandom(wordlist, 256) -> wordlist, 256-bit (24 words)
101
+ * fromRandom(256) -> ENGLISH, 256-bit
102
+ * fromRandom(256, wordlist) -> wordlist, 256-bit
103
+ *
104
+ * Prior to 7.0.1 the second argument was silently dropped, so the documented
105
+ * `fromRandom(wordlist, 256)` form returned a weaker 12-word (128-bit) phrase
106
+ * with no error — a security foot-gun this fix closes. Invalid entropy still
107
+ * throws (must be a multiple of 32 and >= 128).
108
+ *
109
+ * @param {Array<string>|number} [wordlist] - wordlist, or entropy bits
110
+ * @param {number|Array<string>} [ent] - entropy bits, or wordlist
111
+ * @returns {Mnemonic}
112
+ */
113
+ Mnemonic.fromRandom = function (wordlist, ent) {
114
+ // A number in the first position is the entropy; normalise so `ent` holds it
115
+ // and `wordlist` holds the array (either argument order is accepted).
116
+ if (_.isNumber(wordlist)) {
117
+ var swapped = ent
118
+ ent = wordlist
119
+ wordlist = swapped
120
+ }
121
+ wordlist = wordlist || Mnemonic.Words.ENGLISH
122
+ ent = ent || 128
123
+ return new Mnemonic(ent, wordlist)
94
124
  }
95
125
 
96
126
  Mnemonic.fromString = function (mnemonic, wordlist = Mnemonic.Words.ENGLISH) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartledger/bsv",
3
- "version": "6.2.2",
3
+ "version": "7.0.1",
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",
@@ -9,6 +9,19 @@
9
9
  },
10
10
  "main": "index.js",
11
11
  "types": "bsv.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./bsv.d.ts",
15
+ "import": "./index.mjs",
16
+ "require": "./index.js",
17
+ "default": "./index.js"
18
+ },
19
+ "./package.json": "./package.json",
20
+ "./version": "./version.js",
21
+ "./lib/*.js": "./lib/*.js",
22
+ "./lib/*": "./lib/*.js",
23
+ "./*": "./*"
24
+ },
12
25
  "engines": {
13
26
  "node": ">=20.19.0"
14
27
  },
@@ -51,7 +64,7 @@
51
64
  "preimage:extract": "node examples/preimage/extract_preimage_bidirectional.js",
52
65
  "prepublishOnly": "npm run build-all",
53
66
  "sync-cdn": "node scripts/sync-cdn-urls.js",
54
- "version": "node scripts/sync-cdn-urls.js && node scripts/sync-version.js && git add README.md docs version.js",
67
+ "version": "node scripts/sync-cdn-urls.js && node scripts/sync-version.js && node scripts/gen-esm-wrapper.js && git add README.md docs version.js index.mjs",
55
68
  "test:browser:ci": "node tests/browser-smoke-runner.js"
56
69
  },
57
70
  "unpkg": "bsv.min.js",
@@ -59,6 +72,7 @@
59
72
  "cdn": "bsv.min.js",
60
73
  "files": [
61
74
  "index.js",
75
+ "index.mjs",
62
76
  "version.js",
63
77
  "test/",
64
78
  ".mocharc.json",
@@ -0,0 +1,66 @@
1
+ 'use strict'
2
+
3
+ /* global describe, it */
4
+
5
+ // Guards index.mjs — the real ESM entry behind the package's `import` condition.
6
+ // Asserts (a) it hasn't drifted from the CJS surface, (b) native ESM default AND
7
+ // named imports actually work, and (c) deprecated getters (SmartUTXO) are NOT
8
+ // forced as named exports (they'd fire their warning on every import).
9
+ //
10
+ // ESM behaviour is exercised in a child process (via an --input-type=module
11
+ // script string) rather than a dynamic import() in this file, so the source
12
+ // stays parseable by the pinned standard@12 linter.
13
+
14
+ require('chai').should()
15
+ var fs = require('fs')
16
+ var path = require('path')
17
+ var execFileSync = require('child_process').execFileSync
18
+ var spawnSync = require('child_process').spawnSync
19
+ var gen = require('../../scripts/gen-esm-wrapper')
20
+
21
+ var ROOT = path.resolve(__dirname, '../..')
22
+ var MJS = path.join(ROOT, 'index.mjs')
23
+
24
+ function runEsm (body) {
25
+ return execFileSync(process.execPath, ['--input-type=module', '-e', body],
26
+ { cwd: ROOT, stdio: 'pipe' })
27
+ }
28
+
29
+ describe('ESM wrapper (index.mjs)', function () {
30
+ it('is in sync with the CJS surface (no drift)', function () {
31
+ var onDisk = fs.readFileSync(MJS, 'utf8')
32
+ onDisk.should.equal(gen.generate())
33
+ })
34
+
35
+ it('exports every non-accessor top-level member as a named binding', function () {
36
+ var bsv = require('../..')
37
+ var names = gen.exportableNames(bsv)
38
+ names.length.should.be.above(100)
39
+ // SmartUTXO is an accessor (deprecated getter) and must be excluded.
40
+ names.should.not.include('SmartUTXO')
41
+ Object.getOwnPropertyDescriptor(bsv, 'SmartUTXO').get.should.be.a('function')
42
+ })
43
+
44
+ it('resolves real ESM default + named imports natively', function () {
45
+ runEsm(
46
+ 'import bsv, { PrivateKey, crypto } from ' + JSON.stringify('./index.mjs') + ';' +
47
+ 'process.exit((bsv && typeof PrivateKey === "function" && ' +
48
+ 'PrivateKey === bsv.PrivateKey && typeof crypto.ECDSA === "function") ? 0 : 3)')
49
+ })
50
+
51
+ it('does not emit the SmartUTXO deprecation warning at import (getter excluded)', function () {
52
+ // Re-exporting the getter would trip its console.warn at import time; stderr
53
+ // must be empty for a bare `import './index.mjs'`.
54
+ var res = spawnSync(process.execPath,
55
+ ['--input-type=module', '-e', 'import ' + JSON.stringify('./index.mjs') + ';'],
56
+ { cwd: ROOT, encoding: 'utf8' })
57
+ res.status.should.equal(0)
58
+ res.stderr.should.equal('')
59
+ })
60
+
61
+ it('keeps deprecated getters reachable via the default export only', function () {
62
+ runEsm(
63
+ 'import bsv from ' + JSON.stringify('./index.mjs') + ';' +
64
+ 'process.exit(("SmartUTXO" in bsv) ? 0 : 3)')
65
+ })
66
+ })
@@ -0,0 +1,58 @@
1
+ 'use strict'
2
+
3
+ /* global describe, it */
4
+
5
+ // Guards the 7.0 package.json `exports` map. Adding `exports` switches Node to
6
+ // strict subpath resolution, so this asserts the surface consumers actually use
7
+ // still resolves (via package self-reference, which Node enables once `exports`
8
+ // exists): the main entry, package.json, ./version, the shipped bundles, and
9
+ // lib/* deep imports in BOTH extension styles. Also smoke-tests the ESM `import`
10
+ // condition in a child process so the dual-ready "." entry can't silently rot.
11
+
12
+ require('chai').should()
13
+ var path = require('path')
14
+ var execFileSync = require('child_process').execFileSync
15
+
16
+ var N = '@smartledger/bsv'
17
+ var ROOT = path.resolve(__dirname, '../..')
18
+
19
+ describe('7.0 exports map resolution', function () {
20
+ it('resolves the main entry via self-reference', function () {
21
+ var bsv = require(N)
22
+ bsv.should.be.an('object')
23
+ bsv.PrivateKey.should.be.a('function')
24
+ bsv.crypto.ECDSA.should.be.a('function')
25
+ })
26
+
27
+ it('exposes package.json and ./version, and they agree', function () {
28
+ var pkg = require(N + '/package.json')
29
+ var version = require(N + '/version')
30
+ pkg.name.should.equal(N)
31
+ version.should.equal(pkg.version)
32
+ })
33
+
34
+ it('resolves lib/* deep imports with and without the .js extension', function () {
35
+ var withExt = require(N + '/lib/crypto/ecdsa.js')
36
+ var noExt = require(N + '/lib/crypto/ecdsa')
37
+ withExt.should.be.a('function')
38
+ noExt.should.equal(withExt) // same module, one cache entry
39
+ })
40
+
41
+ it('serves the full browser bundle over a subpath (loaded in isolation)', function () {
42
+ // The bundle is a self-contained bsv; loading it in-process collides with the
43
+ // main require above ("multiple bsv instances"), so resolve+load it in a child.
44
+ var script = 'var m=require(' + JSON.stringify(N + '/bsv.min.js') + ');' +
45
+ 'process.exit(m && m.PrivateKey ? 0 : 3)'
46
+ execFileSync(process.execPath, ['-e', script], { cwd: ROOT, stdio: 'pipe' })
47
+ })
48
+
49
+ it('routes the ESM `import` condition to index.mjs with default AND named exports', function () {
50
+ // Proves the dual-ESM entry end-to-end through the package specifier: the
51
+ // `import` condition resolves to index.mjs, which exposes real named bindings.
52
+ var script =
53
+ 'import bsv, { PrivateKey, Transaction, crypto } from ' + JSON.stringify(N) + ';' +
54
+ 'process.exit((bsv && bsv.PrivateKey && PrivateKey === bsv.PrivateKey && ' +
55
+ 'typeof Transaction === "function" && typeof crypto.ECDSA === "function") ? 0 : 3)'
56
+ execFileSync(process.execPath, ['--input-type=module', '-e', script], { cwd: ROOT, stdio: 'pipe' })
57
+ })
58
+ })
@@ -176,7 +176,7 @@ describe('ECDSA', function () {
176
176
  it('should create a valid signature', function () {
177
177
  ecdsa.randomK()
178
178
  ecdsa.sign()
179
- ecdsa.verify().verified.should.equal(true)
179
+ ecdsa.verify().should.equal(true)
180
180
  })
181
181
 
182
182
  it('should should throw an error if hashbuf is not 32 bytes', function () {
@@ -253,11 +253,36 @@ describe('ECDSA', function () {
253
253
  it('should verify a signature that was just signed', function () {
254
254
  ecdsa.sig = Signature.fromString('3046022100e9915e6236695f093a4128ac2a956c' +
255
255
  '40ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827')
256
- ecdsa.verify().verified.should.equal(true)
256
+ ecdsa.verify().should.equal(true)
257
+ })
258
+ // 7.0: verify() returns a strict boolean, not the (always-truthy) instance.
259
+ // A forged signature must be REJECTED by `if (ecdsa.verify())`, which was the
260
+ // whole point of removing the trap.
261
+ it('returns a strict boolean, not the instance (7.0 trap closed)', function () {
262
+ ecdsa.signRandomK()
263
+ var result = ecdsa.verify()
264
+ result.should.be.a('boolean')
265
+ result.should.equal(true)
266
+ // Forge the signature: the return value itself must be falsy.
267
+ var forged = new ECDSA()
268
+ forged.hashbuf = ecdsa.hashbuf
269
+ forged.pubkey = ecdsa.pubkey
270
+ forged.sig = new Signature(ecdsa.sig.r.add(new BN(1)), ecdsa.sig.s)
271
+ forged.verify().should.equal(false)
272
+ ;(!!forged.verify()).should.equal(false) // `if (forged.verify())` does NOT enter
273
+ })
274
+ it('verifyBool() remains a strict-boolean alias', function () {
275
+ ecdsa.signRandomK()
276
+ ecdsa.verifyBool().should.equal(true)
277
+ var forged = new ECDSA()
278
+ forged.hashbuf = ecdsa.hashbuf
279
+ forged.pubkey = ecdsa.pubkey
280
+ forged.sig = new Signature(ecdsa.sig.r.add(new BN(1)), ecdsa.sig.s)
281
+ forged.verifyBool().should.equal(false)
257
282
  })
258
283
  it('should verify this known good signature', function () {
259
284
  ecdsa.signRandomK()
260
- ecdsa.verify().verified.should.equal(true)
285
+ ecdsa.verify().should.equal(true)
261
286
  })
262
287
  it('should verify a valid signature, and unverify an invalid signature', function () {
263
288
  var sig = ECDSA.sign(ecdsa.hashbuf, ecdsa.privkey)
@@ -295,7 +320,7 @@ describe('ECDSA', function () {
295
320
  ecdsa2.k.toString().should.equal(ecdsa.k.toString())
296
321
  ecdsa2.sig.toString().should.equal(ecdsa.sig.toString())
297
322
  ecdsa2.sig.i.should.equal(ecdsa.sig.i)
298
- ecdsa.verify().verified.should.equal(true)
323
+ ecdsa.verify().should.equal(true)
299
324
  })
300
325
  })
301
326
 
@@ -27,6 +27,38 @@ describe('Mnemonic', function () {
27
27
  Mnemonic.Words.SPANISH.includes(mnemonic3.toString().split(' ')[1]).should.equal(true)
28
28
  Mnemonic.Words.SPANISH.includes(mnemonic3.toString().split(' ')[2]).should.equal(true)
29
29
  })
30
+
31
+ // Regression: the second argument used to be silently dropped, so the
32
+ // documented fromRandom(wordlist, 256) form returned a WEAK 12-word phrase.
33
+ function wordCount (m) { return m.toString().split(' ').length }
34
+
35
+ it('defaults to a 12-word (128-bit) phrase', function () {
36
+ wordCount(Mnemonic.fromRandom()).should.equal(12)
37
+ wordCount(Mnemonic.fromRandom(Mnemonic.Words.ENGLISH)).should.equal(12)
38
+ })
39
+
40
+ it('honours entropy passed as the second argument (wordlist, ent)', function () {
41
+ // The previously-broken documented form: must now yield 24 words.
42
+ wordCount(Mnemonic.fromRandom(Mnemonic.Words.ENGLISH, 256)).should.equal(24)
43
+ wordCount(Mnemonic.fromRandom(Mnemonic.Words.ENGLISH, 192)).should.equal(18)
44
+ wordCount(Mnemonic.fromRandom(Mnemonic.Words.ENGLISH, 160)).should.equal(15)
45
+ })
46
+
47
+ it('honours entropy passed as the first argument (ent) or (ent, wordlist)', function () {
48
+ wordCount(Mnemonic.fromRandom(256)).should.equal(24)
49
+ var es = Mnemonic.fromRandom(256, Mnemonic.Words.SPANISH)
50
+ wordCount(es).should.equal(24)
51
+ Mnemonic.Words.SPANISH.includes(es.toString().split(' ')[0]).should.equal(true)
52
+ })
53
+
54
+ it('throws on invalid entropy instead of silently degrading', function () {
55
+ ;(function () { Mnemonic.fromRandom(Mnemonic.Words.ENGLISH, 200) }).should.throw(/ENT/)
56
+ ;(function () { Mnemonic.fromRandom(64) }).should.throw(/ENT/)
57
+ })
58
+
59
+ it('reports a meaningful arity (2), not 0', function () {
60
+ Mnemonic.fromRandom.length.should.equal(2)
61
+ })
30
62
  })
31
63
 
32
64
  describe('# Mnemonic', function () {
@@ -49,15 +49,19 @@ describe('security: fail-closed verification contracts', function () {
49
49
  inst(wrongPub).verifyBool().should.equal(false)
50
50
  })
51
51
 
52
- // Locks the documented trap: instance .verify() returns the INSTANCE (truthy), so it
53
- // must never be read as a boolean. If this ever changes (e.g. a deliberate 7.0 that
54
- // makes verify() return a bool), this test forces the change to be intentional.
55
- it('instance .verify() returns the instance with the result on .verified (documented trap)', function () {
56
- var e = new ECDSA(); e.hashbuf = hash; e.sig = goodSig; e.pubkey = wrongPub
57
- var r = e.verify()
58
- ;(typeof r).should.equal('object') // NOT a boolean — the trap
59
- isStrictBool(r.verified).should.equal(true)
60
- r.verified.should.equal(false) // forged → verified is false, even though `r` is truthy
52
+ // 7.0: the trap is CLOSED. Instance .verify() now returns a STRICT boolean, so
53
+ // `if (ecdsa.verify())` correctly rejects a forgery. (Pre-7.0 it returned the
54
+ // truthy instance with the real result on .verified; that idiom is gone.)
55
+ it('instance .verify() returns a strict boolean and fails closed on a forgery', function () {
56
+ var good = new ECDSA(); good.hashbuf = hash; good.sig = goodSig; good.pubkey = rightPub
57
+ isStrictBool(good.verify()).should.equal(true)
58
+ good.verify().should.equal(true)
59
+
60
+ var forged = new ECDSA(); forged.hashbuf = hash; forged.sig = goodSig; forged.pubkey = wrongPub
61
+ isStrictBool(forged.verify()).should.equal(true)
62
+ forged.verify().should.equal(false) // forged → false, and `if (forged.verify())` does NOT enter
63
+ // The result is still mirrored on .verified as a side effect.
64
+ forged.verified.should.equal(false)
61
65
  })
62
66
  })
63
67
 
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 = '6.2.2'
3
+ module.exports = '7.0.1'