1use crate::error::{ParseError, PolicyError, SignError};
39use crate::types::{ParsedTx, PolicyResult};
40
41pub type SignatureBytes = [u8; 64];
51
52#[derive(Debug, thiserror::Error)]
64pub enum SigningError {
65 #[error("failed to parse transaction: {0}")]
69 ParseError(#[from] ParseError),
70
71 #[error("policy check failed: {0}")]
76 PolicyError(#[from] PolicyError),
77
78 #[error("signing failed: {0}")]
82 SignError(#[from] SignError),
83
84 #[error("transaction denied by policy: {reason}")]
89 PolicyDenied {
90 reason: String,
92 },
93}
94
95impl SigningError {
96 #[must_use]
98 pub fn policy_denied(reason: impl Into<String>) -> Self {
99 Self::PolicyDenied {
100 reason: reason.into(),
101 }
102 }
103
104 #[must_use]
106 pub const fn is_policy_denied(&self) -> bool {
107 matches!(self, Self::PolicyDenied { .. })
108 }
109
110 #[must_use]
112 pub fn denial_reason(&self) -> Option<&str> {
113 match self {
114 Self::PolicyDenied { reason } => Some(reason),
115 _ => None,
116 }
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
129pub enum PolicyCheckResult {
130 Allowed,
132 Denied {
134 rule: String,
136 reason: String,
138 },
139}
140
141impl PolicyCheckResult {
142 #[must_use]
144 pub const fn is_allowed(&self) -> bool {
145 matches!(self, Self::Allowed)
146 }
147
148 #[must_use]
150 pub const fn is_denied(&self) -> bool {
151 matches!(self, Self::Denied { .. })
152 }
153
154 #[must_use]
156 pub fn denied(rule: impl Into<String>, reason: impl Into<String>) -> Self {
157 Self::Denied {
158 rule: rule.into(),
159 reason: reason.into(),
160 }
161 }
162}
163
164impl From<PolicyResult> for PolicyCheckResult {
165 fn from(result: PolicyResult) -> Self {
166 match result {
167 PolicyResult::Allowed => Self::Allowed,
168 PolicyResult::Denied { rule, reason } => Self::Denied { rule, reason },
169 }
170 }
171}
172
173#[derive(Debug, Clone)]
195pub struct SigningResult {
196 pub parsed_tx: ParsedTx,
198
199 pub policy_result: PolicyCheckResult,
201
202 pub signature: Option<SignatureBytes>,
208
209 pub recovery_id: Option<u8>,
214}
215
216impl SigningResult {
217 #[must_use]
219 pub const fn allowed(parsed_tx: ParsedTx, signature: SignatureBytes, recovery_id: u8) -> Self {
220 Self {
221 parsed_tx,
222 policy_result: PolicyCheckResult::Allowed,
223 signature: Some(signature),
224 recovery_id: Some(recovery_id),
225 }
226 }
227
228 #[must_use]
230 pub const fn checked(parsed_tx: ParsedTx, policy_result: PolicyCheckResult) -> Self {
231 Self {
232 parsed_tx,
233 policy_result,
234 signature: None,
235 recovery_id: None,
236 }
237 }
238
239 #[must_use]
241 pub const fn is_allowed(&self) -> bool {
242 self.policy_result.is_allowed()
243 }
244
245 #[must_use]
247 pub const fn has_signature(&self) -> bool {
248 self.signature.is_some()
249 }
250
251 #[must_use]
255 pub fn signature_with_recovery_id(&self) -> Option<[u8; 65]> {
256 match (self.signature, self.recovery_id) {
257 (Some(sig), Some(v)) => {
258 let mut result = [0u8; 65];
259 result[..64].copy_from_slice(&sig);
260 result[64] = v;
261 Some(result)
262 }
263 _ => None,
264 }
265 }
266}
267
268pub struct SigningService<C, P, S> {
310 chain: C,
312 policy: P,
314 signer: S,
316}
317
318impl<C, P, S> std::fmt::Debug for SigningService<C, P, S> {
319 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320 f.debug_struct("SigningService")
321 .field("chain", &"<Chain>")
322 .field("policy", &"<PolicyEngine>")
323 .field("signer", &"<Signer>")
324 .finish()
325 }
326}
327
328impl<C, P, S> SigningService<C, P, S> {
329 #[must_use]
343 pub const fn new(chain: C, policy: P, signer: S) -> Self {
344 Self {
345 chain,
346 policy,
347 signer,
348 }
349 }
350
351 #[must_use]
353 pub const fn chain(&self) -> &C {
354 &self.chain
355 }
356
357 #[must_use]
359 pub const fn policy(&self) -> &P {
360 &self.policy
361 }
362
363 #[must_use]
365 pub const fn signer(&self) -> &S {
366 &self.signer
367 }
368}
369
370pub trait ChainParser: Send + Sync {
375 fn parse(&self, raw: &[u8]) -> Result<ParsedTx, ParseError>;
381}
382
383pub trait PolicyEngineExt: Send + Sync {
388 fn check(&self, tx: &ParsedTx) -> Result<PolicyResult, PolicyError>;
394
395 fn record(&self, tx: &ParsedTx) -> Result<(), PolicyError>;
401}
402
403pub trait SignerExt: Send + Sync {
408 fn sign(&self, hash: &[u8; 32]) -> Result<Vec<u8>, SignError>;
417}
418
419impl<C, P, S> SigningService<C, P, S>
420where
421 C: ChainParser,
422 P: PolicyEngineExt,
423 S: SignerExt,
424{
425 pub fn sign(&self, raw_tx: &[u8]) -> Result<SigningResult, SigningError> {
464 let parsed_tx = self.chain.parse(raw_tx)?;
466
467 let policy_result = self.policy.check(&parsed_tx)?;
469
470 if let PolicyResult::Denied { reason, .. } = &policy_result {
472 return Err(SigningError::PolicyDenied {
473 reason: reason.clone(),
474 });
475 }
476
477 let sig_bytes = self.signer.sign(&parsed_tx.hash)?;
479
480 let (signature, recovery_id) = extract_signature_components(&sig_bytes)?;
482
483 self.policy.record(&parsed_tx)?;
485
486 Ok(SigningResult {
488 parsed_tx,
489 policy_result: PolicyCheckResult::Allowed,
490 signature: Some(signature),
491 recovery_id: Some(recovery_id),
492 })
493 }
494
495 pub fn check(&self, raw_tx: &[u8]) -> Result<SigningResult, SigningError> {
533 let parsed_tx = self.chain.parse(raw_tx)?;
535
536 let policy_result = self.policy.check(&parsed_tx)?;
538
539 Ok(SigningResult {
541 parsed_tx,
542 policy_result: policy_result.into(),
543 signature: None,
544 recovery_id: None,
545 })
546 }
547}
548
549fn extract_signature_components(sig: &[u8]) -> Result<(SignatureBytes, u8), SigningError> {
557 let sig_array: [u8; 65] = sig.try_into().map_err(|_| {
559 SigningError::SignError(SignError::signature_failed(format!(
560 "expected 65-byte signature, got {} bytes",
561 sig.len()
562 )))
563 })?;
564
565 let mut signature = [0u8; 64];
566 signature.copy_from_slice(&sig_array[..64]);
567 let recovery_id = sig_array[64];
568
569 Ok((signature, recovery_id))
570}
571
572#[cfg(test)]
577mod tests {
578 #![allow(
579 clippy::expect_used,
580 clippy::unwrap_used,
581 clippy::panic,
582 clippy::indexing_slicing,
583 clippy::large_enum_variant,
584 clippy::redundant_clone,
585 dead_code
586 )]
587
588 use super::*;
589 use crate::types::TxType;
590 use std::collections::HashMap;
591 use std::sync::atomic::{AtomicUsize, Ordering};
592
593 #[derive(Clone)]
599 enum MockChainBehavior {
600 Success(ParsedTx),
601 Failure(MockParseErrorKind),
602 }
603
604 #[derive(Clone, Copy)]
606 enum MockParseErrorKind {
607 UnknownTxType,
608 MalformedTransaction,
609 }
610
611 impl MockParseErrorKind {
612 fn to_error(self) -> ParseError {
613 match self {
614 Self::UnknownTxType => ParseError::UnknownTxType,
615 Self::MalformedTransaction => ParseError::malformed_transaction("mock error"),
616 }
617 }
618 }
619
620 struct MockChain {
622 behavior: MockChainBehavior,
623 }
624
625 impl MockChain {
626 fn success(tx: ParsedTx) -> Self {
627 Self {
628 behavior: MockChainBehavior::Success(tx),
629 }
630 }
631
632 fn failure(kind: MockParseErrorKind) -> Self {
633 Self {
634 behavior: MockChainBehavior::Failure(kind),
635 }
636 }
637 }
638
639 impl ChainParser for MockChain {
640 fn parse(&self, _raw: &[u8]) -> Result<ParsedTx, ParseError> {
641 match &self.behavior {
642 MockChainBehavior::Success(tx) => Ok(tx.clone()),
643 MockChainBehavior::Failure(kind) => Err(kind.to_error()),
644 }
645 }
646 }
647
648 #[derive(Clone)]
650 enum MockPolicyBehavior {
651 Allowed,
652 Denied { rule: String, reason: String },
653 Error(MockPolicyErrorKind),
654 }
655
656 #[derive(Clone, Copy)]
658 enum MockPolicyErrorKind {
659 InvalidConfiguration,
660 }
661
662 impl MockPolicyErrorKind {
663 fn to_error(self) -> PolicyError {
664 match self {
665 Self::InvalidConfiguration => PolicyError::invalid_configuration("mock error"),
666 }
667 }
668 }
669
670 struct MockPolicy {
672 check_behavior: MockPolicyBehavior,
673 record_count: AtomicUsize,
674 }
675
676 impl MockPolicy {
677 fn allowed() -> Self {
678 Self {
679 check_behavior: MockPolicyBehavior::Allowed,
680 record_count: AtomicUsize::new(0),
681 }
682 }
683
684 fn denied(rule: &str, reason: &str) -> Self {
685 Self {
686 check_behavior: MockPolicyBehavior::Denied {
687 rule: rule.to_string(),
688 reason: reason.to_string(),
689 },
690 record_count: AtomicUsize::new(0),
691 }
692 }
693
694 #[allow(dead_code)]
695 fn check_error(kind: MockPolicyErrorKind) -> Self {
696 Self {
697 check_behavior: MockPolicyBehavior::Error(kind),
698 record_count: AtomicUsize::new(0),
699 }
700 }
701
702 fn recorded_count(&self) -> usize {
703 self.record_count.load(Ordering::SeqCst)
704 }
705 }
706
707 impl PolicyEngineExt for MockPolicy {
708 fn check(&self, _tx: &ParsedTx) -> Result<PolicyResult, PolicyError> {
709 match &self.check_behavior {
710 MockPolicyBehavior::Allowed => Ok(PolicyResult::Allowed),
711 MockPolicyBehavior::Denied { rule, reason } => Ok(PolicyResult::Denied {
712 rule: rule.clone(),
713 reason: reason.clone(),
714 }),
715 MockPolicyBehavior::Error(kind) => Err(kind.to_error()),
716 }
717 }
718
719 fn record(&self, _tx: &ParsedTx) -> Result<(), PolicyError> {
720 self.record_count.fetch_add(1, Ordering::SeqCst);
721 Ok(())
722 }
723 }
724
725 enum MockSignerBehavior {
727 Success { recovery_id: u8 },
728 Failure(MockSignErrorKind),
729 }
730
731 #[derive(Clone, Copy)]
733 enum MockSignErrorKind {
734 InvalidKey,
735 }
736
737 impl MockSignErrorKind {
738 fn to_error(self) -> SignError {
739 match self {
740 Self::InvalidKey => SignError::InvalidKey,
741 }
742 }
743 }
744
745 struct MockSigner {
747 behavior: MockSignerBehavior,
748 }
749
750 impl MockSigner {
751 fn success() -> Self {
752 Self {
753 behavior: MockSignerBehavior::Success { recovery_id: 0 },
754 }
755 }
756
757 fn success_with_recovery_id(recovery_id: u8) -> Self {
758 Self {
759 behavior: MockSignerBehavior::Success { recovery_id },
760 }
761 }
762
763 fn failure(kind: MockSignErrorKind) -> Self {
764 Self {
765 behavior: MockSignerBehavior::Failure(kind),
766 }
767 }
768 }
769
770 impl SignerExt for MockSigner {
771 fn sign(&self, _hash: &[u8; 32]) -> Result<Vec<u8>, SignError> {
772 match &self.behavior {
773 MockSignerBehavior::Success { recovery_id } => {
774 let mut sig = vec![0u8; 65];
775 sig[..32].copy_from_slice(&[0xab; 32]); sig[32..64].copy_from_slice(&[0xcd; 32]); sig[64] = *recovery_id; Ok(sig)
779 }
780 MockSignerBehavior::Failure(kind) => Err(kind.to_error()),
781 }
782 }
783 }
784
785 fn test_tx() -> ParsedTx {
787 ParsedTx {
788 hash: [0x42; 32],
789 recipient: Some("0x1234".to_string()),
790 amount: Some(crate::U256::from(100)),
791 token: Some("ETH".to_string()),
792 token_address: None,
793 tx_type: TxType::Transfer,
794 chain: "ethereum".to_string(),
795 nonce: Some(1),
796 chain_id: Some(1),
797 metadata: HashMap::new(),
798 }
799 }
800
801 mod signing_error_tests {
806 use super::*;
807
808 #[test]
809 fn test_from_parse_error() {
810 let err = ParseError::UnknownTxType;
811 let signing_err: SigningError = err.into();
812
813 assert!(matches!(signing_err, SigningError::ParseError(_)));
814 assert!(!signing_err.is_policy_denied());
815 assert!(signing_err.denial_reason().is_none());
816 }
817
818 #[test]
819 fn test_from_policy_error() {
820 let err = PolicyError::invalid_configuration("test");
821 let signing_err: SigningError = err.into();
822
823 assert!(matches!(signing_err, SigningError::PolicyError(_)));
824 assert!(!signing_err.is_policy_denied());
825 }
826
827 #[test]
828 fn test_from_sign_error() {
829 let err = SignError::InvalidKey;
830 let signing_err: SigningError = err.into();
831
832 assert!(matches!(signing_err, SigningError::SignError(_)));
833 assert!(!signing_err.is_policy_denied());
834 }
835
836 #[test]
837 fn test_policy_denied() {
838 let err = SigningError::policy_denied("blacklisted address");
839
840 assert!(err.is_policy_denied());
841 assert_eq!(err.denial_reason(), Some("blacklisted address"));
842 assert!(err.to_string().contains("denied by policy"));
843 }
844
845 #[test]
846 fn test_error_display() {
847 let parse_err: SigningError = ParseError::UnknownTxType.into();
848 assert!(parse_err.to_string().contains("parse transaction"));
849
850 let policy_err: SigningError = PolicyError::invalid_configuration("test").into();
851 assert!(policy_err.to_string().contains("policy check failed"));
852
853 let sign_err: SigningError = SignError::InvalidKey.into();
854 assert!(sign_err.to_string().contains("signing failed"));
855
856 let denied_err = SigningError::policy_denied("test reason");
857 assert!(denied_err.to_string().contains("denied by policy"));
858 assert!(denied_err.to_string().contains("test reason"));
859 }
860 }
861
862 mod policy_check_result_tests {
867 use super::*;
868
869 #[test]
870 fn test_allowed() {
871 let result = PolicyCheckResult::Allowed;
872 assert!(result.is_allowed());
873 assert!(!result.is_denied());
874 }
875
876 #[test]
877 fn test_denied() {
878 let result = PolicyCheckResult::denied("blacklist", "address blocked");
879 assert!(!result.is_allowed());
880 assert!(result.is_denied());
881 }
882
883 #[test]
884 fn test_from_policy_result_allowed() {
885 let policy_result = PolicyResult::Allowed;
886 let check_result: PolicyCheckResult = policy_result.into();
887 assert!(check_result.is_allowed());
888 }
889
890 #[test]
891 fn test_from_policy_result_denied() {
892 let policy_result = PolicyResult::Denied {
893 rule: "whitelist".to_string(),
894 reason: "not in list".to_string(),
895 };
896 let check_result: PolicyCheckResult = policy_result.into();
897 assert!(check_result.is_denied());
898
899 if let PolicyCheckResult::Denied { rule, reason } = check_result {
900 assert_eq!(rule, "whitelist");
901 assert_eq!(reason, "not in list");
902 } else {
903 panic!("expected Denied variant");
904 }
905 }
906 }
907
908 mod signing_result_tests {
913 use super::*;
914
915 #[test]
916 fn test_allowed_constructor() {
917 let tx = test_tx();
918 let sig = [0xab; 64];
919 let result = SigningResult::allowed(tx.clone(), sig, 0);
920
921 assert!(result.is_allowed());
922 assert!(result.has_signature());
923 assert_eq!(result.signature, Some(sig));
924 assert_eq!(result.recovery_id, Some(0));
925 assert_eq!(result.parsed_tx.hash, tx.hash);
926 }
927
928 #[test]
929 fn test_checked_constructor() {
930 let tx = test_tx();
931 let result = SigningResult::checked(tx.clone(), PolicyCheckResult::Allowed);
932
933 assert!(result.is_allowed());
934 assert!(!result.has_signature());
935 assert!(result.signature.is_none());
936 assert!(result.recovery_id.is_none());
937 }
938
939 #[test]
940 fn test_signature_with_recovery_id() {
941 let tx = test_tx();
942 let sig = [0xab; 64];
943 let result = SigningResult::allowed(tx, sig, 1);
944
945 let full_sig = result.signature_with_recovery_id().unwrap();
946 assert_eq!(full_sig.len(), 65);
947 assert_eq!(&full_sig[..64], &sig);
948 assert_eq!(full_sig[64], 1);
949 }
950
951 #[test]
952 fn test_signature_with_recovery_id_none() {
953 let tx = test_tx();
954 let result = SigningResult::checked(tx, PolicyCheckResult::Allowed);
955
956 assert!(result.signature_with_recovery_id().is_none());
957 }
958 }
959
960 mod signing_service_tests {
965 use super::*;
966
967 #[test]
968 fn test_successful_signing_flow() {
969 let tx = test_tx();
970 let chain = MockChain::success(tx.clone());
971 let policy = MockPolicy::allowed();
972 let signer = MockSigner::success();
973
974 let service = SigningService::new(chain, policy, signer);
975 let result = service.sign(&[0x01, 0x02, 0x03]).unwrap();
976
977 assert!(result.is_allowed());
978 assert!(result.has_signature());
979 assert!(result.signature.is_some());
980 assert_eq!(result.recovery_id, Some(0));
981 assert_eq!(result.parsed_tx.hash, tx.hash);
982 }
983
984 #[test]
985 fn test_policy_denial_flow() {
986 let tx = test_tx();
987 let chain = MockChain::success(tx);
988 let policy = MockPolicy::denied("blacklist", "address is blacklisted");
989 let signer = MockSigner::success();
990
991 let service = SigningService::new(chain, policy, signer);
992 let result = service.sign(&[0x01]);
993
994 assert!(result.is_err());
995 let err = result.unwrap_err();
996 assert!(err.is_policy_denied());
997 assert_eq!(err.denial_reason(), Some("address is blacklisted"));
998 }
999
1000 #[test]
1001 fn test_parse_error_handling() {
1002 let chain = MockChain::failure(MockParseErrorKind::UnknownTxType);
1003 let policy = MockPolicy::allowed();
1004 let signer = MockSigner::success();
1005
1006 let service = SigningService::new(chain, policy, signer);
1007 let result = service.sign(&[0x01]);
1008
1009 assert!(result.is_err());
1010 assert!(matches!(result.unwrap_err(), SigningError::ParseError(_)));
1011 }
1012
1013 #[test]
1014 fn test_sign_error_handling() {
1015 let tx = test_tx();
1016 let chain = MockChain::success(tx);
1017 let policy = MockPolicy::allowed();
1018 let signer = MockSigner::failure(MockSignErrorKind::InvalidKey);
1019
1020 let service = SigningService::new(chain, policy, signer);
1021 let result = service.sign(&[0x01]);
1022
1023 assert!(result.is_err());
1024 assert!(matches!(result.unwrap_err(), SigningError::SignError(_)));
1025 }
1026
1027 #[test]
1028 fn test_dry_run_check() {
1029 let tx = test_tx();
1030 let chain = MockChain::success(tx.clone());
1031 let policy = MockPolicy::allowed();
1032 let signer = MockSigner::success();
1033
1034 let service = SigningService::new(chain, policy, signer);
1035 let result = service.check(&[0x01]).unwrap();
1036
1037 assert!(result.is_allowed());
1038 assert!(!result.has_signature());
1039 assert!(result.signature.is_none());
1040 assert_eq!(result.parsed_tx.hash, tx.hash);
1041 }
1042
1043 #[test]
1044 fn test_dry_run_check_denied() {
1045 let tx = test_tx();
1046 let chain = MockChain::success(tx);
1047 let policy = MockPolicy::denied("tx_limit", "exceeds limit");
1048 let signer = MockSigner::success();
1049
1050 let service = SigningService::new(chain, policy, signer);
1051 let result = service.check(&[0x01]).unwrap();
1052
1053 assert!(!result.is_allowed());
1055 assert!(!result.has_signature());
1056 }
1057
1058 #[test]
1059 fn test_record_called_on_success() {
1060 let tx = test_tx();
1061 let chain = MockChain::success(tx);
1062 let policy = MockPolicy::allowed();
1063 let signer = MockSigner::success();
1064
1065 let service = SigningService::new(chain, policy, signer);
1066
1067 assert_eq!(service.policy().recorded_count(), 0);
1069
1070 service.sign(&[0x01]).unwrap();
1072
1073 assert_eq!(service.policy().recorded_count(), 1);
1075 }
1076
1077 #[test]
1078 fn test_record_not_called_on_denial() {
1079 let tx = test_tx();
1080 let chain = MockChain::success(tx);
1081 let policy = MockPolicy::denied("test", "denied");
1082 let signer = MockSigner::success();
1083
1084 let service = SigningService::new(chain, policy, signer);
1085
1086 let _ = service.sign(&[0x01]);
1088
1089 assert_eq!(service.policy().recorded_count(), 0);
1091 }
1092
1093 #[test]
1094 fn test_record_not_called_on_check() {
1095 let tx = test_tx();
1096 let chain = MockChain::success(tx);
1097 let policy = MockPolicy::allowed();
1098 let signer = MockSigner::success();
1099
1100 let service = SigningService::new(chain, policy, signer);
1101
1102 service.check(&[0x01]).unwrap();
1104
1105 assert_eq!(service.policy().recorded_count(), 0);
1107 }
1108
1109 #[test]
1110 fn test_recovery_id_passed_through() {
1111 let tx = test_tx();
1112 let chain = MockChain::success(tx);
1113 let policy = MockPolicy::allowed();
1114 let signer = MockSigner::success_with_recovery_id(1);
1115
1116 let service = SigningService::new(chain, policy, signer);
1117 let result = service.sign(&[0x01]).unwrap();
1118
1119 assert_eq!(result.recovery_id, Some(1));
1120 }
1121
1122 #[test]
1123 fn test_accessors() {
1124 let tx = test_tx();
1125 let chain = MockChain::success(tx);
1126 let policy = MockPolicy::allowed();
1127 let signer = MockSigner::success();
1128
1129 let service = SigningService::new(chain, policy, signer);
1130
1131 let _ = service.chain();
1133 let _ = service.policy();
1134 let _ = service.signer();
1135 }
1136
1137 #[test]
1138 fn test_debug_impl() {
1139 let tx = test_tx();
1140 let chain = MockChain::success(tx);
1141 let policy = MockPolicy::allowed();
1142 let signer = MockSigner::success();
1143
1144 let service = SigningService::new(chain, policy, signer);
1145 let debug_str = format!("{service:?}");
1146
1147 assert!(debug_str.contains("SigningService"));
1148 }
1149 }
1150
1151 mod extract_signature_tests {
1156 use super::*;
1157
1158 #[test]
1159 fn test_valid_65_byte_signature() {
1160 let mut sig = vec![0u8; 65];
1161 sig[..32].copy_from_slice(&[0xaa; 32]);
1162 sig[32..64].copy_from_slice(&[0xbb; 32]);
1163 sig[64] = 1;
1164
1165 let (signature, recovery_id) = extract_signature_components(&sig).unwrap();
1166
1167 assert_eq!(&signature[..32], &[0xaa; 32]);
1168 assert_eq!(&signature[32..64], &[0xbb; 32]);
1169 assert_eq!(recovery_id, 1);
1170 }
1171
1172 #[test]
1173 fn test_invalid_length_too_short() {
1174 let sig = vec![0u8; 64];
1175 let result = extract_signature_components(&sig);
1176
1177 assert!(result.is_err());
1178 assert!(matches!(result.unwrap_err(), SigningError::SignError(_)));
1179 }
1180
1181 #[test]
1182 fn test_invalid_length_too_long() {
1183 let sig = vec![0u8; 66];
1184 let result = extract_signature_components(&sig);
1185
1186 assert!(result.is_err());
1187 }
1188 }
1189
1190 mod send_sync_tests {
1195 use super::*;
1196
1197 #[test]
1198 fn test_signing_error_is_send_sync() {
1199 fn assert_send_sync<T: Send + Sync>() {}
1200 assert_send_sync::<SigningError>();
1201 }
1202
1203 #[test]
1204 fn test_signing_result_is_send_sync() {
1205 fn assert_send_sync<T: Send + Sync>() {}
1206 assert_send_sync::<SigningResult>();
1207 }
1208
1209 #[test]
1210 fn test_policy_check_result_is_send_sync() {
1211 fn assert_send_sync<T: Send + Sync>() {}
1212 assert_send_sync::<PolicyCheckResult>();
1213 }
1214
1215 }
1219}