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

This commit is contained in:
2024-07-07 21:45:59 +02:00
parent b1dfd56446
commit 937b8eb6b7
11 changed files with 218 additions and 111 deletions

View File

@@ -1,25 +1,6 @@
[package] [workspace]
name = "rssss" resolver = "2"
version = "0.1.0" members=[
authors = ["TQ Hirsch <thequux@thequux.com>"] "rssss",
edition = "2021" "rssss-wasm",
]
[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"

15
rssss-wasm/Cargo.toml Normal file
View File

@@ -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"

91
rssss-wasm/src/lib.rs Normal file
View File

@@ -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<Poly<GF256>>,
last_share: u8,
}
// Share format: [0, min_shares, *data]
impl ShareGenerator {
pub fn new_from_vec(min_shares: u8, secret: Vec<u8>) -> 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<u8>) -> Option<Share> {
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<GF256>);
#[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<Share, String> {
s4::Share::<GF256>::read_from(blob)
.map(Share)
.map_err(|err| format!("{}", err))
}
#[wasm_bindgen(getter)]
pub fn x(&self) -> usize {
usize::from(self.0.x)
}
}

26
rssss/Cargo.toml Normal file
View File

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

View File

@@ -1,3 +1,4 @@
#![allow(unused)]
use std::path::Path; use std::path::Path;
use crate::gf256::GF256; use crate::gf256::GF256;

View File

@@ -1,9 +1,9 @@
use std::ops::{Add, Mul, Sub}; use std::ops::{Add, Mul, Sub};
use generic_array::{ArrayLength, GenericArray}; use generic_array::{ArrayLength, GenericArray};
use rand::{Rng, RngCore}; use rand::Rng;
use generic_array::typenum::Unsigned; use generic_array::typenum::Unsigned;
pub trait GF: Add<Output=Self> + Sub<Output=Self> + Mul<Output=Self> + Copy + Clone + Eq + TryFrom<usize> { pub trait GF: Add<Output=Self> + Sub<Output=Self> + Mul<Output=Self> + Copy + Clone + Eq + TryFrom<usize> + Into<usize> {
const ORDER: usize; const ORDER: usize;
type ChunkSize: ArrayLength; type ChunkSize: ArrayLength;

View File

@@ -1,10 +1,9 @@
use std::error::Error; use std::error::Error;
use std::fmt::{Debug, Display, Formatter}; 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::GenericArray;
use generic_array::typenum::U1; use generic_array::typenum::U1;
use rand::distributions::Standard; use rand::distributions::Standard;
use rand::prelude::Distribution;
use rand::Rng; use rand::Rng;
#[cfg(rssss_have_codegen)] #[cfg(rssss_have_codegen)]
@@ -32,6 +31,8 @@ impl GF256 {
GF256(poly << 1 ^ (0x1B & mask)) GF256(poly << 1 ^ (0x1B & mask))
} }
// This exists for the benefit of the table generator
#[allow(unused)]
pub(crate) fn minv_slow(self) -> Self { pub(crate) fn minv_slow(self) -> Self {
let mut res = self; let mut res = self;
let mut factor = 1; let mut factor = 1;
@@ -163,6 +164,11 @@ impl TryFrom<usize> for GF256 {
} }
} }
impl From<GF256> for usize {
fn from(value: GF256) -> Self {
value.0 as usize
}
}
// Utility functions // Utility functions
/// Unsigned negate /// Unsigned negate

View File

@@ -133,7 +133,7 @@ impl Command for GenPoly {
fn run(&self) -> Result<(), Error> { fn run(&self) -> Result<(), Error> {
use std::io::Read; use std::io::Read;
let mut rng = rand::rngs::JitterRng::new()?; let mut rng = rand::rngs::OsRng;
let mut payload = vec![]; let mut payload = vec![];
io::stdin().read_to_end(&mut payload)?; io::stdin().read_to_end(&mut payload)?;
@@ -160,9 +160,8 @@ impl Command for GenPoly {
impl Command for GenShare { impl Command for GenShare {
fn run(&self) -> Result<(), Error> { fn run(&self) -> Result<(), Error> {
eprintln!("Reading poly"); eprintln!("Reading poly");
let mut poly: Vec<rssss::poly::Poly<GF256>> = serde_cbor::from_reader(io::stdin())?; let poly: Vec<rssss::poly::Poly<GF256>> = serde_cbor::from_reader(io::stdin())?;
eprintln!("Read poly"); 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()); let share = rssss::s4::Share::new((self.share_no as u8).into(), poly.as_slice());
// eprintln!("{share:?}"); // eprintln!("{share:?}");
@@ -190,7 +189,7 @@ impl Command for Solve {
impl Command for ExtractSecret { impl Command for ExtractSecret {
fn run(&self) -> Result<(), Error> { fn run(&self) -> Result<(), Error> {
use std::io::Write; use std::io::Write;
let mut poly: Vec<rssss::poly::Poly<GF256>> = serde_cbor::from_reader(io::stdin())?; let poly: Vec<rssss::poly::Poly<GF256>> = serde_cbor::from_reader(io::stdin())?;
let interpolated = poly.into_iter().map(|p| p.eval_at(GF256::ZERO)) let interpolated = poly.into_iter().map(|p| p.eval_at(GF256::ZERO))
.flat_map(|f| f.encode()) .flat_map(|f| f.encode())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@@ -210,7 +209,7 @@ impl Command for Combine {
.map_err(|err| Error::from(err) .map_err(|err| Error::from(err)
.context(format!("Failed to read {:?}", filename)))?) .context(format!("Failed to read {:?}", filename)))?)
}).collect::<Result<Vec<rssss::s4::Share<GF256>>, Error>>()?; }).collect::<Result<Vec<rssss::s4::Share<GF256>>, Error>>()?;
let mut interpolated = rssss::s4::interpolate0(&shares[..]); let interpolated = rssss::s4::interpolate0(&shares[..]);
let mut secret = Vec::with_capacity(interpolated.len() * <GF256 as GF>::ChunkSize::to_usize()); let mut secret = Vec::with_capacity(interpolated.len() * <GF256 as GF>::ChunkSize::to_usize());
for chunk in interpolated { for chunk in interpolated {
secret.extend_from_slice(&chunk.encode()[..]) secret.extend_from_slice(&chunk.encode()[..])

View File

@@ -1,20 +1,15 @@
use std::fmt::Formatter; use std::fmt::Formatter;
use std::io::{self, prelude::*};
use std::marker::PhantomData; 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::prelude::Distribution;
use rand::Rng; use rand::Rng;
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::de::{SeqAccess, Visitor}; use serde::de::{SeqAccess, Visitor};
use serde::ser::SerializeSeq; use serde::ser::SerializeSeq;
use serde_derive::{Serialize, Deserialize};
use crate::gf::GF; use crate::gf::GF;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Poly<F: GF> { pub struct Poly<F: GF> {
// coeff[0] + coeff[1]*x + coeff[2] * x^2 // coeff[0] + coeff[1]*x + coeff[2] * x^2
@@ -57,6 +52,10 @@ impl<F: GF> Poly<F> {
} }
} }
} }
pub fn degree(&self) -> usize {
self.coeff.len().max(1) - 1
}
} }
impl<F: GF> Add<&Poly<F>> for Poly<F> { impl<F: GF> Add<&Poly<F>> for Poly<F> {
@@ -111,7 +110,7 @@ impl<F: GF> AddAssign<F> for Poly<F> {
impl <F: GF> Sub<F> for Poly<F> { impl <F: GF> Sub<F> for Poly<F> {
type Output = Self; type Output = Self;
fn sub(mut self, rhs: F) -> Self::Output { fn sub(self, rhs: F) -> Self::Output {
self + rhs self + rhs
} }
} }
@@ -169,7 +168,7 @@ impl<F: GF> DivAssign<F> for Poly<F> {
impl<F: GF> Neg for Poly<F> { impl<F: GF> Neg for Poly<F> {
type Output = Poly<F>; type Output = Poly<F>;
fn neg(mut self) -> Self::Output { fn neg(self) -> Self::Output {
// Elements in GF(2^n) are their own negative // Elements in GF(2^n) are their own negative
self self
} }

View File

@@ -1,21 +1,17 @@
use std::io::{self, prelude::*}; use std::io::{self, prelude::*};
use std::ops::Deref; use std::ops::Deref;
use byteorder::{BE, ReadBytesExt, WriteBytesExt}; use byteorder::{BE, LE, ReadBytesExt, WriteBytesExt};
use crc::Hasher32; use crc::Crc;
use failure::err_msg;
use generic_array::GenericArray; use generic_array::GenericArray;
use crate::gf256::GF256; use generic_array::typenum::Unsigned;
use crate::gf::GF; use crate::gf::GF;
use crate::poly::Poly; use crate::poly::Poly;
/// Structure /// Structure
/// | off | len | meaning | /// | off | len | meaning |
/// | 0 | 1 | Length of payload (L) /// | 0 | L | Payload
/// | 1 | L | Payload /// | L | 4 | CRC32c
/// | L+1 | 4 | CRC32c
/// Note that these bytes are interpreted in big-endian form to get a field element; /// Note that these bytes are interpreted in big-endian form to get a field element;
/// this way, the high bit is always unset /// this way, the high bit is always unset
#[derive(Default,Clone, Debug)] #[derive(Default,Clone, Debug)]
@@ -25,35 +21,6 @@ pub struct Secret {
} }
fn write_varint(mut v: u32, w: &mut Vec<u8>) {
while v > 127 {
w.push((v & 0x7F) as u8 | 0x80);
v >>= 7;
}
w.push(v as u8);
}
fn read_varint(buf: &mut &[u8]) -> Option<u32> {
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 { impl Secret {
pub fn new(data: Vec<u8>) -> Self { pub fn new(data: Vec<u8>) -> Self {
if data.len() == 0 { if data.len() == 0 {
@@ -70,15 +37,12 @@ impl Secret {
} }
fn compute_crc(&self) -> u32 { fn compute_crc(&self) -> u32 {
use crc::crc32::{CASTAGNOLI, Digest, Hasher32}; use crc::CRC_32_ISCSI;
let mut hasher = Digest::new(CASTAGNOLI); let crc = Crc::<u32>::new(&CRC_32_ISCSI);
let mut size_vi = Vec::with_capacity(5); let mut hasher = crc.digest();
hasher.reset(); hasher.update(&self.data[..]);
hasher.write(&[2]); hasher.update(&[0, 0, 0, 0]);
write_varint(self.data.len() as u32, &mut size_vi); hasher.finalize()
hasher.write(&self.data[..]);
hasher.write(&[0, 0, 0, 0]);
hasher.sum32()
} }
pub fn check_crc(&self) -> bool { pub fn check_crc(&self) -> bool {
@@ -86,20 +50,14 @@ impl Secret {
crc == self.crc32 crc == self.crc32
} }
pub fn from_buf(mut buf: &[u8]) -> Result<Self, failure::Error> { pub fn from_buf(buf: &[u8]) -> Result<Self, failure::Error> {
use failure::err_msg; 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 (content, mut buf) = buf.split_at(buf.len() - 4);
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 data = content.into(); let data = content.into();
let crc32 = buf.read_u32::<BE>().unwrap(); let crc32 = buf.read_u32::<BE>().unwrap();
@@ -112,8 +70,7 @@ impl Secret {
} }
pub fn to_buf(&self, buf: &mut Vec<u8>) { pub fn to_buf(&self, buf: &mut Vec<u8>) {
buf.push(2); // version buf.reserve(self.data.len() + 4);
write_varint(self.data.len() as u32, &mut *buf);
buf.extend_from_slice(self.data.as_slice()); buf.extend_from_slice(self.data.as_slice());
buf.write_u32::<BE>(self.crc32).unwrap(); buf.write_u32::<BE>(self.crc32).unwrap();
} }
@@ -131,20 +88,46 @@ impl Deref for Secret {
#[derive(Debug)] #[derive(Debug)]
pub struct Share<Field: GF> { pub struct Share<Field: GF> {
pub n_required: usize,
pub x: Field, pub x: Field,
pub y: Vec<Field>, pub y: Vec<Field>,
} }
impl <Field: GF> AsRef<Share<Field>> for Share<Field> {
fn as_ref(&self) -> &Share<Field> {
self
}
}
impl<F: GF> Share<F> { impl<F: GF> Share<F> {
pub fn new(share_no: F, poly: &[Poly<F>]) -> Self { pub fn new(share_no: F, poly: &[Poly<F>]) -> Self {
let n_required = poly.iter().map(Poly::degree).max().unwrap_or(0);
let x= share_no; let x= share_no;
let y = poly.iter().map(|word| word.eval_at(x)).collect(); let y = poly.iter().map(|word| word.eval_at(x)).collect();
Share { 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<u8> {
let mut ret = Vec::with_capacity(self.encoded_length());
self.write_to(&mut ret).unwrap();
ret
}
pub fn write_to<W: Write>(&self, mut w: W) -> io::Result<()> { pub fn write_to<W: Write>(&self, mut w: W) -> io::Result<()> {
w.write_u8(0)?;
w.write_uint::<LE>(self.n_required as u64, F::ChunkSize::to_usize())?;
w.write_all(&self.x.encode()[..])?; w.write_all(&self.x.encode()[..])?;
for y in self.y.iter().copied() { for y in self.y.iter().copied() {
w.write_all(&y.encode()[..])?; w.write_all(&y.encode()[..])?;
@@ -154,6 +137,13 @@ impl<F: GF> Share<F> {
pub fn read_from<R: Read>(mut r: R) -> io::Result<Self> { pub fn read_from<R: Read>(mut r: R) -> io::Result<Self> {
let mut buf = GenericArray::<u8, F::ChunkSize>::default(); let mut buf = GenericArray::<u8, F::ChunkSize>::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::<LE>(F::ChunkSize::to_usize())? as usize;
r.read_exact(&mut buf[..])?; r.read_exact(&mut buf[..])?;
let x = F::decode(buf.clone()); let x = F::decode(buf.clone());
let mut y = Vec::new(); let mut y = Vec::new();
@@ -162,26 +152,26 @@ impl<F: GF> Share<F> {
y.push(F::decode(buf.clone())); y.push(F::decode(buf.clone()));
} }
Ok(Share { x, y }) Ok(Share { n_required, x, y })
} }
} }
pub fn solve<F: GF>(shares: &[Share<F>]) -> Vec<Poly<F>> { pub fn solve<F: GF>(shares: &[impl AsRef<Share<F>>]) -> Vec<Poly<F>> {
// TODO: handle the case where shares have different size // TODO: handle the case where shares have different size
if shares.len() == 0 { if shares.len() == 0 {
return vec![]; return vec![];
} }
let mut result: Vec<Poly<F>> = vec![Poly::<F>::zero(); shares[0].y.len()]; let mut result: Vec<Poly<F>> = vec![Poly::<F>::zero(); shares[0].as_ref().y.len()];
for j in 0..shares.len() { for j in 0..shares.len() {
let mut basis: Vec<Poly<F>> = shares[j].y.iter().copied().map(|y| Poly::<F>::zero() + y).collect(); let mut basis: Vec<Poly<F>> = shares[j].as_ref().y.iter().copied().map(|y| Poly::<F>::zero() + y).collect();
for m in 0..shares.len() { for m in 0..shares.len() {
if j == m { if j == m {
continue; 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() { 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); result.iter_mut().zip(basis).for_each(|(result, basis)| *result += &basis);
@@ -192,7 +182,6 @@ pub fn solve<F: GF>(shares: &[Share<F>]) -> Vec<Poly<F>> {
} }
pub fn interpolate0<F: GF>(shares: &[Share<F>]) -> Vec<F> { pub fn interpolate0<F: GF>(shares: &[Share<F>]) -> Vec<F> {
use num_traits::Zero;
let mut result: Vec<F> = vec![F::ZERO; shares[0].y.len()]; let mut result: Vec<F> = vec![F::ZERO; shares[0].y.len()];
for j in shares { for j in shares {
let mut basis = j.y.clone(); let mut basis = j.y.clone();