Skip to content

Commit b80896e

Browse files
committed
Compress EC subgroup points before serialising
1 parent 6ab0bc5 commit b80896e

File tree

2 files changed

+82
-14
lines changed

2 files changed

+82
-14
lines changed

src/backends/plonky2/primitives/ec/curve.rs

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ use std::{
1111
use num::{bigint::BigUint, Num, One};
1212
use plonky2::{
1313
field::{
14-
extension::{quintic::QuinticExtension, Extendable, FieldExtension},
14+
extension::{quintic::QuinticExtension, Extendable, FieldExtension, Frobenius},
1515
goldilocks_field::GoldilocksField,
1616
ops::Square,
17-
types::{Field, PrimeField},
17+
types::{Field, Field64, PrimeField},
1818
},
1919
hash::poseidon::PoseidonHash,
2020
iop::{generator::SimpleGenerator, target::BoolTarget, witness::WitnessWrite},
@@ -35,6 +35,30 @@ use crate::backends::plonky2::{
3535

3636
type ECField = QuinticExtension<GoldilocksField>;
3737

38+
/// Computes sqrt in ECField as sqrt(x) = sqrt(x^r)/x^((r-1)/2) with r
39+
/// = 1 + p + ... + p^4, where the numerator involves a sqrt in
40+
/// GoldilocksField, cf.
41+
/// https://github.com/pornin/ecgfp5/blob/ce059c6d1e1662db437aecbf3db6bb67fe63c716/rust/src/field.rs#L1041
42+
pub fn ec_field_sqrt(x: &ECField) -> Option<ECField> {
43+
// Compute x^r.
44+
let x_to_the_r = (0..5)
45+
.map(|i| x.repeated_frobenius(i))
46+
.reduce(|a, b| a * b)
47+
.expect("Iterator should be nonempty.");
48+
let num = QuinticExtension([
49+
x_to_the_r.0[0].sqrt()?,
50+
GoldilocksField::ZERO,
51+
GoldilocksField::ZERO,
52+
GoldilocksField::ZERO,
53+
GoldilocksField::ZERO,
54+
]);
55+
// Compute x^((r-1)/2) = x^(p*((1+p)/2)*(1+p^2))
56+
let x1 = x.frobenius();
57+
let x2 = x1.exp_u64((1 + GoldilocksField::ORDER) / 2);
58+
let den = x2 * x2.repeated_frobenius(2);
59+
Some(num / den)
60+
}
61+
3862
fn ec_field_to_bytes(x: &ECField) -> Vec<u8> {
3963
x.0.iter()
4064
.flat_map(|f| {
@@ -78,14 +102,39 @@ impl Point {
78102
pub fn as_fields(&self) -> Vec<crate::middleware::F> {
79103
self.x.0.iter().chain(self.u.0.iter()).cloned().collect()
80104
}
81-
pub fn as_bytes(&self) -> Vec<u8> {
82-
[ec_field_to_bytes(&self.x), ec_field_to_bytes(&self.u)].concat()
105+
pub fn compress_from_subgroup(&self) -> Result<ECField, Error> {
106+
match self.is_in_subgroup() {
107+
true => Ok(self.u),
108+
false => Err(Error::custom(format!(
109+
"Point must lie in EC subgroup: ({}, {})",
110+
self.x, self.u
111+
))),
112+
}
113+
}
114+
pub fn decompress_into_subgroup(u: &ECField) -> Result<Self, Error> {
115+
if u == &ECField::ZERO {
116+
return Ok(Self::ZERO);
117+
}
118+
// Figure out x.
119+
let b = ECField::TWO - ECField::ONE / (u.square());
120+
let d = b.square() - ECField::TWO.square() * Self::b();
121+
let alpha = ECField::NEG_ONE * b / ECField::TWO;
122+
let beta = ec_field_sqrt(&d)
123+
.ok_or(Error::custom(format!("Not a quadratic residue: {}", d)))?
124+
/ ECField::TWO;
125+
let mut points = [ECField::ONE, ECField::NEG_ONE].into_iter().map(|s| Point {
126+
x: alpha + s * beta,
127+
u: *u,
128+
});
129+
points.find(|p| p.is_in_subgroup()).ok_or(Error::custom(
130+
"One of the points must lie in the EC subgroup.".into(),
131+
))
132+
}
133+
pub fn as_bytes_from_subgroup(&self) -> Result<Vec<u8>, Error> {
134+
self.compress_from_subgroup().map(|u| ec_field_to_bytes(&u))
83135
}
84-
pub fn from_bytes(b: &[u8]) -> Result<Self, Error> {
85-
let x_bytes = &b[..40];
86-
let u_bytes = &b[40..];
87-
ec_field_from_bytes(x_bytes)
88-
.and_then(|x| ec_field_from_bytes(u_bytes).map(|u| Self { x, u }))
136+
pub fn from_bytes_into_subgroup(b: &[u8]) -> Result<Self, Error> {
137+
ec_field_from_bytes(b).and_then(|u| Self::decompress_into_subgroup(&u))
89138
}
90139
}
91140

@@ -648,7 +697,12 @@ mod test {
648697
use num::{BigUint, FromPrimitive};
649698
use num_bigint::RandBigInt;
650699
use plonky2::{
651-
field::{goldilocks_field::GoldilocksField, types::Field},
700+
field::{
701+
extension::quintic::QuinticExtension,
702+
goldilocks_field::GoldilocksField,
703+
ops::Square,
704+
types::{Field, Sample},
705+
},
652706
iop::witness::PartialWitness,
653707
plonk::{
654708
circuit_builder::CircuitBuilder, circuit_data::CircuitConfig,
@@ -659,7 +713,9 @@ mod test {
659713

660714
use crate::backends::plonky2::primitives::ec::{
661715
bits::CircuitBuilderBits,
662-
curve::{CircuitBuilderElliptic, ECField, Point, WitnessWriteCurve, GROUP_ORDER},
716+
curve::{
717+
ec_field_sqrt, CircuitBuilderElliptic, ECField, Point, WitnessWriteCurve, GROUP_ORDER,
718+
},
663719
};
664720

665721
#[test]
@@ -688,6 +744,13 @@ mod test {
688744
assert_eq!(p2, p3);
689745
}
690746

747+
#[test]
748+
fn test_sqrt() {
749+
let x = QuinticExtension::rand().square();
750+
let y = ec_field_sqrt(&x);
751+
assert_eq!(y.map(|a| a.square()), Some(x));
752+
}
753+
691754
#[test]
692755
fn test_associativity() {
693756
let g = Point::generator();

src/backends/plonky2/signedpod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ impl SignedPod {
138138
let signer_bytes = deserialize_bytes(&data.signer)?;
139139
let signature_bytes = deserialize_bytes(&data.signature)?;
140140

141-
if signer_bytes.len() != 80 {
141+
if signer_bytes.len() != 40 {
142142
return Err(Error::custom(
143143
"Invalid byte encoding of signed POD signer.".to_string(),
144144
));
@@ -149,7 +149,7 @@ impl SignedPod {
149149
));
150150
}
151151

152-
let signer = Point::from_bytes(&signer_bytes)?;
152+
let signer = Point::from_bytes_into_subgroup(&signer_bytes)?;
153153
let signature = Signature::from_bytes(&signature_bytes)?;
154154

155155
Ok(Box::new(Self {
@@ -197,7 +197,12 @@ impl Pod for SignedPod {
197197
}
198198

199199
fn serialize_data(&self) -> serde_json::Value {
200-
let signer = serialize_bytes(&self.signer.as_bytes());
200+
let signer = serialize_bytes(
201+
&self
202+
.signer
203+
.as_bytes_from_subgroup()
204+
.expect("Signer public key must lie in EC subgroup."),
205+
);
201206
let signature = serialize_bytes(&self.signature.as_bytes());
202207
serde_json::to_value(Data {
203208
signer,

0 commit comments

Comments
 (0)