nervur 0.19.2 → 0.20.1
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/README.md +156 -104
- package/dist/being/being.d.ts +8 -15
- package/dist/being/being.js +20 -67
- package/dist/being/digest.d.ts +1 -1
- package/dist/being/digest.js +7 -27
- package/dist/being/faculty.d.ts +10 -0
- package/dist/being/faculty.js +74 -0
- package/dist/being/index.d.ts +5 -5
- package/dist/being/index.js +6 -5
- package/dist/being/kind.d.ts +4 -0
- package/dist/being/kind.js +28 -0
- package/dist/being/types.d.ts +35 -63
- package/dist/being/types.js +3 -57
- package/dist/being/words.d.ts +13 -0
- package/dist/being/words.js +14 -0
- package/dist/cli/command.d.ts +13 -0
- package/dist/cli/command.js +194 -0
- package/dist/cli/harbor.d.ts +21 -0
- package/dist/cli/harbor.js +141 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +14 -0
- package/dist/contract/index.d.ts +39 -0
- package/dist/contract/index.js +33 -0
- package/dist/crypto/aes.d.ts +3 -0
- package/dist/crypto/aes.js +24 -0
- package/dist/crypto/bytes.d.ts +6 -0
- package/dist/crypto/bytes.js +33 -0
- package/dist/crypto/ed25519.d.ts +3 -0
- package/dist/crypto/ed25519.js +85 -0
- package/dist/crypto/hash.d.ts +2 -0
- package/dist/crypto/hash.js +11 -0
- package/dist/crypto/index.d.ts +7 -0
- package/dist/crypto/index.js +10 -0
- package/dist/crypto/json.d.ts +8 -0
- package/dist/crypto/json.js +250 -0
- package/dist/crypto/mlkem.d.ts +12 -0
- package/dist/crypto/mlkem.js +36 -0
- package/dist/crypto/subtle.d.ts +10 -0
- package/dist/crypto/subtle.js +29 -0
- package/dist/crypto/x25519.d.ts +2 -0
- package/dist/crypto/x25519.js +21 -0
- package/dist/folder/index.d.ts +14 -0
- package/dist/folder/index.js +87 -0
- package/dist/harbor/catalogue.d.ts +25 -0
- package/dist/harbor/catalogue.js +93 -0
- package/dist/harbor/dock.d.ts +29 -0
- package/dist/harbor/dock.js +122 -0
- package/dist/harbor/harbor.d.ts +31 -0
- package/dist/harbor/harbor.js +146 -0
- package/dist/harbor/index.d.ts +7 -10
- package/dist/harbor/index.js +9 -18
- package/dist/harbor/package.d.ts +10 -0
- package/dist/harbor/package.js +115 -0
- package/dist/harbor/registry.d.ts +9 -0
- package/dist/harbor/registry.js +78 -0
- package/dist/harbor/root-line.d.ts +9 -0
- package/dist/harbor/root-line.js +36 -0
- package/dist/harbor/terrain.d.ts +9 -0
- package/dist/harbor/terrain.js +2 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -0
- package/dist/pointer/bodies.d.ts +26 -0
- package/dist/pointer/bodies.js +64 -0
- package/dist/pointer/index.d.ts +2 -0
- package/dist/pointer/index.js +4 -0
- package/dist/pointer/world.d.ts +35 -0
- package/dist/pointer/world.js +85 -0
- package/dist/quo/door.d.ts +34 -0
- package/dist/quo/door.js +172 -0
- package/dist/quo/index.d.ts +8 -0
- package/dist/quo/index.js +11 -0
- package/dist/quo/invitation.d.ts +7 -0
- package/dist/quo/invitation.js +15 -0
- package/dist/quo/keys.d.ts +40 -0
- package/dist/quo/keys.js +79 -0
- package/dist/quo/payload.d.ts +15 -0
- package/dist/quo/payload.js +55 -0
- package/dist/quo/relations.d.ts +40 -0
- package/dist/quo/relations.js +33 -0
- package/dist/quo/reply.d.ts +13 -0
- package/dist/quo/reply.js +35 -0
- package/dist/quo/seal.d.ts +42 -0
- package/dist/quo/seal.js +78 -0
- package/dist/quo/standing.d.ts +39 -0
- package/dist/quo/standing.js +90 -0
- package/dist/tcp/frame.d.ts +29 -0
- package/dist/tcp/frame.js +74 -0
- package/dist/tcp/index.d.ts +18 -0
- package/dist/tcp/index.js +164 -0
- package/dist/ward/allowance.d.ts +13 -9
- package/dist/ward/allowance.js +28 -67
- package/dist/ward/cells.d.ts +6 -6
- package/dist/ward/cells.js +67 -179
- package/dist/ward/index.d.ts +6 -10
- package/dist/ward/index.js +7 -13
- package/dist/ward/partition.d.ts +44 -60
- package/dist/ward/partition.js +56 -285
- package/dist/ward/pilot.d.ts +8 -0
- package/dist/ward/pilot.js +50 -0
- package/dist/ward/stance.d.ts +27 -20
- package/dist/ward/stance.js +174 -406
- package/dist/ward/ward-being.d.ts +35 -0
- package/dist/ward/ward-being.js +82 -0
- package/dist/ward/ward.d.ts +45 -11
- package/dist/ward/ward.js +239 -357
- package/package.json +24 -31
- package/src/being/being.ts +35 -78
- package/src/being/digest.ts +7 -35
- package/src/being/faculty.ts +73 -0
- package/src/being/index.ts +6 -6
- package/src/being/kind.ts +30 -0
- package/src/being/types.ts +50 -146
- package/src/being/words.ts +31 -0
- package/src/cli/command.ts +188 -0
- package/src/cli/harbor.ts +137 -0
- package/src/cli/main.ts +13 -0
- package/src/contract/index.ts +73 -0
- package/src/crypto/aes.ts +26 -0
- package/src/crypto/bytes.ts +37 -0
- package/src/crypto/ed25519.ts +84 -0
- package/src/crypto/hash.ts +14 -0
- package/src/crypto/index.ts +10 -0
- package/src/crypto/json.ts +241 -0
- package/src/crypto/mlkem.ts +38 -0
- package/src/crypto/subtle.ts +33 -0
- package/src/crypto/x25519.ts +21 -0
- package/src/folder/index.ts +92 -0
- package/src/harbor/catalogue.ts +110 -0
- package/src/harbor/dock.ts +137 -0
- package/src/harbor/harbor.ts +180 -0
- package/src/harbor/index.ts +9 -19
- package/src/harbor/package.ts +131 -0
- package/src/harbor/registry.ts +73 -0
- package/src/harbor/root-line.ts +36 -0
- package/src/harbor/terrain.ts +15 -0
- package/src/index.ts +20 -0
- package/src/pointer/bodies.ts +65 -0
- package/src/pointer/index.ts +4 -0
- package/src/pointer/world.ts +94 -0
- package/src/quo/door.ts +178 -0
- package/src/quo/index.ts +11 -0
- package/src/quo/invitation.ts +15 -0
- package/src/quo/keys.ts +91 -0
- package/src/quo/payload.ts +61 -0
- package/src/quo/relations.ts +62 -0
- package/src/quo/reply.ts +38 -0
- package/src/quo/seal.ts +101 -0
- package/src/quo/standing.ts +111 -0
- package/src/stand/main.ts +20 -0
- package/src/stand/stand.ts +220 -0
- package/src/tcp/frame.ts +78 -0
- package/src/tcp/index.ts +173 -0
- package/src/ward/allowance.ts +37 -75
- package/src/ward/cells.ts +63 -176
- package/src/ward/index.ts +7 -17
- package/src/ward/partition.ts +86 -326
- package/src/ward/pilot.ts +50 -0
- package/src/ward/stance.ts +186 -420
- package/src/ward/ward-being.ts +103 -0
- package/src/ward/ward.ts +256 -359
- package/dist/being/lent.d.ts +0 -32
- package/dist/being/lent.js +0 -72
- package/dist/being/silence.d.ts +0 -12
- package/dist/being/silence.js +0 -41
- package/dist/conformance/assert.d.ts +0 -11
- package/dist/conformance/assert.js +0 -106
- package/dist/conformance/beings.d.ts +0 -199
- package/dist/conformance/beings.js +0 -188
- package/dist/conformance/estate.d.ts +0 -5
- package/dist/conformance/estate.js +0 -388
- package/dist/conformance/index.d.ts +0 -80
- package/dist/conformance/index.js +0 -819
- package/dist/conformance/reach.d.ts +0 -10
- package/dist/conformance/reach.js +0 -72
- package/dist/conformance/store.d.ts +0 -5
- package/dist/conformance/store.js +0 -113
- package/dist/harbor/box.d.ts +0 -121
- package/dist/harbor/box.js +0 -121
- package/dist/harbor/core.d.ts +0 -55
- package/dist/harbor/core.js +0 -662
- package/dist/harbor/dial.d.ts +0 -9
- package/dist/harbor/dial.js +0 -81
- package/dist/harbor/memory.d.ts +0 -29
- package/dist/harbor/memory.js +0 -104
- package/dist/harbor/reach.d.ts +0 -36
- package/dist/harbor/reach.js +0 -199
- package/dist/harbor/store.d.ts +0 -35
- package/dist/harbor/store.js +0 -62
- package/dist/vector/cases.d.ts +0 -42
- package/dist/vector/cases.js +0 -223
- package/dist/vector/index.d.ts +0 -6
- package/dist/vector/index.js +0 -8
- package/dist/vector/stand.d.ts +0 -9
- package/dist/vector/stand.js +0 -77
- package/dist/vector/world.d.ts +0 -144
- package/dist/vector/world.js +0 -209
- package/dist/ward/arithmetic.d.ts +0 -31
- package/dist/ward/arithmetic.js +0 -249
- package/dist/ward/door.d.ts +0 -18
- package/dist/ward/door.js +0 -186
- package/dist/ward/ground.d.ts +0 -29
- package/dist/ward/ground.js +0 -56
- package/dist/ward/heirs.d.ts +0 -13
- package/dist/ward/heirs.js +0 -117
- package/dist/ward/json.d.ts +0 -2
- package/dist/ward/json.js +0 -163
- package/dist/ward/owner.d.ts +0 -13
- package/dist/ward/owner.js +0 -220
- package/dist/ward/seal.d.ts +0 -52
- package/dist/ward/seal.js +0 -150
- package/src/being/lent.ts +0 -72
- package/src/being/silence.ts +0 -46
- package/src/conformance/assert.ts +0 -100
- package/src/conformance/beings.ts +0 -188
- package/src/conformance/estate.ts +0 -412
- package/src/conformance/index.ts +0 -965
- package/src/conformance/reach.ts +0 -83
- package/src/conformance/store.ts +0 -125
- package/src/harbor/box.ts +0 -131
- package/src/harbor/core.ts +0 -699
- package/src/harbor/dial.ts +0 -112
- package/src/harbor/memory.ts +0 -123
- package/src/harbor/reach.ts +0 -221
- package/src/harbor/store.ts +0 -91
- package/src/vector/cases.ts +0 -257
- package/src/vector/index.ts +0 -11
- package/src/vector/stand.ts +0 -76
- package/src/vector/world.ts +0 -232
- package/src/ward/arithmetic.ts +0 -251
- package/src/ward/door.ts +0 -186
- package/src/ward/ground.ts +0 -142
- package/src/ward/heirs.ts +0 -116
- package/src/ward/json.ts +0 -144
- package/src/ward/owner.ts +0 -214
- package/src/ward/seal.ts +0 -178
package/src/vector/cases.ts
DELETED
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// The door's thirteen cases as records. One record per case, D1 through
|
|
3
|
-
// D13, both halves of D8 and both of D9. Each is taken in its own world, so
|
|
4
|
-
// the stream is at the same place every time and a record is reproduced
|
|
5
|
-
// without running the ones before it.
|
|
6
|
-
//
|
|
7
|
-
// A case is its setup and its arrival, held apart: `arrive` stands the world
|
|
8
|
-
// in the case's state and seals the ask, and whoever holds the record knocks.
|
|
9
|
-
// `doorCases` knocks at once and is what `door.json` is written from and
|
|
10
|
-
// held to. Vector mode stops after `arrive` and lets a verifier bring the
|
|
11
|
-
// bytes, which is the whole reason the two halves are two.
|
|
12
|
-
//
|
|
13
|
-
// `kind` is what the reply is, not what it says: `silence` is the one shape
|
|
14
|
-
// every stranger gets, `word` is what a ward says to a key it has bound, and
|
|
15
|
-
// `object` is an answer. The law of one silence is a law about `silence`
|
|
16
|
-
// alone, and the words are here so a kit sees they are a different length on
|
|
17
|
-
// purpose.
|
|
18
|
-
import { Ward } from '../ward/ward.ts';
|
|
19
|
-
import * as seal from '../ward/seal.ts';
|
|
20
|
-
import { hex } from '../ward/arithmetic.ts';
|
|
21
|
-
import { digest } from '../being/digest.ts';
|
|
22
|
-
import type { Json, JsonObject } from '../being/types.ts';
|
|
23
|
-
import type { Booted } from '../harbor/memory.ts';
|
|
24
|
-
import { world, sealed, bound, secret, rnd, drawn, partitionDigest, Caller } from './world.ts';
|
|
25
|
-
import type { World, Hand, Sealed } from './world.ts';
|
|
26
|
-
|
|
27
|
-
export type DoorRecord = {
|
|
28
|
-
case: string;
|
|
29
|
-
name: string;
|
|
30
|
-
state: string;
|
|
31
|
-
ward: string; // the seed of the ward whose door judged it
|
|
32
|
-
kind: 'silence' | 'word' | 'object';
|
|
33
|
-
heard: boolean; // whether a key that door holds spoke
|
|
34
|
-
ask: string; // hex, exactly as the bytes arrive
|
|
35
|
-
reply: string; // hex, exactly as they leave
|
|
36
|
-
opens: Json | null; // what the reply opens to where the hand holds the lid's secret
|
|
37
|
-
draws: number; // how many words of the stream were spent before the arrival
|
|
38
|
-
blueprint?: JsonObject; // the shape `seen` is the digest of, where a reply carries one
|
|
39
|
-
wrote: boolean; // the partition changed while the door judged. a refusal never sets it
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// `pin` is where the case's own setup ends and the arrival begins. A record
|
|
43
|
-
// that measured the partition before its heir was minted would call the
|
|
44
|
-
// minting a write of the door's, and every refusal would read as one.
|
|
45
|
-
export type Pin = () => Promise<void>;
|
|
46
|
-
|
|
47
|
-
// The world stood in a case's state, the ask sealed and not yet knocked.
|
|
48
|
-
export type Arrival = { kind: DoorRecord['kind']; door: Booted; ask: Sealed | { bytes: Uint8Array; open: null } };
|
|
49
|
-
|
|
50
|
-
export type Case = { case: string; name: string; state: string; ward: string; arrive: (w: World, pin: Pin) => Promise<Arrival> };
|
|
51
|
-
|
|
52
|
-
// Stand one case: its world, its setup, its ask. What comes back is enough
|
|
53
|
-
// to knock, and enough for a verifier to check the hand before the door.
|
|
54
|
-
export async function stand(c: Case): Promise<{ w: World; before: string; draws: number; arrival: Arrival }> {
|
|
55
|
-
const w = await world();
|
|
56
|
-
let before = await partitionDigest(w.b, c.ward);
|
|
57
|
-
const arrival = await c.arrive(w, async () => {
|
|
58
|
-
before = await partitionDigest(w.b, c.ward);
|
|
59
|
-
});
|
|
60
|
-
// Where the stream stands now, which is where the door is about to draw
|
|
61
|
-
// from. A kit that stood this case its own way sets its stream here.
|
|
62
|
-
return { w, before, draws: drawn(), arrival };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// The blueprint a reply's `seen` was taken over. The host is the only being
|
|
66
|
-
// in this world whose shape reaches a stranger, and the asker is the id her
|
|
67
|
-
// heir was opened for, so there is one blueprint to find and it is checked
|
|
68
|
-
// against the digest before it is written down.
|
|
69
|
-
async function shown(w: World, seen: string): Promise<JsonObject | undefined> {
|
|
70
|
-
for (const id of Object.keys(w.host.cells.occupants)) {
|
|
71
|
-
const bp = w.host.describe({ id }) as unknown as JsonObject;
|
|
72
|
-
if ((await digest(bp)) === seen) return bp;
|
|
73
|
-
}
|
|
74
|
-
return undefined;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async function record(c: Case): Promise<DoorRecord> {
|
|
78
|
-
const { w, before, draws, arrival } = await stand(c);
|
|
79
|
-
const judged = await arrival.door.door(arrival.ask.bytes);
|
|
80
|
-
const after = await partitionDigest(w.b, c.ward);
|
|
81
|
-
const opens = arrival.ask.open ? ((await arrival.ask.open(judged.bytes)) as Json | null) : null;
|
|
82
|
-
// A reply that carries `seen` carries a being's shape on the wire, which is
|
|
83
|
-
// the one place in this corpus where what a being answers reaches a
|
|
84
|
-
// stranger. So the blueprint that digest is taken over is written down
|
|
85
|
-
// beside it: a kit stands a being that answers exactly this to the empty
|
|
86
|
-
// ask, and its door's bytes are these bytes. Every other record is the
|
|
87
|
-
// door alone and needs no being at all.
|
|
88
|
-
const seen = opens !== null && typeof opens === 'object' && !Array.isArray(opens) ? (opens).seen : undefined;
|
|
89
|
-
const blueprint = typeof seen === 'string' ? await shown(w, seen) : undefined;
|
|
90
|
-
return { case: c.case, name: c.name, state: c.state, ward: c.ward, kind: arrival.kind, heard: judged.heard, draws, ask: hex(arrival.ask.bytes), reply: hex(judged.bytes), opens, ...(blueprint ? { blueprint } : {}), wrote: before !== after };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// One after another and never at once: the cases share one stream, and two
|
|
94
|
-
// of them in flight together would draw from it in whatever order the
|
|
95
|
-
// machine's crypto happened to finish in.
|
|
96
|
-
export async function doorCases(): Promise<DoorRecord[]> {
|
|
97
|
-
const out: DoorRecord[] = [];
|
|
98
|
-
for (const c of cases) out.push(await record(c));
|
|
99
|
-
return out;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// A case by its two names, which together are unique in the corpus.
|
|
103
|
-
export const caseNamed = (kase: string, name: string): Case | undefined => cases.find((c) => c.case === kase && c.name === name);
|
|
104
|
-
|
|
105
|
-
const at = async (door: Booted, to: string | null, kind: DoorRecord['kind'], h: Hand = {}): Promise<Arrival> => ({ kind, door, ask: await sealed(door.pk, to, h) });
|
|
106
|
-
|
|
107
|
-
// The D2 body: a bound relation, then one field of the wrong shape.
|
|
108
|
-
async function bad(w: World, pin: Pin, h: Hand): Promise<Arrival> {
|
|
109
|
-
const b = await bound(w);
|
|
110
|
-
await pin();
|
|
111
|
-
return at(w.WB, b.inv.heir!, 'silence', { signer: b.own, ...h });
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export const cases: Case[] = [
|
|
115
|
-
{
|
|
116
|
-
case: 'D1',
|
|
117
|
-
name: 'wrong padlock',
|
|
118
|
-
state: "a heir the door holds, sealed to another ward's padlock",
|
|
119
|
-
ward: 'B',
|
|
120
|
-
arrive: async (w) => at(w.WB, w.inv.heir!, 'silence', { signer: secret(w.inv), padlock: (await seal.wardKey('other')).padlockPk }),
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
case: 'D1',
|
|
124
|
-
name: 'noise',
|
|
125
|
-
state: 'bytes that are not a sealed ask at all',
|
|
126
|
-
ward: 'B',
|
|
127
|
-
// Nobody holds a secret for the lid this one is sealed to: the door
|
|
128
|
-
// answers to the first thirty-two bytes of the noise, and those are not
|
|
129
|
-
// a key anyone made. That is the case, not a gap in it.
|
|
130
|
-
arrive: async (w) => ({ kind: 'silence', door: w.WB, ask: { bytes: Uint8Array.of(1, ...rnd(32), ...rnd(80)), open: null } }),
|
|
131
|
-
},
|
|
132
|
-
{ case: 'D2', name: 'method is not a string', state: 'a bound key, one field of the wrong shape', ward: 'B', arrive: (w, pin) => bad(w, pin, { method: 7 as unknown as string }) },
|
|
133
|
-
{ case: 'D2', name: 'args is not an object', state: 'a bound key, one field of the wrong shape', ward: 'B', arrive: (w, pin) => bad(w, pin, { args: 7 as unknown as JsonObject }) },
|
|
134
|
-
{ case: 'D2', name: 'seq is zero', state: 'a bound key, one field of the wrong shape', ward: 'B', arrive: (w, pin) => bad(w, pin, { seq: 0 }) },
|
|
135
|
-
{ case: 'D2', name: 'time is negative', state: 'a bound key, one field of the wrong shape', ward: 'B', arrive: (w, pin) => bad(w, pin, { time: -1 }) },
|
|
136
|
-
{ case: 'D2', name: 'by is not a key', state: 'a bound key, one field of the wrong shape', ward: 'B', arrive: (w, pin) => bad(w, pin, { by: 'nope' }) },
|
|
137
|
-
{ case: 'D2', name: 'hops at zero', state: 'a bound key, and a chain that has run out', ward: 'B', arrive: (w, pin) => bad(w, pin, { hops: 0 }) },
|
|
138
|
-
{ case: 'D3', name: 'for nobody, nobody home', state: 'a ward with no public being', ward: 'B', arrive: (w) => at(w.WB, null, 'silence') },
|
|
139
|
-
{ case: 'D4', name: 'for nobody, the signature fails', state: 'a ward with a public being, a body signed by another key', ward: 'P', arrive: async (w) => at(w.WP, null, 'silence', { by: (await seal.beingKey(rnd())).pk }) },
|
|
140
|
-
{ case: 'D5', name: 'a heir not held', state: 'a heir this ward never minted', ward: 'B', arrive: (w) => at(w.WB, 'ff'.repeat(32), 'silence') },
|
|
141
|
-
{
|
|
142
|
-
case: 'D6',
|
|
143
|
-
name: 'a key not admitted',
|
|
144
|
-
state: 'a heir the door holds, a key nobody announced',
|
|
145
|
-
ward: 'B',
|
|
146
|
-
arrive: async (w, pin) => {
|
|
147
|
-
const b = await bound(w);
|
|
148
|
-
await pin();
|
|
149
|
-
return at(w.WB, b.inv.heir!, 'silence');
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
case: 'D7',
|
|
154
|
-
name: 'the signature fails under an admitted key',
|
|
155
|
-
state: 'a bound key named in by, a body signed by another',
|
|
156
|
-
ward: 'B',
|
|
157
|
-
arrive: async (w, pin) => {
|
|
158
|
-
const b = await bound(w);
|
|
159
|
-
await pin();
|
|
160
|
-
return at(w.WB, b.inv.heir!, 'silence', { signer: rnd(), by: b.ownPk });
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
case: 'D8',
|
|
165
|
-
name: 'she is not there',
|
|
166
|
-
state: 'the ward restarted without her class: her heirs stand and she does not',
|
|
167
|
-
ward: 'B',
|
|
168
|
-
// Under the key she bound: the heir was spent by her knock and admits
|
|
169
|
-
// nothing now, so an arrival signed with its secret would be D6 and not
|
|
170
|
-
// this case. Her own key is what the door holds for her, and it is the
|
|
171
|
-
// key that hears the word.
|
|
172
|
-
arrive: async (w, pin) => {
|
|
173
|
-
const b = await bound(w);
|
|
174
|
-
const WB = await w.b.reboot('B', Ward, { Caller });
|
|
175
|
-
await pin();
|
|
176
|
-
return at(WB, b.inv.heir!, 'word', { signer: b.own });
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
case: 'D8',
|
|
181
|
-
name: 'she is there and the record is not',
|
|
182
|
-
state: 'the being is booted and the occupant her heir was opened for is gone',
|
|
183
|
-
ward: 'B',
|
|
184
|
-
arrive: async (w, pin) => {
|
|
185
|
-
const b = await bound(w);
|
|
186
|
-
delete (w.host.cells.occupants as Record<string, unknown>)[b.rec().id];
|
|
187
|
-
await pin();
|
|
188
|
-
return at(w.WB, b.inv.heir!, 'word', { signer: b.own });
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
case: 'D9',
|
|
193
|
-
name: 'a heir that announces nothing',
|
|
194
|
-
state: 'a fresh heir, and no next in the body',
|
|
195
|
-
ward: 'B',
|
|
196
|
-
arrive: async (w, pin) => {
|
|
197
|
-
const inv = await w.host.welcome();
|
|
198
|
-
await pin();
|
|
199
|
-
return at(w.WB, inv.heir!, 'word', { signer: secret(inv), next: null });
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
case: 'D9',
|
|
204
|
-
name: 'a bound key that announces nothing',
|
|
205
|
-
state: 'a key the door already vouched for, and no next in the body',
|
|
206
|
-
ward: 'B',
|
|
207
|
-
arrive: async (w, pin) => {
|
|
208
|
-
const b = await bound(w);
|
|
209
|
-
await pin();
|
|
210
|
-
return at(w.WB, b.inv.heir!, 'object', { signer: b.own, next: null });
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
case: 'D10',
|
|
215
|
-
name: 'a number already spent',
|
|
216
|
-
state: 'a bound key, a seq at or below the mark',
|
|
217
|
-
ward: 'B',
|
|
218
|
-
arrive: async (w, pin) => {
|
|
219
|
-
const b = await bound(w);
|
|
220
|
-
await pin();
|
|
221
|
-
return at(w.WB, b.inv.heir!, 'word', { signer: b.own, seq: 1 });
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
case: 'D11',
|
|
226
|
-
name: 'she threw',
|
|
227
|
-
state: 'a bound key on an ask that raises',
|
|
228
|
-
ward: 'B',
|
|
229
|
-
arrive: async (w, pin) => {
|
|
230
|
-
const b = await bound(w);
|
|
231
|
-
await pin();
|
|
232
|
-
return at(w.WB, b.inv.heir!, 'word', { signer: b.own, method: 'boom' });
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
case: 'D12',
|
|
237
|
-
name: 'she said silence',
|
|
238
|
-
state: 'a bound key on an ask that answers the value silence',
|
|
239
|
-
ward: 'B',
|
|
240
|
-
arrive: async (w, pin) => {
|
|
241
|
-
const b = await bound(w);
|
|
242
|
-
await pin();
|
|
243
|
-
return at(w.WB, b.inv.heir!, 'silence', { signer: b.own, method: 'quiet' });
|
|
244
|
-
},
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
case: 'D13',
|
|
248
|
-
name: 'a word out of her',
|
|
249
|
-
state: 'a bound key on an ask that returns a shape only a ward may make',
|
|
250
|
-
ward: 'B',
|
|
251
|
-
arrive: async (w, pin) => {
|
|
252
|
-
const b = await bound(w);
|
|
253
|
-
await pin();
|
|
254
|
-
return at(w.WB, b.inv.heir!, 'word', { signer: b.own, method: 'bad' });
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
|
-
];
|
package/src/vector/index.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// The vector shelf: the world the door's cases are taken in, the cases as
|
|
3
|
-
// records, and the stand a verifier replays them against. `door.json` is
|
|
4
|
-
// written from `doorCases` by a hand and held to by the suite; `Stand` is
|
|
5
|
-
// vector mode, the spec's chapter of that name.
|
|
6
|
-
export { splitmix64, STREAM_SEED, rnd, must, P, B, SILENT, WORD, said, secret, snap, heardBy, partitionDigest, Host, Caller, classes, world, sealed, hand, bound } from './world.ts';
|
|
7
|
-
export type { World, Door, Hand, Sealed } from './world.ts';
|
|
8
|
-
export { cases, caseNamed, stand, doorCases } from './cases.ts';
|
|
9
|
-
export type { DoorRecord, Pin, Arrival, Case } from './cases.ts';
|
|
10
|
-
export { Stand } from './stand.ts';
|
|
11
|
-
export type { Answer } from './stand.ts';
|
package/src/vector/stand.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// Vector mode: the harness the spec's chapter of that name describes, as one
|
|
3
|
-
// handler that names no runtime. A listener on any terrain turns its own
|
|
4
|
-
// request into `(method, path, body)` and writes the answer back; on Node
|
|
5
|
-
// that is `test/stand.ts`. What the handler does with an ask is what the
|
|
6
|
-
// request reach does everywhere: post the bytes to `<url>/<pk>`, get the
|
|
7
|
-
// reply bytes back. Beside it, two routes that exist only here: `stand`
|
|
8
|
-
// puts the world in the state of one record of `door.json` and `digest`
|
|
9
|
-
// reads the judged ward's partition as one value.
|
|
10
|
-
//
|
|
11
|
-
// Every answer carries `access-control-allow-origin: *` and an OPTIONS is
|
|
12
|
-
// answered, because a verifier is a page in a stranger's tab as often as a
|
|
13
|
-
// shell, and a stand that only its own origin can reach passes from one and
|
|
14
|
-
// fails from the other while the door is the same.
|
|
15
|
-
//
|
|
16
|
-
// Nothing here is reachable from a harbor that is not in the mode. This file
|
|
17
|
-
// is imported by a hand that stands a kit for a verifier, and by nothing a
|
|
18
|
-
// world runs on.
|
|
19
|
-
import { SUITE } from '../harbor/reach.ts';
|
|
20
|
-
import { hex } from '../ward/arithmetic.ts';
|
|
21
|
-
import { readJson } from '../ward/json.ts';
|
|
22
|
-
import { caseNamed, stand } from './cases.ts';
|
|
23
|
-
import type { World } from './world.ts';
|
|
24
|
-
import { partitionDigest } from './world.ts';
|
|
25
|
-
|
|
26
|
-
export type Answer = { status: number; headers: Record<string, string>; body: Uint8Array };
|
|
27
|
-
|
|
28
|
-
const CORS = {
|
|
29
|
-
'access-control-allow-origin': '*',
|
|
30
|
-
'access-control-allow-methods': 'GET, POST, OPTIONS',
|
|
31
|
-
'access-control-allow-headers': 'content-type, quo-suite',
|
|
32
|
-
};
|
|
33
|
-
const text = (s: string) => new TextEncoder().encode(s);
|
|
34
|
-
const json = (status: number, v: unknown): Answer => ({ status, headers: { ...CORS, 'content-type': 'application/json' }, body: text(JSON.stringify(v)) });
|
|
35
|
-
const bytes = (b: Uint8Array): Answer => ({ status: 200, headers: { ...CORS, 'content-type': 'application/octet-stream' }, body: b });
|
|
36
|
-
const NONE: Answer = { status: 404, headers: CORS, body: new Uint8Array(0) };
|
|
37
|
-
|
|
38
|
-
export class Stand {
|
|
39
|
-
#w: World | null = null;
|
|
40
|
-
#ward = '';
|
|
41
|
-
|
|
42
|
-
async handle(method: string, path: string, body: Uint8Array, headers: Record<string, string | undefined> = {}): Promise<Answer> {
|
|
43
|
-
const p = path.replace(/\/$/, '');
|
|
44
|
-
if (method === 'OPTIONS') return { status: 204, headers: CORS, body: new Uint8Array(0) };
|
|
45
|
-
if (method === 'POST' && p === '/stand') {
|
|
46
|
-
let named: { case?: unknown; name?: unknown };
|
|
47
|
-
try {
|
|
48
|
-
named = readJson(body) as typeof named;
|
|
49
|
-
} catch {
|
|
50
|
-
return NONE;
|
|
51
|
-
}
|
|
52
|
-
const c = typeof named.case === 'string' && typeof named.name === 'string' ? caseNamed(named.case, named.name) : undefined;
|
|
53
|
-
if (!c) return NONE;
|
|
54
|
-
const stood = await stand(c);
|
|
55
|
-
this.#w = stood.w;
|
|
56
|
-
this.#ward = c.ward;
|
|
57
|
-
// What is returned is hex, the corpus's own encoding, so a verifier
|
|
58
|
-
// compares strings and never decodes anything.
|
|
59
|
-
return json(200, { ward: stood.arrival.door.pk, before: stood.before, ask: hex(stood.arrival.ask.bytes) });
|
|
60
|
-
}
|
|
61
|
-
if (method === 'GET' && p === '/digest') {
|
|
62
|
-
if (!this.#w) return NONE;
|
|
63
|
-
return json(200, { after: await partitionDigest(this.#w.b, this.#ward) });
|
|
64
|
-
}
|
|
65
|
-
if (method === 'POST' && /^\/[0-9a-f]{128}$/.test(p)) {
|
|
66
|
-
const suite = headers['quo-suite'];
|
|
67
|
-
if (suite !== undefined && suite !== String(SUITE)) return NONE; // a suite this door does not speak: nothing delivered
|
|
68
|
-
if (!this.#w) return NONE;
|
|
69
|
-
const pk = p.slice(1);
|
|
70
|
-
const door = this.#w.b.doors.get(pk) ?? this.#w.a.doors.get(pk);
|
|
71
|
-
if (!door) return NONE;
|
|
72
|
-
return bytes((await door(body)).bytes);
|
|
73
|
-
}
|
|
74
|
-
return NONE;
|
|
75
|
-
}
|
|
76
|
-
}
|
package/src/vector/world.ts
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// The world the door's cases are proven in. Two harbors linked as a wire.
|
|
3
|
-
// Alice, a caller, on ward A of harbor a. A host on ward B of harbor b, with
|
|
4
|
-
// an ask for every way of not answering. A public host on ward P of harbor
|
|
5
|
-
// b. And a hand that seals bytes exactly as a ward would, for the cases only
|
|
6
|
-
// bytes can reach.
|
|
7
|
-
//
|
|
8
|
-
// Nothing here draws from the device. A door's reply is sealed under a fresh
|
|
9
|
-
// ephemeral key and a fresh nonce, so the bytes are fixed only where the
|
|
10
|
-
// entropy is, and every draw in this file comes from one written-down stream.
|
|
11
|
-
// That is what lets `quo/vectors/door.json` be a corpus and not a
|
|
12
|
-
// sample, and what lets a kit stand in vector mode for a verifier that holds
|
|
13
|
-
// no key: the spec's chapter of that name is the contract this file keeps.
|
|
14
|
-
import { MemoryHarbor } from '../harbor/memory.ts';
|
|
15
|
-
import { Ward } from '../ward/ward.ts';
|
|
16
|
-
import * as seal from '../ward/seal.ts';
|
|
17
|
-
import { unhex } from '../ward/arithmetic.ts';
|
|
18
|
-
import type { Partition } from '../ward/partition.ts';
|
|
19
|
-
import { Being } from '../being/being.ts';
|
|
20
|
-
import { silence, unreached, isSilence, isUnreached, told } from '../being/silence.ts';
|
|
21
|
-
import { digest } from '../being/digest.ts';
|
|
22
|
-
import type { Answer, Asker, BeingClass, Blueprint, Invitation, Json, JsonObject, Reply, Wanted } from '../being/types.ts';
|
|
23
|
-
|
|
24
|
-
// ---- the stream
|
|
25
|
-
//
|
|
26
|
-
// SplitMix64, written out in the spec's vector-mode chapter with both
|
|
27
|
-
// multipliers and all three shifts, because a kit that reproduces it by
|
|
28
|
-
// recognising it is a kit that guessed. This is that, and it moves only
|
|
29
|
-
// when that does. It is
|
|
30
|
-
// not a cipher and is not meant to be one. Its whole job is that the bytes
|
|
31
|
-
// this file produces are the same bytes twice.
|
|
32
|
-
const GAMMA = 0x9e3779b97f4a7c15n;
|
|
33
|
-
const M64 = (1n << 64n) - 1n;
|
|
34
|
-
export function splitmix64(seed: bigint): () => Uint8Array {
|
|
35
|
-
let s = seed & M64;
|
|
36
|
-
return () => {
|
|
37
|
-
s = (s + GAMMA) & M64;
|
|
38
|
-
let z = s;
|
|
39
|
-
z = ((z ^ (z >> 30n)) * 0xbf58476d1ce4e5b9n) & M64;
|
|
40
|
-
z = ((z ^ (z >> 27n)) * 0x94d049bb133111ebn) & M64;
|
|
41
|
-
z = z ^ (z >> 31n);
|
|
42
|
-
const out = new Uint8Array(8);
|
|
43
|
-
for (let i = 0; i < 8; i++) out[i] = Number((z >> BigInt(8 * i)) & 0xffn);
|
|
44
|
-
return out;
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// The one seed every case stands on, and the first number a kit sets its own
|
|
49
|
-
// stream to when it reproduces `door.json`.
|
|
50
|
-
export const STREAM_SEED = 20250901n;
|
|
51
|
-
|
|
52
|
-
let draw = splitmix64(STREAM_SEED);
|
|
53
|
-
// How many sixty-four bit words have been spent since the stream was reset.
|
|
54
|
-
// A record carries this number at the moment of its arrival, so a kit that
|
|
55
|
-
// built the door's state its own way sets its stream here and draws what
|
|
56
|
-
// this one would have drawn. It is the whole of what the setup's history is
|
|
57
|
-
// worth to a stranger: not how the world was made, but where the stream
|
|
58
|
-
// stands when the door is about to answer.
|
|
59
|
-
let spent = 0;
|
|
60
|
-
export const drawn = (): number => spent;
|
|
61
|
-
// Entropy, from the stream and never from the device. Every signer secret,
|
|
62
|
-
// every ephemeral seed, every byte of noise and every key a ward in this
|
|
63
|
-
// world mints comes through here, in the order the code asks for it.
|
|
64
|
-
export const rnd = (n = 32): Uint8Array => {
|
|
65
|
-
const out = new Uint8Array(n);
|
|
66
|
-
for (let i = 0; i < n; i += 8) {
|
|
67
|
-
out.set(draw().subarray(0, Math.min(8, n - i)), i);
|
|
68
|
-
spent += 1;
|
|
69
|
-
}
|
|
70
|
-
return out;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// A claim the world stands on. It throws, because a world that is not the
|
|
74
|
-
// one the corpus was taken in cannot produce the corpus, and the sooner that
|
|
75
|
-
// is said the nearer the fault is named.
|
|
76
|
-
export const must = (ok: boolean, what: string): void => {
|
|
77
|
-
if (!ok) throw new Error(what);
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export const P = (h: MemoryHarbor, seed: string) => h.partitions.get(seed) as unknown as Partition;
|
|
81
|
-
export const B = <T>(h: MemoryHarbor, seed: string, key: string) => h.objects.get(P(h, seed).beings[key]) as T;
|
|
82
|
-
export const SILENT = { silence: true };
|
|
83
|
-
export const WORD = (quo: string) => ({ quo });
|
|
84
|
-
// Her ward's word for what happened, as she receives it. `told` names the
|
|
85
|
-
// word and leaves everything else alone, so an assertion that goes red says
|
|
86
|
-
// which answer arrived instead of collapsing to false.
|
|
87
|
-
export const said = (x: unknown, w: string): boolean => told(x) === w;
|
|
88
|
-
export const secret = (inv: Invitation) => unhex(inv.secret!);
|
|
89
|
-
// A snapshot that survives the guard on cells: values only, which is all a partition holds.
|
|
90
|
-
export const snap = <T>(v: T): T => JSON.parse(JSON.stringify(v)) as T;
|
|
91
|
-
export const heardBy = (h: Host) => (h.cells.heard as JsonObject[]).length;
|
|
92
|
-
// What a partition is, as one value a kit compares. `nothing written` is this
|
|
93
|
-
// number before and after, and never a sentence a reader has to trust.
|
|
94
|
-
export const partitionDigest = (h: MemoryHarbor, seed: string): Promise<string> => digest(snap(P(h, seed)));
|
|
95
|
-
|
|
96
|
-
// A host. One ask per way of not answering, and a describe that can be told
|
|
97
|
-
// to throw or fall silent, so the empty ask has its cases too.
|
|
98
|
-
export class Host extends Being {
|
|
99
|
-
static override cells = { heard: [] as JsonObject[], describe: 'ok' as string, minted: 0 };
|
|
100
|
-
static override asks = {
|
|
101
|
-
hello: { input: { type: 'object' } },
|
|
102
|
-
err: { input: { type: 'object' } },
|
|
103
|
-
nul: { input: { type: 'object' } },
|
|
104
|
-
quiet: { input: { type: 'object' } },
|
|
105
|
-
boom: { input: { type: 'object' } },
|
|
106
|
-
bad: { input: { type: 'object' } },
|
|
107
|
-
never: { input: { type: 'object' } },
|
|
108
|
-
hidden: { input: { type: 'object' }, for: () => false },
|
|
109
|
-
};
|
|
110
|
-
hello(args: JsonObject, asker: Asker) {
|
|
111
|
-
(this.cells.heard as JsonObject[]).push({ id: asker.id ?? null, ...args });
|
|
112
|
-
return { hi: asker.id ?? null };
|
|
113
|
-
}
|
|
114
|
-
err() {
|
|
115
|
-
return { error: 'nope' };
|
|
116
|
-
}
|
|
117
|
-
nul() {
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
quiet() {
|
|
121
|
-
return silence;
|
|
122
|
-
}
|
|
123
|
-
boom(): never {
|
|
124
|
-
throw new Error('she threw');
|
|
125
|
-
}
|
|
126
|
-
bad(): Reply {
|
|
127
|
-
return unreached(); // a shape that is not hers to make: her ward reads it as silence
|
|
128
|
-
}
|
|
129
|
-
never(): Promise<Reply> {
|
|
130
|
-
return new Promise(() => {});
|
|
131
|
-
}
|
|
132
|
-
hidden() {
|
|
133
|
-
return { secret: true };
|
|
134
|
-
}
|
|
135
|
-
override describe(asker: Asker): Blueprint {
|
|
136
|
-
if (this.cells.describe === 'throw') throw new Error('not now');
|
|
137
|
-
if (this.cells.describe === 'silent') return silence as unknown as Blueprint;
|
|
138
|
-
return super.describe(asker);
|
|
139
|
-
}
|
|
140
|
-
async welcome(id?: string): Promise<Invitation> {
|
|
141
|
-
return (await this.invite(id ?? `g${++(this.cells.minted as number)}`))!;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// A caller. Knocks, takes, asks, and hands back exactly what her ward said.
|
|
146
|
-
export class Caller extends Being {
|
|
147
|
-
static override asks = { hi: { input: { type: 'object' } } };
|
|
148
|
-
hi() {
|
|
149
|
-
return { hi: true };
|
|
150
|
-
}
|
|
151
|
-
async join(id: string, inv: Invitation): Promise<Answer> {
|
|
152
|
-
const out = await this.knock(inv, 'hello', { via: 'knock' });
|
|
153
|
-
if (!isSilence(out) && !isUnreached(out)) must((await this.take(id, inv)) === id, `take ${id}`);
|
|
154
|
-
return out;
|
|
155
|
-
}
|
|
156
|
-
ask(id: string, method?: string, args: JsonObject = {}, wanted?: Wanted): Promise<Answer> {
|
|
157
|
-
return this.standings[id]!.ask(method, args, wanted);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
export const classes: Record<string, BeingClass> = { Host, Caller };
|
|
161
|
-
|
|
162
|
-
// The number a hand's asks count from. Reset with the stream, so a case seals
|
|
163
|
-
// the same seq every run and not merely a fresh one.
|
|
164
|
-
let counted = 1000;
|
|
165
|
-
|
|
166
|
-
export async function world() {
|
|
167
|
-
draw = splitmix64(STREAM_SEED); // a fresh world is a fresh stream: two runs, one corpus
|
|
168
|
-
spent = 0;
|
|
169
|
-
counted = 1000;
|
|
170
|
-
const a = new MemoryHarbor(),
|
|
171
|
-
b = new MemoryHarbor();
|
|
172
|
-
a.random = rnd;
|
|
173
|
-
b.random = rnd;
|
|
174
|
-
a.link(b);
|
|
175
|
-
const WA = await a.boot('A', Ward, classes);
|
|
176
|
-
const WB = await b.boot('B', Ward, classes);
|
|
177
|
-
const WP = await b.boot('P', Ward, classes);
|
|
178
|
-
await WA.ask('boot', { key: 'alice', class: 'Caller' });
|
|
179
|
-
await WB.ask('boot', { key: 'host', class: 'Host' });
|
|
180
|
-
await WP.ask('boot', { key: 'pub', class: 'Host' });
|
|
181
|
-
await WP.ask('public', { key: 'pub' });
|
|
182
|
-
const alice = B<Caller>(a, 'A', 'alice');
|
|
183
|
-
const host = B<Host>(b, 'B', 'host');
|
|
184
|
-
const pub = B<Host>(b, 'P', 'pub');
|
|
185
|
-
const inv = await host.welcome('alice');
|
|
186
|
-
const joined = await alice.join('host', inv);
|
|
187
|
-
must(JSON.stringify(joined) === JSON.stringify({ hi: 'alice' }), 'alice joins the host');
|
|
188
|
-
const heir = () => P(b, 'B').heirs[inv.heir!];
|
|
189
|
-
const alicesSeq = () => P(a, 'A').bind.alice.standings.host.seq;
|
|
190
|
-
return { a, b, WA, WB, WP, alice, host, pub, inv, heir, alicesSeq };
|
|
191
|
-
}
|
|
192
|
-
export type World = Awaited<ReturnType<typeof world>>;
|
|
193
|
-
|
|
194
|
-
// What a door answers: the bytes, and whether a key it holds spoke.
|
|
195
|
-
export type Door = (b: Uint8Array) => Promise<{ bytes: Uint8Array; heard: boolean }>;
|
|
196
|
-
|
|
197
|
-
// A hand at a door, in two halves. `sealed` is the ask as a ward would seal
|
|
198
|
-
// it, with the lid kept so the reply can be opened; `hand` is that and the
|
|
199
|
-
// arrival in one. The halves are apart because vector mode stands a world
|
|
200
|
-
// with the ask sealed and the door not yet knocked on: the verifier brings
|
|
201
|
-
// the bytes, and the stream must already have drawn what the hand draws.
|
|
202
|
-
export type Hand = Partial<seal.AskPayload> & { signer?: Uint8Array; padlock?: Uint8Array };
|
|
203
|
-
export type Sealed = {
|
|
204
|
-
bytes: Uint8Array; // the ask, exactly as it will arrive
|
|
205
|
-
open(out: Uint8Array): Promise<seal.ReplyPayload | null>; // what a reply opens to, where the hand holds the lid
|
|
206
|
-
};
|
|
207
|
-
export async function sealed(wardPk: string, to: string | null, h: Hand = {}): Promise<Sealed> {
|
|
208
|
-
const signer = h.signer ?? rnd();
|
|
209
|
-
const by = h.by ?? (await seal.beingKey(signer)).pk;
|
|
210
|
-
const payload: seal.AskPayload = { by, next: null, seq: ++counted, time: 30_000, method: 'hello', args: {}, ...h };
|
|
211
|
-
delete (payload as Hand).signer;
|
|
212
|
-
delete (payload as Hand).padlock;
|
|
213
|
-
const { bytes, ephemeral } = await seal.sealAsk(to, payload, signer, h.padlock ?? seal.wardPadlock(wardPk), rnd());
|
|
214
|
-
return { bytes, open: (out) => seal.openReply(out, ephemeral, seal.wardSignPk(wardPk)) };
|
|
215
|
-
}
|
|
216
|
-
export async function hand(door: Door, wardPk: string, to: string | null, h: Hand = {}) {
|
|
217
|
-
const s = await sealed(wardPk, to, h);
|
|
218
|
-
const judged = await door(s.bytes);
|
|
219
|
-
const out = judged.bytes;
|
|
220
|
-
return { bytes: s.bytes, out, heard: judged.heard, reply: await s.open(out), again: async () => s.open((await door(s.bytes)).bytes) };
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// A fresh relation on the host, knocked by hand so the test holds the keys.
|
|
224
|
-
export async function bound(w: World) {
|
|
225
|
-
const inv = await w.host.welcome();
|
|
226
|
-
const own = rnd();
|
|
227
|
-
const ownPk = (await seal.beingKey(own)).pk;
|
|
228
|
-
const knock = await hand(w.WB.door, w.WB.pk, inv.heir!, { signer: secret(inv), next: ownPk });
|
|
229
|
-
const minted = w.host.cells.minted as number;
|
|
230
|
-
must(JSON.stringify((knock.reply as { object: Json }).object) === JSON.stringify({ hi: `g${minted}` }), `bound g${minted}`);
|
|
231
|
-
return { inv, own, ownPk, rec: () => P(w.b, 'B').heirs[inv.heir!] };
|
|
232
|
-
}
|