use crate::Hash;
use blvm_secp256k1::ecdsa::{
ecdsa_sig_parse_compact, ecdsa_sig_verify, ecdsa_sign_compact_rfc6979, ge_from_pubkey_bytes,
ge_to_compressed, pubkey_from_secret, verify_ecdsa_direct,
};
use blvm_secp256k1::scalar::Scalar;
use hex;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
fn sign_compact(hash: &[u8; 32], seckey: &[u8; 32]) -> Result<Vec<u8>, Bip70Error> {
ecdsa_sign_compact_rfc6979(hash, seckey)
.map(|s| s.to_vec())
.ok_or_else(|| Bip70Error::SignatureError("ECDSA signing failed".to_string()))
}
fn verify_sig(sig_bytes: &[u8], pubkey: &[u8], hash: &[u8; 32]) -> Result<(), Bip70Error> {
let valid = if sig_bytes.len() == 64 {
let compact: &[u8; 64] = sig_bytes
.try_into()
.map_err(|_| Bip70Error::SignatureError("Invalid signature length".to_string()))?;
let (sigr, sigs) = ecdsa_sig_parse_compact(compact)
.ok_or_else(|| Bip70Error::SignatureError("Invalid compact signature".to_string()))?;
let pk = ge_from_pubkey_bytes(pubkey)
.ok_or_else(|| Bip70Error::SignatureError("Invalid public key".to_string()))?;
let mut msg = Scalar::zero();
let _ = msg.set_b32(hash);
ecdsa_sig_verify(&sigr, &sigs, &pk, &msg)
} else {
verify_ecdsa_direct(sig_bytes, pubkey, hash, false, false)
.ok_or_else(|| Bip70Error::SignatureError("Signature parse error".to_string()))?
};
if valid {
Ok(())
} else {
Err(Bip70Error::SignatureError(
"Signature verification failed".to_string(),
))
}
}
fn pubkey_bytes_from_secret(seckey: &[u8; 32]) -> Result<[u8; 33], Bip70Error> {
let mut sec = Scalar::zero();
if sec.set_b32(seckey) || sec.is_zero() {
return Err(Bip70Error::SignatureError("Invalid secret key".to_string()));
}
let ge = pubkey_from_secret(&sec);
Ok(ge_to_compressed(&ge))
}
pub const PAYMENT_PROTOCOL_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentDetails {
pub network: String,
pub outputs: Vec<PaymentOutput>,
pub time: u64,
pub expires: Option<u64>,
pub memo: Option<String>,
pub payment_url: Option<String>,
pub merchant_data: Option<Vec<u8>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PaymentOutput {
pub script: Vec<u8>,
pub amount: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignedRefundAddress {
pub address: PaymentOutput,
pub signature: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentRequest {
pub payment_details: PaymentDetails,
pub merchant_pubkey: Option<Vec<u8>>,
pub signature: Option<Vec<u8>>,
pub authorized_refund_addresses: Option<Vec<SignedRefundAddress>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Payment {
pub transactions: Vec<Vec<u8>>,
pub refund_to: Option<Vec<PaymentOutput>>,
pub merchant_data: Option<Vec<u8>>,
pub memo: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentACK {
pub payment: Payment,
pub memo: Option<String>,
pub signature: Option<Vec<u8>>,
}
impl PaymentACK {
pub fn sign(&mut self, private_key: &[u8; 32]) -> Result<(), Bip70Error> {
let mut ack_for_signing = self.clone();
ack_for_signing.signature = None;
let serialized = bincode::serialize(&ack_for_signing)
.map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
let hash: [u8; 32] = Sha256::digest(&serialized).into();
self.signature = Some(sign_compact(&hash, private_key)?);
Ok(())
}
pub fn verify_signature(&self, merchant_pubkey: &[u8]) -> Result<(), Bip70Error> {
let sig = self
.signature
.as_ref()
.ok_or_else(|| Bip70Error::SignatureError("No signature".to_string()))?;
let mut ack = self.clone();
ack.signature = None;
let serialized =
bincode::serialize(&ack).map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
let hash: [u8; 32] = Sha256::digest(&serialized).into();
verify_sig(sig, merchant_pubkey, &hash).map_err(|_| {
Bip70Error::SignatureError("PaymentACK signature verification failed".to_string())
})
}
}
impl PaymentRequest {
pub fn new(network: String, outputs: Vec<PaymentOutput>, time: u64) -> Self {
Self {
payment_details: PaymentDetails {
network,
outputs,
time,
expires: None,
memo: None,
payment_url: None,
merchant_data: None,
},
merchant_pubkey: None,
signature: None,
authorized_refund_addresses: None,
}
}
pub fn with_merchant_key(mut self, pubkey: [u8; 33]) -> Self {
self.merchant_pubkey = Some(pubkey.to_vec());
self
}
pub fn with_authorized_refund(mut self, signed_refund: SignedRefundAddress) -> Self {
if self.authorized_refund_addresses.is_none() {
self.authorized_refund_addresses = Some(Vec::new());
}
self.authorized_refund_addresses
.as_mut()
.unwrap()
.push(signed_refund);
self
}
pub fn with_expires(mut self, expires: u64) -> Self {
self.payment_details.expires = Some(expires);
self
}
pub fn with_memo(mut self, memo: String) -> Self {
self.payment_details.memo = Some(memo);
self
}
pub fn with_payment_url(mut self, url: String) -> Self {
self.payment_details.payment_url = Some(url);
self
}
pub fn with_merchant_data(mut self, data: Vec<u8>) -> Self {
self.payment_details.merchant_data = Some(data);
self
}
pub fn sign(&mut self, private_key: &[u8; 32]) -> Result<(), Bip70Error> {
let serialized = bincode::serialize(&self.payment_details)
.map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
let hash: [u8; 32] = Sha256::digest(&serialized).into();
let pubkey = pubkey_bytes_from_secret(private_key)?;
self.signature = Some(sign_compact(&hash, private_key)?);
self.merchant_pubkey = Some(pubkey.to_vec());
Ok(())
}
pub fn verify_signature(&self) -> Result<(), Bip70Error> {
let pubkey = self
.merchant_pubkey
.as_ref()
.ok_or_else(|| Bip70Error::SignatureError("No merchant public key".to_string()))?;
let sig = self
.signature
.as_ref()
.ok_or_else(|| Bip70Error::SignatureError("No signature".to_string()))?;
let serialized = bincode::serialize(&self.payment_details)
.map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
let hash: [u8; 32] = Sha256::digest(&serialized).into();
verify_sig(sig, pubkey, &hash)
.map_err(|_| Bip70Error::SignatureError("Signature verification failed".to_string()))
}
pub fn validate(&self) -> Result<(), Bip70Error> {
if let Some(expires) = self.payment_details.expires {
let now = crate::time::current_timestamp();
if now > expires {
return Err(Bip70Error::Expired);
}
}
if self.payment_details.outputs.is_empty() {
return Err(Bip70Error::InvalidRequest("No payment outputs".to_string()));
}
let valid_networks = ["main", "test", "regtest"];
if !valid_networks.contains(&self.payment_details.network.as_str()) {
return Err(Bip70Error::InvalidRequest(format!(
"Invalid network: {}",
self.payment_details.network
)));
}
Ok(())
}
}
impl Payment {
pub fn new(transactions: Vec<Vec<u8>>) -> Self {
Self {
transactions,
refund_to: None,
merchant_data: None,
memo: None,
}
}
pub fn with_refund_to(mut self, outputs: Vec<PaymentOutput>) -> Self {
self.refund_to = Some(outputs);
self
}
pub fn validate_refund_addresses(
&self,
authorized_refunds: &[SignedRefundAddress],
) -> Result<(), Bip70Error> {
if let Some(ref refund_to) = self.refund_to {
for refund_addr in refund_to {
let is_authorized = authorized_refunds.iter().any(|auth| {
auth.address.script == refund_addr.script
&& auth.address.amount == refund_addr.amount
});
if !is_authorized {
return Err(Bip70Error::InvalidPayment(format!(
"Refund address not authorized: {:?}",
refund_addr.script
)));
}
}
}
Ok(())
}
pub fn with_merchant_data(mut self, data: Vec<u8>) -> Self {
self.merchant_data = Some(data);
self
}
pub fn with_memo(mut self, memo: String) -> Self {
self.memo = Some(memo);
self
}
pub fn validate(&self) -> Result<(), Bip70Error> {
if self.transactions.is_empty() {
return Err(Bip70Error::InvalidPayment("No transactions".to_string()));
}
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
pub enum Bip70Error {
#[error("Payment request expired")]
Expired,
#[error("Invalid payment request: {0}")]
InvalidRequest(String),
#[error("Invalid payment: {0}")]
InvalidPayment(String),
#[error("Certificate validation failed: {0}")]
CertificateError(String),
#[error("Signature verification failed: {0}")]
SignatureError(String),
#[error("HTTP error: {0}")]
HttpError(String),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Validation error: {0}")]
ValidationError(String),
}
pub struct PaymentProtocolClient;
impl PaymentProtocolClient {
pub fn validate_payment_request(
payment_request: &PaymentRequest,
expected_merchant_pubkey: Option<&[u8]>,
) -> Result<(), Bip70Error> {
payment_request.verify_signature()?;
payment_request.validate()?;
if let Some(expected_pubkey) = expected_merchant_pubkey {
if let Some(ref req_pubkey) = payment_request.merchant_pubkey {
if req_pubkey.as_slice() != expected_pubkey {
return Err(Bip70Error::SignatureError(
"PaymentRequest pubkey mismatch".to_string(),
));
}
}
}
Ok(())
}
pub fn validate_payment_ack(
payment_ack: &PaymentACK,
merchant_signature: &[u8],
merchant_pubkey: &[u8],
) -> Result<(), Bip70Error> {
if let Some(ref sig) = payment_ack.signature {
if !sig.is_empty() {
return payment_ack.verify_signature(merchant_pubkey);
}
}
if !merchant_signature.is_empty() {
let mut ack = payment_ack.clone();
ack.signature = None;
let serialized = bincode::serialize(&ack)
.map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
let hash: [u8; 32] = Sha256::digest(&serialized).into();
verify_sig(merchant_signature, merchant_pubkey, &hash)?;
}
Ok(())
}
}
pub struct PaymentProtocolServer;
impl PaymentProtocolServer {
pub fn create_signed_payment_request(
details: PaymentDetails,
merchant_private_key: &[u8; 32],
authorized_refunds: Option<Vec<SignedRefundAddress>>,
) -> Result<PaymentRequest, Bip70Error> {
let mut payment_request = PaymentRequest {
payment_details: details,
merchant_pubkey: None,
signature: None,
authorized_refund_addresses: authorized_refunds,
};
payment_request.sign(merchant_private_key)?;
Ok(payment_request)
}
pub fn process_payment(
payment: &Payment,
original_request: &PaymentRequest,
merchant_private_key: Option<&[u8; 32]>,
) -> Result<PaymentACK, Bip70Error> {
payment.validate()?;
if let Some(ref authorized_refunds) = original_request.authorized_refund_addresses {
payment.validate_refund_addresses(authorized_refunds)?;
}
if let Some(ref pm_data) = payment.merchant_data {
if let Some(ref req_data) = original_request.payment_details.merchant_data {
if pm_data != req_data {
return Err(Bip70Error::ValidationError(
"Merchant data mismatch".to_string(),
));
}
}
} else if original_request.payment_details.merchant_data.is_some() {
return Err(Bip70Error::ValidationError(
"Payment missing merchant data".to_string(),
));
}
Self::verify_payment_transactions(payment, original_request)?;
let mut payment_ack = PaymentACK {
payment: payment.clone(),
memo: Some("Payment received".to_string()),
signature: None,
};
if let Some(private_key) = merchant_private_key {
payment_ack.sign(private_key)?;
}
Ok(payment_ack)
}
fn verify_payment_transactions(
payment: &Payment,
original_request: &PaymentRequest,
) -> Result<(), Bip70Error> {
use crate::Transaction;
let mut all_outputs = Vec::new();
for tx_bytes in &payment.transactions {
let tx: Transaction = bincode::deserialize(tx_bytes)
.map_err(|e| Bip70Error::SerializationError(format!("Invalid transaction: {e}")))?;
for output in &tx.outputs {
all_outputs.push(PaymentOutput {
script: output.script_pubkey.clone(),
amount: Some(output.value as u64), });
}
}
for requested_output in &original_request.payment_details.outputs {
let found = all_outputs.iter().any(|output| {
output.script == requested_output.script
&& match (output.amount, requested_output.amount) {
(Some(amt), Some(req_amt)) => amt >= req_amt, (Some(_), None) => true, (None, Some(_)) => false, (None, None) => true, }
});
if !found {
return Err(Bip70Error::ValidationError(format!(
"Payment missing required output: script={}, amount={:?}",
hex::encode(&requested_output.script),
requested_output.amount
)));
}
}
Ok(())
}
pub fn sign_refund_address(
address: PaymentOutput,
merchant_private_key: &[u8; 32],
) -> Result<SignedRefundAddress, Bip70Error> {
let serialized = bincode::serialize(&address)
.map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
let hash: [u8; 32] = Sha256::digest(&serialized).into();
Ok(SignedRefundAddress {
address,
signature: sign_compact(&hash, merchant_private_key)?,
})
}
pub fn verify_refund_address(
signed_refund: &SignedRefundAddress,
merchant_pubkey: &[u8],
) -> Result<(), Bip70Error> {
let serialized = bincode::serialize(&signed_refund.address)
.map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
let hash: [u8; 32] = Sha256::digest(&serialized).into();
verify_sig(&signed_refund.signature, merchant_pubkey, &hash).map_err(|_| {
Bip70Error::SignatureError("Refund address signature verification failed".to_string())
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CovenantProof {
pub template_hash: Hash,
pub transaction_template: TransactionTemplate,
pub payment_request_id: String,
pub created_at: u64,
pub signature: Option<Vec<u8>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TransactionTemplate {
pub version: u32,
pub inputs: Vec<TemplateInput>,
pub outputs: Vec<TemplateOutput>,
pub lock_time: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TemplateInput {
pub prevout_hash: Hash,
pub prevout_index: u32,
pub sequence: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TemplateOutput {
pub value: u64,
pub script_pubkey: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SettlementStatus {
ProofCreated,
ProofBroadcast,
InMempool { tx_hash: Hash },
Settled {
tx_hash: Hash,
block_hash: Hash,
confirmation_count: u32,
},
Failed { reason: String },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_payment_request_creation() {
let output = PaymentOutput {
script: vec![blvm_consensus::opcodes::OP_1],
amount: Some(100000), };
let request = PaymentRequest::new("main".to_string(), vec![output], 1234567890);
assert_eq!(request.payment_details.network, "main");
assert_eq!(request.payment_details.outputs.len(), 1);
assert_eq!(request.payment_details.time, 1234567890);
}
#[test]
fn test_payment_request_validation() {
let request = PaymentRequest::new(
"main".to_string(),
vec![PaymentOutput {
script: vec![blvm_consensus::opcodes::OP_1],
amount: Some(100000),
}],
1234567890,
);
assert!(request.validate().is_ok());
}
#[test]
fn test_payment_request_expired() {
let expired_time = 1000;
let request = PaymentRequest::new(
"main".to_string(),
vec![PaymentOutput {
script: vec![blvm_consensus::opcodes::OP_1],
amount: Some(100000),
}],
expired_time,
)
.with_expires(1001);
let result = request.validate();
assert!(result.is_err());
}
#[test]
fn test_payment_creation() {
let tx = vec![0x01, 0x00, 0x00, 0x00]; let payment = Payment::new(vec![tx.clone()]);
assert_eq!(payment.transactions.len(), 1);
assert_eq!(payment.transactions[0], tx);
}
#[test]
fn test_payment_validation() {
let payment = Payment::new(vec![vec![0x01, 0x02, 0x03]]);
assert!(payment.validate().is_ok());
let empty_payment = Payment::new(vec![]);
assert!(empty_payment.validate().is_err());
}
}