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:
31
Cargo.toml
31
Cargo.toml
@@ -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
15
rssss-wasm/Cargo.toml
Normal 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
91
rssss-wasm/src/lib.rs
Normal 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
26
rssss/Cargo.toml
Normal 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"
|
||||
@@ -1,3 +1,4 @@
|
||||
#![allow(unused)]
|
||||
use std::path::Path;
|
||||
use crate::gf256::GF256;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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()[..])
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user