@kokuin/controller 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/epoch-events.d.ts +139 -0
- package/lib/epoch-events.js +177 -0
- package/lib/epoch.d.ts +90 -0
- package/lib/epoch.js +505 -0
- package/lib/events.d.ts +97 -143
- package/lib/events.js +94 -143
- package/lib/fold.d.ts +78 -100
- package/lib/fold.js +200 -225
- package/lib/history.d.ts +102 -51
- package/lib/history.js +160 -71
- package/lib/identity.d.ts +13 -36
- package/lib/identity.js +27 -63
- package/lib/index.d.ts +11 -6
- package/lib/index.js +10 -5
- package/lib/keystore-identity.d.ts +1 -4
- package/lib/keystore-identity.js +11 -8
- package/lib/limits.d.ts +29 -0
- package/lib/limits.js +27 -0
- package/lib/log.d.ts +124 -0
- package/lib/log.js +268 -0
- package/lib/members.d.ts +103 -0
- package/lib/members.js +152 -0
- package/lib/profiles.js +7 -1
- package/lib/resolver.d.ts +34 -29
- package/lib/resolver.js +64 -68
- package/lib/state-resolver.d.ts +63 -27
- package/lib/state-resolver.js +130 -53
- package/lib/state.d.ts +10 -26
- package/lib/state.js +23 -28
- package/lib/supersede.d.ts +22 -63
- package/lib/supersede.js +100 -186
- package/package.json +12 -12
package/lib/events.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import type { DIDString
|
|
1
|
+
import type { DIDString } from '@kokuin/token';
|
|
2
|
+
import type { EpochEventCommon } from './epoch-events.js';
|
|
2
3
|
export declare const DID_PREFIX = "did:kokuin:";
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* The root chain's event types, and only those: it holds the key-establishing events. Management
|
|
6
|
+
* actions — member adds, renewals, removals and revokes — live in the epoch a chain event opens,
|
|
7
|
+
* never on the chain. A chain entry of any other `t`, a `rev` from an older log included, takes the
|
|
8
|
+
* fold's unknown-type path, so a critical one fails the fold closed.
|
|
9
|
+
*/
|
|
10
|
+
export type EventType = 'icp' | 'rot';
|
|
4
11
|
export type EventCommon = {
|
|
5
12
|
/** Inception format version. Absent must never be inferred; always written. */
|
|
6
13
|
v: 1;
|
|
@@ -41,7 +48,11 @@ export type InceptionEvent = EventCommon & {
|
|
|
41
48
|
*/
|
|
42
49
|
r: string;
|
|
43
50
|
};
|
|
44
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Generic over both tiers: a root-chain event and an epoch event share the envelope and signatures
|
|
53
|
+
* over canonical bytes, and nothing else.
|
|
54
|
+
*/
|
|
55
|
+
export type SignedEvent<E extends EventCommon | EpochEventCommon = EventCommon> = {
|
|
45
56
|
event: E;
|
|
46
57
|
/** base64url ed25519 signatures over the canonical event bytes, positional against `event.k`. */
|
|
47
58
|
sigs: Array<string>;
|
|
@@ -51,7 +62,14 @@ export type SignedEvent<E extends EventCommon = EventCommon> = {
|
|
|
51
62
|
*/
|
|
52
63
|
recoveryKey?: string;
|
|
53
64
|
};
|
|
54
|
-
export declare function signEvent(event: EventCommon, privateKeys: Array<Uint8Array>): Array<string>;
|
|
65
|
+
export declare function signEvent(event: EventCommon | EpochEventCommon, privateKeys: Array<Uint8Array>): Array<string>;
|
|
66
|
+
/**
|
|
67
|
+
* A published key list: an array of strings. `k`, `n`, `ka` and the deny snapshot are carried into
|
|
68
|
+
* the folded state, so a bad shape must be rejected where it is published, not one event away where
|
|
69
|
+
* it is next read (a non-array `n` would throw in the *next* rotate; a non-array `k` is a `KeyState`
|
|
70
|
+
* whose type lies).
|
|
71
|
+
*/
|
|
72
|
+
export declare function isKeyList(value: unknown): value is Array<string>;
|
|
55
73
|
/**
|
|
56
74
|
* Total: a malformed signature or key yields false rather than throwing, including a body the
|
|
57
75
|
* canonicalizer refuses. `canonicalBytes` throws on a non-finite number or nesting past
|
|
@@ -60,17 +78,7 @@ export declare function signEvent(event: EventCommon, privateKeys: Array<Uint8Ar
|
|
|
60
78
|
* total and a direct caller reaches it with a parsed body. `false` is honest: a body with no
|
|
61
79
|
* canonical encoding has no bytes for a signature to be over.
|
|
62
80
|
*/
|
|
63
|
-
export declare function verifySignatures(event: EventCommon, sigs: Array<string>, keys: Array<string>): boolean;
|
|
64
|
-
/**
|
|
65
|
-
* Whether one of an event's signatures was made by `key` — the counterpart of
|
|
66
|
-
* {@link verifySignatures} for a signer the *event* does not name (resolved from a DID elsewhere). A
|
|
67
|
-
* capability-authorised revoke needs exactly this: its author is the capability's `aud`, which the
|
|
68
|
-
* fold cannot resolve, so it is handed the key and checks the signature itself.
|
|
69
|
-
*
|
|
70
|
-
* Total, EdDSA only — all {@link signEvent} produces. An ES256 audience therefore cannot author a
|
|
71
|
-
* revoke, which fails closed until events grow a second signature algorithm.
|
|
72
|
-
*/
|
|
73
|
-
export declare function verifyEventSignedBy(signed: SignedEvent, key: ResolvedSigningKey): boolean;
|
|
81
|
+
export declare function verifySignatures(event: EventCommon | EpochEventCommon, sigs: Array<string>, keys: Array<string>): boolean;
|
|
74
82
|
/**
|
|
75
83
|
* Deterministic inception. Only seed-derived, canonical material — no timestamp, nonce, or label —
|
|
76
84
|
* so the DID is a pure function of the seed and profile index. Never add a label: a mistyped one on
|
|
@@ -81,6 +89,36 @@ export declare function didFromInception(event: InceptionEvent): DIDString;
|
|
|
81
89
|
/** The did:kokuin: identifier a seed and profile produce, without signing an inception. */
|
|
82
90
|
export declare function didFor(seed: Uint8Array, profile: number): DIDString;
|
|
83
91
|
export declare function verifyInception(signed: SignedEvent<InceptionEvent>, did: string): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* One management member as a snapshot carries it: the DID it acts as, the Ed25519 key that DID is
|
|
94
|
+
* pinned to (in the spelling `k` uses), the digest of the `add` that admitted it — its incarnation,
|
|
95
|
+
* which renewals and removals name — and its lease end in unix seconds.
|
|
96
|
+
*
|
|
97
|
+
* The fold never reads `lease` against a clock: it bounds the grants the member mints, nothing on the
|
|
98
|
+
* log. Only a removal ends a member's log authority.
|
|
99
|
+
*/
|
|
100
|
+
export type MemberEntry = {
|
|
101
|
+
did: string;
|
|
102
|
+
key: string;
|
|
103
|
+
add: string;
|
|
104
|
+
lease: number;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* The whole management state a root-chain event leaves behind: the member set and the deny set. The
|
|
108
|
+
* state after the event **is** this snapshot — nothing before it contributes — so it is root's
|
|
109
|
+
* chosen replacement state, not a proven merge of everything that happened before it.
|
|
110
|
+
*/
|
|
111
|
+
export type StateSnapshot = {
|
|
112
|
+
members: Array<MemberEntry>;
|
|
113
|
+
deny: Array<string>;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* The shape of one snapshot member, nothing more: whether the DID carries the key, whether the key
|
|
117
|
+
* is Ed25519, and whether it collides with a root key are rules over the whole snapshot and its chain,
|
|
118
|
+
* checked by the fold. `lease` must be a non-negative safe integer — unix seconds — because grants
|
|
119
|
+
* compare `exp` against it, and a fraction, a negative or an unsafe integer has no single reading.
|
|
120
|
+
*/
|
|
121
|
+
export declare function isMemberEntry(value: unknown): value is MemberEntry;
|
|
84
122
|
export type RotateEvent = EventCommon & {
|
|
85
123
|
t: 'rot';
|
|
86
124
|
/** Keys the prior event pre-committed, now revealed, multicodec-tagged and multibase-encoded. */
|
|
@@ -100,57 +138,47 @@ export type RotateEvent = EventCommon & {
|
|
|
100
138
|
* {@link isPublishedRotate}). Not surfaced in `KeyState`: it belongs to a position, not the keys.
|
|
101
139
|
*/
|
|
102
140
|
a?: string;
|
|
103
|
-
/** Deny-set snapshot. Replaces the accumulated set, pruning it. */
|
|
104
|
-
d?: Array<string>;
|
|
105
|
-
};
|
|
106
|
-
export type CreateRotateOptions = {
|
|
107
|
-
seal?: string;
|
|
108
141
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* "also deny this" silently drops every accumulated entry — un-revoking devices and un-retiring
|
|
112
|
-
* leaked keys — with no error and nothing in the log saying anything was lost. Build it from the
|
|
113
|
-
* folded state with {@link pruneDenySet}. `[]` is the deliberate "clear everything" a cold rotate
|
|
114
|
-
* does to recover from a bad management tier. A rotate cannot establish a key its own snapshot
|
|
115
|
-
* denies; the fold refuses such an event.
|
|
142
|
+
* Member half of the state snapshot — see {@link StateSnapshot}. Required, with `d`, on every
|
|
143
|
+
* rotate that is not a reset: every rotate is a settlement. A reset carries both or neither.
|
|
116
144
|
*/
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
|
|
120
|
-
* fold's `keyGen`/`keySeq`.
|
|
121
|
-
*
|
|
122
|
-
* Defaults to `prior`'s position, right only while sequence and derivation index still coincide: a
|
|
123
|
-
* `rev` advances `s` and establishes no key, so after the first revoke of a generation the two part
|
|
124
|
-
* company **and never rejoin**. Every rotate after a revoke needs this, not just the one directly
|
|
125
|
-
* on it. Optional rather than required because {@link createRotate} verifies the key it reveals
|
|
126
|
-
* against `prior`'s commitment and throws on disagreement — provably right where omitted, an error
|
|
127
|
-
* (not an unfoldable event) where not. Take it from `KeyState.keyGen`/`keySeq`.
|
|
128
|
-
*/
|
|
129
|
-
keyPosition?: {
|
|
130
|
-
gen: number;
|
|
131
|
-
seq: number;
|
|
132
|
-
};
|
|
145
|
+
m?: Array<MemberEntry>;
|
|
146
|
+
/** Deny half of the state snapshot. Replaces the set the prior state held outright. */
|
|
147
|
+
d?: Array<string>;
|
|
133
148
|
};
|
|
134
149
|
export type CreateRotateParams = {
|
|
135
150
|
seed: Uint8Array;
|
|
136
151
|
profile: number;
|
|
137
152
|
did: string;
|
|
138
153
|
prior: EventCommon;
|
|
139
|
-
|
|
154
|
+
/**
|
|
155
|
+
* The complete state this rotate leaves behind — **a replacement, not an addition**. The state
|
|
156
|
+
* after a rotate is its snapshot and nothing before it, so a caller writing one deny entry meaning
|
|
157
|
+
* "also deny this" silently drops every other entry and every member — un-revoking devices and
|
|
158
|
+
* dismissing the management tier — with nothing in the log saying anything was lost. Build it from
|
|
159
|
+
* the folded state with {@link snapshotOf}, which also carries every member forward.
|
|
160
|
+
*
|
|
161
|
+
* Required: there is no "carry the prior state forward" rotate, because the prior state may hold
|
|
162
|
+
* epoch effects a verifier holding only the chain cannot reproduce. Two rules the fold enforces
|
|
163
|
+
* here: a member may not pin a key on, or committed by, this generation's root chain; and a
|
|
164
|
+
* retired-key denial an earlier rotate in the generation carried may not be dropped.
|
|
165
|
+
*/
|
|
166
|
+
snapshot: StateSnapshot;
|
|
167
|
+
/** Seal: an anchored external digest — see {@link RotateEvent.a}. */
|
|
168
|
+
seal?: string;
|
|
140
169
|
};
|
|
141
170
|
/**
|
|
142
171
|
* A rotate reveals the keys the prior event pre-committed and commits the next set, signed by the
|
|
143
172
|
* newly revealed keys (per KERI — what makes a stolen current key unable to rotate). Reproducible
|
|
144
|
-
* from the seed
|
|
173
|
+
* from the seed and the snapshot unless it carries a seal.
|
|
174
|
+
*
|
|
175
|
+
* The root chain holds only key-establishing events, so a rotate's sequence and its derivation index
|
|
176
|
+
* are the same number: the key it reveals sits one past `prior`'s position.
|
|
145
177
|
*
|
|
146
178
|
* **Throws rather than emitting an event the fold will reject.** The revealed key must be the one
|
|
147
|
-
* `prior` committed in `n`,
|
|
148
|
-
* position used to produce a well-formed, signed event no fold would accept. The two are checked
|
|
149
|
-
* against each other here. When `prior` carries no commitment (a `rev`), `keyPosition` is required:
|
|
150
|
-
* the default would derive a key one past the *revoke*, which nothing committed, and this is the one
|
|
151
|
-
* case unverifiable from `prior` alone.
|
|
179
|
+
* `prior` committed in `n`, which a prior not built from this chain's own events need not be.
|
|
152
180
|
*/
|
|
153
|
-
export declare function createRotate({ seed, profile, did, prior,
|
|
181
|
+
export declare function createRotate({ seed, profile, did, prior, snapshot, seal, }: CreateRotateParams): SignedEvent<RotateEvent>;
|
|
154
182
|
/**
|
|
155
183
|
* A rotate is valid when it chains to the prior digest, its revealed keys match the prior event's
|
|
156
184
|
* pre-rotation commitment, and its signatures verify against those keys.
|
|
@@ -165,15 +193,25 @@ export declare function verifyRotate(signed: SignedEvent<RotateEvent>, prior: {
|
|
|
165
193
|
*
|
|
166
194
|
* Anchored to the inception, not the log head: the recovery key lives on the root-retained
|
|
167
195
|
* derivation branch and its digest is committed in the deterministic inception, so a restored
|
|
168
|
-
* mnemonic can author one with no log knowledge or availability.
|
|
169
|
-
*
|
|
170
|
-
*
|
|
196
|
+
* mnemonic can author one with no log knowledge or availability.
|
|
197
|
+
*
|
|
198
|
+
* **Blind** (no `snapshot`): no seal, `d: []` and no `m`, so a blind reset is a pure function of
|
|
199
|
+
* `(seed, profile, gen)` and two blind resets at one generation produce identical bytes that resolve
|
|
200
|
+
* as idempotent re-derivation, not duplicity. The new epoch starts empty and root re-adds members.
|
|
201
|
+
* This is the path for a root that does not know the log's state.
|
|
202
|
+
*
|
|
203
|
+
* **With a snapshot**: the same reset carries the member and deny sets, so one recovery-key
|
|
204
|
+
* signature is the whole recovery. Two resets at one generation with different bytes are then
|
|
205
|
+
* duplicity — which only the recovery-key holder can cause, and a reset at a higher generation
|
|
206
|
+
* resolves. Members keep their `add` references, but every grant names its generation, so every
|
|
207
|
+
* grant minted under the prior one still dies. The snapshot is checked against the **new**
|
|
208
|
+
* generation's keys, and no retired-key denial of the old one need carry over.
|
|
171
209
|
*
|
|
172
210
|
* The root need not know the current generation, only eventually exceed it (no attacker can author a
|
|
173
211
|
* competing reset at any generation): a blind root starts at `gen = 1` and retries higher if it
|
|
174
212
|
* learns of one — the cost is a round trip, never loss of control.
|
|
175
213
|
*/
|
|
176
|
-
export declare function createReset(seed: Uint8Array, profile: number, gen: number): SignedEvent<RotateEvent>;
|
|
214
|
+
export declare function createReset(seed: Uint8Array, profile: number, gen: number, snapshot?: StateSnapshot): SignedEvent<RotateEvent>;
|
|
177
215
|
/**
|
|
178
216
|
* A reset verifies against the committed recovery digest, not the pre-rotation set. Both values it
|
|
179
217
|
* needs — the anchor digest and the recovery commitment — come from the inception, the *only* place
|
|
@@ -185,102 +223,18 @@ export declare function createReset(seed: Uint8Array, profile: number, gen: numb
|
|
|
185
223
|
*/
|
|
186
224
|
export declare function verifyReset(signed: SignedEvent<RotateEvent>, inception: InceptionEvent): boolean;
|
|
187
225
|
/**
|
|
188
|
-
* How a
|
|
226
|
+
* How a deny entry names a **key** rather than a DID: `#<the multibase key exactly as it appears in
|
|
189
227
|
* `k`>`, the same spelling a token's `kid` uses. One spelling for one thing — unambiguous against
|
|
190
228
|
* `did:…` on sight, so a bare multibase string is not a second accepted encoding, any more than for
|
|
191
229
|
* `kid`.
|
|
192
230
|
*/
|
|
193
231
|
export declare const KEY_TARGET_PREFIX = "#";
|
|
194
|
-
/** The deny-set spelling of `key` — what
|
|
232
|
+
/** The deny-set spelling of `key` — what a snapshot's `d` carries to deny it. */
|
|
195
233
|
export declare function keyTarget(key: string): string;
|
|
196
234
|
/**
|
|
197
|
-
* The key a
|
|
198
|
-
*
|
|
235
|
+
* The key a deny entry names, or `null` when it names a DID instead. Total on any string: an entry of
|
|
236
|
+
* neither form lands in the deny set as the opaque identifier it is. The body is not validated
|
|
199
237
|
* against multibase — a `#` naming nothing denies nothing, exactly as a `did:` naming nobody does,
|
|
200
238
|
* and rejecting it would only add a way for a well-formed log to stop folding.
|
|
201
239
|
*/
|
|
202
240
|
export declare function keyFromTarget(target: string): string | null;
|
|
203
|
-
export type RevokeEvent = EventCommon & {
|
|
204
|
-
t: 'rev';
|
|
205
|
-
/**
|
|
206
|
-
* What to deny, in one of two spellings, never a capability `jti`.
|
|
207
|
-
*
|
|
208
|
-
* A **DID** (`did:…`) denies a holder: no capability whose `aud` is that DID is valid from this
|
|
209
|
-
* position onward — one entry covers a device's life. A **key** ({@link keyTarget}) denies a
|
|
210
|
-
* signer: nothing this profile signed with that key verifies from this position onward, and it
|
|
211
|
-
* names a key of *this* profile (the deny set is the subject's own).
|
|
212
|
-
*
|
|
213
|
-
* One field carries both because they are enforced at opposite ends of the same set: the DID form
|
|
214
|
-
* by `@kokuin/capability` against a capability's `aud`, the key form by the resolver against the
|
|
215
|
-
* key a `kid` selects. Neither spelling can be mistaken for the other.
|
|
216
|
-
*/
|
|
217
|
-
x: string;
|
|
218
|
-
/** A serialized capability authorising a non-authority signer. Verified in the fold. */
|
|
219
|
-
cap?: string;
|
|
220
|
-
};
|
|
221
|
-
export type CreateRevokeParams = {
|
|
222
|
-
seed: Uint8Array;
|
|
223
|
-
profile: number;
|
|
224
|
-
did: string;
|
|
225
|
-
prior: EventCommon;
|
|
226
|
-
target: string;
|
|
227
|
-
keyPosition: {
|
|
228
|
-
gen: number;
|
|
229
|
-
seq: number;
|
|
230
|
-
};
|
|
231
|
-
/** A serialized capability authorising this signer, when the signer is not an authority key. */
|
|
232
|
-
cap?: string;
|
|
233
|
-
};
|
|
234
|
-
/**
|
|
235
|
-
* Revoke a DID or a key — see {@link RevokeEvent.x} for the two spellings.
|
|
236
|
-
*
|
|
237
|
-
* Naming the device DID rather than a `jti` makes this one entry per device for its life — covering
|
|
238
|
-
* capabilities the verifier has never seen and future re-mints, where per-`jti` revocation would grow
|
|
239
|
-
* with every renewal. A key target retires a leaked key for material it *already* signed: `rotate`
|
|
240
|
-
* only retires it for new issuance (`resolve` is head-only), but already-issued material verifies
|
|
241
|
-
* through `resolveHistoric`, which survives a rotate by design, so a thief holding a rotated-away key
|
|
242
|
-
* could still mint one that verified. Retirement is therefore explicit, keeping the promise that a
|
|
243
|
-
* routine rotate does not invalidate already-issued material intact.
|
|
244
|
-
*
|
|
245
|
-
* **A key the profile currently publishes cannot be denied** — the fold rejects it (see the `rev`
|
|
246
|
-
* branch of `stepEvent`). Rotate first, then deny the key the rotate retired.
|
|
247
|
-
*
|
|
248
|
-
* `prior` answers "next sequence, what to chain to"; `keyPosition` separately answers "where the
|
|
249
|
-
* active authority key lives". They coincide only for an `icp`/`rot` prior — a `rev` establishes no
|
|
250
|
-
* key, so a revoke on a revoke still points at the last `icp`/`rot` position.
|
|
251
|
-
*/
|
|
252
|
-
export declare function createRevoke({ seed, profile, did, prior, target, keyPosition, cap, }: CreateRevokeParams): SignedEvent<RevokeEvent>;
|
|
253
|
-
export type CreateRevokeOptions = {
|
|
254
|
-
/** A serialized capability authorising this signer, when the signer is not an authority key. */
|
|
255
|
-
cap?: string;
|
|
256
|
-
};
|
|
257
|
-
export type CreateRevokeWithKeyParams = {
|
|
258
|
-
privateKey: Uint8Array;
|
|
259
|
-
did: string;
|
|
260
|
-
prior: EventCommon;
|
|
261
|
-
target: string;
|
|
262
|
-
/** A serialized capability authorising this signer, when the signer is not an authority key. */
|
|
263
|
-
cap?: string;
|
|
264
|
-
};
|
|
265
|
-
/**
|
|
266
|
-
* Revoke a DID, signing with an Ed25519 key the caller already holds rather than deriving one from
|
|
267
|
-
* the profile seed.
|
|
268
|
-
*
|
|
269
|
-
* The builder for the actor a capability-authorised revoke exists for: a device holding a management
|
|
270
|
-
* capability, which by design never receives the profile sub-seed. `createRevoke` can only sign as
|
|
271
|
-
* the profile itself, so without this the feature had no API but re-implementing the signing
|
|
272
|
-
* convention per consumer — the duplication `createControllerCapabilityVerifier` prevents on the
|
|
273
|
-
* verifying side.
|
|
274
|
-
*
|
|
275
|
-
* The key is the audience key the capability pins in `cnf`, and the fold checks the event's signature
|
|
276
|
-
* against exactly that, so the two must match. Ed25519 only. Takes the private key rather than an
|
|
277
|
-
* identity because no identity type here signs raw bytes — a `KeyStore` entry hands back exactly this
|
|
278
|
-
* shape. Byte-identical to {@link createRevoke} given the same key; no `keyPosition`, which exists
|
|
279
|
-
* only to derive a key from a seed.
|
|
280
|
-
*/
|
|
281
|
-
export declare function createRevokeWithKey({ privateKey, did, prior, target, cap, }: CreateRevokeWithKeyParams): SignedEvent<RevokeEvent>;
|
|
282
|
-
/** Authority-signed revoke. Capability-authorised revokes are checked in the fold. */
|
|
283
|
-
export declare function verifyRevoke(signed: SignedEvent<RevokeEvent>, prior: {
|
|
284
|
-
digest: string;
|
|
285
|
-
keys: Array<string>;
|
|
286
|
-
}): boolean;
|