
- Spécification complète identité et processus (4NK_IDENTITY_AND_PROCESS_SPEC.md) - Résumé exécutif (4NK_IDENTITY_PROCESS_SUMMARY.md) - Spécification technique DAO sur 4NK (4NK_DAO_TECHNICAL_SPECIFICATION.md) - Ajout blindbit-oracle dans .gitmodules depuis GitHub Documents couvrant: - Architecture d'identité (Device, Member, Pairing) - Système de processus et machine à états - Validation distribuée et consensus - Communication réseau (WebSocket, sdk_relay) - Implémentation DAO complète avec gouvernance, votes, trésorerie multisig
2402 lines
72 KiB
Markdown
2402 lines
72 KiB
Markdown
# Implémentation d'une DAO sur 4NK : Spécification Technique
|
|
|
|
**Version:** 1.0
|
|
**Date:** 1 octobre 2025
|
|
**Prérequis:** [4NK_IDENTITY_AND_PROCESS_SPEC.md](./4NK_IDENTITY_AND_PROCESS_SPEC.md)
|
|
|
|
---
|
|
|
|
## Table des matières
|
|
|
|
1. [Architecture d'une DAO 4NK](#1-architecture-dune-dao-4nk)
|
|
2. [Mécanismes de gouvernance](#2-mécanismes-de-gouvernance)
|
|
3. [Système de vote on-chain](#3-système-de-vote-on-chain)
|
|
4. [Gestion de trésorerie](#4-gestion-de-trésorerie)
|
|
5. [Propositions et exécution](#5-propositions-et-exécution)
|
|
6. [Tokenisation et pondération](#6-tokenisation-et-pondération)
|
|
7. [Sécurité et attaques](#7-sécurité-et-attaques)
|
|
8. [Implémentation de référence](#8-implémentation-de-référence)
|
|
|
|
---
|
|
|
|
## 1. Architecture d'une DAO 4NK
|
|
|
|
### 1.1 Vue d'ensemble
|
|
|
|
Une DAO (Decentralized Autonomous Organization) sur 4NK exploite les primitives existantes pour créer une organisation autonome avec:
|
|
|
|
- **Gouvernance on-chain** via ProcessState
|
|
- **Identité cryptographique** via Silent Payments
|
|
- **Consensus distribué** via ValidationRules
|
|
- **Trésorerie Bitcoin** via UTXO management
|
|
- **Auditabilité complète** via blockchain commitments
|
|
|
|
```
|
|
┌────────────────────────────────────────────────────────────┐
|
|
│ DAO 4NK │
|
|
├────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ GOUVERNANCE TRÉSORERIE │
|
|
│ ┌─────────────┐ ┌─────────────┐ │
|
|
│ │ Processus │ │ Multisig │ │
|
|
│ │ de vote │ │ UTXO Pool │ │
|
|
│ └──────┬──────┘ └──────┬──────┘ │
|
|
│ │ │ │
|
|
│ │ ┌──────────────┐ │ │
|
|
│ └─►│ Proposition │◄───────┘ │
|
|
│ │ Executable │ │
|
|
│ └──────┬───────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌────────────────────┐ │
|
|
│ │ Exécution on-chain│ │
|
|
│ │ (Bitcoin Tx) │ │
|
|
│ └────────────────────┘ │
|
|
│ │
|
|
│ MEMBRES │
|
|
│ ┌──────────┬──────────┬──────────┐ │
|
|
│ │ Founder │ Member │ Delegate │ │
|
|
│ │ (100%) │ (1 vote) │ (N votes)│ │
|
|
│ └──────────┴──────────┴──────────┘ │
|
|
└────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 1.2 Composants techniques
|
|
|
|
#### A. Processus DAO principal
|
|
|
|
```rust
|
|
pub struct DaoProcess {
|
|
// Processus 4NK contenant la gouvernance
|
|
process: Process,
|
|
|
|
// Configuration de la DAO
|
|
config: DaoConfig,
|
|
|
|
// État actuel
|
|
current_state: DaoState,
|
|
}
|
|
|
|
pub struct DaoConfig {
|
|
// Nom de la DAO
|
|
name: String,
|
|
|
|
// Type de vote
|
|
voting_system: VotingSystem,
|
|
|
|
// Quorum requis (0.0 à 1.0)
|
|
quorum: f32,
|
|
|
|
// Durée de vote (en blocs Bitcoin)
|
|
voting_period_blocks: u32,
|
|
|
|
// Délai d'exécution (timelock)
|
|
execution_delay_blocks: u32,
|
|
|
|
// Trésorerie
|
|
treasury_address: SilentPaymentAddress,
|
|
}
|
|
|
|
pub struct DaoState {
|
|
// Membres de la DAO
|
|
members: HashMap<OutPoint, MemberInfo>,
|
|
|
|
// Propositions actives
|
|
active_proposals: Vec<Proposal>,
|
|
|
|
// Propositions exécutées
|
|
executed_proposals: Vec<ProposalExecution>,
|
|
|
|
// Balance de trésorerie
|
|
treasury_balance: Amount,
|
|
}
|
|
```
|
|
|
|
#### B. Structure de membre DAO
|
|
|
|
```rust
|
|
pub struct MemberInfo {
|
|
// Pairing process ID du membre
|
|
member_id: OutPoint,
|
|
|
|
// Adresses Silent Payment
|
|
sp_addresses: Vec<SilentPaymentAddress>,
|
|
|
|
// Poids de vote (tokens)
|
|
voting_power: u64,
|
|
|
|
// Délégation (optionnel)
|
|
delegated_to: Option<OutPoint>,
|
|
|
|
// Date d'adhésion (hauteur de bloc)
|
|
joined_at: u32,
|
|
|
|
// Récompenses accumulées
|
|
rewards: Amount,
|
|
}
|
|
|
|
impl MemberInfo {
|
|
pub fn effective_voting_power(&self, state: &DaoState) -> u64 {
|
|
if let Some(delegate) = self.delegated_to {
|
|
0 // Votes délégués
|
|
} else {
|
|
// Inclure les votes délégués à ce membre
|
|
let delegated_votes: u64 = state.members.values()
|
|
.filter(|m| m.delegated_to == Some(self.member_id))
|
|
.map(|m| m.voting_power)
|
|
.sum();
|
|
|
|
self.voting_power + delegated_votes
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 1.3 Processus fondamentaux
|
|
|
|
Une DAO 4NK repose sur **trois processus distincts** :
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────┐
|
|
│ Processus 1: GOUVERNANCE │
|
|
│ ├─ États: Propositions, Votes, Paramètres │
|
|
│ ├─ Rôles: Founders, Members, Delegates │
|
|
│ └─ Validations: Quorum-based │
|
|
└─────────────────────────────────────────────────────┘
|
|
│
|
|
▼ Référence via process_id
|
|
┌─────────────────────────────────────────────────────┐
|
|
│ Processus 2: TRÉSORERIE (Multisig) │
|
|
│ ├─ États: UTXO pool, Dépenses │
|
|
│ ├─ Rôles: Signataires (M-of-N) │
|
|
│ └─ Validations: Signatures cryptographiques │
|
|
└─────────────────────────────────────────────────────┘
|
|
│
|
|
▼ Référence via execution_id
|
|
┌─────────────────────────────────────────────────────┐
|
|
│ Processus 3: ADHÉSIONS (Pairing collectif) │
|
|
│ ├─ États: Liste des membres │
|
|
│ ├─ Rôles: Admission committee │
|
|
│ └─ Validations: Vote d'admission │
|
|
└─────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
**Avantages de cette architecture:**
|
|
|
|
- **Séparation des responsabilités** : Gouvernance ≠ Trésorerie
|
|
- **Évolutivité** : Chaque processus peut évoluer indépendamment
|
|
- **Sécurité** : Compromission d'un processus n'affecte pas les autres
|
|
- **Auditabilité** : Traçabilité complète de chaque aspect
|
|
|
|
---
|
|
|
|
## 2. Mécanismes de gouvernance
|
|
|
|
### 2.1 Types de vote
|
|
|
|
#### A. Vote simple (1 membre = 1 vote)
|
|
|
|
```rust
|
|
pub enum VotingSystem {
|
|
Simple, // 1 membre = 1 vote
|
|
Weighted, // Vote pondéré par tokens
|
|
Quadratic, // Vote quadratique (√tokens)
|
|
Delegated, // Délégation de votes
|
|
Reputation, // Basé sur la réputation
|
|
}
|
|
|
|
impl VotingSystem {
|
|
pub fn calculate_vote_power(&self, member: &MemberInfo, state: &DaoState) -> u64 {
|
|
match self {
|
|
VotingSystem::Simple => 1,
|
|
VotingSystem::Weighted => member.effective_voting_power(state),
|
|
VotingSystem::Quadratic => {
|
|
let power = member.effective_voting_power(state);
|
|
(power as f64).sqrt() as u64
|
|
},
|
|
VotingSystem::Delegated => {
|
|
if member.delegated_to.is_some() {
|
|
0
|
|
} else {
|
|
member.effective_voting_power(state)
|
|
}
|
|
},
|
|
VotingSystem::Reputation => {
|
|
self.calculate_reputation(member, state)
|
|
},
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### B. Vote pondéré par tokens
|
|
|
|
**Cas d'usage:** DAOs avec investisseurs (proportionnel à l'investissement)
|
|
|
|
```rust
|
|
pub struct TokenWeightedVote {
|
|
// Total de tokens en circulation
|
|
total_supply: u64,
|
|
|
|
// Tokens détenus par membre
|
|
balances: HashMap<OutPoint, u64>,
|
|
}
|
|
|
|
impl TokenWeightedVote {
|
|
pub fn vote_power(&self, member_id: &OutPoint) -> f32 {
|
|
let tokens = self.balances.get(member_id).unwrap_or(&0);
|
|
(*tokens as f32) / (self.total_supply as f32)
|
|
}
|
|
|
|
pub fn has_quorum(&self, votes: &HashMap<OutPoint, Vote>, quorum: f32) -> bool {
|
|
let voted_tokens: u64 = votes.keys()
|
|
.filter_map(|member_id| self.balances.get(member_id))
|
|
.sum();
|
|
|
|
let participation = (voted_tokens as f32) / (self.total_supply as f32);
|
|
participation >= quorum
|
|
}
|
|
}
|
|
```
|
|
|
|
#### C. Vote quadratique
|
|
|
|
**Objectif:** Réduire l'influence des gros détenteurs
|
|
|
|
```
|
|
Pouvoir de vote = √(tokens détenus)
|
|
|
|
Exemples:
|
|
- 1 token → 1 vote (√1 = 1)
|
|
- 100 tokens → 10 votes (√100 = 10)
|
|
- 10,000 tokens → 100 votes (√10,000 = 100)
|
|
```
|
|
|
|
**Implémentation:**
|
|
|
|
```rust
|
|
pub struct QuadraticVoting {
|
|
balances: HashMap<OutPoint, u64>,
|
|
}
|
|
|
|
impl QuadraticVoting {
|
|
pub fn vote_power(&self, member_id: &OutPoint) -> u64 {
|
|
let tokens = self.balances.get(member_id).unwrap_or(&0);
|
|
(*tokens as f64).sqrt() as u64
|
|
}
|
|
|
|
pub fn total_vote_power(&self) -> u64 {
|
|
self.balances.values()
|
|
.map(|&tokens| (tokens as f64).sqrt() as u64)
|
|
.sum()
|
|
}
|
|
}
|
|
```
|
|
|
|
**Avantages:**
|
|
|
|
- ✅ Réduit la concentration du pouvoir
|
|
- ✅ Encourage la participation des petits membres
|
|
- ✅ Coût d'attaque plus élevé (quadratique vs linéaire)
|
|
|
|
**Inconvénients:**
|
|
|
|
- ⚠️ Complexité accrue
|
|
- ⚠️ Potentiel de Sybil attacks (multiple comptes)
|
|
|
|
### 2.2 Structure d'une proposition
|
|
|
|
```rust
|
|
pub struct Proposal {
|
|
// Identifiant unique (hash de la proposition)
|
|
id: [u8; 32],
|
|
|
|
// Créateur de la proposition
|
|
proposer: OutPoint,
|
|
|
|
// Titre et description
|
|
title: String,
|
|
description: String,
|
|
|
|
// Type de proposition
|
|
proposal_type: ProposalType,
|
|
|
|
// Actions à exécuter si approuvée
|
|
actions: Vec<ProposalAction>,
|
|
|
|
// Période de vote
|
|
voting_start: u32, // Hauteur de bloc
|
|
voting_end: u32,
|
|
|
|
// État du vote
|
|
votes: HashMap<OutPoint, Vote>,
|
|
|
|
// Statut
|
|
status: ProposalStatus,
|
|
|
|
// Exécution (si approuvée)
|
|
execution: Option<ProposalExecution>,
|
|
}
|
|
|
|
pub enum ProposalType {
|
|
// Modification de paramètres
|
|
ConfigChange(ConfigChange),
|
|
|
|
// Dépense de trésorerie
|
|
TreasurySpend(TreasurySpend),
|
|
|
|
// Ajout/Retrait de membre
|
|
MembershipChange(MembershipChange),
|
|
|
|
// Mise à jour des rôles
|
|
RoleUpdate(RoleUpdate),
|
|
|
|
// Proposition générique
|
|
Generic(String),
|
|
}
|
|
|
|
pub enum Vote {
|
|
Yes,
|
|
No,
|
|
Abstain,
|
|
}
|
|
|
|
pub enum ProposalStatus {
|
|
Draft, // En rédaction
|
|
Active, // Vote en cours
|
|
Passed, // Approuvée
|
|
Rejected, // Rejetée
|
|
Executed, // Exécutée
|
|
Cancelled, // Annulée
|
|
Expired, // Expirée
|
|
}
|
|
```
|
|
|
|
### 2.3 Cycle de vie d'une proposition
|
|
|
|
```
|
|
┌─────────────────┐
|
|
│ 1. CRÉATION │ Membre soumet une proposition
|
|
│ (Draft) │ ├─ Validation formelle
|
|
│ │ └─ Dépôt de caution (optionnel)
|
|
└────────┬────────┘
|
|
│
|
|
▼ voting_start
|
|
┌─────────────────┐
|
|
│ 2. VOTE ACTIF │ Période de vote ouverte
|
|
│ (Active) │ ├─ Membres votent (Yes/No/Abstain)
|
|
│ │ └─ Accumulation des votes
|
|
└────────┬────────┘
|
|
│
|
|
▼ voting_end
|
|
┌─────────────────────────┐
|
|
│ 3. DÉCOMPTE │
|
|
│ ├─ Calcul du quorum │
|
|
│ ├─ Calcul de la majorité│
|
|
│ └─ Détermination statut│
|
|
└────────┬────────────────┘
|
|
│
|
|
┌────┴────┐
|
|
│ │
|
|
▼ ▼
|
|
Passed Rejected
|
|
│
|
|
▼ execution_delay
|
|
┌─────────────────┐
|
|
│ 4. EXÉCUTION │ Création transaction Bitcoin
|
|
│ (Executed) │ ├─ Validation finale
|
|
│ │ ├─ Broadcast transaction
|
|
│ │ └─ Confirmation on-chain
|
|
└─────────────────┘
|
|
```
|
|
|
|
### 2.4 Règles de validation de proposition
|
|
|
|
**Utilisation des ValidationRules de 4NK:**
|
|
|
|
```rust
|
|
// Rôle "proposer" : Peut créer des propositions
|
|
let proposer_role = RoleDefinition {
|
|
members: vec![/* tous les membres */],
|
|
validation_rules: vec![
|
|
ValidationRule::new(
|
|
0.01, // 1% pour créer une proposition (anti-spam)
|
|
vec!["proposals".to_string()],
|
|
0.5, // 50% des devices du membre
|
|
)?,
|
|
],
|
|
storages: vec![],
|
|
};
|
|
|
|
// Rôle "voter" : Peut voter
|
|
let voter_role = RoleDefinition {
|
|
members: vec![/* tous les membres */],
|
|
validation_rules: vec![
|
|
ValidationRule::new(
|
|
0.0, // Pas de quorum (lecture seule)
|
|
vec!["votes".to_string()],
|
|
0.5, // 50% des devices
|
|
)?,
|
|
],
|
|
storages: vec![],
|
|
};
|
|
|
|
// Rôle "executor" : Peut exécuter les propositions approuvées
|
|
let executor_role = RoleDefinition {
|
|
members: vec![/* signataires multisig */],
|
|
validation_rules: vec![
|
|
ValidationRule::new(
|
|
0.67, // 2/3 des signataires
|
|
vec!["executions".to_string()],
|
|
1.0, // Tous les devices (sécurité max)
|
|
)?,
|
|
],
|
|
storages: vec![],
|
|
};
|
|
```
|
|
|
|
### 2.5 Calcul du quorum et de la majorité
|
|
|
|
```rust
|
|
pub struct VoteResult {
|
|
yes_votes: u64,
|
|
no_votes: u64,
|
|
abstain_votes: u64,
|
|
total_power: u64,
|
|
}
|
|
|
|
impl VoteResult {
|
|
pub fn from_votes(
|
|
votes: &HashMap<OutPoint, Vote>,
|
|
members: &HashMap<OutPoint, MemberInfo>,
|
|
voting_system: &VotingSystem,
|
|
state: &DaoState,
|
|
) -> Self {
|
|
let mut yes_votes = 0;
|
|
let mut no_votes = 0;
|
|
let mut abstain_votes = 0;
|
|
|
|
for (member_id, vote) in votes.iter() {
|
|
if let Some(member) = members.get(member_id) {
|
|
let power = voting_system.calculate_vote_power(member, state);
|
|
match vote {
|
|
Vote::Yes => yes_votes += power,
|
|
Vote::No => no_votes += power,
|
|
Vote::Abstain => abstain_votes += power,
|
|
}
|
|
}
|
|
}
|
|
|
|
let total_power = members.values()
|
|
.map(|m| voting_system.calculate_vote_power(m, state))
|
|
.sum();
|
|
|
|
Self { yes_votes, no_votes, abstain_votes, total_power }
|
|
}
|
|
|
|
pub fn has_quorum(&self, required_quorum: f32) -> bool {
|
|
let participation = (self.yes_votes + self.no_votes + self.abstain_votes) as f32
|
|
/ self.total_power as f32;
|
|
participation >= required_quorum
|
|
}
|
|
|
|
pub fn is_approved(&self, majority: f32) -> bool {
|
|
let yes_ratio = self.yes_votes as f32 / (self.yes_votes + self.no_votes) as f32;
|
|
yes_ratio >= majority
|
|
}
|
|
|
|
pub fn passes(&self, quorum: f32, majority: f32) -> bool {
|
|
self.has_quorum(quorum) && self.is_approved(majority)
|
|
}
|
|
}
|
|
```
|
|
|
|
**Exemples de configuration:**
|
|
|
|
```rust
|
|
// Simple majorité (>50%)
|
|
let config_simple = DaoConfig {
|
|
quorum: 0.2, // 20% de participation minimum
|
|
majority: 0.51, // 51% de oui requis
|
|
..Default::default()
|
|
};
|
|
|
|
// Super-majorité (2/3)
|
|
let config_critical = DaoConfig {
|
|
quorum: 0.4, // 40% de participation
|
|
majority: 0.67, // 67% de oui requis
|
|
..Default::default()
|
|
};
|
|
|
|
// Unanimité (propositions critiques)
|
|
let config_unanimous = DaoConfig {
|
|
quorum: 0.9, // 90% de participation
|
|
majority: 1.0, // 100% de oui requis
|
|
..Default::default()
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Système de vote on-chain
|
|
|
|
### 3.1 Enregistrement d'un vote
|
|
|
|
**Flux cryptographique:**
|
|
|
|
```rust
|
|
pub fn cast_vote(
|
|
dao: &DaoProcess,
|
|
proposal_id: [u8; 32],
|
|
vote: Vote,
|
|
member: &Device,
|
|
) -> Result<ProcessState> {
|
|
// 1. Vérifier l'éligibilité
|
|
let member_info = dao.current_state.members
|
|
.get(&member.get_pairing_commitment().unwrap())
|
|
.ok_or("Not a DAO member")?;
|
|
|
|
// 2. Vérifier la période de vote
|
|
let proposal = dao.get_proposal(&proposal_id)?;
|
|
let current_block = get_current_block_height()?;
|
|
if current_block < proposal.voting_start || current_block > proposal.voting_end {
|
|
return Err(anyhow!("Voting period not active"));
|
|
}
|
|
|
|
// 3. Créer le vote signé
|
|
let vote_commitment = VoteCommitment {
|
|
proposal_id,
|
|
member_id: member_info.member_id,
|
|
vote: vote.clone(),
|
|
timestamp: current_block,
|
|
};
|
|
|
|
// 4. Construire le nouvel état
|
|
let mut new_state = dao.process.get_latest_committed_state().unwrap().clone();
|
|
|
|
// Mise à jour du vote dans public_data
|
|
let mut votes_json = new_state.public_data.get_as_json("votes")?;
|
|
let votes_map = votes_json.as_object_mut().unwrap();
|
|
votes_map.insert(
|
|
hex::encode(member_info.member_id.txid.as_byte_array()),
|
|
serde_json::to_value(&vote)?,
|
|
);
|
|
|
|
new_state.public_data.insert_serializable(
|
|
"votes".to_string(),
|
|
&votes_json,
|
|
)?;
|
|
|
|
// 5. Recalculer les commitments
|
|
new_state.pcd_commitment = PcdCommitments::new(
|
|
&new_state.commited_in,
|
|
&Pcd::new(BTreeMap::new()), // Pas de données privées
|
|
&new_state.roles,
|
|
)?;
|
|
|
|
// 6. Calculer le nouveau state_id
|
|
let merkle_tree = new_state.pcd_commitment.create_merkle_tree()?;
|
|
new_state.state_id = merkle_tree.root().unwrap();
|
|
|
|
// 7. Signer avec la clé du membre
|
|
let spend_key: SecretKey = member.get_sp_client().get_spend_key().try_into()?;
|
|
let message_hash = new_state.get_message_hash(true)?;
|
|
let proof = Proof::new(message_hash, spend_key);
|
|
new_state.validation_tokens.push(proof);
|
|
|
|
Ok(new_state)
|
|
}
|
|
```
|
|
|
|
### 3.2 Vote avec preuve de possession
|
|
|
|
**Objectif:** Prouver qu'un membre détient effectivement ses tokens sans révéler son solde exact.
|
|
|
|
```rust
|
|
pub struct VoteWithProof {
|
|
// Vote
|
|
vote: Vote,
|
|
|
|
// Preuve de possession (Merkle proof)
|
|
token_proof: TokenOwnershipProof,
|
|
|
|
// Signature
|
|
signature: Proof,
|
|
}
|
|
|
|
pub struct TokenOwnershipProof {
|
|
// UTXO prouvé
|
|
outpoint: OutPoint,
|
|
|
|
// Chemin Merkle dans l'arbre des tokens
|
|
merkle_path: Vec<[u8; 32]>,
|
|
|
|
// Index dans l'arbre
|
|
leaf_index: usize,
|
|
|
|
// Montant (optionnel, peut être masqué)
|
|
amount: Option<Amount>,
|
|
}
|
|
|
|
impl TokenOwnershipProof {
|
|
pub fn verify(&self, merkle_root: [u8; 32]) -> Result<()> {
|
|
// 1. Reconstruire la feuille
|
|
let leaf = if let Some(amount) = self.amount {
|
|
// Preuve complète
|
|
hash_leaf(&self.outpoint, amount)
|
|
} else {
|
|
// Preuve masquée (zero-knowledge)
|
|
hash_leaf_masked(&self.outpoint)
|
|
};
|
|
|
|
// 2. Remonter le chemin Merkle
|
|
let mut current_hash = leaf;
|
|
let mut current_index = self.leaf_index;
|
|
|
|
for sibling in &self.merkle_path {
|
|
current_hash = if current_index % 2 == 0 {
|
|
hash_pair(¤t_hash, sibling)
|
|
} else {
|
|
hash_pair(sibling, ¤t_hash)
|
|
};
|
|
current_index /= 2;
|
|
}
|
|
|
|
// 3. Vérifier la racine
|
|
if current_hash == merkle_root {
|
|
Ok(())
|
|
} else {
|
|
Err(anyhow!("Invalid Merkle proof"))
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.3 Vote confidentiel (Optionnel)
|
|
|
|
**Schéma:** Vote chiffré jusqu'à la fin de la période
|
|
|
|
```rust
|
|
pub struct ConfidentialVote {
|
|
// Vote chiffré
|
|
encrypted_vote: Vec<u8>,
|
|
|
|
// Preuve zero-knowledge que le vote est valide
|
|
validity_proof: ValidityProof,
|
|
|
|
// Clé de déchiffrement (révélée après voting_end)
|
|
decryption_key_commitment: [u8; 32],
|
|
}
|
|
|
|
impl ConfidentialVote {
|
|
pub fn new(vote: Vote, public_key: &PublicKey) -> Result<Self> {
|
|
// 1. Chiffrer le vote
|
|
let vote_bytes = bincode::serialize(&vote)?;
|
|
let (ciphertext, nonce, secret) = encrypt_vote(&vote_bytes, public_key)?;
|
|
|
|
// 2. Générer la preuve de validité
|
|
// Prouve que encrypted_vote ∈ {Yes, No, Abstain} sans révéler lequel
|
|
let validity_proof = ValidityProof::generate(&vote, &secret)?;
|
|
|
|
// 3. Commitment de la clé de déchiffrement
|
|
let key_commitment = sha256(&secret);
|
|
|
|
Ok(Self {
|
|
encrypted_vote: ciphertext,
|
|
validity_proof,
|
|
decryption_key_commitment: key_commitment,
|
|
})
|
|
}
|
|
|
|
pub fn reveal(&self, decryption_key: &[u8]) -> Result<Vote> {
|
|
// 1. Vérifier le commitment
|
|
if sha256(decryption_key) != self.decryption_key_commitment {
|
|
return Err(anyhow!("Invalid decryption key"));
|
|
}
|
|
|
|
// 2. Déchiffrer
|
|
let plaintext = decrypt_vote(&self.encrypted_vote, decryption_key)?;
|
|
Ok(bincode::deserialize(&plaintext)?)
|
|
}
|
|
}
|
|
```
|
|
|
|
**Avantages:**
|
|
|
|
- ✅ Pas de biais de vote (pas d'influence du dernier votant)
|
|
- ✅ Protection contre la coercition
|
|
- ✅ Décompte vérifiable après révélation
|
|
|
|
**Inconvénients:**
|
|
|
|
- ⚠️ Complexité cryptographique élevée
|
|
- ⚠️ Nécessite un mécanisme de révélation fiable
|
|
|
|
### 3.4 Commit on-chain du résultat
|
|
|
|
Une fois le vote terminé et le résultat calculé, l'état est committé sur Bitcoin:
|
|
|
|
```rust
|
|
pub fn finalize_proposal(
|
|
dao: &mut DaoProcess,
|
|
proposal_id: [u8; 32],
|
|
) -> Result<Transaction> {
|
|
// 1. Calculer le résultat
|
|
let proposal = dao.get_proposal(&proposal_id)?;
|
|
let result = VoteResult::from_votes(
|
|
&proposal.votes,
|
|
&dao.current_state.members,
|
|
&dao.config.voting_system,
|
|
&dao.current_state,
|
|
);
|
|
|
|
// 2. Déterminer le statut
|
|
let passed = result.passes(dao.config.quorum, 0.51);
|
|
let new_status = if passed {
|
|
ProposalStatus::Passed
|
|
} else {
|
|
ProposalStatus::Rejected
|
|
};
|
|
|
|
// 3. Construire le nouvel état
|
|
let mut new_state = dao.process.get_latest_committed_state().unwrap().clone();
|
|
|
|
// Mise à jour du statut de la proposition
|
|
let mut proposals_json = new_state.public_data.get_as_json("proposals")?;
|
|
let proposals_array = proposals_json.as_array_mut().unwrap();
|
|
|
|
for prop in proposals_array.iter_mut() {
|
|
if prop["id"].as_str() == Some(&hex::encode(proposal_id)) {
|
|
prop["status"] = serde_json::to_value(&new_status)?;
|
|
prop["result"] = serde_json::to_value(&result)?;
|
|
break;
|
|
}
|
|
}
|
|
|
|
new_state.public_data.insert_serializable(
|
|
"proposals".to_string(),
|
|
&proposals_json,
|
|
)?;
|
|
|
|
// 4. Recalculer state_id
|
|
new_state.pcd_commitment = PcdCommitments::new(
|
|
&new_state.commited_in,
|
|
&Pcd::new(BTreeMap::new()),
|
|
&new_state.roles,
|
|
)?;
|
|
let merkle_tree = new_state.pcd_commitment.create_merkle_tree()?;
|
|
new_state.state_id = merkle_tree.root().unwrap();
|
|
|
|
// 5. Collecter les signatures (rôle "executor")
|
|
// ... (processus de validation multi-signatures)
|
|
|
|
// 6. Créer la transaction Bitcoin
|
|
let tx = create_commit_transaction(&dao.process, &new_state)?;
|
|
|
|
Ok(tx)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Gestion de trésorerie
|
|
|
|
### 4.1 Architecture de trésorerie multisig
|
|
|
|
La trésorerie d'une DAO 4NK utilise un **processus distinct** avec validation multisig:
|
|
|
|
```rust
|
|
pub struct TreasuryProcess {
|
|
// Processus 4NK dédié
|
|
process: Process,
|
|
|
|
// Configuration multisig
|
|
multisig_config: MultisigConfig,
|
|
|
|
// UTXO pool
|
|
utxos: HashMap<OutPoint, TreasuryUtxo>,
|
|
|
|
// Dépenses en attente
|
|
pending_spends: Vec<TreasurySpend>,
|
|
}
|
|
|
|
pub struct MultisigConfig {
|
|
// M-of-N signature scheme
|
|
required_signatures: usize, // M
|
|
total_signers: usize, // N
|
|
|
|
// Signataires (process IDs)
|
|
signers: Vec<OutPoint>,
|
|
|
|
// Adresse de réception (Silent Payment)
|
|
receive_address: SilentPaymentAddress,
|
|
}
|
|
|
|
pub struct TreasuryUtxo {
|
|
outpoint: OutPoint,
|
|
amount: Amount,
|
|
script_pubkey: ScriptBuf,
|
|
confirmed: bool,
|
|
reserved: bool, // Réservé pour une dépense
|
|
}
|
|
```
|
|
|
|
### 4.2 Scripts multisig sur Bitcoin
|
|
|
|
**Option 1: Taproot multisig (recommandé)**
|
|
|
|
```rust
|
|
use bitcoin::taproot::{TaprootBuilder, TaprootSpendInfo};
|
|
use bitcoin::secp256k1::XOnlyPublicKey;
|
|
|
|
pub fn create_taproot_multisig(
|
|
signers: &[XOnlyPublicKey],
|
|
threshold: usize,
|
|
) -> Result<TaprootSpendInfo> {
|
|
// 1. Créer l'arbre Taproot
|
|
let mut builder = TaprootBuilder::new();
|
|
|
|
// 2. Générer toutes les combinaisons M-of-N
|
|
let combinations = generate_combinations(signers, threshold);
|
|
|
|
for combo in combinations {
|
|
// Script: OP_CHECKSIGADD ... OP_GREATERTHAN
|
|
let script = create_musig_script(&combo, threshold)?;
|
|
builder = builder.add_leaf(0, script)?;
|
|
}
|
|
|
|
// 3. Finaliser l'arbre
|
|
let spend_info = builder.finalize(
|
|
&Secp256k1::new(),
|
|
signers[0], // Key path (optionnel)
|
|
)?;
|
|
|
|
Ok(spend_info)
|
|
}
|
|
|
|
fn create_musig_script(keys: &[XOnlyPublicKey], threshold: usize) -> Result<ScriptBuf> {
|
|
let mut script = ScriptBuf::new();
|
|
|
|
// Empiler les clés et vérifier les signatures
|
|
for key in keys {
|
|
script.push_slice(key.serialize());
|
|
script.push_opcode(opcodes::all::OP_CHECKSIG);
|
|
script.push_opcode(opcodes::all::OP_CHECKSIGADD);
|
|
}
|
|
|
|
// Vérifier le seuil
|
|
script.push_int(threshold as i64);
|
|
script.push_opcode(opcodes::all::OP_GREATERTHANOREQUAL);
|
|
|
|
Ok(script)
|
|
}
|
|
```
|
|
|
|
**Option 2: MuSig2 (plus efficace)**
|
|
|
|
```rust
|
|
use secp256k1::musig2::{PartialSignature, AggregateSignature};
|
|
|
|
pub struct MusigTreasury {
|
|
// Clé publique agrégée
|
|
aggregate_pubkey: XOnlyPublicKey,
|
|
|
|
// Participants
|
|
participants: Vec<OutPoint>,
|
|
|
|
// Session de signature en cours
|
|
signing_session: Option<MusigSession>,
|
|
}
|
|
|
|
impl MusigTreasury {
|
|
pub fn new(signers: &[XOnlyPublicKey]) -> Result<Self> {
|
|
// Agréger les clés publiques
|
|
let aggregate_pubkey = aggregate_public_keys(signers)?;
|
|
|
|
Ok(Self {
|
|
aggregate_pubkey,
|
|
participants: vec![],
|
|
signing_session: None,
|
|
})
|
|
}
|
|
|
|
pub fn initiate_spend(
|
|
&mut self,
|
|
spend: &TreasurySpend,
|
|
) -> Result<MusigSession> {
|
|
// 1. Créer la transaction à signer
|
|
let tx = self.create_spend_tx(spend)?;
|
|
|
|
// 2. Initialiser la session MuSig2
|
|
let session = MusigSession::new(
|
|
&self.aggregate_pubkey,
|
|
&tx.txid(),
|
|
&self.participants,
|
|
)?;
|
|
|
|
self.signing_session = Some(session.clone());
|
|
Ok(session)
|
|
}
|
|
|
|
pub fn add_partial_signature(
|
|
&mut self,
|
|
signer: OutPoint,
|
|
partial_sig: PartialSignature,
|
|
) -> Result<Option<AggregateSignature>> {
|
|
let session = self.signing_session.as_mut()
|
|
.ok_or(anyhow!("No active signing session"))?;
|
|
|
|
// Ajouter la signature partielle
|
|
session.add_signature(signer, partial_sig)?;
|
|
|
|
// Vérifier si on a assez de signatures
|
|
if session.has_threshold() {
|
|
let aggregate = session.aggregate()?;
|
|
Ok(Some(aggregate))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Avantages MuSig2:**
|
|
|
|
- ✅ **Efficacité** : 1 signature agrégée (64 bytes) vs M signatures
|
|
- ✅ **Confidentialité** : Indistinguable d'une signature simple
|
|
- ✅ **Coût** : Frais de transaction réduits
|
|
|
|
### 4.3 Processus de dépense
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────┐
|
|
│ ÉTAPE 1: PROPOSITION DE DÉPENSE │
|
|
├──────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ Membre crée une proposition: │
|
|
│ ├─ Bénéficiaire: sp1qqxxx... │
|
|
│ ├─ Montant: 1.5 BTC │
|
|
│ ├─ Description: "Paiement développeur Q4" │
|
|
│ └─ UTXOs sources: [utxo1, utxo2] │
|
|
│ │
|
|
│ → Vote DAO (processus gouvernance) │
|
|
└──────────────────────────────────────────────────────────┘
|
|
│
|
|
▼ Approuvée
|
|
┌──────────────────────────────────────────────────────────┐
|
|
│ ÉTAPE 2: CONSTRUCTION TRANSACTION │
|
|
├──────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ Transaction Bitcoin: │
|
|
│ Inputs: [utxo1, utxo2] (trésorerie) │
|
|
│ Outputs: │
|
|
│ - Bénéficiaire: 1.5 BTC │
|
|
│ - Change (trésorerie): 0.49 BTC │
|
|
│ - Frais: 0.01 BTC │
|
|
│ │
|
|
│ Script multisig (Taproot ou MuSig2) │
|
|
└──────────────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌──────────────────────────────────────────────────────────┐
|
|
│ ÉTAPE 3: COLLECTE DES SIGNATURES │
|
|
├──────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ Signataires 1, 2, ..., M signent: │
|
|
│ ├─ Signature avec spend_key │
|
|
│ ├─ Envoi via WebSocket (sdk_relay) │
|
|
│ └─ Accumulation dans processus trésorerie │
|
|
│ │
|
|
│ Quorum atteint: M-of-N signatures │
|
|
└──────────────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌──────────────────────────────────────────────────────────┐
|
|
│ ÉTAPE 4: FINALISATION ET BROADCAST │
|
|
├──────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ├─ Agrégation signatures (MuSig2) ou script complet │
|
|
│ ├─ Broadcast sur réseau Bitcoin │
|
|
│ ├─ Attente confirmation (1-6 blocs) │
|
|
│ └─ Mise à jour état trésorerie (UTXO dépensé) │
|
|
│ │
|
|
│ Enregistrement on-chain (OP_RETURN): │
|
|
│ État trésorerie + référence proposition │
|
|
└──────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 4.4 Implémentation de dépense
|
|
|
|
```rust
|
|
pub async fn execute_treasury_spend(
|
|
treasury: &mut TreasuryProcess,
|
|
spend: &TreasurySpend,
|
|
signers: &[Device],
|
|
) -> Result<Txid> {
|
|
// 1. Vérifier que la proposition est approuvée
|
|
if spend.proposal_status != ProposalStatus::Passed {
|
|
return Err(anyhow!("Proposal not approved"));
|
|
}
|
|
|
|
// 2. Sélectionner les UTXOs
|
|
let selected_utxos = treasury.select_utxos(spend.amount)?;
|
|
let total_input = selected_utxos.iter()
|
|
.map(|u| u.amount)
|
|
.sum::<Amount>();
|
|
|
|
// 3. Calculer le change
|
|
let fee = estimate_fee(&selected_utxos, 2)?; // 2 outputs
|
|
let change = total_input - spend.amount - fee;
|
|
|
|
// 4. Construire la transaction
|
|
let mut tx = Transaction {
|
|
version: 2,
|
|
lock_time: LockTime::ZERO,
|
|
input: vec![],
|
|
output: vec![],
|
|
};
|
|
|
|
// Inputs
|
|
for utxo in &selected_utxos {
|
|
tx.input.push(TxIn {
|
|
previous_output: utxo.outpoint,
|
|
script_sig: ScriptBuf::new(),
|
|
sequence: Sequence::MAX,
|
|
witness: Witness::new(),
|
|
});
|
|
}
|
|
|
|
// Outputs
|
|
// Output 1: Bénéficiaire
|
|
tx.output.push(TxOut {
|
|
value: spend.amount,
|
|
script_pubkey: spend.recipient.to_script_pubkey()?,
|
|
});
|
|
|
|
// Output 2: Change (retour à la trésorerie)
|
|
if change > Amount::ZERO {
|
|
tx.output.push(TxOut {
|
|
value: change,
|
|
script_pubkey: treasury.multisig_config.receive_address.to_script_pubkey()?,
|
|
});
|
|
}
|
|
|
|
// 5. Signer avec multisig
|
|
let mut partial_sigs = vec![];
|
|
for (i, signer) in signers.iter().enumerate() {
|
|
let sighash = compute_sighash(&tx, i)?;
|
|
let spend_key: SecretKey = signer.get_sp_client().get_spend_key().try_into()?;
|
|
let sig = sign_sighash(&sighash, &spend_key)?;
|
|
partial_sigs.push(sig);
|
|
}
|
|
|
|
// 6. Finaliser les témoins (witness)
|
|
if treasury.multisig_config.use_musig2 {
|
|
// MuSig2: Agréger les signatures
|
|
let aggregate_sig = aggregate_signatures(&partial_sigs)?;
|
|
for input in &mut tx.input {
|
|
input.witness.push(aggregate_sig.serialize());
|
|
}
|
|
} else {
|
|
// Script multisig traditionnel
|
|
for (i, input) in tx.input.iter_mut().enumerate() {
|
|
let script_witness = build_multisig_witness(&partial_sigs, i)?;
|
|
input.witness = script_witness;
|
|
}
|
|
}
|
|
|
|
// 7. Broadcast
|
|
let txid = broadcast_transaction(&tx).await?;
|
|
|
|
// 8. Mettre à jour l'état de la trésorerie
|
|
treasury.record_spend(txid, spend)?;
|
|
|
|
Ok(txid)
|
|
}
|
|
```
|
|
|
|
### 4.5 Audit de trésorerie
|
|
|
|
**Transparency on-chain:**
|
|
|
|
```rust
|
|
pub struct TreasuryAudit {
|
|
// Snapshot de la trésorerie
|
|
snapshot_time: u32, // Hauteur de bloc
|
|
|
|
// Balance totale
|
|
total_balance: Amount,
|
|
|
|
// UTXOs détaillés
|
|
utxos: Vec<UtxoAudit>,
|
|
|
|
// Historique des dépenses
|
|
spending_history: Vec<SpendAudit>,
|
|
}
|
|
|
|
pub struct UtxoAudit {
|
|
outpoint: OutPoint,
|
|
amount: Amount,
|
|
received_at: u32, // Hauteur de bloc
|
|
script_type: String, // "P2TR", "P2WSH", etc.
|
|
confirmations: u32,
|
|
}
|
|
|
|
pub struct SpendAudit {
|
|
txid: Txid,
|
|
proposal_id: [u8; 32],
|
|
amount: Amount,
|
|
recipient: String,
|
|
executed_at: u32, // Hauteur de bloc
|
|
signers: Vec<OutPoint>, // Qui a signé
|
|
}
|
|
|
|
impl TreasuryProcess {
|
|
pub fn generate_audit_report(&self, block_height: u32) -> Result<TreasuryAudit> {
|
|
let mut utxos_audit = vec![];
|
|
let mut total = Amount::ZERO;
|
|
|
|
for (outpoint, utxo) in &self.utxos {
|
|
if !utxo.reserved {
|
|
total += utxo.amount;
|
|
utxos_audit.push(UtxoAudit {
|
|
outpoint: *outpoint,
|
|
amount: utxo.amount,
|
|
received_at: 0, // À récupérer depuis blockchain
|
|
script_type: "P2TR".to_string(),
|
|
confirmations: block_height - utxo.received_at,
|
|
});
|
|
}
|
|
}
|
|
|
|
Ok(TreasuryAudit {
|
|
snapshot_time: block_height,
|
|
total_balance: total,
|
|
utxos: utxos_audit,
|
|
spending_history: self.get_spending_history()?,
|
|
})
|
|
}
|
|
|
|
pub fn verify_audit(&self, audit: &TreasuryAudit) -> Result<()> {
|
|
// Vérifier que tous les UTXOs existent on-chain
|
|
for utxo in &audit.utxos {
|
|
verify_utxo_exists(&utxo.outpoint)?;
|
|
}
|
|
|
|
// Vérifier que la somme est correcte
|
|
let calculated_total: Amount = audit.utxos.iter()
|
|
.map(|u| u.amount)
|
|
.sum();
|
|
|
|
if calculated_total != audit.total_balance {
|
|
return Err(anyhow!("Balance mismatch"));
|
|
}
|
|
|
|
// Vérifier l'historique des dépenses
|
|
for spend in &audit.spending_history {
|
|
verify_spend_on_chain(&spend.txid)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Propositions et exécution
|
|
|
|
### 5.1 Types d'actions exécutables
|
|
|
|
```rust
|
|
pub enum ProposalAction {
|
|
// Modification de paramètre
|
|
UpdateConfig {
|
|
parameter: String,
|
|
old_value: Value,
|
|
new_value: Value,
|
|
},
|
|
|
|
// Dépense de trésorerie
|
|
TreasuryTransfer {
|
|
recipient: SilentPaymentAddress,
|
|
amount: Amount,
|
|
description: String,
|
|
},
|
|
|
|
// Ajout/Retrait de membre
|
|
ModifyMembership {
|
|
member_id: OutPoint,
|
|
action: MembershipAction,
|
|
},
|
|
|
|
// Modification des rôles
|
|
UpdateRole {
|
|
role_name: String,
|
|
new_definition: RoleDefinition,
|
|
},
|
|
|
|
// Mise à jour du contrat de la DAO
|
|
UpdateContract {
|
|
contract_hash: [u8; 32],
|
|
contract_url: String,
|
|
},
|
|
|
|
// Appel à un autre processus
|
|
CrossProcessCall {
|
|
target_process: OutPoint,
|
|
function: String,
|
|
params: Value,
|
|
},
|
|
|
|
// Action personnalisée
|
|
Custom {
|
|
action_type: String,
|
|
payload: Vec<u8>,
|
|
},
|
|
}
|
|
|
|
pub enum MembershipAction {
|
|
Add(MemberInfo),
|
|
Remove,
|
|
UpdateVotingPower(u64),
|
|
UpdateDelegation(Option<OutPoint>),
|
|
}
|
|
```
|
|
|
|
### 5.2 Exécution atomique
|
|
|
|
**Garantie:** Toutes les actions d'une proposition s'exécutent atomiquement.
|
|
|
|
```rust
|
|
pub fn execute_proposal(
|
|
dao: &mut DaoProcess,
|
|
treasury: &mut TreasuryProcess,
|
|
proposal: &Proposal,
|
|
) -> Result<Vec<ExecutionResult>> {
|
|
if proposal.status != ProposalStatus::Passed {
|
|
return Err(anyhow!("Proposal not approved"));
|
|
}
|
|
|
|
// Vérifier le timelock
|
|
let current_block = get_current_block_height()?;
|
|
let execution_block = proposal.voting_end + dao.config.execution_delay_blocks;
|
|
if current_block < execution_block {
|
|
return Err(anyhow!("Execution timelock not expired"));
|
|
}
|
|
|
|
// Exécuter toutes les actions
|
|
let mut results = vec![];
|
|
let mut rollback_stack = vec![];
|
|
|
|
for (i, action) in proposal.actions.iter().enumerate() {
|
|
match execute_action(dao, treasury, action) {
|
|
Ok(result) => {
|
|
results.push(result.clone());
|
|
rollback_stack.push((i, result));
|
|
}
|
|
Err(e) => {
|
|
// Rollback de toutes les actions précédentes
|
|
eprintln!("Action {} failed: {}", i, e);
|
|
for (idx, result) in rollback_stack.iter().rev() {
|
|
rollback_action(dao, treasury, *idx, result)?;
|
|
}
|
|
return Err(anyhow!("Proposal execution failed at action {}: {}", i, e));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Marquer la proposition comme exécutée
|
|
dao.mark_proposal_executed(proposal.id, results.clone())?;
|
|
|
|
Ok(results)
|
|
}
|
|
|
|
fn execute_action(
|
|
dao: &mut DaoProcess,
|
|
treasury: &mut TreasuryProcess,
|
|
action: &ProposalAction,
|
|
) -> Result<ExecutionResult> {
|
|
match action {
|
|
ProposalAction::UpdateConfig { parameter, new_value, .. } => {
|
|
dao.update_config(parameter, new_value)?;
|
|
Ok(ExecutionResult::ConfigUpdated)
|
|
}
|
|
|
|
ProposalAction::TreasuryTransfer { recipient, amount, .. } => {
|
|
let spend = TreasurySpend {
|
|
recipient: recipient.clone(),
|
|
amount: *amount,
|
|
proposal_id: dao.current_proposal_id,
|
|
proposal_status: ProposalStatus::Passed,
|
|
};
|
|
let txid = execute_treasury_spend(treasury, &spend, &dao.signers).await?;
|
|
Ok(ExecutionResult::TransferExecuted(txid))
|
|
}
|
|
|
|
ProposalAction::ModifyMembership { member_id, action } => {
|
|
match action {
|
|
MembershipAction::Add(info) => {
|
|
dao.add_member(*member_id, info.clone())?;
|
|
Ok(ExecutionResult::MemberAdded(*member_id))
|
|
}
|
|
MembershipAction::Remove => {
|
|
dao.remove_member(*member_id)?;
|
|
Ok(ExecutionResult::MemberRemoved(*member_id))
|
|
}
|
|
MembershipAction::UpdateVotingPower(power) => {
|
|
dao.update_voting_power(*member_id, *power)?;
|
|
Ok(ExecutionResult::VotingPowerUpdated(*member_id, *power))
|
|
}
|
|
MembershipAction::UpdateDelegation(delegate) => {
|
|
dao.update_delegation(*member_id, *delegate)?;
|
|
Ok(ExecutionResult::DelegationUpdated(*member_id))
|
|
}
|
|
}
|
|
}
|
|
|
|
ProposalAction::UpdateRole { role_name, new_definition } => {
|
|
dao.update_role(role_name, new_definition.clone())?;
|
|
Ok(ExecutionResult::RoleUpdated(role_name.clone()))
|
|
}
|
|
|
|
ProposalAction::CrossProcessCall { target_process, function, params } => {
|
|
let result = call_external_process(*target_process, function, params)?;
|
|
Ok(ExecutionResult::ExternalCallSucceeded(result))
|
|
}
|
|
|
|
ProposalAction::Custom { action_type, payload } => {
|
|
execute_custom_action(dao, action_type, payload)?;
|
|
Ok(ExecutionResult::CustomActionExecuted)
|
|
}
|
|
|
|
_ => Err(anyhow!("Unknown action type")),
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5.3 Timelock et sécurité
|
|
|
|
**Délai d'exécution** : Période entre l'approbation et l'exécution
|
|
|
|
```rust
|
|
pub struct ExecutionTimelock {
|
|
// Délai minimum (en blocs)
|
|
min_delay: u32,
|
|
|
|
// Délai variable selon le type de proposition
|
|
delay_by_type: HashMap<String, u32>,
|
|
|
|
// Délai d'urgence (rôle spécial requis)
|
|
emergency_delay: u32,
|
|
}
|
|
|
|
impl ExecutionTimelock {
|
|
pub fn calculate_delay(&self, proposal: &Proposal) -> u32 {
|
|
// Délai par défaut
|
|
let mut delay = self.min_delay;
|
|
|
|
// Ajuster selon le type
|
|
if let Some(&custom_delay) = self.delay_by_type.get(&proposal.proposal_type.to_string()) {
|
|
delay = delay.max(custom_delay);
|
|
}
|
|
|
|
// Propositions critiques = délai plus long
|
|
if proposal.is_critical() {
|
|
delay *= 2;
|
|
}
|
|
|
|
// Propositions d'urgence (nécessite rôle "emergency")
|
|
if proposal.is_emergency() && self.has_emergency_approval(proposal) {
|
|
delay = self.emergency_delay;
|
|
}
|
|
|
|
delay
|
|
}
|
|
|
|
fn has_emergency_approval(&self, proposal: &Proposal) -> bool {
|
|
// Vérifier que les membres du rôle "emergency" ont approuvé
|
|
proposal.votes.iter()
|
|
.filter(|(member, vote)| {
|
|
self.is_emergency_member(member) && **vote == Vote::Yes
|
|
})
|
|
.count() >= self.emergency_quorum()
|
|
}
|
|
}
|
|
```
|
|
|
|
**Exemple de configuration:**
|
|
|
|
```rust
|
|
let timelock = ExecutionTimelock {
|
|
min_delay: 144, // 1 jour (144 blocs)
|
|
delay_by_type: HashMap::from([
|
|
("TreasuryTransfer".to_string(), 288), // 2 jours
|
|
("UpdateRole".to_string(), 432), // 3 jours
|
|
("UpdateContract".to_string(), 1008), // 1 semaine
|
|
]),
|
|
emergency_delay: 6, // 1 heure
|
|
};
|
|
```
|
|
|
|
**Rationale:**
|
|
|
|
- ✅ **Protection contre les attaques** : Temps de réaction avant exécution
|
|
- ✅ **Transparence** : Tous les membres voient les actions à venir
|
|
- ✅ **Révocabilité** : Possibilité d'annuler via nouvelle proposition
|
|
|
|
### 5.4 Annulation de proposition
|
|
|
|
```rust
|
|
pub fn cancel_proposal(
|
|
dao: &mut DaoProcess,
|
|
proposal_id: [u8; 32],
|
|
reason: String,
|
|
) -> Result<()> {
|
|
let proposal = dao.get_proposal(&proposal_id)?;
|
|
|
|
// Vérifications
|
|
if proposal.status == ProposalStatus::Executed {
|
|
return Err(anyhow!("Cannot cancel executed proposal"));
|
|
}
|
|
|
|
// Qui peut annuler ?
|
|
// 1. Le proposeur (avant le début du vote)
|
|
// 2. Le rôle "guardian" (à tout moment)
|
|
// 3. Une super-majorité (>80% vote pour annuler)
|
|
|
|
let current_block = get_current_block_height()?;
|
|
let caller = get_caller_member_id()?;
|
|
|
|
let can_cancel =
|
|
// Cas 1: Proposeur avant le vote
|
|
(caller == proposal.proposer && current_block < proposal.voting_start)
|
|
|
|
// Cas 2: Guardian
|
|
|| dao.is_guardian(&caller)
|
|
|
|
// Cas 3: Vote de la communauté (nouvelle proposition)
|
|
|| false; // Nécessite une proposition séparée
|
|
|
|
if !can_cancel {
|
|
return Err(anyhow!("Not authorized to cancel"));
|
|
}
|
|
|
|
// Annuler
|
|
dao.set_proposal_status(proposal_id, ProposalStatus::Cancelled)?;
|
|
|
|
// Enregistrer la raison
|
|
dao.add_cancellation_record(proposal_id, reason)?;
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Tokenisation et pondération
|
|
|
|
### 6.1 Système de tokens natif
|
|
|
|
**Design:** Tokens représentés comme UTXOs Bitcoin avec métadonnées
|
|
|
|
```rust
|
|
pub struct DaoToken {
|
|
// UTXO porteur du token
|
|
outpoint: OutPoint,
|
|
|
|
// Montant (en sats)
|
|
amount: Amount,
|
|
|
|
// Métadonnées (commitées on-chain)
|
|
metadata: TokenMetadata,
|
|
}
|
|
|
|
pub struct TokenMetadata {
|
|
// Identifiant du token
|
|
token_id: [u8; 32],
|
|
|
|
// Supply totale
|
|
total_supply: u64,
|
|
|
|
// Propriétaire
|
|
owner: SilentPaymentAddress,
|
|
|
|
// Transferable ?
|
|
transferable: bool,
|
|
|
|
// Vesting (optionnel)
|
|
vesting: Option<VestingSchedule>,
|
|
}
|
|
|
|
pub struct VestingSchedule {
|
|
// Montant initial bloqué
|
|
initial_amount: u64,
|
|
|
|
// Date de début (hauteur de bloc)
|
|
start_block: u32,
|
|
|
|
// Durée (en blocs)
|
|
duration_blocks: u32,
|
|
|
|
// Cliff (période initiale sans unlock)
|
|
cliff_blocks: u32,
|
|
|
|
// Montant déjà débloqué
|
|
unlocked_amount: u64,
|
|
}
|
|
|
|
impl VestingSchedule {
|
|
pub fn available_amount(&self, current_block: u32) -> u64 {
|
|
if current_block < self.start_block + self.cliff_blocks {
|
|
// Cliff period
|
|
return 0;
|
|
}
|
|
|
|
let elapsed = current_block.saturating_sub(self.start_block);
|
|
if elapsed >= self.duration_blocks {
|
|
// Fully vested
|
|
return self.initial_amount;
|
|
}
|
|
|
|
// Linear vesting
|
|
let vested = (self.initial_amount as f64 * elapsed as f64 / self.duration_blocks as f64) as u64;
|
|
vested.saturating_sub(self.unlocked_amount)
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6.2 Distribution initiale
|
|
|
|
**Mécanisme:** Création d'un processus "token_distribution"
|
|
|
|
```rust
|
|
pub fn create_token_distribution(
|
|
dao: &DaoProcess,
|
|
distribution: &TokenDistribution,
|
|
) -> Result<Process> {
|
|
// 1. Créer le processus de distribution
|
|
let mut process = Process::new(OutPoint::null());
|
|
|
|
// 2. Définir la supply totale
|
|
let total_supply = distribution.allocations.values().sum::<u64>();
|
|
|
|
// 3. Créer l'état initial avec les allocations
|
|
let mut allocations_map = BTreeMap::new();
|
|
for (member_id, amount) in &distribution.allocations {
|
|
allocations_map.insert(
|
|
hex::encode(member_id.txid.as_byte_array()),
|
|
json!({
|
|
"amount": amount,
|
|
"vesting": distribution.vesting.clone(),
|
|
}),
|
|
);
|
|
}
|
|
|
|
let public_data = json!({
|
|
"token_id": distribution.token_id,
|
|
"total_supply": total_supply,
|
|
"allocations": allocations_map,
|
|
"metadata": {
|
|
"name": distribution.name,
|
|
"symbol": distribution.symbol,
|
|
"decimals": distribution.decimals,
|
|
}
|
|
});
|
|
|
|
let initial_state = ProcessState::new(
|
|
process.get_process_tip()?,
|
|
Pcd::new(BTreeMap::new()), // Pas de données privées
|
|
public_data.try_into()?,
|
|
distribution.roles.clone(),
|
|
)?;
|
|
|
|
// 4. Signer avec le rôle "founder"
|
|
let founder_signatures = collect_founder_signatures(&initial_state, dao)?;
|
|
initial_state.validation_tokens = founder_signatures;
|
|
|
|
// 5. Insérer l'état
|
|
process.insert_concurrent_state(initial_state)?;
|
|
|
|
Ok(process)
|
|
}
|
|
|
|
pub struct TokenDistribution {
|
|
token_id: [u8; 32],
|
|
name: String,
|
|
symbol: String,
|
|
decimals: u8,
|
|
|
|
// Allocations initiales
|
|
allocations: HashMap<OutPoint, u64>,
|
|
|
|
// Vesting (optionnel)
|
|
vesting: Option<VestingSchedule>,
|
|
|
|
// Rôles de gouvernance du token
|
|
roles: Roles,
|
|
}
|
|
```
|
|
|
|
**Exemple de distribution:**
|
|
|
|
```rust
|
|
let distribution = TokenDistribution {
|
|
token_id: sha256(b"MyDAO_TOKEN"),
|
|
name: "MyDAO Governance Token".to_string(),
|
|
symbol: "MDAO".to_string(),
|
|
decimals: 18,
|
|
|
|
allocations: HashMap::from([
|
|
// Founders (30%)
|
|
(founder1_id, 300_000 * 10u64.pow(18)),
|
|
(founder2_id, 300_000 * 10u64.pow(18)),
|
|
|
|
// Early investors (20%)
|
|
(investor1_id, 200_000 * 10u64.pow(18)),
|
|
|
|
// Community treasury (40%)
|
|
(dao_treasury_id, 400_000 * 10u64.pow(18)),
|
|
|
|
// Team (10%, vested)
|
|
(team_pool_id, 100_000 * 10u64.pow(18)),
|
|
]),
|
|
|
|
vesting: Some(VestingSchedule {
|
|
initial_amount: 100_000 * 10u64.pow(18),
|
|
start_block: current_block,
|
|
duration_blocks: 52560, // 1 an (6 blocs/h * 24h * 365j)
|
|
cliff_blocks: 4380, // 1 mois
|
|
unlocked_amount: 0,
|
|
}),
|
|
|
|
roles: create_token_governance_roles(),
|
|
};
|
|
```
|
|
|
|
### 6.3 Transfert de tokens
|
|
|
|
```rust
|
|
pub fn transfer_tokens(
|
|
from: &Device,
|
|
to: &SilentPaymentAddress,
|
|
amount: u64,
|
|
token_process: &mut Process,
|
|
) -> Result<ProcessState> {
|
|
// 1. Vérifier le solde
|
|
let current_state = token_process.get_latest_committed_state().unwrap();
|
|
let from_id = from.get_pairing_commitment().unwrap();
|
|
|
|
let balances: HashMap<String, u64> = current_state
|
|
.public_data
|
|
.get_as_json("balances")?
|
|
.as_object()
|
|
.unwrap()
|
|
.iter()
|
|
.map(|(k, v)| (k.clone(), v.as_u64().unwrap()))
|
|
.collect();
|
|
|
|
let from_balance = balances.get(&hex::encode(from_id.txid.as_byte_array()))
|
|
.copied()
|
|
.unwrap_or(0);
|
|
|
|
if from_balance < amount {
|
|
return Err(anyhow!("Insufficient balance"));
|
|
}
|
|
|
|
// 2. Créer le nouvel état
|
|
let mut new_balances = balances;
|
|
*new_balances.get_mut(&hex::encode(from_id.txid.as_byte_array())).unwrap() -= amount;
|
|
|
|
let to_key = hex::encode(to.get_scan_public_key().serialize());
|
|
*new_balances.entry(to_key).or_insert(0) += amount;
|
|
|
|
let mut new_state = current_state.clone();
|
|
new_state.public_data.insert_serializable(
|
|
"balances".to_string(),
|
|
&json!(new_balances),
|
|
)?;
|
|
|
|
// 3. Recalculer state_id
|
|
new_state.pcd_commitment = PcdCommitments::new(
|
|
&new_state.commited_in,
|
|
&Pcd::new(BTreeMap::new()),
|
|
&new_state.roles,
|
|
)?;
|
|
let merkle_tree = new_state.pcd_commitment.create_merkle_tree()?;
|
|
new_state.state_id = merkle_tree.root().unwrap();
|
|
|
|
// 4. Signer
|
|
let spend_key: SecretKey = from.get_sp_client().get_spend_key().try_into()?;
|
|
let message_hash = new_state.get_message_hash(true)?;
|
|
let proof = Proof::new(message_hash, spend_key);
|
|
new_state.validation_tokens.push(proof);
|
|
|
|
Ok(new_state)
|
|
}
|
|
```
|
|
|
|
### 6.4 Mécanismes anti-Sybil
|
|
|
|
**Problème:** Vote quadratique vulnérable aux attaques Sybil (création de multiples identités)
|
|
|
|
**Solutions:**
|
|
|
|
#### A. Preuve d'humanité
|
|
|
|
```rust
|
|
pub struct HumanityProof {
|
|
// Preuve cryptographique d'humanité
|
|
proof_type: HumanityProofType,
|
|
|
|
// Données de la preuve
|
|
proof_data: Vec<u8>,
|
|
|
|
// Validité
|
|
valid_until: u32, // Hauteur de bloc
|
|
}
|
|
|
|
pub enum HumanityProofType {
|
|
// Preuve biométrique (empreinte faciale)
|
|
Biometric(BiometricProof),
|
|
|
|
// Preuve sociale (attestations)
|
|
SocialVouching(Vec<Attestation>),
|
|
|
|
// Preuve financière (dépôt important)
|
|
StakeProof(Amount),
|
|
|
|
// Intégration Worldcoin/autres
|
|
ExternalOracle(String),
|
|
}
|
|
|
|
pub struct Attestation {
|
|
// Attestateur
|
|
attester: OutPoint,
|
|
|
|
// Sujet attesté
|
|
subject: OutPoint,
|
|
|
|
// Signature
|
|
signature: Proof,
|
|
|
|
// Validité
|
|
expires_at: u32,
|
|
}
|
|
```
|
|
|
|
#### B. Coût progressif de création de compte
|
|
|
|
```rust
|
|
pub struct MembershipFee {
|
|
// Frais de base
|
|
base_fee: Amount,
|
|
|
|
// Multiplicateur par compte existant
|
|
multiplier: f32,
|
|
|
|
// Maximum
|
|
max_fee: Amount,
|
|
}
|
|
|
|
impl MembershipFee {
|
|
pub fn calculate_fee(&self, existing_accounts: usize) -> Amount {
|
|
let fee = self.base_fee.to_sat() as f32 * self.multiplier.powi(existing_accounts as i32);
|
|
Amount::from_sat((fee as u64).min(self.max_fee.to_sat()))
|
|
}
|
|
}
|
|
|
|
// Exemple: Frais exponentiels
|
|
let fee_structure = MembershipFee {
|
|
base_fee: Amount::from_sat(10_000), // 10k sats
|
|
multiplier: 1.5, // +50% par compte
|
|
max_fee: Amount::from_sat(1_000_000), // 1M sats max
|
|
};
|
|
|
|
// Coûts:
|
|
// Compte 1: 10,000 sats
|
|
// Compte 2: 15,000 sats
|
|
// Compte 3: 22,500 sats
|
|
// Compte 10: 576,650 sats
|
|
```
|
|
|
|
#### C. Délégation restreinte
|
|
|
|
```rust
|
|
pub struct DelegationPolicy {
|
|
// Limite de délégations reçues par membre
|
|
max_delegations_received: usize,
|
|
|
|
// Pénalité de vote pour sur-délégation
|
|
over_delegation_penalty: f32,
|
|
}
|
|
|
|
impl DelegationPolicy {
|
|
pub fn effective_voting_power(
|
|
&self,
|
|
member: &MemberInfo,
|
|
delegations_received: usize,
|
|
) -> u64 {
|
|
let base_power = member.voting_power;
|
|
|
|
if delegations_received <= self.max_delegations_received {
|
|
base_power
|
|
} else {
|
|
// Pénalité sur les votes délégués en excès
|
|
let excess = delegations_received - self.max_delegations_received;
|
|
let penalty = 1.0 - (excess as f32 * self.over_delegation_penalty);
|
|
(base_power as f32 * penalty.max(0.0)) as u64
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Sécurité et attaques
|
|
|
|
### 7.1 Vecteurs d'attaque
|
|
|
|
| Attaque | Description | Mitigation |
|
|
|---------|-------------|------------|
|
|
| **51% Attack** | Contrôle majoritaire des tokens | Vote quadratique, quorum élevé, timelock |
|
|
| **Flash Loan Attack** | Emprunt temporaire de tokens | Snapshot voting (bloc fixe) |
|
|
| **Sybil Attack** | Création de multiples identités | Preuve d'humanité, frais progressifs |
|
|
| **Bribe Attack** | Corruption de votants | Vote confidentiel, slashing |
|
|
| **Censorship** | Relay bloque les messages | Multi-relays, P2P gossip |
|
|
| **Front-running** | Observation et devancement de votes | Vote chiffré avec révélation |
|
|
| **Governance Capture** | Centralisation progressive du pouvoir | Limites de délégation, quorum dynamique |
|
|
|
|
### 7.2 Flash loan attack mitigation
|
|
|
|
**Problème:** Emprunter massivement des tokens juste pour voter
|
|
|
|
```rust
|
|
pub struct SnapshotVoting {
|
|
// Bloc de snapshot (avant le début du vote)
|
|
snapshot_block: u32,
|
|
|
|
// Balances figées à ce bloc
|
|
snapshot_balances: HashMap<OutPoint, u64>,
|
|
}
|
|
|
|
impl SnapshotVoting {
|
|
pub fn create_snapshot(
|
|
proposal: &Proposal,
|
|
lookback_blocks: u32,
|
|
) -> Result<Self> {
|
|
// Snapshot N blocs avant le début du vote
|
|
let snapshot_block = proposal.voting_start.saturating_sub(lookback_blocks);
|
|
|
|
// Récupérer les balances à ce bloc
|
|
let snapshot_balances = get_balances_at_block(snapshot_block)?;
|
|
|
|
Ok(Self {
|
|
snapshot_block,
|
|
snapshot_balances,
|
|
})
|
|
}
|
|
|
|
pub fn get_voting_power(&self, member: &OutPoint) -> u64 {
|
|
self.snapshot_balances.get(member).copied().unwrap_or(0)
|
|
}
|
|
}
|
|
|
|
// Utilisation
|
|
let snapshot = SnapshotVoting::create_snapshot(proposal, 144)?; // 1 jour avant
|
|
let voting_power = snapshot.get_voting_power(&member_id);
|
|
```
|
|
|
|
**Avantages:**
|
|
|
|
- ✅ Impossible d'emprunter des tokens juste pour voter
|
|
- ✅ Prévisibilité (pouvoir de vote connu à l'avance)
|
|
- ✅ Résistance aux manipulations de marché
|
|
|
|
### 7.3 Slashing pour mauvais comportement
|
|
|
|
```rust
|
|
pub struct SlashingPolicy {
|
|
// Types de fautes
|
|
offenses: HashMap<OffenseType, SlashingRule>,
|
|
}
|
|
|
|
pub enum OffenseType {
|
|
// Vote malveillant (deux fois différemment)
|
|
DoubleVoting,
|
|
|
|
// Proposition spam
|
|
SpamProposal,
|
|
|
|
// Exécution malveillante
|
|
MaliciousExecution,
|
|
|
|
// Violation de règles
|
|
RuleViolation,
|
|
}
|
|
|
|
pub struct SlashingRule {
|
|
// Pénalité (% des tokens)
|
|
slash_percentage: f32,
|
|
|
|
// Montant minimum slashé
|
|
min_slash: Amount,
|
|
|
|
// Destination des tokens slashés
|
|
destination: SlashDestination,
|
|
}
|
|
|
|
pub enum SlashDestination {
|
|
Burn, // Détruire
|
|
Treasury, // Trésorerie DAO
|
|
Reporter(OutPoint), // Celui qui a signalé
|
|
Redistribute, // Redistribuer aux autres membres
|
|
}
|
|
|
|
impl SlashingPolicy {
|
|
pub fn slash_member(
|
|
&self,
|
|
dao: &mut DaoProcess,
|
|
member_id: &OutPoint,
|
|
offense: OffenseType,
|
|
evidence: &Evidence,
|
|
) -> Result<Amount> {
|
|
// 1. Vérifier l'évidence
|
|
if !self.verify_evidence(offense, evidence)? {
|
|
return Err(anyhow!("Invalid evidence"));
|
|
}
|
|
|
|
// 2. Calculer le montant à slasher
|
|
let rule = self.offenses.get(&offense)
|
|
.ok_or(anyhow!("Unknown offense"))?;
|
|
|
|
let member_balance = dao.get_member_balance(member_id)?;
|
|
let slash_amount = (member_balance.to_sat() as f32 * rule.slash_percentage) as u64;
|
|
let slash_amount = slash_amount.max(rule.min_slash.to_sat());
|
|
let slash_amount = Amount::from_sat(slash_amount);
|
|
|
|
// 3. Exécuter le slashing
|
|
dao.reduce_member_balance(member_id, slash_amount)?;
|
|
|
|
// 4. Distribuer selon la règle
|
|
match &rule.destination {
|
|
SlashDestination::Burn => {
|
|
dao.burn_tokens(slash_amount)?;
|
|
}
|
|
SlashDestination::Treasury => {
|
|
dao.transfer_to_treasury(slash_amount)?;
|
|
}
|
|
SlashDestination::Reporter(reporter) => {
|
|
dao.transfer_to_member(reporter, slash_amount)?;
|
|
}
|
|
SlashDestination::Redistribute => {
|
|
dao.redistribute_to_all(slash_amount)?;
|
|
}
|
|
}
|
|
|
|
// 5. Enregistrer l'événement
|
|
dao.record_slashing_event(member_id, offense, slash_amount)?;
|
|
|
|
Ok(slash_amount)
|
|
}
|
|
}
|
|
```
|
|
|
|
### 7.4 Quorum dynamique
|
|
|
|
**Objectif:** Adapter le quorum selon l'activité de la DAO
|
|
|
|
```rust
|
|
pub struct DynamicQuorum {
|
|
// Quorum de base
|
|
base_quorum: f32,
|
|
|
|
// Historique de participation
|
|
participation_history: Vec<f32>,
|
|
|
|
// Fenêtre d'historique
|
|
history_window: usize,
|
|
}
|
|
|
|
impl DynamicQuorum {
|
|
pub fn calculate_quorum(&self) -> f32 {
|
|
if self.participation_history.is_empty() {
|
|
return self.base_quorum;
|
|
}
|
|
|
|
// Moyenne de participation récente
|
|
let avg_participation: f32 = self.participation_history.iter().sum::<f32>()
|
|
/ self.participation_history.len() as f32;
|
|
|
|
// Quorum = 50% de la participation moyenne (avec minimum)
|
|
let dynamic_quorum = avg_participation * 0.5;
|
|
dynamic_quorum.max(self.base_quorum)
|
|
}
|
|
|
|
pub fn update_with_vote(&mut self, participation: f32) {
|
|
self.participation_history.push(participation);
|
|
|
|
// Garder seulement les N derniers votes
|
|
if self.participation_history.len() > self.history_window {
|
|
self.participation_history.remove(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Exemple
|
|
let mut quorum = DynamicQuorum {
|
|
base_quorum: 0.1, // 10% minimum
|
|
participation_history: vec![],
|
|
history_window: 10, // 10 derniers votes
|
|
};
|
|
|
|
// Après plusieurs votes:
|
|
// Participations: [0.3, 0.4, 0.35, 0.5, 0.45, 0.4, 0.38, 0.42, 0.36, 0.41]
|
|
// Moyenne: 0.397
|
|
// Quorum dynamique: 0.397 * 0.5 = 0.1985 ≈ 20%
|
|
```
|
|
|
|
**Avantages:**
|
|
|
|
- ✅ S'adapte à l'activité réelle
|
|
- ✅ Évite les blocages (quorum trop élevé)
|
|
- ✅ Maintient la légitimité (quorum basé sur participation)
|
|
|
|
### 7.5 Rate limiting
|
|
|
|
```rust
|
|
pub struct RateLimiter {
|
|
// Limite de propositions par membre
|
|
max_proposals_per_period: usize,
|
|
|
|
// Période (en blocs)
|
|
period_blocks: u32,
|
|
|
|
// Historique
|
|
proposal_history: HashMap<OutPoint, Vec<u32>>,
|
|
}
|
|
|
|
impl RateLimiter {
|
|
pub fn can_propose(&mut self, member_id: &OutPoint, current_block: u32) -> bool {
|
|
let history = self.proposal_history
|
|
.entry(*member_id)
|
|
.or_insert_with(Vec::new);
|
|
|
|
// Nettoyer l'historique (garder seulement la période récente)
|
|
history.retain(|&block| block > current_block.saturating_sub(self.period_blocks));
|
|
|
|
// Vérifier la limite
|
|
if history.len() >= self.max_proposals_per_period {
|
|
return false;
|
|
}
|
|
|
|
// Ajouter cette proposition
|
|
history.push(current_block);
|
|
true
|
|
}
|
|
}
|
|
|
|
// Exemple: Max 3 propositions par semaine
|
|
let limiter = RateLimiter {
|
|
max_proposals_per_period: 3,
|
|
period_blocks: 1008, // ~1 semaine
|
|
proposal_history: HashMap::new(),
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Implémentation de référence
|
|
|
|
### 8.1 Structure complète d'une DAO
|
|
|
|
```rust
|
|
pub struct CompleteDAO {
|
|
// Processus principal (gouvernance)
|
|
governance: DaoProcess,
|
|
|
|
// Processus de trésorerie
|
|
treasury: TreasuryProcess,
|
|
|
|
// Processus de distribution de tokens
|
|
token_distribution: Process,
|
|
|
|
// Processus d'adhésion
|
|
membership: Process,
|
|
|
|
// Configuration
|
|
config: DaoConfiguration,
|
|
|
|
// Politiques de sécurité
|
|
security: SecurityPolicies,
|
|
}
|
|
|
|
pub struct DaoConfiguration {
|
|
// Identité
|
|
name: String,
|
|
description: String,
|
|
website: String,
|
|
|
|
// Gouvernance
|
|
voting_system: VotingSystem,
|
|
quorum: DynamicQuorum,
|
|
majority: f32,
|
|
voting_period_blocks: u32,
|
|
execution_delay_blocks: u32,
|
|
|
|
// Tokens
|
|
token_name: String,
|
|
token_symbol: String,
|
|
total_supply: u64,
|
|
|
|
// Membres
|
|
min_stake: Amount,
|
|
membership_fee: MembershipFee,
|
|
|
|
// Trésorerie
|
|
treasury_multisig: MultisigConfig,
|
|
|
|
// Sécurité
|
|
slashing: SlashingPolicy,
|
|
rate_limiting: RateLimiter,
|
|
}
|
|
|
|
pub struct SecurityPolicies {
|
|
// Anti-Sybil
|
|
humanity_proof_required: bool,
|
|
|
|
// Flash loan protection
|
|
snapshot_voting: bool,
|
|
snapshot_lookback_blocks: u32,
|
|
|
|
// Slashing
|
|
slashing_enabled: bool,
|
|
|
|
// Timelock
|
|
timelocks: ExecutionTimelock,
|
|
|
|
// Rate limiting
|
|
rate_limiting_enabled: bool,
|
|
}
|
|
```
|
|
|
|
### 8.2 Initialisation d'une DAO
|
|
|
|
```rust
|
|
pub async fn initialize_dao(
|
|
founders: Vec<Device>,
|
|
config: DaoConfiguration,
|
|
) -> Result<CompleteDAO> {
|
|
// 1. Créer le processus de gouvernance
|
|
let governance_process = create_governance_process(&founders, &config)?;
|
|
|
|
// 2. Créer le processus de trésorerie
|
|
let treasury_process = create_treasury_process(&founders, &config.treasury_multisig)?;
|
|
|
|
// 3. Créer la distribution de tokens
|
|
let token_process = create_token_distribution(&config)?;
|
|
|
|
// 4. Créer le processus d'adhésion
|
|
let membership_process = create_membership_process(&founders, &config)?;
|
|
|
|
// 5. Lier les processus
|
|
link_processes(&governance_process, &treasury_process)?;
|
|
link_processes(&governance_process, &token_process)?;
|
|
link_processes(&governance_process, &membership_process)?;
|
|
|
|
// 6. Commit initial on-chain
|
|
let governance_tx = commit_initial_state(&governance_process).await?;
|
|
let treasury_tx = commit_initial_state(&treasury_process).await?;
|
|
let token_tx = commit_initial_state(&token_process).await?;
|
|
let membership_tx = commit_initial_state(&membership_process).await?;
|
|
|
|
// 7. Attendre les confirmations
|
|
wait_for_confirmations(&[governance_tx, treasury_tx, token_tx, membership_tx], 6).await?;
|
|
|
|
// 8. Construire la DAO complète
|
|
let dao = CompleteDAO {
|
|
governance: DaoProcess {
|
|
process: governance_process,
|
|
config: config.clone(),
|
|
current_state: DaoState::default(),
|
|
},
|
|
treasury: treasury_process,
|
|
token_distribution: token_process,
|
|
membership: membership_process,
|
|
config,
|
|
security: SecurityPolicies::default(),
|
|
};
|
|
|
|
Ok(dao)
|
|
}
|
|
|
|
fn create_governance_process(
|
|
founders: &[Device],
|
|
config: &DaoConfiguration,
|
|
) -> Result<Process> {
|
|
let mut process = Process::new(OutPoint::null());
|
|
|
|
// Rôles initiaux
|
|
let founder_ids: Vec<OutPoint> = founders.iter()
|
|
.map(|f| f.get_pairing_commitment().unwrap())
|
|
.collect();
|
|
|
|
let roles = Roles::new(BTreeMap::from([
|
|
// Founders: Contrôle total initial
|
|
("founder".to_string(), RoleDefinition {
|
|
members: founder_ids.clone(),
|
|
validation_rules: vec![
|
|
ValidationRule::new(0.67, vec!["proposals", "config", "roles"], 1.0)?,
|
|
],
|
|
storages: vec![],
|
|
}),
|
|
|
|
// Members: Peuvent voter et proposer
|
|
("member".to_string(), RoleDefinition {
|
|
members: vec![], // Sera rempli au fur et à mesure
|
|
validation_rules: vec![
|
|
ValidationRule::new(0.01, vec!["proposals"], 0.5)?,
|
|
ValidationRule::new(0.0, vec!["votes"], 0.5)?,
|
|
],
|
|
storages: vec![],
|
|
}),
|
|
|
|
// Guardians: Peuvent annuler des propositions
|
|
("guardian".to_string(), RoleDefinition {
|
|
members: founder_ids.clone(),
|
|
validation_rules: vec![
|
|
ValidationRule::new(0.51, vec!["cancellations"], 1.0)?,
|
|
],
|
|
storages: vec![],
|
|
}),
|
|
|
|
// Emergency: Exécution rapide
|
|
("emergency".to_string(), RoleDefinition {
|
|
members: founder_ids.clone(),
|
|
validation_rules: vec![
|
|
ValidationRule::new(0.9, vec!["emergency_actions"], 1.0)?,
|
|
],
|
|
storages: vec![],
|
|
}),
|
|
]));
|
|
|
|
// État initial
|
|
let initial_data = json!({
|
|
"name": config.name,
|
|
"description": config.description,
|
|
"proposals": [],
|
|
"members": founder_ids.iter().map(|id| {
|
|
json!({
|
|
"id": hex::encode(id.txid.as_byte_array()),
|
|
"voting_power": 1,
|
|
"joined_at": 0,
|
|
})
|
|
}).collect::<Vec<_>>(),
|
|
"config": config,
|
|
});
|
|
|
|
let initial_state = ProcessState::new(
|
|
process.get_process_tip()?,
|
|
Pcd::new(BTreeMap::new()),
|
|
initial_data.try_into()?,
|
|
roles,
|
|
)?;
|
|
|
|
// Signatures des founders
|
|
let mut validation_tokens = vec![];
|
|
for founder in founders {
|
|
let spend_key: SecretKey = founder.get_sp_client().get_spend_key().try_into()?;
|
|
let message_hash = initial_state.get_message_hash(true)?;
|
|
validation_tokens.push(Proof::new(message_hash, spend_key));
|
|
}
|
|
initial_state.validation_tokens = validation_tokens;
|
|
|
|
process.insert_concurrent_state(initial_state)?;
|
|
|
|
Ok(process)
|
|
}
|
|
```
|
|
|
|
### 8.3 Cycle de vie complet
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ PHASE 1: INITIALISATION │
|
|
│ ├─ Création des processus (gouvernance, trésorerie) │
|
|
│ ├─ Distribution initiale de tokens │
|
|
│ ├─ Définition des rôles et permissions │
|
|
│ └─ Commit initial on-chain │
|
|
└─────────────────┬───────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ PHASE 2: CROISSANCE │
|
|
│ ├─ Admission de nouveaux membres │
|
|
│ ├─ Propositions et votes actifs │
|
|
│ ├─ Gestion de trésorerie │
|
|
│ └─ Évolution de la gouvernance │
|
|
└─────────────────┬───────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ PHASE 3: MATURITÉ │
|
|
│ ├─ Décentralisation progressive │
|
|
│ ├─ Réduction du pouvoir des founders │
|
|
│ ├─ Délégation de responsabilités │
|
|
│ └─ Optimisation des processus │
|
|
└─────────────────┬───────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ PHASE 4: AUTONOMIE │
|
|
│ ├─ Gouvernance entièrement décentralisée │
|
|
│ ├─ Exécution automatique de propositions │
|
|
│ ├─ Intégration avec d'autres protocoles │
|
|
│ └─ Évolution continue │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Conclusion
|
|
|
|
### Points forts d'une DAO sur 4NK
|
|
|
|
✅ **Identité cryptographique** : Silent Payments Bitcoin (souveraineté)
|
|
✅ **Gouvernance on-chain** : Immuabilité et traçabilité complète
|
|
✅ **Flexibilité des rôles** : ValidationRules adaptables
|
|
✅ **Trésorerie native** : Gestion Bitcoin multisig
|
|
✅ **Sécurité cryptographique** : Signatures Schnorr, Merkle commitments
|
|
✅ **Multi-device** : Pairing pour résilience
|
|
✅ **Auditabilité** : Tous les états sur blockchain
|
|
✅ **Pas de smart contracts** : Pas de vulnérabilités Solidity
|
|
|
|
### Défis techniques
|
|
|
|
⚠️ **Complexité** : Architecture multi-processus
|
|
⚠️ **Coûts** : Frais Bitcoin par état
|
|
⚠️ **Latence** : Confirmations blockchain (10 min)
|
|
⚠️ **Scalabilité** : Limité par débit Bitcoin
|
|
⚠️ **UX** : Courbe d'apprentissage
|
|
|
|
### Recommandations
|
|
|
|
1. **Démarrage** : Utiliser un petit groupe de founders de confiance
|
|
2. **Décentralisation progressive** : Réduire le pouvoir des founders sur 6-12 mois
|
|
3. **Tests extensifs** : Déployer d'abord sur testnet/signet
|
|
4. **Auditabilité** : Outils de monitoring et dashboards
|
|
5. **Sécurité** : Audits réguliers, bug bounties
|
|
|
|
### Prochaines étapes
|
|
|
|
- 🔄 **Lightning Network** : Votes off-chain avec règlement on-chain
|
|
- 🔄 **ZK-SNARKs** : Vote confidentiel vérifiable
|
|
- 🔄 **Cross-chain bridges** : Interopérabilité avec autres blockchains
|
|
- 🔄 **Optimisations** : Batch voting, état compressé
|
|
|
|
---
|
|
|
|
**Document complet:** 165 pages
|
|
**Code de référence:** https://git.4nkweb.com/4nk/dao-reference
|
|
**Contact:** dao@4nkweb.com
|