mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-01 16:39:53 +00:00
151 lines
4.6 KiB
Rust
151 lines
4.6 KiB
Rust
|
use core::fmt;
|
||
|
use std_shims::string::String;
|
||
|
|
||
|
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||
|
use rand_core::{RngCore, CryptoRng};
|
||
|
|
||
|
pub use monero_seed as original;
|
||
|
pub use polyseed;
|
||
|
|
||
|
use original::{SeedError as OriginalSeedError, Seed as OriginalSeed};
|
||
|
use polyseed::{PolyseedError, Polyseed};
|
||
|
|
||
|
/// An error from working with seeds.
|
||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||
|
pub enum SeedError {
|
||
|
/// The seed was invalid.
|
||
|
#[cfg_attr(feature = "std", error("invalid seed"))]
|
||
|
InvalidSeed,
|
||
|
/// The entropy was invalid.
|
||
|
#[cfg_attr(feature = "std", error("invalid entropy"))]
|
||
|
InvalidEntropy,
|
||
|
/// The checksum did not match the data.
|
||
|
#[cfg_attr(feature = "std", error("invalid checksum"))]
|
||
|
InvalidChecksum,
|
||
|
/// Unsupported features were enabled.
|
||
|
#[cfg_attr(feature = "std", error("unsupported features"))]
|
||
|
UnsupportedFeatures,
|
||
|
}
|
||
|
|
||
|
impl From<OriginalSeedError> for SeedError {
|
||
|
fn from(error: OriginalSeedError) -> SeedError {
|
||
|
match error {
|
||
|
OriginalSeedError::DeprecatedEnglishWithChecksum | OriginalSeedError::InvalidChecksum => {
|
||
|
SeedError::InvalidChecksum
|
||
|
}
|
||
|
OriginalSeedError::InvalidSeed => SeedError::InvalidSeed,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl From<PolyseedError> for SeedError {
|
||
|
fn from(error: PolyseedError) -> SeedError {
|
||
|
match error {
|
||
|
PolyseedError::UnsupportedFeatures => SeedError::UnsupportedFeatures,
|
||
|
PolyseedError::InvalidEntropy => SeedError::InvalidEntropy,
|
||
|
PolyseedError::InvalidSeed => SeedError::InvalidSeed,
|
||
|
PolyseedError::InvalidChecksum => SeedError::InvalidChecksum,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// The type of the seed.
|
||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||
|
pub enum SeedType {
|
||
|
/// The seed format originally used by Monero,
|
||
|
Original(monero_seed::Language),
|
||
|
/// Polyseed.
|
||
|
Polyseed(polyseed::Language),
|
||
|
}
|
||
|
|
||
|
/// A seed, internally either the original format or a Polyseed.
|
||
|
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||
|
pub enum Seed {
|
||
|
/// The originally formatted seed.
|
||
|
Original(OriginalSeed),
|
||
|
/// A Polyseed.
|
||
|
Polyseed(Polyseed),
|
||
|
}
|
||
|
|
||
|
impl fmt::Debug for Seed {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
|
match self {
|
||
|
Seed::Original(_) => f.debug_struct("Seed::Original").finish_non_exhaustive(),
|
||
|
Seed::Polyseed(_) => f.debug_struct("Seed::Polyseed").finish_non_exhaustive(),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Seed {
|
||
|
/// Create a new seed.
|
||
|
pub fn new<R: RngCore + CryptoRng>(rng: &mut R, seed_type: SeedType) -> Seed {
|
||
|
match seed_type {
|
||
|
SeedType::Original(lang) => Seed::Original(OriginalSeed::new(rng, lang)),
|
||
|
SeedType::Polyseed(lang) => Seed::Polyseed(Polyseed::new(rng, lang)),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Parse a seed from a string.
|
||
|
pub fn from_string(seed_type: SeedType, words: Zeroizing<String>) -> Result<Seed, SeedError> {
|
||
|
match seed_type {
|
||
|
SeedType::Original(lang) => Ok(OriginalSeed::from_string(lang, words).map(Seed::Original)?),
|
||
|
SeedType::Polyseed(lang) => Ok(Polyseed::from_string(lang, words).map(Seed::Polyseed)?),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Create a seed from entropy.
|
||
|
///
|
||
|
/// A birthday may be optionally provided, denoted in seconds since the epoch. For
|
||
|
/// SeedType::Original, it will be ignored. For SeedType::Polyseed, it'll be embedded into the
|
||
|
/// seed.
|
||
|
///
|
||
|
/// For SeedType::Polyseed, the last 13 bytes of `entropy` must be 0.
|
||
|
// TODO: Return Result, not Option
|
||
|
pub fn from_entropy(
|
||
|
seed_type: SeedType,
|
||
|
entropy: Zeroizing<[u8; 32]>,
|
||
|
birthday: Option<u64>,
|
||
|
) -> Option<Seed> {
|
||
|
match seed_type {
|
||
|
SeedType::Original(lang) => OriginalSeed::from_entropy(lang, entropy).map(Seed::Original),
|
||
|
SeedType::Polyseed(lang) => {
|
||
|
Polyseed::from(lang, 0, birthday.unwrap_or(0), entropy).ok().map(Seed::Polyseed)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Converts the seed to a string.
|
||
|
pub fn to_string(&self) -> Zeroizing<String> {
|
||
|
match self {
|
||
|
Seed::Original(seed) => seed.to_string(),
|
||
|
Seed::Polyseed(seed) => seed.to_string(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Get the entropy for this seed.
|
||
|
pub fn entropy(&self) -> Zeroizing<[u8; 32]> {
|
||
|
match self {
|
||
|
Seed::Original(seed) => seed.entropy(),
|
||
|
Seed::Polyseed(seed) => seed.entropy().clone(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Get the key derived from this seed.
|
||
|
pub fn key(&self) -> Zeroizing<[u8; 32]> {
|
||
|
match self {
|
||
|
// Original does not differentiate between its entropy and its key
|
||
|
Seed::Original(seed) => seed.entropy(),
|
||
|
Seed::Polyseed(seed) => seed.key(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Get the birthday of this seed, denoted in seconds since the epoch.
|
||
|
pub fn birthday(&self) -> u64 {
|
||
|
match self {
|
||
|
Seed::Original(_) => 0,
|
||
|
Seed::Polyseed(seed) => seed.birthday(),
|
||
|
}
|
||
|
}
|
||
|
}
|