From 3eae9dc777136ce575f03f66318aa448828bc17e Mon Sep 17 00:00:00 2001 From: "Seshanth.S" <35675963+seshanthS@users.noreply.github.com> Date: Tue, 24 Mar 2026 17:51:38 +0530 Subject: [PATCH] docs: add WV-07 and WV-08 specs for proving machine in webview-app (#1861) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WV-07 covers SelfClient assembly: exporting useProvingStore from the browser entry point, mapping bridge adapters to SDK interfaces, creating a keychain-backed DocumentsAdapter via the existing secureStorage bridge, and wiring a real SelfClient in the webview-app provider. WV-08 covers the tunnel proving flow: replacing the mock 3-second timer with real provingMachine integration (register → disclose), storing Sumsub KYC results as KycData documents in native keychain, and driving UI from proving state transitions. Co-authored-by: Claude Opus 4.6 --- .../projects/sdk/workstreams/webview/SPEC.md | 4 + .../WV-07-selfclient-proving-assembly.md | 317 ++++++++++++++++++ .../plans/WV-08-tunnel-proving-flow.md | 262 +++++++++++++++ 3 files changed, 583 insertions(+) create mode 100644 specs/projects/sdk/workstreams/webview/plans/WV-07-selfclient-proving-assembly.md create mode 100644 specs/projects/sdk/workstreams/webview/plans/WV-08-tunnel-proving-flow.md diff --git a/specs/projects/sdk/workstreams/webview/SPEC.md b/specs/projects/sdk/workstreams/webview/SPEC.md index 213f82a8a..7e780d46b 100644 --- a/specs/projects/sdk/workstreams/webview/SPEC.md +++ b/specs/projects/sdk/workstreams/webview/SPEC.md @@ -52,6 +52,8 @@ On **March 11, 2026**, the active SDK scope changed to **WebView only, with no c | WV-04 | Define the host callback contract for launch, dismiss, and final result without native modules | Done | Medium | WV-02 | [plans/WV-04-host-callback-contract.md](./plans/WV-04-host-callback-contract.md) | Browser host fallback now uses `postMessage` for iframe/popup embedding while native transports keep their current behavior | | WV-05 | Integrate KYC provider Web SDK into ProviderLaunchScreen (Sumsub as default) | In Progress | High | WV-02 | [plans/WV-05-sumsub-web-sdk.md](./plans/WV-05-sumsub-web-sdk.md) | Code complete on `feat/webview-sdk`, needs testing | | WV-06 | Wire KYC result through verification pipeline to host lifecycle callback | Ready | High | WV-05 | [plans/WV-06-kyc-result-flow.md](./plans/WV-06-kyc-result-flow.md) | Sumsub result → kycResultStore → ConfirmIdentificationScreen → lifecycle.setResult() | +| WV-07 | SelfClient assembly and proving machine export for WebView | Ready | High | SC-03 | [plans/WV-07-selfclient-proving-assembly.md](./plans/WV-07-selfclient-proving-assembly.md) | Export useProvingStore, map bridge→SDK adapters, keychain-backed documents, create real SelfClient | +| WV-08 | Wire tunnel flow with real proving machine (register → disclose) | Ready | High | WV-07 | [plans/WV-08-tunnel-proving-flow.md](./plans/WV-08-tunnel-proving-flow.md) | Replace mock tunnel proving with real provingMachine: Sumsub → store doc → prove → disclose → result | Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done` @@ -65,6 +67,8 @@ Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done` | [plans/WV-04-host-callback-contract.md](./plans/WV-04-host-callback-contract.md) | WV-04 | Done | | [plans/WV-05-sumsub-web-sdk.md](./plans/WV-05-sumsub-web-sdk.md) | WV-05 | In Progress (code complete, needs testing) | | [plans/WV-06-kyc-result-flow.md](./plans/WV-06-kyc-result-flow.md) | WV-06 | Ready | +| [plans/WV-07-selfclient-proving-assembly.md](./plans/WV-07-selfclient-proving-assembly.md) | WV-07 | Ready | +| [plans/WV-08-tunnel-proving-flow.md](./plans/WV-08-tunnel-proving-flow.md) | WV-08 | Ready | ## Completion Checklist diff --git a/specs/projects/sdk/workstreams/webview/plans/WV-07-selfclient-proving-assembly.md b/specs/projects/sdk/workstreams/webview/plans/WV-07-selfclient-proving-assembly.md new file mode 100644 index 000000000..d80866502 --- /dev/null +++ b/specs/projects/sdk/workstreams/webview/plans/WV-07-selfclient-proving-assembly.md @@ -0,0 +1,317 @@ +# WV-07: SelfClient Assembly & Proving Machine Export for WebView + +> Last updated: 2026-03-24 +> Status: Ready +> Priority: High +> Depends on: SC-03 (Ready — creates `createWebNetworkAdapter()`) + +- Workstream: webview +- Backlog ID: WV-07 +- Owner: TBD +- Branch: TBD +- PR: TBD + +## Why + +The webview-app needs to run the proving machine for ZK proof generation, but +today it has no `SelfClient` — only raw bridge adapters with incompatible +interfaces. The proving machine (`useProvingStore`) is not exported from the +SDK's browser entry point, and no adapter mapping exists to bridge the gap +between `BridgeCryptoAdapter` / `BridgeStorageAdapter` and the SDK's +`CryptoAdapter` / `DocumentsAdapter` interfaces. + +This spec wires the missing pieces so WV-08 can drive the proving machine from +the tunnel flow. + +## What You Will Do + +### 1. Export proving machine from browser entry point + +**File:** `packages/mobile-sdk-alpha/src/browser.ts` + +The proving machine is already browser-compatible (zero RN imports). Add value +exports alongside the existing type-only exports: + +```typescript +// Existing (keep): +export type { ProvingStateType, provingMachineCircuitType } from './proving/provingMachine'; + +// Add: +export type { ProvingState } from './proving/provingMachine'; +export { useProvingStore, getPostVerificationRoute } from './proving/provingMachine'; +``` + +Additive-only — no RN regression risk. + +### 2. Create a keychain-backed DocumentsAdapter + +**Create:** `packages/webview-bridge/src/adapters/keychain-documents.ts` + +The SDK's `DocumentsAdapter` interface requires structured document CRUD. The +webview-app must persist passport data in the native keychain (security +boundary), not IndexedDB. Use the existing `secureStorage` bridge domain. + +```typescript +import type { WebViewBridge } from '../bridge'; +import type { DocumentsAdapter, DocumentCatalog, IDDocument } from '@selfxyz/mobile-sdk-alpha'; + +const CATALOG_KEY = 'self_document_catalog'; +const DOC_PREFIX = 'self_doc_'; + +export function createKeychainDocumentsAdapter(bridge: WebViewBridge): DocumentsAdapter { + async function storageGet(key: string): Promise { + const result = await bridge.request<{ value: string | null }>( + 'secureStorage', 'get', { key }, + ); + return result?.value ?? null; + } + + async function storageSet(key: string, value: string): Promise { + await bridge.request('secureStorage', 'set', { key, value }); + } + + async function storageRemove(key: string): Promise { + await bridge.request('secureStorage', 'remove', { key }); + } + + return { + async loadDocumentCatalog(): Promise { + const raw = await storageGet(CATALOG_KEY); + return raw ? JSON.parse(raw) : { documents: [], selectedId: null }; + }, + async saveDocumentCatalog(catalog: DocumentCatalog): Promise { + await storageSet(CATALOG_KEY, JSON.stringify(catalog)); + }, + async loadDocumentById(id: string): Promise { + const raw = await storageGet(`${DOC_PREFIX}${id}`); + return raw ? JSON.parse(raw) : null; + }, + async saveDocument(id: string, doc: IDDocument): Promise { + await storageSet(`${DOC_PREFIX}${id}`, JSON.stringify(doc)); + }, + async deleteDocument(id: string): Promise { + await storageRemove(`${DOC_PREFIX}${id}`); + }, + }; +} +``` + +Export from `packages/webview-bridge/src/adapters/index.ts` barrel. + +### 3. Create SDK adapter mapping functions + +**Create:** `packages/webview-bridge/src/adapters/sdk-adapter-map.ts` + +Bridge adapters have different interfaces from SDK adapters. Create thin mapping +functions. The bridge crypto adapter already implements the right methods +(`hash`, `sign`, `generateKey`, `getPublicKey`) — it just needs type coercion. + +```typescript +import type { WebViewBridge } from '../bridge'; +import type { + Adapters, + CryptoAdapter, + AuthAdapter, + NavigationAdapter, + NetworkAdapter, + NFCScannerAdapter, +} from '@selfxyz/mobile-sdk-alpha'; +import { bridgeCryptoAdapter } from './crypto'; +import { bridgeAuthAdapter } from './auth'; +import { createKeychainDocumentsAdapter } from './keychain-documents'; +import { + createWebAnalyticsAdapter, + createWebNetworkAdapter, // from SC-03 + webNFCScannerShim, +} from '@selfxyz/mobile-sdk-alpha/browser'; + +export interface CreateSdkAdaptersOpts { + bridge: WebViewBridge; + navigate: (path: string) => void; + goBack: () => void; +} + +export function createSdkAdapters(opts: CreateSdkAdaptersOpts): Adapters { + const { bridge, navigate, goBack } = opts; + const bridgeCrypto = bridgeCryptoAdapter(bridge); + + const crypto: CryptoAdapter = { + hash: bridgeCrypto.hash, + sign: bridgeCrypto.sign, + generateKey: bridgeCrypto.generateKey, + getPublicKey: bridgeCrypto.getPublicKey, + }; + + const bridgeAuth = bridgeAuthAdapter(bridge); + const auth: AuthAdapter = { + getPrivateKey: bridgeAuth.getPrivateKey, + }; + + const navigation: NavigationAdapter = { + goBack, + goTo: (routeName, params) => { + const query = params ? `?${new URLSearchParams(params as Record)}` : ''; + navigate(`/${routeName}${query}`); + }, + }; + + return { + scanner: webNFCScannerShim(), + crypto, + network: createWebNetworkAdapter(), + auth, + documents: createKeychainDocumentsAdapter(bridge), + navigation, + analytics: createWebAnalyticsAdapter(), + }; +} +``` + +Export from `packages/webview-bridge/src/adapters/index.ts` barrel. + +### 4. Replace SelfClientProvider with real SelfClient + +**File:** `packages/webview-app/src/providers/SelfClientProvider.tsx` + +Replace the current `SelfClientAdapters` bag-of-adapters with a real +`SelfClient` instance created via `createSelfClient()`. + +```typescript +import { createSelfClient, createListenersMap } from '@selfxyz/mobile-sdk-alpha/browser'; +import { createSdkAdapters } from '@selfxyz/webview-bridge/adapters'; + +// Replace SelfClientAdapters type with SelfClient from SDK +// Keep lifecycle, haptic, biometrics as supplementary adapters +// (they are WebView-specific and not part of SDK Adapters interface) + +export interface WebViewAdapters { + client: SelfClient; // real SDK client with provingMachine access + lifecycle: BridgeLifecycleAdapter; + haptic: BridgeHapticAdapter; + biometrics: BridgeBiometricsAdapter; +} +``` + +The `SelfClient` instance gives webview-app access to: +- `client.useProvingStore` — Zustand hook for proving state +- `client.getProvingState()` — snapshot accessor +- `client.emit()` / `client.on()` — event system +- All internal stores and adapters the proving machine needs + +### 5. Add dependencies and Buffer polyfill to webview-app + +**File:** `packages/webview-app/package.json` + +Add packages externalized by the SDK's tsup build: + +| Package | Version | Why | +|--------------------|---------|----------------------------------| +| `socket.io-client` | ^4.8.3 | TEE status WebSocket listener | +| `xstate` | ^5.20.2 | Internal state machine | +| `node-forge` | ^1.3.3 | AES-256-GCM encryption | +| `buffer` | ^6.0.3 | Node.js Buffer polyfill | +| `elliptic` | ^6.5.4 | Crypto ops via @selfxyz/common | + +`zustand` and `@selfxyz/common` are already deps. + +**File:** `packages/webview-app/src/main.tsx` + +Add at the very top (before any other imports): + +```typescript +import { Buffer } from 'buffer'; +globalThis.Buffer = Buffer; +``` + +The proving machine uses `Buffer` at lines 196, 219, 567, 606, 826. + +### 6. Update SelfClientProvider to pass config + +**File:** `packages/webview-app/src/providers/SelfClientProvider.tsx` + +Pass required config fields to `createSelfClient()`: + +```typescript +const client = useMemo(() => { + const adapters = createSdkAdapters({ bridge, navigate, goBack: () => navigate(-1) }); + const listeners = createListenersMap(); + return createSelfClient({ + config: { + platform: 'webview', + debug: import.meta.env.DEV, + env: verificationRequest?.env ?? 'prod', + }, + adapters, + listeners, + }); +}, [bridge, navigate]); +``` + +## Files You Will Create + +| File | What | Risk | +|-----------------------------------------------------------|---------------------------------------------------|----------| +| `packages/webview-bridge/src/adapters/keychain-documents.ts` | Keychain-backed DocumentsAdapter via secureStorage | **Low** | +| `packages/webview-bridge/src/adapters/sdk-adapter-map.ts` | Bridge→SDK adapter mapping + factory | **Low** | + +## Files You Will Modify + +| File | Change | Risk | +|--------------------------------------------------------------|------------------------------------------------------|------------| +| `packages/mobile-sdk-alpha/src/browser.ts` | Add `useProvingStore`, `ProvingState` exports | **Low** | +| `packages/webview-bridge/src/adapters/index.ts` | Add barrel exports for new adapters | **Low** | +| `packages/webview-app/src/providers/SelfClientProvider.tsx` | Replace adapter bag with real SelfClient | **Medium** | +| `packages/webview-app/package.json` | Add 5 dependencies | **Low** | +| `packages/webview-app/src/main.tsx` | Add Buffer polyfill (2 lines) | **Low** | +| `specs/projects/sdk/workstreams/webview/SPEC.md` | Add WV-07 to backlog | **None** | + +## Files You Will NOT Modify + +| File | Why | +|---------------------------------------------------------------|--------------------------------------------------------------| +| `packages/mobile-sdk-alpha/src/proving/provingMachine.ts` | Engine is already browser-compatible; no changes needed | +| `packages/mobile-sdk-alpha/src/client.ts` | `createSelfClient()` factory is correct as-is | +| `packages/native-shell-android/**` | secureStorage handler already exists and handles JSON values | +| `packages/native-shell-ios/**` | secureStorage handler already exists and handles JSON values | +| `packages/webview-app/src/screens/**` | Screen wiring is WV-08 scope | + +## Constraints + +- **No regressions in the RN app.** Browser.ts exports are additive. No + existing exports change. +- **Keychain is always native-managed.** Documents go through secureStorage + bridge to native keychain. No IndexedDB fallback for passport data. +- **Bridge protocol unchanged.** No new bridge domains. Uses existing + `secureStorage` domain for document persistence. +- **Don't wire screens.** This spec creates the SelfClient and makes + provingMachine accessible. WV-08 wires screens to it. +- **SC-03 must land first.** `createWebNetworkAdapter()` is imported by the + adapter mapping layer. + +## Validation + +```bash +# SDK types still pass (additive exports only) +cd packages/mobile-sdk-alpha && yarn types + +# Bridge builds +cd packages/webview-bridge && yarn build + +# webview-app builds with new imports +cd packages/webview-app && yarn build +``` + +## Definition of Done + +- [ ] `useProvingStore` and `ProvingState` exported from `browser.ts` +- [ ] `createKeychainDocumentsAdapter()` persists documents via secureStorage bridge +- [ ] `createSdkAdapters()` maps bridge adapters to SDK Adapters interface +- [ ] `SelfClientProvider` creates a real `SelfClient` via `createSelfClient()` +- [ ] Buffer polyfill added to webview-app entry point +- [ ] `yarn types` clean in mobile-sdk-alpha +- [ ] `yarn build` clean in webview-bridge and webview-app +- [ ] Backlog row added in SPEC.md + +## Status Log + +- 2026-03-24: Plan created. diff --git a/specs/projects/sdk/workstreams/webview/plans/WV-08-tunnel-proving-flow.md b/specs/projects/sdk/workstreams/webview/plans/WV-08-tunnel-proving-flow.md new file mode 100644 index 000000000..5f9e6e025 --- /dev/null +++ b/specs/projects/sdk/workstreams/webview/plans/WV-08-tunnel-proving-flow.md @@ -0,0 +1,262 @@ +# WV-08: Wire Tunnel Flow with Real Proving Machine + +> Last updated: 2026-03-24 +> Status: Ready +> Priority: High +> Depends on: WV-07 (Ready — SelfClient assembly + proving machine export) + +- Workstream: webview +- Backlog ID: WV-08 +- Owner: TBD +- Branch: TBD +- PR: TBD + +## Why + +The tunnel flow is currently a pure UI mockup — hardcoded data, a 3-second +timer for "proving," and no real backend interaction. With WV-07 landing +`SelfClient` and `useProvingStore` in webview-app, the tunnel flow can drive +real ZK proof generation. + +The tunnel flow is the first end-to-end integration: **Sumsub → store document +in keychain → provingMachine (register) → disclose → lifecycle.setResult()**. +Getting this working validates the entire WebView proving pipeline. + +## Prerequisites + +- **WV-07 done** — `SelfClient` available in webview-app, `useProvingStore` + accessible, keychain-backed documents adapter working +- **WV-05 done** — Sumsub Web SDK integrated in `ProviderLaunchScreen` +- **WV-06 done** — KYC result normalization into `KycProviderResult` + +## What You Will Do + +### 1. Wire Sumsub result → document storage + +**File:** `packages/webview-app/src/screens/tunnel/` (new or modified screen) + +After Sumsub returns a successful `KycProviderResult`: + +1. Extract attestation data (`serializedApplicantInfo`, `signature`, `pubkey`) +2. Transform into an `IDDocument` (using `@selfxyz/common` parsing utilities) +3. Call `storePassportData(selfClient, document)` — this persists to native + keychain via the `createKeychainDocumentsAdapter` from WV-07 + +The KYC provider result contains the fields the circuit needs: country, idType, +idNumber, issuanceDate, expiryDate, fullName, dob, photoHash, phoneNumber, +gender, address. + +### 2. Replace mock TunnelProvingScreen with real proving + +**File:** `packages/webview-app/src/screens/tunnel/TunnelProvingScreen.tsx` + +Replace the 3-second timer mock with real proving machine integration: + +```typescript +import { useProvingStore } from '@selfxyz/mobile-sdk-alpha/browser'; +import { useSelfClient } from '../../providers/SelfClientProvider'; + +export const TunnelProvingScreen: React.FC = () => { + const { client } = useSelfClient(); + const currentState = useProvingStore(s => s.currentState); + const init = useProvingStore(s => s.init); + const error_code = useProvingStore(s => s.error_code); + const reason = useProvingStore(s => s.reason); + + useEffect(() => { + // Init proving machine with 'register' circuit type + init(client, 'register'); + }, [client, init]); + + // Drive ProofGenerationScreen UI from currentState + // idle → parsing_id_document → fetching_data → validating_document + // → init_tee_connexion → ready_to_prove → proving → post_proving → completed +}; +``` + +**State → UI mapping:** + +| provingMachine state | UI shown | +|--------------------------|--------------------------------------------------| +| `idle` | Loading spinner | +| `parsing_id_document` | "Preparing document..." | +| `fetching_data` | "Fetching verification data..." | +| `validating_document` | "Validating document..." | +| `init_tee_connexion` | "Connecting to prover..." | +| `ready_to_prove` | "Ready" (auto-confirm or user confirms) | +| `proving` | "Generating proof..." (with Euclid animation) | +| `post_proving` | "Finalizing..." | +| `completed` | Navigate to result screen (success) | +| `error` / `failure` | Navigate to result screen (error with code) | +| `passport_not_supported` | Show unsupported document error | +| `passport_data_not_found`| Show missing document error | + +### 3. Wire disclose flow after register + +**File:** `packages/webview-app/src/screens/tunnel/TunnelProvingScreen.tsx` + +The tunnel flow runs register → disclose in sequence. After `completed` state +for register: + +1. Listen for `completed` state on register circuit +2. Re-init proving machine with `'disclose'` circuit type +3. Show disclosure proving UI +4. On disclose `completed`, navigate to result screen + +Use `useProvingStore` subscription to detect state transitions: + +```typescript +useEffect(() => { + if (currentState === 'completed' && circuitPhase === 'register') { + // Register done, start disclose + setCircuitPhase('disclose'); + init(client, 'disclose'); + } else if (currentState === 'completed' && circuitPhase === 'disclose') { + // Both done, navigate to result + navigate('/tunnel/proof/result', { state: { success: true } }); + } else if (currentState === 'error' || currentState === 'failure') { + navigate('/tunnel/proof/result', { + state: { success: false, error_code, reason }, + }); + } +}, [currentState, circuitPhase]); +``` + +### 4. Update TunnelResultScreen with real result + +**File:** `packages/webview-app/src/screens/tunnel/TunnelResultScreen.tsx` + +Replace hardcoded success with navigation state from proving: + +- Success: show "Identity Verified" with Euclid StatusState +- Failure: show error with code and reason, offer retry +- On "Continue": call `lifecycle.setResult()` then `lifecycle.dismiss()` + +### 5. Wire tunnel route sequence + +**File:** `packages/webview-app/src/App.tsx` + +Update tunnel routes if needed. The flow becomes: + +``` +/tunnel/tour/:step + → /tunnel/kyc + → /tunnel/registration/country + → /tunnel/registration/id-type + → /tunnel/provider (Sumsub — from WV-05) + → /tunnel/provider-result (normalize KYC result — from WV-06) + → /tunnel/proof/generating (real provingMachine — this spec) + → /tunnel/proof/result (real result — this spec) +``` + +### 6. Handle user confirmation gate + +**File:** `packages/webview-app/src/screens/tunnel/TunnelProofReceiptScreen.tsx` + +The proving machine has a `ready_to_prove` state where it waits for user +confirmation. Wire the receipt screen's "Verify" button to call +`useProvingStore.getState().setUserConfirmed(client)` so the proving machine +transitions from `ready_to_prove` → `proving`. + +Alternatively, pass `userConfirmed: true` to `init()` to auto-confirm in the +tunnel flow. + +## Files You Will Modify + +| File | Change | Risk | +|----------------------------------------------------------------------------|-------------------------------------------------|------------| +| `packages/webview-app/src/screens/tunnel/TunnelProvingScreen.tsx` | Replace mock with real provingMachine | **Medium** | +| `packages/webview-app/src/screens/tunnel/TunnelResultScreen.tsx` | Wire real result from proving state | **Low** | +| `packages/webview-app/src/screens/tunnel/TunnelProofReceiptScreen.tsx` | Wire user confirmation to provingMachine | **Low** | +| `packages/webview-app/src/App.tsx` | Update tunnel routes if needed | **Low** | +| `specs/projects/sdk/workstreams/webview/SPEC.md` | Add WV-08 to backlog | **None** | + +## Files You Will NOT Modify + +| File | Why | +|---------------------------------------------------------------|--------------------------------------------------------| +| `packages/mobile-sdk-alpha/src/proving/provingMachine.ts` | Engine unchanged — consumed as-is | +| `packages/webview-bridge/**` | Bridge layer unchanged — WV-07 already handled mapping | +| `packages/native-shell-android/**` | No new native handlers needed | +| `packages/native-shell-ios/**` | No new native handlers needed | +| `packages/webview-app/src/screens/proving/ProvingScreen.tsx` | Non-tunnel proving screen — separate concern | +| `packages/webview-app/src/providers/SelfClientProvider.tsx` | Already wired in WV-07 | + +## Files You May Create + +| File | What | +|-----------------------------------------------------------------------|---------------------------------------------------| +| `packages/webview-app/src/hooks/useProvingFlow.ts` | Optional: shared hook for register→disclose chain | +| `packages/webview-app/src/screens/tunnel/TunnelProviderScreen.tsx` | If tunnel needs its own Sumsub launch screen | + +## Constraints + +- **No provingMachine changes.** The engine is consumed as-is from + mobile-sdk-alpha. If behavior doesn't match expectations, file a separate + SDK Core issue. +- **Tunnel flow is the proving integration point.** Do not change the + non-tunnel `ProvingScreen` — that follows a different flow (external app + requests proof of an already-registered document). +- **Register then disclose.** The tunnel flow always runs both circuits in + sequence. Register creates the on-chain identity, disclose proves specific + attributes to the requesting party. +- **Sumsub provides the document.** NFC scanning is not part of this flow. + The `IDDocument` is constructed from Sumsub's KYC attestation, not from + passport chip data. + +## Resolved Questions + +1. **IDDocument shape from Sumsub attestation** — `KycData` (from + `@selfxyz/common/utils/types`) is already a subtype of `IDDocument`. + Construct it directly from Sumsub's attestation: + ```typescript + const kycData: KycData = { + documentType: deserializeApplicantInfo(attestation.serializedApplicantInfo).idType, + documentCategory: 'kyc', + mock: false, + signature: attestation.signature, + pubkey: attestation.pubkey, + serializedApplicantInfo: attestation.serializedApplicantInfo, + }; + ``` + `deserializeApplicantInfo()` from `@selfxyz/common/utils/kyc/api` parses + the base64 blob into structured fields. The proving machine calls this + internally when it needs circuit inputs. This is the same pattern the RN + app uses (`app/src/hooks/useSumsubWebSocket.ts`). +2. **DSC circuit** — Not needed for KYC documents. The tunnel flow runs + `register → disclose` only. DSC is only relevant for NFC-scanned + passports where the Document Signer Certificate needs updating. +3. **Auto-confirm vs user gate** — Deferred. This is a design decision for + when the full Euclid screen set is integrated. For now, use + `userConfirmed: true` in the `init()` call to auto-confirm. The receipt + screen already shows disclosures before proving starts. + +## Validation + +```bash +# webview-app builds +cd packages/webview-app && yarn build + +# Manual: launch tunnel flow in test app, verify: +# 1. Sumsub completes → document stored in keychain +# 2. Proving machine transitions through states +# 3. Proof generated successfully (or meaningful error) +# 4. Result screen shows real outcome +# 5. lifecycle.setResult() sends proof to host +``` + +## Definition of Done + +- [ ] TunnelProvingScreen drives real provingMachine (no 3-second mock) +- [ ] Sumsub result stored as IDDocument in native keychain via secureStorage +- [ ] Register circuit runs to completion (or meaningful error state) +- [ ] Disclose circuit runs after register completes +- [ ] TunnelResultScreen shows real success/failure from proving state +- [ ] `lifecycle.setResult()` called with proof data on success +- [ ] Error/failure states show actionable UI +- [ ] `yarn build` passes for webview-app +- [ ] Backlog row added in SPEC.md + +## Status Log + +- 2026-03-24: Plan created.