nervur 0.12.0 → 0.13.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/GETTING-STARTED.md +111 -0
- package/README.md +44 -18
- package/SECURITY.md +11 -0
- package/being.mjs +73 -33
- package/blueprint.mjs +13 -13
- package/client.mjs +9 -6
- package/contract.mjs +6 -4
- package/ground.mjs +95 -36
- package/index.mjs +2 -2
- package/modules.mjs +13 -70
- package/package.json +5 -2
- package/tools.mjs +67 -48
- package/voice.mjs +49 -63
package/ground.mjs
CHANGED
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
import { ADMIN } from './admin.mjs';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Being,
|
|
4
|
+
Compartment,
|
|
5
|
+
Registry,
|
|
6
|
+
SUCCESSION,
|
|
7
|
+
make,
|
|
8
|
+
rootFor,
|
|
9
|
+
spend,
|
|
10
|
+
successionOf,
|
|
11
|
+
} from './being.mjs';
|
|
3
12
|
import { Blueprint, composed, coreClass } from './blueprint.mjs';
|
|
4
13
|
import { Client } from './client.mjs';
|
|
5
14
|
import { Contracts } from './contract.mjs';
|
|
6
|
-
import { Memory
|
|
15
|
+
import { Memory } from './modules.mjs';
|
|
7
16
|
import { Mcp, Pointer } from './pointer.mjs';
|
|
8
17
|
import { Program } from './program.mjs';
|
|
9
|
-
import { Stamp, Tools } from './tools.mjs';
|
|
18
|
+
import { Bytes, Stamp, Tools } from './tools.mjs';
|
|
10
19
|
import { Voice } from './voice.mjs';
|
|
11
20
|
|
|
12
21
|
const GROUND = 'Ground';
|
|
13
22
|
|
|
14
|
-
const FLOOR = [
|
|
23
|
+
const FLOOR = [
|
|
24
|
+
'blueprints',
|
|
25
|
+
'cells',
|
|
26
|
+
'cipher',
|
|
27
|
+
'contracts',
|
|
28
|
+
'keychain',
|
|
29
|
+
'moved',
|
|
30
|
+
'paths',
|
|
31
|
+
'watch',
|
|
32
|
+
'wire',
|
|
33
|
+
];
|
|
15
34
|
|
|
16
35
|
const UNPROVEN = 'ground/unproven/';
|
|
17
36
|
|
|
@@ -19,7 +38,11 @@ const HEIR = 'ground/heir';
|
|
|
19
38
|
|
|
20
39
|
const commits = (cells, bundle) => {
|
|
21
40
|
const boxPk = bundle?.heirBoxPk;
|
|
22
|
-
|
|
41
|
+
try {
|
|
42
|
+
if (typeof boxPk !== 'string' || Bytes.unbase64(boxPk).length !== 32) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
23
46
|
return undefined;
|
|
24
47
|
}
|
|
25
48
|
cells.put(HEIR, { boxPk, nextPkHash: bundle.nextPkHash });
|
|
@@ -43,8 +66,8 @@ const unprovenAt = (cells) => (at) => {
|
|
|
43
66
|
|
|
44
67
|
const unprovenRows = (cells) => cells.keys(UNPROVEN).map((row) => cells.peek(row));
|
|
45
68
|
|
|
46
|
-
const install = ({ cells, keychain, program, bundle }) => {
|
|
47
|
-
if (bundle?.next !== undefined) {
|
|
69
|
+
const install = ({ cells, keychain, program, bundle, cipher }) => {
|
|
70
|
+
if (cells.peek('ground/admin') !== undefined || bundle?.next !== undefined) {
|
|
48
71
|
return undefined;
|
|
49
72
|
}
|
|
50
73
|
const voice = Voice.revive(bundle ?? {});
|
|
@@ -57,13 +80,13 @@ const install = ({ cells, keychain, program, bundle }) => {
|
|
|
57
80
|
if (sealed === undefined) {
|
|
58
81
|
return undefined;
|
|
59
82
|
}
|
|
60
|
-
make({ memory, voice, address, program });
|
|
83
|
+
make({ memory, voice, address, program, cipher });
|
|
61
84
|
cells.put('ground/admin', { address, sealed });
|
|
62
85
|
commits(cells, bundle);
|
|
63
86
|
return address;
|
|
64
87
|
};
|
|
65
88
|
|
|
66
|
-
const succeeds = ({ adminMine, adminVoice, address, bundle, cells, keychain, memory }) => {
|
|
89
|
+
const succeeds = ({ adminMine, adminVoice, address, bundle, cells, cipher, keychain, memory }) => {
|
|
67
90
|
if (bundle?.next !== undefined) {
|
|
68
91
|
return undefined;
|
|
69
92
|
}
|
|
@@ -72,21 +95,22 @@ const succeeds = ({ adminMine, adminVoice, address, bundle, cells, keychain, mem
|
|
|
72
95
|
if (heir === undefined || committed?.value !== Tools.digest(heir.pk)) {
|
|
73
96
|
return undefined;
|
|
74
97
|
}
|
|
75
|
-
const root = rootFor({ memory, voice: adminVoice, address });
|
|
98
|
+
const root = rootFor({ memory, voice: adminVoice, address, cipher });
|
|
76
99
|
const sealed = root === undefined ? undefined : keychain.seal(heir.pack());
|
|
77
100
|
if (sealed === undefined) {
|
|
78
101
|
return undefined;
|
|
79
102
|
}
|
|
80
103
|
const rewrapped = adminMine.write(`keyring/${Tools.address(heir.pk)}`, {
|
|
81
|
-
boxPk:
|
|
82
|
-
wrapped: Voice.sealTo(heir.boxPk,
|
|
104
|
+
boxPk: Bytes.base64(heir.boxPk),
|
|
105
|
+
wrapped: Voice.sealTo(heir.boxPk, Bytes.base64(root), cipher),
|
|
83
106
|
});
|
|
84
107
|
if (rewrapped === undefined) {
|
|
85
108
|
return undefined;
|
|
86
109
|
}
|
|
87
110
|
const version = committed.version + 1;
|
|
88
|
-
const
|
|
89
|
-
const
|
|
111
|
+
const certV = successionOf(memory, address).length + 1;
|
|
112
|
+
const handover = { at: address, next: Tools.address(heir.pk), v: certV };
|
|
113
|
+
const certified = adminMine.write(`${SUCCESSION}${certV}`, {
|
|
90
114
|
...handover,
|
|
91
115
|
sig: adminVoice.sign(Tools.canon(handover)),
|
|
92
116
|
});
|
|
@@ -96,7 +120,7 @@ const succeeds = ({ adminMine, adminVoice, address, bundle, cells, keychain, mem
|
|
|
96
120
|
const claim = {
|
|
97
121
|
address,
|
|
98
122
|
nextPkHash: heir.nextPkHash,
|
|
99
|
-
pk:
|
|
123
|
+
pk: Bytes.hex(heir.pk),
|
|
100
124
|
version,
|
|
101
125
|
};
|
|
102
126
|
if (memory.replace({ ...claim, sig: heir.sign(Tools.canon(claim)) }) === undefined) {
|
|
@@ -112,7 +136,14 @@ const succeeds = ({ adminMine, adminVoice, address, bundle, cells, keychain, mem
|
|
|
112
136
|
const Ground = (modules = {}, { span } = {}) => {
|
|
113
137
|
const cells = modules.cells;
|
|
114
138
|
const keychain = modules.keychain;
|
|
115
|
-
|
|
139
|
+
const cipher = modules.cipher;
|
|
140
|
+
if (
|
|
141
|
+
cells === undefined ||
|
|
142
|
+
keychain?.seal === undefined ||
|
|
143
|
+
keychain?.unseal === undefined ||
|
|
144
|
+
cipher?.seal === undefined ||
|
|
145
|
+
cipher?.unseal === undefined
|
|
146
|
+
) {
|
|
116
147
|
return undefined;
|
|
117
148
|
}
|
|
118
149
|
|
|
@@ -134,7 +165,7 @@ const Ground = (modules = {}, { span } = {}) => {
|
|
|
134
165
|
const installed = cells.peek('ground/admin');
|
|
135
166
|
if (installed === undefined) {
|
|
136
167
|
return Object.freeze({
|
|
137
|
-
init: (bundle) => install({ cells, keychain, program, bundle }),
|
|
168
|
+
init: (bundle) => install({ cells, keychain, program, bundle, cipher: modules.cipher }),
|
|
138
169
|
});
|
|
139
170
|
}
|
|
140
171
|
const bundle = keychain.unseal(installed.sealed);
|
|
@@ -155,17 +186,22 @@ const Ground = (modules = {}, { span } = {}) => {
|
|
|
155
186
|
};
|
|
156
187
|
|
|
157
188
|
const address = installed.address;
|
|
158
|
-
const memory = Memory(cells, unprovenAt(cells));
|
|
159
|
-
const paths = modules.paths ??
|
|
160
|
-
const wire =
|
|
189
|
+
const memory = Memory(cells, unprovenAt(cells), modules.moved);
|
|
190
|
+
const paths = modules.paths ?? Object.freeze({ find: () => undefined, publish: () => undefined });
|
|
191
|
+
const wire =
|
|
192
|
+
modules.wire ??
|
|
193
|
+
Object.freeze({
|
|
194
|
+
carry: async (endpoint, envelope) =>
|
|
195
|
+
typeof endpoint === 'function' ? endpoint(envelope) : undefined,
|
|
196
|
+
});
|
|
161
197
|
|
|
162
198
|
const stamp = Stamp();
|
|
163
199
|
const boundFor = (voice) =>
|
|
164
200
|
Object.fromEntries(
|
|
165
201
|
bound.map(([name, one]) => [
|
|
166
202
|
name,
|
|
167
|
-
Pointer(written.get(one.contract).sdl, (source, variables) =>
|
|
168
|
-
deliver(Tools.envelope(voice, one.at, { ask: { source, variables } }, stamp())),
|
|
203
|
+
Pointer(written.get(one.contract).sdl, async (source, variables) =>
|
|
204
|
+
deliver(await Tools.envelope(voice, one.at, { ask: { source, variables } }, stamp())),
|
|
169
205
|
),
|
|
170
206
|
]),
|
|
171
207
|
);
|
|
@@ -199,9 +235,10 @@ const Ground = (modules = {}, { span } = {}) => {
|
|
|
199
235
|
memory,
|
|
200
236
|
voice: adminVoice,
|
|
201
237
|
address,
|
|
202
|
-
root: rootFor({ memory, voice: adminVoice, address }),
|
|
238
|
+
root: rootFor({ memory, voice: adminVoice, address, cipher: modules.cipher }),
|
|
203
239
|
classOf: (key) =>
|
|
204
240
|
Program(memory.read(address, 'program')?.value)?.classOf(key) ?? coreClass(key),
|
|
241
|
+
cipher: modules.cipher,
|
|
205
242
|
});
|
|
206
243
|
|
|
207
244
|
const receive = async (envelope, token) => {
|
|
@@ -223,6 +260,7 @@ const Ground = (modules = {}, { span } = {}) => {
|
|
|
223
260
|
paths.publish(at, undefined);
|
|
224
261
|
if (voice === undefined) {
|
|
225
262
|
cells.drop(`${at}/`);
|
|
263
|
+
modules.moved?.(at);
|
|
226
264
|
return undefined;
|
|
227
265
|
}
|
|
228
266
|
return spend({ memory, voice, address: at });
|
|
@@ -236,16 +274,30 @@ const Ground = (modules = {}, { span } = {}) => {
|
|
|
236
274
|
if (one.at === undefined) {
|
|
237
275
|
return null;
|
|
238
276
|
}
|
|
239
|
-
const seen = await deliver(Tools.envelope(adminVoice, one.at, { describe: {} }, stamp()));
|
|
277
|
+
const seen = await deliver(await Tools.envelope(adminVoice, one.at, { describe: {} }, stamp()));
|
|
240
278
|
return seen?.body?.sdl === undefined
|
|
241
279
|
? false
|
|
242
280
|
: written.get(one.contract).satisfies(seen.body.sdl);
|
|
243
281
|
};
|
|
244
282
|
|
|
283
|
+
let adminDoor;
|
|
284
|
+
const buildAdmin = () =>
|
|
285
|
+
Being({
|
|
286
|
+
address,
|
|
287
|
+
cipher: modules.cipher,
|
|
288
|
+
deliver: (envelope) => deliver(envelope),
|
|
289
|
+
modules: modulesFor(adminVoice, adminDeclared()),
|
|
290
|
+
registry,
|
|
291
|
+
span,
|
|
292
|
+
vouched,
|
|
293
|
+
voice: adminVoice,
|
|
294
|
+
watch,
|
|
295
|
+
});
|
|
245
296
|
const registry = Registry({
|
|
246
297
|
memory,
|
|
247
298
|
adminMine,
|
|
248
299
|
address,
|
|
300
|
+
cipher: modules.cipher,
|
|
249
301
|
deliver: (envelope) => deliver(envelope),
|
|
250
302
|
modules: offeredNames,
|
|
251
303
|
modulesFor,
|
|
@@ -255,18 +307,16 @@ const Ground = (modules = {}, { span } = {}) => {
|
|
|
255
307
|
vouched,
|
|
256
308
|
attest,
|
|
257
309
|
watch,
|
|
310
|
+
extended: () => {
|
|
311
|
+
adminDoor = undefined;
|
|
312
|
+
},
|
|
258
313
|
});
|
|
259
|
-
const admin = (envelope, token) =>
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
span,
|
|
266
|
-
vouched,
|
|
267
|
-
voice: adminVoice,
|
|
268
|
-
watch,
|
|
269
|
-
})(envelope, token);
|
|
314
|
+
const admin = (envelope, token) => {
|
|
315
|
+
if (adminDoor === undefined) {
|
|
316
|
+
adminDoor = buildAdmin();
|
|
317
|
+
}
|
|
318
|
+
return adminDoor(envelope, token);
|
|
319
|
+
};
|
|
270
320
|
|
|
271
321
|
announce(address);
|
|
272
322
|
for (const key of adminMine.list('identity/custody/')) {
|
|
@@ -284,7 +334,16 @@ const Ground = (modules = {}, { span } = {}) => {
|
|
|
284
334
|
address,
|
|
285
335
|
receive,
|
|
286
336
|
rotate: (bundle) =>
|
|
287
|
-
succeeds({
|
|
337
|
+
succeeds({
|
|
338
|
+
adminMine,
|
|
339
|
+
adminVoice,
|
|
340
|
+
address,
|
|
341
|
+
bundle,
|
|
342
|
+
cells,
|
|
343
|
+
cipher: modules.cipher,
|
|
344
|
+
keychain,
|
|
345
|
+
memory,
|
|
346
|
+
}),
|
|
288
347
|
serve: (voice) => {
|
|
289
348
|
const client = Client({ voice, deliver, resolve });
|
|
290
349
|
const sdlOf = async (at) => (await client.describe(at))?.body?.sdl;
|
package/index.mjs
CHANGED
|
@@ -13,8 +13,8 @@ export {
|
|
|
13
13
|
export { Client, speaking } from './client.mjs';
|
|
14
14
|
export { Contract, Contracts } from './contract.mjs';
|
|
15
15
|
export { FLOOR, GROUND, Ground } from './ground.mjs';
|
|
16
|
-
export {
|
|
16
|
+
export { Memory } from './modules.mjs';
|
|
17
17
|
export { Mcp, Pointer } from './pointer.mjs';
|
|
18
18
|
export { Program } from './program.mjs';
|
|
19
|
-
export { MEASURE, SPAN, Stamp, Tools } from './tools.mjs';
|
|
19
|
+
export { Bytes, MEASURE, SPAN, Stamp, Tools } from './tools.mjs';
|
|
20
20
|
export { Voice } from './voice.mjs';
|
package/modules.mjs
CHANGED
|
@@ -1,38 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Tools } from './tools.mjs';
|
|
1
|
+
import { Bytes, Tools } from './tools.mjs';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const flush = () => {
|
|
9
|
-
if (file) {
|
|
10
|
-
writeFileSync(`${file}.next`, JSON.stringify(Object.fromEntries(cells)));
|
|
11
|
-
renameSync(`${file}.next`, file);
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
return Object.freeze({
|
|
15
|
-
drop: (prefix) => {
|
|
16
|
-
for (const cell of [...cells.keys()].filter((one) => one.startsWith(prefix))) {
|
|
17
|
-
cells.delete(cell);
|
|
18
|
-
}
|
|
19
|
-
flush();
|
|
20
|
-
},
|
|
21
|
-
dump: () => JSON.stringify(Object.fromEntries(cells)),
|
|
22
|
-
keys: (prefix) => [...cells.keys()].filter((cell) => cell.startsWith(prefix)),
|
|
23
|
-
peek: (cell) => cells.get(cell),
|
|
24
|
-
put: (cell, held) => {
|
|
25
|
-
cells.set(cell, held);
|
|
26
|
-
flush();
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const Memory = (cells, unproven) => {
|
|
3
|
+
// The ground's own machinery over its cells. Every implementation a host
|
|
4
|
+
// supplies — cells, keychain, cipher, paths, wire — belongs to that host's
|
|
5
|
+
// floor and never to the core (X, Everything is a module).
|
|
6
|
+
const Memory = (cells, unproven, moved) => {
|
|
32
7
|
const held = (address, key) => cells.peek(`${address}/${key}`);
|
|
33
8
|
const writerAt = (address) => {
|
|
34
9
|
const chain = held(address, 'chain/pk');
|
|
35
|
-
return chain === undefined ? Tools.pkOf(address) :
|
|
10
|
+
return chain === undefined ? Tools.pkOf(address) : Bytes.unhex(chain.value);
|
|
36
11
|
};
|
|
37
12
|
return Object.freeze({
|
|
38
13
|
list: (address, prefix) =>
|
|
@@ -42,16 +17,17 @@ const Memory = (cells, unproven) => {
|
|
|
42
17
|
|
|
43
18
|
replace: ({ address, pk, nextPkHash, sig }) => {
|
|
44
19
|
const committed = held(address, 'chain/nextPkHash');
|
|
45
|
-
if (committed === undefined || Tools.digest(
|
|
20
|
+
if (committed === undefined || Tools.digest(Bytes.unhex(pk)) !== committed.value) {
|
|
46
21
|
return undefined;
|
|
47
22
|
}
|
|
48
23
|
const version = committed.version + 1;
|
|
49
24
|
const claim = { address, nextPkHash, pk, version };
|
|
50
|
-
if (!Tools.verifies(
|
|
25
|
+
if (!Tools.verifies(Bytes.unhex(pk), Tools.canon(claim), sig)) {
|
|
51
26
|
return undefined;
|
|
52
27
|
}
|
|
53
28
|
cells.put(`${address}/chain/pk`, { value: pk, version });
|
|
54
29
|
cells.put(`${address}/chain/nextPkHash`, { value: nextPkHash, version });
|
|
30
|
+
moved?.(address);
|
|
55
31
|
return version;
|
|
56
32
|
},
|
|
57
33
|
|
|
@@ -68,45 +44,12 @@ const Memory = (cells, unproven) => {
|
|
|
68
44
|
return undefined;
|
|
69
45
|
}
|
|
70
46
|
cells.put(`${address}/${key}`, { value, version });
|
|
47
|
+
if (!key.startsWith('refs/')) {
|
|
48
|
+
moved?.(address);
|
|
49
|
+
}
|
|
71
50
|
return version;
|
|
72
51
|
},
|
|
73
52
|
});
|
|
74
53
|
};
|
|
75
54
|
|
|
76
|
-
|
|
77
|
-
let at = 0;
|
|
78
|
-
return Object.freeze({ now: () => ticks[Math.min(at++, ticks.length - 1)] });
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const Entropy = (grains) => {
|
|
82
|
-
let at = 0;
|
|
83
|
-
return Object.freeze({ grain: () => grains[Math.min(at++, grains.length - 1)] });
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const Keychain = (secret) =>
|
|
87
|
-
Object.freeze({
|
|
88
|
-
seal: (value) => {
|
|
89
|
-
try {
|
|
90
|
-
return Tools.seal(secret, value);
|
|
91
|
-
} catch {
|
|
92
|
-
return undefined;
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
unseal: (sealed) => Tools.unseal(secret, sealed),
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const Dns = () => new Map();
|
|
99
|
-
|
|
100
|
-
const Paths = (dns) =>
|
|
101
|
-
Object.freeze({
|
|
102
|
-
find: (address) => dns.get(address),
|
|
103
|
-
publish: (address, endpoint) =>
|
|
104
|
-
endpoint === undefined ? dns.delete(address) : dns.set(address, endpoint),
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const Wire = () =>
|
|
108
|
-
Object.freeze({
|
|
109
|
-
carry: async (endpoint, envelope) => (endpoint === undefined ? undefined : endpoint(envelope)),
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
export { Cells, Clock, Dns, Entropy, Keychain, Memory, Paths, Wire };
|
|
55
|
+
export { Memory };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nervur",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "A distributed system of sovereign beings — one door, one question: by whose authority.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Razvan Gherghina",
|
|
@@ -11,12 +11,15 @@
|
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"*.mjs",
|
|
14
|
-
"
|
|
14
|
+
"GETTING-STARTED.md",
|
|
15
|
+
"NOTICE",
|
|
16
|
+
"SECURITY.md"
|
|
15
17
|
],
|
|
16
18
|
"engines": {
|
|
17
19
|
"node": ">=22"
|
|
18
20
|
},
|
|
19
21
|
"dependencies": {
|
|
22
|
+
"@noble/curves": "2.3.0",
|
|
20
23
|
"graphql": "^17.0.2"
|
|
21
24
|
}
|
|
22
25
|
}
|
package/tools.mjs
CHANGED
|
@@ -1,13 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
createDecipheriv,
|
|
4
|
-
createHash,
|
|
5
|
-
createPublicKey,
|
|
6
|
-
randomBytes,
|
|
7
|
-
verify,
|
|
8
|
-
} from 'node:crypto';
|
|
1
|
+
import { ed25519 } from '@noble/curves/ed25519.js';
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
9
3
|
|
|
10
|
-
const
|
|
4
|
+
const HEX = '0123456789abcdef';
|
|
5
|
+
|
|
6
|
+
const utf8 = new TextEncoder();
|
|
7
|
+
const text8 = new TextDecoder();
|
|
8
|
+
|
|
9
|
+
// Buffer is Node's alone. Every byte string here is a Uint8Array, and these
|
|
10
|
+
// are the encodings the substrate names: hex on a digest, base64 on a
|
|
11
|
+
// signature, utf8 under canon.
|
|
12
|
+
const Bytes = Object.freeze({
|
|
13
|
+
base64: (bytes) => {
|
|
14
|
+
let binary = '';
|
|
15
|
+
for (const byte of bytes) {
|
|
16
|
+
binary += String.fromCharCode(byte);
|
|
17
|
+
}
|
|
18
|
+
return btoa(binary);
|
|
19
|
+
},
|
|
20
|
+
concat: (parts) => {
|
|
21
|
+
const whole = new Uint8Array(parts.reduce((run, one) => run + one.length, 0));
|
|
22
|
+
let at = 0;
|
|
23
|
+
for (const one of parts) {
|
|
24
|
+
whole.set(one, at);
|
|
25
|
+
at += one.length;
|
|
26
|
+
}
|
|
27
|
+
return whole;
|
|
28
|
+
},
|
|
29
|
+
equal: (one, other) => one.length === other.length && one.every((byte, at) => byte === other[at]),
|
|
30
|
+
hex: (bytes) => {
|
|
31
|
+
let out = '';
|
|
32
|
+
for (const byte of bytes) {
|
|
33
|
+
out += HEX[byte >> 4] + HEX[byte & 15];
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
},
|
|
37
|
+
of: (value) => utf8.encode(value),
|
|
38
|
+
read: (bytes) => text8.decode(bytes),
|
|
39
|
+
unbase64: (value) => {
|
|
40
|
+
const binary = atob(value);
|
|
41
|
+
const bytes = new Uint8Array(binary.length);
|
|
42
|
+
for (let at = 0; at < binary.length; at += 1) {
|
|
43
|
+
bytes[at] = binary.charCodeAt(at);
|
|
44
|
+
}
|
|
45
|
+
return bytes;
|
|
46
|
+
},
|
|
47
|
+
unhex: (value) => {
|
|
48
|
+
const bytes = new Uint8Array(value.length >> 1);
|
|
49
|
+
for (let at = 0; at < bytes.length; at += 1) {
|
|
50
|
+
bytes[at] = Number.parseInt(value.substr(at << 1, 2), 16);
|
|
51
|
+
}
|
|
52
|
+
return bytes;
|
|
53
|
+
},
|
|
54
|
+
});
|
|
11
55
|
|
|
12
56
|
const B32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
|
13
57
|
|
|
@@ -70,21 +114,23 @@ const unbase32 = (text) => {
|
|
|
70
114
|
bits -= 8;
|
|
71
115
|
}
|
|
72
116
|
}
|
|
73
|
-
return
|
|
117
|
+
return Uint8Array.from(out);
|
|
74
118
|
};
|
|
75
119
|
|
|
76
120
|
const Tools = Object.freeze({
|
|
77
121
|
address: (pk) => {
|
|
78
|
-
const payload =
|
|
122
|
+
const payload = Bytes.concat([Uint8Array.of(VERSION_G), pk]);
|
|
79
123
|
const crc = crc16(payload);
|
|
80
|
-
return base32(
|
|
124
|
+
return base32(Bytes.concat([payload, Uint8Array.of(crc & 0xff, crc >> 8)]));
|
|
81
125
|
},
|
|
82
|
-
canon: (claim) =>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
126
|
+
canon: (claim) => Bytes.of(JSON.stringify(ordered(claim))),
|
|
127
|
+
// The label arrives as text and the digest is over its utf8 bytes, which is
|
|
128
|
+
// the byte string the substrate has always hashed.
|
|
129
|
+
derive: (root, label) => sha256.create().update(root).update(Bytes.of(label)).digest(),
|
|
130
|
+
digest: (bytes) => Bytes.hex(sha256(bytes)),
|
|
131
|
+
envelope: async (voice, to, op, seq) => {
|
|
86
132
|
const from = Tools.address(voice.pk);
|
|
87
|
-
return { from, op, seq, sig: voice.sign(Tools.canon({ from, op, seq, to })), to };
|
|
133
|
+
return { from, op, seq, sig: await voice.sign(Tools.canon({ from, op, seq, to })), to };
|
|
88
134
|
},
|
|
89
135
|
pkOf: (address) => {
|
|
90
136
|
const bytes = typeof address === 'string' && address.length === 56 ? unbase32(address) : null;
|
|
@@ -93,39 +139,12 @@ const Tools = Object.freeze({
|
|
|
93
139
|
}
|
|
94
140
|
const payload = bytes.subarray(0, 33);
|
|
95
141
|
const checksum = bytes[33] | (bytes[34] << 8);
|
|
96
|
-
return crc16(payload) === checksum ?
|
|
97
|
-
},
|
|
98
|
-
seal: (secret, value) => {
|
|
99
|
-
const iv = randomBytes(12);
|
|
100
|
-
const cipher = createCipheriv('aes-256-gcm', secret, iv);
|
|
101
|
-
const sealed = Buffer.concat([cipher.update(JSON.stringify(value), 'utf8'), cipher.final()]);
|
|
102
|
-
return Buffer.concat([iv, cipher.getAuthTag(), sealed]).toString('base64');
|
|
103
|
-
},
|
|
104
|
-
secret: () => randomBytes(32),
|
|
105
|
-
unseal: (secret, sealed) => {
|
|
106
|
-
try {
|
|
107
|
-
const bytes = Buffer.from(sealed, 'base64');
|
|
108
|
-
const decipher = createDecipheriv('aes-256-gcm', secret, bytes.subarray(0, 12));
|
|
109
|
-
decipher.setAuthTag(bytes.subarray(12, 28));
|
|
110
|
-
return JSON.parse(
|
|
111
|
-
Buffer.concat([decipher.update(bytes.subarray(28)), decipher.final()]).toString('utf8'),
|
|
112
|
-
);
|
|
113
|
-
} catch {
|
|
114
|
-
return undefined;
|
|
115
|
-
}
|
|
142
|
+
return crc16(payload) === checksum ? payload.slice(1) : null;
|
|
116
143
|
},
|
|
144
|
+
secret: () => crypto.getRandomValues(new Uint8Array(32)),
|
|
117
145
|
verifies: (pk, bytes, sig) => {
|
|
118
146
|
try {
|
|
119
|
-
return verify(
|
|
120
|
-
null,
|
|
121
|
-
bytes,
|
|
122
|
-
createPublicKey({
|
|
123
|
-
key: Buffer.concat([ED25519_SPKI_PREFIX, pk]),
|
|
124
|
-
format: 'der',
|
|
125
|
-
type: 'spki',
|
|
126
|
-
}),
|
|
127
|
-
Buffer.from(sig, 'base64'),
|
|
128
|
-
);
|
|
147
|
+
return ed25519.verify(Bytes.unbase64(sig), bytes, pk);
|
|
129
148
|
} catch {
|
|
130
149
|
return false;
|
|
131
150
|
}
|
|
@@ -140,4 +159,4 @@ const Stamp = () => {
|
|
|
140
159
|
};
|
|
141
160
|
};
|
|
142
161
|
|
|
143
|
-
export { MEASURE, SPAN, Stamp, Tools };
|
|
162
|
+
export { Bytes, MEASURE, SPAN, Stamp, Tools };
|