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 {
283 let script = Builder::new()
284 .push_opcode(OP_RETURN)
285 .push_slice(slice)
286 .into_script();
287
288 TxOut {
289 value: Amount::from_sat(0),
290 script_pubkey: script,
291 }
292}
293
294pub fn create_move_to_vault_txhandler(
307 deposit_data: &mut DepositData,
308 paramset: &'static ProtocolParamset,
309) -> Result<TxHandler<Unsigned>, BridgeError> {
310 let nofn_xonly_pk = deposit_data.get_nofn_xonly_pk()?;
311 let deposit_outpoint = deposit_data.get_deposit_outpoint();
312 let nofn_script = Arc::new(CheckSig::new(nofn_xonly_pk));
313 let security_council_script = Arc::new(Multisig::from_security_council(
314 deposit_data.security_council.clone(),
315 ));
316
317 let deposit_scripts = deposit_data.get_deposit_scripts(paramset)?;
318
319 Ok(TxHandlerBuilder::new(TransactionType::MoveToVault)
320 .with_version(NON_STANDARD_V3)
321 .add_input(
322 NormalSignatureKind::NotStored,
323 SpendableTxIn::from_scripts(
324 deposit_outpoint,
325 paramset.bridge_amount,
326 deposit_scripts,
327 None,
328 paramset.network,
329 ),
330 SpendPath::ScriptSpend(0),
331 DEFAULT_SEQUENCE,
332 )
333 .add_output(UnspentTxOut::from_scripts(
334 paramset.bridge_amount,
335 vec![nofn_script, security_council_script],
336 None,
337 paramset.network,
338 ))
339 .add_output(UnspentTxOut::from_partial(anchor_output(Amount::from_sat(
341 0,
342 ))))
343 .finalize())
344}
345
346pub fn create_emergency_stop_txhandler(
361 deposit_data: &mut DepositData,
362 move_to_vault_txhandler: &TxHandler,
363 paramset: &'static ProtocolParamset,
364) -> Result<TxHandler<Unsigned>, BridgeError> {
365 const EACH_EMERGENCY_STOP_VBYTES: Amount = Amount::from_sat(126);
367 let security_council = deposit_data.security_council.clone();
368
369 let builder = TxHandlerBuilder::new(TransactionType::EmergencyStop)
370 .add_input(
371 NormalSignatureKind::NotStored,
372 move_to_vault_txhandler.get_spendable_output(UtxoVout::DepositInMove)?,
373 SpendPath::ScriptSpend(0),
374 DEFAULT_SEQUENCE,
375 )
376 .add_output(UnspentTxOut::from_scripts(
377 paramset.bridge_amount - paramset.anchor_amount() - EACH_EMERGENCY_STOP_VBYTES * 3,
378 vec![Arc::new(Multisig::from_security_council(security_council))],
379 None,
380 paramset.network,
381 ))
382 .finalize();
383
384 Ok(builder)
385}
386
387#[cfg(test)]
404pub fn create_replacement_deposit_txhandler(
405 old_move_txid: Txid,
406 input_outpoint: OutPoint,
407 old_nofn_xonly_pk: XOnlyPublicKey,
408 new_nofn_xonly_pk: XOnlyPublicKey,
409 paramset: &'static ProtocolParamset,
410 security_council: SecurityCouncil,
411) -> Result<TxHandler, BridgeError> {
412 Ok(TxHandlerBuilder::new(TransactionType::ReplacementDeposit)
413 .with_version(NON_STANDARD_V3)
414 .add_input(
415 NormalSignatureKind::NoSignature,
416 SpendableTxIn::from_scripts(
417 input_outpoint,
418 paramset.bridge_amount,
419 vec![
420 Arc::new(CheckSig::new(old_nofn_xonly_pk)),
421 Arc::new(Multisig::from_security_council(security_council.clone())),
422 ],
423 None,
424 paramset.network,
425 ),
426 crate::builder::script::SpendPath::ScriptSpend(0),
427 DEFAULT_SEQUENCE,
428 )
429 .add_output(UnspentTxOut::from_scripts(
430 paramset.bridge_amount,
431 vec![
432 Arc::new(ReplacementDepositScript::new(
433 new_nofn_xonly_pk,
434 old_move_txid,
435 )),
436 Arc::new(Multisig::from_security_council(security_council)),
437 ],
438 None,
439 paramset.network,
440 ))
441 .add_output(UnspentTxOut::from_partial(anchor_output(Amount::from_sat(
443 0,
444 ))))
445 .finalize())
446}
447
448pub fn create_disprove_taproot_output(
462 operator_timeout_script: Arc<dyn SpendableScript>,
463 additional_script: ScriptBuf,
464 disprove_path: DisprovePath,
465 amount: Amount,
466 network: bitcoin::Network,
467) -> UnspentTxOut {
468 use crate::bitvm_client::{SECP, UNSPENDABLE_XONLY_PUBKEY};
469 use bitcoin::taproot::{TapNodeHash, TaprootBuilder};
470
471 let mut scripts: Vec<ScriptBuf> = vec![additional_script.clone()];
472
473 let builder = match disprove_path.clone() {
474 DisprovePath::Scripts(extra_scripts) => {
475 let mut builder = TaprootBuilder::new();
476
477 builder = builder
478 .add_leaf(1, operator_timeout_script.to_script_buf())
479 .expect("add operator timeout script")
480 .add_leaf(2, additional_script)
481 .expect("add additional script");
482
483 let depths = calculate_taproot_leaf_depths(extra_scripts.len());
485
486 for (depth, script) in depths.into_iter().zip(extra_scripts.iter()) {
490 let main_tree_depth = 2 + depth;
491 builder = builder
492 .add_leaf(main_tree_depth, script.clone())
493 .expect("add inlined disprove script");
494 }
495
496 scripts.extend(extra_scripts);
498 builder
499 }
500 DisprovePath::HiddenNode(root_hash) => TaprootBuilder::new()
501 .add_leaf(1, operator_timeout_script.to_script_buf())
502 .expect("empty taptree will accept a script node")
503 .add_leaf(2, additional_script)
504 .expect("taptree with one node will accept a node at depth 2")
505 .add_hidden_node(2, TapNodeHash::from_byte_array(*root_hash))
506 .expect("taptree with two nodes will accept a node at depth 2"),
507 };
508
509 let taproot_spend_info = builder
510 .finalize(&SECP, *UNSPENDABLE_XONLY_PUBKEY)
511 .expect("valid taptree");
512
513 let address = Address::p2tr(
514 &SECP,
515 *UNSPENDABLE_XONLY_PUBKEY,
516 taproot_spend_info.merkle_root(),
517 network,
518 );
519
520 let mut spendable_scripts: Vec<Arc<dyn SpendableScript>> = vec![operator_timeout_script];
521 let other_spendable_scripts: Vec<Arc<dyn SpendableScript>> = scripts
522 .into_iter()
523 .map(|script| Arc::new(OtherSpendable::new(script)) as Arc<dyn SpendableScript>)
524 .collect();
525
526 spendable_scripts.extend(other_spendable_scripts);
527
528 UnspentTxOut::new(
529 TxOut {
530 value: amount,
531 script_pubkey: address.script_pubkey(),
532 },
533 spendable_scripts,
534 Some(taproot_spend_info),
535 )
536}
537
538pub fn create_taproot_output_with_hidden_node(
554 script: Arc<dyn SpendableScript>,
555 hidden_node: HiddenNode,
556 amount: Amount,
557 network: bitcoin::Network,
558) -> UnspentTxOut {
559 use crate::bitvm_client::{SECP, UNSPENDABLE_XONLY_PUBKEY};
560 use bitcoin::taproot::{TapNodeHash, TaprootBuilder};
561
562 let builder = TaprootBuilder::new()
563 .add_leaf(1, script.to_script_buf())
564 .expect("empty taptree will accept a script node")
565 .add_hidden_node(1, TapNodeHash::from_byte_array(*hidden_node))
566 .expect("taptree with one node will accept a node at depth 1");
567
568 let taproot_spend_info = builder
569 .finalize(&SECP, *UNSPENDABLE_XONLY_PUBKEY)
570 .expect("cannot fail since it is a valid taptree");
571
572 let address = Address::p2tr(
573 &SECP,
574 *UNSPENDABLE_XONLY_PUBKEY,
575 taproot_spend_info.merkle_root(),
576 network,
577 );
578
579 UnspentTxOut::new(
580 TxOut {
581 value: amount,
582 script_pubkey: address.script_pubkey(),
583 },
584 vec![script.clone()],
585 Some(taproot_spend_info),
586 )
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592 use bitcoin::secp256k1::XOnlyPublicKey;
593 use std::str::FromStr;
594
595 #[test]
596 fn test_security_council_from_str() {
597 let pk1 = XOnlyPublicKey::from_slice(&[1; 32]).unwrap();
599 let pk2 = XOnlyPublicKey::from_slice(&[2; 32]).unwrap();
600
601 let input = format!(
603 "2:{},{}",
604 hex::encode(pk1.serialize()),
605 hex::encode(pk2.serialize())
606 );
607 let council = SecurityCouncil::from_str(&input).unwrap();
608 assert_eq!(council.threshold, 2);
609 assert_eq!(council.pks.len(), 2);
610 assert_eq!(council.pks[0], pk1);
611 assert_eq!(council.pks[1], pk2);
612
613 let input = format!(
615 "3:{},{}",
616 hex::encode(pk1.serialize()),
617 hex::encode(pk2.serialize())
618 );
619 assert!(SecurityCouncil::from_str(&input).is_err());
620
621 let input = "2:invalid,pk2";
623 assert!(SecurityCouncil::from_str(input).is_err());
624
625 assert!(SecurityCouncil::from_str("2").is_err());
627 assert!(SecurityCouncil::from_str(":").is_err());
628
629 let input = format!(
631 "2:{},{}:extra",
632 hex::encode(pk1.serialize()),
633 hex::encode(pk2.serialize())
634 );
635 assert!(SecurityCouncil::from_str(&input).is_err());
636
637 assert!(SecurityCouncil::from_str("2:").is_err());
639 }
640
641 #[test]
642 fn test_security_council_round_trip() {
643 let pk1 = XOnlyPublicKey::from_slice(&[1; 32]).unwrap();
645 let pk2 = XOnlyPublicKey::from_slice(&[2; 32]).unwrap();
646
647 let original = SecurityCouncil {
648 pks: vec![pk1, pk2],
649 threshold: 2,
650 };
651
652 let string = original.to_string();
653 let parsed = SecurityCouncil::from_str(&string).unwrap();
654
655 assert_eq!(original, parsed);
656 }
657}