@bsv/sdk 1.9.24 → 1.9.30
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/primitives/AESGCM.js +160 -76
- package/dist/cjs/src/primitives/AESGCM.js.map +1 -1
- package/dist/cjs/src/primitives/ECDSA.js +22 -23
- package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
- package/dist/cjs/src/primitives/Point.js +102 -22
- package/dist/cjs/src/primitives/Point.js.map +1 -1
- package/dist/cjs/src/primitives/PrivateKey.js +2 -2
- package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
- package/dist/cjs/src/primitives/PublicKey.js +1 -1
- package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
- package/dist/cjs/src/primitives/SymmetricKey.js +20 -19
- package/dist/cjs/src/primitives/SymmetricKey.js.map +1 -1
- package/dist/cjs/src/primitives/hex.js +1 -3
- package/dist/cjs/src/primitives/hex.js.map +1 -1
- package/dist/cjs/src/primitives/utils.js +10 -0
- package/dist/cjs/src/primitives/utils.js.map +1 -1
- package/dist/cjs/src/totp/totp.js +3 -1
- package/dist/cjs/src/totp/totp.js.map +1 -1
- package/dist/cjs/src/wallet/ProtoWallet.js +4 -2
- package/dist/cjs/src/wallet/ProtoWallet.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/primitives/AESGCM.js +158 -75
- package/dist/esm/src/primitives/AESGCM.js.map +1 -1
- package/dist/esm/src/primitives/ECDSA.js +22 -23
- package/dist/esm/src/primitives/ECDSA.js.map +1 -1
- package/dist/esm/src/primitives/Point.js +102 -22
- package/dist/esm/src/primitives/Point.js.map +1 -1
- package/dist/esm/src/primitives/PrivateKey.js +2 -2
- package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
- package/dist/esm/src/primitives/PublicKey.js +1 -1
- package/dist/esm/src/primitives/PublicKey.js.map +1 -1
- package/dist/esm/src/primitives/SymmetricKey.js +20 -19
- package/dist/esm/src/primitives/SymmetricKey.js.map +1 -1
- package/dist/esm/src/primitives/hex.js +1 -3
- package/dist/esm/src/primitives/hex.js.map +1 -1
- package/dist/esm/src/primitives/utils.js +9 -0
- package/dist/esm/src/primitives/utils.js.map +1 -1
- package/dist/esm/src/totp/totp.js +3 -1
- package/dist/esm/src/totp/totp.js.map +1 -1
- package/dist/esm/src/wallet/ProtoWallet.js +4 -2
- package/dist/esm/src/wallet/ProtoWallet.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/AESGCM.d.ts +59 -9
- package/dist/types/src/primitives/AESGCM.d.ts.map +1 -1
- package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
- package/dist/types/src/primitives/Point.d.ts +2 -0
- package/dist/types/src/primitives/Point.d.ts.map +1 -1
- package/dist/types/src/primitives/SymmetricKey.d.ts.map +1 -1
- package/dist/types/src/primitives/hex.d.ts.map +1 -1
- package/dist/types/src/primitives/utils.d.ts +1 -0
- package/dist/types/src/primitives/utils.d.ts.map +1 -1
- package/dist/types/src/totp/totp.d.ts.map +1 -1
- package/dist/types/src/wallet/ProtoWallet.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 +206 -60
- package/package.json +1 -1
- package/src/primitives/AESGCM.ts +225 -103
- package/src/primitives/ECDSA.ts +25 -23
- package/src/primitives/Point.ts +142 -23
- package/src/primitives/PrivateKey.ts +2 -2
- package/src/primitives/PublicKey.ts +1 -1
- package/src/primitives/SymmetricKey.ts +28 -20
- package/src/primitives/__tests/AESGCM.test.ts +254 -354
- package/src/primitives/__tests/ECDSA.test.ts +39 -0
- package/src/primitives/__tests/Point.test.ts +112 -0
- package/src/primitives/__tests/utils.test.ts +24 -1
- package/src/primitives/hex.ts +1 -3
- package/src/primitives/utils.ts +10 -0
- package/src/totp/__tests/totp.test.ts +21 -0
- package/src/totp/totp.ts +9 -1
- package/src/wallet/ProtoWallet.ts +8 -3
- package/src/wallet/__tests/ProtoWallet.test.ts +55 -34
|
@@ -2,6 +2,7 @@ import * as ECDSA from '../../primitives/ECDSA'
|
|
|
2
2
|
import BigNumber from '../../primitives/BigNumber'
|
|
3
3
|
import Curve from '../../primitives/Curve'
|
|
4
4
|
import Signature from '../../primitives/Signature'
|
|
5
|
+
import Point from '../../primitives/Point'
|
|
5
6
|
|
|
6
7
|
const msg = new BigNumber('deadbeef', 16)
|
|
7
8
|
const key = new BigNumber(
|
|
@@ -90,4 +91,42 @@ describe('ECDSA', () => {
|
|
|
90
91
|
ECDSA.sign(msg, key, undefined, n)
|
|
91
92
|
).toThrow()
|
|
92
93
|
})
|
|
94
|
+
|
|
95
|
+
it('k·G + (−k·G) results in point at infinity (TOB-25)', () => {
|
|
96
|
+
const k = new BigNumber('123456789abcdef', 16)
|
|
97
|
+
|
|
98
|
+
const P = curve.g.mul(k)
|
|
99
|
+
const negP = P.neg()
|
|
100
|
+
const sum = P.add(negP)
|
|
101
|
+
|
|
102
|
+
expect(sum.isInfinity()).toBe(true)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('scalar multiplication by zero returns point at infinity (TOB-25)', () => {
|
|
106
|
+
const zero = new BigNumber(0)
|
|
107
|
+
const result = curve.g.mul(zero)
|
|
108
|
+
|
|
109
|
+
expect(result.isInfinity()).toBe(true)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('ECDSA verify rejects point-at-infinity public key (TOB-25)', () => {
|
|
113
|
+
const signature = ECDSA.sign(msg, key)
|
|
114
|
+
const infinityPub = new Point(null, null)
|
|
115
|
+
|
|
116
|
+
expect(() =>
|
|
117
|
+
ECDSA.verify(msg, signature, infinityPub)
|
|
118
|
+
).toThrow()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('sign/verify works with large private key (mulCT stress)', () => {
|
|
122
|
+
const bigKey = new BigNumber(
|
|
123
|
+
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f',
|
|
124
|
+
16
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
const sig = ECDSA.sign(msg, bigKey)
|
|
128
|
+
const pub = curve.g.mul(bigKey)
|
|
129
|
+
|
|
130
|
+
expect(ECDSA.verify(msg, sig, pub)).toBe(true)
|
|
131
|
+
})
|
|
93
132
|
})
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import Point from '../../primitives/Point'
|
|
2
|
+
import BigNumber from '../../primitives/BigNumber'
|
|
3
|
+
|
|
4
|
+
describe('Point.fromJSON / fromDER / fromX curve validation (TOB-24)', () => {
|
|
5
|
+
it('rejects clearly off-curve coordinates', () => {
|
|
6
|
+
expect(() =>
|
|
7
|
+
Point.fromJSON([123, 456], true)
|
|
8
|
+
).toThrow(/Invalid point/)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('rejects nested off-curve precomputed points', () => {
|
|
12
|
+
const bad = [
|
|
13
|
+
123,
|
|
14
|
+
456,
|
|
15
|
+
{
|
|
16
|
+
doubles: {
|
|
17
|
+
step: 2,
|
|
18
|
+
points: [
|
|
19
|
+
[1, 2],
|
|
20
|
+
[3, 4]
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
expect(() => Point.fromJSON(bad, true)).toThrow(/Invalid point/)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('accepts valid generator point from toJSON → fromJSON roundtrip', () => {
|
|
29
|
+
// Compressed secp256k1 G:
|
|
30
|
+
const G_COMPRESSED =
|
|
31
|
+
'0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'
|
|
32
|
+
|
|
33
|
+
const g = Point.fromString(G_COMPRESSED)
|
|
34
|
+
const serialized = g.toJSON()
|
|
35
|
+
const restored = Point.fromJSON(serialized as any, true)
|
|
36
|
+
|
|
37
|
+
expect(restored.eq(g)).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('rejects invalid compressed points in fromDER', () => {
|
|
41
|
+
// 0x02 is a valid compressed prefix, but x = 0 gives y^2 = 7,
|
|
42
|
+
// which has no square root mod p on secp256k1 → invalid point.
|
|
43
|
+
const der = [0x02, ...Array(32).fill(0x00)]
|
|
44
|
+
expect(() => Point.fromDER(der)).toThrow(/Invalid point/)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('fromX rejects values with no square root mod p', () => {
|
|
48
|
+
// x = 0 ⇒ y^2 = 7, which has no square root mod p on secp256k1.
|
|
49
|
+
// This guarantees that fromX must reject it.
|
|
50
|
+
const badX = '0000000000000000000000000000000000000000000000000000000000000000'
|
|
51
|
+
expect(() => Point.fromX(badX, true)).toThrow(/Invalid point/)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('Point.mulCT (constant-time scalar multiplication)', () => {
|
|
56
|
+
const G = Point.fromString(
|
|
57
|
+
'0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
it('returns point at infinity for scalar = 0', () => {
|
|
61
|
+
const r = G.mulCT(0)
|
|
62
|
+
expect(r.isInfinity()).toBe(true)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('matches regular mul for small scalar', () => {
|
|
66
|
+
const k = 5
|
|
67
|
+
const r1 = G.mul(k)
|
|
68
|
+
const r2 = G.mulCT(k)
|
|
69
|
+
|
|
70
|
+
expect(r2.eq(r1)).toBe(true)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('matches regular mul for large scalar', () => {
|
|
74
|
+
const k =
|
|
75
|
+
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'
|
|
76
|
+
|
|
77
|
+
const r1 = G.mul(k)
|
|
78
|
+
const r2 = G.mulCT(k)
|
|
79
|
+
|
|
80
|
+
expect(r2.eq(r1)).toBe(true)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('works with non-generator base point', () => {
|
|
84
|
+
const base = G.mul(3)
|
|
85
|
+
const k = 11
|
|
86
|
+
|
|
87
|
+
const r1 = base.mul(k)
|
|
88
|
+
const r2 = base.mulCT(k)
|
|
89
|
+
|
|
90
|
+
expect(r2.eq(r1)).toBe(true)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('handles alternating bit patterns (ctSwap exercised)', () => {
|
|
94
|
+
// 101010... pattern forces both swap paths
|
|
95
|
+
const k = BigInt(
|
|
96
|
+
'0b101010101010101010101010101010101010101010101010101010101010101'
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
const r1 = G.mul(k.toString(10))
|
|
100
|
+
const r2 = G.mulCT(k.toString(10))
|
|
101
|
+
|
|
102
|
+
expect(r2.eq(r1)).toBe(true)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('handles negative scalars correctly', () => {
|
|
106
|
+
const k = new BigNumber('123456', 16)
|
|
107
|
+
const r1 = G.mul(k.neg())
|
|
108
|
+
const r2 = G.mulCT(k.neg())
|
|
109
|
+
expect(r2.eq(r1)).toBe(true)
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
toBase58,
|
|
10
10
|
fromBase58Check,
|
|
11
11
|
toBase58Check,
|
|
12
|
-
verifyNotNull
|
|
12
|
+
verifyNotNull,
|
|
13
|
+
constantTimeEquals
|
|
13
14
|
} from '../../primitives/utils'
|
|
14
15
|
import Point from '../../primitives/Point'
|
|
15
16
|
|
|
@@ -376,3 +377,25 @@ describe('Point.encode infinity handling', () => {
|
|
|
376
377
|
expect(() => p.encode()).not.toThrow()
|
|
377
378
|
})
|
|
378
379
|
})
|
|
380
|
+
|
|
381
|
+
describe('constantTimeEquals', () => {
|
|
382
|
+
it('returns true for identical arrays', () => {
|
|
383
|
+
expect(constantTimeEquals([1, 2, 3], [1, 2, 3])).toBe(true)
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
it('returns false for arrays with different content', () => {
|
|
387
|
+
expect(constantTimeEquals([1, 2, 3], [1, 2, 4])).toBe(false)
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it('returns false for arrays of different length', () => {
|
|
391
|
+
expect(constantTimeEquals([1, 2], [1, 2, 3])).toBe(false)
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
it('runs through entire array (no early exit)', () => {
|
|
395
|
+
expect(constantTimeEquals([0,0,0,0,9], [0,0,0,0,8])).toBe(false)
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
it('works with Uint8Array', () => {
|
|
399
|
+
expect(constantTimeEquals(new Uint8Array([5,6,7]), new Uint8Array([5,6,7]))).toBe(true)
|
|
400
|
+
})
|
|
401
|
+
})
|
package/src/primitives/hex.ts
CHANGED
|
@@ -5,15 +5,13 @@ const PURE_HEX_REGEX = /^[0-9a-fA-F]*$/
|
|
|
5
5
|
|
|
6
6
|
export function assertValidHex (msg: string): void {
|
|
7
7
|
if (typeof msg !== 'string') {
|
|
8
|
-
|
|
9
|
-
throw new Error('Invalid hex string')
|
|
8
|
+
throw new TypeError('Invalid hex string')
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
// allow empty
|
|
13
12
|
if (msg.length === 0) return
|
|
14
13
|
|
|
15
14
|
if (!PURE_HEX_REGEX.test(msg)) {
|
|
16
|
-
console.error('assertValidHex FAIL (bad hex):', msg)
|
|
17
15
|
throw new Error('Invalid hex string')
|
|
18
16
|
}
|
|
19
17
|
}
|
package/src/primitives/utils.ts
CHANGED
|
@@ -951,3 +951,13 @@ export function verifyNotNull<T> (value: T | undefined | null, errorMessage: str
|
|
|
951
951
|
if (value == null) throw new Error(errorMessage)
|
|
952
952
|
return value
|
|
953
953
|
}
|
|
954
|
+
|
|
955
|
+
export function constantTimeEquals (a: Uint8Array | number[], b: Uint8Array | number[]): boolean {
|
|
956
|
+
if (a.length !== b.length) return false
|
|
957
|
+
|
|
958
|
+
let diff = 0
|
|
959
|
+
for (let i = 0; i < a.length; i++) {
|
|
960
|
+
diff |= a[i] ^ b[i]
|
|
961
|
+
}
|
|
962
|
+
return diff === 0
|
|
963
|
+
}
|
|
@@ -78,4 +78,25 @@ describe('totp generation and validation', () => {
|
|
|
78
78
|
checkAdjacentWindow(time - i * periodMS, false)
|
|
79
79
|
}
|
|
80
80
|
})
|
|
81
|
+
|
|
82
|
+
test('should reject wrong passcode with same length', () => {
|
|
83
|
+
jest.setSystemTime(0)
|
|
84
|
+
|
|
85
|
+
const correct = TOTP.generate(secret, options)
|
|
86
|
+
|
|
87
|
+
// Same length but definitely wrong
|
|
88
|
+
const wrong = correct === '123456' ? '654321' : '123456'
|
|
89
|
+
|
|
90
|
+
expect(wrong.length).toBe(correct.length)
|
|
91
|
+
expect(TOTP.validate(secret, wrong, options)).toBe(false)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('should validate correct passcode using constant-time comparison', () => {
|
|
95
|
+
jest.setSystemTime(0)
|
|
96
|
+
|
|
97
|
+
const correct = TOTP.generate(secret, options)
|
|
98
|
+
|
|
99
|
+
// Ensure the code path executes constantTimeEquals and returns true
|
|
100
|
+
expect(TOTP.validate(secret, correct, options)).toBe(true)
|
|
101
|
+
})
|
|
81
102
|
})
|
package/src/totp/totp.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SHA1HMAC, SHA256HMAC, SHA512HMAC } from '../primitives/Hash.js'
|
|
2
2
|
import BigNumber from '../primitives/BigNumber.js'
|
|
3
|
+
import { constantTimeEquals, toArray } from '../primitives/utils.js'
|
|
3
4
|
|
|
4
5
|
export type TOTPAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-512'
|
|
5
6
|
|
|
@@ -68,7 +69,14 @@ export class TOTP {
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
for (const c of counters) {
|
|
71
|
-
|
|
72
|
+
const expected = generateHOTP(secret, c, _options)
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
constantTimeEquals(
|
|
76
|
+
toArray(passcode, 'utf8'),
|
|
77
|
+
toArray(expected, 'utf8')
|
|
78
|
+
)
|
|
79
|
+
) {
|
|
72
80
|
return true
|
|
73
81
|
}
|
|
74
82
|
}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
WalletEncryptArgs,
|
|
31
31
|
WalletEncryptResult
|
|
32
32
|
} from './Wallet.interfaces.js'
|
|
33
|
+
import { constantTimeEquals, toArray } from '../primitives/utils.js'
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* A ProtoWallet is precursor to a full wallet, capable of performing all foundational cryptographic operations.
|
|
@@ -222,9 +223,13 @@ export class ProtoWallet {
|
|
|
222
223
|
args.keyID,
|
|
223
224
|
args.counterparty ?? 'self'
|
|
224
225
|
)
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
const computed = Hash.sha256hmac(key.toArray(), args.data)
|
|
227
|
+
const provided = args.hmac
|
|
228
|
+
|
|
229
|
+
const valid = constantTimeEquals(
|
|
230
|
+
toArray(computed),
|
|
231
|
+
toArray(provided)
|
|
232
|
+
)
|
|
228
233
|
if (!valid) {
|
|
229
234
|
const e = new Error('HMAC is not valid') as Error & { code: string }
|
|
230
235
|
e.code = 'ERR_INVALID_HMAC'
|
|
@@ -4,6 +4,19 @@ import { createNonce, verifyNonce } from '../../auth/utils'
|
|
|
4
4
|
|
|
5
5
|
const sampleData = [3, 1, 4, 1, 5, 9]
|
|
6
6
|
|
|
7
|
+
let userKey: PrivateKey
|
|
8
|
+
let counterpartyKey: PrivateKey
|
|
9
|
+
let user: ProtoWallet
|
|
10
|
+
let counterparty: ProtoWallet
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
userKey = PrivateKey.fromRandom()
|
|
14
|
+
counterpartyKey = PrivateKey.fromRandom()
|
|
15
|
+
user = new ProtoWallet(userKey)
|
|
16
|
+
counterparty = new ProtoWallet(counterpartyKey)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
|
|
7
20
|
describe('ProtoWallet', () => {
|
|
8
21
|
it('Throws when unsupported functions are called', async () => {
|
|
9
22
|
const wallet = new ProtoWallet('anyone')
|
|
@@ -80,10 +93,6 @@ describe('ProtoWallet', () => {
|
|
|
80
93
|
)
|
|
81
94
|
})
|
|
82
95
|
it('Encrypts messages decryptable by the counterparty', async () => {
|
|
83
|
-
const userKey = PrivateKey.fromRandom()
|
|
84
|
-
const counterpartyKey = PrivateKey.fromRandom()
|
|
85
|
-
const user = new ProtoWallet(userKey)
|
|
86
|
-
const counterparty = new ProtoWallet(counterpartyKey)
|
|
87
96
|
const { ciphertext } = await user.encrypt({
|
|
88
97
|
plaintext: sampleData,
|
|
89
98
|
protocolID: [2, 'tests'],
|
|
@@ -100,10 +109,6 @@ describe('ProtoWallet', () => {
|
|
|
100
109
|
expect(ciphertext).not.toEqual(plaintext)
|
|
101
110
|
})
|
|
102
111
|
it('Fails to decryupt messages for the wrong protocol, key, and counterparty', async () => {
|
|
103
|
-
const userKey = PrivateKey.fromRandom()
|
|
104
|
-
const counterpartyKey = PrivateKey.fromRandom()
|
|
105
|
-
const user = new ProtoWallet(userKey)
|
|
106
|
-
const counterparty = new ProtoWallet(counterpartyKey)
|
|
107
112
|
const { ciphertext } = await user.encrypt({
|
|
108
113
|
plaintext: sampleData,
|
|
109
114
|
protocolID: [2, 'tests'],
|
|
@@ -139,10 +144,6 @@ describe('ProtoWallet', () => {
|
|
|
139
144
|
).rejects.toThrow()
|
|
140
145
|
})
|
|
141
146
|
it('Correctly derives keys for a counterparty', async () => {
|
|
142
|
-
const userKey = PrivateKey.fromRandom()
|
|
143
|
-
const counterpartyKey = PrivateKey.fromRandom()
|
|
144
|
-
const user = new ProtoWallet(userKey)
|
|
145
|
-
const counterparty = new ProtoWallet(counterpartyKey)
|
|
146
147
|
const { publicKey: identityKey } = await user.getPublicKey({
|
|
147
148
|
identityKey: true
|
|
148
149
|
})
|
|
@@ -162,10 +163,6 @@ describe('ProtoWallet', () => {
|
|
|
162
163
|
expect(derivedForCounterparty).toEqual(derivedByCounterparty)
|
|
163
164
|
})
|
|
164
165
|
it('Signs messages verifiable by the counterparty', async () => {
|
|
165
|
-
const userKey = PrivateKey.fromRandom()
|
|
166
|
-
const counterpartyKey = PrivateKey.fromRandom()
|
|
167
|
-
const user = new ProtoWallet(userKey)
|
|
168
|
-
const counterparty = new ProtoWallet(counterpartyKey)
|
|
169
166
|
const { signature } = await user.createSignature({
|
|
170
167
|
data: sampleData,
|
|
171
168
|
protocolID: [2, 'tests'],
|
|
@@ -183,10 +180,6 @@ describe('ProtoWallet', () => {
|
|
|
183
180
|
expect(signature.length).not.toEqual(0)
|
|
184
181
|
})
|
|
185
182
|
it('Directly signs hash of message verifiable by the counterparty', async () => {
|
|
186
|
-
const userKey = PrivateKey.fromRandom()
|
|
187
|
-
const counterpartyKey = PrivateKey.fromRandom()
|
|
188
|
-
const user = new ProtoWallet(userKey)
|
|
189
|
-
const counterparty = new ProtoWallet(counterpartyKey)
|
|
190
183
|
const { signature } = await user.createSignature({
|
|
191
184
|
hashToDirectlySign: Hash.sha256(sampleData),
|
|
192
185
|
protocolID: [2, 'tests'],
|
|
@@ -212,10 +205,6 @@ describe('ProtoWallet', () => {
|
|
|
212
205
|
expect(signature.length).not.toEqual(0)
|
|
213
206
|
})
|
|
214
207
|
it('Fails to verify signature for the wrong data, protocol, key, and counterparty', async () => {
|
|
215
|
-
const userKey = PrivateKey.fromRandom()
|
|
216
|
-
const counterpartyKey = PrivateKey.fromRandom()
|
|
217
|
-
const user = new ProtoWallet(userKey)
|
|
218
|
-
const counterparty = new ProtoWallet(counterpartyKey)
|
|
219
208
|
const { signature } = await user.createSignature({
|
|
220
209
|
data: sampleData,
|
|
221
210
|
protocolID: [2, 'tests'],
|
|
@@ -264,10 +253,6 @@ describe('ProtoWallet', () => {
|
|
|
264
253
|
).rejects.toThrow()
|
|
265
254
|
})
|
|
266
255
|
it('Computes HMAC over messages verifiable by the counterparty', async () => {
|
|
267
|
-
const userKey = PrivateKey.fromRandom()
|
|
268
|
-
const counterpartyKey = PrivateKey.fromRandom()
|
|
269
|
-
const user = new ProtoWallet(userKey)
|
|
270
|
-
const counterparty = new ProtoWallet(counterpartyKey)
|
|
271
256
|
const { hmac } = await user.createHmac({
|
|
272
257
|
data: sampleData,
|
|
273
258
|
protocolID: [2, 'tests'],
|
|
@@ -285,10 +270,6 @@ describe('ProtoWallet', () => {
|
|
|
285
270
|
expect(hmac.length).toEqual(32)
|
|
286
271
|
})
|
|
287
272
|
it('Fails to verify HMAC for the wrong data, protocol, key, and counterparty', async () => {
|
|
288
|
-
const userKey = PrivateKey.fromRandom()
|
|
289
|
-
const counterpartyKey = PrivateKey.fromRandom()
|
|
290
|
-
const user = new ProtoWallet(userKey)
|
|
291
|
-
const counterparty = new ProtoWallet(counterpartyKey)
|
|
292
273
|
const { hmac } = await user.createHmac({
|
|
293
274
|
data: sampleData,
|
|
294
275
|
protocolID: [2, 'tests'],
|
|
@@ -337,8 +318,6 @@ describe('ProtoWallet', () => {
|
|
|
337
318
|
).rejects.toThrow()
|
|
338
319
|
})
|
|
339
320
|
it('Uses anyone for creating signatures and self for other operations if no counterparty is provided', async () => {
|
|
340
|
-
const userKey = PrivateKey.fromRandom()
|
|
341
|
-
const user = new ProtoWallet(userKey)
|
|
342
321
|
const { hmac } = await user.createHmac({
|
|
343
322
|
data: sampleData,
|
|
344
323
|
protocolID: [2, 'tests'],
|
|
@@ -589,4 +568,46 @@ describe('ProtoWallet', () => {
|
|
|
589
568
|
expect(linkage).toEqual(expectedLinkage)
|
|
590
569
|
})
|
|
591
570
|
})
|
|
571
|
+
|
|
572
|
+
it('Fails constant-time HMAC validation for wrong-but-same-length HMAC', async () => {
|
|
573
|
+
const { hmac: correctHmac } = await user.createHmac({
|
|
574
|
+
data: sampleData,
|
|
575
|
+
protocolID: [2, 'tests'],
|
|
576
|
+
keyID: '4',
|
|
577
|
+
counterparty: counterpartyKey.toPublicKey().toString()
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
// Create a different HMAC with same length
|
|
581
|
+
const wrong = correctHmac.slice()
|
|
582
|
+
wrong[0] = (wrong[0] + 1) & 0xff // minimally alter 1 byte
|
|
583
|
+
|
|
584
|
+
await expect(async () =>
|
|
585
|
+
await counterparty.verifyHmac({
|
|
586
|
+
hmac: wrong,
|
|
587
|
+
data: sampleData,
|
|
588
|
+
protocolID: [2, 'tests'],
|
|
589
|
+
keyID: '4',
|
|
590
|
+
counterparty: userKey.toPublicKey().toString()
|
|
591
|
+
})
|
|
592
|
+
).rejects.toThrow('HMAC is not valid')
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
it('Validates correct HMAC using the constant-time comparison path', async () => {
|
|
596
|
+
const { hmac } = await user.createHmac({
|
|
597
|
+
data: sampleData,
|
|
598
|
+
protocolID: [2, 'tests'],
|
|
599
|
+
keyID: '4',
|
|
600
|
+
counterparty: counterpartyKey.toPublicKey().toString()
|
|
601
|
+
})
|
|
602
|
+
|
|
603
|
+
const { valid } = await counterparty.verifyHmac({
|
|
604
|
+
hmac,
|
|
605
|
+
data: sampleData,
|
|
606
|
+
protocolID: [2, 'tests'],
|
|
607
|
+
keyID: '4',
|
|
608
|
+
counterparty: userKey.toPublicKey().toString()
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
expect(valid).toBe(true)
|
|
612
|
+
})
|
|
592
613
|
})
|