Compare commits

...

2 Commits

11 changed files with 275 additions and 123 deletions

View File

@@ -1,21 +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"
[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"

30
rssss/build.rs Normal file
View File

@@ -0,0 +1,30 @@
#![allow(unused)]
use std::path::Path;
use crate::gf256::GF256;
#[path="src/gf.rs"]
mod gf;
#[path="src/gf256.rs"]
mod gf256;
fn write_tbl(path: impl AsRef<Path>, name: &str, tbl: &[u8; 256]) {
let mut res = format!("const INV_TBL: [{name}; {tbl_len}] = [\n", tbl_len=tbl.len());
for v in tbl {
res += &format!("\t{name}({v}),\n");
}
res += "];\n";
std::fs::write(path, res).unwrap();
}
pub fn main() {
let mut gf256_inv_tbl = [0;256];
for i in 0..256 {
gf256_inv_tbl[i] = u8::from(GF256::from(i as u8).minv_slow());
}
let mut out_file = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
out_file.push("gf256_inv.rs");
write_tbl(&out_file, "GF256", &gf256_inv_tbl);
println!("cargo::rustc-cfg=rssss_have_codegen")
}

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,11 +1,12 @@
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::distributions::Standard;
use rand::Rng;
#[cfg(rssss_have_codegen)]
use serde_derive::{Deserialize, Serialize};
use crate::gf::GF;
@@ -15,9 +16,12 @@ use crate::gf::GF;
///
/// Multiplication is performed modulo $x^8 + x^4 + x^3 + x + 1$, or `{11B}`
///
#[derive(Serialize, Deserialize)]
#[cfg_attr(rssss_have_codegen, derive(Serialize, Deserialize))]
pub struct GF256(u8);
#[cfg(rssss_have_codegen)]
include!(concat!(env!("OUT_DIR"), "/gf256_inv.rs"));
impl GF256 {
/// Multiply by x, reducing modulo 14E
@@ -27,6 +31,21 @@ 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;
for _ in 0..6 {
res = res * res * self;
factor = factor * 2 + 1;
}
res = res * res;
factor = factor*2;
//assert_eq!(factor, 254);
res
}
}
impl GF for GF256 {
@@ -38,19 +57,13 @@ impl GF for GF256 {
/// Multiplicative inverse
/// Note that this is an expensive operation
#[cfg(rssss_have_codegen)]
fn minv(self) -> Self {
// For constant time evaluation, we compute self**254
let mut res = self;
let mut factor = 1;
for _ in 0..6 {
res = res * res * self;
factor = factor * 2 + 1;
}
res = res * res;
factor = factor*2;
assert_eq!(factor, 254);
res
INV_TBL[self.0 as usize]
}
#[cfg(not(rssss_have_codegen))]
fn minv(self) -> Self {
self.minv_slow()
}
fn decode(chunk: GenericArray<u8, Self::ChunkSize>) -> Self {
@@ -151,10 +164,15 @@ impl TryFrom<usize> for GF256 {
}
}
impl From<GF256> for usize {
fn from(value: GF256) -> Self {
value.0 as usize
}
}
// Utility functions
/// Unsigned negate
fn uneg(v: u8) -> u8 {
const fn uneg(v: u8) -> u8 {
(-(v as i8)) as u8
}

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,12 +160,11 @@ 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:?}");
// eprintln!("{share:?}");
share.write_to(io::stdout())?;
Ok(())
}
@@ -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();