From ec3624d54750a81c475698575878cd6e04309fbc Mon Sep 17 00:00:00 2001 From: quake Date: Tue, 7 May 2024 15:49:00 +0900 Subject: [PATCH 1/2] chore: change amount type to u128 --- contracts/commitment-lock/src/main.rs | 65 +++++++++++++++++++-------- tests/src/tests.rs | 8 ++-- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/contracts/commitment-lock/src/main.rs b/contracts/commitment-lock/src/main.rs index 8b8d3be..90cb177 100644 --- a/contracts/commitment-lock/src/main.rs +++ b/contracts/commitment-lock/src/main.rs @@ -68,13 +68,41 @@ pub fn program_entry() -> i8 { // min witness script length: 8 (local_delay_epoch) + 20 (local_delay_pubkey_hash) + 20 (revocation_pubkey_hash) = 48 const MIN_WITNESS_SCRIPT_LEN: usize = 48; -// HTLC script length: 1 (htlc_type) + 8 (payment_amount) + 20 (payment_hash) + 20 (remote_htlc_pubkey_hash) + 20 (local_htlc_pubkey_hash) + 8 (htlc_expiry) = 77 -const HTLC_SCRIPT_LEN: usize = 77; +// HTLC script length: 1 (htlc_type) + 16 (payment_amount) + 20 (payment_hash) + 20 (remote_htlc_pubkey_hash) + 20 (local_htlc_pubkey_hash) + 8 (htlc_expiry) = 85 +const HTLC_SCRIPT_LEN: usize = 85; // 1 (unlock_type) + 65 (signature) = 66 const UNLOCK_WITH_SIGNATURE_LEN: usize = 66; const PREIMAGE_LEN: usize = 32; const MIN_WITNESS_LEN: usize = MIN_WITNESS_SCRIPT_LEN + UNLOCK_WITH_SIGNATURE_LEN; +struct Htlc<'a>(&'a [u8]); + +impl<'a> Htlc<'a> { + pub fn htlc_type(&self) -> u8 { + self.0[0] + } + + pub fn payment_amount(&self) -> u128 { + u128::from_le_bytes(self.0[1..17].try_into().unwrap()) + } + + pub fn payment_hash(&self) -> &'a [u8] { + &self.0[17..37] + } + + pub fn remote_htlc_pubkey_hash(&self) -> &'a [u8] { + &self.0[37..57] + } + + pub fn local_htlc_pubkey_hash(&self) -> &'a [u8] { + &self.0[57..77] + } + + pub fn htlc_expiry(&self) -> u64 { + u64::from_le_bytes(self.0[77..85].try_into().unwrap()) + } +} + fn auth() -> Result<(), Error> { // since local_delay_pubkey and revocation_pubkey are derived, the scripts are usually unique, // to simplify the implementation of the following unlocking logic, we check the number of inputs should be 1 @@ -144,59 +172,60 @@ fn auth() -> Result<(), Error> { return Err(Error::InvalidUnlockType); } - let mut new_capacity = load_cell_capacity(0, Source::GroupInput)?; + let mut new_capacity = load_cell_capacity(0, Source::GroupInput)? as u128; let mut new_witness_script: Vec<&[u8]> = Vec::new(); new_witness_script.push(&witness[0..MIN_WITNESS_SCRIPT_LEN]); - for (i, htlc) in witness[MIN_WITNESS_SCRIPT_LEN..witness_script_len] + for (i, htlc_script) in witness[MIN_WITNESS_SCRIPT_LEN..witness_script_len] .chunks(HTLC_SCRIPT_LEN) .enumerate() { + let htlc = Htlc(htlc_script); if unlock_htlc == i { - if htlc[0] == 0 { + if htlc.htlc_type() == 0 { // offered HTLC let raw_since_value = load_input_since(0, Source::GroupInput)?; if raw_since_value == 0 { // when input since is 0, it means the unlock logic is for remote_htlc pubkey and preimage if preimage - .map(|p| blake2b_256(p)[0..20] != htlc[9..29]) + .map(|p| htlc.payment_hash() != &blake2b_256(p)[0..20]) .unwrap_or(false) { return Err(Error::PreimageError); } - new_capacity -= u64::from_le_bytes(htlc[1..9].try_into().unwrap()); - pubkey_hash.copy_from_slice(&htlc[29..49]); + new_capacity -= htlc.payment_amount(); + pubkey_hash.copy_from_slice(htlc.remote_htlc_pubkey_hash()); } else { // when input since is not 0, it means the unlock logic is for local_htlc pubkey and htlc expiry let since = Since::new(raw_since_value); let htlc_expiry = - Since::new(u64::from_le_bytes(htlc[69..77].try_into().unwrap())); + Since::new(htlc.htlc_expiry()); if since >= htlc_expiry { - pubkey_hash.copy_from_slice(&htlc[49..69]); + pubkey_hash.copy_from_slice(htlc.local_htlc_pubkey_hash()); } else { return Err(Error::InvalidSince); } } - } else if htlc[0] == 1 { + } else if htlc.htlc_type() == 1 { // received HTLC let raw_since_value = load_input_since(0, Source::GroupInput)?; if raw_since_value == 0 { // when input since is 0, it means the unlock logic is for local_htlc pubkey and preimage if preimage - .map(|p| blake2b_256(p)[0..20] != htlc[9..29]) + .map(|p| htlc.payment_hash() != &blake2b_256(p)[0..20]) .unwrap_or(false) { return Err(Error::PreimageError); } - pubkey_hash.copy_from_slice(&htlc[49..69]); + pubkey_hash.copy_from_slice(&htlc.local_htlc_pubkey_hash()); } else { // when input since is not 0, it means the unlock logic is for remote_htlc pubkey and htlc expiry let since = Since::new(raw_since_value); let htlc_expiry = - Since::new(u64::from_le_bytes(htlc[69..77].try_into().unwrap())); + Since::new(htlc.htlc_expiry()); if since >= htlc_expiry { - new_capacity -= u64::from_le_bytes(htlc[1..9].try_into().unwrap()); - pubkey_hash.copy_from_slice(&htlc[29..49]); + new_capacity -= htlc.payment_amount(); + pubkey_hash.copy_from_slice(htlc.remote_htlc_pubkey_hash()); } else { return Err(Error::InvalidSince); } @@ -205,11 +234,11 @@ fn auth() -> Result<(), Error> { return Err(Error::InvalidHtlcType); } } else { - new_witness_script.push(htlc); + new_witness_script.push(htlc_script); } } // verify the first output cell's capacity and lock is correct - let output_capacity = load_cell_capacity(0, Source::Output)?; + let output_capacity = load_cell_capacity(0, Source::Output)? as u128; if output_capacity != new_capacity { return Err(Error::OutputCapacityError); } diff --git a/tests/src/tests.rs b/tests/src/tests.rs index 38fe415..5c6b047 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -318,8 +318,8 @@ fn test_commitment_lock_with_two_pending_htlcs() { let local_htlc_key2 = generator.gen_keypair(); let preimage1 = [42u8; 32]; let preimage2 = [24u8; 32]; - let payment_amount1 = 5 * BYTE_SHANNONS; - let payment_amount2 = 8 * BYTE_SHANNONS; + let payment_amount1 = 5 * BYTE_SHANNONS as u128; + let payment_amount2 = 8 * BYTE_SHANNONS as u128; // timeout after 2024-04-01 01:00:00 let expiry1 = Since::from_timestamp(1711976400, true).unwrap(); // timeout after 2024-04-02 01:00:00 @@ -468,7 +468,7 @@ fn test_commitment_lock_with_two_pending_htlcs() { .args(blake2b_256(&new_witness_script)[0..20].to_vec().pack()) .build(); let outputs = vec![CellOutput::new_builder() - .capacity((1000 * BYTE_SHANNONS - payment_amount1).pack()) + .capacity((1000 * BYTE_SHANNONS - payment_amount1 as u64).pack()) .lock(new_lock_script.clone()) .build()]; let outputs_data = vec![Bytes::new()]; @@ -572,7 +572,7 @@ fn test_commitment_lock_with_two_pending_htlcs() { .args(blake2b_256(&new_witness_script)[0..20].to_vec().pack()) .build(); let outputs = vec![CellOutput::new_builder() - .capacity((1000 * BYTE_SHANNONS - payment_amount2).pack()) + .capacity((1000 * BYTE_SHANNONS - payment_amount2 as u64).pack()) .lock(new_lock_script.clone()) .build()]; let outputs_data = vec![Bytes::new()]; From e38eccfd1b306b974bbacd286a3209b88ad385c8 Mon Sep 17 00:00:00 2001 From: quake Date: Tue, 7 May 2024 19:17:10 +0900 Subject: [PATCH 2/2] feat: supports sudt/xudt for commitment lock --- contracts/commitment-lock/src/main.rs | 61 ++++- deps/README.md | 11 + deps/simple_udt | Bin 0 -> 1696 bytes tests/src/tests.rs | 371 ++++++++++++++++++++++++++ 4 files changed, 429 insertions(+), 14 deletions(-) create mode 100755 deps/simple_udt diff --git a/contracts/commitment-lock/src/main.rs b/contracts/commitment-lock/src/main.rs index 90cb177..9318982 100644 --- a/contracts/commitment-lock/src/main.rs +++ b/contracts/commitment-lock/src/main.rs @@ -18,8 +18,8 @@ use ckb_std::{ ckb_types::{bytes::Bytes, core::ScriptHashType, prelude::*}, error::SysError, high_level::{ - exec_cell, load_cell_capacity, load_cell_lock, load_input_since, load_script, load_tx_hash, - load_witness, + exec_cell, load_cell_capacity, load_cell_data, load_cell_lock, load_cell_type, + load_input_since, load_script, load_tx_hash, load_witness, }, since::Since, }; @@ -43,6 +43,8 @@ pub enum Error { WitnessHashError, OutputCapacityError, OutputLockError, + OutputTypeError, + OutputUdtAmountError, PreimageError, AuthError, } @@ -110,6 +112,9 @@ fn auth() -> Result<(), Error> { return Err(Error::MultipleInputs); } + // no need to check the type script is sudt / xudt or not, because the offchain tx collaboration will ensure the correct type script. + let type_script = load_cell_type(0, Source::GroupInput)?; + let script = load_script()?; let args: Bytes = script.args().unpack(); if args.len() != 20 { @@ -172,7 +177,12 @@ fn auth() -> Result<(), Error> { return Err(Error::InvalidUnlockType); } - let mut new_capacity = load_cell_capacity(0, Source::GroupInput)? as u128; + let mut new_amount = if type_script.is_some() { + let input_cell_data = load_cell_data(0, Source::GroupInput)?; + u128::from_le_bytes(input_cell_data[0..16].try_into().unwrap()) + } else { + load_cell_capacity(0, Source::GroupInput)? as u128 + }; let mut new_witness_script: Vec<&[u8]> = Vec::new(); new_witness_script.push(&witness[0..MIN_WITNESS_SCRIPT_LEN]); @@ -193,13 +203,12 @@ fn auth() -> Result<(), Error> { { return Err(Error::PreimageError); } - new_capacity -= htlc.payment_amount(); + new_amount -= htlc.payment_amount(); pubkey_hash.copy_from_slice(htlc.remote_htlc_pubkey_hash()); } else { // when input since is not 0, it means the unlock logic is for local_htlc pubkey and htlc expiry let since = Since::new(raw_since_value); - let htlc_expiry = - Since::new(htlc.htlc_expiry()); + let htlc_expiry = Since::new(htlc.htlc_expiry()); if since >= htlc_expiry { pubkey_hash.copy_from_slice(htlc.local_htlc_pubkey_hash()); } else { @@ -221,10 +230,9 @@ fn auth() -> Result<(), Error> { } else { // when input since is not 0, it means the unlock logic is for remote_htlc pubkey and htlc expiry let since = Since::new(raw_since_value); - let htlc_expiry = - Since::new(htlc.htlc_expiry()); + let htlc_expiry = Since::new(htlc.htlc_expiry()); if since >= htlc_expiry { - new_capacity -= htlc.payment_amount(); + new_amount -= htlc.payment_amount(); pubkey_hash.copy_from_slice(htlc.remote_htlc_pubkey_hash()); } else { return Err(Error::InvalidSince); @@ -237,12 +245,8 @@ fn auth() -> Result<(), Error> { new_witness_script.push(htlc_script); } } - // verify the first output cell's capacity and lock is correct - let output_capacity = load_cell_capacity(0, Source::Output)? as u128; - if output_capacity != new_capacity { - return Err(Error::OutputCapacityError); - } + // verify the first output cell's lock script is correct let output_lock = load_cell_lock(0, Source::Output)?; let expected_lock_args = blake2b_256(new_witness_script.concat())[0..20].pack(); if output_lock.code_hash() != script.code_hash() @@ -251,6 +255,35 @@ fn auth() -> Result<(), Error> { { return Err(Error::OutputLockError); } + + match type_script { + Some(udt_script) => { + // verify the first output cell's capacity, type script and udt amount are correct + let output_capacity = load_cell_capacity(0, Source::Output)?; + let input_capacity = load_cell_capacity(0, Source::GroupInput)?; + if output_capacity != input_capacity { + return Err(Error::OutputCapacityError); + } + + let output_type = load_cell_type(0, Source::Output)?; + if output_type != Some(udt_script) { + return Err(Error::OutputTypeError); + } + + let output_data = load_cell_data(0, Source::Output)?; + let output_amount = u128::from_le_bytes(output_data[0..16].try_into().unwrap()); + if output_amount != new_amount { + return Err(Error::OutputUdtAmountError); + } + } + None => { + // verify the first output cell's capacity is correct + let output_capacity = load_cell_capacity(0, Source::Output)? as u128; + if output_capacity != new_amount { + return Err(Error::OutputCapacityError); + } + } + } } // AuthAlgorithmIdCkb = 0 diff --git a/deps/README.md b/deps/README.md index 2abc1f0..7df48db 100644 --- a/deps/README.md +++ b/deps/README.md @@ -3,6 +3,17 @@ ```bash git clone https://github.com/nervosnetwork/ckb-auth.git cd ckb-auth +git submodule update --init make all-via-docker cp build/auth ckb-pcn-scripts/deps ``` + +`simple_udt` is a binary built from the `ckb-production-scripts` project. You may rebuild it by running following commands: + +```bash +git clone https://github.com/nervosnetwork/ckb-production-scripts.git +cd ckb-production-scripts +git submodule update --init --recursive +make all-via-docker +cp build/simple_udt ckb-pcn-scripts/deps +``` diff --git a/deps/simple_udt b/deps/simple_udt new file mode 100755 index 0000000000000000000000000000000000000000..e092e8d74530459b71b89ad10f820ec3f52ea866 GIT binary patch literal 1696 zcma)7eM}Q)7=P}o*P^tV6B`6K;UY5T8ip(~;hAmMtV(X4ngUW$Ki=%sG|o z^`JBXSC3K|6Y4*j_=hoTj0p`+W2R`9P3JPrGBxZJ+?V)Skk;$&Xbb9JzRBJDJ|Dm5 zchB#xPj_=GoH&L-X5nxjbn90l<+G;)VJdady>_SoT)UG%Oys6R>C8;6)V?Vl%NR^`)zGKa(#kqd(}A^23r*EEYFs3t$`p~Ply#5x%pHBfQ#@n! z5z;!g^nQXZB~-SYP*a^#wBGr^6!%+1LgyA-X!<}l=E7F>FFRFpM5JmeThDlfuvkW@ zM1`nE_IwPH6W6iop3i&Kc7)8+ka578sm+$Y_jt0KbVWohHBpvAsYmO1yt$@D;1Bwg z9OFG@_)&a&Ep=SRUBFlX|E8U3x8P_^l2n4j$pdR;tqXr>c1tXj8&=xRju5A5Ae=Nv zG({`M)2d`(m)wZ!O6odJvM%;&Z*b++65r1L?4{?9p?HUhFzOFFMH_cVW?>Gg|G znQOeJtod+!d+iTVWqkE5y`!rUv_{30uYbO-s6Br$#*>MQ(VexYqT6ckMqa5Mj9^PI zMnVMuvT&da=N1Cy9J!PHQ8r$nbglN)1$evpP7{YlV`j0~3CzO^Xc$Dv+1YuQiXqQ@KiO?CL#r#7 zL$Pbqy9SXwG@Th8NNR}N!&$PGz#obIbT@S=Sv4~tdU6VH?4`jd1X`&@*j>B7-A^&J z6WH(z*qIi&*#+MPXVQ5R4f&3Mfjtsa(d|P9#d=)rrj?|}`bdn0zICAbF@tP1Yn5Xm zHcVsUA?=IQe#>*YsLxu3I&YEU!FHhdXiLnFdWUi1&`%%-4Y&|!<8yj#o#^2(o?p#S zXOoLw3q#;+p2S0Ln|{X5&E;MVXLFt=lXhiu3`Qmk>#HzN6Yp?T&MW3!@4qzfbz7OO z6u{QFuF<{KUH1vt+zns2!G<<(+}N = vec![(total_sudt_amount / 2).to_le_bytes().to_vec().into(); 2]; + + // build transaction with revocation unlock logic + let tx = TransactionBuilder::default() + .cell_deps(cell_deps.clone()) + .input(input) + .outputs(outputs.clone()) + .outputs_data(outputs_data.pack()) + .build(); + + // sign with revocation key + let message: [u8; 32] = tx.hash().as_slice().try_into().unwrap(); + + let signature = revocation_key + .0 + .sign_recoverable(&message.into()) + .unwrap() + .serialize(); + let witness = [witness_script.clone(), vec![0xFF], signature].concat(); + + let tx = tx.as_advanced_builder().witness(witness.pack()).build(); + println!("tx: {:?}", tx); + + // run + let cycles = context + .verify_tx(&tx, MAX_CYCLES) + .expect("pass verification"); + println!("consume cycles: {}", cycles); + + // build transaction with local_delay_epoch unlock logic + // delay 48 hours + let since = Since::from_epoch(EpochNumberWithFraction::new(12, 0, 1), false); + let input = CellInput::new_builder() + .previous_output(input_out_point.clone()) + .since(since.as_u64().pack()) + .build(); + + let tx = TransactionBuilder::default() + .cell_deps(cell_deps.clone()) + .input(input) + .outputs(outputs) + .outputs_data(outputs_data.pack()) + .build(); + + // sign with local_delay_epoch_key + let message: [u8; 32] = tx.hash().as_slice().try_into().unwrap(); + + let signature = local_delay_epoch_key + .0 + .sign_recoverable(&message.into()) + .unwrap() + .serialize(); + let witness = [witness_script.clone(), vec![0xFF], signature].concat(); + + let tx = tx.as_advanced_builder().witness(witness.pack()).build(); + println!("tx: {:?}", tx); + + // run + let cycles = context + .verify_tx(&tx, MAX_CYCLES) + .expect("pass verification"); + println!("consume cycles: {}", cycles); + + // build transaction with remote_htlc_pubkey unlock offered pending htlc 1 + let input = CellInput::new_builder() + .previous_output(input_out_point.clone()) + .build(); + + let new_witness_script = [ + local_delay_epoch.as_u64().to_le_bytes().to_vec(), + blake2b_256(local_delay_epoch_key.1.serialize())[0..20].to_vec(), + blake2b_256(revocation_key.1.serialize())[0..20].to_vec(), + [1u8].to_vec(), + payment_amount2.to_le_bytes().to_vec(), + blake2b_256(preimage2)[0..20].to_vec(), + blake2b_256(remote_htlc_key2.1.serialize())[0..20].to_vec(), + blake2b_256(local_htlc_key2.1.serialize())[0..20].to_vec(), + expiry2.as_u64().to_le_bytes().to_vec(), + ] + .concat(); + let new_lock_script = lock_script + .clone() + .as_builder() + .args(blake2b_256(&new_witness_script)[0..20].to_vec().pack()) + .build(); + let outputs = vec![CellOutput::new_builder() + .capacity((1000 * BYTE_SHANNONS).pack()) + .lock(new_lock_script.clone()) + .type_(Some(type_script.clone()).pack()) + .build()]; + let outputs_data: Vec = vec![(total_sudt_amount - payment_amount1) + .to_le_bytes() + .to_vec() + .into()]; + let tx = TransactionBuilder::default() + .cell_deps(cell_deps.clone()) + .input(input) + .outputs(outputs) + .outputs_data(outputs_data.pack()) + .build(); + + // sign with remote_htlc_pubkey + let message: [u8; 32] = tx.hash().as_slice().try_into().unwrap(); + + let signature = remote_htlc_key1 + .0 + .sign_recoverable(&message.into()) + .unwrap() + .serialize(); + let witness = [ + witness_script.clone(), + vec![0x00], + signature, + preimage1.to_vec(), + ] + .concat(); + + let tx = tx.as_advanced_builder().witness(witness.pack()).build(); + println!("tx: {:?}", tx); + + // run + let cycles = context + .verify_tx(&tx, MAX_CYCLES) + .expect("pass verification"); + println!("consume cycles: {}", cycles); + + // build transaction with local_htlc_pubkey unlock offered pending htlc 1 + let since = Since::from_timestamp(1711976400 + 1000, true).unwrap(); + + let input = CellInput::new_builder() + .previous_output(input_out_point.clone()) + .since(since.as_u64().pack()) + .build(); + let outputs = vec![CellOutput::new_builder() + .capacity((1000 * BYTE_SHANNONS).pack()) + .lock(new_lock_script.clone()) + .type_(Some(type_script.clone()).pack()) + .build()]; + let outputs_data: Vec = vec![total_sudt_amount.to_le_bytes().to_vec().into()]; + let tx = TransactionBuilder::default() + .cell_deps(cell_deps.clone()) + .input(input) + .outputs(outputs) + .outputs_data(outputs_data.pack()) + .build(); + + // sign with local_htlc_pubkey + let message: [u8; 32] = tx.hash().as_slice().try_into().unwrap(); + + let signature = local_htlc_key1 + .0 + .sign_recoverable(&message.into()) + .unwrap() + .serialize(); + let witness = [ + witness_script.clone(), + vec![0x00], + signature, + preimage1.to_vec(), + ] + .concat(); + + let tx = tx.as_advanced_builder().witness(witness.pack()).build(); + println!("tx: {:?}", tx); + + // run + let cycles = context + .verify_tx(&tx, MAX_CYCLES) + .expect("pass verification"); + println!("consume cycles: {}", cycles); + + // build transaction with remote_htlc_pubkey unlock received pending htlc 2 + let since = Since::from_timestamp(1712062800 + 1000, true).unwrap(); + let input = CellInput::new_builder() + .since(since.as_u64().pack()) + .previous_output(input_out_point.clone()) + .build(); + + let new_witness_script = [ + local_delay_epoch.as_u64().to_le_bytes().to_vec(), + blake2b_256(local_delay_epoch_key.1.serialize())[0..20].to_vec(), + blake2b_256(revocation_key.1.serialize())[0..20].to_vec(), + [0u8].to_vec(), + payment_amount1.to_le_bytes().to_vec(), + blake2b_256(preimage1)[0..20].to_vec(), + blake2b_256(remote_htlc_key1.1.serialize())[0..20].to_vec(), + blake2b_256(local_htlc_key1.1.serialize())[0..20].to_vec(), + expiry1.as_u64().to_le_bytes().to_vec(), + ] + .concat(); + let new_lock_script = lock_script + .as_builder() + .args(blake2b_256(&new_witness_script)[0..20].to_vec().pack()) + .build(); + let outputs = vec![CellOutput::new_builder() + .capacity((1000 * BYTE_SHANNONS).pack()) + .lock(new_lock_script.clone()) + .type_(Some(type_script.clone()).pack()) + .build()]; + let outputs_data: Vec = vec![(total_sudt_amount - payment_amount2) + .to_le_bytes() + .to_vec() + .into()]; + let tx = TransactionBuilder::default() + .cell_deps(cell_deps.clone()) + .input(input) + .outputs(outputs) + .outputs_data(outputs_data.pack()) + .build(); + + // sign with remote_htlc_pubkey + let message: [u8; 32] = tx.hash().as_slice().try_into().unwrap(); + + let signature = remote_htlc_key2 + .0 + .sign_recoverable(&message.into()) + .unwrap() + .serialize(); + let witness = [witness_script.clone(), vec![0x01], signature].concat(); + + let tx = tx.as_advanced_builder().witness(witness.pack()).build(); + println!("tx: {:?}", tx); + + // run + let cycles = context + .verify_tx(&tx, MAX_CYCLES) + .expect("pass verification"); + println!("consume cycles: {}", cycles); + + // // build transaction with local_htlc_pubkey unlock received pending htlc 2 + let input = CellInput::new_builder() + .previous_output(input_out_point.clone()) + .build(); + let outputs = vec![CellOutput::new_builder() + .capacity((1000 * BYTE_SHANNONS).pack()) + .lock(new_lock_script.clone()) + .type_(Some(type_script.clone()).pack()) + .build()]; + let outputs_data: Vec = vec![total_sudt_amount.to_le_bytes().to_vec().into()]; + let tx = TransactionBuilder::default() + .cell_deps(cell_deps) + .input(input) + .outputs(outputs) + .outputs_data(outputs_data.pack()) + .build(); + + // sign with local_htlc_pubkey + let message: [u8; 32] = tx.hash().as_slice().try_into().unwrap(); + + let signature = local_htlc_key2 + .0 + .sign_recoverable(&message.into()) + .unwrap() + .serialize(); + let witness = [ + witness_script.clone(), + vec![0x01], + signature, + preimage2.to_vec(), + ] + .concat(); + + let tx = tx.as_advanced_builder().witness(witness.pack()).build(); + println!("tx: {:?}", tx); + + // run + let cycles = context + .verify_tx(&tx, MAX_CYCLES) + .expect("pass verification"); + println!("consume cycles: {}", cycles); +}