47 KiB
Spécification du Système d'Identité et de Processus 4NK
Version: 1.0
Date: 1 octobre 2025
Auteur: Analyse complète des composants sdk_client, sdk_common, sdk_relay, sdk_storage, ihm_client, rust-silentPayments
Table des matières
- Vue d'ensemble
- Architecture d'identité
- Système de processus
- Validation et consensus
- Communication réseau
- Stockage et persistance
- Flux de données
- 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 les Silent Payments (BIP352) pour l'identité et le chiffrement. Le système permet à plusieurs parties de collaborer sur des processus avec des états vérifiables, des rôles définis et des règles de validation cryptographiques.
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 Bitcoin
- Validation distribuée : Signatures cryptographiques multiples requises
- Communication P2P : Relais WebSocket 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
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:
-
Création :
Device::new(sp_client)
- Génère une adresse Silent Payment locale
- Initialise un
Member
avec cette adresse seule
-
Pairing :
device.pair(commitment_outpoint, member)
- Associe le device à un processus de pairing
- Lie plusieurs adresses Silent Payment ensemble
-
Unpairing :
device.unpair()
- Réinitialise à l'état local unique
2.3 Structure Member
Fichier source: sdk_common/src/pcd.rs
(lignes 140-223)
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:
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:
-
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
- Un device crée un nouveau
-
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)
- Fichier:
-
État après pairing
device.pairing_process_commitment = Some(process_id); device.paired_member = Member::new(all_paired_addresses);
Code de validation (extrait):
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:
- Adresse Silent Payment : Identité publique cryptographique
- Process ID (OutPoint) : Identifiant du processus de pairing (si multi-device)
- Member : Groupe d'adresses associées
Carte d'identité réseau:
// 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 (initial empty)
├─ State 1 (premier commit)
├─ State 2 (update)
├─ ...
└─ State n (dernier = toujours empty)
Règle fondamentale: Le dernier état est toujours vide, représentant le prochain UTXO à dépenser.
3.2 Structure ProcessState
Fichier source: sdk_common/src/process.rs
(lignes 19-35)
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:
-
commited_in
: UTXO Bitcoin qui "porte" cet état- Permet de lier l'état à la blockchain
- Forme l'identifiant du processus (premier
commited_in
)
-
pcd_commitment
: Commitments Merkle des données privées- Map
field_name -> hash(compressed_value)
- Hash taggé:
AnkPcdHash::from_pcd_value(data, field, outpoint)
- Map
-
state_id
: Racine Merkle de tous les commitments + rôles- Identifiant unique de l'état
- Utilisé pour les signatures de validation
-
validation_tokens
: Signatures cryptographiques- Prouvent l'approbation des membres
- Type:
Proof
(signature Schnorr surstate_id
)
-
public_data
: Données non chiffrées- Lisibles par tous les participants
- Exemple:
"pairedAddresses"
pour le pairing
-
roles
: Définition des rôles et permissions- Map
role_name -> RoleDefinition
- Contrôle qui peut modifier quels champs
- Map
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:
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, validation par adresses existantes
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
- Fichier:
sdk_common/src/process.rs
(lignes 133-159)
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:
ValidationRule::new(1.0, all_keys, 1.0)
- Fichier:
sdk_common/src/process.rs
(lignes 103-130)
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)
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
(process IDs) plutôt que des adresses- Permet d'ajouter des devices sans modifier les rôles
- Résolu via
OutPointMemberMap
au moment de la validation
-
validation_rules : Définit quels champs peuvent être modifiés
- Quorum requis
- Signatures minimales par membre
-
storages : Liste 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)
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:
// Règle : 50% des membres, chacun avec au moins 1 device
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 fournir au moins 50% de ses devices
- 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:
pub fn is_valid(
&self,
previous_state: Option<&ProcessState>,
members_list: &OutPointMemberMap,
) -> anyhow::Result<()>
-
Vérification de base
validation_tokens
non vide
-
Cas spéciaux (prioritaires):
- Oblitération :
state_id == [0u8; 32]
- Pairing : Rôle
"pairing"
présent
- Oblitération :
-
Gestion du Demiurge (état initial uniquement):
if previous_state.is_none() { if let Some(demiurge) = roles.get("demiurge") { // Génère une règle couvrant tous les champs } }
-
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:
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)
pub fn is_satisfied(
&self,
field: &str,
merkle_root: [u8; 32],
proofs: &[Proof],
members: &[&Member],
) -> Result<()>
Algorithme:
-
Calcul du quorum:
let required_members = (members.len() as f32 * self.quorum).ceil() as usize;
-
Filtrage des membres validants:
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();
-
Vérification du quorum:
validating_members >= required_members
4.6 Signatures cryptographiques
Structure Proof
pub struct Proof {
signature: SchnorrSignature,
public_key: PublicKey,
message: [u8; 32],
}
Types de messages signés:
pub enum AnkHash {
ValidationYes(AnkValidationYesHash),
ValidationNo(AnkValidationNoHash),
}
- ValidationYes : Approbation du
state_id
- ValidationNo : Rejet du
state_id
Création d'une signature:
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:
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%)
// 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
// 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
):
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 (testnet)
Sync, // Synchronisation d'état
}
Structure d'enveloppe:
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)
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)
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)
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:
-
Client : Envoie
CommitMessage
avecvalidation_tokens = []
- Annonce l'intention de commit
-
Relay : Diffuse aux autres clients
-
Autres clients : Signent et renvoient leurs
validation_tokens
-
Client initiateur : Accumule les signatures, renvoie
CommitMessage
complet -
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)
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:
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:
-
ZMQ :
rawtx
: Transactions dans le mempoolhashblock
: Nouveaux blocs minés
-
Bitcoin RPC :
- Récupération des blocs complets
- Vérification des confirmations
Détection de transactions pertinentes:
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:
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:
// RoleDefinition indique les storages autorisés
pub struct RoleDefinition {
pub storages: Vec<String>, // URLs des sdk_storage
}
Cas d'usage:
- Données chiffrées volumineuses : Documents, images
- Partage de clés : Clés de déchiffrement AES
- Coordination : États temporaires avant commit
6.3 Données sur blockchain
Seules les données commitées on-chain:
// 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)
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:
// 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
pub struct PcdCommitments(BTreeMap<String, [u8; 32]>);
Génération des commitments:
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
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:
- Option 1: Via
sdk_storage
(URL dansRoleDefinition.storages
) - Option 2: Chiffrement asymétrique avec clés publiques Silent Payment
- 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:
// 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:
- Usurpation d'identité : ✅ Signatures Schnorr obligatoires
- Modification non autorisée : ✅ Validation multi-signatures
- Replay attacks : ✅ state_id unique (Merkle root)
- Man-in-the-middle : ✅ Messages signés cryptographiquement
- Observation réseau : ✅ Données chiffrées (AES-256-GCM)
- Perte de clés : ✅ Multi-device pairing
Menaces non couvertes:
- Compromission du device : ❌ Clés stockées localement
- Collusion des membres : ⚠️ Dépend du quorum configuré
- Attaque 51% Bitcoin : ❌ Dépendance à la sécurité Bitcoin
- 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
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:
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:
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é:
-
Privé (PCD) :
- Données chiffrées
- Seul le commitment on-chain
- Clés distribuées aux membres autorisés
-
Public (public_data) :
- Lisibles par tous les participants réseau
- Exemple:
"pairedAddresses"
-
On-chain :
- Uniquement
state_id
(32 bytes) - Aucune donnée sensible
- Uniquement
Gestion des clés:
// 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
)
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
// 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:
- Récupérer la chaîne de transactions
- Vérifier la continuité (input.prev_out = prev_state.commited_in)
- Vérifier les OP_RETURN (state_id)
- Recalculer les Merkle roots
- 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é:
{
"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:
- Quorum élevé pour actions critiques : ≥ 0.67 (2/3)
- min_sig_member = 1.0 : Tous les devices d'un membre doivent signer
- Rôle "apophis" : Toujours définir pour permettre l'oblitération
- Chiffrement systématique : Données sensibles dans PCD (privé)
- Validation côté client : Avant d'envoyer
CommitMessage
Pour les utilisateurs:
- Multi-device : Pairer au moins 2 devices (backup)
- Sauvegarde des clés : Export régulier du wallet
- Vérification des rôles : Avant de signer un état
- Vérification des champs : Utiliser
get_fields_to_validate_for_member()
Configuration de roles sécurisée:
// 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:
- Identité cryptographique : Silent Payments Bitcoin (BIP352)
- Multi-device : Pairing flexible sans compromis de sécurité
- Consensus distribué : Validation multi-signatures avec quorum configurables
- Immutabilité : Commitments Bitcoin pour traçabilité
- 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)