Initial commit

This commit is contained in:
2019-02-08 19:01:03 +01:00
commit d2f5ec25ee
7 changed files with 655 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
/target
**/*.rs.bk
Cargo.lock
local
.idea
*.iml

18
Cargo.toml Normal file
View File

@@ -0,0 +1,18 @@
[package]
name = "rssss"
version = "0.1.0"
authors = ["TQ Hirsch <thequux@thequux.com>"]
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"

8
src/lib.rs Normal file
View File

@@ -0,0 +1,8 @@
use num_bigint::BigUint;
use lazy_static::lazy_static;
pub mod primes;
pub mod poly;
pub mod s4;

187
src/main.rs Normal file
View File

@@ -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 <secret >poly
///
/// # Generate a few shares
///
/// rssss gen-share <poly >share-1
///
/// rssss gen-share <poly >share-2
///
/// rssss gen-share <poly >share-3
///
/// rssss gen-share <poly >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 <recovered-poly >share-5
///
/// # Get the secret back
///
/// rssss extract <recovered-poly >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<PathBuf>,
}
/// 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::<Result<Vec<rssss::s4::Share>, 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

182
src/poly.rs Normal file
View File

@@ -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<BigUint>,
}
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<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 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<BigUint> for Poly {
type Output = Poly;
fn div(mut self, rhs: BigUint) -> Poly {
self /= rhs;
self
}
}
impl DivAssign<BigUint> 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
}
}

116
src/primes.rs Normal file
View File

@@ -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<Self, Self::Err> {
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 *= &quot;
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);
}
}

138
src/s4.rs Normal file
View File

@@ -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<u8>,
crc32: u32,
}
impl Secret {
pub fn new(data: Vec<u8>) -> 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<u8>;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl From<BigUint> 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<Secret> 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::<BigEndian>(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<Rng: num_bigint::RandBigInt + ?Sized>(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<W: Write>(&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<R: Read>(mut r: R, prime: crate::primes::Prime) -> io::Result<Self> {
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 })
}
}