Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windowing algorithm for point multiplication #215

Merged
merged 23 commits into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]
### Added
- [\#215](https://github.com/Manta-Network/manta-rs/pull/215) Add windowed multiplication algorithm for groups.
- [\#213](https://github.com/Manta-Network/manta-rs/pull/197) Add Ceremony Utilities
- [\#206](https://github.com/Manta-Network/manta-rs/pull/206) Move Poseidon sage script to test the hardcoded round constant values.
- [\#172](https://github.com/Manta-Network/manta-rs/pull/172) Add abstract Phase 2 for Groth16 trusted setup
Expand Down
4 changes: 2 additions & 2 deletions manta-accounting/src/fs/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1309,14 +1309,14 @@ pub mod test {
&mut F::options()
.create_new(true)
.write(true)
.open(&path, password)
.open(path, password)
.expect("Unable to create file for writing."),
))
.expect("Unable to serialize and encrypt the data.");
let decrypted_data = T::deserialize(&mut Deserializer::new(
&mut F::options()
.read(true)
.open(&path, password)
.open(path, password)
.expect("Unable to open file for reading."),
))
.expect("Unable to decrypt and deserialize the data.");
Expand Down
176 changes: 174 additions & 2 deletions manta-crypto/src/algebra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
//! Algebraic Constructions

use crate::{
eclair::alloc::Constant,
eclair::{
alloc::Constant,
bool::{Bool, ConditionalSelect},
num::Zero,
Has,
},
key,
rand::{RngCore, Sample},
};
Expand All @@ -32,9 +37,32 @@ use manta_util::{
use manta_util::serde::{Deserialize, Serialize};

/// Group
pub trait Group<COM = ()> {
pub trait Group<COM = ()>: Sized {
/// Adds `rhs` to `self` in the group.
fn add(&self, rhs: &Self, compiler: &mut COM) -> Self;

/// Adds `rhs` to `self` in the group and assigns the result to `self`.
#[inline]
fn add_assign(&mut self, rhs: &Self, compiler: &mut COM) -> &mut Self {
*self = self.add(rhs, compiler);
self
}

/// Doubles `self` in the group and assigns the result to `self`.
#[inline]
fn double_assign(&mut self, compiler: &mut COM) -> &mut Self {
*self = self.add(self, compiler);
self
}

/// Doubles `self` `k` times in the group and assigns the result to `self`.
#[inline]
fn repeated_double_assign(&mut self, k: usize, compiler: &mut COM) -> &mut Self {
for _ in 0..k {
self.double_assign(compiler);
}
self
}
}

/// Ring
Expand Down Expand Up @@ -113,6 +141,116 @@ impl<G, const N: usize> PrecomputedBaseTable<G, N> {
}
}

/// Group Element Table for Windowed Point Multiplication
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct Window<G> {
/// Multiplication Table
table: Vec<G>,
}

impl<G> Window<G> {
/// Creates a new [`Window`] from `table` without checking its correctness.
#[inline]
pub fn new_unchecked(table: Vec<G>) -> Self {
Self { table }
}

/// Creates a new [`Window`] table by repeatedly adding `point` to itself to
/// support a table with windowed multiplication with `window_size`.
///
/// # Panics
///
/// This method panics if `window_size` is less than `1`.
///
/// # Implementation Note
///
/// Windowed multiplication consists of `B/n` rounds, where `B` is the number of
/// bits of a group element and `n` is the window size. Creating the table costs
/// `2^n - 2` additions in the group, and each round involves `1` table look-up,
/// `n` doublings and `1` addition. Asymptotically, the optimal window size is
/// `n = 2`.
#[inline]
pub fn new<COM>(window_size: usize, point: G, compiler: &mut COM) -> Self
where
G: Clone + Group<COM> + Zero<COM>,
{
assert!(window_size > 0, "Window size must be at least 1.");
let table_length = 2usize.pow(window_size as u32);
SupremoUGH marked this conversation as resolved.
Show resolved Hide resolved
let mut table = Vec::with_capacity(table_length);
table.push(G::zero(compiler));
table.push(point.clone());
for _ in 2..table_length {
table.push(table.last().unwrap().add(&point, compiler));
}
Self::new_unchecked(table)
}

/// Returns the window size.
#[inline]
pub fn window_size(&self) -> usize {
self.table.len().trailing_zeros() as usize
}

/// Returns a shared reference to the multiplication table.
#[inline]
pub fn table(&self) -> &[G] {
&self.table
}

/// Returns the multiplication table, dropping `self`.
#[inline]
pub fn into_inner(self) -> Vec<G> {
self.table
}

/// Doubles `result`, `window_size`-many times and then adds the element from `table` corresponding to `chunk`.
#[inline]
fn scalar_mul_round<COM>(
window_size: usize,
table: &[G],
chunk: &[&Bool<COM>],
result: &mut G,
compiler: &mut COM,
) where
G: Clone + ConditionalSelect<COM> + Group<COM>,
COM: Has<bool>,
{
let chunk = chunk.iter().copied().rev();
SupremoUGH marked this conversation as resolved.
Show resolved Hide resolved
let selected_element = G::select_from_table(chunk, table, compiler);
result
.repeated_double_assign(window_size, compiler)
.add_assign(&selected_element, compiler);
}

/// Multiplies a point in G by `scalar` using `self` as the window table.
///
/// # Implementation Note
///
/// For `scalar_mul` to be compatible with `ConditionalSelect::select_from_table` and `Window::new`,
/// `bits` must be in the big-endian representation.
#[inline]
pub fn scalar_mul<'b, B, COM>(&self, bits: B, compiler: &mut COM) -> G
where
Bool<COM>: 'b,
B: IntoIterator<Item = &'b Bool<COM>>,
COM: Has<bool>,
G: Clone + ConditionalSelect<COM> + Group<COM> + Zero<COM>,
{
let bit_vector = bits.into_iter().collect::<Vec<_>>(); // TODO: Switch to chunking iterator.
let window_size = self.window_size();
let mut result = G::zero(compiler);
let mut iter_chunks = bit_vector.chunks_exact(window_size);
for chunk in &mut iter_chunks {
Self::scalar_mul_round(window_size, &self.table, chunk, &mut result, compiler);
}
let remainder = iter_chunks.remainder();
let last_window_size = remainder.len();
let subtable = &self.table[0..1 << last_window_size];
Self::scalar_mul_round(last_window_size, subtable, remainder, &mut result, compiler);
result
}
}

/// Diffie-Hellman Key Agreement Scheme
#[cfg_attr(
feature = "serde",
Expand Down Expand Up @@ -308,3 +446,37 @@ pub mod security {
/// ```
pub trait DecisionalDiffieHellmanHardness: ComputationalDiffieHellmanHardness {}
}

/// Testing Framework
#[cfg(feature = "test")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "test")))]
pub mod test {
use super::*;
use crate::eclair::{bool::Assert, cmp::PartialEq};

/// Tests if windowed scalar multiplication of the bit decomposition of `scalar` with `point` returns the
/// product `scalar` * `point`
#[inline]
pub fn window_correctness<S, G, F, B, COM>(
window_size: usize,
scalar: &S,
point: G,
bit_conversion: F,
compiler: &mut COM,
) where
G: Clone
+ ConditionalSelect<COM>
+ PartialEq<G, COM>
+ ScalarMulGroup<S, COM, Output = G>
+ Zero<COM>,
F: FnOnce(&S, &mut COM) -> B,
B: IntoIterator<Item = Bool<COM>>,
COM: Assert,
{
let product = point.scalar_mul(scalar, compiler);
let window = Window::new(window_size, point, compiler);
let bits = Vec::from_iter(bit_conversion(scalar, compiler)); // TODO: use iter instead of Vec.
let windowed_product = window.scalar_mul(&bits, compiler);
product.assert_equal(&windowed_product, compiler);
SupremoUGH marked this conversation as resolved.
Show resolved Hide resolved
}
}
43 changes: 43 additions & 0 deletions manta-crypto/src/eclair/bool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
//! [`Bool`].

use crate::eclair::{cmp::PartialEq, Has, Type};
use alloc::vec::Vec;
use manta_util::{iter::IteratorExt, vec::VecExt};

/// Boolean Type Inside of the Compiler
pub type Bool<COM = ()> = Type<COM, bool>;
Expand Down Expand Up @@ -96,6 +98,47 @@ where
{
/// Selects `true_value` when `bit == true` and `false_value` when `bit == false`.
fn select(bit: &Bool<COM>, true_value: &Self, false_value: &Self, compiler: &mut COM) -> Self;

/// Selects an element from `table` by repeated iteration of `select` over `bits`.
/// The `bits` are ordered from most significant to least significant, forming unsigned
/// integers in binary representation which are understood as the `table` indices.
#[inline]
fn select_from_table<'s, B, T>(bits: B, table: T, compiler: &mut COM) -> Self
SupremoUGH marked this conversation as resolved.
Show resolved Hide resolved
where
Self: 's + Clone,
Bool<COM>: 's,
B: IntoIterator<Item = &'s Bool<COM>>,
B::IntoIter: ExactSizeIterator,
T: IntoIterator<Item = &'s Self>,
T::IntoIter: ExactSizeIterator,
{
let mut table = table.into_iter();
let mut bits = bits.into_iter();
assert_eq!(
table.len(),
1 << bits.len(),
"Table length must equal 2^(number of bits)."
);
if let Some(first_bit) = bits.next() {
let mut table = table
.chunk_by()
.map(|[x, y]| Self::select(first_bit, y, x, compiler))
.collect::<Vec<_>>();
for bit in bits {
table = table
.into_iter()
.chunk_by()
.map(|[x, y]| Self::select(bit, &y, &x, compiler))
.collect();
}
table.take_first()
} else {
table
.next()
.expect("Table of length 1 always has one element.")
.clone()
}
}
}

/// Implements [`ConditionalSelect`] for the given `$type`.
Expand Down
2 changes: 1 addition & 1 deletion manta-crypto/src/merkle_tree/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ where
InnerDigest<C>: 'i,
I: IntoIterator<Item = &'i InnerDigest<C>>,
{
let mut iter = iter.into_iter().peekable();
let mut iter = iter.into_iter();
(depth..path_length::<C, _>()).fold(base, move |acc, _| {
Self::fold_fn(parameters, index.into_parent(), &acc, default, || {
iter.next().unwrap()
Expand Down
2 changes: 1 addition & 1 deletion manta-parameters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ pub mod github {
P: AsRef<Path>,
{
let path = path.as_ref();
download_unchecked(branch, data_path, &path)?;
download_unchecked(branch, data_path, path)?;
anyhow::ensure!(
verify_file(path, checksum)?,
"Checksum did not match. Expected: {:?}",
Expand Down
16 changes: 9 additions & 7 deletions manta-pay/src/crypto/constraint/arkworks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ use manta_crypto::{
algebra,
arkworks::{
ff::{Field, FpParameters, PrimeField, ToConstraintField},
r1cs_std::{alloc::AllocVar, eq::EqGadget, select::CondSelectGadget, ToBitsGadget},
r1cs_std::{
alloc::AllocVar, eq::EqGadget, fields::FieldVar, select::CondSelectGadget, ToBitsGadget,
},
relations::{
ns,
r1cs::{
Expand Down Expand Up @@ -681,11 +683,12 @@ where

/// Conditionally select from `lhs` and `rhs` depending on the value of `bit`.
#[inline]
fn conditionally_select<F>(bit: &Boolean<F>, lhs: &FpVar<F>, rhs: &FpVar<F>) -> FpVar<F>
pub fn conditionally_select<F, T>(bit: &Boolean<F>, lhs: &T, rhs: &T) -> T
where
F: PrimeField,
T: CondSelectGadget<F>,
{
FpVar::conditionally_select(bit, lhs, rhs)
CondSelectGadget::conditionally_select(bit, lhs, rhs)
.expect("Conditionally selecting from two values is not allowed to fail.")
}

Expand Down Expand Up @@ -741,14 +744,13 @@ where
#[inline]
fn zero(compiler: &mut R1CS<F>) -> Self {
let _ = compiler;
FpVar::Constant(F::zero())
FieldVar::zero()
}

#[inline]
fn is_zero(&self, compiler: &mut R1CS<F>) -> Self::Verification {
let _ = compiler;
self.is_eq(&FpVar::Constant(F::zero()))
.expect("Comparison with zero is not allowed to fail.")
FieldVar::is_zero(self).expect("Comparison with zero is not allowed to fail.")
}
}

Expand Down Expand Up @@ -817,7 +819,7 @@ mod tests {
R: RngCore + ?Sized,
F: PrimeField,
{
let bound = Fp(F::from(2u64).pow(&[BITS as u64]));
let bound = Fp(F::from(2u64).pow([BITS as u64]));
check_assert_within_range::<_, BITS>(Fp(F::zero()), true);
check_assert_within_range::<_, BITS>(Fp(bound.0 - F::one()), true);
check_assert_within_range::<_, BITS>(bound, false);
Expand Down
Loading