{"version":3,"file":"seed-keys.mjs","names":[],"sources":["../src/seed-keys.ts"],"sourcesContent":["/**\n * Seed → recovery keys.\n *\n * A single canonical derivation so that:\n *   - wallet and any future re-implementation produce IDENTICAL keys for the\n *     same seed (a typo in one of the HKDF labels would make a backup\n *     unrecoverable — locking the labels in code prevents that),\n *   - the same seed on any device finds the same vault on the same server.\n *\n * Three keys, three labels:\n *\n *   askarPassKey      ─ used as Askar `keyMethod:'raw'` passKey to encrypt\n *                       the local SQLite store before export.\n *   vaultPassphrase   ─ the high-entropy passphrase fed to the S3 suite's\n *                       Argon2id KDF. Argon2id then produces the CEK that\n *                       encrypts the Askar-DB bytes under AES-256-GCM.\n *   vaultId           ─ deterministic 16-byte lookup ID, base64url-encoded.\n *                       The wallet sends `RetrieveVaultMessage(vaultId)` to\n *                       the backup server, which returns this user's vault.\n *\n * All three are derived with HKDF-SHA256 from the same input seed, with\n * domain-separated info strings. They are mutually independent: leaking one\n * does not reveal the others.\n *\n * Implementation note: this file imports only @noble/hashes (pure JS) and a\n * tiny local base64url codec. It deliberately does NOT route through the\n * crypto provider — that means it's safe to import from either the Node\n * entry or the React Native entry without forcing the wrong provider into\n * the bundle.\n */\nimport { hkdf } from '@noble/hashes/hkdf'\nimport { sha256 } from '@noble/hashes/sha2'\n\nimport { toBase64UrlPure } from './utils/base64url'\n\nexport interface BackupKeys {\n  /**\n   * 32-byte raw key for the Askar store, base64url-encoded. Pass to\n   * `Store.copyTo({ keyMethod: 'raw', passKey })`.\n   *\n   * The raw bytes are also exposed via `askarPassKeyBytes` for the rare\n   * Askar binding that wants a non-base64url shape.\n   */\n  askarPassKey: string\n  askarPassKeyBytes: Uint8Array\n\n  /**\n   * Passphrase fed to the outer vault encryption (S3 suite, Argon2id). 32\n   * bytes of HKDF output encoded as base64url. Treated as a string\n   * passphrase by `encryptWithPassphraseRn` / `VaultsApi.create`.\n   */\n  vaultPassphrase: string\n\n  /**\n   * Deterministic vault lookup ID. 16 bytes → 22 base64url chars. Use as\n   * `vaultId` in StoreVaultMessage / RetrieveVaultMessage so any device with\n   * the same seed locates the same vault on the server.\n   */\n  vaultId: string\n}\n\nconst LABEL_ASKAR_PASS = 'vaults/v1/askar-store-key'\nconst LABEL_VAULT_PASS = 'vaults/v1/vault-passphrase'\nconst LABEL_VAULT_ID = 'vaults/v1/vault-id'\n\nconst enc = new TextEncoder()\n\nfunction expand(seed: Uint8Array, label: string, length: number): Uint8Array {\n  // HKDF-SHA256 with empty salt — matches the WASM reference's `hkdfExpand`\n  // behaviour and the rn provider's behaviour. RFC 5869: salt absent ⇒ block\n  // of HashLen zero bytes is used internally; @noble accepts `undefined` to\n  // mean the same.\n  return hkdf(sha256, seed, undefined, enc.encode(label), length)\n}\n\n/**\n * Derive the three recovery keys from a wallet seed.\n *\n * @param seed Raw seed bytes (e.g. the entropy of a BIP-39 24-word phrase, 32\n *             bytes). Minimum 16 bytes accepted; 32+ recommended.\n */\nexport function deriveBackupKeysFromSeed(seed: Uint8Array): BackupKeys {\n  if (!(seed instanceof Uint8Array)) {\n    throw new TypeError('seed must be a Uint8Array')\n  }\n  if (seed.length < 16) {\n    throw new Error(`seed must be at least 16 bytes (got ${seed.length}); use BIP-39 entropy or 32 random bytes`)\n  }\n\n  const askarBytes = expand(seed, LABEL_ASKAR_PASS, 32)\n  const passBytes = expand(seed, LABEL_VAULT_PASS, 32)\n  const idBytes = expand(seed, LABEL_VAULT_ID, 16)\n\n  return {\n    askarPassKey: toBase64UrlPure(askarBytes),\n    askarPassKeyBytes: askarBytes,\n    vaultPassphrase: toBase64UrlPure(passBytes),\n    vaultId: toBase64UrlPure(idBytes),\n  }\n}\n\n/**\n * The HKDF labels used by `deriveBackupKeysFromSeed`. Exported as constants\n * so external test suites can pin against them.\n */\nexport const BackupKeyLabels = {\n  askarPassKey: LABEL_ASKAR_PASS,\n  vaultPassphrase: LABEL_VAULT_PASS,\n  vaultId: LABEL_VAULT_ID,\n} as const\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,MAAM,mBAAmB;AACzB,MAAM,mBAAmB;AACzB,MAAM,iBAAiB;AAEvB,MAAM,MAAM,IAAI,aAAa;AAE7B,SAAS,OAAO,MAAkB,OAAe,QAA4B;AAK3E,QAAO,KAAK,QAAQ,MAAM,QAAW,IAAI,OAAO,MAAM,EAAE,OAAO;;;;;;;;AASjE,SAAgB,yBAAyB,MAA8B;AACrE,KAAI,EAAE,gBAAgB,YACpB,OAAM,IAAI,UAAU,4BAA4B;AAElD,KAAI,KAAK,SAAS,GAChB,OAAM,IAAI,MAAM,uCAAuC,KAAK,OAAO,0CAA0C;CAG/G,MAAM,aAAa,OAAO,MAAM,kBAAkB,GAAG;CACrD,MAAM,YAAY,OAAO,MAAM,kBAAkB,GAAG;CACpD,MAAM,UAAU,OAAO,MAAM,gBAAgB,GAAG;AAEhD,QAAO;EACL,cAAc,gBAAgB,WAAW;EACzC,mBAAmB;EACnB,iBAAiB,gBAAgB,UAAU;EAC3C,SAAS,gBAAgB,QAAQ;EAClC;;;;;;AAOH,MAAa,kBAAkB;CAC7B,cAAc;CACd,iBAAiB;CACjB,SAAS;CACV"}