@arkstack/encryption 0.18.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.
@@ -0,0 +1,914 @@
1
+ //#region src/EncryptionKey.d.ts
2
+ /**
3
+ * A symmetric key, held as raw bytes and convertible to every representation
4
+ * the rest of the library (and the wire) needs.
5
+ *
6
+ * Keys are values: two keys with the same bytes are equal regardless of how
7
+ * they were produced, and comparison is constant time.
8
+ */
9
+ declare class EncryptionKey {
10
+ readonly bytes: Uint8Array;
11
+ /**
12
+ * @param bytes Raw key material.
13
+ */
14
+ constructor(bytes: Uint8Array);
15
+ /**
16
+ * Generate a random key.
17
+ *
18
+ * @param length Key length in bytes, defaults to 32 (AES-256).
19
+ * @returns
20
+ */
21
+ static generate(length?: number): EncryptionKey;
22
+ /**
23
+ * Derive a key from an arbitrary secret by hashing it with SHA-256.
24
+ *
25
+ * This mirrors how Arkstack turns `APP_KEY` into a cipher key, so a value
26
+ * encrypted on the server with the app key can be decrypted in the browser
27
+ * from the same secret.
28
+ *
29
+ * @param secret
30
+ * @returns
31
+ */
32
+ static fromSecret(secret: string): Promise<EncryptionKey>;
33
+ /**
34
+ * Restore a key from its base64url representation.
35
+ *
36
+ * @param value
37
+ * @returns
38
+ */
39
+ static fromBase64Url(value: string): EncryptionKey;
40
+ /**
41
+ * Restore a key from its hex representation.
42
+ *
43
+ * @param value
44
+ * @returns
45
+ */
46
+ static fromHex(value: string): EncryptionKey;
47
+ /**
48
+ * Stretch a password into a key using PBKDF2-HMAC-SHA256.
49
+ *
50
+ * Prefer this over {@link fromSecret} for anything a human typed; the
51
+ * returned salt and iteration count must be stored alongside the
52
+ * ciphertext to reproduce the key later.
53
+ *
54
+ * @param password
55
+ * @param options
56
+ * @returns
57
+ */
58
+ static derive(password: string, options?: DeriveOptions): Promise<DerivedKey>;
59
+ /**
60
+ * Expand shared secret material into a key using HKDF-SHA256.
61
+ *
62
+ * Used internally by the ECDH channel and sealed box helpers, and exposed
63
+ * because deriving sub-keys from one root key is a common need.
64
+ *
65
+ * @param material
66
+ * @param salt
67
+ * @param info
68
+ * @param length
69
+ * @returns
70
+ */
71
+ static expand(material: Uint8Array, salt: Uint8Array, info: string, length?: number): Promise<EncryptionKey>;
72
+ /**
73
+ * Coerce any accepted key representation into an `EncryptionKey`.
74
+ *
75
+ * A string of exactly `length` bytes once base64url decoded is treated as
76
+ * raw key material; anything else is treated as a passphrase and hashed.
77
+ *
78
+ * @param input
79
+ * @param length Expected key length in bytes.
80
+ * @returns
81
+ */
82
+ static resolve(input: KeyInput, length?: number): Promise<EncryptionKey>;
83
+ /**
84
+ * Constant time comparison of two keys, in any representation that does not
85
+ * require asynchronous work.
86
+ *
87
+ * @param left
88
+ * @param right
89
+ * @returns
90
+ */
91
+ static compare(left: EncryptionKey | Uint8Array | string, right: EncryptionKey | Uint8Array | string): boolean;
92
+ /**
93
+ * Import this key into Web Crypto for the given algorithm.
94
+ *
95
+ * @param algorithm
96
+ * @param usages
97
+ * @returns
98
+ */
99
+ cryptoKey(algorithm?: AlgorithmIdentifier | AesKeyAlgorithm | HmacImportParams, usages?: KeyUsage[]): Promise<CryptoKey>;
100
+ /**
101
+ * A stable, shareable digest of this key. Safe to log or display; it does
102
+ * not reveal the key itself.
103
+ *
104
+ * @param options
105
+ * @returns
106
+ */
107
+ fingerprint(options?: FingerprintOptions): Promise<string>;
108
+ /**
109
+ * Constant time comparison against another key.
110
+ *
111
+ * @param other
112
+ * @returns
113
+ */
114
+ equals(other: EncryptionKey | Uint8Array | string): boolean;
115
+ /**
116
+ * Key length in bytes.
117
+ *
118
+ * @returns
119
+ */
120
+ get length(): number;
121
+ /**
122
+ * Base64url representation, the format used to persist and transport keys.
123
+ *
124
+ * @returns
125
+ */
126
+ toBase64Url(): string;
127
+ /**
128
+ * Hex representation.
129
+ *
130
+ * @returns
131
+ */
132
+ toHex(): string;
133
+ /**
134
+ * Base64url representation.
135
+ *
136
+ * @returns
137
+ */
138
+ toString(): string;
139
+ /**
140
+ * Keep keys out of accidental `JSON.stringify` output of surrounding
141
+ * objects by requiring an explicit `toBase64Url()` call.
142
+ *
143
+ * @returns
144
+ */
145
+ toJSON(): string;
146
+ /**
147
+ * Reduce a comparable key representation to bytes.
148
+ *
149
+ * @param value
150
+ * @returns
151
+ */
152
+ private static materialize;
153
+ }
154
+ //#endregion
155
+ //#region src/types.d.ts
156
+ /**
157
+ * Anything that can stand in for a symmetric key.
158
+ *
159
+ * - `EncryptionKey`: an already materialised key.
160
+ * - `Uint8Array`: raw key bytes (must match the cipher key length).
161
+ * - `CryptoKey`: a Web Crypto key, used as-is.
162
+ * - `string`: a base64url encoded key of the right length, otherwise treated as
163
+ * a passphrase and hashed with SHA-256 (matching Arkstack's `APP_KEY` behaviour).
164
+ */
165
+ type KeyInput = EncryptionKey | Uint8Array | CryptoKey | string;
166
+ /**
167
+ * A serialised, transport safe key pair. Both halves are base64url strings:
168
+ * the public key is SPKI DER, the private key is PKCS#8 DER.
169
+ */
170
+ interface SerializedKeyPair {
171
+ publicKey: string;
172
+ privateKey: string;
173
+ }
174
+ /**
175
+ * Optional per-operation cipher settings.
176
+ */
177
+ interface CipherOptions {
178
+ /**
179
+ * Additional authenticated data. Not encrypted, but bound to the
180
+ * ciphertext: decryption fails unless the same value is supplied.
181
+ */
182
+ aad?: Uint8Array | string;
183
+ }
184
+ /**
185
+ * Options for password based key derivation (PBKDF2-HMAC-SHA256).
186
+ */
187
+ interface DeriveOptions {
188
+ /**
189
+ * Salt bytes or a base64url encoded salt. Generated when omitted.
190
+ */
191
+ salt?: Uint8Array | string;
192
+ /**
193
+ * PBKDF2 iteration count. Defaults to 210,000 (OWASP 2023 guidance).
194
+ */
195
+ iterations?: number;
196
+ /**
197
+ * Derived key length in bytes. Defaults to 32 (AES-256).
198
+ */
199
+ length?: number;
200
+ }
201
+ /**
202
+ * The result of a password based derivation, including the salt needed to
203
+ * reproduce it.
204
+ */
205
+ interface DerivedKey {
206
+ key: EncryptionKey;
207
+ salt: string;
208
+ iterations: number;
209
+ }
210
+ /**
211
+ * Options controlling how a shared secret is stretched into a channel key.
212
+ */
213
+ interface ChannelOptions {
214
+ /**
215
+ * Domain separation string mixed into the HKDF `info` parameter. Two peers
216
+ * must use the same value to land on the same key.
217
+ */
218
+ info?: string;
219
+ }
220
+ /**
221
+ * Options for rendering a key fingerprint.
222
+ */
223
+ interface FingerprintOptions {
224
+ /**
225
+ * Number of digest bytes to render. Defaults to 32 (the full SHA-256).
226
+ */
227
+ length?: number;
228
+ /**
229
+ * Output encoding. Defaults to `'hex'`.
230
+ */
231
+ encoding?: 'hex' | 'base64url';
232
+ /**
233
+ * When set, group the output into blocks of this many characters,
234
+ * separated by spaces. Makes fingerprints readable for manual comparison.
235
+ */
236
+ group?: number;
237
+ }
238
+ //#endregion
239
+ //#region src/Cipher.d.ts
240
+ /**
241
+ * AES-256-GCM symmetric encryption built on the Web Crypto API.
242
+ *
243
+ * Payloads are colon delimited base64url triples — `<iv>:<authTag>:<ciphertext>`
244
+ * — which is byte for byte the format Arkstack has always written. A value
245
+ * encrypted by a Node server decrypts in the browser and vice versa, provided
246
+ * both sides hold the same key.
247
+ */
248
+ declare class Cipher {
249
+ readonly key: EncryptionKey;
250
+ /** Initialisation vector length in bytes. */
251
+ static readonly ivLength = 12;
252
+ /** GCM authentication tag length in bytes. */
253
+ static readonly tagLength = 16;
254
+ /**
255
+ * @param key The symmetric key this cipher operates with.
256
+ */
257
+ constructor(key: EncryptionKey);
258
+ /**
259
+ * Build a cipher from any accepted key representation.
260
+ *
261
+ * @param key
262
+ * @returns
263
+ */
264
+ static from(key: KeyInput): Promise<Cipher>;
265
+ /**
266
+ * Build a cipher backed by a freshly generated random key.
267
+ *
268
+ * @returns
269
+ */
270
+ static create(): Cipher;
271
+ /**
272
+ * Encrypt a string.
273
+ *
274
+ * @param value
275
+ * @param key
276
+ * @param options
277
+ * @returns
278
+ */
279
+ static encrypt(value: string, key: KeyInput, options?: CipherOptions): Promise<string>;
280
+ /**
281
+ * Decrypt a payload produced by {@link encrypt}.
282
+ *
283
+ * @param payload
284
+ * @param key
285
+ * @param options
286
+ * @returns
287
+ */
288
+ static decrypt(payload: string, key: KeyInput, options?: CipherOptions): Promise<string>;
289
+ /**
290
+ * Whether a string is shaped like a cipher payload. A cheap structural
291
+ * check, not an authenticity check.
292
+ *
293
+ * @param value
294
+ * @returns
295
+ */
296
+ static looksLikePayload(value: unknown): value is string;
297
+ /**
298
+ * Encrypt a UTF-8 string.
299
+ *
300
+ * @param value
301
+ * @param options
302
+ * @returns
303
+ */
304
+ encrypt(value: string, options?: CipherOptions): Promise<string>;
305
+ /**
306
+ * Decrypt a payload back into a UTF-8 string.
307
+ *
308
+ * @param payload
309
+ * @param options
310
+ * @returns
311
+ */
312
+ decrypt(payload: string, options?: CipherOptions): Promise<string>;
313
+ /**
314
+ * Encrypt arbitrary bytes.
315
+ *
316
+ * @param bytes
317
+ * @param options
318
+ * @returns
319
+ */
320
+ encryptBytes(bytes: Uint8Array, options?: CipherOptions): Promise<string>;
321
+ /**
322
+ * Decrypt a payload back into raw bytes.
323
+ *
324
+ * @param payload
325
+ * @param options
326
+ * @returns
327
+ */
328
+ decryptBytes(payload: string, options?: CipherOptions): Promise<Uint8Array>;
329
+ /**
330
+ * Import the key once per cipher instance.
331
+ *
332
+ * @returns
333
+ */
334
+ private cryptoKey;
335
+ /**
336
+ * Build the AES-GCM parameters for a single operation.
337
+ *
338
+ * @param iv
339
+ * @param options
340
+ * @returns
341
+ */
342
+ private parameters;
343
+ private imported?;
344
+ }
345
+ //#endregion
346
+ //#region src/KeyPair.d.ts
347
+ /**
348
+ * An ECDH P-256 key pair — the identity half of end-to-end encryption.
349
+ *
350
+ * P-256 is the curve every mainstream Web Crypto implementation supports, so a
351
+ * key pair generated in Node imports cleanly in the browser and vice versa.
352
+ * Keys serialise to base64url DER (SPKI for public, PKCS#8 for private), which
353
+ * survives JSON, headers, query strings and database columns unchanged.
354
+ */
355
+ declare class KeyPair {
356
+ readonly publicKey: CryptoKey;
357
+ readonly privateKey?: CryptoKey | undefined;
358
+ /**
359
+ * @param publicKey
360
+ * @param privateKey Absent for peer key pairs, where only the public half is known.
361
+ */
362
+ constructor(publicKey: CryptoKey, privateKey?: CryptoKey | undefined);
363
+ /**
364
+ * Generate a new key pair.
365
+ *
366
+ * @returns
367
+ */
368
+ static generate(): Promise<KeyPair>;
369
+ /**
370
+ * Restore a key pair from its serialised form.
371
+ *
372
+ * @param serialized
373
+ * @returns
374
+ */
375
+ static import(serialized: SerializedKeyPair): Promise<KeyPair>;
376
+ /**
377
+ * Restore a full key pair from the private half alone; the public key is
378
+ * recovered from the private key's curve point.
379
+ *
380
+ * @param privateKey
381
+ * @returns
382
+ */
383
+ static fromPrivateKey(privateKey: string | CryptoKey): Promise<KeyPair>;
384
+ /**
385
+ * Wrap a peer's public key. The result can verify fingerprints and receive
386
+ * sealed messages, but cannot derive shared secrets on its own.
387
+ *
388
+ * @param publicKey
389
+ * @returns
390
+ */
391
+ static fromPublicKey(publicKey: string | CryptoKey): Promise<KeyPair>;
392
+ /**
393
+ * Import a base64url SPKI public key.
394
+ *
395
+ * @param publicKey
396
+ * @returns
397
+ */
398
+ static importPublicKey(publicKey: string): Promise<CryptoKey>;
399
+ /**
400
+ * Import a base64url PKCS#8 private key.
401
+ *
402
+ * @param privateKey
403
+ * @returns
404
+ */
405
+ static importPrivateKey(privateKey: string): Promise<CryptoKey>;
406
+ /**
407
+ * Export a public key to its base64url SPKI form.
408
+ *
409
+ * @param publicKey
410
+ * @returns
411
+ */
412
+ static exportPublicKey(publicKey: CryptoKey): Promise<string>;
413
+ /**
414
+ * Derive raw ECDH shared bits between a private key and a peer public key.
415
+ *
416
+ * The result is the raw curve point and must be stretched with a KDF before
417
+ * use as a cipher key — {@link SecureChannel} does that for you.
418
+ *
419
+ * @param privateKey
420
+ * @param peerPublicKey
421
+ * @param length Output length in bits, defaults to the P-256 field size.
422
+ * @returns
423
+ */
424
+ static sharedBits(privateKey: CryptoKey, peerPublicKey: CryptoKey, length?: number): Promise<Uint8Array>;
425
+ /**
426
+ * A human comparable digest of a public key. Two peers reading the same
427
+ * fingerprint aloud are holding the same key.
428
+ *
429
+ * @param publicKey
430
+ * @param options
431
+ * @returns
432
+ */
433
+ static fingerprintOf(publicKey: string | CryptoKey, options?: FingerprintOptions): Promise<string>;
434
+ /**
435
+ * The digest of both participants' public keys, ordered deterministically
436
+ * so each side computes the same value. Rendered as five digit groups in
437
+ * the style of a messaging app's safety number.
438
+ *
439
+ * @param first
440
+ * @param second
441
+ * @param groups How many five digit groups to render, defaults to 12.
442
+ * @returns
443
+ */
444
+ static safetyNumber(first: string, second: string, groups?: number): Promise<string>;
445
+ /**
446
+ * Order two public keys deterministically so both peers derive identical
447
+ * salts and safety numbers regardless of who initiated.
448
+ *
449
+ * @param first
450
+ * @param second
451
+ * @returns
452
+ */
453
+ static order(first: string, second: string): [string, string];
454
+ /**
455
+ * Whether the private half is available.
456
+ *
457
+ * @returns
458
+ */
459
+ get isComplete(): boolean;
460
+ /**
461
+ * Serialise both halves. Throws when the private key is missing.
462
+ *
463
+ * @returns
464
+ */
465
+ export(): Promise<SerializedKeyPair>;
466
+ /**
467
+ * The base64url SPKI public key, safe to publish.
468
+ *
469
+ * @returns
470
+ */
471
+ exportPublicKey(): Promise<string>;
472
+ /**
473
+ * Derive the raw ECDH shared bits with a peer.
474
+ *
475
+ * @param peerPublicKey
476
+ * @returns
477
+ */
478
+ sharedBits(peerPublicKey: string | CryptoKey | KeyPair): Promise<Uint8Array>;
479
+ /**
480
+ * A comparable digest of this key pair's public key.
481
+ *
482
+ * @param options
483
+ * @returns
484
+ */
485
+ fingerprint(options?: FingerprintOptions): Promise<string>;
486
+ /**
487
+ * Normalise anything that can stand in for a public key.
488
+ *
489
+ * @param value
490
+ * @returns
491
+ */
492
+ static resolvePublic(value: string | CryptoKey | KeyPair): Promise<CryptoKey>;
493
+ }
494
+ //#endregion
495
+ //#region src/Keys.d.ts
496
+ /**
497
+ * Key generation and comparison helpers.
498
+ *
499
+ * Generating keys is easy to get wrong quietly and comparing them is easy to
500
+ * get wrong dangerously, so both live here: every comparison in this class runs
501
+ * in constant time, and every generator draws from the platform CSPRNG.
502
+ */
503
+ declare class Keys {
504
+ /**
505
+ * Generate a random symmetric key.
506
+ *
507
+ * @param length Key length in bytes, defaults to 32 (AES-256).
508
+ * @returns
509
+ */
510
+ static generate(length?: number): EncryptionKey;
511
+ /**
512
+ * Generate a random symmetric key as a base64url string, ready to store in
513
+ * an environment variable or a database column.
514
+ *
515
+ * @param length
516
+ * @returns
517
+ */
518
+ static generateString(length?: number): string;
519
+ /**
520
+ * Generate a random, URL safe token. Not a key — use it for invites,
521
+ * one-time links and other opaque identifiers.
522
+ *
523
+ * @param bytes
524
+ * @returns
525
+ */
526
+ static token(bytes?: number): string;
527
+ /**
528
+ * Generate an end-to-end encryption identity: an ECDH key pair whose public
529
+ * half is published and whose private half never leaves its owner.
530
+ *
531
+ * @returns
532
+ */
533
+ static generatePair(): Promise<KeyPair>;
534
+ /**
535
+ * Generate an identity and return it already serialised for storage or
536
+ * transport.
537
+ *
538
+ * @returns
539
+ */
540
+ static generateSerializedPair(): Promise<SerializedKeyPair>;
541
+ /**
542
+ * Hash an arbitrary secret into a key with SHA-256, the same way Arkstack
543
+ * turns `APP_KEY` into a cipher key.
544
+ *
545
+ * @param secret
546
+ * @returns
547
+ */
548
+ static fromSecret(secret: string): Promise<EncryptionKey>;
549
+ /**
550
+ * Stretch a user supplied password into a key with PBKDF2-HMAC-SHA256.
551
+ *
552
+ * @param password
553
+ * @param options
554
+ * @returns
555
+ */
556
+ static derive(password: string, options?: DeriveOptions): Promise<DerivedKey>;
557
+ /**
558
+ * Constant time comparison of two keys already in key form.
559
+ *
560
+ * @param left
561
+ * @param right
562
+ * @returns
563
+ */
564
+ static compare(left: EncryptionKey | Uint8Array | string, right: EncryptionKey | Uint8Array | string): boolean;
565
+ /**
566
+ * Constant time comparison that first resolves both sides through the same
567
+ * rules the ciphers use, so a passphrase can be checked against the key it
568
+ * produces.
569
+ *
570
+ * @param left
571
+ * @param right
572
+ * @param length Expected key length in bytes.
573
+ * @returns
574
+ */
575
+ static matches(left: KeyInput, right: KeyInput, length?: number): Promise<boolean>;
576
+ /**
577
+ * A displayable digest of a symmetric key.
578
+ *
579
+ * @param key
580
+ * @param options
581
+ * @returns
582
+ */
583
+ static fingerprint(key: KeyInput, options?: FingerprintOptions): Promise<string>;
584
+ /**
585
+ * A displayable digest of a public key, for comparing identities.
586
+ *
587
+ * @param publicKey
588
+ * @param options
589
+ * @returns
590
+ */
591
+ static fingerprintPublicKey(publicKey: string | CryptoKey, options?: FingerprintOptions): Promise<string>;
592
+ /**
593
+ * The safety number for a conversation between two public keys. Both peers
594
+ * compute the same string; showing it side by side proves no third party
595
+ * substituted a key in transit.
596
+ *
597
+ * @param first
598
+ * @param second
599
+ * @param groups
600
+ * @returns
601
+ */
602
+ static safetyNumber(first: string, second: string, groups?: number): Promise<string>;
603
+ /**
604
+ * Confirm a safety number a user read out or scanned, in constant time.
605
+ *
606
+ * @param first
607
+ * @param second
608
+ * @param expected
609
+ * @returns
610
+ */
611
+ static confirmSafetyNumber(first: string, second: string, expected: string): Promise<boolean>;
612
+ /**
613
+ * Whether two public keys refer to the same identity.
614
+ *
615
+ * @param left
616
+ * @param right
617
+ * @returns
618
+ */
619
+ static samePublicKey(left: string | CryptoKey | KeyPair, right: string | CryptoKey | KeyPair): Promise<boolean>;
620
+ }
621
+ //#endregion
622
+ //#region src/SealedBox.d.ts
623
+ /**
624
+ * Anonymous encryption to a public key.
625
+ *
626
+ * The sender needs no identity of their own: a throwaway key pair is generated
627
+ * per message, agreed with the recipient's public key over ECDH, and its public
628
+ * half is carried in the payload so the recipient can reproduce the secret.
629
+ * Only the holder of the matching private key can open the result — including
630
+ * the sender, who cannot decrypt their own message afterwards.
631
+ *
632
+ * Payloads look like `ark1:<ephemeralPublicKey>:<iv>:<authTag>:<ciphertext>`.
633
+ */
634
+ declare class SealedBox {
635
+ /** Payload discriminator. */
636
+ static readonly prefix = "ark1";
637
+ /**
638
+ * Encrypt a message to a recipient's public key.
639
+ *
640
+ * @param message
641
+ * @param recipientPublicKey
642
+ * @param options
643
+ * @returns
644
+ */
645
+ static seal(message: string, recipientPublicKey: string | CryptoKey | KeyPair, options?: CipherOptions): Promise<string>;
646
+ /**
647
+ * Open a sealed payload with the recipient's private key.
648
+ *
649
+ * @param payload
650
+ * @param recipientPrivateKey
651
+ * @param options
652
+ * @returns
653
+ */
654
+ static open(payload: string, recipientPrivateKey: string | CryptoKey | KeyPair, options?: CipherOptions): Promise<string>;
655
+ /**
656
+ * Whether a string is shaped like a sealed payload.
657
+ *
658
+ * @param value
659
+ * @returns
660
+ */
661
+ static looksLikePayload(value: unknown): value is string;
662
+ /**
663
+ * Derive the one-off message key. Both sides feed the same ordered pair of
664
+ * public keys into the salt, so sender and recipient agree.
665
+ *
666
+ * @param owner The side holding a private key.
667
+ * @param peer The other side's public key.
668
+ * @param ephemeralPublicKey
669
+ * @param recipientPublicKey
670
+ * @returns
671
+ */
672
+ private static derive;
673
+ }
674
+ //#endregion
675
+ //#region src/SecureChannel.d.ts
676
+ /**
677
+ * A two party end-to-end encrypted channel.
678
+ *
679
+ * Each side combines its own private key with the other side's public key over
680
+ * ECDH, stretches the result with HKDF-SHA256, and ends up holding the exact
681
+ * same AES-256-GCM key without that key ever crossing the wire. Messages
682
+ * encrypted by either peer — in Node or in a browser — decrypt on the other.
683
+ *
684
+ * ```ts
685
+ * const alice = await KeyPair.generate()
686
+ * const bob = await KeyPair.generate()
687
+ *
688
+ * const outbound = await SecureChannel.between(alice, await bob.exportPublicKey())
689
+ * const inbound = await SecureChannel.between(bob, await alice.exportPublicKey())
690
+ *
691
+ * await inbound.decrypt(await outbound.encrypt('hey')) // 'hey'
692
+ * ```
693
+ */
694
+ declare class SecureChannel {
695
+ readonly cipher: Cipher;
696
+ readonly localPublicKey: string;
697
+ readonly remotePublicKey: string;
698
+ /**
699
+ * @param cipher The cipher bound to the derived shared key.
700
+ * @param localPublicKey This side's public key, base64url.
701
+ * @param remotePublicKey The peer's public key, base64url.
702
+ */
703
+ private constructor();
704
+ /**
705
+ * Open a channel between a local key pair (or private key) and a peer's
706
+ * public key.
707
+ *
708
+ * @param local
709
+ * @param peerPublicKey
710
+ * @param options
711
+ * @returns
712
+ */
713
+ static between(local: KeyPair | string | CryptoKey, peerPublicKey: KeyPair | string | CryptoKey, options?: ChannelOptions): Promise<SecureChannel>;
714
+ /**
715
+ * The HKDF salt for a pair of participants: a digest over both public keys
716
+ * in a deterministic order, so both sides compute the same value.
717
+ *
718
+ * @param first
719
+ * @param second
720
+ * @returns
721
+ */
722
+ static salt(first: string, second: string): Promise<Uint8Array>;
723
+ /**
724
+ * The shared key both peers derived. Persist it only if you intend to skip
725
+ * the handshake later; it is as sensitive as the messages themselves.
726
+ *
727
+ * @returns
728
+ */
729
+ get key(): EncryptionKey;
730
+ /**
731
+ * Encrypt a message for the peer.
732
+ *
733
+ * @param message
734
+ * @param options
735
+ * @returns
736
+ */
737
+ encrypt(message: string, options?: CipherOptions): Promise<string>;
738
+ /**
739
+ * Decrypt a message from the peer.
740
+ *
741
+ * @param payload
742
+ * @param options
743
+ * @returns
744
+ */
745
+ decrypt(payload: string, options?: CipherOptions): Promise<string>;
746
+ /**
747
+ * Fingerprint of the derived shared key. Identical on both sides, and the
748
+ * cheapest way to assert two peers really did agree on the same secret.
749
+ *
750
+ * @param options
751
+ * @returns
752
+ */
753
+ fingerprint(options?: FingerprintOptions): Promise<string>;
754
+ /**
755
+ * The conversation's safety number: show it to both participants so they
756
+ * can confirm out of band that nobody is sitting in the middle.
757
+ *
758
+ * @param groups
759
+ * @returns
760
+ */
761
+ safetyNumber(groups?: number): Promise<string>;
762
+ }
763
+ //#endregion
764
+ //#region src/support/codec.d.ts
765
+ /**
766
+ * Runtime agnostic binary/text conversion helpers.
767
+ *
768
+ * Everything here is implemented against `Uint8Array`, `TextEncoder` and
769
+ * `TextDecoder` so the exact same code path runs in Node, Deno, Bun, browsers
770
+ * and workers. No `Buffer`, no `node:crypto`.
771
+ */
772
+ declare class Codec {
773
+ private static readonly encoder;
774
+ private static readonly decoder;
775
+ /**
776
+ * Encode a UTF-8 string to bytes.
777
+ *
778
+ * @param value
779
+ * @returns
780
+ */
781
+ static encodeUtf8(value: string): Uint8Array;
782
+ /**
783
+ * Decode bytes back to a UTF-8 string.
784
+ *
785
+ * @param bytes
786
+ * @returns
787
+ */
788
+ static decodeUtf8(bytes: Uint8Array): string;
789
+ /**
790
+ * Encode bytes as standard (padded) base64.
791
+ *
792
+ * @param bytes
793
+ * @returns
794
+ */
795
+ static encodeBase64(bytes: Uint8Array): string;
796
+ /**
797
+ * Decode standard (padded or unpadded) base64 to bytes.
798
+ *
799
+ * @param value
800
+ * @returns
801
+ */
802
+ static decodeBase64(value: string): Uint8Array;
803
+ /**
804
+ * Encode bytes as unpadded base64url, the wire format used by every
805
+ * Arkstack encryption payload.
806
+ *
807
+ * @param bytes
808
+ * @returns
809
+ */
810
+ static encodeBase64Url(bytes: Uint8Array): string;
811
+ /**
812
+ * Decode a base64url string to bytes.
813
+ *
814
+ * @param value
815
+ * @returns
816
+ */
817
+ static decodeBase64Url(value: string): Uint8Array;
818
+ /**
819
+ * Encode bytes as lowercase hex.
820
+ *
821
+ * @param bytes
822
+ * @returns
823
+ */
824
+ static encodeHex(bytes: Uint8Array): string;
825
+ /**
826
+ * Decode a hex string to bytes.
827
+ *
828
+ * @param value
829
+ * @returns
830
+ */
831
+ static decodeHex(value: string): Uint8Array;
832
+ /**
833
+ * Concatenate byte sequences into a single buffer.
834
+ *
835
+ * @param parts
836
+ * @returns
837
+ */
838
+ static concat(...parts: Uint8Array[]): Uint8Array;
839
+ /**
840
+ * Compare two byte sequences without leaking their contents through timing.
841
+ *
842
+ * The length check is intentionally not constant time; key and digest
843
+ * lengths are public information.
844
+ *
845
+ * @param left
846
+ * @param right
847
+ * @returns
848
+ */
849
+ static equals(left: Uint8Array, right: Uint8Array): boolean;
850
+ /**
851
+ * Normalize a `Uint8Array`, `ArrayBuffer` or `ArrayBufferView` to bytes.
852
+ *
853
+ * @param value
854
+ * @returns
855
+ */
856
+ static toBytes(value: ArrayBuffer | ArrayBufferView): Uint8Array;
857
+ /**
858
+ * Restore base64 padding stripped by the base64url encoding.
859
+ *
860
+ * @param value
861
+ * @returns
862
+ */
863
+ private static pad;
864
+ /**
865
+ * Pure JS base64 encoder used when `btoa` is unavailable.
866
+ *
867
+ * @param bytes
868
+ * @returns
869
+ */
870
+ private static fallbackEncodeBase64;
871
+ /**
872
+ * Pure JS base64 decoder used when `atob` is unavailable.
873
+ *
874
+ * @param value
875
+ * @returns
876
+ */
877
+ private static fallbackDecodeBase64;
878
+ }
879
+ //#endregion
880
+ //#region src/support/subtle.d.ts
881
+ /**
882
+ * Resolve the ambient Web Crypto implementation.
883
+ *
884
+ * Node exposes it as `globalThis.crypto` from v19 (and behind
885
+ * `node:crypto`'s `webcrypto` export from v15), browsers and workers expose it
886
+ * on `window`/`self`. Secure contexts are required in browsers, hence the
887
+ * explicit error message.
888
+ *
889
+ * @returns
890
+ */
891
+ declare const webCrypto: () => Crypto;
892
+ /**
893
+ * Resolve `crypto.subtle`.
894
+ *
895
+ * @returns
896
+ */
897
+ declare const subtle: () => SubtleCrypto;
898
+ /**
899
+ * Fill a buffer with cryptographically secure random bytes.
900
+ *
901
+ * @param length
902
+ * @returns
903
+ */
904
+ declare const randomBytes: (length: number) => Uint8Array;
905
+ /**
906
+ * SHA digest helper returning bytes instead of an `ArrayBuffer`.
907
+ *
908
+ * @param data
909
+ * @param algorithm
910
+ * @returns
911
+ */
912
+ declare const digest: (data: Uint8Array, algorithm?: "SHA-256" | "SHA-384" | "SHA-512") => Promise<Uint8Array>;
913
+ //#endregion
914
+ export { ChannelOptions, Cipher, CipherOptions, Codec, DeriveOptions, DerivedKey, EncryptionKey, FingerprintOptions, KeyInput, KeyPair, Keys, SealedBox, SecureChannel, SerializedKeyPair, digest, randomBytes, subtle, webCrypto };