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/ground.mjs CHANGED
@@ -1,17 +1,36 @@
1
1
  import { ADMIN } from './admin.mjs';
2
- import { Being, Compartment, Registry, SUCCESSION, make, rootFor, spend } 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
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
- if (typeof boxPk !== 'string' || Buffer.from(boxPk, 'base64').length !== 32) {
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: Buffer.from(heir.boxPk).toString('base64'),
82
- wrapped: Voice.sealTo(heir.boxPk, root.toString('base64')),
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 handover = { at: address, next: Buffer.from(heir.pk).toString('hex'), v: version };
89
- 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}`, {
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: Buffer.from(heir.pk).toString('hex'),
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
- 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
+ ) {
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 ?? Paths(new Map());
160
- 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
+ });
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
- Being({
261
- address,
262
- deliver: (envelope) => deliver(envelope),
263
- modules: modulesFor(adminVoice, adminDeclared()),
264
- registry,
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({ 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
+ }),
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 { Cells, Clock, Dns, Entropy, Keychain, Memory, Paths, Wire } from './modules.mjs';
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 { 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, 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) : 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, unproven) => {
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
 
@@ -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
- const Clock = (ticks) => {
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.12.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
 
@@ -70,21 +114,23 @@ const unbase32 = (text) => {
70
114
  bits -= 8;
71
115
  }
72
116
  }
73
- return Buffer.from(out);
117
+ return Uint8Array.from(out);
74
118
  };
75
119
 
76
120
  const Tools = Object.freeze({
77
121
  address: (pk) => {
78
- const payload = Buffer.concat([Buffer.from([VERSION_G]), pk]);
122
+ const payload = Bytes.concat([Uint8Array.of(VERSION_G), pk]);
79
123
  const crc = crc16(payload);
80
- return base32(Buffer.concat([payload, Buffer.from([crc & 0xff, crc >> 8])]));
124
+ return base32(Bytes.concat([payload, Uint8Array.of(crc & 0xff, crc >> 8)]));
81
125
  },
82
- canon: (claim) => Buffer.from(JSON.stringify(ordered(claim))),
83
- derive: (root, label) => createHash('sha256').update(root).update(label).digest(),
84
- digest: (bytes) => createHash('sha256').update(bytes).digest('hex'),
85
- 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) => {
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 ? Buffer.from(payload.subarray(1)) : null;
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 };