use crate::ZincError;
use bdk_wallet::bitcoin::hashes::{sha256, Hash};
use bdk_wallet::bitcoin::secp256k1::XOnlyPublicKey;
use bdk_wallet::bitcoin::secp256k1::{schnorr::Signature, Keypair, Message, Secp256k1, SecretKey};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OfferEnvelopeV1 {
pub version: u8,
pub seller_pubkey_hex: String,
pub network: String,
pub inscription_id: String,
pub seller_outpoint: String,
pub ask_sats: u64,
pub fee_rate_sat_vb: u64,
pub psbt_base64: String,
pub created_at_unix: i64,
pub expires_at_unix: i64,
pub nonce: u64,
}
impl OfferEnvelopeV1 {
fn validate(&self) -> Result<(), ZincError> {
if self.version != 1 {
return Err(ZincError::OfferError(format!(
"unsupported offer version {}",
self.version
)));
}
if self.seller_pubkey_hex.is_empty()
|| self.network.is_empty()
|| self.inscription_id.is_empty()
|| self.seller_outpoint.is_empty()
|| self.psbt_base64.is_empty()
{
return Err(ZincError::OfferError(
"offer contains empty required fields".to_string(),
));
}
if self.expires_at_unix <= self.created_at_unix {
return Err(ZincError::OfferError(
"offer expiration must be greater than creation time".to_string(),
));
}
Ok(())
}
pub fn canonical_json(&self) -> Result<Vec<u8>, ZincError> {
self.validate()?;
serde_json::to_vec(self).map_err(|e| ZincError::SerializationError(e.to_string()))
}
pub fn offer_id_digest(&self) -> Result<[u8; 32], ZincError> {
let canonical = self.canonical_json()?;
let digest = sha256::Hash::hash(&canonical);
Ok(digest.to_byte_array())
}
pub fn offer_id_hex(&self) -> Result<String, ZincError> {
let digest = self.offer_id_digest()?;
Ok(digest.iter().map(|b| format!("{b:02x}")).collect())
}
pub fn sign_schnorr_hex(&self, secret_key_hex: &str) -> Result<String, ZincError> {
let secret_key = SecretKey::from_str(secret_key_hex)
.map_err(|e| ZincError::OfferError(format!("invalid secret key: {e}")))?;
let secp = Secp256k1::new();
let keypair = Keypair::from_secret_key(&secp, &secret_key);
let message = Message::from_digest(self.offer_id_digest()?);
let signature = secp.sign_schnorr_no_aux_rand(&message, &keypair);
Ok(signature.to_string())
}
pub fn verify_schnorr_hex(&self, signature_hex: &str) -> Result<(), ZincError> {
let pubkey = XOnlyPublicKey::from_str(&self.seller_pubkey_hex)
.map_err(|e| ZincError::OfferError(format!("invalid seller pubkey: {e}")))?;
let signature = Signature::from_str(signature_hex)
.map_err(|e| ZincError::OfferError(format!("invalid schnorr signature: {e}")))?;
let secp = Secp256k1::verification_only();
let message = Message::from_digest(self.offer_id_digest()?);
secp.verify_schnorr(&signature, &message, &pubkey)
.map_err(|e| ZincError::OfferError(format!("signature verification failed: {e}")))
}
}