1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use core::fmt;
use std_shims::string::String;

use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
use rand_core::{RngCore, CryptoRng};

pub(crate) mod classic;
pub(crate) mod polyseed;
use classic::{CLASSIC_SEED_LENGTH, CLASSIC_SEED_LENGTH_WITH_CHECKSUM, ClassicSeed};
use polyseed::{POLYSEED_LENGTH, Polyseed};

/// Error when decoding a seed.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "std", derive(thiserror::Error))]
pub enum SeedError {
  #[cfg_attr(feature = "std", error("invalid number of words in seed"))]
  InvalidSeedLength,
  #[cfg_attr(feature = "std", error("unknown language"))]
  UnknownLanguage,
  #[cfg_attr(feature = "std", error("invalid checksum"))]
  InvalidChecksum,
  #[cfg_attr(feature = "std", error("english old seeds don't support checksums"))]
  EnglishOldWithChecksum,
  #[cfg_attr(feature = "std", error("provided entropy is not valid"))]
  InvalidEntropy,
  #[cfg_attr(feature = "std", error("invalid seed"))]
  InvalidSeed,
  #[cfg_attr(feature = "std", error("provided features are not supported"))]
  UnsupportedFeatures,
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SeedType {
  Classic(classic::Language),
  Polyseed(polyseed::Language),
}

/// A Monero seed.
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub enum Seed {
  Classic(ClassicSeed),
  Polyseed(Polyseed),
}

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(),
      Seed::Polyseed(_) => f.debug_struct("Seed::Polyseed").finish_non_exhaustive(),
    }
  }
}

impl Seed {
  /// Creates a new `Seed`.
  pub fn new<R: RngCore + CryptoRng>(rng: &mut R, seed_type: SeedType) -> Seed {
    match seed_type {
      SeedType::Classic(lang) => Seed::Classic(ClassicSeed::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> {
    let word_count = words.split_whitespace().count();
    match seed_type {
      SeedType::Classic(lang) => {
        if word_count != CLASSIC_SEED_LENGTH && word_count != CLASSIC_SEED_LENGTH_WITH_CHECKSUM {
          Err(SeedError::InvalidSeedLength)?
        } else {
          ClassicSeed::from_string(lang, words).map(Seed::Classic)
        }
      }
      SeedType::Polyseed(lang) => {
        if word_count != POLYSEED_LENGTH {
          Err(SeedError::InvalidSeedLength)?
        } else {
          Polyseed::from_string(lang, words).map(Seed::Polyseed)
        }
      }
    }
  }

  /// Creates a `Seed` from an entropy and an optional birthday (denoted in seconds since the
  /// epoch).
  ///
  /// For `SeedType::Classic`, the birthday is ignored.
  ///
  /// 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::Classic(lang) => ClassicSeed::from_entropy(lang, entropy).map(Seed::Classic),
      SeedType::Polyseed(lang) => {
        Polyseed::from(lang, 0, birthday.unwrap_or(0), entropy).map(Seed::Polyseed).ok()
      }
    }
  }

  /// Returns seed as `String`.
  pub fn to_string(&self) -> Zeroizing<String> {
    match self {
      Seed::Classic(seed) => seed.to_string(),
      Seed::Polyseed(seed) => seed.to_string(),
    }
  }

  /// Returns the entropy for this seed.
  pub fn entropy(&self) -> Zeroizing<[u8; 32]> {
    match self {
      Seed::Classic(seed) => seed.entropy(),
      Seed::Polyseed(seed) => seed.entropy().clone(),
    }
  }

  /// Returns the key derived from this seed.
  pub fn key(&self) -> Zeroizing<[u8; 32]> {
    match self {
      // Classic does not differentiate between its entropy and its key
      Seed::Classic(seed) => seed.entropy(),
      Seed::Polyseed(seed) => seed.key(),
    }
  }

  /// Returns the birthday of this seed.
  pub fn birthday(&self) -> u64 {
    match self {
      Seed::Classic(_) => 0,
      Seed::Polyseed(seed) => seed.birthday(),
    }
  }
}