rename: aldabra → aldabra (per Sulkta 2026-05-04)
Aldabra giant tortoise (Aldabrachelys gigantea) — endemic to the
Aldabra atoll, up to 250 kg, 150-year lifespan. Long-lived,
defended, slow but unstoppable. Better metaphor for the wallet
than 'aldabra' which was on-the-tin descriptive.
All renames in one pass:
- repo: Sulkta-Coop/aldabra → Sulkta-Coop/aldabra (via gitea API)
- workspace dir: aldabra → aldabra
- crate dirs: wallet-{core,chain,mcp} → aldabra-{core,chain,mcp}
- crate names + path imports in Cargo.toml workspace + each crate
- binary name: aldabra → aldabra
- README, ROADMAP, docs/architecture: all references swept
This commit is contained in:
parent
56bcceb593
commit
edc976e5d9
9 changed files with 39 additions and 34 deletions
142
crates/aldabra-core/src/lib.rs
Normal file
142
crates/aldabra-core/src/lib.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
//! aldabra core — keys, addresses, signing.
|
||||
//!
|
||||
//! This crate is the security boundary. Everything that touches private
|
||||
//! key material lives here, and only here. No I/O, no network, no MCP.
|
||||
//!
|
||||
//! ## Layout (target)
|
||||
//!
|
||||
//! - [`mnemonic`] — 24-word BIP-39 input → root key (CIP-3)
|
||||
//! - [`derive`] — Root key → payment + stake key (CIP-1852 paths)
|
||||
//! - [`address`] — Public keys → bech32 addresses (mainnet / testnet)
|
||||
//! - [`signing`] — Sign an unsigned transaction body
|
||||
//!
|
||||
//! ## Phase 1 (this scaffold)
|
||||
//!
|
||||
//! Just types + a placeholder address-derivation function. Real impl
|
||||
//! lands as we wire up `pallas-crypto`'s key derivation API.
|
||||
//!
|
||||
//! ## Memory hygiene rule
|
||||
//!
|
||||
//! Anything that holds a private key MUST `derive(ZeroizeOnDrop)` or
|
||||
//! manually zeroize when going out of scope. Use the `zeroize` crate.
|
||||
//! This is non-negotiable — RAM-resident keys leak via core dumps,
|
||||
//! swap, hibernate state, etc.
|
||||
|
||||
use thiserror::Error;
|
||||
use zeroize::ZeroizeOnDrop;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum WalletError {
|
||||
#[error("invalid mnemonic: {0}")]
|
||||
InvalidMnemonic(String),
|
||||
|
||||
#[error("derivation failed: {0}")]
|
||||
Derivation(String),
|
||||
|
||||
#[error("address encoding failed: {0}")]
|
||||
Address(String),
|
||||
|
||||
#[error("not yet implemented (phase 1 scaffold)")]
|
||||
NotYetImplemented,
|
||||
}
|
||||
|
||||
/// A 24-word BIP-39 mnemonic. Held in memory only while deriving keys;
|
||||
/// callers should drop this immediately after `derive_root_key`.
|
||||
#[derive(ZeroizeOnDrop)]
|
||||
pub struct Mnemonic {
|
||||
/// Stored as a single string (joined with spaces). The
|
||||
/// `ZeroizeOnDrop` derive ensures this gets wiped from RAM when
|
||||
/// the struct is dropped.
|
||||
phrase: String,
|
||||
}
|
||||
|
||||
impl Mnemonic {
|
||||
/// Parse a 24-word mnemonic from a whitespace-separated string.
|
||||
/// Validates word count + checksum via the `bip39` crate.
|
||||
///
|
||||
/// # Phase 1
|
||||
/// TODO: wire up `bip39::Mnemonic::parse_in` once we lock the
|
||||
/// API. For now this just stores the phrase verbatim — DO NOT
|
||||
/// rely on validation yet.
|
||||
pub fn from_phrase(phrase: &str) -> Result<Self, WalletError> {
|
||||
// TODO(phase 1): real validation
|
||||
if phrase.split_whitespace().count() != 24 {
|
||||
return Err(WalletError::InvalidMnemonic(
|
||||
"expected 24 words".into(),
|
||||
));
|
||||
}
|
||||
Ok(Self {
|
||||
phrase: phrase.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Derive the Cardano CIP-3 root key from this mnemonic. Consumes
|
||||
/// the mnemonic so the source phrase is dropped + zeroized
|
||||
/// immediately after.
|
||||
pub fn into_root_key(self) -> Result<RootKey, WalletError> {
|
||||
// TODO(phase 1): pallas-crypto's PBKDF2 + entropy + chain code
|
||||
// derivation per CIP-3. Reference:
|
||||
// https://input-output-hk.github.io/cardano-wallet/concepts/master-key-generation
|
||||
Err(WalletError::NotYetImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
/// CIP-3 root key. Holds the seed material from which payment + stake
|
||||
/// keys are derived via CIP-1852 paths. Zeroized on drop.
|
||||
#[derive(ZeroizeOnDrop)]
|
||||
pub struct RootKey {
|
||||
/// 96 bytes per CIP-3 (extended secret + chain code).
|
||||
bytes: [u8; 96],
|
||||
}
|
||||
|
||||
/// Network parameter — bech32 prefix + protocol magic.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Network {
|
||||
Mainnet,
|
||||
Preview,
|
||||
Preprod,
|
||||
}
|
||||
|
||||
impl Network {
|
||||
pub fn bech32_hrp_prefix(&self) -> &'static str {
|
||||
match self {
|
||||
Network::Mainnet => "addr",
|
||||
Network::Preview | Network::Preprod => "addr_test",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Derive a base address (payment + stake) at account 0, address index 0.
|
||||
///
|
||||
/// # Phase 1
|
||||
/// TODO: real CIP-1852 derivation using pallas-crypto's HD key derivation.
|
||||
/// For now this returns a placeholder that lets the MCP layer be tested
|
||||
/// without real keys.
|
||||
pub fn derive_base_address(
|
||||
_root: &RootKey,
|
||||
network: Network,
|
||||
_account: u32,
|
||||
_index: u32,
|
||||
) -> Result<String, WalletError> {
|
||||
// TODO(phase 1): real implementation
|
||||
let prefix = network.bech32_hrp_prefix();
|
||||
Ok(format!("{prefix}1placeholder_phase_1_scaffold"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mnemonic_word_count_validation() {
|
||||
let too_few = "one two three";
|
||||
assert!(Mnemonic::from_phrase(too_few).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn placeholder_address_has_network_prefix() {
|
||||
let dummy_root = RootKey { bytes: [0u8; 96] };
|
||||
let addr = derive_base_address(&dummy_root, Network::Mainnet, 0, 0).unwrap();
|
||||
assert!(addr.starts_with("addr1"));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue