1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
//! CTV Covenant Engine for Payment Proofs
//!
//! Creates and verifies CTV (CheckTemplateVerify) covenants for instant payment proofs.
//! Provides cryptographic commitment to payment structure without requiring instant settlement.
use crate::payment::processor::PaymentError;
use crate::utils::current_timestamp;
use crate::{Hash, Transaction};
#[cfg(feature = "ctv")]
use blvm_protocol::bip119::calculate_template_hash;
use blvm_protocol::payment::PaymentOutput;
pub use blvm_protocol::payment::{
CovenantProof, SettlementStatus, TemplateInput, TemplateOutput, TransactionTemplate,
};
/// CTV Covenant Engine
pub struct CovenantEngine;
impl CovenantEngine {
/// Create a new covenant engine
pub fn new() -> Self {
Self
}
/// Create CTV covenant proof for payment commitment
///
/// Creates a transaction template that commits to specific payment outputs
/// using CTV (CheckTemplateVerify). This provides instant proof of payment
/// intent without requiring instant on-chain settlement.
///
/// # Arguments
///
/// * `payment_request_id` - ID of the payment request
/// * `payment_outputs` - Payment outputs to commit to
/// * `input_prevout` - Previous output to spend (optional, for template)
///
/// # Returns
///
/// CTV covenant proof with template hash and transaction structure
pub fn create_payment_covenant(
&self,
payment_request_id: &str,
payment_outputs: &[PaymentOutput],
input_prevout: Option<(Hash, u32)>,
) -> Result<CovenantProof, PaymentError> {
#[cfg(not(feature = "ctv"))]
{
return Err(PaymentError::FeatureNotEnabled(
"CTV covenant requires --features ctv".to_string(),
));
}
#[cfg(feature = "ctv")]
{
// Create transaction template from payment outputs
let template = self.create_transaction_template(payment_outputs, input_prevout)?;
// Calculate CTV template hash
let template_hash = self.calculate_template_hash_for_template(&template, 0)?;
let created_at = current_timestamp();
Ok(CovenantProof {
template_hash,
transaction_template: template,
payment_request_id: payment_request_id.to_string(),
created_at,
signature: None,
})
}
}
/// Verify CTV covenant proof matches expected outputs
///
/// Verifies that the covenant proof commits to the expected payment outputs.
///
/// # Arguments
///
/// * `proof` - The covenant proof to verify
/// * `expected_outputs` - Expected payment outputs
///
/// # Returns
///
/// `true` if proof matches expected outputs, `false` otherwise
pub fn verify_covenant_proof(
&self,
proof: &CovenantProof,
expected_outputs: &[PaymentOutput],
) -> Result<bool, PaymentError> {
#[cfg(not(feature = "ctv"))]
{
return Err(PaymentError::FeatureNotEnabled(
"CTV covenant requires --features ctv".to_string(),
));
}
#[cfg(feature = "ctv")]
{
// Verify outputs match
if proof.transaction_template.outputs.len() != expected_outputs.len() {
return Ok(false);
}
for (template_output, expected_output) in proof
.transaction_template
.outputs
.iter()
.zip(expected_outputs.iter())
{
if template_output.value != expected_output.amount.unwrap_or(0) {
return Ok(false);
}
if template_output.script_pubkey != expected_output.script {
return Ok(false);
}
}
// Recalculate template hash to verify it matches
let recalculated_hash =
self.calculate_template_hash_for_template(&proof.transaction_template, 0)?;
Ok(recalculated_hash == proof.template_hash)
}
}
/// Verify that a transaction matches the CTV covenant
///
/// Verifies that an actual transaction matches the CTV template hash
/// from the covenant proof.
///
/// # Arguments
///
/// * `tx` - The transaction to verify
/// * `covenant_proof` - The covenant proof to verify against
/// * `input_index` - Index of the input being verified
///
/// # Returns
///
/// `true` if transaction matches covenant, `false` otherwise
pub fn verify_transaction_matches_covenant(
&self,
tx: &Transaction,
covenant_proof: &CovenantProof,
input_index: usize,
) -> Result<bool, PaymentError> {
#[cfg(not(feature = "ctv"))]
{
return Err(PaymentError::FeatureNotEnabled(
"CTV covenant requires --features ctv".to_string(),
));
}
#[cfg(feature = "ctv")]
{
// Transaction type from blvm-node is the same as blvm-consensus (re-exported)
// Calculate template hash for the transaction
let tx_template_hash = calculate_template_hash(tx, input_index).map_err(|e| {
PaymentError::ProcessingError(format!("CTV hash calculation failed: {}", e))
})?;
// Verify template hash matches
Ok(tx_template_hash == covenant_proof.template_hash)
}
}
/// Create transaction template from payment outputs
fn create_transaction_template(
&self,
payment_outputs: &[PaymentOutput],
input_prevout: Option<(Hash, u32)>,
) -> Result<TransactionTemplate, PaymentError> {
// Create template inputs
let inputs = if let Some((prevout_hash, prevout_index)) = input_prevout {
vec![TemplateInput {
prevout_hash,
prevout_index,
sequence: 0xffffffff, // Default sequence
}]
} else {
// Dummy input for template (will be replaced with actual input when creating transaction)
vec![TemplateInput {
prevout_hash: [0u8; 32],
prevout_index: 0,
sequence: 0xffffffff,
}]
};
// Convert payment outputs to template outputs
let outputs: Result<Vec<TemplateOutput>, PaymentError> = payment_outputs
.iter()
.map(|output| {
Ok(TemplateOutput {
value: output.amount.unwrap_or(0),
script_pubkey: output.script.clone(),
})
})
.collect();
Ok(TransactionTemplate {
version: 2, // Standard transaction version
inputs,
outputs: outputs?,
lock_time: 0, // No locktime by default
})
}
/// Calculate template hash for transaction template
#[cfg(feature = "ctv")]
fn calculate_template_hash_for_template(
&self,
template: &TransactionTemplate,
input_index: usize,
) -> Result<Hash, PaymentError> {
// Convert template to actual transaction for hash calculation
// (CTV hash calculation requires a Transaction struct)
let tx = self.template_to_transaction(template)?;
calculate_template_hash(&tx, input_index).map_err(|e| {
PaymentError::ProcessingError(format!("CTV hash calculation failed: {}", e))
})
}
/// Convert transaction template to Transaction (for CTV hash calculation)
#[cfg(feature = "ctv")]
fn template_to_transaction(
&self,
template: &TransactionTemplate,
) -> Result<blvm_protocol::types::Transaction, PaymentError> {
// Convert template inputs to transaction inputs
use blvm_protocol::types::{
ByteString, Integer, Natural, OutPoint, Transaction as ConsensusTransaction,
TransactionInput as ConsensusInput, TransactionOutput as ConsensusOutput,
};
let inputs: Vec<ConsensusInput> = template
.inputs
.iter()
.map(|ti| {
ConsensusInput {
prevout: OutPoint {
hash: ti.prevout_hash,
index: ti.prevout_index,
},
script_sig: ByteString::from(vec![]), // CTV: no scriptSig
sequence: Natural::from(ti.sequence as u64),
}
})
.collect();
// Convert template outputs to transaction outputs
// Integer is i64, so we need to convert u64 to i64 (saturating at i64::MAX)
let outputs: Vec<ConsensusOutput> = template
.outputs
.iter()
.map(|to| ConsensusOutput {
value: Integer::from(to.value.min(i64::MAX as u64) as i64),
script_pubkey: ByteString::from(to.script_pubkey.clone()),
})
.collect();
Ok(ConsensusTransaction {
version: Natural::from(template.version as u64),
inputs: inputs.into(),
outputs: outputs.into(),
lock_time: Natural::from(template.lock_time as u64),
})
}
}
impl Default for CovenantEngine {
fn default() -> Self {
Self::new()
}
}