Advanced Cryptography: Multi-Sig, Threshold Signatures, Ring Signatures

Implementing advanced cryptographic schemes: multi-signature wallets, threshold signatures (Schnorr, BLS), and ring signatures.

Advanced⏱️ 60 min📚 Prerequisites: 2

Advanced Cryptography: Multi-Sig, Threshold Signatures, Ring Signatures

Advanced cryptographic schemes for enhanced security and privacy.

Multi-Signature Wallets

Multi-sig requires multiple signatures to authorize a transaction.

Implementation

RUST
struct MultiSigWallet {
    owners: Vec<String>,
    threshold: usize, // Minimum signatures required
    nonce: u64,
}

struct Signature {
    signer: String,
    signature: Vec<u8>,
}

struct Transaction {
    to: String,
    amount: u64,
    nonce: u64,
    signatures: Vec<Signature>,
}

impl MultiSigWallet {
    fn new(owners: Vec<String>, threshold: usize) -> Self {
        if threshold > owners.len() || threshold == 0 {
            panic!("Invalid threshold");
        }
        
        MultiSigWallet {
            owners,
            threshold,
            nonce: 0,
        }
    }
    
    fn create_transaction(
        &mut self,
        to: String,
        amount: u64,
    ) -> Transaction {
        let tx = Transaction {
            to,
            amount,
            nonce: self.nonce,
            signatures: Vec::new(),
        };
        
        self.nonce += 1;
        tx
    }
    
    fn add_signature(
        &mut self,
        tx: &mut Transaction,
        signer: &str,
        signature: Vec<u8>,
    ) -> Result<(), String> {
        // Verify signer is owner
        if !self.owners.contains(&signer.to_string()) {
            return Err(String::from("Not an owner"));
        }
        
        // Verify not already signed
        if tx.signatures.iter().any(|s| s.signer == signer) {
            return Err(String::from("Already signed"));
        }
        
        // Verify signature (simplified)
        if !self.verify_signature(tx, signer, &signature) {
            return Err(String::from("Invalid signature"));
        }
        
        tx.signatures.push(Signature {
            signer: signer.to_string(),
            signature,
        });
        
        Ok(())
    }
    
    fn execute_transaction(&self, tx: &Transaction) -> Result<(), String> {
        // Check threshold
        if tx.signatures.len() < self.threshold {
            return Err(format!("Need {} signatures, got {}", 
                             self.threshold, tx.signatures.len()));
        }
        
        // Verify all signatures are from owners
        for sig in &tx.signatures {
            if !self.owners.contains(&sig.signer) {
                return Err(String::from("Invalid signer"));
            }
        }
        
        // Execute transaction
        Ok(())
    }
    
    fn verify_signature(
        &self,
        tx: &Transaction,
        signer: &str,
        signature: &[u8],
    ) -> bool {
        // In real implementation: verify cryptographic signature
        // Simplified: just check signature is not empty
        !signature.is_empty()
    }
}

Threshold Signatures

Threshold signatures allow a group to sign with only a subset (threshold) of members.

Schnorr Signatures

RUST
// Simplified Schnorr threshold signature concept
struct SchnorrKeyShare {
    public_key: Vec<u8>,
    secret_share: Vec<u8>, // Secret share (never revealed)
    index: usize,
}

struct SchnorrThresholdSig {
    threshold: usize,
    total: usize,
    public_key: Vec<u8>, // Aggregate public key
    key_shares: Vec<SchnorrKeyShare>,
}

impl SchnorrThresholdSig {
    fn generate_shares(threshold: usize, total: usize) -> Vec<SchnorrKeyShare> {
        // Generate secret shares using Shamir's Secret Sharing
        // Each participant gets one share
        // Need 'threshold' shares to reconstruct
        
        vec![] // Simplified
    }
    
    fn sign_with_shares(
        &self,
        message: &[u8],
        shares: &[SchnorrKeyShare],
    ) -> Result<Vec<u8>, String> {
        if shares.len() < self.threshold {
            return Err(String::from("Insufficient shares"));
        }
        
        // Combine signatures from shares
        // Create aggregate signature
        
        Ok(vec![]) // Simplified
    }
    
    fn verify(
        &self,
        message: &[u8],
        signature: &[u8],
    ) -> bool {
        // Verify aggregate signature against aggregate public key
        true // Simplified
    }
}

BLS Signatures

RUST
// BLS (Boneh-Lynn-Shacham) signatures support aggregation
struct BLSSignature {
    signature: Vec<u8>,
    public_key: Vec<u8>,
}

struct BLSAggregator {
    signatures: Vec<BLSSignature>,
}

impl BLSAggregator {
    fn aggregate(&self) -> Vec<u8> {
        // BLS signatures can be aggregated
        // Aggregate signature is same size as single signature
        // Very efficient for multi-sig
        
        vec![] // Simplified
    }
    
    fn verify_aggregate(
        &self,
        message: &[u8],
        aggregate_sig: &[u8],
        public_keys: &[Vec<u8>],
    ) -> bool {
        // Verify aggregate signature against all public keys
        true // Simplified
    }
}

Ring Signatures

Ring signatures provide anonymity by signing with a group without revealing which member signed.

RUST
struct RingMember {
    public_key: Vec<u8>,
    index: usize,
}

struct RingSignature {
    ring: Vec<RingMember>,
    signature: Vec<u8>,
    message: Vec<u8>,
}

impl RingSignature {
    fn sign(
        message: &[u8],
        signer_private_key: &[u8],
        ring: Vec<RingMember>,
    ) -> RingSignature {
        // Create signature that proves:
        // 1. Someone in the ring signed
        // 2. But doesn't reveal who
        
        RingSignature {
            ring,
            signature: vec![], // Simplified
            message: message.to_vec(),
        }
    }
    
    fn verify(&self) -> bool {
        // Verify signature is valid
        // But cannot determine which ring member signed
        true // Simplified
    }
}

Homomorphic Encryption

Homomorphic encryption allows computation on encrypted data without decrypting.

Concept

RUST
// Simplified homomorphic encryption concept
struct EncryptedValue {
    ciphertext: Vec<u8>,
    public_key: Vec<u8>,
}

struct HomomorphicEncryption {
    // Supports operations on encrypted data
}

impl HomomorphicEncryption {
    fn encrypt(&self, value: u64, public_key: &[u8]) -> EncryptedValue {
        // Encrypt value
        EncryptedValue {
            ciphertext: vec![], // Simplified
            public_key: public_key.to_vec(),
        }
    }
    
    fn add_encrypted(
        &self,
        a: &EncryptedValue,
        b: &EncryptedValue,
    ) -> EncryptedValue {
        // Add encrypted values without decrypting
        // Result is encryption of (a + b)
        EncryptedValue {
            ciphertext: vec![], // Simplified
            public_key: a.public_key.clone(),
        }
    }
    
    fn decrypt(&self, encrypted: &EncryptedValue, private_key: &[u8]) -> u64 {
        // Decrypt to get result
        0 // Simplified
    }
}

Use Cases

Multi-Sig

  • Treasury Management: Require multiple approvals
  • DAO Governance: Multi-sig for proposals
  • Exchange Security: Cold wallet multi-sig

Threshold Signatures

  • Distributed Validators: Validator key split across nodes
  • Custody Solutions: Shared custody without single point of failure
  • Consensus: BFT consensus with threshold signatures

Ring Signatures

  • Privacy Coins: Monero uses ring signatures
  • Anonymous Voting: Vote without revealing identity
  • Whistleblowing: Prove membership without revealing identity

Homomorphic Encryption

  • Private Computation: Compute on encrypted data
  • Zero-Knowledge Proofs: Used in some ZKP schemes
  • Secure Multi-Party Computation: Privacy-preserving computation

Real-World Examples

  • Gnosis Safe: Multi-sig wallet
  • Monero: Ring signatures for privacy
  • Ethereum 2.0: BLS signatures for validators
  • Zcash: Uses homomorphic encryption concepts

Security Considerations

  • Key Management: Secure storage of private keys
  • Threshold Selection: Balance security vs. usability
  • Signature Verification: Always verify before execution
  • Key Rotation: Ability to rotate compromised keys

Code Examples

Multi-Signature Wallet

Basic multi-sig implementation

RUST
use std::collections::HashSet;

struct Transaction {
    to: String,
    amount: u64,
    nonce: u64,
    signers: HashSet<String>,
}

struct MultiSigWallet {
    owners: Vec<String>,
    threshold: usize,
    nonce: u64,
}

impl MultiSigWallet {
    fn new(owners: Vec<String>, threshold: usize) -> Self {
        if threshold > owners.len() || threshold == 0 {
            panic!("Invalid threshold");
        }
        
        MultiSigWallet {
            owners,
            threshold,
            nonce: 0,
        }
    }
    
    fn create_transaction(&mut self, to: String, amount: u64) -> Transaction {
        let tx = Transaction {
            to,
            amount,
            nonce: self.nonce,
            signers: HashSet::new(),
        };
        
        self.nonce += 1;
        tx
    }
    
    fn sign_transaction(
        &self,
        tx: &mut Transaction,
        signer: &str,
    ) -> Result<(), String> {
        if !self.owners.contains(&signer.to_string()) {
            return Err(String::from("Not an owner"));
        }
        
        if tx.signers.contains(signer) {
            return Err(String::from("Already signed"));
        }
        
        tx.signers.insert(signer.to_string());
        Ok(())
    }
    
    fn can_execute(&self, tx: &Transaction) -> bool {
        tx.signers.len() >= self.threshold
    }
    
    fn execute(&self, tx: &Transaction) -> Result<(), String> {
        if !self.can_execute(tx) {
            return Err(format!("Need {} signatures, got {}", 
                             self.threshold, tx.signers.len()));
        }
        
        // Verify all signers are owners
        for signer in &tx.signers {
            if !self.owners.contains(signer) {
                return Err(String::from("Invalid signer"));
            }
        }
        
        println!("Executing transaction: {} to {}, amount: {}", 
                tx.nonce, tx.to, tx.amount);
        
        Ok(())
    }
}

fn main() {
    let owners = vec![
        String::from("alice"),
        String::from("bob"),
        String::from("charlie"),
    ];
    
    let mut wallet = MultiSigWallet::new(owners, 2); // 2 of 3
    
    let mut tx = wallet.create_transaction(String::from("0xRecipient"), 1000);
    
    // Sign with 2 owners
    wallet.sign_transaction(&mut tx, "alice").unwrap();
    wallet.sign_transaction(&mut tx, "bob").unwrap();
    
    // Execute (has 2/3 signatures)
    wallet.execute(&tx).unwrap();
    
    println!("Multi-sig transaction executed successfully!");
}

Explanation:

Multi-sig wallets require multiple signatures to authorize transactions. This provides security: even if one key is compromised, the wallet is still secure. Common setups are 2-of-3 or 3-of-5.

Threshold Signature Concept

Conceptual threshold signature system

RUST
// Simplified threshold signature concept
struct KeyShare {
    index: usize,
    public_share: Vec<u8>,
    // secret_share is never stored, only known to participant
}

struct ThresholdSignature {
    threshold: usize,
    total: usize,
    public_key: Vec<u8>, // Aggregate public key
    key_shares: Vec<KeyShare>,
}

impl ThresholdSignature {
    fn new(threshold: usize, total: usize) -> Self {
        ThresholdSignature {
            threshold,
            total,
            public_key: vec![], // Generated from shares
            key_shares: vec![],
        }
    }
    
    fn can_sign(&self, shares_available: usize) -> bool {
        shares_available >= self.threshold
    }
    
    fn aggregate_signature(&self, partial_signatures: &[Vec<u8>]) -> Vec<u8> {
        // Combine partial signatures into full signature
        // Need exactly 'threshold' partial signatures
        
        if partial_signatures.len() < self.threshold {
            panic!("Insufficient signatures");
        }
        
        // In real implementation: use cryptographic aggregation
        vec![] // Simplified aggregate signature
    }
    
    fn verify(&self, message: &[u8], signature: &[u8]) -> bool {
        // Verify signature against aggregate public key
        // Doesn't reveal which participants signed
        true // Simplified
    }
}

fn main() {
    // 3-of-5 threshold signature
    let threshold_sig = ThresholdSignature::new(3, 5);
    
    println!("Threshold: {}/{}", threshold_sig.threshold, threshold_sig.total);
    println!("Need {} signatures to create valid signature", threshold_sig.threshold);
    println!("Even if 2 participants are compromised, wallet is secure!");
}

Explanation:

Threshold signatures allow a group to sign with only a subset of members. The private key is split into shares. Need 'threshold' shares to sign, but no single person has the full key. More secure than multi-sig.

Exercises

Multi-Sig Check

Check if transaction has enough signatures!

Easy

Starter Code:

RUST
struct Transaction {
    signers: Vec<String>,
}

fn can_execute(tx: &Transaction, threshold: usize) -> bool {
    // Check if enough signatures
}