ox 1.2.0 → 1.3.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 +34 -0
- package/dist/core/AbiFunction.d.ts +1 -1
- package/dist/core/AbiFunction.d.ts.map +1 -1
- package/dist/core/AbiFunction.js +6 -2
- package/dist/core/AbiFunction.js.map +1 -1
- package/dist/core/AbiParameters.d.ts +1 -1
- package/dist/core/AbiParameters.d.ts.map +1 -1
- package/dist/core/AbiParameters.js +1 -13
- package/dist/core/AbiParameters.js.map +1 -1
- package/dist/core/AesGcm.d.ts +36 -1
- package/dist/core/AesGcm.d.ts.map +1 -1
- package/dist/core/AesGcm.js +44 -0
- package/dist/core/AesGcm.js.map +1 -1
- package/dist/core/Ed25519.d.ts +46 -1
- package/dist/core/Ed25519.d.ts.map +1 -1
- package/dist/core/Ed25519.js +43 -0
- package/dist/core/Ed25519.js.map +1 -1
- package/dist/core/Engine.d.ts +1 -1
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Hash.d.ts +164 -1
- package/dist/core/Hash.d.ts.map +1 -1
- package/dist/core/Hash.js +172 -0
- package/dist/core/Hash.js.map +1 -1
- package/dist/core/Hex.d.ts.map +1 -1
- package/dist/core/Hex.js +1 -4
- package/dist/core/Hex.js.map +1 -1
- package/dist/core/Rlp.d.ts +32 -0
- package/dist/core/Rlp.d.ts.map +1 -1
- package/dist/core/Rlp.js +146 -0
- package/dist/core/Rlp.js.map +1 -1
- package/dist/core/Secp256k1.d.ts +46 -1
- package/dist/core/Secp256k1.d.ts.map +1 -1
- package/dist/core/Secp256k1.js +48 -0
- package/dist/core/Secp256k1.js.map +1 -1
- package/dist/core/WebAuthn.d.ts +224 -0
- package/dist/core/WebAuthn.d.ts.map +1 -0
- package/dist/core/WebAuthn.js +343 -0
- package/dist/core/WebAuthn.js.map +1 -0
- package/dist/core/WebAuthnP256.d.ts +46 -50
- package/dist/core/WebAuthnP256.d.ts.map +1 -1
- package/dist/core/WebAuthnP256.js +44 -50
- package/dist/core/WebAuthnP256.js.map +1 -1
- package/dist/core/internal/abiParameters.d.ts +7 -87
- package/dist/core/internal/abiParameters.d.ts.map +1 -1
- package/dist/core/internal/abiParameters.js +339 -194
- package/dist/core/internal/abiParameters.js.map +1 -1
- package/dist/core/internal/engine.d.ts +35 -2
- package/dist/core/internal/engine.d.ts.map +1 -1
- package/dist/core/internal/engine.js +12 -1
- package/dist/core/internal/engine.js.map +1 -1
- package/dist/core/internal/hash.d.ts +10 -0
- package/dist/core/internal/hash.d.ts.map +1 -1
- package/dist/core/internal/hash.js +26 -1
- package/dist/core/internal/hash.js.map +1 -1
- package/dist/index.d.ts +23 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -14
- package/dist/index.js.map +1 -1
- package/dist/node/Engine.d.ts +1 -1
- package/dist/node/Engine.d.ts.map +1 -1
- package/dist/node/Engine.js.map +1 -1
- package/dist/node/Hash.d.ts +6 -4
- package/dist/node/Hash.d.ts.map +1 -1
- package/dist/node/Hash.js +35 -3
- package/dist/node/Hash.js.map +1 -1
- package/dist/tempo/AuthorizationTempo.d.ts +6 -6
- package/dist/tempo/AuthorizationTempo.js +6 -6
- package/dist/tempo/KeyAuthorization.d.ts +3 -8
- package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/KeyAuthorization.js +3 -8
- package/dist/tempo/KeyAuthorization.js.map +1 -1
- package/dist/tempo/SignatureEnvelope.d.ts +8 -8
- package/dist/tempo/SignatureEnvelope.js +8 -8
- package/dist/wasm/Engine.d.ts +1 -1
- package/dist/wasm/Engine.d.ts.map +1 -1
- package/dist/wasm/Engine.js.map +1 -1
- package/dist/wasm/Hash.d.ts +1 -1
- package/dist/wasm/Hash.d.ts.map +1 -1
- package/dist/wasm/Hash.js +5 -0
- package/dist/wasm/Hash.js.map +1 -1
- package/dist/wasm/internal/blake3.d.ts +10 -1
- package/dist/wasm/internal/blake3.d.ts.map +1 -1
- package/dist/wasm/internal/blake3.js +12 -1
- package/dist/wasm/internal/blake3.js.map +1 -1
- package/dist/wasm/internal/blake3.wasm.d.ts +3 -1
- package/dist/wasm/internal/blake3.wasm.d.ts.map +1 -1
- package/dist/wasm/internal/blake3.wasm.js +3 -1
- package/dist/wasm/internal/blake3.wasm.js.map +1 -1
- package/dist/wasm/internal/hash.d.ts +9 -0
- package/dist/wasm/internal/hash.d.ts.map +1 -1
- package/dist/wasm/internal/hash.js +13 -1
- package/dist/wasm/internal/hash.js.map +1 -1
- package/dist/wasm/internal/hashState.d.ts +25 -0
- package/dist/wasm/internal/hashState.d.ts.map +1 -0
- package/dist/wasm/internal/hashState.js +105 -0
- package/dist/wasm/internal/hashState.js.map +1 -0
- package/dist/wasm/internal/hashes.d.ts +12 -1
- package/dist/wasm/internal/hashes.d.ts.map +1 -1
- package/dist/wasm/internal/hashes.js +18 -1
- package/dist/wasm/internal/hashes.js.map +1 -1
- package/dist/wasm/internal/hashes.wasm.d.ts +11 -3
- package/dist/wasm/internal/hashes.wasm.d.ts.map +1 -1
- package/dist/wasm/internal/hashes.wasm.js +11 -3
- package/dist/wasm/internal/hashes.wasm.js.map +1 -1
- package/dist/webauthn/Authentication.d.ts +2 -0
- package/dist/webauthn/Authentication.d.ts.map +1 -1
- package/dist/webauthn/Authentication.js +2 -1
- package/dist/webauthn/Authentication.js.map +1 -1
- package/dist/webauthn/Registration.d.ts.map +1 -1
- package/dist/webauthn/Registration.js +2 -1
- package/dist/webauthn/Registration.js.map +1 -1
- package/dist/webauthn/Types.d.ts +27 -6
- package/dist/webauthn/Types.d.ts.map +1 -1
- package/dist/webauthn/internal/utils.d.ts.map +1 -1
- package/dist/webauthn/internal/utils.js +35 -9
- package/dist/webauthn/internal/utils.js.map +1 -1
- package/dist/zod/TypedData.d.ts +2 -2
- package/package.json +9 -1
- package/src/benchmarks/Ox.bench.ts +6 -0
- package/src/benchmarks/OxNode.bench.ts +8 -0
- package/src/benchmarks/OxV0.bench.ts +4 -0
- package/src/benchmarks/OxWasm.bench.ts +22 -0
- package/src/core/AbiFunction.ts +5 -5
- package/src/core/AbiParameters.ts +8 -20
- package/src/core/AesGcm.ts +86 -1
- package/src/core/Ed25519.ts +87 -1
- package/src/core/Engine.ts +1 -0
- package/src/core/Hash.ts +262 -1
- package/src/core/Hex.ts +1 -4
- package/src/core/Rlp.ts +172 -5
- package/src/core/Secp256k1.ts +92 -1
- package/src/core/WebAuthn.ts +579 -0
- package/src/core/WebAuthnP256.ts +47 -50
- package/src/core/_test/AbiFunction.test.ts +9 -0
- package/src/core/_test/AbiParameters.differential.fuzz.ts +156 -0
- package/src/core/_test/AbiParameters.encode.test.ts +22 -0
- package/src/core/_test/AbiParameters.fuzz.ts +4 -0
- package/src/core/_test/AesGcm.test.ts +76 -0
- package/src/core/_test/Ed25519.test-d.ts +13 -1
- package/src/core/_test/Ed25519.test.ts +120 -1
- package/src/core/_test/Engine.test-d.ts +9 -0
- package/src/core/_test/Engine.test.ts +1 -1
- package/src/core/_test/Hash.test-d.ts +23 -0
- package/src/core/_test/Hash.test.ts +179 -1
- package/src/core/_test/Hex.test.ts +2 -0
- package/src/core/_test/Rlp.test-d.ts +18 -0
- package/src/core/_test/Rlp.test.ts +109 -0
- package/src/core/_test/Secp256k1.test-d.ts +14 -0
- package/src/core/_test/Secp256k1.test.ts +91 -1
- package/src/core/_test/WebAuthn.test-d.ts +142 -0
- package/src/core/_test/WebAuthn.test.ts +766 -0
- package/src/core/_test/index.test.ts +1 -0
- package/src/core/internal/abiParameters.ts +466 -326
- package/src/core/internal/engine.ts +47 -2
- package/src/core/internal/hash.ts +53 -1
- package/src/index.ts +24 -14
- package/src/node/Engine.ts +1 -0
- package/src/node/Hash.ts +43 -6
- package/src/node/_test/Engine.test-d.ts +1 -0
- package/src/node/_test/Engine.test.ts +2 -0
- package/src/node/_test/Hash.test-d.ts +10 -0
- package/src/node/_test/Hash.test.ts +46 -0
- package/src/tempo/AuthorizationTempo.ts +6 -6
- package/src/tempo/KeyAuthorization.ts +3 -8
- package/src/tempo/SignatureEnvelope.ts +8 -8
- package/src/version.ts +1 -1
- package/src/wasm/Engine.ts +1 -0
- package/src/wasm/Hash.ts +10 -0
- package/src/wasm/_test/Engine.test-d.ts +1 -0
- package/src/wasm/_test/Engine.test.ts +5 -0
- package/src/wasm/_test/Hash.browser.test.ts +28 -0
- package/src/wasm/_test/Hash.test-d.ts +16 -0
- package/src/wasm/_test/Hash.test.ts +250 -1
- package/src/wasm/internal/blake3.ts +23 -2
- package/src/wasm/internal/blake3.wasm.ts +4 -1
- package/src/wasm/internal/hash.ts +27 -1
- package/src/wasm/internal/hashState.ts +154 -0
- package/src/wasm/internal/hashes.ts +56 -15
- package/src/wasm/internal/hashes.wasm.ts +15 -3
- package/src/webauthn/Authentication.ts +4 -0
- package/src/webauthn/Registration.ts +2 -0
- package/src/webauthn/Types.ts +32 -2
- package/src/webauthn/_test/Authentication.test.ts +123 -1
- package/src/webauthn/_test/Registration.test.ts +26 -6
- package/src/webauthn/_test/WebAuthn.browser.test.ts +28 -0
- package/src/webauthn/internal/utils.ts +41 -8
|
@@ -136,5 +136,14 @@ describe('Engine', () => {
|
|
|
136
136
|
expectTypeOf<NonNullable<Engine.Hash['keccak256']>>().toEqualTypeOf<
|
|
137
137
|
(input: Uint8Array) => Uint8Array
|
|
138
138
|
>()
|
|
139
|
+
expectTypeOf<NonNullable<Engine.Hash['createKeccak256']>>().toEqualTypeOf<
|
|
140
|
+
() => Engine.HashState
|
|
141
|
+
>()
|
|
142
|
+
expectTypeOf<Engine.HashState>().toEqualTypeOf<{
|
|
143
|
+
clone(): Engine.HashState
|
|
144
|
+
destroy(): void
|
|
145
|
+
digestInto(output: Uint8Array): void
|
|
146
|
+
update(input: Uint8Array): void
|
|
147
|
+
}>()
|
|
139
148
|
})
|
|
140
149
|
})
|
|
@@ -266,7 +266,7 @@ describe('set', () => {
|
|
|
266
266
|
).toThrowErrorMatchingInlineSnapshot(`
|
|
267
267
|
[Engine.UnknownPrimitiveError: \`keccak_256\` is not a valid primitive for the \`Hash\` slot.
|
|
268
268
|
|
|
269
|
-
Valid primitives: blake3, hmacSha256, keccak256, ripemd160, sha256]
|
|
269
|
+
Valid primitives: blake3, createBlake3, createHmacSha256, createKeccak256, createRipemd160, createSha256, hmacSha256, keccak256, ripemd160, sha256]
|
|
270
270
|
`)
|
|
271
271
|
})
|
|
272
272
|
|
|
@@ -16,3 +16,26 @@ describe('blake3', () => {
|
|
|
16
16
|
).toEqualTypeOf<Hex.Hex>()
|
|
17
17
|
})
|
|
18
18
|
})
|
|
19
|
+
|
|
20
|
+
describe('incremental', () => {
|
|
21
|
+
test('factories', () => {
|
|
22
|
+
expectTypeOf(Hash.createBlake3()).toEqualTypeOf<Hash.Hasher>()
|
|
23
|
+
expectTypeOf(Hash.createHmac256('0x')).toEqualTypeOf<Hash.Hasher>()
|
|
24
|
+
expectTypeOf(Hash.createKeccak256()).toEqualTypeOf<Hash.Hasher>()
|
|
25
|
+
expectTypeOf(Hash.createRipemd160()).toEqualTypeOf<Hash.Hasher>()
|
|
26
|
+
expectTypeOf(Hash.createSha256()).toEqualTypeOf<Hash.Hasher>()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('Hasher', () => {
|
|
30
|
+
const hash = Hash.createSha256()
|
|
31
|
+
|
|
32
|
+
expectTypeOf(hash.update('0x')).toEqualTypeOf<Hash.Hasher>()
|
|
33
|
+
expectTypeOf(hash.update(new Uint8Array())).toEqualTypeOf<Hash.Hasher>()
|
|
34
|
+
expectTypeOf(hash.clone()).toEqualTypeOf<Hash.Hasher>()
|
|
35
|
+
expectTypeOf(hash.digest()).toEqualTypeOf<Hex.Hex>()
|
|
36
|
+
expectTypeOf(hash.digest({ as: 'Hex' })).toEqualTypeOf<Hex.Hex>()
|
|
37
|
+
expectTypeOf(hash.digest({ as: 'Bytes' })).toEqualTypeOf<Bytes.Bytes>()
|
|
38
|
+
expectTypeOf(hash.digestInto(new Uint8Array())).toEqualTypeOf<void>()
|
|
39
|
+
expectTypeOf(hash.destroy()).toEqualTypeOf<void>()
|
|
40
|
+
})
|
|
41
|
+
})
|
|
@@ -1,6 +1,151 @@
|
|
|
1
|
-
import { Engine, Hash } from 'ox'
|
|
1
|
+
import { type Bytes, Engine, Hash, type Hex } from 'ox'
|
|
2
2
|
import { describe, expect, test } from 'vp/test'
|
|
3
3
|
|
|
4
|
+
type Factory = {
|
|
5
|
+
create(): Hash.Hasher
|
|
6
|
+
digest(value: Hex.Hex | Bytes.Bytes): Bytes.Bytes
|
|
7
|
+
digestSize: number
|
|
8
|
+
name: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const factories = [
|
|
12
|
+
{
|
|
13
|
+
create: () => Hash.createBlake3(),
|
|
14
|
+
digest: (value) => Hash.blake3(value, { as: 'Bytes' }),
|
|
15
|
+
digestSize: 32,
|
|
16
|
+
name: 'createBlake3',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
create: () => Hash.createHmac256('0x6b6579'),
|
|
20
|
+
digest: (value) => Hash.hmac256('0x6b6579', value, { as: 'Bytes' }),
|
|
21
|
+
digestSize: 32,
|
|
22
|
+
name: 'createHmac256',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
create: () => Hash.createKeccak256(),
|
|
26
|
+
digest: (value) => Hash.keccak256(value, { as: 'Bytes' }),
|
|
27
|
+
digestSize: 32,
|
|
28
|
+
name: 'createKeccak256',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
create: () => Hash.createRipemd160(),
|
|
32
|
+
digest: (value) => Hash.ripemd160(value, { as: 'Bytes' }),
|
|
33
|
+
digestSize: 20,
|
|
34
|
+
name: 'createRipemd160',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
create: () => Hash.createSha256(),
|
|
38
|
+
digest: (value) => Hash.sha256(value, { as: 'Bytes' }),
|
|
39
|
+
digestSize: 32,
|
|
40
|
+
name: 'createSha256',
|
|
41
|
+
},
|
|
42
|
+
] as const satisfies readonly Factory[]
|
|
43
|
+
|
|
44
|
+
describe('incremental', () => {
|
|
45
|
+
test.each(factories)(
|
|
46
|
+
'$name: chunked input matches the one-shot function',
|
|
47
|
+
({ create, digest }) => {
|
|
48
|
+
const hash = create()
|
|
49
|
+
hash.update('0xdead').update(Uint8Array.of(0xbe, 0xef))
|
|
50
|
+
expect(hash.digest({ as: 'Bytes' })).toEqual(digest('0xdeadbeef'))
|
|
51
|
+
},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
test.each(factories)(
|
|
55
|
+
'$name: empty input matches the one-shot function',
|
|
56
|
+
({ create, digest }) => {
|
|
57
|
+
expect(create().digest({ as: 'Bytes' })).toEqual(digest('0x'))
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
test.each(factories)(
|
|
62
|
+
'$name: clone branches from the same prefix',
|
|
63
|
+
({ create, digest }) => {
|
|
64
|
+
const first = create().update('0xdead')
|
|
65
|
+
const second = first.clone()
|
|
66
|
+
|
|
67
|
+
first.update('0xbeef')
|
|
68
|
+
second.update('0xcafe')
|
|
69
|
+
|
|
70
|
+
expect(first.digest({ as: 'Bytes' })).toEqual(digest('0xdeadbeef'))
|
|
71
|
+
expect(second.digest({ as: 'Bytes' })).toEqual(digest('0xdeadcafe'))
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
test.each(factories)(
|
|
76
|
+
'$name: digestInto validates bounds without consuming the state',
|
|
77
|
+
({ create, digest, digestSize }) => {
|
|
78
|
+
const hash = create().update('0xdeadbeef')
|
|
79
|
+
|
|
80
|
+
expect(() =>
|
|
81
|
+
hash.digestInto(new Uint8Array(digestSize - 1)),
|
|
82
|
+
).toThrowError(Hash.InvalidDigestSizeError)
|
|
83
|
+
|
|
84
|
+
const output = new Uint8Array(digestSize + 2).fill(0xff)
|
|
85
|
+
hash.digestInto(output)
|
|
86
|
+
|
|
87
|
+
expect(output.slice(0, digestSize)).toEqual(digest('0xdeadbeef'))
|
|
88
|
+
expect(output.slice(digestSize)).toEqual(Uint8Array.of(0xff, 0xff))
|
|
89
|
+
expect(() => hash.update('0x')).toThrowError(Hash.HasherDestroyedError)
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
test.each(factories)(
|
|
94
|
+
'$name: digest consumes the state and destroy is idempotent',
|
|
95
|
+
({ create }) => {
|
|
96
|
+
const hash = create()
|
|
97
|
+
hash.digest()
|
|
98
|
+
|
|
99
|
+
expect(() => hash.clone()).toThrowError(Hash.HasherDestroyedError)
|
|
100
|
+
expect(() => hash.digest()).toThrowError(Hash.HasherDestroyedError)
|
|
101
|
+
expect(() => hash.digestInto(new Uint8Array(32))).toThrowError(
|
|
102
|
+
Hash.HasherDestroyedError,
|
|
103
|
+
)
|
|
104
|
+
expect(() => hash.update('0x')).toThrowError(Hash.HasherDestroyedError)
|
|
105
|
+
expect(() => {
|
|
106
|
+
hash.destroy()
|
|
107
|
+
hash.destroy()
|
|
108
|
+
}).not.toThrow()
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
test.each(factories)(
|
|
113
|
+
'$name: destroy consumes the state and is idempotent',
|
|
114
|
+
({ create }) => {
|
|
115
|
+
const hash = create()
|
|
116
|
+
hash.destroy()
|
|
117
|
+
hash.destroy()
|
|
118
|
+
|
|
119
|
+
expect(() => hash.clone()).toThrowError(Hash.HasherDestroyedError)
|
|
120
|
+
expect(() => hash.digest()).toThrowError(Hash.HasherDestroyedError)
|
|
121
|
+
expect(() => hash.update('0x')).toThrowError(Hash.HasherDestroyedError)
|
|
122
|
+
},
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
test('behavior: independent states can be interleaved', () => {
|
|
126
|
+
const first = Hash.createSha256().update('0xaa')
|
|
127
|
+
const second = Hash.createSha256().update('0xbb')
|
|
128
|
+
|
|
129
|
+
first.update('0xcc')
|
|
130
|
+
second.update('0xdd')
|
|
131
|
+
|
|
132
|
+
expect(first.digest()).toEqual(Hash.sha256('0xaacc'))
|
|
133
|
+
expect(second.digest()).toEqual(Hash.sha256('0xbbdd'))
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('behavior: a state keeps the provider captured at creation', () => {
|
|
137
|
+
Engine.set({ Hash: { createSha256: () => sentinelState(1) } })
|
|
138
|
+
const first = Hash.createSha256()
|
|
139
|
+
|
|
140
|
+
Engine.set({ Hash: { createSha256: () => sentinelState(2) } })
|
|
141
|
+
|
|
142
|
+
expect(first.digest({ as: 'Bytes' })[0]).toMatchInlineSnapshot('1')
|
|
143
|
+
expect(
|
|
144
|
+
Hash.createSha256().digest({ as: 'Bytes' })[0],
|
|
145
|
+
).toMatchInlineSnapshot('2')
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
4
149
|
describe('blake3', () => {
|
|
5
150
|
test('default', () => {
|
|
6
151
|
expect(Hash.blake3('0x')).toMatchInlineSnapshot(
|
|
@@ -625,15 +770,48 @@ describe('validate', () => {
|
|
|
625
770
|
})
|
|
626
771
|
})
|
|
627
772
|
|
|
773
|
+
function sentinelState(byte: number): Engine.HashState {
|
|
774
|
+
let active = true
|
|
775
|
+
|
|
776
|
+
const assertActive = () => {
|
|
777
|
+
if (!active) throw new Error('destroyed')
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return {
|
|
781
|
+
clone: () => {
|
|
782
|
+
assertActive()
|
|
783
|
+
return sentinelState(byte)
|
|
784
|
+
},
|
|
785
|
+
destroy: () => {
|
|
786
|
+
active = false
|
|
787
|
+
},
|
|
788
|
+
digestInto: (output) => {
|
|
789
|
+
assertActive()
|
|
790
|
+
active = false
|
|
791
|
+
output.fill(byte, 0, 32)
|
|
792
|
+
},
|
|
793
|
+
update: () => {
|
|
794
|
+
assertActive()
|
|
795
|
+
},
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
628
799
|
test('exports', () => {
|
|
629
800
|
expect(Object.keys(Hash)).toMatchInlineSnapshot(`
|
|
630
801
|
[
|
|
802
|
+
"createBlake3",
|
|
803
|
+
"createHmac256",
|
|
804
|
+
"createKeccak256",
|
|
805
|
+
"createRipemd160",
|
|
806
|
+
"createSha256",
|
|
631
807
|
"blake3",
|
|
632
808
|
"keccak256",
|
|
633
809
|
"hmac256",
|
|
634
810
|
"ripemd160",
|
|
635
811
|
"sha256",
|
|
636
812
|
"validate",
|
|
813
|
+
"HasherDestroyedError",
|
|
814
|
+
"InvalidDigestSizeError",
|
|
637
815
|
]
|
|
638
816
|
`)
|
|
639
817
|
})
|
|
@@ -30,6 +30,8 @@ describe('assert', () => {
|
|
|
30
30
|
|
|
31
31
|
describe('concat', () => {
|
|
32
32
|
test('default', () => {
|
|
33
|
+
expect(Hex.concat()).toMatchInlineSnapshot(`"0x"`)
|
|
34
|
+
expect(Hex.concat('0x1234')).toMatchInlineSnapshot(`"0x1234"`)
|
|
33
35
|
expect(Hex.concat('0x0', '0x1')).toBe('0x01')
|
|
34
36
|
expect(Hex.concat('0x1', '0x69', '0x420')).toBe('0x169420')
|
|
35
37
|
expect(Hex.concat('0x00000001', '0x00000069', '0x00000420')).toBe(
|
|
@@ -2,6 +2,24 @@ import { Bytes, type Hex, Rlp } from 'ox'
|
|
|
2
2
|
import { describe, expectTypeOf, test } from 'vp/test'
|
|
3
3
|
import type { RecursiveArray } from '../internal/types.js'
|
|
4
4
|
|
|
5
|
+
describe('Rlp.encodeTo', () => {
|
|
6
|
+
test('write', () => {
|
|
7
|
+
expectTypeOf(
|
|
8
|
+
Rlp.encodeTo('0x', (value) => {
|
|
9
|
+
expectTypeOf(value).toEqualTypeOf<Bytes.Bytes>()
|
|
10
|
+
}),
|
|
11
|
+
).toEqualTypeOf<void>()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('rejects asynchronous callbacks', () => {
|
|
15
|
+
Rlp.encodeTo(
|
|
16
|
+
'0x',
|
|
17
|
+
// @ts-expect-error RLP write callbacks are synchronous.
|
|
18
|
+
async () => {},
|
|
19
|
+
)
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
5
23
|
describe('Rlp.to', () => {
|
|
6
24
|
test('default', () => {
|
|
7
25
|
expectTypeOf(Rlp.toHex('0x')).toEqualTypeOf<RecursiveArray<Hex.Hex>>()
|
|
@@ -10,6 +10,7 @@ test('exports', () => {
|
|
|
10
10
|
"decodeRlpCursor",
|
|
11
11
|
"readLength",
|
|
12
12
|
"readList",
|
|
13
|
+
"encodeTo",
|
|
13
14
|
"from",
|
|
14
15
|
"fromBytes",
|
|
15
16
|
"fromHex",
|
|
@@ -32,6 +33,114 @@ const generateList = (length: number) => {
|
|
|
32
33
|
return bytes
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
function encodeToBytes(value: Parameters<typeof Rlp.encodeTo>[0]) {
|
|
37
|
+
const chunks: Uint8Array[] = []
|
|
38
|
+
Rlp.encodeTo(value, (chunk) => {
|
|
39
|
+
chunks.push(chunk)
|
|
40
|
+
})
|
|
41
|
+
return Bytes.concat(...chunks)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe('Rlp.encodeTo', () => {
|
|
45
|
+
test('matches `from` for nested Hex values', () => {
|
|
46
|
+
const values = [
|
|
47
|
+
'0x',
|
|
48
|
+
'0x00',
|
|
49
|
+
'0x7f',
|
|
50
|
+
'0x80',
|
|
51
|
+
'0x1',
|
|
52
|
+
'0xabc',
|
|
53
|
+
['0x', '0x80', ['0x1', `0x${'ab'.repeat(56)}`], `0x${'cd'.repeat(256)}`],
|
|
54
|
+
] as const
|
|
55
|
+
|
|
56
|
+
for (const value of values)
|
|
57
|
+
expect(encodeToBytes(value)).toEqual(Rlp.from(value, { as: 'Bytes' }))
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('matches `from` for nested byte values', () => {
|
|
61
|
+
const values = [
|
|
62
|
+
new Uint8Array(),
|
|
63
|
+
Uint8Array.of(0),
|
|
64
|
+
Uint8Array.of(0x7f),
|
|
65
|
+
Uint8Array.of(0x80),
|
|
66
|
+
[
|
|
67
|
+
new Uint8Array(),
|
|
68
|
+
Uint8Array.of(0x80),
|
|
69
|
+
[generateBytes(56), generateBytes(256)],
|
|
70
|
+
],
|
|
71
|
+
] as const
|
|
72
|
+
|
|
73
|
+
for (const value of values)
|
|
74
|
+
expect(encodeToBytes(value)).toEqual(Rlp.from(value, { as: 'Bytes' }))
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('does not mutate emitted chunks', () => {
|
|
78
|
+
const chunks: Uint8Array[] = []
|
|
79
|
+
const values: Hex.Hex[] = []
|
|
80
|
+
|
|
81
|
+
Rlp.encodeTo(['0x01', `0x${'ab'.repeat(16_386)}`, '0x80'], (chunk) => {
|
|
82
|
+
chunks.push(chunk)
|
|
83
|
+
values.push(Hex.fromBytes(chunk))
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
expect(chunks.map((chunk) => Hex.fromBytes(chunk))).toEqual(values)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('passes byte leaves without copying', () => {
|
|
90
|
+
const leaf = generateBytes(64)
|
|
91
|
+
let found = false
|
|
92
|
+
|
|
93
|
+
Rlp.encodeTo(leaf, (chunk) => {
|
|
94
|
+
if (chunk === leaf) found = true
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
expect(found).toMatchInlineSnapshot(`true`)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('validates every Hex leaf before writing', () => {
|
|
101
|
+
let writes = 0
|
|
102
|
+
|
|
103
|
+
expect(() =>
|
|
104
|
+
Rlp.encodeTo(['0x01', ['0x02', '0xzz' as Hex.Hex]], () => {
|
|
105
|
+
writes++
|
|
106
|
+
}),
|
|
107
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
108
|
+
`[BaseError: Invalid hex string \`0xzz\`.]`,
|
|
109
|
+
)
|
|
110
|
+
expect(writes).toMatchInlineSnapshot(`0`)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('propagates callback errors after partial output', () => {
|
|
114
|
+
const chunks: Uint8Array[] = []
|
|
115
|
+
|
|
116
|
+
expect(() =>
|
|
117
|
+
Rlp.encodeTo([Uint8Array.of(1), Uint8Array.of(2)], (chunk) => {
|
|
118
|
+
chunks.push(chunk)
|
|
119
|
+
if (chunks.length === 2) throw new Error('write failed')
|
|
120
|
+
}),
|
|
121
|
+
).toThrowErrorMatchingInlineSnapshot(`[Error: write failed]`)
|
|
122
|
+
expect(chunks.map((chunk) => Hex.fromBytes(chunk))).toMatchInlineSnapshot(`
|
|
123
|
+
[
|
|
124
|
+
"0xc2",
|
|
125
|
+
"0x01",
|
|
126
|
+
]
|
|
127
|
+
`)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test('snapshots list structure before writing', () => {
|
|
131
|
+
const value: Hex.Hex[] = ['0x01']
|
|
132
|
+
const expected = Rlp.from(value, { as: 'Bytes' })
|
|
133
|
+
const chunks: Uint8Array[] = []
|
|
134
|
+
|
|
135
|
+
Rlp.encodeTo(value, (chunk) => {
|
|
136
|
+
chunks.push(chunk)
|
|
137
|
+
if (chunks.length === 1) value.push('0x02')
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
expect(Bytes.concat(...chunks)).toEqual(expected)
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
35
144
|
describe('Rlp.to', () => {
|
|
36
145
|
test('no bytes', () => {
|
|
37
146
|
const rlpHex = '0x'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Bytes, type Hex, Secp256k1 } from 'ox'
|
|
2
|
+
import { expectTypeOf, test } from 'vp/test'
|
|
3
|
+
|
|
4
|
+
test('fromPrf', () => {
|
|
5
|
+
const prf =
|
|
6
|
+
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
|
|
7
|
+
|
|
8
|
+
expectTypeOf(Secp256k1.fromPrf(prf)).toEqualTypeOf<Hex.Hex>()
|
|
9
|
+
expectTypeOf(Secp256k1.fromPrf(new Uint8Array(32))).toEqualTypeOf<Hex.Hex>()
|
|
10
|
+
expectTypeOf(Secp256k1.fromPrf(prf, { as: 'Hex' })).toEqualTypeOf<Hex.Hex>()
|
|
11
|
+
expectTypeOf(
|
|
12
|
+
Secp256k1.fromPrf(prf, { as: 'Bytes' }),
|
|
13
|
+
).toEqualTypeOf<Bytes.Bytes>()
|
|
14
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Address, Bytes, Hex, PublicKey, Secp256k1 } from 'ox'
|
|
1
|
+
import { Address, Bytes, Engine, Hex, PublicKey, Secp256k1 } from 'ox'
|
|
2
2
|
import { describe, expect, test } from 'vp/test'
|
|
3
3
|
import { accounts } from '../../../test/constants/accounts.js'
|
|
4
4
|
|
|
@@ -123,6 +123,94 @@ describe('createKeyPair', () => {
|
|
|
123
123
|
})
|
|
124
124
|
})
|
|
125
125
|
|
|
126
|
+
describe('fromPrf', () => {
|
|
127
|
+
test.each([
|
|
128
|
+
{
|
|
129
|
+
privateKey:
|
|
130
|
+
'0xd23e6d2bbfdc109c1d4706bc3a78a32356682a4e6ef09c3bbeabf5460235de36',
|
|
131
|
+
prf: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
privateKey:
|
|
135
|
+
'0xcf0eab798d92f274ee2b9b6f3397186cdd3a08b09c77b7313fb1abf529611bc5',
|
|
136
|
+
prf: '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
|
|
137
|
+
},
|
|
138
|
+
] as const)('vector: $prf', ({ privateKey, prf }) => {
|
|
139
|
+
expect(Secp256k1.fromPrf(prf)).toBe(privateKey)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('value: Bytes', () => {
|
|
143
|
+
const prf = Bytes.fromHex(
|
|
144
|
+
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
|
|
145
|
+
)
|
|
146
|
+
expect(Secp256k1.fromPrf(prf)).toMatchInlineSnapshot(
|
|
147
|
+
`"0xcf0eab798d92f274ee2b9b6f3397186cdd3a08b09c77b7313fb1abf529611bc5"`,
|
|
148
|
+
)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('options: as', () => {
|
|
152
|
+
const privateKey = Secp256k1.fromPrf(
|
|
153
|
+
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
|
|
154
|
+
{ as: 'Bytes' },
|
|
155
|
+
)
|
|
156
|
+
expect(privateKey).toEqual(
|
|
157
|
+
Bytes.fromHex(
|
|
158
|
+
'0xcf0eab798d92f274ee2b9b6f3397186cdd3a08b09c77b7313fb1abf529611bc5',
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test('behavior: output is a valid secp256k1 private key', () => {
|
|
164
|
+
const privateKey = Secp256k1.fromPrf(Bytes.random(32), { as: 'Bytes' })
|
|
165
|
+
expect(Secp256k1.noble.utils.isValidSecretKey(privateKey)).toBe(true)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('behavior: skips invalid scalar candidates', () => {
|
|
169
|
+
const messages: Hex.Hex[] = []
|
|
170
|
+
const privateKey = Engine.with(
|
|
171
|
+
{
|
|
172
|
+
Hash: {
|
|
173
|
+
hmacSha256: (_key, message) => {
|
|
174
|
+
messages.push(Hex.fromBytes(message))
|
|
175
|
+
if (messages.length === 1) return new Uint8Array(32)
|
|
176
|
+
return Bytes.fromHex(
|
|
177
|
+
'0xef3f982137910a4c362017e69ba173fbb342325802ec827767aa6942027af35b',
|
|
178
|
+
)
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
() =>
|
|
183
|
+
Secp256k1.fromPrf(
|
|
184
|
+
'0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
185
|
+
),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
expect(privateKey).toBe(
|
|
189
|
+
'0xef3f982137910a4c362017e69ba173fbb342325802ec827767aa6942027af35b',
|
|
190
|
+
)
|
|
191
|
+
expect(messages).toMatchInlineSnapshot(`
|
|
192
|
+
[
|
|
193
|
+
"0x6f782e736563703235366b312e66726f6d5072662e763100000000",
|
|
194
|
+
"0x6f782e736563703235366b312e66726f6d5072662e763100000001",
|
|
195
|
+
]
|
|
196
|
+
`)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
test('error: PRF output is too short', () => {
|
|
200
|
+
expect(() => Secp256k1.fromPrf(new Uint8Array(31)))
|
|
201
|
+
.toThrowErrorMatchingInlineSnapshot(`
|
|
202
|
+
[Secp256k1.InvalidPrfSizeError: PRF output must be exactly 32 bytes. Received 31 bytes.]
|
|
203
|
+
`)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('error: PRF output is too long', () => {
|
|
207
|
+
expect(() => Secp256k1.fromPrf(new Uint8Array(33)))
|
|
208
|
+
.toThrowErrorMatchingInlineSnapshot(`
|
|
209
|
+
[Secp256k1.InvalidPrfSizeError: PRF output must be exactly 32 bytes. Received 33 bytes.]
|
|
210
|
+
`)
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
126
214
|
describe('getSharedSecret', () => {
|
|
127
215
|
test('default', () => {
|
|
128
216
|
const privateKeyA = accounts[0].privateKey
|
|
@@ -483,6 +571,7 @@ test('exports', () => {
|
|
|
483
571
|
[
|
|
484
572
|
"noble",
|
|
485
573
|
"createKeyPair",
|
|
574
|
+
"fromPrf",
|
|
486
575
|
"getPublicKey",
|
|
487
576
|
"getSharedSecret",
|
|
488
577
|
"randomPrivateKey",
|
|
@@ -490,6 +579,7 @@ test('exports', () => {
|
|
|
490
579
|
"recoverPublicKey",
|
|
491
580
|
"sign",
|
|
492
581
|
"verify",
|
|
582
|
+
"InvalidPrfSizeError",
|
|
493
583
|
]
|
|
494
584
|
`)
|
|
495
585
|
})
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Bytes,
|
|
3
|
+
type Hex,
|
|
4
|
+
Secp256k1,
|
|
5
|
+
WebAuthn,
|
|
6
|
+
type WebAuthnP256,
|
|
7
|
+
} from 'ox'
|
|
8
|
+
import { describe, expectTypeOf, test } from 'vp/test'
|
|
9
|
+
|
|
10
|
+
declare const input: Bytes.Bytes
|
|
11
|
+
|
|
12
|
+
describe('createCredential', () => {
|
|
13
|
+
test('behavior: returns required PRF output when requested', () => {
|
|
14
|
+
const infer = async () => {
|
|
15
|
+
const credential = await WebAuthn.createCredential({
|
|
16
|
+
name: 'Example',
|
|
17
|
+
prf: true,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
expectTypeOf(credential.prf).toEqualTypeOf<Bytes.Bytes>()
|
|
21
|
+
expectTypeOf(Secp256k1.fromPrf(credential.prf)).toEqualTypeOf<Hex.Hex>()
|
|
22
|
+
}
|
|
23
|
+
expectTypeOf(infer).toBeFunction()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('behavior: preserves ordinary credential return type', () => {
|
|
27
|
+
const infer = async () => {
|
|
28
|
+
const credential = await WebAuthn.createCredential({
|
|
29
|
+
name: 'Example',
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
expectTypeOf(credential).toEqualTypeOf<WebAuthnP256.P256Credential>()
|
|
33
|
+
// @ts-expect-error Ordinary credential creation has no PRF output.
|
|
34
|
+
void credential.prf
|
|
35
|
+
}
|
|
36
|
+
expectTypeOf(infer).toBeFunction()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('behavior: accepts custom input', () => {
|
|
40
|
+
const infer = async () => {
|
|
41
|
+
const credential = await WebAuthn.createCredential({
|
|
42
|
+
name: 'Example',
|
|
43
|
+
prf: {
|
|
44
|
+
input,
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
expectTypeOf(credential.prf).toEqualTypeOf<Bytes.Bytes>()
|
|
49
|
+
}
|
|
50
|
+
expectTypeOf(infer).toBeFunction()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('error: managed options', () => {
|
|
54
|
+
const managedExtension = () =>
|
|
55
|
+
// @ts-expect-error The PRF extension is managed by createCredential.
|
|
56
|
+
WebAuthn.createCredential({
|
|
57
|
+
extensions: {
|
|
58
|
+
prf: {
|
|
59
|
+
eval: {
|
|
60
|
+
first: input,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
name: 'Example',
|
|
65
|
+
prf: true,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const userVerification = () =>
|
|
69
|
+
WebAuthn.createCredential({
|
|
70
|
+
authenticatorSelection: {
|
|
71
|
+
// @ts-expect-error User verification is always required.
|
|
72
|
+
userVerification: 'preferred',
|
|
73
|
+
},
|
|
74
|
+
name: 'Example',
|
|
75
|
+
prf: true,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const rawOptions = {
|
|
79
|
+
name: 'Example',
|
|
80
|
+
prf: true,
|
|
81
|
+
publicKey: {},
|
|
82
|
+
} as const
|
|
83
|
+
const publicKey = () =>
|
|
84
|
+
// @ts-expect-error Raw credential options cannot be combined with PRF.
|
|
85
|
+
WebAuthn.createCredential(rawOptions)
|
|
86
|
+
|
|
87
|
+
expectTypeOf(managedExtension).toBeFunction()
|
|
88
|
+
expectTypeOf(publicKey).toBeFunction()
|
|
89
|
+
expectTypeOf(userVerification).toBeFunction()
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
describe('getCredential', () => {
|
|
94
|
+
test('behavior: returns required PRF output', () => {
|
|
95
|
+
const infer = async () => {
|
|
96
|
+
const credential = await WebAuthn.getCredential({
|
|
97
|
+
prf: true,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
expectTypeOf(credential.prf).toEqualTypeOf<Bytes.Bytes>()
|
|
101
|
+
expectTypeOf(credential.id).toEqualTypeOf<string>()
|
|
102
|
+
expectTypeOf(credential.raw).toEqualTypeOf<WebAuthn.PublicKeyCredential>()
|
|
103
|
+
// @ts-expect-error PRF requests do not parse the assertion signature.
|
|
104
|
+
void credential.signature
|
|
105
|
+
}
|
|
106
|
+
expectTypeOf(infer).toBeFunction()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('error: managed options', () => {
|
|
110
|
+
const managedExtension = () =>
|
|
111
|
+
WebAuthn.getCredential({
|
|
112
|
+
extensions: {
|
|
113
|
+
// @ts-expect-error The PRF extension is managed by getCredential.
|
|
114
|
+
prf: {
|
|
115
|
+
eval: {
|
|
116
|
+
first: input,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
prf: true,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const userVerification = () =>
|
|
124
|
+
WebAuthn.getCredential({
|
|
125
|
+
prf: true,
|
|
126
|
+
// @ts-expect-error User verification is always required.
|
|
127
|
+
userVerification: 'preferred',
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const rawOptions = {
|
|
131
|
+
prf: true,
|
|
132
|
+
publicKey: {},
|
|
133
|
+
} as const
|
|
134
|
+
const publicKey = () =>
|
|
135
|
+
// @ts-expect-error Raw credential options cannot be combined with PRF.
|
|
136
|
+
WebAuthn.getCredential(rawOptions)
|
|
137
|
+
|
|
138
|
+
expectTypeOf(managedExtension).toBeFunction()
|
|
139
|
+
expectTypeOf(publicKey).toBeFunction()
|
|
140
|
+
expectTypeOf(userVerification).toBeFunction()
|
|
141
|
+
})
|
|
142
|
+
})
|