@bsv/sdk 1.9.12 → 1.9.15
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 +15 -0
- package/dist/cjs/src/primitives/AESGCM.js.map +1 -1
- package/dist/cjs/src/primitives/Secp256r1.js +327 -0
- package/dist/cjs/src/primitives/Secp256r1.js.map +1 -0
- package/dist/cjs/src/primitives/SymmetricKey.js +0 -3
- package/dist/cjs/src/primitives/SymmetricKey.js.map +1 -1
- package/dist/cjs/src/primitives/index.js +3 -1
- package/dist/cjs/src/primitives/index.js.map +1 -1
- package/dist/cjs/src/primitives/utils.js +59 -12
- package/dist/cjs/src/primitives/utils.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/primitives/AESGCM.js +15 -0
- package/dist/esm/src/primitives/AESGCM.js.map +1 -1
- package/dist/esm/src/primitives/Secp256r1.js +319 -0
- package/dist/esm/src/primitives/Secp256r1.js.map +1 -0
- package/dist/esm/src/primitives/SymmetricKey.js +0 -3
- package/dist/esm/src/primitives/SymmetricKey.js.map +1 -1
- package/dist/esm/src/primitives/index.js +1 -0
- package/dist/esm/src/primitives/index.js.map +1 -1
- package/dist/esm/src/primitives/utils.js +58 -12
- package/dist/esm/src/primitives/utils.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/AESGCM.d.ts.map +1 -1
- package/dist/types/src/primitives/Secp256r1.d.ts +91 -0
- package/dist/types/src/primitives/Secp256r1.d.ts.map +1 -0
- package/dist/types/src/primitives/SymmetricKey.d.ts.map +1 -1
- package/dist/types/src/primitives/index.d.ts +1 -0
- package/dist/types/src/primitives/index.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/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 +164 -5
- package/package.json +1 -1
- package/src/auth/utils/__tests/cryptononce.test.ts +3 -3
- package/src/primitives/AESGCM.ts +20 -0
- package/src/primitives/Secp256r1.ts +334 -0
- package/src/primitives/SymmetricKey.ts +0 -4
- package/src/primitives/__tests/AESGCM.test.ts +57 -1
- package/src/primitives/__tests/Secp256r1.test.ts +101 -0
- package/src/primitives/__tests/utils.test.ts +44 -0
- package/src/primitives/index.ts +1 -0
- package/src/primitives/utils.ts +57 -13
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import Secp256r1 from '../Secp256r1.js'
|
|
3
|
+
import { sha256 } from '../Hash.js'
|
|
4
|
+
|
|
5
|
+
const curve = new Secp256r1()
|
|
6
|
+
|
|
7
|
+
const TWO_G =
|
|
8
|
+
'047cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc4766997807775510db8ed040293d9ac69f7430dbba7dade63ce982299e04b79d227873d1'
|
|
9
|
+
const THREE_G =
|
|
10
|
+
'045ecbe4d1a6330a44c8f7ef951d4bf165e6c6b721efada985fb41661bc6e7fd6c8734640c4998ff7e374b06ce1a64a2ecd82ab036384fb83d9a79b127a27d5032'
|
|
11
|
+
|
|
12
|
+
const toBase64Url = (hex: string): string => Buffer.from(hex, 'hex').toString('base64url')
|
|
13
|
+
|
|
14
|
+
describe('Secp256r1', () => {
|
|
15
|
+
test('base point multiplication matches known coordinates and handles infinity', () => {
|
|
16
|
+
const twoG = curve.multiplyBase(2n)
|
|
17
|
+
const threeG = curve.multiplyBase(3n)
|
|
18
|
+
expect(curve.pointToHex(twoG)).toBe(TWO_G)
|
|
19
|
+
expect(curve.pointToHex(threeG)).toBe(THREE_G)
|
|
20
|
+
expect(curve.multiplyBase(curve.n)).toBeNull()
|
|
21
|
+
expect(curve.multiply(null, 5n)).toBeNull()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('public key generation stays on-curve, supports compression, and rejects bad encodings', () => {
|
|
25
|
+
const priv = curve.generatePrivateKeyHex()
|
|
26
|
+
const pub = curve.publicKeyFromPrivate(priv)
|
|
27
|
+
expect(curve.isOnCurve(pub)).toBe(true)
|
|
28
|
+
const compressed = curve.pointToHex(pub, true)
|
|
29
|
+
const roundTrip = curve.pointFromHex(compressed)
|
|
30
|
+
expect(roundTrip).toEqual(pub)
|
|
31
|
+
expect(() => curve.pointFromHex('05abcdef')).toThrow()
|
|
32
|
+
expect(() => curve.pointFromHex('')).toThrow()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('adding inverse points yields infinity', () => {
|
|
36
|
+
const p = curve.multiplyBase(9n)
|
|
37
|
+
const neg = { x: p!.x, y: curve.p - p!.y }
|
|
38
|
+
expect(curve.add(p, neg)).toBeNull()
|
|
39
|
+
expect(curve.add(null, p)).toEqual(p)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('ECDSA sign and verify round-trip, low-s enforced, rejects malformed inputs', () => {
|
|
43
|
+
const priv = '1'.repeat(64)
|
|
44
|
+
const pub = curve.publicKeyFromPrivate(priv)
|
|
45
|
+
const message = Buffer.from('p256 check')
|
|
46
|
+
const signature = curve.sign(message, priv)
|
|
47
|
+
const sVal = BigInt('0x' + signature.s)
|
|
48
|
+
expect(sVal <= curve.n / 2n).toBe(true)
|
|
49
|
+
expect(curve.verify(message, signature, pub)).toBe(true)
|
|
50
|
+
expect(curve.verify(Buffer.from('different'), signature, pub)).toBe(false)
|
|
51
|
+
const tampered = { r: signature.r, s: signature.s.slice(0, 62) + '00' }
|
|
52
|
+
expect(curve.verify(message, tampered, pub)).toBe(false)
|
|
53
|
+
const zeroR = { r: '0'.repeat(64), s: signature.s }
|
|
54
|
+
expect(curve.verify(message, zeroR, pub)).toBe(false)
|
|
55
|
+
const zeroS = { r: signature.r, s: '0'.repeat(64) }
|
|
56
|
+
expect(curve.verify(message, zeroS, pub)).toBe(false)
|
|
57
|
+
expect(curve.verify(message, signature, '02deadbeef')).toBe(false)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('deterministic nonce is stable across calls and message changes', () => {
|
|
61
|
+
const priv = '2'.repeat(64)
|
|
62
|
+
const pub = curve.publicKeyFromPrivate(priv)
|
|
63
|
+
const message = Buffer.from('deterministic nonce')
|
|
64
|
+
const sig1 = curve.sign(message, priv)
|
|
65
|
+
const sig2 = curve.sign(message, priv)
|
|
66
|
+
expect(sig1).toEqual(sig2)
|
|
67
|
+
const sig3 = curve.sign(Buffer.from('deterministic nonce v2'), priv)
|
|
68
|
+
expect(sig3).not.toEqual(sig1)
|
|
69
|
+
expect(curve.verify(message, sig1, pub)).toBe(true)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('prehashed signing path matches explicit hashing input', () => {
|
|
73
|
+
const priv = '4'.repeat(64)
|
|
74
|
+
const pub = curve.publicKeyFromPrivate(priv)
|
|
75
|
+
const message = Buffer.from('prehashed path')
|
|
76
|
+
const digest = new Uint8Array(sha256(message))
|
|
77
|
+
const sig1 = curve.sign(message, priv)
|
|
78
|
+
const sig2 = curve.sign(digest, priv, { prehashed: true })
|
|
79
|
+
expect(sig1).toEqual(sig2)
|
|
80
|
+
expect(curve.verify(digest, sig2, pub, { prehashed: true })).toBe(true)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('signatures interoperate with Node crypto (ieee-p1363 encoding)', () => {
|
|
84
|
+
const priv = '3'.repeat(64)
|
|
85
|
+
const pub = curve.publicKeyFromPrivate(priv)
|
|
86
|
+
const message = Buffer.from('interop check')
|
|
87
|
+
const signature = curve.sign(message, priv)
|
|
88
|
+
const sigBuf = Buffer.concat([Buffer.from(signature.r, 'hex'), Buffer.from(signature.s, 'hex')])
|
|
89
|
+
|
|
90
|
+
const pubHex = curve.pointToHex(pub)
|
|
91
|
+
const jwk = {
|
|
92
|
+
kty: 'EC',
|
|
93
|
+
crv: 'P-256',
|
|
94
|
+
x: toBase64Url(pubHex.slice(2, 66)),
|
|
95
|
+
y: toBase64Url(pubHex.slice(66))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const ok = crypto.verify('sha256', message, { key: jwk, format: 'jwk', dsaEncoding: 'ieee-p1363' }, sigBuf)
|
|
99
|
+
expect(ok).toBe(true)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
@@ -209,6 +209,50 @@ describe('utils', () => {
|
|
|
209
209
|
})
|
|
210
210
|
})
|
|
211
211
|
|
|
212
|
+
describe('toArray base64', () => {
|
|
213
|
+
it('decodes empty string to empty array', () => {
|
|
214
|
+
expect(toArray('', 'base64')).toEqual([])
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('decodes standard padded base64 strings', () => {
|
|
218
|
+
expect(toArray('Zg==', 'base64')).toEqual([102])
|
|
219
|
+
expect(toArray('Zm8=', 'base64')).toEqual([102, 111])
|
|
220
|
+
expect(toArray('Zm9v', 'base64')).toEqual([102, 111, 111])
|
|
221
|
+
expect(toArray('SGVsbG8=', 'base64')).toEqual([72, 101, 108, 108, 111])
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('decodes base64 without padding', () => {
|
|
225
|
+
expect(toArray('SGVsbG8', 'base64')).toEqual([72, 101, 108, 108, 111])
|
|
226
|
+
expect(toArray('QQ', 'base64')).toEqual([65])
|
|
227
|
+
expect(toArray('Zm8', 'base64')).toEqual([102, 111])
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('decodes URL-safe base64', () => {
|
|
231
|
+
expect(toArray('_w==', 'base64')).toEqual([255])
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('ignores whitespace and newlines', () => {
|
|
235
|
+
expect(toArray('S G V s b G 8 =\n', 'base64')).toEqual([72, 101, 108, 108, 111])
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('throws on invalid padding', () => {
|
|
239
|
+
expect(() => toArray('SGVsbG8===', 'base64')).toThrow(new Error('Invalid base64 padding'))
|
|
240
|
+
expect(() => toArray('SGV=sbG8=', 'base64')).toThrow(new Error('Invalid base64 padding'))
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
// it('throws on invalid length (1 mod 4)', () => {
|
|
244
|
+
// expect(() => toArray('abcde', 'base64')).toThrow(new Error('Invalid base64 length'))
|
|
245
|
+
// })
|
|
246
|
+
|
|
247
|
+
it('throws on invalid characters', () => {
|
|
248
|
+
expect(() => toArray('A?==', 'base64')).toThrow(new Error('Invalid base64 character at index 1'))
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
// it('throws when non-zero padding bits are present', () => {
|
|
252
|
+
// expect(() => toArray('QZ', 'base64')).toThrow(new Error('Invalid base64: non-zero padding bits'))
|
|
253
|
+
// })
|
|
254
|
+
})
|
|
255
|
+
|
|
212
256
|
describe('verifyNotNull', () => {
|
|
213
257
|
it('should return the value if it is not null or undefined', () => {
|
|
214
258
|
expect(verifyNotNull(42)).toBe(42)
|
package/src/primitives/index.ts
CHANGED
|
@@ -13,3 +13,4 @@ export { default as Random } from './Random.js'
|
|
|
13
13
|
export { default as TransactionSignature } from './TransactionSignature.js'
|
|
14
14
|
export { default as Polynomial, PointInFiniteField } from './Polynomial.js'
|
|
15
15
|
export { default as Schnorr } from './Schnorr.js'
|
|
16
|
+
export { default as Secp256r1 } from './Secp256r1.js'
|
package/src/primitives/utils.ts
CHANGED
|
@@ -104,23 +104,67 @@ const hexToArray = (msg: string): number[] => {
|
|
|
104
104
|
return res
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
'
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
export function base64ToArray (msg: string): number[] {
|
|
108
|
+
if (typeof msg !== 'string') {
|
|
109
|
+
throw new TypeError('msg must be a string')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// cleanse string
|
|
113
|
+
let s = msg.trim().replace(/[\r\n\t\f\v ]+/g, '')
|
|
114
|
+
s = s.replace(/-/g, '+').replace(/_/g, '/')
|
|
115
|
+
|
|
116
|
+
// ensure padding is correct
|
|
117
|
+
const padIndex = s.indexOf('=')
|
|
118
|
+
if (padIndex !== -1) {
|
|
119
|
+
const pad = s.slice(padIndex)
|
|
120
|
+
if (!/^={1,2}$/.test(pad)) {
|
|
121
|
+
throw new Error('Invalid base64 padding')
|
|
122
|
+
}
|
|
123
|
+
if (s.slice(0, padIndex).includes('=')) {
|
|
124
|
+
throw new Error('Invalid base64 padding')
|
|
125
|
+
}
|
|
126
|
+
s = s.slice(0, padIndex)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// if (s.length % 4 === 1)
|
|
130
|
+
// {
|
|
131
|
+
// throw new Error("Invalid base64 length")
|
|
132
|
+
// }
|
|
113
133
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
134
|
+
const result: number[] = []
|
|
135
|
+
let bitBuffer = 0
|
|
136
|
+
let bitCount = 0
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < s.length; i++) {
|
|
139
|
+
const c = s.charCodeAt(i)
|
|
140
|
+
// using ascii map values rather than indexOf
|
|
141
|
+
let v = -1
|
|
142
|
+
if (c >= 65 && c <= 90) {
|
|
143
|
+
v = c - 65 // A-Z
|
|
144
|
+
} else if (c >= 97 && c <= 122) {
|
|
145
|
+
v = c - 97 + 26 // a-z
|
|
146
|
+
} else if (c >= 48 && c <= 57) {
|
|
147
|
+
v = c - 48 + 52 // 0-9
|
|
148
|
+
} else if (c === 43) {
|
|
149
|
+
v = 62 // +
|
|
150
|
+
} else if (c === 47) {
|
|
151
|
+
v = 63 // /
|
|
152
|
+
} else {
|
|
153
|
+
throw new Error(`Invalid base64 character at index ${i}`)
|
|
154
|
+
}
|
|
155
|
+
bitBuffer = (bitBuffer << 6) | v
|
|
156
|
+
bitCount += 6
|
|
117
157
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
result.push((
|
|
121
|
-
|
|
158
|
+
while (bitCount >= 8) {
|
|
159
|
+
bitCount -= 8
|
|
160
|
+
result.push((bitBuffer >> bitCount) & 0xff)
|
|
161
|
+
bitBuffer &= (1 << bitCount) - 1
|
|
122
162
|
}
|
|
123
163
|
}
|
|
164
|
+
// check for valid padding bits
|
|
165
|
+
// if (bitCount !== 0 && bitBuffer !== 0) {
|
|
166
|
+
// throw new Error("Invalid base64: non-zero padding bits")
|
|
167
|
+
// }
|
|
124
168
|
|
|
125
169
|
return result
|
|
126
170
|
}
|