nervur 0.11.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/contract.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { buildSchema, isEnumType, isInputObjectType, parse, print } from 'graphql';
1
+ import { buildSchema, isEnumType, isInputObjectType, specifiedScalarTypes } from 'graphql';
2
2
  import { Tools } from './tools.mjs';
3
3
 
4
4
  const argsAgree = (wanted, given) => {
@@ -16,9 +16,8 @@ const argsAgree = (wanted, given) => {
16
16
  const Contract = (name, sdl) => {
17
17
  try {
18
18
  const schema = buildSchema(sdl);
19
- const printed = print(parse(sdl));
20
19
  return Object.freeze({
21
- digest: Tools.digest(Tools.canon({ interface: printed, name })),
20
+ digest: Tools.digest(Tools.canon({ interface: sdl, name })),
22
21
  name,
23
22
  satisfies: (offered) => {
24
23
  const theirs = (() => {
@@ -36,6 +35,9 @@ const Contract = (name, sdl) => {
36
35
  continue;
37
36
  }
38
37
  const given = theirs.getTypeMap()[typeName];
38
+ if (given === undefined && !specifiedScalarTypes.some((one) => one.name === typeName)) {
39
+ return false;
40
+ }
39
41
  if (isEnumType(wanted)) {
40
42
  if (
41
43
  !isEnumType(given) ||
@@ -73,7 +75,7 @@ const Contract = (name, sdl) => {
73
75
  }
74
76
  return true;
75
77
  },
76
- sdl: printed,
78
+ sdl,
77
79
  });
78
80
  } catch {
79
81
  return undefined;
package/ground.mjs CHANGED
@@ -1,20 +1,73 @@
1
1
  import { ADMIN } from './admin.mjs';
2
- import { Being, Compartment, Registry, SUCCESSION, make, rootFor } from './being.mjs';
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, Paths, Wire } from './modules.mjs';
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 = ['blueprints', 'cells', 'contracts', 'keychain', 'paths', 'watch', 'wire'];
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
- const install = ({ cells, keychain, program, bundle }) => {
17
- if (bundle?.next !== undefined) {
35
+ const UNPROVEN = 'ground/unproven/';
36
+
37
+ const HEIR = 'ground/heir';
38
+
39
+ const commits = (cells, bundle) => {
40
+ const boxPk = bundle?.heirBoxPk;
41
+ try {
42
+ if (typeof boxPk !== 'string' || Bytes.unbase64(boxPk).length !== 32) {
43
+ return undefined;
44
+ }
45
+ } catch {
46
+ return undefined;
47
+ }
48
+ cells.put(HEIR, { boxPk, nextPkHash: bundle.nextPkHash });
49
+ return undefined;
50
+ };
51
+
52
+ const unprovenAt = (cells) => (at) => {
53
+ try {
54
+ const stood = cells.peek(`${UNPROVEN}${at}`);
55
+ const seen = Date.now();
56
+ cells.put(`${UNPROVEN}${at}`, {
57
+ at,
58
+ last: seen,
59
+ seen: (stood?.seen ?? 0) + 1,
60
+ since: stood?.since ?? seen,
61
+ });
62
+ } catch {
63
+ return undefined;
64
+ }
65
+ };
66
+
67
+ const unprovenRows = (cells) => cells.keys(UNPROVEN).map((row) => cells.peek(row));
68
+
69
+ const install = ({ cells, keychain, program, bundle, cipher }) => {
70
+ if (cells.peek('ground/admin') !== undefined || bundle?.next !== undefined) {
18
71
  return undefined;
19
72
  }
20
73
  const voice = Voice.revive(bundle ?? {});
@@ -27,12 +80,13 @@ const install = ({ cells, keychain, program, bundle }) => {
27
80
  if (sealed === undefined) {
28
81
  return undefined;
29
82
  }
30
- make({ memory, voice, address, program });
83
+ make({ memory, voice, address, program, cipher });
31
84
  cells.put('ground/admin', { address, sealed });
85
+ commits(cells, bundle);
32
86
  return address;
33
87
  };
34
88
 
35
- const succeeds = ({ adminMine, adminVoice, address, bundle, cells, keychain, memory }) => {
89
+ const succeeds = ({ adminMine, adminVoice, address, bundle, cells, cipher, keychain, memory }) => {
36
90
  if (bundle?.next !== undefined) {
37
91
  return undefined;
38
92
  }
@@ -41,21 +95,22 @@ const succeeds = ({ adminMine, adminVoice, address, bundle, cells, keychain, mem
41
95
  if (heir === undefined || committed?.value !== Tools.digest(heir.pk)) {
42
96
  return undefined;
43
97
  }
44
- const root = rootFor({ memory, voice: adminVoice, address });
98
+ const root = rootFor({ memory, voice: adminVoice, address, cipher });
45
99
  const sealed = root === undefined ? undefined : keychain.seal(heir.pack());
46
100
  if (sealed === undefined) {
47
101
  return undefined;
48
102
  }
49
103
  const rewrapped = adminMine.write(`keyring/${Tools.address(heir.pk)}`, {
50
- boxPk: Buffer.from(heir.boxPk).toString('base64'),
51
- wrapped: Voice.sealTo(heir.boxPk, root.toString('base64')),
104
+ boxPk: Bytes.base64(heir.boxPk),
105
+ wrapped: Voice.sealTo(heir.boxPk, Bytes.base64(root), cipher),
52
106
  });
53
107
  if (rewrapped === undefined) {
54
108
  return undefined;
55
109
  }
56
110
  const version = committed.version + 1;
57
- const handover = { at: address, next: Buffer.from(heir.pk).toString('hex'), v: version };
58
- const certified = adminMine.write(`${SUCCESSION}${version}`, {
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}`, {
59
114
  ...handover,
60
115
  sig: adminVoice.sign(Tools.canon(handover)),
61
116
  });
@@ -65,20 +120,30 @@ const succeeds = ({ adminMine, adminVoice, address, bundle, cells, keychain, mem
65
120
  const claim = {
66
121
  address,
67
122
  nextPkHash: heir.nextPkHash,
68
- pk: Buffer.from(heir.pk).toString('hex'),
123
+ pk: Bytes.hex(heir.pk),
69
124
  version,
70
125
  };
71
126
  if (memory.replace({ ...claim, sig: heir.sign(Tools.canon(claim)) }) === undefined) {
72
127
  return undefined;
73
128
  }
74
129
  cells.put('ground/admin', { address, sealed });
130
+ commits(cells, bundle);
75
131
  return Tools.address(heir.pk);
76
132
  };
77
133
 
78
- const Ground = (modules = {}) => {
134
+ // Never a key in the modules object: everything there that is not in FLOOR
135
+ // becomes a module handed to programs under that name.
136
+ const Ground = (modules = {}, { span } = {}) => {
79
137
  const cells = modules.cells;
80
138
  const keychain = modules.keychain;
81
- if (cells === undefined || keychain?.seal === undefined || keychain?.unseal === undefined) {
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
+ ) {
82
147
  return undefined;
83
148
  }
84
149
 
@@ -100,7 +165,7 @@ const Ground = (modules = {}) => {
100
165
  const installed = cells.peek('ground/admin');
101
166
  if (installed === undefined) {
102
167
  return Object.freeze({
103
- init: (bundle) => install({ cells, keychain, program, bundle }),
168
+ init: (bundle) => install({ cells, keychain, program, bundle, cipher: modules.cipher }),
104
169
  });
105
170
  }
106
171
  const bundle = keychain.unseal(installed.sealed);
@@ -121,17 +186,22 @@ const Ground = (modules = {}) => {
121
186
  };
122
187
 
123
188
  const address = installed.address;
124
- const memory = Memory(cells);
125
- const paths = modules.paths ?? Paths(new Map());
126
- const wire = modules.wire ?? 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
+ });
127
197
 
128
198
  const stamp = Stamp();
129
199
  const boundFor = (voice) =>
130
200
  Object.fromEntries(
131
201
  bound.map(([name, one]) => [
132
202
  name,
133
- Pointer(written.get(one.contract).sdl, (source, variables) =>
134
- 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())),
135
205
  ),
136
206
  ]),
137
207
  );
@@ -165,9 +235,10 @@ const Ground = (modules = {}) => {
165
235
  memory,
166
236
  voice: adminVoice,
167
237
  address,
168
- root: rootFor({ memory, voice: adminVoice, address }),
238
+ root: rootFor({ memory, voice: adminVoice, address, cipher: modules.cipher }),
169
239
  classOf: (key) =>
170
240
  Program(memory.read(address, 'program')?.value)?.classOf(key) ?? coreClass(key),
241
+ cipher: modules.cipher,
171
242
  });
172
243
 
173
244
  const receive = async (envelope, token) => {
@@ -185,9 +256,14 @@ const Ground = (modules = {}) => {
185
256
  return door(envelope, token);
186
257
  };
187
258
  const announce = (at) => paths.publish(at, receive);
188
- const forget = (at) => {
259
+ const forget = (at, voice) => {
189
260
  paths.publish(at, undefined);
190
- cells.drop(`${at}/`);
261
+ if (voice === undefined) {
262
+ cells.drop(`${at}/`);
263
+ modules.moved?.(at);
264
+ return undefined;
265
+ }
266
+ return spend({ memory, voice, address: at });
191
267
  };
192
268
 
193
269
  const attest = async (name) => {
@@ -198,35 +274,49 @@ const Ground = (modules = {}) => {
198
274
  if (one.at === undefined) {
199
275
  return null;
200
276
  }
201
- const seen = await deliver(Tools.envelope(adminVoice, one.at, { describe: {} }, stamp()));
277
+ const seen = await deliver(await Tools.envelope(adminVoice, one.at, { describe: {} }, stamp()));
202
278
  return seen?.body?.sdl === undefined
203
279
  ? false
204
280
  : written.get(one.contract).satisfies(seen.body.sdl);
205
281
  };
206
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
+ });
207
296
  const registry = Registry({
208
297
  memory,
209
298
  adminMine,
210
299
  address,
300
+ cipher: modules.cipher,
211
301
  deliver: (envelope) => deliver(envelope),
212
302
  modules: offeredNames,
213
303
  modulesFor,
214
304
  announce,
215
305
  forget,
306
+ span,
216
307
  vouched,
217
308
  attest,
218
309
  watch,
310
+ extended: () => {
311
+ adminDoor = undefined;
312
+ },
219
313
  });
220
- const admin = (envelope, token) =>
221
- Being({
222
- address,
223
- deliver: (envelope) => deliver(envelope),
224
- modules: modulesFor(adminVoice, adminDeclared()),
225
- registry,
226
- vouched,
227
- voice: adminVoice,
228
- watch,
229
- })(envelope, token);
314
+ const admin = (envelope, token) => {
315
+ if (adminDoor === undefined) {
316
+ adminDoor = buildAdmin();
317
+ }
318
+ return adminDoor(envelope, token);
319
+ };
230
320
 
231
321
  announce(address);
232
322
  for (const key of adminMine.list('identity/custody/')) {
@@ -244,7 +334,16 @@ const Ground = (modules = {}) => {
244
334
  address,
245
335
  receive,
246
336
  rotate: (bundle) =>
247
- succeeds({ adminMine, adminVoice, address, bundle, cells, keychain, memory }),
337
+ succeeds({
338
+ adminMine,
339
+ adminVoice,
340
+ address,
341
+ bundle,
342
+ cells,
343
+ cipher: modules.cipher,
344
+ keychain,
345
+ memory,
346
+ }),
248
347
  serve: (voice) => {
249
348
  const client = Client({ voice, deliver, resolve });
250
349
  const sdlOf = async (at) => (await client.describe(at))?.body?.sdl;
@@ -268,6 +367,14 @@ const Ground = (modules = {}) => {
268
367
  },
269
368
  });
270
369
  },
370
+ seated: (at, from) => {
371
+ if (at === from) {
372
+ return true;
373
+ }
374
+ const row = held(at) ? memory.read(at, `refs/${from}`)?.value : undefined;
375
+ return row !== undefined && row !== null;
376
+ },
377
+ unproven: () => unprovenRows(cells),
271
378
  });
272
379
  };
273
380
 
package/index.mjs CHANGED
@@ -1,10 +1,20 @@
1
1
  export { ADMIN } from './admin.mjs';
2
- export { Blueprint, CLASSES, CORE_ONLY, OPEN, STRUCTURAL, coreClass, reads } from './blueprint.mjs';
2
+ export {
3
+ Blueprint,
4
+ CLASSES,
5
+ CORE_ONLY,
6
+ OPEN,
7
+ STRUCTURAL,
8
+ composition,
9
+ coreClass,
10
+ faults,
11
+ reads,
12
+ } from './blueprint.mjs';
3
13
  export { Client, speaking } from './client.mjs';
4
14
  export { Contract, Contracts } from './contract.mjs';
5
15
  export { FLOOR, GROUND, Ground } from './ground.mjs';
6
- export { Cells, Clock, Dns, Entropy, Keychain, Memory, Paths, Wire } from './modules.mjs';
16
+ export { Memory } from './modules.mjs';
7
17
  export { Mcp, Pointer } from './pointer.mjs';
8
18
  export { Program } from './program.mjs';
9
- export { MEASURE, Stamp, Tools } from './tools.mjs';
19
+ export { Bytes, MEASURE, SPAN, Stamp, Tools } from './tools.mjs';
10
20
  export { Voice } from './voice.mjs';
package/modules.mjs CHANGED
@@ -1,38 +1,13 @@
1
- import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
- import { Tools } from './tools.mjs';
1
+ import { Bytes, Tools } from './tools.mjs';
3
2
 
4
- const Cells = ({ file } = {}) => {
5
- const cells = new Map(
6
- file && existsSync(file) ? Object.entries(JSON.parse(readFileSync(file, 'utf8'))) : [],
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) => {
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) : Buffer.from(chain.value, 'hex');
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) => {
42
17
 
43
18
  replace: ({ address, pk, nextPkHash, sig }) => {
44
19
  const committed = held(address, 'chain/nextPkHash');
45
- if (committed === undefined || Tools.digest(Buffer.from(pk, 'hex')) !== committed.value) {
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(Buffer.from(pk, 'hex'), Tools.canon(claim), sig)) {
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
 
@@ -64,48 +40,16 @@ const Memory = (cells) => {
64
40
  return undefined;
65
41
  }
66
42
  if (!Tools.verifies(writer, Tools.canon({ address, key, value, version }), sig)) {
43
+ unproven?.(address);
67
44
  return undefined;
68
45
  }
69
46
  cells.put(`${address}/${key}`, { value, version });
47
+ if (!key.startsWith('refs/')) {
48
+ moved?.(address);
49
+ }
70
50
  return version;
71
51
  },
72
52
  });
73
53
  };
74
54
 
75
- const Clock = (ticks) => {
76
- let at = 0;
77
- return Object.freeze({ now: () => ticks[Math.min(at++, ticks.length - 1)] });
78
- };
79
-
80
- const Entropy = (grains) => {
81
- let at = 0;
82
- return Object.freeze({ grain: () => grains[Math.min(at++, grains.length - 1)] });
83
- };
84
-
85
- const Keychain = (secret) =>
86
- Object.freeze({
87
- seal: (value) => {
88
- try {
89
- return Tools.seal(secret, value);
90
- } catch {
91
- return undefined;
92
- }
93
- },
94
- unseal: (sealed) => Tools.unseal(secret, sealed),
95
- });
96
-
97
- const Dns = () => new Map();
98
-
99
- const Paths = (dns) =>
100
- Object.freeze({
101
- find: (address) => dns.get(address),
102
- publish: (address, endpoint) =>
103
- endpoint === undefined ? dns.delete(address) : dns.set(address, endpoint),
104
- });
105
-
106
- const Wire = () =>
107
- Object.freeze({
108
- carry: async (endpoint, envelope) => (endpoint === undefined ? undefined : endpoint(envelope)),
109
- });
110
-
111
- 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.11.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
- "NOTICE"
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
- createCipheriv,
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 ED25519_SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
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
 
@@ -15,6 +59,8 @@ const VERSION_G = 6 << 3;
15
59
 
16
60
  const MEASURE = 8_388_608;
17
61
 
62
+ const SPAN = 60_000;
63
+
18
64
  const ordered = (value) =>
19
65
  value === null || typeof value !== 'object'
20
66
  ? value
@@ -68,21 +114,23 @@ const unbase32 = (text) => {
68
114
  bits -= 8;
69
115
  }
70
116
  }
71
- return Buffer.from(out);
117
+ return Uint8Array.from(out);
72
118
  };
73
119
 
74
120
  const Tools = Object.freeze({
75
121
  address: (pk) => {
76
- const payload = Buffer.concat([Buffer.from([VERSION_G]), pk]);
122
+ const payload = Bytes.concat([Uint8Array.of(VERSION_G), pk]);
77
123
  const crc = crc16(payload);
78
- return base32(Buffer.concat([payload, Buffer.from([crc & 0xff, crc >> 8])]));
124
+ return base32(Bytes.concat([payload, Uint8Array.of(crc & 0xff, crc >> 8)]));
79
125
  },
80
- canon: (claim) => Buffer.from(JSON.stringify(ordered(claim))),
81
- derive: (root, label) => createHash('sha256').update(root).update(label).digest(),
82
- digest: (bytes) => createHash('sha256').update(bytes).digest('hex'),
83
- envelope: (voice, to, op, seq) => {
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) => {
84
132
  const from = Tools.address(voice.pk);
85
- 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 };
86
134
  },
87
135
  pkOf: (address) => {
88
136
  const bytes = typeof address === 'string' && address.length === 56 ? unbase32(address) : null;
@@ -91,39 +139,12 @@ const Tools = Object.freeze({
91
139
  }
92
140
  const payload = bytes.subarray(0, 33);
93
141
  const checksum = bytes[33] | (bytes[34] << 8);
94
- return crc16(payload) === checksum ? Buffer.from(payload.subarray(1)) : null;
95
- },
96
- seal: (secret, value) => {
97
- const iv = randomBytes(12);
98
- const cipher = createCipheriv('aes-256-gcm', secret, iv);
99
- const sealed = Buffer.concat([cipher.update(JSON.stringify(value), 'utf8'), cipher.final()]);
100
- return Buffer.concat([iv, cipher.getAuthTag(), sealed]).toString('base64');
101
- },
102
- secret: () => randomBytes(32),
103
- unseal: (secret, sealed) => {
104
- try {
105
- const bytes = Buffer.from(sealed, 'base64');
106
- const decipher = createDecipheriv('aes-256-gcm', secret, bytes.subarray(0, 12));
107
- decipher.setAuthTag(bytes.subarray(12, 28));
108
- return JSON.parse(
109
- Buffer.concat([decipher.update(bytes.subarray(28)), decipher.final()]).toString('utf8'),
110
- );
111
- } catch {
112
- return undefined;
113
- }
142
+ return crc16(payload) === checksum ? payload.slice(1) : null;
114
143
  },
144
+ secret: () => crypto.getRandomValues(new Uint8Array(32)),
115
145
  verifies: (pk, bytes, sig) => {
116
146
  try {
117
- return verify(
118
- null,
119
- bytes,
120
- createPublicKey({
121
- key: Buffer.concat([ED25519_SPKI_PREFIX, pk]),
122
- format: 'der',
123
- type: 'spki',
124
- }),
125
- Buffer.from(sig, 'base64'),
126
- );
147
+ return ed25519.verify(Bytes.unbase64(sig), bytes, pk);
127
148
  } catch {
128
149
  return false;
129
150
  }
@@ -138,4 +159,4 @@ const Stamp = () => {
138
159
  };
139
160
  };
140
161
 
141
- export { MEASURE, Stamp, Tools };
162
+ export { Bytes, MEASURE, SPAN, Stamp, Tools };