Skip to content

Commit

Permalink
Merge pull request #51 from zkcrypto/batch-from-bytes
Browse files Browse the repository at this point in the history
Add `jubjub::AffinePoint::batch_from_bytes` API
  • Loading branch information
str4d committed Aug 10, 2021
2 parents f192388 + d3fe3eb commit 96ab416
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 1 deletion.
5 changes: 5 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Unreleased
## Added
- `jubjub::AffinePoint::batch_from_bytes`, which enables the inversion inside
`jubjub::AffinePoint::from_bytes` to be batched.

# 0.7.0
## Security
- A bug in the `jubjub::{AffinePoint, ExtendedPoint, SubgroupPoint}::from_bytes`
Expand Down
107 changes: 106 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
// operators, and so this lint is triggered unnecessarily.
#![allow(clippy::suspicious_arithmetic_impl)]

#[cfg(feature = "alloc")]
extern crate alloc;

#[cfg(test)]
#[macro_use]
extern crate std;
Expand All @@ -46,6 +49,9 @@ use group::{
use rand_core::RngCore;
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};

#[cfg(feature = "alloc")]
use alloc::vec::Vec;

#[cfg(feature = "alloc")]
use group::WnafGroup;

Expand Down Expand Up @@ -522,6 +528,102 @@ impl AffinePoint {
})
}

/// Attempts to interpret a batch of byte representations of affine points.
///
/// Returns None for each element if it is not on the curve, or is non-canonical
/// according to ZIP 216.
#[cfg(feature = "alloc")]
pub fn batch_from_bytes(items: impl Iterator<Item = [u8; 32]>) -> Vec<CtOption<Self>> {
#[derive(Clone, Copy, Default)]
struct Item {
sign: u8,
v: Fq,
numerator: Fq,
denominator: Fq,
}

impl ConditionallySelectable for Item {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
Item {
sign: u8::conditional_select(&a.sign, &b.sign, choice),
v: Fq::conditional_select(&a.v, &b.v, choice),
numerator: Fq::conditional_select(&a.numerator, &b.numerator, choice),
denominator: Fq::conditional_select(&a.denominator, &b.denominator, choice),
}
}
}

let items = items.map(|mut b| {
// Grab the sign bit from the representation
let sign = b[31] >> 7;

// Mask away the sign bit
b[31] &= 0b0111_1111;

// Interpret what remains as the v-coordinate
Fq::from_bytes(&b).map(|v| {
// -u^2 + v^2 = 1 + d.u^2.v^2
// -u^2 = 1 + d.u^2.v^2 - v^2 (rearrange)
// -u^2 - d.u^2.v^2 = 1 - v^2 (rearrange)
// u^2 + d.u^2.v^2 = v^2 - 1 (flip signs)
// u^2 (1 + d.v^2) = v^2 - 1 (factor)
// u^2 = (v^2 - 1) / (1 + d.v^2) (isolate u^2)
// We know that (1 + d.v^2) is nonzero for all v:
// (1 + d.v^2) = 0
// d.v^2 = -1
// v^2 = -(1 / d) No solutions, as -(1 / d) is not a square

let v2 = v.square();

Item {
v,
sign,
numerator: (v2 - Fq::one()),
denominator: Fq::one() + EDWARDS_D * v2,
}
})
});

let mut acc = Fq::one();
let mut tmp = Vec::with_capacity(items.size_hint().0);
for item in items {
tmp.push((acc, item));
acc *= item.map(|item| item.denominator).unwrap_or(Fq::one());
}
acc = acc.invert().unwrap();

let mut ret: Vec<_> = tmp
.into_iter()
.rev()
.map(|(tmp, item)| {
let ret = item.and_then(
|Item {
v, sign, numerator, ..
}| {
let inv_denominator = tmp * acc;
(numerator * inv_denominator).sqrt().and_then(|u| {
// Fix the sign of `u` if necessary
let flip_sign = Choice::from((u.to_bytes()[0] ^ sign) & 1);
let u_negated = -u;
let final_u = Fq::conditional_select(&u, &u_negated, flip_sign);

// If u == 0, flip_sign == sign_bit. We therefore want to reject the
// encoding as non-canonical if all of the following occur:
// - u == 0
// - flip_sign == true
let u_is_zero = u.ct_eq(&Fq::zero());
CtOption::new(AffinePoint { u: final_u, v }, !(u_is_zero & flip_sign))
})
},
);
acc *= item.map(|item| item.denominator).unwrap_or(Fq::one());
ret
})
.collect();
ret.reverse();
ret
}

/// Returns the `u`-coordinate of this point.
pub fn get_u(&self) -> Fq {
self.u
Expand Down Expand Up @@ -1786,12 +1888,15 @@ fn test_serialization_consistency() {
],
];

for expected_serialized in v {
let batched = AffinePoint::batch_from_bytes(v.iter().cloned());

for (expected_serialized, batch_deserialized) in v.into_iter().zip(batched.into_iter()) {
assert!(p.is_on_curve_vartime());
let affine = AffinePoint::from(p);
let serialized = affine.to_bytes();
let deserialized = AffinePoint::from_bytes(serialized).unwrap();
assert_eq!(affine, deserialized);
assert_eq!(affine, batch_deserialized.unwrap());
assert_eq!(expected_serialized, serialized);
p += gen;
}
Expand Down

0 comments on commit 96ab416

Please sign in to comment.