1
1
use super :: * ;
2
- use crate :: { MontgomeryPoint , Scalar } ;
2
+ use crate :: scalar:: test:: BASEPOINT_ORDER_MINUS_ONE ;
3
+ use crate :: { traits:: IsIdentity , MontgomeryPoint } ;
3
4
4
- use rand:: { thread_rng, Rng } ;
5
+ use rand:: Rng ;
6
+ use rand_core:: { CryptoRng , RngCore } ;
7
+
8
+ // Generates a new Keypair using, and returns the public key representative
9
+ // along, with its public key as a newly allocated edwards25519.Point.
10
+ fn generate < R : RngCore + CryptoRng > ( rng : & mut R ) -> ( [ u8 ; 32 ] , EdwardsPoint ) {
11
+ for _ in 0 ..63 {
12
+ let y_sk = rng. gen :: < [ u8 ; 32 ] > ( ) ;
13
+ let y_sk_tweak = rng. next_u32 ( ) as u8 ;
14
+
15
+ let y_repr_bytes = match Randomized :: to_representative ( & y_sk, y_sk_tweak) . into ( ) {
16
+ Some ( r) => r,
17
+ None => continue ,
18
+ } ;
19
+ let y_pk = Randomized :: mul_base_clamped ( y_sk) ;
20
+
21
+ assert_eq ! (
22
+ MontgomeryPoint :: from_representative:: <Randomized >( & y_repr_bytes)
23
+ . expect( "failed to re-derive point from representative" ) ,
24
+ y_pk. to_montgomery( )
25
+ ) ;
26
+
27
+ return ( y_repr_bytes, y_pk) ;
28
+ }
29
+ panic ! ( "failed to generate a valid keypair" ) ;
30
+ }
31
+
32
+ /// Returns a new edwards25519.Point that is v multiplied by the subgroup order.
33
+ ///
34
+ /// BASEPOINT_ORDER_MINUS_ONE is the same as scMinusOne in filippo.io/edwards25519.
35
+ /// https://github.com/FiloSottile/edwards25519/blob/v1.0.0/scalar.go#L34
36
+ fn scalar_mult_order ( v : & EdwardsPoint ) -> EdwardsPoint {
37
+ // v * (L - 1) + v => v * L
38
+ let p = v * BASEPOINT_ORDER_MINUS_ONE ;
39
+ p + v
40
+ }
5
41
6
42
#[ test]
7
43
#[ cfg( feature = "elligator2" ) ]
@@ -30,45 +66,6 @@ use rand::{thread_rng, Rng};
30
66
// work around this by multiplying the point by L - 1, then adding the
31
67
// point once to the product.
32
68
fn pubkey_subgroup_check ( ) {
33
- // This is the same as scMinusOne in filippo.io/edwards25519.
34
- // https://github.com/FiloSottile/edwards25519/blob/v1.0.0/scalar.go#L34
35
- let scalar_order_minus1 = Scalar :: from_canonical_bytes ( [
36
- 236_u8 , 211 , 245 , 92 , 26 , 99 , 18 , 88 , 214 , 156 , 247 , 162 , 222 , 249 , 222 , 20 , 0 , 0 , 0 , 0 , 0 ,
37
- 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 16 ,
38
- ] )
39
- . unwrap ( ) ;
40
-
41
- // Returns a new edwards25519.Point that is v multiplied by the subgroup order.
42
- let scalar_mult_order = |v : & EdwardsPoint | -> EdwardsPoint {
43
- // v * (L - 1) + v => v * L
44
- let p = v * scalar_order_minus1;
45
- p + v
46
- } ;
47
-
48
- // Generates a new Keypair using, and returns the public key representative
49
- // along, with its public key as a newly allocated edwards25519.Point.
50
- let generate = || -> ( [ u8 ; 32 ] , EdwardsPoint ) {
51
- for _ in 0 ..63 {
52
- let y_sk = thread_rng ( ) . gen :: < [ u8 ; 32 ] > ( ) ;
53
- let y_sk_tweak = thread_rng ( ) . gen :: < u8 > ( ) ;
54
-
55
- let y_repr_bytes = match Randomized :: to_representative ( & y_sk, y_sk_tweak) . into ( ) {
56
- Some ( r) => r,
57
- None => continue ,
58
- } ;
59
- let y_pk = Randomized :: mul_base_clamped ( y_sk) ;
60
-
61
- assert_eq ! (
62
- MontgomeryPoint :: from_representative:: <Randomized >( & y_repr_bytes)
63
- . expect( "failed to re-derive point from representative" ) ,
64
- y_pk. to_montgomery( )
65
- ) ;
66
-
67
- return ( y_repr_bytes, y_pk) ;
68
- }
69
- panic ! ( "failed to generate a valid keypair" ) ;
70
- } ;
71
-
72
69
// These are all the points of low order that may result from
73
70
// multiplying an Elligator-mapped point by L. We will test that all of
74
71
// them are covered.
@@ -94,8 +91,10 @@ fn pubkey_subgroup_check() {
94
91
// and break the loop when it reaches 8, so when representatives are
95
92
// actually uniform we will usually run much fewer iterations.
96
93
let mut num_covered = 0 ;
94
+
95
+ let mut rng = rand:: thread_rng ( ) ;
97
96
for _ in 0 ..255 {
98
- let ( repr, pk) = generate ( ) ;
97
+ let ( repr, pk) = generate ( & mut rng ) ;
99
98
let v = scalar_mult_order ( & pk) ;
100
99
101
100
let b = v. compress ( ) . to_bytes ( ) ;
@@ -131,3 +130,129 @@ fn pubkey_subgroup_check() {
131
130
panic ! ( "not all low order points were covered" )
132
131
}
133
132
}
133
+
134
+ #[ test]
135
+ fn off_subgroup_check_edw ( ) {
136
+ let mut rng = rand:: thread_rng ( ) ;
137
+ for _ in 0 ..100 {
138
+ let ( repr, pk) = generate ( & mut rng) ;
139
+
140
+ // check if the generated public key is off the subgroup
141
+ let v = scalar_mult_order ( & pk) ;
142
+ let pk_off = !v. is_identity ( ) ;
143
+
144
+ // ---
145
+
146
+ // check if the public key derived from the representative (top bit 0)
147
+ // is off the subgroup
148
+ let mut yr_255 = repr;
149
+ yr_255[ 31 ] &= 0xbf ;
150
+ let pk_255 = EdwardsPoint :: from_representative :: < RFC9380 > ( & yr_255)
151
+ . expect ( "from_repr_255, should never fail" ) ;
152
+ let v = scalar_mult_order ( & pk_255) ;
153
+ let off_255 = !v. is_identity ( ) ;
154
+
155
+ // check if the public key derived from the representative (top two bits 0 - as
156
+ // our representatives are) is off the subgroup.
157
+ let mut yr_254 = repr;
158
+ yr_254[ 31 ] &= 0x3f ;
159
+ let pk_254 = EdwardsPoint :: from_representative :: < RFC9380 > ( & yr_254)
160
+ . expect ( "from_repr_254, should never fail" ) ;
161
+ let v = scalar_mult_order ( & pk_254) ;
162
+ let off_254 = !v. is_identity ( ) ;
163
+
164
+ println ! ( "pk_gen: {pk_off}, pk_255: {off_255}, pk_254: {off_254}" ) ;
165
+ }
166
+ }
167
+
168
+ use crate :: constants:: BASEPOINT_ORDER_PRIVATE ;
169
+
170
+ fn check ( pk : MontgomeryPoint ) -> bool {
171
+ let z = pk * BASEPOINT_ORDER_PRIVATE ;
172
+ !z. is_identity ( )
173
+ }
174
+
175
+ /// check a point in the group, assuming it is a representative and given a
176
+ /// variant by which to convert it to a point.
177
+ fn check_r < V : MapToPointVariant > ( r : [ u8 ; 32 ] ) -> bool {
178
+ let pk = MontgomeryPoint :: from_representative :: < V > ( & r) . expect ( "from_representative failed" ) ;
179
+ check ( pk)
180
+ }
181
+
182
+ #[ test]
183
+ /// Somehow there should be a montgomery distinguisher where all real representatives
184
+ /// map to the curve subgroup. For our keys this should only (consistently) happen
185
+ /// for representatives with the top two bits cleared (i.e. 254 but representatives).
186
+ fn off_subgroup_check_mgt ( ) {
187
+ let mut rng = rand:: thread_rng ( ) ;
188
+
189
+ for _ in 0 ..100 {
190
+ let ( mut repr, pk) = generate ( & mut rng) ;
191
+ repr[ 31 ] &= MASK_UNSET_BYTE ;
192
+
193
+ let off_pk = check ( pk. to_montgomery ( ) ) ;
194
+
195
+ let off_rfc = check_r :: < RFC9380 > ( repr) ;
196
+
197
+ let off_rand = check_r :: < Randomized > ( repr) ;
198
+
199
+ let ( u, _v) = elligator_dir_map ( repr) ;
200
+ let u = MontgomeryPoint ( u. as_bytes ( ) ) ;
201
+ let off = check ( u) ;
202
+
203
+ println ! ( "pk: {off_pk},\t rfc: {off_rfc},\t rand: {off_rand},\t cust: {off}" ) ;
204
+ }
205
+ }
206
+
207
+ #[ test]
208
+ /// Somehow there should be a montgomery distinguisher where all real representatives
209
+ /// map to the curve subgroup. For our keys this should only (consistently) happen
210
+ /// for representatives with the top two bits cleared (i.e. 254 but representatives).
211
+ fn off_subgroup_check_custom ( ) {
212
+ let mut rng = rand:: thread_rng ( ) ;
213
+
214
+ for _ in 0 ..100 {
215
+ let ( mut repr, _) = generate ( & mut rng) ;
216
+ repr[ 31 ] &= MASK_UNSET_BYTE ;
217
+
218
+ let ( u, _v) = elligator_dir_map ( repr) ;
219
+ let u = MontgomeryPoint ( u. as_bytes ( ) ) ;
220
+ let off = check ( u) ;
221
+
222
+ println ! ( "custom: {off}" ) ;
223
+ }
224
+ }
225
+
226
+ /// Direct elligator map translate as accurately as possible from `obfs4-subgroup-check.py`.
227
+ fn elligator_dir_map ( rb : [ u8 ; 32 ] ) -> ( FieldElement , FieldElement ) {
228
+ let r = FieldElement :: from_bytes ( & rb) ;
229
+ let two = & FieldElement :: ONE + & FieldElement :: ONE ;
230
+ let ufactor = & -& two * & SQRT_M1 ;
231
+ let ( _, vfactor) = FieldElement :: sqrt_ratio_i ( & ufactor, & FieldElement :: ONE ) ;
232
+
233
+ let u = r. square ( ) ;
234
+ let t1 = r. square2 ( ) ;
235
+ let v = & t1 + & FieldElement :: ONE ;
236
+ let t2 = v. square ( ) ;
237
+ let t3 = MONTGOMERY_A . square ( ) ;
238
+ let t3 = & t3 * & t1;
239
+ let t3 = & t3 - & t2;
240
+ let t3 = & t3 * & MONTGOMERY_A ;
241
+ let t1 = & t2 * & v;
242
+
243
+ let ( is_sq, t1) = FieldElement :: sqrt_ratio_i ( & FieldElement :: ONE , & ( & t3 * & t1) ) ;
244
+ let u = & u * & ufactor;
245
+ let v = & r * & vfactor;
246
+ let u = FieldElement :: conditional_select ( & u, & FieldElement :: ONE , is_sq) ;
247
+ let v = FieldElement :: conditional_select ( & v, & FieldElement :: ONE , is_sq) ;
248
+ let v = & v * & t3;
249
+ let v = & v * & t1;
250
+ let t1 = t1. square ( ) ;
251
+ let u = & u * & -& MONTGOMERY_A ;
252
+ let u = & u * & t3;
253
+ let u = & u * & t2;
254
+ let u = & u * & t1;
255
+ let t1 = -& v;
256
+ let v = FieldElement :: conditional_select ( & v, & t1, is_sq ^ v. is_negative ( ) ) ;
257
+ ( u, v)
258
+ }
0 commit comments