@smartledger/bsv 7.0.2 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +137 -0
- package/README.md +38 -38
- package/bsv-gdaf.min.js +46 -46
- package/bsv-ltp.min.js +46 -46
- package/bsv-smartcontract.min.js +15 -15
- package/bsv.bundle.js +46 -46
- package/bsv.min.js +46 -46
- package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
- package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
- package/docs/getting-started/INSTALLATION.md +23 -23
- package/docs/getting-started/QUICK_START.md +7 -7
- package/docs/migration/FROM_BSV_1_5_6.md +5 -5
- package/lib/crypto/ecdsa.js +24 -2
- package/lib/crypto/signature.js +33 -6
- package/lib/crypto/smartledger_verify.js +5 -6
- package/lib/ltp/claim.js +2 -3
- package/lib/ltp/obligation.js +2 -6
- package/lib/ltp/registry.js +7 -6
- package/lib/ltp/right.js +2 -6
- package/lib/util/id.js +48 -0
- package/package.json +1 -1
- package/test/build/esm_wrapper.js +10 -3
- package/test/crypto/ecdsa.js +73 -0
- package/test/crypto/security.js +51 -2
- package/test/crypto/signature.js +81 -0
- package/test/ltp/ids.js +74 -0
- package/test/script/script.js +4 -2
- package/test/util/id.js +72 -0
- package/version.js +1 -1
package/test/crypto/ecdsa.js
CHANGED
|
@@ -117,6 +117,79 @@ describe('ECDSA', function () {
|
|
|
117
117
|
})
|
|
118
118
|
})
|
|
119
119
|
|
|
120
|
+
describe('nonce reuse', function () {
|
|
121
|
+
// Signing two different messages under one nonce reveals the private key.
|
|
122
|
+
var N = point.getN()
|
|
123
|
+
var h1 = Hash.sha256(Buffer.from('pay alice 1 coin'))
|
|
124
|
+
var h2 = Hash.sha256(Buffer.from('pay bob 2 coins'))
|
|
125
|
+
|
|
126
|
+
var freshSigner = function () {
|
|
127
|
+
return ECDSA().set({
|
|
128
|
+
privkey: new Privkey(BN.fromBuffer(Buffer.from(
|
|
129
|
+
'fee0a1f7afebf9d2a5a80c0c98a31c709681cce195cbcd06342b517970c0be1e', 'hex')))
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
it('does not reuse k when one instance signs two different messages', function () {
|
|
134
|
+
var e = freshSigner()
|
|
135
|
+
var sig1 = e.set({ hashbuf: h1 }).sign().sig
|
|
136
|
+
var k1 = e.k
|
|
137
|
+
var sig2 = e.set({ hashbuf: h2 }).sign().sig
|
|
138
|
+
var k2 = e.k;
|
|
139
|
+
(k1.cmp(k2) === 0).should.equal(false)
|
|
140
|
+
sig1.r.eq(sig2.r).should.equal(false)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('does not reuse k across signRandomK calls on one instance', function () {
|
|
144
|
+
var e = freshSigner()
|
|
145
|
+
var sig1 = e.set({ hashbuf: h1 }).signRandomK().sig
|
|
146
|
+
var sig2 = e.set({ hashbuf: h2 }).sign().sig
|
|
147
|
+
sig1.r.eq(sig2.r).should.equal(false)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('does not leak the private key from two signatures by one instance', function () {
|
|
151
|
+
var e = freshSigner()
|
|
152
|
+
var d = e.privkey.bn
|
|
153
|
+
var sig1 = e.set({ hashbuf: h1 }).sign().sig
|
|
154
|
+
var sig2 = e.set({ hashbuf: h2 }).sign().sig
|
|
155
|
+
|
|
156
|
+
// toLowS may have flipped either s, so try every sign combination.
|
|
157
|
+
var red = BN.red(N)
|
|
158
|
+
var inv = function (x) { return x.toRed(red).redInvm().fromRed() }
|
|
159
|
+
var mul = function (a, b) { return a.toRed(red).redMul(b.toRed(red)).fromRed() }
|
|
160
|
+
var e1 = BN.fromBuffer(h1)
|
|
161
|
+
var e2 = BN.fromBuffer(h2)
|
|
162
|
+
var recovered = false
|
|
163
|
+
;[1, -1].forEach(function (f1) {
|
|
164
|
+
;[1, -1].forEach(function (f2) {
|
|
165
|
+
var S1 = f1 === 1 ? sig1.s : N.sub(sig1.s)
|
|
166
|
+
var S2 = f2 === 1 ? sig2.s : N.sub(sig2.s)
|
|
167
|
+
var diff = S1.sub(S2).umod(N)
|
|
168
|
+
if (diff.isZero()) return
|
|
169
|
+
var k = mul(e1.sub(e2).umod(N), inv(diff))
|
|
170
|
+
var guess = mul(S1.mul(k).umod(N).sub(e1).umod(N), inv(sig1.r))
|
|
171
|
+
if (guess.toString(16) === d.toString(16)) recovered = true
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
recovered.should.equal(false)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('still honours an explicitly supplied k for a single signature', function () {
|
|
178
|
+
var k = new BN('114860389168127852803919605627759231199925249596762615988727970217268189974335', 10)
|
|
179
|
+
var a = freshSigner().set({ hashbuf: h1, k: k })
|
|
180
|
+
var b = freshSigner().set({ hashbuf: h1, k: k })
|
|
181
|
+
a.sign().sig.r.eq(b.sign().sig.r).should.equal(true)
|
|
182
|
+
a.k.eq(k).should.equal(true)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('signs the same message deterministically across instances', function () {
|
|
186
|
+
var a = freshSigner().set({ hashbuf: h1 }).sign().sig
|
|
187
|
+
var b = freshSigner().set({ hashbuf: h1 }).sign().sig
|
|
188
|
+
a.r.eq(b.r).should.equal(true)
|
|
189
|
+
a.s.eq(b.s).should.equal(true)
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
120
193
|
describe('#toPublicKey', function () {
|
|
121
194
|
it('should calculate the correct public key', function () {
|
|
122
195
|
ecdsa.k = new BN('114860389168127852803919605627759231199925249596762615988727970217268189974335', 10)
|
package/test/crypto/security.js
CHANGED
|
@@ -69,6 +69,52 @@ describe('SmartLedger Security Patches', function () {
|
|
|
69
69
|
// change which key/message the signature is valid for.
|
|
70
70
|
canonical.r.cmp(highS.r).should.equal(0)
|
|
71
71
|
})
|
|
72
|
+
|
|
73
|
+
it('toCanonical() returns a new signature and leaves the original alone', function () {
|
|
74
|
+
var sig = ECDSA.sign(hash, privateKey)
|
|
75
|
+
var n = Point.getN()
|
|
76
|
+
var lowS = sig.s.lte(n.shrn(1)) ? sig.s : n.sub(sig.s)
|
|
77
|
+
var highS = new Signature({ r: sig.r, s: n.sub(lowS) })
|
|
78
|
+
var before = highS.s.toString()
|
|
79
|
+
|
|
80
|
+
var canonical = highS.toCanonical()
|
|
81
|
+
canonical.should.not.equal(highS)
|
|
82
|
+
highS.s.toString().should.equal(before)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('applySecurityPatches', function () {
|
|
87
|
+
var highSig = function () {
|
|
88
|
+
var sig = ECDSA.sign(hash, privateKey)
|
|
89
|
+
var n = Point.getN()
|
|
90
|
+
var lowS = sig.s.lte(n.shrn(1)) ? sig.s : n.sub(sig.s)
|
|
91
|
+
return new Signature({ r: sig.r, s: n.sub(lowS) })
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
it('accepts a canonical signature and returns it for chaining', function () {
|
|
95
|
+
var sig = ECDSA.sign(hash, privateKey)
|
|
96
|
+
sig.applySecurityPatches().should.equal(sig)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('rejects a non-canonical (high-S) signature rather than rewriting it', function () {
|
|
100
|
+
// Rewriting s to n-s cannot protect against malleability: the rewritten
|
|
101
|
+
// signature verifies exactly as the original, so silently normalizing only
|
|
102
|
+
// hides from the caller that the signature was malleated.
|
|
103
|
+
var highS = highSig()
|
|
104
|
+
var before = highS.s.toString()
|
|
105
|
+
|
|
106
|
+
expect(function () { highS.applySecurityPatches() }).to.throw(/non-canonical/)
|
|
107
|
+
highS.s.toString().should.equal(before)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('rejects zero and out-of-range components', function () {
|
|
111
|
+
expect(function () {
|
|
112
|
+
new Signature({ r: new BN(0), s: new BN(1) }).applySecurityPatches()
|
|
113
|
+
}).to.throw(/zero r or s/)
|
|
114
|
+
expect(function () {
|
|
115
|
+
new Signature({ r: new BN(1), s: Point.getN() }).applySecurityPatches()
|
|
116
|
+
}).to.throw(/out of range/)
|
|
117
|
+
})
|
|
72
118
|
})
|
|
73
119
|
|
|
74
120
|
describe('SmartVerify Enhanced Validation', function () {
|
|
@@ -77,12 +123,15 @@ describe('SmartLedger Security Patches', function () {
|
|
|
77
123
|
SmartVerify.smartVerify(hash, sig, publicKey).should.equal(true)
|
|
78
124
|
})
|
|
79
125
|
|
|
80
|
-
it('
|
|
126
|
+
it('rejects a malleated (high-S) signature', function () {
|
|
81
127
|
var sig = ECDSA.sign(hash, privateKey)
|
|
82
128
|
var n = Point.getN()
|
|
83
129
|
var lowS = sig.s.lte(n.shrn(1)) ? sig.s : n.sub(sig.s)
|
|
84
130
|
var highS = new Signature({ r: sig.r, s: n.sub(lowS) })
|
|
85
|
-
SmartVerify.
|
|
131
|
+
SmartVerify.isCanonical(highS).should.equal(false)
|
|
132
|
+
SmartVerify.smartVerify(hash, highS, publicKey).should.equal(false)
|
|
133
|
+
// ...while the canonical form of the same signature still verifies.
|
|
134
|
+
SmartVerify.smartVerify(hash, new Signature({ r: sig.r, s: lowS }), publicKey).should.equal(true)
|
|
86
135
|
})
|
|
87
136
|
|
|
88
137
|
it('throws on an invalid (non-32-byte) message hash', function () {
|
package/test/crypto/signature.js
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
var _ = require('../../lib/util/_')
|
|
4
4
|
var should = require('chai').should()
|
|
5
|
+
var expect = require('chai').expect
|
|
5
6
|
var bsv = require('../..')
|
|
6
7
|
var BN = bsv.crypto.BN
|
|
7
8
|
var Signature = bsv.crypto.Signature
|
|
9
|
+
var ECDSA = bsv.crypto.ECDSA
|
|
10
|
+
var Hash = bsv.crypto.Hash
|
|
11
|
+
var PrivateKey = bsv.PrivateKey
|
|
8
12
|
var JSUtil = bsv.util.js
|
|
9
13
|
var Interpreter = bsv.Script.Interpreter
|
|
10
14
|
|
|
@@ -189,6 +193,83 @@ describe('Signature', function () {
|
|
|
189
193
|
var parsed = Signature.parseDER(sig, false)
|
|
190
194
|
should.exist(parsed)
|
|
191
195
|
})
|
|
196
|
+
|
|
197
|
+
it('reports sneg for a padded s', function () {
|
|
198
|
+
// 02 21 00ab.. — s carries the pad that clears its high bit.
|
|
199
|
+
var sighex = '304502203e4516da7253cf068effec6b95c41221c0cf3a8e6ccb8cbf1725b562e9afde2c' +
|
|
200
|
+
'022100ab1e3da73d67e32045a20e0b999e049978ea8d6ee5480d485fcf2ce0d03b2ef0'
|
|
201
|
+
var parsed = Signature.parseDER(Buffer.from(sighex, 'hex'))
|
|
202
|
+
parsed.sbuf[0].should.equal(0x00)
|
|
203
|
+
parsed.sneg.should.equal(true)
|
|
204
|
+
parsed.rneg.should.equal(false)
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
describe('#parseDER canonicality', function () {
|
|
209
|
+
// A non-canonical INTEGER is a second encoding of the same number, i.e. a
|
|
210
|
+
// malleable signature. Strict (the default, used by fromDER/fromString and
|
|
211
|
+
// so by credential/proof verification) rejects them; non-strict must keep
|
|
212
|
+
// accepting them, because signatures already on chain are not canonical and
|
|
213
|
+
// fromTxFormat has to parse them.
|
|
214
|
+
var cases = [
|
|
215
|
+
{ name: 'r negative (high bit set, unpadded)', hex: '3006' + '0201ff' + '020101', err: /Value of r is negative/ },
|
|
216
|
+
{ name: 's negative (high bit set, unpadded)', hex: '3006' + '020101' + '0201ff', err: /Value of s is negative/ },
|
|
217
|
+
{ name: 'r excessively padded', hex: '3007' + '02020011' + '020101', err: /Value of r is excessively padded/ },
|
|
218
|
+
{ name: 's excessively padded', hex: '3007' + '020101' + '02020011', err: /Value of s is excessively padded/ },
|
|
219
|
+
{ name: 'r zero-length', hex: '3005' + '0200' + '020101', err: /Length of r is zero/ },
|
|
220
|
+
{ name: 's zero-length', hex: '3005' + '020101' + '0200', err: /Length of s is zero/ }
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
cases.forEach(function (c) {
|
|
224
|
+
it('strict rejects ' + c.name, function () {
|
|
225
|
+
expect(function () {
|
|
226
|
+
Signature.parseDER(Buffer.from(c.hex, 'hex'))
|
|
227
|
+
}).to.throw(c.err)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('non-strict still accepts ' + c.name + ' (chain compatibility)', function () {
|
|
231
|
+
should.exist(Signature.parseDER(Buffer.from(c.hex, 'hex'), false))
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('strict accepts a minimally padded integer', function () {
|
|
236
|
+
// 0x00ff is canonical: the pad is required to clear the high bit.
|
|
237
|
+
should.exist(Signature.parseDER(Buffer.from('3008020200ff020200ff', 'hex')))
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('strict accepts a canonical zero integer', function () {
|
|
241
|
+
should.exist(Signature.parseDER(Buffer.from('3006020100020100', 'hex')))
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('rejects a malleated re-encoding while the original still verifies', function () {
|
|
245
|
+
var privkey = new PrivateKey()
|
|
246
|
+
var hashbuf = Hash.sha256(Buffer.from('der canonicality'))
|
|
247
|
+
var sig = ECDSA.sign(hashbuf, privkey)
|
|
248
|
+
var der = sig.toDER()
|
|
249
|
+
|
|
250
|
+
ECDSA.verify(hashbuf, Signature.fromDER(der), privkey.toPublicKey()).should.equal(true)
|
|
251
|
+
|
|
252
|
+
// Re-encode r with one pad byte more than DER allows: same r, different
|
|
253
|
+
// bytes. The extra zero goes on top of the canonical encoding, so this is
|
|
254
|
+
// excess padding whether or not r needed a pad to begin with.
|
|
255
|
+
var canonical = function (nbuf) {
|
|
256
|
+
return (nbuf[0] & 0x80) ? Buffer.concat([Buffer.from([0x00]), nbuf]) : nbuf
|
|
257
|
+
}
|
|
258
|
+
var padded = Buffer.concat([Buffer.from([0x00]), canonical(sig.r.toBuffer())])
|
|
259
|
+
var sEnc = canonical(sig.s.toBuffer())
|
|
260
|
+
var body = Buffer.concat([
|
|
261
|
+
Buffer.from([0x02, padded.length]), padded,
|
|
262
|
+
Buffer.from([0x02, sEnc.length]), sEnc
|
|
263
|
+
])
|
|
264
|
+
var malleated = Buffer.concat([Buffer.from([0x30, body.length]), body])
|
|
265
|
+
|
|
266
|
+
malleated.toString('hex').should.not.equal(der.toString('hex'))
|
|
267
|
+
expect(function () {
|
|
268
|
+
Signature.fromDER(malleated)
|
|
269
|
+
}).to.throw(/excessively padded/)
|
|
270
|
+
// The same bytes remain parseable on the non-strict (chain) path.
|
|
271
|
+
Signature.parseDER(malleated, false).r.eq(sig.r).should.equal(true)
|
|
272
|
+
})
|
|
192
273
|
})
|
|
193
274
|
|
|
194
275
|
describe('#toDER', function () {
|
package/test/ltp/ids.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/* global describe, it */
|
|
4
|
+
|
|
5
|
+
require('chai').should()
|
|
6
|
+
var expect = require('chai').expect
|
|
7
|
+
|
|
8
|
+
var bsv = require('../../index.js')
|
|
9
|
+
|
|
10
|
+
var UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
|
|
11
|
+
|
|
12
|
+
// A token id is covered by the credential's signature, keys the registry's
|
|
13
|
+
// registration/revocation maps, and feeds proof material — so it must be
|
|
14
|
+
// unpredictable, not merely distinct.
|
|
15
|
+
describe('LTP identifiers', function () {
|
|
16
|
+
var generators = [
|
|
17
|
+
{ name: 'Right._generateUUID', fn: function () { return bsv.LTP.Right._generateUUID() }, format: UUID_V4 },
|
|
18
|
+
{ name: 'Obligation._generateUUID', fn: function () { return bsv.LTP.Obligation._generateUUID() }, format: UUID_V4 },
|
|
19
|
+
{ name: 'Registry._generateRegistryId', fn: function () { return bsv.LTP.Registry._generateRegistryId() }, format: /^reg_[0-9a-f]{16}$/ },
|
|
20
|
+
{ name: 'Registry._generateAuditId', fn: function () { return bsv.LTP.Registry._generateAuditId() }, format: /^audit_[0-9a-f]{32}$/ },
|
|
21
|
+
{ name: 'Claim._generateBatchId', fn: function () { return bsv.LTP.Claim._generateBatchId() }, format: /^[0-9a-f]{16}$/ }
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
var withFrozenMathRandom = function (fn) {
|
|
25
|
+
var original = Math.random
|
|
26
|
+
Math.random = function () { return 0.5 }
|
|
27
|
+
try { return fn() } finally { Math.random = original }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
generators.forEach(function (g) {
|
|
31
|
+
describe(g.name, function () {
|
|
32
|
+
it('keeps its documented format', function () {
|
|
33
|
+
g.fn().should.match(g.format)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('does not repeat', function () {
|
|
37
|
+
var seen = {}
|
|
38
|
+
for (var i = 0; i < 500; i++) {
|
|
39
|
+
var id = g.fn()
|
|
40
|
+
expect(seen[id], 'duplicate id: ' + id).to.equal(undefined)
|
|
41
|
+
seen[id] = true
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('does not draw from the engine PRNG', function () {
|
|
46
|
+
withFrozenMathRandom(function () {
|
|
47
|
+
var seen = {}
|
|
48
|
+
for (var i = 0; i < 100; i++) {
|
|
49
|
+
var id = g.fn()
|
|
50
|
+
expect(seen[id], 'id is predictable from the engine PRNG: ' + id).to.equal(undefined)
|
|
51
|
+
seen[id] = true
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('issues unpredictable ids on real tokens', function () {
|
|
59
|
+
var type = Object.values(bsv.LTP.Right.RightTypes)[0]
|
|
60
|
+
var ids = {}
|
|
61
|
+
for (var i = 0; i < 25; i++) {
|
|
62
|
+
var token = bsv.LTP.Right.create(
|
|
63
|
+
type,
|
|
64
|
+
'did:example:issuer',
|
|
65
|
+
'did:example:subject',
|
|
66
|
+
{ description: 'test right' }
|
|
67
|
+
)
|
|
68
|
+
token.id.should.match(/^urn:uuid:/)
|
|
69
|
+
token.id.replace('urn:uuid:', '').should.match(UUID_V4)
|
|
70
|
+
expect(ids[token.id], 'duplicate token id').to.equal(undefined)
|
|
71
|
+
ids[token.id] = true
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
})
|
package/test/script/script.js
CHANGED
|
@@ -1237,8 +1237,10 @@ describe('Script', function () {
|
|
|
1237
1237
|
Script(s2).getSignatureOperationsCount(false).should.equal(20)
|
|
1238
1238
|
})
|
|
1239
1239
|
it('should handle P2SH-multisig-in scripts from utility', function () {
|
|
1240
|
-
// create a well-formed signature, does not need to match pubkeys
|
|
1241
|
-
|
|
1240
|
+
// create a well-formed signature, does not need to match pubkeys.
|
|
1241
|
+
// r and s are 0x00ff: the pad is required, as a bare 0xff would have its
|
|
1242
|
+
// high bit set and so would not be a canonical DER integer.
|
|
1243
|
+
var signature = bsv.crypto.Signature.fromString('3008020200FF020200FF')
|
|
1242
1244
|
var signatures = [ signature.toBuffer() ]
|
|
1243
1245
|
var p2sh = Script.buildP2SHMultisigIn(pubKeyHexes, 1, signatures, {})
|
|
1244
1246
|
p2sh.getSignatureOperationsCount(true).should.equal(0)
|
package/test/util/id.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/* global describe, it */
|
|
4
|
+
|
|
5
|
+
require('chai').should()
|
|
6
|
+
var expect = require('chai').expect
|
|
7
|
+
|
|
8
|
+
var Id = require('../../lib/util/id')
|
|
9
|
+
|
|
10
|
+
var UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
|
|
11
|
+
|
|
12
|
+
describe('util/id', function () {
|
|
13
|
+
describe('#uuid4', function () {
|
|
14
|
+
it('produces a well-formed RFC 4122 v4 uuid', function () {
|
|
15
|
+
Id.uuid4().should.match(UUID_V4)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('sets the version and variant bits on every draw', function () {
|
|
19
|
+
for (var i = 0; i < 200; i++) {
|
|
20
|
+
Id.uuid4().should.match(UUID_V4)
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('does not repeat', function () {
|
|
25
|
+
var seen = {}
|
|
26
|
+
for (var i = 0; i < 2000; i++) {
|
|
27
|
+
var id = Id.uuid4()
|
|
28
|
+
expect(seen[id], 'duplicate uuid: ' + id).to.equal(undefined)
|
|
29
|
+
seen[id] = true
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('#randomHex', function () {
|
|
35
|
+
it('returns the requested number of hex characters', function () {
|
|
36
|
+
Id.randomHex(16).should.match(/^[0-9a-f]{16}$/)
|
|
37
|
+
Id.randomHex(12).should.match(/^[0-9a-f]{12}$/)
|
|
38
|
+
Id.randomHex(1).should.match(/^[0-9a-f]$/)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('does not repeat', function () {
|
|
42
|
+
var seen = {}
|
|
43
|
+
for (var i = 0; i < 2000; i++) {
|
|
44
|
+
var id = Id.randomHex(16)
|
|
45
|
+
expect(seen[id], 'duplicate id: ' + id).to.equal(undefined)
|
|
46
|
+
seen[id] = true
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('entropy source', function () {
|
|
52
|
+
// Identifiers must come from the CSPRNG. Freezing the engine PRNG must not
|
|
53
|
+
// affect them; if it does, they are predictable from observed output.
|
|
54
|
+
var withFrozenMathRandom = function (fn) {
|
|
55
|
+
var original = Math.random
|
|
56
|
+
Math.random = function () { return 0.5 }
|
|
57
|
+
try { return fn() } finally { Math.random = original }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
it('uuid4 is unaffected by the engine PRNG', function () {
|
|
61
|
+
withFrozenMathRandom(function () {
|
|
62
|
+
Id.uuid4().should.not.equal(Id.uuid4())
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('randomHex is unaffected by the engine PRNG', function () {
|
|
67
|
+
withFrozenMathRandom(function () {
|
|
68
|
+
Id.randomHex(16).should.not.equal(Id.randomHex(16))
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
})
|
package/version.js
CHANGED