Skip to main content

neo_vm_core/
native.rs

1//! Native Contract Implementations
2//!
3//! Built-in contracts that provide core blockchain functionality.
4
5use crate::stack_item::StackItem;
6use sha2::{Digest, Sha256};
7
8/// Maximum input size for native contract functions (1MB)
9const MAX_INPUT_SIZE: usize = 1024 * 1024;
10
11/// Native contract interface
12pub trait NativeContract {
13    fn hash(&self) -> [u8; 20];
14    fn invoke(&self, method: &str, args: Vec<StackItem>) -> Result<StackItem, String>;
15}
16
17/// StdLib native contract - utility functions
18#[derive(Debug, Default)]
19pub struct StdLib;
20
21impl StdLib {
22    #[inline]
23    pub fn new() -> Self {
24        Self
25    }
26
27    #[inline]
28    fn serialize(&self, args: Vec<StackItem>) -> Result<StackItem, String> {
29        if args.is_empty() {
30            return Err("serialize requires 1 argument".to_string());
31        }
32        let bytes = bincode::serialize(&args[0]).map_err(|e| e.to_string())?;
33        Ok(StackItem::ByteString(bytes))
34    }
35
36    fn deserialize(&self, args: Vec<StackItem>) -> Result<StackItem, String> {
37        if let Some(StackItem::ByteString(bytes)) = args.first() {
38            if bytes.len() > MAX_INPUT_SIZE {
39                return Err(format!(
40                    "deserialize input exceeds maximum size of {} bytes",
41                    MAX_INPUT_SIZE
42                ));
43            }
44            bincode::deserialize(bytes).map_err(|e| format!("deserialize failed: {}", e))
45        } else {
46            Err("deserialize requires ByteString argument".to_string())
47        }
48    }
49
50    #[inline]
51    fn json_serialize(&self, args: Vec<StackItem>) -> Result<StackItem, String> {
52        if args.is_empty() {
53            return Err("jsonSerialize requires 1 argument".to_string());
54        }
55        let json = serde_json::to_string(&args[0]).map_err(|e| e.to_string())?;
56        if json.len() > MAX_INPUT_SIZE {
57            return Err(format!(
58                "jsonSerialize output exceeds maximum size of {} bytes",
59                MAX_INPUT_SIZE
60            ));
61        }
62        Ok(StackItem::ByteString(json.into_bytes()))
63    }
64}
65
66impl StdLib {
67    #[inline]
68    fn base64_encode(&self, args: Vec<StackItem>) -> Result<StackItem, String> {
69        if let Some(StackItem::ByteString(bytes)) = args.first() {
70            if bytes.len() > MAX_INPUT_SIZE {
71                return Err(format!(
72                    "base64Encode input exceeds maximum size of {} bytes",
73                    MAX_INPUT_SIZE
74                ));
75            }
76            use base64::{engine::general_purpose::STANDARD, Engine};
77            let encoded = STANDARD.encode(bytes);
78            Ok(StackItem::ByteString(encoded.into_bytes()))
79        } else {
80            Err("base64Encode requires ByteString".to_string())
81        }
82    }
83
84    #[inline]
85    fn base64_decode(&self, args: Vec<StackItem>) -> Result<StackItem, String> {
86        if let Some(StackItem::ByteString(bytes)) = args.first() {
87            if bytes.len() > MAX_INPUT_SIZE {
88                return Err(format!(
89                    "base64Decode input exceeds maximum size of {} bytes",
90                    MAX_INPUT_SIZE
91                ));
92            }
93            use base64::{engine::general_purpose::STANDARD, Engine};
94            let s = String::from_utf8_lossy(bytes);
95            let decoded = STANDARD.decode(s.as_ref()).map_err(|e| e.to_string())?;
96            Ok(StackItem::ByteString(decoded))
97        } else {
98            Err("base64Decode requires ByteString".to_string())
99        }
100    }
101}
102
103impl StdLib {
104    #[inline]
105    fn itoa(&self, args: Vec<StackItem>) -> Result<StackItem, String> {
106        if let Some(StackItem::Integer(n)) = args.first() {
107            let base = args
108                .get(1)
109                .and_then(|i| {
110                    if let StackItem::Integer(b) = i {
111                        Some(*b as u32)
112                    } else {
113                        None
114                    }
115                })
116                .unwrap_or(10);
117            if base != 2 && base != 10 && base != 16 {
118                return Err(format!(
119                    "Unsupported base {}. Supported bases: 2 (binary), 10 (decimal), 16 (hexadecimal)",
120                    base
121                ));
122            }
123            let s = match base {
124                2 => format!("{:b}", n),
125                10 => format!("{}", n),
126                16 => format!("{:x}", n),
127                _ => unreachable!(),
128            };
129            Ok(StackItem::ByteString(s.into_bytes()))
130        } else {
131            Err("itoa requires Integer".to_string())
132        }
133    }
134
135    #[inline]
136    fn atoi(&self, args: Vec<StackItem>) -> Result<StackItem, String> {
137        if let Some(StackItem::ByteString(bytes)) = args.first() {
138            if bytes.len() > MAX_INPUT_SIZE {
139                return Err(format!(
140                    "atoi input exceeds maximum size of {} bytes",
141                    MAX_INPUT_SIZE
142                ));
143            }
144            let s = String::from_utf8_lossy(bytes);
145            let base = args
146                .get(1)
147                .and_then(|i| {
148                    if let StackItem::Integer(b) = i {
149                        Some(*b as u32)
150                    } else {
151                        None
152                    }
153                })
154                .unwrap_or(10);
155            if base != 2 && base != 10 && base != 16 {
156                return Err(format!(
157                    "Unsupported base {}. Supported bases: 2 (binary), 10 (decimal), 16 (hexadecimal)",
158                    base
159                ));
160            }
161            let n = i128::from_str_radix(s.trim(), base).map_err(|e| e.to_string())?;
162            Ok(StackItem::Integer(n))
163        } else {
164            Err("atoi requires ByteString".to_string())
165        }
166    }
167}
168
169impl NativeContract for StdLib {
170    #[inline]
171    fn hash(&self) -> [u8; 20] {
172        [
173            0xac, 0xce, 0x6f, 0xd8, 0x0d, 0x44, 0xe1, 0xa3, 0x92, 0x6d, 0xe2, 0x1c, 0xcf, 0x30,
174            0x96, 0x9a, 0x22, 0x4b, 0xc0, 0x6b,
175        ]
176    }
177
178    #[inline]
179    fn invoke(&self, method: &str, args: Vec<StackItem>) -> Result<StackItem, String> {
180        match method {
181            "serialize" => self.serialize(args),
182            "deserialize" => self.deserialize(args),
183            "jsonSerialize" => self.json_serialize(args),
184            "base64Encode" => self.base64_encode(args),
185            "base64Decode" => self.base64_decode(args),
186            "itoa" => self.itoa(args),
187            "atoi" => self.atoi(args),
188            _ => Err(format!("Unknown method: {}", method)),
189        }
190    }
191}
192
193/// CryptoLib native contract - cryptographic functions
194#[derive(Debug, Default)]
195pub struct CryptoLib;
196
197impl CryptoLib {
198    pub fn new() -> Self {
199        Self
200    }
201}
202
203impl NativeContract for CryptoLib {
204    #[inline]
205    fn hash(&self) -> [u8; 20] {
206        [
207            0x72, 0x6c, 0xb6, 0xe0, 0xcd, 0x8b, 0x0a, 0xc3, 0x3c, 0xe1, 0xde, 0xc0, 0xd4, 0x7e,
208            0x5c, 0x3c, 0x4a, 0x6b, 0x8a, 0x0d,
209        ]
210    }
211
212    #[inline]
213    fn invoke(&self, method: &str, args: Vec<StackItem>) -> Result<StackItem, String> {
214        match method {
215            "sha256" => self.sha256(args),
216            "ripemd160" => self.ripemd160(args),
217            "verifyWithECDsa" => self.verify_ecdsa(args),
218            _ => Err(format!("Unknown method: {}", method)),
219        }
220    }
221}
222
223impl CryptoLib {
224    #[inline]
225    fn sha256(&self, args: Vec<StackItem>) -> Result<StackItem, String> {
226        if let Some(StackItem::ByteString(data)) = args.first() {
227            if data.len() > MAX_INPUT_SIZE {
228                return Err(format!(
229                    "sha256 input exceeds maximum size of {} bytes",
230                    MAX_INPUT_SIZE
231                ));
232            }
233            let hash = Sha256::digest(data);
234            Ok(StackItem::ByteString(hash.to_vec()))
235        } else {
236            Err("sha256 requires ByteString".to_string())
237        }
238    }
239
240    #[inline]
241    fn ripemd160(&self, args: Vec<StackItem>) -> Result<StackItem, String> {
242        if let Some(StackItem::ByteString(data)) = args.first() {
243            if data.len() > MAX_INPUT_SIZE {
244                return Err(format!(
245                    "ripemd160 input exceeds maximum size of {} bytes",
246                    MAX_INPUT_SIZE
247                ));
248            }
249            use ripemd::Ripemd160;
250            let hash = Ripemd160::digest(data);
251            Ok(StackItem::ByteString(hash.to_vec()))
252        } else {
253            Err("ripemd160 requires ByteString".to_string())
254        }
255    }
256
257    #[inline]
258    fn verify_ecdsa(&self, args: Vec<StackItem>) -> Result<StackItem, String> {
259        use k256::ecdsa::{signature::Verifier, Signature, VerifyingKey};
260
261        if args.len() < 2 {
262            return Err("verify_ecdsa requires at least 2 arguments".to_string());
263        }
264
265        let message = match &args[0] {
266            StackItem::ByteString(msg) => msg.as_slice(),
267            _ => return Err("verify_ecdsa: first argument must be ByteString".to_string()),
268        };
269
270        let signature = match &args[1] {
271            StackItem::ByteString(sig) => sig.as_slice(),
272            _ => return Err("verify_ecdsa: second argument must be ByteString".to_string()),
273        };
274
275        let pubkey = if args.len() >= 3 {
276            match &args[2] {
277                StackItem::ByteString(pk) => pk.as_slice(),
278                _ => return Err("verify_ecdsa: third argument must be ByteString".to_string()),
279            }
280        } else {
281            return Err("verify_ecdsa: public key required".to_string());
282        };
283
284        if message.len() > MAX_INPUT_SIZE {
285            return Err(format!(
286                "verify_ecdsa message exceeds maximum size of {} bytes",
287                MAX_INPUT_SIZE
288            ));
289        }
290
291        let signature = Signature::from_slice(signature)
292            .map_err(|_| "Invalid ECDSA signature format".to_string())?;
293        let verifying_key = VerifyingKey::from_sec1_bytes(pubkey)
294            .map_err(|_| "Invalid public key format".to_string())?;
295
296        Ok(StackItem::Boolean(
297            verifying_key.verify(message, &signature).is_ok(),
298        ))
299    }
300}
301
302/// Native contract registry
303#[derive(Default)]
304pub struct NativeRegistry {
305    stdlib: StdLib,
306    cryptolib: CryptoLib,
307}
308
309impl NativeRegistry {
310    #[inline]
311    pub fn new() -> Self {
312        Self {
313            stdlib: StdLib::new(),
314            cryptolib: CryptoLib::new(),
315        }
316    }
317
318    #[inline]
319    pub fn invoke(
320        &self,
321        hash: &[u8; 20],
322        method: &str,
323        args: Vec<StackItem>,
324    ) -> Result<StackItem, String> {
325        if *hash == self.stdlib.hash() {
326            self.stdlib.invoke(method, args)
327        } else if *hash == self.cryptolib.hash() {
328            self.cryptolib.invoke(method, args)
329        } else {
330            Err("Unknown native contract".to_string())
331        }
332    }
333}