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::SIGNATURE_LENGTH;
use crate::error::{Error, Result};
use crate::model::account::PublicKey;
use crate::model::ByteString;
use crate::util::{Base58, Crypto};
use curve25519_dalek::montgomery::MontgomeryPoint;
use ed25519_dalek::{PublicKey as EdPublicKey, Signature, Verifier};

#[derive(Clone, Eq, PartialEq, Hash)]
pub struct PrivateKey {
    bytes: [u8; 32],
    public_key: PublicKey,
}

impl std::str::FromStr for PrivateKey {
    type Err = Error;

    fn from_str(base58string: &str) -> Result<PrivateKey> {
        let bytes = Base58::decode(base58string)?;
        let bytes_array: [u8; 32] = bytes
            .try_into()
            .map_err(Error::VectorToArrayConversionError)?;
        PrivateKey::from_bytes(bytes_array)
    }
}

impl PrivateKey {
    pub fn from_seed(seed_phrase: &str, nonce: u8) -> Result<Self> {
        let hash_seed = Crypto::get_account_seed(seed_phrase.as_bytes(), nonce)?;
        let private_key = Crypto::get_private_key(&hash_seed)?;
        let public_key = PublicKey::from_bytes(&Crypto::get_public_key(&private_key))?;
        Ok(Self {
            bytes: private_key,
            public_key,
        })
    }

    pub fn from_bytes(bytes: [u8; 32]) -> Result<Self> {
        let public_key = PublicKey::from_bytes(&Crypto::get_public_key(&bytes))?;
        Ok(Self { bytes, public_key })
    }

    pub fn encoded(&self) -> String {
        Base58::encode(&self.bytes.to_vec(), false)
    }

    pub fn bytes(&self) -> [u8; 32] {
        self.bytes
    }

    pub fn public_key(&self) -> PublicKey {
        self.public_key.clone()
    }

    pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>> {
        Ok(Crypto::sign(&self.bytes, message))
    }

    pub fn is_signature_valid(&self, message: &[u8], signature: &[u8]) -> Result<bool> {
        let sig_arr = <[u8; SIGNATURE_LENGTH]>::try_from(signature.to_owned()).map_err(|_| {
            Error::InvalidBytesLength {
                expected_len: SIGNATURE_LENGTH,
                actual_len: signature.len(),
            }
        })?;
        let sign = sig_arr[63] & 0x80;
        let mut sig = [0u8; SIGNATURE_LENGTH];
        sig.copy_from_slice(signature);
        sig[63] &= 0x7f;

        let public_key_bytes = self.public_key.bytes();
        let mut ed_public_key =
            MontgomeryPoint(<[u8; 32]>::try_from(public_key_bytes.clone()).map_err(|_| {
                Error::InvalidBytesLength {
                    expected_len: 32,
                    actual_len: public_key_bytes.len(),
                }
            })?)
            .to_edwards(sign)
            .ok_or(Error::MontgomeryPointConversionError)?
            .compress()
            .to_bytes();
        ed_public_key[31] &= 0x7F;
        ed_public_key[31] |= sign;

        Ok(EdPublicKey::from_bytes(&ed_public_key)?
            .verify(message, &Signature::from_bytes(&sig)?)
            .is_ok())
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use crate::error::Error::InvalidBytesLength;
    use crate::error::Result;
    use crate::model::account::PrivateKey;
    use crate::model::ByteString;

    #[test]
    fn test_private_key_from_seed() -> Result<()> {
        let seed_phrase = "blame vacant regret company chase trip grant funny brisk innocent";

        let expected_private_key_with_nonce_0 = "3j2aMHzh9azPphzuW7aF3cmUefGEQC9dcWYXYCyoPcJg";
        let expected_private_key_with_nonce_128 = "HCK7dUsScMH9mTCoyaV7bVhkTxwsyCHdbMBfb9TpVhPd";
        let expected_private_key_with_nonce_255 = "5Kdsn9jH3ifWSrZ19NYqnaCN9GmaPmNpZYnuSAEE4Yga";

        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!(
            PrivateKey::from_seed(seed_phrase, 0)?.encoded(),
            expected_private_key_with_nonce_0
        );
        assert_eq!(
            PrivateKey::from_seed(seed_phrase, 128)?.encoded(),
            expected_private_key_with_nonce_128
        );
        assert_eq!(
            PrivateKey::from_seed(seed_phrase, 255)?.encoded(),
            expected_private_key_with_nonce_255
        );

        assert_eq!(
            PrivateKey::from_seed(seed_phrase, 0)?
                .public_key()
                .encoded(),
            expected_public_key_from_nonce_0
        );
        assert_eq!(
            PrivateKey::from_seed(seed_phrase, 128)?
                .public_key()
                .encoded(),
            expected_public_key_from_nonce_128
        );
        assert_eq!(
            PrivateKey::from_seed(seed_phrase, 255)?
                .public_key()
                .encoded(),
            expected_public_key_from_nonce_255
        );
        Ok(())
    }

    #[test]
    fn test_invalid_signature_size() -> Result<()> {
        let private_key = PrivateKey::from_seed("a", 0)?;
        let result = private_key.is_signature_valid(&[], &[0_u8; 32]);
        match result {
            Ok(_) => panic!("expected error"),
            Err(err) => match err {
                InvalidBytesLength { .. } => Ok(()),
                _ => panic!("expected error"),
            },
        }
    }

    #[test]
    fn test_private_key_from_bytes() -> Result<()> {
        let private_key = PrivateKey::from_bytes([0; 32])?;
        assert_eq!(private_key.bytes(), [0_u8; 32]);
        Ok(())
    }

    #[test]
    fn test_private_key_std_from_str() -> Result<()> {
        let seed_phrase = "blame vacant regret company chase trip grant funny brisk innocent";
        let private_key_from_seed = PrivateKey::from_seed(seed_phrase, 0)?;
        let private_key_from_str = PrivateKey::from_str(&private_key_from_seed.encoded())?;
        assert_eq!(private_key_from_seed.bytes(), private_key_from_str.bytes());
        Ok(())
    }
}