1use crate::{Error, Result};
9use core::{
10 cmp::Ordering,
11 fmt::{self, Debug},
12 ops::Add,
13 str,
14};
15use generic_array::{
16 typenum::{U1, U28, U32, U48, U66},
17 ArrayLength, GenericArray,
18};
19
20#[cfg(feature = "alloc")]
21use alloc::boxed::Box;
22
23#[cfg(feature = "serde")]
24use serde::{de, ser, Deserialize, Serialize};
25
26#[cfg(feature = "subtle")]
27use subtle::{Choice, ConditionallySelectable};
28
29#[cfg(feature = "zeroize")]
30use zeroize::Zeroize;
31
32pub trait ModulusSize: 'static + ArrayLength<u8> + Copy + Debug {
36 type CompressedPointSize: 'static + ArrayLength<u8> + Copy + Debug;
40
41 type UncompressedPointSize: 'static + ArrayLength<u8> + Copy + Debug;
45
46 type UntaggedPointSize: 'static + ArrayLength<u8> + Copy + Debug;
49}
50
51macro_rules! impl_modulus_size {
52 ($($size:ty),+) => {
53 $(impl ModulusSize for $size {
54 type CompressedPointSize = <$size as Add<U1>>::Output;
55 type UncompressedPointSize = <Self::UntaggedPointSize as Add<U1>>::Output;
56 type UntaggedPointSize = <$size as Add>::Output;
57 })+
58 }
59}
60
61impl_modulus_size!(U28, U32, U48, U66);
62
63#[derive(Clone, Default)]
69pub struct EncodedPoint<Size>
70where
71 Size: ModulusSize,
72{
73 bytes: GenericArray<u8, Size::UncompressedPointSize>,
74}
75
76#[allow(clippy::len_without_is_empty)]
77impl<Size> EncodedPoint<Size>
78where
79 Size: ModulusSize,
80{
81 pub fn from_bytes(input: impl AsRef<[u8]>) -> Result<Self> {
88 let input = input.as_ref();
89
90 let tag = input
92 .first()
93 .cloned()
94 .ok_or(Error::PointEncoding)
95 .and_then(Tag::from_u8)?;
96
97 let expected_len = tag.message_len(Size::to_usize());
99
100 if input.len() != expected_len {
101 return Err(Error::PointEncoding);
102 }
103
104 let mut bytes = GenericArray::default();
105 bytes[..expected_len].copy_from_slice(input);
106 Ok(Self { bytes })
107 }
108
109 pub fn from_untagged_bytes(bytes: &GenericArray<u8, Size::UntaggedPointSize>) -> Self {
113 let (x, y) = bytes.split_at(Size::to_usize());
114 Self::from_affine_coordinates(x.into(), y.into(), false)
115 }
116
117 pub fn from_affine_coordinates(
120 x: &GenericArray<u8, Size>,
121 y: &GenericArray<u8, Size>,
122 compress: bool,
123 ) -> Self {
124 let tag = if compress {
125 Tag::compress_y(y.as_slice())
126 } else {
127 Tag::Uncompressed
128 };
129
130 let mut bytes = GenericArray::default();
131 bytes[0] = tag.into();
132 bytes[1..(Size::to_usize() + 1)].copy_from_slice(x);
133
134 if !compress {
135 bytes[(Size::to_usize() + 1)..].copy_from_slice(y);
136 }
137
138 Self { bytes }
139 }
140
141 pub fn identity() -> Self {
144 Self::default()
145 }
146
147 pub fn len(&self) -> usize {
149 self.tag().message_len(Size::to_usize())
150 }
151
152 pub fn as_bytes(&self) -> &[u8] {
154 &self.bytes[..self.len()]
155 }
156
157 #[cfg(feature = "alloc")]
159 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
160 pub fn to_bytes(&self) -> Box<[u8]> {
161 self.as_bytes().to_vec().into_boxed_slice()
162 }
163
164 pub fn is_compact(&self) -> bool {
166 self.tag().is_compact()
167 }
168
169 pub fn is_compressed(&self) -> bool {
171 self.tag().is_compressed()
172 }
173
174 pub fn is_identity(&self) -> bool {
176 self.tag().is_identity()
177 }
178
179 pub fn compress(&self) -> Self {
181 match self.coordinates() {
182 Coordinates::Compressed { .. }
183 | Coordinates::Compact { .. }
184 | Coordinates::Identity => self.clone(),
185 Coordinates::Uncompressed { x, y } => Self::from_affine_coordinates(x, y, true),
186 }
187 }
188
189 pub fn tag(&self) -> Tag {
191 Tag::from_u8(self.bytes[0]).expect("invalid tag")
193 }
194
195 #[inline]
197 pub fn coordinates(&self) -> Coordinates<'_, Size> {
198 if self.is_identity() {
199 return Coordinates::Identity;
200 }
201
202 let (x, y) = self.bytes[1..].split_at(Size::to_usize());
203
204 if self.is_compressed() {
205 Coordinates::Compressed {
206 x: x.into(),
207 y_is_odd: self.tag() as u8 & 1 == 1,
208 }
209 } else if self.is_compact() {
210 Coordinates::Compact { x: x.into() }
211 } else {
212 Coordinates::Uncompressed {
213 x: x.into(),
214 y: y.into(),
215 }
216 }
217 }
218
219 pub fn x(&self) -> Option<&GenericArray<u8, Size>> {
223 match self.coordinates() {
224 Coordinates::Identity => None,
225 Coordinates::Compressed { x, .. } => Some(x),
226 Coordinates::Uncompressed { x, .. } => Some(x),
227 Coordinates::Compact { x } => Some(x),
228 }
229 }
230
231 pub fn y(&self) -> Option<&GenericArray<u8, Size>> {
235 match self.coordinates() {
236 Coordinates::Compressed { .. } | Coordinates::Identity => None,
237 Coordinates::Uncompressed { y, .. } => Some(y),
238 Coordinates::Compact { .. } => None,
239 }
240 }
241}
242
243impl<Size> AsRef<[u8]> for EncodedPoint<Size>
244where
245 Size: ModulusSize,
246{
247 #[inline]
248 fn as_ref(&self) -> &[u8] {
249 self.as_bytes()
250 }
251}
252
253#[cfg(feature = "subtle")]
254impl<Size> ConditionallySelectable for EncodedPoint<Size>
255where
256 Size: ModulusSize,
257 <Size::UncompressedPointSize as ArrayLength<u8>>::ArrayType: Copy,
258{
259 fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
260 let mut bytes = GenericArray::default();
261
262 for (i, byte) in bytes.iter_mut().enumerate() {
263 *byte = u8::conditional_select(&a.bytes[i], &b.bytes[i], choice);
264 }
265
266 Self { bytes }
267 }
268}
269
270impl<Size> Copy for EncodedPoint<Size>
271where
272 Size: ModulusSize,
273 <Size::UncompressedPointSize as ArrayLength<u8>>::ArrayType: Copy,
274{
275}
276
277impl<Size> Debug for EncodedPoint<Size>
278where
279 Size: ModulusSize,
280{
281 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282 write!(f, "EncodedPoint({:?})", self.coordinates())
283 }
284}
285
286impl<Size: ModulusSize> Eq for EncodedPoint<Size> {}
287
288impl<Size> PartialEq for EncodedPoint<Size>
289where
290 Size: ModulusSize,
291{
292 fn eq(&self, other: &Self) -> bool {
293 self.as_bytes() == other.as_bytes()
294 }
295}
296
297impl<Size: ModulusSize> PartialOrd for EncodedPoint<Size>
298where
299 Size: ModulusSize,
300{
301 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
302 Some(self.cmp(other))
303 }
304}
305
306impl<Size: ModulusSize> Ord for EncodedPoint<Size>
307where
308 Size: ModulusSize,
309{
310 fn cmp(&self, other: &Self) -> Ordering {
311 self.as_bytes().cmp(other.as_bytes())
312 }
313}
314
315impl<Size: ModulusSize> TryFrom<&[u8]> for EncodedPoint<Size>
316where
317 Size: ModulusSize,
318{
319 type Error = Error;
320
321 fn try_from(bytes: &[u8]) -> Result<Self> {
322 Self::from_bytes(bytes)
323 }
324}
325
326#[cfg(feature = "zeroize")]
327impl<Size> Zeroize for EncodedPoint<Size>
328where
329 Size: ModulusSize,
330{
331 fn zeroize(&mut self) {
332 self.bytes.zeroize();
333 *self = Self::identity();
334 }
335}
336
337impl<Size> fmt::Display for EncodedPoint<Size>
338where
339 Size: ModulusSize,
340{
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 write!(f, "{:X}", self)
343 }
344}
345
346impl<Size> fmt::LowerHex for EncodedPoint<Size>
347where
348 Size: ModulusSize,
349{
350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351 for byte in self.as_bytes() {
352 write!(f, "{:02x}", byte)?;
353 }
354 Ok(())
355 }
356}
357
358impl<Size> fmt::UpperHex for EncodedPoint<Size>
359where
360 Size: ModulusSize,
361{
362 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363 for byte in self.as_bytes() {
364 write!(f, "{:02X}", byte)?;
365 }
366 Ok(())
367 }
368}
369
370impl<Size> str::FromStr for EncodedPoint<Size>
375where
376 Size: ModulusSize,
377{
378 type Err = Error;
379
380 fn from_str(hex: &str) -> Result<Self> {
381 let mut buffer = GenericArray::<u8, Size::UncompressedPointSize>::default();
382 let decoded_len = hex.as_bytes().len() / 2;
383
384 if hex.as_bytes().len() % 2 != 0 || decoded_len > buffer.len() {
385 return Err(Error::PointEncoding);
386 }
387
388 let mut upper_case = None;
389
390 for &byte in hex.as_bytes() {
392 match byte {
393 b'0'..=b'9' => (),
394 b'a'..=b'z' => match upper_case {
395 Some(true) => return Err(Error::PointEncoding),
396 Some(false) => (),
397 None => upper_case = Some(false),
398 },
399 b'A'..=b'Z' => match upper_case {
400 Some(true) => (),
401 Some(false) => return Err(Error::PointEncoding),
402 None => upper_case = Some(true),
403 },
404 _ => return Err(Error::PointEncoding),
405 }
406 }
407
408 for (digit, byte) in hex.as_bytes().chunks_exact(2).zip(buffer.iter_mut()) {
409 *byte = str::from_utf8(digit)
410 .ok()
411 .and_then(|s| u8::from_str_radix(s, 16).ok())
412 .ok_or(Error::PointEncoding)?;
413 }
414
415 Self::from_bytes(&buffer[..decoded_len])
416 }
417}
418
419#[cfg(feature = "serde")]
420#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
421impl<Size> Serialize for EncodedPoint<Size>
422where
423 Size: ModulusSize,
424{
425 #[cfg(not(feature = "alloc"))]
426 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
427 where
428 S: ser::Serializer,
429 {
430 self.as_bytes().serialize(serializer)
431 }
432
433 #[cfg(feature = "alloc")]
434 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
435 where
436 S: ser::Serializer,
437 {
438 use alloc::string::ToString;
439 if serializer.is_human_readable() {
440 self.to_string().serialize(serializer)
441 } else {
442 self.as_bytes().serialize(serializer)
443 }
444 }
445}
446
447#[cfg(feature = "serde")]
448#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
449impl<'de, Size> Deserialize<'de> for EncodedPoint<Size>
450where
451 Size: ModulusSize,
452{
453 #[cfg(not(feature = "alloc"))]
454 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
455 where
456 D: de::Deserializer<'de>,
457 {
458 use de::Error;
459 <&[u8]>::deserialize(deserializer)
460 .and_then(|slice| Self::from_bytes(slice).map_err(D::Error::custom))
461 }
462
463 #[cfg(feature = "alloc")]
464 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
465 where
466 D: de::Deserializer<'de>,
467 {
468 use de::Error;
469 if deserializer.is_human_readable() {
470 <&str>::deserialize(deserializer)?
471 .parse()
472 .map_err(D::Error::custom)
473 } else {
474 <&[u8]>::deserialize(deserializer)
475 .and_then(|bytes| Self::from_bytes(bytes).map_err(D::Error::custom))
476 }
477 }
478}
479
480#[derive(Copy, Clone, Debug, Eq, PartialEq)]
483pub enum Coordinates<'a, Size: ModulusSize> {
484 Identity,
486
487 Compact {
489 x: &'a GenericArray<u8, Size>,
491 },
492
493 Compressed {
495 x: &'a GenericArray<u8, Size>,
497
498 y_is_odd: bool,
500 },
501
502 Uncompressed {
504 x: &'a GenericArray<u8, Size>,
506
507 y: &'a GenericArray<u8, Size>,
509 },
510}
511
512impl<'a, Size: ModulusSize> Coordinates<'a, Size> {
513 pub fn tag(&self) -> Tag {
515 match self {
516 Coordinates::Compact { .. } => Tag::Compact,
517 Coordinates::Compressed { y_is_odd, .. } => {
518 if *y_is_odd {
519 Tag::CompressedOddY
520 } else {
521 Tag::CompressedEvenY
522 }
523 }
524 Coordinates::Identity => Tag::Identity,
525 Coordinates::Uncompressed { .. } => Tag::Uncompressed,
526 }
527 }
528}
529
530#[derive(Copy, Clone, Debug, Eq, PartialEq)]
532#[repr(u8)]
533pub enum Tag {
534 Identity = 0,
536
537 CompressedEvenY = 2,
539
540 CompressedOddY = 3,
542
543 Uncompressed = 4,
545
546 Compact = 5,
548}
549
550impl Tag {
551 pub fn from_u8(byte: u8) -> Result<Self> {
553 match byte {
554 0 => Ok(Tag::Identity),
555 2 => Ok(Tag::CompressedEvenY),
556 3 => Ok(Tag::CompressedOddY),
557 4 => Ok(Tag::Uncompressed),
558 5 => Ok(Tag::Compact),
559 _ => Err(Error::PointEncoding),
560 }
561 }
562
563 pub fn is_compact(self) -> bool {
565 matches!(self, Tag::Compact)
566 }
567
568 pub fn is_compressed(self) -> bool {
570 matches!(self, Tag::CompressedEvenY | Tag::CompressedOddY)
571 }
572
573 pub fn is_identity(self) -> bool {
575 self == Tag::Identity
576 }
577
578 pub fn message_len(self, field_element_size: usize) -> usize {
582 1 + match self {
583 Tag::Identity => 0,
584 Tag::CompressedEvenY | Tag::CompressedOddY => field_element_size,
585 Tag::Uncompressed => field_element_size * 2,
586 Tag::Compact => field_element_size,
587 }
588 }
589
590 fn compress_y(y: &[u8]) -> Self {
592 if y.as_ref().last().expect("empty y-coordinate") & 1 == 1 {
594 Tag::CompressedOddY
595 } else {
596 Tag::CompressedEvenY
597 }
598 }
599}
600
601impl From<Tag> for u8 {
602 fn from(tag: Tag) -> u8 {
603 tag as u8
604 }
605}
606
607#[cfg(test)]
608mod tests {
609 use super::{Coordinates, Tag};
610 use core::str::FromStr;
611 use generic_array::{typenum::U32, GenericArray};
612 use hex_literal::hex;
613
614 #[cfg(feature = "alloc")]
615 use alloc::string::ToString;
616
617 #[cfg(feature = "subtle")]
618 use subtle::ConditionallySelectable;
619
620 type EncodedPoint = super::EncodedPoint<U32>;
621
622 const IDENTITY_BYTES: [u8; 1] = [0];
624
625 const UNCOMPRESSED_BYTES: [u8; 65] = hex!("0411111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222222");
627
628 const COMPRESSED_BYTES: [u8; 33] =
630 hex!("021111111111111111111111111111111111111111111111111111111111111111");
631
632 #[test]
633 fn decode_compressed_point() {
634 let compressed_even_y_bytes =
636 hex!("020100000000000000000000000000000000000000000000000000000000000000");
637
638 let compressed_even_y = EncodedPoint::from_bytes(&compressed_even_y_bytes[..]).unwrap();
639
640 assert!(compressed_even_y.is_compressed());
641 assert_eq!(compressed_even_y.tag(), Tag::CompressedEvenY);
642 assert_eq!(compressed_even_y.len(), 33);
643 assert_eq!(compressed_even_y.as_bytes(), &compressed_even_y_bytes[..]);
644
645 assert_eq!(
646 compressed_even_y.coordinates(),
647 Coordinates::Compressed {
648 x: &hex!("0100000000000000000000000000000000000000000000000000000000000000").into(),
649 y_is_odd: false
650 }
651 );
652
653 assert_eq!(
654 compressed_even_y.x().unwrap(),
655 &hex!("0100000000000000000000000000000000000000000000000000000000000000").into()
656 );
657 assert_eq!(compressed_even_y.y(), None);
658
659 let compressed_odd_y_bytes =
661 hex!("030200000000000000000000000000000000000000000000000000000000000000");
662
663 let compressed_odd_y = EncodedPoint::from_bytes(&compressed_odd_y_bytes[..]).unwrap();
664
665 assert!(compressed_odd_y.is_compressed());
666 assert_eq!(compressed_odd_y.tag(), Tag::CompressedOddY);
667 assert_eq!(compressed_odd_y.len(), 33);
668 assert_eq!(compressed_odd_y.as_bytes(), &compressed_odd_y_bytes[..]);
669
670 assert_eq!(
671 compressed_odd_y.coordinates(),
672 Coordinates::Compressed {
673 x: &hex!("0200000000000000000000000000000000000000000000000000000000000000").into(),
674 y_is_odd: true
675 }
676 );
677
678 assert_eq!(
679 compressed_odd_y.x().unwrap(),
680 &hex!("0200000000000000000000000000000000000000000000000000000000000000").into()
681 );
682 assert_eq!(compressed_odd_y.y(), None);
683 }
684
685 #[test]
686 fn decode_uncompressed_point() {
687 let uncompressed_point = EncodedPoint::from_bytes(&UNCOMPRESSED_BYTES[..]).unwrap();
688
689 assert!(!uncompressed_point.is_compressed());
690 assert_eq!(uncompressed_point.tag(), Tag::Uncompressed);
691 assert_eq!(uncompressed_point.len(), 65);
692 assert_eq!(uncompressed_point.as_bytes(), &UNCOMPRESSED_BYTES[..]);
693
694 assert_eq!(
695 uncompressed_point.coordinates(),
696 Coordinates::Uncompressed {
697 x: &hex!("1111111111111111111111111111111111111111111111111111111111111111").into(),
698 y: &hex!("2222222222222222222222222222222222222222222222222222222222222222").into()
699 }
700 );
701
702 assert_eq!(
703 uncompressed_point.x().unwrap(),
704 &hex!("1111111111111111111111111111111111111111111111111111111111111111").into()
705 );
706 assert_eq!(
707 uncompressed_point.y().unwrap(),
708 &hex!("2222222222222222222222222222222222222222222222222222222222222222").into()
709 );
710 }
711
712 #[test]
713 fn decode_identity() {
714 let identity_point = EncodedPoint::from_bytes(&IDENTITY_BYTES[..]).unwrap();
715 assert!(identity_point.is_identity());
716 assert_eq!(identity_point.tag(), Tag::Identity);
717 assert_eq!(identity_point.len(), 1);
718 assert_eq!(identity_point.as_bytes(), &IDENTITY_BYTES[..]);
719 assert_eq!(identity_point.coordinates(), Coordinates::Identity);
720 assert_eq!(identity_point.x(), None);
721 assert_eq!(identity_point.y(), None);
722 }
723
724 #[test]
725 fn decode_invalid_tag() {
726 let mut compressed_bytes = COMPRESSED_BYTES.clone();
727 let mut uncompressed_bytes = UNCOMPRESSED_BYTES.clone();
728
729 for bytes in &mut [&mut compressed_bytes[..], &mut uncompressed_bytes[..]] {
730 for tag in 0..=0xFF {
731 if tag == 2 || tag == 3 || tag == 4 || tag == 5 {
733 continue;
734 }
735
736 (*bytes)[0] = tag;
737 let decode_result = EncodedPoint::from_bytes(&*bytes);
738 assert!(decode_result.is_err());
739 }
740 }
741 }
742
743 #[test]
744 fn decode_truncated_point() {
745 for bytes in &[&COMPRESSED_BYTES[..], &UNCOMPRESSED_BYTES[..]] {
746 for len in 0..bytes.len() {
747 let decode_result = EncodedPoint::from_bytes(&bytes[..len]);
748 assert!(decode_result.is_err());
749 }
750 }
751 }
752
753 #[test]
754 fn from_untagged_point() {
755 let untagged_bytes = hex!("11111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222222");
756 let uncompressed_point =
757 EncodedPoint::from_untagged_bytes(GenericArray::from_slice(&untagged_bytes[..]));
758 assert_eq!(uncompressed_point.as_bytes(), &UNCOMPRESSED_BYTES[..]);
759 }
760
761 #[test]
762 fn from_affine_coordinates() {
763 let x = hex!("1111111111111111111111111111111111111111111111111111111111111111");
764 let y = hex!("2222222222222222222222222222222222222222222222222222222222222222");
765
766 let uncompressed_point = EncodedPoint::from_affine_coordinates(&x.into(), &y.into(), false);
767 assert_eq!(uncompressed_point.as_bytes(), &UNCOMPRESSED_BYTES[..]);
768
769 let compressed_point = EncodedPoint::from_affine_coordinates(&x.into(), &y.into(), true);
770 assert_eq!(compressed_point.as_bytes(), &COMPRESSED_BYTES[..]);
771 }
772
773 #[test]
774 fn compress() {
775 let uncompressed_point = EncodedPoint::from_bytes(&UNCOMPRESSED_BYTES[..]).unwrap();
776 let compressed_point = uncompressed_point.compress();
777 assert_eq!(compressed_point.as_bytes(), &COMPRESSED_BYTES[..]);
778 }
779
780 #[cfg(feature = "subtle")]
781 #[test]
782 fn conditional_select() {
783 let a = EncodedPoint::from_bytes(&COMPRESSED_BYTES[..]).unwrap();
784 let b = EncodedPoint::from_bytes(&UNCOMPRESSED_BYTES[..]).unwrap();
785
786 let a_selected = EncodedPoint::conditional_select(&a, &b, 0.into());
787 assert_eq!(a, a_selected);
788
789 let b_selected = EncodedPoint::conditional_select(&a, &b, 1.into());
790 assert_eq!(b, b_selected);
791 }
792
793 #[test]
794 fn identity() {
795 let identity_point = EncodedPoint::identity();
796 assert_eq!(identity_point.tag(), Tag::Identity);
797 assert_eq!(identity_point.len(), 1);
798 assert_eq!(identity_point.as_bytes(), &IDENTITY_BYTES[..]);
799
800 assert_eq!(identity_point, EncodedPoint::default());
802 }
803
804 #[test]
805 fn decode_hex() {
806 let point = EncodedPoint::from_str(
807 "021111111111111111111111111111111111111111111111111111111111111111",
808 )
809 .unwrap();
810 assert_eq!(point.as_bytes(), COMPRESSED_BYTES);
811 }
812
813 #[cfg(feature = "alloc")]
814 #[test]
815 fn to_bytes() {
816 let uncompressed_point = EncodedPoint::from_bytes(&UNCOMPRESSED_BYTES[..]).unwrap();
817 assert_eq!(&*uncompressed_point.to_bytes(), &UNCOMPRESSED_BYTES[..]);
818 }
819
820 #[cfg(feature = "alloc")]
821 #[test]
822 fn to_string() {
823 let point = EncodedPoint::from_bytes(&COMPRESSED_BYTES[..]).unwrap();
824 assert_eq!(
825 point.to_string(),
826 "021111111111111111111111111111111111111111111111111111111111111111"
827 );
828 }
829}