Skip to main content

neo_devpack_solidity/neo/
contract_hash.rs

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