pub const SCHNORR_PROOF_SIZE: usize = 64;
pub const ELGAMAL_TOTAL_SIZE: usize = 66;
pub const PEDERSEN_COMMIT_SIZE: usize = 33;
pub const BLINDING_FACTOR_SIZE: usize = 32;
pub const PUBKEY_COMPRESSED_SIZE: usize = 33;
pub const COMPACT_CLAWBACK_PROOF_SIZE: usize = 64;
pub const COMPACT_CONVERTBACK_PROOF_SIZE: usize = 128;
pub const COMPACT_STANDARD_PROOF_SIZE: usize = 192;
pub const SINGLE_BULLETPROOF_SIZE: usize = 688;
pub const DOUBLE_BULLETPROOF_SIZE: usize = 754;
pub const CLAWBACK_PROOF_SIZE: usize = COMPACT_CLAWBACK_PROOF_SIZE;
pub const CONVERT_BACK_PROOF_SIZE: usize = COMPACT_CONVERTBACK_PROOF_SIZE + SINGLE_BULLETPROOF_SIZE;
pub const SEND_PROOF_SIZE: usize = COMPACT_STANDARD_PROOF_SIZE + DOUBLE_BULLETPROOF_SIZE;
pub const ENCRYPTION_KEY_LENGTH: usize = PUBKEY_COMPRESSED_SIZE * 2;
pub const BLINDING_FACTOR_LENGTH: usize = BLINDING_FACTOR_SIZE * 2;
pub const SCHNORR_PROOF_LENGTH: usize = SCHNORR_PROOF_SIZE * 2;
pub const CIPHERTEXT_LENGTH: usize = ELGAMAL_TOTAL_SIZE * 2;
pub const COMMITMENT_LENGTH: usize = PEDERSEN_COMMIT_SIZE * 2;
pub const CLAWBACK_PROOF_LENGTH: usize = CLAWBACK_PROOF_SIZE * 2;
pub const SEND_PROOF_LENGTH: usize = SEND_PROOF_SIZE * 2;
pub const CONVERT_BACK_PROOF_LENGTH: usize = CONVERT_BACK_PROOF_SIZE * 2;
use crate::core::addresscodec::decode_classic_address;
use crate::models::{XRPLModelException, XRPLModelResult};
const ISSUER_HEX_START: usize = 8;
const ISSUER_HEX_END: usize = 48;
pub(crate) fn address_is_issuer(issuance_id_hex: &str, address: &str) -> bool {
if issuance_id_hex.len() < ISSUER_HEX_END {
return false;
}
let Ok(account_id) = decode_classic_address(address) else {
return false;
};
let issuer_hex = &issuance_id_hex[ISSUER_HEX_START..ISSUER_HEX_END];
issuer_hex.eq_ignore_ascii_case(&hex::encode(account_id))
}
pub(crate) fn validate_hex_length(
field: &str,
value: &str,
expected: usize,
) -> XRPLModelResult<()> {
if value.len() != expected || !value.bytes().all(|b| b.is_ascii_hexdigit()) {
return Err(XRPLModelException::InvalidValueFormat {
field: field.into(),
format: alloc::format!("{expected}-char ASCII hex string ({} bytes)", expected / 2),
found: alloc::format!("{}-char value", value.len()),
});
}
Ok(())
}
pub(crate) const MAX_MPT_AMOUNT: u64 = i64::MAX as u64;
pub(crate) fn validate_mpt_amount(
field: &str,
value: &str,
must_be_positive: bool,
) -> XRPLModelResult<()> {
let amount: u64 = value
.parse()
.map_err(|_| XRPLModelException::InvalidValueFormat {
field: field.into(),
format: "string-encoded unsigned 64-bit integer".into(),
found: value.into(),
})?;
if must_be_positive && amount == 0 {
return Err(XRPLModelException::ValueZero(field.into()));
}
if amount > MAX_MPT_AMOUNT {
return Err(XRPLModelException::InvalidValueFormat {
field: field.into(),
format: alloc::format!("MPT amount in [0, {MAX_MPT_AMOUNT}] (2^63 - 1)"),
found: value.into(),
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mpt_amount_validation() {
assert!(validate_mpt_amount("mpt_amount", "0", false).is_ok());
assert!(validate_mpt_amount("mpt_amount", "0", true).is_err());
assert!(validate_mpt_amount("mpt_amount", "1000", true).is_ok());
assert!(validate_mpt_amount("mpt_amount", "-1", false).is_err());
assert!(validate_mpt_amount("mpt_amount", "abc", false).is_err());
assert!(validate_mpt_amount("mpt_amount", "9223372036854775807", false).is_ok());
assert!(validate_mpt_amount("mpt_amount", "9223372036854775808", false).is_err());
assert!(validate_mpt_amount("mpt_amount", "18446744073709551615", false).is_err());
}
#[test]
fn composite_proof_sizes_match_xls_0096() {
assert_eq!(CLAWBACK_PROOF_SIZE, 64);
assert_eq!(CONVERT_BACK_PROOF_SIZE, 816);
assert_eq!(SEND_PROOF_SIZE, 946);
assert_eq!(CLAWBACK_PROOF_LENGTH, 128);
assert_eq!(CONVERT_BACK_PROOF_LENGTH, 1632);
assert_eq!(SEND_PROOF_LENGTH, 1892);
assert_eq!(CIPHERTEXT_LENGTH, 132);
assert_eq!(COMMITMENT_LENGTH, 66);
assert_eq!(ENCRYPTION_KEY_LENGTH, 66);
assert_eq!(BLINDING_FACTOR_LENGTH, 64);
assert_eq!(SCHNORR_PROOF_LENGTH, 128);
}
#[test]
fn hex_length_validation_rejects_wrong_length_and_non_hex() {
assert!(validate_hex_length("zk_proof", &"AB".repeat(64), 128).is_ok());
assert!(validate_hex_length("zk_proof", &"AB".repeat(63), 128).is_err());
assert!(validate_hex_length("zk_proof", &"ZZ".repeat(64), 128).is_err());
}
}