386 lines
12 KiB
Markdown
386 lines
12 KiB
Markdown
# Analyse du Workflow de Pairing - Processus de Couplage d'Appareils
|
|
|
|
## Introduction
|
|
|
|
Ce document présente une analyse complète du workflow de pairing (processus de couplage) dans l'écosystème 4NK, qui permet à deux appareils de s'associer de manière sécurisée. Le processus implique à la fois le code TypeScript côté client (`ihm_client_dev2`) et les fonctionnalités WebAssembly compilées depuis Rust (`sdk_client`).
|
|
|
|
## Vue d'ensemble du Processus
|
|
|
|
Le workflow de pairing est déclenché par la fonction `onCreateButtonClick()` et suit une séquence d'actions coordonnées entre l'interface utilisateur, les services JavaScript et le module WebAssembly.
|
|
|
|
## Étapes Détaillées du Workflow
|
|
|
|
### 1. Déclenchement Initial
|
|
|
|
**Fonction :** `onCreateButtonClick()`
|
|
**Localisation :** `src/utils/sp-address.utils.ts:152-160`
|
|
|
|
```@/home/ank/dev/ihm_client_dev2/src/utils/sp-address.utils.ts#152:160
|
|
async function onCreateButtonClick() {
|
|
try {
|
|
await prepareAndSendPairingTx();
|
|
// Don't call confirmPairing immediately - it will be called when the pairing process is complete
|
|
console.log('Pairing process initiated. Waiting for completion...');
|
|
} catch (e) {
|
|
console.error(`onCreateButtonClick error: ${e}`);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Actions :**
|
|
- Appel de `prepareAndSendPairingTx()`
|
|
- Gestion d'erreur avec logging
|
|
- Attente de la complétion du processus
|
|
|
|
### 2. Préparation et Envoi de la Transaction de Pairing
|
|
|
|
**Fonction :** `prepareAndSendPairingTx()`
|
|
**Localisation :** `src/utils/sp-address.utils.ts:162-201`
|
|
|
|
**Actions principales :**
|
|
|
|
#### 2.1 Initialisation du Service
|
|
```typescript
|
|
const service = await Services.getInstance();
|
|
const relayAddress = service.getAllRelays();
|
|
```
|
|
|
|
#### 2.2 Création du Processus de Pairing
|
|
```typescript
|
|
const createPairingProcessReturn = await service.createPairingProcess("", []);
|
|
```
|
|
|
|
#### 2.3 Vérification des Connexions
|
|
```typescript
|
|
await service.checkConnections(
|
|
createPairingProcessReturn.updated_process.current_process,
|
|
createPairingProcessReturn.updated_process.current_process.states[0].state_id
|
|
);
|
|
```
|
|
|
|
#### 2.4 Configuration des Identifiants
|
|
```typescript
|
|
service.setProcessId(createPairingProcessReturn.updated_process.process_id);
|
|
service.setStateId(createPairingProcessReturn.updated_process.current_process.states[0].state_id);
|
|
```
|
|
|
|
#### 2.5 Mise à Jour du Device
|
|
```typescript
|
|
const currentDevice = await service.getDeviceFromDatabase();
|
|
if (currentDevice) {
|
|
currentDevice.pairing_process_commitment = createPairingProcessReturn.updated_process.process_id;
|
|
await service.saveDeviceInDatabase(currentDevice);
|
|
}
|
|
```
|
|
|
|
#### 2.6 Traitement du Retour API
|
|
```typescript
|
|
await service.handleApiReturn(createPairingProcessReturn);
|
|
```
|
|
|
|
### 3. Création du Processus de Pairing (Côté Service)
|
|
|
|
**Fonction :** `createPairingProcess()`
|
|
**Localisation :** `src/services/service.ts:334-371`
|
|
|
|
**Paramètres :**
|
|
- `userName`: Nom d'utilisateur (vide dans ce cas)
|
|
- `pairWith`: Liste des adresses à coupler (vide initialement)
|
|
|
|
**Actions :**
|
|
|
|
#### 3.1 Vérification de l'État de Pairing
|
|
```typescript
|
|
if (this.sdkClient.is_paired()) {
|
|
throw new Error('Device already paired');
|
|
}
|
|
```
|
|
|
|
#### 3.2 Préparation des Données
|
|
```typescript
|
|
const myAddress: string = this.sdkClient.get_address();
|
|
pairWith.push(myAddress);
|
|
|
|
const privateData = {
|
|
description: 'pairing',
|
|
counter: 0,
|
|
};
|
|
|
|
const publicData = {
|
|
memberPublicName: userName,
|
|
pairedAddresses: pairWith,
|
|
};
|
|
```
|
|
|
|
#### 3.3 Définition des Rôles
|
|
```typescript
|
|
const roles: Record<string, RoleDefinition> = {
|
|
pairing: {
|
|
members: [],
|
|
validation_rules: [{
|
|
quorum: 1.0,
|
|
fields: validation_fields,
|
|
min_sig_member: 1.0,
|
|
}],
|
|
storages: [STORAGEURL]
|
|
},
|
|
};
|
|
```
|
|
|
|
#### 3.4 Appel de Création du Processus
|
|
```typescript
|
|
return this.createProcess(privateData, publicData, roles);
|
|
```
|
|
|
|
### 4. Création du Processus (Côté WebAssembly)
|
|
|
|
**Fonction :** `create_new_process()`
|
|
**Localisation :** `sdk_client/src/api.rs:1218-1264`
|
|
|
|
**Actions principales :**
|
|
|
|
#### 4.1 Validation des Rôles
|
|
```rust
|
|
if roles.is_empty() {
|
|
return Err(ApiError { message: "Roles can't be empty".to_owned() });
|
|
}
|
|
```
|
|
|
|
#### 4.2 Création de la Transaction
|
|
```rust
|
|
let relay_address: SilentPaymentAddress = relay_address.try_into()?;
|
|
let tx = create_transaction_for_addresses(&local_device, &freezed_utxos, &vec![relay_address], fee_rate_checked)?;
|
|
let unsigned_transaction = SpClient::finalize_transaction(tx)?;
|
|
```
|
|
|
|
#### 4.3 Gestion des Secrets Partagés
|
|
```rust
|
|
let new_secrets = get_shared_secrets_in_transaction(&unsigned_transaction, &vec![relay_address])?;
|
|
let mut shared_secrets = lock_shared_secrets()?;
|
|
for (address, secret) in new_secrets {
|
|
shared_secrets.confirm_secret_for_address(secret, address);
|
|
}
|
|
```
|
|
|
|
#### 4.4 Création de l'État du Processus
|
|
```rust
|
|
let process_id = OutPoint::new(unsigned_transaction.unsigned_tx.as_ref().unwrap().txid(), 0);
|
|
let mut new_state = ProcessState::new(process_id, private_data.clone(), public_data.clone(), roles.clone())?;
|
|
let mut process = Process::new(process_id);
|
|
```
|
|
|
|
### 5. Vérification des Connexions
|
|
|
|
**Fonction :** `checkConnections()`
|
|
**Localisation :** `src/services/service.ts:232-289`
|
|
|
|
**Actions :**
|
|
|
|
#### 5.1 Validation des États
|
|
```typescript
|
|
if (process.states.length < 2) {
|
|
throw new Error('Process doesn\'t have any state yet');
|
|
}
|
|
```
|
|
|
|
#### 5.2 Extraction des Rôles et Membres
|
|
```typescript
|
|
let roles: Record<string, RoleDefinition> | null = null;
|
|
if (!stateId) {
|
|
roles = process.states[process.states.length - 2].roles;
|
|
} else {
|
|
roles = process.states.find(state => state.state_id === stateId)?.roles || null;
|
|
}
|
|
```
|
|
|
|
#### 5.3 Gestion des Processus de Pairing
|
|
```typescript
|
|
if (members.size === 0) {
|
|
// This must be a pairing process
|
|
const publicData = process.states[0]?.public_data;
|
|
if (!publicData || !publicData['pairedAddresses']) {
|
|
throw new Error('Not a pairing process');
|
|
}
|
|
const decodedAddresses = this.decodeValue(publicData['pairedAddresses']);
|
|
members.add({ sp_addresses: decodedAddresses });
|
|
}
|
|
```
|
|
|
|
#### 5.4 Connexion aux Adresses Non Connectées
|
|
```typescript
|
|
if (unconnectedAddresses && unconnectedAddresses.size != 0) {
|
|
const apiResult = await this.connectAddresses(Array.from(unconnectedAddresses));
|
|
await this.handleApiReturn(apiResult);
|
|
}
|
|
```
|
|
|
|
### 6. Traitement du Retour API
|
|
|
|
**Fonction :** `handleApiReturn()`
|
|
**Localisation :** `src/services/service.ts:648-775`
|
|
|
|
**Actions principales :**
|
|
|
|
#### 6.1 Signature de Transaction
|
|
```typescript
|
|
if (apiReturn.partial_tx) {
|
|
const res = this.sdkClient.sign_transaction(apiReturn.partial_tx);
|
|
apiReturn.new_tx_to_send = res.new_tx_to_send;
|
|
}
|
|
```
|
|
|
|
#### 6.2 Envoi de Transaction
|
|
```typescript
|
|
if (apiReturn.new_tx_to_send && apiReturn.new_tx_to_send.transaction.length != 0) {
|
|
this.sendNewTxMessage(JSON.stringify(apiReturn.new_tx_to_send));
|
|
await new Promise(r => setTimeout(r, 500));
|
|
}
|
|
```
|
|
|
|
#### 6.3 Gestion des Secrets
|
|
```typescript
|
|
if (apiReturn.secrets) {
|
|
const unconfirmedSecrets = apiReturn.secrets.unconfirmed_secrets;
|
|
const confirmedSecrets = apiReturn.secrets.shared_secrets;
|
|
// Sauvegarde en base de données
|
|
}
|
|
```
|
|
|
|
#### 6.4 Mise à Jour du Processus
|
|
```typescript
|
|
if (apiReturn.updated_process) {
|
|
const updatedProcess = apiReturn.updated_process;
|
|
const processId: string = updatedProcess.process_id;
|
|
await this.saveProcessToDb(processId, updatedProcess.current_process);
|
|
}
|
|
```
|
|
|
|
#### 6.5 Confirmation Automatique du Pairing
|
|
```typescript
|
|
// Check if this is a pairing process that's ready for confirmation
|
|
const existingDevice = await this.getDeviceFromDatabase();
|
|
if (existingDevice && existingDevice.pairing_process_commitment === processId) {
|
|
const lastState = updatedProcess.current_process.states[updatedProcess.current_process.states.length - 1];
|
|
if (lastState && lastState.public_data && lastState.public_data['pairedAddresses']) {
|
|
console.log('Pairing process updated with paired addresses, confirming pairing...');
|
|
await this.confirmPairing();
|
|
}
|
|
}
|
|
```
|
|
|
|
### 7. Confirmation du Pairing
|
|
|
|
**Fonction :** `confirmPairing()`
|
|
**Localisation :** `src/services/service.ts:793-851`
|
|
|
|
**Actions :**
|
|
|
|
#### 7.1 Récupération du Processus de Pairing
|
|
```typescript
|
|
const existingDevice = await this.getDeviceFromDatabase();
|
|
const pairingProcessId = existingDevice.pairing_process_commitment;
|
|
const myPairingProcess = await this.getProcess(pairingProcessId);
|
|
```
|
|
|
|
#### 7.2 Extraction des Adresses Couplées
|
|
```typescript
|
|
let myPairingState = this.getLastCommitedState(myPairingProcess);
|
|
const encodedSpAddressList = myPairingState.public_data['pairedAddresses'];
|
|
const spAddressList = this.decodeValue(encodedSpAddressList);
|
|
```
|
|
|
|
#### 7.3 Pairing Effectif du Device
|
|
```typescript
|
|
this.sdkClient.unpair_device(); // Clear any existing pairing
|
|
this.sdkClient.pair_device(pairingProcessId, spAddressList);
|
|
```
|
|
|
|
#### 7.4 Sauvegarde du Device Mis à Jour
|
|
```typescript
|
|
const newDevice = this.dumpDeviceFromMemory();
|
|
newDevice.pairing_process_commitment = pairingProcessId;
|
|
await this.saveDeviceInDatabase(newDevice);
|
|
```
|
|
|
|
## Architecture Technique
|
|
|
|
### Côté Client (TypeScript)
|
|
|
|
**Composants principaux :**
|
|
- **Interface Utilisateur :** Gestion des événements de clic et affichage des emojis
|
|
- **Services :** Orchestration du workflow et communication avec WebAssembly
|
|
- **Base de Données :** Stockage des devices, secrets et processus
|
|
- **WebSocket :** Communication avec les relais
|
|
|
|
### Côté WebAssembly (Rust)
|
|
|
|
**Fonctionnalités clés :**
|
|
- **Gestion des Wallets :** Création et manipulation des portefeuilles Silent Payment
|
|
- **Cryptographie :** Génération de secrets partagés et signatures
|
|
- **Transactions :** Création et finalisation des transactions Bitcoin
|
|
- **Processus :** Gestion des états et validation des changements
|
|
|
|
## Types de Données Importants
|
|
|
|
### Device
|
|
```typescript
|
|
interface Device {
|
|
sp_wallet: SpWallet;
|
|
pairing_process_commitment: OutPoint | null;
|
|
paired_member: Member;
|
|
}
|
|
```
|
|
|
|
### Process
|
|
```typescript
|
|
interface Process {
|
|
states: ProcessState[];
|
|
}
|
|
```
|
|
|
|
### ApiReturn
|
|
```typescript
|
|
interface ApiReturn {
|
|
secrets: SecretsStore | null;
|
|
updated_process: UpdatedProcess | null;
|
|
new_tx_to_send: NewTxMessage | null;
|
|
ciphers_to_send: string[];
|
|
commit_to_send: CommitMessage | null;
|
|
push_to_storage: string[];
|
|
partial_tx: TsUnsignedTransaction | null;
|
|
}
|
|
```
|
|
|
|
## Flux de Communication
|
|
|
|
1. **UI → Service :** Déclenchement du processus via `onCreateButtonClick()`
|
|
2. **Service → WebAssembly :** Appel des fonctions WASM pour la création du processus
|
|
3. **WebAssembly → Service :** Retour des données via `ApiReturn`
|
|
4. **Service → WebSocket :** Envoi des messages aux relais
|
|
5. **WebSocket → Service :** Réception des réponses des relais
|
|
6. **Service → Database :** Sauvegarde des états et secrets
|
|
7. **Service → UI :** Mise à jour de l'interface utilisateur
|
|
|
|
## Points d'Attention
|
|
|
|
### Sécurité
|
|
- Les secrets partagés sont générés côté WebAssembly (Rust)
|
|
- Les transactions sont signées de manière sécurisée
|
|
- Les données sensibles sont chiffrées avant stockage
|
|
|
|
### Asynchronisme
|
|
- Le processus est entièrement asynchrone
|
|
- Utilisation de promesses et callbacks pour la coordination
|
|
- Gestion d'erreur à chaque étape critique
|
|
|
|
### État du Processus
|
|
- Suivi de l'état via `processId` et `stateId`
|
|
- Sauvegarde persistante en base de données
|
|
- Récupération possible en cas d'interruption
|
|
|
|
## Conclusion
|
|
|
|
Le workflow de pairing représente un processus complexe mais bien structuré qui coordonne l'interface utilisateur TypeScript avec les fonctionnalités cryptographiques Rust via WebAssembly. Cette architecture permet une sécurité maximale tout en maintenant une expérience utilisateur fluide. Le processus est conçu pour être résilient aux interruptions et permet une récupération d'état en cas de problème.
|
|
|
|
La séparation claire entre les responsabilités (UI, orchestration, cryptographie) facilite la maintenance et les évolutions futures du système de pairing.
|