1#[cfg(test)]
33use super::script::ReplacementDepositScript;
34use super::script::SpendPath;
35use super::script::{CheckSig, Multisig, SpendableScript};
36use crate::builder::address::calculate_taproot_leaf_depths;
37use crate::builder::script::OtherSpendable;
38use crate::builder::transaction::challenge::*;
39use crate::builder::transaction::input::SpendableTxIn;
40use crate::builder::transaction::operator_assert::*;
41use crate::builder::transaction::operator_collateral::*;
42use crate::builder::transaction::operator_reimburse::*;
43use crate::builder::transaction::output::UnspentTxOut;
44use crate::config::protocol::ProtocolParamset;
45use crate::constants::{NON_EPHEMERAL_ANCHOR_AMOUNT, NON_STANDARD_V3};
46use crate::deposit::DepositData;
47#[cfg(test)]
48use crate::deposit::SecurityCouncil;
49use crate::rpc::clementine::grpc_transaction_id;
50use crate::rpc::clementine::GrpcTransactionId;
51use crate::rpc::clementine::{
52 NormalSignatureKind, NormalTransactionId, NumberedTransactionId, NumberedTransactionType,
53};
54use bitcoin::hashes::Hash;
55use bitcoin::opcodes::all::OP_RETURN;
56use bitcoin::script::Builder;
57use bitcoin::transaction::Version;
58use bitcoin::{Address, Amount, ScriptBuf, TxOut};
59#[cfg(test)]
60use bitcoin::{OutPoint, Txid, XOnlyPublicKey};
61use clementine_errors::BridgeError;
62use clementine_primitives::{RoundIndex, TransactionType};
63use input::UtxoVout;
64use std::sync::Arc;
65
66pub use crate::builder::transaction::txhandler::*;
68pub use creator::{
69 create_round_txhandlers, create_txhandlers, ContractContext, KickoffWinternitzKeys,
70 ReimburseDbCache, TxHandlerCache,
71};
72pub use operator_collateral::{
73 create_burn_unused_kickoff_connectors_txhandler, create_round_nth_txhandler,
74};
75pub use operator_reimburse::{create_optimistic_payout_txhandler, create_payout_txhandler};
76pub use txhandler::Unsigned;
77
78pub mod challenge;
79mod creator;
80pub mod deposit_signature_owner;
81pub mod input;
82mod operator_assert;
83mod operator_collateral;
84mod operator_reimburse;
85pub mod output;
86pub mod sign;
87mod txhandler;
88
89type HiddenNode<'a> = &'a [u8; 32];
90
91impl TryFrom<GrpcTransactionId> for TransactionType {
93 type Error = ::prost::UnknownEnumValue;
94 fn try_from(value: GrpcTransactionId) -> Result<Self, Self::Error> {
95 use NormalTransactionId as Normal;
96 use NumberedTransactionType as Numbered;
97 let inner_id = value.id.ok_or(::prost::UnknownEnumValue(0))?;
99 match inner_id {
100 grpc_transaction_id::Id::NormalTransaction(idx) => {
101 let tx_type = NormalTransactionId::try_from(idx)?;
102 match tx_type {
103 Normal::Round => Ok(Self::Round),
104 Normal::Kickoff => Ok(Self::Kickoff),
105 Normal::MoveToVault => Ok(Self::MoveToVault),
106 Normal::Payout => Ok(Self::Payout),
107 Normal::Challenge => Ok(Self::Challenge),
108 Normal::Disprove => Ok(Self::Disprove),
109 Normal::DisproveTimeout => Ok(Self::DisproveTimeout),
110 Normal::Reimburse => Ok(Self::Reimburse),
111 Normal::AllNeededForDeposit => Ok(Self::AllNeededForDeposit),
112 Normal::Dummy => Ok(Self::Dummy),
113 Normal::ReadyToReimburse => Ok(Self::ReadyToReimburse),
114 Normal::KickoffNotFinalized => Ok(Self::KickoffNotFinalized),
115 Normal::ChallengeTimeout => Ok(Self::ChallengeTimeout),
116 Normal::UnspecifiedTransactionType => Err(::prost::UnknownEnumValue(idx)),
117 Normal::BurnUnusedKickoffConnectors => Ok(Self::BurnUnusedKickoffConnectors),
118 Normal::YieldKickoffTxid => Ok(Self::YieldKickoffTxid),
119 Normal::ReplacementDeposit => Ok(Self::ReplacementDeposit),
120 Normal::LatestBlockhashTimeout => Ok(Self::LatestBlockhashTimeout),
121 Normal::LatestBlockhash => Ok(Self::LatestBlockhash),
122 Normal::OptimisticPayout => Ok(Self::OptimisticPayout),
123 }
124 }
125 grpc_transaction_id::Id::NumberedTransaction(transaction_id) => {
126 let tx_type = NumberedTransactionType::try_from(transaction_id.transaction_type)?;
127 match tx_type {
128 Numbered::WatchtowerChallenge => {
129 Ok(Self::WatchtowerChallenge(transaction_id.index as usize))
130 }
131 Numbered::OperatorChallengeNack => {
132 Ok(Self::OperatorChallengeNack(transaction_id.index as usize))
133 }
134 Numbered::OperatorChallengeAck => {
135 Ok(Self::OperatorChallengeAck(transaction_id.index as usize))
136 }
137 Numbered::AssertTimeout => {
138 Ok(Self::AssertTimeout(transaction_id.index as usize))
139 }
140 Numbered::UnspentKickoff => {
141 Ok(Self::UnspentKickoff(transaction_id.index as usize))
142 }
143 Numbered::MiniAssert => Ok(Self::MiniAssert(transaction_id.index as usize)),
144 Numbered::WatchtowerChallengeTimeout => Ok(Self::WatchtowerChallengeTimeout(
145 transaction_id.index as usize,
146 )),
147 Numbered::UnspecifiedIndexedTransactionType => {
148 Err(::prost::UnknownEnumValue(transaction_id.transaction_type))
149 }
150 }
151 }
152 }
153 }
154}
155
156impl From<TransactionType> for GrpcTransactionId {
157 fn from(value: TransactionType) -> Self {
158 use grpc_transaction_id::Id::*;
159 use NormalTransactionId as Normal;
160 use NumberedTransactionType as Numbered;
161 GrpcTransactionId {
162 id: Some(match value {
163 TransactionType::Round => NormalTransaction(Normal::Round as i32),
164 TransactionType::Kickoff => NormalTransaction(Normal::Kickoff as i32),
165 TransactionType::MoveToVault => NormalTransaction(Normal::MoveToVault as i32),
166 TransactionType::Payout => NormalTransaction(Normal::Payout as i32),
167 TransactionType::Challenge => NormalTransaction(Normal::Challenge as i32),
168 TransactionType::Disprove => NormalTransaction(Normal::Disprove as i32),
169 TransactionType::DisproveTimeout => {
170 NormalTransaction(Normal::DisproveTimeout as i32)
171 }
172 TransactionType::Reimburse => NormalTransaction(Normal::Reimburse as i32),
173 TransactionType::AllNeededForDeposit => {
174 NormalTransaction(Normal::AllNeededForDeposit as i32)
175 }
176 TransactionType::Dummy => NormalTransaction(Normal::Dummy as i32),
177 TransactionType::ReadyToReimburse => {
178 NormalTransaction(Normal::ReadyToReimburse as i32)
179 }
180 TransactionType::KickoffNotFinalized => {
181 NormalTransaction(Normal::KickoffNotFinalized as i32)
182 }
183 TransactionType::ChallengeTimeout => {
184 NormalTransaction(Normal::ChallengeTimeout as i32)
185 }
186 TransactionType::ReplacementDeposit => {
187 NormalTransaction(Normal::ReplacementDeposit as i32)
188 }
189 TransactionType::LatestBlockhashTimeout => {
190 NormalTransaction(Normal::LatestBlockhashTimeout as i32)
191 }
192 TransactionType::LatestBlockhash => {
193 NormalTransaction(Normal::LatestBlockhash as i32)
194 }
195 TransactionType::OptimisticPayout => {
196 NormalTransaction(Normal::OptimisticPayout as i32)
197 }
198 TransactionType::WatchtowerChallenge(index) => {
199 NumberedTransaction(NumberedTransactionId {
200 transaction_type: Numbered::WatchtowerChallenge as i32,
201 index: index as i32,
202 })
203 }
204 TransactionType::OperatorChallengeNack(index) => {
205 NumberedTransaction(NumberedTransactionId {
206 transaction_type: Numbered::OperatorChallengeNack as i32,
207 index: index as i32,
208 })
209 }
210 TransactionType::OperatorChallengeAck(index) => {
211 NumberedTransaction(NumberedTransactionId {
212 transaction_type: Numbered::OperatorChallengeAck as i32,
213 index: index as i32,
214 })
215 }
216 TransactionType::AssertTimeout(index) => {
217 NumberedTransaction(NumberedTransactionId {
218 transaction_type: Numbered::AssertTimeout as i32,
219 index: index as i32,
220 })
221 }
222 TransactionType::UnspentKickoff(index) => {
223 NumberedTransaction(NumberedTransactionId {
224 transaction_type: Numbered::UnspentKickoff as i32,
225 index: index as i32,
226 })
227 }
228 TransactionType::MiniAssert(index) => NumberedTransaction(NumberedTransactionId {
229 transaction_type: Numbered::MiniAssert as i32,
230 index: index as i32,
231 }),
232 TransactionType::WatchtowerChallengeTimeout(index) => {
233 NumberedTransaction(NumberedTransactionId {
234 transaction_type: Numbered::WatchtowerChallengeTimeout as i32,
235 index: index as i32,
236 })
237 }
238 TransactionType::BurnUnusedKickoffConnectors => {
239 NormalTransaction(Normal::BurnUnusedKickoffConnectors as i32)
240 }
241 TransactionType::YieldKickoffTxid => {
242 NormalTransaction(Normal::YieldKickoffTxid as i32)
243 }
244 TransactionType::EmergencyStop => {
245 NormalTransaction(Normal::UnspecifiedTransactionType as i32)
246 }
247 }),
248 }
249 }
250}
251
252pub fn anchor_output(amount: Amount) -> TxOut {
258 TxOut {
259 value: amount,
260 script_pubkey: ScriptBuf::from_hex("51024e73").expect("statically valid script"),
261 }
262}
263
264pub fn non_ephemeral_anchor_output() -> TxOut {
267 TxOut {
268 value: NON_EPHEMERAL_ANCHOR_AMOUNT,
269 script_pubkey: ScriptBuf::from_hex("51024e73").expect("statically valid script"),
270 }
271}
272
273pub fn op_return_txout<S: AsRef<bitcoin::script::PushBytes>>(slice: S) -> TxOut {
287 let script = Builder::new()
288 .push_opcode(OP_RETURN)
289 .push_slice(slice)
290 .into_script();
291
292 TxOut {
293 value: Amount::from_sat(0),
294 script_pubkey: script,
295 }
296}
297
298pub fn create_move_to_vault_txhandler(
311 deposit_data: &mut DepositData,
312 paramset: &'static ProtocolParamset,
313) -> Result<TxHandler<Unsigned>, BridgeError> {
314 let nofn_xonly_pk = deposit_data.get_nofn_xonly_pk()?;
315 let deposit_outpoint = deposit_data.get_deposit_outpoint();
316 let nofn_script = Arc::new(CheckSig::new(nofn_xonly_pk));
317 let security_council_script = Arc::new(Multisig::from_security_council(
318 deposit_data.security_council.clone(),
319 ));
320
321 let deposit_scripts = deposit_data.get_deposit_scripts(paramset)?;
322
323 Ok(TxHandlerBuilder::new(TransactionType::MoveToVault)
324 .with_version(NON_STANDARD_V3)
325 .add_input(
326 NormalSignatureKind::NotStored,
327 SpendableTxIn::from_scripts(
328 deposit_outpoint,
329 paramset.bridge_amount,
330 deposit_scripts,
331 None,
332 paramset.network,
333 ),
334 SpendPath::ScriptSpend(0),
335 DEFAULT_SEQUENCE,
336 )
337 .add_output(UnspentTxOut::from_scripts(
338 paramset.bridge_amount,
339 vec![nofn_script, security_council_script],
340 None,
341 paramset.network,
342 ))
343 .add_output(UnspentTxOut::from_partial(anchor_output(Amount::from_sat(
345 0,
346 ))))
347 .finalize())
348}
349
350pub fn create_emergency_stop_txhandler(
365 deposit_data: &mut DepositData,
366 move_to_vault_txhandler: &TxHandler,
367 paramset: &'static ProtocolParamset,
368) -> Result<TxHandler<Unsigned>, BridgeError> {
369 const EACH_EMERGENCY_STOP_VBYTES: Amount = Amount::from_sat(126);
371 let security_council = deposit_data.security_council.clone();
372
373 let builder = TxHandlerBuilder::new(TransactionType::EmergencyStop)
374 .add_input(
375 NormalSignatureKind::NotStored,
376 move_to_vault_txhandler.get_spendable_output(UtxoVout::DepositInMove)?,
377 SpendPath::ScriptSpend(0),
378 DEFAULT_SEQUENCE,
379 )
380 .add_output(UnspentTxOut::from_scripts(
381 paramset.bridge_amount - paramset.anchor_amount() - EACH_EMERGENCY_STOP_VBYTES * 3,
382 vec![Arc::new(Multisig::from_security_council(security_council))],
383 None,
384 paramset.network,
385 ))
386 .finalize();
387
388 Ok(builder)
389}
390
391#[cfg(test)]
408pub fn create_replacement_deposit_txhandler(
409 old_move_txid: Txid,
410 input_outpoint: OutPoint,
411 old_nofn_xonly_pk: XOnlyPublicKey,
412 new_nofn_xonly_pk: XOnlyPublicKey,
413 paramset: &'static ProtocolParamset,
414 security_council: SecurityCouncil,
415) -> Result<TxHandler, BridgeError> {
416 Ok(TxHandlerBuilder::new(TransactionType::ReplacementDeposit)
417 .with_version(NON_STANDARD_V3)
418 .add_input(
419 NormalSignatureKind::NoSignature,
420 SpendableTxIn::from_scripts(
421 input_outpoint,
422 paramset.bridge_amount,
423 vec![
424 Arc::new(CheckSig::new(old_nofn_xonly_pk)),
425 Arc::new(Multisig::from_security_council(security_council.clone())),
426 ],
427 None,
428 paramset.network,
429 ),
430 crate::builder::script::SpendPath::ScriptSpend(0),
431 DEFAULT_SEQUENCE,
432 )
433 .add_output(UnspentTxOut::from_scripts(
434 paramset.bridge_amount,
435 vec![
436 Arc::new(ReplacementDepositScript::new(
437 new_nofn_xonly_pk,
438 old_move_txid,
439 )),
440 Arc::new(Multisig::from_security_council(security_council)),
441 ],
442 None,
443 paramset.network,
444 ))
445 .add_output(UnspentTxOut::from_partial(anchor_output(Amount::from_sat(
447 0,
448 ))))
449 .finalize())
450}
451
452pub fn create_disprove_taproot_output(
466 operator_timeout_script: Arc<dyn SpendableScript>,
467 additional_script: ScriptBuf,
468 disprove_path: DisprovePath,
469 amount: Amount,
470 network: bitcoin::Network,
471) -> UnspentTxOut {
472 use crate::bitvm_client::{SECP, UNSPENDABLE_XONLY_PUBKEY};
473 use bitcoin::taproot::{TapNodeHash, TaprootBuilder};
474
475 let mut scripts: Vec<ScriptBuf> = vec![additional_script.clone()];
476
477 let builder = match disprove_path.clone() {
478 DisprovePath::Scripts(extra_scripts) => {
479 let mut builder = TaprootBuilder::new();
480
481 builder = builder
482 .add_leaf(1, operator_timeout_script.to_script_buf())
483 .expect("add operator timeout script")
484 .add_leaf(2, additional_script)
485 .expect("add additional script");
486
487 let depths = calculate_taproot_leaf_depths(extra_scripts.len());
489
490 for (depth, script) in depths.into_iter().zip(extra_scripts.iter()) {
494 let main_tree_depth = 2 + depth;
495 builder = builder
496 .add_leaf(main_tree_depth, script.clone())
497 .expect("add inlined disprove script");
498 }
499
500 scripts.extend(extra_scripts);
502 builder
503 }
504 DisprovePath::HiddenNode(root_hash) => TaprootBuilder::new()
505 .add_leaf(1, operator_timeout_script.to_script_buf())
506 .expect("empty taptree will accept a script node")
507 .add_leaf(2, additional_script)
508 .expect("taptree with one node will accept a node at depth 2")
509 .add_hidden_node(2, TapNodeHash::from_byte_array(*root_hash))
510 .expect("taptree with two nodes will accept a node at depth 2"),
511 };
512
513 let taproot_spend_info = builder
514 .finalize(&SECP, *UNSPENDABLE_XONLY_PUBKEY)
515 .expect("valid taptree");
516
517 let address = Address::p2tr(
518 &SECP,
519 *UNSPENDABLE_XONLY_PUBKEY,
520 taproot_spend_info.merkle_root(),
521 network,
522 );
523
524 let mut spendable_scripts: Vec<Arc<dyn SpendableScript>> = vec![operator_timeout_script];
525 let other_spendable_scripts: Vec<Arc<dyn SpendableScript>> = scripts
526 .into_iter()
527 .map(|script| Arc::new(OtherSpendable::new(script)) as Arc<dyn SpendableScript>)
528 .collect();
529
530 spendable_scripts.extend(other_spendable_scripts);
531
532 UnspentTxOut::new(
533 TxOut {
534 value: amount,
535 script_pubkey: address.script_pubkey(),
536 },
537 spendable_scripts,
538 Some(taproot_spend_info),
539 )
540}
541
542pub fn create_taproot_output_with_hidden_node(
558 script: Arc<dyn SpendableScript>,
559 hidden_node: HiddenNode,
560 amount: Amount,
561 network: bitcoin::Network,
562) -> UnspentTxOut {
563 use crate::bitvm_client::{SECP, UNSPENDABLE_XONLY_PUBKEY};
564 use bitcoin::taproot::{TapNodeHash, TaprootBuilder};
565
566 let builder = TaprootBuilder::new()
567 .add_leaf(1, script.to_script_buf())
568 .expect("empty taptree will accept a script node")
569 .add_hidden_node(1, TapNodeHash::from_byte_array(*hidden_node))
570 .expect("taptree with one node will accept a node at depth 1");
571
572 let taproot_spend_info = builder
573 .finalize(&SECP, *UNSPENDABLE_XONLY_PUBKEY)
574 .expect("cannot fail since it is a valid taptree");
575
576 let address = Address::p2tr(
577 &SECP,
578 *UNSPENDABLE_XONLY_PUBKEY,
579 taproot_spend_info.merkle_root(),
580 network,
581 );
582
583 UnspentTxOut::new(
584 TxOut {
585 value: amount,
586 script_pubkey: address.script_pubkey(),
587 },
588 vec![script.clone()],
589 Some(taproot_spend_info),
590 )
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596 use bitcoin::secp256k1::XOnlyPublicKey;
597 use std::str::FromStr;
598
599 #[test]
600 fn test_security_council_from_str() {
601 let pk1 = XOnlyPublicKey::from_slice(&[1; 32]).unwrap();
603 let pk2 = XOnlyPublicKey::from_slice(&[2; 32]).unwrap();
604
605 let input = format!(
607 "2:{},{}",
608 hex::encode(pk1.serialize()),
609 hex::encode(pk2.serialize())
610 );
611 let council = SecurityCouncil::from_str(&input).unwrap();
612 assert_eq!(council.threshold, 2);
613 assert_eq!(council.pks.len(), 2);
614 assert_eq!(council.pks[0], pk1);
615 assert_eq!(council.pks[1], pk2);
616
617 let input = format!(
619 "3:{},{}",
620 hex::encode(pk1.serialize()),
621 hex::encode(pk2.serialize())
622 );
623 assert!(SecurityCouncil::from_str(&input).is_err());
624
625 let input = "2:invalid,pk2";
627 assert!(SecurityCouncil::from_str(input).is_err());
628
629 assert!(SecurityCouncil::from_str("2").is_err());
631 assert!(SecurityCouncil::from_str(":").is_err());
632
633 let input = format!(
635 "2:{},{}:extra",
636 hex::encode(pk1.serialize()),
637 hex::encode(pk2.serialize())
638 );
639 assert!(SecurityCouncil::from_str(&input).is_err());
640
641 assert!(SecurityCouncil::from_str("2:").is_err());
643 }
644
645 #[test]
646 fn test_security_council_round_trip() {
647 let pk1 = XOnlyPublicKey::from_slice(&[1; 32]).unwrap();
649 let pk2 = XOnlyPublicKey::from_slice(&[2; 32]).unwrap();
650
651 let original = SecurityCouncil {
652 pks: vec![pk1, pk2],
653 threshold: 2,
654 };
655
656 let string = original.to_string();
657 let parsed = SecurityCouncil::from_str(&string).unwrap();
658
659 assert_eq!(original, parsed);
660 }
661}