@ghostlytools/cli 1.3.3 → 1.3.4
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/README.md +1 -1
- package/SKILL.md +2 -1
- package/dist/assets/{api-Bg7bh_6E.js → api-BCwaCbKh.js} +2 -2
- package/dist/assets/{api-B1fADtqd.js → api-Bp_hWAiU.js} +44 -9
- package/dist/assets/api-Bp_hWAiU.js.map +1 -0
- package/dist/assets/{apiKit-BsUZPHLn.js → apiKit-3MLUyuhL.js} +2 -2
- package/dist/assets/{apiKit-BsUZPHLn.js.map → apiKit-3MLUyuhL.js.map} +1 -1
- package/dist/assets/{apps-DepomCiX.js → apps-B6gqk4GD.js} +1 -1
- package/dist/assets/{apps-BaUNt-5c.js → apps-BXw3Ui_3.js} +2 -2
- package/dist/assets/{apps-BaUNt-5c.js.map → apps-BXw3Ui_3.js.map} +1 -1
- package/dist/assets/{arkade-CNnMaoEQ.js → arkade-CyplNY_v.js} +2 -2
- package/dist/assets/{arkade-CNnMaoEQ.js.map → arkade-CyplNY_v.js.map} +1 -1
- package/dist/assets/{backup-Bbz3lyC5.js → backup-BGE4BDzf.js} +4 -4
- package/dist/assets/{backup-Bbz3lyC5.js.map → backup-BGE4BDzf.js.map} +1 -1
- package/dist/assets/{backup-B5Q1W-Ey.js → backup-Cm8ptQQA.js} +1 -1
- package/dist/assets/{buttons-DS1iURp8.js → buttons-52NT4sA_.js} +3 -3
- package/dist/assets/{buttons-DS1iURp8.js.map → buttons-52NT4sA_.js.map} +1 -1
- package/dist/assets/buttons-PCvHRlkS.js +2 -0
- package/dist/assets/{envelope-fNC941z4.js → envelope-CsRRCrL3.js} +2 -2
- package/dist/assets/{envelope-fNC941z4.js.map → envelope-CsRRCrL3.js.map} +1 -1
- package/dist/assets/{groupFiles-6qnLCnOm.js → groupFiles-BurezP4Z.js} +3 -3
- package/dist/assets/{groupFiles-6qnLCnOm.js.map → groupFiles-BurezP4Z.js.map} +1 -1
- package/dist/assets/{host-Dj3C1fn3.js → host-C74Sy5jT.js} +8 -8
- package/dist/assets/{host-Dj3C1fn3.js.map → host-C74Sy5jT.js.map} +1 -1
- package/dist/assets/{hyperdhtRelay-CrgYQN-g.js → hyperdhtRelay-D0MvfMzX.js} +2 -2
- package/dist/assets/{hyperdhtRelay-CrgYQN-g.js.map → hyperdhtRelay-D0MvfMzX.js.map} +1 -1
- package/dist/assets/{irohWeb-DSyLp5Re.js → irohWeb-DynqwRwl.js} +2 -2
- package/dist/assets/{irohWeb-DSyLp5Re.js.map → irohWeb-DynqwRwl.js.map} +1 -1
- package/dist/assets/{main-BDd11Yd1.js → main-DNzQOdNG.js} +23 -17
- package/dist/assets/{main-BDd11Yd1.js.map → main-DNzQOdNG.js.map} +1 -1
- package/dist/assets/{mainline-CLTo0nGa.js → mainline-COqCHn1R.js} +2 -2
- package/dist/assets/{mainline-CLTo0nGa.js.map → mainline-COqCHn1R.js.map} +1 -1
- package/dist/assets/{networkReason-DwcadZz5.js → networkReason-WikfjkIw.js} +2 -2
- package/dist/assets/{networkReason-DwcadZz5.js.map → networkReason-WikfjkIw.js.map} +1 -1
- package/dist/assets/{nwc-BNFyyECH.js → nwc-ClsjYuyP.js} +3 -3
- package/dist/assets/{nwc-BNFyyECH.js.map → nwc-ClsjYuyP.js.map} +1 -1
- package/dist/assets/{nwcProvider-BbjJOfLD.js → nwcProvider-ksMYZra9.js} +2 -2
- package/dist/assets/{nwcProvider-BbjJOfLD.js.map → nwcProvider-ksMYZra9.js.map} +1 -1
- package/dist/assets/{registry-CoLHpxkb.js → registry-CJh9L_kF.js} +3 -3
- package/dist/assets/{registry-CoLHpxkb.js.map → registry-CJh9L_kF.js.map} +1 -1
- package/dist/assets/{registry-DoTb6uEr.js → registry-S7SFF_JB.js} +1 -1
- package/dist/assets/{resolve-BIDJ-UUl.js → resolve-BJxBhU5j.js} +2 -2
- package/dist/assets/{resolve-BIDJ-UUl.js.map → resolve-BJxBhU5j.js.map} +1 -1
- package/dist/assets/{server-Bx3RGkBC.js → server-C05N9q4G.js} +28 -15
- package/dist/assets/server-C05N9q4G.js.map +1 -0
- package/dist/assets/{src-D7gcJElT.js → src-RQ6OJOUO.js} +7 -1
- package/dist/assets/src-RQ6OJOUO.js.map +1 -0
- package/dist/assets/{turnRefs-PN644M9o.js → turnRefs-u-U75DFx.js} +2 -2
- package/dist/assets/{turnRefs-PN644M9o.js.map → turnRefs-u-U75DFx.js.map} +1 -1
- package/dist/assets/{usdt-B2TLRlYa.js → usdt-A1B3r3Mq.js} +3 -3
- package/dist/assets/{usdt-B2TLRlYa.js.map → usdt-A1B3r3Mq.js.map} +1 -1
- package/dist/ghostly.mjs +1 -1
- package/package.json +1 -1
- package/dist/assets/api-B1fADtqd.js.map +0 -1
- package/dist/assets/buttons-CUd7j0ZA.js +0 -2
- package/dist/assets/server-Bx3RGkBC.js.map +0 -1
- package/dist/assets/src-D7gcJElT.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry-CoLHpxkb.js","names":["HEX64","hex","unhex","nip19Decode","exactKeys","scopeFor","callbackParams","currentHost","hostOf","exactKeys"],"sources":["../../../browser/src/proofs/bitcoinWallets.ts","../../../browser/src/proofs/providers/bitcoin.ts","../../../browser/src/proofs/nostr.ts","../../../browser/src/proofs/providers/nostr.ts","../../../browser/src/proofs/providers/domain.ts","../../../browser/src/proofs/openpgp.ts","../../../browser/src/proofs/providers/openpgp.ts","../../../browser/src/proofs/sshForges.ts","../../../browser/src/proofs/providers/ssh.ts","../../../browser/src/host.ts","../../../browser/src/proofs/oidc/providers.ts","../../../browser/src/proofs/oidc/jwks.ts","../../../browser/src/proofs/oidc/verify.ts","../../../browser/src/proofs/oidc/flow.ts","../../../browser/src/proofs/providers/oidc.ts","../../../browser/src/proofs/did.ts","../../../browser/src/proofs/providers/did.ts","../../../browser/src/proofs/atproto/oauth.ts","../../../browser/src/proofs/providers/atproto.ts","../../../browser/src/proofs/pubky.ts","../../../browser/src/proofs/providers/pubky.ts","../../../browser/src/proofs/testing.ts","../../../browser/src/proofs/registry.ts"],"sourcesContent":["import type { BitcoinScriptType } from \"@ghostly/core\";\n\n/**\n * Person-facing steps to sign Ghostly's proof statement for a Bitcoin address, per common wallet: which\n * menu, what to paste where, which format to pick. Ghostly never sees the wallet or its keys; the person\n * pastes the signature back.\n *\n * Only what each wallet's own documentation or UI shows is claimed here. Hardware wallets differ by model\n * and firmware (ADAPTER-ROADMAP.md, \"Hardware and signing\"); their entries point to the vendor's guide.\n */\n\nexport type BitcoinSigningFormat = \"bip322\" | \"legacy\";\n\nexport interface BitcoinWalletGuide {\n id: string;\n name: string;\n /** Address types this wallet can sign for in a format Ghostly accepts. */\n scripts: readonly BitcoinScriptType[];\n format: BitcoinSigningFormat;\n steps: readonly string[];\n /** How to use the test networks, for Testnet mode. */\n testnet?: string;\n note?: string;\n docs?: string;\n}\n\n/** What a proof proves and what it does not; shown next to every Bitcoin proof, given or received. */\nexport const BITCOIN_PROOF_LIMITS = [\n \"Proves: whoever made this signature could sign with the key behind this address when they signed.\",\n \"Does not prove the address holds any balance, now or ever.\",\n \"Does not prove they sent or received any past payment.\",\n \"Does not prove they would pay you, or spend from this address at all.\",\n] as const;\n\nexport const BITCOIN_WALLET_GUIDES: readonly BitcoinWalletGuide[] = [\n {\n id: \"sparrow\",\n name: \"Sparrow Wallet\",\n scripts: [\"p2wpkh\", \"p2tr\", \"p2pkh\"],\n format: \"bip322\",\n steps: [\n \"Open the wallet that holds the address, then Tools → Sign/Verify Message.\",\n \"Address: paste the address you are proving.\",\n \"Message: paste the statement Ghostly shows, exactly as it is.\",\n \"Format: BIP322 (Simple) for a bc1q… or bc1p… address (tb1… on test networks). For a legacy 1… address, Standard (Electrum).\",\n \"Sign Message, then copy the whole Signature (it starts with smp…) and paste it into Ghostly.\",\n ],\n testnet: \"Tools → Restart In → Testnet or Signet, with a wallet on that network.\",\n note: \"Sparrow offers BIP322 (Simple) for software wallets only; a connected hardware wallet signs the Standard (Electrum) format, which Ghostly accepts only for a legacy 1… address.\",\n docs: \"https://sparrowwallet.com/docs/\",\n },\n {\n id: \"bitcoin-core\",\n name: \"Bitcoin Core\",\n scripts: [\"p2pkh\"],\n format: \"legacy\",\n steps: [\n \"Bitcoin Core signs messages in the legacy format only, for a legacy address (1… on mainnet, m… or n… on test networks). Make one in the Receive tab with the Base58 (Legacy) type, or with `getnewaddress \\\"\\\" legacy`.\",\n \"GUI: File → Sign message…; paste the address, paste the statement, Sign Message, copy the signature.\",\n \"Command line: bitcoin-cli signmessage \\\"<address>\\\" '<statement>' (single quotes keep the statement's double quotes intact).\",\n ],\n testnet: \"Run it with -signet, -testnet4 or -regtest; add the same flag to bitcoin-cli.\",\n docs: \"https://developer.bitcoin.org/reference/rpc/signmessage.html\",\n },\n {\n id: \"electrum\",\n name: \"Electrum\",\n scripts: [\"p2pkh\"],\n format: \"legacy\",\n steps: [\n \"Electrum signs in the legacy format, which Ghostly accepts only for a legacy 1… address (m…/n… on test networks); its default bc1… wallets need another wallet.\",\n \"Addresses tab (View → Show Addresses), right-click the address → Sign/verify message.\",\n \"Paste the statement into Message, click Sign, copy the Signature and paste it into Ghostly.\",\n ],\n testnet: \"Start Electrum with --testnet or --signet.\",\n docs: \"https://electrum.readthedocs.io/\",\n },\n {\n id: \"coldcard\",\n name: \"COLDCARD\",\n scripts: [\"p2wpkh\", \"p2tr\", \"p2pkh\"],\n format: \"bip322\",\n steps: [\n \"Follow COLDCARD's BIP-322 guide for your firmware: it signs the statement for one of its addresses via MicroSD, NFC or QR, depending on the model.\",\n \"Give it the statement exactly as Ghostly shows it and the address you are proving.\",\n \"Paste the signature it produces into Ghostly.\",\n ],\n note: \"Older firmware signs only the legacy format (Ghostly accepts that for a legacy 1… address) and limits message length and characters.\",\n docs: \"https://coldcard.com/docs/bip322/\",\n },\n {\n id: \"trezor\",\n name: \"Trezor Suite\",\n scripts: [\"p2pkh\"],\n format: \"legacy\",\n steps: [\n \"Trezor signs messages in the legacy format; Ghostly accepts it for an address of a Legacy account (1…).\",\n \"Open the Legacy account and choose Sign & verify in its menu.\",\n \"Paste the statement into Message, pick the address, Sign, confirm on the device, copy the signature into Ghostly.\",\n ],\n },\n {\n id: \"other\",\n name: \"Another wallet\",\n scripts: [\"p2wpkh\", \"p2tr\", \"p2sh\", \"p2pkh\"],\n format: \"bip322\",\n steps: [\n \"Look for \\\"Sign message\\\" in the wallet. Choose BIP-322 when it asks for a format.\",\n \"Sign the statement exactly as Ghostly shows it, with the address you are proving.\",\n \"A nested SegWit address (3…, or 2… on test networks) needs a full BIP-322 signature (it starts with ful…).\",\n \"Paste the signature into Ghostly; it tells you right away if it does not match.\",\n ],\n },\n];\n\n/** The guides that can sign for an address of this type. */\nexport function guidesFor(script: BitcoinScriptType): BitcoinWalletGuide[] {\n return BITCOIN_WALLET_GUIDES.filter(g => g.scripts.includes(script));\n}\n","import { BitcoinAddressError, decodeAnyBitcoinAddress, verifyBitcoinMessage, type BitcoinMessageVerdict, type BitcoinProofNetwork, type BitcoinScriptType, type IdentityStatement } from \"@ghostly/core\";\nimport type { ExternalToolSigner, IdentityProofProvider, InstructionStep } from \"../contract\";\nimport { BITCOIN_PROOF_LIMITS, BITCOIN_WALLET_GUIDES, guidesFor, type BitcoinWalletGuide } from \"../bitcoinWallets\";\n\n/**\n * Bitcoin address proofs: the person signs the statement with the key behind an address, in their own\n * wallet, and pastes the signature. BIP-322 (simple or full) or, for a legacy P2PKH address only, the\n * legacy `signmessage` format; checked on this device by `verifyBitcoinMessage` (@ghostly/core, pinned to\n * BIP-322 2.0.0). No blockchain lookup: the proof says nothing about coins. See\n * docs/wisps/306-bitcoin.md.\n *\n * The address's own prefix says its network: a proof for a test-network address (tb1…, bcrt1…, m…, n…, 2…)\n * is verified as such and labelled \"test network\" wherever it is shown.\n *\n * TODO(ghostly-wallet signer): \"Sign with my Ghostly wallet\" belongs here as an `in-app` signer once an\n * on-chain source can sign messages. None can today: `OnchainProvider` (engine/paymentAdapters/providers/\n * onchain.ts) has no such method, Bitcoin Core's `signmessage` is legacy-only, and bdk-wallet-web has no\n * message signing. The BDK source keeps its phrase in the engine, so the path is an optional\n * `signMessage?(address, message)` on `OnchainProvider`, implemented in bdk.ts with a BIP-322 simple\n * witness for its own P2WPKH/P2TR address, exposed through an engine RPC that shows the statement and\n * asks first; never sign silently, and never hand the key to the UI.\n */\n\nexport interface BitcoinEvidence {\n /** As the wallet printed it: `smp…`/`ful…`, unprefixed BIP-322, or a legacy base64 signature. */\n signature: string;\n}\n\nconst MAX_SIGNATURE = 8192;\nconst SIGNATURE = /^(smp|ful|pof)?[A-Za-z0-9+/]+={0,2}$/;\n\nconst SCRIPT_NAMES: Record<BitcoinScriptType, string> = {\n p2wpkh: \"native SegWit (bc1q…)\", p2tr: \"Taproot (bc1p…)\", p2sh: \"nested SegWit (3…)\", p2pkh: \"legacy (1…)\",\n p2wsh: \"script / multisig (P2WSH)\", \"witness-unknown\": \"future SegWit version\",\n};\n\nfunction normalizeAddress(input: string): string {\n const value = input.trim();\n let info;\n try { info = decodeAnyBitcoinAddress(value); } catch (e) {\n throw Object.assign(new Error(e instanceof BitcoinAddressError ? e.message : \"Enter a Bitcoin address\"), { cause: e });\n }\n if (info.type === \"p2wsh\" || info.type === \"witness-unknown\")\n throw new Error(\"Ghostly can check proofs for single-key addresses only (bc1q…, bc1p…, 3…, 1…), not multisig or script addresses\");\n // Bech32 may be written in upper case; its canonical form is lower case. Base58 is case-sensitive.\n return info.witnessVersion === undefined ? value : value.toLowerCase();\n}\n\nconst isTestAddress = (address: string) => { try { return decodeAnyBitcoinAddress(address).network === \"testnet\"; } catch { return false; } };\n\nfunction shortAddress(address: string): string {\n return `${address.slice(0, address.startsWith(\"bcrt1\") ? 9 : 7)}…${address.slice(-4)}${isTestAddress(address) ? \" · test network\" : \"\"}`;\n}\n\n/** Wallet steps for this statement's address: the guide's own, or why this wallet cannot sign for it. */\nfunction walletInstructions(guide: BitcoinWalletGuide, statement: IdentityStatement) {\n const address = statement.binding.subject;\n const info = decodeAnyBitcoinAddress(address);\n const steps: InstructionStep[] = [];\n if (!guide.scripts.includes(info.type)) {\n const others = guidesFor(info.type).filter(g => g.id !== \"other\").map(g => g.name);\n steps.push({ text: `${guide.name} cannot sign for a ${SCRIPT_NAMES[info.type]} address in a format Ghostly accepts.${others.length ? ` Try ${others.join(\" or \")}, or another wallet that signs BIP-322.` : \" Use a wallet that signs BIP-322.\"}` });\n } else {\n steps.push({ text: \"Sign exactly this statement, as one line, without adding a line break:\", copy: statement.text });\n steps.push({ text: \"With this address:\", copy: address });\n for (const text of guide.steps) steps.push({ text });\n if (guide.id === \"bitcoin-core\") steps.push({ text: `Command line${info.network === \"testnet\" ? \" (add -signet, -testnet4 or -regtest)\" : \"\"}:`, copy: `bitcoin-cli signmessage \"${address}\" '${statement.text}'` });\n if (info.network === \"testnet\" && guide.testnet) steps.push({ text: `Test networks: ${guide.testnet}` });\n if (guide.note) steps.push({ text: guide.note });\n }\n steps.push({ text: \"This shows you can sign for the address. It does not show a balance, a past payment, or that you would pay.\" });\n return { steps, paste: { label: \"Signature\", placeholder: \"smp… (BIP-322) or the base64 signature your wallet shows\", multiline: true } };\n}\n\nfunction cleanSignature(pasted: string): string {\n const signature = pasted.replace(/\\s+/g, \"\");\n if (!signature) throw new Error(\"Paste the signature your wallet made\");\n if (signature.length > MAX_SIGNATURE || !SIGNATURE.test(signature)) throw new Error(\"That is not a Bitcoin message signature: paste the whole signature, as the wallet shows it\");\n return signature;\n}\n\nfunction check(statement: IdentityStatement, signature: string): { verdict: Extract<BitcoinMessageVerdict, { state: \"valid\" }>; network: BitcoinProofNetwork } {\n const address = statement.binding.subject;\n const network = decodeAnyBitcoinAddress(address).network;\n const verdict = verifyBitcoinMessage({ address, message: statement.text, signature, network });\n if (verdict.state !== \"valid\") throw new Error(verdict.reason);\n return { verdict, network };\n}\n\nfunction walletSigner(guide: BitcoinWalletGuide): ExternalToolSigner<BitcoinEvidence> {\n return {\n id: guide.id, kind: \"external-tool\", label: guide.name,\n description: guide.format === \"legacy\" ? \"Legacy message signature: for a legacy 1… address only.\" : \"BIP-322 message signature.\",\n instructions: statement => walletInstructions(guide, statement),\n parse(pasted, statement) {\n const signature = cleanSignature(pasted);\n // Say right away when it does not match, while the wallet is still open.\n check(statement, signature);\n return { signature };\n },\n };\n}\n\nexport const bitcoin: IdentityProofProvider<BitcoinEvidence> = {\n id: \"bitcoin\",\n label: \"Bitcoin address\",\n category: \"self-custodied\",\n summary: \"Sign a message in your own wallet\",\n description: \"Proves you can sign with the key behind a Bitcoin address: your wallet signs the statement and you paste the signature.\",\n limits: \"It does not prove a balance, a past payment, or that you would pay.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Bitcoin address\",\n placeholder: \"bc1q… (tb1… on test networks)\",\n help: BITCOIN_PROOF_LIMITS.join(\" \"),\n normalize: normalizeAddress,\n short: shortAddress,\n },\n // BIP-21\n publicUri: address => `bitcoin:${address}`,\n validity: { defaultDays: 90, maxDays: 365 },\n privacy: \"Nothing: the signature is checked on this device, with no blockchain lookup. Contacts you share it with learn the address.\",\n signers: BITCOIN_WALLET_GUIDES.map(walletSigner),\n parseEvidence(raw) {\n const e = raw as BitcoinEvidence;\n if (!e || typeof e !== \"object\" || Array.isArray(e) || Object.keys(e).join(\",\") !== \"signature\" || typeof e.signature !== \"string\" ||\n e.signature.length > MAX_SIGNATURE || !SIGNATURE.test(e.signature)) throw new Error(\"That is not a Bitcoin message signature\");\n return { signature: e.signature };\n },\n async verify(statement, evidence) {\n const { verdict, network } = check(statement, evidence.signature);\n const format = verdict.format === \"legacy\" ? \"Legacy signmessage\" : verdict.format === \"bip322-full\" ? \"BIP-322 full\" : \"BIP-322 simple\";\n const script = { p2pkh: \"P2PKH\", p2wpkh: \"P2WPKH\", \"p2sh-p2wpkh\": \"P2SH-P2WPKH\", p2tr: \"P2TR\" }[verdict.script];\n return { subject: statement.binding.subject, source: `${format} signature, ${script}${network === \"testnet\" ? \", test network\" : \"\"}` };\n },\n};\n","import type { BunkerPointer, BunkerSigner } from 'nostr-tools/nip46';\nimport type { SimplePool } from 'nostr-tools/pool';\nimport { generateSecretKey } from 'nostr-tools/pure';\nimport type { NostrProofEvent } from '@ghostly/core';\n\nexport interface NostrSigner {\n getPublicKey(): Promise<string>;\n signEvent(event: { kind: number; created_at: number; tags: string[][]; content: string }): Promise<NostrProofEvent>;\n}\nexport function extensionSigner(): NostrSigner | undefined {\n return (globalThis as typeof globalThis & { nostr?: NostrSigner }).nostr;\n}\n/** No NIP-05 lookup, arbitrary fetch, private key import or automatic relay switching. */\nexport function parseProofBunker(input: string): BunkerPointer {\n if (input.length > 4096) throw new Error('Bunker URL too long');\n let uri: URL;\n try { uri = new URL(input); } catch { throw new Error('Enter a bunker:// connection URL from your signer'); }\n const relays = uri.searchParams.getAll('relay');\n if (uri.protocol !== 'bunker:' || !/^[a-f0-9]{64}$/.test(uri.hostname) || uri.username || uri.password || uri.port || uri.hash || (uri.pathname && uri.pathname !== '/') ||\n relays.length < 1 || relays.length > 3) throw new Error('Invalid bunker connection URL');\n for (const address of relays) {\n const relay = new URL(address);\n if (relay.username || relay.password || relay.hash || !(relay.protocol === 'wss:' ||\n (relay.protocol === 'ws:' && ['127.0.0.1', 'localhost', '[::1]'].includes(relay.hostname))))\n throw new Error('Use secure wss:// relays (ws:// is allowed only on loopback for local testing)');\n }\n return { pubkey: uri.hostname, relays, secret: uri.searchParams.get('secret') };\n}\n/**\n * Runs `work` with the person's Nostr signer: the page's NIP-07 extension, or a NIP-46 bunker. `permissions`\n * is what the bunker is asked for (NIP-46 `connect`): the identity proof's `sign_event:30078` by default;\n * the social layer asks for the kinds it publishes.\n */\nexport async function withNostrSigner<T>(options: { bunker?: string; signal: AbortSignal; onAuth(url: string): void; permissions?: string; appName?: string }, work: (signer: NostrSigner) => Promise<T>): Promise<T> {\n options.signal.throwIfAborted();\n let signer: BunkerSigner | undefined;\n let pool: SimplePool | undefined;\n let clientKey: Uint8Array | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let abort: (() => void) | undefined;\n let closed = false;\n const guard: NostrSigner = {\n getPublicKey: async () => { if (closed) throw new Error('Signer request cancelled'); const key = await active!.getPublicKey(); if (closed) throw new Error('Signer request cancelled'); return key; },\n signEvent: async event => { if (closed) throw new Error('Signer request cancelled'); const signed = await active!.signEvent(event); if (closed) throw new Error('Signer request cancelled'); return signed; },\n };\n let active: NostrSigner | undefined;\n try {\n const timeout = new Promise<never>((_, reject) => {\n abort = () => { closed = true; reject(new Error('Signing cancelled')); };\n options.signal.addEventListener('abort', abort, { once: true });\n timer = setTimeout(() => { closed = true; reject(new Error('Signer timed out. Your Ghostly identity is unchanged.')); }, 120_000);\n });\n const action = async () => {\n if (options.bunker) {\n const bp = parseProofBunker(options.bunker);\n // Loaded here, not with the app: the NIP-46 signer and its relay pool are only for a bunker.\n const [{ BunkerSigner }, { SimplePool }] = await Promise.all([import('nostr-tools/nip46'), import('nostr-tools/pool')]);\n if (closed) throw new Error('Signer request cancelled');\n pool = new SimplePool(); clientKey = generateSecretKey();\n signer = BunkerSigner.fromBunker(clientKey, bp, { pool, skipSwitchRelays: true, onauth: url => {\n try { const u = new URL(url); if (!closed && u.protocol === 'https:' && !u.username && !u.password) options.onAuth(u.href); } catch { /* Invalid signer URL: never navigate. */ }\n } });\n await signer.sendRequest('connect', [bp.pubkey, bp.secret ?? '', options.permissions ?? 'sign_event:30078', JSON.stringify({ name: options.appName ?? 'Ghostly — optional conversation proof' })]);\n active = signer;\n } else {\n active = extensionSigner();\n if (!active) throw new Error('No NIP-07 signer in this window. Use a bunker connection from your Nostr signer.');\n }\n if (closed) throw new Error('Signer request cancelled');\n return work(guard);\n };\n return await Promise.race([action(), timeout]);\n } finally {\n closed = true;\n if (timer) clearTimeout(timer);\n if (abort) options.signal.removeEventListener('abort', abort);\n await signer?.close(); pool?.destroy(); clientKey?.fill(0);\n }\n}\n","import { schnorr } from \"@noble/curves/secp256k1.js\";\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport { npubEncode, decode as nip19Decode } from \"nostr-tools/nip19\";\nimport { utf8Encode, type IdentityStatement } from \"@ghostly/core\";\nimport type { IdentityProofProvider, InAppSigner, SignerSession } from \"../contract\";\nimport { extensionSigner, withNostrSigner } from \"../nostr\";\n\n/** A signed Nostr event (NIP-01). The evidence of a Nostr proof. */\nexport interface NostrEvent { id: string; pubkey: string; created_at: number; kind: number; tags: string[][]; content: string; sig: string }\n\n/**\n * NIP-78 application data (kind 30078): no new kind is claimed. Signers show it as such; it is never\n * published to a relay — the event travels only to the contacts the person shares it with.\n */\nexport const NOSTR_PROOF_KIND = 30078;\nconst HEX64 = /^[a-f0-9]{64}$/;\n\n/** The exact event a signer is asked to sign: the statement as content, dated at issue, expiring with it. */\nexport function nostrProofTemplate(statement: IdentityStatement) {\n const { issuedAt, expiresAt } = statement.binding;\n return { kind: NOSTR_PROOF_KIND, created_at: issuedAt, content: statement.text,\n tags: [[\"d\", \"ghostly-identity-proof\"], [\"expiration\", String(expiresAt)]] };\n}\n\nconst hex = (b: Uint8Array) => Array.from(b, x => x.toString(16).padStart(2, \"0\")).join(\"\");\nconst unhex = (s: string) => Uint8Array.from(s.match(/../g)!, x => parseInt(x, 16));\n\nfunction normalizeNostrKey(input: string): string {\n const value = input.trim();\n if (HEX64.test(value.toLowerCase())) return value.toLowerCase();\n if (value.startsWith(\"npub1\")) {\n try { const decoded = nip19Decode(value); if (decoded.type === \"npub\") return decoded.data; } catch { /* fall through */ }\n }\n throw new Error(\"Enter a Nostr public key: npub1… or 64 hex characters\");\n}\n\nfunction nostrSigner(id: \"nip07\" | \"nip46\"): InAppSigner<NostrEvent> {\n return {\n id, kind: \"in-app\",\n label: id === \"nip07\" ? \"Browser extension (NIP-07)\" : \"Remote signer (NIP-46)\",\n description: id === \"nip07\" ? \"Approve in the Nostr extension of this browser.\" : \"Paste the bunker:// link from your signer app, then approve there.\",\n fields: id === \"nip46\" ? [{ name: \"bunker\", label: \"Signer connection link\", kind: \"secret\", placeholder: \"bunker://…\" }] : undefined,\n platforms: id === \"nip07\" ? [\"web\", \"desktop\"] : undefined,\n available: id === \"nip07\" ? () => !!extensionSigner() : undefined,\n run: (ctx, work) => withNostrSigner({ bunker: id === \"nip46\" ? ctx.values.bunker : undefined, signal: ctx.signal, onAuth: ctx.onAuthUrl }, signer => {\n const session: SignerSession<NostrEvent> = {\n subject: async () => normalizeNostrKey(await signer.getPublicKey()),\n sign: async statement => {\n ctx.onProgress(\"Approve the Ghostly identity statement in your signer.\");\n return (await signer.signEvent(nostrProofTemplate(statement))) as NostrEvent;\n },\n };\n return work(session);\n }),\n };\n}\n\nexport const nostr: IdentityProofProvider<NostrEvent> = {\n id: \"nostr\",\n label: \"Nostr\",\n category: \"self-custodied\",\n summary: \"Sign once with your Nostr signer\",\n description: \"Proves you hold a Nostr key: your signer signs the statement, your key never leaves it.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Public key\", placeholder: \"npub1…\",\n normalize: normalizeNostrKey,\n short: key => { const npub = npubEncode(key); return `${npub.slice(0, 10)}…${npub.slice(-4)}`; },\n },\n // NIP-21\n publicUri: key => `nostr:${npubEncode(key)}`,\n validity: { defaultDays: 90, maxDays: 365 },\n privacy: \"Nothing: the signature is checked on this device. The proof is never published to a relay.\",\n signers: [nostrSigner(\"nip07\"), nostrSigner(\"nip46\")],\n parseEvidence(raw) {\n const e = raw as NostrEvent;\n if (!e || typeof e !== \"object\" || Array.isArray(e) || Object.keys(e).sort().join(\",\") !== \"content,created_at,id,kind,pubkey,sig,tags\" ||\n typeof e.id !== \"string\" || !HEX64.test(e.id) || typeof e.pubkey !== \"string\" || !HEX64.test(e.pubkey) ||\n typeof e.sig !== \"string\" || !/^[a-f0-9]{128}$/.test(e.sig) || !Number.isSafeInteger(e.kind) || !Number.isSafeInteger(e.created_at) ||\n typeof e.content !== \"string\" || e.content.length > 2048 || !Array.isArray(e.tags) || e.tags.length > 4 ||\n !e.tags.every(t => Array.isArray(t) && t.length <= 4 && t.every(x => typeof x === \"string\" && x.length <= 128)))\n throw new Error(\"That is not a signed Nostr event\");\n return { id: e.id, pubkey: e.pubkey, created_at: e.created_at, kind: e.kind, tags: e.tags.map(t => [...t]), content: e.content, sig: e.sig };\n },\n async verify(statement, event) {\n const expected = nostrProofTemplate(statement);\n if (event.pubkey !== statement.binding.subject) throw new Error(\"Signed by another Nostr key\");\n if (event.kind !== expected.kind || event.created_at !== expected.created_at || event.content !== expected.content ||\n JSON.stringify(event.tags) !== JSON.stringify(expected.tags)) throw new Error(\"The signer did not sign the exact Ghostly statement\");\n // NIP-01 id, recomputed here: never trust the id or a verification flag the event carries.\n const id = hex(sha256(utf8Encode(JSON.stringify([0, event.pubkey, event.created_at, event.kind, event.tags, event.content]))));\n if (event.id !== id) throw new Error(\"The Nostr event id does not match its content\");\n let valid: boolean;\n try { valid = schnorr.verify(unhex(event.sig), unhex(id), unhex(event.pubkey)); } catch { valid = false; }\n if (!valid) throw new Error(\"Invalid Nostr signature\");\n return { subject: event.pubkey, source: \"Nostr signature (NIP-01, BIP-340)\" };\n },\n // The public profile, follows and notes of a verified key are the Nostr social layer's\n // (engine/nostrSocial.ts): loaded on request from the relays the person configured, not here.\n};\n","import { dnsProofName, domainTxtRecord, domainWellKnownFile, normalizeDomain, wellKnownUrl, type DomainRecord, type IdentityStatement } from \"@ghostly/core\";\nimport type { IdentityProofProvider, InAppSigner, PublishSigner, SignerSession } from \"../contract\";\nimport { assertDomainRecord, assertNip05, chosenResolver, resolverById } from \"../domain\";\nimport { extensionSigner, withNostrSigner } from \"../nostr\";\nimport { nostr, nostrProofTemplate, type NostrEvent } from \"./nostr\";\n\n/**\n * A domain the person controls (draft WISP 304, docs/wisps/304-domain.md).\n *\n * The statement (\"I control domain:example.com and authorize the Ghostly key K …\") is vouched for by\n * publishing, once, under the domain: a TXT record at `_ghostly.<domain>` or the file\n * `/.well-known/ghostly.json`, naming the proof key K and the statement id. The id commits the record to\n * the whole statement (dates and nonce too), so one published record vouches for exactly one proof. The\n * record names no contact and no conversation; sharing per contact is the contract's presentation.\n *\n * People who already run NIP-05 can instead sign the statement with the Nostr key their\n * `/.well-known/nostr.json` names for the domain itself (`_`): the proof holds only if both agree.\n */\nexport type DomainEvidence = { method: \"dns\" } | { method: \"https\" } | { method: \"nip05\"; event: NostrEvent };\n\nconst recordFor = (s: IdentityStatement): DomainRecord => ({ key: s.binding.key, proof: s.id });\nconst exactKeys = (raw: unknown, keys: string) => !!raw && typeof raw === \"object\" && !Array.isArray(raw) && Object.keys(raw).sort().join(\",\") === keys;\n\nconst dnsSigner: PublishSigner<DomainEvidence> = {\n id: \"dns\", kind: \"publish\", label: \"DNS TXT record\",\n description: \"Add a TXT record at your DNS host. Contacts' apps look it up through a DNS-over-HTTPS resolver.\",\n instructions(statement) {\n const domain = statement.binding.subject;\n return { steps: [\n { text: `Open the DNS settings of ${domain} at your registrar or DNS host and add a TXT record with this name. Some hosts add the domain themselves: then enter only the part before ${domain}.`, copy: dnsProofName(domain) },\n { text: \"Value (one string, exactly as shown):\", copy: domainTxtRecord(recordFor(statement)) },\n { text: \"Set the TTL to 300 seconds or less, so removing the record takes effect quickly. Then press Check: DNS changes can take a few minutes to appear.\" },\n { text: \"The record stays public while the proof is in use. Removing it withdraws this proof from every contact at their next check.\" },\n ] };\n },\n evidence: () => ({ method: \"dns\" }),\n};\n\nconst httpsSigner: PublishSigner<DomainEvidence> = {\n id: \"https\", kind: \"publish\", label: \"File on your website\",\n description: \"Upload a small file to your website. Contacts' apps download it from your server.\",\n instructions(statement) {\n const domain = statement.binding.subject;\n return { steps: [\n { text: `Create this file on the web server of ${domain}:`, copy: wellKnownUrl(domain) },\n { text: \"With exactly this content:\", copy: domainWellKnownFile([recordFor(statement)]) },\n { text: \"Serve it over HTTPS at exactly that address, without any redirect (not even to www), and with this header so browsers may read it:\", copy: \"Access-Control-Allow-Origin: *\" },\n { text: \"Then press Check. The file stays public while the proof is in use; deleting it withdraws this proof from every contact at their next check.\" },\n ] };\n },\n evidence: () => ({ method: \"https\" }),\n};\n\nconst domainField = { name: \"domain\", label: \"Domain\", kind: \"text\", placeholder: \"example.com\",\n help: \"Its /.well-known/nostr.json must name this Nostr key for \\\"_\\\" (the domain itself).\" } as const;\n\n/** NIP-05: the statement is signed by the Nostr key the domain's nostr.json names for itself. */\nfunction nip05Signer(id: \"nip05-nip07\" | \"nip05-nip46\"): InAppSigner<DomainEvidence> {\n const extension = id === \"nip05-nip07\";\n return {\n id, kind: \"in-app\",\n label: extension ? \"NIP-05 with a browser extension\" : \"NIP-05 with a remote signer\",\n description: \"Already run NIP-05? Sign with the Nostr key your domain's nostr.json names for itself. No new record needed.\",\n fields: extension ? [domainField] : [domainField, { name: \"bunker\", label: \"Signer connection link\", kind: \"secret\", placeholder: \"bunker://…\" }],\n platforms: extension ? [\"web\", \"desktop\"] : undefined,\n available: extension ? () => !!extensionSigner() : undefined,\n run: (ctx, work) => {\n const domain = normalizeDomain(ctx.values.domain ?? \"\");\n return withNostrSigner({ bunker: extension ? undefined : ctx.values.bunker, signal: ctx.signal, onAuth: ctx.onAuthUrl }, signer => {\n const session: SignerSession<DomainEvidence> = {\n subject: async () => domain,\n sign: async statement => {\n ctx.onProgress(\"Approve the Ghostly identity statement in your signer.\");\n return { method: \"nip05\", event: (await signer.signEvent(nostrProofTemplate(statement))) as NostrEvent };\n },\n };\n return work(session);\n });\n },\n };\n}\n\nexport const domain: IdentityProofProvider<DomainEvidence> = {\n id: \"domain\",\n label: \"Domain\",\n category: \"self-custodied\",\n summary: \"A DNS record or a file on your site\",\n description: \"Proves you control a domain name: publish a DNS TXT record or a file on its website, once. Contacts' apps look it up when you share it and can check again later.\",\n limits: \"Does not prove who runs the domain, only that whoever added the proof could publish under it.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Domain\", placeholder: \"example.com\",\n help: \"A domain or subdomain you control. A proof for alice.example.com is published under alice.example.com.\",\n normalize: normalizeDomain,\n },\n publicUri: domain => `https://${domain}`,\n validity: { defaultDays: 90, maxDays: 365 },\n recheck: { afterSeconds: 86_400 },\n privacy: \"DNS: the contact's app asks the DNS-over-HTTPS resolver chosen in its settings (Quad9 by default; the next of Quad9, Cloudflare and Google when it cannot be reached) for the record, \" +\n \"so that resolver learns the domain was checked. Website or NIP-05: the resolver learns the domain, and the domain's web server sees the contact's IP address.\",\n signers: [dnsSigner, httpsSigner, nip05Signer(\"nip05-nip07\"), nip05Signer(\"nip05-nip46\")],\n parseEvidence(raw) {\n const e = raw as DomainEvidence;\n if (exactKeys(raw, \"method\") && (e.method === \"dns\" || e.method === \"https\")) return { method: e.method };\n if (exactKeys(raw, \"event,method\") && e.method === \"nip05\") return { method: \"nip05\", event: nostr.parseEvidence((e as { event: unknown }).event) };\n throw new Error(\"That is not domain proof evidence\");\n },\n async verify(statement, evidence, ctx) {\n const subject = statement.binding.subject;\n if (normalizeDomain(subject) !== subject) throw new Error(\"The domain is not in canonical form\");\n const resolver = chosenResolver();\n const options = { fetch: ctx.fetch, signal: ctx.signal, resolver: resolver.id, now: () => ctx.now * 1000 };\n if (evidence.method === \"nip05\") {\n // The same checks as a Nostr proof, for the key that signed; then the domain must name that key.\n await nostr.verify({ ...statement, binding: { ...statement.binding, subject: evidence.event.pubkey } }, evidence.event, ctx);\n await assertNip05(subject, evidence.event.pubkey, options);\n return { subject, source: `Nostr signature by the key ${wellKnownUrl(subject, \"nip05\")} names for ${subject}` };\n }\n const found = await assertDomainRecord(subject, evidence.method, recordFor(statement), options);\n return { subject, source: evidence.method === \"dns\"\n ? `DNS TXT record at ${dnsProofName(subject)}, via ${resolverById(found.resolver).name}${found.dnssec ? \", DNSSEC validated\" : \"\"}`\n : `File at ${wellKnownUrl(subject)}` };\n },\n};\n","import { fromBase64Url, toBase64Url, utf8Encode } from '@ghostly/core';\nimport type * as OpenPGP from 'openpgp';\n\n/**\n * OpenPGP identity proofs (RFC 9580, and the RFC 4880 v4 keys people actually have).\n *\n * The person signs a Ghostly statement once — the binding by which their\n * OpenPGP key authorizes a Ghostly proof key (see PROOFS.md); each chat\n * then gets a presentation the app signs itself. They sign with their own tooling —\n * `gpg --clearsign`, `gpg --detach-sign --armor`, a YubiKey or another OpenPGP\n * card behind gpg — and pastes the result with their public key. No secret key\n * ever enters Ghostly: a pasted private key is refused before it is parsed.\n *\n * What is shared with a contact is small and self-contained: one signature\n * packet and a minimal copy of the public key (the primary key, its user IDs\n * with their self-signatures, the subkey that signed). The contact verifies it\n * locally with the statement their own app builds; nothing is fetched.\n *\n * A key's user IDs are claims its holder wrote. Possession of the key proves\n * control of the key, not that a name or an email in a user ID belongs to the\n * person — the UI must say so wherever user IDs are shown.\n *\n * OpenPGP.js is loaded lazily (the \"lightweight\" build: ~233 KB minified,\n * ~64 KB gzipped, plus a ~46 KB curve chunk only for ECDSA keys), so nobody\n * pays for it until an OpenPGP proof is opened.\n */\n\nexport const PGP_SCHEME = 'openpgp/1';\n\n/** Strict size limits. Pasted input is the person's own; shared input comes from a peer. */\nexport const PGP_LIMITS = {\n /** A clearsigned statement or an armored detached signature. RSA-4096 with the statement is ~1.5 KB. */\n pastedSignature: 16 * 1024,\n /** An armored public key as exported, before it is minimized. Flooded keys are refused, not trimmed. */\n pastedKey: 256 * 1024,\n /** One signature packet, binary. RSA-4096 with the usual subpackets is ~600 bytes. */\n sharedSignature: 1536,\n /** The minimal key, binary. Both fit, base64url, in the contract's 16 KiB of evidence JSON. */\n sharedKey: 9 * 1024,\n /** User IDs kept in the minimal key and shown. */\n userIds: 16,\n /** Bytes per user ID. */\n userIdLength: 256,\n /** A keyserver response. */\n keyserverResponse: 256 * 1024,\n} as const;\n\n/** Clock difference tolerated between the signer's machine and the verifier. */\nexport const PGP_CLOCK_SKEW = 300;\n\nexport interface PgpEvidence {\n scheme: typeof PGP_SCHEME;\n /** base64url of the minimal transferable public key (binary packets). */\n key: string;\n /** base64url of exactly one signature packet over the statement. */\n signature: string;\n}\n\nexport interface PgpKeyInfo {\n /** Primary key fingerprint, uppercase hex (40 characters for v4, 64 for v6). */\n fingerprint: string;\n /** The key that signs: the primary key or a signing subkey. */\n signingFingerprint: string;\n version: 4 | 6;\n /** \"Ed25519\", \"RSA 3072\", \"ECDSA P-256\", … for the signing key. */\n algorithm: string;\n createdAt: number;\n /** Seconds, or null when the key does not expire. */\n expiresAt: number | null;\n /** Self-certified, unrevoked user IDs: claims by the key holder, not verified facts. */\n userIds: string[];\n}\n\nexport interface PgpVerified extends PgpKeyInfo {\n /** Signature creation time, seconds. */\n signedAt: number;\n}\n\nexport interface PgpVerifyOptions {\n /** Verification time, seconds. The key and its signing subkey must be valid now. */\n now?: number;\n /** Refuse signatures made before this time (seconds), e.g. the start of the binding's validity. */\n signedAfter?: number;\n /** The primary key fingerprint the statement names: any other key is refused, even with a valid signature. */\n expectedFingerprint?: string;\n}\n\ntype Pgp = typeof OpenPGP;\nlet loading: Promise<Pgp> | undefined;\n/** The only place OpenPGP.js is imported: a separate chunk, fetched on first use. */\nexport function loadOpenPgp(): Promise<Pgp> {\n loading ??= import('openpgp/lightweight').then(m => m as unknown as Pgp).catch(e => { loading = undefined; throw e; });\n return loading;\n}\n\nfunction config(pgp: Pgp): OpenPGP.PartialConfig {\n const { hash, publicKey, curve } = pgp.enums;\n return {\n minRSABits: 2048,\n maxUserIDLength: PGP_LIMITS.userIdLength,\n enableParsingV5Entities: false,\n ignoreUnsupportedPackets: true,\n ignoreMalformedPackets: false,\n // Self-signatures from older keys may still use SHA-1; the proof signature may not (below).\n rejectHashAlgorithms: new Set([hash.md5, hash.ripemd]),\n rejectMessageHashAlgorithms: new Set([hash.md5, hash.ripemd, hash.sha1]),\n rejectPublicKeyAlgorithms: new Set([publicKey.elgamal, publicKey.dsa]),\n rejectCurves: new Set([curve.secp256k1]),\n };\n}\n\n/** The allowlist, checked on the primary key and on the key that signs. */\nfunction allowedAlgorithm(packet: OpenPGP.PublicKeyPacket | OpenPGP.SecretKeyPacket | OpenPGP.PublicSubkeyPacket | OpenPGP.SecretSubkeyPacket, signing: boolean): string {\n const { algorithm, bits, curve } = packet.getAlgorithmInfo();\n const names: Record<string, string> = { nistP256: 'P-256', nistP384: 'P-384', nistP521: 'P-521',\n brainpoolP256r1: 'brainpoolP256r1', brainpoolP384r1: 'brainpoolP384r1', brainpoolP512r1: 'brainpoolP512r1' };\n if ((algorithm === 'rsaEncryptSign' || (algorithm === 'rsaSign' && signing)) && bits && bits >= 2048) return `RSA ${bits}`;\n if (algorithm === 'ed25519' || (algorithm === 'eddsaLegacy' && curve === 'ed25519Legacy')) return 'Ed25519';\n if (algorithm === 'ed448') return 'Ed448';\n if (algorithm === 'ecdsa' && curve && names[curve]) return `ECDSA ${names[curve]}`;\n const shown = algorithm.startsWith('rsa') ? `RSA ${bits ?? '?'}` : curve ? `${algorithm} ${curve}` : algorithm;\n throw new PgpProofError(`This key uses ${shown}, which Ghostly does not accept. Use Ed25519, ECDSA (P-256, P-384, P-521, brainpool) or RSA with at least 2048 bits.`);\n}\nconst ALLOWED_HASHES = new Set([8, 9, 10, 11, 12, 14]); // SHA-256, -384, -512, -224, SHA3-256, SHA3-512\n\nexport class PgpProofError extends Error { override name = 'PgpProofError'; }\n\n/** Armored blocks in pasted text; more BEGIN lines than any real paste has are refused before the regex scans. */\nfunction armorBlocks(text: string): RegExpExecArray[] {\n if ((text.match(/-----BEGIN PGP /g) ?? []).length > 4) throw new PgpProofError('Paste only what gpg printed: one signature and your public key.');\n return [...text.matchAll(ARMOR)];\n}\n// A clearsigned message has no END line of its own: it ends with its signature block.\nconst ARMOR = /-----BEGIN PGP SIGNED MESSAGE-----[\\s\\S]*?-----END PGP SIGNATURE-----|-----BEGIN PGP ([A-Z ,/0-9]+)-----[\\s\\S]*?-----END PGP \\1-----/g;\n/** The single armored block in pasted text (terminal output may surround it). */\nfunction armoredBlock(text: string, what: 'key' | 'signature'): { type: string; block: string } {\n if (/PRIVATE KEY BLOCK/.test(text))\n throw new PgpProofError('This is a PRIVATE key. Never paste it anywhere: Ghostly only needs your public key (gpg --armor --export).');\n const blocks = armorBlocks(text);\n if (blocks.length === 0) throw new PgpProofError(what === 'key' ? 'Paste an armored public key: it starts with -----BEGIN PGP PUBLIC KEY BLOCK-----.' : 'Paste the whole signed text: it starts with -----BEGIN PGP SIGNED MESSAGE----- (or -----BEGIN PGP SIGNATURE----- for a detached signature).');\n if (blocks.length > 1) throw new PgpProofError(`Paste only one ${what === 'key' ? 'public key' : 'signature'}.`);\n return { type: blocks[0][1] ?? 'SIGNED MESSAGE', block: blocks[0][0] };\n}\n\nfunction secondsOf(date: Date | number | null | typeof Infinity): number | null {\n if (date === null || date === Infinity) return null;\n return Math.floor((date instanceof Date ? date.getTime() : date) / 1000);\n}\nconst hexOf = (bytes: Uint8Array) => Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('').toUpperCase();\nconst nowSeconds = () => Math.floor(Date.now() / 1000);\n/** Clearsigned text as it is hashed: trailing whitespace dropped, no final newline. */\nexport function canonicalStatement(statement: string): string {\n return statement.replace(/\\r\\n?/g, '\\n').split('\\n').map(l => l.replace(/[ \\t]+$/, '')).join('\\n').replace(/\\n+$/, '');\n}\n\n/**\n * One paste holding what gpg printed: the signed statement (clearsigned, or a detached signature) and,\n * optionally, the public key. Anything around the blocks (prompts, blank lines) is ignored.\n */\nexport function splitPgpPaste(pasted: string): { signature: string; publicKey?: string } {\n if (typeof pasted !== 'string' || pasted.length > PGP_LIMITS.pastedKey + PGP_LIMITS.pastedSignature) throw new PgpProofError('This is too large. Paste only what gpg printed.');\n if (/PRIVATE KEY BLOCK/.test(pasted)) throw new PgpProofError('This is a PRIVATE key. Never paste it anywhere: Ghostly only needs your public key (gpg --armor --export).');\n const blocks = armorBlocks(pasted).map(m => ({ type: m[1] ?? 'SIGNED MESSAGE', block: m[0] }));\n const keys = blocks.filter(b => b.type === 'PUBLIC KEY BLOCK'), others = blocks.filter(b => b.type !== 'PUBLIC KEY BLOCK');\n if (keys.length > 1) throw new PgpProofError('Paste only one public key.');\n if (others.length === 0) throw new PgpProofError('Paste the signed statement too: it starts with -----BEGIN PGP SIGNED MESSAGE-----.');\n if (others.length > 1) throw new PgpProofError('Paste only one signature.');\n return { signature: others[0].block, publicKey: keys[0]?.block };\n}\n\n/** Refuses a statement that `gpg --clearsign` would alter (dash-escaping, trailing spaces) or that is not plain text. */\nexport function assertSignableStatement(statement: string): void {\n if (!statement || statement.length > 4096 || statement !== canonicalStatement(statement) || /^-/m.test(statement) || /[^\\x20-\\x7e\\n]/.test(statement))\n throw new PgpProofError('Statement is not signable as plain text');\n}\n\n/** Why a key or a subkey is not usable now, in words the person can act on. */\nasync function explainInvalidKey(key: OpenPGP.PublicKey, keyId: OpenPGP.KeyID | undefined, at: Date, cfg: OpenPGP.PartialConfig, when: 'now' | 'signing'): Promise<never> {\n const past = when === 'signing' ? ' when it signed' : '';\n if (await key.isRevoked(undefined, undefined, at, cfg as OpenPGP.Config).catch(() => false))\n throw new PgpProofError(`This key has been revoked${past}. A revoked key proves nothing.`);\n try { await key.verifyPrimaryKey(at, undefined, cfg as OpenPGP.Config); }\n catch (e) {\n const expiry = await key.getExpirationTime(undefined, cfg as OpenPGP.Config).catch(() => null);\n if (expiry instanceof Date && expiry <= at) throw new PgpProofError(`This key expired on ${expiry.toISOString().slice(0, 10)}${past}. Extend its expiry (gpg --quick-set-expire) and export it again.`);\n throw new PgpProofError(`This key is not valid${past}: ${message(e)}`);\n }\n const sub = keyId ? key.getSubkeys(keyId)[0] : undefined;\n if (sub) {\n if (sub.revocationSignatures.length && await sub.verify(at, cfg as OpenPGP.Config).then(() => false, e => /revoked/i.test(message(e))))\n throw new PgpProofError(`The subkey that signed (${sub.getFingerprint().toUpperCase()}) has been revoked${past}.`);\n const expiry = await sub.getExpirationTime(at, cfg as OpenPGP.Config).catch(() => null);\n if (expiry instanceof Date && expiry <= at) throw new PgpProofError(`The subkey that signed expired on ${expiry.toISOString().slice(0, 10)}${past}.`);\n throw new PgpProofError(`The subkey that signed is not a valid signing subkey${past}.`);\n }\n throw new PgpProofError(`The key that signed is not a valid signing key${past}.`);\n}\nconst message = (e: unknown) => e instanceof Error ? e.message : String(e);\n\n/** Parses exactly one public key; refuses private keys, v3/v5 keys and key rings. */\nasync function readOneKey(pgp: Pgp, input: { armored: string } | { binary: Uint8Array }): Promise<OpenPGP.PublicKey> {\n const cfg = config(pgp);\n let keys: OpenPGP.Key[];\n try {\n keys = 'armored' in input ? await pgp.readKeys({ armoredKeys: input.armored, config: cfg }) : await pgp.readKeys({ binaryKeys: input.binary, config: cfg });\n } catch (e) { throw new PgpProofError(`Could not read this public key: ${message(e)}`); }\n if (keys.length !== 1) throw new PgpProofError('Paste one public key, not a key ring.');\n const [key] = keys;\n if (key.isPrivate()) throw new PgpProofError('This is a PRIVATE key. Ghostly only needs your public key (gpg --armor --export).');\n const version = key.keyPacket.version;\n if (version !== 4 && version !== 6) throw new PgpProofError(`OpenPGP v${version} keys are not accepted; use a v4 (RFC 4880) or v6 (RFC 9580) key.`);\n return key as OpenPGP.PublicKey;\n}\n\n/** Valid, unrevoked, self-certified user IDs at `at`. */\nasync function validUserIds(key: OpenPGP.PublicKey, at: Date, cfg: OpenPGP.PartialConfig): Promise<OpenPGP.User[]> {\n const valid: OpenPGP.User[] = [];\n for (const user of key.users) {\n if (!user.userID || valid.length >= PGP_LIMITS.userIds) continue;\n if (await user.verify(at, cfg as OpenPGP.Config).then(() => true, () => false)) valid.push(user);\n }\n return valid;\n}\n\n/**\n * The transferable key a contact receives: primary key and its direct/revocation\n * signatures, valid user IDs with self-certifications only (third-party\n * certifications and photos dropped), and the one subkey that signed.\n */\nasync function minimalKey(pgp: Pgp, key: OpenPGP.PublicKey, signer: OpenPGP.PublicKey | OpenPGP.Subkey, at: Date, cfg: OpenPGP.PartialConfig): Promise<Uint8Array> {\n const packets = new pgp.PacketList<OpenPGP.AnyPacket>();\n packets.push(key.keyPacket, ...key.revocationSignatures);\n // Direct-key self-signatures carry v6 key properties; OpenPGP.js keeps them here.\n const direct = (key as unknown as { directSignatures?: OpenPGP.SignaturePacket[] }).directSignatures ?? [];\n packets.push(...direct);\n for (const user of await validUserIds(key, at, cfg)) packets.push(user.userID!, ...user.selfCertifications, ...user.revocationSignatures);\n if (signer !== key) {\n const sub = signer as OpenPGP.Subkey;\n packets.push(sub.keyPacket, ...sub.bindingSignatures, ...sub.revocationSignatures);\n }\n const bytes = new pgp.PublicKey(packets).write();\n if (bytes.length > PGP_LIMITS.sharedKey) throw new PgpProofError('This key is too large to share, even without third-party certifications. Remove old user IDs or use a smaller key.');\n return bytes;\n}\n\nasync function keyInfo(key: OpenPGP.PublicKey, signer: OpenPGP.PublicKey | OpenPGP.Subkey, algorithm: string, at: Date, cfg: OpenPGP.PartialConfig): Promise<PgpKeyInfo> {\n const expiry = await key.getExpirationTime(undefined, cfg as OpenPGP.Config).catch(() => null);\n const subExpiry = signer !== key ? await (signer as OpenPGP.Subkey).getExpirationTime(at, cfg as OpenPGP.Config).catch(() => null) : null;\n const expires = [secondsOf(expiry), secondsOf(subExpiry)].filter((x): x is number => x !== null);\n return {\n fingerprint: key.getFingerprint().toUpperCase(),\n signingFingerprint: signer.getFingerprint().toUpperCase(),\n version: key.keyPacket.version as 4 | 6,\n algorithm,\n createdAt: secondsOf(key.getCreationTime())!,\n expiresAt: expires.length ? Math.min(...expires) : null,\n userIds: (await validUserIds(key, at, cfg)).map(u => u.userID!.userID),\n };\n}\n\n/** What a pasted public key is, before anything is signed: fingerprint and user IDs to show. */\nexport async function readPgpPublicKey(armored: string, options: { now?: number } = {}): Promise<PgpKeyInfo> {\n if (typeof armored !== 'string' || armored.length > PGP_LIMITS.pastedKey) throw new PgpProofError('This public key is too large. Export it with --export-options export-minimal.');\n const { type, block } = armoredBlock(armored, 'key');\n if (type !== 'PUBLIC KEY BLOCK') throw new PgpProofError('Paste a public key block (gpg --armor --export).');\n const pgp = await loadOpenPgp();\n const cfg = config(pgp);\n const key = await readOneKey(pgp, { armored: block });\n const at = new Date((options.now ?? nowSeconds()) * 1000);\n allowedAlgorithm(key.keyPacket, false);\n let signer: OpenPGP.PublicKey | OpenPGP.Subkey;\n try { signer = await key.getSigningKey(undefined, at, undefined, cfg as OpenPGP.Config) as OpenPGP.PublicKey | OpenPGP.Subkey; }\n catch { return explainInvalidKey(key, undefined, at, cfg, 'now'); }\n return keyInfo(key, signer, allowedAlgorithm(signer.keyPacket, true), at, cfg);\n}\n\nasync function readSignaturePacket(pgp: Pgp, input: { armored: string } | { binary: Uint8Array }): Promise<OpenPGP.SignaturePacket> {\n let sig: OpenPGP.Signature;\n try {\n sig = 'armored' in input ? await pgp.readSignature({ armoredSignature: input.armored, config: config(pgp) }) : await pgp.readSignature({ binarySignature: input.binary, config: config(pgp) });\n } catch (e) { throw new PgpProofError(`Could not read this signature: ${message(e)}`); }\n const packets = sig.packets.filterByTag(pgp.enums.packet.signature) as unknown as OpenPGP.SignaturePacket[];\n if (sig.packets.length !== 1 || packets.length !== 1) throw new PgpProofError('Sign with one key only: this signature has several signers.');\n return packets[0];\n}\n\n/**\n * The core check, shared by the person (on paste) and the contact (on receipt):\n * one signature over the statement, by a key or signing subkey of `key` that is\n * valid both when it signed and now, with allowed algorithms and hash.\n */\nasync function verifyPacket(pgp: Pgp, key: OpenPGP.PublicKey, sig: OpenPGP.SignaturePacket, statement: string, options: PgpVerifyOptions): Promise<{ info: PgpVerified; signer: OpenPGP.PublicKey | OpenPGP.Subkey }> {\n const cfg = config(pgp);\n const now = options.now ?? nowSeconds();\n const at = new Date(now * 1000);\n const { signature } = pgp.enums;\n if (sig.signatureType !== signature.text && sig.signatureType !== signature.binary) throw new PgpProofError('This is not a signature over a text or a file.');\n if (sig.hashAlgorithm === null || !ALLOWED_HASHES.has(sig.hashAlgorithm)) throw new PgpProofError('This signature uses a weak hash (SHA-1 or older). Sign again with gpg\\'s default (SHA-256 or stronger): --digest-algo SHA256.');\n if (!sig.created) throw new PgpProofError('This signature has no creation time.');\n const signedAt = secondsOf(sig.created)!;\n if (signedAt > now + PGP_CLOCK_SKEW) throw new PgpProofError('This signature is dated in the future. Check the clock of the machine that signed.');\n if (options.expectedFingerprint !== undefined && key.getFingerprint().toUpperCase() !== options.expectedFingerprint.toUpperCase())\n throw new PgpProofError('This key is not the one the statement names.');\n const issuer = key.getKeys(sig.issuerKeyID)[0];\n if (!issuer) throw new PgpProofError(`This signature was made by another key (${sig.issuerKeyID.toHex().toUpperCase()}), not the public key given.`);\n if (sig.issuerFingerprint && hexOf(sig.issuerFingerprint) !== issuer.getFingerprint().toUpperCase())\n throw new PgpProofError('This signature was made by another key, not the public key given.');\n allowedAlgorithm(key.keyPacket, false);\n // Valid now (not expired, not revoked, signing-capable with its back-signature)…\n let signer: OpenPGP.PublicKey | OpenPGP.Subkey;\n try { signer = await key.getSigningKey(sig.issuerKeyID, at, undefined, cfg as OpenPGP.Config) as OpenPGP.PublicKey | OpenPGP.Subkey; }\n catch { return explainInvalidKey(key, sig.issuerKeyID, at, cfg, 'now'); }\n const algorithm = allowedAlgorithm(signer.keyPacket, true);\n // …and when it signed.\n const created = new Date(Math.min(signedAt, now) * 1000);\n try { await key.getSigningKey(sig.issuerKeyID, created, undefined, cfg as OpenPGP.Config); }\n catch { return explainInvalidKey(key, sig.issuerKeyID, created, cfg, 'signing'); }\n // After the key checks, so an expired or revoked key is reported as such.\n if (options.signedAfter !== undefined && signedAt < options.signedAfter - PGP_CLOCK_SKEW) throw new PgpProofError('This signature is older than the statement it should sign.');\n // A detached signature over a file holds its final newline; clearsigned text does not.\n const text = canonicalStatement(statement);\n const packets = new pgp.PacketList<OpenPGP.AnyPacket>(); packets.push(sig);\n const detached = new pgp.Signature(packets as OpenPGP.PacketList<OpenPGP.SignaturePacket>);\n let verified = false;\n for (const candidate of [text, `${text}\\n`, `${text}\\r\\n`]) {\n const message = await pgp.createMessage({ binary: utf8Encode(candidate) });\n const result = await pgp.verify({ message, signature: detached, verificationKeys: key, date: new Date(Math.max(now, signedAt) * 1000), config: cfg });\n if (result.signatures.length === 1 && await result.signatures[0].verified.then(() => true, () => false)) { verified = true; break; }\n }\n if (!verified) throw new PgpProofError('This signature is not over the Ghostly statement (it signs other text, or it was changed).');\n return { info: { ...await keyInfo(key, signer, algorithm, at, cfg), signedAt }, signer };\n}\n\n/**\n * The person's side: checks what they pasted against the statement and\n * produces the evidence to share. Either a clearsigned statement or an\n * armored detached signature is accepted.\n */\nexport async function preparePgpEvidence(input: { statement: string; signature: string; publicKey: string }, options: PgpVerifyOptions = {}): Promise<{ evidence: PgpEvidence; verified: PgpVerified }> {\n if (typeof input.signature !== 'string' || input.signature.length > PGP_LIMITS.pastedSignature) throw new PgpProofError('This signature is too large. Paste only the output of gpg --clearsign.');\n if (typeof input.publicKey !== 'string' || input.publicKey.length > PGP_LIMITS.pastedKey) throw new PgpProofError('This public key is too large. Export it with --export-options export-minimal.');\n const { type, block } = armoredBlock(input.signature, 'signature');\n const keyBlock = armoredBlock(input.publicKey, 'key');\n if (keyBlock.type !== 'PUBLIC KEY BLOCK') throw new PgpProofError('Paste a public key block (gpg --armor --export).');\n if (type === 'MESSAGE') throw new PgpProofError('This is a signed message (gpg --sign). Use gpg --clearsign, or --detach-sign --armor.');\n if (type === 'PUBLIC KEY BLOCK') throw new PgpProofError('This is a public key; paste it in the public key field and the signature here.');\n const pgp = await loadOpenPgp();\n const key = await readOneKey(pgp, { armored: keyBlock.block });\n let sig: OpenPGP.SignaturePacket;\n if (type === 'SIGNED MESSAGE') {\n let cleartext: OpenPGP.CleartextMessage;\n try { cleartext = await pgp.readCleartextMessage({ cleartextMessage: block, config: config(pgp) }); }\n catch (e) { throw new PgpProofError(`Could not read this signed text: ${message(e)}`); }\n if (canonicalStatement(cleartext.getText()) !== canonicalStatement(input.statement))\n throw new PgpProofError('This signs different text. Sign exactly the statement shown, without editing it.');\n // Typed as private in OpenPGP.js's declarations, public at runtime.\n const packets = (cleartext as unknown as { signature: OpenPGP.Signature }).signature.packets;\n if (packets.length !== 1 || !(packets[0] instanceof pgp.SignaturePacket)) throw new PgpProofError('Sign with one key only: this signature has several signers.');\n sig = packets[0];\n } else if (type === 'SIGNATURE') sig = await readSignaturePacket(pgp, { armored: block });\n else throw new PgpProofError('Paste the output of gpg --clearsign (or gpg --detach-sign --armor).');\n const { info, signer } = await verifyPacket(pgp, key, sig, input.statement, options);\n const single = new pgp.PacketList<OpenPGP.AnyPacket>(); single.push(sig);\n const signature = new pgp.Signature(single as OpenPGP.PacketList<OpenPGP.SignaturePacket>).write();\n if (signature.length > PGP_LIMITS.sharedSignature) throw new PgpProofError('This signature is too large to share.');\n const at = new Date((options.now ?? nowSeconds()) * 1000);\n const evidence: PgpEvidence = { scheme: PGP_SCHEME, key: toBase64Url(await minimalKey(pgp, key, signer, at, config(pgp))), signature: toBase64Url(signature) };\n return { evidence, verified: info };\n}\n\nconst b64 = /^[A-Za-z0-9_-]+$/;\n/** Untrusted evidence off the wire: exact shape, bounded, canonical base64url. Checked before any OpenPGP parsing. */\nexport function parsePgpEvidence(raw: unknown): PgpEvidence {\n const e = raw as PgpEvidence;\n if (!e || typeof e !== 'object' || Array.isArray(e) || e.scheme !== PGP_SCHEME || Object.keys(e).sort().join(',') !== 'key,scheme,signature' ||\n typeof e.key !== 'string' || typeof e.signature !== 'string' || !b64.test(e.key) || !b64.test(e.signature) ||\n e.key.length > Math.ceil(PGP_LIMITS.sharedKey * 4 / 3) || e.signature.length > Math.ceil(PGP_LIMITS.sharedSignature * 4 / 3))\n throw new PgpProofError('Invalid OpenPGP proof');\n let canonical: boolean;\n try { canonical = toBase64Url(fromBase64Url(e.key)) === e.key && toBase64Url(fromBase64Url(e.signature)) === e.signature; } catch { canonical = false; }\n if (!canonical) throw new PgpProofError('Invalid OpenPGP proof encoding');\n return { scheme: PGP_SCHEME, key: e.key, signature: e.signature };\n}\n\n/** The contact's side: verifies shared evidence against the statement their own app built. */\nexport async function verifyPgpEvidence(raw: PgpEvidence, statement: string, options: PgpVerifyOptions = {}): Promise<PgpVerified> {\n const evidence = parsePgpEvidence(raw);\n const keyBytes = fromBase64Url(evidence.key), sigBytes = fromBase64Url(evidence.signature);\n const pgp = await loadOpenPgp();\n const key = await readOneKey(pgp, { binary: keyBytes });\n const sig = await readSignaturePacket(pgp, { binary: sigBytes });\n return (await verifyPacket(pgp, key, sig, statement, options)).info;\n}\n\n/** Groups of four, as gpg prints fingerprints. */\nexport function formatFingerprint(fingerprint: string): string {\n return fingerprint.replace(/(.{4})/g, '$1 ').trim();\n}\n\n/** keys.openpgp.org publishes a user ID only after its owner confirms the email. */\nexport const KEYSERVER = 'https://keys.openpgp.org';\nexport interface KeyserverKey { armored: string; source: typeof KEYSERVER; fetchedAt: number }\n/**\n * Fetches a public key from keys.openpgp.org. Only ever called when the person\n * asks for it, after the UI said this contacts that server.\n */\nexport async function fetchKeyFromKeyserver(query: string, options: { signal?: AbortSignal; fetch?: typeof fetch; now?: number } = {}): Promise<KeyserverKey> {\n const q = query.trim().replace(/\\s+/g, '').replace(/^0x/i, '');\n let path: string;\n if (/^[A-Fa-f0-9]{40}$/.test(q) || /^[A-Fa-f0-9]{64}$/.test(q)) path = `/vks/v1/by-fingerprint/${q.toUpperCase()}`;\n else if (/^[^\\s@<>]{1,64}@[^\\s@<>]{1,190}$/.test(query.trim())) path = `/vks/v1/by-email/${encodeURIComponent(query.trim())}`;\n else throw new PgpProofError('Enter a full fingerprint (40 hex characters) or an email address.');\n const response = await (options.fetch ?? fetch)(`${KEYSERVER}${path}`, { signal: options.signal, credentials: 'omit', referrerPolicy: 'no-referrer', redirect: 'error' });\n if (response.status === 404) throw new PgpProofError('keys.openpgp.org has no key for this. Paste your public key instead.');\n if (!response.ok) throw new PgpProofError(`keys.openpgp.org answered ${response.status}. Paste your public key instead.`);\n const length = Number(response.headers.get('content-length') ?? 0);\n if (length > PGP_LIMITS.keyserverResponse) throw new PgpProofError('The key from keys.openpgp.org is too large.');\n const armored = await response.text();\n if (armored.length > PGP_LIMITS.keyserverResponse) throw new PgpProofError('The key from keys.openpgp.org is too large.');\n return { armored, source: KEYSERVER, fetchedAt: options.now ?? nowSeconds() };\n}\n\n/**\n * Emails keys.openpgp.org vouches for on this key: it publishes a user ID only\n * once the email's owner confirmed it, so every user ID in its copy is one the\n * keyserver checked. That is the keyserver's check, and says nothing about a name.\n */\nexport async function keyserverVerifiedEmails(keyserverArmored: string, fingerprint: string, options: { now?: number } = {}): Promise<string[]> {\n const info = await readPgpPublicKey(keyserverArmored, options);\n if (info.fingerprint !== fingerprint.toUpperCase()) throw new PgpProofError('keys.openpgp.org returned a different key.');\n const emails = info.userIds.map(u => /<([^<>\\s]+@[^<>\\s]+)>\\s*$/.exec(u)?.[1] ?? (/^[^<>\\s]+@[^<>\\s]+$/.test(u) ? u : undefined));\n return [...new Set(emails.filter((e): e is string => !!e).map(e => e.toLowerCase()))];\n}\n","import type { IdentityStatement } from \"@ghostly/core\";\nimport type { ExternalToolSigner, IdentityProofProvider, SignerInstructions } from \"../contract\";\nimport {\n KEYSERVER, PgpProofError, assertSignableStatement, fetchKeyFromKeyserver, formatFingerprint, keyserverVerifiedEmails, parsePgpEvidence,\n preparePgpEvidence, splitPgpPaste, verifyPgpEvidence, type PgpEvidence,\n} from \"../openpgp\";\n\n/**\n * OpenPGP keys, signed with the person's own gpg — a YubiKey or another OpenPGP card works unchanged,\n * since gpg drives it. The subject is the primary key's fingerprint. See docs/wisps/305-openpgp.md.\n */\n\n/** A v4 (40 hex) or v6 (64 hex) fingerprint, as gpg prints it or not: spaces and 0x are dropped. */\nexport function normalizeFingerprint(input: string): string {\n const value = input.trim().replace(/^0x/i, \"\").replace(/\\s+/g, \"\").toUpperCase();\n if (/^[A-F0-9]{40}$/.test(value) || /^[A-F0-9]{64}$/.test(value)) return value;\n if (/^[A-F0-9]{8}$|^[A-F0-9]{16}$/.test(value)) throw new PgpProofError(\"Use the full fingerprint, not a key ID: gpg --fingerprint shows it.\");\n throw new PgpProofError(\"Enter your key's fingerprint: 40 hex characters (gpg --fingerprint).\");\n}\n\n/** The user ID is its holder's own words: shown with that caveat, never as a checked name. */\nconst USER_ID_SOURCE = \"User ID on the key, written by its holder: not proof that the name or email is theirs\";\nconst KEYSERVER_SOURCE = \"Email confirmed to keys.openpgp.org by its owner: the keyserver's check, not Ghostly's\";\n\nfunction steps(statement: IdentityStatement, exportKey: boolean): SignerInstructions {\n assertSignableStatement(statement.text);\n // The statement is one line of space-separated fields with no quote in it, so it is safe in single quotes.\n if (statement.text.includes(\"'\")) throw new PgpProofError(\"Statement is not signable as plain text\");\n const fpr = normalizeFingerprint(statement.binding.subject);\n const sign = `gpg --local-user ${fpr} --clearsign --output - ghostly-identity.txt`;\n return {\n steps: [\n { text: \"Save the statement to a file (it is one line, with no newline at the end):\", copy: `printf '%s' '${statement.text}' > ghostly-identity.txt` },\n exportKey\n ? { text: \"Sign it and export your public key, then copy everything this prints:\", copy: `${sign} && gpg --armor --export --export-options export-minimal ${fpr}` }\n : { text: \"Sign it, then copy everything this prints:\", copy: sign },\n { text: \"On a YubiKey or another OpenPGP card, gpg asks for the card's PIN, and for a touch if the card requires one. Without a shell, save the statement below in a text file and sign that file.\", copy: statement.text },\n ],\n paste: { label: exportKey ? \"Signed statement and public key\" : \"Signed statement\", placeholder: \"-----BEGIN PGP SIGNED MESSAGE-----\", multiline: true },\n };\n}\n\nasync function evidenceFrom(statement: IdentityStatement, signature: string, publicKey: string): Promise<PgpEvidence> {\n const { evidence } = await preparePgpEvidence({ statement: statement.text, signature, publicKey },\n { expectedFingerprint: normalizeFingerprint(statement.binding.subject), signedAfter: statement.binding.issuedAt });\n return evidence;\n}\n\nconst gpg: ExternalToolSigner<PgpEvidence> = {\n id: \"gpg\", kind: \"external-tool\", label: \"gpg\",\n description: \"Sign with gpg and paste what it prints, public key included. Nothing is looked up.\",\n instructions: statement => steps(statement, true),\n async parse(pasted, statement) {\n const { signature, publicKey } = splitPgpPaste(pasted);\n if (!publicKey) throw new PgpProofError(\"Paste your public key too: the second command prints it (-----BEGIN PGP PUBLIC KEY BLOCK-----).\");\n return evidenceFrom(statement, signature, publicKey);\n },\n};\n\nconst gpgKeyserver: ExternalToolSigner<PgpEvidence> = {\n id: \"gpg-keys-openpgp-org\", kind: \"external-tool\", label: \"gpg, key from keys.openpgp.org\",\n description: \"Sign with gpg and paste the signature; Ghostly then fetches your public key from keys.openpgp.org, which learns which key was looked up.\",\n instructions: statement => steps(statement, false),\n async parse(pasted, statement) {\n const { signature, publicKey } = splitPgpPaste(pasted);\n // Only here, on the person's choice of this signer, is the keyserver contacted.\n const key = publicKey ?? (await fetchKeyFromKeyserver(statement.binding.subject)).armored;\n return evidenceFrom(statement, signature, key);\n },\n};\n\nexport const openpgp: IdentityProofProvider<PgpEvidence> = {\n id: \"openpgp\",\n label: \"OpenPGP key\",\n category: \"self-custodied\",\n summary: \"Sign with gpg and paste the result\",\n description: \"Proves you hold an OpenPGP key: sign the statement with gpg (a YubiKey works too) and paste the result.\",\n limits: \"Holding a key does not prove the names or emails in its user IDs.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Key fingerprint\", placeholder: \"40 hex characters, from gpg --fingerprint\",\n help: \"The primary key's full fingerprint. Holding a key does not prove the names or emails in its user IDs.\",\n normalize: normalizeFingerprint,\n short: fpr => `…${formatFingerprint(fpr.slice(-16))}`,\n },\n // The fingerprint URI scheme OpenPGP apps share keys with.\n publicUri: fpr => `openpgp4fpr:${fpr}`,\n validity: { defaultDays: 90, maxDays: 365 },\n privacy: `Nothing: the signature is checked on this device. ${KEYSERVER.replace(\"https://\", \"\")} is contacted only if you choose it for your key, or ask it to confirm emails.`,\n signers: [gpg, gpgKeyserver],\n lookupLabel: \"Check emails with keys.openpgp.org\",\n parseEvidence: raw => parsePgpEvidence(raw),\n async verify(statement, evidence, ctx) {\n ctx.signal.throwIfAborted();\n const { binding } = statement;\n const verified = await verifyPgpEvidence(evidence, statement.text, { now: ctx.now, signedAfter: binding.issuedAt, expectedFingerprint: normalizeFingerprint(binding.subject) });\n const subkey = verified.signingFingerprint !== verified.fingerprint;\n const [userId] = verified.userIds;\n return {\n subject: verified.fingerprint,\n source: `OpenPGP signature (${verified.algorithm}${subkey ? \" signing subkey\" : \"\"}, v${verified.version} key)`,\n ...(verified.expiresAt !== null && verified.expiresAt < binding.expiresAt ? { expiresAt: verified.expiresAt } : {}),\n ...(userId ? { display: { name: userId, source: USER_ID_SOURCE, fetchedAt: ctx.now } } : {}),\n };\n },\n /** On request only: which emails on this key keys.openpgp.org confirmed, and whether it holds a revocation. */\n async lookupDisplay(subject, { signal }) {\n const fpr = normalizeFingerprint(subject);\n const found = await fetchKeyFromKeyserver(fpr, { signal });\n const emails = await keyserverVerifiedEmails(found.armored, fpr);\n if (!emails.length) return undefined;\n return { name: emails[0], url: `${KEYSERVER}/search?q=${fpr}`, source: KEYSERVER_SOURCE, fetchedAt: found.fetchedAt };\n },\n};\n","import { parseSshPublicKey, sameSshKey, type SshPublicKey } from '@ghostly/core';\nimport type { IdentityFetch } from './contract';\n\n/** \"This account publishes that key\", without OAuth: GitHub and GitLab publish every account's SSH\n * public keys, and only the account holder can add one. The verifier's own lookup, through the\n * engine's bounded `ctx.fetch`; it tells the forge (and the network) which account was checked. */\n\nexport type SshForge = 'github' | 'gitlab';\nexport const SSH_FORGE_LABEL: Record<SshForge, string> = { github: 'GitHub', gitlab: 'GitLab' };\n/** The only host each forge lookup contacts, for the privacy line. */\nexport const SSH_FORGE_HOST: Record<SshForge, string> = { github: 'api.github.com', gitlab: 'gitlab.com' };\n\nexport type SshForgeStatus = 'linked' | 'not-listed' | 'no-account';\n\nconst MAX_BODY = 256 * 1024;\nconst MAX_KEYS = 200;\n\nexport function validForgeLogin(forge: SshForge, login: string): boolean {\n if (typeof login !== 'string') return false;\n // GitHub: 1-39 alphanumerics or single inner hyphens. GitLab: letters, digits, _ . - ;\n // not starting with - and not ending in .git/.atom. Capped at 64 so it fits every label.\n return forge === 'github'\n ? /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/.test(login)\n : /^[A-Za-z0-9_.][A-Za-z0-9_.-]{0,63}$/.test(login) && !/\\.(?:git|atom)$/i.test(login);\n}\n\nasync function get(fetcher: IdentityFetch, url: string, signal?: AbortSignal): Promise<{ status: 404 } | { status: 200; body: unknown }> {\n const response = await fetcher(url, { maxBytes: MAX_BODY, signal });\n if (response.status === 404) return { status: 404 };\n if (response.status === 403 || response.status === 429) throw new Error(`${new URL(url).host} is limiting lookups; check again later`);\n if (response.status !== 200) throw new Error(`${new URL(url).host} answered HTTP ${response.status}`);\n try { return { status: 200, body: JSON.parse(response.text) }; } catch { throw new Error(`Unexpected answer from ${new URL(url).host}`); }\n}\n\nfunction keysFrom(body: unknown): SshPublicKey[] {\n if (!Array.isArray(body)) throw new Error('Unexpected key list');\n const keys: SshPublicKey[] = [];\n for (const entry of body.slice(0, MAX_KEYS)) {\n const line = (entry as { key?: unknown } | null)?.key;\n if (typeof line !== 'string') continue;\n try { keys.push(parseSshPublicKey(line)); } catch { /* A key type Ghostly cannot verify never matches. */ }\n }\n return keys;\n}\n\n/** The account's published keys, or null when there is no such account. */\nasync function listKeys(forge: SshForge, login: string, fetcher: IdentityFetch, signal?: AbortSignal): Promise<SshPublicKey[] | null> {\n if (forge === 'github') {\n const r = await get(fetcher, `https://api.github.com/users/${encodeURIComponent(login)}/keys?per_page=100`, signal);\n return r.status === 404 ? null : keysFrom(r.body);\n }\n const users = await get(fetcher, `https://gitlab.com/api/v4/users?username=${encodeURIComponent(login)}`, signal);\n const list = users.status === 200 && Array.isArray(users.body) ? users.body as { id?: unknown; username?: unknown }[] : [];\n const user = list.find(u => typeof u?.username === 'string' && u.username.toLowerCase() === login.toLowerCase());\n if (!user) return null;\n if (!Number.isSafeInteger(user.id) || (user.id as number) <= 0) throw new Error('Unexpected answer from gitlab.com');\n const r = await get(fetcher, `https://gitlab.com/api/v4/users/${user.id as number}/keys?per_page=100`, signal);\n return r.status === 404 ? null : keysFrom(r.body);\n}\n\n// Only concurrent identical lookups share a request. What stays checked is the verifier's stored\n// result, which the app offers to re-check once the provider's `recheck` time has passed.\nconst inFlight = new WeakMap<IdentityFetch, Map<string, Promise<SshPublicKey[] | null>>>();\n\n/** Whether `key` is one of `login`'s published SSH keys. Throws when the forge cannot be asked. */\nexport async function checkSshForge(forge: SshForge, login: string, key: Pick<SshPublicKey, 'blob'>, fetcher: IdentityFetch, signal?: AbortSignal): Promise<SshForgeStatus> {\n if (!validForgeLogin(forge, login)) throw new Error(`Not a ${SSH_FORGE_LABEL[forge]} username`);\n let pending = inFlight.get(fetcher);\n if (!pending) inFlight.set(fetcher, pending = new Map());\n const map = pending, id = `${forge}:${login.toLowerCase()}`;\n let lookup = map.get(id);\n if (!lookup) {\n lookup = listKeys(forge, login, fetcher, signal).finally(() => map.delete(id));\n map.set(id, lookup);\n }\n const keys = await lookup;\n if (!keys) return 'no-account';\n return keys.some(k => sameSshKey(k, key)) ? 'linked' : 'not-listed';\n}\n","import {\n parseSshPublicKey, parseSshSignature, sshSignCommand, SshSigError, SSHSIG_MAX_ARMOR, SSHSIG_NAMESPACE, utf8Encode, verifySshSignature,\n type IdentityStatement, type SshSignatureCheck,\n} from \"@ghostly/core\";\nimport type { ExternalToolSigner, IdentityProofProvider, VerifyContext } from \"../contract\";\nimport { checkSshForge, SSH_FORGE_HOST, SSH_FORGE_LABEL, validForgeLogin, type SshForge } from \"../sshForges\";\n\n/**\n * SSH keys (draft WISP 307-ssh). The person signs the binding statement once with their own\n * `ssh-keygen -Y sign -n ghostly` and pastes the armored SSHSIG; the private key never enters Ghostly.\n *\n * - `ssh` the subject is the key's SHA-256 fingerprint; checked on this device, no network.\n * - `ssh-github` the subject is a GitHub account: the signing key must be one of the SSH keys GitHub\n * - `ssh-gitlab` publishes for it (GitLab alike). No OAuth: only the account holder can add a key, so\n * \"this account lists the key\" links the two. Re-checked, since keys get removed.\n */\n\nexport interface SshEvidence { signature: string }\n\nconst FINGERPRINT = /^SHA256:[A-Za-z0-9+/]{43}$/;\n\nfunction normalizeKeySubject(input: string): string {\n const value = input.trim();\n if (FINGERPRINT.test(value)) return value;\n try { return parseSshPublicKey(value).fingerprint; }\n catch (e) {\n // Say why a real key is refused (a DSA key, a small RSA key); anything else gets the instruction.\n if (e instanceof SshSigError && /^(Unsupported SSH key type|RSA keys must)/.test(e.message)) throw Object.assign(new Error(e.message), { cause: e });\n throw Object.assign(new Error(\"Paste your public key (the one line in ~/.ssh/id_ed25519.pub) or its SHA256: fingerprint\"), { cause: e });\n }\n}\n\nfunction normalizeLogin(forge: SshForge, input: string): string {\n const login = input.trim().replace(/^@/, \"\").toLowerCase();\n if (!validForgeLogin(forge, login)) throw new Error(`Enter a ${SSH_FORGE_LABEL[forge]} username`);\n return login;\n}\n\n/** The armor, re-wrapped canonically, so evidence is the same whatever the clipboard did to it. */\nfunction canonicalArmor(pasted: string): string {\n const parsed = parseSshSignature(pasted);\n if (parsed.namespace !== SSHSIG_NAMESPACE) throw new Error(`That signature is for \"${parsed.namespace}\", not \"${SSHSIG_NAMESPACE}\": run the command with -n ${SSHSIG_NAMESPACE}`);\n const body = pasted.replace(/\\r\\n/g, \"\\n\").trim().split(\"\\n\").map(l => l.trim()).slice(1, -1).join(\"\");\n return `-----BEGIN SSH SIGNATURE-----\\n${body.match(/.{1,70}/g)!.join(\"\\n\")}\\n-----END SSH SIGNATURE-----\\n`;\n}\n\n/** Exactly the statement; or with one trailing newline, which an editor adds when the statement is\n * saved to a file and signed there (the Windows route). Nothing else is accepted. */\nasync function verifyStatementSignature(statement: IdentityStatement, evidence: SshEvidence): Promise<SshSignatureCheck> {\n try { return await verifySshSignature(evidence.signature, statement.bytes); }\n catch (e) {\n if (!(e instanceof SshSigError) || !/does not match this statement/.test(e.message)) throw e;\n try { return await verifySshSignature(evidence.signature, utf8Encode(`${statement.text}\\n`)); }\n catch (again) { throw Object.assign(new Error(\"The signature is not over this statement: copy the command again and sign exactly what it shows\"), { cause: again }); }\n }\n}\n\nconst keyKind = (c: SshSignatureCheck) => c.securityKey\n ? `security key, touch${c.securityKey.userVerified ? \" and PIN\" : \"\"} confirmed`\n : c.key.type === \"ssh-rsa\" ? `RSA ${c.key.bits}` : c.key.type.replace(/^ssh-|^ecdsa-sha2-/, \"\");\n\nfunction sshKeygenSigner(forAccount?: SshForge): ExternalToolSigner<SshEvidence> {\n return {\n id: \"ssh-keygen\", kind: \"external-tool\", label: \"ssh-keygen\",\n description: forAccount\n ? `Sign with an SSH key that your ${SSH_FORGE_LABEL[forAccount]} account lists (Settings → SSH keys).`\n : \"Sign with OpenSSH's ssh-keygen (8.1 or later): a key file, ssh-agent, or a FIDO security key.\",\n instructions: statement => {\n let command: string | undefined;\n try { command = sshSignCommand(statement.text); } catch { command = undefined; }\n return {\n steps: [\n ...(command ? [{ text: \"Run this in a terminal (macOS, Linux, or Git Bash/WSL on Windows). If your key is not ~/.ssh/id_ed25519, change the path after -f; a .pub path works when the private key is in ssh-agent or on a security key.\", copy: command }] : []),\n { text: `${command ? \"Or save\" : \"Save\"} this statement to a file, run ssh-keygen -Y sign -n ${SSHSIG_NAMESPACE} -f <your key> <file>, and paste the <file>.sig it writes.`, copy: statement.text },\n { text: \"Paste the whole signature it prints, from -----BEGIN SSH SIGNATURE----- to -----END SSH SIGNATURE-----. Your private key never leaves your computer.\" },\n ],\n paste: { label: \"SSH signature\", placeholder: \"-----BEGIN SSH SIGNATURE-----\", multiline: true },\n };\n },\n parse(pasted, statement) {\n const signature = canonicalArmor(pasted);\n if (!forAccount) {\n const { key } = parseSshSignature(signature);\n if (key.fingerprint !== statement.binding.subject) throw new Error(`That signature was made by ${key.fingerprint}, not the key you entered (${statement.binding.subject})`);\n }\n return { signature };\n },\n };\n}\n\nfunction parseSshEvidence(raw: unknown): SshEvidence {\n const e = raw as SshEvidence;\n if (!e || typeof e !== \"object\" || Array.isArray(e) || Object.keys(e).join(\",\") !== \"signature\" ||\n typeof e.signature !== \"string\" || e.signature.length > SSHSIG_MAX_ARMOR) throw new Error(\"That is not an SSH signature\");\n return { signature: e.signature };\n}\n\nconst shortFingerprint = (f: string) => `${f.slice(0, 15)}…${f.slice(-4)}`;\n\nexport const ssh: IdentityProofProvider<SshEvidence> = {\n id: \"ssh\",\n label: \"SSH key\",\n category: \"self-custodied\",\n summary: \"Sign once with ssh-keygen\",\n description: \"Proves you hold an SSH key, including FIDO security keys: sign the statement once with ssh-keygen and paste the signature.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Public key\", placeholder: \"ssh-ed25519 AAAA… (or SHA256:…)\",\n help: \"Your public key, the one line in the .pub file. Contacts see its fingerprint.\",\n normalize: normalizeKeySubject, short: shortFingerprint,\n },\n validity: { defaultDays: 90, maxDays: 365 },\n privacy: \"Nothing: the signature is checked on this device. The key's fingerprint links every chat you share it in.\",\n signers: [sshKeygenSigner()],\n parseEvidence: parseSshEvidence,\n async verify(statement, evidence) {\n const check = await verifyStatementSignature(statement, evidence);\n if (check.key.fingerprint !== statement.binding.subject) throw new Error(\"Signed by another SSH key\");\n return { subject: statement.binding.subject, source: `SSH signature (${keyKind(check)})` };\n },\n};\n\nfunction forgeProvider(forge: SshForge): IdentityProofProvider<SshEvidence> {\n const label = SSH_FORGE_LABEL[forge], host = SSH_FORGE_HOST[forge];\n return {\n id: `ssh-${forge}`,\n label: `${label} (SSH key)`,\n category: \"self-custodied\",\n summary: `A key your ${label} account lists`,\n description: `Proves you hold an SSH key your ${label} account publishes: sign the statement once with ssh-keygen. No ${label} login.`,\n limits: `Does not prove a ${label} login: only that a key the account lists is yours, for as long as it stays listed.`,\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: { label: `${label} username`, placeholder: forge === \"github\" ? \"octocat\" : \"username\", normalize: input => normalizeLogin(forge, input) },\n publicUri: login => `https://${forge === \"github\" ? \"github.com\" : \"gitlab.com\"}/${login}`,\n validity: { defaultDays: 90, maxDays: 365 },\n // Keys get removed from accounts; the contact's app offers \"Check again\" after this.\n recheck: { afterSeconds: 600 },\n privacy: `Your contact's app asks ${host} for this account's public SSH keys, when you share it and on each re-check: ${label} learns the account was looked up, and from which IP address. So does this app when you add it.`,\n signers: [sshKeygenSigner(forge)],\n parseEvidence: parseSshEvidence,\n async verify(statement, evidence, ctx: VerifyContext) {\n const login = statement.binding.subject;\n if (normalizeLogin(forge, login) !== login) throw new Error(`Not a ${label} username`);\n const check = await verifyStatementSignature(statement, evidence);\n const status = await checkSshForge(forge, login, check.key, ctx.fetch, ctx.signal);\n if (status === \"no-account\") throw new Error(`${label} has no account ${login}`);\n if (status === \"not-listed\") throw new Error(`${label}: ${login} does not list the key that signed (${check.key.fingerprint})`);\n return { subject: login, source: `${label}: ${login} (via published SSH key)` };\n },\n };\n}\n\nexport const sshGithub = forgeProvider(\"github\");\nexport const sshGitlab = forgeProvider(\"gitlab\");\n","import type { FoundUpdate } from \"../../../apps/ui/src/lib/updates\";\nimport type { EngineEvent, RpcRequest, RpcResponse } from \"./shared/rpc\";\nimport type { OidcPlatform } from \"./proofs/oidc/providers\";\nimport type { OidcWindow } from \"./proofs/oidc/flow\";\nimport type { AtprotoHost } from \"./proofs/atproto/oauth\";\nimport type { CallMedia } from \"@ghostly/core\";\n\n/**\n * What differs between the places this peer runs. The extension keeps the\n * peer in an offscreen document and reaches it through extension messaging;\n * the web app runs it in the page. Everything else is shared.\n */\nexport interface EngineConnection {\n send(request: RpcRequest): void;\n}\n\n/**\n * How a client finds a new version of itself and puts it in place. The web\n * app reloads the tab; an extension from the store reloads itself; an unpacked\n * extension can only point at the download. A host that leaves this out has no\n * updater, and the UI never mentions one.\n */\nexport interface UpdateSource {\n /** Where someone installing by hand should go. */\n downloadUrl: string;\n /** The newest published version, or null when this client already runs it. */\n check(): Promise<FoundUpdate | null>;\n /** Never called for `apply: \"manual\"`. `onProgress` is used where the platform reports it. */\n install(update: FoundUpdate, onProgress?: (fraction: number) => void): Promise<void>;\n}\n\nexport interface BrowserHost {\n version: string;\n /** Something the user should know about this client, shown in the sidebar. */\n notice?: string;\n /** Left out where this client has no way to learn about new versions. */\n updates?: UpdateSource;\n /**\n * Where this host frames mini-apps (WISP 1200): its runner page, served with the runner's policy as a header. Left\n * out where it runs none (the extension, until it gets its own runner).\n */\n appRunner?: string;\n /** The runner for apps granted `internet` (WISP 1200 · Permissions), under a policy that allows HTTPS and WSS. */\n appNetRunner?: string;\n /**\n * The host serves its runner itself, always with the runner's policy (Desktop: Rust's `ghostly-app` scheme, to app\n * windows only), so the page does not ask for its header. Left out where a web server serves it (it may be one\n * without the header).\n */\n appRunnerServed?: boolean;\n features: {\n /** Can this host reach web apps on the user's machine? A web page only can if they allow it with CORS. */\n shareLocalServices: boolean;\n /** Can this host show a contact's web app on an origin of its own? */\n openServices: boolean;\n /** Can this host restart as another local profile (WISP 04)? The page's own peer, or the extension's offscreen one. */\n profiles?: boolean;\n };\n /**\n * Starts a new peer in place of one that stopped for good (\"Clear all data\"). Left out where the peer lives in the\n * page: reloading the page starts it again. The extension's peer outlives its pages, so it is restarted on purpose.\n */\n restartEngine?(): Promise<void>;\n /** Reaches the peer. `onDisconnect` fires when it goes away; the client then connects again. */\n connect(onMessage: (message: EngineEvent | RpcResponse) => void, onDisconnect: () => void): Promise<EngineConnection>;\n /**\n * Asks the user for access to a local origin, where the platform has such a thing: `originPattern` is the\n * extension's host pattern (`http://localhost/*`), `origin` the exact address with its port (`http://localhost:3400`),\n * which the desktop app has Rust ask about in a native dialog.\n */\n requestLocalAccess(originPattern: string, origin: string): Promise<boolean>;\n /** The profile no longer shares any app at `origin` (desktop: off Rust's list of addresses it may reach). */\n forgetLocalAccess?(origin: string): Promise<void>;\n /** `tag` (`viewerTag` of this side's key for the chat) goes in the app's origin, so each profile's storage for it is its own. */\n openService(peerPubKeyZ32: string, serviceId: string, tag: string): Promise<void>;\n /**\n * Opens a `lightning:` or `bitcoin:` link in a wallet on this device. Left out where a plain link\n * already does it (a web page); the desktop app and the extension have to hand it to the system.\n */\n openPaymentLink?(uri: string): Promise<void>;\n /**\n * Puts the app's window in or out of full screen. The desktop app on Linux has it: WebKitGTK's element full screen\n * aborts the app there, so the page fills the window with the video instead (apps/desktop/src/fullscreen.rs).\n */\n fullscreenWindow?(on: boolean): Promise<void>;\n /**\n * Hands a link to the system's share sheet, pointing at `anchor` (the button, in CSS pixels).\n * Resolves to false where the platform has no sheet to show; left out where the page's own Web\n * Share API is the way (a web page).\n */\n shareText?(text: string, anchor?: ShareAnchor): Promise<boolean>;\n /**\n * The clipboard's text, read by the platform itself, for a paste button the user just clicked.\n * Left out where the page's `navigator.clipboard.readText()` is the way (a web page, the\n * extension); the desktop app reads natively because WKWebView asks for a second click.\n */\n readClipboardText?(): Promise<string>;\n /**\n * Files copied in a file manager, or a picture, read by the platform itself for a paste whose event\n * carried neither files nor text (a webview that hides them from the page). Left out where the page\n * sees them in the paste event (a browser, WebKit on macOS).\n */\n readClipboardFiles?(): Promise<ClipboardFile[]>;\n /**\n * Where calls get their media, when not from the page's own WebRTC. Ghostly Desktop on Linux: WebKitGTK has\n * no WebRTC there, and GStreamer runs the calls. Left out where the page has it.\n */\n callMedia?: CallMedia;\n /** Signing in with an OpenID Connect provider for an identity proof. Left out where the platform cannot. */\n oidc?: OidcHost;\n /** Signing in to the person's AT Protocol server (Bluesky or another PDS) for an identity proof. Left out where the platform cannot. */\n atproto?: AtprotoHost;\n /**\n * Opens Pubky Passport's authorize page (`https://passport.pubky.app/authorize#d=…`, the request in its fragment)\n * outside the app. The desktop app hands it to the system browser; left out where the page opens a popup itself.\n */\n openPubkyPassport?(url: string): Promise<void>;\n /**\n * A Pubky cookie session's requests to the homeserver, made outside the page with a cookie jar of their own: the\n * desktop app, whose WKWebView drops the homeserver's session cookie (a third-party one there). Left out where\n * the page's own fetch keeps it.\n */\n pubkyCookieSession?(): PubkyCookieSession;\n /**\n * Keeps the computer from sleeping while idle, or lets it sleep again (\"Keep this computer awake\", WISP 06 § User\n * experience): the desktop app, so a phone can take the profile over while the person is out. Resolves to whether\n * it is held now. Left out where the platform cannot (a web page, the extension).\n */\n keepAwake?(on: boolean): Promise<boolean>;\n}\n\n/** One approval's cookie session, outside the page: its cookies never reach the page, and `close` forgets them. */\nexport interface PubkyCookieSession {\n fetch(request: { url: string; method: string; headers: [string, string][]; body: Uint8Array | null }): Promise<{ status: number; headers: [string, string][]; body: Uint8Array }>;\n close(): void;\n}\n\n/** Where a share sheet points: the button that opened it, in CSS pixels from the page's top left. */\nexport interface ShareAnchor { x: number; y: number; width: number; height: number }\n\nexport interface OidcHost {\n platform: OidcPlatform;\n /**\n * Call it straight from the click: the web app opens its popup and the\n * extension asks for the `identity` permission before anything is awaited,\n * or the browser blocks them.\n */\n open(): Promise<OidcWindow>;\n}\n\nlet current: BrowserHost | null = null;\n\n/** Must be called before anything in `platform/` is used. */\nexport function setBrowserHost(host: BrowserHost): void {\n current = host;\n}\n\nexport function getBrowserHost(): BrowserHost {\n if (!current) throw new Error(\"No browser host configured\");\n return current;\n}\n\n/**\n * One thing a paste brought, held by the platform: a file (its name) or a picture (no name: the page\n * names it). `read` hands its bytes a step at a time; `done` says the page has what it wants of\n * it (or gave up), so the platform lets its copy go: nothing is read after it.\n */\nexport interface ClipboardFile {\n name: string | null;\n size: number;\n mime: string | null;\n read(offset: number, length: number): Promise<Uint8Array>;\n done?(): void;\n}\n","/**\n * The OpenID Connect providers Ghostly can ask for a signed ID token. A contact\n * verifies that token against the provider's published keys, with no Ghostly\n * server in between, so a provider is only listed when its ID tokens are signed\n * and its keys and sign-in page are reachable from a browser without a client\n * secret. See docs/wisps/3xx-oidc-proofs.md for the providers that are not\n * (Facebook, X, LinkedIn, GitHub) and why.\n *\n * Client IDs are public configuration, identical in every build, because the\n * contact's app checks `aud` against them. They stay empty until the OAuth\n * clients are registered (docs/OIDC-PROVIDERS.md); a provider without a client\n * ID is neither offered nor accepted.\n */\n\nexport type OidcProviderId = 'google' | 'microsoft' | 'apple' | 'gitlab' | 'twitch' | 'test';\nexport type OidcAlg = 'RS256' | 'ES256';\nexport type OidcPlatform = 'web' | 'extension' | 'desktop';\n/** What the person chooses to put in the token beyond the account identifier. */\nexport type OidcReveal = 'email' | 'profile';\n\nexport interface OidcProvider {\n id: OidcProviderId;\n name: string;\n /** Exact `iss`. Microsoft's contains the tenant: see `issuerFor`. */\n issuer: string;\n /** Other spellings the provider documents for the same issuer. */\n issuerAliases?: string[];\n discovery: string;\n authorizationEndpoint: string;\n /** Pinned rather than discovered, so a changed discovery document cannot redirect key lookup. */\n jwksUri: string;\n /** Only for `code`: the token endpoint, reached with PKCE and no secret. */\n tokenEndpoint?: string;\n /** Best effort after a `code` exchange: the access token Ghostly never wanted is revoked at once. */\n revocationEndpoint?: string;\n /** Pinned per provider; the token header's `alg` must be one of these. */\n algs: OidcAlg[];\n /**\n * `id_token` returns only the signed ID token in the fragment: no access\n * token, no code, nothing to exchange, so no secret. `code id_token` is\n * Apple's shape (its code needs a secret; Ghostly drops it). `code` is for\n * providers without an ID-token-only response, exchanged with PKCE.\n */\n responseType: 'id_token' | 'code id_token' | 'code';\n /** Which extra claims this provider can add, and the scope (or claims request) for each. */\n reveal: Partial<Record<OidcReveal, { scope?: string; claims?: string[] }>>;\n /** What the provider puts in the token whatever the person picks. Shown before sign-in. */\n alwaysIncluded: string;\n /** Extra authorization parameters, e.g. an account chooser. */\n extraParams?: Record<string, string>;\n /** Client IDs per platform. Desktop signs in through the web callback page, so it uses the web client. */\n clientIds: Partial<Record<OidcPlatform, string>>;\n}\n\nexport const TEST_ISSUER = /^https:\\/\\/[a-z0-9-]+(\\.[a-z0-9-]+)*\\.test$/;\nconst MICROSOFT_TENANT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n\nexport const OIDC_PROVIDERS: Record<Exclude<OidcProviderId, 'test'>, OidcProvider> = {\n google: {\n id: 'google', name: 'Google',\n issuer: 'https://accounts.google.com', issuerAliases: ['accounts.google.com'],\n discovery: 'https://accounts.google.com/.well-known/openid-configuration',\n authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',\n jwksUri: 'https://www.googleapis.com/oauth2/v3/certs',\n algs: ['RS256'], responseType: 'id_token',\n reveal: { email: { scope: 'email' }, profile: { scope: 'profile' } },\n alwaysIncluded: 'A Google account identifier.',\n extraParams: { prompt: 'select_account' },\n clientIds: { web: '', extension: '' },\n },\n microsoft: {\n // `common` takes work, school and personal accounts; the issuer names the account's tenant.\n id: 'microsoft', name: 'Microsoft',\n issuer: 'https://login.microsoftonline.com/{tenantid}/v2.0',\n discovery: 'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration',\n authorizationEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',\n jwksUri: 'https://login.microsoftonline.com/common/discovery/v2.0/keys',\n algs: ['RS256'], responseType: 'id_token',\n reveal: { email: { scope: 'email' }, profile: { scope: 'profile' } },\n alwaysIncluded: 'A Microsoft account identifier and the directory (tenant) the account belongs to.',\n extraParams: { prompt: 'select_account', response_mode: 'fragment' },\n clientIds: { web: '', extension: '' },\n },\n apple: {\n // Apple only returns the ID token in the fragment when no scope is asked\n // for; name and email need `form_post`, which a static page cannot read.\n id: 'apple', name: 'Apple',\n issuer: 'https://appleid.apple.com',\n discovery: 'https://appleid.apple.com/.well-known/openid-configuration',\n authorizationEndpoint: 'https://appleid.apple.com/auth/authorize',\n jwksUri: 'https://appleid.apple.com/auth/keys',\n algs: ['RS256'], responseType: 'code id_token',\n reveal: {},\n alwaysIncluded: 'An Apple ID identifier that only Ghostly sees. Apple cannot add your email here without a Ghostly server.',\n extraParams: { response_mode: 'fragment' },\n clientIds: { web: '', extension: '' },\n },\n gitlab: {\n id: 'gitlab', name: 'GitLab',\n issuer: 'https://gitlab.com',\n discovery: 'https://gitlab.com/.well-known/openid-configuration',\n authorizationEndpoint: 'https://gitlab.com/oauth/authorize',\n tokenEndpoint: 'https://gitlab.com/oauth/token',\n revocationEndpoint: 'https://gitlab.com/oauth/revoke',\n jwksUri: 'https://gitlab.com/oauth/discovery/keys',\n algs: ['RS256'], responseType: 'code',\n reveal: { email: { scope: 'email' }, profile: { scope: 'profile' } },\n alwaysIncluded: 'A GitLab.com user ID and the groups you belong to directly: GitLab adds them to every ID token.',\n clientIds: { web: '', extension: '' },\n },\n twitch: {\n // Twitch's email claim needs `user:read:email`, an API scope: not asked for.\n id: 'twitch', name: 'Twitch',\n issuer: 'https://id.twitch.tv/oauth2',\n discovery: 'https://id.twitch.tv/oauth2/.well-known/openid-configuration',\n authorizationEndpoint: 'https://id.twitch.tv/oauth2/authorize',\n jwksUri: 'https://id.twitch.tv/oauth2/keys',\n algs: ['RS256'], responseType: 'id_token',\n reveal: { profile: { claims: ['preferred_username'] } },\n alwaysIncluded: 'A Twitch user ID.',\n extraParams: { force_verify: 'true' },\n clientIds: { web: '', extension: '' },\n },\n};\n\n/**\n * The local issuer the e2e suite runs (e2e/support/oidcIssuer.ts). It exists\n * only in a build made with VITE_OIDC_TEST_ISSUER, and only on a `.test` host\n * (a name reserved for testing, never a real issuer); a release build has no\n * such provider, so a token from it is refused like any unknown issuer.\n */\nexport function testProvider(issuer: string): OidcProvider {\n if (!TEST_ISSUER.test(issuer)) throw new Error('A test issuer lives on a .test host');\n const base = issuer;\n return {\n id: 'test', name: 'Test issuer', issuer: base,\n discovery: `${base}/.well-known/openid-configuration`,\n authorizationEndpoint: `${base}/authorize`,\n jwksUri: `${base}/jwks`,\n algs: ['RS256', 'ES256'], responseType: 'id_token',\n reveal: { email: { scope: 'email' }, profile: { scope: 'profile' } },\n alwaysIncluded: 'A test account identifier.',\n clientIds: { web: 'ghostly-e2e', extension: 'ghostly-e2e', desktop: 'ghostly-e2e' },\n };\n}\n\nfunction configuredTestIssuer(): string | undefined {\n try {\n const value = import.meta.env?.VITE_OIDC_TEST_ISSUER as string | undefined;\n return value && TEST_ISSUER.test(value) ? value : undefined;\n } catch { return undefined; }\n}\n\n/** Every provider this build knows, configured or not. */\nexport function oidcProviders(testIssuer = configuredTestIssuer()): OidcProvider[] {\n return [...Object.values(OIDC_PROVIDERS), ...(testIssuer ? [testProvider(testIssuer)] : [])];\n}\n\nexport function oidcProvider(id: string, testIssuer = configuredTestIssuer()): OidcProvider | undefined {\n return oidcProviders(testIssuer).find(p => p.id === id);\n}\n\n/** Every client ID this provider has, on any platform: a contact may use another platform than the prover. */\nexport function audiences(provider: OidcProvider): string[] {\n return [...new Set(Object.values(provider.clientIds).filter((id): id is string => !!id))];\n}\n\nexport function clientIdFor(provider: OidcProvider, platform: OidcPlatform): string {\n return provider.clientIds[platform] || provider.clientIds.web || '';\n}\n\n/** A provider is offered only once its client ID for this platform exists. */\nexport function isConfigured(provider: OidcProvider, platform: OidcPlatform): boolean {\n return !!clientIdFor(provider, platform);\n}\n\n/** The exact issuer a token from this provider must carry, or undefined when its claims do not name one. */\nexport function issuerFor(provider: OidcProvider, claims: { tid?: unknown }): string[] {\n if (provider.id !== 'microsoft') return [provider.issuer, ...(provider.issuerAliases ?? [])];\n return typeof claims.tid === 'string' && MICROSOFT_TENANT.test(claims.tid) ? [provider.issuer.replace('{tenantid}', claims.tid)] : [];\n}\n\n/** The provider a proof's subject (an issuer, as `OidcProvider.issuer` spells it) names. */\nexport function providerForIssuer(issuer: string, list = oidcProviders()): OidcProvider | undefined {\n return list.find(p => p.issuer === issuer);\n}\n","/**\n * A provider's signing keys, fetched from its pinned JWKS URL with limits and\n * cached. A key ID that is not in the cached set triggers one early refetch\n * (providers rotate keys), rate-limited so that a stream of made-up key IDs\n * cannot turn a contact's app into a request generator.\n */\n\nexport interface Jwk {\n kty: string; kid?: string; use?: string; alg?: string; key_ops?: string[];\n n?: string; e?: string; crv?: string; x?: string; y?: string;\n /** Microsoft adds the issuer each key signs for. */\n issuer?: string;\n}\n/**\n * How keys are fetched. In `verify` this is the engine's `ctx.fetch` (HTTPS GET, no credentials, no\n * redirects, time-out, size cap); `cacheControl` is used when the fetcher reports it.\n */\nexport type JwksFetch = (url: string, options: { maxBytes: number }) => Promise<{ status: number; text: string; cacheControl?: string | null }>;\n\nexport const JWKS_MAX_BYTES = 64 * 1024;\nexport const JWKS_MAX_KEYS = 32;\nconst MIN_TTL_MS = 5 * 60_000;\nconst MAX_TTL_MS = 24 * 60 * 60_000;\nconst DEFAULT_TTL_MS = 60 * 60_000;\n/** A missing `kid` refetches at most this often per URL. */\nexport const REFETCH_INTERVAL_MS = 60_000;\n\ninterface Entry { keys: Jwk[]; expires: number; fetched: number }\n\nfunction allowedUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return u.protocol === 'https:' && !u.username && !u.password && !u.hash;\n } catch { return false; }\n}\n\nfunction ttl(cacheControl: string | null | undefined): number {\n const maxAge = /(?:^|[,\\s])max-age=(\\d+)/i.exec(cacheControl ?? '')?.[1];\n const ms = maxAge ? Number(maxAge) * 1000 : DEFAULT_TTL_MS;\n return Math.min(MAX_TTL_MS, Math.max(MIN_TTL_MS, ms));\n}\n\nfunction parseKeys(text: string): Jwk[] {\n if (text.length > JWKS_MAX_BYTES) throw new Error('Provider key set too large');\n let doc: { keys?: unknown };\n try { doc = JSON.parse(text) as { keys?: unknown }; } catch { throw new Error('Invalid provider key set'); }\n if (!doc || !Array.isArray(doc.keys) || doc.keys.length > JWKS_MAX_KEYS) throw new Error('Invalid provider key set');\n return doc.keys.filter((k): k is Jwk => !!k && typeof k === 'object' && typeof (k as Jwk).kty === 'string');\n}\n\nexport class JwksCache {\n private entries = new Map<string, Entry>();\n private inflight = new Map<string, Promise<Entry>>();\n constructor(private options: { now?: () => number } = {}) {}\n private now() { return (this.options.now ?? Date.now)(); }\n\n private load(url: string, fetcher: JwksFetch): Promise<Entry> {\n const running = this.inflight.get(url);\n if (running) return running;\n const task = (async () => {\n if (!allowedUrl(url)) throw new Error('Provider key URL refused');\n const response = await fetcher(url, { maxBytes: JWKS_MAX_BYTES });\n if (response.status !== 200) throw new Error(`Provider keys unavailable (${response.status})`);\n const keys = parseKeys(response.text);\n const fetched = this.now();\n const entry = { keys, fetched, expires: fetched + ttl(response.cacheControl) };\n this.entries.set(url, entry);\n return entry;\n })();\n this.inflight.set(url, task);\n return task.finally(() => this.inflight.delete(url));\n }\n\n /** The key with this `kid`, refetching once when it is unknown (rotation) or the cache expired. */\n async key(url: string, kid: string, fetcher: JwksFetch): Promise<Jwk | undefined> {\n let entry = this.entries.get(url);\n if (!entry || entry.expires <= this.now()) entry = await this.load(url, fetcher);\n let found = entry.keys.find(k => k.kid === kid);\n if (!found && this.now() - entry.fetched >= REFETCH_INTERVAL_MS) {\n entry = await this.load(url, fetcher);\n found = entry.keys.find(k => k.kid === kid);\n }\n return found;\n }\n\n clear(): void { this.entries.clear(); }\n}\n\n/** One cache per app: every contact's proofs share the providers' keys. */\nexport const sharedJwksCache = new JwksCache();\n","import { fromBase64Url, toBase64Url, utf8Decode, utf8Encode } from '@ghostly/core';\nimport { audiences, issuerFor, type OidcAlg, type OidcProvider, type OidcProviderId } from './providers';\nimport { sharedJwksCache, type Jwk, type JwksCache, type JwksFetch } from './jwks';\n\n/**\n * Checks an ID token the way a contact must: the provider's signature under\n * a key from its own JWKS, the pinned `alg`, the issuer, `aud` = one of\n * Ghostly's client IDs for that provider, the time window, and `nonce` = the\n * hash of the exact Ghostly proof statement. Nothing here talks to a Ghostly\n * server, and nothing here trusts what the token says about its own key.\n */\n\n/** Seconds of clock difference accepted on `iat`, `nbf` and `exp`. */\nexport const CLOCK_TOLERANCE = 60;\n/** An ID token that claims to live longer than a day is not a sign-in result. */\nconst MAX_LIFETIME = 24 * 60 * 60;\n/** Room for Microsoft's larger tokens, well inside a peer frame. */\nexport const MAX_ID_TOKEN_LENGTH = 6144;\nconst SEGMENT = /^[A-Za-z0-9_-]+$/;\nconst REFUSED_HEADERS = ['crit', 'jku', 'jwk', 'x5u', 'x5c', 'zip', 'enc'];\n\nexport class OidcVerificationError extends Error {\n constructor(message: string) { super(message); this.name = 'OidcVerificationError'; }\n}\nconst refuse = (message: string): never => { throw new OidcVerificationError(message); };\n\n/** What a contact is shown: only claims the provider signed, bounded. */\nexport interface OidcIdentity {\n provider: OidcProviderId;\n issuer: string;\n subject: string;\n audience: string;\n issuedAt: number;\n expiresAt: number;\n email?: string;\n emailVerified?: boolean;\n name?: string;\n username?: string;\n /** Microsoft's directory (tenant) ID. */\n tenant?: string;\n}\n\nfunction segment(value: string): Uint8Array {\n if (!SEGMENT.test(value)) refuse('Malformed ID token');\n let bytes: Uint8Array;\n try { bytes = fromBase64Url(value); } catch { return refuse('Malformed ID token'); }\n if (toBase64Url(bytes) !== value) refuse('Malformed ID token');\n return bytes;\n}\n\nfunction json(value: string): Record<string, unknown> {\n let parsed: unknown;\n try { parsed = JSON.parse(utf8Decode(segment(value))); } catch (e) { if (e instanceof OidcVerificationError) throw e; return refuse('Malformed ID token'); }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) refuse('Malformed ID token');\n return parsed as Record<string, unknown>;\n}\n\nconst text = (value: unknown, max: number): string | undefined =>\n typeof value === 'string' && value.length > 0 && value.length <= max ? value : undefined;\n\n/** Splits and decodes without verifying anything. Only for display of a token already verified. */\nexport function decodeIdToken(token: string): { header: Record<string, unknown>; claims: Record<string, unknown>; signature: Uint8Array; signed: Uint8Array } {\n if (typeof token !== 'string' || token.length > MAX_ID_TOKEN_LENGTH) refuse('ID token missing or too large');\n const parts = token.split('.');\n if (parts.length !== 3) refuse('Not a signed ID token');\n return { header: json(parts[0]), claims: json(parts[1]), signature: segment(parts[2]), signed: utf8Encode(`${parts[0]}.${parts[1]}`) };\n}\n\nasync function verifySignature(alg: OidcAlg, jwk: Jwk, signed: Uint8Array, signature: Uint8Array): Promise<boolean> {\n if (jwk.use !== undefined && jwk.use !== 'sig') refuse('Provider key is not a signing key');\n if (jwk.alg !== undefined && jwk.alg !== alg) refuse('Provider key is for another algorithm');\n if (jwk.key_ops !== undefined && !(Array.isArray(jwk.key_ops) && jwk.key_ops.includes('verify'))) refuse('Provider key is not a signing key');\n let key: CryptoKey;\n if (alg === 'RS256') {\n if (jwk.kty !== 'RSA' || typeof jwk.n !== 'string' || typeof jwk.e !== 'string') return refuse('Provider key does not match the token algorithm');\n const modulus = segment(jwk.n);\n const bits = (modulus.length - (modulus[0] === 0 ? 1 : 0)) * 8;\n if (bits < 2048 || bits > 8192) refuse('Provider key size refused');\n key = await crypto.subtle.importKey('jwk', { kty: 'RSA', n: jwk.n, e: jwk.e, ext: true }, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['verify']);\n return crypto.subtle.verify('RSASSA-PKCS1-v1_5', key, signature as BufferSource, signed as BufferSource);\n }\n if (jwk.kty !== 'EC' || jwk.crv !== 'P-256' || typeof jwk.x !== 'string' || typeof jwk.y !== 'string') return refuse('Provider key does not match the token algorithm');\n if (signature.length !== 64) refuse('Invalid ID token signature');\n key = await crypto.subtle.importKey('jwk', { kty: 'EC', crv: 'P-256', x: jwk.x, y: jwk.y, ext: true }, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);\n return crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, signature as BufferSource, signed as BufferSource);\n}\n\n/** Whether `iss` is this provider's, as it words it (Microsoft: with the token's own tenant). */\nexport function issuerMatches(provider: OidcProvider, claims: Record<string, unknown>): boolean {\n const iss = text(claims.iss, 512);\n return !!iss && issuerFor(provider, claims).includes(iss);\n}\n\nexport interface VerifyOptions {\n provider: OidcProvider;\n /** The nonce the sign-in was asked for: an identity proof's `statement.id`. */\n nonce: string;\n /** How the provider's keys are fetched: `ctx.fetch` in a proof's `verify`. */\n fetch: JwksFetch;\n /** Unix seconds. */\n now?: number;\n jwks?: JwksCache;\n /**\n * `[from, to]` in Unix seconds: the token must have been issued in this window. For a proof, the\n * sign-in that followed the statement; the token's own `exp` (an hour) then does not matter, the\n * statement's validity does. Without it, the token must still be unexpired.\n */\n issuedWithin?: [number, number];\n}\n\nexport async function verifyIdToken(token: string, options: VerifyOptions): Promise<OidcIdentity> {\n const { provider, nonce } = options;\n const now = options.now ?? Math.floor(Date.now() / 1000);\n const { header, claims, signature, signed } = decodeIdToken(token);\n\n // The algorithm is the provider's, never the token's choice: `none`, HS256\n // (a public key used as an HMAC secret) and anything unpinned stop here.\n const alg = header.alg;\n if (typeof alg !== 'string' || !provider.algs.includes(alg as OidcAlg)) refuse(`Algorithm ${typeof alg === 'string' ? alg : 'missing'} is not accepted for ${provider.name}`);\n if (header.typ !== undefined && (typeof header.typ !== 'string' || header.typ.toUpperCase() !== 'JWT')) refuse('Not an ID token');\n for (const name of REFUSED_HEADERS) if (name in header) refuse('ID token header refused');\n const kid = text(header.kid, 256) ?? refuse('ID token names no provider key');\n\n if (!issuerMatches(provider, claims)) refuse(`Token was not issued by ${provider.name}`);\n const iss = claims.iss as string;\n\n const expected = audiences(provider);\n if (!expected.length) refuse(`${provider.name} is not configured in this Ghostly build`);\n const aud = typeof claims.aud === 'string' ? [claims.aud] : Array.isArray(claims.aud) && claims.aud.every(a => typeof a === 'string') ? claims.aud as string[] : [];\n const audience = aud.find(a => expected.includes(a)) ?? refuse('Token was issued to another app');\n if (aud.length > 1 && claims.azp !== audience) refuse('Token was issued to another app');\n if (claims.azp !== undefined && claims.azp !== audience) refuse('Token was issued to another app');\n\n const iat = claims.iat, exp = claims.exp, nbf = claims.nbf;\n if (typeof iat !== 'number' || typeof exp !== 'number' || !Number.isFinite(iat) || !Number.isFinite(exp)) return refuse('Token has no validity window');\n if (exp <= iat || exp - iat > MAX_LIFETIME) refuse('Token validity window refused');\n if (iat > now + CLOCK_TOLERANCE) refuse('Token is not valid yet');\n if (options.issuedWithin) {\n const [from, to] = options.issuedWithin;\n if (iat < from || iat > to) refuse('The sign-in does not belong to this proof: it happened at another time');\n } else {\n if (typeof nbf === 'number' && nbf > now + CLOCK_TOLERANCE) refuse('Token is not valid yet');\n if (exp + CLOCK_TOLERANCE < now) refuse('Token expired');\n }\n\n if (typeof claims.nonce !== 'string' || claims.nonce !== nonce) refuse('Token answers another proof request');\n const subject = text(claims.sub, 255) ?? refuse('Token names no account');\n\n const jwk = await (options.jwks ?? sharedJwksCache).key(provider.jwksUri, kid, options.fetch) ?? refuse(`${provider.name} does not publish the key that signed this token`);\n if (provider.id === 'microsoft' && jwk.issuer !== undefined && jwk.issuer !== provider.issuer && jwk.issuer !== iss) refuse('Provider key belongs to another issuer');\n let valid = false;\n try { valid = await verifySignature(alg as OidcAlg, jwk, signed, signature); }\n catch (e) { if (e instanceof OidcVerificationError) throw e; refuse('Invalid ID token signature'); }\n if (!valid) refuse('Invalid ID token signature');\n\n const verified = claims.email_verified === true || claims.email_verified === 'true' ? true : claims.email_verified === false || claims.email_verified === 'false' ? false : undefined;\n return {\n provider: provider.id, issuer: iss, subject, audience, issuedAt: Math.floor(iat), expiresAt: Math.floor(exp),\n email: text(claims.email, 320), emailVerified: text(claims.email, 320) ? verified : undefined,\n name: text(claims.name, 200), username: text(claims.preferred_username, 200) ?? text(claims.nickname, 200),\n tenant: provider.id === 'microsoft' ? text(claims.tid, 64) : undefined,\n };\n}\n","import { randomBytes, toBase64Url, utf8Encode } from '@ghostly/core';\nimport { clientIdFor, type OidcPlatform, type OidcProvider, type OidcReveal } from './providers';\nimport { decodeIdToken, issuerMatches } from './verify';\n\n/**\n * The sign-in half: send the person to the provider with the identity proof's\n * statement id as `nonce`, and take back only the ID token. The token's\n * signature is checked by the proof's `verify`, before saving and on every\n * contact's device; here only that it answers this request. Access and refresh tokens are never requested\n * where the provider allows it, and dropped (and revoked) where it does not.\n */\n\n/** How a platform shows the provider's page and hands back where it redirected. */\nexport interface OidcWindow {\n /** The redirect URI registered for this platform. */\n redirectUri: string;\n /** Extra state prefix the platform needs to route the answer back (desktop: its loopback port). */\n statePrefix?: string;\n /** Opens `url` and resolves with the full URL the provider redirected to. */\n authorize(url: string, signal: AbortSignal): Promise<string>;\n /** Releases the window or listener, answered or not. */\n close(): void;\n}\n\nexport interface OidcRequest {\n url: string;\n state: string;\n nonce: string;\n redirectUri: string;\n clientId: string;\n /** PKCE verifier, only for `code`. Held in memory for the one exchange. */\n verifier?: string;\n}\n\n/** Optional platform prefix (desktop `d.<port>.`, Android deep link `a.`), then 32 random bytes. */\nconst STATE = /^(?:d\\.\\d{1,5}\\.|a\\.)?[A-Za-z0-9_-]{43}$/;\n\nexport function scopeFor(provider: OidcProvider, reveal: OidcReveal[]): { scope: string; claims?: string } {\n const scopes = provider.id === 'apple' ? [] : ['openid'];\n const claims: string[] = [];\n for (const r of reveal) {\n const option = provider.reveal[r];\n if (!option) continue;\n if (option.scope) scopes.push(option.scope);\n if (option.claims) claims.push(...option.claims);\n }\n return { scope: scopes.join(' '), claims: claims.length ? JSON.stringify({ id_token: Object.fromEntries(claims.map(c => [c, null])) }) : undefined };\n}\n\nexport async function authorizationRequest(options: { provider: OidcProvider; platform: OidcPlatform; nonce: string; reveal: OidcReveal[]; redirectUri: string; statePrefix?: string }): Promise<OidcRequest> {\n const { provider } = options;\n const clientId = clientIdFor(provider, options.platform);\n if (!clientId) throw new Error(`${provider.name} sign-in is not set up in this Ghostly build yet.`);\n const { nonce } = options;\n if (!/^[A-Za-z0-9_-]{16,128}$/.test(nonce)) throw new Error('Invalid sign-in nonce');\n const state = `${options.statePrefix ?? ''}${toBase64Url(randomBytes(32))}`;\n const url = new URL(provider.authorizationEndpoint);\n const { scope, claims } = scopeFor(provider, options.reveal);\n const params: Record<string, string> = {\n client_id: clientId, redirect_uri: options.redirectUri, response_type: provider.responseType,\n nonce, state, ...(scope ? { scope } : {}), ...(claims ? { claims } : {}), ...provider.extraParams,\n };\n let verifier: string | undefined;\n if (provider.responseType === 'code') {\n verifier = toBase64Url(randomBytes(32));\n params.code_challenge = toBase64Url(new Uint8Array(await crypto.subtle.digest('SHA-256', utf8Encode(verifier) as BufferSource)));\n params.code_challenge_method = 'S256';\n }\n for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);\n return { url: url.toString(), state, nonce, redirectUri: options.redirectUri, clientId, verifier };\n}\n\n/** Parameters from the redirect: the fragment for implicit responses, the query for `code`. */\nexport function callbackParams(redirected: string, redirectUri: string): URLSearchParams {\n const url = new URL(redirected), expected = new URL(redirectUri);\n if (url.origin !== expected.origin || url.pathname !== expected.pathname) throw new Error('Sign-in returned to an unexpected address.');\n const params = new URLSearchParams(url.hash.slice(1));\n for (const [k, v] of new URLSearchParams(url.search)) if (!params.has(k)) params.set(k, v);\n return params;\n}\n\nconst PROVIDER_ERRORS: Record<string, string> = {\n access_denied: 'Sign-in was cancelled.',\n login_required: 'Sign in to your account and try again.',\n consent_required: 'Consent was not given.',\n interaction_required: 'The provider needs you to finish signing in.',\n unauthorized_client: 'This provider has not accepted Ghostly’s sign-in yet.',\n invalid_request: 'The provider refused the sign-in request.',\n};\n\nasync function exchangeCode(provider: OidcProvider, request: OidcRequest, code: string, doFetch: typeof fetch): Promise<string> {\n if (!provider.tokenEndpoint || !request.verifier) throw new Error('This provider cannot finish sign-in here.');\n const response = await doFetch(provider.tokenEndpoint, {\n method: 'POST', redirect: 'error', credentials: 'omit', referrerPolicy: 'no-referrer', cache: 'no-store',\n headers: { 'content-type': 'application/x-www-form-urlencoded', accept: 'application/json' },\n body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: request.redirectUri, client_id: request.clientId, code_verifier: request.verifier }),\n });\n const body = await response.json().catch(() => ({})) as { id_token?: unknown; access_token?: unknown; refresh_token?: unknown };\n // Only the ID token leaves this function. The others were never wanted:\n // revoke them without waiting, and let them go out of scope.\n for (const token of [body.refresh_token, body.access_token]) {\n if (typeof token === 'string' && provider.revocationEndpoint)\n void doFetch(provider.revocationEndpoint, { method: 'POST', redirect: 'error', credentials: 'omit', referrerPolicy: 'no-referrer',\n headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ token, client_id: request.clientId }) }).catch(() => {});\n }\n if (!response.ok || typeof body.id_token !== 'string') throw new Error(`${provider.name} did not return an ID token.`);\n return body.id_token;\n}\n\n/** Turns the redirect into the ID token: state, provider error, code exchange if needed, and that it answers this request. */\nexport async function completeSignIn(provider: OidcProvider, request: OidcRequest, redirected: string, options: { fetch?: typeof fetch } = {}): Promise<string> {\n const params = callbackParams(redirected, request.redirectUri);\n const state = params.get('state');\n if (!state || !STATE.test(state) || state !== request.state) throw new Error('Sign-in answer does not belong to this request.');\n const error = params.get('error');\n if (error) throw new Error(PROVIDER_ERRORS[error] ?? 'The provider refused the sign-in.');\n let idToken: string | null;\n if (provider.responseType === 'code') {\n const code = params.get('code');\n if (!code || code.length > 2048) throw new Error(`${provider.name} did not return a sign-in code.`);\n idToken = await exchangeCode(provider, request, code, options.fetch ?? fetch);\n } else idToken = params.get('id_token');\n // Apple's code needs a client secret to redeem, which Ghostly does not have: it is dropped with the rest.\n if (!idToken) throw new Error(`${provider.name} did not return an ID token.`);\n const { claims } = decodeIdToken(idToken);\n if (claims.nonce !== request.nonce || !issuerMatches(provider, claims)) throw new Error(`${provider.name} answered another sign-in request.`);\n return idToken;\n}\n\n/** Everything the prover does: sign in on this platform and return the ID token for this nonce. */\nexport async function signInForProof(options: {\n provider: OidcProvider; platform: OidcPlatform; nonce: string; reveal: OidcReveal[];\n window: OidcWindow; signal: AbortSignal; fetch?: typeof fetch;\n}): Promise<string> {\n const { window: w } = options;\n try {\n options.signal.throwIfAborted();\n const request = await authorizationRequest({ ...options, redirectUri: w.redirectUri, statePrefix: w.statePrefix });\n const redirected = await w.authorize(request.url, options.signal);\n options.signal.throwIfAborted();\n return await completeSignIn(options.provider, request, redirected, options);\n } finally { w.close(); }\n}\n","import { IDENTITY_CLOCK_SKEW, type IdentityStatement } from \"@ghostly/core\";\nimport type { IdentityProofProvider, IdentityPlatform, RedirectSigner } from \"../contract\";\nimport { getBrowserHost, type OidcHost } from \"../../host\";\nimport { audiences, isConfigured, oidcProviders, providerForIssuer, type OidcProvider, type OidcReveal } from \"../oidc/providers\";\nimport { signInForProof } from \"../oidc/flow\";\nimport { MAX_ID_TOKEN_LENGTH, verifyIdToken } from \"../oidc/verify\";\nimport { JwksCache, sharedJwksCache } from \"../oidc/jwks\";\n\n/**\n * Identity proofs through OpenID Connect: the person signs in with a provider (Google, Microsoft,\n * Apple, GitLab, Twitch) whose ID token carries the statement id as `nonce`. The token is the evidence;\n * a contact's app checks the provider's signature against its published keys, the issuer, Ghostly's\n * client ID, the nonce and when the sign-in happened. No Ghostly server is involved, and the proof is\n * only as good as the provider's word: it is attested, not a key the person holds.\n *\n * The statement's subject is the issuer the person picked; the account comes out of `verify`.\n * Configuration and flows per provider: ../oidc/, docs/OIDC-PROVIDERS.md, docs/wisps/3xx-oidc-proofs.md.\n */\nexport interface OidcEvidence { token: string }\n\n/** How long after the statement the sign-in may have happened: time to pick an account and approve. */\nconst SIGN_IN_WINDOW = 15 * 60;\nconst TOKEN = /^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/;\nconst SUB = /^[\\x21-\\x7e]{1,255}$/;\n\nfunction currentHost(): OidcHost | undefined {\n try { return getBrowserHost().oidc; } catch { return undefined; }\n}\n\nconst hostOf = (issuer: string) => { try { return new URL(issuer).host; } catch { return issuer; } };\n\nexport interface OidcIdentityOptions {\n /** The OIDC providers this build knows (tests pass configured ones). */\n providers?: () => OidcProvider[];\n /** The platform's sign-in window. */\n host?: () => OidcHost | undefined;\n /** Provider keys, cached across proofs. */\n jwks?: JwksCache;\n /** The UI-side fetch for GitLab's code exchange. */\n fetch?: typeof fetch;\n}\n\nexport function createOidcIdentityProvider(options: OidcIdentityOptions = {}): IdentityProofProvider<OidcEvidence> {\n const providers = options.providers ?? (() => oidcProviders());\n const host = options.host ?? currentHost;\n const configured = () => providers().filter(p => audiences(p).length > 0);\n\n const signer = (id: string, label: string, description: string, reveal: OidcReveal[]): RedirectSigner<OidcEvidence> => ({\n id, kind: \"redirect\", label, description,\n available: () => { const h = host(); return !!h && providers().some(p => isConfigured(p, h.platform)); },\n start(statement: IdentityStatement, ctx) {\n // Before any await: the popup and the extension's permission prompt need the click's user activation.\n const h = host();\n const opening = h?.open();\n return (async () => {\n if (!h || !opening) throw new Error(\"Signing in with a provider is not available here.\");\n const provider = providerForIssuer(statement.binding.subject, providers());\n if (!provider || !isConfigured(provider, h.platform)) { void opening.then(w => w.close(), () => {}); throw new Error(\"This provider is not set up in this Ghostly build.\"); }\n ctx.onProgress(`Sign in with ${provider.name} in the window that opened.`);\n const token = await signInForProof({\n provider, platform: h.platform, nonce: statement.id, window: await opening, signal: ctx.signal, fetch: options.fetch,\n // What the provider cannot put in its token is not asked for.\n reveal: reveal.filter(r => provider.reveal[r]),\n });\n return { token };\n })();\n },\n });\n\n return {\n id: \"oidc\",\n label: \"Account at a provider\",\n category: \"provider-attested\",\n summary: \"Sign in with Google, Microsoft, Apple…\",\n description: \"Sign in with Google, Microsoft, Apple, GitLab or Twitch: the provider vouches to Ghostly, with a signed token, that the account is yours.\",\n limits: \"Attested by the provider, not by a key you hold: your contact trusts that company.\",\n platforms: [\"web\", \"extension\", \"desktop\"] satisfies IdentityPlatform[],\n subject: {\n label: \"Provider\",\n help: \"Your contact sees your account’s identifier at that provider, plus what you choose to add. GitLab also adds the groups you belong to directly; Apple never gives Ghostly your email.\",\n get options() { return configured().map(p => ({ value: p.issuer, label: p.name })); },\n normalize(input: string) {\n const provider = providerForIssuer(input.trim(), configured());\n if (!provider) throw new Error(\"Choose a provider\");\n return provider.issuer;\n },\n short: subject => providerForIssuer(subject, providers())?.name ?? hostOf(subject),\n },\n // Providers rotate their signing keys within weeks (Google about every two); a contact can only\n // check a token while its key is still published, so a proof is short-lived.\n validity: { defaultDays: 7, maxDays: 14 },\n privacy: \"Your contact’s app downloads the provider’s public keys (for example from www.googleapis.com), so the provider sees that some app checked a sign-in, not whose. Your contact sees the account identifier and what you chose to share.\",\n signers: [\n signer(\"oidc-account\", \"Sign in: share the account only\", \"The token carries the provider’s identifier for your account, nothing else.\", []),\n signer(\"oidc-email\", \"Sign in: share the account and email\", \"Adds your email address, where the provider offers it.\", [\"email\"]),\n signer(\"oidc-profile\", \"Sign in: share the account, email and name\", \"Adds your email address and name or username, where the provider offers them.\", [\"email\", \"profile\"]),\n ],\n parseEvidence(raw) {\n const e = raw as OidcEvidence;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw) || Object.keys(raw).join(\",\") !== \"token\" ||\n typeof e.token !== \"string\" || e.token.length > MAX_ID_TOKEN_LENGTH || !TOKEN.test(e.token)) throw new Error(\"Invalid sign-in token\");\n return { token: e.token };\n },\n async verify(statement, { token }, ctx) {\n const provider = providerForIssuer(statement.binding.subject, providers());\n if (!provider) throw new Error(\"This app does not know this provider\");\n const id = await verifyIdToken(token, {\n provider, nonce: statement.id, now: ctx.now, jwks: options.jwks ?? sharedJwksCache,\n fetch: async (url, { maxBytes }) => { const r = await ctx.fetch(url, { maxBytes, signal: ctx.signal }); return { status: r.status, text: r.text }; },\n issuedWithin: [statement.binding.issuedAt - IDENTITY_CLOCK_SKEW, statement.binding.issuedAt + SIGN_IN_WINDOW],\n });\n if (!SUB.test(id.subject)) throw new Error(\"The provider’s account identifier cannot be shown\");\n const attester = hostOf(provider.issuer);\n const person = id.name ?? id.username;\n const name = id.email ? (person && `${person} · ${id.email}`.length <= 64 ? `${person} · ${id.email}` : id.email) : person;\n const url = id.username && /^[A-Za-z0-9_.-]{1,64}$/.test(id.username)\n ? provider.id === \"gitlab\" ? `https://gitlab.com/${id.username}` : provider.id === \"twitch\" ? `https://www.twitch.tv/${id.username}` : undefined\n : undefined;\n return {\n // Microsoft's issuer names the account's tenant; the others are fixed.\n subject: `${provider.id === \"microsoft\" ? id.issuer : provider.issuer}#${id.subject}`,\n attester,\n source: `ID token signed by ${attester}`,\n ...(name || url ? { display: {\n ...(name ? { name } : {}), ...(url ? { url } : {}), fetchedAt: id.issuedAt,\n source: `Signed by ${provider.name}${id.email ? id.emailVerified ? `; email verified by ${provider.name}` : `; email not verified by ${provider.name}` : \"\"}`,\n } } : {}),\n };\n },\n };\n}\n\nexport const oidc = createOidcIdentityProvider();\n","import {\n DidError, DID_DOCUMENT_MAX_BYTES, resolveDidDht, DID_FILE_MAX_BYTES, didWebDocumentUrl, didWebFileUrl, didWebHost, parseDid, parseDidDocument, staticDidDocument,\n type DidDocumentView, type DidMethod,\n} from \"@ghostly/core\";\nimport type { IdentityFetch } from \"./contract\";\nimport { assertPublicHost, chosenResolver, DomainCheckError, type DohResolverId } from \"./domain\";\n\n/**\n * Network side of DID proofs: resolving a DID to the keys its document lists, under the same limits as the\n * other providers. did:key and did:jwk need no network. did:web is one HTTPS GET of its did.json (bounded,\n * no redirect, after checking that the domain's addresses are public, like the domain proof). did:dht is a\n * signed Pkarr record read from a relay (`resolveDidDht`, WISP 310-did-dht).\n *\n * What a check reveals, and to whom:\n * - did:web: the DNS-over-HTTPS resolver learns the domain; the domain's web server, run by the person\n * being checked, sees this device's IP address and when.\n * - did:dht: the Pkarr relay learns which DID was looked up, from which IP address.\n */\n\nexport interface DidResolution {\n method: DidMethod;\n document: DidDocumentView;\n /** did:web: the host, and where its did.json was read. */\n host?: string;\n documentUrl?: string;\n /** did:dht: the relay that answered. */\n relay?: string;\n}\n\nexport interface DidResolveOptions {\n fetch: IdentityFetch;\n signal?: AbortSignal;\n /** Milliseconds. */\n now?: () => number;\n resolver?: DohResolverId;\n /** did:dht resolution; tests pass their own. */\n dht?: typeof resolveDidDht;\n}\n\n/** Resolutions are shared for this long: back-to-back checks and the add dialog's preview, never a stale key for long. */\nconst CACHE_MS = 30_000;\nconst cache = new Map<string, { until: number; result: Promise<DidResolution> }>();\n/** The last document seen per DID, without expiry, for the signer's instructions (which cannot wait). */\nconst seen = new Map<string, DidResolution>();\n\n/** Tests only. */\nexport function clearDidCache(): void { cache.clear(); seen.clear(); }\n\n/** What this app last resolved for `did`, if anything: the add dialog's instructions name its keys. */\nexport const lastDidResolution = (did: string): DidResolution | undefined => seen.get(did);\n\nasync function fetchDidWeb(did: string, url: string, maxBytes: number, options: DidResolveOptions): Promise<string | undefined> {\n const host = didWebHost(did);\n try {\n await assertPublicHost(host, { fetch: options.fetch, signal: options.signal, resolver: options.resolver ?? chosenResolver().id, now: options.now });\n } catch (e) { throw e instanceof DomainCheckError ? new DidError(e.message) : e; }\n let response: Awaited<ReturnType<IdentityFetch>>;\n try {\n response = await options.fetch(url, { headers: { accept: \"application/did+json, application/json\" }, maxBytes, signal: options.signal, redirect: \"error\" });\n } catch (e) {\n if (options.signal?.aborted) throw e;\n throw new DidError(/too large/i.test(String(e)) ? `${url} is too large`\n : `${host} could not be reached. It must serve ${url} over HTTPS, without a redirect, with the header Access-Control-Allow-Origin: *`);\n }\n if (response.status === 404 || response.status === 410) return undefined;\n if (response.status >= 300 && response.status < 400) throw new DidError(`${host} answered with a redirect; it must serve exactly ${url}`);\n if (response.status !== 200) throw new DidError(`${host} answered ${response.status} for ${url}`);\n try { return new TextDecoder(\"utf-8\", { fatal: true }).decode(response.bytes); } catch { throw new DidError(`${url} is not text`); }\n}\n\nasync function resolve(did: string, method: DidMethod, options: DidResolveOptions): Promise<DidResolution> {\n if (method === \"key\" || method === \"jwk\") return { method, document: staticDidDocument(did) };\n if (method === \"web\") {\n const documentUrl = didWebDocumentUrl(did);\n const text = await fetchDidWeb(did, documentUrl, DID_DOCUMENT_MAX_BYTES, options);\n if (text === undefined) throw new DidError(`No DID document at ${documentUrl}`);\n let raw: unknown;\n try { raw = JSON.parse(text); } catch { throw new DidError(`${documentUrl} is not JSON`); }\n return { method, document: parseDidDocument(did, raw), host: didWebHost(did), documentUrl };\n }\n const found = await (options.dht ?? resolveDidDht)(did, { fetch: options.fetch, signal: options.signal });\n if (found.metadata.deactivated) throw new DidError(\"This DID is deactivated: its owner retired it\");\n return { method, document: parseDidDocument(did, found.document), relay: found.relay };\n}\n\n/** A DID's document as far as Ghostly uses it. Concurrent and back-to-back resolutions share one lookup. */\nexport async function resolveDid(input: string, options: DidResolveOptions): Promise<DidResolution> {\n const { did, method } = parseDid(input);\n const now = options.now ?? Date.now;\n const hit = cache.get(did);\n if (hit && hit.until > now()) return hit.result;\n const entry = { until: now() + CACHE_MS, result: resolve(did, method, options) };\n cache.set(did, entry);\n if (cache.size > 64) cache.delete(cache.keys().next().value!);\n try {\n const result = await entry.result;\n seen.delete(did);\n seen.set(did, result);\n if (seen.size > 16) seen.delete(seen.keys().next().value!);\n return result;\n } catch (e) {\n if (cache.get(did) === entry) cache.delete(did);\n throw e;\n }\n}\n\n/** The statement file a did:web publishes for proof `proofId`; undefined when the server says it is not there. */\nexport async function fetchDidWebFile(did: string, proofId: string, options: DidResolveOptions): Promise<string | undefined> {\n return fetchDidWeb(did, didWebFileUrl(did, proofId), DID_FILE_MAX_BYTES, options);\n}\n","import {\n checkDidJws, checkDidSignature, DidError, DID_JWS_MAX_LENGTH, didGhostlyServiceEntry, didJwsAlg, didSignedMessages, didWebFile, didWebFileNames,\n didWebFileUrl, normalizeDid, parseDid, parseDidSignaturePaste, signingKeys, staticDidDocument, strictBase64Url, toBase64Url,\n type DidCurve, type DidKey, type IdentityStatement,\n} from \"@ghostly/core\";\nimport type { ExternalToolSigner, IdentityProofProvider, PublishSigner, SignerInstructions, SubjectPreview } from \"../contract\";\nimport { fetchDidWebFile, lastDidResolution, resolveDid, type DidResolution } from \"../did\";\nimport { ownDid } from \"../ownDid\";\nimport { boundedIdentityFetch } from \"../verify\";\n\n/**\n * A decentralized identifier (W3C DID Core), draft WISP 311-did. The subject is the DID; the statement is\n * vouched for by a key its document lists under `authentication` or `assertionMethod` (a JWS or a raw\n * signature pasted back, like SSH and PGP), or, for did:web, by publishing it where the DID document lives\n * (a file beside did.json, or a service entry in it), like the domain proof.\n *\n * Methods: did:key and did:jwk (the key is the identifier: checked offline), did:dht (a signed Pkarr\n * record), did:web (HTTPS). Every check resolves the DID again, so a key removed from a did:web or did:dht\n * document stops counting at the contact's next check.\n */\n\nexport type DidEvidence =\n | { method: \"jws\"; vm: string; jws: string }\n | { method: \"sig\"; vm: string; sig: string }\n | { method: \"file\" }\n | { method: \"service\" };\n\nconst VM = /^did:[a-z0-9]+:[\\x21-\\x7e]+#[\\x21-\\x7e]+$/;\nconst SIG = /^[A-Za-z0-9_-]{86}$/;\nconst exactKeys = (raw: unknown, keys: string) => !!raw && typeof raw === \"object\" && !Array.isArray(raw) && Object.keys(raw).sort().join(\",\") === keys;\nconst validVm = (vm: unknown): vm is string => typeof vm === \"string\" && vm.length <= 640 && VM.test(vm);\n\nexport { setOwnDidSource } from \"../ownDid\";\n\n/** The add dialog's checks run in the UI, with the same bounded fetch the engine uses. */\nconst uiFetch = boundedIdentityFetch({ online: () => globalThis.navigator?.onLine !== false });\n\n/** `#key-1`, shortened when a multibase fragment is long. */\nconst fragment = (id: string) => {\n const f = id.slice(id.indexOf(\"#\"));\n return f.length > 20 ? `${f.slice(0, 11)}…${f.slice(-4)}` : f;\n};\n\nfunction shortDid(did: string): string {\n if (did.length <= 30) return did;\n const { method, id } = parseDidSafe(did);\n if (method === \"web\") return `did:web:${id.split(\":\")[0]}${id.includes(\":\") ? \":…\" : \"\"}`;\n return `did:${method}:${id.slice(0, 8)}…${id.slice(-4)}`;\n}\nconst parseDidSafe = (did: string) => { try { return parseDid(did); } catch { return { did, method: did.split(\":\")[1] ?? \"\", id: did.split(\":\").slice(2).join(\":\") }; } };\n\n/** Keys the instructions can name before anything is fetched: the identifier's own, or what the preview resolved. */\nfunction knownKeys(did: string): DidKey[] {\n const { method } = parseDid(did);\n if (method === \"key\" || method === \"jwk\") return signingKeys(staticDidDocument(did));\n const seen = lastDidResolution(did);\n return seen ? signingKeys(seen.document) : [];\n}\n\nconst NOBLE: Record<DidCurve, { name: string; module: string }> = {\n Ed25519: { name: \"ed25519\", module: \"ed25519.js\" },\n secp256k1: { name: \"secp256k1\", module: \"secp256k1.js\" },\n \"P-256\": { name: \"p256\", module: \"nist.js\" },\n};\n\nfunction joseScript(statement: string, kid?: string): string {\n return [\n \"// npm install jose; then: node sign.mjs your-private-key.jwk.json\",\n \"import { readFileSync } from \\\"node:fs\\\";\",\n \"import { CompactSign, importJWK } from \\\"jose\\\";\",\n `const statement = ${JSON.stringify(statement)};`,\n \"const jwk = JSON.parse(readFileSync(process.argv[2], \\\"utf8\\\"));\",\n \"const alg = { Ed25519: \\\"EdDSA\\\", \\\"P-256\\\": \\\"ES256\\\" }[jwk.crv];\",\n \"const jws = await new CompactSign(new TextEncoder().encode(statement))\",\n ` .setProtectedHeader({ alg${kid ? `, kid: ${JSON.stringify(kid)}` : \"\"} })`,\n \" .sign(await importJWK(jwk, alg));\",\n \"console.log(jws);\",\n ].join(\"\\n\");\n}\n\nfunction nobleScript(statement: string, curve: DidCurve): string {\n const lib = NOBLE[curve];\n return [\n \"// npm install @noble/curves; then: node sign.mjs your-private-key.hex\",\n \"import { readFileSync } from \\\"node:fs\\\";\",\n `import { ${lib.name} } from \"@noble/curves/${lib.module}\";`,\n `const statement = new TextEncoder().encode(${JSON.stringify(statement)});`,\n \"const secret = Uint8Array.from(Buffer.from(readFileSync(process.argv[2], \\\"utf8\\\").trim(), \\\"hex\\\"));\",\n `console.log(Buffer.from(${lib.name}.sign(statement, secret)).toString(\"hex\"));`,\n ].join(\"\\n\");\n}\n\nfunction opensslCommand(statement: string, curve: DidCurve): string {\n const save = `printf '%s' '${statement}' > statement.txt`;\n return curve === \"Ed25519\"\n ? `${save} && openssl pkeyutl -sign -rawin -inkey key.pem -in statement.txt | xxd -p | tr -d '\\\\n'`\n : `${save} && openssl dgst -sha256 -sign key.pem statement.txt | xxd -p | tr -d '\\\\n'`;\n}\n\nconst signer: ExternalToolSigner<DidEvidence> = {\n id: \"sign\", kind: \"external-tool\", label: \"Sign with a key of the DID\",\n description: \"Sign the statement with a key your DID document lists for authentication or assertionMethod, and paste a JWS or the raw signature. The private key never enters Ghostly.\",\n instructions(statement): SignerInstructions {\n const did = statement.binding.subject;\n if (statement.text.includes(\"'\")) throw new DidError(\"Statement is not signable as plain text\");\n const keys = knownKeys(did);\n const key = keys.length === 1 ? keys[0] : undefined;\n const curve = key?.curve ?? keys[0]?.curve ?? \"Ed25519\";\n const which = keys.length ? keys.map(k => `${fragment(k.id)} (${k.curve})`).join(\", \") : \"a key listed under authentication or assertionMethod\";\n return {\n steps: [\n { text: `Sign this statement with the private key of ${which}. It is one line, with no newline at the end:`, copy: statement.text },\n ...(curve === \"secp256k1\" ? [] : [{ text: `With jose (Node.js 20 or later), a JWS: save this as sign.mjs and run it with your ${curve} private key as a JWK file.`, copy: joseScript(statement.text, key?.id) }]),\n { text: `With @noble/curves (Node.js), a raw signature: save this as sign.mjs and run it with your ${curve} private key in hex.`, copy: nobleScript(statement.text, curve) },\n { text: `Or with OpenSSL 3 and your ${curve} key in PEM:`, copy: opensslCommand(statement.text, curve) },\n { text: `Paste what it prints: a compact JWS (${didJwsAlg(curve)}, attached or detached), or the signature in hex or base64. If your DID lists several keys, you may write the key id first: #key-1 <signature>.` },\n ],\n paste: { label: \"JWS or signature\", placeholder: \"eyJhbGciOi… or the signature in hex\", multiline: true },\n };\n },\n async parse(pasted, statement) {\n const did = statement.binding.subject;\n const paste = parseDidSignaturePaste(pasted);\n const { document } = await resolveDid(did, { fetch: uiFetch });\n const messages = didSignedMessages(statement.text);\n if (paste.kind === \"jws\") return { method: \"jws\", vm: checkDidJws(paste.jws, messages, document).key.id, jws: paste.jws };\n const check = checkDidSignature(paste.signature, messages, document, paste.vm);\n return { method: \"sig\", vm: check.key.id, sig: toBase64Url(check.signature) };\n },\n};\n\nconst webOnly = (statement: IdentityStatement) => {\n const did = statement.binding.subject;\n if (parseDid(did).method !== \"web\") throw new DidError(\"Publishing works for did:web only: sign the statement instead\");\n return did;\n};\n\nconst fileSigner: PublishSigner<DidEvidence> = {\n id: \"file\", kind: \"publish\", label: \"Publish a file beside did.json\",\n description: \"did:web only: upload a small file next to your did.json. Contacts' apps download both from your server.\",\n instructions(statement) {\n const did = webOnly(statement);\n return { steps: [\n { text: \"Create this file on your web server, beside your did.json:\", copy: didWebFileUrl(did, statement.id) },\n { text: \"With exactly this content:\", copy: didWebFile(did, statement.text) },\n { text: \"Serve it over HTTPS at exactly that address, without any redirect, and with this header so browsers may read it (your did.json needs it too):\", copy: \"Access-Control-Allow-Origin: *\" },\n { text: \"Then press Check. The file stays public while the proof is in use; deleting it withdraws this proof from every contact at their next check.\" },\n ] };\n },\n evidence: () => ({ method: \"file\" }),\n};\n\nconst serviceSigner: PublishSigner<DidEvidence> = {\n id: \"service\", kind: \"publish\", label: \"Add a service to did.json\",\n description: \"did:web only: add one entry to the service list of your did.json. Contacts' apps read it there.\",\n instructions(statement) {\n const did = webOnly(statement);\n const seen = lastDidResolution(did);\n return { steps: [\n { text: `Add this entry to the \"service\" list of your did.json${seen?.documentUrl ? ` (${seen.documentUrl})` : \"\"}:`, copy: JSON.stringify(didGhostlyServiceEntry(did, statement.binding.key, statement.id), null, 2) },\n { text: \"Then press Check. Removing the entry withdraws this proof from every contact at their next check.\" },\n ] };\n },\n evidence: () => ({ method: \"service\" }),\n};\n\nfunction previewOf(r: DidResolution): SubjectPreview {\n const keys = signingKeys(r.document);\n const unusable = r.document.keys.length - keys.length + r.document.skipped;\n if (!keys.length && r.method !== \"web\") throw new DidError(\"This DID lists no Ed25519, secp256k1 or P-256 key for authentication or assertionMethod, so it cannot sign\");\n const facts: { label: string; value: string }[] = [{ label: \"Method\", value: `did:${r.method}${r.method === \"key\" || r.method === \"jwk\" ? \", the key is the identifier\" : \"\"}` }];\n if (r.host) facts.push({ label: \"Domain\", value: r.host });\n if (r.documentUrl) facts.push({ label: \"Document\", value: r.documentUrl });\n if (r.relay) facts.push({ label: \"Relay\", value: r.relay.replace(/^https:\\/\\//, \"\") });\n facts.push({ label: keys.length === 1 ? \"Key\" : \"Keys\", value: keys.length ? keys.map(k => `${fragment(k.id)} (${k.curve})`).join(\", \") : \"none that can sign: publish instead\" });\n if (unusable > 0) facts.push({ label: \"Not usable\", value: `${unusable} ${unusable === 1 ? \"key\" : \"keys\"}: another curve, or not for authentication or assertionMethod` });\n return { facts, signers: r.method === \"web\" ? [...(keys.length ? [\"sign\"] : []), \"file\", \"service\"] : [\"sign\"] };\n}\n\nexport const did: IdentityProofProvider<DidEvidence> = {\n id: \"did\",\n label: \"DID\",\n category: \"self-custodied\",\n summary: \"did:key, did:jwk, did:dht or did:web\",\n description: \"Proves you control a decentralized identifier (W3C DID): sign the statement with a key its DID document lists, or, for did:web, publish it beside your did.json.\",\n limits: \"Does not prove who controls the DID, only that whoever added the proof held one of its keys (or its website).\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n advanced: true,\n experimental: true,\n subject: {\n label: \"DID\", placeholder: \"did:key:z6Mk… or did:web:example.com\",\n help: \"did:key, did:jwk, did:dht or did:web. Contacts see the whole DID, and the key that signed.\",\n normalize(input) {\n const canonical = normalizeDid(input);\n if (canonical === ownDid()) throw new DidError(\"That is this profile's own Ghostly DID: your contacts already see your Ghostly identity. Add another DID.\");\n return canonical;\n },\n short: shortDid,\n preview: async (subject, { signal }) => previewOf(await resolveDid(subject, { fetch: uiFetch, signal })),\n },\n // A proven DID is already a URI: the profile's own did:dht may list it under alsoKnownAs.\n publicUri: subject => subject,\n validity: { defaultDays: 90, maxDays: 365 },\n // A did:web or did:dht document can drop a key, or the file be deleted: contacts can check again.\n recheck: { afterSeconds: 86_400 },\n privacy: \"did:key and did:jwk: nothing, checked on this device. did:web: the contact's app asks its DNS-over-HTTPS resolver for your domain and downloads your did.json, \" +\n \"so your web server sees the contact's IP address. did:dht: a Pkarr relay learns which DID was looked up.\",\n signers: [signer, fileSigner, serviceSigner],\n parseEvidence(raw) {\n const e = raw as Record<string, unknown>;\n if (exactKeys(raw, \"jws,method,vm\") && e.method === \"jws\" && typeof e.jws === \"string\" && e.jws.length <= DID_JWS_MAX_LENGTH && validVm(e.vm)) return { method: \"jws\", vm: e.vm, jws: e.jws };\n if (exactKeys(raw, \"method,sig,vm\") && e.method === \"sig\" && typeof e.sig === \"string\" && SIG.test(e.sig) && validVm(e.vm)) return { method: \"sig\", vm: e.vm, sig: e.sig };\n if (exactKeys(raw, \"method\") && (e.method === \"file\" || e.method === \"service\")) return { method: e.method };\n throw new Error(\"That is not DID proof evidence\");\n },\n async verify(statement, evidence, ctx) {\n const subject = statement.binding.subject;\n const { method } = parseDid(subject);\n if (normalizeDid(subject) !== subject) throw new DidError(\"The DID is not in canonical form\");\n if ((evidence.method === \"file\" || evidence.method === \"service\") && method !== \"web\") throw new DidError(\"Only a did:web can publish its proof\");\n const options = { fetch: ctx.fetch, signal: ctx.signal, now: () => ctx.now * 1000 };\n const { document, host } = await resolveDid(subject, options);\n const messages = didSignedMessages(statement.text);\n if (evidence.method === \"jws\" || evidence.method === \"sig\") {\n const check = evidence.method === \"jws\"\n ? checkDidJws(evidence.jws, messages, document)\n : checkDidSignature(strictBase64Url(evidence.sig), messages, document, evidence.vm);\n if (check.key.id !== evidence.vm) throw new DidError(\"Signed by another key than the proof names\");\n return { subject, source: `did:${method} · ${evidence.method === \"jws\" ? `${check.alg} JWS` : `${check.alg} signature`} by ${fragment(check.key.id)}` };\n }\n if (evidence.method === \"service\") {\n if (!document.services.some(s => s.proof === statement.id && s.key === statement.binding.key)) throw new DidError(\"The did.json has no Ghostly service for this proof: it was removed, or not added yet\");\n return { subject, source: `did:web · service in the did.json of ${host}` };\n }\n const text = await fetchDidWebFile(subject, statement.id, options);\n if (text === undefined) throw new DidError(`${didWebFileUrl(subject, statement.id)} was not found: it was deleted, or not uploaded yet`);\n if (!didWebFileNames(text, subject, statement.text)) throw new DidError(`${didWebFileUrl(subject, statement.id)} is not the statement of this proof`);\n return { subject, source: `did:web · statement file beside the did.json of ${host}` };\n },\n};\n","import {\n ATPROTO_DECLARED_SCOPE, ATPROTO_FULL_SCOPE, ATPROTO_PROOF_COLLECTION, ATPROTO_PROOF_SCOPE, atprotoProofRecord, atprotoProofRkey,\n type AtprotoDidDocument, type IdentityStatement,\n} from \"@ghostly/core\";\nimport type { IdentityFetch, IdentityPlatform } from \"../contract\";\nimport { resolveAtprotoDid, resolveAtprotoHandle, type AtprotoResolveOptions } from \"./resolve\";\n\n/**\n * The prover's half of AT Protocol proofs: AT Protocol OAuth (PAR + PKCE + DPoP, through the official\n * `@atproto/oauth-client`) against the person's own server, then one record written or deleted. Every\n * flow gets a fresh client whose stores live in memory only: the DPoP key (a non-extractable WebCrypto\n * key), the PKCE verifier and the tokens exist for this one flow, are never logged or persisted, and the\n * session is revoked (signOut) as soon as the record is written. Nothing is kept for later: removing\n * the proof asks the person again.\n *\n * The client is identified by the URL of its metadata document (no registration with anyone):\n * https://ghostly.tools/oauth/client-metadata.json, which is ATPROTO_CLIENT_METADATA verbatim\n * (apps/website/public/oauth/client-metadata.json; a unit test keeps the two equal). A web app served from\n * a loopback address (development, the e2e suite) uses AT Protocol's `http://localhost` development\n * client instead, which needs no document.\n */\n\nexport const ATPROTO_CLIENT_ID = \"https://ghostly.tools/oauth/client-metadata.json\";\n/** The extension's id in the Chrome Web Store: its chromiumapp.org redirect is registered. Unpacked builds have another id. */\nexport const EXTENSION_ID = \"nbedaagicniejlmfcncndfjcejaidbcf\";\nexport const ATPROTO_REDIRECTS = {\n /** The web app's static callback page (shared with OpenID Connect): the answer stays in the fragment. */\n web: \"https://app.ghostly.tools/oidc-callback.html\",\n /** chrome.identity.launchWebAuthFlow catches it without loading it. */\n extension: `https://${EXTENSION_ID}.chromiumapp.org/atproto`,\n /** The desktop app's one-shot listener on 127.0.0.1; the port is not part of the match. */\n desktop: \"http://127.0.0.1/oidc-callback\",\n} as const;\n\n/**\n * `native` because the desktop app's loopback redirect is only allowed for native clients; HTTPS\n * redirects (web, extension) are allowed for them too. No `refresh_token` grant: Ghostly never keeps\n * a session, so it never gets a refresh token to lose.\n */\nexport const ATPROTO_CLIENT_METADATA = {\n client_id: ATPROTO_CLIENT_ID,\n client_name: \"Ghostly\",\n client_uri: \"https://ghostly.tools\",\n logo_uri: \"https://ghostly.tools/icon-192.png\",\n application_type: \"native\",\n grant_types: [\"authorization_code\"],\n response_types: [\"code\"],\n scope: ATPROTO_DECLARED_SCOPE,\n token_endpoint_auth_method: \"none\",\n dpop_bound_access_tokens: true,\n redirect_uris: [ATPROTO_REDIRECTS.web, ATPROTO_REDIRECTS.extension, ATPROTO_REDIRECTS.desktop],\n} as const;\n\n/** AT Protocol's development client: no document, loopback redirects only, any port. */\nexport function loopbackClientMetadata(redirectUri: string) {\n const registered = new URL(redirectUri);\n registered.port = \"\";\n const id = `http://localhost?${new URLSearchParams({ redirect_uri: registered.href, scope: ATPROTO_DECLARED_SCOPE })}`;\n return {\n client_id: id, application_type: \"native\", grant_types: [\"authorization_code\"], response_types: [\"code\"], scope: ATPROTO_DECLARED_SCOPE,\n token_endpoint_auth_method: \"none\", dpop_bound_access_tokens: true, redirect_uris: [registered.href],\n } as const;\n}\n\nconst isLoopback = (url: string) => { try { const u = new URL(url); return u.protocol === \"http:\" && (u.hostname === \"127.0.0.1\" || u.hostname === \"[::1]\"); } catch { return false; } };\nconst withoutPort = (url: string) => { const u = new URL(url); u.port = \"\"; return u.href; };\n\n/**\n * The client for a redirect: the published one, or the development client on a loopback web app. A\n * loopback redirect is registered without its port (servers match any port), but the library checks\n * the exact address locally, so the one in use is added to the copy it gets; the server only ever sees\n * the client id and reads the registered addresses itself.\n */\nexport function clientMetadataFor(platform: IdentityPlatform, redirectUri: string) {\n const base = platform === \"web\" && isLoopback(redirectUri) ? loopbackClientMetadata(redirectUri) : ATPROTO_CLIENT_METADATA;\n const registered = base.redirect_uris as readonly string[];\n const matches = registered.includes(redirectUri) || (isLoopback(redirectUri) && registered.includes(withoutPort(redirectUri)));\n if (!matches) throw new Error(\"This Ghostly build cannot sign in to an AT Protocol server here (its return address is not registered).\");\n return { ...base, redirect_uris: [...new Set([redirectUri, ...registered])] };\n}\n\n/** How a platform shows the server's page and hands back where it redirected. */\nexport interface AtprotoWindow {\n /** The redirect URI for this platform, as used in the request (the desktop's carries its port). */\n redirectUri: string;\n /** Opens `url`; resolves with the full address the server redirected to, carrying `state`. */\n authorize(url: string, state: string, signal: AbortSignal): Promise<string>;\n /** Releases the window or listener, answered or not. */\n close(): void;\n}\n\nexport interface AtprotoHost {\n platform: IdentityPlatform;\n /** Call it straight from the click: the web app opens its popup before anything is awaited. */\n open(): Promise<AtprotoWindow>;\n}\n\n/** Which permission to ask for: only Ghostly's records, or the whole repository (older servers). */\nexport type AtprotoAccess = \"proof-records\" | \"full\";\nexport const scopeFor = (access: AtprotoAccess) => (access === \"full\" ? ATPROTO_FULL_SCOPE : ATPROTO_PROOF_SCOPE);\n\n/** The server refused the narrow permission: it predates granular scopes. */\nexport class AtprotoScopeError extends Error {}\n\n/** The account a handle (or a DID typed as is) names, with its document. */\nexport async function lookupAtprotoAccount(input: string, options: AtprotoResolveOptions & { normalize(input: string): string | null }): Promise<AtprotoDidDocument> {\n const value = input.trim();\n if (value.startsWith(\"did:\")) return resolveAtprotoDid(value, options);\n const handle = options.normalize(value);\n if (!handle) throw new Error(\"That is not a handle (like alice.bsky.social)\");\n const did = await resolveAtprotoHandle(handle, options);\n if (!did) throw new Error(`${handle} does not point to an AT Protocol account`);\n const doc = await resolveAtprotoDid(did, options);\n if (doc.handle !== handle) throw new Error(`${handle} points to an account that names another handle`);\n return doc;\n}\n\nexport interface AtprotoFlowOptions {\n platform: IdentityPlatform;\n window: AtprotoWindow;\n account: AtprotoDidDocument;\n access: AtprotoAccess;\n signal: AbortSignal;\n /** The resolution the library uses: the same bounded lookups as the verifier. */\n resolve: AtprotoResolveOptions;\n onProgress(message: string): void;\n /** The HTTP client for the OAuth requests and the record (the page's fetch). */\n fetch?: typeof fetch;\n}\n\nconst MESSAGES: Record<string, string> = {\n access_denied: \"You declined on your server. Nothing was published.\",\n login_required: \"Log in on your server and try again.\",\n consent_required: \"Your server needs your approval to continue.\",\n};\n\n/** The redirect's parameters: the fragment (Ghostly asks for fragment responses), then the query. */\nexport function callbackParams(redirected: string, redirectUri: string): URLSearchParams {\n const url = new URL(redirected), expected = new URL(redirectUri);\n if (url.origin !== expected.origin || url.pathname !== expected.pathname) throw new Error(\"Sign-in returned to an unexpected address.\");\n const params = new URLSearchParams(url.hash.slice(1));\n for (const [k, v] of new URLSearchParams(url.search)) if (!params.has(k)) params.set(k, v);\n return params;\n}\n\nconst pdsHost = (pds: string) => new URL(pds).host;\n\n/** Runs OAuth for `account`, hands `work` a session scoped to this flow, and always revokes it afterwards. */\nasync function withSession<T>(options: AtprotoFlowOptions, work: (session: { did: string; fetchHandler(path: string, init?: RequestInit): Promise<Response> }) => Promise<T>): Promise<T> {\n const { window: w, account, signal } = options;\n signal.throwIfAborted();\n const [{ OAuthClient }, { WebcryptoKey }] = await Promise.all([import(\"@atproto/oauth-client\"), import(\"@atproto/jwk-webcrypto\")]);\n const states = new Map<string, unknown>(), sessions = new Map<string, unknown>();\n let state: string | undefined;\n const memory = <V>(map: Map<string, V>, onSet?: (key: string) => void) => ({\n async get(key: string) { return map.get(key); },\n async set(key: string, value: V) { onSet?.(key); map.set(key, value); },\n async del(key: string) { map.delete(key); },\n });\n const client = new OAuthClient({\n responseMode: \"fragment\",\n clientMetadata: clientMetadataFor(options.platform, w.redirectUri) as never,\n stateStore: memory(states, key => { state = key; }) as never,\n sessionStore: memory(sessions) as never,\n runtimeImplementation: {\n createKey: algs => WebcryptoKey.generate(algs),\n getRandomValues: length => crypto.getRandomValues(new Uint8Array(length)),\n digest: async (data, { name }) => new Uint8Array(await crypto.subtle.digest(`SHA-${name.slice(3)}`, data)),\n },\n // The same lookups the verifier makes, bounded, through the person's DoH resolver: no third-party handle resolver.\n identityResolver: {\n async resolve(input: string) {\n const did = input.startsWith(\"did:\") ? input : await resolveAtprotoHandle(input, options.resolve);\n if (!did) throw new Error(\"The account could not be resolved\");\n const doc = await fetchDidJson(did, options.resolve);\n return { did, didDoc: doc.json, handle: doc.parsed.handle ?? \"handle.invalid\" } as never;\n },\n },\n fetch: options.fetch ?? globalThis.fetch.bind(globalThis),\n allowHttp: false,\n });\n let url: URL;\n try {\n url = await client.authorize(account.did, { scope: scopeFor(options.access), redirect_uri: w.redirectUri as never, signal });\n } catch (e) {\n const text = String((e as { error?: string })?.error ?? (e as Error)?.message ?? e);\n if (/invalid_scope/i.test(text) || /scope/i.test(String((e as { errorDescription?: string })?.errorDescription ?? \"\"))) throw new AtprotoScopeError(\n \"Your server does not offer a permission limited to Ghostly's records: it only offers full access to your account. Choose \\\"Your server, full access\\\" to continue anyway; Ghostly still only writes this one record.\");\n // eslint-disable-next-line preserve-caught-error -- A message people can act on; the library's error stays out of the UI (ES2020 has no Error.cause).\n throw new Error(`Your server (${pdsHost(account.pds)}) refused to start the sign-in.`);\n }\n if (!state) throw new Error(\"The sign-in could not start.\");\n options.onProgress(`Approve on ${pdsHost(account.pds)} in the window that opened…`);\n const redirected = await w.authorize(url.href, state, signal);\n signal.throwIfAborted();\n const params = callbackParams(redirected, w.redirectUri);\n const error = params.get(\"error\");\n if (error) throw new Error(MESSAGES[error] ?? \"Your server refused the sign-in.\");\n let session: Awaited<ReturnType<typeof client.callback>>[\"session\"];\n try { ({ session } = await client.callback(params, { redirect_uri: w.redirectUri as never })); }\n catch { throw new Error(\"The sign-in could not be completed. Nothing was published.\"); }\n try {\n if (session.did !== account.did) throw new Error(\"You logged in to another account than the one you typed. Nothing was published.\");\n return await work(session);\n } finally {\n // Revokes the tokens on the server; the DPoP key and verifier go out of scope with the client.\n await session.signOut().catch(() => {});\n sessions.clear(); states.clear();\n }\n}\n\n/** A DID's document, checked, and the JSON the library reads its services from. */\nasync function fetchDidJson(did: string, options: AtprotoResolveOptions): Promise<{ parsed: AtprotoDidDocument; json: unknown }> {\n let json: unknown;\n const recording: IdentityFetch = async (url, init) => {\n const response = await options.fetch(url, init);\n try { json = JSON.parse(response.text); } catch { /* resolveAtprotoDid says so */ }\n return response;\n };\n const parsed = await resolveAtprotoDid(did, { ...options, fetch: recording });\n return { parsed, json };\n}\n\nasync function xrpcError(response: Response): Promise<string> {\n const body = await response.json().catch(() => ({})) as { error?: string; message?: string };\n return `${body.error ?? response.status}${body.message ? `: ${body.message}` : \"\"}`;\n}\n\n/** Writes the proof record (rkey = the proof key) and returns once the server has it. */\nexport async function publishAtprotoProof(statement: IdentityStatement, options: AtprotoFlowOptions): Promise<void> {\n await withSession(options, async session => {\n options.onProgress(`Publishing the proof on ${pdsHost(options.account.pds)}…`);\n const response = await session.fetchHandler(\"/xrpc/com.atproto.repo.createRecord\", {\n method: \"POST\", headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ repo: session.did, collection: ATPROTO_PROOF_COLLECTION, rkey: atprotoProofRkey(statement.binding.key), record: atprotoProofRecord(statement, new Date()) }),\n });\n if (response.status === 403) throw new AtprotoScopeError(\"Your server did not let Ghostly write its record. Choose \\\"Your server, full access\\\" to try again.\");\n if (!response.ok) throw new Error(`Your server did not publish the record (${await xrpcError(response)}).`);\n });\n}\n\n/** Deletes the proof record. Deleting a record that is not there succeeds. */\nexport async function unpublishAtprotoProof(proofKey: string, options: AtprotoFlowOptions): Promise<void> {\n await withSession(options, async session => {\n options.onProgress(`Deleting the record on ${pdsHost(options.account.pds)}…`);\n const response = await session.fetchHandler(\"/xrpc/com.atproto.repo.deleteRecord\", {\n method: \"POST\", headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ repo: session.did, collection: ATPROTO_PROOF_COLLECTION, rkey: atprotoProofRkey(proofKey) }),\n });\n if (response.status === 403) throw new AtprotoScopeError(\"Your server did not let Ghostly delete its record.\");\n if (!response.ok) throw new Error(`Your server did not delete the record (${await xrpcError(response)}).`);\n });\n}\n","import {\n ATPROTO_PROOF_COLLECTION, AtprotoProofError, atprotoProofRkey, checkAtprotoProofRecord, isAtprotoDid, normalizeAtprotoHandle, shortAtprotoDid,\n verifyAtprotoRecordProof,\n} from \"@ghostly/core\";\nimport type { IdentityFetch, IdentityProofProvider, InAppSigner, SignerContext } from \"../contract\";\nimport { getBrowserHost } from \"../../host\";\nimport { boundedIdentityFetch } from \"../verify\";\nimport { chosenResolver } from \"../domain\";\nimport { lookupAtprotoAccount, publishAtprotoProof, unpublishAtprotoProof, type AtprotoAccess, type AtprotoHost, AtprotoScopeError } from \"../atproto/oauth\";\nimport { assertPublicServer, resolveAtprotoDid, verifiedAtprotoHandle, type AtprotoResolveOptions } from \"../atproto/resolve\";\n\n/**\n * Identity proofs for an AT Protocol account (Bluesky, or any PDS): the account publishes, in its own\n * repository, one record of the collection `tools.ghostly.proof` carrying the statement, keyed by the\n * proof key. Publishing takes an OAuth approval on the person's server that lets Ghostly create and\n * delete records of that collection only. A contact's app checks it without logging in: the DID\n * document (PLC directory or did:web) names the signing key and the server; the server's\n * `com.atproto.sync.getRecord` answer is a CAR whose commit must be signed by that key and whose tree\n * must lead to the record. The handle is shown only when it resolves back to the DID.\n *\n * The subject is the DID (it outlives handles). Evidence is empty: everything is fetched from the\n * account's own repository, so deleting the record (removing the proof here does) makes re-checks fail.\n * See docs/wisps/312-atproto.md and docs/lexicons/tools.ghostly.proof.json.\n */\nexport type AtprotoEvidence = Record<string, never>;\n\n/** The proof CAR: a commit, the tree path and one small record. Real repositories' paths are a few KiB. */\nexport const RECORD_PROOF_MAX_BYTES = 256 * 1024;\n\nfunction currentHost(): AtprotoHost | undefined {\n try { return getBrowserHost().atproto; } catch { return undefined; }\n}\n\nconst hostOf = (url: string) => new URL(url).host;\n\nexport interface AtprotoIdentityOptions {\n /** The platform's sign-in window. */\n host?: () => AtprotoHost | undefined;\n /** The UI side's network for looking the account up before signing in. */\n fetch?: IdentityFetch;\n /** The page's fetch for OAuth and the record (tests pass a fake server). */\n oauthFetch?: typeof fetch;\n /** The PLC directory (tests). */\n plc?: string;\n}\n\nexport function createAtprotoIdentityProvider(options: AtprotoIdentityOptions = {}): IdentityProofProvider<AtprotoEvidence> {\n const host = options.host ?? currentHost;\n const uiFetch = () => options.fetch ?? boundedIdentityFetch({ online: () => true });\n const resolveOptions = (fetch: IdentityFetch, signal?: AbortSignal): AtprotoResolveOptions =>\n ({ fetch, signal, resolver: chosenResolver().id, ...(options.plc ? { plc: options.plc } : {}) });\n\n /** Opens the window from the click, looks the account up, then runs OAuth for `work`; always closes the window. */\n function signIn<T>(input: string, access: AtprotoAccess, ctx: SignerContext, work: (flow: Parameters<typeof publishAtprotoProof>[1]) => Promise<T>): Promise<T> {\n // Before any await: the web app's popup needs the click's user activation.\n const h = host();\n const opening = h?.open();\n return (async () => {\n if (!h || !opening) throw new Error(\"Signing in to an AT Protocol server is not available here.\");\n const w = await opening;\n try {\n const resolve = resolveOptions(uiFetch(), ctx.signal);\n ctx.onProgress(`Looking up ${input.trim()}…`);\n const account = await lookupAtprotoAccount(input, { ...resolve, normalize: normalizeAtprotoHandle });\n ctx.signal.throwIfAborted();\n return await work({ platform: h.platform, window: w, account, access, signal: ctx.signal, resolve, onProgress: ctx.onProgress, fetch: options.oauthFetch });\n } finally { w.close(); }\n })();\n }\n\n const signer = (id: string, access: AtprotoAccess, label: string, description: string): InAppSigner<AtprotoEvidence> => ({\n id, kind: \"in-app\", label, description,\n action: access === \"full\" ? \"Continue on your server (full access)\" : \"Continue on your server\",\n available: () => !!host(),\n fields: [{\n name: \"handle\", label: \"Handle\", kind: \"text\", placeholder: \"alice.bsky.social\",\n help: \"Your Bluesky handle, your own domain if you use one, or your DID.\",\n }],\n run(ctx, work) {\n return signIn(ctx.values.handle ?? \"\", access, ctx, flow => work({\n subject: async () => flow.account.did,\n sign: async statement => { await publishAtprotoProof(statement, flow); return {}; },\n }));\n },\n });\n\n // Removing asks for a fresh approval; the server that refused the narrow permission once is asked for full access.\n const fullAccessServers = new Set<string>();\n\n return {\n id: \"atproto\",\n label: \"Bluesky / AT Protocol\",\n category: \"self-custodied\",\n summary: \"Publish a record on your Bluesky server\",\n description: \"Proves you control a Bluesky (AT Protocol) account: approve on your server, and Ghostly publishes one record in your account's repository. It is public, like the rest of your repository: anyone can see this account made a Ghostly proof, not who you share it with. Contacts check it without logging in.\",\n limits: \"Signed with your account's key, which your server usually holds. It proves control of the account, not who you are; the handle is shown only while it points back to the account.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Account (DID)\",\n placeholder: \"did:plc:…\",\n normalize(input: string) {\n const did = input.trim();\n if (!isAtprotoDid(did)) throw new Error(\"Not an AT Protocol account (did:plc or did:web)\");\n return did;\n },\n short: shortAtprotoDid,\n },\n // The account's AT URI, for a public DID document's alsoKnownAs (#247), when the person lists it there.\n publicUri: did => `at://${did}`,\n validity: { defaultDays: 90, maxDays: 365 },\n // The record can be deleted any time: contacts look again after an hour.\n recheck: { afterSeconds: 3600 },\n privacy: \"Your contact's app asks the PLC directory (or your did:web host) for your account's document, your server for the record, and its DNS-over-HTTPS resolver or your handle's website for your handle, so they learn this account was checked.\",\n signers: [\n signer(\"atproto-oauth\", \"proof-records\", \"Your server (Bluesky or another PDS)\",\n \"Opens your server's login page. Ghostly asks only to create and delete its own proof records: never to post, read your messages or edit your profile.\"),\n signer(\"atproto-oauth-full\", \"full\", \"Your server, full access (older servers)\",\n \"Only if your server says it cannot limit Ghostly's permission: it then asks for access to your whole account. Ghostly still only writes this one record, and gives the access back right away.\"),\n ],\n parseEvidence(raw) {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw) || Object.keys(raw).length) throw new Error(\"Invalid AT Protocol evidence\");\n return {};\n },\n async verify(statement, _evidence, ctx) {\n const did = statement.binding.subject;\n if (!isAtprotoDid(did)) throw new Error(\"Not an AT Protocol account\");\n const resolve = resolveOptions(ctx.fetch, ctx.signal);\n // The server comes from the DID document, never from the proof.\n const doc = await resolveAtprotoDid(did, resolve);\n await assertPublicServer(hostOf(doc.pds).replace(/:\\d+$/, \"\"), resolve);\n const rkey = atprotoProofRkey(statement.binding.key);\n const url = `${doc.pds}/xrpc/com.atproto.sync.getRecord?${new URLSearchParams({ did, collection: ATPROTO_PROOF_COLLECTION, rkey })}`;\n let response: Awaited<ReturnType<IdentityFetch>>;\n try { response = await ctx.fetch(url, { headers: { accept: \"application/vnd.ipld.car\" }, maxBytes: RECORD_PROOF_MAX_BYTES, signal: ctx.signal, redirect: \"error\" }); }\n // eslint-disable-next-line preserve-caught-error -- A message people can act on; the library's error stays out of the UI (ES2020 has no Error.cause).\n catch (e) { throw new Error(/too large/i.test(String(e)) ? \"The account's server sent a proof that is too large\" : `The account's server (${hostOf(doc.pds)}) could not be reached`); }\n if (response.status !== 200) {\n let code = \"\";\n try { code = String((JSON.parse(response.text) as { error?: unknown }).error ?? \"\"); } catch { /* not JSON */ }\n if (code === \"RecordNotFound\") throw new Error(\"The record is no longer in the account's repository\");\n if (/^Repo(NotFound|Deactivated|Takendown|Suspended)$/.test(code)) throw new Error(\"The account is no longer on its server\");\n throw new Error(`The account's server (${hostOf(doc.pds)}) did not send the record`);\n }\n let proof: ReturnType<typeof verifyAtprotoRecordProof>;\n try { proof = verifyAtprotoRecordProof(response.bytes, { did, key: doc.key, collection: ATPROTO_PROOF_COLLECTION, rkey }); }\n // eslint-disable-next-line preserve-caught-error -- A message people can act on; the library's error stays out of the UI (ES2020 has no Error.cause).\n catch (e) { throw new Error(e instanceof AtprotoProofError ? `The account's repository does not prove the record: ${e.message}` : \"The account's repository could not be read\"); }\n if (!proof.record) throw new Error(\"The record is no longer in the account's repository\");\n checkAtprotoProofRecord(proof.record.value, statement);\n const handle = await verifiedAtprotoHandle(doc, resolve);\n return {\n subject: did,\n source: `Record signed with the account's key, from ${hostOf(doc.pds)}`,\n display: {\n ...(handle ? { name: `@${handle}` } : {}),\n url: `https://bsky.app/profile/${did}`,\n source: handle ? \"Handle checked both ways: it points to this account\" : \"The account's handle does not point back to it, so it is not shown\",\n fetchedAt: ctx.now,\n },\n };\n },\n unpublish: {\n description: \"Also deletes the record on your server: your server asks you to approve.\",\n run(proof, ctx) {\n return signIn(proof.subject, \"proof-records\", ctx, async flow => {\n const access: AtprotoAccess = fullAccessServers.has(flow.account.pds) ? \"full\" : \"proof-records\";\n try { await unpublishAtprotoProof(proof.key, { ...flow, access }); }\n catch (e) {\n if (e instanceof AtprotoScopeError && access !== \"full\") {\n fullAccessServers.add(flow.account.pds);\n // eslint-disable-next-line preserve-caught-error -- A message people can act on; the library's error stays out of the UI (ES2020 has no Error.cause).\n throw new Error(\"Your server only offers full access to your account. Try again to approve that; Ghostly only deletes this one record.\");\n }\n throw e;\n }\n });\n },\n },\n };\n}\n\nexport const atproto = createAtprotoIdentityProvider();\n","import {\n homeserverWebEndpoint, IdentityCheckUnavailable, openRelayPayload, pubkyHomeserverOf, PUBKY_PROOF_MAX_BYTES, RELAY_PAYLOAD_MAX_BYTES,\n type HomeserverEndpoint,\n} from \"@ghostly/core\";\nimport type { AuthFlow, GrantAuthFlow, Session } from \"@synonymdev/pubky\";\nimport type { ApprovalRequest, IdentityFetch } from \"./contract\";\nimport { assertPublicHost, chosenResolver, type DohResolverId } from \"./domain\";\nimport { getBrowserHost, type PubkyCookieSession } from \"../host\";\n\n/**\n * Pubky, browser side: reading a key's records and files the way a contact's app does (through the proof\n * contract's bounded `ctx.fetch` only), and asking the person to approve one Pubky auth request in Pubky Ring or\n * Pubky Passport (https://github.com/pubky/pubky-passport/blob/main/docs/integration.md). See WISP 302.\n */\n\n// -- reading ---------------------------------------------------------------------------------------\n\n/** Pkarr relays the records are read from: Pubky's own, the ones the Pubky SDK uses. */\nexport const PUBKY_RELAYS: readonly string[] = [\"https://pkarr.pubky.org\", \"https://pkarr.pubky.app\"];\n\n/**\n * The newest DNS packet the relays hold for `key`, its signature checked against `key`. Every relay is asked; one\n * that is down, answers garbage or holds an older packet does not count. Throws when none has a valid one.\n */\nexport async function pubkyRecords(key: string, fetch: IdentityFetch, signal?: AbortSignal, relays: readonly string[] = PUBKY_RELAYS): Promise<Uint8Array> {\n const failed: unknown[] = [];\n const answers = await Promise.all(relays.map(async relay => {\n let r;\n try { r = await fetch(`${relay.replace(/\\/+$/, \"\")}/${key}`, { maxBytes: RELAY_PAYLOAD_MAX_BYTES, signal }); }\n catch (e) { failed.push(e); return undefined; }\n try { return r.status === 200 ? openRelayPayload(key, r.bytes) : undefined; } catch { return undefined; }\n }));\n const newest = answers.filter(a => !!a).sort((a, b) => (b.seq > a.seq ? 1 : b.seq < a.seq ? -1 : 0))[0];\n // No relay answered at all (offline, a time-out): say that, not that the key has no records.\n if (!newest && failed.length === relays.length && failed[0] instanceof Error) throw failed[0];\n if (!newest) throw new Error(\"This Pubky key has no records on the Pkarr relays\");\n return newest.dnsPacket;\n}\n\n/** Where `key`'s homeserver answers browsers: its `_pubky` record, then the homeserver's own HTTPS record. */\nexport async function pubkyHomeserver(key: string, fetch: IdentityFetch, signal?: AbortSignal, relays?: readonly string[]): Promise<{ homeserver: string; endpoint: HomeserverEndpoint }> {\n const homeserver = pubkyHomeserverOf(await pubkyRecords(key, fetch, signal, relays), key);\n if (!homeserver) throw new Error(\"This Pubky key names no homeserver\");\n const endpoint = homeserverWebEndpoint(await pubkyRecords(homeserver, fetch, signal, relays), homeserver);\n if (!endpoint) throw new Error(\"The homeserver of this Pubky key publishes no address a browser can reach\");\n return { homeserver, endpoint };\n}\n\n/**\n * A public file of `key` at `path`, from the homeserver its own records name: bounded, no redirects, the owner in the\n * `pubky-host` header the way the SDK addresses it. `undefined` when the homeserver says it is not there.\n *\n * The key's owner chooses that host, so it is contacted only on port 443 and only once its addresses are all public\n * (the name is resolved first through the chosen DNS-over-HTTPS resolver): a proof never makes a contact's app knock\n * on its own network. Failures on the way there are IdentityCheckUnavailable, which the contact is not told about.\n */\nexport async function readPubkyFile(key: string, path: `/pub/${string}`, fetch: IdentityFetch, signal?: AbortSignal, relays?: readonly string[], resolver: DohResolverId = chosenResolver().id): Promise<{ text: string | undefined; host: string }> {\n const { endpoint: { host, port } } = await pubkyHomeserver(key, fetch, signal, relays);\n if (port !== undefined) throw new IdentityCheckUnavailable(`The homeserver ${host} answers on port ${port}; only port 443 is contacted`);\n try { await assertPublicHost(host, { fetch, signal, resolver }); }\n catch (e) { throw new IdentityCheckUnavailable(`The homeserver ${host} was not contacted: ${e instanceof Error ? e.message : String(e)}`); }\n let r: Awaited<ReturnType<IdentityFetch>>;\n try { r = await fetch(`https://${host}${path}`, { headers: { \"pubky-host\": key }, maxBytes: PUBKY_PROOF_MAX_BYTES, signal }); }\n catch (e) {\n // A file over the cap is the owner's doing, and says nothing about this side's network.\n if (/too large/i.test(String(e))) throw e;\n throw new IdentityCheckUnavailable(`The homeserver ${host} could not be reached`);\n }\n if (r.status === 404) return { text: undefined, host };\n if (r.status !== 200) throw new IdentityCheckUnavailable(`The homeserver ${host} answered ${r.status}`);\n return { text: r.text, host };\n}\n\n// -- approving -------------------------------------------------------------------------------------\n\n/** The app identifier Pubky Ring and Passport list the grant under. */\nexport const PUBKY_CLIENT_ID = \"ghostly.tools\";\nexport const PASSPORT_ORIGIN = \"https://passport.pubky.app\";\n/** How long one request waits: Pubky's relay keeps a message about five minutes. */\nconst APPROVAL_TIMEOUT = 5 * 60_000;\nconst POLL_EVERY = 1_000;\n\n/**\n * Passport's authorize page for a request. The authorization URL carries the relay secret: it goes in the fragment\n * (never sent to a server), encoded exactly once, and is never logged.\n */\nexport const passportUrl = (authorizationUrl: string) => `${PASSPORT_ORIGIN}/authorize#d=${encodeURIComponent(authorizationUrl)}`;\n\n/** Messages from the SDK may quote the request: nothing that looks like one reaches the UI or a log. */\nconst redact = (text: string) => text.replace(/pubkyauth:\\/\\/\\S*/gi, \"(request)\").replace(/#d=\\S*/g, \"#d=(request)\");\nconst failure = (what: string, e: unknown) => new Error(`${what}${e instanceof Error && e.message ? `: ${redact(e.message)}` : \"\"}`);\n\n/** An approved Pubky session, only as long as `work` runs: the key, and writes inside the one folder granted. */\nexport interface PubkyApprovedSession {\n key: string;\n put(path: `/pub/${string}`, text: string): Promise<void>;\n delete(path: `/pub/${string}`): Promise<void>;\n}\n\nexport interface PubkyApprovalOptions {\n /** The one capability asked for, `/pub/…/:w`. A session granted anything else is refused. */\n capability: `/pub/${string}/:w`;\n signal: AbortSignal;\n onApproval(request: ApprovalRequest | null): void;\n onProgress(message: string): void;\n /** Where Passport opens. The desktop app hands it to the system browser; elsewhere a popup from the click. */\n openPassport?: (url: string) => Window | null | void;\n /** Tests: the SDK's relay, and a shorter wait. */\n relay?: string;\n timeoutMs?: number;\n}\n\n/**\n * Opens a Passport window from the click that calls it. It must run synchronously in the click handler, before\n * anything is awaited, or the browser blocks the popup; the desktop app opens the system browser instead.\n */\nexport function openPassportWindow(url: string): Window | null | void {\n let host: ReturnType<typeof getBrowserHost> | undefined;\n try { host = getBrowserHost(); } catch { host = undefined; }\n if (host?.openPubkyPassport) { void host.openPubkyPassport(url).catch(() => {}); return; }\n // Not `noopener`: the reference lets Ghostly close the window once either signer approved.\n return window.open(url, \"pubky-passport\", \"popup,width=520,height=760\");\n}\n\n/** What a request of either kind offers while it waits: one poll at a time, freed once no poll is in flight. */\ninterface PendingRequest { tryPollOnce(): Promise<Session | undefined>; free(): void }\n\n/** A 401 or 403 from the homeserver: the session it was given is not one it accepts. */\nconst refused = (e: unknown) => {\n const status = (e as { data?: { statusCode?: unknown } } | null)?.data?.statusCode;\n return status === 401 || status === 403;\n};\n\n/**\n * Polls every request until one of them is approved and resolves with that session and its request; a cancel, the\n * deadline or a failed poll ends the wait for all of them. Each request is freed once its last poll has settled\n * (never while one is in flight), and a session that lands after the end is signed out at once; `onSettled` runs\n * once all of that is done.\n */\nfunction firstApproval(requests: readonly PendingRequest[], signal: AbortSignal, timeoutMs: number, onSettled: () => void): Promise<{ session: Session; request: PendingRequest }> {\n return new Promise((resolve, reject) => {\n let over = false;\n let polling = requests.length;\n const wakers = new Set<() => void>();\n const end = (settle: () => void) => {\n if (over) return;\n over = true;\n clearTimeout(timer);\n signal.removeEventListener(\"abort\", cancel);\n for (const wake of wakers) wake();\n settle();\n };\n const cancel = () => end(() => reject(signal.reason));\n const timer = setTimeout(() => end(() => reject(new Error(\"Nobody approved the request in time. Start again for a new one.\"))), timeoutMs);\n if (signal.aborted) cancel(); else signal.addEventListener(\"abort\", cancel, { once: true });\n const pause = () => new Promise<void>(done => {\n const wake = () => { clearTimeout(t); wakers.delete(wake); done(); };\n const t = setTimeout(wake, POLL_EVERY);\n wakers.add(wake);\n });\n\n // The end does not wait for a poll in flight (one that got an approval is still redeeming it at the homeserver):\n // its request is freed when it settles, and a session it brings is signed out.\n const poll = async (request: PendingRequest) => {\n try {\n while (!over) {\n let session: Session | undefined;\n try { session = await request.tryPollOnce(); }\n catch (e) { end(() => reject(failure(\"The Pubky request failed\", e))); return; }\n if (session && over) { try { await session.signout(); } catch { /* best effort */ } finally { session.free(); } return; }\n if (session) { const approved = session; end(() => resolve({ session: approved, request })); return; }\n if (!over) await pause();\n }\n } finally {\n request.free();\n if (--polling === 0) onSettled();\n }\n };\n for (const request of requests) void poll(request);\n });\n}\n\n/**\n * A request of a cookie session (Ring's approval) to its homeserver: its `/session` or a file of its storage\n * (`/storage/<key>/pub/…`, or `/pub/…` where the homeserver does not address storage by path), sent with the\n * browser's credentials. A grant session's requests carry an `Authorization` header instead, and the relay's\n * and the Pkarr relays' are elsewhere: those stay the page's.\n */\nconst cookieSessionRequest = (request: Request) =>\n request.credentials === \"include\" && !request.headers.has(\"authorization\") && /^\\/(?:session$|storage\\/|pub\\/)/.test(new URL(request.url).pathname);\n\n/** Statuses a Response carries no body with. */\nconst NO_BODY = new Set([204, 205, 304]);\n\n/**\n * Sends the SDK's cookie-session requests through `transport` until the returned function runs. The SDK fetches with\n * the global fetch, so this wraps it; everything else goes on to the page's fetch as it was. The cookie the\n * homeserver sets stays in the transport: the page, and so the SDK, never sees it.\n */\nfunction routeCookieSession(transport: PubkyCookieSession): () => void {\n const pageFetch = globalThis.fetch;\n let closed = false;\n const routed = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n // A Request made from another takes its body over: whatever is made here is what goes on.\n const request = input instanceof Request && init === undefined ? input : new Request(input, init);\n // Once closed, a wrapper something else has wrapped since stays in place: it only passes requests on.\n if (closed || !cookieSessionRequest(request)) return pageFetch(request);\n request.signal.throwIfAborted();\n const body = request.body ? new Uint8Array(await request.arrayBuffer()) : null;\n let answer;\n try { answer = await transport.fetch({ url: request.url, method: request.method, headers: [...request.headers], body }); }\n catch (e) { throw Object.assign(new TypeError(e instanceof Error ? e.message : String(e)), { cause: e }); }\n const response = new Response(NO_BODY.has(answer.status) ? null : answer.body as BodyInit, { status: answer.status, headers: answer.headers });\n // The SDK parses the answer's URL, which a Response made here does not have: it is the request's (no redirects).\n Object.defineProperty(response, \"url\", { value: request.url });\n return response;\n };\n globalThis.fetch = routed;\n return () => {\n if (closed) return;\n closed = true;\n if (globalThis.fetch === routed) globalThis.fetch = pageFetch;\n transport.close();\n };\n}\n\n/** The host's way around a page that drops the homeserver's cookie, if it has one. */\nfunction cookieSessionTransport(): PubkyCookieSession | undefined {\n try { return getBrowserHost().pubkyCookieSession?.(); } catch { return undefined; }\n}\n\n/**\n * Starts one request for `capability` in each of the two forms Pubky signers read (a grant request for Passport, a\n * cookie request for Ring's QR code: see below), shows both on one screen, and waits for whichever is approved\n * first, then runs `work` with that session. The session lives only in this renderer's memory while `work` runs,\n * and is signed out afterwards; nothing about it is stored. An approval that arrives after the end is signed out.\n */\nexport async function withPubkyApproval<T>(options: PubkyApprovalOptions, work: (session: PubkyApprovedSession) => Promise<T>): Promise<T> {\n const { signal } = options;\n signal.throwIfAborted();\n const { AuthFlow, GrantAuthFlow, AuthFlowKind } = await import(\"@synonymdev/pubky\");\n signal.throwIfAborted();\n const relay = options.relay ?? null;\n let grant: GrantAuthFlow, cookie: AuthFlow;\n try {\n // The flow's proof-of-possession key stays in this flow's memory (not the SDK's delegated IndexedDB key).\n grant = GrantAuthFlow.start(options.capability, AuthFlowKind.signin(), { clientId: PUBKY_CLIENT_ID, relay, xCallback: { xSource: \"Ghostly\" } });\n } catch (e) { throw failure(\"Could not start a Pubky request\", e); }\n try {\n // Same capability and relay, its own secret. The SDK deprecates this flow, but it is the one Ring's store build reads.\n cookie = AuthFlow.start(options.capability, AuthFlowKind.signin(), relay, { xSource: \"Ghostly\" });\n } catch (e) { grant.free(); throw failure(\"Could not start a Pubky request\", e); }\n\n const opened: { window?: Window | null } = {};\n options.onApproval({\n open: {\n label: \"Approve in your browser (Pubky Passport)\",\n description: \"Opens Pubky Passport. No Passport identity yet? It offers “Continue with Google” and makes one.\",\n run: () => {\n const window = (options.openPassport ?? openPassportWindow)(passportUrl(grant.authorizationUrl));\n opened.window = window ?? null;\n // null: the browser refused the popup (undefined: it opened elsewhere, the system browser).\n if (window === null) options.onProgress(\"Your browser blocked the Passport window: allow pop-ups for Ghostly and press the button again, or scan the code with Pubky Ring.\");\n },\n },\n // Ring gets the cookie request (`pubkyauth://signin?…`), not the grant one: the Ring in the app stores (1.19)\n // predates pubky/pubky-ring#360 and answers \"Unrecognized format\" to a `signin_grant` link. Move this to\n // grant.authorizationUrl, and drop the cookie flow, once Ring's store build parses `signin_grant`.\n qr: { value: cookie.authorizationUrl, label: \"Or scan with Pubky Ring\" },\n notes: [\n \"An identity Passport makes with Google is recovered with Google plus Passport: both are needed, neither alone can.\",\n \"The code is your request: scan it yourself, and do not share it.\",\n ],\n });\n\n // Where the page would drop the cookie session's cookie (the desktop app), its requests go through the host, from\n // the first poll (an approval is redeemed inside one) until the last poll has settled and the work is over.\n const transport = cookieSessionTransport();\n const unroute = transport ? routeCookieSession(transport) : () => {};\n let settled!: () => void;\n const polled = new Promise<void>(done => { settled = done; });\n try {\n return await approveAndWork(options, work, { grant, cookie, opened, settled, cookieKept: !!transport });\n } finally {\n void polled.then(unroute);\n }\n}\n\n/**\n * The rest of `withPubkyApproval`, once both requests are shown: the wait, the check, and the work. `cookieKept`: the\n * host keeps a cookie session's cookie, so a refusal is not the page's cookie policy.\n */\nasync function approveAndWork<T>(options: PubkyApprovalOptions, work: (session: PubkyApprovedSession) => Promise<T>, { grant, cookie, opened, settled, cookieKept }: {\n grant: GrantAuthFlow; cookie: AuthFlow; opened: { window?: Window | null }; settled: () => void; cookieKept: boolean;\n}): Promise<T> {\n const { signal } = options;\n let session: Session, viaCookie: boolean;\n try {\n const first = await firstApproval([grant, cookie], signal, options.timeoutMs ?? APPROVAL_TIMEOUT, settled);\n session = first.session;\n viaCookie = first.request === cookie;\n } finally {\n options.onApproval(null);\n try { if (opened.window && !opened.window.closed) opened.window.close(); } catch { /* another origin's window: best effort */ }\n }\n\n options.onProgress(\"Approved. Checking what Pubky granted…\");\n let key: string;\n try {\n const info = session.info;\n const publicKey = info.publicKey;\n try {\n key = publicKey.z32();\n const granted = info.capabilities;\n if (granted.length !== 1 || granted[0] !== options.capability)\n throw new Error(\"The signer granted different permissions than Ghostly asked for (one proof folder). Nothing was written.\");\n } finally { publicKey.free(); info.free(); }\n } catch (e) {\n await session.signout().catch(() => {});\n session.free();\n throw e;\n }\n\n // A cookie session lives in the homeserver's cookie, a third-party cookie in a page. WebKit (Safari) and Vanadium drop it, so the\n // homeserver then refuses the session's writes: say that, not \"401\". The desktop app keeps it outside its WebView.\n const fail = (what: string) => (e: unknown) => {\n if (viaCookie && !cookieKept && refused(e))\n throw new Error(\"Pubky Ring approved, but this browser blocks your homeserver's sign-in cookie (Vanadium and Safari block cookies from other sites), so nothing was written. Approve with Pubky Passport instead, or use Firefox or the Ghostly desktop app.\");\n throw failure(what, e);\n };\n const approved = session;\n try {\n return await work({\n key,\n put: (path, text) => approved.storage.putText(path, text).catch(fail(\"Could not write to your homeserver\")),\n delete: path => approved.storage.delete(path).catch(fail(\"Could not delete from your homeserver\")),\n });\n } finally {\n // The grant is only needed for this: end it, and forget the session.\n try { await approved.signout(); } catch { /* the grant expires on its own */ }\n approved.free();\n }\n}\n","import {\n isPubkyKey, isPubkyProofFolder, newPubkyProofFolder, normalizePubkyKey, pubkyProofCapability, pubkyProofFile, pubkyProofPath,\n} from \"@ghostly/core\";\nimport type { IdentityPlatform, IdentityProofProvider, InAppSigner } from \"../contract\";\nimport { readPubkyFile, withPubkyApproval, type PubkyApprovalOptions } from \"../pubky\";\n\n/**\n * A Pubky identity (WISP 302, docs/wisps/302-pubky.md), proven by publication: Pubky Ring and Pubky Passport sign\n * only standard Pubky auth requests, so Ghostly asks, in ONE request either of them can approve, for write access to\n * one fresh folder of the key's homeserver (`/pub/ghostly.app/proofs/<random>/`), and writes the identity statement\n * there. Contacts' apps read the file back from the homeserver the key's own signed Pkarr records name. The session\n * is used once, in memory, and signed out; removing the proof needs a new approval to delete the file.\n */\nexport interface PubkyEvidence { folder: string }\n\nconst short = (key: string) => `pubky${key.slice(0, 4)}…${key.slice(-4)}`;\n\n/** Strict: exactly `{ folder }`, 64 hex characters. Nothing else, so no evidence can name a host or another path. */\nfunction parseEvidence(raw: unknown): PubkyEvidence {\n const e = raw as PubkyEvidence;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw) || Object.keys(raw).join(\",\") !== \"folder\" || !isPubkyProofFolder(e.folder))\n throw new Error(\"That is not Pubky proof evidence\");\n return { folder: e.folder };\n}\n\nexport interface PubkyIdentityOptions {\n /** Pkarr relays the verifier reads (tests). */\n relays?: readonly string[];\n /** The approval flow's knobs (tests: a local relay, a Passport stand-in). */\n approval?: Partial<Pick<PubkyApprovalOptions, \"relay\" | \"timeoutMs\" | \"openPassport\">>;\n}\n\nexport function createPubkyIdentityProvider(options: PubkyIdentityOptions = {}): IdentityProofProvider<PubkyEvidence> {\n const approve = <T>(capability: `/pub/${string}/:w`, ctx: Parameters<InAppSigner<PubkyEvidence>[\"run\"]>[0], work: Parameters<typeof withPubkyApproval<T>>[1]) => {\n if (!ctx.onApproval) throw new Error(\"This screen cannot show a Pubky request\");\n return withPubkyApproval({ ...options.approval, capability, signal: ctx.signal, onApproval: ctx.onApproval, onProgress: ctx.onProgress }, work);\n };\n\n const signer: InAppSigner<PubkyEvidence> = {\n id: \"pubky-auth\", kind: \"in-app\",\n label: \"Pubky Ring or Pubky Passport\",\n action: \"Continue\",\n description: \"One request: approve it in Pubky Passport in your browser, or scan it with Pubky Ring. Ghostly only asks to write one small file to a new folder of your homeserver.\",\n run(ctx, work) {\n const folder = newPubkyProofFolder();\n return approve(pubkyProofCapability(folder), ctx, async session => {\n let written: `/pub/${string}` | undefined;\n try {\n return await work({\n subject: async () => session.key,\n async sign(statement) {\n if (statement.binding.subject !== session.key) throw new Error(\"That is another Pubky identity\");\n ctx.onProgress(\"Writing the proof to your homeserver…\");\n const path = pubkyProofPath(folder, statement.id);\n await session.put(path, pubkyProofFile(statement));\n written = path;\n return { folder };\n },\n });\n } catch (e) {\n // Not saved (a failed check, a cancel): the file does not stay behind.\n if (written) await session.delete(written).catch(() => {});\n throw e;\n }\n });\n },\n };\n\n return {\n id: \"pubky\",\n label: \"Pubky\",\n category: \"self-custodied\",\n summary: \"Approve in Pubky Ring or Passport\",\n description: \"Proves you hold a Pubky key: approve one request in Pubky Ring or Pubky Passport, and Ghostly writes a small proof file to your homeserver. Contacts’ apps read it back through your key’s own records.\",\n limits: \"The file is on your homeserver: whoever runs it could write there too. It does not prove who holds the key.\",\n platforms: [\"web\", \"extension\", \"desktop\"] satisfies IdentityPlatform[],\n subject: {\n label: \"Pubky key\",\n placeholder: \"pubky…\",\n help: \"Comes from Pubky Ring or Passport when you approve.\",\n normalize: normalizePubkyKey,\n short,\n },\n validity: { defaultDays: 90, maxDays: 365 },\n // The file can be deleted, or the key can move to another homeserver, before the proof expires.\n recheck: { afterSeconds: 86_400 },\n privacy: \"Your contact’s app reads your key’s records from Pubky’s Pkarr relays (pkarr.pubky.org, pkarr.pubky.app), looks up your homeserver’s address through its DNS-over-HTTPS resolver (or the next one when that cannot be reached; it only contacts a public address, on port 443), and reads the proof file from your homeserver, which sees the contact’s IP address.\",\n signers: [signer],\n // Pubky's own URI form for a key, as its SDK writes resources (`pubky://<key>/…`).\n publicUri: key => `pubky://${key}`,\n parseEvidence,\n async verify(statement, { folder }, ctx) {\n const key = statement.binding.subject;\n if (!isPubkyKey(key)) throw new Error(\"That is not a Pubky key\");\n const { text, host } = await readPubkyFile(key, pubkyProofPath(folder, statement.id), ctx.fetch, ctx.signal, options.relays);\n if (text === undefined) throw new Error(\"The proof file is not on the homeserver\");\n if (text !== pubkyProofFile(statement)) throw new Error(\"The file on the homeserver is not this proof\");\n return { subject: key, source: `File on the homeserver ${host}, found through the key’s own Pkarr records` };\n },\n unpublish: {\n description: \"Also deletes the proof file on your homeserver: that takes one more approval in Pubky Ring or Passport, since Ghostly kept no access to it.\",\n async run({ id, subject, evidence }, ctx) {\n // Stored when the proof was made: parsed like any evidence, so a bad record never names another path.\n const { folder } = parseEvidence(evidence);\n const path = pubkyProofPath(folder, id);\n return approve(pubkyProofCapability(folder), ctx, async session => {\n if (session.key !== subject) throw new Error(\"That is another Pubky identity: approve with the one this proof is for\");\n ctx.onProgress(\"Deleting the proof file from your homeserver…\");\n await session.delete(path);\n });\n },\n },\n };\n}\n\nexport const pubky = createPubkyIdentityProvider();\n","import { ed25519 } from \"@noble/curves/ed25519.js\";\nimport { fromBase64Url, toBase64Url, utf8Decode, utf8Encode, type IdentityStatement } from \"@ghostly/core\";\nimport { testBuild } from \"../shared/features\";\nimport type { IdentityProofProvider } from \"./contract\";\n\n/**\n * Fake identity providers, for unit tests and e2e. No network, no real accounts. They are in the\n * registry only in a dev or e2e build (`testBuild`) that has `localStorage[\"ghostly-test-identities\"] === \"1\"`\n * (or a test passes them itself); a release build ignores the key.\n *\n * - fake-key self-custodied: an Ed25519 key held by a fake in-app signer, or signed \"outside\" and pasted.\n * - fake-account provider-attested: a fake issuer signs a token whose nonce is the statement id (like OIDC).\n * - fake-record self-custodied, published: a record the verifier fetches through `ctx.fetch` (like DNS).\n */\nexport const TEST_IDENTITIES_FLAG = \"ghostly-test-identities\";\nexport const testIdentitiesEnabled = () => { try { return testBuild() && globalThis.localStorage?.getItem(TEST_IDENTITIES_FLAG) === \"1\"; } catch { return false; } };\n\nconst HEX64 = /^[a-f0-9]{64}$/;\nconst hex = (b: Uint8Array) => Array.from(b, x => x.toString(16).padStart(2, \"0\")).join(\"\");\nconst unhex = (s: string) => Uint8Array.from(s.match(/../g)!, x => parseInt(x, 16));\nconst normalizeHexKey = (input: string) => { const v = input.trim().toLowerCase(); if (!HEX64.test(v)) throw new Error(\"Enter 64 hex characters\"); return v; };\nconst exactKeys = (raw: unknown, keys: string) => !!raw && typeof raw === \"object\" && !Array.isArray(raw) && Object.keys(raw).sort().join(\",\") === keys;\n\n/** The key behind the fake in-app signer, made on first use so a release start draws no key. Tests may replace it. */\nlet fakeSeed: Uint8Array | undefined;\nexport const fakeKeyring = { get seed() { return fakeSeed ??= ed25519.utils.randomSecretKey(); }, set seed(value: Uint8Array) { fakeSeed = value; } };\nexport const fakeKeySubject = (seed = fakeKeyring.seed) => hex(ed25519.getPublicKey(seed));\nexport const fakeKeySign = (statement: IdentityStatement, seed = fakeKeyring.seed) => ({ sig: toBase64Url(ed25519.sign(statement.bytes, seed)) });\n\nexport interface FakeKeyEvidence { sig: string }\nexport const fakeKey: IdentityProofProvider<FakeKeyEvidence> = {\n id: \"fake-key\", label: \"Test key\", category: \"self-custodied\",\n summary: \"Sign with the test key\", description: \"A test identity: an Ed25519 key held by this page. For tests only.\",\n platforms: [\"web\", \"desktop\"],\n subject: { label: \"Public key\", placeholder: \"64 hex characters\", normalize: normalizeHexKey, short: k => `${k.slice(0, 8)}…${k.slice(-4)}` },\n publicUri: k => `urn:ghostly-test:${k}`,\n validity: { defaultDays: 30, maxDays: 365 },\n privacy: \"Nothing: checked on this device.\",\n signers: [\n { id: \"fake-signer\", kind: \"in-app\", label: \"Test signer\", fields: [{ name: \"refuse\", label: \"Refuse\", kind: \"text\", optional: true }],\n run: async (ctx, work) => {\n ctx.signal.throwIfAborted();\n if (ctx.values.refuse) throw new Error(\"The test signer refused\");\n return work({ subject: async () => fakeKeySubject(), sign: async statement => fakeKeySign(statement) });\n } },\n { id: \"fake-tool\", kind: \"external-tool\", label: \"Sign outside Ghostly\",\n instructions: statement => ({ steps: [{ text: \"Sign this statement with your test key:\", copy: statement.text }], paste: { label: \"Signature (base64url)\" } }),\n parse: pasted => { const sig = pasted.trim(); if (!/^[A-Za-z0-9_-]{86}$/.test(sig)) throw new Error(\"That is not a test signature\"); return { sig }; } },\n ],\n parseEvidence(raw) {\n const e = raw as FakeKeyEvidence;\n if (!exactKeys(raw, \"sig\") || typeof e.sig !== \"string\" || !/^[A-Za-z0-9_-]{86}$/.test(e.sig)) throw new Error(\"Invalid test evidence\");\n return { sig: e.sig };\n },\n async verify(statement, evidence) {\n let ok: boolean;\n try { ok = ed25519.verify(fromBase64Url(evidence.sig), statement.bytes, unhex(statement.binding.subject)); } catch { ok = false; }\n if (!ok) throw new Error(\"Invalid test signature\");\n return { subject: statement.binding.subject, source: \"Test signature (Ed25519)\" };\n },\n};\n\n/** The fake issuer's key. Its public half is what fake-account trusts, like a provider's JWKS. */\nconst ISSUER_SEED = unhex(\"7a\".repeat(32));\nexport const FAKE_ISSUER = \"https://issuer.ghostly.test\";\nexport interface FakeAccountEvidence { token: string }\n/** What the fake login page signs: iss, sub, nonce (= statement id), iat. */\nexport function fakeAccountToken(statement: IdentityStatement, account = \"alice\", seed = ISSUER_SEED, iat = statement.binding.issuedAt): FakeAccountEvidence {\n const payload = toBase64Url(utf8Encode(JSON.stringify({ iss: FAKE_ISSUER, sub: account, nonce: statement.id, iat })));\n return { token: `${payload}.${toBase64Url(ed25519.sign(utf8Encode(payload), seed))}` };\n}\nexport const fakeAccount: IdentityProofProvider<FakeAccountEvidence> = {\n id: \"fake-account\", label: \"Test account\", category: \"provider-attested\",\n summary: \"Log in to the test issuer\", description: \"A test login provider that vouches for an account. For tests only.\",\n platforms: [\"web\", \"desktop\"],\n subject: { label: \"Provider\", options: [{ value: FAKE_ISSUER, label: \"Test issuer\" }],\n normalize: input => { if (input.trim() !== FAKE_ISSUER) throw new Error(\"Unknown test issuer\"); return FAKE_ISSUER; } },\n validity: { defaultDays: 30, maxDays: 90 },\n privacy: \"Nothing: the issuer's signature is checked on this device.\",\n signers: [{ id: \"fake-login\", kind: \"redirect\", label: \"Log in to the test issuer\",\n start: async (statement, ctx) => { ctx.signal.throwIfAborted(); return fakeAccountToken(statement, ctx.values.account || \"alice\"); } }],\n parseEvidence(raw) {\n const e = raw as FakeAccountEvidence;\n if (!exactKeys(raw, \"token\") || typeof e.token !== \"string\" || e.token.length > 2048 || !/^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]{86}$/.test(e.token)) throw new Error(\"Invalid test token\");\n return { token: e.token };\n },\n async verify(statement, { token }) {\n const [payload, sig] = token.split(\".\");\n if (!ed25519.verify(fromBase64Url(sig), utf8Encode(payload), ed25519.getPublicKey(ISSUER_SEED))) throw new Error(\"Not signed by the test issuer\");\n const claims = JSON.parse(utf8Decode(fromBase64Url(payload))) as { iss: string; sub: string; nonce: string };\n if (claims.iss !== statement.binding.subject) throw new Error(\"Token from another issuer\");\n if (claims.nonce !== statement.id) throw new Error(\"Token for another statement\");\n if (typeof claims.sub !== \"string\" || !/^[a-z0-9]{1,64}$/.test(claims.sub)) throw new Error(\"Invalid account\");\n return { subject: `${claims.iss}#${claims.sub}`, attester: \"issuer.ghostly.test\", source: \"Token signed by the test issuer\",\n display: { name: claims.sub, source: \"Test issuer profile\", fetchedAt: statement.binding.issuedAt } };\n },\n};\n\n/** fake-record's records: subject → published text. The verifier reads `https://records.ghostly.test/<subject>`. */\nexport const FAKE_RECORD_HOST = \"https://records.ghostly.test/\";\nexport const fakeRecordText = (statement: IdentityStatement) => `ghostly-proof=${statement.id}`;\nexport const fakeRecord: IdentityProofProvider<Record<string, never>> = {\n id: \"fake-record\", label: \"Test record\", category: \"self-custodied\",\n summary: \"Publish a test record\", description: \"A test record published where the verifier looks it up, like DNS. For tests only.\",\n platforms: [\"web\", \"desktop\"],\n subject: { label: \"Name\", placeholder: \"alice\", normalize: input => { const v = input.trim().toLowerCase(); if (!/^[a-z0-9-]{1,63}$/.test(v)) throw new Error(\"Letters, digits and dashes\"); return v; } },\n validity: { defaultDays: 30, maxDays: 365 },\n recheck: { afterSeconds: 3600 },\n privacy: \"The verifier asks records.ghostly.test for the record: that server learns the name was checked.\",\n signers: [{ id: \"fake-publish\", kind: \"publish\", label: \"Publish a record\",\n instructions: statement => ({ steps: [{ text: `Publish this record for ${statement.binding.subject}:`, copy: fakeRecordText(statement) }] }),\n evidence: () => ({}) }],\n parseEvidence(raw) { if (!exactKeys(raw, \"\")) throw new Error(\"Invalid test evidence\"); return {}; },\n async verify(statement, _evidence, ctx) {\n const response = await ctx.fetch(`${FAKE_RECORD_HOST}${statement.binding.subject}`, { maxBytes: 4096, signal: ctx.signal });\n if (response.status !== 200 || !response.text.split(\"\\n\").includes(fakeRecordText(statement))) throw new Error(\"The record is not published\");\n return { subject: statement.binding.subject, source: \"Record published at records.ghostly.test\" };\n },\n};\n\nexport const FAKE_IDENTITY_PROVIDERS: readonly IdentityProofProvider[] = [fakeKey, fakeAccount, fakeRecord];\n","import type { IdentityProofProvider } from \"./contract\";\nimport { bitcoin } from \"./providers/bitcoin\";\nimport { nostr } from \"./providers/nostr\";\nimport { domain } from \"./providers/domain\";\nimport { openpgp } from \"./providers/openpgp\";\nimport { ssh, sshGithub, sshGitlab } from \"./providers/ssh\";\nimport { oidc } from \"./providers/oidc\";\nimport { did } from \"./providers/did\";\nimport { atproto } from \"./providers/atproto\";\nimport { pubky } from \"./providers/pubky\";\nimport { FAKE_IDENTITY_PROVIDERS, testIdentitiesEnabled } from \"./testing\";\nimport { registeredIdentityProviders, reserveAdapterIds } from \"../plugins/registry\";\n\n/**\n * Every identity-proof provider built into Ghostly. Adding one is adding its module under `providers/`\n * and ONE line here; see PROOFS.md. The order is the order of the picker on the Identities page. A\n * provider written outside the app registers through `plugins/registry.ts` (the SDK) and comes after.\n */\nexport const IDENTITY_PROVIDERS: readonly IdentityProofProvider[] = [\n nostr,\n pubky,\n domain,\n openpgp,\n bitcoin,\n ssh, sshGithub, sshGitlab,\n oidc,\n did,\n atproto,\n];\n\nreserveAdapterIds(\"identity\", IDENTITY_PROVIDERS.map((p) => p.id));\n\nconst warned = new Set<string>();\n/** The built-in providers, then the ones plugins registered (a built-in's id wins), then the fakes when this browser asked for them (e2e). */\nexport function identityProviders(): readonly IdentityProofProvider[] {\n const external = registeredIdentityProviders().filter((p) => {\n const taken = IDENTITY_PROVIDERS.some((b) => b.id === p.id);\n if (taken && !warned.has(p.id)) { warned.add(p.id); console.error(`[ghostly] identity provider ${p.id} from a plugin is ignored: a built-in has that id`); }\n return !taken;\n });\n return [...IDENTITY_PROVIDERS, ...external, ...(testIdentitiesEnabled() ? FAKE_IDENTITY_PROVIDERS : [])];\n}\n\nexport function identityProvider(id: string, providers = identityProviders()): IdentityProofProvider | undefined {\n return providers.find(p => p.id === id);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"registry-CJh9L_kF.js","names":["HEX64","hex","unhex","nip19Decode","exactKeys","scopeFor","callbackParams","currentHost","hostOf","exactKeys"],"sources":["../../../browser/src/proofs/bitcoinWallets.ts","../../../browser/src/proofs/providers/bitcoin.ts","../../../browser/src/proofs/nostr.ts","../../../browser/src/proofs/providers/nostr.ts","../../../browser/src/proofs/providers/domain.ts","../../../browser/src/proofs/openpgp.ts","../../../browser/src/proofs/providers/openpgp.ts","../../../browser/src/proofs/sshForges.ts","../../../browser/src/proofs/providers/ssh.ts","../../../browser/src/host.ts","../../../browser/src/proofs/oidc/providers.ts","../../../browser/src/proofs/oidc/jwks.ts","../../../browser/src/proofs/oidc/verify.ts","../../../browser/src/proofs/oidc/flow.ts","../../../browser/src/proofs/providers/oidc.ts","../../../browser/src/proofs/did.ts","../../../browser/src/proofs/providers/did.ts","../../../browser/src/proofs/atproto/oauth.ts","../../../browser/src/proofs/providers/atproto.ts","../../../browser/src/proofs/pubky.ts","../../../browser/src/proofs/providers/pubky.ts","../../../browser/src/proofs/testing.ts","../../../browser/src/proofs/registry.ts"],"sourcesContent":["import type { BitcoinScriptType } from \"@ghostly/core\";\n\n/**\n * Person-facing steps to sign Ghostly's proof statement for a Bitcoin address, per common wallet: which\n * menu, what to paste where, which format to pick. Ghostly never sees the wallet or its keys; the person\n * pastes the signature back.\n *\n * Only what each wallet's own documentation or UI shows is claimed here. Hardware wallets differ by model\n * and firmware (ADAPTER-ROADMAP.md, \"Hardware and signing\"); their entries point to the vendor's guide.\n */\n\nexport type BitcoinSigningFormat = \"bip322\" | \"legacy\";\n\nexport interface BitcoinWalletGuide {\n id: string;\n name: string;\n /** Address types this wallet can sign for in a format Ghostly accepts. */\n scripts: readonly BitcoinScriptType[];\n format: BitcoinSigningFormat;\n steps: readonly string[];\n /** How to use the test networks, for Testnet mode. */\n testnet?: string;\n note?: string;\n docs?: string;\n}\n\n/** What a proof proves and what it does not; shown next to every Bitcoin proof, given or received. */\nexport const BITCOIN_PROOF_LIMITS = [\n \"Proves: whoever made this signature could sign with the key behind this address when they signed.\",\n \"Does not prove the address holds any balance, now or ever.\",\n \"Does not prove they sent or received any past payment.\",\n \"Does not prove they would pay you, or spend from this address at all.\",\n] as const;\n\nexport const BITCOIN_WALLET_GUIDES: readonly BitcoinWalletGuide[] = [\n {\n id: \"sparrow\",\n name: \"Sparrow Wallet\",\n scripts: [\"p2wpkh\", \"p2tr\", \"p2pkh\"],\n format: \"bip322\",\n steps: [\n \"Open the wallet that holds the address, then Tools → Sign/Verify Message.\",\n \"Address: paste the address you are proving.\",\n \"Message: paste the statement Ghostly shows, exactly as it is.\",\n \"Format: BIP322 (Simple) for a bc1q… or bc1p… address (tb1… on test networks). For a legacy 1… address, Standard (Electrum).\",\n \"Sign Message, then copy the whole Signature (it starts with smp…) and paste it into Ghostly.\",\n ],\n testnet: \"Tools → Restart In → Testnet or Signet, with a wallet on that network.\",\n note: \"Sparrow offers BIP322 (Simple) for software wallets only; a connected hardware wallet signs the Standard (Electrum) format, which Ghostly accepts only for a legacy 1… address.\",\n docs: \"https://sparrowwallet.com/docs/\",\n },\n {\n id: \"bitcoin-core\",\n name: \"Bitcoin Core\",\n scripts: [\"p2pkh\"],\n format: \"legacy\",\n steps: [\n \"Bitcoin Core signs messages in the legacy format only, for a legacy address (1… on mainnet, m… or n… on test networks). Make one in the Receive tab with the Base58 (Legacy) type, or with `getnewaddress \\\"\\\" legacy`.\",\n \"GUI: File → Sign message…; paste the address, paste the statement, Sign Message, copy the signature.\",\n \"Command line: bitcoin-cli signmessage \\\"<address>\\\" '<statement>' (single quotes keep the statement's double quotes intact).\",\n ],\n testnet: \"Run it with -signet, -testnet4 or -regtest; add the same flag to bitcoin-cli.\",\n docs: \"https://developer.bitcoin.org/reference/rpc/signmessage.html\",\n },\n {\n id: \"electrum\",\n name: \"Electrum\",\n scripts: [\"p2pkh\"],\n format: \"legacy\",\n steps: [\n \"Electrum signs in the legacy format, which Ghostly accepts only for a legacy 1… address (m…/n… on test networks); its default bc1… wallets need another wallet.\",\n \"Addresses tab (View → Show Addresses), right-click the address → Sign/verify message.\",\n \"Paste the statement into Message, click Sign, copy the Signature and paste it into Ghostly.\",\n ],\n testnet: \"Start Electrum with --testnet or --signet.\",\n docs: \"https://electrum.readthedocs.io/\",\n },\n {\n id: \"coldcard\",\n name: \"COLDCARD\",\n scripts: [\"p2wpkh\", \"p2tr\", \"p2pkh\"],\n format: \"bip322\",\n steps: [\n \"Follow COLDCARD's BIP-322 guide for your firmware: it signs the statement for one of its addresses via MicroSD, NFC or QR, depending on the model.\",\n \"Give it the statement exactly as Ghostly shows it and the address you are proving.\",\n \"Paste the signature it produces into Ghostly.\",\n ],\n note: \"Older firmware signs only the legacy format (Ghostly accepts that for a legacy 1… address) and limits message length and characters.\",\n docs: \"https://coldcard.com/docs/bip322/\",\n },\n {\n id: \"trezor\",\n name: \"Trezor Suite\",\n scripts: [\"p2pkh\"],\n format: \"legacy\",\n steps: [\n \"Trezor signs messages in the legacy format; Ghostly accepts it for an address of a Legacy account (1…).\",\n \"Open the Legacy account and choose Sign & verify in its menu.\",\n \"Paste the statement into Message, pick the address, Sign, confirm on the device, copy the signature into Ghostly.\",\n ],\n },\n {\n id: \"other\",\n name: \"Another wallet\",\n scripts: [\"p2wpkh\", \"p2tr\", \"p2sh\", \"p2pkh\"],\n format: \"bip322\",\n steps: [\n \"Look for \\\"Sign message\\\" in the wallet. Choose BIP-322 when it asks for a format.\",\n \"Sign the statement exactly as Ghostly shows it, with the address you are proving.\",\n \"A nested SegWit address (3…, or 2… on test networks) needs a full BIP-322 signature (it starts with ful…).\",\n \"Paste the signature into Ghostly; it tells you right away if it does not match.\",\n ],\n },\n];\n\n/** The guides that can sign for an address of this type. */\nexport function guidesFor(script: BitcoinScriptType): BitcoinWalletGuide[] {\n return BITCOIN_WALLET_GUIDES.filter(g => g.scripts.includes(script));\n}\n","import { BitcoinAddressError, decodeAnyBitcoinAddress, verifyBitcoinMessage, type BitcoinMessageVerdict, type BitcoinProofNetwork, type BitcoinScriptType, type IdentityStatement } from \"@ghostly/core\";\nimport type { ExternalToolSigner, IdentityProofProvider, InstructionStep } from \"../contract\";\nimport { BITCOIN_PROOF_LIMITS, BITCOIN_WALLET_GUIDES, guidesFor, type BitcoinWalletGuide } from \"../bitcoinWallets\";\n\n/**\n * Bitcoin address proofs: the person signs the statement with the key behind an address, in their own\n * wallet, and pastes the signature. BIP-322 (simple or full) or, for a legacy P2PKH address only, the\n * legacy `signmessage` format; checked on this device by `verifyBitcoinMessage` (@ghostly/core, pinned to\n * BIP-322 2.0.0). No blockchain lookup: the proof says nothing about coins. See\n * docs/wisps/306-bitcoin.md.\n *\n * The address's own prefix says its network: a proof for a test-network address (tb1…, bcrt1…, m…, n…, 2…)\n * is verified as such and labelled \"test network\" wherever it is shown.\n *\n * TODO(ghostly-wallet signer): \"Sign with my Ghostly wallet\" belongs here as an `in-app` signer once an\n * on-chain source can sign messages. None can today: `OnchainProvider` (engine/paymentAdapters/providers/\n * onchain.ts) has no such method, Bitcoin Core's `signmessage` is legacy-only, and bdk-wallet-web has no\n * message signing. The BDK source keeps its phrase in the engine, so the path is an optional\n * `signMessage?(address, message)` on `OnchainProvider`, implemented in bdk.ts with a BIP-322 simple\n * witness for its own P2WPKH/P2TR address, exposed through an engine RPC that shows the statement and\n * asks first; never sign silently, and never hand the key to the UI.\n */\n\nexport interface BitcoinEvidence {\n /** As the wallet printed it: `smp…`/`ful…`, unprefixed BIP-322, or a legacy base64 signature. */\n signature: string;\n}\n\nconst MAX_SIGNATURE = 8192;\nconst SIGNATURE = /^(smp|ful|pof)?[A-Za-z0-9+/]+={0,2}$/;\n\nconst SCRIPT_NAMES: Record<BitcoinScriptType, string> = {\n p2wpkh: \"native SegWit (bc1q…)\", p2tr: \"Taproot (bc1p…)\", p2sh: \"nested SegWit (3…)\", p2pkh: \"legacy (1…)\",\n p2wsh: \"script / multisig (P2WSH)\", \"witness-unknown\": \"future SegWit version\",\n};\n\nfunction normalizeAddress(input: string): string {\n const value = input.trim();\n let info;\n try { info = decodeAnyBitcoinAddress(value); } catch (e) {\n throw Object.assign(new Error(e instanceof BitcoinAddressError ? e.message : \"Enter a Bitcoin address\"), { cause: e });\n }\n if (info.type === \"p2wsh\" || info.type === \"witness-unknown\")\n throw new Error(\"Ghostly can check proofs for single-key addresses only (bc1q…, bc1p…, 3…, 1…), not multisig or script addresses\");\n // Bech32 may be written in upper case; its canonical form is lower case. Base58 is case-sensitive.\n return info.witnessVersion === undefined ? value : value.toLowerCase();\n}\n\nconst isTestAddress = (address: string) => { try { return decodeAnyBitcoinAddress(address).network === \"testnet\"; } catch { return false; } };\n\nfunction shortAddress(address: string): string {\n return `${address.slice(0, address.startsWith(\"bcrt1\") ? 9 : 7)}…${address.slice(-4)}${isTestAddress(address) ? \" · test network\" : \"\"}`;\n}\n\n/** Wallet steps for this statement's address: the guide's own, or why this wallet cannot sign for it. */\nfunction walletInstructions(guide: BitcoinWalletGuide, statement: IdentityStatement) {\n const address = statement.binding.subject;\n const info = decodeAnyBitcoinAddress(address);\n const steps: InstructionStep[] = [];\n if (!guide.scripts.includes(info.type)) {\n const others = guidesFor(info.type).filter(g => g.id !== \"other\").map(g => g.name);\n steps.push({ text: `${guide.name} cannot sign for a ${SCRIPT_NAMES[info.type]} address in a format Ghostly accepts.${others.length ? ` Try ${others.join(\" or \")}, or another wallet that signs BIP-322.` : \" Use a wallet that signs BIP-322.\"}` });\n } else {\n steps.push({ text: \"Sign exactly this statement, as one line, without adding a line break:\", copy: statement.text });\n steps.push({ text: \"With this address:\", copy: address });\n for (const text of guide.steps) steps.push({ text });\n if (guide.id === \"bitcoin-core\") steps.push({ text: `Command line${info.network === \"testnet\" ? \" (add -signet, -testnet4 or -regtest)\" : \"\"}:`, copy: `bitcoin-cli signmessage \"${address}\" '${statement.text}'` });\n if (info.network === \"testnet\" && guide.testnet) steps.push({ text: `Test networks: ${guide.testnet}` });\n if (guide.note) steps.push({ text: guide.note });\n }\n steps.push({ text: \"This shows you can sign for the address. It does not show a balance, a past payment, or that you would pay.\" });\n return { steps, paste: { label: \"Signature\", placeholder: \"smp… (BIP-322) or the base64 signature your wallet shows\", multiline: true } };\n}\n\nfunction cleanSignature(pasted: string): string {\n const signature = pasted.replace(/\\s+/g, \"\");\n if (!signature) throw new Error(\"Paste the signature your wallet made\");\n if (signature.length > MAX_SIGNATURE || !SIGNATURE.test(signature)) throw new Error(\"That is not a Bitcoin message signature: paste the whole signature, as the wallet shows it\");\n return signature;\n}\n\nfunction check(statement: IdentityStatement, signature: string): { verdict: Extract<BitcoinMessageVerdict, { state: \"valid\" }>; network: BitcoinProofNetwork } {\n const address = statement.binding.subject;\n const network = decodeAnyBitcoinAddress(address).network;\n const verdict = verifyBitcoinMessage({ address, message: statement.text, signature, network });\n if (verdict.state !== \"valid\") throw new Error(verdict.reason);\n return { verdict, network };\n}\n\nfunction walletSigner(guide: BitcoinWalletGuide): ExternalToolSigner<BitcoinEvidence> {\n return {\n id: guide.id, kind: \"external-tool\", label: guide.name,\n description: guide.format === \"legacy\" ? \"Legacy message signature: for a legacy 1… address only.\" : \"BIP-322 message signature.\",\n instructions: statement => walletInstructions(guide, statement),\n parse(pasted, statement) {\n const signature = cleanSignature(pasted);\n // Say right away when it does not match, while the wallet is still open.\n check(statement, signature);\n return { signature };\n },\n };\n}\n\nexport const bitcoin: IdentityProofProvider<BitcoinEvidence> = {\n id: \"bitcoin\",\n label: \"Bitcoin address\",\n category: \"self-custodied\",\n summary: \"Sign a message in your own wallet\",\n description: \"Proves you can sign with the key behind a Bitcoin address: your wallet signs the statement and you paste the signature.\",\n limits: \"It does not prove a balance, a past payment, or that you would pay.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Bitcoin address\",\n placeholder: \"bc1q… (tb1… on test networks)\",\n help: BITCOIN_PROOF_LIMITS.join(\" \"),\n normalize: normalizeAddress,\n short: shortAddress,\n },\n // BIP-21\n publicUri: address => `bitcoin:${address}`,\n validity: { defaultDays: 90, maxDays: 365 },\n privacy: \"Nothing: the signature is checked on this device, with no blockchain lookup. Contacts you share it with learn the address.\",\n signers: BITCOIN_WALLET_GUIDES.map(walletSigner),\n parseEvidence(raw) {\n const e = raw as BitcoinEvidence;\n if (!e || typeof e !== \"object\" || Array.isArray(e) || Object.keys(e).join(\",\") !== \"signature\" || typeof e.signature !== \"string\" ||\n e.signature.length > MAX_SIGNATURE || !SIGNATURE.test(e.signature)) throw new Error(\"That is not a Bitcoin message signature\");\n return { signature: e.signature };\n },\n async verify(statement, evidence) {\n const { verdict, network } = check(statement, evidence.signature);\n const format = verdict.format === \"legacy\" ? \"Legacy signmessage\" : verdict.format === \"bip322-full\" ? \"BIP-322 full\" : \"BIP-322 simple\";\n const script = { p2pkh: \"P2PKH\", p2wpkh: \"P2WPKH\", \"p2sh-p2wpkh\": \"P2SH-P2WPKH\", p2tr: \"P2TR\" }[verdict.script];\n return { subject: statement.binding.subject, source: `${format} signature, ${script}${network === \"testnet\" ? \", test network\" : \"\"}` };\n },\n};\n","import type { BunkerPointer, BunkerSigner } from 'nostr-tools/nip46';\nimport type { SimplePool } from 'nostr-tools/pool';\nimport { generateSecretKey } from 'nostr-tools/pure';\nimport type { NostrProofEvent } from '@ghostly/core';\n\nexport interface NostrSigner {\n getPublicKey(): Promise<string>;\n signEvent(event: { kind: number; created_at: number; tags: string[][]; content: string }): Promise<NostrProofEvent>;\n}\nexport function extensionSigner(): NostrSigner | undefined {\n return (globalThis as typeof globalThis & { nostr?: NostrSigner }).nostr;\n}\n/** No NIP-05 lookup, arbitrary fetch, private key import or automatic relay switching. */\nexport function parseProofBunker(input: string): BunkerPointer {\n if (input.length > 4096) throw new Error('Bunker URL too long');\n let uri: URL;\n try { uri = new URL(input); } catch { throw new Error('Enter a bunker:// connection URL from your signer'); }\n const relays = uri.searchParams.getAll('relay');\n if (uri.protocol !== 'bunker:' || !/^[a-f0-9]{64}$/.test(uri.hostname) || uri.username || uri.password || uri.port || uri.hash || (uri.pathname && uri.pathname !== '/') ||\n relays.length < 1 || relays.length > 3) throw new Error('Invalid bunker connection URL');\n for (const address of relays) {\n const relay = new URL(address);\n if (relay.username || relay.password || relay.hash || !(relay.protocol === 'wss:' ||\n (relay.protocol === 'ws:' && ['127.0.0.1', 'localhost', '[::1]'].includes(relay.hostname))))\n throw new Error('Use secure wss:// relays (ws:// is allowed only on loopback for local testing)');\n }\n return { pubkey: uri.hostname, relays, secret: uri.searchParams.get('secret') };\n}\n/**\n * Runs `work` with the person's Nostr signer: the page's NIP-07 extension, or a NIP-46 bunker. `permissions`\n * is what the bunker is asked for (NIP-46 `connect`): the identity proof's `sign_event:30078` by default;\n * the social layer asks for the kinds it publishes.\n */\nexport async function withNostrSigner<T>(options: { bunker?: string; signal: AbortSignal; onAuth(url: string): void; permissions?: string; appName?: string }, work: (signer: NostrSigner) => Promise<T>): Promise<T> {\n options.signal.throwIfAborted();\n let signer: BunkerSigner | undefined;\n let pool: SimplePool | undefined;\n let clientKey: Uint8Array | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let abort: (() => void) | undefined;\n let closed = false;\n const guard: NostrSigner = {\n getPublicKey: async () => { if (closed) throw new Error('Signer request cancelled'); const key = await active!.getPublicKey(); if (closed) throw new Error('Signer request cancelled'); return key; },\n signEvent: async event => { if (closed) throw new Error('Signer request cancelled'); const signed = await active!.signEvent(event); if (closed) throw new Error('Signer request cancelled'); return signed; },\n };\n let active: NostrSigner | undefined;\n try {\n const timeout = new Promise<never>((_, reject) => {\n abort = () => { closed = true; reject(new Error('Signing cancelled')); };\n options.signal.addEventListener('abort', abort, { once: true });\n timer = setTimeout(() => { closed = true; reject(new Error('Signer timed out. Your Ghostly identity is unchanged.')); }, 120_000);\n });\n const action = async () => {\n if (options.bunker) {\n const bp = parseProofBunker(options.bunker);\n // Loaded here, not with the app: the NIP-46 signer and its relay pool are only for a bunker.\n const [{ BunkerSigner }, { SimplePool }] = await Promise.all([import('nostr-tools/nip46'), import('nostr-tools/pool')]);\n if (closed) throw new Error('Signer request cancelled');\n pool = new SimplePool(); clientKey = generateSecretKey();\n signer = BunkerSigner.fromBunker(clientKey, bp, { pool, skipSwitchRelays: true, onauth: url => {\n try { const u = new URL(url); if (!closed && u.protocol === 'https:' && !u.username && !u.password) options.onAuth(u.href); } catch { /* Invalid signer URL: never navigate. */ }\n } });\n await signer.sendRequest('connect', [bp.pubkey, bp.secret ?? '', options.permissions ?? 'sign_event:30078', JSON.stringify({ name: options.appName ?? 'Ghostly — optional conversation proof' })]);\n active = signer;\n } else {\n active = extensionSigner();\n if (!active) throw new Error('No NIP-07 signer in this window. Use a bunker connection from your Nostr signer.');\n }\n if (closed) throw new Error('Signer request cancelled');\n return work(guard);\n };\n return await Promise.race([action(), timeout]);\n } finally {\n closed = true;\n if (timer) clearTimeout(timer);\n if (abort) options.signal.removeEventListener('abort', abort);\n await signer?.close(); pool?.destroy(); clientKey?.fill(0);\n }\n}\n","import { schnorr } from \"@noble/curves/secp256k1.js\";\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport { npubEncode, decode as nip19Decode } from \"nostr-tools/nip19\";\nimport { utf8Encode, type IdentityStatement } from \"@ghostly/core\";\nimport type { IdentityProofProvider, InAppSigner, SignerSession } from \"../contract\";\nimport { extensionSigner, withNostrSigner } from \"../nostr\";\n\n/** A signed Nostr event (NIP-01). The evidence of a Nostr proof. */\nexport interface NostrEvent { id: string; pubkey: string; created_at: number; kind: number; tags: string[][]; content: string; sig: string }\n\n/**\n * NIP-78 application data (kind 30078): no new kind is claimed. Signers show it as such; it is never\n * published to a relay — the event travels only to the contacts the person shares it with.\n */\nexport const NOSTR_PROOF_KIND = 30078;\nconst HEX64 = /^[a-f0-9]{64}$/;\n\n/** The exact event a signer is asked to sign: the statement as content, dated at issue, expiring with it. */\nexport function nostrProofTemplate(statement: IdentityStatement) {\n const { issuedAt, expiresAt } = statement.binding;\n return { kind: NOSTR_PROOF_KIND, created_at: issuedAt, content: statement.text,\n tags: [[\"d\", \"ghostly-identity-proof\"], [\"expiration\", String(expiresAt)]] };\n}\n\nconst hex = (b: Uint8Array) => Array.from(b, x => x.toString(16).padStart(2, \"0\")).join(\"\");\nconst unhex = (s: string) => Uint8Array.from(s.match(/../g)!, x => parseInt(x, 16));\n\nfunction normalizeNostrKey(input: string): string {\n const value = input.trim();\n if (HEX64.test(value.toLowerCase())) return value.toLowerCase();\n if (value.startsWith(\"npub1\")) {\n try { const decoded = nip19Decode(value); if (decoded.type === \"npub\") return decoded.data; } catch { /* fall through */ }\n }\n throw new Error(\"Enter a Nostr public key: npub1… or 64 hex characters\");\n}\n\nfunction nostrSigner(id: \"nip07\" | \"nip46\"): InAppSigner<NostrEvent> {\n return {\n id, kind: \"in-app\",\n label: id === \"nip07\" ? \"Browser extension (NIP-07)\" : \"Remote signer (NIP-46)\",\n description: id === \"nip07\" ? \"Approve in the Nostr extension of this browser.\" : \"Paste the bunker:// link from your signer app, then approve there.\",\n fields: id === \"nip46\" ? [{ name: \"bunker\", label: \"Signer connection link\", kind: \"secret\", placeholder: \"bunker://…\" }] : undefined,\n platforms: id === \"nip07\" ? [\"web\", \"desktop\"] : undefined,\n available: id === \"nip07\" ? () => !!extensionSigner() : undefined,\n run: (ctx, work) => withNostrSigner({ bunker: id === \"nip46\" ? ctx.values.bunker : undefined, signal: ctx.signal, onAuth: ctx.onAuthUrl }, signer => {\n const session: SignerSession<NostrEvent> = {\n subject: async () => normalizeNostrKey(await signer.getPublicKey()),\n sign: async statement => {\n ctx.onProgress(\"Approve the Ghostly identity statement in your signer.\");\n return (await signer.signEvent(nostrProofTemplate(statement))) as NostrEvent;\n },\n };\n return work(session);\n }),\n };\n}\n\nexport const nostr: IdentityProofProvider<NostrEvent> = {\n id: \"nostr\",\n label: \"Nostr\",\n category: \"self-custodied\",\n summary: \"Sign once with your Nostr signer\",\n description: \"Proves you hold a Nostr key: your signer signs the statement, your key never leaves it.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Public key\", placeholder: \"npub1…\",\n normalize: normalizeNostrKey,\n short: key => { const npub = npubEncode(key); return `${npub.slice(0, 10)}…${npub.slice(-4)}`; },\n },\n // NIP-21\n publicUri: key => `nostr:${npubEncode(key)}`,\n validity: { defaultDays: 90, maxDays: 365 },\n privacy: \"Nothing: the signature is checked on this device. The proof is never published to a relay.\",\n signers: [nostrSigner(\"nip07\"), nostrSigner(\"nip46\")],\n parseEvidence(raw) {\n const e = raw as NostrEvent;\n if (!e || typeof e !== \"object\" || Array.isArray(e) || Object.keys(e).sort().join(\",\") !== \"content,created_at,id,kind,pubkey,sig,tags\" ||\n typeof e.id !== \"string\" || !HEX64.test(e.id) || typeof e.pubkey !== \"string\" || !HEX64.test(e.pubkey) ||\n typeof e.sig !== \"string\" || !/^[a-f0-9]{128}$/.test(e.sig) || !Number.isSafeInteger(e.kind) || !Number.isSafeInteger(e.created_at) ||\n typeof e.content !== \"string\" || e.content.length > 2048 || !Array.isArray(e.tags) || e.tags.length > 4 ||\n !e.tags.every(t => Array.isArray(t) && t.length <= 4 && t.every(x => typeof x === \"string\" && x.length <= 128)))\n throw new Error(\"That is not a signed Nostr event\");\n return { id: e.id, pubkey: e.pubkey, created_at: e.created_at, kind: e.kind, tags: e.tags.map(t => [...t]), content: e.content, sig: e.sig };\n },\n async verify(statement, event) {\n const expected = nostrProofTemplate(statement);\n if (event.pubkey !== statement.binding.subject) throw new Error(\"Signed by another Nostr key\");\n if (event.kind !== expected.kind || event.created_at !== expected.created_at || event.content !== expected.content ||\n JSON.stringify(event.tags) !== JSON.stringify(expected.tags)) throw new Error(\"The signer did not sign the exact Ghostly statement\");\n // NIP-01 id, recomputed here: never trust the id or a verification flag the event carries.\n const id = hex(sha256(utf8Encode(JSON.stringify([0, event.pubkey, event.created_at, event.kind, event.tags, event.content]))));\n if (event.id !== id) throw new Error(\"The Nostr event id does not match its content\");\n let valid: boolean;\n try { valid = schnorr.verify(unhex(event.sig), unhex(id), unhex(event.pubkey)); } catch { valid = false; }\n if (!valid) throw new Error(\"Invalid Nostr signature\");\n return { subject: event.pubkey, source: \"Nostr signature (NIP-01, BIP-340)\" };\n },\n // The public profile, follows and notes of a verified key are the Nostr social layer's\n // (engine/nostrSocial.ts): loaded on request from the relays the person configured, not here.\n};\n","import { dnsProofName, domainTxtRecord, domainWellKnownFile, normalizeDomain, wellKnownUrl, type DomainRecord, type IdentityStatement } from \"@ghostly/core\";\nimport type { IdentityProofProvider, InAppSigner, PublishSigner, SignerSession } from \"../contract\";\nimport { assertDomainRecord, assertNip05, chosenResolver, resolverById } from \"../domain\";\nimport { extensionSigner, withNostrSigner } from \"../nostr\";\nimport { nostr, nostrProofTemplate, type NostrEvent } from \"./nostr\";\n\n/**\n * A domain the person controls (draft WISP 304, docs/wisps/304-domain.md).\n *\n * The statement (\"I control domain:example.com and authorize the Ghostly key K …\") is vouched for by\n * publishing, once, under the domain: a TXT record at `_ghostly.<domain>` or the file\n * `/.well-known/ghostly.json`, naming the proof key K and the statement id. The id commits the record to\n * the whole statement (dates and nonce too), so one published record vouches for exactly one proof. The\n * record names no contact and no conversation; sharing per contact is the contract's presentation.\n *\n * People who already run NIP-05 can instead sign the statement with the Nostr key their\n * `/.well-known/nostr.json` names for the domain itself (`_`): the proof holds only if both agree.\n */\nexport type DomainEvidence = { method: \"dns\" } | { method: \"https\" } | { method: \"nip05\"; event: NostrEvent };\n\nconst recordFor = (s: IdentityStatement): DomainRecord => ({ key: s.binding.key, proof: s.id });\nconst exactKeys = (raw: unknown, keys: string) => !!raw && typeof raw === \"object\" && !Array.isArray(raw) && Object.keys(raw).sort().join(\",\") === keys;\n\nconst dnsSigner: PublishSigner<DomainEvidence> = {\n id: \"dns\", kind: \"publish\", label: \"DNS TXT record\",\n description: \"Add a TXT record at your DNS host. Contacts' apps look it up through a DNS-over-HTTPS resolver.\",\n instructions(statement) {\n const domain = statement.binding.subject;\n return { steps: [\n { text: `Open the DNS settings of ${domain} at your registrar or DNS host and add a TXT record with this name. Some hosts add the domain themselves: then enter only the part before ${domain}.`, copy: dnsProofName(domain) },\n { text: \"Value (one string, exactly as shown):\", copy: domainTxtRecord(recordFor(statement)) },\n { text: \"Set the TTL to 300 seconds or less, so removing the record takes effect quickly. Then press Check: DNS changes can take a few minutes to appear.\" },\n { text: \"The record stays public while the proof is in use. Removing it withdraws this proof from every contact at their next check.\" },\n ] };\n },\n evidence: () => ({ method: \"dns\" }),\n};\n\nconst httpsSigner: PublishSigner<DomainEvidence> = {\n id: \"https\", kind: \"publish\", label: \"File on your website\",\n description: \"Upload a small file to your website. Contacts' apps download it from your server.\",\n instructions(statement) {\n const domain = statement.binding.subject;\n return { steps: [\n { text: `Create this file on the web server of ${domain}:`, copy: wellKnownUrl(domain) },\n { text: \"With exactly this content:\", copy: domainWellKnownFile([recordFor(statement)]) },\n { text: \"Serve it over HTTPS at exactly that address, without any redirect (not even to www), and with this header so browsers may read it:\", copy: \"Access-Control-Allow-Origin: *\" },\n { text: \"Then press Check. The file stays public while the proof is in use; deleting it withdraws this proof from every contact at their next check.\" },\n ] };\n },\n evidence: () => ({ method: \"https\" }),\n};\n\nconst domainField = { name: \"domain\", label: \"Domain\", kind: \"text\", placeholder: \"example.com\",\n help: \"Its /.well-known/nostr.json must name this Nostr key for \\\"_\\\" (the domain itself).\" } as const;\n\n/** NIP-05: the statement is signed by the Nostr key the domain's nostr.json names for itself. */\nfunction nip05Signer(id: \"nip05-nip07\" | \"nip05-nip46\"): InAppSigner<DomainEvidence> {\n const extension = id === \"nip05-nip07\";\n return {\n id, kind: \"in-app\",\n label: extension ? \"NIP-05 with a browser extension\" : \"NIP-05 with a remote signer\",\n description: \"Already run NIP-05? Sign with the Nostr key your domain's nostr.json names for itself. No new record needed.\",\n fields: extension ? [domainField] : [domainField, { name: \"bunker\", label: \"Signer connection link\", kind: \"secret\", placeholder: \"bunker://…\" }],\n platforms: extension ? [\"web\", \"desktop\"] : undefined,\n available: extension ? () => !!extensionSigner() : undefined,\n run: (ctx, work) => {\n const domain = normalizeDomain(ctx.values.domain ?? \"\");\n return withNostrSigner({ bunker: extension ? undefined : ctx.values.bunker, signal: ctx.signal, onAuth: ctx.onAuthUrl }, signer => {\n const session: SignerSession<DomainEvidence> = {\n subject: async () => domain,\n sign: async statement => {\n ctx.onProgress(\"Approve the Ghostly identity statement in your signer.\");\n return { method: \"nip05\", event: (await signer.signEvent(nostrProofTemplate(statement))) as NostrEvent };\n },\n };\n return work(session);\n });\n },\n };\n}\n\nexport const domain: IdentityProofProvider<DomainEvidence> = {\n id: \"domain\",\n label: \"Domain\",\n category: \"self-custodied\",\n summary: \"A DNS record or a file on your site\",\n description: \"Proves you control a domain name: publish a DNS TXT record or a file on its website, once. Contacts' apps look it up when you share it and can check again later.\",\n limits: \"Does not prove who runs the domain, only that whoever added the proof could publish under it.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Domain\", placeholder: \"example.com\",\n help: \"A domain or subdomain you control. A proof for alice.example.com is published under alice.example.com.\",\n normalize: normalizeDomain,\n },\n publicUri: domain => `https://${domain}`,\n validity: { defaultDays: 90, maxDays: 365 },\n recheck: { afterSeconds: 86_400 },\n privacy: \"DNS: the contact's app asks the DNS-over-HTTPS resolver chosen in its settings (Quad9 by default; the next of Quad9, Cloudflare and Google when it cannot be reached) for the record, \" +\n \"so that resolver learns the domain was checked. Website or NIP-05: the resolver learns the domain, and the domain's web server sees the contact's IP address.\",\n signers: [dnsSigner, httpsSigner, nip05Signer(\"nip05-nip07\"), nip05Signer(\"nip05-nip46\")],\n parseEvidence(raw) {\n const e = raw as DomainEvidence;\n if (exactKeys(raw, \"method\") && (e.method === \"dns\" || e.method === \"https\")) return { method: e.method };\n if (exactKeys(raw, \"event,method\") && e.method === \"nip05\") return { method: \"nip05\", event: nostr.parseEvidence((e as { event: unknown }).event) };\n throw new Error(\"That is not domain proof evidence\");\n },\n async verify(statement, evidence, ctx) {\n const subject = statement.binding.subject;\n if (normalizeDomain(subject) !== subject) throw new Error(\"The domain is not in canonical form\");\n const resolver = chosenResolver();\n const options = { fetch: ctx.fetch, signal: ctx.signal, resolver: resolver.id, now: () => ctx.now * 1000 };\n if (evidence.method === \"nip05\") {\n // The same checks as a Nostr proof, for the key that signed; then the domain must name that key.\n await nostr.verify({ ...statement, binding: { ...statement.binding, subject: evidence.event.pubkey } }, evidence.event, ctx);\n await assertNip05(subject, evidence.event.pubkey, options);\n return { subject, source: `Nostr signature by the key ${wellKnownUrl(subject, \"nip05\")} names for ${subject}` };\n }\n const found = await assertDomainRecord(subject, evidence.method, recordFor(statement), options);\n return { subject, source: evidence.method === \"dns\"\n ? `DNS TXT record at ${dnsProofName(subject)}, via ${resolverById(found.resolver).name}${found.dnssec ? \", DNSSEC validated\" : \"\"}`\n : `File at ${wellKnownUrl(subject)}` };\n },\n};\n","import { fromBase64Url, toBase64Url, utf8Encode } from '@ghostly/core';\nimport type * as OpenPGP from 'openpgp';\n\n/**\n * OpenPGP identity proofs (RFC 9580, and the RFC 4880 v4 keys people actually have).\n *\n * The person signs a Ghostly statement once — the binding by which their\n * OpenPGP key authorizes a Ghostly proof key (see PROOFS.md); each chat\n * then gets a presentation the app signs itself. They sign with their own tooling —\n * `gpg --clearsign`, `gpg --detach-sign --armor`, a YubiKey or another OpenPGP\n * card behind gpg — and pastes the result with their public key. No secret key\n * ever enters Ghostly: a pasted private key is refused before it is parsed.\n *\n * What is shared with a contact is small and self-contained: one signature\n * packet and a minimal copy of the public key (the primary key, its user IDs\n * with their self-signatures, the subkey that signed). The contact verifies it\n * locally with the statement their own app builds; nothing is fetched.\n *\n * A key's user IDs are claims its holder wrote. Possession of the key proves\n * control of the key, not that a name or an email in a user ID belongs to the\n * person — the UI must say so wherever user IDs are shown.\n *\n * OpenPGP.js is loaded lazily (the \"lightweight\" build: ~233 KB minified,\n * ~64 KB gzipped, plus a ~46 KB curve chunk only for ECDSA keys), so nobody\n * pays for it until an OpenPGP proof is opened.\n */\n\nexport const PGP_SCHEME = 'openpgp/1';\n\n/** Strict size limits. Pasted input is the person's own; shared input comes from a peer. */\nexport const PGP_LIMITS = {\n /** A clearsigned statement or an armored detached signature. RSA-4096 with the statement is ~1.5 KB. */\n pastedSignature: 16 * 1024,\n /** An armored public key as exported, before it is minimized. Flooded keys are refused, not trimmed. */\n pastedKey: 256 * 1024,\n /** One signature packet, binary. RSA-4096 with the usual subpackets is ~600 bytes. */\n sharedSignature: 1536,\n /** The minimal key, binary. Both fit, base64url, in the contract's 16 KiB of evidence JSON. */\n sharedKey: 9 * 1024,\n /** User IDs kept in the minimal key and shown. */\n userIds: 16,\n /** Bytes per user ID. */\n userIdLength: 256,\n /** A keyserver response. */\n keyserverResponse: 256 * 1024,\n} as const;\n\n/** Clock difference tolerated between the signer's machine and the verifier. */\nexport const PGP_CLOCK_SKEW = 300;\n\nexport interface PgpEvidence {\n scheme: typeof PGP_SCHEME;\n /** base64url of the minimal transferable public key (binary packets). */\n key: string;\n /** base64url of exactly one signature packet over the statement. */\n signature: string;\n}\n\nexport interface PgpKeyInfo {\n /** Primary key fingerprint, uppercase hex (40 characters for v4, 64 for v6). */\n fingerprint: string;\n /** The key that signs: the primary key or a signing subkey. */\n signingFingerprint: string;\n version: 4 | 6;\n /** \"Ed25519\", \"RSA 3072\", \"ECDSA P-256\", … for the signing key. */\n algorithm: string;\n createdAt: number;\n /** Seconds, or null when the key does not expire. */\n expiresAt: number | null;\n /** Self-certified, unrevoked user IDs: claims by the key holder, not verified facts. */\n userIds: string[];\n}\n\nexport interface PgpVerified extends PgpKeyInfo {\n /** Signature creation time, seconds. */\n signedAt: number;\n}\n\nexport interface PgpVerifyOptions {\n /** Verification time, seconds. The key and its signing subkey must be valid now. */\n now?: number;\n /** Refuse signatures made before this time (seconds), e.g. the start of the binding's validity. */\n signedAfter?: number;\n /** The primary key fingerprint the statement names: any other key is refused, even with a valid signature. */\n expectedFingerprint?: string;\n}\n\ntype Pgp = typeof OpenPGP;\nlet loading: Promise<Pgp> | undefined;\n/** The only place OpenPGP.js is imported: a separate chunk, fetched on first use. */\nexport function loadOpenPgp(): Promise<Pgp> {\n loading ??= import('openpgp/lightweight').then(m => m as unknown as Pgp).catch(e => { loading = undefined; throw e; });\n return loading;\n}\n\nfunction config(pgp: Pgp): OpenPGP.PartialConfig {\n const { hash, publicKey, curve } = pgp.enums;\n return {\n minRSABits: 2048,\n maxUserIDLength: PGP_LIMITS.userIdLength,\n enableParsingV5Entities: false,\n ignoreUnsupportedPackets: true,\n ignoreMalformedPackets: false,\n // Self-signatures from older keys may still use SHA-1; the proof signature may not (below).\n rejectHashAlgorithms: new Set([hash.md5, hash.ripemd]),\n rejectMessageHashAlgorithms: new Set([hash.md5, hash.ripemd, hash.sha1]),\n rejectPublicKeyAlgorithms: new Set([publicKey.elgamal, publicKey.dsa]),\n rejectCurves: new Set([curve.secp256k1]),\n };\n}\n\n/** The allowlist, checked on the primary key and on the key that signs. */\nfunction allowedAlgorithm(packet: OpenPGP.PublicKeyPacket | OpenPGP.SecretKeyPacket | OpenPGP.PublicSubkeyPacket | OpenPGP.SecretSubkeyPacket, signing: boolean): string {\n const { algorithm, bits, curve } = packet.getAlgorithmInfo();\n const names: Record<string, string> = { nistP256: 'P-256', nistP384: 'P-384', nistP521: 'P-521',\n brainpoolP256r1: 'brainpoolP256r1', brainpoolP384r1: 'brainpoolP384r1', brainpoolP512r1: 'brainpoolP512r1' };\n if ((algorithm === 'rsaEncryptSign' || (algorithm === 'rsaSign' && signing)) && bits && bits >= 2048) return `RSA ${bits}`;\n if (algorithm === 'ed25519' || (algorithm === 'eddsaLegacy' && curve === 'ed25519Legacy')) return 'Ed25519';\n if (algorithm === 'ed448') return 'Ed448';\n if (algorithm === 'ecdsa' && curve && names[curve]) return `ECDSA ${names[curve]}`;\n const shown = algorithm.startsWith('rsa') ? `RSA ${bits ?? '?'}` : curve ? `${algorithm} ${curve}` : algorithm;\n throw new PgpProofError(`This key uses ${shown}, which Ghostly does not accept. Use Ed25519, ECDSA (P-256, P-384, P-521, brainpool) or RSA with at least 2048 bits.`);\n}\nconst ALLOWED_HASHES = new Set([8, 9, 10, 11, 12, 14]); // SHA-256, -384, -512, -224, SHA3-256, SHA3-512\n\nexport class PgpProofError extends Error { override name = 'PgpProofError'; }\n\n/** Armored blocks in pasted text; more BEGIN lines than any real paste has are refused before the regex scans. */\nfunction armorBlocks(text: string): RegExpExecArray[] {\n if ((text.match(/-----BEGIN PGP /g) ?? []).length > 4) throw new PgpProofError('Paste only what gpg printed: one signature and your public key.');\n return [...text.matchAll(ARMOR)];\n}\n// A clearsigned message has no END line of its own: it ends with its signature block.\nconst ARMOR = /-----BEGIN PGP SIGNED MESSAGE-----[\\s\\S]*?-----END PGP SIGNATURE-----|-----BEGIN PGP ([A-Z ,/0-9]+)-----[\\s\\S]*?-----END PGP \\1-----/g;\n/** The single armored block in pasted text (terminal output may surround it). */\nfunction armoredBlock(text: string, what: 'key' | 'signature'): { type: string; block: string } {\n if (/PRIVATE KEY BLOCK/.test(text))\n throw new PgpProofError('This is a PRIVATE key. Never paste it anywhere: Ghostly only needs your public key (gpg --armor --export).');\n const blocks = armorBlocks(text);\n if (blocks.length === 0) throw new PgpProofError(what === 'key' ? 'Paste an armored public key: it starts with -----BEGIN PGP PUBLIC KEY BLOCK-----.' : 'Paste the whole signed text: it starts with -----BEGIN PGP SIGNED MESSAGE----- (or -----BEGIN PGP SIGNATURE----- for a detached signature).');\n if (blocks.length > 1) throw new PgpProofError(`Paste only one ${what === 'key' ? 'public key' : 'signature'}.`);\n return { type: blocks[0][1] ?? 'SIGNED MESSAGE', block: blocks[0][0] };\n}\n\nfunction secondsOf(date: Date | number | null | typeof Infinity): number | null {\n if (date === null || date === Infinity) return null;\n return Math.floor((date instanceof Date ? date.getTime() : date) / 1000);\n}\nconst hexOf = (bytes: Uint8Array) => Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('').toUpperCase();\nconst nowSeconds = () => Math.floor(Date.now() / 1000);\n/** Clearsigned text as it is hashed: trailing whitespace dropped, no final newline. */\nexport function canonicalStatement(statement: string): string {\n return statement.replace(/\\r\\n?/g, '\\n').split('\\n').map(l => l.replace(/[ \\t]+$/, '')).join('\\n').replace(/\\n+$/, '');\n}\n\n/**\n * One paste holding what gpg printed: the signed statement (clearsigned, or a detached signature) and,\n * optionally, the public key. Anything around the blocks (prompts, blank lines) is ignored.\n */\nexport function splitPgpPaste(pasted: string): { signature: string; publicKey?: string } {\n if (typeof pasted !== 'string' || pasted.length > PGP_LIMITS.pastedKey + PGP_LIMITS.pastedSignature) throw new PgpProofError('This is too large. Paste only what gpg printed.');\n if (/PRIVATE KEY BLOCK/.test(pasted)) throw new PgpProofError('This is a PRIVATE key. Never paste it anywhere: Ghostly only needs your public key (gpg --armor --export).');\n const blocks = armorBlocks(pasted).map(m => ({ type: m[1] ?? 'SIGNED MESSAGE', block: m[0] }));\n const keys = blocks.filter(b => b.type === 'PUBLIC KEY BLOCK'), others = blocks.filter(b => b.type !== 'PUBLIC KEY BLOCK');\n if (keys.length > 1) throw new PgpProofError('Paste only one public key.');\n if (others.length === 0) throw new PgpProofError('Paste the signed statement too: it starts with -----BEGIN PGP SIGNED MESSAGE-----.');\n if (others.length > 1) throw new PgpProofError('Paste only one signature.');\n return { signature: others[0].block, publicKey: keys[0]?.block };\n}\n\n/** Refuses a statement that `gpg --clearsign` would alter (dash-escaping, trailing spaces) or that is not plain text. */\nexport function assertSignableStatement(statement: string): void {\n if (!statement || statement.length > 4096 || statement !== canonicalStatement(statement) || /^-/m.test(statement) || /[^\\x20-\\x7e\\n]/.test(statement))\n throw new PgpProofError('Statement is not signable as plain text');\n}\n\n/** Why a key or a subkey is not usable now, in words the person can act on. */\nasync function explainInvalidKey(key: OpenPGP.PublicKey, keyId: OpenPGP.KeyID | undefined, at: Date, cfg: OpenPGP.PartialConfig, when: 'now' | 'signing'): Promise<never> {\n const past = when === 'signing' ? ' when it signed' : '';\n if (await key.isRevoked(undefined, undefined, at, cfg as OpenPGP.Config).catch(() => false))\n throw new PgpProofError(`This key has been revoked${past}. A revoked key proves nothing.`);\n try { await key.verifyPrimaryKey(at, undefined, cfg as OpenPGP.Config); }\n catch (e) {\n const expiry = await key.getExpirationTime(undefined, cfg as OpenPGP.Config).catch(() => null);\n if (expiry instanceof Date && expiry <= at) throw new PgpProofError(`This key expired on ${expiry.toISOString().slice(0, 10)}${past}. Extend its expiry (gpg --quick-set-expire) and export it again.`);\n throw new PgpProofError(`This key is not valid${past}: ${message(e)}`);\n }\n const sub = keyId ? key.getSubkeys(keyId)[0] : undefined;\n if (sub) {\n if (sub.revocationSignatures.length && await sub.verify(at, cfg as OpenPGP.Config).then(() => false, e => /revoked/i.test(message(e))))\n throw new PgpProofError(`The subkey that signed (${sub.getFingerprint().toUpperCase()}) has been revoked${past}.`);\n const expiry = await sub.getExpirationTime(at, cfg as OpenPGP.Config).catch(() => null);\n if (expiry instanceof Date && expiry <= at) throw new PgpProofError(`The subkey that signed expired on ${expiry.toISOString().slice(0, 10)}${past}.`);\n throw new PgpProofError(`The subkey that signed is not a valid signing subkey${past}.`);\n }\n throw new PgpProofError(`The key that signed is not a valid signing key${past}.`);\n}\nconst message = (e: unknown) => e instanceof Error ? e.message : String(e);\n\n/** Parses exactly one public key; refuses private keys, v3/v5 keys and key rings. */\nasync function readOneKey(pgp: Pgp, input: { armored: string } | { binary: Uint8Array }): Promise<OpenPGP.PublicKey> {\n const cfg = config(pgp);\n let keys: OpenPGP.Key[];\n try {\n keys = 'armored' in input ? await pgp.readKeys({ armoredKeys: input.armored, config: cfg }) : await pgp.readKeys({ binaryKeys: input.binary, config: cfg });\n } catch (e) { throw new PgpProofError(`Could not read this public key: ${message(e)}`); }\n if (keys.length !== 1) throw new PgpProofError('Paste one public key, not a key ring.');\n const [key] = keys;\n if (key.isPrivate()) throw new PgpProofError('This is a PRIVATE key. Ghostly only needs your public key (gpg --armor --export).');\n const version = key.keyPacket.version;\n if (version !== 4 && version !== 6) throw new PgpProofError(`OpenPGP v${version} keys are not accepted; use a v4 (RFC 4880) or v6 (RFC 9580) key.`);\n return key as OpenPGP.PublicKey;\n}\n\n/** Valid, unrevoked, self-certified user IDs at `at`. */\nasync function validUserIds(key: OpenPGP.PublicKey, at: Date, cfg: OpenPGP.PartialConfig): Promise<OpenPGP.User[]> {\n const valid: OpenPGP.User[] = [];\n for (const user of key.users) {\n if (!user.userID || valid.length >= PGP_LIMITS.userIds) continue;\n if (await user.verify(at, cfg as OpenPGP.Config).then(() => true, () => false)) valid.push(user);\n }\n return valid;\n}\n\n/**\n * The transferable key a contact receives: primary key and its direct/revocation\n * signatures, valid user IDs with self-certifications only (third-party\n * certifications and photos dropped), and the one subkey that signed.\n */\nasync function minimalKey(pgp: Pgp, key: OpenPGP.PublicKey, signer: OpenPGP.PublicKey | OpenPGP.Subkey, at: Date, cfg: OpenPGP.PartialConfig): Promise<Uint8Array> {\n const packets = new pgp.PacketList<OpenPGP.AnyPacket>();\n packets.push(key.keyPacket, ...key.revocationSignatures);\n // Direct-key self-signatures carry v6 key properties; OpenPGP.js keeps them here.\n const direct = (key as unknown as { directSignatures?: OpenPGP.SignaturePacket[] }).directSignatures ?? [];\n packets.push(...direct);\n for (const user of await validUserIds(key, at, cfg)) packets.push(user.userID!, ...user.selfCertifications, ...user.revocationSignatures);\n if (signer !== key) {\n const sub = signer as OpenPGP.Subkey;\n packets.push(sub.keyPacket, ...sub.bindingSignatures, ...sub.revocationSignatures);\n }\n const bytes = new pgp.PublicKey(packets).write();\n if (bytes.length > PGP_LIMITS.sharedKey) throw new PgpProofError('This key is too large to share, even without third-party certifications. Remove old user IDs or use a smaller key.');\n return bytes;\n}\n\nasync function keyInfo(key: OpenPGP.PublicKey, signer: OpenPGP.PublicKey | OpenPGP.Subkey, algorithm: string, at: Date, cfg: OpenPGP.PartialConfig): Promise<PgpKeyInfo> {\n const expiry = await key.getExpirationTime(undefined, cfg as OpenPGP.Config).catch(() => null);\n const subExpiry = signer !== key ? await (signer as OpenPGP.Subkey).getExpirationTime(at, cfg as OpenPGP.Config).catch(() => null) : null;\n const expires = [secondsOf(expiry), secondsOf(subExpiry)].filter((x): x is number => x !== null);\n return {\n fingerprint: key.getFingerprint().toUpperCase(),\n signingFingerprint: signer.getFingerprint().toUpperCase(),\n version: key.keyPacket.version as 4 | 6,\n algorithm,\n createdAt: secondsOf(key.getCreationTime())!,\n expiresAt: expires.length ? Math.min(...expires) : null,\n userIds: (await validUserIds(key, at, cfg)).map(u => u.userID!.userID),\n };\n}\n\n/** What a pasted public key is, before anything is signed: fingerprint and user IDs to show. */\nexport async function readPgpPublicKey(armored: string, options: { now?: number } = {}): Promise<PgpKeyInfo> {\n if (typeof armored !== 'string' || armored.length > PGP_LIMITS.pastedKey) throw new PgpProofError('This public key is too large. Export it with --export-options export-minimal.');\n const { type, block } = armoredBlock(armored, 'key');\n if (type !== 'PUBLIC KEY BLOCK') throw new PgpProofError('Paste a public key block (gpg --armor --export).');\n const pgp = await loadOpenPgp();\n const cfg = config(pgp);\n const key = await readOneKey(pgp, { armored: block });\n const at = new Date((options.now ?? nowSeconds()) * 1000);\n allowedAlgorithm(key.keyPacket, false);\n let signer: OpenPGP.PublicKey | OpenPGP.Subkey;\n try { signer = await key.getSigningKey(undefined, at, undefined, cfg as OpenPGP.Config) as OpenPGP.PublicKey | OpenPGP.Subkey; }\n catch { return explainInvalidKey(key, undefined, at, cfg, 'now'); }\n return keyInfo(key, signer, allowedAlgorithm(signer.keyPacket, true), at, cfg);\n}\n\nasync function readSignaturePacket(pgp: Pgp, input: { armored: string } | { binary: Uint8Array }): Promise<OpenPGP.SignaturePacket> {\n let sig: OpenPGP.Signature;\n try {\n sig = 'armored' in input ? await pgp.readSignature({ armoredSignature: input.armored, config: config(pgp) }) : await pgp.readSignature({ binarySignature: input.binary, config: config(pgp) });\n } catch (e) { throw new PgpProofError(`Could not read this signature: ${message(e)}`); }\n const packets = sig.packets.filterByTag(pgp.enums.packet.signature) as unknown as OpenPGP.SignaturePacket[];\n if (sig.packets.length !== 1 || packets.length !== 1) throw new PgpProofError('Sign with one key only: this signature has several signers.');\n return packets[0];\n}\n\n/**\n * The core check, shared by the person (on paste) and the contact (on receipt):\n * one signature over the statement, by a key or signing subkey of `key` that is\n * valid both when it signed and now, with allowed algorithms and hash.\n */\nasync function verifyPacket(pgp: Pgp, key: OpenPGP.PublicKey, sig: OpenPGP.SignaturePacket, statement: string, options: PgpVerifyOptions): Promise<{ info: PgpVerified; signer: OpenPGP.PublicKey | OpenPGP.Subkey }> {\n const cfg = config(pgp);\n const now = options.now ?? nowSeconds();\n const at = new Date(now * 1000);\n const { signature } = pgp.enums;\n if (sig.signatureType !== signature.text && sig.signatureType !== signature.binary) throw new PgpProofError('This is not a signature over a text or a file.');\n if (sig.hashAlgorithm === null || !ALLOWED_HASHES.has(sig.hashAlgorithm)) throw new PgpProofError('This signature uses a weak hash (SHA-1 or older). Sign again with gpg\\'s default (SHA-256 or stronger): --digest-algo SHA256.');\n if (!sig.created) throw new PgpProofError('This signature has no creation time.');\n const signedAt = secondsOf(sig.created)!;\n if (signedAt > now + PGP_CLOCK_SKEW) throw new PgpProofError('This signature is dated in the future. Check the clock of the machine that signed.');\n if (options.expectedFingerprint !== undefined && key.getFingerprint().toUpperCase() !== options.expectedFingerprint.toUpperCase())\n throw new PgpProofError('This key is not the one the statement names.');\n const issuer = key.getKeys(sig.issuerKeyID)[0];\n if (!issuer) throw new PgpProofError(`This signature was made by another key (${sig.issuerKeyID.toHex().toUpperCase()}), not the public key given.`);\n if (sig.issuerFingerprint && hexOf(sig.issuerFingerprint) !== issuer.getFingerprint().toUpperCase())\n throw new PgpProofError('This signature was made by another key, not the public key given.');\n allowedAlgorithm(key.keyPacket, false);\n // Valid now (not expired, not revoked, signing-capable with its back-signature)…\n let signer: OpenPGP.PublicKey | OpenPGP.Subkey;\n try { signer = await key.getSigningKey(sig.issuerKeyID, at, undefined, cfg as OpenPGP.Config) as OpenPGP.PublicKey | OpenPGP.Subkey; }\n catch { return explainInvalidKey(key, sig.issuerKeyID, at, cfg, 'now'); }\n const algorithm = allowedAlgorithm(signer.keyPacket, true);\n // …and when it signed.\n const created = new Date(Math.min(signedAt, now) * 1000);\n try { await key.getSigningKey(sig.issuerKeyID, created, undefined, cfg as OpenPGP.Config); }\n catch { return explainInvalidKey(key, sig.issuerKeyID, created, cfg, 'signing'); }\n // After the key checks, so an expired or revoked key is reported as such.\n if (options.signedAfter !== undefined && signedAt < options.signedAfter - PGP_CLOCK_SKEW) throw new PgpProofError('This signature is older than the statement it should sign.');\n // A detached signature over a file holds its final newline; clearsigned text does not.\n const text = canonicalStatement(statement);\n const packets = new pgp.PacketList<OpenPGP.AnyPacket>(); packets.push(sig);\n const detached = new pgp.Signature(packets as OpenPGP.PacketList<OpenPGP.SignaturePacket>);\n let verified = false;\n for (const candidate of [text, `${text}\\n`, `${text}\\r\\n`]) {\n const message = await pgp.createMessage({ binary: utf8Encode(candidate) });\n const result = await pgp.verify({ message, signature: detached, verificationKeys: key, date: new Date(Math.max(now, signedAt) * 1000), config: cfg });\n if (result.signatures.length === 1 && await result.signatures[0].verified.then(() => true, () => false)) { verified = true; break; }\n }\n if (!verified) throw new PgpProofError('This signature is not over the Ghostly statement (it signs other text, or it was changed).');\n return { info: { ...await keyInfo(key, signer, algorithm, at, cfg), signedAt }, signer };\n}\n\n/**\n * The person's side: checks what they pasted against the statement and\n * produces the evidence to share. Either a clearsigned statement or an\n * armored detached signature is accepted.\n */\nexport async function preparePgpEvidence(input: { statement: string; signature: string; publicKey: string }, options: PgpVerifyOptions = {}): Promise<{ evidence: PgpEvidence; verified: PgpVerified }> {\n if (typeof input.signature !== 'string' || input.signature.length > PGP_LIMITS.pastedSignature) throw new PgpProofError('This signature is too large. Paste only the output of gpg --clearsign.');\n if (typeof input.publicKey !== 'string' || input.publicKey.length > PGP_LIMITS.pastedKey) throw new PgpProofError('This public key is too large. Export it with --export-options export-minimal.');\n const { type, block } = armoredBlock(input.signature, 'signature');\n const keyBlock = armoredBlock(input.publicKey, 'key');\n if (keyBlock.type !== 'PUBLIC KEY BLOCK') throw new PgpProofError('Paste a public key block (gpg --armor --export).');\n if (type === 'MESSAGE') throw new PgpProofError('This is a signed message (gpg --sign). Use gpg --clearsign, or --detach-sign --armor.');\n if (type === 'PUBLIC KEY BLOCK') throw new PgpProofError('This is a public key; paste it in the public key field and the signature here.');\n const pgp = await loadOpenPgp();\n const key = await readOneKey(pgp, { armored: keyBlock.block });\n let sig: OpenPGP.SignaturePacket;\n if (type === 'SIGNED MESSAGE') {\n let cleartext: OpenPGP.CleartextMessage;\n try { cleartext = await pgp.readCleartextMessage({ cleartextMessage: block, config: config(pgp) }); }\n catch (e) { throw new PgpProofError(`Could not read this signed text: ${message(e)}`); }\n if (canonicalStatement(cleartext.getText()) !== canonicalStatement(input.statement))\n throw new PgpProofError('This signs different text. Sign exactly the statement shown, without editing it.');\n // Typed as private in OpenPGP.js's declarations, public at runtime.\n const packets = (cleartext as unknown as { signature: OpenPGP.Signature }).signature.packets;\n if (packets.length !== 1 || !(packets[0] instanceof pgp.SignaturePacket)) throw new PgpProofError('Sign with one key only: this signature has several signers.');\n sig = packets[0];\n } else if (type === 'SIGNATURE') sig = await readSignaturePacket(pgp, { armored: block });\n else throw new PgpProofError('Paste the output of gpg --clearsign (or gpg --detach-sign --armor).');\n const { info, signer } = await verifyPacket(pgp, key, sig, input.statement, options);\n const single = new pgp.PacketList<OpenPGP.AnyPacket>(); single.push(sig);\n const signature = new pgp.Signature(single as OpenPGP.PacketList<OpenPGP.SignaturePacket>).write();\n if (signature.length > PGP_LIMITS.sharedSignature) throw new PgpProofError('This signature is too large to share.');\n const at = new Date((options.now ?? nowSeconds()) * 1000);\n const evidence: PgpEvidence = { scheme: PGP_SCHEME, key: toBase64Url(await minimalKey(pgp, key, signer, at, config(pgp))), signature: toBase64Url(signature) };\n return { evidence, verified: info };\n}\n\nconst b64 = /^[A-Za-z0-9_-]+$/;\n/** Untrusted evidence off the wire: exact shape, bounded, canonical base64url. Checked before any OpenPGP parsing. */\nexport function parsePgpEvidence(raw: unknown): PgpEvidence {\n const e = raw as PgpEvidence;\n if (!e || typeof e !== 'object' || Array.isArray(e) || e.scheme !== PGP_SCHEME || Object.keys(e).sort().join(',') !== 'key,scheme,signature' ||\n typeof e.key !== 'string' || typeof e.signature !== 'string' || !b64.test(e.key) || !b64.test(e.signature) ||\n e.key.length > Math.ceil(PGP_LIMITS.sharedKey * 4 / 3) || e.signature.length > Math.ceil(PGP_LIMITS.sharedSignature * 4 / 3))\n throw new PgpProofError('Invalid OpenPGP proof');\n let canonical: boolean;\n try { canonical = toBase64Url(fromBase64Url(e.key)) === e.key && toBase64Url(fromBase64Url(e.signature)) === e.signature; } catch { canonical = false; }\n if (!canonical) throw new PgpProofError('Invalid OpenPGP proof encoding');\n return { scheme: PGP_SCHEME, key: e.key, signature: e.signature };\n}\n\n/** The contact's side: verifies shared evidence against the statement their own app built. */\nexport async function verifyPgpEvidence(raw: PgpEvidence, statement: string, options: PgpVerifyOptions = {}): Promise<PgpVerified> {\n const evidence = parsePgpEvidence(raw);\n const keyBytes = fromBase64Url(evidence.key), sigBytes = fromBase64Url(evidence.signature);\n const pgp = await loadOpenPgp();\n const key = await readOneKey(pgp, { binary: keyBytes });\n const sig = await readSignaturePacket(pgp, { binary: sigBytes });\n return (await verifyPacket(pgp, key, sig, statement, options)).info;\n}\n\n/** Groups of four, as gpg prints fingerprints. */\nexport function formatFingerprint(fingerprint: string): string {\n return fingerprint.replace(/(.{4})/g, '$1 ').trim();\n}\n\n/** keys.openpgp.org publishes a user ID only after its owner confirms the email. */\nexport const KEYSERVER = 'https://keys.openpgp.org';\nexport interface KeyserverKey { armored: string; source: typeof KEYSERVER; fetchedAt: number }\n/**\n * Fetches a public key from keys.openpgp.org. Only ever called when the person\n * asks for it, after the UI said this contacts that server.\n */\nexport async function fetchKeyFromKeyserver(query: string, options: { signal?: AbortSignal; fetch?: typeof fetch; now?: number } = {}): Promise<KeyserverKey> {\n const q = query.trim().replace(/\\s+/g, '').replace(/^0x/i, '');\n let path: string;\n if (/^[A-Fa-f0-9]{40}$/.test(q) || /^[A-Fa-f0-9]{64}$/.test(q)) path = `/vks/v1/by-fingerprint/${q.toUpperCase()}`;\n else if (/^[^\\s@<>]{1,64}@[^\\s@<>]{1,190}$/.test(query.trim())) path = `/vks/v1/by-email/${encodeURIComponent(query.trim())}`;\n else throw new PgpProofError('Enter a full fingerprint (40 hex characters) or an email address.');\n const response = await (options.fetch ?? fetch)(`${KEYSERVER}${path}`, { signal: options.signal, credentials: 'omit', referrerPolicy: 'no-referrer', redirect: 'error' });\n if (response.status === 404) throw new PgpProofError('keys.openpgp.org has no key for this. Paste your public key instead.');\n if (!response.ok) throw new PgpProofError(`keys.openpgp.org answered ${response.status}. Paste your public key instead.`);\n const length = Number(response.headers.get('content-length') ?? 0);\n if (length > PGP_LIMITS.keyserverResponse) throw new PgpProofError('The key from keys.openpgp.org is too large.');\n const armored = await response.text();\n if (armored.length > PGP_LIMITS.keyserverResponse) throw new PgpProofError('The key from keys.openpgp.org is too large.');\n return { armored, source: KEYSERVER, fetchedAt: options.now ?? nowSeconds() };\n}\n\n/**\n * Emails keys.openpgp.org vouches for on this key: it publishes a user ID only\n * once the email's owner confirmed it, so every user ID in its copy is one the\n * keyserver checked. That is the keyserver's check, and says nothing about a name.\n */\nexport async function keyserverVerifiedEmails(keyserverArmored: string, fingerprint: string, options: { now?: number } = {}): Promise<string[]> {\n const info = await readPgpPublicKey(keyserverArmored, options);\n if (info.fingerprint !== fingerprint.toUpperCase()) throw new PgpProofError('keys.openpgp.org returned a different key.');\n const emails = info.userIds.map(u => /<([^<>\\s]+@[^<>\\s]+)>\\s*$/.exec(u)?.[1] ?? (/^[^<>\\s]+@[^<>\\s]+$/.test(u) ? u : undefined));\n return [...new Set(emails.filter((e): e is string => !!e).map(e => e.toLowerCase()))];\n}\n","import type { IdentityStatement } from \"@ghostly/core\";\nimport type { ExternalToolSigner, IdentityProofProvider, SignerInstructions } from \"../contract\";\nimport {\n KEYSERVER, PgpProofError, assertSignableStatement, fetchKeyFromKeyserver, formatFingerprint, keyserverVerifiedEmails, parsePgpEvidence,\n preparePgpEvidence, splitPgpPaste, verifyPgpEvidence, type PgpEvidence,\n} from \"../openpgp\";\n\n/**\n * OpenPGP keys, signed with the person's own gpg — a YubiKey or another OpenPGP card works unchanged,\n * since gpg drives it. The subject is the primary key's fingerprint. See docs/wisps/305-openpgp.md.\n */\n\n/** A v4 (40 hex) or v6 (64 hex) fingerprint, as gpg prints it or not: spaces and 0x are dropped. */\nexport function normalizeFingerprint(input: string): string {\n const value = input.trim().replace(/^0x/i, \"\").replace(/\\s+/g, \"\").toUpperCase();\n if (/^[A-F0-9]{40}$/.test(value) || /^[A-F0-9]{64}$/.test(value)) return value;\n if (/^[A-F0-9]{8}$|^[A-F0-9]{16}$/.test(value)) throw new PgpProofError(\"Use the full fingerprint, not a key ID: gpg --fingerprint shows it.\");\n throw new PgpProofError(\"Enter your key's fingerprint: 40 hex characters (gpg --fingerprint).\");\n}\n\n/** The user ID is its holder's own words: shown with that caveat, never as a checked name. */\nconst USER_ID_SOURCE = \"User ID on the key, written by its holder: not proof that the name or email is theirs\";\nconst KEYSERVER_SOURCE = \"Email confirmed to keys.openpgp.org by its owner: the keyserver's check, not Ghostly's\";\n\nfunction steps(statement: IdentityStatement, exportKey: boolean): SignerInstructions {\n assertSignableStatement(statement.text);\n // The statement is one line of space-separated fields with no quote in it, so it is safe in single quotes.\n if (statement.text.includes(\"'\")) throw new PgpProofError(\"Statement is not signable as plain text\");\n const fpr = normalizeFingerprint(statement.binding.subject);\n const sign = `gpg --local-user ${fpr} --clearsign --output - ghostly-identity.txt`;\n return {\n steps: [\n { text: \"Save the statement to a file (it is one line, with no newline at the end):\", copy: `printf '%s' '${statement.text}' > ghostly-identity.txt` },\n exportKey\n ? { text: \"Sign it and export your public key, then copy everything this prints:\", copy: `${sign} && gpg --armor --export --export-options export-minimal ${fpr}` }\n : { text: \"Sign it, then copy everything this prints:\", copy: sign },\n { text: \"On a YubiKey or another OpenPGP card, gpg asks for the card's PIN, and for a touch if the card requires one. Without a shell, save the statement below in a text file and sign that file.\", copy: statement.text },\n ],\n paste: { label: exportKey ? \"Signed statement and public key\" : \"Signed statement\", placeholder: \"-----BEGIN PGP SIGNED MESSAGE-----\", multiline: true },\n };\n}\n\nasync function evidenceFrom(statement: IdentityStatement, signature: string, publicKey: string): Promise<PgpEvidence> {\n const { evidence } = await preparePgpEvidence({ statement: statement.text, signature, publicKey },\n { expectedFingerprint: normalizeFingerprint(statement.binding.subject), signedAfter: statement.binding.issuedAt });\n return evidence;\n}\n\nconst gpg: ExternalToolSigner<PgpEvidence> = {\n id: \"gpg\", kind: \"external-tool\", label: \"gpg\",\n description: \"Sign with gpg and paste what it prints, public key included. Nothing is looked up.\",\n instructions: statement => steps(statement, true),\n async parse(pasted, statement) {\n const { signature, publicKey } = splitPgpPaste(pasted);\n if (!publicKey) throw new PgpProofError(\"Paste your public key too: the second command prints it (-----BEGIN PGP PUBLIC KEY BLOCK-----).\");\n return evidenceFrom(statement, signature, publicKey);\n },\n};\n\nconst gpgKeyserver: ExternalToolSigner<PgpEvidence> = {\n id: \"gpg-keys-openpgp-org\", kind: \"external-tool\", label: \"gpg, key from keys.openpgp.org\",\n description: \"Sign with gpg and paste the signature; Ghostly then fetches your public key from keys.openpgp.org, which learns which key was looked up.\",\n instructions: statement => steps(statement, false),\n async parse(pasted, statement) {\n const { signature, publicKey } = splitPgpPaste(pasted);\n // Only here, on the person's choice of this signer, is the keyserver contacted.\n const key = publicKey ?? (await fetchKeyFromKeyserver(statement.binding.subject)).armored;\n return evidenceFrom(statement, signature, key);\n },\n};\n\nexport const openpgp: IdentityProofProvider<PgpEvidence> = {\n id: \"openpgp\",\n label: \"OpenPGP key\",\n category: \"self-custodied\",\n summary: \"Sign with gpg and paste the result\",\n description: \"Proves you hold an OpenPGP key: sign the statement with gpg (a YubiKey works too) and paste the result.\",\n limits: \"Holding a key does not prove the names or emails in its user IDs.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Key fingerprint\", placeholder: \"40 hex characters, from gpg --fingerprint\",\n help: \"The primary key's full fingerprint. Holding a key does not prove the names or emails in its user IDs.\",\n normalize: normalizeFingerprint,\n short: fpr => `…${formatFingerprint(fpr.slice(-16))}`,\n },\n // The fingerprint URI scheme OpenPGP apps share keys with.\n publicUri: fpr => `openpgp4fpr:${fpr}`,\n validity: { defaultDays: 90, maxDays: 365 },\n privacy: `Nothing: the signature is checked on this device. ${KEYSERVER.replace(\"https://\", \"\")} is contacted only if you choose it for your key, or ask it to confirm emails.`,\n signers: [gpg, gpgKeyserver],\n lookupLabel: \"Check emails with keys.openpgp.org\",\n parseEvidence: raw => parsePgpEvidence(raw),\n async verify(statement, evidence, ctx) {\n ctx.signal.throwIfAborted();\n const { binding } = statement;\n const verified = await verifyPgpEvidence(evidence, statement.text, { now: ctx.now, signedAfter: binding.issuedAt, expectedFingerprint: normalizeFingerprint(binding.subject) });\n const subkey = verified.signingFingerprint !== verified.fingerprint;\n const [userId] = verified.userIds;\n return {\n subject: verified.fingerprint,\n source: `OpenPGP signature (${verified.algorithm}${subkey ? \" signing subkey\" : \"\"}, v${verified.version} key)`,\n ...(verified.expiresAt !== null && verified.expiresAt < binding.expiresAt ? { expiresAt: verified.expiresAt } : {}),\n ...(userId ? { display: { name: userId, source: USER_ID_SOURCE, fetchedAt: ctx.now } } : {}),\n };\n },\n /** On request only: which emails on this key keys.openpgp.org confirmed, and whether it holds a revocation. */\n async lookupDisplay(subject, { signal }) {\n const fpr = normalizeFingerprint(subject);\n const found = await fetchKeyFromKeyserver(fpr, { signal });\n const emails = await keyserverVerifiedEmails(found.armored, fpr);\n if (!emails.length) return undefined;\n return { name: emails[0], url: `${KEYSERVER}/search?q=${fpr}`, source: KEYSERVER_SOURCE, fetchedAt: found.fetchedAt };\n },\n};\n","import { parseSshPublicKey, sameSshKey, type SshPublicKey } from '@ghostly/core';\nimport type { IdentityFetch } from './contract';\n\n/** \"This account publishes that key\", without OAuth: GitHub and GitLab publish every account's SSH\n * public keys, and only the account holder can add one. The verifier's own lookup, through the\n * engine's bounded `ctx.fetch`; it tells the forge (and the network) which account was checked. */\n\nexport type SshForge = 'github' | 'gitlab';\nexport const SSH_FORGE_LABEL: Record<SshForge, string> = { github: 'GitHub', gitlab: 'GitLab' };\n/** The only host each forge lookup contacts, for the privacy line. */\nexport const SSH_FORGE_HOST: Record<SshForge, string> = { github: 'api.github.com', gitlab: 'gitlab.com' };\n\nexport type SshForgeStatus = 'linked' | 'not-listed' | 'no-account';\n\nconst MAX_BODY = 256 * 1024;\nconst MAX_KEYS = 200;\n\nexport function validForgeLogin(forge: SshForge, login: string): boolean {\n if (typeof login !== 'string') return false;\n // GitHub: 1-39 alphanumerics or single inner hyphens. GitLab: letters, digits, _ . - ;\n // not starting with - and not ending in .git/.atom. Capped at 64 so it fits every label.\n return forge === 'github'\n ? /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/.test(login)\n : /^[A-Za-z0-9_.][A-Za-z0-9_.-]{0,63}$/.test(login) && !/\\.(?:git|atom)$/i.test(login);\n}\n\nasync function get(fetcher: IdentityFetch, url: string, signal?: AbortSignal): Promise<{ status: 404 } | { status: 200; body: unknown }> {\n const response = await fetcher(url, { maxBytes: MAX_BODY, signal });\n if (response.status === 404) return { status: 404 };\n if (response.status === 403 || response.status === 429) throw new Error(`${new URL(url).host} is limiting lookups; check again later`);\n if (response.status !== 200) throw new Error(`${new URL(url).host} answered HTTP ${response.status}`);\n try { return { status: 200, body: JSON.parse(response.text) }; } catch { throw new Error(`Unexpected answer from ${new URL(url).host}`); }\n}\n\nfunction keysFrom(body: unknown): SshPublicKey[] {\n if (!Array.isArray(body)) throw new Error('Unexpected key list');\n const keys: SshPublicKey[] = [];\n for (const entry of body.slice(0, MAX_KEYS)) {\n const line = (entry as { key?: unknown } | null)?.key;\n if (typeof line !== 'string') continue;\n try { keys.push(parseSshPublicKey(line)); } catch { /* A key type Ghostly cannot verify never matches. */ }\n }\n return keys;\n}\n\n/** The account's published keys, or null when there is no such account. */\nasync function listKeys(forge: SshForge, login: string, fetcher: IdentityFetch, signal?: AbortSignal): Promise<SshPublicKey[] | null> {\n if (forge === 'github') {\n const r = await get(fetcher, `https://api.github.com/users/${encodeURIComponent(login)}/keys?per_page=100`, signal);\n return r.status === 404 ? null : keysFrom(r.body);\n }\n const users = await get(fetcher, `https://gitlab.com/api/v4/users?username=${encodeURIComponent(login)}`, signal);\n const list = users.status === 200 && Array.isArray(users.body) ? users.body as { id?: unknown; username?: unknown }[] : [];\n const user = list.find(u => typeof u?.username === 'string' && u.username.toLowerCase() === login.toLowerCase());\n if (!user) return null;\n if (!Number.isSafeInteger(user.id) || (user.id as number) <= 0) throw new Error('Unexpected answer from gitlab.com');\n const r = await get(fetcher, `https://gitlab.com/api/v4/users/${user.id as number}/keys?per_page=100`, signal);\n return r.status === 404 ? null : keysFrom(r.body);\n}\n\n// Only concurrent identical lookups share a request. What stays checked is the verifier's stored\n// result, which the app offers to re-check once the provider's `recheck` time has passed.\nconst inFlight = new WeakMap<IdentityFetch, Map<string, Promise<SshPublicKey[] | null>>>();\n\n/** Whether `key` is one of `login`'s published SSH keys. Throws when the forge cannot be asked. */\nexport async function checkSshForge(forge: SshForge, login: string, key: Pick<SshPublicKey, 'blob'>, fetcher: IdentityFetch, signal?: AbortSignal): Promise<SshForgeStatus> {\n if (!validForgeLogin(forge, login)) throw new Error(`Not a ${SSH_FORGE_LABEL[forge]} username`);\n let pending = inFlight.get(fetcher);\n if (!pending) inFlight.set(fetcher, pending = new Map());\n const map = pending, id = `${forge}:${login.toLowerCase()}`;\n let lookup = map.get(id);\n if (!lookup) {\n lookup = listKeys(forge, login, fetcher, signal).finally(() => map.delete(id));\n map.set(id, lookup);\n }\n const keys = await lookup;\n if (!keys) return 'no-account';\n return keys.some(k => sameSshKey(k, key)) ? 'linked' : 'not-listed';\n}\n","import {\n parseSshPublicKey, parseSshSignature, sshSignCommand, SshSigError, SSHSIG_MAX_ARMOR, SSHSIG_NAMESPACE, utf8Encode, verifySshSignature,\n type IdentityStatement, type SshSignatureCheck,\n} from \"@ghostly/core\";\nimport type { ExternalToolSigner, IdentityProofProvider, VerifyContext } from \"../contract\";\nimport { checkSshForge, SSH_FORGE_HOST, SSH_FORGE_LABEL, validForgeLogin, type SshForge } from \"../sshForges\";\n\n/**\n * SSH keys (draft WISP 307-ssh). The person signs the binding statement once with their own\n * `ssh-keygen -Y sign -n ghostly` and pastes the armored SSHSIG; the private key never enters Ghostly.\n *\n * - `ssh` the subject is the key's SHA-256 fingerprint; checked on this device, no network.\n * - `ssh-github` the subject is a GitHub account: the signing key must be one of the SSH keys GitHub\n * - `ssh-gitlab` publishes for it (GitLab alike). No OAuth: only the account holder can add a key, so\n * \"this account lists the key\" links the two. Re-checked, since keys get removed.\n */\n\nexport interface SshEvidence { signature: string }\n\nconst FINGERPRINT = /^SHA256:[A-Za-z0-9+/]{43}$/;\n\nfunction normalizeKeySubject(input: string): string {\n const value = input.trim();\n if (FINGERPRINT.test(value)) return value;\n try { return parseSshPublicKey(value).fingerprint; }\n catch (e) {\n // Say why a real key is refused (a DSA key, a small RSA key); anything else gets the instruction.\n if (e instanceof SshSigError && /^(Unsupported SSH key type|RSA keys must)/.test(e.message)) throw Object.assign(new Error(e.message), { cause: e });\n throw Object.assign(new Error(\"Paste your public key (the one line in ~/.ssh/id_ed25519.pub) or its SHA256: fingerprint\"), { cause: e });\n }\n}\n\nfunction normalizeLogin(forge: SshForge, input: string): string {\n const login = input.trim().replace(/^@/, \"\").toLowerCase();\n if (!validForgeLogin(forge, login)) throw new Error(`Enter a ${SSH_FORGE_LABEL[forge]} username`);\n return login;\n}\n\n/** The armor, re-wrapped canonically, so evidence is the same whatever the clipboard did to it. */\nfunction canonicalArmor(pasted: string): string {\n const parsed = parseSshSignature(pasted);\n if (parsed.namespace !== SSHSIG_NAMESPACE) throw new Error(`That signature is for \"${parsed.namespace}\", not \"${SSHSIG_NAMESPACE}\": run the command with -n ${SSHSIG_NAMESPACE}`);\n const body = pasted.replace(/\\r\\n/g, \"\\n\").trim().split(\"\\n\").map(l => l.trim()).slice(1, -1).join(\"\");\n return `-----BEGIN SSH SIGNATURE-----\\n${body.match(/.{1,70}/g)!.join(\"\\n\")}\\n-----END SSH SIGNATURE-----\\n`;\n}\n\n/** Exactly the statement; or with one trailing newline, which an editor adds when the statement is\n * saved to a file and signed there (the Windows route). Nothing else is accepted. */\nasync function verifyStatementSignature(statement: IdentityStatement, evidence: SshEvidence): Promise<SshSignatureCheck> {\n try { return await verifySshSignature(evidence.signature, statement.bytes); }\n catch (e) {\n if (!(e instanceof SshSigError) || !/does not match this statement/.test(e.message)) throw e;\n try { return await verifySshSignature(evidence.signature, utf8Encode(`${statement.text}\\n`)); }\n catch (again) { throw Object.assign(new Error(\"The signature is not over this statement: copy the command again and sign exactly what it shows\"), { cause: again }); }\n }\n}\n\nconst keyKind = (c: SshSignatureCheck) => c.securityKey\n ? `security key, touch${c.securityKey.userVerified ? \" and PIN\" : \"\"} confirmed`\n : c.key.type === \"ssh-rsa\" ? `RSA ${c.key.bits}` : c.key.type.replace(/^ssh-|^ecdsa-sha2-/, \"\");\n\nfunction sshKeygenSigner(forAccount?: SshForge): ExternalToolSigner<SshEvidence> {\n return {\n id: \"ssh-keygen\", kind: \"external-tool\", label: \"ssh-keygen\",\n description: forAccount\n ? `Sign with an SSH key that your ${SSH_FORGE_LABEL[forAccount]} account lists (Settings → SSH keys).`\n : \"Sign with OpenSSH's ssh-keygen (8.1 or later): a key file, ssh-agent, or a FIDO security key.\",\n instructions: statement => {\n let command: string | undefined;\n try { command = sshSignCommand(statement.text); } catch { command = undefined; }\n return {\n steps: [\n ...(command ? [{ text: \"Run this in a terminal (macOS, Linux, or Git Bash/WSL on Windows). If your key is not ~/.ssh/id_ed25519, change the path after -f; a .pub path works when the private key is in ssh-agent or on a security key.\", copy: command }] : []),\n { text: `${command ? \"Or save\" : \"Save\"} this statement to a file, run ssh-keygen -Y sign -n ${SSHSIG_NAMESPACE} -f <your key> <file>, and paste the <file>.sig it writes.`, copy: statement.text },\n { text: \"Paste the whole signature it prints, from -----BEGIN SSH SIGNATURE----- to -----END SSH SIGNATURE-----. Your private key never leaves your computer.\" },\n ],\n paste: { label: \"SSH signature\", placeholder: \"-----BEGIN SSH SIGNATURE-----\", multiline: true },\n };\n },\n parse(pasted, statement) {\n const signature = canonicalArmor(pasted);\n if (!forAccount) {\n const { key } = parseSshSignature(signature);\n if (key.fingerprint !== statement.binding.subject) throw new Error(`That signature was made by ${key.fingerprint}, not the key you entered (${statement.binding.subject})`);\n }\n return { signature };\n },\n };\n}\n\nfunction parseSshEvidence(raw: unknown): SshEvidence {\n const e = raw as SshEvidence;\n if (!e || typeof e !== \"object\" || Array.isArray(e) || Object.keys(e).join(\",\") !== \"signature\" ||\n typeof e.signature !== \"string\" || e.signature.length > SSHSIG_MAX_ARMOR) throw new Error(\"That is not an SSH signature\");\n return { signature: e.signature };\n}\n\nconst shortFingerprint = (f: string) => `${f.slice(0, 15)}…${f.slice(-4)}`;\n\nexport const ssh: IdentityProofProvider<SshEvidence> = {\n id: \"ssh\",\n label: \"SSH key\",\n category: \"self-custodied\",\n summary: \"Sign once with ssh-keygen\",\n description: \"Proves you hold an SSH key, including FIDO security keys: sign the statement once with ssh-keygen and paste the signature.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Public key\", placeholder: \"ssh-ed25519 AAAA… (or SHA256:…)\",\n help: \"Your public key, the one line in the .pub file. Contacts see its fingerprint.\",\n normalize: normalizeKeySubject, short: shortFingerprint,\n },\n validity: { defaultDays: 90, maxDays: 365 },\n privacy: \"Nothing: the signature is checked on this device. The key's fingerprint links every chat you share it in.\",\n signers: [sshKeygenSigner()],\n parseEvidence: parseSshEvidence,\n async verify(statement, evidence) {\n const check = await verifyStatementSignature(statement, evidence);\n if (check.key.fingerprint !== statement.binding.subject) throw new Error(\"Signed by another SSH key\");\n return { subject: statement.binding.subject, source: `SSH signature (${keyKind(check)})` };\n },\n};\n\nfunction forgeProvider(forge: SshForge): IdentityProofProvider<SshEvidence> {\n const label = SSH_FORGE_LABEL[forge], host = SSH_FORGE_HOST[forge];\n return {\n id: `ssh-${forge}`,\n label: `${label} (SSH key)`,\n category: \"self-custodied\",\n summary: `A key your ${label} account lists`,\n description: `Proves you hold an SSH key your ${label} account publishes: sign the statement once with ssh-keygen. No ${label} login.`,\n limits: `Does not prove a ${label} login: only that a key the account lists is yours, for as long as it stays listed.`,\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: { label: `${label} username`, placeholder: forge === \"github\" ? \"octocat\" : \"username\", normalize: input => normalizeLogin(forge, input) },\n publicUri: login => `https://${forge === \"github\" ? \"github.com\" : \"gitlab.com\"}/${login}`,\n validity: { defaultDays: 90, maxDays: 365 },\n // Keys get removed from accounts; the contact's app offers \"Check again\" after this.\n recheck: { afterSeconds: 600 },\n privacy: `Your contact's app asks ${host} for this account's public SSH keys, when you share it and on each re-check: ${label} learns the account was looked up, and from which IP address. So does this app when you add it.`,\n signers: [sshKeygenSigner(forge)],\n parseEvidence: parseSshEvidence,\n async verify(statement, evidence, ctx: VerifyContext) {\n const login = statement.binding.subject;\n if (normalizeLogin(forge, login) !== login) throw new Error(`Not a ${label} username`);\n const check = await verifyStatementSignature(statement, evidence);\n const status = await checkSshForge(forge, login, check.key, ctx.fetch, ctx.signal);\n if (status === \"no-account\") throw new Error(`${label} has no account ${login}`);\n if (status === \"not-listed\") throw new Error(`${label}: ${login} does not list the key that signed (${check.key.fingerprint})`);\n return { subject: login, source: `${label}: ${login} (via published SSH key)` };\n },\n };\n}\n\nexport const sshGithub = forgeProvider(\"github\");\nexport const sshGitlab = forgeProvider(\"gitlab\");\n","import type { FoundUpdate } from \"../../../apps/ui/src/lib/updates\";\nimport type { EngineEvent, RpcRequest, RpcResponse } from \"./shared/rpc\";\nimport type { OidcPlatform } from \"./proofs/oidc/providers\";\nimport type { OidcWindow } from \"./proofs/oidc/flow\";\nimport type { AtprotoHost } from \"./proofs/atproto/oauth\";\nimport type { CallMedia } from \"@ghostly/core\";\n\n/**\n * What differs between the places this peer runs. The extension keeps the\n * peer in an offscreen document and reaches it through extension messaging;\n * the web app runs it in the page. Everything else is shared.\n */\nexport interface EngineConnection {\n send(request: RpcRequest): void;\n}\n\n/**\n * How a client finds a new version of itself and puts it in place. The web\n * app reloads the tab; an extension from the store reloads itself; an unpacked\n * extension can only point at the download. A host that leaves this out has no\n * updater, and the UI never mentions one.\n */\nexport interface UpdateSource {\n /** Where someone installing by hand should go. */\n downloadUrl: string;\n /** The newest published version, or null when this client already runs it. */\n check(): Promise<FoundUpdate | null>;\n /** Never called for `apply: \"manual\"`. `onProgress` is used where the platform reports it. */\n install(update: FoundUpdate, onProgress?: (fraction: number) => void): Promise<void>;\n}\n\nexport interface BrowserHost {\n version: string;\n /** Something the user should know about this client, shown in the sidebar. */\n notice?: string;\n /** Left out where this client has no way to learn about new versions. */\n updates?: UpdateSource;\n /**\n * Where this host frames mini-apps (WISP 1200): its runner page, served with the runner's policy as a header. Left\n * out where it runs none (the extension, until it gets its own runner).\n */\n appRunner?: string;\n /** The runner for apps granted `internet` (WISP 1200 · Permissions), under a policy that allows HTTPS and WSS. */\n appNetRunner?: string;\n /**\n * The host serves its runner itself, always with the runner's policy (Desktop: Rust's `ghostly-app` scheme, to app\n * windows only), so the page does not ask for its header. Left out where a web server serves it (it may be one\n * without the header).\n */\n appRunnerServed?: boolean;\n features: {\n /** Can this host reach web apps on the user's machine? A web page only can if they allow it with CORS. */\n shareLocalServices: boolean;\n /** Can this host show a contact's web app on an origin of its own? */\n openServices: boolean;\n /** Can this host restart as another local profile (WISP 04)? The page's own peer, or the extension's offscreen one. */\n profiles?: boolean;\n };\n /**\n * Starts a new peer in place of one that stopped for good (\"Clear all data\"). Left out where the peer lives in the\n * page: reloading the page starts it again. The extension's peer outlives its pages, so it is restarted on purpose.\n */\n restartEngine?(): Promise<void>;\n /** Reaches the peer. `onDisconnect` fires when it goes away; the client then connects again. */\n connect(onMessage: (message: EngineEvent | RpcResponse) => void, onDisconnect: () => void): Promise<EngineConnection>;\n /**\n * Asks the user for access to a local origin, where the platform has such a thing: `originPattern` is the\n * extension's host pattern (`http://localhost/*`), `origin` the exact address with its port (`http://localhost:3400`),\n * which the desktop app has Rust ask about in a native dialog.\n */\n requestLocalAccess(originPattern: string, origin: string): Promise<boolean>;\n /** The profile no longer shares any app at `origin` (desktop: off Rust's list of addresses it may reach). */\n forgetLocalAccess?(origin: string): Promise<void>;\n /** `tag` (`viewerTag` of this side's key for the chat) goes in the app's origin, so each profile's storage for it is its own. */\n openService(peerPubKeyZ32: string, serviceId: string, tag: string): Promise<void>;\n /**\n * Opens a `lightning:` or `bitcoin:` link in a wallet on this device. Left out where a plain link\n * already does it (a web page); the desktop app and the extension have to hand it to the system.\n */\n openPaymentLink?(uri: string): Promise<void>;\n /**\n * Puts the app's window in or out of full screen. The desktop app on Linux has it: WebKitGTK's element full screen\n * aborts the app there, so the page fills the window with the video instead (apps/desktop/src/fullscreen.rs).\n */\n fullscreenWindow?(on: boolean): Promise<void>;\n /**\n * Hands a link to the system's share sheet, pointing at `anchor` (the button, in CSS pixels).\n * Resolves to false where the platform has no sheet to show; left out where the page's own Web\n * Share API is the way (a web page).\n */\n shareText?(text: string, anchor?: ShareAnchor): Promise<boolean>;\n /**\n * The clipboard's text, read by the platform itself, for a paste button the user just clicked.\n * Left out where the page's `navigator.clipboard.readText()` is the way (a web page, the\n * extension); the desktop app reads natively because WKWebView asks for a second click.\n */\n readClipboardText?(): Promise<string>;\n /**\n * Files copied in a file manager, or a picture, read by the platform itself for a paste whose event\n * carried neither files nor text (a webview that hides them from the page). Left out where the page\n * sees them in the paste event (a browser, WebKit on macOS).\n */\n readClipboardFiles?(): Promise<ClipboardFile[]>;\n /**\n * Where calls get their media, when not from the page's own WebRTC. Ghostly Desktop on Linux: WebKitGTK has\n * no WebRTC there, and GStreamer runs the calls. Left out where the page has it.\n */\n callMedia?: CallMedia;\n /** Signing in with an OpenID Connect provider for an identity proof. Left out where the platform cannot. */\n oidc?: OidcHost;\n /** Signing in to the person's AT Protocol server (Bluesky or another PDS) for an identity proof. Left out where the platform cannot. */\n atproto?: AtprotoHost;\n /**\n * Opens Pubky Passport's authorize page (`https://passport.pubky.app/authorize#d=…`, the request in its fragment)\n * outside the app. The desktop app hands it to the system browser; left out where the page opens a popup itself.\n */\n openPubkyPassport?(url: string): Promise<void>;\n /**\n * A Pubky cookie session's requests to the homeserver, made outside the page with a cookie jar of their own: the\n * desktop app, whose WKWebView drops the homeserver's session cookie (a third-party one there). Left out where\n * the page's own fetch keeps it.\n */\n pubkyCookieSession?(): PubkyCookieSession;\n /**\n * Keeps the computer from sleeping while idle, or lets it sleep again (\"Keep this computer awake\", WISP 06 § User\n * experience): the desktop app, so a phone can take the profile over while the person is out. Resolves to whether\n * it is held now. Left out where the platform cannot (a web page, the extension).\n */\n keepAwake?(on: boolean): Promise<boolean>;\n}\n\n/** One approval's cookie session, outside the page: its cookies never reach the page, and `close` forgets them. */\nexport interface PubkyCookieSession {\n fetch(request: { url: string; method: string; headers: [string, string][]; body: Uint8Array | null }): Promise<{ status: number; headers: [string, string][]; body: Uint8Array }>;\n close(): void;\n}\n\n/** Where a share sheet points: the button that opened it, in CSS pixels from the page's top left. */\nexport interface ShareAnchor { x: number; y: number; width: number; height: number }\n\nexport interface OidcHost {\n platform: OidcPlatform;\n /**\n * Call it straight from the click: the web app opens its popup and the\n * extension asks for the `identity` permission before anything is awaited,\n * or the browser blocks them.\n */\n open(): Promise<OidcWindow>;\n}\n\nlet current: BrowserHost | null = null;\n\n/** Must be called before anything in `platform/` is used. */\nexport function setBrowserHost(host: BrowserHost): void {\n current = host;\n}\n\nexport function getBrowserHost(): BrowserHost {\n if (!current) throw new Error(\"No browser host configured\");\n return current;\n}\n\n/**\n * One thing a paste brought, held by the platform: a file (its name) or a picture (no name: the page\n * names it). `read` hands its bytes a step at a time; `done` says the page has what it wants of\n * it (or gave up), so the platform lets its copy go: nothing is read after it.\n */\nexport interface ClipboardFile {\n name: string | null;\n size: number;\n mime: string | null;\n read(offset: number, length: number): Promise<Uint8Array>;\n done?(): void;\n}\n","/**\n * The OpenID Connect providers Ghostly can ask for a signed ID token. A contact\n * verifies that token against the provider's published keys, with no Ghostly\n * server in between, so a provider is only listed when its ID tokens are signed\n * and its keys and sign-in page are reachable from a browser without a client\n * secret. See docs/wisps/3xx-oidc-proofs.md for the providers that are not\n * (Facebook, X, LinkedIn, GitHub) and why.\n *\n * Client IDs are public configuration, identical in every build, because the\n * contact's app checks `aud` against them. They stay empty until the OAuth\n * clients are registered (docs/OIDC-PROVIDERS.md); a provider without a client\n * ID is neither offered nor accepted.\n */\n\nexport type OidcProviderId = 'google' | 'microsoft' | 'apple' | 'gitlab' | 'twitch' | 'test';\nexport type OidcAlg = 'RS256' | 'ES256';\nexport type OidcPlatform = 'web' | 'extension' | 'desktop';\n/** What the person chooses to put in the token beyond the account identifier. */\nexport type OidcReveal = 'email' | 'profile';\n\nexport interface OidcProvider {\n id: OidcProviderId;\n name: string;\n /** Exact `iss`. Microsoft's contains the tenant: see `issuerFor`. */\n issuer: string;\n /** Other spellings the provider documents for the same issuer. */\n issuerAliases?: string[];\n discovery: string;\n authorizationEndpoint: string;\n /** Pinned rather than discovered, so a changed discovery document cannot redirect key lookup. */\n jwksUri: string;\n /** Only for `code`: the token endpoint, reached with PKCE and no secret. */\n tokenEndpoint?: string;\n /** Best effort after a `code` exchange: the access token Ghostly never wanted is revoked at once. */\n revocationEndpoint?: string;\n /** Pinned per provider; the token header's `alg` must be one of these. */\n algs: OidcAlg[];\n /**\n * `id_token` returns only the signed ID token in the fragment: no access\n * token, no code, nothing to exchange, so no secret. `code id_token` is\n * Apple's shape (its code needs a secret; Ghostly drops it). `code` is for\n * providers without an ID-token-only response, exchanged with PKCE.\n */\n responseType: 'id_token' | 'code id_token' | 'code';\n /** Which extra claims this provider can add, and the scope (or claims request) for each. */\n reveal: Partial<Record<OidcReveal, { scope?: string; claims?: string[] }>>;\n /** What the provider puts in the token whatever the person picks. Shown before sign-in. */\n alwaysIncluded: string;\n /** Extra authorization parameters, e.g. an account chooser. */\n extraParams?: Record<string, string>;\n /** Client IDs per platform. Desktop signs in through the web callback page, so it uses the web client. */\n clientIds: Partial<Record<OidcPlatform, string>>;\n}\n\nexport const TEST_ISSUER = /^https:\\/\\/[a-z0-9-]+(\\.[a-z0-9-]+)*\\.test$/;\nconst MICROSOFT_TENANT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n\nexport const OIDC_PROVIDERS: Record<Exclude<OidcProviderId, 'test'>, OidcProvider> = {\n google: {\n id: 'google', name: 'Google',\n issuer: 'https://accounts.google.com', issuerAliases: ['accounts.google.com'],\n discovery: 'https://accounts.google.com/.well-known/openid-configuration',\n authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',\n jwksUri: 'https://www.googleapis.com/oauth2/v3/certs',\n algs: ['RS256'], responseType: 'id_token',\n reveal: { email: { scope: 'email' }, profile: { scope: 'profile' } },\n alwaysIncluded: 'A Google account identifier.',\n extraParams: { prompt: 'select_account' },\n clientIds: { web: '', extension: '' },\n },\n microsoft: {\n // `common` takes work, school and personal accounts; the issuer names the account's tenant.\n id: 'microsoft', name: 'Microsoft',\n issuer: 'https://login.microsoftonline.com/{tenantid}/v2.0',\n discovery: 'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration',\n authorizationEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',\n jwksUri: 'https://login.microsoftonline.com/common/discovery/v2.0/keys',\n algs: ['RS256'], responseType: 'id_token',\n reveal: { email: { scope: 'email' }, profile: { scope: 'profile' } },\n alwaysIncluded: 'A Microsoft account identifier and the directory (tenant) the account belongs to.',\n extraParams: { prompt: 'select_account', response_mode: 'fragment' },\n clientIds: { web: '', extension: '' },\n },\n apple: {\n // Apple only returns the ID token in the fragment when no scope is asked\n // for; name and email need `form_post`, which a static page cannot read.\n id: 'apple', name: 'Apple',\n issuer: 'https://appleid.apple.com',\n discovery: 'https://appleid.apple.com/.well-known/openid-configuration',\n authorizationEndpoint: 'https://appleid.apple.com/auth/authorize',\n jwksUri: 'https://appleid.apple.com/auth/keys',\n algs: ['RS256'], responseType: 'code id_token',\n reveal: {},\n alwaysIncluded: 'An Apple ID identifier that only Ghostly sees. Apple cannot add your email here without a Ghostly server.',\n extraParams: { response_mode: 'fragment' },\n clientIds: { web: '', extension: '' },\n },\n gitlab: {\n id: 'gitlab', name: 'GitLab',\n issuer: 'https://gitlab.com',\n discovery: 'https://gitlab.com/.well-known/openid-configuration',\n authorizationEndpoint: 'https://gitlab.com/oauth/authorize',\n tokenEndpoint: 'https://gitlab.com/oauth/token',\n revocationEndpoint: 'https://gitlab.com/oauth/revoke',\n jwksUri: 'https://gitlab.com/oauth/discovery/keys',\n algs: ['RS256'], responseType: 'code',\n reveal: { email: { scope: 'email' }, profile: { scope: 'profile' } },\n alwaysIncluded: 'A GitLab.com user ID and the groups you belong to directly: GitLab adds them to every ID token.',\n clientIds: { web: '', extension: '' },\n },\n twitch: {\n // Twitch's email claim needs `user:read:email`, an API scope: not asked for.\n id: 'twitch', name: 'Twitch',\n issuer: 'https://id.twitch.tv/oauth2',\n discovery: 'https://id.twitch.tv/oauth2/.well-known/openid-configuration',\n authorizationEndpoint: 'https://id.twitch.tv/oauth2/authorize',\n jwksUri: 'https://id.twitch.tv/oauth2/keys',\n algs: ['RS256'], responseType: 'id_token',\n reveal: { profile: { claims: ['preferred_username'] } },\n alwaysIncluded: 'A Twitch user ID.',\n extraParams: { force_verify: 'true' },\n clientIds: { web: '', extension: '' },\n },\n};\n\n/**\n * The local issuer the e2e suite runs (e2e/support/oidcIssuer.ts). It exists\n * only in a build made with VITE_OIDC_TEST_ISSUER, and only on a `.test` host\n * (a name reserved for testing, never a real issuer); a release build has no\n * such provider, so a token from it is refused like any unknown issuer.\n */\nexport function testProvider(issuer: string): OidcProvider {\n if (!TEST_ISSUER.test(issuer)) throw new Error('A test issuer lives on a .test host');\n const base = issuer;\n return {\n id: 'test', name: 'Test issuer', issuer: base,\n discovery: `${base}/.well-known/openid-configuration`,\n authorizationEndpoint: `${base}/authorize`,\n jwksUri: `${base}/jwks`,\n algs: ['RS256', 'ES256'], responseType: 'id_token',\n reveal: { email: { scope: 'email' }, profile: { scope: 'profile' } },\n alwaysIncluded: 'A test account identifier.',\n clientIds: { web: 'ghostly-e2e', extension: 'ghostly-e2e', desktop: 'ghostly-e2e' },\n };\n}\n\nfunction configuredTestIssuer(): string | undefined {\n try {\n const value = import.meta.env?.VITE_OIDC_TEST_ISSUER as string | undefined;\n return value && TEST_ISSUER.test(value) ? value : undefined;\n } catch { return undefined; }\n}\n\n/** Every provider this build knows, configured or not. */\nexport function oidcProviders(testIssuer = configuredTestIssuer()): OidcProvider[] {\n return [...Object.values(OIDC_PROVIDERS), ...(testIssuer ? [testProvider(testIssuer)] : [])];\n}\n\nexport function oidcProvider(id: string, testIssuer = configuredTestIssuer()): OidcProvider | undefined {\n return oidcProviders(testIssuer).find(p => p.id === id);\n}\n\n/** Every client ID this provider has, on any platform: a contact may use another platform than the prover. */\nexport function audiences(provider: OidcProvider): string[] {\n return [...new Set(Object.values(provider.clientIds).filter((id): id is string => !!id))];\n}\n\nexport function clientIdFor(provider: OidcProvider, platform: OidcPlatform): string {\n return provider.clientIds[platform] || provider.clientIds.web || '';\n}\n\n/** A provider is offered only once its client ID for this platform exists. */\nexport function isConfigured(provider: OidcProvider, platform: OidcPlatform): boolean {\n return !!clientIdFor(provider, platform);\n}\n\n/** The exact issuer a token from this provider must carry, or undefined when its claims do not name one. */\nexport function issuerFor(provider: OidcProvider, claims: { tid?: unknown }): string[] {\n if (provider.id !== 'microsoft') return [provider.issuer, ...(provider.issuerAliases ?? [])];\n return typeof claims.tid === 'string' && MICROSOFT_TENANT.test(claims.tid) ? [provider.issuer.replace('{tenantid}', claims.tid)] : [];\n}\n\n/** The provider a proof's subject (an issuer, as `OidcProvider.issuer` spells it) names. */\nexport function providerForIssuer(issuer: string, list = oidcProviders()): OidcProvider | undefined {\n return list.find(p => p.issuer === issuer);\n}\n","/**\n * A provider's signing keys, fetched from its pinned JWKS URL with limits and\n * cached. A key ID that is not in the cached set triggers one early refetch\n * (providers rotate keys), rate-limited so that a stream of made-up key IDs\n * cannot turn a contact's app into a request generator.\n */\n\nexport interface Jwk {\n kty: string; kid?: string; use?: string; alg?: string; key_ops?: string[];\n n?: string; e?: string; crv?: string; x?: string; y?: string;\n /** Microsoft adds the issuer each key signs for. */\n issuer?: string;\n}\n/**\n * How keys are fetched. In `verify` this is the engine's `ctx.fetch` (HTTPS GET, no credentials, no\n * redirects, time-out, size cap); `cacheControl` is used when the fetcher reports it.\n */\nexport type JwksFetch = (url: string, options: { maxBytes: number }) => Promise<{ status: number; text: string; cacheControl?: string | null }>;\n\nexport const JWKS_MAX_BYTES = 64 * 1024;\nexport const JWKS_MAX_KEYS = 32;\nconst MIN_TTL_MS = 5 * 60_000;\nconst MAX_TTL_MS = 24 * 60 * 60_000;\nconst DEFAULT_TTL_MS = 60 * 60_000;\n/** A missing `kid` refetches at most this often per URL. */\nexport const REFETCH_INTERVAL_MS = 60_000;\n\ninterface Entry { keys: Jwk[]; expires: number; fetched: number }\n\nfunction allowedUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return u.protocol === 'https:' && !u.username && !u.password && !u.hash;\n } catch { return false; }\n}\n\nfunction ttl(cacheControl: string | null | undefined): number {\n const maxAge = /(?:^|[,\\s])max-age=(\\d+)/i.exec(cacheControl ?? '')?.[1];\n const ms = maxAge ? Number(maxAge) * 1000 : DEFAULT_TTL_MS;\n return Math.min(MAX_TTL_MS, Math.max(MIN_TTL_MS, ms));\n}\n\nfunction parseKeys(text: string): Jwk[] {\n if (text.length > JWKS_MAX_BYTES) throw new Error('Provider key set too large');\n let doc: { keys?: unknown };\n try { doc = JSON.parse(text) as { keys?: unknown }; } catch { throw new Error('Invalid provider key set'); }\n if (!doc || !Array.isArray(doc.keys) || doc.keys.length > JWKS_MAX_KEYS) throw new Error('Invalid provider key set');\n return doc.keys.filter((k): k is Jwk => !!k && typeof k === 'object' && typeof (k as Jwk).kty === 'string');\n}\n\nexport class JwksCache {\n private entries = new Map<string, Entry>();\n private inflight = new Map<string, Promise<Entry>>();\n constructor(private options: { now?: () => number } = {}) {}\n private now() { return (this.options.now ?? Date.now)(); }\n\n private load(url: string, fetcher: JwksFetch): Promise<Entry> {\n const running = this.inflight.get(url);\n if (running) return running;\n const task = (async () => {\n if (!allowedUrl(url)) throw new Error('Provider key URL refused');\n const response = await fetcher(url, { maxBytes: JWKS_MAX_BYTES });\n if (response.status !== 200) throw new Error(`Provider keys unavailable (${response.status})`);\n const keys = parseKeys(response.text);\n const fetched = this.now();\n const entry = { keys, fetched, expires: fetched + ttl(response.cacheControl) };\n this.entries.set(url, entry);\n return entry;\n })();\n this.inflight.set(url, task);\n return task.finally(() => this.inflight.delete(url));\n }\n\n /** The key with this `kid`, refetching once when it is unknown (rotation) or the cache expired. */\n async key(url: string, kid: string, fetcher: JwksFetch): Promise<Jwk | undefined> {\n let entry = this.entries.get(url);\n if (!entry || entry.expires <= this.now()) entry = await this.load(url, fetcher);\n let found = entry.keys.find(k => k.kid === kid);\n if (!found && this.now() - entry.fetched >= REFETCH_INTERVAL_MS) {\n entry = await this.load(url, fetcher);\n found = entry.keys.find(k => k.kid === kid);\n }\n return found;\n }\n\n clear(): void { this.entries.clear(); }\n}\n\n/** One cache per app: every contact's proofs share the providers' keys. */\nexport const sharedJwksCache = new JwksCache();\n","import { fromBase64Url, toBase64Url, utf8Decode, utf8Encode } from '@ghostly/core';\nimport { audiences, issuerFor, type OidcAlg, type OidcProvider, type OidcProviderId } from './providers';\nimport { sharedJwksCache, type Jwk, type JwksCache, type JwksFetch } from './jwks';\n\n/**\n * Checks an ID token the way a contact must: the provider's signature under\n * a key from its own JWKS, the pinned `alg`, the issuer, `aud` = one of\n * Ghostly's client IDs for that provider, the time window, and `nonce` = the\n * hash of the exact Ghostly proof statement. Nothing here talks to a Ghostly\n * server, and nothing here trusts what the token says about its own key.\n */\n\n/** Seconds of clock difference accepted on `iat`, `nbf` and `exp`. */\nexport const CLOCK_TOLERANCE = 60;\n/** An ID token that claims to live longer than a day is not a sign-in result. */\nconst MAX_LIFETIME = 24 * 60 * 60;\n/** Room for Microsoft's larger tokens, well inside a peer frame. */\nexport const MAX_ID_TOKEN_LENGTH = 6144;\nconst SEGMENT = /^[A-Za-z0-9_-]+$/;\nconst REFUSED_HEADERS = ['crit', 'jku', 'jwk', 'x5u', 'x5c', 'zip', 'enc'];\n\nexport class OidcVerificationError extends Error {\n constructor(message: string) { super(message); this.name = 'OidcVerificationError'; }\n}\nconst refuse = (message: string): never => { throw new OidcVerificationError(message); };\n\n/** What a contact is shown: only claims the provider signed, bounded. */\nexport interface OidcIdentity {\n provider: OidcProviderId;\n issuer: string;\n subject: string;\n audience: string;\n issuedAt: number;\n expiresAt: number;\n email?: string;\n emailVerified?: boolean;\n name?: string;\n username?: string;\n /** Microsoft's directory (tenant) ID. */\n tenant?: string;\n}\n\nfunction segment(value: string): Uint8Array {\n if (!SEGMENT.test(value)) refuse('Malformed ID token');\n let bytes: Uint8Array;\n try { bytes = fromBase64Url(value); } catch { return refuse('Malformed ID token'); }\n if (toBase64Url(bytes) !== value) refuse('Malformed ID token');\n return bytes;\n}\n\nfunction json(value: string): Record<string, unknown> {\n let parsed: unknown;\n try { parsed = JSON.parse(utf8Decode(segment(value))); } catch (e) { if (e instanceof OidcVerificationError) throw e; return refuse('Malformed ID token'); }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) refuse('Malformed ID token');\n return parsed as Record<string, unknown>;\n}\n\nconst text = (value: unknown, max: number): string | undefined =>\n typeof value === 'string' && value.length > 0 && value.length <= max ? value : undefined;\n\n/** Splits and decodes without verifying anything. Only for display of a token already verified. */\nexport function decodeIdToken(token: string): { header: Record<string, unknown>; claims: Record<string, unknown>; signature: Uint8Array; signed: Uint8Array } {\n if (typeof token !== 'string' || token.length > MAX_ID_TOKEN_LENGTH) refuse('ID token missing or too large');\n const parts = token.split('.');\n if (parts.length !== 3) refuse('Not a signed ID token');\n return { header: json(parts[0]), claims: json(parts[1]), signature: segment(parts[2]), signed: utf8Encode(`${parts[0]}.${parts[1]}`) };\n}\n\nasync function verifySignature(alg: OidcAlg, jwk: Jwk, signed: Uint8Array, signature: Uint8Array): Promise<boolean> {\n if (jwk.use !== undefined && jwk.use !== 'sig') refuse('Provider key is not a signing key');\n if (jwk.alg !== undefined && jwk.alg !== alg) refuse('Provider key is for another algorithm');\n if (jwk.key_ops !== undefined && !(Array.isArray(jwk.key_ops) && jwk.key_ops.includes('verify'))) refuse('Provider key is not a signing key');\n let key: CryptoKey;\n if (alg === 'RS256') {\n if (jwk.kty !== 'RSA' || typeof jwk.n !== 'string' || typeof jwk.e !== 'string') return refuse('Provider key does not match the token algorithm');\n const modulus = segment(jwk.n);\n const bits = (modulus.length - (modulus[0] === 0 ? 1 : 0)) * 8;\n if (bits < 2048 || bits > 8192) refuse('Provider key size refused');\n key = await crypto.subtle.importKey('jwk', { kty: 'RSA', n: jwk.n, e: jwk.e, ext: true }, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['verify']);\n return crypto.subtle.verify('RSASSA-PKCS1-v1_5', key, signature as BufferSource, signed as BufferSource);\n }\n if (jwk.kty !== 'EC' || jwk.crv !== 'P-256' || typeof jwk.x !== 'string' || typeof jwk.y !== 'string') return refuse('Provider key does not match the token algorithm');\n if (signature.length !== 64) refuse('Invalid ID token signature');\n key = await crypto.subtle.importKey('jwk', { kty: 'EC', crv: 'P-256', x: jwk.x, y: jwk.y, ext: true }, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);\n return crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, signature as BufferSource, signed as BufferSource);\n}\n\n/** Whether `iss` is this provider's, as it words it (Microsoft: with the token's own tenant). */\nexport function issuerMatches(provider: OidcProvider, claims: Record<string, unknown>): boolean {\n const iss = text(claims.iss, 512);\n return !!iss && issuerFor(provider, claims).includes(iss);\n}\n\nexport interface VerifyOptions {\n provider: OidcProvider;\n /** The nonce the sign-in was asked for: an identity proof's `statement.id`. */\n nonce: string;\n /** How the provider's keys are fetched: `ctx.fetch` in a proof's `verify`. */\n fetch: JwksFetch;\n /** Unix seconds. */\n now?: number;\n jwks?: JwksCache;\n /**\n * `[from, to]` in Unix seconds: the token must have been issued in this window. For a proof, the\n * sign-in that followed the statement; the token's own `exp` (an hour) then does not matter, the\n * statement's validity does. Without it, the token must still be unexpired.\n */\n issuedWithin?: [number, number];\n}\n\nexport async function verifyIdToken(token: string, options: VerifyOptions): Promise<OidcIdentity> {\n const { provider, nonce } = options;\n const now = options.now ?? Math.floor(Date.now() / 1000);\n const { header, claims, signature, signed } = decodeIdToken(token);\n\n // The algorithm is the provider's, never the token's choice: `none`, HS256\n // (a public key used as an HMAC secret) and anything unpinned stop here.\n const alg = header.alg;\n if (typeof alg !== 'string' || !provider.algs.includes(alg as OidcAlg)) refuse(`Algorithm ${typeof alg === 'string' ? alg : 'missing'} is not accepted for ${provider.name}`);\n if (header.typ !== undefined && (typeof header.typ !== 'string' || header.typ.toUpperCase() !== 'JWT')) refuse('Not an ID token');\n for (const name of REFUSED_HEADERS) if (name in header) refuse('ID token header refused');\n const kid = text(header.kid, 256) ?? refuse('ID token names no provider key');\n\n if (!issuerMatches(provider, claims)) refuse(`Token was not issued by ${provider.name}`);\n const iss = claims.iss as string;\n\n const expected = audiences(provider);\n if (!expected.length) refuse(`${provider.name} is not configured in this Ghostly build`);\n const aud = typeof claims.aud === 'string' ? [claims.aud] : Array.isArray(claims.aud) && claims.aud.every(a => typeof a === 'string') ? claims.aud as string[] : [];\n const audience = aud.find(a => expected.includes(a)) ?? refuse('Token was issued to another app');\n if (aud.length > 1 && claims.azp !== audience) refuse('Token was issued to another app');\n if (claims.azp !== undefined && claims.azp !== audience) refuse('Token was issued to another app');\n\n const iat = claims.iat, exp = claims.exp, nbf = claims.nbf;\n if (typeof iat !== 'number' || typeof exp !== 'number' || !Number.isFinite(iat) || !Number.isFinite(exp)) return refuse('Token has no validity window');\n if (exp <= iat || exp - iat > MAX_LIFETIME) refuse('Token validity window refused');\n if (iat > now + CLOCK_TOLERANCE) refuse('Token is not valid yet');\n if (options.issuedWithin) {\n const [from, to] = options.issuedWithin;\n if (iat < from || iat > to) refuse('The sign-in does not belong to this proof: it happened at another time');\n } else {\n if (typeof nbf === 'number' && nbf > now + CLOCK_TOLERANCE) refuse('Token is not valid yet');\n if (exp + CLOCK_TOLERANCE < now) refuse('Token expired');\n }\n\n if (typeof claims.nonce !== 'string' || claims.nonce !== nonce) refuse('Token answers another proof request');\n const subject = text(claims.sub, 255) ?? refuse('Token names no account');\n\n const jwk = await (options.jwks ?? sharedJwksCache).key(provider.jwksUri, kid, options.fetch) ?? refuse(`${provider.name} does not publish the key that signed this token`);\n if (provider.id === 'microsoft' && jwk.issuer !== undefined && jwk.issuer !== provider.issuer && jwk.issuer !== iss) refuse('Provider key belongs to another issuer');\n let valid = false;\n try { valid = await verifySignature(alg as OidcAlg, jwk, signed, signature); }\n catch (e) { if (e instanceof OidcVerificationError) throw e; refuse('Invalid ID token signature'); }\n if (!valid) refuse('Invalid ID token signature');\n\n const verified = claims.email_verified === true || claims.email_verified === 'true' ? true : claims.email_verified === false || claims.email_verified === 'false' ? false : undefined;\n return {\n provider: provider.id, issuer: iss, subject, audience, issuedAt: Math.floor(iat), expiresAt: Math.floor(exp),\n email: text(claims.email, 320), emailVerified: text(claims.email, 320) ? verified : undefined,\n name: text(claims.name, 200), username: text(claims.preferred_username, 200) ?? text(claims.nickname, 200),\n tenant: provider.id === 'microsoft' ? text(claims.tid, 64) : undefined,\n };\n}\n","import { randomBytes, toBase64Url, utf8Encode } from '@ghostly/core';\nimport { clientIdFor, type OidcPlatform, type OidcProvider, type OidcReveal } from './providers';\nimport { decodeIdToken, issuerMatches } from './verify';\n\n/**\n * The sign-in half: send the person to the provider with the identity proof's\n * statement id as `nonce`, and take back only the ID token. The token's\n * signature is checked by the proof's `verify`, before saving and on every\n * contact's device; here only that it answers this request. Access and refresh tokens are never requested\n * where the provider allows it, and dropped (and revoked) where it does not.\n */\n\n/** How a platform shows the provider's page and hands back where it redirected. */\nexport interface OidcWindow {\n /** The redirect URI registered for this platform. */\n redirectUri: string;\n /** Extra state prefix the platform needs to route the answer back (desktop: its loopback port). */\n statePrefix?: string;\n /** Opens `url` and resolves with the full URL the provider redirected to. */\n authorize(url: string, signal: AbortSignal): Promise<string>;\n /** Releases the window or listener, answered or not. */\n close(): void;\n}\n\nexport interface OidcRequest {\n url: string;\n state: string;\n nonce: string;\n redirectUri: string;\n clientId: string;\n /** PKCE verifier, only for `code`. Held in memory for the one exchange. */\n verifier?: string;\n}\n\n/** Optional platform prefix (desktop `d.<port>.`, Android deep link `a.`), then 32 random bytes. */\nconst STATE = /^(?:d\\.\\d{1,5}\\.|a\\.)?[A-Za-z0-9_-]{43}$/;\n\nexport function scopeFor(provider: OidcProvider, reveal: OidcReveal[]): { scope: string; claims?: string } {\n const scopes = provider.id === 'apple' ? [] : ['openid'];\n const claims: string[] = [];\n for (const r of reveal) {\n const option = provider.reveal[r];\n if (!option) continue;\n if (option.scope) scopes.push(option.scope);\n if (option.claims) claims.push(...option.claims);\n }\n return { scope: scopes.join(' '), claims: claims.length ? JSON.stringify({ id_token: Object.fromEntries(claims.map(c => [c, null])) }) : undefined };\n}\n\nexport async function authorizationRequest(options: { provider: OidcProvider; platform: OidcPlatform; nonce: string; reveal: OidcReveal[]; redirectUri: string; statePrefix?: string }): Promise<OidcRequest> {\n const { provider } = options;\n const clientId = clientIdFor(provider, options.platform);\n if (!clientId) throw new Error(`${provider.name} sign-in is not set up in this Ghostly build yet.`);\n const { nonce } = options;\n if (!/^[A-Za-z0-9_-]{16,128}$/.test(nonce)) throw new Error('Invalid sign-in nonce');\n const state = `${options.statePrefix ?? ''}${toBase64Url(randomBytes(32))}`;\n const url = new URL(provider.authorizationEndpoint);\n const { scope, claims } = scopeFor(provider, options.reveal);\n const params: Record<string, string> = {\n client_id: clientId, redirect_uri: options.redirectUri, response_type: provider.responseType,\n nonce, state, ...(scope ? { scope } : {}), ...(claims ? { claims } : {}), ...provider.extraParams,\n };\n let verifier: string | undefined;\n if (provider.responseType === 'code') {\n verifier = toBase64Url(randomBytes(32));\n params.code_challenge = toBase64Url(new Uint8Array(await crypto.subtle.digest('SHA-256', utf8Encode(verifier) as BufferSource)));\n params.code_challenge_method = 'S256';\n }\n for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);\n return { url: url.toString(), state, nonce, redirectUri: options.redirectUri, clientId, verifier };\n}\n\n/** Parameters from the redirect: the fragment for implicit responses, the query for `code`. */\nexport function callbackParams(redirected: string, redirectUri: string): URLSearchParams {\n const url = new URL(redirected), expected = new URL(redirectUri);\n if (url.origin !== expected.origin || url.pathname !== expected.pathname) throw new Error('Sign-in returned to an unexpected address.');\n const params = new URLSearchParams(url.hash.slice(1));\n for (const [k, v] of new URLSearchParams(url.search)) if (!params.has(k)) params.set(k, v);\n return params;\n}\n\nconst PROVIDER_ERRORS: Record<string, string> = {\n access_denied: 'Sign-in was cancelled.',\n login_required: 'Sign in to your account and try again.',\n consent_required: 'Consent was not given.',\n interaction_required: 'The provider needs you to finish signing in.',\n unauthorized_client: 'This provider has not accepted Ghostly’s sign-in yet.',\n invalid_request: 'The provider refused the sign-in request.',\n};\n\nasync function exchangeCode(provider: OidcProvider, request: OidcRequest, code: string, doFetch: typeof fetch): Promise<string> {\n if (!provider.tokenEndpoint || !request.verifier) throw new Error('This provider cannot finish sign-in here.');\n const response = await doFetch(provider.tokenEndpoint, {\n method: 'POST', redirect: 'error', credentials: 'omit', referrerPolicy: 'no-referrer', cache: 'no-store',\n headers: { 'content-type': 'application/x-www-form-urlencoded', accept: 'application/json' },\n body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: request.redirectUri, client_id: request.clientId, code_verifier: request.verifier }),\n });\n const body = await response.json().catch(() => ({})) as { id_token?: unknown; access_token?: unknown; refresh_token?: unknown };\n // Only the ID token leaves this function. The others were never wanted:\n // revoke them without waiting, and let them go out of scope.\n for (const token of [body.refresh_token, body.access_token]) {\n if (typeof token === 'string' && provider.revocationEndpoint)\n void doFetch(provider.revocationEndpoint, { method: 'POST', redirect: 'error', credentials: 'omit', referrerPolicy: 'no-referrer',\n headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ token, client_id: request.clientId }) }).catch(() => {});\n }\n if (!response.ok || typeof body.id_token !== 'string') throw new Error(`${provider.name} did not return an ID token.`);\n return body.id_token;\n}\n\n/** Turns the redirect into the ID token: state, provider error, code exchange if needed, and that it answers this request. */\nexport async function completeSignIn(provider: OidcProvider, request: OidcRequest, redirected: string, options: { fetch?: typeof fetch } = {}): Promise<string> {\n const params = callbackParams(redirected, request.redirectUri);\n const state = params.get('state');\n if (!state || !STATE.test(state) || state !== request.state) throw new Error('Sign-in answer does not belong to this request.');\n const error = params.get('error');\n if (error) throw new Error(PROVIDER_ERRORS[error] ?? 'The provider refused the sign-in.');\n let idToken: string | null;\n if (provider.responseType === 'code') {\n const code = params.get('code');\n if (!code || code.length > 2048) throw new Error(`${provider.name} did not return a sign-in code.`);\n idToken = await exchangeCode(provider, request, code, options.fetch ?? fetch);\n } else idToken = params.get('id_token');\n // Apple's code needs a client secret to redeem, which Ghostly does not have: it is dropped with the rest.\n if (!idToken) throw new Error(`${provider.name} did not return an ID token.`);\n const { claims } = decodeIdToken(idToken);\n if (claims.nonce !== request.nonce || !issuerMatches(provider, claims)) throw new Error(`${provider.name} answered another sign-in request.`);\n return idToken;\n}\n\n/** Everything the prover does: sign in on this platform and return the ID token for this nonce. */\nexport async function signInForProof(options: {\n provider: OidcProvider; platform: OidcPlatform; nonce: string; reveal: OidcReveal[];\n window: OidcWindow; signal: AbortSignal; fetch?: typeof fetch;\n}): Promise<string> {\n const { window: w } = options;\n try {\n options.signal.throwIfAborted();\n const request = await authorizationRequest({ ...options, redirectUri: w.redirectUri, statePrefix: w.statePrefix });\n const redirected = await w.authorize(request.url, options.signal);\n options.signal.throwIfAborted();\n return await completeSignIn(options.provider, request, redirected, options);\n } finally { w.close(); }\n}\n","import { IDENTITY_CLOCK_SKEW, type IdentityStatement } from \"@ghostly/core\";\nimport type { IdentityProofProvider, IdentityPlatform, RedirectSigner } from \"../contract\";\nimport { getBrowserHost, type OidcHost } from \"../../host\";\nimport { audiences, isConfigured, oidcProviders, providerForIssuer, type OidcProvider, type OidcReveal } from \"../oidc/providers\";\nimport { signInForProof } from \"../oidc/flow\";\nimport { MAX_ID_TOKEN_LENGTH, verifyIdToken } from \"../oidc/verify\";\nimport { JwksCache, sharedJwksCache } from \"../oidc/jwks\";\n\n/**\n * Identity proofs through OpenID Connect: the person signs in with a provider (Google, Microsoft,\n * Apple, GitLab, Twitch) whose ID token carries the statement id as `nonce`. The token is the evidence;\n * a contact's app checks the provider's signature against its published keys, the issuer, Ghostly's\n * client ID, the nonce and when the sign-in happened. No Ghostly server is involved, and the proof is\n * only as good as the provider's word: it is attested, not a key the person holds.\n *\n * The statement's subject is the issuer the person picked; the account comes out of `verify`.\n * Configuration and flows per provider: ../oidc/, docs/OIDC-PROVIDERS.md, docs/wisps/3xx-oidc-proofs.md.\n */\nexport interface OidcEvidence { token: string }\n\n/** How long after the statement the sign-in may have happened: time to pick an account and approve. */\nconst SIGN_IN_WINDOW = 15 * 60;\nconst TOKEN = /^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/;\nconst SUB = /^[\\x21-\\x7e]{1,255}$/;\n\nfunction currentHost(): OidcHost | undefined {\n try { return getBrowserHost().oidc; } catch { return undefined; }\n}\n\nconst hostOf = (issuer: string) => { try { return new URL(issuer).host; } catch { return issuer; } };\n\nexport interface OidcIdentityOptions {\n /** The OIDC providers this build knows (tests pass configured ones). */\n providers?: () => OidcProvider[];\n /** The platform's sign-in window. */\n host?: () => OidcHost | undefined;\n /** Provider keys, cached across proofs. */\n jwks?: JwksCache;\n /** The UI-side fetch for GitLab's code exchange. */\n fetch?: typeof fetch;\n}\n\nexport function createOidcIdentityProvider(options: OidcIdentityOptions = {}): IdentityProofProvider<OidcEvidence> {\n const providers = options.providers ?? (() => oidcProviders());\n const host = options.host ?? currentHost;\n const configured = () => providers().filter(p => audiences(p).length > 0);\n\n const signer = (id: string, label: string, description: string, reveal: OidcReveal[]): RedirectSigner<OidcEvidence> => ({\n id, kind: \"redirect\", label, description,\n available: () => { const h = host(); return !!h && providers().some(p => isConfigured(p, h.platform)); },\n start(statement: IdentityStatement, ctx) {\n // Before any await: the popup and the extension's permission prompt need the click's user activation.\n const h = host();\n const opening = h?.open();\n return (async () => {\n if (!h || !opening) throw new Error(\"Signing in with a provider is not available here.\");\n const provider = providerForIssuer(statement.binding.subject, providers());\n if (!provider || !isConfigured(provider, h.platform)) { void opening.then(w => w.close(), () => {}); throw new Error(\"This provider is not set up in this Ghostly build.\"); }\n ctx.onProgress(`Sign in with ${provider.name} in the window that opened.`);\n const token = await signInForProof({\n provider, platform: h.platform, nonce: statement.id, window: await opening, signal: ctx.signal, fetch: options.fetch,\n // What the provider cannot put in its token is not asked for.\n reveal: reveal.filter(r => provider.reveal[r]),\n });\n return { token };\n })();\n },\n });\n\n return {\n id: \"oidc\",\n label: \"Account at a provider\",\n category: \"provider-attested\",\n summary: \"Sign in with Google, Microsoft, Apple…\",\n description: \"Sign in with Google, Microsoft, Apple, GitLab or Twitch: the provider vouches to Ghostly, with a signed token, that the account is yours.\",\n limits: \"Attested by the provider, not by a key you hold: your contact trusts that company.\",\n platforms: [\"web\", \"extension\", \"desktop\"] satisfies IdentityPlatform[],\n subject: {\n label: \"Provider\",\n help: \"Your contact sees your account’s identifier at that provider, plus what you choose to add. GitLab also adds the groups you belong to directly; Apple never gives Ghostly your email.\",\n get options() { return configured().map(p => ({ value: p.issuer, label: p.name })); },\n normalize(input: string) {\n const provider = providerForIssuer(input.trim(), configured());\n if (!provider) throw new Error(\"Choose a provider\");\n return provider.issuer;\n },\n short: subject => providerForIssuer(subject, providers())?.name ?? hostOf(subject),\n },\n // Providers rotate their signing keys within weeks (Google about every two); a contact can only\n // check a token while its key is still published, so a proof is short-lived.\n validity: { defaultDays: 7, maxDays: 14 },\n privacy: \"Your contact’s app downloads the provider’s public keys (for example from www.googleapis.com), so the provider sees that some app checked a sign-in, not whose. Your contact sees the account identifier and what you chose to share.\",\n signers: [\n signer(\"oidc-account\", \"Sign in: share the account only\", \"The token carries the provider’s identifier for your account, nothing else.\", []),\n signer(\"oidc-email\", \"Sign in: share the account and email\", \"Adds your email address, where the provider offers it.\", [\"email\"]),\n signer(\"oidc-profile\", \"Sign in: share the account, email and name\", \"Adds your email address and name or username, where the provider offers them.\", [\"email\", \"profile\"]),\n ],\n parseEvidence(raw) {\n const e = raw as OidcEvidence;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw) || Object.keys(raw).join(\",\") !== \"token\" ||\n typeof e.token !== \"string\" || e.token.length > MAX_ID_TOKEN_LENGTH || !TOKEN.test(e.token)) throw new Error(\"Invalid sign-in token\");\n return { token: e.token };\n },\n async verify(statement, { token }, ctx) {\n const provider = providerForIssuer(statement.binding.subject, providers());\n if (!provider) throw new Error(\"This app does not know this provider\");\n const id = await verifyIdToken(token, {\n provider, nonce: statement.id, now: ctx.now, jwks: options.jwks ?? sharedJwksCache,\n fetch: async (url, { maxBytes }) => { const r = await ctx.fetch(url, { maxBytes, signal: ctx.signal }); return { status: r.status, text: r.text }; },\n issuedWithin: [statement.binding.issuedAt - IDENTITY_CLOCK_SKEW, statement.binding.issuedAt + SIGN_IN_WINDOW],\n });\n if (!SUB.test(id.subject)) throw new Error(\"The provider’s account identifier cannot be shown\");\n const attester = hostOf(provider.issuer);\n const person = id.name ?? id.username;\n const name = id.email ? (person && `${person} · ${id.email}`.length <= 64 ? `${person} · ${id.email}` : id.email) : person;\n const url = id.username && /^[A-Za-z0-9_.-]{1,64}$/.test(id.username)\n ? provider.id === \"gitlab\" ? `https://gitlab.com/${id.username}` : provider.id === \"twitch\" ? `https://www.twitch.tv/${id.username}` : undefined\n : undefined;\n return {\n // Microsoft's issuer names the account's tenant; the others are fixed.\n subject: `${provider.id === \"microsoft\" ? id.issuer : provider.issuer}#${id.subject}`,\n attester,\n source: `ID token signed by ${attester}`,\n ...(name || url ? { display: {\n ...(name ? { name } : {}), ...(url ? { url } : {}), fetchedAt: id.issuedAt,\n source: `Signed by ${provider.name}${id.email ? id.emailVerified ? `; email verified by ${provider.name}` : `; email not verified by ${provider.name}` : \"\"}`,\n } } : {}),\n };\n },\n };\n}\n\nexport const oidc = createOidcIdentityProvider();\n","import {\n DidError, DID_DOCUMENT_MAX_BYTES, resolveDidDht, DID_FILE_MAX_BYTES, didWebDocumentUrl, didWebFileUrl, didWebHost, parseDid, parseDidDocument, staticDidDocument,\n type DidDocumentView, type DidMethod,\n} from \"@ghostly/core\";\nimport type { IdentityFetch } from \"./contract\";\nimport { assertPublicHost, chosenResolver, DomainCheckError, type DohResolverId } from \"./domain\";\n\n/**\n * Network side of DID proofs: resolving a DID to the keys its document lists, under the same limits as the\n * other providers. did:key and did:jwk need no network. did:web is one HTTPS GET of its did.json (bounded,\n * no redirect, after checking that the domain's addresses are public, like the domain proof). did:dht is a\n * signed Pkarr record read from a relay (`resolveDidDht`, WISP 310-did-dht).\n *\n * What a check reveals, and to whom:\n * - did:web: the DNS-over-HTTPS resolver learns the domain; the domain's web server, run by the person\n * being checked, sees this device's IP address and when.\n * - did:dht: the Pkarr relay learns which DID was looked up, from which IP address.\n */\n\nexport interface DidResolution {\n method: DidMethod;\n document: DidDocumentView;\n /** did:web: the host, and where its did.json was read. */\n host?: string;\n documentUrl?: string;\n /** did:dht: the relay that answered. */\n relay?: string;\n}\n\nexport interface DidResolveOptions {\n fetch: IdentityFetch;\n signal?: AbortSignal;\n /** Milliseconds. */\n now?: () => number;\n resolver?: DohResolverId;\n /** did:dht resolution; tests pass their own. */\n dht?: typeof resolveDidDht;\n}\n\n/** Resolutions are shared for this long: back-to-back checks and the add dialog's preview, never a stale key for long. */\nconst CACHE_MS = 30_000;\nconst cache = new Map<string, { until: number; result: Promise<DidResolution> }>();\n/** The last document seen per DID, without expiry, for the signer's instructions (which cannot wait). */\nconst seen = new Map<string, DidResolution>();\n\n/** Tests only. */\nexport function clearDidCache(): void { cache.clear(); seen.clear(); }\n\n/** What this app last resolved for `did`, if anything: the add dialog's instructions name its keys. */\nexport const lastDidResolution = (did: string): DidResolution | undefined => seen.get(did);\n\nasync function fetchDidWeb(did: string, url: string, maxBytes: number, options: DidResolveOptions): Promise<string | undefined> {\n const host = didWebHost(did);\n try {\n await assertPublicHost(host, { fetch: options.fetch, signal: options.signal, resolver: options.resolver ?? chosenResolver().id, now: options.now });\n } catch (e) { throw e instanceof DomainCheckError ? new DidError(e.message) : e; }\n let response: Awaited<ReturnType<IdentityFetch>>;\n try {\n response = await options.fetch(url, { headers: { accept: \"application/did+json, application/json\" }, maxBytes, signal: options.signal, redirect: \"error\" });\n } catch (e) {\n if (options.signal?.aborted) throw e;\n throw new DidError(/too large/i.test(String(e)) ? `${url} is too large`\n : `${host} could not be reached. It must serve ${url} over HTTPS, without a redirect, with the header Access-Control-Allow-Origin: *`);\n }\n if (response.status === 404 || response.status === 410) return undefined;\n if (response.status >= 300 && response.status < 400) throw new DidError(`${host} answered with a redirect; it must serve exactly ${url}`);\n if (response.status !== 200) throw new DidError(`${host} answered ${response.status} for ${url}`);\n try { return new TextDecoder(\"utf-8\", { fatal: true }).decode(response.bytes); } catch { throw new DidError(`${url} is not text`); }\n}\n\nasync function resolve(did: string, method: DidMethod, options: DidResolveOptions): Promise<DidResolution> {\n if (method === \"key\" || method === \"jwk\") return { method, document: staticDidDocument(did) };\n if (method === \"web\") {\n const documentUrl = didWebDocumentUrl(did);\n const text = await fetchDidWeb(did, documentUrl, DID_DOCUMENT_MAX_BYTES, options);\n if (text === undefined) throw new DidError(`No DID document at ${documentUrl}`);\n let raw: unknown;\n try { raw = JSON.parse(text); } catch { throw new DidError(`${documentUrl} is not JSON`); }\n return { method, document: parseDidDocument(did, raw), host: didWebHost(did), documentUrl };\n }\n const found = await (options.dht ?? resolveDidDht)(did, { fetch: options.fetch, signal: options.signal });\n if (found.metadata.deactivated) throw new DidError(\"This DID is deactivated: its owner retired it\");\n return { method, document: parseDidDocument(did, found.document), relay: found.relay };\n}\n\n/** A DID's document as far as Ghostly uses it. Concurrent and back-to-back resolutions share one lookup. */\nexport async function resolveDid(input: string, options: DidResolveOptions): Promise<DidResolution> {\n const { did, method } = parseDid(input);\n const now = options.now ?? Date.now;\n const hit = cache.get(did);\n if (hit && hit.until > now()) return hit.result;\n const entry = { until: now() + CACHE_MS, result: resolve(did, method, options) };\n cache.set(did, entry);\n if (cache.size > 64) cache.delete(cache.keys().next().value!);\n try {\n const result = await entry.result;\n seen.delete(did);\n seen.set(did, result);\n if (seen.size > 16) seen.delete(seen.keys().next().value!);\n return result;\n } catch (e) {\n if (cache.get(did) === entry) cache.delete(did);\n throw e;\n }\n}\n\n/** The statement file a did:web publishes for proof `proofId`; undefined when the server says it is not there. */\nexport async function fetchDidWebFile(did: string, proofId: string, options: DidResolveOptions): Promise<string | undefined> {\n return fetchDidWeb(did, didWebFileUrl(did, proofId), DID_FILE_MAX_BYTES, options);\n}\n","import {\n checkDidJws, checkDidSignature, DidError, DID_JWS_MAX_LENGTH, didGhostlyServiceEntry, didJwsAlg, didSignedMessages, didWebFile, didWebFileNames,\n didWebFileUrl, normalizeDid, parseDid, parseDidSignaturePaste, signingKeys, staticDidDocument, strictBase64Url, toBase64Url,\n type DidCurve, type DidKey, type IdentityStatement,\n} from \"@ghostly/core\";\nimport type { ExternalToolSigner, IdentityProofProvider, PublishSigner, SignerInstructions, SubjectPreview } from \"../contract\";\nimport { fetchDidWebFile, lastDidResolution, resolveDid, type DidResolution } from \"../did\";\nimport { ownDid } from \"../ownDid\";\nimport { boundedIdentityFetch } from \"../verify\";\n\n/**\n * A decentralized identifier (W3C DID Core), draft WISP 311-did. The subject is the DID; the statement is\n * vouched for by a key its document lists under `authentication` or `assertionMethod` (a JWS or a raw\n * signature pasted back, like SSH and PGP), or, for did:web, by publishing it where the DID document lives\n * (a file beside did.json, or a service entry in it), like the domain proof.\n *\n * Methods: did:key and did:jwk (the key is the identifier: checked offline), did:dht (a signed Pkarr\n * record), did:web (HTTPS). Every check resolves the DID again, so a key removed from a did:web or did:dht\n * document stops counting at the contact's next check.\n */\n\nexport type DidEvidence =\n | { method: \"jws\"; vm: string; jws: string }\n | { method: \"sig\"; vm: string; sig: string }\n | { method: \"file\" }\n | { method: \"service\" };\n\nconst VM = /^did:[a-z0-9]+:[\\x21-\\x7e]+#[\\x21-\\x7e]+$/;\nconst SIG = /^[A-Za-z0-9_-]{86}$/;\nconst exactKeys = (raw: unknown, keys: string) => !!raw && typeof raw === \"object\" && !Array.isArray(raw) && Object.keys(raw).sort().join(\",\") === keys;\nconst validVm = (vm: unknown): vm is string => typeof vm === \"string\" && vm.length <= 640 && VM.test(vm);\n\nexport { setOwnDidSource } from \"../ownDid\";\n\n/** The add dialog's checks run in the UI, with the same bounded fetch the engine uses. */\nconst uiFetch = boundedIdentityFetch({ online: () => globalThis.navigator?.onLine !== false });\n\n/** `#key-1`, shortened when a multibase fragment is long. */\nconst fragment = (id: string) => {\n const f = id.slice(id.indexOf(\"#\"));\n return f.length > 20 ? `${f.slice(0, 11)}…${f.slice(-4)}` : f;\n};\n\nfunction shortDid(did: string): string {\n if (did.length <= 30) return did;\n const { method, id } = parseDidSafe(did);\n if (method === \"web\") return `did:web:${id.split(\":\")[0]}${id.includes(\":\") ? \":…\" : \"\"}`;\n return `did:${method}:${id.slice(0, 8)}…${id.slice(-4)}`;\n}\nconst parseDidSafe = (did: string) => { try { return parseDid(did); } catch { return { did, method: did.split(\":\")[1] ?? \"\", id: did.split(\":\").slice(2).join(\":\") }; } };\n\n/** Keys the instructions can name before anything is fetched: the identifier's own, or what the preview resolved. */\nfunction knownKeys(did: string): DidKey[] {\n const { method } = parseDid(did);\n if (method === \"key\" || method === \"jwk\") return signingKeys(staticDidDocument(did));\n const seen = lastDidResolution(did);\n return seen ? signingKeys(seen.document) : [];\n}\n\nconst NOBLE: Record<DidCurve, { name: string; module: string }> = {\n Ed25519: { name: \"ed25519\", module: \"ed25519.js\" },\n secp256k1: { name: \"secp256k1\", module: \"secp256k1.js\" },\n \"P-256\": { name: \"p256\", module: \"nist.js\" },\n};\n\nfunction joseScript(statement: string, kid?: string): string {\n return [\n \"// npm install jose; then: node sign.mjs your-private-key.jwk.json\",\n \"import { readFileSync } from \\\"node:fs\\\";\",\n \"import { CompactSign, importJWK } from \\\"jose\\\";\",\n `const statement = ${JSON.stringify(statement)};`,\n \"const jwk = JSON.parse(readFileSync(process.argv[2], \\\"utf8\\\"));\",\n \"const alg = { Ed25519: \\\"EdDSA\\\", \\\"P-256\\\": \\\"ES256\\\" }[jwk.crv];\",\n \"const jws = await new CompactSign(new TextEncoder().encode(statement))\",\n ` .setProtectedHeader({ alg${kid ? `, kid: ${JSON.stringify(kid)}` : \"\"} })`,\n \" .sign(await importJWK(jwk, alg));\",\n \"console.log(jws);\",\n ].join(\"\\n\");\n}\n\nfunction nobleScript(statement: string, curve: DidCurve): string {\n const lib = NOBLE[curve];\n return [\n \"// npm install @noble/curves; then: node sign.mjs your-private-key.hex\",\n \"import { readFileSync } from \\\"node:fs\\\";\",\n `import { ${lib.name} } from \"@noble/curves/${lib.module}\";`,\n `const statement = new TextEncoder().encode(${JSON.stringify(statement)});`,\n \"const secret = Uint8Array.from(Buffer.from(readFileSync(process.argv[2], \\\"utf8\\\").trim(), \\\"hex\\\"));\",\n `console.log(Buffer.from(${lib.name}.sign(statement, secret)).toString(\"hex\"));`,\n ].join(\"\\n\");\n}\n\nfunction opensslCommand(statement: string, curve: DidCurve): string {\n const save = `printf '%s' '${statement}' > statement.txt`;\n return curve === \"Ed25519\"\n ? `${save} && openssl pkeyutl -sign -rawin -inkey key.pem -in statement.txt | xxd -p | tr -d '\\\\n'`\n : `${save} && openssl dgst -sha256 -sign key.pem statement.txt | xxd -p | tr -d '\\\\n'`;\n}\n\nconst signer: ExternalToolSigner<DidEvidence> = {\n id: \"sign\", kind: \"external-tool\", label: \"Sign with a key of the DID\",\n description: \"Sign the statement with a key your DID document lists for authentication or assertionMethod, and paste a JWS or the raw signature. The private key never enters Ghostly.\",\n instructions(statement): SignerInstructions {\n const did = statement.binding.subject;\n if (statement.text.includes(\"'\")) throw new DidError(\"Statement is not signable as plain text\");\n const keys = knownKeys(did);\n const key = keys.length === 1 ? keys[0] : undefined;\n const curve = key?.curve ?? keys[0]?.curve ?? \"Ed25519\";\n const which = keys.length ? keys.map(k => `${fragment(k.id)} (${k.curve})`).join(\", \") : \"a key listed under authentication or assertionMethod\";\n return {\n steps: [\n { text: `Sign this statement with the private key of ${which}. It is one line, with no newline at the end:`, copy: statement.text },\n ...(curve === \"secp256k1\" ? [] : [{ text: `With jose (Node.js 20 or later), a JWS: save this as sign.mjs and run it with your ${curve} private key as a JWK file.`, copy: joseScript(statement.text, key?.id) }]),\n { text: `With @noble/curves (Node.js), a raw signature: save this as sign.mjs and run it with your ${curve} private key in hex.`, copy: nobleScript(statement.text, curve) },\n { text: `Or with OpenSSL 3 and your ${curve} key in PEM:`, copy: opensslCommand(statement.text, curve) },\n { text: `Paste what it prints: a compact JWS (${didJwsAlg(curve)}, attached or detached), or the signature in hex or base64. If your DID lists several keys, you may write the key id first: #key-1 <signature>.` },\n ],\n paste: { label: \"JWS or signature\", placeholder: \"eyJhbGciOi… or the signature in hex\", multiline: true },\n };\n },\n async parse(pasted, statement) {\n const did = statement.binding.subject;\n const paste = parseDidSignaturePaste(pasted);\n const { document } = await resolveDid(did, { fetch: uiFetch });\n const messages = didSignedMessages(statement.text);\n if (paste.kind === \"jws\") return { method: \"jws\", vm: checkDidJws(paste.jws, messages, document).key.id, jws: paste.jws };\n const check = checkDidSignature(paste.signature, messages, document, paste.vm);\n return { method: \"sig\", vm: check.key.id, sig: toBase64Url(check.signature) };\n },\n};\n\nconst webOnly = (statement: IdentityStatement) => {\n const did = statement.binding.subject;\n if (parseDid(did).method !== \"web\") throw new DidError(\"Publishing works for did:web only: sign the statement instead\");\n return did;\n};\n\nconst fileSigner: PublishSigner<DidEvidence> = {\n id: \"file\", kind: \"publish\", label: \"Publish a file beside did.json\",\n description: \"did:web only: upload a small file next to your did.json. Contacts' apps download both from your server.\",\n instructions(statement) {\n const did = webOnly(statement);\n return { steps: [\n { text: \"Create this file on your web server, beside your did.json:\", copy: didWebFileUrl(did, statement.id) },\n { text: \"With exactly this content:\", copy: didWebFile(did, statement.text) },\n { text: \"Serve it over HTTPS at exactly that address, without any redirect, and with this header so browsers may read it (your did.json needs it too):\", copy: \"Access-Control-Allow-Origin: *\" },\n { text: \"Then press Check. The file stays public while the proof is in use; deleting it withdraws this proof from every contact at their next check.\" },\n ] };\n },\n evidence: () => ({ method: \"file\" }),\n};\n\nconst serviceSigner: PublishSigner<DidEvidence> = {\n id: \"service\", kind: \"publish\", label: \"Add a service to did.json\",\n description: \"did:web only: add one entry to the service list of your did.json. Contacts' apps read it there.\",\n instructions(statement) {\n const did = webOnly(statement);\n const seen = lastDidResolution(did);\n return { steps: [\n { text: `Add this entry to the \"service\" list of your did.json${seen?.documentUrl ? ` (${seen.documentUrl})` : \"\"}:`, copy: JSON.stringify(didGhostlyServiceEntry(did, statement.binding.key, statement.id), null, 2) },\n { text: \"Then press Check. Removing the entry withdraws this proof from every contact at their next check.\" },\n ] };\n },\n evidence: () => ({ method: \"service\" }),\n};\n\nfunction previewOf(r: DidResolution): SubjectPreview {\n const keys = signingKeys(r.document);\n const unusable = r.document.keys.length - keys.length + r.document.skipped;\n if (!keys.length && r.method !== \"web\") throw new DidError(\"This DID lists no Ed25519, secp256k1 or P-256 key for authentication or assertionMethod, so it cannot sign\");\n const facts: { label: string; value: string }[] = [{ label: \"Method\", value: `did:${r.method}${r.method === \"key\" || r.method === \"jwk\" ? \", the key is the identifier\" : \"\"}` }];\n if (r.host) facts.push({ label: \"Domain\", value: r.host });\n if (r.documentUrl) facts.push({ label: \"Document\", value: r.documentUrl });\n if (r.relay) facts.push({ label: \"Relay\", value: r.relay.replace(/^https:\\/\\//, \"\") });\n facts.push({ label: keys.length === 1 ? \"Key\" : \"Keys\", value: keys.length ? keys.map(k => `${fragment(k.id)} (${k.curve})`).join(\", \") : \"none that can sign: publish instead\" });\n if (unusable > 0) facts.push({ label: \"Not usable\", value: `${unusable} ${unusable === 1 ? \"key\" : \"keys\"}: another curve, or not for authentication or assertionMethod` });\n return { facts, signers: r.method === \"web\" ? [...(keys.length ? [\"sign\"] : []), \"file\", \"service\"] : [\"sign\"] };\n}\n\nexport const did: IdentityProofProvider<DidEvidence> = {\n id: \"did\",\n label: \"DID\",\n category: \"self-custodied\",\n summary: \"did:key, did:jwk, did:dht or did:web\",\n description: \"Proves you control a decentralized identifier (W3C DID): sign the statement with a key its DID document lists, or, for did:web, publish it beside your did.json.\",\n limits: \"Does not prove who controls the DID, only that whoever added the proof held one of its keys (or its website).\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n advanced: true,\n experimental: true,\n subject: {\n label: \"DID\", placeholder: \"did:key:z6Mk… or did:web:example.com\",\n help: \"did:key, did:jwk, did:dht or did:web. Contacts see the whole DID, and the key that signed.\",\n normalize(input) {\n const canonical = normalizeDid(input);\n if (canonical === ownDid()) throw new DidError(\"That is this profile's own Ghostly DID: your contacts already see your Ghostly identity. Add another DID.\");\n return canonical;\n },\n short: shortDid,\n preview: async (subject, { signal }) => previewOf(await resolveDid(subject, { fetch: uiFetch, signal })),\n },\n // A proven DID is already a URI: the profile's own did:dht may list it under alsoKnownAs.\n publicUri: subject => subject,\n validity: { defaultDays: 90, maxDays: 365 },\n // A did:web or did:dht document can drop a key, or the file be deleted: contacts can check again.\n recheck: { afterSeconds: 86_400 },\n privacy: \"did:key and did:jwk: nothing, checked on this device. did:web: the contact's app asks its DNS-over-HTTPS resolver for your domain and downloads your did.json, \" +\n \"so your web server sees the contact's IP address. did:dht: a Pkarr relay learns which DID was looked up.\",\n signers: [signer, fileSigner, serviceSigner],\n parseEvidence(raw) {\n const e = raw as Record<string, unknown>;\n if (exactKeys(raw, \"jws,method,vm\") && e.method === \"jws\" && typeof e.jws === \"string\" && e.jws.length <= DID_JWS_MAX_LENGTH && validVm(e.vm)) return { method: \"jws\", vm: e.vm, jws: e.jws };\n if (exactKeys(raw, \"method,sig,vm\") && e.method === \"sig\" && typeof e.sig === \"string\" && SIG.test(e.sig) && validVm(e.vm)) return { method: \"sig\", vm: e.vm, sig: e.sig };\n if (exactKeys(raw, \"method\") && (e.method === \"file\" || e.method === \"service\")) return { method: e.method };\n throw new Error(\"That is not DID proof evidence\");\n },\n async verify(statement, evidence, ctx) {\n const subject = statement.binding.subject;\n const { method } = parseDid(subject);\n if (normalizeDid(subject) !== subject) throw new DidError(\"The DID is not in canonical form\");\n if ((evidence.method === \"file\" || evidence.method === \"service\") && method !== \"web\") throw new DidError(\"Only a did:web can publish its proof\");\n const options = { fetch: ctx.fetch, signal: ctx.signal, now: () => ctx.now * 1000 };\n const { document, host } = await resolveDid(subject, options);\n const messages = didSignedMessages(statement.text);\n if (evidence.method === \"jws\" || evidence.method === \"sig\") {\n const check = evidence.method === \"jws\"\n ? checkDidJws(evidence.jws, messages, document)\n : checkDidSignature(strictBase64Url(evidence.sig), messages, document, evidence.vm);\n if (check.key.id !== evidence.vm) throw new DidError(\"Signed by another key than the proof names\");\n return { subject, source: `did:${method} · ${evidence.method === \"jws\" ? `${check.alg} JWS` : `${check.alg} signature`} by ${fragment(check.key.id)}` };\n }\n if (evidence.method === \"service\") {\n if (!document.services.some(s => s.proof === statement.id && s.key === statement.binding.key)) throw new DidError(\"The did.json has no Ghostly service for this proof: it was removed, or not added yet\");\n return { subject, source: `did:web · service in the did.json of ${host}` };\n }\n const text = await fetchDidWebFile(subject, statement.id, options);\n if (text === undefined) throw new DidError(`${didWebFileUrl(subject, statement.id)} was not found: it was deleted, or not uploaded yet`);\n if (!didWebFileNames(text, subject, statement.text)) throw new DidError(`${didWebFileUrl(subject, statement.id)} is not the statement of this proof`);\n return { subject, source: `did:web · statement file beside the did.json of ${host}` };\n },\n};\n","import {\n ATPROTO_DECLARED_SCOPE, ATPROTO_FULL_SCOPE, ATPROTO_PROOF_COLLECTION, ATPROTO_PROOF_SCOPE, atprotoProofRecord, atprotoProofRkey,\n type AtprotoDidDocument, type IdentityStatement,\n} from \"@ghostly/core\";\nimport type { IdentityFetch, IdentityPlatform } from \"../contract\";\nimport { resolveAtprotoDid, resolveAtprotoHandle, type AtprotoResolveOptions } from \"./resolve\";\n\n/**\n * The prover's half of AT Protocol proofs: AT Protocol OAuth (PAR + PKCE + DPoP, through the official\n * `@atproto/oauth-client`) against the person's own server, then one record written or deleted. Every\n * flow gets a fresh client whose stores live in memory only: the DPoP key (a non-extractable WebCrypto\n * key), the PKCE verifier and the tokens exist for this one flow, are never logged or persisted, and the\n * session is revoked (signOut) as soon as the record is written. Nothing is kept for later: removing\n * the proof asks the person again.\n *\n * The client is identified by the URL of its metadata document (no registration with anyone):\n * https://ghostly.tools/oauth/client-metadata.json, which is ATPROTO_CLIENT_METADATA verbatim\n * (apps/website/public/oauth/client-metadata.json; a unit test keeps the two equal). A web app served from\n * a loopback address (development, the e2e suite) uses AT Protocol's `http://localhost` development\n * client instead, which needs no document.\n */\n\nexport const ATPROTO_CLIENT_ID = \"https://ghostly.tools/oauth/client-metadata.json\";\n/** The extension's id in the Chrome Web Store: its chromiumapp.org redirect is registered. Unpacked builds have another id. */\nexport const EXTENSION_ID = \"nbedaagicniejlmfcncndfjcejaidbcf\";\nexport const ATPROTO_REDIRECTS = {\n /** The web app's static callback page (shared with OpenID Connect): the answer stays in the fragment. */\n web: \"https://app.ghostly.tools/oidc-callback.html\",\n /** chrome.identity.launchWebAuthFlow catches it without loading it. */\n extension: `https://${EXTENSION_ID}.chromiumapp.org/atproto`,\n /** The desktop app's one-shot listener on 127.0.0.1; the port is not part of the match. */\n desktop: \"http://127.0.0.1/oidc-callback\",\n} as const;\n\n/**\n * `native` because the desktop app's loopback redirect is only allowed for native clients; HTTPS\n * redirects (web, extension) are allowed for them too. No `refresh_token` grant: Ghostly never keeps\n * a session, so it never gets a refresh token to lose.\n */\nexport const ATPROTO_CLIENT_METADATA = {\n client_id: ATPROTO_CLIENT_ID,\n client_name: \"Ghostly\",\n client_uri: \"https://ghostly.tools\",\n logo_uri: \"https://ghostly.tools/icon-192.png\",\n application_type: \"native\",\n grant_types: [\"authorization_code\"],\n response_types: [\"code\"],\n scope: ATPROTO_DECLARED_SCOPE,\n token_endpoint_auth_method: \"none\",\n dpop_bound_access_tokens: true,\n redirect_uris: [ATPROTO_REDIRECTS.web, ATPROTO_REDIRECTS.extension, ATPROTO_REDIRECTS.desktop],\n} as const;\n\n/** AT Protocol's development client: no document, loopback redirects only, any port. */\nexport function loopbackClientMetadata(redirectUri: string) {\n const registered = new URL(redirectUri);\n registered.port = \"\";\n const id = `http://localhost?${new URLSearchParams({ redirect_uri: registered.href, scope: ATPROTO_DECLARED_SCOPE })}`;\n return {\n client_id: id, application_type: \"native\", grant_types: [\"authorization_code\"], response_types: [\"code\"], scope: ATPROTO_DECLARED_SCOPE,\n token_endpoint_auth_method: \"none\", dpop_bound_access_tokens: true, redirect_uris: [registered.href],\n } as const;\n}\n\nconst isLoopback = (url: string) => { try { const u = new URL(url); return u.protocol === \"http:\" && (u.hostname === \"127.0.0.1\" || u.hostname === \"[::1]\"); } catch { return false; } };\nconst withoutPort = (url: string) => { const u = new URL(url); u.port = \"\"; return u.href; };\n\n/**\n * The client for a redirect: the published one, or the development client on a loopback web app. A\n * loopback redirect is registered without its port (servers match any port), but the library checks\n * the exact address locally, so the one in use is added to the copy it gets; the server only ever sees\n * the client id and reads the registered addresses itself.\n */\nexport function clientMetadataFor(platform: IdentityPlatform, redirectUri: string) {\n const base = platform === \"web\" && isLoopback(redirectUri) ? loopbackClientMetadata(redirectUri) : ATPROTO_CLIENT_METADATA;\n const registered = base.redirect_uris as readonly string[];\n const matches = registered.includes(redirectUri) || (isLoopback(redirectUri) && registered.includes(withoutPort(redirectUri)));\n if (!matches) throw new Error(\"This Ghostly build cannot sign in to an AT Protocol server here (its return address is not registered).\");\n return { ...base, redirect_uris: [...new Set([redirectUri, ...registered])] };\n}\n\n/** How a platform shows the server's page and hands back where it redirected. */\nexport interface AtprotoWindow {\n /** The redirect URI for this platform, as used in the request (the desktop's carries its port). */\n redirectUri: string;\n /** Opens `url`; resolves with the full address the server redirected to, carrying `state`. */\n authorize(url: string, state: string, signal: AbortSignal): Promise<string>;\n /** Releases the window or listener, answered or not. */\n close(): void;\n}\n\nexport interface AtprotoHost {\n platform: IdentityPlatform;\n /** Call it straight from the click: the web app opens its popup before anything is awaited. */\n open(): Promise<AtprotoWindow>;\n}\n\n/** Which permission to ask for: only Ghostly's records, or the whole repository (older servers). */\nexport type AtprotoAccess = \"proof-records\" | \"full\";\nexport const scopeFor = (access: AtprotoAccess) => (access === \"full\" ? ATPROTO_FULL_SCOPE : ATPROTO_PROOF_SCOPE);\n\n/** The server refused the narrow permission: it predates granular scopes. */\nexport class AtprotoScopeError extends Error {}\n\n/** The account a handle (or a DID typed as is) names, with its document. */\nexport async function lookupAtprotoAccount(input: string, options: AtprotoResolveOptions & { normalize(input: string): string | null }): Promise<AtprotoDidDocument> {\n const value = input.trim();\n if (value.startsWith(\"did:\")) return resolveAtprotoDid(value, options);\n const handle = options.normalize(value);\n if (!handle) throw new Error(\"That is not a handle (like alice.bsky.social)\");\n const did = await resolveAtprotoHandle(handle, options);\n if (!did) throw new Error(`${handle} does not point to an AT Protocol account`);\n const doc = await resolveAtprotoDid(did, options);\n if (doc.handle !== handle) throw new Error(`${handle} points to an account that names another handle`);\n return doc;\n}\n\nexport interface AtprotoFlowOptions {\n platform: IdentityPlatform;\n window: AtprotoWindow;\n account: AtprotoDidDocument;\n access: AtprotoAccess;\n signal: AbortSignal;\n /** The resolution the library uses: the same bounded lookups as the verifier. */\n resolve: AtprotoResolveOptions;\n onProgress(message: string): void;\n /** The HTTP client for the OAuth requests and the record (the page's fetch). */\n fetch?: typeof fetch;\n}\n\nconst MESSAGES: Record<string, string> = {\n access_denied: \"You declined on your server. Nothing was published.\",\n login_required: \"Log in on your server and try again.\",\n consent_required: \"Your server needs your approval to continue.\",\n};\n\n/** The redirect's parameters: the fragment (Ghostly asks for fragment responses), then the query. */\nexport function callbackParams(redirected: string, redirectUri: string): URLSearchParams {\n const url = new URL(redirected), expected = new URL(redirectUri);\n if (url.origin !== expected.origin || url.pathname !== expected.pathname) throw new Error(\"Sign-in returned to an unexpected address.\");\n const params = new URLSearchParams(url.hash.slice(1));\n for (const [k, v] of new URLSearchParams(url.search)) if (!params.has(k)) params.set(k, v);\n return params;\n}\n\nconst pdsHost = (pds: string) => new URL(pds).host;\n\n/** Runs OAuth for `account`, hands `work` a session scoped to this flow, and always revokes it afterwards. */\nasync function withSession<T>(options: AtprotoFlowOptions, work: (session: { did: string; fetchHandler(path: string, init?: RequestInit): Promise<Response> }) => Promise<T>): Promise<T> {\n const { window: w, account, signal } = options;\n signal.throwIfAborted();\n const [{ OAuthClient }, { WebcryptoKey }] = await Promise.all([import(\"@atproto/oauth-client\"), import(\"@atproto/jwk-webcrypto\")]);\n const states = new Map<string, unknown>(), sessions = new Map<string, unknown>();\n let state: string | undefined;\n const memory = <V>(map: Map<string, V>, onSet?: (key: string) => void) => ({\n async get(key: string) { return map.get(key); },\n async set(key: string, value: V) { onSet?.(key); map.set(key, value); },\n async del(key: string) { map.delete(key); },\n });\n const client = new OAuthClient({\n responseMode: \"fragment\",\n clientMetadata: clientMetadataFor(options.platform, w.redirectUri) as never,\n stateStore: memory(states, key => { state = key; }) as never,\n sessionStore: memory(sessions) as never,\n runtimeImplementation: {\n createKey: algs => WebcryptoKey.generate(algs),\n getRandomValues: length => crypto.getRandomValues(new Uint8Array(length)),\n digest: async (data, { name }) => new Uint8Array(await crypto.subtle.digest(`SHA-${name.slice(3)}`, data)),\n },\n // The same lookups the verifier makes, bounded, through the person's DoH resolver: no third-party handle resolver.\n identityResolver: {\n async resolve(input: string) {\n const did = input.startsWith(\"did:\") ? input : await resolveAtprotoHandle(input, options.resolve);\n if (!did) throw new Error(\"The account could not be resolved\");\n const doc = await fetchDidJson(did, options.resolve);\n return { did, didDoc: doc.json, handle: doc.parsed.handle ?? \"handle.invalid\" } as never;\n },\n },\n fetch: options.fetch ?? globalThis.fetch.bind(globalThis),\n allowHttp: false,\n });\n let url: URL;\n try {\n url = await client.authorize(account.did, { scope: scopeFor(options.access), redirect_uri: w.redirectUri as never, signal });\n } catch (e) {\n const text = String((e as { error?: string })?.error ?? (e as Error)?.message ?? e);\n if (/invalid_scope/i.test(text) || /scope/i.test(String((e as { errorDescription?: string })?.errorDescription ?? \"\"))) throw new AtprotoScopeError(\n \"Your server does not offer a permission limited to Ghostly's records: it only offers full access to your account. Choose \\\"Your server, full access\\\" to continue anyway; Ghostly still only writes this one record.\");\n // eslint-disable-next-line preserve-caught-error -- A message people can act on; the library's error stays out of the UI (ES2020 has no Error.cause).\n throw new Error(`Your server (${pdsHost(account.pds)}) refused to start the sign-in.`);\n }\n if (!state) throw new Error(\"The sign-in could not start.\");\n options.onProgress(`Approve on ${pdsHost(account.pds)} in the window that opened…`);\n const redirected = await w.authorize(url.href, state, signal);\n signal.throwIfAborted();\n const params = callbackParams(redirected, w.redirectUri);\n const error = params.get(\"error\");\n if (error) throw new Error(MESSAGES[error] ?? \"Your server refused the sign-in.\");\n let session: Awaited<ReturnType<typeof client.callback>>[\"session\"];\n try { ({ session } = await client.callback(params, { redirect_uri: w.redirectUri as never })); }\n catch { throw new Error(\"The sign-in could not be completed. Nothing was published.\"); }\n try {\n if (session.did !== account.did) throw new Error(\"You logged in to another account than the one you typed. Nothing was published.\");\n return await work(session);\n } finally {\n // Revokes the tokens on the server; the DPoP key and verifier go out of scope with the client.\n await session.signOut().catch(() => {});\n sessions.clear(); states.clear();\n }\n}\n\n/** A DID's document, checked, and the JSON the library reads its services from. */\nasync function fetchDidJson(did: string, options: AtprotoResolveOptions): Promise<{ parsed: AtprotoDidDocument; json: unknown }> {\n let json: unknown;\n const recording: IdentityFetch = async (url, init) => {\n const response = await options.fetch(url, init);\n try { json = JSON.parse(response.text); } catch { /* resolveAtprotoDid says so */ }\n return response;\n };\n const parsed = await resolveAtprotoDid(did, { ...options, fetch: recording });\n return { parsed, json };\n}\n\nasync function xrpcError(response: Response): Promise<string> {\n const body = await response.json().catch(() => ({})) as { error?: string; message?: string };\n return `${body.error ?? response.status}${body.message ? `: ${body.message}` : \"\"}`;\n}\n\n/** Writes the proof record (rkey = the proof key) and returns once the server has it. */\nexport async function publishAtprotoProof(statement: IdentityStatement, options: AtprotoFlowOptions): Promise<void> {\n await withSession(options, async session => {\n options.onProgress(`Publishing the proof on ${pdsHost(options.account.pds)}…`);\n const response = await session.fetchHandler(\"/xrpc/com.atproto.repo.createRecord\", {\n method: \"POST\", headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ repo: session.did, collection: ATPROTO_PROOF_COLLECTION, rkey: atprotoProofRkey(statement.binding.key), record: atprotoProofRecord(statement, new Date()) }),\n });\n if (response.status === 403) throw new AtprotoScopeError(\"Your server did not let Ghostly write its record. Choose \\\"Your server, full access\\\" to try again.\");\n if (!response.ok) throw new Error(`Your server did not publish the record (${await xrpcError(response)}).`);\n });\n}\n\n/** Deletes the proof record. Deleting a record that is not there succeeds. */\nexport async function unpublishAtprotoProof(proofKey: string, options: AtprotoFlowOptions): Promise<void> {\n await withSession(options, async session => {\n options.onProgress(`Deleting the record on ${pdsHost(options.account.pds)}…`);\n const response = await session.fetchHandler(\"/xrpc/com.atproto.repo.deleteRecord\", {\n method: \"POST\", headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ repo: session.did, collection: ATPROTO_PROOF_COLLECTION, rkey: atprotoProofRkey(proofKey) }),\n });\n if (response.status === 403) throw new AtprotoScopeError(\"Your server did not let Ghostly delete its record.\");\n if (!response.ok) throw new Error(`Your server did not delete the record (${await xrpcError(response)}).`);\n });\n}\n","import {\n ATPROTO_PROOF_COLLECTION, AtprotoProofError, atprotoProofRkey, checkAtprotoProofRecord, isAtprotoDid, normalizeAtprotoHandle, shortAtprotoDid,\n verifyAtprotoRecordProof,\n} from \"@ghostly/core\";\nimport type { IdentityFetch, IdentityProofProvider, InAppSigner, SignerContext } from \"../contract\";\nimport { getBrowserHost } from \"../../host\";\nimport { boundedIdentityFetch } from \"../verify\";\nimport { chosenResolver } from \"../domain\";\nimport { lookupAtprotoAccount, publishAtprotoProof, unpublishAtprotoProof, type AtprotoAccess, type AtprotoHost, AtprotoScopeError } from \"../atproto/oauth\";\nimport { assertPublicServer, resolveAtprotoDid, verifiedAtprotoHandle, type AtprotoResolveOptions } from \"../atproto/resolve\";\n\n/**\n * Identity proofs for an AT Protocol account (Bluesky, or any PDS): the account publishes, in its own\n * repository, one record of the collection `tools.ghostly.proof` carrying the statement, keyed by the\n * proof key. Publishing takes an OAuth approval on the person's server that lets Ghostly create and\n * delete records of that collection only. A contact's app checks it without logging in: the DID\n * document (PLC directory or did:web) names the signing key and the server; the server's\n * `com.atproto.sync.getRecord` answer is a CAR whose commit must be signed by that key and whose tree\n * must lead to the record. The handle is shown only when it resolves back to the DID.\n *\n * The subject is the DID (it outlives handles). Evidence is empty: everything is fetched from the\n * account's own repository, so deleting the record (removing the proof here does) makes re-checks fail.\n * See docs/wisps/312-atproto.md and docs/lexicons/tools.ghostly.proof.json.\n */\nexport type AtprotoEvidence = Record<string, never>;\n\n/** The proof CAR: a commit, the tree path and one small record. Real repositories' paths are a few KiB. */\nexport const RECORD_PROOF_MAX_BYTES = 256 * 1024;\n\nfunction currentHost(): AtprotoHost | undefined {\n try { return getBrowserHost().atproto; } catch { return undefined; }\n}\n\nconst hostOf = (url: string) => new URL(url).host;\n\nexport interface AtprotoIdentityOptions {\n /** The platform's sign-in window. */\n host?: () => AtprotoHost | undefined;\n /** The UI side's network for looking the account up before signing in. */\n fetch?: IdentityFetch;\n /** The page's fetch for OAuth and the record (tests pass a fake server). */\n oauthFetch?: typeof fetch;\n /** The PLC directory (tests). */\n plc?: string;\n}\n\nexport function createAtprotoIdentityProvider(options: AtprotoIdentityOptions = {}): IdentityProofProvider<AtprotoEvidence> {\n const host = options.host ?? currentHost;\n const uiFetch = () => options.fetch ?? boundedIdentityFetch({ online: () => true });\n const resolveOptions = (fetch: IdentityFetch, signal?: AbortSignal): AtprotoResolveOptions =>\n ({ fetch, signal, resolver: chosenResolver().id, ...(options.plc ? { plc: options.plc } : {}) });\n\n /** Opens the window from the click, looks the account up, then runs OAuth for `work`; always closes the window. */\n function signIn<T>(input: string, access: AtprotoAccess, ctx: SignerContext, work: (flow: Parameters<typeof publishAtprotoProof>[1]) => Promise<T>): Promise<T> {\n // Before any await: the web app's popup needs the click's user activation.\n const h = host();\n const opening = h?.open();\n return (async () => {\n if (!h || !opening) throw new Error(\"Signing in to an AT Protocol server is not available here.\");\n const w = await opening;\n try {\n const resolve = resolveOptions(uiFetch(), ctx.signal);\n ctx.onProgress(`Looking up ${input.trim()}…`);\n const account = await lookupAtprotoAccount(input, { ...resolve, normalize: normalizeAtprotoHandle });\n ctx.signal.throwIfAborted();\n return await work({ platform: h.platform, window: w, account, access, signal: ctx.signal, resolve, onProgress: ctx.onProgress, fetch: options.oauthFetch });\n } finally { w.close(); }\n })();\n }\n\n const signer = (id: string, access: AtprotoAccess, label: string, description: string): InAppSigner<AtprotoEvidence> => ({\n id, kind: \"in-app\", label, description,\n action: access === \"full\" ? \"Continue on your server (full access)\" : \"Continue on your server\",\n available: () => !!host(),\n fields: [{\n name: \"handle\", label: \"Handle\", kind: \"text\", placeholder: \"alice.bsky.social\",\n help: \"Your Bluesky handle, your own domain if you use one, or your DID.\",\n }],\n run(ctx, work) {\n return signIn(ctx.values.handle ?? \"\", access, ctx, flow => work({\n subject: async () => flow.account.did,\n sign: async statement => { await publishAtprotoProof(statement, flow); return {}; },\n }));\n },\n });\n\n // Removing asks for a fresh approval; the server that refused the narrow permission once is asked for full access.\n const fullAccessServers = new Set<string>();\n\n return {\n id: \"atproto\",\n label: \"Bluesky / AT Protocol\",\n category: \"self-custodied\",\n summary: \"Publish a record on your Bluesky server\",\n description: \"Proves you control a Bluesky (AT Protocol) account: approve on your server, and Ghostly publishes one record in your account's repository. It is public, like the rest of your repository: anyone can see this account made a Ghostly proof, not who you share it with. Contacts check it without logging in.\",\n limits: \"Signed with your account's key, which your server usually holds. It proves control of the account, not who you are; the handle is shown only while it points back to the account.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Account (DID)\",\n placeholder: \"did:plc:…\",\n normalize(input: string) {\n const did = input.trim();\n if (!isAtprotoDid(did)) throw new Error(\"Not an AT Protocol account (did:plc or did:web)\");\n return did;\n },\n short: shortAtprotoDid,\n },\n // The account's AT URI, for a public DID document's alsoKnownAs (#247), when the person lists it there.\n publicUri: did => `at://${did}`,\n validity: { defaultDays: 90, maxDays: 365 },\n // The record can be deleted any time: contacts look again after an hour.\n recheck: { afterSeconds: 3600 },\n privacy: \"Your contact's app asks the PLC directory (or your did:web host) for your account's document, your server for the record, and its DNS-over-HTTPS resolver or your handle's website for your handle, so they learn this account was checked.\",\n signers: [\n signer(\"atproto-oauth\", \"proof-records\", \"Your server (Bluesky or another PDS)\",\n \"Opens your server's login page. Ghostly asks only to create and delete its own proof records: never to post, read your messages or edit your profile.\"),\n signer(\"atproto-oauth-full\", \"full\", \"Your server, full access (older servers)\",\n \"Only if your server says it cannot limit Ghostly's permission: it then asks for access to your whole account. Ghostly still only writes this one record, and gives the access back right away.\"),\n ],\n parseEvidence(raw) {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw) || Object.keys(raw).length) throw new Error(\"Invalid AT Protocol evidence\");\n return {};\n },\n async verify(statement, _evidence, ctx) {\n const did = statement.binding.subject;\n if (!isAtprotoDid(did)) throw new Error(\"Not an AT Protocol account\");\n const resolve = resolveOptions(ctx.fetch, ctx.signal);\n // The server comes from the DID document, never from the proof.\n const doc = await resolveAtprotoDid(did, resolve);\n await assertPublicServer(hostOf(doc.pds).replace(/:\\d+$/, \"\"), resolve);\n const rkey = atprotoProofRkey(statement.binding.key);\n const url = `${doc.pds}/xrpc/com.atproto.sync.getRecord?${new URLSearchParams({ did, collection: ATPROTO_PROOF_COLLECTION, rkey })}`;\n let response: Awaited<ReturnType<IdentityFetch>>;\n try { response = await ctx.fetch(url, { headers: { accept: \"application/vnd.ipld.car\" }, maxBytes: RECORD_PROOF_MAX_BYTES, signal: ctx.signal, redirect: \"error\" }); }\n // eslint-disable-next-line preserve-caught-error -- A message people can act on; the library's error stays out of the UI (ES2020 has no Error.cause).\n catch (e) { throw new Error(/too large/i.test(String(e)) ? \"The account's server sent a proof that is too large\" : `The account's server (${hostOf(doc.pds)}) could not be reached`); }\n if (response.status !== 200) {\n let code = \"\";\n try { code = String((JSON.parse(response.text) as { error?: unknown }).error ?? \"\"); } catch { /* not JSON */ }\n if (code === \"RecordNotFound\") throw new Error(\"The record is no longer in the account's repository\");\n if (/^Repo(NotFound|Deactivated|Takendown|Suspended)$/.test(code)) throw new Error(\"The account is no longer on its server\");\n throw new Error(`The account's server (${hostOf(doc.pds)}) did not send the record`);\n }\n let proof: ReturnType<typeof verifyAtprotoRecordProof>;\n try { proof = verifyAtprotoRecordProof(response.bytes, { did, key: doc.key, collection: ATPROTO_PROOF_COLLECTION, rkey }); }\n // eslint-disable-next-line preserve-caught-error -- A message people can act on; the library's error stays out of the UI (ES2020 has no Error.cause).\n catch (e) { throw new Error(e instanceof AtprotoProofError ? `The account's repository does not prove the record: ${e.message}` : \"The account's repository could not be read\"); }\n if (!proof.record) throw new Error(\"The record is no longer in the account's repository\");\n checkAtprotoProofRecord(proof.record.value, statement);\n const handle = await verifiedAtprotoHandle(doc, resolve);\n return {\n subject: did,\n source: `Record signed with the account's key, from ${hostOf(doc.pds)}`,\n display: {\n ...(handle ? { name: `@${handle}` } : {}),\n url: `https://bsky.app/profile/${did}`,\n source: handle ? \"Handle checked both ways: it points to this account\" : \"The account's handle does not point back to it, so it is not shown\",\n fetchedAt: ctx.now,\n },\n };\n },\n unpublish: {\n description: \"Also deletes the record on your server: your server asks you to approve.\",\n run(proof, ctx) {\n return signIn(proof.subject, \"proof-records\", ctx, async flow => {\n const access: AtprotoAccess = fullAccessServers.has(flow.account.pds) ? \"full\" : \"proof-records\";\n try { await unpublishAtprotoProof(proof.key, { ...flow, access }); }\n catch (e) {\n if (e instanceof AtprotoScopeError && access !== \"full\") {\n fullAccessServers.add(flow.account.pds);\n // eslint-disable-next-line preserve-caught-error -- A message people can act on; the library's error stays out of the UI (ES2020 has no Error.cause).\n throw new Error(\"Your server only offers full access to your account. Try again to approve that; Ghostly only deletes this one record.\");\n }\n throw e;\n }\n });\n },\n },\n };\n}\n\nexport const atproto = createAtprotoIdentityProvider();\n","import {\n homeserverWebEndpoint, IdentityCheckUnavailable, openRelayPayload, pubkyHomeserverOf, PUBKY_PROOF_MAX_BYTES, RELAY_PAYLOAD_MAX_BYTES,\n type HomeserverEndpoint,\n} from \"@ghostly/core\";\nimport type { AuthFlow, GrantAuthFlow, Session } from \"@synonymdev/pubky\";\nimport type { ApprovalRequest, IdentityFetch } from \"./contract\";\nimport { assertPublicHost, chosenResolver, type DohResolverId } from \"./domain\";\nimport { getBrowserHost, type PubkyCookieSession } from \"../host\";\n\n/**\n * Pubky, browser side: reading a key's records and files the way a contact's app does (through the proof\n * contract's bounded `ctx.fetch` only), and asking the person to approve one Pubky auth request in Pubky Ring or\n * Pubky Passport (https://github.com/pubky/pubky-passport/blob/main/docs/integration.md). See WISP 302.\n */\n\n// -- reading ---------------------------------------------------------------------------------------\n\n/** Pkarr relays the records are read from: Pubky's own, the ones the Pubky SDK uses. */\nexport const PUBKY_RELAYS: readonly string[] = [\"https://pkarr.pubky.org\", \"https://pkarr.pubky.app\"];\n\n/**\n * The newest DNS packet the relays hold for `key`, its signature checked against `key`. Every relay is asked; one\n * that is down, answers garbage or holds an older packet does not count. Throws when none has a valid one.\n */\nexport async function pubkyRecords(key: string, fetch: IdentityFetch, signal?: AbortSignal, relays: readonly string[] = PUBKY_RELAYS): Promise<Uint8Array> {\n const failed: unknown[] = [];\n const answers = await Promise.all(relays.map(async relay => {\n let r;\n try { r = await fetch(`${relay.replace(/\\/+$/, \"\")}/${key}`, { maxBytes: RELAY_PAYLOAD_MAX_BYTES, signal }); }\n catch (e) { failed.push(e); return undefined; }\n try { return r.status === 200 ? openRelayPayload(key, r.bytes) : undefined; } catch { return undefined; }\n }));\n const newest = answers.filter(a => !!a).sort((a, b) => (b.seq > a.seq ? 1 : b.seq < a.seq ? -1 : 0))[0];\n // No relay answered at all (offline, a time-out): say that, not that the key has no records.\n if (!newest && failed.length === relays.length && failed[0] instanceof Error) throw failed[0];\n if (!newest) throw new Error(\"This Pubky key has no records on the Pkarr relays\");\n return newest.dnsPacket;\n}\n\n/** Where `key`'s homeserver answers browsers: its `_pubky` record, then the homeserver's own HTTPS record. */\nexport async function pubkyHomeserver(key: string, fetch: IdentityFetch, signal?: AbortSignal, relays?: readonly string[]): Promise<{ homeserver: string; endpoint: HomeserverEndpoint }> {\n const homeserver = pubkyHomeserverOf(await pubkyRecords(key, fetch, signal, relays), key);\n if (!homeserver) throw new Error(\"This Pubky key names no homeserver\");\n const endpoint = homeserverWebEndpoint(await pubkyRecords(homeserver, fetch, signal, relays), homeserver);\n if (!endpoint) throw new Error(\"The homeserver of this Pubky key publishes no address a browser can reach\");\n return { homeserver, endpoint };\n}\n\n/**\n * A public file of `key` at `path`, from the homeserver its own records name: bounded, no redirects, the owner in the\n * `pubky-host` header the way the SDK addresses it. `undefined` when the homeserver says it is not there.\n *\n * The key's owner chooses that host, so it is contacted only on port 443 and only once its addresses are all public\n * (the name is resolved first through the chosen DNS-over-HTTPS resolver): a proof never makes a contact's app knock\n * on its own network. Failures on the way there are IdentityCheckUnavailable, which the contact is not told about.\n */\nexport async function readPubkyFile(key: string, path: `/pub/${string}`, fetch: IdentityFetch, signal?: AbortSignal, relays?: readonly string[], resolver: DohResolverId = chosenResolver().id): Promise<{ text: string | undefined; host: string }> {\n const { endpoint: { host, port } } = await pubkyHomeserver(key, fetch, signal, relays);\n if (port !== undefined) throw new IdentityCheckUnavailable(`The homeserver ${host} answers on port ${port}; only port 443 is contacted`);\n try { await assertPublicHost(host, { fetch, signal, resolver }); }\n catch (e) { throw new IdentityCheckUnavailable(`The homeserver ${host} was not contacted: ${e instanceof Error ? e.message : String(e)}`); }\n let r: Awaited<ReturnType<IdentityFetch>>;\n try { r = await fetch(`https://${host}${path}`, { headers: { \"pubky-host\": key }, maxBytes: PUBKY_PROOF_MAX_BYTES, signal }); }\n catch (e) {\n // A file over the cap is the owner's doing, and says nothing about this side's network.\n if (/too large/i.test(String(e))) throw e;\n throw new IdentityCheckUnavailable(`The homeserver ${host} could not be reached`);\n }\n if (r.status === 404) return { text: undefined, host };\n if (r.status !== 200) throw new IdentityCheckUnavailable(`The homeserver ${host} answered ${r.status}`);\n return { text: r.text, host };\n}\n\n// -- approving -------------------------------------------------------------------------------------\n\n/** The app identifier Pubky Ring and Passport list the grant under. */\nexport const PUBKY_CLIENT_ID = \"ghostly.tools\";\nexport const PASSPORT_ORIGIN = \"https://passport.pubky.app\";\n/** How long one request waits: Pubky's relay keeps a message about five minutes. */\nconst APPROVAL_TIMEOUT = 5 * 60_000;\nconst POLL_EVERY = 1_000;\n\n/**\n * Passport's authorize page for a request. The authorization URL carries the relay secret: it goes in the fragment\n * (never sent to a server), encoded exactly once, and is never logged.\n */\nexport const passportUrl = (authorizationUrl: string) => `${PASSPORT_ORIGIN}/authorize#d=${encodeURIComponent(authorizationUrl)}`;\n\n/** Messages from the SDK may quote the request: nothing that looks like one reaches the UI or a log. */\nconst redact = (text: string) => text.replace(/pubkyauth:\\/\\/\\S*/gi, \"(request)\").replace(/#d=\\S*/g, \"#d=(request)\");\nconst failure = (what: string, e: unknown) => new Error(`${what}${e instanceof Error && e.message ? `: ${redact(e.message)}` : \"\"}`);\n\n/** An approved Pubky session, only as long as `work` runs: the key, and writes inside the one folder granted. */\nexport interface PubkyApprovedSession {\n key: string;\n put(path: `/pub/${string}`, text: string): Promise<void>;\n delete(path: `/pub/${string}`): Promise<void>;\n}\n\nexport interface PubkyApprovalOptions {\n /** The one capability asked for, `/pub/…/:w`. A session granted anything else is refused. */\n capability: `/pub/${string}/:w`;\n signal: AbortSignal;\n onApproval(request: ApprovalRequest | null): void;\n onProgress(message: string): void;\n /** Where Passport opens. The desktop app hands it to the system browser; elsewhere a popup from the click. */\n openPassport?: (url: string) => Window | null | void;\n /** Tests: the SDK's relay, and a shorter wait. */\n relay?: string;\n timeoutMs?: number;\n}\n\n/**\n * Opens a Passport window from the click that calls it. It must run synchronously in the click handler, before\n * anything is awaited, or the browser blocks the popup; the desktop app opens the system browser instead.\n */\nexport function openPassportWindow(url: string): Window | null | void {\n let host: ReturnType<typeof getBrowserHost> | undefined;\n try { host = getBrowserHost(); } catch { host = undefined; }\n if (host?.openPubkyPassport) { void host.openPubkyPassport(url).catch(() => {}); return; }\n // Not `noopener`: the reference lets Ghostly close the window once either signer approved.\n return window.open(url, \"pubky-passport\", \"popup,width=520,height=760\");\n}\n\n/** What a request of either kind offers while it waits: one poll at a time, freed once no poll is in flight. */\ninterface PendingRequest { tryPollOnce(): Promise<Session | undefined>; free(): void }\n\n/** A 401 or 403 from the homeserver: the session it was given is not one it accepts. */\nconst refused = (e: unknown) => {\n const status = (e as { data?: { statusCode?: unknown } } | null)?.data?.statusCode;\n return status === 401 || status === 403;\n};\n\n/**\n * Polls every request until one of them is approved and resolves with that session and its request; a cancel, the\n * deadline or a failed poll ends the wait for all of them. Each request is freed once its last poll has settled\n * (never while one is in flight), and a session that lands after the end is signed out at once; `onSettled` runs\n * once all of that is done.\n */\nfunction firstApproval(requests: readonly PendingRequest[], signal: AbortSignal, timeoutMs: number, onSettled: () => void): Promise<{ session: Session; request: PendingRequest }> {\n return new Promise((resolve, reject) => {\n let over = false;\n let polling = requests.length;\n const wakers = new Set<() => void>();\n const end = (settle: () => void) => {\n if (over) return;\n over = true;\n clearTimeout(timer);\n signal.removeEventListener(\"abort\", cancel);\n for (const wake of wakers) wake();\n settle();\n };\n const cancel = () => end(() => reject(signal.reason));\n const timer = setTimeout(() => end(() => reject(new Error(\"Nobody approved the request in time. Start again for a new one.\"))), timeoutMs);\n if (signal.aborted) cancel(); else signal.addEventListener(\"abort\", cancel, { once: true });\n const pause = () => new Promise<void>(done => {\n const wake = () => { clearTimeout(t); wakers.delete(wake); done(); };\n const t = setTimeout(wake, POLL_EVERY);\n wakers.add(wake);\n });\n\n // The end does not wait for a poll in flight (one that got an approval is still redeeming it at the homeserver):\n // its request is freed when it settles, and a session it brings is signed out.\n const poll = async (request: PendingRequest) => {\n try {\n while (!over) {\n let session: Session | undefined;\n try { session = await request.tryPollOnce(); }\n catch (e) { end(() => reject(failure(\"The Pubky request failed\", e))); return; }\n if (session && over) { try { await session.signout(); } catch { /* best effort */ } finally { session.free(); } return; }\n if (session) { const approved = session; end(() => resolve({ session: approved, request })); return; }\n if (!over) await pause();\n }\n } finally {\n request.free();\n if (--polling === 0) onSettled();\n }\n };\n for (const request of requests) void poll(request);\n });\n}\n\n/**\n * A request of a cookie session (Ring's approval) to its homeserver: its `/session` or a file of its storage\n * (`/storage/<key>/pub/…`, or `/pub/…` where the homeserver does not address storage by path), sent with the\n * browser's credentials. A grant session's requests carry an `Authorization` header instead, and the relay's\n * and the Pkarr relays' are elsewhere: those stay the page's.\n */\nconst cookieSessionRequest = (request: Request) =>\n request.credentials === \"include\" && !request.headers.has(\"authorization\") && /^\\/(?:session$|storage\\/|pub\\/)/.test(new URL(request.url).pathname);\n\n/** Statuses a Response carries no body with. */\nconst NO_BODY = new Set([204, 205, 304]);\n\n/**\n * Sends the SDK's cookie-session requests through `transport` until the returned function runs. The SDK fetches with\n * the global fetch, so this wraps it; everything else goes on to the page's fetch as it was. The cookie the\n * homeserver sets stays in the transport: the page, and so the SDK, never sees it.\n */\nfunction routeCookieSession(transport: PubkyCookieSession): () => void {\n const pageFetch = globalThis.fetch;\n let closed = false;\n const routed = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n // A Request made from another takes its body over: whatever is made here is what goes on.\n const request = input instanceof Request && init === undefined ? input : new Request(input, init);\n // Once closed, a wrapper something else has wrapped since stays in place: it only passes requests on.\n if (closed || !cookieSessionRequest(request)) return pageFetch(request);\n request.signal.throwIfAborted();\n const body = request.body ? new Uint8Array(await request.arrayBuffer()) : null;\n let answer;\n try { answer = await transport.fetch({ url: request.url, method: request.method, headers: [...request.headers], body }); }\n catch (e) { throw Object.assign(new TypeError(e instanceof Error ? e.message : String(e)), { cause: e }); }\n const response = new Response(NO_BODY.has(answer.status) ? null : answer.body as BodyInit, { status: answer.status, headers: answer.headers });\n // The SDK parses the answer's URL, which a Response made here does not have: it is the request's (no redirects).\n Object.defineProperty(response, \"url\", { value: request.url });\n return response;\n };\n globalThis.fetch = routed;\n return () => {\n if (closed) return;\n closed = true;\n if (globalThis.fetch === routed) globalThis.fetch = pageFetch;\n transport.close();\n };\n}\n\n/** The host's way around a page that drops the homeserver's cookie, if it has one. */\nfunction cookieSessionTransport(): PubkyCookieSession | undefined {\n try { return getBrowserHost().pubkyCookieSession?.(); } catch { return undefined; }\n}\n\n/**\n * Starts one request for `capability` in each of the two forms Pubky signers read (a grant request for Passport, a\n * cookie request for Ring's QR code: see below), shows both on one screen, and waits for whichever is approved\n * first, then runs `work` with that session. The session lives only in this renderer's memory while `work` runs,\n * and is signed out afterwards; nothing about it is stored. An approval that arrives after the end is signed out.\n */\nexport async function withPubkyApproval<T>(options: PubkyApprovalOptions, work: (session: PubkyApprovedSession) => Promise<T>): Promise<T> {\n const { signal } = options;\n signal.throwIfAborted();\n const { AuthFlow, GrantAuthFlow, AuthFlowKind } = await import(\"@synonymdev/pubky\");\n signal.throwIfAborted();\n const relay = options.relay ?? null;\n let grant: GrantAuthFlow, cookie: AuthFlow;\n try {\n // The flow's proof-of-possession key stays in this flow's memory (not the SDK's delegated IndexedDB key).\n grant = GrantAuthFlow.start(options.capability, AuthFlowKind.signin(), { clientId: PUBKY_CLIENT_ID, relay, xCallback: { xSource: \"Ghostly\" } });\n } catch (e) { throw failure(\"Could not start a Pubky request\", e); }\n try {\n // Same capability and relay, its own secret. The SDK deprecates this flow, but it is the one Ring's store build reads.\n cookie = AuthFlow.start(options.capability, AuthFlowKind.signin(), relay, { xSource: \"Ghostly\" });\n } catch (e) { grant.free(); throw failure(\"Could not start a Pubky request\", e); }\n\n const opened: { window?: Window | null } = {};\n options.onApproval({\n open: {\n label: \"Approve in your browser (Pubky Passport)\",\n description: \"Opens Pubky Passport. No Passport identity yet? It offers “Continue with Google” and makes one.\",\n run: () => {\n const window = (options.openPassport ?? openPassportWindow)(passportUrl(grant.authorizationUrl));\n opened.window = window ?? null;\n // null: the browser refused the popup (undefined: it opened elsewhere, the system browser).\n if (window === null) options.onProgress(\"Your browser blocked the Passport window: allow pop-ups for Ghostly and press the button again, or scan the code with Pubky Ring.\");\n },\n },\n // Ring gets the cookie request (`pubkyauth://signin?…`), not the grant one: the Ring in the app stores (1.19)\n // predates pubky/pubky-ring#360 and answers \"Unrecognized format\" to a `signin_grant` link. Move this to\n // grant.authorizationUrl, and drop the cookie flow, once Ring's store build parses `signin_grant`.\n qr: { value: cookie.authorizationUrl, label: \"Or scan with Pubky Ring\" },\n notes: [\n \"An identity Passport makes with Google is recovered with Google plus Passport: both are needed, neither alone can.\",\n \"The code is your request: scan it yourself, and do not share it.\",\n ],\n });\n\n // Where the page would drop the cookie session's cookie (the desktop app), its requests go through the host, from\n // the first poll (an approval is redeemed inside one) until the last poll has settled and the work is over.\n const transport = cookieSessionTransport();\n const unroute = transport ? routeCookieSession(transport) : () => {};\n let settled!: () => void;\n const polled = new Promise<void>(done => { settled = done; });\n try {\n return await approveAndWork(options, work, { grant, cookie, opened, settled, cookieKept: !!transport });\n } finally {\n void polled.then(unroute);\n }\n}\n\n/**\n * The rest of `withPubkyApproval`, once both requests are shown: the wait, the check, and the work. `cookieKept`: the\n * host keeps a cookie session's cookie, so a refusal is not the page's cookie policy.\n */\nasync function approveAndWork<T>(options: PubkyApprovalOptions, work: (session: PubkyApprovedSession) => Promise<T>, { grant, cookie, opened, settled, cookieKept }: {\n grant: GrantAuthFlow; cookie: AuthFlow; opened: { window?: Window | null }; settled: () => void; cookieKept: boolean;\n}): Promise<T> {\n const { signal } = options;\n let session: Session, viaCookie: boolean;\n try {\n const first = await firstApproval([grant, cookie], signal, options.timeoutMs ?? APPROVAL_TIMEOUT, settled);\n session = first.session;\n viaCookie = first.request === cookie;\n } finally {\n options.onApproval(null);\n try { if (opened.window && !opened.window.closed) opened.window.close(); } catch { /* another origin's window: best effort */ }\n }\n\n options.onProgress(\"Approved. Checking what Pubky granted…\");\n let key: string;\n try {\n const info = session.info;\n const publicKey = info.publicKey;\n try {\n key = publicKey.z32();\n const granted = info.capabilities;\n if (granted.length !== 1 || granted[0] !== options.capability)\n throw new Error(\"The signer granted different permissions than Ghostly asked for (one proof folder). Nothing was written.\");\n } finally { publicKey.free(); info.free(); }\n } catch (e) {\n await session.signout().catch(() => {});\n session.free();\n throw e;\n }\n\n // A cookie session lives in the homeserver's cookie, a third-party cookie in a page. WebKit (Safari) and Vanadium drop it, so the\n // homeserver then refuses the session's writes: say that, not \"401\". The desktop app keeps it outside its WebView.\n const fail = (what: string) => (e: unknown) => {\n if (viaCookie && !cookieKept && refused(e))\n throw new Error(\"Pubky Ring approved, but this browser blocks your homeserver's sign-in cookie (Vanadium and Safari block cookies from other sites), so nothing was written. Approve with Pubky Passport instead, or use Firefox or the Ghostly desktop app.\");\n throw failure(what, e);\n };\n const approved = session;\n try {\n return await work({\n key,\n put: (path, text) => approved.storage.putText(path, text).catch(fail(\"Could not write to your homeserver\")),\n delete: path => approved.storage.delete(path).catch(fail(\"Could not delete from your homeserver\")),\n });\n } finally {\n // The grant is only needed for this: end it, and forget the session.\n try { await approved.signout(); } catch { /* the grant expires on its own */ }\n approved.free();\n }\n}\n","import {\n isPubkyKey, isPubkyProofFolder, newPubkyProofFolder, normalizePubkyKey, pubkyProofCapability, pubkyProofFile, pubkyProofPath,\n} from \"@ghostly/core\";\nimport type { IdentityPlatform, IdentityProofProvider, InAppSigner } from \"../contract\";\nimport { readPubkyFile, withPubkyApproval, type PubkyApprovalOptions } from \"../pubky\";\n\n/**\n * A Pubky identity (WISP 302, docs/wisps/302-pubky.md), proven by publication: Pubky Ring and Pubky Passport sign\n * only standard Pubky auth requests, so Ghostly asks, in ONE request either of them can approve, for write access to\n * one fresh folder of the key's homeserver (`/pub/ghostly.app/proofs/<random>/`), and writes the identity statement\n * there. Contacts' apps read the file back from the homeserver the key's own signed Pkarr records name. The session\n * is used once, in memory, and signed out; removing the proof needs a new approval to delete the file.\n */\nexport interface PubkyEvidence { folder: string }\n\nconst short = (key: string) => `pubky${key.slice(0, 4)}…${key.slice(-4)}`;\n\n/** Strict: exactly `{ folder }`, 64 hex characters. Nothing else, so no evidence can name a host or another path. */\nfunction parseEvidence(raw: unknown): PubkyEvidence {\n const e = raw as PubkyEvidence;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw) || Object.keys(raw).join(\",\") !== \"folder\" || !isPubkyProofFolder(e.folder))\n throw new Error(\"That is not Pubky proof evidence\");\n return { folder: e.folder };\n}\n\nexport interface PubkyIdentityOptions {\n /** Pkarr relays the verifier reads (tests). */\n relays?: readonly string[];\n /** The approval flow's knobs (tests: a local relay, a Passport stand-in). */\n approval?: Partial<Pick<PubkyApprovalOptions, \"relay\" | \"timeoutMs\" | \"openPassport\">>;\n}\n\nexport function createPubkyIdentityProvider(options: PubkyIdentityOptions = {}): IdentityProofProvider<PubkyEvidence> {\n const approve = <T>(capability: `/pub/${string}/:w`, ctx: Parameters<InAppSigner<PubkyEvidence>[\"run\"]>[0], work: Parameters<typeof withPubkyApproval<T>>[1]) => {\n if (!ctx.onApproval) throw new Error(\"This screen cannot show a Pubky request\");\n return withPubkyApproval({ ...options.approval, capability, signal: ctx.signal, onApproval: ctx.onApproval, onProgress: ctx.onProgress }, work);\n };\n\n const signer: InAppSigner<PubkyEvidence> = {\n id: \"pubky-auth\", kind: \"in-app\",\n label: \"Pubky Ring or Pubky Passport\",\n action: \"Continue\",\n description: \"One request: approve it in Pubky Passport in your browser, or scan it with Pubky Ring. Ghostly only asks to write one small file to a new folder of your homeserver.\",\n run(ctx, work) {\n const folder = newPubkyProofFolder();\n return approve(pubkyProofCapability(folder), ctx, async session => {\n let written: `/pub/${string}` | undefined;\n try {\n return await work({\n subject: async () => session.key,\n async sign(statement) {\n if (statement.binding.subject !== session.key) throw new Error(\"That is another Pubky identity\");\n ctx.onProgress(\"Writing the proof to your homeserver…\");\n const path = pubkyProofPath(folder, statement.id);\n await session.put(path, pubkyProofFile(statement));\n written = path;\n return { folder };\n },\n });\n } catch (e) {\n // Not saved (a failed check, a cancel): the file does not stay behind.\n if (written) await session.delete(written).catch(() => {});\n throw e;\n }\n });\n },\n };\n\n return {\n id: \"pubky\",\n label: \"Pubky\",\n category: \"self-custodied\",\n summary: \"Approve in Pubky Ring or Passport\",\n description: \"Proves you hold a Pubky key: approve one request in Pubky Ring or Pubky Passport, and Ghostly writes a small proof file to your homeserver. Contacts’ apps read it back through your key’s own records.\",\n limits: \"The file is on your homeserver: whoever runs it could write there too. It does not prove who holds the key.\",\n platforms: [\"web\", \"extension\", \"desktop\"] satisfies IdentityPlatform[],\n subject: {\n label: \"Pubky key\",\n placeholder: \"pubky…\",\n help: \"Comes from Pubky Ring or Passport when you approve.\",\n normalize: normalizePubkyKey,\n short,\n },\n validity: { defaultDays: 90, maxDays: 365 },\n // The file can be deleted, or the key can move to another homeserver, before the proof expires.\n recheck: { afterSeconds: 86_400 },\n privacy: \"Your contact’s app reads your key’s records from Pubky’s Pkarr relays (pkarr.pubky.org, pkarr.pubky.app), looks up your homeserver’s address through its DNS-over-HTTPS resolver (or the next one when that cannot be reached; it only contacts a public address, on port 443), and reads the proof file from your homeserver, which sees the contact’s IP address.\",\n signers: [signer],\n // Pubky's own URI form for a key, as its SDK writes resources (`pubky://<key>/…`).\n publicUri: key => `pubky://${key}`,\n parseEvidence,\n async verify(statement, { folder }, ctx) {\n const key = statement.binding.subject;\n if (!isPubkyKey(key)) throw new Error(\"That is not a Pubky key\");\n const { text, host } = await readPubkyFile(key, pubkyProofPath(folder, statement.id), ctx.fetch, ctx.signal, options.relays);\n if (text === undefined) throw new Error(\"The proof file is not on the homeserver\");\n if (text !== pubkyProofFile(statement)) throw new Error(\"The file on the homeserver is not this proof\");\n return { subject: key, source: `File on the homeserver ${host}, found through the key’s own Pkarr records` };\n },\n unpublish: {\n description: \"Also deletes the proof file on your homeserver: that takes one more approval in Pubky Ring or Passport, since Ghostly kept no access to it.\",\n async run({ id, subject, evidence }, ctx) {\n // Stored when the proof was made: parsed like any evidence, so a bad record never names another path.\n const { folder } = parseEvidence(evidence);\n const path = pubkyProofPath(folder, id);\n return approve(pubkyProofCapability(folder), ctx, async session => {\n if (session.key !== subject) throw new Error(\"That is another Pubky identity: approve with the one this proof is for\");\n ctx.onProgress(\"Deleting the proof file from your homeserver…\");\n await session.delete(path);\n });\n },\n },\n };\n}\n\nexport const pubky = createPubkyIdentityProvider();\n","import { ed25519 } from \"@noble/curves/ed25519.js\";\nimport { fromBase64Url, toBase64Url, utf8Decode, utf8Encode, type IdentityStatement } from \"@ghostly/core\";\nimport { testBuild } from \"../shared/features\";\nimport type { IdentityProofProvider } from \"./contract\";\n\n/**\n * Fake identity providers, for unit tests and e2e. No network, no real accounts. They are in the\n * registry only in a dev or e2e build (`testBuild`) that has `localStorage[\"ghostly-test-identities\"] === \"1\"`\n * (or a test passes them itself); a release build ignores the key.\n *\n * - fake-key self-custodied: an Ed25519 key held by a fake in-app signer, or signed \"outside\" and pasted.\n * - fake-account provider-attested: a fake issuer signs a token whose nonce is the statement id (like OIDC).\n * - fake-record self-custodied, published: a record the verifier fetches through `ctx.fetch` (like DNS).\n */\nexport const TEST_IDENTITIES_FLAG = \"ghostly-test-identities\";\nexport const testIdentitiesEnabled = () => { try { return testBuild() && globalThis.localStorage?.getItem(TEST_IDENTITIES_FLAG) === \"1\"; } catch { return false; } };\n\nconst HEX64 = /^[a-f0-9]{64}$/;\nconst hex = (b: Uint8Array) => Array.from(b, x => x.toString(16).padStart(2, \"0\")).join(\"\");\nconst unhex = (s: string) => Uint8Array.from(s.match(/../g)!, x => parseInt(x, 16));\nconst normalizeHexKey = (input: string) => { const v = input.trim().toLowerCase(); if (!HEX64.test(v)) throw new Error(\"Enter 64 hex characters\"); return v; };\nconst exactKeys = (raw: unknown, keys: string) => !!raw && typeof raw === \"object\" && !Array.isArray(raw) && Object.keys(raw).sort().join(\",\") === keys;\n\n/** The key behind the fake in-app signer, made on first use so a release start draws no key. Tests may replace it. */\nlet fakeSeed: Uint8Array | undefined;\nexport const fakeKeyring = { get seed() { return fakeSeed ??= ed25519.utils.randomSecretKey(); }, set seed(value: Uint8Array) { fakeSeed = value; } };\nexport const fakeKeySubject = (seed = fakeKeyring.seed) => hex(ed25519.getPublicKey(seed));\nexport const fakeKeySign = (statement: IdentityStatement, seed = fakeKeyring.seed) => ({ sig: toBase64Url(ed25519.sign(statement.bytes, seed)) });\n\nexport interface FakeKeyEvidence { sig: string }\nexport const fakeKey: IdentityProofProvider<FakeKeyEvidence> = {\n id: \"fake-key\", label: \"Test key\", category: \"self-custodied\",\n summary: \"Sign with the test key\", description: \"A test identity: an Ed25519 key held by this page. For tests only.\",\n platforms: [\"web\", \"desktop\"],\n subject: { label: \"Public key\", placeholder: \"64 hex characters\", normalize: normalizeHexKey, short: k => `${k.slice(0, 8)}…${k.slice(-4)}` },\n publicUri: k => `urn:ghostly-test:${k}`,\n validity: { defaultDays: 30, maxDays: 365 },\n privacy: \"Nothing: checked on this device.\",\n signers: [\n { id: \"fake-signer\", kind: \"in-app\", label: \"Test signer\", fields: [{ name: \"refuse\", label: \"Refuse\", kind: \"text\", optional: true }],\n run: async (ctx, work) => {\n ctx.signal.throwIfAborted();\n if (ctx.values.refuse) throw new Error(\"The test signer refused\");\n return work({ subject: async () => fakeKeySubject(), sign: async statement => fakeKeySign(statement) });\n } },\n { id: \"fake-tool\", kind: \"external-tool\", label: \"Sign outside Ghostly\",\n instructions: statement => ({ steps: [{ text: \"Sign this statement with your test key:\", copy: statement.text }], paste: { label: \"Signature (base64url)\" } }),\n parse: pasted => { const sig = pasted.trim(); if (!/^[A-Za-z0-9_-]{86}$/.test(sig)) throw new Error(\"That is not a test signature\"); return { sig }; } },\n ],\n parseEvidence(raw) {\n const e = raw as FakeKeyEvidence;\n if (!exactKeys(raw, \"sig\") || typeof e.sig !== \"string\" || !/^[A-Za-z0-9_-]{86}$/.test(e.sig)) throw new Error(\"Invalid test evidence\");\n return { sig: e.sig };\n },\n async verify(statement, evidence) {\n let ok: boolean;\n try { ok = ed25519.verify(fromBase64Url(evidence.sig), statement.bytes, unhex(statement.binding.subject)); } catch { ok = false; }\n if (!ok) throw new Error(\"Invalid test signature\");\n return { subject: statement.binding.subject, source: \"Test signature (Ed25519)\" };\n },\n};\n\n/** The fake issuer's key. Its public half is what fake-account trusts, like a provider's JWKS. */\nconst ISSUER_SEED = unhex(\"7a\".repeat(32));\nexport const FAKE_ISSUER = \"https://issuer.ghostly.test\";\nexport interface FakeAccountEvidence { token: string }\n/** What the fake login page signs: iss, sub, nonce (= statement id), iat. */\nexport function fakeAccountToken(statement: IdentityStatement, account = \"alice\", seed = ISSUER_SEED, iat = statement.binding.issuedAt): FakeAccountEvidence {\n const payload = toBase64Url(utf8Encode(JSON.stringify({ iss: FAKE_ISSUER, sub: account, nonce: statement.id, iat })));\n return { token: `${payload}.${toBase64Url(ed25519.sign(utf8Encode(payload), seed))}` };\n}\nexport const fakeAccount: IdentityProofProvider<FakeAccountEvidence> = {\n id: \"fake-account\", label: \"Test account\", category: \"provider-attested\",\n summary: \"Log in to the test issuer\", description: \"A test login provider that vouches for an account. For tests only.\",\n platforms: [\"web\", \"desktop\"],\n subject: { label: \"Provider\", options: [{ value: FAKE_ISSUER, label: \"Test issuer\" }],\n normalize: input => { if (input.trim() !== FAKE_ISSUER) throw new Error(\"Unknown test issuer\"); return FAKE_ISSUER; } },\n validity: { defaultDays: 30, maxDays: 90 },\n privacy: \"Nothing: the issuer's signature is checked on this device.\",\n signers: [{ id: \"fake-login\", kind: \"redirect\", label: \"Log in to the test issuer\",\n start: async (statement, ctx) => { ctx.signal.throwIfAborted(); return fakeAccountToken(statement, ctx.values.account || \"alice\"); } }],\n parseEvidence(raw) {\n const e = raw as FakeAccountEvidence;\n if (!exactKeys(raw, \"token\") || typeof e.token !== \"string\" || e.token.length > 2048 || !/^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]{86}$/.test(e.token)) throw new Error(\"Invalid test token\");\n return { token: e.token };\n },\n async verify(statement, { token }) {\n const [payload, sig] = token.split(\".\");\n if (!ed25519.verify(fromBase64Url(sig), utf8Encode(payload), ed25519.getPublicKey(ISSUER_SEED))) throw new Error(\"Not signed by the test issuer\");\n const claims = JSON.parse(utf8Decode(fromBase64Url(payload))) as { iss: string; sub: string; nonce: string };\n if (claims.iss !== statement.binding.subject) throw new Error(\"Token from another issuer\");\n if (claims.nonce !== statement.id) throw new Error(\"Token for another statement\");\n if (typeof claims.sub !== \"string\" || !/^[a-z0-9]{1,64}$/.test(claims.sub)) throw new Error(\"Invalid account\");\n return { subject: `${claims.iss}#${claims.sub}`, attester: \"issuer.ghostly.test\", source: \"Token signed by the test issuer\",\n display: { name: claims.sub, source: \"Test issuer profile\", fetchedAt: statement.binding.issuedAt } };\n },\n};\n\n/** fake-record's records: subject → published text. The verifier reads `https://records.ghostly.test/<subject>`. */\nexport const FAKE_RECORD_HOST = \"https://records.ghostly.test/\";\nexport const fakeRecordText = (statement: IdentityStatement) => `ghostly-proof=${statement.id}`;\nexport const fakeRecord: IdentityProofProvider<Record<string, never>> = {\n id: \"fake-record\", label: \"Test record\", category: \"self-custodied\",\n summary: \"Publish a test record\", description: \"A test record published where the verifier looks it up, like DNS. For tests only.\",\n platforms: [\"web\", \"desktop\"],\n subject: { label: \"Name\", placeholder: \"alice\", normalize: input => { const v = input.trim().toLowerCase(); if (!/^[a-z0-9-]{1,63}$/.test(v)) throw new Error(\"Letters, digits and dashes\"); return v; } },\n validity: { defaultDays: 30, maxDays: 365 },\n recheck: { afterSeconds: 3600 },\n privacy: \"The verifier asks records.ghostly.test for the record: that server learns the name was checked.\",\n signers: [{ id: \"fake-publish\", kind: \"publish\", label: \"Publish a record\",\n instructions: statement => ({ steps: [{ text: `Publish this record for ${statement.binding.subject}:`, copy: fakeRecordText(statement) }] }),\n evidence: () => ({}) }],\n parseEvidence(raw) { if (!exactKeys(raw, \"\")) throw new Error(\"Invalid test evidence\"); return {}; },\n async verify(statement, _evidence, ctx) {\n const response = await ctx.fetch(`${FAKE_RECORD_HOST}${statement.binding.subject}`, { maxBytes: 4096, signal: ctx.signal });\n if (response.status !== 200 || !response.text.split(\"\\n\").includes(fakeRecordText(statement))) throw new Error(\"The record is not published\");\n return { subject: statement.binding.subject, source: \"Record published at records.ghostly.test\" };\n },\n};\n\nexport const FAKE_IDENTITY_PROVIDERS: readonly IdentityProofProvider[] = [fakeKey, fakeAccount, fakeRecord];\n","import type { IdentityProofProvider } from \"./contract\";\nimport { bitcoin } from \"./providers/bitcoin\";\nimport { nostr } from \"./providers/nostr\";\nimport { domain } from \"./providers/domain\";\nimport { openpgp } from \"./providers/openpgp\";\nimport { ssh, sshGithub, sshGitlab } from \"./providers/ssh\";\nimport { oidc } from \"./providers/oidc\";\nimport { did } from \"./providers/did\";\nimport { atproto } from \"./providers/atproto\";\nimport { pubky } from \"./providers/pubky\";\nimport { FAKE_IDENTITY_PROVIDERS, testIdentitiesEnabled } from \"./testing\";\nimport { registeredIdentityProviders, reserveAdapterIds } from \"../plugins/registry\";\n\n/**\n * Every identity-proof provider built into Ghostly. Adding one is adding its module under `providers/`\n * and ONE line here; see PROOFS.md. The order is the order of the picker on the Identities page. A\n * provider written outside the app registers through `plugins/registry.ts` (the SDK) and comes after.\n */\nexport const IDENTITY_PROVIDERS: readonly IdentityProofProvider[] = [\n nostr,\n pubky,\n domain,\n openpgp,\n bitcoin,\n ssh, sshGithub, sshGitlab,\n oidc,\n did,\n atproto,\n];\n\nreserveAdapterIds(\"identity\", IDENTITY_PROVIDERS.map((p) => p.id));\n\nconst warned = new Set<string>();\n/** The built-in providers, then the ones plugins registered (a built-in's id wins), then the fakes when this browser asked for them (e2e). */\nexport function identityProviders(): readonly IdentityProofProvider[] {\n const external = registeredIdentityProviders().filter((p) => {\n const taken = IDENTITY_PROVIDERS.some((b) => b.id === p.id);\n if (taken && !warned.has(p.id)) { warned.add(p.id); console.error(`[ghostly] identity provider ${p.id} from a plugin is ignored: a built-in has that id`); }\n return !taken;\n });\n return [...IDENTITY_PROVIDERS, ...external, ...(testIdentitiesEnabled() ? FAKE_IDENTITY_PROVIDERS : [])];\n}\n\nexport function identityProvider(id: string, providers = identityProviders()): IdentityProofProvider | undefined {\n return providers.find(p => p.id === id);\n}\n"],"mappings":"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"}
|