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) - ✅
AccountAuthstructure - ✅ 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
dilithiumcrate dependency - ⏳ Implement
DilithiumVerifier - ⏳ Key generation utilities
- ⏳ Serialization/deserialization
- ⏳ Integration tests
Phase 4: Falcon Integration
- ⏳ Add
falconcrate 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:
- Prepare Infrastructure: Type system supports PQ algorithms
- Gradual Migration: Hybrid mode allows coexistence
- Monitor NIST: Follow post-quantum standardization
- 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:
- Proof of Control: Must sign with current auth
- Validation: New auth must pass
validate() - Event Logging: Emit
SecurityEvent::AuthChanged - 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