use crate::wallet::error::WalletError;
use crate::wallet::interfaces::*;
fn invalid(field: &str, requirement: &str) -> WalletError {
WalletError::InvalidParameter(format!("{}: must be {}", field, requirement))
}
fn validate_string_length(s: &str, name: &str, min: usize, max: usize) -> Result<(), WalletError> {
let len = s.len();
if len < min {
return Err(invalid(name, &format!("at least {} bytes", min)));
}
if len > max {
return Err(invalid(name, &format!("no more than {} bytes", max)));
}
Ok(())
}
fn validate_label(s: &str) -> Result<(), WalletError> {
validate_string_length(s, "label", 1, 300)
}
fn validate_tag(s: &str) -> Result<(), WalletError> {
validate_string_length(s, "tag", 1, 300)
}
fn validate_basket(s: &str) -> Result<(), WalletError> {
validate_basket_name(s)
}
fn validate_description(s: &str, name: &str) -> Result<(), WalletError> {
validate_string_length(s, name, 5, 2000)
}
fn validate_optional_limit(limit: Option<u32>) -> Result<(), WalletError> {
if let Some(v) = limit {
if !(1..=10000).contains(&v) {
return Err(invalid("limit", "between 1 and 10000"));
}
}
Ok(())
}
pub fn normalize_identifier(s: &str) -> String {
s.trim().to_lowercase()
}
fn validate_protocol_id(protocol: &crate::wallet::types::Protocol) -> Result<(), WalletError> {
if protocol.security_level > 2 {
return Err(invalid("protocol_id.security_level", "0, 1, or 2"));
}
let normalized = normalize_identifier(&protocol.protocol);
if normalized.is_empty() {
return Err(invalid("protocol_id.protocol", "non-empty"));
}
validate_string_length(&normalized, "protocol_id.protocol", 1, 400)?;
if normalized.contains(" ") {
return Err(invalid(
"protocol_id.protocol",
"free of consecutive spaces",
));
}
if !normalized
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == ' ')
{
return Err(invalid(
"protocol_id.protocol",
"only lowercase letters, numbers, and spaces",
));
}
if normalized.ends_with("protocol") {
return Err(invalid(
"protocol_id.protocol",
"not ending with 'protocol'",
));
}
if normalized.starts_with('p') {
return Err(invalid(
"protocol_id.protocol",
"not starting with 'p' (reserved per BRC-98)",
));
}
Ok(())
}
fn validate_basket_name(s: &str) -> Result<(), WalletError> {
let normalized = normalize_identifier(s);
validate_string_length(&normalized, "basket", 5, 300)?;
if !normalized
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == ' ')
{
return Err(invalid(
"basket",
"only lowercase letters, numbers, and spaces",
));
}
if normalized.contains(" ") {
return Err(invalid("basket", "free of consecutive spaces"));
}
if normalized.ends_with("basket") {
return Err(invalid("basket", "not ending with 'basket'"));
}
if normalized.starts_with("admin") {
return Err(invalid("basket", "not starting with 'admin'"));
}
if normalized == "default" {
return Err(invalid("basket", "not 'default'"));
}
if normalized.starts_with('p') {
return Err(invalid(
"basket",
"not starting with 'p' (reserved per BRC-99)",
));
}
Ok(())
}
fn validate_key_id(key_id: &str) -> Result<(), WalletError> {
if key_id.is_empty() {
return Err(invalid("key_id", "non-empty"));
}
validate_string_length(key_id, "key_id", 1, 800)
}
fn validate_privileged_reason(
privileged: bool,
reason: &Option<String>,
) -> Result<(), WalletError> {
if privileged {
if let Some(r) = reason {
validate_string_length(r, "privileged_reason", 5, 50)?;
} else {
return Err(invalid(
"privileged_reason",
"provided when privileged is true",
));
}
}
Ok(())
}
fn validate_optional_privileged_reason(
privileged: Option<bool>,
reason: &Option<String>,
) -> Result<(), WalletError> {
if privileged.unwrap_or(false) {
if let Some(r) = reason {
validate_string_length(r, "privileged_reason", 5, 50)?;
} else {
return Err(invalid(
"privileged_reason",
"provided when privileged is true",
));
}
}
Ok(())
}
pub fn validate_create_action_args(args: &CreateActionArgs) -> Result<(), WalletError> {
validate_description(&args.description, "description")?;
for label in &args.labels {
validate_label(label)?;
}
for output in &args.outputs {
if output.locking_script.is_none() && output.output_description.is_empty() {
return Err(invalid(
"output",
"has locking_script or output_description",
));
}
for tag in &output.tags {
validate_tag(tag)?;
}
if let Some(ref basket) = output.basket {
validate_basket(basket)?;
}
}
for input in &args.inputs {
if input.unlocking_script.is_none() && input.unlocking_script_length.is_none() {
return Err(invalid(
"input",
"has unlocking_script or unlocking_script_length",
));
}
}
Ok(())
}
pub fn validate_sign_action_args(args: &SignActionArgs) -> Result<(), WalletError> {
if args.reference.is_empty() {
return Err(invalid("reference", "non-empty"));
}
if args.spends.is_empty() {
return Err(invalid("spends", "at least one spend"));
}
for spend in args.spends.values() {
if spend.unlocking_script.is_empty() {
return Err(invalid("unlocking_script", "non-empty"));
}
}
Ok(())
}
pub fn validate_abort_action_args(args: &AbortActionArgs) -> Result<(), WalletError> {
if args.reference.is_empty() {
return Err(invalid("reference", "non-empty"));
}
Ok(())
}
pub fn validate_list_actions_args(args: &ListActionsArgs) -> Result<(), WalletError> {
if args.labels.is_empty() {
return Err(invalid("labels", "non-empty"));
}
for label in &args.labels {
validate_label(label)?;
}
validate_optional_limit(args.limit)?;
Ok(())
}
pub fn validate_internalize_action_args(args: &InternalizeActionArgs) -> Result<(), WalletError> {
if args.tx.is_empty() {
return Err(invalid("tx", "non-empty"));
}
if args.outputs.is_empty() {
return Err(invalid("outputs", "at least one output"));
}
validate_description(&args.description, "description")?;
for label in &args.labels {
validate_label(label)?;
}
let _ = &args.outputs;
Ok(())
}
pub fn validate_list_outputs_args(args: &ListOutputsArgs) -> Result<(), WalletError> {
validate_basket(&args.basket)?;
validate_optional_limit(args.limit)?;
for tag in &args.tags {
validate_tag(tag)?;
}
Ok(())
}
pub fn validate_relinquish_output_args(args: &RelinquishOutputArgs) -> Result<(), WalletError> {
validate_basket(&args.basket)?;
if args.output.is_empty() {
return Err(invalid("output", "non-empty"));
}
Ok(())
}
pub fn validate_get_public_key_args(args: &GetPublicKeyArgs) -> Result<(), WalletError> {
if !args.identity_key {
if args.protocol_id.is_none() {
return Err(invalid(
"protocol_id",
"provided when identity_key is false",
));
}
if args.key_id.is_none() {
return Err(invalid("key_id", "provided when identity_key is false"));
}
if let Some(ref p) = args.protocol_id {
validate_protocol_id(p)?;
}
if let Some(ref k) = args.key_id {
validate_key_id(k)?;
}
}
Ok(())
}
pub fn validate_encrypt_args(args: &EncryptArgs) -> Result<(), WalletError> {
validate_protocol_id(&args.protocol_id)?;
validate_key_id(&args.key_id)?;
if args.plaintext.is_empty() {
return Err(invalid("plaintext", "non-empty"));
}
validate_privileged_reason(args.privileged, &args.privileged_reason)?;
Ok(())
}
pub fn validate_decrypt_args(args: &DecryptArgs) -> Result<(), WalletError> {
validate_protocol_id(&args.protocol_id)?;
validate_key_id(&args.key_id)?;
if args.ciphertext.is_empty() {
return Err(invalid("ciphertext", "non-empty"));
}
validate_privileged_reason(args.privileged, &args.privileged_reason)?;
Ok(())
}
pub fn validate_create_hmac_args(args: &CreateHmacArgs) -> Result<(), WalletError> {
validate_protocol_id(&args.protocol_id)?;
validate_key_id(&args.key_id)?;
if args.data.is_empty() {
return Err(invalid("data", "non-empty"));
}
validate_privileged_reason(args.privileged, &args.privileged_reason)?;
Ok(())
}
pub fn validate_verify_hmac_args(args: &VerifyHmacArgs) -> Result<(), WalletError> {
validate_protocol_id(&args.protocol_id)?;
validate_key_id(&args.key_id)?;
if args.data.is_empty() {
return Err(invalid("data", "non-empty"));
}
if args.hmac.is_empty() {
return Err(invalid("hmac", "non-empty"));
}
validate_privileged_reason(args.privileged, &args.privileged_reason)?;
Ok(())
}
pub fn validate_create_signature_args(args: &CreateSignatureArgs) -> Result<(), WalletError> {
validate_protocol_id(&args.protocol_id)?;
validate_key_id(&args.key_id)?;
let has_data = args.data.as_ref().is_some_and(|d| !d.is_empty());
let has_hash = args
.hash_to_directly_sign
.as_ref()
.is_some_and(|h| !h.is_empty());
if !has_data && !has_hash {
return Err(invalid(
"data",
"provided (either data or hash_to_directly_sign)",
));
}
if has_hash {
if let Some(ref h) = args.hash_to_directly_sign {
if h.len() != 32 {
return Err(invalid("hash_to_directly_sign", "exactly 32 bytes"));
}
}
}
validate_privileged_reason(args.privileged, &args.privileged_reason)?;
Ok(())
}
pub fn validate_verify_signature_args(args: &VerifySignatureArgs) -> Result<(), WalletError> {
validate_protocol_id(&args.protocol_id)?;
validate_key_id(&args.key_id)?;
let has_data = args.data.as_ref().is_some_and(|d| !d.is_empty());
let has_hash = args
.hash_to_directly_verify
.as_ref()
.is_some_and(|h| !h.is_empty());
if !has_data && !has_hash {
return Err(invalid(
"data",
"provided (either data or hash_to_directly_verify)",
));
}
if has_hash {
if let Some(ref h) = args.hash_to_directly_verify {
if h.len() != 32 {
return Err(invalid("hash_to_directly_verify", "exactly 32 bytes"));
}
}
}
if args.signature.is_empty() {
return Err(invalid("signature", "non-empty"));
}
validate_privileged_reason(args.privileged, &args.privileged_reason)?;
Ok(())
}
pub fn validate_acquire_certificate_args(args: &AcquireCertificateArgs) -> Result<(), WalletError> {
match args.acquisition_protocol {
AcquisitionProtocol::Direct => {
if args.serial_number.is_none() {
return Err(invalid("serial_number", "provided for direct acquisition"));
}
if args.signature.is_none() {
return Err(invalid("signature", "provided for direct acquisition"));
}
if args.revocation_outpoint.is_none() {
return Err(invalid(
"revocation_outpoint",
"provided for direct acquisition",
));
}
if args.keyring_revealer.is_none() {
return Err(invalid(
"keyring_revealer",
"provided for direct acquisition",
));
}
if args.keyring_for_subject.is_none() {
return Err(invalid(
"keyring_for_subject",
"provided for direct acquisition",
));
}
}
AcquisitionProtocol::Issuance => {
if args.certifier_url.is_none() {
return Err(invalid(
"certifier_url",
"provided for issuance acquisition",
));
}
}
}
validate_privileged_reason(args.privileged, &args.privileged_reason)?;
for field_name in args.fields.keys() {
validate_string_length(field_name, "field_name", 1, 50)?;
}
Ok(())
}
pub fn validate_list_certificates_args(args: &ListCertificatesArgs) -> Result<(), WalletError> {
validate_optional_limit(args.limit)?;
validate_optional_privileged_reason(args.privileged.0, &args.privileged_reason)?;
Ok(())
}
pub fn validate_prove_certificate_args(args: &ProveCertificateArgs) -> Result<(), WalletError> {
if args.fields_to_reveal.is_empty() {
return Err(invalid("fields_to_reveal", "non-empty"));
}
for field in &args.fields_to_reveal {
validate_string_length(field, "fields_to_reveal entry", 1, 50)?;
}
validate_optional_privileged_reason(args.privileged.0, &args.privileged_reason)?;
Ok(())
}
pub fn validate_relinquish_certificate_args(
args: &RelinquishCertificateArgs,
) -> Result<(), WalletError> {
let _ = args;
Ok(())
}
pub fn validate_discover_by_identity_key_args(
args: &DiscoverByIdentityKeyArgs,
) -> Result<(), WalletError> {
let _ = args;
Ok(())
}
pub fn validate_discover_by_attributes_args(
args: &DiscoverByAttributesArgs,
) -> Result<(), WalletError> {
if args.attributes.is_empty() {
return Err(invalid("attributes", "non-empty"));
}
Ok(())
}
pub fn validate_reveal_counterparty_key_linkage_args(
args: &RevealCounterpartyKeyLinkageArgs,
) -> Result<(), WalletError> {
validate_optional_privileged_reason(args.privileged, &args.privileged_reason)?;
Ok(())
}
pub fn validate_reveal_specific_key_linkage_args(
args: &RevealSpecificKeyLinkageArgs,
) -> Result<(), WalletError> {
validate_protocol_id(&args.protocol_id)?;
validate_key_id(&args.key_id)?;
validate_optional_privileged_reason(args.privileged, &args.privileged_reason)?;
Ok(())
}
pub fn validate_get_header_args(args: &GetHeaderArgs) -> Result<(), WalletError> {
if args.height == 0 {
return Err(invalid("height", "greater than 0"));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use crate::primitives::private_key::PrivateKey;
use crate::wallet::types::{
BooleanDefaultFalse, BooleanDefaultTrue, Counterparty, CounterpartyType, Protocol,
};
fn test_pubkey() -> crate::primitives::public_key::PublicKey {
let pk = PrivateKey::from_bytes(&{
let mut buf = [0u8; 32];
buf[31] = 42;
buf
})
.unwrap();
pk.to_public_key()
}
fn test_counterparty() -> Counterparty {
Counterparty {
counterparty_type: CounterpartyType::Other,
public_key: Some(test_pubkey()),
}
}
fn test_protocol() -> Protocol {
Protocol {
security_level: 1,
protocol: "test signing".to_string(),
}
}
#[test]
fn test_create_action_valid() {
let args = CreateActionArgs {
description: "Valid description text".to_string(),
input_beef: None,
inputs: vec![],
outputs: vec![],
lock_time: None,
version: None,
labels: vec![],
options: None,
reference: None,
};
assert!(validate_create_action_args(&args).is_ok());
}
#[test]
fn test_create_action_short_description() {
let args = CreateActionArgs {
description: "Hi".to_string(),
input_beef: None,
inputs: vec![],
outputs: vec![],
lock_time: None,
version: None,
labels: vec![],
options: None,
reference: None,
};
assert!(validate_create_action_args(&args).is_err());
}
#[test]
fn test_create_action_label_too_long() {
let args = CreateActionArgs {
description: "Valid description text".to_string(),
input_beef: None,
inputs: vec![],
outputs: vec![],
lock_time: None,
version: None,
labels: vec!["x".repeat(301)],
options: None,
reference: None,
};
assert!(validate_create_action_args(&args).is_err());
}
#[test]
fn test_create_action_input_needs_script_or_length() {
let args = CreateActionArgs {
description: "Valid description text".to_string(),
input_beef: None,
inputs: vec![CreateActionInput {
outpoint: "abc.0".to_string(),
input_description: "test input".to_string(),
unlocking_script: None,
unlocking_script_length: None,
sequence_number: None,
}],
outputs: vec![],
lock_time: None,
version: None,
labels: vec![],
options: None,
reference: None,
};
assert!(validate_create_action_args(&args).is_err());
}
#[test]
fn test_sign_action_valid() {
let mut spends = HashMap::new();
spends.insert(
0,
SignActionSpend {
unlocking_script: vec![1, 2, 3],
sequence_number: None,
},
);
let args = SignActionArgs {
reference: vec![1, 2, 3],
spends,
options: None,
};
assert!(validate_sign_action_args(&args).is_ok());
}
#[test]
fn test_sign_action_empty_reference() {
let mut spends = HashMap::new();
spends.insert(
0,
SignActionSpend {
unlocking_script: vec![1],
sequence_number: None,
},
);
let args = SignActionArgs {
reference: vec![],
spends,
options: None,
};
assert!(validate_sign_action_args(&args).is_err());
}
#[test]
fn test_sign_action_empty_spends() {
let args = SignActionArgs {
reference: vec![1, 2, 3],
spends: HashMap::new(),
options: None,
};
assert!(validate_sign_action_args(&args).is_err());
}
#[test]
fn test_sign_action_empty_unlocking_script() {
let mut spends = HashMap::new();
spends.insert(
0,
SignActionSpend {
unlocking_script: vec![],
sequence_number: None,
},
);
let args = SignActionArgs {
reference: vec![1, 2, 3],
spends,
options: None,
};
assert!(validate_sign_action_args(&args).is_err());
}
#[test]
fn test_abort_action_valid() {
let args = AbortActionArgs {
reference: vec![1, 2, 3],
};
assert!(validate_abort_action_args(&args).is_ok());
}
#[test]
fn test_abort_action_empty_reference() {
let args = AbortActionArgs { reference: vec![] };
assert!(validate_abort_action_args(&args).is_err());
}
#[test]
fn test_list_actions_valid() {
let args = ListActionsArgs {
labels: vec!["test".to_string()],
label_query_mode: None,
include_labels: BooleanDefaultFalse(None),
include_inputs: BooleanDefaultFalse(None),
include_input_source_locking_scripts: BooleanDefaultFalse(None),
include_input_unlocking_scripts: BooleanDefaultFalse(None),
include_outputs: BooleanDefaultFalse(None),
include_output_locking_scripts: BooleanDefaultFalse(None),
limit: Some(10),
offset: None,
seek_permission: BooleanDefaultTrue(None),
};
assert!(validate_list_actions_args(&args).is_ok());
}
#[test]
fn test_list_actions_empty_labels() {
let args = ListActionsArgs {
labels: vec![],
label_query_mode: None,
include_labels: BooleanDefaultFalse(None),
include_inputs: BooleanDefaultFalse(None),
include_input_source_locking_scripts: BooleanDefaultFalse(None),
include_input_unlocking_scripts: BooleanDefaultFalse(None),
include_outputs: BooleanDefaultFalse(None),
include_output_locking_scripts: BooleanDefaultFalse(None),
limit: None,
offset: None,
seek_permission: BooleanDefaultTrue(None),
};
assert!(validate_list_actions_args(&args).is_err());
}
#[test]
fn test_list_actions_limit_too_high() {
let args = ListActionsArgs {
labels: vec!["test".to_string()],
label_query_mode: None,
include_labels: BooleanDefaultFalse(None),
include_inputs: BooleanDefaultFalse(None),
include_input_source_locking_scripts: BooleanDefaultFalse(None),
include_input_unlocking_scripts: BooleanDefaultFalse(None),
include_outputs: BooleanDefaultFalse(None),
include_output_locking_scripts: BooleanDefaultFalse(None),
limit: Some(10001),
offset: None,
seek_permission: BooleanDefaultTrue(None),
};
assert!(validate_list_actions_args(&args).is_err());
}
#[test]
fn test_list_actions_limit_zero() {
let args = ListActionsArgs {
labels: vec!["test".to_string()],
label_query_mode: None,
include_labels: BooleanDefaultFalse(None),
include_inputs: BooleanDefaultFalse(None),
include_input_source_locking_scripts: BooleanDefaultFalse(None),
include_input_unlocking_scripts: BooleanDefaultFalse(None),
include_outputs: BooleanDefaultFalse(None),
include_output_locking_scripts: BooleanDefaultFalse(None),
limit: Some(0),
offset: None,
seek_permission: BooleanDefaultTrue(None),
};
assert!(validate_list_actions_args(&args).is_err());
}
#[test]
fn test_internalize_action_valid() {
let args = InternalizeActionArgs {
tx: vec![1, 2, 3],
description: "Valid description text".to_string(),
labels: vec![],
seek_permission: BooleanDefaultTrue(None),
outputs: vec![InternalizeOutput::BasketInsertion {
output_index: 0,
insertion: BasketInsertion {
basket: "test-basket".to_string(),
custom_instructions: None,
tags: vec![],
},
}],
};
assert!(validate_internalize_action_args(&args).is_ok());
}
#[test]
fn test_internalize_action_empty_tx() {
let args = InternalizeActionArgs {
tx: vec![],
description: "Valid description text".to_string(),
labels: vec![],
seek_permission: BooleanDefaultTrue(None),
outputs: vec![InternalizeOutput::BasketInsertion {
output_index: 0,
insertion: BasketInsertion {
basket: "test".to_string(),
custom_instructions: None,
tags: vec![],
},
}],
};
assert!(validate_internalize_action_args(&args).is_err());
}
#[test]
fn test_internalize_action_empty_outputs() {
let args = InternalizeActionArgs {
tx: vec![1, 2, 3],
description: "Valid description text".to_string(),
labels: vec![],
seek_permission: BooleanDefaultTrue(None),
outputs: vec![],
};
assert!(validate_internalize_action_args(&args).is_err());
}
#[test]
fn test_list_outputs_valid() {
let args = ListOutputsArgs {
basket: "token store".to_string(),
tags: vec![],
tag_query_mode: None,
include: None,
include_custom_instructions: BooleanDefaultFalse(None),
include_tags: BooleanDefaultFalse(None),
include_labels: BooleanDefaultFalse(None),
limit: Some(10),
offset: None,
seek_permission: BooleanDefaultTrue(None),
};
assert!(validate_list_outputs_args(&args).is_ok());
}
#[test]
fn test_list_outputs_empty_basket() {
let args = ListOutputsArgs {
basket: "".to_string(),
tags: vec![],
tag_query_mode: None,
include: None,
include_custom_instructions: BooleanDefaultFalse(None),
include_tags: BooleanDefaultFalse(None),
include_labels: BooleanDefaultFalse(None),
limit: None,
offset: None,
seek_permission: BooleanDefaultTrue(None),
};
assert!(validate_list_outputs_args(&args).is_err());
}
#[test]
fn test_list_outputs_limit_too_high() {
let args = ListOutputsArgs {
basket: "token store".to_string(),
tags: vec![],
tag_query_mode: None,
include: None,
include_custom_instructions: BooleanDefaultFalse(None),
include_tags: BooleanDefaultFalse(None),
include_labels: BooleanDefaultFalse(None),
limit: Some(10001),
offset: None,
seek_permission: BooleanDefaultTrue(None),
};
assert!(validate_list_outputs_args(&args).is_err());
}
#[test]
fn test_relinquish_output_valid() {
let args = RelinquishOutputArgs {
basket: "token store".to_string(),
output: "abc123.0".to_string(),
};
assert!(validate_relinquish_output_args(&args).is_ok());
}
#[test]
fn test_relinquish_output_empty_basket() {
let args = RelinquishOutputArgs {
basket: "".to_string(),
output: "abc123.0".to_string(),
};
assert!(validate_relinquish_output_args(&args).is_err());
}
#[test]
fn test_relinquish_output_empty_output() {
let args = RelinquishOutputArgs {
basket: "token store".to_string(),
output: "".to_string(),
};
assert!(validate_relinquish_output_args(&args).is_err());
}
#[test]
fn test_get_public_key_identity() {
let args = GetPublicKeyArgs {
identity_key: true,
protocol_id: None,
key_id: None,
counterparty: None,
privileged: false,
privileged_reason: None,
for_self: None,
seek_permission: None,
};
assert!(validate_get_public_key_args(&args).is_ok());
}
#[test]
fn test_get_public_key_derived_valid() {
let args = GetPublicKeyArgs {
identity_key: false,
protocol_id: Some(test_protocol()),
key_id: Some("my-key".to_string()),
counterparty: Some(test_counterparty()),
privileged: false,
privileged_reason: None,
for_self: None,
seek_permission: None,
};
assert!(validate_get_public_key_args(&args).is_ok());
}
#[test]
fn test_get_public_key_derived_missing_protocol() {
let args = GetPublicKeyArgs {
identity_key: false,
protocol_id: None,
key_id: Some("my-key".to_string()),
counterparty: None,
privileged: false,
privileged_reason: None,
for_self: None,
seek_permission: None,
};
assert!(validate_get_public_key_args(&args).is_err());
}
#[test]
fn test_get_public_key_derived_missing_key_id() {
let args = GetPublicKeyArgs {
identity_key: false,
protocol_id: Some(test_protocol()),
key_id: None,
counterparty: None,
privileged: false,
privileged_reason: None,
for_self: None,
seek_permission: None,
};
assert!(validate_get_public_key_args(&args).is_err());
}
#[test]
fn test_encrypt_valid() {
let args = EncryptArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
plaintext: vec![1, 2, 3],
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_encrypt_args(&args).is_ok());
}
#[test]
fn test_encrypt_empty_plaintext() {
let args = EncryptArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
plaintext: vec![],
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_encrypt_args(&args).is_err());
}
#[test]
fn test_encrypt_empty_protocol() {
let args = EncryptArgs {
protocol_id: Protocol {
security_level: 1,
protocol: "".to_string(),
},
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
plaintext: vec![1, 2, 3],
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_encrypt_args(&args).is_err());
}
#[test]
fn test_encrypt_empty_key_id() {
let args = EncryptArgs {
protocol_id: test_protocol(),
key_id: "".to_string(),
counterparty: test_counterparty(),
plaintext: vec![1, 2, 3],
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_encrypt_args(&args).is_err());
}
#[test]
fn test_encrypt_invalid_security_level() {
let args = EncryptArgs {
protocol_id: Protocol {
security_level: 5,
protocol: "test".to_string(),
},
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
plaintext: vec![1, 2, 3],
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_encrypt_args(&args).is_err());
}
#[test]
fn test_decrypt_valid() {
let args = DecryptArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
ciphertext: vec![1, 2, 3],
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_decrypt_args(&args).is_ok());
}
#[test]
fn test_decrypt_empty_ciphertext() {
let args = DecryptArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
ciphertext: vec![],
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_decrypt_args(&args).is_err());
}
#[test]
fn test_create_hmac_valid() {
let args = CreateHmacArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
data: vec![1, 2, 3],
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_create_hmac_args(&args).is_ok());
}
#[test]
fn test_create_hmac_empty_data() {
let args = CreateHmacArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
data: vec![],
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_create_hmac_args(&args).is_err());
}
#[test]
fn test_verify_hmac_valid() {
let args = VerifyHmacArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
data: vec![1, 2, 3],
hmac: vec![4, 5, 6],
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_verify_hmac_args(&args).is_ok());
}
#[test]
fn test_verify_hmac_empty_hmac() {
let args = VerifyHmacArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
data: vec![1, 2, 3],
hmac: vec![],
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_verify_hmac_args(&args).is_err());
}
#[test]
fn test_create_signature_valid() {
let args = CreateSignatureArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
data: Some(vec![1, 2, 3]),
hash_to_directly_sign: None,
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_create_signature_args(&args).is_ok());
}
#[test]
fn test_create_signature_empty_data() {
let args = CreateSignatureArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
data: Some(vec![]),
hash_to_directly_sign: None,
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_create_signature_args(&args).is_err());
}
#[test]
fn test_verify_signature_valid() {
let args = VerifySignatureArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
data: Some(vec![1, 2, 3]),
hash_to_directly_verify: None,
signature: vec![4, 5, 6],
for_self: None,
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_verify_signature_args(&args).is_ok());
}
#[test]
fn test_verify_signature_empty_signature() {
let args = VerifySignatureArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
data: Some(vec![1, 2, 3]),
hash_to_directly_verify: None,
signature: vec![],
for_self: None,
privileged: false,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_verify_signature_args(&args).is_err());
}
#[test]
fn test_acquire_certificate_direct_valid() {
let args = AcquireCertificateArgs {
cert_type: CertificateType([0u8; 32]),
certifier: test_pubkey(),
acquisition_protocol: AcquisitionProtocol::Direct,
fields: HashMap::new(),
serial_number: Some(SerialNumber([0u8; 32])),
revocation_outpoint: Some("abc.0".to_string()),
signature: Some(vec![1, 2, 3]),
certifier_url: None,
keyring_revealer: Some(KeyringRevealer::Certifier),
keyring_for_subject: Some(HashMap::new()),
privileged: false,
privileged_reason: None,
};
assert!(validate_acquire_certificate_args(&args).is_ok());
}
#[test]
fn test_acquire_certificate_direct_missing_serial() {
let args = AcquireCertificateArgs {
cert_type: CertificateType([0u8; 32]),
certifier: test_pubkey(),
acquisition_protocol: AcquisitionProtocol::Direct,
fields: HashMap::new(),
serial_number: None,
revocation_outpoint: Some("abc.0".to_string()),
signature: Some(vec![1, 2, 3]),
certifier_url: None,
keyring_revealer: Some(KeyringRevealer::Certifier),
keyring_for_subject: Some(HashMap::new()),
privileged: false,
privileged_reason: None,
};
assert!(validate_acquire_certificate_args(&args).is_err());
}
#[test]
fn test_acquire_certificate_issuance_valid() {
let args = AcquireCertificateArgs {
cert_type: CertificateType([0u8; 32]),
certifier: test_pubkey(),
acquisition_protocol: AcquisitionProtocol::Issuance,
fields: HashMap::new(),
serial_number: None,
revocation_outpoint: None,
signature: None,
certifier_url: Some("https://example.com".to_string()),
keyring_revealer: None,
keyring_for_subject: None,
privileged: false,
privileged_reason: None,
};
assert!(validate_acquire_certificate_args(&args).is_ok());
}
#[test]
fn test_acquire_certificate_issuance_missing_url() {
let args = AcquireCertificateArgs {
cert_type: CertificateType([0u8; 32]),
certifier: test_pubkey(),
acquisition_protocol: AcquisitionProtocol::Issuance,
fields: HashMap::new(),
serial_number: None,
revocation_outpoint: None,
signature: None,
certifier_url: None,
keyring_revealer: None,
keyring_for_subject: None,
privileged: false,
privileged_reason: None,
};
assert!(validate_acquire_certificate_args(&args).is_err());
}
#[test]
fn test_list_certificates_valid() {
let args = ListCertificatesArgs {
certifiers: vec![],
types: vec![],
limit: Some(10),
offset: None,
privileged: BooleanDefaultFalse(None),
privileged_reason: None,
partial: None,
};
assert!(validate_list_certificates_args(&args).is_ok());
}
#[test]
fn test_list_certificates_limit_too_high() {
let args = ListCertificatesArgs {
certifiers: vec![],
types: vec![],
limit: Some(10001),
offset: None,
privileged: BooleanDefaultFalse(None),
privileged_reason: None,
partial: None,
};
assert!(validate_list_certificates_args(&args).is_err());
}
#[test]
fn test_prove_certificate_valid() {
let args = ProveCertificateArgs {
certificate: Certificate {
cert_type: CertificateType([0u8; 32]),
serial_number: SerialNumber([0u8; 32]),
subject: test_pubkey(),
certifier: test_pubkey(),
revocation_outpoint: None,
fields: None,
signature: None,
}
.into(),
fields_to_reveal: vec!["name".to_string()],
verifier: test_pubkey(),
privileged: BooleanDefaultFalse(None),
privileged_reason: None,
};
assert!(validate_prove_certificate_args(&args).is_ok());
}
#[test]
fn test_prove_certificate_empty_fields() {
let args = ProveCertificateArgs {
certificate: Certificate {
cert_type: CertificateType([0u8; 32]),
serial_number: SerialNumber([0u8; 32]),
subject: test_pubkey(),
certifier: test_pubkey(),
revocation_outpoint: None,
fields: None,
signature: None,
}
.into(),
fields_to_reveal: vec![],
verifier: test_pubkey(),
privileged: BooleanDefaultFalse(None),
privileged_reason: None,
};
assert!(validate_prove_certificate_args(&args).is_err());
}
#[test]
fn test_relinquish_certificate_valid() {
let args = RelinquishCertificateArgs {
cert_type: CertificateType([0u8; 32]),
serial_number: SerialNumber([0u8; 32]),
certifier: test_pubkey(),
};
assert!(validate_relinquish_certificate_args(&args).is_ok());
}
#[test]
fn test_discover_by_identity_key_valid() {
let args = DiscoverByIdentityKeyArgs {
identity_key: test_pubkey(),
limit: None,
offset: None,
seek_permission: None,
};
assert!(validate_discover_by_identity_key_args(&args).is_ok());
}
#[test]
fn test_discover_by_attributes_valid() {
let mut attrs = HashMap::new();
attrs.insert("name".to_string(), "Alice".to_string());
let args = DiscoverByAttributesArgs {
attributes: attrs,
limit: None,
offset: None,
seek_permission: None,
};
assert!(validate_discover_by_attributes_args(&args).is_ok());
}
#[test]
fn test_discover_by_attributes_empty() {
let args = DiscoverByAttributesArgs {
attributes: HashMap::new(),
limit: None,
offset: None,
seek_permission: None,
};
assert!(validate_discover_by_attributes_args(&args).is_err());
}
#[test]
fn test_reveal_counterparty_key_linkage_valid() {
let args = RevealCounterpartyKeyLinkageArgs {
counterparty: test_pubkey(),
verifier: test_pubkey(),
privileged: None,
privileged_reason: None,
};
assert!(validate_reveal_counterparty_key_linkage_args(&args).is_ok());
}
#[test]
fn test_reveal_specific_key_linkage_valid() {
let args = RevealSpecificKeyLinkageArgs {
counterparty: test_counterparty(),
verifier: test_pubkey(),
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
privileged: None,
privileged_reason: None,
};
assert!(validate_reveal_specific_key_linkage_args(&args).is_ok());
}
#[test]
fn test_reveal_specific_key_linkage_empty_key_id() {
let args = RevealSpecificKeyLinkageArgs {
counterparty: test_counterparty(),
verifier: test_pubkey(),
protocol_id: test_protocol(),
key_id: "".to_string(),
privileged: None,
privileged_reason: None,
};
assert!(validate_reveal_specific_key_linkage_args(&args).is_err());
}
#[test]
fn test_get_header_valid() {
let args = GetHeaderArgs { height: 100 };
assert!(validate_get_header_args(&args).is_ok());
}
#[test]
fn test_get_header_zero_height() {
let args = GetHeaderArgs { height: 0 };
assert!(validate_get_header_args(&args).is_err());
}
#[test]
fn test_privileged_without_reason() {
let args = EncryptArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
plaintext: vec![1, 2, 3],
privileged: true,
privileged_reason: None,
seek_permission: None,
};
assert!(validate_encrypt_args(&args).is_err());
}
#[test]
fn test_privileged_with_short_reason() {
let args = EncryptArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
plaintext: vec![1, 2, 3],
privileged: true,
privileged_reason: Some("ab".to_string()),
seek_permission: None,
};
assert!(validate_encrypt_args(&args).is_err());
}
#[test]
fn test_privileged_with_valid_reason() {
let args = EncryptArgs {
protocol_id: test_protocol(),
key_id: "my-key".to_string(),
counterparty: test_counterparty(),
plaintext: vec![1, 2, 3],
privileged: true,
privileged_reason: Some("Admin access required".to_string()),
seek_permission: None,
};
assert!(validate_encrypt_args(&args).is_ok());
}
}