1use crate::error::SignerError;
7use crate::threshold::frost::keygen::{derive_interpolating_value, KeyPackage};
8#[allow(unused_imports)]
9use k256::elliptic_curve::group::GroupEncoding;
10use k256::elliptic_curve::ops::Reduce;
11use k256::elliptic_curve::sec1::ToEncodedPoint;
12use k256::{AffinePoint, ProjectivePoint, Scalar};
13use sha2::{Digest, Sha256};
14use zeroize::Zeroizing;
15
16const CONTEXT_STRING: &[u8] = b"FROST-secp256k1-SHA256-v1";
18
19pub struct SigningNonces {
23 pub(crate) hiding: Zeroizing<Scalar>,
25 pub(crate) binding: Zeroizing<Scalar>,
27 pub commitments: SigningCommitments,
29}
30
31impl Drop for SigningNonces {
32 fn drop(&mut self) {
33 }
35}
36
37#[derive(Clone, Debug)]
39pub struct SigningCommitments {
40 pub identifier: u16,
42 pub hiding: AffinePoint,
44 pub binding: AffinePoint,
46}
47
48#[derive(Clone)]
50pub struct SignatureShare {
51 pub identifier: u16,
53 pub share: Scalar,
55}
56
57impl core::fmt::Debug for SignatureShare {
58 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
59 f.debug_struct("SignatureShare")
60 .field("identifier", &self.identifier)
61 .field("share", &"[REDACTED]")
62 .finish()
63 }
64}
65
66#[derive(Clone, Debug)]
68#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
69pub struct FrostSignature {
70 pub r_bytes: Vec<u8>,
72 pub s_bytes: [u8; 32],
74}
75
76impl FrostSignature {
77 pub fn to_bytes(&self) -> Vec<u8> {
79 let mut out = Vec::with_capacity(65);
80 out.extend_from_slice(&self.r_bytes);
81 out.extend_from_slice(&self.s_bytes);
82 out
83 }
84}
85
86fn h1(data: &[u8]) -> Scalar {
90 hash_to_scalar(b"rho", data)
91}
92
93fn h2(data: &[u8]) -> Scalar {
95 hash_to_scalar(b"chal", data)
96}
97
98#[allow(dead_code)]
100fn h3(data: &[u8]) -> Scalar {
101 hash_to_scalar(b"nonce", data)
102}
103
104fn h4(data: &[u8]) -> [u8; 32] {
106 hash_to_bytes(b"msg", data)
107}
108
109fn h5(data: &[u8]) -> [u8; 32] {
111 hash_to_bytes(b"com", data)
112}
113
114fn hash_to_scalar(tag: &[u8], data: &[u8]) -> Scalar {
118 let mut dst = Vec::with_capacity(CONTEXT_STRING.len() + tag.len());
120 dst.extend_from_slice(CONTEXT_STRING);
121 dst.extend_from_slice(tag);
122
123 let expanded = expand_message_xmd(data, &dst, 48);
125
126 let mut wide = [0u8; 48];
128 wide.copy_from_slice(&expanded);
129 scalar_from_wide(&wide)
130}
131
132fn hash_to_bytes(tag: &[u8], data: &[u8]) -> [u8; 32] {
134 let mut h = Sha256::new();
135 h.update(CONTEXT_STRING);
136 h.update(tag);
137 h.update(data);
138 let result = h.finalize();
139 let mut out = [0u8; 32];
140 out.copy_from_slice(&result);
141 out
142}
143
144fn expand_message_xmd(msg: &[u8], dst: &[u8], len_in_bytes: usize) -> Vec<u8> {
146 let b_in_bytes = 32usize; let ell = len_in_bytes.div_ceil(b_in_bytes);
148
149 let dst_prime_len = dst.len() as u8;
151
152 let z_pad = [0u8; 64];
154
155 let l_i_b = (len_in_bytes as u16).to_be_bytes();
157
158 let mut h0 = Sha256::new();
160 h0.update(z_pad);
161 h0.update(msg);
162 h0.update(l_i_b);
163 h0.update([0u8]);
164 h0.update(dst);
165 h0.update([dst_prime_len]);
166 let b_0 = h0.finalize();
167
168 let mut h1 = Sha256::new();
170 h1.update(b_0);
171 h1.update([1u8]);
172 h1.update(dst);
173 h1.update([dst_prime_len]);
174 let mut b_vals = vec![h1.finalize()];
175
176 for i in 2..=ell {
177 let prev = &b_vals[i - 2];
179 let mut xored = [0u8; 32];
180 for (j, byte) in xored.iter_mut().enumerate() {
181 *byte = b_0[j] ^ prev[j];
182 }
183 let mut hi = Sha256::new();
184 hi.update(xored);
185 hi.update([i as u8]);
186 hi.update(dst);
187 hi.update([dst_prime_len]);
188 b_vals.push(hi.finalize());
189 }
190
191 let mut output = Vec::with_capacity(len_in_bytes);
192 for b in &b_vals {
193 output.extend_from_slice(b);
194 }
195 output.truncate(len_in_bytes);
196 output
197}
198
199fn scalar_from_wide(bytes: &[u8; 48]) -> Scalar {
201 let mut u256_hi = [0u8; 32];
204 let mut u256_lo = [0u8; 32];
205 u256_hi[16..].copy_from_slice(&bytes[..16]);
206 u256_lo.copy_from_slice(&bytes[16..]);
207
208 let hi = k256::U256::from_be_slice(&u256_hi);
209 let lo = k256::U256::from_be_slice(&u256_lo);
210
211 let hi_scalar = <Scalar as Reduce<k256::U256>>::reduce(hi);
213 let lo_scalar = <Scalar as Reduce<k256::U256>>::reduce(lo);
214
215 let two_256_mod_n = {
217 let _bytes = [0u8; 32];
218 let max = k256::U256::from_be_hex(
221 "0000000000000000000000000000000100000000000000000000000000000000",
222 );
223 <Scalar as Reduce<k256::U256>>::reduce(max)
224 };
225
226 hi_scalar * two_256_mod_n + lo_scalar
227}
228
229pub fn commit(key_package: &KeyPackage) -> Result<SigningNonces, SignerError> {
236 let hiding = crate::threshold::frost::keygen::random_scalar()?;
237 let binding = crate::threshold::frost::keygen::random_scalar()?;
238
239 let hiding_commitment = (ProjectivePoint::GENERATOR * hiding).to_affine();
240 let binding_commitment = (ProjectivePoint::GENERATOR * binding).to_affine();
241
242 Ok(SigningNonces {
243 hiding: Zeroizing::new(hiding),
244 binding: Zeroizing::new(binding),
245 commitments: SigningCommitments {
246 identifier: key_package.identifier,
247 hiding: hiding_commitment,
248 binding: binding_commitment,
249 },
250 })
251}
252
253fn compute_binding_factor(
257 group_public_key: &AffinePoint,
258 commitments_list: &[SigningCommitments],
259 identifier: u16,
260 message: &[u8],
261) -> Scalar {
262 let mut commit_data = Vec::new();
264 for c in commitments_list {
265 let hiding_enc = ProjectivePoint::from(c.hiding)
266 .to_affine()
267 .to_encoded_point(true);
268 let binding_enc = ProjectivePoint::from(c.binding)
269 .to_affine()
270 .to_encoded_point(true);
271 commit_data.extend_from_slice(hiding_enc.as_bytes());
272 commit_data.extend_from_slice(binding_enc.as_bytes());
273 }
274 let encoded_commitments_hash = h5(&commit_data);
275
276 let msg_hash = h4(message);
278
279 let pk_enc = ProjectivePoint::from(*group_public_key)
281 .to_affine()
282 .to_encoded_point(true);
283 let mut input = Vec::new();
284 input.extend_from_slice(pk_enc.as_bytes());
285 input.extend_from_slice(&msg_hash);
286 input.extend_from_slice(&encoded_commitments_hash);
287
288 let mut id_bytes = [0u8; 32];
290 let id_be = (identifier as u64).to_be_bytes();
291 id_bytes[24..].copy_from_slice(&id_be);
292 input.extend_from_slice(&id_bytes);
293
294 h1(&input)
295}
296
297fn compute_group_commitment(
299 commitments_list: &[SigningCommitments],
300 binding_factors: &[(u16, Scalar)],
301) -> ProjectivePoint {
302 let mut r = ProjectivePoint::IDENTITY;
303
304 for c in commitments_list {
305 let rho = binding_factors
306 .iter()
307 .find(|(id, _)| *id == c.identifier)
308 .map(|(_, bf)| *bf)
309 .unwrap_or(Scalar::ZERO);
310
311 r += ProjectivePoint::from(c.hiding) + ProjectivePoint::from(c.binding) * rho;
312 }
313
314 r
315}
316
317pub fn sign(
324 key_package: &KeyPackage,
325 nonces: SigningNonces,
326 commitments_list: &[SigningCommitments],
327 message: &[u8],
328) -> Result<SignatureShare, SignerError> {
329 let mut binding_factors = Vec::new();
331 for c in commitments_list {
332 let bf = compute_binding_factor(
333 &key_package.group_public_key,
334 commitments_list,
335 c.identifier,
336 message,
337 );
338 binding_factors.push((c.identifier, bf));
339 }
340
341 let group_commitment = compute_group_commitment(commitments_list, &binding_factors);
343 let r_enc = group_commitment.to_affine().to_encoded_point(true);
344
345 let pk_enc = ProjectivePoint::from(key_package.group_public_key)
347 .to_affine()
348 .to_encoded_point(true);
349 let mut challenge_input = Vec::new();
350 challenge_input.extend_from_slice(r_enc.as_bytes());
351 challenge_input.extend_from_slice(pk_enc.as_bytes());
352 challenge_input.extend_from_slice(message);
353 let challenge = h2(&challenge_input);
354
355 let my_rho = binding_factors
357 .iter()
358 .find(|(id, _)| *id == key_package.identifier)
359 .map(|(_, bf)| *bf)
360 .ok_or_else(|| SignerError::SigningFailed("participant not in commitments list".into()))?;
361
362 let participant_ids: Vec<Scalar> = commitments_list
364 .iter()
365 .map(|c| Scalar::from(u64::from(c.identifier)))
366 .collect();
367 let lambda = derive_interpolating_value(
368 &Scalar::from(u64::from(key_package.identifier)),
369 &participant_ids,
370 )?;
371
372 let z = *nonces.hiding
374 + (*nonces.binding * my_rho)
375 + (lambda * *key_package.secret_share() * challenge);
376
377 Ok(SignatureShare {
378 identifier: key_package.identifier,
379 share: z,
380 })
381}
382
383pub fn aggregate(
390 commitments_list: &[SigningCommitments],
391 sig_shares: &[SignatureShare],
392 group_public_key: &AffinePoint,
393 message: &[u8],
394) -> Result<FrostSignature, SignerError> {
395 if sig_shares.len() < 2 {
396 return Err(SignerError::SigningFailed(
397 "need at least 2 signature shares".into(),
398 ));
399 }
400
401 let mut binding_factors = Vec::new();
403 for c in commitments_list {
404 let bf = compute_binding_factor(group_public_key, commitments_list, c.identifier, message);
405 binding_factors.push((c.identifier, bf));
406 }
407
408 let group_commitment = compute_group_commitment(commitments_list, &binding_factors);
410
411 let mut s = Scalar::ZERO;
413 for share in sig_shares {
414 s += share.share;
415 }
416
417 let r_enc = group_commitment.to_affine().to_encoded_point(true);
418
419 Ok(FrostSignature {
420 r_bytes: r_enc.as_bytes().to_vec(),
421 s_bytes: s.to_bytes().into(),
422 })
423}
424
425pub fn verify(
429 signature: &FrostSignature,
430 group_public_key: &AffinePoint,
431 message: &[u8],
432) -> Result<bool, SignerError> {
433 let r_ct = AffinePoint::from_bytes(signature.r_bytes.as_slice().into());
435 if !bool::from(r_ct.is_some()) {
436 return Ok(false);
437 }
438 #[allow(clippy::unwrap_used)]
440 let r_point = ProjectivePoint::from(r_ct.unwrap());
441
442 let s_wide = k256::U256::from_be_slice(&signature.s_bytes);
444 let s_scalar = <Scalar as Reduce<k256::U256>>::reduce(s_wide);
445
446 let r_enc = r_point.to_affine().to_encoded_point(true);
448 let pk_enc = ProjectivePoint::from(*group_public_key)
449 .to_affine()
450 .to_encoded_point(true);
451 let mut challenge_input = Vec::new();
452 challenge_input.extend_from_slice(r_enc.as_bytes());
453 challenge_input.extend_from_slice(pk_enc.as_bytes());
454 challenge_input.extend_from_slice(message);
455 let challenge = h2(&challenge_input);
456
457 let lhs = ProjectivePoint::GENERATOR * s_scalar;
459 let rhs = r_point + ProjectivePoint::from(*group_public_key) * challenge;
460
461 Ok(lhs == rhs)
462}
463
464pub fn verify_share(
468 share: &SignatureShare,
469 commitment: &SigningCommitments,
470 public_key_share: &AffinePoint,
471 group_public_key: &AffinePoint,
472 commitments_list: &[SigningCommitments],
473 message: &[u8],
474) -> Result<bool, SignerError> {
475 let binding_factor = compute_binding_factor(
477 group_public_key,
478 commitments_list,
479 share.identifier,
480 message,
481 );
482
483 let mut binding_factors = Vec::new();
485 for c in commitments_list {
486 let bf = compute_binding_factor(group_public_key, commitments_list, c.identifier, message);
487 binding_factors.push((c.identifier, bf));
488 }
489
490 let group_commitment = compute_group_commitment(commitments_list, &binding_factors);
492 let r_enc = group_commitment.to_affine().to_encoded_point(true);
493
494 let pk_enc = ProjectivePoint::from(*group_public_key)
496 .to_affine()
497 .to_encoded_point(true);
498 let mut challenge_input = Vec::new();
499 challenge_input.extend_from_slice(r_enc.as_bytes());
500 challenge_input.extend_from_slice(pk_enc.as_bytes());
501 challenge_input.extend_from_slice(message);
502 let challenge = h2(&challenge_input);
503
504 let participant_ids: Vec<Scalar> = commitments_list
506 .iter()
507 .map(|c| Scalar::from(u64::from(c.identifier)))
508 .collect();
509 let lambda =
510 derive_interpolating_value(&Scalar::from(u64::from(share.identifier)), &participant_ids)?;
511
512 let lhs = ProjectivePoint::GENERATOR * share.share;
514
515 let rhs = ProjectivePoint::from(commitment.hiding)
517 + ProjectivePoint::from(commitment.binding) * binding_factor
518 + ProjectivePoint::from(*public_key_share) * (lambda * challenge);
519
520 Ok(lhs == rhs)
521}
522
523pub fn identify_misbehaving(
539 sig_shares: &[SignatureShare],
540 commitments_list: &[SigningCommitments],
541 key_packages: &[KeyPackage],
542 group_public_key: &AffinePoint,
543 message: &[u8],
544) -> Result<Vec<u16>, SignerError> {
545 let mut cheaters = Vec::new();
546
547 for share in sig_shares {
548 let commitment = commitments_list
550 .iter()
551 .find(|c| c.identifier == share.identifier);
552
553 let commitment = match commitment {
554 Some(c) => c,
555 None => {
556 cheaters.push(share.identifier);
557 continue;
558 }
559 };
560
561 let key_package = key_packages
563 .iter()
564 .find(|kp| kp.identifier == share.identifier);
565
566 let key_package = match key_package {
567 Some(kp) => kp,
568 None => {
569 cheaters.push(share.identifier);
570 continue;
571 }
572 };
573
574 let pk_share = key_package.public_key();
575 let valid = verify_share(
576 share,
577 commitment,
578 &pk_share,
579 group_public_key,
580 commitments_list,
581 message,
582 )?;
583
584 if !valid {
585 cheaters.push(share.identifier);
586 }
587 }
588
589 Ok(cheaters)
590}
591
592#[cfg(test)]
593#[allow(clippy::unwrap_used, clippy::expect_used)]
594mod tests {
595 use super::*;
596 use crate::threshold::frost::keygen;
597
598 fn setup_2_of_3() -> (keygen::KeyGenOutput, AffinePoint) {
599 let secret = [0x42u8; 32];
600 let output = keygen::trusted_dealer_keygen(&secret, 2, 3).unwrap();
601 let group_pk = output.group_public_key;
602 (output, group_pk)
603 }
604
605 #[test]
608 fn test_frost_2_of_3_roundtrip() {
609 let (kgen, group_pk) = setup_2_of_3();
610 let msg = b"frost threshold message";
611
612 let nonce1 = commit(&kgen.key_packages[0]).unwrap();
614 let nonce2 = commit(&kgen.key_packages[1]).unwrap();
615 let commitments = vec![nonce1.commitments.clone(), nonce2.commitments.clone()];
616
617 let share1 = sign(&kgen.key_packages[0], nonce1, &commitments, msg).unwrap();
619 let share2 = sign(&kgen.key_packages[1], nonce2, &commitments, msg).unwrap();
620
621 let sig = aggregate(&commitments, &[share1, share2], &group_pk, msg).unwrap();
623 assert_eq!(sig.to_bytes().len(), 65); let valid = verify(&sig, &group_pk, msg).unwrap();
627 assert!(valid, "FROST 2-of-3 signature must verify");
628 }
629
630 #[test]
633 fn test_frost_different_participant_subsets() {
634 let (kgen, group_pk) = setup_2_of_3();
635 let msg = b"subset test";
636
637 let n1 = commit(&kgen.key_packages[0]).unwrap();
639 let n3 = commit(&kgen.key_packages[2]).unwrap();
640 let comms = vec![n1.commitments.clone(), n3.commitments.clone()];
641 let s1 = sign(&kgen.key_packages[0], n1, &comms, msg).unwrap();
642 let s3 = sign(&kgen.key_packages[2], n3, &comms, msg).unwrap();
643 let sig = aggregate(&comms, &[s1, s3], &group_pk, msg).unwrap();
644 assert!(
645 verify(&sig, &group_pk, msg).unwrap(),
646 "subset {{1,3}} must verify"
647 );
648 }
649
650 #[test]
653 fn test_frost_verify_share() {
654 let (kgen, group_pk) = setup_2_of_3();
655 let msg = b"share verify test";
656
657 let n1 = commit(&kgen.key_packages[0]).unwrap();
658 let n2 = commit(&kgen.key_packages[1]).unwrap();
659 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
660 let s1 = sign(&kgen.key_packages[0], n1, &comms, msg).unwrap();
661
662 let pk1 = kgen.key_packages[0].public_key();
664 let valid = verify_share(&s1, &comms[0], &pk1, &group_pk, &comms, msg).unwrap();
665 assert!(valid, "valid share must verify");
666 }
667
668 #[test]
671 fn test_frost_wrong_message_fails() {
672 let (kgen, group_pk) = setup_2_of_3();
673 let msg = b"correct msg";
674
675 let n1 = commit(&kgen.key_packages[0]).unwrap();
676 let n2 = commit(&kgen.key_packages[1]).unwrap();
677 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
678 let s1 = sign(&kgen.key_packages[0], n1, &comms, msg).unwrap();
679 let s2 = sign(&kgen.key_packages[1], n2, &comms, msg).unwrap();
680 let sig = aggregate(&comms, &[s1, s2], &group_pk, msg).unwrap();
681
682 let wrong = verify(&sig, &group_pk, b"wrong msg").unwrap();
683 assert!(!wrong, "wrong message must fail verification");
684 }
685
686 #[test]
689 fn test_frost_different_messages_different_sigs() {
690 let (kgen, group_pk) = setup_2_of_3();
691
692 let make_sig = |m: &[u8]| -> Vec<u8> {
693 let n1 = commit(&kgen.key_packages[0]).unwrap();
694 let n2 = commit(&kgen.key_packages[1]).unwrap();
695 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
696 let s1 = sign(&kgen.key_packages[0], n1, &comms, m).unwrap();
697 let s2 = sign(&kgen.key_packages[1], n2, &comms, m).unwrap();
698 aggregate(&comms, &[s1, s2], &group_pk, m)
699 .unwrap()
700 .to_bytes()
701 };
702
703 let sig_a = make_sig(b"message A");
704 let sig_b = make_sig(b"message B");
705 assert_ne!(sig_a, sig_b);
706 }
707
708 #[test]
711 fn test_frost_vss_commitments_verify() {
712 let (kgen, _) = setup_2_of_3();
713 for pkg in &kgen.key_packages {
714 assert!(
715 kgen.vss_commitments
716 .verify_share(pkg.identifier, pkg.secret_share()),
717 "VSS share must verify for participant {}",
718 pkg.identifier
719 );
720 }
721 }
722
723 #[test]
726 fn test_frost_aggregate_rejects_single_share() {
727 let (kgen, group_pk) = setup_2_of_3();
728 let msg = b"need 2";
729
730 let n1 = commit(&kgen.key_packages[0]).unwrap();
731 let comms = vec![n1.commitments.clone()];
732 let s1 = sign(&kgen.key_packages[0], n1, &comms, msg).unwrap();
733
734 assert!(aggregate(&comms, &[s1], &group_pk, msg).is_err());
736 }
737
738 #[test]
741 fn test_frost_keygen_deterministic() {
742 let secret = [0x42u8; 32];
743 let out1 = keygen::trusted_dealer_keygen(&secret, 2, 3).unwrap();
744 let out2 = keygen::trusted_dealer_keygen(&secret, 2, 3).unwrap();
745 assert_eq!(
746 out1.group_public_key.to_encoded_point(true).as_bytes(),
747 out2.group_public_key.to_encoded_point(true).as_bytes()
748 );
749 }
750
751 #[test]
754 fn test_identify_misbehaving_all_honest() {
755 let (kgen, group_pk) = setup_2_of_3();
756 let msg = b"identifiable abort honest";
757
758 let n1 = commit(&kgen.key_packages[0]).unwrap();
759 let n2 = commit(&kgen.key_packages[1]).unwrap();
760 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
761 let s1 = sign(&kgen.key_packages[0], n1, &comms, msg).unwrap();
762 let s2 = sign(&kgen.key_packages[1], n2, &comms, msg).unwrap();
763
764 let cheaters =
765 identify_misbehaving(&[s1, s2], &comms, &kgen.key_packages, &group_pk, msg).unwrap();
766 assert!(cheaters.is_empty(), "no cheaters expected");
767 }
768
769 #[test]
770 fn test_identify_misbehaving_tampered_share() {
771 let (kgen, group_pk) = setup_2_of_3();
772 let msg = b"identifiable abort tampered";
773
774 let n1 = commit(&kgen.key_packages[0]).unwrap();
775 let n2 = commit(&kgen.key_packages[1]).unwrap();
776 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
777 let s1 = sign(&kgen.key_packages[0], n1, &comms, msg).unwrap();
778 let s2 = sign(&kgen.key_packages[1], n2, &comms, msg).unwrap();
779
780 let tampered_s2 = SignatureShare {
782 identifier: s2.identifier,
783 share: s2.share + Scalar::ONE, };
785
786 let cheaters = identify_misbehaving(
787 &[s1, tampered_s2],
788 &comms,
789 &kgen.key_packages,
790 &group_pk,
791 msg,
792 )
793 .unwrap();
794 assert_eq!(
795 cheaters,
796 vec![2],
797 "participant 2 should be identified as cheater"
798 );
799 }
800
801 #[test]
802 fn test_identify_misbehaving_both_tampered() {
803 let (kgen, group_pk) = setup_2_of_3();
804 let msg = b"both bad";
805
806 let n1 = commit(&kgen.key_packages[0]).unwrap();
807 let n2 = commit(&kgen.key_packages[1]).unwrap();
808 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
809 let s1 = sign(&kgen.key_packages[0], n1, &comms, msg).unwrap();
810 let s2 = sign(&kgen.key_packages[1], n2, &comms, msg).unwrap();
811
812 let bad1 = SignatureShare {
813 identifier: s1.identifier,
814 share: Scalar::ZERO,
815 };
816 let bad2 = SignatureShare {
817 identifier: s2.identifier,
818 share: Scalar::ZERO,
819 };
820
821 let cheaters =
822 identify_misbehaving(&[bad1, bad2], &comms, &kgen.key_packages, &group_pk, msg)
823 .unwrap();
824 assert_eq!(cheaters.len(), 2, "both participants should be identified");
825 }
826}