@cef-ai/wallet-identity 1.0.0 → 1.1.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.
package/dist/index.cjs CHANGED
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- var crypto = require('crypto');
4
3
  var bs58 = require('bs58');
5
4
  var blake2b = require('@noble/hashes/blake2b');
6
5
  var sha3 = require('@noble/hashes/sha3');
@@ -36,8 +35,48 @@ var __async = (__this, __arguments, generator) => {
36
35
  step((generator = generator.apply(__this, __arguments)).next());
37
36
  });
38
37
  };
38
+
39
+ // src/types.ts
40
+ function supportsSessionHandoff(identity) {
41
+ return typeof identity.exportSession === "function" && typeof identity.adoptSession === "function";
42
+ }
43
+
44
+ // src/constants.ts
39
45
  var PRF_INPUT_LABEL = "cere-wallet-prf-v1";
40
- var PRF_INPUT_SEED = new Uint8Array(crypto.createHash("sha256").update(PRF_INPUT_LABEL).digest());
46
+ var PRF_INPUT_SEED = new Uint8Array([
47
+ 241,
48
+ 55,
49
+ 252,
50
+ 128,
51
+ 200,
52
+ 121,
53
+ 231,
54
+ 173,
55
+ 149,
56
+ 167,
57
+ 104,
58
+ 105,
59
+ 124,
60
+ 215,
61
+ 6,
62
+ 125,
63
+ 113,
64
+ 218,
65
+ 113,
66
+ 114,
67
+ 238,
68
+ 51,
69
+ 232,
70
+ 185,
71
+ 156,
72
+ 209,
73
+ 26,
74
+ 46,
75
+ 78,
76
+ 6,
77
+ 58,
78
+ 137
79
+ ]);
41
80
  var EVM_HKDF_INFO = "cere-wallet-evm-secp256k1-v1";
42
81
 
43
82
  // src/browser.ts
@@ -150,7 +189,7 @@ var WebAuthnCeremonyAdapter = class {
150
189
  }
151
190
  register(opts) {
152
191
  return __async(this, null, function* () {
153
- var _a, _b, _c, _d;
192
+ var _a, _b, _c, _d, _e, _f;
154
193
  if (!this.credentials) {
155
194
  throw new Error("WebAuthnCeremonyAdapter: navigator.credentials is unavailable");
156
195
  }
@@ -160,8 +199,11 @@ var WebAuthnCeremonyAdapter = class {
160
199
  rp: { id: opts.rpId, name: opts.rpId },
161
200
  user: {
162
201
  id: opts.userHandle,
163
- name: (_a = opts.label) != null ? _a : "scp-wallet",
164
- displayName: (_b = opts.label) != null ? _b : "SCP Wallet"
202
+ // WebAuthn convention: `name` is the account identifier (email), shown
203
+ // by passkey managers; `displayName` is the friendly name. Fall back to
204
+ // the legacy single `label`, then the product defaults.
205
+ name: (_b = (_a = opts.email) != null ? _a : opts.label) != null ? _b : "scp-wallet",
206
+ displayName: (_d = (_c = opts.name) != null ? _c : opts.label) != null ? _d : "SCP Wallet"
165
207
  },
166
208
  pubKeyCredParams: [
167
209
  { alg: -8, type: "public-key" },
@@ -191,7 +233,7 @@ var WebAuthnCeremonyAdapter = class {
191
233
  const response = cred.response;
192
234
  const transports = typeof response.getTransports === "function" ? response.getTransports() : [];
193
235
  const ext = cred.getClientExtensionResults();
194
- const prfFirst = (_d = (_c = ext == null ? void 0 : ext.prf) == null ? void 0 : _c.results) == null ? void 0 : _d.first;
236
+ const prfFirst = (_f = (_e = ext == null ? void 0 : ext.prf) == null ? void 0 : _e.results) == null ? void 0 : _f.first;
195
237
  return {
196
238
  credentialId: cred.id,
197
239
  clientDataJSON: bytesToB64u(response.clientDataJSON),
@@ -465,6 +507,21 @@ var IdentityImpl = class {
465
507
  this.cachedJwt = null;
466
508
  /** Server-returned credential ID (used as the public identity.credentialId). */
467
509
  this.serverCredentialId = null;
510
+ /**
511
+ * Raw derived keys for the CURRENT session, captured at every `vault.set()`
512
+ * call site (register / login / adoptSession). The vault deliberately hides
513
+ * the seeds behind its closure (Spec §3.6 invariant 1); this field is the
514
+ * single, explicit, same-origin-only seam that lets `exportSession()` hand a
515
+ * live session to a sibling wallet-origin surface (the persistent iframe).
516
+ * Cleared on logout / cross-tab logout alongside the vault. Never serialised
517
+ * to storage and never sent to a host page.
518
+ */
519
+ this.sessionKeys = null;
520
+ /**
521
+ * Absolute expiry (epoch ms) for `exportSession()`. Tracks the cached JWT
522
+ * expiry after register/login; set from the adopted session in adoptSession.
523
+ */
524
+ this.sessionExpMs = null;
468
525
  // Public observable state — derived from vault snapshot + serverCredentialId.
469
526
  // Kept in sync via syncPublicState() so React `observer()` wrappers around
470
527
  // components reading isAuthenticated/addresses/credentialId actually re-render
@@ -488,6 +545,8 @@ var IdentityImpl = class {
488
545
  this.vault.clear();
489
546
  this.cachedJwt = null;
490
547
  this.serverCredentialId = null;
548
+ this.sessionKeys = null;
549
+ this.sessionExpMs = null;
491
550
  this.syncPublicState();
492
551
  }
493
552
  });
@@ -531,7 +590,9 @@ var IdentityImpl = class {
531
590
  challenge: b64uDecode(start.challenge),
532
591
  userHandle: b64uDecode(start.userHandle),
533
592
  prfInput: PRF_INPUT_SEED,
534
- label: opts.label
593
+ label: opts.label,
594
+ name: opts.name,
595
+ email: opts.email
535
596
  });
536
597
  if (!cer.prfOutput) {
537
598
  throw new walletApiClient.WalletError("prf-unsupported", "authenticator did not return a PRF output");
@@ -561,6 +622,8 @@ var IdentityImpl = class {
561
622
  });
562
623
  this.serverCredentialId = finish.credentialId;
563
624
  this.cachedJwt = { token: finish.token, expMs: decodeJwtExpMs(finish.token) };
625
+ this.sessionKeys = keys;
626
+ this.sessionExpMs = this.cachedJwt.expMs;
564
627
  this.syncPublicState();
565
628
  });
566
629
  }
@@ -598,6 +661,8 @@ var IdentityImpl = class {
598
661
  });
599
662
  this.serverCredentialId = finish.credentialId;
600
663
  this.cachedJwt = { token: finish.token, expMs: decodeJwtExpMs(finish.token) };
664
+ this.sessionKeys = keys;
665
+ this.sessionExpMs = this.cachedJwt.expMs;
601
666
  this.syncPublicState();
602
667
  });
603
668
  }
@@ -605,6 +670,8 @@ var IdentityImpl = class {
605
670
  this.vault.clear();
606
671
  this.cachedJwt = null;
607
672
  this.serverCredentialId = null;
673
+ this.sessionKeys = null;
674
+ this.sessionExpMs = null;
608
675
  this.syncPublicState();
609
676
  this.crossTabSync.broadcast({ type: "logout" });
610
677
  }
@@ -633,6 +700,59 @@ var IdentityImpl = class {
633
700
  getVault() {
634
701
  return this.vault;
635
702
  }
703
+ /**
704
+ * Export the current session for a same-origin handoff (register popup →
705
+ * iframe). Returns `null` unless the vault is open, we captured the derived
706
+ * keys, and the session has not expired. The raw seeds ARE included — this is
707
+ * the deliberate, narrow seam described on `IdentitySession`; callers must
708
+ * only pass the result across the same-origin wallet BroadcastChannel.
709
+ */
710
+ exportSession() {
711
+ var _a, _b, _c;
712
+ if (!this.vault.isOpen() || !this.sessionKeys) return null;
713
+ const snap = this.vault.snapshot();
714
+ if (!snap) return null;
715
+ const expMs = (_c = (_b = this.sessionExpMs) != null ? _b : (_a = this.cachedJwt) == null ? void 0 : _a.expMs) != null ? _c : 0;
716
+ if (expMs <= Date.now()) return null;
717
+ return {
718
+ // Copy so the caller (and the structured clone the BroadcastChannel makes)
719
+ // cannot mutate our live key buffers.
720
+ edSeed: new Uint8Array(this.sessionKeys.edSeed),
721
+ secpKey: new Uint8Array(this.sessionKeys.secpKey),
722
+ addresses: snap.addresses,
723
+ expMs
724
+ };
725
+ }
726
+ /**
727
+ * Adopt a session handed over from a same-origin sibling surface. Rebuilds
728
+ * the `DerivedKeys` from the transported seeds (public keys are recomputed
729
+ * from the seeds; we do NOT re-run the HKDF step so the exact transported
730
+ * secpKey is preserved), opens the vault, and syncs public state — no
731
+ * ceremony. There is no credentialId in the handoff, so login() would need a
732
+ * fresh ceremony; but the session key is warm, so `sign`/`claim` (which use
733
+ * the vault directly, no JWT) work immediately.
734
+ */
735
+ adoptSession(session) {
736
+ if (session.expMs <= Date.now()) return;
737
+ const edSeed = new Uint8Array(session.edSeed);
738
+ const secpKey = new Uint8Array(session.secpKey);
739
+ const keys = {
740
+ edSeed,
741
+ secpKey,
742
+ edPubkey: ed25519.ed25519.getPublicKey(edSeed),
743
+ secpPubkeyUncompressed: secp256k1.secp256k1.getPublicKey(secpKey, false)
744
+ };
745
+ this.vault.set({
746
+ keys,
747
+ addresses: session.addresses,
748
+ // No credentialId travels in the handoff. login() (which needs it) will
749
+ // fall back to a fresh ceremony; the adopted key covers ceremony-free ops.
750
+ credentialId: ""
751
+ });
752
+ this.sessionKeys = keys;
753
+ this.sessionExpMs = session.expMs;
754
+ this.syncPublicState();
755
+ }
636
756
  /**
637
757
  * Release the BroadcastChannel + cross-tab subscriber. Idempotent.
638
758
  *
@@ -686,5 +806,6 @@ exports.deriveSolanaAddress = deriveSolanaAddress;
686
806
  exports.derivedKeys = derivedKeys;
687
807
  exports.detectPrfSupport = detectPrfSupport;
688
808
  exports.isPrfSupportedResult = isPrfSupportedResult;
809
+ exports.supportsSessionHandoff = supportsSessionHandoff;
689
810
  //# sourceMappingURL=index.cjs.map
690
811
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants.ts","../src/browser.ts","../src/addresses.ts","../src/derive.ts","../src/base64url.ts","../src/ceremony.ts","../src/SoftAuthenticator.ts","../src/vault.ts","../src/pop.ts","../src/CrossTabSync.ts","../src/IdentityImpl.ts"],"names":["createHash","concatBytes","blake2b","bs58","keccak_256","hkdf","sha256","ed25519","secp256k1","WalletError","observable","runInAction"],"mappings":"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","file":"index.cjs","sourcesContent":["import { createHash } from 'node:crypto';\n\n/** Spec §3.2 locked decision #14. Permanent for v1; rotate via \"-v2\" suffix. */\nexport const PRF_INPUT_LABEL = 'cere-wallet-prf-v1';\n\n/**\n * SHA-256(\"cere-wallet-prf-v1\") — 32 bytes. Fed to WebAuthn `prf.eval.first`.\n *\n * Implementation note: `createHash` runs at module evaluation time. In the test\n * environment (Vitest on Node) this works natively via `node:crypto`. In the\n * browser this depends on a `node:crypto` polyfill — `vite-plugin-node-polyfills`\n * is wired in by Cycle 1 Task 8. A follow-up cycle should replace this expression\n * with a hard-coded `Uint8Array` literal to eliminate the polyfill dependency.\n */\nexport const PRF_INPUT_SEED: Uint8Array = new Uint8Array(createHash('sha256').update(PRF_INPUT_LABEL).digest());\n\n/** Spec §3.2. Used as HKDF-SHA256 info to derive the secp256k1 32-byte secret. */\nexport const EVM_HKDF_INFO = 'cere-wallet-evm-secp256k1-v1';\n","/**\n * Runtime support detection for WebAuthn + PRF.\n *\n * Spec §3.7. Real PRF capability is only knowable from a completed ceremony\n * (via `clientExtensionResults.prf.enabled` or `prf.results.first`). This\n * pre-check returns the best signal we can get without spending a credential.\n */\nexport interface PrfSupportResult {\n webAuthn: boolean;\n /**\n * Whether a platform authenticator (Touch ID / Windows Hello) is enrolled.\n * INFORMATIONAL ONLY — it is NOT a precondition for using the wallet, which\n * also works with roaming security keys and phone passkeys (via hybrid).\n */\n platformAuthenticator: boolean;\n /**\n * Best-effort PRF signal. `true` unless we can prove otherwise: modern\n * browsers expose `PublicKeyCredential.getClientCapabilities()`, which gives\n * a definitive `extensions:prf` answer. When that API is absent (older\n * browsers) we stay optimistic and let the ceremony be the final arbiter\n * (it throws `prf-unsupported` if the authenticator returns no PRF output).\n */\n prfPotentiallySupported: boolean;\n}\n\nexport async function detectPrfSupport(): Promise<PrfSupportResult> {\n const PKC = (globalThis as any).PublicKeyCredential;\n const webAuthn = typeof PKC !== 'undefined';\n if (!webAuthn) {\n return { webAuthn: false, platformAuthenticator: false, prfPotentiallySupported: false };\n }\n\n let platformAuthenticator = false;\n try {\n platformAuthenticator =\n typeof PKC.isUserVerifyingPlatformAuthenticatorAvailable === 'function' &&\n (await PKC.isUserVerifyingPlatformAuthenticatorAvailable());\n } catch {\n platformAuthenticator = false;\n }\n\n // Definitive PRF capability when the browser supports getClientCapabilities\n // (Chrome 133+, Safari 18+). Only a hard `false` blocks; absence keeps us\n // optimistic. We deliberately do NOT require a platform authenticator here.\n let prfPotentiallySupported = true;\n try {\n if (typeof PKC.getClientCapabilities === 'function') {\n const caps = await PKC.getClientCapabilities();\n if (caps && caps['extensions:prf'] === false) {\n prfPotentiallySupported = false;\n }\n }\n } catch {\n // Keep optimistic; the ceremony will surface prf-unsupported if needed.\n }\n\n return { webAuthn, platformAuthenticator, prfPotentiallySupported };\n}\n\n/**\n * Inspect a `PublicKeyCredential.getClientExtensionResults()` payload to\n * determine whether PRF actually worked in the ceremony. Spec §3.7 calls this\n * \"final answer; the pre-check above is best-effort only\".\n */\nexport function isPrfSupportedResult(extensions: {\n prf?: { enabled?: boolean; results?: { first?: Uint8Array } };\n}): boolean {\n const prf = extensions?.prf;\n if (!prf) return false;\n if (prf.enabled === true) return true;\n if (prf.results?.first instanceof Uint8Array) return true;\n return false;\n}\n","import bs58 from 'bs58';\nimport { blake2b } from '@noble/hashes/blake2b';\nimport { keccak_256 } from '@noble/hashes/sha3';\nimport { concatBytes } from '@noble/hashes/utils';\n\n/** Cere's SS58 network prefix. Spec §3.2. */\nexport const CERE_SS58_PREFIX = 54;\n\nconst SS58PRE = new TextEncoder().encode('SS58PRE');\n\n/**\n * SS58 address for the Cere network.\n *\n * SS58 spec (Substrate): for prefixes 0–63 the format is:\n * [prefix byte] || [32-byte pubkey] || [2-byte checksum]\n * where checksum = blake2b-512(\"SS58PRE\" || prefix || pubkey)[0..2].\n * The whole thing is base58-encoded. We use prefix 54 (Cere) which fits in\n * the 1-byte encoding (< 64).\n *\n * @param edPubkey 32-byte Ed25519 public key.\n */\nexport function deriveCereAddress(edPubkey: Uint8Array): string {\n if (edPubkey.length !== 32) {\n throw new Error(`expected 32-byte Ed25519 pubkey, got ${edPubkey.length}`);\n }\n const prefix = new Uint8Array([CERE_SS58_PREFIX]);\n const body = concatBytes(prefix, edPubkey);\n const checksum = blake2b(concatBytes(SS58PRE, body), { dkLen: 64 }).slice(0, 2);\n const payload = concatBytes(body, checksum);\n return bs58.encode(payload);\n}\n\n/**\n * Solana address: base58(Ed25519 pubkey). No prefix, no checksum.\n *\n * @param edPubkey 32-byte Ed25519 public key.\n */\nexport function deriveSolanaAddress(edPubkey: Uint8Array): string {\n if (edPubkey.length !== 32) {\n throw new Error(`expected 32-byte Ed25519 pubkey, got ${edPubkey.length}`);\n }\n return bs58.encode(edPubkey);\n}\n\n/**\n * Recover the raw 32-byte Ed25519 public key from a Solana address.\n *\n * The Solana address is `base58(edPubkey)` with no prefix or checksum\n * ({@link deriveSolanaAddress}), so a base58 decode yields the pubkey directly.\n * The public key is not secret — it IS the address — so this introduces no key\n * exposure beyond what the address already reveals; the session keys never\n * leave `SessionVault`. The same Ed25519 key backs the Cere SS58 address.\n *\n * @param solanaAddress base58 Solana address.\n * @returns the 32-byte Ed25519 public key.\n */\nexport function decodeEd25519Pubkey(solanaAddress: string): Uint8Array {\n const pubkey = bs58.decode(solanaAddress);\n if (pubkey.length !== 32) {\n throw new Error(`expected a 32-byte Ed25519 pubkey from the Solana address, got ${pubkey.length}`);\n }\n return pubkey;\n}\n\n/**\n * EVM address: '0x' + last 20 bytes of keccak256(secp256k1 uncompressed pubkey X||Y).\n *\n * Accepts either 64-byte (raw X||Y) or 65-byte (0x04||X||Y) input. The 0x04\n * prefix byte is stripped if present.\n *\n * @param secpPubkey secp256k1 public key.\n */\nexport function deriveEvmAddress(secpPubkey: Uint8Array): string {\n let xy: Uint8Array;\n if (secpPubkey.length === 65 && secpPubkey[0] === 0x04) {\n xy = secpPubkey.slice(1);\n } else if (secpPubkey.length === 64) {\n xy = secpPubkey;\n } else {\n throw new Error(`expected 64-byte (X||Y) or 65-byte (0x04||X||Y) secp256k1 pubkey, got ${secpPubkey.length}`);\n }\n const hash = keccak_256(xy);\n const last20 = hash.slice(12);\n return '0x' + Buffer.from(last20).toString('hex');\n}\n","import { ed25519 } from '@noble/curves/ed25519';\nimport { secp256k1 } from '@noble/curves/secp256k1';\nimport { hkdf } from '@noble/hashes/hkdf';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { EVM_HKDF_INFO } from './constants';\n\nexport interface DerivedKeys {\n /** Ed25519 32-byte secret key seed; used for Cere + Solana signing. */\n edSeed: Uint8Array;\n /** secp256k1 32-byte secret key; used for EVM signing. */\n secpKey: Uint8Array;\n /** 32-byte Ed25519 public key derived from edSeed. */\n edPubkey: Uint8Array;\n /** 65-byte uncompressed secp256k1 public key (0x04 || X || Y) derived from secpKey. */\n secpPubkeyUncompressed: Uint8Array;\n}\n\n/**\n * Derive the v2 session keys from a WebAuthn PRF output. Spec §3.2.\n *\n * edSeed = prfOutput\n * secpKey = HKDF-SHA256(prfOutput, salt=\"\", info=\"cere-wallet-evm-secp256k1-v1\", L=32)\n *\n * @param prfOutput 32-byte PRF output from `prf.results.first`.\n */\nexport function derivedKeys(prfOutput: Uint8Array): DerivedKeys {\n if (prfOutput.length !== 32) {\n throw new Error(`expected 32-byte PRF output, got ${prfOutput.length}`);\n }\n const edSeed = new Uint8Array(prfOutput); // copy to detach from caller's buffer\n const secpKey = hkdf(sha256, prfOutput, new Uint8Array(0), EVM_HKDF_INFO, 32);\n\n const edPubkey = ed25519.getPublicKey(edSeed);\n // @noble/curves exposes both compressed and uncompressed forms; we want uncompressed.\n const secpPubkeyUncompressed = secp256k1.getPublicKey(secpKey, false);\n\n return { edSeed, secpKey, edPubkey, secpPubkeyUncompressed };\n}\n","/**\n * Base64url (RFC 4648 §5) helpers used by the WebAuthn ceremony adapter.\n * Trailing `=` padding is stripped on encode; both padded and unpadded inputs\n * are accepted on decode.\n */\n\n/** Encode a Uint8Array or ArrayBuffer as a base64url string (no padding). */\nexport function bytesToB64u(input: Uint8Array | ArrayBuffer): string {\n const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);\n let bin = '';\n for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);\n const b64 = btoa(bin);\n return b64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/** Decode a base64url string into a Uint8Array. Padding is optional. */\nexport function b64uToBytes(s: string): Uint8Array {\n const b64 = s.replace(/-/g, '+').replace(/_/g, '/');\n const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);\n const bin = atob(padded); // throws DOMException on invalid input — correct behavior\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n","import { bytesToB64u, b64uToBytes } from './base64url';\n\n/**\n * `hints` is a top-level field on the WebAuthn creation/request options\n * (WebAuthn L3), but our TS DOM lib doesn't ship it yet. Declare-merge the\n * precise W3C union so the ceremony can request authenticator hints without\n * casts. Drop this augmentation once the workspace lib.dom.d.ts ships `hints`.\n */\ndeclare global {\n interface PublicKeyCredentialCreationOptions {\n hints?: ('client-device' | 'hybrid' | 'security-key')[];\n }\n interface PublicKeyCredentialRequestOptions {\n hints?: ('client-device' | 'hybrid' | 'security-key')[];\n }\n}\n\n/**\n * Adapter that runs WebAuthn ceremonies. Production implementations call\n * `navigator.credentials.create/get`; the test implementation in\n * `SoftAuthenticator` produces deterministic results without browser APIs.\n *\n * Spec §3.7. The `prfOutput` field is the result of the WebAuthn PRF extension\n * — present when the authenticator supports PRF, absent otherwise.\n */\nexport interface CeremonyAdapter {\n register(opts: RegisterOptions): Promise<RegisterResult>;\n login(opts: LoginOptions): Promise<LoginResult>;\n}\n\nexport interface RegisterOptions {\n rpId: string;\n challenge: Uint8Array;\n userHandle: Uint8Array;\n /** 32-byte PRF input (typically `PRF_INPUT_SEED`). */\n prfInput: Uint8Array;\n /** Human-readable label for the credential. Currently a hint, not stored. */\n label?: string;\n}\n\nexport interface RegisterResult {\n credentialId: string; // base64url\n clientDataJSON: string; // base64url\n attestationObject: string; // base64url\n transports: string[];\n /** Present iff PRF extension worked. */\n prfOutput?: Uint8Array;\n}\n\nexport interface LoginOptions {\n rpId: string;\n challenge: Uint8Array;\n /** Optional: if absent, the authenticator picks a credential. Present from us. */\n credentialId?: string;\n prfInput: Uint8Array;\n}\n\nexport interface LoginResult {\n credentialId: string;\n clientDataJSON: string;\n authenticatorData: string;\n signature: string;\n prfOutput?: Uint8Array;\n}\n\nexport interface WebAuthnCeremonyAdapterOptions {\n /**\n * Override for the WebAuthn credentials manager. Defaults to\n * `globalThis.navigator?.credentials`. Tests inject a mock.\n */\n credentials?: CredentialsContainer;\n}\n\n/**\n * Authenticator selection `hints` (WebAuthn L3, Chrome/Edge/Safari 18+).\n *\n * These bias the browser's passkey picker toward authenticators that\n * reliably support the PRF extension — platform authenticators (Touch ID /\n * Windows Hello / Android), phone passkeys (hybrid), and security keys.\n *\n * Hints are *preferences, not filters*: a software password-manager passkey\n * (e.g. Bitwarden, 1Password) still appears in the picker, just de-prioritized.\n * On a machine with Touch ID enrolled this makes \"This device\" the default\n * choice, so users no longer land on Bitwarden's passkey (which can't satisfy\n * PRF and fails the ceremony with `prf-unsupported`). The post-ceremony\n * `prfOutput` check remains the real capability gate.\n */\nconst PRF_CAPABLE_HINTS = ['client-device', 'hybrid', 'security-key'] as const;\n\n/**\n * Production WebAuthn ceremony adapter. Calls `navigator.credentials.create/get`\n * with the PRF extension. Spec §3.7.\n *\n * - `register`: requires a platform authenticator, user verification, Ed25519\n * (`alg: -8`), and the PRF extension with the wallet's input seed.\n * - `login`: allows a specific credentialId if known; otherwise lets the\n * authenticator pick (resident key).\n *\n * Returned ArrayBuffers are base64url-encoded for API transport.\n */\nexport class WebAuthnCeremonyAdapter implements CeremonyAdapter {\n private readonly credentials?: CredentialsContainer;\n\n constructor(opts: WebAuthnCeremonyAdapterOptions = {}) {\n this.credentials = opts.credentials ?? (globalThis as any).navigator?.credentials;\n }\n\n async register(opts: RegisterOptions): Promise<RegisterResult> {\n if (!this.credentials) {\n throw new Error('WebAuthnCeremonyAdapter: navigator.credentials is unavailable');\n }\n const cred = (await this.credentials.create({\n publicKey: {\n challenge: opts.challenge,\n rp: { id: opts.rpId, name: opts.rpId },\n user: {\n id: opts.userHandle,\n name: opts.label ?? 'scp-wallet',\n displayName: opts.label ?? 'SCP Wallet',\n },\n pubKeyCredParams: [\n { alg: -8, type: 'public-key' }, // EdDSA (Ed25519 security keys) — preferred\n { alg: -7, type: 'public-key' }, // ES256 (Apple/Windows/Android platform authenticators)\n ],\n authenticatorSelection: {\n // No `authenticatorAttachment`: allow platform authenticators\n // (Touch ID / Windows Hello), roaming security keys, and phone\n // passkeys via hybrid — PRF works across all of them, and the\n // pubKeyCredParams above explicitly prefer Ed25519 security keys.\n // The post-ceremony PRF result is the real capability gate.\n userVerification: 'required',\n residentKey: 'preferred',\n },\n // Bias the picker toward PRF-capable authenticators so the user\n // doesn't default onto a password-manager passkey. See PRF_CAPABLE_HINTS.\n hints: [...PRF_CAPABLE_HINTS],\n timeout: 60_000,\n extensions: { prf: { eval: { first: opts.prfInput } } } as AuthenticationExtensionsClientInputs,\n },\n })) as PublicKeyCredential | null;\n if (!cred) {\n throw new Error('WebAuthnCeremonyAdapter.register: credentials.create returned null');\n }\n\n const response = cred.response as AuthenticatorAttestationResponse;\n const transports = typeof response.getTransports === 'function' ? response.getTransports() : [];\n const ext = cred.getClientExtensionResults() as { prf?: { results?: { first?: ArrayBuffer } } };\n const prfFirst = ext?.prf?.results?.first;\n\n return {\n credentialId: cred.id,\n clientDataJSON: bytesToB64u(response.clientDataJSON),\n attestationObject: bytesToB64u(response.attestationObject),\n transports,\n prfOutput: prfFirst ? new Uint8Array(prfFirst) : undefined,\n };\n }\n\n async login(opts: LoginOptions): Promise<LoginResult> {\n if (!this.credentials) {\n throw new Error('WebAuthnCeremonyAdapter: navigator.credentials is unavailable');\n }\n const cred = (await this.credentials.get({\n publicKey: {\n challenge: opts.challenge,\n rpId: opts.rpId,\n allowCredentials: opts.credentialId ? [{ id: b64uToBytes(opts.credentialId), type: 'public-key' }] : undefined,\n userVerification: 'required',\n // Bias first-login-on-device (no allowCredentials) toward PRF-capable\n // authenticators, mirroring register. See PRF_CAPABLE_HINTS.\n hints: [...PRF_CAPABLE_HINTS],\n timeout: 60_000,\n extensions: { prf: { eval: { first: opts.prfInput } } } as AuthenticationExtensionsClientInputs,\n },\n })) as PublicKeyCredential | null;\n if (!cred) {\n throw new Error('WebAuthnCeremonyAdapter.login: credentials.get returned null');\n }\n\n const response = cred.response as AuthenticatorAssertionResponse;\n const ext = cred.getClientExtensionResults() as { prf?: { results?: { first?: ArrayBuffer } } };\n const prfFirst = ext?.prf?.results?.first;\n\n return {\n credentialId: cred.id,\n clientDataJSON: bytesToB64u(response.clientDataJSON),\n authenticatorData: bytesToB64u(response.authenticatorData),\n signature: bytesToB64u(response.signature),\n prfOutput: prfFirst ? new Uint8Array(prfFirst) : undefined,\n };\n }\n}\n","import { ed25519 } from '@noble/curves/ed25519';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { hkdf } from '@noble/hashes/hkdf';\nimport { concatBytes } from '@noble/hashes/utils';\nimport type { CeremonyAdapter, RegisterOptions, RegisterResult, LoginOptions, LoginResult } from './ceremony';\n\n/** base64url encode without padding. */\nfunction b64u(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\ninterface StoredCredential {\n credentialId: string;\n edSeed: Uint8Array;\n prfSecret: Uint8Array;\n}\n\nexport interface SoftAuthenticatorOptions {\n /**\n * If provided, derive the per-registration `edSeed`, `prfSecret`, and\n * `credentialId` deterministically from this seed (mixed with a monotonic\n * counter so multiple register() calls on the same instance still produce\n * distinct credentials). Used by Playwright e2e specs that pin server-side\n * address stubs against the SoftAuthenticator's output. Production code\n * never sets this — the `useSoft` branch in `createIdentity.ts` is itself\n * forbidden in production builds.\n *\n * Default: undefined (each register() draws fresh CSPRNG bytes, matching\n * the pre-cycle-8.1 behavior used by unit tests in `ceremony.test.ts`).\n */\n seed?: string;\n\n /**\n * When the caller invokes login() without a credentialId, fall back to the\n * most-recently-registered credential in memory. Default: false (login()\n * throws when credentialId is missing, matching pre-cycle-8 behavior).\n *\n * Required for e2e flows that simulate the user signing back in after the\n * vault has been cleared (post-logout re-login): the wallet has discarded\n * the credentialId locally, but the soft authenticator's in-memory creds\n * map still holds the registration, and the spec test needs login() to\n * succeed anyway. Production code must never enable this — real WebAuthn's\n * no-allowCredentials path shows an OS credential picker; the fallback\n * here is a deterministic test convenience, not a real picker, so leaving\n * it on by default would silently let a single registered credential\n * impersonate any login.\n */\n fallbackToLastRegistered?: boolean;\n}\n\n/**\n * Deterministic in-memory WebAuthn authenticator for tests. NOT for production.\n *\n * - Stores credentials in a private Map keyed by credentialId.\n * - `prfOutput` is computed as HKDF-SHA256(prfSecret, salt=\"\", info=prfInput, L=32),\n * so for a given credential and prfInput the result is stable across login calls.\n * - `signature` is a valid Ed25519 signature over `authenticatorData || sha256(clientDataJSON)`.\n *\n * The output shapes match the real WebAuthn response well enough that the\n * `Identity` integration tests (Task 6) can drive register → login → derive\n * → cross-check without a real browser.\n */\nexport class SoftAuthenticator implements CeremonyAdapter {\n private readonly creds: Map<string, StoredCredential> = new Map();\n private readonly seedBytes: Uint8Array | null;\n private readonly fallbackToLastRegistered: boolean;\n /** Monotonic counter mixed into the seed-derived material so successive\n * register() calls on a seeded instance produce distinct credentials. */\n private regCounter = 0;\n\n constructor(opts: SoftAuthenticatorOptions = {}) {\n this.seedBytes = opts.seed != null ? new TextEncoder().encode(opts.seed) : null;\n this.fallbackToLastRegistered = opts.fallbackToLastRegistered === true;\n }\n\n async register(opts: RegisterOptions): Promise<RegisterResult> {\n // Generate per-credential material. When seeded, derive deterministically\n // via HKDF-SHA256(seed, salt=<counter>, info=<purpose>) so the e2e specs\n // can pin server-side address stubs against the resulting addresses.\n let edSeed: Uint8Array;\n let prfSecret: Uint8Array;\n let credentialIdBytes: Uint8Array;\n if (this.seedBytes) {\n const salt = new TextEncoder().encode(`reg-${this.regCounter++}`);\n edSeed = hkdf(sha256, this.seedBytes, salt, 'ed-seed', 32);\n prfSecret = hkdf(sha256, this.seedBytes, salt, 'prf-secret', 32);\n credentialIdBytes = hkdf(sha256, this.seedBytes, salt, 'credential-id', 16);\n } else {\n edSeed = ed25519.utils.randomPrivateKey();\n prfSecret = ed25519.utils.randomPrivateKey(); // 32 random bytes\n credentialIdBytes = ed25519.utils.randomPrivateKey().slice(0, 16); // 16-byte id\n }\n const credentialId = b64u(credentialIdBytes);\n\n this.creds.set(credentialId, { credentialId, edSeed, prfSecret });\n\n // Compose the WebAuthn-shaped artifacts. These are NOT spec-correct attestations;\n // they're test fixtures whose only requirement is to round-trip through identity.\n const clientData = {\n type: 'webauthn.create',\n challenge: b64u(opts.challenge),\n origin: `https://${opts.rpId}`,\n crossOrigin: false,\n };\n const clientDataJSON = new TextEncoder().encode(JSON.stringify(clientData));\n const attestationObject = new Uint8Array([0xaa]); // placeholder; not parsed by client tests\n\n const prfOutput = this.computePrf(prfSecret, opts.prfInput);\n\n return {\n credentialId,\n clientDataJSON: b64u(clientDataJSON),\n attestationObject: b64u(attestationObject),\n transports: ['internal'],\n prfOutput,\n };\n }\n\n async login(opts: LoginOptions): Promise<LoginResult> {\n // Two modes:\n // 1. Explicit credentialId (the common path, including IdentityImpl's\n // warm-vault re-mint flow): look it up and throw on miss.\n // 2. No credentialId: by default this is an error (pre-cycle-8 behaviour),\n // because a silent \"pick the last one\" is a security trap if it ever\n // leaks into production. Callers that genuinely need the fallback\n // (Playwright post-logout re-login) opt in via\n // `new SoftAuthenticator({ fallbackToLastRegistered: true })`.\n let stored: StoredCredential | undefined;\n if (opts.credentialId) {\n stored = this.creds.get(opts.credentialId);\n if (!stored) {\n throw new Error(`SoftAuthenticator.login: unknown credential ${opts.credentialId}`);\n }\n } else {\n if (!this.fallbackToLastRegistered) {\n throw new Error(\n 'SoftAuthenticator.login: credentialId is required ' +\n '(set `fallbackToLastRegistered: true` in the constructor to opt into the e2e convenience of picking the most-recently-registered credential)',\n );\n }\n const all = Array.from(this.creds.values());\n stored = all[all.length - 1];\n if (!stored) {\n throw new Error('SoftAuthenticator.login: no credentials registered');\n }\n }\n\n const clientData = {\n type: 'webauthn.get',\n challenge: b64u(opts.challenge),\n origin: `https://${opts.rpId}`,\n crossOrigin: false,\n };\n const clientDataJSON = new TextEncoder().encode(JSON.stringify(clientData));\n\n // Minimal authenticatorData: 32-byte rpIdHash || 1-byte flags || 4-byte signCount.\n // We don't need a real rpIdHash for client-side tests.\n const authenticatorData = new Uint8Array(37);\n authenticatorData[32] = 0x05; // UP + UV flags\n\n const clientDataHash = sha256(clientDataJSON);\n const toSign = concatBytes(authenticatorData, clientDataHash);\n const signature = ed25519.sign(toSign, stored.edSeed);\n\n const prfOutput = this.computePrf(stored.prfSecret, opts.prfInput);\n\n return {\n credentialId: stored.credentialId,\n clientDataJSON: b64u(clientDataJSON),\n authenticatorData: b64u(authenticatorData),\n signature: b64u(signature),\n prfOutput,\n };\n }\n\n /** Public-key bytes for a credential. Used by integration tests for cross-checks. */\n getEd25519PublicKey(credentialId: string): Uint8Array {\n const stored = this.creds.get(credentialId);\n if (!stored) throw new Error(`unknown credential ${credentialId}`);\n return ed25519.getPublicKey(stored.edSeed);\n }\n\n private computePrf(prfSecret: Uint8Array, prfInput: Uint8Array): Uint8Array {\n // Deterministic per (prfSecret, prfInput) — matches the spirit of how a real\n // authenticator's PRF extension works (HKDF-style mixing of a per-credential\n // secret with the caller's input). NOT identical to any real authenticator.\n return hkdf(sha256, prfSecret, new Uint8Array(0), prfInput, 32);\n }\n}\n","import { ed25519 } from '@noble/curves/ed25519';\nimport { secp256k1 } from '@noble/curves/secp256k1';\nimport type { DerivedKeys } from './derive';\nimport type { Addresses, Chain } from './types';\n\nexport interface VaultSnapshot {\n addresses: Addresses;\n credentialId: string;\n}\n\nexport interface SessionVault {\n isOpen(): boolean;\n /** Public-safe view: never includes raw keys. */\n snapshot(): VaultSnapshot | null;\n /** Open the vault with derived keys + the addresses they yielded + the credentialId. */\n set(args: { keys: DerivedKeys; addresses: Addresses; credentialId: string }): void;\n /** Drop the keys and address state. Safe to call when already closed. */\n clear(): void;\n /**\n * Sign a payload with the chain-appropriate key. Spec §3.6 invariant:\n * this is the only way the rest of the package can reach the keys.\n *\n * - 'cere' / 'solana': Ed25519 signature over the raw payload (64 bytes).\n * - 'evm': secp256k1 ECDSA over the raw payload (65 bytes: r || s || v).\n * The caller (an EVM signer) is responsible for framing (e.g. EIP-191)\n * and hashing (keccak256) before passing the digest as the payload.\n */\n sign(chain: Chain, payload: Uint8Array): Promise<Uint8Array>;\n}\n\n/**\n * Closure-encapsulated session vault. The keys live in the closure variables\n * `state.edSeed` / `state.secpKey` and are not reachable from outside.\n *\n * Spec §3.4. Lifetime: from `set()` until `clear()` or tab unload.\n */\nexport function createSessionVault(): SessionVault {\n let state: {\n edSeed: Uint8Array;\n secpKey: Uint8Array;\n addresses: Addresses;\n credentialId: string;\n } | null = null;\n\n return {\n isOpen() {\n return state !== null;\n },\n snapshot() {\n if (!state) return null;\n return { addresses: state.addresses, credentialId: state.credentialId };\n },\n set({ keys, addresses, credentialId }) {\n state = {\n edSeed: new Uint8Array(keys.edSeed),\n secpKey: new Uint8Array(keys.secpKey),\n addresses,\n credentialId,\n };\n },\n clear() {\n if (state) {\n state.edSeed.fill(0);\n state.secpKey.fill(0);\n }\n state = null;\n },\n async sign(chain, payload) {\n if (!state) {\n throw new Error('SessionVault: closed — no session keys available');\n }\n if (chain === 'cere' || chain === 'solana') {\n return ed25519.sign(payload, state.edSeed);\n }\n if (chain === 'evm') {\n // Caller (chain-specific signer) is responsible for framing + hashing.\n // We sign the payload directly and return 65 bytes: r (32) || s (32) || v (1),\n // where v is the recovery byte (0 or 1). Spec §3.6 invariant 1 holds —\n // the secpKey never leaves this closure.\n const sig = secp256k1.sign(payload, state.secpKey);\n const compact = sig.toCompactRawBytes(); // 64 bytes (r || s)\n const v = sig.recovery; // 0 or 1\n const out = new Uint8Array(65);\n out.set(compact, 0);\n out[64] = v;\n return out;\n }\n throw new Error(`SessionVault.sign: unknown chain \"${String(chain)}\"`);\n },\n };\n}\n","import { ed25519 } from '@noble/curves/ed25519';\nimport { secp256k1 } from '@noble/curves/secp256k1';\nimport type { DerivedKeys } from './derive';\n\nexport interface RegistrationProof {\n /** ed25519 signature (64B) over the raw challenge — proves cere + solana. */\n identitySig: Uint8Array;\n /** secp256k1 uncompressed pubkey (65B, 0x04 || X || Y). */\n evmPubkey: Uint8Array;\n /** secp256k1 compact r||s signature (64B) over the raw challenge. */\n evmSig: Uint8Array;\n}\n\n/**\n * Sign the single-use registration challenge with the PRF-derived keys to\n * prove possession of the chain identity. The server (wallet-api)\n * verifies these against the claimed addresses. Spec §3.3 (revised).\n *\n * - ed25519: sign the raw 32-byte challenge (EdDSA hashes internally).\n * - secp256k1: ECDSA over the 32-byte challenge treated as the message digest\n * (matches the server's `ecdsa.Verify(challenge, pubkey)`). The challenge is\n * 32 random single-use bytes, so it is passed directly as the prehashed\n * digest — NOT re-hashed (no `{ prehash: true }`). low-S is enforced by\n * @noble's default, so the compact form is canonical.\n */\nexport function signRegistrationProof(keys: DerivedKeys, challenge: Uint8Array): RegistrationProof {\n const identitySig = ed25519.sign(challenge, keys.edSeed);\n const evmSig = secp256k1.sign(challenge, keys.secpKey).toCompactRawBytes();\n return { identitySig, evmPubkey: keys.secpPubkeyUncompressed, evmSig };\n}\n","/**\n * Cross-tab session synchronisation helper.\n *\n * Wraps a `BroadcastChannel` on a fixed channel name so all tabs of the same\n * origin can coordinate. Today we only use it for `{ type: 'logout' }` — when\n * one tab logs out, every other tab clears its in-memory vault + JWT so its\n * MobX-reactive UI immediately reflects the logged-out state.\n *\n * The channel name must match `BROADCAST_CHANNEL_NAME` in\n * `packages/embed-sdk/src/protocol.ts` ('scp-wallet-v2'). Defined here as a\n * local constant rather than imported to avoid a circular dep between\n * `@cef-ai/wallet-identity` and `@cef-ai/wallet`. The name is part of the\n * wire protocol — if you change one, change the other (and bump the major).\n *\n * Spec §9.4 (cross-tab session sharing).\n */\n\n/** MUST match `packages/embed-sdk/src/protocol.ts` `BROADCAST_CHANNEL_NAME`. */\nconst CHANNEL_NAME = 'scp-wallet-v2';\n\nexport type CrossTabMessage = { type: 'logout' };\n\nexport class CrossTabSync {\n private channel: BroadcastChannel | null;\n private readonly listeners = new Set<(msg: CrossTabMessage) => void>();\n\n constructor() {\n // SSR / Node-without-BroadcastChannel safety. The class becomes a no-op\n // (broadcast() returns silently, subscribe() returns a real unsub that\n // just empties the local set, close() is a no-op).\n if (typeof BroadcastChannel === 'undefined') {\n this.channel = null;\n return;\n }\n this.channel = new BroadcastChannel(CHANNEL_NAME);\n this.channel.onmessage = (ev: MessageEvent) => {\n const msg = ev.data as CrossTabMessage | undefined;\n if (!msg || typeof msg !== 'object' || typeof (msg as { type?: unknown }).type !== 'string') {\n return;\n }\n for (const fn of this.listeners) {\n try {\n fn(msg);\n } catch {\n // Listener errors must not break the channel for other listeners.\n }\n }\n };\n }\n\n /** Broadcast a message to all other tabs (and not to this tab — that's the\n * BroadcastChannel spec). The originating tab is expected to have already\n * applied the state change locally before broadcasting. */\n broadcast(msg: CrossTabMessage): void {\n this.channel?.postMessage(msg);\n }\n\n /** Subscribe to messages from other tabs. Returns an unsubscribe function. */\n subscribe(fn: (msg: CrossTabMessage) => void): () => void {\n this.listeners.add(fn);\n return () => {\n this.listeners.delete(fn);\n };\n }\n\n /**\n * Close the underlying channel and drop all listeners. Idempotent.\n *\n * Sets `this.channel = null` so subsequent `broadcast()` calls become a\n * no-op (the `?.postMessage` short-circuits) and we don't accidentally\n * postMessage on a closed BroadcastChannel (which throws InvalidStateError\n * in some implementations).\n */\n close(): void {\n this.channel?.close();\n this.channel = null;\n this.listeners.clear();\n }\n}\n","import { observable, runInAction } from 'mobx';\nimport { WalletError, type ApiClient, type Addresses as ApiAddresses } from '@cef-ai/wallet-api-client';\nimport type { Identity, Addresses } from './types';\nimport type { CeremonyAdapter } from './ceremony';\nimport { createSessionVault, type SessionVault } from './vault';\nimport { derivedKeys } from './derive';\nimport { signRegistrationProof } from './pop';\nimport { bytesToB64u } from './base64url';\nimport { deriveCereAddress, deriveSolanaAddress, deriveEvmAddress } from './addresses';\nimport { PRF_INPUT_SEED } from './constants';\nimport { CrossTabSync } from './CrossTabSync';\n\nexport interface IdentityImplOptions {\n apiClient: ApiClient;\n ceremony: CeremonyAdapter;\n}\n\n/** Minimum slack before expiry where we treat the JWT as \"needs refresh\". */\nconst JWT_REFRESH_SLACK_MS = 60_000;\n\nfunction decodeJwtExpMs(token: string): number {\n const parts = token.split('.');\n if (parts.length < 2) {\n throw new WalletError('validation', 'JWT does not have 3 parts');\n }\n const payloadB64 = parts[1];\n const payload = JSON.parse(Buffer.from(payloadB64.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8'));\n if (typeof payload.exp !== 'number') {\n throw new WalletError('validation', 'JWT missing exp');\n }\n return payload.exp * 1000;\n}\n\nfunction b64uDecode(s: string): Uint8Array {\n return Uint8Array.from(Buffer.from(s.replace(/-/g, '+').replace(/_/g, '/'), 'base64'));\n}\n\nexport class IdentityImpl implements Identity {\n private readonly vault: SessionVault = createSessionVault();\n private cachedJwt: { token: string; expMs: number } | null = null;\n /** Server-returned credential ID (used as the public identity.credentialId). */\n private serverCredentialId: string | null = null;\n\n // Public observable state — derived from vault snapshot + serverCredentialId.\n // Kept in sync via syncPublicState() so React `observer()` wrappers around\n // components reading isAuthenticated/addresses/credentialId actually re-render\n // on register/login/logout. The vault itself stays non-observable (security:\n // the closure-encapsulated keys must not be tracked by MobX).\n private readonly _isAuthenticated = observable.box(false);\n // `deep: false` keeps the stored value a plain object instead of wrapping it\n // in a MobX proxy. addresses is a wholesale-replaced snapshot (never mutated\n // field-by-field), so deep observability buys nothing — and a proxy is not\n // structured-cloneable, which breaks `postMessage` when the embed popup\n // bridge sends addresses to the host page (wallet:login:ok).\n private readonly _addresses = observable.box<Addresses | null>(null, { deep: false });\n private readonly _credentialId = observable.box<string | null>(null);\n\n /** Cross-tab logout coordination via BroadcastChannel('scp-wallet-v2'). */\n private readonly crossTabSync = new CrossTabSync();\n /** Handle to detach the cross-tab logout subscriber on dispose(). */\n private readonly crossTabSyncUnsubscribe: () => void;\n /** Idempotency flag for dispose(). */\n private disposed = false;\n\n constructor(private readonly opts: IdentityImplOptions) {\n // Hydrate the boxes from any existing vault snapshot. Currently the vault\n // starts closed, so this is a no-op — but it's the right call site for a\n // future BroadcastChannel-restored session.\n this.syncPublicState();\n // Cross-tab logout: when another tab broadcasts {type:'logout'}, mirror it\n // locally so this tab's MobX-bound UI re-renders without a page reload.\n // The `vault.isOpen()` guard prevents the originating tab from\n // double-clearing — by the time its own listener fires (BroadcastChannel\n // doesn't deliver to the sender, but defence-in-depth), the vault is\n // already closed. The guard also makes the listener a no-op for tabs\n // that were already signed out.\n //\n // Capture the unsubscribe handle so dispose() can detach this subscriber\n // before closing the channel — without this, a long-lived test suite or\n // a hot-reloaded SPA leaks listener closures into the next IdentityImpl.\n this.crossTabSyncUnsubscribe = this.crossTabSync.subscribe((msg) => {\n if (msg.type === 'logout' && this.vault.isOpen()) {\n this.vault.clear();\n this.cachedJwt = null;\n this.serverCredentialId = null;\n this.syncPublicState();\n }\n });\n }\n\n get isAuthenticated(): boolean {\n return this._isAuthenticated.get();\n }\n\n get addresses(): Addresses | null {\n return this._addresses.get();\n }\n\n /**\n * Returns the server-returned credentialId from the last register/login ceremony.\n * The vault internally tracks the authenticator-generated credential ID for login use.\n */\n get credentialId(): string | null {\n return this._credentialId.get();\n }\n\n /**\n * Read the (non-observable) vault snapshot + cached server credential ID and\n * push the values into the three public observable boxes. Call after any\n * mutation that could change the public derived state: register, login,\n * logout, or constructor (hydration).\n */\n private syncPublicState(): void {\n const snap = this.vault.snapshot();\n runInAction(() => {\n this._isAuthenticated.set(this.vault.isOpen());\n this._addresses.set(snap?.addresses ?? null);\n this._credentialId.set(this.serverCredentialId);\n });\n }\n\n async register(opts: { label?: string } = {}): Promise<void> {\n // 1. Start ceremony — get the challenge from the API.\n // The client-derived addresses are determined post-ceremony from the PRF\n // output. The server only needs them at /finish; at /start we send a\n // placeholder that the server ignores. (Per API.md, /start accepts the\n // addresses field but the load-bearing check is at /finish.)\n const start = await this.opts.apiClient.passkey.registerStart({\n addresses: { cere: '5GplaceholderClientCannotKnowYet' } as any,\n label: opts.label,\n });\n\n // 2. Run the ceremony.\n const cer = await this.opts.ceremony.register({\n rpId: start.rpId,\n challenge: b64uDecode(start.challenge),\n userHandle: b64uDecode(start.userHandle),\n prfInput: PRF_INPUT_SEED,\n label: opts.label,\n });\n if (!cer.prfOutput) {\n throw new WalletError('prf-unsupported', 'authenticator did not return a PRF output');\n }\n\n // 3. Derive keys + addresses locally.\n const keys = derivedKeys(cer.prfOutput);\n const clientAddresses: Addresses = {\n cere: deriveCereAddress(keys.edPubkey),\n solana: deriveSolanaAddress(keys.edPubkey),\n evm: deriveEvmAddress(keys.secpPubkeyUncompressed),\n };\n\n // 4. Prove possession of the PRF-derived chain keys by signing the\n // single-use challenge. The server binds addresses.{cere,solana,evm}\n // to these signatures (proof-of-possession), not to the WebAuthn\n // credential key. Spec §3.3 (revised).\n const proof = signRegistrationProof(keys, b64uDecode(start.challenge));\n\n // 5. Finish ceremony with the API.\n const finish = await this.opts.apiClient.passkey.registerFinish({\n challengeId: start.challengeId,\n clientDataJSON: cer.clientDataJSON,\n attestationObject: cer.attestationObject,\n addresses: clientAddresses,\n transports: cer.transports,\n identitySig: bytesToB64u(proof.identitySig),\n evmPubkey: bytesToB64u(proof.evmPubkey),\n evmSig: bytesToB64u(proof.evmSig),\n });\n\n // 6. Cross-check returned addresses against locally-derived. Spec §3.3.\n this.crossCheckAddresses(clientAddresses, finish.addresses);\n\n // 7. Install the vault + cache the JWT.\n // The vault stores the authenticator-generated credential ID (cer.credentialId)\n // so subsequent login() calls can pass it back to the same authenticator.\n // The server-returned credential ID is tracked separately for the public getter.\n this.vault.set({\n keys,\n addresses: clientAddresses,\n credentialId: cer.credentialId,\n });\n this.serverCredentialId = finish.credentialId;\n this.cachedJwt = { token: finish.token, expMs: decodeJwtExpMs(finish.token) };\n this.syncPublicState();\n }\n\n async login(): Promise<void> {\n const start = await this.opts.apiClient.passkey.loginStart();\n\n const cer = await this.opts.ceremony.login({\n rpId: start.rpId,\n challenge: b64uDecode(start.challenge),\n credentialId: this.vault.snapshot()?.credentialId,\n prfInput: PRF_INPUT_SEED,\n });\n if (!cer.prfOutput) {\n throw new WalletError('prf-unsupported', 'authenticator did not return a PRF output');\n }\n\n const keys = derivedKeys(cer.prfOutput);\n const clientAddresses: Addresses = {\n cere: deriveCereAddress(keys.edPubkey),\n solana: deriveSolanaAddress(keys.edPubkey),\n evm: deriveEvmAddress(keys.secpPubkeyUncompressed),\n };\n\n const finish = await this.opts.apiClient.passkey.loginFinish({\n challengeId: start.challengeId,\n credentialId: cer.credentialId,\n clientDataJSON: cer.clientDataJSON,\n authenticatorData: cer.authenticatorData,\n signature: cer.signature,\n });\n\n this.crossCheckAddresses(clientAddresses, finish.addresses);\n\n this.vault.set({\n keys,\n addresses: clientAddresses,\n credentialId: cer.credentialId,\n });\n this.serverCredentialId = finish.credentialId;\n this.cachedJwt = { token: finish.token, expMs: decodeJwtExpMs(finish.token) };\n this.syncPublicState();\n }\n\n logout(): void {\n this.vault.clear();\n this.cachedJwt = null;\n this.serverCredentialId = null;\n this.syncPublicState();\n // Broadcast AFTER clearing local state so other tabs converge on the same\n // post-logout snapshot. BroadcastChannel does not deliver to the sender,\n // so this tab won't re-enter the subscriber callback above.\n this.crossTabSync.broadcast({ type: 'logout' });\n }\n\n async getJwt(): Promise<string> {\n if (this.cachedJwt && Date.now() < this.cachedJwt.expMs - JWT_REFRESH_SLACK_MS) {\n return this.cachedJwt.token;\n }\n if (!this.vault.isOpen()) {\n throw new WalletError('unauthorized', 'session is closed; call register() or login() first');\n }\n // Vault is warm but JWT is stale — re-run login to mint a fresh JWT.\n await this.login();\n return this.cachedJwt!.token;\n }\n\n /** Helper used by callers (e.g. ApiClient.getAuthToken). */\n getCachedJwtForApiClient(): string | null {\n return this.cachedJwt?.token ?? null;\n }\n\n /**\n * Returns the session vault. Used by popup-side `wallet:sign` handlers\n * which need direct access to `vault.sign(chain, payload)`. NOT for host-\n * side use — keys never reach the host. Spec §3.6 invariant 1.\n */\n getVault(): SessionVault {\n return this.vault;\n }\n\n /**\n * Release the BroadcastChannel + cross-tab subscriber. Idempotent.\n *\n * Called by:\n * - WalletProvider's useEffect cleanup (SPA unmount / apiBaseUrl change)\n * - test afterEach hooks (prevent listener leakage across tests)\n *\n * After dispose(), the identity is unusable: register/login/logout/getJwt\n * still execute their normal logic, but cross-tab broadcasts no longer\n * propagate (channel is closed) and the local subscriber is detached.\n * The vault remains cleared (logout() runs locally). Spec §9.4 lifecycle.\n */\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n // Detach subscriber first so any in-flight onmessage from this channel\n // does not re-enter the now-closing identity.\n this.crossTabSyncUnsubscribe();\n this.crossTabSync.close();\n }\n\n // ---- private --------------------------------------------------------------\n\n private crossCheckAddresses(client: Addresses, server: ApiAddresses | undefined): void {\n if (!server) {\n throw new WalletError('derivation-mismatch', 'API did not return addresses');\n }\n if (server.cere != null && client.cere !== server.cere) {\n throw new WalletError('derivation-mismatch', `Cere mismatch: client=${client.cere} api=${server.cere}`);\n }\n if (server.solana != null && client.solana !== server.solana) {\n throw new WalletError('derivation-mismatch', `Solana mismatch: client=${client.solana} api=${server.solana}`);\n }\n if (server.evm != null && client.evm.toLowerCase() !== server.evm.toLowerCase()) {\n throw new WalletError('derivation-mismatch', `EVM mismatch: client=${client.evm} api=${server.evm}`);\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/constants.ts","../src/browser.ts","../src/addresses.ts","../src/derive.ts","../src/base64url.ts","../src/ceremony.ts","../src/SoftAuthenticator.ts","../src/vault.ts","../src/pop.ts","../src/CrossTabSync.ts","../src/IdentityImpl.ts"],"names":["concatBytes","blake2b","bs58","keccak_256","hkdf","sha256","ed25519","secp256k1","WalletError","observable","runInAction"],"mappings":"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","file":"index.cjs","sourcesContent":["/** Spec §3.1. Public contract for the identity layer. */\nexport interface Addresses {\n cere: string;\n solana: string;\n evm: string;\n}\n\nexport type Chain = 'cere' | 'solana' | 'evm';\n\n/**\n * A transferable snapshot of the current session — the raw chain-key material\n * plus the addresses it yields and an absolute expiry. This is the payload the\n * register-popup → iframe handoff moves across the same-origin\n * `BroadcastChannel('scp-wallet-v2')` (see embed-sdk's `BroadcastSessionShare`).\n *\n * SECURITY: unlike `VaultSnapshot`, this DOES carry the raw seeds. It exists\n * only so a same-origin sibling surface (the persistent iframe) can adopt a\n * session a punched-out popup just created — Safari blocks the WebAuthn\n * `create()` ceremony in a cross-origin iframe (Task 6), so the popup runs the\n * ceremony and hands its result back. It must NEVER cross an origin boundary or\n * reach a host page. Spec §3.6 invariant 1 (keys never leave the wallet origin)\n * still holds: the channel is same-origin and wallet-only.\n *\n * The field shape intentionally matches embed-sdk's `BroadcastSession`. It is\n * declared here (not imported from embed-sdk) to avoid a circular package\n * dependency — embed-sdk already depends on this package.\n */\nexport interface IdentitySession {\n /** Ed25519 32-byte secret key seed; Cere + Solana signing. */\n edSeed: Uint8Array;\n /** secp256k1 32-byte secret key; EVM signing. */\n secpKey: Uint8Array;\n addresses: Addresses;\n /** Absolute Unix epoch ms when the session expires. */\n expMs: number;\n}\n\n/**\n * Same-origin session-handoff capability. Kept OFF the core `Identity`\n * interface deliberately: it is an optional, wallet-origin-only concern (the\n * register-popup → iframe handoff), and forcing every `Identity` implementation\n * — including thin test doubles — to provide raw-key export/import would widen\n * the security surface for no benefit. Concrete `IdentityImpl` implements it;\n * SPA wiring feature-detects via `supportsSessionHandoff()`.\n */\nexport interface SessionHandoff {\n /**\n * Export the current session for a same-origin handoff, or `null` when no\n * live session exists (vault closed / expired / no captured keys). See\n * `IdentitySession` for the security contract.\n */\n exportSession(): IdentitySession | null;\n /**\n * Adopt a session handed over from a same-origin sibling surface (e.g. the\n * register popup). Rebuilds the derived keys from the transported seeds, opens\n * the vault, and syncs public state — WITHOUT running a WebAuthn ceremony.\n */\n adoptSession(session: IdentitySession): void;\n}\n\n/**\n * Narrow an `Identity` to one that supports same-origin session handoff.\n * `IdentityImpl` returns true; thin test doubles return false and are simply\n * skipped by the responder/requester wiring.\n */\nexport function supportsSessionHandoff(identity: Identity): identity is Identity & SessionHandoff {\n return (\n typeof (identity as Partial<SessionHandoff>).exportSession === 'function' &&\n typeof (identity as Partial<SessionHandoff>).adoptSession === 'function'\n );\n}\n\nexport interface Identity {\n readonly isAuthenticated: boolean;\n readonly addresses: Addresses | null;\n readonly credentialId: string | null;\n\n register(opts?: { label?: string; name?: string; email?: string }): Promise<void>;\n login(): Promise<void>;\n logout(): void;\n\n /** Returns a valid JWT; runs the passkey ceremony if needed. */\n getJwt(): Promise<string>;\n\n /**\n * Release any browser-owned handles this identity holds (cross-tab channel,\n * timers, broadcast listeners). After `dispose()` the identity is unusable.\n *\n * Idempotent: callable multiple times safely.\n */\n dispose(): void;\n}\n\n/**\n * Signers/* receive this function via a constructor-time closure handoff\n * from a concrete Identity instance. It is exported as a type-only alias so\n * dependents can annotate the closures they receive, but it is intentionally\n * absent from the `Identity` interface — there is no public `sign()` method.\n */\nexport type InternalSign = (chain: Chain, payload: Uint8Array) => Promise<Uint8Array>;\n","/** Spec §3.2 locked decision #14. Permanent for v1; rotate via \"-v2\" suffix. */\nexport const PRF_INPUT_LABEL = 'cere-wallet-prf-v1';\n\n/**\n * SHA-256(\"cere-wallet-prf-v1\") — 32 bytes. Fed to WebAuthn `prf.eval.first`.\n *\n * Hard-coded as a byte literal so this module is browser-safe with NO\n * `node:crypto` dependency — it is evaluated at module-load time, and relying on\n * `createHash` there forced every browser consumer to wire a `node:crypto`\n * polyfill (`vite-plugin-node-polyfills`). The literal is verified to equal\n * `sha256(PRF_INPUT_LABEL)` in `constants.test.ts`, so the constant cannot drift\n * silently. Rotating the label (`-v2`) means recomputing these bytes.\n */\nexport const PRF_INPUT_SEED: Uint8Array = new Uint8Array([\n 241, 55, 252, 128, 200, 121, 231, 173, 149, 167, 104, 105, 124, 215, 6, 125, 113, 218, 113, 114, 238, 51, 232, 185,\n 156, 209, 26, 46, 78, 6, 58, 137,\n]);\n\n/** Spec §3.2. Used as HKDF-SHA256 info to derive the secp256k1 32-byte secret. */\nexport const EVM_HKDF_INFO = 'cere-wallet-evm-secp256k1-v1';\n","/**\n * Runtime support detection for WebAuthn + PRF.\n *\n * Spec §3.7. Real PRF capability is only knowable from a completed ceremony\n * (via `clientExtensionResults.prf.enabled` or `prf.results.first`). This\n * pre-check returns the best signal we can get without spending a credential.\n */\nexport interface PrfSupportResult {\n webAuthn: boolean;\n /**\n * Whether a platform authenticator (Touch ID / Windows Hello) is enrolled.\n * INFORMATIONAL ONLY — it is NOT a precondition for using the wallet, which\n * also works with roaming security keys and phone passkeys (via hybrid).\n */\n platformAuthenticator: boolean;\n /**\n * Best-effort PRF signal. `true` unless we can prove otherwise: modern\n * browsers expose `PublicKeyCredential.getClientCapabilities()`, which gives\n * a definitive `extensions:prf` answer. When that API is absent (older\n * browsers) we stay optimistic and let the ceremony be the final arbiter\n * (it throws `prf-unsupported` if the authenticator returns no PRF output).\n */\n prfPotentiallySupported: boolean;\n}\n\nexport async function detectPrfSupport(): Promise<PrfSupportResult> {\n const PKC = (globalThis as any).PublicKeyCredential;\n const webAuthn = typeof PKC !== 'undefined';\n if (!webAuthn) {\n return { webAuthn: false, platformAuthenticator: false, prfPotentiallySupported: false };\n }\n\n let platformAuthenticator = false;\n try {\n platformAuthenticator =\n typeof PKC.isUserVerifyingPlatformAuthenticatorAvailable === 'function' &&\n (await PKC.isUserVerifyingPlatformAuthenticatorAvailable());\n } catch {\n platformAuthenticator = false;\n }\n\n // Definitive PRF capability when the browser supports getClientCapabilities\n // (Chrome 133+, Safari 18+). Only a hard `false` blocks; absence keeps us\n // optimistic. We deliberately do NOT require a platform authenticator here.\n let prfPotentiallySupported = true;\n try {\n if (typeof PKC.getClientCapabilities === 'function') {\n const caps = await PKC.getClientCapabilities();\n if (caps && caps['extensions:prf'] === false) {\n prfPotentiallySupported = false;\n }\n }\n } catch {\n // Keep optimistic; the ceremony will surface prf-unsupported if needed.\n }\n\n return { webAuthn, platformAuthenticator, prfPotentiallySupported };\n}\n\n/**\n * Inspect a `PublicKeyCredential.getClientExtensionResults()` payload to\n * determine whether PRF actually worked in the ceremony. Spec §3.7 calls this\n * \"final answer; the pre-check above is best-effort only\".\n */\nexport function isPrfSupportedResult(extensions: {\n prf?: { enabled?: boolean; results?: { first?: Uint8Array } };\n}): boolean {\n const prf = extensions?.prf;\n if (!prf) return false;\n if (prf.enabled === true) return true;\n if (prf.results?.first instanceof Uint8Array) return true;\n return false;\n}\n","import bs58 from 'bs58';\nimport { blake2b } from '@noble/hashes/blake2b';\nimport { keccak_256 } from '@noble/hashes/sha3';\nimport { concatBytes } from '@noble/hashes/utils';\n\n/** Cere's SS58 network prefix. Spec §3.2. */\nexport const CERE_SS58_PREFIX = 54;\n\nconst SS58PRE = new TextEncoder().encode('SS58PRE');\n\n/**\n * SS58 address for the Cere network.\n *\n * SS58 spec (Substrate): for prefixes 0–63 the format is:\n * [prefix byte] || [32-byte pubkey] || [2-byte checksum]\n * where checksum = blake2b-512(\"SS58PRE\" || prefix || pubkey)[0..2].\n * The whole thing is base58-encoded. We use prefix 54 (Cere) which fits in\n * the 1-byte encoding (< 64).\n *\n * @param edPubkey 32-byte Ed25519 public key.\n */\nexport function deriveCereAddress(edPubkey: Uint8Array): string {\n if (edPubkey.length !== 32) {\n throw new Error(`expected 32-byte Ed25519 pubkey, got ${edPubkey.length}`);\n }\n const prefix = new Uint8Array([CERE_SS58_PREFIX]);\n const body = concatBytes(prefix, edPubkey);\n const checksum = blake2b(concatBytes(SS58PRE, body), { dkLen: 64 }).slice(0, 2);\n const payload = concatBytes(body, checksum);\n return bs58.encode(payload);\n}\n\n/**\n * Solana address: base58(Ed25519 pubkey). No prefix, no checksum.\n *\n * @param edPubkey 32-byte Ed25519 public key.\n */\nexport function deriveSolanaAddress(edPubkey: Uint8Array): string {\n if (edPubkey.length !== 32) {\n throw new Error(`expected 32-byte Ed25519 pubkey, got ${edPubkey.length}`);\n }\n return bs58.encode(edPubkey);\n}\n\n/**\n * Recover the raw 32-byte Ed25519 public key from a Solana address.\n *\n * The Solana address is `base58(edPubkey)` with no prefix or checksum\n * ({@link deriveSolanaAddress}), so a base58 decode yields the pubkey directly.\n * The public key is not secret — it IS the address — so this introduces no key\n * exposure beyond what the address already reveals; the session keys never\n * leave `SessionVault`. The same Ed25519 key backs the Cere SS58 address.\n *\n * @param solanaAddress base58 Solana address.\n * @returns the 32-byte Ed25519 public key.\n */\nexport function decodeEd25519Pubkey(solanaAddress: string): Uint8Array {\n const pubkey = bs58.decode(solanaAddress);\n if (pubkey.length !== 32) {\n throw new Error(`expected a 32-byte Ed25519 pubkey from the Solana address, got ${pubkey.length}`);\n }\n return pubkey;\n}\n\n/**\n * EVM address: '0x' + last 20 bytes of keccak256(secp256k1 uncompressed pubkey X||Y).\n *\n * Accepts either 64-byte (raw X||Y) or 65-byte (0x04||X||Y) input. The 0x04\n * prefix byte is stripped if present.\n *\n * @param secpPubkey secp256k1 public key.\n */\nexport function deriveEvmAddress(secpPubkey: Uint8Array): string {\n let xy: Uint8Array;\n if (secpPubkey.length === 65 && secpPubkey[0] === 0x04) {\n xy = secpPubkey.slice(1);\n } else if (secpPubkey.length === 64) {\n xy = secpPubkey;\n } else {\n throw new Error(`expected 64-byte (X||Y) or 65-byte (0x04||X||Y) secp256k1 pubkey, got ${secpPubkey.length}`);\n }\n const hash = keccak_256(xy);\n const last20 = hash.slice(12);\n return '0x' + Buffer.from(last20).toString('hex');\n}\n","import { ed25519 } from '@noble/curves/ed25519';\nimport { secp256k1 } from '@noble/curves/secp256k1';\nimport { hkdf } from '@noble/hashes/hkdf';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { EVM_HKDF_INFO } from './constants';\n\nexport interface DerivedKeys {\n /** Ed25519 32-byte secret key seed; used for Cere + Solana signing. */\n edSeed: Uint8Array;\n /** secp256k1 32-byte secret key; used for EVM signing. */\n secpKey: Uint8Array;\n /** 32-byte Ed25519 public key derived from edSeed. */\n edPubkey: Uint8Array;\n /** 65-byte uncompressed secp256k1 public key (0x04 || X || Y) derived from secpKey. */\n secpPubkeyUncompressed: Uint8Array;\n}\n\n/**\n * Derive the v2 session keys from a WebAuthn PRF output. Spec §3.2.\n *\n * edSeed = prfOutput\n * secpKey = HKDF-SHA256(prfOutput, salt=\"\", info=\"cere-wallet-evm-secp256k1-v1\", L=32)\n *\n * @param prfOutput 32-byte PRF output from `prf.results.first`.\n */\nexport function derivedKeys(prfOutput: Uint8Array): DerivedKeys {\n if (prfOutput.length !== 32) {\n throw new Error(`expected 32-byte PRF output, got ${prfOutput.length}`);\n }\n const edSeed = new Uint8Array(prfOutput); // copy to detach from caller's buffer\n const secpKey = hkdf(sha256, prfOutput, new Uint8Array(0), EVM_HKDF_INFO, 32);\n\n const edPubkey = ed25519.getPublicKey(edSeed);\n // @noble/curves exposes both compressed and uncompressed forms; we want uncompressed.\n const secpPubkeyUncompressed = secp256k1.getPublicKey(secpKey, false);\n\n return { edSeed, secpKey, edPubkey, secpPubkeyUncompressed };\n}\n","/**\n * Base64url (RFC 4648 §5) helpers used by the WebAuthn ceremony adapter.\n * Trailing `=` padding is stripped on encode; both padded and unpadded inputs\n * are accepted on decode.\n */\n\n/** Encode a Uint8Array or ArrayBuffer as a base64url string (no padding). */\nexport function bytesToB64u(input: Uint8Array | ArrayBuffer): string {\n const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);\n let bin = '';\n for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);\n const b64 = btoa(bin);\n return b64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/** Decode a base64url string into a Uint8Array. Padding is optional. */\nexport function b64uToBytes(s: string): Uint8Array {\n const b64 = s.replace(/-/g, '+').replace(/_/g, '/');\n const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);\n const bin = atob(padded); // throws DOMException on invalid input — correct behavior\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n","import { bytesToB64u, b64uToBytes } from './base64url';\n\n/**\n * `hints` is a top-level field on the WebAuthn creation/request options\n * (WebAuthn L3), but our TS DOM lib doesn't ship it yet. Declare-merge the\n * precise W3C union so the ceremony can request authenticator hints without\n * casts. Drop this augmentation once the workspace lib.dom.d.ts ships `hints`.\n */\ndeclare global {\n interface PublicKeyCredentialCreationOptions {\n hints?: ('client-device' | 'hybrid' | 'security-key')[];\n }\n interface PublicKeyCredentialRequestOptions {\n hints?: ('client-device' | 'hybrid' | 'security-key')[];\n }\n}\n\n/**\n * Adapter that runs WebAuthn ceremonies. Production implementations call\n * `navigator.credentials.create/get`; the test implementation in\n * `SoftAuthenticator` produces deterministic results without browser APIs.\n *\n * Spec §3.7. The `prfOutput` field is the result of the WebAuthn PRF extension\n * — present when the authenticator supports PRF, absent otherwise.\n */\nexport interface CeremonyAdapter {\n register(opts: RegisterOptions): Promise<RegisterResult>;\n login(opts: LoginOptions): Promise<LoginResult>;\n}\n\nexport interface RegisterOptions {\n rpId: string;\n challenge: Uint8Array;\n userHandle: Uint8Array;\n /** 32-byte PRF input (typically `PRF_INPUT_SEED`). */\n prfInput: Uint8Array;\n /**\n * Legacy single label. Used as a fallback for both `user.displayName` and\n * `user.name` when `name`/`email` are not supplied. Prefer `name` + `email`.\n */\n label?: string;\n /** Friendly account name → WebAuthn `user.displayName` (e.g. \"Alex Müller\"). */\n name?: string;\n /**\n * Account identifier → WebAuthn `user.name` (e.g. \"alex@example.com\"). This is\n * the field most OS/browser passkey managers surface to distinguish credentials.\n */\n email?: string;\n}\n\nexport interface RegisterResult {\n credentialId: string; // base64url\n clientDataJSON: string; // base64url\n attestationObject: string; // base64url\n transports: string[];\n /** Present iff PRF extension worked. */\n prfOutput?: Uint8Array;\n}\n\nexport interface LoginOptions {\n rpId: string;\n challenge: Uint8Array;\n /** Optional: if absent, the authenticator picks a credential. Present from us. */\n credentialId?: string;\n prfInput: Uint8Array;\n}\n\nexport interface LoginResult {\n credentialId: string;\n clientDataJSON: string;\n authenticatorData: string;\n signature: string;\n prfOutput?: Uint8Array;\n}\n\nexport interface WebAuthnCeremonyAdapterOptions {\n /**\n * Override for the WebAuthn credentials manager. Defaults to\n * `globalThis.navigator?.credentials`. Tests inject a mock.\n */\n credentials?: CredentialsContainer;\n}\n\n/**\n * Authenticator selection `hints` (WebAuthn L3, Chrome/Edge/Safari 18+).\n *\n * These bias the browser's passkey picker toward authenticators that\n * reliably support the PRF extension — platform authenticators (Touch ID /\n * Windows Hello / Android), phone passkeys (hybrid), and security keys.\n *\n * Hints are *preferences, not filters*: a software password-manager passkey\n * (e.g. Bitwarden, 1Password) still appears in the picker, just de-prioritized.\n * On a machine with Touch ID enrolled this makes \"This device\" the default\n * choice, so users no longer land on Bitwarden's passkey (which can't satisfy\n * PRF and fails the ceremony with `prf-unsupported`). The post-ceremony\n * `prfOutput` check remains the real capability gate.\n */\nconst PRF_CAPABLE_HINTS = ['client-device', 'hybrid', 'security-key'] as const;\n\n/**\n * Production WebAuthn ceremony adapter. Calls `navigator.credentials.create/get`\n * with the PRF extension. Spec §3.7.\n *\n * - `register`: requires a platform authenticator, user verification, Ed25519\n * (`alg: -8`), and the PRF extension with the wallet's input seed.\n * - `login`: allows a specific credentialId if known; otherwise lets the\n * authenticator pick (resident key).\n *\n * Returned ArrayBuffers are base64url-encoded for API transport.\n */\nexport class WebAuthnCeremonyAdapter implements CeremonyAdapter {\n private readonly credentials?: CredentialsContainer;\n\n constructor(opts: WebAuthnCeremonyAdapterOptions = {}) {\n this.credentials = opts.credentials ?? (globalThis as any).navigator?.credentials;\n }\n\n async register(opts: RegisterOptions): Promise<RegisterResult> {\n if (!this.credentials) {\n throw new Error('WebAuthnCeremonyAdapter: navigator.credentials is unavailable');\n }\n const cred = (await this.credentials.create({\n publicKey: {\n challenge: opts.challenge,\n rp: { id: opts.rpId, name: opts.rpId },\n user: {\n id: opts.userHandle,\n // WebAuthn convention: `name` is the account identifier (email), shown\n // by passkey managers; `displayName` is the friendly name. Fall back to\n // the legacy single `label`, then the product defaults.\n name: opts.email ?? opts.label ?? 'scp-wallet',\n displayName: opts.name ?? opts.label ?? 'SCP Wallet',\n },\n pubKeyCredParams: [\n { alg: -8, type: 'public-key' }, // EdDSA (Ed25519 security keys) — preferred\n { alg: -7, type: 'public-key' }, // ES256 (Apple/Windows/Android platform authenticators)\n ],\n authenticatorSelection: {\n // No `authenticatorAttachment`: allow platform authenticators\n // (Touch ID / Windows Hello), roaming security keys, and phone\n // passkeys via hybrid — PRF works across all of them, and the\n // pubKeyCredParams above explicitly prefer Ed25519 security keys.\n // The post-ceremony PRF result is the real capability gate.\n userVerification: 'required',\n residentKey: 'preferred',\n },\n // Bias the picker toward PRF-capable authenticators so the user\n // doesn't default onto a password-manager passkey. See PRF_CAPABLE_HINTS.\n hints: [...PRF_CAPABLE_HINTS],\n timeout: 60_000,\n extensions: { prf: { eval: { first: opts.prfInput } } } as AuthenticationExtensionsClientInputs,\n },\n })) as PublicKeyCredential | null;\n if (!cred) {\n throw new Error('WebAuthnCeremonyAdapter.register: credentials.create returned null');\n }\n\n const response = cred.response as AuthenticatorAttestationResponse;\n const transports = typeof response.getTransports === 'function' ? response.getTransports() : [];\n const ext = cred.getClientExtensionResults() as { prf?: { results?: { first?: ArrayBuffer } } };\n const prfFirst = ext?.prf?.results?.first;\n\n return {\n credentialId: cred.id,\n clientDataJSON: bytesToB64u(response.clientDataJSON),\n attestationObject: bytesToB64u(response.attestationObject),\n transports,\n prfOutput: prfFirst ? new Uint8Array(prfFirst) : undefined,\n };\n }\n\n async login(opts: LoginOptions): Promise<LoginResult> {\n if (!this.credentials) {\n throw new Error('WebAuthnCeremonyAdapter: navigator.credentials is unavailable');\n }\n const cred = (await this.credentials.get({\n publicKey: {\n challenge: opts.challenge,\n rpId: opts.rpId,\n allowCredentials: opts.credentialId ? [{ id: b64uToBytes(opts.credentialId), type: 'public-key' }] : undefined,\n userVerification: 'required',\n // Bias first-login-on-device (no allowCredentials) toward PRF-capable\n // authenticators, mirroring register. See PRF_CAPABLE_HINTS.\n hints: [...PRF_CAPABLE_HINTS],\n timeout: 60_000,\n extensions: { prf: { eval: { first: opts.prfInput } } } as AuthenticationExtensionsClientInputs,\n },\n })) as PublicKeyCredential | null;\n if (!cred) {\n throw new Error('WebAuthnCeremonyAdapter.login: credentials.get returned null');\n }\n\n const response = cred.response as AuthenticatorAssertionResponse;\n const ext = cred.getClientExtensionResults() as { prf?: { results?: { first?: ArrayBuffer } } };\n const prfFirst = ext?.prf?.results?.first;\n\n return {\n credentialId: cred.id,\n clientDataJSON: bytesToB64u(response.clientDataJSON),\n authenticatorData: bytesToB64u(response.authenticatorData),\n signature: bytesToB64u(response.signature),\n prfOutput: prfFirst ? new Uint8Array(prfFirst) : undefined,\n };\n }\n}\n","import { ed25519 } from '@noble/curves/ed25519';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { hkdf } from '@noble/hashes/hkdf';\nimport { concatBytes } from '@noble/hashes/utils';\nimport type { CeremonyAdapter, RegisterOptions, RegisterResult, LoginOptions, LoginResult } from './ceremony';\n\n/** base64url encode without padding. */\nfunction b64u(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\ninterface StoredCredential {\n credentialId: string;\n edSeed: Uint8Array;\n prfSecret: Uint8Array;\n}\n\nexport interface SoftAuthenticatorOptions {\n /**\n * If provided, derive the per-registration `edSeed`, `prfSecret`, and\n * `credentialId` deterministically from this seed (mixed with a monotonic\n * counter so multiple register() calls on the same instance still produce\n * distinct credentials). Used by Playwright e2e specs that pin server-side\n * address stubs against the SoftAuthenticator's output. Production code\n * never sets this — the `useSoft` branch in `createIdentity.ts` is itself\n * forbidden in production builds.\n *\n * Default: undefined (each register() draws fresh CSPRNG bytes, matching\n * the pre-cycle-8.1 behavior used by unit tests in `ceremony.test.ts`).\n */\n seed?: string;\n\n /**\n * When the caller invokes login() without a credentialId, fall back to the\n * most-recently-registered credential in memory. Default: false (login()\n * throws when credentialId is missing, matching pre-cycle-8 behavior).\n *\n * Required for e2e flows that simulate the user signing back in after the\n * vault has been cleared (post-logout re-login): the wallet has discarded\n * the credentialId locally, but the soft authenticator's in-memory creds\n * map still holds the registration, and the spec test needs login() to\n * succeed anyway. Production code must never enable this — real WebAuthn's\n * no-allowCredentials path shows an OS credential picker; the fallback\n * here is a deterministic test convenience, not a real picker, so leaving\n * it on by default would silently let a single registered credential\n * impersonate any login.\n */\n fallbackToLastRegistered?: boolean;\n}\n\n/**\n * Deterministic in-memory WebAuthn authenticator for tests. NOT for production.\n *\n * - Stores credentials in a private Map keyed by credentialId.\n * - `prfOutput` is computed as HKDF-SHA256(prfSecret, salt=\"\", info=prfInput, L=32),\n * so for a given credential and prfInput the result is stable across login calls.\n * - `signature` is a valid Ed25519 signature over `authenticatorData || sha256(clientDataJSON)`.\n *\n * The output shapes match the real WebAuthn response well enough that the\n * `Identity` integration tests (Task 6) can drive register → login → derive\n * → cross-check without a real browser.\n */\nexport class SoftAuthenticator implements CeremonyAdapter {\n private readonly creds: Map<string, StoredCredential> = new Map();\n private readonly seedBytes: Uint8Array | null;\n private readonly fallbackToLastRegistered: boolean;\n /** Monotonic counter mixed into the seed-derived material so successive\n * register() calls on a seeded instance produce distinct credentials. */\n private regCounter = 0;\n\n constructor(opts: SoftAuthenticatorOptions = {}) {\n this.seedBytes = opts.seed != null ? new TextEncoder().encode(opts.seed) : null;\n this.fallbackToLastRegistered = opts.fallbackToLastRegistered === true;\n }\n\n async register(opts: RegisterOptions): Promise<RegisterResult> {\n // Generate per-credential material. When seeded, derive deterministically\n // via HKDF-SHA256(seed, salt=<counter>, info=<purpose>) so the e2e specs\n // can pin server-side address stubs against the resulting addresses.\n let edSeed: Uint8Array;\n let prfSecret: Uint8Array;\n let credentialIdBytes: Uint8Array;\n if (this.seedBytes) {\n const salt = new TextEncoder().encode(`reg-${this.regCounter++}`);\n edSeed = hkdf(sha256, this.seedBytes, salt, 'ed-seed', 32);\n prfSecret = hkdf(sha256, this.seedBytes, salt, 'prf-secret', 32);\n credentialIdBytes = hkdf(sha256, this.seedBytes, salt, 'credential-id', 16);\n } else {\n edSeed = ed25519.utils.randomPrivateKey();\n prfSecret = ed25519.utils.randomPrivateKey(); // 32 random bytes\n credentialIdBytes = ed25519.utils.randomPrivateKey().slice(0, 16); // 16-byte id\n }\n const credentialId = b64u(credentialIdBytes);\n\n this.creds.set(credentialId, { credentialId, edSeed, prfSecret });\n\n // Compose the WebAuthn-shaped artifacts. These are NOT spec-correct attestations;\n // they're test fixtures whose only requirement is to round-trip through identity.\n const clientData = {\n type: 'webauthn.create',\n challenge: b64u(opts.challenge),\n origin: `https://${opts.rpId}`,\n crossOrigin: false,\n };\n const clientDataJSON = new TextEncoder().encode(JSON.stringify(clientData));\n const attestationObject = new Uint8Array([0xaa]); // placeholder; not parsed by client tests\n\n const prfOutput = this.computePrf(prfSecret, opts.prfInput);\n\n return {\n credentialId,\n clientDataJSON: b64u(clientDataJSON),\n attestationObject: b64u(attestationObject),\n transports: ['internal'],\n prfOutput,\n };\n }\n\n async login(opts: LoginOptions): Promise<LoginResult> {\n // Two modes:\n // 1. Explicit credentialId (the common path, including IdentityImpl's\n // warm-vault re-mint flow): look it up and throw on miss.\n // 2. No credentialId: by default this is an error (pre-cycle-8 behaviour),\n // because a silent \"pick the last one\" is a security trap if it ever\n // leaks into production. Callers that genuinely need the fallback\n // (Playwright post-logout re-login) opt in via\n // `new SoftAuthenticator({ fallbackToLastRegistered: true })`.\n let stored: StoredCredential | undefined;\n if (opts.credentialId) {\n stored = this.creds.get(opts.credentialId);\n if (!stored) {\n throw new Error(`SoftAuthenticator.login: unknown credential ${opts.credentialId}`);\n }\n } else {\n if (!this.fallbackToLastRegistered) {\n throw new Error(\n 'SoftAuthenticator.login: credentialId is required ' +\n '(set `fallbackToLastRegistered: true` in the constructor to opt into the e2e convenience of picking the most-recently-registered credential)',\n );\n }\n const all = Array.from(this.creds.values());\n stored = all[all.length - 1];\n if (!stored) {\n throw new Error('SoftAuthenticator.login: no credentials registered');\n }\n }\n\n const clientData = {\n type: 'webauthn.get',\n challenge: b64u(opts.challenge),\n origin: `https://${opts.rpId}`,\n crossOrigin: false,\n };\n const clientDataJSON = new TextEncoder().encode(JSON.stringify(clientData));\n\n // Minimal authenticatorData: 32-byte rpIdHash || 1-byte flags || 4-byte signCount.\n // We don't need a real rpIdHash for client-side tests.\n const authenticatorData = new Uint8Array(37);\n authenticatorData[32] = 0x05; // UP + UV flags\n\n const clientDataHash = sha256(clientDataJSON);\n const toSign = concatBytes(authenticatorData, clientDataHash);\n const signature = ed25519.sign(toSign, stored.edSeed);\n\n const prfOutput = this.computePrf(stored.prfSecret, opts.prfInput);\n\n return {\n credentialId: stored.credentialId,\n clientDataJSON: b64u(clientDataJSON),\n authenticatorData: b64u(authenticatorData),\n signature: b64u(signature),\n prfOutput,\n };\n }\n\n /** Public-key bytes for a credential. Used by integration tests for cross-checks. */\n getEd25519PublicKey(credentialId: string): Uint8Array {\n const stored = this.creds.get(credentialId);\n if (!stored) throw new Error(`unknown credential ${credentialId}`);\n return ed25519.getPublicKey(stored.edSeed);\n }\n\n private computePrf(prfSecret: Uint8Array, prfInput: Uint8Array): Uint8Array {\n // Deterministic per (prfSecret, prfInput) — matches the spirit of how a real\n // authenticator's PRF extension works (HKDF-style mixing of a per-credential\n // secret with the caller's input). NOT identical to any real authenticator.\n return hkdf(sha256, prfSecret, new Uint8Array(0), prfInput, 32);\n }\n}\n","import { ed25519 } from '@noble/curves/ed25519';\nimport { secp256k1 } from '@noble/curves/secp256k1';\nimport type { DerivedKeys } from './derive';\nimport type { Addresses, Chain } from './types';\n\nexport interface VaultSnapshot {\n addresses: Addresses;\n credentialId: string;\n}\n\nexport interface SessionVault {\n isOpen(): boolean;\n /** Public-safe view: never includes raw keys. */\n snapshot(): VaultSnapshot | null;\n /** Open the vault with derived keys + the addresses they yielded + the credentialId. */\n set(args: { keys: DerivedKeys; addresses: Addresses; credentialId: string }): void;\n /** Drop the keys and address state. Safe to call when already closed. */\n clear(): void;\n /**\n * Sign a payload with the chain-appropriate key. Spec §3.6 invariant:\n * this is the only way the rest of the package can reach the keys.\n *\n * - 'cere' / 'solana': Ed25519 signature over the raw payload (64 bytes).\n * - 'evm': secp256k1 ECDSA over the raw payload (65 bytes: r || s || v).\n * The caller (an EVM signer) is responsible for framing (e.g. EIP-191)\n * and hashing (keccak256) before passing the digest as the payload.\n */\n sign(chain: Chain, payload: Uint8Array): Promise<Uint8Array>;\n}\n\n/**\n * Closure-encapsulated session vault. The keys live in the closure variables\n * `state.edSeed` / `state.secpKey` and are not reachable from outside.\n *\n * Spec §3.4. Lifetime: from `set()` until `clear()` or tab unload.\n */\nexport function createSessionVault(): SessionVault {\n let state: {\n edSeed: Uint8Array;\n secpKey: Uint8Array;\n addresses: Addresses;\n credentialId: string;\n } | null = null;\n\n return {\n isOpen() {\n return state !== null;\n },\n snapshot() {\n if (!state) return null;\n return { addresses: state.addresses, credentialId: state.credentialId };\n },\n set({ keys, addresses, credentialId }) {\n state = {\n edSeed: new Uint8Array(keys.edSeed),\n secpKey: new Uint8Array(keys.secpKey),\n addresses,\n credentialId,\n };\n },\n clear() {\n if (state) {\n state.edSeed.fill(0);\n state.secpKey.fill(0);\n }\n state = null;\n },\n async sign(chain, payload) {\n if (!state) {\n throw new Error('SessionVault: closed — no session keys available');\n }\n if (chain === 'cere' || chain === 'solana') {\n return ed25519.sign(payload, state.edSeed);\n }\n if (chain === 'evm') {\n // Caller (chain-specific signer) is responsible for framing + hashing.\n // We sign the payload directly and return 65 bytes: r (32) || s (32) || v (1),\n // where v is the recovery byte (0 or 1). Spec §3.6 invariant 1 holds —\n // the secpKey never leaves this closure.\n const sig = secp256k1.sign(payload, state.secpKey);\n const compact = sig.toCompactRawBytes(); // 64 bytes (r || s)\n const v = sig.recovery; // 0 or 1\n const out = new Uint8Array(65);\n out.set(compact, 0);\n out[64] = v;\n return out;\n }\n throw new Error(`SessionVault.sign: unknown chain \"${String(chain)}\"`);\n },\n };\n}\n","import { ed25519 } from '@noble/curves/ed25519';\nimport { secp256k1 } from '@noble/curves/secp256k1';\nimport type { DerivedKeys } from './derive';\n\nexport interface RegistrationProof {\n /** ed25519 signature (64B) over the raw challenge — proves cere + solana. */\n identitySig: Uint8Array;\n /** secp256k1 uncompressed pubkey (65B, 0x04 || X || Y). */\n evmPubkey: Uint8Array;\n /** secp256k1 compact r||s signature (64B) over the raw challenge. */\n evmSig: Uint8Array;\n}\n\n/**\n * Sign the single-use registration challenge with the PRF-derived keys to\n * prove possession of the chain identity. The server (wallet-api)\n * verifies these against the claimed addresses. Spec §3.3 (revised).\n *\n * - ed25519: sign the raw 32-byte challenge (EdDSA hashes internally).\n * - secp256k1: ECDSA over the 32-byte challenge treated as the message digest\n * (matches the server's `ecdsa.Verify(challenge, pubkey)`). The challenge is\n * 32 random single-use bytes, so it is passed directly as the prehashed\n * digest — NOT re-hashed (no `{ prehash: true }`). low-S is enforced by\n * @noble's default, so the compact form is canonical.\n */\nexport function signRegistrationProof(keys: DerivedKeys, challenge: Uint8Array): RegistrationProof {\n const identitySig = ed25519.sign(challenge, keys.edSeed);\n const evmSig = secp256k1.sign(challenge, keys.secpKey).toCompactRawBytes();\n return { identitySig, evmPubkey: keys.secpPubkeyUncompressed, evmSig };\n}\n","/**\n * Cross-tab session synchronisation helper.\n *\n * Wraps a `BroadcastChannel` on a fixed channel name so all tabs of the same\n * origin can coordinate. Today we only use it for `{ type: 'logout' }` — when\n * one tab logs out, every other tab clears its in-memory vault + JWT so its\n * MobX-reactive UI immediately reflects the logged-out state.\n *\n * The channel name must match `BROADCAST_CHANNEL_NAME` in\n * `packages/embed-sdk/src/protocol.ts` ('scp-wallet-v2'). Defined here as a\n * local constant rather than imported to avoid a circular dep between\n * `@cef-ai/wallet-identity` and `@cef-ai/wallet`. The name is part of the\n * wire protocol — if you change one, change the other (and bump the major).\n *\n * Spec §9.4 (cross-tab session sharing).\n */\n\n/** MUST match `packages/embed-sdk/src/protocol.ts` `BROADCAST_CHANNEL_NAME`. */\nconst CHANNEL_NAME = 'scp-wallet-v2';\n\nexport type CrossTabMessage = { type: 'logout' };\n\nexport class CrossTabSync {\n private channel: BroadcastChannel | null;\n private readonly listeners = new Set<(msg: CrossTabMessage) => void>();\n\n constructor() {\n // SSR / Node-without-BroadcastChannel safety. The class becomes a no-op\n // (broadcast() returns silently, subscribe() returns a real unsub that\n // just empties the local set, close() is a no-op).\n if (typeof BroadcastChannel === 'undefined') {\n this.channel = null;\n return;\n }\n this.channel = new BroadcastChannel(CHANNEL_NAME);\n this.channel.onmessage = (ev: MessageEvent) => {\n const msg = ev.data as CrossTabMessage | undefined;\n if (!msg || typeof msg !== 'object' || typeof (msg as { type?: unknown }).type !== 'string') {\n return;\n }\n for (const fn of this.listeners) {\n try {\n fn(msg);\n } catch {\n // Listener errors must not break the channel for other listeners.\n }\n }\n };\n }\n\n /** Broadcast a message to all other tabs (and not to this tab — that's the\n * BroadcastChannel spec). The originating tab is expected to have already\n * applied the state change locally before broadcasting. */\n broadcast(msg: CrossTabMessage): void {\n this.channel?.postMessage(msg);\n }\n\n /** Subscribe to messages from other tabs. Returns an unsubscribe function. */\n subscribe(fn: (msg: CrossTabMessage) => void): () => void {\n this.listeners.add(fn);\n return () => {\n this.listeners.delete(fn);\n };\n }\n\n /**\n * Close the underlying channel and drop all listeners. Idempotent.\n *\n * Sets `this.channel = null` so subsequent `broadcast()` calls become a\n * no-op (the `?.postMessage` short-circuits) and we don't accidentally\n * postMessage on a closed BroadcastChannel (which throws InvalidStateError\n * in some implementations).\n */\n close(): void {\n this.channel?.close();\n this.channel = null;\n this.listeners.clear();\n }\n}\n","import { observable, runInAction } from 'mobx';\nimport { ed25519 } from '@noble/curves/ed25519';\nimport { secp256k1 } from '@noble/curves/secp256k1';\nimport { WalletError, type ApiClient, type Addresses as ApiAddresses } from '@cef-ai/wallet-api-client';\nimport type { Identity, Addresses, IdentitySession, SessionHandoff } from './types';\nimport type { CeremonyAdapter } from './ceremony';\nimport { createSessionVault, type SessionVault } from './vault';\nimport { derivedKeys, type DerivedKeys } from './derive';\nimport { signRegistrationProof } from './pop';\nimport { bytesToB64u } from './base64url';\nimport { deriveCereAddress, deriveSolanaAddress, deriveEvmAddress } from './addresses';\nimport { PRF_INPUT_SEED } from './constants';\nimport { CrossTabSync } from './CrossTabSync';\n\nexport interface IdentityImplOptions {\n apiClient: ApiClient;\n ceremony: CeremonyAdapter;\n}\n\n/** Minimum slack before expiry where we treat the JWT as \"needs refresh\". */\nconst JWT_REFRESH_SLACK_MS = 60_000;\n\nfunction decodeJwtExpMs(token: string): number {\n const parts = token.split('.');\n if (parts.length < 2) {\n throw new WalletError('validation', 'JWT does not have 3 parts');\n }\n const payloadB64 = parts[1];\n const payload = JSON.parse(Buffer.from(payloadB64.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8'));\n if (typeof payload.exp !== 'number') {\n throw new WalletError('validation', 'JWT missing exp');\n }\n return payload.exp * 1000;\n}\n\nfunction b64uDecode(s: string): Uint8Array {\n return Uint8Array.from(Buffer.from(s.replace(/-/g, '+').replace(/_/g, '/'), 'base64'));\n}\n\nexport class IdentityImpl implements Identity, SessionHandoff {\n private readonly vault: SessionVault = createSessionVault();\n private cachedJwt: { token: string; expMs: number } | null = null;\n /** Server-returned credential ID (used as the public identity.credentialId). */\n private serverCredentialId: string | null = null;\n /**\n * Raw derived keys for the CURRENT session, captured at every `vault.set()`\n * call site (register / login / adoptSession). The vault deliberately hides\n * the seeds behind its closure (Spec §3.6 invariant 1); this field is the\n * single, explicit, same-origin-only seam that lets `exportSession()` hand a\n * live session to a sibling wallet-origin surface (the persistent iframe).\n * Cleared on logout / cross-tab logout alongside the vault. Never serialised\n * to storage and never sent to a host page.\n */\n private sessionKeys: DerivedKeys | null = null;\n /**\n * Absolute expiry (epoch ms) for `exportSession()`. Tracks the cached JWT\n * expiry after register/login; set from the adopted session in adoptSession.\n */\n private sessionExpMs: number | null = null;\n\n // Public observable state — derived from vault snapshot + serverCredentialId.\n // Kept in sync via syncPublicState() so React `observer()` wrappers around\n // components reading isAuthenticated/addresses/credentialId actually re-render\n // on register/login/logout. The vault itself stays non-observable (security:\n // the closure-encapsulated keys must not be tracked by MobX).\n private readonly _isAuthenticated = observable.box(false);\n // `deep: false` keeps the stored value a plain object instead of wrapping it\n // in a MobX proxy. addresses is a wholesale-replaced snapshot (never mutated\n // field-by-field), so deep observability buys nothing — and a proxy is not\n // structured-cloneable, which breaks `postMessage` when the embed popup\n // bridge sends addresses to the host page (wallet:login:ok).\n private readonly _addresses = observable.box<Addresses | null>(null, { deep: false });\n private readonly _credentialId = observable.box<string | null>(null);\n\n /** Cross-tab logout coordination via BroadcastChannel('scp-wallet-v2'). */\n private readonly crossTabSync = new CrossTabSync();\n /** Handle to detach the cross-tab logout subscriber on dispose(). */\n private readonly crossTabSyncUnsubscribe: () => void;\n /** Idempotency flag for dispose(). */\n private disposed = false;\n\n constructor(private readonly opts: IdentityImplOptions) {\n // Hydrate the boxes from any existing vault snapshot. Currently the vault\n // starts closed, so this is a no-op — but it's the right call site for a\n // future BroadcastChannel-restored session.\n this.syncPublicState();\n // Cross-tab logout: when another tab broadcasts {type:'logout'}, mirror it\n // locally so this tab's MobX-bound UI re-renders without a page reload.\n // The `vault.isOpen()` guard prevents the originating tab from\n // double-clearing — by the time its own listener fires (BroadcastChannel\n // doesn't deliver to the sender, but defence-in-depth), the vault is\n // already closed. The guard also makes the listener a no-op for tabs\n // that were already signed out.\n //\n // Capture the unsubscribe handle so dispose() can detach this subscriber\n // before closing the channel — without this, a long-lived test suite or\n // a hot-reloaded SPA leaks listener closures into the next IdentityImpl.\n this.crossTabSyncUnsubscribe = this.crossTabSync.subscribe((msg) => {\n if (msg.type === 'logout' && this.vault.isOpen()) {\n this.vault.clear();\n this.cachedJwt = null;\n this.serverCredentialId = null;\n this.sessionKeys = null;\n this.sessionExpMs = null;\n this.syncPublicState();\n }\n });\n }\n\n get isAuthenticated(): boolean {\n return this._isAuthenticated.get();\n }\n\n get addresses(): Addresses | null {\n return this._addresses.get();\n }\n\n /**\n * Returns the server-returned credentialId from the last register/login ceremony.\n * The vault internally tracks the authenticator-generated credential ID for login use.\n */\n get credentialId(): string | null {\n return this._credentialId.get();\n }\n\n /**\n * Read the (non-observable) vault snapshot + cached server credential ID and\n * push the values into the three public observable boxes. Call after any\n * mutation that could change the public derived state: register, login,\n * logout, or constructor (hydration).\n */\n private syncPublicState(): void {\n const snap = this.vault.snapshot();\n runInAction(() => {\n this._isAuthenticated.set(this.vault.isOpen());\n this._addresses.set(snap?.addresses ?? null);\n this._credentialId.set(this.serverCredentialId);\n });\n }\n\n async register(opts: { label?: string; name?: string; email?: string } = {}): Promise<void> {\n // 1. Start ceremony — get the challenge from the API.\n // The client-derived addresses are determined post-ceremony from the PRF\n // output. The server only needs them at /finish; at /start we send a\n // placeholder that the server ignores. (Per API.md, /start accepts the\n // addresses field but the load-bearing check is at /finish.)\n const start = await this.opts.apiClient.passkey.registerStart({\n addresses: { cere: '5GplaceholderClientCannotKnowYet' } as any,\n label: opts.label,\n });\n\n // 2. Run the ceremony.\n const cer = await this.opts.ceremony.register({\n rpId: start.rpId,\n challenge: b64uDecode(start.challenge),\n userHandle: b64uDecode(start.userHandle),\n prfInput: PRF_INPUT_SEED,\n label: opts.label,\n name: opts.name,\n email: opts.email,\n });\n if (!cer.prfOutput) {\n throw new WalletError('prf-unsupported', 'authenticator did not return a PRF output');\n }\n\n // 3. Derive keys + addresses locally.\n const keys = derivedKeys(cer.prfOutput);\n const clientAddresses: Addresses = {\n cere: deriveCereAddress(keys.edPubkey),\n solana: deriveSolanaAddress(keys.edPubkey),\n evm: deriveEvmAddress(keys.secpPubkeyUncompressed),\n };\n\n // 4. Prove possession of the PRF-derived chain keys by signing the\n // single-use challenge. The server binds addresses.{cere,solana,evm}\n // to these signatures (proof-of-possession), not to the WebAuthn\n // credential key. Spec §3.3 (revised).\n const proof = signRegistrationProof(keys, b64uDecode(start.challenge));\n\n // 5. Finish ceremony with the API.\n const finish = await this.opts.apiClient.passkey.registerFinish({\n challengeId: start.challengeId,\n clientDataJSON: cer.clientDataJSON,\n attestationObject: cer.attestationObject,\n addresses: clientAddresses,\n transports: cer.transports,\n identitySig: bytesToB64u(proof.identitySig),\n evmPubkey: bytesToB64u(proof.evmPubkey),\n evmSig: bytesToB64u(proof.evmSig),\n });\n\n // 6. Cross-check returned addresses against locally-derived. Spec §3.3.\n this.crossCheckAddresses(clientAddresses, finish.addresses);\n\n // 7. Install the vault + cache the JWT.\n // The vault stores the authenticator-generated credential ID (cer.credentialId)\n // so subsequent login() calls can pass it back to the same authenticator.\n // The server-returned credential ID is tracked separately for the public getter.\n this.vault.set({\n keys,\n addresses: clientAddresses,\n credentialId: cer.credentialId,\n });\n this.serverCredentialId = finish.credentialId;\n this.cachedJwt = { token: finish.token, expMs: decodeJwtExpMs(finish.token) };\n // Capture the session for a possible same-origin handoff (register popup →\n // iframe). See `sessionKeys` field + `exportSession()`.\n this.sessionKeys = keys;\n this.sessionExpMs = this.cachedJwt.expMs;\n this.syncPublicState();\n }\n\n async login(): Promise<void> {\n const start = await this.opts.apiClient.passkey.loginStart();\n\n const cer = await this.opts.ceremony.login({\n rpId: start.rpId,\n challenge: b64uDecode(start.challenge),\n credentialId: this.vault.snapshot()?.credentialId,\n prfInput: PRF_INPUT_SEED,\n });\n if (!cer.prfOutput) {\n throw new WalletError('prf-unsupported', 'authenticator did not return a PRF output');\n }\n\n const keys = derivedKeys(cer.prfOutput);\n const clientAddresses: Addresses = {\n cere: deriveCereAddress(keys.edPubkey),\n solana: deriveSolanaAddress(keys.edPubkey),\n evm: deriveEvmAddress(keys.secpPubkeyUncompressed),\n };\n\n const finish = await this.opts.apiClient.passkey.loginFinish({\n challengeId: start.challengeId,\n credentialId: cer.credentialId,\n clientDataJSON: cer.clientDataJSON,\n authenticatorData: cer.authenticatorData,\n signature: cer.signature,\n });\n\n this.crossCheckAddresses(clientAddresses, finish.addresses);\n\n this.vault.set({\n keys,\n addresses: clientAddresses,\n credentialId: cer.credentialId,\n });\n this.serverCredentialId = finish.credentialId;\n this.cachedJwt = { token: finish.token, expMs: decodeJwtExpMs(finish.token) };\n this.sessionKeys = keys;\n this.sessionExpMs = this.cachedJwt.expMs;\n this.syncPublicState();\n }\n\n logout(): void {\n this.vault.clear();\n this.cachedJwt = null;\n this.serverCredentialId = null;\n this.sessionKeys = null;\n this.sessionExpMs = null;\n this.syncPublicState();\n // Broadcast AFTER clearing local state so other tabs converge on the same\n // post-logout snapshot. BroadcastChannel does not deliver to the sender,\n // so this tab won't re-enter the subscriber callback above.\n this.crossTabSync.broadcast({ type: 'logout' });\n }\n\n async getJwt(): Promise<string> {\n if (this.cachedJwt && Date.now() < this.cachedJwt.expMs - JWT_REFRESH_SLACK_MS) {\n return this.cachedJwt.token;\n }\n if (!this.vault.isOpen()) {\n throw new WalletError('unauthorized', 'session is closed; call register() or login() first');\n }\n // Vault is warm but JWT is stale — re-run login to mint a fresh JWT.\n await this.login();\n return this.cachedJwt!.token;\n }\n\n /** Helper used by callers (e.g. ApiClient.getAuthToken). */\n getCachedJwtForApiClient(): string | null {\n return this.cachedJwt?.token ?? null;\n }\n\n /**\n * Returns the session vault. Used by popup-side `wallet:sign` handlers\n * which need direct access to `vault.sign(chain, payload)`. NOT for host-\n * side use — keys never reach the host. Spec §3.6 invariant 1.\n */\n getVault(): SessionVault {\n return this.vault;\n }\n\n /**\n * Export the current session for a same-origin handoff (register popup →\n * iframe). Returns `null` unless the vault is open, we captured the derived\n * keys, and the session has not expired. The raw seeds ARE included — this is\n * the deliberate, narrow seam described on `IdentitySession`; callers must\n * only pass the result across the same-origin wallet BroadcastChannel.\n */\n exportSession(): IdentitySession | null {\n if (!this.vault.isOpen() || !this.sessionKeys) return null;\n const snap = this.vault.snapshot();\n if (!snap) return null;\n const expMs = this.sessionExpMs ?? this.cachedJwt?.expMs ?? 0;\n if (expMs <= Date.now()) return null;\n return {\n // Copy so the caller (and the structured clone the BroadcastChannel makes)\n // cannot mutate our live key buffers.\n edSeed: new Uint8Array(this.sessionKeys.edSeed),\n secpKey: new Uint8Array(this.sessionKeys.secpKey),\n addresses: snap.addresses,\n expMs,\n };\n }\n\n /**\n * Adopt a session handed over from a same-origin sibling surface. Rebuilds\n * the `DerivedKeys` from the transported seeds (public keys are recomputed\n * from the seeds; we do NOT re-run the HKDF step so the exact transported\n * secpKey is preserved), opens the vault, and syncs public state — no\n * ceremony. There is no credentialId in the handoff, so login() would need a\n * fresh ceremony; but the session key is warm, so `sign`/`claim` (which use\n * the vault directly, no JWT) work immediately.\n */\n adoptSession(session: IdentitySession): void {\n if (session.expMs <= Date.now()) return;\n const edSeed = new Uint8Array(session.edSeed);\n const secpKey = new Uint8Array(session.secpKey);\n const keys: DerivedKeys = {\n edSeed,\n secpKey,\n edPubkey: ed25519.getPublicKey(edSeed),\n secpPubkeyUncompressed: secp256k1.getPublicKey(secpKey, false),\n };\n this.vault.set({\n keys,\n addresses: session.addresses,\n // No credentialId travels in the handoff. login() (which needs it) will\n // fall back to a fresh ceremony; the adopted key covers ceremony-free ops.\n credentialId: '',\n });\n this.sessionKeys = keys;\n this.sessionExpMs = session.expMs;\n this.syncPublicState();\n }\n\n /**\n * Release the BroadcastChannel + cross-tab subscriber. Idempotent.\n *\n * Called by:\n * - WalletProvider's useEffect cleanup (SPA unmount / apiBaseUrl change)\n * - test afterEach hooks (prevent listener leakage across tests)\n *\n * After dispose(), the identity is unusable: register/login/logout/getJwt\n * still execute their normal logic, but cross-tab broadcasts no longer\n * propagate (channel is closed) and the local subscriber is detached.\n * The vault remains cleared (logout() runs locally). Spec §9.4 lifecycle.\n */\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n // Detach subscriber first so any in-flight onmessage from this channel\n // does not re-enter the now-closing identity.\n this.crossTabSyncUnsubscribe();\n this.crossTabSync.close();\n }\n\n // ---- private --------------------------------------------------------------\n\n private crossCheckAddresses(client: Addresses, server: ApiAddresses | undefined): void {\n if (!server) {\n throw new WalletError('derivation-mismatch', 'API did not return addresses');\n }\n if (server.cere != null && client.cere !== server.cere) {\n throw new WalletError('derivation-mismatch', `Cere mismatch: client=${client.cere} api=${server.cere}`);\n }\n if (server.solana != null && client.solana !== server.solana) {\n throw new WalletError('derivation-mismatch', `Solana mismatch: client=${client.solana} api=${server.solana}`);\n }\n if (server.evm != null && client.evm.toLowerCase() !== server.evm.toLowerCase()) {\n throw new WalletError('derivation-mismatch', `EVM mismatch: client=${client.evm} api=${server.evm}`);\n }\n }\n}\n"]}