iso-ucan 0.5.2 → 2.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.map +1 -1
- package/dist/src/envelope.d.ts +40 -9
- package/dist/src/envelope.d.ts.map +1 -1
- package/dist/src/invocation.d.ts +1 -2
- 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.d.ts +9 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils.d.ts +30 -2
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/varsig.d.ts +0 -1
- package/dist/src/varsig.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/delegation.js +7 -1
- package/src/envelope.js +113 -36
- package/src/invocation.js +53 -18
- package/src/policy.js +597 -195
- package/src/types/envelope.ts +4 -4
- package/src/types/policy.ts +5 -2
- package/src/types.ts +9 -1
- package/src/utils.js +64 -10
- package/src/varsig.js +30 -19
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.ts
CHANGED
|
@@ -123,14 +123,22 @@ export interface InvocationOptions extends DelegationValidateOptions {
|
|
|
123
123
|
* Issued at time in seconds
|
|
124
124
|
*/
|
|
125
125
|
iat?: number
|
|
126
|
-
nbf?: number
|
|
127
126
|
nonce?: Uint8Array
|
|
128
127
|
cause?: CID
|
|
128
|
+
/**
|
|
129
|
+
* Metadata, omitted from the payload when empty
|
|
130
|
+
*/
|
|
129
131
|
meta?: CborObject
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
export interface InvocationFromOptions extends DelegationValidateOptions {
|
|
133
135
|
bytes: Uint8Array
|
|
136
|
+
/**
|
|
137
|
+
* The current time in seconds, used for the invocation's own `exp`, the time
|
|
138
|
+
* bounds of its proofs and `maxTtl`. Mostly used for testing.
|
|
139
|
+
* @default Math.floor(Date.now() / 1000)
|
|
140
|
+
*/
|
|
141
|
+
now?: number
|
|
134
142
|
audience?: VerifiableDID
|
|
135
143
|
resolveProof: ResolveProof
|
|
136
144
|
/**
|
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
|
/**
|
|
@@ -51,6 +52,29 @@ export async function cid(envelope) {
|
|
|
51
52
|
return CID.create(1, dagCbor.code, hash)
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Assert that `bytes` is the canonical DAG-CBOR encoding of `envelope`.
|
|
57
|
+
*
|
|
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
|
+
*
|
|
62
|
+
* @param {import('./types.js').DecodedEnvelope<PayloadSpec>} envelope
|
|
63
|
+
* @param {Uint8Array} bytes
|
|
64
|
+
*/
|
|
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
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
54
78
|
/**
|
|
55
79
|
* Replay key of a UCAN: the CID of its signature payload, the exact bytes the
|
|
56
80
|
* signature covers.
|
|
@@ -124,6 +148,21 @@ export function assertExpiration(exp, now = nowInSeconds()) {
|
|
|
124
148
|
}
|
|
125
149
|
}
|
|
126
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Validate an optional UCAN timestamp (e.g. `nbf` or `iat`): when present it
|
|
153
|
+
* must be an integer number of seconds within ±(2^53 − 1).
|
|
154
|
+
*
|
|
155
|
+
* @param {unknown} value
|
|
156
|
+
* @param {string} name - Field name used in the error message
|
|
157
|
+
*/
|
|
158
|
+
export function assertTimestamp(value, name) {
|
|
159
|
+
if (value !== undefined && !Number.isSafeInteger(value)) {
|
|
160
|
+
throw new TypeError(
|
|
161
|
+
`UCAN ${name} must be a safe integer. Received: ${value}`
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
127
166
|
/**
|
|
128
167
|
* Validate the not before time of a UCAN
|
|
129
168
|
*
|
|
@@ -131,7 +170,7 @@ export function assertExpiration(exp, now = nowInSeconds()) {
|
|
|
131
170
|
* @param {number} [now]
|
|
132
171
|
*/
|
|
133
172
|
export function assertNotBefore(nbf, now = nowInSeconds()) {
|
|
134
|
-
if (nbf && nbf > now) {
|
|
173
|
+
if (nbf !== undefined && nbf > now) {
|
|
135
174
|
throw new Error('UCAN not valid yet')
|
|
136
175
|
}
|
|
137
176
|
}
|
|
@@ -488,17 +527,32 @@ export const cborValue =
|
|
|
488
527
|
})
|
|
489
528
|
export const cborObject = z.record(z.string(), cborValue)
|
|
490
529
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
530
|
+
/**
|
|
531
|
+
* Policy selector, validated with {@link parseSelector}.
|
|
532
|
+
*
|
|
533
|
+
* @see https://github.com/ucan-wg/delegation#selectors
|
|
534
|
+
*/
|
|
535
|
+
export const selector = z.string().superRefine((value, ctx) => {
|
|
536
|
+
try {
|
|
537
|
+
parseSelector(value)
|
|
538
|
+
} catch (error) {
|
|
539
|
+
ctx.addIssue({
|
|
540
|
+
code: 'custom',
|
|
541
|
+
message: /** @type {Error} */ (error).message,
|
|
542
|
+
})
|
|
543
|
+
}
|
|
544
|
+
})
|
|
495
545
|
|
|
496
546
|
/**
|
|
497
547
|
* @typedef {z.infer<typeof selector>} Selector
|
|
498
548
|
*/
|
|
499
549
|
|
|
500
550
|
/**
|
|
501
|
-
* Statement
|
|
551
|
+
* Statement, as defined by the spec's IPLD schema. Every statement is a tuple
|
|
552
|
+
* of exact arity. Inequality values are integers or floats; large integers
|
|
553
|
+
* decode from dag-cbor as `bigint`.
|
|
554
|
+
*
|
|
555
|
+
* @see https://github.com/ucan-wg/delegation#policy
|
|
502
556
|
*/
|
|
503
557
|
export const statement =
|
|
504
558
|
/** @type {typeof z.lazy<z.ZodType<import('../src/types.js').Statement<unknown>>>} */ (
|
|
@@ -509,7 +563,7 @@ export const statement =
|
|
|
509
563
|
z.tuple([
|
|
510
564
|
z.union([z.literal('=='), z.literal('!=')]),
|
|
511
565
|
selector,
|
|
512
|
-
|
|
566
|
+
cborValue,
|
|
513
567
|
]), // Equality
|
|
514
568
|
z.tuple([
|
|
515
569
|
z.union([
|
|
@@ -519,14 +573,14 @@ export const statement =
|
|
|
519
573
|
z.literal('>='),
|
|
520
574
|
]),
|
|
521
575
|
selector,
|
|
522
|
-
z.number(),
|
|
576
|
+
z.union([z.number(), z.bigint()]),
|
|
523
577
|
]), // Inequality
|
|
524
578
|
z.tuple([z.literal('like'), selector, z.string()]), // Like
|
|
525
579
|
z.tuple([z.literal('not'), statement]),
|
|
526
580
|
z.tuple([z.literal('and'), z.array(statement)]),
|
|
527
581
|
z.tuple([z.literal('or'), z.array(statement)]),
|
|
528
|
-
z.tuple([z.literal('all'),
|
|
529
|
-
z.tuple([z.literal('any'),
|
|
582
|
+
z.tuple([z.literal('all'), selector, statement]),
|
|
583
|
+
z.tuple([z.literal('any'), selector, statement]),
|
|
530
584
|
])
|
|
531
585
|
})
|
|
532
586
|
|
package/src/varsig.js
CHANGED
|
@@ -95,7 +95,6 @@ function getPreset(bytes, presets) {
|
|
|
95
95
|
throw new TypeError(`Unsupported preset ${bytes}`)
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
/**
|
|
99
98
|
/**
|
|
100
99
|
* Decode varsig header
|
|
101
100
|
*
|
|
@@ -119,39 +118,51 @@ export function decode(buf) {
|
|
|
119
118
|
}
|
|
120
119
|
const [code] = varint.decode(buf, 2)
|
|
121
120
|
|
|
121
|
+
/** @type {import('./types.js').DecodeOutput} */
|
|
122
|
+
let output
|
|
122
123
|
switch (SIG_PREFIX[code]) {
|
|
123
124
|
case 'RSA': {
|
|
124
|
-
|
|
125
|
+
output = {
|
|
125
126
|
alg: getPreset(bytes.readBytes(5), ['RS256']),
|
|
126
127
|
enc: getEncoding(bytes.readBytes(1)),
|
|
127
128
|
}
|
|
129
|
+
break
|
|
128
130
|
}
|
|
129
131
|
case 'EdDSA': {
|
|
130
|
-
|
|
132
|
+
output = {
|
|
131
133
|
alg: getPreset(bytes.readBytes(5), ['Ed25519']),
|
|
132
134
|
enc: getEncoding(bytes.readBytes(1)),
|
|
133
135
|
}
|
|
136
|
+
break
|
|
134
137
|
}
|
|
135
138
|
case 'ECDSA': {
|
|
136
139
|
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
|
-
}
|
|
140
|
+
output = isEIP191
|
|
141
|
+
? {
|
|
142
|
+
alg: getPreset(bytes.readBytes(8), ['EIP191']),
|
|
143
|
+
enc: getEncoding(bytes.readBytes(1)),
|
|
144
|
+
}
|
|
145
|
+
: {
|
|
146
|
+
alg: getPreset(bytes.readBytes(5), [
|
|
147
|
+
'ES256',
|
|
148
|
+
'ES256K',
|
|
149
|
+
'ES384',
|
|
150
|
+
'ES512',
|
|
151
|
+
]),
|
|
152
|
+
enc: getEncoding(bytes.readBytes(1)),
|
|
153
|
+
}
|
|
154
|
+
break
|
|
153
155
|
}
|
|
154
156
|
default:
|
|
155
157
|
throw new TypeError(`Unsupported algorithm ${code}`)
|
|
156
158
|
}
|
|
159
|
+
|
|
160
|
+
const trailing = buf.length - bytes.offset
|
|
161
|
+
if (trailing !== 0) {
|
|
162
|
+
throw new TypeError(
|
|
163
|
+
`Invalid varsig header ${trailing} trailing byte(s) after the payload encoding`
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return output
|
|
157
168
|
}
|