@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.
- package/LICENSE.md +201 -0
- package/README.md +74 -2
- package/THIRD_PARTY_NOTICES.md +504 -0
- package/dist/compose-host.mjs +11 -0
- package/dist/esm/browser/ribbon.js +489 -0
- package/dist/esm/capture/prompt.js +515 -0
- package/dist/esm/capture/visual.js +87 -0
- package/dist/esm/descriptor.js +7 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/react/VoiceAssistantCard.css +313 -0
- package/dist/esm/react/VoiceAssistantCard.js +147 -0
- package/dist/esm/react.js +3 -0
- package/dist/hosting.d.mts +4 -0
- package/dist/hosting.mjs +91 -0
- package/dist/public/sdk/voiceid/AURORA_LICENSE.txt +202 -0
- package/dist/public/sdk/voiceid/NOTICE.txt +54 -0
- package/dist/public/sdk/voiceid/SILERO_LICENSE.txt +21 -0
- package/dist/public/sdk/voiceid/addVoiceId-CeBXVHqs.js +1 -0
- package/dist/public/sdk/voiceid/approval-CCbdLhTv.js +1 -0
- package/dist/public/sdk/voiceid/aurora-int8.onnx +0 -0
- package/dist/public/sdk/voiceid/deviceBridge-wRAbqdap.js +1 -0
- package/dist/public/sdk/voiceid/enrollment-DIRoCkx5.js +1 -0
- package/dist/public/sdk/voiceid/host.js +80 -0
- package/dist/public/sdk/voiceid/ort-wasm-simd-threaded.mjs +59 -0
- package/dist/public/sdk/voiceid/ort-wasm-simd-threaded.wasm +0 -0
- package/dist/public/sdk/voiceid/ort.wasm.bundle.min-7y-hlRN8.js +3 -0
- package/dist/public/sdk/voiceid/prompt-CCrXTRRx.js +606 -0
- package/dist/public/sdk/voiceid/redimnet-b1.onnx +0 -0
- package/dist/public/sdk/voiceid/silero-vad.onnx +0 -0
- package/dist/public/sdk/voiceid/unlock-E7YHqBGT.js +1 -0
- package/dist/public/sdk/voiceid/voice-activity-worklet.js +21 -0
- package/dist/types/activation/addVoiceId.d.ts +11 -0
- package/dist/types/activation/manageEnrollment.d.ts +4 -0
- package/dist/types/activation/ownerConfirmation.d.ts +17 -0
- package/dist/types/browser/deviceStore.d.ts +19 -0
- package/dist/types/browser/enrollment.d.ts +37 -0
- package/dist/types/browser/enrollmentStore.d.ts +91 -0
- package/dist/types/browser/inference.d.ts +39 -0
- package/dist/types/browser/microphone.d.ts +61 -0
- package/dist/types/browser/model.d.ts +8 -0
- package/dist/types/browser/phrase.d.ts +35 -0
- package/dist/types/browser/phraseVocabulary.d.ts +3 -0
- package/dist/types/browser/progress.d.ts +2 -0
- package/dist/types/browser/ribbon.d.ts +63 -0
- package/dist/types/browser/verificationError.d.ts +6 -0
- package/dist/types/browser/voiceActivity.d.ts +15 -0
- package/dist/types/capture/approval.d.ts +36 -0
- package/dist/types/capture/bridge.d.ts +12 -0
- package/dist/types/capture/deviceBridge.d.ts +29 -0
- package/dist/types/capture/prompt.d.ts +41 -0
- package/dist/types/capture/surface.d.ts +27 -0
- package/dist/types/capture/unlockFrame.d.ts +13 -0
- package/dist/types/capture/visual.d.ts +24 -0
- package/dist/types/descriptor.d.ts +4 -0
- package/dist/types/host.d.ts +4 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/react/VoiceAssistantCard.d.ts +37 -0
- package/dist/types/react.d.ts +5 -0
- package/dist/types/session/activation.d.ts +61 -0
- package/dist/types/session/activationStore.d.ts +21 -0
- package/dist/types/session/httpClient.d.ts +39 -0
- package/dist/types/session/unlock.d.ts +4 -0
- package/dist/types/session/unlockFromDevice.d.ts +18 -0
- package/package.json +66 -4
- package/src/README.md +270 -0
|
@@ -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.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
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.
|