diff --git a/.github/workflows/check_pre-merge.yml b/.github/workflows/check_pre-merge.yml index efd7602..2cceb78 100644 --- a/.github/workflows/check_pre-merge.yml +++ b/.github/workflows/check_pre-merge.yml @@ -6,11 +6,13 @@ on: - master - develop - features/sprint* + - stable_v* pull_request: branches: - master - develop - features/sprint* + - stable_v* jobs: rust-test: diff --git a/.github/workflows/create_release-and-upload.yml b/.github/workflows/create_release-and-upload.yml index c8cd964..a462f91 100644 --- a/.github/workflows/create_release-and-upload.yml +++ b/.github/workflows/create_release-and-upload.yml @@ -77,14 +77,13 @@ jobs: - name: cmake-build run: | cd cfd-sys/cfd-cmake - cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DENABLE_SHARED=${{ matrix.shared }} -DENABLE_CAPI=on -DENABLE_TESTS=off -DENABLE_JS_WRAPPER=off + cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DENABLE_SHARED=${{ matrix.shared }} -DENABLE_CAPI=on -DENABLE_TESTS=off -DENABLE_JS_WRAPPER=off -DCMAKE_INSTALL_PREFIX="./dist" cmake --build build --parallel 2 --config Release cd ../.. timeout-minutes: 20 - name: cmake-install run: | cd cfd-sys/cfd-cmake - cmake -DCMAKE_INSTALL_PREFIX="./dist" --install build cmake --install build cd dist del /F /Q cmake\wallycore-* diff --git a/.github/workflows/workflow_modify_checker.yml b/.github/workflows/workflow_modify_checker.yml deleted file mode 100644 index 8c49f8e..0000000 --- a/.github/workflows/workflow_modify_checker.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: workflow modify checker - -# This file is is check the workflow modification on the pull request. -# If this workflow is set to work, it will forcibly cancel all workflows when any workflow changes are made. -# To get this workflow working, configure the following settings: -# 1. Push this file to the default branch. -# 2. Add issue label 'update CI'. -# 3. Set github secrets 'CANCEL_WORKFLOW_TOKEN'. (Token can cancel the repository workflow.) -# -# If you want to modify any of the workflow files, set the Pull Request label to 'update CI'. -# Then re-run the canceled workflow. - -on: - pull_request_target: - types: [opened, reopened, synchronize, labeled] - paths: - '.github/workflows/**' - -jobs: - check-actions: - name: check actions - runs-on: ubuntu-20.04 - - steps: - - name: check secret - id: check_secret - run: | - if [[ -n '${{secrets.CANCEL_WORKFLOW_TOKEN}}' ]]; then - echo "::set-output name=exist_secret::true" - else - echo "::set-output name=exist_secret::false" - fi - - if: contains(github.event.pull_request.labels.*.name, 'update CI') != true && steps.check_secret.outputs.exist_secret == 'true' - name: wait 20 sec - run: sleep 20 - - if: contains(github.event.pull_request.labels.*.name, 'update CI') != true && steps.check_secret.outputs.exist_secret == 'true' - name: cancel workflows - id: cancel_workflow - uses: actions/github-script@v3 - with: - github-token: ${{ secrets.CANCEL_WORKFLOW_TOKEN }} - script: | - const creator = context.payload.sender.login - const opts1 = github.actions.listWorkflowRunsForRepo.endpoint.merge({ - owner: context.repo.owner, - repo: context.repo.repo, - event: 'pull_request', - status: 'queued' - }) - const queuedWorkflows = await github.paginate(opts1) - const opts2 = github.actions.listWorkflowRunsForRepo.endpoint.merge({ - owner: context.repo.owner, - repo: context.repo.repo, - event: 'pull_request', - status: 'in_progress' - }) - const inProgressWorkflows = await github.paginate(opts2) - const workflows = queuedWorkflows.concat(inProgressWorkflows) - //console.log(workflows); - for (const workflow of workflows) { - if (workflow.run_number == context.runNumber) continue - await github.actions.cancelWorkflowRun({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: workflow.id - }) - console.log(`cancel: ${workflow.name} (${workflow.id}),`, - `path:${workflow.head_repository.full_name}/tree/${workflow.head_branch}`); - } - throw new Error('Change workflow file. please set label "update CI".') - - if: success() - run: echo "enabled editing workflow." diff --git a/Cargo.toml b/Cargo.toml index a16770e..9e53f40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cfd-rust" -version = "0.3.0" +version = "0.3.1" license = "MIT" readme = "README.md" keywords = ["build-dependencies"] diff --git a/cfd-sys/Cargo.toml b/cfd-sys/Cargo.toml index 2b89ec5..e7a5257 100644 --- a/cfd-sys/Cargo.toml +++ b/cfd-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cfd_sys" -version = "0.3.0" +version = "0.3.1" license = "MIT" readme = "../README.md" keywords = ["build-dependencies"] diff --git a/cfd-sys/cfd-cmake/external/CMakeLists.txt b/cfd-sys/cfd-cmake/external/CMakeLists.txt index 23b8e9f..d239ebd 100644 --- a/cfd-sys/cfd-cmake/external/CMakeLists.txt +++ b/cfd-sys/cfd-cmake/external/CMakeLists.txt @@ -43,7 +43,7 @@ if(CFD_TARGET_VERSION) set(CFD_TARGET_TAG ${CFD_TARGET_VERSION}) message(STATUS "[external project local] cfd target=${CFD_TARGET_VERSION}") else() -set(CFD_TARGET_TAG v0.3.0) +set(CFD_TARGET_TAG v0.3.1) endif() if(CFD_TARGET_URL) set(CFD_TARGET_REP ${CFD_TARGET_URL}) diff --git a/cfd-sys/src/lib.rs b/cfd-sys/src/lib.rs index 0664873..7baa79d 100644 --- a/cfd-sys/src/lib.rs +++ b/cfd-sys/src/lib.rs @@ -113,6 +113,17 @@ fns! { confidential_key: *mut *mut c_char, network_type: *mut c_int, ) -> c_int; + pub fn CfdGetPeginAddress( + handle: *const c_void, + mainchain_network_type: c_int, + fedpeg_script: *const c_char, + hash_type: c_int, + pubkey: *const c_char, + redeem_script: *const c_char, + pegin_address: *mut *mut c_char, + claim_script: *mut *mut c_char, + tweaked_fedpeg_script: *mut *mut c_char, + ) -> c_int; pub fn CfdParseDescriptor( handle: *const c_void, descriptor: *const i8, @@ -121,6 +132,24 @@ fns! { descriptor_handle: *mut *mut c_void, max_index: *mut c_uint, ) -> c_int; + pub fn CfdGetDescriptorRootData( + handle: *const c_void, + descriptor_handle: *const c_void, + script_type: *mut c_int, + locking_script: *mut *mut c_char, + address: *mut *mut c_char, + hash_type: *mut c_int, + redeem_script: *mut *mut c_char, + key_type: *mut c_int, + pubkey: *mut *mut c_char, + ext_pubkey: *mut *mut c_char, + ext_privkey: *mut *mut c_char, + schnorr_pubkey: *mut *mut c_char, + tree_string: *mut *mut c_char, + is_multisig: *mut bool, + max_key_num: *mut c_uint, + req_sig_num: *mut c_uint, + ) -> c_int; pub fn CfdGetDescriptorData( handle: *const c_void, descriptor_handle: *const c_void, @@ -696,6 +725,21 @@ fns! { direct_locking_script: *const i8, asset_string: *const i8, ) -> c_int; + pub fn CfdSplitTxOut( + handle: *const c_void, + create_handle: *const c_void, + split_output_handle: *const c_void, + txout_index: c_uint, + ) -> c_int; + pub fn CfdUpdateWitnessStack( + handle: *const c_void, + create_handle: *const c_void, + stack_type: c_int, + txid: *const i8, + vout: c_uint, + stack_index: c_uint, + stack_item: *const i8, + ) -> c_int; pub fn CfdClearWitnessStack( handle: *const c_void, create_handle: *const c_void, @@ -802,6 +846,23 @@ fns! { tx_hex: *mut *mut c_char, ) -> c_int; pub fn CfdFreeTransactionHandle(handle: *const c_void, create_handle: *const c_void) -> c_int; + pub fn CfdCreateSplitTxOutHandle( + handle: *const c_void, + create_handle: *const c_void, + split_output_handle: *mut *mut c_void, + ) -> c_int; + pub fn CfdAddSplitTxOutData( + handle: *const c_void, + split_output_handle: *const c_void, + amount: c_longlong, + address: *const i8, + direct_locking_script: *const i8, + direct_nonce: *const i8, + ) -> c_int; + pub fn CfdFreeSplitTxOutHandle( + handle: *const c_void, + split_output_handle: *const c_void, + ) -> c_int; pub fn CfdUpdateTxOutAmount( handle: *const c_void, network_type: c_int, @@ -1244,6 +1305,9 @@ fns! { pub fn CfdGetTxOutIndexByHandle( handle: *const c_void, tx_data_handle: *const c_void, address: *const c_char, direct_locking_script: *const c_char, index: *mut c_uint) -> c_int; + pub fn CfdGetTxOutIndexWithOffsetByHandle( + handle: *const c_void, tx_data_handle: *const c_void, offset: c_uint, address: *const c_char, + direct_locking_script: *const c_char, index: *mut c_uint) -> c_int; pub fn CfdInitializeConfidentialTx( handle: *const c_void, version: c_uint, locktime: c_uint, tx_string: *mut *mut c_char) -> c_int; pub fn CfdAddConfidentialTxOut( @@ -1347,6 +1411,31 @@ fns! { handle: *const c_void, create_handle: *const c_void, value_satoshi: c_longlong, address: *const c_char, direct_locking_script: *const c_char, asset_string: *const c_char, nonce: *const c_char) -> c_int; + pub fn CfdSetIssueAsset( + handle: *const c_void, create_handle: *const c_void, txid: *const c_char, + vout: c_uint, contract_hash: *const c_char, asset_amount: c_longlong, + asset_address: *const c_char, asset_locking_script: *const c_char, + token_amount: c_longlong, token_address: *const c_char, token_locking_script: *const c_char, + is_blind_asset: bool, entropy: *mut *mut c_char, asset_string: *mut *mut c_char, + token_string: *mut *mut c_char) -> c_int; + pub fn CfdSetReissueAsset( + handle: *const c_void, create_handle: *const c_void, txid: *const c_char, + vout: c_uint, asset_amount: c_longlong, blinding_nonce: *const c_char, + entropy: *const c_char, address: *const c_char, direct_locking_script: *const c_char, + asset_string: *mut *mut c_char) -> c_int; + pub fn CfdAddTxPeginInput( + handle: *const c_void, create_handle: *const c_void, txid: *const c_char, + vout: c_uint, amount: c_longlong, asset: *const c_char, + mainchain_genesis_block_hash: *const c_char, claim_script: *const c_char, + mainchain_tx_hex: *const c_char, txout_proof: *const c_char) -> c_int; + pub fn CfdAddTxPegoutOutput( + handle: *const c_void, create_handle: *const c_void, asset: *const c_char, + amount: c_longlong, mainchain_network_type: c_int, + elements_network_type: c_int, + mainchain_genesis_block_hash: *const c_char, online_pubkey: *const c_char, + master_online_key: *const c_char, mainchain_output_descriptor: *const c_char, + bip32_counter: c_uint, whitelist: *const c_char, + mainchain_address: *mut *mut c_char) -> c_int; pub fn CfdCreatePsbtHandle( handle: *const c_void, net_type: c_int, psbt_string: *const c_char, tx_hex_string: *const c_char, version: c_uint, locktime: c_uint, diff --git a/src/address.rs b/src/address.rs index 257fb4a..a5ad4ce 100644 --- a/src/address.rs +++ b/src/address.rs @@ -14,7 +14,7 @@ use std::str::FromStr; use self::cfd_sys::{ CfdCreateAddress, CfdFreeAddressesMultisigHandle, CfdGetAddressFromLockingScript, - CfdGetAddressFromMultisigKey, CfdGetAddressInfo, CfdGetAddressesFromMultisig, + CfdGetAddressFromMultisigKey, CfdGetAddressInfo, CfdGetAddressesFromMultisig, CfdGetPeginAddress, }; /// Hash type of locking script. @@ -283,6 +283,14 @@ impl fmt::Display for WitnessVersion { } } +/// A container that stores a pegin data. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct PeginData { + pub address: Address, + pub claim_script: Script, + pub tweaked_fedpeg_script: Script, +} + /// A container that stores a bitcoin address. #[derive(Debug, PartialEq, Eq, Clone)] pub struct Address { @@ -625,6 +633,87 @@ impl Address { ) } + /// Create pegin address by pubkey. + /// + /// # Arguments + /// * `fedpeg_script` - A fedpeg script. + /// * `pubkey` - A pubkey. + /// * `hash_type` - A hash type. + /// * `network_type` - A target mainchain network. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{Address, HashType, Network, Pubkey, Script}; + /// use std::str::FromStr; + /// let fedpeg_script = Script::from_hex("522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb53ae").expect("Fail"); + /// let pubkey = Pubkey::from_str("02522952c3fc2a53a8651b08ce10988b7506a3b40a5c26f9648a911be33e73e1a0").expect("Fail"); + /// let pegin_data = Address::pegin_by_pubkey( + /// &fedpeg_script, &pubkey, &HashType::P2wsh, &Network::Regtest).expect("Fail"); + /// ``` + pub fn pegin_by_pubkey( + fedpeg_script: &Script, + pubkey: &Pubkey, + hash_type: &HashType, + network_type: &Network, + ) -> Result { + let (addr, claim_script, tweaked_fedpeg) = Address::get_pegin_address( + fedpeg_script, + pubkey, + ptr::null(), + hash_type.to_c_value(), + network_type.to_c_value(), + )?; + Ok(PeginData { + address: addr, + claim_script, + tweaked_fedpeg_script: tweaked_fedpeg, + }) + } + + /// Create pegin address by script. + /// + /// # Arguments + /// * `fedpeg_script` - A fedpeg script. + /// * `redeem_script` - A redeem script. + /// * `hash_type` - A hash type. + /// * `network_type` - A target mainchain network. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{Address, HashType, Network, Script}; + /// use std::str::FromStr; + /// let fedpeg_script = Script::from_hex("522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb53ae").expect("Fail"); + /// let redeem_script = Script::from_hex("522102522952c3fc2a53a8651b08ce10988b7506a3b40a5c26f9648a911be33e73e1a0210340b52ae45bc1be5de083f1730fe537374e219c4836400623741d2a874e60590c21024a3477bc8b933a320eb5667ee72c35a81aa155c8e20cc51c65fb666de3a43b8253ae").expect("Fail"); + /// let pegin_data = Address::pegin_by_script( + /// &fedpeg_script, &redeem_script, &HashType::P2wsh, &Network::Regtest).expect("Fail"); + /// ``` + pub fn pegin_by_script( + fedpeg_script: &Script, + redeem_script: &Script, + hash_type: &HashType, + network_type: &Network, + ) -> Result { + let (addr, claim_script, tweaked_fedpeg) = Address::get_pegin_address( + fedpeg_script, + ptr::null(), + redeem_script, + hash_type.to_c_value(), + network_type.to_c_value(), + )?; + Ok(PeginData { + address: addr, + claim_script, + tweaked_fedpeg_script: tweaked_fedpeg, + }) + } + + #[inline] + pub fn is_valid(&self) -> bool { + !self.address.is_empty() + } + #[inline] pub fn to_str(&self) -> &str { &self.address @@ -889,6 +978,63 @@ impl Address { handle.free_handle(); result } + + fn get_pegin_address( + fedpeg_script: &Script, + pubkey: *const Pubkey, + script: *const Script, + hash_type: c_int, + network_type: c_int, + ) -> Result<(Address, Script, Script), CfdError> { + let pubkey_hex = unsafe { + match pubkey.as_ref() { + Some(pubkey) => alloc_c_string(&pubkey.to_hex()), + _ => alloc_c_string(""), + } + }?; + let redeem_script = unsafe { + match script.as_ref() { + Some(script) => alloc_c_string(&script.to_hex()), + _ => alloc_c_string(""), + } + }?; + let fedpeg_script_hex = alloc_c_string(&fedpeg_script.to_hex())?; + let mut handle = ErrorHandle::new()?; + let mut address: *mut c_char = ptr::null_mut(); + let mut claim_script: *mut c_char = ptr::null_mut(); + let mut tweaked_fedpeg_script: *mut c_char = ptr::null_mut(); + let error_code = unsafe { + CfdGetPeginAddress( + handle.as_handle(), + network_type, + fedpeg_script_hex.as_ptr(), + hash_type, + pubkey_hex.as_ptr(), + redeem_script.as_ptr(), + &mut address, + &mut claim_script, + &mut tweaked_fedpeg_script, + ) + }; + let result = match error_code { + 0 => { + let str_list = unsafe { + collect_multi_cstring_and_free(&[address, claim_script, tweaked_fedpeg_script]) + }?; + let addr_str = &str_list[0]; + let claim_script_obj = &str_list[1]; + let fedpeg_script_obj = &str_list[2]; + Ok(( + Address::from_string(&addr_str)?, + Script::from_hex(&claim_script_obj)?, + Script::from_hex(&fedpeg_script_obj)?, + )) + } + _ => Err(handle.get_error(error_code)), + }; + handle.free_handle(); + result + } } impl FromStr for Address { diff --git a/src/confidential_transaction.rs b/src/confidential_transaction.rs index 4951ead..dd04e19 100644 --- a/src/confidential_transaction.rs +++ b/src/confidential_transaction.rs @@ -8,9 +8,10 @@ use crate::common::{ ErrorHandle, Network, ReverseContainer, }; use crate::transaction::{ - set_fund_tx_option, FeeData, FeeOption, FundOptionValue, FundTargetOption, FundTransactionData, - HashTypeData, OutPoint, ScriptWitness, SigHashOption, TransactionOperation, TxData, TxDataHandle, - TxInData, Txid, UtxoData, SEQUENCE_LOCK_TIME_FINAL, + set_fund_tx_option, BlockHash, CreateTxData, FeeData, FeeOption, FundOptionValue, + FundTargetOption, FundTransactionData, HashTypeData, OutPoint, ScriptWitness, SigHashOption, + Transaction, TransactionOperation, TxData, TxDataHandle, TxInData, Txid, UtxoData, + SEQUENCE_LOCK_TIME_FINAL, }; use crate::{ address::{Address, HashType}, @@ -30,17 +31,18 @@ use self::cfd_sys::{ CfdAddCoinSelectionUtxoTemplate, CfdAddConfidentialTxOutput, CfdAddConfidentialTxSignWithPrivkeySimple, CfdAddTargetAmountForFundRawTx, CfdAddTransactionInput, CfdAddTxInTemplateForEstimateFee, CfdAddTxInTemplateForFundRawTx, - CfdAddUtxoTemplateForFundRawTx, CfdCreateConfidentialSighash, CfdFinalizeBlindTx, - CfdFinalizeCoinSelection, CfdFinalizeEstimateFee, CfdFinalizeFundRawTx, CfdFinalizeTransaction, - CfdFreeBlindHandle, CfdFreeCoinSelectionHandle, CfdFreeEstimateFeeHandle, CfdFreeFundRawTxHandle, + CfdAddTxPeginInput, CfdAddTxPegoutOutput, CfdAddUtxoTemplateForFundRawTx, + CfdCreateConfidentialSighash, CfdFinalizeBlindTx, CfdFinalizeCoinSelection, + CfdFinalizeEstimateFee, CfdFinalizeFundRawTx, CfdFinalizeTransaction, CfdFreeBlindHandle, + CfdFreeCoinSelectionHandle, CfdFreeEstimateFeeHandle, CfdFreeFundRawTxHandle, CfdFreeTransactionHandle, CfdGetAppendTxOutFundRawTx, CfdGetAssetCommitment, CfdGetBlindTxBlindData, CfdGetConfidentialTxInfoByHandle, CfdGetConfidentialTxOutSimpleByHandle, CfdGetConfidentialValueHex, CfdGetDefaultBlindingKey, CfdGetIssuanceBlindingKey, CfdGetSelectedCoinIndex, CfdGetTxInByHandle, CfdGetTxInIndexByHandle, CfdGetTxInIssuanceInfoByHandle, CfdGetTxOutIndex, CfdGetValueCommitment, CfdInitializeBlindTx, CfdInitializeCoinSelection, CfdInitializeEstimateFee, CfdInitializeFundRawTx, - CfdInitializeTransaction, CfdSetBlindTxOption, CfdSetOptionCoinSelection, - CfdSetOptionEstimateFee, CfdSetRawReissueAsset, CfdUnblindIssuance, CfdUnblindTxOut, + CfdInitializeTransaction, CfdSetBlindTxOption, CfdSetIssueAsset, CfdSetOptionCoinSelection, + CfdSetOptionEstimateFee, CfdSetReissueAsset, CfdUnblindIssuance, CfdUnblindTxOut, CfdUpdateTxOutAmount, BLIND_OPT_COLLECT_BLINDER, BLIND_OPT_EXPONENT, BLIND_OPT_MINIMUM_BITS, BLIND_OPT_MINIMUM_RANGE_VALUE, COIN_OPT_BLIND_EXPONENT, COIN_OPT_BLIND_MINIMUM_BITS, DEFAULT_BLIND_MINIMUM_BITS, FEE_OPT_BLIND_EXPONENT, FEE_OPT_BLIND_MINIMUM_BITS, @@ -1301,6 +1303,99 @@ impl Default for ConfidentialTxOutData { } } +/// A container that stores an issuance input data. +#[derive(PartialEq, Eq, Clone)] +pub struct IssuanceInputData { + pub contract_hash: ByteData, + pub asset_amount: i64, + pub asset_address: InputAddress, + pub asset_locking_script: Script, + pub token_amount: i64, + pub token_address: InputAddress, + pub token_locking_script: Script, + pub has_blind: bool, +} + +impl Default for IssuanceInputData { + fn default() -> IssuanceInputData { + IssuanceInputData { + contract_hash: ByteData::default(), + asset_amount: 0, + asset_address: InputAddress::Addr(Address::default()), + asset_locking_script: Script::default(), + token_amount: 0, + token_address: InputAddress::Addr(Address::default()), + token_locking_script: Script::default(), + has_blind: false, + } + } +} + +/// A container that stores a reissuance input data. +#[derive(PartialEq, Eq, Clone)] +pub struct ReissuanceInputData { + pub blinding_nonce: BlindFactor, + pub entropy: BlindFactor, + pub asset_amount: i64, + pub asset_address: InputAddress, + pub asset_locking_script: Script, +} + +impl Default for ReissuanceInputData { + fn default() -> ReissuanceInputData { + ReissuanceInputData { + blinding_nonce: BlindFactor::default(), + entropy: BlindFactor::default(), + asset_amount: 0, + asset_address: InputAddress::Addr(Address::default()), + asset_locking_script: Script::default(), + } + } +} + +/// A container that stores a issuance output data. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct IssuanceOutputData { + pub entropy: BlindFactor, + pub asset: ConfidentialAsset, + pub token: ConfidentialAsset, +} + +impl Default for IssuanceOutputData { + fn default() -> IssuanceOutputData { + IssuanceOutputData { + entropy: BlindFactor::default(), + asset: ConfidentialAsset::default(), + token: ConfidentialAsset::default(), + } + } +} + +/// A container that stores a pegin input data. +#[derive(PartialEq, Eq, Clone)] +pub struct PeginInputData { + pub amount: i64, + pub asset: ConfidentialAsset, + pub mainchain_genesis_block_hash: BlockHash, + pub claim_script: Script, + pub transaction: Transaction, + pub txout_proof: ByteData, +} + +/// A container that stores a pegout input data. +#[derive(PartialEq, Eq, Clone)] +pub struct PegoutInputData { + pub amount: i64, + pub asset: ConfidentialAsset, + pub mainchain_network_type: Network, + pub elements_network_type: Network, + pub mainchain_genesis_block_hash: BlockHash, + pub online_privkey: Privkey, + pub offline_output_descriptor: String, + pub bip32_counter: u32, + pub whitelist: ByteData, +} + /// A container that stores ConfidentialTransaction data. #[derive(Debug, PartialEq, Eq, Clone)] pub struct ConfidentialTxData { @@ -1724,6 +1819,55 @@ impl ConfidentialTransaction { }) } + /// Update witness stack. + /// + /// # Arguments + /// * `outpoint` - An outpoint. + /// * `stack_index` - A witness stack index. + /// * `data` - A witness stack data. + pub fn update_witness_stack( + &self, + outpoint: &OutPoint, + stack_index: u32, + data: &ByteData, + ) -> Result { + let mut ope = ConfidentialTxOperation::new(&Network::LiquidV1); + let tx = ope.update_witness_stack(&hex_from_bytes(&self.tx), outpoint, stack_index, data)?; + Ok(ConfidentialTransaction { + tx, + data: ope.get_last_tx_data().clone(), + txin_list: ope.get_txin_list_cache().to_vec(), + txout_list: self.txout_list.clone(), + // txin_utxo_list: self.txin_utxo_list.clone(), + }) + } + + /// Update pegin witness stack. + /// + /// # Arguments + /// * `outpoint` - An outpoint. + /// * `stack_index` - A pegin witness stack index. + /// * `data` - A witness stack data. + /// + /// # Example + pub fn update_pegin_witness_stack( + &self, + outpoint: &OutPoint, + stack_index: u32, + data: &ByteData, + ) -> Result { + let mut ope = ConfidentialTxOperation::new(&Network::LiquidV1); + let tx = + ope.update_pegin_witness_stack(&hex_from_bytes(&self.tx), outpoint, stack_index, data)?; + Ok(ConfidentialTransaction { + tx, + data: ope.get_last_tx_data().clone(), + txin_list: ope.get_txin_list_cache().to_vec(), + txout_list: self.txout_list.clone(), + // txin_utxo_list: self.txin_utxo_list.clone(), + }) + } + /// Update amount. /// /// # Arguments @@ -1800,6 +1944,50 @@ impl ConfidentialTransaction { Ok(tx_obj) } + /// Split txout. + /// + /// # Arguments + /// * `index` - A txout index. + /// * `txout_list` - txout list. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{ + /// Address, ConfidentialAsset, OutPoint, ConfidentialTransaction, + /// TxInData, ConfidentialTxOutData, + /// }; + /// use std::str::FromStr; + /// let outpoint = OutPoint::from_str( + /// "0202020202020202020202020202020202020202020202020202020202020202", + /// 1).expect("Fail"); + /// let txin_list = [TxInData::new(&outpoint)]; + /// let amount: i64 = 50000; + /// let addr = Address::from_string("ex1qjex3wgf33j9u0vqk6r4exa9xyr5t3z5c7saq0d").expect("Fail"); + /// let addr2 = Address::from_string("ex1qyewtv8juq97qyfcd0l3ctwsgsdnpdsgc4zmnkj").expect("Fail"); + /// let asset = ConfidentialAsset::from_str("0202020202020202020202020202020202020202020202020202020202020202").expect("Fail"); + /// let txout_list = [ConfidentialTxOutData::from_address(amount, &asset, &addr), ConfidentialTxOutData::from_fee(5000, &asset)]; + /// let split_txout_list = [ConfidentialTxOutData::from_address(10000, &asset, &addr2)]; + /// let tx = ConfidentialTransaction::create_tx(2, 0, &txin_list, &txout_list).expect("Fail"); + /// let tx2 = tx.split_txout(0, &split_txout_list).expect("Fail"); + /// ``` + pub fn split_txout( + &self, + index: u32, + txout_list: &[ConfidentialTxOutData], + ) -> Result { + let mut ope = ConfidentialTxOperation::new(&Network::LiquidV1); + let tx = ope.split_txout(&hex_from_bytes(&self.tx), index, txout_list)?; + let txout_list = ope.get_txout_list_cache(); + Ok(ConfidentialTransaction { + tx, + data: ope.get_last_tx_data().clone(), + txin_list: self.txin_list.clone(), + txout_list: txout_list.to_vec(), + // txin_utxo_list: self.txin_utxo_list.clone(), + }) + } + /// Blind transaction. /// /// # Arguments @@ -1949,43 +2137,98 @@ impl ConfidentialTransaction { ) } + /// Set issuance input and output. + /// + /// # Arguments + /// * `outpoint` - An issuance outpoint. + /// * `data` - A issuance input data. + /// * `issuance_data` - (out) An issuance output data. + pub fn set_issuance( + &self, + outpoint: &OutPoint, + data: &IssuanceInputData, + issuance_data: &mut IssuanceOutputData, + ) -> Result { + let mut ope = ConfidentialTxOperation::new(&Network::LiquidV1); + let out_data = ope.set_issuance(&hex_from_bytes(&self.tx), outpoint, data)?; + *issuance_data = out_data; + let tx = ope.get_last_tx(); + let tx_obj = ConfidentialTransaction { + tx: byte_from_hex(tx)?, + data: ope.get_last_tx_data().clone(), + txin_list: ope.get_txin_list_cache().to_vec(), + txout_list: ope.get_txout_list_cache().to_vec(), + }; + Ok(tx_obj) + } + /// Set reissuance input and output. /// /// # Arguments /// * `outpoint` - An issuance outpoint. - /// * `asset_amount` - A reissuance asset amount. - /// * `blinding_nonce` - A token utxo's asset blind factor. - /// * `entropy` - A issuance entropy. - /// * `send_address` - An append txout address. + /// * `data` - A reissuance input data. /// * `output_data` - (out) An appended txout data. pub fn set_reissuance( &self, outpoint: &OutPoint, - asset_amount: i64, - blinding_nonce: &BlindFactor, - entropy: &BlindFactor, - send_address: &InputAddress, + data: &ReissuanceInputData, output_data: &mut ConfidentialTxOutData, ) -> Result { let mut ope = ConfidentialTxOperation::new(&Network::LiquidV1); - let addr_str = match send_address { - InputAddress::Addr(address) => address.to_str().to_string(), - InputAddress::CtAddr(address) => address.to_str().to_string(), - }; - let data = ope.set_reissuance( - &hex_from_bytes(&self.tx), - outpoint, - asset_amount, - blinding_nonce, - entropy, - &addr_str, - )?; + let out_data = ope.set_reissuance(&hex_from_bytes(&self.tx), outpoint, data)?; + *output_data = out_data; let tx = ope.get_last_tx(); - let tx_obj = ConfidentialTransaction::from_str(tx)?; - *output_data = data; + let tx_obj = ConfidentialTransaction { + tx: byte_from_hex(tx)?, + data: ope.get_last_tx_data().clone(), + txin_list: ope.get_txin_list_cache().to_vec(), + txout_list: ope.get_txout_list_cache().to_vec(), + }; Ok(tx_obj) } + /// add pegin input. + /// + /// # Arguments + /// * `outpoint` - An issuance outpoint. + /// * `data` - A reissuance input data. + /// * `output_data` - (out) An appended txout data. + pub fn add_pegin_input( + &self, + outpoint: &OutPoint, + data: &PeginInputData, + ) -> Result { + let mut ope = ConfidentialTxOperation::new(&Network::LiquidV1); + ope.add_pegin_input(&hex_from_bytes(&self.tx), outpoint, data)?; + Ok(ConfidentialTransaction { + tx: byte_from_hex(ope.get_last_tx())?, + data: ope.get_last_tx_data().clone(), + txin_list: ope.get_txin_list_cache().to_vec(), + txout_list: ope.get_txout_list_cache().to_vec(), + }) + } + + /// add pegout output. + /// + /// # Arguments + /// * `data` - A reissuance input data. + /// * `output_data` - (out) An appended txout data. + pub fn add_pegout_output( + &self, + data: &PegoutInputData, + pegout_address: &mut Address, + ) -> Result { + let mut ope = ConfidentialTxOperation::new(&Network::LiquidV1); + let (tx_bytes, addr) = ope.add_pegout_output(&hex_from_bytes(&self.tx), data)?; + *pegout_address = addr; + Ok(ConfidentialTransaction { + tx: tx_bytes, + data: ope.get_last_tx_data().clone(), + txin_list: ope.get_txin_list_cache().to_vec(), + txout_list: ope.get_txout_list_cache().to_vec(), + }) + } + pub fn get_txin_index(&self, outpoint: &OutPoint) -> Result { let ope = TransactionOperation::new(&Network::LiquidV1); ope.get_txin_index_by_outpoint(&hex_from_bytes(&self.tx), outpoint) @@ -2006,6 +2249,16 @@ impl ConfidentialTransaction { ope.get_txout_index_by_script(&hex_from_bytes(&self.tx), &Script::default()) } + pub fn get_txout_indexes_by_address(&self, address: &Address) -> Result, CfdError> { + let ope = TransactionOperation::new(&Network::LiquidV1); + ope.get_txout_indexes_by_address(&hex_from_bytes(&self.tx), address) + } + + pub fn get_txout_indexes_by_script(&self, script: &Script) -> Result, CfdError> { + let ope = TransactionOperation::new(&Network::LiquidV1); + ope.get_txout_indexes_by_script(&hex_from_bytes(&self.tx), script) + } + /// Create signature hash by pubkey. /// /// # Arguments @@ -2799,6 +3052,9 @@ pub(in crate) struct ConfidentialTxOperation { impl ConfidentialTxOperation { pub fn new(network: &Network) -> ConfidentialTxOperation { + if !network.is_elements() { + panic!("invalid network type."); + } ConfidentialTxOperation { network: *network, last_tx: String::default(), @@ -3144,52 +3400,265 @@ impl ConfidentialTxOperation { result } + pub fn set_issuance( + &mut self, + tx: &str, + outpoint: &OutPoint, + data: &IssuanceInputData, + ) -> Result { + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let tx_result = { + let output = self.set_issuance_internal(&handle, &tx_handle, outpoint, data)?; + self.tx_data = self.get_all_data_internal(&handle, &tx_handle, &String::default())?; + self.get_tx_internal(&handle, &tx_handle, &String::default())?; + Ok(output) + }; + tx_handle.free_handle(&handle); + tx_result + }; + handle.free_handle(); + result + } + + pub fn set_issuance_internal( + &mut self, + handle: &ErrorHandle, + tx_handle: &TxDataHandle, + outpoint: &OutPoint, + data: &IssuanceInputData, + ) -> Result { + let asset_addr_str = match &data.asset_address { + InputAddress::Addr(address) => address.to_str().to_string(), + InputAddress::CtAddr(address) => address.to_str().to_string(), + }; + let token_addr_str = match &data.token_address { + InputAddress::Addr(address) => address.to_str().to_string(), + InputAddress::CtAddr(address) => address.to_str().to_string(), + }; + let txid = alloc_c_string(&outpoint.get_txid().to_hex())?; + let contract_hash = alloc_c_string(&data.contract_hash.to_hex())?; + let asset_address = alloc_c_string(&asset_addr_str)?; + let asset_script = alloc_c_string(&data.asset_locking_script.to_hex())?; + let token_address = alloc_c_string(&token_addr_str)?; + let token_script = alloc_c_string(&data.token_locking_script.to_hex())?; + let mut entropy: *mut c_char = ptr::null_mut(); + let mut asset: *mut c_char = ptr::null_mut(); + let mut token: *mut c_char = ptr::null_mut(); + let error_code = unsafe { + CfdSetIssueAsset( + handle.as_handle(), + tx_handle.as_handle(), + txid.as_ptr(), + outpoint.get_vout(), + contract_hash.as_ptr(), + data.asset_amount, + asset_address.as_ptr(), + asset_script.as_ptr(), + data.token_amount, + token_address.as_ptr(), + token_script.as_ptr(), + data.has_blind, + &mut entropy, + &mut asset, + &mut token, + ) + }; + match error_code { + 0 => { + let str_list = unsafe { collect_multi_cstring_and_free(&[entropy, asset, token]) }?; + let entropy_obj = BlindFactor::from_str(&str_list[0])?; + let asset_obj = ConfidentialAsset::from_str(&str_list[1])?; + let token_obj = ConfidentialAsset::from_str(&str_list[2])?; + Ok(IssuanceOutputData { + entropy: entropy_obj, + asset: asset_obj, + token: token_obj, + }) + } + _ => Err(handle.get_error(error_code)), + } + } + pub fn set_reissuance( &mut self, tx: &str, outpoint: &OutPoint, - asset_amount: i64, - blinding_nonce: &BlindFactor, - entropy: &BlindFactor, - send_address: &str, + data: &ReissuanceInputData, ) -> Result { - let tx_str = alloc_c_string(tx)?; - let txid = alloc_c_string(&outpoint.get_txid().to_hex())?; - let nonce = alloc_c_string(&blinding_nonce.to_hex())?; - let entropy_hex = alloc_c_string(&entropy.to_hex())?; - let address = alloc_c_string(send_address)?; - let empty_str = alloc_c_string("")?; let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let tx_result = { + let output = self.set_reissuance_internal(&handle, &tx_handle, outpoint, data)?; + self.tx_data = self.get_all_data_internal(&handle, &tx_handle, &String::default())?; + self.get_tx_internal(&handle, &tx_handle, &String::default())?; + Ok(output) + }; + tx_handle.free_handle(&handle); + tx_result + }; + handle.free_handle(); + result + } + + pub fn set_reissuance_internal( + &mut self, + handle: &ErrorHandle, + tx_handle: &TxDataHandle, + outpoint: &OutPoint, + data: &ReissuanceInputData, + ) -> Result { + let asset_addr_str = match &data.asset_address { + InputAddress::Addr(address) => address.to_str().to_string(), + InputAddress::CtAddr(address) => address.to_str().to_string(), + }; + let txid = alloc_c_string(&outpoint.get_txid().to_hex())?; + let nonce = alloc_c_string(&data.blinding_nonce.to_hex())?; + let entropy_hex = alloc_c_string(&data.entropy.to_hex())?; + let address = alloc_c_string(&asset_addr_str)?; + let script = alloc_c_string(&data.asset_locking_script.to_hex())?; let mut asset: *mut c_char = ptr::null_mut(); - let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { - CfdSetRawReissueAsset( + CfdSetReissueAsset( handle.as_handle(), - tx_str.as_ptr(), + tx_handle.as_handle(), txid.as_ptr(), outpoint.get_vout(), - asset_amount, + data.asset_amount, nonce.as_ptr(), entropy_hex.as_ptr(), address.as_ptr(), - empty_str.as_ptr(), + script.as_ptr(), &mut asset, - &mut output, ) }; - let result = match error_code { + match error_code { 0 => { - let str_list = unsafe { collect_multi_cstring_and_free(&[output, asset]) }?; - self.last_tx = str_list[0].clone(); - let asset_obj = ConfidentialAsset::from_str(&str_list[1])?; - ConfidentialTxOutData::from_str(send_address, &asset_obj, asset_amount) + let asset_str = unsafe { collect_cstring_and_free(asset) }?; + let asset_obj = ConfidentialAsset::from_str(&asset_str)?; + ConfidentialTxOutData::from_str(&asset_addr_str, &asset_obj, data.asset_amount) } _ => Err(handle.get_error(error_code)), + } + } + + pub fn add_pegin_input( + &mut self, + tx: &str, + outpoint: &OutPoint, + data: &PeginInputData, + ) -> Result, CfdError> { + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let tx_result = { + self.add_pegin_input_internal(&handle, &tx_handle, outpoint, data)?; + self.tx_data = self.get_all_data_internal(&handle, &tx_handle, &String::default())?; + self.get_tx_internal(&handle, &tx_handle, &String::default()) + }; + tx_handle.free_handle(&handle); + tx_result + }; + handle.free_handle(); + result + } + + pub fn add_pegin_input_internal( + &mut self, + handle: &ErrorHandle, + tx_handle: &TxDataHandle, + outpoint: &OutPoint, + data: &PeginInputData, + ) -> Result<(), CfdError> { + let txid = alloc_c_string(&outpoint.get_txid().to_hex())?; + let asset = alloc_c_string(&data.asset.to_hex())?; + let block_hash = alloc_c_string(&data.mainchain_genesis_block_hash.to_hex())?; + let script = alloc_c_string(&data.claim_script.to_hex())?; + let tx = alloc_c_string(&data.transaction.to_str())?; + let txout_proof = alloc_c_string(&data.txout_proof.to_hex())?; + let error_code = unsafe { + CfdAddTxPeginInput( + handle.as_handle(), + tx_handle.as_handle(), + txid.as_ptr(), + outpoint.get_vout(), + data.amount, + asset.as_ptr(), + block_hash.as_ptr(), + script.as_ptr(), + tx.as_ptr(), + txout_proof.as_ptr(), + ) + }; + match error_code { + 0 => Ok(()), + _ => Err(handle.get_error(error_code)), + } + } + + pub fn add_pegout_output( + &mut self, + tx: &str, + data: &PegoutInputData, + ) -> Result<(Vec, Address), CfdError> { + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let tx_result = { + let addr = self.add_pegout_output_internal(&handle, &tx_handle, data)?; + self.tx_data = self.get_all_data_internal(&handle, &tx_handle, &String::default())?; + let tx_bytes = self.get_tx_internal(&handle, &tx_handle, &String::default())?; + Ok((tx_bytes, addr)) + }; + tx_handle.free_handle(&handle); + tx_result }; handle.free_handle(); result } + pub fn add_pegout_output_internal( + &mut self, + handle: &ErrorHandle, + tx_handle: &TxDataHandle, + data: &PegoutInputData, + ) -> Result { + let online_pubkey_obj = data.online_privkey.get_pubkey()?; + let asset = alloc_c_string(&data.asset.to_hex())?; + let online_key = alloc_c_string(&data.online_privkey.to_hex())?; + let online_pubkey = alloc_c_string(&online_pubkey_obj.to_hex())?; + let genesis_block_hash = alloc_c_string(&data.mainchain_genesis_block_hash.to_hex())?; + let descriptor = alloc_c_string(&data.offline_output_descriptor)?; + let whitelist = alloc_c_string(&data.whitelist.to_hex())?; + let mut address: *mut c_char = ptr::null_mut(); + let error_code = unsafe { + CfdAddTxPegoutOutput( + handle.as_handle(), + tx_handle.as_handle(), + asset.as_ptr(), + data.amount, + data.mainchain_network_type.to_c_value(), + data.elements_network_type.to_c_value(), + genesis_block_hash.as_ptr(), + online_pubkey.as_ptr(), + online_key.as_ptr(), + descriptor.as_ptr(), + data.bip32_counter, + whitelist.as_ptr(), + &mut address, + ) + }; + match error_code { + 0 => { + let addr_str = unsafe { collect_cstring_and_free(address) }?; + Address::from_str(&addr_str) + } + _ => Err(handle.get_error(error_code)), + } + } + pub fn create( &mut self, version: u32, @@ -3209,6 +3678,66 @@ impl ConfidentialTxOperation { self.create_tx(0, 0, tx, txin_list, txout_list) } + pub fn update_witness_stack( + &mut self, + tx: &str, + outpoint: &OutPoint, + stack_index: u32, + data: &ByteData, + ) -> Result, CfdError> { + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let tx_result = { + TransactionOperation::update_witness_stack_internal( + &CreateTxData::new(&self.network), + &handle, + &tx_handle, + 0, + outpoint, + stack_index, + data, + )?; + self.get_txin_by_outpoint_internal(&handle, &tx_handle, &String::default(), outpoint)?; + self.get_tx_internal(&handle, &tx_handle, &String::default()) + }?; + tx_handle.free_handle(&handle); + tx_result + }; + handle.free_handle(); + Ok(result) + } + + pub fn update_pegin_witness_stack( + &mut self, + tx: &str, + outpoint: &OutPoint, + stack_index: u32, + data: &ByteData, + ) -> Result, CfdError> { + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let tx_result = { + TransactionOperation::update_witness_stack_internal( + &CreateTxData::new(&self.network), + &handle, + &tx_handle, + 1, + outpoint, + stack_index, + data, + )?; + self.get_txin_by_outpoint_internal(&handle, &tx_handle, &String::default(), outpoint)?; + self.get_tx_internal(&handle, &tx_handle, &String::default()) + }?; + tx_handle.free_handle(&handle); + tx_result + }; + handle.free_handle(); + Ok(result) + } + pub fn update_output_amount( &mut self, tx: &str, @@ -3287,24 +3816,39 @@ impl ConfidentialTxOperation { result } + pub fn split_txout( + &mut self, + tx: &str, + index: u32, + txout_list: &[ConfidentialTxOutData], + ) -> Result, CfdError> { + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let split_result = { + TransactionOperation::split_txout_internal( + &self.network, + &handle, + &tx_handle, + &String::default(), + index, + &txout_list, + )?; + self.tx_data = self.get_all_data_internal(&handle, &tx_handle, &String::default())?; + self.get_tx_internal(&handle, &tx_handle, &String::default()) + }?; + tx_handle.free_handle(&handle); + split_result + }; + handle.free_handle(); + Ok(result) + } + pub fn get_all_data(&mut self, tx: &str) -> Result { let mut handle = ErrorHandle::new()?; let result = { let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; - let tx_result = { - let data = self.get_tx_data_internal(&handle, &tx_handle, tx)?; - let in_count = - TransactionOperation::get_count_internal(&self.network, &handle, &tx_handle, tx, true)?; - let out_count = - TransactionOperation::get_count_internal(&self.network, &handle, &tx_handle, tx, false)?; - let in_indexes = ConfidentialTxOperation::create_index_list(in_count); - let out_indexes = ConfidentialTxOperation::create_index_list(out_count); - let in_data = self.get_tx_input_list_internal(&handle, &tx_handle, tx, &in_indexes)?; - let out_data = self.get_tx_output_list_internal(&handle, &tx_handle, tx, &out_indexes)?; - self.txin_list = in_data; - self.txout_list = out_data; - Ok(data) - }; + let tx_result = self.get_all_data_internal(&handle, &tx_handle, &String::default()); tx_handle.free_handle(&handle); tx_result }; @@ -3312,6 +3856,36 @@ impl ConfidentialTxOperation { result } + fn get_all_data_internal( + &mut self, + handle: &ErrorHandle, + tx_handle: &TxDataHandle, + tx: &str, + ) -> Result { + let tx_data_handle = match tx_handle.is_null() { + false => tx_handle.clone(), + _ => TxDataHandle::new(&handle, &self.network, tx)?, + }; + let result = { + let data = self.get_tx_data_internal(&handle, &tx_handle, tx)?; + let in_count = + TransactionOperation::get_count_internal(&self.network, &handle, &tx_handle, tx, true)?; + let out_count = + TransactionOperation::get_count_internal(&self.network, &handle, &tx_handle, tx, false)?; + let in_indexes = ConfidentialTxOperation::create_index_list(in_count); + let out_indexes = ConfidentialTxOperation::create_index_list(out_count); + let in_data = self.get_tx_input_list_internal(&handle, &tx_handle, tx, &in_indexes)?; + let out_data = self.get_tx_output_list_internal(&handle, &tx_handle, tx, &out_indexes)?; + self.txin_list = in_data; + self.txout_list = out_data; + Ok(data) + }; + if tx_handle.is_null() { + tx_data_handle.free_handle(&handle); + } + result + } + fn create_index_list(index_count: u32) -> Vec { let mut indexes: Vec = vec![]; if index_count == 0 { @@ -3333,6 +3907,36 @@ impl ConfidentialTxOperation { &self.txout_list } + fn get_tx_internal( + &mut self, + handle: &ErrorHandle, + tx_handle: &TxDataHandle, + tx: &str, + ) -> Result, CfdError> { + let tx_data_handle = match tx_handle.is_null() { + false => tx_handle.clone(), + _ => TxDataHandle::new(&handle, &self.network, tx)?, + }; + let mut output: *mut c_char = ptr::null_mut(); + let result = { + let error_code = unsafe { + CfdFinalizeTransaction(handle.as_handle(), tx_data_handle.as_handle(), &mut output) + }; + match error_code { + 0 => { + let output_obj = unsafe { collect_cstring_and_free(output) }?; + self.last_tx = output_obj; + Ok(byte_from_hex_unsafe(&self.last_tx)) + } + _ => Err(handle.get_error(error_code)), + } + }; + if tx_handle.is_null() { + tx_data_handle.free_handle(&handle); + } + result + } + pub fn get_tx_data(&self, tx: &str) -> Result { let mut handle = ErrorHandle::new()?; let result = self.get_tx_data_internal(&handle, &TxDataHandle::empty(), tx); @@ -3433,6 +4037,54 @@ impl ConfidentialTxOperation { result } + pub fn get_txin_by_outpoint_internal( + &mut self, + handle: &ErrorHandle, + tx_handle: &TxDataHandle, + tx: &str, + outpoint: &OutPoint, + ) -> Result { + let tx_data_handle = match tx_handle.is_null() { + false => tx_handle.clone(), + _ => TxDataHandle::new(&handle, &self.network, tx)?, + }; + let list_result = { + let index = { + let mut index: c_uint = 0; + let txid = alloc_c_string(&outpoint.get_txid().to_hex())?; + let error_code = unsafe { + CfdGetTxInIndexByHandle( + handle.as_handle(), + tx_data_handle.as_handle(), + txid.as_ptr(), + outpoint.get_vout(), + &mut index, + ) + }; + match error_code { + 0 => Ok(index), + _ => Err(handle.get_error(error_code)), + } + }?; + + let indexes = vec![index]; + let list_result = self.get_tx_input_list_internal(&handle, &tx_data_handle, tx, &indexes)?; + let data_result = self.get_tx_data_internal(&handle, &tx_data_handle, tx)?; + self.tx_data = data_result; + if list_result.is_empty() { + Err(CfdError::Internal("Failed to empty list.".to_string())) + } else { + self.last_txin_index = index; + self.txin_list = vec![list_result[0].clone()]; + Ok(list_result[0].clone()) + } + }; + if tx_handle.is_null() { + tx_data_handle.free_handle(&handle); + } + list_result + } + pub fn get_tx_input_list_internal( &self, handle: &ErrorHandle, diff --git a/src/descriptor.rs b/src/descriptor.rs index 99faa0b..6dbb9d6 100644 --- a/src/descriptor.rs +++ b/src/descriptor.rs @@ -9,7 +9,8 @@ use crate::common::{ }; use crate::hdwallet::{ExtPrivkey, ExtPubkey}; use crate::key::Pubkey; -use crate::script::Script; +use crate::schnorr::SchnorrPubkey; +use crate::script::{Script, TapBranch}; use std::fmt; use std::ptr; use std::result::Result::{Err, Ok}; @@ -17,7 +18,7 @@ use std::str::FromStr; use self::cfd_sys::{ CfdFreeDescriptorHandle, CfdGetDescriptorChecksum, CfdGetDescriptorData, - CfdGetDescriptorMultisigKey, CfdParseDescriptor, + CfdGetDescriptorMultisigKey, CfdGetDescriptorRootData, CfdParseDescriptor, }; /// The script type on descriptor. @@ -45,6 +46,10 @@ pub enum DescriptorScriptType { Addr, /// ScriptType: raw Raw, + /// ScriptType: miniscript (internal) + MiniScript, + /// ScriptType: taproot + Taproot, } impl DescriptorScriptType { @@ -60,6 +65,8 @@ impl DescriptorScriptType { 8 => DescriptorScriptType::SortedMulti, 9 => DescriptorScriptType::Addr, 10 => DescriptorScriptType::Raw, + 11 => DescriptorScriptType::MiniScript, + 12 => DescriptorScriptType::Taproot, _ => DescriptorScriptType::Null, } } @@ -79,6 +86,8 @@ impl fmt::Display for DescriptorScriptType { DescriptorScriptType::SortedMulti => write!(f, "type:sorted-multi"), DescriptorScriptType::Addr => write!(f, "type:address"), DescriptorScriptType::Raw => write!(f, "type:raw"), + DescriptorScriptType::MiniScript => write!(f, "type:miniscript"), + DescriptorScriptType::Taproot => write!(f, "type:taproot"), } } } @@ -94,6 +103,8 @@ pub enum DescriptorKeyType { Bip32, /// key type: bip32 ext privkey Bip32Priv, + /// key type: schnorr pubkey + Schnorr, } impl DescriptorKeyType { @@ -102,6 +113,7 @@ impl DescriptorKeyType { 1 => DescriptorKeyType::Public, 2 => DescriptorKeyType::Bip32, 3 => DescriptorKeyType::Bip32Priv, + 4 => DescriptorKeyType::Schnorr, _ => DescriptorKeyType::Null, } } @@ -114,6 +126,7 @@ impl fmt::Display for DescriptorKeyType { DescriptorKeyType::Public => write!(f, "keyType:public"), DescriptorKeyType::Bip32 => write!(f, "keyType:bip32"), DescriptorKeyType::Bip32Priv => write!(f, "keyType:bip32-priv"), + DescriptorKeyType::Schnorr => write!(f, "keyType:schnorr"), } } } @@ -125,6 +138,7 @@ pub struct KeyData { pubkey: Pubkey, ext_pubkey: ExtPubkey, ext_privkey: ExtPrivkey, + schnorr_pubkey: SchnorrPubkey, } impl KeyData { @@ -135,6 +149,7 @@ impl KeyData { pubkey: pubkey.clone(), ext_pubkey: ExtPubkey::default(), ext_privkey: ExtPrivkey::default(), + schnorr_pubkey: SchnorrPubkey::default(), } } else { KeyData::default() @@ -148,6 +163,7 @@ impl KeyData { pubkey: Pubkey::default(), ext_pubkey: ext_pubkey.clone(), ext_privkey: ExtPrivkey::default(), + schnorr_pubkey: SchnorrPubkey::default(), } } else { KeyData::default() @@ -161,6 +177,20 @@ impl KeyData { pubkey: Pubkey::default(), ext_pubkey: ExtPubkey::default(), ext_privkey: ext_privkey.clone(), + schnorr_pubkey: SchnorrPubkey::default(), + }, + _ => KeyData::default(), + } + } + + pub fn from_schnorr_pubkey(schnorr_pubkey: &SchnorrPubkey) -> KeyData { + match schnorr_pubkey.valid() { + true => KeyData { + key_type: DescriptorKeyType::Schnorr, + pubkey: Pubkey::default(), + ext_pubkey: ExtPubkey::default(), + ext_privkey: ExtPrivkey::default(), + schnorr_pubkey: schnorr_pubkey.clone(), }, _ => KeyData::default(), } @@ -171,6 +201,7 @@ impl KeyData { DescriptorKeyType::Public => self.pubkey.to_hex(), DescriptorKeyType::Bip32 => self.ext_pubkey.to_str().to_string(), DescriptorKeyType::Bip32Priv => self.ext_privkey.to_str().to_string(), + DescriptorKeyType::Schnorr => self.schnorr_pubkey.to_hex(), _ => String::default(), } } @@ -190,6 +221,10 @@ impl KeyData { pub fn get_ext_privkey(&self) -> &ExtPrivkey { &self.ext_privkey } + + pub fn get_schnorr_pubkey(&self) -> &SchnorrPubkey { + &self.schnorr_pubkey + } } impl fmt::Display for KeyData { @@ -205,6 +240,7 @@ impl Default for KeyData { pubkey: Pubkey::default(), ext_pubkey: ExtPubkey::default(), ext_privkey: ExtPrivkey::default(), + schnorr_pubkey: SchnorrPubkey::default(), } } } @@ -220,6 +256,7 @@ pub struct DescriptorScriptData { key_data: KeyData, multisig_key_list: Vec, multisig_require_num: u8, + script_tree: TapBranch, } impl DescriptorScriptData { @@ -291,8 +328,7 @@ impl DescriptorScriptData { address, redeem_script, key_data, - multisig_key_list: vec![], - multisig_require_num: 0, + ..DescriptorScriptData::default() } } @@ -311,9 +347,26 @@ impl DescriptorScriptData { hash_type, address, redeem_script, - key_data: KeyData::default(), multisig_key_list: multisig_key_list.to_vec(), multisig_require_num, + ..DescriptorScriptData::default() + } + } + + pub fn by_taproot( + script_type: DescriptorScriptType, + hash_type: HashType, + address: Address, + key_data: KeyData, + script_tree: TapBranch, + ) -> DescriptorScriptData { + DescriptorScriptData { + script_type, + hash_type, + address, + key_data, + script_tree, + ..DescriptorScriptData::default() } } @@ -347,6 +400,10 @@ impl DescriptorScriptData { pub fn get_multisig_require_num(&self) -> u8 { self.multisig_require_num } + + pub fn get_script_tree(&self) -> &TapBranch { + &self.script_tree + } } impl fmt::Display for DescriptorScriptData { @@ -366,6 +423,7 @@ impl Default for DescriptorScriptData { key_data: KeyData::default(), multisig_key_list: vec![], multisig_require_num: 0, + script_tree: TapBranch::default(), } } } @@ -680,7 +738,105 @@ impl Descriptor { Err(CfdError::Unknown("Failed to parse_descriptor.".to_string())); let mut list: Vec = vec![]; list.reserve(max_num as usize); + let mut key_list_obj: Vec = vec![]; let mut index = 0; + let root_data = { + let mut script_type: c_int = 0; + let mut key_type: c_int = 0; + let mut hash_type: c_int = 0; + let mut locking_script: *mut c_char = ptr::null_mut(); + let mut address: *mut c_char = ptr::null_mut(); + let mut pubkey: *mut c_char = ptr::null_mut(); + let mut redeem_script: *mut c_char = ptr::null_mut(); + let mut ext_pubkey: *mut c_char = ptr::null_mut(); + let mut ext_privkey: *mut c_char = ptr::null_mut(); + let mut schnorr_pubkey: *mut c_char = ptr::null_mut(); + let mut tree_string: *mut c_char = ptr::null_mut(); + let mut is_multisig: bool = false; + let mut max_key_num: c_uint = 0; + let mut req_sig_num: c_uint = 0; + let error_code = unsafe { + CfdGetDescriptorRootData( + handle.as_handle(), + descriptor_handle, + &mut script_type, + &mut locking_script, + &mut address, + &mut hash_type, + &mut redeem_script, + &mut key_type, + &mut pubkey, + &mut ext_pubkey, + &mut ext_privkey, + &mut schnorr_pubkey, + &mut tree_string, + &mut is_multisig, + &mut max_key_num, + &mut req_sig_num, + ) + }; + if error_code != 0 { + Err(handle.get_error(error_code)) + } else { + let str_list = unsafe { + collect_multi_cstring_and_free(&[ + locking_script, + address, + pubkey, + redeem_script, + ext_pubkey, + ext_privkey, + schnorr_pubkey, + tree_string, + ]) + }?; + let addr_str = &str_list[1]; + let pubkey_obj = &str_list[2]; + let script_str = &str_list[3]; + let ext_pubkey_obj = &str_list[4]; + let ext_privkey_obj = &str_list[5]; + let schnorr_pubkey_obj = &str_list[6]; + let tree_string_obj = &str_list[7]; + let addr = match addr_str.is_empty() { + false => Address::from_string(&addr_str)?, + _ => Address::default(), + }; + let script = match script_str.is_empty() { + false => Script::from_hex(&script_str)?, + _ => Script::default(), + }; + let script_tree = match tree_string_obj.is_empty() { + false => TapBranch::from_string(&tree_string_obj)?, + _ => TapBranch::default(), + }; + if is_multisig { + key_list_obj = + Descriptor::collect_multisig_data(&handle, descriptor_handle, max_key_num)?; + } + let key_type_obj = DescriptorKeyType::from_c_value(key_type); + let key_data = match key_type_obj { + DescriptorKeyType::Null => KeyData::default(), + _ => Descriptor::collect_key_data( + key_type, + &pubkey_obj, + &ext_pubkey_obj, + &ext_privkey_obj, + &schnorr_pubkey_obj, + )?, + }; + Ok(DescriptorScriptData { + script_type: DescriptorScriptType::from_c_value(script_type), + depth: 0, + hash_type: HashType::from_c_value(hash_type), + address: addr, + redeem_script: script, + key_data, + multisig_key_list: key_list_obj.clone(), + multisig_require_num: req_sig_num as u8, + script_tree, + }) + } + }?; while index <= max_num { let mut max_index: c_uint = 0; @@ -755,6 +911,7 @@ impl Descriptor { &pubkey_obj, &ext_pubkey_obj, &ext_privkey_obj, + "", )?; Ok(DescriptorScriptData::from_pubkey( type_obj, @@ -769,8 +926,10 @@ impl Descriptor { | DescriptorScriptType::Multi | DescriptorScriptType::SortedMulti => { if is_multisig { - let key_list_obj = - Descriptor::collect_multisig_data(&handle, descriptor_handle, max_key_num)?; + if key_list_obj.is_empty() { + key_list_obj = + Descriptor::collect_multisig_data(&handle, descriptor_handle, max_key_num)?; + } Ok(DescriptorScriptData::from_multisig( type_obj, depth, @@ -815,6 +974,7 @@ impl Descriptor { &pubkey_obj, &ext_pubkey_obj, &ext_privkey_obj, + "", )?; Ok(DescriptorScriptData::from_pubkey( type_obj, @@ -832,7 +992,11 @@ impl Descriptor { index += 1; } if list.len() == ((max_num + 1) as usize) { - result = Ok(Descriptor::analyze_list(descriptor, &list)); + result = Ok(Descriptor { + descriptor: descriptor.to_string(), + script_list: list, + root_data, + }); } result }; @@ -867,6 +1031,10 @@ impl Descriptor { &self.root_data.address } + pub fn get_script_tree(&self) -> &TapBranch { + &self.root_data.script_tree + } + /// Exist script-hash. /// /// # Example @@ -933,6 +1101,10 @@ impl Descriptor { } } + pub fn has_taproot(&self) -> bool { + self.root_data.script_type == DescriptorScriptType::Taproot + } + pub fn get_key_data(&self) -> Result<&KeyData, CfdError> { match self.root_data.key_data.key_type { DescriptorKeyType::Null => Err(CfdError::IllegalState("Not exist key data.".to_string())), @@ -996,6 +1168,7 @@ impl Descriptor { pubkey: &str, ext_pubkey: &str, ext_privkey: &str, + schnorr_pubkey: &str, ) -> Result { let key_type_obj = DescriptorKeyType::from_c_value(key_type); match key_type_obj { @@ -1011,6 +1184,13 @@ impl Descriptor { let ext_key_obj = ExtPrivkey::from_str(ext_privkey)?; Ok(KeyData::from_ext_privkey(&ext_key_obj)) } + DescriptorKeyType::Schnorr => { + let key_obj = match schnorr_pubkey.is_empty() { + true => SchnorrPubkey::from_str(pubkey)?, + _ => SchnorrPubkey::from_str(schnorr_pubkey)?, + }; + Ok(KeyData::from_schnorr_pubkey(&key_obj)) + } _ => Err(CfdError::Internal("invalid key type status.".to_string())), } } @@ -1053,7 +1233,7 @@ impl Descriptor { let pubkey_obj = &str_list[0]; let ext_pubkey_obj = &str_list[1]; let ext_privkey_obj = &str_list[2]; - Descriptor::collect_key_data(key_type, pubkey_obj, ext_pubkey_obj, ext_privkey_obj) + Descriptor::collect_key_data(key_type, pubkey_obj, ext_pubkey_obj, ext_privkey_obj, "") } }?; list.push(key_data); @@ -1064,85 +1244,6 @@ impl Descriptor { } result } - - fn analyze_list(descriptor: &str, script_list: &[DescriptorScriptData]) -> Descriptor { - let mut desc = Descriptor { - descriptor: descriptor.to_string(), - script_list: script_list.to_vec(), - root_data: DescriptorScriptData::default(), - }; - let first = &script_list[0]; - match first.hash_type { - HashType::P2sh | HashType::P2wsh => { - if (script_list.len() > 1) && (script_list[1].hash_type == HashType::P2pkh) { - desc.root_data = DescriptorScriptData::from_key_and_script( - first.script_type, - first.depth, - first.hash_type, - first.address.clone(), - first.redeem_script.clone(), - script_list[1].key_data.clone(), - ); - return desc; - } - } - HashType::P2shP2wsh => { - if (script_list.len() > 2) && (script_list[2].hash_type == HashType::P2pkh) { - desc.root_data = DescriptorScriptData::from_key_and_script( - first.script_type, - first.depth, - first.hash_type, - first.address.clone(), - script_list[1].redeem_script.clone(), - script_list[2].key_data.clone(), - ); - return desc; - } - } - _ => {} - } - - if script_list.len() == 1 { - desc.root_data = first.clone(); - return desc; - } - - let second = &script_list[1]; - match first.hash_type { - HashType::P2shP2wsh => { - if second.multisig_require_num > 0 { - desc.root_data = DescriptorScriptData::from_multisig( - first.script_type, - first.depth, - first.hash_type, - first.address.clone(), - second.redeem_script.clone(), - &second.multisig_key_list.clone().to_vec(), - second.multisig_require_num, - ); - } else { - desc.root_data = DescriptorScriptData::from_script( - first.script_type, - first.depth, - first.hash_type, - first.address.clone(), - second.redeem_script.clone(), - ); - } - } - HashType::P2shP2wpkh => { - desc.root_data = DescriptorScriptData::from_pubkey( - first.script_type, - first.depth, - first.hash_type, - first.address.clone(), - second.key_data.clone(), - ); - } - _ => desc.root_data = first.clone(), - } - desc - } } impl fmt::Display for Descriptor { diff --git a/src/key.rs b/src/key.rs index dab46fc..310b981 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1031,6 +1031,18 @@ pub enum SigHashType { NonePlusAnyoneCanPay, /// SigHashType::Single + AnyoneCanPay SinglePlusAnyoneCanPay, + /// SigHashType::All + Rangeproof + AllPlusRangeproof, + /// SigHashType::None + Rangeproof + NonePlusRangeproof, + /// SigHashType::Single + Rangeproof + SinglePlusRangeproof, + /// SigHashType::All + AnyoneCanPay + Rangeproof + AllPlusAnyoneCanPayRangeproof, + /// SigHashType::None + AnyoneCanPay + Rangeproof + NonePlusAnyoneCanPayRangeproof, + /// SigHashType::Single + AnyoneCanPay + Rangeproof + SinglePlusAnyoneCanPayRangeproof, } impl SigHashType { @@ -1044,29 +1056,70 @@ impl SigHashType { /// /// ``` /// use cfd_rust::SigHashType; - /// let all_anyone_can_pay = SigHashType::new(&SigHashType::All, true); - /// ``` - #[inline] - pub fn new(sighash_type: &SigHashType, anyone_can_pay: bool) -> SigHashType { - if anyone_can_pay { - match sighash_type { - SigHashType::All | SigHashType::AllPlusAnyoneCanPay => SigHashType::AllPlusAnyoneCanPay, - SigHashType::None | SigHashType::NonePlusAnyoneCanPay => SigHashType::NonePlusAnyoneCanPay, - SigHashType::Single | SigHashType::SinglePlusAnyoneCanPay => { + /// let all_anyone_can_pay = SigHashType::new(&SigHashType::All, true, false); + /// ``` + pub fn new(sighash_type: &SigHashType, anyone_can_pay: bool, rangeproof: bool) -> SigHashType { + let base_type = sighash_type.get_base_type(); + let is_anyone_can_pay = anyone_can_pay || sighash_type.is_anyone_can_pay(); + let is_rangeproof = rangeproof || sighash_type.is_rangeproof(); + match *base_type { + SigHashType::Default => *base_type, + SigHashType::None => { + if is_anyone_can_pay && is_rangeproof { + SigHashType::NonePlusAnyoneCanPayRangeproof + } else if is_anyone_can_pay { + SigHashType::NonePlusAnyoneCanPay + } else if is_rangeproof { + SigHashType::NonePlusRangeproof + } else { + *base_type + } + } + SigHashType::Single => { + if is_anyone_can_pay && is_rangeproof { + SigHashType::SinglePlusAnyoneCanPayRangeproof + } else if is_anyone_can_pay { SigHashType::SinglePlusAnyoneCanPay + } else if is_rangeproof { + SigHashType::SinglePlusRangeproof + } else { + *base_type } - SigHashType::Default => SigHashType::Default, } - } else { - match sighash_type { - SigHashType::All | SigHashType::AllPlusAnyoneCanPay => SigHashType::All, - SigHashType::None | SigHashType::NonePlusAnyoneCanPay => SigHashType::None, - SigHashType::Single | SigHashType::SinglePlusAnyoneCanPay => SigHashType::Single, - SigHashType::Default => SigHashType::Default, + _ => { + // SigHashType::All + if is_anyone_can_pay && is_rangeproof { + SigHashType::AllPlusAnyoneCanPayRangeproof + } else if is_anyone_can_pay { + SigHashType::AllPlusAnyoneCanPay + } else if is_rangeproof { + SigHashType::AllPlusRangeproof + } else { + *base_type + } } } } + /// Get a base type. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::SigHashType; + /// let hash_type = SigHashType::NonePlusAnyoneCanPay; + /// let base_type = hash_type.get_base_type(); + /// ``` + pub fn get_base_type(&self) -> &SigHashType { + match self.to_c_value() & 0x0f { + 0 => &SigHashType::Default, + 1 => &SigHashType::All, + 2 => &SigHashType::None, + 3 => &SigHashType::Single, + _ => &SigHashType::All, + } + } + /// Get an anyone can pay flag. /// /// # Example @@ -1076,46 +1129,70 @@ impl SigHashType { /// let hash_type = SigHashType::NonePlusAnyoneCanPay; /// let anyone_can_pay = hash_type.is_anyone_can_pay(); /// ``` - #[inline] pub fn is_anyone_can_pay(&self) -> bool { - match self { - SigHashType::AllPlusAnyoneCanPay - | SigHashType::NonePlusAnyoneCanPay - | SigHashType::SinglePlusAnyoneCanPay => true, - SigHashType::All | SigHashType::None | SigHashType::Single | SigHashType::Default => false, - } + !matches!(self.to_c_value() & 0x80, 0) + } + + /// Get a rangeproof flag. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::SigHashType; + /// let hash_type = SigHashType::NonePlusRangeproof; + /// let rangeproof = hash_type.is_rangeproof(); + /// ``` + pub fn is_rangeproof(&self) -> bool { + !matches!(self.to_c_value() & 0x40, 0) } pub(in crate) fn from_c_value(sighash_type: c_int) -> SigHashType { - match sighash_type { + let base_type = match sighash_type & 0x0f { 0 => SigHashType::Default, 1 => SigHashType::All, 2 => SigHashType::None, 3 => SigHashType::Single, _ => SigHashType::All, - } + }; + let anyone_can_pay = !matches!(sighash_type & 0x80, 0); + let is_rangeproof = !matches!(sighash_type & 0x40, 0); + SigHashType::new(&base_type, anyone_can_pay, is_rangeproof) } pub(in crate) fn to_c_value(&self) -> c_int { match self { SigHashType::Default => 0, - SigHashType::All | SigHashType::AllPlusAnyoneCanPay => 1, - SigHashType::None | SigHashType::NonePlusAnyoneCanPay => 2, - SigHashType::Single | SigHashType::SinglePlusAnyoneCanPay => 3, + SigHashType::All => 1, + SigHashType::None => 2, + SigHashType::Single => 3, + SigHashType::AllPlusAnyoneCanPay => 0x81, + SigHashType::NonePlusAnyoneCanPay => 0x82, + SigHashType::SinglePlusAnyoneCanPay => 0x83, + SigHashType::AllPlusRangeproof => 0x41, + SigHashType::NonePlusRangeproof => 0x42, + SigHashType::SinglePlusRangeproof => 0x43, + SigHashType::AllPlusAnyoneCanPayRangeproof => 0xc1, + SigHashType::NonePlusAnyoneCanPayRangeproof => 0xc2, + SigHashType::SinglePlusAnyoneCanPayRangeproof => 0xc3, } } } impl fmt::Display for SigHashType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let _ = match *self { + let base_type = self.get_base_type(); + let _ = match base_type { SigHashType::Default => write!(f, "sighashType:Default"), - SigHashType::All | SigHashType::AllPlusAnyoneCanPay => write!(f, "sighashType:All"), - SigHashType::None | SigHashType::NonePlusAnyoneCanPay => write!(f, "sighashType:None"), - SigHashType::Single | SigHashType::SinglePlusAnyoneCanPay => write!(f, "sighashType:Single"), + SigHashType::None => write!(f, "sighashType:None"), + SigHashType::Single => write!(f, "sighashType:Single"), + _ => write!(f, "sighashType:All"), }?; match self.is_anyone_can_pay() { - true => write!(f, ", anyoneCanPay"), + true => write!(f, "|anyoneCanPay"), + _ => Ok(()), + }?; + match self.is_rangeproof() { + true => write!(f, "|rangeproof"), _ => Ok(()), } } @@ -1381,7 +1458,11 @@ impl SignParameter { let der_decoded = unsafe { collect_cstring_and_free(signature) }?; let sign_param = SignParameter::from_vec(byte_from_hex_unsafe(&der_decoded)); let sighash_type = SigHashType::from_c_value(sighash_type_value); - Ok(sign_param.set_signature_hash(&SigHashType::new(&sighash_type, is_anyone_can_pay))) + Ok(sign_param.set_signature_hash(&SigHashType::new( + &sighash_type, + is_anyone_can_pay, + false, + ))) } _ => Err(handle.get_error(error_code)), }; diff --git a/src/lib.rs b/src/lib.rs index 0006ffd..e53873a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,8 +43,13 @@ pub use confidential_transaction::ElementsUtxoData; pub use confidential_transaction::ElementsUtxoOptionData; pub use confidential_transaction::InputAddress; pub use confidential_transaction::Issuance; +pub use confidential_transaction::IssuanceInputData; pub use confidential_transaction::IssuanceKeyMap; +pub use confidential_transaction::IssuanceOutputData; pub use confidential_transaction::KeyIndexMap; +pub use confidential_transaction::PeginInputData; +pub use confidential_transaction::PegoutInputData; +pub use confidential_transaction::ReissuanceInputData; pub use confidential_transaction::BLIND_FACTOR_SIZE; pub use confidential_transaction::COMMITMENT_SIZE; pub use descriptor::Descriptor; @@ -86,6 +91,7 @@ pub use script::TapBranch; pub use script::TAPROOT_HASH_SIZE; pub use script::TAPSCRIPT_LEAF_VERSION; +pub use transaction::BlockHash; pub use transaction::CoinSelectionData; pub use transaction::FeeData; pub use transaction::FeeOption; diff --git a/src/schnorr.rs b/src/schnorr.rs index f1aff1f..293ac8f 100644 --- a/src/schnorr.rs +++ b/src/schnorr.rs @@ -555,11 +555,9 @@ pub struct SchnorrPubkey { impl SchnorrPubkey { fn from_bytes(data: &[u8]) -> SchnorrPubkey { - let mut nonce = SchnorrPubkey { - data: [0; SCHNORR_NONCE_SIZE], - }; - nonce.data = copy_array_32byte(&data); - nonce + SchnorrPubkey { + data: copy_array_32byte(&data), + } } /// Generate from slice. @@ -827,6 +825,24 @@ impl SchnorrPubkey { handle.free_handle(); result } + + /// Check valid data. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{SchnorrPubkey}; + /// use std::str::FromStr; + /// let key = SchnorrPubkey::from_str("b33cc9edc096d0a83416964bd3c6247b8fecd256e4efa7870d2c854bdeb33390").expect("Fail"); + /// let is_valid = key.valid(); + /// assert_eq!(true, is_valid); + /// assert_eq!(false, SchnorrPubkey::default().valid()); + /// ``` + #[inline] + pub fn valid(&self) -> bool { + let null_value = SchnorrPubkey::default(); + null_value.data != self.data + } } impl fmt::Display for SchnorrPubkey { diff --git a/src/transaction.rs b/src/transaction.rs index be8e6a6..03a0a77 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -8,7 +8,7 @@ use crate::common::{ hex_from_bytes, Amount, ByteData, CfdError, ErrorHandle, Network, ReverseContainer, }; use crate::confidential_address::ConfidentialAddress; -use crate::confidential_transaction::ConfidentialAsset; +use crate::confidential_transaction::{ConfidentialAsset, ConfidentialTxOutData}; use crate::descriptor::Descriptor; use crate::key::{KeyPair, Privkey, Pubkey, SigHashType, SignParameter}; use crate::script::{Script, TAPROOT_HASH_SIZE}; @@ -24,22 +24,24 @@ use std::str::FromStr; use self::cfd_sys::{ CfdAddCoinSelectionAmount, CfdAddCoinSelectionUtxoTemplate, CfdAddMultisigSignData, CfdAddMultisigSignDataToDer, CfdAddPubkeyHashSign, CfdAddScriptHashSign, - CfdAddSignWithPrivkeyByHandle, CfdAddSignWithPrivkeySimple, CfdAddTaprootSignByHandle, - CfdAddTargetAmountForFundRawTx, CfdAddTransactionInput, CfdAddTransactionOutput, - CfdAddTxInTemplateForEstimateFee, CfdAddTxInTemplateForFundRawTx, CfdAddTxSign, - CfdAddTxSignByHandle, CfdAddUtxoTemplateForFundRawTx, CfdCreateSighash, CfdCreateSighashByHandle, - CfdFinalizeCoinSelection, CfdFinalizeEstimateFee, CfdFinalizeFundRawTx, CfdFinalizeMultisigSign, - CfdFinalizeTransaction, CfdFreeCoinSelectionHandle, CfdFreeEstimateFeeHandle, - CfdFreeFundRawTxHandle, CfdFreeMultisigSignHandle, CfdFreeTxDataHandle, + CfdAddSignWithPrivkeyByHandle, CfdAddSignWithPrivkeySimple, CfdAddSplitTxOutData, + CfdAddTaprootSignByHandle, CfdAddTargetAmountForFundRawTx, CfdAddTransactionInput, + CfdAddTransactionOutput, CfdAddTxInTemplateForEstimateFee, CfdAddTxInTemplateForFundRawTx, + CfdAddTxSign, CfdAddTxSignByHandle, CfdAddUtxoTemplateForFundRawTx, CfdCreateSighash, + CfdCreateSighashByHandle, CfdCreateSplitTxOutHandle, CfdFinalizeCoinSelection, + CfdFinalizeEstimateFee, CfdFinalizeFundRawTx, CfdFinalizeMultisigSign, CfdFinalizeTransaction, + CfdFreeCoinSelectionHandle, CfdFreeEstimateFeeHandle, CfdFreeFundRawTxHandle, + CfdFreeMultisigSignHandle, CfdFreeSplitTxOutHandle, CfdFreeTxDataHandle, CfdGetAppendTxOutFundRawTx, CfdGetSelectedCoinIndex, CfdGetTxInByHandle, CfdGetTxInCountByHandle, CfdGetTxInIndex, CfdGetTxInIndexByHandle, CfdGetTxInWitnessByHandle, CfdGetTxInWitnessCountByHandle, CfdGetTxInfoByHandle, CfdGetTxOutByHandle, - CfdGetTxOutCountByHandle, CfdGetTxOutIndexByHandle, CfdInitializeCoinSelection, - CfdInitializeEstimateFee, CfdInitializeFundRawTx, CfdInitializeMultisigSign, - CfdInitializeTransaction, CfdInitializeTxDataHandle, CfdSetOptionFundRawTx, - CfdSetTransactionUtxoData, CfdUpdateTxOutAmount, CfdVerifySignature, CfdVerifyTxSign, - CfdVerifyTxSignByHandle, DEFAULT_BLIND_MINIMUM_BITS, FUND_OPT_DUST_FEE_RATE, - FUND_OPT_KNAPSACK_MIN_CHANGE, FUND_OPT_LONG_TERM_FEE_RATE, WITNESS_STACK_TYPE_NORMAL, + CfdGetTxOutCountByHandle, CfdGetTxOutIndexByHandle, CfdGetTxOutIndexWithOffsetByHandle, + CfdInitializeCoinSelection, CfdInitializeEstimateFee, CfdInitializeFundRawTx, + CfdInitializeMultisigSign, CfdInitializeTransaction, CfdInitializeTxDataHandle, + CfdSetOptionFundRawTx, CfdSetTransactionUtxoData, CfdSplitTxOut, CfdUpdateTxOutAmount, + CfdUpdateWitnessStack, CfdVerifySignature, CfdVerifyTxSign, CfdVerifyTxSignByHandle, + DEFAULT_BLIND_MINIMUM_BITS, FUND_OPT_DUST_FEE_RATE, FUND_OPT_KNAPSACK_MIN_CHANGE, + FUND_OPT_LONG_TERM_FEE_RATE, WITNESS_STACK_TYPE_NORMAL, }; // fund option @@ -116,6 +118,63 @@ impl Default for Txid { } } +/// A container that stores a txid. +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct BlockHash { + hash: ReverseContainer, +} + +impl BlockHash { + /// Generate from slice. + /// + /// # Arguments + /// * `hash` - An unsigned 8bit slice that holds the block hash. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::BlockHash; + /// let bytes = [2; 32]; + /// let data = BlockHash::from_slice(&bytes); + /// ``` + pub fn from_slice(hash: &[u8; TXID_SIZE]) -> BlockHash { + BlockHash { + hash: ReverseContainer::from_slice(hash), + } + } + + #[inline] + pub fn to_slice(&self) -> &[u8; TXID_SIZE] { + self.hash.to_slice() + } + + pub fn to_hex(&self) -> String { + self.hash.to_hex() + } +} + +impl FromStr for BlockHash { + type Err = CfdError; + fn from_str(text: &str) -> Result { + let hash = ReverseContainer::from_str(text)?; + Ok(BlockHash { hash }) + } +} + +impl fmt::Display for BlockHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", &self.to_hex()) + } +} + +impl Default for BlockHash { + fn default() -> BlockHash { + BlockHash { + hash: ReverseContainer::default(), + } + } +} + /// A container that stores a txid and vout. #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub struct OutPoint { @@ -701,6 +760,15 @@ impl TxOutData { asset: String::default(), } } + + pub fn from_locking_script(amount: i64, locking_script: &Script) -> TxOutData { + TxOutData { + amount, + address: Address::default(), + locking_script: locking_script.clone(), + asset: String::default(), + } + } } /// A container that stores transaction input. @@ -968,6 +1036,43 @@ impl Transaction { }) } + /// Update witness stack. + /// + /// # Arguments + /// * `outpoint` - An outpoint. + /// * `stack_index` - A witness stack index. + /// * `data` - A witness stack data. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{OutPoint, Transaction, ByteData}; + /// use std::str::FromStr; + /// let outpoint = OutPoint::from_str( + /// "2fea883042440d030ca5929814ead927075a8f52fef5f4720fa3cec2e475d916", + /// 0).expect("Fail"); + /// let data = ByteData::from_str("61f75636003a870b7a1685abae84eedf8c9527227ac70183c376f7b3a35b07ebcbea14749e58ce1a87565b035b2f3963baa5ae3ede95e89fd607ab7849f2087201").expect("Fail"); + /// let tx = Transaction::from_str("0200000000010116d975e4c2cea30f72f4f5fe528f5a0727d9ea149892a50c030d44423088ea2f0000000000ffffffff0130f1029500000000160014164e985d0fc92c927a66c0cbaf78e6ea389629d5014161f75636003a870b7a1685abae84eedf8c9527227ac70183c376f7b3a35b07ebcbea14749e58ce1a87565b035b2f3963baa5ae3ede95e89fd607ab7849f208720200000000").expect("Fail"); + /// let tx2 = tx.update_witness_stack(&outpoint, 0, &data).expect("Fail"); + /// ``` + pub fn update_witness_stack( + &self, + outpoint: &OutPoint, + stack_index: u32, + data: &ByteData, + ) -> Result { + let mut ope = TransactionOperation::new(&Network::Mainnet); + let tx = ope.update_witness_stack(&hex_from_bytes(&self.tx), outpoint, stack_index, data)?; + let data = ope.get_last_tx_data(); + Ok(Transaction { + tx, + data: data.clone(), + txin_list: ope.get_txin_list_cache().to_vec(), + txout_list: self.txout_list.clone(), + txin_utxo_list: self.txin_utxo_list.clone(), + }) + } + /// Update amount. /// /// # Arguments @@ -991,10 +1096,10 @@ impl Transaction { pub fn update_amount(&self, index: u32, amount: i64) -> Result { let mut ope = TransactionOperation::new(&Network::Mainnet); let tx = ope.update_output_amount(&hex_from_bytes(&self.tx), index, amount)?; - let data = ope.get_tx_data(ope.get_last_tx())?; + let data = ope.get_last_tx_data(); let mut tx_obj = Transaction { tx, - data, + data: data.clone(), txin_list: self.txin_list.clone(), txout_list: self.txout_list.clone(), txin_utxo_list: self.txin_utxo_list.clone(), @@ -1003,6 +1108,42 @@ impl Transaction { Ok(tx_obj) } + /// Split txout. + /// + /// # Arguments + /// * `index` - A txout index. + /// * `txout_list` - txout list. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{Address, OutPoint, Transaction, TxInData, TxOutData}; + /// let outpoint = OutPoint::from_str( + /// "0202020202020202020202020202020202020202020202020202020202020202", + /// 1).expect("Fail"); + /// let txin_list = [TxInData::new(&outpoint)]; + /// let amount: i64 = 50000; + /// let addr = Address::from_string("bc1q7jm5vw5cunpy3lkvwdl3sr3qfm794xd4jcdzrv").expect("Fail"); + /// let addr2 = Address::from_string("2MvmzAFKZ5xh44vyb7qY7NB2AoDuS55rVFW").expect("Fail"); + /// let txout_list = [TxOutData::from_address(amount, &addr)]; + /// let split_txout_list = [TxOutData::from_address(10000, &addr2)]; + /// let tx = Transaction::create_tx(2, 0, &txin_list, &txout_list).expect("Fail"); + /// let tx2 = tx.split_txout(0, &split_txout_list).expect("Fail"); + /// ``` + pub fn split_txout(&self, index: u32, txout_list: &[TxOutData]) -> Result { + let mut ope = TransactionOperation::new(&Network::Mainnet); + let tx = ope.split_txout(&hex_from_bytes(&self.tx), index, txout_list)?; + let data = ope.get_last_tx_data(); + let txout_list = ope.get_txout_list_cache(); + Ok(Transaction { + tx, + data: data.clone(), + txin_list: self.txin_list.clone(), + txout_list: txout_list.to_vec(), + txin_utxo_list: self.txin_utxo_list.clone(), + }) + } + pub fn get_txin_index(&self, outpoint: &OutPoint) -> Result { let ope = TransactionOperation::new(&Network::Mainnet); ope.get_txin_index_by_outpoint(&hex_from_bytes(&self.tx), outpoint) @@ -1018,6 +1159,16 @@ impl Transaction { ope.get_txout_index_by_script(&hex_from_bytes(&self.tx), script) } + pub fn get_txout_indexes_by_address(&self, address: &Address) -> Result, CfdError> { + let ope = TransactionOperation::new(&Network::Mainnet); + ope.get_txout_indexes_by_address(&hex_from_bytes(&self.tx), address) + } + + pub fn get_txout_indexes_by_script(&self, script: &Script) -> Result, CfdError> { + let ope = TransactionOperation::new(&Network::Mainnet); + ope.get_txout_indexes_by_script(&hex_from_bytes(&self.tx), script) + } + /// Create signature hash by pubkey. /// /// # Arguments @@ -2207,6 +2358,30 @@ impl HashTypeData { } } +#[derive(Debug, PartialEq, Eq, Clone)] +pub(in crate) struct CreateTxData { + pub tx: String, + pub network: Network, +} + +impl CreateTxData { + pub fn new(network: &Network) -> CreateTxData { + CreateTxData { + tx: String::default(), + network: *network, + } + } +} + +impl Default for CreateTxData { + fn default() -> CreateTxData { + CreateTxData { + tx: String::default(), + network: Network::Mainnet, + } + } +} + /// A container that operating transaction base. #[derive(Debug, PartialEq, Eq, Clone)] pub(in crate) struct TransactionOperation { @@ -2266,6 +2441,74 @@ impl TransactionOperation { self.create_tx(0, 0, tx, txin_list, txout_list) } + pub fn update_witness_stack( + &mut self, + tx: &str, + outpoint: &OutPoint, + stack_index: u32, + data: &ByteData, + ) -> Result, CfdError> { + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let tx_result = { + TransactionOperation::update_witness_stack_internal( + &CreateTxData::default(), + &handle, + &tx_handle, + 0, + outpoint, + stack_index, + data, + )?; + self.get_txin_by_outpoint_internal(&handle, &tx_handle, &String::default(), outpoint)?; + self.get_tx_internal(&handle, &tx_handle, &String::default()) + }?; + tx_handle.free_handle(&handle); + tx_result + }; + handle.free_handle(); + Ok(result) + } + + pub fn update_witness_stack_internal( + tx: &CreateTxData, + handle: &ErrorHandle, + tx_handle: &TxDataHandle, + witness_type: i32, + outpoint: &OutPoint, + stack_index: u32, + data: &ByteData, + ) -> Result<(), CfdError> { + let tx_data_handle = match tx_handle.is_null() { + false => tx_handle.clone(), + _ => TxDataHandle::new(&handle, &tx.network, &tx.tx)?, + }; + let result = { + let txid = alloc_c_string(&outpoint.txid.to_hex())?; + let stack_item = alloc_c_string(&data.to_hex())?; + let error_code = unsafe { + CfdUpdateWitnessStack( + handle.as_handle(), + tx_data_handle.as_handle(), + witness_type, + txid.as_ptr(), + outpoint.get_vout(), + stack_index, + stack_item.as_ptr(), + ) + }; + match error_code { + 0 => Ok(()), + _ => Err(handle.get_error(error_code)), + } + }; + if tx_handle.is_null() { + tx_data_handle.free_handle(&handle); + } + result + } + pub fn update_output_amount( &mut self, tx: &str, @@ -2297,6 +2540,132 @@ impl TransactionOperation { result } + pub fn split_txout( + &mut self, + tx: &str, + index: u32, + txout_list: &[TxOutData], + ) -> Result, CfdError> { + let mut conv_list = vec![]; + for txout in txout_list { + conv_list.push(ConfidentialTxOutData { + amount: txout.amount, + address: txout.address.clone(), + locking_script: txout.locking_script.clone(), + ..ConfidentialTxOutData::default() + }); + } + + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let split_result = { + TransactionOperation::split_txout_internal( + &self.network, + &handle, + &tx_handle, + &String::default(), + index, + &conv_list, + )?; + self.tx_data = self.get_all_data_internal(&handle, &tx_handle, &String::default())?; + self.get_tx_internal(&handle, &tx_handle, &String::default()) + }?; + tx_handle.free_handle(&handle); + split_result + }; + handle.free_handle(); + Ok(result) + } + + pub fn split_txout_internal( + network: &Network, + handle: &ErrorHandle, + tx_handle: &TxDataHandle, + tx: &str, + index: u32, + txout_list: &[ConfidentialTxOutData], + ) -> Result<(), CfdError> { + let tx_data_handle = match tx_handle.is_null() { + false => tx_handle.clone(), + _ => TxDataHandle::new(&handle, network, tx)?, + }; + let result = { + let split_handle = { + let mut split_tx_handle: *mut c_void = ptr::null_mut(); + let error_code = unsafe { + CfdCreateSplitTxOutHandle( + handle.as_handle(), + tx_data_handle.as_handle(), + &mut split_tx_handle, + ) + }; + match error_code { + 0 => Ok(split_tx_handle), + _ => Err(handle.get_error(error_code)), + } + }?; + let split_ret = { + for txout in txout_list { + { + let address = match txout.confidential_address.to_str().is_empty() { + false => alloc_c_string(&txout.confidential_address.to_str()), + true => match txout.address.is_valid() { + true => alloc_c_string(&txout.address.to_str()), + false => alloc_c_string(""), + }, + }?; + let script = match txout.locking_script.to_hex().len() { + 0 => alloc_c_string(""), + _ => alloc_c_string(&txout.locking_script.to_hex()), + }?; + let nonce = match txout.confidential_address.to_str().is_empty() { + false => alloc_c_string(&txout.confidential_address.get_confidential_key().to_str()), + true => match txout.nonce.is_empty() { + false => alloc_c_string(&txout.nonce.to_hex()), + true => alloc_c_string(""), + }, + }?; + let error_code = unsafe { + CfdAddSplitTxOutData( + handle.as_handle(), + split_handle, + txout.amount, + address.as_ptr(), + script.as_ptr(), + nonce.as_ptr(), + ) + }; + match error_code { + 0 => Ok(()), + _ => Err(handle.get_error(error_code)), + } + }? + } + let error_code = unsafe { + CfdSplitTxOut( + handle.as_handle(), + tx_data_handle.as_handle(), + split_handle, + index, + ) + }; + match error_code { + 0 => Ok(()), + _ => Err(handle.get_error(error_code)), + } + }; + unsafe { + CfdFreeSplitTxOutHandle(handle.as_handle(), split_handle); + } + split_ret + }; + if tx_handle.is_null() { + tx_data_handle.free_handle(&handle); + } + result + } + pub fn get_all_data(&mut self, tx: &str) -> Result { let mut handle = ErrorHandle::new()?; let result = { @@ -2741,6 +3110,39 @@ impl TransactionOperation { result } + pub fn get_txout_indexes_by_address( + &self, + tx: &str, + address: &Address, + ) -> Result, CfdError> { + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let result = Self::get_txout_indexes(&handle, &tx_handle, address, &Script::default()); + tx_handle.free_handle(&handle); + result + }; + handle.free_handle(); + result + } + + pub fn get_txout_indexes_by_script( + &self, + tx: &str, + locking_script: &Script, + ) -> Result, CfdError> { + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let result = + Self::get_txout_indexes(&handle, &tx_handle, &Address::default(), locking_script); + tx_handle.free_handle(&handle); + result + }; + handle.free_handle(); + result + } + pub fn create_sighash( &self, tx: &str, @@ -3904,6 +4306,63 @@ impl TransactionOperation { } } + fn get_txout_indexes( + handle: &ErrorHandle, + tx_data_handle: &TxDataHandle, + address: &Address, + locking_script: &Script, + ) -> Result, CfdError> { + let mut list = vec![]; + let addr = alloc_c_string(address.to_str())?; + let script = alloc_c_string(&locking_script.to_hex())?; + let mut offset: c_uint = 0; + let mut index: c_uint = 0; + let error_code = unsafe { + CfdGetTxOutIndexWithOffsetByHandle( + handle.as_handle(), + tx_data_handle.as_handle(), + offset, + addr.as_ptr(), + script.as_ptr(), + &mut index, + ) + }; + match error_code { + 0 => { + list.push(index); + offset = index + 1; + loop { + let has_loop = { + let error_code = unsafe { + CfdGetTxOutIndexWithOffsetByHandle( + handle.as_handle(), + tx_data_handle.as_handle(), + offset, + addr.as_ptr(), + script.as_ptr(), + &mut index, + ) + }; + match error_code { + 0 => { + list.push(index); + offset = index + 1; + Ok(true) + } + 3 => Ok(false), // out-of-range + _ => Err(handle.get_error(error_code)), + } + }?; + if !has_loop { + break; + } + } + Ok(list) + } + _ => Err(handle.get_error(error_code)), + } + } + fn create_tx( &mut self, version: u32, diff --git a/tests/confidential_transaction_test.rs b/tests/confidential_transaction_test.rs index e4d43ff..4b4a3aa 100644 --- a/tests/confidential_transaction_test.rs +++ b/tests/confidential_transaction_test.rs @@ -4,11 +4,13 @@ extern crate cfd_rust; mod elements_tests { use cfd_rust::{ decode_raw_transaction, get_default_blinding_key, get_issuance_blinding_key, Address, - BlindFactor, BlindOption, ByteData, ConfidentialAddress, ConfidentialAsset, ConfidentialNonce, - ConfidentialTransaction, ConfidentialTxOutData, ConfidentialValue, Descriptor, - ElementsUtxoData, ExtPrivkey, ExtPubkey, FeeOption, FundTargetOption, FundTransactionData, - HashType, InputAddress, IssuanceKeyMap, KeyIndexMap, Network, OutPoint, Privkey, Pubkey, - Script, SigHashType, SignParameter, TxInData, Txid, + BlindFactor, BlindOption, BlockHash, ByteData, ConfidentialAddress, ConfidentialAsset, + ConfidentialNonce, ConfidentialTransaction, ConfidentialTxOutData, ConfidentialValue, + Descriptor, ElementsUtxoData, ExtPrivkey, ExtPubkey, FeeOption, FundTargetOption, + FundTransactionData, HashType, InputAddress, IssuanceInputData, IssuanceKeyMap, + IssuanceOutputData, KeyIndexMap, Network, OutPoint, PeginInputData, PegoutInputData, Privkey, + Pubkey, ReissuanceInputData, Script, SigHashType, SignParameter, Transaction, TxInData, + TxOutData, Txid, UtxoData, }; use std::str::FromStr; @@ -360,9 +362,7 @@ mod elements_tests { fn blind_tx_test() { let mut tx = ConfidentialTransaction::from_str("0200000000020f231181a6d8fa2c5f7020948464110fbcc925f94d673d5752ce66d00250a1570000000000ffffffff0f231181a6d8fa2c5f7020948464110fbcc925f94d673d5752ce66d00250a1570100008000ffffffffd8bbe31bc590cbb6a47d2e53a956ec25d8890aefd60dcfc93efd34727554890b0683fe0819a4f9770c8a7cd5824e82975c825e017aff8ba0d6a5eb4959cf9c6f010000000023c346000004017981c1f171d7973a1fd922652f559f47d6d1506a4be2394b27a54951957f6c1801000000003b947f6002200d8510dfcf8e2330c0795c771d1e6064daab2f274ac32a6e2708df9bfa893d17a914ef3e40882e17d6e477082fcafeb0f09dc32d377b87010bad521bafdac767421d45b71b29a349c7b2ca2a06b5d8e3b5898c91df2769ed010000000029b9270002cc645552109331726c0ffadccab21620dd7a5a33260c6ac7bd1c78b98cb1e35a1976a9146c22e209d36612e0d9d2a20b814d7d8648cc7a7788ac017981c1f171d7973a1fd922652f559f47d6d1506a4be2394b27a54951957f6c1801000000000000c350000001cdb0ed311810e61036ac9255674101497850f5eee5e4320be07479c05473cbac010000000023c3460003ce4c4eac09fe317f365e45c00ffcf2e9639bc0fd792c10f72cdc173c4e5ed8791976a9149bdcb18911fa9faad6632ca43b81739082b0a19588ac00000000").expect("Fail"); - let mut utxos: Vec = vec![]; - // set utxo data - utxos.push( + let utxos: Vec = vec![ ElementsUtxoData::from_outpoint( &OutPoint::from_str( "57a15002d066ce52573d674df925c9bc0f1164849420705f2cfad8a68111230f", @@ -382,8 +382,6 @@ mod elements_tests { &BlindFactor::from_str("ae0f46d1940f297c2dc3bbd82bf8ef6931a2431fbb05b3d3bc5df41af86ae808") .expect("Fail"), ), - ); - utxos.push( ElementsUtxoData::from_outpoint( &OutPoint::from_str( "57a15002d066ce52573d674df925c9bc0f1164849420705f2cfad8a68111230f", @@ -403,7 +401,7 @@ mod elements_tests { &BlindFactor::from_str("62e36e1f0fa4916b031648a6b6903083069fa587572a88b729250cde528cfd3b") .expect("Fail"), ), - ); + ]; let mut issuance_keys = IssuanceKeyMap::default(); let ct_address_list: Vec = vec![]; @@ -529,9 +527,7 @@ mod elements_tests { fn blind_tx_and_option_test() { let tx = ConfidentialTransaction::from_str("0200000000020f231181a6d8fa2c5f7020948464110fbcc925f94d673d5752ce66d00250a1570000000000ffffffff0f231181a6d8fa2c5f7020948464110fbcc925f94d673d5752ce66d00250a1570100008000ffffffffd8bbe31bc590cbb6a47d2e53a956ec25d8890aefd60dcfc93efd34727554890b0683fe0819a4f9770c8a7cd5824e82975c825e017aff8ba0d6a5eb4959cf9c6f010000000023c346000004017981c1f171d7973a1fd922652f559f47d6d1506a4be2394b27a54951957f6c1801000000003b947f6002200d8510dfcf8e2330c0795c771d1e6064daab2f274ac32a6e2708df9bfa893d17a914ef3e40882e17d6e477082fcafeb0f09dc32d377b87010bad521bafdac767421d45b71b29a349c7b2ca2a06b5d8e3b5898c91df2769ed010000000029b9270002cc645552109331726c0ffadccab21620dd7a5a33260c6ac7bd1c78b98cb1e35a1976a9146c22e209d36612e0d9d2a20b814d7d8648cc7a7788ac017981c1f171d7973a1fd922652f559f47d6d1506a4be2394b27a54951957f6c1801000000000000c350000001cdb0ed311810e61036ac9255674101497850f5eee5e4320be07479c05473cbac010000000023c3460003ce4c4eac09fe317f365e45c00ffcf2e9639bc0fd792c10f72cdc173c4e5ed8791976a9149bdcb18911fa9faad6632ca43b81739082b0a19588ac00000000").expect("Fail"); - let mut utxos: Vec = vec![]; - // set utxo data - utxos.push( + let utxos: Vec = vec![ ElementsUtxoData::from_outpoint( &OutPoint::from_str( "57a15002d066ce52573d674df925c9bc0f1164849420705f2cfad8a68111230f", @@ -551,8 +547,6 @@ mod elements_tests { &BlindFactor::from_str("ae0f46d1940f297c2dc3bbd82bf8ef6931a2431fbb05b3d3bc5df41af86ae808") .expect("Fail"), ), - ); - utxos.push( ElementsUtxoData::from_outpoint( &OutPoint::from_str( "57a15002d066ce52573d674df925c9bc0f1164849420705f2cfad8a68111230f", @@ -572,7 +566,7 @@ mod elements_tests { &BlindFactor::from_str("62e36e1f0fa4916b031648a6b6903083069fa587572a88b729250cde528cfd3b") .expect("Fail"), ), - ); + ]; let mut issuance_keys = IssuanceKeyMap::default(); let mut ct_address_list: Vec = vec![]; @@ -980,8 +974,7 @@ mod elements_tests { let desc2 = Descriptor::new(desc_multi, &Network::ElementsRegtest).expect("Fail"); // set utxo data - let mut utxos: Vec = vec![]; - utxos.push( + let utxos: Vec = vec![ ElementsUtxoData::from_descriptor( &OutPoint::from_str( "57a15002d066ce52573d674df925c9bc0f1164849420705f2cfad8a68111230f", @@ -1002,8 +995,6 @@ mod elements_tests { &BlindFactor::from_str("ae0f46d1940f297c2dc3bbd82bf8ef6931a2431fbb05b3d3bc5df41af86ae808") .expect("Fail"), ), - ); - utxos.push( ElementsUtxoData::from_descriptor( &OutPoint::from_str( "57a15002d066ce52573d674df925c9bc0f1164849420705f2cfad8a68111230f", @@ -1025,7 +1016,7 @@ mod elements_tests { .expect("Fail"), ) .set_option_info(true, true, false, 0, &Script::default()), - ); + ]; // estimate fee on blind tx let mut option = FeeOption::new(&Network::LiquidV1); @@ -1090,9 +1081,10 @@ mod elements_tests { &Network::LiquidV1, ) .expect("Fail"); - let mut target_list: Vec = vec![]; - target_list.push(FundTargetOption::from_asset(0, &fee_asset, &addr1)); - target_list.push(FundTargetOption::from_asset(0, &asset_b, &addr2)); + let target_list: Vec = vec![ + FundTargetOption::from_asset(0, &fee_asset, &addr1), + FundTargetOption::from_asset(0, &asset_b, &addr2), + ]; let mut option = FeeOption::new(&Network::LiquidV1); option.fee_rate = 0.1; option.fee_asset = fee_asset; @@ -1161,11 +1153,16 @@ mod elements_tests { tx = tx .set_reissuance( &outpoint, - 600000000, - &input_utxo.asset_blind_factor, - &BlindFactor::from_str("6f9ccf5949eba5d6a08bff7a015e825c97824e82d57c8a0c77f9a41908fe8306") + &ReissuanceInputData { + asset_amount: 600000000, + blinding_nonce: input_utxo.asset_blind_factor.clone(), + entropy: BlindFactor::from_str( + "6f9ccf5949eba5d6a08bff7a015e825c97824e82d57c8a0c77f9a41908fe8306", + ) .expect("Fail"), - &InputAddress::Addr(out_addr2), + asset_address: InputAddress::Addr(out_addr2), + ..ReissuanceInputData::default() + }, &mut out_data, ) .expect("Fail"); @@ -1211,9 +1208,10 @@ mod elements_tests { &Network::LiquidV1, ) .expect("Fail"); - let mut target_list: Vec = vec![]; - target_list.push(FundTargetOption::from_asset(0, &fee_asset, &addr1)); - target_list.push(FundTargetOption::from_asset(0, &asset_b, &addr2)); + let target_list: Vec = vec![ + FundTargetOption::from_asset(0, &fee_asset, &addr1), + FundTargetOption::from_asset(0, &asset_b, &addr2), + ]; let mut option = FeeOption::new(&Network::LiquidV1); option.fee_rate = 0.1; option.fee_asset = fee_asset; @@ -1296,11 +1294,10 @@ mod elements_tests { &Network::ElementsRegtest, ) .expect("Fail"); - let mut target_list: Vec = vec![]; - target_list.push(FundTargetOption::from_asset_and_address( - 0, &fee_asset, &ct_addr1, - )); - target_list.push(FundTargetOption::from_asset(0, &asset_b, &addr2)); + let target_list: Vec = vec![ + FundTargetOption::from_asset_and_address(0, &fee_asset, &ct_addr1), + FundTargetOption::from_asset(0, &asset_b, &addr2), + ]; let mut option = FeeOption::new(&Network::ElementsRegtest); option.fee_rate = 0.1; option.fee_asset = fee_asset; @@ -1377,9 +1374,10 @@ mod elements_tests { &Network::Mainnet, ) .expect("Fail"); - let mut target_list: Vec = vec![]; - target_list.push(FundTargetOption::from_asset(0, &fee_asset, &addr1)); - target_list.push(FundTargetOption::from_asset(0, &asset_b, &addr2)); + let target_list: Vec = vec![ + FundTargetOption::from_asset(0, &fee_asset, &addr1), + FundTargetOption::from_asset(0, &asset_b, &addr2), + ]; let mut option = FeeOption::new(&Network::ElementsRegtest); option.fee_rate = 0.1; option.fee_asset = fee_asset; @@ -1402,8 +1400,7 @@ mod elements_tests { let desc2 = Descriptor::new(desc_multi, &Network::ElementsRegtest).expect("Fail"); // set utxo data - let mut utxos: Vec = vec![]; - utxos.push( + let utxos: Vec = vec![ ElementsUtxoData::from_descriptor( &OutPoint::from_str( "57a15002d066ce52573d674df925c9bc0f1164849420705f2cfad8a68111230f", @@ -1424,8 +1421,6 @@ mod elements_tests { &BlindFactor::from_str("ae0f46d1940f297c2dc3bbd82bf8ef6931a2431fbb05b3d3bc5df41af86ae808") .expect("Fail"), ), - ); - utxos.push( ElementsUtxoData::from_descriptor( &OutPoint::from_str( "57a15002d066ce52573d674df925c9bc0f1164849420705f2cfad8a68111230f", @@ -1446,7 +1441,7 @@ mod elements_tests { &BlindFactor::from_str("62e36e1f0fa4916b031648a6b6903083069fa587572a88b729250cde528cfd3b") .expect("Fail"), ), - ); + ]; let asset_1 = ConfidentialAsset::from_str( "186c7f955149a5274b39e24b6a50d1d6479f552f6522d91f3a97d771f1c18179", @@ -1486,13 +1481,18 @@ mod elements_tests { tx = tx .set_reissuance( &utxos[1].utxo.outpoint, - 600000000, - &utxos[1].asset_blind_factor, - &BlindFactor::from_str("6f9ccf5949eba5d6a08bff7a015e825c97824e82d57c8a0c77f9a41908fe8306") + &ReissuanceInputData { + asset_amount: 600000000, + blinding_nonce: utxos[1].asset_blind_factor.clone(), + entropy: BlindFactor::from_str( + "6f9ccf5949eba5d6a08bff7a015e825c97824e82d57c8a0c77f9a41908fe8306", + ) .expect("Fail"), - &InputAddress::Addr( - Address::from_str("2dodsWJgP3pTWWidK5hDxuYHqC1U4CEnT3n").expect("Fail"), - ), + asset_address: InputAddress::Addr( + Address::from_str("2dodsWJgP3pTWWidK5hDxuYHqC1U4CEnT3n").expect("Fail"), + ), + ..ReissuanceInputData::default() + }, &mut data, ) .expect("Fail"); @@ -1507,8 +1507,7 @@ mod elements_tests { let asset_c = ConfidentialAsset::from_str(ASSET_C).expect("Fail"); let desc = "sh(wpkh([ef735203/0'/0'/7']022c2409fbf657ba25d97bb3dab5426d20677b774d4fc7bd3bfac27ff96ada3dd1))#4z2vy08x"; let descriptor = Descriptor::new(desc, network).expect("Fail"); - let mut list: Vec = vec![]; - list.push( + let list: Vec = vec![ ElementsUtxoData::from_descriptor( &OutPoint::from_str( "7ca81dd22c934747f4f5ab7844178445fe931fb248e0704c062b8f4fbd3d500a", @@ -1520,8 +1519,6 @@ mod elements_tests { &descriptor, ) .expect("Fail"), - ); - list.push( ElementsUtxoData::from_descriptor( &OutPoint::from_str( "30f71f39d210f7ee291b0969c6935debf11395b0935dca84d30c810a75339a0a", @@ -1533,8 +1530,6 @@ mod elements_tests { &descriptor, ) .expect("Fail"), - ); - list.push( ElementsUtxoData::from_descriptor( &OutPoint::from_str( "9e1ead91c432889cb478237da974dd1e9009c9e22694fd1e3999c40a1ef59b0a", @@ -1546,8 +1541,6 @@ mod elements_tests { &descriptor, ) .expect("Fail"), - ); - list.push( ElementsUtxoData::from_descriptor( &OutPoint::from_str( "8f4af7ee42e62a3d32f25ca56f618fb2f5df3d4c3a9c59e2c3646c5535a3d40a", @@ -1559,8 +1552,6 @@ mod elements_tests { &descriptor, ) .expect("Fail"), - ); - list.push( ElementsUtxoData::from_descriptor( &OutPoint::from_str( "4d97d0119b90421818bff4ec9033e5199199b53358f56390cb20f8148e76f40a", @@ -1572,8 +1563,6 @@ mod elements_tests { &descriptor, ) .expect("Fail"), - ); - list.push( ElementsUtxoData::from_descriptor( &OutPoint::from_str( "b9720ed2265a4ced42425bffdb4ef90a473b4106811a802fce53f7c57487fa0b", @@ -1585,8 +1574,6 @@ mod elements_tests { &descriptor, ) .expect("Fail"), - ); - list.push( ElementsUtxoData::from_descriptor( &OutPoint::from_str( "0000000000000000000000000000000000000000000000000000000000000b01", @@ -1598,8 +1585,6 @@ mod elements_tests { &descriptor, ) .expect("Fail"), - ); - list.push( ElementsUtxoData::from_descriptor( &OutPoint::from_str( "0000000000000000000000000000000000000000000000000000000000000b02", @@ -1611,8 +1596,6 @@ mod elements_tests { &descriptor, ) .expect("Fail"), - ); - list.push( ElementsUtxoData::from_descriptor( &OutPoint::from_str( "0000000000000000000000000000000000000000000000000000000000000b03", @@ -1624,8 +1607,6 @@ mod elements_tests { &descriptor, ) .expect("Fail"), - ); - list.push( ElementsUtxoData::from_descriptor( &OutPoint::from_str( "0000000000000000000000000000000000000000000000000000000000000b04", @@ -1637,8 +1618,6 @@ mod elements_tests { &descriptor, ) .expect("Fail"), - ); - list.push( ElementsUtxoData::from_descriptor( &OutPoint::from_str( "0000000000000000000000000000000000000000000000000000000000000c01", @@ -1650,8 +1629,6 @@ mod elements_tests { &descriptor, ) .expect("Fail"), - ); - list.push( ElementsUtxoData::from_descriptor( &OutPoint::from_str( "0000000000000000000000000000000000000000000000000000000000000c02", @@ -1663,7 +1640,533 @@ mod elements_tests { &descriptor, ) .expect("Fail"), - ); + ]; list } + + #[test] + fn sign_sighash_rangeproof_test() { + let tx_hex = "020000000101b7fc3ad65a21649fdf9a225a6165a2f945e895f2eac6b2bbc1d2f3681080f8030000000000ffffffff030b3584c0a40110d0a208aad09ca9be67dbe5fc7343b6287a8abb122439def1cc8c094d982039de127c6ffb3a012d7a54cb790baf1e923f48307eefd189aaa830dd4f03536ffcfc5365ae010225b8011f8b94e495574b4d0527350fef11fdbd93dbe21b17a914a3949e9a8b0b813db67c8fc5ad14194a297979cd870bcf3a513f93f3098858ba760efdcea670e9675930b210b9b7e5c33a87ebe3823d08e6b041a5120606213de33e800d38636a5a18277badf9f9393db822b15f3973aa03eaa4f0b5baacb04a6d8b37b5e70584550be4962178e20a9c0aec5faef855ccc617a914a3949e9a8b0b813db67c8fc5ad14194a297979cd870125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a0100000000000027100000000000000000000043010001033a6e5463aeaf8460ef48520746c9a162f4b715eb6d4f00cb975e3b7a01e57092012829e2e1e87787fe5e04512e0b68500f97ae8771a44c05a87d3646e9215bfd4d0b602300000000000000014d3500fbe175b8e442af9d8322a0c892e2bca2c25561799c01c07f66cbb936e9c90404338f40bcf348912cc25855cbd38621076efd088b36b1933baf9513aea1bbd522551c82a478a7fa34e1dcda8ef648761031332ad5388ccbc171f9475c9fde056908bdec0b0c408c41691c4bed297da4b1740bbf0f81eb2ccf0f112ba6ca2c6f1d6bf3c1cecc613aaac01153bfaf7906df82402dcc8d0867eecd212fee82827e1ab5fb7ab448b36bed57bc9edb153544a961ee5e22a33385336e77f2b67dd6274c4d0232635c3112f7cb9dbb758beeac7db0ca1945c3497d1187aee1106181b2b2bd8c5602b1460085d2ac1319d0df500169a9344528feed3304ad212678e79946ebcf915cbbb9be47b0e850e0022c058e95e6e18d73227d354910b6d2ef7435c8a5f6e1355c2e35173e7bef86924a14a0f3e747562af1898a8c65ac52ba31bd860e81090f3cc1da296557685c4529681b25066d4db6febed3cbc3252e87df247a96a6f8c44550ebaf0610b3ebfdc80c744d41b4cb80b17d3ffa95024fac8ec40cbad4a2e442479e334021ee50218dabfec1d68bfda180d7d509e0d2da389f589ee6024791c2557b4175aae469e7e11b2779d1a8433ffe99ac7db6cbc7a4296c1eba50636839b1a378ad27c9fca08859689d5f08103c45f8de0eedabe7987afa9dd3aeb970cb8276c22fd329f182b8200b1d1a4baa88f2a82e54b40ae07cb10bd7c9bad09e59b461ea0f955791ace19af8dd90cb7b55cd09d319abc75686d15606b88356cf1f6838f59d61d473d4d128821d7628ac3c9b9068e65acfbab69fcbe498ee115fcb1cb3133d51d66e4727a4c37a56b66b940d40f4aaa16be4fc7a8c58d1df13614905472354a996f463c0102503a5191c4677a9666f3190555022a1f447d0cc45f65a845dd8a0a9fd9835948fe4aee490ead426df02327ce189458c85d08aad605810e5b9372023992040213e0df83c1d775c248edd7095271f88d065c073a15b37c3455b23ae946fa4cb7e40a14dc8810614deacf8ccab5634bce2f6475c35867c0c71add1d2af013269bc0973d8a54a78b2f6234c9520ea490f188aa493d612f5bef278cfb9f48e525f7eb202f3603bfe5d978b1233447872dff46a21b1d19ec8a8f6a9c4dc1c4ee3ebb4124b4c2a9d7d28705eecb04b9c592cee292155c7204a5a3f781cbc970d9549f5afba68d2e1ddc4b08ee3af6085716c7af487c92a1abe79d7c9f5027cb03190c9f47e05db5896cd94b119c53de617533dd7bdce57e9f3ce69cf8f25b6bcfce6d8abdcf14e098551d8f72256c2c04a5fb835f5b9f5ba1c7a5989a1c93e181240cb175e34ecedb9f2ef23614054f44cbaacb732e5529f02a7622c2ffaf811d205ed2be83f723ff8ba4ff23dbca65aabad5196c44c37982bbd5a54f9b38e776b52b8e817ca2f2c246dcd7b68922f9b2b4e8792c4a00c02a81de950afa532ce77ded61182f8fb65a49d416ffd697a8bab26feff05c9e0e7bcf028fd4775566f5864c4cdd51e425756369d9d4478ca027907511cedab3dcd098f9c9a019714a6ce3b40f54fcfaa6e035c4c8e566bb050ef9b9f4ac995155f2fcda379f94a3cf2819e5d57221ebe63af2dbcf319942728ada876379a0d98991d25125168307e089bd2bc24843d0b58f958d75b37f097dced7d7579d94346127d7a2029c1966ccf7e80ede17511da539ed2e4cb7b65f644e906f9c57c5a17ec9375ab5b9b867d1ab68a00371cbb5fa209d6badae3728723e0dff89cd5f9bd38dec57321b43b32c747b0f645e02df0ce9d946a723243d53a85232c4f26853b2d788c7f5c6e9974da549af5cf23813eaf88a409198729f12f5b83cccff34b6153e0d3adb79a3ce11dde701807519db5df63939d4ed44abd34971bc14a477ed35a7596907753477aabdfc15265f2625787d8a152f9b5deabc5371682657b0c9bd4dc20ca95e388de3454ec80ded9b9b268ec55e2f0816be7e9b979a81685e2885bce1f0c873d6f268fda1a8d4fe8f736e95842fc9b05f84ed1d14a67566dfe931fa13ffff2672d9f9d30b13e62ecca4ff20f462be0626c0b93493446ff90d7ff1bddd867c8c6d9ccee603ab0fb0896544a20f2f9a5f5928e026bfd0ea93cc4c26abcf6159fa2c15245f2f3190cfc201316985a17b666e16230598a84e4bc31a6ae90491ea1f4550f96360778342c84c494c9faa073624396d8ca2e1ced3b545959040be0a5cb9d8bedd9584ba2db6f3bbf2dca734806076cb406432793f7b3ba7bc4ac35325e97d1f48d219c9e0e5a1555cb49cd97fdbfda3034ef08073a490624e2e1b76450e091d878c3caf9fe066680b96af0db14d9939c489dfd387b2e86b89ecd4bf1e6cc4c7a7f0e4533c6dcb510bec483faac1a9bc73cf2e1e595e8ffad98c7c400601b56840766f4d7934b2cbd6aa70bf6ebd18436d23f963b1a7711b3966415e50a0b0c67cb6de142e1a1ba5d7dfa77542c44bbbaa0e95ed0e702a506fdab23ee7aaa7843083266693dfb6c31be76e0bc3b33fa18b11e95888a18cdc4793ae7bc1a4686c546a52c9843fa19739522cdbbf874ec59d0c38b6a4b3aedbfbe72d82b8efba6deac3cbdb2f18ac25de8c8f095f1c921431e6a7c342fb17e8d3a5cb809521765de4adb001ae63d92e109317d090425dcdc197dfdbe7440b857824ef6128397653e20c33f995b4c782db036b420945b4c2c3d953ead32378939e9b74537e2dd26cd360bac1dbf752f66f1ddbb03eede3c76efc619bb06ebac2a0cfbb6c40f9a2762995e0911950c07eb7b5ca642234e0c0df99db32dfe99253003c052db72d4f744e423cc28620e1742363e5745ba759d15b62f77bb9c6534bcc13ee8ef1381219ac07219f8a75caa256ab7c3fffd0c40f93556541c929a1754c289235c4f80b68f0cd0043c9e0c2922e512a0730a80c2b4bf038ee9d5ece4e5dbc1f84258a81da43e4921278f1e4aeffa6cbfb8b2f83e9dc756811a00a44a48498b7f0f1b8ab9b1808914cb66b3fd41e6d4ff6579747b2174b4651c66382fd6000b6db2dec4763f61667115113469b73de1d911af384a36f7e4c960e10102e015b8d1250af9ea7981e79d84b49f714aa3210fb6e7f9b2a860d9df18c981f69b479bfd744a6c054df476919407bd227c58c76f244c8b8ea4265912e95b4285da68be013e3888b92d57b8689ad1ef5c6f0d3c0bed3cf25e86931e7c4bf14042d8ca9b7c323b2875b981eb9b5883c4e3bd75233cd9ae19568a3adede390bcfa787067596ab704c9b057d6a91b77c514de5c034a96cb31db3393ead266aab3eaebf2fbeffa63c4202c5febaa3f2b848878a5c2235619071c10f6146868e735971c8a53f59d3235b0e3b4a460473e7173f29d42982f69cef40207d43e2fd308a7ff6421044c8960c991f33a9d3334a8d63109251f672fd89f75ae1d9b32559b73c5a22e019f46a503f9dfb5bc82deb5103dde9e91f145c7507ad55b21e0ff8f96b5dcb6222dc8c5e428ddd9e9c7658a8ba8d318aa6180e75c260bb83e16898a68711be33c0ba8906e3233c8c1f31c9fc75857655ef26ba7b53952dab096a48ccf9258490a0b6e05304da43a43878eb0740fe1f952b9028d450b904ebca2d00d036bd38a435ffcf73d795397a84ec18f72a93cdddd4b8dfdac4c6c7877e16b480e819857c8e18920beb0ee09e6c3b2b6baadcffeac776591744394eb512d5814c1f68ff0c73e2bb3e2f4e0186282cd8da6b8e96e1d5bca47c98329608edaebe36c01895638a7c728cf871d2f19d5833234d2277d3acfd60ebd6c4add790eee3758bd840bc22962f1fa2227ae2853813cc36b5be46e37b246f6a9b0a21b6159a82b5f92800941e33df02188761bc730c2b284aa15dd4545ae0f4c4229a4d8c9b415a691a355e1858a26c7433195a42e55c020cbc0efcb51b560d561715c546091d655dd5cac04b4ce0986941229591f33f7c96e9acef7e878043c49a4a420de859ff20695196c37660511276e43705f73751d68f7ef1929b3ab8e32faabc3c83ac13466c543650220aa2669a3ed7fe34d6f6e6ddb44e390b7338a8a3056a8643010001ff410fe519866271fcc3d47b83120429858ebaa008dce79ae35b4455d7ec3f3045ffab1a938bbf628ff8c14f87f08943acca6bdeebfb06c18b58ec8f4d1d9c51fd4d0b60230000000000000001e20700490755f44beb25382ec7d71b25290f105ca685a456ebd1a7c560386ec9d03fbcb13ae429d8a04902b5daf4ef10010551a2848a31c42a43dc4037705a3ccd2e329f4ae6b02bebe80e58062b374e35d099147cdb36dcbf174ced965c6697a4187f68eb482706f30b24c4312a93a576f07398cd3c5a001645885aa6fdd659a1aeebfd51fe2fe0fc8f26ca9071ee7480de50a9c0637a9c551fedb0215045a888c40d4629109d8c93be540df83c991ac8c1ac9c3e2bb798fcea1c4a4791925c01d349e4ee5832b6e3d53a6d2dc2193b78d97b3b0dd951a846d48d83ceb067518e558214fa17837b747285b80aa92200c5340bdfb727e55d72c26dbc7816073a3654b084022e2d951463a0838f4683ec74184f18af1c0fddbebe292b6615c3c12b7bc381943f2a0968df482be55dd45ce19491349f2cb982935c092cd281f24e3ccffea71f3c776dc79cd4338fc93e393d92de1456166bc1019c1252932f43408338c3a84716dc842cea2a069b057a968d73892429ffd02225550bef7faa19e580cda00ec18d1d60d8e338a9cab95aaa857c579904eea03873c96b23bdd7b2d7aa39d402d5e81cf3585d390c990ba6d786ff2e8183c15e8ca4faeebefd7630b54179a6edea020f6de31ba0b8f36173ba0caf6eace25d9aaaf2ec1ec3cf6a87caef74787447017a2f661598ca5252baccedcf3b7cab0d1bf3d13d69651d92c0dda2baed6f979b5bf5b1f9e4b60592da53cad89bdc53111aafa7a9d8874e7d9f145e128162b709db2557456a1f06789ff8508bce78b47fcd6d63914753e3c5002b1a3a31cf5ac6e42ab6638499c0b964681e854e7284a9fad3a96e5c53ead3ea652e9d0af6e6b86fd920edb6187fe22a03f47fb617ab4232155fd249922d4893d2c786abb074bd399b210e4461c75169f13b0ddedc25c4def03f9a7e6e58e1a9575fe59556cefab31114c3b59fcfe80883920aede6ca6db4db539367894bf9c0a465a0448c6b2f370a3c41e1a9790dcad74918f41dcc7f568559401bc4a471102e43489a731328aad4cb8f9cb459298569a724048b2e879964143837eee75e8a4906fc3bfa22581589f3ca9f6b9958c46a30747e54b5c7fe66f510c77d658458f2311140287b9fc421a48e17073707087f37c1098e9d60b67bac01f1b5b2feef6c1902bbe5581a1b13836f555e63f6fefcc715f4b818acb499ac17e9a3633bf97e975ac49cbf76aaa1ef85411a3fb9062195e202d03a6aed9b652bcb380424032cbaadc3a8b808414836fe68f29b62d27a22abdb5a618191707b442e9dba525fb13698c1af1d00db5927e8178eb7e69ecd21b3199b3eb10ce7d9619428070fa17664b0b8134b45c6c532c74fb32b581a63f3af2d675a74ab467703f4dfede85c7f570fe65c21f71c709db4ae94e4b6fb92314aafb88f953dc8ed3b28da983910413fc9bdc78627c98b519bcaec8de7cf21abbe44deea9c3bd4ead89433bbffaf7a00f1712c09601df224c9635612f97df35812b64fe88a6055d7a971611b358ff7c65320a4eec533a007824fbfb1f0640f5634a0875189321a692fda6e4b39c2e339e3072e181dd228603c82d232f3e7370d344512ea0c0ae8428835974600bbb5870bb922de9f47be0c4fc2f3632f4ae44c7029589f3463ea4418e51c4038788942306cf7715ff8a09bd27413211db78d165fd0a698968f1f1b1023feccb850c99596933da86be7cea9590ec25d487739a25f1552a7f06f8265111dbd65b20241557234a6ddff88a42222e3622c2bb8c0020ed5e21cbce129b734df3cccb008386f78eab530f9625117a0d6d29a396e849564a0c74ec2198da0200dd80a5071fb23f282d0ef2c9bb0290bbad54f91fb3175d38b66cd7b729e370f64948fccdd69703c99196afef66b2187f7a8b9ad13a012cd344e43dc16b3cfc28a680a9764c150c93bf1db12ccb98414aaefa426ef91360acdad7cc13be34660751a8af8f11975ebfb646e8fe5293b51553dfe22d8be3c7d1ab1c4850a362ce11d12b2048a64e8b6398eb5e2078a14eebd532542917b337033b6e82b35a8630cccf170bd73f7e6868634409f1c3351fc83ad399afb3847fbaa7beb4c534bbdbd87df1cb49c9462901f46f00d9e4a1c02c8d817ed31a8e77cdc271ca8f05498951dba32abc0153637790377917ce9d872a4853c67ea639befdc9873ebeb66c30b803d9866c8118e5c7ced668fab3a80b2d4b68f8a387dd0efe3b493888cd02479c7186eea9b58f6b4b8dca92dea056ba78a81d35b29e2586a25385e3574ba4e28ce2702fe1d781b38aae3dd7ab7375014e631421fedfef38b00bc2e622422c3e5cfaf92180c20903e1ab4e983ef8fa20201f47674ce3f85f37691c611155367075ed9e131606105973b428d2e2f7badd32bb4d460735aa5d5774835d5b25369a3959bc71e60a53696c7473101c76a36fcf84192dd3f8ed934c7485033519d1704c78aecda9c8abf0dc118879779dbf4ca888dfd4dad2a6e4bb832e0283563a6057237122f325c72eef7dc89adc842b6f305ed9b2e7b24723754a1d3a26e149b90ba51cb0207d46ccd0a69bd63ad1e95f641069f69639bdccea03428601606bc85a5f2b747a3476e3449ff2aeed12214468353a5326e322354fb56c3f1dac4fa61584ddc38c5a54c5fb8ec332085f50ebfffde12c46e0283656fd954d121191dea53d19fe287e8aee935011fa7b3a240afdb24db95f3ae77c86462028f5b054ef8af36d3f7b4453ad50f470fec39f95901a57d3b3bb01e952269983475f1d13c5a5921a8709332fa9586a0fd8f82f87d06e66e9ec2272fd67a9cfcc1f48b7a1da9a3af716216b811f03fcf4a12c8e832c4e36bcb83f7cae5dd17a385cb9a679d9cc64be8d9f972ea022cd9625664180e98c3f292e62b56b5bea3b9845ce720482f5fb6931dfbd35aa3fc816a776a4553360ec18888b1d9d9acb07edeb5908955373fe6984abc8dbc6724cfec5a908fd606169d0c5ff914e0db3f1a566602dba4265c67f3233b1623b016306d8d3bb0d8c2f47e101fb626c42849c9cd83b5dd7d148ff4a488b6929e52048cf0a61d8e2d506e5d6e70c8beac188de2c14bf8f7c385461257886baf4bf3ad78e16c657e3de62e5cb42015a2cce88516887e1995018a51fa5a2a6688613dde7edf213266e5520b73336cc775ea542908f3f76e67d2792e73afb3429aeae188e39aceb3d17652740ae37fb3639c16888336bc9f1cfea546d3443214d153405e03216ae0ebb79b949ead937daf5587409cf53b6be2428c289dedd6194c2a42719660bc3ca3706f8ba4141734b6e2734ec0e90bb267fc59b92e91a38f554097193494073fb7559b6571994dc5381b5b4ca4443665cf4da2f41dbc737b554a4266c5a40bccc36f94585788eaceddbcc6a082cc1265811c7546f562743264fd8c6926b4f28a7136583057701b8386ba7821ffb12ba742d8a475765c58c1ab68d44adf9a45127a9d0a456152f2ec1317c3c7dda48944fa8595d0e86662c311e6970bb6e51589105def2032e775d5025553d3f44c89044272f870aa7b19a7f71ae0e5d184b6cb7d9e697ae8a56ac1d5619e0d9f1673072928e201975ec84417c83c0fd611cde590995d7d44ad1dbc09c98e178e48bc861b37e46c5257c8a66f6323a1a33958b14eb811ab6c8827dbf16955d01b5ce46bd1e1b19afd48a70310dfbc54d8bb6ed68ec4612c71145bff3a1833e1bb8c52962ec51219abbb58de0ba4c6ca3105fc2c181809102df98ba0e6c22ed21c6d5b30fa2432e8065d5b2b98b95800d6e5600aef541990321bf28be3cff705457c9c00d2a727352e92d102b15a9f7105457b27f93111bf4552ae1588e69e7656e2f1cb723c969c6e8a886564bee122eab57b145fbb2781dea3c099633a80141dfddefa16d93ed7becedff15f196dbd8adff8c47affc10d75aec5e9e03828e371787276193cae56253fc54eb9d1bf925152ad5f3b671f3944f9f61ab35f52b3790c655dc6f0f30ce33169b563f85057b1235fbd62c1d0f9ae9642c639c951bde2baf544117687ab8a3682206ab35b010000"; + let expect_tx_hex = "020000000101b7fc3ad65a21649fdf9a225a6165a2f945e895f2eac6b2bbc1d2f3681080f8030000000000ffffffff030b3584c0a40110d0a208aad09ca9be67dbe5fc7343b6287a8abb122439def1cc8c094d982039de127c6ffb3a012d7a54cb790baf1e923f48307eefd189aaa830dd4f03536ffcfc5365ae010225b8011f8b94e495574b4d0527350fef11fdbd93dbe21b17a914a3949e9a8b0b813db67c8fc5ad14194a297979cd870bcf3a513f93f3098858ba760efdcea670e9675930b210b9b7e5c33a87ebe3823d08e6b041a5120606213de33e800d38636a5a18277badf9f9393db822b15f3973aa03eaa4f0b5baacb04a6d8b37b5e70584550be4962178e20a9c0aec5faef855ccc617a914a3949e9a8b0b813db67c8fc5ad14194a297979cd870125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a0100000000000027100000000000000000024730440220750ed6df0b947abbe4ed7addfa7160a9bd628e1b6e01305562e518c8e683f278022044badfa55477a26acc5f4a160fe7f1f4c7f9e8d6bfd535711802800e814a9ba3412103f942716865bb9b62678d99aa34de4632249d066d99de2b5a2e542e54908450d60043010001033a6e5463aeaf8460ef48520746c9a162f4b715eb6d4f00cb975e3b7a01e57092012829e2e1e87787fe5e04512e0b68500f97ae8771a44c05a87d3646e9215bfd4d0b602300000000000000014d3500fbe175b8e442af9d8322a0c892e2bca2c25561799c01c07f66cbb936e9c90404338f40bcf348912cc25855cbd38621076efd088b36b1933baf9513aea1bbd522551c82a478a7fa34e1dcda8ef648761031332ad5388ccbc171f9475c9fde056908bdec0b0c408c41691c4bed297da4b1740bbf0f81eb2ccf0f112ba6ca2c6f1d6bf3c1cecc613aaac01153bfaf7906df82402dcc8d0867eecd212fee82827e1ab5fb7ab448b36bed57bc9edb153544a961ee5e22a33385336e77f2b67dd6274c4d0232635c3112f7cb9dbb758beeac7db0ca1945c3497d1187aee1106181b2b2bd8c5602b1460085d2ac1319d0df500169a9344528feed3304ad212678e79946ebcf915cbbb9be47b0e850e0022c058e95e6e18d73227d354910b6d2ef7435c8a5f6e1355c2e35173e7bef86924a14a0f3e747562af1898a8c65ac52ba31bd860e81090f3cc1da296557685c4529681b25066d4db6febed3cbc3252e87df247a96a6f8c44550ebaf0610b3ebfdc80c744d41b4cb80b17d3ffa95024fac8ec40cbad4a2e442479e334021ee50218dabfec1d68bfda180d7d509e0d2da389f589ee6024791c2557b4175aae469e7e11b2779d1a8433ffe99ac7db6cbc7a4296c1eba50636839b1a378ad27c9fca08859689d5f08103c45f8de0eedabe7987afa9dd3aeb970cb8276c22fd329f182b8200b1d1a4baa88f2a82e54b40ae07cb10bd7c9bad09e59b461ea0f955791ace19af8dd90cb7b55cd09d319abc75686d15606b88356cf1f6838f59d61d473d4d128821d7628ac3c9b9068e65acfbab69fcbe498ee115fcb1cb3133d51d66e4727a4c37a56b66b940d40f4aaa16be4fc7a8c58d1df13614905472354a996f463c0102503a5191c4677a9666f3190555022a1f447d0cc45f65a845dd8a0a9fd9835948fe4aee490ead426df02327ce189458c85d08aad605810e5b9372023992040213e0df83c1d775c248edd7095271f88d065c073a15b37c3455b23ae946fa4cb7e40a14dc8810614deacf8ccab5634bce2f6475c35867c0c71add1d2af013269bc0973d8a54a78b2f6234c9520ea490f188aa493d612f5bef278cfb9f48e525f7eb202f3603bfe5d978b1233447872dff46a21b1d19ec8a8f6a9c4dc1c4ee3ebb4124b4c2a9d7d28705eecb04b9c592cee292155c7204a5a3f781cbc970d9549f5afba68d2e1ddc4b08ee3af6085716c7af487c92a1abe79d7c9f5027cb03190c9f47e05db5896cd94b119c53de617533dd7bdce57e9f3ce69cf8f25b6bcfce6d8abdcf14e098551d8f72256c2c04a5fb835f5b9f5ba1c7a5989a1c93e181240cb175e34ecedb9f2ef23614054f44cbaacb732e5529f02a7622c2ffaf811d205ed2be83f723ff8ba4ff23dbca65aabad5196c44c37982bbd5a54f9b38e776b52b8e817ca2f2c246dcd7b68922f9b2b4e8792c4a00c02a81de950afa532ce77ded61182f8fb65a49d416ffd697a8bab26feff05c9e0e7bcf028fd4775566f5864c4cdd51e425756369d9d4478ca027907511cedab3dcd098f9c9a019714a6ce3b40f54fcfaa6e035c4c8e566bb050ef9b9f4ac995155f2fcda379f94a3cf2819e5d57221ebe63af2dbcf319942728ada876379a0d98991d25125168307e089bd2bc24843d0b58f958d75b37f097dced7d7579d94346127d7a2029c1966ccf7e80ede17511da539ed2e4cb7b65f644e906f9c57c5a17ec9375ab5b9b867d1ab68a00371cbb5fa209d6badae3728723e0dff89cd5f9bd38dec57321b43b32c747b0f645e02df0ce9d946a723243d53a85232c4f26853b2d788c7f5c6e9974da549af5cf23813eaf88a409198729f12f5b83cccff34b6153e0d3adb79a3ce11dde701807519db5df63939d4ed44abd34971bc14a477ed35a7596907753477aabdfc15265f2625787d8a152f9b5deabc5371682657b0c9bd4dc20ca95e388de3454ec80ded9b9b268ec55e2f0816be7e9b979a81685e2885bce1f0c873d6f268fda1a8d4fe8f736e95842fc9b05f84ed1d14a67566dfe931fa13ffff2672d9f9d30b13e62ecca4ff20f462be0626c0b93493446ff90d7ff1bddd867c8c6d9ccee603ab0fb0896544a20f2f9a5f5928e026bfd0ea93cc4c26abcf6159fa2c15245f2f3190cfc201316985a17b666e16230598a84e4bc31a6ae90491ea1f4550f96360778342c84c494c9faa073624396d8ca2e1ced3b545959040be0a5cb9d8bedd9584ba2db6f3bbf2dca734806076cb406432793f7b3ba7bc4ac35325e97d1f48d219c9e0e5a1555cb49cd97fdbfda3034ef08073a490624e2e1b76450e091d878c3caf9fe066680b96af0db14d9939c489dfd387b2e86b89ecd4bf1e6cc4c7a7f0e4533c6dcb510bec483faac1a9bc73cf2e1e595e8ffad98c7c400601b56840766f4d7934b2cbd6aa70bf6ebd18436d23f963b1a7711b3966415e50a0b0c67cb6de142e1a1ba5d7dfa77542c44bbbaa0e95ed0e702a506fdab23ee7aaa7843083266693dfb6c31be76e0bc3b33fa18b11e95888a18cdc4793ae7bc1a4686c546a52c9843fa19739522cdbbf874ec59d0c38b6a4b3aedbfbe72d82b8efba6deac3cbdb2f18ac25de8c8f095f1c921431e6a7c342fb17e8d3a5cb809521765de4adb001ae63d92e109317d090425dcdc197dfdbe7440b857824ef6128397653e20c33f995b4c782db036b420945b4c2c3d953ead32378939e9b74537e2dd26cd360bac1dbf752f66f1ddbb03eede3c76efc619bb06ebac2a0cfbb6c40f9a2762995e0911950c07eb7b5ca642234e0c0df99db32dfe99253003c052db72d4f744e423cc28620e1742363e5745ba759d15b62f77bb9c6534bcc13ee8ef1381219ac07219f8a75caa256ab7c3fffd0c40f93556541c929a1754c289235c4f80b68f0cd0043c9e0c2922e512a0730a80c2b4bf038ee9d5ece4e5dbc1f84258a81da43e4921278f1e4aeffa6cbfb8b2f83e9dc756811a00a44a48498b7f0f1b8ab9b1808914cb66b3fd41e6d4ff6579747b2174b4651c66382fd6000b6db2dec4763f61667115113469b73de1d911af384a36f7e4c960e10102e015b8d1250af9ea7981e79d84b49f714aa3210fb6e7f9b2a860d9df18c981f69b479bfd744a6c054df476919407bd227c58c76f244c8b8ea4265912e95b4285da68be013e3888b92d57b8689ad1ef5c6f0d3c0bed3cf25e86931e7c4bf14042d8ca9b7c323b2875b981eb9b5883c4e3bd75233cd9ae19568a3adede390bcfa787067596ab704c9b057d6a91b77c514de5c034a96cb31db3393ead266aab3eaebf2fbeffa63c4202c5febaa3f2b848878a5c2235619071c10f6146868e735971c8a53f59d3235b0e3b4a460473e7173f29d42982f69cef40207d43e2fd308a7ff6421044c8960c991f33a9d3334a8d63109251f672fd89f75ae1d9b32559b73c5a22e019f46a503f9dfb5bc82deb5103dde9e91f145c7507ad55b21e0ff8f96b5dcb6222dc8c5e428ddd9e9c7658a8ba8d318aa6180e75c260bb83e16898a68711be33c0ba8906e3233c8c1f31c9fc75857655ef26ba7b53952dab096a48ccf9258490a0b6e05304da43a43878eb0740fe1f952b9028d450b904ebca2d00d036bd38a435ffcf73d795397a84ec18f72a93cdddd4b8dfdac4c6c7877e16b480e819857c8e18920beb0ee09e6c3b2b6baadcffeac776591744394eb512d5814c1f68ff0c73e2bb3e2f4e0186282cd8da6b8e96e1d5bca47c98329608edaebe36c01895638a7c728cf871d2f19d5833234d2277d3acfd60ebd6c4add790eee3758bd840bc22962f1fa2227ae2853813cc36b5be46e37b246f6a9b0a21b6159a82b5f92800941e33df02188761bc730c2b284aa15dd4545ae0f4c4229a4d8c9b415a691a355e1858a26c7433195a42e55c020cbc0efcb51b560d561715c546091d655dd5cac04b4ce0986941229591f33f7c96e9acef7e878043c49a4a420de859ff20695196c37660511276e43705f73751d68f7ef1929b3ab8e32faabc3c83ac13466c543650220aa2669a3ed7fe34d6f6e6ddb44e390b7338a8a3056a8643010001ff410fe519866271fcc3d47b83120429858ebaa008dce79ae35b4455d7ec3f3045ffab1a938bbf628ff8c14f87f08943acca6bdeebfb06c18b58ec8f4d1d9c51fd4d0b60230000000000000001e20700490755f44beb25382ec7d71b25290f105ca685a456ebd1a7c560386ec9d03fbcb13ae429d8a04902b5daf4ef10010551a2848a31c42a43dc4037705a3ccd2e329f4ae6b02bebe80e58062b374e35d099147cdb36dcbf174ced965c6697a4187f68eb482706f30b24c4312a93a576f07398cd3c5a001645885aa6fdd659a1aeebfd51fe2fe0fc8f26ca9071ee7480de50a9c0637a9c551fedb0215045a888c40d4629109d8c93be540df83c991ac8c1ac9c3e2bb798fcea1c4a4791925c01d349e4ee5832b6e3d53a6d2dc2193b78d97b3b0dd951a846d48d83ceb067518e558214fa17837b747285b80aa92200c5340bdfb727e55d72c26dbc7816073a3654b084022e2d951463a0838f4683ec74184f18af1c0fddbebe292b6615c3c12b7bc381943f2a0968df482be55dd45ce19491349f2cb982935c092cd281f24e3ccffea71f3c776dc79cd4338fc93e393d92de1456166bc1019c1252932f43408338c3a84716dc842cea2a069b057a968d73892429ffd02225550bef7faa19e580cda00ec18d1d60d8e338a9cab95aaa857c579904eea03873c96b23bdd7b2d7aa39d402d5e81cf3585d390c990ba6d786ff2e8183c15e8ca4faeebefd7630b54179a6edea020f6de31ba0b8f36173ba0caf6eace25d9aaaf2ec1ec3cf6a87caef74787447017a2f661598ca5252baccedcf3b7cab0d1bf3d13d69651d92c0dda2baed6f979b5bf5b1f9e4b60592da53cad89bdc53111aafa7a9d8874e7d9f145e128162b709db2557456a1f06789ff8508bce78b47fcd6d63914753e3c5002b1a3a31cf5ac6e42ab6638499c0b964681e854e7284a9fad3a96e5c53ead3ea652e9d0af6e6b86fd920edb6187fe22a03f47fb617ab4232155fd249922d4893d2c786abb074bd399b210e4461c75169f13b0ddedc25c4def03f9a7e6e58e1a9575fe59556cefab31114c3b59fcfe80883920aede6ca6db4db539367894bf9c0a465a0448c6b2f370a3c41e1a9790dcad74918f41dcc7f568559401bc4a471102e43489a731328aad4cb8f9cb459298569a724048b2e879964143837eee75e8a4906fc3bfa22581589f3ca9f6b9958c46a30747e54b5c7fe66f510c77d658458f2311140287b9fc421a48e17073707087f37c1098e9d60b67bac01f1b5b2feef6c1902bbe5581a1b13836f555e63f6fefcc715f4b818acb499ac17e9a3633bf97e975ac49cbf76aaa1ef85411a3fb9062195e202d03a6aed9b652bcb380424032cbaadc3a8b808414836fe68f29b62d27a22abdb5a618191707b442e9dba525fb13698c1af1d00db5927e8178eb7e69ecd21b3199b3eb10ce7d9619428070fa17664b0b8134b45c6c532c74fb32b581a63f3af2d675a74ab467703f4dfede85c7f570fe65c21f71c709db4ae94e4b6fb92314aafb88f953dc8ed3b28da983910413fc9bdc78627c98b519bcaec8de7cf21abbe44deea9c3bd4ead89433bbffaf7a00f1712c09601df224c9635612f97df35812b64fe88a6055d7a971611b358ff7c65320a4eec533a007824fbfb1f0640f5634a0875189321a692fda6e4b39c2e339e3072e181dd228603c82d232f3e7370d344512ea0c0ae8428835974600bbb5870bb922de9f47be0c4fc2f3632f4ae44c7029589f3463ea4418e51c4038788942306cf7715ff8a09bd27413211db78d165fd0a698968f1f1b1023feccb850c99596933da86be7cea9590ec25d487739a25f1552a7f06f8265111dbd65b20241557234a6ddff88a42222e3622c2bb8c0020ed5e21cbce129b734df3cccb008386f78eab530f9625117a0d6d29a396e849564a0c74ec2198da0200dd80a5071fb23f282d0ef2c9bb0290bbad54f91fb3175d38b66cd7b729e370f64948fccdd69703c99196afef66b2187f7a8b9ad13a012cd344e43dc16b3cfc28a680a9764c150c93bf1db12ccb98414aaefa426ef91360acdad7cc13be34660751a8af8f11975ebfb646e8fe5293b51553dfe22d8be3c7d1ab1c4850a362ce11d12b2048a64e8b6398eb5e2078a14eebd532542917b337033b6e82b35a8630cccf170bd73f7e6868634409f1c3351fc83ad399afb3847fbaa7beb4c534bbdbd87df1cb49c9462901f46f00d9e4a1c02c8d817ed31a8e77cdc271ca8f05498951dba32abc0153637790377917ce9d872a4853c67ea639befdc9873ebeb66c30b803d9866c8118e5c7ced668fab3a80b2d4b68f8a387dd0efe3b493888cd02479c7186eea9b58f6b4b8dca92dea056ba78a81d35b29e2586a25385e3574ba4e28ce2702fe1d781b38aae3dd7ab7375014e631421fedfef38b00bc2e622422c3e5cfaf92180c20903e1ab4e983ef8fa20201f47674ce3f85f37691c611155367075ed9e131606105973b428d2e2f7badd32bb4d460735aa5d5774835d5b25369a3959bc71e60a53696c7473101c76a36fcf84192dd3f8ed934c7485033519d1704c78aecda9c8abf0dc118879779dbf4ca888dfd4dad2a6e4bb832e0283563a6057237122f325c72eef7dc89adc842b6f305ed9b2e7b24723754a1d3a26e149b90ba51cb0207d46ccd0a69bd63ad1e95f641069f69639bdccea03428601606bc85a5f2b747a3476e3449ff2aeed12214468353a5326e322354fb56c3f1dac4fa61584ddc38c5a54c5fb8ec332085f50ebfffde12c46e0283656fd954d121191dea53d19fe287e8aee935011fa7b3a240afdb24db95f3ae77c86462028f5b054ef8af36d3f7b4453ad50f470fec39f95901a57d3b3bb01e952269983475f1d13c5a5921a8709332fa9586a0fd8f82f87d06e66e9ec2272fd67a9cfcc1f48b7a1da9a3af716216b811f03fcf4a12c8e832c4e36bcb83f7cae5dd17a385cb9a679d9cc64be8d9f972ea022cd9625664180e98c3f292e62b56b5bea3b9845ce720482f5fb6931dfbd35aa3fc816a776a4553360ec18888b1d9d9acb07edeb5908955373fe6984abc8dbc6724cfec5a908fd606169d0c5ff914e0db3f1a566602dba4265c67f3233b1623b016306d8d3bb0d8c2f47e101fb626c42849c9cd83b5dd7d148ff4a488b6929e52048cf0a61d8e2d506e5d6e70c8beac188de2c14bf8f7c385461257886baf4bf3ad78e16c657e3de62e5cb42015a2cce88516887e1995018a51fa5a2a6688613dde7edf213266e5520b73336cc775ea542908f3f76e67d2792e73afb3429aeae188e39aceb3d17652740ae37fb3639c16888336bc9f1cfea546d3443214d153405e03216ae0ebb79b949ead937daf5587409cf53b6be2428c289dedd6194c2a42719660bc3ca3706f8ba4141734b6e2734ec0e90bb267fc59b92e91a38f554097193494073fb7559b6571994dc5381b5b4ca4443665cf4da2f41dbc737b554a4266c5a40bccc36f94585788eaceddbcc6a082cc1265811c7546f562743264fd8c6926b4f28a7136583057701b8386ba7821ffb12ba742d8a475765c58c1ab68d44adf9a45127a9d0a456152f2ec1317c3c7dda48944fa8595d0e86662c311e6970bb6e51589105def2032e775d5025553d3f44c89044272f870aa7b19a7f71ae0e5d184b6cb7d9e697ae8a56ac1d5619e0d9f1673072928e201975ec84417c83c0fd611cde590995d7d44ad1dbc09c98e178e48bc861b37e46c5257c8a66f6323a1a33958b14eb811ab6c8827dbf16955d01b5ce46bd1e1b19afd48a70310dfbc54d8bb6ed68ec4612c71145bff3a1833e1bb8c52962ec51219abbb58de0ba4c6ca3105fc2c181809102df98ba0e6c22ed21c6d5b30fa2432e8065d5b2b98b95800d6e5600aef541990321bf28be3cff705457c9c00d2a727352e92d102b15a9f7105457b27f93111bf4552ae1588e69e7656e2f1cb723c969c6e8a886564bee122eab57b145fbb2781dea3c099633a80141dfddefa16d93ed7becedff15f196dbd8adff8c47affc10d75aec5e9e03828e371787276193cae56253fc54eb9d1bf925152ad5f3b671f3944f9f61ab35f52b3790c655dc6f0f30ce33169b563f85057b1235fbd62c1d0f9ae9642c639c951bde2baf544117687ab8a3682206ab35b010000"; + let mut tx = ConfidentialTransaction::from_str(tx_hex).expect("Fail"); + + let outpoint = OutPoint::from_str( + "03f8801068f3d2c1bbb2c6eaf295e845f9a265615a229adf9f64215ad63afcb7", + 0, + ) + .expect("Fail"); + let privkey = + Privkey::from_str("cU4KjNUT7GjHm7CkjRjG46SzLrXHXoH3ekXmqa2jTCFPMkQ64sw1").expect("Fail"); + let sighash_type = SigHashType::AllPlusRangeproof; + let value = ConfidentialValue::from_str( + "09b6e7605917e27f35690dcae922f664c8a3b057e2c6249db6cd304096aa87a226", + ) + .expect("Fail"); + tx = tx + .sign_with_privkey( + &outpoint, + &HashType::P2wpkh, + &privkey, + &sighash_type, + &value, + ) + .expect("Fail"); + assert_eq!(expect_tx_hex, tx.to_str()); + } + + #[test] + fn split_txout_test() { + let dummy_asset = ConfidentialAsset::default(); + let mut tx = ConfidentialTransaction::from_str( + "02000000000109c4149d4e59119f2b11b3e160b02694bc4ecbf56f6de4ab587128f86bf4e7d30000000000ffffffff0201f38611eb688e6fcd06f25e2faf52b9f98364dc14c379ab085f1b57d56b4b1a6f010000000005ee3fe00374eb131b54a7b528e5449b3827bcaa5069c259346810f20cf9079bd17b32fe481976a914d753351535a2a55f33ab39bbd6c70a55d46904e788ac01f38611eb688e6fcd06f25e2faf52b9f98364dc14c379ab085f1b57d56b4b1a6f01000000000007a120000000000000").expect("Fail"); + tx = tx + .split_txout( + 0, + &[ + ConfidentialTxOutData::from_address( + 9000000, + &dummy_asset, + &Address::from_str("ert1qz33wef9ehrvd7c64p27jf5xtvn50946xeekx50").expect("Fail"), + ), + ConfidentialTxOutData::from_address( + 500000, + &dummy_asset, + &Address::from_str("XWMioJVK77vhKHgnSpaCcSBDgf93LFHzYg").expect("Fail"), + ), + ], + ) + .expect("Fail"); + assert_eq!( + "02000000000109c4149d4e59119f2b11b3e160b02694bc4ecbf56f6de4ab587128f86bf4e7d30000000000ffffffff0401f38611eb688e6fcd06f25e2faf52b9f98364dc14c379ab085f1b57d56b4b1a6f0100000000055d4a800374eb131b54a7b528e5449b3827bcaa5069c259346810f20cf9079bd17b32fe481976a914d753351535a2a55f33ab39bbd6c70a55d46904e788ac01f38611eb688e6fcd06f25e2faf52b9f98364dc14c379ab085f1b57d56b4b1a6f01000000000007a120000001f38611eb688e6fcd06f25e2faf52b9f98364dc14c379ab085f1b57d56b4b1a6f010000000000895440001600141462eca4b9b8d8df63550abd24d0cb64e8f2d74601f38611eb688e6fcd06f25e2faf52b9f98364dc14c379ab085f1b57d56b4b1a6f01000000000007a1200017a914d081b8e259b744aa903e1831cfce8956941273ce8700000000", + tx.to_str()); + + tx = ConfidentialTransaction::from_str( + "020000000002a38845c1a19b389f27217b91e2120273b447db3e595bba628f0be833f301a24a0000000000ffffffffa38845c1a19b389f27217b91e2120273b447db3e595bba628f0be833f301a24a0200000000ffffffff020125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000001c8400000125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000befe33cc397c03f234757d0e00e6a7a7a3b4b2b31fb0328d7b9f755cd1093d9f61892fef3116871976a91435ef6d4b59f26089dfe2abca21408e15fee42a3388ac00000000").expect("Fail"); + tx = tx.split_txout(1, &[ + ConfidentialTxOutData::from_confidential_address( + 9997999992700, &dummy_asset, + &ConfidentialAddress::from_str( + "lq1qqf6e92446smp3hdp87a8rcue8nt4z7n39576f9nycphwr0farac2laprx8zp3m69z7axgjkka87fj6q66sunwxxytxeqzrd9w").expect("Fail") + ), + ConfidentialTxOutData::from_confidential_address( + 1000000000, &dummy_asset, + &ConfidentialAddress::from_str( + "Azpn9vbC1Sjvwc2evnjaZjEHPdQxvdr4sTew6psnwxoomvdDBfpfDJNXU4Zthvhy1TkUgX4USjTZpjSL").expect("Fail") + ), + ]).expect("Fail"); + assert_eq!( + "020000000002a38845c1a19b389f27217b91e2120273b447db3e595bba628f0be833f301a24a0000000000ffffffffa38845c1a19b389f27217b91e2120273b447db3e595bba628f0be833f301a24a0200000000ffffffff040125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000001c8400000125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000b5e620f4800003f234757d0e00e6a7a7a3b4b2b31fb0328d7b9f755cd1093d9f61892fef3116871976a91435ef6d4b59f26089dfe2abca21408e15fee42a3388ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a0100000917d73cef7c027592aab5d43618dda13fba71e3993cd7517a712d3da49664c06ee1bd3d1f70af160014f42331c418ef4517ba644ad6e9fc99681ad439370125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000003b9aca00027592aab5d43618dda13fba71e3993cd7517a712d3da49664c06ee1bd3d1f70af17a9149ec42b6cfa1b0bc3f55f07af29867057cb0b8a2e8700000000", + tx.to_str()); + + tx = ConfidentialTransaction::from_str("020000000002a38845c1a19b389f27217b91e2120273b447db3e595bba628f0be833f301a24a0000000000ffffffffa38845c1a19b389f27217b91e2120273b447db3e595bba628f0be833f301a24a0200000000ffffffff020125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000001c8400000125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000befe33cc397c03f234757d0e00e6a7a7a3b4b2b31fb0328d7b9f755cd1093d9f61892fef3116871976a91435ef6d4b59f26089dfe2abca21408e15fee42a3388ac00000000").expect("Fail"); + tx = tx + .split_txout( + 1, + &[ + ConfidentialTxOutData::from_locking_script( + 9997999992700, + &dummy_asset, + &Script::from_hex("0014f42331c418ef4517ba644ad6e9fc99681ad43937").expect("Fail"), + &ConfidentialNonce::from_str( + "027592aab5d43618dda13fba71e3993cd7517a712d3da49664c06ee1bd3d1f70af", + ) + .expect("Fail"), + ), + ConfidentialTxOutData::from_locking_script( + 1000000000, + &dummy_asset, + &Script::from_hex("a9149ec42b6cfa1b0bc3f55f07af29867057cb0b8a2e87").expect("Fail"), + &ConfidentialNonce::from_str( + "027592aab5d43618dda13fba71e3993cd7517a712d3da49664c06ee1bd3d1f70af", + ) + .expect("Fail"), + ), + ], + ) + .expect("Fail"); + assert_eq!("020000000002a38845c1a19b389f27217b91e2120273b447db3e595bba628f0be833f301a24a0000000000ffffffffa38845c1a19b389f27217b91e2120273b447db3e595bba628f0be833f301a24a0200000000ffffffff040125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000001c8400000125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000b5e620f4800003f234757d0e00e6a7a7a3b4b2b31fb0328d7b9f755cd1093d9f61892fef3116871976a91435ef6d4b59f26089dfe2abca21408e15fee42a3388ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a0100000917d73cef7c027592aab5d43618dda13fba71e3993cd7517a712d3da49664c06ee1bd3d1f70af160014f42331c418ef4517ba644ad6e9fc99681ad439370125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000003b9aca00027592aab5d43618dda13fba71e3993cd7517a712d3da49664c06ee1bd3d1f70af17a9149ec42b6cfa1b0bc3f55f07af29867057cb0b8a2e8700000000", + tx.to_str()); + } + + #[test] + fn update_witness_stack_test() { + let mut tx = ConfidentialTransaction::from_str("0200000001010e3c60901da7ffc518253e5736b9b73fd8aa5f79f249fa75bfe662c0f6ee42c301000000171600140c2eade9f3c984d0b2cedc79075a5793b0f5ce05ffffffff0201f38611eb688e6fcd06f25e2faf52b9f98364dc14c379ab085f1b57d56b4b1a6f010000000005f5e100001976a914b3c03c18599d13a481d1eb8a0ac2cc156564b4c688ac01f38611eb688e6fcd06f25e2faf52b9f98364dc14c379ab085f1b57d56b4b1a6f01000000000007a1200000000000000000024730440220437d443d290dcbd21b9dfdc612ac6cc5134f5acca19aa3c90b870ec41480839d02205662e29994c06cbeba70640aa74c7a4aafa50dba52ff45117800a1680240af6b0104111111110000000000").expect("Fail"); + tx = tx + .update_witness_stack( + &OutPoint::from_str( + "c342eef6c062e6bf75fa49f2795faad83fb7b936573e2518c5ffa71d90603c0e", + 1, + ) + .expect("Fail"), + 1, + &ByteData::from_str("02d8595abf5033d37a8a04947a537e8b28e2cb863e1ccd742012334c47e2c87a09") + .expect("Fail"), + ) + .expect("Fail"); + assert_eq!( + "0200000001010e3c60901da7ffc518253e5736b9b73fd8aa5f79f249fa75bfe662c0f6ee42c301000000171600140c2eade9f3c984d0b2cedc79075a5793b0f5ce05ffffffff0201f38611eb688e6fcd06f25e2faf52b9f98364dc14c379ab085f1b57d56b4b1a6f010000000005f5e100001976a914b3c03c18599d13a481d1eb8a0ac2cc156564b4c688ac01f38611eb688e6fcd06f25e2faf52b9f98364dc14c379ab085f1b57d56b4b1a6f01000000000007a1200000000000000000024730440220437d443d290dcbd21b9dfdc612ac6cc5134f5acca19aa3c90b870ec41480839d02205662e29994c06cbeba70640aa74c7a4aafa50dba52ff45117800a1680240af6b012102d8595abf5033d37a8a04947a537e8b28e2cb863e1ccd742012334c47e2c87a090000000000", + tx.to_str()); + } + + #[test] + fn update_pegin_witness_stack_test() { + let mut tx = ConfidentialTransaction::from_str( + "0200000001017926299350fdc2f4d0da1d4f0fbbd3789d29f9dc016358ae42463c0cebf393f30000004000ffffffff020125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a0100000002540ba97c0017a91414b71442e11941fd7807a82eabee13d6ec171ed9870125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000003a84000000000000000000060800e40b54020000002025b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a2006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f16001412dcdeef890f60967896391c95b0e02c9258dfe5fdda060200000000010a945efd42ce42de413aa7398a95c35facc14ec5d35bb23e5f980014e94ab96a620000000017160014ca2041536307bbe086e8c7fe8563e1c9b9b6eb84feffffffe50b46ecadb5cc52a7ef149a23323464353415f02d7b4a943963b26a9beb2a030000000017160014ca2041536307bbe086e8c7fe8563e1c9b9b6eb84feffffff67173609ca4c13662356a2507c71e5d497baeff56a3c42af989f3b270bc870560000000017160014ca2041536307bbe086e8c7fe8563e1c9b9b6eb84feffffff784a9fd151fe2808949fae18afcf52244a77702b9a83950bc7ec52a8239104850000000017160014ca2041536307bbe086e8c7fe8563e1c9b9b6eb84feffffff259618278cecbae1bed8b7806133d14987c3c6118d2744707f509c58ea2c0e870000000017160014ca2041536307bbe086e8c7fe8563e1c9b9b6eb84feffffff5c30c2fdcb6ce0b666120777ec18ce5211dd4741f40f033648432694b0919da50000000017160014a8a7c0032d1d283e39889861b3f05156e379cfb6feffffffbb0f857d4b143c74c7fdb678bf41b65e7e3f2e7644b3613ae6370d21c0882ad60000000017160014a8a7c0032d1d283e39889861b3f05156e379cfb6feffffffbce488c283e07bf364edb5057e020aa3d137d8d6130711dc12f03f35564945680000000017160014ca2041536307bbe086e8c7fe8563e1c9b9b6eb84feffffff258cb927989780ac92a3952ffd1f54e9b65e59fb07219eb106840b5d76b547fb0000000017160014ca2041536307bbe086e8c7fe8563e1c9b9b6eb84feffffffe98ec686efbca9bdd18ae85a3a8235a607e1cfb6138bac1461d400cbbabbe00f0000000017160014a8a7c0032d1d283e39889861b3f05156e379cfb6feffffff0100e40b540200000017a91472c44f957fc011d97e3406667dca5b1c930c4026870247304402206b4de54956e864dfe3ff3a4957e329cf171e919498bb8f98c242bef7b0d5e3350220505355401a500aabf193b93492d6bceb93c3b183034f252d65a139245c7486a601210281465587e09d80f5a7b8ce94bab4a4571dc8cff4483cc9eb89e76ecfa650b6f40247304402200fc48c7b5bd6de74c951250c60e8e6c9d3a605dc557bdc93ce86e85d2f27834a02205d2a8768adad669683416d1126c8537ab1eb36b0e83d5d9e6a583297b7f9d2cb01210281465587e09d80f5a7b8ce94bab4a4571dc8cff4483cc9eb89e76ecfa650b6f40247304402207ad97500fbe6049d559a1e10586cd0b1f02baeb98dc641a971a506a57288aa0002202a6646bc4262904f6d1a9288c12ff586b5a674f5a351dfaba2698c8b8265366f01210281465587e09d80f5a7b8ce94bab4a4571dc8cff4483cc9eb89e76ecfa650b6f4024730440220271e41a1e8f953b6817333e43d6a5e2924b291d52120011a5f7f1fb8049ae41b02200f1a25ed9da813122caadf8edf8d01da190f9407c2b61c27d4b671e07136bce701210281465587e09d80f5a7b8ce94bab4a4571dc8cff4483cc9eb89e76ecfa650b6f402473044022050291184dcd4733de6e6a43d9efb1e21e7d2c563e9138481f04010f3acbb139f02206c01c3bfe4e5b71c4aac524a18f35e25ae7306ca110b3c3b079ae6da2b0a0a5701210281465587e09d80f5a7b8ce94bab4a4571dc8cff4483cc9eb89e76ecfa650b6f402473044022045a188c10aec4f1a3a6c8a3a3c9f7d4dc63b9eacc011839c907d1c5da206a1390220399ca60516204efd9d220eaa0c804867137133c4d70780223fdde699288af3790121031c01fd031bc09b385d138b3b2f44ec04c03934b66f6485f37a17b4899f1b8d7802473044022053621a5c74b313c648d179041c154152372060941d9c9080340eb913358b705602201ac178f639360356ca7d75656d92bd7801d976e74bd5d2e30d6310a94940d0bc0121031c01fd031bc09b385d138b3b2f44ec04c03934b66f6485f37a17b4899f1b8d780247304402207b4a7a271a8fc03e8045ca367cb64046fa06e5b13a105e67efe7dd6571503fcb022072852e1c3f87eeac039601a0df855fb5d65bbdcd3ad95ff96bfc7b534fd89f7601210281465587e09d80f5a7b8ce94bab4a4571dc8cff4483cc9eb89e76ecfa650b6f402473044022037e9f0943a79e155a57526e251cfd39e004552b76c0de892448eb939d2d12fdf02203a02f0045e8f90739eddc06c026c95b4a653aeb89528d851ab75952fd7db07b801210281465587e09d80f5a7b8ce94bab4a4571dc8cff4483cc9eb89e76ecfa650b6f402473044022057a9953ba83d5e710fc64e1c533d81b0913f434b3e1c865cebd6cb106e09fa77022012930afe63ae7f1115a2f3b13039e71387fc2d4ed0e36eaa7be55a754c8c84830121031c01fd031bc09b385d138b3b2f44ec04c03934b66f6485f37a17b4899f1b8d78130e00009700000020fe3b574c1ce6d5cb68fc518e86f7976e599fafc0a2e5754aace7ca16d97a7c78ef9325b8d4f0a4921e060fc5e71435f46a18fa339688142cd4b028c8488c9f8dd1495b5dffff7f200200000002000000024a180a6822abffc3b1080c49016899c6dac25083936df14af12f58db11958ef27926299350fdc2f4d0da1d4f0fbbd3789d29f9dc016358ae42463c0cebf393f3010500000000").expect("Fail"); + tx = tx.update_pegin_witness_stack( + &OutPoint::from_str("f393f3eb0c3c4642ae586301dcf9299d78d3bb0f4f1ddad0f4c2fd5093292679", 0).expect("Fail"), + 5, + &ByteData::from_str("000000204e28f541a3b2400720e1b7034c037e98e4806deb13f93927bf325eea3bcd5436a701767035de031ba3471b589ea214b54f0baa26d1118d2fb13a679f7b4b472e71128b5dffff7f2000000000020000000237f9a1552febc7194d5fac93e52a10dde4009ff485fbcc172b22d621b58c2d69109d857925ebfb477c6e6e70069814f279e4a6d871af9165631df4e5982e22710105").expect("Fail"), + ).expect("Fail"); + assert_eq!( + "0200000001017926299350fdc2f4d0da1d4f0fbbd3789d29f9dc016358ae42463c0cebf393f30000004000ffffffff020125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a0100000002540ba97c0017a91414b71442e11941fd7807a82eabee13d6ec171ed9870125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000003a84000000000000000000060800e40b54020000002025b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a2006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f16001412dcdeef890f60967896391c95b0e02c9258dfe5fdda060200000000010a945efd42ce42de413aa7398a95c35facc14ec5d35bb23e5f980014e94ab96a620000000017160014ca2041536307bbe086e8c7fe8563e1c9b9b6eb84feffffffe50b46ecadb5cc52a7ef149a23323464353415f02d7b4a943963b26a9beb2a030000000017160014ca2041536307bbe086e8c7fe8563e1c9b9b6eb84feffffff67173609ca4c13662356a2507c71e5d497baeff56a3c42af989f3b270bc870560000000017160014ca2041536307bbe086e8c7fe8563e1c9b9b6eb84feffffff784a9fd151fe2808949fae18afcf52244a77702b9a83950bc7ec52a8239104850000000017160014ca2041536307bbe086e8c7fe8563e1c9b9b6eb84feffffff259618278cecbae1bed8b7806133d14987c3c6118d2744707f509c58ea2c0e870000000017160014ca2041536307bbe086e8c7fe8563e1c9b9b6eb84feffffff5c30c2fdcb6ce0b666120777ec18ce5211dd4741f40f033648432694b0919da50000000017160014a8a7c0032d1d283e39889861b3f05156e379cfb6feffffffbb0f857d4b143c74c7fdb678bf41b65e7e3f2e7644b3613ae6370d21c0882ad60000000017160014a8a7c0032d1d283e39889861b3f05156e379cfb6feffffffbce488c283e07bf364edb5057e020aa3d137d8d6130711dc12f03f35564945680000000017160014ca2041536307bbe086e8c7fe8563e1c9b9b6eb84feffffff258cb927989780ac92a3952ffd1f54e9b65e59fb07219eb106840b5d76b547fb0000000017160014ca2041536307bbe086e8c7fe8563e1c9b9b6eb84feffffffe98ec686efbca9bdd18ae85a3a8235a607e1cfb6138bac1461d400cbbabbe00f0000000017160014a8a7c0032d1d283e39889861b3f05156e379cfb6feffffff0100e40b540200000017a91472c44f957fc011d97e3406667dca5b1c930c4026870247304402206b4de54956e864dfe3ff3a4957e329cf171e919498bb8f98c242bef7b0d5e3350220505355401a500aabf193b93492d6bceb93c3b183034f252d65a139245c7486a601210281465587e09d80f5a7b8ce94bab4a4571dc8cff4483cc9eb89e76ecfa650b6f40247304402200fc48c7b5bd6de74c951250c60e8e6c9d3a605dc557bdc93ce86e85d2f27834a02205d2a8768adad669683416d1126c8537ab1eb36b0e83d5d9e6a583297b7f9d2cb01210281465587e09d80f5a7b8ce94bab4a4571dc8cff4483cc9eb89e76ecfa650b6f40247304402207ad97500fbe6049d559a1e10586cd0b1f02baeb98dc641a971a506a57288aa0002202a6646bc4262904f6d1a9288c12ff586b5a674f5a351dfaba2698c8b8265366f01210281465587e09d80f5a7b8ce94bab4a4571dc8cff4483cc9eb89e76ecfa650b6f4024730440220271e41a1e8f953b6817333e43d6a5e2924b291d52120011a5f7f1fb8049ae41b02200f1a25ed9da813122caadf8edf8d01da190f9407c2b61c27d4b671e07136bce701210281465587e09d80f5a7b8ce94bab4a4571dc8cff4483cc9eb89e76ecfa650b6f402473044022050291184dcd4733de6e6a43d9efb1e21e7d2c563e9138481f04010f3acbb139f02206c01c3bfe4e5b71c4aac524a18f35e25ae7306ca110b3c3b079ae6da2b0a0a5701210281465587e09d80f5a7b8ce94bab4a4571dc8cff4483cc9eb89e76ecfa650b6f402473044022045a188c10aec4f1a3a6c8a3a3c9f7d4dc63b9eacc011839c907d1c5da206a1390220399ca60516204efd9d220eaa0c804867137133c4d70780223fdde699288af3790121031c01fd031bc09b385d138b3b2f44ec04c03934b66f6485f37a17b4899f1b8d7802473044022053621a5c74b313c648d179041c154152372060941d9c9080340eb913358b705602201ac178f639360356ca7d75656d92bd7801d976e74bd5d2e30d6310a94940d0bc0121031c01fd031bc09b385d138b3b2f44ec04c03934b66f6485f37a17b4899f1b8d780247304402207b4a7a271a8fc03e8045ca367cb64046fa06e5b13a105e67efe7dd6571503fcb022072852e1c3f87eeac039601a0df855fb5d65bbdcd3ad95ff96bfc7b534fd89f7601210281465587e09d80f5a7b8ce94bab4a4571dc8cff4483cc9eb89e76ecfa650b6f402473044022037e9f0943a79e155a57526e251cfd39e004552b76c0de892448eb939d2d12fdf02203a02f0045e8f90739eddc06c026c95b4a653aeb89528d851ab75952fd7db07b801210281465587e09d80f5a7b8ce94bab4a4571dc8cff4483cc9eb89e76ecfa650b6f402473044022057a9953ba83d5e710fc64e1c533d81b0913f434b3e1c865cebd6cb106e09fa77022012930afe63ae7f1115a2f3b13039e71387fc2d4ed0e36eaa7be55a754c8c84830121031c01fd031bc09b385d138b3b2f44ec04c03934b66f6485f37a17b4899f1b8d78130e000097000000204e28f541a3b2400720e1b7034c037e98e4806deb13f93927bf325eea3bcd5436a701767035de031ba3471b589ea214b54f0baa26d1118d2fb13a679f7b4b472e71128b5dffff7f2000000000020000000237f9a1552febc7194d5fac93e52a10dde4009ff485fbcc172b22d621b58c2d69109d857925ebfb477c6e6e70069814f279e4a6d871af9165631df4e5982e2271010500000000", + tx.to_str()); + } + + #[test] + fn get_txout_index_test() { + let tx = ConfidentialTransaction::from_str("020000000002a38845c1a19b389f27217b91e2120273b447db3e595bba628f0be833f301a24a0000000000ffffffffa38845c1a19b389f27217b91e2120273b447db3e595bba628f0be833f301a24a0200000000ffffffff030125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000001c8400000125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000befe33cc397c03f234757d0e00e6a7a7a3b4b2b31fb0328d7b9f755cd1093d9f61892fef3116871976a91435ef6d4b59f26089dfe2abca21408e15fee42a3388ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000befe33cc397c03f234757d0e00e6a7a7a3b4b2b31fb0328d7b9f755cd1093d9f61892fef3116871976a91435ef6d4b59f26089dfe2abca21408e15fee42a3388ac00000000").expect("Fail"); + let indexes = tx + .get_txout_indexes_by_address( + &Address::from_str("2deLw2MsbXTr44ZXKBS91midF2WzJPfQ8cz").expect("Fail"), + ) + .expect("Fail"); + assert_eq!(2, indexes.len()); + if indexes.len() == 2 { + assert_eq!(1, indexes[0]); + assert_eq!(2, indexes[1]); + } + } + + #[test] + fn issue_asset_test() { + let blinding_key = + Privkey::from_str("1c9c3636830860edfe1cc70649417f33b0799959ea7197a4e75a5ba2a326ddd3") + .expect("Fail"); + let confidential_address = ConfidentialAddress::from_str( + "CTErYaEfjCbu7recW9N2PoJq4Qt6XAqSoEAq31vfjGjJvvLV3hRGnfGgFuyJw9AqYGgZh57nYLjzHGcM", + ) + .expect("Fail"); + let token_amount: i64 = 1000000000; + + let mut tx = ConfidentialTransaction::from_str("020000000001db3e7442a3a033e04def374fe6e3ce4351122655705e55e9fb02c7135508775e0000000000ffffffff02017981c1f171d7973a1fd922652f559f47d6d1506a4be2394b27a54951957f6c1801000000003b9328e00017a9149d4a252d04e5072497ef2ac59574b1b14a7831b187017981c1f171d7973a1fd922652f559f47d6d1506a4be2394b27a54951957f6c1801000000000007a120000000000000").expect("Fail"); + let mut issue_data = IssuanceOutputData::default(); + tx = tx + .set_issuance( + &OutPoint::from_str( + "5e77085513c702fbe9555e705526125143cee3e64f37ef4de033a0a342743edb", + 0, + ) + .expect("Fail"), + &IssuanceInputData { + asset_amount: 500000000, + asset_address: InputAddress::CtAddr( + ConfidentialAddress::from_str( + "CTEmp5tY22tBaWCEUiEUReuRcQV95geubpwi1By249nnCbFU94iv75V1Y1ESRET7gU8JqbxrBTSjkaUx", + ) + .expect("Fail"), + ), + token_amount, + token_address: InputAddress::CtAddr(confidential_address), + has_blind: false, + ..IssuanceInputData::default() + }, + &mut issue_data, + ) + .expect("Fail"); + assert_eq!( + "020000000001db3e7442a3a033e04def374fe6e3ce4351122655705e55e9fb02c7135508775e0000008000ffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000001dcd650001000000003b9aca0004017981c1f171d7973a1fd922652f559f47d6d1506a4be2394b27a54951957f6c1801000000003b9328e00017a9149d4a252d04e5072497ef2ac59574b1b14a7831b187017981c1f171d7973a1fd922652f559f47d6d1506a4be2394b27a54951957f6c1801000000000007a12000000154d634b51f6463ef827c1aca10ebf9758ca38ed0b969d6be1f5e28afe021406e01000000001dcd6500024e93dfae62a90ff7ebf8813fd9ffcf1d22115b88c9020ac3a144eccef98e8b981976a9148bba9241b14f785130e7ff186901997a5a1cc65688ac019f8e8c650b600dd566a087727cf24c01a02095c0e4329c82f27bceb31cc880c901000000003b9aca0002fd54c734e48c544c3c3ad1aab0607f896eb95e23e7058b174a580826a7940ad81976a914e55f5b7134f05f779d0913413b6e0cb7d208780488ac00000000", + tx.to_str()); + assert_eq!( + "6e4021e0af285e1fbed669b9d08ea38c75f9eb10ca1a7c82ef63641fb534d654", + issue_data.asset.as_str() + ); + assert_eq!( + "c980c81cb3ce7bf2829c32e4c09520a0014cf27c7287a066d50d600b658c8e9f", + issue_data.token.as_str() + ); + + // blind + let base_asset = ConfidentialAsset::from_str( + "186c7f955149a5274b39e24b6a50d1d6479f552f6522d91f3a97d771f1c18179", + ) + .expect("Fail"); + let outpoint = OutPoint::from_str( + "5e77085513c702fbe9555e705526125143cee3e64f37ef4de033a0a342743edb", + 0, + ) + .expect("Fail"); + tx = tx + .blind( + &[ElementsUtxoData { + asset_blind_factor: BlindFactor::from_str( + "28093061ab2e407c6015f8cb33f337ffb118eaf3beb2d254de64203aa27ecbf7", + ) + .expect("Fail"), + amount_blind_factor: BlindFactor::from_str( + "f87734c279533d8beba96c5369e169e6caf5f307a34d72d4a0f9c9a7b8f8f269", + ) + .expect("Fail"), + ..ElementsUtxoData::from_outpoint(&outpoint, 1000000000, &base_asset).expect("Fail") + }], + &IssuanceKeyMap::default(), + &[], + &KeyIndexMap::default(), + &BlindOption::default(), + ) + .expect("Fail"); + tx = tx + .sign_with_privkey( + &outpoint, + &HashType::P2wpkh, + &Privkey::from_str("cU4KjNUT7GjHm7CkjRjG46SzLrXHXoH3ekXmqa2jTCFPMkQ64sw1").expect("Fail"), + &SigHashType::All, + &ConfidentialValue::from_amount(1000000000).expect("Fail"), + ) + .expect("Fail"); + + let fee_utxo_index = 0; + let token_index = 3; + + let unblind_data = tx.unblind_txout(token_index, &blinding_key).expect("Fail"); + assert_eq!(issue_data.token.as_str(), unblind_data.asset.as_str()); + assert_eq!(token_amount, unblind_data.amount.to_amount()); + assert_ne!( + "0000000000000000000000000000000000000000000000000000000000000000", + unblind_data.asset_blind_factor.to_hex() + ); + assert_ne!( + "0000000000000000000000000000000000000000000000000000000000000000", + unblind_data.amount_blind_factor.to_hex() + ); + + // create reissue base tx + let mut reissue_tx = ConfidentialTransaction::create_tx( + 2, 0, + &[ + TxInData::new( + &OutPoint::new(tx.as_txid(), fee_utxo_index)), + TxInData::new( + &OutPoint::new(tx.as_txid(), token_index)), + ], &[ + ConfidentialTxOutData::from_confidential_address( + 999000000, &base_asset, + &ConfidentialAddress::from_str( + "el1qqf4026u44983693n58xhxd9ej6l0q4seka289pluyqr4seext7v5jl9xs3ya8x54m2guds5rsu04s7m5k3wpv3dr07xgxdla8kdvflhxv603xs3tm3wz" + ).expect("Fail"), + ), + ConfidentialTxOutData::from_fee(500000, &base_asset), + ConfidentialTxOutData::from_confidential_address( + token_amount, &issue_data.token, + &ConfidentialAddress::from_str( + "AzpotonWHeKeBs4mZfXbnVvNCR23oKZ5UzpccaAZeP3igcWZLT2anN1QdrTYPMcFBMRD5411hS7pmATo" + ).expect("Fail"), + ), + ], + ).expect("Fail"); + let mut reissue_txout_data = ConfidentialTxOutData::default(); + reissue_tx = reissue_tx + .set_reissuance( + &OutPoint::new(tx.as_txid(), token_index), + &ReissuanceInputData { + blinding_nonce: unblind_data.asset_blind_factor, + entropy: issue_data.entropy.clone(), + asset_amount: 300000000, + asset_address: InputAddress::CtAddr( + ConfidentialAddress::from_str( + "AzpkYfJkupsG2p8Px1BafsjzaxKEoMUFKypr2x7jd6kZQHcRyx6zYtZHCUEEzzSayr8Kj9JPNnWceL7W", + ) + .expect("Fail"), + ), + ..ReissuanceInputData::default() + }, + &mut reissue_txout_data, + ) + .expect("Fail"); + assert_eq!(issue_data.asset.as_str(), reissue_txout_data.asset.as_str()); + + let txout = reissue_tx.get_txout_list()[3].clone(); + assert_eq!(issue_data.asset.as_str(), txout.asset.as_str()); + assert_eq!(300000000, txout.value.to_amount()); + assert_eq!( + "a914f70fa95299789b76e11b35164ad9ff94b24954f587", + txout.locking_script.to_hex() + ); + } + + #[test] + fn pegin_test() { + // fedpeg script + let fedpeg_script = Script::from_hex( + "522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb53ae").expect("Fail"); + let privkey = + Privkey::from_str("cUfipPioYnHU61pfYTH9uuNoswRXx8rtzXhJZrsPeVV1LRFdTxvp").expect("Fail"); + let pubkey = privkey.get_pubkey().expect("Fail"); + + // create pegin address + let pegin_data = Address::pegin_by_pubkey( + &fedpeg_script, + &pubkey, + &HashType::P2shP2wsh, + &Network::Regtest, + ) + .expect("Fail"); + assert_eq!( + "2MvmzAFKZ5xh44vyb7qY7NB2AoDuS55rVFW", + pegin_data.address.to_str() + ); + assert_eq!( + "0014e794713e386d83f32baa0e9d03e47c0839dc57a8", + pegin_data.claim_script.to_hex() + ); + + // create bitcoin tx + let amount: i64 = 100000000; + let pegin_amount = amount - 500; + let outpoint = OutPoint::from_str( + "ea9d5a9e974af1d167305aa6ee598706d63274e8a40f4f33af97db37a7adde4c", + 0, + ) + .expect("Fail"); + let mut tx = Transaction::create_tx( + 2, + 0, + &[TxInData::new(&outpoint)], + &[TxOutData::from_address(pegin_amount, &pegin_data.address)], + ) + .expect("Fail"); + + tx = tx + .append_utxo_list(&[UtxoData::from_descriptor( + &outpoint, + amount, + &Descriptor::new( + "wpkh(cNYKHjNc33ZyNMcDck59yWm1CYohgPhr2DYyCtmWNkL6sqb5L1rH)", + &Network::Testnet, + ) + .expect("Fail"), + )]) + .expect("Fail"); + tx = tx + .sign_with_privkey_by_utxo_list( + &outpoint, + &Privkey::from_str("cNYKHjNc33ZyNMcDck59yWm1CYohgPhr2DYyCtmWNkL6sqb5L1rH").expect("Fail"), + &SigHashType::All, + &[], + &[], + ) + .expect("Fail"); + assert_eq!( + "020000000001014cdeada737db97af334f0fa4e87432d6068759eea65a3067d1f14a979e5a9dea0000000000ffffffff010cdff5050000000017a91426b9ba9cf5d822b70cf490ad0394566f9db20c63870247304402200b3ca71e82551a333fe5c8ce9a8f8454eb8f08aa194180e5a87c79ccf2e46212022065c1f2a363ebcb155a80e234258394140d08f6ab807581953bb21a58f2d229a6012102fd54c734e48c544c3c3ad1aab0607f896eb95e23e7058b174a580826a7940ad800000000", + tx.to_str()); + assert_eq!( + "12708508f0baf8691a3d7e22fd19afbf9bd8bf0d358e3310838bcc7916539c7b", + tx.as_txid().to_hex() + ); + + let pegin_index = 0; + let txout_proof = ByteData::from_str( + "00000020fe3b574c1ce6d5cb68fc518e86f7976e599fafc0a2e5754aace7ca16d97a7c78ef9325b8d4f0a4921e060fc5e71435f46a18fa339688142cd4b028c8488c9f8dd1495b5dffff7f200200000002000000024a180a6822abffc3b1080c49016899c6dac25083936df14af12f58db11958ef27926299350fdc2f4d0da1d4f0fbbd3789d29f9dc016358ae42463c0cebf393f30105").expect("Fail"); + + // create pegin tx + let genesis_block_hash = + BlockHash::from_str("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206") + .expect("Fail"); + let asset = ConfidentialAsset::from_str( + "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225", + ) + .expect("Fail"); + + let pegin_outpoint = OutPoint::new(tx.as_txid(), pegin_index); + let mut pegin_tx = ConfidentialTransaction::new(2, 0).expect("Fail"); + pegin_tx = pegin_tx + .add_pegin_input( + &pegin_outpoint, + &PeginInputData { + amount: pegin_amount, + asset: asset.clone(), + mainchain_genesis_block_hash: genesis_block_hash, + claim_script: pegin_data.claim_script, + transaction: tx, + txout_proof, + }, + ) + .expect("Fail"); + + pegin_tx = pegin_tx.append_data( + &[], + &[ + ConfidentialTxOutData::from_confidential_address( + 99998500, &asset, + &ConfidentialAddress::from_str( + "el1qqtl9a3n6878ex25u0wv8u5qlzpfkycc0cftk65t52pkauk55jqka0fajk8d80lafn4t9kqxe77cu9ez2dyr6sq54lwy009uex").expect("Fail"), + ), + ConfidentialTxOutData::from_fee(1000, &asset), + ConfidentialTxOutData::from_locking_script( + 0, &asset, &Script::from_asm("OP_RETURN").expect("Fail"), + &ConfidentialNonce::from_str( + "03662a01c232918c9deb3b330272483c3e4ec0c6b5da86df59252835afeb4ab5f9").expect("Fail"), + ), + ], + ).expect("Fail"); + + // add dummy output (for blind) + assert_eq!( + "0200000001017b9c531679cc8b8310338e350dbfd89bbfaf19fd227e3d1a69f8baf0088570120000004000ffffffff030125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000005f5db2402fe5ec67a3f8f932a9c7b987e501f105362630fc2576d5174506dde5a94902dd7160014a7b2b1da77ffa99d565b00d9f7b1c2e44a6907a80125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a0100000000000003e800000125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000000003662a01c232918c9deb3b330272483c3e4ec0c6b5da86df59252835afeb4ab5f9016a0000000000000006080cdff505000000002025b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a2006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f160014e794713e386d83f32baa0e9d03e47c0839dc57a8c0020000000001014cdeada737db97af334f0fa4e87432d6068759eea65a3067d1f14a979e5a9dea0000000000ffffffff010cdff5050000000017a91426b9ba9cf5d822b70cf490ad0394566f9db20c63870247304402200b3ca71e82551a333fe5c8ce9a8f8454eb8f08aa194180e5a87c79ccf2e46212022065c1f2a363ebcb155a80e234258394140d08f6ab807581953bb21a58f2d229a6012102fd54c734e48c544c3c3ad1aab0607f896eb95e23e7058b174a580826a7940ad8000000009700000020fe3b574c1ce6d5cb68fc518e86f7976e599fafc0a2e5754aace7ca16d97a7c78ef9325b8d4f0a4921e060fc5e71435f46a18fa339688142cd4b028c8488c9f8dd1495b5dffff7f200200000002000000024a180a6822abffc3b1080c49016899c6dac25083936df14af12f58db11958ef27926299350fdc2f4d0da1d4f0fbbd3789d29f9dc016358ae42463c0cebf393f30105000000000000", + pegin_tx.to_str()); + + // blind + pegin_tx = pegin_tx + .blind( + &[ElementsUtxoData::from_outpoint(&pegin_outpoint, pegin_amount, &asset).expect("Fail")], + &IssuanceKeyMap::default(), + &[], + &KeyIndexMap::default(), + &BlindOption::default(), + ) + .expect("Fail"); + // add sign + pegin_tx + .sign_with_privkey( + &pegin_outpoint, + &HashType::P2wpkh, + &privkey, + &SigHashType::All, + &ConfidentialValue::from_amount(pegin_amount).expect("Fail"), + ) + .expect("Fail"); + } + + #[test] + fn pegout_test() { + // mainchain address descriptor + let mainchain_xpubkey = ExtPubkey::from_str( + "xpub6A53gzNdDBQYCtFFpZT7kUpoBGpzWigaukrdF9xoUZt7cYMD2qCAHVLsstNoQEDMFJWdX78KvT6yxpC76aGCN5mENVdWtFGcWZoKdtLq5jW").expect("Fail"); + let mainchain_pubkey = mainchain_xpubkey.get_pubkey(); + let negate_mainchain_pubkey = mainchain_pubkey.negate().expect("Fail"); + let mainchain_output_descriptor = format!("pkh({}/0/*)", mainchain_xpubkey.to_str()); + let bip32_counter = 0; + + let online_privkey = + Privkey::from_str("L52AgshDAE14NHJuovwAw8hyrTNK4YQjuiPC9EES4sfM7oBPzU4o").expect("Fail"); + let online_pubkey = online_privkey.get_pubkey().expect("Fail"); + let pak_entry = format!( + "{}{}", + negate_mainchain_pubkey.to_hex(), + online_pubkey.to_hex() + ); + let whitelist = ByteData::from_str(&pak_entry).expect("Fail"); + + let genesis_block_hash = + BlockHash::from_str("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206") + .expect("Fail"); + let asset = ConfidentialAsset::from_str( + "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225", + ) + .expect("Fail"); + + let mut tx = ConfidentialTransaction::create_tx( + 2, + 0, + &[TxInData { + outpoint: OutPoint::from_str( + "4aa201f333e80b8f62ba5b593edb47b4730212e2917b21279f389ba1c14588a3", + 0, + ) + .expect("Fail"), + sequence: 4294967293, + script_sig: Script::default(), + }], + &[ConfidentialTxOutData::from_address( + 209998999992700, + &asset, + &Address::from_str("XBMr6srTXmWuHifFd8gs54xYfiCBsvrksA").expect("Fail"), + )], + ) + .expect("Fail"); + + let mut addr = Address::default(); + tx = tx + .add_pegout_output( + &PegoutInputData { + amount: 1000000000, + asset: asset.clone(), + mainchain_network_type: Network::Mainnet, + elements_network_type: Network::LiquidV1, + mainchain_genesis_block_hash: genesis_block_hash, + online_privkey, + offline_output_descriptor: mainchain_output_descriptor, + bip32_counter, + whitelist, + }, + &mut addr, + ) + .expect("Fail"); + assert_eq!("1NrcpiZmCxjC7KVKAYT22SzVhhcXtp5o4v", addr.to_str()); + + tx = tx + .append_data(&[], &[ConfidentialTxOutData::from_fee(7300, &asset)]) + .expect("Fail"); + assert_eq!("020000000001a38845c1a19b389f27217b91e2120273b447db3e595bba628f0be833f301a24a0000000000fdffffff030125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000befe33cc397c0017a914001d6db698e75a5a8af771730c4ab258af30546b870125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000003b9aca0000a06a2006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f1976a914efbced4774546c03a8554ce2da27c0300c9dd43b88ac2103700dcb030588ed828d85f645b48971de0d31e8c0244da46710d18681627f5a4a4101044e949dcf8ac2daac82a3e4999ee28e2711661793570c4daab34cd38d76a425d6bfe102f3fea8be12109925fad32c78b65afea4de1d17a826e7375d0e2d00660125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000001c84000000000000", + tx.to_str()); + } } diff --git a/tests/descriptor_test.rs b/tests/descriptor_test.rs index 1b3fead..d59624a 100644 --- a/tests/descriptor_test.rs +++ b/tests/descriptor_test.rs @@ -459,4 +459,134 @@ mod tests { assert_eq!(false, descriptor.has_key_hash()); assert_eq!(2, descriptor.get_script_list().len()); } + + #[test] + fn descriptor_taproot_schnorr_test() { + let desc = "tr(ef514f1aeb14baa6cc57ab3268fb329ca540c48454f7f46771ed731e34ba521a)"; + let descriptor = Descriptor::new(desc, &Network::Regtest).expect("Fail"); + assert_eq!( + "tr(ef514f1aeb14baa6cc57ab3268fb329ca540c48454f7f46771ed731e34ba521a)#mavrnmjy", + descriptor.to_str() + ); + assert_eq!(&HashType::Taproot, descriptor.get_hash_type()); + assert_eq!( + "bcrt1paag57xhtzja2dnzh4vex37ejnjj5p3yy2nmlgem3a4e3ud962gdqqctzwn", + descriptor.get_address().to_str() + ); + assert_eq!( + "OP_1 ef514f1aeb14baa6cc57ab3268fb329ca540c48454f7f46771ed731e34ba521a", + descriptor.get_address().get_locking_script().to_asm() + ); + assert_eq!(false, descriptor.has_multisig()); + assert_eq!(false, descriptor.has_script_hash()); + assert_eq!(false, descriptor.has_key_hash()); + assert_eq!(true, descriptor.has_taproot()); + assert_eq!(1, descriptor.get_script_list().len()); + assert_eq!("", descriptor.get_script_tree().to_str()); + let key_data = descriptor.get_key_data().expect("fail"); + assert_eq!(&DescriptorKeyType::Schnorr, key_data.get_type()); + assert_eq!( + "ef514f1aeb14baa6cc57ab3268fb329ca540c48454f7f46771ed731e34ba521a", + key_data.to_str() + ); + } + + #[test] + fn descriptor_taproot_xpub_derive_test() { + let desc = "tr([bd16bee5/0]xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*)"; + let descriptor = Descriptor::with_derive_bip32path(desc, "1", &Network::Mainnet).expect("Fail"); + assert_eq!( + "tr([bd16bee5/0]xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*)#aa0v9ye4", + descriptor.to_str() + ); + assert_eq!(&HashType::Taproot, descriptor.get_hash_type()); + assert_eq!( + "bc1p33h4j4kre3e9r4yrl35rlgrtyt2w9hw8f94zty9vacmvfgcnlqtq0txdxt", + descriptor.get_address().to_str() + ); + assert_eq!( + "OP_1 8c6f5956c3cc7251d483fc683fa06b22d4e2ddc7496a2590acee36c4a313f816", + descriptor.get_address().get_locking_script().to_asm() + ); + assert_eq!(false, descriptor.has_multisig()); + assert_eq!(false, descriptor.has_script_hash()); + assert_eq!(false, descriptor.has_key_hash()); + assert_eq!(true, descriptor.has_taproot()); + assert_eq!(1, descriptor.get_script_list().len()); + assert_eq!("", descriptor.get_script_tree().to_str()); + let key_data = descriptor.get_key_data().expect("fail"); + assert_eq!(&DescriptorKeyType::Bip32, key_data.get_type()); + assert_eq!( + "xpub6EKMC2gSMfKgSwn7V9VZn7x1MvoeeVzSmmtSJ4z2L2d6R4WxvdQMouokypZHVp4fgKycrrQnGr6WJ5ED5jG9Q9FiA1q5gKYUc8u6JHJhdo8", + key_data.to_str() + ); + } + + #[test] + fn descriptor_taproot_tapscript_single_test() { + let desc = "tr(ef514f1aeb14baa6cc57ab3268fb329ca540c48454f7f46771ed731e34ba521a,c:pk_k(8c6f5956c3cc7251d483fc683fa06b22d4e2ddc7496a2590acee36c4a313f816))"; + let descriptor = Descriptor::new(desc, &Network::Regtest).expect("Fail"); + assert_eq!( + "tr(ef514f1aeb14baa6cc57ab3268fb329ca540c48454f7f46771ed731e34ba521a,c:pk_k(8c6f5956c3cc7251d483fc683fa06b22d4e2ddc7496a2590acee36c4a313f816))#agrnj9m2", + descriptor.to_str() + ); + assert_eq!(&HashType::Taproot, descriptor.get_hash_type()); + assert_eq!( + "bcrt1p2druqmxfa49j9ph0ea8d9y4gzrhy2x7u2zj0p2622d9r7k28v02s6x9jx3", + descriptor.get_address().to_str() + ); + assert_eq!( + "OP_1 5347c06cc9ed4b2286efcf4ed292a810ee451bdc50a4f0ab4a534a3f594763d5", + descriptor.get_address().get_locking_script().to_asm() + ); + assert_eq!(false, descriptor.has_multisig()); + assert_eq!(false, descriptor.has_script_hash()); + assert_eq!(false, descriptor.has_key_hash()); + assert_eq!(true, descriptor.has_taproot()); + assert_eq!(1, descriptor.get_script_list().len()); + assert_eq!( + "tl(208c6f5956c3cc7251d483fc683fa06b22d4e2ddc7496a2590acee36c4a313f816ac)", + descriptor.get_script_tree().to_str() + ); + let key_data = descriptor.get_key_data().expect("fail"); + assert_eq!(&DescriptorKeyType::Schnorr, key_data.get_type()); + assert_eq!( + "ef514f1aeb14baa6cc57ab3268fb329ca540c48454f7f46771ed731e34ba521a", + key_data.to_str() + ); + } + + #[test] + fn descriptor_taproot_tapscript_tapbranch_test() { + let desc = "tr(ef514f1aeb14baa6cc57ab3268fb329ca540c48454f7f46771ed731e34ba521a,{c:pk_k(8c6f5956c3cc7251d483fc683fa06b22d4e2ddc7496a2590acee36c4a313f816),{c:pk_k([bd16bee5/0]xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*),thresh(2,c:pk_k(5cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),a:hash160(dd69735817e0e3f6f826a9238dc2e291184f0131))}})"; + let descriptor = Descriptor::with_derive_bip32path(desc, "1", &Network::Regtest).expect("Fail"); + assert_eq!( + "tr(ef514f1aeb14baa6cc57ab3268fb329ca540c48454f7f46771ed731e34ba521a,{c:pk_k(8c6f5956c3cc7251d483fc683fa06b22d4e2ddc7496a2590acee36c4a313f816),{c:pk_k([bd16bee5/0]xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*),thresh(2,c:pk_k(5cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),a:hash160(dd69735817e0e3f6f826a9238dc2e291184f0131))}})#7ezsl729", + descriptor.to_str() + ); + assert_eq!(&HashType::Taproot, descriptor.get_hash_type()); + assert_eq!( + "bcrt1pfuqf4j7ceyzmu3rsmude93ctu948r565hf2ucrn9z7zn7a7hjegskj3rsv", + descriptor.get_address().to_str() + ); + assert_eq!( + "OP_1 4f009acbd8c905be4470df1b92c70be16a71d354ba55cc0e6517853f77d79651", + descriptor.get_address().get_locking_script().to_asm() + ); + assert_eq!(false, descriptor.has_multisig()); + assert_eq!(false, descriptor.has_script_hash()); + assert_eq!(false, descriptor.has_key_hash()); + assert_eq!(true, descriptor.has_taproot()); + assert_eq!(1, descriptor.get_script_list().len()); + assert_eq!( + "{tl(208c6f5956c3cc7251d483fc683fa06b22d4e2ddc7496a2590acee36c4a313f816ac),{tl(208c6f5956c3cc7251d483fc683fa06b22d4e2ddc7496a2590acee36c4a313f816ac),tl(205cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bcac7c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87936b82012088a914dd69735817e0e3f6f826a9238dc2e291184f0131876c935287)}}", + descriptor.get_script_tree().to_str() + ); + let key_data = descriptor.get_key_data().expect("fail"); + assert_eq!(&DescriptorKeyType::Schnorr, key_data.get_type()); + assert_eq!( + "ef514f1aeb14baa6cc57ab3268fb329ca540c48454f7f46771ed731e34ba521a", + key_data.to_str() + ); + } } diff --git a/tests/key_test.rs b/tests/key_test.rs index d44e80c..2543907 100644 --- a/tests/key_test.rs +++ b/tests/key_test.rs @@ -3,7 +3,7 @@ extern crate sha2; #[cfg(test)] mod tests { - use cfd_rust::{ByteData, Network, Privkey, Pubkey, SignParameter}; + use cfd_rust::{ByteData, Network, Privkey, Pubkey, SigHashType, SignParameter}; use std::str::FromStr; #[test] @@ -192,5 +192,32 @@ mod tests { assert_eq!("30440220773420c0ded41a55b1f1205cfb632f08f3f911a53e7338a0dac73ec6cbe3ca4702201907434d046185abedc5afddc2761a642bccc70af6d22b46394f1d04a8b2422601", der_encoded_sig.to_hex()); let der_decoded_sig = der_encoded_sig.to_der_decode().expect("Fail"); assert_eq!("773420c0ded41a55b1f1205cfb632f08f3f911a53e7338a0dac73ec6cbe3ca471907434d046185abedc5afddc2761a642bccc70af6d22b46394f1d04a8b24226", der_decoded_sig.to_hex()); + + // sighash rangeproof test + let sighashtype_array = vec![ + // SigHashType::Default, // unuse der encode. + SigHashType::All, + SigHashType::None, + SigHashType::Single, + SigHashType::AllPlusAnyoneCanPay, + SigHashType::NonePlusAnyoneCanPay, + SigHashType::SinglePlusAnyoneCanPay, + SigHashType::AllPlusRangeproof, + SigHashType::NonePlusRangeproof, + SigHashType::SinglePlusRangeproof, + SigHashType::AllPlusAnyoneCanPayRangeproof, + SigHashType::NonePlusAnyoneCanPayRangeproof, + SigHashType::SinglePlusAnyoneCanPayRangeproof, + ]; + let sig_str = "773420c0ded41a55b1f1205cfb632f08f3f911a53e7338a0dac73ec6cbe3ca471907434d046185abedc5afddc2761a642bccc70af6d22b46394f1d04a8b24226"; + for sighash_type2 in sighashtype_array { + let signature2 = SignParameter::from_str(sig_str) + .expect("Fail") + .set_signature_hash(&sighash_type2); + let der_encoded_sig2 = signature2.to_der_encode().expect("Fail"); + let der_decoded_sig2 = der_encoded_sig2.to_der_decode().expect("Fail"); + assert_eq!(sig_str, der_decoded_sig2.to_hex()); + assert_eq!(&sighash_type2, der_decoded_sig2.get_sighash_type()); + } } } diff --git a/tests/transaction_test.rs b/tests/transaction_test.rs index a30fe83..c92e6aa 100644 --- a/tests/transaction_test.rs +++ b/tests/transaction_test.rs @@ -1326,4 +1326,76 @@ mod tests { .expect("Fail"); assert_eq!(true, is_verify); } + + #[test] + fn split_txout_test() { + let mut tx = Transaction::from_str("0200000001ffa8db90b81db256874ff7a98fb7202cdc0b91b5b02d7c3427c4190adc66981f0000000000ffffffff0118f50295000000002251201777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb00000000").expect("Fail"); + tx = tx + .split_txout( + 0, + &[TxOutData::from_address( + 499999000, + &Address::from_str("bc1qz33wef9ehrvd7c64p27jf5xtvn50946xfzpxx4").expect("Fail"), + )], + ) + .expect("Fail"); + assert_eq!("0200000001ffa8db90b81db256874ff7a98fb7202cdc0b91b5b02d7c3427c4190adc66981f0000000000ffffffff0200943577000000002251201777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb1861cd1d000000001600141462eca4b9b8d8df63550abd24d0cb64e8f2d74600000000", + tx.to_str()); + + tx = Transaction::from_str("0200000001ffa8db90b81db256874ff7a98fb7202cdc0b91b5b02d7c3427c4190adc66981f0000000000ffffffff0118f50295000000002251201777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb00000000").expect("Fail"); + tx = tx + .split_txout( + 0, + &[ + TxOutData::from_locking_script( + 400000000, + &Script::from_hex("00141462eca4b9b8d8df63550abd24d0cb64e8f2d746").expect("Fail"), + ), + TxOutData::from_locking_script( + 99999000, + &Script::from_hex("0014164e985d0fc92c927a66c0cbaf78e6ea389629d5").expect("Fail"), + ), + ], + ) + .expect("Fail"); + assert_eq!("0200000001ffa8db90b81db256874ff7a98fb7202cdc0b91b5b02d7c3427c4190adc66981f0000000000ffffffff0300943577000000002251201777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb0084d717000000001600141462eca4b9b8d8df63550abd24d0cb64e8f2d74618ddf50500000000160014164e985d0fc92c927a66c0cbaf78e6ea389629d500000000", + tx.to_str()); + } + + #[test] + fn update_witness_stack_test() { + let mut tx = Transaction::from_str("020000000001014cdeada737db97af334f0fa4e87432d6068759eea65a3067d1f14a979e5a9dea0000000000ffffffff010cdff5050000000017a91426b9ba9cf5d822b70cf490ad0394566f9db20c63870247304402200b3ca71e82551a333fe5c8ce9a8f8454eb8f08aa194180e5a87c79ccf2e46212022065c1f2a363ebcb155a80e234258394140d08f6ab807581953bb21a58f2d229a6012102fd54c734e48c544c3c3ad1aab0607f896eb95e23e7058b174a580826a7940ad800000000").expect("Fail"); + tx = tx + .update_witness_stack( + &OutPoint::from_str( + "ea9d5a9e974af1d167305aa6ee598706d63274e8a40f4f33af97db37a7adde4c", + 0, + ) + .expect("Fail"), + 1, + &ByteData::from_str("03aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79") + .expect("Fail"), + ) + .expect("Fail"); + assert_eq!( + "020000000001014cdeada737db97af334f0fa4e87432d6068759eea65a3067d1f14a979e5a9dea0000000000ffffffff010cdff5050000000017a91426b9ba9cf5d822b70cf490ad0394566f9db20c63870247304402200b3ca71e82551a333fe5c8ce9a8f8454eb8f08aa194180e5a87c79ccf2e46212022065c1f2a363ebcb155a80e234258394140d08f6ab807581953bb21a58f2d229a6012103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf7900000000", + tx.to_str()); + } + + #[test] + fn get_txout_index_test() { + let tx = Transaction::from_str( + "02000000034cdeada737db97af334f0fa4e87432d6068759eea65a3067d1f14a979e5a9dea0000000000ffffffff81ddd34c6c0c32544e3b89f5e24c6cd7afca62f2b5069281ac9fced6251191d20000000000ffffffff81ddd34c6c0c32544e3b89f5e24c6cd7afca62f2b5069281ac9fced6251191d20100000000ffffffff040200000000000000220020c5ae4ff17cec055e964b573601328f3f879fa441e53ef88acdfd4d8e8df429ef406f400100000000220020ea5a7208cddfbc20dd93e12bf29deb00b68c056382a502446c9c5b55490954d215cd5b0700000000220020f39f6272ba6b57918eb047c5dc44fb475356b0f24c12fca39b19284e80008a42406f400100000000220020ea5a7208cddfbc20dd93e12bf29deb00b68c056382a502446c9c5b55490954d200000000").expect("Fail"); + let indexes = tx + .get_txout_indexes_by_address( + &Address::from_str("bc1qafd8yzxdm77zphvnuy4l980tqzmgcptrs2jsy3rvn3d42jgf2nfqc4zt4j") + .expect("Fail"), + ) + .expect("Fail"); + assert_eq!(2, indexes.len()); + if indexes.len() == 2 { + assert_eq!(1, indexes[0]); + assert_eq!(3, indexes[1]); + } + } }