Compare commits

..

1 Commits

Author SHA1 Message Date
15885cc00b
Add --unscramble and pad short words
All checks were successful
moparisthebest/seedxor/pipeline/head This commit looks good
2023-12-07 02:19:22 -05:00
5 changed files with 97 additions and 40 deletions

9
Cargo.lock generated
View File

@ -40,12 +40,19 @@ version = "0.2.148"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
[[package]]
name = "permutohedron"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b687ff7b5da449d39e418ad391e5e08da53ec334903ddbb921db208908fc372c"
[[package]] [[package]]
name = "seedxor" name = "seedxor"
version = "1.0.0" version = "1.1.0"
dependencies = [ dependencies = [
"bip39", "bip39",
"getrandom", "getrandom",
"permutohedron",
] ]
[[package]] [[package]]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "seedxor" name = "seedxor"
version = "1.0.0" version = "1.1.0"
edition = "2021" edition = "2021"
authors = ["moparisthebest <admin@moparisthebest.com>", "KaiWitt <kaiwitt@protonmail.com>"] authors = ["moparisthebest <admin@moparisthebest.com>", "KaiWitt <kaiwitt@protonmail.com>"]
description = "XOR bip39 mnemonics." description = "XOR bip39 mnemonics."
@ -20,5 +20,6 @@ include = [
] ]
[dependencies] [dependencies]
bip39 = { version = "2.0", default-features = false } bip39 = { version = "2.0", default-features = false }
getrandom = { version = "0.2", default-features = false } getrandom = { version = "0.2", default-features = false }
permutohedron = { version = "0.2.4", default-features = false }

View File

@ -3,7 +3,7 @@
seedxor builds on top of [rust-bip39](https://github.com/rust-bitcoin/rust-bip39/) and is a fork of [seed-xor](https://github.com/kaiwolfram/seed-xor) seedxor builds on top of [rust-bip39](https://github.com/rust-bitcoin/rust-bip39/) and is a fork of [seed-xor](https://github.com/kaiwolfram/seed-xor)
and lets you XOR bip39 mnemonics as described in [Coldcards docs](https://github.com/Coldcard/firmware/blob/master/docs/seed-xor.md). and lets you XOR bip39 mnemonics as described in [Coldcards docs](https://github.com/Coldcard/firmware/blob/master/docs/seed-xor.md).
It also lets you split existing mnemonics into as many seeds as you wish It also lets you split existing mnemonics into as many seeds as you wish, and unscramble parts of a seed in random order into valid seeds.
It is also possible to XOR mnemonics with differing numbers of words. It is also possible to XOR mnemonics with differing numbers of words.
For this the xored value takes on the entropy surplus of the longer seed. For this the xored value takes on the entropy surplus of the longer seed.
@ -12,17 +12,18 @@ For this the xored value takes on the entropy surplus of the longer seed.
``` ```
usage: seedxor [options...] usage: seedxor [options...]
-h, --help Display this help -h, --help Display this help
-s, --split <seed> Split seed into num-seeds -s, --split <seed> Split seed into num-seeds
-n, --num-seeds <num> Number of seeds to split into or generate -n, --num-seeds <num> Number of seeds to split into or generate
default 2 default 2
-y, --no-validate Do not validate a split can be successfully recombined, useful for -y, --no-validate Do not validate a split can be successfully recombined, useful for
non-bip39 seeds, like ethereum non-bip39 seeds, like ethereum
-g, --generate Generate num-seeds -g, --generate Generate num-seeds
-w, --word-count <num> Number of words to generate in the seed -w, --word-count <num> Number of words to generate in the seed
default 24 default 24
-c, --combine <seeds...> Combine seeds into one seed -c, --combine <seeds...> Combine seeds into one seed
-r, --short Display only first 4 letters of seed words -r, --short Display only first 4 letters of seed words
-u, --unscramble <seed-parts...> Unscramble seed words in random order to valid seeds
``` ```
``` ```
@ -45,6 +46,17 @@ spell system smoke army frame vacant trick jacket anchor gasp acoustic supply de
solar lab option erosion unit example convince viable soft smart smile spoon range card gentle miracle latin they verify want reject side cheese panther solar lab option erosion unit example convince viable soft smart smile spoon range card gentle miracle latin they verify want reject side cheese panther
$ seedxor -c 'spell system smoke army frame vacant trick jacket anchor gasp acoustic supply deputy portion butter similar trend scorpion cause fish outer armor process faint' 'solar lab option erosion unit example convince viable soft smart smile spoon range card gentle miracle latin they verify want reject side cheese panther' $ seedxor -c 'spell system smoke army frame vacant trick jacket anchor gasp acoustic supply deputy portion butter similar trend scorpion cause fish outer armor process faint' 'solar lab option erosion unit example convince viable soft smart smile spoon range card gentle miracle latin they verify want reject side cheese panther'
butter patch first doll raise safe side lounge shiver protect solid area melody member lazy easily nice canvas stomach pattern claim slot million stomach butter patch first doll raise safe side lounge shiver protect solid area melody member lazy easily nice canvas stomach pattern claim slot million stomach
$ seedxor -u 'affair mutual spare' 'smooth mushroom scale' 'include neck grab' 'fly maze obtain'
# total permutations: 24
include neck grab smooth mushroom scale fly maze obtain affair mutual spare
# good: 1 bad: 23 total: 24
$ seedxor -u 'squirrel tray cheese' 'seek enhance oval' 'expect sense fish' 'total salad page'
# total permutations: 24
squirrel tray cheese seek enhance oval expect sense fish total salad page
expect sense fish squirrel tray cheese seek enhance oval total salad page
# good: 2 bad: 22 total: 24
``` ```
## Library Example ## Library Example

View File

@ -37,11 +37,10 @@
//! ``` //! ```
//! //!
pub use bip39::{Error, Language}; pub use bip39::{Error, Language};
use std::fmt::Display;
use std::ops::{Deref, DerefMut};
use std::{ use std::{
fmt, fmt,
ops::{BitXor, BitXorAssign}, fmt::Display,
ops::{BitXor, BitXorAssign, Deref, DerefMut},
str::FromStr, str::FromStr,
}; };
@ -149,11 +148,18 @@ impl Mnemonic {
pub fn to_short_string(&self) -> String { pub fn to_short_string(&self) -> String {
let mut ret = self.word_iter().fold(String::new(), |mut s, w| { let mut ret = self.word_iter().fold(String::new(), |mut s, w| {
w.chars().take(4).for_each(|c| s.push(c)); if w.len() == 3 {
s.push(' '); s.push_str(w);
s.push_str(" ");
} else {
w.chars().take(4).for_each(|c| s.push(c));
s.push(' ');
}
s s
}); });
ret.pop(); while ret.chars().last() == Some(' ') {
ret.pop();
}
ret ret
} }
@ -401,7 +407,7 @@ mod tests {
let short_string = seed.to_short_string(); let short_string = seed.to_short_string();
assert_eq!( assert_eq!(
"sile toe meat poss chai blos wait occu this wort opti boy", "sile toe meat poss chai blos wait occu this wort opti boy",
short_string short_string
); );
//assert_eq!(Language::English, bip39::Mnemonic::language_of(&short_string).unwrap()); //assert_eq!(Language::English, bip39::Mnemonic::language_of(&short_string).unwrap());
@ -414,7 +420,7 @@ mod tests {
let short_string = seed.to_short_string(); let short_string = seed.to_short_string();
assert_eq!( assert_eq!(
"song vani mist nigh drin add modi lens aver cool evil ches", "song vani mist nigh drin add modi lens aver cool evil ches",
short_string short_string
); );
assert_eq!( assert_eq!(
@ -429,7 +435,7 @@ mod tests {
let short_string = seed.to_short_string(); let short_string = seed.to_short_string();
assert_eq!( assert_eq!(
"ramp exot reso icon sun addi equi sand leis spar swin toas", "ramp exot reso icon sun addi equi sand leis spar swin toas",
short_string short_string
); );
assert_eq!( assert_eq!(

View File

@ -1,6 +1,5 @@
use seedxor::{Language, Mnemonic, SeedXor}; use seedxor::{expand_words, Language, Mnemonic, SeedXor};
use std::process::ExitCode; use std::{process::ExitCode, str::FromStr};
use std::str::FromStr;
pub struct Args { pub struct Args {
args: Vec<String>, args: Vec<String>,
@ -74,17 +73,18 @@ const WORD_COUNT: usize = 24;
fn help(success: bool) -> ExitCode { fn help(success: bool) -> ExitCode {
println!( println!(
r###"usage: seedxor [options...] r###"usage: seedxor [options...]
-h, --help Display this help -h, --help Display this help
-s, --split <seed> Split seed into num-seeds -s, --split <seed> Split seed into num-seeds
-n, --num-seeds <num> Number of seeds to split into or generate -n, --num-seeds <num> Number of seeds to split into or generate
default {NUM_SEEDS} default {NUM_SEEDS}
-y, --no-validate Do not validate a split can be successfully recombined, useful for -y, --no-validate Do not validate a split can be successfully recombined, useful for
non-bip39 seeds, like ethereum non-bip39 seeds, like ethereum
-g, --generate Generate num-seeds -g, --generate Generate num-seeds
-w, --word-count <num> Number of words to generate in the seed -w, --word-count <num> Number of words to generate in the seed
default {WORD_COUNT} default {WORD_COUNT}
-c, --combine <seeds...> Combine seeds into one seed -c, --combine <seeds...> Combine seeds into one seed
-r, --short Display only first 4 letters of seed words -r, --short Display only first 4 letters of seed words
-u, --unscramble <seed-parts...> Unscramble seed words in random order to valid seeds
"### "###
); );
if success { if success {
@ -157,8 +157,39 @@ fn main() -> ExitCode {
.collect(); .collect();
let seed = Mnemonic::xor_all(&parts).unwrap(); let seed = Mnemonic::xor_all(&parts).unwrap();
println!("{}", seed.to_display_string(short)); println!("{}", seed.to_display_string(short));
} else if args.flags(&["-u", "--unscramble"]) {
let remaining = args.remaining();
if remaining.is_empty() {
println!("error: --unscramble needs > 0 arguments");
return help(false);
}
let mut parts: Vec<String> = remaining
.into_iter()
.map(|s| expand_words(&s).expect("invalid bip39 seed words"))
.collect();
let total: u128 = (1..=parts.len() as u128).product();
eprintln!("# total permutations: {total}");
if total > u64::MAX as u128 {
println!("total too large, will never finish, aborting");
return ExitCode::FAILURE;
}
let mut heap = permutohedron::Heap::new(&mut parts);
let mut good = 0u64;
while let Some(words) = heap.next_permutation() {
let words = words.join(" ");
if let Ok(mnemonic) = Mnemonic::from_str(&words) {
if short {
println!("{}", mnemonic.to_short_string());
} else {
println!("{words}");
}
good += 1;
}
}
let bad = total - good as u128;
eprintln!("# good: {good} bad: {bad} total: {total}");
} else { } else {
println!("error: need one of -s/-g/-c"); println!("error: need one of -s/-g/-c/-u");
return help(false); return help(false);
} }
ExitCode::SUCCESS ExitCode::SUCCESS