Nate Houk
Nate Houk

Reputation: 365

How to do cryptographic signature in Rust to avoid Python call

Why in the bottom section of this code, do I need to use the following pattern:

        let a = urlpath.to_string();
        let b = nonce.to_string();
        let c = ordertype.to_string();
        let d = pair.to_string();
        let e = price.to_string();
        let f = type_.to_string();
        let g = volume.to_string();
        let h = api_sec.to_string();
        let kwargs = vec![("cmd", "account_balance"), ("urlpath", &a), ("nonce", &b), ("ordertype", &c), ("pair", &d), ("price", &e), ("type", &f), ("volume", &g), ("secret", &h)];

If I replace the variable &a in the vec! with &urlpath.to_string() then it fails saying a temporary value is being dropped and it's later used.

But doesn't that expression evaluate to the same thing, regardless if I add the additional let statements? How can I make this more Rust idiomatic?

use std::{time::{SystemTime, UNIX_EPOCH}};
use pyo3::types::IntoPyDict;

fn main() -> PyResult<()> {

    let urlpath = "/0/private/Balance";
    println!("{}", urlpath);
    let api_sec = "<REPLACE>";
    println!("{}", api_sec);
    let nonce =  SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_millis();
    println!("{}", nonce);
    let ordertype = "limit";
    println!("{}", ordertype);
    let pair = "XBTUSD";
    println!("{}", pair);
    let price: i32 = 37500;
    println!("{}", price);
    let type_ = "buy";
    println!("{}", type_);
    let volume = 1.25;
    println!("{}", volume);

    Python::with_gil(|py| {
        let fun: Py<PyAny> = PyModule::from_code(
            py,
            "
import urllib.parse
import hashlib
import hmac
import base64

def get_kraken_signature(*args, **kwargs):
    if args != ():
        print('called with args', args)
    if kwargs != {}:
        print('called with kwargs', kwargs)
    if args == () and kwargs == {}:
        print('called with no arguments')
    if kwargs[\"cmd\"] == \"account_balance\":
        urlpath = kwargs[\"urlpath\"]
        data = {
            \"nonce\": kwargs[\"nonce\"],
        }
        secret = kwargs[\"secret\"]
    elif kwargs[\"cmd\"] == \"send_order\":
        urlpath = kwargs[\"urlpath\"]
        data = {
            \"nonce\": kwargs[\"nonce\"],
            \"ordertype\": kwargs[\"ordertype\"], 
            \"pair\": kwargs[\"pair\"],
            \"price\": kwargs[\"price\"], 
            \"type\": kwargs[\"type\"],
            \"volume\": kwargs[\"volume\"],
        }
        secret = kwargs[\"secret\"]
    else:
        exit(0)

    postdata = urllib.parse.urlencode(data)
    encoded = (str(data['nonce']) + postdata).encode()
    message = urlpath.encode() + hashlib.sha256(encoded).digest()
    mac = hmac.new(base64.b64decode(secret), message, hashlib.sha512)
    sigdigest = base64.b64encode(mac.digest())
    print(\"API-Sign: {}\".format(sigdigest.decode()))
    return sigdigest.decode()
",
            "",
            "",
        )?.getattr("get_kraken_signature")?.into();

        let a = urlpath.to_string();
        let b = nonce.to_string();
        let c = ordertype.to_string();
        let d = pair.to_string();
        let e = price.to_string();
        let f = type_.to_string();
        let g = volume.to_string();
        let h = api_sec.to_string();
        let kwargs = vec![("cmd", "account_balance"), ("urlpath", &a), ("nonce", &b), ("ordertype", &c), ("pair", &d), ("price", &e), ("type", &f), ("volume", &g), ("secret", &h)];
        let result = fun.call(py, (), Some(kwargs.into_py_dict(py)))?;

        println!("{}", result);
        Ok(())
    })
}

BONUS: Second part of the question, is how can I rewrite the Python portion in idiomatic Rust? I have tried and failed, so would be helpful if any crypto experts can assist.

Upvotes: 1

Views: 160

Answers (1)

Chandan
Chandan

Reputation: 11807

First Part: (Explanation)

Since a is the owner of the value and you are passing reference using the owner which will remain in scope even after execution but in the case when you directly pass &urlpath.to_string() there isn't any owner and as soon the execution ends the value would be dropped and there will be a dangling reference which is the cause for the message.

Second Part: (Python to rust conversion)
I am not a crypto expert but I tried to convert the same script you provided without the condition part and matched the output in python and rust.

extern crate url;
extern crate base64;
// use std::time::{SystemTime, UNIX_EPOCH};
use url::form_urlencoded;
use sha2::{Sha256, Digest};

extern crate ring;
extern crate data_encoding;

use ring::hmac;
use data_encoding::BASE64;
use std::collections::HashMap;

fn main() {
    let urlpath = String::from("/0/private/Balance");
    // let nonce = SystemTime::now()
    //     .duration_since(UNIX_EPOCH)
    //     .expect("Time went backwards")
    //     .as_millis();
    let nonce: &str = &(1645371362680 as i64).to_string();

    let mut data = HashMap::new();
    data.insert("nonce", nonce);

     let postdata: String = form_urlencoded::Serializer::new(String::new())
        .extend_pairs(data.iter())
        .finish();
    let encoded = format!("{}{}", nonce, postdata);
    let message: Vec<u8> = [urlpath.as_bytes(), Sha256::digest(encoded.as_bytes()).as_ref()].concat();

    let secret_key = String::from("secret");
    let signed_key = hmac::Key::new(hmac::HMAC_SHA512, secret_key.as_bytes());
    let signature = hmac::sign(&signed_key, &message);
    let b64_encoded_sig = BASE64.encode(signature.as_ref());
    println!("Output: {}", b64_encoded_sig);
}

Playground

Upvotes: 1

Related Questions