ci: docker_tag=dev-test

**Motivations :**
- Correction du chiffrement : PBKDF2 génère les clés du SDK, pas des clés personnalisées
- WebAuthn chiffre maintenant les clés du SDK générées par PBKDF2
- Ajout de getDeviceFromSDK() pour récupérer les clés du SDK

**Modifications :**
- Remplacement de generateSpendKey/generateScanKey par getDeviceFromSDK()
- WebAuthn chiffre maintenant device.sp_wallet.spend_key et device.sp_wallet.scan_key
- Ajout de la méthode getDeviceFromSDK() pour accéder au SDK

**Pages affectées :**
- src/services/secure-credentials.service.ts
This commit is contained in:
NicolasCantu 2025-10-24 01:57:30 +02:00
parent 3f387ee97f
commit 4a3b23c9d7
3 changed files with 81 additions and 64 deletions

View File

@ -542,8 +542,8 @@ async function handleMainPairing(): Promise<void> {
if (mainStatus) { if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--error-color)">❌ Failed to create credentials</span>'; mainStatus.innerHTML = '<span style="color: var(--error-color)">❌ Failed to create credentials</span>';
} }
return; return;
} }
// Now proceed with pairing process // Now proceed with pairing process
console.log('🚀 Starting pairing process...'); console.log('🚀 Starting pairing process...');

View File

@ -137,13 +137,15 @@ export class SecureCredentialsService {
const publicKey = response.getPublicKey(); const publicKey = response.getPublicKey();
const credentialId = credential.id; const credentialId = credential.id;
// Générer les clés privées réelles (spend/scan) avec PBKDF2 // Récupérer les clés du SDK générées par PBKDF2
const spendKey = await this.generateSpendKey(password); const device = await this.getDeviceFromSDK();
const scanKey = await this.generateScanKey(password); if (!device || !device.sp_wallet) {
throw new Error('SDK device not found or wallet not initialized');
}
// Chiffrer les clés privées avec la clé WebAuthn // Chiffrer les clés du SDK avec la clé WebAuthn
const encryptedSpendKey = await this.encryptWithWebAuthn(spendKey, publicKey, credentialId); const encryptedSpendKey = await this.encryptWithWebAuthn(device.sp_wallet.spend_key, publicKey, credentialId);
const encryptedScanKey = await this.encryptWithWebAuthn(scanKey, publicKey, credentialId); const encryptedScanKey = await this.encryptWithWebAuthn(device.sp_wallet.scan_key, publicKey, credentialId);
const credentialData: CredentialData = { const credentialData: CredentialData = {
spendKey: encryptedSpendKey, // Clé chiffrée spendKey: encryptedSpendKey, // Clé chiffrée
@ -173,6 +175,21 @@ export class SecureCredentialsService {
} }
} }
/**
* Récupère le device du SDK pour obtenir les clés générées par PBKDF2
*/
private async getDeviceFromSDK(): Promise<any> {
try {
// Importer le service pour accéder au SDK
const { Services } = await import('./service');
const service = await Services.getInstance();
return service.dumpDeviceFromMemory();
} catch (error) {
console.error('❌ Failed to get device from SDK:', error);
throw error;
}
}
/** /**
* Génère une clé spend avec PBKDF2 * Génère une clé spend avec PBKDF2
*/ */

View File

@ -291,7 +291,7 @@ export default class Services {
} }
// Memory is sufficient, load WebAssembly // Memory is sufficient, load WebAssembly
Services.instance = await Services.initializing; Services.instance = await Services.initializing;
Services.initializing = null; Services.initializing = null;
console.log('✅ Services initialized with WebAssembly'); console.log('✅ Services initialized with WebAssembly');
@ -474,50 +474,50 @@ export default class Services {
* Waits for at least one handshake message before returning. * Waits for at least one handshake message before returning.
*/ */
public async connectAllRelays(): Promise<void> { public async connectAllRelays(): Promise<void> {
const relayUrls = Object.keys(this.relayAddresses); const relayUrls = Object.keys(this.relayAddresses);
console.log(`🚀 Connecting to ${relayUrls.length} relays in parallel...`); console.log(`🚀 Connecting to ${relayUrls.length} relays in parallel...`);
// Create the relay ready promise immediately when starting connections // Create the relay ready promise immediately when starting connections
this.getRelayReadyPromise(); this.getRelayReadyPromise();
// Connect to all relays in parallel // Connect to all relays in parallel
const connectionPromises = relayUrls.map(async wsurl => { const connectionPromises = relayUrls.map(async wsurl => {
try { try {
console.log(`🔗 Connecting to: ${wsurl}`); console.log(`🔗 Connecting to: ${wsurl}`);
await this.addWebsocketConnection(wsurl); await this.addWebsocketConnection(wsurl);
console.log(`✅ Successfully connected to: ${wsurl}`); console.log(`✅ Successfully connected to: ${wsurl}`);
return wsurl; return wsurl;
} catch (error) { } catch (error) {
console.error(`❌ Failed to connect to ${wsurl}:`, error); console.error(`❌ Failed to connect to ${wsurl}:`, error);
return null; return null;
} }
}); });
// Wait for all connections to complete (success or failure) // Wait for all connections to complete (success or failure)
const results = await Promise.allSettled(connectionPromises); const results = await Promise.allSettled(connectionPromises);
const connectedUrls = results const connectedUrls = results
.filter( .filter(
(result): result is PromiseFulfilledResult<string> => (result): result is PromiseFulfilledResult<string> =>
result.status === 'fulfilled' && result.value !== null result.status === 'fulfilled' && result.value !== null
) )
.map(result => result.value); .map(result => result.value);
console.log(`✅ Connected to ${connectedUrls.length}/${relayUrls.length} relays`); console.log(`✅ Connected to ${connectedUrls.length}/${relayUrls.length} relays`);
// Wait for at least one handshake message if we have connections // Wait for at least one handshake message if we have connections
if (connectedUrls.length > 0) { if (connectedUrls.length > 0) {
try { try {
await this.waitForHandshakeMessage(); await this.waitForHandshakeMessage();
console.log(`✅ Handshake received from at least one relay`); console.log(`✅ Handshake received from at least one relay`);
} catch (error) { } catch (error) {
console.warn( console.warn(
`⚠️ No handshake received within timeout, but continuing with ${connectedUrls.length} connections` `⚠️ No handshake received within timeout, but continuing with ${connectedUrls.length} connections`
); );
// Continue anyway - we have connections even without handshake // Continue anyway - we have connections even without handshake
}
} else {
console.warn(`⚠️ No relay connections established`);
} }
} else {
console.warn(`⚠️ No relay connections established`);
}
} }
private getRelayReadyPromise(): Promise<void> { private getRelayReadyPromise(): Promise<void> {
@ -574,7 +574,7 @@ export default class Services {
* @returns The SP Address if found, or undefined if not. * @returns The SP Address if found, or undefined if not.
*/ */
public getSpAddress(wsurl: string): string | undefined { public getSpAddress(wsurl: string): string | undefined {
return this.relayAddresses[wsurl]; return this.relayAddresses[wsurl];
} }
/** /**
@ -582,10 +582,10 @@ export default class Services {
* @returns An array of objects containing wsurl and spAddress. * @returns An array of objects containing wsurl and spAddress.
*/ */
public getAllRelays(): { wsurl: string; spAddress: string }[] { public getAllRelays(): { wsurl: string; spAddress: string }[] {
return Object.entries(this.relayAddresses).map(([wsurl, spAddress]) => ({ return Object.entries(this.relayAddresses).map(([wsurl, spAddress]) => ({
wsurl, wsurl,
spAddress, spAddress,
})); }));
} }
/** /**
@ -593,9 +593,9 @@ export default class Services {
*/ */
public printAllRelays(): void { public printAllRelays(): void {
console.log('Current relay addresses:'); console.log('Current relay addresses:');
for (const [wsurl, spAddress] of Object.entries(this.relayAddresses)) { for (const [wsurl, spAddress] of Object.entries(this.relayAddresses)) {
console.log(`${wsurl} -> ${spAddress}`); console.log(`${wsurl} -> ${spAddress}`);
} }
} }
public isPaired(): boolean { public isPaired(): boolean {
@ -653,7 +653,7 @@ export default class Services {
} }
private async getTokensFromFaucet(): Promise<void> { private async getTokensFromFaucet(): Promise<void> {
await this.ensureSufficientAmount(); await this.ensureSufficientAmount();
} }
// If we're updating a process, we must call that after update especially if roles are part of it // If we're updating a process, we must call that after update especially if roles are part of it
@ -926,12 +926,12 @@ export default class Services {
roles: Record<string, RoleDefinition> roles: Record<string, RoleDefinition>
): Promise<ApiReturn> { ): Promise<ApiReturn> {
// Attendre que le relai soit prêt avec son spAddress // Attendre que le relai soit prêt avec son spAddress
console.log('⏳ Waiting for relays to be ready...'); console.log('⏳ Waiting for relays to be ready...');
// Update UI status // Update UI status
const { updateCreatorStatus } = await import('../utils/sp-address.utils'); const { updateCreatorStatus } = await import('../utils/sp-address.utils');
updateCreatorStatus('⏳ Waiting for relays to be ready...'); updateCreatorStatus('⏳ Waiting for relays to be ready...');
await this.getRelayReadyPromise(); await this.getRelayReadyPromise();
// Vérifier que nous avons maintenant un spAddress // Vérifier que nous avons maintenant un spAddress
const relays = this.getAllRelays(); const relays = this.getAllRelays();
@ -992,11 +992,11 @@ export default class Services {
console.log('🔍 DEBUG: Members array sample:', members.slice(0, 3)); console.log('🔍 DEBUG: Members array sample:', members.slice(0, 3));
const result = this.sdkClient.create_new_process( const result = this.sdkClient.create_new_process(
encodedPrivateData, encodedPrivateData,
roles, roles,
encodedPublicData, encodedPublicData,
relayAddress, relayAddress,
feeRate, feeRate,
members members
); );
@ -1171,7 +1171,7 @@ export default class Services {
// Only log as error if it's not a pairing-related issue // Only log as error if it's not a pairing-related issue
if (!(e as Error).message?.includes('Failed to handle decrypted message')) { if (!(e as Error).message?.includes('Failed to handle decrypted message')) {
console.error(`Parsed cipher with error: ${e}`); console.error(`Parsed cipher with error: ${e}`);
} }
} }
// await this.saveCipherTxToDb(parsedTx) // await this.saveCipherTxToDb(parsedTx)
@ -1307,7 +1307,7 @@ export default class Services {
} }
} catch (scanError) { } catch (scanError) {
console.error('❌ Failed to scan blocks:', scanError); console.error('❌ Failed to scan blocks:', scanError);
} }
} }
} catch (e) { } catch (e) {
console.debug(e); console.debug(e);
@ -1515,7 +1515,7 @@ export default class Services {
// Check if the commitment is set and not null/empty // Check if the commitment is set and not null/empty
if ( if (
device.pairing_process_commitment && device.pairing_process_commitment &&
device.pairing_process_commitment !== null && device.pairing_process_commitment !== null &&
device.pairing_process_commitment !== '' device.pairing_process_commitment !== ''
) { ) {
console.log('✅ Pairing process commitment found:', device.pairing_process_commitment); console.log('✅ Pairing process commitment found:', device.pairing_process_commitment);
@ -1524,11 +1524,11 @@ export default class Services {
// For quorum=1.0 processes, the creator must commit themselves // For quorum=1.0 processes, the creator must commit themselves
// Check if the process is ready for the creator to commit // Check if the process is ready for the creator to commit
if (currentPairingId && currentPairingId === processId) { if (currentPairingId && currentPairingId === processId) {
console.log( console.log(
'✅ Creator process is synchronized and ready for self-commitment (quorum=1.0)' '✅ Creator process is synchronized and ready for self-commitment (quorum=1.0)'
); );
return; return;
} }
// For quorum=1 test, if we have a process but no commitment yet, // For quorum=1 test, if we have a process but no commitment yet,
@ -1577,11 +1577,11 @@ export default class Services {
); );
} }
} }
} catch (e) { } catch (e) {
console.log( console.log(
`❌ Attempt ${i + 1}/${maxRetries}: Error during synchronization - ${(e as Error).message}` `❌ Attempt ${i + 1}/${maxRetries}: Error during synchronization - ${(e as Error).message}`
); );
} }
if (i < maxRetries - 1) { if (i < maxRetries - 1) {
console.log(`⏳ Waiting ${retryDelay}ms before next attempt...`); console.log(`⏳ Waiting ${retryDelay}ms before next attempt...`);
@ -1682,7 +1682,7 @@ export default class Services {
throw new Error('SDK not initialized - cannot get amount'); throw new Error('SDK not initialized - cannot get amount');
} }
try { try {
const amount = this.sdkClient.get_available_amount(); const amount = this.sdkClient.get_available_amount();
console.log(`💰 SDK get_available_amount() returned: ${amount}`); console.log(`💰 SDK get_available_amount() returned: ${amount}`);
// Additional debugging: check wallet state // Additional debugging: check wallet state
@ -1701,7 +1701,7 @@ export default class Services {
console.warn('⚠️ Error getting wallet debugging info:', error); console.warn('⚠️ Error getting wallet debugging info:', error);
} }
return amount; return amount;
} catch (error) { } catch (error) {
console.error('❌ Error calling get_available_amount():', error); console.error('❌ Error calling get_available_amount():', error);
throw error; throw error;
@ -2002,7 +2002,7 @@ export default class Services {
throw new Error('Birthday not found'); throw new Error('Birthday not found');
} }
if (birthday === 0) { if (birthday === 0) {
// This is a new device, set birthday to scan from much earlier to catch faucet transactions // This is a new device, set birthday to scan from much earlier to catch faucet transactions
// Scan from 100 blocks earlier to ensure we catch all faucet transactions // Scan from 100 blocks earlier to ensure we catch all faucet transactions
console.log('🔧 Updating birthday for new device:', { console.log('🔧 Updating birthday for new device:', {
@ -2668,7 +2668,7 @@ export default class Services {
*/ */
public getAllMembersSorted(): Record<string, Member> { public getAllMembersSorted(): Record<string, Member> {
return Object.fromEntries( return Object.fromEntries(
Object.entries(this.membersList).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) Object.entries(this.membersList).sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
); );
} }