Rename the coins folder to networks (#583)

* Rename the coins folder to networks

Ethereum isn't a coin. It's a network.

Resolves #357.

* More renames of coins -> networks in orchestration

* Correct paths in tests/

* cargo fmt
This commit is contained in:
Luke Parker
2024-07-18 12:16:45 -07:00
committed by GitHub
parent 40cc180853
commit 7d2d739042
244 changed files with 102 additions and 99 deletions

View File

@@ -0,0 +1,46 @@
[package]
name = "polyseed"
version = "0.1.0"
description = "Rust implementation of Polyseed"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/networks/monero/wallet/polyseed"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
edition = "2021"
rust-version = "1.79"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lints]
workspace = true
[dependencies]
std-shims = { path = "../../../../common/std-shims", version = "^0.1.1", default-features = false }
thiserror = { version = "1", default-features = false, optional = true }
subtle = { version = "^2.4", default-features = false }
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
rand_core = { version = "0.6", default-features = false }
sha3 = { version = "0.10", default-features = false }
pbkdf2 = { version = "0.12", features = ["simple"], default-features = false }
[dev-dependencies]
hex = { version = "0.4", default-features = false, features = ["std"] }
[features]
std = [
"std-shims/std",
"thiserror",
"subtle/std",
"zeroize/std",
"rand_core/std",
"sha3/std",
"pbkdf2/std",
]
default = ["std"]

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022-2024 Luke Parker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,11 @@
# Polyseed
Rust implementation of [Polyseed](https://github.com/tevador/polyseed).
This library is usable under no-std when the `std` feature (on by default) is
disabled.
### Cargo Features
- `std` (on by default): Enables `std` (and with it, more efficient internal
implementations).

View File

@@ -0,0 +1,477 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
use core::fmt;
use std_shims::{sync::OnceLock, string::String, collections::HashMap};
#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};
use subtle::ConstantTimeEq;
use zeroize::{Zeroize, Zeroizing, ZeroizeOnDrop};
use rand_core::{RngCore, CryptoRng};
use sha3::Sha3_256;
use pbkdf2::pbkdf2_hmac;
#[cfg(test)]
mod tests;
// Features
const FEATURE_BITS: u8 = 5;
#[allow(dead_code)]
const INTERNAL_FEATURES: u8 = 2;
const USER_FEATURES: u8 = 3;
const USER_FEATURES_MASK: u8 = (1 << USER_FEATURES) - 1;
const ENCRYPTED_MASK: u8 = 1 << 4;
const RESERVED_FEATURES_MASK: u8 = ((1 << FEATURE_BITS) - 1) ^ ENCRYPTED_MASK;
fn user_features(features: u8) -> u8 {
features & USER_FEATURES_MASK
}
fn polyseed_features_supported(features: u8) -> bool {
(features & RESERVED_FEATURES_MASK) == 0
}
// Dates
const DATE_BITS: u8 = 10;
const DATE_MASK: u16 = (1u16 << DATE_BITS) - 1;
const POLYSEED_EPOCH: u64 = 1635768000; // 1st November 2021 12:00 UTC
const TIME_STEP: u64 = 2629746; // 30.436875 days = 1/12 of the Gregorian year
// After ~85 years, this will roll over.
fn birthday_encode(time: u64) -> u16 {
u16::try_from((time.saturating_sub(POLYSEED_EPOCH) / TIME_STEP) & u64::from(DATE_MASK))
.expect("value masked by 2**10 - 1 didn't fit into a u16")
}
fn birthday_decode(birthday: u16) -> u64 {
POLYSEED_EPOCH + (u64::from(birthday) * TIME_STEP)
}
// Polyseed parameters
const SECRET_BITS: usize = 150;
const BITS_PER_BYTE: usize = 8;
const SECRET_SIZE: usize = SECRET_BITS.div_ceil(BITS_PER_BYTE); // 19
const CLEAR_BITS: usize = (SECRET_SIZE * BITS_PER_BYTE) - SECRET_BITS; // 2
// Polyseed calls this CLEAR_MASK and has a very complicated formula for this fundamental
// equivalency
#[allow(clippy::cast_possible_truncation)]
const LAST_BYTE_SECRET_BITS_MASK: u8 = ((1 << (BITS_PER_BYTE - CLEAR_BITS)) - 1) as u8;
const SECRET_BITS_PER_WORD: usize = 10;
// The amount of words in a seed.
const POLYSEED_LENGTH: usize = 16;
// Amount of characters each word must have if trimmed
pub(crate) const PREFIX_LEN: usize = 4;
const POLY_NUM_CHECK_DIGITS: usize = 1;
const DATA_WORDS: usize = POLYSEED_LENGTH - POLY_NUM_CHECK_DIGITS;
// Polynomial
const GF_BITS: usize = 11;
const POLYSEED_MUL2_TABLE: [u16; 8] = [5, 7, 1, 3, 13, 15, 9, 11];
type Poly = [u16; POLYSEED_LENGTH];
fn elem_mul2(x: u16) -> u16 {
if x < 1024 {
return 2 * x;
}
POLYSEED_MUL2_TABLE[usize::from(x % 8)] + (16 * ((x - 1024) / 8))
}
fn poly_eval(poly: &Poly) -> u16 {
// Horner's method at x = 2
let mut result = poly[POLYSEED_LENGTH - 1];
for i in (0 .. (POLYSEED_LENGTH - 1)).rev() {
result = elem_mul2(result) ^ poly[i];
}
result
}
// Key gen parameters
const POLYSEED_SALT: &[u8] = b"POLYSEED key";
const POLYSEED_KEYGEN_ITERATIONS: u32 = 10000;
// Polyseed technically supports multiple coins, and the value for Monero is 0
// See: https://github.com/tevador/polyseed/blob/dfb05d8edb682b0e8f743b1b70c9131712ff4157
// /include/polyseed.h#L57
const COIN: u16 = 0;
/// An error when working with a Polyseed.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "std", derive(thiserror::Error))]
pub enum PolyseedError {
/// 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 feature bits were set.
#[cfg_attr(feature = "std", error("unsupported features"))]
UnsupportedFeatures,
}
/// Language options for Polyseed.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Zeroize)]
pub enum Language {
/// English language option.
English,
/// Spanish language option.
Spanish,
/// French language option.
French,
/// Italian language option.
Italian,
/// Japanese language option.
Japanese,
/// Korean language option.
Korean,
/// Czech language option.
Czech,
/// Portuguese language option.
Portuguese,
/// Simplified Chinese language option.
ChineseSimplified,
/// Traditional Chinese language option.
ChineseTraditional,
}
struct WordList {
words: &'static [&'static str],
has_prefix: bool,
has_accent: bool,
}
impl WordList {
fn new(words: &'static [&'static str], has_prefix: bool, has_accent: bool) -> WordList {
let res = WordList { words, has_prefix, has_accent };
// This is needed for a later unwrap to not fails
assert!(words.len() < usize::from(u16::MAX));
res
}
}
static LANGUAGES_CELL: OnceLock<HashMap<Language, WordList>> = OnceLock::new();
#[allow(non_snake_case)]
fn LANGUAGES() -> &'static HashMap<Language, WordList> {
LANGUAGES_CELL.get_or_init(|| {
HashMap::from([
(Language::Czech, WordList::new(include!("./words/cs.rs"), true, false)),
(Language::French, WordList::new(include!("./words/fr.rs"), true, true)),
(Language::Korean, WordList::new(include!("./words/ko.rs"), false, false)),
(Language::English, WordList::new(include!("./words/en.rs"), true, false)),
(Language::Italian, WordList::new(include!("./words/it.rs"), true, false)),
(Language::Spanish, WordList::new(include!("./words/es.rs"), true, true)),
(Language::Japanese, WordList::new(include!("./words/ja.rs"), false, false)),
(Language::Portuguese, WordList::new(include!("./words/pt.rs"), true, false)),
(
Language::ChineseSimplified,
WordList::new(include!("./words/zh_simplified.rs"), false, false),
),
(
Language::ChineseTraditional,
WordList::new(include!("./words/zh_traditional.rs"), false, false),
),
])
})
}
/// A Polyseed.
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct Polyseed {
language: Language,
features: u8,
birthday: u16,
entropy: Zeroizing<[u8; 32]>,
checksum: u16,
}
impl fmt::Debug for Polyseed {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Polyseed").finish_non_exhaustive()
}
}
fn valid_entropy(entropy: &Zeroizing<[u8; 32]>) -> bool {
// Last byte of the entropy should only use certain bits
let mut res =
entropy[SECRET_SIZE - 1].ct_eq(&(entropy[SECRET_SIZE - 1] & LAST_BYTE_SECRET_BITS_MASK));
// Last 13 bytes of the buffer should be unused
for b in SECRET_SIZE .. entropy.len() {
res &= entropy[b].ct_eq(&0);
}
res.into()
}
impl Polyseed {
// TODO: Clean this
fn to_poly(&self) -> Poly {
let mut extra_bits = u32::from(FEATURE_BITS + DATE_BITS);
let extra_val = (u16::from(self.features) << DATE_BITS) | self.birthday;
let mut entropy_idx = 0;
let mut secret_bits = BITS_PER_BYTE;
let mut seed_rem_bits = SECRET_BITS - BITS_PER_BYTE;
let mut poly = [0; POLYSEED_LENGTH];
for i in 0 .. DATA_WORDS {
extra_bits -= 1;
let mut word_bits = 0;
let mut word_val = 0;
while word_bits < SECRET_BITS_PER_WORD {
if secret_bits == 0 {
entropy_idx += 1;
secret_bits = seed_rem_bits.min(BITS_PER_BYTE);
seed_rem_bits -= secret_bits;
}
let chunk_bits = secret_bits.min(SECRET_BITS_PER_WORD - word_bits);
secret_bits -= chunk_bits;
word_bits += chunk_bits;
word_val <<= chunk_bits;
word_val |=
(u16::from(self.entropy[entropy_idx]) >> secret_bits) & ((1u16 << chunk_bits) - 1);
}
word_val <<= 1;
word_val |= (extra_val >> extra_bits) & 1;
poly[POLY_NUM_CHECK_DIGITS + i] = word_val;
}
poly
}
fn from_internal(
language: Language,
masked_features: u8,
encoded_birthday: u16,
entropy: Zeroizing<[u8; 32]>,
) -> Result<Polyseed, PolyseedError> {
if !polyseed_features_supported(masked_features) {
Err(PolyseedError::UnsupportedFeatures)?;
}
if !valid_entropy(&entropy) {
Err(PolyseedError::InvalidEntropy)?;
}
let mut res = Polyseed {
language,
birthday: encoded_birthday,
features: masked_features,
entropy,
checksum: 0,
};
res.checksum = poly_eval(&res.to_poly());
Ok(res)
}
/// Create a new `Polyseed` with specific internals.
///
/// `birthday` is defined in seconds since the epoch.
pub fn from(
language: Language,
features: u8,
birthday: u64,
entropy: Zeroizing<[u8; 32]>,
) -> Result<Polyseed, PolyseedError> {
Self::from_internal(language, user_features(features), birthday_encode(birthday), entropy)
}
/// Create a new `Polyseed`.
///
/// This uses the system's time for the birthday, if available, else 0.
pub fn new<R: RngCore + CryptoRng>(rng: &mut R, language: Language) -> Polyseed {
// Get the birthday
#[cfg(feature = "std")]
let birthday =
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(core::time::Duration::ZERO).as_secs();
#[cfg(not(feature = "std"))]
let birthday = 0;
// Derive entropy
let mut entropy = Zeroizing::new([0; 32]);
rng.fill_bytes(entropy.as_mut());
entropy[SECRET_SIZE ..].fill(0);
entropy[SECRET_SIZE - 1] &= LAST_BYTE_SECRET_BITS_MASK;
Self::from(language, 0, birthday, entropy).unwrap()
}
/// Create a new `Polyseed` from a String.
#[allow(clippy::needless_pass_by_value)]
pub fn from_string(lang: Language, seed: Zeroizing<String>) -> Result<Polyseed, PolyseedError> {
// Decode the seed into its polynomial coefficients
let mut poly = [0; POLYSEED_LENGTH];
// Validate words are in the lang word list
let lang_word_list: &WordList = &LANGUAGES()[&lang];
for (i, word) in seed.split_whitespace().enumerate() {
// Find the word's index
fn check_if_matches<S: AsRef<str>, I: Iterator<Item = S>>(
has_prefix: bool,
mut lang_words: I,
word: &str,
) -> Option<usize> {
if has_prefix {
// Get the position of the word within the iterator
// Doesn't use starts_with and some words are substrs of others, leading to false
// positives
let mut get_position = || {
lang_words.position(|lang_word| {
let mut lang_word = lang_word.as_ref().chars();
let mut word = word.chars();
let mut res = true;
for _ in 0 .. PREFIX_LEN {
res &= lang_word.next() == word.next();
}
res
})
};
let res = get_position();
// If another word has this prefix, don't call it a match
if get_position().is_some() {
return None;
}
res
} else {
lang_words.position(|lang_word| lang_word.as_ref() == word)
}
}
let Some(coeff) = (if lang_word_list.has_accent {
let ascii = |word: &str| word.chars().filter(char::is_ascii).collect::<String>();
check_if_matches(
lang_word_list.has_prefix,
lang_word_list.words.iter().map(|lang_word| ascii(lang_word)),
&ascii(word),
)
} else {
check_if_matches(lang_word_list.has_prefix, lang_word_list.words.iter(), word)
}) else {
Err(PolyseedError::InvalidSeed)?
};
// WordList asserts the word list length is less than u16::MAX
poly[i] = u16::try_from(coeff).expect("coeff exceeded u16");
}
// xor out the coin
poly[POLY_NUM_CHECK_DIGITS] ^= COIN;
// Validate the checksum
if poly_eval(&poly) != 0 {
Err(PolyseedError::InvalidChecksum)?;
}
// Convert the polynomial into entropy
let mut entropy = Zeroizing::new([0; 32]);
let mut extra = 0;
let mut entropy_idx = 0;
let mut entropy_bits = 0;
let checksum = poly[0];
for mut word_val in poly.into_iter().skip(POLY_NUM_CHECK_DIGITS) {
// Parse the bottom bit, which is one of the bits of extra
// This iterates for less than 16 iters, meaning this won't drop any bits
extra <<= 1;
extra |= word_val & 1;
word_val >>= 1;
// 10 bits per word creates a [8, 2], [6, 4], [4, 6], [2, 8] cycle
// 15 % 4 is 3, leaving 2 bits off, and 152 (19 * 8) - 2 is 150, the amount of bits in the
// secret
let mut word_bits = GF_BITS - 1;
while word_bits > 0 {
if entropy_bits == BITS_PER_BYTE {
entropy_idx += 1;
entropy_bits = 0;
}
let chunk_bits = word_bits.min(BITS_PER_BYTE - entropy_bits);
word_bits -= chunk_bits;
let chunk_mask = (1u16 << chunk_bits) - 1;
if chunk_bits < BITS_PER_BYTE {
entropy[entropy_idx] <<= chunk_bits;
}
entropy[entropy_idx] |=
u8::try_from((word_val >> word_bits) & chunk_mask).expect("chunk exceeded u8");
entropy_bits += chunk_bits;
}
}
let birthday = extra & DATE_MASK;
// extra is contained to u16, and DATE_BITS > 8
let features =
u8::try_from(extra >> DATE_BITS).expect("couldn't convert extra >> DATE_BITS to u8");
let res = Self::from_internal(lang, features, birthday, entropy);
if let Ok(res) = res.as_ref() {
debug_assert_eq!(res.checksum, checksum);
}
res
}
/// When this seed was created, defined in seconds since the epoch.
pub fn birthday(&self) -> u64 {
birthday_decode(self.birthday)
}
/// This seed's features.
pub fn features(&self) -> u8 {
self.features
}
/// This seed's entropy.
pub fn entropy(&self) -> &Zeroizing<[u8; 32]> {
&self.entropy
}
/// The key derived from this seed.
pub fn key(&self) -> Zeroizing<[u8; 32]> {
let mut key = Zeroizing::new([0; 32]);
pbkdf2_hmac::<Sha3_256>(
self.entropy.as_slice(),
POLYSEED_SALT,
POLYSEED_KEYGEN_ITERATIONS,
key.as_mut(),
);
key
}
/// The String representation of this seed.
pub fn to_string(&self) -> Zeroizing<String> {
// Encode the polynomial with the existing checksum
let mut poly = self.to_poly();
poly[0] = self.checksum;
// Embed the coin
poly[POLY_NUM_CHECK_DIGITS] ^= COIN;
// Output words
let mut seed = Zeroizing::new(String::new());
let words = &LANGUAGES()[&self.language].words;
for i in 0 .. poly.len() {
seed.push_str(words[usize::from(poly[i])]);
if i < poly.len() - 1 {
seed.push(' ');
}
}
seed
}
}

View File

@@ -0,0 +1,218 @@
use zeroize::Zeroizing;
use rand_core::OsRng;
use crate::*;
#[test]
fn test_polyseed() {
struct Vector {
language: Language,
seed: String,
entropy: String,
birthday: u64,
has_prefix: bool,
has_accent: bool,
}
let vectors = [
Vector {
language: Language::English,
seed: "raven tail swear infant grief assist regular lamp \
duck valid someone little harsh puppy airport language"
.into(),
entropy: "dd76e7359a0ded37cd0ff0f3c829a5ae01673300000000000000000000000000".into(),
birthday: 1638446400,
has_prefix: true,
has_accent: false,
},
Vector {
language: Language::Spanish,
seed: "eje fin parte célebre tabú pestaña lienzo puma \
prisión hora regalo lengua existir lápiz lote sonoro"
.into(),
entropy: "5a2b02df7db21fcbe6ec6df137d54c7b20fd2b00000000000000000000000000".into(),
birthday: 3118651200,
has_prefix: true,
has_accent: true,
},
Vector {
language: Language::French,
seed: "valable arracher décaler jeudi amusant dresser mener épaissir risible \
prouesse réserve ampleur ajuster muter caméra enchère"
.into(),
entropy: "11cfd870324b26657342c37360c424a14a050b00000000000000000000000000".into(),
birthday: 1679314966,
has_prefix: true,
has_accent: true,
},
Vector {
language: Language::Italian,
seed: "caduco midollo copione meninge isotopo illogico riflesso tartaruga fermento \
olandese normale tristezza episodio voragine forbito achille"
.into(),
entropy: "7ecc57c9b4652d4e31428f62bec91cfd55500600000000000000000000000000".into(),
birthday: 1679316358,
has_prefix: true,
has_accent: false,
},
Vector {
language: Language::Portuguese,
seed: "caverna custear azedo adeus senador apertada sedoso omitir \
sujeito aurora videira molho cartaz gesso dentista tapar"
.into(),
entropy: "45473063711376cae38f1b3eba18c874124e1d00000000000000000000000000".into(),
birthday: 1679316657,
has_prefix: true,
has_accent: false,
},
Vector {
language: Language::Czech,
seed: "usmrtit nora dotaz komunita zavalit funkce mzda sotva akce \
vesta kabel herna stodola uvolnit ustrnout email"
.into(),
entropy: "7ac8a4efd62d9c3c4c02e350d32326df37821c00000000000000000000000000".into(),
birthday: 1679316898,
has_prefix: true,
has_accent: false,
},
Vector {
language: Language::Korean,
seed: "전망 선풍기 국제 무궁화 설사 기름 이론적 해안 절망 예선 \
지우개 보관 절망 말기 시각 귀신"
.into(),
entropy: "684663fda420298f42ed94b2c512ed38ddf12b00000000000000000000000000".into(),
birthday: 1679317073,
has_prefix: false,
has_accent: false,
},
Vector {
language: Language::Japanese,
seed: "うちあわせ ちつじょ つごう しはい けんこう とおる てみやげ はんとし たんとう \
といれ おさない おさえる むかう ぬぐう なふだ せまる"
.into(),
entropy: "94e6665518a6286c6e3ba508a2279eb62b771f00000000000000000000000000".into(),
birthday: 1679318722,
has_prefix: false,
has_accent: false,
},
Vector {
language: Language::ChineseTraditional,
seed: "亂 挖 斤 柄 代 圈 枝 轄 魯 論 函 開 勘 番 榮 壁".into(),
entropy: "b1594f585987ab0fd5a31da1f0d377dae5283f00000000000000000000000000".into(),
birthday: 1679426433,
has_prefix: false,
has_accent: false,
},
Vector {
language: Language::ChineseSimplified,
seed: "啊 百 族 府 票 划 伪 仓 叶 虾 借 溜 晨 左 等 鬼".into(),
entropy: "21cdd366f337b89b8d1bc1df9fe73047c22b0300000000000000000000000000".into(),
birthday: 1679426817,
has_prefix: false,
has_accent: false,
},
// The following seed requires the language specification in order to calculate
// a single valid checksum
Vector {
language: Language::Spanish,
seed: "impo sort usua cabi venu nobl oliv clim \
cont barr marc auto prod vaca torn fati"
.into(),
entropy: "dbfce25fe09b68a340e01c62417eeef43ad51800000000000000000000000000".into(),
birthday: 1701511650,
has_prefix: true,
has_accent: true,
},
];
for vector in vectors {
let add_whitespace = |mut seed: String| {
seed.push(' ');
seed
};
let seed_without_accents = |seed: &str| {
seed
.split_whitespace()
.map(|w| w.chars().filter(char::is_ascii).collect::<String>())
.collect::<Vec<_>>()
.join(" ")
};
let trim_seed = |seed: &str| {
let seed_to_trim =
if vector.has_accent { seed_without_accents(seed) } else { seed.to_string() };
seed_to_trim
.split_whitespace()
.map(|w| {
let mut ascii = 0;
let mut to_take = w.len();
for (i, char) in w.chars().enumerate() {
if char.is_ascii() {
ascii += 1;
}
if ascii == PREFIX_LEN {
// +1 to include this character, which put us at the prefix length
to_take = i + 1;
break;
}
}
w.chars().take(to_take).collect::<String>()
})
.collect::<Vec<_>>()
.join(" ")
};
// String -> Seed
println!("{}. language: {:?}, seed: {}", line!(), vector.language, vector.seed.clone());
let seed = Polyseed::from_string(vector.language, Zeroizing::new(vector.seed.clone())).unwrap();
let trim = trim_seed(&vector.seed);
let add_whitespace = add_whitespace(vector.seed.clone());
let seed_without_accents = seed_without_accents(&vector.seed);
// Make sure a version with added whitespace still works
let whitespaced_seed =
Polyseed::from_string(vector.language, Zeroizing::new(add_whitespace)).unwrap();
assert_eq!(seed, whitespaced_seed);
// Check trimmed versions works
if vector.has_prefix {
let trimmed_seed = Polyseed::from_string(vector.language, Zeroizing::new(trim)).unwrap();
assert_eq!(seed, trimmed_seed);
}
// Check versions without accents work
if vector.has_accent {
let seed_without_accents =
Polyseed::from_string(vector.language, Zeroizing::new(seed_without_accents)).unwrap();
assert_eq!(seed, seed_without_accents);
}
let entropy = Zeroizing::new(hex::decode(vector.entropy).unwrap().try_into().unwrap());
assert_eq!(*seed.entropy(), entropy);
assert!(seed.birthday().abs_diff(vector.birthday) < TIME_STEP);
// Entropy -> Seed
let from_entropy = Polyseed::from(vector.language, 0, seed.birthday(), entropy).unwrap();
assert_eq!(seed.to_string(), from_entropy.to_string());
// Check against ourselves
{
let seed = Polyseed::new(&mut OsRng, vector.language);
println!("{}. seed: {}", line!(), *seed.to_string());
assert_eq!(seed, Polyseed::from_string(vector.language, seed.to_string()).unwrap());
assert_eq!(
seed,
Polyseed::from(vector.language, 0, seed.birthday(), seed.entropy().clone(),).unwrap()
);
}
}
}
#[test]
fn test_invalid_polyseed() {
// This seed includes unsupported features bits and should error on decode
let seed = "include domain claim resemble urban hire lunch bird \
crucial fire best wife ring warm ignore model"
.into();
let res = Polyseed::from_string(Language::English, Zeroizing::new(seed));
assert_eq!(res, Err(PolyseedError::UnsupportedFeatures));
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff