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.
- package/dist/src/invocation.d.ts +12 -1
- package/dist/src/invocation.d.ts.map +1 -1
- package/dist/src/replay.d.ts +40 -0
- package/dist/src/replay.d.ts.map +1 -0
- package/dist/src/rpc/index.d.ts +1 -1
- package/dist/src/rpc/server.d.ts +20 -0
- package/dist/src/rpc/server.d.ts.map +1 -1
- package/dist/src/rpc/types.d.ts +17 -1
- package/dist/src/rpc/types.d.ts.map +1 -1
- package/dist/src/types.d.ts +38 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils.d.ts +41 -2
- package/dist/src/utils.d.ts.map +1 -1
- package/package.json +10 -2
- package/readme.md +53 -0
- package/src/delegation.js +2 -2
- package/src/invocation.js +58 -3
- package/src/replay.js +58 -0
- package/src/rpc/client.js +2 -2
- package/src/rpc/index.js +1 -1
- package/src/rpc/server.js +21 -0
- package/src/rpc/types.ts +22 -1
- package/src/types.ts +39 -0
- package/src/utils.js +152 -6
package/dist/src/invocation.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/src/rpc/index.d.ts
CHANGED
|
@@ -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
|
package/dist/src/rpc/server.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/src/rpc/types.d.ts
CHANGED
|
@@ -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":"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,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/rpc/types.ts"],"names":[],"mappings":"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"}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -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
|
package/dist/src/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,KAAK,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,SAAS,CAAA;AACvD,OAAO,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AACrE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sCAAsC,CAAA;AACpE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAE5C,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC9D,YAAY,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AACnD,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAE9B,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;AAC9D,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AACtD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAA;AACvC,MAAM,MAAM,WAAW,GAAG,YAAY,CAAA;AAEtC,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,MAAM,GACN,UAAU,GACV,WAAW,CAAA;AACf,MAAM,MAAM,SAAS,GAAG,SAAS,EAAE,GAAG,SAAS,SAAS,EAAE,CAAA;AAC1D,MAAM,MAAM,UAAU,GAClB;KAAG,GAAG,IAAI,MAAM,GAAG,SAAS;CAAE,GAC9B;KAAG,GAAG,IAAI,MAAM,CAAC,CAAC,EAAE,SAAS,GAAG,SAAS;CAAE,CAAA;AAE/C,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,UAAU,CAAA;AAE9D;;GAEG;AAEH,MAAM,WAAW,iBAAiB,CAAC,MAAM,SAAS,gBAAgB;IAChE,GAAG,EAAE,OAAO,CAAA;IACZ,GAAG,EAAE,GAAG,CAAA;IACR,GAAG,EAAE,GAAG,GAAG,IAAI,CAAA;IACf,GAAG,EAAE,MAAM,CAAC,gBAAgB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAA;IACjD;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,gBAAgB,EAAE,gBAAgB,CAAA;IAClC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,qBAAsB,SAAQ,yBAAyB;IACtE,KAAK,EAAE,UAAU,CAAA;CAClB;AAED;;GAEG;AAEH;;GAEG;AACH,MAAM,WAAW,iBAAkB,SAAQ,yBAAyB;IAClE,GAAG,EAAE,OAAO,CAAA;IACZ,GAAG,CAAC,EAAE,GAAG,CAAA;IACT,GAAG,EAAE,GAAG,CAAA;IACR,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,UAAU,CAAA;IAChB;;OAEG;IACH,GAAG,EAAE,UAAU,EAAE,CAAA;IACjB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,KAAK,CAAC,EAAE,GAAG,CAAA;IACX,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB;AAED,MAAM,WAAW,qBAAsB,SAAQ,yBAAyB;IACtE,KAAK,EAAE,UAAU,CAAA;IACjB,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,YAAY,EAAE,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/src/utils.d.ts
CHANGED
|
@@ -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
|
-
* @
|
|
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(
|
|
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
|
*
|
package/dist/src/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.js"],"names":[],"mappings":"
|
|
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.
|
|
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(
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
80
|
+
ttl: options.ttl,
|
|
81
81
|
verifierResolver: options.verifierResolver,
|
|
82
82
|
})
|
|
83
83
|
},
|
package/src/rpc/index.js
CHANGED
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 {
|
|
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
|
-
* @
|
|
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(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
|