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.
- package/dist/src/envelope.d.ts +1 -1
- package/dist/src/envelope.d.ts.map +1 -1
- 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/receipt.d.ts +2 -2
- 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 +56 -2
- package/dist/src/utils.d.ts.map +1 -1
- package/license +9 -0
- package/package.json +21 -13
- package/readme.md +108 -18
- package/src/delegation.js +2 -2
- package/src/invocation.js +61 -5
- 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 +171 -6
package/dist/src/envelope.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
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
|
|
@@ -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.
|
|
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.
|
|
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;
|
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":"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
|
|
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
|
*
|
|
@@ -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
|
-
* @
|
|
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(
|
|
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
|
*
|
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/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.
|
|
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": "^
|
|
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-
|
|
103
|
-
"iso-
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
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": "^
|
|
119
|
+
"@types/node": "^26.6.2",
|
|
113
120
|
"delay": "^7.0.0",
|
|
114
|
-
"playwright-test": "^
|
|
115
|
-
"typescript": "
|
|
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 [](https://www.npmjs.com/package/iso-ucan) [](https://github.com/hugomrdias/iso-repo/blob/main/license) [](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
|
-
|
|
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(
|
|
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
|
@@ -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(
|
|
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 (!
|
|
294
|
+
if (!commandCovers(current.cmd, next.cmd)) {
|
|
239
295
|
throw new Error(
|
|
240
|
-
`UCAN Invocation command mismatch, expected ${current.cmd} to
|
|
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
|
-
|
|
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
|
*
|
|
@@ -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
|
-
* @
|
|
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(
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
|