Rivellum

Rivellum Portal

Checking...
testnet

Post-Quantum Ready Account Model

Overview

Rivellum implements a flexible account authentication system designed to be post-quantum resistant. The system supports multiple cryptographic schemes including Ed25519 (current), multi-signature, and post-quantum algorithms (Dilithium, Falcon).

Architecture

KeyScheme Enum

#[non_exhaustive]
pub enum KeyScheme {
    /// Standard Ed25519 single signature
    Ed25519,
    /// Ed25519 multi-signature with threshold
    Ed25519MultiSig { threshold: u8 },
    /// CRYSTALS-Dilithium (NIST PQ standard, stubbed)
    Dilithium,
    /// Falcon (NIST PQ finalist, stubbed)
    Falcon,
}

Properties:

  • #[non_exhaustive]: Allows adding new schemes without breaking changes
  • Explicit threshold for multi-sig variants
  • Separation of quantum-safe and classical schemes

PublicKey Type

pub enum PublicKey {
    Ed25519([u8; 32]),
    Dilithium(Vec<u8>),  // Variable-length PQ keys
    Falcon(Vec<u8>),
}

Design Rationale:

  • Fixed-size for Ed25519 (32 bytes)
  • Variable-size Vec for PQ schemes (larger keys)
  • Type-safe: impossible to mix key types

Signature Type

pub enum Signature {
    Ed25519([u8; 64]),
    Dilithium(Vec<u8>),  // Variable-length PQ signatures
    Falcon(Vec<u8>),
}

Properties:

  • Ed25519: 64-byte signature
  • PQ signatures: Larger (1KB-3KB typical)
  • Serializable for storage and transmission

AccountAuth Structure

pub struct AccountAuth {
    /// Key scheme in use
    pub scheme: KeyScheme,
    /// Public keys (single for Ed25519, multiple for MultiSig)
    pub public_keys: Vec<PublicKey>,
    /// Threshold for multi-sig (must be <= public_keys.len())
    pub threshold: Option<u8>,
}

Construction Helpers:

// Single-key Ed25519 (default)
AccountAuth::ed25519_single(public_key: [u8; 32])

// Multi-sig Ed25519 (2-of-3 example)
AccountAuth::ed25519_multisig(
    vec![pk1, pk2, pk3],
    2  // threshold
)

Account State Integration

AccountState Extension

pub struct AccountState {
    pub balances: HashMap<AssetId, u128>,
    pub nonce: u64,
    pub auth: Option<AccountAuth>,  // None = default Ed25519
}

Backward Compatibility:

  • auth: None → Treated as single-key Ed25519
  • Existing accounts migrate seamlessly
  • No breaking changes to state structure

Default Behavior

New accounts default to Ed25519 single-key authentication:

let account = AccountState::new(1000);
// account.auth is None, treated as Ed25519 single-key

Signature Verification

SignatureVerifier Trait

pub trait SignatureVerifier {
    fn verify(
        &self,
        message: &[u8],
        signature: &Signature,
        public_key: &PublicKey
    ) -> bool;
}

Ed25519 Verification

impl SignatureVerifier for Ed25519Verifier {
    fn verify(&self, message: &[u8], signature: &Signature, public_key: &PublicKey) -> bool {
        match (signature, public_key) {
            (Signature::Ed25519(sig_bytes), PublicKey::Ed25519(pk_bytes)) => {
                let sig = ed25519_dalek::Signature::from_bytes(sig_bytes);
                let pk = VerifyingKey::from_bytes(pk_bytes)?;
                pk.verify(message, &sig).is_ok()
            }
            _ => false,  // Type mismatch
        }
    }
}

Multi-Sig Verification

pub struct Ed25519MultiSigVerifier {
    threshold: u8,
}

impl Ed25519MultiSigVerifier {
    pub fn verify_multisig(
        &self,
        message: &[u8],
        signatures: &[Signature],
        public_keys: &[PublicKey],
    ) -> bool {
        if signatures.len() < self.threshold as usize {
            return false;
        }

        let mut valid_count = 0;
        for signature in signatures {
            for public_key in public_keys {
                if Ed25519Verifier.verify(message, signature, public_key) {
                    valid_count += 1;
                    break;
                }
            }
        }

        valid_count >= self.threshold as usize
    }
}

Properties:

  • Each signature must match at least one public key
  • Count valid signatures
  • Pass if valid_count >= threshold

Account Auth Verification

pub fn verify_account_auth(
    message: &[u8],
    signatures: &[Signature],
    auth: &AccountAuth,
) -> Result<bool, String> {
    auth.validate()?;

    match &auth.scheme {
        KeyScheme::Ed25519 => {
            // Require exactly 1 signature
            if signatures.len() != 1 { return Ok(false); }
            Ed25519Verifier.verify(message, &signatures[0], &auth.public_keys[0])
        }
        KeyScheme::Ed25519MultiSig { threshold } => {
            Ed25519MultiSigVerifier::new(*threshold)
                .verify_multisig(message, signatures, &auth.public_keys)
        }
        KeyScheme::Dilithium => {
            Err("Dilithium not yet implemented")
        }
        KeyScheme::Falcon => {
            Err("Falcon not yet implemented")
        }
        _ => {
            Err(format!("Unsupported scheme: {:?}", auth.scheme))
        }
    }
}

Account Management Operations

Set Account Auth

PlainPayload::SetAccountAuth {
    auth: AccountAuth,
}

Requirements:

  • Must provide proof of current auth control
  • Validates new auth configuration
  • Emits SecurityEvent::AuthChanged

Example: Upgrade to 2-of-3 multi-sig

let new_auth = AccountAuth::ed25519_multisig(
    vec![key1, key2, key3],
    2
)?;

let intent = Intent {
    payload: PlainPayload::SetAccountAuth { auth: new_auth },
    // ... sender, nonce, signatures
};

Key Rotation

Within the same scheme, rotate keys while maintaining security:

Single-key rotation:

// Sign with old key to prove control
let proof_sig = sign_with_old_key(&intent_data);

// Set new auth with new key
let new_auth = AccountAuth::ed25519_single(new_public_key);

Multi-sig rotation: Add/remove keys while maintaining threshold

// 2-of-3 → add key4 → 2-of-4
let new_auth = AccountAuth::ed25519_multisig(
    vec![key1, key2, key3, key4],
    2
)?;

Upgrade Paths

Ed25519 Single → Multi-Sig

// Current: Single key
// Target: 2-of-3 multi-sig

let new_auth = AccountAuth::ed25519_multisig(
    vec![old_key, new_key1, new_key2],
    2
)?;

Best Practice: Include old key in multi-sig during transition.

Ed25519 → Post-Quantum Hybrid

// Future: Combine Ed25519 + PQ
let new_auth = AccountAuth {
    scheme: KeyScheme::Hybrid {
        classical: KeyScheme::Ed25519,
        quantum_safe: KeyScheme::Dilithium,
    },
    public_keys: vec![
        PublicKey::Ed25519(classical_key),
        PublicKey::Dilithium(pq_key),
    ],
    threshold: Some(2),  // Require both
};

Post-Quantum Integration Roadmap

Phase 1: Infrastructure (Current)

  • ✅ Type definitions (KeyScheme, PublicKey, Signature)
  • AccountAuth structure
  • ✅ Ed25519 single-key verification
  • ✅ Ed25519 multi-sig verification
  • ✅ Signature verification trait
  • ⏳ Account auth setting operations

Phase 2: PQ Algorithm Stubs

  • ⏳ Dilithium placeholder with TODO comments
  • ⏳ Falcon placeholder with TODO comments
  • ⏳ Error messages for unimplemented schemes
  • ⏳ Test framework for PQ integration

Phase 3: Dilithium Integration

  • ⏳ Add dilithium crate dependency
  • ⏳ Implement DilithiumVerifier
  • ⏳ Key generation utilities
  • ⏳ Serialization/deserialization
  • ⏳ Integration tests

Phase 4: Falcon Integration

  • ⏳ Add falcon crate dependency (if available)
  • ⏳ Implement FalconVerifier
  • ⏳ Key generation utilities
  • ⏳ Performance benchmarking vs Dilithium

Phase 5: Hybrid Mode

  • ⏳ Combine classical + PQ in single auth
  • ⏳ Dual-signature requirement
  • ⏳ Migration tools for existing accounts

Phase 6: Production Hardening

  • ⏳ Key management best practices
  • ⏳ Hardware wallet support for PQ
  • ⏳ Backup and recovery procedures
  • ⏳ Security audit of PQ implementation

Security Considerations

Threat Model

Quantum Computer Attacks:

  • Shor's Algorithm: Breaks RSA, ECDSA, Ed25519
  • Grover's Algorithm: Weakens symmetric crypto (AES-256 → AES-128 effective)
  • Timeline: 10-30 years until practical quantum computers

Defense Strategy:

  1. Prepare Infrastructure: Type system supports PQ algorithms
  2. Gradual Migration: Hybrid mode allows coexistence
  3. Monitor NIST: Follow post-quantum standardization
  4. Test Early: Stub implementations enable testing

Multi-Sig Security

Threshold Choice:

  • 2-of-3: Good balance (lose 1 key, still secure)
  • 3-of-5: High security (tolerate 2 key losses)
  • 5-of-7: Enterprise grade

Key Distribution:

  • Store keys on separate devices
  • Use hardware wallets when possible
  • Consider geographic distribution

Attack Scenarios:

  • Single key compromise: Below threshold, no impact
  • Threshold compromise: Account is compromised
  • Social engineering: Multi-sig resists single-target attacks

Auth Change Security

Requirements for Auth Changes:

  1. Proof of Control: Must sign with current auth
  2. Validation: New auth must pass validate()
  3. Event Logging: Emit SecurityEvent::AuthChanged
  4. Confirmation Period: Consider time-lock for critical changes

Best Practices:

// Multi-step confirmation for high-value accounts
// 1. Propose auth change (time-locked)
// 2. Wait 24-48 hours
// 3. Confirm auth change with current auth
// 4. Apply new auth

SDK Integration

TypeScript Types

export enum KeyScheme {
  Ed25519 = 'ed25519',
  Ed25519MultiSig = 'ed25519_multisig',
  Dilithium = 'dilithium',
  Falcon = 'falcon',
}

export interface PublicKey {
  scheme: KeyScheme;
  bytes: Uint8Array;
}

export interface AccountAuth {
  scheme: KeyScheme;
  publicKeys: PublicKey[];
  threshold?: number;
}

export interface Signature {
  scheme: KeyScheme;
  bytes: Uint8Array;
}

SDK Methods

import { RivellumClient } from '@rivellum/sdk';

const client = new RivellumClient('https://rpc.rivellum.network');

// Get account auth configuration
const auth = await client.getAccountAuth('0x...');
console.log(auth.scheme, auth.threshold);

// Build multi-sig intent
const intent = await client.buildMultiSigIntent({
  payload: { type: 'transfer', to: '0x...', amount: 1000 },
  signatures: [sig1, sig2],  // Provide multiple signatures
  publicKeys: [pk1, pk2, pk3],
  threshold: 2,
});

// Set account auth (upgrade to multi-sig)
const setAuthTx = await client.setAccountAuth({
  auth: {
    scheme: KeyScheme.Ed25519MultiSig,
    publicKeys: [pk1, pk2, pk3],
    threshold: 2,
  },
});

CLI Wallet Integration

natos-wallet Commands

# View account auth
natos-wallet account auth show

# Upgrade to multi-sig
natos-wallet account auth set-multisig \
  --keys pk1.json,pk2.json,pk3.json \
  --threshold 2

# Rotate key
natos-wallet account auth rotate-key \
  --old-key old.json \
  --new-key new.json

# Sign with multi-sig
natos-wallet transaction sign \
  --multisig \
  --keys key1.json,key2.json \
  --threshold 2 \
  tx.json

Examples

Creating 2-of-3 Multi-Sig Account

use rivellum_crypto::{generate_keypair, address_from_public};
use rivellum_types::AccountAuth;

// Generate 3 keypairs
let kp1 = generate_keypair();
let kp2 = generate_keypair();
let kp3 = generate_keypair();

// Create 2-of-3 multi-sig auth
let auth = AccountAuth::ed25519_multisig(
    vec![
        kp1.public.to_bytes(),
        kp2.public.to_bytes(),
        kp3.public.to_bytes(),
    ],
    2,
)?;

// Validate auth configuration
auth.validate()?;

// Set on account
let mut account = AccountState::new(1000);
account.set_auth(auth)?;

Signing with Multi-Sig

use rivellum_crypto::sign_intent_data;
use rivellum_types::Signature as TypesSignature;

let intent_data = bincode::serialize(&intent)?;

// Sign with 2 of the 3 keys
let sig1 = sign_intent_data(&intent_data, &kp1.secret);
let sig2 = sign_intent_data(&intent_data, &kp2.secret);

let signatures = vec![
    TypesSignature::Ed25519(sig1.to_bytes()),
    TypesSignature::Ed25519(sig2.to_bytes()),
];

// Verify (would pass with 2 signatures)
let valid = verify_account_auth(&intent_data, &signatures, &auth)?;
assert!(valid);

Testing

Unit Tests

#[test]
fn test_multisig_2_of_3() {
    let kp1 = generate_keypair();
    let kp2 = generate_keypair();
    let kp3 = generate_keypair();

    let auth = AccountAuth::ed25519_multisig(
        vec![kp1.public.to_bytes(), kp2.public.to_bytes(), kp3.public.to_bytes()],
        2,
    ).unwrap();

    let message = b"test message";
    let sig1 = sign_intent_data(message, &kp1.secret);
    let sig2 = sign_intent_data(message, &kp2.secret);

    let signatures = vec![
        Signature::Ed25519(sig1.to_bytes()),
        Signature::Ed25519(sig2.to_bytes()),
    ];

    assert!(verify_account_auth(message, &signatures, &auth).unwrap());
}

Future Extensions

Hardware Wallet Support

  • PQ-compatible hardware wallets
  • Secure key generation
  • Transaction signing

Account Recovery

  • Social recovery (M-of-N friends)
  • Time-locked recovery
  • Backup key activation

Delegation

  • Temporary key delegation
  • Limited-scope permissions
  • Revocable access

Zero-Knowledge Proofs

  • Private authentication
  • Selective disclosure
  • Ring signatures for anonymity