├── CODEOWNERS ├── .gitmodules ├── .vscode ├── extensions.json ├── settings.json └── launch.json ├── setup.sh ├── src ├── proof │ ├── mod.rs │ ├── pokcr.rs │ ├── poe.rs │ └── poke2.rs ├── hash │ ├── blake2b.rs │ ├── mod.rs │ └── primality │ │ ├── constants.rs │ │ └── mod.rs ├── group │ ├── ristretto.rs │ ├── rsa.rs │ ├── mod.rs │ └── class.rs ├── vector_commitment.rs ├── lib.rs ├── util.rs ├── uint.rs └── accumulator.rs ├── .gitignore ├── benches ├── hash │ ├── hashes.rs │ └── primality.rs ├── proof │ ├── pokcr.rs │ ├── poe.rs │ └── poke2.rs ├── uint.rs ├── accumulator │ ├── delete.rs │ └── add.rs └── group │ ├── rsa.rs │ └── class.rs ├── README.md ├── flamegraph.sh ├── release.sh ├── LICENSE ├── tests └── stress.rs ├── Cargo.toml ├── CONTRIBUTING.md └── flamegraph ├── stackcollapse.pl └── flamegraph.pl /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Default set of code owners. 2 | @eddiew @whaatt @plra @lucasege @alanefl @mstraka100 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs"] 2 | path = docs 3 | url = ssh://git@github.com/cambrian/accumulator.git 4 | branch = gh-pages 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "bungcip.better-toml", 4 | "rust-lang.rust", 5 | "serayuzgur.crates", 6 | "vadimcn.vscode-lldb" 7 | ] 8 | } -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # Set up the Rust toolchain and modify PATH. 5 | curl https://sh.rustup.rs -sSf | sh 6 | source $HOME/.cargo/env 7 | rustup update 8 | 9 | # Install useful extensions. 10 | cargo install cargo-watch 11 | cargo install cargo-edit 12 | rustup component add rls rust-analysis rust-src clippy 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.insertSpaces": true, 4 | "editor.rulers": [ 5 | 100 6 | ], 7 | "editor.tabSize": 2, 8 | "editor.wordWrap": "wordWrapColumn", 9 | "editor.wordWrapColumn": 100, 10 | "editor.wrappingIndent": "indent", 11 | "files.eol": "\n", 12 | "files.trimTrailingWhitespace": true, 13 | "rust.clippy_preference": "on" 14 | } -------------------------------------------------------------------------------- /src/proof/mod.rs: -------------------------------------------------------------------------------- 1 | //! Succinct proofs over unknown-order groups. These proofs are used as building blocks for many of 2 | //! the cryptographic primitives in this library. 3 | //! 4 | //! Use standalone with caution. 5 | //! 6 | //! Implementations are based on Section 3 of BBF. 7 | mod poe; 8 | pub use poe::Poe; 9 | mod pokcr; 10 | pub use pokcr::Pokcr; 11 | mod poke2; 12 | pub use poke2::Poke2; 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files and executables. 2 | /target/ 3 | 4 | # Remove Cargo.lock from gitignore if creating an executable; leave it for libraries. 5 | # More information here: https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 6 | Cargo.lock 7 | 8 | # Backup files generated by rustfmt. 9 | **/*.rs.bk 10 | 11 | # Flamegraphs output. 12 | /flamegraph/graphs 13 | /flamegraph/stacks 14 | 15 | # MacOS attribute files. 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "lldb", 6 | "request": "launch", 7 | "name": "Debug Unit Tests", 8 | "cargo": { 9 | "args": [ 10 | "test", 11 | "--no-run", 12 | "--lib", 13 | "--package=crypto" 14 | ], 15 | "filter": { 16 | "kind": "lib" 17 | } 18 | }, 19 | "args": [], 20 | "cwd": "${workspaceFolder}" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /benches/hash/hashes.rs: -------------------------------------------------------------------------------- 1 | /// See https://bheisler.github.io/criterion.rs/book/getting_started.html to add more benchmarks. 2 | #[macro_use] 3 | extern crate criterion; 4 | 5 | use criterion::Criterion; 6 | use accumulator::hash::{blake2b, hash_to_prime}; 7 | use rand::Rng; 8 | 9 | fn bench_blake2() { 10 | blake2b("werg"); 11 | } 12 | 13 | fn bench_hash_to_prime() { 14 | let random_bytes = rand::thread_rng().gen::<[u8; 32]>(); 15 | hash_to_prime(&random_bytes); 16 | } 17 | 18 | fn criterion_benchmark(c: &mut Criterion) { 19 | c.bench_function("blake2", |b| b.iter(bench_blake2)); 20 | c.bench_function("hash_to_prime", |b| b.iter(bench_hash_to_prime)); 21 | } 22 | 23 | criterion_group!(benches, criterion_benchmark); 24 | criterion_main!(benches); 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # accumulator 2 | Cryptographic accumulators in Rust, implemented over a generic group interface. Batteries (RSA and 3 | class group implementations) included! 4 | 5 | ## Installation 6 | ```toml 7 | # Cargo.toml 8 | [dependencies] 9 | accumulator = { git = "https://github.com/cambrian/accumulator.git", tag = "v0.2.1" } 10 | ``` 11 | 12 | ## Docs 13 | Available [here](https://cambrian.dev/accumulator/docs), and feel free to reach out with any 14 | questions. 15 | 16 | ## Demo 17 | We have a [proof-of-concept](https://github.com/cambrian/accumulator-demo) for stateless Bitcoin 18 | nodes. 19 | 20 | ## Contributing 21 | Please see our 22 | [contribution guide](https://github.com/cambrian/accumulator/blob/master/CONTRIBUTING.md). We are 23 | looking for long-term maintainers! 24 | -------------------------------------------------------------------------------- /benches/proof/pokcr.rs: -------------------------------------------------------------------------------- 1 | /// See https://bheisler.github.io/criterion.rs/book/getting_started.html to add more benchmarks. 2 | #[macro_use] 3 | extern crate criterion; 4 | 5 | use accumulator::group::{ElemFrom, Rsa2048}; 6 | use accumulator::proof::Pokcr; 7 | use accumulator::util::int; 8 | use criterion::Criterion; 9 | 10 | fn bench_pokcr_rsa() { 11 | let witnesses = [Rsa2048::elem(2), Rsa2048::elem(3)]; 12 | let x = [int(2), int(2)]; 13 | let alphas = [Rsa2048::elem(4), Rsa2048::elem(9)]; 14 | let proof = Pokcr::::prove(&witnesses); 15 | Pokcr::verify(&alphas, &x, &proof); 16 | } 17 | 18 | fn criterion_benchmark(c: &mut Criterion) { 19 | c.bench_function("pokcr_rsa", |b| b.iter(bench_pokcr_rsa)); 20 | } 21 | 22 | criterion_group!(benches, criterion_benchmark); 23 | criterion_main!(benches); 24 | -------------------------------------------------------------------------------- /benches/proof/poe.rs: -------------------------------------------------------------------------------- 1 | /// See https://bheisler.github.io/criterion.rs/book/getting_started.html to add more benchmarks. 2 | #[macro_use] 3 | extern crate criterion; 4 | 5 | use criterion::Criterion; 6 | use accumulator::group::{ElemFrom, Rsa2048, UnknownOrderGroup}; 7 | use accumulator::proof::Poe; 8 | use accumulator::util::int; 9 | 10 | fn bench_poe_rsa() { 11 | let base = Rsa2048::unknown_order_elem(); 12 | let exp = int(20); 13 | let result = Rsa2048::elem(1_048_576); 14 | let proof = Poe::::prove(&base, &exp, &result); 15 | Poe::::verify(&base, &exp, &result, &proof); 16 | } 17 | 18 | fn criterion_benchmark(c: &mut Criterion) { 19 | c.bench_function("poe_rsa", |b| b.iter(bench_poe_rsa)); 20 | } 21 | 22 | criterion_group!(benches, criterion_benchmark); 23 | criterion_main!(benches); 24 | -------------------------------------------------------------------------------- /benches/proof/poke2.rs: -------------------------------------------------------------------------------- 1 | /// See https://bheisler.github.io/criterion.rs/book/getting_started.html to add more benchmarks. 2 | #[macro_use] 3 | extern crate criterion; 4 | 5 | use accumulator::group::Rsa2048; 6 | use accumulator::group::{ElemFrom, UnknownOrderGroup}; 7 | use accumulator::proof::Poke2; 8 | use accumulator::util::int; 9 | use criterion::Criterion; 10 | 11 | fn bench_poke2_rsa() { 12 | let base = Rsa2048::unknown_order_elem(); 13 | let exp = int(20); 14 | let result = Rsa2048::elem(1_048_576); 15 | let proof = Poke2::::prove(&base, &exp, &result); 16 | Poke2::verify(&base, &result, &proof); 17 | } 18 | 19 | fn criterion_benchmark(c: &mut Criterion) { 20 | c.bench_function("poke2_rsa", |b| b.iter(bench_poke2_rsa)); 21 | } 22 | 23 | criterion_group!(benches, criterion_benchmark); 24 | criterion_main!(benches); 25 | -------------------------------------------------------------------------------- /benches/uint.rs: -------------------------------------------------------------------------------- 1 | /// See https://bheisler.github.io/criterion.rs/book/getting_started.html to add more benchmarks. 2 | #[macro_use] 3 | extern crate criterion; 4 | 5 | use accumulator::hash::blake2b; 6 | use accumulator::uint::U256; 7 | use criterion::{black_box, Criterion}; 8 | use rug::integer::Order; 9 | use std::ops::Mul; 10 | 11 | fn bench_mul(a: T, b: T) { 12 | black_box(a * b); 13 | } 14 | 15 | fn criterion_benchmark(c: &mut Criterion) { 16 | let int = blake2b("data"); 17 | let mut bytes = [0; 4]; 18 | int.write_digits(&mut bytes, Order::LsfBe); 19 | let u256 = U256::from(bytes); 20 | c.bench_function("mul_rug", move |b| b.iter(|| bench_mul(&int, &int))); 21 | c.bench_function("mul_u256", move |b| b.iter(|| bench_mul(u256, u256))); 22 | } 23 | 24 | criterion_group!(benches, criterion_benchmark); 25 | criterion_main!(benches); 26 | -------------------------------------------------------------------------------- /src/hash/blake2b.rs: -------------------------------------------------------------------------------- 1 | //! `GeneralHasher` interface for `blake2_rfc`. 2 | use super::GeneralHasher; 3 | use blake2_rfc::blake2b::Blake2b as Blake2b_; 4 | use std::hash::Hasher; 5 | 6 | /// Thin wrapper around `Blake2b` from `blake2_rfc`. 7 | pub struct Blake2b(pub Blake2b_); 8 | 9 | impl Default for Blake2b { 10 | fn default() -> Self { 11 | // 32 bytes = 256 bits 12 | Self(Blake2b_::new(32)) 13 | } 14 | } 15 | 16 | impl Hasher for Blake2b { 17 | /// We could return a truncated hash but it's easier just to not use this fn for now. 18 | fn finish(&self) -> u64 { 19 | panic!("Don't use! Prefer finalize(self).") 20 | } 21 | fn write(&mut self, bytes: &[u8]) { 22 | Blake2b_::update(&mut self.0, bytes) 23 | } 24 | } 25 | 26 | impl GeneralHasher for Blake2b { 27 | type Output = [u8; 32]; 28 | fn finalize(self) -> Self::Output { 29 | let res = self.0.finalize(); 30 | *array_ref![res.as_bytes(), 0, 32] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /flamegraph.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | FLAMEGRAPH_FILENAME=$1.svg 4 | FLAMEGRAPH_DIR=./flamegraph/graphs 5 | STACKS_FILENAME=$1.stacks 6 | STACKS_DIR=./flamegraph/stacks 7 | 8 | if [ $# -eq 0 ] 9 | then 10 | echo "Please supply bench name as argument." 11 | fi 12 | 13 | mkdir -p $FLAMEGRAPH_DIR 14 | mkdir -p $STACKS_DIR 15 | 16 | echo "Calling cargo bench in background to generate bench fn executable." 17 | nohup cargo bench --bench $1 >/dev/null 2>&1 & 18 | bench_exec=$(ls -t ./target/release/deps/$1-* | head -1) 19 | echo "Calling dtrace to run this executable and collect its stack frames." 20 | sudo nohup dtrace -x ustackframes=100 -c './'$bench_exec -o $STACKS_DIR/$STACKS_FILENAME -n 'profile-997 { @[ustack()] = count(); }' >/dev/null 2>&1 21 | echo "Generating flamegraph." 22 | ./flamegraph/stackcollapse.pl $STACKS_DIR/$STACKS_FILENAME | grep closure | ./flamegraph/flamegraph.pl > $FLAMEGRAPH_DIR/$FLAMEGRAPH_FILENAME 23 | echo "Done. Your flamegraph is now at: "$FLAMEGRAPH_DIR/$FLAMEGRAPH_FILENAME 24 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # Sanity check the code. 5 | cargo build 6 | cargo test 7 | 8 | # Clone docs branch in submodule. 9 | git submodule update --init docs 10 | cd docs 11 | git checkout gh-pages 12 | cd .. 13 | 14 | # Backup manually-placed files. 15 | mkdir -p docs_tmp 16 | cp docs/404.html docs_tmp 17 | cp docs/_config.yml docs_tmp 18 | cp docs/README.md docs_tmp 19 | 20 | # Generate docs. 21 | rm -rf target/doc docs/* 22 | cargo doc --no-deps 23 | cp -r target/doc/* docs 24 | 25 | # Restore manually-placed files. 26 | cp docs_tmp/* docs 27 | rm -rf docs_tmp 28 | 29 | # Site is published at BASE/accumulator. 30 | # Docs are at BASE/accumulator/docs. 31 | mv docs/accumulator docs/docs 32 | 33 | # Add Jekyll redirect for BASE/accumulator. 34 | echo '0a 35 | --- 36 | redirect_from: "/index.html" 37 | --- 38 | . 39 | w' | ed docs/docs/index.html 40 | 41 | # Commit regenerated docs to submodule. 42 | cd docs 43 | git add --all 44 | git commit -m "Regenerating docs via script." 45 | cd .. 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Cambrian Technologies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /benches/hash/primality.rs: -------------------------------------------------------------------------------- 1 | /// See https://bheisler.github.io/criterion.rs/book/getting_started.html to add more benchmarks. 2 | #[macro_use] 3 | extern crate criterion; 4 | 5 | use accumulator::hash::primality::{passes_lucas, passes_miller_rabin_base_2}; 6 | use accumulator::uint::u256; 7 | use criterion::Criterion; 8 | use rand::Rng; 9 | use rug::integer::Order; 10 | use rug::Integer; 11 | 12 | fn bench_mr2(bytes: &[u8; 32]) { 13 | passes_miller_rabin_base_2(&u256(bytes)); 14 | } 15 | 16 | fn bench_mr2_rug(bytes: &[u8; 32]) { 17 | let n = Integer::from_digits(bytes, Order::Lsf); 18 | // GMP does not let us demand a base-2 Fermat test so we just do one of random base. 19 | n.is_probably_prime(1); 20 | } 21 | 22 | fn bench_lucas(bytes: &[u8; 32]) { 23 | passes_lucas(&u256(bytes)); 24 | } 25 | 26 | fn criterion_benchmark(c: &mut Criterion) { 27 | let mut random_bytes = rand::thread_rng().gen::<[u8; 32]>(); 28 | random_bytes[0] |= 1; 29 | c.bench_function("mr2", move |b| b.iter(|| bench_mr2(&random_bytes))); 30 | c.bench_function("mr2_rug", move |b| b.iter(|| bench_mr2_rug(&random_bytes))); 31 | c.bench_function("lucas", move |b| b.iter(|| bench_lucas(&random_bytes))); 32 | } 33 | 34 | criterion_group!(benches, criterion_benchmark); 35 | criterion_main!(benches); 36 | -------------------------------------------------------------------------------- /src/proof/pokcr.rs: -------------------------------------------------------------------------------- 1 | //! Non-Interactive Proofs of Knowledge of Co-prime Roots (NI-PoKCR). See BBF (page 11) for details. 2 | use crate::group::{multi_exp, Group}; 3 | use rug::Integer; 4 | 5 | #[allow(non_snake_case)] 6 | #[derive(PartialEq, Eq, Hash, Clone, Debug)] 7 | /// Struct for NI-PoKCR. 8 | pub struct Pokcr { 9 | w: G::Elem, 10 | } 11 | 12 | impl Pokcr { 13 | /// Generates an NI-PoKCR proof. 14 | pub fn prove(witnesses: &[G::Elem]) -> Self { 15 | Self { 16 | w: witnesses.iter().fold(G::id(), |a, b| G::op(&a, b)), 17 | } 18 | } 19 | 20 | /// Verifies an NI-PoKCR proof. 21 | pub fn verify(alphas: &[G::Elem], x: &[Integer], proof: &Self) -> bool { 22 | let y = multi_exp::(alphas, x); 23 | let lhs = G::exp(&proof.w, &x.iter().product()); 24 | lhs == y 25 | } 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | use crate::group::{ElemFrom, Rsa2048}; 32 | use crate::util::int; 33 | 34 | #[test] 35 | fn test_pokcr() { 36 | let witnesses = [Rsa2048::elem(2), Rsa2048::elem(3)]; 37 | let x = [int(2), int(2)]; 38 | let alphas = [Rsa2048::elem(4), Rsa2048::elem(9)]; 39 | let proof = Pokcr::::prove(&witnesses); 40 | assert!(proof.w == Rsa2048::elem(6)); 41 | assert!(Pokcr::verify(&alphas, &x, &proof)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /benches/accumulator/delete.rs: -------------------------------------------------------------------------------- 1 | /// See https://bheisler.github.io/criterion.rs/book/getting_started.html to add more benchmarks. 2 | #[macro_use] 3 | extern crate criterion; 4 | 5 | use accumulator::group::{ClassGroup, Rsa2048, UnknownOrderGroup}; 6 | use accumulator::{Accumulator, MembershipProof}; 7 | use criterion::Criterion; 8 | 9 | fn bench_delete( 10 | acc: &Accumulator, 11 | c_proof: &MembershipProof, 12 | ) { 13 | acc 14 | .clone() 15 | .delete_with_proof(&[("c", c_proof.clone().witness)]) 16 | .expect("Valid delete expected."); 17 | } 18 | 19 | macro_rules! benchmark_delete { 20 | ($group_type : ty, $criterion: ident) => { 21 | let group_type_str = String::from(stringify!($group_type)).to_lowercase(); 22 | let acc_0 = Accumulator::<$group_type, &'static str>::empty().add(&["a", "b"]); 23 | let (acc_1, c_proof) = acc_0.clone().add_with_proof(&["c"]); 24 | $criterion.bench_function(format! {"{}_delete", group_type_str}.as_str(), move |b| { 25 | b.iter(|| bench_delete(&acc_1.clone(), &c_proof)) 26 | }); 27 | }; 28 | } 29 | 30 | fn criterion_benchmark(c: &mut Criterion) { 31 | benchmark_delete! {Rsa2048, c}; 32 | benchmark_delete! {ClassGroup, c}; 33 | } 34 | 35 | criterion_group!(benches, criterion_benchmark); 36 | criterion_main!(benches); 37 | -------------------------------------------------------------------------------- /tests/stress.rs: -------------------------------------------------------------------------------- 1 | use accumulator::group::Rsa2048; 2 | use accumulator::Accumulator; 3 | use rand::Rng; 4 | 5 | /// Adds 10,000 random primes to accumulator (unverified), then tests 100 more random additions 6 | /// (with verification) and 100 random elements are verified to be nonmembers. 7 | /// 8 | /// Takes about 5 minutes. 9 | /// 10 | /// TODO: Use a counter instead of random bits. 11 | #[test] 12 | #[ignore] 13 | fn stress_test() { 14 | let mut acc_set = Vec::new(); 15 | let mut acc = Accumulator::::empty(); 16 | for _ in 0..100 { 17 | let random_elem = rand::thread_rng().gen::<[u8; 32]>(); 18 | acc_set.push(random_elem); 19 | } 20 | println!("Starting add"); 21 | acc = acc.clone().add(&acc_set); 22 | println!("{}", acc_set.len()); 23 | for _ in 0..100 { 24 | let new_elem = rand::thread_rng().gen::<[u8; 32]>(); 25 | assert!(!acc_set.contains(&new_elem)); 26 | let (new_acc, add_proof) = acc.clone().add_with_proof(&[new_elem]); 27 | assert!(new_acc.verify_membership(&new_elem, &add_proof)); 28 | let (_, del_proof) = new_acc 29 | .clone() 30 | .delete_with_proof(&[(new_elem, add_proof.witness)]) 31 | .unwrap(); 32 | assert!(new_acc.verify_membership(&new_elem, &del_proof)); 33 | let nonmem_proof = acc 34 | .prove_nonmembership(&acc_set, &[new_elem]) 35 | .expect("It works"); 36 | assert!(acc.verify_nonmembership(&[new_elem], &nonmem_proof)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "accumulator" 3 | version = "0.2.1" 4 | edition = "2018" 5 | authors = [ 6 | "Pablo Aldape ", 7 | "Lucas Ege ", 8 | "Alan Flores-Lopez ", 9 | "Sanjay Kannan ", 10 | "Michael Straka ", 11 | "Eddie Wang " 12 | ] 13 | description = "Cryptographic accumulators in Rust." 14 | keywords = ["cryptography", "crypto", "accumulator", "rsa", "vector", "commitment"] 15 | readme = "README.md" 16 | repository = "https://github.com/cambrian/accumulator" 17 | 18 | [dependencies] 19 | arrayref = "0.3.5" 20 | blake2-rfc = "0.2.18" 21 | curve25519-dalek = "1.1.3" 22 | gmp-mpfr-sys = "1.1.12" 23 | lazy_static = "1.3.0" 24 | rug = "1.3.0" 25 | 26 | [dev-dependencies] 27 | criterion = "0.2.11" 28 | rand = "0.6.5" 29 | 30 | [[bench]] 31 | name = "comparison" 32 | path = "benches/group/rsa.rs" 33 | harness = false 34 | 35 | [[bench]] 36 | name = "class" 37 | path = "benches/group/class.rs" 38 | harness = false 39 | 40 | [[bench]] 41 | name = "add" 42 | path = "benches/accumulator/add.rs" 43 | harness = false 44 | 45 | [[bench]] 46 | name = "delete" 47 | path = "benches/accumulator/delete.rs" 48 | harness = false 49 | 50 | [[bench]] 51 | name = "hashes" 52 | path = "benches/hash/hashes.rs" 53 | harness = false 54 | 55 | [[bench]] 56 | name = "primality" 57 | path = "benches/hash/primality.rs" 58 | harness = false 59 | 60 | [[bench]] 61 | name = "poe" 62 | path = "benches/proof/poe.rs" 63 | harness = false 64 | 65 | [[bench]] 66 | name = "pokcr" 67 | path = "benches/proof/pokcr.rs" 68 | harness = false 69 | 70 | [[bench]] 71 | name = "poke2" 72 | path = "benches/proof/poke2.rs" 73 | harness = false 74 | 75 | [[bench]] 76 | name = "uint" 77 | path = "benches/uint.rs" 78 | harness = false 79 | 80 | [profile.bench] 81 | opt-level = 3 82 | debug = true 83 | 84 | # Flamegraph debug symbols. 85 | [profile.release] 86 | debug = true 87 | -------------------------------------------------------------------------------- /src/proof/poe.rs: -------------------------------------------------------------------------------- 1 | //! Non-Interactive Proofs of Exponentiation (NI-PoE). See BBF (pages 8 and 42) for details. 2 | use crate::group::Group; 3 | use crate::hash::hash_to_prime; 4 | use crate::util::int; 5 | use rug::Integer; 6 | 7 | #[allow(non_snake_case)] 8 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 9 | /// Struct for NI-PoE. 10 | pub struct Poe { 11 | Q: G::Elem, 12 | } 13 | 14 | impl Poe { 15 | /// Computes a proof that `base ^ exp` was performed to derive `result`. 16 | pub fn prove(base: &G::Elem, exp: &Integer, result: &G::Elem) -> Self { 17 | let l = hash_to_prime(&(base, exp, result)); 18 | let q = exp / l; 19 | Self { 20 | Q: G::exp(&base, &q), 21 | } 22 | } 23 | 24 | /// Verifies that `base ^ exp = result` using the given proof to avoid computation. 25 | pub fn verify(base: &G::Elem, exp: &Integer, result: &G::Elem, proof: &Self) -> bool { 26 | let l = hash_to_prime(&(base, exp, result)); 27 | let r = int(exp % &l); 28 | // w = Q^l * u^r 29 | let w = G::op(&G::exp(&proof.Q, &l), &G::exp(&base, &r)); 30 | w == *result 31 | } 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::*; 37 | use crate::group::{ElemFrom, Rsa2048, UnknownOrderGroup}; 38 | use crate::util::int; 39 | 40 | #[test] 41 | fn test_poe_small_exp() { 42 | // 2^20 = 1048576 43 | let base = Rsa2048::unknown_order_elem(); 44 | let exp = int(20); 45 | let result = Rsa2048::elem(1_048_576); 46 | let proof = Poe::::prove(&base, &exp, &result); 47 | assert!(Poe::verify(&base, &exp, &result, &proof)); 48 | assert!( 49 | proof 50 | == Poe { 51 | Q: Rsa2048::elem(1) 52 | } 53 | ); 54 | 55 | // 2^35 = 34359738368 56 | let exp_2 = int(35); 57 | let result_2 = Rsa2048::elem(34_359_738_368u64); 58 | let proof_2 = Poe::::prove(&base, &exp_2, &result_2); 59 | assert!(Poe::verify(&base, &exp_2, &result_2, &proof_2)); 60 | assert!( 61 | proof_2 62 | == Poe { 63 | Q: Rsa2048::elem(1) 64 | } 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Contributions are encouraged from anyone! 3 | 4 | ## Guidelines 5 | - We adhere to the Rust [Code of Conduct](https://www.rust-lang.org/conduct.html). 6 | - For bugs and feature requests, please 7 | [submit an issue](https://github.com/cambrian/accumulator/issues). 8 | - For code contributions, take a look at the information below. 9 | 10 | ## Recommended Setup 11 | 1. Clone this repository. 12 | 2. Run `./setup.sh` (if you already have the Rust toolchain installed, take a look at the setup 13 | script and add only the tools you are missing). 14 | 3. Open VS Code and install the recommended extensions. **Note**: You are welcome to use your 15 | favorite IDE, but the original authors of this library have found VS Code extremely handy. 16 | 4. Restart VS Code. 17 | 18 | ## Development 19 | - Leave `cargo watch -x "clippy -- -W clippy::pedantic"` running to type-check on save. Please 20 | adhere to the `pedantic` option in Clippy, as any contribution must pass those checks (without 21 | errors or warnings) to be included. 22 | - Ensure that your code is formatted with `rustfmt`. If you use the recommended VS Code setup, this 23 | should happen whenever you save a file. 24 | - Write tests! The repository has many examples of tests; run `cargo test` early and often. 25 | - The command `cargo bench` uses [Criterion](https://crates.io/crates/criterion) benchmarks. 26 | - When you are ready to submit your branch, create a pull request to `master`. A code owner will 27 | shepherd your PR through a review process prior to merge. 28 | 29 | ## Starting Points 30 | - Comments in the code labeled `REVIEW` or `TODO` need attention of some sort. 31 | - You are also welcome to triage and resolve 32 | [issues](https://github.com/cambrian/accumulator/issues), particularly those labeled `good first 33 | issue`. 34 | - So little time, so much [code to review](https://github.com/cambrian/accumulator/pulls)... 35 | 36 | ## Notes 37 | - This repository falls under the MIT License. Ensure that your contributions are MIT-compatible (or 38 | ask for help if you are not sure). 39 | - To become a code owner or collaborator on this repository, please message one of the code owners. 40 | 41 | ## Troubleshooting 42 | - If your Rust Language Server (RLS) hangs at `building` in VS Code, run 43 | `cargo clean && rustup update`. 44 | - If you get unexpected build errors, delete `Cargo.lock`, run `cargo update`, and re-build. 45 | -------------------------------------------------------------------------------- /benches/accumulator/add.rs: -------------------------------------------------------------------------------- 1 | /// See https://bheisler.github.io/criterion.rs/book/getting_started.html to add more benchmarks. 2 | #[macro_use] 3 | extern crate criterion; 4 | 5 | use accumulator::group::{ClassGroup, Rsa2048, UnknownOrderGroup}; 6 | use accumulator::hash::hash_to_prime; 7 | use accumulator::{Accumulator, MembershipProof}; 8 | use criterion::Criterion; 9 | use rand::Rng; 10 | use rug::Integer; 11 | 12 | fn bench_add(elems: &[Integer]) { 13 | let acc = Accumulator::::empty(); 14 | acc.add(elems); 15 | } 16 | 17 | fn bench_verify( 18 | acc: &Accumulator, 19 | elems: &[Integer], 20 | proof: &MembershipProof, 21 | ) { 22 | assert!(acc.verify_membership_batch(elems, proof)); 23 | } 24 | 25 | #[allow(dead_code)] 26 | fn bench_iterative_add(elems: &[Integer]) { 27 | let mut acc = Accumulator::::empty(); 28 | for elem in elems.chunks(1) { 29 | acc = acc.add(elem); 30 | } 31 | } 32 | 33 | fn init_acc() -> ( 34 | Accumulator, 35 | MembershipProof, 36 | Vec, 37 | ) { 38 | let mut elems = Vec::new(); 39 | for _ in 0..100 { 40 | let random_bytes = rand::thread_rng().gen::<[u8; 32]>(); 41 | let prime = hash_to_prime(&random_bytes); 42 | elems.push(prime); 43 | } 44 | let acc = Accumulator::::empty(); 45 | let (mut acc, mut proof) = acc.clone().add_with_proof(&elems); 46 | for _ in 0..100 { 47 | elems = vec![]; 48 | for _ in 0..100 { 49 | let random_bytes = rand::thread_rng().gen::<[u8; 32]>(); 50 | let prime = hash_to_prime(&random_bytes); 51 | elems.push(prime); 52 | } 53 | let (curr_acc, curr_proof) = acc.add_with_proof(&elems); 54 | acc = curr_acc; 55 | proof = curr_proof; 56 | } 57 | (acc, proof, elems) 58 | } 59 | 60 | macro_rules! benchmark_add { 61 | ($group_type : ty, $criterion: ident) => { 62 | let group_type_str = String::from(stringify!($group_type)).to_lowercase(); 63 | let (acc, proof, elems) = init_acc::<$group_type>(); 64 | let elems_1 = elems.clone(); 65 | let elems_2 = elems.clone(); 66 | let elems_3 = elems.clone(); 67 | 68 | $criterion.bench_function(format!("{}_add_1", group_type_str).as_str(), move |b| { 69 | b.iter(|| bench_add::<$group_type>(&elems_1[0..1])) 70 | }); 71 | $criterion.bench_function(format!("{}_add_10", group_type_str).as_str(), move |b| { 72 | b.iter(|| bench_add::<$group_type>(&elems_2[0..10])) 73 | }); 74 | $criterion.bench_function(format!("{}_add_100", group_type_str).as_str(), move |b| { 75 | b.iter(|| bench_add::<$group_type>(&elems_3)) 76 | }); 77 | $criterion.bench_function(format!("{}_verify", group_type_str).as_str(), move |b| { 78 | b.iter(|| bench_verify(&acc, &elems, &proof)) 79 | }); 80 | }; 81 | } 82 | 83 | fn criterion_benchmark(c: &mut Criterion) { 84 | benchmark_add! {Rsa2048, c}; 85 | benchmark_add! {ClassGroup, c}; 86 | } 87 | 88 | criterion_group!(benches, criterion_benchmark); 89 | criterion_main!(benches); 90 | -------------------------------------------------------------------------------- /src/proof/poke2.rs: -------------------------------------------------------------------------------- 1 | //! Non-Interactive Proofs of Knowledge of Exponent (NI-PoKE2). See BBF (pages 10 and 42) for 2 | //! details. 3 | use crate::group::UnknownOrderGroup; 4 | use crate::hash::{blake2b, hash_to_prime}; 5 | use rug::Integer; 6 | 7 | #[allow(non_snake_case)] 8 | #[derive(PartialEq, Eq, Hash, Clone, Debug)] 9 | /// Struct for NI-PoKE2. 10 | pub struct Poke2 { 11 | z: G::Elem, 12 | Q: G::Elem, 13 | r: Integer, 14 | } 15 | 16 | impl Poke2 { 17 | /// Computes a proof that you know `exp` s.t. `base ^ exp = result`. 18 | pub fn prove(base: &G::Elem, exp: &Integer, result: &G::Elem) -> Self { 19 | let g = G::unknown_order_elem(); 20 | let z = G::exp(&g, exp); 21 | let l = hash_to_prime(&(base, result, &z)); 22 | let alpha = blake2b(&(base, result, &z, &l)); 23 | let (q, r) = <(Integer, Integer)>::from(exp.div_rem_euc_ref(&l)); 24 | #[allow(non_snake_case)] 25 | let Q = G::exp(&G::op(&base, &G::exp(&g, &alpha)), &q); 26 | Self { z, Q, r } 27 | } 28 | 29 | /// Verifies that the prover knows `exp` s.t. `base ^ exp = result`. 30 | #[allow(non_snake_case)] 31 | pub fn verify(base: &G::Elem, result: &G::Elem, Self { z, Q, r }: &Self) -> bool { 32 | let g = G::unknown_order_elem(); 33 | let l = hash_to_prime(&(base, result, &z)); 34 | let alpha = blake2b(&(base, result, &z, &l)); 35 | let lhs = G::op( 36 | &G::exp(Q, &l), 37 | &G::exp(&G::op(&base, &G::exp(&g, &alpha)), &r), 38 | ); 39 | let rhs = G::op(result, &G::exp(&z, &alpha)); 40 | lhs == rhs 41 | } 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use super::*; 47 | use crate::group::{ElemFrom, Group, Rsa2048}; 48 | use crate::util::int; 49 | 50 | #[test] 51 | fn test_poke2() { 52 | // 2^20 = 1048576 53 | let base = Rsa2048::unknown_order_elem(); 54 | let exp = int(20); 55 | let result = Rsa2048::elem(1_048_576); 56 | let proof = Poke2::::prove(&base, &exp, &result); 57 | assert!(Poke2::verify(&base, &result, &proof)); 58 | // Must compare entire structs since elements `z`, `Q`, and `r` are private. 59 | assert!( 60 | proof 61 | == Poke2 { 62 | z: Rsa2048::elem(1_048_576), 63 | Q: Rsa2048::elem(1), 64 | r: int(20) 65 | } 66 | ); 67 | 68 | // 2^35 = 34359738368 69 | let exp_2 = int(35); 70 | let result_2 = Rsa2048::elem(34_359_738_368u64); 71 | let proof_2 = Poke2::::prove(&base, &exp_2, &result_2); 72 | assert!(Poke2::verify(&base, &result_2, &proof_2)); 73 | // Cannot verify wrong base/exp/result triple with wrong pair. 74 | assert!(!Poke2::verify(&base, &result_2, &proof)); 75 | assert!( 76 | proof_2 77 | == Poke2 { 78 | z: Rsa2048::elem(34_359_738_368u64), 79 | Q: Rsa2048::elem(1), 80 | r: int(35) 81 | } 82 | ); 83 | } 84 | 85 | #[test] 86 | fn test_poke2_negative() { 87 | let base = Rsa2048::elem(2); 88 | let exp = int(-5); 89 | let result = Rsa2048::exp(&base, &exp); 90 | let proof = Poke2::::prove(&base, &exp, &result); 91 | assert!(Poke2::verify(&base, &result, &proof)); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/hash/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module wraps `blake2b_rfc` into a convenient hashing interface (`GeneralHasher`) and 2 | //! exports the generalized `hash` function. Also exported is `hash_to_prime`, which works by 3 | //! repeatedly `hash`ing a value together with an incrementing nonce until the output is prime. 4 | use crate::uint::u256; 5 | use rug::integer::Order; 6 | use rug::Integer; 7 | use std::hash::{Hash, Hasher}; 8 | 9 | mod blake2b; 10 | pub use blake2b::Blake2b; 11 | pub mod primality; 12 | 13 | /// Like `std::hash::Hasher`, but general over output type. 14 | pub trait GeneralHasher: Hasher { 15 | /// The associated output type of the Hasher. 16 | type Output; 17 | /// Similar to `Hasher::finish`, but consumes `self`. 18 | fn finalize(self) -> Self::Output; 19 | } 20 | 21 | // Note: We explicitly pass in the hasher constructor so we don't have to specify its type via 22 | // generics. Rust has poor support for type applications, so if we wanted to pass `H` at the 23 | // type-level, we'd need to fully specify `T` as well, which is a pain in the ass. 24 | // 25 | // Instead of writing: 26 | // `hash::(&(base, exp, result))` 27 | // 28 | // This lets us write: 29 | // `hash(&Blake2b::default, &(base, exp, result))` 30 | 31 | /// Hash using the general Hasher. 32 | /// 33 | /// This function takes in the hash constructor as an argument for convenience. 34 | pub fn hash(new_hasher: &Fn() -> H, t: &T) -> H::Output { 35 | let mut h = new_hasher(); 36 | t.hash(&mut h); 37 | h.finalize() 38 | } 39 | 40 | /// Calls `hash` with a Blake2b hasher. 41 | pub fn blake2b(t: &T) -> Integer { 42 | Integer::from_digits(&hash(&Blake2b::default, t), Order::Msf) 43 | } 44 | 45 | /// Hashes `t` to an odd prime. 46 | /// 47 | /// Uses `Blake2b` as the hash function, and hashes with a counter until a prime is found via 48 | /// probabilistic primality checking. 49 | /// 50 | /// This function is optimized for 256-bit integers. 51 | #[allow(clippy::module_name_repetitions)] 52 | pub fn hash_to_prime(t: &T) -> Integer { 53 | let mut counter = 0_u64; 54 | loop { 55 | let mut hash = hash(&Blake2b::default, &(t, counter)); 56 | // Make the candidate prime odd. This gives ~7% performance gain on a 2018 Macbook Pro. 57 | hash[0] |= 1; 58 | let candidate_prime = u256(hash); 59 | if primality::is_prob_prime(&candidate_prime) { 60 | return Integer::from(candidate_prime); 61 | } 62 | counter += 1; 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | 70 | #[test] 71 | fn test_blake2() { 72 | let data = b"martian cyborg gerbil attack"; 73 | hash(&Blake2b::default, data); 74 | } 75 | 76 | #[test] 77 | fn test_() { 78 | let b_1 = "boom i got ur boyfriend"; 79 | let b_2 = "boom i got ur boyfriene"; 80 | assert_ne!(b_1, b_2); 81 | let h_1 = hash_to_prime(b_1); 82 | let h_2 = hash_to_prime(b_2); 83 | assert_ne!(h_1, h_2); 84 | let mut digits1 = [0; 4]; 85 | h_1.write_digits(&mut digits1, Order::Lsf); 86 | assert!(primality::is_prob_prime(&u256(digits1))); 87 | let mut digits2 = [0; 4]; 88 | h_2.write_digits(&mut digits2, Order::Lsf); 89 | assert!(primality::is_prob_prime(&u256(digits2))); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /benches/group/rsa.rs: -------------------------------------------------------------------------------- 1 | /// See https://bheisler.github.io/criterion.rs/book/getting_started.html to add more benchmarks. 2 | #[macro_use] 3 | extern crate criterion; 4 | 5 | use criterion::Criterion; 6 | use accumulator::group::{ElemFrom, Group, Rsa2048}; 7 | use rug::Integer; 8 | use std::str::FromStr; 9 | 10 | fn bench_op>() { 11 | G::op( 12 | &G::elem( 13 | Integer::from_str( 14 | "111066521363124532171649626395987136074128970245601106158251038731392583290069", 15 | ) 16 | .unwrap(), 17 | ), 18 | &G::elem( 19 | Integer::from_str( 20 | "106610920435831899020588753249099054915951032185883121197718271189872278955399", 21 | ) 22 | .unwrap(), 23 | ), 24 | ); 25 | } 26 | 27 | fn bench_op_large>() { 28 | G::op( 29 | &G::elem(Integer::from_str( 30 | "21720738995539542858936915878186921869751915989840152165899303861582487240810878492659751749\ 31 | 672737203717627738047648700009977053044057502917091973287111671693426065546612150833232954361\ 32 | 536709981055037121764270784874720971933716065574032615073613728454497477072129686538873330572\ 33 | 773963696018637078230885896090312654536801520372853122471254294946328305929844982319416384204\ 34 | 134056551840145916685870951507887895129356414704422748714217113880489703934147612551938082501\ 35 | 753055296801829703017260731439871110215618988509545129088484396848644805730347466581515692959\ 36 | 313583208325725034506693916571047785061884094866050395109710", 37 | ) 38 | .unwrap()), 39 | &G::elem(Integer::from_str( 40 | "31720738995539542858936915878186921869751915989840152165899303861582487240810878492659751749\ 41 | 672737203717627738047648700009977053044057502917091973287111671693426065546612150833232954361\ 42 | 536709981055037121764270784874720971933716065574032615073613728454497477072129686538873330572\ 43 | 773963696018637078230885896090312654536801520372853122471254294946328305929844982319416384204\ 44 | 134056551840145916685870951507887895129356414704422748714217113880489703934147612551938082501\ 45 | 753055296801829703017260731439871110215618988509545129088484396848644805730347466581515692959\ 46 | 313583208325725034506693916571047785061884094866050395109710", 47 | ) 48 | .unwrap()), 49 | ); 50 | } 51 | 52 | fn bench_exp>() { 53 | G::exp( 54 | &G::elem(2), 55 | &Integer::from_str( 56 | "65315136833896061809557254466951240071191890612435768575001173256020447546800029221544380288\ 57 | 474666886816442984548106882909827295319824031764930714696522619672276938781971873901815262421\ 58 | 654562691730669161126673833543570922556193096897121287444423696122691826661878849856991509472\ 59 | 508677693535083051665283493383", 60 | ) 61 | .unwrap(), 62 | ); 63 | } 64 | 65 | fn bench_inv>() { 66 | G::inv(&G::elem(2)); 67 | } 68 | 69 | fn criterion_benchmark(c: &mut Criterion) { 70 | c.bench_function("group_rsa_op", |b| b.iter(bench_op::)); 71 | c.bench_function("group_rsa_op_large", |b| b.iter(bench_op_large::)); 72 | c.bench_function("group_rsa_exp", |b| b.iter(bench_exp::)); 73 | c.bench_function("group_rsa_inv", |b| b.iter(bench_inv::)); 74 | } 75 | 76 | criterion_group!(benches, criterion_benchmark); 77 | criterion_main!(benches); 78 | -------------------------------------------------------------------------------- /flamegraph/stackcollapse.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | # 3 | # stackcollapse.pl collapse multiline stacks into single lines. 4 | # 5 | # Parses a multiline stack followed by a number on a separate line, and 6 | # outputs a semicolon separated stack followed by a space and the number. 7 | # If memory addresses (+0xd) are present, they are stripped, and resulting 8 | # identical stacks are colased with their counts summed. 9 | # 10 | # USAGE: ./stackcollapse.pl infile > outfile 11 | # 12 | # Example input: 13 | # 14 | # unix`i86_mwait+0xd 15 | # unix`cpu_idle_mwait+0xf1 16 | # unix`idle+0x114 17 | # unix`thread_start+0x8 18 | # 1641 19 | # 20 | # Example output: 21 | # 22 | # unix`thread_start;unix`idle;unix`cpu_idle_mwait;unix`i86_mwait 1641 23 | # 24 | # Input may contain many stacks, and can be generated using DTrace. The 25 | # first few lines of input are skipped (see $headerlines). 26 | # 27 | # Copyright 2011 Joyent, Inc. All rights reserved. 28 | # Copyright 2011 Brendan Gregg. All rights reserved. 29 | # 30 | # CDDL HEADER START 31 | # 32 | # The contents of this file are subject to the terms of the 33 | # Common Development and Distribution License (the "License"). 34 | # You may not use this file except in compliance with the License. 35 | # 36 | # You can obtain a copy of the license at docs/cddl1.txt or 37 | # http://opensource.org/licenses/CDDL-1.0. 38 | # See the License for the specific language governing permissions 39 | # and limitations under the License. 40 | # 41 | # When distributing Covered Code, include this CDDL HEADER in each 42 | # file and include the License file at docs/cddl1.txt. 43 | # If applicable, add the following below this CDDL HEADER, with the 44 | # fields enclosed by brackets "[]" replaced with your own identifying 45 | # information: Portions Copyright [yyyy] [name of copyright owner] 46 | # 47 | # CDDL HEADER END 48 | # 49 | # 14-Aug-2011 Brendan Gregg Created this. 50 | 51 | use strict; 52 | 53 | my $headerlines = 3; # number of input lines to skip 54 | my $includeoffset = 0; # include function offset (except leafs) 55 | my %collapsed; 56 | 57 | sub remember_stack { 58 | my ($stack, $count) = @_; 59 | $collapsed{$stack} += $count; 60 | } 61 | 62 | my $nr = 0; 63 | my @stack; 64 | 65 | foreach (<>) { 66 | next if $nr++ < $headerlines; 67 | chomp; 68 | 69 | if (m/^\s*(\d+)+$/) { 70 | my $count = $1; 71 | my $joined = join(";", @stack); 72 | 73 | # trim leaf offset if these were retained: 74 | $joined =~ s/\+[^+]*$// if $includeoffset; 75 | 76 | remember_stack($joined, $count); 77 | @stack = (); 78 | next; 79 | } 80 | 81 | next if (m/^\s*$/); 82 | 83 | my $frame = $_; 84 | $frame =~ s/^\s*//; 85 | $frame =~ s/\+[^+]*$// unless $includeoffset; 86 | 87 | # Remove arguments from C++ function names: 88 | $frame =~ s/(::.*)[(<].*/$1/; 89 | 90 | $frame = "-" if $frame eq ""; 91 | 92 | my @inline; 93 | for (split /\->/, $frame) { 94 | my $func = $_; 95 | 96 | # Strip out L and ; included in java stacks 97 | $func =~ tr/\;/:/; 98 | $func =~ s/^L//; 99 | $func .= "_[i]" if scalar(@inline) > 0; #inlined 100 | 101 | push @inline, $func; 102 | } 103 | 104 | unshift @stack, @inline; 105 | } 106 | 107 | foreach my $k (sort { $a cmp $b } keys %collapsed) { 108 | print "$k $collapsed{$k}\n"; 109 | } 110 | -------------------------------------------------------------------------------- /src/group/ristretto.rs: -------------------------------------------------------------------------------- 1 | //! Ristretto group implementation (based on the `curve25519-dalek` crate). 2 | use super::Group; 3 | use crate::util::{int, TypeRep}; 4 | use curve25519_dalek::ristretto::RistrettoPoint; 5 | use curve25519_dalek::scalar::Scalar; 6 | use curve25519_dalek::traits::Identity; 7 | use rug::integer::Order; 8 | use rug::ops::Pow; 9 | use rug::Integer; 10 | use std::hash::{Hash, Hasher}; 11 | 12 | #[allow(clippy::module_name_repetitions)] 13 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 14 | /// Ristretto group implementation (based on the `curve25519-dalek` crate). 15 | pub enum Ristretto {} 16 | 17 | lazy_static! { 18 | pub static ref MAX_SAFE_EXPONENT: Integer = int(2).pow(255) - 1; 19 | pub static ref MAX_SAFE_SCALAR: Scalar = { 20 | let mut digits: [u8; 32] = [0; 32]; 21 | MAX_SAFE_EXPONENT.write_digits(&mut digits, Order::LsfLe); 22 | Scalar::from_bytes_mod_order(digits) 23 | }; 24 | } 25 | 26 | impl Ristretto { 27 | fn max_safe_exponent() -> &'static Integer { 28 | &MAX_SAFE_EXPONENT 29 | } 30 | } 31 | 32 | // REVIEW: Ideally we'd just use `RistrettoPoint` here, but only traits defined in this crate can 33 | // be implemented for arbitrary types. How to fix without wrapping? 34 | // 35 | // It may make sense to fork `curve25519-dalek` to add the `Hash` impl. Then we won't need to wrap. 36 | #[allow(clippy::module_name_repetitions)] 37 | #[derive(Clone, Debug, PartialEq, Eq)] 38 | /// A Ristretto group element, directly wrapping a Ristretto point. 39 | pub struct RistrettoElem(RistrettoPoint); 40 | 41 | #[allow(clippy::derive_hash_xor_eq)] 42 | impl Hash for RistrettoElem { 43 | fn hash(&self, state: &mut H) { 44 | self.0.compress().as_bytes().hash(state); 45 | } 46 | } 47 | 48 | impl TypeRep for Ristretto { 49 | type Rep = (); 50 | fn rep() -> &'static Self::Rep { 51 | &() 52 | } 53 | } 54 | 55 | impl Group for Ristretto { 56 | type Elem = RistrettoElem; 57 | 58 | fn op_(_: &(), a: &RistrettoElem, b: &RistrettoElem) -> RistrettoElem { 59 | RistrettoElem(a.0 + b.0) 60 | } 61 | 62 | fn id_(_: &()) -> RistrettoElem { 63 | RistrettoElem(RistrettoPoint::identity()) 64 | } 65 | 66 | fn inv_(_: &(), x: &RistrettoElem) -> RistrettoElem { 67 | RistrettoElem(-x.0) 68 | } 69 | 70 | fn exp_(_: &(), x: &RistrettoElem, n: &Integer) -> RistrettoElem { 71 | let mut remaining = n.clone(); 72 | let mut result = Self::id(); 73 | 74 | while remaining > *MAX_SAFE_EXPONENT { 75 | result = RistrettoElem(result.0 + x.0 * (*MAX_SAFE_SCALAR)); 76 | remaining -= Self::max_safe_exponent(); 77 | } 78 | 79 | let mut digits: [u8; 32] = [0; 32]; 80 | remaining.write_digits(&mut digits, Order::LsfLe); 81 | let factor = Scalar::from_bytes_mod_order(digits); 82 | RistrettoElem(result.0 + x.0 * factor) 83 | } 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | use super::*; 89 | use crate::util::int; 90 | use curve25519_dalek::constants; 91 | 92 | #[test] 93 | fn test_inv() { 94 | let bp = RistrettoElem(constants::RISTRETTO_BASEPOINT_POINT); 95 | let bp_inv = Ristretto::inv(&bp); 96 | assert!(Ristretto::op(&bp, &bp_inv) == Ristretto::id()); 97 | assert_ne!(bp, bp_inv); 98 | } 99 | 100 | #[test] 101 | fn test_exp() { 102 | let bp = RistrettoElem(constants::RISTRETTO_BASEPOINT_POINT); 103 | let exp_a = Ristretto::exp(&bp, &int(2).pow(258)); 104 | let exp_b = Ristretto::exp(&bp, &int(2).pow(257)); 105 | let exp_b_2 = Ristretto::exp(&exp_b, &int(2)); 106 | assert_eq!(exp_a, exp_b_2); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /benches/group/class.rs: -------------------------------------------------------------------------------- 1 | /// See https://bheisler.github.io/criterion.rs/book/getting_started.html to add more benchmarks. 2 | #[macro_use] 3 | extern crate criterion; 4 | 5 | use accumulator::group::{ClassGroup, ElemFrom, Group, UnknownOrderGroup}; 6 | use criterion::Criterion; 7 | use rug::Integer; 8 | use std::str::FromStr; 9 | 10 | fn criterion_benchmark(c: &mut Criterion) { 11 | let left = ClassGroup::elem(( 12 | Integer::from_str("16").unwrap(), 13 | Integer::from_str("9").unwrap(), 14 | Integer::from_str( 15 | "47837607866886756167333839869251273774207619337757918597995294777816250058331116325341018110\ 16 | 672047217112377476473502060121352842575308793237621563947157630098485131517401073775191194319\ 17 | 531549483898334742144138601661120476425524333273122132151927833887323969998955713328783526854\ 18 | 198871332313399489386997681827578317938792170918711794684859311697439726596656501594138449739\ 19 | 494228617068329664776714484742276158090583495714649193839084110987149118615158361352488488402\ 20 | 038894799695420483272708933239751363849397287571692736881031223140446926522431859701738994562\ 21 | 9057462766047140854869124473221137588347335081555186814036", 22 | ) 23 | .unwrap(), 24 | )); 25 | let right = left.clone(); 26 | 27 | // Generator element. 28 | let base = ClassGroup::elem(( 29 | Integer::from(2), 30 | Integer::from(1), 31 | Integer::from_str( 32 | "38270086293509404933867071895401019019366095470206334878396235822253000046664893060272814488\ 33 | 537637773689901981178801648097082274060247034590097251157726104078788105213920859020152955455\ 34 | 625239587118667793715310881328896381140419466618497705721542267109859175999164570663026821483\ 35 | 359097065850719591509598145462062654351033736734969435747887449357951781277325201275310759791\ 36 | 595382893654663731821371587793820926472466796571719355071267288789719294892126689081990790721\ 37 | 631115839756336386618167146591801091079517830057354189504824978512357541217945487761391195650\ 38 | 32459702128377126838952995785769100706778680652441494512278", 39 | ) 40 | .unwrap(), 41 | )); 42 | let exp = Integer::from_str("65315").unwrap(); 43 | let g_inv = base.clone(); 44 | let g_sq = ClassGroup::unknown_order_elem(); 45 | 46 | let aa = Integer::from_str("16").unwrap(); 47 | let bb = Integer::from_str("105").unwrap(); 48 | let cc = Integer::from_str( 49 | "4783760786688675616733383986925127377420761933775791859799529477781625005833111632534101811067\ 50 | 20472171123774764735020601213528425753087932376215639471576300984851315174010737751911943195315\ 51 | 49483898334742144138601661120476425524333273122132151927833887323969998955713328783526854198871\ 52 | 33231339948938699768182757831793879217091871179468485931169743972659665650159413844973949422861\ 53 | 70683296647767144847422761580905834957146491938390841109871491186151583613524884884020388947996\ 54 | 95420483272708933239751363849397287571692736881031223140446926522431859701738994562905746276604\ 55 | 7140854869124473221137588347335081555186814207", 56 | ) 57 | .unwrap(); 58 | 59 | // Element which requires one iteration to reduce, represented as a tuple here, since only 60 | // reduced representations of ClassElem are allowed. 61 | let g_red = (cc.clone(), bb.clone(), aa.clone()); 62 | let g_norm = (aa.clone(), bb.clone(), cc.clone()); 63 | 64 | c.bench_function("group_class_op", move |b| { 65 | b.iter(|| ClassGroup::op(&left, &right)) 66 | }); 67 | c.bench_function("group_class_exp", move |b| { 68 | b.iter(|| ClassGroup::exp(&base, &exp)) 69 | }); 70 | c.bench_function("group_class_inv", move |b| { 71 | b.iter(|| ClassGroup::inv(&g_inv)) 72 | }); 73 | c.bench_function("group_class_normalize", move |b| { 74 | b.iter_with_setup( 75 | || g_norm.clone(), 76 | |g| ClassGroup::normalize(g.0, g.1, g.2), 77 | ) 78 | }); 79 | c.bench_function("group_class_reduce", move |b| { 80 | b.iter_with_setup( 81 | || g_red.clone(), 82 | |g| ClassGroup::reduce(g.0, g.1, g.2), 83 | ) 84 | }); 85 | c.bench_function("group_class_square", move |b| { 86 | b.iter_with_setup(|| g_sq.clone(), |g| ClassGroup::square(&g)) 87 | }); 88 | } 89 | 90 | criterion_group!(benches, criterion_benchmark); 91 | criterion_main!(benches); 92 | -------------------------------------------------------------------------------- /src/vector_commitment.rs: -------------------------------------------------------------------------------- 1 | //! Vector commitment library, built on a generic group interface. **Very much a WIP.** 2 | use super::accumulator::{Accumulator, MembershipProof, NonmembershipProof, Witness}; 3 | use crate::group::UnknownOrderGroup; 4 | use rug::Integer; 5 | use std::collections::HashSet; 6 | 7 | #[derive(Debug)] 8 | /// The different types of vector commitment errors. 9 | pub enum VCError { 10 | /// When there are conflicting indices in the vector commitment. 11 | ConflictingIndices, 12 | /// When an opening fails. 13 | InvalidOpen, 14 | /// Unexpected state during an update. 15 | UnexpectedState, 16 | } 17 | 18 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 19 | /// A vector commitment, wrapping an underlying accumulator. The accumulator contains indices of an 20 | /// abstract vector where the corresponding bit is True. 21 | pub struct VectorCommitment(Accumulator); 22 | 23 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 24 | /// A vector commitment proof. 25 | pub struct VectorProof { 26 | membership_proof: MembershipProof, 27 | nonmembership_proof: NonmembershipProof, 28 | } 29 | 30 | fn group_elems_by_bit(bits: &[(bool, Integer)]) -> Result<(Vec, Vec), VCError> { 31 | let mut elems_with_one = vec![]; 32 | let mut elems_with_zero = vec![]; 33 | let mut seen_indices = HashSet::new(); 34 | for (bit, i) in bits { 35 | if !seen_indices.insert(i) { 36 | return Err(VCError::ConflictingIndices); 37 | } 38 | if *bit { 39 | elems_with_one.push(i.clone()); 40 | } else { 41 | elems_with_zero.push(i.clone()); 42 | } 43 | } 44 | Ok((elems_with_zero, elems_with_one)) 45 | } 46 | 47 | impl VectorCommitment { 48 | /// Initializes a new vector commitment (VC). 49 | pub fn empty() -> Self { 50 | Self(Accumulator::::empty()) 51 | } 52 | 53 | /// Updates a VC with a list of values and indices. 54 | /// 55 | /// # Arguments 56 | /// 57 | /// * `vc_acc_set` - All indices that are set (True). 58 | /// * `bits` - Tuples (truth value, bit index) to set. 59 | /// 60 | /// Uses a move instead of a `&self` reference to prevent accidental use of the old VC state. 61 | pub fn update( 62 | vc: Self, 63 | vc_acc_set: &[Integer], 64 | bits: &[(bool, Integer)], 65 | ) -> Result<(Self, VectorProof), VCError> { 66 | let (elems_with_zero, elems_with_one) = group_elems_by_bit(&bits)?; 67 | let (new_acc, membership_proof) = vc.0.add_with_proof(&elems_with_one); 68 | let nonmembership_proof = new_acc 69 | .prove_nonmembership(vc_acc_set, &elems_with_zero) 70 | .map_err(|_| VCError::UnexpectedState)?; 71 | Ok(( 72 | Self(new_acc), 73 | VectorProof { 74 | membership_proof, 75 | nonmembership_proof, 76 | }, 77 | )) 78 | } 79 | 80 | /// Opens/generates a commitment to indices in the VC. 81 | /// 82 | /// # Arguments 83 | /// * `vc_acc_set` - All indices that are set (True). 84 | /// * `zero_bits` - Indices you want to prove are unset (False). 85 | /// * `one_bit_witnesses` - Indices you want to prove are set (True) and their witnesses. 86 | pub fn open( 87 | vc: &Self, 88 | vc_acc_set: &[Integer], 89 | zero_bits: &[Integer], 90 | one_bit_witnesses: &[(Integer, Witness)], 91 | ) -> Result, VCError> { 92 | let membership_proof = vc 93 | .0 94 | .prove_membership(one_bit_witnesses) 95 | .map_err(|_| VCError::InvalidOpen)?; 96 | let nonmembership_proof = vc 97 | .0 98 | .prove_nonmembership(vc_acc_set, zero_bits) 99 | .map_err(|_| VCError::InvalidOpen)?; 100 | Ok(VectorProof { 101 | membership_proof, 102 | nonmembership_proof, 103 | }) 104 | } 105 | 106 | /// Verifies a commitment to indices in the VC. 107 | /// 108 | /// # Arguments 109 | /// 110 | /// * `bits` - Tuples (truth value, bit index) to verify. 111 | /// * `VectorProof` - A `VectorProof` to verify against. 112 | pub fn verify( 113 | vc: &Self, 114 | bits: &[(bool, Integer)], 115 | VectorProof { 116 | membership_proof, 117 | nonmembership_proof, 118 | }: &VectorProof, 119 | ) -> bool { 120 | let group_result = group_elems_by_bit(&bits); 121 | if group_result.is_err() { 122 | return false; 123 | } 124 | let (elems_with_zero, elems_with_one) = group_result.unwrap(); 125 | let verified_membership = vc 126 | .0 127 | .verify_membership_batch(&elems_with_one, membership_proof); 128 | let verified_nonmembership = vc 129 | .0 130 | .verify_nonmembership(&elems_with_zero, nonmembership_proof); 131 | verified_membership && verified_nonmembership 132 | } 133 | } 134 | 135 | // TODO: Write tests. 136 | #[cfg(test)] 137 | mod tests {} 138 | -------------------------------------------------------------------------------- /src/group/rsa.rs: -------------------------------------------------------------------------------- 1 | //! RSA (2048) group using GMP integers in the `rug` crate. 2 | use super::{ElemFrom, Group, UnknownOrderGroup}; 3 | use crate::util::{int, TypeRep}; 4 | use rug::Integer; 5 | use std::str::FromStr; 6 | 7 | #[allow(clippy::module_name_repetitions)] 8 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 9 | /// RSA-2048 group implementation. Modulus taken from 10 | /// [here](https://en.wikipedia.org/wiki/RSA_numbers#RSA-2048). **Note**: If you want to use 11 | /// `Rsa2048` outside the context of this crate, be advised that it treats `x` and `-x` as the same 12 | /// element for sound proofs-of-exponentiation. See BBF (page 9). 13 | pub enum Rsa2048 {} 14 | 15 | /// RSA-2048 modulus, taken from [Wikipedia](https://en.wikipedia.org/wiki/RSA_numbers#RSA-2048). 16 | const RSA2048_MODULUS_DECIMAL: &str = 17 | "251959084756578934940271832400483985714292821262040320277771378360436620207075955562640185258807\ 18 | 8440691829064124951508218929855914917618450280848912007284499268739280728777673597141834727026189\ 19 | 6375014971824691165077613379859095700097330459748808428401797429100642458691817195118746121515172\ 20 | 6546322822168699875491824224336372590851418654620435767984233871847744479207399342365848238242811\ 21 | 9816381501067481045166037730605620161967625613384414360383390441495263443219011465754445417842402\ 22 | 0924616515723350778707749817125772467962926386356373289912154831438167899885040445364023527381951\ 23 | 378636564391212010397122822120720357"; 24 | 25 | lazy_static! { 26 | pub static ref RSA2048_MODULUS: Integer = Integer::from_str(RSA2048_MODULUS_DECIMAL).unwrap(); 27 | pub static ref HALF_MODULUS: Integer = RSA2048_MODULUS.clone() / 2; 28 | } 29 | 30 | #[allow(clippy::module_name_repetitions)] 31 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 32 | /// An RSA 2048 group element, directly wrapping a GMP integer from the `rug` crate. 33 | pub struct Rsa2048Elem(Integer); 34 | 35 | impl TypeRep for Rsa2048 { 36 | type Rep = Integer; 37 | fn rep() -> &'static Self::Rep { 38 | &RSA2048_MODULUS 39 | } 40 | } 41 | 42 | impl Group for Rsa2048 { 43 | type Elem = Rsa2048Elem; 44 | fn op_(modulus: &Integer, a: &Rsa2048Elem, b: &Rsa2048Elem) -> Rsa2048Elem { 45 | Self::elem(int(&a.0 * &b.0) % modulus) 46 | } 47 | 48 | fn id_(_: &Integer) -> Rsa2048Elem { 49 | Self::elem(1) 50 | } 51 | 52 | fn inv_(modulus: &Integer, x: &Rsa2048Elem) -> Rsa2048Elem { 53 | Self::elem(x.0.invert_ref(modulus).unwrap()) 54 | } 55 | 56 | fn exp_(modulus: &Integer, x: &Rsa2048Elem, n: &Integer) -> Rsa2048Elem { 57 | // A side-channel resistant impl is 40% slower; we'll consider it in the future if we need to. 58 | Self::elem(x.0.pow_mod_ref(n, modulus).unwrap()) 59 | } 60 | } 61 | 62 | impl ElemFrom for Rsa2048 63 | where 64 | Integer: From, 65 | { 66 | fn elem(t: T) -> Rsa2048Elem { 67 | let modulus = Self::rep(); 68 | let val = int(t) % modulus; 69 | if val > *HALF_MODULUS { 70 | Rsa2048Elem(<(Integer, Integer)>::from((-val).div_rem_euc_ref(&modulus)).1) 71 | } else { 72 | Rsa2048Elem(val) 73 | } 74 | } 75 | } 76 | 77 | impl UnknownOrderGroup for Rsa2048 { 78 | fn unknown_order_elem_(_: &Integer) -> Rsa2048Elem { 79 | Self::elem(2) 80 | } 81 | } 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use super::*; 86 | 87 | #[test] 88 | fn test_init() { 89 | let _x = &Rsa2048::rep(); 90 | } 91 | 92 | #[test] 93 | fn test_op() { 94 | let a = Rsa2048::op(&Rsa2048::elem(2), &Rsa2048::elem(3)); 95 | assert!(a == Rsa2048::elem(6)); 96 | let b = Rsa2048::op(&Rsa2048::elem(-2), &Rsa2048::elem(-3)); 97 | assert!(b == Rsa2048::elem(6)); 98 | } 99 | 100 | /// Tests that `-x` and `x` are treated as the same element. 101 | #[test] 102 | fn test_cosets() { 103 | assert!(Rsa2048::elem(3) == Rsa2048::elem(RSA2048_MODULUS.clone() - 3)); 104 | // TODO: Add a trickier coset test involving `op`. 105 | } 106 | 107 | #[test] 108 | fn test_exp() { 109 | let a = Rsa2048::exp(&Rsa2048::elem(2), &int(3)); 110 | assert!(a == Rsa2048::elem(8)); 111 | let b = Rsa2048::exp(&Rsa2048::elem(2), &int(4096)); 112 | assert!( 113 | b == Rsa2048::elem( 114 | Integer::parse( 115 | "2172073899553954285893691587818692186975191598984015216589930386158248724081087849265975\ 116 | 17496727372037176277380476487000099770530440575029170919732871116716934260655466121508332\ 117 | 32954361536709981055037121764270784874720971933716065574032615073613728454497477072129686\ 118 | 53887333057277396369601863707823088589609031265453680152037285312247125429494632830592984\ 119 | 49823194163842041340565518401459166858709515078878951293564147044227487142171138804897039\ 120 | 34147612551938082501753055296801829703017260731439871110215618988509545129088484396848644\ 121 | 805730347466581515692959313583208325725034506693916571047785061884094866050395109710" 122 | ) 123 | .unwrap() 124 | ) 125 | ); 126 | let c = Rsa2048::exp(&Rsa2048::elem(2), &RSA2048_MODULUS); 127 | dbg!(c); 128 | let d = Rsa2048::exp(&Rsa2048::elem(2), &(RSA2048_MODULUS.clone() * int(2))); 129 | dbg!(d); 130 | } 131 | 132 | #[test] 133 | fn test_inv() { 134 | let x = Rsa2048::elem(2); 135 | let inv = Rsa2048::inv(&x); 136 | assert!(Rsa2048::op(&x, &inv) == Rsa2048::id()); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Fast cryptographic accumulator and vector commitment library, originally written by Cambrian 2 | //! Technologies [\[GitHub\]](https://github.com/cambrian/accumulator). 3 | //! 4 | //! **Disclaimer**: This library is intended to be production-quality code, but it has not been 5 | //! independently-audited for correctness or tested to a critical degree. As such, please treat this 6 | //! library as **research-grade** for the time being. 7 | //! 8 | //! # Important Note 9 | //! 10 | //! To ensure correspondence between accumulator methods and logical set operations in your 11 | //! application, you must ensure that **no element is accumulated twice**. In particular, deleting 12 | //! a doubly-accumulated element will remove only one "copy" of it from the accumulator, meaning 13 | //! that its membership can still be verified. Hence, an accumulator without this invariant can be 14 | //! viewed as a multiset. 15 | //! 16 | //! # What is an accumulator? 17 | //! 18 | //! An accumulator is a cryptographic primitive which functions essentially as a secure 19 | //! decentralized set. It allows parties to maintain consensus on a set of values via a 20 | //! _succinct binding commitment_ as well as to issue _efficiently verifiable (non)membership 21 | //! proofs_ for elements of interest, all without requiring any party to store the entire set. 22 | //! 23 | //! Similarly to a Merkle tree, the accumulator stores its state commitment in constant space. A 24 | //! notable difference, however, is that its inclusion and exclusion proofs also take up constant 25 | //! space, and can be verified in constant time. For a far more detailed discussion of accumulators 26 | //! as implemented here, see _Batching Techniques for Accumulators with Applications to IOPs and 27 | //! Stateless Blockchains_ (Boneh, Bünz, and Fisch 2018) 28 | //! [\[Link\]](https://eprint.iacr.org/2018/1188.pdf). 29 | //! 30 | //! Throughout our code, we refer to this paper as `BBF`. We also refer to another paper, _Universal 31 | //! Accumulators with Efficient Nonmembership Proofs_ (Li, Li, Xue 2007) 32 | //! [\[Link\]](https://link.springer.com/content/pdf/10.1007/978-3-540-72738-5_17.pdf), abbreviated 33 | //! henceforth as `LLX`. 34 | //! 35 | //! # What is a vector commitment? 36 | //! 37 | //! A vector commitment (VC) is a closely-related primitive, distinguished from an accumulator in 38 | //! that it provides a _position-binding_ commitment to state. That is, a VC allows parties to 39 | //! prove or disprove that a certain element exists at a certain position. 40 | //! 41 | //! (Think VC : Vector :: Accumulator : Set.) 42 | //! 43 | //! Our vector commitment implementation is a work-in-progress (WIP), and should be treated with 44 | //! even more skepticism than our accumulators. 45 | //! 46 | //! # Usage 47 | //! ``` 48 | //! // A very basic example. 49 | //! use accumulator::Accumulator; 50 | //! use accumulator::group::Rsa2048; 51 | //! 52 | //! let acc = Accumulator::::empty(); 53 | //! 54 | //! // Accumulate "dog" and "cat". The `add_with_proof` method returns the new accumulator state 55 | //! // and a proof that you accumulated "dog" and "cat". 56 | //! let (acc, proof) = acc.add_with_proof(&["dog", "cat"]); 57 | //! 58 | //! // A network participant who sees (acc, proof, and ["dog", "cat"]) can verify that the update 59 | //! // was formed correctly ... 60 | //! assert!(acc.verify_membership_batch(&["dog", "cat"], &proof)); 61 | //! 62 | //! // ... and trying to verify something that has not been accumulated will fail. 63 | //! assert!(!acc.verify_membership(&"cow", &proof)); 64 | //! ``` 65 | //! 66 | //! Typical users of this library will access public-facing routines on `accumulator` and 67 | //! `vector_commitment`. However, we also export internal modules for useful traits, types (such as 68 | //! the `Rsa2048` group), and specialized procedures. **Use internal components at your own risk**. 69 | //! 70 | //! You can find a more interesting application of our library 71 | //! [here](https://github.com/cambrian/accumulator-demo), where we create a proof-of-concept for 72 | //! stateless Bitcoin nodes! 73 | //! 74 | //! # Groups 75 | //! 76 | //! Accumulator and vector commitment operations take place over algebraic groups with certain 77 | //! cryptographic properties. We provide implementations for two suitable groups: 78 | //! (1) an RSA group with the [RSA-2048 modulus](https://en.wikipedia.org/wiki/RSA_numbers#RSA-2048) 79 | //! and (2) an ideal class group with a fixed discriminant generated by OpenSSL. 80 | //! 81 | //! The RSA group is fast but relies on the security of the RSA-2048 modulus and needs trusted 82 | //! setup if using a different modulus. The class group is slower but eliminates the need for a 83 | //! trusted setup. For more on class groups, please visit this 84 | //! [thorough explainer](https://www.michaelstraka.com/posts/classgroups/) by contributor Michael 85 | //! Straka. 86 | //! 87 | //! # Performance 88 | //! 89 | //! Most accumulator or vector commitment functions will bottleneck in hashing to large primes. To 90 | //! alleviate this, we created a zero-allocation `U256` type that uses the low-level `mpn_` 91 | //! functions in [GMP](https://gmplib.org). Our `hash_to_prime` uses this type internally. 92 | //! 93 | //! Class groups are currently not performant for any meaningful use case. A pull request is in the 94 | //! works to drastically improve their performance using techniques learned from the 95 | //! [Chia VDF competition](https://github.com/Chia-Network/vdf-competition). 96 | #![allow(clippy::unknown_clippy_lints)] 97 | #![allow(clippy::many_single_char_names)] 98 | #![allow(clippy::empty_enum)] 99 | #![warn(missing_docs)] 100 | 101 | #[macro_use] 102 | extern crate lazy_static; 103 | 104 | #[macro_use] 105 | extern crate arrayref; 106 | 107 | mod accumulator; 108 | pub use crate::accumulator::*; 109 | mod vector_commitment; 110 | pub use vector_commitment::*; 111 | 112 | pub mod group; 113 | pub mod hash; 114 | pub mod proof; 115 | #[allow(missing_docs)] 116 | pub mod uint; 117 | pub mod util; 118 | -------------------------------------------------------------------------------- /src/group/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementations for different mathematical groups, each of which satisfies our 2 | //! `UnknownOrderGroup` trait. They can be used with the accumulator and vector commitment 3 | //! structures, or standalone if you have a custom application. 4 | //! 5 | //! The preferred elliptic group implementation is the `Ristretto` group, which is a cyclic subset 6 | //! of the `Ed25519` group. 7 | use crate::util::{int, TypeRep}; 8 | use rug::Integer; 9 | use std::fmt::Debug; 10 | use std::hash::Hash; 11 | use std::marker::Sized; 12 | 13 | mod class; 14 | pub use class::{ClassElem, ClassGroup}; 15 | mod ristretto; 16 | pub use ristretto::{Ristretto, RistrettoElem}; 17 | mod rsa; 18 | pub use rsa::{Rsa2048, Rsa2048Elem}; 19 | 20 | /// A mathematical group. 21 | /// 22 | /// This trait allows the implementation of standard group routines: 23 | /// - Identity 24 | /// - Op (the fundamental group operation) 25 | /// - Exponentiation 26 | /// - Inverse (particularly where this is efficient to compute) 27 | /// 28 | /// The `TypeRep` trait lets us emulate type-level static fields, e.g. the modulus in an RSA group 29 | /// or the discriminant in a class group. 30 | /// 31 | /// Clients of this trait need to implement functions of the form `*_`, which take in `TypeRep` 32 | /// data as a parameter. Consumers use functions without the underscore: `id`, `op`, `exp`, and 33 | /// `inv`. 34 | 35 | // The other traits are only required here because Rust can't figure out how to do stuff with an 36 | // `Accumulator` even though it's just a wrapped `G::Elem`. If possible we'd remove them. 37 | pub trait Group: Clone + Debug + Eq + Hash + TypeRep + Send + Sync { 38 | // In theory the association `Group::Elem` is bijective, such that it makes sense to write 39 | // something like `Elem::Group::get()`. This would let us define `op`, `exp`, `inv`, etc. on the 40 | //`Elem` type and avoid using prefix notation for all of our group operations. Bijective 41 | // associated types are not currently supported by Rust. 42 | 43 | /// The associated group element type for this group. 44 | type Elem: Clone + Debug + Eq + Hash + Sized + Send + Sync; 45 | 46 | /// A group-specific wrapper for `id`. 47 | fn id_(rep: &Self::Rep) -> Self::Elem; 48 | 49 | /// A group-specific wrapper for `op`. 50 | fn op_(rep: &Self::Rep, a: &Self::Elem, b: &Self::Elem) -> Self::Elem; 51 | 52 | /// A group-specific wrapper for `exp`, although it comes with a default implementation via 53 | /// repeated squaring. 54 | /// 55 | /// Specific implementations may provide more performant specializations as needed (e.g. 56 | /// Montgomery multiplication for RSA groups). 57 | fn exp_(_rep: &Self::Rep, a: &Self::Elem, n: &Integer) -> Self::Elem { 58 | let (mut val, mut a, mut n) = { 59 | if *n < int(0) { 60 | (Self::id(), Self::inv(a), int(-n)) 61 | } else { 62 | (Self::id(), a.clone(), n.clone()) 63 | } 64 | }; 65 | while n > int(0) { 66 | if n.is_odd() { 67 | val = Self::op(&val, &a); 68 | } 69 | a = Self::op(&a, &a); 70 | n >>= 1; 71 | } 72 | val 73 | } 74 | 75 | /// A group-specific wrapper for `inv`. 76 | fn inv_(rep: &Self::Rep, a: &Self::Elem) -> Self::Elem; 77 | 78 | // ------------------- 79 | // END OF REQUIRED FNS 80 | // ------------------- 81 | 82 | /// Returns the identity element of the group. 83 | fn id() -> Self::Elem { 84 | Self::id_(Self::rep()) 85 | } 86 | 87 | /// Applies the group operation to elements `a` and `b` and returns the result. 88 | fn op(a: &Self::Elem, b: &Self::Elem) -> Self::Elem { 89 | Self::op_(Self::rep(), a, b) 90 | } 91 | 92 | /// Applies the group operation to `a` and itself `n` times and returns the result. 93 | fn exp(a: &Self::Elem, n: &Integer) -> Self::Elem { 94 | Self::exp_(Self::rep(), a, n) 95 | } 96 | 97 | /// Returns the group inverse of `a`. 98 | fn inv(a: &Self::Elem) -> Self::Elem { 99 | Self::inv_(Self::rep(), a) 100 | } 101 | } 102 | 103 | /// A group containing elements of unknown order. 104 | /// 105 | /// **Note**: This trait does not imply that the group itself has unknown order (e.g. RSA groups). 106 | #[allow(clippy::module_name_repetitions)] 107 | pub trait UnknownOrderGroup: Group { 108 | /// Returns an element of unknown order in the group. 109 | fn unknown_order_elem() -> Self::Elem { 110 | Self::unknown_order_elem_(Self::rep()) 111 | } 112 | 113 | /// A group-specific wrapper for `unknown_order_elem`. 114 | fn unknown_order_elem_(rep: &Self::Rep) -> Self::Elem; 115 | } 116 | 117 | /// Like `From`, but implemented on the `Group` instead of the element type. 118 | pub trait ElemFrom: Group { 119 | /// Returns a group element from an initial value. 120 | fn elem(val: T) -> Self::Elem; 121 | } 122 | 123 | /// Computes the product of `alpha_i ^ (p(x) / x_i)`, where `i` is an index into the `alphas` and 124 | /// `x` arrays, and `p(x)` is the product of all `x_i`. See BBF (page 11). 125 | pub fn multi_exp(alphas: &[G::Elem], x: &[Integer]) -> G::Elem { 126 | if alphas.len() == 1 { 127 | return alphas[0].clone(); 128 | } 129 | 130 | let n_half = alphas.len() / 2; 131 | let alpha_l = &alphas[..n_half]; 132 | let alpha_r = &alphas[n_half..]; 133 | let x_l = &x[..n_half]; 134 | let x_r = &x[n_half..]; 135 | let x_star_l = x_l.iter().product(); 136 | let x_star_r = x_r.iter().product(); 137 | let l = multi_exp::(alpha_l, x_l); 138 | let r = multi_exp::(alpha_r, x_r); 139 | G::op(&G::exp(&l, &x_star_r), &G::exp(&r, &x_star_l)) 140 | } 141 | 142 | #[cfg(test)] 143 | mod tests { 144 | use super::*; 145 | use crate::util::int; 146 | 147 | #[test] 148 | fn test_multi_exp() { 149 | let alpha_1 = Rsa2048::elem(2); 150 | let alpha_2 = Rsa2048::elem(3); 151 | let x_1 = int(3); 152 | let x_2 = int(2); 153 | let res = multi_exp::( 154 | &[alpha_1.clone(), alpha_2.clone()], 155 | &[x_1.clone(), x_2.clone()], 156 | ); 157 | assert!(res == Rsa2048::elem(108)); 158 | let alpha_3 = Rsa2048::elem(5); 159 | let x_3 = int(1); 160 | let res_2 = multi_exp::(&[alpha_1, alpha_2, alpha_3], &[x_1, x_2, x_3]); 161 | assert!(res_2 == Rsa2048::elem(1_687_500)); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | //! Miscellaneous functions used throughout the library. 2 | use crate::group::Group; 3 | use crate::hash::hash_to_prime; 4 | use rug::Integer; 5 | use std::hash::Hash; 6 | 7 | /// Pseudo-type-level programming. 8 | /// This trait allows us to reflect "type-level" (i.e. static) information at runtime. 9 | pub trait TypeRep: 'static { 10 | /// The associated type of the simulated type-level static information. 11 | type Rep: 'static; 12 | 13 | /// Returns the static data for the type. 14 | fn rep() -> &'static Self::Rep; 15 | } 16 | 17 | /// Convenience wrapper for creating `Rug` integers. 18 | pub fn int(val: T) -> Integer 19 | where 20 | Integer: From, 21 | { 22 | Integer::from(val) 23 | } 24 | 25 | /// Hashes its arguments to primes and takes their product. 26 | pub fn prime_hash_product(ts: &[T]) -> Integer { 27 | ts.iter().map(hash_to_prime).product() 28 | } 29 | 30 | /// Computes the `(xy)`th root of `g` given the `x`th and `y`th roots of `g` and `(x, y)` coprime. 31 | // TODO: Consider moving this to the `accumulator` module? 32 | #[allow(clippy::similar_names)] 33 | pub fn shamir_trick( 34 | xth_root: &G::Elem, 35 | yth_root: &G::Elem, 36 | x: &Integer, 37 | y: &Integer, 38 | ) -> Option { 39 | if G::exp(xth_root, x) != G::exp(yth_root, y) { 40 | return None; 41 | } 42 | 43 | let (gcd, a, b) = <(Integer, Integer, Integer)>::from(x.gcd_cofactors_ref(&y)); 44 | 45 | if gcd != int(1) { 46 | return None; 47 | } 48 | 49 | Some(G::op(&G::exp(xth_root, &b), &G::exp(yth_root, &a))) 50 | } 51 | 52 | /// Solves a linear congruence of form `ax = b mod m` for the set of solutions `x`. Solution sets 53 | /// are characterized by integers `mu` and `v` s.t. `x = mu + vn` and `n` is any integer. 54 | pub fn solve_linear_congruence( 55 | a: &Integer, 56 | b: &Integer, 57 | m: &Integer, 58 | ) -> Option<(Integer, Integer)> { 59 | // g = gcd(a, m) => da + em = g 60 | let (g, d, _) = <(Integer, Integer, Integer)>::from(a.gcd_cofactors_ref(m)); 61 | 62 | // q = floor_div(b, g) 63 | // r = b % g 64 | let (q, r) = <(Integer, Integer)>::from(b.div_rem_floor_ref(&g)); 65 | if r != 0 { 66 | return None; 67 | } 68 | 69 | let mu = (q * d) % m; 70 | let v = m / g; 71 | Some((mu, v)) 72 | } 73 | 74 | /// Folds over `xs` but in a divide-and-conquer fashion: Instead of `F(F(F(F(acc, a), b), c), d))` 75 | /// this computes `F(acc, F(F(a, b), F(c, d)))`. 76 | pub fn divide_and_conquer(f: F, acc: T, xs: &[T]) -> Result 77 | where 78 | F: Fn(&T, &T) -> Result, 79 | { 80 | if xs.is_empty() { 81 | return Ok(acc); 82 | } 83 | 84 | Ok(f(&acc, ÷_and_conquer_(&f, xs)?)?) 85 | } 86 | 87 | fn divide_and_conquer_(f: &F, xs: &[T]) -> Result 88 | where 89 | F: Fn(&T, &T) -> Result, 90 | { 91 | if xs.len() == 1 { 92 | return Ok(xs[0].clone()); 93 | } 94 | 95 | let mid = xs.len() / 2; 96 | let left = &xs[..mid]; 97 | let right = &xs[mid..]; 98 | Ok(f( 99 | ÷_and_conquer_(f, left)?, 100 | ÷_and_conquer_(f, right)?, 101 | )?) 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use super::*; 107 | use crate::group::{Group, Rsa2048, UnknownOrderGroup}; 108 | use crate::util::int; 109 | 110 | #[derive(Debug)] 111 | enum Never {} 112 | 113 | /// Merge-based computation of `Integer` array products. Faster than the iterative 114 | /// `iter.product()` for really large integers. 115 | fn merge_product(xs: &[Integer]) -> Integer { 116 | divide_and_conquer( 117 | |a, b| -> Result { Ok(int(a * b)) }, 118 | int(1), 119 | &xs, 120 | ) 121 | .unwrap() 122 | } 123 | 124 | #[test] 125 | fn test_linear_congruence_solver() { 126 | assert_eq!( 127 | (Integer::from(-2), Integer::from(4)), 128 | solve_linear_congruence(&Integer::from(3), &Integer::from(2), &Integer::from(4)).unwrap() 129 | ); 130 | 131 | assert_eq!( 132 | (Integer::from(-2), Integer::from(4)), 133 | solve_linear_congruence(&Integer::from(3), &Integer::from(2), &Integer::from(4)).unwrap() 134 | ); 135 | 136 | assert_eq!( 137 | (Integer::from(1), Integer::from(2)), 138 | solve_linear_congruence(&Integer::from(5), &Integer::from(1), &Integer::from(2)).unwrap() 139 | ); 140 | 141 | assert_eq!( 142 | (Integer::from(-3), Integer::from(5)), 143 | solve_linear_congruence(&Integer::from(2), &Integer::from(4), &Integer::from(5)).unwrap() 144 | ); 145 | 146 | assert_eq!( 147 | (Integer::from(2491), Integer::from(529)), 148 | solve_linear_congruence( 149 | &Integer::from(230), 150 | &Integer::from(1081), 151 | &Integer::from(12167) 152 | ) 153 | .unwrap() 154 | ); 155 | } 156 | 157 | #[test] 158 | fn test_linear_congruence_solver_no_solution() { 159 | // Let `g = gcd(a, m)`. If `b` is not divisible by `g`, there are no solutions. If `b` is 160 | // divisible by `g`, there are `g` solutions. 161 | let result = 162 | solve_linear_congruence(&Integer::from(33), &Integer::from(7), &Integer::from(143)); 163 | assert!(result.is_none()); 164 | 165 | let result = 166 | solve_linear_congruence(&Integer::from(13), &Integer::from(14), &Integer::from(39)); 167 | assert!(result.is_none()); 168 | } 169 | 170 | #[test] 171 | fn test_shamir_trick() { 172 | let (x, y, z) = (&int(13), &int(17), &int(19)); 173 | let xth_root = Rsa2048::exp(&Rsa2048::unknown_order_elem(), &int(y * z)); 174 | let yth_root = Rsa2048::exp(&Rsa2048::unknown_order_elem(), &int(x * z)); 175 | let xyth_root = Rsa2048::exp(&Rsa2048::unknown_order_elem(), z); 176 | assert!(shamir_trick::(&xth_root, &yth_root, x, y) == Some(xyth_root)); 177 | } 178 | 179 | #[test] 180 | fn test_shamir_trick_failure() { 181 | let (x, y, z) = (&int(7), &int(14), &int(19)); // Inputs not coprime. 182 | let xth_root = Rsa2048::exp(&Rsa2048::unknown_order_elem(), &int(y * z)); 183 | let yth_root = Rsa2048::exp(&Rsa2048::unknown_order_elem(), &int(x * z)); 184 | assert!(shamir_trick::(&xth_root, &yth_root, x, y) == None); 185 | } 186 | 187 | #[test] 188 | fn test_merge_product() { 189 | let ints = vec![int(3), int(5), int(7), int(9), int(11)]; 190 | assert!(merge_product(&ints) == int(10395)); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/hash/primality/constants.rs: -------------------------------------------------------------------------------- 1 | /// Used as a prefilter to `is_prob_prime`. The number of these has been tuned on a 2018 Macbook 2 | /// Pro. 3 | pub const SMALL_PRIMES: [u64; 200] = [ 4 | 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 5 | 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 6 | 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 7 | 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 8 | 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 9 | 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 10 | 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 11 | 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 12 | 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 13 | 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 14 | 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 15 | ]; 16 | 17 | #[allow(dead_code, clippy::unreadable_literal)] 18 | pub const MED_PRIMES: [u64; 456] = [ 19 | 106957, 106961, 106963, 106979, 106993, 107021, 107033, 107053, 107057, 107069, 107071, 107077, 20 | 107089, 107099, 107101, 107119, 107123, 107137, 107171, 107183, 107197, 107201, 107209, 107227, 21 | 107243, 107251, 107269, 107273, 107279, 107309, 107323, 107339, 107347, 107351, 107357, 107377, 22 | 107441, 107449, 107453, 107467, 107473, 107507, 107509, 107563, 107581, 107599, 107603, 107609, 23 | 107621, 107641, 107647, 107671, 107687, 107693, 107699, 107713, 107717, 107719, 107741, 107747, 24 | 107761, 107773, 107777, 107791, 107827, 107837, 107839, 107843, 107857, 107867, 107873, 107881, 25 | 107897, 107903, 107923, 107927, 107941, 107951, 107971, 107981, 107999, 108007, 108011, 108013, 26 | 108023, 108037, 108041, 108061, 108079, 108089, 108107, 108109, 108127, 108131, 108139, 108161, 27 | 108179, 108187, 108191, 108193, 108203, 108211, 108217, 108223, 108233, 108247, 108263, 108271, 28 | 108287, 108289, 108293, 108301, 108343, 108347, 108359, 108377, 108379, 108401, 108413, 108421, 29 | 108439, 108457, 108461, 108463, 108497, 108499, 108503, 108517, 108529, 108533, 108541, 108553, 30 | 108557, 108571, 108587, 108631, 108637, 108643, 108649, 108677, 108707, 108709, 108727, 108739, 31 | 108751, 108761, 108769, 108791, 108793, 108799, 108803, 108821, 108827, 108863, 108869, 108877, 32 | 108881, 108883, 108887, 108893, 108907, 108917, 108923, 108929, 108943, 108947, 108949, 108959, 33 | 108961, 108967, 108971, 108991, 109001, 109013, 109037, 109049, 109063, 109073, 109097, 109103, 34 | 109111, 109121, 109133, 109139, 109141, 109147, 109159, 109169, 109171, 109199, 109201, 109211, 35 | 109229, 109253, 109267, 109279, 109297, 109303, 109313, 109321, 109331, 109357, 109363, 109367, 36 | 109379, 109387, 109391, 109397, 109423, 109433, 109441, 109451, 109453, 109469, 109471, 109481, 37 | 109507, 109517, 109519, 109537, 109541, 109547, 109567, 109579, 109583, 109589, 109597, 109609, 38 | 109619, 109621, 109639, 109661, 109663, 109673, 109717, 109721, 109741, 109751, 109789, 109793, 39 | 109807, 109819, 109829, 109831, 109841, 109843, 109847, 109849, 109859, 109873, 109883, 109891, 40 | 109897, 109903, 109913, 109919, 109937, 109943, 109961, 109987, 110017, 110023, 110039, 110051, 41 | 110059, 110063, 110069, 110083, 110119, 110129, 110161, 110183, 110221, 110233, 110237, 110251, 42 | 110261, 110269, 110273, 110281, 110291, 110311, 110321, 110323, 110339, 110359, 110419, 110431, 43 | 110437, 110441, 110459, 110477, 110479, 110491, 110501, 110503, 110527, 110533, 110543, 110557, 44 | 110563, 110567, 110569, 110573, 110581, 110587, 110597, 110603, 110609, 110623, 110629, 110641, 45 | 110647, 110651, 110681, 110711, 110729, 110731, 110749, 110753, 110771, 110777, 110807, 110813, 46 | 110819, 110821, 110849, 110863, 110879, 110881, 110899, 110909, 110917, 110921, 110923, 110927, 47 | 110933, 110939, 110947, 110951, 110969, 110977, 110989, 111029, 111031, 111043, 111049, 111053, 48 | 111091, 111103, 111109, 111119, 111121, 111127, 111143, 111149, 111187, 111191, 111211, 111217, 49 | 111227, 111229, 111253, 111263, 111269, 111271, 111301, 111317, 111323, 111337, 111341, 111347, 50 | 111373, 111409, 111427, 111431, 111439, 111443, 111467, 111487, 111491, 111493, 111497, 111509, 51 | 111521, 111533, 111539, 111577, 111581, 111593, 111599, 111611, 111623, 111637, 111641, 111653, 52 | 111659, 111667, 111697, 111721, 111731, 111733, 111751, 111767, 111773, 111779, 111781, 111791, 53 | 111799, 111821, 111827, 111829, 111833, 111847, 111857, 111863, 111869, 111871, 111893, 111913, 54 | 111919, 111949, 111953, 111959, 111973, 111977, 111997, 112019, 112031, 112061, 112067, 112069, 55 | 112087, 112097, 112103, 112111, 112121, 112129, 112139, 112153, 112163, 112181, 112199, 112207, 56 | 112213, 112223, 112237, 112241, 112247, 112249, 112253, 112261, 112279, 112289, 112291, 112297, 57 | ]; 58 | 59 | #[allow(dead_code)] 60 | pub const LARGE_PRIMES: [u64; 4] = [ 61 | 553_525_575_239_331_913, 62 | 12_702_637_924_034_044_211, 63 | 378_373_571_372_703_133, 64 | 8_640_171_141_336_142_787, 65 | ]; 66 | 67 | #[allow(dead_code)] 68 | pub const STRONG_BASE_2_PSEUDOPRIMES: [u64; 10] = [ 69 | 2047, 3277, 4033, 4681, 8321, 15841, 29341, 42799, 49141, 52633, 70 | ]; 71 | 72 | #[allow(dead_code)] 73 | pub const STRONG_LUCAS_PSEUDOPRIMES: [u64; 10] = [ 74 | 5459, 5777, 10877, 16109, 18971, 22499, 24569, 25199, 40309, 58519, 75 | ]; 76 | 77 | #[allow(dead_code)] 78 | pub const EXTRA_STRONG_LUCAS_PSEUDOPRIMES: [u64; 10] = [ 79 | 989, 3239, 5777, 10877, 27971, 29681, 30739, 31631, 39059, 72389, 80 | ]; 81 | 82 | pub const D_VALUES: [i32; 500] = [ 83 | 5, -7, 9, -11, 13, -15, 17, -19, 21, -23, 25, -27, 29, -31, 33, -35, 37, -39, 41, -43, 45, -47, 84 | 49, -51, 53, -55, 57, -59, 61, -63, 65, -67, 69, -71, 73, -75, 77, -79, 81, -83, 85, -87, 89, 85 | -91, 93, -95, 97, -99, 101, -103, 105, -107, 109, -111, 113, -115, 117, -119, 121, -123, 125, 86 | -127, 129, -131, 133, -135, 137, -139, 141, -143, 145, -147, 149, -151, 153, -155, 157, -159, 87 | 161, -163, 165, -167, 169, -171, 173, -175, 177, -179, 181, -183, 185, -187, 189, -191, 193, 88 | -195, 197, -199, 201, -203, 205, -207, 209, -211, 213, -215, 217, -219, 221, -223, 225, -227, 89 | 229, -231, 233, -235, 237, -239, 241, -243, 245, -247, 249, -251, 253, -255, 257, -259, 261, 90 | -263, 265, -267, 269, -271, 273, -275, 277, -279, 281, -283, 285, -287, 289, -291, 293, -295, 91 | 297, -299, 301, -303, 305, -307, 309, -311, 313, -315, 317, -319, 321, -323, 325, -327, 329, 92 | -331, 333, -335, 337, -339, 341, -343, 345, -347, 349, -351, 353, -355, 357, -359, 361, -363, 93 | 365, -367, 369, -371, 373, -375, 377, -379, 381, -383, 385, -387, 389, -391, 393, -395, 397, 94 | -399, 401, -403, 405, -407, 409, -411, 413, -415, 417, -419, 421, -423, 425, -427, 429, -431, 95 | 433, -435, 437, -439, 441, -443, 445, -447, 449, -451, 453, -455, 457, -459, 461, -463, 465, 96 | -467, 469, -471, 473, -475, 477, -479, 481, -483, 485, -487, 489, -491, 493, -495, 497, -499, 97 | 501, -503, 505, -507, 509, -511, 513, -515, 517, -519, 521, -523, 525, -527, 529, -531, 533, 98 | -535, 537, -539, 541, -543, 545, -547, 549, -551, 553, -555, 557, -559, 561, -563, 565, -567, 99 | 569, -571, 573, -575, 577, -579, 581, -583, 585, -587, 589, -591, 593, -595, 597, -599, 601, 100 | -603, 605, -607, 609, -611, 613, -615, 617, -619, 621, -623, 625, -627, 629, -631, 633, -635, 101 | 637, -639, 641, -643, 645, -647, 649, -651, 653, -655, 657, -659, 661, -663, 665, -667, 669, 102 | -671, 673, -675, 677, -679, 681, -683, 685, -687, 689, -691, 693, -695, 697, -699, 701, -703, 103 | 705, -707, 709, -711, 713, -715, 717, -719, 721, -723, 725, -727, 729, -731, 733, -735, 737, 104 | -739, 741, -743, 745, -747, 749, -751, 753, -755, 757, -759, 761, -763, 765, -767, 769, -771, 105 | 773, -775, 777, -779, 781, -783, 785, -787, 789, -791, 793, -795, 797, -799, 801, -803, 805, 106 | -807, 809, -811, 813, -815, 817, -819, 821, -823, 825, -827, 829, -831, 833, -835, 837, -839, 107 | 841, -843, 845, -847, 849, -851, 853, -855, 857, -859, 861, -863, 865, -867, 869, -871, 873, 108 | -875, 877, -879, 881, -883, 885, -887, 889, -891, 893, -895, 897, -899, 901, -903, 905, -907, 109 | 909, -911, 913, -915, 917, -919, 921, -923, 925, -927, 929, -931, 933, -935, 937, -939, 941, 110 | -943, 945, -947, 949, -951, 953, -955, 957, -959, 961, -963, 965, -967, 969, -971, 973, -975, 111 | 977, -979, 981, -983, 985, -987, 989, -991, 993, -995, 997, -999, 1001, -1003, 112 | ]; 113 | -------------------------------------------------------------------------------- /src/hash/primality/mod.rs: -------------------------------------------------------------------------------- 1 | //! Primality testing for U256 inputs. Use `is_prob_prime` unless you have a specific reason to use 2 | //! a lower-level test. 3 | use crate::uint::{u256, u512, U256}; 4 | 5 | mod constants; 6 | use constants::{D_VALUES, SMALL_PRIMES}; 7 | 8 | /// Implements the Baillie-PSW probabilistic primality test, which is known to be deterministic over 9 | /// all integers up to 64 bits. 10 | /// 11 | /// Outperforms naked Miller-Rabin (i.e. iterated Fermat tests of random base) at wide `n` since 12 | /// Fermat and Lucas pseudoprimes have been shown to be anticorrelated. Steps of BPSW are as 13 | /// follows: 14 | /// 15 | /// 1. Accept small primes and reject multiples of them. 16 | /// 2. Do a single iteration of Miller-Rabin (in particular, a base-2 Fermat test). 17 | /// 3. Do a strong probabilistic Lucas test (squares filtered during test initialization). 18 | pub fn is_prob_prime(n: &U256) -> bool { 19 | for &p in SMALL_PRIMES.iter() { 20 | if n.is_divisible_u(p) { 21 | return *n == p; 22 | } 23 | } 24 | passes_miller_rabin_base_2(&n) && passes_lucas(&n) 25 | } 26 | 27 | /// A single iteration of the Miller-Rabin test (base-2 Fermat test). 28 | pub fn passes_miller_rabin_base_2(n: &U256) -> bool { 29 | let (d, r) = (n - 1).remove_factor(u256(2)); 30 | let mut x = u256(2).pow_mod(d, n); 31 | if x == 1 || x == n - 1 { 32 | return true; 33 | } 34 | for _ in 1..r { 35 | x = x * x % n; 36 | if x == 1 { 37 | return false; 38 | } 39 | if x == n - 1 { 40 | return true; 41 | } 42 | } 43 | false 44 | } 45 | 46 | /// Strong Lucas probable prime test (NOT the more common Lucas primality test which requires 47 | /// factorization of `n-1`). 48 | /// 49 | /// Selects parameters `d`, `p`, `q` according to Selfridge's method. 50 | /// 51 | /// If `n` passes, it is either prime or a "strong" Lucas pseudoprime. (The precise meaning of 52 | /// "strong" is not fixed in the literature.) Procedure can be further strengthened by implementing 53 | /// more tests in Section 6 of [Baillie and Wagstaff 1980], but for now this is TODO. 54 | /// 55 | /// See also: [Lucas pseudoprime](https://en.wikipedia.org/wiki/Lucas_pseudoprime) on Wikipedia. 56 | pub fn passes_lucas(n: &U256) -> bool { 57 | let d_ = choose_d(n); 58 | if d_.is_err() { 59 | return false; 60 | } 61 | let d = d_.unwrap(); 62 | let q = (1 - d) / 4; 63 | 64 | let (u_delta, v_delta, q_delta_over_2) = 65 | compute_lucas_sequences(*n + 1, n, u256(1), u256(1), q, d); 66 | // `u_delta % n != 0` proves n composite. 67 | u_delta == 0 68 | // Additional check which is not strictly part of Lucas test but nonetheless filters some 69 | // composite n for free. See section "Checking additional congruence conditions" on Wikipedia. 70 | && v_delta.is_congruent(2 * q, n) 71 | // Congruence check which holds for prime n by Euler's criterion. 72 | && q_delta_over_2.is_congruent(q * U256::jacobi(q, n), n) 73 | } 74 | 75 | #[derive(Debug)] 76 | struct IsPerfectSquare(); 77 | 78 | /// Finds and returns first `D` in `[5, -7, 9, ..., 5 + 2 * max_iter]` for which Jacobi symbol 79 | /// `(D/n) = -1`, or `Err` if no such `D` exists. In the case that `n` is square, there is no such 80 | /// `D` even with `max_iter` infinite. Hence if you are not entirely sure that `n` is nonsquare, 81 | /// you should pass a low value to `max_iter` to avoid wasting too much time. Note that the average 82 | /// number of iterations required for nonsquare `n` is 1.8, and empirically we find it is extremely 83 | /// rare that `|d| > 13`. 84 | /// 85 | /// We experimented with postponing the `is_perfect_square` check until after some number of 86 | /// iterations but ultimately found no performance gain. It is likely that most perfect squares 87 | /// are caught by the Miller-Rabin test. 88 | fn choose_d(n: &U256) -> Result { 89 | if n.is_perfect_square() { 90 | return Err(IsPerfectSquare()); 91 | } 92 | for &d in D_VALUES.iter() { 93 | if U256::jacobi(d, n) == -1 { 94 | return Ok(d); 95 | } 96 | } 97 | panic!("n is not square but we still couldn't find a d value!") 98 | } 99 | 100 | /// Computes the Lucas sequences `{u_i(p, q)}` and `{v_i(p, q)}` up to a specified index `k_target` 101 | /// in O(log(`k_target`)) time by recursively calculating only the `(2i)`th and `(2i+1)`th elements 102 | /// in an order determined by the binary expansion of `k`. Also returns `q^{k/2} (mod n)`, which is 103 | /// used in a stage of the strong Lucas test. In the Lucas case we specify that `d = p^2 - 4q` and 104 | /// set `k_target = delta = n - (d/n) = n + 1`. 105 | /// 106 | /// Note that `p` does not show up in the code because it is set to 1. 107 | #[allow(clippy::cast_sign_loss)] 108 | fn compute_lucas_sequences( 109 | k_target: U256, 110 | n: &U256, 111 | mut u: U256, 112 | mut v: U256, 113 | q0: i32, 114 | d: i32, 115 | ) -> (U256, U256, U256) { 116 | // Mod an `i32` into the `[0, n)` range. 117 | let i_mod_n = |x: i32| { 118 | if x < 0 { 119 | *n - (u256(x.abs() as u64) % n) 120 | } else { 121 | u256(x as u64) % n 122 | } 123 | }; 124 | let q0 = i_mod_n(q0); 125 | let d = i_mod_n(d); 126 | let mut q = q0; 127 | let mut q_k_over_2 = q0; 128 | 129 | // Finds `t` in `Z_n` with `2t = x (mod n)`. 130 | // Assumes `x` in `[0, n)`. 131 | let half = |x: U256| { 132 | if x.is_odd() { 133 | (x >> 1) + (*n >> 1) + 1 134 | } else { 135 | x >> 1 136 | } 137 | }; 138 | let sub_mod_n = |a, b| { 139 | if a > b { 140 | (a - b) % n 141 | } else { 142 | *n - (b - a) % n 143 | } 144 | }; 145 | // Write binary expansion of `k` as [x_1, ..., x_l], e.g. [1, 0, 1, 1] for 11. `x_1` is always 146 | // 1. For `i = 2, 3, ..., l`, do the following: if `x_i = 0`, then update `u_k` and `v_k` to 147 | // `u_{2k}` and `v_{2k}`, respectively. Else if `x_i = 1`, update to `u_{2k+1}` and `v_{2k+1}`. 148 | // At the end of the loop we will have computed `u_k` and `v_k`, with `k` as given, in 149 | // `log(delta)` time. 150 | let mut k_target_bits = [0; 257]; 151 | let len = k_target.write_binary(&mut k_target_bits); 152 | for &bit in k_target_bits[..len].iter().skip(1) { 153 | // Compute `(u, v)_{2k}` from `(u, v)_k` according to the following: 154 | // u_2k = u_k * v_k (mod n) 155 | // v_2k = v_k^2 - 2*q^k (mod n) 156 | u = u * v % n; 157 | v = sub_mod_n(v * v, u512(q) << 1); 158 | // Continuously maintain `q_k = q^k (mod n)` and `q_k_over_2 = q^{k/2} (mod n)`. 159 | q_k_over_2 = q; 160 | q = q * q % n; 161 | if bit == 1 { 162 | // Compute `(u, v)_{2k+1}` from `(u, v)_{2k}` according to the following: 163 | // u_{2k+1} = 1/2 * (p*u_{2k} + v_{2k}) (mod n) 164 | // v_{2k+1} = 1/2 * (d*u_{2k} + p*v_{2k}) (mod n) 165 | let u_old = u; 166 | u = half((u512(u) + u512(v)) % n); 167 | v = half((d * u_old + u512(v)) % n); 168 | q = q * q0 % n; 169 | } 170 | } 171 | // These are all `mod n` so the `low_u256` is lossless. We could make it checked... 172 | (u, v, q_k_over_2) 173 | } 174 | 175 | #[cfg(test)] 176 | mod tests { 177 | use self::constants::*; 178 | use super::*; 179 | #[test] 180 | fn test_miller_rabin() { 181 | assert!(passes_miller_rabin_base_2(&u256(13))); 182 | assert!(!passes_miller_rabin_base_2(&u256(65))); 183 | for &p in LARGE_PRIMES.iter() { 184 | assert!(passes_miller_rabin_base_2(&u256(p))); 185 | assert!(!passes_miller_rabin_base_2( 186 | &(u256(p) * u256(106_957)).low_u256() 187 | )); 188 | } 189 | for &n in STRONG_BASE_2_PSEUDOPRIMES.iter() { 190 | assert!(passes_miller_rabin_base_2(&u256(n))); 191 | } 192 | } 193 | 194 | #[test] 195 | fn test_lucas() { 196 | assert!(passes_lucas(&u256(5))); 197 | // Should fail on `p = 2`. 198 | for &sp in SMALL_PRIMES[1..].iter() { 199 | assert!(passes_lucas(&u256(sp))); 200 | assert!(!passes_lucas(&(u256(sp) * u256(2047)).low_u256())); 201 | } 202 | for &mp in MED_PRIMES.iter() { 203 | assert!(passes_lucas(&u256(mp))); 204 | assert!(!passes_lucas(&(u256(mp) * u256(5)).low_u256())); 205 | } 206 | for &lp in LARGE_PRIMES.iter() { 207 | assert!(passes_lucas(&u256(lp))); 208 | assert!(!passes_lucas(&(u256(lp) * u256(7)).low_u256())); 209 | } 210 | } 211 | 212 | #[test] 213 | fn test_is_prob_prime() { 214 | // Sanity checks. 215 | assert!(is_prob_prime(&u256(2))); 216 | assert!(is_prob_prime(&u256(5))); 217 | assert!(is_prob_prime(&u256(7))); 218 | assert!(is_prob_prime(&u256(241))); 219 | assert!(is_prob_prime(&u256(7919))); 220 | assert!(is_prob_prime(&u256(48131))); 221 | assert!(is_prob_prime(&u256(76463))); 222 | assert!(is_prob_prime(&u256(115_547))); 223 | 224 | // Medium primes. 225 | for &p in MED_PRIMES.iter() { 226 | assert!(is_prob_prime(&u256(p))); 227 | } 228 | 229 | // Large primes. 230 | for &p in LARGE_PRIMES.iter() { 231 | assert!(is_prob_prime(&u256(p))); 232 | } 233 | 234 | // Large, difficult-to-factor composites. 235 | for &p in LARGE_PRIMES.iter() { 236 | for &q in LARGE_PRIMES.iter() { 237 | assert!(!is_prob_prime(&(u256(p) * u256(q)).low_u256())); 238 | } 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/uint.rs: -------------------------------------------------------------------------------- 1 | //! Zero-allocation U256 and U512 types built on GMP. We created this module specifically for our 2 | //! use case of implementing primality checking over 256-bit integers, but it may be worth 3 | //! polishing a bit for more general use. 4 | //! 5 | //! Obviously there are a lot of `unsafe` blocks to work with GMP. Take care when using this module 6 | //! because there may be bugs we did not catch. 7 | //! 8 | //! TODO: Benchmark our U256 vs. 256-bit `rug::Integer` vs. Parity U256. 9 | #![allow(clippy::cast_sign_loss)] 10 | 11 | use gmp_mpfr_sys::gmp; 12 | use gmp_mpfr_sys::gmp::mpz_t; 13 | use rug::integer::Order; 14 | use rug::Integer; 15 | use std::cmp::{min, Ord, Ordering, PartialOrd}; 16 | use std::convert::From; 17 | use std::mem::transmute; 18 | use std::ops; 19 | use std::ptr; 20 | 21 | macro_rules! u_types { 22 | ($($t:ident,$size:expr),+) => { 23 | $( 24 | #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)] 25 | pub struct $t { 26 | // Field `size` also denotes the sign of the number, while `limbs` reflect only the 27 | // magnitude. 28 | // We keep size >= 0 except in very rare circumstances. 29 | size: i64, 30 | limbs: [u64; $size], 31 | } 32 | 33 | impl $t { 34 | fn data(&self) -> *mut u64 { 35 | &self.limbs as *const u64 as *mut u64 36 | } 37 | 38 | fn normalize_size(&mut self) { 39 | self.size = 0; 40 | for i in (0..$size).rev() { 41 | if self.limbs[i] != 0 { 42 | self.size = (i + 1) as i64; 43 | break; 44 | } 45 | } 46 | } 47 | 48 | // The cast from `i64` to `i32` is fine since |`size`| is <= 4 for `U256`, and <= 8 for 49 | // `U512`. 50 | #[allow(clippy::cast_possible_truncation)] 51 | fn as_mpz(&self) -> mpz_t { 52 | mpz_t { 53 | size: self.size as i32, 54 | d: std::ptr::NonNull::new((self.data())).unwrap(), 55 | alloc: $size, 56 | } 57 | } 58 | 59 | pub fn zero() -> Self { 60 | Self { size: 0, limbs: [0; $size] } 61 | } 62 | 63 | pub fn is_zero(&self) -> bool { 64 | self.size == 0 65 | } 66 | 67 | pub fn one() -> Self { 68 | let mut limbs = [0; $size]; 69 | limbs[0] = 1; 70 | Self { size: 1, limbs } 71 | } 72 | 73 | pub fn is_odd(&self) -> bool { 74 | self.limbs[0] & 1 == 1 75 | } 76 | 77 | #[allow(clippy::if_not_else)] 78 | /// Panics if `m == 0`. 79 | pub fn mod_inv(self, m: &Self) -> Option { 80 | let mut out = Self::zero(); 81 | let outmpz = out.as_mpz(); 82 | let s = self.as_mpz(); 83 | let m = m.as_mpz(); 84 | let exists = unsafe { gmp::mpz_invert(mut_ptr(&outmpz), mut_ptr(&s), mut_ptr(&m)) }; 85 | if exists != 0 { 86 | out.size = i64::from(outmpz.size); 87 | Some(out) 88 | } 89 | else { 90 | None 91 | } 92 | } 93 | 94 | /// Panics if `m == 0`. 95 | pub fn pow_mod(self, e: Self, m: &Self) -> Self { 96 | let mut out = Self::zero(); 97 | let outmpz = out.as_mpz(); 98 | let s = self.as_mpz(); 99 | let e = e.as_mpz(); 100 | let m = m.as_mpz(); 101 | unsafe { gmp::mpz_powm(mut_ptr(&outmpz), mut_ptr(&s), mut_ptr(&e), mut_ptr(&m)) }; 102 | out.size = i64::from(outmpz.size); 103 | out 104 | } 105 | 106 | pub fn is_perfect_square(&self) -> bool { 107 | let issqr = unsafe { gmp::mpn_perfect_square_p(self.data(), self.size) }; 108 | issqr != 0 109 | } 110 | 111 | pub fn jacobi(a: i32, b: &Self) -> i32 { 112 | let mut a_data = 0; 113 | let a = i32_to_mpz(a, &mut a_data); 114 | let b = b.as_mpz(); 115 | unsafe { gmp::mpz_jacobi(&a as *const mpz_t, &b as *const mpz_t) } 116 | } 117 | 118 | pub fn is_congruent(self, i: i32, m: &Self) -> bool { 119 | let mut data = 0; 120 | let x = i32_to_mpz(i, &mut data); 121 | let s = self.as_mpz(); 122 | let m = m.as_mpz(); 123 | let res = unsafe { gmp::mpz_congruent_p(mut_ptr(&s), mut_ptr(&x), mut_ptr(&m)) }; 124 | res != 0 125 | } 126 | 127 | pub fn is_divisible_u(&self, u: u64) -> bool { 128 | let s = self.as_mpz(); 129 | let divisible = unsafe {gmp::mpz_divisible_ui_p(mut_ptr(&s), u)}; 130 | divisible != 0 131 | } 132 | 133 | /// Panics if `buf` is not large enough. 134 | pub fn write_binary(&self, buf: &mut [u8]) -> usize { 135 | unsafe { gmp::mpn_get_str(mut_ptr(&buf[0]), 2, self.data(), self.size) } 136 | } 137 | 138 | pub fn from_be_bytes(bytes: &[u8]) -> Self { 139 | let x = Self::zero(); 140 | unsafe { gmp::mpn_set_str(x.data(), &bytes[0] as *const u8, bytes.len(), 256) }; 141 | x 142 | } 143 | } 144 | 145 | impl PartialEq for $t { 146 | fn eq(&self, u: &u64) -> bool { 147 | (*u == 0 && self.size == 0) || (self.size == 1 && self.limbs[0] == *u) 148 | } 149 | } 150 | 151 | impl From<[u64; $size]> for $t { 152 | fn from(limbs: [u64; $size]) -> Self { 153 | let mut x = Self { size: 0, limbs }; 154 | x.normalize_size(); 155 | x 156 | } 157 | } 158 | 159 | impl From for $t { 160 | fn from(x: u64) -> Self { 161 | let mut limbs = [0; $size]; 162 | limbs[0] = x; 163 | Self::from(limbs) 164 | } 165 | } 166 | 167 | /// Lower-endian `bytes`. 168 | impl From<[u8; $size * 8]> for $t { 169 | fn from(bytes: [u8; $size * 8]) -> Self { 170 | let chunks = unsafe { transmute::<[u8; $size * 8], [[u8; 8]; $size]>(bytes) }; 171 | let mut limbs = [0; $size]; 172 | for i in 0..$size { 173 | limbs[i] = u64::from_le_bytes(chunks[i]); 174 | } 175 | Self::from(limbs) 176 | } 177 | } 178 | 179 | /// Lower-endian `bytes`. 180 | impl From<&[u8; $size * 8]> for $t { 181 | fn from(bytes: &[u8; $size * 8]) -> Self { 182 | let chunks = unsafe { transmute::<[u8; $size * 8], [[u8; 8]; $size]>(*bytes) }; 183 | let mut limbs = [0; $size]; 184 | for i in 0..$size { 185 | limbs[i] = u64::from_le_bytes(chunks[i]); 186 | } 187 | Self::from(limbs) 188 | } 189 | } 190 | 191 | impl PartialOrd for $t { 192 | fn partial_cmp(&self, x: &Self) -> Option { 193 | let x = unsafe { gmp::mpn_cmp(self.data(), x.data(), $size) }; 194 | Some({ 195 | if x < 0 { 196 | Ordering::Less 197 | } else if x == 0 { 198 | Ordering::Equal 199 | } else { 200 | Ordering::Greater 201 | } 202 | }) 203 | } 204 | } 205 | 206 | impl Ord for $t { 207 | fn cmp(&self, x: &Self) -> Ordering { 208 | let x = unsafe { gmp::mpn_cmp(self.data(), x.data(), $size) }; 209 | if x < 0 { 210 | Ordering::Less 211 | } else if x == 0 { 212 | Ordering::Equal 213 | } else { 214 | Ordering::Greater 215 | } 216 | } 217 | } 218 | 219 | impl ops::ShlAssign for $t { 220 | fn shl_assign(&mut self, mut x: u32) { 221 | while x != 0 { 222 | let sz = min(gmp::LIMB_BITS as u32, x); 223 | x -= sz; 224 | unsafe { gmp::mpn_lshift(self.data(), self.data(), $size, sz) }; 225 | } 226 | self.normalize_size(); 227 | } 228 | } 229 | 230 | impl ops::Shl for $t { 231 | type Output = Self; 232 | fn shl(self, x: u32) -> Self { 233 | let mut y = self; 234 | y <<= x; 235 | y 236 | } 237 | } 238 | 239 | impl ops::ShrAssign for $t { 240 | fn shr_assign(&mut self, mut x: u32) { 241 | while x != 0 { 242 | let sz = min(gmp::LIMB_BITS as u32, x); 243 | x -= sz; 244 | unsafe { gmp::mpn_rshift(self.data(), self.data(), $size, sz) }; 245 | } 246 | self.normalize_size(); 247 | } 248 | } 249 | 250 | impl ops::Shr for $t { 251 | type Output = Self; 252 | fn shr(self, x: u32) -> Self { 253 | let mut y = self; 254 | y >>= x; 255 | y 256 | } 257 | } 258 | 259 | impl ops::AddAssign for $t { 260 | /// Panics if result overflows. 261 | fn add_assign(&mut self, x: Self) { 262 | let carry = unsafe { gmp::mpn_add_n(self.data(), self.data(), x.data(), $size) }; 263 | assert!(carry == 0); 264 | self.normalize_size(); 265 | } 266 | } 267 | 268 | impl ops::Add for $t { 269 | /// Panics if result overflows. 270 | type Output = Self; 271 | fn add(self, x: Self) -> Self { 272 | let mut y = self; 273 | y += x; 274 | y 275 | } 276 | } 277 | impl ops::Add for $t { 278 | type Output = Self; 279 | /// Panics if result overflows. 280 | fn add(self, x: u64) -> Self { 281 | self + Self::from(x) 282 | } 283 | } 284 | 285 | impl ops::SubAssign for $t { 286 | /// Panics if result is negative. 287 | fn sub_assign(&mut self, x: Self) { 288 | let borrow = unsafe { gmp::mpn_sub_n(self.data(), self.data(), x.data(), $size) }; 289 | assert!(borrow == 0); 290 | self.normalize_size(); 291 | } 292 | } 293 | 294 | impl ops::Sub for $t { 295 | type Output = Self; 296 | /// Panics if result is negative. 297 | fn sub(self, x: Self) -> Self { 298 | let mut y = self; 299 | y -= x; 300 | y 301 | } 302 | } 303 | 304 | impl ops::Sub for $t { 305 | type Output = Self; 306 | /// Panics if result is negative. 307 | fn sub(self, x: u64) -> Self { 308 | self - Self::from(x) 309 | } 310 | } 311 | 312 | impl ops::Sub for &$t { 313 | type Output = $t; 314 | /// Panics if result is negative. 315 | fn sub(self, x: u64) -> $t { 316 | *self - $t::from(x) 317 | } 318 | } 319 | 320 | impl ops::Rem<&Self> for $t { 321 | type Output = Self; 322 | fn rem(self, x: &Self) -> Self { 323 | if x.size > self.size { 324 | return self; 325 | } 326 | let (y, mut rem) = (Self::zero(), Self::zero()); 327 | unsafe { 328 | gmp::mpn_tdiv_qr( 329 | y.data(), 330 | rem.data(), 331 | 0, 332 | self.data(), 333 | self.size, 334 | x.data(), 335 | x.size, 336 | ) 337 | }; 338 | rem.normalize_size(); 339 | rem 340 | } 341 | } 342 | 343 | impl ops::Rem for $t { 344 | type Output = Self; 345 | fn rem(self, x: Self) -> Self { 346 | #![allow(clippy::op_ref)] 347 | self % &x 348 | } 349 | } 350 | 351 | impl ops::RemAssign<&Self> for $t { 352 | fn rem_assign(&mut self, x: &Self) { 353 | if x.size > self.size { 354 | return; 355 | } 356 | let y = Self::zero(); 357 | unsafe { 358 | gmp::mpn_tdiv_qr( 359 | y.data(), 360 | self.data(), 361 | 0, 362 | self.data(), 363 | self.size, 364 | x.data(), 365 | x.size, 366 | ) 367 | }; 368 | self.normalize_size(); 369 | } 370 | } 371 | 372 | impl ops::RemAssign for $t { 373 | fn rem_assign(&mut self, x: Self) { 374 | #![allow(clippy::op_ref)] 375 | *self %= &x; 376 | } 377 | } 378 | 379 | impl ops::Div<&Self> for $t { 380 | type Output = Self; 381 | fn div(self, x: &Self) -> Self { 382 | if x.size > self.size { 383 | return self; 384 | } 385 | let (mut y, rem) = (Self::zero(), Self::zero()); 386 | unsafe { 387 | gmp::mpn_tdiv_qr( 388 | y.data(), 389 | rem.data(), 390 | 0, 391 | self.data(), 392 | self.size, 393 | x.data(), 394 | x.size, 395 | ) 396 | }; 397 | y.normalize_size(); 398 | y 399 | } 400 | } 401 | 402 | impl ops::Div for $t { 403 | type Output = Self; 404 | fn div(self, x: Self) -> Self { 405 | #![allow(clippy::op_ref)] 406 | self / &x 407 | } 408 | } 409 | 410 | impl From<$t> for Integer { 411 | fn from(x: $t) -> Self { 412 | Self::from_digits(&x.limbs, Order::Lsf) 413 | } 414 | } 415 | )+ 416 | } 417 | } 418 | 419 | u_types!(U256, 4, U512, 8); 420 | 421 | impl U512 { 422 | /// Returns the lower half of this `U512` as a `U256`. 423 | /// TODO: Make checked? 424 | pub fn low_u256(self) -> U256 { 425 | let mut x = unsafe { transmute::(self) }.0; 426 | x.normalize_size(); 427 | x 428 | } 429 | } 430 | 431 | impl From<&U256> for U512 { 432 | fn from(x: &U256) -> Self { 433 | let mut limbs = [0; 8]; 434 | limbs[..4].copy_from_slice(&x.limbs); 435 | Self { 436 | size: x.size, 437 | limbs, 438 | } 439 | } 440 | } 441 | 442 | impl From for U512 { 443 | fn from(x: U256) -> Self { 444 | Self::from(&x) 445 | } 446 | } 447 | 448 | // This gets its own implementation for performance. 449 | impl ops::Rem<&U256> for U512 { 450 | type Output = U256; 451 | fn rem(self, x: &U256) -> U256 { 452 | if x.size > self.size { 453 | return self.low_u256(); 454 | } 455 | let (y, mut rem) = (Self::zero(), U256::zero()); 456 | unsafe { 457 | gmp::mpn_tdiv_qr( 458 | y.data(), 459 | rem.data(), 460 | 0, 461 | self.data(), 462 | self.size, 463 | x.data(), 464 | x.size, 465 | ) 466 | }; 467 | rem.normalize_size(); 468 | rem 469 | } 470 | } 471 | 472 | impl ops::Rem for U512 { 473 | type Output = U256; 474 | fn rem(self, x: U256) -> U256 { 475 | #![allow(clippy::op_ref)] 476 | self % &x 477 | } 478 | } 479 | 480 | impl U256 { 481 | /// Returns (result of removing all `f`s, number of `f`s removed) 482 | pub fn remove_factor(self, f: Self) -> (Self, u64) { 483 | // For some reason this needs extra scratch space. 484 | let mut out = U512::zero(); 485 | let outmpz = out.as_mpz(); 486 | let s = self.as_mpz(); 487 | let f = f.as_mpz(); 488 | let c = unsafe { gmp::mpz_remove(mut_ptr(&outmpz), mut_ptr(&s), mut_ptr(&f)) }; 489 | out.size = i64::from(outmpz.size); 490 | (out.low_u256(), c) 491 | } 492 | } 493 | 494 | /// It turns out to be faster to provide multiplication as `U256 * U256 -> U512`, because it lets us 495 | /// use `mpn_mul_n` instead of `mpn_mul`. 496 | impl ops::Mul<&Self> for U256 { 497 | type Output = U512; 498 | fn mul(self, x: &Self) -> U512 { 499 | let mut y = U512::zero(); 500 | unsafe { gmp::mpn_mul_n(y.data(), self.data(), x.data(), 4) }; 501 | y.normalize_size(); 502 | y 503 | } 504 | } 505 | 506 | impl ops::Mul for U256 { 507 | type Output = U512; 508 | fn mul(self, x: Self) -> U512 { 509 | #![allow(clippy::op_ref)] 510 | self * &x 511 | } 512 | } 513 | 514 | #[allow(unused_mut)] 515 | fn mut_ptr(mut t: &T) -> *mut T { 516 | t as *const T as *mut T 517 | } 518 | 519 | pub fn u256(t: T) -> U256 520 | where 521 | U256: From, 522 | { 523 | U256::from(t) 524 | } 525 | 526 | pub fn u512(t: T) -> U512 527 | where 528 | U512: From, 529 | { 530 | U512::from(t) 531 | } 532 | 533 | fn i32_to_mpz(i: i32, data: &mut u64) -> mpz_t { 534 | *data = i.abs() as u64; 535 | mpz_t { 536 | size: i.signum(), 537 | d: std::ptr::NonNull::new(data).unwrap() , 538 | alloc: 1, 539 | } 540 | } 541 | 542 | #[cfg(test)] 543 | mod tests { 544 | use super::*; 545 | 546 | #[test] 547 | fn test_add() { 548 | assert!(u256(1) + u256(0) == u256(1)); 549 | assert!(u256(1) + u256(2) == u256(3)); 550 | assert!(u256([0, 1, 0, 0]) + u256([0, 1, 0, 0]) == u256([0, 2, 0, 0])); 551 | assert!(u256([0, 1, 0, 0]) + u256([0, 1, 1, 1]) == u256([0, 2, 1, 1])); 552 | } 553 | 554 | #[should_panic(expected = "assertion failed: carry == 0")] 555 | #[test] 556 | fn test_add_overflow() { 557 | let _ = u256([0, 0, 0, u64::max_value()]) + u256([0, 0, 0, u64::max_value()]); 558 | } 559 | 560 | #[test] 561 | fn test_sub() { 562 | assert!(u256(1) - u256(0) == u256(1)); 563 | assert!(u256(1) - u256(1) == u256(0)); 564 | assert!(u256([0, 1, 0, 0]) - u256([0, 1, 0, 0]) == u256([0, 0, 0, 0])); 565 | assert!(u256([0, 1, 0, 1]) - u256([0, 1, 0, 0]) == u256([0, 0, 0, 1])); 566 | } 567 | 568 | #[should_panic(expected = "assertion failed: borrow == 0")] 569 | #[test] 570 | fn test_sub_borrow() { 571 | let _ = u256([0, 1, 0, 0]) - u256([0, 0, 1, 0]); 572 | } 573 | 574 | #[test] 575 | fn test_mul() { 576 | assert!(u256(0) * u256(3) == u512(0)); 577 | assert!(u256(2) * u256(3) == u512(6)); 578 | assert!(u256([0, 1, 0, 0]) * u256([0, 1, 0, 0]) == u512([0, 0, 1, 0, 0, 0, 0, 0])); 579 | assert!(u256([0, 2, 0, 0]) * u256([0, 1, 0, 1]) == u512([0, 0, 2, 0, 2, 0, 0, 0])); 580 | } 581 | 582 | #[test] 583 | fn test_div() { 584 | assert!(u256(0) / u256(3) == u256(0)); 585 | assert!(u256(5) / u256(3) == u256(1)); 586 | assert!(u256(6) / u256(3) == u256(2)); 587 | assert!(u256([0, 0, 1, 0]) / u256([0, 1, 0, 0]) == u256([0, 1, 0, 0])); 588 | } 589 | 590 | #[test] 591 | fn test_rem() { 592 | assert!(u256(0) % u256(3) == u256(0)); 593 | assert!(u256(5) % u256(3) == u256(2)); 594 | assert!(u256(6) % u256(3) == u256(0)); 595 | assert!(u256([1, 0, 1, 0]) % u256([0, 1, 0, 0]) == u256(1)); 596 | } 597 | 598 | #[test] 599 | fn test_rem512() { 600 | assert!(u512(0) % u256(3) == u256(0)); 601 | assert!(u512(5) % u256(3) == u256(2)); 602 | assert!(u512(6) % u256(3) == u256(0)); 603 | assert!(u512([1, 0, 1, 0, 0, 0, 0, 0]) % u256([0, 1, 0, 0]) == u256(1)); 604 | } 605 | } 606 | -------------------------------------------------------------------------------- /src/accumulator.rs: -------------------------------------------------------------------------------- 1 | //! Accumulator library, built on a generic group interface. 2 | use crate::group::UnknownOrderGroup; 3 | use crate::hash::hash_to_prime; 4 | use crate::proof::{Poe, Poke2}; 5 | use crate::util::{divide_and_conquer, int, prime_hash_product, shamir_trick}; 6 | use rug::Integer; 7 | use std::hash::Hash; 8 | use std::marker::PhantomData; 9 | 10 | #[derive(Debug)] 11 | /// The different types of accumulator errors. 12 | pub enum AccError { 13 | /// Bad witness. 14 | BadWitness, 15 | 16 | /// Error when updating a witness. 17 | BadWitnessUpdate, 18 | 19 | /// Division by zero. 20 | DivisionByZero, 21 | 22 | /// Inexact division where exact division was expected. 23 | InexactDivision, 24 | 25 | /// Inputs not coprime when they were expected to be coprime. 26 | InputsNotCoprime, 27 | } 28 | 29 | // See https://doc.rust-lang.org/std/marker/struct.PhantomData.html#ownership-and-the-drop-check 30 | // for recommendations regarding phantom types. Note that we disregard the suggestion to use a 31 | // const reference in the phantom type parameter, which causes issues for the `Send` trait. 32 | #[derive(Debug, Eq, Hash, PartialEq)] 33 | /// A cryptographic accumulator. Wraps a single unknown-order group element and phantom data 34 | /// representing the type `T` being hashed-to-prime and accumulated. 35 | pub struct Accumulator { 36 | phantom: PhantomData, 37 | value: G::Elem, 38 | } 39 | 40 | // Manual clone impl required because Rust's type inference is not good. See 41 | // https://github.com/rust-lang/rust/issues/26925. 42 | impl Clone for Accumulator { 43 | fn clone(&self) -> Self { 44 | Self { 45 | phantom: PhantomData, 46 | value: self.value.clone(), 47 | } 48 | } 49 | } 50 | 51 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 52 | /// A witness to one or more values in an accumulator, represented as an accumulator. 53 | pub struct Witness(pub Accumulator); 54 | 55 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 56 | /// A succinct proof of membership (some element is in some accumulator). 57 | pub struct MembershipProof { 58 | /// The witness for the element in question. 59 | pub witness: Witness, 60 | proof: Poe, 61 | } 62 | 63 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 64 | /// A succinct proof of nonmembership (some element is not in some accumulator). 65 | pub struct NonmembershipProof { 66 | phantom: PhantomData<*const T>, 67 | d: G::Elem, 68 | v: G::Elem, 69 | gv_inv: G::Elem, 70 | poke2_proof: Poke2, 71 | poe_proof: Poe, 72 | } 73 | 74 | impl Accumulator { 75 | /// Returns a new, empty accumulator. 76 | pub fn empty() -> Self { 77 | Self { 78 | phantom: PhantomData, 79 | value: G::unknown_order_elem(), 80 | } 81 | } 82 | 83 | /// Internal add method that also returns the prime hash product of added elements, enabling an 84 | /// efficient `add_with_proof`. 85 | fn add_(&self, elems: &[T]) -> (Self, Integer) { 86 | let x = prime_hash_product(elems); 87 | let acc_elem = G::exp(&self.value, &x); 88 | ( 89 | Self { 90 | phantom: PhantomData, 91 | value: acc_elem, 92 | }, 93 | x, 94 | ) 95 | } 96 | 97 | // The conciseness of `accumulator.add()` and low probability of confusion with implementations of 98 | // the `Add` trait probably justify this... 99 | #[allow(clippy::should_implement_trait)] 100 | /// Adds `elems` to the accumulator. This cannot check whether the elements have already been 101 | /// added, so is up to clients to ensure uniqueness. 102 | /// 103 | /// Uses a move instead of a `&self` reference to prevent accidental use of the old accumulator. 104 | pub fn add(self, elems: &[T]) -> Self { 105 | self.add_(elems).0 106 | } 107 | 108 | /// A specialized version of `add` that also returns a batch membership proof for added elements. 109 | pub fn add_with_proof(self, elems: &[T]) -> (Self, MembershipProof) { 110 | let (acc, x) = self.add_(elems); 111 | let proof = Poe::::prove(&self.value, &x, &acc.value); 112 | ( 113 | acc, 114 | MembershipProof { 115 | witness: Witness(self), 116 | proof, 117 | }, 118 | ) 119 | } 120 | 121 | /// Internal delete method that also returns the prime hash product of deleted elements, enabling 122 | /// an efficient `delete_with_proof`. 123 | /// 124 | /// Uses a divide-and-conquer approach to running the ShamirTrick, which keeps the average input 125 | /// smaller: For `[a, b, c, d]` do `S(S(a, b), S(c, d))` instead of `S(S(S(a, b), c), d)`. 126 | fn delete_(self, elem_witnesses: &[(T, Witness)]) -> Result<(Self, Integer), AccError> { 127 | let prime_witnesses = elem_witnesses 128 | .iter() 129 | .map(|(elem, witness)| (hash_to_prime(elem), witness.0.value.clone())) 130 | .collect::>(); 131 | 132 | for (p, witness_elem) in &prime_witnesses { 133 | if G::exp(&witness_elem, &p) != self.value { 134 | return Err(AccError::BadWitness); 135 | } 136 | } 137 | 138 | let (prime_product, acc_elem) = divide_and_conquer( 139 | |(p1, v1), (p2, v2)| Ok((int(p1 * p2), shamir_trick::(&v1, &v2, p1, p2).unwrap())), 140 | (int(1), self.value), 141 | &prime_witnesses[..], 142 | )?; 143 | 144 | Ok(( 145 | Self { 146 | phantom: PhantomData, 147 | value: acc_elem.clone(), 148 | }, 149 | prime_product, 150 | )) 151 | } 152 | 153 | /// Removes the elements in `elem_witnesses` from the accumulator. 154 | /// 155 | /// # Arguments 156 | /// 157 | /// * `elem_witnesses` - Tuples consisting of (element to delete, element's witness). 158 | /// 159 | /// Uses a move instead of a `&self` reference to prevent accidental use of the old accumulator. 160 | pub fn delete(self, elem_witnesses: &[(T, Witness)]) -> Result { 161 | Ok(self.delete_(elem_witnesses)?.0) 162 | } 163 | 164 | /// A specialized version of `delete` that also returns a batch membership proof for deleted 165 | /// elements. 166 | pub fn delete_with_proof( 167 | self, 168 | elem_witnesses: &[(T, Witness)], 169 | ) -> Result<(Self, MembershipProof), AccError> { 170 | let (acc, prime_product) = self.clone().delete_(elem_witnesses)?; 171 | let proof = Poe::::prove(&acc.value, &prime_product, &self.value); 172 | Ok(( 173 | acc.clone(), 174 | MembershipProof { 175 | witness: Witness(acc), 176 | proof, 177 | }, 178 | )) 179 | } 180 | 181 | /// Computes the batch membership proof for the elements in `elem_witnesses` w.r.t this 182 | /// accumulator. 183 | /// 184 | /// # Arguments 185 | /// 186 | /// * `elem_witnesses` - Tuples consisting of (element to prove, element's witness). 187 | pub fn prove_membership( 188 | &self, 189 | elem_witnesses: &[(T, Witness)], 190 | ) -> Result, AccError> { 191 | let witness_accum = self.clone().delete(elem_witnesses)?; 192 | let prod = elem_witnesses 193 | .iter() 194 | .map(|(t, _)| hash_to_prime(t)) 195 | .product(); 196 | let proof = Poe::::prove(&witness_accum.value, &prod, &self.value); 197 | Ok(MembershipProof { 198 | witness: Witness(witness_accum), 199 | proof, 200 | }) 201 | } 202 | 203 | /// Verifies a membership proof against the current accumulator and an element `t` whose 204 | /// inclusion is being proven. 205 | pub fn verify_membership( 206 | &self, 207 | t: &T, 208 | MembershipProof { witness, proof }: &MembershipProof, 209 | ) -> bool { 210 | let exp = hash_to_prime(t); 211 | Poe::verify(&witness.0.value, &exp, &self.value, proof) 212 | } 213 | 214 | /// Batch version of `verify_membership` for multiple `elems`. 215 | pub fn verify_membership_batch( 216 | &self, 217 | elems: &[T], 218 | MembershipProof { witness, proof }: &MembershipProof, 219 | ) -> bool { 220 | let exp = prime_hash_product(elems); 221 | Poe::verify(&witness.0.value, &exp, &self.value, proof) 222 | } 223 | 224 | /// Updates a `witness` for `tracked_elems` w.r.t the current accumulator, adding the elements in 225 | /// `untracked_additions` to the tracked set and removing the elements in `untracked_deletions` 226 | /// from the tracked set. 227 | /// 228 | /// See Section 4.2 of LLX for implementation details. 229 | pub fn update_membership_witness( 230 | &self, 231 | witness: Witness, 232 | tracked_elems: &[T], 233 | untracked_additions: &[T], 234 | untracked_deletions: &[T], 235 | ) -> Result, AccError> { 236 | let x = prime_hash_product(tracked_elems); 237 | let x_hat = prime_hash_product(untracked_deletions); 238 | 239 | for elem in tracked_elems { 240 | if untracked_additions.contains(elem) || untracked_deletions.contains(elem) { 241 | return Err(AccError::BadWitnessUpdate); 242 | } 243 | } 244 | 245 | let (gcd, a, b) = <(Integer, Integer, Integer)>::from(x.gcd_cofactors_ref(&x_hat)); 246 | assert!(gcd == int(1)); 247 | 248 | let w = witness.0.add(untracked_additions); 249 | let w_to_b = G::exp(&w.value, &b); 250 | let acc_new_to_a = G::exp(&self.value, &a); 251 | Ok(Witness(Self { 252 | phantom: PhantomData, 253 | value: G::op(&w_to_b, &acc_new_to_a), 254 | })) 255 | } 256 | 257 | /// Computes the batch non-membership proof for the elements in `elems` w.r.t this accumulator 258 | /// and its `acc_set`. 259 | /// 260 | /// # Arguments 261 | /// 262 | /// * `acc_set` - The set of elements committed to by this accumulator. 263 | /// * `elems` - The set of elements you want to prove are not in `acc_set`. 264 | pub fn prove_nonmembership( 265 | &self, 266 | acc_set: &[T], 267 | elems: &[T], 268 | ) -> Result, AccError> { 269 | let x: Integer = elems.iter().map(hash_to_prime).product(); 270 | let s = acc_set.iter().map(hash_to_prime).product(); 271 | let (gcd, a, b) = <(Integer, Integer, Integer)>::from(x.gcd_cofactors_ref(&s)); 272 | 273 | if gcd != int(1) { 274 | return Err(AccError::InputsNotCoprime); 275 | } 276 | 277 | let g = G::unknown_order_elem(); 278 | let d = G::exp(&g, &a); 279 | let v = G::exp(&self.value, &b); 280 | let gv_inv = G::op(&g, &G::inv(&v)); 281 | 282 | let poke2_proof = Poke2::prove(&self.value, &b, &v); 283 | let poe_proof = Poe::prove(&d, &x, &gv_inv); 284 | Ok(NonmembershipProof { 285 | phantom: PhantomData, 286 | d, 287 | v, 288 | gv_inv, 289 | poke2_proof, 290 | poe_proof, 291 | }) 292 | } 293 | 294 | /// Verifies a non-membership proof against the current accumulator and elements `elems` whose 295 | /// non-inclusion is being proven. 296 | pub fn verify_nonmembership( 297 | &self, 298 | elems: &[T], 299 | NonmembershipProof { 300 | d, 301 | v, 302 | gv_inv, 303 | poke2_proof, 304 | poe_proof, 305 | .. 306 | }: &NonmembershipProof, 307 | ) -> bool { 308 | let x = elems.iter().map(hash_to_prime).product(); 309 | Poke2::verify(&self.value, v, poke2_proof) && Poe::verify(d, &x, gv_inv, poe_proof) 310 | } 311 | } 312 | 313 | impl From<&[T]> for Accumulator { 314 | fn from(ts: &[T]) -> Self { 315 | Self::empty().add(ts) 316 | } 317 | } 318 | 319 | impl Witness { 320 | /// Given a witness for `witness_set`, returns a witness for `witness_subset`. 321 | /// 322 | /// The `witness_subset` must be a subset of the `witness_set`. 323 | pub fn compute_subset_witness( 324 | self, 325 | witness_set: &[T], 326 | witness_subset: &[T], 327 | ) -> Result 328 | where 329 | T: PartialEq, 330 | { 331 | for witness in witness_subset { 332 | if !witness_set.contains(witness) { 333 | return Err(AccError::BadWitness); 334 | } 335 | } 336 | 337 | let numerator = prime_hash_product(witness_set); 338 | let denominator = prime_hash_product(witness_subset); 339 | let (quotient, remainder) = numerator.div_rem(denominator); 340 | 341 | if remainder != int(0) { 342 | return Err(AccError::InexactDivision); 343 | } 344 | 345 | Ok(Self(Accumulator { 346 | phantom: PhantomData, 347 | value: G::exp(&self.0.value, "ient), 348 | })) 349 | } 350 | 351 | /// Given a witness for many `elems`, computes a sub-witness for each individual element in 352 | /// O(N log N) time. 353 | pub fn compute_individual_witnesses(&self, elems: &[T]) -> Vec<(T, Self)> { 354 | let hashes = elems.iter().map(hash_to_prime).collect::>(); 355 | elems 356 | .iter() 357 | .zip(self.root_factor(&hashes).iter()) 358 | .map(|(x, y)| (x.clone(), y.clone())) 359 | .collect() 360 | } 361 | 362 | #[allow(non_snake_case)] 363 | fn root_factor(&self, elems: &[Integer]) -> Vec { 364 | if elems.len() == 1 { 365 | return vec![self.clone()]; 366 | } 367 | let half_n = elems.len() / 2; 368 | let g_l = elems[..half_n].iter().fold(self.clone(), |sum, x| { 369 | Self(Accumulator { 370 | phantom: PhantomData, 371 | value: G::exp(&sum.0.value, x), 372 | }) 373 | }); 374 | let g_r = elems[half_n..].iter().fold(self.clone(), |sum, x| { 375 | Self(Accumulator { 376 | phantom: PhantomData, 377 | value: G::exp(&sum.0.value, x), 378 | }) 379 | }); 380 | let mut L = g_r.root_factor(&Vec::from(&elems[..half_n])); 381 | let mut R = g_l.root_factor(&Vec::from(&elems[half_n..])); 382 | L.append(&mut R); 383 | L 384 | } 385 | } 386 | 387 | #[cfg(test)] 388 | mod tests { 389 | use super::*; 390 | use crate::group::{ClassGroup, Rsa2048}; 391 | 392 | fn new_acc(data: &[T]) -> Accumulator { 393 | Accumulator::::empty().add(data) 394 | } 395 | 396 | macro_rules! test_all_groups { 397 | ($test_func:ident, $func_name_rsa:ident, $func_name_class:ident, $($attr:meta)*) => { 398 | #[test] 399 | $( 400 | #[$attr] 401 | )* 402 | fn $func_name_rsa() { 403 | $test_func::(); 404 | } 405 | 406 | #[test] 407 | $( 408 | #[$attr] 409 | )* 410 | fn $func_name_class() { 411 | $test_func::(); 412 | } 413 | }; 414 | } 415 | 416 | test_all_groups!(test_add, test_add_rsa2048, test_add_class,); 417 | fn test_add() { 418 | let acc = new_acc::(&["a", "b"]); 419 | let new_elems = ["c", "d"]; 420 | let (acc_new, proof) = acc.add_with_proof(&new_elems); 421 | let acc_expected = G::exp( 422 | &G::unknown_order_elem(), 423 | &prime_hash_product(&["a", "b", "c", "d"]), 424 | ); 425 | assert!(acc_new.value == acc_expected); 426 | assert!(acc_new.verify_membership_batch(&new_elems, &proof)); 427 | } 428 | 429 | test_all_groups!(test_delete, test_delete_rsa2048, test_delete_class,); 430 | fn test_delete() { 431 | let acc_0 = new_acc::(&["a", "b"]); 432 | let (acc_1, c_proof) = acc_0.clone().add_with_proof(&["c"]); 433 | let (acc_2, proof) = acc_1 434 | .clone() 435 | .delete_with_proof(&[("c", c_proof.witness)]) 436 | .expect("valid delete expected"); 437 | assert!(acc_2 == acc_0); 438 | assert!(acc_1.verify_membership(&"c", &proof)); 439 | } 440 | 441 | test_all_groups!( 442 | test_delete_empty, 443 | test_delete_empty_rsa2048, 444 | test_delete_empty_class, 445 | ); 446 | fn test_delete_empty() { 447 | let acc = new_acc::(&["a", "b"]); 448 | let (acc_new, proof) = acc 449 | .clone() 450 | .delete_with_proof(&[]) 451 | .expect("valid delete expected"); 452 | assert!(acc_new == acc); 453 | assert!(acc.verify_membership_batch(&[], &proof)); 454 | } 455 | 456 | test_all_groups!( 457 | test_delete_bad_witness, 458 | test_delete_bad_witness_rsa2048, 459 | test_delete_bad_witness_class, 460 | should_panic(expected = "BadWitness") 461 | ); 462 | fn test_delete_bad_witness() { 463 | let acc = Accumulator::::empty(); 464 | let a_witness = Witness(new_acc::(&["b", "c"])); 465 | let b_witness = Witness(new_acc::(&["a", "c"])); 466 | acc.delete(&[("a", a_witness), ("b", b_witness)]).unwrap(); 467 | } 468 | 469 | test_all_groups!( 470 | test_update_membership_witness, 471 | test_update_membership_witness_rsa2048, 472 | test_update_membership_witness_class, 473 | ); 474 | fn test_update_membership_witness() { 475 | let acc = new_acc::(&["a", "b", "c"]); 476 | let witness = Witness(new_acc::(&["c", "d"])); 477 | let witness_new = acc 478 | .update_membership_witness(witness, &["a"], &["b"], &["d"]) 479 | .unwrap(); 480 | assert!(witness_new.0.add(&["a"]) == acc); 481 | } 482 | 483 | test_all_groups!( 484 | test_update_membership_witness_failure, 485 | test_update_membership_witness_failure_rsa2048, 486 | test_update_membership_witness_failure_class, 487 | should_panic(expected = "BadWitnessUpdate") 488 | ); 489 | fn test_update_membership_witness_failure() { 490 | let acc = new_acc::(&["a", "b", "c"]); 491 | let witness = Witness(new_acc::(&["c", "d"])); 492 | acc 493 | .update_membership_witness(witness, &["a"], &["b"], &["a"]) 494 | .unwrap(); 495 | } 496 | 497 | test_all_groups!( 498 | test_prove_nonmembership, 499 | test_prove_nonmembership_rsa2048, 500 | test_prove_nonmembership_class, 501 | ); 502 | fn test_prove_nonmembership() { 503 | let acc_set = ["a", "b"]; 504 | let acc = new_acc::(&acc_set); 505 | let non_members = ["c", "d"]; 506 | let proof = acc 507 | .prove_nonmembership(&acc_set, &non_members) 508 | .expect("valid proof expected"); 509 | assert!(acc.verify_nonmembership(&non_members, &proof)); 510 | } 511 | 512 | test_all_groups!( 513 | test_compute_sub_witness, 514 | test_compute_sub_witness_rsa2048, 515 | test_compute_sub_witness_class, 516 | ); 517 | fn test_compute_sub_witness() { 518 | let empty_witness = Witness(Accumulator::::empty()); 519 | let sub_witness = empty_witness 520 | .compute_subset_witness(&["a", "b"], &["a"]) 521 | .unwrap(); 522 | let exp_quotient_expected = Witness(new_acc::(&["b"])); 523 | assert!(sub_witness == exp_quotient_expected); 524 | } 525 | 526 | test_all_groups!( 527 | test_compute_sub_witness_failure, 528 | test_compute_sub_witness_failure_rsa2048, 529 | test_compute_sub_witness_failure_class, 530 | should_panic(expected = "BadWitness") 531 | ); 532 | fn test_compute_sub_witness_failure() { 533 | let empty_witness = Witness(Accumulator::::empty()); 534 | empty_witness 535 | .compute_subset_witness(&["a", "b"], &["c"]) 536 | .unwrap(); 537 | } 538 | 539 | fn test_compute_individual_witnesses() { 540 | let acc = new_acc::(&["a", "b", "c"]); 541 | let witness_multiple = Witness(new_acc::(&["a"])); 542 | let witnesses = witness_multiple.compute_individual_witnesses(&["b", "c"]); 543 | for (elem, witness) in witnesses { 544 | assert_eq!(acc.value, G::exp(&witness.0.value, &hash_to_prime(elem))); 545 | } 546 | } 547 | 548 | #[test] 549 | fn test_compute_individual_witnesses_rsa2048() { 550 | // Class version takes too long for a unit test. 551 | test_compute_individual_witnesses::(); 552 | } 553 | } 554 | -------------------------------------------------------------------------------- /src/group/class.rs: -------------------------------------------------------------------------------- 1 | //! Fixed-discriminant implementation of an ideal class group, with future optimizations. 2 | //! 3 | //! Using a class group instead of an RSA group for accumulators or vector commitments eliminates 4 | //! the need for a trusted setup, albeit at the expense of slower operations. 5 | use super::{ElemFrom, Group, UnknownOrderGroup}; 6 | use crate::util; 7 | use crate::util::{int, TypeRep}; 8 | use rug::{Assign, Integer}; 9 | use std::hash::{Hash, Hasher}; 10 | use std::str::FromStr; 11 | 12 | #[allow(clippy::module_name_repetitions)] 13 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 14 | /// Class group implementation, with future optimizations available via the `--features` flag. 15 | /// Discriminant generated via OpenSSL. 16 | pub enum ClassGroup {} 17 | 18 | // 2048-bit prime, negated, congruent to `3 mod 4`. Generated using OpenSSL. 19 | // According to "A Survey of IQ Cryptography" (Buchmann & Hamdy) Table 1, IQ-MPQS for computing 20 | // discrete logarithms in class groups with a 2048-bit discriminant is comparable in complexity to 21 | // GNFS for factoring a 4096-bit integer. 22 | const DISCRIMINANT2048_DECIMAL: &str = 23 | "-30616069034807523947093657516320815215492876376165067902716988657802400037331914448218251590830\ 24 | 1102189519215849430413184776658192481976276720778009261808832630304841711366872161223643645001916\ 25 | 6969493423497224870506311710491233557329479816457723381368788734079933165653042145718668727765268\ 26 | 0575673207678516369650123480826989387975548598309959486361425021860161020248607833276306314923730\ 27 | 9854570972702350567411779734372573754840570138310317754359137013512655926325773048926718050691092\ 28 | 9453371727344087286361426404588335160385998280988603297435639020911295652025967761702701701471162\ 29 | 3966286152805654229445219531956098223"; 30 | 31 | lazy_static! { 32 | pub static ref CLASS_GROUP_DISCRIMINANT: Integer = 33 | Integer::from_str(DISCRIMINANT2048_DECIMAL).unwrap(); 34 | } 35 | 36 | #[allow(clippy::module_name_repetitions)] 37 | #[derive(Clone, Debug, Eq)] 38 | /// A class group element, which wraps three GMP integers from the `rug` crate. You should never 39 | /// need to construct a class group element yourself. 40 | pub struct ClassElem { 41 | a: Integer, 42 | b: Integer, 43 | c: Integer, 44 | } 45 | 46 | // `ClassElem` and `ClassGroup` ops based on Chia's fantastic doc explaining applied class groups: 47 | // https://github.com/Chia-Network/vdf-competition/blob/master/classgroups.pdf. 48 | impl ClassGroup { 49 | /// This method is only public for benchmarking. You should not need to use it. 50 | pub fn normalize(a: Integer, b: Integer, c: Integer) -> (Integer, Integer, Integer) { 51 | if Self::is_normal(&a, &b, &c) { 52 | return (a, b, c); 53 | } 54 | // r = floor_div((a - b), 2a) 55 | // (a, b, c) = (a, b + 2ra, ar^2 + br + c) 56 | let (r, _) = int(&a - &b).div_rem_floor(int(2 * &a)); 57 | let new_b = &b + 2 * int(&r * &a); 58 | let new_c = c + b * &r + &a * r.square(); 59 | (a, new_b, new_c) 60 | } 61 | 62 | /// This method is only public for benchmarking. You should not need to use it. 63 | // Note: Does not return a `ClassElem` because the output is not guaranteed to be 64 | // a valid `ClassElem` for all inputs. 65 | pub fn reduce(mut a: Integer, mut b: Integer, mut c: Integer) -> (Integer, Integer, Integer) { 66 | while !Self::is_reduced(&a, &b, &c) { 67 | // s = floor_div(c + b, 2c) 68 | let (s, _) = int(&c + &b).div_rem_floor(int(2 * &c)); 69 | 70 | // (a, b, c) = (c, −b + 2sc, cs^2 − bs + a) 71 | let old_a = a.clone(); 72 | let old_b = b.clone(); 73 | a = c.clone(); 74 | b = -b + 2 * int(&s * &c); 75 | c = -int(&old_b * &s) + old_a + c * s.square(); 76 | } 77 | Self::normalize(a, b, c) 78 | } 79 | 80 | #[allow(non_snake_case)] 81 | /// This method is only public for benchmarking. You should not need to use it. 82 | pub fn square(x: &ClassElem) -> ClassElem { 83 | // Solve `bk = c mod a` for `k`, represented by `mu`, `v` and any integer `n` s.t. 84 | // `k = mu + v * n`. 85 | let (mu, _) = util::solve_linear_congruence(&x.b, &x.c, &x.a).unwrap(); 86 | 87 | // A = a^2 88 | // B = b - 2a * mu 89 | // tmp = (b * mu) / a 90 | // C = mu^2 - tmp 91 | let a = int(x.a.square_ref()); 92 | let b = &x.b - int(2 * &x.a) * μ 93 | let (tmp, _) = <(Integer, Integer)>::from(int((&x.b * &mu) - &x.c).div_rem_floor_ref(&x.a)); 94 | let c = mu.square() - tmp; 95 | 96 | Self::elem((a, b, c)) 97 | } 98 | 99 | fn discriminant(a: &Integer, b: &Integer, c: &Integer) -> Integer { 100 | int(b.square_ref()) - int(4) * a * c 101 | } 102 | 103 | fn validate(a: &Integer, b: &Integer, c: &Integer) -> bool { 104 | Self::discriminant(a, b, c) == *Self::rep() 105 | } 106 | 107 | fn is_reduced(a: &Integer, b: &Integer, c: &Integer) -> bool { 108 | Self::is_normal(a, b, c) && (a <= c && !(a == c && *b < int(0))) 109 | } 110 | 111 | fn is_normal(a: &Integer, b: &Integer, _c: &Integer) -> bool { 112 | -int(a) < int(b) && b <= a 113 | } 114 | } 115 | 116 | impl TypeRep for ClassGroup { 117 | type Rep = Integer; 118 | fn rep() -> &'static Self::Rep { 119 | &CLASS_GROUP_DISCRIMINANT 120 | } 121 | } 122 | 123 | impl Group for ClassGroup { 124 | type Elem = ClassElem; 125 | 126 | #[allow(non_snake_case)] 127 | fn op_(_: &Integer, x: &ClassElem, y: &ClassElem) -> ClassElem { 128 | // g = (b1 + b2) / 2 129 | // h = (b2 - b1) / 2 130 | // w = gcd(a1, a2, g) 131 | let (g, _) = (int(&x.b) + &y.b).div_rem_floor(int(2)); 132 | let (h, _) = (&y.b - int(&x.b)).div_rem_floor(int(2)); 133 | let w = int(x.a.gcd_ref(&y.a)).gcd(&g); 134 | 135 | // j = w 136 | // s = a1 / w 137 | // t = a2 / w 138 | // u = g / ww 139 | // r = 0 140 | let j = int(&w); 141 | let (s, _) = <(Integer, Integer)>::from(x.a.div_rem_floor_ref(&w)); 142 | let (t, _) = <(Integer, Integer)>::from(y.a.div_rem_floor_ref(&w)); 143 | let (u, _) = g.div_rem_floor(w); 144 | 145 | // a = tu 146 | // b = hu + sc 147 | // m = st 148 | // Solve linear congruence `(tu)k = hu + sc mod st` or `ak = b mod m` for solutions `k`. 149 | let a = int(&t * &u); 150 | let b = int(&h * &u) + (&s * &x.c); 151 | let mut m = int(&s * &t); 152 | let (mu, v) = util::solve_linear_congruence(&a, &b, &m).unwrap(); 153 | 154 | // a = tv 155 | // b = h - t * mu 156 | // m = s 157 | // Solve linear congruence `(tv)k = h - t * mu mod s` or `ak = b mod m` for solutions `k`. 158 | let a = int(&t * &v); 159 | let b = &h - int(&t * &mu); 160 | m.assign(&s); 161 | let (lambda, _) = util::solve_linear_congruence(&a, &b, &m).unwrap(); 162 | 163 | // k = mu + v * lambda 164 | // l = (k * t - h) / s 165 | // m = (tuk - hu - cs) / st 166 | let k = &mu + int(&v * &lambda); 167 | let (l, _) = <(Integer, Integer)>::from((int(&k * &t) - &h).div_rem_floor_ref(&s)); 168 | let (m, _) = (int(&t * &u) * &k - &h * &u - &x.c * &s).div_rem_floor(int(&s * &t)); 169 | 170 | // A = st 171 | // B = ju - kt + ls 172 | // C = kl - jm 173 | let a = int(&s * &t); 174 | let b = int(&j * &u) - (int(&k * &t) + int(&l * &s)); 175 | let c = int(&k * &l) - int(&j * &m); 176 | Self::elem((a, b, c)) 177 | } 178 | 179 | // Constructs the reduced element directly instead of using `Self::Elem()`. 180 | fn id_(d: &Integer) -> ClassElem { 181 | let a = int(1); 182 | let b = int(1); 183 | 184 | // c = (b * b - d) / 4a 185 | let (c, _) = int(1 - d).div_rem_floor(int(4)); 186 | ClassElem { a, b, c } 187 | } 188 | 189 | // Constructs the inverse directly instead of using `Self::Elem()`. 190 | fn inv_(_: &Integer, x: &ClassElem) -> ClassElem { 191 | ClassElem { 192 | a: int(&x.a), 193 | b: int(-(&x.b)), 194 | c: int(&x.c), 195 | } 196 | } 197 | 198 | fn exp_(_: &Integer, a: &ClassElem, n: &Integer) -> ClassElem { 199 | let (mut val, mut a, mut n) = { 200 | if *n < int(0) { 201 | (Self::id(), Self::inv(a), int(-n)) 202 | } else { 203 | (Self::id(), a.clone(), n.clone()) 204 | } 205 | }; 206 | loop { 207 | if n == int(0) { 208 | return val; 209 | } 210 | if n.is_odd() { 211 | val = Self::op(&val, &a); 212 | } 213 | a = Self::square(&a); 214 | n >>= 1; 215 | } 216 | } 217 | } 218 | 219 | impl UnknownOrderGroup for ClassGroup { 220 | fn unknown_order_elem_(d: &Integer) -> ClassElem { 221 | // a = 2 222 | // b = 1 223 | // c = (b * b - d) / 4a 224 | let a = int(2); 225 | let b = int(1); 226 | let c = int(1 - d) / int(8); 227 | ClassElem { a, b, c } 228 | } 229 | } 230 | 231 | impl Hash for ClassElem { 232 | // Assumes `ClassElem` is reduced and normalized, which will be the case unless a struct is 233 | // instantiated manually in this module. 234 | fn hash(&self, state: &mut H) { 235 | self.a.hash(state); 236 | self.b.hash(state); 237 | self.c.hash(state); 238 | } 239 | } 240 | 241 | impl PartialEq for ClassElem { 242 | fn eq(&self, other: &Self) -> bool { 243 | self.a == other.a && self.b == other.b && self.c == other.c 244 | } 245 | } 246 | 247 | /// Panics if `(a, b, c)` cannot be reduced to a valid class element. 248 | impl ElemFrom<(A, B, C)> for ClassGroup 249 | where 250 | Integer: From, 251 | Integer: From, 252 | Integer: From, 253 | { 254 | fn elem(abc: (A, B, C)) -> ClassElem { 255 | let (a, b, c) = Self::reduce(int(abc.0), int(abc.1), int(abc.2)); 256 | 257 | // Ideally, this should return an error and the return type of `ElemFrom` should be 258 | // `Result`, but this would require a lot of ugly `unwrap`s in the 259 | // accumulator library. Besides, users should not need to create new class group elements, so 260 | // an invalid `ElemFrom` here should signal a severe internal error. 261 | assert!(Self::validate(&a, &b, &c)); 262 | 263 | ClassElem { a, b, c } 264 | } 265 | } 266 | 267 | // Caveat: Tests that use "ground truth" use outputs from Chia's sample implementation in python: 268 | // https://github.com/Chia-Network/vdf-competition/blob/master/inkfish/classgroup.py. 269 | #[cfg(test)] 270 | mod tests { 271 | use super::*; 272 | use std::collections::hash_map::DefaultHasher; 273 | 274 | // Makes a class elem tuple but does not reduce. 275 | fn construct_raw_elem_from_strings(a: &str, b: &str, c: &str) -> ClassElem { 276 | ClassElem { 277 | a: Integer::from_str(a).unwrap(), 278 | b: Integer::from_str(b).unwrap(), 279 | c: Integer::from_str(c).unwrap(), 280 | } 281 | } 282 | 283 | #[should_panic] 284 | #[test] 285 | fn test_bad_elem() { 286 | let _ = ClassGroup::elem((1, 2, 3)); 287 | } 288 | 289 | #[test] 290 | fn test_elem_from() { 291 | let a1 = Integer::from_str("16").unwrap(); 292 | let b1 = Integer::from_str("105").unwrap(); 293 | let c1 = Integer::from_str( 294 | "47837607866886756167333839869251273774207619337757918597995294777816250058331116325341018110\ 295 | 672047217112377476473502060121352842575308793237621563947157630098485131517401073775191194319\ 296 | 531549483898334742144138601661120476425524333273122132151927833887323969998955713328783526854\ 297 | 198871332313399489386997681827578317938792170918711794684859311697439726596656501594138449739\ 298 | 494228617068329664776714484742276158090583495714649193839084110987149118615158361352488488402\ 299 | 038894799695420483272708933239751363849397287571692736881031223140446926522431859701738994562\ 300 | 9057462766047140854869124473221137588347335081555186814207", 301 | ) 302 | .unwrap(); 303 | 304 | let a2 = Integer::from_str("16").unwrap(); 305 | let b2 = Integer::from_str("9").unwrap(); 306 | let c2 = Integer::from_str( 307 | "47837607866886756167333839869251273774207619337757918597995294777816250058331116325341018110\ 308 | 672047217112377476473502060121352842575308793237621563947157630098485131517401073775191194319\ 309 | 531549483898334742144138601661120476425524333273122132151927833887323969998955713328783526854\ 310 | 198871332313399489386997681827578317938792170918711794684859311697439726596656501594138449739\ 311 | 494228617068329664776714484742276158090583495714649193839084110987149118615158361352488488402\ 312 | 038894799695420483272708933239751363849397287571692736881031223140446926522431859701738994562\ 313 | 9057462766047140854869124473221137588347335081555186814036", 314 | ) 315 | .unwrap(); 316 | 317 | let reduced_elem = ClassGroup::elem((a1, b1, c1)); 318 | let also_reduced_elem = ClassGroup::elem((a2, b2, c2)); 319 | assert_eq!(reduced_elem, also_reduced_elem); 320 | } 321 | 322 | #[test] 323 | fn test_equality() { 324 | let not_reduced = construct_raw_elem_from_strings( 325 | "16", 326 | "105", 327 | "47837607866886756167333839869251273774207619337757918597995294777816250058331116325341018110\ 328 | 672047217112377476473502060121352842575308793237621563947157630098485131517401073775191194319\ 329 | 531549483898334742144138601661120476425524333273122132151927833887323969998955713328783526854\ 330 | 198871332313399489386997681827578317938792170918711794684859311697439726596656501594138449739\ 331 | 494228617068329664776714484742276158090583495714649193839084110987149118615158361352488488402\ 332 | 038894799695420483272708933239751363849397287571692736881031223140446926522431859701738994562\ 333 | 9057462766047140854869124473221137588347335081555186814207" 334 | ); 335 | 336 | let reduced_ground_truth = construct_raw_elem_from_strings( 337 | "16", 338 | "9", 339 | "47837607866886756167333839869251273774207619337757918597995294777816250058331116325341018110\ 340 | 672047217112377476473502060121352842575308793237621563947157630098485131517401073775191194319\ 341 | 531549483898334742144138601661120476425524333273122132151927833887323969998955713328783526854\ 342 | 198871332313399489386997681827578317938792170918711794684859311697439726596656501594138449739\ 343 | 494228617068329664776714484742276158090583495714649193839084110987149118615158361352488488402\ 344 | 038894799695420483272708933239751363849397287571692736881031223140446926522431859701738994562\ 345 | 9057462766047140854869124473221137588347335081555186814036" 346 | ); 347 | 348 | let diff_elem = construct_raw_elem_from_strings( 349 | "4", 350 | "1", 351 | "19135043146754702466933535947700509509683047735103167439198117911126500023332446530136407244\ 352 | 268818886844950990589400824048541137030123517295048625578863052039394052606960429510076477727\ 353 | 812619793559333896857655440664448190570209733309248852860771133554929587999582285331513410741\ 354 | 679548532925359795754799072731031327175516868367484717873943724678975890638662600637655379895\ 355 | 797691446827331865910685793896910463236233398285859677535633644394859647446063344540995395360\ 356 | 815557919878168193309083573295900545539758915028677094752412489256178770608972743880695597825\ 357 | 16229851064188563419476497892884550353389340326220747256139" 358 | ); 359 | 360 | assert!(not_reduced != reduced_ground_truth); 361 | assert!(not_reduced == not_reduced.clone()); 362 | assert!(reduced_ground_truth == reduced_ground_truth.clone()); 363 | assert!(not_reduced != diff_elem); 364 | assert!(reduced_ground_truth != diff_elem); 365 | 366 | let reduced = ClassGroup::elem((not_reduced.a, not_reduced.b, not_reduced.c)); 367 | assert!(reduced == reduced_ground_truth); 368 | } 369 | 370 | #[test] 371 | fn test_hash() { 372 | let not_reduced = construct_raw_elem_from_strings( 373 | "16", 374 | "105", 375 | "47837607866886756167333839869251273774207619337757918597995294777816250058331116325341018110\ 376 | 672047217112377476473502060121352842575308793237621563947157630098485131517401073775191194319\ 377 | 531549483898334742144138601661120476425524333273122132151927833887323969998955713328783526854\ 378 | 198871332313399489386997681827578317938792170918711794684859311697439726596656501594138449739\ 379 | 494228617068329664776714484742276158090583495714649193839084110987149118615158361352488488402\ 380 | 038894799695420483272708933239751363849397287571692736881031223140446926522431859701738994562\ 381 | 9057462766047140854869124473221137588347335081555186814207" 382 | ); 383 | 384 | let reduced_ground_truth = construct_raw_elem_from_strings( 385 | "16", 386 | "9", 387 | "47837607866886756167333839869251273774207619337757918597995294777816250058331116325341018110\ 388 | 672047217112377476473502060121352842575308793237621563947157630098485131517401073775191194319\ 389 | 531549483898334742144138601661120476425524333273122132151927833887323969998955713328783526854\ 390 | 198871332313399489386997681827578317938792170918711794684859311697439726596656501594138449739\ 391 | 494228617068329664776714484742276158090583495714649193839084110987149118615158361352488488402\ 392 | 038894799695420483272708933239751363849397287571692736881031223140446926522431859701738994562\ 393 | 9057462766047140854869124473221137588347335081555186814036" 394 | ); 395 | 396 | let diff_elem = construct_raw_elem_from_strings( 397 | "4", 398 | "1", 399 | "19135043146754702466933535947700509509683047735103167439198117911126500023332446530136407244\ 400 | 268818886844950990589400824048541137030123517295048625578863052039394052606960429510076477727\ 401 | 812619793559333896857655440664448190570209733309248852860771133554929587999582285331513410741\ 402 | 679548532925359795754799072731031327175516868367484717873943724678975890638662600637655379895\ 403 | 797691446827331865910685793896910463236233398285859677535633644394859647446063344540995395360\ 404 | 815557919878168193309083573295900545539758915028677094752412489256178770608972743880695597825\ 405 | 16229851064188563419476497892884550353389340326220747256139" 406 | ); 407 | 408 | let mut hasher_lh = DefaultHasher::new(); 409 | let mut hasher_rh = DefaultHasher::new(); 410 | not_reduced.hash(&mut hasher_lh); 411 | reduced_ground_truth.hash(&mut hasher_rh); 412 | 413 | assert!(hasher_lh.finish() != hasher_rh.finish()); 414 | assert!(hasher_lh.finish() == hasher_lh.finish()); 415 | assert!(hasher_rh.finish() == hasher_rh.finish()); 416 | 417 | hasher_lh = DefaultHasher::new(); 418 | hasher_rh = DefaultHasher::new(); 419 | let reduced = ClassGroup::elem((not_reduced.a, not_reduced.b, not_reduced.c)); 420 | reduced.hash(&mut hasher_lh); 421 | reduced_ground_truth.hash(&mut hasher_rh); 422 | assert!(hasher_lh.finish() == hasher_rh.finish()); 423 | 424 | hasher_lh = DefaultHasher::new(); 425 | hasher_rh = DefaultHasher::new(); 426 | reduced.hash(&mut hasher_lh); 427 | diff_elem.hash(&mut hasher_rh); 428 | assert!(hasher_lh.finish() != hasher_rh.finish()); 429 | } 430 | 431 | #[test] 432 | fn test_reduce_basic() { 433 | // Unreduced element. 434 | let to_reduce = construct_raw_elem_from_strings( 435 | "59162244921619725812008939143220718157267937427074598447911241410131470159247784852210767449\ 436 | 675610037288729551814191198624164179866076352187405442496568188988272422133088755036699145362\ 437 | 385840772236403043664778415471196678638241785773530531198720497580622741709880533724904220122\ 438 | 358854068046553219863419609777498761804625479650772123754523807001976654588225908928022367436\ 439 | 8", 440 | "18760351095004839755193532164856605650590306627169248964100884295652838905828158941233738613\ 441 | 175821849253748329102319504958410190952820220503570113920576542676928659211807590199941027958\ 442 | 195895385446372444261885022800653454209101497963588809819572703579484085278913354621371362285\ 443 | 341138299691587953249270188429393417132110841259813122945626515477865766896056280729710478647\ 444 | 13", 445 | "14872270891432803054791175727694631095755964943358394411314110783404577714102170379700365256\ 446 | 599679049493824862742803590079461712691146098397470840896560034332315858221821103076776907123\ 447 | 277315116632337385101204055232891361405428635972040596205450316747012080794838691280547894128\ 448 | 246741601088755087359234554141346980837292342320288111397175220296098629890108459305643419353\ 449 | 36" 450 | ); 451 | 452 | let reduced_ground_truth = construct_raw_elem_from_strings( 453 | "26888935961824081232597112540509824504614070059776273347136888921115497522070287009841688662\ 454 | 983066376019079593372296556420848446780369918809384119124783870290778875424468497961559643807\ 455 | 918398860928578027038014112641529893817109240852544158309292025321122680747989987560029531021\ 456 | 808743313150630063377037854944", 457 | "14529985196481999393995154363327100184407232892559561136140792409262328867440167480822808496\ 458 | 853924547751298342980606034124112579835255733824790020119078588372593288210628255956605240171\ 459 | 744703418426092073347584357826862813733154338737148962212641444735717023402201569115323580814\ 460 | 54099903972209626147819759991", 461 | "28467266502267127591420289007165819749231433586093061478772560429058231137856046130384492811\ 462 | 816456933286039468940950129263300933723839212086399375780796041634531383342902918719073416087\ 463 | 614456845205980227091403964285870107268917183244016635907926846271829374679124848388403486656\ 464 | 1564478239095738726823372184204" 465 | ); 466 | 467 | let (a, b, c) = ClassGroup::reduce(to_reduce.a, to_reduce.b, to_reduce.c); 468 | assert_eq!(ClassElem { a, b, c }, reduced_ground_truth.clone()); 469 | 470 | let reduced_ground_truth_ = reduced_ground_truth.clone(); 471 | let (a, b, c) = ClassGroup::reduce( 472 | reduced_ground_truth_.a, 473 | reduced_ground_truth_.b, 474 | reduced_ground_truth_.c, 475 | ); 476 | assert_eq!(ClassElem { a, b, c }, reduced_ground_truth); 477 | } 478 | 479 | #[test] 480 | // REVIEW: This test should be restructured to not construct `ClassElem`s but it will do for now. 481 | fn test_normalize_basic() { 482 | let unnormalized = construct_raw_elem_from_strings( 483 | "16", 484 | "105", 485 | "47837607866886756167333839869251273774207619337757918597995294777816250058331116325341018110\ 486 | 672047217112377476473502060121352842575308793237621563947157630098485131517401073775191194319\ 487 | 531549483898334742144138601661120476425524333273122132151927833887323969998955713328783526854\ 488 | 198871332313399489386997681827578317938792170918711794684859311697439726596656501594138449739\ 489 | 494228617068329664776714484742276158090583495714649193839084110987149118615158361352488488402\ 490 | 038894799695420483272708933239751363849397287571692736881031223140446926522431859701738994562\ 491 | 9057462766047140854869124473221137588347335081555186814207", 492 | 493 | ); 494 | 495 | let normalized_ground_truth = construct_raw_elem_from_strings( 496 | "16", 497 | "9", 498 | "4783760786688675616733383986925127377420761933775791859799529477781625005833111632534101811\ 499 | 06720472171123774764735020601213528425753087932376215639471576300984851315174010737751911943\ 500 | 19531549483898334742144138601661120476425524333273122132151927833887323969998955713328783526\ 501 | 85419887133231339948938699768182757831793879217091871179468485931169743972659665650159413844\ 502 | 97394942286170683296647767144847422761580905834957146491938390841109871491186151583613524884\ 503 | 88402038894799695420483272708933239751363849397287571692736881031223140446926522431859701738\ 504 | 9945629057462766047140854869124473221137588347335081555186814036", 505 | ); 506 | 507 | let (a, b, c) = ClassGroup::normalize(unnormalized.a, unnormalized.b, unnormalized.c); 508 | assert_eq!(normalized_ground_truth, ClassElem { a, b, c }); 509 | } 510 | 511 | #[test] 512 | // REVIEW: This test should be rewritten, because it may be broken by `unknown_order_elem` not 513 | // working correctly. 514 | fn test_discriminant_basic() { 515 | let g = ClassGroup::unknown_order_elem(); 516 | assert_eq!( 517 | ClassGroup::discriminant(&g.a, &g.b, &g.c), 518 | *ClassGroup::rep() 519 | ); 520 | } 521 | 522 | #[test] 523 | // REVIEW: This test should be rewritten. See review for `test_discriminant_basic`. 524 | fn test_discriminant_across_ops() { 525 | let id = ClassGroup::id(); 526 | let g1 = ClassGroup::unknown_order_elem(); 527 | let g2 = ClassGroup::op(&g1, &g1); 528 | let g3 = ClassGroup::op(&id, &g2); 529 | let g3_inv = ClassGroup::inv(&g3); 530 | 531 | assert!(ClassGroup::validate(&id.a, &id.b, &id.c)); 532 | assert!(ClassGroup::validate(&g1.a, &g1.b, &g1.c)); 533 | assert!(ClassGroup::validate(&g2.a, &g2.b, &g2.c)); 534 | assert!(ClassGroup::validate(&g3.a, &g3.b, &g3.c)); 535 | assert!(ClassGroup::validate(&g3_inv.a, &g3_inv.b, &g3_inv.c)); 536 | } 537 | 538 | #[test] 539 | fn test_op_single() { 540 | let a = construct_raw_elem_from_strings( 541 | "4", 542 | "1", 543 | "19135043146754702466933535947700509509683047735103167439198117911126500023332446530136407244\ 544 | 268818886844950990589400824048541137030123517295048625578863052039394052606960429510076477727\ 545 | 812619793559333896857655440664448190570209733309248852860771133554929587999582285331513410741\ 546 | 679548532925359795754799072731031327175516868367484717873943724678975890638662600637655379895\ 547 | 797691446827331865910685793896910463236233398285859677535633644394859647446063344540995395360\ 548 | 815557919878168193309083573295900545539758915028677094752412489256178770608972743880695597825\ 549 | 16229851064188563419476497892884550353389340326220747256139" 550 | ); 551 | 552 | let b = construct_raw_elem_from_strings( 553 | "16", 554 | "41", 555 | "47837607866886756167333839869251273774207619337757918597995294777816250058331116325341018110\ 556 | 672047217112377476473502060121352842575308793237621563947157630098485131517401073775191194319\ 557 | 531549483898334742144138601661120476425524333273122132151927833887323969998955713328783526854\ 558 | 198871332313399489386997681827578317938792170918711794684859311697439726596656501594138449739\ 559 | 494228617068329664776714484742276158090583495714649193839084110987149118615158361352488488402\ 560 | 038894799695420483272708933239751363849397287571692736881031223140446926522431859701738994562\ 561 | 9057462766047140854869124473221137588347335081555186814061" 562 | ); 563 | 564 | let ground_truth = construct_raw_elem_from_strings( 565 | "64", 566 | "9", 567 | "11959401966721689041833459967312818443551904834439479649498823694454062514582779081335254527\ 568 | 668011804278094369118375515030338210643827198309405390986789407524621282879350268443797798579\ 569 | 882887370974583685536034650415280119106381083318280533037981958471830992499738928332195881713\ 570 | 549717833078349872346749420456894579484698042729677948671214827924359931649164125398534612434\ 571 | 873557154267082416194178621185569039522645873928662298459771027746787279653789590338122122100\ 572 | 50972369992385512081817723330993784096234932189292318422025780578511173163060796492543474864\ 573 | 07264365691511785213717281118305284397086833770388796703509" 574 | ); 575 | 576 | assert_eq!(ClassGroup::op(&a, &b), ground_truth); 577 | } 578 | 579 | #[test] 580 | fn test_op_alternating() { 581 | let g_anchor = ClassGroup::unknown_order_elem(); 582 | let mut g = ClassGroup::id(); 583 | let mut g_star = ClassGroup::id(); 584 | 585 | // g 586 | g = ClassGroup::op(&g_anchor, &g); 587 | 588 | // g^2, g^* = g^2 589 | g = ClassGroup::op(&g_anchor, &g); 590 | g_star = ClassGroup::op(&g, &g_star); 591 | 592 | // g^3 593 | g = ClassGroup::op(&g_anchor, &g); 594 | 595 | // g^4, g^* = g^2 * g^4 = g^6 596 | g = ClassGroup::op(&g_anchor, &g); 597 | g_star = ClassGroup::op(&g, &g_star); 598 | 599 | let ground_truth = construct_raw_elem_from_strings( 600 | "64", 601 | "9", 602 | "11959401966721689041833459967312818443551904834439479649498823694454062514582779081335254527\ 603 | 668011804278094369118375515030338210643827198309405390986789407524621282879350268443797798579\ 604 | 882887370974583685536034650415280119106381083318280533037981958471830992499738928332195881713\ 605 | 549717833078349872346749420456894579484698042729677948671214827924359931649164125398534612434\ 606 | 873557154267082416194178621185569039522645873928662298459771027746787279653789590338122122100\ 607 | 509723699923855120818177233309937840962349321892923184220257805785111731630607964925434748640\ 608 | 7264365691511785213717281118305284397086833770388796703509" 609 | ); 610 | 611 | assert_eq!(ground_truth, g_star); 612 | } 613 | 614 | #[test] 615 | fn test_op_complex() { 616 | // 1. Take g^100, g^200, ..., g^1000. 617 | // 2. Compute g^* = g^100 * ... * g^1000. 618 | // 3. For each of g^100, g^200, ..., g^1000 compute the inverse of that element and assert that 619 | // g^* * current_inverse = product of g^100, g^200, ..., g^1000 without the inversed-out 620 | // element. 621 | let g_anchor = ClassGroup::unknown_order_elem(); 622 | let mut g = ClassGroup::id(); 623 | 624 | let mut gs = vec![]; 625 | let mut gs_invs = vec![]; 626 | 627 | let mut g_star = ClassGroup::id(); 628 | for i in 1..=1000 { 629 | g = ClassGroup::op(&g_anchor, &g); 630 | assert!(ClassGroup::validate(&g.a, &g.b, &g.c)); 631 | if i % 100 == 0 { 632 | gs.push(g.clone()); 633 | gs_invs.push(ClassGroup::inv(&g)); 634 | g_star = ClassGroup::op(&g, &g_star); 635 | assert!(ClassGroup::validate(&g_star.a, &g_star.b, &g_star.c)); 636 | } 637 | } 638 | 639 | let elems_n_invs = gs.iter().zip(gs_invs.iter()); 640 | for (g_elem, g_inv) in elems_n_invs { 641 | assert!(ClassGroup::validate(&g_elem.a, &g_elem.b, &g_elem.c)); 642 | assert!(ClassGroup::validate(&g_inv.a, &g_inv.b, &g_inv.c)); 643 | let mut curr_prod = ClassGroup::id(); 644 | for elem in &gs { 645 | if elem != g_elem { 646 | curr_prod = ClassGroup::op(&curr_prod, &elem); 647 | assert!(ClassGroup::validate( 648 | &curr_prod.a, 649 | &curr_prod.b, 650 | &curr_prod.c 651 | )); 652 | } 653 | } 654 | assert_eq!(ClassGroup::id(), ClassGroup::op(&g_inv, &g_elem)); 655 | assert_eq!(curr_prod, ClassGroup::op(&g_inv, &g_star)); 656 | } 657 | } 658 | 659 | #[test] 660 | fn test_id_basic() { 661 | let g = ClassGroup::unknown_order_elem(); 662 | let id = ClassGroup::id(); 663 | assert_eq!(g, ClassGroup::op(&g, &id)); 664 | assert_eq!(g, ClassGroup::op(&id, &g)); 665 | assert_eq!(id, ClassGroup::op(&id, &id)); 666 | } 667 | 668 | #[test] 669 | fn test_id_repeated() { 670 | let mut id = ClassGroup::id(); 671 | let g_anchor = ClassGroup::unknown_order_elem(); 672 | let mut g = ClassGroup::unknown_order_elem(); 673 | for _ in 0..1000 { 674 | id = ClassGroup::op(&id, &id); 675 | assert_eq!(id, ClassGroup::id()); 676 | g = ClassGroup::op(&g, &ClassGroup::id()); 677 | assert_eq!(g, g_anchor); 678 | } 679 | } 680 | 681 | #[test] 682 | fn test_inv() { 683 | let id = ClassGroup::id(); 684 | let g_anchor = ClassGroup::unknown_order_elem(); 685 | let mut g = ClassGroup::unknown_order_elem(); 686 | 687 | for _ in 0..1000 { 688 | g = ClassGroup::op(&g, &g_anchor); 689 | let g_inv = ClassGroup::inv(&g); 690 | assert_eq!(id, ClassGroup::op(&g_inv, &g)); 691 | assert_eq!(id, ClassGroup::op(&g, &g_inv)); 692 | assert_eq!(g, ClassGroup::inv(&g_inv)); 693 | } 694 | } 695 | 696 | #[test] 697 | fn test_exp_basic() { 698 | let g_anchor = ClassGroup::unknown_order_elem(); 699 | let mut g = ClassGroup::id(); 700 | 701 | for i in 1..=1000 { 702 | g = ClassGroup::op(&g, &g_anchor); 703 | assert_eq!(&g, &ClassGroup::exp(&g_anchor, &int(i))); 704 | } 705 | } 706 | 707 | #[test] 708 | fn test_square_basic() { 709 | let g = ClassGroup::unknown_order_elem(); 710 | let mut g4 = ClassGroup::id(); 711 | 712 | // g^4 713 | for _ in 0..4 { 714 | g4 = ClassGroup::op(&g, &g4); 715 | } 716 | 717 | // g^2 718 | let mut g2 = ClassGroup::op(&g, &g); 719 | 720 | // g^4 721 | g2 = ClassGroup::square(&g2); 722 | 723 | assert_eq!(&g2, &g4); 724 | } 725 | } 726 | -------------------------------------------------------------------------------- /flamegraph/flamegraph.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | # 3 | # flamegraph.pl flame stack grapher. 4 | # 5 | # This takes stack samples and renders a call graph, allowing hot functions 6 | # and codepaths to be quickly identified. Stack samples can be generated using 7 | # tools such as DTrace, perf, SystemTap, and Instruments. 8 | # 9 | # USAGE: ./flamegraph.pl [options] input.txt > graph.svg 10 | # 11 | # grep funcA input.txt | ./flamegraph.pl [options] > graph.svg 12 | # 13 | # Then open the resulting .svg in a web browser, for interactivity: mouse-over 14 | # frames for info, click to zoom, and ctrl-F to search. 15 | # 16 | # Options are listed in the usage message (--help). 17 | # 18 | # The input is stack frames and sample counts formatted as single lines. Each 19 | # frame in the stack is semicolon separated, with a space and count at the end 20 | # of the line. These can be generated for Linux perf script output using 21 | # stackcollapse-perf.pl, for DTrace using stackcollapse.pl, and for other tools 22 | # using the other stackcollapse programs. Example input: 23 | # 24 | # swapper;start_kernel;rest_init;cpu_idle;default_idle;native_safe_halt 1 25 | # 26 | # An optional extra column of counts can be provided to generate a differential 27 | # flame graph of the counts, colored red for more, and blue for less. This 28 | # can be useful when using flame graphs for non-regression testing. 29 | # See the header comment in the difffolded.pl program for instructions. 30 | # 31 | # The input functions can optionally have annotations at the end of each 32 | # function name, following a precedent by some tools (Linux perf's _[k]): 33 | # _[k] for kernel 34 | # _[i] for inlined 35 | # _[j] for jit 36 | # _[w] for waker 37 | # Some of the stackcollapse programs support adding these annotations, eg, 38 | # stackcollapse-perf.pl --kernel --jit. They are used merely for colors by 39 | # some palettes, eg, flamegraph.pl --color=java. 40 | # 41 | # The output flame graph shows relative presence of functions in stack samples. 42 | # The ordering on the x-axis has no meaning; since the data is samples, time 43 | # order of events is not known. The order used sorts function names 44 | # alphabetically. 45 | # 46 | # While intended to process stack samples, this can also process stack traces. 47 | # For example, tracing stacks for memory allocation, or resource usage. You 48 | # can use --title to set the title to reflect the content, and --countname 49 | # to change "samples" to "bytes" etc. 50 | # 51 | # There are a few different palettes, selectable using --color. By default, 52 | # the colors are selected at random (except for differentials). Functions 53 | # called "-" will be printed gray, which can be used for stack separators (eg, 54 | # between user and kernel stacks). 55 | # 56 | # HISTORY 57 | # 58 | # This was inspired by Neelakanth Nadgir's excellent function_call_graph.rb 59 | # program, which visualized function entry and return trace events. As Neel 60 | # wrote: "The output displayed is inspired by Roch's CallStackAnalyzer which 61 | # was in turn inspired by the work on vftrace by Jan Boerhout". See: 62 | # https://blogs.oracle.com/realneel/entry/visualizing_callstacks_via_dtrace_and 63 | # 64 | # Copyright 2016 Netflix, Inc. 65 | # Copyright 2011 Joyent, Inc. All rights reserved. 66 | # Copyright 2011 Brendan Gregg. All rights reserved. 67 | # 68 | # CDDL HEADER START 69 | # 70 | # The contents of this file are subject to the terms of the 71 | # Common Development and Distribution License (the "License"). 72 | # You may not use this file except in compliance with the License. 73 | # 74 | # You can obtain a copy of the license at docs/cddl1.txt or 75 | # http://opensource.org/licenses/CDDL-1.0. 76 | # See the License for the specific language governing permissions 77 | # and limitations under the License. 78 | # 79 | # When distributing Covered Code, include this CDDL HEADER in each 80 | # file and include the License file at docs/cddl1.txt. 81 | # If applicable, add the following below this CDDL HEADER, with the 82 | # fields enclosed by brackets "[]" replaced with your own identifying 83 | # information: Portions Copyright [yyyy] [name of copyright owner] 84 | # 85 | # CDDL HEADER END 86 | # 87 | # 11-Oct-2014 Adrien Mahieux Added zoom. 88 | # 21-Nov-2013 Shawn Sterling Added consistent palette file option 89 | # 17-Mar-2013 Tim Bunce Added options and more tunables. 90 | # 15-Dec-2011 Dave Pacheco Support for frames with whitespace. 91 | # 10-Sep-2011 Brendan Gregg Created this. 92 | 93 | use strict; 94 | 95 | use Getopt::Long; 96 | 97 | use open qw(:std :utf8); 98 | 99 | # tunables 100 | my $encoding; 101 | my $fonttype = "Verdana"; 102 | my $imagewidth = 1200; # max width, pixels 103 | my $frameheight = 16; # max height is dynamic 104 | my $fontsize = 12; # base text size 105 | my $fontwidth = 0.59; # avg width relative to fontsize 106 | my $minwidth = 0.1; # min function width, pixels 107 | my $nametype = "Function:"; # what are the names in the data? 108 | my $countname = "samples"; # what are the counts in the data? 109 | my $colors = "hot"; # color theme 110 | my $bgcolor1 = "#eeeeee"; # background color gradient start 111 | my $bgcolor2 = "#eeeeb0"; # background color gradient stop 112 | my $nameattrfile; # file holding function attributes 113 | my $timemax; # (override the) sum of the counts 114 | my $factor = 1; # factor to scale counts by 115 | my $hash = 0; # color by function name 116 | my $palette = 0; # if we use consistent palettes (default off) 117 | my %palette_map; # palette map hash 118 | my $pal_file = "palette.map"; # palette map file name 119 | my $stackreverse = 0; # reverse stack order, switching merge end 120 | my $inverted = 0; # icicle graph 121 | my $flamechart = 0; # produce a flame chart (sort by time, do not merge stacks) 122 | my $negate = 0; # switch differential hues 123 | my $titletext = ""; # centered heading 124 | my $titledefault = "Flame Graph"; # overwritten by --title 125 | my $titleinverted = "Icicle Graph"; # " " 126 | my $searchcolor = "rgb(230,0,230)"; # color for search highlighting 127 | my $notestext = ""; # embedded notes in SVG 128 | my $subtitletext = ""; # second level title (optional) 129 | my $help = 0; 130 | 131 | sub usage { 132 | die < outfile.svg\n 134 | --title TEXT # change title text 135 | --subtitle TEXT # second level title (optional) 136 | --width NUM # width of image (default 1200) 137 | --height NUM # height of each frame (default 16) 138 | --minwidth NUM # omit smaller functions (default 0.1 pixels) 139 | --fonttype FONT # font type (default "Verdana") 140 | --fontsize NUM # font size (default 12) 141 | --countname TEXT # count type label (default "samples") 142 | --nametype TEXT # name type label (default "Function:") 143 | --colors PALETTE # set color palette. choices are: hot (default), mem, 144 | # io, wakeup, chain, java, js, perl, red, green, blue, 145 | # aqua, yellow, purple, orange 146 | --hash # colors are keyed by function name hash 147 | --cp # use consistent palette (palette.map) 148 | --reverse # generate stack-reversed flame graph 149 | --inverted # icicle graph 150 | --flamechart # produce a flame chart (sort by time, do not merge stacks) 151 | --negate # switch differential hues (blue<->red) 152 | --notes TEXT # add notes comment in SVG (for debugging) 153 | --help # this message 154 | 155 | eg, 156 | $0 --title="Flame Graph: malloc()" trace.txt > graph.svg 157 | USAGE_END 158 | } 159 | 160 | GetOptions( 161 | 'fonttype=s' => \$fonttype, 162 | 'width=i' => \$imagewidth, 163 | 'height=i' => \$frameheight, 164 | 'encoding=s' => \$encoding, 165 | 'fontsize=f' => \$fontsize, 166 | 'fontwidth=f' => \$fontwidth, 167 | 'minwidth=f' => \$minwidth, 168 | 'title=s' => \$titletext, 169 | 'subtitle=s' => \$subtitletext, 170 | 'nametype=s' => \$nametype, 171 | 'countname=s' => \$countname, 172 | 'nameattr=s' => \$nameattrfile, 173 | 'total=s' => \$timemax, 174 | 'factor=f' => \$factor, 175 | 'colors=s' => \$colors, 176 | 'hash' => \$hash, 177 | 'cp' => \$palette, 178 | 'reverse' => \$stackreverse, 179 | 'inverted' => \$inverted, 180 | 'flamechart' => \$flamechart, 181 | 'negate' => \$negate, 182 | 'notes=s' => \$notestext, 183 | 'help' => \$help, 184 | ) or usage(); 185 | $help && usage(); 186 | 187 | # internals 188 | my $ypad1 = $fontsize * 3; # pad top, include title 189 | my $ypad2 = $fontsize * 2 + 10; # pad bottom, include labels 190 | my $ypad3 = $fontsize * 2; # pad top, include subtitle (optional) 191 | my $xpad = 10; # pad lefm and right 192 | my $framepad = 1; # vertical padding for frames 193 | my $depthmax = 0; 194 | my %Events; 195 | my %nameattr; 196 | 197 | if ($flamechart && $titletext eq "") { 198 | $titletext = "Flame Chart"; 199 | } 200 | 201 | if ($titletext eq "") { 202 | unless ($inverted) { 203 | $titletext = $titledefault; 204 | } else { 205 | $titletext = $titleinverted; 206 | } 207 | } 208 | 209 | if ($nameattrfile) { 210 | # The name-attribute file format is a function name followed by a tab then 211 | # a sequence of tab separated name=value pairs. 212 | open my $attrfh, $nameattrfile or die "Can't read $nameattrfile: $!\n"; 213 | while (<$attrfh>) { 214 | chomp; 215 | my ($funcname, $attrstr) = split /\t/, $_, 2; 216 | die "Invalid format in $nameattrfile" unless defined $attrstr; 217 | $nameattr{$funcname} = { map { split /=/, $_, 2 } split /\t/, $attrstr }; 218 | } 219 | } 220 | 221 | if ($notestext =~ /[<>]/) { 222 | die "Notes string can't contain < or >" 223 | } 224 | 225 | # background colors: 226 | # - yellow gradient: default (hot, java, js, perl) 227 | # - blue gradient: mem, chain 228 | # - gray gradient: io, wakeup, flat colors (red, green, blue, ...) 229 | if ($colors eq "mem" or $colors eq "chain") { 230 | $bgcolor1 = "#eeeeee"; $bgcolor2 = "#e0e0ff"; 231 | } 232 | if ($colors =~ /^(io|wakeup|red|green|blue|aqua|yellow|purple|orange)$/) { 233 | $bgcolor1 = "#f8f8f8"; $bgcolor2 = "#e8e8e8"; 234 | } 235 | 236 | # SVG functions 237 | { package SVG; 238 | sub new { 239 | my $class = shift; 240 | my $self = {}; 241 | bless ($self, $class); 242 | return $self; 243 | } 244 | 245 | sub header { 246 | my ($self, $w, $h) = @_; 247 | my $enc_attr = ''; 248 | if (defined $encoding) { 249 | $enc_attr = qq{ encoding="$encoding"}; 250 | } 251 | $self->{svg} .= < 253 | 254 | 255 | 256 | 257 | SVG 258 | } 259 | 260 | sub include { 261 | my ($self, $content) = @_; 262 | $self->{svg} .= $content; 263 | } 264 | 265 | sub colorAllocate { 266 | my ($self, $r, $g, $b) = @_; 267 | return "rgb($r,$g,$b)"; 268 | } 269 | 270 | sub group_start { 271 | my ($self, $attr) = @_; 272 | 273 | my @g_attr = map { 274 | exists $attr->{$_} ? sprintf(qq/$_="%s"/, $attr->{$_}) : () 275 | } qw(class style onmouseover onmouseout onclick); 276 | push @g_attr, $attr->{g_extra} if $attr->{g_extra}; 277 | $self->{svg} .= sprintf qq/\n/, join(' ', @g_attr); 278 | 279 | $self->{svg} .= sprintf qq/%s<\/title>/, $attr->{title} 280 | if $attr->{title}; # should be first element within g container 281 | 282 | if ($attr->{href}) { 283 | my @a_attr; 284 | push @a_attr, sprintf qq/xlink:href="%s"/, $attr->{href} if $attr->{href}; 285 | # default target=_top else links will open within SVG 286 | push @a_attr, sprintf qq/target="%s"/, $attr->{target} || "_top"; 287 | push @a_attr, $attr->{a_extra} if $attr->{a_extra}; 288 | $self->{svg} .= sprintf qq//, join(' ', @a_attr); 289 | } 290 | } 291 | 292 | sub group_end { 293 | my ($self, $attr) = @_; 294 | $self->{svg} .= qq/<\/a>\n/ if $attr->{href}; 295 | $self->{svg} .= qq/<\/g>\n/; 296 | } 297 | 298 | sub filledRectangle { 299 | my ($self, $x1, $y1, $x2, $y2, $fill, $extra) = @_; 300 | $x1 = sprintf "%0.1f", $x1; 301 | $x2 = sprintf "%0.1f", $x2; 302 | my $w = sprintf "%0.1f", $x2 - $x1; 303 | my $h = sprintf "%0.1f", $y2 - $y1; 304 | $extra = defined $extra ? $extra : ""; 305 | $self->{svg} .= qq/\n/; 306 | } 307 | 308 | sub stringTTF { 309 | my ($self, $color, $font, $size, $angle, $x, $y, $str, $loc, $extra) = @_; 310 | $x = sprintf "%0.2f", $x; 311 | $loc = defined $loc ? $loc : "left"; 312 | $extra = defined $extra ? $extra : ""; 313 | $self->{svg} .= qq/$str<\/text>\n/; 314 | } 315 | 316 | sub svg { 317 | my $self = shift; 318 | return "$self->{svg}\n"; 319 | } 320 | 1; 321 | } 322 | 323 | sub namehash { 324 | # Generate a vector hash for the name string, weighting early over 325 | # later characters. We want to pick the same colors for function 326 | # names across different flame graphs. 327 | my $name = shift; 328 | my $vector = 0; 329 | my $weight = 1; 330 | my $max = 1; 331 | my $mod = 10; 332 | # if module name present, trunc to 1st char 333 | $name =~ s/.(.*?)`//; 334 | foreach my $c (split //, $name) { 335 | my $i = (ord $c) % $mod; 336 | $vector += ($i / ($mod++ - 1)) * $weight; 337 | $max += 1 * $weight; 338 | $weight *= 0.70; 339 | last if $mod > 12; 340 | } 341 | return (1 - $vector / $max) 342 | } 343 | 344 | sub color { 345 | my ($type, $hash, $name) = @_; 346 | my ($v1, $v2, $v3); 347 | 348 | if ($hash) { 349 | $v1 = namehash($name); 350 | $v2 = $v3 = namehash(scalar reverse $name); 351 | } else { 352 | $v1 = rand(1); 353 | $v2 = rand(1); 354 | $v3 = rand(1); 355 | } 356 | 357 | # theme palettes 358 | if (defined $type and $type eq "hot") { 359 | my $r = 205 + int(50 * $v3); 360 | my $g = 0 + int(230 * $v1); 361 | my $b = 0 + int(55 * $v2); 362 | return "rgb($r,$g,$b)"; 363 | } 364 | if (defined $type and $type eq "mem") { 365 | my $r = 0; 366 | my $g = 190 + int(50 * $v2); 367 | my $b = 0 + int(210 * $v1); 368 | return "rgb($r,$g,$b)"; 369 | } 370 | if (defined $type and $type eq "io") { 371 | my $r = 80 + int(60 * $v1); 372 | my $g = $r; 373 | my $b = 190 + int(55 * $v2); 374 | return "rgb($r,$g,$b)"; 375 | } 376 | 377 | # multi palettes 378 | if (defined $type and $type eq "java") { 379 | # Handle both annotations (_[j], _[i], ...; which are 380 | # accurate), as well as input that lacks any annotations, as 381 | # best as possible. Without annotations, we get a little hacky 382 | # and match on java|org|com, etc. 383 | if ($name =~ m:_\[j\]$:) { # jit annotation 384 | $type = "green"; 385 | } elsif ($name =~ m:_\[i\]$:) { # inline annotation 386 | $type = "aqua"; 387 | } elsif ($name =~ m:^L?(java|org|com|io|sun)/:) { # Java 388 | $type = "green"; 389 | } elsif ($name =~ m:_\[k\]$:) { # kernel annotation 390 | $type = "orange"; 391 | } elsif ($name =~ /::/) { # C++ 392 | $type = "yellow"; 393 | } else { # system 394 | $type = "red"; 395 | } 396 | # fall-through to color palettes 397 | } 398 | if (defined $type and $type eq "perl") { 399 | if ($name =~ /::/) { # C++ 400 | $type = "yellow"; 401 | } elsif ($name =~ m:Perl: or $name =~ m:\.pl:) { # Perl 402 | $type = "green"; 403 | } elsif ($name =~ m:_\[k\]$:) { # kernel 404 | $type = "orange"; 405 | } else { # system 406 | $type = "red"; 407 | } 408 | # fall-through to color palettes 409 | } 410 | if (defined $type and $type eq "js") { 411 | # Handle both annotations (_[j], _[i], ...; which are 412 | # accurate), as well as input that lacks any annotations, as 413 | # best as possible. Without annotations, we get a little hacky, 414 | # and match on a "/" with a ".js", etc. 415 | if ($name =~ m:_\[j\]$:) { # jit annotation 416 | if ($name =~ m:/:) { 417 | $type = "green"; # source 418 | } else { 419 | $type = "aqua"; # builtin 420 | } 421 | } elsif ($name =~ /::/) { # C++ 422 | $type = "yellow"; 423 | } elsif ($name =~ m:/.*\.js:) { # JavaScript (match "/" in path) 424 | $type = "green"; 425 | } elsif ($name =~ m/:/) { # JavaScript (match ":" in builtin) 426 | $type = "aqua"; 427 | } elsif ($name =~ m/^ $/) { # Missing symbol 428 | $type = "green"; 429 | } elsif ($name =~ m:_\[k\]:) { # kernel 430 | $type = "orange"; 431 | } else { # system 432 | $type = "red"; 433 | } 434 | # fall-through to color palettes 435 | } 436 | if (defined $type and $type eq "wakeup") { 437 | $type = "aqua"; 438 | # fall-through to color palettes 439 | } 440 | if (defined $type and $type eq "chain") { 441 | if ($name =~ m:_\[w\]:) { # waker 442 | $type = "aqua" 443 | } else { # off-CPU 444 | $type = "blue"; 445 | } 446 | # fall-through to color palettes 447 | } 448 | 449 | # color palettes 450 | if (defined $type and $type eq "red") { 451 | my $r = 200 + int(55 * $v1); 452 | my $x = 50 + int(80 * $v1); 453 | return "rgb($r,$x,$x)"; 454 | } 455 | if (defined $type and $type eq "green") { 456 | my $g = 200 + int(55 * $v1); 457 | my $x = 50 + int(60 * $v1); 458 | return "rgb($x,$g,$x)"; 459 | } 460 | if (defined $type and $type eq "blue") { 461 | my $b = 205 + int(50 * $v1); 462 | my $x = 80 + int(60 * $v1); 463 | return "rgb($x,$x,$b)"; 464 | } 465 | if (defined $type and $type eq "yellow") { 466 | my $x = 175 + int(55 * $v1); 467 | my $b = 50 + int(20 * $v1); 468 | return "rgb($x,$x,$b)"; 469 | } 470 | if (defined $type and $type eq "purple") { 471 | my $x = 190 + int(65 * $v1); 472 | my $g = 80 + int(60 * $v1); 473 | return "rgb($x,$g,$x)"; 474 | } 475 | if (defined $type and $type eq "aqua") { 476 | my $r = 50 + int(60 * $v1); 477 | my $g = 165 + int(55 * $v1); 478 | my $b = 165 + int(55 * $v1); 479 | return "rgb($r,$g,$b)"; 480 | } 481 | if (defined $type and $type eq "orange") { 482 | my $r = 190 + int(65 * $v1); 483 | my $g = 90 + int(65 * $v1); 484 | return "rgb($r,$g,0)"; 485 | } 486 | 487 | return "rgb(0,0,0)"; 488 | } 489 | 490 | sub color_scale { 491 | my ($value, $max) = @_; 492 | my ($r, $g, $b) = (255, 255, 255); 493 | $value = -$value if $negate; 494 | if ($value > 0) { 495 | $g = $b = int(210 * ($max - $value) / $max); 496 | } elsif ($value < 0) { 497 | $r = $g = int(210 * ($max + $value) / $max); 498 | } 499 | return "rgb($r,$g,$b)"; 500 | } 501 | 502 | sub color_map { 503 | my ($colors, $func) = @_; 504 | if (exists $palette_map{$func}) { 505 | return $palette_map{$func}; 506 | } else { 507 | $palette_map{$func} = color($colors, $hash, $func); 508 | return $palette_map{$func}; 509 | } 510 | } 511 | 512 | sub write_palette { 513 | open(FILE, ">$pal_file"); 514 | foreach my $key (sort keys %palette_map) { 515 | print FILE $key."->".$palette_map{$key}."\n"; 516 | } 517 | close(FILE); 518 | } 519 | 520 | sub read_palette { 521 | if (-e $pal_file) { 522 | open(FILE, $pal_file) or die "can't open file $pal_file: $!"; 523 | while ( my $line = ) { 524 | chomp($line); 525 | (my $key, my $value) = split("->",$line); 526 | $palette_map{$key}=$value; 527 | } 528 | close(FILE) 529 | } 530 | } 531 | 532 | my %Node; # Hash of merged frame data 533 | my %Tmp; 534 | 535 | # flow() merges two stacks, storing the merged frames and value data in %Node. 536 | sub flow { 537 | my ($last, $this, $v, $d) = @_; 538 | 539 | my $len_a = @$last - 1; 540 | my $len_b = @$this - 1; 541 | 542 | my $i = 0; 543 | my $len_same; 544 | for (; $i <= $len_a; $i++) { 545 | last if $i > $len_b; 546 | last if $last->[$i] ne $this->[$i]; 547 | } 548 | $len_same = $i; 549 | 550 | for ($i = $len_a; $i >= $len_same; $i--) { 551 | my $k = "$last->[$i];$i"; 552 | # a unique ID is constructed from "func;depth;etime"; 553 | # func-depth isn't unique, it may be repeated later. 554 | $Node{"$k;$v"}->{stime} = delete $Tmp{$k}->{stime}; 555 | if (defined $Tmp{$k}->{delta}) { 556 | $Node{"$k;$v"}->{delta} = delete $Tmp{$k}->{delta}; 557 | } 558 | delete $Tmp{$k}; 559 | } 560 | 561 | for ($i = $len_same; $i <= $len_b; $i++) { 562 | my $k = "$this->[$i];$i"; 563 | $Tmp{$k}->{stime} = $v; 564 | if (defined $d) { 565 | $Tmp{$k}->{delta} += $i == $len_b ? $d : 0; 566 | } 567 | } 568 | 569 | return $this; 570 | } 571 | 572 | # parse input 573 | my @Data; 574 | my @SortedData; 575 | my $last = []; 576 | my $time = 0; 577 | my $delta = undef; 578 | my $ignored = 0; 579 | my $line; 580 | my $maxdelta = 1; 581 | 582 | # reverse if needed 583 | foreach (<>) { 584 | chomp; 585 | $line = $_; 586 | if ($stackreverse) { 587 | # there may be an extra samples column for differentials 588 | # XXX todo: redo these REs as one. It's repeated below. 589 | my($stack, $samples) = (/^(.*)\s+?(\d+(?:\.\d*)?)$/); 590 | my $samples2 = undef; 591 | if ($stack =~ /^(.*)\s+?(\d+(?:\.\d*)?)$/) { 592 | $samples2 = $samples; 593 | ($stack, $samples) = $stack =~ (/^(.*)\s+?(\d+(?:\.\d*)?)$/); 594 | unshift @Data, join(";", reverse split(";", $stack)) . " $samples $samples2"; 595 | } else { 596 | unshift @Data, join(";", reverse split(";", $stack)) . " $samples"; 597 | } 598 | } else { 599 | unshift @Data, $line; 600 | } 601 | } 602 | 603 | if ($flamechart) { 604 | # In flame chart mode, just reverse the data so time moves from left to right. 605 | @SortedData = reverse @Data; 606 | } else { 607 | @SortedData = sort @Data; 608 | } 609 | 610 | # process and merge frames 611 | foreach (@SortedData) { 612 | chomp; 613 | # process: folded_stack count 614 | # eg: func_a;func_b;func_c 31 615 | my ($stack, $samples) = (/^(.*)\s+?(\d+(?:\.\d*)?)$/); 616 | unless (defined $samples and defined $stack) { 617 | ++$ignored; 618 | next; 619 | } 620 | 621 | # there may be an extra samples column for differentials: 622 | my $samples2 = undef; 623 | if ($stack =~ /^(.*)\s+?(\d+(?:\.\d*)?)$/) { 624 | $samples2 = $samples; 625 | ($stack, $samples) = $stack =~ (/^(.*)\s+?(\d+(?:\.\d*)?)$/); 626 | } 627 | $delta = undef; 628 | if (defined $samples2) { 629 | $delta = $samples2 - $samples; 630 | $maxdelta = abs($delta) if abs($delta) > $maxdelta; 631 | } 632 | 633 | # for chain graphs, annotate waker frames with "_[w]", for later 634 | # coloring. This is a hack, but has a precedent ("_[k]" from perf). 635 | if ($colors eq "chain") { 636 | my @parts = split ";--;", $stack; 637 | my @newparts = (); 638 | $stack = shift @parts; 639 | $stack .= ";--;"; 640 | foreach my $part (@parts) { 641 | $part =~ s/;/_[w];/g; 642 | $part .= "_[w]"; 643 | push @newparts, $part; 644 | } 645 | $stack .= join ";--;", @parts; 646 | } 647 | 648 | # merge frames and populate %Node: 649 | $last = flow($last, [ '', split ";", $stack ], $time, $delta); 650 | 651 | if (defined $samples2) { 652 | $time += $samples2; 653 | } else { 654 | $time += $samples; 655 | } 656 | } 657 | flow($last, [], $time, $delta); 658 | 659 | warn "Ignored $ignored lines with invalid format\n" if $ignored; 660 | unless ($time) { 661 | warn "ERROR: No stack counts found\n"; 662 | my $im = SVG->new(); 663 | # emit an error message SVG, for tools automating flamegraph use 664 | my $imageheight = $fontsize * 5; 665 | $im->header($imagewidth, $imageheight); 666 | $im->stringTTF($im->colorAllocate(0, 0, 0), $fonttype, $fontsize + 2, 667 | 0.0, int($imagewidth / 2), $fontsize * 2, 668 | "ERROR: No valid input provided to flamegraph.pl.", "middle"); 669 | print $im->svg; 670 | exit 2; 671 | } 672 | if ($timemax and $timemax < $time) { 673 | warn "Specified --total $timemax is less than actual total $time, so ignored\n" 674 | if $timemax/$time > 0.02; # only warn is significant (e.g., not rounding etc) 675 | undef $timemax; 676 | } 677 | $timemax ||= $time; 678 | 679 | my $widthpertime = ($imagewidth - 2 * $xpad) / $timemax; 680 | my $minwidth_time = $minwidth / $widthpertime; 681 | 682 | # prune blocks that are too narrow and determine max depth 683 | while (my ($id, $node) = each %Node) { 684 | my ($func, $depth, $etime) = split ";", $id; 685 | my $stime = $node->{stime}; 686 | die "missing start for $id" if not defined $stime; 687 | 688 | if (($etime-$stime) < $minwidth_time) { 689 | delete $Node{$id}; 690 | next; 691 | } 692 | $depthmax = $depth if $depth > $depthmax; 693 | } 694 | 695 | # draw canvas, and embed interactive JavaScript program 696 | my $imageheight = (($depthmax + 1) * $frameheight) + $ypad1 + $ypad2; 697 | $imageheight += $ypad3 if $subtitletext ne ""; 698 | my $im = SVG->new(); 699 | $im->header($imagewidth, $imageheight); 700 | my $inc = < 702 | 703 | 704 | 705 | 706 | 707 | 710 | 1027 | INC 1028 | $im->include($inc); 1029 | $im->filledRectangle(0, 0, $imagewidth, $imageheight, 'url(#background)'); 1030 | my ($white, $black, $vvdgrey, $vdgrey, $dgrey) = ( 1031 | $im->colorAllocate(255, 255, 255), 1032 | $im->colorAllocate(0, 0, 0), 1033 | $im->colorAllocate(40, 40, 40), 1034 | $im->colorAllocate(160, 160, 160), 1035 | $im->colorAllocate(200, 200, 200), 1036 | ); 1037 | $im->stringTTF($black, $fonttype, $fontsize + 5, 0.0, int($imagewidth / 2), $fontsize * 2, $titletext, "middle"); 1038 | if ($subtitletext ne "") { 1039 | $im->stringTTF($vdgrey, $fonttype, $fontsize, 0.0, int($imagewidth / 2), $fontsize * 4, $subtitletext, "middle"); 1040 | } 1041 | $im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $imageheight - ($ypad2 / 2), " ", "", 'id="details"'); 1042 | $im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $fontsize * 2, 1043 | "Reset Zoom", "", 'id="unzoom" onclick="unzoom()" style="opacity:0.0;cursor:pointer"'); 1044 | $im->stringTTF($black, $fonttype, $fontsize, 0.0, $imagewidth - $xpad - 100, 1045 | $fontsize * 2, "Search", "", 'id="search" onmouseover="searchover()" onmouseout="searchout()" onclick="search_prompt()" style="opacity:0.1;cursor:pointer"'); 1046 | $im->stringTTF($black, $fonttype, $fontsize, 0.0, $imagewidth - $xpad - 100, $imageheight - ($ypad2 / 2), " ", "", 'id="matched"'); 1047 | 1048 | if ($palette) { 1049 | read_palette(); 1050 | } 1051 | 1052 | # draw frames 1053 | while (my ($id, $node) = each %Node) { 1054 | my ($func, $depth, $etime) = split ";", $id; 1055 | my $stime = $node->{stime}; 1056 | my $delta = $node->{delta}; 1057 | 1058 | $etime = $timemax if $func eq "" and $depth == 0; 1059 | 1060 | my $x1 = $xpad + $stime * $widthpertime; 1061 | my $x2 = $xpad + $etime * $widthpertime; 1062 | my ($y1, $y2); 1063 | unless ($inverted) { 1064 | $y1 = $imageheight - $ypad2 - ($depth + 1) * $frameheight + $framepad; 1065 | $y2 = $imageheight - $ypad2 - $depth * $frameheight; 1066 | } else { 1067 | $y1 = $ypad1 + $depth * $frameheight; 1068 | $y2 = $ypad1 + ($depth + 1) * $frameheight - $framepad; 1069 | } 1070 | 1071 | my $samples = sprintf "%.0f", ($etime - $stime) * $factor; 1072 | (my $samples_txt = $samples) # add commas per perlfaq5 1073 | =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g; 1074 | 1075 | my $info; 1076 | if ($func eq "" and $depth == 0) { 1077 | $info = "all ($samples_txt $countname, 100%)"; 1078 | } else { 1079 | my $pct = sprintf "%.2f", ((100 * $samples) / ($timemax * $factor)); 1080 | my $escaped_func = $func; 1081 | # clean up SVG breaking characters: 1082 | $escaped_func =~ s/&/&/g; 1083 | $escaped_func =~ s//>/g; 1085 | $escaped_func =~ s/"/"/g; 1086 | $escaped_func =~ s/_\[[kwij]\]$//; # strip any annotation 1087 | unless (defined $delta) { 1088 | $info = "$escaped_func ($samples_txt $countname, $pct%)"; 1089 | } else { 1090 | my $d = $negate ? -$delta : $delta; 1091 | my $deltapct = sprintf "%.2f", ((100 * $d) / ($timemax * $factor)); 1092 | $deltapct = $d > 0 ? "+$deltapct" : $deltapct; 1093 | $info = "$escaped_func ($samples_txt $countname, $pct%; $deltapct%)"; 1094 | } 1095 | } 1096 | 1097 | my $nameattr = { %{ $nameattr{$func}||{} } }; # shallow clone 1098 | $nameattr->{class} ||= "func_g"; 1099 | $nameattr->{onmouseover} ||= "s(this)"; 1100 | $nameattr->{onmouseout} ||= "c()"; 1101 | $nameattr->{onclick} ||= "zoom(this)"; 1102 | $nameattr->{title} ||= $info; 1103 | $im->group_start($nameattr); 1104 | 1105 | my $color; 1106 | if ($func eq "--") { 1107 | $color = $vdgrey; 1108 | } elsif ($func eq "-") { 1109 | $color = $dgrey; 1110 | } elsif (defined $delta) { 1111 | $color = color_scale($delta, $maxdelta); 1112 | } elsif ($palette) { 1113 | $color = color_map($colors, $func); 1114 | } else { 1115 | $color = color($colors, $hash, $func); 1116 | } 1117 | $im->filledRectangle($x1, $y1, $x2, $y2, $color, 'rx="2" ry="2"'); 1118 | 1119 | my $chars = int( ($x2 - $x1) / ($fontsize * $fontwidth)); 1120 | my $text = ""; 1121 | if ($chars >= 3) { # room for one char plus two dots 1122 | $func =~ s/_\[[kwij]\]$//; # strip any annotation 1123 | $text = substr $func, 0, $chars; 1124 | substr($text, -2, 2) = ".." if $chars < length $func; 1125 | $text =~ s/&/&/g; 1126 | $text =~ s//>/g; 1128 | } 1129 | $im->stringTTF($black, $fonttype, $fontsize, 0.0, $x1 + 3, 3 + ($y1 + $y2) / 2, $text, ""); 1130 | 1131 | $im->group_end($nameattr); 1132 | } 1133 | 1134 | print $im->svg; 1135 | 1136 | if ($palette) { 1137 | write_palette(); 1138 | } 1139 | 1140 | # vim: ts=8 sts=8 sw=8 noexpandtab 1141 | --------------------------------------------------------------------------------