@yielded/crypto 0.0.0-stage → 0.1.0-beta.20

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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -2
  3. package/THIRD_PARTY_NOTICES.md +378 -0
  4. package/dist/Aead.d.mts +34 -0
  5. package/dist/Aead.mjs +28 -0
  6. package/dist/Aead.mjs.map +1 -0
  7. package/dist/Errors.d.mts +20 -0
  8. package/dist/Errors.mjs +19 -0
  9. package/dist/Errors.mjs.map +1 -0
  10. package/dist/Hmac.d.mts +39 -0
  11. package/dist/Hmac.mjs +26 -0
  12. package/dist/Hmac.mjs.map +1 -0
  13. package/dist/Kdf.d.mts +54 -0
  14. package/dist/Kdf.mjs +78 -0
  15. package/dist/Kdf.mjs.map +1 -0
  16. package/dist/KdfAdmission.d.mts +24 -0
  17. package/dist/KdfAdmission.mjs +48 -0
  18. package/dist/KdfAdmission.mjs.map +1 -0
  19. package/dist/Portable.d.mts +11 -0
  20. package/dist/Portable.mjs +13 -0
  21. package/dist/Portable.mjs.map +1 -0
  22. package/dist/Signature.d.mts +70 -0
  23. package/dist/Signature.mjs +73 -0
  24. package/dist/Signature.mjs.map +1 -0
  25. package/dist/WebCrypto.d.mts +20 -0
  26. package/dist/WebCrypto.mjs +33 -0
  27. package/dist/WebCrypto.mjs.map +1 -0
  28. package/dist/index.d.mts +7 -0
  29. package/dist/index.mjs +7 -0
  30. package/dist/internal/aead.mjs +81 -0
  31. package/dist/internal/aead.mjs.map +1 -0
  32. package/dist/internal/argon2-core.mjs +293 -0
  33. package/dist/internal/argon2-core.mjs.map +1 -0
  34. package/dist/internal/argon2.mjs +16 -0
  35. package/dist/internal/argon2.mjs.map +1 -0
  36. package/dist/internal/blake2b.mjs +359 -0
  37. package/dist/internal/blake2b.mjs.map +1 -0
  38. package/dist/internal/common.mjs +20 -0
  39. package/dist/internal/common.mjs.map +1 -0
  40. package/dist/internal/hmac.mjs +68 -0
  41. package/dist/internal/hmac.mjs.map +1 -0
  42. package/dist/internal/kdf.mjs +99 -0
  43. package/dist/internal/kdf.mjs.map +1 -0
  44. package/dist/internal/layer.mjs +26 -0
  45. package/dist/internal/layer.mjs.map +1 -0
  46. package/dist/internal/node.d.mts +11 -0
  47. package/dist/internal/node.mjs +34 -0
  48. package/dist/internal/node.mjs.map +1 -0
  49. package/dist/internal/poly1305.mjs +247 -0
  50. package/dist/internal/poly1305.mjs.map +1 -0
  51. package/dist/internal/signature.mjs +140 -0
  52. package/dist/internal/signature.mjs.map +1 -0
  53. package/dist/internal/word64.mjs +25 -0
  54. package/dist/internal/word64.mjs.map +1 -0
  55. package/dist/internal/xchacha-core.mjs +285 -0
  56. package/dist/internal/xchacha-core.mjs.map +1 -0
  57. package/dist/internal/xchacha.mjs +21 -0
  58. package/dist/internal/xchacha.mjs.map +1 -0
  59. package/dist/platform-bun.d.mts +2 -0
  60. package/dist/platform-bun.mjs +2 -0
  61. package/dist/platform-node.d.mts +2 -0
  62. package/dist/platform-node.mjs +2 -0
  63. package/package.json +1 -6
  64. package/src/Aead.ts +39 -0
  65. package/src/Errors.ts +27 -0
  66. package/src/Hmac.ts +41 -0
  67. package/src/Kdf.ts +87 -0
  68. package/src/KdfAdmission.ts +87 -0
  69. package/src/Portable.ts +8 -0
  70. package/src/Signature.ts +87 -0
  71. package/src/WebCrypto.ts +46 -0
  72. package/src/index.ts +6 -0
  73. package/src/internal/aead.ts +119 -0
  74. package/src/internal/argon2-core.ts +470 -0
  75. package/src/internal/argon2.ts +23 -0
  76. package/src/internal/blake2b.ts +230 -0
  77. package/src/internal/common.ts +44 -0
  78. package/src/internal/hmac.ts +111 -0
  79. package/src/internal/kdf.ts +150 -0
  80. package/src/internal/layer.ts +32 -0
  81. package/src/internal/node.ts +46 -0
  82. package/src/internal/poly1305.ts +289 -0
  83. package/src/internal/signature.ts +188 -0
  84. package/src/internal/word64.ts +48 -0
  85. package/src/internal/xchacha-core.ts +397 -0
  86. package/src/internal/xchacha.ts +26 -0
  87. package/src/platform-bun.ts +2 -0
  88. package/src/platform-node.ts +2 -0
@@ -0,0 +1,188 @@
1
+ import { Effect, Redacted, Schema } from "effect";
2
+ import { Base64Url } from "effect/encoding";
3
+
4
+ import { InvalidInput } from "../Errors";
5
+ import {
6
+ type Algorithm,
7
+ PrivateKeyParameters,
8
+ PublicKeyParameters,
9
+ SignInput,
10
+ Signature,
11
+ VerifyInput,
12
+ } from "../Signature";
13
+ import { copy, decode, importError, nativeError, withSecret } from "./common";
14
+
15
+ const derBytes = Schema.Uint8Array.check(Schema.isMinLength(1), Schema.isMaxLength(16384));
16
+
17
+ const RsaAlgorithm = Schema.Struct({
18
+ modulusLength: Schema.Int.check(Schema.isGreaterThanOrEqualTo(2048)),
19
+ });
20
+
21
+ const importAlgorithm = (algorithm: Algorithm) => {
22
+ switch (algorithm) {
23
+ case "ECDSA-P256-SHA256":
24
+ return { name: "ECDSA", namedCurve: "P-256" };
25
+ case "RSA-PSS-SHA256":
26
+ return { name: "RSA-PSS", hash: "SHA-256" };
27
+ case "RSASSA-PKCS1-v1_5-SHA256":
28
+ return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
29
+ case "Ed25519":
30
+ return { name: "Ed25519" };
31
+ }
32
+ };
33
+
34
+ const operationAlgorithm = (algorithm: Algorithm) => {
35
+ switch (algorithm) {
36
+ case "ECDSA-P256-SHA256":
37
+ return { name: "ECDSA", hash: "SHA-256" };
38
+ case "RSA-PSS-SHA256":
39
+ return { name: "RSA-PSS", saltLength: 32 };
40
+ case "RSASSA-PKCS1-v1_5-SHA256":
41
+ return { name: "RSASSA-PKCS1-v1_5" };
42
+ case "Ed25519":
43
+ return { name: "Ed25519" };
44
+ }
45
+ };
46
+
47
+ export const makeSignature = (subtle: SubtleCrypto): Signature["Service"] => {
48
+ // JOSE owns JWK metadata and policy. The adapter translates validated raw
49
+ // components through its platform key parser.
50
+ const importComponents = Effect.fnUntraced(function* (
51
+ parameters: PublicKeyParameters | PrivateKeyParameters,
52
+ usage: "sign" | "verify",
53
+ ) {
54
+ let jwk: JsonWebKey;
55
+
56
+ switch (parameters.algorithm) {
57
+ case "ECDSA-P256-SHA256":
58
+ jwk = {
59
+ kty: "EC",
60
+ crv: "P-256",
61
+ x: Base64Url.encode(parameters.x),
62
+ y: Base64Url.encode(parameters.y),
63
+ };
64
+ break;
65
+ case "Ed25519":
66
+ jwk = { kty: "OKP", crv: "Ed25519", x: Base64Url.encode(parameters.x) };
67
+ break;
68
+ case "RSASSA-PKCS1-v1_5-SHA256":
69
+ case "RSA-PSS-SHA256":
70
+ jwk = { kty: "RSA", n: Base64Url.encode(parameters.n), e: Base64Url.encode(parameters.e) };
71
+ if ("p" in parameters) {
72
+ jwk.p = Base64Url.encode(parameters.p);
73
+ jwk.q = Base64Url.encode(parameters.q);
74
+ jwk.dp = Base64Url.encode(parameters.dp);
75
+ jwk.dq = Base64Url.encode(parameters.dq);
76
+ jwk.qi = Base64Url.encode(parameters.qi);
77
+ }
78
+ }
79
+ if ("d" in parameters) jwk.d = Base64Url.encode(parameters.d);
80
+
81
+ const key = yield* Effect.tryPromise({
82
+ try: () => subtle.importKey("jwk", jwk, importAlgorithm(parameters.algorithm), true, [usage]),
83
+ catch: importError,
84
+ });
85
+
86
+ if (
87
+ parameters.algorithm === "RSA-PSS-SHA256" ||
88
+ parameters.algorithm === "RSASSA-PKCS1-v1_5-SHA256"
89
+ ) {
90
+ yield* Schema.decodeUnknownEffect(RsaAlgorithm)(key.algorithm).pipe(
91
+ Effect.mapError(() => InvalidInput.make({ reason: "key" })),
92
+ );
93
+ }
94
+
95
+ return key;
96
+ });
97
+
98
+ const importKey = Effect.fnUntraced(function* (
99
+ algorithm: Algorithm,
100
+ format: "pkcs8" | "spki",
101
+ material: Uint8Array<ArrayBuffer>,
102
+ ) {
103
+ yield* decode(derBytes, material, "key");
104
+
105
+ const key = yield* Effect.tryPromise({
106
+ try: () =>
107
+ subtle.importKey(format, material, importAlgorithm(algorithm), false, [
108
+ format === "pkcs8" ? "sign" : "verify",
109
+ ]),
110
+ catch: importError,
111
+ });
112
+
113
+ if (algorithm === "RSA-PSS-SHA256" || algorithm === "RSASSA-PKCS1-v1_5-SHA256") {
114
+ // The platform parsed this DER key. Validate its actual modulus, not caller metadata.
115
+ yield* Schema.decodeUnknownEffect(RsaAlgorithm)(key.algorithm).pipe(
116
+ Effect.mapError(() => InvalidInput.make({ reason: "key" })),
117
+ );
118
+ }
119
+
120
+ return key;
121
+ });
122
+
123
+ return Signature.of({
124
+ encodePublicKey: Effect.fnUntraced(function* (input) {
125
+ const parameters = yield* decode(PublicKeyParameters, input, "key");
126
+ const key = yield* importComponents(parameters, "verify");
127
+
128
+ const encoded = yield* Effect.tryPromise({
129
+ try: () => subtle.exportKey("spki", key),
130
+ catch: nativeError,
131
+ });
132
+
133
+ return new Uint8Array(encoded);
134
+ }),
135
+ encodePrivateKey: Effect.fnUntraced(function* (input) {
136
+ const wrapped = yield* decode(
137
+ Schema.Redacted(PrivateKeyParameters, { disallowJsonEncode: true }),
138
+ input,
139
+ "key",
140
+ );
141
+
142
+ const parameters = Redacted.value(wrapped);
143
+ const key = yield* importComponents(parameters, "sign");
144
+
145
+ const encoded = yield* Effect.tryPromise({
146
+ try: () => subtle.exportKey("pkcs8", key),
147
+ catch: nativeError,
148
+ });
149
+
150
+ return Redacted.make(new Uint8Array(encoded));
151
+ }),
152
+ sign: Effect.fnUntraced(function* (input) {
153
+ const value = yield* decode(SignInput, input, "data");
154
+ const data = yield* copy(value.data);
155
+
156
+ return yield* withSecret(value.privateKey, (material) =>
157
+ Effect.gen(function* () {
158
+ const key = yield* importKey(value.algorithm, "pkcs8", material);
159
+
160
+ const result = yield* Effect.tryPromise({
161
+ try: () => subtle.sign(operationAlgorithm(value.algorithm), key, data),
162
+ catch: nativeError,
163
+ });
164
+
165
+ return new Uint8Array(result);
166
+ }).pipe(Effect.uninterruptible),
167
+ );
168
+ }),
169
+ verify: Effect.fnUntraced(function* (input) {
170
+ const value = yield* decode(VerifyInput, input, "data");
171
+ const material = yield* copy(value.publicKey);
172
+ const data = yield* copy(value.data);
173
+ const signature = yield* copy(value.signature);
174
+ const key = yield* importKey(value.algorithm, "spki", material);
175
+
176
+ if (
177
+ (value.algorithm === "ECDSA-P256-SHA256" || value.algorithm === "Ed25519") &&
178
+ signature.length !== 64
179
+ )
180
+ return false;
181
+
182
+ return yield* Effect.tryPromise({
183
+ try: () => subtle.verify(operationAlgorithm(value.algorithm), key, signature, data),
184
+ catch: nativeError,
185
+ });
186
+ }),
187
+ });
188
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Adapted from noble-hashes, Copyright (c) 2022 Paul Miller.
3
+ * MIT; source revision 8060cc83bbab6681d23c9eed071db3f3a6b442da.
4
+ * See ../../THIRD_PARTY_NOTICES.md for source mapping and license.
5
+ */
6
+ // High 32-bit half of a 64-bit right rotate, valid for `s` in `1..31`.
7
+ export const rotrSH = (h: number, l: number, s: number): number => (h >>> s) | (l << (32 - s));
8
+ // Low 32-bit half of a 64-bit right rotate, valid for `s` in `1..31`.
9
+ export const rotrSL = (h: number, l: number, s: number): number => (h << (32 - s)) | (l >>> s);
10
+
11
+ // High 32-bit half of a 64-bit right rotate, valid for `s` in `33..63`; `32` uses `rotr32*`.
12
+ export const rotrBH = (h: number, l: number, s: number): number =>
13
+ (h << (64 - s)) | (l >>> (s - 32));
14
+
15
+ // Low 32-bit half of a 64-bit right rotate, valid for `s` in `33..63`; `32` uses `rotr32*`.
16
+ export const rotrBL = (h: number, l: number, s: number): number =>
17
+ (h >>> (s - 32)) | (l << (64 - s));
18
+
19
+ // High 32-bit half of a 64-bit right rotate for `s === 32`; this is just the swapped low half.
20
+ export const rotr32H = (_h: number, l: number): number => l;
21
+ // Low 32-bit half of a 64-bit right rotate for `s === 32`; this is just the swapped high half.
22
+ export const rotr32L = (h: number, _l: number): number => h;
23
+
24
+ // Add two split 64-bit words and return the split `{ h, l }` sum.
25
+ // JS uses 32-bit signed integers for bitwise operations, so we cannot simply shift the carry out
26
+ // of the low sum and instead use division.
27
+ export function add(
28
+ Ah: number,
29
+ Al: number,
30
+ Bh: number,
31
+ Bl: number,
32
+ ): {
33
+ h: number;
34
+ l: number;
35
+ } {
36
+ const l = (Al >>> 0) + (Bl >>> 0);
37
+
38
+ return { h: (Ah + Bh + ((l / 2 ** 32) | 0)) | 0, l: l | 0 };
39
+ }
40
+
41
+ // Addition with more than 2 elements
42
+ // Unmasked low-word accumulator for 3-way addition; pass the raw result into `add3H(...)`.
43
+ export const add3L = (Al: number, Bl: number, Cl: number): number =>
44
+ (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0);
45
+
46
+ // High-word finalize step for 3-way addition; `low` must be the untruncated output of `add3L(...)`.
47
+ export const add3H = (low: number, Ah: number, Bh: number, Ch: number): number =>
48
+ (Ah + Bh + Ch + ((low / 2 ** 32) | 0)) | 0;
@@ -0,0 +1,397 @@
1
+ /**
2
+ * Adapted from noble-ciphers, Copyright (c) 2022 Paul Miller.
3
+ * MIT; source revision 785181b0772ba84afbe812025714163e4e48262f.
4
+ * Poly1305 arithmetic derives from the public-domain poly1305-donna implementation.
5
+ * See ../../THIRD_PARTY_NOTICES.md for source mapping and licenses.
6
+ */
7
+ import { Poly1305 } from "./poly1305";
8
+
9
+ const rotl = (word: number, shift: number): number => (word << shift) | (word >>> (32 - shift));
10
+
11
+ function chachaCore(
12
+ s: Uint32Array,
13
+ k: Uint32Array,
14
+ n: Uint32Array,
15
+ out: Uint32Array,
16
+ cnt: number,
17
+ rounds = 20,
18
+ ): void {
19
+ let y00 = s[0],
20
+ y01 = s[1],
21
+ y02 = s[2],
22
+ y03 = s[3], // "expa" "nd 3" "2-by" "te k"
23
+ y04 = k[0],
24
+ y05 = k[1],
25
+ y06 = k[2],
26
+ y07 = k[3], // Key Key Key Key
27
+ y08 = k[4],
28
+ y09 = k[5],
29
+ y10 = k[6],
30
+ y11 = k[7], // Key Key Key Key
31
+ y12 = cnt,
32
+ y13 = n[0],
33
+ y14 = n[1],
34
+ y15 = n[2]; // Counter Counter Nonce Nonce
35
+
36
+ // Save state to temporary variables
37
+ let x00 = y00,
38
+ x01 = y01,
39
+ x02 = y02,
40
+ x03 = y03,
41
+ x04 = y04,
42
+ x05 = y05,
43
+ x06 = y06,
44
+ x07 = y07,
45
+ x08 = y08,
46
+ x09 = y09,
47
+ x10 = y10,
48
+ x11 = y11,
49
+ x12 = y12,
50
+ x13 = y13,
51
+ x14 = y14,
52
+ x15 = y15;
53
+
54
+ for (let r = 0; r < rounds; r += 2) {
55
+ x00 = (x00 + x04) | 0;
56
+ x12 = rotl(x12 ^ x00, 16);
57
+ x08 = (x08 + x12) | 0;
58
+ x04 = rotl(x04 ^ x08, 12);
59
+ x00 = (x00 + x04) | 0;
60
+ x12 = rotl(x12 ^ x00, 8);
61
+ x08 = (x08 + x12) | 0;
62
+ x04 = rotl(x04 ^ x08, 7);
63
+
64
+ x01 = (x01 + x05) | 0;
65
+ x13 = rotl(x13 ^ x01, 16);
66
+ x09 = (x09 + x13) | 0;
67
+ x05 = rotl(x05 ^ x09, 12);
68
+ x01 = (x01 + x05) | 0;
69
+ x13 = rotl(x13 ^ x01, 8);
70
+ x09 = (x09 + x13) | 0;
71
+ x05 = rotl(x05 ^ x09, 7);
72
+
73
+ x02 = (x02 + x06) | 0;
74
+ x14 = rotl(x14 ^ x02, 16);
75
+ x10 = (x10 + x14) | 0;
76
+ x06 = rotl(x06 ^ x10, 12);
77
+ x02 = (x02 + x06) | 0;
78
+ x14 = rotl(x14 ^ x02, 8);
79
+ x10 = (x10 + x14) | 0;
80
+ x06 = rotl(x06 ^ x10, 7);
81
+
82
+ x03 = (x03 + x07) | 0;
83
+ x15 = rotl(x15 ^ x03, 16);
84
+ x11 = (x11 + x15) | 0;
85
+ x07 = rotl(x07 ^ x11, 12);
86
+ x03 = (x03 + x07) | 0;
87
+ x15 = rotl(x15 ^ x03, 8);
88
+ x11 = (x11 + x15) | 0;
89
+ x07 = rotl(x07 ^ x11, 7);
90
+
91
+ x00 = (x00 + x05) | 0;
92
+ x15 = rotl(x15 ^ x00, 16);
93
+ x10 = (x10 + x15) | 0;
94
+ x05 = rotl(x05 ^ x10, 12);
95
+ x00 = (x00 + x05) | 0;
96
+ x15 = rotl(x15 ^ x00, 8);
97
+ x10 = (x10 + x15) | 0;
98
+ x05 = rotl(x05 ^ x10, 7);
99
+
100
+ x01 = (x01 + x06) | 0;
101
+ x12 = rotl(x12 ^ x01, 16);
102
+ x11 = (x11 + x12) | 0;
103
+ x06 = rotl(x06 ^ x11, 12);
104
+ x01 = (x01 + x06) | 0;
105
+ x12 = rotl(x12 ^ x01, 8);
106
+ x11 = (x11 + x12) | 0;
107
+ x06 = rotl(x06 ^ x11, 7);
108
+
109
+ x02 = (x02 + x07) | 0;
110
+ x13 = rotl(x13 ^ x02, 16);
111
+ x08 = (x08 + x13) | 0;
112
+ x07 = rotl(x07 ^ x08, 12);
113
+ x02 = (x02 + x07) | 0;
114
+ x13 = rotl(x13 ^ x02, 8);
115
+ x08 = (x08 + x13) | 0;
116
+ x07 = rotl(x07 ^ x08, 7);
117
+
118
+ x03 = (x03 + x04) | 0;
119
+ x14 = rotl(x14 ^ x03, 16);
120
+ x09 = (x09 + x14) | 0;
121
+ x04 = rotl(x04 ^ x09, 12);
122
+ x03 = (x03 + x04) | 0;
123
+ x14 = rotl(x14 ^ x03, 8);
124
+ x09 = (x09 + x14) | 0;
125
+ x04 = rotl(x04 ^ x09, 7);
126
+ }
127
+ // Write output
128
+ let oi = 0;
129
+
130
+ out[oi++] = (y00 + x00) | 0;
131
+ out[oi++] = (y01 + x01) | 0;
132
+ out[oi++] = (y02 + x02) | 0;
133
+ out[oi++] = (y03 + x03) | 0;
134
+ out[oi++] = (y04 + x04) | 0;
135
+ out[oi++] = (y05 + x05) | 0;
136
+ out[oi++] = (y06 + x06) | 0;
137
+ out[oi++] = (y07 + x07) | 0;
138
+ out[oi++] = (y08 + x08) | 0;
139
+ out[oi++] = (y09 + x09) | 0;
140
+ out[oi++] = (y10 + x10) | 0;
141
+ out[oi++] = (y11 + x11) | 0;
142
+ out[oi++] = (y12 + x12) | 0;
143
+ out[oi++] = (y13 + x13) | 0;
144
+ out[oi++] = (y14 + x14) | 0;
145
+ out[oi++] = (y15 + x15) | 0;
146
+ }
147
+ /**
148
+ * hchacha hashes key and nonce into key' and nonce' for xchacha20.
149
+ */
150
+ function hchacha(s: Uint32Array, k: Uint32Array, i: Uint32Array, out: Uint32Array): void {
151
+ let x00 = s[0],
152
+ x01 = s[1],
153
+ x02 = s[2],
154
+ x03 = s[3],
155
+ x04 = k[0],
156
+ x05 = k[1],
157
+ x06 = k[2],
158
+ x07 = k[3],
159
+ x08 = k[4],
160
+ x09 = k[5],
161
+ x10 = k[6],
162
+ x11 = k[7],
163
+ x12 = i[0],
164
+ x13 = i[1],
165
+ x14 = i[2],
166
+ x15 = i[3];
167
+
168
+ for (let r = 0; r < 20; r += 2) {
169
+ x00 = (x00 + x04) | 0;
170
+ x12 = rotl(x12 ^ x00, 16);
171
+ x08 = (x08 + x12) | 0;
172
+ x04 = rotl(x04 ^ x08, 12);
173
+ x00 = (x00 + x04) | 0;
174
+ x12 = rotl(x12 ^ x00, 8);
175
+ x08 = (x08 + x12) | 0;
176
+ x04 = rotl(x04 ^ x08, 7);
177
+
178
+ x01 = (x01 + x05) | 0;
179
+ x13 = rotl(x13 ^ x01, 16);
180
+ x09 = (x09 + x13) | 0;
181
+ x05 = rotl(x05 ^ x09, 12);
182
+ x01 = (x01 + x05) | 0;
183
+ x13 = rotl(x13 ^ x01, 8);
184
+ x09 = (x09 + x13) | 0;
185
+ x05 = rotl(x05 ^ x09, 7);
186
+
187
+ x02 = (x02 + x06) | 0;
188
+ x14 = rotl(x14 ^ x02, 16);
189
+ x10 = (x10 + x14) | 0;
190
+ x06 = rotl(x06 ^ x10, 12);
191
+ x02 = (x02 + x06) | 0;
192
+ x14 = rotl(x14 ^ x02, 8);
193
+ x10 = (x10 + x14) | 0;
194
+ x06 = rotl(x06 ^ x10, 7);
195
+
196
+ x03 = (x03 + x07) | 0;
197
+ x15 = rotl(x15 ^ x03, 16);
198
+ x11 = (x11 + x15) | 0;
199
+ x07 = rotl(x07 ^ x11, 12);
200
+ x03 = (x03 + x07) | 0;
201
+ x15 = rotl(x15 ^ x03, 8);
202
+ x11 = (x11 + x15) | 0;
203
+ x07 = rotl(x07 ^ x11, 7);
204
+
205
+ x00 = (x00 + x05) | 0;
206
+ x15 = rotl(x15 ^ x00, 16);
207
+ x10 = (x10 + x15) | 0;
208
+ x05 = rotl(x05 ^ x10, 12);
209
+ x00 = (x00 + x05) | 0;
210
+ x15 = rotl(x15 ^ x00, 8);
211
+ x10 = (x10 + x15) | 0;
212
+ x05 = rotl(x05 ^ x10, 7);
213
+
214
+ x01 = (x01 + x06) | 0;
215
+ x12 = rotl(x12 ^ x01, 16);
216
+ x11 = (x11 + x12) | 0;
217
+ x06 = rotl(x06 ^ x11, 12);
218
+ x01 = (x01 + x06) | 0;
219
+ x12 = rotl(x12 ^ x01, 8);
220
+ x11 = (x11 + x12) | 0;
221
+ x06 = rotl(x06 ^ x11, 7);
222
+
223
+ x02 = (x02 + x07) | 0;
224
+ x13 = rotl(x13 ^ x02, 16);
225
+ x08 = (x08 + x13) | 0;
226
+ x07 = rotl(x07 ^ x08, 12);
227
+ x02 = (x02 + x07) | 0;
228
+ x13 = rotl(x13 ^ x02, 8);
229
+ x08 = (x08 + x13) | 0;
230
+ x07 = rotl(x07 ^ x08, 7);
231
+
232
+ x03 = (x03 + x04) | 0;
233
+ x14 = rotl(x14 ^ x03, 16);
234
+ x09 = (x09 + x14) | 0;
235
+ x04 = rotl(x04 ^ x09, 12);
236
+ x03 = (x03 + x04) | 0;
237
+ x14 = rotl(x14 ^ x03, 8);
238
+ x09 = (x09 + x14) | 0;
239
+ x04 = rotl(x04 ^ x09, 7);
240
+ }
241
+ let oi = 0;
242
+
243
+ out[oi++] = x00;
244
+ out[oi++] = x01;
245
+ out[oi++] = x02;
246
+ out[oi++] = x03;
247
+ out[oi++] = x12;
248
+ out[oi++] = x13;
249
+ out[oi++] = x14;
250
+ out[oi++] = x15;
251
+ }
252
+
253
+ const sigma = Uint32Array.from([0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]);
254
+ const padding = new Uint8Array(16);
255
+
256
+ interface State {
257
+ readonly key: Uint32Array;
258
+ readonly nonce: Uint32Array;
259
+ readonly words: Uint32Array;
260
+ readonly stream: Uint8Array;
261
+ }
262
+
263
+ const readWords = (data: Uint8Array, words: Uint32Array): void => {
264
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
265
+
266
+ for (let i = 0; i < words.length; i++) words[i] = view.getUint32(i * 4, true);
267
+ };
268
+
269
+ function withState<A>(key: Uint8Array, nonce: Uint8Array, operation: (state: State) => A): A {
270
+ const original = new Uint32Array(8);
271
+ const extendedNonce = new Uint32Array(4);
272
+
273
+ const state: State = {
274
+ key: new Uint32Array(8),
275
+ nonce: new Uint32Array(3),
276
+ words: new Uint32Array(16),
277
+ stream: new Uint8Array(64),
278
+ };
279
+
280
+ try {
281
+ readWords(key, original);
282
+ readWords(nonce.subarray(0, 16), extendedNonce);
283
+ hchacha(sigma, original, extendedNonce, state.key);
284
+ // XChaCha uses HChaCha(key, nonce[0..16]), then 0 || nonce[16..24].
285
+ const view = new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength);
286
+
287
+ state.nonce[1] = view.getUint32(16, true);
288
+ state.nonce[2] = view.getUint32(20, true);
289
+
290
+ return operation(state);
291
+ } finally {
292
+ original.fill(0);
293
+ extendedNonce.fill(0);
294
+ state.key.fill(0);
295
+ state.nonce.fill(0);
296
+ state.words.fill(0);
297
+ state.stream.fill(0);
298
+ }
299
+ }
300
+
301
+ const blockAt = (state: State, counter: number): void => {
302
+ chachaCore(sigma, state.key, state.nonce, state.words, counter);
303
+ const view = new DataView(state.stream.buffer, state.stream.byteOffset, state.stream.byteLength);
304
+
305
+ for (let i = 0; i < 16; i++) view.setUint32(i * 4, state.words[i], true);
306
+ };
307
+
308
+ const xor = (state: State, data: Uint8Array, out: Uint8Array): void => {
309
+ for (let offset = 0, counter = 1; offset < data.length; offset += 64, counter++) {
310
+ blockAt(state, counter);
311
+ const length = Math.min(64, data.length - offset);
312
+
313
+ for (let i = 0; i < length; i++) out[offset + i] = data[offset + i] ^ state.stream[i];
314
+ }
315
+ };
316
+
317
+ const padded = (mac: Poly1305, data: Uint8Array): void => {
318
+ mac.update(data);
319
+ const remainder = data.length % 16;
320
+
321
+ if (remainder !== 0) mac.update(padding.subarray(remainder));
322
+ };
323
+
324
+ const tag = (state: State, ciphertext: Uint8Array, additionalData: Uint8Array): Uint8Array => {
325
+ const lengths = new Uint8Array(16);
326
+ const view = new DataView(lengths.buffer);
327
+
328
+ // RFC 8439 authenticates little-endian 64-bit byte lengths, AAD first.
329
+ view.setUint32(0, additionalData.length >>> 0, true);
330
+ view.setUint32(4, Math.floor(additionalData.length / 0x100000000), true);
331
+ view.setUint32(8, ciphertext.length >>> 0, true);
332
+ view.setUint32(12, Math.floor(ciphertext.length / 0x100000000), true);
333
+ blockAt(state, 0);
334
+ const mac = new Poly1305(state.stream.subarray(0, 32));
335
+
336
+ try {
337
+ padded(mac, additionalData);
338
+ padded(mac, ciphertext);
339
+
340
+ return mac.update(lengths).digest();
341
+ } finally {
342
+ mac.destroy();
343
+ lengths.fill(0);
344
+ state.stream.fill(0);
345
+ }
346
+ };
347
+
348
+ export const encrypt = (
349
+ key: Uint8Array,
350
+ nonce: Uint8Array,
351
+ additionalData: Uint8Array,
352
+ plaintext: Uint8Array,
353
+ ): Uint8Array =>
354
+ withState(key, nonce, (state) => {
355
+ const out = new Uint8Array(plaintext.length + 16);
356
+ const ciphertext = out.subarray(0, plaintext.length);
357
+
358
+ try {
359
+ xor(state, plaintext, ciphertext);
360
+ const authentication = tag(state, ciphertext, additionalData);
361
+
362
+ out.set(authentication, plaintext.length);
363
+ authentication.fill(0);
364
+
365
+ return out;
366
+ } catch (error) {
367
+ out.fill(0);
368
+ throw error;
369
+ }
370
+ });
371
+
372
+ /** Authenticate the complete input before allocating or producing plaintext. */
373
+ export const decrypt = (
374
+ key: Uint8Array,
375
+ nonce: Uint8Array,
376
+ additionalData: Uint8Array,
377
+ sealed: Uint8Array,
378
+ ): Uint8Array | undefined =>
379
+ withState(key, nonce, (state) => {
380
+ const ciphertext = sealed.subarray(0, -16);
381
+ const authentication = tag(state, ciphertext, additionalData);
382
+ let difference = 0;
383
+
384
+ for (let i = 0; i < 16; i++) difference |= authentication[i] ^ sealed[ciphertext.length + i];
385
+ authentication.fill(0);
386
+ if (difference !== 0) return undefined;
387
+ const out = new Uint8Array(ciphertext.length);
388
+
389
+ try {
390
+ xor(state, ciphertext, out);
391
+
392
+ return out;
393
+ } catch (error) {
394
+ out.fill(0);
395
+ throw error;
396
+ }
397
+ });
@@ -0,0 +1,26 @@
1
+ import { Effect } from "effect";
2
+
3
+ import { AuthenticationFailed, InvalidInput } from "../Errors";
4
+ import type { XChaCha } from "./aead";
5
+ import { decrypt, encrypt } from "./xchacha-core";
6
+
7
+ // Counter zero derives the MAC key; payload blocks use the remaining uint32 values.
8
+ const maximumPlaintextBytes = 0xffffffff * 64;
9
+
10
+ export const xchacha: XChaCha = {
11
+ encrypt: Effect.fnUntraced(function* ({ key, nonce, additionalData, data }) {
12
+ if (data.length > maximumPlaintextBytes) return yield* InvalidInput.make({ reason: "data" });
13
+
14
+ return encrypt(key, nonce, additionalData, data);
15
+ }),
16
+ decrypt: Effect.fnUntraced(function* ({ key, nonce, additionalData, data }) {
17
+ if (data.length - 16 > maximumPlaintextBytes)
18
+ return yield* InvalidInput.make({ reason: "data" });
19
+
20
+ const plaintext = decrypt(key, nonce, additionalData, data);
21
+
22
+ if (plaintext === undefined) return yield* AuthenticationFailed.make({});
23
+
24
+ return plaintext;
25
+ }),
26
+ };
@@ -0,0 +1,2 @@
1
+ /** Cryptography for Bun through its Node-compatible native APIs. */
2
+ export { layer } from "./internal/node";
@@ -0,0 +1,2 @@
1
+ /** Cryptography for Node.js. Requires a shared KdfAdmission Layer. */
2
+ export { layer } from "./internal/node";