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"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
[[package]]
name = "permutohedron"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b687ff7b5da449d39e418ad391e5e08da53ec334903ddbb921db208908fc372c"
[[package]]
name = "seedxor"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"bip39",
"getrandom",
"permutohedron",
]
[[package]]

View File

@ -1,6 +1,6 @@
[package]
name = "seedxor"
version = "1.0.0"
version = "1.1.0"
edition = "2021"
authors = ["moparisthebest <admin@moparisthebest.com>", "KaiWitt <kaiwitt@protonmail.com>"]
description = "XOR bip39 mnemonics."
@ -22,3 +22,4 @@ include = [
[dependencies]
bip39 = { version = "2.0", 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)
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.
For this the xored value takes on the entropy surplus of the longer seed.
@ -23,6 +23,7 @@ usage: seedxor [options...]
default 24
-c, --combine <seeds...> Combine seeds into one seed
-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
$ 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
$ 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

View File

@ -37,11 +37,10 @@
//! ```
//!
pub use bip39::{Error, Language};
use std::fmt::Display;
use std::ops::{Deref, DerefMut};
use std::{
fmt,
ops::{BitXor, BitXorAssign},
fmt::Display,
ops::{BitXor, BitXorAssign, Deref, DerefMut},
str::FromStr,
};
@ -149,11 +148,18 @@ impl Mnemonic {
pub fn to_short_string(&self) -> String {
let mut ret = self.word_iter().fold(String::new(), |mut s, w| {
if w.len() == 3 {
s.push_str(w);
s.push_str(" ");
} else {
w.chars().take(4).for_each(|c| s.push(c));
s.push(' ');
}
s
});
while ret.chars().last() == Some(' ') {
ret.pop();
}
ret
}

View File

@ -1,6 +1,5 @@
use seedxor::{Language, Mnemonic, SeedXor};
use std::process::ExitCode;
use std::str::FromStr;
use seedxor::{expand_words, Language, Mnemonic, SeedXor};
use std::{process::ExitCode, str::FromStr};
pub struct Args {
args: Vec<String>,
@ -85,6 +84,7 @@ fn help(success: bool) -> ExitCode {
default {WORD_COUNT}
-c, --combine <seeds...> Combine seeds into one seed
-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 {
@ -157,8 +157,39 @@ fn main() -> ExitCode {
.collect();
let seed = Mnemonic::xor_all(&parts).unwrap();
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!("error: need one of -s/-g/-c");
println!("{words}");
}
good += 1;
}
}
let bad = total - good as u128;
eprintln!("# good: {good} bad: {bad} total: {total}");
} else {
println!("error: need one of -s/-g/-c/-u");
return help(false);
}
ExitCode::SUCCESS