2023-03-10 22:16:00 +03:00
|
|
|
use core::fmt;
|
2023-06-29 04:14:29 -04:00
|
|
|
use std_shims::string::String;
|
2023-03-10 22:16:00 +03:00
|
|
|
|
|
|
|
|
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
|
|
|
|
use rand_core::{RngCore, CryptoRng};
|
|
|
|
|
|
|
|
|
|
pub(crate) mod classic;
|
|
|
|
|
use classic::{CLASSIC_SEED_LENGTH, CLASSIC_SEED_LENGTH_WITH_CHECKSUM, ClassicSeed};
|
|
|
|
|
|
|
|
|
|
/// Error when decoding a seed.
|
2023-06-29 04:14:29 -04:00
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
|
|
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
2023-03-10 22:16:00 +03:00
|
|
|
pub enum SeedError {
|
2023-06-29 04:14:29 -04:00
|
|
|
#[cfg_attr(feature = "std", error("invalid number of words in seed"))]
|
2023-03-10 22:16:00 +03:00
|
|
|
InvalidSeedLength,
|
2023-06-29 04:14:29 -04:00
|
|
|
#[cfg_attr(feature = "std", error("unknown language"))]
|
2023-03-10 22:16:00 +03:00
|
|
|
UnknownLanguage,
|
2023-06-29 04:14:29 -04:00
|
|
|
#[cfg_attr(feature = "std", error("invalid checksum"))]
|
2023-03-10 22:16:00 +03:00
|
|
|
InvalidChecksum,
|
2023-06-29 04:14:29 -04:00
|
|
|
#[cfg_attr(feature = "std", error("english old seeds don't support checksums"))]
|
2023-03-10 22:16:00 +03:00
|
|
|
EnglishOldWithChecksum,
|
2023-06-29 04:14:29 -04:00
|
|
|
#[cfg_attr(feature = "std", error("invalid seed"))]
|
2023-03-10 22:16:00 +03:00
|
|
|
InvalidSeed,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
|
|
|
|
pub enum Language {
|
|
|
|
|
Chinese,
|
|
|
|
|
English,
|
|
|
|
|
Dutch,
|
|
|
|
|
French,
|
|
|
|
|
Spanish,
|
|
|
|
|
German,
|
|
|
|
|
Italian,
|
|
|
|
|
Portuguese,
|
|
|
|
|
Japanese,
|
|
|
|
|
Russian,
|
|
|
|
|
Esperanto,
|
|
|
|
|
Lojban,
|
|
|
|
|
EnglishOld,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A Monero seed.
|
|
|
|
|
// TODO: Add polyseed to enum
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
|
|
|
|
pub enum Seed {
|
|
|
|
|
Classic(ClassicSeed),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Debug for Seed {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
Seed::Classic(_) => f.debug_struct("Seed::Classic").finish_non_exhaustive(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Seed {
|
|
|
|
|
/// Create a new seed.
|
|
|
|
|
pub fn new<R: RngCore + CryptoRng>(rng: &mut R, lang: Language) -> Seed {
|
|
|
|
|
Seed::Classic(ClassicSeed::new(rng, lang))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse a seed from a String.
|
|
|
|
|
pub fn from_string(words: Zeroizing<String>) -> Result<Seed, SeedError> {
|
|
|
|
|
match words.split_whitespace().count() {
|
|
|
|
|
CLASSIC_SEED_LENGTH | CLASSIC_SEED_LENGTH_WITH_CHECKSUM => {
|
|
|
|
|
ClassicSeed::from_string(words).map(Seed::Classic)
|
|
|
|
|
}
|
|
|
|
|
_ => Err(SeedError::InvalidSeedLength)?,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create a Seed from entropy.
|
|
|
|
|
pub fn from_entropy(lang: Language, entropy: Zeroizing<[u8; 32]>) -> Option<Seed> {
|
|
|
|
|
ClassicSeed::from_entropy(lang, entropy).map(Seed::Classic)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convert a seed to a String.
|
|
|
|
|
pub fn to_string(&self) -> Zeroizing<String> {
|
|
|
|
|
match self {
|
|
|
|
|
Seed::Classic(seed) => seed.to_string(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Return the entropy for this seed.
|
|
|
|
|
pub fn entropy(&self) -> Zeroizing<[u8; 32]> {
|
|
|
|
|
match self {
|
|
|
|
|
Seed::Classic(seed) => seed.entropy(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|