validation.rs (5936B)
1 use anyhow::{Context, Result, bail}; 2 3 use super::{ 4 HASH_BYTES, PUBLIC_KEY_BYTES, SIGNATURE_BYTES, Transaction, TxInput, TxOutput, decode_hex, 5 decode_hex_array, stratum::STRATUM_MINE_HEADER_BYTES, 6 }; 7 8 pub fn validate_address(address: &str, label: &str) -> Result<()> { 9 decode_hex_array::<PUBLIC_KEY_BYTES>(address) 10 .with_context(|| format!("invalid {label} address"))?; 11 Ok(()) 12 } 13 14 pub(super) fn validate_hash(hash: &str, label: &str) -> Result<()> { 15 decode_hex_array::<HASH_BYTES>(hash).with_context(|| format!("invalid {label}"))?; 16 Ok(()) 17 } 18 19 pub(super) fn validate_signature(signature: &str, label: &str) -> Result<()> { 20 decode_hex_array::<SIGNATURE_BYTES>(signature).with_context(|| format!("invalid {label}"))?; 21 Ok(()) 22 } 23 24 pub(super) fn validate_stratum_header(header: &str) -> Result<()> { 25 decode_hex_array::<STRATUM_MINE_HEADER_BYTES>(header) 26 .context("invalid mine transaction proof header")?; 27 Ok(()) 28 } 29 30 pub(super) fn validate_protocol_id(value: &str, label: &str) -> Result<()> { 31 let bytes = decode_hex(value).with_context(|| format!("invalid {label}"))?; 32 match bytes.len() { 33 HASH_BYTES | SIGNATURE_BYTES => Ok(()), 34 length => bail!("invalid {label}: expected 32 or 64 bytes, got {length}"), 35 } 36 } 37 38 pub(super) fn canonical_transaction_size_bytes(transaction: &Transaction) -> usize { 39 match transaction { 40 Transaction::Transfer { 41 inputs, 42 outputs, 43 fee, 44 signature, 45 } => { 46 1 + compact_len(inputs.len() as u128) 47 + compact_inputs_size_bytes(inputs) 48 + compact_len(outputs.len() as u128) 49 + compact_outputs_size_bytes(outputs) 50 + compact_len(u128::from(*fee)) 51 + signature_size_bytes(signature) 52 } 53 Transaction::Burn { 54 inputs, 55 change, 56 amount, 57 fee, 58 signature, 59 } => { 60 1 + compact_len(inputs.len() as u128) 61 + compact_inputs_size_bytes(inputs) 62 + compact_len(change.len() as u128) 63 + compact_outputs_size_bytes(change) 64 + compact_len(u128::from(*amount)) 65 + compact_len(u128::from(*fee)) 66 + signature_size_bytes(signature) 67 } 68 Transaction::Mine { 69 recipient, 70 anchor, 71 salt, 72 nonce, 73 difficulty_bits, 74 proof_header, 75 signature, 76 } => { 77 1 + address_size_bytes(recipient) 78 + hash_size_bytes(anchor) 79 + compact_len(u128::from(*salt)) 80 + compact_len(u128::from(*nonce)) 81 + compact_len(u128::from(*difficulty_bits)) 82 + 1 83 + proof_header 84 .as_ref() 85 .map(|header| stratum_header_size_bytes(header)) 86 .unwrap_or(0) 87 + hash_size_bytes(signature) 88 } 89 } 90 } 91 92 fn compact_inputs_size_bytes(inputs: &[TxInput]) -> usize { 93 inputs 94 .iter() 95 .map(|input| { 96 protocol_id_size_bytes(&input.outpoint.txid) 97 + compact_len(u128::from(input.outpoint.index)) 98 + address_size_bytes(&input.owner) 99 }) 100 .sum() 101 } 102 103 fn compact_outputs_size_bytes(outputs: &[TxOutput]) -> usize { 104 outputs.iter().map(compact_output_size_bytes).sum() 105 } 106 107 fn compact_output_size_bytes(output: &TxOutput) -> usize { 108 address_size_bytes(&output.address) + compact_len(u128::from(output.amount)) 109 } 110 111 fn address_size_bytes(address: &str) -> usize { 112 debug_assert!(validate_address(address, "debug address").is_ok()); 113 PUBLIC_KEY_BYTES 114 } 115 116 fn hash_size_bytes(hash: &str) -> usize { 117 debug_assert!(validate_hash(hash, "debug hash").is_ok()); 118 HASH_BYTES 119 } 120 121 fn signature_size_bytes(signature: &str) -> usize { 122 debug_assert!(validate_signature(signature, "debug signature").is_ok()); 123 SIGNATURE_BYTES 124 } 125 126 fn stratum_header_size_bytes(header: &str) -> usize { 127 debug_assert!(validate_stratum_header(header).is_ok()); 128 STRATUM_MINE_HEADER_BYTES 129 } 130 131 fn protocol_id_size_bytes(value: &str) -> usize { 132 match decode_hex(value).map(|bytes| bytes.len()) { 133 Ok(HASH_BYTES) => HASH_BYTES, 134 Ok(SIGNATURE_BYTES) => SIGNATURE_BYTES, 135 _ => SIGNATURE_BYTES, 136 } 137 } 138 139 pub(super) fn compact_len(mut value: u128) -> usize { 140 let mut bytes = 1; 141 while value >= 0x80 { 142 value >>= 7; 143 bytes += 1; 144 } 145 bytes 146 } 147 148 #[cfg(test)] 149 mod tests { 150 use super::{ 151 compact_len, validate_address, validate_hash, validate_protocol_id, validate_signature, 152 validate_stratum_header, 153 }; 154 155 #[test] 156 fn validators_accept_expected_protocol_lengths() { 157 assert!(validate_address(&"0".repeat(64), "test").is_ok()); 158 assert!(validate_hash(&"0".repeat(64), "test").is_ok()); 159 assert!(validate_signature(&"0".repeat(128), "test").is_ok()); 160 assert!(validate_stratum_header(&"0".repeat(160)).is_ok()); 161 assert!(validate_protocol_id(&"0".repeat(64), "test").is_ok()); 162 assert!(validate_protocol_id(&"0".repeat(128), "test").is_ok()); 163 } 164 165 #[test] 166 fn validators_reject_wrong_lengths_and_bad_hex() { 167 assert!(validate_address(&"0".repeat(62), "test").is_err()); 168 assert!(validate_hash(&"0".repeat(66), "test").is_err()); 169 assert!(validate_signature("zz", "test").is_err()); 170 assert!(validate_stratum_header(&"0".repeat(158)).is_err()); 171 assert!(validate_protocol_id(&"0".repeat(96), "test").is_err()); 172 } 173 174 #[test] 175 fn compact_len_uses_base_128_varint_width() { 176 assert_eq!(compact_len(0), 1); 177 assert_eq!(compact_len(127), 1); 178 assert_eq!(compact_len(128), 2); 179 assert_eq!(compact_len(16_383), 2); 180 assert_eq!(compact_len(16_384), 3); 181 } 182 }