@bsv/sdk 1.9.18 → 1.9.20
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/dist/cjs/package.json +1 -1
- package/dist/cjs/src/auth/certificates/MasterCertificate.js +9 -2
- package/dist/cjs/src/auth/certificates/MasterCertificate.js.map +1 -1
- package/dist/cjs/src/primitives/DRBG.js +11 -6
- package/dist/cjs/src/primitives/DRBG.js.map +1 -1
- package/dist/cjs/src/primitives/ECDSA.js +1 -1
- package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
- package/dist/cjs/src/primitives/Hash.js +6 -5
- package/dist/cjs/src/primitives/Hash.js.map +1 -1
- package/dist/cjs/src/primitives/hex.js +33 -0
- package/dist/cjs/src/primitives/hex.js.map +1 -0
- package/dist/cjs/src/primitives/utils.js +69 -59
- package/dist/cjs/src/primitives/utils.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/auth/certificates/MasterCertificate.js +9 -2
- package/dist/esm/src/auth/certificates/MasterCertificate.js.map +1 -1
- package/dist/esm/src/primitives/DRBG.js +11 -6
- package/dist/esm/src/primitives/DRBG.js.map +1 -1
- package/dist/esm/src/primitives/ECDSA.js +1 -1
- package/dist/esm/src/primitives/ECDSA.js.map +1 -1
- package/dist/esm/src/primitives/Hash.js +6 -5
- package/dist/esm/src/primitives/Hash.js.map +1 -1
- package/dist/esm/src/primitives/hex.js +29 -0
- package/dist/esm/src/primitives/hex.js.map +1 -0
- package/dist/esm/src/primitives/utils.js +69 -59
- package/dist/esm/src/primitives/utils.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/auth/certificates/MasterCertificate.d.ts.map +1 -1
- package/dist/types/src/primitives/DRBG.d.ts +2 -1
- package/dist/types/src/primitives/DRBG.d.ts.map +1 -1
- package/dist/types/src/primitives/Hash.d.ts.map +1 -1
- package/dist/types/src/primitives/hex.d.ts +3 -0
- package/dist/types/src/primitives/hex.d.ts.map +1 -0
- package/dist/types/src/primitives/utils.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +3 -3
- package/dist/umd/bundle.js.map +1 -1
- package/docs/reference/primitives.md +2 -2
- package/package.json +1 -1
- package/src/auth/__tests/Peer.test.ts +2 -1
- package/src/auth/certificates/MasterCertificate.ts +9 -2
- package/src/auth/certificates/__tests/MasterCertificate.test.ts +46 -9
- package/src/primitives/DRBG.ts +14 -7
- package/src/primitives/ECDSA.ts +1 -1
- package/src/primitives/Hash.ts +9 -6
- package/src/primitives/__tests/DRBG.test.ts +272 -9
- package/src/primitives/__tests/ECDSA.test.ts +29 -0
- package/src/primitives/__tests/HMAC.test.ts +13 -2
- package/src/primitives/__tests/Hash.test.ts +24 -0
- package/src/primitives/__tests/hex.test.ts +57 -0
- package/src/primitives/__tests/utils.test.ts +39 -0
- package/src/primitives/hex.ts +35 -0
- package/src/primitives/utils.ts +71 -65
- package/src/script/__tests/Script.test.ts +1 -1
|
@@ -843,7 +843,7 @@ export default class DRBG {
|
|
|
843
843
|
V: number[];
|
|
844
844
|
constructor(entropy: number[] | string, nonce: number[] | string)
|
|
845
845
|
hmac(): SHA256HMAC
|
|
846
|
-
update(seed
|
|
846
|
+
update(seed?: number[]): void
|
|
847
847
|
generate(len: number): string
|
|
848
848
|
}
|
|
849
849
|
```
|
|
@@ -899,7 +899,7 @@ Updates the `K` and `V` values of the instance based on the seed.
|
|
|
899
899
|
The seed if not provided uses `V` as seed.
|
|
900
900
|
|
|
901
901
|
```ts
|
|
902
|
-
update(seed
|
|
902
|
+
update(seed?: number[]): void
|
|
903
903
|
```
|
|
904
904
|
|
|
905
905
|
Returns
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@ import { SimplifiedFetchTransport } from '../../auth/transports/SimplifiedFetchT
|
|
|
12
12
|
const certifierPrivKey = new PrivateKey(21)
|
|
13
13
|
const alicePrivKey = new PrivateKey(22)
|
|
14
14
|
const bobPrivKey = new PrivateKey(23)
|
|
15
|
+
const DUMMY_REVOCATION_OUTPOINT_HEX = '00'.repeat(36)
|
|
15
16
|
|
|
16
17
|
jest.mock('../../auth/utils/getVerifiableCertificates')
|
|
17
18
|
|
|
@@ -101,7 +102,7 @@ describe('Peer class mutual authentication and certificate exchange', () => {
|
|
|
101
102
|
subjectPubKey,
|
|
102
103
|
fields,
|
|
103
104
|
certificateType,
|
|
104
|
-
async () =>
|
|
105
|
+
async () => DUMMY_REVOCATION_OUTPOINT_HEX
|
|
105
106
|
)
|
|
106
107
|
|
|
107
108
|
// For test consistency, you could override the auto-generated serialNumber:
|
|
@@ -232,7 +232,7 @@ export class MasterCertificate extends Certificate {
|
|
|
232
232
|
certificateType: string,
|
|
233
233
|
getRevocationOutpoint = async (_serial: string): Promise<string> => {
|
|
234
234
|
void _serial // Explicitly acknowledge unused parameter
|
|
235
|
-
return '
|
|
235
|
+
return '00'.repeat(32)
|
|
236
236
|
},
|
|
237
237
|
serialNumber?: string
|
|
238
238
|
): Promise<MasterCertificate> {
|
|
@@ -246,11 +246,18 @@ export class MasterCertificate extends Certificate {
|
|
|
246
246
|
// 3. Obtain a revocation outpoint
|
|
247
247
|
const revocationOutpoint = await getRevocationOutpoint(finalSerialNumber)
|
|
248
248
|
|
|
249
|
+
let subjectIdentityKey: string
|
|
250
|
+
if (subject === 'self') {
|
|
251
|
+
subjectIdentityKey = (await certifierWallet.getPublicKey({ identityKey: true })).publicKey
|
|
252
|
+
} else {
|
|
253
|
+
subjectIdentityKey = subject
|
|
254
|
+
}
|
|
255
|
+
|
|
249
256
|
// 4. Create new MasterCertificate instance
|
|
250
257
|
const certificate = new MasterCertificate(
|
|
251
258
|
certificateType,
|
|
252
259
|
finalSerialNumber,
|
|
253
|
-
|
|
260
|
+
subjectIdentityKey,
|
|
254
261
|
(await certifierWallet.getPublicKey({ identityKey: true })).publicKey,
|
|
255
262
|
revocationOutpoint,
|
|
256
263
|
certificateFields,
|
|
@@ -14,7 +14,8 @@ const verifierKey2 = new PrivateKey(81)
|
|
|
14
14
|
|
|
15
15
|
// A mock revocation outpoint for testing
|
|
16
16
|
const mockRevocationOutpoint =
|
|
17
|
-
'
|
|
17
|
+
'deadbeefdeadbeefdeadbeefdeadbeef00000001'
|
|
18
|
+
|
|
18
19
|
|
|
19
20
|
// Arbitrary certificate data (in plaintext)
|
|
20
21
|
const plaintextFields = {
|
|
@@ -355,33 +356,69 @@ describe('MasterCertificate', () => {
|
|
|
355
356
|
expect(newCert.fields[fieldName]).toMatch(/^[A-Za-z0-9+/]+=*$/)
|
|
356
357
|
}
|
|
357
358
|
})
|
|
359
|
+
|
|
358
360
|
it('should allow issuing a self-signed certificate and decrypt it with the same wallet', async () => {
|
|
359
|
-
// In a self-signed scenario, the subject and certifier are the same
|
|
360
361
|
const subjectWallet = new CompletedProtoWallet(subjectKey2)
|
|
361
362
|
|
|
362
|
-
// Some sample fields
|
|
363
363
|
const selfSignedFields = {
|
|
364
364
|
owner: 'Bob',
|
|
365
365
|
organization: 'SelfCo'
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
-
|
|
368
|
+
const subjectIdentityKey = (
|
|
369
|
+
await subjectWallet.getPublicKey({ identityKey: true })
|
|
370
|
+
).publicKey
|
|
371
|
+
|
|
372
|
+
// Issue the certificate: subject = actual identity key (valid hex)
|
|
369
373
|
const selfSignedCert = await MasterCertificate.issueCertificateForSubject(
|
|
370
|
-
subjectWallet,
|
|
371
|
-
'self',
|
|
374
|
+
subjectWallet, // acts as certifier
|
|
375
|
+
subjectIdentityKey, // <-- was 'self', now real hex
|
|
372
376
|
selfSignedFields,
|
|
373
377
|
'SELF_SIGNED_TEST'
|
|
374
378
|
)
|
|
375
379
|
|
|
376
|
-
//
|
|
380
|
+
// Decrypt with the same wallet
|
|
377
381
|
const decrypted = await MasterCertificate.decryptFields(
|
|
378
382
|
subjectWallet,
|
|
379
383
|
selfSignedCert.masterKeyring,
|
|
380
384
|
selfSignedCert.fields,
|
|
381
|
-
'self'
|
|
385
|
+
'self' // still fine here if decryptFields treats 'self' specially
|
|
382
386
|
)
|
|
383
387
|
|
|
384
388
|
expect(decrypted).toEqual(selfSignedFields)
|
|
385
389
|
})
|
|
390
|
+
|
|
391
|
+
it('resolves subject === "self" to the certifier wallet identity key', async () => {
|
|
392
|
+
const certifierWallet = new CompletedProtoWallet(new PrivateKey(99))
|
|
393
|
+
|
|
394
|
+
const certifierIdentityKey = (
|
|
395
|
+
await certifierWallet.getPublicKey({ identityKey: true })
|
|
396
|
+
).publicKey
|
|
397
|
+
|
|
398
|
+
const cert = await MasterCertificate.issueCertificateForSubject(
|
|
399
|
+
certifierWallet,
|
|
400
|
+
'self',
|
|
401
|
+
{ name: 'Alice' },
|
|
402
|
+
'TEST_CERT'
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
expect(cert.subject).toBe(certifierIdentityKey)
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
it('uses provided subjectIdentityKey when subject is a valid hex string', async () => {
|
|
409
|
+
const certifierWallet = new CompletedProtoWallet(new PrivateKey(42))
|
|
410
|
+
|
|
411
|
+
const validPubkey =
|
|
412
|
+
'0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798'
|
|
413
|
+
|
|
414
|
+
const cert = await MasterCertificate.issueCertificateForSubject(
|
|
415
|
+
certifierWallet,
|
|
416
|
+
validPubkey,
|
|
417
|
+
{ name: 'Alice' },
|
|
418
|
+
'TEST_CERT'
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
expect(cert.subject).toBe(validPubkey)
|
|
422
|
+
})
|
|
386
423
|
})
|
|
387
|
-
})
|
|
424
|
+
})
|
package/src/primitives/DRBG.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { toHex, toArray } from './utils.js'
|
|
|
10
10
|
* @param nonce - Initial nonce either in number array or hexadecimal string.
|
|
11
11
|
*
|
|
12
12
|
* @throws Throws an error message 'Not enough entropy. Minimum is 256 bits' when entropy's length is less than 32.
|
|
13
|
+
* @throws Thrown an error message 'Nonce must be exactly 32 bytes (256 bits)' when nonce's length is less than 32.
|
|
13
14
|
*
|
|
14
15
|
* @example
|
|
15
16
|
* const drbg = new DRBG('af12de...', '123ef...');
|
|
@@ -19,13 +20,18 @@ export default class DRBG {
|
|
|
19
20
|
V: number[]
|
|
20
21
|
|
|
21
22
|
constructor (entropy: number[] | string, nonce: number[] | string) {
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const entropyBytes = toArray(entropy, 'hex')
|
|
24
|
+
const nonceBytes = toArray(nonce, 'hex')
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
// RFC 6979 for secp256k1 assumes 256-bit x and h1.
|
|
27
|
+
if (entropyBytes.length !== 32) {
|
|
28
|
+
throw new Error('Entropy must be exactly 32 bytes (256 bits)')
|
|
27
29
|
}
|
|
28
|
-
|
|
30
|
+
if (nonceBytes.length !== 32) {
|
|
31
|
+
throw new Error('Nonce must be exactly 32 bytes (256 bits)')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const seedMaterial = entropyBytes.concat(nonceBytes)
|
|
29
35
|
|
|
30
36
|
this.K = new Array(32)
|
|
31
37
|
this.V = new Array(32)
|
|
@@ -33,7 +39,8 @@ export default class DRBG {
|
|
|
33
39
|
this.K[i] = 0x00
|
|
34
40
|
this.V[i] = 0x01
|
|
35
41
|
}
|
|
36
|
-
|
|
42
|
+
|
|
43
|
+
this.update(seedMaterial)
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
/**
|
|
@@ -60,7 +67,7 @@ export default class DRBG {
|
|
|
60
67
|
* @example
|
|
61
68
|
* drbg.update('e13af...');
|
|
62
69
|
*/
|
|
63
|
-
update (seed
|
|
70
|
+
update (seed?: number[]): void {
|
|
64
71
|
let kmac = this.hmac().update(this.V).update([0x00])
|
|
65
72
|
if (seed !== undefined) {
|
|
66
73
|
kmac = kmac.update(seed)
|
package/src/primitives/ECDSA.ts
CHANGED
|
@@ -87,7 +87,7 @@ export const sign = (
|
|
|
87
87
|
if (kBN == null) throw new Error('k is undefined')
|
|
88
88
|
kBN = truncateToN(kBN, true)
|
|
89
89
|
|
|
90
|
-
if (kBN.cmpn(1)
|
|
90
|
+
if (kBN.cmpn(1) < 0 || kBN.cmp(ns1) > 0) {
|
|
91
91
|
if (BigNumber.isBN(customK)) {
|
|
92
92
|
throw new Error('Invalid fixed custom K value (must be >1 and <N‑1)')
|
|
93
93
|
}
|
package/src/primitives/Hash.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
// @ts-nocheck
|
|
3
3
|
/* eslint-disable @typescript-eslint/naming-convention */
|
|
4
|
+
import { assertValidHex, normalizeHex } from './hex.js'
|
|
5
|
+
|
|
4
6
|
const assert = (
|
|
5
7
|
expression: unknown,
|
|
6
8
|
message: string = 'Hash assertion failed'
|
|
@@ -169,6 +171,10 @@ abstract class BaseHash {
|
|
|
169
171
|
*/
|
|
170
172
|
private _pad (): number[] {
|
|
171
173
|
const len = this.pendingTotal
|
|
174
|
+
if (!Number.isSafeInteger(len) || len < 0) {
|
|
175
|
+
throw new Error('Message too long for this hash function')
|
|
176
|
+
}
|
|
177
|
+
|
|
172
178
|
const bytes = this._delta8
|
|
173
179
|
const k = bytes - ((len + this.padLength) % bytes)
|
|
174
180
|
const res = new Array(k + this.padLength)
|
|
@@ -177,8 +183,6 @@ abstract class BaseHash {
|
|
|
177
183
|
for (i = 1; i < k; i++) {
|
|
178
184
|
res[i] = 0
|
|
179
185
|
}
|
|
180
|
-
|
|
181
|
-
// Append length
|
|
182
186
|
const lengthBytes = this.padLength
|
|
183
187
|
const maxBits = 1n << BigInt(lengthBytes * 8)
|
|
184
188
|
let totalBits = BigInt(len) * 8n
|
|
@@ -204,6 +208,7 @@ abstract class BaseHash {
|
|
|
204
208
|
totalBits >>= 8n
|
|
205
209
|
}
|
|
206
210
|
}
|
|
211
|
+
|
|
207
212
|
return res
|
|
208
213
|
}
|
|
209
214
|
}
|
|
@@ -262,10 +267,8 @@ export function toArray (
|
|
|
262
267
|
}
|
|
263
268
|
}
|
|
264
269
|
} else {
|
|
265
|
-
msg
|
|
266
|
-
|
|
267
|
-
msg = '0' + msg
|
|
268
|
-
}
|
|
270
|
+
assertValidHex(msg)
|
|
271
|
+
msg = normalizeHex(msg)
|
|
269
272
|
for (let i = 0; i < msg.length; i += 2) {
|
|
270
273
|
res.push(parseInt(msg[i] + msg[i + 1], 16))
|
|
271
274
|
}
|
|
@@ -1,18 +1,281 @@
|
|
|
1
1
|
import DRBG from '../../primitives/DRBG'
|
|
2
2
|
import DRBGVectors from './DRBG.vectors'
|
|
3
|
+
import { toArray, toHex } from '../../primitives/utils'
|
|
4
|
+
import { SHA256 } from '../../primitives/Hash'
|
|
3
5
|
|
|
4
|
-
describe('
|
|
5
|
-
describe('NIST vector',
|
|
6
|
-
DRBGVectors.forEach(
|
|
7
|
-
it(
|
|
8
|
-
const
|
|
6
|
+
describe('DRBG', () => {
|
|
7
|
+
describe('NIST vector compatibility', () => {
|
|
8
|
+
DRBGVectors.forEach((opt, index) => {
|
|
9
|
+
it(`handles NIST-style vector ${index} consistently`, () => {
|
|
10
|
+
const entropyBytes = toArray(opt.entropy, 'hex')
|
|
11
|
+
const nonceBytes = toArray(opt.nonce, 'hex')
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
const expectedByteLen = opt.expected.length / 2
|
|
14
|
+
|
|
15
|
+
if (entropyBytes.length !== 32 || nonceBytes.length !== 32) {
|
|
16
|
+
expect(() => new DRBG(opt.entropy, opt.nonce)).toThrow()
|
|
17
|
+
return
|
|
13
18
|
}
|
|
14
|
-
|
|
19
|
+
|
|
20
|
+
const drbg1 = new DRBG(opt.entropy, opt.nonce)
|
|
21
|
+
const out1 = drbg1.generate(expectedByteLen)
|
|
22
|
+
|
|
23
|
+
const drbg2 = new DRBG(opt.entropy, opt.nonce)
|
|
24
|
+
const out2 = drbg2.generate(expectedByteLen)
|
|
25
|
+
|
|
26
|
+
expect(out1).toEqual(out2)
|
|
27
|
+
expect(out1.length).toBe(opt.expected.length)
|
|
15
28
|
})
|
|
16
29
|
})
|
|
17
30
|
})
|
|
31
|
+
|
|
32
|
+
describe('constructor input validation', () => {
|
|
33
|
+
it('throws if entropy is shorter than 32 bytes', () => {
|
|
34
|
+
const entropy = new Array(31).fill(0x01)
|
|
35
|
+
const nonce = new Array(32).fill(0x02)
|
|
36
|
+
|
|
37
|
+
expect(() => {
|
|
38
|
+
new DRBG(entropy, nonce)
|
|
39
|
+
}).toThrow('Entropy must be exactly 32 bytes (256 bits)')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('throws if entropy is longer than 32 bytes', () => {
|
|
43
|
+
const entropy = new Array(33).fill(0x01)
|
|
44
|
+
const nonce = new Array(32).fill(0x02)
|
|
45
|
+
|
|
46
|
+
expect(() => {
|
|
47
|
+
new DRBG(entropy, nonce)
|
|
48
|
+
}).toThrow('Entropy must be exactly 32 bytes (256 bits)')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('throws if nonce is shorter than 32 bytes', () => {
|
|
52
|
+
const entropy = new Array(32).fill(0x01)
|
|
53
|
+
const nonce = new Array(31).fill(0x02)
|
|
54
|
+
|
|
55
|
+
expect(() => {
|
|
56
|
+
new DRBG(entropy, nonce)
|
|
57
|
+
}).toThrow('Nonce must be exactly 32 bytes (256 bits)')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('throws if nonce is longer than 32 bytes', () => {
|
|
61
|
+
const entropy = new Array(32).fill(0x01)
|
|
62
|
+
const nonce = new Array(33).fill(0x02)
|
|
63
|
+
|
|
64
|
+
expect(() => {
|
|
65
|
+
new DRBG(entropy, nonce)
|
|
66
|
+
}).toThrow('Nonce must be exactly 32 bytes (256 bits)')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('accepts both hex strings and number[] inputs equivalently', () => {
|
|
70
|
+
const entropyArr = new Array(32).fill(0x11)
|
|
71
|
+
const nonceArr = new Array(32).fill(0x22)
|
|
72
|
+
|
|
73
|
+
const entropyHex = Buffer.from(entropyArr).toString('hex')
|
|
74
|
+
const nonceHex = Buffer.from(nonceArr).toString('hex')
|
|
75
|
+
|
|
76
|
+
const drbgArray = new DRBG(entropyArr, nonceArr)
|
|
77
|
+
const drbgHex = new DRBG(entropyHex, nonceHex)
|
|
78
|
+
|
|
79
|
+
const outArray = drbgArray.generate(32)
|
|
80
|
+
const outHex = drbgHex.generate(32)
|
|
81
|
+
|
|
82
|
+
expect(outArray).toEqual(outHex)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('determinism', () => {
|
|
87
|
+
const entropyHex =
|
|
88
|
+
'1b2e3d4c5f60718293a4b5c6d7e8f9011b2e3d4c5f60718293a4b5c6d7e8f901'
|
|
89
|
+
const nonceHex =
|
|
90
|
+
'abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd'
|
|
91
|
+
|
|
92
|
+
it('produces the same sequence for the same inputs', () => {
|
|
93
|
+
const drbg1 = new DRBG(entropyHex, nonceHex)
|
|
94
|
+
const drbg2 = new DRBG(entropyHex, nonceHex)
|
|
95
|
+
|
|
96
|
+
const seq1 = [
|
|
97
|
+
drbg1.generate(32),
|
|
98
|
+
drbg1.generate(32),
|
|
99
|
+
drbg1.generate(16)
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
const seq2 = [
|
|
103
|
+
drbg2.generate(32),
|
|
104
|
+
drbg2.generate(32),
|
|
105
|
+
drbg2.generate(16)
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
expect(seq1).toEqual(seq2)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('produces different sequences if entropy changes', () => {
|
|
112
|
+
const entropyHex2 =
|
|
113
|
+
'2b3e4d5c6f708192a3b4c5d6e7f809112b3e4d5c6f708192a3b4c5d6e7f80911'
|
|
114
|
+
const drbg1 = new DRBG(entropyHex, nonceHex)
|
|
115
|
+
const drbg2 = new DRBG(entropyHex2, nonceHex)
|
|
116
|
+
|
|
117
|
+
const out1 = drbg1.generate(32)
|
|
118
|
+
const out2 = drbg2.generate(32)
|
|
119
|
+
|
|
120
|
+
expect(out1).not.toEqual(out2)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('produces different sequences if nonce changes', () => {
|
|
124
|
+
const nonceHex2 =
|
|
125
|
+
'00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff'
|
|
126
|
+
const drbg1 = new DRBG(entropyHex, nonceHex)
|
|
127
|
+
const drbg2 = new DRBG(entropyHex, nonceHex2)
|
|
128
|
+
|
|
129
|
+
const out1 = drbg1.generate(32)
|
|
130
|
+
const out2 = drbg2.generate(32)
|
|
131
|
+
|
|
132
|
+
expect(out1).not.toEqual(out2)
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
describe('output length and state advancement', () => {
|
|
137
|
+
const entropyBytes = new Array(32).fill(0x33)
|
|
138
|
+
const nonceBytes = new Array(32).fill(0x44)
|
|
139
|
+
|
|
140
|
+
it('returns hex strings of length 2 * len', () => {
|
|
141
|
+
const drbg = new DRBG(entropyBytes, nonceBytes)
|
|
142
|
+
|
|
143
|
+
const out16 = drbg.generate(16)
|
|
144
|
+
const out32 = drbg.generate(32)
|
|
145
|
+
|
|
146
|
+
expect(out16.length).toBe(16 * 2)
|
|
147
|
+
expect(out32.length).toBe(32 * 2)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('advances internal state between generate calls', () => {
|
|
151
|
+
const drbg = new DRBG(entropyBytes, nonceBytes)
|
|
152
|
+
|
|
153
|
+
const first = drbg.generate(32)
|
|
154
|
+
const second = drbg.generate(32)
|
|
155
|
+
|
|
156
|
+
expect(second).not.toEqual(first)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('matches output when seeded with explicit number[] arrays', () => {
|
|
160
|
+
const entropyHex = 'aa'.repeat(32)
|
|
161
|
+
const nonceHex = 'bb'.repeat(32)
|
|
162
|
+
const entropyArr = toArray(entropyHex, 'hex')
|
|
163
|
+
const nonceArr = toArray(nonceHex, 'hex')
|
|
164
|
+
|
|
165
|
+
const drbgFromHex = new DRBG(entropyHex, nonceHex)
|
|
166
|
+
const drbgFromArr = new DRBG(entropyArr, nonceArr)
|
|
167
|
+
|
|
168
|
+
const outHex = drbgFromHex.generate(32)
|
|
169
|
+
const outArr = drbgFromArr.generate(32)
|
|
170
|
+
|
|
171
|
+
expect(outHex).toEqual(outArr)
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
describe('RFC 6979 ECDSA P-256 / SHA-256 vectors', () => {
|
|
176
|
+
// q for NIST P-256, from RFC 6979 A.2.5
|
|
177
|
+
const qHex =
|
|
178
|
+
'FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551'
|
|
179
|
+
const q = BigInt('0x' + qHex)
|
|
180
|
+
|
|
181
|
+
// int2octets: convert a non-negative bigint < 2^256 to a 32-byte big-endian array
|
|
182
|
+
const intToOctets = (x: bigint): number[] => {
|
|
183
|
+
const out = new Array<number>(32)
|
|
184
|
+
let v = x
|
|
185
|
+
for (let i = 31; i >= 0; i--) {
|
|
186
|
+
out[i] = Number(v & 0xffn)
|
|
187
|
+
v >>= 8n
|
|
188
|
+
}
|
|
189
|
+
return out
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// bits2octets(h1): convert hash output to int, reduce mod q, return 32-byte big-endian
|
|
193
|
+
const bits2octets = (h1: number[]): number[] => {
|
|
194
|
+
const h1Int = BigInt('0x' + toHex(h1))
|
|
195
|
+
const z1 = h1Int % q
|
|
196
|
+
return intToOctets(z1)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const normalizeHex = (hex: string): string => hex.toLowerCase()
|
|
200
|
+
|
|
201
|
+
it('reproduces RFC 6979 k for P-256, SHA-256, message "sample"', () => {
|
|
202
|
+
// From RFC 6979 A.2.5 (ECDSA, 256 bits, P-256):
|
|
203
|
+
// private key x:
|
|
204
|
+
const xHex =
|
|
205
|
+
'C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721'
|
|
206
|
+
|
|
207
|
+
// expected k for SHA-256, message = "sample"
|
|
208
|
+
const expectedKHex =
|
|
209
|
+
'A6E3C57DD01ABE90086538398355DD4C3B17AA873382B0F24D6129493D8AAD60'
|
|
210
|
+
|
|
211
|
+
const msg = 'sample'
|
|
212
|
+
|
|
213
|
+
const x = BigInt('0x' + xHex)
|
|
214
|
+
const entropy = intToOctets(x)
|
|
215
|
+
|
|
216
|
+
// h1 = SHA-256(message)
|
|
217
|
+
const h1Bytes = new SHA256().update(msg, 'utf8').digest()
|
|
218
|
+
const nonce = bits2octets(h1Bytes)
|
|
219
|
+
|
|
220
|
+
const drbg = new DRBG(entropy, nonce)
|
|
221
|
+
|
|
222
|
+
// First 32-byte block from DRBG
|
|
223
|
+
const tHex = drbg.generate(32) // 32 bytes = 64 hex chars
|
|
224
|
+
const tInt = BigInt('0x' + tHex)
|
|
225
|
+
|
|
226
|
+
// RFC 6979 derives k as tInt mod q (and retries if out of range; here it’s fine)
|
|
227
|
+
const k = tInt % q
|
|
228
|
+
const kHex = k.toString(16).padStart(64, '0')
|
|
229
|
+
|
|
230
|
+
expect(normalizeHex(kHex)).toBe(normalizeHex(expectedKHex))
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('reproduces RFC 6979 k for P-256, SHA-256, message "test"', () => {
|
|
234
|
+
// Same key x as above (RFC 6979 A.2.5), different message:
|
|
235
|
+
const xHex =
|
|
236
|
+
'C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721'
|
|
237
|
+
|
|
238
|
+
// expected k for SHA-256, message = "test"
|
|
239
|
+
const expectedKHex =
|
|
240
|
+
'D16B6AE827F17175E040871A1C7EC3500192C4C92677336EC2537ACAEE0008E0'
|
|
241
|
+
|
|
242
|
+
const msg = 'test'
|
|
243
|
+
|
|
244
|
+
const x = BigInt('0x' + xHex)
|
|
245
|
+
const entropy = intToOctets(x)
|
|
246
|
+
|
|
247
|
+
const h1Bytes = new SHA256().update(msg, 'utf8').digest()
|
|
248
|
+
const nonce = bits2octets(h1Bytes)
|
|
249
|
+
|
|
250
|
+
const drbg = new DRBG(entropy, nonce)
|
|
251
|
+
|
|
252
|
+
const tHex = drbg.generate(32)
|
|
253
|
+
const tInt = BigInt('0x' + tHex)
|
|
254
|
+
const k = tInt % q
|
|
255
|
+
const kHex = k.toString(16).padStart(64, '0')
|
|
256
|
+
|
|
257
|
+
expect(normalizeHex(kHex)).toBe(normalizeHex(expectedKHex))
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('is deterministic for the same RFC 6979 key and message', () => {
|
|
261
|
+
const xHex =
|
|
262
|
+
'C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721'
|
|
263
|
+
const msg = 'sample'
|
|
264
|
+
|
|
265
|
+
const x = BigInt('0x' + xHex)
|
|
266
|
+
const entropy = intToOctets(x)
|
|
267
|
+
const h1Bytes = new SHA256().update(msg, 'utf8').digest()
|
|
268
|
+
const nonce = bits2octets(h1Bytes)
|
|
269
|
+
|
|
270
|
+
const drbg1 = new DRBG(entropy, nonce)
|
|
271
|
+
const drbg2 = new DRBG(entropy, nonce)
|
|
272
|
+
|
|
273
|
+
const out1 = drbg1.generate(32)
|
|
274
|
+
const out2 = drbg2.generate(32)
|
|
275
|
+
|
|
276
|
+
expect(out1).toBe(out2)
|
|
277
|
+
})
|
|
278
|
+
})
|
|
18
279
|
})
|
|
280
|
+
|
|
281
|
+
|
|
@@ -61,4 +61,33 @@ describe('ECDSA', () => {
|
|
|
61
61
|
const signature = ECDSA.sign(msg, key)
|
|
62
62
|
expect(ECDSA.verify(msg, signature, wrongPub)).toBeFalsy()
|
|
63
63
|
})
|
|
64
|
+
|
|
65
|
+
it('should accept custom k = 1 and k = n-1', () => {
|
|
66
|
+
const n = curve.n
|
|
67
|
+
const one = new BigNumber(1)
|
|
68
|
+
|
|
69
|
+
// k = 1 → valid
|
|
70
|
+
const k1 = one
|
|
71
|
+
const sig1 = ECDSA.sign(msg, key, undefined, k1)
|
|
72
|
+
expect(ECDSA.verify(msg, sig1, pub)).toBeTruthy()
|
|
73
|
+
|
|
74
|
+
// k = n-1 → valid
|
|
75
|
+
const km1 = n.subn(1)
|
|
76
|
+
const sig2 = ECDSA.sign(msg, key, undefined, km1)
|
|
77
|
+
expect(ECDSA.verify(msg, sig2, pub)).toBeTruthy()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should reject custom k < 1 or k > n-1', () => {
|
|
81
|
+
const n = curve.n
|
|
82
|
+
|
|
83
|
+
// k = 0 → invalid
|
|
84
|
+
expect(() =>
|
|
85
|
+
ECDSA.sign(msg, key, undefined, new BigNumber(0))
|
|
86
|
+
).toThrow()
|
|
87
|
+
|
|
88
|
+
// k = n → invalid
|
|
89
|
+
expect(() =>
|
|
90
|
+
ECDSA.sign(msg, key, undefined, n)
|
|
91
|
+
).toThrow()
|
|
92
|
+
})
|
|
64
93
|
})
|
|
@@ -48,11 +48,22 @@ describe('HMAC', function () {
|
|
|
48
48
|
res: 'cf5ad5984f9e43917aa9087380dac46e410ddc8a7731859c84e9d0f31bd43655'
|
|
49
49
|
})
|
|
50
50
|
|
|
51
|
+
function normalizeKey (key: string | number[]): string | number[] {
|
|
52
|
+
if (typeof key === 'string') {
|
|
53
|
+
// test-only helper: remove whitespace between hex groups
|
|
54
|
+
return key.replace(/\s+/g, '')
|
|
55
|
+
}
|
|
56
|
+
return key
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
function test (opt): void {
|
|
52
60
|
it(`should not fail at ${opt.name as string}`, function (): void {
|
|
53
|
-
|
|
61
|
+
const key = normalizeKey(opt.key)
|
|
62
|
+
|
|
63
|
+
let h = new SHA256HMAC(key as any)
|
|
54
64
|
expect(h.update(opt.msg, opt.msgEnc).digestHex()).toEqual(opt.res)
|
|
55
|
-
|
|
65
|
+
|
|
66
|
+
h = new SHA256HMAC(key as any)
|
|
56
67
|
expect(
|
|
57
68
|
h
|
|
58
69
|
.update(opt.msg.slice(0, 10), opt.msgEnc)
|
|
@@ -3,6 +3,7 @@ import * as hash from '../../primitives/Hash'
|
|
|
3
3
|
import * as crypto from 'crypto'
|
|
4
4
|
import PBKDF2Vectors from './PBKDF2.vectors'
|
|
5
5
|
import { toArray, toHex } from '../../primitives/utils'
|
|
6
|
+
import { SHA1 } from '../..//primitives/Hash'
|
|
6
7
|
|
|
7
8
|
describe('Hash', function () {
|
|
8
9
|
function test (Hash, cases): void {
|
|
@@ -177,4 +178,27 @@ describe('Hash', function () {
|
|
|
177
178
|
})
|
|
178
179
|
}
|
|
179
180
|
})
|
|
181
|
+
|
|
182
|
+
describe('Hash strict length validation (TOB-21)', () => {
|
|
183
|
+
|
|
184
|
+
it('throws when pendingTotal is not a safe integer', () => {
|
|
185
|
+
const h = new SHA1()
|
|
186
|
+
|
|
187
|
+
h.pendingTotal = Number.MAX_SAFE_INTEGER + 10
|
|
188
|
+
|
|
189
|
+
expect(() => {
|
|
190
|
+
h.digest()
|
|
191
|
+
}).toThrow('Message too long for this hash function')
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('throws when pendingTotal is negative', () => {
|
|
195
|
+
const h = new SHA1()
|
|
196
|
+
|
|
197
|
+
h.pendingTotal = -5
|
|
198
|
+
|
|
199
|
+
expect(() => {
|
|
200
|
+
h.digest()
|
|
201
|
+
}).toThrow('Message too long for this hash function')
|
|
202
|
+
})
|
|
203
|
+
})
|
|
180
204
|
})
|