Skip to content

Commit

Permalink
Special handling of threshold=1 does not reduce number of shares now
Browse files Browse the repository at this point in the history
  • Loading branch information
wigy-opensource-developer committed Jan 30, 2020
1 parent 0a491bb commit d849978
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 204 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ structopt = "0.3.2"
tiny-bip39 = "0.6.2"

[dev-dependencies]

[patch.crates-io]
sssmc39 = { git = "https://github.com/wigy-opensource-developer/rust-sssmc39", branch = "fix/group_threshold_one" }
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# slip39-rust

![Rust compilation results](https://github.com/Internet-of-People/slip39-rust/workflows/Rust/badge.svg)

[SLIP-0039](https://github.com/satoshilabs/slips/blob/master/slip-0039.md) compatible secret sharing tool

## Table of Contents <!-- omit in toc -->
Expand Down
277 changes: 149 additions & 128 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,157 +1,178 @@
use bip39::{Mnemonic as Bip39Mnemonic, Language};
use bip39::{Language, Mnemonic as Bip39Mnemonic};
use failure::{format_err, Fallible};
use structopt::StructOpt;
use regex::Regex;
use structopt::StructOpt;

mod master_secret;
mod slip39;

pub use master_secret::MasterSecret;
pub use slip39::{Slip39, ShareInspector};
use sssmc39::{Share, combine_mnemonics};
pub use slip39::{ShareInspector, Slip39};
use sssmc39::{combine_mnemonics, Share};

#[derive(Debug, StructOpt)]
#[structopt(rename_all="kebab")]
#[structopt(rename_all = "kebab")]
enum Options {
/// Generate master secret and split it to parts
///
/// SLIP-0039 defines a 2-level split: The master secret is split into group secrets and then
/// those are split further into member secrets. You can define the required and total number of
/// members in each group, and also define how many groups are required to restore the master secret.
Generate {
#[structopt(short, long, default_value = "256")]
/// Length of the master secret in bits
bits: u16,
#[structopt(flatten)]
split_options: SplitOptions,
},
/// Split a master secret encoded as a BIP-0039 mnemonic into parts
///
/// SLIP-0039 defines a 2-level split: The master secret is split into group secrets and then
/// those are split further into member secrets. You can define the required and total number of
/// members in each group, and also define how many groups are required to restore the master secret.
Split {
#[structopt(short, long, env = "SLIP39_ENTROPY", hide_env_values = true)]
/// BIP-0039 mnemonic to split. Use double quotes around it, but preferably provide it
/// through an environment variable to avoid leaking it to other processes on this machine
entropy: String,
#[structopt(long)]
/// If provided, mnemonic needs to be the master secret is decoded as hexadecimal, not as a
/// BIP39 mnemonic
hex: bool,
#[structopt(flatten)]
split_options: SplitOptions,
},
Combine {
#[structopt(flatten)]
password: Password,
#[structopt(long)]
/// If provided, mnemonic needs to be the master secret is decoded as hexadecimal, not as a
/// BIP39 mnemonic
hex: bool,
#[structopt(short, long("mnemonic"), required=true, number_of_values=1)]
mnemonics: Vec<String>,
},
Inspect {
#[structopt(short, long)]
/// SLIP-0039 mnemonic to inspect. Use double quotes around it, but preferably provide it
/// through an environment variable to avoid leaking it to other processes on this machine
mnemonic: String,
}
/// Generate master secret and split it to parts
///
/// SLIP-0039 defines a 2-level split: The master secret is split into group secrets and then
/// those are split further into member secrets. You can define the required and total number of
/// members in each group, and also define how many groups are required to restore the master secret.
Generate {
#[structopt(short, long, default_value = "256")]
/// Length of the master secret in bits
bits: u16,
#[structopt(flatten)]
split_options: SplitOptions,
},
/// Split a master secret encoded as a BIP-0039 mnemonic into parts
///
/// SLIP-0039 defines a 2-level split: The master secret is split into group secrets and then
/// those are split further into member secrets. You can define the required and total number of
/// members in each group, and also define how many groups are required to restore the master secret.
Split {
#[structopt(short, long, env = "SLIP39_ENTROPY", hide_env_values = true)]
/// BIP-0039 mnemonic to split. Use double quotes around it, but preferably provide it
/// through an environment variable to avoid leaking it to other processes on this machine
entropy: String,
#[structopt(long)]
/// If provided, mnemonic needs to be the master secret is decoded as hexadecimal, not as a
/// BIP39 mnemonic
hex: bool,
#[structopt(flatten)]
split_options: SplitOptions,
},
Combine {
#[structopt(flatten)]
password: Password,
#[structopt(long)]
/// If provided, mnemonic needs to be the master secret is decoded as hexadecimal, not as a
/// BIP39 mnemonic
hex: bool,
#[structopt(short, long("mnemonic"), required = true, number_of_values = 1)]
mnemonics: Vec<String>,
},
Inspect {
#[structopt(short, long)]
/// SLIP-0039 mnemonic to inspect. Use double quotes around it, but preferably provide it
/// through an environment variable to avoid leaking it to other processes on this machine
mnemonic: String,
},
}

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

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

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

impl SplitOptions {
fn split(&self, master_secret: &MasterSecret) -> Fallible<Slip39> {
let required_groups = self.required_groups
.unwrap_or_else(|| self.groups.len() as u8);
let slip39 = Slip39::new(
required_groups,
&self.groups,
&master_secret,
&self.password.password,
0 // self.iterations
)?;
Ok(slip39)
}
fn split(&self, master_secret: &MasterSecret) -> Fallible<Slip39> {
let required_groups = self
.required_groups
.unwrap_or_else(|| self.groups.len() as u8);
let slip39 = Slip39::new(
required_groups,
&self.groups,
&master_secret,
&self.password.password,
self.iterations,
)?;
Ok(slip39)
}
}

fn print_split(options: &SplitOptions, master_secret: &MasterSecret) -> Fallible<()> {
let slip39 = options.split(&master_secret)?;
println!("{}", serde_json::to_string_pretty(&slip39)?);
Ok(())
let slip39 = options.split(&master_secret)?;
println!("{}", serde_json::to_string_pretty(&slip39)?);
Ok(())
}

fn main() -> Fallible<()> {
use Options::*;
let options = Options::from_args();
match options {
Generate { bits, split_options } => {
let master_secret = MasterSecret::new(bits)?;
print_split(&split_options, &master_secret)?;
}
Split { entropy, hex, split_options } => {
let master_secret = if hex {
let bytes = hex::decode(entropy)?;
MasterSecret::from(&bytes)
} else {
let bip39 = Bip39Mnemonic::from_phrase(entropy, Language::English)?;
MasterSecret::from(bip39.entropy())
};
print_split(&split_options, &master_secret)?;
}
Combine { password, hex, mnemonics} => {
let mnemonics = mnemonics.iter()
.map(|m| {
m.split_ascii_whitespace().map(str::to_owned).collect()
})
.collect();
let master_secret = combine_mnemonics(&mnemonics, &password.password)?;
let output = if hex {
hex::encode(master_secret)
} else {
let bip39 = bip39::Mnemonic::from_entropy(&master_secret, Language::English)?;
bip39.into_phrase()
};
println!("{}", output);
}
Inspect { mnemonic } => {
let words = mnemonic.split_ascii_whitespace().map(str::to_owned).collect();
let share = Share::from_mnemonic(&words)?;
println!("{}", serde_json::to_string_pretty(&ShareInspector::from(&share))?);
}
}
Ok(())
}
use Options::*;
let options = Options::from_args();
match options {
Generate {
bits,
split_options,
} => {
let master_secret = MasterSecret::new(bits)?;
print_split(&split_options, &master_secret)?;
}
Split {
entropy,
hex,
split_options,
} => {
let master_secret = if hex {
let bytes = hex::decode(entropy)?;
MasterSecret::from(&bytes)
} else {
let bip39 = Bip39Mnemonic::from_phrase(entropy, Language::English)?;
MasterSecret::from(bip39.entropy())
};
print_split(&split_options, &master_secret)?;
}
Combine {
password,
hex,
mnemonics,
} => {
let mnemonics = mnemonics
.iter()
.map(|m| m.split_ascii_whitespace().map(str::to_owned).collect())
.collect::<Vec<_>>();
let master_secret = combine_mnemonics(&mnemonics, &password.password)?;
let output = if hex {
hex::encode(master_secret)
} else {
let bip39 = bip39::Mnemonic::from_entropy(&master_secret, Language::English)?;
bip39.into_phrase()
};
println!("{}", output);
}
Inspect { mnemonic } => {
let words = mnemonic
.split_ascii_whitespace()
.map(str::to_owned)
.collect::<Vec<_>>();
let share = Share::from_mnemonic(&words)?;
println!(
"{}",
serde_json::to_string_pretty(&ShareInspector::from(&share))?
);
}
}
Ok(())
}
49 changes: 24 additions & 25 deletions src/master_secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,37 @@ use sssmc39::*;
pub struct MasterSecret(Vec<u8>);

impl MasterSecret {
pub fn new(strength_bits: u16) -> Result<Self, Error> {
use rand::{thread_rng, Rng};
let proto_share = Share::new()?; // shamir::share::ShareConfig is not exported
if strength_bits < proto_share.config.min_strength_bits {
return Err(ErrorKind::Value(format!(
"The requested strength of the master secret({} bits) must be at least {} bits.",
strength_bits, proto_share.config.min_strength_bits,
)))?;
}
if strength_bits % 16 != 0 {
return Err(ErrorKind::Value(format!(
pub fn new(strength_bits: u16) -> Result<Self, Error> {
use rand::{thread_rng, Rng};
let proto_share = Share::new()?; // shamir::share::ShareConfig is not exported
if strength_bits < proto_share.config.min_strength_bits {
return Err(ErrorKind::Value(format!(
"The requested strength of the master secret({} bits) must be at least {} bits.",
strength_bits, proto_share.config.min_strength_bits,
)))?;
}
if strength_bits % 16 != 0 {
return Err(ErrorKind::Value(format!(
"The requested strength of the master secret({} bits) must be a multiple of 16 bits.",
strength_bits,
)))?;
}
let mut v = vec![];
for _ in 0..strength_bits as usize / 8 {
v.push(thread_rng().gen());
}
Ok(Self(v))
}
}
let mut v = vec![];
for _ in 0..strength_bits as usize / 8 {
v.push(thread_rng().gen());
}
Ok(Self(v))
}
}

impl<T: AsRef<[u8]>> From<T> for MasterSecret {
fn from(value: T) -> Self {
Self( value.as_ref().to_owned() )
}
fn from(value: T) -> Self {
Self(value.as_ref().to_owned())
}
}

impl AsRef<Vec<u8>> for MasterSecret {
fn as_ref(&self) -> &Vec<u8> {
&self.0
}
fn as_ref(&self) -> &Vec<u8> {
&self.0
}
}

Loading

0 comments on commit d849978

Please sign in to comment.