ox 1.0.4 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/dist/core/Base64.d.ts +10 -21
- package/dist/core/Base64.d.ts.map +1 -1
- package/dist/core/Base64.js +8 -127
- package/dist/core/Base64.js.map +1 -1
- package/dist/core/BinaryStateTree.d.ts.map +1 -1
- package/dist/core/BinaryStateTree.js +2 -2
- package/dist/core/BinaryStateTree.js.map +1 -1
- package/dist/core/Bls.d.ts.map +1 -1
- package/dist/core/Bls.js +19 -44
- package/dist/core/Bls.js.map +1 -1
- package/dist/core/Ed25519.d.ts.map +1 -1
- package/dist/core/Ed25519.js +7 -6
- package/dist/core/Ed25519.js.map +1 -1
- package/dist/core/Engine.d.ts +124 -0
- package/dist/core/Engine.d.ts.map +1 -0
- package/dist/core/Engine.js +180 -0
- package/dist/core/Engine.js.map +1 -0
- package/dist/core/Hash.d.ts +4 -4
- package/dist/core/Hash.d.ts.map +1 -1
- package/dist/core/Hash.js +9 -12
- package/dist/core/Hash.js.map +1 -1
- package/dist/core/Keystore.d.ts.map +1 -1
- package/dist/core/Keystore.js +15 -10
- package/dist/core/Keystore.js.map +1 -1
- package/dist/core/Mnemonic.d.ts.map +1 -1
- package/dist/core/Mnemonic.js +3 -2
- package/dist/core/Mnemonic.js.map +1 -1
- package/dist/core/P256.d.ts.map +1 -1
- package/dist/core/P256.js +8 -9
- package/dist/core/P256.js.map +1 -1
- package/dist/core/Secp256k1.d.ts.map +1 -1
- package/dist/core/Secp256k1.js +8 -9
- package/dist/core/Secp256k1.js.map +1 -1
- package/dist/core/WebCryptoP256.d.ts.map +1 -1
- package/dist/core/WebCryptoP256.js +2 -2
- package/dist/core/WebCryptoP256.js.map +1 -1
- package/dist/core/X25519.d.ts.map +1 -1
- package/dist/core/X25519.js +4 -3
- package/dist/core/X25519.js.map +1 -1
- package/dist/core/internal/bls.d.ts +12 -0
- package/dist/core/internal/bls.d.ts.map +1 -0
- package/dist/core/internal/bls.js +79 -0
- package/dist/core/internal/bls.js.map +1 -0
- package/dist/core/internal/codec/base64.d.ts +84 -0
- package/dist/core/internal/codec/base64.d.ts.map +1 -0
- package/dist/core/internal/codec/base64.js +177 -0
- package/dist/core/internal/codec/base64.js.map +1 -0
- package/dist/core/internal/codec/hex.d.ts +49 -4
- package/dist/core/internal/codec/hex.d.ts.map +1 -1
- package/dist/core/internal/codec/hex.js +109 -35
- package/dist/core/internal/codec/hex.js.map +1 -1
- package/dist/core/internal/curves.d.ts +17 -0
- package/dist/core/internal/curves.d.ts.map +1 -0
- package/dist/core/internal/curves.js +17 -0
- package/dist/core/internal/curves.js.map +1 -0
- package/dist/core/internal/ed25519.d.ts +14 -0
- package/dist/core/internal/ed25519.d.ts.map +1 -0
- package/dist/core/internal/ed25519.js +29 -0
- package/dist/core/internal/ed25519.js.map +1 -0
- package/dist/core/internal/engine.d.ts +278 -0
- package/dist/core/internal/engine.d.ts.map +1 -0
- package/dist/core/internal/engine.js +131 -0
- package/dist/core/internal/engine.js.map +1 -0
- package/dist/core/internal/hash.d.ts +12 -0
- package/dist/core/internal/hash.d.ts.map +1 -0
- package/dist/core/internal/hash.js +30 -0
- package/dist/core/internal/hash.js.map +1 -0
- package/dist/core/internal/keystore.d.ts +14 -0
- package/dist/core/internal/keystore.d.ts.map +1 -0
- package/dist/core/internal/keystore.js +32 -0
- package/dist/core/internal/keystore.js.map +1 -0
- package/dist/core/internal/mnemonic.d.ts +4 -0
- package/dist/core/internal/mnemonic.d.ts.map +1 -0
- package/dist/core/internal/mnemonic.js +14 -0
- package/dist/core/internal/mnemonic.js.map +1 -0
- package/dist/core/internal/p256.d.ts +14 -0
- package/dist/core/internal/p256.d.ts.map +1 -0
- package/dist/core/internal/p256.js +36 -0
- package/dist/core/internal/p256.js.map +1 -0
- package/dist/core/internal/secp256k1.d.ts +14 -0
- package/dist/core/internal/secp256k1.d.ts.map +1 -0
- package/dist/core/internal/secp256k1.js +36 -0
- package/dist/core/internal/secp256k1.js.map +1 -0
- package/dist/core/internal/x25519.d.ts +8 -0
- package/dist/core/internal/x25519.d.ts.map +1 -0
- package/dist/core/internal/x25519.js +20 -0
- package/dist/core/internal/x25519.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.docs.d.ts +2 -0
- package/dist/index.docs.d.ts.map +1 -1
- package/dist/index.docs.js +2 -0
- package/dist/index.docs.js.map +1 -1
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -1
- package/dist/node/Engine.d.ts +55 -0
- package/dist/node/Engine.d.ts.map +1 -0
- package/dist/node/Engine.js +51 -0
- package/dist/node/Engine.js.map +1 -0
- package/dist/node/Hash.d.ts +39 -0
- package/dist/node/Hash.d.ts.map +1 -0
- package/dist/node/Hash.js +37 -0
- package/dist/node/Hash.js.map +1 -0
- package/dist/node/index.d.ts +39 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +39 -0
- package/dist/node/index.js.map +1 -0
- package/dist/tempo/VirtualMaster.d.ts +2 -0
- package/dist/tempo/VirtualMaster.d.ts.map +1 -1
- package/dist/tempo/VirtualMaster.js +19 -5
- package/dist/tempo/VirtualMaster.js.map +1 -1
- package/dist/tempo/ZoneId.d.ts +35 -17
- package/dist/tempo/ZoneId.d.ts.map +1 -1
- package/dist/tempo/ZoneId.js +41 -16
- package/dist/tempo/ZoneId.js.map +1 -1
- package/dist/tempo/index.d.ts +5 -6
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +5 -6
- package/dist/tempo/index.js.map +1 -1
- package/dist/tempo/internal/mine.wasm.d.ts +2 -4
- package/dist/tempo/internal/mine.wasm.d.ts.map +1 -1
- package/dist/tempo/internal/mine.wasm.js +7 -12
- package/dist/tempo/internal/mine.wasm.js.map +1 -1
- package/dist/tempo/internal/virtualMasterPool.js +4 -1
- package/dist/tempo/internal/virtualMasterPool.js.map +1 -1
- package/dist/wasm/Engine.d.ts +62 -0
- package/dist/wasm/Engine.d.ts.map +1 -0
- package/dist/wasm/Engine.js +57 -0
- package/dist/wasm/Engine.js.map +1 -0
- package/dist/wasm/Hash.d.ts +49 -0
- package/dist/wasm/Hash.d.ts.map +1 -0
- package/dist/wasm/Hash.js +70 -0
- package/dist/wasm/Hash.js.map +1 -0
- package/dist/wasm/index.d.ts +46 -0
- package/dist/wasm/index.d.ts.map +1 -0
- package/dist/wasm/index.js +46 -0
- package/dist/wasm/index.js.map +1 -0
- package/dist/wasm/internal/hash.d.ts +9 -0
- package/dist/wasm/internal/hash.d.ts.map +1 -0
- package/dist/wasm/internal/hash.js +26 -0
- package/dist/wasm/internal/hash.js.map +1 -0
- package/dist/wasm/internal/hashes.wasm.d.ts +5 -0
- package/dist/wasm/internal/hashes.wasm.d.ts.map +1 -0
- package/dist/wasm/internal/hashes.wasm.js +12 -0
- package/dist/wasm/internal/hashes.wasm.js.map +1 -0
- package/dist/wasm/internal/instantiate.d.ts +56 -0
- package/dist/wasm/internal/instantiate.d.ts.map +1 -0
- package/dist/wasm/internal/instantiate.js +92 -0
- package/dist/wasm/internal/instantiate.js.map +1 -0
- package/dist/zod/TypedData.d.ts +12 -12
- package/package.json +36 -1
- package/src/core/Base64.ts +19 -166
- package/src/core/BinaryStateTree.ts +2 -3
- package/src/core/Bls.ts +27 -77
- package/src/core/Ed25519.ts +7 -7
- package/src/core/Engine.ts +235 -0
- package/src/core/Hash.ts +9 -12
- package/src/core/Keystore.ts +17 -17
- package/src/core/Mnemonic.ts +3 -6
- package/src/core/P256.ts +15 -24
- package/src/core/Rlp_tx.bench.ts +2 -4
- package/src/core/Secp256k1.ts +9 -14
- package/src/core/TxEnvelopeEip1559.bench.ts +0 -5
- package/src/core/WebCryptoP256.ts +2 -2
- package/src/core/X25519.ts +4 -3
- package/src/core/_test/AbiParameters.adversarial.fuzz.ts +1 -2
- package/src/core/_test/AbiParameters.fuzz.ts +1 -2
- package/src/core/_test/Base.fuzz.ts +1 -2
- package/src/core/_test/Cbor.fuzz.ts +1 -2
- package/src/core/_test/Engine.fuzz.ts +293 -0
- package/src/core/_test/Engine.test-d.ts +84 -0
- package/src/core/_test/Engine.test.ts +523 -0
- package/src/core/_test/Hash.vectors.test.ts +56 -0
- package/src/core/_test/Hex.fuzz.ts +1 -2
- package/src/core/_test/Rlp.fuzz.ts +1 -2
- package/src/core/_test/Signature.fuzz.ts +1 -2
- package/src/core/_test/curves.test.ts +7 -0
- package/src/core/_test/index.test.ts +1 -0
- package/src/core/internal/bls.ts +121 -0
- package/src/core/internal/codec/_test/base64.conformance.ts +156 -0
- package/src/core/internal/codec/_test/hex.conformance.ts +204 -0
- package/src/core/internal/codec/_test/hex.fuzz.ts +234 -0
- package/src/core/internal/codec/_test/hex.test.ts +124 -0
- package/src/core/internal/codec/base64.ts +222 -0
- package/src/core/internal/codec/hex.ts +115 -34
- package/src/core/internal/curves.ts +18 -0
- package/src/core/internal/ed25519.ts +64 -0
- package/src/core/internal/engine.ts +410 -0
- package/src/core/internal/hash.ts +48 -0
- package/src/core/internal/keystore.ts +107 -0
- package/src/core/internal/mnemonic.ts +18 -0
- package/src/core/internal/p256.ts +91 -0
- package/src/core/internal/secp256k1.ts +91 -0
- package/src/core/internal/x25519.ts +40 -0
- package/src/index.docs.ts +2 -0
- package/src/index.ts +36 -0
- package/src/node/Engine.ts +69 -0
- package/src/node/Hash.ts +56 -0
- package/src/node/_test/Engine.test.ts +62 -0
- package/src/node/_test/Hash.test-d.ts +26 -0
- package/src/node/_test/Hash.test.ts +102 -0
- package/src/node/index.ts +40 -0
- package/src/tempo/VirtualMaster.test.ts +75 -0
- package/src/tempo/VirtualMaster.ts +28 -6
- package/src/tempo/ZoneId.test-d.ts +16 -0
- package/src/tempo/ZoneId.test.ts +48 -13
- package/src/tempo/ZoneId.ts +55 -19
- package/src/tempo/index.ts +5 -6
- package/src/tempo/internal/mine.wasm.ts +7 -13
- package/src/tempo/internal/virtualMasterPool.ts +4 -1
- package/src/tsconfig.json +2 -1
- package/src/version.ts +1 -1
- package/src/wasm/Engine.ts +76 -0
- package/src/wasm/Hash.bench.ts +60 -0
- package/src/wasm/Hash.ts +109 -0
- package/src/wasm/_test/Engine.test.ts +62 -0
- package/src/wasm/_test/Hash.browser.test.ts +50 -0
- package/src/wasm/_test/Hash.fuzz.ts +188 -0
- package/src/wasm/_test/Hash.test-d.ts +28 -0
- package/src/wasm/_test/Hash.test.ts +211 -0
- package/src/wasm/_test/Hash.vectors.test.ts +117 -0
- package/src/wasm/_test/Runtime.test.ts +21 -0
- package/src/wasm/_test/runtime.wasm.ts +11 -0
- package/src/wasm/index.ts +47 -0
- package/src/wasm/internal/hash.ts +50 -0
- package/src/wasm/internal/hashes.wasm.ts +14 -0
- package/src/wasm/internal/instantiate.ts +135 -0
- package/src/zod/tempo/_test/ZoneId.test.ts +2 -2
- package/src/tempo/internal/mine.c +0 -182
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { bls12_381 as bls } from '@noble/curves/bls12-381.js'
|
|
2
|
+
import { type Bls, type Complete, overrides } from './engine.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolvers for the `Bls` slot, and ox's defaults for it, backed by
|
|
6
|
+
* `@noble/curves`.
|
|
7
|
+
*
|
|
8
|
+
* Declaring the defaults against the slot contract is what keeps them honest: a
|
|
9
|
+
* default that goes missing, or whose signature drifts, fails to compile rather
|
|
10
|
+
* than failing at the call site.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Branching on the group rather than selecting a curve into a variable: the
|
|
14
|
+
// two `Point` types are unrelated, so a union of them has no common `add`.
|
|
15
|
+
const aggregateDefault: Complete<Bls>['aggregate'] = (points, group) => {
|
|
16
|
+
if (group === 'G1') {
|
|
17
|
+
let point = bls.G1.Point.ZERO
|
|
18
|
+
for (const value of points) point = point.add(bls.G1.Point.fromBytes(value))
|
|
19
|
+
return point.toBytes()
|
|
20
|
+
}
|
|
21
|
+
let point = bls.G2.Point.ZERO
|
|
22
|
+
for (const value of points) point = point.add(bls.G2.Point.fromBytes(value))
|
|
23
|
+
return point.toBytes()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const getPublicKeyDefault: Complete<Bls>['getPublicKey'] = (
|
|
27
|
+
privateKey,
|
|
28
|
+
group,
|
|
29
|
+
) => {
|
|
30
|
+
const curve = group === 'G1' ? bls.G1 : bls.G2
|
|
31
|
+
return curve.Point.BASE.multiply(
|
|
32
|
+
curve.Point.Fn.fromBytes(privateKey),
|
|
33
|
+
).toBytes()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const randomSecretKeyDefault: Complete<Bls>['randomSecretKey'] = () =>
|
|
37
|
+
bls.utils.randomSecretKey()
|
|
38
|
+
|
|
39
|
+
const signDefault: Complete<Bls>['sign'] = (payload, privateKey, options) => {
|
|
40
|
+
const { dst, group } = options
|
|
41
|
+
const signatureCurve = group === 'G1' ? bls.G1 : bls.G2
|
|
42
|
+
// The scalar comes from the group the public key lives in, which is the one
|
|
43
|
+
// the signature does not.
|
|
44
|
+
const privateKeyCurve = group === 'G1' ? bls.G2 : bls.G1
|
|
45
|
+
const payloadPoint = signatureCurve.hashToCurve(
|
|
46
|
+
payload,
|
|
47
|
+
dst ? { DST: dst } : undefined,
|
|
48
|
+
)
|
|
49
|
+
return payloadPoint
|
|
50
|
+
.multiply(privateKeyCurve.Point.Fn.fromBytes(privateKey))
|
|
51
|
+
.toBytes()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const verifyDefault: Complete<Bls>['verify'] = (
|
|
55
|
+
signature,
|
|
56
|
+
payload,
|
|
57
|
+
publicKey,
|
|
58
|
+
options,
|
|
59
|
+
) => {
|
|
60
|
+
const { dst, signatureGroup } = options
|
|
61
|
+
const shortSignature = signatureGroup === 'G1'
|
|
62
|
+
const curve = shortSignature ? bls.G1 : bls.G2
|
|
63
|
+
const payloadPoint = curve.hashToCurve(
|
|
64
|
+
payload,
|
|
65
|
+
dst ? { DST: dst } : undefined,
|
|
66
|
+
)
|
|
67
|
+
const pairing = shortSignature
|
|
68
|
+
? bls.pairingBatch([
|
|
69
|
+
{
|
|
70
|
+
g1: payloadPoint as InstanceType<typeof bls.G1.Point>,
|
|
71
|
+
g2: bls.G2.Point.fromBytes(publicKey),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
g1: bls.G1.Point.fromBytes(signature),
|
|
75
|
+
g2: bls.G2.Point.BASE.negate(),
|
|
76
|
+
},
|
|
77
|
+
])
|
|
78
|
+
: bls.pairingBatch([
|
|
79
|
+
{
|
|
80
|
+
g1: bls.G1.Point.fromBytes(publicKey).negate(),
|
|
81
|
+
g2: payloadPoint as InstanceType<typeof bls.G2.Point>,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
g1: bls.G1.Point.BASE,
|
|
85
|
+
g2: bls.G2.Point.fromBytes(signature),
|
|
86
|
+
},
|
|
87
|
+
])
|
|
88
|
+
return bls.fields.Fp12.eql(pairing, bls.fields.Fp12.ONE)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @internal */
|
|
92
|
+
export const aggregate: Complete<Bls>['aggregate'] = (points, group) =>
|
|
93
|
+
(overrides.Bls?.aggregate ?? aggregateDefault)(points, group)
|
|
94
|
+
|
|
95
|
+
/** @internal */
|
|
96
|
+
export const getPublicKey: Complete<Bls>['getPublicKey'] = (
|
|
97
|
+
privateKey,
|
|
98
|
+
group,
|
|
99
|
+
) => (overrides.Bls?.getPublicKey ?? getPublicKeyDefault)(privateKey, group)
|
|
100
|
+
|
|
101
|
+
/** @internal */
|
|
102
|
+
export const randomSecretKey: Complete<Bls>['randomSecretKey'] = () =>
|
|
103
|
+
(overrides.Bls?.randomSecretKey ?? randomSecretKeyDefault)()
|
|
104
|
+
|
|
105
|
+
/** @internal */
|
|
106
|
+
export const sign: Complete<Bls>['sign'] = (payload, privateKey, options) =>
|
|
107
|
+
(overrides.Bls?.sign ?? signDefault)(payload, privateKey, options)
|
|
108
|
+
|
|
109
|
+
/** @internal */
|
|
110
|
+
export const verify: Complete<Bls>['verify'] = (
|
|
111
|
+
signature,
|
|
112
|
+
payload,
|
|
113
|
+
publicKey,
|
|
114
|
+
options,
|
|
115
|
+
) =>
|
|
116
|
+
(overrides.Bls?.verify ?? verifyDefault)(
|
|
117
|
+
signature,
|
|
118
|
+
payload,
|
|
119
|
+
publicKey,
|
|
120
|
+
options,
|
|
121
|
+
)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base64 codec conformance, written once and run by every runtime.
|
|
3
|
+
*
|
|
4
|
+
* Declares the tiers this runtime can actually execute, pins them to published
|
|
5
|
+
* vectors, and compares them against each other. Coverage widens on its own
|
|
6
|
+
* where more tiers exist -- Node runs the loop, a browser the loop and
|
|
7
|
+
* its native method -- without this file changing or naming a runtime.
|
|
8
|
+
*
|
|
9
|
+
* Collected by both the `core` and `browser` projects, via their
|
|
10
|
+
* `*.conformance.ts` globs.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, expect, test } from 'vp/test'
|
|
16
|
+
import {
|
|
17
|
+
corrupted,
|
|
18
|
+
expectTiersAgree,
|
|
19
|
+
expectTiersMatch,
|
|
20
|
+
expectTiersReject,
|
|
21
|
+
sampleBytes,
|
|
22
|
+
type Tier,
|
|
23
|
+
} from '../../../../../test/conformance.js'
|
|
24
|
+
import * as base64 from '../base64.js'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Published vectors, transcribed verbatim rather than regenerated, so a change
|
|
28
|
+
* to ox cannot quietly move the expected values.
|
|
29
|
+
*
|
|
30
|
+
* - RFC 4648 section 10 specifies Base64 and gives the `""` through `"foobar"`
|
|
31
|
+
* progression. https://www.rfc-editor.org/rfc/rfc4648#section-10
|
|
32
|
+
* - test262 pins which characters the native codec must reject.
|
|
33
|
+
* https://github.com/tc39/test262/tree/main/test/built-ins/Uint8Array/fromBase64
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** Bytes and their Base64, from RFC 4648 section 10. */
|
|
37
|
+
const roundTrip = ['', 'f', 'fo', 'foo', 'foob', 'fooba', 'foobar'].map(
|
|
38
|
+
(value, i) => ({
|
|
39
|
+
bytes: Uint8Array.from(value, (c) => c.charCodeAt(0)),
|
|
40
|
+
encoded: ['', 'Zg==', 'Zm8=', 'Zm9v', 'Zm9vYg==', 'Zm9vYmE=', 'Zm9vYmFy'][
|
|
41
|
+
i
|
|
42
|
+
]!,
|
|
43
|
+
}),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Strings a decoder must reject, from test262
|
|
48
|
+
* `fromBase64/illegal-characters.js`.
|
|
49
|
+
*
|
|
50
|
+
* U+2212 and U+FF0B are the interesting ones: they are homoglyphs for `-` and
|
|
51
|
+
* `+`, both of which *are* Base64 alphabet characters, and U+FF0B sits above
|
|
52
|
+
* the Latin-1 table. Before the guard it decoded silently as `A` rather than
|
|
53
|
+
* being refused.
|
|
54
|
+
*/
|
|
55
|
+
const illegal = [
|
|
56
|
+
'Zm.9v',
|
|
57
|
+
'Zm9v^',
|
|
58
|
+
'Zg==&',
|
|
59
|
+
'Z\u2212==', // minus sign, homoglyph for '-'
|
|
60
|
+
'Z\uff0b==', // fullwidth plus sign, homoglyph for '+'
|
|
61
|
+
'Zg\u00a0==', // no-break space
|
|
62
|
+
'Zg\u2009==', // thin space
|
|
63
|
+
'Zg\u2028==', // line separator
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
describe('base64', () => {
|
|
67
|
+
const options = [
|
|
68
|
+
{ pad: true, url: false },
|
|
69
|
+
{ pad: false, url: false },
|
|
70
|
+
{ pad: true, url: true },
|
|
71
|
+
{ pad: false, url: true },
|
|
72
|
+
] as const
|
|
73
|
+
|
|
74
|
+
type EncodeInput = { bytes: Uint8Array; options: base64.Options }
|
|
75
|
+
const encodeTiers: Tier<EncodeInput, string>[] = [
|
|
76
|
+
{
|
|
77
|
+
name: 'loop',
|
|
78
|
+
run: ({ bytes, options }) => base64.fromBytesLoop(bytes, options),
|
|
79
|
+
},
|
|
80
|
+
...(base64.fromBytesNative
|
|
81
|
+
? [
|
|
82
|
+
{
|
|
83
|
+
name: 'native',
|
|
84
|
+
run: ({ bytes, options }: EncodeInput) =>
|
|
85
|
+
base64.fromBytesNative!(bytes, options),
|
|
86
|
+
},
|
|
87
|
+
]
|
|
88
|
+
: []),
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
// `toBytes` strips padding before handing the body to a tier, so the suite
|
|
92
|
+
// has to do the same to compare them.
|
|
93
|
+
const bodyEnd = (value: string) => {
|
|
94
|
+
let end = value.length
|
|
95
|
+
while (end > 0 && value.charCodeAt(end - 1) === 61) end--
|
|
96
|
+
return end
|
|
97
|
+
}
|
|
98
|
+
// Only one decode tier: `Uint8Array.fromBase64` cannot match ox's
|
|
99
|
+
// alphabet-agnostic decoding, so the loop is all there is. The suite still
|
|
100
|
+
// pins it to the published
|
|
101
|
+
const decodeTiers: Tier<string, Uint8Array>[] = [
|
|
102
|
+
{ name: 'loop', run: (v) => base64.toBytesLoop(v, bodyEnd(v)) },
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
test(`behavior: exercises ${encodeTiers.length} encode / ${decodeTiers.length} decode tiers`, () => {
|
|
106
|
+
expect(encodeTiers.length).toBeGreaterThan(0)
|
|
107
|
+
expect(decodeTiers.length).toBe(1)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('vectors: RFC 4648', () => {
|
|
111
|
+
expectTiersMatch(
|
|
112
|
+
encodeTiers,
|
|
113
|
+
roundTrip.map(({ bytes, encoded }) => ({
|
|
114
|
+
input: { bytes, options: { pad: true, url: false } },
|
|
115
|
+
output: encoded,
|
|
116
|
+
})),
|
|
117
|
+
{ describe: ({ bytes }) => `${bytes.length} bytes` },
|
|
118
|
+
)
|
|
119
|
+
expectTiersMatch(
|
|
120
|
+
decodeTiers,
|
|
121
|
+
roundTrip.map(({ bytes, encoded }) => ({
|
|
122
|
+
input: encoded,
|
|
123
|
+
output: bytes,
|
|
124
|
+
})),
|
|
125
|
+
)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('vectors: illegal characters are rejected by every tier', () => {
|
|
129
|
+
expectTiersReject(decodeTiers, illegal)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('differential: tiers agree on valid input', () => {
|
|
133
|
+
for (const option of options)
|
|
134
|
+
expectTiersAgree(
|
|
135
|
+
encodeTiers,
|
|
136
|
+
[...sampleBytes(200)].map((bytes) => ({ bytes, options: option })),
|
|
137
|
+
{
|
|
138
|
+
describe: ({ bytes }) =>
|
|
139
|
+
`${bytes.length} bytes, ${JSON.stringify(option)}`,
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
expectTiersAgree(
|
|
143
|
+
decodeTiers,
|
|
144
|
+
[...sampleBytes(400)].map((b) => base64.fromBytesLoop(b)),
|
|
145
|
+
{ describe: (v) => `${v.slice(0, 20)}… (${v.length})` },
|
|
146
|
+
)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('differential: tiers agree on corrupted input', () => {
|
|
150
|
+
expectTiersAgree(
|
|
151
|
+
decodeTiers,
|
|
152
|
+
[...corrupted((b) => base64.fromBytesLoop(b), 400)],
|
|
153
|
+
{ describe: (v) => JSON.stringify(v.slice(0, 24)) },
|
|
154
|
+
)
|
|
155
|
+
})
|
|
156
|
+
})
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hex codec conformance, written once and run by every runtime.
|
|
3
|
+
*
|
|
4
|
+
* Declares the tiers this runtime can actually execute, pins them to published
|
|
5
|
+
* vectors, and compares them against each other. Coverage widens on its own
|
|
6
|
+
* where more tiers exist -- Node runs the loop and `Buffer`, a browser the loop and
|
|
7
|
+
* its native method -- without this file changing or naming a runtime.
|
|
8
|
+
*
|
|
9
|
+
* Collected by both the `core` and `browser` projects, via their
|
|
10
|
+
* `*.conformance.ts` globs.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, expect, test } from 'vp/test'
|
|
16
|
+
import {
|
|
17
|
+
corrupted,
|
|
18
|
+
expectTiersAgree,
|
|
19
|
+
expectTiersMatch,
|
|
20
|
+
expectTiersReject,
|
|
21
|
+
sampleBytes,
|
|
22
|
+
type Tier,
|
|
23
|
+
} from '../../../../../test/conformance.js'
|
|
24
|
+
import * as hex from '../hex.js'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Published vectors, transcribed verbatim rather than regenerated, so a change
|
|
28
|
+
* to ox cannot quietly move the expected values.
|
|
29
|
+
*
|
|
30
|
+
* - RFC 4648 section 10 specifies Base16 and gives the `""` through `"foobar"`
|
|
31
|
+
* progression. Section 8 defines it as case-insensitive on decode; ox emits
|
|
32
|
+
* lowercase. https://www.rfc-editor.org/rfc/rfc4648#section-10
|
|
33
|
+
* - test262 pins the native codec ox delegates to, including which characters
|
|
34
|
+
* it must reject.
|
|
35
|
+
* https://github.com/tc39/test262/tree/main/test/built-ins/Uint8Array/fromHex
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/** The RFC 4648 section 10 inputs, as bytes. */
|
|
39
|
+
const foobar = ['', 'f', 'fo', 'foo', 'foob', 'fooba', 'foobar'].map((value) =>
|
|
40
|
+
Uint8Array.from(value, (c) => c.charCodeAt(0)),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Bytes and their lowercase hex, from RFC 4648 section 10 and test262
|
|
45
|
+
* `fromHex/results.js` / `toHex/results.js`. The two agree except on case: the
|
|
46
|
+
* RFC writes uppercase, the ECMAScript methods emit lowercase.
|
|
47
|
+
*/
|
|
48
|
+
const roundTrip = foobar.map((bytes, i) => ({
|
|
49
|
+
bytes,
|
|
50
|
+
encoded: [
|
|
51
|
+
'',
|
|
52
|
+
'66',
|
|
53
|
+
'666f',
|
|
54
|
+
'666f6f',
|
|
55
|
+
'666f6f62',
|
|
56
|
+
'666f6f6261',
|
|
57
|
+
'666f6f626172',
|
|
58
|
+
][i]!,
|
|
59
|
+
}))
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Uppercase and mixed-case bodies that must decode to the same bytes as their
|
|
63
|
+
* lowercase form, from test262 `fromHex/results.js` and the uppercase spellings
|
|
64
|
+
* RFC 4648 section 10 uses.
|
|
65
|
+
*/
|
|
66
|
+
const decodeCaseInsensitive = [
|
|
67
|
+
{ bytes: Uint8Array.of(102, 111), encoded: '666F' },
|
|
68
|
+
{ bytes: Uint8Array.of(102, 111, 111), encoded: '666F6f' },
|
|
69
|
+
{ bytes: foobar[6]!, encoded: '666F6F626172' },
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Characters a decoder must reject, from test262
|
|
74
|
+
* `fromHex/illegal-characters.js`: punctuation, every flavor of ASCII
|
|
75
|
+
* whitespace, and three non-ASCII spaces.
|
|
76
|
+
*
|
|
77
|
+
* The last two sit above the Latin-1 lookup table ox decodes through, so a
|
|
78
|
+
* table read for them yields `undefined` rather than the invalid sentinel --
|
|
79
|
+
* the exact shape of the bug this suite exists to catch.
|
|
80
|
+
*/
|
|
81
|
+
const illegalCharacters = [
|
|
82
|
+
'.',
|
|
83
|
+
'^',
|
|
84
|
+
' ',
|
|
85
|
+
'\t',
|
|
86
|
+
'\u000a', // line feed
|
|
87
|
+
'\u000c', // form feed
|
|
88
|
+
'\u000d', // carriage return
|
|
89
|
+
'\u00a0', // no-break space -- inside the Latin-1 table
|
|
90
|
+
'\u2009', // thin space -- above it
|
|
91
|
+
'\u2028', // line separator -- above it
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Bodies that Node's `Buffer` hex decoder accepted, wrongly, before ox guarded
|
|
96
|
+
* against it: it masks each UTF-16 code unit to 8 bits, so a character above
|
|
97
|
+
* U+00FF can alias a hex digit. U+C230 masks to `0x30`, the digit `0`, and the
|
|
98
|
+
* surrogate pair for U+1D330 masks to `40`.
|
|
99
|
+
*/
|
|
100
|
+
const maskedNonAscii = [
|
|
101
|
+
{ body: '\uc2300', note: 'U+C230 masks to the digit 0' },
|
|
102
|
+
{ body: '\u{1d330}', note: 'surrogate pair masks to 4 and 0' },
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
describe('hex', () => {
|
|
106
|
+
const encodeTiers: Tier<Uint8Array, string>[] = [
|
|
107
|
+
{ name: 'loop', run: hex.bytesToHexLoop },
|
|
108
|
+
...(hex.bytesToHexNative
|
|
109
|
+
? [{ name: 'native', run: hex.bytesToHexNative }]
|
|
110
|
+
: []),
|
|
111
|
+
...(hex.bytesToHexBuffer
|
|
112
|
+
? [{ name: 'buffer', run: hex.bytesToHexBuffer }]
|
|
113
|
+
: []),
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
const byteLength = (value: string) => (value.length - 2) >> 1
|
|
117
|
+
// No native decode tier: `Uint8Array.fromHex` cannot be trusted to reject
|
|
118
|
+
// non-ASCII across the browser versions ox supports.
|
|
119
|
+
const decodeTiers: Tier<string, Uint8Array>[] = [
|
|
120
|
+
{ name: 'loop', run: (v) => hex.hexToBytesLoop(v, byteLength(v)) },
|
|
121
|
+
...(hex.hexToBytesBuffer
|
|
122
|
+
? [
|
|
123
|
+
{
|
|
124
|
+
name: 'buffer',
|
|
125
|
+
run: (v: string) => hex.hexToBytesBuffer!(v, byteLength(v)),
|
|
126
|
+
},
|
|
127
|
+
]
|
|
128
|
+
: []),
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
test(`behavior: exercises ${encodeTiers.length} encode / ${decodeTiers.length} decode tiers`, () => {
|
|
132
|
+
// Named so the report says which tiers a runtime actually covered, and so
|
|
133
|
+
// a tier silently disappearing is visible rather than quietly shrinking
|
|
134
|
+
// coverage. Encoding has one more tier than decoding: the native method is
|
|
135
|
+
// trusted to encode but not to validate.
|
|
136
|
+
expect(encodeTiers.length).toBeGreaterThan(0)
|
|
137
|
+
expect(decodeTiers.length).toBeGreaterThan(0)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('vectors: RFC 4648 and test262', () => {
|
|
141
|
+
expectTiersMatch(
|
|
142
|
+
encodeTiers,
|
|
143
|
+
roundTrip.map(({ bytes, encoded }) => ({
|
|
144
|
+
input: bytes,
|
|
145
|
+
output: `0x${encoded}`,
|
|
146
|
+
})),
|
|
147
|
+
)
|
|
148
|
+
expectTiersMatch(
|
|
149
|
+
decodeTiers,
|
|
150
|
+
roundTrip.map(({ bytes, encoded }) => ({
|
|
151
|
+
input: `0x${encoded}`,
|
|
152
|
+
output: bytes,
|
|
153
|
+
})),
|
|
154
|
+
)
|
|
155
|
+
expectTiersMatch(
|
|
156
|
+
decodeTiers,
|
|
157
|
+
decodeCaseInsensitive.map(({ bytes, encoded }) => ({
|
|
158
|
+
input: `0x${encoded}`,
|
|
159
|
+
output: bytes,
|
|
160
|
+
})),
|
|
161
|
+
)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('vectors: illegal characters are rejected by every tier', () => {
|
|
165
|
+
const pad = 'ab'.repeat(96)
|
|
166
|
+
expectTiersReject(
|
|
167
|
+
decodeTiers,
|
|
168
|
+
illegalCharacters.flatMap((char) => [
|
|
169
|
+
`0xaa${char}a`,
|
|
170
|
+
`0x${pad}aa${char}a`,
|
|
171
|
+
]),
|
|
172
|
+
)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test('vectors: characters Buffer would mask into a digit are rejected', () => {
|
|
176
|
+
const pad = 'ab'.repeat(96)
|
|
177
|
+
expectTiersReject(
|
|
178
|
+
decodeTiers,
|
|
179
|
+
maskedNonAscii.flatMap(({ body }) => [`0x${body}`, `0x${pad}${body}`]),
|
|
180
|
+
)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
test('differential: tiers agree on valid input', () => {
|
|
184
|
+
expectTiersAgree(encodeTiers, sampleBytes(400), {
|
|
185
|
+
describe: (b) => `${b.length} bytes`,
|
|
186
|
+
})
|
|
187
|
+
expectTiersAgree(
|
|
188
|
+
decodeTiers,
|
|
189
|
+
[...sampleBytes(400)].map((b) => hex.bytesToHexLoop(b)),
|
|
190
|
+
{ describe: (v) => `${v.slice(0, 20)}… (${v.length})` },
|
|
191
|
+
)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
test('differential: tiers agree on corrupted input', () => {
|
|
195
|
+
expectTiersAgree(
|
|
196
|
+
decodeTiers,
|
|
197
|
+
[...corrupted((b) => hex.bytesToHexLoop(b), 400)].filter(
|
|
198
|
+
// Odd bodies never reach a tier; the dispatcher rejects them first.
|
|
199
|
+
(v) => (v.length - 2) % 2 === 0,
|
|
200
|
+
),
|
|
201
|
+
{ describe: (v) => JSON.stringify(v.slice(0, 24)) },
|
|
202
|
+
)
|
|
203
|
+
})
|
|
204
|
+
})
|