use hex;
use secp256k1::ecdsa::Signature;
use secp256k1::{Message, Secp256k1};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
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: &secp256k1::SecretKey) -> 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 mut hasher = Sha256::new();
hasher.update(&serialized);
let hash = hasher.finalize();
let message = Message::from_digest(hash.into());
let secp = Secp256k1::new();
let signature = secp.sign_ecdsa(&message, private_key);
self.signature = Some(signature.serialize_compact().to_vec());
Ok(())
}
pub fn verify_signature(&self, merchant_pubkey: &[u8]) -> Result<(), Bip70Error> {
let signature_bytes = self
.signature
.as_ref()
.ok_or_else(|| Bip70Error::SignatureError("No signature".to_string()))?;
let pubkey = secp256k1::PublicKey::from_slice(merchant_pubkey)
.map_err(|e| Bip70Error::SignatureError(format!("Invalid public key: {e}")))?;
let signature = Signature::from_compact(signature_bytes)
.map_err(|e| Bip70Error::SignatureError(format!("Invalid signature: {e}")))?;
let mut ack_for_verification = self.clone();
ack_for_verification.signature = None;
let serialized = bincode::serialize(&ack_for_verification)
.map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
let mut hasher = Sha256::new();
hasher.update(&serialized);
let hash = hasher.finalize();
let message = Message::from_digest(hash.into());
let secp = Secp256k1::new();
secp.verify_ecdsa(&message, &signature, &pubkey)
.map_err(|_| {
Bip70Error::SignatureError("PaymentACK signature verification failed".to_string())
})?;
Ok(())
}
}
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: &secp256k1::SecretKey) -> Result<(), Bip70Error> {
let serialized = bincode::serialize(&self.payment_details)
.map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
let mut hasher = Sha256::new();
hasher.update(&serialized);
let hash = hasher.finalize();
let message = Message::from_digest(hash.into());
let secp = Secp256k1::new();
let signature = secp.sign_ecdsa(&message, private_key);
let pubkey = secp256k1::PublicKey::from_secret_key(&secp, private_key);
let pubkey_serialized = pubkey.serialize();
self.signature = Some(signature.serialize_compact().to_vec());
self.merchant_pubkey = Some(pubkey_serialized.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 signature_bytes = self
.signature
.as_ref()
.ok_or_else(|| Bip70Error::SignatureError("No signature".to_string()))?;
let pubkey = secp256k1::PublicKey::from_slice(pubkey)
.map_err(|e| Bip70Error::SignatureError(format!("Invalid public key: {e}")))?;
let signature = Signature::from_compact(signature_bytes)
.map_err(|e| Bip70Error::SignatureError(format!("Invalid signature: {e}")))?;
let serialized = bincode::serialize(&self.payment_details)
.map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
let mut hasher = Sha256::new();
hasher.update(&serialized);
let hash = hasher.finalize();
let message = Message::from_digest(hash.into());
let secp = Secp256k1::new();
secp.verify_ecdsa(&message, &signature, &pubkey)
.map_err(|_| Bip70Error::SignatureError("Signature verification failed".to_string()))?;
Ok(())
}
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 pubkey = secp256k1::PublicKey::from_slice(merchant_pubkey)
.map_err(|e| Bip70Error::SignatureError(format!("Invalid pubkey: {e}")))?;
let mut ack_for_verification = payment_ack.clone();
ack_for_verification.signature = None;
let serialized = bincode::serialize(&ack_for_verification)
.map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
let mut hasher = Sha256::new();
hasher.update(&serialized);
let hash = hasher.finalize();
let message = Message::from_digest(hash.into());
let signature = Signature::from_compact(merchant_signature)
.map_err(|e| Bip70Error::SignatureError(format!("Invalid signature: {e}")))?;
let secp = Secp256k1::new();
secp.verify_ecdsa(&message, &signature, &pubkey)
.map_err(|_| {
Bip70Error::SignatureError(
"PaymentACK signature verification failed".to_string(),
)
})?;
}
Ok(())
}
}
pub struct PaymentProtocolServer;
impl PaymentProtocolServer {
pub fn create_signed_payment_request(
details: PaymentDetails,
merchant_private_key: &secp256k1::SecretKey,
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<&secp256k1::SecretKey>,
) -> 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 payment_merchant_data) = payment.merchant_data {
if let Some(ref request_merchant_data) = original_request.payment_details.merchant_data
{
if payment_merchant_data != request_merchant_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: &secp256k1::SecretKey,
) -> Result<SignedRefundAddress, Bip70Error> {
let serialized = bincode::serialize(&address)
.map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
let mut hasher = Sha256::new();
hasher.update(&serialized);
let hash = hasher.finalize();
let message = Message::from_digest(hash.into());
let secp = Secp256k1::new();
let signature = secp.sign_ecdsa(&message, merchant_private_key);
Ok(SignedRefundAddress {
address,
signature: signature.serialize_compact().to_vec(),
})
}
pub fn verify_refund_address(
signed_refund: &SignedRefundAddress,
merchant_pubkey: &[u8],
) -> Result<(), Bip70Error> {
let pubkey = secp256k1::PublicKey::from_slice(merchant_pubkey)
.map_err(|e| Bip70Error::SignatureError(format!("Invalid pubkey: {e}")))?;
let serialized = bincode::serialize(&signed_refund.address)
.map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
let mut hasher = Sha256::new();
hasher.update(&serialized);
let hash = hasher.finalize();
let message = Message::from_digest(hash.into());
let signature = Signature::from_compact(&signed_refund.signature)
.map_err(|e| Bip70Error::SignatureError(format!("Invalid signature: {e}")))?;
let secp = Secp256k1::new();
secp.verify_ecdsa(&message, &signature, &pubkey)
.map_err(|_| {
Bip70Error::SignatureError(
"Refund address signature verification failed".to_string(),
)
})?;
Ok(())
}
}
#[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());
}
}