iuna

iuna - experimental devnet protocol
git clone https://iuna.jhx.app/git/iuna.git
Log | Files | Refs | README | LICENSE

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 }