@seams/voiceid 0.0.0-stage → 0.24.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.
Files changed (65) hide show
  1. package/LICENSE.md +201 -0
  2. package/README.md +74 -2
  3. package/THIRD_PARTY_NOTICES.md +504 -0
  4. package/dist/compose-host.mjs +11 -0
  5. package/dist/esm/browser/ribbon.js +489 -0
  6. package/dist/esm/capture/prompt.js +515 -0
  7. package/dist/esm/capture/visual.js +87 -0
  8. package/dist/esm/descriptor.js +7 -0
  9. package/dist/esm/index.js +2 -0
  10. package/dist/esm/react/VoiceAssistantCard.css +313 -0
  11. package/dist/esm/react/VoiceAssistantCard.js +147 -0
  12. package/dist/esm/react.js +3 -0
  13. package/dist/hosting.d.mts +4 -0
  14. package/dist/hosting.mjs +91 -0
  15. package/dist/public/sdk/voiceid/AURORA_LICENSE.txt +202 -0
  16. package/dist/public/sdk/voiceid/NOTICE.txt +54 -0
  17. package/dist/public/sdk/voiceid/SILERO_LICENSE.txt +21 -0
  18. package/dist/public/sdk/voiceid/addVoiceId-CeBXVHqs.js +1 -0
  19. package/dist/public/sdk/voiceid/approval-CCbdLhTv.js +1 -0
  20. package/dist/public/sdk/voiceid/aurora-int8.onnx +0 -0
  21. package/dist/public/sdk/voiceid/deviceBridge-wRAbqdap.js +1 -0
  22. package/dist/public/sdk/voiceid/enrollment-DIRoCkx5.js +1 -0
  23. package/dist/public/sdk/voiceid/host.js +80 -0
  24. package/dist/public/sdk/voiceid/ort-wasm-simd-threaded.mjs +59 -0
  25. package/dist/public/sdk/voiceid/ort-wasm-simd-threaded.wasm +0 -0
  26. package/dist/public/sdk/voiceid/ort.wasm.bundle.min-7y-hlRN8.js +3 -0
  27. package/dist/public/sdk/voiceid/prompt-CCrXTRRx.js +606 -0
  28. package/dist/public/sdk/voiceid/redimnet-b1.onnx +0 -0
  29. package/dist/public/sdk/voiceid/silero-vad.onnx +0 -0
  30. package/dist/public/sdk/voiceid/unlock-E7YHqBGT.js +1 -0
  31. package/dist/public/sdk/voiceid/voice-activity-worklet.js +21 -0
  32. package/dist/types/activation/addVoiceId.d.ts +11 -0
  33. package/dist/types/activation/manageEnrollment.d.ts +4 -0
  34. package/dist/types/activation/ownerConfirmation.d.ts +17 -0
  35. package/dist/types/browser/deviceStore.d.ts +19 -0
  36. package/dist/types/browser/enrollment.d.ts +37 -0
  37. package/dist/types/browser/enrollmentStore.d.ts +91 -0
  38. package/dist/types/browser/inference.d.ts +39 -0
  39. package/dist/types/browser/microphone.d.ts +61 -0
  40. package/dist/types/browser/model.d.ts +8 -0
  41. package/dist/types/browser/phrase.d.ts +35 -0
  42. package/dist/types/browser/phraseVocabulary.d.ts +3 -0
  43. package/dist/types/browser/progress.d.ts +2 -0
  44. package/dist/types/browser/ribbon.d.ts +63 -0
  45. package/dist/types/browser/verificationError.d.ts +6 -0
  46. package/dist/types/browser/voiceActivity.d.ts +15 -0
  47. package/dist/types/capture/approval.d.ts +36 -0
  48. package/dist/types/capture/bridge.d.ts +12 -0
  49. package/dist/types/capture/deviceBridge.d.ts +29 -0
  50. package/dist/types/capture/prompt.d.ts +41 -0
  51. package/dist/types/capture/surface.d.ts +27 -0
  52. package/dist/types/capture/unlockFrame.d.ts +13 -0
  53. package/dist/types/capture/visual.d.ts +24 -0
  54. package/dist/types/descriptor.d.ts +4 -0
  55. package/dist/types/host.d.ts +4 -0
  56. package/dist/types/index.d.ts +2 -0
  57. package/dist/types/react/VoiceAssistantCard.d.ts +37 -0
  58. package/dist/types/react.d.ts +5 -0
  59. package/dist/types/session/activation.d.ts +61 -0
  60. package/dist/types/session/activationStore.d.ts +21 -0
  61. package/dist/types/session/httpClient.d.ts +39 -0
  62. package/dist/types/session/unlock.d.ts +4 -0
  63. package/dist/types/session/unlockFromDevice.d.ts +18 -0
  64. package/package.json +66 -4
  65. package/src/README.md +270 -0
@@ -0,0 +1,4 @@
1
+ import type { VoiceIdAuthMethodPlugin } from '@seams/wallet/voiceid-host';
2
+ /** A small opt-in descriptor with no capture, model, or host implementation. */
3
+ export declare function voiceIdAuthMethod(): VoiceIdAuthMethodPlugin;
4
+ //# sourceMappingURL=descriptor.d.ts.map
@@ -0,0 +1,4 @@
1
+ import type { WalletAuthMethodHostPlugin } from '@seams/wallet/voiceid-host';
2
+ export declare const hostApiVersion = 1;
3
+ export declare const voiceIdHostPlugin: WalletAuthMethodHostPlugin;
4
+ //# sourceMappingURL=host.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { voiceIdAuthMethod } from './descriptor';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import { type VoiceBeamLevel } from 'voice-glow';
3
+ export type VoiceAssistantInput = {
4
+ readonly kind: 'level';
5
+ /** 0–1, or a getter sampled by the glow without React renders. */
6
+ readonly level: VoiceBeamLevel;
7
+ readonly stream?: never;
8
+ } | {
9
+ readonly kind: 'microphone';
10
+ readonly stream: MediaStream;
11
+ readonly level?: never;
12
+ };
13
+ export type VoiceAssistantState = {
14
+ readonly kind: 'idle';
15
+ readonly input?: never;
16
+ } | {
17
+ readonly kind: 'listening';
18
+ readonly input: VoiceAssistantInput;
19
+ } | {
20
+ readonly kind: 'processing';
21
+ readonly input?: never;
22
+ };
23
+ export type VoiceAssistantCardProps = {
24
+ readonly name: string;
25
+ readonly initials: string;
26
+ readonly description: string;
27
+ readonly message: string;
28
+ readonly transcript: React.ReactNode;
29
+ readonly state: VoiceAssistantState;
30
+ readonly theme: 'dark' | 'light';
31
+ readonly onToggle: () => void;
32
+ readonly className?: string;
33
+ readonly style?: React.CSSProperties;
34
+ };
35
+ /** Controlled presentation. The caller owns audio, transcripts, and work. */
36
+ export declare function VoiceAssistantCard(props: VoiceAssistantCardProps): React.ReactNode;
37
+ //# sourceMappingURL=VoiceAssistantCard.d.ts.map
@@ -0,0 +1,5 @@
1
+ export { VoiceBeam } from 'voice-glow';
2
+ export type { VoiceBeamLevel, VoiceBeamProps } from 'voice-glow';
3
+ export { VoiceAssistantCard } from './react/VoiceAssistantCard';
4
+ export type { VoiceAssistantCardProps, VoiceAssistantInput, VoiceAssistantState, } from './react/VoiceAssistantCard';
5
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1,61 @@
1
+ import type { VoiceCaptureObserver } from '../browser/microphone';
2
+ import type { SpokenPhrase } from '../browser/phrase';
3
+ import { type VoiceIdSeedEnvelope } from '@seams/wallet/voiceid-host';
4
+ import { type DigestB64u } from '@seams/wallet/voiceid-host';
5
+ import type { WalletSessionOperationCredentialV1 } from '@seams/wallet/voiceid-host';
6
+ import { type PasskeyEnvelopeId, type WalletAuthMethodId, type WalletId } from '@seams/wallet/voiceid-host';
7
+ import { type VoiceIdDevicePublicKeyB64u } from '@seams/wallet/voiceid-host';
8
+ import type { ActiveVoiceIdWalletAuthMethodRecordV2 } from '@seams/wallet/voiceid-host';
9
+ import type { UnlockedWalletEd25519ExportRootCapabilityV1 } from '@seams/wallet/voiceid-host';
10
+ import { captureVoiceIdActivation } from '../capture/deviceBridge';
11
+ import type { VoiceIdHttpClient } from './httpClient';
12
+ export type PendingVoiceIdActivation = Readonly<{
13
+ kind: 'pending_voiceid_activation_v1';
14
+ walletId: WalletId;
15
+ walletAuthMethodId: WalletAuthMethodId;
16
+ devicePublicKeyB64u: VoiceIdDevicePublicKeyB64u;
17
+ deviceKeyId: DigestB64u;
18
+ custodyEnvelope: VoiceIdSeedEnvelope;
19
+ }>;
20
+ export declare function parsePendingVoiceIdActivation(raw: unknown): Promise<PendingVoiceIdActivation>;
21
+ type NewVoiceIdActivation = Readonly<{
22
+ kind: 'new';
23
+ walletId: WalletId;
24
+ walletAuthMethodId: WalletAuthMethodId;
25
+ envelopeId: PasskeyEnvelopeId;
26
+ capability: UnlockedWalletEd25519ExportRootCapabilityV1;
27
+ reseal: VoiceIdSeedResealPort;
28
+ pending?: never;
29
+ }>;
30
+ type ResumeVoiceIdActivation = Readonly<{
31
+ kind: 'resume';
32
+ pending: PendingVoiceIdActivation;
33
+ walletId?: never;
34
+ walletAuthMethodId?: never;
35
+ envelopeId?: never;
36
+ capability?: never;
37
+ reseal?: never;
38
+ }>;
39
+ export type VoiceIdSeedResealPort = (input: {
40
+ readonly capability: UnlockedWalletEd25519ExportRootCapabilityV1;
41
+ readonly bindingJson: string;
42
+ readonly factorSecret: ArrayBuffer;
43
+ }) => Promise<unknown>;
44
+ export type VoiceIdActivationPlan = NewVoiceIdActivation | ResumeVoiceIdActivation;
45
+ export type VoiceIdActivationOutcome = Readonly<{
46
+ authMethod: ActiveVoiceIdWalletAuthMethodRecordV2;
47
+ pending: PendingVoiceIdActivation;
48
+ }>;
49
+ export declare function activateVoiceIdForUnlockedWallet(input: {
50
+ readonly plan: VoiceIdActivationPlan;
51
+ readonly ownerCredential: WalletSessionOperationCredentialV1;
52
+ readonly http: Pick<VoiceIdHttpClient, 'activate'>;
53
+ readonly signal: AbortSignal;
54
+ readonly onCapture: VoiceCaptureObserver;
55
+ readonly phrase: SpokenPhrase;
56
+ readonly capture: typeof captureVoiceIdActivation;
57
+ readonly persistPending: (pending: PendingVoiceIdActivation) => Promise<void>;
58
+ readonly persistActive: (authMethod: ActiveVoiceIdWalletAuthMethodRecordV2, pending: PendingVoiceIdActivation) => Promise<void>;
59
+ }): Promise<VoiceIdActivationOutcome>;
60
+ export {};
61
+ //# sourceMappingURL=activation.d.ts.map
@@ -0,0 +1,21 @@
1
+ import { type ActiveVoiceIdWalletAuthMethodRecordV2 } from '@seams/wallet/voiceid-host';
2
+ import type { WalletAuthMethodId, WalletId } from '@seams/wallet/voiceid-host';
3
+ import { type PendingVoiceIdActivation } from './activation';
4
+ export type VoiceIdActivationStoragePort = {
5
+ getAppState(key: string): Promise<unknown | undefined>;
6
+ setAppState(key: string, value: unknown): Promise<void>;
7
+ listAppStateEntriesByPrefix(prefix: string): Promise<ReadonlyArray<{
8
+ key: string;
9
+ value: unknown;
10
+ }>>;
11
+ upsertWalletAuthMethodV2(record: ActiveVoiceIdWalletAuthMethodRecordV2): Promise<void>;
12
+ };
13
+ export declare class VoiceIdActivationStore {
14
+ private readonly storage;
15
+ constructor(storage: VoiceIdActivationStoragePort);
16
+ persistPending(pending: PendingVoiceIdActivation): Promise<void>;
17
+ persistActive(authMethod: ActiveVoiceIdWalletAuthMethodRecordV2, pending: PendingVoiceIdActivation): Promise<void>;
18
+ readPending(walletId: WalletId, walletAuthMethodId: WalletAuthMethodId): Promise<PendingVoiceIdActivation | null>;
19
+ listPending(walletId: WalletId): Promise<readonly PendingVoiceIdActivation[]>;
20
+ }
21
+ //# sourceMappingURL=activationStore.d.ts.map
@@ -0,0 +1,39 @@
1
+ import { type VoiceIdDeviceAssertion, type VoiceIdDevicePublicKeyB64u, type VoiceIdWalletSessionChallenge } from '@seams/wallet/voiceid-host';
2
+ import { type VoiceIdSeedEnvelope } from '@seams/wallet/voiceid-host';
3
+ import type { ActiveWalletSessionV1, WalletSessionOperationCredentialV1 } from '@seams/wallet/voiceid-host';
4
+ import type { DigestB64u } from '@seams/wallet/voiceid-host';
5
+ import type { WalletAuthMethodId, WalletId } from '@seams/wallet/voiceid-host';
6
+ import { type ActiveVoiceIdWalletAuthMethodRecordV2 } from '@seams/wallet/voiceid-host';
7
+ import type { AuthMethodNearInstallation } from '@seams/wallet/voiceid-host';
8
+ export type VoiceIdHttpSend = (request: Request) => Promise<Response>;
9
+ export type VoiceIdVerifiedSession = Readonly<{
10
+ near: AuthMethodNearInstallation;
11
+ walletSession: ActiveWalletSessionV1;
12
+ operationCredential: WalletSessionOperationCredentialV1;
13
+ custodyEnvelope: VoiceIdSeedEnvelope;
14
+ }>;
15
+ export declare class VoiceIdHttpClient {
16
+ private readonly relayerUrl;
17
+ private readonly send;
18
+ private readonly walletOrigin;
19
+ constructor(relayerUrl: string, walletOrigin: string, send: VoiceIdHttpSend);
20
+ activate(input: {
21
+ readonly ownerCredential: WalletSessionOperationCredentialV1;
22
+ readonly walletId: WalletId;
23
+ readonly walletAuthMethodId: WalletAuthMethodId;
24
+ readonly devicePublicKeyB64u: VoiceIdDevicePublicKeyB64u;
25
+ readonly deviceKeyId: DigestB64u;
26
+ readonly custodyEnvelope: VoiceIdSeedEnvelope;
27
+ readonly signal: AbortSignal;
28
+ }): Promise<ActiveVoiceIdWalletAuthMethodRecordV2>;
29
+ challenge(input: {
30
+ readonly walletId: WalletId;
31
+ readonly walletAuthMethodId: WalletAuthMethodId;
32
+ readonly signal: AbortSignal;
33
+ }): Promise<VoiceIdWalletSessionChallenge>;
34
+ verify(input: {
35
+ readonly assertion: VoiceIdDeviceAssertion;
36
+ readonly signal: AbortSignal;
37
+ }): Promise<VoiceIdVerifiedSession>;
38
+ }
39
+ //# sourceMappingURL=httpClient.d.ts.map
@@ -0,0 +1,4 @@
1
+ import type { AuthMethodUnlockSuccess, WalletAuthMethodHostPlugin } from '@seams/wallet/voiceid-host';
2
+ import type { PreparedVoiceCapture } from '../capture/surface';
3
+ export declare function prepareUnlockSurface(input: Parameters<WalletAuthMethodHostPlugin['unlock']>[0], signal: AbortSignal, parent: HTMLElement): Promise<PreparedVoiceCapture<AuthMethodUnlockSuccess>>;
4
+ //# sourceMappingURL=unlock.d.ts.map
@@ -0,0 +1,18 @@
1
+ import type { VoiceCaptureObserver } from '../browser/microphone';
2
+ import type { SpokenPhrase } from '../browser/phrase';
3
+ import type { VoiceIdWalletSessionChallenge } from '@seams/wallet/voiceid-host';
4
+ import type { ActiveVoiceIdWalletAuthMethodRecordV2 } from '@seams/wallet/voiceid-host';
5
+ import { captureVoiceIdUnlock } from '../capture/deviceBridge';
6
+ import type { VoiceIdHttpClient, VoiceIdVerifiedSession } from './httpClient';
7
+ /** The installer must finish custody activation before the caller reports unlock. */
8
+ export declare function verifyVoiceIdChallengeWithDevice<T>(input: {
9
+ readonly authMethod: ActiveVoiceIdWalletAuthMethodRecordV2;
10
+ readonly challenge: VoiceIdWalletSessionChallenge;
11
+ readonly http: Pick<VoiceIdHttpClient, 'verify'>;
12
+ readonly capture: typeof captureVoiceIdUnlock;
13
+ readonly install: (verified: VoiceIdVerifiedSession, factorSecret32: Uint8Array) => Promise<T>;
14
+ readonly signal: AbortSignal;
15
+ readonly onCapture: VoiceCaptureObserver;
16
+ readonly phrase: SpokenPhrase;
17
+ }): Promise<T>;
18
+ //# sourceMappingURL=unlockFromDevice.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "@seams/voiceid",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.24.0",
4
+ "type": "module",
5
+ "description": "Optional browser VoiceID authentication for Seams Wallet",
6
+ "license": "Apache-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/seams-tech/seams-wallet.git"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/types/index.d.ts",
14
+ "import": "./dist/esm/index.js"
15
+ },
16
+ "./react": {
17
+ "types": "./dist/types/react.d.ts",
18
+ "import": "./dist/esm/react.js"
19
+ },
20
+ "./hosting": {
21
+ "types": "./dist/hosting.d.mts",
22
+ "import": "./dist/hosting.mjs"
23
+ },
24
+ "./package.json": "./package.json",
25
+ "./styles": "./dist/esm/react/VoiceAssistantCard.css"
26
+ },
27
+ "files": [
28
+ "dist/",
29
+ "README.md",
30
+ "LICENSE.md",
31
+ "THIRD_PARTY_NOTICES.md",
32
+ "!dist/**/*.map",
33
+ "src/README.md"
34
+ ],
35
+ "sideEffects": [
36
+ "**/*.css"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "scripts": {
42
+ "build": "node scripts/build.mjs && tsrs -p tsconfig.build.json",
43
+ "type-check": "tsrs --noEmit",
44
+ "prepack": "node ../../scripts/package-license.mjs stage",
45
+ "postpack": "node ../../scripts/package-license.mjs clean"
46
+ },
47
+ "dependencies": {
48
+ "onnxruntime-web": "1.30.0",
49
+ "preact": "10.29.8",
50
+ "voice-glow": "0.2.1"
51
+ },
52
+ "peerDependencies": {
53
+ "@seams/wallet": "0.24.0",
54
+ "react": "^18.0.0 || ^19.0.0"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "react": {
58
+ "optional": true
59
+ }
60
+ },
61
+ "devDependencies": {
62
+ "@seams/wallet": "workspace:*",
63
+ "rolldown": "^1.0.0-beta.27"
64
+ },
65
+ "bin": {
66
+ "seams-voiceid-host": "dist/compose-host.mjs"
67
+ }
68
+ }
package/src/README.md ADDED
@@ -0,0 +1,270 @@
1
+ # Browser VoiceID plugin
2
+
3
+ Enable with `authMethods: [voiceIdAuthMethod()]`. The descriptor is small and
4
+ loads no model code. Import it from `@seams/voiceid`. The operator deploys this
5
+ package's assets alongside a matching Wallet host. The trusted Wallet registry
6
+ loads `/sdk/voiceid/host.js` on first use and checks the host API version.
7
+ The plugin owns browser microphone capture, three-sample enrollment, ReDimNet B1
8
+ speaker matching, AuroraASR phrase recognition, WebCrypto device credentials, and the existing server proof
9
+ exchange. SDK core owns custody installation, quotas, and NEAR/ECDSA MPC signing.
10
+
11
+ Availability and selection are separate. Configuring the descriptor does not apply
12
+ voice approval to passkey or Email OTP signing. The Wallet selects policy from the
13
+ requested wallet's trusted authority selection. Enrolling VoiceID does not change
14
+ the current owner method. Unlock with VoiceID to select voice approval; unlock with
15
+ a passkey to return to passkey signing. Built-in methods do not load the VoiceID
16
+ entry or inference assets, including after a reload with an enrolled voice method.
17
+ A signing lane must match its review policy, so a method change cannot reuse an
18
+ incompatible approval. Omitting the plugin while VoiceID is selected rejects the
19
+ operation; it cannot silently bypass voice approval.
20
+
21
+
22
+ After a passkey or Email OTP owner unlock, call
23
+ `registration.activateAuthMethod({ walletId, method: 'voiceid' })`.
24
+ It resolves with `{ kind: 'activated', walletId, walletAuthMethodId }`, or with
25
+ `{ kind: 'cancelled' }` when the person closes the Wallet surface.
26
+ The Wallet requests microphone permission and enrolls this wallet method through three
27
+ Record clicks. For an existing method, the same command opens Manage voice. Later use `auth.unlockWithAuthMethod` with that wallet and method
28
+ ID. A VoiceID-selected NEAR or Arc testnet transfer requires Confirm and a fresh voice match.
29
+ The button confirms intent. Read the displayed phrase at a normal pace. Use Cancel to cancel.
30
+
31
+ `browser/` contains capture, model inference, enrollment, and device storage.
32
+ No native service, extension, Keychain, or OS settings are required. The model
33
+ loads only when enabled VoiceID needs inference. Audio stays local and is
34
+ discarded after processing. Browser storage retains the template, nonextractable
35
+ Ed25519 and AES-GCM keys, and encrypted random factor. Clearing Wallet-origin
36
+ storage requires owner unlock and enrollment again. Nonextractability does not
37
+ prevent trusted Wallet-origin code from using a key; server verification proves
38
+ device-key possession, and the Wallet code enforces speaker and phrase matching.
39
+
40
+ ## Hosted model asset
41
+
42
+ Place the approved exports at `packages/wallet/assets/voiceid/redimnet-b1.onnx`
43
+ and `packages/wallet/assets/voiceid/aurora-int8.onnx` before building the SDK. The existing
44
+ static-asset build includes it and the matching ONNX Runtime Web WASM files.
45
+ If the optional model is absent, ordinary Wallet builds still work, and enabling
46
+ VoiceID reports that its model is unavailable. Release builds offering VoiceID
47
+ must provision both assets. It is not fetched from a third party during capture.
48
+
49
+ Current model: ReDimNet B1, VoxCeleb2 large-margin fine-tuned, MIT license.
50
+ Source revision: `ce039a624cb99fe127702ceb94c6080090e5032f`.
51
+ Weight revision: `5180983b6895dafa0cd88aa88e8a07296df27834`.
52
+ The full-waveform export accepts 16 kHz mono PCM and returns a normalized
53
+ 192-dimensional embedding. Model bytes: 8,859,593.
54
+ SHA-256: `e04dd7a6a1e23fc7b89e47682ea9d0a3946cf7bc59d07922c3db3efc52ae4269`.
55
+ The deployment includes the model's MIT attribution and export details in NOTICE.txt.
56
+ Use the pinned exporter in `tests/scripts/voiceid-model-evaluation/prepare.py`
57
+ to reproduce the model. The browser evaluation evidence records parity checks.
58
+
59
+ The prototype uses a B1 threshold of 0.5: the next tenth above the four-second
60
+ calibration cohort's maximum impostor score (0.4417). The separate evaluation
61
+ cohort had 0/728 false accepts and 2/56 false rejects at this threshold. This small
62
+ clean-speech experiment does not qualify production authentication accuracy.
63
+
64
+ AuroraASR-1M checks the spoken phrase. Its INT8 full-waveform ONNX export is
65
+ 1,342,384 bytes and shares the existing WASM runtime. It accepts 16 kHz mono PCM.
66
+ Source revision: `cd307b54b4d666d6ad7dc982a7a58a96d6e7f942`.
67
+ SHA-256: `83ed6f3856458cd96d49a70d8c5175aeec18ba8c184e5367e089cd424eebefd6`.
68
+ The deployment includes its Apache-2.0 license and export details.
69
+ See [export and screening instructions](../../../../../tests/scripts/voiceid-aurora/README.md).
70
+
71
+ ## Download progress and browser cache
72
+
73
+ First activation shows an accessible progress bar with bytes and percentage while
74
+ each model downloads. Enrollment recording becomes available after preparation.
75
+ The trusted Wallet origin stores only hash-verified model bytes in Cache Storage,
76
+ under a key containing the model digest. A reload reads and verifies that cached
77
+ copy before loading inference. The normal HTTP cache also applies to runtime assets.
78
+
79
+ Storage can be unavailable or full. In that case inference continues for the
80
+ current session and the UI explains that the model could not be saved. Clearing
81
+ site data or browser eviction can require another download. A corrupted cached
82
+ model is discarded and downloaded again. Failed loads remain retryable.
83
+
84
+ ## Re-enrollment after a model change
85
+
86
+ The persistence boundary treats an enrollment for a different model as unavailable.
87
+ Voice-only unlock cannot use it. The user must first unlock with a passkey and
88
+ select Manage voice, then Record voice again. Activation reuses an existing local device
89
+ credential for that wallet and authority, then replaces the speaker template only
90
+ after all three samples succeed. It does not create another server method or change
91
+ the existing custody envelope. Cancellation preserves the prior stored record.
92
+
93
+ ## Manual adaptive enrollment
94
+
95
+ Manage voice shows whether the enrollment is original or improved, and the
96
+ remaining checks and time of an improvement. It provides Improve recognition,
97
+ Reset improvements, and Record voice again, and shows only the actions that apply.
98
+ Each action requires a fresh passkey confirmation. The trusted
99
+ Wallet validates the assertion's signature, challenge, origin, RP ID, user presence,
100
+ and user verification. ES256 passkey credentials use the existing public-key crypto
101
+ worker for COSE and signature decoding, then WebCrypto for signature verification. No credential PRF
102
+ output is requested. Confirmation authorizes one local enrollment change only.
103
+ Both ES256 and RS256 passkey credentials are supported. RS256 uses WebCrypto and
104
+ the existing small CBOR dependency for its RSA public key. These are passkey algorithms.
105
+ Wallet transaction signers remain ECDSA/secp256k1 and Ed25519. VoiceID signing currently
106
+ supports Ed25519-Yao NEAR transfers and native ECDSA Arc transfers on testnet. The server must still recognize the
107
+ same owner session before confirmation and before saving; no signing quota is consumed.
108
+
109
+ Enrollment storage binds wallet, authority, method, and device key. An atomic commit
110
+ checks owner selection, lock generation, availability revision, and enrollment
111
+ generation/revision. A competing update cannot be overwritten. Capture watches
112
+ owner lifecycle changes and releases the microphone if the action becomes invalid.
113
+
114
+ Three accepted supplements contribute 20% of the effective template; the immutable
115
+ baseline contributes 80%. Each supplement must match the baseline at 0.5. The final
116
+ template must stay within cosine 0.98 of the baseline. Supplements remain saved
117
+ without a time or usage limit. Reset removes them and preserves the baseline. Re-recording creates a
118
+ new baseline generation. These are bounded experimental settings, not calibrated
119
+ accuracy or false-acceptance guarantees. Automatic learning is not implemented.
120
+
121
+ Old browser-wide records cannot authenticate directly. Manage voice offers an
122
+ explicit, passkey-confirmed import for the selected method. This migration boundary
123
+ is the only reader of the old record. Other wallets require their own import or
124
+ enrollment. Import deletes it once all known active VoiceID methods have scoped records.
125
+
126
+ No additional model weights are needed. Fresh passkey confirmation can load the
127
+ existing crypto worker and WASM if uncached: about 1.34 MB uncompressed or 0.31 MB
128
+ with local gzip compression, plus management JavaScript. This is separate from the
129
+ 26.86 MB uncompressed voice models and inference runtime. Actual network transfer
130
+ depends on compression and caching. The local enrollment record has a 32 KiB limit.
131
+
132
+ To replace the implementation, preserve `WalletAuthMethodHostPlugin`, the device
133
+ assertion contract, and method-bound custody envelopes. To remove VoiceID, remove
134
+ its registry entry, descriptor exports, implementation directory, ONNX dependency,
135
+ and optional assets. Omitting the descriptor prevents runtime use and downloads.
136
+
137
+ ## Smaller models and spoken phrases
138
+
139
+ See the [model candidate evaluation plan](../../../../../docs/voiceid-model-candidates.md)
140
+ for verified download sizes, speaker-verification and STT shortlists, licensing,
141
+ and qualification criteria. The prototype uses pinned B1 and AuroraASR exports.
142
+
143
+ ## Prompt and preparation
144
+
145
+ Enrollment, unlock, and transfer approval randomly select one of 16 short English
146
+ trading-oriented sentences of 4–6 words. The synthetic natural-paced fixtures
147
+ last about 1.2–2.4 seconds.
148
+ Each attempt holds its displayed sentence until recording ends. Retries
149
+ select another phrase. Read naturally, then pause. Capture stops after 1.5 seconds
150
+ of quiet once audible input has continued for 100 ms. Initial silence does not
151
+ stop capture. An eight-second maximum bounds silence, continued noise, and
152
+ recordings where live analysis is unavailable. Both speaker and phrase checks use the same recording.
153
+ Existing B1 enrollment remains valid. Aurora adds a separate phrase check.
154
+
155
+ Aurora decodes the audio independently of the displayed phrase. The matcher compares
156
+ that text with all 16 phrases. It ignores case, punctuation, and articles, and maps
157
+ selected homophones to a common spelling. It permits at most 30% normalized character
158
+ edit distance, requires a 10-point distance margin over the next candidate, and
159
+ limits decoded word count to within two words of the candidate. Negation must
160
+ match the candidate phrase; added, omitted, or changed negation rejects. These are development bounds for an approximate challenge. Some changed
161
+ content words can pass; this check does not interpret transaction intent.
162
+ A different recognized phrase, ambiguity, or an out-of-bounds result prevents
163
+ enrollment, device-key use, or signing. The speaker threshold remains 0.5. A signing retry
164
+ keeps the original transaction review and deadline and requires another Confirm.
165
+ No audio or transcript leaves the browser or enters persistent storage.
166
+
167
+ The finite phrase bank does not establish replay resistance or liveness. The MVP
168
+ still assumes physical possession of an open, active device. Spoof detection stays
169
+ outside scope. See [screening results](../../../../../docs/evidence/voiceid-aurora-20261024/README.md).
170
+
171
+ The prompt reports its dimensions through the Wallet request measurement binding.
172
+ This reveals the parent modal immediately after layout, without its four-second
173
+ fallback. The reporter watches the stable outer section during enrollment changes
174
+ and disconnects when the prompt closes.
175
+
176
+ The lazy host entry contains the prompt shell. It mounts the modal and yields a
177
+ frame before importing the unlock or activation implementation. The card shows
178
+ Loading VoiceID and allows cancellation while its recording button is disabled.
179
+ Unlock then prepares its core session installer, including lock cleanup and a new
180
+ lock generation. It checks local state, loads the cached model if needed, and
181
+ requests its server challenge. One cumulative indicator covers the engine WASM
182
+ and all three models inside the same card. Verified cached bytes count toward the
183
+ same total. All four downloads finish before the card shows “Preparing VoiceID…”
184
+ and creates inference sessions. The streamed engine bytes go directly to ONNX
185
+ Runtime, so initialization does not fetch that binary again. The engine uses the
186
+ same hash-verified browser cache as the models. If storage is unavailable, loading
187
+ still works for the current session. Transaction approval uses the same preparation
188
+ before starting its bounded recording attempt.
189
+ The lightweight shell itself still needs its first module download.
190
+ Unlock starts listening automatically after preparation. The initial Unlock with
191
+ VoiceID action opens this flow. Back cancels capture and returns to the login menu.
192
+ Retries require Try again. The card uses the wallet's Back icon and Seams wordmark,
193
+ with one rounded surface and a transparent activation container.
194
+ Cancellation aborts the challenge request and prevents late UI or recording.
195
+ Shared model loading can finish and populate the cache after cancellation.
196
+
197
+ ## Minimal capture UI
198
+
199
+ Enrollment, unlock, and signing share the plugin-owned `VoicePrompt`. The card shows
200
+ Read aloud, a large phrase, a live waveform, a short status, and Cancel. Enrollment
201
+ also shows the sample number and a Record control between samples. The record
202
+ control disappears during capture. Download details disappear when preparation ends.
203
+
204
+ After transaction Confirm, the Wallet replaces the review contents with the plugin's
205
+ trusted DOM surface. The Wallet retains its modal, cancellation, expiry, and review
206
+ binding. A mismatch returns to the original review and requires another Confirm.
207
+ The core host loads no VoiceID UI code when the plugin is disabled.
208
+
209
+ The prompt combines a thin, four-layer flowing ribbon with the SDK's existing
210
+ VoiceBeam glow. Both use the recorder's activity levels; neither opens an audio
211
+ graph or requests a microphone. Speech drives full movement and ambient sound
212
+ is restrained. The ribbon settles when recording ends and the glow indicates
213
+ processing. Reduced motion removes ribbon travel and the glow honors the system
214
+ preference. Disposal cancels the ribbon clock and unmounts the glow.
215
+ The visual is loaded through the optional VoiceID host module. It cannot grant
216
+ approval or change capture, phrase, speaker, or signing policy.
217
+
218
+ ## Recording quality feedback
219
+
220
+ Short or quiet recordings stay in the unlock prompt with Try again. The original
221
+ server challenge and expiry timer remain unchanged. Only typed insufficient-audio
222
+ results can take this path. A retry displays a new phrase before capture starts.
223
+ The normal phrase and speaker checks still apply before device-key use.
224
+
225
+ The prompt shows brief failure guidance and a retry control. It exposes no
226
+ Recording details expander or technical recognition text. Internal measurements
227
+ retain the input label, duration, stop reason, and frame levels for behavior checks. The
228
+ level threshold is 0.006. The quality precheck requires 600 ms of qualifying audio.
229
+ Aurora phrase and B1 speaker thresholds are unchanged. This energy proxy cannot generally separate
230
+ short speech from quiet or suppressed speech. Each measurement has an attempt ID
231
+ and timestamp. No audio or transcript is saved or sent by these diagnostics.
232
+
233
+
234
+ ## Settings and SeamsAuthMenu
235
+
236
+ The VoiceID card in Authentication methods shows the enable/disable switch, the
237
+ state in this browser, and **Manage voice**.
238
+ Management requires an enabled VoiceID method and an active owner session.
239
+ Each change requires a fresh passkey assertion from the same authority. VoiceID
240
+ can remain selected while that passkey confirms the change. Use
241
+ `registration.getAuthMethodAvailability({ walletId, method: 'voiceid' })` to read
242
+ `not_enrolled`, `enabled`, or `disabled`. Use
243
+ `registration.setAuthMethodEnabled({ walletId, method: 'voiceid', enabled })`
244
+ to change the browser preference. It preserves enrollment. Enable requires an
245
+ owner passkey or Email OTP session. Disable requires another active method and
246
+ locks a current VoiceID session. Existing enrollments without a preference start
247
+ enabled at the persistence boundary. Removal is a separate revocation action.
248
+
249
+ The login menu lists a wallet once, with its available methods below. Discovery
250
+ reads local metadata without loading the voice models. Voice login uses the menu's
251
+ measurement and cancellation channel; the old menu is hidden and inert during
252
+ capture. Back restores it. The minimal signing prompt is unchanged, with an
253
+ explicit “Confirm with VoiceID” review button. Capture remains click initiated.
254
+
255
+ ## Background noise
256
+
257
+ Silero VAD adds 2,327,524 bytes, loaded only with VoiceID and cached with integrity
258
+ verification. Browser noise suppression, echo cancellation, and automatic gain
259
+ control remain requested. High-noise recordings receive brief retry guidance.
260
+
261
+ Speech detection replaces volume alone for pause detection and clip qualification.
262
+ It retains the 1.5-second pause and eight-second recording cap. Speech probability
263
+ must reach 0.5 and the original minimum volume must also pass. The speaker
264
+ threshold remains 0.5. Phrase matching retains the bounded text-difference and
265
+ candidate-margin checks described above.
266
+
267
+ Noise estimates compare frames classified as speech with other frames. They are
268
+ approximate guidance, not a calibrated acoustic SNR or an authorization decision.
269
+ Nearby conversation can still register as speech. No extra denoiser is shipped.
270
+ See `tests/scripts/voiceid-noise/README.md` for model provisioning and evaluation.