iso-ucan 1.0.0 → 3.0.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/dist/src/delegation.d.ts +1 -1
- package/dist/src/delegation.d.ts.map +1 -1
- package/dist/src/envelope.d.ts +42 -10
- package/dist/src/envelope.d.ts.map +1 -1
- package/dist/src/invocation.d.ts +2 -14
- package/dist/src/invocation.d.ts.map +1 -1
- package/dist/src/policy.d.ts +114 -0
- package/dist/src/policy.d.ts.map +1 -1
- package/dist/src/types/envelope.d.ts +4 -4
- package/dist/src/types/envelope.d.ts.map +1 -1
- package/dist/src/types/policy.d.ts +6 -2
- package/dist/src/types/policy.d.ts.map +1 -1
- package/dist/src/types/varsig.d.ts +11 -1
- package/dist/src/types/varsig.d.ts.map +1 -1
- package/dist/src/types.d.ts +10 -9
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils.d.ts +27 -38
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/varsig.d.ts +2 -3
- package/dist/src/varsig.d.ts.map +1 -1
- package/package.json +3 -3
- package/readme.md +57 -9
- package/src/delegation.js +11 -5
- package/src/envelope.js +117 -36
- package/src/invocation.js +50 -41
- package/src/policy.js +597 -195
- package/src/types/envelope.ts +4 -4
- package/src/types/policy.ts +5 -2
- package/src/types/varsig.ts +12 -1
- package/src/types.ts +10 -9
- package/src/utils.js +64 -157
- package/src/varsig.js +32 -29
package/src/types/envelope.ts
CHANGED
|
@@ -41,10 +41,6 @@ export interface PayloadBase {
|
|
|
41
41
|
* Expiration UTC Unix Timestamp in seconds (valid until)
|
|
42
42
|
*/
|
|
43
43
|
exp: number | null
|
|
44
|
-
/**
|
|
45
|
-
* "Not before" UTC Unix Timestamp in seconds (valid from)
|
|
46
|
-
*/
|
|
47
|
-
nbf?: number
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
/**
|
|
@@ -83,6 +79,10 @@ export interface DelegationPayload<Args = unknown> extends PayloadBase {
|
|
|
83
79
|
*/
|
|
84
80
|
sub: DID | null
|
|
85
81
|
pol: Policy<Args>
|
|
82
|
+
/**
|
|
83
|
+
* "Not before" UTC Unix Timestamp in seconds (valid from)
|
|
84
|
+
*/
|
|
85
|
+
nbf?: number
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
export type Payload<Args = unknown> =
|
package/src/types/policy.ts
CHANGED
|
@@ -24,7 +24,6 @@ type PathsToStringProps<T> = T extends
|
|
|
24
24
|
| `[${number}:${number}]`
|
|
25
25
|
| `[${number}:]`
|
|
26
26
|
| `[:${number}]`
|
|
27
|
-
| `[:]`
|
|
28
27
|
| PathsToStringProps<U>
|
|
29
28
|
: T extends object
|
|
30
29
|
? {
|
|
@@ -73,7 +72,11 @@ type SelectorValue<T, S extends string> = S extends `.${infer K}`
|
|
|
73
72
|
export type EqualityOp = '==' | '!='
|
|
74
73
|
export type Equality<Args = unknown> = [EqualityOp, Selector<Args>, unknown]
|
|
75
74
|
export type InequalityOp = '<' | '<=' | '>' | '>='
|
|
76
|
-
export type Inequality<Args = unknown> = [
|
|
75
|
+
export type Inequality<Args = unknown> = [
|
|
76
|
+
InequalityOp,
|
|
77
|
+
Selector<Args>,
|
|
78
|
+
number | bigint,
|
|
79
|
+
]
|
|
77
80
|
|
|
78
81
|
export type NegateOp = 'not'
|
|
79
82
|
export type Negate<Args = unknown> = [NegateOp, Statement<Args>]
|
package/src/types/varsig.ts
CHANGED
|
@@ -11,12 +11,23 @@ export type VarsigEncoding = 'RAW' | 'DAG-CBOR'
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Supported signature types
|
|
14
|
+
*
|
|
15
|
+
* RSA (`RS256`) is not supported.
|
|
14
16
|
*/
|
|
15
|
-
export type VarsigAlgorithm = SignatureType
|
|
17
|
+
export type VarsigAlgorithm = Exclude<SignatureType, 'RS256'>
|
|
16
18
|
|
|
17
19
|
export interface VarsigOptions {
|
|
18
20
|
enc: VarsigEncoding
|
|
19
21
|
alg: VarsigAlgorithm
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Options for varsig `encode`. Accepts any signer type so unsupported
|
|
26
|
+
* algorithms (e.g. `RS256`) fail at runtime with a clear error.
|
|
27
|
+
*/
|
|
28
|
+
export interface VarsigEncodeOptions {
|
|
29
|
+
enc: VarsigEncoding
|
|
30
|
+
alg: SignatureType
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
export type DecodeOutput = VarsigOptions
|
package/src/types.ts
CHANGED
|
@@ -19,13 +19,6 @@ export * from './types/policy'
|
|
|
19
19
|
export * from './types/varsig'
|
|
20
20
|
|
|
21
21
|
export type ResolveProof = (proof: CID) => Promise<Delegation>
|
|
22
|
-
/**
|
|
23
|
-
* Check whether a delegation CID is revoked.
|
|
24
|
-
*
|
|
25
|
-
* Called once for each CID the delegation could have been issued under
|
|
26
|
-
* (up to 2 for ECDSA and 4 for EIP-191 signatures), since those signatures
|
|
27
|
-
* can be re-encoded into another valid signature with a different CID.
|
|
28
|
-
*/
|
|
29
22
|
export type IsRevoked = (cid: CID) => Promise<boolean>
|
|
30
23
|
export type VerifierResolver = Resolver
|
|
31
24
|
export type DidResolver = _DidResolver
|
|
@@ -123,14 +116,22 @@ export interface InvocationOptions extends DelegationValidateOptions {
|
|
|
123
116
|
* Issued at time in seconds
|
|
124
117
|
*/
|
|
125
118
|
iat?: number
|
|
126
|
-
nbf?: number
|
|
127
119
|
nonce?: Uint8Array
|
|
128
120
|
cause?: CID
|
|
121
|
+
/**
|
|
122
|
+
* Metadata, omitted from the payload when empty
|
|
123
|
+
*/
|
|
129
124
|
meta?: CborObject
|
|
130
125
|
}
|
|
131
126
|
|
|
132
127
|
export interface InvocationFromOptions extends DelegationValidateOptions {
|
|
133
128
|
bytes: Uint8Array
|
|
129
|
+
/**
|
|
130
|
+
* The current time in seconds, used for the invocation's own `exp`, the time
|
|
131
|
+
* bounds of its proofs and `maxTtl`. Mostly used for testing.
|
|
132
|
+
* @default Math.floor(Date.now() / 1000)
|
|
133
|
+
*/
|
|
134
|
+
now?: number
|
|
134
135
|
audience?: VerifiableDID
|
|
135
136
|
resolveProof: ResolveProof
|
|
136
137
|
/**
|
|
@@ -156,7 +157,7 @@ export interface ReplayStore {
|
|
|
156
157
|
* Atomically record `key` and resolve `true`, or resolve `false` if `key`
|
|
157
158
|
* was already recorded and has not expired.
|
|
158
159
|
*
|
|
159
|
-
* @param key -
|
|
160
|
+
* @param key - CID of the invocation, see {@link Invocation.cid}
|
|
160
161
|
* @param expiration - Unix time in seconds after which the key can be forgotten, or `null` to keep it forever
|
|
161
162
|
*/
|
|
162
163
|
checkAndSet(key: string, expiration: number | null): Promise<boolean>
|
package/src/utils.js
CHANGED
|
@@ -6,6 +6,7 @@ import { sha256 } from 'multiformats/hashes/sha2'
|
|
|
6
6
|
import { z } from 'zod/v4'
|
|
7
7
|
|
|
8
8
|
import * as Envelope from './envelope.js'
|
|
9
|
+
import { parseSelector } from './policy.js'
|
|
9
10
|
import * as varsig from './varsig.js'
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -52,19 +53,26 @@ export async function cid(envelope) {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
|
-
*
|
|
56
|
-
* signature covers.
|
|
56
|
+
* Assert that `bytes` is the canonical DAG-CBOR encoding of `envelope`.
|
|
57
57
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
58
|
+
* {@link cid} hashes the re-encoded envelope, and the DAG-CBOR decoder accepts
|
|
59
|
+
* some non-canonical input (e.g. unsorted map keys or short floats). Without
|
|
60
|
+
* this check, different byte strings would share a CID.
|
|
61
61
|
*
|
|
62
62
|
* @param {import('./types.js').DecodedEnvelope<PayloadSpec>} envelope
|
|
63
|
+
* @param {Uint8Array} bytes
|
|
63
64
|
*/
|
|
64
|
-
export
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
export function assertCanonical(envelope, bytes) {
|
|
66
|
+
const encoded = Envelope.encode({
|
|
67
|
+
signature: envelope.signature,
|
|
68
|
+
// @ts-expect-error
|
|
69
|
+
signaturePayload: signaturePayload(envelope),
|
|
70
|
+
})
|
|
71
|
+
if (!equals(encoded, bytes)) {
|
|
72
|
+
throw new TypeError(
|
|
73
|
+
'UCAN envelope is not canonical DAG-CBOR, re-encoding does not match the received bytes'
|
|
74
|
+
)
|
|
75
|
+
}
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
/**
|
|
@@ -94,9 +102,6 @@ export function isSigAndDidCompatible(did, sigType) {
|
|
|
94
102
|
return true
|
|
95
103
|
}
|
|
96
104
|
|
|
97
|
-
if (did.verifiableDid.type === 'RSA' && sigType === 'RS256') {
|
|
98
|
-
return true
|
|
99
|
-
}
|
|
100
105
|
if (did.verifiableDid.type === 'secp256k1' && sigType === 'EIP191') {
|
|
101
106
|
return true
|
|
102
107
|
}
|
|
@@ -124,6 +129,21 @@ export function assertExpiration(exp, now = nowInSeconds()) {
|
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Validate an optional UCAN timestamp (e.g. `nbf` or `iat`): when present it
|
|
134
|
+
* must be an integer number of seconds within ±(2^53 − 1).
|
|
135
|
+
*
|
|
136
|
+
* @param {unknown} value
|
|
137
|
+
* @param {string} name - Field name used in the error message
|
|
138
|
+
*/
|
|
139
|
+
export function assertTimestamp(value, name) {
|
|
140
|
+
if (value !== undefined && !Number.isSafeInteger(value)) {
|
|
141
|
+
throw new TypeError(
|
|
142
|
+
`UCAN ${name} must be a safe integer. Received: ${value}`
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
127
147
|
/**
|
|
128
148
|
* Validate the not before time of a UCAN
|
|
129
149
|
*
|
|
@@ -131,7 +151,7 @@ export function assertExpiration(exp, now = nowInSeconds()) {
|
|
|
131
151
|
* @param {number} [now]
|
|
132
152
|
*/
|
|
133
153
|
export function assertNotBefore(nbf, now = nowInSeconds()) {
|
|
134
|
-
if (nbf && nbf > now) {
|
|
154
|
+
if (nbf !== undefined && nbf > now) {
|
|
135
155
|
throw new Error('UCAN not valid yet')
|
|
136
156
|
}
|
|
137
157
|
}
|
|
@@ -173,6 +193,9 @@ export async function verifySignature(
|
|
|
173
193
|
message: dagCbor.encode(signaturePayload(envelope)),
|
|
174
194
|
did: issuer,
|
|
175
195
|
type: envelope.alg,
|
|
196
|
+
// Accept only canonical signatures, such as low-S ECDSA, so each UCAN
|
|
197
|
+
// has one CID for replay and revocation checks
|
|
198
|
+
strict: true,
|
|
176
199
|
})
|
|
177
200
|
|
|
178
201
|
if (!isVerified) {
|
|
@@ -310,146 +333,15 @@ export function assertMeta(meta) {
|
|
|
310
333
|
}
|
|
311
334
|
}
|
|
312
335
|
|
|
313
|
-
/**
|
|
314
|
-
* ECDSA curve orders by signature algorithm, with the byte length of each
|
|
315
|
-
* signature component.
|
|
316
|
-
*
|
|
317
|
-
* @type {Record<string, {n: bigint, size: number}>}
|
|
318
|
-
*/
|
|
319
|
-
const ECDSA_CURVES = {
|
|
320
|
-
ES256: {
|
|
321
|
-
n: 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n,
|
|
322
|
-
size: 32,
|
|
323
|
-
},
|
|
324
|
-
ES384: {
|
|
325
|
-
n: 0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973n,
|
|
326
|
-
size: 48,
|
|
327
|
-
},
|
|
328
|
-
ES512: {
|
|
329
|
-
n: 0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409n,
|
|
330
|
-
size: 66,
|
|
331
|
-
},
|
|
332
|
-
ES256K: {
|
|
333
|
-
n: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n,
|
|
334
|
-
size: 32,
|
|
335
|
-
},
|
|
336
|
-
EIP191: {
|
|
337
|
-
n: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n,
|
|
338
|
-
size: 32,
|
|
339
|
-
},
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* @param {Uint8Array} bytes
|
|
344
|
-
*/
|
|
345
|
-
function bytesToBigInt(bytes) {
|
|
346
|
-
let n = 0n
|
|
347
|
-
for (const b of bytes) n = (n << 8n) | BigInt(b)
|
|
348
|
-
return n
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* @param {bigint} n
|
|
353
|
-
* @param {number} size
|
|
354
|
-
*/
|
|
355
|
-
function bigIntToBytes(n, size) {
|
|
356
|
-
const out = new Uint8Array(size)
|
|
357
|
-
for (let i = size - 1; i >= 0; i--) {
|
|
358
|
-
out[i] = Number(n & 0xffn)
|
|
359
|
-
n >>= 8n
|
|
360
|
-
}
|
|
361
|
-
return out
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Signatures a signer could have issued that verify the same as `signature`.
|
|
366
|
-
*
|
|
367
|
-
* ECDSA signatures are malleable: `(r, s)` and `(r, n - s)` are both valid.
|
|
368
|
-
* EIP-191 signatures additionally carry a recovery byte `v` that wallets
|
|
369
|
-
* return as 27/28 or 0/1. Verifiers also accept EIP-155 style `v >= 35`,
|
|
370
|
-
* but no signer issues those for `personal_sign`, so they are not listed.
|
|
371
|
-
*
|
|
372
|
-
* The result always includes `signature` itself. Other algorithms (Ed25519,
|
|
373
|
-
* RSA) have a single valid signature.
|
|
374
|
-
*
|
|
375
|
-
* @param {import('iso-signatures/types').SignatureType} alg
|
|
376
|
-
* @param {Uint8Array} signature
|
|
377
|
-
* @returns {Uint8Array[]}
|
|
378
|
-
*/
|
|
379
|
-
export function equivalentSignatures(alg, signature) {
|
|
380
|
-
const curve = ECDSA_CURVES[alg]
|
|
381
|
-
if (!curve) {
|
|
382
|
-
return [signature]
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const { n, size } = curve
|
|
386
|
-
const isEip191 = alg === 'EIP191'
|
|
387
|
-
if (signature.length !== size * 2 + (isEip191 ? 1 : 0)) {
|
|
388
|
-
return [signature]
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
const r = signature.subarray(0, size)
|
|
392
|
-
const s = bytesToBigInt(signature.subarray(size, size * 2))
|
|
393
|
-
if (s === 0n || s >= n) {
|
|
394
|
-
return [signature]
|
|
395
|
-
}
|
|
396
|
-
const sFlipped = bigIntToBytes(n - s, size)
|
|
397
|
-
|
|
398
|
-
if (!isEip191) {
|
|
399
|
-
return [signature, new Uint8Array([...r, ...sFlipped])]
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const v = signature[size * 2]
|
|
403
|
-
/** @type {number} */
|
|
404
|
-
let parity
|
|
405
|
-
if (v === 0 || v === 27) parity = 0
|
|
406
|
-
else if (v === 1 || v === 28) parity = 1
|
|
407
|
-
else if (v >= 35) parity = v % 2 === 0 ? 1 : 0
|
|
408
|
-
else return [signature]
|
|
409
|
-
|
|
410
|
-
const sBytes = signature.subarray(size, size * 2)
|
|
411
|
-
const candidates = [
|
|
412
|
-
new Uint8Array([...r, ...sBytes, parity]),
|
|
413
|
-
new Uint8Array([...r, ...sBytes, parity + 27]),
|
|
414
|
-
new Uint8Array([...r, ...sFlipped, 1 - parity]),
|
|
415
|
-
new Uint8Array([...r, ...sFlipped, 28 - parity]),
|
|
416
|
-
]
|
|
417
|
-
return [signature, ...candidates.filter((c) => !equals(c, signature))]
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* CIDs of every envelope that verifies the same as `envelope`: one per
|
|
422
|
-
* signature in {@link equivalentSignatures}, starting with the CID of
|
|
423
|
-
* `envelope` itself.
|
|
424
|
-
*
|
|
425
|
-
* @param {import('./types.js').DecodedEnvelope<PayloadSpec>} envelope
|
|
426
|
-
*/
|
|
427
|
-
export async function equivalentCids(envelope) {
|
|
428
|
-
return await Promise.all(
|
|
429
|
-
equivalentSignatures(envelope.alg, envelope.signature).map((signature) =>
|
|
430
|
-
cid({ ...envelope, signature })
|
|
431
|
-
)
|
|
432
|
-
)
|
|
433
|
-
}
|
|
434
|
-
|
|
435
336
|
/**
|
|
436
337
|
* Assert that the input is not revoked.
|
|
437
338
|
*
|
|
438
|
-
*
|
|
439
|
-
* so re-encoding a revoked UCAN with another valid signature does not get it
|
|
440
|
-
* past a CID-based revocation list.
|
|
441
|
-
*
|
|
442
|
-
* @param {import('./types.js').DecodedEnvelope<PayloadSpec>} envelope
|
|
339
|
+
* @param {CID} cid
|
|
443
340
|
* @param {(cid: CID) => Promise<boolean>} [isRevokedFn]
|
|
444
341
|
*/
|
|
445
|
-
export async function assertNotRevoked(
|
|
446
|
-
if (
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
for (const c of await equivalentCids(envelope)) {
|
|
450
|
-
if (await isRevokedFn(c)) {
|
|
451
|
-
throw new Error('UCAN revoked')
|
|
452
|
-
}
|
|
342
|
+
export async function assertNotRevoked(cid, isRevokedFn) {
|
|
343
|
+
if (isRevokedFn && (await isRevokedFn(cid))) {
|
|
344
|
+
throw new Error('UCAN revoked')
|
|
453
345
|
}
|
|
454
346
|
}
|
|
455
347
|
|
|
@@ -488,17 +380,32 @@ export const cborValue =
|
|
|
488
380
|
})
|
|
489
381
|
export const cborObject = z.record(z.string(), cborValue)
|
|
490
382
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
383
|
+
/**
|
|
384
|
+
* Policy selector, validated with {@link parseSelector}.
|
|
385
|
+
*
|
|
386
|
+
* @see https://github.com/ucan-wg/delegation#selectors
|
|
387
|
+
*/
|
|
388
|
+
export const selector = z.string().superRefine((value, ctx) => {
|
|
389
|
+
try {
|
|
390
|
+
parseSelector(value)
|
|
391
|
+
} catch (error) {
|
|
392
|
+
ctx.addIssue({
|
|
393
|
+
code: 'custom',
|
|
394
|
+
message: /** @type {Error} */ (error).message,
|
|
395
|
+
})
|
|
396
|
+
}
|
|
397
|
+
})
|
|
495
398
|
|
|
496
399
|
/**
|
|
497
400
|
* @typedef {z.infer<typeof selector>} Selector
|
|
498
401
|
*/
|
|
499
402
|
|
|
500
403
|
/**
|
|
501
|
-
* Statement
|
|
404
|
+
* Statement, as defined by the spec's IPLD schema. Every statement is a tuple
|
|
405
|
+
* of exact arity. Inequality values are integers or floats; large integers
|
|
406
|
+
* decode from dag-cbor as `bigint`.
|
|
407
|
+
*
|
|
408
|
+
* @see https://github.com/ucan-wg/delegation#policy
|
|
502
409
|
*/
|
|
503
410
|
export const statement =
|
|
504
411
|
/** @type {typeof z.lazy<z.ZodType<import('../src/types.js').Statement<unknown>>>} */ (
|
|
@@ -509,7 +416,7 @@ export const statement =
|
|
|
509
416
|
z.tuple([
|
|
510
417
|
z.union([z.literal('=='), z.literal('!=')]),
|
|
511
418
|
selector,
|
|
512
|
-
|
|
419
|
+
cborValue,
|
|
513
420
|
]), // Equality
|
|
514
421
|
z.tuple([
|
|
515
422
|
z.union([
|
|
@@ -519,14 +426,14 @@ export const statement =
|
|
|
519
426
|
z.literal('>='),
|
|
520
427
|
]),
|
|
521
428
|
selector,
|
|
522
|
-
z.number(),
|
|
429
|
+
z.union([z.number(), z.bigint()]),
|
|
523
430
|
]), // Inequality
|
|
524
431
|
z.tuple([z.literal('like'), selector, z.string()]), // Like
|
|
525
432
|
z.tuple([z.literal('not'), statement]),
|
|
526
433
|
z.tuple([z.literal('and'), z.array(statement)]),
|
|
527
434
|
z.tuple([z.literal('or'), z.array(statement)]),
|
|
528
|
-
z.tuple([z.literal('all'),
|
|
529
|
-
z.tuple([z.literal('any'),
|
|
435
|
+
z.tuple([z.literal('all'), selector, statement]),
|
|
436
|
+
z.tuple([z.literal('any'), selector, statement]),
|
|
530
437
|
])
|
|
531
438
|
})
|
|
532
439
|
|
package/src/varsig.js
CHANGED
|
@@ -19,7 +19,6 @@ export const CODE_ENCODING = /** @types {const} */ {
|
|
|
19
19
|
* @type {Record<number, string>}
|
|
20
20
|
*/
|
|
21
21
|
export const SIG_PREFIX = /** @type {const} */ ({
|
|
22
|
-
0x12_05: 'RSA',
|
|
23
22
|
0xed: 'EdDSA',
|
|
24
23
|
0xec: 'ECDSA',
|
|
25
24
|
})
|
|
@@ -37,8 +36,6 @@ const VARINT = {
|
|
|
37
36
|
'DAG-CBOR': [113],
|
|
38
37
|
|
|
39
38
|
/** Presets */
|
|
40
|
-
// rsa(2b)+SHA2-256(1b)+256(1b)
|
|
41
|
-
RS256: [133, 36, 18, 128, 2],
|
|
42
39
|
// eddsa(2b)+edwards25519(2b)+SHA2-512(1b)
|
|
43
40
|
Ed25519: [237, 1, 237, 1, 19],
|
|
44
41
|
// ECDSA(2b) + p256(2b) + SHA2-256(1b)
|
|
@@ -54,7 +51,7 @@ const VARINT = {
|
|
|
54
51
|
}
|
|
55
52
|
|
|
56
53
|
/**
|
|
57
|
-
* @param {import('./types.js').
|
|
54
|
+
* @param {import('./types.js').VarsigEncodeOptions} options
|
|
58
55
|
*/
|
|
59
56
|
export function encode(options) {
|
|
60
57
|
const enc = VARINT[options.enc]
|
|
@@ -62,7 +59,9 @@ export function encode(options) {
|
|
|
62
59
|
throw new TypeError(`Unsupported encoding ${options.enc}`)
|
|
63
60
|
}
|
|
64
61
|
|
|
65
|
-
const alg = VARINT[
|
|
62
|
+
const alg = /** @type {Partial<Record<string, number[]>>} */ (VARINT)[
|
|
63
|
+
options.alg
|
|
64
|
+
]
|
|
66
65
|
if (!alg) {
|
|
67
66
|
throw new TypeError(`Unsupported algorithm ${options.alg}`)
|
|
68
67
|
}
|
|
@@ -95,7 +94,6 @@ function getPreset(bytes, presets) {
|
|
|
95
94
|
throw new TypeError(`Unsupported preset ${bytes}`)
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
/**
|
|
99
97
|
/**
|
|
100
98
|
* Decode varsig header
|
|
101
99
|
*
|
|
@@ -119,39 +117,44 @@ export function decode(buf) {
|
|
|
119
117
|
}
|
|
120
118
|
const [code] = varint.decode(buf, 2)
|
|
121
119
|
|
|
120
|
+
/** @type {import('./types.js').DecodeOutput} */
|
|
121
|
+
let output
|
|
122
122
|
switch (SIG_PREFIX[code]) {
|
|
123
|
-
case 'RSA': {
|
|
124
|
-
return {
|
|
125
|
-
alg: getPreset(bytes.readBytes(5), ['RS256']),
|
|
126
|
-
enc: getEncoding(bytes.readBytes(1)),
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
123
|
case 'EdDSA': {
|
|
130
|
-
|
|
124
|
+
output = {
|
|
131
125
|
alg: getPreset(bytes.readBytes(5), ['Ed25519']),
|
|
132
126
|
enc: getEncoding(bytes.readBytes(1)),
|
|
133
127
|
}
|
|
128
|
+
break
|
|
134
129
|
}
|
|
135
130
|
case 'ECDSA': {
|
|
136
131
|
const isEIP191 = varint.decode(buf, 2 + 5)[0] === EIP191_ENCODING
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
132
|
+
output = isEIP191
|
|
133
|
+
? {
|
|
134
|
+
alg: getPreset(bytes.readBytes(8), ['EIP191']),
|
|
135
|
+
enc: getEncoding(bytes.readBytes(1)),
|
|
136
|
+
}
|
|
137
|
+
: {
|
|
138
|
+
alg: getPreset(bytes.readBytes(5), [
|
|
139
|
+
'ES256',
|
|
140
|
+
'ES256K',
|
|
141
|
+
'ES384',
|
|
142
|
+
'ES512',
|
|
143
|
+
]),
|
|
144
|
+
enc: getEncoding(bytes.readBytes(1)),
|
|
145
|
+
}
|
|
146
|
+
break
|
|
153
147
|
}
|
|
154
148
|
default:
|
|
155
149
|
throw new TypeError(`Unsupported algorithm ${code}`)
|
|
156
150
|
}
|
|
151
|
+
|
|
152
|
+
const trailing = buf.length - bytes.offset
|
|
153
|
+
if (trailing !== 0) {
|
|
154
|
+
throw new TypeError(
|
|
155
|
+
`Invalid varsig header ${trailing} trailing byte(s) after the payload encoding`
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return output
|
|
157
160
|
}
|