-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Date: 2025-11-29
Status: Draft
Context: P1-7 Implementation - new_unified() Constructor
Problem
Original design anchored identity on PQC keypairs:
seed → PQC keypair → key_id → DID → everything else
Fatal flaw: pqcrypto-* crates don't support seeded keypair generation.
dilithium2::keypair()uses globalthread_rng()(always random)- No API to pass custom RNG or seed
- Same seed → different Dilithium keypair → different DID → broken determinism
This breaks:
- ❌ Deterministic identity recovery
- ❌ Multi-device identity sync
- ❌ Predictable DIDs, NodeIds, secrets
- ❌ Reproducible tests
- ❌ Sovereign identity model
Decision
Anchor identity on the seed, not on PQC keypairs.
New Architecture: Seed as Root of Trust
seed (root of trust)
├─ DID = did:zhtp:{Blake3(seed || ZHTP_DID_V1)}
├─ IdentityId = Blake3(DID)
├─ zk_identity_secret = Blake3(seed || ZHTP_ZK_SECRET_V1)
├─ wallet_master_seed = XOF(seed || ZHTP_WALLET_SEED_V1) [64 bytes]
├─ dao_member_id = Blake3(DAO: || DID)
├─ NodeIds = f(DID, device)
└─ PQC keypairs (random, attached, rotatable)
├─ Dilithium2 (for signatures)
└─ Kyber512 (for KEM)
Key Principles
-
Seed defines identity
- Same seed → same DID (forever)
- All secrets derive from seed deterministically
- Identity survives across devices, backups, recoveries
-
PQC keys are attachments, not anchors
- Generated randomly via
pqcrypto-*APIs - Bound to DID via attestation/signature
- Can be rotated, replaced, lost without breaking identity
- Stored as credentials belonging to the DID
- Generated randomly via
-
Determinism where it matters
- ✅ DID (from seed)
- ✅ All secrets (from seed)
- ✅ NodeIds (from DID + device)
- ✅ DAO membership (from DID)
- ❌ PQC keypairs (random, by design)
Implementation
Signature
pub fn new_unified(
identity_type: IdentityType,
age: Option<u64>,
jurisdiction: Option<String>,
primary_device: &str,
seed: Option<[u8; 64]>, // NEW: optional seed for determinism
) -> Result<Self>Behavior
- seed = Some(s): Deterministic identity (same seed → same DID/secrets)
- seed = None: Generate random seed via
OsRng(new identity, exportable)
Derivation Functions
All use domain-separated Blake3:
// DID from seed (not from PQC key_id)
fn derive_did_from_seed(seed: &[u8; 64]) -> String {
let hash = Blake3(seed || ZHTP_DID_V1);
format!(did:zhtp:{}, hex::encode(hash))
}
// Secrets from seed
fn derive_zk_secret(seed: &[u8; 64]) -> [u8; 32] {
Blake3(seed || ZHTP_ZK_SECRET_V1)
}
fn derive_wallet_seed(seed: &[u8; 64]) -> [u8; 64] {
Blake3_XOF(seed || ZHTP_WALLET_SEED_V1, 64)
}PQC Key Handling
// Generate random PQC keypairs (not from seed)
let (dilithium_pk, dilithium_sk) = dilithium2::keypair();
let (kyber_pk, kyber_sk) = kyber512::keypair();
// Store as belonging to DID (not defining DID)
identity.public_key = PublicKey {
dilithium_pk,
kyber_pk,
key_id: Blake3(dilithium_pk || kyber_pk), // For lookups only
};Consequences
Positive
✅ Deterministic recovery: Same seed → same identity across devices
✅ PQC API compatibility: Works with current pqcrypto-* crates
✅ Key rotation: Can replace PQC keys without breaking DID
✅ Future-proof: Compatible with future seeded PQC (liboqs, etc.)
✅ Testable: Reproducible golden vectors for all derived fields
✅ Clean separation: Identity (seed-based) vs. Transport (PQC-based)
Tradeoffs
Migration Path
- Old identities:
DID = did:zhtp:{key_id}(from PQC) - New identities:
DID = did:zhtp:{Blake3(seed)}(from seed) - Both can coexist with version markers
Alternatives Considered
Option 1: Accept broken determinism
Rejected: Defeats sovereign identity purpose
Option 2: Switch to liboqs-rust immediately
Deferred:
- Large refactor, more dependencies
- PQC ecosystem still evolving
- Can adopt later without breaking this architecture
Option 3: Seed-anchored identity (CHOSEN)
Rationale:
- Solves determinism without fighting PQC APIs
- Matches industry patterns (Signal, Tor, EUDI, Bitcoin HD)
- Enables recovery, multi-device, and testing
- Future-compatible with better PQC libraries
References
- Issue [P1-7] Update new_unified() constructor with complete derivations #10: Update new_unified() Constructor
- P1-6: Cryptographic Derivation Functions
- ARCHITECTURE_CONSOLIDATION.md: Identity field specifications
- Similar patterns:
- Signal: Seed → Identity Key (Ed25519) + Prekeys (random)
- Tor: Seed → Ed25519 identity + Random circuit keys
- Bitcoin HD Wallets: Seed → All keys via BIP32/44
- EUDI Wallet: Seed + KDF → All credentials
Status
** Draft implemented in P1-7.**
This decision log captures the pivot from PQC-anchored to seed-anchored identity architecture, ensuring deterministic sovereign identity with current PQC library constraints.