@dfx.swiss/wallet 0.0.0-stage → 0.1.1-beta.1

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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -2
  3. package/dist/config/environment.d.ts +40 -0
  4. package/dist/config/environment.d.ts.map +1 -0
  5. package/dist/config/environment.js +40 -0
  6. package/dist/config/environment.js.map +1 -0
  7. package/dist/derivation/bitcoin-keys.d.ts +20 -0
  8. package/dist/derivation/bitcoin-keys.d.ts.map +1 -0
  9. package/dist/derivation/bitcoin-keys.js +50 -0
  10. package/dist/derivation/bitcoin-keys.js.map +1 -0
  11. package/dist/derivation/evm-keys.d.ts +8 -0
  12. package/dist/derivation/evm-keys.d.ts.map +1 -0
  13. package/dist/derivation/evm-keys.js +32 -0
  14. package/dist/derivation/evm-keys.js.map +1 -0
  15. package/dist/derivation/keyring.d.ts +68 -0
  16. package/dist/derivation/keyring.d.ts.map +1 -0
  17. package/dist/derivation/keyring.js +150 -0
  18. package/dist/derivation/keyring.js.map +1 -0
  19. package/dist/derivation/public-data.d.ts +26 -0
  20. package/dist/derivation/public-data.d.ts.map +1 -0
  21. package/dist/derivation/public-data.js +19 -0
  22. package/dist/derivation/public-data.js.map +1 -0
  23. package/dist/derivation/seed-check.d.ts +6 -0
  24. package/dist/derivation/seed-check.d.ts.map +1 -0
  25. package/dist/derivation/seed-check.js +13 -0
  26. package/dist/derivation/seed-check.js.map +1 -0
  27. package/dist/derivation/spark-keys.d.ts +14 -0
  28. package/dist/derivation/spark-keys.d.ts.map +1 -0
  29. package/dist/derivation/spark-keys.js +64 -0
  30. package/dist/derivation/spark-keys.js.map +1 -0
  31. package/dist/derivation/v1.d.ts +32 -0
  32. package/dist/derivation/v1.d.ts.map +1 -0
  33. package/dist/derivation/v1.js +64 -0
  34. package/dist/derivation/v1.js.map +1 -0
  35. package/dist/index.d.ts +46 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +36 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/passkey/ceremonies.d.ts +91 -0
  40. package/dist/passkey/ceremonies.d.ts.map +1 -0
  41. package/dist/passkey/ceremonies.js +250 -0
  42. package/dist/passkey/ceremonies.js.map +1 -0
  43. package/dist/passkey/ceremony-gate.d.ts +25 -0
  44. package/dist/passkey/ceremony-gate.d.ts.map +1 -0
  45. package/dist/passkey/ceremony-gate.js +45 -0
  46. package/dist/passkey/ceremony-gate.js.map +1 -0
  47. package/dist/passkey/credential-cache.d.ts +38 -0
  48. package/dist/passkey/credential-cache.d.ts.map +1 -0
  49. package/dist/passkey/credential-cache.js +91 -0
  50. package/dist/passkey/credential-cache.js.map +1 -0
  51. package/dist/passkey/diagnostics.d.ts +22 -0
  52. package/dist/passkey/diagnostics.d.ts.map +1 -0
  53. package/dist/passkey/diagnostics.js +59 -0
  54. package/dist/passkey/diagnostics.js.map +1 -0
  55. package/dist/passkey/errors.d.ts +49 -0
  56. package/dist/passkey/errors.d.ts.map +1 -0
  57. package/dist/passkey/errors.js +87 -0
  58. package/dist/passkey/errors.js.map +1 -0
  59. package/dist/passkey/platform.d.ts +77 -0
  60. package/dist/passkey/platform.d.ts.map +1 -0
  61. package/dist/passkey/platform.js +163 -0
  62. package/dist/passkey/platform.js.map +1 -0
  63. package/dist/passkey/webauthn-json.d.ts +35 -0
  64. package/dist/passkey/webauthn-json.d.ts.map +1 -0
  65. package/dist/passkey/webauthn-json.js +79 -0
  66. package/dist/passkey/webauthn-json.js.map +1 -0
  67. package/dist/providers/blocklist.d.ts +22 -0
  68. package/dist/providers/blocklist.d.ts.map +1 -0
  69. package/dist/providers/blocklist.js +51 -0
  70. package/dist/providers/blocklist.js.map +1 -0
  71. package/dist/providers/chain-registry.d.ts +26 -0
  72. package/dist/providers/chain-registry.d.ts.map +1 -0
  73. package/dist/providers/chain-registry.js +59 -0
  74. package/dist/providers/chain-registry.js.map +1 -0
  75. package/dist/providers/dfx-data.d.ts +25 -0
  76. package/dist/providers/dfx-data.d.ts.map +1 -0
  77. package/dist/providers/dfx-data.js +44 -0
  78. package/dist/providers/dfx-data.js.map +1 -0
  79. package/dist/providers/price.d.ts +34 -0
  80. package/dist/providers/price.d.ts.map +1 -0
  81. package/dist/providers/price.js +46 -0
  82. package/dist/providers/price.js.map +1 -0
  83. package/dist/providers/snapshot.d.ts +28 -0
  84. package/dist/providers/snapshot.d.ts.map +1 -0
  85. package/dist/providers/snapshot.js +2 -0
  86. package/dist/providers/snapshot.js.map +1 -0
  87. package/dist/session/idle-lock.d.ts +31 -0
  88. package/dist/session/idle-lock.d.ts.map +1 -0
  89. package/dist/session/idle-lock.js +73 -0
  90. package/dist/session/idle-lock.js.map +1 -0
  91. package/dist/session/passkey-auth.d.ts +95 -0
  92. package/dist/session/passkey-auth.d.ts.map +1 -0
  93. package/dist/session/passkey-auth.js +356 -0
  94. package/dist/session/passkey-auth.js.map +1 -0
  95. package/dist/session/tab-lock.d.ts +30 -0
  96. package/dist/session/tab-lock.d.ts.map +1 -0
  97. package/dist/session/tab-lock.js +79 -0
  98. package/dist/session/tab-lock.js.map +1 -0
  99. package/dist/signing/auto-signer.d.ts +26 -0
  100. package/dist/signing/auto-signer.d.ts.map +1 -0
  101. package/dist/signing/auto-signer.js +76 -0
  102. package/dist/signing/auto-signer.js.map +1 -0
  103. package/dist/signing/bitcoin-message.d.ts +11 -0
  104. package/dist/signing/bitcoin-message.d.ts.map +1 -0
  105. package/dist/signing/bitcoin-message.js +62 -0
  106. package/dist/signing/bitcoin-message.js.map +1 -0
  107. package/dist/signing/login-messages.d.ts +15 -0
  108. package/dist/signing/login-messages.d.ts.map +1 -0
  109. package/dist/signing/login-messages.js +16 -0
  110. package/dist/signing/login-messages.js.map +1 -0
  111. package/dist/signing/login-text-guard.d.ts +3 -0
  112. package/dist/signing/login-text-guard.d.ts.map +1 -0
  113. package/dist/signing/login-text-guard.js +13 -0
  114. package/dist/signing/login-text-guard.js.map +1 -0
  115. package/dist/signing/spark-login.d.ts +17 -0
  116. package/dist/signing/spark-login.d.ts.map +1 -0
  117. package/dist/signing/spark-login.js +51 -0
  118. package/dist/signing/spark-login.js.map +1 -0
  119. package/dist/util/bytes.d.ts +11 -0
  120. package/dist/util/bytes.d.ts.map +1 -0
  121. package/dist/util/bytes.js +26 -0
  122. package/dist/util/bytes.js.map +1 -0
  123. package/dist/util/emitter.d.ts +17 -0
  124. package/dist/util/emitter.d.ts.map +1 -0
  125. package/dist/util/emitter.js +53 -0
  126. package/dist/util/emitter.js.map +1 -0
  127. package/dist/util/errors.d.ts +12 -0
  128. package/dist/util/errors.d.ts.map +1 -0
  129. package/dist/util/errors.js +15 -0
  130. package/dist/util/errors.js.map +1 -0
  131. package/dist/util/jwt.d.ts +6 -0
  132. package/dist/util/jwt.d.ts.map +1 -0
  133. package/dist/util/jwt.js +23 -0
  134. package/dist/util/jwt.js.map +1 -0
  135. package/dist/util/kv-store.d.ts +51 -0
  136. package/dist/util/kv-store.d.ts.map +1 -0
  137. package/dist/util/kv-store.js +133 -0
  138. package/dist/util/kv-store.js.map +1 -0
  139. package/dist/util/random.d.ts +3 -0
  140. package/dist/util/random.d.ts.map +1 -0
  141. package/dist/util/random.js +18 -0
  142. package/dist/util/random.js.map +1 -0
  143. package/package.json +43 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spark-keys.d.ts","sourceRoot":"","sources":["../../src/derivation/spark-keys.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAG,kBAA2B,CAAC;AAW/D,yGAAyG;AACzG,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,UAAU,GAAG,MAAM,CAGhE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CA0B9D"}
@@ -0,0 +1,64 @@
1
+ import { secp256k1 } from '@noble/curves/secp256k1';
2
+ import { bech32m } from '@scure/base';
3
+ import { concatBytes, isBytes } from '../util/bytes';
4
+ import { WalletError } from '../util/errors';
5
+ /**
6
+ * Spark identity key: Spark SDK default derivation on the main mnemonic, mainnet account 1 (Breez SDK Spark
7
+ * `defaultExternalSigners(mnemonic, null, 'mainnet', 1)`). Pinned by the known-answer vectors.
8
+ */
9
+ export const SPARK_IDENTITY_PATH = "m/8797555'/1'/0'";
10
+ const SPARK_HRP = 'spark';
11
+ // Protobuf `SparkAddress { bytes identity_public_key = 1; }` with a 33-byte key: tag 0x0a, length 0x21.
12
+ const PAYLOAD_PREFIX = new Uint8Array([0x0a, 0x21]);
13
+ const PAYLOAD_BYTES = PAYLOAD_PREFIX.length + 33;
14
+ // Longer than any bech32m string we accept; the payload length check below is the real limit.
15
+ const BECH32_LIMIT = 1024;
16
+ /** Plain mainnet Spark address: bech32m(`spark`, 0x0a 0x21 ‖ identity public key), no invoice fields. */
17
+ export function encodeSparkAddress(publicKey) {
18
+ checkIdentityPublicKey(publicKey);
19
+ return bech32m.encode(SPARK_HRP, bech32m.toWords(concatBytes(PAYLOAD_PREFIX, publicKey)), BECH32_LIMIT);
20
+ }
21
+ /**
22
+ * Strict decoder for plain mainnet Spark addresses: lowercase, HRP `spark`, payload exactly 0x0a 0x21 followed by
23
+ * a valid compressed secp256k1 point. Invoice addresses, other networks and raw-key payloads are refused.
24
+ * Returns the 33-byte identity public key.
25
+ */
26
+ export function decodeSparkAddress(address) {
27
+ if (typeof address !== 'string' || address !== address.toLowerCase()) {
28
+ throw invalid('Spark address must be lowercase');
29
+ }
30
+ let decoded;
31
+ try {
32
+ decoded = bech32m.decode(address, BECH32_LIMIT);
33
+ }
34
+ catch (error) {
35
+ throw invalid('Spark address is not valid bech32m', error);
36
+ }
37
+ if (decoded.prefix !== SPARK_HRP)
38
+ throw invalid(`Spark address prefix must be "${SPARK_HRP}"`);
39
+ const payload = bech32m.fromWordsUnsafe(decoded.words);
40
+ if (!payload ||
41
+ payload.length !== PAYLOAD_BYTES ||
42
+ payload[0] !== PAYLOAD_PREFIX[0] ||
43
+ payload[1] !== PAYLOAD_PREFIX[1]) {
44
+ throw invalid('Spark address payload must be a plain identity public key');
45
+ }
46
+ const publicKey = payload.slice(PAYLOAD_PREFIX.length);
47
+ checkIdentityPublicKey(publicKey);
48
+ return publicKey;
49
+ }
50
+ function checkIdentityPublicKey(publicKey) {
51
+ if (!isBytes(publicKey) || publicKey.length !== 33 || (publicKey[0] !== 0x02 && publicKey[0] !== 0x03)) {
52
+ throw invalid('Spark identity public key must be a 33-byte compressed key');
53
+ }
54
+ try {
55
+ secp256k1.ProjectivePoint.fromHex(publicKey).assertValidity();
56
+ }
57
+ catch (error) {
58
+ throw invalid('Spark identity public key is not a secp256k1 point', error);
59
+ }
60
+ }
61
+ function invalid(message, cause) {
62
+ return new WalletError('invalid-spark-address', message, cause === undefined ? undefined : { cause });
63
+ }
64
+ //# sourceMappingURL=spark-keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spark-keys.js","sourceRoot":"","sources":["../../src/derivation/spark-keys.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Derivation version v1: PRF output → BIP-39 mnemonic. These values are frozen forever; changing any of them
3
+ * changes every wallet. They are pinned by the known-answer vectors in `__tests__/fixtures/derivation-v1.json`.
4
+ */
5
+ export declare const DERIVATION_V1: Readonly<{
6
+ readonly version: "v1";
7
+ /** PRF input (`prf.eval.first`) = SHA-256 of this UTF-8 label. */
8
+ readonly prfInputLabel: "dfx.swiss/passkey-seed/prf/v1";
9
+ /** HKDF-SHA-256 salt (UTF-8). */
10
+ readonly hkdfSalt: "dfx.swiss/passkey-seed/hkdf/v1";
11
+ /** HKDF-SHA-256 info (UTF-8). */
12
+ readonly hkdfInfo: "bip39-entropy-128";
13
+ /** BIP-39 entropy length: 16 bytes = 12 English words. */
14
+ readonly entropyBytes: 16;
15
+ /** BIP-39 passphrase. */
16
+ readonly passphrase: "";
17
+ }>;
18
+ /** Length of a WebAuthn PRF output. */
19
+ export declare const PRF_OUTPUT_BYTES = 32;
20
+ /** The PRF input of every v1 ceremony: SHA-256(UTF-8 label), 32 bytes. */
21
+ export declare function prfInputV1(): Uint8Array;
22
+ /** HKDF-SHA-256 over the PRF output → 16 bytes of BIP-39 entropy. */
23
+ export declare function entropyFromPrfV1(prfOutput: Uint8Array): Uint8Array;
24
+ /** BIP-39 English mnemonic of the given entropy. */
25
+ export declare function mnemonicFromEntropy(entropy: Uint8Array): string;
26
+ /** PRF output → 12-word English mnemonic (derivation v1). */
27
+ export declare function mnemonicFromPrfV1(prfOutput: Uint8Array): string;
28
+ /** Collapses whitespace and checks that the mnemonic is a valid 12-word English BIP-39 phrase. */
29
+ export declare function normalizeMnemonicV1(mnemonic: string): string;
30
+ /** BIP-39 seed (64 bytes). v1 always uses the empty passphrase; other values exist for the BIP-39 test vectors. */
31
+ export declare function seedFromMnemonic(mnemonic: string, passphrase?: string): Uint8Array;
32
+ //# sourceMappingURL=v1.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1.d.ts","sourceRoot":"","sources":["../../src/derivation/v1.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,eAAO,MAAM,aAAa;;IAExB,kEAAkE;;IAElE,iCAAiC;;IAEjC,iCAAiC;;IAEjC,0DAA0D;;IAE1D,yBAAyB;;EAEhB,CAAC;AAEZ,uCAAuC;AACvC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAInC,0EAA0E;AAC1E,wBAAgB,UAAU,IAAI,UAAU,CAEvC;AAED,qEAAqE;AACrE,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,UAAU,GAAG,UAAU,CAWlE;AAED,oDAAoD;AACpD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAE/D;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,UAAU,GAAG,MAAM,CAO/D;AAED,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM5D;AAED,mHAAmH;AACnH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAE,MAAiC,GAAG,UAAU,CAE5G"}
@@ -0,0 +1,64 @@
1
+ import { hkdf } from '@noble/hashes/hkdf';
2
+ import { sha256 } from '@noble/hashes/sha2';
3
+ import { entropyToMnemonic, mnemonicToSeedSync, validateMnemonic } from '@scure/bip39';
4
+ import { wordlist } from '@scure/bip39/wordlists/english';
5
+ import { isBytes, utf8ToBytes, wipeBytes } from '../util/bytes';
6
+ import { WalletError } from '../util/errors';
7
+ /**
8
+ * Derivation version v1: PRF output → BIP-39 mnemonic. These values are frozen forever; changing any of them
9
+ * changes every wallet. They are pinned by the known-answer vectors in `__tests__/fixtures/derivation-v1.json`.
10
+ */
11
+ export const DERIVATION_V1 = Object.freeze({
12
+ version: 'v1',
13
+ /** PRF input (`prf.eval.first`) = SHA-256 of this UTF-8 label. */
14
+ prfInputLabel: 'dfx.swiss/passkey-seed/prf/v1',
15
+ /** HKDF-SHA-256 salt (UTF-8). */
16
+ hkdfSalt: 'dfx.swiss/passkey-seed/hkdf/v1',
17
+ /** HKDF-SHA-256 info (UTF-8). */
18
+ hkdfInfo: 'bip39-entropy-128',
19
+ /** BIP-39 entropy length: 16 bytes = 12 English words. */
20
+ entropyBytes: 16,
21
+ /** BIP-39 passphrase. */
22
+ passphrase: '',
23
+ });
24
+ /** Length of a WebAuthn PRF output. */
25
+ export const PRF_OUTPUT_BYTES = 32;
26
+ const MNEMONIC_WORDS = 12;
27
+ /** The PRF input of every v1 ceremony: SHA-256(UTF-8 label), 32 bytes. */
28
+ export function prfInputV1() {
29
+ return sha256(utf8ToBytes(DERIVATION_V1.prfInputLabel));
30
+ }
31
+ /** HKDF-SHA-256 over the PRF output → 16 bytes of BIP-39 entropy. */
32
+ export function entropyFromPrfV1(prfOutput) {
33
+ if (!isBytes(prfOutput) || prfOutput.length !== PRF_OUTPUT_BYTES) {
34
+ throw new WalletError('invalid-prf-output', `PRF output must be ${PRF_OUTPUT_BYTES} bytes`);
35
+ }
36
+ return hkdf(sha256, prfOutput, utf8ToBytes(DERIVATION_V1.hkdfSalt), utf8ToBytes(DERIVATION_V1.hkdfInfo), DERIVATION_V1.entropyBytes);
37
+ }
38
+ /** BIP-39 English mnemonic of the given entropy. */
39
+ export function mnemonicFromEntropy(entropy) {
40
+ return entropyToMnemonic(entropy, wordlist);
41
+ }
42
+ /** PRF output → 12-word English mnemonic (derivation v1). */
43
+ export function mnemonicFromPrfV1(prfOutput) {
44
+ const entropy = entropyFromPrfV1(prfOutput);
45
+ try {
46
+ return mnemonicFromEntropy(entropy);
47
+ }
48
+ finally {
49
+ wipeBytes(entropy);
50
+ }
51
+ }
52
+ /** Collapses whitespace and checks that the mnemonic is a valid 12-word English BIP-39 phrase. */
53
+ export function normalizeMnemonicV1(mnemonic) {
54
+ const normalized = typeof mnemonic === 'string' ? mnemonic.trim().split(/\s+/).join(' ') : '';
55
+ if (normalized.split(' ').length !== MNEMONIC_WORDS || !validateMnemonic(normalized, wordlist)) {
56
+ throw new WalletError('invalid-mnemonic', `Expected a valid ${MNEMONIC_WORDS}-word English BIP-39 mnemonic`);
57
+ }
58
+ return normalized;
59
+ }
60
+ /** BIP-39 seed (64 bytes). v1 always uses the empty passphrase; other values exist for the BIP-39 test vectors. */
61
+ export function seedFromMnemonic(mnemonic, passphrase = DERIVATION_V1.passphrase) {
62
+ return mnemonicToSeedSync(mnemonic, passphrase);
63
+ }
64
+ //# sourceMappingURL=v1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1.js","sourceRoot":"","sources":["../../src/derivation/v1.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC;IACzC,OAAO,EAAE,IAAI;IACb,kEAAkE;IAClE,aAAa,EAAE,+BAA+B;IAC9C,iCAAiC;IACjC,QAAQ,EAAE,gCAAgC;IAC1C,iCAAiC;IACjC,QAAQ,EAAE,mBAAmB;IAC7B,0DAA0D;IAC1D,YAAY,EAAE,EAAE;IAChB,yBAAyB;IACzB,UAAU,EAAE,EAAE;CACN,CAAC,CAAC;AAEZ,uCAAuC;AACvC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAEnC,MAAM,cAAc,GAAG,EAAE,CAAC;AAE1B,0EAA0E;AAC1E,MAAM,UAAU,UAAU;IACxB,OAAO,MAAM,CAAC,WAAW,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,gBAAgB,CAAC,SAAqB;IACpD,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,MAAM,KAAK,gBAAgB,EAAE,CAAC;QACjE,MAAM,IAAI,WAAW,CAAC,oBAAoB,EAAE,sBAAsB,gBAAgB,QAAQ,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,IAAI,CACT,MAAM,EACN,SAAS,EACT,WAAW,CAAC,aAAa,CAAC,QAAQ,CAAC,EACnC,WAAW,CAAC,aAAa,CAAC,QAAQ,CAAC,EACnC,aAAa,CAAC,YAAY,CAC3B,CAAC;AACJ,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,mBAAmB,CAAC,OAAmB;IACrD,OAAO,iBAAiB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC9C,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,iBAAiB,CAAC,SAAqB;IACrD,MAAM,OAAO,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,CAAC;QACH,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,OAAO,CAAC,CAAC;IACrB,CAAC;AACH,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,MAAM,UAAU,GAAG,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9F,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,cAAc,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC/F,MAAM,IAAI,WAAW,CAAC,kBAAkB,EAAE,oBAAoB,cAAc,+BAA+B,CAAC,CAAC;IAC/G,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,mHAAmH;AACnH,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,aAAqB,aAAa,CAAC,UAAU;IAC9F,OAAO,kBAAkB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,46 @@
1
+ export { WALLET_ENVS, isWalletEnv, RP_IDS, SIGN_MESSAGE_PREFIX, WALLET_NETWORKS } from './config/environment';
2
+ export type { WalletEnv, BitcoinNetwork, SparkNetwork, EvmNetworkKind, WalletNetworks } from './config/environment';
3
+ export { WalletError, isWalletError } from './util/errors';
4
+ export { WALLET_DB_NAME, MemoryKeyValueStore, IndexedDbKeyValueStore, createDefaultKeyValueStore, } from './util/kv-store';
5
+ export type { KeyValueStore, IndexedDbKeyValueStoreOptions } from './util/kv-store';
6
+ export { DERIVATION_V1, PRF_OUTPUT_BYTES, prfInputV1, mnemonicFromPrfV1, normalizeMnemonicV1 } from './derivation/v1';
7
+ export { KeyRing } from './derivation/keyring';
8
+ export type { KeyRingOptions, BitcoinAccountInfo, BitcoinAddressInfo, SparkIdentityInfo } from './derivation/keyring';
9
+ export type { BitcoinChain } from './derivation/bitcoin-keys';
10
+ export { seedPublicData } from './derivation/public-data';
11
+ export type { SeedPublicData } from './derivation/public-data';
12
+ export { encodeSparkAddress, decodeSparkAddress } from './derivation/spark-keys';
13
+ export { seedCheck } from './derivation/seed-check';
14
+ export type { EvmAddress } from './derivation/evm-keys';
15
+ export { LOGIN_TEMPLATES, buildLoginMessage } from './signing/login-messages';
16
+ export type { LoginTemplate } from './signing/login-messages';
17
+ export { LoginAutoSigner, LoginMessageMismatchError, eip191MessageHash } from './signing/auto-signer';
18
+ export { SPARK_LOGIN_TAG, sparkLoginDigestV1, verifySparkLogin } from './signing/spark-login';
19
+ export { bitcoinMessageMagicHash, verifyBitcoinMessage } from './signing/bitcoin-message';
20
+ export { isDfxLoginText } from './signing/login-text-guard';
21
+ export { checkPasskeyPlatform, isInAppBrowser, openInSystemBrowser, iosInstalledVersion, androidInstalledVersion, } from './passkey/platform';
22
+ export type { PlatformReport, PlatformHost } from './passkey/platform';
23
+ export { PasskeyCeremonies } from './passkey/ceremonies';
24
+ export type { CeremonyCredentials, CeremonyWindow, PendingSeedPasskey, CreatedSeedPasskey, SeedAssertion, } from './passkey/ceremonies';
25
+ export { PasskeyError, EmbeddedContextError, WrongOriginError, PlatformUnsupportedError, InAppBrowserError, CrossDeviceRegistrationError, PrfMismatchError, PrfUnavailableError, CeremonySupersededError, CeremonyCancelledError, UserActivationMissingError, classifyWebAuthnError, } from './passkey/errors';
26
+ export type { WebAuthnErrorKind } from './passkey/errors';
27
+ export { CredentialCache } from './passkey/credential-cache';
28
+ export type { CachedPasskeyCredential, CredentialStorage } from './passkey/credential-cache';
29
+ export { passkeyDiagnostic } from './passkey/diagnostics';
30
+ export type { PasskeyDiagnostic, PasskeyDiagnosticInput } from './passkey/diagnostics';
31
+ export { credentialToJSON } from './passkey/webauthn-json';
32
+ export type { WebAuthnJson } from './passkey/webauthn-json';
33
+ export { ChainRegistry } from './providers/chain-registry';
34
+ export { DfxData, ProviderUnavailableError } from './providers/dfx-data';
35
+ export type { DfxDataOptions } from './providers/dfx-data';
36
+ export { PricePolicy, PRICE_STALE_MS } from './providers/price';
37
+ export type { PricePolicyOptions } from './providers/price';
38
+ export { Blocklists } from './providers/blocklist';
39
+ export type { WalletSnapshot, SnapshotAsset, SnapshotBlocklists } from './providers/snapshot';
40
+ export { PasskeyAuth, PasskeyRefusedError } from './session/passkey-auth';
41
+ export type { WalletState, AuthResult, PasskeyRefusal, RegisterExtras, PasskeyAuthDeps } from './session/passkey-auth';
42
+ export { IdleLock } from './session/idle-lock';
43
+ export type { IdleDelay, IdleLockOptions } from './session/idle-lock';
44
+ export { TabLock } from './session/tab-lock';
45
+ export type { LockManagerLike } from './session/tab-lock';
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC9G,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGpH,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG3D,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,aAAa,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AAGpF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,UAAU,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtH,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACtH,YAAY,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,YAAY,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC9E,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACtG,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9F,OAAO,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG5D,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,YAAY,EACV,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,GACd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,EAChB,wBAAwB,EACxB,iBAAiB,EACjB,4BAA4B,EAC5B,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,YAAY,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC7F,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAG5D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACzE,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAChE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG9F,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC1E,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACvH,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,36 @@
1
+ // Public API. This package does not depend on the Breez or WalletConnect SDKs.
2
+ // Environment
3
+ export { WALLET_ENVS, isWalletEnv, RP_IDS, SIGN_MESSAGE_PREFIX, WALLET_NETWORKS } from './config/environment';
4
+ // Errors
5
+ export { WalletError, isWalletError } from './util/errors';
6
+ // Storage
7
+ export { WALLET_DB_NAME, MemoryKeyValueStore, IndexedDbKeyValueStore, createDefaultKeyValueStore, } from './util/kv-store';
8
+ // Derivation v1 (§7.1, §7.2)
9
+ export { DERIVATION_V1, PRF_OUTPUT_BYTES, prfInputV1, mnemonicFromPrfV1, normalizeMnemonicV1 } from './derivation/v1';
10
+ export { KeyRing } from './derivation/keyring';
11
+ export { seedPublicData } from './derivation/public-data';
12
+ export { encodeSparkAddress, decodeSparkAddress } from './derivation/spark-keys';
13
+ export { seedCheck } from './derivation/seed-check';
14
+ // Signing (§8.5, §9.3, D31)
15
+ export { LOGIN_TEMPLATES, buildLoginMessage } from './signing/login-messages';
16
+ export { LoginAutoSigner, LoginMessageMismatchError, eip191MessageHash } from './signing/auto-signer';
17
+ export { SPARK_LOGIN_TAG, sparkLoginDigestV1, verifySparkLogin } from './signing/spark-login';
18
+ export { bitcoinMessageMagicHash, verifyBitcoinMessage } from './signing/bitcoin-message';
19
+ export { isDfxLoginText } from './signing/login-text-guard';
20
+ // Passkey (§6, §8.3, §8.4, §11.4)
21
+ export { checkPasskeyPlatform, isInAppBrowser, openInSystemBrowser, iosInstalledVersion, androidInstalledVersion, } from './passkey/platform';
22
+ export { PasskeyCeremonies } from './passkey/ceremonies';
23
+ export { PasskeyError, EmbeddedContextError, WrongOriginError, PlatformUnsupportedError, InAppBrowserError, CrossDeviceRegistrationError, PrfMismatchError, PrfUnavailableError, CeremonySupersededError, CeremonyCancelledError, UserActivationMissingError, classifyWebAuthnError, } from './passkey/errors';
24
+ export { CredentialCache } from './passkey/credential-cache';
25
+ export { passkeyDiagnostic } from './passkey/diagnostics';
26
+ export { credentialToJSON } from './passkey/webauthn-json';
27
+ // Providers (D8, D23, D32, §9.2)
28
+ export { ChainRegistry } from './providers/chain-registry';
29
+ export { DfxData, ProviderUnavailableError } from './providers/dfx-data';
30
+ export { PricePolicy, PRICE_STALE_MS } from './providers/price';
31
+ export { Blocklists } from './providers/blocklist';
32
+ // Session and flows (§8, D5, X24)
33
+ export { PasskeyAuth, PasskeyRefusedError } from './session/passkey-auth';
34
+ export { IdleLock } from './session/idle-lock';
35
+ export { TabLock } from './session/tab-lock';
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAE/E,cAAc;AACd,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG9G,SAAS;AACT,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE3D,UAAU;AACV,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,iBAAiB,CAAC;AAGzB,6BAA6B;AAC7B,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,UAAU,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtH,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAG/C,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAGpD,4BAA4B;AAC5B,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE9E,OAAO,EAAE,eAAe,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACtG,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9F,OAAO,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,kCAAkC;AAClC,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAQzD,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,EAChB,wBAAwB,EACxB,iBAAiB,EACjB,4BAA4B,EAC5B,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAG3D,iCAAiC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAEzE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGnD,kCAAkC;AAClC,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE1E,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,91 @@
1
+ import type { WalletEnv } from '../config/environment';
2
+ import { WebAuthnJson } from './webauthn-json';
3
+ /** Credentials surface the ceremonies drive (the browser's, or a fake in tests). */
4
+ export interface CeremonyCredentials {
5
+ create(options: CredentialCreationOptions): Promise<PublicKeyCredential | null>;
6
+ get(options: CredentialRequestOptions): Promise<PublicKeyCredential | null>;
7
+ }
8
+ /** Window surface the ceremonies inspect. */
9
+ export interface CeremonyWindow {
10
+ location: {
11
+ hostname: string;
12
+ };
13
+ navigator: {
14
+ userAgent: string;
15
+ userActivation?: {
16
+ isActive: boolean;
17
+ };
18
+ platform?: string;
19
+ maxTouchPoints?: number;
20
+ };
21
+ PublicKeyCredential?: {
22
+ signalUnknownCredential?: (input: {
23
+ credentialId: string;
24
+ rpId: string;
25
+ }) => Promise<void>;
26
+ };
27
+ }
28
+ /** A created passkey that still needs its confirming second tap (§6.3). */
29
+ export interface PendingSeedPasskey {
30
+ readonly credentialId: Uint8Array;
31
+ /** Tap 2: the confirming `get()` that proves the credential and yields the PRF output. */
32
+ confirm(): Promise<CreatedSeedPasskey>;
33
+ /** Drops the aborted registration and tells the platform the credential is unknown. */
34
+ abort(): Promise<void>;
35
+ }
36
+ export interface CreatedSeedPasskey {
37
+ credentialId: Uint8Array;
38
+ userHandle: Uint8Array;
39
+ /** The `create()` result serialized for the API (PRF results stripped). */
40
+ attestation: WebAuthnJson;
41
+ /** The confirming `get()` result serialized for the API (PRF results stripped). */
42
+ assertion: WebAuthnJson;
43
+ prfOutput: Uint8Array;
44
+ attachment: AuthenticatorAttachment | null;
45
+ transports: string[];
46
+ }
47
+ export interface SeedAssertion {
48
+ credentialId: Uint8Array;
49
+ /** The `get()` result serialized for the API (PRF results stripped). */
50
+ assertion: WebAuthnJson;
51
+ prfOutput?: Uint8Array;
52
+ backupEligible: boolean;
53
+ backedUp: boolean;
54
+ }
55
+ /**
56
+ * The wallet's passkey ceremonies. Every option is built locally — only the challenge comes from the
57
+ * server — and exactly one ceremony runs at a time; a newer call or `cancel()` supersedes the running one.
58
+ * No AbortSignal is passed on iOS/iPadOS WebKit (it breaks ceremonies there); superseding still applies.
59
+ */
60
+ export declare class PasskeyCeremonies {
61
+ private readonly env;
62
+ private readonly credentials;
63
+ private readonly win;
64
+ private readonly gate;
65
+ constructor(env: WalletEnv, credentials?: CeremonyCredentials, win?: CeremonyWindow);
66
+ /** Tap 1 of the registration: creates the credential with the PRF extension and returns the pending passkey. */
67
+ createCredential(input: {
68
+ challenge: Uint8Array;
69
+ excludeCredentialIds?: Uint8Array[];
70
+ }): Promise<PendingSeedPasskey>;
71
+ /** A login assertion with PRF output when the authenticator provides it.
72
+ * `continueCeremony` is only for a retry inside a ceremony that already ran create(). */
73
+ assert(input: {
74
+ challenge?: Uint8Array;
75
+ credentialId?: Uint8Array;
76
+ continueCeremony?: boolean;
77
+ }): Promise<SeedAssertion>;
78
+ /** Best-effort `signalUnknownCredential` for aborted registrations (§4.2); never throws. */
79
+ signalUnknownCredential(credentialId: Uint8Array): Promise<void>;
80
+ /** Supersedes the running ceremony without starting a new one. */
81
+ cancel(): void;
82
+ /** Tap 2 helper: the confirming `get()` restricted to the new credential; PRF output is mandatory.
83
+ * The gesture that opened tap 1 does not survive that dialog, so this prompt is not gated on it. */
84
+ private derivePrf;
85
+ /** Runs one gated WebAuthn call: user gesture, superseding, and the platform-appropriate signal. */
86
+ private call;
87
+ private withSignal;
88
+ private requireTopLevelOrigin;
89
+ private requireActivation;
90
+ }
91
+ //# sourceMappingURL=ceremonies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ceremonies.d.ts","sourceRoot":"","sources":["../../src/passkey/ceremonies.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,250 @@
1
+ import { RP_IDS } from '../config/environment';
2
+ import { prfInputV1 } from '../derivation/v1';
3
+ import { equalBytes } from '../util/bytes';
4
+ import { randomBytes } from '../util/random';
5
+ import { CeremonyGate } from './ceremony-gate';
6
+ import { CeremonySupersededError, CrossDeviceRegistrationError, EmbeddedContextError, PasskeyError, PrfMismatchError, PrfUnavailableError, UserActivationMissingError, WrongOriginError, } from './errors';
7
+ import { isIpadDesktopMode } from './platform';
8
+ import { bytesToBase64Url, credentialToJSON } from './webauthn-json';
9
+ const TIMEOUT_MS = 300000;
10
+ const CRED_PARAMS = [
11
+ { type: 'public-key', alg: -7 },
12
+ { type: 'public-key', alg: -257 },
13
+ ];
14
+ function copy(input) {
15
+ if (!input)
16
+ return new Uint8Array(0);
17
+ const out = new Uint8Array(input.byteLength);
18
+ out.set(input instanceof ArrayBuffer
19
+ ? new Uint8Array(input)
20
+ : new Uint8Array(input.buffer, input.byteOffset, input.byteLength));
21
+ return out;
22
+ }
23
+ /** Copies caller bytes into a plain ArrayBuffer-backed view, the WebAuthn `BufferSource` shape. */
24
+ function source(bytes) {
25
+ const out = new Uint8Array(bytes.length);
26
+ out.set(bytes);
27
+ return out;
28
+ }
29
+ function readPrfFirst(credential) {
30
+ const results = credential.getClientExtensionResults();
31
+ const first = results.prf?.results?.first;
32
+ if (first === undefined || first === null)
33
+ return undefined;
34
+ const bytes = copy(first);
35
+ return bytes.length > 0 ? bytes : undefined;
36
+ }
37
+ function backupFlags(authenticatorData) {
38
+ const bytes = authenticatorData ? new Uint8Array(authenticatorData) : new Uint8Array();
39
+ const flags = bytes.length > 32 ? bytes[32] : 0;
40
+ return { backupEligible: (flags & 0x08) !== 0, backedUp: (flags & 0x10) !== 0 };
41
+ }
42
+ function userHandleLabel(now = new Date()) {
43
+ const pad = (value) => String(value).padStart(2, '0');
44
+ return `DFX Wallet ${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}`;
45
+ }
46
+ /** Browser `navigator.credentials` when both `create` and `get` exist; otherwise a dummy that resolves null. */
47
+ function defaultCredentials() {
48
+ const credentials = globalThis.navigator
49
+ ?.credentials;
50
+ if (typeof credentials?.create === 'function' && typeof credentials?.get === 'function') {
51
+ return credentials;
52
+ }
53
+ return { create: async () => null, get: async () => null };
54
+ }
55
+ /**
56
+ * The wallet's passkey ceremonies. Every option is built locally — only the challenge comes from the
57
+ * server — and exactly one ceremony runs at a time; a newer call or `cancel()` supersedes the running one.
58
+ * No AbortSignal is passed on iOS/iPadOS WebKit (it breaks ceremonies there); superseding still applies.
59
+ */
60
+ export class PasskeyCeremonies {
61
+ constructor(env, credentials = defaultCredentials(), win = globalThis ?? {
62
+ location: { hostname: '' },
63
+ navigator: { userAgent: '' },
64
+ }) {
65
+ this.env = env;
66
+ this.credentials = credentials;
67
+ this.win = win;
68
+ this.gate = new CeremonyGate();
69
+ }
70
+ /** Tap 1 of the registration: creates the credential with the PRF extension and returns the pending passkey. */
71
+ async createCredential(input) {
72
+ this.requireTopLevelOrigin();
73
+ this.requireActivation();
74
+ const label = userHandleLabel();
75
+ const userId = source(randomBytes(32));
76
+ const options = {
77
+ publicKey: {
78
+ challenge: source(input.challenge),
79
+ rp: { id: RP_IDS[this.env], name: 'DFX' },
80
+ user: { id: userId, name: label, displayName: label },
81
+ pubKeyCredParams: CRED_PARAMS,
82
+ authenticatorSelection: { residentKey: 'required', requireResidentKey: true, userVerification: 'required' },
83
+ attestation: 'none',
84
+ timeout: TIMEOUT_MS,
85
+ excludeCredentials: (input.excludeCredentialIds ?? []).map((id) => ({
86
+ type: 'public-key',
87
+ id: source(id),
88
+ })),
89
+ extensions: { prf: { eval: { first: prfInputV1() } } },
90
+ },
91
+ };
92
+ const { result, run } = await this.call((signal) => this.credentials.create(this.withSignal(options, signal)));
93
+ const credential = result;
94
+ try {
95
+ run.checkCurrent();
96
+ }
97
+ catch (error) {
98
+ if (credential)
99
+ await this.signalUnknownCredential(copy(credential.rawId));
100
+ throw error;
101
+ }
102
+ if (!credential)
103
+ throw new PasskeyError('no-credential', 'The authenticator returned no credential');
104
+ const rawAttachment = credential.authenticatorAttachment;
105
+ const attachment = rawAttachment === 'platform' || rawAttachment === 'cross-platform' ? rawAttachment : null;
106
+ const transports = credential.response.getTransports?.() ?? [];
107
+ if (attachment === 'cross-platform' || transports.includes('hybrid')) {
108
+ await this.signalUnknownCredential(copy(credential.rawId));
109
+ throw new CrossDeviceRegistrationError();
110
+ }
111
+ const credentialId = copy(credential.rawId);
112
+ // The attestation response carries no user handle; the server gets the id this wallet generated.
113
+ const userHandle = userId;
114
+ const attestation = credentialToJSON(credential);
115
+ const prfFromCreate = readPrfFirst(credential);
116
+ const derivePrf = (id) => this.derivePrf(id, input.challenge);
117
+ const signalUnknown = (id) => this.signalUnknownCredential(id);
118
+ return {
119
+ credentialId,
120
+ async confirm() {
121
+ const { prfOutput, credential: assertionCredential } = await derivePrf(credentialId);
122
+ if (prfFromCreate && !equalBytes(prfOutput, prfFromCreate)) {
123
+ await signalUnknown(credentialId);
124
+ throw new PrfMismatchError();
125
+ }
126
+ return {
127
+ credentialId,
128
+ userHandle,
129
+ attestation,
130
+ assertion: credentialToJSON(assertionCredential),
131
+ prfOutput,
132
+ attachment,
133
+ transports,
134
+ };
135
+ },
136
+ async abort() {
137
+ await signalUnknown(credentialId);
138
+ },
139
+ };
140
+ }
141
+ /** A login assertion with PRF output when the authenticator provides it.
142
+ * `continueCeremony` is only for a retry inside a ceremony that already ran create(). */
143
+ async assert(input) {
144
+ this.requireTopLevelOrigin();
145
+ if (!input.continueCeremony)
146
+ this.requireActivation();
147
+ const options = {
148
+ publicKey: {
149
+ challenge: source(input.challenge ?? randomBytes(32)),
150
+ rpId: RP_IDS[this.env],
151
+ userVerification: 'required',
152
+ timeout: TIMEOUT_MS,
153
+ ...(input.credentialId
154
+ ? { allowCredentials: [{ type: 'public-key', id: source(input.credentialId) }] }
155
+ : {}),
156
+ extensions: { prf: { eval: { first: prfInputV1() } } },
157
+ },
158
+ };
159
+ const { result, run } = await this.call((signal) => this.credentials.get(this.withSignal(options, signal)));
160
+ run.checkCurrent();
161
+ const credential = result;
162
+ if (!credential)
163
+ throw new PasskeyError('no-credential', 'The authenticator returned no assertion');
164
+ return {
165
+ credentialId: copy(credential.rawId),
166
+ assertion: credentialToJSON(credential),
167
+ prfOutput: readPrfFirst(credential),
168
+ ...backupFlags(credential.response.authenticatorData),
169
+ };
170
+ }
171
+ /** Best-effort `signalUnknownCredential` for aborted registrations (§4.2); never throws. */
172
+ async signalUnknownCredential(credentialId) {
173
+ try {
174
+ await this.win.PublicKeyCredential?.signalUnknownCredential?.({
175
+ credentialId: bytesToBase64Url(credentialId),
176
+ rpId: RP_IDS[this.env],
177
+ });
178
+ }
179
+ catch {
180
+ // signalling is advisory; a rejection must never break the ceremony
181
+ }
182
+ }
183
+ /** Supersedes the running ceremony without starting a new one. */
184
+ cancel() {
185
+ this.gate.cancelActive();
186
+ }
187
+ /** Tap 2 helper: the confirming `get()` restricted to the new credential; PRF output is mandatory.
188
+ * The gesture that opened tap 1 does not survive that dialog, so this prompt is not gated on it. */
189
+ async derivePrf(credentialId, challenge) {
190
+ const options = {
191
+ publicKey: {
192
+ challenge: source(challenge),
193
+ rpId: RP_IDS[this.env],
194
+ allowCredentials: [{ type: 'public-key', id: source(credentialId) }],
195
+ userVerification: 'required',
196
+ timeout: TIMEOUT_MS,
197
+ extensions: { prf: { eval: { first: prfInputV1() } } },
198
+ },
199
+ };
200
+ const { result, run } = await this.call((signal) => this.credentials.get(this.withSignal(options, signal)));
201
+ run.checkCurrent();
202
+ const prfOutput = result ? readPrfFirst(result) : undefined;
203
+ if (!prfOutput || !result) {
204
+ await this.signalUnknownCredential(credentialId);
205
+ throw new PrfUnavailableError();
206
+ }
207
+ return { prfOutput, credential: result };
208
+ }
209
+ /** Runs one gated WebAuthn call: user gesture, superseding, and the platform-appropriate signal. */
210
+ async call(action) {
211
+ const nav = this.win.navigator;
212
+ const allowSignal = !isIpadDesktopMode(nav) && !/iPhone|iPad|iPod/i.test(nav.userAgent);
213
+ const controller = allowSignal ? new AbortController() : undefined;
214
+ const run = this.gate.begin(() => controller?.abort());
215
+ try {
216
+ const result = await action(controller?.signal);
217
+ return { result, run };
218
+ }
219
+ catch (error) {
220
+ if (!run.isCurrent())
221
+ throw new CeremonySupersededError();
222
+ throw error;
223
+ }
224
+ }
225
+ withSignal(options, signal) {
226
+ return signal ? { ...options, signal } : options;
227
+ }
228
+ requireTopLevelOrigin() {
229
+ if (this.win.location.hostname !== RP_IDS[this.env]) {
230
+ throw new WrongOriginError(this.win.location.hostname, RP_IDS[this.env]);
231
+ }
232
+ // `top` is only readable on same-origin pages; a cross-origin access throws and means embedded (NG3).
233
+ try {
234
+ const top = this.win.top;
235
+ if (top !== undefined && top !== this.win)
236
+ throw new EmbeddedContextError();
237
+ }
238
+ catch (error) {
239
+ if (error instanceof EmbeddedContextError)
240
+ throw error;
241
+ throw new EmbeddedContextError();
242
+ }
243
+ }
244
+ requireActivation() {
245
+ const activation = this.win.navigator.userActivation;
246
+ if (activation && !activation.isActive)
247
+ throw new UserActivationMissingError();
248
+ }
249
+ }
250
+ //# sourceMappingURL=ceremonies.js.map