neo_solidity/neo/
contract_hash.rs

1use ripemd::Ripemd160;
2
3/// Compute the Neo N3 contract hash for a deployment transaction.
4///
5/// Neo derives the deployed contract hash from:
6/// - the deploy transaction sender (UInt160)
7/// - the NEF checksum (u32)
8/// - the manifest name (string)
9///
10/// This matches `Neo.SmartContract.Helper.GetContractHash(sender, nefChecksum, name)` in the
11/// Neo node implementation:
12/// - ScriptBuilder emits: `ABORT`, `PUSH sender`, `PUSH nefChecksum`, `PUSH name`
13/// - Contract hash is `Hash160(script)` (RIPEMD160(SHA256(script))) interpreted as UInt160.
14///
15/// Inputs and outputs use the VM byte order for UInt160 (little-endian 20 bytes).
16pub fn compute_contract_hash(sender_le: [u8; 20], nef_checksum: u32, name: &str) -> [u8; 20] {
17    let mut script = Vec::new();
18    script.push(0x38); // ABORT
19    emit_push_bytes(&mut script, &sender_le);
20    emit_push_u32(&mut script, nef_checksum);
21    emit_push_bytes(&mut script, name.as_bytes());
22    hash160(&script)
23}
24
25/// Parse a Neo UInt160 from a 0x-prefixed (or raw) big-endian hex string.
26///
27/// Returns the UInt160 in NeoVM byte order (little-endian 20 bytes).
28pub fn parse_uint160_hex_be(value: &str) -> Result<[u8; 20], String> {
29    let trimmed = value.trim();
30    let without_prefix = trimmed.strip_prefix("0x").unwrap_or(trimmed);
31    if without_prefix.len() != 40 {
32        return Err(format!(
33            "expected 40 hex characters for UInt160 (got {})",
34            without_prefix.len()
35        ));
36    }
37    let bytes_be = hex::decode(without_prefix)
38        .map_err(|err| format!("invalid hex UInt160 '{value}': {err}"))?;
39    if bytes_be.len() != 20 {
40        return Err(format!(
41            "expected 20 bytes for UInt160 (got {})",
42            bytes_be.len()
43        ));
44    }
45    let mut le = [0u8; 20];
46    for (dst, src) in le.iter_mut().zip(bytes_be.into_iter().rev()) {
47        *dst = src;
48    }
49    Ok(le)
50}
51
52/// Format a Neo UInt160 (little-endian bytes) as a 0x-prefixed big-endian hex string.
53pub fn format_uint160_hex_be(value_le: &[u8; 20]) -> String {
54    let be: Vec<u8> = value_le.iter().rev().copied().collect();
55    format!("0x{}", hex::encode(be))
56}
57
58fn hash160(data: &[u8]) -> [u8; 20] {
59    let sha = Sha256::digest(data);
60    let digest = Ripemd160::digest(sha);
61    let mut out = [0u8; 20];
62    out.copy_from_slice(&digest[..20]);
63    out
64}
65
66fn emit_push_bytes(script: &mut Vec<u8>, data: &[u8]) {
67    if data.len() <= u8::MAX as usize {
68        script.push(0x0C); // PUSHDATA1
69        script.push(data.len() as u8);
70    } else if data.len() <= u16::MAX as usize {
71        script.push(0x0D); // PUSHDATA2
72        script.extend_from_slice(&(data.len() as u16).to_le_bytes());
73    } else {
74        script.push(0x0E); // PUSHDATA4
75        script.extend_from_slice(&(data.len() as u32).to_le_bytes());
76    }
77    script.extend_from_slice(data);
78}
79
80fn emit_push_u32(script: &mut Vec<u8>, value: u32) {
81    if value == 0 {
82        script.push(0x10); // PUSH0
83        return;
84    }
85    if value <= 16 {
86        script.push(0x10 + value as u8);
87        return;
88    }
89    if value <= i8::MAX as u32 {
90        script.push(0x00); // PUSHINT8
91        script.push(value as u8);
92        return;
93    }
94    if value <= i16::MAX as u32 {
95        script.push(0x01); // PUSHINT16
96        script.extend_from_slice(&(value as i16).to_le_bytes());
97        return;
98    }
99    if value <= i32::MAX as u32 {
100        script.push(0x02); // PUSHINT32
101        script.extend_from_slice(&(value as i32).to_le_bytes());
102        return;
103    }
104    script.push(0x03); // PUSHINT64
105    script.extend_from_slice(&(value as i64).to_le_bytes());
106}
107