ark-r1cs-std/r1cs-std/src/groups/curves/short_weierstrass/mod.rs
r1cs-std/src/groups/curves/short_weierstrass/mod.rsRaw
1use algebra::{
2 curves::{
3 short_weierstrass_jacobian::{GroupAffine as SWAffine, GroupProjective as SWProjective},
4 SWModelParameters,
5 },
6 AffineCurve, BitIterator, Field, PrimeField, ProjectiveCurve,
7};
8use r1cs_core::{ConstraintSystem, SynthesisError};
9use std::{borrow::Borrow, marker::PhantomData, ops::Neg};
10
11use crate::{prelude::*, Assignment};
12
13pub mod bls12;
14
15#[derive(Derivative)]
16#[derivative(Debug, Clone)]
17#[must_use]
18pub struct AffineGadget<
19 P: SWModelParameters,
20 ConstraintF: Field,
21 F: FieldGadget<P::BaseField, ConstraintF>,
22> {
23 pub x: F,
24 pub y: F,
25 _params: PhantomData<P>,
26 _engine: PhantomData<ConstraintF>,
27}
28
29impl<P: SWModelParameters, ConstraintF: Field, F: FieldGadget<P::BaseField, ConstraintF>>
30 AffineGadget<P, ConstraintF, F>
31{
32 pub fn new(x: F, y: F) -> Self {
33 Self {
34 x,
35 y,
36 _params: PhantomData,
37 _engine: PhantomData,
38 }
39 }
40
41 pub fn alloc_without_check<FN, CS: ConstraintSystem<ConstraintF>>(
42 mut cs: CS,
43 value_gen: F,
44 ) -> Result<Self, SynthesisError>
45 where
46 F: FnOnce() -> Result<SWProjective<P>, SynthesisError>,
47 {
48 let (x, y) = match value_gen() {
49 Ok(fe) => {
50 let fe = fe.into_affine();
51 (Ok(fe.x), Ok(fe.y))
52 },
53 _ => (
54 Err(SynthesisError::AssignmentMissing),
55 Err(SynthesisError::AssignmentMissing),
56 ),
57 };
58
59 let x = F::alloc(&mut cs.ns(|| "x"), || x)?;
60 let y = F::alloc(&mut cs.ns(|| "y"), || y)?;
61
62 Ok(Self::new(x, y))
63 }
64}
65
66impl<P, ConstraintF, F> PartialEq for AffineGadget<P, ConstraintF, F>
67where
68 P: SWModelParameters,
69 ConstraintF: Field,
70 F: FieldGadget<P::BaseField, ConstraintF>,
71{
72 fn eq(&self, other: &Self) -> bool {
73 self.x == other.x && self.y == other.y
74 }
75}
76
77impl<P, ConstraintF, F> Eq for AffineGadget<P, ConstraintF, F>
78where
79 P: SWModelParameters,
80 ConstraintF: Field,
81 F: FieldGadget<P::BaseField, ConstraintF>,
82{
83}
84
85impl<P, ConstraintF, F> GroupGadget<SWProjective<P>, ConstraintF>
86 for AffineGadget<P, ConstraintF, F>
87where
88 P: SWModelParameters,
89 ConstraintF: Field,
90 F: FieldGadget<P::BaseField, ConstraintF>,
91{
92 type Value = SWProjective<P>;
93 type Variable = (F::Variable, F::Variable);
94
95 #[inline]
96 fn get_value(&self) -> Option<Self::Value> {
97 match (self.x.get_value(), self.y.get_value()) {
98 (Some(x), Some(y)) => {
99 let is_zero = x.is_zero() && y.is_one();
100 Some(SWAffine::new(x, y, is_zero).into_projective())
101 },
102 (None, None) => None,
103 _ => unreachable!(),
104 }
105 }
106
107 #[inline]
108 fn get_variable(&self) -> Self::Variable {
109 (self.x.get_variable(), self.y.get_variable())
110 }
111
112 #[inline]
113 fn zero<CS: ConstraintSystem<ConstraintF>>(mut cs: CS) -> Result<Self, SynthesisError> {
114 Ok(Self::new(
115 F::zero(cs.ns(|| "zero"))?,
116 F::one(cs.ns(|| "one"))?,
117 ))
118 }
119
120 #[inline]
121 /// Incomplete addition: neither `self` nor `other` can be the neutral
122 /// element.
123 fn add<CS: ConstraintSystem<ConstraintF>>(
124 &self,
125 mut cs: CS,
126 other: &Self,
127 ) -> Result<Self, SynthesisError> {
128 // lambda = (B.y - A.y)/(B.x - A.x)
129 // C.x = lambda^2 - A.x - B.x
130 // C.y = lambda(A.x - C.x) - A.y
131 //
132 // Special cases:
133 //
134 // doubling: if B.y = A.y and B.x = A.x then lambda is unbound and
135 // C = (lambda^2, lambda^3)
136 //
137 // addition of negative point: if B.y = -A.y and B.x = A.x then no
138 // lambda can satisfy the first equation unless B.y - A.y = 0. But
139 // then this reduces to doubling.
140 //
141 // So we need to check that A.x - B.x != 0, which can be done by
142 // enforcing I * (B.x - A.x) = 1
143
144 let x2_minus_x1 = other.x.sub(cs.ns(|| "x2 - x1"), &self.x)?;
145 let y2_minus_y1 = other.y.sub(cs.ns(|| "y2 - y1"), &self.y)?;
146
147 let inv = x2_minus_x1.inverse(cs.ns(|| "compute inv"))?;
148
149 let lambda = F::alloc(cs.ns(|| "lambda"), || {
150 Ok(y2_minus_y1.get_value().get()? * &inv.get_value().get()?)
151 })?;
152
153 let x_3 = F::alloc(&mut cs.ns(|| "x_3"), || {
154 let lambda_val = lambda.get_value().get()?;
155 let x1 = self.x.get_value().get()?;
156 let x2 = other.x.get_value().get()?;
157 Ok((lambda_val.square() - &x1) - &x2)
158 })?;
159
160 let y_3 = F::alloc(&mut cs.ns(|| "y_3"), || {
161 let lambda_val = lambda.get_value().get()?;
162 let x_1 = self.x.get_value().get()?;
163 let y_1 = self.y.get_value().get()?;
164 let x_3 = x_3.get_value().get()?;
165 Ok(lambda_val * &(x_1 - &x_3) - &y_1)
166 })?;
167
168 // Check lambda
169 lambda.mul_equals(cs.ns(|| "check lambda"), &x2_minus_x1, &y2_minus_y1)?;
170
171 // Check x3
172 let x3_plus_x1_plus_x2 = x_3
173 .add(cs.ns(|| "x3 + x1"), &self.x)?
174 .add(cs.ns(|| "x3 + x1 + x2"), &other.x)?;
175 lambda.mul_equals(cs.ns(|| "check x3"), &lambda, &x3_plus_x1_plus_x2)?;
176
177 // Check y3
178 let y3_plus_y1 = y_3.add(cs.ns(|| "y3 + y1"), &self.y)?;
179 let x1_minus_x3 = self.x.sub(cs.ns(|| "x1 - x3"), &x_3)?;
180
181 lambda.mul_equals(cs.ns(|| ""), &x1_minus_x3, &y3_plus_y1)?;
182
183 Ok(Self::new(x_3, y_3))
184 }
185
186 /// Incomplete addition: neither `self` nor `other` can be the neutral
187 /// element.
188 fn add_constant<CS: ConstraintSystem<ConstraintF>>(
189 &self,
190 mut cs: CS,
191 other: &SWProjective<P>,
192 ) -> Result<Self, SynthesisError> {
193 // lambda = (B.y - A.y)/(B.x - A.x)
194 // C.x = lambda^2 - A.x - B.x
195 // C.y = lambda(A.x - C.x) - A.y
196 //
197 // Special cases:
198 //
199 // doubling: if B.y = A.y and B.x = A.x then lambda is unbound and
200 // C = (lambda^2, lambda^3)
201 //
202 // addition of negative point: if B.y = -A.y and B.x = A.x then no
203 // lambda can satisfy the first equation unless B.y - A.y = 0. But
204 // then this reduces to doubling.
205 //
206 // So we need to check that A.x - B.x != 0, which can be done by
207 // enforcing I * (B.x - A.x) = 1
208 if other.is_zero() {
209 return Err(SynthesisError::AssignmentMissing);
210 }
211 let other = other.into_affine();
212 let other_x = other.x;
213 let other_y = other.y;
214
215 let x2_minus_x1 = self
216 .x
217 .sub_constant(cs.ns(|| "x2 - x1"), &other_x)?
218 .negate(cs.ns(|| "neg1"))?;
219 let y2_minus_y1 = self
220 .y
221 .sub_constant(cs.ns(|| "y2 - y1"), &other_y)?
222 .negate(cs.ns(|| "neg2"))?;
223
224 let inv = x2_minus_x1.inverse(cs.ns(|| "compute inv"))?;
225
226 let lambda = F::alloc(cs.ns(|| "lambda"), || {
227 Ok(y2_minus_y1.get_value().get()? * &inv.get_value().get()?)
228 })?;
229
230 let x_3 = F::alloc(&mut cs.ns(|| "x_3"), || {
231 let lambda_val = lambda.get_value().get()?;
232 let x1 = self.x.get_value().get()?;
233 let x2 = other_x;
234 Ok((lambda_val.square() - &x1) - &x2)
235 })?;
236
237 let y_3 = F::alloc(&mut cs.ns(|| "y_3"), || {
238 let lambda_val = lambda.get_value().get()?;
239 let x_1 = self.x.get_value().get()?;
240 let y_1 = self.y.get_value().get()?;
241 let x_3 = x_3.get_value().get()?;
242 Ok(lambda_val * &(x_1 - &x_3) - &y_1)
243 })?;
244
245 // Check lambda
246 lambda.mul_equals(cs.ns(|| "check lambda"), &x2_minus_x1, &y2_minus_y1)?;
247
248 // Check x3
249 let x3_plus_x1_plus_x2 = x_3
250 .add(cs.ns(|| "x3 + x1"), &self.x)?
251 .add_constant(cs.ns(|| "x3 + x1 + x2"), &other_x)?;
252 lambda.mul_equals(cs.ns(|| "check x3"), &lambda, &x3_plus_x1_plus_x2)?;
253
254 // Check y3
255 let y3_plus_y1 = y_3.add(cs.ns(|| "y3 + y1"), &self.y)?;
256 let x1_minus_x3 = self.x.sub(cs.ns(|| "x1 - x3"), &x_3)?;
257
258 lambda.mul_equals(cs.ns(|| ""), &x1_minus_x3, &y3_plus_y1)?;
259
260 Ok(Self::new(x_3, y_3))
261 }
262
263 #[inline]
264 fn double_in_place<CS: ConstraintSystem<ConstraintF>>(
265 &mut self,
266 mut cs: CS,
267 ) -> Result<(), SynthesisError> {
268 let a = P::COEFF_A;
269 let x_squared = self.x.square(cs.ns(|| "x^2"))?;
270
271 let one = P::BaseField::one();
272 let two = one.double();
273 let three = two + &one;
274
275 let three_x_squared = x_squared.mul_by_constant(cs.ns(|| "3 * x^2"), &three)?;
276 let three_x_squared_plus_a = three_x_squared.add_constant(cs.ns(|| "3 * x^2 + a"), &a)?;
277
278 let two_y = self.y.double(cs.ns(|| "2y"))?;
279
280 let lambda = F::alloc(cs.ns(|| "lambda"), || {
281 let y_doubled_inv = two_y.get_value().get()?.inverse().get()?;
282 Ok(three_x_squared_plus_a.get_value().get()? * &y_doubled_inv)
283 })?;
284
285 // Check lambda
286 lambda.mul_equals(cs.ns(|| "check lambda"), &two_y, &three_x_squared_plus_a)?;
287
288 let x = lambda
289 .square(cs.ns(|| "lambda^2"))?
290 .sub(cs.ns(|| "lambda^2 - x"), &self.x)?
291 .sub(cs.ns(|| "lambda^2 - 2x"), &self.x)?;
292
293 let y = self
294 .x
295 .sub(cs.ns(|| "x - self.x"), &x)?
296 .mul(cs.ns(|| "times lambda"), &lambda)?
297 .sub(cs.ns(|| "plus self.y"), &self.y)?;
298
299 *self = Self::new(x, y);
300 Ok(())
301 }
302
303 fn negate<CS: ConstraintSystem<ConstraintF>>(
304 &self,
305 mut cs: CS,
306 ) -> Result<Self, SynthesisError> {
307 Ok(Self::new(
308 self.x.clone(),
309 self.y.negate(cs.ns(|| "negate y"))?,
310 ))
311 }
312
313 fn cost_of_add() -> usize {
314 3 * F::cost_of_mul() + F::cost_of_inv()
315 }
316
317 fn cost_of_double() -> usize {
318 4 * F::cost_of_mul()
319 }
320}
321
322impl<P, ConstraintF, F> CondSelectGadget<ConstraintF> for AffineGadget<P, ConstraintF, F>
323where
324 P: SWModelParameters,
325 ConstraintF: Field,
326 F: FieldGadget<P::BaseField, ConstraintF>,
327{
328 #[inline]
329 fn conditionally_select<CS: ConstraintSystem<ConstraintF>>(
330 mut cs: CS,
331 cond: &Boolean,
332 first: &Self,
333 second: &Self,
334 ) -> Result<Self, SynthesisError> {
335 let x = F::conditionally_select(&mut cs.ns(|| "x"), cond, &first.x, &second.x)?;
336 let y = F::conditionally_select(&mut cs.ns(|| "y"), cond, &first.y, &second.y)?;
337
338 Ok(Self::new(x, y))
339 }
340
341 fn cost() -> usize {
342 2 * <F as CondSelectGadget<ConstraintF>>::cost()
343 }
344}
345
346impl<P, ConstraintF, F> EqGadget<ConstraintF> for AffineGadget<P, ConstraintF, F>
347where
348 P: SWModelParameters,
349 ConstraintF: Field,
350 F: FieldGadget<P::BaseField, ConstraintF>,
351{
352}
353
354impl<P, ConstraintF, F> ConditionalEqGadget<ConstraintF> for AffineGadget<P, ConstraintF, F>
355where
356 P: SWModelParameters,
357 ConstraintF: Field,
358 F: FieldGadget<P::BaseField, ConstraintF>,
359{
360 #[inline]
361 fn conditional_enforce_equal<CS: ConstraintSystem<ConstraintF>>(
362 &self,
363 mut cs: CS,
364 other: &Self,
365 condition: &Boolean,
366 ) -> Result<(), SynthesisError> {
367 self.x.conditional_enforce_equal(
368 &mut cs.ns(|| "X Coordinate Conditional Equality"),
369 &other.x,
370 condition,
371 )?;
372 self.y.conditional_enforce_equal(
373 &mut cs.ns(|| "Y Coordinate Conditional Equality"),
374 &other.y,
375 condition,
376 )?;
377 Ok(())
378 }
379
380 fn cost() -> usize {
381 2 * <F as ConditionalEqGadget<ConstraintF>>::cost()
382 }
383}
384
385impl<P, ConstraintF, F> NEqGadget<ConstraintF> for AffineGadget<P, ConstraintF, F>
386where
387 P: SWModelParameters,
388 ConstraintF: Field,
389 F: FieldGadget<P::BaseField, ConstraintF>,
390{
391 #[inline]
392 fn enforce_not_equal<CS: ConstraintSystem<ConstraintF>>(
393 &self,
394 mut cs: CS,
395 other: &Self,
396 ) -> Result<(), SynthesisError> {
397 self.x
398 .enforce_not_equal(&mut cs.ns(|| "X Coordinate Inequality"), &other.x)?;
399 self.y
400 .enforce_not_equal(&mut cs.ns(|| "Y Coordinate Inequality"), &other.y)?;
401 Ok(())
402 }
403
404 fn cost() -> usize {
405 2 * <F as NEqGadget<ConstraintF>>::cost()
406 }
407}
408
409impl<P, ConstraintF, F> AllocGadget<SWProjective<P>, ConstraintF>
410 for AffineGadget<P, ConstraintF, F>
411where
412 P: SWModelParameters,
413 ConstraintF: Field,
414 F: FieldGadget<P::BaseField, ConstraintF>,
415{
416 #[inline]
417 fn alloc<FN, T, CS: ConstraintSystem<ConstraintF>>(
418 mut cs: CS,
419 value_gen: FN,
420 ) -> Result<Self, SynthesisError>
421 where
422 FN: FnOnce() -> Result<T, SynthesisError>,
423 T: Borrow<SWProjective<P>>,
424 {
425 let (x, y) = match value_gen() {
426 Ok(ge) => {
427 let ge = ge.borrow().into_affine();
428 (Ok(ge.x), Ok(ge.y))
429 },
430 _ => (
431 Err(SynthesisError::AssignmentMissing),
432 Err(SynthesisError::AssignmentMissing),
433 ),
434 };
435
436 // Perform on-curve check.
437 let b = P::COEFF_B;
438 let a = P::COEFF_A;
439
440 let x = F::alloc(&mut cs.ns(|| "x"), || x)?;
441 let y = F::alloc(&mut cs.ns(|| "y"), || y)?;
442
443 // Check that y^2 = x^3 + ax +b
444 // We do this by checking that y^2 - b = x * (x^2 +a)
445 let x2 = x.square(&mut cs.ns(|| "x^2"))?;
446 let y2 = y.square(&mut cs.ns(|| "y^2"))?;
447
448 let x2_plus_a = x2.add_constant(cs.ns(|| "x^2 + a"), &a)?;
449 let y2_minus_b = y2.add_constant(cs.ns(|| "y^2 - b"), &b.neg())?;
450
451 x2_plus_a.mul_equals(cs.ns(|| "on curve check"), &x, &y2_minus_b)?;
452
453 Ok(Self::new(x, y))
454 }
455
456 #[inline]
457 fn alloc_checked<FN, T, CS: ConstraintSystem<ConstraintF>>(
458 mut cs: CS,
459 value_gen: FN,
460 ) -> Result<Self, SynthesisError>
461 where
462 FN: FnOnce() -> Result<T, SynthesisError>,
463 T: Borrow<SWProjective<P>>,
464 {
465 let cofactor_weight = BitIterator::new(P::COFACTOR).filter(|b| *b).count();
466 // If we multiply by r, we actually multiply by r - 2.
467 let r_minus_1 = (-P::ScalarField::one()).into_repr();
468 let r_weight = BitIterator::new(&r_minus_1).filter(|b| *b).count();
469
470 // We pick the most efficient method of performing the prime order check:
471 // If the cofactor has lower hamming weight than the scalar field's modulus,
472 // we first multiply by the inverse of the cofactor, and then, after allocating,
473 // multiply by the cofactor. This ensures the resulting point has no cofactors
474 //
475 // Else, we multiply by the scalar field's modulus and ensure that the result
476 // is zero.
477 if cofactor_weight < r_weight {
478 let ge = Self::alloc(cs.ns(|| "Alloc checked"), || {
479 value_gen().map(|ge| {
480 ge.borrow()
481 .into_affine()
482 .mul_by_cofactor_inv()
483 .into_projective()
484 })
485 })?;
486 let mut seen_one = false;
487 let mut result = Self::zero(cs.ns(|| "result"))?;
488 for (i, b) in BitIterator::new(P::COFACTOR).enumerate() {
489 let mut cs = cs.ns(|| format!("Iteration {}", i));
490
491 let old_seen_one = seen_one;
492 if seen_one {
493 result.double_in_place(cs.ns(|| "Double"))?;
494 } else {
495 seen_one = b;
496 }
497
498 if b {
499 result = if old_seen_one {
500 result.add(cs.ns(|| "Add"), &ge)?
501 } else {
502 ge.clone()
503 };
504 }
505 }
506 Ok(result)
507 } else {
508 let ge = Self::alloc(cs.ns(|| "Alloc checked"), value_gen)?;
509 let mut seen_one = false;
510 let mut result = Self::zero(cs.ns(|| "result"))?;
511 // Returns bits in big-endian order
512 for (i, b) in BitIterator::new(r_minus_1).enumerate() {
513 let mut cs = cs.ns(|| format!("Iteration {}", i));
514
515 let old_seen_one = seen_one;
516 if seen_one {
517 result.double_in_place(cs.ns(|| "Double"))?;
518 } else {
519 seen_one = b;
520 }
521
522 if b {
523 result = if old_seen_one {
524 result.add(cs.ns(|| "Add"), &ge)?
525 } else {
526 ge.clone()
527 };
528 }
529 }
530 let neg_ge = ge.negate(cs.ns(|| "Negate ge"))?;
531 neg_ge.enforce_equal(cs.ns(|| "Check equals"), &result)?;
532 Ok(ge)
533 }
534 }
535
536 #[inline]
537 fn alloc_input<FN, T, CS: ConstraintSystem<ConstraintF>>(
538 mut cs: CS,
539 value_gen: FN,
540 ) -> Result<Self, SynthesisError>
541 where
542 FN: FnOnce() -> Result<T, SynthesisError>,
543 T: Borrow<SWProjective<P>>,
544 {
545 let (x, y) = match value_gen() {
546 Ok(ge) => {
547 let ge = ge.borrow().into_affine();
548 (Ok(ge.x), Ok(ge.y))
549 },
550 _ => (
551 Err(SynthesisError::AssignmentMissing),
552 Err(SynthesisError::AssignmentMissing),
553 ),
554 };
555
556 let b = P::COEFF_B;
557 let a = P::COEFF_A;
558
559 let x = F::alloc_input(&mut cs.ns(|| "x"), || x)?;
560 let y = F::alloc_input(&mut cs.ns(|| "y"), || y)?;
561
562 // Check that y^2 = x^3 + ax +b
563 // We do this by checking that y^2 - b = x * (x^2 +a)
564 let x2 = x.square(&mut cs.ns(|| "x^2"))?;
565 let y2 = y.square(&mut cs.ns(|| "y^2"))?;
566
567 let x2_plus_a = x2.add_constant(cs.ns(|| "x^2 + a"), &a)?;
568 let y2_minus_b = y2.add_constant(cs.ns(|| "y^2 - b"), &b.neg())?;
569
570 x2_plus_a.mul_equals(cs.ns(|| "on curve check"), &x, &y2_minus_b)?;
571
572 Ok(Self::new(x, y))
573 }
574}
575
576impl<P, ConstraintF, F> ToBitsGadget<ConstraintF> for AffineGadget<P, ConstraintF, F>
577where
578 P: SWModelParameters,
579 ConstraintF: Field,
580 F: FieldGadget<P::BaseField, ConstraintF>,
581{
582 fn to_bits<CS: ConstraintSystem<ConstraintF>>(
583 &self,
584 mut cs: CS,
585 ) -> Result<Vec<Boolean>, SynthesisError> {
586 let mut x_bits = self.x.to_bits(&mut cs.ns(|| "X Coordinate To Bits"))?;
587 let y_bits = self.y.to_bits(&mut cs.ns(|| "Y Coordinate To Bits"))?;
588 x_bits.extend_from_slice(&y_bits);
589 Ok(x_bits)
590 }
591
592 fn to_bits_strict<CS: ConstraintSystem<ConstraintF>>(
593 &self,
594 mut cs: CS,
595 ) -> Result<Vec<Boolean>, SynthesisError> {
596 let mut x_bits = self
597 .x
598 .to_bits_strict(&mut cs.ns(|| "X Coordinate To Bits"))?;
599 let y_bits = self
600 .y
601 .to_bits_strict(&mut cs.ns(|| "Y Coordinate To Bits"))?;
602 x_bits.extend_from_slice(&y_bits);
603
604 Ok(x_bits)
605 }
606}
607
608impl<P, ConstraintF, F> ToBytesGadget<ConstraintF> for AffineGadget<P, ConstraintF, F>
609where
610 P: SWModelParameters,
611 ConstraintF: Field,
612 F: FieldGadget<P::BaseField, ConstraintF>,
613{
614 fn to_bytes<CS: ConstraintSystem<ConstraintF>>(
615 &self,
616 mut cs: CS,
617 ) -> Result<Vec<UInt8>, SynthesisError> {
618 let mut x_bytes = self.x.to_bytes(&mut cs.ns(|| "X Coordinate To Bytes"))?;
619 let y_bytes = self.y.to_bytes(&mut cs.ns(|| "Y Coordinate To Bytes"))?;
620 x_bytes.extend_from_slice(&y_bytes);
621 Ok(x_bytes)
622 }
623
624 fn to_bytes_strict<CS: ConstraintSystem<ConstraintF>>(
625 &self,
626 mut cs: CS,
627 ) -> Result<Vec<UInt8>, SynthesisError> {
628 let mut x_bytes = self
629 .x
630 .to_bytes_strict(&mut cs.ns(|| "X Coordinate To Bytes"))?;
631 let y_bytes = self
632 .y
633 .to_bytes_strict(&mut cs.ns(|| "Y Coordinate To Bytes"))?;
634 x_bytes.extend_from_slice(&y_bytes);
635
636 Ok(x_bytes)
637 }
638}