@seams/voiceid 0.24.0 → 0.26.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/README.md +31 -1
- package/dist/esm/capture/prompt.js +2 -1
- package/dist/public/sdk/voiceid/{addVoiceId-CeBXVHqs.js → addVoiceId-Bz9BDPI4.js} +1 -1
- package/dist/public/sdk/voiceid/approval-CME3DxNc.js +1 -0
- package/dist/public/sdk/voiceid/{deviceBridge-wRAbqdap.js → deviceBridge-DDviNxL6.js} +1 -1
- package/dist/public/sdk/voiceid/{enrollment-DIRoCkx5.js → enrollment-D8oQnM_e.js} +1 -1
- package/dist/public/sdk/voiceid/host.js +2 -2
- package/dist/public/sdk/voiceid/prompt-DEkI70F2.js +606 -0
- package/dist/public/sdk/voiceid/{unlock-E7YHqBGT.js → unlock-CGnhhdt4.js} +1 -1
- package/dist/types/browser/confirmationCommand.d.ts +11 -0
- package/dist/types/browser/phrase.d.ts +2 -0
- package/dist/types/browser/phraseVocabulary.d.ts +8 -1
- package/dist/types/capture/approval.d.ts +3 -1
- package/package.json +2 -2
- package/src/README.md +13 -3
- package/dist/public/sdk/voiceid/approval-CCbdLhTv.js +0 -1
- package/dist/public/sdk/voiceid/prompt-CCrXTRRx.js +0 -606
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./prompt-
|
|
1
|
+
import"./prompt-DEkI70F2.js";import{a as e,n as t}from"./deviceBridge-DDviNxL6.js";import{f as n,o as r,s as i}from"./enrollment-D8oQnM_e.js";import{IndexedDBManager as a,fetchWithWalletHomeDiscovery as o,readAuthMethodEnabled as s}from"../voiceid-host.js";async function c(e){let{authMethod:t,challenge:n}=e;if(n.walletId!==t.walletId||n.walletAuthMethodId!==t.walletAuthMethodId||n.walletAuthorityId!==t.walletAuthorityId||n.deviceKeyId!==t.deviceKeyId)throw Error(`VoiceID challenge does not match the selected device method.`);let r=await e.capture({challenge:n,signal:e.signal,onCapture:e.onCapture,phrase:e.phrase});try{if(e.signal.aborted)throw Error(`VoiceID unlock was cancelled.`);let n=await e.http.verify({assertion:r.assertion,signal:e.signal});if(e.signal.aborted)throw Error(`VoiceID unlock was cancelled.`);if(n.walletSession.walletId!==t.walletId||n.walletSession.authMethodId!==t.walletAuthMethodId||n.walletSession.authorityId!==t.walletAuthorityId||n.custodyEnvelope.factor.deviceKeyId!==t.deviceKeyId)throw Error(`VoiceID server verified a different device method.`);return await e.install(n,r.factorSecret32)}finally{r.factorSecret32.fill(0)}}async function l(t,c,l){if(!await s(t.walletId,t.walletAuthMethodId))throw Error(`VoiceID is turned off in this browser. Enable it in Authentication methods.`);let u=await t.prepareSession(c),f=await a.getWalletAuthMethodV2(t.walletAuthMethodId);if(!f||f.kind!==`voiceid`||f.status!==`active`||f.walletId!==t.walletId||f.walletAuthMethodId!==t.walletAuthMethodId)throw Error(`Activate VoiceID on this device before unlocking this wallet.`);let p=new e(t.relayerUrl,location.origin,o);if(!await r(await i(t.walletId,t.walletAuthMethodId)))throw Error(`Unlock with a passkey, then open Manage voice to import or record your enrollment.`);await n(l),c.throwIfAborted(),t.assertActive();let m=await p.challenge({walletId:t.walletId,walletAuthMethodId:t.walletAuthMethodId,signal:AbortSignal.any([c,AbortSignal.timeout(15e3)])});t.assertActive();let h={authMethod:f,challenge:m,http:p,install:u};return c.throwIfAborted(),{kind:`recording`,expiresAtMs:m.expiresAtMs,action:d.bind(null,h)}}function u(e,t,n,r){return e({walletSession:n.walletSession,operationCredential:n.operationCredential,custodyEnvelope:n.custodyEnvelope,factorSecret32:r,signal:t,near:n.near})}async function d(e,n,r,i){return await c({authMethod:e.authMethod,challenge:e.challenge,http:e.http,capture:t,install:u.bind(null,e.install,n),signal:n,onCapture:r,phrase:i})}export{l as prepareUnlockSurface};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CONFIRMATION_PHRASES } from './phraseVocabulary';
|
|
2
|
+
export type VoiceConfirmation = {
|
|
3
|
+
readonly kind: 'random';
|
|
4
|
+
readonly action?: never;
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: 'action';
|
|
7
|
+
readonly action: keyof typeof CONFIRMATION_PHRASES;
|
|
8
|
+
};
|
|
9
|
+
/** App copy selects a phrase family. It never supplies transaction terms or permissions. */
|
|
10
|
+
export declare function parseVoiceConfirmation(value: unknown): VoiceConfirmation;
|
|
11
|
+
//# sourceMappingURL=confirmationCommand.d.ts.map
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { BrowserInferenceModel } from './inference';
|
|
2
2
|
import { SPOKEN_PHRASES } from './phraseVocabulary';
|
|
3
|
+
import type { VoiceConfirmation } from './confirmationCommand';
|
|
3
4
|
export type SpokenPhrase = (typeof SPOKEN_PHRASES)[number];
|
|
4
5
|
export declare const phraseModel: BrowserInferenceModel;
|
|
5
6
|
export declare function newSpokenPhrase(previous: SpokenPhrase | null): SpokenPhrase;
|
|
7
|
+
export declare function newConfirmationPhrase(confirmation: VoiceConfirmation, previous: SpokenPhrase | null): SpokenPhrase;
|
|
6
8
|
type PhraseRecognition = {
|
|
7
9
|
kind: 'recognized';
|
|
8
10
|
phrase: SpokenPhrase;
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
export declare const
|
|
1
|
+
export declare const CHALLENGE_PHRASES: readonly ["please sign this trade now", "confirm my order for today", "i review every order carefully", "dump on them slowly", "send this order to market", "i'm here for the tech", "keep this trade on track", "the order book looks busy", "if you panic panic early", "i check the latest quote", "exit liquidity for all passengers", "no crying in the casino", "this trade fits my plan", "approve this transfer at once", "check the price before trading", "let this trade go through", "be retarded when others are fearful", "be fearful when others are retarded", "life is short go long to hedge", "but they won't trap me", "approve the swap before market close", "approve the swap when prices settle", "approve the swap after market opens", "check my order before market close", "check my order when prices settle", "check my order after market opens", "confirm the trade before market close", "confirm the trade when prices settle", "confirm the trade after market opens", "review this transfer before market close", "review this transfer when prices settle", "review this transfer after market opens"];
|
|
2
|
+
export declare const CONFIRMATION_PHRASES: {
|
|
3
|
+
readonly sale: readonly ["approve this sale at once", "let the sale go through"];
|
|
4
|
+
readonly purchase: readonly ["this purchase has my approval", "please confirm my purchase"];
|
|
5
|
+
readonly swap: readonly ["confirm my swap today", "the swap is ready to proceed"];
|
|
6
|
+
readonly transfer: readonly ["confirm this transfer today", "please send this transfer now"];
|
|
7
|
+
};
|
|
8
|
+
export declare const SPOKEN_PHRASES: readonly ["please sign this trade now", "confirm my order for today", "i review every order carefully", "dump on them slowly", "send this order to market", "i'm here for the tech", "keep this trade on track", "the order book looks busy", "if you panic panic early", "i check the latest quote", "exit liquidity for all passengers", "no crying in the casino", "this trade fits my plan", "approve this transfer at once", "check the price before trading", "let this trade go through", "be retarded when others are fearful", "be fearful when others are retarded", "life is short go long to hedge", "but they won't trap me", "approve the swap before market close", "approve the swap when prices settle", "approve the swap after market opens", "check my order before market close", "check my order when prices settle", "check my order after market opens", "confirm the trade before market close", "confirm the trade when prices settle", "confirm the trade after market opens", "review this transfer before market close", "review this transfer when prices settle", "review this transfer after market opens", "approve this sale at once", "let the sale go through", "this purchase has my approval", "please confirm my purchase", "confirm my swap today", "the swap is ready to proceed", "confirm this transfer today", "please send this transfer now"];
|
|
2
9
|
export declare const PHRASE_TOKEN_PIECES: readonly string[];
|
|
3
10
|
//# sourceMappingURL=phraseVocabulary.d.ts.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type VoiceConfirmation } from '../browser/confirmationCommand';
|
|
1
2
|
import type { AuthMethodApprovalResult } from '@seams/wallet/voiceid-host';
|
|
2
3
|
import type { ParentToChildEnvelope } from '@seams/wallet/voiceid-host';
|
|
3
4
|
import { type ConfiguredWalletHost } from '@seams/wallet/voiceid-host';
|
|
@@ -15,6 +16,7 @@ export declare class VoiceIdApproval {
|
|
|
15
16
|
private readonly reviewExpiresAtMs;
|
|
16
17
|
private readonly operationHash;
|
|
17
18
|
private readonly walletId;
|
|
19
|
+
private readonly confirmation;
|
|
18
20
|
private phrase;
|
|
19
21
|
private readonly prompt;
|
|
20
22
|
readonly surface: HTMLElement;
|
|
@@ -24,7 +26,7 @@ export declare class VoiceIdApproval {
|
|
|
24
26
|
};
|
|
25
27
|
private state;
|
|
26
28
|
private constructor();
|
|
27
|
-
static forReview(review: TransactionReviewWire, operationHash: string, walletId: WalletId): VoiceIdApproval;
|
|
29
|
+
static forReview(review: TransactionReviewWire, operationHash: string, walletId: WalletId, confirmation: VoiceConfirmation): VoiceIdApproval;
|
|
28
30
|
approve(signal: AbortSignal): Promise<AuthMethodApprovalResult>;
|
|
29
31
|
/** Binds one listening attempt to the operation and to what remains of the review. */
|
|
30
32
|
private beginAttempt;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seams/voiceid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Optional browser VoiceID authentication for Seams Wallet",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"voice-glow": "0.2.1"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@seams/wallet": "0.
|
|
53
|
+
"@seams/wallet": "0.26.0",
|
|
54
54
|
"react": "^18.0.0 || ^19.0.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependenciesMeta": {
|
package/src/README.md
CHANGED
|
@@ -142,8 +142,8 @@ and qualification criteria. The prototype uses pinned B1 and AuroraASR exports.
|
|
|
142
142
|
|
|
143
143
|
## Prompt and preparation
|
|
144
144
|
|
|
145
|
-
Enrollment, unlock, and transfer approval randomly select one of
|
|
146
|
-
trading-oriented sentences
|
|
145
|
+
Enrollment, unlock, and ordinary transfer approval randomly select one of 32 short
|
|
146
|
+
English trading-oriented sentences. The synthetic natural-paced fixtures
|
|
147
147
|
last about 1.2–2.4 seconds.
|
|
148
148
|
Each attempt holds its displayed sentence until recording ends. Retries
|
|
149
149
|
select another phrase. Read naturally, then pause. Capture stops after 1.5 seconds
|
|
@@ -153,7 +153,8 @@ recordings where live analysis is unavailable. Both speaker and phrase checks us
|
|
|
153
153
|
Existing B1 enrollment remains valid. Aurora adds a separate phrase check.
|
|
154
154
|
|
|
155
155
|
Aurora decodes the audio independently of the displayed phrase. The matcher compares
|
|
156
|
-
that text with all
|
|
156
|
+
that text with all 40 phrases. Eight additional phrases form four action-only
|
|
157
|
+
confirmation families. It ignores case, punctuation, and articles, and maps
|
|
157
158
|
selected homophones to a common spelling. It permits at most 30% normalized character
|
|
158
159
|
edit distance, requires a 10-point distance margin over the next candidate, and
|
|
159
160
|
limits decoded word count to within two words of the candidate. Negation must
|
|
@@ -164,6 +165,15 @@ enrollment, device-key use, or signing. The speaker threshold remains 0.5. A sig
|
|
|
164
165
|
keeps the original transaction review and deadline and requires another Confirm.
|
|
165
166
|
No audio or transcript leaves the browser or enters persistent storage.
|
|
166
167
|
|
|
168
|
+
Per-call `options.voiceId.confirmationCommand` selects an action-only family from
|
|
169
|
+
the first English verb: sell, buy, swap, send, or transfer. Other wording keeps
|
|
170
|
+
the default challenges. The host validates this optional configuration once and
|
|
171
|
+
rejects empty, oversized, non-string, or unknown configuration fields. Each retry
|
|
172
|
+
rotates within the family. The raw command is included in the frozen operation
|
|
173
|
+
hash. It never changes transaction decoding, operation permissions, or speaker
|
|
174
|
+
and phrase thresholds. Numbers, asset names, and prices are not spoken. The
|
|
175
|
+
application cannot supply a replacement challenge.
|
|
176
|
+
|
|
167
177
|
The finite phrase bank does not establish replay resistance or liveness. The MVP
|
|
168
178
|
still assumes physical possession of an open, active device. Spoof detection stays
|
|
169
179
|
outside scope. See [screening results](../../../../../docs/evidence/voiceid-aurora-20261024/README.md).
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{c as e,f as t,g as n,t as r,u as i}from"./prompt-CCrXTRRx.js";import{f as a,n as o,s}from"./enrollment-DIRoCkx5.js";import{ActionType as c,IndexedDBManager as l,TransactionReviewError as u,isPlainObject as d,parseTransactionReviewWire as f,resolveNearNetwork as p,walletForHostRequest as m,walletHostExecutionFingerprint as h}from"../voiceid-host.js";function g(e,t){return e.attemptId===t.attemptId&&e.operationHash===t.operationHash&&e.expiresAtMs===t.expiresAtMs}async function _(t,r,a,s,c,l){let u=Math.min(r.expiresAtMs,Date.now()+a);if(s.aborted||u<=Date.now())throw Error(`VoiceID review expired or was cancelled.`);let d=await o({kind:`active`,scope:t},s,c,l);if(s.aborted||u<=Date.now())throw Error(`VoiceID review expired or was cancelled.`);switch(d.kind){case`matched`:return{kind:`approved`};case`insufficient_audio`:return{kind:`retry`,message:e(d.quality)};case`mismatch`:return{kind:`retry`,message:new n(d.quality).message};case`phrase_mismatch`:return{kind:`retry`,message:new i().message};default:return v(d)}}function v(e){throw Error(`Unexpected voice result: ${String(e)}`)}function y(e){throw Error(`Unexpected voice outcome: ${String(e)}`)}function b(e){if(!(!d(e)&&!Array.isArray(e))){for(let t of Object.values(e))b(t);Object.freeze(e)}}async function x(e,t){switch(e.type){case`PM_SIGN_TX_WITH_ACTIONS`:case`PM_SIGN_DELEGATE_ACTION`:case`PM_SIGN_NEP413`:throw T();case`PM_SIGN_TEMPO`:{let n=e.payload;if(!n||n.operationKind!==`evm_transaction`)throw T();let{tx:r}=n.request,i=n.chainTarget;if(!t.config.network.chains.some(S)||i.kind!==`evm`||i.namespace!==`eip155`||i.networkSlug!==`arc-testnet`||i.chainId!==5042002||n.request.chain!==`evm`||n.request.kind!==`eip1559`||n.request.senderSignatureAlgorithm!==`secp256k1`||r.chainId!==i.chainId||typeof r.to!=`string`||!/^0x[0-9a-fA-F]{40}$/.test(r.to)||r.data!==void 0&&r.data!==`0x`||r.accessList!==void 0&&r.accessList.length!==0||r.abi!==void 0)throw T();return await w(e,t)}case`PM_SIGN_AND_SEND_TX`:{if(p(t.config.network.chains)!==`testnet`)throw new u(`review_unsupported_mode`,`This VoiceID prototype requires NEAR testnet.`);let n=e.payload;if(!n||n.transaction.actions.length!==1||n.transaction.actions[0].type!==c.Transfer||!(`transactionReview`in e))throw T();return await w(e,t)}default:return{kind:`disabled`}}}function S(e){return e.network===`arc-testnet`&&e.chainId===5042002}function C(e,t){return typeof t==`bigint`?{bigint:t.toString()}:t}async function w(e,t){let n=f(e.transactionReview);if(n.validity.kind!==`expires_at`)throw new u(`review_invalid_input`,`VoiceID requires a review deadline.`);b(e);let r=new TextEncoder().encode(JSON.stringify({domain:`seams-voiceid-operation-v1`,origin:location.origin,executionFingerprint:h(t.config),request:e},C)),i=await crypto.subtle.digest(`SHA-256`,r),a=``;for(let e of new Uint8Array(i))a+=e.toString(16).padStart(2,`0`);let o=m(e,null);if(!o)throw Error(`VoiceID review requires a wallet identity.`);return{kind:`required`,approval:E.forReview(n,a,o)}}function T(){return new u(`review_unsupported_mode`,`VoiceID requires a reviewed NEAR or Arc testnet transfer.`)}var E=class e{phrase=t(null);prompt;surface;get messages(){return{confirm:`Confirm with VoiceID`,approved:`Voice approved this operation.`}}state={kind:`pending`};constructor(e,t,n){this.reviewExpiresAtMs=e,this.operationHash=t,this.walletId=n,this.prompt=new r(document,`Approve transfer with VoiceID`,this.phrase,`segments`),this.prompt.record.hidden=!0,this.prompt.cancel.hidden=!0,this.surface=this.prompt.element}static forReview(t,n,r){if(t.validity.kind!==`expires_at`||!/^[0-9a-f]{64}$/.test(n))throw new u(`review_invalid_input`,`VoiceID requires a bounded operation.`);return new e(t.validity.atMs,n,r)}async approve(e){if(this.state.kind===`approved`){if(e.aborted||performance.now()>=this.state.deadline||Date.now()>=this.state.binding.expiresAtMs)throw Error(`Voice approval expired or closed.`);return{kind:`approved`}}if(this.state.kind!==`pending`)throw Error(`Voice approval already started.`);if(!navigator.userActivation.isActive||document.visibilityState!==`visible`||!document.hasFocus())throw Error(`Open the Wallet review and select Confirm to start voice capture.`);this.state={kind:`preparing`},this.prompt.status.textContent=`Loading VoiceID…`;try{let n=await l.resolveSelectedWalletAuthority(this.walletId);if(n.kind!==`resolved`||n.authMethod.kind!==`voiceid`||n.selection.lockState!==`unlocked`)throw Error(`Unlock this wallet with VoiceID before voice approval.`);let r=await s(this.walletId,n.authMethod.walletAuthMethodId);await a(this.prompt.progress),this.prompt.progress.replaceChildren();let i=this.beginAttempt(e);for(;;){let{binding:n,deadline:a,remaining:o}=i,s=await _(r,n,o,e,this.prompt.capture,this.phrase);if(this.assertListening(n),e.aborted||performance.now()>=a||Date.now()>=n.expiresAtMs)throw Error(`Voice approval expired or closed.`);switch(s.kind){case`approved`:return this.state={kind:`approved`,binding:n,deadline:a,signal:e},s;case`retry`:this.phrase=t(this.phrase),this.prompt.setPhrase(this.phrase),this.prompt.record.textContent=`Try again`,this.prompt.ready(s.message),await this.waitForRetry(e),i=this.beginAttempt(e);break;default:return y(s)}}}catch(e){throw this.state={kind:`closed`},e}}beginAttempt(e){let t=Math.min(25e3,this.reviewExpiresAtMs-Date.now());if(t<250||e.aborted)throw Error(`This voice review has expired or closed.`);let n=Object.freeze({attemptId:crypto.randomUUID(),operationHash:this.operationHash,expiresAtMs:Date.now()+t}),r=performance.now()+t;return this.prompt.record.hidden=!0,this.prompt.status.textContent=`Opening microphone…`,this.state={kind:`listening`,binding:n,deadline:r,signal:e},{binding:n,deadline:r,remaining:t}}waitForRetry(e){return new Promise((t,n)=>{let r=this.prompt.record,i=i=>{clearTimeout(s),r.removeEventListener(`click`,a),e.removeEventListener(`abort`,o),i?t():n(Error(`This voice review has expired or closed.`))},a=i.bind(null,!0),o=i.bind(null,!1),s=setTimeout(o,Math.max(0,this.reviewExpiresAtMs-Date.now()));r.addEventListener(`click`,a),e.addEventListener(`abort`,o),e.aborted&&o(),r.focus()})}consume(){let e=this.state;if(this.state={kind:`consumed`},e.kind!==`approved`||e.signal.aborted||performance.now()>=e.deadline||Date.now()>=e.binding.expiresAtMs)throw Error(`A current voice approval is required before signing.`)}assertListening(e){if(this.state.kind!==`listening`||!g(e,this.state.binding))throw Error(`This voice attempt has closed.`)}};export{x as voiceIdPolicyForRequest};
|