From 937b8eb6b7540bc4a411367f84662a8644f53e53 Mon Sep 17 00:00:00 2001 From: TQ Hirsch Date: Sun, 7 Jul 2024 21:45:59 +0200 Subject: [PATCH] Added wasm bindings, modified format to have version in share rather than secret and remove some baggage from the encoded share. This is an incompatible change --- Cargo.toml | 31 ++------- rssss-wasm/Cargo.toml | 15 +++++ rssss-wasm/src/lib.rs | 91 ++++++++++++++++++++++++++ rssss/Cargo.toml | 26 ++++++++ build.rs => rssss/build.rs | 1 + {src => rssss/src}/gf.rs | 4 +- {src => rssss/src}/gf256.rs | 10 ++- {src => rssss/src}/lib.rs | 0 {src => rssss/src}/main.rs | 9 ++- {src => rssss/src}/poly.rs | 17 +++-- {src => rssss/src}/s4.rs | 125 ++++++++++++++++-------------------- 11 files changed, 218 insertions(+), 111 deletions(-) create mode 100644 rssss-wasm/Cargo.toml create mode 100644 rssss-wasm/src/lib.rs create mode 100644 rssss/Cargo.toml rename build.rs => rssss/build.rs (97%) rename {src => rssss/src}/gf.rs (92%) rename {src => rssss/src}/gf256.rs (94%) rename {src => rssss/src}/lib.rs (100%) rename {src => rssss/src}/main.rs (94%) rename {src => rssss/src}/poly.rs (94%) rename {src => rssss/src}/s4.rs (60%) diff --git a/Cargo.toml b/Cargo.toml index 2721e1c..ec713a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,25 +1,6 @@ -[package] -name = "rssss" -version = "0.1.0" -authors = ["TQ Hirsch "] -edition = "2021" - -[dependencies] -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" -serde_json = "1.0.111" -bendy = "0.3.3" -generic-array = "1.0.0" -anyhow = "1.0.79" - -[build-dependencies] -rand = "0.6.5" -generic-array = "1.0.0" +[workspace] +resolver = "2" +members=[ + "rssss", + "rssss-wasm", +] diff --git a/rssss-wasm/Cargo.toml b/rssss-wasm/Cargo.toml new file mode 100644 index 0000000..9710370 --- /dev/null +++ b/rssss-wasm/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rssss-wasm" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type=["cdylib"] + +[dependencies] +wasm-bindgen = "0.2.92" +rssss = {path = "../rssss"} +rand = { version = "0.8.5", default-features = false, features = ["getrandom"] } +getrandom = { version = "0.2.15", default-features = false, features = ["js"] } +anyhow = "1.0.86" +thiserror = "1.0.61" diff --git a/rssss-wasm/src/lib.rs b/rssss-wasm/src/lib.rs new file mode 100644 index 0000000..2d085f8 --- /dev/null +++ b/rssss-wasm/src/lib.rs @@ -0,0 +1,91 @@ +use rand::Rng; +use wasm_bindgen::prelude::*; +use rssss::poly::{Poly, UniformPoly}; +use rssss::gf256::GF256; +use rssss::s4; + +#[wasm_bindgen] +pub struct ShareGenerator { + poly: Vec>, + last_share: u8, +} + +// Share format: [0, min_shares, *data] +impl ShareGenerator { + pub fn new_from_vec(min_shares: u8, secret: Vec) -> Self { + let mut secret_buf = Vec::with_capacity(secret.len() + 5); + let secret = s4::Secret::new(secret); + secret.to_buf(&mut secret_buf); + + let mut rng = rand::rngs::OsRng; + + let mut poly = Vec::with_capacity(secret.len()); + for c in secret_buf.iter().copied() { + poly.push(rng.sample(UniformPoly { intercept: GF256::from(c), degree: min_shares as usize - 1 })) + } + + Self{ + poly, + last_share: 0, + } + } +} + +#[wasm_bindgen] +impl ShareGenerator { + #[wasm_bindgen(constructor)] + pub fn new_from_buf(min_shares: u8, secret: &[u8]) -> Self { + Self::new_from_vec(min_shares, Vec::from(secret)) + } + + pub fn from_string(min_shares: u8, secret: String) -> Self { + Self::new_from_vec(min_shares, secret.into()) + } + + pub fn gen_share(&mut self, n: Option) -> Option { + let n = if let Some(n) = n { + if self.last_share < n { + self.last_share = n; + } + n + } else { + self.last_share += 1; + self.last_share + }; + + if n == 0 { + return None + } + + let share = s4::Share::new(GF256::from(n), self.poly.as_slice()); + + Some(Share(share)) + } +} + +#[wasm_bindgen] +#[repr(transparent)] +pub struct Share(s4::Share); + +#[wasm_bindgen] +impl Share { + pub fn to_blob(&self) -> Box<[u8]> { + let mut ret = Vec::with_capacity(self.0.y.len() + 1); + self.0.write_to(&mut ret).unwrap(); + ret.into_boxed_slice() + } + + pub fn from_blob(blob: &[u8]) -> Result { + + s4::Share::::read_from(blob) + .map(Share) + .map_err(|err| format!("{}", err)) + + } + + #[wasm_bindgen(getter)] + pub fn x(&self) -> usize { + usize::from(self.0.x) + } +} + diff --git a/rssss/Cargo.toml b/rssss/Cargo.toml new file mode 100644 index 0000000..96d940b --- /dev/null +++ b/rssss/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "rssss" +version = "0.1.0" +authors = ["TQ Hirsch "] +edition = "2021" + +[dependencies] +lazy_static = "1.2.0" +structopt = "0.2.14" +failure = "0.1.5" +crc = "3.2.1" +byteorder = "1.3.1" +num-traits = "0.2.6" +rand = "0.8.5" +serde = "1.0.87" +serde_derive = "1.0.87" +serde_cbor = "0.11.2" +serde_json = "1.0.111" +bendy = "0.3.3" +generic-array = "1.0.0" +anyhow = "1.0.79" +thiserror = "1.0.61" + +[build-dependencies] +rand = "0.8.5" +generic-array = "1.0.0" diff --git a/build.rs b/rssss/build.rs similarity index 97% rename from build.rs rename to rssss/build.rs index e576481..15b4d9c 100644 --- a/build.rs +++ b/rssss/build.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use std::path::Path; use crate::gf256::GF256; diff --git a/src/gf.rs b/rssss/src/gf.rs similarity index 92% rename from src/gf.rs rename to rssss/src/gf.rs index 24dbab7..c54be8d 100644 --- a/src/gf.rs +++ b/rssss/src/gf.rs @@ -1,9 +1,9 @@ use std::ops::{Add, Mul, Sub}; use generic_array::{ArrayLength, GenericArray}; -use rand::{Rng, RngCore}; +use rand::Rng; use generic_array::typenum::Unsigned; -pub trait GF: Add + Sub + Mul + Copy + Clone + Eq + TryFrom { +pub trait GF: Add + Sub + Mul + Copy + Clone + Eq + TryFrom + Into { const ORDER: usize; type ChunkSize: ArrayLength; diff --git a/src/gf256.rs b/rssss/src/gf256.rs similarity index 94% rename from src/gf256.rs rename to rssss/src/gf256.rs index c04efed..e266660 100644 --- a/src/gf256.rs +++ b/rssss/src/gf256.rs @@ -1,10 +1,9 @@ use std::error::Error; use std::fmt::{Debug, Display, Formatter}; -use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign}; +use std::ops::{Add, AddAssign, Sub, SubAssign, Mul}; use generic_array::GenericArray; use generic_array::typenum::U1; use rand::distributions::Standard; -use rand::prelude::Distribution; use rand::Rng; #[cfg(rssss_have_codegen)] @@ -32,6 +31,8 @@ impl GF256 { GF256(poly << 1 ^ (0x1B & mask)) } + // This exists for the benefit of the table generator + #[allow(unused)] pub(crate) fn minv_slow(self) -> Self { let mut res = self; let mut factor = 1; @@ -163,6 +164,11 @@ impl TryFrom for GF256 { } } +impl From for usize { + fn from(value: GF256) -> Self { + value.0 as usize + } +} // Utility functions /// Unsigned negate diff --git a/src/lib.rs b/rssss/src/lib.rs similarity index 100% rename from src/lib.rs rename to rssss/src/lib.rs diff --git a/src/main.rs b/rssss/src/main.rs similarity index 94% rename from src/main.rs rename to rssss/src/main.rs index f60f8cc..e98d4e2 100644 --- a/src/main.rs +++ b/rssss/src/main.rs @@ -133,7 +133,7 @@ impl Command for GenPoly { fn run(&self) -> Result<(), Error> { use std::io::Read; - let mut rng = rand::rngs::JitterRng::new()?; + let mut rng = rand::rngs::OsRng; let mut payload = vec![]; io::stdin().read_to_end(&mut payload)?; @@ -160,9 +160,8 @@ impl Command for GenPoly { impl Command for GenShare { fn run(&self) -> Result<(), Error> { eprintln!("Reading poly"); - let mut poly: Vec> = serde_cbor::from_reader(io::stdin())?; + let poly: Vec> = serde_cbor::from_reader(io::stdin())?; eprintln!("Read poly"); - let mut rng = rand::rngs::OsRng::new()?; let share = rssss::s4::Share::new((self.share_no as u8).into(), poly.as_slice()); // eprintln!("{share:?}"); @@ -190,7 +189,7 @@ impl Command for Solve { impl Command for ExtractSecret { fn run(&self) -> Result<(), Error> { use std::io::Write; - let mut poly: Vec> = serde_cbor::from_reader(io::stdin())?; + let poly: Vec> = serde_cbor::from_reader(io::stdin())?; let interpolated = poly.into_iter().map(|p| p.eval_at(GF256::ZERO)) .flat_map(|f| f.encode()) .collect::>(); @@ -210,7 +209,7 @@ impl Command for Combine { .map_err(|err| Error::from(err) .context(format!("Failed to read {:?}", filename)))?) }).collect::>, Error>>()?; - let mut interpolated = rssss::s4::interpolate0(&shares[..]); + let interpolated = rssss::s4::interpolate0(&shares[..]); let mut secret = Vec::with_capacity(interpolated.len() * ::ChunkSize::to_usize()); for chunk in interpolated { secret.extend_from_slice(&chunk.encode()[..]) diff --git a/src/poly.rs b/rssss/src/poly.rs similarity index 94% rename from src/poly.rs rename to rssss/src/poly.rs index 3dbaeac..0b02138 100644 --- a/src/poly.rs +++ b/rssss/src/poly.rs @@ -1,20 +1,15 @@ use std::fmt::Formatter; -use std::io::{self, prelude::*}; use std::marker::PhantomData; -use std::ops::{Mul, Div, Add, AddAssign, DivAssign, Neg, Sub}; +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub}; -use num_traits::{Zero, One}; -use rand::distributions::Standard; use rand::prelude::Distribution; use rand::Rng; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::de::{SeqAccess, Visitor}; use serde::ser::SerializeSeq; -use serde_derive::{Serialize, Deserialize}; + use crate::gf::GF; - - #[derive(Clone, Debug)] pub struct Poly { // coeff[0] + coeff[1]*x + coeff[2] * x^2 @@ -57,6 +52,10 @@ impl Poly { } } } + + pub fn degree(&self) -> usize { + self.coeff.len().max(1) - 1 + } } impl Add<&Poly> for Poly { @@ -111,7 +110,7 @@ impl AddAssign for Poly { impl Sub for Poly { type Output = Self; - fn sub(mut self, rhs: F) -> Self::Output { + fn sub(self, rhs: F) -> Self::Output { self + rhs } } @@ -169,7 +168,7 @@ impl DivAssign for Poly { impl Neg for Poly { type Output = Poly; - fn neg(mut self) -> Self::Output { + fn neg(self) -> Self::Output { // Elements in GF(2^n) are their own negative self } diff --git a/src/s4.rs b/rssss/src/s4.rs similarity index 60% rename from src/s4.rs rename to rssss/src/s4.rs index 8f8fcb7..47712c1 100644 --- a/src/s4.rs +++ b/rssss/src/s4.rs @@ -1,21 +1,17 @@ use std::io::{self, prelude::*}; use std::ops::Deref; -use byteorder::{BE, ReadBytesExt, WriteBytesExt}; -use crc::Hasher32; -use failure::err_msg; +use byteorder::{BE, LE, ReadBytesExt, WriteBytesExt}; +use crc::Crc; use generic_array::GenericArray; -use crate::gf256::GF256; +use generic_array::typenum::Unsigned; use crate::gf::GF; - use crate::poly::Poly; - /// Structure /// | off | len | meaning | -/// | 0 | 1 | Length of payload (L) -/// | 1 | L | Payload -/// | L+1 | 4 | CRC32c +/// | 0 | L | Payload +/// | L | 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)] @@ -25,35 +21,6 @@ pub struct Secret { } -fn write_varint(mut v: u32, w: &mut Vec) { - while v > 127 { - w.push((v & 0x7F) as u8 | 0x80); - v >>= 7; - } - w.push(v as u8); -} - -fn read_varint(buf: &mut &[u8]) -> Option { - let mut res = 0; - let mut scale = 0; - while buf.len() > 0 && (buf[0] & 0x80 != 0) { - let hd = buf[0]; - eprintln!("Got head byte {hd:02x}"); - res += (hd as u32 & 0x7F).checked_shl(scale)?; - *buf = &buf[1..]; - scale += 7; - } - if buf.len() > 0 { - let hd = buf[0]; - eprintln!("Got head byte {hd:02x}"); - res += (hd as u32 & 0x7F).checked_shl(scale)?; - *buf = &buf[1..]; - Some(res) - } else { - None - } -} - impl Secret { pub fn new(data: Vec) -> Self { if data.len() == 0 { @@ -70,15 +37,12 @@ impl Secret { } fn compute_crc(&self) -> u32 { - use crc::crc32::{CASTAGNOLI, Digest, Hasher32}; - let mut hasher = Digest::new(CASTAGNOLI); - let mut size_vi = Vec::with_capacity(5); - hasher.reset(); - hasher.write(&[2]); - write_varint(self.data.len() as u32, &mut size_vi); - hasher.write(&self.data[..]); - hasher.write(&[0, 0, 0, 0]); - hasher.sum32() + use crc::CRC_32_ISCSI; + let crc = Crc::::new(&CRC_32_ISCSI); + let mut hasher = crc.digest(); + hasher.update(&self.data[..]); + hasher.update(&[0, 0, 0, 0]); + hasher.finalize() } pub fn check_crc(&self) -> bool { @@ -86,20 +50,14 @@ impl Secret { crc == self.crc32 } - pub fn from_buf(mut buf: &[u8]) -> Result { + pub fn from_buf(buf: &[u8]) -> Result { use failure::err_msg; - if buf[0] != 2 { - return Err(err_msg("Invalid version")) // Invalid version + + if buf.len() < 4 { + return Err(err_msg("Message too short")) } - buf = &buf[1..]; - let size = read_varint(&mut buf) - .ok_or(err_msg("Invalid length"))? - as usize; // Invalid size - if size + 4 > buf.len() { - return Err(err_msg("Unexpected EOF")) // Unexpected EOF - } - let (content, mut buf) = buf.split_at(size); + let (content, mut buf) = buf.split_at(buf.len() - 4); let data = content.into(); let crc32 = buf.read_u32::().unwrap(); @@ -112,8 +70,7 @@ impl Secret { } pub fn to_buf(&self, buf: &mut Vec) { - buf.push(2); // version - write_varint(self.data.len() as u32, &mut *buf); + buf.reserve(self.data.len() + 4); buf.extend_from_slice(self.data.as_slice()); buf.write_u32::(self.crc32).unwrap(); } @@ -131,20 +88,46 @@ impl Deref for Secret { #[derive(Debug)] pub struct Share { + pub n_required: usize, pub x: Field, pub y: Vec, } +impl AsRef> for Share { + fn as_ref(&self) -> &Share { + self + } +} + impl Share { pub fn new(share_no: F, poly: &[Poly]) -> Self { + let n_required = poly.iter().map(Poly::degree).max().unwrap_or(0); let x= share_no; let y = poly.iter().map(|word| word.eval_at(x)).collect(); Share { - x, y, + n_required, + x, + y, } } + // Share format: [version, *n_req, *x, data...] + // n_req is little-endian, same size as field element + // Note: v0 is GF(2^8), v1 (unimplemented) is GF(2^16) + + pub fn encoded_length(&self) -> usize { + (self.y.len() + 2) * F::ChunkSize::to_usize() + 1 + } + + pub fn to_buf(&self) -> Vec { + let mut ret = Vec::with_capacity(self.encoded_length()); + self.write_to(&mut ret).unwrap(); + ret + } + pub fn write_to(&self, mut w: W) -> io::Result<()> { + w.write_u8(0)?; + w.write_uint::(self.n_required as u64, F::ChunkSize::to_usize())?; w.write_all(&self.x.encode()[..])?; for y in self.y.iter().copied() { w.write_all(&y.encode()[..])?; @@ -154,6 +137,13 @@ impl Share { pub fn read_from(mut r: R) -> io::Result { let mut buf = GenericArray::::default(); + + let version = r.read_u8()?; + if version != 0 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "Unknown version")) + } + let n_required = r.read_uint::(F::ChunkSize::to_usize())? as usize; + r.read_exact(&mut buf[..])?; let x = F::decode(buf.clone()); let mut y = Vec::new(); @@ -162,26 +152,26 @@ impl Share { y.push(F::decode(buf.clone())); } - Ok(Share { x, y }) + Ok(Share { n_required, x, y }) } } -pub fn solve(shares: &[Share]) -> Vec> { +pub fn solve(shares: &[impl AsRef>]) -> Vec> { // TODO: handle the case where shares have different size if shares.len() == 0 { return vec![]; } - let mut result: Vec> = vec![Poly::::zero(); shares[0].y.len()]; + let mut result: Vec> = vec![Poly::::zero(); shares[0].as_ref().y.len()]; for j in 0..shares.len() { - let mut basis: Vec> = shares[j].y.iter().copied().map(|y| Poly::::zero() + y).collect(); + let mut basis: Vec> = shares[j].as_ref().y.iter().copied().map(|y| Poly::::zero() + y).collect(); for m in 0..shares.len() { if j == m { continue; } - let dx_inv = (shares[j].x - shares[m].x).minv(); // TODO: we can cut runtime in half by sharing (j,m) with (m,j) + let dx_inv = (shares[j].as_ref().x - shares[m].as_ref().x).minv(); // TODO: we can cut runtime in half by sharing (j,m) with (m,j) for bvec in basis.iter_mut() { - *bvec = (Poly::x() - shares[m].x) * &*bvec * dx_inv; + *bvec = (Poly::x() - shares[m].as_ref().x) * &*bvec * dx_inv; } } result.iter_mut().zip(basis).for_each(|(result, basis)| *result += &basis); @@ -192,7 +182,6 @@ pub fn solve(shares: &[Share]) -> Vec> { } pub fn interpolate0(shares: &[Share]) -> Vec { - use num_traits::Zero; let mut result: Vec = vec![F::ZERO; shares[0].y.len()]; for j in shares { let mut basis = j.y.clone();