diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a79af18 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,49 @@ +name: Release + +permissions: + pull-requests: write + contents: write + +on: + push: + branches: + - main + +jobs: + release: + name: release + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + + - name: Install cargo-semver-checks + uses: taiki-e/install-action@cargo-binstall + with: + tool: cargo-semver-checks + + - name: Check semver + run: cargo semver-checks check-release + continue-on-error: true + + - name: Update Cargo.lock + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore: update Cargo.lock" + file_pattern: "Cargo.lock" + + - name: Run release-plz + uses: MarcoIeni/release-plz-action@v0.5.41 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 59d329b..98fdf54 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,103 +1,206 @@ -on: [push, pull_request] - name: Rust +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + CARGO_TERM_COLOR: always jobs: + rust-fmt: + name: rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: rustfmt + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + with: + key: rust/rustfmt + + - name: Run Rust fmt + run: cargo fmt --all -- --check + + toml-fmt: + name: taplo + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Install taplo + run: | + curl -L https://github.com/tamasfe/taplo/releases/latest/download/taplo-linux-x86_64.gz -o taplo.gz + gunzip taplo.gz + chmod +x taplo + sudo mv taplo /usr/local/bin/ + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + with: + key: rust/taplo + + - name: Run TOML fmt + run: taplo fmt --check + check: - name: Check + name: check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v5 + + - name: Install Rust + uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: stable - override: true - - uses: actions-rs/cargo@v1 + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 with: - command: check + key: rust/check - test-std: - name: Test std + - name: Run cargo check + run: cargo check --workspace + + clippy: + name: clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v5 + + - name: Install Rust + uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: stable - override: true - - uses: actions-rs/cargo@v1 + components: clippy + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 with: - command: test + key: rust/clippy - test-nostd: - name: Test no-std + - name: Build + run: cargo build --workspace + + - name: Clippy + run: cargo clippy --all-targets --all-features -- --deny warnings + + test-std: + name: test-std runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v5 + + - name: Install Rust + uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: stable - override: true - - uses: actions-rs/cargo@v1 + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 with: - command: test - args: --no-default-features + key: rust/test + + - name: Run tests + run: cargo test --verbose - format: - name: Format + test-nostd: + name: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v5 + + - name: Install Rust + uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: stable - override: true - - run: rustup component add rustfmt - - uses: actions-rs/cargo@v1 + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 with: - command: fmt - args: --all -- --check + key: rust/test + + - name: Run tests + run: cargo test --verbose --no-default-features - quality: - name: Quality + doc: + name: doc runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v5 + + - name: Install Rust + uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: stable - override: true - - run: rustup component add clippy - - uses: actions-rs/cargo@v1 + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 with: - command: clippy - args: -- -D warnings + key: rust/doc + + - name: Run cargo doc to check for warnings + run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features coverage: - name: Coverage + name: coverage runs-on: ubuntu-latest + continue-on-error: true steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v5 + + - name: Install Rust + uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: stable - override: true - - uses: actions-rs/tarpaulin@v0.1 - - uses: codecov/codecov-action@v1.0.5 + components: llvm-tools-preview + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Run cargo-llvm-cov + run: cargo llvm-cov --all-features --workspace --html --output-dir target/coverage + + - name: Upload coverage to Artifacts + uses: actions/upload-artifact@v4 with: - token: ${{secrets.CODECOV_TOKEN}} + name: coverage-report + path: target/coverage - security: - name: Security + udeps: + name: udeps runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions-rs/audit-check@v1 + - uses: actions/checkout@v5 + + - name: Install Rust + uses: dtolnay/rust-toolchain@master with: - token: ${{ secrets.GITHUB_TOKEN }} + toolchain: stable + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + with: + key: rust/udeps + + - name: Install cargo-udeps + uses: taiki-e/install-action@v2 + with: + tool: cargo-udeps + + - name: Run cargo udeps + run: cargo +nightly udeps --all-targets --workspace diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 44bfd4b..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,71 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [0.5.0] - 2021-03-14 -### Added -- Zeroize memory on drop for generated secret shares - -## [0.4.3] - 2021-02-04 -### Changed -- Upgraded project dependencies - -## [0.4.2] - 2020-08-03 -### Fixed -- Small fix in docs - -## [0.4.1] - 2020-04-23 -### Added -- Fuzz tests - -### Fixed -- Unexpected panic when trying to recover secret from different length shares -- Unexpected panic when trying to convert less than 2 bytes to `Share` - -## [0.4.0] - 2020-04-02 -### Added -- It is now possible to compile without `std` with `--no-default-features` - -## [0.3.3] - 2020-03-23 -### Changed -- Fix codecov badge - -## [0.3.2] - 2020-03-09 -### Changed -- Share structs now derives the `Clone` trait - -## [0.3.1] - 2020-01-23 -### Changed -- Sharks recover method now accepts any iterable collection - -## [0.3.0] - 2020-01-22 -### Added -- Share struct which allows to convert from/to byte vectors - -### Changed -- Methods use the new Share struct, instead of (GF245, Vec) tuples - -## [0.2.0] - 2020-01-21 -### Added -- Computations performed over GF256 (much faster) -- Secret can now be arbitrarily long - -### Changed -- Some method names and docs -- Maximum number of shares enforced by Rust static types instead of conditional branching - -### Removed -- Modular arithmetic around Mersenne primes - -## [0.1.1] - 2020-01-13 -### Fixed -- Typo in cargo description - -### Removed -- Maintenance badges in cargo file - -## [0.1.0] - 2020-01-13 -### Added -- Initial version diff --git a/COPYRIGHT b/COPYRIGHT deleted file mode 100644 index 38c3e0d..0000000 --- a/COPYRIGHT +++ /dev/null @@ -1,12 +0,0 @@ -Copyrights in the Sharks project are retained by their contributors. No -copyright assignment is required to contribute to the Sharks project. - -For full authorship information, see the version control history. - -Except as otherwise noted (below and/or in individual files), Sharks is -licensed under the Apache License, Version 2.0 or - or the MIT license - or , at your option. - -The Sharks project includes code from the Rust project -published under these same licenses. diff --git a/Cargo.toml b/Cargo.toml index 3200ae8..b27cd6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,23 @@ [package] -name = "sharks" -version = "0.5.0" -authors = ["Aitor Ruano "] +name = "ssskit" +version = "0.1.0" +authors = [ + "Multifactor, Inc. ", + "Sambhav Dusad ", +] description = "Fast, small and secure Shamir's Secret Sharing library crate" -homepage = "https://github.com/c0dearm/sharks" -repository = "https://github.com/c0dearm/sharks" +homepage = "https://github.com/multifactor/ssskit" +repository = "https://github.com/multifactor/ssskit" +documentation = "https://docs.rs/ssskit" readme = "README.md" keywords = ["shamir", "secret", "sharing", "share", "crypto"] categories = ["algorithms", "cryptography", "mathematics"] -license = "MIT/Apache-2.0" -edition = "2018" +license = "MIT OR Apache-2.0" +edition = "2021" +include = ["src/**", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] [badges] maintenance = { status = "actively-developed" } -codecov = { repository = "c0dearm/sharks" } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] @@ -23,15 +27,40 @@ fuzzing = ["std", "arbitrary"] zeroize_memory = ["zeroize"] [dependencies] -rand = { version = "0.8", default-features = false } -hashbrown = "0.9" -arbitrary = { version = "0.4.7", features = ["derive"], optional = true } -zeroize = { version = "1.2.0", features = ["zeroize_derive"], optional = true } +rand = { version = "0.8.5", default-features = false } +hashbrown = { version = "0.16", default-features = false, features = [ + "default-hasher", +] } +arbitrary = { version = "1.4.2", default-features = false, features = [ + "derive", +], optional = true } +zeroize = { version = "1.8.2", default-features = false, features = [ + "alloc", + "zeroize_derive", +], optional = true } [dev-dependencies] -criterion = "0.3" -rand_chacha = "0.3" +criterion = "0.7.0" +rand_chacha = "0.3.1" +rstest = "0.26.1" [[bench]] name = "benchmarks" harness = false + +[package.metadata.docs.rs] +features = ["std"] +all-features = false +rustdoc-args = ["--cfg", "docsrs"] + +[profile.release] +opt-level = 3 +codegen-units = 1 +incremental = false +debug = 1 +strip = true + +[profile.bench] +inherits = "release" +debug = 1 +strip = false diff --git a/README.md b/README.md index 565f0fa..d1f83f3 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,17 @@ -# Sharks +# SSSKit -[![Rust](https://github.com/c0dearm/sharks/workflows/Rust/badge.svg?branch=master)](https://github.com/c0dearm/sharks/actions) -[![Crates](https://img.shields.io/crates/v/sharks.svg)](https://crates.io/crates/sharks) -[![Docs](https://docs.rs/sharks/badge.svg)](https://docs.rs/sharks) -[![Codecov](https://codecov.io/gh/c0dearm/sharks/branch/master/graph/badge.svg)](https://codecov.io/gh/c0dearm/sharks) -[![License](https://camo.githubusercontent.com/47069b7e06b64b608c692a8a7f40bc6915cf629c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865322e302532464d49542d626c75652e737667)](https://github.com/c0dearm/sharks/blob/master/COPYRIGHT) +[![Rust](https://github.com/multifactor/ssskit/workflows/Rust/badge.svg?branch=master)](https://github.com/multifactor/ssskit/actions) +[![Crates](https://img.shields.io/crates/v/ssskit.svg)](https://crates.io/crates/ssskit) +[![Docs](https://docs.rs/ssskit/badge.svg)](https://docs.rs/ssskit) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE-MIT) -Fast, small and secure [Shamir's Secret Sharing](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing) library crate +Fast, small, generic and secure [Shamir's Secret Sharing](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing) library crate + +> [!Note] +> This repository is a fork of [c0dearm/sharks](https://github.com/c0dearm/sharks), but will be actively developed and maintained by Multifactor. Documentation: -- [API reference (docs.rs)](https://docs.rs/sharks) +- [API reference (docs.rs)](https://docs.rs/ssskit) ## Usage @@ -17,17 +19,17 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -sharks = "0.4" +ssskit = "0.1" ``` If your environment doesn't support `std`: ```toml [dependencies] -sharks = { version = "0.4", default-features = false } +ssskit = { version = "0.1", default-features = false } ``` -To get started using Sharks, see the [Rust docs](https://docs.rs/sharks) +To get started using ssskit, see the [Rust docs](https://docs.rs/ssskit) ## Features @@ -37,6 +39,12 @@ The API is simple and to the point, with minimal configuration. ### Fast and small The code is as idiomatic and clean as possible, with minimum external dependencies. +### Generic on irreducible polynomial +GF256 field support largely used primitive irreducible polynomials like 0x11B (AES), 0x11D (RS codes), 0x12B (Reed-Solomon codes), and more. + +### Compile time asserts +Any operation on the field with a non-whitelisted polynomial will fail to build due to const assertions done at compile time. + ### Secure by design The implementation forbids the user to choose parameters that would result in an insecure application, like generating more shares than what's allowed by the finite field length. @@ -54,15 +62,13 @@ You can run them with `cargo test` and `cargo bench`. ### Benchmark results [min mean max] -| CPU | obtain_shares_dealer | step_shares_dealer | recover_secret | share_from_bytes | share_to_bytes | -| ----------------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | -| Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz | [1.4321 us 1.4339 us 1.4357 us] | [1.3385 ns 1.3456 ns 1.3552 ns] | [228.77 us 232.17 us 236.23 us] | [24.688 ns 25.083 ns 25.551 ns] | [22.832 ns 22.910 ns 22.995 ns] | -| Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz | [1.3439 us 1.3499 us 1.3562 us] | [1.5416 ns 1.5446 ns 1.5481 ns] | [197.46 us 198.37 us 199.22 us] | [20.455 ns 20.486 ns 20.518 ns] | [18.726 ns 18.850 ns 18.993 ns] | -| Apple M1 ARM (Macbook Air) | [3.3367 us 3.3629 us 3.4058 us] | [741.75 ps 742.65 ps 743.52 ps] | [210.14 us 210.23 us 210.34 us] | [27.567 ns 27.602 ns 27.650 ns] | [26.716 ns 26.735 ns 26.755 ns] | +| CPU | obtain_shares_dealer | step_shares_dealer | recover_secret | share_from_bytes | share_to_bytes | +| ------------ | ------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | +| Apple M1 Pro | [2.6976 µs 2.7007 µs 2.7039 µs] | [938.79 ps 939.83 ps 941.04 ps] | [190.00 µs 190.46 µs 191.06 µs] | [31.176 ns 31.311 ns 31.529 ns] | [23.196 ns 23.211 ns 23.230 ns] | # Contributing -If you find a vulnerability, bug or would like a new feature, [open a new issue](https://github.com/c0dearm/sharks/issues/new). +If you find a vulnerability, bug or would like a new feature, [open a new issue](https://github.com/multifactor/ssskit/issues/new). To introduce your changes into the codebase, submit a Pull Request. @@ -70,8 +76,11 @@ Many thanks! # License -Sharks is distributed under the terms of both the MIT license and the +ssskit is distributed under the terms of both the MIT license and the Apache License (Version 2.0). -See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT), and -[COPYRIGHT](COPYRIGHT) for details. +See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. + +# Acknowledgments + +This project is derived from the excellent work in the original [sharks](https://github.com/c0dearm/sharks) repository by Aitor Ruano (`c0dearm`). We appreciate Aitor's foundational contributions, on which this crate is based. diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index b80eb17..48aeaf8 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -1,34 +1,37 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use std::convert::TryFrom; +use criterion::{criterion_group, criterion_main, Criterion}; +use std::hint::black_box; -use sharks::{Share, Sharks}; +use ssskit::{SecretSharing, Share}; -fn dealer(c: &mut Criterion) { - let sharks = Sharks(255); - let mut dealer = sharks.dealer(&[1]); +const POLY: u16 = 0x11d_u16; + +fn dealer(c: &mut Criterion) { + let sss = SecretSharing::(255); + let mut dealer = sss.dealer(&[1]); c.bench_function("obtain_shares_dealer", |b| { - b.iter(|| sharks.dealer(black_box(&[1]))) + b.iter(|| sss.dealer(black_box(&[1]))) }); c.bench_function("step_shares_dealer", |b| b.iter(|| dealer.next())); } -fn recover(c: &mut Criterion) { - let sharks = Sharks(255); - let shares: Vec = sharks.dealer(&[1]).take(255).collect(); +fn recover(c: &mut Criterion) { + let sss = SecretSharing::(255); + let dealer = sss.dealer(&[1]); + let shares = dealer.take(255).collect::>>(); c.bench_function("recover_secret", |b| { - b.iter(|| sharks.recover(black_box(shares.as_slice()))) + b.iter(|| sss.recover(black_box(&shares))) }); } -fn share(c: &mut Criterion) { +fn share(c: &mut Criterion) { let bytes_vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let bytes = bytes_vec.as_slice(); - let share = Share::try_from(bytes).unwrap(); + let share = Share::::try_from(bytes).unwrap(); c.bench_function("share_from_bytes", |b| { - b.iter(|| Share::try_from(black_box(bytes))) + b.iter(|| Share::::try_from(black_box(bytes))) }); c.bench_function("share_to_bytes", |b| { @@ -36,5 +39,5 @@ fn share(c: &mut Criterion) { }); } -criterion_group!(benches, dealer, recover, share); +criterion_group!(benches, dealer::, recover::, share::); criterion_main!(benches); diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 24b3742..0000000 --- a/codecov.yml +++ /dev/null @@ -1,20 +0,0 @@ -codecov: - require_ci_to_pass: yes - -coverage: - precision: 2 - round: down - range: "90...100" - -parsers: - gcov: - branch_detection: - conditional: yes - loop: yes - method: no - macro: no - -comment: - layout: "reach,diff,flags,tree" - behavior: default - require_changes: no diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index f7e4f39..f669217 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -1,19 +1,19 @@ [package] -name = "sharks-fuzz" +name = "ssskit-fuzz" version = "0.0.0" authors = ["Automatically generated"] publish = false -edition = "2018" +edition = "2024" [package.metadata] cargo-fuzz = true [dependencies] -libfuzzer-sys = "0.3" -arbitrary = { version = "0.4.2", features = ["derive"] } +libfuzzer-sys = "0.4" +arbitrary = { version = "1.4", features = ["derive"] } -[dependencies.sharks] +[dependencies.ssskit] path = ".." features = ["fuzzing"] diff --git a/fuzz/fuzz_targets/deserialize_share.rs b/fuzz/fuzz_targets/deserialize_share.rs index 74be5d1..78be895 100644 --- a/fuzz/fuzz_targets/deserialize_share.rs +++ b/fuzz/fuzz_targets/deserialize_share.rs @@ -1,8 +1,9 @@ #![no_main] use core::convert::TryFrom; use libfuzzer_sys::fuzz_target; -use sharks::Share; +use ssskit::Share; fuzz_target!(|data: &[u8]| { - let _share = Share::try_from(data); + const POLY: u16 = 0x11d_u16; + let _share = Share::::try_from(data); }); diff --git a/fuzz/fuzz_targets/generate_shares.rs b/fuzz/fuzz_targets/generate_shares.rs index 98a6f9e..f2c063f 100644 --- a/fuzz/fuzz_targets/generate_shares.rs +++ b/fuzz/fuzz_targets/generate_shares.rs @@ -2,7 +2,7 @@ use libfuzzer_sys::fuzz_target; use arbitrary::Arbitrary; -use sharks::{Share, Sharks}; +use ssskit::{SecretSharing, Share}; #[derive(Debug, Arbitrary)] struct Parameters { @@ -12,8 +12,9 @@ struct Parameters { } fuzz_target!(|params: Parameters| { - let sharks = Sharks(params.threshold); - let dealer = sharks.dealer(¶ms.secret); + const POLY: u16 = 0x11d_u16; + let sss = SecretSharing::(params.threshold); + let dealer = sss.dealer(¶ms.secret); - let _shares: Vec = dealer.take(params.n_shares).collect(); + let _shares = dealer.take(params.n_shares).collect::>>(); }); diff --git a/fuzz/fuzz_targets/recover.rs b/fuzz/fuzz_targets/recover.rs index 1bdc33f..7d27cf5 100644 --- a/fuzz/fuzz_targets/recover.rs +++ b/fuzz/fuzz_targets/recover.rs @@ -2,15 +2,16 @@ use libfuzzer_sys::fuzz_target; use arbitrary::Arbitrary; -use sharks::{Share, Sharks}; +use ssskit::{SecretSharing, Share}; +const POLY: u16 = 0x11d_u16; #[derive(Debug, Arbitrary)] struct Parameters { pub threshold: u8, - pub shares: Vec, + pub shares: Vec>, } fuzz_target!(|params: Parameters| { - let sharks = Sharks(params.threshold); - let _secret = sharks.recover(¶ms.shares); + let sss = SecretSharing::(params.threshold); + let _secret = sss.recover(¶ms.shares); }); diff --git a/fuzz/fuzz_targets/serialize_share.rs b/fuzz/fuzz_targets/serialize_share.rs index e45fe95..c859486 100644 --- a/fuzz/fuzz_targets/serialize_share.rs +++ b/fuzz/fuzz_targets/serialize_share.rs @@ -1,8 +1,9 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use sharks::Share; +use ssskit::Share; -fuzz_target!(|share: Share| { +const POLY: u16 = 0x11d_u16; +fuzz_target!(|share: Share| { let _data: Vec = (&share).into(); }); diff --git a/src/field.rs b/src/field.rs index 05b4960..67189f9 100644 --- a/src/field.rs +++ b/src/field.rs @@ -1,5 +1,5 @@ -// Basic operations overrided for the Galois Field 256 (2**8) -// Uses pre-calculated tables for 0x11d primitive polynomial (x**8 + x**4 + x**3 + x**2 + 1) +//! Basic operations overrided for the Galois Field 256 (2**8) +//! Implements the operations over general irreducible polynomials. use core::iter::{Product, Sum}; use core::ops::{Add, Div, Mul, Sub}; @@ -10,123 +10,242 @@ use arbitrary::Arbitrary; #[cfg(feature = "zeroize_memory")] use zeroize::Zeroize; -const LOG_TABLE: [u8; 256] = [ - 0x00, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, - 0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71, - 0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, - 0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6, - 0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, - 0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, - 0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, - 0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, - 0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, - 0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, - 0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, - 0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, - 0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6, - 0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, - 0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, - 0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf, -]; +#[inline] +// GF(2^8) multiplication via Russian peasant method with polynomial reduction. +// `poly` encodes the irreducible degree-8 polynomial (e.g., 0x11D for x^8 + x^4 + x^3 + x + 1). +// When the x^8 term would appear (carry), we reduce by XOR-ing with `poly` after the left shift. +// Returns the canonical byte representative in GF(256). +const fn gf256_mul(a: u8, b: u8, poly: u16) -> u8 { + let mut result = 0u16; + let mut a_val = a as u16; + let mut b_val = b as u16; + + while b_val > 0 { + if (b_val & 1) == 1 { + result ^= a_val; + } + let carry = (a_val & 0x80) != 0; + a_val <<= 1; + if carry { + a_val ^= poly; + } + b_val >>= 1; + } + + (result & 0xFF) as u8 +} + +#[inline] +// Constant-time-ish binary exponentiation in GF(2^8). +// Used by generator checks and table construction. +const fn gf256_pow(base: u8, exponent: u8, poly: u16) -> u8 { + let mut result = 1u8; + let mut base_val = base; + let mut exponent_val = exponent; + + while exponent_val > 0 { + if (exponent_val & 1) == 1 { + result = gf256_mul(result, base_val, poly); + } + base_val = gf256_mul(base_val, base_val, poly); + exponent_val >>= 1; + } + result +} -const EXP_TABLE: [u8; 512] = [ - 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, - 0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, - 0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, - 0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, - 0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, - 0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, - 0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, - 0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, - 0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, - 0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, - 0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, - 0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, - 0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, - 0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09, - 0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16, - 0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x01, - 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c, - 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x9d, - 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46, - 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f, - 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 0xfd, - 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9, - 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81, - 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85, - 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8, - 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6, - 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3, - 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82, - 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51, - 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09, 0x12, - 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16, 0x2c, - 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x01, 0x02, +/// Known primitive degree-8 polynomials over GF(2). +/// Any `POLY` must be one of these to give a field with multiplicative group of order 255. +/// +/// References: +/// - [Primitive elements and irreducible polynomials of GF(256)](https://codyplanteen.com/assets/rs/gf256_prim.pdf) +pub const PRIMITIVE_POLYS: &[u16] = &[ + 0x11B, 0x11D, 0x12B, 0x12D, 0x139, 0x13F, 0x14D, 0x15F, 0x163, 0x165, 0x169, 0x171, 0x177, + 0x17B, 0x187, 0x18B, 0x18D, 0x19F, 0x1A3, 0x1A9, 0x1B1, 0x1BD, 0x1C3, 0x1CF, 0x1D7, 0x1DD, + 0x1E7, 0x1F3, 0x1F5, 0x1F9, ]; +#[inline] +/// Simple membership check for compile time primitive polynomial check. +const fn is_primitive(poly: u16) -> bool { + let mut i = 0; + + while i < PRIMITIVE_POLYS.len() { + if PRIMITIVE_POLYS[i] == poly { + return true; + } + i += 1; + } + false +} + +/// Field element type parametrized by the irreducible polynomial at the type level. +/// Different `POLY` values produce distinct, non-interoperable types. #[derive(Debug, PartialEq, Clone)] #[cfg_attr(feature = "fuzzing", derive(Arbitrary))] #[cfg_attr(feature = "zeroize_memory", derive(Zeroize))] #[cfg_attr(feature = "zeroize_memory", zeroize(drop))] -pub struct GF256(pub u8); +pub struct GF256(pub u8); + +/// Precomputed tables for fast log/exp arithmetic. +/// Note: `exp` is duplicated to length 512 so additions/subtractions of logs can index without explicit mod 255. +pub struct Tables { + pub log: [u8; 256], + pub exp: [u8; 512], +} + +impl Default for Tables { + fn default() -> Self { + Self::new() + } +} + +#[inline] +/// Checks if `x` has multiplicative order 255 by testing x^(255/p) != 1 for all prime factors p of 255 (=3,5,17). +const fn is_primitive_element(x: u8) -> bool { + const FACTORS: [u8; 3] = [3, 5, 17]; + + let mut i = 0; + while i < FACTORS.len() { + if gf256_pow(x, 255 / FACTORS[i], POLY) == 1 { + return false; + } + i += 1; + } + gf256_pow(x, 255, POLY) == 1 +} + +#[inline] +/// Linear search for a primitive element (generator) of GF(256) under `POLY`. +const fn find_generator() -> u8 { + let mut i = 1u16; + while i <= 255 { + if is_primitive_element::(i as u8) { + return i as u8; + } + i += 1; + } + panic!("No primitive element found"); +} + +impl Tables { + /// Builds log/exp tables at compile time; panics at compile time if `POLY` is not primitive. + pub const fn new() -> Self { + assert!(is_primitive(POLY), "POLY must be primitive"); + + let mut log = [0u8; 256]; + let mut exp = [0u8; 512]; + + let gen = find_generator::(); + + let mut i = 0usize; + let mut x = 1u8; + while i < 255 { + exp[i] = x; + log[x as usize] = i as u8; + x = gf256_mul(x, gen, POLY); + i += 1; + } + + let mut j = 255usize; + // Duplicate exp table to avoid modulus: exp[i + 255] == exp[i]. + while j < 512 { + exp[j] = exp[j - 255]; + j += 1; + } + + Self { log, exp } + } +} + +impl GF256 { + /// Compile-time assertion tying this type to a primitive polynomial. + const POLY_CHECK: () = assert!(is_primitive(POLY), "POLY must be primitive"); + /// Precompute tables once per concrete `POLY` type. + pub const TABLES: Tables = Tables::new(); + + pub fn add(self, other: Self) -> Self { + #[allow(path_statements)] + Self::POLY_CHECK; // const check will be amortized by the compiler + Self(self.0 ^ other.0) + } + + pub fn sub(self, other: Self) -> Self { + #[allow(path_statements)] + Self::POLY_CHECK; + Self(self.0 ^ other.0) + } + + pub fn mul(self, other: Self) -> Self { + // Map to log space; zeros are handled explicitly to avoid using log(0). + let log_x = Self::TABLES.log[self.0 as usize] as usize; + let log_y = Self::TABLES.log[other.0 as usize] as usize; + + if self.0 == 0 || other.0 == 0 { + Self(0) + } else { + // Addition in log space corresponds to multiplication in the field. + Self(Self::TABLES.exp[log_x + log_y]) + } + } + + pub fn div(self, other: Self) -> Self { + // Map to log space; requires non-zero divisor. + let log_x = Self::TABLES.log[self.0 as usize] as usize; + let log_y = Self::TABLES.log[other.0 as usize] as usize; + + if self.0 == 0 { + Self(0) + } else { + // Subtraction in log space corresponds to division; +255 implements wrap-around. + // Precondition: `other` must be non-zero; dividing by zero is undefined for this API. + Self(Self::TABLES.exp[log_x + 255 - log_y]) + } + } +} #[allow(clippy::suspicious_arithmetic_impl)] -impl Add for GF256 { - type Output = GF256; +impl Add for GF256 { + type Output = Self; fn add(self, other: Self) -> Self::Output { - Self(self.0 ^ other.0) + self.add(other) } } #[allow(clippy::suspicious_arithmetic_impl)] -impl Sub for GF256 { +impl Sub for GF256 { type Output = Self; fn sub(self, other: Self) -> Self::Output { - Self(self.0 ^ other.0) + self.sub(other) } } #[allow(clippy::suspicious_arithmetic_impl)] -impl Mul for GF256 { +impl Mul for GF256 { type Output = Self; fn mul(self, other: Self) -> Self::Output { - let log_x = LOG_TABLE[self.0 as usize] as usize; - let log_y = LOG_TABLE[other.0 as usize] as usize; - - if self.0 == 0 || other.0 == 0 { - Self(0) - } else { - Self(EXP_TABLE[log_x + log_y]) - } + self.mul(other) } } #[allow(clippy::suspicious_arithmetic_impl)] -impl Div for GF256 { +impl Div for GF256 { type Output = Self; fn div(self, other: Self) -> Self::Output { - let log_x = LOG_TABLE[self.0 as usize] as usize; - let log_y = LOG_TABLE[other.0 as usize] as usize; - - if self.0 == 0 { - Self(0) - } else { - Self(EXP_TABLE[log_x + 255 - log_y]) - } + self.div(other) } } -impl Sum for GF256 { +impl Sum for GF256 { fn sum>(iter: I) -> Self { iter.fold(Self(0), |acc, x| acc + x) } } -impl Product for GF256 { +impl Product for GF256 { fn product>(iter: I) -> Self { iter.fold(Self(1), |acc, x| acc * x) } @@ -134,8 +253,68 @@ impl Product for GF256 { #[cfg(test)] mod tests { - use super::{EXP_TABLE, GF256, LOG_TABLE}; + use super::GF256; use alloc::vec; + use rstest::rstest; + + const POLY: u16 = 0x11d_u16; + const LOG_TABLE: [u8; 256] = [ + 0x00, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, + 0x4b, 0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, + 0x4c, 0x71, 0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, + 0x12, 0x82, 0x45, 0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x09, 0x78, + 0x4d, 0xe4, 0x72, 0xa6, 0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, + 0xb3, 0x10, 0x91, 0x22, 0x88, 0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, + 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, 0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, + 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, 0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, + 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, 0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, + 0x0d, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, 0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, + 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, 0x37, 0x3f, 0xd1, 0x5b, 0x95, + 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, 0xf2, 0x56, 0xd3, 0xab, + 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, 0x1f, 0x2d, 0x43, + 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6, 0x6c, 0xa1, + 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, 0xcb, + 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, + 0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, + 0xaf, + ]; + const EXP_TABLE: [u8; 512] = [ + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, + 0x26, 0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, + 0x60, 0xc0, 0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, + 0xc1, 0x9f, 0x23, 0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, + 0xb9, 0x6f, 0xde, 0xa1, 0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, + 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, + 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, + 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, + 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, + 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, + 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6, 0xd1, 0xbf, 0x63, 0xc6, + 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3, 0xdb, 0xab, 0x4b, + 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82, 0x19, 0x32, + 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51, 0xa2, + 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09, 0x12, + 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16, + 0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, + 0x26, 0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, + 0x60, 0xc0, 0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, + 0xc1, 0x9f, 0x23, 0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, + 0xb9, 0x6f, 0xde, 0xa1, 0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, + 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, + 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, + 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, + 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, + 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, + 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6, 0xd1, 0xbf, 0x63, 0xc6, + 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3, 0xdb, 0xab, 0x4b, + 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82, 0x19, 0x32, + 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51, 0xa2, + 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09, 0x12, + 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16, + 0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, + 0x01, 0x02, + ]; #[test] fn add_works() { @@ -157,7 +336,10 @@ mod tests { ]; for (i, a) in answers.iter().enumerate() { - assert_eq!((GF256(LOG_TABLE[i]) + GF256(EXP_TABLE[i])).0, *a); + assert_eq!( + (GF256::(LOG_TABLE[i]) + GF256::(EXP_TABLE[i])).0, + *a + ); } } @@ -186,7 +368,10 @@ mod tests { ]; for (i, a) in answers.iter().enumerate() { - assert_eq!((GF256(LOG_TABLE[i]) * GF256(EXP_TABLE[i])).0, *a); + assert_eq!( + (GF256::(LOG_TABLE[i]) * GF256::(EXP_TABLE[i])).0, + *a + ); } } @@ -210,19 +395,130 @@ mod tests { ]; for (i, a) in answers.iter().enumerate() { - assert_eq!((GF256(LOG_TABLE[i]) / GF256(EXP_TABLE[i])).0, *a); + assert_eq!( + (GF256::(LOG_TABLE[i]) / GF256::(EXP_TABLE[i])).0, + *a + ); } } #[test] fn sum_works() { - let values = vec![GF256(0x53), GF256(0xCA), GF256(0)]; - assert_eq!(values.into_iter().sum::().0, 0x99); + let values = vec![GF256::(0x53), GF256::(0xCA), GF256::(0)]; + assert_eq!(values.into_iter().sum::>().0, 0x99); } #[test] fn product_works() { - let values = vec![GF256(1), GF256(1), GF256(4)]; - assert_eq!(values.into_iter().product::().0, 4); + let values = vec![GF256::(1), GF256::(1), GF256::(4)]; + assert_eq!(values.into_iter().product::>().0, 4); + } + + #[rstest] + #[case(0x11B)] + #[case(0x11D)] + #[case(0x12B)] + #[case(0x12D)] + fn primitive_poly(#[case] poly: u16) { + assert!(super::is_primitive(poly)); } + + #[rstest] + #[case(0x100)] + #[case(0x102)] + #[case(0x103)] + fn non_primitive_poly(#[case] poly: u16) { + assert!(!super::is_primitive(poly)); + } + + // Shared test body generic over the primitive polynomial + fn run_ops_all() { + // Addition/Subtraction properties across full byte domain + let mut a_val = 0u16; + while a_val <= 255 { + let a = a_val as u8; + assert_eq!((GF256::<{ POLY }>(a) + GF256::<{ POLY }>(0)).0, a); + assert_eq!((GF256::<{ POLY }>(a) - GF256::<{ POLY }>(0)).0, a); + assert_eq!((GF256::<{ POLY }>(a) - GF256::<{ POLY }>(a)).0, 0); + assert_eq!((GF256::<{ POLY }>(a) + GF256::<{ POLY }>(a)).0, 0); + a_val += 1; + } + + // Sampled values to validate mul/div against reference implementation + let samples: [u8; 16] = [0, 1, 2, 3, 4, 7, 11, 13, 29, 63, 64, 95, 127, 128, 199, 255]; + + for &x in &samples { + for &y in &samples { + // mul equals reference bitwise implementation with POLY + let prod = (GF256::<{ POLY }>(x) * GF256::<{ POLY }>(y)).0; + let ref_prod = super::gf256_mul(x, y, POLY); + assert_eq!(prod, ref_prod); + + // Division inverse property + if y != 0 { + assert_eq!( + ((GF256::<{ POLY }>(x) * GF256::<{ POLY }>(y)) / GF256::<{ POLY }>(y)).0, + x + ); + } + + // Zero rules + assert_eq!((GF256::<{ POLY }>(0) * GF256::<{ POLY }>(y)).0, 0); + assert_eq!((GF256::<{ POLY }>(x) * GF256::<{ POLY }>(0)).0, 0); + + // Self-division for non-zero + if x != 0 { + assert_eq!((GF256::<{ POLY }>(x) / GF256::<{ POLY }>(x)).0, 1); + } else { + assert_eq!((GF256::<{ POLY }>(0) / GF256::<{ POLY }>(1)).0, 0); + } + } + } + } + + // Minimal macro: declare per-test constant and call the shared test body + macro_rules! gen_ops_tests { + ( $( ($poly:expr, $name:ident) ),+ $(,)? ) => { + $( + #[test] + fn $name() { + const POLY: u16 = $poly; + run_ops_all::<{ POLY }>(); + } + )+ + }; + } + + gen_ops_tests!( + (0x11B, ops_poly_11b), + (0x11D, ops_poly_11d), + (0x12B, ops_poly_12b), + (0x12D, ops_poly_12d), + (0x139, ops_poly_139), + (0x13F, ops_poly_13f), + (0x14D, ops_poly_14d), + (0x15F, ops_poly_15f), + (0x163, ops_poly_163), + (0x165, ops_poly_165), + (0x169, ops_poly_169), + (0x171, ops_poly_171), + (0x177, ops_poly_177), + (0x17B, ops_poly_17b), + (0x187, ops_poly_187), + (0x18B, ops_poly_18b), + (0x18D, ops_poly_18d), + (0x19F, ops_poly_19f), + (0x1A3, ops_poly_1a3), + (0x1A9, ops_poly_1a9), + (0x1B1, ops_poly_1b1), + (0x1BD, ops_poly_1bd), + (0x1C3, ops_poly_1c3), + (0x1CF, ops_poly_1cf), + (0x1D7, ops_poly_1d7), + (0x1DD, ops_poly_1dd), + (0x1E7, ops_poly_1e7), + (0x1F3, ops_poly_1f3), + (0x1F5, ops_poly_1f5), + (0x1F9, ops_poly_1f9), + ); } diff --git a/src/lib.rs b/src/lib.rs index ecc6cef..2b853b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,39 +1,53 @@ //! Fast, small and secure [Shamir's Secret Sharing](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing) library crate //! -//! Usage example (std): +//! # Usage +//! ## (std) +//! //! ``` -//! use sharks::{ Sharks, Share }; +//! use ssskit::{ SecretSharing, Share }; //! -//! // Set a minimum threshold of 10 shares -//! let sharks = Sharks(10); +//! # const POLY: u16 = 0x11d_u16; +//! // Set a minimum threshold of 10 shares for an irreducible polynomial POLY +//! let sss = SecretSharing::(10); //! // Obtain an iterator over the shares for secret [1, 2, 3, 4] //! # #[cfg(feature = "std")] //! # { -//! let dealer = sharks.dealer(&[1, 2, 3, 4]); +//! let dealer = sss.dealer(&[1, 2, 3, 4]); //! // Get 10 shares -//! let shares: Vec = dealer.take(10).collect(); +//! let shares = dealer.take(10).collect::>>(); //! // Recover the original secret! -//! let secret = sharks.recover(shares.as_slice()).unwrap(); +//! let secret = sss.recover(shares.as_slice()).unwrap(); //! assert_eq!(secret, vec![1, 2, 3, 4]); //! # } //! ``` //! -//! Usage example (no std): +//! ## (no std) +//! //! ``` -//! use sharks::{ Sharks, Share }; -//! use rand_chacha::rand_core::SeedableRng; +//! use ssskit::{ SecretSharing, Share }; +//! use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; //! +//! # const POLY: u16 = 0x11d_u16; //! // Set a minimum threshold of 10 shares -//! let sharks = Sharks(10); +//! let sss = SecretSharing::(10); //! // Obtain an iterator over the shares for secret [1, 2, 3, 4] //! let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); -//! let dealer = sharks.dealer_rng(&[1, 2, 3, 4], &mut rng); +//! let dealer = sss.dealer_rng::(&[1, 2, 3, 4], &mut rng); //! // Get 10 shares -//! let shares: Vec = dealer.take(10).collect(); +//! let shares = dealer.take(10).collect::>>(); //! // Recover the original secret! -//! let secret = sharks.recover(shares.as_slice()).unwrap(); +//! let secret = sss.recover(shares.as_slice()).unwrap(); //! assert_eq!(secret, vec![1, 2, 3, 4]); //! ``` +//! +//! # Irreducible Polynomials +//! +//! This crate supports all 30 degree-8 irreducible polynomials over GF(2). +//! See the exported list [`PRIMITIVE_POLYS`] (defined in `field.rs`). +//! +//! Commonly used polynomials: +//! - 0x11B — used in AES (Rijndael) +//! - 0x11D — commonly used in Reed–Solomon (e.g., QR codes) #![cfg_attr(not(feature = "std"), no_std)] mod field; @@ -46,6 +60,7 @@ use alloc::vec::Vec; use hashbrown::HashSet; use field::GF256; +pub use field::PRIMITIVE_POLYS; pub use share::Share; /// Tuple struct which implements methods to generate shares and recover secrets over a 256 bits Galois Field. @@ -53,23 +68,24 @@ pub use share::Share; /// /// Usage example: /// ``` -/// # use sharks::{ Sharks, Share }; +/// # use ssskit::{ SecretSharing, Share }; +/// # const POLY: u16 = 0x11d_u16; /// // Set a minimum threshold of 10 shares -/// let sharks = Sharks(10); +/// let sss = SecretSharing::(10); /// // Obtain an iterator over the shares for secret [1, 2, 3, 4] /// # #[cfg(feature = "std")] /// # { -/// let dealer = sharks.dealer(&[1, 2, 3, 4]); +/// let dealer = sss.dealer(&[1, 2, 3, 4]); /// // Get 10 shares -/// let shares: Vec = dealer.take(10).collect(); +/// let shares = dealer.take(10).collect::>>(); /// // Recover the original secret! -/// let secret = sharks.recover(shares.as_slice()).unwrap(); +/// let secret = sss.recover(&shares).unwrap(); /// assert_eq!(secret, vec![1, 2, 3, 4]); /// # } /// ``` -pub struct Sharks(pub u8); +pub struct SecretSharing(pub u8); -impl Sharks { +impl SecretSharing { /// This method is useful when `std` is not available. For typical usage /// see the `dealer` method. /// @@ -79,19 +95,20 @@ impl Sharks { /// /// Example: /// ``` - /// # use sharks::{ Sharks, Share }; - /// # use rand_chacha::rand_core::SeedableRng; - /// # let sharks = Sharks(3); + /// # use ssskit::{ SecretSharing, Share }; + /// # use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; + /// # const POLY: u16 = 0x11d_u16; + /// # let sss = SecretSharing::(3); /// // Obtain an iterator over the shares for secret [1, 2] /// let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); - /// let dealer = sharks.dealer_rng(&[1, 2], &mut rng); + /// let dealer = sss.dealer_rng::(&[1, 2], &mut rng); /// // Get 3 shares - /// let shares: Vec = dealer.take(3).collect(); + /// let shares = dealer.take(3).collect::>>(); pub fn dealer_rng( &self, secret: &[u8], rng: &mut R, - ) -> impl Iterator { + ) -> impl Iterator> { let mut polys = Vec::with_capacity(secret.len()); for chunk in secret { @@ -106,14 +123,15 @@ impl Sharks { /// /// Example: /// ``` - /// # use sharks::{ Sharks, Share }; - /// # let sharks = Sharks(3); + /// # use ssskit::{ SecretSharing, Share }; + /// # const POLY: u16 = 0x11d_u16; + /// # let sss = SecretSharing::(3); /// // Obtain an iterator over the shares for secret [1, 2] - /// let dealer = sharks.dealer(&[1, 2]); + /// let dealer = sss.dealer(&[1, 2]); /// // Get 3 shares - /// let shares: Vec = dealer.take(3).collect(); + /// let shares = dealer.take(3).collect::>>(); #[cfg(feature = "std")] - pub fn dealer(&self, secret: &[u8]) -> impl Iterator { + pub fn dealer(&self, secret: &[u8]) -> impl Iterator> { let mut rng = rand::thread_rng(); self.dealer_rng(secret, &mut rng) } @@ -124,28 +142,29 @@ impl Sharks { /// /// Example: /// ``` - /// # use sharks::{ Sharks, Share }; - /// # use rand_chacha::rand_core::SeedableRng; - /// # let sharks = Sharks(3); + /// # use ssskit::{ SecretSharing, Share }; + /// # use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; + /// # const POLY: u16 = 0x11d_u16; + /// # let sss = SecretSharing::(3); /// # let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); - /// # let mut shares: Vec = sharks.dealer_rng(&[1], &mut rng).take(3).collect(); + /// # let mut shares = sss.dealer_rng::(&[1], &mut rng).take(3).collect::>>(); /// // Recover original secret from shares - /// let mut secret = sharks.recover(&shares); + /// let mut secret = sss.recover(&shares); /// // Secret correctly recovered /// assert!(secret.is_ok()); /// // Remove shares for demonstration purposes /// shares.clear(); - /// secret = sharks.recover(&shares); + /// secret = sss.recover(&shares); /// // Not enough shares to recover secret /// assert!(secret.is_err()); pub fn recover<'a, T>(&self, shares: T) -> Result, &str> where - T: IntoIterator, - T::IntoIter: Iterator, + T: IntoIterator>, + T::IntoIter: Iterator>, { let mut share_length: Option = None; let mut keys: HashSet = HashSet::new(); - let mut values: Vec = Vec::new(); + let mut values: Vec> = Vec::new(); for share in shares.into_iter() { if share_length.is_none() { @@ -173,13 +192,14 @@ impl Sharks { /// /// Example: /// ``` - /// # use sharks::{ Sharks, Share }; - /// # use rand_chacha::rand_core::SeedableRng; - /// # let sharks = Sharks(2); + /// # use ssskit::{ SecretSharing, Share }; + /// # use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; + /// # const POLY: u16 = 0x11d_u16; + /// # let sss = SecretSharing::(2); /// # let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); - /// # let shares: Vec = sharks.dealer_rng(&[1, 2, 3, 4], &mut rng).take(3).collect(); + /// # let shares = sss.dealer_rng::(&[1, 2, 3, 4], &mut rng).take(3).collect::>>(); /// // Recover original shares from original shares up to threshold shares - /// let recovered_shares = sharks.recover_shares( + /// let recovered_shares = sss.recover_shares( /// [Some(&shares[0]), None, Some(&shares[2])], /// 3, /// ); @@ -188,17 +208,17 @@ impl Sharks { /// let recovered_shares = recovered_shares.unwrap(); /// assert_eq!(recovered_shares.len(), 3); /// // Remove shares for demonstration purposes - /// let recovered_shares = sharks.recover_shares([Some(&shares[0]), None, None], 3); + /// let recovered_shares = sss.recover_shares([Some(&shares[0]), None, None], 3); /// // Not enough shares to recover shares /// assert!(recovered_shares.is_err()); - pub fn recover_shares<'a, T>(&self, shares: T, n: usize) -> Result, &str> + pub fn recover_shares<'a, T>(&self, shares: T, n: usize) -> Result>, &str> where - T: IntoIterator>, - T::IntoIter: Iterator>, + T: IntoIterator>>, + T::IntoIter: Iterator>>, { let mut share_length: Option = None; let mut keys: HashSet = HashSet::new(); - let mut values: Vec = Vec::new(); + let mut values: Vec> = Vec::new(); let mut count = 0; for share in shares.into_iter() { @@ -228,25 +248,25 @@ impl Sharks { if keys.is_empty() || (keys.len() < self.0 as usize) { Err("Not enough shares to recover original shares") + } else if self.0 == 1 { + // if threshold is 1, return the shares as is n times + Ok(values.iter().cloned().cycle().take(n).collect()) } else { - if self.0 == 1 { - // if threshold is 1, return the shares as is n times - Ok(values.iter().cloned().cycle().take(n).collect()) - } else { - Ok((1..=n).map(|i| math::reshare(&values, i)).collect()) - } + Ok((1..=n).map(|i| math::reshare(&values, i)).collect()) } } } #[cfg(test)] mod tests { - use super::{Share, Sharks}; + use super::{SecretSharing, Share}; use alloc::{vec, vec::Vec}; - impl Sharks { + const POLY: u16 = 0x11d_u16; + + impl SecretSharing { #[cfg(not(feature = "std"))] - fn make_shares(&self, secret: &[u8]) -> impl Iterator { + fn make_shares(&self, secret: &[u8]) -> impl Iterator> { use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; let mut rng = ChaCha8Rng::from_seed([0x90; 32]); @@ -254,45 +274,45 @@ mod tests { } #[cfg(feature = "std")] - fn make_shares(&self, secret: &[u8]) -> impl Iterator { + fn make_shares(&self, secret: &[u8]) -> impl Iterator> { self.dealer(secret) } } #[test] fn test_insufficient_shares_err() { - let sharks = Sharks(255); - let shares: Vec = sharks.make_shares(&[1]).take(254).collect(); - let secret = sharks.recover(&shares); + let sss = SecretSharing::(255); + let shares: Vec> = sss.make_shares(&[1]).take(254).collect(); + let secret = sss.recover(&shares); assert!(secret.is_err()); } #[test] fn test_duplicate_shares_err() { - let sharks = Sharks(255); - let mut shares: Vec = sharks.make_shares(&[1]).take(255).collect(); + let sss = SecretSharing::(255); + let mut shares: Vec> = sss.make_shares(&[1]).take(255).collect(); shares[1] = Share { x: shares[0].x.clone(), y: shares[0].y.clone(), }; - let secret = sharks.recover(&shares); + let secret = sss.recover(&shares); assert!(secret.is_err()); } #[test] fn test_integration_works() { - let sharks = Sharks(255); - let shares: Vec = sharks.make_shares(&[1, 2, 3, 4]).take(255).collect(); - let secret = sharks.recover(&shares).unwrap(); + let sss = SecretSharing::(255); + let shares: Vec> = sss.make_shares(&[1, 2, 3, 4]).take(255).collect(); + let secret = sss.recover(&shares).unwrap(); assert_eq!(secret, vec![1, 2, 3, 4]); } #[test] fn test_reshare_works() { - let sharks = Sharks(3); - let shares: Vec = sharks.make_shares(&[1, 2, 3, 4]).take(4).collect(); + let sss = SecretSharing::(3); + let shares: Vec> = sss.make_shares(&[1, 2, 3, 4]).take(4).collect(); - let recovered_shares = sharks + let recovered_shares = sss .recover_shares( [Some(&shares[0]), None, Some(&shares[2]), Some(&shares[3])], 4, @@ -305,7 +325,7 @@ mod tests { assert_eq!(recovered_share.y, share.y); } - let recovered_shares = sharks + let recovered_shares = sss .recover_shares( [None, Some(&shares[1]), Some(&shares[2]), Some(&shares[3])], 4, @@ -318,7 +338,7 @@ mod tests { assert_eq!(recovered_share.y, share.y); } - let recovered_shares = sharks + let recovered_shares = sss .recover_shares( [Some(&shares[0]), Some(&shares[1]), Some(&shares[2]), None], 4, @@ -332,7 +352,7 @@ mod tests { } let recovered_shares = - sharks.recover_shares([Some(&shares[0]), None, None, Some(&shares[3])], 4); + sss.recover_shares([Some(&shares[0]), None, None, Some(&shares[3])], 4); assert!(recovered_shares.is_err()); } } diff --git a/src/math.rs b/src/math.rs index 193382a..43193a6 100644 --- a/src/math.rs +++ b/src/math.rs @@ -2,7 +2,8 @@ use alloc::vec::Vec; -use rand::distributions::{Distribution, Uniform}; +use rand::distributions::Distribution; +use rand::distributions::Uniform; use super::field::GF256; use super::share::Share; @@ -11,7 +12,7 @@ use super::share::Share; // The expected `shares` argument format is the same as the output by the `get_evaluator´ function. // Where each (key, value) pair corresponds to one share, where the key is the `x` and the value is a vector of `y`, // where each element corresponds to one of the secret's byte chunks. -pub fn interpolate(shares: &[Share]) -> Vec { +pub fn interpolate(shares: &[Share]) -> Vec { (0..shares[0].y.len()) .map(|s| { shares @@ -21,17 +22,21 @@ pub fn interpolate(shares: &[Share]) -> Vec { .iter() .filter(|s_j| s_j.x != s_i.x) .map(|s_j| s_j.x.clone() / (s_j.x.clone() - s_i.x.clone())) - .product::() + .product::>() * s_i.y[s].clone() }) - .sum::() + .sum::>() .0 }) .collect() } /// Takes N sample points and returns the value at a given x using Lagrange interpolation over GF(256). -pub fn interpolate_polynomial(x_samples: &[GF256], y_samples: &[GF256], x: GF256) -> GF256 { +pub fn interpolate_polynomial( + x_samples: &[GF256], + y_samples: &[GF256], + x: GF256, +) -> GF256 { assert!( x_samples.len() == y_samples.len(), "sample length mistmatch" @@ -60,7 +65,8 @@ pub fn interpolate_polynomial(x_samples: &[GF256], y_samples: &[GF256], x: GF256 result } -pub fn reshare(shares: &[Share], index: usize) -> Share { +/// Resharing a share at a given index. +pub fn reshare(shares: &[Share], index: usize) -> Share { // assert that atleast 2 shares exist assert!( shares.len() >= 2 && shares.len() <= 255, @@ -90,9 +96,13 @@ pub fn reshare(shares: &[Share], index: usize) -> Share { } } -// Generates `k` polynomial coefficients, being the last one `s` and the others randomly generated between `[1, 255]`. -// Coefficient degrees go from higher to lower in the returned vector order. -pub fn random_polynomial(s: GF256, k: u8, rng: &mut R) -> Vec { +/// Generates `k` polynomial coefficients, being the last one `s` and the others randomly generated between `[1, 255]`. +/// Coefficient degrees go from higher to lower in the returned vector order. +pub fn random_polynomial( + s: GF256, + k: u8, + rng: &mut R, +) -> Vec> { let k = k as usize; let mut poly = Vec::with_capacity(k); let between = Uniform::new_inclusive(1, 255); @@ -109,8 +119,10 @@ pub fn random_polynomial(s: GF256, k: u8, rng: &mut R) -> Vec>) -> impl Iterator { - (1..=u8::max_value()).map(GF256).map(move |x| Share { +pub fn get_evaluator( + polys: Vec>>, +) -> impl Iterator> { + (1..=u8::MAX).map(GF256).map(move |x| Share { x: x.clone(), y: polys .iter() @@ -124,21 +136,26 @@ pub fn get_evaluator(polys: Vec>) -> impl Iterator { #[cfg(test)] mod tests { - use super::{get_evaluator, interpolate, random_polynomial, Share, GF256}; + use super::{get_evaluator, interpolate, random_polynomial, reshare, Share, GF256}; use alloc::{vec, vec::Vec}; use rand_chacha::rand_core::SeedableRng; - - #[test] - fn random_polynomial_works() { - let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); - let poly = random_polynomial(GF256(1), 3, &mut rng); - assert_eq!(poly.len(), 3); - assert_eq!(poly[2], GF256(1)); + use rstest::rstest; + + const POLY: u16 = 0x11d_u16; + + #[rstest] + #[case([0x90; 32], 3)] + #[case([0x10; 32], 8)] + #[case([0x20; 32], 16)] + fn random_polynomial_works(#[case] seed: [u8; 32], #[case] k: usize) { + let mut rng = rand_chacha::ChaCha8Rng::from_seed(seed); + let poly = random_polynomial::<_, POLY>(GF256(1), k as u8, &mut rng); + assert_eq!(poly.len(), k); } #[test] fn evaluator_works() { - let iter = get_evaluator(vec![vec![GF256(3), GF256(2), GF256(5)]]); + let iter = get_evaluator::(vec![vec![GF256(3), GF256(2), GF256(5)]]); let values: Vec<_> = iter.take(2).map(|s| (s.x.clone(), s.y.clone())).collect(); assert_eq!( values, @@ -146,13 +163,30 @@ mod tests { ); } - #[test] - fn interpolate_works() { - let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); - let poly = random_polynomial(GF256(185), 10, &mut rng); + #[rstest] + #[case([0x90; 32], 10)] + #[case([0x10; 32], 8)] + #[case([0x20; 32], 16)] + fn interpolate_works(#[case] seed: [u8; 32], #[case] k: usize) { + let mut rng = rand_chacha::ChaCha8Rng::from_seed(seed); + let poly = random_polynomial(GF256(185), k as u8, &mut rng); let iter = get_evaluator(vec![poly]); - let shares: Vec = iter.take(10).collect(); + let shares: Vec> = iter.take(k).collect(); let root = interpolate(&shares); assert_eq!(root, vec![185]); } + + #[rstest] + #[case([0x90; 32], 10, 2)] + #[case([0x90; 32], 10, 5)] + #[case([0x10; 32], 8, 7)] + #[case([0x10; 32], 8, 8)] + fn reshare_works(#[case] seed: [u8; 32], #[case] k: usize, #[case] index: usize) { + let mut rng = rand_chacha::ChaCha8Rng::from_seed(seed); + let poly = random_polynomial(GF256(185), k as u8, &mut rng); + let iter = get_evaluator(vec![poly]); + let shares: Vec> = iter.take(k).collect(); + let share = reshare(&shares, index); + assert_eq!(share.y, shares[index - 1].y); + } } diff --git a/src/share.rs b/src/share.rs index c9d1592..6563c9b 100644 --- a/src/share.rs +++ b/src/share.rs @@ -12,16 +12,17 @@ use zeroize::Zeroize; /// /// Usage example: /// ``` -/// use sharks::{Sharks, Share}; +/// use ssskit::{SecretSharing, Share}; /// use core::convert::TryFrom; -/// # use rand_chacha::rand_core::SeedableRng; +/// # use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; /// # fn send_to_printer(_: Vec) {} /// # fn ask_shares() -> Vec> {vec![vec![1, 2], vec![2, 3], vec![3, 4]]} /// /// // Transmit the share bytes to a printer -/// let sharks = Sharks(3); +/// # const POLY: u16 = 0x11d_u16; +/// let sss = SecretSharing::(3); /// let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); -/// let dealer = sharks.dealer_rng(&[1, 2, 3], &mut rng); +/// let dealer = sss.dealer_rng::(&[1, 2, 3], &mut rng); /// /// // Get 5 shares and print paper keys /// for s in dealer.take(5) { @@ -30,20 +31,20 @@ use zeroize::Zeroize; /// /// // Get share bytes from an external source and recover secret /// let shares_bytes: Vec> = ask_shares(); -/// let shares: Vec = shares_bytes.iter().map(|s| Share::try_from(s.as_slice()).unwrap()).collect(); -/// let secret = sharks.recover(&shares).unwrap(); +/// let shares: Vec> = shares_bytes.iter().map(|s| Share::::try_from(s.as_slice()).unwrap()).collect(); +/// let secret = sss.recover(&shares).unwrap(); #[derive(Clone)] #[cfg_attr(feature = "fuzzing", derive(Arbitrary, Debug))] #[cfg_attr(feature = "zeroize_memory", derive(Zeroize))] #[cfg_attr(feature = "zeroize_memory", zeroize(drop))] -pub struct Share { - pub x: GF256, - pub y: Vec, +pub struct Share { + pub x: GF256, + pub y: Vec>, } /// Obtains a byte vector from a `Share` instance -impl From<&Share> for Vec { - fn from(s: &Share) -> Vec { +impl From<&Share> for Vec { + fn from(s: &Share) -> Vec { let mut bytes = Vec::with_capacity(s.y.len() + 1); bytes.push(s.x.0); bytes.extend(s.y.iter().map(|p| p.0)); @@ -52,10 +53,10 @@ impl From<&Share> for Vec { } /// Obtains a `Share` instance from a byte slice -impl core::convert::TryFrom<&[u8]> for Share { +impl core::convert::TryFrom<&[u8]> for Share { type Error = &'static str; - fn try_from(s: &[u8]) -> Result { + fn try_from(s: &[u8]) -> Result, Self::Error> { if s.len() < 2 { Err("A Share must be at least 2 bytes long") } else { @@ -71,10 +72,11 @@ mod tests { use super::{Share, GF256}; use alloc::{vec, vec::Vec}; use core::convert::TryFrom; + const POLY: u16 = 0x11d_u16; #[test] fn vec_from_share_works() { - let share = Share { + let share = Share:: { x: GF256(1), y: vec![GF256(2), GF256(3)], }; @@ -85,7 +87,7 @@ mod tests { #[test] fn share_from_u8_slice_works() { let bytes = [1, 2, 3]; - let share = Share::try_from(&bytes[..]).unwrap(); + let share = Share::::try_from(&bytes[..]).unwrap(); assert_eq!(share.x, GF256(1)); assert_eq!(share.y, vec![GF256(2), GF256(3)]); }