Skip to content

Commit

Permalink
Change to the new range definition. [some proof creation fails]
Browse files Browse the repository at this point in the history
  • Loading branch information
fjarri committed Dec 17, 2024
1 parent 341d39d commit b9ed866
Showing 1 changed file with 121 additions and 127 deletions.
248 changes: 121 additions & 127 deletions synedrion/src/uint/secret_signed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,16 @@ where
CtOption::new(value, self.in_bound(bound))
}

/// Asserts that the value is within the interval the paper denotes as $\pm 2^exp$.
/// Asserts that the value is within the interval the paper denotes as $±2^exp$.
/// Panics if it is not the case.
///
/// That is, the value must be within $[-2^{exp}, 2^{exp}]$
/// (See Section 2).
/// That is, the value must be within $[-2^{exp-1}+1, 2^{exp-1}]$
/// (See Section 3, Groups & Fields).
pub fn assert_exponent_range(&self, exp: u32) {
let in_bound = self.in_bound(exp);
// Have to check for the ends of the range too
let is_end = self.abs().expose_secret().ct_eq(&(T::one() << exp));
assert!(bool::from(in_bound | is_end), "out of bounds $\\pm 2^{exp}$",)
let in_bound = self.in_bound(exp - 1);
// Have to check for the high end of the range too
let is_high_end = self.abs().expose_secret().ct_eq(&(T::one() << (exp - 1))) & !self.is_negative();
assert!(bool::from(in_bound | is_high_end), "out of bounds $±2^{exp}$",)
}
}

Expand Down Expand Up @@ -256,102 +256,77 @@ where

impl<T> SecretSigned<T>
where
T: ConditionallySelectable + Zeroize + Integer + Bounded + RandomMod,
T: Zeroize + Integer + Bounded + RandomMod,
{
// Returns a random value in range `[-range, range]`.
//
// Note: variable time in bit size of `range`.
fn random_in_range(rng: &mut impl CryptoRngCore, range: &NonZero<T>) -> Self {
let range_bits = range.as_ref().bits_vartime();
/// Returns a random value in range $±2^{exp}$ as defined by the paper, that is
/// sampling from $[-2^{exp-1}+1, 2^{exp-1}]$ (See Section 3, Groups & Fields).
///
/// Note: variable time in `exp`.
pub fn random_in_exp_range(rng: &mut impl CryptoRngCore, exp: u32) -> Self {
assert!(exp > 0, "`exp` must be greater than zero");
assert!(
range_bits < T::BITS,
"Out of bounds: range_bits was {} but must be smaller or equal to {}",
range_bits,
T::BITS - 1
exp < T::BITS,
"Out of bounds: `exp` was {exp} but must be smaller or equal to {}",
T::BITS
);
// Will not overflow because of the assertion above
let positive_bound = range
.as_ref()
.overflowing_shl_vartime(1)
.expect("Just asserted that range is smaller than precision; qed")
.checked_add(&T::one())
.expect("Checked bounds above");
let positive_result = Secret::init_with(|| {
T::random_mod(
rng,
&NonZero::new(positive_bound).expect("the range is non-zero by construction"),
)
});

// Sampling in range `[0, 2^exp)` and translating to the desired range by subtracting `2^{exp-1}-1`.
let positive_bound = NonZero::new(
T::one()
.overflowing_shl_vartime(exp)
.expect("does not overflow because of the assertions above"),
)
.expect("non-zero as long as `exp` doesn't overflow, which was checked above");
let shift = T::one()
.overflowing_shl_vartime(exp - 1)
.expect("does not overflow because of the assertions above")
.checked_sub(&T::one())
.expect("does not overflow because of the assertions above");
let positive_result = Secret::init_with(|| T::random_mod(rng, &positive_bound));
Self::new_from_unsigned_unchecked(
Secret::init_with(|| positive_result.expose_secret().wrapping_sub(range.as_ref())),
range_bits,
Secret::init_with(|| positive_result.expose_secret().wrapping_sub(&shift)),
exp,
)
}

/// Returns a random value in range `[-2^bound_bits, 2^bound_bits]`.
///
/// Note: variable time in `bound_bits`.
pub fn random_in_exp_range(rng: &mut impl CryptoRngCore, range_bits: u32) -> Self {
assert!(
range_bits < T::BITS - 1,
"Out of bounds: bound_bits was {} but must be smaller than {}",
range_bits,
T::BITS - 1
);

let bound = NonZero::new(T::one() << range_bits).expect("Checked bound_bits just above; qed");
Self::random_in_range(rng, &bound)
}
}

impl<T> SecretSigned<T>
where
T: Zeroize + Integer + Bounded + HasWide,
T::Wide: Zeroize + ConditionallySelectable + Bounded + RandomMod,
T::Wide: Zeroize + Bounded + RandomMod,
{
/// Returns a random value in range `[-2^bound_bits * scale, 2^bound_bits * scale]`.
/// Returns a random value in range $±2^{exp} scale$ as defined by the paper, that is
/// sampling from $[-scale (2^{exp-1}+1), scale 2^{exp-1}]$ (See Section 3, Groups & Fields).
///
/// Note: variable time in `bound_bits` and bit size of `scale`.
pub fn random_in_exp_range_scaled(
rng: &mut impl CryptoRngCore,
bound_bits: u32,
scale: &T,
) -> SecretSigned<T::Wide> {
/// Note: variable time in `exp` and bit size of `scale`.
pub fn random_in_exp_range_scaled(rng: &mut impl CryptoRngCore, exp: u32, scale: &T) -> SecretSigned<T::Wide> {
assert!(exp > 0, "`exp` must be greater than zero");
assert!(
bound_bits < T::BITS - 1,
"Out of bounds: bound_bits was {} but must be smaller than {}",
bound_bits,
T::BITS - 1
exp < T::BITS,
"Out of bounds: `exp` was {exp} but must be smaller than {}",
T::BITS
);
let scaled_bound: <T as HasWide>::Wide = scale

// Sampling in range `[0, scale * 2^exp)` and translating to the desired range
// by subtracting `scale * 2^{exp-1}-1`.
let positive_bound = NonZero::new(
scale
.to_wide()
.overflowing_shl_vartime(exp)
.expect("`2^exp` fits into `T`, so the result fits into `T::Wide`"),
)
.expect("non-zero as long as `exp` doesn't overflow, which was checked above");
let shift = scale
.to_wide()
.overflowing_shl_vartime(bound_bits)
.expect("Just asserted that bound bits is smaller than T's bit precision");

// Sampling in range [0, 2^bound_bits * scale * 2 + 1) and translating to the desired range.
let positive_bound = scaled_bound
.overflowing_shl_vartime(1)
.expect(concat![
"`scaled_bound` is double the size of a T; we asserted that the `bound_bits` ",
"will not cause overflow in T ⇒ it's safe to left-shift 1 step ",
"(aka multiply by 2)."
])
.checked_add(&T::Wide::one())
.expect(concat![
"`scaled_bound` is double the size of a T; we asserted that the `bound_bits` ",
"will not cause overflow in T ⇒ it's safe to add 1."
]);
let positive_result = Secret::init_with(|| {
T::Wide::random_mod(
rng,
&NonZero::new(positive_bound)
.expect("Input guaranteed to be positive and it's non-zero because we added 1"),
)
});
let value = Secret::init_with(|| positive_result.expose_secret().wrapping_sub(&scaled_bound));

SecretSigned::new_from_unsigned_unchecked(value, bound_bits + scale.bits_vartime())
.overflowing_shl_vartime(exp - 1)
.expect("`2^exp` fits into `T`, so the result fits into `T::Wide`")
.checked_sub(&T::Wide::one())
.expect("does not overflow because of the assertions above");

let positive_result = Secret::init_with(|| T::Wide::random_mod(rng, &positive_bound));
let value = Secret::init_with(|| positive_result.expose_secret().wrapping_sub(&shift));

SecretSigned::new_from_unsigned_unchecked(value, exp + scale.bits_vartime())
}
}

Expand All @@ -361,48 +336,42 @@ where
T::Wide: Zeroize + HasWide,
<T::Wide as HasWide>::Wide: Zeroize + ConditionallySelectable + Bounded,
{
/// Returns a random value in range `[-2^bound_bits * scale, 2^bound_bits * scale]`.
/// Returns a random value in range $±2^{exp} scale$ as defined by the paper, that is
/// sampling from $[-scale (2^{exp-1}+1), scale 2^{exp-1}]$ (See Section 3, Groups & Fields).
///
/// Note: variable time in `bound_bits` and `scale`.
/// Note: variable time in `exp` and bit size of `scale`.
pub fn random_in_exp_range_scaled_wide(
rng: &mut impl CryptoRngCore,
bound_bits: u32,
exp: u32,
scale: &T::Wide,
) -> SecretSigned<<T::Wide as HasWide>::Wide> {
assert!(exp > 0, "`exp` must be greater than zero");
assert!(
bound_bits < T::BITS - 1,
"Out of bounds: bound_bits was {} but must be smaller than {}",
bound_bits,
T::BITS - 1
exp < T::BITS,
"Out of bounds: `exp` was {exp} but must be smaller than {}",
T::BITS
);
let scaled_bound = scale

// Sampling in range `[0, scale * 2^exp)` and translating to the desired range
// by subtracting `scale * 2^{exp-1}-1`.
let positive_bound = NonZero::new(
scale
.to_wide()
.overflowing_shl_vartime(exp)
.expect("`2^exp` fits into `T`, so the result fits into `T::Wide::Wide`"),
)
.expect("non-zero as long as `exp` doesn't overflow, which was checked above");
let shift = scale
.to_wide()
.overflowing_shl_vartime(bound_bits)
.expect("Just asserted that bound_bits is smaller than bit precision of T");

// Sampling in range [0, 2^bound_bits * scale * 2 + 1) and translating to the desired range.
let positive_bound = scaled_bound
.overflowing_shl_vartime(1)
.expect(concat![
"`scaled_bound` is double the size of a T::Wide; we asserted that the `bound_bits` ",
"will not cause overflow in T::Wide ⇒ it's safe to left-shift 1 step ",
"(aka multiply by 2)."
])
.checked_add(&<T::Wide as HasWide>::Wide::one())
.expect(concat![
"`scaled_bound` is double the size of a T::Wide; we asserted that the `bound_bits` ",
"will not cause overflow in T::Wide ⇒ it's safe to add 1."
]);
let positive_result = Secret::init_with(|| {
<T::Wide as HasWide>::Wide::random_mod(
rng,
&NonZero::new(positive_bound)
.expect("Input guaranteed to be positive and it's non-zero because we added 1"),
)
});
let result = Secret::init_with(|| positive_result.expose_secret().wrapping_sub(&scaled_bound));

SecretSigned::new_from_unsigned_unchecked(result, bound_bits + scale.bits_vartime())
.overflowing_shl_vartime(exp - 1)
.expect("`2^exp` fits into `T`, so the result fits into `T::Wide::Wide`")
.checked_sub(&<T::Wide as HasWide>::Wide::one())
.expect("does not overflow because of the assertions above");

let positive_result = Secret::init_with(|| <T::Wide as HasWide>::Wide::random_mod(rng, &positive_bound));
let value = Secret::init_with(|| positive_result.expose_secret().wrapping_sub(&shift));

SecretSigned::new_from_unsigned_unchecked(value, exp + scale.bits_vartime())
}
}

Expand Down Expand Up @@ -625,21 +594,46 @@ mod tests {
#[test]
fn random_bounded_bits_is_sane() {
let mut rng = ChaCha8Rng::seed_from_u64(SEED);
for bound_bits in 1..U1024::BITS - 1 {
let signed: SecretSigned<U1024> = SecretSigned::random_in_exp_range(&mut rng, bound_bits);
assert!(*signed.abs().expose_secret() < U1024::MAX >> (U1024::BITS - 1 - bound_bits));
signed.assert_exponent_range(bound_bits);
for exp in [1, 2, 3, U1024::BITS - 1] {
let signed: SecretSigned<U1024> = SecretSigned::random_in_exp_range(&mut rng, exp);
let value = *signed.abs().expose_secret();
let bound = U1024::ONE << (exp - 1);
assert!(value < bound || (value == bound && (!signed.is_negative()).into()));
}
}

#[test]
fn exponent_range() {
// If the exponential bound is 3, in the paper definition $∈ ±2^3$ is $∈ [-3, 4]$.

// 3 is fine
let signed = test_new_from_unsigned(U1024::from_u8(3), 2).unwrap();
signed.assert_exponent_range(3);

// -3 is fine
signed.neg().assert_exponent_range(3);

// 4 is fine
let signed = test_new_from_unsigned(U1024::from_u8(4), 3).unwrap();
signed.assert_exponent_range(3);
}

#[test]
#[should_panic(expected = "out of bounds $±2^3$")]
fn exponent_bound_panics() {
// -4 is out of $∈ ±2^3$ range
let signed = test_new_from_unsigned(U1024::from_u8(4), 3).unwrap();
signed.neg().assert_exponent_range(3);
}

#[test]
fn signed_with_low_bounds() {
// a 2 bit bound means numbers must be smaller or equal to 3
let bound = 2;
let value = U1024::from_u8(3);
let signed = test_new_from_unsigned(value, bound).unwrap();
assert!(*signed.abs().expose_secret() < U1024::MAX >> (U1024::BITS - 1 - bound));
signed.assert_exponent_range(bound);
assert!(bool::from(signed.ensure_bound(bound).is_some()));
// 4 is too big
let value = U1024::from_u8(4);
let signed = test_new_from_unsigned(value, bound);
Expand All @@ -650,7 +644,7 @@ mod tests {
let value = U1024::from_u8(1);
let signed = test_new_from_unsigned(value, bound).unwrap();
assert!(*signed.abs().expose_secret() < U1024::MAX >> (U1024::BITS - 1 - bound));
signed.assert_exponent_range(bound);
assert!(bool::from(signed.ensure_bound(bound).is_some()));
// 2 is too big
let value = U1024::from_u8(2);
let signed = test_new_from_unsigned(value, bound);
Expand All @@ -661,7 +655,7 @@ mod tests {
let value = U1024::from_u8(0);
let signed = test_new_from_unsigned(value, bound).unwrap();
assert!(*signed.abs().expose_secret() < U1024::MAX >> (U1024::BITS - 1 - bound));
signed.assert_exponent_range(bound);
assert!(bool::from(signed.ensure_bound(bound).is_some()));
// 1 is too big
let value = U1024::from_u8(1);
let signed = test_new_from_unsigned(value, bound);
Expand Down

0 comments on commit b9ed866

Please sign in to comment.