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 (;;) {