iso-ucan 0.5.0 → 0.5.2

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.
@@ -52,7 +52,7 @@ export function sign(options: EnvelopeSignOptions): Promise<{
52
52
  * @param {EnvelopeEncodeOptions} options
53
53
  *
54
54
  */
55
- export function encode(options: EnvelopeEncodeOptions): ByteView<(Uint8Array<ArrayBufferLike> | SignaturePayload<unknown>)[]>;
55
+ export function encode(options: EnvelopeEncodeOptions): ByteView<(Uint8Array<ArrayBufferLike> | SignaturePayload<unknown>)[], ArrayBufferLike>;
56
56
  /**
57
57
  * Decode a UCAN envelope
58
58
  *
@@ -1 +1 @@
1
- {"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../../src/envelope.js"],"names":[],"mappings":"AAYA;;;;;;;;GAQG;AACH,6CALG;IAA6B,IAAI,EAAzB,WAAW;IACM,OAAO,EAAxB,OAAO;IACU,OAAO;IAC8B,aAAa,EAAnE,OAAO,sBAAsB,EAAE,aAAa;CACtD,oBAgBA;AAED;;;;GAIG;AACH,8CAFW,UAAU;;;;EAkCpB;AAED;;;;;GAKG;AACH,6CAHW,OAAO,GACL,OAAO,IAAI,iBAAiB,CAIxC;AAED;;;;;GAKG;AACH,6CAHW,OAAO,GACL,OAAO,IAAI,iBAAiB,CAIxC;AAED;;;GAGG;AACH,8BAFW,mBAAmB;;;GAgB7B;AAED;;;;;;GAMG;AACH,gCAHW,qBAAqB,yEAS/B;AAED;;;;;;;GAOG;AACH,uBAL2B,IAAI,SAAlB,WAAa,WAEf,qBAAqB,GACnB,gBAAgB,IAAI,CAAC,CAgCjC;AAtKD;;;GAGG;AAEH,cAAc;AACd,sBAAuB,YAAY,CAAA;iCALuK,YAAY;6BAAZ,YAAY;sCAAZ,YAAY;uCAAZ,YAAY;uCAAZ,YAAY;yCAAZ,YAAY;2CAAZ,YAAY;8BACzL,cAAc;2CAD+J,YAAY;qCAAZ,YAAY"}
1
+ {"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../../src/envelope.js"],"names":[],"mappings":"AAYA;;;;;;;;GAQG;AACH,6CALG;IAA6B,IAAI,EAAzB,WAAW;IACM,OAAO,EAAxB,OAAO;IACU,OAAO;IAC8B,aAAa,EAAnE,OAAO,sBAAsB,EAAE,aAAa;CACtD,oBAgBA;AAED;;;;GAIG;AACH,8CAFW,UAAU;;;;EAkCpB;AAED;;;;;GAKG;AACH,6CAHW,OAAO,GACL,OAAO,IAAI,iBAAiB,CAIxC;AAED;;;;;GAKG;AACH,6CAHW,OAAO,GACL,OAAO,IAAI,iBAAiB,CAIxC;AAED;;;GAGG;AACH,8BAFW,mBAAmB;;;GAgB7B;AAED;;;;;;GAMG;AACH,gCAHW,qBAAqB,0FAS/B;AAED;;;;;;;GAOG;AACH,uBAL2B,IAAI,SAAlB,WAAa,WAEf,qBAAqB,GACnB,gBAAgB,IAAI,CAAC,CAgCjC;AAtKD;;;GAGG;AAEH,cAAc;AACd,sBAAuB,YAAY,CAAA;iCALuK,YAAY;6BAAZ,YAAY;sCAAZ,YAAY;uCAAZ,YAAY;uCAAZ,YAAY;yCAAZ,YAAY;2CAAZ,YAAY;8BACzL,cAAc;2CAD+J,YAAY;qCAAZ,YAAY"}
@@ -27,8 +27,9 @@ export class Invocation {
27
27
  * @param {Uint8Array} bytes
28
28
  * @param {CID} cid
29
29
  * @param {Delegation[]} delegations
30
+ * @param {string} replayKey
30
31
  */
31
- constructor(envelope: import("./types.js").DecodedEnvelope<"inv">, bytes: Uint8Array, cid: CID, delegations: Delegation[]);
32
+ constructor(envelope: import("./types.js").DecodedEnvelope<"inv">, bytes: Uint8Array, cid: CID, delegations: Delegation[], replayKey: string);
32
33
  /**
33
34
  * @type {Delegation[]}
34
35
  */
@@ -37,6 +38,16 @@ export class Invocation {
37
38
  * @type {CID}
38
39
  */
39
40
  cid: CID;
41
+ /**
42
+ * Key to deduplicate this invocation on: the CID of its signature payload.
43
+ *
44
+ * Use this instead of {@link Invocation.cid} to detect replays. The CID
45
+ * covers the signature bytes, and some signature algorithms (e.g. ECDSA)
46
+ * accept more than one valid signature for the same payload.
47
+ *
48
+ * @type {string}
49
+ */
50
+ replayKey: string;
40
51
  envelope: import("./types.js").DecodedEnvelope<"inv">;
41
52
  payload: import("./types.js").InvocationPayload;
42
53
  bytes: Uint8Array<ArrayBufferLike>;
@@ -1 +1 @@
1
- {"version":3,"file":"invocation.d.ts","sourceRoot":"","sources":["../../src/invocation.js"],"names":[],"mappings":"AAqLA;;;;GAIG;AACH,sCAHW,OAAO,YAAY,EAAE,iBAAiB,UACtC,UAAU,EAAE,WA6DtB;AAtOD;;;GAGG;AAEH;;GAEG;AACH;IA0BE;;;OAGG;IACH,qBAFW,OAAO,YAAY,EAAE,qBAAqB,uBAgDpD;IAED;;OAEG;IACH,uBAFW,OAAO,YAAY,EAAE,iBAAiB,uBA0DhD;IA9HD;;;;;;OAMG;IACH,sBALW,OAAO,YAAY,EAAE,eAAe,CAAC,KAAK,CAAC,SAC3C,UAAU,OACV,GAAG,eACH,UAAU,EAAE,EAQtB;IAvBD;;OAEG;IACH,aAFU,UAAU,EAAE,CAEX;IAEX;;OAEG;IACH,KAFU,GAAG,CAEV;IAUD,sDAAwB;IACxB,gDAA+B;IAC/B,mCAAkB;CAqHrB;gCAjJ4B,iBAAiB;yBACxB,cAAc"}
1
+ {"version":3,"file":"invocation.d.ts","sourceRoot":"","sources":["../../src/invocation.js"],"names":[],"mappings":"AA6OA;;;;GAIG;AACH,sCAHW,OAAO,YAAY,EAAE,iBAAiB,UACtC,UAAU,EAAE,WA6DtB;AA3RD;;;GAGG;AAEH;;GAEG;AACH;IAuCE;;;OAGG;IACH,qBAFW,OAAO,YAAY,EAAE,qBAAqB,uBAgEpD;IAED;;OAEG;IACH,uBAFW,OAAO,YAAY,EAAE,iBAAiB,uBAgEhD;IAtJD;;;;;;;OAOG;IACH,sBANW,OAAO,YAAY,EAAE,eAAe,CAAC,KAAK,CAAC,SAC3C,UAAU,OACV,GAAG,eACH,UAAU,EAAE,aACZ,MAAM,EAShB;IApCD;;OAEG;IACH,aAFU,UAAU,EAAE,CAEX;IAEX;;OAEG;IACH,KAFU,GAAG,CAEV;IAEH;;;;;;;;OAQG;IACH,WAFU,MAAM,CAEP;IAWP,sDAAwB;IACxB,gDAA+B;IAC/B,mCAAkB;CA4IrB;gCApL4B,iBAAiB;yBACxB,cAAc"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @import {Driver} from 'iso-kv'
3
+ * @import {ReplayStore} from './types.js'
4
+ */
5
+ /**
6
+ * Replay store backed by an `iso-kv` driver.
7
+ *
8
+ * Keys are kept until the invocation expires, or forever when the invocation
9
+ * has no expiration.
10
+ *
11
+ * `checkAndSet` is atomic within one JavaScript realm only: calls are
12
+ * serialized through an in-process queue. When several processes or instances
13
+ * share a driver, implement {@link ReplayStore} on top of an atomic
14
+ * set-if-absent operation instead (for example Redis `SET NX` or SQL
15
+ * `INSERT ... ON CONFLICT DO NOTHING`).
16
+ *
17
+ * @implements {ReplayStore}
18
+ */
19
+ export class KVReplayStore implements ReplayStore {
20
+ /**
21
+ * @param {Driver} [driver] - Defaults to a {@link MemoryDriver}.
22
+ */
23
+ constructor(driver?: Driver);
24
+ /** @type {KV} */
25
+ kv: KV;
26
+ /**
27
+ * Record `key` and return `true`, or return `false` if it was already
28
+ * recorded and has not expired.
29
+ *
30
+ * @param {string} key
31
+ * @param {number | null} expiration - Unix time in seconds, or `null` to keep forever
32
+ * @returns {Promise<boolean>}
33
+ */
34
+ checkAndSet(key: string, expiration: number | null): Promise<boolean>;
35
+ #private;
36
+ }
37
+ import type { ReplayStore } from './types.js';
38
+ import { KV } from 'iso-kv';
39
+ import type { Driver } from 'iso-kv';
40
+ //# sourceMappingURL=replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/replay.js"],"names":[],"mappings":"AAGA;;;GAGG;AAEH;;;;;;;;;;;;;GAaG;AACH;IAOE;;OAEG;IACH,qBAFW,MAAM,EAIhB;IAXD,iBAAiB;IACjB,IADW,EAAE,CACX;IAYF;;;;;;;OAOG;IACH,iBAJW,MAAM,cACN,MAAM,GAAG,IAAI,GACX,OAAO,CAAC,OAAO,CAAC,CAc5B;;CACF;iCApD6B,YAAY;mBALvB,QAAQ;4BAIF,QAAQ"}
@@ -1,5 +1,5 @@
1
1
  export { defineClient } from "./client.js";
2
2
  export { defineCommand } from "./command.js";
3
- export { defineServer } from "./server.js";
4
3
  export { cidSchema, receipt, receiptError, receiptResult, receiptServerError } from "./receipt.js";
4
+ export { DEFAULT_MAX_TTL, defineServer } from "./server.js";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -75,7 +75,7 @@ export function receipt<Result extends z.ZodType, Error extends z.ZodType>(resul
75
75
  * produces when serialized through `JSON.stringify`) and normalizes to a
76
76
  * {@link CID}.
77
77
  */
78
- export const cidSchema: z.ZodUnion<readonly [z.ZodCustom<CID<unknown, number, number, import("multiformats").Version>, CID<unknown, number, number, import("multiformats").Version>>, z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodObject<{
78
+ export const cidSchema: z.ZodUnion<readonly [z.ZodInstanceOf<CID<unknown, number, number, import("multiformats").Version>>, z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodObject<{
79
79
  '/': z.ZodString;
80
80
  }, z.core.$strip>, z.ZodTransform<string, {
81
81
  '/': string;
@@ -100,7 +100,7 @@ export const receiptServerError: z.ZodObject<{
100
100
  error: z.ZodObject<{
101
101
  code: z.ZodLiteral<"SERVER_ERROR">;
102
102
  message: z.ZodString;
103
- cid: z.ZodOptional<z.ZodUnion<readonly [z.ZodCustom<CID<unknown, number, number, import("multiformats").Version>, CID<unknown, number, number, import("multiformats").Version>>, z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodObject<{
103
+ cid: z.ZodOptional<z.ZodUnion<readonly [z.ZodInstanceOf<CID<unknown, number, number, import("multiformats").Version>>, z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodObject<{
104
104
  '/': z.ZodString;
105
105
  }, z.core.$strip>, z.ZodTransform<string, {
106
106
  '/': string;
@@ -6,12 +6,32 @@
6
6
  * schema, and dispatches to the corresponding handler in `options.handlers`.
7
7
  * The handler's return value is serialized to JSON.
8
8
  *
9
+ * Each invocation is executed at most once: replays are rejected using
10
+ * `options.replayStore`, which defaults to an in-memory {@link KVReplayStore}.
11
+ * Pass a shared store when running more than one server instance.
12
+ *
13
+ * Invocations must expire within `options.maxTtl` seconds, which defaults to
14
+ * {@link DEFAULT_MAX_TTL}. Pass `null` to accept any expiration.
15
+ *
9
16
  * @template {CommandsRecord} Commands
10
17
  * @param {Commands} commands
11
18
  * @param {DefineServerOptions<Commands>} options
12
19
  * @returns {ServerRequestHandler}
13
20
  */
14
21
  export function defineServer<Commands extends CommandsRecord>(commands: Commands, options: DefineServerOptions<Commands>): ServerRequestHandler;
22
+ /**
23
+ * @import {
24
+ * CommandsRecord,
25
+ * DefineServerOptions,
26
+ * ServerRequestHandler,
27
+ * } from './types.js'
28
+ * @import {ReceiptServerError} from './receipt.js'
29
+ */
30
+ /**
31
+ * Default maximum invocation lifetime accepted by {@link defineServer}, in
32
+ * seconds. Twice the client default `ttl`, to tolerate clock skew.
33
+ */
34
+ export const DEFAULT_MAX_TTL: 600;
15
35
  import type { CommandsRecord } from './types.js';
16
36
  import type { DefineServerOptions } from './types.js';
17
37
  import type { ServerRequestHandler } from './types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/rpc/server.js"],"names":[],"mappings":"AAkDA;;;;;;;;;;;;GAYG;AACH,6BAL8B,QAAQ,SAAzB,cAAgB,YAClB,QAAQ,WACR,oBAAoB,QAAQ,CAAC,GAC3B,oBAAoB,CA6FhC;oCAlJS,YAAY;yCAAZ,YAAY;0CAAZ,YAAY"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/rpc/server.js"],"names":[],"mappings":"AAyDA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,6BAL8B,QAAQ,SAAzB,cAAgB,YAClB,QAAQ,WACR,oBAAoB,QAAQ,CAAC,GAC3B,oBAAoB,CAoGhC;AA3KD;;;;;;;GAOG;AAEH;;;GAGG;AACH,8BAA+B,GAAG,CAAA;oCARxB,YAAY;yCAAZ,YAAY;0CAAZ,YAAY"}
@@ -3,7 +3,7 @@ import type { ISigner } from 'iso-signatures/types';
3
3
  import type { Capability } from '../capability.js';
4
4
  import type { Invocation } from '../invocation.js';
5
5
  import type { Store } from '../store.js';
6
- import type { ClientOptions, Promisable, VerifierResolver } from '../types.js';
6
+ import type { ClientOptions, Promisable, ReplayStore, VerifierResolver } from '../types.js';
7
7
  export interface CommandOptions<Args extends StandardSchemaV1, Cmd extends string = string, ReceiptSchema extends StandardSchemaV1 = StandardSchemaV1> {
8
8
  cmd: Cmd;
9
9
  args: Args;
@@ -104,6 +104,22 @@ export interface DefineServerOptions<Commands extends CommandsRecord> {
104
104
  signer: ISigner;
105
105
  store: Store;
106
106
  verifierResolver: VerifierResolver;
107
+ /**
108
+ * Store of seen invocations, used to reject replays.
109
+ * Use a store shared by all instances when running more than one server.
110
+ *
111
+ * @default new KVReplayStore() (in memory)
112
+ */
113
+ replayStore?: ReplayStore;
114
+ /**
115
+ * Maximum lifetime in seconds accepted for invocations. Invocations without
116
+ * an expiration, or expiring later than `maxTtl` seconds from now, are
117
+ * rejected. This keeps the replay store bounded. Pass `null` to accept any
118
+ * expiration, including none.
119
+ *
120
+ * @default 600
121
+ */
122
+ maxTtl?: number | null;
107
123
  }
108
124
  /**
109
125
  * Handler for an incoming HTTP request, returned by {@link defineServer}.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/rpc/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/rpc/types.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,13 @@ export * from './types/envelope';
17
17
  export * from './types/policy';
18
18
  export * from './types/varsig';
19
19
  export type ResolveProof = (proof: CID) => Promise<Delegation>;
20
+ /**
21
+ * Check whether a delegation CID is revoked.
22
+ *
23
+ * Called once for each CID the delegation could have been issued under
24
+ * (up to 2 for ECDSA and 4 for EIP-191 signatures), since those signatures
25
+ * can be re-encoded into another valid signature with a different CID.
26
+ */
20
27
  export type IsRevoked = (cid: CID) => Promise<boolean>;
21
28
  export type VerifierResolver = Resolver;
22
29
  export type DidResolver = _DidResolver;
@@ -110,6 +117,32 @@ export interface InvocationFromOptions extends DelegationValidateOptions {
110
117
  bytes: Uint8Array;
111
118
  audience?: VerifiableDID;
112
119
  resolveProof: ResolveProof;
120
+ /**
121
+ * Store of seen invocations. When set, an invocation whose signed payload was
122
+ * already seen is rejected. Checked after all other validation passes.
123
+ *
124
+ * Executors MUST use one to prevent replay attacks.
125
+ */
126
+ replayStore?: ReplayStore;
127
+ /**
128
+ * Maximum lifetime in seconds accepted for the invocation. When set,
129
+ * invocations without an expiration, or expiring more than `maxTtl` seconds
130
+ * after `now`, are rejected. This keeps the replay store bounded.
131
+ */
132
+ maxTtl?: number;
133
+ }
134
+ /**
135
+ * Store of seen invocations, used to reject replays.
136
+ */
137
+ export interface ReplayStore {
138
+ /**
139
+ * Atomically record `key` and resolve `true`, or resolve `false` if `key`
140
+ * was already recorded and has not expired.
141
+ *
142
+ * @param key - Replay key of the invocation, see {@link Invocation.replayKey}
143
+ * @param expiration - Unix time in seconds after which the key can be forgotten, or `null` to keep it forever
144
+ */
145
+ checkAndSet(key: string, expiration: number | null): Promise<boolean>;
113
146
  }
114
147
  /**
115
148
  * Capability types
@@ -215,5 +248,10 @@ export type ClientOptions = {
215
248
  store: Store;
216
249
  capabilities: Capability<StandardSchemaV1, string>[];
217
250
  verifierResolver: VerifierResolver;
251
+ /**
252
+ * Time to live in seconds of the invocations created by the client
253
+ * @default 300
254
+ */
255
+ ttl?: number;
218
256
  };
219
257
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,17 @@ export function nowInSeconds(): number;
17
17
  * @param {import('./types.js').DecodedEnvelope<PayloadSpec>} envelope
18
18
  */
19
19
  export function cid(envelope: import("./types.js").DecodedEnvelope<PayloadSpec>): Promise<CID<any, 113, 18, 1>>;
20
+ /**
21
+ * Replay key of a UCAN: the CID of its signature payload, the exact bytes the
22
+ * signature covers.
23
+ *
24
+ * Unlike {@link cid}, it does not depend on the signature bytes, so a second
25
+ * valid signature over the same payload (for example a malleated ECDSA
26
+ * signature) gets the same key.
27
+ *
28
+ * @param {import('./types.js').DecodedEnvelope<PayloadSpec>} envelope
29
+ */
30
+ export function replayKey(envelope: import("./types.js").DecodedEnvelope<PayloadSpec>): Promise<string>;
20
31
  /**
21
32
  * Check if a DID and signature type are compatible
22
33
  *
@@ -53,6 +64,21 @@ export function validateIssuerAndSignature(envelope: import("./types.js").Decode
53
64
  * @param {import('iso-did').Resolver} [didResolver]
54
65
  */
55
66
  export function verifySignature(envelope: import("./types.js").DecodedEnvelope<PayloadSpec>, signatureVerifierResolver: import("iso-signatures/verifiers/resolver.js").Resolver, didResolver?: import("iso-did").Resolver): Promise<boolean>;
67
+ /**
68
+ * Check if a command is covered by (equal to or a segment-wise child of) another command.
69
+ *
70
+ * Commands are compared by path segment, not by string prefix: `/crypto` covers
71
+ * `/crypto` and `/crypto/sign` but not `/cryptocurrency`. The command `/` covers
72
+ * every command.
73
+ *
74
+ * Both commands are expected to be valid per {@link assertIsValidCommand}, so
75
+ * `${parent}/` is always a real segment boundary.
76
+ *
77
+ * @param {string} parent - The broader command, e.g. from a delegation.
78
+ * @param {string} child - The command that must be attenuated from `parent`.
79
+ * @returns {boolean}
80
+ */
81
+ export function commandCovers(parent: string, child: string): boolean;
56
82
  /**
57
83
  * Asserts that a UCAN command string is syntactically valid.
58
84
  * If the command is invalid, it throws a descriptive error.
@@ -97,13 +123,41 @@ export function assertArgs(args: unknown): void;
97
123
  * @returns {void}
98
124
  */
99
125
  export function assertMeta(meta: unknown): void;
126
+ /**
127
+ * Signatures a signer could have issued that verify the same as `signature`.
128
+ *
129
+ * ECDSA signatures are malleable: `(r, s)` and `(r, n - s)` are both valid.
130
+ * EIP-191 signatures additionally carry a recovery byte `v` that wallets
131
+ * return as 27/28 or 0/1. Verifiers also accept EIP-155 style `v >= 35`,
132
+ * but no signer issues those for `personal_sign`, so they are not listed.
133
+ *
134
+ * The result always includes `signature` itself. Other algorithms (Ed25519,
135
+ * RSA) have a single valid signature.
136
+ *
137
+ * @param {import('iso-signatures/types').SignatureType} alg
138
+ * @param {Uint8Array} signature
139
+ * @returns {Uint8Array[]}
140
+ */
141
+ export function equivalentSignatures(alg: import("iso-signatures/types").SignatureType, signature: Uint8Array): Uint8Array[];
142
+ /**
143
+ * CIDs of every envelope that verifies the same as `envelope`: one per
144
+ * signature in {@link equivalentSignatures}, starting with the CID of
145
+ * `envelope` itself.
146
+ *
147
+ * @param {import('./types.js').DecodedEnvelope<PayloadSpec>} envelope
148
+ */
149
+ export function equivalentCids(envelope: import("./types.js").DecodedEnvelope<PayloadSpec>): Promise<CID<any, 113, 18, 1>[]>;
100
150
  /**
101
151
  * Assert that the input is not revoked.
102
152
  *
103
- * @param {import('multiformats/cid').CID} cid
153
+ * Checks every equivalent CID of the envelope (see {@link equivalentCids}),
154
+ * so re-encoding a revoked UCAN with another valid signature does not get it
155
+ * past a CID-based revocation list.
156
+ *
157
+ * @param {import('./types.js').DecodedEnvelope<PayloadSpec>} envelope
104
158
  * @param {(cid: CID) => Promise<boolean>} [isRevokedFn]
105
159
  */
106
- export function assertNotRevoked(cid: import("multiformats/cid").CID, isRevokedFn?: (cid: CID) => Promise<boolean>): Promise<void>;
160
+ export function assertNotRevoked(envelope: import("./types.js").DecodedEnvelope<PayloadSpec>, isRevokedFn?: (cid: CID) => Promise<boolean>): Promise<void>;
107
161
  /**
108
162
  * Expiration or TTL (default 300 seconds)
109
163
  *
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.js"],"names":[],"mappings":"AASA;;GAEG;AAEH;;;;;GAKG;AACH,2CAHW,OAAO,YAAY,EAAE,eAAe,CAAC,WAAW,CAAC;;;EAe3D;AAED,uCAEC;AAED;;;GAGG;AACH,8BAFW,OAAO,YAAY,EAAE,eAAe,CAAC,WAAW,CAAC,iCAW3D;AAED;;;;;GAKG;AACH,2CAHW,OAAO,eAAe,EAAE,aAAa,WACrC,OAAO,sBAAsB,EAAE,aAAa,WA+BtD;AAED;;;;;GAKG;AACH,sCAHW,MAAM,GAAG,IAAI,QACb,MAAM,QAchB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,QACN,MAAM,QAMhB;AAED;;;;;GAKG;AACH,qDAHW,OAAO,YAAY,EAAE,eAAe,CAAC,WAAW,CAAC,gBACjD,OAAO,SAAS,EAAE,QAAQ,gBAYpC;AAED;;;;;;GAMG;AACH,0CAJW,OAAO,YAAY,EAAE,eAAe,CAAC,WAAW,CAAC,6BACjD,OAAO,sCAAsC,EAAE,QAAQ,gBACvD,OAAO,SAAS,EAAE,QAAQ,oBAwBpC;AAED;;;;;;;;;;;;;GAaG;AACH,8CAJW,MAAM,GAEJ,IAAI,CAuChB;AAED;;;;;;;;;;;;;GAaG;AACH,mCAXW,OAAO,GAEL,IAAI,CAehB;AACD;;;;GAIG;AACH,iCAFW,OAAO,QASjB;AAED;;;;;;GAMG;AACH,iCAJW,OAAO,GAEL,IAAI,CAWhB;AAED;;;;;GAKG;AACH,sCAHW,OAAO,kBAAkB,EAAE,GAAG,gBAC9B,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,OAAO,CAAC,iBAQxC;AAED;;;;;;GAMG;AACH,uCAHG;IAAgC,GAAG;IACV,GAAG;CAAC,iBAU/B;AAgED;;GAEG;AACH,qCAFW,OAAO,QASjB;AAED;;;;;;GAMG;AACH,gCAJW,OAAO,GACL,KAAK,IAAI,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAKjD;AAnFD,iJAeI;AACJ,4KAAyD;AAEzD,oGAGE;AAEF;;GAEG;AAEH;;GAEG;AACH,mKA4BI;AAEJ,kLAA8C;uBApCjC,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC;iCAjUP,YAAY;oBARtB,kBAAkB;oBADlB,SAAS;kBAGX,QAAQ"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.js"],"names":[],"mappings":"AAUA;;GAEG;AAEH;;;;;GAKG;AACH,2CAHW,OAAO,YAAY,EAAE,eAAe,CAAC,WAAW,CAAC;;;EAe3D;AAED,uCAEC;AAED;;;GAGG;AACH,8BAFW,OAAO,YAAY,EAAE,eAAe,CAAC,WAAW,CAAC,iCAW3D;AAED;;;;;;;;;GASG;AACH,oCAFW,OAAO,YAAY,EAAE,eAAe,CAAC,WAAW,CAAC,mBAM3D;AAED;;;;;GAKG;AACH,2CAHW,OAAO,eAAe,EAAE,aAAa,WACrC,OAAO,sBAAsB,EAAE,aAAa,WA+BtD;AAED;;;;;GAKG;AACH,sCAHW,MAAM,GAAG,IAAI,QACb,MAAM,QAchB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,QACN,MAAM,QAMhB;AAED;;;;;GAKG;AACH,qDAHW,OAAO,YAAY,EAAE,eAAe,CAAC,WAAW,CAAC,gBACjD,OAAO,SAAS,EAAE,QAAQ,gBAYpC;AAED;;;;;;GAMG;AACH,0CAJW,OAAO,YAAY,EAAE,eAAe,CAAC,WAAW,CAAC,6BACjD,OAAO,sCAAsC,EAAE,QAAQ,gBACvD,OAAO,SAAS,EAAE,QAAQ,oBAwBpC;AAED;;;;;;;;;;;;;GAaG;AACH,sCAJW,MAAM,SACN,MAAM,GACJ,OAAO,CAKnB;AAED;;;;;;;;;;;;;GAaG;AACH,8CAJW,MAAM,GAEJ,IAAI,CAuChB;AAED;;;;;;;;;;;;;GAaG;AACH,mCAXW,OAAO,GAEL,IAAI,CAehB;AACD;;;;GAIG;AACH,iCAFW,OAAO,QASjB;AAED;;;;;;GAMG;AACH,iCAJW,OAAO,GAEL,IAAI,CAWhB;AAqDD;;;;;;;;;;;;;;GAcG;AACH,0CAJW,OAAO,sBAAsB,EAAE,aAAa,aAC5C,UAAU,GACR,UAAU,EAAE,CAyCxB;AAED;;;;;;GAMG;AACH,yCAFW,OAAO,YAAY,EAAE,eAAe,CAAC,WAAW,CAAC,mCAQ3D;AAED;;;;;;;;;GASG;AACH,2CAHW,OAAO,YAAY,EAAE,eAAe,CAAC,WAAW,CAAC,gBACjD,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,OAAO,CAAC,iBAWxC;AAED;;;;;;GAMG;AACH,uCAHG;IAAgC,GAAG;IACV,GAAG;CAAC,iBAU/B;AAgED;;GAEG;AACH,qCAFW,OAAO,QASjB;AAED;;;;;;GAMG;AACH,gCAJW,OAAO,GACL,KAAK,IAAI,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAKjD;AAnFD,iJAeI;AACJ,4KAAyD;AAEzD,oGAGE;AAEF;;GAEG;AAEH;;GAEG;AACH,mKA4BI;AAEJ,kLAA8C;uBApCjC,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC;iCAreP,YAAY;oBARtB,kBAAkB;oBADlB,SAAS;kBAGX,QAAQ"}
package/license ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) Hugo Dias <hugomrdias@gmail.com> (hugodias.me)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "iso-ucan",
3
3
  "type": "module",
4
- "version": "0.5.0",
4
+ "version": "0.5.2",
5
5
  "description": "Isomorphic UCAN",
6
6
  "author": "Hugo Dias <hugomrdias@gmail.com> (hugodias.me)",
7
7
  "license": "MIT",
@@ -44,6 +44,10 @@
44
44
  "types": "./dist/src/varsig.d.ts",
45
45
  "default": "./src/varsig.js"
46
46
  },
47
+ "./replay": {
48
+ "types": "./dist/src/replay.d.ts",
49
+ "default": "./src/replay.js"
50
+ },
47
51
  "./policy": {
48
52
  "types": "./dist/src/policy.d.ts",
49
53
  "default": "./src/policy.js"
@@ -81,6 +85,9 @@
81
85
  "policy": [
82
86
  "dist/src/policy"
83
87
  ],
88
+ "replay": [
89
+ "dist/src/replay"
90
+ ],
84
91
  "rpc": [
85
92
  "dist/src/rpc/index"
86
93
  ],
@@ -95,24 +102,25 @@
95
102
  "dist/src/**/*.d.ts.map"
96
103
  ],
97
104
  "dependencies": {
98
- "@ipld/dag-cbor": "^9.2.5",
105
+ "@ipld/dag-cbor": "^10.0.2",
99
106
  "@standard-schema/spec": "^1.1.0",
100
107
  "@standard-schema/utils": "^0.3.0",
101
108
  "iobuffer": "^6.0.1",
102
- "iso-kv": "^3.1.1",
103
- "iso-web": "^2.1.0",
104
- "it-merge": "^3.0.12",
105
- "multiformats": "^13.4.1",
106
- "zod": "^4.3.6",
107
- "iso-base": "^4.3.1",
108
- "iso-did": "^2.1.1",
109
- "iso-signatures": "^0.5.1"
109
+ "iso-base": "^4.4.0",
110
+ "iso-did": "^3.0.0",
111
+ "iso-kv": "^3.2.0",
112
+ "iso-signatures": "^0.5.2",
113
+ "iso-web": "^4.0.0",
114
+ "it-merge": "^3.0.15",
115
+ "multiformats": "^14.0.5",
116
+ "zod": "^4.6.5"
110
117
  },
111
118
  "devDependencies": {
112
- "@types/node": "^25.6.0",
119
+ "@types/node": "^26.6.2",
113
120
  "delay": "^7.0.0",
114
- "playwright-test": "^14.1.13",
115
- "typescript": "5.9.3"
121
+ "playwright-test": "^15.0.1",
122
+ "typescript": "^6.0.3",
123
+ "viem": "~2.56.9"
116
124
  },
117
125
  "publishConfig": {
118
126
  "provenance": true
package/readme.md CHANGED
@@ -1,6 +1,23 @@
1
1
  # iso-ucan [![NPM Version](https://img.shields.io/npm/v/iso-ucan.svg)](https://www.npmjs.com/package/iso-ucan) [![License](https://img.shields.io/npm/l/iso-ucan.svg)](https://github.com/hugomrdias/iso-repo/blob/main/license) [![iso-ucan](https://github.com/hugomrdias/iso-repo/actions/workflows/iso-ucan.yml/badge.svg)](https://github.com/hugomrdias/iso-repo/actions/workflows/iso-ucan.yml)
2
2
 
3
- > Isomorphic UCAN
3
+ > Isomorphic UCAN primitives for delegations, invocations, proof storage, and
4
+ > typed RPC.
5
+
6
+ ## Overview
7
+
8
+ UCAN is a local-first authorization model where permissions are
9
+ cryptographically signed, delegated, and verified without relying on a central
10
+ authority. See [ucan.xyz](https://ucan.xyz/) for more background.
11
+
12
+ `iso-ucan` provides:
13
+
14
+ - Typed capabilities backed by schema validation.
15
+ - Delegation and invocation builders for UCAN authorization flows.
16
+ - Proof storage with pluggable `iso-kv` drivers.
17
+ - Signer and verifier support through `iso-signatures`.
18
+ - EIP-191 wallet signing support through `iso-signatures` `EIP191Signer`.
19
+ - Filsnap-powered UCAN signature insights in MetaMask signature popups.
20
+ - A typed RPC layer for request/response APIs built on UCAN invocations.
4
21
 
5
22
  ## Install
6
23
 
@@ -43,39 +60,100 @@ const invoker = await EdDSASigner.generate()
43
60
  const nowInSeconds = Math.floor(Date.now() / 1000)
44
61
 
45
62
  const ownerDelegation = await AccountCap.delegate({
46
- iss: owner,
47
- aud: bob,
48
- sub: owner,
49
- pol: [],
50
- exp: nowInSeconds + 1000,
63
+ iss: owner,
64
+ aud: bob,
65
+ sub: owner,
66
+ pol: [],
67
+ exp: nowInSeconds + 1000,
51
68
  })
52
69
 
53
70
  await store.set(ownerDelegation)
54
71
 
55
72
  const bobDelegation = await AccountCap.delegate({
56
- iss: bob,
57
- aud: invoker,
58
- sub: owner,
59
- pol: [],
60
- exp: nowInSeconds + 1000,
73
+ iss: bob,
74
+ aud: invoker,
75
+ sub: owner,
76
+ pol: [],
77
+ exp: nowInSeconds + 1000,
61
78
  })
62
79
 
63
80
  await store.set(bobDelegation)
64
81
 
65
82
  const invocation = await AccountCreateCap.invoke({
66
- iss: invoker,
67
- sub: owner,
68
- args: {
83
+ iss: invoker,
84
+ sub: owner,
85
+ args: {
69
86
  type: 'account',
70
87
  properties: {
71
- name: 'John Doe',
88
+ name: 'John Doe',
72
89
  },
73
- },
74
- store,
75
- exp: nowInSeconds + 1000,
90
+ },
91
+ store,
92
+ exp: nowInSeconds + 1000,
76
93
  })
77
94
  ```
78
95
 
96
+ ## Replay protection
97
+
98
+ The UCAN spec requires executors to reject replayed invocations. Pass a
99
+ `replayStore` to `Invocation.from` so an invocation is accepted only once, and
100
+ set `maxTtl` so invocations must expire soon, which keeps the store bounded:
101
+
102
+ ```ts
103
+ import { Invocation } from 'iso-ucan/invocation'
104
+ import { KVReplayStore } from 'iso-ucan/replay'
105
+
106
+ const replayStore = new KVReplayStore() // in memory by default
107
+
108
+ const invocation = await Invocation.from({
109
+ bytes,
110
+ audience: executor,
111
+ verifierResolver,
112
+ resolveProof: (cid) => store.resolveProof(cid),
113
+ replayStore,
114
+ maxTtl: 300,
115
+ })
116
+ ```
117
+
118
+ The store is keyed on `invocation.replayKey`, the CID of the signed payload.
119
+ Do not deduplicate on `invocation.cid`: it also covers the signature, and
120
+ some signature algorithms (such as ECDSA) accept more than one valid signature
121
+ for the same payload.
122
+
123
+ `KVReplayStore` is atomic within one process only. When several processes or
124
+ instances execute invocations, implement the `ReplayStore` interface on a
125
+ shared store with an atomic set-if-absent, such as Redis `SET NX` or SQL
126
+ `INSERT ... ON CONFLICT DO NOTHING`.
127
+
128
+ ## Revocation
129
+
130
+ Pass `isRevoked` to `Delegation.from`, `Delegation#validate` or
131
+ `Invocation.from` to reject revoked delegations by CID. Because ECDSA and
132
+ EIP-191 signatures can be re-encoded into another valid signature, a revoked
133
+ delegation can reappear under a different CID. `iso-ucan` therefore calls
134
+ `isRevoked` with every CID the delegation could have been issued under (up to
135
+ 2 for ECDSA and 4 for EIP-191), and rejects it if any is revoked.
136
+
137
+ ## EIP-191 wallet signing
138
+
139
+ `iso-ucan` can use EIP-191 wallet signatures through `EIP191Signer` from
140
+ `iso-signatures`. That lets UCAN delegations and invocations be issued by an
141
+ Ethereum account exposed through an EIP-1193 provider, such as an injected
142
+ MetaMask wallet, while verification is handled by the matching EIP-191 verifier
143
+ from `iso-signatures`.
144
+
145
+ [`examples/eip191`](../../examples/eip191) shows this flow in a browser web
146
+ app. It uses `wagmi` to connect the wallet, wraps the provider in
147
+ `EIP191Signer`, then creates and verifies UCAN delegations and invocations
148
+ directly in the browser.
149
+
150
+ The example uses `filsnap-adapter` to install or enable
151
+ [Filsnap](https://github.com/filecoin-project/filsnap), the Filecoin Wallet
152
+ MetaMask Snap. Filsnap supports UCAN signature insights for EIP-191 signers, so
153
+ when the app asks MetaMask to sign a UCAN delegation, the MetaMask signature
154
+ popup can show a readable summary of the issuer, audience, subject, command,
155
+ expiration, and raw payload.
156
+
79
157
  ## RPC
80
158
 
81
159
  `iso-ucan/rpc` is a small request/response layer on top of UCAN
@@ -177,6 +255,14 @@ app.post('/rpc', (c) => rpc(c.req.raw))
177
255
  serve({ fetch: app.fetch, port: 3000 })
178
256
  ```
179
257
 
258
+ `defineServer` rejects replayed invocations with an in-memory `KVReplayStore`
259
+ by default. Pass `replayStore` to share it between server instances.
260
+
261
+ It also rejects invocations that never expire or expire more than `maxTtl`
262
+ seconds from now (default 600). Pass a different `maxTtl` to change the
263
+ limit, or `null` to accept any expiration. See
264
+ [Replay protection](#replay-protection).
265
+
180
266
  ### Client
181
267
 
182
268
  ```ts
@@ -189,7 +275,11 @@ const client = defineClient(Protocol, {
189
275
  store,
190
276
  verifierResolver,
191
277
  })
278
+ ```
192
279
 
280
+ Invocations created by the client expire after `ttl` seconds (default 300).
281
+
282
+ ```ts
193
283
  // `args` and the returned receipt are typed from the protocol entry for `cmd`.
194
284
  const r = await client.request({ cmd: '/todo/complete', args: { id: '42' } })
195
285
 
package/src/delegation.js CHANGED
@@ -89,7 +89,7 @@ export class Delegation {
89
89
  await verifySignature(envelope, verifierResolver, didResolveOptions)
90
90
 
91
91
  const _cid = await cid(envelope)
92
- await assertNotRevoked(_cid, options.isRevoked)
92
+ await assertNotRevoked(envelope, options.isRevoked)
93
93
 
94
94
  return new Delegation(envelope, bytes, _cid)
95
95
  }
@@ -182,7 +182,7 @@ export class Delegation {
182
182
  options.verifierResolver,
183
183
  options.didResolver
184
184
  )
185
- await assertNotRevoked(this.cid, options.isRevoked)
185
+ await assertNotRevoked(this.envelope, options.isRevoked)
186
186
 
187
187
  return true
188
188
  }
package/src/invocation.js CHANGED
@@ -9,7 +9,10 @@ import {
9
9
  assertMeta,
10
10
  assertNonce,
11
11
  cid,
12
+ commandCovers,
12
13
  expOrTtl,
14
+ nowInSeconds,
15
+ replayKey,
13
16
  verifySignature,
14
17
  } from './utils.js'
15
18
 
@@ -32,19 +35,32 @@ export class Invocation {
32
35
  */
33
36
  cid
34
37
 
38
+ /**
39
+ * Key to deduplicate this invocation on: the CID of its signature payload.
40
+ *
41
+ * Use this instead of {@link Invocation.cid} to detect replays. The CID
42
+ * covers the signature bytes, and some signature algorithms (e.g. ECDSA)
43
+ * accept more than one valid signature for the same payload.
44
+ *
45
+ * @type {string}
46
+ */
47
+ replayKey
48
+
35
49
  /**
36
50
  *
37
51
  * @param {import("./types.js").DecodedEnvelope<'inv'>} envelope
38
52
  * @param {Uint8Array} bytes
39
53
  * @param {CID} cid
40
54
  * @param {Delegation[]} delegations
55
+ * @param {string} replayKey
41
56
  */
42
- constructor(envelope, bytes, cid, delegations) {
57
+ constructor(envelope, bytes, cid, delegations, replayKey) {
43
58
  this.envelope = envelope
44
59
  this.payload = envelope.payload
45
60
  this.bytes = bytes
46
61
  this.cid = cid
47
62
  this.delegations = delegations
63
+ this.replayKey = replayKey
48
64
  }
49
65
 
50
66
  /**
@@ -95,8 +111,24 @@ export class Invocation {
95
111
  assertProofs(envelope.payload, proofs)
96
112
  }
97
113
 
114
+ if (options.maxTtl !== undefined) {
115
+ assertMaxTtl(envelope.payload.exp, options.maxTtl, options.now)
116
+ }
117
+
118
+ const _replayKey = await replayKey(envelope)
119
+
120
+ // Must run last, so only fully validated invocations are recorded
121
+ if (
122
+ options.replayStore &&
123
+ !(await options.replayStore.checkAndSet(_replayKey, envelope.payload.exp))
124
+ ) {
125
+ throw new Error(
126
+ `UCAN Invocation replay detected, already seen ${_replayKey}`
127
+ )
128
+ }
129
+
98
130
  const _cid = await cid(envelope)
99
- return new Invocation(envelope, bytes, _cid, proofs)
131
+ return new Invocation(envelope, bytes, _cid, proofs, _replayKey)
100
132
  }
101
133
 
102
134
  /**
@@ -157,7 +189,31 @@ export class Invocation {
157
189
  version: Envelope.VERSION,
158
190
  }
159
191
 
160
- return new Invocation(envelope, bytes, await cid(envelope), options.prf)
192
+ return new Invocation(
193
+ envelope,
194
+ bytes,
195
+ await cid(envelope),
196
+ options.prf,
197
+ await replayKey(envelope)
198
+ )
199
+ }
200
+ }
201
+
202
+ /**
203
+ * @param {number | null} exp
204
+ * @param {number} maxTtl
205
+ * @param {number} [now]
206
+ */
207
+ function assertMaxTtl(exp, maxTtl, now = nowInSeconds()) {
208
+ if (exp === null) {
209
+ throw new Error(
210
+ `UCAN Invocation must expire within ${maxTtl} seconds, but has no expiration`
211
+ )
212
+ }
213
+ if (exp > now + maxTtl) {
214
+ throw new Error(
215
+ `UCAN Invocation must expire within ${maxTtl} seconds. Received: ${exp} but current time is ${now}`
216
+ )
161
217
  }
162
218
  }
163
219
 
@@ -235,9 +291,9 @@ export function assertProofs(payload, proofs) {
235
291
  )
236
292
  }
237
293
 
238
- if (!next.cmd.startsWith(current.cmd)) {
294
+ if (!commandCovers(current.cmd, next.cmd)) {
239
295
  throw new Error(
240
- `UCAN Invocation command mismatch, expected ${current.cmd} to be a broader than ${next.cmd}`
296
+ `UCAN Invocation command mismatch, expected ${current.cmd} to cover ${next.cmd}`
241
297
  )
242
298
  }
243
299
  }
package/src/replay.js ADDED
@@ -0,0 +1,58 @@
1
+ import { KV } from 'iso-kv'
2
+ import { MemoryDriver } from 'iso-kv/drivers/memory.js'
3
+
4
+ /**
5
+ * @import {Driver} from 'iso-kv'
6
+ * @import {ReplayStore} from './types.js'
7
+ */
8
+
9
+ /**
10
+ * Replay store backed by an `iso-kv` driver.
11
+ *
12
+ * Keys are kept until the invocation expires, or forever when the invocation
13
+ * has no expiration.
14
+ *
15
+ * `checkAndSet` is atomic within one JavaScript realm only: calls are
16
+ * serialized through an in-process queue. When several processes or instances
17
+ * share a driver, implement {@link ReplayStore} on top of an atomic
18
+ * set-if-absent operation instead (for example Redis `SET NX` or SQL
19
+ * `INSERT ... ON CONFLICT DO NOTHING`).
20
+ *
21
+ * @implements {ReplayStore}
22
+ */
23
+ export class KVReplayStore {
24
+ /** @type {KV} */
25
+ kv
26
+
27
+ /** @type {Promise<unknown>} */
28
+ #queue = Promise.resolve()
29
+
30
+ /**
31
+ * @param {Driver} [driver] - Defaults to a {@link MemoryDriver}.
32
+ */
33
+ constructor(driver = new MemoryDriver()) {
34
+ this.kv = new KV({ driver })
35
+ }
36
+
37
+ /**
38
+ * Record `key` and return `true`, or return `false` if it was already
39
+ * recorded and has not expired.
40
+ *
41
+ * @param {string} key
42
+ * @param {number | null} expiration - Unix time in seconds, or `null` to keep forever
43
+ * @returns {Promise<boolean>}
44
+ */
45
+ checkAndSet(key, expiration) {
46
+ const result = this.#queue.then(async () => {
47
+ if (await this.kv.has([key])) {
48
+ return false
49
+ }
50
+ await this.kv.set([key], true, { expiration })
51
+ return true
52
+ })
53
+ this.#queue = result.catch(() => {
54
+ // Errors are reported to the caller, keep the queue going
55
+ })
56
+ return result
57
+ }
58
+ }
package/src/rpc/client.js CHANGED
@@ -39,7 +39,7 @@ export function defineClient(commands, options) {
39
39
  iss: options.issuer,
40
40
  sub: options.audience.did,
41
41
  store: options.store,
42
- exp: null,
42
+ ttl: options.ttl,
43
43
  verifierResolver: options.verifierResolver,
44
44
  })
45
45
 
@@ -77,7 +77,7 @@ export function defineClient(commands, options) {
77
77
  iss: options.issuer,
78
78
  sub: options.audience.did,
79
79
  store: options.store,
80
- exp: null,
80
+ ttl: options.ttl,
81
81
  verifierResolver: options.verifierResolver,
82
82
  })
83
83
  },
package/src/rpc/index.js CHANGED
@@ -7,4 +7,4 @@ export {
7
7
  receiptResult,
8
8
  receiptServerError,
9
9
  } from './receipt.js'
10
- export { defineServer } from './server.js'
10
+ export { DEFAULT_MAX_TTL, defineServer } from './server.js'
package/src/rpc/server.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { u8 } from 'iso-base/utils'
2
2
  import { Invocation } from '../invocation.js'
3
+ import { KVReplayStore } from '../replay.js'
3
4
 
4
5
  /**
5
6
  * @import {
@@ -10,6 +11,12 @@ import { Invocation } from '../invocation.js'
10
11
  * @import {ReceiptServerError} from './receipt.js'
11
12
  */
12
13
 
14
+ /**
15
+ * Default maximum invocation lifetime accepted by {@link defineServer}, in
16
+ * seconds. Twice the client default `ttl`, to tolerate clock skew.
17
+ */
18
+ export const DEFAULT_MAX_TTL = 600
19
+
13
20
  /**
14
21
  * Build a {@link ReceiptServerError} payload.
15
22
  *
@@ -56,6 +63,13 @@ function serverErrorResponse(status, message, extra) {
56
63
  * schema, and dispatches to the corresponding handler in `options.handlers`.
57
64
  * The handler's return value is serialized to JSON.
58
65
  *
66
+ * Each invocation is executed at most once: replays are rejected using
67
+ * `options.replayStore`, which defaults to an in-memory {@link KVReplayStore}.
68
+ * Pass a shared store when running more than one server instance.
69
+ *
70
+ * Invocations must expire within `options.maxTtl` seconds, which defaults to
71
+ * {@link DEFAULT_MAX_TTL}. Pass `null` to accept any expiration.
72
+ *
59
73
  * @template {CommandsRecord} Commands
60
74
  * @param {Commands} commands
61
75
  * @param {DefineServerOptions<Commands>} options
@@ -70,6 +84,11 @@ export function defineServer(commands, options) {
70
84
 
71
85
  /** @type {Record<string, (opts: any) => unknown>} */
72
86
  const handlers = options.handlers
87
+ const replayStore = options.replayStore ?? new KVReplayStore()
88
+ const maxTtl =
89
+ options.maxTtl === undefined
90
+ ? DEFAULT_MAX_TTL
91
+ : (options.maxTtl ?? undefined)
73
92
 
74
93
  return async (request) => {
75
94
  /** @type {Invocation | undefined} */
@@ -83,6 +102,8 @@ export function defineServer(commands, options) {
83
102
  audience: options.signer,
84
103
  verifierResolver: options.verifierResolver,
85
104
  resolveProof: options.store.resolveProof.bind(options.store),
105
+ replayStore,
106
+ maxTtl,
86
107
  })
87
108
  } catch (error) {
88
109
  return serverErrorResponse(
package/src/rpc/types.ts CHANGED
@@ -3,7 +3,12 @@ import type { ISigner } from 'iso-signatures/types'
3
3
  import type { Capability } from '../capability.js'
4
4
  import type { Invocation } from '../invocation.js'
5
5
  import type { Store } from '../store.js'
6
- import type { ClientOptions, Promisable, VerifierResolver } from '../types.js'
6
+ import type {
7
+ ClientOptions,
8
+ Promisable,
9
+ ReplayStore,
10
+ VerifierResolver,
11
+ } from '../types.js'
7
12
 
8
13
  export interface CommandOptions<
9
14
  Args extends StandardSchemaV1,
@@ -145,6 +150,22 @@ export interface DefineServerOptions<Commands extends CommandsRecord> {
145
150
  signer: ISigner
146
151
  store: Store
147
152
  verifierResolver: VerifierResolver
153
+ /**
154
+ * Store of seen invocations, used to reject replays.
155
+ * Use a store shared by all instances when running more than one server.
156
+ *
157
+ * @default new KVReplayStore() (in memory)
158
+ */
159
+ replayStore?: ReplayStore
160
+ /**
161
+ * Maximum lifetime in seconds accepted for invocations. Invocations without
162
+ * an expiration, or expiring later than `maxTtl` seconds from now, are
163
+ * rejected. This keeps the replay store bounded. Pass `null` to accept any
164
+ * expiration, including none.
165
+ *
166
+ * @default 600
167
+ */
168
+ maxTtl?: number | null
148
169
  }
149
170
 
150
171
  /**
package/src/types.ts CHANGED
@@ -19,6 +19,13 @@ 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
+ */
22
29
  export type IsRevoked = (cid: CID) => Promise<boolean>
23
30
  export type VerifierResolver = Resolver
24
31
  export type DidResolver = _DidResolver
@@ -126,6 +133,33 @@ export interface InvocationFromOptions extends DelegationValidateOptions {
126
133
  bytes: Uint8Array
127
134
  audience?: VerifiableDID
128
135
  resolveProof: ResolveProof
136
+ /**
137
+ * Store of seen invocations. When set, an invocation whose signed payload was
138
+ * already seen is rejected. Checked after all other validation passes.
139
+ *
140
+ * Executors MUST use one to prevent replay attacks.
141
+ */
142
+ replayStore?: ReplayStore
143
+ /**
144
+ * Maximum lifetime in seconds accepted for the invocation. When set,
145
+ * invocations without an expiration, or expiring more than `maxTtl` seconds
146
+ * after `now`, are rejected. This keeps the replay store bounded.
147
+ */
148
+ maxTtl?: number
149
+ }
150
+
151
+ /**
152
+ * Store of seen invocations, used to reject replays.
153
+ */
154
+ export interface ReplayStore {
155
+ /**
156
+ * Atomically record `key` and resolve `true`, or resolve `false` if `key`
157
+ * was already recorded and has not expired.
158
+ *
159
+ * @param key - Replay key of the invocation, see {@link Invocation.replayKey}
160
+ * @param expiration - Unix time in seconds after which the key can be forgotten, or `null` to keep it forever
161
+ */
162
+ checkAndSet(key: string, expiration: number | null): Promise<boolean>
129
163
  }
130
164
 
131
165
  /**
@@ -276,4 +310,9 @@ export type ClientOptions = {
276
310
  store: Store
277
311
  capabilities: Capability<StandardSchemaV1, string>[]
278
312
  verifierResolver: VerifierResolver
313
+ /**
314
+ * Time to live in seconds of the invocations created by the client
315
+ * @default 300
316
+ */
317
+ ttl?: number
279
318
  }
package/src/utils.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as dagCbor from '@ipld/dag-cbor'
2
+ import { equals } from 'iso-base/utils'
2
3
  import { DID } from 'iso-did'
3
4
  import { CID } from 'multiformats/cid'
4
5
  import { sha256 } from 'multiformats/hashes/sha2'
@@ -50,6 +51,22 @@ export async function cid(envelope) {
50
51
  return CID.create(1, dagCbor.code, hash)
51
52
  }
52
53
 
54
+ /**
55
+ * Replay key of a UCAN: the CID of its signature payload, the exact bytes the
56
+ * signature covers.
57
+ *
58
+ * Unlike {@link cid}, it does not depend on the signature bytes, so a second
59
+ * valid signature over the same payload (for example a malleated ECDSA
60
+ * signature) gets the same key.
61
+ *
62
+ * @param {import('./types.js').DecodedEnvelope<PayloadSpec>} envelope
63
+ */
64
+ export async function replayKey(envelope) {
65
+ const hash = await sha256.digest(dagCbor.encode(signaturePayload(envelope)))
66
+
67
+ return CID.create(1, dagCbor.code, hash).toString()
68
+ }
69
+
53
70
  /**
54
71
  * Check if a DID and signature type are compatible
55
72
  *
@@ -168,6 +185,25 @@ export async function verifySignature(
168
185
  return true
169
186
  }
170
187
 
188
+ /**
189
+ * Check if a command is covered by (equal to or a segment-wise child of) another command.
190
+ *
191
+ * Commands are compared by path segment, not by string prefix: `/crypto` covers
192
+ * `/crypto` and `/crypto/sign` but not `/cryptocurrency`. The command `/` covers
193
+ * every command.
194
+ *
195
+ * Both commands are expected to be valid per {@link assertIsValidCommand}, so
196
+ * `${parent}/` is always a real segment boundary.
197
+ *
198
+ * @param {string} parent - The broader command, e.g. from a delegation.
199
+ * @param {string} child - The command that must be attenuated from `parent`.
200
+ * @returns {boolean}
201
+ */
202
+ export function commandCovers(parent, child) {
203
+ if (parent === '/') return child.startsWith('/')
204
+ return child === parent || child.startsWith(`${parent}/`)
205
+ }
206
+
171
207
  /**
172
208
  * Asserts that a UCAN command string is syntactically valid.
173
209
  * If the command is invalid, it throws a descriptive error.
@@ -274,17 +310,146 @@ export function assertMeta(meta) {
274
310
  }
275
311
  }
276
312
 
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
+
277
435
  /**
278
436
  * Assert that the input is not revoked.
279
437
  *
280
- * @param {import('multiformats/cid').CID} cid
438
+ * Checks every equivalent CID of the envelope (see {@link equivalentCids}),
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
281
443
  * @param {(cid: CID) => Promise<boolean>} [isRevokedFn]
282
444
  */
283
- export async function assertNotRevoked(cid, isRevokedFn) {
284
- isRevokedFn = isRevokedFn ?? (async () => false)
285
- const isRevoked = await isRevokedFn(cid)
286
- if (isRevoked) {
287
- throw new Error('UCAN revoked')
445
+ export async function assertNotRevoked(envelope, isRevokedFn) {
446
+ if (!isRevokedFn) {
447
+ return
448
+ }
449
+ for (const c of await equivalentCids(envelope)) {
450
+ if (await isRevokedFn(c)) {
451
+ throw new Error('UCAN revoked')
452
+ }
288
453
  }
289
454
  }
290
455