emerald-vault 0.36.0

Emerald Vault - Key Storage for Emerald Wallet
Documentation
use std::{
    convert::{TryFrom, TryInto},
    str::FromStr,
};

use bitcoin::{bip32::{ChainCode, ChildNumber, Xpub as Bitcoin_XPub, Fingerprint}, NetworkKind, PublicKey};
use protobuf::ProtobufEnum;

use crate::{
    blockchain::bitcoin::{AddressType, XPub},
    convert::error::ConversionError,
    proto::common::BlockchainId as proto_BlockchainId,
    proto::address::{
        Address as proto_Address,
        AddressType as proto_AddressType,
        Address_oneof_address_type as proto_AddressRefType,
        Bip32Public as proto_Bip32Public,
    },
    structs::book::AddressRef,
    EthereumAddress,
};
use crate::blockchain::chains::Blockchain;

impl From<AddressType> for proto_AddressType {
    fn from(value: AddressType) -> Self {
        match value {
            AddressType::P2PKH => proto_AddressType::BITCOIN_P2PKH,
            AddressType::P2SH => proto_AddressType::BITCOIN_P2SH,
            AddressType::P2WPKHinP2SH => proto_AddressType::BITCOIN_P2WPKH_P2SH,
            AddressType::P2WSHinP2SH => proto_AddressType::BITCOIN_P2WSH_P2SH,
            AddressType::P2WPKH => proto_AddressType::BITCOIN_P2WPKH,
            AddressType::P2WSH => proto_AddressType::BITCOIN_P2WSH,
        }
    }
}

impl From<&XPub> for proto_Bip32Public {
    fn from(xpub: &XPub) -> Self {
        let mut result = proto_Bip32Public::default();
        result.set_level(xpub.value.depth as u32);
        result.set_parent_fingerprint(u32::from_be_bytes(
            *xpub.value.parent_fingerprint.as_bytes(),
        ));
        result.set_child_number(xpub.value.child_number.into());
        result.set_chaincode(xpub.value.chain_code.as_bytes().to_vec());
        result.set_point(xpub.value.public_key.serialize_uncompressed().to_vec());
        result.set_address_type(xpub.address_type.into());
        result.set_network(match xpub.value.network {
            NetworkKind::Main => proto_BlockchainId::CHAIN_BITCOIN,
            NetworkKind::Test => proto_BlockchainId::CHAIN_TESTNET_BITCOIN,
        });
        result
    }
}

impl From<&AddressRef> for proto_Address {
    fn from(value: &AddressRef) -> Self {
        let mut result = proto_Address::default();
        match value {
            AddressRef::EthereumAddress(address) => {
                result.set_plain_address(address.to_string());
            }
            AddressRef::ExtendedPub(xpub) => result.set_xpub(xpub.into()),
            AddressRef::BitcoinAddress(address) => {
                result.set_plain_address(address.to_string())
            }
        }
        result
    }
}

impl TryFrom<proto_AddressType> for AddressType {
    type Error = ConversionError;

    fn try_from(value: proto_AddressType) -> Result<Self, Self::Error> {
        match value {
            proto_AddressType::BITCOIN_P2WPKH => Ok(AddressType::P2WPKH),
            proto_AddressType::BITCOIN_P2WSH => Ok(AddressType::P2WSH),
            proto_AddressType::BITCOIN_P2PKH => Ok(AddressType::P2PKH),
            proto_AddressType::BITCOIN_P2SH => Ok(AddressType::P2SH),
            proto_AddressType::BITCOIN_P2WPKH_P2SH => Ok(AddressType::P2WPKHinP2SH),
            proto_AddressType::BITCOIN_P2WSH_P2SH => Ok(AddressType::P2WSHinP2SH),
            _ => Err(ConversionError::InvalidFieldValue(
                value.value().to_string(),
            )),
        }
    }
}


impl TryFrom<&proto_Bip32Public> for XPub {
    type Error = ConversionError;

    fn try_from(value: &proto_Bip32Public) -> Result<Self, Self::Error> {
        let depth: u8 = if value.level <= 255 {
            value.level as u8
        } else {
            return Err(ConversionError::InvalidFieldValue("level".to_string()));
        };
        let parent_fingerprint = Fingerprint::from(value.parent_fingerprint.to_be_bytes());
        let child_number = ChildNumber::from(value.child_number);
        let chain_code = if value.chaincode.len() == 32 {
            ChainCode::try_from(value.chaincode.as_slice()).unwrap()
        } else {
            return Err(ConversionError::InvalidFieldValue("chain_code".to_string()));
        };
        let public_key = PublicKey::from_slice(value.point.as_slice())
            .map_err(|_| ConversionError::InvalidFieldValue("public_key".to_string()))?
            .inner;
        let address_type = value.address_type.try_into()?;
        let network = Blockchain::try_from(value.network.value() as u32)
            .map_err(|_| ConversionError::InvalidFieldValue("network".to_string()))?
            .as_bitcoin_network_kind();

        Ok(XPub {
            address_type,
            value: Bitcoin_XPub {
                network,
                depth,
                parent_fingerprint,
                child_number,
                public_key,
                chain_code,
            },
        })
    }
}

impl TryFrom<&proto_Address> for Option<AddressRef> {
    type Error = ConversionError;

    fn try_from(value: &proto_Address) -> Result<Self, Self::Error> {
        let result = match &value.address_type {
            Some(address_type) => match address_type {
                proto_AddressRefType::plain_address(a) => Some(AddressRef::EthereumAddress(
                    EthereumAddress::from_str(a.as_str())?,
                )),
                proto_AddressRefType::xpub(xpub) => {
                    Some(AddressRef::ExtendedPub(XPub::try_from(xpub)?))
                }
            },
            None => None,
        };
        Ok(result)
    }
}

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

    use crate::{
        blockchain::bitcoin::XPub,
        proto::address::Address as proto_Address,
        structs::book::AddressRef,
        EthereumAddress,
    };

    #[test]
    fn encode_decode_plain_ethereum() {
        let initial = AddressRef::EthereumAddress(
            EthereumAddress::from_str("0xdac17f958d2ee523a2206206994597c13d831ec7").unwrap(),
        );
        let m: proto_Address = (&initial).into();
        let act: Option<AddressRef> = (&m).try_into().expect("parsed back");
        assert_eq!(act, Some(initial));
    }

    #[test]
    fn encode_decode_xpub() {
        let initial = AddressRef::ExtendedPub(
            XPub::from_str("zpub6tMBbzkLBxnSw8VSGXrnyBSY3r2j4KJRrxrMWm1pskuhbCnKS8R5SuHGjakEvf6efbqsM1NoPMxXZrPmQWTV7ZXZuK9dZcbEzkftLBDJHKj").unwrap()
        );
        let m: proto_Address = (&initial).into();
        let act: Option<AddressRef> = (&m).try_into().expect("parsed back");
        assert_eq!(act, Some(initial));
    }

    #[test]
    fn encode_decode_testnet_xpub() {
        let initial = AddressRef::ExtendedPub(
            XPub::from_str("vpub5Yxb4hoHAGV32y67pPDQCbPFUbB9w95gkR1nCxv92t2axDYWeNV4xzo1wxgz8A1S5QGWusHzCP969uaBbt4hjV8CT3PKe7tfic4v9RMbFc4").unwrap()
        );
        let m: proto_Address = (&initial).into();
        let act: Option<AddressRef> = (&m).try_into().expect("parsed back");
        assert!(act.is_some());
        assert_eq!(act, Some(initial));
    }
}