diff --git a/data/sync-utxos.log b/data/sync-utxos.log index dbe6626..66405ab 100644 --- a/data/sync-utxos.log +++ b/data/sync-utxos.log @@ -1,45 +1,3 @@ - ⏳ Traitement: 200000/225826 UTXOs insérés... - ⏳ Traitement: 210000/225826 UTXOs insérés... - ⏳ Traitement: 220000/225826 UTXOs insérés... -💾 Mise à jour des UTXOs dépensés... - -📊 Résumé: - - UTXOs vérifiés: 61609 - - UTXOs toujours disponibles: 61609 - - UTXOs dépensés détectés: 0 - -📈 Statistiques finales: - - Total UTXOs: 68398 - - Dépensés: 6789 - - Non dépensés: 61609 - -✅ Synchronisation terminée -🔍 Démarrage de la synchronisation des UTXOs dépensés... - -📊 UTXOs à vérifier: 61609 -📡 Récupération des UTXOs depuis Bitcoin... -📊 UTXOs disponibles dans Bitcoin: 225837 -💾 Création de la table temporaire... -💾 Insertion des UTXOs disponibles par batch... - ⏳ Traitement: 10000/225837 UTXOs insérés... - ⏳ Traitement: 20000/225837 UTXOs insérés... - ⏳ Traitement: 30000/225837 UTXOs insérés... - ⏳ Traitement: 40000/225837 UTXOs insérés... - ⏳ Traitement: 50000/225837 UTXOs insérés... - ⏳ Traitement: 60000/225837 UTXOs insérés... - ⏳ Traitement: 70000/225837 UTXOs insérés... - ⏳ Traitement: 80000/225837 UTXOs insérés... - ⏳ Traitement: 90000/225837 UTXOs insérés... - ⏳ Traitement: 100000/225837 UTXOs insérés... - ⏳ Traitement: 110000/225837 UTXOs insérés... - ⏳ Traitement: 120000/225837 UTXOs insérés... - ⏳ Traitement: 130000/225837 UTXOs insérés... - ⏳ Traitement: 140000/225837 UTXOs insérés... - ⏳ Traitement: 150000/225837 UTXOs insérés... - ⏳ Traitement: 160000/225837 UTXOs insérés... - ⏳ Traitement: 170000/225837 UTXOs insérés... - ⏳ Traitement: 180000/225837 UTXOs insérés... - ⏳ Traitement: 190000/225837 UTXOs insérés... ⏳ Traitement: 200000/225837 UTXOs insérés... ⏳ Traitement: 210000/225837 UTXOs insérés... ⏳ Traitement: 220000/225837 UTXOs insérés... @@ -98,3 +56,45 @@ - Non dépensés: 61609 ✅ Synchronisation terminée +🔍 Démarrage de la synchronisation des UTXOs dépensés... + +📊 UTXOs à vérifier: 61598 +📡 Récupération des UTXOs depuis Bitcoin... +📊 UTXOs disponibles dans Bitcoin: 225867 +💾 Création de la table temporaire... +💾 Insertion des UTXOs disponibles par batch... + ⏳ Traitement: 10000/225867 UTXOs insérés... + ⏳ Traitement: 20000/225867 UTXOs insérés... + ⏳ Traitement: 30000/225867 UTXOs insérés... + ⏳ Traitement: 40000/225867 UTXOs insérés... + ⏳ Traitement: 50000/225867 UTXOs insérés... + ⏳ Traitement: 60000/225867 UTXOs insérés... + ⏳ Traitement: 70000/225867 UTXOs insérés... + ⏳ Traitement: 80000/225867 UTXOs insérés... + ⏳ Traitement: 90000/225867 UTXOs insérés... + ⏳ Traitement: 100000/225867 UTXOs insérés... + ⏳ Traitement: 110000/225867 UTXOs insérés... + ⏳ Traitement: 120000/225867 UTXOs insérés... + ⏳ Traitement: 130000/225867 UTXOs insérés... + ⏳ Traitement: 140000/225867 UTXOs insérés... + ⏳ Traitement: 150000/225867 UTXOs insérés... + ⏳ Traitement: 160000/225867 UTXOs insérés... + ⏳ Traitement: 170000/225867 UTXOs insérés... + ⏳ Traitement: 180000/225867 UTXOs insérés... + ⏳ Traitement: 190000/225867 UTXOs insérés... + ⏳ Traitement: 200000/225867 UTXOs insérés... + ⏳ Traitement: 210000/225867 UTXOs insérés... + ⏳ Traitement: 220000/225867 UTXOs insérés... +💾 Mise à jour des UTXOs dépensés... + +📊 Résumé: + - UTXOs vérifiés: 61598 + - UTXOs toujours disponibles: 61598 + - UTXOs dépensés détectés: 0 + +📈 Statistiques finales: + - Total UTXOs: 68398 + - Dépensés: 6800 + - Non dépensés: 61598 + +✅ Synchronisation terminée diff --git a/userwallet/src/components/LoginScreen.tsx b/userwallet/src/components/LoginScreen.tsx index bf6f340..703f3c4 100644 --- a/userwallet/src/components/LoginScreen.tsx +++ b/userwallet/src/components/LoginScreen.tsx @@ -272,14 +272,14 @@ export function LoginScreen(): JSX.Element { ); const endpoints = relays.map((r) => r.endpoint); // Boucle de collecte : fetch signatures par hash jusqu'à satisfaction ou timeout - merged = await runCollectLoop( - endpoints, - proof.challenge.hash, - proof.signatures, - loginPath, + merged = await runCollectLoop({ + relayEndpoints: endpoints, + hash: proof.challenge.hash, + ourSigs: proof.signatures, + path: loginPath, pairToMembers, pubkeyToPair, - { + opts: { pollMs: COLLECT_POLL_MS, timeoutMs: COLLECT_TIMEOUT_MS, onProgress: (m) => { @@ -290,7 +290,7 @@ export function LoginScreen(): JSX.Element { }); }, }, - ); + }); } finally { setIsCollecting(false); setCollectProgressState(null); @@ -884,14 +884,14 @@ export function LoginScreen(): JSX.Element { identity?.publicKey ?? '', loginPath.pairs_attendus, ); - const merged = await runCollectLoop( - endpoints, - proof.challenge.hash, - proof.signatures, - loginPath, + const merged = await runCollectLoop({ + relayEndpoints: endpoints, + hash: proof.challenge.hash, + ourSigs: proof.signatures, + path: loginPath, pairToMembers, pubkeyToPair, - { + opts: { pollMs: COLLECT_POLL_MS, timeoutMs: COLLECT_TIMEOUT_MS, onProgress: (m) => { @@ -902,7 +902,7 @@ export function LoginScreen(): JSX.Element { }); }, }, - ); + }); setCollectedMerged(merged); } finally { setIsCollecting(false); diff --git a/userwallet/src/components/PairingDisplayScreen.tsx b/userwallet/src/components/PairingDisplayScreen.tsx index f71b930..555d202 100644 --- a/userwallet/src/components/PairingDisplayScreen.tsx +++ b/userwallet/src/components/PairingDisplayScreen.tsx @@ -89,15 +89,15 @@ export function PairingDisplayScreen(): JSX.Element { } setIsConfirming(true); try { - const ok = await runDevice2Confirmation( - local.uuid, - remote.uuid, + const ok = await runDevice2Confirmation({ + pairLocal: local.uuid, + pairRemote: remote.uuid, identity, relays, - identity.t0_anniversaire, - Date.now(), - pubkeyHex, - ); + start: identity.t0_anniversaire, + end: Date.now(), + remotePublicKey: pubkeyHex, + }); setJustConnected(ok); } catch (err) { console.error('Pairing confirmation (device 2):', err); diff --git a/userwallet/src/components/PairingSetupBlock.tsx b/userwallet/src/components/PairingSetupBlock.tsx index e9e8146..af3f888 100644 --- a/userwallet/src/components/PairingSetupBlock.tsx +++ b/userwallet/src/components/PairingSetupBlock.tsx @@ -94,13 +94,13 @@ export function PairingSetupBlock(): JSX.Element { } setIsConfirming(true); try { - await runDevice1Confirmation( - local.uuid, - remote.uuid, + await runDevice1Confirmation({ + pairLocal: local.uuid, + pairRemote: remote.uuid, identity, relays, - pubkeyHex, - ); + remotePublicKey: pubkeyHex, + }); } catch (err) { console.error('Pairing confirmation (device 1):', err); setRemoteError( diff --git a/userwallet/src/components/SyncScreen.tsx b/userwallet/src/components/SyncScreen.tsx index 5b0e62d..3cdc88e 100644 --- a/userwallet/src/components/SyncScreen.tsx +++ b/userwallet/src/components/SyncScreen.tsx @@ -57,15 +57,15 @@ export function SyncScreen(): JSX.Element { remote !== undefined && identity.privateKey !== undefined ) { - void checkPairingConfirmationFromSync( + void checkPairingConfirmationFromSync({ relays, - local.uuid, - remote.uuid, + pairLocal: local.uuid, + pairRemote: remote.uuid, identity, start, end, - remote.publicKey, - ).catch((err: unknown) => { + remotePublicKey: remote.publicKey, + }).catch((err: unknown) => { console.error('Pairing confirmation check during sync:', err); }); } diff --git a/userwallet/src/main.tsx b/userwallet/src/main.tsx index b2bc486..29dfc6c 100644 --- a/userwallet/src/main.tsx +++ b/userwallet/src/main.tsx @@ -3,7 +3,11 @@ import ReactDOM from 'react-dom/client'; import { App } from './App'; import './index.css'; -ReactDOM.createRoot(document.getElementById('root')!).render( +const rootElement = document.getElementById('root'); +if (rootElement === null) { + throw new Error('Root element not found'); +} +ReactDOM.createRoot(rootElement).render( , diff --git a/userwallet/src/services/pairingConfirm.ts b/userwallet/src/services/pairingConfirm.ts index 2d09390..c3bcc33 100644 --- a/userwallet/src/services/pairingConfirm.ts +++ b/userwallet/src/services/pairingConfirm.ts @@ -171,17 +171,22 @@ function buildMsgCle( }; } +interface PublishPairingMessageParams { + relays: RelayConfig[]; + message: MembreFinaliserMessage; + hash: string; + recipientPublicKey: string; + senderIdentity: LocalIdentity; +} + /** * Publish a pairing message (MsgChiffre) to relays. * DH obligatoire: encrypt with ECDH and POST MsgCle. recipientPublicKey and senderIdentity required. */ async function publishPairingMessage( - relays: RelayConfig[], - message: MembreFinaliserMessage, - hash: string, - recipientPublicKey: string, - senderIdentity: LocalIdentity, + params: PublishPairingMessageParams, ): Promise { + const { relays, message, hash, recipientPublicKey, senderIdentity } = params; if (recipientPublicKey === undefined || recipientPublicKey === '') { throw new Error( 'Pairing DH obligatoire : clé publique du pair distant requise (exiger publicKey).', @@ -212,25 +217,31 @@ async function publishPairingMessage( } } +interface PublishPairingMessageAndSignatureParams { + relays: RelayConfig[]; + message: MembreFinaliserMessage; + hash: string; + sig: Signature; + recipientPublicKey: string; + senderIdentity: LocalIdentity; +} + /** * Publish message and first signature to relays. * DH obligatoire: recipientPublicKey and senderIdentity required. */ export async function publishPairingMessageAndSignature( - relays: RelayConfig[], - message: MembreFinaliserMessage, - hash: string, - sig: Signature, - recipientPublicKey: string, - senderIdentity: LocalIdentity, + params: PublishPairingMessageAndSignatureParams, ): Promise { - await publishPairingMessage( + const { relays, message, hash, sig, recipientPublicKey, senderIdentity } = + params; + await publishPairingMessage({ relays, message, hash, recipientPublicKey, senderIdentity, - ); + }); const enabled = relays.filter((r) => r.enabled); const msgSig: MsgSignature = { signature: sig }; for (const r of enabled) { @@ -255,19 +266,32 @@ export async function publishPairingSignature( } } +interface FetchPairingMessageParams { + relays: RelayConfig[]; + pairLocal: string; + pairRemote: string; + start: number; + end: number; + senderPublicKey?: string; + ourIdentity?: LocalIdentity; +} + /** * Fetch messages in time window, find "membre finaliser" v1 for our pairs. * DH only: ECDH decryption when senderPublicKey and ourIdentity provided. No base64. */ export async function fetchPairingMessage( - relays: RelayConfig[], - pairLocal: string, - pairRemote: string, - start: number, - end: number, - senderPublicKey?: string, - ourIdentity?: LocalIdentity, + params: FetchPairingMessageParams, ): Promise<{ message: MembreFinaliserMessage; hash: string } | null> { + const { + relays, + pairLocal, + pairRemote, + start, + end, + senderPublicKey, + ourIdentity, + } = params; const enabled = relays.filter((r) => r.enabled); const key = sortedPairKey(pairLocal, pairRemote); const useEcdh = @@ -401,13 +425,13 @@ async function buildAndPublishPairingVersion2( }; const canonical = JSON.stringify(m2); const hash2 = await hashStringAsync(canonical, 'sha256'); - await publishPairingMessage( + await publishPairingMessage({ relays, - m2, - hash2, + message: m2, + hash: hash2, recipientPublicKey, senderIdentity, - ); + }); } /** @@ -456,17 +480,22 @@ function delay(ms: number): Promise { }); } +interface RunDevice1ConfirmationParams { + pairLocal: string; + pairRemote: string; + identity: LocalIdentity; + relays: RelayConfig[]; + remotePublicKey: string; +} + /** * Run confirmation flow for device 1: create M, sign, publish, poll for remote sig, store. * remotePublicKey: identity public key of device 2, for ECDH encryption. Required (DH obligatoire). */ export async function runDevice1Confirmation( - pairLocal: string, - pairRemote: string, - identity: LocalIdentity, - relays: RelayConfig[], - remotePublicKey: string, + params: RunDevice1ConfirmationParams, ): Promise { + const { pairLocal, pairRemote, identity, relays, remotePublicKey } = params; if (remotePublicKey === undefined || remotePublicKey === '') { throw new Error( 'Pairing DH obligatoire : clé publique du pair distant requise (exiger publicKey).', @@ -479,14 +508,14 @@ export async function runDevice1Confirmation( ); const n = generateUuid(); const sig = signMembreFinaliser(hash, identity, n); - await publishPairingMessageAndSignature( + await publishPairingMessageAndSignature({ relays, message, hash, sig, - remotePublicKey, - identity, - ); + recipientPublicKey: remotePublicKey, + senderIdentity: identity, + }); for (let i = 0; i < POLL_ATTEMPTS; i++) { if (i > 0) { await delay(POLL_DELAY_MS); @@ -508,28 +537,41 @@ export async function runDevice1Confirmation( return false; } +interface RunDevice2ConfirmationParams { + pairLocal: string; + pairRemote: string; + identity: LocalIdentity; + relays: RelayConfig[]; + start: number; + end: number; + remotePublicKey?: string; +} + /** * Run confirmation flow for device 2: fetch M, verify sig1, sign, publish sig2, store. * remotePublicKey: identity public key of device 1 (sender), for ECDH decryption. */ export async function runDevice2Confirmation( - pairLocal: string, - pairRemote: string, - identity: LocalIdentity, - relays: RelayConfig[], - start: number, - end: number, - remotePublicKey?: string, + params: RunDevice2ConfirmationParams, ): Promise { - const found = await fetchPairingMessage( + const { + pairLocal, + pairRemote, + identity, + relays, + start, + end, + remotePublicKey, + } = params; + const found = await fetchPairingMessage({ relays, pairLocal, pairRemote, start, end, - remotePublicKey, - identity, - ); + senderPublicKey: remotePublicKey, + ourIdentity: identity, + }); if (found === null) { return false; } @@ -547,33 +589,46 @@ export async function runDevice2Confirmation( return true; } +interface CheckPairingConfirmationFromSyncParams { + relays: RelayConfig[]; + pairLocal: string; + pairRemote: string; + identity: LocalIdentity; + start: number; + end: number; + remotePublicKey?: string; +} + /** * Check for pairing confirmation during Sync (device 1 timed out, user runs Sync later). * Fetches pairing message v1, signatures; if both signers present, stores confirmation. * remotePublicKey: identity public key of the other device (sender), for ECDH decryption. */ export async function checkPairingConfirmationFromSync( - relays: RelayConfig[], - pairLocal: string, - pairRemote: string, - identity: LocalIdentity, - start: number, - end: number, - remotePublicKey?: string, + params: CheckPairingConfirmationFromSyncParams, ): Promise { + const { + relays, + pairLocal, + pairRemote, + identity, + start, + end, + remotePublicKey, + } = params; const already = await getPairingConfirmed(pairLocal, pairRemote); if (already) { return true; } - const found = await fetchPairingMessage( + const found = await fetchPairingMessage({ relays, pairLocal, pairRemote, start, end, - remotePublicKey, - identity, - ); + senderPublicKey: remotePublicKey, + ourIdentity: identity, + }); if (found === null) { return false; } diff --git a/userwallet/src/utils/bip32.ts b/userwallet/src/utils/bip32.ts index a879375..f9e66dd 100644 --- a/userwallet/src/utils/bip32.ts +++ b/userwallet/src/utils/bip32.ts @@ -273,8 +273,18 @@ export function uuidToBip32Words(uuid: string): string[] { const uuidBytes = hexToBytes(uuid.replace(/-/g, '')); const words: string[] = []; for (let i = 0; i < uuidBytes.length; i += 2) { - const index = (uuidBytes[i]! << 8) | (uuidBytes[i + 1] ?? 0); - words.push(BIP32_WORDLIST[index % BIP32_WORDLIST.length]!); + const byte1 = uuidBytes[i]; + if (byte1 === undefined) { + continue; + } + const byte2 = uuidBytes[i + 1] ?? 0; + const index = (byte1 << 8) | byte2; + const wordIndex = index % BIP32_WORDLIST.length; + const word = BIP32_WORDLIST[wordIndex]; + if (word === undefined) { + continue; + } + words.push(word); } return words; } @@ -294,8 +304,12 @@ export function bip32WordsToUuid(words: string[]): string | null { } const bytes = new Uint8Array(indices.length * 2); for (let i = 0; i < indices.length; i++) { - bytes[i * 2] = (indices[i]! >> 8) & 0xff; - bytes[i * 2 + 1] = indices[i]! & 0xff; + const index = indices[i]; + if (index === undefined) { + continue; + } + bytes[i * 2] = (index >> 8) & 0xff; + bytes[i * 2 + 1] = index & 0xff; } const hex = bytesToHex(bytes); return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`; diff --git a/userwallet/src/utils/collectSignatures.ts b/userwallet/src/utils/collectSignatures.ts index d757a9c..dfc5231 100644 --- a/userwallet/src/utils/collectSignatures.ts +++ b/userwallet/src/utils/collectSignatures.ts @@ -151,19 +151,32 @@ export interface CollectLoopOpts { onProgress?: (merged: ProofSignature[]) => void; } +interface RunCollectLoopParams { + relayEndpoints: string[]; + hash: string; + ourSigs: ProofSignature[]; + path: LoginPath; + pairToMembers: Map; + pubkeyToPair: Map; + opts: CollectLoopOpts; +} + /** * Collect signatures from relays until we have enough per member, or timeout. * Optional onProgress called each poll for UI (e.g. X/Y signatures). */ export async function runCollectLoop( - relayEndpoints: string[], - hash: string, - ourSigs: ProofSignature[], - path: LoginPath, - pairToMembers: Map, - pubkeyToPair: Map, - opts: CollectLoopOpts, + params: RunCollectLoopParams, ): Promise { + const { + relayEndpoints, + hash, + ourSigs, + path, + pairToMembers, + pubkeyToPair, + opts, + } = params; const start = Date.now(); let merged = ourSigs; for (;;) {