iso-ucan 0.5.1 → 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.
@@ -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":"AAsLA;;;;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
@@ -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":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,WAAW,EACX,gBAAgB,EACjB,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,cAAc,CAC7B,IAAI,SAAS,gBAAgB,EAC7B,GAAG,SAAS,MAAM,GAAG,MAAM,EAC3B,aAAa,SAAS,gBAAgB,GAAG,gBAAgB;IAEzD,GAAG,EAAE,GAAG,CAAA;IACR,IAAI,EAAE,IAAI,CAAA;IACV,OAAO,EAAE,aAAa,CAAA;CACvB;AAED,MAAM,WAAW,aAAa,CAC5B,IAAI,SAAS,gBAAgB,EAC7B,GAAG,SAAS,MAAM,GAAG,MAAM,EAC3B,aAAa,SAAS,gBAAgB,GAAG,gBAAgB;IAEzD,UAAU,EAAE,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACjC,OAAO,EAAE,aAAa,CAAA;CACvB;AAED,MAAM,MAAM,UAAU,GAAG,aAAa,CACpC,gBAAgB,EAChB,MAAM,EACN,gBAAgB,CACjB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;AAEvD;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,CAAC,QAAQ,SAAS,cAAc,IAAI;KACzD,CAAC,IAAI,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,SAAS,aAAa,CACtD,MAAM,IAAI,EACV,MAAM,GAAG,EACT,MAAM,aAAa,CACpB,GACG;QACE,GAAG,EAAE,GAAG,CAAA;QACR,IAAI,EAAE,gBAAgB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QACvC,OAAO,EAAE,gBAAgB,CAAC,WAAW,CAAC,aAAa,CAAC,CAAA;KACrD,GACD,KAAK;CACV,CAAC,MAAM,QAAQ,CAAC,CAAA;AAEjB;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,CAAC,QAAQ,SAAS,cAAc,IAAI;KAC/D,CAAC,IAAI,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,SAAS,aAAa,CACtD,MAAM,IAAI,EACV,MAAM,GAAG,EACT,MAAM,aAAa,CACpB,GACG;QACE,GAAG,EAAE,GAAG,CAAA;QACR,IAAI,EAAE,gBAAgB,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;QACxC,OAAO,EAAE,gBAAgB,CAAC,UAAU,CAAC,aAAa,CAAC,CAAA;KACpD,GACD,KAAK;CACV,CAAC,MAAM,QAAQ,CAAC,CAAA;AAEjB;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,aAAa,EAAE,cAAc,CAAC,CAAA;AAErE;;GAEG;AACH,MAAM,MAAM,cAAc,CACxB,QAAQ,SAAS,cAAc,EAC/B,GAAG,SAAS,YAAY,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,IACvE;IACF,GAAG,EAAE,GAAG,CAAA;IACR,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE;QAAE,GAAG,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC,MAAM,CAAC,CAAA;CAC5D,CAAA;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,QAAQ,SAAS,cAAc;IAC5D,OAAO,CAAC,GAAG,SAAS,YAAY,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,EAC/C,OAAO,EAAE,cAAc,CAAC,QAAQ,EAAE,GAAG,CAAC,GACrC,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE;QAAE,GAAG,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;IACpE,MAAM,CAAC,GAAG,SAAS,YAAY,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,EAC9C,OAAO,EAAE,cAAc,CAAC,QAAQ,EAAE,GAAG,CAAC,GACrC,OAAO,CAAC,UAAU,CAAC,CAAA;CACvB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB,CAAC,IAAI;IACxC,IAAI,EAAE,IAAI,CAAA;IACV,UAAU,EAAE,UAAU,CAAA;IACtB,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,KAAK,CAAA;CACb;AAED;;;;;GAKG;AACH,MAAM,MAAM,cAAc,CAAC,QAAQ,SAAS,cAAc,IAAI;KAC3D,GAAG,IAAI,kBAAkB,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,CAC5C,OAAO,EAAE,oBAAoB,CAC3B,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,EAAE;QAAE,GAAG,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC,MAAM,CAAC,CAC5D,KACE,UAAU,CACb,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,EAAE;QAAE,GAAG,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC,SAAS,CAAC,CAC/D;CACF,CAAA;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,QAAQ,SAAS,cAAc;IAClE,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAA;IAClC;;;;OAIG;IACH,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,KAAK,CAAA;IACZ,gBAAgB,EAAE,gBAAgB,CAAA;IAClC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA"}
@@ -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
  *
@@ -112,13 +123,41 @@ export function assertArgs(args: unknown): void;
112
123
  * @returns {void}
113
124
  */
114
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>[]>;
115
150
  /**
116
151
  * Assert that the input is not revoked.
117
152
  *
118
- * @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
119
158
  * @param {(cid: CID) => Promise<boolean>} [isRevokedFn]
120
159
  */
121
- 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>;
122
161
  /**
123
162
  * Expiration or TTL (default 300 seconds)
124
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,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;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;iCApVP,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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "iso-ucan",
3
3
  "type": "module",
4
- "version": "0.5.1",
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
  ],
@@ -112,7 +119,8 @@
112
119
  "@types/node": "^26.6.2",
113
120
  "delay": "^7.0.0",
114
121
  "playwright-test": "^15.0.1",
115
- "typescript": "^6.0.3"
122
+ "typescript": "^6.0.3",
123
+ "viem": "~2.56.9"
116
124
  },
117
125
  "publishConfig": {
118
126
  "provenance": true
package/readme.md CHANGED
@@ -93,6 +93,47 @@ const invocation = await AccountCreateCap.invoke({
93
93
  })
94
94
  ```
95
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
+
96
137
  ## EIP-191 wallet signing
97
138
 
98
139
  `iso-ucan` can use EIP-191 wallet signatures through `EIP191Signer` from
@@ -214,6 +255,14 @@ app.post('/rpc', (c) => rpc(c.req.raw))
214
255
  serve({ fetch: app.fetch, port: 3000 })
215
256
  ```
216
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
+
217
266
  ### Client
218
267
 
219
268
  ```ts
@@ -226,7 +275,11 @@ const client = defineClient(Protocol, {
226
275
  store,
227
276
  verifierResolver,
228
277
  })
278
+ ```
229
279
 
280
+ Invocations created by the client expire after `ttl` seconds (default 300).
281
+
282
+ ```ts
230
283
  // `args` and the returned receipt are typed from the protocol entry for `cmd`.
231
284
  const r = await client.request({ cmd: '/todo/complete', args: { id: '42' } })
232
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
@@ -11,6 +11,8 @@ import {
11
11
  cid,
12
12
  commandCovers,
13
13
  expOrTtl,
14
+ nowInSeconds,
15
+ replayKey,
14
16
  verifySignature,
15
17
  } from './utils.js'
16
18
 
@@ -33,19 +35,32 @@ export class Invocation {
33
35
  */
34
36
  cid
35
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
+
36
49
  /**
37
50
  *
38
51
  * @param {import("./types.js").DecodedEnvelope<'inv'>} envelope
39
52
  * @param {Uint8Array} bytes
40
53
  * @param {CID} cid
41
54
  * @param {Delegation[]} delegations
55
+ * @param {string} replayKey
42
56
  */
43
- constructor(envelope, bytes, cid, delegations) {
57
+ constructor(envelope, bytes, cid, delegations, replayKey) {
44
58
  this.envelope = envelope
45
59
  this.payload = envelope.payload
46
60
  this.bytes = bytes
47
61
  this.cid = cid
48
62
  this.delegations = delegations
63
+ this.replayKey = replayKey
49
64
  }
50
65
 
51
66
  /**
@@ -96,8 +111,24 @@ export class Invocation {
96
111
  assertProofs(envelope.payload, proofs)
97
112
  }
98
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
+
99
130
  const _cid = await cid(envelope)
100
- return new Invocation(envelope, bytes, _cid, proofs)
131
+ return new Invocation(envelope, bytes, _cid, proofs, _replayKey)
101
132
  }
102
133
 
103
134
  /**
@@ -158,7 +189,31 @@ export class Invocation {
158
189
  version: Envelope.VERSION,
159
190
  }
160
191
 
161
- 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
+ )
162
217
  }
163
218
  }
164
219
 
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
  *
@@ -293,17 +310,146 @@ export function assertMeta(meta) {
293
310
  }
294
311
  }
295
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
+
296
435
  /**
297
436
  * Assert that the input is not revoked.
298
437
  *
299
- * @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
300
443
  * @param {(cid: CID) => Promise<boolean>} [isRevokedFn]
301
444
  */
302
- export async function assertNotRevoked(cid, isRevokedFn) {
303
- isRevokedFn = isRevokedFn ?? (async () => false)
304
- const isRevoked = await isRevokedFn(cid)
305
- if (isRevoked) {
306
- 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
+ }
307
453
  }
308
454
  }
309
455