├── .gitignore ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── prove_and_verify.rs └── src ├── errors.rs ├── lib.rs ├── proof.rs ├── transcript.rs └── util.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.lock 3 | *.swp 4 | *.swo 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | All contributions are greatly appreciated! 3 | 4 | For feature requests, suggestions, and bug reports, please open an issue on [Github](https://github.com/cargodog/arcturus). (Or, send me an email if you're opposed to using Github for whatever reason.) 5 | 6 | Patches are welcomed as pull requests on [Github](https://github.com/cargodog/arcturus), as well as by email. My contact can be found in [Cargo.toml](Cargo.toml). 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | rand = "0.7" 3 | polynomials = "0.2" 4 | 5 | [dependencies.curve25519-dalek] 6 | default-features = false 7 | version = "3.0" 8 | features = ["serde", "alloc"] 9 | 10 | [dependencies.blake2] 11 | default-features = false 12 | version = "0.9" 13 | 14 | [dependencies.merlin] 15 | default-features = false 16 | version = "2" 17 | 18 | [dependencies.serde] 19 | default-features = false 20 | version = "1.0" 21 | features = ["derive"] 22 | optional = true 23 | 24 | [dependencies.itertools] 25 | default-features = false 26 | version = "0.10.0" 27 | 28 | [dev-dependencies] 29 | criterion = "0.3" 30 | 31 | [features] 32 | default = ["std", "serde", "curve25519-dalek/u32_backend"] 33 | std = [] 34 | simd_backend = ["curve25519-dalek/simd_backend", "blake2/simd_asm"] 35 | u64_backend = ["curve25519-dalek/u64_backend"] 36 | u32_backend = ["curve25519-dalek/u32_backend"] 37 | 38 | [[bench]] 39 | name = "prove_and_verify" 40 | harness = false 41 | 42 | [package] 43 | name = "arcturus" 44 | version = "0.4.0" 45 | authors = ["cargodog "] 46 | edition = "2018" 47 | description = "Implementation of Arcturus zero-knowledge proofs for confidential transactions." 48 | readme = "README.md" 49 | license-file = "LICENSE" 50 | repository = "https://github.com/cargodog/arcturus" 51 | keywords = ["cryptography", "crypto", "zero-knowledge", "zk", "no-std"] 52 | categories = ["cryptography", "no-std"] 53 | exclude = ["**/.gitignore", ".gitignore"] 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 cargodog 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚠️ Security Warning 2 | A break has been identified in the security assumptions as written in the Arcutur paper. This project exists for research purposes and historical context only, and under no circumstances should be used in any real application. Read more about the break here: https://github.com/cargodog/arcturus/issues/43 3 | 4 | 5 | # arcturus 6 | A pure Rust, light-weight, and performant implementation of the Arcturus zero-knowledge 7 | proof system [[link][arcturus-paper]]. 8 | 9 | Arcturus enables efficient proof and verification of confidential transactions with very large 10 | anonymity sets. A correct proof provides the following guarantees: 11 | 1) The signer posesses the signing key for each spent output in the ring. 12 | 1) The sum of spent inputs matches the sum of newly minted outputs.[1](#usage-notes) 13 | 1) Each spent input is accompanied with a unique, deterministic, linking tag to detect double 14 | spends.[2](#usage-notes) 15 | 1) The transaction input and output values are hidden (aka confidential). 16 | 1) The transaction inputs and signing keys are hidden in a large anonymity set.[3](#usage-notes) 17 | 18 | # Documentation 19 | Detailed documentation can be found [here][docs-external]. 20 | 21 | # Usage and Features 22 | Latest release version can be found in the git tags or on [crates.io][arcturus-crate]. Add the latest version to your project's `Cargo.toml`: 23 | ```toml 24 | arcturus = "x.y.z" 25 | ``` 26 | 27 | By default, `std` and `serde` features are enabled. To build without `std` or without `serde` 28 | implementations, use the `--no-default-features` option when building. Note, if default features 29 | are disabled, a backend must be specified. The available backends are: 30 | * `simd_backend` 31 | * `u64_backend` 32 | * `u32_backend` 33 | 34 | The following example 35 | builds without `std`, but still implements `serde`: 36 | ```sh 37 | cargo build --no-default-features --features "serde simd_backend" 38 | ``` 39 | 40 | Please keep the following points in mind when building a project around this library: 41 | 1) This library does not include range proofs. To ensure no input or output value is 42 | negative, each input and output commitment should be accompanied with a range proof, such as 43 | [bulletproofs][bulletproofs-crate]. Failure to prevent negative inputs or outputs 44 | could allow an attacker to create new coins (e.g. inflation bug). 45 | 46 | 2) To prevent double spends, each input's linking tag should be checked for uniqueness and 47 | recorded in a list of spent outputs. If a tag is ever seen twice, this means that the 48 | corresponding input has already been spent. 49 | 50 | 3) This library leaves selection of the anonymity set up to the user. Selecting a good 51 | ring of UTXOs is essential to providing anonymity for the signer and his transaction inputs. 52 | 53 | 54 | # Example: 55 | ```rust 56 | use arcturus::*; 57 | use curve25519_dalek::ristretto::RistrettoPoint; 58 | use curve25519_dalek::scalar::Scalar; 59 | use merlin::Transcript; 60 | use rand::rngs::OsRng; // You should use a more secure RNG 61 | 62 | // Set up proof generators for 5 bit binary proofs with up to 2 spends in a proof. 63 | let gens = ArcturusGens::new(2, 5, 2).unwrap(); 64 | 65 | // Given a ring of UTXOs, assume the signer controls outputs at indices 13 & 14 (signer knows spend 66 | // key, amount, and blinding factor for each of his inputs) and wishes to spend those inputs in a 67 | // transaction which mints a new output whose value balances against the inputs: 68 | let ring: Vec = ...; 69 | 70 | // Indices of UTXOs to spend as transaction inputs 71 | let idxs = vec![13, 14]; 72 | 73 | // Secret data to spend each input UTXO 74 | let spends = vec![ // Secret data to spend each input UTXO 75 | SpendSecret::new(spend_key_13, spend_amt_13, spend_blind_13), 76 | SpendSecret::new(spend_key_14, spend_amt_14, spend_blind_14) 77 | ]; 78 | 79 | // Secret data for new minted outputs (Total mint ammounts must balance total input ammounts). 80 | let mints = vec![MintSecret::new(mint_pubkey, mint_amt, mint_blind)]; 81 | 82 | // Signer computes the transaction proof 83 | let mut t = Transcript::new(b"Test proof"); 84 | let proof = gens.prove(&mut t, &ring[..], &idxs[..], &spends[..], &mints[..]).unwrap(); 85 | 86 | // The verifier my verify the proof as follows: 87 | let mut t = Transcript::new(b"Test proof"); 88 | let proofs = vec![proof]; 89 | assert!(gens.verify(&mut t, &ring[..], &proofs[..]).is_ok()); 90 | ``` 91 | 92 | # Performance 93 | Benchmarks are run using [criterion.rs][criterion-crate]. 94 | ```sh 95 | export RUSTFLAGS="-C target_cpu=native" 96 | cargo bench --no-default-features --features "std simd_backend serde" 97 | ``` 98 | 99 | # Contributing 100 | Please see [CONTRIBUTING.md][contributing]. 101 | 102 | 103 | [arcturus-paper]: https://eprint.iacr.org/2020/312 104 | [arcturus-crate]: https://crates.io/crates/arcturus 105 | [docs-external]: https://docs.rs/arcturus 106 | [bulletproofs-crate]: https://crates.io/crates/bulletproofs 107 | [criterion-crate]: https://crates.io/crates/criterion 108 | [contributing]: https://github.com/cargodog/arcturus/blob/master/CONTRIBUTING.md 109 | -------------------------------------------------------------------------------- /benches/prove_and_verify.rs: -------------------------------------------------------------------------------- 1 | use arcturus::proof::*; 2 | use criterion::{ 3 | black_box, criterion_group, criterion_main, BenchmarkId, Criterion, SamplingMode, Throughput, 4 | }; 5 | use curve25519_dalek::ristretto::RistrettoPoint; 6 | use curve25519_dalek::scalar::Scalar; 7 | use merlin::Transcript; 8 | use rand::rngs::OsRng; // You should use a more secure RNG 9 | use std::iter; 10 | 11 | fn setup_prover_data( 12 | n: usize, 13 | m: usize, 14 | w: usize, 15 | ) -> ( 16 | ArcturusGens, 17 | Vec, 18 | Vec, 19 | Vec, 20 | Vec, 21 | ) { 22 | let gens = ArcturusGens::new(n, m, w).unwrap(); 23 | let ring_size = gens.ring_size(); 24 | 25 | // Build a random ring of outputs 26 | let mut ring = iter::repeat_with(|| { 27 | Output::new( 28 | RistrettoPoint::random(&mut OsRng), 29 | RistrettoPoint::random(&mut OsRng), 30 | ) 31 | }) 32 | .take(ring_size) 33 | .collect::>(); 34 | 35 | // Generate the secret data for this proof 36 | let idxs = vec![3, 4, 7]; // Each proof spends 3 inputs and mints 3 outputs 37 | let mut spends = Vec::new(); 38 | let mut mints = Vec::new(); 39 | for &idx in &idxs { 40 | let privkey = Scalar::random(&mut OsRng); 41 | let pubkey = RistrettoPoint::random(&mut OsRng); 42 | let amount = 6600; 43 | let blind_spend = Scalar::random(&mut OsRng); 44 | let blind_mint = Scalar::random(&mut OsRng); 45 | let spend = SpendSecret::new(privkey, amount, blind_spend); 46 | let mint = MintSecret::new(pubkey, amount, blind_mint); 47 | ring[idx] = spend.to_output(); 48 | spends.push(spend); 49 | mints.push(mint); 50 | } 51 | 52 | (gens, ring, idxs, spends, mints) 53 | } 54 | 55 | fn proof_time(c: &mut Criterion) { 56 | let mut group = c.benchmark_group("Proof Time"); 57 | group.sample_size(10); 58 | 59 | // Iterate over list of proof configs (n, m, w) 60 | for cfg in [(16, 2, 3), (16, 3, 3)].iter() { 61 | let (gens, ring, idxs, spends, mints) = setup_prover_data(cfg.0, cfg.1, cfg.2); 62 | group.throughput(Throughput::Elements(1)); 63 | group.bench_with_input( 64 | BenchmarkId::from_parameter(gens.ring_size()), 65 | &(ring, idxs, spends, mints), 66 | |b, (ring, idxs, spends, mints)| { 67 | b.iter(|| { 68 | gens.prove( 69 | &mut Transcript::new(b"Arcturus-Becnhmark"), 70 | &ring[..], 71 | &idxs[..], 72 | &spends[..], 73 | &mints[..], 74 | ) 75 | .unwrap() 76 | }); 77 | }, 78 | ); 79 | } 80 | group.finish(); 81 | } 82 | 83 | fn verification_time(c: &mut Criterion) { 84 | static BATCH_SIZE: usize = 1024; 85 | let mut group = c.benchmark_group("Verification Time"); 86 | group.sample_size(10); 87 | 88 | // Iterate over list of proof configs (n, m, w) 89 | for cfg in [(16, 2, 3), (16, 3, 3)].iter() { 90 | let (gens, ring, idxs, spends, mints) = setup_prover_data(cfg.0, cfg.1, cfg.2); 91 | let proof = gens 92 | .prove( 93 | &mut Transcript::new(b"Arcturus-Becnhmark"), 94 | &ring[..], 95 | &idxs[..], 96 | &spends[..], 97 | &mints[..], 98 | ) 99 | .unwrap(); 100 | let proofs = iter::once(proof) 101 | .cycle() 102 | .take(BATCH_SIZE) 103 | .collect::>(); 104 | group.throughput(Throughput::Elements(BATCH_SIZE as u64)); 105 | group.bench_with_input( 106 | BenchmarkId::from_parameter(gens.ring_size()), 107 | &(ring, idxs, spends, mints), 108 | |b, (ring, idxs, spends, mints)| { 109 | b.iter(|| { 110 | gens.verify( 111 | &mut Transcript::new(b"Arcturus-Becnhmark"), 112 | &ring[..], 113 | &proofs[..], 114 | ) 115 | .unwrap() 116 | }); 117 | }, 118 | ); 119 | } 120 | group.finish(); 121 | } 122 | 123 | criterion_group!(benches, verification_time); 124 | criterion_main!(benches); 125 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | pub type ArcturusResult = core::result::Result; 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub enum ArcturusError { 5 | BadArg, 6 | MintsAndSpendsImbalance, 7 | Overflow, 8 | ProofDigitsTooSmall, 9 | ProofNumSignersTooSmall, 10 | ProofNumSignersTooLarge, 11 | ProofRadixTooSmall, 12 | RingSizeTooSmall, 13 | RingSizeTooLarge, 14 | Unimplemented, 15 | VerificationFailed, 16 | } 17 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A light-weight and performant implementation of the Arcturus zero-knowledge proof system 2 | //! [[link](https://eprint.iacr.org/2020/312)]. 3 | 4 | #![cfg_attr(not(feature = "std"), no_std)] 5 | 6 | //----------------------------------------------------------------------------- 7 | // External dependencies: 8 | //----------------------------------------------------------------------------- 9 | #[cfg(not(feature = "std"))] 10 | #[macro_use] 11 | extern crate alloc; 12 | extern crate blake2; 13 | extern crate curve25519_dalek; 14 | extern crate itertools; 15 | extern crate polynomials; 16 | 17 | //----------------------------------------------------------------------------- 18 | // Public modules 19 | //----------------------------------------------------------------------------- 20 | pub mod errors; 21 | pub mod proof; 22 | 23 | //----------------------------------------------------------------------------- 24 | // Re-exports 25 | //----------------------------------------------------------------------------- 26 | pub use proof::*; 27 | 28 | //----------------------------------------------------------------------------- 29 | // Internal modules 30 | //----------------------------------------------------------------------------- 31 | pub(crate) mod transcript; 32 | pub(crate) mod util; 33 | -------------------------------------------------------------------------------- /src/proof.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | use crate::errors::{ArcturusError, ArcturusResult}; 3 | use crate::transcript::TranscriptProtocol; 4 | use crate::util::{exp_iter, sized_flatten}; 5 | use crate::{flatten_2d, flatten_3d}; 6 | #[cfg(not(feature = "std"))] 7 | use alloc::vec::Vec; 8 | use blake2::Blake2b; 9 | use core::iter::{once, repeat, Iterator}; 10 | use curve25519_dalek::constants; 11 | use curve25519_dalek::ristretto::RistrettoPoint; 12 | use curve25519_dalek::scalar::Scalar; 13 | use curve25519_dalek::traits::{IsIdentity, MultiscalarMul, VartimeMultiscalarMul}; 14 | use itertools::izip; 15 | use merlin::Transcript; 16 | use polynomials::Polynomial; 17 | use rand::thread_rng; 18 | #[cfg(feature = "serde")] 19 | use serde::{Deserialize, Serialize}; 20 | #[cfg(feature = "std")] 21 | use std::vec::Vec; 22 | 23 | /// An output consists of a public key and a value commitment 24 | #[derive(Debug, Clone, PartialEq)] 25 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 26 | pub struct Output { 27 | pubkey: RistrettoPoint, 28 | commit: RistrettoPoint, 29 | } 30 | 31 | impl Output { 32 | pub fn new(pubkey: RistrettoPoint, commit: RistrettoPoint) -> Self { 33 | Output { pubkey, commit } 34 | } 35 | } 36 | 37 | /// Secret data needed to spend an existing [`Output`] 38 | #[derive(Debug, Clone)] 39 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 40 | pub struct SpendSecret { 41 | privkey: Scalar, 42 | amount: Scalar, 43 | blind: Scalar, 44 | } 45 | 46 | impl SpendSecret { 47 | pub fn new(privkey: Scalar, amount: u64, blind: Scalar) -> Self { 48 | let amount = Scalar::from(amount); 49 | SpendSecret { 50 | privkey, 51 | amount, 52 | blind, 53 | } 54 | } 55 | 56 | pub fn output(&self) -> Output { 57 | let G = constants::RISTRETTO_BASEPOINT_POINT; 58 | let H = derive_generator(&G, 0, 0, 0); 59 | let pubkey = self.privkey * G; 60 | let commit = self.amount * H + self.blind * G; 61 | Output { pubkey, commit } 62 | } 63 | 64 | pub fn tag(&self) -> RistrettoPoint { 65 | let G = constants::RISTRETTO_BASEPOINT_POINT; 66 | let U = RistrettoPoint::hash_from_bytes::(G.compress().as_bytes()); 67 | self.privkey.invert() * U 68 | } 69 | } 70 | 71 | /// Secret data needed to mint a new [`Output`] 72 | #[derive(Debug, Clone)] 73 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 74 | pub struct MintSecret { 75 | pubkey: RistrettoPoint, 76 | amount: Scalar, 77 | blind: Scalar, 78 | } 79 | 80 | impl MintSecret { 81 | pub fn new(pubkey: RistrettoPoint, amount: u64, blind: Scalar) -> Self { 82 | let amount = Scalar::from(amount); 83 | MintSecret { 84 | pubkey, 85 | amount, 86 | blind, 87 | } 88 | } 89 | 90 | pub fn output(&self) -> Output { 91 | let G = constants::RISTRETTO_BASEPOINT_POINT; 92 | let H = derive_generator(&G, 0, 0, 0); 93 | let pubkey = self.pubkey; 94 | let commit = self.amount * H + self.blind * G; 95 | Output { pubkey, commit } 96 | } 97 | } 98 | 99 | /// Zero-knowledge proof that some minted outputs are valid spends of existing outputs in a ring 100 | #[derive(Debug, Clone)] 101 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 102 | pub struct ArcturusProof { 103 | mints: Vec, 104 | A: RistrettoPoint, 105 | B: RistrettoPoint, 106 | C: RistrettoPoint, 107 | D: RistrettoPoint, 108 | X_j: Vec, 109 | Y_j: Vec, 110 | Z_j: Vec, 111 | J_u: Vec, 112 | f_uji: Vec>>, 113 | zA: Scalar, 114 | zC: Scalar, 115 | zR_u: Vec, 116 | zS: Scalar, 117 | } 118 | 119 | impl ArcturusProof { 120 | pub fn count_spends(&self) -> usize { 121 | self.J_u.len() 122 | } 123 | 124 | pub fn count_mints(&self) -> usize { 125 | self.mints.len() 126 | } 127 | 128 | pub fn tags(&self) -> &Vec { 129 | &self.J_u 130 | } 131 | 132 | pub fn mints(&self) -> &Vec { 133 | &self.mints 134 | } 135 | } 136 | 137 | /// Generators and proof context necessary to prove and verify Arcturus proofs. 138 | #[derive(Debug, Clone)] 139 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 140 | pub struct ArcturusGens { 141 | n: usize, 142 | m: usize, 143 | w: usize, 144 | G: RistrettoPoint, 145 | U: RistrettoPoint, 146 | H_uji: Vec>>, 147 | } 148 | 149 | impl ArcturusGens { 150 | /// Compute a new set of generators. 151 | /// 152 | /// This will precompute the generators necessary an `m` digit `n`-ary anonymity set. For 153 | /// example, if `n = 2`, `m = 8`, the computed generators will support proofs with an anonymity 154 | /// set of `2^8 = 256`. 155 | /// 156 | /// This will also precompute generators for up to `w` signers in a proof. 157 | pub fn new(n: usize, m: usize, w: usize) -> ArcturusResult { 158 | if n < 2 { 159 | return Err(ArcturusError::ProofRadixTooSmall); 160 | } 161 | if m < 2 { 162 | return Err(ArcturusError::ProofDigitsTooSmall); 163 | } 164 | if w < 1 { 165 | return Err(ArcturusError::ProofNumSignersTooSmall); 166 | } 167 | if let Some(x) = n.checked_pow(m as u32) { 168 | if x < w { 169 | return Err(ArcturusError::ProofNumSignersTooLarge); 170 | } 171 | } else { 172 | return Err(ArcturusError::Overflow); 173 | } 174 | 175 | // G is the Ristretto basepoint. 176 | // U is derived from hash(G) directly. 177 | // Each of H[u][j][i] are computed as an indexed derivation from G. 178 | let G = constants::RISTRETTO_BASEPOINT_POINT; 179 | let U = RistrettoPoint::hash_from_bytes::(G.compress().as_bytes()); 180 | let mut H_uji = Vec::new(); 181 | for u in 0..w { 182 | H_uji.push(Vec::new()); 183 | for j in 0..m { 184 | H_uji[u].push(Vec::new()); 185 | for i in 0..n { 186 | H_uji[u][j].push(derive_generator(&G, u as u32, j as u32, i as u32)); 187 | } 188 | } 189 | } 190 | 191 | Ok(ArcturusGens { 192 | n, 193 | m, 194 | w, 195 | G, 196 | U, 197 | H_uji, 198 | }) 199 | } 200 | 201 | /// Returns the ring size required for proof/verification. 202 | pub fn ring_size(&self) -> usize { 203 | self.n.checked_pow(self.m as u32).unwrap() 204 | } 205 | 206 | /// Create a Pedersen commitment, with value `v` and blinding factor `r`. 207 | pub fn commit(&self, v: &Scalar, r: &Scalar) -> RistrettoPoint { 208 | v * self.H_uji[0][0][0] + r * self.G 209 | } 210 | 211 | /// Prove that newly minted outputs correctly spend existing outputs within the ring. 212 | /// 213 | /// > Note: this does not prove that an output has not been previously spent in a ledger. Each 214 | /// spent output yields a unique 'spend tag'. To avoid double spends, the verifier should query 215 | /// this proof for all spend tags, and confirm no spend tag has been used in a previous 216 | /// transaction. 217 | pub fn prove( 218 | &self, 219 | tscp: &mut Transcript, 220 | ring: &[Output], 221 | idxs: &[usize], 222 | spends: &[SpendSecret], 223 | mints: &[MintSecret], 224 | ) -> ArcturusResult { 225 | tscp.arcturus_domain_sep(self.n as u64, self.m as u64); 226 | 227 | if idxs.len() != spends.len() { 228 | return Err(ArcturusError::BadArg); 229 | } else if idxs.len() > self.w { 230 | return Err(ArcturusError::BadArg); 231 | } 232 | 233 | // First make sure the mints and spends balance to zero 234 | if spends.iter().map(|s| s.amount).sum::() 235 | != mints.iter().map(|m| m.amount).sum::() 236 | { 237 | return Err(ArcturusError::MintsAndSpendsImbalance); 238 | } 239 | 240 | let u = idxs.len(); 241 | 242 | // Create a `TranscriptRng` from the high-level witness data 243 | // 244 | // The prover wants to rekey the RNG with its witness data (`l`and `r`). 245 | let mut rng = { 246 | let mut builder = tscp.build_rng(); 247 | for &i in idxs { 248 | builder = 249 | builder.rekey_with_witness_bytes(b"idx", Scalar::from(i as u32).as_bytes()); 250 | } 251 | for s in spends { 252 | builder = builder.rekey_with_witness_bytes(b"privkey", s.privkey.as_bytes()); 253 | builder = builder.rekey_with_witness_bytes(b"amount", s.amount.as_bytes()); 254 | builder = builder.rekey_with_witness_bytes(b"blind", s.blind.as_bytes()); 255 | } 256 | for m in mints { 257 | builder = builder.rekey_with_witness_bytes(b"amount", m.amount.as_bytes()); 258 | builder = builder.rekey_with_witness_bytes(b"blind", m.blind.as_bytes()); 259 | } 260 | builder.finalize(&mut thread_rng()) 261 | }; 262 | 263 | let mut a_uji = (0..u) 264 | .map(|_| { 265 | (0..self.m) 266 | .map(|_| (1..self.n).map(|_| Scalar::random(&mut rng)).collect()) 267 | .collect() 268 | }) 269 | .collect::>>>(); 270 | for a_ji in a_uji.iter_mut() { 271 | for a_i in a_ji.iter_mut() { 272 | a_i.insert(0, -a_i.iter().sum::()); 273 | } 274 | } 275 | 276 | // A = Com(a, rA) = rA*G + a_uji*H_uji 277 | let rA = Scalar::random(&mut rng); 278 | let A = RistrettoPoint::multiscalar_mul( 279 | once(&rA).chain(flatten_3d!(&a_uji[0..u])), 280 | once(&self.G).chain(flatten_3d!(&self.H_uji[0..u])), 281 | ); 282 | 283 | // Convert each index from binary to a `m` digit `n-ary` number 284 | let sigma_uji = idxs 285 | .iter() 286 | .map(|&l| { 287 | convert_base(l, self.n, self.m) 288 | .iter() 289 | .map(|&l_j| { 290 | let mut l_ji = vec![Scalar::zero(); self.n - 1]; 291 | l_ji.insert(l_j, Scalar::one()); 292 | l_ji 293 | }) 294 | .collect() 295 | }) 296 | .collect::>>>(); 297 | 298 | // B = Com(sigma, rB) = rB*G + sigma_uji*H_uji 299 | let rB = Scalar::random(&mut rng); 300 | let B = RistrettoPoint::multiscalar_mul( 301 | once(&rB).chain(flatten_3d!(&sigma_uji[0..u])), 302 | once(&self.G).chain(flatten_3d!(&self.H_uji[0..u])), 303 | ); 304 | 305 | // C = Com(a*(1-2*sigma), rC) = rB*G + (a_uji*(1-2*sigma_uji))*H_uji 306 | let C_vals_uji = izip!(flatten_3d!(&sigma_uji[0..u]), flatten_3d!(&a_uji[0..u])) 307 | .map(|(sigma, a)| a * (Scalar::one() - Scalar::from(2u32) * sigma)); 308 | let rC = Scalar::random(&mut rng); 309 | let C = RistrettoPoint::multiscalar_mul( 310 | once(rC).chain(C_vals_uji), 311 | once(&self.G).chain(flatten_3d!(&self.H_uji[0..u])), 312 | ); 313 | 314 | // D = Com(-a^2, rD) = rD*G + -a_uji*a_uji*H_uji 315 | let rD = Scalar::random(&mut rng); 316 | let D = RistrettoPoint::multiscalar_mul( 317 | once(rD).chain(flatten_3d!(&a_uji[0..u]).map(|a| -a * a)), 318 | once(&self.G).chain(flatten_3d!(&self.H_uji[0..u])), 319 | ); 320 | 321 | // Generate randomness rho & rhobar 322 | let rho_uj = (0..u) 323 | .map(|_| (0..self.m).map(|_| Scalar::random(&mut rng)).collect()) 324 | .collect::>>(); 325 | let rhobar_uj = (0..u) 326 | .map(|_| (0..self.m).map(|_| Scalar::random(&mut rng)).collect()) 327 | .collect::>>(); 328 | 329 | // Compute spend tags 330 | let J_u = spends.iter().map(|s| s.tag()).collect::>(); 331 | 332 | // Compute minted outputs 333 | let mints_pub = mints.iter().map(|m| m.output()).collect::>(); 334 | 335 | // Commit to public parameters so far 336 | for O in &mints_pub { 337 | tscp.append_point(b"pubkey", &O.pubkey.compress()); 338 | tscp.append_point(b"commit", &O.commit.compress()); 339 | } 340 | tscp.append_point(b"A", &A.compress()); 341 | tscp.append_point(b"B", &B.compress()); 342 | tscp.append_point(b"C", &C.compress()); 343 | tscp.append_point(b"D", &D.compress()); 344 | for J in &J_u { 345 | tscp.append_point(b"J", &J.compress()); 346 | } 347 | 348 | let mut mu_k = exp_iter(tscp.challenge_scalar(b"mu")); 349 | mu_k.next(); // Skip mu^0 350 | 351 | // Build X_j, Y_j, & Z_j 352 | let mut ring_count = 0; 353 | let (mut X_j, y_j, mut Z_j) = 354 | izip!(ring.iter().map(|O| (O.pubkey, O.commit)), mu_k.clone()) 355 | .enumerate() 356 | .map(|(k, ((M, P), mu))| { 357 | ring_count += 1; 358 | let p_j = idxs 359 | .iter() 360 | .enumerate() 361 | .map(|(u, &l)| compute_p_j(k, l, &a_uji[u])) 362 | .fold(vec![Scalar::zero(); self.m], |mut p_j, p_j_uth| { 363 | for j in 0..self.m { 364 | p_j[j] += p_j_uth[j]; 365 | } 366 | p_j 367 | }); 368 | let X_j_kth = p_j.iter().map(|p| p * mu * M).collect::>(); 369 | let y_j_kth = p_j.iter().map(|p| p * mu).collect::>(); 370 | let Z_j_kth = p_j.iter().map(|p| p * P).collect::>(); 371 | (X_j_kth, y_j_kth, Z_j_kth) 372 | }) 373 | .fold( 374 | ( 375 | vec![RistrettoPoint::default(); self.m], 376 | vec![Scalar::zero(); self.m], 377 | vec![RistrettoPoint::default(); self.m], 378 | ), 379 | |(mut X_j, mut y_j, mut Z_j), (X_j_kth, y_j_kth, Z_j_kth)| { 380 | for j in 0..self.m { 381 | X_j[j] += X_j_kth[j]; 382 | y_j[j] += y_j_kth[j]; 383 | Z_j[j] += Z_j_kth[j]; 384 | } 385 | (X_j, y_j, Z_j) 386 | }, 387 | ); 388 | if ring_count < self.ring_size() { 389 | return Err(ArcturusError::RingSizeTooSmall); 390 | } 391 | if ring_count > self.ring_size() { 392 | return Err(ArcturusError::RingSizeTooLarge); 393 | } 394 | let mut Y_j = y_j.iter().map(|y| y * self.U).collect::>(); 395 | for j in 0..self.m { 396 | X_j[j] += rho_uj.iter().map(|rho_j| rho_j[j]).sum::() * self.G; 397 | Y_j[j] += izip!(&rho_uj, &J_u) 398 | .map(|(rho_j, J)| rho_j[j] * J) 399 | .sum::(); 400 | Z_j[j] += rhobar_uj.iter().map(|rhobar_j| rhobar_j[j]).sum::() * self.G; 401 | } 402 | 403 | // Commit to remaining public parameters 404 | for j in 0..self.m { 405 | tscp.append_point(b"X", &X_j[j].compress()); 406 | } 407 | for j in 0..self.m { 408 | tscp.append_point(b"Y", &Y_j[j].compress()); 409 | } 410 | for j in 0..self.m { 411 | tscp.append_point(b"Z", &Z_j[j].compress()); 412 | } 413 | let x = tscp.challenge_scalar(b"x"); 414 | let x_exp_m = exp_iter(x).nth(self.m).unwrap(); 415 | 416 | let zA = rA + x * rB; 417 | let zC = rD + x * rC; 418 | let f_uji = izip!(&sigma_uji, &a_uji) 419 | .map(|(sigma_ji, a_ji)| { 420 | izip!(sigma_ji, a_ji) 421 | .map(|(sigma_i, a_i)| { 422 | izip!(&sigma_i[1..], &a_i[1..]) 423 | .map(|(sigma, a)| sigma * x + a) 424 | .collect() 425 | }) 426 | .collect() 427 | }) 428 | .collect(); 429 | let zR_u = izip!(idxs, spends.iter().map(|o| o.privkey), &rho_uj) 430 | .map(|(&l, r, rho_j)| { 431 | mu_k.clone().nth(l).unwrap() * r * x_exp_m 432 | - izip!(exp_iter(x), rho_j) 433 | .map(|(exp_x, rho)| rho * exp_x) 434 | .sum::() 435 | }) 436 | .collect::>(); 437 | let zS = x_exp_m 438 | * (spends.iter().map(|s| s.blind).sum::() 439 | - mints.iter().map(|m| m.blind).sum::()) 440 | - exp_iter(x) 441 | .take(self.m) 442 | .enumerate() 443 | .map(|(j, x_exp_j)| { 444 | x_exp_j * rhobar_uj.iter().map(|rhobar_j| rhobar_j[j]).sum::() 445 | }) 446 | .sum::(); 447 | 448 | Ok(ArcturusProof { 449 | mints: mints_pub, 450 | A, 451 | B, 452 | C, 453 | D, 454 | X_j, 455 | Y_j, 456 | Z_j, 457 | J_u, 458 | f_uji, 459 | zA, 460 | zC, 461 | zR_u, 462 | zS, 463 | }) 464 | } 465 | 466 | /// Verify in zero-knowledge that a proof is valid 467 | pub fn verify( 468 | &self, 469 | tscp: &mut Transcript, 470 | ring: &[Output], 471 | proof: ArcturusProof, 472 | ) -> ArcturusResult<()> { 473 | self.verify_batch(tscp, ring, &[proof]) 474 | } 475 | 476 | /// Verify in zero-knowledge that a batch of proofs are valid 477 | pub fn verify_batch( 478 | &self, 479 | tscp: &mut Transcript, 480 | ring: &[Output], 481 | proofs: &[ArcturusProof], 482 | ) -> ArcturusResult<()> { 483 | for p in proofs.iter() { 484 | if p.X_j.len() != self.m 485 | || p.Y_j.len() != self.m 486 | || p.Z_j.len() != self.m 487 | || p.f_uji.len() != p.J_u.len() 488 | || p.zR_u.len() != p.J_u.len() 489 | { 490 | return Err(ArcturusError::VerificationFailed); 491 | } 492 | } 493 | 494 | tscp.arcturus_domain_sep(self.n as u64, self.m as u64); 495 | 496 | // Compute `x` and `mu_k` for each proof 497 | let mut mu_pk = Vec::with_capacity(proofs.len()); 498 | let mut x_p = Vec::with_capacity(proofs.len()); 499 | for p in proofs { 500 | let mut t = tscp.clone(); 501 | for O in &p.mints { 502 | t.validate_and_append_point(b"pubkey", &O.pubkey.compress())?; 503 | t.validate_and_append_point(b"commit", &O.commit.compress())?; 504 | } 505 | t.validate_and_append_point(b"A", &p.A.compress())?; 506 | t.validate_and_append_point(b"B", &p.B.compress())?; 507 | t.validate_and_append_point(b"C", &p.C.compress())?; 508 | t.validate_and_append_point(b"D", &p.D.compress())?; 509 | for J in &p.J_u { 510 | t.validate_and_append_point(b"J", &J.compress())?; 511 | } 512 | let mut mu_k = exp_iter(t.challenge_scalar(b"mu")); 513 | mu_k.next(); // Skip mu^0 514 | mu_pk.push(mu_k.take(self.ring_size()).collect::>()); 515 | for X in &p.X_j { 516 | t.validate_and_append_point(b"X", &X.compress())?; 517 | } 518 | for Y in &p.Y_j { 519 | t.validate_and_append_point(b"Y", &Y.compress())?; 520 | } 521 | for Z in &p.Z_j { 522 | t.validate_and_append_point(b"Z", &Z.compress())?; 523 | } 524 | x_p.push(t.challenge_scalar(b"x")); 525 | } 526 | 527 | let u_max = proofs.iter().map(|p| p.f_uji.len()).max().unwrap(); 528 | 529 | // To efficiently verify a proof in a single multiexponentiation, each 530 | // inequality in the protocol is joined into one large inequality. To 531 | // prevent an attacker from selecting terms that break the 532 | // relationships guaranteed by the individual inequalities, the 533 | // verifier must randomly weight each of the terms. Similarly, since we 534 | // also verify multiple proofs in batch, the terms from each proof must 535 | // have unique weights as well. 536 | // 537 | // See the below resources for more explanation: 538 | // * https://github.com/cargodog/arcturus/issues/28 539 | // * https://eprint.iacr.org/2017/1066 540 | // 541 | // Accordingly, we generate 5 random weights for each of the 5 542 | // inequalities in this proof protocol, for each proof being verified. 543 | // Subsequently, all terms pertaining to the first proof proof and the 544 | // second inequality (labeled (2) in the Arcuturs paper), will be 545 | // weighted by `wt_[0][1]. 546 | let mut rng = tscp.build_rng().finalize(&mut thread_rng()); 547 | let wt_pn = (0..proofs.len()) 548 | .map(|_| (0..5).map(|_| Scalar::random(&mut rng)).collect()) 549 | .collect::>>(); 550 | 551 | // Join f_puj0 & each of p.f_uji to create f_puji 552 | let f_puji = izip!(proofs, &x_p) 553 | .map(|(p, x)| { 554 | p.f_uji 555 | .iter() 556 | .map(|f_ji| { 557 | f_ji.iter() 558 | .map(|f_i| { 559 | once(x - f_i.iter().sum::()) 560 | .chain(f_i.iter().cloned()) 561 | .collect::>() 562 | }) 563 | .collect::>() 564 | }) 565 | .collect::>() 566 | }) 567 | .collect::>(); 568 | 569 | // Evaluate each tensor polynomial 570 | let f_poly_pk = f_puji 571 | .iter() 572 | .map(|f_uji| { 573 | cycle_tensor_poly_evals(&f_uji) 574 | .take(self.ring_size()) 575 | .collect::>() 576 | }) 577 | .collect::>(); 578 | 579 | // Each of equations (1), (2), (3), (4), & (5) are comprised of terms of point 580 | // multiplications. Below we collect each point to be multiplied, and compute the 581 | // aggregated scalar factors by which to multiply each point. 582 | 583 | // First, collect all points used in each proof 584 | let points_G = once(&self.G); 585 | let points_U = once(&self.U); 586 | let points_H_uji = flatten_3d!(&self.H_uji[0..u_max]); 587 | let points_A_p = proofs.iter().map(|p| &p.A); 588 | let points_B_p = proofs.iter().map(|p| &p.B); 589 | let points_C_p = proofs.iter().map(|p| &p.C); 590 | let points_D_p = proofs.iter().map(|p| &p.D); 591 | let points_X_pj = sized_flatten(proofs.iter().map(|p| p.X_j.iter())); 592 | let points_Y_pj = sized_flatten(proofs.iter().map(|p| p.Y_j.iter())); 593 | let points_Z_pj = sized_flatten(proofs.iter().map(|p| p.Z_j.iter())); 594 | let points_J_pu = sized_flatten(proofs.iter().map(|p| p.J_u.iter())); 595 | let points_Q_pt = sized_flatten(proofs.iter().map(|p| p.mints.iter().map(|O| &O.commit))); 596 | let points_M_k = ring.iter().take(self.ring_size()).map(|O| &O.pubkey); 597 | let points_P_k = ring.iter().take(self.ring_size()).map(|O| &O.commit); 598 | 599 | // Chain all points into a single iterator 600 | let points = points_G 601 | .chain(points_U) 602 | .chain(points_H_uji) 603 | .chain(points_A_p) 604 | .chain(points_B_p) 605 | .chain(points_C_p) 606 | .chain(points_D_p) 607 | .chain(points_X_pj) 608 | .chain(points_Y_pj) 609 | .chain(points_Z_pj) 610 | .chain(points_J_pu) 611 | .chain(points_Q_pt) 612 | .chain(points_M_k) 613 | .chain(points_P_k); 614 | 615 | // Next, collect all scalar factors to be multiplied with the aforementioned points 616 | let scalars_G = once( 617 | izip!(proofs, &wt_pn) 618 | .map(|(p, wt_n)| wt_n[0] * p.zA) 619 | .sum::() 620 | + izip!(proofs, &wt_pn) 621 | .map(|(p, wt_n)| wt_n[1] * p.zC) 622 | .sum::() 623 | - izip!(proofs, &wt_pn) 624 | .map(|(p, wt_n)| wt_n[2] * p.zR_u.iter().sum::()) 625 | .sum::() 626 | - izip!(proofs, &wt_pn) 627 | .map(|(p, wt_n)| wt_n[4] * p.zS) 628 | .sum::(), 629 | ); 630 | let scalars_U = once( 631 | izip!(&mu_pk, &f_poly_pk, &wt_pn) 632 | .map(|(mu_k, f_poly_k, wt_n)| { 633 | izip!(mu_k, f_poly_k) 634 | .map(|(mu, f_poly)| wt_n[3] * mu * f_poly) 635 | .sum::() 636 | }) 637 | .sum::(), 638 | ); 639 | let scalars_H_uji = (0..self.n * self.m * u_max).map(|l| { 640 | izip!(&f_puji, &x_p, &wt_pn) 641 | .map(|(f_uji, x, wt_n)| { 642 | let f = f_uji 643 | .iter() 644 | .flat_map(|f_ji| f_ji.iter()) 645 | .flat_map(|f_i| f_i.iter()) 646 | .nth(l) 647 | .unwrap(); 648 | wt_n[0] * f + wt_n[1] * f * (x - f) // Combination of terms from equations (1) & (2) 649 | }) 650 | .sum::() 651 | }); 652 | 653 | let scalars_A_p = wt_pn.iter().map(|wt_n| -wt_n[0]); 654 | let scalars_B_p = izip!(&wt_pn, &x_p).map(|(wt_n, x)| -wt_n[0] * x); 655 | let scalars_C_p = izip!(&wt_pn, &x_p).map(|(wt_n, x)| -wt_n[1] * x); 656 | let scalars_D_p = wt_pn.iter().map(|wt_n| -wt_n[1]); 657 | let scalars_X_pj = sized_flatten( 658 | izip!(&x_p, &wt_pn) 659 | .map(|(&x, wt_n)| exp_iter(x).take(self.m).map(move |xj| -wt_n[2] * xj)), 660 | ); 661 | let scalars_Y_pj = sized_flatten( 662 | izip!(&x_p, &wt_pn) 663 | .map(|(&x, wt_n)| exp_iter(x).take(self.m).map(move |xj| -wt_n[3] * xj)), 664 | ); 665 | let scalars_Z_pj = sized_flatten( 666 | izip!(&x_p, &wt_pn) 667 | .map(|(&x, wt_n)| exp_iter(x).take(self.m).map(move |xj| -wt_n[4] * xj)), 668 | ); 669 | let scalars_J_pu = sized_flatten( 670 | izip!(proofs, &wt_pn).map(|(p, wt_n)| p.zR_u.iter().map(move |zR| -wt_n[3] * zR)), 671 | ); 672 | let scalars_Q_pt = sized_flatten(izip!(proofs, &x_p, &wt_pn).map(|(p, &x, wt_n)| { 673 | repeat(-wt_n[4] * exp_iter(x).nth(self.m).unwrap()).take(p.mints.len()) 674 | })); 675 | let scalars_M_k = (0..self.ring_size()).map(|k| { 676 | izip!(&mu_pk, &f_poly_pk, &wt_pn) 677 | .map(|(mu_k, f_poly_k, wt_n)| wt_n[2] * mu_k[k] * f_poly_k[k]) 678 | .sum() 679 | }); 680 | let scalars_P_k = (0..self.ring_size()).map(|k| { 681 | izip!(&f_poly_pk, &wt_pn) 682 | .map(|(f_poly_k, wt_n)| wt_n[4] * f_poly_k[k]) 683 | .sum() 684 | }); 685 | 686 | // Chain all scalar factors into single iterator 687 | let scalars = scalars_G 688 | .chain(scalars_U) 689 | .chain(scalars_H_uji) 690 | .chain(scalars_A_p) 691 | .chain(scalars_B_p) 692 | .chain(scalars_C_p) 693 | .chain(scalars_D_p) 694 | .chain(scalars_X_pj) 695 | .chain(scalars_Y_pj) 696 | .chain(scalars_Z_pj) 697 | .chain(scalars_J_pu) 698 | .chain(scalars_Q_pt) 699 | .chain(scalars_M_k) 700 | .chain(scalars_P_k); 701 | 702 | // Evaluate everything as a single multiscalar multiplication 703 | if RistrettoPoint::vartime_multiscalar_mul(scalars, points).is_identity() { 704 | return Ok(()); 705 | } else { 706 | return Err(ArcturusError::VerificationFailed); 707 | } 708 | } 709 | } 710 | 711 | /// Derive a generator point from some other base point, without a known logarithm. 712 | /// A generator is derived by hashing 3 derivation indices (u, j, i) and the base point. 713 | /// 714 | /// For example, to derive a new generator H_123 from base point G: 715 | /// H_123 = hash(1 || 2 || 3 || G) 716 | fn derive_generator(base: &RistrettoPoint, u: u32, j: u32, i: u32) -> RistrettoPoint { 717 | let mut bytes = [0u8; 4 + 4 + 4 + 32]; 718 | bytes[0..4].copy_from_slice(&u.to_be_bytes()); 719 | bytes[4..8].copy_from_slice(&j.to_be_bytes()); 720 | bytes[8..12].copy_from_slice(&i.to_be_bytes()); 721 | bytes[12..].copy_from_slice(base.compress().as_bytes()); 722 | RistrettoPoint::hash_from_bytes::(&bytes) 723 | } 724 | 725 | fn convert_base(num: usize, base: usize, digits: usize) -> Vec { 726 | let mut x_j = vec![0usize; digits]; 727 | let mut x = num; 728 | let mut j = 0; 729 | while x > 0 { 730 | let q = x / base; 731 | x_j[j] = x - base * q; 732 | x = q; 733 | j += 1; 734 | } 735 | x_j 736 | } 737 | 738 | struct CycleTensorPolyEvals<'a> { 739 | w: usize, 740 | m: usize, 741 | n: usize, 742 | f_uji: &'a Vec>>, 743 | partial_digits_j: Vec, 744 | partial_prods_ju: Vec>, 745 | } 746 | 747 | fn cycle_tensor_poly_evals<'a>(f_uji: &'a Vec>>) -> CycleTensorPolyEvals<'a> { 748 | let w = f_uji.len(); 749 | let m = f_uji[0].len(); 750 | let n = f_uji[0][0].len(); 751 | let partial_digits_j = Vec::with_capacity(m); 752 | let partial_prods_ju = vec![vec![Scalar::one(); w]; m]; 753 | CycleTensorPolyEvals { 754 | w, 755 | m, 756 | n, 757 | f_uji, 758 | partial_digits_j, 759 | partial_prods_ju, 760 | } 761 | } 762 | 763 | impl CycleTensorPolyEvals<'_> { 764 | // Recursively multiply the factors corresponding to each digit of k 765 | fn update(&mut self) { 766 | if let Some(mut i) = self.partial_digits_j.pop() { 767 | i += 1; 768 | if i >= self.n { 769 | self.update(); 770 | } else { 771 | let j = self.partial_digits_j.len(); 772 | for u in 0..self.w { 773 | self.partial_prods_ju[j][u] = self.f_uji[u][self.m - j - 1][i]; 774 | } 775 | if j > 0 { 776 | for u in 0..self.w { 777 | let prev_part = self.partial_prods_ju[j - 1][u]; 778 | self.partial_prods_ju[j][u] *= prev_part; 779 | } 780 | } 781 | self.partial_digits_j.push(i); 782 | } 783 | } 784 | for j in self.partial_digits_j.len()..self.m { 785 | self.partial_digits_j.push(0); 786 | for u in 0..self.w { 787 | self.partial_prods_ju[j][u] = self.f_uji[u][self.m - j - 1][0]; 788 | } 789 | if j > 0 { 790 | for u in 0..self.w { 791 | let prev_part = self.partial_prods_ju[j - 1][u]; 792 | self.partial_prods_ju[j][u] *= prev_part; 793 | } 794 | } 795 | } 796 | } 797 | } 798 | 799 | impl Iterator for CycleTensorPolyEvals<'_> { 800 | type Item = Scalar; 801 | 802 | #[inline] 803 | fn next(&mut self) -> Option { 804 | self.update(); 805 | Some(self.partial_prods_ju.last().unwrap().iter().sum::()) 806 | } 807 | 808 | #[inline] 809 | fn size_hint(&self) -> (usize, Option) { 810 | (usize::max_value(), None) 811 | } 812 | } 813 | 814 | fn compute_p_j(k: usize, l: usize, a_ji: &Vec>) -> Vec { 815 | let m = a_ji.len(); 816 | let n = a_ji[0].len(); 817 | 818 | // Create polynomials for each n 819 | let mut p = Polynomial::from(Vec::with_capacity(m)); 820 | p.push(Scalar::one()); 821 | 822 | // Convert k & l to vectors of n-ary digits 823 | let k = convert_base(k, n, m); 824 | let l = convert_base(l, n, m); 825 | 826 | // Multiply each polynomial 827 | for j in 0..m { 828 | let mut f = Polynomial::from(Vec::with_capacity(m)); 829 | f.push(a_ji[j][k[j]]); 830 | if k[j] == l[j] { 831 | f.push(Scalar::one()); 832 | } 833 | p *= f; 834 | } 835 | 836 | // Resize the vectors to be `m` bits wide 837 | let mut v: Vec<_> = p.into(); 838 | v.resize_with(m, || Scalar::zero()); 839 | v 840 | } 841 | 842 | #[cfg(test)] 843 | mod tests { 844 | use super::*; 845 | use curve25519_dalek::ristretto::CompressedRistretto; 846 | use rand::rngs::OsRng; // You should use a more secure RNG 847 | 848 | const G: RistrettoPoint = constants::RISTRETTO_BASEPOINT_POINT; 849 | const H: CompressedRistretto = CompressedRistretto([ 850 | 88, 20, 136, 75, 248, 210, 190, 177, 173, 233, 152, 155, 220, 94, 58, 58, 119, 94, 58, 212, 851 | 93, 51, 131, 1, 105, 167, 53, 130, 234, 250, 194, 116, 852 | ]); 853 | const U: CompressedRistretto = CompressedRistretto([ 854 | 198, 215, 127, 137, 59, 90, 1, 165, 233, 149, 190, 90, 86, 142, 85, 187, 34, 243, 147, 30, 855 | 230, 134, 242, 78, 93, 33, 27, 238, 150, 126, 198, 109, 856 | ]); 857 | 858 | #[test] 859 | fn output_new() { 860 | let mut rng = rand::thread_rng(); 861 | let pubkey = RistrettoPoint::random(&mut rng); 862 | let commit = RistrettoPoint::random(&mut rng); 863 | let O = Output::new(pubkey, commit); 864 | assert_eq!(pubkey, O.pubkey); 865 | assert_eq!(commit, O.commit); 866 | } 867 | 868 | struct SpendSecretTest { 869 | privkey_bytes: [u8; 32], 870 | amount: u64, 871 | blind_bytes: [u8; 32], 872 | output_pubkey: CompressedRistretto, 873 | output_commit: CompressedRistretto, 874 | tag: CompressedRistretto, 875 | } 876 | const SPEND_SECRET_TESTS: [SpendSecretTest; 3] = [ 877 | SpendSecretTest { 878 | privkey_bytes: [ 879 | 199, 49, 149, 210, 237, 208, 113, 146, 229, 119, 140, 101, 71, 221, 7, 57, 192, 880 | 167, 27, 81, 96, 93, 227, 46, 52, 113, 106, 48, 108, 215, 89, 5, 881 | ], 882 | amount: 9351062461309522351, 883 | blind_bytes: [ 884 | 51, 102, 48, 203, 162, 146, 42, 217, 85, 174, 55, 26, 2, 241, 18, 49, 130, 224, 61, 885 | 241, 175, 14, 186, 90, 31, 48, 63, 54, 208, 9, 54, 7, 886 | ], 887 | output_pubkey: CompressedRistretto([ 888 | 106, 68, 82, 198, 54, 61, 203, 175, 140, 87, 223, 195, 153, 105, 142, 131, 234, 889 | 235, 94, 92, 2, 111, 161, 121, 63, 24, 117, 56, 115, 220, 90, 42, 890 | ]), 891 | output_commit: CompressedRistretto([ 892 | 248, 19, 97, 81, 254, 71, 179, 41, 172, 142, 79, 91, 19, 187, 213, 14, 155, 228, 893 | 197, 105, 14, 219, 17, 184, 37, 82, 244, 252, 235, 253, 168, 43, 894 | ]), 895 | tag: CompressedRistretto([ 896 | 224, 120, 26, 30, 161, 71, 24, 221, 142, 36, 244, 207, 142, 99, 127, 139, 7, 25, 897 | 207, 182, 0, 48, 3, 25, 96, 226, 150, 174, 93, 117, 11, 60, 898 | ]), 899 | }, 900 | SpendSecretTest { 901 | privkey_bytes: [ 902 | 59, 221, 78, 25, 134, 165, 2, 25, 75, 12, 33, 169, 176, 110, 35, 149, 150, 96, 48, 903 | 248, 246, 228, 168, 81, 199, 157, 196, 81, 106, 0, 218, 4, 904 | ], 905 | amount: 17228059461709348854, 906 | blind_bytes: [ 907 | 243, 92, 3, 227, 23, 136, 147, 135, 11, 131, 250, 56, 157, 224, 181, 245, 245, 17, 908 | 33, 87, 94, 1, 37, 61, 46, 10, 91, 80, 80, 22, 237, 3, 909 | ], 910 | output_pubkey: CompressedRistretto([ 911 | 134, 150, 244, 210, 225, 207, 126, 163, 172, 65, 0, 85, 155, 100, 90, 39, 119, 125, 912 | 223, 74, 68, 10, 73, 34, 123, 176, 179, 26, 92, 117, 175, 98, 913 | ]), 914 | output_commit: CompressedRistretto([ 915 | 48, 195, 75, 120, 80, 23, 72, 56, 46, 147, 171, 99, 196, 236, 212, 252, 53, 38, 916 | 201, 194, 220, 8, 46, 73, 123, 144, 133, 245, 56, 70, 33, 16, 917 | ]), 918 | tag: CompressedRistretto([ 919 | 132, 44, 143, 68, 212, 174, 73, 121, 148, 159, 165, 136, 61, 215, 38, 221, 198, 920 | 214, 233, 189, 149, 138, 225, 215, 236, 184, 102, 97, 84, 244, 249, 112, 921 | ]), 922 | }, 923 | SpendSecretTest { 924 | privkey_bytes: [ 925 | 193, 220, 228, 127, 249, 213, 40, 253, 151, 135, 102, 3, 35, 133, 15, 203, 212, 926 | 208, 65, 179, 204, 68, 211, 121, 251, 214, 110, 5, 29, 213, 171, 9, 927 | ], 928 | amount: 1865828838350312698, 929 | blind_bytes: [ 930 | 177, 115, 105, 217, 47, 207, 175, 228, 86, 29, 52, 219, 120, 93, 210, 10, 134, 180, 931 | 130, 246, 79, 63, 70, 243, 243, 112, 63, 232, 54, 55, 236, 12, 932 | ], 933 | output_pubkey: CompressedRistretto([ 934 | 72, 209, 22, 128, 103, 48, 98, 140, 77, 64, 201, 152, 217, 104, 133, 36, 150, 93, 935 | 218, 69, 3, 92, 7, 25, 52, 142, 222, 241, 220, 218, 127, 2, 936 | ]), 937 | output_commit: CompressedRistretto([ 938 | 76, 96, 211, 192, 179, 169, 6, 39, 142, 108, 149, 86, 56, 7, 93, 13, 228, 174, 156, 939 | 136, 156, 244, 184, 34, 84, 144, 165, 193, 217, 198, 70, 14, 940 | ]), 941 | tag: CompressedRistretto([ 942 | 68, 234, 247, 30, 128, 78, 125, 77, 151, 14, 219, 30, 14, 24, 218, 216, 173, 194, 943 | 214, 12, 11, 95, 164, 234, 226, 188, 37, 67, 74, 192, 11, 64, 944 | ]), 945 | }, 946 | ]; 947 | 948 | #[test] 949 | fn spend_secret_new() { 950 | let mut rng = rand::thread_rng(); 951 | let privkey = Scalar::random(&mut rng); 952 | let blind = Scalar::random(&mut rng); 953 | let amount: u64 = rand::random(); 954 | let ss = SpendSecret::new(privkey, amount, blind); 955 | assert_eq!(privkey, ss.privkey); 956 | assert_eq!(Scalar::from(amount), ss.amount); 957 | assert_eq!(blind, ss.blind); 958 | } 959 | 960 | #[test] 961 | fn spend_secret_output() { 962 | let mut rng = rand::thread_rng(); 963 | 964 | // Test against some random cases 965 | for _ in 0..4 { 966 | let privkey = Scalar::random(&mut rng); 967 | let blind = Scalar::random(&mut rng); 968 | let amount: u64 = rand::random(); 969 | let pubkey = privkey * G; 970 | let commit = Scalar::from(amount) * H.decompress().unwrap() + blind * G; 971 | let O = SpendSecret::new(privkey, amount, blind).output(); 972 | assert_eq!(pubkey, O.pubkey); 973 | assert_eq!(commit, O.commit); 974 | } 975 | 976 | // Test against some hard-coded cases 977 | for test in &SPEND_SECRET_TESTS { 978 | let privkey = Scalar::from_bytes_mod_order(test.privkey_bytes); 979 | let blind = Scalar::from_bytes_mod_order(test.blind_bytes); 980 | let pubkey = privkey * G; 981 | let commit = Scalar::from(test.amount) * H.decompress().unwrap() + blind * G; 982 | let O = SpendSecret::new(privkey, test.amount, blind).output(); 983 | assert_eq!(test.output_pubkey, pubkey.compress()); 984 | assert_eq!(test.output_pubkey, O.pubkey.compress()); 985 | assert_eq!(test.output_commit, commit.compress()); 986 | assert_eq!(test.output_commit, O.commit.compress()); 987 | } 988 | } 989 | 990 | #[test] 991 | fn spend_secret_tag() { 992 | let mut rng = rand::thread_rng(); 993 | 994 | // Test against some random cases 995 | for _ in 0..4 { 996 | let privkey = Scalar::random(&mut rng); 997 | let blind = Scalar::random(&mut rng); 998 | let amount: u64 = rand::random(); 999 | let ss = SpendSecret::new(privkey, amount, blind); 1000 | let tag = ss.tag(); 1001 | assert_eq!(tag, privkey.invert() * U.decompress().unwrap()); 1002 | } 1003 | 1004 | // Test against some hard-coded cases 1005 | for test in &SPEND_SECRET_TESTS { 1006 | let privkey = Scalar::from_bytes_mod_order(test.privkey_bytes); 1007 | let blind = Scalar::from_bytes_mod_order(test.blind_bytes); 1008 | let ss = SpendSecret::new(privkey, test.amount, blind); 1009 | let tag = ss.tag(); 1010 | assert_eq!(test.tag, tag.compress()); 1011 | assert_eq!(tag, privkey.invert() * U.decompress().unwrap()); 1012 | } 1013 | } 1014 | 1015 | struct MintSecretTest { 1016 | pubkey: CompressedRistretto, 1017 | amount: u64, 1018 | blind_bytes: [u8; 32], 1019 | output_pubkey: CompressedRistretto, 1020 | output_commit: CompressedRistretto, 1021 | } 1022 | const MINT_SECRET_TESTS: [MintSecretTest; 2] = [ 1023 | MintSecretTest { 1024 | pubkey: CompressedRistretto([ 1025 | 26, 180, 76, 213, 19, 22, 162, 69, 60, 240, 54, 0, 85, 11, 248, 132, 173, 38, 10, 1026 | 90, 122, 224, 11, 52, 140, 155, 139, 124, 0, 216, 125, 99, 1027 | ]), 1028 | amount: 3762547364937691008, 1029 | blind_bytes: [ 1030 | 231, 232, 180, 230, 197, 129, 36, 213, 178, 125, 90, 187, 183, 66, 63, 106, 123, 1031 | 78, 145, 202, 181, 206, 194, 92, 170, 112, 210, 228, 26, 148, 47, 12, 1032 | ], 1033 | output_pubkey: CompressedRistretto([ 1034 | 26, 180, 76, 213, 19, 22, 162, 69, 60, 240, 54, 0, 85, 11, 248, 132, 173, 38, 10, 1035 | 90, 122, 224, 11, 52, 140, 155, 139, 124, 0, 216, 125, 99, 1036 | ]), 1037 | output_commit: CompressedRistretto([ 1038 | 164, 139, 233, 64, 44, 43, 62, 12, 203, 157, 40, 25, 173, 70, 120, 126, 155, 200, 1039 | 69, 77, 135, 224, 43, 6, 151, 28, 99, 5, 123, 241, 59, 8, 1040 | ]), 1041 | }, 1042 | MintSecretTest { 1043 | pubkey: CompressedRistretto([ 1044 | 252, 184, 136, 4, 72, 247, 191, 166, 246, 20, 105, 149, 95, 28, 85, 107, 117, 36, 1045 | 122, 229, 29, 134, 201, 65, 122, 137, 194, 168, 12, 73, 115, 73, 1046 | ]), 1047 | amount: 15455118957639524404, 1048 | blind_bytes: [ 1049 | 53, 195, 30, 103, 129, 253, 249, 179, 60, 94, 58, 247, 111, 19, 245, 72, 244, 40, 1050 | 54, 63, 178, 215, 211, 2, 7, 192, 198, 27, 168, 119, 228, 9, 1051 | ], 1052 | output_pubkey: CompressedRistretto([ 1053 | 252, 184, 136, 4, 72, 247, 191, 166, 246, 20, 105, 149, 95, 28, 85, 107, 117, 36, 1054 | 122, 229, 29, 134, 201, 65, 122, 137, 194, 168, 12, 73, 115, 73, 1055 | ]), 1056 | output_commit: CompressedRistretto([ 1057 | 224, 252, 68, 165, 233, 145, 236, 149, 109, 96, 35, 1, 56, 123, 100, 221, 131, 216, 1058 | 118, 5, 0, 255, 90, 120, 100, 67, 35, 215, 34, 254, 236, 115, 1059 | ]), 1060 | }, 1061 | ]; 1062 | 1063 | #[test] 1064 | fn mint_secret_new() { 1065 | let mut rng = rand::thread_rng(); 1066 | 1067 | // Test against some random cases 1068 | for _ in 0..4 { 1069 | let pubkey = RistrettoPoint::random(&mut rng); 1070 | let amount: u64 = rand::random(); 1071 | let blind = Scalar::random(&mut rng); 1072 | let ms = MintSecret::new(pubkey, amount, blind); 1073 | assert_eq!(pubkey, ms.pubkey); 1074 | assert_eq!(Scalar::from(amount), ms.amount); 1075 | assert_eq!(blind, ms.blind); 1076 | } 1077 | 1078 | // Test against some hard-coded cases 1079 | for test in &MINT_SECRET_TESTS { 1080 | let pubkey = test.pubkey.decompress().unwrap(); 1081 | let blind = Scalar::from_bytes_mod_order(test.blind_bytes); 1082 | let ms = MintSecret::new(pubkey, test.amount, blind); 1083 | assert_eq!(test.pubkey, ms.pubkey.compress()); 1084 | assert_eq!(Scalar::from(test.amount), ms.amount); 1085 | assert_eq!(blind, ms.blind); 1086 | } 1087 | } 1088 | 1089 | #[test] 1090 | fn mint_secret_output() { 1091 | let mut rng = rand::thread_rng(); 1092 | 1093 | // Test against some random cases 1094 | for _ in 0..4 { 1095 | let pubkey = RistrettoPoint::random(&mut rng); 1096 | let amount: u64 = rand::random(); 1097 | let blind = Scalar::random(&mut rng); 1098 | let commit = Scalar::from(amount) * H.decompress().unwrap() + blind * G; 1099 | let O = MintSecret::new(pubkey, amount, blind).output(); 1100 | assert_eq!(pubkey, O.pubkey); 1101 | assert_eq!(commit, O.commit); 1102 | } 1103 | 1104 | // Test against some hard-coded cases 1105 | for test in &MINT_SECRET_TESTS { 1106 | let pubkey = test.pubkey.decompress().unwrap(); 1107 | let blind = Scalar::from_bytes_mod_order(test.blind_bytes); 1108 | let commit = Scalar::from(test.amount) * H.decompress().unwrap() + blind * G; 1109 | let O = MintSecret::new(pubkey, test.amount, blind).output(); 1110 | assert_eq!(test.output_pubkey, pubkey.compress()); 1111 | assert_eq!(test.output_pubkey, O.pubkey.compress()); 1112 | assert_eq!(test.output_commit, commit.compress()); 1113 | assert_eq!(test.output_commit, O.commit.compress()); 1114 | } 1115 | } 1116 | 1117 | #[test] 1118 | fn arcturus_proof() { 1119 | let gens = ArcturusGens::new(2, 5, 8).unwrap(); 1120 | 1121 | // Build a ring of random outputs 1122 | let mut ring = (0..gens.ring_size()) 1123 | .map(|_| { 1124 | Output::new( 1125 | RistrettoPoint::random(&mut OsRng), 1126 | RistrettoPoint::random(&mut OsRng), 1127 | ) 1128 | }) 1129 | .collect::>(); 1130 | assert!(ring.len() > 2); 1131 | 1132 | // Inject known outputs into the ring, which we can use to sign a proof 1133 | // Known outputs should just be injected at arbitrary indices. 1134 | let idxs = (2..7).collect::>(); 1135 | let mut spends = Vec::new(); 1136 | let mut mints = Vec::new(); 1137 | for &idx in idxs.iter() { 1138 | let privkey = Scalar::random(&mut OsRng); 1139 | let pubkey = RistrettoPoint::random(&mut OsRng); 1140 | let amount = 6600; 1141 | let blind_spend = Scalar::random(&mut OsRng); 1142 | let blind_mint = Scalar::random(&mut OsRng); 1143 | let spend = SpendSecret::new(privkey, amount, blind_spend); 1144 | let mint = MintSecret::new(pubkey, amount, blind_mint); 1145 | ring[idx] = spend.output(); 1146 | spends.push(spend); 1147 | mints.push(mint); 1148 | } 1149 | 1150 | // Build the proof 1151 | let proof = gens 1152 | .prove( 1153 | &mut Transcript::new(b"Arcturus-Test"), 1154 | &ring[..], 1155 | &idxs[..], 1156 | &spends[..], 1157 | &mints[..], 1158 | ) 1159 | .unwrap(); 1160 | assert_eq!(proof.count_spends(), spends.len()); 1161 | assert_eq!(proof.count_mints(), mints.len()); 1162 | for (tag, spend) in izip!(proof.tags().clone(), &spends) { 1163 | assert_eq!(tag, spend.tag()); 1164 | } 1165 | for (i, mint) in proof.mints().iter().enumerate() { 1166 | assert_eq!(mint, &mints[i].output()); 1167 | } 1168 | } 1169 | 1170 | #[test] 1171 | fn arcturus_gens_new() { 1172 | let gens = ArcturusGens::new(3, 2, 1).unwrap(); 1173 | assert_eq!(gens.n, 3); 1174 | assert_eq!(gens.m, 2); 1175 | assert_eq!(gens.w, 1); 1176 | assert_eq!( 1177 | ArcturusGens::new(1, 3, 3).unwrap_err(), 1178 | ArcturusError::ProofRadixTooSmall 1179 | ); 1180 | assert!(ArcturusGens::new(2, 3, 3).is_ok()); 1181 | assert!(ArcturusGens::new(32, 3, 3).is_ok()); 1182 | assert_eq!( 1183 | ArcturusGens::new(3, 1, 3).unwrap_err(), 1184 | ArcturusError::ProofDigitsTooSmall 1185 | ); 1186 | assert!(ArcturusGens::new(3, 2, 3).is_ok()); 1187 | assert!(ArcturusGens::new(2, 32, 3).is_ok()); 1188 | assert_eq!( 1189 | ArcturusGens::new(3, 3, 0).unwrap_err(), 1190 | ArcturusError::ProofNumSignersTooSmall 1191 | ); 1192 | assert!(ArcturusGens::new(3, 3, 1).is_ok()); 1193 | assert!(ArcturusGens::new(2, 6, 32).is_ok()); 1194 | assert!(ArcturusGens::new(2, 6, 33).is_ok()); 1195 | assert!(ArcturusGens::new(3, 3, 27).is_ok()); 1196 | assert_eq!( 1197 | ArcturusGens::new(3, 3, 28).unwrap_err(), 1198 | ArcturusError::ProofNumSignersTooLarge 1199 | ); 1200 | } 1201 | 1202 | #[test] 1203 | fn arcturus_gens_ring_size() { 1204 | let gens = ArcturusGens::new(2, 2, 1).unwrap(); 1205 | assert_eq!(gens.ring_size(), 4); 1206 | let gens = ArcturusGens::new(3, 2, 1).unwrap(); 1207 | assert_eq!(gens.ring_size(), 9); 1208 | let gens = ArcturusGens::new(4, 4, 1).unwrap(); 1209 | assert_eq!(gens.ring_size(), 256); 1210 | } 1211 | 1212 | #[test] 1213 | fn arcturus_gens_commit() { 1214 | let gens = ArcturusGens::new(2, 2, 1).unwrap(); 1215 | let A = gens.commit(&Scalar::from(100u32), &Scalar::from(10u32)); 1216 | let B = gens.commit(&Scalar::from(200u32), &Scalar::from(20u32)); 1217 | let C = gens.commit(&Scalar::from(300u32), &Scalar::from(30u32)); 1218 | assert_eq!( 1219 | A, 1220 | Scalar::from(100u32) * H.decompress().unwrap() + Scalar::from(10u32) * G 1221 | ); 1222 | assert_eq!( 1223 | B, 1224 | Scalar::from(200u32) * H.decompress().unwrap() + Scalar::from(20u32) * G 1225 | ); 1226 | assert_eq!( 1227 | C, 1228 | Scalar::from(300u32) * H.decompress().unwrap() + Scalar::from(30u32) * G 1229 | ); 1230 | assert_eq!(A + B, C); 1231 | } 1232 | 1233 | #[test] 1234 | fn prove_and_verify_5x2() { 1235 | let gens = ArcturusGens::new(5, 2, 8).unwrap(); 1236 | 1237 | // Build a random ring of outputs 1238 | let mut ring = (0..gens.ring_size()) 1239 | .map(|_| { 1240 | Output::new( 1241 | RistrettoPoint::random(&mut OsRng), 1242 | RistrettoPoint::random(&mut OsRng), 1243 | ) 1244 | }) 1245 | .collect::>(); 1246 | 1247 | // Lets build our outputs 1248 | let mut idxs_p = Vec::new(); 1249 | let mut spends_p = Vec::new(); 1250 | let mut mints_p = Vec::new(); 1251 | for p in 0..4 { 1252 | let mut idxs = Vec::new(); 1253 | for i in 0..3 { 1254 | idxs.push(p * 3 + i); 1255 | } 1256 | let mut spends = Vec::new(); 1257 | let mut mints = Vec::new(); 1258 | for &idx in &idxs { 1259 | let privkey = Scalar::random(&mut OsRng); 1260 | let pubkey = RistrettoPoint::random(&mut OsRng); 1261 | let amount = 6600; 1262 | let blind_spend = Scalar::random(&mut OsRng); 1263 | let blind_mint = Scalar::random(&mut OsRng); 1264 | let spend = SpendSecret::new(privkey, amount, blind_spend); 1265 | let mint = MintSecret::new(pubkey, amount, blind_mint); 1266 | ring[idx] = spend.output(); 1267 | spends.push(spend); 1268 | mints.push(mint); 1269 | } 1270 | idxs_p.push(idxs); 1271 | spends_p.push(spends); 1272 | mints_p.push(mints); 1273 | } 1274 | 1275 | // Test prooving and verifying individual proofs 1276 | let mut proofs = Vec::new(); 1277 | for (idxs, spends, mints) in izip!(&idxs_p, &spends_p, &mints_p) { 1278 | let proof = gens 1279 | .prove( 1280 | &mut Transcript::new(b"Arcturus-Test"), 1281 | &ring[..], 1282 | &idxs[..], 1283 | &spends[..], 1284 | &mints[..], 1285 | ) 1286 | .unwrap(); 1287 | 1288 | let mut t = Transcript::new(b"Arcturus-Test"); 1289 | assert!(gens 1290 | .verify_batch(&mut t, &ring[..], &[proof.clone()]) 1291 | .is_ok()); 1292 | proofs.push(proof); 1293 | } 1294 | 1295 | // Test batch verification 1296 | let mut t = Transcript::new(b"Arcturus-Test"); 1297 | assert!(gens.verify_batch(&mut t, &ring[..], &proofs[..]).is_ok()); 1298 | 1299 | // Build a proof with too many witnesses, and confirm error 1300 | let mut idxs = Vec::new(); 1301 | for i in 0..9 { 1302 | // One more than max witnesses 1303 | idxs.push(i); 1304 | } 1305 | let mut spends = Vec::new(); 1306 | let mut mints = Vec::new(); 1307 | for &idx in &idxs { 1308 | let privkey = Scalar::random(&mut OsRng); 1309 | let pubkey = RistrettoPoint::random(&mut OsRng); 1310 | let amount = 6600; 1311 | let blind_spend = Scalar::random(&mut OsRng); 1312 | let blind_mint = Scalar::random(&mut OsRng); 1313 | let spend = SpendSecret::new(privkey, amount, blind_spend); 1314 | let mint = MintSecret::new(pubkey, amount, blind_mint); 1315 | ring[idx] = spend.output(); 1316 | spends.push(spend); 1317 | mints.push(mint); 1318 | } 1319 | // Should error: too many witnesses 1320 | assert!(gens 1321 | .prove( 1322 | &mut Transcript::new(b"Arcturus-Test"), 1323 | &ring[..], 1324 | &idxs[..], 1325 | &spends[..], 1326 | &mints[..], 1327 | ) 1328 | .is_err()); 1329 | 1330 | idxs.pop(); 1331 | // Should error: idx.len() != spends.len() 1332 | assert!(gens 1333 | .prove( 1334 | &mut Transcript::new(b"Arcturus-Test"), 1335 | &ring[..], 1336 | &idxs[..], 1337 | &spends[..], 1338 | &mints[..], 1339 | ) 1340 | .is_err()); 1341 | 1342 | spends.pop(); 1343 | // Should error: mints and spends don't balance 1344 | assert!(gens 1345 | .prove( 1346 | &mut Transcript::new(b"Arcturus-Test"), 1347 | &ring[..], 1348 | &idxs[..], 1349 | &spends[..], 1350 | &mints[..], 1351 | ) 1352 | .is_err()); 1353 | 1354 | mints.pop(); 1355 | // Should succeed 1356 | assert!(gens 1357 | .prove( 1358 | &mut Transcript::new(b"Arcturus-Test"), 1359 | &ring[..], 1360 | &idxs[..], 1361 | &spends[..], 1362 | &mints[..], 1363 | ) 1364 | .is_ok()); 1365 | } 1366 | 1367 | #[test] 1368 | fn prove_and_verify_2x5() { 1369 | let gens = ArcturusGens::new(2, 5, 8).unwrap(); 1370 | 1371 | // Build a random ring of outputs 1372 | let mut ring = (0..gens.ring_size()) 1373 | .map(|_| { 1374 | Output::new( 1375 | RistrettoPoint::random(&mut OsRng), 1376 | RistrettoPoint::random(&mut OsRng), 1377 | ) 1378 | }) 1379 | .collect::>(); 1380 | 1381 | // Lets build our outputs 1382 | let mut idxs_p = Vec::new(); 1383 | let mut spends_p = Vec::new(); 1384 | let mut mints_p = Vec::new(); 1385 | for p in 0..4 { 1386 | let mut idxs = Vec::new(); 1387 | for i in 0..3 { 1388 | idxs.push(p * 3 + i); 1389 | } 1390 | let mut spends = Vec::new(); 1391 | let mut mints = Vec::new(); 1392 | for &idx in &idxs { 1393 | let privkey = Scalar::random(&mut OsRng); 1394 | let pubkey = RistrettoPoint::random(&mut OsRng); 1395 | let amount = 6600; 1396 | let blind_spend = Scalar::random(&mut OsRng); 1397 | let blind_mint = Scalar::random(&mut OsRng); 1398 | let spend = SpendSecret::new(privkey, amount, blind_spend); 1399 | let mint = MintSecret::new(pubkey, amount, blind_mint); 1400 | ring[idx] = spend.output(); 1401 | spends.push(spend); 1402 | mints.push(mint); 1403 | } 1404 | idxs_p.push(idxs); 1405 | spends_p.push(spends); 1406 | mints_p.push(mints); 1407 | } 1408 | 1409 | // Test prooving and verifying individual proofs 1410 | let mut proofs = Vec::new(); 1411 | for (idxs, spends, mints) in izip!(&idxs_p, &spends_p, &mints_p) { 1412 | let proof = gens 1413 | .prove( 1414 | &mut Transcript::new(b"Arcturus-Test"), 1415 | &ring[..], 1416 | &idxs[..], 1417 | &spends[..], 1418 | &mints[..], 1419 | ) 1420 | .unwrap(); 1421 | 1422 | let mut t = Transcript::new(b"Arcturus-Test"); 1423 | assert!(gens.verify_batch(&mut t, &ring, &[proof.clone()]).is_ok()); 1424 | proofs.push(proof); 1425 | } 1426 | 1427 | // Test batch verification 1428 | let mut t = Transcript::new(b"Arcturus-Test"); 1429 | assert!(gens.verify_batch(&mut t, &ring, &proofs[..]).is_ok()); 1430 | 1431 | // Build a proof with too many witnesses, and confirm error 1432 | let mut idxs = Vec::new(); 1433 | for i in 0..9 { 1434 | // One more than max witnesses 1435 | idxs.push(i); 1436 | } 1437 | let mut spends = Vec::new(); 1438 | let mut mints = Vec::new(); 1439 | for &idx in &idxs { 1440 | let privkey = Scalar::random(&mut OsRng); 1441 | let pubkey = RistrettoPoint::random(&mut OsRng); 1442 | let amount = 6600; 1443 | let blind_spend = Scalar::random(&mut OsRng); 1444 | let blind_mint = Scalar::random(&mut OsRng); 1445 | let spend = SpendSecret::new(privkey, amount, blind_spend); 1446 | let mint = MintSecret::new(pubkey, amount, blind_mint); 1447 | ring[idx] = spend.output(); 1448 | spends.push(spend); 1449 | mints.push(mint); 1450 | } 1451 | // Should error: too many witnesses 1452 | assert!(gens 1453 | .prove( 1454 | &mut Transcript::new(b"Arcturus-Test"), 1455 | &ring[..], 1456 | &idxs[..], 1457 | &spends[..], 1458 | &mints[..], 1459 | ) 1460 | .is_err()); 1461 | 1462 | idxs.pop(); 1463 | // Should error: idx.len() != spends.len() 1464 | assert!(gens 1465 | .prove( 1466 | &mut Transcript::new(b"Arcturus-Test"), 1467 | &ring[..], 1468 | &idxs[..], 1469 | &spends[..], 1470 | &mints[..], 1471 | ) 1472 | .is_err()); 1473 | 1474 | spends.pop(); 1475 | // Should error: mints and spends don't balance 1476 | assert!(gens 1477 | .prove( 1478 | &mut Transcript::new(b"Arcturus-Test"), 1479 | &ring[..], 1480 | &idxs[..], 1481 | &spends[..], 1482 | &mints[..], 1483 | ) 1484 | .is_err()); 1485 | 1486 | mints.pop(); 1487 | // Should succeed 1488 | assert!(gens 1489 | .prove( 1490 | &mut Transcript::new(b"Arcturus-Test"), 1491 | &ring[..], 1492 | &idxs[..], 1493 | &spends[..], 1494 | &mints[..], 1495 | ) 1496 | .is_ok()); 1497 | } 1498 | 1499 | #[test] 1500 | fn prove_errors() { 1501 | let gens = ArcturusGens::new(2, 5, 8).unwrap(); 1502 | 1503 | // Build a random ring of outputs 1504 | let mut ring = (0..gens.ring_size()) 1505 | .map(|_| { 1506 | Output::new( 1507 | RistrettoPoint::random(&mut OsRng), 1508 | RistrettoPoint::random(&mut OsRng), 1509 | ) 1510 | }) 1511 | .collect::>(); 1512 | 1513 | // Lets build our outputs 1514 | let mut idxs = Vec::new(); 1515 | for i in 0..3 { 1516 | idxs.push(i); 1517 | } 1518 | let mut spends = Vec::new(); 1519 | let mut mints = Vec::new(); 1520 | for &idx in &idxs { 1521 | let privkey = Scalar::random(&mut OsRng); 1522 | let pubkey = RistrettoPoint::random(&mut OsRng); 1523 | let amount = 6600; 1524 | let blind_spend = Scalar::random(&mut OsRng); 1525 | let blind_mint = Scalar::random(&mut OsRng); 1526 | let spend = SpendSecret::new(privkey, amount, blind_spend); 1527 | let mint = MintSecret::new(pubkey, amount, blind_mint); 1528 | ring[idx] = spend.output(); 1529 | spends.push(spend); 1530 | mints.push(mint); 1531 | } 1532 | 1533 | // First make sure the proof succeeds 1534 | assert!(gens 1535 | .prove( 1536 | &mut Transcript::new(b"Arcturus-Test"), 1537 | &ring[..], 1538 | &idxs[..], 1539 | &spends[..], 1540 | &mints[..], 1541 | ) 1542 | .is_ok()); 1543 | 1544 | // Now remove a spend, so that the mints and spends no longer balance. 1545 | spends.pop(); 1546 | assert!(gens 1547 | .prove( 1548 | &mut Transcript::new(b"Arcturus-Test"), 1549 | &ring[..], 1550 | &idxs[..], 1551 | &spends[..], 1552 | &mints[..], 1553 | ) 1554 | .is_err()); 1555 | } 1556 | 1557 | struct DeriveGeneratorTest { 1558 | u: u32, 1559 | j: u32, 1560 | i: u32, 1561 | generator: CompressedRistretto, 1562 | } 1563 | const DERIVE_GENERATOR_TESTS: [DeriveGeneratorTest; 6] = [ 1564 | DeriveGeneratorTest { 1565 | u: 0, 1566 | j: 0, 1567 | i: 0, 1568 | generator: CompressedRistretto([ 1569 | 88, 20, 136, 75, 248, 210, 190, 177, 173, 233, 152, 155, 220, 94, 58, 58, 119, 94, 1570 | 58, 212, 93, 51, 131, 1, 105, 167, 53, 130, 234, 250, 194, 116, 1571 | ]), 1572 | }, 1573 | DeriveGeneratorTest { 1574 | u: 1, 1575 | j: 0, 1576 | i: 0, 1577 | generator: CompressedRistretto([ 1578 | 194, 178, 89, 2, 84, 132, 230, 112, 111, 226, 126, 224, 100, 225, 99, 113, 234, 1579 | 154, 248, 153, 151, 20, 241, 215, 167, 41, 143, 186, 226, 102, 24, 102, 1580 | ]), 1581 | }, 1582 | DeriveGeneratorTest { 1583 | u: 4, 1584 | j: 2, 1585 | i: 0, 1586 | generator: CompressedRistretto([ 1587 | 190, 51, 77, 158, 51, 108, 81, 214, 169, 221, 29, 236, 210, 81, 120, 253, 195, 129, 1588 | 138, 218, 234, 193, 31, 250, 63, 255, 164, 71, 60, 86, 17, 39, 1589 | ]), 1590 | }, 1591 | DeriveGeneratorTest { 1592 | u: 0, 1593 | j: 7, 1594 | i: 0, 1595 | generator: CompressedRistretto([ 1596 | 44, 116, 102, 244, 143, 42, 93, 185, 204, 252, 252, 110, 196, 102, 171, 18, 65, 49, 1597 | 74, 10, 47, 179, 33, 117, 24, 201, 186, 113, 3, 199, 231, 113, 1598 | ]), 1599 | }, 1600 | DeriveGeneratorTest { 1601 | u: 0, 1602 | j: 99, 1603 | i: 1, 1604 | generator: CompressedRistretto([ 1605 | 182, 55, 187, 58, 199, 199, 90, 204, 88, 82, 218, 223, 188, 38, 201, 224, 102, 78, 1606 | 0, 120, 209, 78, 145, 12, 131, 99, 122, 39, 48, 105, 115, 10, 1607 | ]), 1608 | }, 1609 | DeriveGeneratorTest { 1610 | u: 0, 1611 | j: 0, 1612 | i: 8, 1613 | generator: CompressedRistretto([ 1614 | 30, 100, 155, 211, 42, 105, 108, 57, 11, 133, 58, 177, 173, 137, 199, 29, 138, 170, 1615 | 187, 139, 115, 155, 94, 70, 186, 98, 54, 104, 72, 213, 139, 108, 1616 | ]), 1617 | }, 1618 | ]; 1619 | 1620 | #[test] 1621 | fn test_derive_generator() { 1622 | for test in &DERIVE_GENERATOR_TESTS { 1623 | assert_eq!( 1624 | &test.generator, 1625 | &derive_generator(&G, test.u, test.j, test.i).compress() 1626 | ); 1627 | } 1628 | } 1629 | 1630 | struct ConvertBaseTest { 1631 | num: usize, 1632 | base: usize, 1633 | decomposed: [usize; 12], 1634 | } 1635 | const CONVERT_BASE_TESTS: [ConvertBaseTest; 8] = [ 1636 | ConvertBaseTest { 1637 | num: 131, 1638 | base: 2, 1639 | decomposed: [1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], 1640 | }, 1641 | ConvertBaseTest { 1642 | num: 131, 1643 | base: 8, 1644 | decomposed: [3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0], 1645 | }, 1646 | ConvertBaseTest { 1647 | num: 131, 1648 | base: 10, 1649 | decomposed: [1, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 1650 | }, 1651 | ConvertBaseTest { 1652 | num: 131, 1653 | base: 16, 1654 | decomposed: [3, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 1655 | }, 1656 | ConvertBaseTest { 1657 | num: 3819, 1658 | base: 2, 1659 | decomposed: [1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1], 1660 | }, 1661 | ConvertBaseTest { 1662 | num: 3819, 1663 | base: 8, 1664 | decomposed: [3, 5, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0], 1665 | }, 1666 | ConvertBaseTest { 1667 | num: 3819, 1668 | base: 10, 1669 | decomposed: [9, 1, 8, 3, 0, 0, 0, 0, 0, 0, 0, 0], 1670 | }, 1671 | ConvertBaseTest { 1672 | num: 3819, 1673 | base: 16, 1674 | decomposed: [11, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0], 1675 | }, 1676 | ]; 1677 | 1678 | #[test] 1679 | fn test_convert_base() { 1680 | for test in &CONVERT_BASE_TESTS { 1681 | assert_eq!( 1682 | &test.decomposed, 1683 | &convert_base(test.num, test.base, test.decomposed.len())[..] 1684 | ); 1685 | } 1686 | } 1687 | 1688 | #[test] 1689 | fn test_cycle_tensor_poly_evals() { 1690 | // Generate a random f_uji tensor 1691 | let mut rng = rand::thread_rng(); 1692 | let f_uji = (0..5) 1693 | .map(|_| { 1694 | (0..4) 1695 | .map(|_| (0..3).map(|_| Scalar::random(&mut rng)).collect::>()) 1696 | .collect::>() 1697 | }) 1698 | .collect::>(); 1699 | 1700 | let mut digits: Vec = vec![0; f_uji[0].len()]; 1701 | for eval in cycle_tensor_poly_evals(&f_uji).take(81) { 1702 | let mut sum = Scalar::zero(); 1703 | for u in 0..f_uji.len() { 1704 | let mut prod = Scalar::one(); 1705 | for (j, &i) in digits.iter().enumerate() { 1706 | prod *= f_uji[u][j][i]; 1707 | } 1708 | sum += prod; 1709 | } 1710 | for j in 0..digits.len() { 1711 | digits[j] += 1; 1712 | if digits[j] >= f_uji[0][0].len() { 1713 | digits[j] = 0; 1714 | } else { 1715 | break; 1716 | } 1717 | } 1718 | assert_eq!(sum, eval); 1719 | } 1720 | } 1721 | 1722 | struct ComputePJTest { 1723 | k: usize, 1724 | l: usize, 1725 | out_bytes: [[u8; 32]; 4], 1726 | } 1727 | const COMPUTE_P_J_TESTS: [ComputePJTest; 1] = [ComputePJTest { 1728 | k: 2, 1729 | l: 3, 1730 | out_bytes: [ 1731 | [ 1732 | 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1733 | 0, 0, 0, 0, 0, 1734 | ], 1735 | [ 1736 | 78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1737 | 0, 0, 0, 0, 0, 1738 | ], 1739 | [ 1740 | 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1741 | 0, 0, 0, 0, 0, 1742 | ], 1743 | [ 1744 | 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1745 | 0, 0, 0, 0, 1746 | ], 1747 | ], 1748 | }]; 1749 | 1750 | #[test] 1751 | fn test_compute_p_j() { 1752 | // Just some static a_ji 1753 | let a_ji = (1..5) 1754 | .map(|m| (1..5).map(|n| Scalar::from((m * n) as u32)).collect()) 1755 | .collect::>>(); 1756 | 1757 | // Compare hard-coded test cases evaluated using a_ji 1758 | for test in &COMPUTE_P_J_TESTS { 1759 | let scalars = compute_p_j(test.k, test.l, &a_ji); 1760 | for (bytes, scalar) in izip!(&test.out_bytes[..], scalars) { 1761 | assert_eq!(bytes, &scalar.to_bytes()); 1762 | } 1763 | } 1764 | } 1765 | } 1766 | -------------------------------------------------------------------------------- /src/transcript.rs: -------------------------------------------------------------------------------- 1 | //! Defines a `TranscriptProtocol` trait for using a Merlin transcript. 2 | use crate::errors::{ArcturusError, ArcturusResult}; 3 | 4 | use curve25519_dalek::ristretto::CompressedRistretto; 5 | use curve25519_dalek::scalar::Scalar; 6 | 7 | use merlin::Transcript; 8 | 9 | pub trait TranscriptProtocol { 10 | /// Append a domain separator for a one-of-many proof over an `n` bit set. 11 | fn arcturus_domain_sep(&mut self, n: u64, m: u64); 12 | 13 | /// Append a `scalar` with the given `label`. 14 | fn append_scalar(&mut self, label: &'static [u8], scalar: &Scalar); 15 | 16 | /// Append a `point` with the given `label`. 17 | fn append_point(&mut self, label: &'static [u8], point: &CompressedRistretto); 18 | 19 | /// Check that a point is not the identity, then append it to the 20 | /// transcript. Otherwise, return an error. 21 | fn validate_and_append_point( 22 | &mut self, 23 | label: &'static [u8], 24 | point: &CompressedRistretto, 25 | ) -> ArcturusResult<()>; 26 | 27 | /// Compute a `label`ed challenge variable. 28 | fn challenge_scalar(&mut self, label: &'static [u8]) -> Scalar; 29 | } 30 | 31 | impl TranscriptProtocol for Transcript { 32 | fn arcturus_domain_sep(&mut self, n: u64, m: u64) { 33 | self.append_message(b"dom-sep", b"Arcturus v1"); 34 | self.append_u64(b"n", n); 35 | self.append_u64(b"m", m); 36 | } 37 | 38 | fn append_scalar(&mut self, label: &'static [u8], scalar: &Scalar) { 39 | self.append_message(label, scalar.as_bytes()); 40 | } 41 | 42 | fn append_point(&mut self, label: &'static [u8], point: &CompressedRistretto) { 43 | self.append_message(label, point.as_bytes()); 44 | } 45 | 46 | fn validate_and_append_point( 47 | &mut self, 48 | label: &'static [u8], 49 | point: &CompressedRistretto, 50 | ) -> ArcturusResult<()> { 51 | use curve25519_dalek::traits::IsIdentity; 52 | 53 | if point.is_identity() { 54 | Err(ArcturusError::VerificationFailed) 55 | } else { 56 | Ok(self.append_message(label, point.as_bytes())) 57 | } 58 | } 59 | 60 | fn challenge_scalar(&mut self, label: &'static [u8]) -> Scalar { 61 | let mut buf = [0u8; 64]; 62 | self.challenge_bytes(label, &mut buf); 63 | 64 | Scalar::from_bytes_mod_order_wide(&buf) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use curve25519_dalek::scalar::Scalar; 2 | 3 | #[derive(Clone)] 4 | #[must_use = "iterators are lazy and do nothing unless consumed"] 5 | pub struct ScalarExp { 6 | x: Scalar, 7 | next_x: Scalar, 8 | } 9 | 10 | impl Iterator for ScalarExp { 11 | type Item = Scalar; 12 | 13 | #[inline] 14 | fn next(&mut self) -> Option { 15 | let x = self.next_x; 16 | self.next_x *= self.x; 17 | Some(x) 18 | } 19 | 20 | #[inline] 21 | fn size_hint(&self) -> (usize, Option) { 22 | (usize::max_value(), None) 23 | } 24 | } 25 | 26 | pub fn exp_iter(x: Scalar) -> ScalarExp { 27 | let next_x = Scalar::one(); 28 | ScalarExp { x, next_x } 29 | } 30 | 31 | #[derive(Clone, Debug)] 32 | #[must_use = "iterators are lazy and do nothing unless consumed"] 33 | pub struct SizedFlatten { 34 | outer: I, 35 | inner: Option, 36 | } 37 | 38 | impl SizedFlatten 39 | where 40 | I: Iterator, 41 | { 42 | pub fn new(iter: I) -> SizedFlatten { 43 | SizedFlatten { 44 | outer: iter, 45 | inner: None, 46 | } 47 | } 48 | } 49 | 50 | impl Iterator for SizedFlatten 51 | where 52 | I: Clone + Iterator, 53 | U: Clone + Iterator, 54 | I::Item: IntoIterator, 55 | { 56 | type Item = U::Item; 57 | 58 | #[inline] 59 | fn next(&mut self) -> Option { 60 | loop { 61 | if let Some(ref mut i) = self.inner { 62 | match i.next() { 63 | None => self.inner = None, 64 | v @ Some(_) => return v, 65 | } 66 | } 67 | match self.outer.next() { 68 | None => return None, 69 | Some(i) => self.inner = Some(i.into_iter()), 70 | } 71 | } 72 | } 73 | 74 | #[inline] 75 | fn size_hint(&self) -> (usize, Option) { 76 | let (mut lo, mut hi) = self.inner.as_ref().map_or((0, Some(0)), U::size_hint); 77 | for (l, h) in self.outer.clone().map(|i| i.into_iter().size_hint()) { 78 | lo += l; 79 | if let Some(sum) = hi { 80 | hi = match h { 81 | Some(val) => Some(sum + val), 82 | None => None, 83 | }; 84 | } 85 | } 86 | (lo, hi) 87 | } 88 | } 89 | 90 | pub fn sized_flatten(iter: I) -> SizedFlatten { 91 | SizedFlatten::new(iter) 92 | } 93 | 94 | #[macro_export] 95 | macro_rules! flatten_2d { 96 | ( $ii:expr ) => { 97 | sized_flatten($ii.into_iter().map(|v| v.iter())) 98 | }; 99 | } 100 | 101 | #[macro_export] 102 | macro_rules! flatten_3d { 103 | ( $ii:expr ) => { 104 | sized_flatten(flatten_2d!($ii).into_iter().map(|v| v.iter())) 105 | }; 106 | } 107 | 108 | #[cfg(test)] 109 | mod test { 110 | use super::*; 111 | #[cfg(not(feature = "std"))] 112 | use alloc::vec::Vec; 113 | #[cfg(feature = "std")] 114 | use std::vec::Vec; 115 | 116 | #[test] 117 | fn scalar_exp() { 118 | let mut test_rng = rand::thread_rng(); 119 | let seed = Scalar::random(&mut test_rng); 120 | let mut running_prod = Scalar::one(); 121 | 122 | // Test iterator values 123 | for exp in exp_iter(seed).take(25) { 124 | assert_eq!(running_prod, exp); 125 | running_prod *= seed; 126 | } 127 | 128 | // Test size_hint() 129 | assert_eq!((usize::max_value(), None), exp_iter(seed).size_hint()); 130 | } 131 | 132 | #[test] 133 | fn sized_flatten() { 134 | let nested = (0..5) 135 | .map(|i| (0..2).map(|j| 2 * i + j).collect()) 136 | .collect::>>(); 137 | 138 | // Test iterator values 139 | for (k, val) in SizedFlatten::new(nested.iter()).enumerate() { 140 | assert_eq!(&k, val); 141 | } 142 | 143 | // Test size_hint() 144 | assert_eq!((10, Some(10)), SizedFlatten::new(nested.iter()).size_hint()); 145 | } 146 | } 147 | --------------------------------------------------------------------------------