Skip to content

Commit d849978

Browse files
Special handling of threshold=1 does not reduce number of shares now
1 parent 0a491bb commit d849978

File tree

5 files changed

+263
-204
lines changed

5 files changed

+263
-204
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,6 @@ structopt = "0.3.2"
2727
tiny-bip39 = "0.6.2"
2828

2929
[dev-dependencies]
30+
31+
[patch.crates-io]
32+
sssmc39 = { git = "https://github.com/wigy-opensource-developer/rust-sssmc39", branch = "fix/group_threshold_one" }

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# slip39-rust
22

3+
![Rust compilation results](https://github.com/Internet-of-People/slip39-rust/workflows/Rust/badge.svg)
4+
35
[SLIP-0039](https://github.com/satoshilabs/slips/blob/master/slip-0039.md) compatible secret sharing tool
46

57
## Table of Contents <!-- omit in toc -->

src/main.rs

Lines changed: 149 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,157 +1,178 @@
1-
use bip39::{Mnemonic as Bip39Mnemonic, Language};
1+
use bip39::{Language, Mnemonic as Bip39Mnemonic};
22
use failure::{format_err, Fallible};
3-
use structopt::StructOpt;
43
use regex::Regex;
4+
use structopt::StructOpt;
55

66
mod master_secret;
77
mod slip39;
88

99
pub use master_secret::MasterSecret;
10-
pub use slip39::{Slip39, ShareInspector};
11-
use sssmc39::{Share, combine_mnemonics};
10+
pub use slip39::{ShareInspector, Slip39};
11+
use sssmc39::{combine_mnemonics, Share};
1212

1313
#[derive(Debug, StructOpt)]
14-
#[structopt(rename_all="kebab")]
14+
#[structopt(rename_all = "kebab")]
1515
enum Options {
16-
/// Generate master secret and split it to parts
17-
///
18-
/// SLIP-0039 defines a 2-level split: The master secret is split into group secrets and then
19-
/// those are split further into member secrets. You can define the required and total number of
20-
/// members in each group, and also define how many groups are required to restore the master secret.
21-
Generate {
22-
#[structopt(short, long, default_value = "256")]
23-
/// Length of the master secret in bits
24-
bits: u16,
25-
#[structopt(flatten)]
26-
split_options: SplitOptions,
27-
},
28-
/// Split a master secret encoded as a BIP-0039 mnemonic into parts
29-
///
30-
/// SLIP-0039 defines a 2-level split: The master secret is split into group secrets and then
31-
/// those are split further into member secrets. You can define the required and total number of
32-
/// members in each group, and also define how many groups are required to restore the master secret.
33-
Split {
34-
#[structopt(short, long, env = "SLIP39_ENTROPY", hide_env_values = true)]
35-
/// BIP-0039 mnemonic to split. Use double quotes around it, but preferably provide it
36-
/// through an environment variable to avoid leaking it to other processes on this machine
37-
entropy: String,
38-
#[structopt(long)]
39-
/// If provided, mnemonic needs to be the master secret is decoded as hexadecimal, not as a
40-
/// BIP39 mnemonic
41-
hex: bool,
42-
#[structopt(flatten)]
43-
split_options: SplitOptions,
44-
},
45-
Combine {
46-
#[structopt(flatten)]
47-
password: Password,
48-
#[structopt(long)]
49-
/// If provided, mnemonic needs to be the master secret is decoded as hexadecimal, not as a
50-
/// BIP39 mnemonic
51-
hex: bool,
52-
#[structopt(short, long("mnemonic"), required=true, number_of_values=1)]
53-
mnemonics: Vec<String>,
54-
},
55-
Inspect {
56-
#[structopt(short, long)]
57-
/// SLIP-0039 mnemonic to inspect. Use double quotes around it, but preferably provide it
58-
/// through an environment variable to avoid leaking it to other processes on this machine
59-
mnemonic: String,
60-
}
16+
/// Generate master secret and split it to parts
17+
///
18+
/// SLIP-0039 defines a 2-level split: The master secret is split into group secrets and then
19+
/// those are split further into member secrets. You can define the required and total number of
20+
/// members in each group, and also define how many groups are required to restore the master secret.
21+
Generate {
22+
#[structopt(short, long, default_value = "256")]
23+
/// Length of the master secret in bits
24+
bits: u16,
25+
#[structopt(flatten)]
26+
split_options: SplitOptions,
27+
},
28+
/// Split a master secret encoded as a BIP-0039 mnemonic into parts
29+
///
30+
/// SLIP-0039 defines a 2-level split: The master secret is split into group secrets and then
31+
/// those are split further into member secrets. You can define the required and total number of
32+
/// members in each group, and also define how many groups are required to restore the master secret.
33+
Split {
34+
#[structopt(short, long, env = "SLIP39_ENTROPY", hide_env_values = true)]
35+
/// BIP-0039 mnemonic to split. Use double quotes around it, but preferably provide it
36+
/// through an environment variable to avoid leaking it to other processes on this machine
37+
entropy: String,
38+
#[structopt(long)]
39+
/// If provided, mnemonic needs to be the master secret is decoded as hexadecimal, not as a
40+
/// BIP39 mnemonic
41+
hex: bool,
42+
#[structopt(flatten)]
43+
split_options: SplitOptions,
44+
},
45+
Combine {
46+
#[structopt(flatten)]
47+
password: Password,
48+
#[structopt(long)]
49+
/// If provided, mnemonic needs to be the master secret is decoded as hexadecimal, not as a
50+
/// BIP39 mnemonic
51+
hex: bool,
52+
#[structopt(short, long("mnemonic"), required = true, number_of_values = 1)]
53+
mnemonics: Vec<String>,
54+
},
55+
Inspect {
56+
#[structopt(short, long)]
57+
/// SLIP-0039 mnemonic to inspect. Use double quotes around it, but preferably provide it
58+
/// through an environment variable to avoid leaking it to other processes on this machine
59+
mnemonic: String,
60+
},
6161
}
6262

6363
#[derive(Debug, StructOpt)]
6464
struct Password {
65-
#[structopt(short, long, env = "SLIP39_PASSWORD", hide_env_values = true)]
66-
/// Password that is required in addition to the mnemonics to restore the master secret. Preferably
67-
/// provide it through an environment variable to avoid leaking it to other processes.
68-
password: String,
65+
#[structopt(short, long, env = "SLIP39_PASSWORD", hide_env_values = true)]
66+
/// Password that is required in addition to the mnemonics to restore the master secret. Preferably
67+
/// provide it through an environment variable to avoid leaking it to other processes.
68+
password: String,
6969
}
7070

71-
fn parse_group_spec(src: &str) -> Fallible<(u8,u8)> {
72-
let pattern = Regex::new(r"^(?P<group_threshold>\d+)(-?of-?|:|/)(?P<group_members>\d+)$")?;
73-
let captures = pattern.captures(src).ok_or_else(|| format_err!("Group specification '{}' is invalid. Write something like '5of8'", src))?;
74-
let group_threshold: u8 = captures["group_threshold"].parse()?;
75-
let group_members: u8 = captures["group_members"].parse()?;
76-
Ok((group_threshold, group_members))
71+
fn parse_group_spec(src: &str) -> Fallible<(u8, u8)> {
72+
let pattern = Regex::new(r"^(?P<group_threshold>\d+)(-?of-?|:|/)(?P<group_members>\d+)$")?;
73+
let captures = pattern.captures(src).ok_or_else(|| {
74+
format_err!(
75+
"Group specification '{}' is invalid. Write something like '5of8'",
76+
src
77+
)
78+
})?;
79+
let group_threshold: u8 = captures["group_threshold"].parse()?;
80+
let group_members: u8 = captures["group_members"].parse()?;
81+
Ok((group_threshold, group_members))
7782
}
7883

7984
#[derive(Debug, StructOpt)]
8085
struct SplitOptions {
81-
#[structopt(flatten)]
82-
password: Password,
83-
#[structopt(short, long)]
84-
/// Number of groups required for restoring the master secret (by default all groups are required)
85-
required_groups: Option<u8>,
86-
// TODO The sssmc39 crate does not handle well iteration_exponent values other than 0, so we disabled this parameter for now
87-
// #[structopt(short, long, default_value = "0")]
88-
// /// The higher this number, the safer and slower the splitting and combining is
89-
// iterations: u8,
90-
#[structopt(short, long("group"), parse(try_from_str = parse_group_spec), required=true, number_of_values=1)]
91-
/// Specify required and total number of members for each group (e.g. 8-of-15).
92-
/// Multiple groups need multiple occurences of this option.
93-
groups: Vec<(u8,u8)>
86+
#[structopt(flatten)]
87+
password: Password,
88+
#[structopt(short, long)]
89+
/// Number of groups required for restoring the master secret (by default all groups are required)
90+
required_groups: Option<u8>,
91+
#[structopt(short, long, default_value = "0")]
92+
/// The higher this number, the safer and slower the splitting and combining is
93+
iterations: u8,
94+
#[structopt(short, long("group"), parse(try_from_str = parse_group_spec), required=true, number_of_values=1)]
95+
/// Specify required and total number of members for each group (e.g. 8-of-15).
96+
/// Multiple groups need multiple occurences of this option.
97+
groups: Vec<(u8, u8)>,
9498
}
9599

96100
impl SplitOptions {
97-
fn split(&self, master_secret: &MasterSecret) -> Fallible<Slip39> {
98-
let required_groups = self.required_groups
99-
.unwrap_or_else(|| self.groups.len() as u8);
100-
let slip39 = Slip39::new(
101-
required_groups,
102-
&self.groups,
103-
&master_secret,
104-
&self.password.password,
105-
0 // self.iterations
106-
)?;
107-
Ok(slip39)
108-
}
101+
fn split(&self, master_secret: &MasterSecret) -> Fallible<Slip39> {
102+
let required_groups = self
103+
.required_groups
104+
.unwrap_or_else(|| self.groups.len() as u8);
105+
let slip39 = Slip39::new(
106+
required_groups,
107+
&self.groups,
108+
&master_secret,
109+
&self.password.password,
110+
self.iterations,
111+
)?;
112+
Ok(slip39)
113+
}
109114
}
110115

111116
fn print_split(options: &SplitOptions, master_secret: &MasterSecret) -> Fallible<()> {
112-
let slip39 = options.split(&master_secret)?;
113-
println!("{}", serde_json::to_string_pretty(&slip39)?);
114-
Ok(())
117+
let slip39 = options.split(&master_secret)?;
118+
println!("{}", serde_json::to_string_pretty(&slip39)?);
119+
Ok(())
115120
}
116121

117122
fn main() -> Fallible<()> {
118-
use Options::*;
119-
let options = Options::from_args();
120-
match options {
121-
Generate { bits, split_options } => {
122-
let master_secret = MasterSecret::new(bits)?;
123-
print_split(&split_options, &master_secret)?;
124-
}
125-
Split { entropy, hex, split_options } => {
126-
let master_secret = if hex {
127-
let bytes = hex::decode(entropy)?;
128-
MasterSecret::from(&bytes)
129-
} else {
130-
let bip39 = Bip39Mnemonic::from_phrase(entropy, Language::English)?;
131-
MasterSecret::from(bip39.entropy())
132-
};
133-
print_split(&split_options, &master_secret)?;
134-
}
135-
Combine { password, hex, mnemonics} => {
136-
let mnemonics = mnemonics.iter()
137-
.map(|m| {
138-
m.split_ascii_whitespace().map(str::to_owned).collect()
139-
})
140-
.collect();
141-
let master_secret = combine_mnemonics(&mnemonics, &password.password)?;
142-
let output = if hex {
143-
hex::encode(master_secret)
144-
} else {
145-
let bip39 = bip39::Mnemonic::from_entropy(&master_secret, Language::English)?;
146-
bip39.into_phrase()
147-
};
148-
println!("{}", output);
149-
}
150-
Inspect { mnemonic } => {
151-
let words = mnemonic.split_ascii_whitespace().map(str::to_owned).collect();
152-
let share = Share::from_mnemonic(&words)?;
153-
println!("{}", serde_json::to_string_pretty(&ShareInspector::from(&share))?);
154-
}
155-
}
156-
Ok(())
157-
}
123+
use Options::*;
124+
let options = Options::from_args();
125+
match options {
126+
Generate {
127+
bits,
128+
split_options,
129+
} => {
130+
let master_secret = MasterSecret::new(bits)?;
131+
print_split(&split_options, &master_secret)?;
132+
}
133+
Split {
134+
entropy,
135+
hex,
136+
split_options,
137+
} => {
138+
let master_secret = if hex {
139+
let bytes = hex::decode(entropy)?;
140+
MasterSecret::from(&bytes)
141+
} else {
142+
let bip39 = Bip39Mnemonic::from_phrase(entropy, Language::English)?;
143+
MasterSecret::from(bip39.entropy())
144+
};
145+
print_split(&split_options, &master_secret)?;
146+
}
147+
Combine {
148+
password,
149+
hex,
150+
mnemonics,
151+
} => {
152+
let mnemonics = mnemonics
153+
.iter()
154+
.map(|m| m.split_ascii_whitespace().map(str::to_owned).collect())
155+
.collect::<Vec<_>>();
156+
let master_secret = combine_mnemonics(&mnemonics, &password.password)?;
157+
let output = if hex {
158+
hex::encode(master_secret)
159+
} else {
160+
let bip39 = bip39::Mnemonic::from_entropy(&master_secret, Language::English)?;
161+
bip39.into_phrase()
162+
};
163+
println!("{}", output);
164+
}
165+
Inspect { mnemonic } => {
166+
let words = mnemonic
167+
.split_ascii_whitespace()
168+
.map(str::to_owned)
169+
.collect::<Vec<_>>();
170+
let share = Share::from_mnemonic(&words)?;
171+
println!(
172+
"{}",
173+
serde_json::to_string_pretty(&ShareInspector::from(&share))?
174+
);
175+
}
176+
}
177+
Ok(())
178+
}

src/master_secret.rs

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,37 @@ use sssmc39::*;
44
pub struct MasterSecret(Vec<u8>);
55

66
impl MasterSecret {
7-
pub fn new(strength_bits: u16) -> Result<Self, Error> {
8-
use rand::{thread_rng, Rng};
9-
let proto_share = Share::new()?; // shamir::share::ShareConfig is not exported
10-
if strength_bits < proto_share.config.min_strength_bits {
11-
return Err(ErrorKind::Value(format!(
12-
"The requested strength of the master secret({} bits) must be at least {} bits.",
13-
strength_bits, proto_share.config.min_strength_bits,
14-
)))?;
15-
}
16-
if strength_bits % 16 != 0 {
17-
return Err(ErrorKind::Value(format!(
7+
pub fn new(strength_bits: u16) -> Result<Self, Error> {
8+
use rand::{thread_rng, Rng};
9+
let proto_share = Share::new()?; // shamir::share::ShareConfig is not exported
10+
if strength_bits < proto_share.config.min_strength_bits {
11+
return Err(ErrorKind::Value(format!(
12+
"The requested strength of the master secret({} bits) must be at least {} bits.",
13+
strength_bits, proto_share.config.min_strength_bits,
14+
)))?;
15+
}
16+
if strength_bits % 16 != 0 {
17+
return Err(ErrorKind::Value(format!(
1818
"The requested strength of the master secret({} bits) must be a multiple of 16 bits.",
1919
strength_bits,
2020
)))?;
21-
}
22-
let mut v = vec![];
23-
for _ in 0..strength_bits as usize / 8 {
24-
v.push(thread_rng().gen());
25-
}
26-
Ok(Self(v))
27-
}
21+
}
22+
let mut v = vec![];
23+
for _ in 0..strength_bits as usize / 8 {
24+
v.push(thread_rng().gen());
25+
}
26+
Ok(Self(v))
27+
}
2828
}
2929

3030
impl<T: AsRef<[u8]>> From<T> for MasterSecret {
31-
fn from(value: T) -> Self {
32-
Self( value.as_ref().to_owned() )
33-
}
31+
fn from(value: T) -> Self {
32+
Self(value.as_ref().to_owned())
33+
}
3434
}
3535

3636
impl AsRef<Vec<u8>> for MasterSecret {
37-
fn as_ref(&self) -> &Vec<u8> {
38-
&self.0
39-
}
37+
fn as_ref(&self) -> &Vec<u8> {
38+
&self.0
39+
}
4040
}
41-

0 commit comments

Comments
 (0)