Files
darkfi/src/service/btc.rs
2021-09-27 16:16:57 +03:00

326 lines
10 KiB
Rust

use super::bridge::{NetworkClient, TokenNotification, TokenSubscribtion};
use crate::serial::{serialize, Decodable, Encodable};
use crate::{Error, Result};
use async_trait::async_trait;
use bitcoin::blockdata::script::Script;
use bitcoin::hash_types::PubkeyHash as BtcPubKeyHash;
use bitcoin::network::constants::Network;
use bitcoin::util::address::Address;
use bitcoin::util::ecdsa::{PrivateKey as BtcPrivKey, PublicKey as BtcPubKey};
use electrum_client::Client as ElectrumClient;
use log::*;
use secp256k1::key::{PublicKey, SecretKey};
use secp256k1::{rand::rngs::OsRng, Secp256k1};
use async_std::sync::Arc;
use std::str::FromStr;
// Swap out these types for any future non bitcoin-rs types
pub type PubAddress = Address;
pub type PubKey = BtcPubKey;
pub type PrivKey = BtcPrivKey;
const ELECTRUM_SERVER: &str = "ssl://blockstream.info:993";
pub struct BitcoinKeys {
secret_key: SecretKey,
public_key: PublicKey,
_context: Secp256k1<secp256k1::All>,
btc_privkey: BtcPrivKey,
pub btc_pubkey: BtcPubKey,
pub network: Network,
}
impl BitcoinKeys {
pub fn new(network: Network) -> Result<Arc<BitcoinKeys>> {
let secp = Secp256k1::new();
let mut rng = OsRng::new().expect("OsRng");
let (secret_key, public_key) = secp.generate_keypair(&mut rng);
let btc_privkey = BtcPrivKey::new(secret_key, network);
let btc_pubkey = btc_privkey.public_key(&secp);
Ok(Arc::new(BitcoinKeys {
secret_key,
public_key,
_context: secp,
btc_privkey,
btc_pubkey,
network,
}))
}
pub fn pubkey(&self) -> &PublicKey {
&self.public_key
}
pub fn btc_privkey(&self) -> &BtcPrivKey {
&self.btc_privkey
}
//pub async fn start_subscribe(self: Arc<Self>) -> BtcResult<(Txid, u64)> {
// debug!(target: "BTC CLIENT", "Subscribe to scriptpubkey");
// let client = &self.btc_client;
// // Check if script is already subscribed
// if let Some(status_start) = client.script_subscribe(&self.script)? {
// loop {
// match client.script_pop(&self.script)? {
// Some(status) => {
// // Script has a notification update
// if status != status_start {
// let balance = client.script_get_balance(&self.script)?;
// if balance.confirmed > 0 {
// debug!(target: "BTC CLIENT", "BTC Balance: Confirmed!");
// let history = client.script_get_history(&self.script)?;
// //return tx_hash of latest tx that created balance
// return Ok((history[0].tx_hash, balance.confirmed));
// } else {
// debug!(target: "BTC CLIENT", "BTC Balance: Unconfirmed!");
// continue;
// }
// } else {
// debug!(target: "BTC CLIENT", "ScriptPubKey status has not changed");
// continue;
// }
// }
// None => {
// debug!(target: "BTC CLIENT", "Scriptpubkey does not yet exist in script notifications!");
// continue;
// }
// };
// } // Endloop
// } else {
// return Err(BtcFailed::ElectrumError(
// "Did not subscribe to scriptpubkey".to_string(),
// ));
// }
//}
pub fn btc_pubkey(&self) -> &BtcPubKey {
&self.btc_pubkey
}
pub fn btc_pubkey_hash(&self) -> BtcPubKeyHash {
self.btc_pubkey.pubkey_hash()
}
pub fn derive_btc_address(btc_pubkey: BtcPubKey, network: Network) -> Address {
Address::p2pkh(&btc_pubkey, network)
}
pub fn derive_script(btc_pubkey_hash: BtcPubKeyHash) -> Script {
Script::new_p2pkh(&btc_pubkey_hash)
}
}
pub struct BtcClient {
client: Arc<ElectrumClient>,
network: Network,
keypair: BitcoinKeys,
}
impl BtcClient {
pub fn new(network: &str, keypair: BitcoinKeys) -> Result<Arc<Self>> {
let (network, url) = match network {
"mainnet" => (Network::Bitcoin, "https://api.testnet.solana.com"),
"testnet" => (Network::Testnet, "wss://api.testnet.solana.com"),
_ => return Err(Error::NotSupportedNetwork),
};
let electrum_client = ElectrumClient::new(&url)
.map_err(|err| crate::Error::from(super::BtcFailed::from(err)))?;
Ok(Arc::new(Self {
client: Arc::new(electrum_client),
network,
keypair,
}))
}
async fn handle_subscribe_request(self: Arc<Self>, keypair: Arc<BitcoinKeys>) -> Result<()> {
debug!(
target: "BTC BRIDGE",
"Handle subscribe request"
);
// p2pkh script
let _script = BitcoinKeys::derive_script(keypair.btc_pubkey_hash());
//if self.client.script_subscribe(&script)?
//let keypair = serialize(&keypair);
Ok(())
}
}
#[async_trait]
impl NetworkClient for BtcClient {
async fn subscribe(self: Arc<Self>) -> Result<TokenSubscribtion> {
// Generate bitcoin keys
let btc_keys = BitcoinKeys::new(self.network)?;
let btc_privkey = btc_keys.clone();
let btc_privkey = btc_privkey.btc_privkey();
let btc_pubkey = btc_keys.clone();
let btc_pubkey = btc_pubkey.btc_pubkey();
// start scheduler for checking balance
debug!(target: "BRIDGE BITCOIN", "Subscribing for deposit");
//let (_txid, _balance) = btc_keys.start_subscribe().await?;
let self2 = self.clone();
smol::spawn(self2.handle_subscribe_request(btc_keys)).detach();
Ok(TokenSubscribtion {
secret_key: serialize(&btc_privkey.to_bytes()),
public_key: btc_pubkey.to_string(),
})
}
async fn subscribe_with_keypair(
self: Arc<Self>,
_private_key: Vec<u8>,
_public_key: Vec<u8>,
) -> Result<String> {
// TODO this not implemented yet
Ok(String::new())
}
async fn get_notifier(self: Arc<Self>) -> Result<async_channel::Receiver<TokenNotification>> {
// TODO this not implemented yet
let (_, notifier) = async_channel::unbounded();
Ok(notifier)
}
async fn send(self: Arc<Self>, _address: Vec<u8>, _amount: u64) -> Result<()> {
// TODO this not implemented yet
Ok(())
}
}
impl Encodable for bitcoin::Address {
fn encode<S: std::io::Write>(&self, s: S) -> Result<usize> {
let addr = self.to_string();
let len = addr.encode(s)?;
Ok(len)
}
}
impl Decodable for bitcoin::Address {
fn decode<D: std::io::Read>(mut d: D) -> Result<Self> {
let addr: String = Decodable::decode(&mut d)?;
let addr = bitcoin::Address::from_str(&addr)
.map_err(|err| crate::Error::from(BtcFailed::from(err)))?;
Ok(addr)
}
}
impl Encodable for bitcoin::PublicKey {
fn encode<S: std::io::Write>(&self, s: S) -> Result<usize> {
let key = self.to_bytes();
let len = key.encode(s)?;
Ok(len)
}
}
impl Decodable for bitcoin::PublicKey {
fn decode<D: std::io::Read>(mut d: D) -> Result<Self> {
let key: Vec<u8> = Decodable::decode(&mut d)?;
let key = bitcoin::PublicKey::from_slice(&key)
.map_err(|err| crate::Error::from(BtcFailed::from(err)))?;
Ok(key)
}
}
impl Encodable for bitcoin::PrivateKey {
fn encode<S: std::io::Write>(&self, s: S) -> Result<usize> {
let key: String = self.to_string();
let len = key.encode(s)?;
Ok(len)
}
}
impl Decodable for bitcoin::PrivateKey {
fn decode<D: std::io::Read>(mut d: D) -> Result<Self> {
let key: String = Decodable::decode(&mut d)?;
let key = bitcoin::PrivateKey::from_str(&key)
.map_err(|err| crate::Error::from(BtcFailed::from(err)))?;
Ok(key)
}
}
#[derive(Debug)]
pub enum BtcFailed {
NotEnoughValue(u64),
BadBtcAddress(String),
ElectrumError(String),
BtcError(String),
DecodeAndEncodeError(String),
}
impl std::error::Error for BtcFailed {}
impl std::fmt::Display for BtcFailed {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
BtcFailed::NotEnoughValue(i) => {
write!(f, "There is no enough value {}", i)
}
BtcFailed::BadBtcAddress(ref err) => {
write!(f, "Unable to create Electrum Client: {}", err)
}
BtcFailed::ElectrumError(ref err) => write!(f, "could not parse BTC address: {}", err),
BtcFailed::DecodeAndEncodeError(ref err) => {
write!(f, "Decode and decode keys error: {}", err)
}
BtcFailed::BtcError(i) => {
write!(f, "BtcFailed: {}", i)
}
}
}
}
impl From<crate::error::Error> for BtcFailed {
fn from(err: crate::error::Error) -> BtcFailed {
BtcFailed::BtcError(err.to_string())
}
}
impl From<bitcoin::util::address::Error> for BtcFailed {
fn from(err: bitcoin::util::address::Error) -> BtcFailed {
BtcFailed::BadBtcAddress(err.to_string())
}
}
impl From<electrum_client::Error> for BtcFailed {
fn from(err: electrum_client::Error) -> BtcFailed {
BtcFailed::ElectrumError(err.to_string())
}
}
impl From<bitcoin::util::key::Error> for BtcFailed {
fn from(err: bitcoin::util::key::Error) -> BtcFailed {
BtcFailed::DecodeAndEncodeError(err.to_string())
}
}
pub type BtcResult<T> = std::result::Result<T, BtcFailed>;
#[cfg(test)]
mod tests {
use crate::serial::{deserialize, serialize};
use std::str::FromStr;
#[test]
pub fn test_serialize_btc_address() -> super::BtcResult<()> {
let btc_addr =
bitcoin::Address::from_str(&String::from("mxVFsFW5N4mu1HPkxPttorvocvzeZ7KZyk"))?;
let btc_ser = serialize(&btc_addr);
let btc_dser = deserialize(&btc_ser)?;
assert_eq!(btc_addr, btc_dser);
Ok(())
}
}