@quo-systems/quo 0.1.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/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +91 -0
- package/SPEC.md +1595 -0
- package/package.json +64 -0
- package/src/being/being.ts +92 -0
- package/src/being/digest.ts +31 -0
- package/src/being/index.ts +7 -0
- package/src/being/silence.ts +14 -0
- package/src/being/types.ts +88 -0
- package/src/conformance/assert.ts +67 -0
- package/src/conformance/beings.ts +129 -0
- package/src/conformance/estate.ts +339 -0
- package/src/conformance/index.ts +516 -0
- package/src/conformance/reach.ts +83 -0
- package/src/conformance/store.ts +109 -0
- package/src/harbor/core.ts +178 -0
- package/src/harbor/dial.ts +61 -0
- package/src/harbor/index.ts +11 -0
- package/src/harbor/memory.ts +74 -0
- package/src/harbor/reach.ts +189 -0
- package/src/harbor/store.ts +69 -0
- package/src/ward/allowance.ts +74 -0
- package/src/ward/arithmetic.ts +161 -0
- package/src/ward/cells.ts +90 -0
- package/src/ward/door.ts +102 -0
- package/src/ward/ground.ts +35 -0
- package/src/ward/heirs.ts +87 -0
- package/src/ward/index.ts +14 -0
- package/src/ward/owner.ts +122 -0
- package/src/ward/partition.ts +123 -0
- package/src/ward/seal.ts +133 -0
- package/src/ward/stance.ts +264 -0
- package/src/ward/ward.ts +209 -0
- package/vectors/arithmetic.json +110 -0
- package/vectors/framing.json +71 -0
- package/vectors/wire.json +48 -0
package/src/ward/ward.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// A ward that is its own ward. Booted by a harbor with one object, the
|
|
3
|
+
// ground, and hands back two pointers: the door and the ask. Boots itself as
|
|
4
|
+
// the first being in its own map, and every being it later instantiates gets
|
|
5
|
+
// a stance built by the same code. Mints every key, seals every ask that
|
|
6
|
+
// leaves, opens every one that arrives.
|
|
7
|
+
import { silence, isSilence, unreached, isUnreached } from '../being/silence.ts';
|
|
8
|
+
import { OWNER } from '../being/types.ts';
|
|
9
|
+
import type { Asker, BeingLike, Cells, JsonObject, Reply, Stance, Wanted } from '../being/types.ts';
|
|
10
|
+
import type { Ground, WardPointers } from './ground.ts';
|
|
11
|
+
import { at, open, put, emptyBind, emptyCells, type Bind, type Partition, type StandingKeys } from './partition.ts';
|
|
12
|
+
import { Heirs } from './heirs.ts';
|
|
13
|
+
import { makeDoor, type Door } from './door.ts';
|
|
14
|
+
import { buildStance } from './stance.ts';
|
|
15
|
+
import { ownerAnswer } from './owner.ts';
|
|
16
|
+
import { allow, within, LATE } from './allowance.ts';
|
|
17
|
+
import { beingKey, openReply, sealAsk, wardKey, wardPadlock, wardSignPk, type ReplyPayload, type WardKey } from './seal.ts';
|
|
18
|
+
import { unhex } from './arithmetic.ts';
|
|
19
|
+
import { guardCells, cellFault } from './cells.ts';
|
|
20
|
+
|
|
21
|
+
type Booted = Door & { bind: Bind; stance: Stance; cells: Cells & { class?: string } };
|
|
22
|
+
|
|
23
|
+
// How many minted pks she keeps behind her. Enough to name the keys in play.
|
|
24
|
+
const MINTED = 8;
|
|
25
|
+
|
|
26
|
+
// The ward. One call in, two pointers out. Async because keys are derived.
|
|
27
|
+
export async function Ward(ground: Ground): Promise<WardPointers> {
|
|
28
|
+
const w = await Self.boot(ground);
|
|
29
|
+
return { door: w.door, ask: w.ask };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class Self implements BeingLike {
|
|
33
|
+
readonly key: WardKey;
|
|
34
|
+
readonly pk: string;
|
|
35
|
+
readonly g: Ground;
|
|
36
|
+
readonly p: Partition;
|
|
37
|
+
readonly heirs: Heirs;
|
|
38
|
+
readonly doors = new Map<string, Booted>();
|
|
39
|
+
readonly door: WardPointers['door'];
|
|
40
|
+
readonly ask: WardPointers['ask'];
|
|
41
|
+
|
|
42
|
+
static async boot(ground: Ground): Promise<Self> {
|
|
43
|
+
return new Self(ground, await wardKey(ground.seed));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private constructor(ground: Ground, key: WardKey) {
|
|
47
|
+
this.g = ground;
|
|
48
|
+
this.key = key;
|
|
49
|
+
this.pk = key.pk;
|
|
50
|
+
this.p = open(ground.memory); // the partition IS the ward's cells
|
|
51
|
+
this.heirs = new Heirs(this.p);
|
|
52
|
+
this.door = makeDoor(key, this.heirs, this.doors, () => this.p.public, (n) => ground.random(n)); // pointer one
|
|
53
|
+
this.#boot(this.pk, this); // the ward's ward is itself
|
|
54
|
+
// a restart is silent: every being in the cells is constructed again, unasked.
|
|
55
|
+
// A throw on a boot the owner asked for is that boot's answer: silence,
|
|
56
|
+
// and nothing half-lives. This boot nobody asked for, and there is nobody
|
|
57
|
+
// to hand a silence to. Any constructor may throw — a default that is not
|
|
58
|
+
// a value, an author's mistake, a shape she no longer recognises after an
|
|
59
|
+
// upgrade — and let through here it is one being taking down the ward and
|
|
60
|
+
// every other being in it, on a restart. She is absent instead: no door,
|
|
61
|
+
// so silence at the door, and her cells sit untouched, waiting for the
|
|
62
|
+
// run that can read them.
|
|
63
|
+
for (const [k, c] of Object.entries(this.p.beings)) {
|
|
64
|
+
if (k === this.pk || !c.class) continue;
|
|
65
|
+
try {
|
|
66
|
+
this.#instantiate(k, c.class);
|
|
67
|
+
} catch {
|
|
68
|
+
/* she is not here this run. the ward is, and so is everyone else. */
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// pointer two. the owner. the same answer every named asker gets, asker filled in as owner.
|
|
72
|
+
this.ask = async (method, args = {}) => {
|
|
73
|
+
try {
|
|
74
|
+
const out = await this.answer({ id: OWNER }, method, args as JsonObject);
|
|
75
|
+
return isSilence(out) || isUnreached(out) ? silence : out;
|
|
76
|
+
} catch {
|
|
77
|
+
return silence;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---- the ward as a being. asked only by its owners: the root, through
|
|
83
|
+
// the ask pointer, and its occupants, through the door, whom only the root
|
|
84
|
+
// can invite. A stranger cannot be a named asker here, because the door
|
|
85
|
+
// names nobody the ward did not bind.
|
|
86
|
+
async answer(asker: Asker, method?: string, args: JsonObject = {}): Promise<Reply> {
|
|
87
|
+
if (asker.id === undefined) return { error: 'unknown ask' };
|
|
88
|
+
return ownerAnswer(
|
|
89
|
+
{
|
|
90
|
+
pk: this.pk,
|
|
91
|
+
doors: this.doors,
|
|
92
|
+
publicKey: () => this.p.public,
|
|
93
|
+
instantiate: (key, className, isPublic) => this.#instantiate(key, className, isPublic),
|
|
94
|
+
},
|
|
95
|
+
asker,
|
|
96
|
+
method,
|
|
97
|
+
args,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---- ward functions. on the object. no stance reaches them.
|
|
102
|
+
#instantiate(key: string, className: string, isPublic = false): Booted | null {
|
|
103
|
+
if (!key || this.doors.has(key)) return null;
|
|
104
|
+
const door = this.#boot(key, className);
|
|
105
|
+
if (!door) return null;
|
|
106
|
+
door.cells.class = className; // so a restart finds her
|
|
107
|
+
if (isPublic) this.p.public = key;
|
|
108
|
+
return door;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Nothing is written until there is somebody to write it for: a class the
|
|
112
|
+
// harbor does not know makes no being, and a boot that made nobody must
|
|
113
|
+
// leave the partition as it found it.
|
|
114
|
+
#boot(key: string, classNameOrSelf: string | Self): Booted | null {
|
|
115
|
+
const cells = guardCells(at(this.p.beings, key) ?? emptyCells());
|
|
116
|
+
const bind = at(this.p.bind, key) ?? emptyBind();
|
|
117
|
+
const stance = buildStance(
|
|
118
|
+
{
|
|
119
|
+
pk: this.pk,
|
|
120
|
+
mintKey: (b) => this.#mintKey(b),
|
|
121
|
+
openHeir: (heir, being, id) => this.heirs.open(heir, being, id),
|
|
122
|
+
closeHeir: (heir) => this.heirs.close(heir),
|
|
123
|
+
send: (b, keys, announce, method, args, wanted) => this.#send(b, keys, announce, method, args, wanted),
|
|
124
|
+
},
|
|
125
|
+
key,
|
|
126
|
+
cells,
|
|
127
|
+
bind,
|
|
128
|
+
);
|
|
129
|
+
const being = classNameOrSelf === this ? this : this.g.instantiate(classNameOrSelf as string, stance);
|
|
130
|
+
if (!being) return null;
|
|
131
|
+
put(this.p.beings, key, cells);
|
|
132
|
+
put(this.p.bind, key, bind);
|
|
133
|
+
const door: Booted = { key, cells, bind, stance, being };
|
|
134
|
+
this.doors.set(key, door);
|
|
135
|
+
return door;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async #mintKey(bind: Bind): Promise<{ seed: string; pk: string }> {
|
|
139
|
+
const k = await beingKey(this.g.random(32));
|
|
140
|
+
// The last few she minted, and no more. A relation rotates on every ask,
|
|
141
|
+
// so a list of all of them is a partition that grows for as long as she
|
|
142
|
+
// keeps talking and can never be written down. Nothing is read from this
|
|
143
|
+
// but the keys of the moment.
|
|
144
|
+
bind.minted.push(k.pk);
|
|
145
|
+
if (bind.minted.length > MINTED) bind.minted.splice(0, bind.minted.length - MINTED);
|
|
146
|
+
return k;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// One send for every door, the ward's own included. Mine: never leaves.
|
|
150
|
+
// Not mine: carry. Signs as the standing's current key, announces its
|
|
151
|
+
// next, seals to the far ward, opens the reply with the ephemeral secret,
|
|
152
|
+
// and rotates to the announced key once the far door has answered under
|
|
153
|
+
// the current one.
|
|
154
|
+
async #send(bind: Bind, keys: StandingKeys, announce: boolean, method: string | undefined, args: JsonObject, wanted?: Wanted): Promise<ReplyPayload | typeof silence | ReturnType<typeof unreached>> {
|
|
155
|
+
// What she asked for, held to what this ward allows. Asking for nothing is
|
|
156
|
+
// the default, and asking for more than the ceiling is the ceiling: budget
|
|
157
|
+
// is granted by a ward, never minted by a being.
|
|
158
|
+
const budget = allow(wanted);
|
|
159
|
+
const by = (await beingKey(unhex(keys.current))).pk;
|
|
160
|
+
// A public being is reached without a heir, and the door keeps nothing for
|
|
161
|
+
// whoever asked: no key it vouched for, so nothing to announce and nothing
|
|
162
|
+
// to rotate to. A standing on her signs with one key for life.
|
|
163
|
+
if (announce && keys.next === null && keys.heir !== null) keys.next = (await this.#mintKey(bind)).seed;
|
|
164
|
+
const next = keys.next === null ? null : (await beingKey(unhex(keys.next))).pk;
|
|
165
|
+
// Her count for this relation, one higher every call and never reused. The
|
|
166
|
+
// far door honours each number once. One relation sends one at a time, so
|
|
167
|
+
// the numbers leave in the order they were taken.
|
|
168
|
+
const seq = (keys.seq += 1);
|
|
169
|
+
// Args that will not go through the seal are args that never left. A
|
|
170
|
+
// being holds three answers and a throw is not one of them, so an ask
|
|
171
|
+
// that cannot be sealed is told what is true of it: no door was reached.
|
|
172
|
+
// Values only, by the same rule her cells keep: a Date would arrive as a
|
|
173
|
+
// string, a function would not arrive at all, and she would never know
|
|
174
|
+
// the far being was asked something other than what she said.
|
|
175
|
+
if (cellFault(args, 'args') !== null) return unreached();
|
|
176
|
+
let sealed;
|
|
177
|
+
try {
|
|
178
|
+
sealed = await sealAsk(keys.heir, { by, next, seq, time: budget.time, method, args }, unhex(keys.current), wardPadlock(keys.ward), this.g.random(32));
|
|
179
|
+
} catch {
|
|
180
|
+
return unreached();
|
|
181
|
+
}
|
|
182
|
+
const { bytes, ephemeral } = sealed;
|
|
183
|
+
// The wait is bounded, and this is the one thing the ward times. A being
|
|
184
|
+
// holds three answers and a wait that does not end is none of them: a
|
|
185
|
+
// relation that comes back round holds a lane the answer needs, and only a
|
|
186
|
+
// bound on the wait can break that. What comes back late is not read.
|
|
187
|
+
//
|
|
188
|
+
// A wait that ran out is silence, never unreached. Unreached promises
|
|
189
|
+
// nothing was delivered and is safe to retry; a bound that expired knows
|
|
190
|
+
// no such thing, because the far door may have heard and be working still.
|
|
191
|
+
//
|
|
192
|
+
// A harbor answers bytes or nothing. One that throws instead has answered
|
|
193
|
+
// nothing in a louder voice, and is read as nothing: no door was reached.
|
|
194
|
+
const carried = (keys.ward === this.pk ? this.door(new Uint8Array(bytes)) : this.g.carry(keys.ward, bytes)).then(
|
|
195
|
+
(b) => b,
|
|
196
|
+
() => undefined,
|
|
197
|
+
);
|
|
198
|
+
const out = await within(budget.time, carried);
|
|
199
|
+
if (out === LATE) return silence;
|
|
200
|
+
if (out === undefined) return unreached(); // nothing came back. no door was reached.
|
|
201
|
+
const reply = await openReply(out, ephemeral, wardSignPk(keys.ward));
|
|
202
|
+
if (!reply) return silence; // did not open, or not from that ward
|
|
203
|
+
if (!('silence' in reply) && keys.next !== null) {
|
|
204
|
+
keys.current = keys.next; // the far door holds `next` as announced. move to it.
|
|
205
|
+
keys.next = null;
|
|
206
|
+
}
|
|
207
|
+
return reply;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"corpus": "@quo-systems/quo",
|
|
3
|
+
"encoding": "hex",
|
|
4
|
+
"area": "arithmetic",
|
|
5
|
+
"vectors": [
|
|
6
|
+
{
|
|
7
|
+
"name": "SHA-256 of nothing",
|
|
8
|
+
"input": "",
|
|
9
|
+
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "SHA-256 of three bytes",
|
|
13
|
+
"input": "616263",
|
|
14
|
+
"hash": "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "an Ed25519 pair from a fixed secret",
|
|
18
|
+
"secret": "2ebd74bd564a8771fa628b28308c588588f5c0d0db17ae06b045b896d8218ad7",
|
|
19
|
+
"pk": "be6615086e715c9cea8856a892c7e363cc2401b5edccadb49b347a8bee22c6e2"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "an X25519 pair from a fixed secret",
|
|
23
|
+
"secret": "7dbcdb3088339c02378f46525bff3fe2b84515973e28f5814301a99f386e20b1",
|
|
24
|
+
"pk": "bb0109e1147cb2b716f9407f947b0c47a8c3c18ce00339dc02df47c54fd1e202"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "a signature over nothing",
|
|
28
|
+
"secret": "2ebd74bd564a8771fa628b28308c588588f5c0d0db17ae06b045b896d8218ad7",
|
|
29
|
+
"voice": "be6615086e715c9cea8856a892c7e363cc2401b5edccadb49b347a8bee22c6e2",
|
|
30
|
+
"message": "",
|
|
31
|
+
"signature": "6e5517c3c92afd95e6956d974a2c39045677472c8736cf13f7e400dd9d843d7d161334c55166383bd2f33eea2312953778ecf7180a70868c4802350f1a896006"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "a signature over a message",
|
|
35
|
+
"secret": "2ebd74bd564a8771fa628b28308c588588f5c0d0db17ae06b045b896d8218ad7",
|
|
36
|
+
"voice": "be6615086e715c9cea8856a892c7e363cc2401b5edccadb49b347a8bee22c6e2",
|
|
37
|
+
"message": "62792077686f736520617574686f72697479",
|
|
38
|
+
"signature": "60aa62f7129c8fdeba710872fa10016dae1aa12df0f31969a6baabf60b2ebc3d0822cc9a8b1eb12246076b2eea69d381a03c2f581c05ba6bcca3b4ba80c2f504"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "a signature under the wrong voice does not verify",
|
|
42
|
+
"voice": "efc15f8742c6c2755887d9e83884dd30b69d25548ef299cdd296053be9c0df99",
|
|
43
|
+
"message": "62792077686f736520617574686f72697479",
|
|
44
|
+
"signature": "60aa62f7129c8fdeba710872fa10016dae1aa12df0f31969a6baabf60b2ebc3d0822cc9a8b1eb12246076b2eea69d381a03c2f581c05ba6bcca3b4ba80c2f504",
|
|
45
|
+
"refuses": true
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "a signature over other bytes does not verify",
|
|
49
|
+
"voice": "be6615086e715c9cea8856a892c7e363cc2401b5edccadb49b347a8bee22c6e2",
|
|
50
|
+
"message": "62792077686f736520617574686f7269747920",
|
|
51
|
+
"signature": "60aa62f7129c8fdeba710872fa10016dae1aa12df0f31969a6baabf60b2ebc3d0822cc9a8b1eb12246076b2eea69d381a03c2f581c05ba6bcca3b4ba80c2f504",
|
|
52
|
+
"refuses": true
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "a signature under an all-zero public key is silence",
|
|
56
|
+
"voice": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
57
|
+
"message": "62792077686f736520617574686f72697479",
|
|
58
|
+
"signature": "60aa62f7129c8fdeba710872fa10016dae1aa12df0f31969a6baabf60b2ebc3d0822cc9a8b1eb12246076b2eea69d381a03c2f581c05ba6bcca3b4ba80c2f504",
|
|
59
|
+
"refuses": true
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "a signature under a small-order public key is silence",
|
|
63
|
+
"voice": "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05",
|
|
64
|
+
"message": "62792077686f736520617574686f72697479",
|
|
65
|
+
"signature": "60aa62f7129c8fdeba710872fa10016dae1aa12df0f31969a6baabf60b2ebc3d0822cc9a8b1eb12246076b2eea69d381a03c2f581c05ba6bcca3b4ba80c2f504",
|
|
66
|
+
"refuses": true
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "an X25519 agreement",
|
|
70
|
+
"secret": "fcfabee71b7a33993cca5579e6a273ffd1c62cc1749cbf1c9049f599e44f6477",
|
|
71
|
+
"pk": "bb0109e1147cb2b716f9407f947b0c47a8c3c18ce00339dc02df47c54fd1e202",
|
|
72
|
+
"shared": "3d33d3f6044552ecd30503ba772b3d4f69544da0b3a4a46bfa63b056bb191833"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "the agreement is the same from either side",
|
|
76
|
+
"secret": "7dbcdb3088339c02378f46525bff3fe2b84515973e28f5814301a99f386e20b1",
|
|
77
|
+
"pk": "17ff733c9cffec197551af9aaf710b7b07a4d7aeffc9894d6e89b10df3d2d011",
|
|
78
|
+
"shared": "3d33d3f6044552ecd30503ba772b3d4f69544da0b3a4a46bfa63b056bb191833"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "forty-four bytes drawn under the fixed info, key then nonce",
|
|
82
|
+
"info": "71756f2d7365616c",
|
|
83
|
+
"salt": "",
|
|
84
|
+
"shared": "3d33d3f6044552ecd30503ba772b3d4f69544da0b3a4a46bfa63b056bb191833",
|
|
85
|
+
"key": "c55777b57b5a972030441a03e03f9ed5bfcaadcccb1511c853afcc2547a451f5",
|
|
86
|
+
"nonce": "b3a7c637499e0d3ee8606dc2"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "AES-256-GCM with the ephemeral pk as the additional data",
|
|
90
|
+
"shared": "3d33d3f6044552ecd30503ba772b3d4f69544da0b3a4a46bfa63b056bb191833",
|
|
91
|
+
"additional": "17ff733c9cffec197551af9aaf710b7b07a4d7aeffc9894d6e89b10df3d2d011",
|
|
92
|
+
"plaintext": "62792077686f736520617574686f72697479",
|
|
93
|
+
"ciphertext": "94eaa19e94fa885635d4a863c94240629cce6573a9109c0c445cade99fea551ff366"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "a ciphertext with one byte turned does not open",
|
|
97
|
+
"shared": "3d33d3f6044552ecd30503ba772b3d4f69544da0b3a4a46bfa63b056bb191833",
|
|
98
|
+
"additional": "17ff733c9cffec197551af9aaf710b7b07a4d7aeffc9894d6e89b10df3d2d011",
|
|
99
|
+
"ciphertext": "95eaa19e94fa885635d4a863c94240629cce6573a9109c0c445cade99fea551ff366",
|
|
100
|
+
"refuses": true
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "a ciphertext under other additional data does not open",
|
|
104
|
+
"shared": "3d33d3f6044552ecd30503ba772b3d4f69544da0b3a4a46bfa63b056bb191833",
|
|
105
|
+
"additional": "bb0109e1147cb2b716f9407f947b0c47a8c3c18ce00339dc02df47c54fd1e202",
|
|
106
|
+
"ciphertext": "94eaa19e94fa885635d4a863c94240629cce6573a9109c0c445cade99fea551ff366",
|
|
107
|
+
"refuses": true
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"corpus": "@quo-systems/quo",
|
|
3
|
+
"encoding": "hex",
|
|
4
|
+
"area": "framing",
|
|
5
|
+
"note": "The formats a second kit must reproduce byte for byte: the ward pk, the digest, the signed ask body, the sealed ask, the sealed reply, the invitation and the knock. Every random input is fixed here, so every output is fixed. The ask body is JSON.stringify of the payload, which is not canonical: these vectors pin the key order sealAsk writes, to, by, next, seq, time, method, args; hops is in no vector, because nothing sets it. A kit that emits another order still interoperates, because a door verifies the bytes it received, but it will not reproduce these vectors. An invitation is JSON too, ward, heir, secret, and a heir secret is the seed of a being key. A knock is an ask whose by is the heir pk, signed with the heir secret, whose next is the knocker's own key, and whose to is the heir; its answer is an ordinary reply, and the heir is spent by it. The ward here is the one from the ward pk vector, and the heir secret is the first entropy it draws, so the invitation it mints and the knock its door binds are these bytes.",
|
|
6
|
+
"vectors": [
|
|
7
|
+
{
|
|
8
|
+
"name": "a ward pk from a fixed seed",
|
|
9
|
+
"seed": "1111111111111111111111111111111111111111111111111111111111111111",
|
|
10
|
+
"pk": "d04ab232742bb4ab3a1368bd4615e4e6d0224ab71a016baf8520a332c97787377b4e909bbe7ffe44c465a220037d608ee35897d31ef972f07f74892cb0f73f13",
|
|
11
|
+
"signPk": "d04ab232742bb4ab3a1368bd4615e4e6d0224ab71a016baf8520a332c9778737",
|
|
12
|
+
"padlockPk": "7b4e909bbe7ffe44c465a220037d608ee35897d31ef972f07f74892cb0f73f13"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "a being key from a fixed secret",
|
|
16
|
+
"secret": "2222222222222222222222222222222222222222222222222222222222222222",
|
|
17
|
+
"pk": "a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "the digest is SHA-256 of the canonical form",
|
|
21
|
+
"blueprint": "{\"asks\":{\"hello\":{\"note\":\"z\"}},\"note\":null,\"list\":[1,2]}",
|
|
22
|
+
"canonical": "{\"asks\":{\"hello\":{\"note\":\"z\"}},\"list\":[1,2],\"note\":null}",
|
|
23
|
+
"digest": "269c9e90f39f6fcae9deab916e3a4148788dec5ba0d06023edc8e52180c773db"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "the signed ask body, and its signature",
|
|
27
|
+
"payload": "{\"by\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"next\":null,\"seq\":1,\"time\":30000,\"method\":\"hello\",\"args\":{\"name\":\"quo\"}}",
|
|
28
|
+
"body": "7b226279223a2261303961613566343761363735393830326666393535663864633264326131346135633939643233626539376638363431323766663933383334353561346630222c226e657874223a6e756c6c2c22736571223a312c2274696d65223a33303030302c226d6574686f64223a2268656c6c6f222c2261726773223a7b226e616d65223a2271756f227d7d",
|
|
29
|
+
"secret": "2222222222222222222222222222222222222222222222222222222222222222",
|
|
30
|
+
"signature": "21fae42dc81de4a1983ac9dd457338043e3e69ba76331cb5616f3106e007de228fee0f19c49035b90789799667bc7de4d4212618790b2b449e5a26c299134c0b"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "a sealed ask to a heir: the ephemeral pk, then the box, and nothing outside it",
|
|
34
|
+
"to": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
35
|
+
"ephemeralSeed": "3333333333333333333333333333333333333333333333333333333333333333",
|
|
36
|
+
"bytes": "7b0d47d93427f8311160781c7c733fd89f88970aef490d8aa0ee19a4cb8a1b14ed97840c789fc01f8b362dff17c11793d230bc13c4f56401abbbd7c4d26ac0379164fc07794e29b8c9330328f8ae2b3b6280d1fb1ced6ef44c54478381be4834444d3c7c4c7258d7653f481e9bc5cc78394fc9d6742edef6a13e3273f24ab212205f83ca25640d301110062940e3bf2cde21dd1707111e240a6de0674a98ef941decb3e0112bcb444576485a180437b1d2ddb5e0773a527c12d781bc669572bafe8d0f1c31396b5e25e354309d9cd8584c8eaa7c6b0ffc97ca3963a997a289254e498dc597b196720a0a70860815f474c56aee9e0d6a1bd2949d314819e9ea7565e379f8d4d8f60bc4243b117ba9b39ab74ef949f7c916843da444c5387118697317ab720ffd10e9954db6e766664e4e7f9e9e09f05df2dc1ff7f47a4fbd419964db60f395043f6a3a"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "a sealed ask for nobody: the same shape, with to null inside",
|
|
40
|
+
"to": "",
|
|
41
|
+
"ephemeralSeed": "3333333333333333333333333333333333333333333333333333333333333333",
|
|
42
|
+
"bytes": "7b0d47d93427f8311160781c7c733fd89f88970aef490d8aa0ee19a4cb8a1b14ed97840c789f8c0b863b60bc14d954c89130ed4bc4f53006feedd793843e986ec037fb00211a7dbf9036017bfdfd2b6b378085f944b56ba71e5743dbd7b91163111d6f2a4b7500c6712e1e528c9eda7f395484952f2d92b5e2326b33a71eae02355ccbdd2a331976064b0f7e03e8b87ddb72d402414e4832073bb323488cb3c043b7baa4043a920516305f01574069f2939af9bf2d3f05295edf908316e73499bd7932d14673cfb6e89f68b52b6e7edebb44f36a03b7f4c87b723eb601f2142d97d94ad2ae07b44d1a9ab7f1e0c91bf2e210d66d85a298c9509b39a652868391296be1e21fcc909f97305b"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "the sealed silence reply",
|
|
46
|
+
"reply": "{\"silence\":true}",
|
|
47
|
+
"replySeed": "4444444444444444444444444444444444444444444444444444444444444444",
|
|
48
|
+
"bytes": "ff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b76ec8fe27c6573176a8974f1235379842eeabe08f92c8aae00e383195ff41cbb2124de81119eb83326fbb9b8ab00a149ce41ea21f6c056a324333cc76b82c93ac93fbcc3fc74f57a1bff4a07a0c7824ed5516b7fb8a14006a25f6c28d95e4e64"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "a sealed object reply",
|
|
52
|
+
"reply": "{\"object\":{\"hi\":\"quo\"},\"seen\":null}",
|
|
53
|
+
"replySeed": "4444444444444444444444444444444444444444444444444444444444444444",
|
|
54
|
+
"bytes": "ff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b76ec93e97a657e002d9135a7394f3ec33bcd723c9d8947909f4cc659d8dfeed743f5b64bfa87b4ac79074219c1032037de444400b509f31559331b7995f9dfbadbe7fb0f6d46db2257db70f248b7739b3baa2ca79b07b66b29ea680faf79340f2309163c9e3c65fcdb33297c419d1cd8517647"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "an invitation for a heir: the ward pk, the heir pk, the heir secret, and nothing else",
|
|
58
|
+
"wardSeed": "1111111111111111111111111111111111111111111111111111111111111111",
|
|
59
|
+
"heirSecret": "4444444444444444444444444444444444444444444444444444444444444444",
|
|
60
|
+
"invitation": "{\"ward\":\"d04ab232742bb4ab3a1368bd4615e4e6d0224ab71a016baf8520a332c97787377b4e909bbe7ffe44c465a220037d608ee35897d31ef972f07f74892cb0f73f13\",\"heir\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"secret\":\"4444444444444444444444444444444444444444444444444444444444444444\"}"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "a sealed knock: the first ask under a heir, by the heir, announcing her own key, to the heir",
|
|
64
|
+
"heirSecret": "4444444444444444444444444444444444444444444444444444444444444444",
|
|
65
|
+
"ownSecret": "5555555555555555555555555555555555555555555555555555555555555555",
|
|
66
|
+
"ephemeralSeed": "6666666666666666666666666666666666666666666666666666666666666666",
|
|
67
|
+
"payload": "{\"to\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"by\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"next\":\"c6822637c7d310ec57627be00ba259d253749f4aaf644470cffbe53a35f73242\",\"seq\":1,\"time\":30000,\"method\":\"hello\",\"args\":{\"name\":\"quo\"}}",
|
|
68
|
+
"bytes": "219e4d800da968d2a5fcb009c784f4746c7138edb9ee4844b739e830b05cf424263fd9388d350e13808f15e9297f753dd517db9f8eeca10de0708fd81a3b3c1b5444da304de91ae98fa4b94a62dae0673bdc0a7c6d97e445f8e08c5c3094b9facda3fa828caced7992ed8989f85a05dbe67da08c06b47b4e56174fe60e8169fc56c78d0966281c47c9b5300cca473e57d43e954b9d424ceb5a49eba9a4a79cd8b5973d21cdeb70f35b988314f68dcb5736003eb1a56c9be4087005fb5ae7d719eb39860ffb4220871161b76395054ab09ee5a323b6eea9a01f31d436b2a12983d5c32d887377e9d9ea845ecd4690660336e5e3e34d1c46f7e648e5a0d81f7a28ae2a52cc3021d10fe29de5c67b9f487b42f34a3e0149856ce8ee8cc8a90860daf624e0ffc758cd3600f069218abe5abdaa3041783add308fa4110899d216831e0016f5357a194344eb3551e48f0cb70f86b51fa68815c51660615034d2abdf3078c649bcca47fd769001b31928108e3ec9f0c1108e6ceec9a32c5ddd542c4543a1b8260936b3e5"
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"corpus": "@quo-systems/quo",
|
|
3
|
+
"encoding": "hex",
|
|
4
|
+
"area": "wire",
|
|
5
|
+
"note": "The frames on a socket reach, which a second kit reproduces byte for byte: an ask is one kind byte 00, a four-byte big-endian frame id, the 64-byte ward pk and the sealed bytes; a reply is kind 01, the id and the bytes; nothing delivered is kind 02 and the id alone. The one text frame is JSON, {\"announce\":[pk,...],\"suite\":1}, the ward pks a side holds and the wire suite it speaks, sent when the line opens and whenever the pks change. The suite is which frames these are and which four algorithms seal what they carry; it is one value, never negotiated, and it rides in the handshake and never on an ask, so an ask stays bytes nobody can tell from noise. A side meeting a suite it does not know carries nothing for it and closes with code 4001, which names the refusal so the far side is not left reconnecting without ever being told why. A request reach has no frame: the sealed bytes are the body of a POST to the URL with the pk as its last segment, carrying the suite as the header quo-suite, the sealed reply is the body of a 200, and a 404 is nothing delivered, which is what a door answers a suite it does not speak. The ids here are the first a socket mints, one, and the sealed bytes are stand-ins, since the frame reads nothing.",
|
|
6
|
+
"vectors": [
|
|
7
|
+
{
|
|
8
|
+
"name": "an ask frame: kind, id, pk, bytes",
|
|
9
|
+
"id": 1,
|
|
10
|
+
"pk": "d04ab232742bb4ab3a1368bd4615e4e6d0224ab71a016baf8520a332c97787377b4e909bbe7ffe44c465a220037d608ee35897d31ef972f07f74892cb0f73f13",
|
|
11
|
+
"bytes": "010203",
|
|
12
|
+
"frame": "0000000001d04ab232742bb4ab3a1368bd4615e4e6d0224ab71a016baf8520a332c97787377b4e909bbe7ffe44c465a220037d608ee35897d31ef972f07f74892cb0f73f13010203"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "a reply frame: kind, id, bytes",
|
|
16
|
+
"id": 1,
|
|
17
|
+
"bytes": "2a",
|
|
18
|
+
"frame": "01000000012a"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "a none frame: kind and id, nothing delivered",
|
|
22
|
+
"id": 1,
|
|
23
|
+
"frame": "0200000001"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "the announce frame, text",
|
|
27
|
+
"pks": [
|
|
28
|
+
"d04ab232742bb4ab3a1368bd4615e4e6d0224ab71a016baf8520a332c97787377b4e909bbe7ffe44c465a220037d608ee35897d31ef972f07f74892cb0f73f13"
|
|
29
|
+
],
|
|
30
|
+
"frame": "{\"announce\":[\"d04ab232742bb4ab3a1368bd4615e4e6d0224ab71a016baf8520a332c97787377b4e909bbe7ffe44c465a220037d608ee35897d31ef972f07f74892cb0f73f13\"],\"suite\":1}",
|
|
31
|
+
"suite": 1
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "the request door: no frame, one POST, the suite in a header, 200 and 404",
|
|
35
|
+
"method": "POST",
|
|
36
|
+
"url": "<base>/<pk>",
|
|
37
|
+
"headers": {
|
|
38
|
+
"content-type": "application/octet-stream",
|
|
39
|
+
"quo-suite": "1"
|
|
40
|
+
},
|
|
41
|
+
"body": "the sealed ask bytes",
|
|
42
|
+
"replies": {
|
|
43
|
+
"200": "the sealed reply bytes",
|
|
44
|
+
"404": "nothing delivered"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|