commit d2f5ec25eefba3b7c9fd43b1f02b564339d361f2 Author: TQ Hirsch Date: Fri Feb 8 19:01:03 2019 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a7c9b8e --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/target +**/*.rs.bk +Cargo.lock +local +.idea +*.iml diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c83c5f6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "rssss" +version = "0.1.0" +authors = ["TQ Hirsch "] +edition = "2018" + +[dependencies] +num-bigint = {version = "0.2.2", features = ["rand", "serde"]} +lazy_static = "1.2.0" +structopt = "0.2.14" +failure = "0.1.5" +crc = "1.8.1" +byteorder = "1.3.1" +num-traits = "0.2.6" +rand = "0.6.5" +serde = "1.0.87" +serde_derive = "1.0.87" +serde_cbor = "0.9.0" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3d6ae2b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +use num_bigint::BigUint; +use lazy_static::lazy_static; + +pub mod primes; +pub mod poly; +pub mod s4; + + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1f8dcc9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,187 @@ +use structopt::StructOpt; +use std::path::PathBuf; +use failure::Error; +use std::io; + + + +/// Rust implementation of Shamir Secret Sharing +/// +/// Securely divides a small amount of data into shares, some subset of which are required +/// to reproduce the original data +/// +/// Usage: +/// +/// Generating shares: +/// +/// # Precompute data for secret sharing requiring 2 shares to reconsitute the data +/// +/// rssss gen-poly 2 poly +/// +/// # Generate a few shares +/// +/// rssss gen-share share-1 +/// +/// rssss gen-share share-2 +/// +/// rssss gen-share share-3 +/// +/// rssss gen-share share-4 +/// +/// # Recover the poly from two of those shares +/// +/// rssss solve share-2 share-4 >recovered-poly +/// +/// # Generate some more shares from that poly +/// +/// rssss gen-share share-5 +/// +/// # Get the secret back +/// +/// rssss extract recovered-secret +#[derive(StructOpt)] +enum Cmdline { + /// Generate a poly from a secret + #[structopt(name = "gen-poly")] + GenPoly(GenPoly), + + /// Generate a share from a poly + #[structopt(name = "gen-share")] + GenShare(GenShare), + + /// Solve a set of shares to get the poly back + #[structopt(name = "solve")] + Solve(Solve), + + /// Combine a set of shares to get the secret back + #[structopt(name = "combine")] + Combine(Combine), + + /// Extract a secret from a poly + #[structopt(name="extract")] + ExtractSecret(ExtractSecret), +} + +#[derive(StructOpt)] +struct GenPoly { + #[structopt(short="p", long="prime", default_value="p384", parse(try_from_str))] + prime: rssss::primes::Prime, + + min_shares: u32, +} + +#[derive(StructOpt)] +struct GenShare { +} + +#[derive(StructOpt)] +struct Solve { + #[structopt(short="p", long="prime", default_value="p384", parse(try_from_str))] + prime: rssss::primes::Prime, + #[structopt(parse(from_os_str))] + data: Vec, +} + +/// Extract a secret +#[derive(StructOpt)] +struct ExtractSecret { + #[structopt(short="p", long="prime", default_value="p384", parse(try_from_str))] + prime: rssss::primes::Prime, +} + +/// Combine a set of shares to find the secret +/// This is equivalent to solve followed by extract, but without the intermediate step +#[derive(StructOpt)] +struct Combine { +} + +fn main() { + let cmdline = Cmdline::from_args(); + let cmd: &Command = match cmdline { + Cmdline::GenPoly(ref cmd) => cmd, + Cmdline::GenShare(ref cmd) => cmd, + Cmdline::Solve(ref cmd) => cmd, + Cmdline::ExtractSecret(ref cmd) => cmd, + Cmdline::Combine(ref cmd) => cmd, + }; + + cmd.run(); +} + +trait Command { + fn run(&self) -> Result<(), Error>; +} + +impl Command for GenPoly { + fn run(&self) -> Result<(), Error> { + use std::io::Read; + use num_bigint::{RandBigInt, BigUint}; + + let mut rng = rand::rngs::JitterRng::new()?; + + let mut payload = vec![]; + io::stdin().read_to_end(&mut payload)?; + + let secret = rssss::s4::Secret::new(payload); + let mut poly = rssss::poly::Poly::zero(self.prime); + for i in 1..self.min_shares { + poly += rng.gen_biguint_below(self.prime.modulus()); + poly.mul_x(); + } + poly += &BigUint::from(secret); + + // export the poly + serde_cbor::to_writer(&mut io::stdout(), &poly)?; + + Ok(()) + } +} + +impl Command for GenShare { + fn run(&self) -> Result<(), Error> { + let mut poly: rssss::poly::Poly = serde_cbor::from_reader(io::stdin())?; + let mut rng = rand::rngs::JitterRng::new()?; + + let share = rssss::s4::Share::new(&mut rng, &poly); + share.write_to(io::stdout())?; + Ok(()) + } +} + +impl Command for Solve { + fn run(&self) -> Result<(), Error> { + use std::fs::File; + let shares = self.data.iter().map(|filename| { + Ok(File::open(&filename) + .and_then(|f| rssss::s4::Share::read_from(f, self.prime)) + .map_err(|err| Error::from(err) + .context(format!("Failed to read {:?}", filename)))?) + }).collect::, Error>>(); + + Ok(()) + } +} + +impl Command for ExtractSecret { + fn run(&self) -> Result<(), Error> { + use num_bigint::BigUint; + use num_traits::Zero; + use std::io::Write; + let mut poly: rssss::poly::Poly = serde_cbor::from_reader(io::stdin())?; + let secret = rssss::s4::Secret::from(poly.eval_at(&BigUint::zero())); + io::stdout().write_all(&secret[..])?; + Ok(()) + } +} + +impl Command for Combine { + fn run(&self) -> Result<(), Error> { + unimplemented!() + } +} + +// Impl + + + + diff --git a/src/poly.rs b/src/poly.rs new file mode 100644 index 0000000..46e9829 --- /dev/null +++ b/src/poly.rs @@ -0,0 +1,182 @@ +use std::io::{self, prelude::*}; +use std::ops::{Mul, Div, Add, AddAssign, DivAssign, Neg}; + +use num_bigint::BigUint; +use num_traits::{Zero, One}; +use serde_derive::{Serialize, Deserialize}; + +use crate::primes::Prime; + + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Poly { + // coeff[0] + coeff[1]*x + coeff[2] * x^2 + // i.e., $$\sum_i coeff_i x^i$$ + pub prime: Prime, + coeff: Vec, +} + +impl Poly { + pub fn zero(modulus: Prime) -> Self { + Poly { + prime: modulus, + coeff: vec![], + } + } + + pub fn x(modulus: Prime) -> Self { + Poly { + prime: modulus, + coeff: vec![BigUint::zero(), BigUint::one()], + } + } + + pub fn mul_x(&mut self) { + self.coeff.insert(0, BigUint::zero()); + + } + + pub fn eval_at(&self, point: &BigUint) -> BigUint { + let mut result: BigUint = BigUint::zero(); + for item in self.coeff.iter().rev() { + result *= point; + result += item; + result %= self.prime.modulus(); + } + result + } +} + +impl Add<&Poly> for Poly { + type Output = Poly; + + fn add(self, rhs: &Poly) -> Self::Output { + let mut res = self.clone(); + res += rhs; + res + } +} + +impl AddAssign<&Poly> for Poly { + fn add_assign(&mut self, rhs: &Poly) { + assert_eq!(self.prime, rhs.prime); + if self.coeff.len() < rhs.coeff.len() { + self.coeff.extend(::std::iter::repeat(BigUint::zero()).take(rhs.coeff.len() - self.coeff.len())); + } + for (a, b) in self.coeff.iter_mut().zip(rhs.coeff.iter()) { + *a += b; + *a %= self.prime.modulus(); + } + } +} + +impl Add<&BigUint> for Poly { + type Output = Poly; + + fn add(mut self, rhs: &BigUint) -> Self::Output { + self += rhs; + self + } +} + +impl AddAssign<&BigUint> for Poly { + fn add_assign(&mut self, rhs: &BigUint) { + if self.coeff.len() == 0 { + self.coeff.push(rhs % self.prime.modulus()) + } else { + self.coeff[0] += rhs; + self.coeff[0] %= self.prime.modulus() + } + } +} + +impl Add for Poly { + type Output = Poly; + + fn add(mut self, rhs: BigUint) -> Self::Output { + self += rhs; + self + } +} + +impl AddAssign for Poly { + fn add_assign(&mut self, rhs: BigUint) { + if self.coeff.len() == 0 { + self.coeff.push(rhs % self.prime.modulus()) + } else { + self.coeff[0] += rhs; + self.coeff[0] %= self.prime.modulus() + } + } +} + +impl Mul<&BigUint> for Poly { + type Output = Poly; + fn mul(mut self, rhs: &BigUint) -> Poly { + for a in self.coeff.iter_mut() { + *a *= rhs; + *a %= self.prime.modulus(); + } + self + } +} + +impl Mul<&BigUint> for &Poly { + type Output = Poly; + fn mul(mut self, rhs: &BigUint) -> Poly { + Poly { + prime: self.prime, + coeff: self.coeff.iter().map(|a| (a * rhs) % self.prime.modulus()).collect(), + } + } +} + +impl Mul<&Poly> for Poly { + type Output = Poly; + + fn mul(mut self, rhs: &Poly) -> Self::Output { + let mut result = Poly::zero(self.prime); + for factor in rhs.coeff.iter() { + result += &(&self * factor); + self.mul_x(); + } + result + } +} + +impl Mul<&Poly> for &Poly { + type Output = Poly; + + fn mul(self, rhs: &Poly) -> Self::Output { + self.clone() * rhs + } +} + +impl Div for Poly { + type Output = Poly; + + fn div(mut self, rhs: BigUint) -> Poly { + self /= rhs; + self + } +} + +impl DivAssign for Poly { + fn div_assign(&mut self, rhs: BigUint) { + let inv = self.prime.inverse(rhs); + for a in self.coeff.iter_mut() { + *a *= &inv; + } + } +} + +impl Neg for Poly { + type Output = Poly; + + fn neg(mut self) -> Self::Output { + for a in self.coeff.iter_mut() { + *a = self.prime.negate(&*a); + } + self + } +} \ No newline at end of file diff --git a/src/primes.rs b/src/primes.rs new file mode 100644 index 0000000..dde6389 --- /dev/null +++ b/src/primes.rs @@ -0,0 +1,116 @@ +use std::str::FromStr; + +use num_bigint::BigUint; +use lazy_static::lazy_static; +use num_traits::identities::{Zero, One}; + +use serde_derive::{Serialize, Deserialize}; + +lazy_static!{ + // 2^384 - 2^128 - 2^96 + 2^32 - 1 + // Chosen to be large enough to hold any AES key + static ref PRIME_P384: BigUint = BigUint::new(vec![ + 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFE, + 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + ]); + + static ref PRIME_P448: BigUint = BigUint::new(vec![ + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE, + 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFF, + 0xFFFFFFFE, 0xFFFFFFFF, + ]); +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +pub enum Prime { + // 2^384 - 2^128 - 2^96 + 2^32 - 1 + P384, + // 2^448 - 2^224 - 1 + P448, +} + +impl Prime { + pub fn to_id(self) -> u8 { + match self { + Prime::P384 => 1, + Prime::P448 => 2, + } + } + + pub fn byte_size(self) -> usize { + match self { + Prime::P448 => 56, + Prime::P384 => 48, + } + } + + pub fn modulus(self) -> &'static BigUint { + match self { + Prime::P384 => &PRIME_P384, + Prime::P448 => &PRIME_P448, + } + } + + pub fn inverse(self, value: BigUint) -> BigUint { + ext_gcd(value, self.modulus().clone()).0 + } + + pub fn negate(self, value: &BigUint) -> BigUint { + self.modulus() - value + } + +} + +impl FromStr for Prime { + type Err = failure::Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "p384" => Ok(Prime::P384), + "p448" => Ok(Prime::P448), + _ => Err(failure::err_msg("Invalid prime spec; expected P384 or P448")), + } + } +} + +fn ext_gcd(mut a: BigUint, mut b: BigUint) -> (BigUint, BigUint) { + use std::mem::swap; + let mut x = BigUint::zero(); + let mut last_x = BigUint::one(); + let mut y = BigUint::one(); + let mut last_y = BigUint::zero(); + + while !b.is_zero() { + let quot = &a / &b; + swap(&mut a, &mut b); + b %= &a; + swap(&mut x, &mut last_x); + swap(&mut y, &mut last_y); + + + last_x *= " + x -= &last_x; + + last_y *= quot; + y -= &last_y; + } + (last_x, last_y) +} + +#[cfg(test)] +mod test { + use num_bigint::BigUint; + use super::Prime; + + #[test] + fn check_value_of_p384() { + let one = &BigUint::new(vec![1]); + assert_eq!(Prime::P384::modulus, one << 384 - one - one << 128 - one << 96 - one << 32); + } + fn check_value_of_p448() { + let one = &BigUint::new(vec![1]); + assert_eq!(Prime::P448::modulus, one << 448 - one - one << 224); + } +} \ No newline at end of file diff --git a/src/s4.rs b/src/s4.rs new file mode 100644 index 0000000..ec5ccbc --- /dev/null +++ b/src/s4.rs @@ -0,0 +1,138 @@ +use std::io::{self, prelude::*}; +use std::ops::Deref; + + +use num_bigint::BigUint; +use serde::Serialize; + +/// Structure +/// | off | len | meaning | +/// | 0 | 1 | Length of payload (L) +/// | 1 | L | Payload +/// | L+1 | 4 | CRC32c +/// Note that these bytes are interpreted in big-endian form to get a field element; +/// this way, the high bit is always unset +#[derive(Default,Clone, Debug)] +pub struct Secret { + length: u8, + data: Vec, + crc32: u32, + +} + +impl Secret { + pub fn new(data: Vec) -> Self { + if data.len() == 0 { + panic!("Must have at least one byte of payload"); + } else if data.len() > 127 { + panic!("Too much data"); + } + + let mut res = Secret { + length: data.len() as u8, + data, + crc32: 0, + }; + res.crc32 = res.compute_crc(); + + res + } + + fn compute_crc(&self) -> u32 { + use byteorder::{BigEndian, ByteOrder}; + use crc::crc32::{CASTAGNOLI, Digest, Hasher32}; + let mut hasher = Digest::new(CASTAGNOLI); + hasher.reset(); + hasher.write(&[self.length]); + hasher.write(&self.data[..]); + hasher.write(&[0, 0, 0, 0]); + hasher.sum32() + } + + pub fn check_crc(&self) -> bool { + let crc = self.compute_crc(); + crc == self.crc32 + } +} + +impl Deref for Secret { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl From for Secret { + fn from(ui: BigUint) -> Self { + use byteorder::{BigEndian, ByteOrder}; + let decoded = ui.to_bytes_be(); + let mut res = Secret::default(); + res.length = decoded[0]; + let len = res.length as usize; + res.data = decoded[1..len+1].to_owned(); + res.crc32 = BigEndian::read_u32(&decoded[len+1..len+5]); + + res + } +} + +impl From for BigUint { + fn from(obj: Secret) -> BigUint { + use std::io::Write; + use byteorder::{WriteBytesExt, BigEndian}; + let mut buf = vec![]; + buf.write(&[obj.length]); + buf.write(&obj.data[..]); + buf.write_u32::(obj.crc32); + BigUint::from_bytes_be(&buf[..]) + } +} + +// + +pub struct Share { + pub prime: crate::primes::Prime, + pub x: BigUint, + pub y: BigUint, +} + +impl Share { + pub fn new(rnd: &mut Rng, poly: &crate::poly::Poly) -> Self { + use num_bigint::RandBigInt; + let x = rnd.gen_biguint_below(poly.prime.modulus()); + let y = poly.eval_at(&x); + Share { + prime: poly.prime, + x, y, + } + } + + pub fn write_to(&self, mut w: W) -> io::Result<()> { + let mut x = self.x.to_bytes_le(); + x.resize(self.prime.byte_size(), 0); + let mut y = self.y.to_bytes_le(); + y.resize(self.prime.byte_size(), 0); + + (&mut w).write_all(&x[..])?; + (&mut w).write_all(&y[..])?; + Ok(()) + } + + pub fn read_from(mut r: R, prime: crate::primes::Prime) -> io::Result { + let size = prime.byte_size(); + let mut x = vec![0; size]; + (&mut r).read_exact(&mut x[..])?; + let mut x = BigUint::from_bytes_le(&x[..]); + + let mut y = vec![0; size]; + (&mut r).read_exact(&mut y[..])?; + let mut y = BigUint::from_bytes_le(&y[..]); + + if &x >= prime.modulus() || &y >= prime.modulus() { + return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid point: out of range")); + } + + Ok(Share{ prime, x, y }) + } +} \ No newline at end of file