use std::error::Error;
use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use chrono::Utc;
use uuid::Uuid;
use tokio::sync::Mutex;
use crate::security::hsm::provider::{
HsmProvider, KeyGenParams, PublicKeyInfo, KeyInfo, KeyType,
KeyUsage, SigningAlgorithm, EncryptionAlgorithm, EcCurve
};
use crate::security::hsm::error::HsmError;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct BitcoinHsmConfig {
pub base_provider: Arc<dyn HsmProvider>,
pub network: BitcoinNetwork,
pub derivation_path_template: String,
pub use_taproot: bool,
pub miniscript_policy_template: Option<String>,
pub default_key_type: BitcoinKeyType,
}
impl Default for BitcoinHsmConfig {
fn default() -> Self {
Self {
base_provider: Arc::new(NoopHsmProvider {}),
network: BitcoinNetwork::Testnet,
derivation_path_template: "m/86'/0'/0'/0/{}".to_string(),
use_taproot: true,
miniscript_policy_template: Some("and(pk(@0),or(pk(@1),after(144)))".to_string()),
default_key_type: BitcoinKeyType::Taproot,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum BitcoinNetwork {
Mainnet,
Testnet,
Signet,
Regtest,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum BitcoinKeyType {
Legacy,
SegwitNested,
SegwitNative,
Taproot,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitcoinSignatureType {
Ecdsa,
Schnorr,
}
struct NoopHsmProvider {}
#[async_trait]
impl HsmProvider for NoopHsmProvider {
async fn initialize(&mut self) -> Result<(), HsmError> {
Err(HsmError::UnsupportedOperation("Not implemented".to_string()))
}
async fn generate_key_pair(&self, __params: KeyGenParams) -> Result<PublicKeyInfo, HsmError> {
Err(HsmError::UnsupportedOperation("Not implemented".to_string()))
}
async fn sign(&self, __key_id: &str, __algorithm: SigningAlgorithm, __data: &[u8]) -> Result<Vec<u8>, HsmError> {
Err(HsmError::UnsupportedOperation("Not implemented".to_string()))
}
async fn verify(&self, __key_id: &str, __algorithm: SigningAlgorithm, __data: &[u8], __signature: &[u8]) -> Result<bool, HsmError> {
Err(HsmError::UnsupportedOperation("Not implemented".to_string()))
}
async fn encrypt(&self, __key_id: &str, _algorithm: EncryptionAlgorithm, __data: &[u8], _iv: Option<&[u8]>) -> Result<Vec<u8>, HsmError> {
Err(HsmError::UnsupportedOperation("Not implemented".to_string()))
}
async fn decrypt(&self, __key_id: &str, _algorithm: EncryptionAlgorithm, __data: &[u8], _iv: Option<&[u8]>) -> Result<Vec<u8>, HsmError> {
Err(HsmError::UnsupportedOperation("Not implemented".to_string()))
}
async fn get_key_info(&self, __key_id: &str) -> Result<KeyInfo, HsmError> {
Err(HsmError::UnsupportedOperation("Not implemented".to_string()))
}
async fn list_keys(&self) -> Result<Vec<KeyInfo>, HsmError> {
Err(HsmError::UnsupportedOperation("Not implemented".to_string()))
}
async fn delete_key(&self, __key_id: &str) -> Result<(), HsmError> {
Err(HsmError::UnsupportedOperation("Not implemented".to_string()))
}
async fn close(&self) -> Result<(), HsmError> {
Err(HsmError::UnsupportedOperation("Not implemented".to_string()))
}
}
pub struct BitcoinHsmProvider {
config: BitcoinHsmConfig,
derivation_paths: Mutex<HashMap<String, String>>,
script_details: Mutex<HashMap<String, BitcoinScriptDetails>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct BitcoinScriptDetails {
pub script_type: BitcoinScriptType,
pub script_hex: String,
pub address: String,
pub miniscript_policy: Option<String>,
pub taproot_output_key: Option<String>,
pub taproot_internal_key: Option<String>,
pub taproot_merkle_root: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum BitcoinScriptType {
P2PKH,
P2SH,
P2WPKH,
P2WSH,
P2TR,
Custom(String),
}
impl BitcoinHsmProvider {
pub fn new(config: BitcoinHsmConfig) -> Self {
Self {
config,
derivation_paths: Mutex::new(HashMap::new()),
script_details: Mutex::new(HashMap::new()),
}
}
pub async fn generate_bitcoin_key(
&self,
key_purpose: &str,
bitcoin_key_type: Option<BitcoinKeyType>,
derivation_index: Option<u32>,
) -> Result<BitcoinKeyInfo, HsmError> {
let key_type = bitcoin_key_type.unwrap_or(self.config.default_key_type);
let ec_curve = EcCurve::Secp256k1;
let key_id = Uuid::new_v4().to_string();
let key_label = format!("bitcoin-{}-{}", key_purpose, key_id);
let params = KeyGenParams {
id: Some(key_id.clone()),
label: key_label,
key_type: KeyType::Ec { curve: ec_curve },
extractable: false, usages: vec![KeyUsage::Sign, KeyUsage::Verify],
expires_at: None, attributes: HashMap::new(),
};
let public_key_info = self.config.base_provider.generate_key_pair(params).await?;
let derivation_path = match derivation_index {
Some(index) => self.config.derivation_path_template.replace("{}", &index.to_string()),
None => self.config.derivation_path_template.replace("{}", "0"),
};
{
let mut derivation_paths = self.derivation_paths.lock().await;
derivation_paths.insert(key_id.clone(), derivation_path.clone());
}
let script_details = self.create_script_details(key_type, &public_key_info, key_purpose).await?;
{
let mut scripts = self.script_details.lock().await;
scripts.insert(key_id.clone(), script_details.clone());
}
let bitcoin_key_info = BitcoinKeyInfo {
key_id: key_id.clone(),
public_key_info,
key_type,
derivation_path,
network: self.config.network,
script_details,
created_at: Utc::now(),
};
Ok(bitcoin_key_info)
}
async fn create_script_details(
&self,
key_type: BitcoinKeyType,
public_key_info: &PublicKeyInfo,
key_purpose: &str,
) -> Result<BitcoinScriptDetails, HsmError> {
let (script_type, address, script_hex) = match key_type {
BitcoinKeyType::Legacy => {
let script_type = BitcoinScriptType::P2PKH;
let address = format!("1Example{}", key_purpose.chars().next().unwrap_or('X'));
let script_hex = "76a914...88ac".to_string(); (script_type, address, script_hex)
},
BitcoinKeyType::SegwitNested => {
let script_type = BitcoinScriptType::P2SH;
let address = format!("3Example{}", key_purpose.chars().next().unwrap_or('X'));
let script_hex = "a914...87".to_string(); (script_type, address, script_hex)
},
BitcoinKeyType::SegwitNative => {
let script_type = BitcoinScriptType::P2WPKH;
let address = format!("bc1q{}", key_purpose.chars().next().unwrap_or('x'));
let script_hex = "0014...".to_string(); (script_type, address, script_hex)
},
BitcoinKeyType::Taproot => {
let script_type = BitcoinScriptType::P2TR;
let address = format!("bc1p{}", key_purpose.chars().next().unwrap_or('x'));
let script_hex = "5120...".to_string(); (script_type, address, script_hex)
},
};
let (taproot_output_key, taproot_internal_key, taproot_merkle_root, miniscript_policy) =
if key_type == BitcoinKeyType::Taproot {
(
Some(hex::encode(&public_key_info.public_key)),
Some(format!("internal_{}", hex::encode(&public_key_info.public_key[0..4]))),
Some("merkle_root_placeholder".to_string()),
self.config.miniscript_policy_template.clone(),
)
} else {
(None, None, None, None)
};
Ok(BitcoinScriptDetails {
script_type,
script_hex,
address,
miniscript_policy,
taproot_output_key,
taproot_internal_key,
taproot_merkle_root,
})
}
pub async fn sign_bitcoin_transaction(
&self,
_key_id: &str,
tx_hex: &str,
signature_type: BitcoinSignatureType,
sighash_type: u8,
) -> Result<Vec<u8>, HsmError> {
let key_info = self.config.base_provider.get_key_info(key_id).await?;
let script_details = {
let scripts = self.script_details.lock().await;
match scripts.get(key_id) {
Some(script) => script.clone(),
None => return Err(HsmError::InvalidParameterss(format!("No script details found for key {}", key_id))),
}
};
let algorithm = match signature_type {
BitcoinSignatureType::Ecdsa => SigningAlgorithm::EcdsaSha256,
BitcoinSignatureType::Schnorr => {
if script_details.script_type != BitcoinScriptType::P2TR {
return Err(HsmError::InvalidParameterss(
"Schnorr signatures can only be used with Taproot keys".to_string()
));
}
SigningAlgorithm::Ed25519 },
};
let sighash_placeholder = format!("{}_{}", tx_hex, sighash_type);
let signature = self.config.base_provider.sign(
key_id,
algorithm,
sighash_placeholder.as_bytes(),
).await?;
let mut signature_with_sighash = signature.clone();
signature_with_sighash.push(sighash_type);
Ok(signature_with_sighash)
}
pub async fn create_taproot_output(
&self,
internal__key_id: &str,
script_tree: Option<TaprootScriptTree>,
) -> Result<TaprootOutputInfo, HsmError> {
let internal_key_info = self.config.base_provider.get_key_info(internal_key_id).await?;
if let KeyType::Ec { curve } = internal_key_info.key_type {
if curve != EcCurve::Secp256k1 {
return Err(HsmError::InvalidParameterss(
"Taproot internal key must be secp256k1".to_string()
));
}
} else {
return Err(HsmError::InvalidParameterss(
"Taproot internal key must be EC key".to_string()
));
}
let output_key_id = Uuid::new_v4().to_string();
let output_key_bytes = vec![0xDE, 0xAD, 0xBE, 0xEF];
let script_details = BitcoinScriptDetails {
script_type: BitcoinScriptType::P2TR,
script_hex: "5120...".to_string(), address: format!("bc1p{}", hex::encode(&output_key_bytes[0..4])),
miniscript_policy: None,
taproot_output_key: Some(hex::encode(&output_key_bytes)),
taproot_internal_key: Some(internal_key_id.to_string()),
taproot_merkle_root: script_tree.as_ref().map(|_| "merkle_root_placeholder".to_string()),
};
{
let mut scripts = self.script_details.lock().await;
scripts.insert(output_key_id.clone(), script_details.clone());
}
Ok(TaprootOutputInfo {
output_key_id,
output_key: output_key_bytes,
output_script: "5120...".to_string(), address: script_details.address,
script_details,
})
}
pub async fn verify_bitcoin_spv_proof(&self, proof: BitcoinSpvProof) -> Result<bool, HsmError> {
Ok(true)
}
pub async fn get_bitcoin_key_info(&self, _key_id: &str) -> Result<BitcoinKeyInfo, HsmError> {
let public_key_info = match self.config.base_provider.get_key_info(key_id).await {
Ok(key_info) => PublicKeyInfo {
id: key_info.id,
label: key_info.label,
key_type: key_info.key_type,
public_key: vec![], usages: key_info.usages,
created_at: key_info.created_at,
expires_at: key_info.expires_at,
attributes: key_info.attributes,
},
Err(err) => return Err(err),
};
let derivation_path = {
let derivation_paths = self.derivation_paths.lock().await;
match derivation_paths.get(key_id) {
Some(path) => path.clone(),
None => "unknown".to_string(),
}
};
let script_details = {
let scripts = self.script_details.lock().await;
match scripts.get(key_id) {
Some(script) => script.clone(),
None => {
BitcoinScriptDetails {
script_type: BitcoinScriptType::Custom("unknown".to_string()),
script_hex: "unknown".to_string(),
address: "unknown".to_string(),
miniscript_policy: None,
taproot_output_key: None,
taproot_internal_key: None,
taproot_merkle_root: None,
}
},
}
};
let key_type = match script_details.script_type {
BitcoinScriptType::P2PKH => BitcoinKeyType::Legacy,
BitcoinScriptType::P2SH => BitcoinKeyType::SegwitNested,
BitcoinScriptType::P2WPKH | BitcoinScriptType::P2WSH => BitcoinKeyType::SegwitNative,
BitcoinScriptType::P2TR => BitcoinKeyType::Taproot,
BitcoinScriptType::Custom(_) => BitcoinKeyType::Legacy, };
Ok(BitcoinKeyInfo {
key_id: key_id.to_string(),
public_key_info,
key_type,
derivation_path,
network: self.config.network,
script_details,
created_at: Utc::now(), })
}
pub async fn sign_bitcoin_message(
&self,
_key_id: &str,
message: &str,
use_standard_format: bool,
) -> Result<String, HsmError> {
let key_info = self.config.base_provider.get_key_info(key_id).await?;
let script_details = {
let scripts = self.script_details.lock().await;
match scripts.get(key_id) {
Some(script) => script.clone(),
None => return Err(HsmError::InvalidParameterss(format!("No script details found for key {}", key_id))),
}
};
let algorithm = match script_details.script_type {
BitcoinScriptType::P2TR => SigningAlgorithm::Ed25519, _ => SigningAlgorithm::EcdsaSha256,
};
let message_to_sign = if use_standard_format {
let prefix = "\x18Bitcoin Signed Message:\n";
let msg_len = message.len();
let mut formatted = prefix.as_bytes().to_vec();
formatted.push(msg_len as u8); formatted.extend_from_slice(message.as_bytes());
formatted
} else {
message.as_bytes().to_vec()
};
let signature = self.config.base_provider.sign(
key_id,
algorithm,
&message_to_sign,
).await?;
Ok(base64::encode(signature))
}
}
#[derive(Debug, Clone)]
pub struct TaprootScriptTree {
pub root: TaprootScriptNode,
}
#[derive(Debug, Clone)]
pub enum TaprootScriptNode {
Leaf {
script: String,
version: u8,
},
Branch {
left: Box<TaprootScriptNode>,
right: Box<TaprootScriptNode>,
},
}
#[derive(Debug, Clone)]
pub struct BitcoinSpvProof {
pub tx_hash: String,
pub block_header: String,
pub merkle_proof: Vec<String>,
pub block_height: u32,
pub confirmations: u32,
}
#[derive(Debug, Clone)]
pub struct TaprootOutputInfo {
pub output_key_id: String,
pub output_key: Vec<u8>,
pub output_script: String,
pub address: String,
pub script_details: BitcoinScriptDetails,
}
#[derive(Debug, Clone)]
pub struct BitcoinKeyInfo {
pub key_id: String,
pub public_key_info: PublicKeyInfo,
pub key_type: BitcoinKeyType,
pub derivation_path: String,
pub network: BitcoinNetwork,
pub script_details: BitcoinScriptDetails,
pub created_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone)]
pub struct DlcParams {
pub oracle_public_keys: Vec<String>,
pub oracle_r_points: Vec<String>,
pub contract_info: DlcContractInfo,
pub cets: Vec<DlcCetInfo>,
}
#[derive(Debug, Clone)]
pub struct DlcContractInfo {
pub descriptor: String,
pub outcomes: Vec<DlcOutcome>,
pub maturity_time: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone)]
pub struct DlcOutcome {
pub value: String,
pub payout_a: u64,
pub payout_b: u64,
}
#[derive(Debug, Clone)]
pub struct DlcCetInfo {
pub outcome_index: usize,
pub tx_hex: String,
pub adaptor_sig_a: Option<String>,
pub adaptor_sig_b: Option<String>,
}
pub async fn create_dlc(
hsm_provider: &BitcoinHsmProvider,
funding__key_id: &str,
dlc_params: DlcParams,
) -> Result<DlcInfo, HsmError> {
let dlc_id = Uuid::new_v4().to_string();
Ok(DlcInfo {
dlc_id,
funding_key_id: funding_key_id.to_string(),
contract_id: Uuid::new_v4().to_string(),
funding_tx_id: format!("{}_{}", dlc_id, "funding"),
refund_tx_id: format!("{}_{}", dlc_id, "refund"),
cet_tx_ids: dlc_params.cets.iter().enumerate()
.map(|(i, _)| format!("{}_{}", dlc_id, i))
.collect(),
status: DlcStatus::Created,
creation_time: Utc::now(),
})
}
#[derive(Debug, Clone)]
pub struct DlcInfo {
pub dlc_id: String,
pub funding_key_id: String,
pub contract_id: String,
pub funding_tx_id: String,
pub refund_tx_id: String,
pub cet_tx_ids: Vec<String>,
pub status: DlcStatus,
pub creation_time: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DlcStatus {
Created,
Funded,
Executed,
Refunded,
Canceled,
}