@omnituum/pqc-shared 0.2.6

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 (67) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +543 -0
  3. package/dist/crypto/index.cjs +807 -0
  4. package/dist/crypto/index.d.cts +641 -0
  5. package/dist/crypto/index.d.ts +641 -0
  6. package/dist/crypto/index.js +716 -0
  7. package/dist/decrypt-eSHlbh1j.d.cts +321 -0
  8. package/dist/decrypt-eSHlbh1j.d.ts +321 -0
  9. package/dist/fs/index.cjs +1168 -0
  10. package/dist/fs/index.d.cts +400 -0
  11. package/dist/fs/index.d.ts +400 -0
  12. package/dist/fs/index.js +1091 -0
  13. package/dist/index.cjs +2160 -0
  14. package/dist/index.d.cts +282 -0
  15. package/dist/index.d.ts +282 -0
  16. package/dist/index.js +2031 -0
  17. package/dist/integrity-CCYjrap3.d.ts +31 -0
  18. package/dist/integrity-Dx9jukMH.d.cts +31 -0
  19. package/dist/types-61c7Q9ri.d.ts +134 -0
  20. package/dist/types-Ch0y-n7K.d.cts +134 -0
  21. package/dist/utils/index.cjs +129 -0
  22. package/dist/utils/index.d.cts +49 -0
  23. package/dist/utils/index.d.ts +49 -0
  24. package/dist/utils/index.js +114 -0
  25. package/dist/vault/index.cjs +713 -0
  26. package/dist/vault/index.d.cts +237 -0
  27. package/dist/vault/index.d.ts +237 -0
  28. package/dist/vault/index.js +677 -0
  29. package/dist/version-BygzPVGs.d.cts +55 -0
  30. package/dist/version-BygzPVGs.d.ts +55 -0
  31. package/package.json +86 -0
  32. package/src/crypto/dilithium.ts +233 -0
  33. package/src/crypto/hybrid.ts +358 -0
  34. package/src/crypto/index.ts +181 -0
  35. package/src/crypto/kyber.ts +199 -0
  36. package/src/crypto/nacl.ts +204 -0
  37. package/src/crypto/primitives/blake3.ts +141 -0
  38. package/src/crypto/primitives/chacha.ts +211 -0
  39. package/src/crypto/primitives/hkdf.ts +192 -0
  40. package/src/crypto/primitives/index.ts +54 -0
  41. package/src/crypto/primitives.ts +144 -0
  42. package/src/crypto/x25519.ts +134 -0
  43. package/src/fs/aes.ts +343 -0
  44. package/src/fs/argon2.ts +184 -0
  45. package/src/fs/browser.ts +408 -0
  46. package/src/fs/decrypt.ts +320 -0
  47. package/src/fs/encrypt.ts +324 -0
  48. package/src/fs/format.ts +425 -0
  49. package/src/fs/index.ts +144 -0
  50. package/src/fs/types.ts +304 -0
  51. package/src/index.ts +414 -0
  52. package/src/kdf/index.ts +311 -0
  53. package/src/runtime/crypto.ts +16 -0
  54. package/src/security/index.ts +345 -0
  55. package/src/tunnel/index.ts +39 -0
  56. package/src/tunnel/session.ts +229 -0
  57. package/src/tunnel/types.ts +115 -0
  58. package/src/utils/entropy.ts +128 -0
  59. package/src/utils/index.ts +25 -0
  60. package/src/utils/integrity.ts +95 -0
  61. package/src/vault/decrypt.ts +167 -0
  62. package/src/vault/encrypt.ts +207 -0
  63. package/src/vault/index.ts +71 -0
  64. package/src/vault/manager.ts +327 -0
  65. package/src/vault/migrate.ts +190 -0
  66. package/src/vault/types.ts +177 -0
  67. package/src/version.ts +304 -0
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Omnituum PQC Shared - ChaCha20-Poly1305 Primitives
3
+ *
4
+ * AEAD ciphers for authenticated encryption:
5
+ * - ChaCha20-Poly1305: Standard AEAD (12-byte nonce)
6
+ * - XChaCha20-Poly1305: Extended nonce variant (24-byte nonce)
7
+ *
8
+ * Used in Noise protocols and general-purpose encryption.
9
+ */
10
+
11
+ import { chacha20poly1305, xchacha20poly1305 } from '@noble/ciphers/chacha';
12
+
13
+ // ═══════════════════════════════════════════════════════════════════════════
14
+ // TYPES
15
+ // ═══════════════════════════════════════════════════════════════════════════
16
+
17
+ export interface ChaChaPayload {
18
+ /** Nonce (12 bytes for standard, 24 bytes for xchacha) */
19
+ nonce: Uint8Array;
20
+ /** Ciphertext with Poly1305 tag (16 bytes appended) */
21
+ ciphertext: Uint8Array;
22
+ }
23
+
24
+ // ═══════════════════════════════════════════════════════════════════════════
25
+ // CHACHA20-POLY1305 (12-byte nonce)
26
+ // ═══════════════════════════════════════════════════════════════════════════
27
+
28
+ /**
29
+ * Encrypt using ChaCha20-Poly1305.
30
+ *
31
+ * @param key - 32-byte encryption key
32
+ * @param nonce - 12-byte nonce (must be unique per key)
33
+ * @param plaintext - Data to encrypt
34
+ * @param aad - Optional additional authenticated data
35
+ * @returns Ciphertext with Poly1305 tag appended
36
+ */
37
+ export function chaCha20Poly1305Encrypt(
38
+ key: Uint8Array,
39
+ nonce: Uint8Array,
40
+ plaintext: Uint8Array,
41
+ aad?: Uint8Array
42
+ ): Uint8Array {
43
+ if (key.length !== 32) {
44
+ throw new Error('ChaCha20-Poly1305 key must be 32 bytes');
45
+ }
46
+ if (nonce.length !== 12) {
47
+ throw new Error('ChaCha20-Poly1305 nonce must be 12 bytes');
48
+ }
49
+
50
+ const cipher = chacha20poly1305(key, nonce, aad);
51
+ return cipher.encrypt(plaintext);
52
+ }
53
+
54
+ /**
55
+ * Decrypt using ChaCha20-Poly1305.
56
+ *
57
+ * @param key - 32-byte encryption key
58
+ * @param nonce - 12-byte nonce
59
+ * @param ciphertext - Ciphertext with Poly1305 tag
60
+ * @param aad - Optional additional authenticated data
61
+ * @returns Plaintext, or null if authentication fails
62
+ */
63
+ export function chaCha20Poly1305Decrypt(
64
+ key: Uint8Array,
65
+ nonce: Uint8Array,
66
+ ciphertext: Uint8Array,
67
+ aad?: Uint8Array
68
+ ): Uint8Array | null {
69
+ if (key.length !== 32) {
70
+ throw new Error('ChaCha20-Poly1305 key must be 32 bytes');
71
+ }
72
+ if (nonce.length !== 12) {
73
+ throw new Error('ChaCha20-Poly1305 nonce must be 12 bytes');
74
+ }
75
+
76
+ try {
77
+ const cipher = chacha20poly1305(key, nonce, aad);
78
+ return cipher.decrypt(ciphertext);
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
83
+
84
+ // ═══════════════════════════════════════════════════════════════════════════
85
+ // XCHACHA20-POLY1305 (24-byte nonce)
86
+ // ═══════════════════════════════════════════════════════════════════════════
87
+
88
+ /**
89
+ * Encrypt using XChaCha20-Poly1305 (extended 24-byte nonce).
90
+ *
91
+ * XChaCha20-Poly1305 is preferred for most applications because:
92
+ * - 24-byte nonce can be safely generated randomly
93
+ * - No nonce collision risk with ~2^80 messages per key
94
+ *
95
+ * @param key - 32-byte encryption key
96
+ * @param nonce - 24-byte nonce (can be random)
97
+ * @param plaintext - Data to encrypt
98
+ * @param aad - Optional additional authenticated data
99
+ * @returns Ciphertext with Poly1305 tag appended
100
+ */
101
+ export function xChaCha20Poly1305Encrypt(
102
+ key: Uint8Array,
103
+ nonce: Uint8Array,
104
+ plaintext: Uint8Array,
105
+ aad?: Uint8Array
106
+ ): Uint8Array {
107
+ if (key.length !== 32) {
108
+ throw new Error('XChaCha20-Poly1305 key must be 32 bytes');
109
+ }
110
+ if (nonce.length !== 24) {
111
+ throw new Error('XChaCha20-Poly1305 nonce must be 24 bytes');
112
+ }
113
+
114
+ const cipher = xchacha20poly1305(key, nonce, aad);
115
+ return cipher.encrypt(plaintext);
116
+ }
117
+
118
+ /**
119
+ * Decrypt using XChaCha20-Poly1305.
120
+ *
121
+ * @param key - 32-byte encryption key
122
+ * @param nonce - 24-byte nonce
123
+ * @param ciphertext - Ciphertext with Poly1305 tag
124
+ * @param aad - Optional additional authenticated data
125
+ * @returns Plaintext, or null if authentication fails
126
+ */
127
+ export function xChaCha20Poly1305Decrypt(
128
+ key: Uint8Array,
129
+ nonce: Uint8Array,
130
+ ciphertext: Uint8Array,
131
+ aad?: Uint8Array
132
+ ): Uint8Array | null {
133
+ if (key.length !== 32) {
134
+ throw new Error('XChaCha20-Poly1305 key must be 32 bytes');
135
+ }
136
+ if (nonce.length !== 24) {
137
+ throw new Error('XChaCha20-Poly1305 nonce must be 24 bytes');
138
+ }
139
+
140
+ try {
141
+ const cipher = xchacha20poly1305(key, nonce, aad);
142
+ return cipher.decrypt(ciphertext);
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+
148
+ // ═══════════════════════════════════════════════════════════════════════════
149
+ // FACTORY FUNCTIONS (for Noise protocol compatibility)
150
+ // ═══════════════════════════════════════════════════════════════════════════
151
+
152
+ /**
153
+ * Create an XChaCha20-Poly1305 cipher instance.
154
+ * Compatible with @noble/ciphers API used in noise-kyber.
155
+ *
156
+ * @param key - 32-byte encryption key
157
+ * @param nonce - 24-byte nonce
158
+ * @param aad - Optional additional authenticated data
159
+ * @returns Cipher with encrypt/decrypt methods
160
+ */
161
+ export function createXChaCha20Poly1305(
162
+ key: Uint8Array,
163
+ nonce: Uint8Array,
164
+ aad?: Uint8Array
165
+ ) {
166
+ if (key.length !== 32) {
167
+ throw new Error('XChaCha20-Poly1305 key must be 32 bytes');
168
+ }
169
+ if (nonce.length !== 24) {
170
+ throw new Error('XChaCha20-Poly1305 nonce must be 24 bytes');
171
+ }
172
+ return xchacha20poly1305(key, nonce, aad);
173
+ }
174
+
175
+ /**
176
+ * Create a ChaCha20-Poly1305 cipher instance.
177
+ *
178
+ * @param key - 32-byte encryption key
179
+ * @param nonce - 12-byte nonce
180
+ * @param aad - Optional additional authenticated data
181
+ * @returns Cipher with encrypt/decrypt methods
182
+ */
183
+ export function createChaCha20Poly1305(
184
+ key: Uint8Array,
185
+ nonce: Uint8Array,
186
+ aad?: Uint8Array
187
+ ) {
188
+ if (key.length !== 32) {
189
+ throw new Error('ChaCha20-Poly1305 key must be 32 bytes');
190
+ }
191
+ if (nonce.length !== 12) {
192
+ throw new Error('ChaCha20-Poly1305 nonce must be 12 bytes');
193
+ }
194
+ return chacha20poly1305(key, nonce, aad);
195
+ }
196
+
197
+ // ═══════════════════════════════════════════════════════════════════════════
198
+ // CONSTANTS
199
+ // ═══════════════════════════════════════════════════════════════════════════
200
+
201
+ /** ChaCha20-Poly1305 key size (32 bytes) */
202
+ export const CHACHA20_KEY_SIZE = 32;
203
+
204
+ /** ChaCha20-Poly1305 nonce size (12 bytes) */
205
+ export const CHACHA20_NONCE_SIZE = 12;
206
+
207
+ /** XChaCha20-Poly1305 nonce size (24 bytes) */
208
+ export const XCHACHA20_NONCE_SIZE = 24;
209
+
210
+ /** Poly1305 tag size (16 bytes) */
211
+ export const POLY1305_TAG_SIZE = 16;
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Omnituum PQC Shared - HKDF (HMAC-based Key Derivation Function)
3
+ *
4
+ * HKDF is a NIST-approved key derivation function (RFC 5869).
5
+ * Supports SHA-256 (default) and SHA-512 hash functions.
6
+ *
7
+ * Two-step process:
8
+ * 1. Extract: Derive a pseudorandom key from input keying material
9
+ * 2. Expand: Generate output keying material from the PRK
10
+ *
11
+ * Used in Noise protocols for key derivation.
12
+ */
13
+
14
+ import { hkdf, extract, expand } from '@noble/hashes/hkdf';
15
+ import { sha256 } from '@noble/hashes/sha256';
16
+ import { sha512 } from '@noble/hashes/sha512';
17
+
18
+ // ═══════════════════════════════════════════════════════════════════════════
19
+ // TYPES
20
+ // ═══════════════════════════════════════════════════════════════════════════
21
+
22
+ export type HkdfHash = 'sha256' | 'sha512';
23
+
24
+ export interface HkdfOptions {
25
+ /** Hash function to use (default: 'sha256') */
26
+ hash?: HkdfHash;
27
+ /** Context info for domain separation */
28
+ info?: Uint8Array | string;
29
+ /** Salt (defaults to zeroes if not provided) */
30
+ salt?: Uint8Array;
31
+ }
32
+
33
+ // ═══════════════════════════════════════════════════════════════════════════
34
+ // HKDF FUNCTIONS
35
+ // ═══════════════════════════════════════════════════════════════════════════
36
+
37
+ /**
38
+ * Derive keys using HKDF (Extract + Expand).
39
+ *
40
+ * @param ikm - Input keying material
41
+ * @param outputLength - Desired output length in bytes
42
+ * @param options - Optional salt, info, and hash selection
43
+ * @returns Derived key material
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * // Basic key derivation
48
+ * const key = hkdfDerive(sharedSecret, 32);
49
+ *
50
+ * // With domain separation
51
+ * const key = hkdfDerive(sharedSecret, 32, {
52
+ * info: 'MyApp v1.0 encryption key'
53
+ * });
54
+ *
55
+ * // With salt
56
+ * const key = hkdfDerive(sharedSecret, 32, {
57
+ * salt: randomSalt,
58
+ * info: 'session key'
59
+ * });
60
+ * ```
61
+ */
62
+ export function hkdfDerive(
63
+ ikm: Uint8Array,
64
+ outputLength: number,
65
+ options?: HkdfOptions
66
+ ): Uint8Array {
67
+ const hashFn = getHashFunction(options?.hash ?? 'sha256');
68
+ const info = normalizeInfo(options?.info);
69
+ const salt = options?.salt;
70
+
71
+ return hkdf(hashFn, ikm, salt, info, outputLength);
72
+ }
73
+
74
+ /**
75
+ * HKDF-Extract: Derive a pseudorandom key from input keying material.
76
+ *
77
+ * @param ikm - Input keying material
78
+ * @param salt - Optional salt (defaults to zeroes)
79
+ * @param hash - Hash function ('sha256' or 'sha512')
80
+ * @returns Pseudorandom key (32 bytes for SHA-256, 64 for SHA-512)
81
+ */
82
+ export function hkdfExtract(
83
+ ikm: Uint8Array,
84
+ salt?: Uint8Array,
85
+ hash: HkdfHash = 'sha256'
86
+ ): Uint8Array {
87
+ const hashFn = getHashFunction(hash);
88
+ return extract(hashFn, ikm, salt);
89
+ }
90
+
91
+ /**
92
+ * HKDF-Expand: Expand a pseudorandom key into output keying material.
93
+ *
94
+ * @param prk - Pseudorandom key (from hkdfExtract)
95
+ * @param info - Context info for domain separation
96
+ * @param outputLength - Desired output length in bytes
97
+ * @param hash - Hash function ('sha256' or 'sha512')
98
+ * @returns Output keying material
99
+ */
100
+ export function hkdfExpand(
101
+ prk: Uint8Array,
102
+ info: Uint8Array | string | undefined,
103
+ outputLength: number,
104
+ hash: HkdfHash = 'sha256'
105
+ ): Uint8Array {
106
+ const hashFn = getHashFunction(hash);
107
+ return expand(hashFn, prk, normalizeInfo(info), outputLength);
108
+ }
109
+
110
+ // ═══════════════════════════════════════════════════════════════════════════
111
+ // NOISE PROTOCOL HELPERS
112
+ // ═══════════════════════════════════════════════════════════════════════════
113
+
114
+ /**
115
+ * HKDF for Noise protocol key derivation.
116
+ * Splits output into chaining key and output key.
117
+ *
118
+ * @param chainingKey - Current chaining key (salt)
119
+ * @param inputKeyMaterial - New key material to mix in
120
+ * @returns [newChainingKey, outputKey] - both 32 bytes
121
+ */
122
+ export function hkdfSplitForNoise(
123
+ chainingKey: Uint8Array,
124
+ inputKeyMaterial: Uint8Array
125
+ ): [Uint8Array, Uint8Array] {
126
+ const prk = hkdfExtract(inputKeyMaterial, chainingKey, 'sha256');
127
+ const output = hkdfExpand(prk, undefined, 64, 'sha256');
128
+
129
+ return [
130
+ output.slice(0, 32), // New chaining key
131
+ output.slice(32, 64), // Output key
132
+ ];
133
+ }
134
+
135
+ /**
136
+ * HKDF for Noise protocol with three outputs.
137
+ * Used when deriving transport keys at the end of handshake.
138
+ *
139
+ * @param chainingKey - Current chaining key
140
+ * @param inputKeyMaterial - Key material to mix
141
+ * @returns [ck, k1, k2] - chaining key and two output keys
142
+ */
143
+ export function hkdfTripleSplitForNoise(
144
+ chainingKey: Uint8Array,
145
+ inputKeyMaterial: Uint8Array
146
+ ): [Uint8Array, Uint8Array, Uint8Array] {
147
+ const prk = hkdfExtract(inputKeyMaterial, chainingKey, 'sha256');
148
+ const output = hkdfExpand(prk, undefined, 96, 'sha256');
149
+
150
+ return [
151
+ output.slice(0, 32), // New chaining key
152
+ output.slice(32, 64), // Output key 1
153
+ output.slice(64, 96), // Output key 2
154
+ ];
155
+ }
156
+
157
+ // ═══════════════════════════════════════════════════════════════════════════
158
+ // HELPERS
159
+ // ═══════════════════════════════════════════════════════════════════════════
160
+
161
+ function getHashFunction(hash: HkdfHash) {
162
+ switch (hash) {
163
+ case 'sha256':
164
+ return sha256;
165
+ case 'sha512':
166
+ return sha512;
167
+ default:
168
+ throw new Error(`Unsupported HKDF hash: ${hash}`);
169
+ }
170
+ }
171
+
172
+ function normalizeInfo(info: Uint8Array | string | undefined): Uint8Array {
173
+ if (!info) return new Uint8Array(0);
174
+ if (typeof info === 'string') return new TextEncoder().encode(info);
175
+ return info;
176
+ }
177
+
178
+ // ═══════════════════════════════════════════════════════════════════════════
179
+ // CONSTANTS
180
+ // ═══════════════════════════════════════════════════════════════════════════
181
+
182
+ /** HKDF-SHA256 output size (32 bytes) */
183
+ export const HKDF_SHA256_OUTPUT_SIZE = 32;
184
+
185
+ /** HKDF-SHA512 output size (64 bytes) */
186
+ export const HKDF_SHA512_OUTPUT_SIZE = 64;
187
+
188
+ /** Maximum HKDF output length for SHA-256 (255 * 32 = 8160 bytes) */
189
+ export const HKDF_SHA256_MAX_OUTPUT = 255 * 32;
190
+
191
+ /** Maximum HKDF output length for SHA-512 (255 * 64 = 16320 bytes) */
192
+ export const HKDF_SHA512_MAX_OUTPUT = 255 * 64;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Omnituum PQC Shared - Cryptographic Primitives
3
+ *
4
+ * Low-level cryptographic building blocks for higher-level protocols.
5
+ * These are exported for use in noise-kyber and other protocol implementations.
6
+ *
7
+ * PRIMITIVES (use directly):
8
+ * - BLAKE3: Fast hash function for transcripts
9
+ * - ChaCha20-Poly1305: AEAD encryption (12-byte nonce)
10
+ * - XChaCha20-Poly1305: AEAD encryption (24-byte nonce, safer)
11
+ * - HKDF: Key derivation function (NIST-approved)
12
+ */
13
+
14
+ // BLAKE3 hash function
15
+ export {
16
+ blake3,
17
+ blake3Hex,
18
+ blake3Mac,
19
+ blake3DeriveKey,
20
+ BLAKE3_OUTPUT_LENGTH,
21
+ BLAKE3_KEY_LENGTH,
22
+ BLAKE3_BLOCK_SIZE,
23
+ type Blake3Options,
24
+ } from './blake3';
25
+
26
+ // ChaCha20-Poly1305 AEAD
27
+ export {
28
+ chaCha20Poly1305Encrypt,
29
+ chaCha20Poly1305Decrypt,
30
+ xChaCha20Poly1305Encrypt,
31
+ xChaCha20Poly1305Decrypt,
32
+ createChaCha20Poly1305,
33
+ createXChaCha20Poly1305,
34
+ CHACHA20_KEY_SIZE,
35
+ CHACHA20_NONCE_SIZE,
36
+ XCHACHA20_NONCE_SIZE,
37
+ POLY1305_TAG_SIZE,
38
+ type ChaChaPayload,
39
+ } from './chacha';
40
+
41
+ // HKDF key derivation
42
+ export {
43
+ hkdfDerive,
44
+ hkdfExtract,
45
+ hkdfExpand,
46
+ hkdfSplitForNoise,
47
+ hkdfTripleSplitForNoise,
48
+ HKDF_SHA256_OUTPUT_SIZE,
49
+ HKDF_SHA512_OUTPUT_SIZE,
50
+ HKDF_SHA256_MAX_OUTPUT,
51
+ HKDF_SHA512_MAX_OUTPUT,
52
+ type HkdfHash,
53
+ type HkdfOptions,
54
+ } from './hkdf';
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Omnituum PQC Shared - Cryptographic Primitives
3
+ *
4
+ * Pure browser implementation - no Node.js dependencies.
5
+ * Uses Web Crypto API and @noble/hashes for all operations.
6
+ */
7
+
8
+ import { sha256 as nobleSha256 } from '@noble/hashes/sha256';
9
+ import { hmac } from '@noble/hashes/hmac';
10
+
11
+ // ═══════════════════════════════════════════════════════════════════════════
12
+ // TEXT ENCODING
13
+ // ═══════════════════════════════════════════════════════════════════════════
14
+
15
+ export const textEncoder = new TextEncoder();
16
+ export const textDecoder = new TextDecoder();
17
+
18
+ // ═══════════════════════════════════════════════════════════════════════════
19
+ // BASE64 UTILITIES
20
+ // ═══════════════════════════════════════════════════════════════════════════
21
+
22
+ export function toB64(bytes: Uint8Array): string {
23
+ let binary = '';
24
+ for (let i = 0; i < bytes.length; i++) {
25
+ binary += String.fromCharCode(bytes[i]);
26
+ }
27
+ return btoa(binary);
28
+ }
29
+
30
+ export function fromB64(str: string): Uint8Array {
31
+ const binary = atob(str);
32
+ const bytes = new Uint8Array(binary.length);
33
+ for (let i = 0; i < binary.length; i++) {
34
+ bytes[i] = binary.charCodeAt(i);
35
+ }
36
+ return bytes;
37
+ }
38
+
39
+ // ═══════════════════════════════════════════════════════════════════════════
40
+ // HEX UTILITIES
41
+ // ═══════════════════════════════════════════════════════════════════════════
42
+
43
+ export function toHex(bytes: Uint8Array): string {
44
+ return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
45
+ }
46
+
47
+ export function fromHex(hex: string): Uint8Array {
48
+ const s = hex.startsWith('0x') ? hex.slice(2) : hex;
49
+ const normalized = s.length % 2 ? '0' + s : s;
50
+ const out = new Uint8Array(normalized.length / 2);
51
+ for (let i = 0; i < out.length; i++) {
52
+ out[i] = parseInt(normalized.slice(i * 2, i * 2 + 2), 16);
53
+ }
54
+ return out;
55
+ }
56
+
57
+ // ═══════════════════════════════════════════════════════════════════════════
58
+ // VALIDATION
59
+ // ═══════════════════════════════════════════════════════════════════════════
60
+
61
+ export function assertLen(label: string, arr: Uint8Array, n: number): void {
62
+ if (arr.length !== n) {
63
+ throw new Error(`${label} must be ${n} bytes, got ${arr.length}`);
64
+ }
65
+ }
66
+
67
+ // ═══════════════════════════════════════════════════════════════════════════
68
+ // RANDOMNESS (Web Crypto API)
69
+ // ═══════════════════════════════════════════════════════════════════════════
70
+
71
+ export function rand32(): Uint8Array {
72
+ return globalThis.crypto.getRandomValues(new Uint8Array(32));
73
+ }
74
+
75
+ export function rand24(): Uint8Array {
76
+ return globalThis.crypto.getRandomValues(new Uint8Array(24));
77
+ }
78
+
79
+ export function rand12(): Uint8Array {
80
+ return globalThis.crypto.getRandomValues(new Uint8Array(12));
81
+ }
82
+
83
+ export function randN(n: number): Uint8Array {
84
+ return globalThis.crypto.getRandomValues(new Uint8Array(n));
85
+ }
86
+
87
+ // ═══════════════════════════════════════════════════════════════════════════
88
+ // HASHING (@noble/hashes)
89
+ // ═══════════════════════════════════════════════════════════════════════════
90
+
91
+ export function sha256(bytes: Uint8Array): Uint8Array {
92
+ return nobleSha256(bytes);
93
+ }
94
+
95
+ export function sha256String(str: string): Uint8Array {
96
+ return sha256(textEncoder.encode(str));
97
+ }
98
+
99
+ // ═══════════════════════════════════════════════════════════════════════════
100
+ // KEY DERIVATION (HKDF-SHA-256)
101
+ // ═══════════════════════════════════════════════════════════════════════════
102
+
103
+ export function hkdfSha256(
104
+ ikm: Uint8Array,
105
+ opts?: { salt?: Uint8Array; info?: Uint8Array; length?: number }
106
+ ): Uint8Array {
107
+ const salt = opts?.salt ?? new Uint8Array(32);
108
+ const info = opts?.info ?? new Uint8Array(0);
109
+ const L = opts?.length ?? 32;
110
+
111
+ // Extract
112
+ const prk = hmac(nobleSha256, salt, ikm);
113
+
114
+ // Expand
115
+ let t = new Uint8Array(0);
116
+ const chunks: Uint8Array[] = [];
117
+ for (let i = 1; i <= Math.ceil(L / 32); i++) {
118
+ const input = new Uint8Array(t.length + info.length + 1);
119
+ input.set(t, 0);
120
+ input.set(info, t.length);
121
+ input[input.length - 1] = i;
122
+ t = new Uint8Array(hmac(nobleSha256, prk, input));
123
+ chunks.push(t);
124
+ }
125
+
126
+ const out = new Uint8Array(L);
127
+ let off = 0;
128
+ for (const c of chunks) {
129
+ out.set(c.subarray(0, L - off), off);
130
+ off += c.length;
131
+ if (off >= L) break;
132
+ }
133
+ return out;
134
+ }
135
+
136
+ // ═══════════════════════════════════════════════════════════════════════════
137
+ // CONVENIENCE EXPORTS
138
+ // ═══════════════════════════════════════════════════════════════════════════
139
+
140
+ export const b64 = toB64;
141
+ export const ub64 = fromB64;
142
+
143
+ export const u8 = (s: string | Uint8Array) =>
144
+ typeof s === 'string' ? textEncoder.encode(s) : s;