waves-rust 0.2.6

A Rust library for interacting with the Waves blockchain. Supports node interaction, offline transaction signing and creating addresses and keys.
Documentation
use crate::constants::{ADDRESS_LENGTH, ADDRESS_VERSION, SIGNATURE_LENGTH};
use crate::error::Result;
use crate::util::{Bytes, Hash, WORDS};
use curve25519_dalek::constants;
use curve25519_dalek::scalar::Scalar;
use rand::Rng;
use sha2::digest::Update;
use sha2::Sha512;

pub struct Crypto;

impl Crypto {
    pub fn get_random_seed_phrase(words_count: u8) -> String {
        let mut rng = rand::thread_rng();
        let results = rand::seq::index::sample(&mut rng, 2048, words_count as usize).into_vec();
        let mut seed_phrase_array: Vec<&str> = vec![];
        for result in results {
            seed_phrase_array.push(WORDS[result])
        }
        seed_phrase_array.join(" ")
    }

    pub fn get_account_seed(seed_phrase: &[u8], nonce: u8) -> Result<Vec<u8>> {
        Hash::secure_hash(&Bytes::concat(vec![
            Bytes::from_nonce(nonce),
            seed_phrase.to_vec(),
        ]))
    }

    pub fn get_private_key(account_seed: &Vec<u8>) -> Result<[u8; 32]> {
        let mut private_key = [0u8; 32];
        let hashed_account_seed = Hash::sha256(account_seed);
        private_key.copy_from_slice(&hashed_account_seed);
        private_key[0] &= 248;
        private_key[31] &= 127;
        private_key[31] |= 64;

        Ok(private_key)
    }

    pub fn get_public_key(private_key: &[u8; 32]) -> Vec<u8> {
        let mut pk = [0u8; 32];
        pk.copy_from_slice(private_key);
        let ed_pk = &Scalar::from_bits(pk) * &constants::ED25519_BASEPOINT_TABLE;
        ed_pk.to_montgomery().to_bytes().to_vec()
    }

    pub fn get_public_key_hash(public_key: &[u8]) -> Result<Vec<u8>> {
        let hash = Hash::secure_hash(public_key)?;
        Ok(hash[0..20].to_vec())
    }

    pub fn get_address(chain_id: &u8, public_key_hash: &[u8]) -> Result<Vec<u8>> {
        let mut buf = [0u8; ADDRESS_LENGTH];
        buf[0] = ADDRESS_VERSION;
        buf[1] = *chain_id;
        buf[2..22].copy_from_slice(public_key_hash);
        let checksum = &Hash::secure_hash(&buf[..22])?[..4];
        buf[22..].copy_from_slice(checksum);
        Ok(buf.to_vec())
    }

    pub fn sign(private_key: &[u8; 32], message: &[u8]) -> Vec<u8> {
        let mut hash = Sha512::default();

        hash.update(&INITBUF);

        hash.update(private_key);
        hash.update(message);

        let mut rand = rand::thread_rng();
        let mut rndbuf: Vec<u8> = vec![0; 64];
        (0..63).for_each(|i| rndbuf[i] = rand.gen::<u8>());

        hash.update(&rndbuf);

        let rsc = Scalar::from_hash(hash);
        let r = (&rsc * &constants::ED25519_BASEPOINT_TABLE)
            .compress()
            .to_bytes();

        let ed_public_key = constants::ED25519_BASEPOINT_POINT * Scalar::from_bits(*private_key);
        let public_key = ed_public_key.compress().to_bytes();

        hash = Sha512::default();
        hash.update(&r);
        hash.update(&public_key);
        hash.update(message);
        let s = (Scalar::from_hash(hash) * Scalar::from_bits(*private_key)) + rsc;

        let sign = public_key[31] & 0x80;
        let mut result = [0; SIGNATURE_LENGTH];
        result[..32].copy_from_slice(&r);
        result[32..].copy_from_slice(&s.to_bytes());
        result[63] &= 0x7F;
        result[63] |= sign;
        result.to_vec()
    }
}

#[cfg(test)]
mod tests {

    use crate::error::Result;
    use crate::model::ChainId;
    use crate::util::{Base58, Crypto};

    #[test]
    fn test_get_private_key() {
        let seed_phrase = "blame vacant regret company chase trip grant funny brisk innocent"
            .as_bytes()
            .to_vec();
        let expected_private_key = "3j2aMHzh9azPphzuW7aF3cmUefGEQC9dcWYXYCyoPcJg";
        let account_seed =
            Crypto::get_account_seed(&seed_phrase, 0).expect("failed to get account seed");
        let private_key =
            Crypto::get_private_key(&account_seed).expect("failed to get private key");
        let encoded_private_key = Base58::encode(&private_key.to_vec(), false);
        assert_eq!(encoded_private_key, expected_private_key)
    }

    #[test]
    fn test_get_public_key() {
        let seed_phrase = "blame vacant regret company chase trip grant funny brisk innocent";

        let expected_public_key_from_nonce_0 = "8cj6YzvQPhSHGvnjupNTW8zrADTT8CMAAd2xTuej84gB";
        let expected_public_key_from_nonce_128 = "DTvCW1nzFr7mHrHkGf1apstRfwPp4yYL19YvjjLEAPBh";
        let expected_public_key_from_nonce_255 = "esjbpqVWSg8iCaPYQA3SoxZo3oUkdRJSi9tKLoqKQoC";
        assert_eq!(
            Crypto::get_public_key(
                &private_key(seed_phrase, 0).expect("failed to get private key")
            ),
            Base58::decode(expected_public_key_from_nonce_0).expect("Failed to decode str")
        );
        assert_eq!(
            Crypto::get_public_key(
                &private_key(seed_phrase, 128).expect("failed to get private key")
            ),
            Base58::decode(expected_public_key_from_nonce_128).expect("Failed to decode str")
        );
        assert_eq!(
            Crypto::get_public_key(
                &private_key(seed_phrase, 255).expect("failed to get private key")
            ),
            Base58::decode(expected_public_key_from_nonce_255).expect("Failed to decode str")
        );
    }

    #[test]
    fn test_get_address() {
        let seed_phrase = "blame vacant regret company chase trip grant funny brisk innocent";

        let expected_address = "3Ms87NGAAaPWZux233TB9A3TXps4LDkyJWN";

        let public_key = Crypto::get_public_key(
            &private_key(seed_phrase, 0).expect("failed to get private key"),
        );
        let public_key_hash =
            Crypto::get_public_key_hash(&public_key).expect("failed to get public key hash");

        let address = Crypto::get_address(&ChainId::TESTNET.byte(), &public_key_hash)
            .expect("failed to get address");
        let encoded_address = Base58::encode(&address, false);

        assert_eq!(encoded_address, expected_address)
    }

    #[test]
    fn test_get_random_seed_phrase() {
        let rng_seed_phrase = Crypto::get_random_seed_phrase(12);
        assert_eq!(12, rng_seed_phrase.split(' ').into_iter().count())
    }

    fn private_key(seed_phrase: &str, nonce: u8) -> Result<[u8; 32]> {
        let account_seed = Crypto::get_account_seed(seed_phrase.as_bytes(), nonce)
            .expect("failed to get account seed");
        Crypto::get_private_key(&account_seed)
    }
}

static INITBUF: [u8; 32] = [
    0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
];