nervur 0.19.2 → 0.20.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.
Files changed (205) hide show
  1. package/README.md +66 -101
  2. package/dist/being/being.d.ts +7 -15
  3. package/dist/being/being.js +16 -67
  4. package/dist/being/digest.d.ts +1 -1
  5. package/dist/being/digest.js +7 -27
  6. package/dist/being/faculty.d.ts +9 -0
  7. package/dist/being/faculty.js +68 -0
  8. package/dist/being/index.d.ts +4 -5
  9. package/dist/being/index.js +5 -5
  10. package/dist/being/types.d.ts +35 -62
  11. package/dist/being/types.js +3 -57
  12. package/dist/being/words.d.ts +13 -0
  13. package/dist/being/words.js +14 -0
  14. package/dist/contract/index.d.ts +26 -0
  15. package/dist/contract/index.js +19 -0
  16. package/dist/crypto/aes.d.ts +3 -0
  17. package/dist/crypto/aes.js +24 -0
  18. package/dist/crypto/bytes.d.ts +6 -0
  19. package/dist/crypto/bytes.js +33 -0
  20. package/dist/crypto/ed25519.d.ts +3 -0
  21. package/dist/crypto/ed25519.js +85 -0
  22. package/dist/crypto/hash.d.ts +2 -0
  23. package/dist/crypto/hash.js +11 -0
  24. package/dist/crypto/index.d.ts +7 -0
  25. package/dist/crypto/index.js +10 -0
  26. package/dist/crypto/json.d.ts +8 -0
  27. package/dist/crypto/json.js +250 -0
  28. package/dist/crypto/mlkem.d.ts +12 -0
  29. package/dist/crypto/mlkem.js +36 -0
  30. package/dist/crypto/subtle.d.ts +10 -0
  31. package/dist/crypto/subtle.js +29 -0
  32. package/dist/crypto/x25519.d.ts +2 -0
  33. package/dist/crypto/x25519.js +21 -0
  34. package/dist/folder/index.d.ts +13 -0
  35. package/dist/folder/index.js +125 -0
  36. package/dist/harbor/dock.d.ts +19 -0
  37. package/dist/harbor/dock.js +92 -0
  38. package/dist/harbor/harbor.d.ts +22 -0
  39. package/dist/harbor/harbor.js +84 -0
  40. package/dist/harbor/index.d.ts +4 -10
  41. package/dist/harbor/index.js +5 -18
  42. package/dist/harbor/registry.d.ts +7 -0
  43. package/dist/harbor/registry.js +13 -0
  44. package/dist/harbor/terrain.d.ts +8 -0
  45. package/dist/harbor/terrain.js +2 -0
  46. package/dist/index.d.ts +7 -0
  47. package/dist/index.js +14 -0
  48. package/dist/pointer/bodies.d.ts +20 -0
  49. package/dist/pointer/bodies.js +45 -0
  50. package/dist/pointer/index.d.ts +2 -0
  51. package/dist/pointer/index.js +4 -0
  52. package/dist/pointer/world.d.ts +34 -0
  53. package/dist/pointer/world.js +76 -0
  54. package/dist/quo/door.d.ts +34 -0
  55. package/dist/quo/door.js +172 -0
  56. package/dist/quo/index.d.ts +8 -0
  57. package/dist/quo/index.js +11 -0
  58. package/dist/quo/invitation.d.ts +7 -0
  59. package/dist/quo/invitation.js +15 -0
  60. package/dist/quo/keys.d.ts +40 -0
  61. package/dist/quo/keys.js +79 -0
  62. package/dist/quo/payload.d.ts +15 -0
  63. package/dist/quo/payload.js +55 -0
  64. package/dist/quo/relations.d.ts +40 -0
  65. package/dist/quo/relations.js +33 -0
  66. package/dist/quo/reply.d.ts +13 -0
  67. package/dist/quo/reply.js +35 -0
  68. package/dist/quo/seal.d.ts +42 -0
  69. package/dist/quo/seal.js +78 -0
  70. package/dist/quo/standing.d.ts +39 -0
  71. package/dist/quo/standing.js +90 -0
  72. package/dist/ward/allowance.d.ts +13 -9
  73. package/dist/ward/allowance.js +28 -67
  74. package/dist/ward/cells.d.ts +6 -6
  75. package/dist/ward/cells.js +67 -179
  76. package/dist/ward/index.d.ts +5 -10
  77. package/dist/ward/index.js +6 -13
  78. package/dist/ward/partition.d.ts +44 -60
  79. package/dist/ward/partition.js +56 -285
  80. package/dist/ward/stance.d.ts +29 -20
  81. package/dist/ward/stance.js +173 -406
  82. package/dist/ward/ward-being.d.ts +33 -0
  83. package/dist/ward/ward-being.js +76 -0
  84. package/dist/ward/ward.d.ts +43 -11
  85. package/dist/ward/ward.js +215 -363
  86. package/package.json +19 -33
  87. package/src/being/being.ts +31 -78
  88. package/src/being/digest.ts +7 -35
  89. package/src/being/faculty.ts +66 -0
  90. package/src/being/index.ts +5 -6
  91. package/src/being/types.ts +47 -145
  92. package/src/being/words.ts +31 -0
  93. package/src/contract/index.ts +44 -0
  94. package/src/crypto/aes.ts +26 -0
  95. package/src/crypto/bytes.ts +37 -0
  96. package/src/crypto/ed25519.ts +84 -0
  97. package/src/crypto/hash.ts +14 -0
  98. package/src/crypto/index.ts +10 -0
  99. package/src/crypto/json.ts +241 -0
  100. package/src/crypto/mlkem.ts +38 -0
  101. package/src/crypto/subtle.ts +33 -0
  102. package/src/crypto/x25519.ts +21 -0
  103. package/src/folder/index.ts +134 -0
  104. package/src/harbor/dock.ts +101 -0
  105. package/src/harbor/harbor.ts +105 -0
  106. package/src/harbor/index.ts +5 -19
  107. package/src/harbor/registry.ts +20 -0
  108. package/src/harbor/terrain.ts +13 -0
  109. package/src/index.ts +20 -0
  110. package/src/pointer/bodies.ts +47 -0
  111. package/src/pointer/index.ts +4 -0
  112. package/src/pointer/world.ts +91 -0
  113. package/src/quo/door.ts +178 -0
  114. package/src/quo/index.ts +11 -0
  115. package/src/quo/invitation.ts +15 -0
  116. package/src/quo/keys.ts +91 -0
  117. package/src/quo/payload.ts +61 -0
  118. package/src/quo/relations.ts +62 -0
  119. package/src/quo/reply.ts +38 -0
  120. package/src/quo/seal.ts +101 -0
  121. package/src/quo/standing.ts +111 -0
  122. package/src/stand/main.ts +19 -0
  123. package/src/stand/stand.ts +178 -0
  124. package/src/ward/allowance.ts +37 -75
  125. package/src/ward/cells.ts +63 -176
  126. package/src/ward/index.ts +6 -17
  127. package/src/ward/partition.ts +86 -326
  128. package/src/ward/stance.ts +185 -420
  129. package/src/ward/ward-being.ts +97 -0
  130. package/src/ward/ward.ts +229 -363
  131. package/dist/being/lent.d.ts +0 -32
  132. package/dist/being/lent.js +0 -72
  133. package/dist/being/silence.d.ts +0 -12
  134. package/dist/being/silence.js +0 -41
  135. package/dist/conformance/assert.d.ts +0 -11
  136. package/dist/conformance/assert.js +0 -106
  137. package/dist/conformance/beings.d.ts +0 -199
  138. package/dist/conformance/beings.js +0 -188
  139. package/dist/conformance/estate.d.ts +0 -5
  140. package/dist/conformance/estate.js +0 -388
  141. package/dist/conformance/index.d.ts +0 -80
  142. package/dist/conformance/index.js +0 -819
  143. package/dist/conformance/reach.d.ts +0 -10
  144. package/dist/conformance/reach.js +0 -72
  145. package/dist/conformance/store.d.ts +0 -5
  146. package/dist/conformance/store.js +0 -113
  147. package/dist/harbor/box.d.ts +0 -121
  148. package/dist/harbor/box.js +0 -121
  149. package/dist/harbor/core.d.ts +0 -55
  150. package/dist/harbor/core.js +0 -662
  151. package/dist/harbor/dial.d.ts +0 -9
  152. package/dist/harbor/dial.js +0 -81
  153. package/dist/harbor/memory.d.ts +0 -29
  154. package/dist/harbor/memory.js +0 -104
  155. package/dist/harbor/reach.d.ts +0 -36
  156. package/dist/harbor/reach.js +0 -199
  157. package/dist/harbor/store.d.ts +0 -35
  158. package/dist/harbor/store.js +0 -62
  159. package/dist/vector/cases.d.ts +0 -42
  160. package/dist/vector/cases.js +0 -223
  161. package/dist/vector/index.d.ts +0 -6
  162. package/dist/vector/index.js +0 -8
  163. package/dist/vector/stand.d.ts +0 -9
  164. package/dist/vector/stand.js +0 -77
  165. package/dist/vector/world.d.ts +0 -144
  166. package/dist/vector/world.js +0 -209
  167. package/dist/ward/arithmetic.d.ts +0 -31
  168. package/dist/ward/arithmetic.js +0 -249
  169. package/dist/ward/door.d.ts +0 -18
  170. package/dist/ward/door.js +0 -186
  171. package/dist/ward/ground.d.ts +0 -29
  172. package/dist/ward/ground.js +0 -56
  173. package/dist/ward/heirs.d.ts +0 -13
  174. package/dist/ward/heirs.js +0 -117
  175. package/dist/ward/json.d.ts +0 -2
  176. package/dist/ward/json.js +0 -163
  177. package/dist/ward/owner.d.ts +0 -13
  178. package/dist/ward/owner.js +0 -220
  179. package/dist/ward/seal.d.ts +0 -52
  180. package/dist/ward/seal.js +0 -150
  181. package/src/being/lent.ts +0 -72
  182. package/src/being/silence.ts +0 -46
  183. package/src/conformance/assert.ts +0 -100
  184. package/src/conformance/beings.ts +0 -188
  185. package/src/conformance/estate.ts +0 -412
  186. package/src/conformance/index.ts +0 -965
  187. package/src/conformance/reach.ts +0 -83
  188. package/src/conformance/store.ts +0 -125
  189. package/src/harbor/box.ts +0 -131
  190. package/src/harbor/core.ts +0 -699
  191. package/src/harbor/dial.ts +0 -112
  192. package/src/harbor/memory.ts +0 -123
  193. package/src/harbor/reach.ts +0 -221
  194. package/src/harbor/store.ts +0 -91
  195. package/src/vector/cases.ts +0 -257
  196. package/src/vector/index.ts +0 -11
  197. package/src/vector/stand.ts +0 -76
  198. package/src/vector/world.ts +0 -232
  199. package/src/ward/arithmetic.ts +0 -251
  200. package/src/ward/door.ts +0 -186
  201. package/src/ward/ground.ts +0 -142
  202. package/src/ward/heirs.ts +0 -116
  203. package/src/ward/json.ts +0 -144
  204. package/src/ward/owner.ts +0 -214
  205. package/src/ward/seal.ts +0 -178
@@ -1,58 +1,4 @@
1
- // The ids the ward speaks under, and no being may mint. An asker id is hers:
2
- // she chooses the strings, and the ward has no business in her namespace. But
3
- // the ward must sometimes stand at her door itself — its describe asks every
4
- // being under OWNER — and an id it shares with one of her occupants is two
5
- // different parties wearing one name. She could not tell them apart, and the
6
- // ward would shape its describe through an occupant's gate.
7
- //
8
- // So the ward's words are shouted, and refused at the mint. A word can only
9
- // be reserved before anyone has used it: a partition already holding an
10
- // occupant of that name could never have it taken back. PUBLIC guards nothing
11
- // today — a public asker is {} and carries no id at all — and that is exactly
12
- // why it is claimed now, while claiming it is free.
13
1
  export const OWNER = 'OWNER';
14
- export const PUBLIC = 'PUBLIC';
15
- export const RESERVED_IDS = [OWNER, PUBLIC];
16
- // An invitation as one object of values, which is what it already is. A being
17
- // who hands one on sends it as args, and args are values; this is the one
18
- // place that is spelled, so nobody spells it with a cast. Absent stays absent:
19
- // an invitation to a public being carries no heir and no secret, and a key
20
- // present and undefined is not the same object once JSON has been through it.
21
- export const invitationArgs = (inv) => ({
22
- ward: inv.ward,
23
- ...(inv.heir !== undefined ? { heir: inv.heir } : {}),
24
- ...(inv.secret !== undefined ? { secret: inv.secret } : {}),
25
- });
26
- // Whether a value that arrived is an invitation. The shape is the spec's, so
27
- // the reading of it is too, and it is one reading: a form that
28
- // asks a guest for one, a lent being handed one to wake her maker by, and a
29
- // shell that finds one in a link all ask the same question, and a kinder
30
- // answer in one of them is a value that fails at a door instead of at the
31
- // edge it came in by. A ward pk is a hundred and twenty-eight lowercase hex,
32
- // and a heir comes with its secret or neither comes: an invitation to a
33
- // public being carries no heir, and a heir with no secret opens nothing.
34
- export const isInvitation = (v) => {
35
- if (v === null || typeof v !== 'object' || Array.isArray(v))
36
- return false;
37
- const o = v;
38
- if (typeof o.ward !== 'string' || !/^[0-9a-f]{128}$/.test(o.ward))
39
- return false;
40
- const heir = typeof o.heir === 'string', secret = typeof o.secret === 'string';
41
- return (heir && secret) || (!heir && !secret && !('heir' in o) && !('secret' in o));
42
- };
43
- // Whether what came back from an empty ask is a blueprint. Every describe on
44
- // the far side of a door is somebody else's code, so nothing may be written
45
- // into a standing's record as a blueprint without being read as one first: a
46
- // side walks `asks` by name, and a list of anything else, or no list at all,
47
- // is a side that breaks on a far ward's answer. The asks are checked to the
48
- // depth a side reads them, a name and an input each, and no further: what a
49
- // far being puts beside those is hers.
50
- export const isBlueprint = (v) => {
51
- if (v === null || typeof v !== 'object' || Array.isArray(v))
52
- return false;
53
- const { asks, notes } = v;
54
- if (!Array.isArray(asks) || notes === undefined)
55
- return false;
56
- return asks.every((a) => a !== null && typeof a === 'object' && !Array.isArray(a) && typeof a.name === 'string' && a.input !== null && typeof a.input === 'object' && !Array.isArray(a.input));
57
- };
58
- export const WORD_KEY = Symbol.for('quo.word');
2
+ // The ward-being's key in her ward, and so the id every being she makes
3
+ // knows her by.
4
+ export const WARD = 'ward';
@@ -0,0 +1,13 @@
1
+ import type { Word as DoorWord } from '../quo/index.ts';
2
+ export declare const silence: unique symbol;
3
+ export type Silence = typeof silence;
4
+ export declare const isSilence: (x: unknown) => x is Silence;
5
+ export type WordName = DoorWord | 'unreached' | 'late' | 'dropped' | 'invitation';
6
+ declare const KEY: unique symbol;
7
+ export type Word<W extends WordName = WordName> = {
8
+ readonly [KEY]: W;
9
+ };
10
+ export declare const word: <W extends WordName>(name: W) => Word<W>;
11
+ export declare const isWord: (x: unknown) => x is Word;
12
+ export declare const told: (x: unknown) => unknown;
13
+ export {};
@@ -0,0 +1,14 @@
1
+ export const silence = Symbol.for('nervur.silence');
2
+ export const isSilence = (x) => x === silence;
3
+ const KEY = Symbol.for('nervur.word');
4
+ const made = new Map();
5
+ export const word = (name) => {
6
+ let w = made.get(name);
7
+ if (!w)
8
+ made.set(name, (w = Object.freeze({ [KEY]: name })));
9
+ return w;
10
+ };
11
+ export const isWord = (x) => typeof x === 'object' && x !== null && typeof x[KEY] === 'string';
12
+ // A word by its name, and anything else as it is, so one comparison says
13
+ // which answer came back.
14
+ export const told = (x) => (isWord(x) ? x[KEY] : x);
@@ -0,0 +1,26 @@
1
+ import type { Draw } from '../quo/index.ts';
2
+ export declare abstract class Entropy {
3
+ abstract draw(length: number): Uint8Array;
4
+ get drawer(): Draw;
5
+ }
6
+ export declare abstract class Clock {
7
+ abstract now(): number;
8
+ abstract wait(ms: number): {
9
+ done: Promise<void>;
10
+ cancel(): void;
11
+ };
12
+ }
13
+ export interface Rows {
14
+ readonly rows: Record<string, unknown>;
15
+ told(row: string): void;
16
+ kept(): Promise<boolean>;
17
+ }
18
+ export declare abstract class Memory {
19
+ abstract open(name: string): Promise<Rows>;
20
+ }
21
+ export declare abstract class Custody {
22
+ abstract seed(name: string): Promise<Uint8Array>;
23
+ }
24
+ export declare abstract class Carrier {
25
+ abstract carry(pk: string, bytes: Uint8Array): Promise<Uint8Array | null>;
26
+ }
@@ -0,0 +1,19 @@
1
+ // Drawn bytes.
2
+ export class Entropy {
3
+ // The same source, as the function `quo/` takes.
4
+ get drawer() {
5
+ return (length) => this.draw(length);
6
+ }
7
+ }
8
+ // The moment, and a wait that ends.
9
+ export class Clock {
10
+ }
11
+ export class Memory {
12
+ }
13
+ // A ward's seed by the harbor's name for it: thirty-two bytes, the same
14
+ // every time that name is asked.
15
+ export class Custody {
16
+ }
17
+ // Bytes to a ward pk in; bytes, or null for nothing, out.
18
+ export class Carrier {
19
+ }
@@ -0,0 +1,3 @@
1
+ export declare const TAG = 16;
2
+ export declare const encrypt: (key: Uint8Array, nonce: Uint8Array, additional: Uint8Array, plaintext: Uint8Array) => Promise<Uint8Array>;
3
+ export declare const decrypt: (key: Uint8Array, nonce: Uint8Array, additional: Uint8Array, ciphertext: Uint8Array) => Promise<Uint8Array | null>;
@@ -0,0 +1,24 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // AES-256-GCM with a sixteen-byte tag at the end of the ciphertext.
3
+ import { own, subtle } from './subtle.js';
4
+ export const TAG = 16;
5
+ const cipher = (key, nonce, additional) => ({
6
+ params: { name: 'AES-GCM', iv: own(nonce), additionalData: own(additional), tagLength: TAG * 8 },
7
+ key: subtle().importKey('raw', own(key), 'AES-GCM', false, ['encrypt', 'decrypt']),
8
+ });
9
+ export const encrypt = async (key, nonce, additional, plaintext) => {
10
+ const c = cipher(key, nonce, additional);
11
+ return new Uint8Array(await subtle().encrypt(c.params, await c.key, own(plaintext)));
12
+ };
13
+ // The plaintext, or null when the ciphertext does not open.
14
+ export const decrypt = async (key, nonce, additional, ciphertext) => {
15
+ if (ciphertext.length < TAG)
16
+ return null;
17
+ const c = cipher(key, nonce, additional);
18
+ try {
19
+ return new Uint8Array(await subtle().decrypt(c.params, await c.key, own(ciphertext)));
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ };
@@ -0,0 +1,6 @@
1
+ export declare const hex: (bytes: Uint8Array) => string;
2
+ export declare const isHex: (text: unknown, length?: number) => text is string;
3
+ export declare const unhex: (text: string) => Uint8Array;
4
+ export declare const concat: (...parts: Uint8Array[]) => Uint8Array;
5
+ export declare const zero: (bytes: Uint8Array) => boolean;
6
+ export declare const utf8: (text: string) => Uint8Array;
@@ -0,0 +1,33 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Bytes and the one text spelling Quo gives them: hex, two lowercase digits
3
+ // a byte.
4
+ const DIGITS = Array.from({ length: 256 }, (_, at) => at.toString(16).padStart(2, '0'));
5
+ const HEX = /^(?:[0-9a-f]{2})*$/;
6
+ export const hex = (bytes) => {
7
+ let out = '';
8
+ for (const byte of bytes)
9
+ out += DIGITS[byte];
10
+ return out;
11
+ };
12
+ // Hex of `length` bytes, or any length when none is named.
13
+ export const isHex = (text, length) => typeof text === 'string' && HEX.test(text) && (length === undefined || text.length === length * 2);
14
+ export const unhex = (text) => {
15
+ if (!isHex(text))
16
+ throw new TypeError('not hex');
17
+ const out = new Uint8Array(text.length / 2);
18
+ for (let at = 0; at < out.length; at += 1)
19
+ out[at] = parseInt(text.slice(at * 2, at * 2 + 2), 16);
20
+ return out;
21
+ };
22
+ export const concat = (...parts) => {
23
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
24
+ let at = 0;
25
+ for (const part of parts) {
26
+ out.set(part, at);
27
+ at += part.length;
28
+ }
29
+ return out;
30
+ };
31
+ export const zero = (bytes) => bytes.every((b) => b === 0);
32
+ const encoder = new TextEncoder();
33
+ export const utf8 = (text) => encoder.encode(text);
@@ -0,0 +1,3 @@
1
+ export declare const ed25519Public: (secret: Uint8Array) => Promise<Uint8Array>;
2
+ export declare const sign: (secret: Uint8Array, message: Uint8Array) => Promise<Uint8Array>;
3
+ export declare const verify: (pk: Uint8Array, message: Uint8Array, signature: Uint8Array) => Promise<boolean>;
@@ -0,0 +1,85 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Ed25519 signs, as RFC 8032 defines it, and verifies with the cofactorless
3
+ // check. The spec names exactly where a check fails, and Web Crypto promises
4
+ // none of the decoding rules, so they are held here before Web Crypto runs:
5
+ //
6
+ // a signature of any length but sixty-four
7
+ // an `s` at or above the group order
8
+ // an `R` that does not decode, or whose bytes are not its point's encoding
9
+ // a public key that does not decode, or whose y is at or above the prime
10
+ // a public key of small order, in any spelling
11
+ import { importSecret, own, publicOf, subtle } from './subtle.js';
12
+ const P = 2n ** 255n - 19n;
13
+ const L = 2n ** 252n + 27742317777372353535851937790883648493n;
14
+ const mod = (a) => ((a % P) + P) % P;
15
+ const power = (base, exponent) => {
16
+ let out = 1n;
17
+ let b = mod(base);
18
+ for (let e = exponent; e > 0n; e >>= 1n) {
19
+ if (e & 1n)
20
+ out = (out * b) % P;
21
+ b = (b * b) % P;
22
+ }
23
+ return out;
24
+ };
25
+ const D = mod(-121665n * power(121666n, P - 2n));
26
+ const ROOT_MINUS_ONE = power(2n, (P - 1n) / 4n);
27
+ const little = (bytes) => bytes.reduceRight((n, b) => (n << 8n) | BigInt(b), 0n);
28
+ // A point from its thirty-two bytes, or null where RFC 8032 section 5.1.3
29
+ // refuses it, the sign bit set on x = 0 included.
30
+ const decode = (bytes) => {
31
+ if (bytes.length !== 32)
32
+ return null;
33
+ const sign = (bytes[31] & 0x80) !== 0;
34
+ const y = little(bytes) & ((1n << 255n) - 1n);
35
+ if (y >= P)
36
+ return null;
37
+ const u = mod(y * y - 1n);
38
+ const v = mod(D * y * y + 1n);
39
+ let x = mod(u * power(v, 3n) * power(u * power(v, 7n), (P - 5n) / 8n));
40
+ const check = mod(v * x * x);
41
+ if (check === mod(-u))
42
+ x = mod(x * ROOT_MINUS_ONE);
43
+ else if (check !== u)
44
+ return null;
45
+ if (x === 0n && sign)
46
+ return null;
47
+ if ((x & 1n) !== (sign ? 1n : 0n))
48
+ x = mod(-x);
49
+ return { x, y, z: 1n, t: mod(x * y) };
50
+ };
51
+ const add = (a, b) => {
52
+ const A = mod((a.y - a.x) * (b.y - b.x));
53
+ const B = mod((a.y + a.x) * (b.y + b.x));
54
+ const C = mod(2n * D * a.t * b.t);
55
+ const E = mod(2n * a.z * b.z);
56
+ const [e, f, g, h] = [B - A, E - C, E + C, B + A];
57
+ return { x: mod(e * f), y: mod(g * h), z: mod(f * g), t: mod(e * h) };
58
+ };
59
+ // Small order: eight times the point is the identity.
60
+ const smallOrder = (p) => {
61
+ let q = p;
62
+ for (let i = 0; i < 3; i += 1)
63
+ q = add(q, q);
64
+ return q.x === 0n && q.y === q.z;
65
+ };
66
+ export const ed25519Public = async (secret) => publicOf(await importSecret('Ed25519', secret, ['sign']));
67
+ export const sign = async (secret, message) => new Uint8Array(await subtle().sign('Ed25519', await importSecret('Ed25519', secret, ['sign']), own(message)));
68
+ export const verify = async (pk, message, signature) => {
69
+ if (signature.length !== 64)
70
+ return false;
71
+ const key = decode(pk);
72
+ if (key === null || smallOrder(key))
73
+ return false;
74
+ if (decode(signature.subarray(0, 32)) === null)
75
+ return false;
76
+ if (little(signature.subarray(32)) >= L)
77
+ return false;
78
+ try {
79
+ const imported = await subtle().importKey('raw', own(pk), { name: 'Ed25519' }, false, ['verify']);
80
+ return await subtle().verify('Ed25519', imported, own(signature), own(message));
81
+ }
82
+ catch {
83
+ return false;
84
+ }
85
+ };
@@ -0,0 +1,2 @@
1
+ export declare const sha256: (bytes: Uint8Array) => Promise<Uint8Array>;
2
+ export declare const hkdf: (ikm: Uint8Array, info: Uint8Array, length: number) => Promise<Uint8Array>;
@@ -0,0 +1,11 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SHA-256 hashes, and HKDF-SHA-256 derives under the zero-length salt, with
3
+ // the label's ASCII bytes as `info`.
4
+ import { own, subtle } from './subtle.js';
5
+ const SALT = new Uint8Array(0);
6
+ export const sha256 = async (bytes) => new Uint8Array(await subtle().digest('SHA-256', own(bytes)));
7
+ export const hkdf = async (ikm, info, length) => {
8
+ const material = await subtle().importKey('raw', own(ikm), 'HKDF', false, ['deriveBits']);
9
+ const bits = await subtle().deriveBits({ name: 'HKDF', hash: 'SHA-256', salt: SALT, info: own(info) }, material, length * 8);
10
+ return new Uint8Array(bits);
11
+ };
@@ -0,0 +1,7 @@
1
+ export { concat, hex, isHex, unhex, utf8, zero } from './bytes.ts';
2
+ export { hkdf, sha256 } from './hash.ts';
3
+ export { isCount, readObject, readValue, writeValue, type Fields, type Json } from './json.ts';
4
+ export { decrypt, encrypt, TAG } from './aes.ts';
5
+ export { agree, x25519Public } from './x25519.ts';
6
+ export { ed25519Public, sign, verify } from './ed25519.ts';
7
+ export { CIPHERTEXT, decapsulate, EK, encapsulate, lockFrom, type Lock } from './mlkem.ts';
@@ -0,0 +1,10 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // The six algorithms Quo names, and bytes. Arithmetic only: no label, no
3
+ // key of Quo's, no decision.
4
+ export { concat, hex, isHex, unhex, utf8, zero } from './bytes.js';
5
+ export { hkdf, sha256 } from './hash.js';
6
+ export { isCount, readObject, readValue, writeValue } from './json.js';
7
+ export { decrypt, encrypt, TAG } from './aes.js';
8
+ export { agree, x25519Public } from './x25519.js';
9
+ export { ed25519Public, sign, verify } from './ed25519.js';
10
+ export { CIPHERTEXT, decapsulate, EK, encapsulate, lockFrom } from './mlkem.js';
@@ -0,0 +1,8 @@
1
+ export type Json = null | boolean | number | string | Json[] | {
2
+ [key: string]: Json;
3
+ };
4
+ export type Fields = ReadonlyMap<string, string>;
5
+ export declare const readObject: (input: Uint8Array | string) => Fields | null;
6
+ export declare const readValue: (input: Uint8Array | string, depth: number) => Json | undefined;
7
+ export declare const writeValue: (value: unknown, depth: number) => string | undefined;
8
+ export declare const isCount: (literal: string) => boolean;
@@ -0,0 +1,250 @@
1
+ const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
2
+ const NUMBER = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
3
+ const ESCAPE = /\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4})/y;
4
+ const LONE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
5
+ const LITERALS = ['true', 'false', 'null'];
6
+ const skip = (s, at) => {
7
+ while (at < s.length && ' \t\n\r'.includes(s[at]))
8
+ at += 1;
9
+ return at;
10
+ };
11
+ // The end of the string opening at `at`, or -1.
12
+ const stringEnd = (s, at) => {
13
+ if (s[at] !== '"')
14
+ return -1;
15
+ for (at += 1; at < s.length;) {
16
+ const c = s.charCodeAt(at);
17
+ if (c === 0x22)
18
+ return at + 1;
19
+ if (c < 0x20)
20
+ return -1;
21
+ if (c !== 0x5c) {
22
+ at += 1;
23
+ continue;
24
+ }
25
+ ESCAPE.lastIndex = at;
26
+ if (!ESCAPE.test(s))
27
+ return -1;
28
+ at = ESCAPE.lastIndex;
29
+ }
30
+ return -1;
31
+ };
32
+ const scalarEnd = (s, at) => {
33
+ if (s[at] === '"')
34
+ return stringEnd(s, at);
35
+ const literal = LITERALS.find((word) => s.startsWith(word, at));
36
+ if (literal)
37
+ return at + literal.length;
38
+ NUMBER.lastIndex = at;
39
+ return NUMBER.test(s) ? NUMBER.lastIndex : -1;
40
+ };
41
+ // A key, its colon and the space after it: the start of the key's value, or -1.
42
+ const keyEnd = (s, at) => {
43
+ const end = stringEnd(s, at);
44
+ if (end < 0)
45
+ return -1;
46
+ const colon = skip(s, end);
47
+ return s[colon] === ':' ? skip(s, colon + 1) : -1;
48
+ };
49
+ // The end of the value starting at `at`, held to the grammar at any depth
50
+ // without recursion, or -1.
51
+ const valueEnd = (s, at) => {
52
+ const open = [];
53
+ for (;;) {
54
+ const c = s[at];
55
+ if (c === '{' || c === '[') {
56
+ at = skip(s, at + 1);
57
+ if (s[at] === (c === '{' ? '}' : ']'))
58
+ at += 1;
59
+ else {
60
+ open.push(c);
61
+ if (c === '{' && (at = keyEnd(s, at)) < 0)
62
+ return -1;
63
+ continue;
64
+ }
65
+ }
66
+ else if ((at = scalarEnd(s, at)) < 0)
67
+ return -1;
68
+ for (;;) {
69
+ if (open.length === 0)
70
+ return at;
71
+ at = skip(s, at);
72
+ const top = open.at(-1);
73
+ if (s[at] === ',') {
74
+ at = skip(s, at + 1);
75
+ if (top === '{' && (at = keyEnd(s, at)) < 0)
76
+ return -1;
77
+ break;
78
+ }
79
+ if (s[at] !== (top === '{' ? '}' : ']'))
80
+ return -1;
81
+ open.pop();
82
+ at += 1;
83
+ }
84
+ }
85
+ };
86
+ const text = (input) => {
87
+ if (typeof input === 'string')
88
+ return input;
89
+ try {
90
+ return decoder.decode(input);
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ };
96
+ export const readObject = (input) => {
97
+ const s = text(input);
98
+ if (s === null)
99
+ return null;
100
+ const fields = new Map();
101
+ let at = skip(s, 0);
102
+ if (s[at] !== '{')
103
+ return null;
104
+ at = skip(s, at + 1);
105
+ if (s[at] === '}')
106
+ return skip(s, at + 1) === s.length ? fields : null;
107
+ for (;;) {
108
+ const keyStart = at;
109
+ const valueStart = keyEnd(s, at);
110
+ if (valueStart < 0)
111
+ return null;
112
+ const key = JSON.parse(s.slice(keyStart, stringEnd(s, keyStart)));
113
+ if (fields.has(key))
114
+ return null;
115
+ const end = valueEnd(s, valueStart);
116
+ if (end < 0)
117
+ return null;
118
+ fields.set(key, s.slice(valueStart, end));
119
+ at = skip(s, end);
120
+ if (s[at] === '}')
121
+ return skip(s, at + 1) === s.length ? fields : null;
122
+ if (s[at] !== ',')
123
+ return null;
124
+ at = skip(s, at + 1);
125
+ }
126
+ };
127
+ // A number text a double holds as written: a fraction, or a whole number
128
+ // the double it reads as is exactly, never past the largest double, never
129
+ // a value that rounds to zero, never minus zero.
130
+ const exactNumber = (literal) => {
131
+ const [, sign, whole, frac = '', exp = '0'] = /^(-?)(0|[1-9]\d*)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/.exec(literal);
132
+ const v = Number(literal);
133
+ if (!Number.isFinite(v))
134
+ return false;
135
+ const digits = (whole + frac).replace(/^0+/, '');
136
+ if (v === 0)
137
+ return digits === '' && sign === '';
138
+ const trimmed = digits.replace(/0+$/, '');
139
+ const e = BigInt(exp) - BigInt(frac.length) + BigInt(digits.length - trimmed.length);
140
+ if (e < 0n)
141
+ return true;
142
+ return BigInt(trimmed) * 10n ** e === (v < 0 ? -BigInt(v) : BigInt(v)) || literal === String(v);
143
+ };
144
+ const depthOf = (s) => {
145
+ let depth = 0;
146
+ let deepest = 0;
147
+ for (let at = 0; at < s.length; at += 1) {
148
+ const c = s[at];
149
+ if (c === '"')
150
+ at = stringEnd(s, at) - 1;
151
+ else if (c === '{' || c === '[')
152
+ deepest = Math.max(deepest, (depth += 1));
153
+ else if (c === '}' || c === ']')
154
+ depth -= 1;
155
+ }
156
+ return deepest;
157
+ };
158
+ // Parses a text already held to the grammar and to the depth bound.
159
+ const parse = (s) => {
160
+ let at = 0;
161
+ const value = () => {
162
+ at = skip(s, at);
163
+ const c = s[at];
164
+ if (c === '{' || c === '[') {
165
+ const object = c === '{';
166
+ const out = object ? {} : [];
167
+ at = skip(s, at + 1);
168
+ if (s[at] === (object ? '}' : ']')) {
169
+ at += 1;
170
+ return out;
171
+ }
172
+ for (;;) {
173
+ if (object) {
174
+ const key = str();
175
+ if (Object.hasOwn(out, key))
176
+ throw new Error('two keys of one name');
177
+ at = skip(s, at) + 1;
178
+ Object.defineProperty(out, key, { value: value(), enumerable: true, writable: true, configurable: true });
179
+ }
180
+ else
181
+ out.push(value());
182
+ at = skip(s, at);
183
+ if (s[at++] !== ',')
184
+ return out;
185
+ }
186
+ }
187
+ if (c === '"')
188
+ return str();
189
+ const end = scalarEnd(s, at);
190
+ const literal = s.slice(at, end);
191
+ at = end;
192
+ if (LITERALS.includes(literal))
193
+ return JSON.parse(literal);
194
+ if (!exactNumber(literal))
195
+ throw new Error('a number no double holds as written');
196
+ return Number(literal);
197
+ };
198
+ const str = () => {
199
+ at = skip(s, at);
200
+ const end = stringEnd(s, at);
201
+ const out = JSON.parse(s.slice(at, end));
202
+ if (LONE.test(out))
203
+ throw new Error('a lone surrogate');
204
+ at = end;
205
+ return out;
206
+ };
207
+ return value();
208
+ };
209
+ export const readValue = (input, depth) => {
210
+ const s = text(input);
211
+ if (s === null)
212
+ return undefined;
213
+ const start = skip(s, 0);
214
+ const end = valueEnd(s, start);
215
+ if (end < 0 || skip(s, end) !== s.length || depthOf(s) > depth)
216
+ return undefined;
217
+ try {
218
+ return parse(s);
219
+ }
220
+ catch {
221
+ return undefined;
222
+ }
223
+ };
224
+ // A value as JSON text, or undefined for one that JSON would drop, rewrite
225
+ // or refuse: anything but null, a boolean, a finite number other than minus
226
+ // zero, well-formed text, a list with no holes, or a plain object of own
227
+ // string keys, nested no deeper than `depth`, with no cycle.
228
+ export const writeValue = (value, depth) => {
229
+ const fits = (v, room, path) => {
230
+ if (v === null || typeof v === 'boolean')
231
+ return true;
232
+ if (typeof v === 'string')
233
+ return !LONE.test(v);
234
+ if (typeof v === 'number')
235
+ return Number.isFinite(v) && !Object.is(v, -0);
236
+ if (typeof v !== 'object' || room === 0 || path.has(v))
237
+ return false;
238
+ path.add(v);
239
+ const ok = Array.isArray(v)
240
+ ? Object.keys(v).length === v.length && v.every((x) => fits(x, room - 1, path))
241
+ : [Object.prototype, null].includes(Object.getPrototypeOf(v)) &&
242
+ Object.getOwnPropertySymbols(v).length === 0 &&
243
+ Object.entries(Object.getOwnPropertyDescriptors(v)).every(([k, d]) => !LONE.test(k) && d.enumerable === true && 'value' in d && fits(d.value, room - 1, path));
244
+ path.delete(v);
245
+ return ok;
246
+ };
247
+ return fits(value, depth, new Set()) ? JSON.stringify(value) : undefined;
248
+ };
249
+ // A count number: no sign, no fraction, no exponent, from 1 to 2^53 - 1.
250
+ export const isCount = (literal) => /^[1-9]\d{0,15}$/.test(literal) && Number(literal) <= Number.MAX_SAFE_INTEGER;
@@ -0,0 +1,12 @@
1
+ export declare const EK = 1184;
2
+ export declare const CIPHERTEXT = 1088;
3
+ export type Lock = {
4
+ ek: Uint8Array;
5
+ dk: Uint8Array;
6
+ };
7
+ export declare const lockFrom: (d: Uint8Array, z: Uint8Array) => Lock;
8
+ export declare const encapsulate: (ek: Uint8Array, m: Uint8Array) => {
9
+ ciphertext: Uint8Array;
10
+ shared: Uint8Array;
11
+ } | null;
12
+ export declare const decapsulate: (dk: Uint8Array, ciphertext: Uint8Array) => Uint8Array | null;
@@ -0,0 +1,36 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // ML-KEM-768 of FIPS 203 encapsulates. Web Crypto has none on every terrain,
3
+ // so it comes from `@noble/post-quantum`.
4
+ import { ml_kem768 } from '@noble/post-quantum/ml-kem.js';
5
+ export const EK = 1184;
6
+ export const CIPHERTEXT = 1088;
7
+ // KeyGen_internal(d, z): the lock from sixty-four drawn bytes, d then z.
8
+ export const lockFrom = (d, z) => {
9
+ const { publicKey, secretKey } = ml_kem768.keygen(Uint8Array.from([...d, ...z]));
10
+ return { ek: publicKey, dk: secretKey };
11
+ };
12
+ // Encaps_internal(ek, m), or null for an encapsulation key the modulus check
13
+ // of FIPS 203 section 7.2 refuses.
14
+ export const encapsulate = (ek, m) => {
15
+ if (ek.length !== EK)
16
+ return null;
17
+ try {
18
+ const { cipherText, sharedSecret } = ml_kem768.encapsulate(ek, m);
19
+ return { ciphertext: cipherText, shared: sharedSecret };
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ };
25
+ // Decapsulation is implicit rejection: a ciphertext for another lock gives a
26
+ // shared secret that opens nothing.
27
+ export const decapsulate = (dk, ciphertext) => {
28
+ if (ciphertext.length !== CIPHERTEXT)
29
+ return null;
30
+ try {
31
+ return ml_kem768.decapsulate(ciphertext, dk);
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ };
@@ -0,0 +1,10 @@
1
+ export declare const subtle: () => SubtleCrypto;
2
+ export type Bytes = Uint8Array<ArrayBuffer>;
3
+ export declare const own: (bytes: Uint8Array) => Bytes;
4
+ declare const PKCS8: {
5
+ readonly Ed25519: '302e020100300506032b657004220420';
6
+ readonly X25519: '302e020100300506032b656e04220420';
7
+ };
8
+ export declare const importSecret: (curve: keyof typeof PKCS8, secret: Uint8Array, uses: KeyUsage[]) => Promise<CryptoKey>;
9
+ export declare const publicOf: (key: CryptoKey) => Promise<Uint8Array>;
10
+ export {};