nash_protocol/types/blockchain/
neo.rs

1//! NEO specific types shared across protocol requests
2
3use super::super::{Amount, Asset, AssetOrCrosschain, Nonce, OrderRate, Rate};
4use super::bigdecimal_to_nash_u64;
5use crate::errors::{ProtocolError, Result};
6use bs58::{decode, encode};
7use nash_mpc::curves::secp256_r1::Secp256r1Point;
8use nash_mpc::curves::traits::ECPoint;
9use nash_mpc::rust_bigint::traits::Converter;
10use nash_mpc::rust_bigint::BigInt;
11use ripemd160::Ripemd160;
12use sha2::{Digest, Sha256};
13
14impl Rate {
15    /// Convert any Rate into bytes for encoding in a NEO payload
16    pub fn to_le_bytes(&self) -> Result<[u8; 8]> {
17        let zero_bytes = (0 as f64).to_le_bytes();
18        let bytes = match self {
19            Self::OrderRate(rate) | Self::FeeRate(rate) => rate.to_le_bytes()?,
20            Self::MinOrderRate | Self::MinFeeRate => zero_bytes,
21            Self::MaxOrderRate => [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
22            // 0.0025 * 10^8 = 250,000
23            Self::MaxFeeRate => [0x90, 0xD0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00],
24        };
25        Ok(bytes)
26    }
27}
28
29impl OrderRate {
30    /// Serialize the OrderRate to bytes for payload creation. We always use a
31    /// precision of 8 and multiplication factor of 10^8
32    pub fn to_le_bytes(&self) -> Result<[u8; 8]> {
33        let bytes = bigdecimal_to_nash_u64(&self.to_bigdecimal(), 8)?.to_le_bytes();
34        Ok(bytes)
35    }
36}
37
38impl Asset {
39    /// This maps assets onto their representation in the NEO SC protocol.
40    /// Each asset is represented by two bytes which serve as an identifier
41    pub fn to_neo_bytes(&self) -> Vec<u8> {
42        match self {
43            Self::ETH => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
44                .unwrap()
45                .to_bytes(),
46            Self::BAT => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
47                .unwrap()
48                .to_bytes(),
49            Self::OMG => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
50                .unwrap()
51                .to_bytes(),
52            Self::USDC => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
53                .unwrap()
54                .to_bytes(),
55            Self::USDT => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
56                .unwrap()
57                .to_bytes(),
58            Self::ZRX => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
59                .unwrap()
60                .to_bytes(),
61            Self::LINK => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
62                .unwrap()
63                .to_bytes(),
64            Self::QNT => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
65                .unwrap()
66                .to_bytes(),
67            Self::RLC => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
68                .unwrap()
69                .to_bytes(),
70            Self::ANT => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
71                .unwrap()
72                .to_bytes(),
73            Self::TRAC => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
74                .unwrap()
75                .to_bytes(),
76            Self::GUNTHY => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
77                .unwrap()
78                .to_bytes(),
79            Self::BTC => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
80                .unwrap()
81                .to_bytes(),
82            Self::NOIA => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
83                .unwrap()
84                .to_bytes(),
85            Self::NEO => {
86                BigInt::from_hex("9B7CFFDAA674BEAE0F930EBE6085AF9093E5FE56B34A5C220CCDCF6EFC336FC5")
87                    .unwrap()
88                    .to_bytes()
89            }
90            Self::GAS => {
91                BigInt::from_hex("E72D286979EE6CB1B7E65DFDDFB2E384100B8D148E7758DE42E4168B71792C60")
92                    .unwrap()
93                    .to_bytes()
94            }
95            Self::NNN => BigInt::from_hex("045fab3389daf5602fa0953b4d7db3ef7b57b753")
96                .unwrap()
97                .to_bytes(),
98        }
99    }
100
101    /// Given two bytes asset id, return asset
102    pub fn from_neo_bytes(bytes: &[u8; 32]) -> Result<Self> {
103        match bytes {
104            [0x9B, 0x7C, 0xFF, 0xDA, 0xA6, 0x74, 0xBE, 0xAE, 0x0F, 0x93, 0x0E, 0xBE, 0x60, 0x85, 0xAF, 0x90, 0x93, 0xE5, 0xFE, 0x56, 0xB3, 0x4A, 0x5C, 0x22, 0x0C, 0xCD, 0xCF, 0x6E, 0xFC, 0x33, 0x6F, 0xC5] => {
105                Ok(Self::NEO)
106            }
107            [0xE7, 0x2D, 0x28, 0x69, 0x79, 0xEE, 0x6C, 0xB1, 0xB7, 0xE6, 0x5D, 0xFD, 0xDF, 0xB2, 0xE3, 0x84, 0x10, 0x0B, 0x8D, 0x14, 0x8E, 0x77, 0x58, 0xDE, 0x42, 0xE4, 0x16, 0x8B, 0x71, 0x79, 0x2C, 0x60] => {
108                Ok(Self::GAS)
109            }
110            _ => Err(ProtocolError("Invalid Asset ID in bytes")),
111        }
112    }
113}
114
115impl AssetOrCrosschain {
116    /// Convert asset to id in bytes interpretable by the NEO
117    /// smart contract, or `0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF` if it is a cross-chain asset
118    pub fn to_neo_bytes(&self) -> Vec<u8> {
119        match self {
120            Self::Crosschain => BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
121                .unwrap()
122                .to_bytes(),
123            Self::Asset(asset) => asset.to_neo_bytes().to_vec(),
124        }
125    }
126    /// Read asset bytes from a protocol payload and convert into
127    /// an Asset or mark as cross-chain
128    pub fn from_neo_bytes(bytes: Vec<u8>) -> Result<Self> {
129        if bytes.len() == 20
130            && bytes
131                == [
132                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
133                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
134                ]
135                .to_vec()
136        {
137            Ok(Self::Crosschain)
138        } else if bytes.len() == 32 {
139            // convert vector to fixed-size array
140            let mut arr = [0; 32];
141            let bytes = &bytes[..arr.len()];
142            arr.copy_from_slice(bytes);
143            Ok(Self::Asset(Asset::from_neo_bytes(&arr)?))
144        } else {
145            Err(ProtocolError("Invalid Asset ID in bytes"))
146        }
147    }
148}
149
150impl Amount {
151    /// Serialize Amount to Little Endian bytes for NEO payload creation.
152    pub fn to_le_bytes(&self) -> Result<[u8; 8]> {
153        let bytes = bigdecimal_to_nash_u64(&self.to_bigdecimal(), 8)?.to_le_bytes();
154        Ok(bytes)
155    }
156}
157
158impl Nonce {
159    /// Serialize Nonce for NEO payload as 8 bytes LittleEndian
160    pub fn to_le_bytes(&self) -> [u8; 8] {
161        match self {
162            Self::Value(value) => u64::from(*value).to_le_bytes(),
163            Self::Crosschain => u64::from(Nonce::crosschain()).to_le_bytes(),
164        }
165    }
166}
167
168/// NEO address representation
169#[derive(Clone, Debug, PartialEq)]
170pub struct Address {
171    inner: BigInt,
172}
173
174impl Address {
175    /// Create address from the typical base58 encoded string
176    pub fn new(addr: &str) -> Result<Self> {
177        // base56check implements some basic verification
178        let bytes = decode(addr)
179            .with_check(None)
180            .into_vec()
181            .map_err(|_| ProtocolError("Could not base58check decode NEO address"))?;
182        let hex_str = hex::encode(bytes);
183        // Copied from neon-js
184        Self::from_script_hash(&hex_str[2..42])
185    }
186
187    /// Create address from a script hash
188    pub fn from_script_hash(s: &str) -> Result<Self> {
189        Ok(Self {
190            // FIXME: also do some verification
191            inner: BigInt::from_hex(s)
192                .map_err(|_| ProtocolError("Could not parse NEO script hash as BigInt"))?,
193        })
194    }
195
196    /// Serialize the address into bytes for payload creation
197    pub fn to_bytes(&self) -> Vec<u8> {
198        self.inner.to_bytes()
199    }
200}
201
202/// NEO public key representation
203#[derive(Clone, Debug, PartialEq)]
204pub struct PublicKey {
205    // FIXME: why?
206    pub inner: Secp256r1Point,
207}
208
209impl PublicKey {
210    /// Create a new NEO public key from a hex string
211    pub fn new(hex_str: &str) -> Result<Self> {
212        let inner = Secp256r1Point::from_hex(hex_str).map_err(|_| {
213            ProtocolError("Could not create public key (Secp256r1Point) from hex string")
214        })?;
215        Ok(Self { inner })
216    }
217
218    /// Return the PublicKey as a vector of bytes.
219    pub fn to_bytes(&self) -> Vec<u8> {
220        self.inner.to_bytes()
221    }
222
223    /// generate Neo address from public key
224    pub fn to_address(&self) -> Address {
225        // 0x21 (PUSHBYTES21 opcode = size of compressed public key) | compressed public key | 0xac (CHECKSIG opcode)
226        let addr_script = [
227            vec![0x21],
228            self.to_bytes(),
229            vec![0xac],
230        ]
231        .concat();
232
233        // compute script hash: sha256 first, then ripemd160
234        let hash = Ripemd160::digest(&Sha256::digest(&addr_script));
235
236        // prefix hash with some NEO-specific version (0x17)
237        let neo_hash = [vec![0x17], hash.to_vec()].concat();
238
239        // base58check encode
240        let address = encode(neo_hash).with_check().into_string();
241        Address::new(&address).unwrap()
242    }
243
244    /// Get Secp256r1 point associated with NEO public key
245    pub fn to_point(&self) -> Secp256r1Point {
246        self.inner.clone()
247    }
248
249    /// Conver NEO public key to hex string
250    pub fn to_hex(&self) -> String {
251        self.inner.to_hex()
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::{Address, PublicKey};
258
259    #[test]
260    fn inverse_op() {
261        let pk_string = "029ff76d1287091b34c77bd580c631931307631f5538d7f267ea1d8b2ee1cd5bc2";
262        let pk = PublicKey::new(pk_string).expect("Couldn't create public key.");
263        assert_eq!(pk_string, pk.to_hex());
264        assert_eq!(hex::decode(pk_string).unwrap(), pk.to_bytes());
265    }
266
267    #[test]
268    fn test_pk_to_addr() {
269        assert_eq!(
270            PublicKey::new("035a928f201639204e06b4368b1a93365462a8ebbff0b8818151b74faab3a2b61a")
271                .unwrap()
272                .to_address(),
273            Address::new("AXaXZjZGA3qhQRTCsyG5uFKr9HeShgVhTF").unwrap(),
274        );
275        assert_eq!(
276            PublicKey::new("027973267230b7cba0724589653e667ddea7aa8479c01a82bf8dd398cec93508ef")
277                .unwrap()
278                .to_address(),
279            Address::new("AayaivCAcYnM8q79JCrfpRGXrCEHJRN5bV").unwrap(),
280        );
281        assert_eq!(
282            PublicKey::new("0208035f32c5d0e59f71c55b1e060f6d504c004222c25670ff2b788d76a84af2f2")
283                .unwrap()
284                .to_address(),
285            Address::new("AVSFwsxsedGGip1FgJdjavVBuFHHRXD2Uz").unwrap(),
286        );
287        assert_eq!(
288            PublicKey::new("03a41d39a4209e18eb79245ea368674edf335a4ebeb8ed344b87d3ccaf3a2c730e")
289                .unwrap()
290                .to_address(),
291            Address::new("ANwZ2RFRKrASBvZifGgjqqJNUbfJUW6gbn").unwrap(),
292        );
293    }
294}