mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-12 22:19:26 +00:00
Move constant-time scalar mul gadget divisor creation from dkg to ec-divisors
Anyone creating a divisor for the scalar mul gadget should use constant time code, so this code should at least be in the EC gadgets crate It's of non-trivial complexity to deal with otherwise.
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2293,6 +2293,7 @@ name = "ec-divisors"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dalek-ff-group",
|
||||
"ff",
|
||||
"group",
|
||||
"hex",
|
||||
"pasta_curves",
|
||||
|
||||
@@ -35,7 +35,7 @@ impl_modulus!(
|
||||
type ResidueType = Residue<FieldModulus, { FieldModulus::LIMBS }>;
|
||||
|
||||
/// A constant-time implementation of the Ed25519 field.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)]
|
||||
pub struct FieldElement(ResidueType);
|
||||
|
||||
// Square root of -1.
|
||||
|
||||
@@ -37,7 +37,6 @@ schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "^0.5
|
||||
dleq = { path = "../dleq", version = "^0.4.1", default-features = false }
|
||||
|
||||
# eVRF DKG dependencies
|
||||
subtle = { version = "2", default-features = false, features = ["std"], optional = true }
|
||||
generic-array = { version = "1", default-features = false, features = ["alloc"], optional = true }
|
||||
blake2 = { version = "0.10", default-features = false, features = ["std"], optional = true }
|
||||
rand_chacha = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
||||
@@ -82,7 +81,6 @@ borsh = ["dep:borsh"]
|
||||
evrf = [
|
||||
"std",
|
||||
|
||||
"dep:subtle",
|
||||
"dep:generic-array",
|
||||
|
||||
"dep:blake2",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use core::{marker::PhantomData, ops::Deref, fmt};
|
||||
|
||||
use subtle::*;
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||
@@ -10,10 +9,7 @@ use generic_array::{typenum::Unsigned, ArrayLength, GenericArray};
|
||||
|
||||
use blake2::{Digest, Blake2s256};
|
||||
use ciphersuite::{
|
||||
group::{
|
||||
ff::{Field, PrimeField, PrimeFieldBits},
|
||||
Group, GroupEncoding,
|
||||
},
|
||||
group::{ff::Field, Group, GroupEncoding},
|
||||
Ciphersuite,
|
||||
};
|
||||
|
||||
@@ -24,7 +20,7 @@ use generalized_bulletproofs::{
|
||||
};
|
||||
use generalized_bulletproofs_circuit_abstraction::*;
|
||||
|
||||
use ec_divisors::{DivisorCurve, new_divisor};
|
||||
use ec_divisors::{DivisorCurve, ScalarDecomposition};
|
||||
use generalized_bulletproofs_ec_gadgets::*;
|
||||
|
||||
/// A pair of curves to perform the eVRF with.
|
||||
@@ -309,147 +305,6 @@ impl<C: EvrfCurve> Evrf<C> {
|
||||
debug_assert!(challenged_generators.next().is_none());
|
||||
}
|
||||
|
||||
/// Convert a scalar to a sequence of coefficients for the polynomial 2**i, where the sum of the
|
||||
/// coefficients is F::NUM_BITS.
|
||||
///
|
||||
/// Despite the name, the returned coefficients are not guaranteed to be bits (0 or 1).
|
||||
///
|
||||
/// This scalar will presumably be used in a discrete log proof. That requires calculating a
|
||||
/// divisor which is variable time to the amount of points interpolated. Since the amount of
|
||||
/// points interpolated is equal to the sum of the coefficients in the polynomial, we need all
|
||||
/// scalars to have a constant sum of their coefficients (instead of one variable to its bits).
|
||||
///
|
||||
/// We achieve this by finding the highest non-0 coefficient, decrementing it, and increasing the
|
||||
/// immediately less significant coefficient by 2. This increases the sum of the coefficients by
|
||||
/// 1 (-1+2=1).
|
||||
fn scalar_to_bits(scalar: &<C::EmbeddedCurve as Ciphersuite>::F) -> Vec<u64> {
|
||||
let num_bits = u64::from(<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F::NUM_BITS);
|
||||
|
||||
// Obtain the bits of the private key
|
||||
let num_bits_usize = usize::try_from(num_bits).unwrap();
|
||||
let mut decomposition = vec![0; num_bits_usize];
|
||||
for (i, bit) in scalar.to_le_bits().into_iter().take(num_bits_usize).enumerate() {
|
||||
let bit = u64::from(u8::from(bit));
|
||||
decomposition[i] = bit;
|
||||
}
|
||||
|
||||
// The following algorithm only works if the value of the scalar exceeds num_bits
|
||||
// If it isn't, we increase it by the modulus such that it does exceed num_bits
|
||||
{
|
||||
let mut less_than_num_bits = Choice::from(0);
|
||||
for i in 0 .. num_bits {
|
||||
less_than_num_bits |= scalar.ct_eq(&<C::EmbeddedCurve as Ciphersuite>::F::from(i));
|
||||
}
|
||||
let mut decomposition_of_modulus = vec![0; num_bits_usize];
|
||||
// Decompose negative one
|
||||
for (i, bit) in (-<C::EmbeddedCurve as Ciphersuite>::F::ONE)
|
||||
.to_le_bits()
|
||||
.into_iter()
|
||||
.take(num_bits_usize)
|
||||
.enumerate()
|
||||
{
|
||||
let bit = u64::from(u8::from(bit));
|
||||
decomposition_of_modulus[i] = bit;
|
||||
}
|
||||
// Increment it by one
|
||||
decomposition_of_modulus[0] += 1;
|
||||
|
||||
// Add the decomposition onto the decomposition of the modulus
|
||||
for i in 0 .. num_bits_usize {
|
||||
let new_decomposition = <_>::conditional_select(
|
||||
&decomposition[i],
|
||||
&(decomposition[i] + decomposition_of_modulus[i]),
|
||||
less_than_num_bits,
|
||||
);
|
||||
decomposition[i] = new_decomposition;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculcate the sum of the coefficients
|
||||
let mut sum_of_coefficients: u64 = 0;
|
||||
for decomposition in &decomposition {
|
||||
sum_of_coefficients += *decomposition;
|
||||
}
|
||||
|
||||
/*
|
||||
Now, because we added a log2(k)-bit number to a k-bit number, we may have our sum of
|
||||
coefficients be *too high*. We attempt to reduce the sum of the coefficients accordingly.
|
||||
|
||||
This algorithm is guaranteed to complete as expected. Take the sequence `222`. `222` becomes
|
||||
`032` becomes `013`. Even if the next coefficient in the sequence is `2`, the third
|
||||
coefficient will be reduced once and the next coefficient (`2`, increased to `3`) will only
|
||||
be eligible for reduction once. This demonstrates, even for a worst case of log2(k) `2`s
|
||||
followed by `1`s (as possible if the modulus is a Mersenne prime), the log2(k) `2`s can be
|
||||
reduced as necessary so long as there is a single coefficient after (requiring the entire
|
||||
sequence be at least of length log2(k) + 1). For a 2-bit number, log2(k) + 1 == 2, so this
|
||||
holds for any odd prime field.
|
||||
|
||||
To fully type out the demonstration for the Mersenne prime 3, with scalar to encode 1 (the
|
||||
highest value less than the number of bits):
|
||||
|
||||
10 - Little-endian bits of 1
|
||||
21 - Little-endian bits of 1, plus the modulus
|
||||
02 - After one reduction, where the sum of the coefficients does in fact equal 2 (the target)
|
||||
*/
|
||||
{
|
||||
let mut log2_num_bits = 0;
|
||||
while (1 << log2_num_bits) < num_bits {
|
||||
log2_num_bits += 1;
|
||||
}
|
||||
|
||||
for _ in 0 .. log2_num_bits {
|
||||
// If the sum of coefficients is the amount of bits, we're done
|
||||
let mut done = sum_of_coefficients.ct_eq(&num_bits);
|
||||
|
||||
for i in 0 .. (num_bits_usize - 1) {
|
||||
let should_act = (!done) & decomposition[i].ct_gt(&1);
|
||||
// Subtract 2 from this coefficient
|
||||
let amount_to_sub = <_>::conditional_select(&0, &2, should_act);
|
||||
decomposition[i] -= amount_to_sub;
|
||||
// Add 1 to the next coefficient
|
||||
let amount_to_add = <_>::conditional_select(&0, &1, should_act);
|
||||
decomposition[i + 1] += amount_to_add;
|
||||
|
||||
// Also update the sum of coefficients
|
||||
sum_of_coefficients -= <_>::conditional_select(&0, &1, should_act);
|
||||
|
||||
// If we updated the coefficients this loop iter, we're done for this loop iter
|
||||
done |= should_act;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0 .. num_bits {
|
||||
// If the sum of coefficients is the amount of bits, we're done
|
||||
let mut done = sum_of_coefficients.ct_eq(&num_bits);
|
||||
|
||||
// Find the highest coefficient currently non-zero
|
||||
for i in (1 .. decomposition.len()).rev() {
|
||||
// If this is non-zero, we should decrement this coefficient if we haven't already
|
||||
// decremented a coefficient this round
|
||||
let is_non_zero = !(0.ct_eq(&decomposition[i]));
|
||||
let should_act = (!done) & is_non_zero;
|
||||
|
||||
// Update this coefficient and the prior coefficient
|
||||
let amount_to_sub = <_>::conditional_select(&0, &1, should_act);
|
||||
decomposition[i] -= amount_to_sub;
|
||||
|
||||
let amount_to_add = <_>::conditional_select(&0, &2, should_act);
|
||||
// i must be at least 1, so i - 1 will be at least 0 (meaning it's safe to index with)
|
||||
decomposition[i - 1] += amount_to_add;
|
||||
|
||||
// Also update the sum of coefficients
|
||||
sum_of_coefficients += <_>::conditional_select(&0, &1, should_act);
|
||||
|
||||
// If we updated the coefficients this loop iter, we're done for this loop iter
|
||||
done |= should_act;
|
||||
}
|
||||
}
|
||||
debug_assert!(bool::from(decomposition.iter().sum::<u64>().ct_eq(&num_bits)));
|
||||
|
||||
decomposition
|
||||
}
|
||||
|
||||
/// Prove a point on an elliptic curve had its discrete logarithm generated via an eVRF.
|
||||
pub(crate) fn prove(
|
||||
rng: &mut (impl RngCore + CryptoRng),
|
||||
@@ -471,11 +326,9 @@ impl<C: EvrfCurve> Evrf<C> {
|
||||
|
||||
// A function to calculate a divisor and push it onto the tape
|
||||
// This defines a vec, divisor_points, outside of the fn to reuse its allocation
|
||||
let mut divisor_points =
|
||||
Vec::with_capacity((<C::EmbeddedCurve as Ciphersuite>::F::NUM_BITS as usize) + 1);
|
||||
let mut divisor =
|
||||
|vector_commitment_tape: &mut Vec<_>,
|
||||
dlog: &[u64],
|
||||
dlog: &ScalarDecomposition<<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F>,
|
||||
push_generator: bool,
|
||||
generator: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G,
|
||||
dh: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G| {
|
||||
@@ -484,24 +337,7 @@ impl<C: EvrfCurve> Evrf<C> {
|
||||
generator_tables.push(GeneratorTable::new(&curve_spec, x, y));
|
||||
}
|
||||
|
||||
{
|
||||
let mut generator = generator;
|
||||
for coefficient in dlog {
|
||||
let mut coefficient = *coefficient;
|
||||
while coefficient != 0 {
|
||||
coefficient -= 1;
|
||||
divisor_points.push(generator);
|
||||
}
|
||||
generator = generator.double();
|
||||
}
|
||||
debug_assert_eq!(
|
||||
dlog.iter().sum::<u64>(),
|
||||
u64::from(<C::EmbeddedCurve as Ciphersuite>::F::NUM_BITS)
|
||||
);
|
||||
}
|
||||
divisor_points.push(-dh);
|
||||
let mut divisor = new_divisor(&divisor_points).unwrap().normalize_x_coefficient();
|
||||
divisor_points.zeroize();
|
||||
let mut divisor = dlog.scalar_mul_divisor(generator).normalize_x_coefficient();
|
||||
|
||||
vector_commitment_tape.push(divisor.zero_coefficient);
|
||||
|
||||
@@ -540,11 +376,12 @@ impl<C: EvrfCurve> Evrf<C> {
|
||||
let evrf_public_key;
|
||||
let mut actual_coefficients = Vec::with_capacity(coefficients);
|
||||
{
|
||||
let mut dlog = Self::scalar_to_bits(evrf_private_key);
|
||||
let dlog =
|
||||
ScalarDecomposition::<<C::EmbeddedCurve as Ciphersuite>::F>::new(**evrf_private_key);
|
||||
let points = Self::transcript_to_points(transcript, coefficients);
|
||||
|
||||
// Start by pushing the discrete logarithm onto the tape
|
||||
for coefficient in &dlog {
|
||||
for coefficient in dlog.decomposition() {
|
||||
vector_commitment_tape.push(<_>::from(*coefficient));
|
||||
}
|
||||
|
||||
@@ -573,8 +410,6 @@ impl<C: EvrfCurve> Evrf<C> {
|
||||
actual_coefficients.push(res);
|
||||
}
|
||||
debug_assert_eq!(actual_coefficients.len(), coefficients);
|
||||
|
||||
dlog.zeroize();
|
||||
}
|
||||
|
||||
// Now do the ECDHs for the encryption
|
||||
@@ -595,14 +430,15 @@ impl<C: EvrfCurve> Evrf<C> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut dlog = Self::scalar_to_bits(&ecdh_private_key);
|
||||
let dlog =
|
||||
ScalarDecomposition::<<C::EmbeddedCurve as Ciphersuite>::F>::new(ecdh_private_key);
|
||||
let ecdh_commitment = <C::EmbeddedCurve as Ciphersuite>::generator() * ecdh_private_key;
|
||||
ecdh_commitments.push(ecdh_commitment);
|
||||
ecdh_commitments_xy.last_mut().unwrap()[j] =
|
||||
<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::to_xy(ecdh_commitment).unwrap();
|
||||
|
||||
// Start by pushing the discrete logarithm onto the tape
|
||||
for coefficient in &dlog {
|
||||
for coefficient in dlog.decomposition() {
|
||||
vector_commitment_tape.push(<_>::from(*coefficient));
|
||||
}
|
||||
|
||||
@@ -625,7 +461,6 @@ impl<C: EvrfCurve> Evrf<C> {
|
||||
*res += dh_x;
|
||||
|
||||
ecdh_private_key.zeroize();
|
||||
dlog.zeroize();
|
||||
}
|
||||
encryption_masks.push(res);
|
||||
}
|
||||
|
||||
@@ -14,10 +14,11 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
rand_core = { version = "0.6", default-features = false }
|
||||
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
||||
zeroize = { version = "^1.5", default-features = false, features = ["std", "zeroize_derive"] }
|
||||
|
||||
subtle = { version = "2", default-features = false, features = ["std"] }
|
||||
group = "0.13"
|
||||
ff = { version = "0.13", default-features = false, features = ["std", "bits"] }
|
||||
group = { version = "0.13", default-features = false }
|
||||
|
||||
hex = { version = "0.4", optional = true }
|
||||
dalek-ff-group = { path = "../../dalek-ff-group", features = ["std"], optional = true }
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
#![deny(missing_docs)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use subtle::{Choice, ConstantTimeEq, ConditionallySelectable};
|
||||
use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConditionallySelectable};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use group::{
|
||||
ff::{Field, PrimeField},
|
||||
ff::{Field, PrimeField, PrimeFieldBits},
|
||||
Group,
|
||||
};
|
||||
|
||||
mod poly;
|
||||
pub use poly::*;
|
||||
pub use poly::Poly;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -19,7 +20,7 @@ mod tests;
|
||||
/// A curve usable with this library.
|
||||
pub trait DivisorCurve: Group + ConstantTimeEq + ConditionallySelectable {
|
||||
/// An element of the field this curve is defined over.
|
||||
type FieldElement: PrimeField + ConditionallySelectable;
|
||||
type FieldElement: Zeroize + PrimeField + ConditionallySelectable;
|
||||
|
||||
/// The A in the curve equation y^2 = x^3 + A x + B.
|
||||
fn a() -> Self::FieldElement;
|
||||
@@ -257,6 +258,186 @@ pub fn new_divisor<C: DivisorCurve>(points: &[C]) -> Option<Poly<C::FieldElement
|
||||
Some(divisor)
|
||||
}
|
||||
|
||||
/// The decomposition of a scalar.
|
||||
///
|
||||
/// The decomposition ($d$) of a scalar ($s$) has the following two properties:
|
||||
///
|
||||
/// - $\sum^{\mathsf{NUM_BITS} - 1}_{i=0} d_i * 2^i = s$
|
||||
/// - $\sum^{\mathsf{NUM_BITS} - 1}_{i=0} d_i = \mathsf{NUM_BITS}$
|
||||
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct ScalarDecomposition<F: Zeroize + PrimeFieldBits> {
|
||||
scalar: F,
|
||||
decomposition: Vec<u64>,
|
||||
}
|
||||
|
||||
impl<F: Zeroize + PrimeFieldBits> ScalarDecomposition<F> {
|
||||
/// Decompose a scalar.
|
||||
pub fn new(scalar: F) -> Self {
|
||||
/*
|
||||
We need the sum of the coefficients to equal F::NUM_BITS. The scalar's bits will be less than
|
||||
F::NUM_BITS. Accordingly, we need to increment the sum of the coefficients without
|
||||
incrementing the scalar represented. We do this by finding the highest non-0 coefficient,
|
||||
decrementing it, and increasing the immediately less significant coefficient by 2. This
|
||||
increases the sum of the coefficients by 1 (-1+2=1).
|
||||
*/
|
||||
|
||||
let num_bits = u64::from(F::NUM_BITS);
|
||||
|
||||
// Obtain the bits of the scalar
|
||||
let num_bits_usize = usize::try_from(num_bits).unwrap();
|
||||
let mut decomposition = vec![0; num_bits_usize];
|
||||
for (i, bit) in scalar.to_le_bits().into_iter().take(num_bits_usize).enumerate() {
|
||||
let bit = u64::from(u8::from(bit));
|
||||
decomposition[i] = bit;
|
||||
}
|
||||
|
||||
// The following algorithm only works if the value of the scalar exceeds num_bits
|
||||
// If it isn't, we increase it by the modulus such that it does exceed num_bits
|
||||
{
|
||||
let mut less_than_num_bits = Choice::from(0);
|
||||
for i in 0 .. num_bits {
|
||||
less_than_num_bits |= scalar.ct_eq(&F::from(i));
|
||||
}
|
||||
let mut decomposition_of_modulus = vec![0; num_bits_usize];
|
||||
// Decompose negative one
|
||||
for (i, bit) in (-F::ONE).to_le_bits().into_iter().take(num_bits_usize).enumerate() {
|
||||
let bit = u64::from(u8::from(bit));
|
||||
decomposition_of_modulus[i] = bit;
|
||||
}
|
||||
// Increment it by one
|
||||
decomposition_of_modulus[0] += 1;
|
||||
|
||||
// Add the decomposition onto the decomposition of the modulus
|
||||
for i in 0 .. num_bits_usize {
|
||||
let new_decomposition = <_>::conditional_select(
|
||||
&decomposition[i],
|
||||
&(decomposition[i] + decomposition_of_modulus[i]),
|
||||
less_than_num_bits,
|
||||
);
|
||||
decomposition[i] = new_decomposition;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculcate the sum of the coefficients
|
||||
let mut sum_of_coefficients: u64 = 0;
|
||||
for decomposition in &decomposition {
|
||||
sum_of_coefficients += *decomposition;
|
||||
}
|
||||
|
||||
/*
|
||||
Now, because we added a log2(k)-bit number to a k-bit number, we may have our sum of
|
||||
coefficients be *too high*. We attempt to reduce the sum of the coefficients accordingly.
|
||||
|
||||
This algorithm is guaranteed to complete as expected. Take the sequence `222`. `222` becomes
|
||||
`032` becomes `013`. Even if the next coefficient in the sequence is `2`, the third
|
||||
coefficient will be reduced once and the next coefficient (`2`, increased to `3`) will only
|
||||
be eligible for reduction once. This demonstrates, even for a worst case of log2(k) `2`s
|
||||
followed by `1`s (as possible if the modulus is a Mersenne prime), the log2(k) `2`s can be
|
||||
reduced as necessary so long as there is a single coefficient after (requiring the entire
|
||||
sequence be at least of length log2(k) + 1). For a 2-bit number, log2(k) + 1 == 2, so this
|
||||
holds for any odd prime field.
|
||||
|
||||
To fully type out the demonstration for the Mersenne prime 3, with scalar to encode 1 (the
|
||||
highest value less than the number of bits):
|
||||
|
||||
10 - Little-endian bits of 1
|
||||
21 - Little-endian bits of 1, plus the modulus
|
||||
02 - After one reduction, where the sum of the coefficients does in fact equal 2 (the target)
|
||||
*/
|
||||
{
|
||||
let mut log2_num_bits = 0;
|
||||
while (1 << log2_num_bits) < num_bits {
|
||||
log2_num_bits += 1;
|
||||
}
|
||||
|
||||
for _ in 0 .. log2_num_bits {
|
||||
// If the sum of coefficients is the amount of bits, we're done
|
||||
let mut done = sum_of_coefficients.ct_eq(&num_bits);
|
||||
|
||||
for i in 0 .. (num_bits_usize - 1) {
|
||||
let should_act = (!done) & decomposition[i].ct_gt(&1);
|
||||
// Subtract 2 from this coefficient
|
||||
let amount_to_sub = <_>::conditional_select(&0, &2, should_act);
|
||||
decomposition[i] -= amount_to_sub;
|
||||
// Add 1 to the next coefficient
|
||||
let amount_to_add = <_>::conditional_select(&0, &1, should_act);
|
||||
decomposition[i + 1] += amount_to_add;
|
||||
|
||||
// Also update the sum of coefficients
|
||||
sum_of_coefficients -= <_>::conditional_select(&0, &1, should_act);
|
||||
|
||||
// If we updated the coefficients this loop iter, we're done for this loop iter
|
||||
done |= should_act;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0 .. num_bits {
|
||||
// If the sum of coefficients is the amount of bits, we're done
|
||||
let mut done = sum_of_coefficients.ct_eq(&num_bits);
|
||||
|
||||
// Find the highest coefficient currently non-zero
|
||||
for i in (1 .. decomposition.len()).rev() {
|
||||
// If this is non-zero, we should decrement this coefficient if we haven't already
|
||||
// decremented a coefficient this round
|
||||
let is_non_zero = !(0.ct_eq(&decomposition[i]));
|
||||
let should_act = (!done) & is_non_zero;
|
||||
|
||||
// Update this coefficient and the prior coefficient
|
||||
let amount_to_sub = <_>::conditional_select(&0, &1, should_act);
|
||||
decomposition[i] -= amount_to_sub;
|
||||
|
||||
let amount_to_add = <_>::conditional_select(&0, &2, should_act);
|
||||
// i must be at least 1, so i - 1 will be at least 0 (meaning it's safe to index with)
|
||||
decomposition[i - 1] += amount_to_add;
|
||||
|
||||
// Also update the sum of coefficients
|
||||
sum_of_coefficients += <_>::conditional_select(&0, &1, should_act);
|
||||
|
||||
// If we updated the coefficients this loop iter, we're done for this loop iter
|
||||
done |= should_act;
|
||||
}
|
||||
}
|
||||
debug_assert!(bool::from(decomposition.iter().sum::<u64>().ct_eq(&num_bits)));
|
||||
|
||||
ScalarDecomposition { scalar, decomposition }
|
||||
}
|
||||
|
||||
/// The decomposition of the scalar.
|
||||
pub fn decomposition(&self) -> &[u64] {
|
||||
&self.decomposition
|
||||
}
|
||||
|
||||
/// A divisor to prove a scalar multiplication.
|
||||
///
|
||||
/// The divisor will interpolate $d_i$ instances of $2^i \cdot G$ with $-(s \cdot G)$.
|
||||
///
|
||||
/// This function executes in constant time with regards to the scalar.
|
||||
///
|
||||
/// This function MAY panic if this scalar is zero.
|
||||
pub fn scalar_mul_divisor<C: Zeroize + DivisorCurve<Scalar = F>>(
|
||||
&self,
|
||||
mut generator: C,
|
||||
) -> Poly<C::FieldElement> {
|
||||
// The following for loop is constant time to the sum of `dlog`'s elements
|
||||
let mut divisor_points =
|
||||
Vec::with_capacity(usize::try_from(<C::Scalar as PrimeField>::NUM_BITS).unwrap());
|
||||
divisor_points.push(-generator * self.scalar);
|
||||
for coefficient in &self.decomposition {
|
||||
let mut coefficient = *coefficient;
|
||||
while coefficient != 0 {
|
||||
coefficient -= 1;
|
||||
divisor_points.push(generator);
|
||||
}
|
||||
generator = generator.double();
|
||||
}
|
||||
|
||||
let res = new_divisor(&divisor_points).unwrap();
|
||||
divisor_points.zeroize();
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "pasta"))]
|
||||
mod pasta {
|
||||
use group::{ff::Field, Curve};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use core::ops::{Add, Neg, Sub, Mul, Rem};
|
||||
|
||||
use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConditionallySelectable};
|
||||
use zeroize::Zeroize;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use group::ff::PrimeField;
|
||||
|
||||
@@ -30,20 +30,20 @@ impl ConstantTimeGreater for CoefficientIndex {
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure representing a Polynomial with x**i, y**i, and y**i * x**j terms.
|
||||
#[derive(Clone, Debug, Zeroize)]
|
||||
pub struct Poly<F: From<u64> + PrimeField> {
|
||||
/// c[i] * y ** (i + 1)
|
||||
/// A structure representing a Polynomial with x^i, y^i, and y^i * x^j terms.
|
||||
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct Poly<F: From<u64> + Zeroize + PrimeField> {
|
||||
/// c\[i] * y^(i + 1)
|
||||
pub y_coefficients: Vec<F>,
|
||||
/// c[i][j] * y ** (i + 1) x ** (j + 1)
|
||||
/// c\[i]\[j] * y^(i + 1) x^(j + 1)
|
||||
pub yx_coefficients: Vec<Vec<F>>,
|
||||
/// c[i] * x ** (i + 1)
|
||||
/// c\[i] * x^(i + 1)
|
||||
pub x_coefficients: Vec<F>,
|
||||
/// Coefficient for x ** 0, y ** 0, and x ** 0 y ** 0 (the coefficient for 1)
|
||||
/// Coefficient for x^0, y^0, and x^0 y^0 (the coefficient for 1)
|
||||
pub zero_coefficient: F,
|
||||
}
|
||||
|
||||
impl<F: From<u64> + PrimeField> PartialEq for Poly<F> {
|
||||
impl<F: From<u64> + Zeroize + PrimeField> PartialEq for Poly<F> {
|
||||
fn eq(&self, b: &Poly<F>) -> bool {
|
||||
{
|
||||
let mutual_y_coefficients = self.y_coefficients.len().min(b.y_coefficients.len());
|
||||
@@ -103,9 +103,9 @@ impl<F: From<u64> + PrimeField> PartialEq for Poly<F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: From<u64> + PrimeField> Poly<F> {
|
||||
impl<F: From<u64> + Zeroize + PrimeField> Poly<F> {
|
||||
/// A polynomial for zero.
|
||||
pub fn zero() -> Self {
|
||||
pub(crate) fn zero() -> Self {
|
||||
Poly {
|
||||
y_coefficients: vec![],
|
||||
yx_coefficients: vec![],
|
||||
@@ -115,7 +115,7 @@ impl<F: From<u64> + PrimeField> Poly<F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: From<u64> + PrimeField> Add<&Self> for Poly<F> {
|
||||
impl<F: From<u64> + Zeroize + PrimeField> Add<&Self> for Poly<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(mut self, other: &Self) -> Self {
|
||||
@@ -153,7 +153,7 @@ impl<F: From<u64> + PrimeField> Add<&Self> for Poly<F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: From<u64> + PrimeField> Neg for Poly<F> {
|
||||
impl<F: From<u64> + Zeroize + PrimeField> Neg for Poly<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(mut self) -> Self {
|
||||
@@ -174,7 +174,7 @@ impl<F: From<u64> + PrimeField> Neg for Poly<F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: From<u64> + PrimeField> Sub for Poly<F> {
|
||||
impl<F: From<u64> + Zeroize + PrimeField> Sub for Poly<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self {
|
||||
@@ -182,7 +182,7 @@ impl<F: From<u64> + PrimeField> Sub for Poly<F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: From<u64> + PrimeField> Mul<F> for Poly<F> {
|
||||
impl<F: From<u64> + Zeroize + PrimeField> Mul<F> for Poly<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(mut self, scalar: F) -> Self {
|
||||
@@ -202,7 +202,7 @@ impl<F: From<u64> + PrimeField> Mul<F> for Poly<F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: From<u64> + PrimeField> Poly<F> {
|
||||
impl<F: From<u64> + Zeroize + PrimeField> Poly<F> {
|
||||
#[must_use]
|
||||
fn shift_by_x(mut self, power_of_x: usize) -> Self {
|
||||
if power_of_x == 0 {
|
||||
@@ -256,14 +256,14 @@ impl<F: From<u64> + PrimeField> Poly<F> {
|
||||
self.zero_coefficient = F::ZERO;
|
||||
|
||||
// Move the x coefficients
|
||||
self.yx_coefficients[power_of_y - 1] = self.x_coefficients;
|
||||
std::mem::swap(&mut self.yx_coefficients[power_of_y - 1], &mut self.x_coefficients);
|
||||
self.x_coefficients = vec![];
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: From<u64> + PrimeField> Mul<&Poly<F>> for Poly<F> {
|
||||
impl<F: From<u64> + Zeroize + PrimeField> Mul<&Poly<F>> for Poly<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: &Self) -> Self {
|
||||
@@ -290,7 +290,7 @@ impl<F: From<u64> + PrimeField> Mul<&Poly<F>> for Poly<F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: From<u64> + PrimeField> Poly<F> {
|
||||
impl<F: From<u64> + Zeroize + PrimeField> Poly<F> {
|
||||
// The leading y coefficient and associated x coefficient.
|
||||
fn leading_coefficient(&self) -> (usize, usize) {
|
||||
if self.y_coefficients.len() > self.yx_coefficients.len() {
|
||||
@@ -355,7 +355,7 @@ impl<F: From<u64> + PrimeField> Poly<F> {
|
||||
|
||||
/// Perform multiplication mod `modulus`.
|
||||
#[must_use]
|
||||
pub fn mul_mod(self, other: &Self, modulus: &Self) -> Self {
|
||||
pub(crate) fn mul_mod(self, other: &Self, modulus: &Self) -> Self {
|
||||
(self * other) % modulus
|
||||
}
|
||||
|
||||
@@ -366,10 +366,13 @@ impl<F: From<u64> + PrimeField> Poly<F> {
|
||||
///
|
||||
/// Panics upon division by a polynomial where all coefficients are zero.
|
||||
#[must_use]
|
||||
pub fn div_rem(self, denominator: &Self) -> (Self, Self) {
|
||||
pub(crate) fn div_rem(self, denominator: &Self) -> (Self, Self) {
|
||||
// These functions have undefined, unsafe behavior if this isn't a valid index
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
fn ct_get<'a, F: From<u64> + PrimeField>(poly: &'a Poly<F>, coeff: CoefficientIndex) -> &'a F {
|
||||
fn ct_get<'a, F: From<u64> + Zeroize + PrimeField>(
|
||||
poly: &'a Poly<F>,
|
||||
coeff: CoefficientIndex,
|
||||
) -> &'a F {
|
||||
let y_pow = isize::try_from(coeff.y_pow).unwrap();
|
||||
let x_pow = isize::try_from(coeff.x_pow).unwrap();
|
||||
|
||||
@@ -415,14 +418,14 @@ impl<F: From<u64> + PrimeField> Poly<F> {
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
fn ct_get_mut<'a, F: From<u64> + PrimeField>(
|
||||
fn ct_get_mut<'a, F: From<u64> + Zeroize + PrimeField>(
|
||||
poly: &'a mut Poly<F>,
|
||||
coeff: CoefficientIndex,
|
||||
) -> &'a mut F {
|
||||
unsafe { (ct_get(poly, coeff) as *const F as *mut F).as_mut().unwrap() }
|
||||
}
|
||||
|
||||
fn structurally_eq<F: From<u64> + PrimeField>(a: &Poly<F>, b: &Poly<F>) -> bool {
|
||||
fn structurally_eq<F: From<u64> + Zeroize + PrimeField>(a: &Poly<F>, b: &Poly<F>) -> bool {
|
||||
if a.y_coefficients.len() != b.y_coefficients.len() {
|
||||
return false;
|
||||
}
|
||||
@@ -440,7 +443,7 @@ impl<F: From<u64> + PrimeField> Poly<F> {
|
||||
true
|
||||
}
|
||||
|
||||
fn conditional_select_poly<F: From<u64> + PrimeField>(
|
||||
fn conditional_select_poly<F: From<u64> + Zeroize + PrimeField>(
|
||||
mut a: Poly<F>,
|
||||
b: &Poly<F>,
|
||||
choice: Choice,
|
||||
@@ -655,7 +658,7 @@ impl<F: From<u64> + PrimeField> Poly<F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: From<u64> + PrimeField> Rem<&Self> for Poly<F> {
|
||||
impl<F: From<u64> + Zeroize + PrimeField> Rem<&Self> for Poly<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn rem(self, modulus: &Self) -> Self {
|
||||
@@ -663,10 +666,10 @@ impl<F: From<u64> + PrimeField> Rem<&Self> for Poly<F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: From<u64> + PrimeField> Poly<F> {
|
||||
impl<F: From<u64> + Zeroize + PrimeField> Poly<F> {
|
||||
/// Evaluate this polynomial with the specified x/y values.
|
||||
///
|
||||
/// Panics on polynomials with terms whose powers exceed 2**64.
|
||||
/// Panics on polynomials with terms whose powers exceed 2^64.
|
||||
#[must_use]
|
||||
pub fn eval(&self, x: F, y: F) -> F {
|
||||
let mut res = self.zero_coefficient;
|
||||
@@ -693,7 +696,7 @@ impl<F: From<u64> + PrimeField> Poly<F> {
|
||||
res
|
||||
}
|
||||
|
||||
/// Differentiate a polynomial, reduced by a modulus with a leading y term y**2 x**0, by x and y.
|
||||
/// Differentiate a polynomial, reduced by a modulus with a leading y term y^2 x^0, by x and y.
|
||||
///
|
||||
/// This function has undefined behavior if unreduced.
|
||||
#[must_use]
|
||||
|
||||
Reference in New Issue
Block a user