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]
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 = "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",
]

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 crate::gf256::GF256;

View File

@@ -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<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;
type ChunkSize: ArrayLength;

View File

@@ -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<usize> for GF256 {
}
}
impl From<GF256> for usize {
fn from(value: GF256) -> Self {
value.0 as usize
}
}
// Utility functions
/// Unsigned negate

View File

@@ -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<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");
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<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))
.flat_map(|f| f.encode())
.collect::<Vec<_>>();
@@ -210,7 +209,7 @@ impl Command for Combine {
.map_err(|err| Error::from(err)
.context(format!("Failed to read {:?}", filename)))?)
}).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());
for chunk in interpolated {
secret.extend_from_slice(&chunk.encode()[..])

View File

@@ -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<F: GF> {
// 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> {
@@ -111,7 +110,7 @@ impl<F: GF> AddAssign<F> for Poly<F> {
impl <F: GF> Sub<F> for Poly<F> {
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<F: GF> DivAssign<F> for Poly<F> {
impl<F: GF> Neg for 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
self
}

View File

@@ -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<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 {
pub fn new(data: Vec<u8>) -> 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::<u32>::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<Self, failure::Error> {
pub fn from_buf(buf: &[u8]) -> Result<Self, failure::Error> {
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::<BE>().unwrap();
@@ -112,8 +70,7 @@ impl Secret {
}
pub fn to_buf(&self, buf: &mut Vec<u8>) {
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::<BE>(self.crc32).unwrap();
}
@@ -131,20 +88,46 @@ impl Deref for Secret {
#[derive(Debug)]
pub struct Share<Field: GF> {
pub n_required: usize,
pub x: 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> {
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 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<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<()> {
w.write_u8(0)?;
w.write_uint::<LE>(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<F: GF> Share<F> {
pub fn read_from<R: Read>(mut r: R) -> io::Result<Self> {
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[..])?;
let x = F::decode(buf.clone());
let mut y = Vec::new();
@@ -162,26 +152,26 @@ impl<F: GF> Share<F> {
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
if shares.len() == 0 {
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() {
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() {
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<F: GF>(shares: &[Share<F>]) -> Vec<Poly<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()];
for j in shares {
let mut basis = j.y.clone();