Skip to main content

bsv/wallet/
validation.rs

1//! Validation helpers for all wallet method arguments.
2//!
3//! Each validator returns `Result<(), WalletError>` using
4//! `WalletError::InvalidParameter` on bad input.
5//! Translated from TS SDK src/wallet/validationHelpers.ts.
6
7use crate::wallet::error::WalletError;
8use crate::wallet::interfaces::*;
9
10// ---------------------------------------------------------------------------
11// Shared validation helpers
12// ---------------------------------------------------------------------------
13
14fn 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
54/// Normalize an identifier per BRC-100: trim whitespace, lowercase.
55/// Matches TS SDK `validateIdentifier` which does `trim().toLowerCase()`.
56pub 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    // BRC-100: must not contain multiple consecutive spaces
70    if normalized.contains("  ") {
71        return Err(invalid(
72            "protocol_id.protocol",
73            "free of consecutive spaces",
74        ));
75    }
76    // BRC-100: must only contain lowercase letters, numbers, and spaces
77    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    // BRC-100: must not end with "protocol"
87    if normalized.ends_with("protocol") {
88        return Err(invalid(
89            "protocol_id.protocol",
90            "not ending with 'protocol'",
91        ));
92    }
93    // BRC-98: must not start with "p"
94    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    // BRC-100: must only contain lowercase letters, numbers, and spaces
107    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    // BRC-100: must not contain consecutive spaces
117    if normalized.contains("  ") {
118        return Err(invalid("basket", "free of consecutive spaces"));
119    }
120    // BRC-100: must not end with "basket"
121    if normalized.ends_with("basket") {
122        return Err(invalid("basket", "not ending with 'basket'"));
123    }
124    // BRC-100: must not start with "admin"
125    if normalized.starts_with("admin") {
126        return Err(invalid("basket", "not starting with 'admin'"));
127    }
128    // BRC-100: must not be "default"
129    if normalized == "default" {
130        return Err(invalid("basket", "not 'default'"));
131    }
132    // BRC-99: must not start with "p"
133    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
183// ---------------------------------------------------------------------------
184// Public validation functions -- one per wallet method's args
185// ---------------------------------------------------------------------------
186
187/// Validate CreateActionArgs.
188pub 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
222/// Validate SignActionArgs.
223pub 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
238/// Validate AbortActionArgs.
239pub 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
246/// Validate ListActionsArgs.
247pub 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
258/// Validate InternalizeActionArgs.
259pub 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    // InternalizeOutput enum variants guarantee that the correct remittance
271    // data is always present -- impossible states are unrepresentable.
272    let _ = &args.outputs;
273    Ok(())
274}
275
276/// Validate ListOutputsArgs.
277pub 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
286/// Validate RelinquishOutputArgs.
287pub 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
295/// Validate GetPublicKeyArgs.
296pub 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
317/// Validate EncryptArgs.
318pub 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
328/// Validate DecryptArgs.
329pub 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
339/// Validate CreateHmacArgs.
340pub 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
350/// Validate VerifyHmacArgs.
351pub 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
364/// Validate CreateSignatureArgs.
365pub 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
390/// Validate VerifySignatureArgs.
391pub 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
419/// Validate AcquireCertificateArgs.
420pub 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    // Validate certificate field names
459    for field_name in args.fields.keys() {
460        validate_string_length(field_name, "field_name", 1, 50)?;
461    }
462    Ok(())
463}
464
465/// Validate ListCertificatesArgs.
466pub 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
472/// Validate ProveCertificateArgs.
473pub 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
484/// Validate RelinquishCertificateArgs.
485pub fn validate_relinquish_certificate_args(
486    args: &RelinquishCertificateArgs,
487) -> Result<(), WalletError> {
488    // cert_type, serial_number, certifier are required typed fields -- presence is enforced by struct.
489    let _ = args;
490    Ok(())
491}
492
493/// Validate DiscoverByIdentityKeyArgs.
494pub fn validate_discover_by_identity_key_args(
495    args: &DiscoverByIdentityKeyArgs,
496) -> Result<(), WalletError> {
497    // identity_key is a required typed field.
498    let _ = args;
499    Ok(())
500}
501
502/// Validate DiscoverByAttributesArgs.
503pub 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
512/// Validate RevealCounterpartyKeyLinkageArgs.
513pub 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
520/// Validate RevealSpecificKeyLinkageArgs.
521pub 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
530/// Validate GetHeaderArgs.
531pub 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// ===========================================================================
539// Tests
540// ===========================================================================
541
542#[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    // ---- CreateActionArgs ----
577
578    #[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    // ---- SignActionArgs ----
649
650    #[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    // ---- AbortActionArgs ----
715
716    #[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    // ---- ListActionsArgs ----
731
732    #[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    // ---- InternalizeActionArgs ----
805
806    #[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    // ---- ListOutputsArgs ----
857
858    #[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    // ---- RelinquishOutputArgs ----
910
911    #[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    // ---- GetPublicKeyArgs ----
939
940    #[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    // ---- EncryptArgs ----
1001
1002    #[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    // ---- DecryptArgs ----
1079
1080    #[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    // ---- CreateHmacArgs ----
1109
1110    #[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    // ---- VerifyHmacArgs ----
1139
1140    #[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    // ---- CreateSignatureArgs ----
1171
1172    #[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    // ---- VerifySignatureArgs ----
1203
1204    #[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    // ---- AcquireCertificateArgs ----
1239
1240    #[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    // ---- ListCertificatesArgs ----
1317
1318    #[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    // ---- ProveCertificateArgs ----
1345
1346    #[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    // ---- RelinquishCertificateArgs ----
1389
1390    #[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    // ---- DiscoverByIdentityKeyArgs ----
1401
1402    #[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    // ---- DiscoverByAttributesArgs ----
1414
1415    #[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    // ---- RevealCounterpartyKeyLinkageArgs ----
1440
1441    #[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    // ---- RevealSpecificKeyLinkageArgs ----
1453
1454    #[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    // ---- GetHeaderArgs ----
1481
1482    #[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    // ---- Privileged reason validation ----
1495
1496    #[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}