nash_protocol/
utils.rs

1//! Miscellaneous helper functions
2
3use crate::errors::{ProtocolError, Result};
4use bigdecimal::BigDecimal;
5use nash_mpc::rust_bigint::traits::Converter;
6use nash_mpc::rust_bigint::BigInt;
7use sha3::{Digest, Keccak256};
8use std::str::FromStr;
9use std::time::{SystemTime, UNIX_EPOCH};
10
11#[cfg(feature = "num_bigint")]
12use num_traits::Num;
13
14/// Format BigInt signature values as hex in the way Nash ME expects
15pub fn bigint_to_nash_sig(num: BigInt) -> String {
16    format!("{:0>1024}", num.to_hex())
17}
18
19/// Format Nash MPC r value as hex in the way Nash ME expects
20pub fn bigint_to_nash_r(r: BigInt) -> String {
21    format!("{:0>66}", r.to_hex())
22}
23
24/// Hash ethereum data with keccak
25pub fn hash_eth_message(msg: &[u8]) -> BigInt {
26    // For signing in Ethereum, the message is first prefixed with the header \x19Ethereum Signed Message:\n followed by the length of the message.
27    // The length of the message is always 32 bytes, since the "message" to be used is a (keccak256) hash of the actual message.
28    const ETH_PREFIX: &[u8] = b"\x19Ethereum Signed Message:\n32";
29    // append the hash of the message to the prefix and hash the result all over again
30    let msg_hash =
31        Keccak256::digest(&[&ETH_PREFIX[..], &Keccak256::digest(&msg).to_vec()].concat());
32    BigInt::from_bytes(&msg_hash)
33}
34
35/// Hash NEO message (FIXME: move)
36pub fn hash_neo_message(msg: &[u8]) -> BigInt {
37    let msg_hash_once = sha2::Sha256::digest(msg);
38    let msg_hash_twice = sha2::Sha256::digest(&msg_hash_once[..]);
39    BigInt::from_str_radix(&hex::encode(msg_hash_twice), 16).unwrap()
40}
41
42// SHA256 hash for ME request payloads
43pub fn hash_message(message: &str) -> BigInt {
44    let hash = sha2::Sha256::digest(message.as_bytes());
45    BigInt::from_str_radix(&hex::encode(hash), 16).unwrap()
46}
47
48/// Produce DER encoding in bytes for secp256k1 signature
49pub fn der_encode_sig(r: &BigInt, s: &BigInt) -> Vec<u8> {
50    let r_bytes = der_encode_sig_value(r);
51    let s_bytes = der_encode_sig_value(s);
52    let mut der_bytes = Vec::new();
53    // beginning of DER encoding, indicates compound value
54    der_bytes.push(0x30);
55    // length descriptor for everything that follows
56    der_bytes.push((r_bytes.len() + s_bytes.len()) as u8);
57    // append r and s values
58    for byte in r_bytes {
59        der_bytes.push(byte);
60    }
61    for byte in s_bytes {
62        der_bytes.push(byte)
63    }
64    der_bytes
65}
66
67fn der_encode_sig_value(r_or_s: &BigInt) -> Vec<u8> {
68    let mut bytes = r_or_s.to_bytes();
69    // if highest bit of first byte is 1, prepend 0x00
70    if bytes[0] >= 0b1000_0000 {
71        bytes.insert(0, 0x00);
72    }
73    // length descriptor for r or s data
74    bytes.insert(0, bytes.len() as u8);
75    // indicates integer value follows
76    bytes.insert(0, 0x02);
77    bytes
78}
79
80/// Get current time in millis as an `i64` for GraphQL timestamps
81pub fn current_time_as_i64() -> i64 {
82    SystemTime::now()
83        .duration_since(UNIX_EPOCH)
84        .unwrap()
85        .as_millis() as i64
86}
87
88pub fn decode_hexstr(hex_str: &str) -> Result<Vec<u8>> {
89    Ok(hex::decode(hex_str).map_err(|_| ProtocolError("Could not decode hex string"))?)
90}
91
92// FIXME: this is super ugly
93/// Pad numerical string with zeros to the desired precision. Required for Nash Me backend
94pub fn pad_zeros(str_num: &str, precision: u32) -> Result<String> {
95    let components: Vec<&str> = str_num.split('.').collect();
96    match components.len() {
97        1 => {
98            let mut zeros = ".".to_string();
99            for _ in 0..precision {
100                zeros += "0"
101            }
102            Ok(str_num.to_string() + &zeros)
103        }
104        2 => {
105            let mut zeros = "".to_string();
106            let existing_count = components[1].len();
107            if existing_count as u32 > precision {
108                // If precision too high, lower it
109                let num_zeros_to_subtract = existing_count as u32 - precision;
110                let reduced_prec = &str_num[..(str_num.len() - (num_zeros_to_subtract as usize))];
111                Ok(reduced_prec.to_string())
112            } else {
113                for _ in 0..(precision - existing_count as u32) {
114                    zeros += "0"
115                }
116                Ok(str_num.to_string() + &zeros)
117            }
118        }
119        _ => Err(ProtocolError(
120            "String was not a valid number for zero padding",
121        )),
122    }
123}
124
125pub fn convert_at_price(amount: &str, price: &str) -> Result<String> {
126    let amount_bd = BigDecimal::from_str(amount)
127        .map_err(|_| ProtocolError("Could not convert amount to bigdecimal"))?;
128    let price_bd = BigDecimal::from_str(price)
129        .map_err(|_| ProtocolError("Could not convert price to bigdecimal"))?;
130    let convert = amount_bd * price_bd.inverse();
131    Ok(convert.to_string())
132}