@@ -9,18 +9,20 @@ use std::{
99} ;
1010
1111use num:: { bigint:: BigUint , Num , One } ;
12+ use num_bigint:: RandBigInt ;
1213use plonky2:: {
1314 field:: {
14- extension:: { quintic:: QuinticExtension , Extendable , FieldExtension } ,
15+ extension:: { quintic:: QuinticExtension , Extendable , FieldExtension , Frobenius } ,
1516 goldilocks_field:: GoldilocksField ,
1617 ops:: Square ,
17- types:: { Field , PrimeField } ,
18+ types:: { Field , Field64 , PrimeField } ,
1819 } ,
1920 hash:: poseidon:: PoseidonHash ,
2021 iop:: { generator:: SimpleGenerator , target:: BoolTarget , witness:: WitnessWrite } ,
2122 plonk:: circuit_builder:: CircuitBuilder ,
2223 util:: serialization:: { Read , Write } ,
2324} ;
25+ use rand:: rngs:: OsRng ;
2426use serde:: { Deserialize , Serialize } ;
2527
2628use crate :: backends:: plonky2:: {
@@ -35,6 +37,30 @@ use crate::backends::plonky2::{
3537
3638type ECField = QuinticExtension < GoldilocksField > ;
3739
40+ /// Computes sqrt in ECField as sqrt(x) = sqrt(x^r)/x^((r-1)/2) with r
41+ /// = 1 + p + ... + p^4, where the numerator involves a sqrt in
42+ /// GoldilocksField, cf.
43+ /// https://github.com/pornin/ecgfp5/blob/ce059c6d1e1662db437aecbf3db6bb67fe63c716/rust/src/field.rs#L1041
44+ pub fn ec_field_sqrt ( x : & ECField ) -> Option < ECField > {
45+ // Compute x^r.
46+ let x_to_the_r = ( 0 ..5 )
47+ . map ( |i| x. repeated_frobenius ( i) )
48+ . reduce ( |a, b| a * b)
49+ . expect ( "Iterator should be nonempty." ) ;
50+ let num = QuinticExtension ( [
51+ x_to_the_r. 0 [ 0 ] . sqrt ( ) ?,
52+ GoldilocksField :: ZERO ,
53+ GoldilocksField :: ZERO ,
54+ GoldilocksField :: ZERO ,
55+ GoldilocksField :: ZERO ,
56+ ] ) ;
57+ // Compute x^((r-1)/2) = x^(p*((1+p)/2)*(1+p^2))
58+ let x1 = x. frobenius ( ) ;
59+ let x2 = x1. exp_u64 ( ( 1 + GoldilocksField :: ORDER ) / 2 ) ;
60+ let den = x2 * x2. repeated_frobenius ( 2 ) ;
61+ Some ( num / den)
62+ }
63+
3864fn ec_field_to_bytes ( x : & ECField ) -> Vec < u8 > {
3965 x. 0 . iter ( )
4066 . flat_map ( |f| {
@@ -75,17 +101,45 @@ pub struct Point {
75101}
76102
77103impl Point {
104+ pub fn new_rand_from_subgroup ( ) -> Self {
105+ & OsRng . gen_biguint_below ( & GROUP_ORDER ) * Self :: generator ( )
106+ }
78107 pub fn as_fields ( & self ) -> Vec < crate :: middleware:: F > {
79108 self . x . 0 . iter ( ) . chain ( self . u . 0 . iter ( ) ) . cloned ( ) . collect ( )
80109 }
81- pub fn as_bytes ( & self ) -> Vec < u8 > {
82- [ ec_field_to_bytes ( & self . x ) , ec_field_to_bytes ( & self . u ) ] . concat ( )
110+ pub fn compress_from_subgroup ( & self ) -> Result < ECField , Error > {
111+ match self . is_in_subgroup ( ) {
112+ true => Ok ( self . u ) ,
113+ false => Err ( Error :: custom ( format ! (
114+ "Point must lie in EC subgroup: ({}, {})" ,
115+ self . x, self . u
116+ ) ) ) ,
117+ }
83118 }
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 } ) )
119+ pub fn decompress_into_subgroup ( u : & ECField ) -> Result < Self , Error > {
120+ if u == & ECField :: ZERO {
121+ return Ok ( Self :: ZERO ) ;
122+ }
123+ // Figure out x.
124+ let b = ECField :: TWO - ECField :: ONE / ( u. square ( ) ) ;
125+ let d = b. square ( ) - ECField :: TWO . square ( ) * Self :: b ( ) ;
126+ let alpha = ECField :: NEG_ONE * b / ECField :: TWO ;
127+ let beta = ec_field_sqrt ( & d)
128+ . ok_or ( Error :: custom ( format ! ( "Not a quadratic residue: {}" , d) ) ) ?
129+ / ECField :: TWO ;
130+ let mut points = [ ECField :: ONE , ECField :: NEG_ONE ] . into_iter ( ) . map ( |s| Point {
131+ x : alpha + s * beta,
132+ u : * u,
133+ } ) ;
134+ points. find ( |p| p. is_in_subgroup ( ) ) . ok_or ( Error :: custom (
135+ "One of the points must lie in the EC subgroup." . into ( ) ,
136+ ) )
137+ }
138+ pub fn as_bytes_from_subgroup ( & self ) -> Result < Vec < u8 > , Error > {
139+ self . compress_from_subgroup ( ) . map ( |u| ec_field_to_bytes ( & u) )
140+ }
141+ pub fn from_bytes_into_subgroup ( b : & [ u8 ] ) -> Result < Self , Error > {
142+ ec_field_from_bytes ( b) . and_then ( |u| Self :: decompress_into_subgroup ( & u) )
89143 }
90144}
91145
@@ -648,7 +702,12 @@ mod test {
648702 use num:: { BigUint , FromPrimitive } ;
649703 use num_bigint:: RandBigInt ;
650704 use plonky2:: {
651- field:: { goldilocks_field:: GoldilocksField , types:: Field } ,
705+ field:: {
706+ extension:: quintic:: QuinticExtension ,
707+ goldilocks_field:: GoldilocksField ,
708+ ops:: Square ,
709+ types:: { Field , Sample } ,
710+ } ,
652711 iop:: witness:: PartialWitness ,
653712 plonk:: {
654713 circuit_builder:: CircuitBuilder , circuit_data:: CircuitConfig ,
@@ -657,9 +716,15 @@ mod test {
657716 } ;
658717 use rand:: rngs:: OsRng ;
659718
660- use crate :: backends:: plonky2:: primitives:: ec:: {
661- bits:: CircuitBuilderBits ,
662- curve:: { CircuitBuilderElliptic , ECField , Point , WitnessWriteCurve , GROUP_ORDER } ,
719+ use crate :: backends:: plonky2:: {
720+ primitives:: ec:: {
721+ bits:: CircuitBuilderBits ,
722+ curve:: {
723+ ec_field_sqrt, CircuitBuilderElliptic , ECField , Point , WitnessWriteCurve ,
724+ GROUP_ORDER ,
725+ } ,
726+ } ,
727+ Error ,
663728 } ;
664729
665730 #[ test]
@@ -688,6 +753,13 @@ mod test {
688753 assert_eq ! ( p2, p3) ;
689754 }
690755
756+ #[ test]
757+ fn test_sqrt ( ) {
758+ let x = QuinticExtension :: rand ( ) . square ( ) ;
759+ let y = ec_field_sqrt ( & x) ;
760+ assert_eq ! ( y. map( |a| a. square( ) ) , Some ( x) ) ;
761+ }
762+
691763 #[ test]
692764 fn test_associativity ( ) {
693765 let g = Point :: generator ( ) ;
@@ -728,6 +800,23 @@ mod test {
728800 assert ! ( !not_sub. is_in_subgroup( ) ) ;
729801 }
730802
803+ #[ test]
804+ fn test_roundtrip_compression ( ) -> Result < ( ) , Error > {
805+ ( 0 ..10 ) . try_for_each ( |_| {
806+ let p = Point :: new_rand_from_subgroup ( ) ;
807+ let p_compressed = p. compress_from_subgroup ( ) ?;
808+ let q = Point :: decompress_into_subgroup ( & p_compressed) ?;
809+
810+ match p == q {
811+ true => Ok ( ( ) ) ,
812+ false => Err ( Error :: custom ( format ! (
813+ "Roundtrip compression failed: {:?} ≠ {:?}" ,
814+ p, q
815+ ) ) ) ,
816+ }
817+ } )
818+ }
819+
731820 #[ test]
732821 fn test_double_circuit ( ) -> Result < ( ) , anyhow:: Error > {
733822 let config = CircuitConfig :: standard_recursion_config ( ) ;
0 commit comments