@waaskey/sdk 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON, AuthenticationResponseJSON } from '@simplewebauthn/browser';
1
+ import { PublicKeyCredentialRequestOptionsJSON, AuthenticationResponseJSON, PublicKeyCredentialCreationOptionsJSON } from '@simplewebauthn/browser';
2
2
 
3
3
  /** Thin typed HTTP client over fetch — the single place requests are issued. */
4
4
  declare class HttpClient {
@@ -270,8 +270,50 @@ interface DeviceReshareAssembleParams {
270
270
  wallet: unknown;
271
271
  /** One Feldman-commitments object per dealer (the broadcast set), opaque JSON. */
272
272
  commitments: unknown[];
273
- /** This device's private sub-share from each dealer, one per dealer, opaque JSON. */
273
+ /**
274
+ * This device's SEALED sub-share from each dealer, one per dealer — hex ciphertext, opened with
275
+ * {@link encryptionSecret} before the core verifies and assembles it.
276
+ */
274
277
  subShares: unknown[];
278
+ /**
279
+ * This holder's X25519 SECRET key (32-byte hex) — the counterpart of the public key the dealers
280
+ * sealed to, and the only thing that can open {@link subShares}.
281
+ *
282
+ * Required: dealers seal every sub-share (waas-core #365 M-A), so an assemble without it cannot
283
+ * even parse its inputs. SECRET — never log it.
284
+ */
285
+ encryptionSecret: string;
286
+ }
287
+ /**
288
+ * Parameters for the device's reshare **deal** stage ({@link MpcCore.runReshareDeal}) — the OLD
289
+ * holder's half, computed locally from its current share. Pure: no relay, no rounds.
290
+ */
291
+ interface DeviceReshareDealParams {
292
+ curve: MpcCurve;
293
+ /** This holder's CURRENT (old-epoch) key share JSON. Secret — never log it. */
294
+ share: string;
295
+ /** Keygen indices of the authorising old quorum, from the ceremony plan. */
296
+ quorumIndices: number[];
297
+ /** New share preimages `I'` (32-byte big-endian hex scalars), new-holder order. */
298
+ newPreimages: string[];
299
+ /** The new signing threshold `t'`. */
300
+ newThreshold: number;
301
+ /**
302
+ * Each NEW holder's X25519 PUBLIC key (32-byte hex), in new-holder order. The dealer seals
303
+ * sub-share `k` to `recipientPubkeys[k]`, so only ciphertext leaves the device.
304
+ */
305
+ recipientPubkeys: string[];
306
+ }
307
+ /**
308
+ * Result of the device reshare **deal** — what the ceremony expects back: the PUBLIC commitments
309
+ * to broadcast and one SEALED sub-share per new holder. No plaintext sub-share is ever produced
310
+ * outside the core.
311
+ */
312
+ interface DeviceReshareDealResult {
313
+ /** The dealer's public Feldman commitments (opaque JSON) — broadcast to every new holder. */
314
+ commitments: unknown;
315
+ /** One sealed sub-share (hex ciphertext) per NEW holder, in new-holder order. */
316
+ sealedSubShares: string[];
275
317
  }
276
318
  /**
277
319
  * Result of the device reshare **assemble** — the bare NEW-epoch core. It cannot sign yet
@@ -441,6 +483,12 @@ interface MpcCore {
441
483
  * it (the web wasm needs a `--features reshare` build).
442
484
  */
443
485
  runReshareAssemble?(params: DeviceReshareAssembleParams): Promise<DeviceReshareAssembleResult>;
486
+ /**
487
+ * Deal this holder's contribution to a reshare (#488) — the OLD-holder stage, pure and local.
488
+ * Optional: only a core built with the reshare capability implements it (the web wasm needs a
489
+ * `--features reshare` build).
490
+ */
491
+ runReshareDeal?(params: DeviceReshareDealParams): Promise<DeviceReshareDealResult>;
444
492
  /**
445
493
  * Complete a reshared bare core into a signable share by running the aux-info ceremony over the
446
494
  * NEW committee (#318 / #95). Relay-driven, mirroring keygen's aux phase. Optional: only a core
@@ -489,6 +537,11 @@ interface ClientWasmModule {
489
537
  * Synchronous in the core (no relay); the SDK awaits it uniformly.
490
538
  */
491
539
  reshareAssemble?(paramsJson: string): unknown | Promise<unknown>;
540
+ /**
541
+ * Device reshare **deal** — the old holder's local contribution; returns
542
+ * `{ commitments_json, sealed_sub_shares_hex }`. Present only in a `--features reshare` build.
543
+ */
544
+ reshareDeal?(paramsJson: string): unknown | Promise<unknown>;
492
545
  /**
493
546
  * Device **complete-reshare** — run the aux ceremony over the new committee and return the
494
547
  * signable share as `{ keyshare_json, shared_public_key_json }`. Present only in a
@@ -537,6 +590,7 @@ declare class WasmMpcCore implements MpcCore {
537
590
  runSign(params: DeviceSignParams): Promise<DeviceSignResult>;
538
591
  pregeneratePrimes(curve: MpcCurve): Promise<string>;
539
592
  runReshareAssemble(params: DeviceReshareAssembleParams): Promise<DeviceReshareAssembleResult>;
593
+ runReshareDeal(params: DeviceReshareDealParams): Promise<DeviceReshareDealResult>;
540
594
  runCompleteReshare(params: DeviceCompleteReshareParams): Promise<DeviceCompleteReshareResult>;
541
595
  runMemberKeygen(params: MemberKeygenParams): Promise<DeviceKeygenResult>;
542
596
  runMemberSign(params: MemberSignParams): Promise<DeviceSignResult>;
@@ -869,8 +923,16 @@ type CustodyType = 'embedded' | 'shared' | 'self_custody';
869
923
  interface WaaskeyOptions {
870
924
  /** Publishable API key issued from the Waaskey dashboard. */
871
925
  apiKey: string;
872
- /** API base URL. Defaults to the Waaskey production API. */
873
- baseUrl?: string;
926
+ /**
927
+ * API base URL — REQUIRED, with no default.
928
+ *
929
+ * There used to be one (`https://api.waaskey.com`), and it does not resolve: every consumer that
930
+ * omitted this got an NXDOMAIN on its first call and a `network` error with nothing pointing at the
931
+ * cause. A default that cannot work is worse than none, because it moves the failure from the
932
+ * constructor — where the fix is obvious — to every request. When a public hostname exists, the
933
+ * default can come back.
934
+ */
935
+ baseUrl: string;
874
936
  /** Custom fetch implementation (e.g. for non-browser runtimes). Defaults to global `fetch`. */
875
937
  fetch?: typeof fetch;
876
938
  /**
@@ -971,6 +1033,17 @@ interface WalletBackupParams {
971
1033
  email: string;
972
1034
  /** Extra factor enrolments beyond the standard three. */
973
1035
  extraFactors?: FactorEnrollment[];
1036
+ /**
1037
+ * A passkey PRF secret to ALSO wrap the backup key with (#510) — pass what
1038
+ * `PasskeyPrfSecretProvider.enroll()` returned. The backup then opens with either the passkey or the
1039
+ * recovery code, so a forgotten code is no longer a lost wallet. Omit to keep the recovery code as
1040
+ * the only key.
1041
+ */
1042
+ passkey?: {
1043
+ secret: string;
1044
+ salt: string;
1045
+ credentialId: string;
1046
+ };
974
1047
  }
975
1048
  /** Options for `wallets.joinCeremony(...)` (#349). */
976
1049
  interface JoinCeremonyOptions {
@@ -1144,6 +1217,10 @@ interface WalletData {
1144
1217
  custodyKinds: CustodyKind[];
1145
1218
  /** Count of {@link custodyKinds} entries the platform itself holds (`platform_signer` + `platform_recovery`). */
1146
1219
  platformShareCount: number;
1220
+ /** The wallet's key epoch: 1 at creation, bumped by every reshare. The public key and address are unchanged across one. */
1221
+ keyEpoch: number;
1222
+ /** True while a reshared committee still needs its aux material regenerated — the wallet cannot sign until it clears. */
1223
+ reshareAuxPending: boolean;
1147
1224
  /**
1148
1225
  * The wallet's custody attestation (#293): `embedded` when `platformShareCount >= threshold`
1149
1226
  * (platform alone is custodial-capable — the default 2-of-3), `self_custody` when
@@ -1534,8 +1611,15 @@ interface OnrampWidgetUrl {
1534
1611
  /** ISO timestamp when the signed URL expires, if applicable. */
1535
1612
  expiresAt?: string;
1536
1613
  }
1537
- /** The recovery factors a wallet enrols (backend `RecoveryFactor`). */
1538
- type RecoveryFactor = 'recovery_code' | 'totp' | 'email_otp';
1614
+ /**
1615
+ * The recovery factors a wallet enrols (backend `RecoveryFactor`).
1616
+ *
1617
+ * `passkey` is enrolled INSTEAD of `recovery_code` when the product wants a recovery flow with nothing
1618
+ * to remember (#510). Note the gate is an AND of every enrolled factor, not M-of-N — so enrolling a
1619
+ * passkey makes that passkey required. Passkeys sync across a user's devices where a written code does
1620
+ * not, which is what makes the trade reasonable, but it is a trade.
1621
+ */
1622
+ type RecoveryFactor = 'recovery_code' | 'totp' | 'email_otp' | 'passkey';
1539
1623
  /**
1540
1624
  * Enrolment of one factor at register time (backend `FactorEnrollmentDto`).
1541
1625
  *
@@ -1550,7 +1634,11 @@ type RecoveryFactor = 'recovery_code' | 'totp' | 'email_otp';
1550
1634
  */
1551
1635
  interface FactorEnrollment {
1552
1636
  type: RecoveryFactor;
1553
- /** recovery_code → lowercase-hex SHA-256 of the code; totp → base32 secret; email_otp → email address. */
1637
+ /**
1638
+ * recovery_code → lowercase-hex SHA-256 of the code; totp → base32 secret; email_otp → email address;
1639
+ * passkey → the base64url credential id of an ALREADY-registered passkey (public, not a secret) —
1640
+ * build it with `passkeyFactorEnrollment`.
1641
+ */
1554
1642
  credential: string;
1555
1643
  }
1556
1644
  /**
@@ -1566,7 +1654,11 @@ interface FactorEnrollment {
1566
1654
  */
1567
1655
  interface FactorVerification {
1568
1656
  type: RecoveryFactor;
1569
- /** recovery_code → the plaintext code (the SDK sends its SHA-256); totp → current 6-digit OTP; email_otp → the emailed OTP. */
1657
+ /**
1658
+ * recovery_code → the plaintext code (the SDK sends its SHA-256); totp → current 6-digit OTP;
1659
+ * email_otp → the emailed OTP; passkey → a WebAuthn assertion as JSON, which no user can type —
1660
+ * build it with `passkeyFactorVerification`.
1661
+ */
1570
1662
  token: string;
1571
1663
  }
1572
1664
  /** A registered recovery record's metadata (backend `RecoveryShareResponse`). */
@@ -1575,22 +1667,51 @@ interface RecoveryShareInfo {
1575
1667
  walletId: string;
1576
1668
  factors: RecoveryFactor[];
1577
1669
  createdAt: string;
1670
+ /** Which key-wrapping methods are enrolled — a single entry means a single point of failure. */
1671
+ wrapMethods?: ('passkey_prf' | 'passphrase')[];
1578
1672
  }
1579
1673
  /** Response to initiating a recovery session (backend `RecoveryChallengeResponse`). */
1580
1674
  interface RecoveryChallengeResponse {
1581
1675
  challengeId: string;
1582
1676
  requiredFactors: RecoveryFactor[];
1677
+ /**
1678
+ * The WebAuthn challenge to sign, present only when a `passkey` factor is enrolled (#510). Server-random
1679
+ * and single-use with this session — feed it to `passkeyFactorVerification`.
1680
+ */
1681
+ passkeyChallenge?: string;
1682
+ }
1683
+ /**
1684
+ * How the key that opens a backup was wrapped (backend `RecoveryKeyWrap`, #510).
1685
+ *
1686
+ * The share is sealed under a data key; that key is wrapped once per method, and every wrapping opens
1687
+ * the SAME share — so losing one path is survivable. Everything here is safe for the server to hold:
1688
+ * a wrapped key is noise without its key-encryption key, and those live only inside the user's
1689
+ * authenticator or in the user's head.
1690
+ */
1691
+ interface RecoveryKeyWrap {
1692
+ /** `passkey_prf` — key from a WebAuthn PRF output; `passphrase` — key from the recovery code. */
1693
+ method: 'passkey_prf' | 'passphrase';
1694
+ /** The data key, wrapped under this method's key-encryption key. */
1695
+ wrapped: string;
1696
+ /** Salt to reproduce that key-encryption key (the PRF evaluation salt, or the KDF salt). */
1697
+ salt: string;
1698
+ /** For `passkey_prf`: which credential to assert. */
1699
+ credentialId?: string;
1583
1700
  }
1584
1701
  /** Encrypted share returned after verifying factors (backend `RecoveryRetrieveResponse`). */
1585
1702
  interface RecoveryRetrieveResponse {
1586
1703
  id: string;
1587
1704
  ciphertext: string;
1705
+ /** Wrapped copies of the key that opens `ciphertext`. Empty for a backup registered before #510. */
1706
+ keyWraps?: RecoveryKeyWrap[];
1588
1707
  }
1589
1708
  /** Result of device-loss recovery — factors verified + shares rotated (backend `RecoverWalletResponse`). */
1590
1709
  interface RecoverWalletResponse {
1591
1710
  recovered: boolean;
1592
1711
  id: string;
1593
1712
  ciphertext: string;
1713
+ /** Wrapped copies of the key that opens `ciphertext`. Empty for a backup registered before #510. */
1714
+ keyWraps?: RecoveryKeyWrap[];
1594
1715
  refreshedAt: string;
1595
1716
  }
1596
1717
  /** Parameters for `recovery.register(...)`. */
@@ -1605,6 +1726,17 @@ interface RegisterRecoveryParams {
1605
1726
  email: string;
1606
1727
  /** Extra factor enrolments beyond the standard three. */
1607
1728
  extraFactors?: FactorEnrollment[];
1729
+ /**
1730
+ * A passkey PRF secret to ALSO wrap the backup key with (#510) — pass what
1731
+ * `PasskeyPrfSecretProvider.enroll()` returned. The backup then opens with either the passkey or the
1732
+ * recovery code, so a forgotten code is no longer a lost wallet. Omit to keep the recovery code as
1733
+ * the only key.
1734
+ */
1735
+ passkey?: {
1736
+ secret: string;
1737
+ salt: string;
1738
+ credentialId: string;
1739
+ };
1608
1740
  }
1609
1741
  /** Parameters for `recovery.recover(...)` / `recovery.retrieveShare(...)`. */
1610
1742
  interface RecoverParams {
@@ -1612,8 +1744,18 @@ interface RecoverParams {
1612
1744
  challengeId: string;
1613
1745
  /** One verification per enrolled factor. */
1614
1746
  verifications: FactorVerification[];
1615
- /** The recovery code — decrypts the retrieved share client-side. */
1616
- recoveryCode: string;
1747
+ /**
1748
+ * The recovery code — opens the retrieved share client-side. Optional only when `passkeySecret` is
1749
+ * supplied AND the backup was registered with a passkey wrap (#510); one of the two is required, and
1750
+ * a backup registered before #510 accepts nothing but the code.
1751
+ */
1752
+ recoveryCode?: string;
1753
+ /**
1754
+ * A passkey PRF output (base64) to open the backup with instead of the code (#510) — pass the
1755
+ * `secret` from `PasskeyPrfSecretProvider.unlock(credentialId, { salt })`, using the `salt` and
1756
+ * `credentialId` the backup's `keyWraps` carry. Never sent to the server.
1757
+ */
1758
+ passkeySecret?: string;
1617
1759
  }
1618
1760
  /**
1619
1761
  * Parameters for `wallets.recoverSign(...)` — the device-loss RECOVERY CO-SIGN of a non-custodial
@@ -1675,6 +1817,96 @@ interface ReshareCompletionCeremony {
1675
1817
  /** Short-lived relay token (JWT) the device presents to join the reshare-aux session; present only when relay auth is enabled. */
1676
1818
  relayToken?: string;
1677
1819
  }
1820
+ /**
1821
+ * A client-dealt reshare ceremony the backend opened (`POST /v1/wallets/:id/reshare-embedded`) — the
1822
+ * client's whole instruction set for its half: who deals, the committee being rotated onto, where each
1823
+ * sub-share must be sealed, and the coordination preimages both stages must agree on.
1824
+ */
1825
+ interface ReshareCeremony {
1826
+ id: string;
1827
+ walletId: string;
1828
+ status: string;
1829
+ curve: WalletCurve;
1830
+ /** OLD party indices of the authorising quorum — passed to the deal unchanged. */
1831
+ quorumIndices: number[];
1832
+ /** The OLD authorising quorum (this client's party plus the platform's). */
1833
+ dealers: {
1834
+ role: string;
1835
+ index: number;
1836
+ }[];
1837
+ /** The NEW ordered party roles, length `n'`. */
1838
+ newParties: string[];
1839
+ /** The NEW signing threshold `t'`. */
1840
+ newThreshold: number;
1841
+ /**
1842
+ * The NEW per-party custody kinds, parallel to {@link newParties}. This is what tells the client
1843
+ * which holders are ITS to assemble and acknowledge — the role names cannot, since the platform's
1844
+ * party is `server` on an embedded committee and `platform` on a member one.
1845
+ */
1846
+ newCustodyKinds: CustodyKind[];
1847
+ /** Each NEW holder's X25519 recipient pubkey (32-byte hex), parallel to {@link newParties}. */
1848
+ recipientPubkeys: string[];
1849
+ /** Fresh 32-byte-hex coordination preimages, one per NEW holder. */
1850
+ newPreimages: string[];
1851
+ /** The NEW key epoch the reshared shares are inserted under. */
1852
+ keyEpoch: number;
1853
+ expiresAt: string;
1854
+ }
1855
+ /** One client holder's assemble material (`GET …/reshare-drop/:ceremonyId/assembly?role=…`). */
1856
+ interface ReshareAssemblyMaterial {
1857
+ ceremonyId: string;
1858
+ walletId: string;
1859
+ status: string;
1860
+ curve: WalletCurve;
1861
+ /** This holder's 0-based position in the new committee. */
1862
+ newPosition: number;
1863
+ newThreshold: number;
1864
+ newPreimages: string[];
1865
+ /** The unchanged `WalletPublicInfo` JSON. */
1866
+ wallet: unknown;
1867
+ /** The wallet's public key — every assembled share must reproduce it. */
1868
+ expectedPublicKey: string;
1869
+ /** One Feldman-commitments object per dealer, in dealer order. */
1870
+ commitments: unknown[];
1871
+ /** This holder's SEALED sub-share from each dealer, parallel to {@link commitments}. */
1872
+ subShares: string[];
1873
+ }
1874
+ /** Parameters for `reshare.rotate(...)` — the committee to move the wallet onto. */
1875
+ interface ReshareRotateParams {
1876
+ /** The new ordered party roles. Omit to re-issue the current committee at a new epoch. */
1877
+ parties?: string[];
1878
+ /** The new signing threshold `t'`. Omit to keep the current one. */
1879
+ threshold?: number;
1880
+ /** The new per-party custody kinds, parallel to {@link parties}. Omit to derive from role names. */
1881
+ custodyKinds?: CustodyKind[];
1882
+ /**
1883
+ * Accept a committee with no user-held backup on a wallet that has one. Refused by default —
1884
+ * the address would not change, so nothing would show that the wallet had stopped being recoverable.
1885
+ */
1886
+ acknowledgeBackupLoss?: boolean;
1887
+ /**
1888
+ * The user's EXISTING recovery code. Required when the new committee keeps a `user_backup` holder:
1889
+ * that holder's new share is re-sealed under it so the wallet's backup survives the rotation. The
1890
+ * code never leaves the device — only the sealed ciphertext is sent.
1891
+ */
1892
+ recoveryCode?: string;
1893
+ }
1894
+ /** Result of `reshare.rotate(...)` — the ceremony the client drove, and what it produced locally. */
1895
+ interface ReshareRotateResult {
1896
+ walletId: string;
1897
+ ceremonyId: string;
1898
+ /** The ceremony's terminal status — `complete` once the last acknowledgement drove the cutover. */
1899
+ status: string;
1900
+ /** The wallet's NEW key epoch. */
1901
+ keyEpoch: number;
1902
+ /** The client-held holders this device assembled, in new-committee order. */
1903
+ assembledRoles: string[];
1904
+ /**
1905
+ * The wallet's public key, verified UNCHANGED by every assembled share — the funds-safety invariant
1906
+ * of a reshare.
1907
+ */
1908
+ sharedPublicKey: string;
1909
+ }
1678
1910
  /** Parameters for `reshare.complete(...)` — everything the device needs to finish its NEW-epoch share. */
1679
1911
  interface ReshareCompletionParams {
1680
1912
  /** Assemble material from the reshare response ({@link ReshareWalletResponse.deviceMaterial}). */
@@ -2016,6 +2248,36 @@ declare class Reshare {
2016
2248
  private readonly http;
2017
2249
  private readonly deps;
2018
2250
  constructor(http: HttpClient, deps?: ReshareDeps);
2251
+ /**
2252
+ * Rotate the wallet onto a NEW committee, keeping its address, and drive this device's whole half of
2253
+ * the ceremony (#488).
2254
+ *
2255
+ * The client is not a bystander here: the platform holds ONE share and cannot reach the threshold, so
2256
+ * without this device's deal there is no ceremony at all.
2257
+ *
2258
+ * 1. Open the ceremony with the committee asked for (an empty request re-issues the current one).
2259
+ * 2. DEAL from this device's current-epoch share — pure and local; every sub-share leaves sealed.
2260
+ * 3. For each CLIENT-held new holder — identified by custody KIND, never by role name — fetch its
2261
+ * material and assemble its new-epoch core locally, refusing any share that reproduces a different
2262
+ * public key.
2263
+ * 4. Acknowledge each one. The `user_backup` holder's ack carries its new share re-sealed under the
2264
+ * user's EXISTING recovery code, so the wallet's no-lock-out property survives the rotation; the
2265
+ * last acknowledgement drives the backend's atomic cutover.
2266
+ *
2267
+ * The assembled cores are BARE (no aux): the wallet is `reshareAuxPending` until aux-completion, which
2268
+ * is {@link complete}'s job — and which re-assembles from the material itself, so nothing here needs
2269
+ * to be kept on the device between the two. Nothing here can leave the device worse off — the old-epoch share is
2270
+ * untouched, and a ceremony that fails cuts nothing over.
2271
+ */
2272
+ rotate(walletId: string, params?: ReshareRotateParams, options?: {
2273
+ signal?: AbortSignal;
2274
+ }): Promise<ReshareRotateResult>;
2275
+ /**
2276
+ * Re-seal the new `user_backup` core under the user's EXISTING recovery code. The code never leaves
2277
+ * the device — only the ciphertext does — which is the same trust model registration has, and the
2278
+ * reason the server cannot check WHICH code sealed it.
2279
+ */
2280
+ private sealBackup;
2019
2281
  /**
2020
2282
  * Complete this device's share for a device-retaining reshare and make the wallet signable on the
2021
2283
  * device under the new epoch.
@@ -2432,11 +2694,13 @@ declare class Wallets {
2432
2694
  */
2433
2695
  recoverSign(walletId: string, params: RecoverSignParams, options?: SignOptions): Promise<string>;
2434
2696
  /**
2435
- * Retrieve the sealed `user_backup` ciphertext via the recovery gate ({@link fetchRecoveryCiphertext}),
2436
- * mapping a "no recovery share registered" (404) to the actionable `share_not_found` this wallet has no
2437
- * client-held `user_backup` backup to co-sign with (its device-loss recovery is the custodial path instead).
2697
+ * Retrieve the sealed `user_backup` backup via the recovery gate ({@link fetchRecoveryBackup}) — the
2698
+ * ciphertext AND its key wraps (#510), since a backup registered with a passkey is an envelope and
2699
+ * the wraps are what open it. Maps a "no recovery share registered" (404) to the actionable
2700
+ * `share_not_found` — this wallet has no client-held `user_backup` backup to co-sign with (its
2701
+ * device-loss recovery is the custodial path instead).
2438
2702
  */
2439
- private fetchUserBackupCiphertext;
2703
+ private fetchUserBackup;
2440
2704
  /**
2441
2705
  * Run one or more device keygen parties (each `mpc.runKeygen`), concurrently, mapping any failure to a
2442
2706
  * single `keygen_failed`. Used for both the single `device` party and the two-party non-custodial
@@ -2487,6 +2751,7 @@ declare function userBackupPendingKey(walletId: string): string;
2487
2751
  *
2488
2752
  * const waaskey = new Waaskey({
2489
2753
  * apiKey: process.env.WAASKEY_PUBLISHABLE_KEY!,
2754
+ * baseUrl: process.env.WAASKEY_API_URL!,
2490
2755
  * mpc: new WasmMpcCore(loadClientWasm),
2491
2756
  * shareStore: EncryptedShareStore.browser(sessionSecret),
2492
2757
  * });
@@ -2499,7 +2764,7 @@ declare class Waaskey {
2499
2764
  readonly wallets: Wallets;
2500
2765
  /** The `recovery` resource — multi-factor, client-encrypted wallet recovery. */
2501
2766
  readonly recovery: Recovery;
2502
- /** The `reshare` resource — device-side completion of a device-retaining reshare (#318). */
2767
+ /** The `reshare` resource — this device's half of a committee rotation (#488) and the aux-completion that follows (#318). */
2503
2768
  readonly reshare: Reshare;
2504
2769
  /** The `balances` resource — client-side balance reads from a chain provider (no backend). */
2505
2770
  readonly balances: Balances;
@@ -2549,6 +2814,175 @@ declare function isNonCustodial(wallet: Pick<WalletData, 'platformShareCount' |
2549
2814
  */
2550
2815
  declare function validateCustodyPolicy(params: Pick<CreateWalletParams, 'threshold' | 'parties' | 'custodyKinds' | 'custodyType'>): void;
2551
2816
 
2817
+ /** A backup blob plus the wrapped copies of the key that opens it. */
2818
+ interface RecoveryBackupEnvelope {
2819
+ /** The share, sealed under a random data key. Opaque to the server. */
2820
+ ciphertext: string;
2821
+ /** One wrapped copy of that data key per enrolled method. */
2822
+ keyWraps: RecoveryKeyWrap[];
2823
+ }
2824
+ /** A passkey PRF secret to wrap the backup key with — the exact shape `PasskeyPrfSecretProvider` returns. */
2825
+ interface PasskeyBackupKey {
2826
+ /** Base64 PRF output (32 bytes). Never persisted, never sent to the server. */
2827
+ secret: string;
2828
+ /** Base64 PRF evaluation salt — non-secret, stored with the wrap so it can be reproduced. */
2829
+ salt: string;
2830
+ /** The credential whose PRF derives this key, so recovery asserts the right passkey. */
2831
+ credentialId: string;
2832
+ }
2833
+ /**
2834
+ * Seal a share into an envelope: a random data key encrypts the share, and that data key is wrapped
2835
+ * once per method (#510 / waas-backend#510).
2836
+ *
2837
+ * The point of the indirection is that EVERY wrapping opens the SAME share. A user whose passkey is
2838
+ * gone still has the recovery code; a user who never wrote the code down still has the passkey. Sealing
2839
+ * the share directly under one secret — which is what this SDK did before — makes losing that one
2840
+ * secret the same as losing the wallet.
2841
+ *
2842
+ * The recovery-code wrap is always produced: it is the path that works on any device, in any browser,
2843
+ * with no authenticator support. The passkey wrap is added when the caller supplies a PRF secret.
2844
+ *
2845
+ * Nothing here is decryptable server-side. The server receives the ciphertext, the wrapped keys, their
2846
+ * salts and a credential id — none of which yields a key-encryption key. That is what keeps the
2847
+ * platform below the signing threshold; a change that lets the server derive one would quietly make a
2848
+ * non-custodial wallet custodial.
2849
+ */
2850
+ declare function sealRecoveryBackup(share: string, recoveryCode: string, passkey?: PasskeyBackupKey): Promise<RecoveryBackupEnvelope>;
2851
+ /**
2852
+ * Open an envelope with whichever key the user still has.
2853
+ *
2854
+ * A passkey secret is preferred when one is supplied and the enrollment carries a matching wrap —
2855
+ * that is the path with nothing to remember. Otherwise the recovery code opens it.
2856
+ *
2857
+ * A blob registered before envelopes existed arrives with NO wraps; that case is handled by the
2858
+ * caller ({@link Recovery}), which falls back to opening the ciphertext with the recovery code
2859
+ * directly. Deciding by `keyWraps.length` rather than by sniffing the blob's shape keeps the two
2860
+ * formats from ever being confused for one another.
2861
+ */
2862
+ declare function openRecoveryBackup(envelope: RecoveryBackupEnvelope, opener: {
2863
+ recoveryCode?: string;
2864
+ passkeySecret?: string;
2865
+ }): Promise<string>;
2866
+
2867
+ /**
2868
+ * Passkey step-up signing assertion (Pattern B).
2869
+ *
2870
+ * The backend accepts an optional `passkeyAssertion` (`AuthenticationResponseJSON`)
2871
+ * on sign/send/recover DTOs and verifies it server-side. This module runs the
2872
+ * WebAuthn assertion over a **server-issued one-time challenge** and returns the
2873
+ * typed `AuthenticationResponseJSON` ready to be included in the POST body.
2874
+ *
2875
+ * ### Challenge (issue #39 — replay-safe)
2876
+ * The challenge is a random nonce the **server** mints (via a step-up challenge
2877
+ * endpoint), NOT a value derived from the request payload. `Wallet.sign`/`send`
2878
+ * fetch it, run the assertion over it, and echo its `challengeId` on the request;
2879
+ * the server verifies the assertion against the stored challenge and **burns** it,
2880
+ * so a captured assertion cannot be replayed for a later identical payload.
2881
+ *
2882
+ * ### Wiring in Wallet
2883
+ * ```ts
2884
+ * // In wallet.sign() (handled by Wallet.resolveStepUp):
2885
+ * const { challengeId, challenge } = await http.request('POST', '/v1/wallets/${id}/stepup/challenge', { operation: 'sign' });
2886
+ * const assertion = await getSigningAssertion(challenge, { credentialId });
2887
+ * await http.request('POST', '/v1/wallets/${id}/sign', { message, passkeyAssertion: assertion, passkeyChallengeId: challengeId });
2888
+ *
2889
+ * // Caller opts in:
2890
+ * await wallet.sign(digest, { requirePasskey: true });
2891
+ * ```
2892
+ */
2893
+
2894
+ /**
2895
+ * Returns `true` if the current runtime has WebAuthn available — i.e. the SDK
2896
+ * can attempt a passkey assertion. Use this as a fast pre-flight before calling
2897
+ * `getSigningAssertion()` when you want to show/hide a "sign with passkey" button.
2898
+ */
2899
+ declare function isPasskeyAssertionSupported(): boolean;
2900
+ /**
2901
+ * Override the WebAuthn `credentials.get` call. Useful for React Native (native
2902
+ * passkey module) or unit tests (mock the assertion response).
2903
+ */
2904
+ interface SigningAssertionCeremony {
2905
+ get(options: PublicKeyCredentialRequestOptionsJSON): Promise<AuthenticationResponseJSON>;
2906
+ }
2907
+ /** Options for `getSigningAssertion()`. */
2908
+ interface SigningAssertionOptions {
2909
+ /**
2910
+ * Restrict the assertion to a specific credential. Pass the `credentialId`
2911
+ * that was enrolled (e.g. from `PasskeyPrfSecretProvider.enroll()`). When
2912
+ * omitted the browser presents all resident credentials for the RP.
2913
+ */
2914
+ credentialId?: string;
2915
+ /**
2916
+ * Fully qualified domain name of the relying party. Defaults to the current
2917
+ * origin's hostname in the browser.
2918
+ */
2919
+ rpId?: string;
2920
+ /**
2921
+ * Custom ceremony implementation (for React Native or tests).
2922
+ */
2923
+ ceremony?: SigningAssertionCeremony;
2924
+ }
2925
+ /**
2926
+ * Run a WebAuthn assertion over a **server-issued one-time `challenge`** and return
2927
+ * the typed `AuthenticationResponseJSON` to attach as `passkeyAssertion` in the
2928
+ * request body.
2929
+ *
2930
+ * @param challenge — the base64url one-time challenge nonce from the server's
2931
+ * step-up challenge endpoint ({@link StepUpChallengeResponse.challenge}). It is
2932
+ * passed to the authenticator verbatim; the server verifies the assertion against
2933
+ * the stored challenge and burns it, so the assertion cannot be replayed.
2934
+ * @param options — optional ceremony override and credential hint.
2935
+ *
2936
+ * @throws `WaaskeyError('unsupported')` when WebAuthn is not available in this runtime.
2937
+ * @throws `WaaskeyError('aborted')` when the user cancels the authenticator prompt.
2938
+ */
2939
+ declare function getSigningAssertion(challenge: string, options?: SigningAssertionOptions): Promise<AuthenticationResponseJSON>;
2940
+
2941
+ /**
2942
+ * The `passkey` recovery factor (waas-backend#510) — proving possession of a registered passkey
2943
+ * instead of typing a recovery code.
2944
+ *
2945
+ * This is what lets an app offer `{passkey, totp, email_otp}`: three factors, none of them a secret
2946
+ * the user has to remember. The PRF wrap protects the backup blob at rest, but the blob is only
2947
+ * released once every enrolled factor verifies, so as long as the only strong factor was a memorized
2948
+ * code, the code stayed mandatory.
2949
+ *
2950
+ * The factor's token is a WebAuthn assertion, not a value a person can type — which is why an app
2951
+ * needs this helper rather than an input field.
2952
+ */
2953
+ /**
2954
+ * Enrol a registered passkey as a recovery factor.
2955
+ *
2956
+ * The `credential` is the credential id, which is public and useless on its own — the private key
2957
+ * never leaves the authenticator. The credential must ALREADY be registered with the platform: the
2958
+ * server refuses an unknown id, because a factor that can never be satisfied is a lock-out the user
2959
+ * would discover only while recovering from a device they no longer have.
2960
+ */
2961
+ declare function passkeyFactorEnrollment(credentialId: string): FactorEnrollment;
2962
+ /**
2963
+ * Produce the `passkey` factor's proof for a recovery challenge.
2964
+ *
2965
+ * Pass the `passkeyChallenge` the challenge endpoint issued for THIS session: the server checks the
2966
+ * assertion against those exact bytes and consumes them, so an assertion captured from an earlier
2967
+ * attempt is worthless. The assertion travels as JSON in the factor's `token`, which is the only value
2968
+ * property the API's factor DTO accepts.
2969
+ *
2970
+ * ```ts
2971
+ * const { challengeId, requiredFactors, passkeyChallenge } = await waaskey.recovery.challenge(walletId);
2972
+ * const verifications = [
2973
+ * await passkeyFactorVerification(passkeyChallenge!),
2974
+ * { type: 'totp', token: code },
2975
+ * { type: 'email_otp', token: otp },
2976
+ * ];
2977
+ * await waaskey.recovery.recover(walletId, { challengeId, verifications, passkeySecret });
2978
+ * ```
2979
+ *
2980
+ * @throws `WaaskeyError('unsupported')` when WebAuthn is unavailable in this runtime (pass a
2981
+ * `ceremony` for React Native or tests).
2982
+ * @throws `WaaskeyError('aborted')` when the user cancels the authenticator prompt.
2983
+ */
2984
+ declare function passkeyFactorVerification(challenge: string, options?: SigningAssertionOptions): Promise<FactorVerification>;
2985
+
2552
2986
  /**
2553
2987
  * **Optional** client-side broadcast helper.
2554
2988
  *
@@ -2697,78 +3131,4 @@ declare class PasskeyPrfSecretProvider {
2697
3131
  }): Promise<PasskeyPrfResult>;
2698
3132
  }
2699
3133
 
2700
- /**
2701
- * Passkey step-up signing assertion (Pattern B).
2702
- *
2703
- * The backend accepts an optional `passkeyAssertion` (`AuthenticationResponseJSON`)
2704
- * on sign/send/recover DTOs and verifies it server-side. This module runs the
2705
- * WebAuthn assertion over a **server-issued one-time challenge** and returns the
2706
- * typed `AuthenticationResponseJSON` ready to be included in the POST body.
2707
- *
2708
- * ### Challenge (issue #39 — replay-safe)
2709
- * The challenge is a random nonce the **server** mints (via a step-up challenge
2710
- * endpoint), NOT a value derived from the request payload. `Wallet.sign`/`send`
2711
- * fetch it, run the assertion over it, and echo its `challengeId` on the request;
2712
- * the server verifies the assertion against the stored challenge and **burns** it,
2713
- * so a captured assertion cannot be replayed for a later identical payload.
2714
- *
2715
- * ### Wiring in Wallet
2716
- * ```ts
2717
- * // In wallet.sign() (handled by Wallet.resolveStepUp):
2718
- * const { challengeId, challenge } = await http.request('POST', '/v1/wallets/${id}/stepup/challenge', { operation: 'sign' });
2719
- * const assertion = await getSigningAssertion(challenge, { credentialId });
2720
- * await http.request('POST', '/v1/wallets/${id}/sign', { message, passkeyAssertion: assertion, passkeyChallengeId: challengeId });
2721
- *
2722
- * // Caller opts in:
2723
- * await wallet.sign(digest, { requirePasskey: true });
2724
- * ```
2725
- */
2726
-
2727
- /**
2728
- * Returns `true` if the current runtime has WebAuthn available — i.e. the SDK
2729
- * can attempt a passkey assertion. Use this as a fast pre-flight before calling
2730
- * `getSigningAssertion()` when you want to show/hide a "sign with passkey" button.
2731
- */
2732
- declare function isPasskeyAssertionSupported(): boolean;
2733
- /**
2734
- * Override the WebAuthn `credentials.get` call. Useful for React Native (native
2735
- * passkey module) or unit tests (mock the assertion response).
2736
- */
2737
- interface SigningAssertionCeremony {
2738
- get(options: PublicKeyCredentialRequestOptionsJSON): Promise<AuthenticationResponseJSON>;
2739
- }
2740
- /** Options for `getSigningAssertion()`. */
2741
- interface SigningAssertionOptions {
2742
- /**
2743
- * Restrict the assertion to a specific credential. Pass the `credentialId`
2744
- * that was enrolled (e.g. from `PasskeyPrfSecretProvider.enroll()`). When
2745
- * omitted the browser presents all resident credentials for the RP.
2746
- */
2747
- credentialId?: string;
2748
- /**
2749
- * Fully qualified domain name of the relying party. Defaults to the current
2750
- * origin's hostname in the browser.
2751
- */
2752
- rpId?: string;
2753
- /**
2754
- * Custom ceremony implementation (for React Native or tests).
2755
- */
2756
- ceremony?: SigningAssertionCeremony;
2757
- }
2758
- /**
2759
- * Run a WebAuthn assertion over a **server-issued one-time `challenge`** and return
2760
- * the typed `AuthenticationResponseJSON` to attach as `passkeyAssertion` in the
2761
- * request body.
2762
- *
2763
- * @param challenge — the base64url one-time challenge nonce from the server's
2764
- * step-up challenge endpoint ({@link StepUpChallengeResponse.challenge}). It is
2765
- * passed to the authenticator verbatim; the server verifies the assertion against
2766
- * the stored challenge and burns it, so the assertion cannot be replayed.
2767
- * @param options — optional ceremony override and credential hint.
2768
- *
2769
- * @throws `WaaskeyError('unsupported')` when WebAuthn is not available in this runtime.
2770
- * @throws `WaaskeyError('aborted')` when the user cancels the authenticator prompt.
2771
- */
2772
- declare function getSigningAssertion(challenge: string, options?: SigningAssertionOptions): Promise<AuthenticationResponseJSON>;
2773
-
2774
- export { Analytics, type AnalyticsEvent, type AnalyticsEventType, type AnalyticsSink, Auth, type Balance, Balances, type BroadcastOptions, type BroadcastResult, CLIENT_WASM_VERSION, type CeremonyParams, type Chain, type ChainConfig, type ChainProvider, type ClientWasmLoader, type ClientWasmModule, type CreateMemberWalletParams, type CreateWalletOptions, type CreateWalletParams, type CreateWalletResponse, type CustodyKind, type CustodyType, type DeviceCompleteReshareParams, type DeviceCompleteReshareResult, type DeviceKeygenParams, type DeviceKeygenResult, type DeviceReshareAssembleParams, type DeviceReshareAssembleResult, type DeviceReshareMaterial, type DeviceSignParams, type DeviceSignResult, type EddsaAssembleRequest, type EddsaCeremonyParams, type EddsaKeygenParams, type EddsaKeygenResult, type EddsaSendSession, type EddsaSignParams, type EddsaSignResult, type EmailStartResult, type EmbeddedSession, EncryptedShareStore, type EndUser, EvmRpcProvider, type FactorEnrollment, type FactorVerification, type FirebaseAuthRequest, HttpAnalyticsSink, IndexedDbKeyValueStore, type JoinCeremonyOptions, type JoinSignCeremonyOptions, type KeyValueStore, type Member, type MemberCeremony, type MemberCeremonyJoinResponse, type MemberCeremonyParams, type MemberKeygenParams, type MemberRole, type MemberSession, type MemberSignCeremony, type MemberSignParams, Members, type MembershipScope, MemoryKeyValueStore, MemoryPrimeStore, type MpcCore, type MpcCurve, Onramp, type OnrampWidgetParams, type OnrampWidgetUrl, type Page, type PageQuery, type PasskeyAssertionJSON, type PasskeyCeremony, type PasskeyPrfEnrollOptions, type PasskeyPrfResult, PasskeyPrfSecretProvider, type PrfCeremony, PrimePool, type PrimePoolOptions, type PrimePoolStore, type RecoverParams, type RecoverSignParams, type RecoverWalletResponse, Recovery, type RecoveryChallengeResponse, type RecoveryFactor, type RecoveryRetrieveResponse, type RecoveryShareInfo, type RegisterRecoveryParams, Reshare, type ReshareCompletionCeremony, type ReshareCompletionParams, type ReshareCompletionResult, type ReshareWalletResponse, type SendOptions, type SendParams, type SendResult, type ShareStore, type SignMessageResponse, type SignOptions, type SignRequestsQuery, type SignSessionResponse, type Signature, type SignatureKind, type SigningAssertionCeremony, type SigningAssertionOptions, type SigningRequestResponse, type SigningRequestStatus, type StepUpChallengeResponse, type StepUpOperation, type TokenBalanceOptions, type TxStatus, type VerifiedWasmLoaderOptions, Waaskey, WaaskeyError, type WaaskeyErrorCode, type WaaskeyErrorOptions, type WaaskeyOptions, Wallet, type WalletActionType, type WalletBackupParams, type WalletCeremony, type WalletCurve, type WalletData, type WalletShareholder, type WalletStatus, Wallets, WasmMpcCore, broadcast, createVerifiedClientWasmLoader, epochShareKey, formatUnits, generateRecoveryCode, getSigningAssertion, isNonCustodial, isPasskeyAssertionSupported, isPasskeySupported, isPrfSupported, loadClientWasm, memberShareKey, userBackupPendingKey, validateCustodyPolicy, verifyWasmIntegrity };
3134
+ export { Analytics, type AnalyticsEvent, type AnalyticsEventType, type AnalyticsSink, Auth, type Balance, Balances, type BroadcastOptions, type BroadcastResult, CLIENT_WASM_VERSION, type CeremonyParams, type Chain, type ChainConfig, type ChainProvider, type ClientWasmLoader, type ClientWasmModule, type CreateMemberWalletParams, type CreateWalletOptions, type CreateWalletParams, type CreateWalletResponse, type CustodyKind, type CustodyType, type DeviceCompleteReshareParams, type DeviceCompleteReshareResult, type DeviceKeygenParams, type DeviceKeygenResult, type DeviceReshareAssembleParams, type DeviceReshareAssembleResult, type DeviceReshareMaterial, type DeviceSignParams, type DeviceSignResult, type EddsaAssembleRequest, type EddsaCeremonyParams, type EddsaKeygenParams, type EddsaKeygenResult, type EddsaSendSession, type EddsaSignParams, type EddsaSignResult, type EmailStartResult, type EmbeddedSession, EncryptedShareStore, type EndUser, EvmRpcProvider, type FactorEnrollment, type FactorVerification, type FirebaseAuthRequest, HttpAnalyticsSink, IndexedDbKeyValueStore, type JoinCeremonyOptions, type JoinSignCeremonyOptions, type KeyValueStore, type Member, type MemberCeremony, type MemberCeremonyJoinResponse, type MemberCeremonyParams, type MemberKeygenParams, type MemberRole, type MemberSession, type MemberSignCeremony, type MemberSignParams, Members, type MembershipScope, MemoryKeyValueStore, MemoryPrimeStore, type MpcCore, type MpcCurve, Onramp, type OnrampWidgetParams, type OnrampWidgetUrl, type Page, type PageQuery, type PasskeyAssertionJSON, type PasskeyBackupKey, type PasskeyCeremony, type PasskeyPrfEnrollOptions, type PasskeyPrfResult, PasskeyPrfSecretProvider, type PrfCeremony, PrimePool, type PrimePoolOptions, type PrimePoolStore, type RecoverParams, type RecoverSignParams, type RecoverWalletResponse, Recovery, type RecoveryBackupEnvelope, type RecoveryChallengeResponse, type RecoveryFactor, type RecoveryKeyWrap, type RecoveryRetrieveResponse, type RecoveryShareInfo, type RegisterRecoveryParams, Reshare, type ReshareAssemblyMaterial, type ReshareCeremony, type ReshareCompletionCeremony, type ReshareCompletionParams, type ReshareCompletionResult, type ReshareRotateParams, type ReshareRotateResult, type ReshareWalletResponse, type SendOptions, type SendParams, type SendResult, type ShareStore, type SignMessageResponse, type SignOptions, type SignRequestsQuery, type SignSessionResponse, type Signature, type SignatureKind, type SigningAssertionCeremony, type SigningAssertionOptions, type SigningRequestResponse, type SigningRequestStatus, type StepUpChallengeResponse, type StepUpOperation, type TokenBalanceOptions, type TxStatus, type VerifiedWasmLoaderOptions, Waaskey, WaaskeyError, type WaaskeyErrorCode, type WaaskeyErrorOptions, type WaaskeyOptions, Wallet, type WalletActionType, type WalletBackupParams, type WalletCeremony, type WalletCurve, type WalletData, type WalletShareholder, type WalletStatus, Wallets, WasmMpcCore, broadcast, createVerifiedClientWasmLoader, epochShareKey, formatUnits, generateRecoveryCode, getSigningAssertion, isNonCustodial, isPasskeyAssertionSupported, isPasskeySupported, isPrfSupported, loadClientWasm, memberShareKey, openRecoveryBackup, passkeyFactorEnrollment, passkeyFactorVerification, sealRecoveryBackup, userBackupPendingKey, validateCustodyPolicy, verifyWasmIntegrity };