1use crate::wallet::error::WalletError;
8use crate::wallet::interfaces::*;
9
10fn invalid(field: &str, requirement: &str) -> WalletError {
15 WalletError::InvalidParameter(format!("{}: must be {}", field, requirement))
16}
17
18fn validate_string_length(s: &str, name: &str, min: usize, max: usize) -> Result<(), WalletError> {
19 let len = s.len();
20 if len < min {
21 return Err(invalid(name, &format!("at least {} bytes", min)));
22 }
23 if len > max {
24 return Err(invalid(name, &format!("no more than {} bytes", max)));
25 }
26 Ok(())
27}
28
29fn validate_label(s: &str) -> Result<(), WalletError> {
30 validate_string_length(s, "label", 1, 300)
31}
32
33fn validate_tag(s: &str) -> Result<(), WalletError> {
34 validate_string_length(s, "tag", 1, 300)
35}
36
37fn validate_basket(s: &str) -> Result<(), WalletError> {
38 validate_basket_name(s)
39}
40
41fn validate_description(s: &str, name: &str) -> Result<(), WalletError> {
42 validate_string_length(s, name, 5, 2000)
43}
44
45fn validate_optional_limit(limit: Option<u32>) -> Result<(), WalletError> {
46 if let Some(v) = limit {
47 if !(1..=10000).contains(&v) {
48 return Err(invalid("limit", "between 1 and 10000"));
49 }
50 }
51 Ok(())
52}
53
54pub fn normalize_identifier(s: &str) -> String {
57 s.trim().to_lowercase()
58}
59
60fn validate_protocol_id(protocol: &crate::wallet::types::Protocol) -> Result<(), WalletError> {
61 if protocol.security_level > 2 {
62 return Err(invalid("protocol_id.security_level", "0, 1, or 2"));
63 }
64 let normalized = normalize_identifier(&protocol.protocol);
65 if normalized.is_empty() {
66 return Err(invalid("protocol_id.protocol", "non-empty"));
67 }
68 validate_string_length(&normalized, "protocol_id.protocol", 1, 400)?;
69 if normalized.contains(" ") {
71 return Err(invalid(
72 "protocol_id.protocol",
73 "free of consecutive spaces",
74 ));
75 }
76 if !normalized
78 .chars()
79 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == ' ')
80 {
81 return Err(invalid(
82 "protocol_id.protocol",
83 "only lowercase letters, numbers, and spaces",
84 ));
85 }
86 if normalized.ends_with("protocol") {
88 return Err(invalid(
89 "protocol_id.protocol",
90 "not ending with 'protocol'",
91 ));
92 }
93 if normalized.starts_with('p') {
95 return Err(invalid(
96 "protocol_id.protocol",
97 "not starting with 'p' (reserved per BRC-98)",
98 ));
99 }
100 Ok(())
101}
102
103fn validate_basket_name(s: &str) -> Result<(), WalletError> {
104 let normalized = normalize_identifier(s);
105 validate_string_length(&normalized, "basket", 5, 300)?;
106 if !normalized
108 .chars()
109 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == ' ')
110 {
111 return Err(invalid(
112 "basket",
113 "only lowercase letters, numbers, and spaces",
114 ));
115 }
116 if normalized.contains(" ") {
118 return Err(invalid("basket", "free of consecutive spaces"));
119 }
120 if normalized.ends_with("basket") {
122 return Err(invalid("basket", "not ending with 'basket'"));
123 }
124 if normalized.starts_with("admin") {
126 return Err(invalid("basket", "not starting with 'admin'"));
127 }
128 if normalized == "default" {
130 return Err(invalid("basket", "not 'default'"));
131 }
132 if normalized.starts_with('p') {
134 return Err(invalid(
135 "basket",
136 "not starting with 'p' (reserved per BRC-99)",
137 ));
138 }
139 Ok(())
140}
141
142fn validate_key_id(key_id: &str) -> Result<(), WalletError> {
143 if key_id.is_empty() {
144 return Err(invalid("key_id", "non-empty"));
145 }
146 validate_string_length(key_id, "key_id", 1, 800)
147}
148
149fn validate_privileged_reason(
150 privileged: bool,
151 reason: &Option<String>,
152) -> Result<(), WalletError> {
153 if privileged {
154 if let Some(r) = reason {
155 validate_string_length(r, "privileged_reason", 5, 50)?;
156 } else {
157 return Err(invalid(
158 "privileged_reason",
159 "provided when privileged is true",
160 ));
161 }
162 }
163 Ok(())
164}
165
166fn validate_optional_privileged_reason(
167 privileged: Option<bool>,
168 reason: &Option<String>,
169) -> Result<(), WalletError> {
170 if privileged.unwrap_or(false) {
171 if let Some(r) = reason {
172 validate_string_length(r, "privileged_reason", 5, 50)?;
173 } else {
174 return Err(invalid(
175 "privileged_reason",
176 "provided when privileged is true",
177 ));
178 }
179 }
180 Ok(())
181}
182
183pub fn validate_create_action_args(args: &CreateActionArgs) -> Result<(), WalletError> {
189 validate_description(&args.description, "description")?;
190
191 for label in &args.labels {
192 validate_label(label)?;
193 }
194
195 for output in &args.outputs {
196 if output.locking_script.is_none() && output.output_description.is_empty() {
197 return Err(invalid(
198 "output",
199 "has locking_script or output_description",
200 ));
201 }
202 for tag in &output.tags {
203 validate_tag(tag)?;
204 }
205 if let Some(ref basket) = output.basket {
206 validate_basket(basket)?;
207 }
208 }
209
210 for input in &args.inputs {
211 if input.unlocking_script.is_none() && input.unlocking_script_length.is_none() {
212 return Err(invalid(
213 "input",
214 "has unlocking_script or unlocking_script_length",
215 ));
216 }
217 }
218
219 Ok(())
220}
221
222pub fn validate_sign_action_args(args: &SignActionArgs) -> Result<(), WalletError> {
224 if args.reference.is_empty() {
225 return Err(invalid("reference", "non-empty"));
226 }
227 if args.spends.is_empty() {
228 return Err(invalid("spends", "at least one spend"));
229 }
230 for spend in args.spends.values() {
231 if spend.unlocking_script.is_empty() {
232 return Err(invalid("unlocking_script", "non-empty"));
233 }
234 }
235 Ok(())
236}
237
238pub fn validate_abort_action_args(args: &AbortActionArgs) -> Result<(), WalletError> {
240 if args.reference.is_empty() {
241 return Err(invalid("reference", "non-empty"));
242 }
243 Ok(())
244}
245
246pub fn validate_list_actions_args(args: &ListActionsArgs) -> Result<(), WalletError> {
248 if args.labels.is_empty() {
249 return Err(invalid("labels", "non-empty"));
250 }
251 for label in &args.labels {
252 validate_label(label)?;
253 }
254 validate_optional_limit(args.limit)?;
255 Ok(())
256}
257
258pub fn validate_internalize_action_args(args: &InternalizeActionArgs) -> Result<(), WalletError> {
260 if args.tx.is_empty() {
261 return Err(invalid("tx", "non-empty"));
262 }
263 if args.outputs.is_empty() {
264 return Err(invalid("outputs", "at least one output"));
265 }
266 validate_description(&args.description, "description")?;
267 for label in &args.labels {
268 validate_label(label)?;
269 }
270 let _ = &args.outputs;
273 Ok(())
274}
275
276pub fn validate_list_outputs_args(args: &ListOutputsArgs) -> Result<(), WalletError> {
278 validate_basket(&args.basket)?;
279 validate_optional_limit(args.limit)?;
280 for tag in &args.tags {
281 validate_tag(tag)?;
282 }
283 Ok(())
284}
285
286pub fn validate_relinquish_output_args(args: &RelinquishOutputArgs) -> Result<(), WalletError> {
288 validate_basket(&args.basket)?;
289 if args.output.is_empty() {
290 return Err(invalid("output", "non-empty"));
291 }
292 Ok(())
293}
294
295pub fn validate_get_public_key_args(args: &GetPublicKeyArgs) -> Result<(), WalletError> {
297 if !args.identity_key {
298 if args.protocol_id.is_none() {
299 return Err(invalid(
300 "protocol_id",
301 "provided when identity_key is false",
302 ));
303 }
304 if args.key_id.is_none() {
305 return Err(invalid("key_id", "provided when identity_key is false"));
306 }
307 if let Some(ref p) = args.protocol_id {
308 validate_protocol_id(p)?;
309 }
310 if let Some(ref k) = args.key_id {
311 validate_key_id(k)?;
312 }
313 }
314 Ok(())
315}
316
317pub fn validate_encrypt_args(args: &EncryptArgs) -> Result<(), WalletError> {
319 validate_protocol_id(&args.protocol_id)?;
320 validate_key_id(&args.key_id)?;
321 if args.plaintext.is_empty() {
322 return Err(invalid("plaintext", "non-empty"));
323 }
324 validate_privileged_reason(args.privileged, &args.privileged_reason)?;
325 Ok(())
326}
327
328pub fn validate_decrypt_args(args: &DecryptArgs) -> Result<(), WalletError> {
330 validate_protocol_id(&args.protocol_id)?;
331 validate_key_id(&args.key_id)?;
332 if args.ciphertext.is_empty() {
333 return Err(invalid("ciphertext", "non-empty"));
334 }
335 validate_privileged_reason(args.privileged, &args.privileged_reason)?;
336 Ok(())
337}
338
339pub fn validate_create_hmac_args(args: &CreateHmacArgs) -> Result<(), WalletError> {
341 validate_protocol_id(&args.protocol_id)?;
342 validate_key_id(&args.key_id)?;
343 if args.data.is_empty() {
344 return Err(invalid("data", "non-empty"));
345 }
346 validate_privileged_reason(args.privileged, &args.privileged_reason)?;
347 Ok(())
348}
349
350pub fn validate_verify_hmac_args(args: &VerifyHmacArgs) -> Result<(), WalletError> {
352 validate_protocol_id(&args.protocol_id)?;
353 validate_key_id(&args.key_id)?;
354 if args.data.is_empty() {
355 return Err(invalid("data", "non-empty"));
356 }
357 if args.hmac.is_empty() {
358 return Err(invalid("hmac", "non-empty"));
359 }
360 validate_privileged_reason(args.privileged, &args.privileged_reason)?;
361 Ok(())
362}
363
364pub fn validate_create_signature_args(args: &CreateSignatureArgs) -> Result<(), WalletError> {
366 validate_protocol_id(&args.protocol_id)?;
367 validate_key_id(&args.key_id)?;
368 let has_data = args.data.as_ref().is_some_and(|d| !d.is_empty());
369 let has_hash = args
370 .hash_to_directly_sign
371 .as_ref()
372 .is_some_and(|h| !h.is_empty());
373 if !has_data && !has_hash {
374 return Err(invalid(
375 "data",
376 "provided (either data or hash_to_directly_sign)",
377 ));
378 }
379 if has_hash {
380 if let Some(ref h) = args.hash_to_directly_sign {
381 if h.len() != 32 {
382 return Err(invalid("hash_to_directly_sign", "exactly 32 bytes"));
383 }
384 }
385 }
386 validate_privileged_reason(args.privileged, &args.privileged_reason)?;
387 Ok(())
388}
389
390pub fn validate_verify_signature_args(args: &VerifySignatureArgs) -> Result<(), WalletError> {
392 validate_protocol_id(&args.protocol_id)?;
393 validate_key_id(&args.key_id)?;
394 let has_data = args.data.as_ref().is_some_and(|d| !d.is_empty());
395 let has_hash = args
396 .hash_to_directly_verify
397 .as_ref()
398 .is_some_and(|h| !h.is_empty());
399 if !has_data && !has_hash {
400 return Err(invalid(
401 "data",
402 "provided (either data or hash_to_directly_verify)",
403 ));
404 }
405 if has_hash {
406 if let Some(ref h) = args.hash_to_directly_verify {
407 if h.len() != 32 {
408 return Err(invalid("hash_to_directly_verify", "exactly 32 bytes"));
409 }
410 }
411 }
412 if args.signature.is_empty() {
413 return Err(invalid("signature", "non-empty"));
414 }
415 validate_privileged_reason(args.privileged, &args.privileged_reason)?;
416 Ok(())
417}
418
419pub fn validate_acquire_certificate_args(args: &AcquireCertificateArgs) -> Result<(), WalletError> {
421 match args.acquisition_protocol {
422 AcquisitionProtocol::Direct => {
423 if args.serial_number.is_none() {
424 return Err(invalid("serial_number", "provided for direct acquisition"));
425 }
426 if args.signature.is_none() {
427 return Err(invalid("signature", "provided for direct acquisition"));
428 }
429 if args.revocation_outpoint.is_none() {
430 return Err(invalid(
431 "revocation_outpoint",
432 "provided for direct acquisition",
433 ));
434 }
435 if args.keyring_revealer.is_none() {
436 return Err(invalid(
437 "keyring_revealer",
438 "provided for direct acquisition",
439 ));
440 }
441 if args.keyring_for_subject.is_none() {
442 return Err(invalid(
443 "keyring_for_subject",
444 "provided for direct acquisition",
445 ));
446 }
447 }
448 AcquisitionProtocol::Issuance => {
449 if args.certifier_url.is_none() {
450 return Err(invalid(
451 "certifier_url",
452 "provided for issuance acquisition",
453 ));
454 }
455 }
456 }
457 validate_privileged_reason(args.privileged, &args.privileged_reason)?;
458 for field_name in args.fields.keys() {
460 validate_string_length(field_name, "field_name", 1, 50)?;
461 }
462 Ok(())
463}
464
465pub fn validate_list_certificates_args(args: &ListCertificatesArgs) -> Result<(), WalletError> {
467 validate_optional_limit(args.limit)?;
468 validate_optional_privileged_reason(args.privileged.0, &args.privileged_reason)?;
469 Ok(())
470}
471
472pub fn validate_prove_certificate_args(args: &ProveCertificateArgs) -> Result<(), WalletError> {
474 if args.fields_to_reveal.is_empty() {
475 return Err(invalid("fields_to_reveal", "non-empty"));
476 }
477 for field in &args.fields_to_reveal {
478 validate_string_length(field, "fields_to_reveal entry", 1, 50)?;
479 }
480 validate_optional_privileged_reason(args.privileged.0, &args.privileged_reason)?;
481 Ok(())
482}
483
484pub fn validate_relinquish_certificate_args(
486 args: &RelinquishCertificateArgs,
487) -> Result<(), WalletError> {
488 let _ = args;
490 Ok(())
491}
492
493pub fn validate_discover_by_identity_key_args(
495 args: &DiscoverByIdentityKeyArgs,
496) -> Result<(), WalletError> {
497 let _ = args;
499 Ok(())
500}
501
502pub fn validate_discover_by_attributes_args(
504 args: &DiscoverByAttributesArgs,
505) -> Result<(), WalletError> {
506 if args.attributes.is_empty() {
507 return Err(invalid("attributes", "non-empty"));
508 }
509 Ok(())
510}
511
512pub fn validate_reveal_counterparty_key_linkage_args(
514 args: &RevealCounterpartyKeyLinkageArgs,
515) -> Result<(), WalletError> {
516 validate_optional_privileged_reason(args.privileged, &args.privileged_reason)?;
517 Ok(())
518}
519
520pub fn validate_reveal_specific_key_linkage_args(
522 args: &RevealSpecificKeyLinkageArgs,
523) -> Result<(), WalletError> {
524 validate_protocol_id(&args.protocol_id)?;
525 validate_key_id(&args.key_id)?;
526 validate_optional_privileged_reason(args.privileged, &args.privileged_reason)?;
527 Ok(())
528}
529
530pub fn validate_get_header_args(args: &GetHeaderArgs) -> Result<(), WalletError> {
532 if args.height == 0 {
533 return Err(invalid("height", "greater than 0"));
534 }
535 Ok(())
536}
537
538#[cfg(test)]
543mod tests {
544 use super::*;
545 use std::collections::HashMap;
546
547 use crate::primitives::private_key::PrivateKey;
548 use crate::wallet::types::{
549 BooleanDefaultFalse, BooleanDefaultTrue, Counterparty, CounterpartyType, Protocol,
550 };
551
552 fn test_pubkey() -> crate::primitives::public_key::PublicKey {
553 let pk = PrivateKey::from_bytes(&{
554 let mut buf = [0u8; 32];
555 buf[31] = 42;
556 buf
557 })
558 .unwrap();
559 pk.to_public_key()
560 }
561
562 fn test_counterparty() -> Counterparty {
563 Counterparty {
564 counterparty_type: CounterpartyType::Other,
565 public_key: Some(test_pubkey()),
566 }
567 }
568
569 fn test_protocol() -> Protocol {
570 Protocol {
571 security_level: 1,
572 protocol: "test signing".to_string(),
573 }
574 }
575
576 #[test]
579 fn test_create_action_valid() {
580 let args = CreateActionArgs {
581 description: "Valid description text".to_string(),
582 input_beef: None,
583 inputs: vec![],
584 outputs: vec![],
585 lock_time: None,
586 version: None,
587 labels: vec![],
588 options: None,
589 reference: None,
590 };
591 assert!(validate_create_action_args(&args).is_ok());
592 }
593
594 #[test]
595 fn test_create_action_short_description() {
596 let args = CreateActionArgs {
597 description: "Hi".to_string(),
598 input_beef: None,
599 inputs: vec![],
600 outputs: vec![],
601 lock_time: None,
602 version: None,
603 labels: vec![],
604 options: None,
605 reference: None,
606 };
607 assert!(validate_create_action_args(&args).is_err());
608 }
609
610 #[test]
611 fn test_create_action_label_too_long() {
612 let args = CreateActionArgs {
613 description: "Valid description text".to_string(),
614 input_beef: None,
615 inputs: vec![],
616 outputs: vec![],
617 lock_time: None,
618 version: None,
619 labels: vec!["x".repeat(301)],
620 options: None,
621 reference: None,
622 };
623 assert!(validate_create_action_args(&args).is_err());
624 }
625
626 #[test]
627 fn test_create_action_input_needs_script_or_length() {
628 let args = CreateActionArgs {
629 description: "Valid description text".to_string(),
630 input_beef: None,
631 inputs: vec![CreateActionInput {
632 outpoint: "abc.0".to_string(),
633 input_description: "test input".to_string(),
634 unlocking_script: None,
635 unlocking_script_length: None,
636 sequence_number: None,
637 }],
638 outputs: vec![],
639 lock_time: None,
640 version: None,
641 labels: vec![],
642 options: None,
643 reference: None,
644 };
645 assert!(validate_create_action_args(&args).is_err());
646 }
647
648 #[test]
651 fn test_sign_action_valid() {
652 let mut spends = HashMap::new();
653 spends.insert(
654 0,
655 SignActionSpend {
656 unlocking_script: vec![1, 2, 3],
657 sequence_number: None,
658 },
659 );
660 let args = SignActionArgs {
661 reference: vec![1, 2, 3],
662 spends,
663 options: None,
664 };
665 assert!(validate_sign_action_args(&args).is_ok());
666 }
667
668 #[test]
669 fn test_sign_action_empty_reference() {
670 let mut spends = HashMap::new();
671 spends.insert(
672 0,
673 SignActionSpend {
674 unlocking_script: vec![1],
675 sequence_number: None,
676 },
677 );
678 let args = SignActionArgs {
679 reference: vec![],
680 spends,
681 options: None,
682 };
683 assert!(validate_sign_action_args(&args).is_err());
684 }
685
686 #[test]
687 fn test_sign_action_empty_spends() {
688 let args = SignActionArgs {
689 reference: vec![1, 2, 3],
690 spends: HashMap::new(),
691 options: None,
692 };
693 assert!(validate_sign_action_args(&args).is_err());
694 }
695
696 #[test]
697 fn test_sign_action_empty_unlocking_script() {
698 let mut spends = HashMap::new();
699 spends.insert(
700 0,
701 SignActionSpend {
702 unlocking_script: vec![],
703 sequence_number: None,
704 },
705 );
706 let args = SignActionArgs {
707 reference: vec![1, 2, 3],
708 spends,
709 options: None,
710 };
711 assert!(validate_sign_action_args(&args).is_err());
712 }
713
714 #[test]
717 fn test_abort_action_valid() {
718 let args = AbortActionArgs {
719 reference: vec![1, 2, 3],
720 };
721 assert!(validate_abort_action_args(&args).is_ok());
722 }
723
724 #[test]
725 fn test_abort_action_empty_reference() {
726 let args = AbortActionArgs { reference: vec![] };
727 assert!(validate_abort_action_args(&args).is_err());
728 }
729
730 #[test]
733 fn test_list_actions_valid() {
734 let args = ListActionsArgs {
735 labels: vec!["test".to_string()],
736 label_query_mode: None,
737 include_labels: BooleanDefaultFalse(None),
738 include_inputs: BooleanDefaultFalse(None),
739 include_input_source_locking_scripts: BooleanDefaultFalse(None),
740 include_input_unlocking_scripts: BooleanDefaultFalse(None),
741 include_outputs: BooleanDefaultFalse(None),
742 include_output_locking_scripts: BooleanDefaultFalse(None),
743 limit: Some(10),
744 offset: None,
745 seek_permission: BooleanDefaultTrue(None),
746 };
747 assert!(validate_list_actions_args(&args).is_ok());
748 }
749
750 #[test]
751 fn test_list_actions_empty_labels() {
752 let args = ListActionsArgs {
753 labels: vec![],
754 label_query_mode: None,
755 include_labels: BooleanDefaultFalse(None),
756 include_inputs: BooleanDefaultFalse(None),
757 include_input_source_locking_scripts: BooleanDefaultFalse(None),
758 include_input_unlocking_scripts: BooleanDefaultFalse(None),
759 include_outputs: BooleanDefaultFalse(None),
760 include_output_locking_scripts: BooleanDefaultFalse(None),
761 limit: None,
762 offset: None,
763 seek_permission: BooleanDefaultTrue(None),
764 };
765 assert!(validate_list_actions_args(&args).is_err());
766 }
767
768 #[test]
769 fn test_list_actions_limit_too_high() {
770 let args = ListActionsArgs {
771 labels: vec!["test".to_string()],
772 label_query_mode: None,
773 include_labels: BooleanDefaultFalse(None),
774 include_inputs: BooleanDefaultFalse(None),
775 include_input_source_locking_scripts: BooleanDefaultFalse(None),
776 include_input_unlocking_scripts: BooleanDefaultFalse(None),
777 include_outputs: BooleanDefaultFalse(None),
778 include_output_locking_scripts: BooleanDefaultFalse(None),
779 limit: Some(10001),
780 offset: None,
781 seek_permission: BooleanDefaultTrue(None),
782 };
783 assert!(validate_list_actions_args(&args).is_err());
784 }
785
786 #[test]
787 fn test_list_actions_limit_zero() {
788 let args = ListActionsArgs {
789 labels: vec!["test".to_string()],
790 label_query_mode: None,
791 include_labels: BooleanDefaultFalse(None),
792 include_inputs: BooleanDefaultFalse(None),
793 include_input_source_locking_scripts: BooleanDefaultFalse(None),
794 include_input_unlocking_scripts: BooleanDefaultFalse(None),
795 include_outputs: BooleanDefaultFalse(None),
796 include_output_locking_scripts: BooleanDefaultFalse(None),
797 limit: Some(0),
798 offset: None,
799 seek_permission: BooleanDefaultTrue(None),
800 };
801 assert!(validate_list_actions_args(&args).is_err());
802 }
803
804 #[test]
807 fn test_internalize_action_valid() {
808 let args = InternalizeActionArgs {
809 tx: vec![1, 2, 3],
810 description: "Valid description text".to_string(),
811 labels: vec![],
812 seek_permission: BooleanDefaultTrue(None),
813 outputs: vec![InternalizeOutput::BasketInsertion {
814 output_index: 0,
815 insertion: BasketInsertion {
816 basket: "test-basket".to_string(),
817 custom_instructions: None,
818 tags: vec![],
819 },
820 }],
821 };
822 assert!(validate_internalize_action_args(&args).is_ok());
823 }
824
825 #[test]
826 fn test_internalize_action_empty_tx() {
827 let args = InternalizeActionArgs {
828 tx: vec![],
829 description: "Valid description text".to_string(),
830 labels: vec![],
831 seek_permission: BooleanDefaultTrue(None),
832 outputs: vec![InternalizeOutput::BasketInsertion {
833 output_index: 0,
834 insertion: BasketInsertion {
835 basket: "test".to_string(),
836 custom_instructions: None,
837 tags: vec![],
838 },
839 }],
840 };
841 assert!(validate_internalize_action_args(&args).is_err());
842 }
843
844 #[test]
845 fn test_internalize_action_empty_outputs() {
846 let args = InternalizeActionArgs {
847 tx: vec![1, 2, 3],
848 description: "Valid description text".to_string(),
849 labels: vec![],
850 seek_permission: BooleanDefaultTrue(None),
851 outputs: vec![],
852 };
853 assert!(validate_internalize_action_args(&args).is_err());
854 }
855
856 #[test]
859 fn test_list_outputs_valid() {
860 let args = ListOutputsArgs {
861 basket: "token store".to_string(),
862 tags: vec![],
863 tag_query_mode: None,
864 include: None,
865 include_custom_instructions: BooleanDefaultFalse(None),
866 include_tags: BooleanDefaultFalse(None),
867 include_labels: BooleanDefaultFalse(None),
868 limit: Some(10),
869 offset: None,
870 seek_permission: BooleanDefaultTrue(None),
871 };
872 assert!(validate_list_outputs_args(&args).is_ok());
873 }
874
875 #[test]
876 fn test_list_outputs_empty_basket() {
877 let args = ListOutputsArgs {
878 basket: "".to_string(),
879 tags: vec![],
880 tag_query_mode: None,
881 include: None,
882 include_custom_instructions: BooleanDefaultFalse(None),
883 include_tags: BooleanDefaultFalse(None),
884 include_labels: BooleanDefaultFalse(None),
885 limit: None,
886 offset: None,
887 seek_permission: BooleanDefaultTrue(None),
888 };
889 assert!(validate_list_outputs_args(&args).is_err());
890 }
891
892 #[test]
893 fn test_list_outputs_limit_too_high() {
894 let args = ListOutputsArgs {
895 basket: "token store".to_string(),
896 tags: vec![],
897 tag_query_mode: None,
898 include: None,
899 include_custom_instructions: BooleanDefaultFalse(None),
900 include_tags: BooleanDefaultFalse(None),
901 include_labels: BooleanDefaultFalse(None),
902 limit: Some(10001),
903 offset: None,
904 seek_permission: BooleanDefaultTrue(None),
905 };
906 assert!(validate_list_outputs_args(&args).is_err());
907 }
908
909 #[test]
912 fn test_relinquish_output_valid() {
913 let args = RelinquishOutputArgs {
914 basket: "token store".to_string(),
915 output: "abc123.0".to_string(),
916 };
917 assert!(validate_relinquish_output_args(&args).is_ok());
918 }
919
920 #[test]
921 fn test_relinquish_output_empty_basket() {
922 let args = RelinquishOutputArgs {
923 basket: "".to_string(),
924 output: "abc123.0".to_string(),
925 };
926 assert!(validate_relinquish_output_args(&args).is_err());
927 }
928
929 #[test]
930 fn test_relinquish_output_empty_output() {
931 let args = RelinquishOutputArgs {
932 basket: "token store".to_string(),
933 output: "".to_string(),
934 };
935 assert!(validate_relinquish_output_args(&args).is_err());
936 }
937
938 #[test]
941 fn test_get_public_key_identity() {
942 let args = GetPublicKeyArgs {
943 identity_key: true,
944 protocol_id: None,
945 key_id: None,
946 counterparty: None,
947 privileged: false,
948 privileged_reason: None,
949 for_self: None,
950 seek_permission: None,
951 };
952 assert!(validate_get_public_key_args(&args).is_ok());
953 }
954
955 #[test]
956 fn test_get_public_key_derived_valid() {
957 let args = GetPublicKeyArgs {
958 identity_key: false,
959 protocol_id: Some(test_protocol()),
960 key_id: Some("my-key".to_string()),
961 counterparty: Some(test_counterparty()),
962 privileged: false,
963 privileged_reason: None,
964 for_self: None,
965 seek_permission: None,
966 };
967 assert!(validate_get_public_key_args(&args).is_ok());
968 }
969
970 #[test]
971 fn test_get_public_key_derived_missing_protocol() {
972 let args = GetPublicKeyArgs {
973 identity_key: false,
974 protocol_id: None,
975 key_id: Some("my-key".to_string()),
976 counterparty: None,
977 privileged: false,
978 privileged_reason: None,
979 for_self: None,
980 seek_permission: None,
981 };
982 assert!(validate_get_public_key_args(&args).is_err());
983 }
984
985 #[test]
986 fn test_get_public_key_derived_missing_key_id() {
987 let args = GetPublicKeyArgs {
988 identity_key: false,
989 protocol_id: Some(test_protocol()),
990 key_id: None,
991 counterparty: None,
992 privileged: false,
993 privileged_reason: None,
994 for_self: None,
995 seek_permission: None,
996 };
997 assert!(validate_get_public_key_args(&args).is_err());
998 }
999
1000 #[test]
1003 fn test_encrypt_valid() {
1004 let args = EncryptArgs {
1005 protocol_id: test_protocol(),
1006 key_id: "my-key".to_string(),
1007 counterparty: test_counterparty(),
1008 plaintext: vec![1, 2, 3],
1009 privileged: false,
1010 privileged_reason: None,
1011 seek_permission: None,
1012 };
1013 assert!(validate_encrypt_args(&args).is_ok());
1014 }
1015
1016 #[test]
1017 fn test_encrypt_empty_plaintext() {
1018 let args = EncryptArgs {
1019 protocol_id: test_protocol(),
1020 key_id: "my-key".to_string(),
1021 counterparty: test_counterparty(),
1022 plaintext: vec![],
1023 privileged: false,
1024 privileged_reason: None,
1025 seek_permission: None,
1026 };
1027 assert!(validate_encrypt_args(&args).is_err());
1028 }
1029
1030 #[test]
1031 fn test_encrypt_empty_protocol() {
1032 let args = EncryptArgs {
1033 protocol_id: Protocol {
1034 security_level: 1,
1035 protocol: "".to_string(),
1036 },
1037 key_id: "my-key".to_string(),
1038 counterparty: test_counterparty(),
1039 plaintext: vec![1, 2, 3],
1040 privileged: false,
1041 privileged_reason: None,
1042 seek_permission: None,
1043 };
1044 assert!(validate_encrypt_args(&args).is_err());
1045 }
1046
1047 #[test]
1048 fn test_encrypt_empty_key_id() {
1049 let args = EncryptArgs {
1050 protocol_id: test_protocol(),
1051 key_id: "".to_string(),
1052 counterparty: test_counterparty(),
1053 plaintext: vec![1, 2, 3],
1054 privileged: false,
1055 privileged_reason: None,
1056 seek_permission: None,
1057 };
1058 assert!(validate_encrypt_args(&args).is_err());
1059 }
1060
1061 #[test]
1062 fn test_encrypt_invalid_security_level() {
1063 let args = EncryptArgs {
1064 protocol_id: Protocol {
1065 security_level: 5,
1066 protocol: "test".to_string(),
1067 },
1068 key_id: "my-key".to_string(),
1069 counterparty: test_counterparty(),
1070 plaintext: vec![1, 2, 3],
1071 privileged: false,
1072 privileged_reason: None,
1073 seek_permission: None,
1074 };
1075 assert!(validate_encrypt_args(&args).is_err());
1076 }
1077
1078 #[test]
1081 fn test_decrypt_valid() {
1082 let args = DecryptArgs {
1083 protocol_id: test_protocol(),
1084 key_id: "my-key".to_string(),
1085 counterparty: test_counterparty(),
1086 ciphertext: vec![1, 2, 3],
1087 privileged: false,
1088 privileged_reason: None,
1089 seek_permission: None,
1090 };
1091 assert!(validate_decrypt_args(&args).is_ok());
1092 }
1093
1094 #[test]
1095 fn test_decrypt_empty_ciphertext() {
1096 let args = DecryptArgs {
1097 protocol_id: test_protocol(),
1098 key_id: "my-key".to_string(),
1099 counterparty: test_counterparty(),
1100 ciphertext: vec![],
1101 privileged: false,
1102 privileged_reason: None,
1103 seek_permission: None,
1104 };
1105 assert!(validate_decrypt_args(&args).is_err());
1106 }
1107
1108 #[test]
1111 fn test_create_hmac_valid() {
1112 let args = CreateHmacArgs {
1113 protocol_id: test_protocol(),
1114 key_id: "my-key".to_string(),
1115 counterparty: test_counterparty(),
1116 data: vec![1, 2, 3],
1117 privileged: false,
1118 privileged_reason: None,
1119 seek_permission: None,
1120 };
1121 assert!(validate_create_hmac_args(&args).is_ok());
1122 }
1123
1124 #[test]
1125 fn test_create_hmac_empty_data() {
1126 let args = CreateHmacArgs {
1127 protocol_id: test_protocol(),
1128 key_id: "my-key".to_string(),
1129 counterparty: test_counterparty(),
1130 data: vec![],
1131 privileged: false,
1132 privileged_reason: None,
1133 seek_permission: None,
1134 };
1135 assert!(validate_create_hmac_args(&args).is_err());
1136 }
1137
1138 #[test]
1141 fn test_verify_hmac_valid() {
1142 let args = VerifyHmacArgs {
1143 protocol_id: test_protocol(),
1144 key_id: "my-key".to_string(),
1145 counterparty: test_counterparty(),
1146 data: vec![1, 2, 3],
1147 hmac: vec![4, 5, 6],
1148 privileged: false,
1149 privileged_reason: None,
1150 seek_permission: None,
1151 };
1152 assert!(validate_verify_hmac_args(&args).is_ok());
1153 }
1154
1155 #[test]
1156 fn test_verify_hmac_empty_hmac() {
1157 let args = VerifyHmacArgs {
1158 protocol_id: test_protocol(),
1159 key_id: "my-key".to_string(),
1160 counterparty: test_counterparty(),
1161 data: vec![1, 2, 3],
1162 hmac: vec![],
1163 privileged: false,
1164 privileged_reason: None,
1165 seek_permission: None,
1166 };
1167 assert!(validate_verify_hmac_args(&args).is_err());
1168 }
1169
1170 #[test]
1173 fn test_create_signature_valid() {
1174 let args = CreateSignatureArgs {
1175 protocol_id: test_protocol(),
1176 key_id: "my-key".to_string(),
1177 counterparty: test_counterparty(),
1178 data: Some(vec![1, 2, 3]),
1179 hash_to_directly_sign: None,
1180 privileged: false,
1181 privileged_reason: None,
1182 seek_permission: None,
1183 };
1184 assert!(validate_create_signature_args(&args).is_ok());
1185 }
1186
1187 #[test]
1188 fn test_create_signature_empty_data() {
1189 let args = CreateSignatureArgs {
1190 protocol_id: test_protocol(),
1191 key_id: "my-key".to_string(),
1192 counterparty: test_counterparty(),
1193 data: Some(vec![]),
1194 hash_to_directly_sign: None,
1195 privileged: false,
1196 privileged_reason: None,
1197 seek_permission: None,
1198 };
1199 assert!(validate_create_signature_args(&args).is_err());
1200 }
1201
1202 #[test]
1205 fn test_verify_signature_valid() {
1206 let args = VerifySignatureArgs {
1207 protocol_id: test_protocol(),
1208 key_id: "my-key".to_string(),
1209 counterparty: test_counterparty(),
1210 data: Some(vec![1, 2, 3]),
1211 hash_to_directly_verify: None,
1212 signature: vec![4, 5, 6],
1213 for_self: None,
1214 privileged: false,
1215 privileged_reason: None,
1216 seek_permission: None,
1217 };
1218 assert!(validate_verify_signature_args(&args).is_ok());
1219 }
1220
1221 #[test]
1222 fn test_verify_signature_empty_signature() {
1223 let args = VerifySignatureArgs {
1224 protocol_id: test_protocol(),
1225 key_id: "my-key".to_string(),
1226 counterparty: test_counterparty(),
1227 data: Some(vec![1, 2, 3]),
1228 hash_to_directly_verify: None,
1229 signature: vec![],
1230 for_self: None,
1231 privileged: false,
1232 privileged_reason: None,
1233 seek_permission: None,
1234 };
1235 assert!(validate_verify_signature_args(&args).is_err());
1236 }
1237
1238 #[test]
1241 fn test_acquire_certificate_direct_valid() {
1242 let args = AcquireCertificateArgs {
1243 cert_type: CertificateType([0u8; 32]),
1244 certifier: test_pubkey(),
1245 acquisition_protocol: AcquisitionProtocol::Direct,
1246 fields: HashMap::new(),
1247 serial_number: Some(SerialNumber([0u8; 32])),
1248 revocation_outpoint: Some("abc.0".to_string()),
1249 signature: Some(vec![1, 2, 3]),
1250 certifier_url: None,
1251 keyring_revealer: Some(KeyringRevealer::Certifier),
1252 keyring_for_subject: Some(HashMap::new()),
1253 privileged: false,
1254 privileged_reason: None,
1255 };
1256 assert!(validate_acquire_certificate_args(&args).is_ok());
1257 }
1258
1259 #[test]
1260 fn test_acquire_certificate_direct_missing_serial() {
1261 let args = AcquireCertificateArgs {
1262 cert_type: CertificateType([0u8; 32]),
1263 certifier: test_pubkey(),
1264 acquisition_protocol: AcquisitionProtocol::Direct,
1265 fields: HashMap::new(),
1266 serial_number: None,
1267 revocation_outpoint: Some("abc.0".to_string()),
1268 signature: Some(vec![1, 2, 3]),
1269 certifier_url: None,
1270 keyring_revealer: Some(KeyringRevealer::Certifier),
1271 keyring_for_subject: Some(HashMap::new()),
1272 privileged: false,
1273 privileged_reason: None,
1274 };
1275 assert!(validate_acquire_certificate_args(&args).is_err());
1276 }
1277
1278 #[test]
1279 fn test_acquire_certificate_issuance_valid() {
1280 let args = AcquireCertificateArgs {
1281 cert_type: CertificateType([0u8; 32]),
1282 certifier: test_pubkey(),
1283 acquisition_protocol: AcquisitionProtocol::Issuance,
1284 fields: HashMap::new(),
1285 serial_number: None,
1286 revocation_outpoint: None,
1287 signature: None,
1288 certifier_url: Some("https://example.com".to_string()),
1289 keyring_revealer: None,
1290 keyring_for_subject: None,
1291 privileged: false,
1292 privileged_reason: None,
1293 };
1294 assert!(validate_acquire_certificate_args(&args).is_ok());
1295 }
1296
1297 #[test]
1298 fn test_acquire_certificate_issuance_missing_url() {
1299 let args = AcquireCertificateArgs {
1300 cert_type: CertificateType([0u8; 32]),
1301 certifier: test_pubkey(),
1302 acquisition_protocol: AcquisitionProtocol::Issuance,
1303 fields: HashMap::new(),
1304 serial_number: None,
1305 revocation_outpoint: None,
1306 signature: None,
1307 certifier_url: None,
1308 keyring_revealer: None,
1309 keyring_for_subject: None,
1310 privileged: false,
1311 privileged_reason: None,
1312 };
1313 assert!(validate_acquire_certificate_args(&args).is_err());
1314 }
1315
1316 #[test]
1319 fn test_list_certificates_valid() {
1320 let args = ListCertificatesArgs {
1321 certifiers: vec![],
1322 types: vec![],
1323 limit: Some(10),
1324 offset: None,
1325 privileged: BooleanDefaultFalse(None),
1326 privileged_reason: None,
1327 };
1328 assert!(validate_list_certificates_args(&args).is_ok());
1329 }
1330
1331 #[test]
1332 fn test_list_certificates_limit_too_high() {
1333 let args = ListCertificatesArgs {
1334 certifiers: vec![],
1335 types: vec![],
1336 limit: Some(10001),
1337 offset: None,
1338 privileged: BooleanDefaultFalse(None),
1339 privileged_reason: None,
1340 };
1341 assert!(validate_list_certificates_args(&args).is_err());
1342 }
1343
1344 #[test]
1347 fn test_prove_certificate_valid() {
1348 let args = ProveCertificateArgs {
1349 certificate: Certificate {
1350 cert_type: CertificateType([0u8; 32]),
1351 serial_number: SerialNumber([0u8; 32]),
1352 subject: test_pubkey(),
1353 certifier: test_pubkey(),
1354 revocation_outpoint: None,
1355 fields: None,
1356 signature: None,
1357 }
1358 .into(),
1359 fields_to_reveal: vec!["name".to_string()],
1360 verifier: test_pubkey(),
1361 privileged: BooleanDefaultFalse(None),
1362 privileged_reason: None,
1363 };
1364 assert!(validate_prove_certificate_args(&args).is_ok());
1365 }
1366
1367 #[test]
1368 fn test_prove_certificate_empty_fields() {
1369 let args = ProveCertificateArgs {
1370 certificate: Certificate {
1371 cert_type: CertificateType([0u8; 32]),
1372 serial_number: SerialNumber([0u8; 32]),
1373 subject: test_pubkey(),
1374 certifier: test_pubkey(),
1375 revocation_outpoint: None,
1376 fields: None,
1377 signature: None,
1378 }
1379 .into(),
1380 fields_to_reveal: vec![],
1381 verifier: test_pubkey(),
1382 privileged: BooleanDefaultFalse(None),
1383 privileged_reason: None,
1384 };
1385 assert!(validate_prove_certificate_args(&args).is_err());
1386 }
1387
1388 #[test]
1391 fn test_relinquish_certificate_valid() {
1392 let args = RelinquishCertificateArgs {
1393 cert_type: CertificateType([0u8; 32]),
1394 serial_number: SerialNumber([0u8; 32]),
1395 certifier: test_pubkey(),
1396 };
1397 assert!(validate_relinquish_certificate_args(&args).is_ok());
1398 }
1399
1400 #[test]
1403 fn test_discover_by_identity_key_valid() {
1404 let args = DiscoverByIdentityKeyArgs {
1405 identity_key: test_pubkey(),
1406 limit: None,
1407 offset: None,
1408 seek_permission: None,
1409 };
1410 assert!(validate_discover_by_identity_key_args(&args).is_ok());
1411 }
1412
1413 #[test]
1416 fn test_discover_by_attributes_valid() {
1417 let mut attrs = HashMap::new();
1418 attrs.insert("name".to_string(), "Alice".to_string());
1419 let args = DiscoverByAttributesArgs {
1420 attributes: attrs,
1421 limit: None,
1422 offset: None,
1423 seek_permission: None,
1424 };
1425 assert!(validate_discover_by_attributes_args(&args).is_ok());
1426 }
1427
1428 #[test]
1429 fn test_discover_by_attributes_empty() {
1430 let args = DiscoverByAttributesArgs {
1431 attributes: HashMap::new(),
1432 limit: None,
1433 offset: None,
1434 seek_permission: None,
1435 };
1436 assert!(validate_discover_by_attributes_args(&args).is_err());
1437 }
1438
1439 #[test]
1442 fn test_reveal_counterparty_key_linkage_valid() {
1443 let args = RevealCounterpartyKeyLinkageArgs {
1444 counterparty: test_pubkey(),
1445 verifier: test_pubkey(),
1446 privileged: None,
1447 privileged_reason: None,
1448 };
1449 assert!(validate_reveal_counterparty_key_linkage_args(&args).is_ok());
1450 }
1451
1452 #[test]
1455 fn test_reveal_specific_key_linkage_valid() {
1456 let args = RevealSpecificKeyLinkageArgs {
1457 counterparty: test_counterparty(),
1458 verifier: test_pubkey(),
1459 protocol_id: test_protocol(),
1460 key_id: "my-key".to_string(),
1461 privileged: None,
1462 privileged_reason: None,
1463 };
1464 assert!(validate_reveal_specific_key_linkage_args(&args).is_ok());
1465 }
1466
1467 #[test]
1468 fn test_reveal_specific_key_linkage_empty_key_id() {
1469 let args = RevealSpecificKeyLinkageArgs {
1470 counterparty: test_counterparty(),
1471 verifier: test_pubkey(),
1472 protocol_id: test_protocol(),
1473 key_id: "".to_string(),
1474 privileged: None,
1475 privileged_reason: None,
1476 };
1477 assert!(validate_reveal_specific_key_linkage_args(&args).is_err());
1478 }
1479
1480 #[test]
1483 fn test_get_header_valid() {
1484 let args = GetHeaderArgs { height: 100 };
1485 assert!(validate_get_header_args(&args).is_ok());
1486 }
1487
1488 #[test]
1489 fn test_get_header_zero_height() {
1490 let args = GetHeaderArgs { height: 0 };
1491 assert!(validate_get_header_args(&args).is_err());
1492 }
1493
1494 #[test]
1497 fn test_privileged_without_reason() {
1498 let args = EncryptArgs {
1499 protocol_id: test_protocol(),
1500 key_id: "my-key".to_string(),
1501 counterparty: test_counterparty(),
1502 plaintext: vec![1, 2, 3],
1503 privileged: true,
1504 privileged_reason: None,
1505 seek_permission: None,
1506 };
1507 assert!(validate_encrypt_args(&args).is_err());
1508 }
1509
1510 #[test]
1511 fn test_privileged_with_short_reason() {
1512 let args = EncryptArgs {
1513 protocol_id: test_protocol(),
1514 key_id: "my-key".to_string(),
1515 counterparty: test_counterparty(),
1516 plaintext: vec![1, 2, 3],
1517 privileged: true,
1518 privileged_reason: Some("ab".to_string()),
1519 seek_permission: None,
1520 };
1521 assert!(validate_encrypt_args(&args).is_err());
1522 }
1523
1524 #[test]
1525 fn test_privileged_with_valid_reason() {
1526 let args = EncryptArgs {
1527 protocol_id: test_protocol(),
1528 key_id: "my-key".to_string(),
1529 counterparty: test_counterparty(),
1530 plaintext: vec![1, 2, 3],
1531 privileged: true,
1532 privileged_reason: Some("Admin access required".to_string()),
1533 seek_permission: None,
1534 };
1535 assert!(validate_encrypt_args(&args).is_ok());
1536 }
1537}