1514 lines
49 KiB
Markdown
1514 lines
49 KiB
Markdown
# Spécification du Système d'Identité et de Processus 4NK
|
|
|
|
**Version:** 1.0
|
|
**Date:** 1 octobre 2025
|
|
**Auteur:** Nicolas Cantu
|
|
**Description:** Analyse complète des composants sdk_client, sdk_common, sdk_relay, sdk_storage, ihm_client, rust-silentPayments
|
|
|
|
## Table des matières
|
|
|
|
1. [Vue d'ensemble](#vue-densemble)
|
|
2. [Architecture d'identité](#architecture-didentité)
|
|
3. [Système de processus](#système-de-processus)
|
|
4. [Validation et consensus](#validation-et-consensus)
|
|
5. [Communication réseau](#communication-réseau)
|
|
6. [Stockage et persistance](#stockage-et-persistance)
|
|
7. [Flux de données](#flux-de-données)
|
|
8. [Sécurité](#sécurité)
|
|
|
|
---
|
|
|
|
## 1. Vue d'ensemble
|
|
|
|
### 1.1 Philosophie du système
|
|
|
|
4NK est un système décentralisé de gestion de processus collaboratifs basé sur la blockchain Bitcoin, utilisant **Silent Payments ([BIP352](https://bips.dev/352/))** pour l'identité et le partage de secrets de chiffrement. Le système permet à plusieurs parties de collaborer sur des processus dont les états successifs sont vérifiables, avec des permissions (lecture, modification) granulaires définies pour différents rôles et utilisateurs.
|
|
|
|
### 1.2 Principes fondamentaux
|
|
|
|
- **Identité décentralisée** : Basée sur des adresses Silent Payment Bitcoin
|
|
- **Device-centric** : Chaque appareil possède sa propre clé cryptographique
|
|
- **Multi-device** : Un utilisateur peut gérer plusieurs appareils via le pairing
|
|
- **Processus à états** : Évolution contrôlée par des commitments dans des transactions Bitcoin
|
|
- **Validation distribuée** : Signatures cryptographiques multiples requises pour chaque changement d'état
|
|
- **Tout dans le browser** : Connexions WebSocket avec des relais pour synchronisation temps réel
|
|
|
|
### 1.3 Composants principaux
|
|
|
|
| Composant | Rôle | Technologie |
|
|
|-----------|------|-------------|
|
|
| `sdk_common` | Types et structures partagés | Rust (WASM-ready) |
|
|
| `sdk_client` | Bibliothèque cliente WASM | Rust → WebAssembly |
|
|
| `sdk_relay` | Relais de messages et synchronisation | Rust + WebSocket |
|
|
| `sdk_storage` | Stockage clé-valeur distribué | Rust + HTTP |
|
|
| `ihm_client` | Interface utilisateur web | TypeScript + Web Components |
|
|
| `rust-silentPayments` | Implémentation Silent Payments | Rust (dépendance) |
|
|
|
|
---
|
|
|
|
## 2. Architecture d'identité
|
|
|
|
### 2.1 Hiérarchie d'identité
|
|
|
|
```
|
|
Device
|
|
├─ SpClient (Silent Payment Client)
|
|
│ ├─ Scan Key (privée)
|
|
│ ├─ Spend Key (privée)
|
|
│ └─ Silent Payment Address (publique)
|
|
├─ SpWallet (outputs UTXO)
|
|
├─ Pairing Process (OutPoint)
|
|
└─ Member (liste d'adresses pairées)
|
|
```
|
|
|
|
### 2.2 Structure `Device`
|
|
|
|
**Fichier source:** `sdk_common/src/device.rs`
|
|
|
|
```rust
|
|
pub struct Device {
|
|
sp_wallet: SpWallet, // Portefeuille Silent Payments
|
|
pairing_process_commitment: Option<OutPoint>, // ID du processus de pairing
|
|
paired_member: Member, // Groupe d'adresses pairées
|
|
}
|
|
```
|
|
|
|
**Opérations principales:**
|
|
|
|
1. **Création** : `Device::new(sp_client)`
|
|
- Génère une adresse Silent Payment locale
|
|
- Initialise un `Member` avec cette adresse seule
|
|
|
|
2. **Pairing** : `device.pair(commitment_outpoint, member)`
|
|
- Associe le device à un processus de pairing
|
|
- D'autres adresses Silent Payment peuvent être ajoutées à ce processus
|
|
|
|
3. **Unpairing** : `device.unpair()`
|
|
- Réinitialise à l'état local unique
|
|
|
|
### 2.3 Structure `Member`
|
|
|
|
**Fichier source:** `sdk_common/src/pcd.rs` (lignes 140-223)
|
|
|
|
```rust
|
|
pub struct Member {
|
|
sp_addresses: Vec<String>, // Liste d'adresses Silent Payment
|
|
}
|
|
```
|
|
|
|
**Caractéristiques:**
|
|
|
|
- Représente un ensemble d'appareils appartenant à une même entité
|
|
- Utilisé pour les validations multi-signatures
|
|
- Comparaison indépendante de l'ordre (HashSet interne)
|
|
- Sérialisation déterministe (tri automatique)
|
|
|
|
**Méthodes clés:**
|
|
|
|
```rust
|
|
pub fn new(sp_addresses: Vec<SilentPaymentAddress>) -> Self
|
|
pub fn get_addresses(&self) -> Vec<String>
|
|
pub fn key_is_part_of_member(&self, key: &PublicKey) -> bool
|
|
pub fn get_address_for_key(&self, key: &PublicKey) -> Option<String>
|
|
```
|
|
|
|
### 2.4 Processus de pairing
|
|
|
|
Le **pairing** est le mécanisme permettant d'associer plusieurs appareils (devices) à une seule identité logique (member).
|
|
|
|
#### Étapes du pairing:
|
|
|
|
1. **Création du processus de pairing**
|
|
- Un device crée un nouveau `Process` avec un rôle spécial `"pairing"`
|
|
- Le `ProcessState` contient un champ public `"pairedAddresses"` avec la liste des adresses
|
|
|
|
2. **Validation du pairing**
|
|
- Fichier: `sdk_common/src/process.rs` (lignes 161-200)
|
|
- Règle: `members` doit être vide (pas de membres préexistants)
|
|
- Une seule règle de validation pour le champ `"pairedAddresses"`
|
|
- Signatures requises:
|
|
- **Création** : Signature de la nouvelle adresse seule
|
|
- **Ajout** : Signature des adresses déjà pairées
|
|
- **Retrait** : Signatures de tous les devices (consensus)
|
|
|
|
3. **État après pairing**
|
|
```rust
|
|
device.pairing_process_commitment = Some(process_id);
|
|
device.paired_member = Member::new(all_paired_addresses);
|
|
```
|
|
|
|
**Code de validation (extrait):**
|
|
|
|
```rust
|
|
fn handle_pairing(
|
|
&self,
|
|
pairing_role: RoleDefinition,
|
|
previous_addresses: Vec<SilentPaymentAddress>,
|
|
) -> anyhow::Result<()> {
|
|
// members must be empty
|
|
if !pairing_role.members.is_empty() {
|
|
return Err(anyhow::Error::msg("Invalid pairing role"));
|
|
}
|
|
|
|
let updated_addresses_json = self.public_data.get_as_json(PAIREDADDRESSES)?;
|
|
let updated_member = Member::new(updated_addresses_json);
|
|
let previous_member = Member::new(previous_addresses);
|
|
|
|
let members = if previous_member.get_addresses().is_empty() {
|
|
vec![&updated_member] // Création
|
|
} else {
|
|
vec![&previous_member] // Modification
|
|
};
|
|
|
|
// Validation des signatures
|
|
pairing_rule.is_satisfied(PAIREDADDRESSES, state_id, validation_tokens, members)
|
|
}
|
|
```
|
|
|
|
### 2.5 Identité dans le réseau
|
|
|
|
Chaque participant au réseau 4NK est identifié par:
|
|
|
|
1. **Adresse Silent Payment** : Identité publique cryptographique
|
|
2. **Process ID (OutPoint)** : Identifiant du processus de pairing (si multi-device)
|
|
3. **Member** : Groupe d'adresses associées
|
|
|
|
**Carte d'identité réseau:**
|
|
|
|
```rust
|
|
// Fichier: sdk_common/src/serialization.rs
|
|
pub struct OutPointMemberMap(pub HashMap<OutPoint, Member>);
|
|
```
|
|
|
|
Cette structure permet au réseau de:
|
|
- Résoudre un `process_id` vers un groupe d'adresses
|
|
- Vérifier qu'une signature provient d'un membre légitime
|
|
- Gérer les membres multi-appareils de façon transparente
|
|
|
|
---
|
|
|
|
## 3. Système de processus
|
|
|
|
### 3.1 Modèle de processus
|
|
|
|
Un **Process** est une machine à états distribuée, commitée sur la blockchain Bitcoin.
|
|
|
|
```
|
|
Process
|
|
└─ states: Vec<ProcessState>
|
|
├─ State 0 (premier commit)
|
|
├─ State 1 (update 1)
|
|
├─ State 2 (update 2)
|
|
├─ ...
|
|
└─ State n (dernier = toujours vide)
|
|
```
|
|
|
|
**Règle fondamentale:** Le dernier état est toujours vide, représentant le prochain UTXO à dépenser. Cela permet d'annoncer publiquement et sans équivoque où se trouvera le prochain état valide de notre processus, voir le principe du [single-use seal](https://docs.rgb.info/distributed-computing-concepts/single-use-seals)
|
|
|
|
### 3.2 Structure `ProcessState`
|
|
|
|
**Fichier source:** `sdk_common/src/process.rs` (lignes 19-35)
|
|
|
|
```rust
|
|
pub struct ProcessState {
|
|
pub commited_in: OutPoint, // UTXO Bitcoin portant cet état
|
|
pub pcd_commitment: PcdCommitments, // Map field -> hash(value)
|
|
pub state_id: [u8; 32], // Merkle root (identifiant unique)
|
|
pub keys: BTreeMap<String, [u8; 32]>, // Clés de déchiffrement (optionnel)
|
|
pub validation_tokens: Vec<Proof>, // Signatures de validation
|
|
pub public_data: Pcd, // Données publiques (non hashées)
|
|
pub roles: Roles, // Définition des rôles
|
|
}
|
|
```
|
|
|
|
#### Éléments clés:
|
|
|
|
1. **`commited_in`** : UTXO Bitcoin qui "porte" cet état
|
|
- Permet de lier l'état à la blockchain
|
|
- Forme l'identifiant du processus (`commited_in` du 1er `ProcessState`)
|
|
|
|
2. **`pcd_commitment`** : Map contenant en clé le nom des attributs contenu dans l'état (privés + publics yc `roles`) et en valeur le sha256 de la donnée
|
|
- Map `field_name -> hash(compressed_value)` La donnée est compressée avant d'être hashé
|
|
- Hash taggé: `AnkPcdHash::from_pcd_value(data, field, outpoint)` Le hash contient en plus de la donnée le nom de l'attribut (deux attributs différents avec la même valeur ont ainsi 2 hash différents) ainsi que l'outpoint (la même valeur sur 2 commitments successifs produira 2 hashs différents aussi)
|
|
|
|
3. **`state_id`** : Racine de Merkle de tous les commitments
|
|
- Identifiant unique de l'état
|
|
- Utilisé pour les signatures de validation
|
|
|
|
4. **`validation_tokens`** : Signatures cryptographiques
|
|
- Prouvent l'approbation des membres
|
|
- Type: `Proof` (signature Schnorr sur `state_id`)
|
|
|
|
5. **`public_data`** : Données non chiffrées
|
|
- Lisibles par tous les participants, yc ceux qui ne font pas partie du processus
|
|
- Exemple: `"pairedAddresses"` pour le pairing
|
|
|
|
6. **`roles`** : Définition des rôles et permissions
|
|
- Map `role_name -> RoleDefinition`
|
|
- Contrôle qui peut modifier quels champs
|
|
- Information publique (Cela permet à n'importe qui de valider un processus même sans en faire partie)
|
|
|
|
### 3.3 Cycle de vie d'un processus
|
|
|
|
```
|
|
┌─────────────────┐
|
|
│ 1. Création │
|
|
│ Process::new() │
|
|
│ État initial │
|
|
│ vide │
|
|
└────────┬────────┘
|
|
│
|
|
v
|
|
┌──────────────────────┐
|
|
│ 2. Proposition │
|
|
│ insert_concurrent │
|
|
│ _state() │
|
|
│ + validation_tokens │
|
|
└────────┬─────────────┘
|
|
│
|
|
v
|
|
┌─────────────────────────┐
|
|
│ 3. Validation │
|
|
│ ProcessState::is_valid()│
|
|
│ Vérif. signatures │
|
|
└────────┬────────────────┘
|
|
│
|
|
v
|
|
┌──────────────────────────┐
|
|
│ 4. Commit blockchain │
|
|
│ Transaction avec │
|
|
│ OP_RETURN (state_id) │
|
|
└────────┬─────────────────┘
|
|
│
|
|
v
|
|
┌──────────────────────────┐
|
|
│ 5. Confirmation │
|
|
│ update_states_tip() │
|
|
│ Nouvel UTXO │
|
|
└────────┬─────────────────┘
|
|
│
|
|
v
|
|
(retour étape 2)
|
|
```
|
|
|
|
### 3.4 États concurrents
|
|
|
|
Le système gère la **concurrence** via des états multiples pour un même `commited_in`:
|
|
|
|
```
|
|
Process {
|
|
states: [
|
|
State(outpoint_0, state_id_A), // Commité
|
|
State(outpoint_1, state_id_B), // Concurrent 1
|
|
State(outpoint_1, state_id_C), // Concurrent 2
|
|
State(outpoint_1, [0u8; 32]), // Empty (tip)
|
|
]
|
|
}
|
|
```
|
|
|
|
- Plusieurs propositions peuvent coexister
|
|
- Une seule sera commitée on-chain
|
|
- Les autres sont prunées après confirmation
|
|
|
|
**Méthodes de gestion:**
|
|
|
|
```rust
|
|
fn get_latest_concurrent_states(&self) -> Vec<&ProcessState>
|
|
fn remove_all_concurrent_states(&mut self) -> Vec<ProcessState>
|
|
```
|
|
|
|
### 3.5 Types de processus spéciaux
|
|
|
|
#### 3.5.1 Processus de pairing
|
|
|
|
- **Rôle:** `"pairing"`
|
|
- **Champ public:** `"pairedAddresses"`
|
|
- **Règle:** Membres vides, les adresses sont contenues dans `pairedAddresses`
|
|
|
|
#### 3.5.2 Oblitération (Termination)
|
|
|
|
- **État:** `state_id = [0u8; 32]`
|
|
- **Rôle:** `"apophis"` (défini dans l'état précédent)
|
|
- **Effet:** Termine définitivement le processus (Plus possible d'ajouter des états supplémentaires)
|
|
- **Fichier:** `sdk_common/src/process.rs` (lignes 133-159)
|
|
|
|
```rust
|
|
fn handle_obliteration(&self, apophis: &RoleDefinition, members_list: &OutPointMemberMap) -> Result<()> {
|
|
// Vérifie que le rôle "apophis" approuve l'oblitération
|
|
let empty_field = "";
|
|
apophis.is_satisfied(
|
|
vec![empty_field.to_owned()],
|
|
[0u8; 32],
|
|
&self.validation_tokens,
|
|
members_list,
|
|
)
|
|
}
|
|
```
|
|
|
|
#### 3.5.3 Rôle "Demiurge"
|
|
|
|
- **Contexte:** État initial uniquement
|
|
- **Pouvoir:** Créer tous les champs initiaux
|
|
- **Règle auto-générée:**
|
|
```rust
|
|
ValidationRule::new(1.0, all_keys, 1.0)
|
|
```
|
|
- **Fichier:** `sdk_common/src/process.rs` (lignes 103-130)
|
|
- **Commentaire:** Ce rôle peut être utile si l'une des parties prenantes doit initialiser le processus sans cependant avoir un rôle qui lui donne toutes les autorisations nécessaires. Ce rôle est ensuite ignoré dans les commitments suivants et peut être retiré si nécessaire.
|
|
|
|
---
|
|
|
|
## 4. Validation et consensus
|
|
|
|
### 4.1 Architecture de validation
|
|
|
|
La validation dans 4NK repose sur un système de **rôles**, **règles** et **preuves cryptographiques**.
|
|
|
|
```
|
|
Validation Flow:
|
|
ProcessState
|
|
└─> roles: Roles
|
|
└─> RoleDefinition
|
|
├─> members: Vec<OutPoint>
|
|
├─> validation_rules: Vec<ValidationRule>
|
|
└─> storages: Vec<String>
|
|
```
|
|
|
|
### 4.2 Structure `RoleDefinition`
|
|
|
|
**Fichier source:** `sdk_common/src/pcd.rs` (lignes 564-610)
|
|
|
|
```rust
|
|
pub struct RoleDefinition {
|
|
pub members: Vec<OutPoint>, // Process IDs des membres
|
|
pub validation_rules: Vec<ValidationRule>, // Règles de modification
|
|
pub storages: Vec<String>, // Storages autorisés
|
|
}
|
|
```
|
|
|
|
**Caractéristiques:**
|
|
|
|
- **members** : Utilise des `OutPoint` (IDs des processus de pairing) plutôt que des adresses
|
|
- Permet de modifier les devices d'un membre sans modifier les rôles dans tous les processus auxquels il prend part
|
|
- Résolu via `OutPointMemberMap` au moment de la validation
|
|
|
|
- **validation_rules** : Définissent quels champs peuvent être modifiés
|
|
- Quorum requis (n/total membres dans le rôle)
|
|
- Signatures minimales par membre (n/total de devices enregistrés par membre)
|
|
|
|
- **storages** : Liste d'url des storages où les données peuvent être déposées
|
|
|
|
### 4.3 Structure `ValidationRule`
|
|
|
|
**Fichier source:** `sdk_common/src/pcd.rs` (lignes 429-562)
|
|
|
|
```rust
|
|
pub struct ValidationRule {
|
|
quorum: f32, // 0.0 à 1.0 (proportion de membres requis)
|
|
fields: Vec<String>, // Champs concernés par cette règle
|
|
min_sig_member: f32, // 0.0 à 1.0 (proportion de devices par membre)
|
|
}
|
|
```
|
|
|
|
**Exemple concret:**
|
|
|
|
```rust
|
|
// Règle : 50% des membres, chacun avec au moins la moitié de leurs devices enregistrés
|
|
ValidationRule {
|
|
quorum: 0.5,
|
|
fields: vec!["contract".to_string(), "amount".to_string()],
|
|
min_sig_member: 0.5,
|
|
}
|
|
```
|
|
|
|
**Interprétation:**
|
|
- 50% des membres du rôle doivent approuver
|
|
- Chaque membre approuvant doit signer avec au moins 50% de ses devices pairés
|
|
- S'applique aux modifications de `"contract"` et `"amount"`
|
|
|
|
### 4.4 Processus de validation
|
|
|
|
**Fichier source:** `sdk_common/src/process.rs` (lignes 202-321)
|
|
|
|
#### Étapes de validation:
|
|
|
|
```rust
|
|
pub fn is_valid(
|
|
&self,
|
|
previous_state: Option<&ProcessState>,
|
|
members_list: &OutPointMemberMap,
|
|
) -> anyhow::Result<()>
|
|
```
|
|
|
|
1. **Vérification de base**
|
|
- `validation_tokens` non vide
|
|
|
|
2. **Cas spéciaux** (prioritaires):
|
|
- **Oblitération** : `state_id == [0u8; 32]`
|
|
- **Pairing** : Rôle `"pairing"` présent
|
|
|
|
3. **Gestion du Demiurge** (état initial uniquement):
|
|
```rust
|
|
if previous_state.is_none() {
|
|
if let Some(demiurge) = roles.get("demiurge") {
|
|
// Génère une règle couvrant tous les champs
|
|
}
|
|
}
|
|
```
|
|
|
|
4. **Validation par champ**:
|
|
Pour chaque champ modifié:
|
|
- Trouver les rôles applicables
|
|
- Vérifier qu'au moins un rôle valide le champ
|
|
- Vérifier les signatures cryptographiques
|
|
|
|
**Algorithme de validation:**
|
|
|
|
```rust
|
|
let all_fields_validated: bool = self.pcd_commitment.keys().all(|field| {
|
|
let applicable_roles = roles.filter(|role| role.has_rule_for(field));
|
|
|
|
applicable_roles.into_iter().any(|(role_name, role_def)| {
|
|
role_def.validation_rules.iter().any(|rule| {
|
|
let members = role_def.members
|
|
.filter_map(|outpoint| members_list.get(outpoint))
|
|
.collect();
|
|
|
|
rule.is_satisfied(field, state_id, validation_tokens, members).is_ok()
|
|
})
|
|
})
|
|
});
|
|
```
|
|
|
|
### 4.5 Validation d'une règle
|
|
|
|
**Fichier source:** `sdk_common/src/pcd.rs` (lignes 466-562)
|
|
|
|
```rust
|
|
pub fn is_satisfied(
|
|
&self,
|
|
field: &str,
|
|
merkle_root: [u8; 32],
|
|
proofs: &[Proof],
|
|
members: &[&Member],
|
|
) -> Result<()>
|
|
```
|
|
|
|
#### Algorithme:
|
|
|
|
1. **Calcul du quorum**:
|
|
```rust
|
|
let required_members = (members.len() as f32 * self.quorum).ceil() as usize;
|
|
```
|
|
|
|
2. **Filtrage des membres validants**:
|
|
```rust
|
|
let validating_members = members.iter().filter(|member| {
|
|
let member_proofs = proofs.iter()
|
|
.filter(|p| member.key_is_part_of_member(&p.get_key()))
|
|
.collect();
|
|
|
|
self.satisfy_min_sig_member(member, merkle_root, member_proofs).is_ok()
|
|
}).count();
|
|
```
|
|
|
|
3. **Vérification du quorum**:
|
|
```rust
|
|
validating_members >= required_members
|
|
```
|
|
|
|
### 4.6 Signatures cryptographiques
|
|
|
|
#### Structure `Proof`
|
|
|
|
```rust
|
|
pub struct Proof {
|
|
signature: SchnorrSignature,
|
|
public_key: PublicKey,
|
|
message: [u8; 32],
|
|
}
|
|
```
|
|
|
|
**Types de messages signés:**
|
|
|
|
```rust
|
|
pub enum AnkHash {
|
|
ValidationYes(AnkValidationYesHash),
|
|
ValidationNo(AnkValidationNoHash),
|
|
}
|
|
```
|
|
|
|
- **ValidationYes** : Approbation du `state_id`
|
|
- **ValidationNo** : Rejet du `state_id`
|
|
|
|
**Création d'une signature:**
|
|
|
|
```rust
|
|
let message_hash = state.get_message_hash(true)?; // true = yes
|
|
let proof = Proof::new(message_hash, spend_key);
|
|
state.validation_tokens.push(proof);
|
|
```
|
|
|
|
**Vérification:**
|
|
|
|
```rust
|
|
fn satisfy_min_sig_member(&self, member: &Member, merkle_root: [u8; 32], proofs: &[&Proof]) -> Result<()> {
|
|
let required_sigs = (member.addresses().len() as f32 * self.min_sig_member).ceil();
|
|
|
|
let yes_votes = proofs.iter()
|
|
.filter(|p| p.verify().is_ok())
|
|
.filter(|p| p.get_message() == AnkValidationYesHash::from_merkle_root(merkle_root))
|
|
.count();
|
|
|
|
if yes_votes >= required_sigs {
|
|
Ok(())
|
|
} else {
|
|
Err("Not enough yes votes")
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4.7 Scénarios de validation
|
|
|
|
#### Scénario 1: Création de processus (2 membres, quorum 100%)
|
|
|
|
```rust
|
|
// Membres
|
|
let alice_bob = Member::new(vec![alice_addr, bob_addr]);
|
|
let carol = Member::new(vec![carol_addr]);
|
|
|
|
// Rôle
|
|
let role = RoleDefinition {
|
|
members: vec![alice_bob_pairing_id, carol_pairing_id],
|
|
validation_rules: vec![
|
|
ValidationRule::new(1.0, vec!["field1"], 0.5) // quorum 100%, 50% devices
|
|
],
|
|
};
|
|
|
|
// Validation
|
|
// Alice signe seule (50% de alice_bob) -> ❌ Insuffisant (1/2 membres)
|
|
// Alice + Carol signent -> ✅ Valide (2/2 membres, quorum atteint)
|
|
```
|
|
|
|
#### Scénario 2: Update avec plusieurs rôles
|
|
|
|
```rust
|
|
// Rôle 1: Owner (peut modifier "roles")
|
|
let owner = RoleDefinition {
|
|
members: vec![alice_pairing_id],
|
|
validation_rules: vec![
|
|
ValidationRule::new(1.0, vec!["roles"], 1.0)
|
|
],
|
|
};
|
|
|
|
// Rôle 2: Validator (peut modifier "idCertified", "roles")
|
|
let validator = RoleDefinition {
|
|
members: vec![bob_pairing_id],
|
|
validation_rules: vec![
|
|
ValidationRule::new(0.5, vec!["idCertified", "roles"], 1.0)
|
|
],
|
|
};
|
|
|
|
// Modification de "roles" -> Peut être validé par owner OU validator
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Communication réseau
|
|
|
|
### 5.1 Architecture réseau
|
|
|
|
```
|
|
┌─────────────┐ WebSocket ┌─────────────┐
|
|
│ ihm_client │◄────────────────────────────►│ sdk_relay │
|
|
│ (WASM) │ │ (Rust) │
|
|
└─────────────┘ └──────┬──────┘
|
|
│
|
|
┌───────┴──────┐
|
|
│ │
|
|
Bitcoin RPC ZMQ Events
|
|
Blindbit API (rawtx, hashblock)
|
|
```
|
|
|
|
### 5.2 Protocole WebSocket
|
|
|
|
**Fichier source:** `sdk_common/src/network.rs`
|
|
|
|
#### Types de messages (`AnkFlag`):
|
|
|
|
```rust
|
|
pub enum AnkFlag {
|
|
Handshake, // Synchronisation initiale
|
|
NewTx, // Nouvelle transaction détectée
|
|
Cipher, // Données chiffrées
|
|
Commit, // Demande de commit on-chain
|
|
Faucet, // Demande de fonds
|
|
Sync, // Synchronisation d'état
|
|
}
|
|
```
|
|
|
|
#### Structure d'enveloppe:
|
|
|
|
```rust
|
|
pub struct Envelope {
|
|
pub flag: AnkFlag,
|
|
pub content: String, // JSON sérialisé
|
|
}
|
|
```
|
|
|
|
### 5.3 Message types
|
|
|
|
#### 5.3.1 HandshakeMessage
|
|
|
|
**Envoyé par:** sdk_relay → client (à la connexion)
|
|
|
|
```rust
|
|
pub struct HandshakeMessage {
|
|
pub sp_address: String, // Adresse du relay
|
|
pub peers_list: OutPointMemberMap, // Carte des membres
|
|
pub processes_list: OutPointProcessMap, // Processus connus
|
|
pub chain_tip: u32, // Hauteur de bloc
|
|
}
|
|
```
|
|
|
|
**Objectif:** Synchroniser l'état initial du réseau
|
|
|
|
#### 5.3.2 NewTxMessage
|
|
|
|
**Envoyé par:** sdk_relay → clients (transaction détectée)
|
|
|
|
```rust
|
|
pub struct NewTxMessage {
|
|
pub transaction: String, // Transaction hex
|
|
pub tweak_data: Option<String>, // Données de tweak Silent Payment
|
|
pub error: Option<AnkError>,
|
|
}
|
|
```
|
|
|
|
**Objectif:** Notifier une nouvelle transaction Bitcoin concernant les participants
|
|
|
|
#### 5.3.3 CommitMessage
|
|
|
|
**Envoyé par:** client → sdk_relay (demande de commit)
|
|
|
|
```rust
|
|
pub struct CommitMessage {
|
|
pub process_id: OutPoint,
|
|
pub pcd_commitment: PcdCommitments,
|
|
pub roles: Roles,
|
|
pub public_data: Pcd,
|
|
pub validation_tokens: Vec<Proof>,
|
|
pub error: Option<AnkError>,
|
|
}
|
|
```
|
|
|
|
**Flux de commit:**
|
|
|
|
1. **Client** : Envoie `CommitMessage` avec `validation_tokens = []`
|
|
- Annonce l'intention de commit
|
|
|
|
2. **Relay** : Diffuse aux autres clients
|
|
|
|
3. **Autres clients** : Signent et renvoient leurs `validation_tokens`
|
|
|
|
4. **Client initiateur** : Accumule les signatures, renvoie `CommitMessage` complet
|
|
|
|
5. **Relay** : Vérifie les signatures, crée la transaction Bitcoin, broadcast
|
|
|
|
### 5.4 Gestion des connexions
|
|
|
|
**Fichier source:** `sdk_relay/src/main.rs` (lignes 178-242)
|
|
|
|
```rust
|
|
async fn handle_connection(
|
|
raw_stream: TcpStream,
|
|
addr: SocketAddr,
|
|
our_sp_address: SilentPaymentAddress,
|
|
) {
|
|
let ws_stream = tokio_tungstenite::accept_async(raw_stream).await?;
|
|
|
|
// Insertion dans la peer map
|
|
let (tx, rx) = unbounded_channel();
|
|
PEERMAP.lock().insert(addr, tx);
|
|
|
|
// Envoi du handshake
|
|
let init_msg = HandshakeMessage::new(
|
|
our_sp_address,
|
|
members_list,
|
|
processes_list,
|
|
chain_tip,
|
|
);
|
|
broadcast_message(AnkFlag::Handshake, init_msg, BroadcastType::Sender(addr));
|
|
|
|
// Gestion des messages
|
|
let (outgoing, incoming) = ws_stream.split();
|
|
|
|
incoming.try_for_each(|msg| {
|
|
process_message(msg.to_text(), addr);
|
|
future::ok(())
|
|
});
|
|
}
|
|
```
|
|
|
|
**Types de broadcast:**
|
|
|
|
```rust
|
|
pub enum BroadcastType {
|
|
All, // Tous les peers
|
|
Sender(SocketAddr), // Peer spécifique
|
|
Exclude(SocketAddr), // Tous sauf un
|
|
}
|
|
```
|
|
|
|
### 5.5 Synchronisation blockchain
|
|
|
|
**Fichier source:** `sdk_relay/src/sync.rs`
|
|
|
|
Le relay écoute les événements Bitcoin via:
|
|
|
|
1. **ZMQ** :
|
|
- `rawtx` : Transactions dans le mempool
|
|
- `hashblock` : Nouveaux blocs minés
|
|
|
|
2. **Bitcoin RPC** :
|
|
- Récupération des blocs complets
|
|
- Vérification des confirmations
|
|
|
|
**Détection de transactions pertinentes:**
|
|
|
|
```rust
|
|
pub fn check_tx_for_process_updates(tx: &Transaction) -> Result<OutPoint> {
|
|
let processes = lock_processes()?;
|
|
|
|
for (process_id, process) in processes.iter() {
|
|
let process_tip = process.get_process_tip()?;
|
|
|
|
// Vérifie si la tx dépense le tip
|
|
if tx.input.iter().any(|input| input.previous_output == process_tip) {
|
|
// Extrait le state_id de l'OP_RETURN
|
|
let op_return = tx.output.iter()
|
|
.find(|o| o.script_pubkey.is_op_return())?;
|
|
let state_id = &op_return.script_pubkey.as_bytes()[2..34];
|
|
|
|
// Met à jour le processus
|
|
process.commit_state(state_id)?;
|
|
return Ok(*process_id);
|
|
}
|
|
}
|
|
|
|
Err("No matching process")
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Stockage et persistance
|
|
|
|
### 6.1 Architecture de stockage
|
|
|
|
```
|
|
┌────────────────────────────────────────────────┐
|
|
│ Couche Application │
|
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
│ │ Process │ │ Member │ │ Device │ │
|
|
│ │ State │ │ Registry │ │ Wallet │ │
|
|
│ └──────────┘ └──────────┘ └──────────┘ │
|
|
└────────────────────┬───────────────────────────┘
|
|
│
|
|
┌────────────────────▼───────────────────────────┐
|
|
│ Couche de Données │
|
|
│ ┌─────────────┐ ┌─────────────┐ │
|
|
│ │ sdk_storage │ │ Blockchain │ │
|
|
│ │ (Key-Value) │ │ Bitcoin │ │
|
|
│ └─────────────┘ └─────────────┘ │
|
|
└────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 6.2 sdk_storage : Stockage clé-valeur
|
|
|
|
**Fichier source:** `sdk_storage/src/main.rs`
|
|
|
|
#### Architecture:
|
|
|
|
- **Type:** Serveur HTTP REST
|
|
- **Stockage:** Fichiers dans `~/.4nk/storage/`
|
|
- **TTL:** Optionnel (expiration automatique)
|
|
|
|
#### API:
|
|
|
|
```http
|
|
POST /store
|
|
{
|
|
"key": "hex64", # Clé 32 bytes en hex
|
|
"value": "hex", # Valeur en hex
|
|
"ttl": 3600 # Optional: secondes
|
|
}
|
|
|
|
GET /retrieve/:key
|
|
→ { "value": "hex" }
|
|
```
|
|
|
|
#### Utilisation dans 4NK:
|
|
|
|
```rust
|
|
// RoleDefinition indique les storages autorisés
|
|
pub struct RoleDefinition {
|
|
pub storages: Vec<String>, // URLs des sdk_storage
|
|
}
|
|
```
|
|
|
|
**Cas d'usage:**
|
|
|
|
1. **Données chiffrées volumineuses** : Documents, images
|
|
2. **Partage de clés** : Clés de déchiffrement AES
|
|
3. **Coordination** : États temporaires avant commit
|
|
|
|
### 6.3 Données sur blockchain
|
|
|
|
**Seules les données commitées on-chain:**
|
|
|
|
```rust
|
|
// Transaction Bitcoin
|
|
TxOut {
|
|
value: Amount::ZERO,
|
|
script_pubkey: ScriptBuf::new_op_return(&state_id), // 32 bytes
|
|
}
|
|
```
|
|
|
|
- **state_id** : Merkle root identifiant l'état
|
|
- **Pas de données brutes** sur la blockchain
|
|
- **Récupération** : Via sdk_storage ou pairs
|
|
|
|
### 6.4 Structure des données (PCD)
|
|
|
|
**Fichier source:** `sdk_common/src/pcd.rs` (lignes 225-333)
|
|
|
|
#### PCD (Private Collaborative Data)
|
|
|
|
```rust
|
|
pub struct Pcd(BTreeMap<String, Vec<u8>>);
|
|
```
|
|
|
|
**Format de sérialisation:**
|
|
|
|
```
|
|
┌─────────────────────────┐
|
|
│ Version (1 byte): 0x01 │
|
|
├─────────────────────────┤
|
|
│ DataType (1 byte): │
|
|
│ 0 = FileBlob │
|
|
│ 1 = JSON │
|
|
├─────────────────────────┤
|
|
│ Compressed data (zstd) │
|
|
└─────────────────────────┘
|
|
```
|
|
|
|
**Exemple d'utilisation:**
|
|
|
|
```rust
|
|
// Insertion
|
|
let mut pcd = Pcd::new(BTreeMap::new());
|
|
let value = json!({ "name": "Alice", "age": 30 });
|
|
pcd.insert_serializable("profile".to_string(), &value)?;
|
|
|
|
// Récupération
|
|
let profile: Value = pcd.get_as_json("profile")?;
|
|
```
|
|
|
|
#### PcdCommitments
|
|
|
|
```rust
|
|
pub struct PcdCommitments(BTreeMap<String, [u8; 32]>);
|
|
```
|
|
|
|
**Génération des commitments:**
|
|
|
|
```rust
|
|
pub fn new(commited_in: &OutPoint, attributes: &Pcd, roles: &Roles) -> Result<Self> {
|
|
let mut field2hash = BTreeMap::new();
|
|
|
|
for (field, value) in attributes.iter() {
|
|
let hash = AnkPcdHash::from_pcd_value(value, field.as_bytes(), commited_in);
|
|
field2hash.insert(field, hash.to_byte_array());
|
|
}
|
|
|
|
// Ajout du hash des rôles
|
|
let roles_hash = AnkPcdHash::from_pcd_value(roles.to_bytes()?, b"roles", commited_in);
|
|
field2hash.insert("roles".to_string(), roles_hash);
|
|
|
|
Ok(Self(field2hash))
|
|
}
|
|
```
|
|
|
|
**Propriétés:**
|
|
|
|
- **Déterministe** : BTreeMap garantit l'ordre
|
|
- **Merkle Tree** : Racine utilisée comme `state_id`
|
|
- **Prouvable** : Chemin Merkle pour prouver un champ
|
|
|
|
### 6.5 Chiffrement des données
|
|
|
|
**Algorithme:** AES-256-GCM
|
|
|
|
```rust
|
|
use sdk_common::crypto::{Aes256Gcm, KeyInit, AeadCore};
|
|
|
|
// Génération de clé
|
|
let key = Aes256Gcm::generate_key(&mut rng);
|
|
|
|
// Chiffrement
|
|
let cipher = Aes256Gcm::new(&key);
|
|
let nonce = Aes256Gcm::generate_nonce(&mut rng);
|
|
let ciphertext = cipher.encrypt(&nonce, plaintext.as_ref())?;
|
|
|
|
// Stockage de la clé dans ProcessState.keys
|
|
state.keys.insert(field_name, key.as_slice().try_into()?);
|
|
```
|
|
|
|
**Distribution des clés:**
|
|
|
|
1. **Option 1:** Via `sdk_storage` (URL dans `RoleDefinition.storages`)
|
|
2. **Option 2:** Chiffrement asymétrique avec clés publiques Silent Payment
|
|
3. **Option 3:** Partage hors-bande
|
|
|
|
---
|
|
|
|
## 7. Flux de données
|
|
|
|
### 7.1 Flux de création de processus
|
|
|
|
```
|
|
┌─────────────┐
|
|
│ Client A │
|
|
└──────┬──────┘
|
|
│ 1. Génère ProcessState initial
|
|
│ ├─ private_data (chiffré)
|
|
│ ├─ public_data
|
|
│ └─ roles
|
|
▼
|
|
┌──────────────────────┐
|
|
│ Génération state_id │
|
|
│ (Merkle root) │
|
|
└──────┬───────────────┘
|
|
│ 2. Signature avec spend_key
|
|
│ proof = sign(state_id)
|
|
▼
|
|
┌─────────────────────────┐
|
|
│ Envoi CommitMessage │
|
|
│ → sdk_relay (WebSocket) │
|
|
└──────┬──────────────────┘
|
|
│ 3. Broadcast aux autres clients
|
|
▼
|
|
┌──────────────┐ ┌──────────────┐
|
|
│ Client B │ │ Client C │
|
|
└──────┬───────┘ └──────┬───────┘
|
|
│ 4. Validation │
|
|
│ is_valid()? │
|
|
│ 5. Signature │
|
|
│ proof = sign() │
|
|
▼ ▼
|
|
┌──────────────────────────────────┐
|
|
│ Accumulation des signatures │
|
|
│ dans CommitMessage │
|
|
└──────┬───────────────────────────┘
|
|
│ 6. Renvoi à sdk_relay (complet)
|
|
▼
|
|
┌────────────────────────────────┐
|
|
│ sdk_relay vérifie quorum │
|
|
│ ├─ is_valid() │
|
|
│ └─ Toutes signatures valides │
|
|
└──────┬─────────────────────────┘
|
|
│ 7. Création transaction Bitcoin
|
|
│ ├─ Input: fonds du relay
|
|
│ ├─ Output 0: UTXO process (546 sats)
|
|
│ └─ Output 1: OP_RETURN (state_id)
|
|
▼
|
|
┌──────────────────────┐
|
|
│ Broadcast Bitcoin │
|
|
└──────┬───────────────┘
|
|
│ 8. Confirmation
|
|
▼
|
|
┌──────────────────────────┐
|
|
│ ZMQ event (rawtx) │
|
|
│ → Tous les clients │
|
|
│ → Mise à jour Process │
|
|
└──────────────────────────┘
|
|
```
|
|
|
|
### 7.2 Flux de mise à jour
|
|
|
|
```
|
|
┌─────────────┐
|
|
│ Client A │
|
|
└──────┬──────┘
|
|
│ 1. Récupère l'état actuel
|
|
│ current = process.get_latest_committed_state()
|
|
▼
|
|
┌────────────────────────┐
|
|
│ Modification de champs │
|
|
│ ├─ update_value(key) │
|
|
│ └─ Nouveau state_id │
|
|
└──────┬─────────────────┘
|
|
│ 2. Vérification des permissions
|
|
│ can_modify = roles.check(member_id, field)
|
|
▼
|
|
┌─────────────────────────┐
|
|
│ Proposition d'état │
|
|
│ insert_concurrent_state │
|
|
└──────┬──────────────────┘
|
|
│ 3. Collecte des signatures
|
|
│ (même flux que création)
|
|
▼
|
|
┌──────────────────────────┐
|
|
│ Commit on-chain │
|
|
│ ├─ Dépense ancien UTXO │
|
|
│ └─ Crée nouveau UTXO │
|
|
└──────┬───────────────────┘
|
|
│ 4. Confirmation
|
|
▼
|
|
┌──────────────────────────┐
|
|
│ Mise à jour réseau │
|
|
│ update_states_tip() │
|
|
│ Pruning états concurrents│
|
|
└──────────────────────────┘
|
|
```
|
|
|
|
### 7.3 Flux de pairing multi-device
|
|
|
|
```
|
|
Device 1 (Desktop) Device 2 (Mobile) sdk_relay
|
|
────────────────── ───────────────── ─────────
|
|
│ │ │
|
|
│ 1. Création pairing │ │
|
|
│ create_pairing_process() │ │
|
|
│────────────────────────────────────────────────────►│
|
|
│ │ │
|
|
│ 2. Broadcast HandshakeMsg │ │
|
|
│◄────────────────────────────────────────────────────│
|
|
│ │◄───────────────────────│
|
|
│ │ │
|
|
│ 3. Affichage QR Code │ │
|
|
│ (pairing_id + addresses) │ │
|
|
│ │ │
|
|
│ │ 4. Scan QR Code │
|
|
│ │ get_pairing_info() │
|
|
│ │ │
|
|
│ │ 5. Demande d'ajout │
|
|
│ │ pair_device(pairing_id)│
|
|
│ │───────────────────────►│
|
|
│ │ │
|
|
│ 6. Notification nouvelle │ │
|
|
│ adresse (CommitMessage) │ │
|
|
│◄────────────────────────────────────────────────────│
|
|
│ │ │
|
|
│ 7. Validation (signature) │ │
|
|
│ approve_pairing() │ │
|
|
│────────────────────────────────────────────────────►│
|
|
│ │ │
|
|
│ 8. Commit on-chain │ │
|
|
│ (nouveau state pairedAddr.) │ │
|
|
│◄────────────────────────────────────────────────────│
|
|
│ │◄───────────────────────│
|
|
│ │ │
|
|
│ 9. Confirmation │ 9. Confirmation │
|
|
│ device.pair() │ device.pair() │
|
|
│ │ │
|
|
```
|
|
|
|
### 7.4 Flux de synchronisation Silent Payments
|
|
|
|
```
|
|
┌──────────────┐
|
|
│ Bitcoin Node │
|
|
│ (ZMQ) │
|
|
└──────┬───────┘
|
|
│ rawtx / hashblock
|
|
▼
|
|
┌─────────────────────┐
|
|
│ sdk_relay │
|
|
│ ┌─────────────────┐ │
|
|
│ │ Blindbit Filter │ │ ← Filtres BIP158 compacts
|
|
│ └────────┬────────┘ │
|
|
│ │ │
|
|
│ ┌────────▼────────┐ │
|
|
│ │ Match Detection │ │
|
|
│ └────────┬────────┘ │
|
|
└──────────┼──────────┘
|
|
│ NewTxMessage (si match)
|
|
▼
|
|
┌────────────────────────┐
|
|
│ Clients │
|
|
│ ┌────────────────────┐ │
|
|
│ │ Silent Payment │ │
|
|
│ │ Scanner │ │
|
|
│ └────────┬───────────┘ │
|
|
│ │ │
|
|
│ ┌────────▼───────────┐ │
|
|
│ │ ECDH + Derivation │ │
|
|
│ │ calculate_ecdh() │ │
|
|
│ └────────┬───────────┘ │
|
|
│ │ │
|
|
│ ┌────────▼───────────┐ │
|
|
│ │ Output Detection │ │
|
|
│ │ OwnedOutput │ │
|
|
│ └────────┬───────────┘ │
|
|
└──────────┼─────────────┘
|
|
│
|
|
▼
|
|
┌──────────────┐
|
|
│ Device Wallet│
|
|
│ update_outputs│
|
|
└──────────────┘
|
|
```
|
|
|
|
**Détails du scanning:**
|
|
|
|
```rust
|
|
// Fichier: sdk_common/src/device.rs (lignes 65-151)
|
|
pub fn update_outputs_with_transaction(
|
|
&mut self,
|
|
tx: &Transaction,
|
|
blockheight: u32,
|
|
partial_tweak: PublicKey,
|
|
) -> Result<HashMap<OutPoint, OwnedOutput>> {
|
|
// 1. Calcul du shared secret
|
|
let shared_secret = calculate_ecdh_shared_secret(
|
|
&partial_tweak,
|
|
&self.sp_wallet.get_sp_client().get_scan_key(),
|
|
);
|
|
|
|
// 2. Extraction des clés publiques P2TR
|
|
let mut pubkeys_to_check: HashMap<XOnlyPublicKey, u32> = HashMap::new();
|
|
for (vout, output) in tx.output.iter().enumerate() {
|
|
if output.script_pubkey.is_p2tr() {
|
|
let xonly = XOnlyPublicKey::from_slice(&output.script_pubkey.as_bytes()[2..])?;
|
|
pubkeys_to_check.insert(xonly, vout as u32);
|
|
}
|
|
}
|
|
|
|
// 3. Scanning avec sp_receiver
|
|
let ours = self.sp_wallet.get_sp_client().sp_receiver
|
|
.scan_transaction(&shared_secret, pubkeys_to_check.keys().cloned().collect())?;
|
|
|
|
// 4. Création des OwnedOutput
|
|
let mut new_outputs = HashMap::new();
|
|
for (label, map) in ours.iter() {
|
|
for (key, scalar) in map {
|
|
let vout = pubkeys_to_check.get(&key).unwrap();
|
|
let outpoint = OutPoint::new(tx.txid(), *vout);
|
|
let owned = OwnedOutput {
|
|
blockheight: Height::from_consensus(blockheight)?,
|
|
tweak: scalar.to_be_bytes(),
|
|
amount: tx.output[*vout as usize].value,
|
|
script: tx.output[*vout as usize].script_pubkey.to_bytes().try_into()?,
|
|
label: label.clone(),
|
|
spend_status: OutputSpendStatus::Unspent,
|
|
};
|
|
new_outputs.insert(outpoint, owned);
|
|
}
|
|
}
|
|
|
|
// 5. Mise à jour du wallet
|
|
self.sp_wallet.get_mut_outputs().extend(new_outputs.clone());
|
|
|
|
// 6. Détection des dépenses
|
|
for input in tx.input.iter() {
|
|
if let Some(prevout) = self.sp_wallet.get_mut_outputs().get_mut(&input.previous_output) {
|
|
prevout.spend_status = OutputSpendStatus::Spent(*tx.txid().as_byte_array());
|
|
}
|
|
}
|
|
|
|
Ok(new_outputs)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Sécurité
|
|
|
|
### 8.1 Modèle de menaces
|
|
|
|
#### Menaces couvertes:
|
|
|
|
1. **Usurpation d'identité** : ✅ Signatures Schnorr obligatoires
|
|
2. **Modification non autorisée** : ✅ Validation multi-signatures
|
|
3. **Replay attacks** : ✅ state_id unique (Merkle root)
|
|
4. **Man-in-the-middle** : ✅ Messages signés cryptographiquement
|
|
5. **Observation réseau** : ✅ Données chiffrées (AES-256-GCM)
|
|
6. **Perte de clés** : ✅ Multi-device pairing
|
|
|
|
#### Menaces non couvertes:
|
|
|
|
1. **Compromission du device** : ❌ Clés stockées localement
|
|
2. **Collusion des membres** : ⚠️ Dépend du quorum configuré
|
|
3. **Attaque 51% Bitcoin** : ❌ Dépendance à la sécurité Bitcoin
|
|
4. **DoS sur sdk_relay** : ⚠️ Rate limiting à implémenter
|
|
|
|
### 8.2 Primitives cryptographiques
|
|
|
|
#### Courbes elliptiques:
|
|
|
|
- **secp256k1** : Courbe Bitcoin standard
|
|
- **Schnorr signatures** : BIP340
|
|
|
|
#### Hachage:
|
|
|
|
- **SHA-256** : Hash principal
|
|
- **Tagged hashes** : BIP340 style
|
|
```rust
|
|
AnkPcdHash::from_pcd_value(data, tag, outpoint)
|
|
```
|
|
|
|
#### Chiffrement symétrique:
|
|
|
|
- **AES-256-GCM** : AEAD (Authenticated Encryption with Associated Data)
|
|
- **Nonce** : 96 bits aléatoires
|
|
- **Key derivation** : Potentiellement HKDF (à vérifier)
|
|
|
|
#### Signatures:
|
|
|
|
```rust
|
|
pub struct Proof {
|
|
// Signature Schnorr (64 bytes)
|
|
signature: [u8; 64],
|
|
// Clé publique (33 bytes compressed)
|
|
public_key: PublicKey,
|
|
// Message (32 bytes)
|
|
message: [u8; 32],
|
|
}
|
|
```
|
|
|
|
**Vérification:**
|
|
|
|
```rust
|
|
impl Proof {
|
|
pub fn verify(&self) -> Result<()> {
|
|
let secp = Secp256k1::verification_only();
|
|
let message = Message::from_slice(&self.message)?;
|
|
secp.verify_schnorr(&self.signature, &message, &self.public_key.x_only_public_key().0)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
```
|
|
|
|
### 8.3 Isolation des données
|
|
|
|
#### Niveaux de visibilité:
|
|
|
|
1. **Privé (PCD)** :
|
|
- Données chiffrées
|
|
- Seul le commitment on-chain
|
|
- Clés distribuées aux membres autorisés
|
|
|
|
2. **Public (public_data)** :
|
|
- Lisibles par tous les participants réseau
|
|
- Exemple: `"pairedAddresses"`
|
|
|
|
3. **On-chain** :
|
|
- Uniquement `state_id` (32 bytes)
|
|
- Aucune donnée sensible
|
|
|
|
#### Gestion des clés:
|
|
|
|
```rust
|
|
// Stockage dans ProcessState
|
|
pub struct ProcessState {
|
|
pub keys: BTreeMap<String, [u8; 32]>, // field_name -> AES key
|
|
}
|
|
```
|
|
|
|
**Distribution:**
|
|
|
|
- **Méthode 1:** Stockage chiffré asymétriquement sur `sdk_storage`
|
|
- **Méthode 2:** Envoi via `CipherMessage` (WebSocket)
|
|
- **Méthode 3:** Dérivation à partir d'un secret partagé (ECDH)
|
|
|
|
### 8.4 Protection contre les attaques
|
|
|
|
#### Rejeu de transactions (Replay):
|
|
|
|
✅ **Protection:** `state_id` unique (Merkle root inclut `commited_in`)
|
|
|
|
```rust
|
|
let hash = AnkPcdHash::from_pcd_value(data, field_name, outpoint);
|
|
// outpoint change à chaque état -> state_id différent
|
|
```
|
|
|
|
#### Double-spending:
|
|
|
|
✅ **Protection:** Consensus Bitcoin
|
|
|
|
- Chaque état possède un UTXO unique
|
|
- Dépense de l'UTXO = commit atomique
|
|
|
|
#### Manipulation des états:
|
|
|
|
✅ **Protection:** Validation multi-signatures + Merkle proofs
|
|
|
|
```rust
|
|
// Impossible de modifier un champ sans:
|
|
// 1. Recalculer le Merkle root (state_id)
|
|
// 2. Obtenir les signatures des membres autorisés
|
|
```
|
|
|
|
#### Attaque Sybil (création de fausses identités):
|
|
|
|
✅ **Protection:** Coût du pairing (frais Bitcoin)
|
|
|
|
- Créer un membre = créer un processus on-chain
|
|
- Coût: frais de transaction Bitcoin
|
|
|
|
#### Censure par le relay:
|
|
|
|
⚠️ **Risque résiduel:**
|
|
|
|
- Un relay malveillant peut ne pas diffuser les messages
|
|
- **Mitigation:** Multi-relays (connexion à plusieurs relays)
|
|
- **Futur:** Gossip protocol P2P
|
|
|
|
### 8.5 Audit et traçabilité
|
|
|
|
#### Traçabilité on-chain:
|
|
|
|
```
|
|
Process Timeline (on Bitcoin):
|
|
Txid 1 (création) → OutPoint 1 → OP_RETURN (state_id_1)
|
|
Txid 2 (update) → OutPoint 2 → OP_RETURN (state_id_2)
|
|
Txid 3 (update) → OutPoint 3 → OP_RETURN (state_id_3)
|
|
...
|
|
```
|
|
|
|
**Vérification:**
|
|
|
|
1. Récupérer la chaîne de transactions
|
|
2. Vérifier la continuité (input.prev_out = prev_state.commited_in)
|
|
3. Vérifier les OP_RETURN (state_id)
|
|
4. Recalculer les Merkle roots
|
|
5. Vérifier les signatures de chaque état
|
|
|
|
#### Logs off-chain:
|
|
|
|
**sdk_relay** conserve:
|
|
|
|
- Historique des messages WebSocket
|
|
- États proposés (même non committés)
|
|
- Signatures reçues
|
|
|
|
**Format de log recommandé:**
|
|
|
|
```json
|
|
{
|
|
"timestamp": "2025-10-01T12:00:00Z",
|
|
"event": "commit_proposal",
|
|
"process_id": "abc123:0",
|
|
"state_id": "def456...",
|
|
"proposer": "sp1qq...",
|
|
"validation_tokens": [
|
|
{
|
|
"public_key": "02abc...",
|
|
"signature": "304402...",
|
|
"timestamp": "2025-10-01T12:00:05Z"
|
|
}
|
|
],
|
|
"committed": true,
|
|
"txid": "xyz789..."
|
|
}
|
|
```
|
|
|
|
### 8.6 Bonnes pratiques
|
|
|
|
#### Pour les développeurs:
|
|
|
|
1. **Quorum élevé pour actions critiques** : ≥ 0.67 (2/3)
|
|
2. **min_sig_member = 1.0** : Tous les devices d'un membre doivent signer
|
|
3. **Rôle "apophis"** : Toujours définir pour permettre l'oblitération
|
|
4. **Chiffrement systématique** : Données sensibles dans PCD (privé)
|
|
5. **Validation côté client** : Avant d'envoyer `CommitMessage`
|
|
|
|
#### Pour les utilisateurs:
|
|
|
|
1. **Multi-device** : Pairer au moins 2 devices (backup)
|
|
2. **Sauvegarde des clés** : Export régulier du wallet
|
|
3. **Vérification des rôles** : Avant de signer un état
|
|
4. **Vérification des champs** : Utiliser `get_fields_to_validate_for_member()`
|
|
|
|
#### Configuration de roles sécurisée:
|
|
|
|
```rust
|
|
// Exemple: Gestion de contrat
|
|
let roles = Roles::new(BTreeMap::from([
|
|
// Rôle owner: peut tout modifier
|
|
("owner".to_string(), RoleDefinition {
|
|
members: vec![owner_pairing_id],
|
|
validation_rules: vec![
|
|
ValidationRule::new(1.0, vec!["roles".to_string()], 1.0)?, // 100% consensus
|
|
],
|
|
storages: vec![],
|
|
}),
|
|
|
|
// Rôle validator: valide les documents
|
|
("validator".to_string(), RoleDefinition {
|
|
members: vec![validator1_id, validator2_id],
|
|
validation_rules: vec![
|
|
ValidationRule::new(0.67, vec!["idCertified".to_string()], 0.5)?, // 2/3 consensus
|
|
],
|
|
storages: vec!["https://storage.4nkweb.com".to_string()],
|
|
}),
|
|
|
|
// Rôle client: lecture seule + modification de son profil
|
|
("client".to_string(), RoleDefinition {
|
|
members: vec![client_id],
|
|
validation_rules: vec![
|
|
ValidationRule::new(1.0, vec!["clientProfile".to_string()], 1.0)?,
|
|
],
|
|
storages: vec![],
|
|
}),
|
|
|
|
// Rôle apophis: oblitération (owner uniquement)
|
|
("apophis".to_string(), RoleDefinition {
|
|
members: vec![owner_pairing_id],
|
|
validation_rules: vec![
|
|
ValidationRule::new(1.0, vec!["".to_string()], 1.0)?,
|
|
],
|
|
storages: vec![],
|
|
}),
|
|
]));
|
|
```
|
|
|
|
---
|
|
|
|
## Conclusion
|
|
|
|
Le système d'identité et de processus de 4NK repose sur une architecture décentralisée innovante combinant:
|
|
|
|
1. **Identité cryptographique** : Silent Payments Bitcoin (BIP352)
|
|
2. **Multi-device** : Pairing flexible sans compromis de sécurité
|
|
3. **Consensus distribué** : Validation multi-signatures avec quorum configurables
|
|
4. **Immutabilité** : Commitments Bitcoin pour traçabilité
|
|
5. **Confidentialité** : Chiffrement bout-en-bout des données sensibles
|
|
|
|
Cette architecture permet des cas d'usage variés:
|
|
- Gestion de contrats collaboratifs
|
|
- Signature électronique multi-parties
|
|
- Workflows d'approbation décentralisés
|
|
- Identité numérique souveraine
|
|
|
|
**Points forts:**
|
|
- Pas de serveur central de confiance
|
|
- Résistance à la censure
|
|
- Auditabilité complète
|
|
- Interopérabilité Bitcoin native
|
|
|
|
**Axes d'amélioration:**
|
|
- Performance du scanning Silent Payments (optimisation Blindbit)
|
|
- Résilience réseau (multi-relays, gossip protocol)
|
|
- Gestion des clés (recovery social, hardware wallets)
|
|
- UX du pairing (NFC, QR codes dynamiques)
|
|
|
|
---
|
|
|
|
**Document généré le 1 octobre 2025**
|
|
**Basé sur l'analyse du code source 4NK (sdk_client, sdk_common, sdk_relay, sdk_storage, ihm_client)**
|
|
|
|
|