4NK_env/docs/4NK_IDENTITY_AND_PROCESS_SPEC.md
2025-10-02 15:32:58 +00:00

49 KiB

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
  2. Architecture d'identité
  3. Système de processus
  4. Validation et consensus
  5. Communication réseau
  6. Stockage et persistance
  7. Flux de données
  8. 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) 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

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)

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:

  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

    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:

  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:

// 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

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:

  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:

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)
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)
  • 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)

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)

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 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:

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):

    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:

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:

  1. Calcul du quorum:

    let required_members = (members.len() as f32 * self.quorum).ceil() as usize;
    
  2. 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();
    
  3. 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
    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:

  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)

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:

  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:

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:

  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:

// 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:

  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:

// 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
    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é:

  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:

// 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:

  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é:

{
  "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:

// 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)