@cef-ai/wallet-identity 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +128 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +111 -8
- package/dist/index.d.ts +111 -8
- package/dist/index.js +128 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -7,12 +7,69 @@ interface Addresses {
|
|
|
7
7
|
evm: string;
|
|
8
8
|
}
|
|
9
9
|
type Chain = 'cere' | 'solana' | 'evm';
|
|
10
|
+
/**
|
|
11
|
+
* A transferable snapshot of the current session — the raw chain-key material
|
|
12
|
+
* plus the addresses it yields and an absolute expiry. This is the payload the
|
|
13
|
+
* register-popup → iframe handoff moves across the same-origin
|
|
14
|
+
* `BroadcastChannel('scp-wallet-v2')` (see embed-sdk's `BroadcastSessionShare`).
|
|
15
|
+
*
|
|
16
|
+
* SECURITY: unlike `VaultSnapshot`, this DOES carry the raw seeds. It exists
|
|
17
|
+
* only so a same-origin sibling surface (the persistent iframe) can adopt a
|
|
18
|
+
* session a punched-out popup just created — Safari blocks the WebAuthn
|
|
19
|
+
* `create()` ceremony in a cross-origin iframe (Task 6), so the popup runs the
|
|
20
|
+
* ceremony and hands its result back. It must NEVER cross an origin boundary or
|
|
21
|
+
* reach a host page. Spec §3.6 invariant 1 (keys never leave the wallet origin)
|
|
22
|
+
* still holds: the channel is same-origin and wallet-only.
|
|
23
|
+
*
|
|
24
|
+
* The field shape intentionally matches embed-sdk's `BroadcastSession`. It is
|
|
25
|
+
* declared here (not imported from embed-sdk) to avoid a circular package
|
|
26
|
+
* dependency — embed-sdk already depends on this package.
|
|
27
|
+
*/
|
|
28
|
+
interface IdentitySession {
|
|
29
|
+
/** Ed25519 32-byte secret key seed; Cere + Solana signing. */
|
|
30
|
+
edSeed: Uint8Array;
|
|
31
|
+
/** secp256k1 32-byte secret key; EVM signing. */
|
|
32
|
+
secpKey: Uint8Array;
|
|
33
|
+
addresses: Addresses;
|
|
34
|
+
/** Absolute Unix epoch ms when the session expires. */
|
|
35
|
+
expMs: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Same-origin session-handoff capability. Kept OFF the core `Identity`
|
|
39
|
+
* interface deliberately: it is an optional, wallet-origin-only concern (the
|
|
40
|
+
* register-popup → iframe handoff), and forcing every `Identity` implementation
|
|
41
|
+
* — including thin test doubles — to provide raw-key export/import would widen
|
|
42
|
+
* the security surface for no benefit. Concrete `IdentityImpl` implements it;
|
|
43
|
+
* SPA wiring feature-detects via `supportsSessionHandoff()`.
|
|
44
|
+
*/
|
|
45
|
+
interface SessionHandoff {
|
|
46
|
+
/**
|
|
47
|
+
* Export the current session for a same-origin handoff, or `null` when no
|
|
48
|
+
* live session exists (vault closed / expired / no captured keys). See
|
|
49
|
+
* `IdentitySession` for the security contract.
|
|
50
|
+
*/
|
|
51
|
+
exportSession(): IdentitySession | null;
|
|
52
|
+
/**
|
|
53
|
+
* Adopt a session handed over from a same-origin sibling surface (e.g. the
|
|
54
|
+
* register popup). Rebuilds the derived keys from the transported seeds, opens
|
|
55
|
+
* the vault, and syncs public state — WITHOUT running a WebAuthn ceremony.
|
|
56
|
+
*/
|
|
57
|
+
adoptSession(session: IdentitySession): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Narrow an `Identity` to one that supports same-origin session handoff.
|
|
61
|
+
* `IdentityImpl` returns true; thin test doubles return false and are simply
|
|
62
|
+
* skipped by the responder/requester wiring.
|
|
63
|
+
*/
|
|
64
|
+
declare function supportsSessionHandoff(identity: Identity): identity is Identity & SessionHandoff;
|
|
10
65
|
interface Identity {
|
|
11
66
|
readonly isAuthenticated: boolean;
|
|
12
67
|
readonly addresses: Addresses | null;
|
|
13
68
|
readonly credentialId: string | null;
|
|
14
69
|
register(opts?: {
|
|
15
70
|
label?: string;
|
|
71
|
+
name?: string;
|
|
72
|
+
email?: string;
|
|
16
73
|
}): Promise<void>;
|
|
17
74
|
login(): Promise<void>;
|
|
18
75
|
logout(): void;
|
|
@@ -39,11 +96,12 @@ declare const PRF_INPUT_LABEL = "cere-wallet-prf-v1";
|
|
|
39
96
|
/**
|
|
40
97
|
* SHA-256("cere-wallet-prf-v1") — 32 bytes. Fed to WebAuthn `prf.eval.first`.
|
|
41
98
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* browser
|
|
45
|
-
*
|
|
46
|
-
*
|
|
99
|
+
* Hard-coded as a byte literal so this module is browser-safe with NO
|
|
100
|
+
* `node:crypto` dependency — it is evaluated at module-load time, and relying on
|
|
101
|
+
* `createHash` there forced every browser consumer to wire a `node:crypto`
|
|
102
|
+
* polyfill (`vite-plugin-node-polyfills`). The literal is verified to equal
|
|
103
|
+
* `sha256(PRF_INPUT_LABEL)` in `constants.test.ts`, so the constant cannot drift
|
|
104
|
+
* silently. Rotating the label (`-v2`) means recomputing these bytes.
|
|
47
105
|
*/
|
|
48
106
|
declare const PRF_INPUT_SEED: Uint8Array;
|
|
49
107
|
/** Spec §3.2. Used as HKDF-SHA256 info to derive the secp256k1 32-byte secret. */
|
|
@@ -183,8 +241,18 @@ interface RegisterOptions {
|
|
|
183
241
|
userHandle: Uint8Array;
|
|
184
242
|
/** 32-byte PRF input (typically `PRF_INPUT_SEED`). */
|
|
185
243
|
prfInput: Uint8Array;
|
|
186
|
-
/**
|
|
244
|
+
/**
|
|
245
|
+
* Legacy single label. Used as a fallback for both `user.displayName` and
|
|
246
|
+
* `user.name` when `name`/`email` are not supplied. Prefer `name` + `email`.
|
|
247
|
+
*/
|
|
187
248
|
label?: string;
|
|
249
|
+
/** Friendly account name → WebAuthn `user.displayName` (e.g. "Alex Müller"). */
|
|
250
|
+
name?: string;
|
|
251
|
+
/**
|
|
252
|
+
* Account identifier → WebAuthn `user.name` (e.g. "alex@example.com"). This is
|
|
253
|
+
* the field most OS/browser passkey managers surface to distinguish credentials.
|
|
254
|
+
*/
|
|
255
|
+
email?: string;
|
|
188
256
|
}
|
|
189
257
|
interface RegisterResult {
|
|
190
258
|
credentialId: string;
|
|
@@ -340,12 +408,27 @@ interface IdentityImplOptions {
|
|
|
340
408
|
apiClient: ApiClient;
|
|
341
409
|
ceremony: CeremonyAdapter;
|
|
342
410
|
}
|
|
343
|
-
declare class IdentityImpl implements Identity {
|
|
411
|
+
declare class IdentityImpl implements Identity, SessionHandoff {
|
|
344
412
|
private readonly opts;
|
|
345
413
|
private readonly vault;
|
|
346
414
|
private cachedJwt;
|
|
347
415
|
/** Server-returned credential ID (used as the public identity.credentialId). */
|
|
348
416
|
private serverCredentialId;
|
|
417
|
+
/**
|
|
418
|
+
* Raw derived keys for the CURRENT session, captured at every `vault.set()`
|
|
419
|
+
* call site (register / login / adoptSession). The vault deliberately hides
|
|
420
|
+
* the seeds behind its closure (Spec §3.6 invariant 1); this field is the
|
|
421
|
+
* single, explicit, same-origin-only seam that lets `exportSession()` hand a
|
|
422
|
+
* live session to a sibling wallet-origin surface (the persistent iframe).
|
|
423
|
+
* Cleared on logout / cross-tab logout alongside the vault. Never serialised
|
|
424
|
+
* to storage and never sent to a host page.
|
|
425
|
+
*/
|
|
426
|
+
private sessionKeys;
|
|
427
|
+
/**
|
|
428
|
+
* Absolute expiry (epoch ms) for `exportSession()`. Tracks the cached JWT
|
|
429
|
+
* expiry after register/login; set from the adopted session in adoptSession.
|
|
430
|
+
*/
|
|
431
|
+
private sessionExpMs;
|
|
349
432
|
private readonly _isAuthenticated;
|
|
350
433
|
private readonly _addresses;
|
|
351
434
|
private readonly _credentialId;
|
|
@@ -372,6 +455,8 @@ declare class IdentityImpl implements Identity {
|
|
|
372
455
|
private syncPublicState;
|
|
373
456
|
register(opts?: {
|
|
374
457
|
label?: string;
|
|
458
|
+
name?: string;
|
|
459
|
+
email?: string;
|
|
375
460
|
}): Promise<void>;
|
|
376
461
|
login(): Promise<void>;
|
|
377
462
|
logout(): void;
|
|
@@ -384,6 +469,24 @@ declare class IdentityImpl implements Identity {
|
|
|
384
469
|
* side use — keys never reach the host. Spec §3.6 invariant 1.
|
|
385
470
|
*/
|
|
386
471
|
getVault(): SessionVault;
|
|
472
|
+
/**
|
|
473
|
+
* Export the current session for a same-origin handoff (register popup →
|
|
474
|
+
* iframe). Returns `null` unless the vault is open, we captured the derived
|
|
475
|
+
* keys, and the session has not expired. The raw seeds ARE included — this is
|
|
476
|
+
* the deliberate, narrow seam described on `IdentitySession`; callers must
|
|
477
|
+
* only pass the result across the same-origin wallet BroadcastChannel.
|
|
478
|
+
*/
|
|
479
|
+
exportSession(): IdentitySession | null;
|
|
480
|
+
/**
|
|
481
|
+
* Adopt a session handed over from a same-origin sibling surface. Rebuilds
|
|
482
|
+
* the `DerivedKeys` from the transported seeds (public keys are recomputed
|
|
483
|
+
* from the seeds; we do NOT re-run the HKDF step so the exact transported
|
|
484
|
+
* secpKey is preserved), opens the vault, and syncs public state — no
|
|
485
|
+
* ceremony. There is no credentialId in the handoff, so login() would need a
|
|
486
|
+
* fresh ceremony; but the session key is warm, so `sign`/`claim` (which use
|
|
487
|
+
* the vault directly, no JWT) work immediately.
|
|
488
|
+
*/
|
|
489
|
+
adoptSession(session: IdentitySession): void;
|
|
387
490
|
/**
|
|
388
491
|
* Release the BroadcastChannel + cross-tab subscriber. Idempotent.
|
|
389
492
|
*
|
|
@@ -440,4 +543,4 @@ declare class CrossTabSync {
|
|
|
440
543
|
close(): void;
|
|
441
544
|
}
|
|
442
545
|
|
|
443
|
-
export { type Addresses, CERE_SS58_PREFIX, type CeremonyAdapter, type Chain, type CrossTabMessage, CrossTabSync, type DerivedKeys, EVM_HKDF_INFO, type Identity, IdentityImpl, type IdentityImplOptions, type InternalSign, type LoginOptions, type LoginResult, PRF_INPUT_LABEL, PRF_INPUT_SEED, type PrfSupportResult, type RegisterOptions, type RegisterResult, type SessionVault, SoftAuthenticator, type VaultSnapshot, WebAuthnCeremonyAdapter, type WebAuthnCeremonyAdapterOptions, b64uToBytes, bytesToB64u, createSessionVault, decodeEd25519Pubkey, deriveCereAddress, deriveEvmAddress, deriveSolanaAddress, derivedKeys, detectPrfSupport, isPrfSupportedResult };
|
|
546
|
+
export { type Addresses, CERE_SS58_PREFIX, type CeremonyAdapter, type Chain, type CrossTabMessage, CrossTabSync, type DerivedKeys, EVM_HKDF_INFO, type Identity, IdentityImpl, type IdentityImplOptions, type IdentitySession, type InternalSign, type LoginOptions, type LoginResult, PRF_INPUT_LABEL, PRF_INPUT_SEED, type PrfSupportResult, type RegisterOptions, type RegisterResult, type SessionHandoff, type SessionVault, SoftAuthenticator, type VaultSnapshot, WebAuthnCeremonyAdapter, type WebAuthnCeremonyAdapterOptions, b64uToBytes, bytesToB64u, createSessionVault, decodeEd25519Pubkey, deriveCereAddress, deriveEvmAddress, deriveSolanaAddress, derivedKeys, detectPrfSupport, isPrfSupportedResult, supportsSessionHandoff };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,12 +7,69 @@ interface Addresses {
|
|
|
7
7
|
evm: string;
|
|
8
8
|
}
|
|
9
9
|
type Chain = 'cere' | 'solana' | 'evm';
|
|
10
|
+
/**
|
|
11
|
+
* A transferable snapshot of the current session — the raw chain-key material
|
|
12
|
+
* plus the addresses it yields and an absolute expiry. This is the payload the
|
|
13
|
+
* register-popup → iframe handoff moves across the same-origin
|
|
14
|
+
* `BroadcastChannel('scp-wallet-v2')` (see embed-sdk's `BroadcastSessionShare`).
|
|
15
|
+
*
|
|
16
|
+
* SECURITY: unlike `VaultSnapshot`, this DOES carry the raw seeds. It exists
|
|
17
|
+
* only so a same-origin sibling surface (the persistent iframe) can adopt a
|
|
18
|
+
* session a punched-out popup just created — Safari blocks the WebAuthn
|
|
19
|
+
* `create()` ceremony in a cross-origin iframe (Task 6), so the popup runs the
|
|
20
|
+
* ceremony and hands its result back. It must NEVER cross an origin boundary or
|
|
21
|
+
* reach a host page. Spec §3.6 invariant 1 (keys never leave the wallet origin)
|
|
22
|
+
* still holds: the channel is same-origin and wallet-only.
|
|
23
|
+
*
|
|
24
|
+
* The field shape intentionally matches embed-sdk's `BroadcastSession`. It is
|
|
25
|
+
* declared here (not imported from embed-sdk) to avoid a circular package
|
|
26
|
+
* dependency — embed-sdk already depends on this package.
|
|
27
|
+
*/
|
|
28
|
+
interface IdentitySession {
|
|
29
|
+
/** Ed25519 32-byte secret key seed; Cere + Solana signing. */
|
|
30
|
+
edSeed: Uint8Array;
|
|
31
|
+
/** secp256k1 32-byte secret key; EVM signing. */
|
|
32
|
+
secpKey: Uint8Array;
|
|
33
|
+
addresses: Addresses;
|
|
34
|
+
/** Absolute Unix epoch ms when the session expires. */
|
|
35
|
+
expMs: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Same-origin session-handoff capability. Kept OFF the core `Identity`
|
|
39
|
+
* interface deliberately: it is an optional, wallet-origin-only concern (the
|
|
40
|
+
* register-popup → iframe handoff), and forcing every `Identity` implementation
|
|
41
|
+
* — including thin test doubles — to provide raw-key export/import would widen
|
|
42
|
+
* the security surface for no benefit. Concrete `IdentityImpl` implements it;
|
|
43
|
+
* SPA wiring feature-detects via `supportsSessionHandoff()`.
|
|
44
|
+
*/
|
|
45
|
+
interface SessionHandoff {
|
|
46
|
+
/**
|
|
47
|
+
* Export the current session for a same-origin handoff, or `null` when no
|
|
48
|
+
* live session exists (vault closed / expired / no captured keys). See
|
|
49
|
+
* `IdentitySession` for the security contract.
|
|
50
|
+
*/
|
|
51
|
+
exportSession(): IdentitySession | null;
|
|
52
|
+
/**
|
|
53
|
+
* Adopt a session handed over from a same-origin sibling surface (e.g. the
|
|
54
|
+
* register popup). Rebuilds the derived keys from the transported seeds, opens
|
|
55
|
+
* the vault, and syncs public state — WITHOUT running a WebAuthn ceremony.
|
|
56
|
+
*/
|
|
57
|
+
adoptSession(session: IdentitySession): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Narrow an `Identity` to one that supports same-origin session handoff.
|
|
61
|
+
* `IdentityImpl` returns true; thin test doubles return false and are simply
|
|
62
|
+
* skipped by the responder/requester wiring.
|
|
63
|
+
*/
|
|
64
|
+
declare function supportsSessionHandoff(identity: Identity): identity is Identity & SessionHandoff;
|
|
10
65
|
interface Identity {
|
|
11
66
|
readonly isAuthenticated: boolean;
|
|
12
67
|
readonly addresses: Addresses | null;
|
|
13
68
|
readonly credentialId: string | null;
|
|
14
69
|
register(opts?: {
|
|
15
70
|
label?: string;
|
|
71
|
+
name?: string;
|
|
72
|
+
email?: string;
|
|
16
73
|
}): Promise<void>;
|
|
17
74
|
login(): Promise<void>;
|
|
18
75
|
logout(): void;
|
|
@@ -39,11 +96,12 @@ declare const PRF_INPUT_LABEL = "cere-wallet-prf-v1";
|
|
|
39
96
|
/**
|
|
40
97
|
* SHA-256("cere-wallet-prf-v1") — 32 bytes. Fed to WebAuthn `prf.eval.first`.
|
|
41
98
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* browser
|
|
45
|
-
*
|
|
46
|
-
*
|
|
99
|
+
* Hard-coded as a byte literal so this module is browser-safe with NO
|
|
100
|
+
* `node:crypto` dependency — it is evaluated at module-load time, and relying on
|
|
101
|
+
* `createHash` there forced every browser consumer to wire a `node:crypto`
|
|
102
|
+
* polyfill (`vite-plugin-node-polyfills`). The literal is verified to equal
|
|
103
|
+
* `sha256(PRF_INPUT_LABEL)` in `constants.test.ts`, so the constant cannot drift
|
|
104
|
+
* silently. Rotating the label (`-v2`) means recomputing these bytes.
|
|
47
105
|
*/
|
|
48
106
|
declare const PRF_INPUT_SEED: Uint8Array;
|
|
49
107
|
/** Spec §3.2. Used as HKDF-SHA256 info to derive the secp256k1 32-byte secret. */
|
|
@@ -183,8 +241,18 @@ interface RegisterOptions {
|
|
|
183
241
|
userHandle: Uint8Array;
|
|
184
242
|
/** 32-byte PRF input (typically `PRF_INPUT_SEED`). */
|
|
185
243
|
prfInput: Uint8Array;
|
|
186
|
-
/**
|
|
244
|
+
/**
|
|
245
|
+
* Legacy single label. Used as a fallback for both `user.displayName` and
|
|
246
|
+
* `user.name` when `name`/`email` are not supplied. Prefer `name` + `email`.
|
|
247
|
+
*/
|
|
187
248
|
label?: string;
|
|
249
|
+
/** Friendly account name → WebAuthn `user.displayName` (e.g. "Alex Müller"). */
|
|
250
|
+
name?: string;
|
|
251
|
+
/**
|
|
252
|
+
* Account identifier → WebAuthn `user.name` (e.g. "alex@example.com"). This is
|
|
253
|
+
* the field most OS/browser passkey managers surface to distinguish credentials.
|
|
254
|
+
*/
|
|
255
|
+
email?: string;
|
|
188
256
|
}
|
|
189
257
|
interface RegisterResult {
|
|
190
258
|
credentialId: string;
|
|
@@ -340,12 +408,27 @@ interface IdentityImplOptions {
|
|
|
340
408
|
apiClient: ApiClient;
|
|
341
409
|
ceremony: CeremonyAdapter;
|
|
342
410
|
}
|
|
343
|
-
declare class IdentityImpl implements Identity {
|
|
411
|
+
declare class IdentityImpl implements Identity, SessionHandoff {
|
|
344
412
|
private readonly opts;
|
|
345
413
|
private readonly vault;
|
|
346
414
|
private cachedJwt;
|
|
347
415
|
/** Server-returned credential ID (used as the public identity.credentialId). */
|
|
348
416
|
private serverCredentialId;
|
|
417
|
+
/**
|
|
418
|
+
* Raw derived keys for the CURRENT session, captured at every `vault.set()`
|
|
419
|
+
* call site (register / login / adoptSession). The vault deliberately hides
|
|
420
|
+
* the seeds behind its closure (Spec §3.6 invariant 1); this field is the
|
|
421
|
+
* single, explicit, same-origin-only seam that lets `exportSession()` hand a
|
|
422
|
+
* live session to a sibling wallet-origin surface (the persistent iframe).
|
|
423
|
+
* Cleared on logout / cross-tab logout alongside the vault. Never serialised
|
|
424
|
+
* to storage and never sent to a host page.
|
|
425
|
+
*/
|
|
426
|
+
private sessionKeys;
|
|
427
|
+
/**
|
|
428
|
+
* Absolute expiry (epoch ms) for `exportSession()`. Tracks the cached JWT
|
|
429
|
+
* expiry after register/login; set from the adopted session in adoptSession.
|
|
430
|
+
*/
|
|
431
|
+
private sessionExpMs;
|
|
349
432
|
private readonly _isAuthenticated;
|
|
350
433
|
private readonly _addresses;
|
|
351
434
|
private readonly _credentialId;
|
|
@@ -372,6 +455,8 @@ declare class IdentityImpl implements Identity {
|
|
|
372
455
|
private syncPublicState;
|
|
373
456
|
register(opts?: {
|
|
374
457
|
label?: string;
|
|
458
|
+
name?: string;
|
|
459
|
+
email?: string;
|
|
375
460
|
}): Promise<void>;
|
|
376
461
|
login(): Promise<void>;
|
|
377
462
|
logout(): void;
|
|
@@ -384,6 +469,24 @@ declare class IdentityImpl implements Identity {
|
|
|
384
469
|
* side use — keys never reach the host. Spec §3.6 invariant 1.
|
|
385
470
|
*/
|
|
386
471
|
getVault(): SessionVault;
|
|
472
|
+
/**
|
|
473
|
+
* Export the current session for a same-origin handoff (register popup →
|
|
474
|
+
* iframe). Returns `null` unless the vault is open, we captured the derived
|
|
475
|
+
* keys, and the session has not expired. The raw seeds ARE included — this is
|
|
476
|
+
* the deliberate, narrow seam described on `IdentitySession`; callers must
|
|
477
|
+
* only pass the result across the same-origin wallet BroadcastChannel.
|
|
478
|
+
*/
|
|
479
|
+
exportSession(): IdentitySession | null;
|
|
480
|
+
/**
|
|
481
|
+
* Adopt a session handed over from a same-origin sibling surface. Rebuilds
|
|
482
|
+
* the `DerivedKeys` from the transported seeds (public keys are recomputed
|
|
483
|
+
* from the seeds; we do NOT re-run the HKDF step so the exact transported
|
|
484
|
+
* secpKey is preserved), opens the vault, and syncs public state — no
|
|
485
|
+
* ceremony. There is no credentialId in the handoff, so login() would need a
|
|
486
|
+
* fresh ceremony; but the session key is warm, so `sign`/`claim` (which use
|
|
487
|
+
* the vault directly, no JWT) work immediately.
|
|
488
|
+
*/
|
|
489
|
+
adoptSession(session: IdentitySession): void;
|
|
387
490
|
/**
|
|
388
491
|
* Release the BroadcastChannel + cross-tab subscriber. Idempotent.
|
|
389
492
|
*
|
|
@@ -440,4 +543,4 @@ declare class CrossTabSync {
|
|
|
440
543
|
close(): void;
|
|
441
544
|
}
|
|
442
545
|
|
|
443
|
-
export { type Addresses, CERE_SS58_PREFIX, type CeremonyAdapter, type Chain, type CrossTabMessage, CrossTabSync, type DerivedKeys, EVM_HKDF_INFO, type Identity, IdentityImpl, type IdentityImplOptions, type InternalSign, type LoginOptions, type LoginResult, PRF_INPUT_LABEL, PRF_INPUT_SEED, type PrfSupportResult, type RegisterOptions, type RegisterResult, type SessionVault, SoftAuthenticator, type VaultSnapshot, WebAuthnCeremonyAdapter, type WebAuthnCeremonyAdapterOptions, b64uToBytes, bytesToB64u, createSessionVault, decodeEd25519Pubkey, deriveCereAddress, deriveEvmAddress, deriveSolanaAddress, derivedKeys, detectPrfSupport, isPrfSupportedResult };
|
|
546
|
+
export { type Addresses, CERE_SS58_PREFIX, type CeremonyAdapter, type Chain, type CrossTabMessage, CrossTabSync, type DerivedKeys, EVM_HKDF_INFO, type Identity, IdentityImpl, type IdentityImplOptions, type IdentitySession, type InternalSign, type LoginOptions, type LoginResult, PRF_INPUT_LABEL, PRF_INPUT_SEED, type PrfSupportResult, type RegisterOptions, type RegisterResult, type SessionHandoff, type SessionVault, SoftAuthenticator, type VaultSnapshot, WebAuthnCeremonyAdapter, type WebAuthnCeremonyAdapterOptions, b64uToBytes, bytesToB64u, createSessionVault, decodeEd25519Pubkey, deriveCereAddress, deriveEvmAddress, deriveSolanaAddress, derivedKeys, detectPrfSupport, isPrfSupportedResult, supportsSessionHandoff };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createHash } from 'crypto';
|
|
2
1
|
import bs58 from 'bs58';
|
|
3
2
|
import { blake2b } from '@noble/hashes/blake2b';
|
|
4
3
|
import { keccak_256 } from '@noble/hashes/sha3';
|
|
@@ -30,8 +29,48 @@ var __async = (__this, __arguments, generator) => {
|
|
|
30
29
|
step((generator = generator.apply(__this, __arguments)).next());
|
|
31
30
|
});
|
|
32
31
|
};
|
|
32
|
+
|
|
33
|
+
// src/types.ts
|
|
34
|
+
function supportsSessionHandoff(identity) {
|
|
35
|
+
return typeof identity.exportSession === "function" && typeof identity.adoptSession === "function";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/constants.ts
|
|
33
39
|
var PRF_INPUT_LABEL = "cere-wallet-prf-v1";
|
|
34
|
-
var PRF_INPUT_SEED = new Uint8Array(
|
|
40
|
+
var PRF_INPUT_SEED = new Uint8Array([
|
|
41
|
+
241,
|
|
42
|
+
55,
|
|
43
|
+
252,
|
|
44
|
+
128,
|
|
45
|
+
200,
|
|
46
|
+
121,
|
|
47
|
+
231,
|
|
48
|
+
173,
|
|
49
|
+
149,
|
|
50
|
+
167,
|
|
51
|
+
104,
|
|
52
|
+
105,
|
|
53
|
+
124,
|
|
54
|
+
215,
|
|
55
|
+
6,
|
|
56
|
+
125,
|
|
57
|
+
113,
|
|
58
|
+
218,
|
|
59
|
+
113,
|
|
60
|
+
114,
|
|
61
|
+
238,
|
|
62
|
+
51,
|
|
63
|
+
232,
|
|
64
|
+
185,
|
|
65
|
+
156,
|
|
66
|
+
209,
|
|
67
|
+
26,
|
|
68
|
+
46,
|
|
69
|
+
78,
|
|
70
|
+
6,
|
|
71
|
+
58,
|
|
72
|
+
137
|
|
73
|
+
]);
|
|
35
74
|
var EVM_HKDF_INFO = "cere-wallet-evm-secp256k1-v1";
|
|
36
75
|
|
|
37
76
|
// src/browser.ts
|
|
@@ -144,7 +183,7 @@ var WebAuthnCeremonyAdapter = class {
|
|
|
144
183
|
}
|
|
145
184
|
register(opts) {
|
|
146
185
|
return __async(this, null, function* () {
|
|
147
|
-
var _a, _b, _c, _d;
|
|
186
|
+
var _a, _b, _c, _d, _e, _f;
|
|
148
187
|
if (!this.credentials) {
|
|
149
188
|
throw new Error("WebAuthnCeremonyAdapter: navigator.credentials is unavailable");
|
|
150
189
|
}
|
|
@@ -154,8 +193,11 @@ var WebAuthnCeremonyAdapter = class {
|
|
|
154
193
|
rp: { id: opts.rpId, name: opts.rpId },
|
|
155
194
|
user: {
|
|
156
195
|
id: opts.userHandle,
|
|
157
|
-
|
|
158
|
-
|
|
196
|
+
// WebAuthn convention: `name` is the account identifier (email), shown
|
|
197
|
+
// by passkey managers; `displayName` is the friendly name. Fall back to
|
|
198
|
+
// the legacy single `label`, then the product defaults.
|
|
199
|
+
name: (_b = (_a = opts.email) != null ? _a : opts.label) != null ? _b : "scp-wallet",
|
|
200
|
+
displayName: (_d = (_c = opts.name) != null ? _c : opts.label) != null ? _d : "SCP Wallet"
|
|
159
201
|
},
|
|
160
202
|
pubKeyCredParams: [
|
|
161
203
|
{ alg: -8, type: "public-key" },
|
|
@@ -185,7 +227,7 @@ var WebAuthnCeremonyAdapter = class {
|
|
|
185
227
|
const response = cred.response;
|
|
186
228
|
const transports = typeof response.getTransports === "function" ? response.getTransports() : [];
|
|
187
229
|
const ext = cred.getClientExtensionResults();
|
|
188
|
-
const prfFirst = (
|
|
230
|
+
const prfFirst = (_f = (_e = ext == null ? void 0 : ext.prf) == null ? void 0 : _e.results) == null ? void 0 : _f.first;
|
|
189
231
|
return {
|
|
190
232
|
credentialId: cred.id,
|
|
191
233
|
clientDataJSON: bytesToB64u(response.clientDataJSON),
|
|
@@ -459,6 +501,21 @@ var IdentityImpl = class {
|
|
|
459
501
|
this.cachedJwt = null;
|
|
460
502
|
/** Server-returned credential ID (used as the public identity.credentialId). */
|
|
461
503
|
this.serverCredentialId = null;
|
|
504
|
+
/**
|
|
505
|
+
* Raw derived keys for the CURRENT session, captured at every `vault.set()`
|
|
506
|
+
* call site (register / login / adoptSession). The vault deliberately hides
|
|
507
|
+
* the seeds behind its closure (Spec §3.6 invariant 1); this field is the
|
|
508
|
+
* single, explicit, same-origin-only seam that lets `exportSession()` hand a
|
|
509
|
+
* live session to a sibling wallet-origin surface (the persistent iframe).
|
|
510
|
+
* Cleared on logout / cross-tab logout alongside the vault. Never serialised
|
|
511
|
+
* to storage and never sent to a host page.
|
|
512
|
+
*/
|
|
513
|
+
this.sessionKeys = null;
|
|
514
|
+
/**
|
|
515
|
+
* Absolute expiry (epoch ms) for `exportSession()`. Tracks the cached JWT
|
|
516
|
+
* expiry after register/login; set from the adopted session in adoptSession.
|
|
517
|
+
*/
|
|
518
|
+
this.sessionExpMs = null;
|
|
462
519
|
// Public observable state — derived from vault snapshot + serverCredentialId.
|
|
463
520
|
// Kept in sync via syncPublicState() so React `observer()` wrappers around
|
|
464
521
|
// components reading isAuthenticated/addresses/credentialId actually re-render
|
|
@@ -482,6 +539,8 @@ var IdentityImpl = class {
|
|
|
482
539
|
this.vault.clear();
|
|
483
540
|
this.cachedJwt = null;
|
|
484
541
|
this.serverCredentialId = null;
|
|
542
|
+
this.sessionKeys = null;
|
|
543
|
+
this.sessionExpMs = null;
|
|
485
544
|
this.syncPublicState();
|
|
486
545
|
}
|
|
487
546
|
});
|
|
@@ -525,7 +584,9 @@ var IdentityImpl = class {
|
|
|
525
584
|
challenge: b64uDecode(start.challenge),
|
|
526
585
|
userHandle: b64uDecode(start.userHandle),
|
|
527
586
|
prfInput: PRF_INPUT_SEED,
|
|
528
|
-
label: opts.label
|
|
587
|
+
label: opts.label,
|
|
588
|
+
name: opts.name,
|
|
589
|
+
email: opts.email
|
|
529
590
|
});
|
|
530
591
|
if (!cer.prfOutput) {
|
|
531
592
|
throw new WalletError("prf-unsupported", "authenticator did not return a PRF output");
|
|
@@ -555,6 +616,8 @@ var IdentityImpl = class {
|
|
|
555
616
|
});
|
|
556
617
|
this.serverCredentialId = finish.credentialId;
|
|
557
618
|
this.cachedJwt = { token: finish.token, expMs: decodeJwtExpMs(finish.token) };
|
|
619
|
+
this.sessionKeys = keys;
|
|
620
|
+
this.sessionExpMs = this.cachedJwt.expMs;
|
|
558
621
|
this.syncPublicState();
|
|
559
622
|
});
|
|
560
623
|
}
|
|
@@ -592,6 +655,8 @@ var IdentityImpl = class {
|
|
|
592
655
|
});
|
|
593
656
|
this.serverCredentialId = finish.credentialId;
|
|
594
657
|
this.cachedJwt = { token: finish.token, expMs: decodeJwtExpMs(finish.token) };
|
|
658
|
+
this.sessionKeys = keys;
|
|
659
|
+
this.sessionExpMs = this.cachedJwt.expMs;
|
|
595
660
|
this.syncPublicState();
|
|
596
661
|
});
|
|
597
662
|
}
|
|
@@ -599,6 +664,8 @@ var IdentityImpl = class {
|
|
|
599
664
|
this.vault.clear();
|
|
600
665
|
this.cachedJwt = null;
|
|
601
666
|
this.serverCredentialId = null;
|
|
667
|
+
this.sessionKeys = null;
|
|
668
|
+
this.sessionExpMs = null;
|
|
602
669
|
this.syncPublicState();
|
|
603
670
|
this.crossTabSync.broadcast({ type: "logout" });
|
|
604
671
|
}
|
|
@@ -627,6 +694,59 @@ var IdentityImpl = class {
|
|
|
627
694
|
getVault() {
|
|
628
695
|
return this.vault;
|
|
629
696
|
}
|
|
697
|
+
/**
|
|
698
|
+
* Export the current session for a same-origin handoff (register popup →
|
|
699
|
+
* iframe). Returns `null` unless the vault is open, we captured the derived
|
|
700
|
+
* keys, and the session has not expired. The raw seeds ARE included — this is
|
|
701
|
+
* the deliberate, narrow seam described on `IdentitySession`; callers must
|
|
702
|
+
* only pass the result across the same-origin wallet BroadcastChannel.
|
|
703
|
+
*/
|
|
704
|
+
exportSession() {
|
|
705
|
+
var _a, _b, _c;
|
|
706
|
+
if (!this.vault.isOpen() || !this.sessionKeys) return null;
|
|
707
|
+
const snap = this.vault.snapshot();
|
|
708
|
+
if (!snap) return null;
|
|
709
|
+
const expMs = (_c = (_b = this.sessionExpMs) != null ? _b : (_a = this.cachedJwt) == null ? void 0 : _a.expMs) != null ? _c : 0;
|
|
710
|
+
if (expMs <= Date.now()) return null;
|
|
711
|
+
return {
|
|
712
|
+
// Copy so the caller (and the structured clone the BroadcastChannel makes)
|
|
713
|
+
// cannot mutate our live key buffers.
|
|
714
|
+
edSeed: new Uint8Array(this.sessionKeys.edSeed),
|
|
715
|
+
secpKey: new Uint8Array(this.sessionKeys.secpKey),
|
|
716
|
+
addresses: snap.addresses,
|
|
717
|
+
expMs
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Adopt a session handed over from a same-origin sibling surface. Rebuilds
|
|
722
|
+
* the `DerivedKeys` from the transported seeds (public keys are recomputed
|
|
723
|
+
* from the seeds; we do NOT re-run the HKDF step so the exact transported
|
|
724
|
+
* secpKey is preserved), opens the vault, and syncs public state — no
|
|
725
|
+
* ceremony. There is no credentialId in the handoff, so login() would need a
|
|
726
|
+
* fresh ceremony; but the session key is warm, so `sign`/`claim` (which use
|
|
727
|
+
* the vault directly, no JWT) work immediately.
|
|
728
|
+
*/
|
|
729
|
+
adoptSession(session) {
|
|
730
|
+
if (session.expMs <= Date.now()) return;
|
|
731
|
+
const edSeed = new Uint8Array(session.edSeed);
|
|
732
|
+
const secpKey = new Uint8Array(session.secpKey);
|
|
733
|
+
const keys = {
|
|
734
|
+
edSeed,
|
|
735
|
+
secpKey,
|
|
736
|
+
edPubkey: ed25519.getPublicKey(edSeed),
|
|
737
|
+
secpPubkeyUncompressed: secp256k1.getPublicKey(secpKey, false)
|
|
738
|
+
};
|
|
739
|
+
this.vault.set({
|
|
740
|
+
keys,
|
|
741
|
+
addresses: session.addresses,
|
|
742
|
+
// No credentialId travels in the handoff. login() (which needs it) will
|
|
743
|
+
// fall back to a fresh ceremony; the adopted key covers ceremony-free ops.
|
|
744
|
+
credentialId: ""
|
|
745
|
+
});
|
|
746
|
+
this.sessionKeys = keys;
|
|
747
|
+
this.sessionExpMs = session.expMs;
|
|
748
|
+
this.syncPublicState();
|
|
749
|
+
}
|
|
630
750
|
/**
|
|
631
751
|
* Release the BroadcastChannel + cross-tab subscriber. Idempotent.
|
|
632
752
|
*
|
|
@@ -662,6 +782,6 @@ var IdentityImpl = class {
|
|
|
662
782
|
}
|
|
663
783
|
};
|
|
664
784
|
|
|
665
|
-
export { CERE_SS58_PREFIX, CrossTabSync, EVM_HKDF_INFO, IdentityImpl, PRF_INPUT_LABEL, PRF_INPUT_SEED, SoftAuthenticator, WebAuthnCeremonyAdapter, b64uToBytes, bytesToB64u, createSessionVault, decodeEd25519Pubkey, deriveCereAddress, deriveEvmAddress, deriveSolanaAddress, derivedKeys, detectPrfSupport, isPrfSupportedResult };
|
|
785
|
+
export { CERE_SS58_PREFIX, CrossTabSync, EVM_HKDF_INFO, IdentityImpl, PRF_INPUT_LABEL, PRF_INPUT_SEED, SoftAuthenticator, WebAuthnCeremonyAdapter, b64uToBytes, bytesToB64u, createSessionVault, decodeEd25519Pubkey, deriveCereAddress, deriveEvmAddress, deriveSolanaAddress, derivedKeys, detectPrfSupport, isPrfSupportedResult, supportsSessionHandoff };
|
|
666
786
|
//# sourceMappingURL=index.js.map
|
|
667
787
|
//# sourceMappingURL=index.js.map
|