diff --git a/.github/workflows/create_release-and-upload.yml b/.github/workflows/create_release-and-upload.yml index fe078d8..cbe1029 100644 --- a/.github/workflows/create_release-and-upload.yml +++ b/.github/workflows/create_release-and-upload.yml @@ -171,6 +171,10 @@ jobs: name: upload-object-alpine needs: create_releases runs-on: ubuntu-18.04 + strategy: + fail-fast: false + matrix: + alpine: ['3.10', '3.12', '3.13'] steps: - name: checkout @@ -180,10 +184,21 @@ jobs: run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} - name: list run: ls -a $GITHUB_WORKSPACE - - name: docker setup + - name: docker setup 3.10 + if: matrix.alpine == '3.10' uses: docker://alpine:3.10 with: entrypoint: /github/workspace/.github/workflows/docker/alpine_build_entrypoint.sh + - name: docker setup 3.12 + if: matrix.alpine == '3.12' + uses: docker://alpine:3.12 + with: + entrypoint: /github/workspace/.github/workflows/docker/alpine_build_entrypoint.sh + - name: docker setup 3.13 + if: matrix.alpine == '3.13' + uses: docker://alpine:3.13 + with: + entrypoint: /github/workspace/.github/workflows/docker/alpine_build_entrypoint.sh - name: create archive file run: | echo "---- dump output data ----" @@ -211,7 +226,7 @@ jobs: with: upload_url: ${{ steps.get_url.outputs.upload_url }} asset_path: /tmp/cfd/cfd.zip - asset_name: cfd-sys-${{ steps.get_version.outputs.VERSION }}-alpine_x86_64.zip + asset_name: cfd-sys-${{ steps.get_version.outputs.VERSION }}-alpine${{ matrix.alpine }}_x86_64.zip asset_content_type: application/zip upload-object-macos: @@ -221,7 +236,7 @@ jobs: strategy: fail-fast: false matrix: - xcode: [10.3, 11.6] + xcode: [10.3, 11.7, 12.4] shared: [on, off] include: - shared: on diff --git a/Cargo.toml b/Cargo.toml index f9818b8..a16770e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cfd-rust" -version = "0.2.2" +version = "0.3.0" license = "MIT" readme = "README.md" keywords = ["build-dependencies"] @@ -15,12 +15,12 @@ name = "cfd_rust" path = "src/lib.rs" [dependencies] -libc = "0.2.74" +libc = "0.2.83" hex = "0.4.2" cfd_sys = { path = "./cfd-sys" } [dev-dependencies] -sha2 = "0.9.1" +sha2 = "0.9.2" [[example]] name = "create_pubkey_address" diff --git a/README.md b/README.md index 8b2b663..c72848a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,51 @@ # Crypto Finance Development Kit for Rust (CFD-RUST) +CFD library for Rust. + +## Overview + +This library is development kit for crypto finance application. +Useful when developing applications for cryptocurrencies. + +### Target Network + +- Bitcoin +- Liquid Network + +### Support function by cfd + +- Bitcoin + - Bitcoin Script (builder, viewer) + - Transaction + - Create, Parse, Decode + - Simple pubkey-hash sign / verify + - Estimate Fee + - Coin Selection (FundRawTransaction) + - ECDSA Pubkey/Privkey (TweakAdd/Mul, Negate, Sign, Verify) + - BIP32, BIP39 + - Output Descriptor (contains miniscript parser) + - Schnorr/Taproot + - Bitcoin Address (Segwit-v0, Segwit-v1, P2PKH/P2SH) +- Liquid Network + - Confidential Transaction + - Blind, Unblind + - Reissuance + - Confidential Address + +### Libraries for each language + +- Rust : cfd-rust + - C/C++ : cfd + - Extend the cfd-core library. Defines the C language API and extension classes. + - C++ : cfd-core + - Core library. Definition base class. +- other language: + - JavaScript : cfd-js + - WebAssembly : cfd-js-wasm + - Python : cfd-python + - C# : cfd-csharp + - Go : cfd-go + ## Dependencies - Rust @@ -7,9 +53,10 @@   - can compile c++11 - CMake (3.14.3 or higher) -### Windows +### Windows download and install files. + - [Rustup](https://rustup.rs/) - [CMake](https://cmake.org/) (3.14.3 or higher) - MSVC @@ -38,7 +85,7 @@ curl https://sh.rustup.rs -sSf | sh (select is 1) ``` cmake version 3.14.2 or lower, download from website and install cmake. -(https://cmake.org/download/) +([https://cmake.org/download/](https://cmake.org/download/)) --- @@ -58,9 +105,10 @@ Using pkg-config, build is quickly. ```Shell # The described version is an example. If you use it, please rewrite it to an appropriate version. -wget https://github.com/cryptogarageinc/cfd-rust/releases/download/v0.0.1/cfd-sys-v0.0.1-osx-xcode9.4-static_x86_64.zip +wget https://github.com/p2pderivatives/cfd-rust/releases/download/v0.3.0/cfd-sys-v0.3.0-osx-xcode12.4-static-x86_64.zip + # decompress -sudo unzip -d / cfd-sys-v0.0.1-osx-xcode9.4-static_x86_64.zip +sudo unzip -d / cfd-sys-v0.3.0-osx-xcode12.4-static-x86_64.zip # build cargo build ``` @@ -68,7 +116,8 @@ cargo build ### Prepare cfd native library from releases asset (Windows) Using cmake find_package. -1. get releases asset. (ex. https://github.com/cryptogarageinc/cfd/releases/download/v0.1.12/cfd-v0.1.12-win-vs2019-x86_64.zip ) + +1. get releases asset. (ex. [https://github.com/p2pderivatives/cfd-rust/releases/download/v0.3.0/cfd-sys-v0.3.0-win-vs2019-x86_64.zip](https://github.com/p2pderivatives/cfd-rust/releases/download/v0.3.0/cfd-sys-v0.3.0-win-vs2019-x86_64.zip) ) 2. Expand to PATH --- @@ -77,13 +126,13 @@ Using cmake find_package. ### Test -``` +```shell cargo test ``` ### Example -``` +```shell cargo run --example create_pubkey_address ``` @@ -109,7 +158,8 @@ cargo run --example create_pubkey_address ### document tool - cargo - ``` + + ```shell cargo doc ``` @@ -123,26 +173,29 @@ cargo run --example create_pubkey_address ## Note -### Git connection: +### Git connection Git repository connections default to HTTPS. However, depending on the connection settings of GitHub, you may only be able to connect via SSH. As a countermeasure, forcibly establish SSH connection by setting `CFD_CMAKE_GIT_SSH=1` in the environment variable. - Windows: (On the command line. Or set from the system setting screen.) -``` + +```bat set CFD_CMAKE_GIT_SSH=1 ``` - MacOS & Linux(Ubuntu): -``` + +```shell export CFD_CMAKE_GIT_SSH=1 ``` -### Ignore git update for CMake External Project: +### Ignore git update for CMake External Project Depending on your git environment, you may get the following error when checking out external: -``` + +```log Performing update step for 'libwally-core-download' Current branch cmake_build is up to date. No stash entries found. @@ -161,11 +214,13 @@ This phenomenon is due to the `git update` related command. Please set an environment variable that skips update processing. - Windows: (On the command line. Or set from the system setting screen.) -``` + +```bat set CFD_CMAKE_GIT_SKIP_UPDATE=1 ``` - MacOS & Linux(Ubuntu): -``` + +```shell export CFD_CMAKE_GIT_SKIP_UPDATE=1 ``` diff --git a/cfd-sys/Cargo.toml b/cfd-sys/Cargo.toml index 2b5b664..2b89ec5 100644 --- a/cfd-sys/Cargo.toml +++ b/cfd-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cfd_sys" -version = "0.2.2" +version = "0.3.0" license = "MIT" readme = "../README.md" keywords = ["build-dependencies"] @@ -17,11 +17,11 @@ features = [ "serde" ] all-features = true [dependencies] -libc = "0.2.74" +libc = "0.2.83" [build-dependencies] -cmake = "0.1.44" -pkg-config = "0.3.18" +cmake = "0.1.45" +pkg-config = "0.3.19" [features] # When building from source, enable building optimized assembly routines. As diff --git a/cfd-sys/cfd-cmake/cmake/CfdCommonOption.cmake b/cfd-sys/cfd-cmake/cmake/CfdCommonOption.cmake index 73a61ce..848197f 100644 --- a/cfd-sys/cfd-cmake/cmake/CfdCommonOption.cmake +++ b/cfd-sys/cfd-cmake/cmake/CfdCommonOption.cmake @@ -6,6 +6,7 @@ endif() option(ENABLE_ELEMENTS "enable elements code (ON or OFF. default:ON)" ON) option(ENABLE_TESTS "enable code tests (ON or OFF. default:ON)" ON) option(ENABLE_EMSCRIPTEN "enable EMSCRIPTEN (ON or OFF. default:OFF)" OFF) +option(STD_CPP_VERSION "c++ version (11/14/17. default:11)" "11") # use "cmake -DCMAKE_BUILD_TYPE=Debug" or "cmake-js -D" # option(ENABLE_DEBUG "enable debugging (ON or OFF. default:OFF)" OFF) diff --git a/cfd-sys/cfd-cmake/cmake/CfdWallyOption.cmake b/cfd-sys/cfd-cmake/cmake/CfdWallyOption.cmake index 63df95d..8dc2cad 100644 --- a/cfd-sys/cfd-cmake/cmake/CfdWallyOption.cmake +++ b/cfd-sys/cfd-cmake/cmake/CfdWallyOption.cmake @@ -6,4 +6,5 @@ option(ENABLE_JS_WRAPPER "enable the Javascript interface wrappers (ON or OFF. d else() set(ENABLE_JS_WRAPPER OFF) endif() -set(GENERATE_WALLY ON) +option(GENERATE_WALLY "generate the wally.xxx library (ON or OFF. default:ON)" ON) +option(EXCLUDE_WALLYCORE_LIB "exclude wallycore lib (ON or OFF. default:OFF)" OFF) diff --git a/cfd-sys/cfd-cmake/cmake/Cpp11Setting.cmake b/cfd-sys/cfd-cmake/cmake/Cpp11Setting.cmake index c6beeab..b92ed3e 100644 --- a/cfd-sys/cfd-cmake/cmake/Cpp11Setting.cmake +++ b/cfd-sys/cfd-cmake/cmake/Cpp11Setting.cmake @@ -13,6 +13,11 @@ if(${USE_EMSCRIPTEN}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s DISABLE_EXCEPTION_CATCHING=0") endif() +if(${STD_CPP_VERSION}) +set(CMAKE_CXX_STANDARD ${STD_CPP_VERSION}) +message(STATUS "[STD_CPP_VERSION] ${STD_CPP_VERSION}") +else() set(CMAKE_CXX_STANDARD 11) +endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/cfd-sys/cfd-cmake/external/CMakeLists.txt b/cfd-sys/cfd-cmake/external/CMakeLists.txt index 4ebcc93..23b8e9f 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.2.2) +set(CFD_TARGET_TAG v0.3.0) 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 6a5c61b..0664873 100644 --- a/cfd-sys/src/lib.rs +++ b/cfd-sys/src/lib.rs @@ -5,7 +5,7 @@ extern crate libc; -use self::libc::{c_char, c_double, c_int, c_longlong, c_uint, c_void}; +use self::libc::{c_char, c_double, c_int, c_longlong, c_uchar, c_uint, c_void}; pub const BLIND_OPT_MINIMUM_RANGE_VALUE: c_int = 1; pub const BLIND_OPT_EXPONENT: c_int = 2; @@ -29,6 +29,20 @@ pub const FEE_OPT_BLIND_MINIMUM_BITS: c_int = 2; pub const DEFAULT_BLIND_MINIMUM_BITS: c_int = 52; +pub const PSBT_RECORD_TYPE_GLOBAL: c_int = 1; +pub const PSBT_RECORD_TYPE_INPUT: c_int = 2; +pub const PSBT_RECORD_TYPE_OUTPUT: c_int = 3; + +pub const PSBT_RECORD_INPUT_SIGNATURE: c_int = 1; +pub const PSBT_RECORD_INPUT_BIP32: c_int = 2; +pub const PSBT_RECORD_OUTPUT_BIP32: c_int = 3; +pub const PSBT_RECORD_GLOBAL_XPUB: c_int = 4; + +pub const PSBT_OPT_ESTIMATE_FEE_RATE: c_int = 1; +pub const PSBT_OPT_DUST_FEE_RATE: c_int = 2; +pub const PSBT_OPT_LONG_TERM_FEE_RATE: c_int = 3; +pub const PSBT_OPT_KNAPSACK_MIN_CHANGE: c_int = 4; + // references: https://github.com/rust-lang/libz-sys #[rustfmt::skip] macro_rules! fns { @@ -66,6 +80,26 @@ fns! { ignore_version_info: bool, value_hex: *mut *mut c_char, ) -> c_int; + pub fn CfdEncryptAES( + handle: *const c_void, key: *const c_char, cbc_iv: *const c_char, buffer: *const c_char, + output: *mut *mut c_char) -> c_int; + pub fn CfdDecryptAES( + handle: *const c_void, key: *const c_char, cbc_iv: *const c_char, buffer: *const c_char, + output: *mut *mut c_char) -> c_int; + pub fn CfdEncodeBase64(handle: *const c_void, buffer: *const c_char, output: *mut *mut c_char) -> c_int; + pub fn CfdDecodeBase64(handle: *const c_void, base64: *const c_char, output: *mut *mut c_char) -> c_int; + pub fn CfdEncodeBase58( + handle: *const c_void, buffer: *const c_char, use_checksum: bool, output: *mut *mut c_char) -> c_int; + pub fn CfdDecodeBase58( + handle: *const c_void, base58: *const c_char, use_checksum: bool, output: *mut *mut c_char) -> c_int; + pub fn CfdRipemd160( + handle: *const c_void, message: *const c_char, has_text: bool, output: *mut *mut c_char) -> c_int; + pub fn CfdSha256( + handle: *const c_void, message: *const c_char, has_text: bool, output: *mut *mut c_char) -> c_int; + pub fn CfdHash160( + handle: *const c_void, message: *const c_char, has_text: bool, output: *mut *mut c_char) -> c_int; + pub fn CfdHash256( + handle: *const c_void, message: *const c_char, has_text: bool, output: *mut *mut c_char) -> c_int; pub fn CfdCreateConfidentialAddress( handle: *const c_void, address: *const i8, @@ -137,7 +171,7 @@ fns! { fingerprint: *const i8, key: *const i8, chain_code: *const i8, - depth: u8, + depth: c_uchar, child_number: u32, extkey: *mut *mut c_char, ) -> c_int; @@ -294,6 +328,11 @@ fns! { is_compressed: bool, pubkey: *mut *mut c_char, ) -> c_int; + pub fn CfdGetPubkeyFingerprint( + handle: *const c_void, + pubkey: *const c_char, + fingerprint: *mut *mut c_char, + ) -> c_int; pub fn CfdCalculateEcSignature( handle: *const c_void, sighash: *const i8, @@ -417,6 +456,19 @@ fns! { nonce: *const i8, signature: *mut *mut c_char, ) -> c_int; + pub fn CfdAddSighashTypeInSchnorrSignature( + handle: *const c_void, + signature: *const i8, + sighash_type: c_int, + anyone_can_pay: bool, + added_signature: *mut *mut c_char, + ) -> c_int; + pub fn CfdGetSighashTypeFromSchnorrSignature( + handle: *const c_void, + signature: *const i8, + sighash_type: *mut c_int, + anyone_can_pay: *mut bool, + ) -> c_int; pub fn CfdComputeSchnorrSigPoint( handle: *const c_void, message: *const i8, @@ -454,6 +506,109 @@ fns! { script_item: *mut *mut c_char, ) -> c_int; pub fn CfdFreeScriptItemHandle(handle: *const c_void, script_handle: *const c_void) -> c_int; + pub fn CfdInitializeTaprootScriptTree( + handle: *const c_void, + tree_handle: *mut *mut c_void, + ) -> c_int; + pub fn CfdSetInitialTapLeaf( + handle: *const c_void, + tree_handle: *const c_void, + tapscript: *const i8, + leaf_version: c_uchar, + ) -> c_int; + pub fn CfdSetInitialTapBranchByHash( + handle: *const c_void, + tree_handle: *const c_void, + hash: *const i8, + ) -> c_int; + pub fn CfdSetScriptTreeFromString( + handle: *const c_void, + tree_handle: *const c_void, + tree_string: *const i8, + tapscript: *const i8, + leaf_version: c_uchar, + control_nodes: *const i8, + ) -> c_int; + pub fn CfdSetTapScriptByWitnessStack( + handle: *const c_void, + tree_handle: *const c_void, + control_block: *const i8, + tapscript: *const i8, + internal_pubkey: *mut *mut i8, + ) -> c_int; + pub fn CfdAddTapBranchByHash( + handle: *const c_void, + tree_handle: *const c_void, + branch_hash: *const i8, + ) -> c_int; + pub fn CfdAddTapBranchByScriptTree( + handle: *const c_void, + tree_handle: *const c_void, + branch_tree: *const c_void, + ) -> c_int; + pub fn CfdAddTapBranchByScriptTreeString( + handle: *const c_void, + tree_handle: *const c_void, + tree_string: *const i8, + ) -> c_int; + pub fn CfdAddTapBranchByTapLeaf( + handle: *const c_void, + tree_handle: *const c_void, + tapscript: *const i8, + leaf_version: c_uchar, + ) -> c_int; + pub fn CfdGetBaseTapLeaf( + handle: *const c_void, + tree_handle: *const c_void, + leaf_version: *mut c_uchar, + tapscript: *mut *mut i8, + tap_leaf_hash: *mut *mut i8, + ) -> c_int; + pub fn CfdGetTapBranchCount( + handle: *const c_void, + tree_handle: *const c_void, + branch_count: *mut c_uint, + ) -> c_int; + pub fn CfdGetTapBranchData( + handle: *const c_void, + tree_handle: *const c_void, + index_from_leaf: c_uchar, + is_root_data: bool, + branch_hash: *mut *mut i8, + leaf_version: *mut c_uchar, + tapscript: *mut *mut i8, + depth_by_leaf_or_end: *mut c_uchar, + ) -> c_int; + pub fn CfdGetTapBranchHandle( + handle: *const c_void, + tree_handle: *const c_void, + index_from_leaf: c_uchar, + branch_hash: *mut *mut i8, + branch_tree_handle: *mut *mut c_void, + ) -> c_int; + pub fn CfdGetTaprootScriptTreeHash( + handle: *const c_void, + tree_handle: *const c_void, + internal_pubkey: *const i8, + hash: *mut *mut i8, + tap_leaf_hash: *mut *mut i8, + control_block: *mut *mut i8, + ) -> c_int; + pub fn CfdGetTaprootTweakedPrivkey( + handle: *const c_void, + tree_handle: *const c_void, + internal_privkey: *const i8, + tweaked_privkey: *mut *mut i8, + ) -> c_int; + pub fn CfdGetTaprootScriptTreeSrting( + handle: *const c_void, + tree_handle: *const c_void, + tree_string: *mut *mut i8, + ) -> c_int; + pub fn CfdFreeTaprootScriptTreeHandle( + handle: *const c_void, + tree_handle: *const c_void, + ) -> c_int; pub fn CfdInitializeMultisigScript( handle: *const c_void, network_type: c_int, @@ -541,6 +696,106 @@ fns! { direct_locking_script: *const i8, asset_string: *const i8, ) -> c_int; + pub fn CfdClearWitnessStack( + handle: *const c_void, + create_handle: *const c_void, + txid: *const i8, + vout: c_uint, + ) -> c_int; + pub fn CfdUpdateTxInScriptSig( + handle: *const c_void, + create_handle: *const c_void, + txid: *const i8, + vout: c_uint, + script_sig: *const i8, + ) -> c_int; + pub fn CfdSetTransactionUtxoData( + handle: *const c_void, + create_handle: *const c_void, + txid: *const i8, + vout: c_uint, + amount: c_longlong, + commitment: *const i8, + descriptor: *const i8, + address: *const i8, + asset: *const i8, + scriptsig_template: *const i8, + can_insert: bool, + ) -> c_int; + pub fn CfdCreateSighashByHandle( + handle: *const c_void, + create_handle: *const c_void, + txid: *const i8, + vout: c_uint, + sighash_type: c_int, + sighash_anyone_can_pay: bool, + pubkey: *const i8, + redeem_script: *const i8, + tapleaf_hash: *const i8, + code_separator_position: c_uint, + annex: *const i8, + sighash: *mut *mut i8, + ) -> c_int; + pub fn CfdAddSignWithPrivkeyByHandle( + handle: *const c_void, + create_handle: *const c_void, + txid: *const i8, + vout: c_uint, + privkey: *const i8, + sighash_type: c_int, + sighash_anyone_can_pay: bool, + has_grind_r: bool, + aux_rand: *const i8, + annex: *const i8, + ) -> c_int; + pub fn CfdVerifyTxSignByHandle( + handle: *const c_void, + create_handle: *const c_void, + txid: *const i8, + vout: c_uint, + ) -> c_int; + pub fn CfdAddTxSignByHandle( + handle: *const c_void, + create_handle: *const c_void, + txid: *const i8, + vout: c_uint, + hash_type: c_int, + sign_data_hex: *const i8, + use_der_encode: bool, + sighash_type: c_int, + sighash_anyone_can_pay: bool, + clear_stack: bool, + ) -> c_int; + pub fn CfdAddTaprootSignByHandle( + handle: *const c_void, + create_handle: *const c_void, + txid: *const i8, + vout: c_uint, + signature: *const i8, + tapscript: *const i8, + control_block: *const i8, + annex: *const i8, + ) -> c_int; + pub fn CfdAddPubkeyHashSignByHandle( + handle: *const c_void, + create_handle: *const c_void, + txid: *const i8, + vout: c_uint, + hash_type: c_int, + pubkey: *const i8, + signature: *const i8, + use_der_encode: bool, + sighash_type: c_int, + sighash_anyone_can_pay: bool, + ) -> c_int; + pub fn CfdAddScriptHashLastSignByHandle( + handle: *const c_void, + create_handle: *const c_void, + txid: *const i8, + vout: c_uint, + hash_type: c_int, + redeem_script: *const i8, + ) -> c_int; pub fn CfdFinalizeTransaction( handle: *const c_void, create_handle: *const c_void, @@ -991,12 +1246,10 @@ fns! { 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; - // Just in case pub fn CfdAddConfidentialTxOut( handle: *const c_void, tx_hex_string: *const c_char, asset_string: *const c_char, value_satoshi: c_longlong, value_commitment: *const c_char, address: *const c_char, direct_locking_script: *const c_char, nonce: *const c_char, tx_string: *mut *mut c_char) -> c_int; - // Needed pub fn CfdUpdateConfidentialTxOut( handle: *const c_void, tx_hex_string: *const c_char, index: c_uint, asset_string: *const c_char, value_satoshi: c_longlong, @@ -1054,7 +1307,7 @@ fns! { is_issuance_asset: *mut bool, is_issuance_token: *mut bool) -> c_int; pub fn CfdFreeBlindHandle(handle: *const c_void, blind_handle: *const c_void) -> c_int; pub fn CfdFinalizeElementsMultisigSign( - handle: *const c_void, multi_sign_handle: *const c_void, tx_hex_string: *const c_char, txid: *const c_char, vout: c_uint, hash_type: c_int, witness_script: *const c_char, redeem_script: *const c_char, clear_stack: bool, tx_string: *mut *mut c_char) -> c_int; + handle: *const c_void, multi_sign_handle: *const c_void, tx_hex_string: *const c_char, txid: *const c_char, vout: c_uint, hash_type: c_int, witness_script: *const c_char, redeem_script: *const c_char, clear_stack: bool, tx_string: *mut *mut c_char) -> c_int; pub fn CfdAddConfidentialTxSignWithPrivkeySimple( handle: *const c_void, tx_hex_string: *const c_char, txid: *const c_char, vout: c_uint, hash_type: c_int, pubkey: *const c_char, privkey: *const c_char, @@ -1094,4 +1347,135 @@ 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 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, + psbt_handle: *mut *mut c_void) -> c_int; + pub fn CfdFreePsbtHandle(handle: *const c_void, psbt_handle: *const c_void) -> c_int; + pub fn CfdGetPsbtData( + handle: *const c_void, psbt_handle: *const c_void, psbt_base64: *mut *mut c_char, psbt_hex: *mut *mut c_char) -> c_int; + pub fn CfdGetPsbtGlobalData( + handle: *const c_void, psbt_handle: *const c_void, psbt_version: *mut c_uint, base_tx: *mut *mut c_char, + txin_count: *mut c_uint, txout_count: *mut c_uint) -> c_int; + pub fn CfdJoinPsbt( + handle: *const c_void, psbt_handle: *const c_void, psbt_join_base64: *const c_char) -> c_int; + pub fn CfdSignPsbt( + handle: *const c_void, psbt_handle: *const c_void, privkey: *const c_char, has_grind_r: bool) -> c_int; + pub fn CfdCombinePsbt( + handle: *const c_void, psbt_handle: *const c_void, psbt_combine_base64: *const c_char) -> c_int; + pub fn CfdFinalizePsbt(handle: *const c_void, psbt_handle: *const c_void) -> c_int; + pub fn CfdExtractPsbtTransaction( + handle: *const c_void, psbt_handle: *const c_void, transaction: *mut *mut c_char) -> c_int; + pub fn CfdIsFinalizedPsbt(handle: *const c_void, psbt_handle: *const c_void) -> c_int; + pub fn CfdIsFinalizedPsbtInput( + handle: *const c_void, psbt_handle: *const c_void, txid: *const c_char, vout: c_uint) -> c_int; + pub fn CfdAddPsbtTxInWithPubkey( + handle: *const c_void, psbt_handle: *const c_void, txid: *const c_char, vout: c_uint, + sequence: c_uint, amount: c_longlong, locking_script: *const c_char, + descriptor: *const c_char, full_tx_hex: *const c_char) -> c_int; + pub fn CfdAddPsbtTxInWithScript( + handle: *const c_void, psbt_handle: *const c_void, txid: *const c_char, vout: c_uint, + sequence: c_uint, amount: c_longlong, locking_script: *const c_char, + redeem_script: *const c_char, descriptor: *const c_char, + full_tx_hex: *const c_char) -> c_int; + pub fn CfdSetPsbtTxInUtxo( + handle: *const c_void, psbt_handle: *const c_void, txid: *const c_char, vout: c_uint, + amount: c_longlong, locking_script: *const c_char, full_tx_hex: *const c_char) -> c_int; + pub fn CfdSetPsbtTxInBip32Pubkey( + handle: *const c_void, psbt_handle: *const c_void, txid: *const c_char, vout: c_uint, + pubkey: *const c_char, fingerprint: *const c_char, bip32_path: *const c_char) -> c_int; + pub fn CfdSetPsbtSignature( + handle: *const c_void, psbt_handle: *const c_void, txid: *const c_char, vout: c_uint, + pubkey: *const c_char, der_signature: *const c_char) -> c_int; + pub fn CfdSetPsbtSighashType( + handle: *const c_void, psbt_handle: *const c_void, txid: *const c_char, vout: c_uint, + sighash_type: c_int) -> c_int; + pub fn CfdSetPsbtFinalizeScript( + handle: *const c_void, psbt_handle: *const c_void, txid: *const c_char, vout: c_uint, + scriptsig: *const c_char) -> c_int; + pub fn CfdClearPsbtSignData( + handle: *const c_void, psbt_handle: *const c_void, txid: *const c_char, vout: c_uint) -> c_int; + pub fn CfdGetPsbtSighashType( + handle: *const c_void, psbt_handle: *const c_void, txid: *const c_char, vout: c_uint, + sighash_type: *mut c_int) -> c_int; + pub fn CfdGetPsbtUtxoData( + handle: *const c_void, psbt_handle: *const c_void, txid: *const c_char, vout: c_uint, + amount: *mut c_longlong, locking_script: *mut *mut c_char, redeem_script: *mut *mut c_char, + descriptor: *mut *mut c_char, full_tx_hex: *mut *mut c_char) -> c_int; + pub fn CfdGetPsbtUtxoDataByIndex( + handle: *const c_void, psbt_handle: *const c_void, index: c_uint, txid: *mut *mut c_char, + vout: *mut c_uint, amount: *mut c_longlong, locking_script: *mut *mut c_char, + redeem_script: *mut *mut c_char, descriptor: *mut *mut c_char, full_tx_hex: *mut *mut c_char) -> c_int; + pub fn CfdAddPsbtTxOutWithPubkey( + handle: *const c_void, psbt_handle: *const c_void, amount: c_longlong, + locking_script: *const c_char, descriptor: *const c_char, index: *mut c_uint) -> c_int; + pub fn CfdAddPsbtTxOutWithScript( + handle: *const c_void, psbt_handle: *const c_void, amount: c_longlong, + locking_script: *const c_char, redeem_script: *const c_char, + descriptor: *const c_char, index: *mut c_uint) -> c_int; + pub fn CfdSetPsbtTxOutBip32Pubkey( + handle: *const c_void, psbt_handle: *const c_void, index: c_uint, pubkey: *const c_char, + fingerprint: *const c_char, bip32_path: *const c_char) -> c_int; + pub fn CfdGetPsbtTxInIndex( + handle: *const c_void, psbt_handle: *const c_void, txid: *const c_char, vout: c_uint, + index: *mut c_uint) -> c_int; + pub fn CfdGetPsbtPubkeyRecord( + handle: *const c_void, psbt_handle: *const c_void, record_kind: c_int, index: c_uint, + pubkey: *const c_char, value: *mut *mut c_char) -> c_int; // (txin:signature, key. txout:key) + pub fn CfdIsFindPsbtPubkeyRecord( + handle: *const c_void, psbt_handle: *const c_void, record_kind: c_int, index: c_uint, + pubkey: *const c_char) -> c_int; + pub fn CfdGetPsbtBip32Data( + handle: *const c_void, psbt_handle: *const c_void, record_kind: c_int, index: c_uint, + pubkey: *const c_char, fingerprint: *mut *mut c_char, bip32_path: *mut *mut c_char) -> c_int; + pub fn CfdGetPsbtPubkeyList( + handle: *const c_void, psbt_handle: *const c_void, record_kind: c_int, index: c_uint, + list_num: *mut c_uint, pubkey_list_handle: *mut *mut c_void) -> c_int; + pub fn CfdGetPsbtPubkeyListData( + handle: *const c_void, pubkey_list_handle: *const c_void, index: c_uint, pubkey: *mut *mut c_char, + pubkey_hex: *mut *mut c_char) -> c_int; + pub fn CfdGetPsbtPubkeyListBip32Data( + handle: *const c_void, pubkey_list_handle: *const c_void, index: c_uint, pubkey: *mut *mut c_char, + fingerprint: *mut *mut c_char, bip32_path: *mut *mut c_char) -> c_int; + pub fn CfdFreePsbtPubkeyList(handle: *const c_void, pubkey_list_handle: *const c_void) -> c_int; + pub fn CfdGetPsbtByteDataList( + handle: *const c_void, psbt_handle: *const c_void, record_kind: c_int, index: c_uint, + list_num: *mut c_uint, data_list_handle: *mut *mut c_void) -> c_int; + pub fn CfdGetPsbtByteDataItem( + handle: *const c_void, data_list_handle: *const c_void, index: c_uint, data: *mut *mut c_char) -> c_int; + pub fn CfdFreePsbtByteDataList(handle: *const c_void, data_list_handle: *const c_void) -> c_int; + pub fn CfdAddPsbtGlobalXpubkey( + handle: *const c_void, psbt_handle: *const c_void, xpubkey: *const c_char, + fingerprint: *const c_char, bip32_path: *const c_char) -> c_int; + pub fn CfdSetPsbtRedeemScript( + handle: *const c_void, psbt_handle: *const c_void, record_type: c_int, index: c_uint, + redeem_script: *const c_char) -> c_int; + pub fn CfdAddPsbtRecord( + handle: *const c_void, psbt_handle: *const c_void, record_type: c_int, index: c_uint, + key: *const c_char, value: *const c_char) -> c_int; + pub fn CfdGetPsbtRecord( + handle: *const c_void, psbt_handle: *const c_void, record_type: c_int, index: c_uint, + key: *const c_char, value: *mut *mut c_char) -> c_int; + pub fn CfdIsFindPsbtRecord( + handle: *const c_void, psbt_handle: *const c_void, record_type: c_int, index: c_uint, + key: *const c_char) -> c_int; + pub fn CfdVerifyPsbtTxIn( + handle: *const c_void, psbt_handle: *const c_void, txid: *const c_char, vout: c_uint) -> c_int; + pub fn CfdInitializeFundPsbt(handle: *const c_void, fund_handle: *mut *mut c_void) -> c_int; + pub fn CfdFundPsbtAddToUtxoList( + handle: *const c_void, fund_handle: *const c_void, txid: *const c_char, vout: c_uint, + amount: c_longlong, asset: *const c_char, descriptor: *const c_char, + scriptsig_template: *const c_char, full_utxo_tx: *const c_char) -> c_int; + pub fn CfdSetOptionFundPsbt( + handle: *const c_void, fund_handle: *const c_void, key: c_int, int64_value: c_longlong, + double_value: c_double, bool_value: bool) -> c_int; + pub fn CfdFinalizeFundPsbt( + handle: *const c_void, psbt_handle: *const c_void, fund_handle: *const c_void, + change_address_descriptor: *const c_char, tx_fee: *mut c_longlong, + used_utxo_count: *mut c_uint) -> c_int; + pub fn CfdGetFundPsbtUsedUtxo( + handle: *const c_void, fund_handle: *const c_void, index: c_uint, utxo_index: *mut c_uint, + txid: *mut *mut c_char, vout: *mut c_uint, amount: *mut c_longlong, asset: *mut *mut c_char, + descriptor: *mut *mut c_char, scriptsig_template: *mut *mut c_char) -> c_int; + pub fn CfdFreeFundPsbt(handle: *const c_void, fund_handle: *const c_void) -> c_int; } diff --git a/src/address.rs b/src/address.rs index 3017c7d..257fb4a 100644 --- a/src/address.rs +++ b/src/address.rs @@ -3,10 +3,10 @@ extern crate libc; use self::libc::{c_char, c_int, c_uint, c_void}; use crate::common::{ - alloc_c_string, collect_cstring_and_free, collect_multi_cstring_and_free, CfdError, ErrorHandle, - Network, + alloc_c_string, collect_cstring_and_free, collect_multi_cstring_and_free, ByteData, CfdError, + ErrorHandle, Network, }; -use crate::{key::Pubkey, script::Script}; +use crate::{key::Pubkey, schnorr::SchnorrPubkey, script::Script}; use std::fmt; use std::ptr; use std::result::Result::{Err, Ok}; @@ -32,6 +32,8 @@ pub enum HashType { P2shP2wpkh, /// p2sh-p2wsh P2shP2wsh, + /// taproot + Taproot, /// unknown type Unknown, } @@ -45,6 +47,7 @@ impl HashType { 4 => HashType::P2wpkh, 5 => HashType::P2shP2wsh, 6 => HashType::P2shP2wpkh, + 7 => HashType::Taproot, _ => HashType::Unknown, } } @@ -57,7 +60,8 @@ impl HashType { HashType::P2wpkh => 4, HashType::P2shP2wsh => 5, HashType::P2shP2wpkh => 6, - HashType::Unknown => 0, + HashType::Taproot => 7, + HashType::Unknown => 0xff, } } @@ -79,6 +83,7 @@ impl HashType { HashType::P2wsh => AddressType::P2wshAddress, HashType::P2shP2wpkh => AddressType::P2shP2wpkhAddress, HashType::P2shP2wsh => AddressType::P2shP2wshAddress, + HashType::Taproot => AddressType::TaprootAddress, HashType::Unknown => AddressType::Unknown, } } @@ -107,6 +112,7 @@ impl fmt::Display for HashType { HashType::P2wpkh => write!(f, "HashType:p2wpkh"), HashType::P2shP2wsh => write!(f, "HashType:p2sh-p2wsh"), HashType::P2shP2wpkh => write!(f, "HashType:p2sh-p2wpkh"), + HashType::Taproot => write!(f, "HashType:taproot"), HashType::Unknown => write!(f, "HashType:unknown"), } } @@ -127,6 +133,8 @@ pub enum AddressType { P2shP2wpkhAddress, /// p2sh-p2wsh address (p2sh-segwit) P2shP2wshAddress, + /// taproot address + TaprootAddress, /// unknown address Unknown, } @@ -140,7 +148,8 @@ impl AddressType { AddressType::P2wpkhAddress => 4, AddressType::P2shP2wshAddress => 5, AddressType::P2shP2wpkhAddress => 6, - AddressType::Unknown => 0, + AddressType::TaprootAddress => 7, + AddressType::Unknown => 0xff, } } @@ -166,6 +175,7 @@ impl AddressType { AddressType::P2wshAddress => HashType::P2wsh, AddressType::P2shP2wpkhAddress => HashType::P2shP2wpkh, AddressType::P2shP2wshAddress => HashType::P2shP2wsh, + AddressType::TaprootAddress => HashType::Taproot, AddressType::Unknown => HashType::Unknown, } } @@ -182,6 +192,7 @@ impl AddressType { /// ``` pub fn get_witness_version(&self) -> WitnessVersion { match self { + AddressType::TaprootAddress => WitnessVersion::Version1, AddressType::P2wpkhAddress => WitnessVersion::Version0, AddressType::P2wshAddress => WitnessVersion::Version0, AddressType::P2shP2wpkhAddress => WitnessVersion::Version0, @@ -200,6 +211,7 @@ impl fmt::Display for AddressType { AddressType::P2wpkhAddress => write!(f, "Address:p2wpkh"), AddressType::P2shP2wshAddress => write!(f, "Address:p2sh-p2wsh"), AddressType::P2shP2wpkhAddress => write!(f, "Address:p2sh-p2wpkh"), + AddressType::TaprootAddress => write!(f, "Address:taproot"), AddressType::Unknown => write!(f, "Address:unknown"), } } @@ -280,6 +292,7 @@ pub struct Address { address_type: AddressType, p2sh_wrapped_segwit_script: Script, witness_version: WitnessVersion, + hash: ByteData, } impl Address { @@ -297,7 +310,7 @@ impl Address { /// ``` pub fn from_string(address: &str) -> Result { let addr = alloc_c_string(address)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut network_type_c: c_int = 0; let mut hash_type_c: c_int = 0; let mut witness_version_c: c_int = 0; @@ -318,6 +331,7 @@ impl Address { 0 => { let str_list = unsafe { collect_multi_cstring_and_free(&[locking_script, hash]) }?; let script_obj = &str_list[0]; + let hash_obj = ByteData::from_str(&str_list[1])?; let script = Script::from_hex(script_obj)?; let hash_type = HashType::from_c_value(hash_type_c); Ok(Address { @@ -325,8 +339,9 @@ impl Address { locking_script: script, network_type: Network::from_c_value(network_type_c), address_type: hash_type.to_address_type(), - p2sh_wrapped_segwit_script: Script::default(), witness_version: hash_type.get_witness_version(), + hash: hash_obj, + ..Address::default() }) } _ => Err(handle.get_error(error_code)), @@ -353,7 +368,7 @@ impl Address { /// ``` pub fn from_locking_script(script: &Script, network_type: &Network) -> Result { let hex = alloc_c_string(&script.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut address: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdGetAddressFromLockingScript( @@ -440,6 +455,7 @@ impl Address { ) -> Result { let script = Script::multisig(require_num, pubkey_list)?; Address::get_address( + ptr::null(), ptr::null(), &script, address_type.to_c_value(), @@ -468,6 +484,7 @@ impl Address { Address::get_address( pubkey, ptr::null(), + ptr::null(), HashType::P2pkh.to_c_value(), network_type.to_c_value(), ) @@ -494,6 +511,7 @@ impl Address { Address::get_address( pubkey, ptr::null(), + ptr::null(), HashType::P2wpkh.to_c_value(), network_type.to_c_value(), ) @@ -520,6 +538,7 @@ impl Address { Address::get_address( pubkey, ptr::null(), + ptr::null(), HashType::P2shP2wpkh.to_c_value(), network_type.to_c_value(), ) @@ -544,6 +563,7 @@ impl Address { /// ``` pub fn p2sh(script: &Script, network_type: &Network) -> Result { Address::get_address( + ptr::null(), ptr::null(), script, HashType::P2sh.to_c_value(), @@ -570,6 +590,7 @@ impl Address { /// ``` pub fn p2wsh(script: &Script, network_type: &Network) -> Result { Address::get_address( + ptr::null(), ptr::null(), script, HashType::P2wsh.to_c_value(), @@ -596,6 +617,7 @@ impl Address { /// ``` pub fn p2sh_p2wsh(script: &Script, network_type: &Network) -> Result { Address::get_address( + ptr::null(), ptr::null(), script, HashType::P2shP2wsh.to_c_value(), @@ -627,6 +649,10 @@ impl Address { self.witness_version } + pub fn get_hash(&self) -> &ByteData { + &self.hash + } + /// Get p2wpkh or p2wsh locking script on p2sh-segwit. /// /// # Example @@ -651,6 +677,35 @@ impl Address { } } + /// Create taproot address. + /// + /// # Arguments + /// * `pubkey` - A schnorr public key. + /// * `network_type` - A target network. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::Address; + /// use cfd_rust::Network; + /// use cfd_rust::Pubkey; + /// use cfd_rust::SchnorrPubkey; + /// use std::str::FromStr; + /// let key_str = "031d7463018f867de51a27db866f869ceaf52abab71827a6051bab8a0fd020f4c1"; + /// let pk = Pubkey::from_str(&key_str).expect("fail"); + /// let (key, parity) = SchnorrPubkey::from_pubkey(&pk).expect("fail"); + /// let addr = Address::taproot(&key, &Network::Mainnet).expect("Fail"); + /// ``` + pub fn taproot(pubkey: &SchnorrPubkey, network_type: &Network) -> Result { + Address::get_address( + ptr::null(), + pubkey, + ptr::null(), + HashType::Taproot.to_c_value(), + network_type.to_c_value(), + ) + } + /// Validate an address. /// /// # Example @@ -668,10 +723,8 @@ impl Address { pub fn valid(&self) -> bool { if self.address.is_empty() { false - } else if let Ok(_result) = Address::from_string(&self.address) { - true } else { - false + matches!(Address::from_string(&self.address), Ok(_result)) } } @@ -699,7 +752,7 @@ impl Address { network_type: &Network, ) -> Result, CfdError> { let redeem_script = alloc_c_string(&multisig_script.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut max_key_num: c_uint = 0; let mut addr_multisig_keys_handle: *mut c_void = ptr::null_mut(); let error_code = unsafe { @@ -768,6 +821,7 @@ impl Address { fn get_address( pubkey: *const Pubkey, + schnorr_pubkey: *const SchnorrPubkey, script: *const Script, hash_type: c_int, network_type: c_int, @@ -775,7 +829,10 @@ impl Address { let pubkey_hex = unsafe { match pubkey.as_ref() { Some(pubkey) => alloc_c_string(&pubkey.to_hex()), - _ => alloc_c_string(""), + _ => match schnorr_pubkey.as_ref() { + Some(schnorr_pubkey) => alloc_c_string(&schnorr_pubkey.to_hex()), + _ => alloc_c_string(""), + }, } }?; let redeem_script = unsafe { @@ -784,7 +841,7 @@ impl Address { _ => alloc_c_string(""), } }?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut address: *mut c_char = ptr::null_mut(); let mut locking_script: *mut c_char = ptr::null_mut(); let mut p2sh_segwit_locking_script: *mut c_char = ptr::null_mut(); @@ -805,19 +862,26 @@ impl Address { let str_list = unsafe { collect_multi_cstring_and_free(&[address, locking_script, p2sh_segwit_locking_script]) }?; - let addr_obj = &str_list[0]; + let addr_str = &str_list[0]; let script_obj = &str_list[1]; let segwit_obj = &str_list[2]; + let hash_obj = unsafe { + match schnorr_pubkey.as_ref() { + Some(schnorr_pubkey) => ByteData::from_str(&schnorr_pubkey.to_hex()), + _ => Ok(ByteData::default()), + }? + }; let addr_locking_script = Script::from_hex(script_obj)?; let segwit_script = Script::from_hex(segwit_obj)?; let hash_type_obj = HashType::from_c_value(hash_type); Ok(Address { - address: addr_obj.clone(), + address: addr_str.clone(), locking_script: addr_locking_script, network_type: Network::from_c_value(network_type), address_type: hash_type_obj.to_address_type(), p2sh_wrapped_segwit_script: segwit_script, witness_version: hash_type_obj.get_witness_version(), + hash: hash_obj, }) } _ => Err(handle.get_error(error_code)), @@ -849,6 +913,7 @@ impl Default for Address { address_type: AddressType::Unknown, p2sh_wrapped_segwit_script: Script::default(), witness_version: WitnessVersion::None, + hash: ByteData::default(), } } } diff --git a/src/common.rs b/src/common.rs index 712c08d..bfd6c89 100644 --- a/src/common.rs +++ b/src/common.rs @@ -38,6 +38,8 @@ pub enum CfdError { DiskAccess(String), /// Sign verification error. SignVerification(String), + /// Not found error. + NotFound(String), } impl fmt::Display for CfdError { @@ -53,6 +55,7 @@ impl fmt::Display for CfdError { CfdError::Connection(ref a) => write!(f, "[Connection]: {}", a), CfdError::DiskAccess(ref a) => write!(f, "[DiskAccess]: {}", a), CfdError::SignVerification(ref a) => write!(f, "[SignVerification]: {}", a), + CfdError::NotFound(ref a) => write!(f, "[NotFound]: {}", a), } } } @@ -70,6 +73,7 @@ impl error::Error for CfdError { CfdError::Connection(..) => "connection error", CfdError::DiskAccess(..) => "disk access error", CfdError::SignVerification(..) => "sign verification error", + CfdError::NotFound(..) => "Not found error", } } } @@ -272,6 +276,23 @@ impl ByteData { self.data.is_empty() } + /// Output fixed 32byte array into byte data. + /// If the size of the byte data is less than 32 bytes, it returns an array of all zeros. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::ByteData; + /// use std::str::FromStr; + /// let data = ByteData::from_str( + /// "4691fbb1196f4675241c8958a7ab6378a63aa0cc008ed03d216fd038357f52fd", + /// ).expect("Fail"); + /// let array = data.to_32byte_array(); + /// ``` + pub fn to_32byte_array(&self) -> [u8; 32] { + copy_array_32byte(&self.data) + } + /// Get serialized byte data. /// /// # Example @@ -286,7 +307,7 @@ impl ByteData { /// ``` pub fn serialize(&self) -> Result { let buffer = alloc_c_string(&hex_from_bytes(&self.data))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdSerializeByteData(handle.as_handle(), buffer.as_ptr(), &mut output) }; @@ -429,7 +450,7 @@ impl Amount { /// // byte_data == "a086010000000000" /// ``` pub fn as_byte(&self) -> Result, CfdError> { - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdGetConfidentialValueHex(handle.as_handle(), self.satoshi_amount, true, &mut output) @@ -519,8 +540,9 @@ impl FromStr for ReverseContainer { } else { let byte_data = ByteData::from_slice_reverse(&bytes); let reverse_bytes = byte_data.to_slice(); - let mut data = ReverseContainer::default(); - data.data = copy_array_32byte(&reverse_bytes); + let data = ReverseContainer { + data: copy_array_32byte(&reverse_bytes), + }; Ok(data) } } @@ -541,7 +563,7 @@ impl Default for ReverseContainer { pub fn request_json(request: &str, option: &str) -> Result { let req_name = alloc_c_string(request)?; let opt_data = alloc_c_string(option)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdRequestExecuteJson( @@ -607,19 +629,17 @@ impl ErrorHandle { } // FIXME: I might use Drop and Rc. - pub fn free_handle(&self) -> bool { + pub fn free_handle(&mut self) -> bool { unsafe { let mut result: bool = false; - if self.handle.is_null() { - // println!("CfdFreeHandle NG. null-ptr."); - } else { + if !self.handle.is_null() { let cfd_ret = CfdFreeHandle(self.handle); if cfd_ret == 0 { - // self.handle = ptr::null_mut(); result = true; } else { println!("CfdFreeHandle NG:{}", cfd_ret); } + self.handle = ptr::null_mut(); } result } @@ -638,6 +658,7 @@ impl ErrorHandle { 5 => CfdError::Connection(err_msg), 6 => CfdError::DiskAccess(err_msg), 7 => CfdError::SignVerification(err_msg), + 8 => CfdError::NotFound(err_msg), _ => CfdError::Unknown(err_msg), } } diff --git a/src/confidential_address.rs b/src/confidential_address.rs index d50aede..dbde83f 100644 --- a/src/confidential_address.rs +++ b/src/confidential_address.rs @@ -49,7 +49,7 @@ impl ConfidentialAddress { ) -> Result { let addr = alloc_c_string(address.to_str())?; let ct_key = alloc_c_string(&confidential_key.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut confidential_address: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdCreateConfidentialAddress( @@ -88,7 +88,7 @@ impl ConfidentialAddress { /// ``` pub fn parse(confidential_address: &str) -> Result { let ct_addr = alloc_c_string(confidential_address)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut network_type_c: c_int = 0; let mut address: *mut c_char = ptr::null_mut(); let mut confidential_key: *mut c_char = ptr::null_mut(); diff --git a/src/confidential_transaction.rs b/src/confidential_transaction.rs index 702e0bb..4951ead 100644 --- a/src/confidential_transaction.rs +++ b/src/confidential_transaction.rs @@ -10,7 +10,7 @@ use crate::common::{ 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_DISABLE, + TxInData, Txid, UtxoData, SEQUENCE_LOCK_TIME_FINAL, }; use crate::{ address::{Address, HashType}, @@ -203,15 +203,17 @@ impl ConfidentialAsset { )), }, 32 => { - let mut asset_obj = ConfidentialAsset::default(); - asset_obj.data = get_commitment_from_byte(data, 32).to_vec(); + let asset_obj = ConfidentialAsset { + data: get_commitment_from_byte(data, 32).to_vec(), + }; Ok(asset_obj) } 33 => match data[0] { 0 => Ok(ConfidentialAsset::default()), 1 | 10 | 11 => { - let mut asset_obj = ConfidentialAsset::default(); - asset_obj.data = get_commitment_from_byte(data, 32).to_vec(); + let asset_obj = ConfidentialAsset { + data: get_commitment_from_byte(data, 32).to_vec(), + }; Ok(asset_obj) } _ => Err(CfdError::IllegalArgument( @@ -236,17 +238,11 @@ impl ConfidentialAsset { } pub fn is_blind(&self) -> bool { - match self.data[0] { - 10 | 11 => true, - _ => false, - } + matches!(self.data[0], 10 | 11) } pub fn is_empty(&self) -> bool { - match self.data[0] { - 0 => true, - _ => false, - } + matches!(self.data[0], 0) } pub fn as_bytes(&self) -> Vec { @@ -288,7 +284,7 @@ impl ConfidentialAsset { } let asset_str = alloc_c_string(&self.to_hex())?; let abf_str = alloc_c_string(&asset_blind_factor.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdGetAssetCommitment( @@ -377,15 +373,17 @@ impl ConfidentialNonce { )), }, 32 => { - let mut nonce_obj = ConfidentialNonce::default(); - nonce_obj.data = get_commitment_from_byte(data, 32).to_vec(); + let nonce_obj = ConfidentialNonce { + data: get_commitment_from_byte(data, 32).to_vec(), + }; Ok(nonce_obj) } 33 => match data[0] { 0 => Ok(ConfidentialNonce::default()), 1 | 2 | 3 => { - let mut nonce_obj = ConfidentialNonce::default(); - nonce_obj.data = get_commitment_from_byte(data, 32).to_vec(); + let nonce_obj = ConfidentialNonce { + data: get_commitment_from_byte(data, 32).to_vec(), + }; Ok(nonce_obj) } _ => Err(CfdError::IllegalArgument( @@ -427,17 +425,11 @@ impl ConfidentialNonce { } pub fn is_blind(&self) -> bool { - match self.data[0] { - 2 | 3 => true, - _ => false, - } + matches!(self.data[0], 2 | 3) } pub fn is_empty(&self) -> bool { - match self.data[0] { - 0 => true, - _ => false, - } + matches!(self.data[0], 0) } pub fn as_bytes(&self) -> Vec { @@ -507,9 +499,10 @@ impl ConfidentialValue { )), }, 8 => { - let mut value_obj = ConfidentialValue::default(); - value_obj.data = get_commitment_from_byte(data, 8).to_vec(); - value_obj.amount = ConfidentialValue::get_amount(data); + let value_obj = ConfidentialValue { + data: get_commitment_from_byte(data, 8).to_vec(), + amount: ConfidentialValue::get_amount(data), + }; Ok(value_obj) } 9 | 33 => match data[0] { @@ -520,8 +513,10 @@ impl ConfidentialValue { "Invalid value version format.".to_string(), )) } else { - let mut value_obj = ConfidentialValue::default(); - value_obj.data = get_commitment_from_byte(data, 8).to_vec(); + let mut value_obj = ConfidentialValue { + data: get_commitment_from_byte(data, 8).to_vec(), + ..ConfidentialValue::default() + }; if data[0] == 1 { let unblind_data = get_byte_from_commitment(&value_obj.data, 8); value_obj.amount = ConfidentialValue::get_amount(&unblind_data); @@ -561,7 +556,7 @@ impl ConfidentialValue { /// ``` pub fn from_amount(amount: i64) -> Result { let data = { - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdGetConfidentialValueHex(handle.as_handle(), amount, true, &mut output) }; @@ -575,9 +570,10 @@ impl ConfidentialValue { handle.free_handle(); result }?; - let mut value_obj = ConfidentialValue::default(); - value_obj.data = get_commitment_from_byte(&data, 8).to_vec(); - value_obj.amount = amount; + let value_obj = ConfidentialValue { + data: get_commitment_from_byte(&data, 8).to_vec(), + amount, + }; Ok(value_obj) } @@ -590,17 +586,11 @@ impl ConfidentialValue { } pub fn is_blind(&self) -> bool { - match self.data[0] { - 8 | 9 => true, - _ => false, - } + matches!(self.data[0], 8 | 9) } pub fn is_empty(&self) -> bool { - match self.data[0] { - 0 => true, - _ => false, - } + matches!(self.data[0], 0) } pub fn as_bytes(&self) -> Vec { @@ -646,7 +636,7 @@ impl ConfidentialValue { } let asset_str = alloc_c_string(&asset_commitment.as_str())?; let vbf_str = alloc_c_string(&amount_blind_factor.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdGetValueCommitment( @@ -719,7 +709,7 @@ pub fn get_issuance_blinding_key( ) -> Result { let privkey = alloc_c_string(&master_blinding_key.to_hex())?; let txid = alloc_c_string(&outpoint.get_txid().to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdGetIssuanceBlindingKey( @@ -763,7 +753,7 @@ pub fn get_default_blinding_key( ) -> Result { let privkey = alloc_c_string(&master_blinding_key.to_hex())?; let script = alloc_c_string(&locking_script.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdGetDefaultBlindingKey( @@ -1212,11 +1202,12 @@ impl ConfidentialTxOutData { asset: &ConfidentialAsset, address: &Address, ) -> ConfidentialTxOutData { - let mut data = ConfidentialTxOutData::default(); - data.amount = amount; - data.address = address.clone(); - data.asset = asset.clone(); - data + ConfidentialTxOutData { + amount, + address: address.clone(), + asset: asset.clone(), + ..ConfidentialTxOutData::default() + } } pub fn from_confidential_address( @@ -1224,12 +1215,13 @@ impl ConfidentialTxOutData { asset: &ConfidentialAsset, confidential_address: &ConfidentialAddress, ) -> ConfidentialTxOutData { - let mut data = ConfidentialTxOutData::default(); - data.amount = amount; - data.address = confidential_address.get_address().clone(); - data.confidential_address = confidential_address.clone(); - data.asset = asset.clone(); - data + ConfidentialTxOutData { + amount, + address: confidential_address.get_address().clone(), + confidential_address: confidential_address.clone(), + asset: asset.clone(), + ..ConfidentialTxOutData::default() + } } pub fn from_locking_script( @@ -1238,29 +1230,33 @@ impl ConfidentialTxOutData { locking_script: &Script, nonce: &ConfidentialNonce, ) -> ConfidentialTxOutData { - let mut data = ConfidentialTxOutData::default(); - data.amount = amount; - data.locking_script = locking_script.clone(); - data.nonce = nonce.clone(); - data.asset = asset.clone(); - data + ConfidentialTxOutData { + amount, + locking_script: locking_script.clone(), + nonce: nonce.clone(), + asset: asset.clone(), + ..ConfidentialTxOutData::default() + } } pub fn from_fee(amount: i64, asset: &ConfidentialAsset) -> ConfidentialTxOutData { - let mut data = ConfidentialTxOutData::default(); - data.amount = amount; - data.asset = asset.clone(); - data + ConfidentialTxOutData { + amount, + asset: asset.clone(), + ..ConfidentialTxOutData::default() + } } pub fn from_destroy_amount( amount: i64, asset: &ConfidentialAsset, ) -> Result { - let mut data = ConfidentialTxOutData::default(); - data.amount = amount; - data.asset = asset.clone(); - data.locking_script = Script::from_slice(&[0x6a])?; + let data = ConfidentialTxOutData { + amount, + asset: asset.clone(), + locking_script: Script::from_slice(&[0x6a])?, + ..ConfidentialTxOutData::default() + }; Ok(data) } @@ -1276,9 +1272,11 @@ impl ConfidentialTxOutData { asset: &ConfidentialAsset, amount: i64, ) -> Result { - let mut txout = ConfidentialTxOutData::default(); - txout.asset = asset.clone(); - txout.amount = amount; + let mut txout = ConfidentialTxOutData { + asset: asset.clone(), + amount, + ..ConfidentialTxOutData::default() + }; let ct_addr_ret = ConfidentialAddress::parse(address); if let Ok(ct_addr) = ct_addr_ret { txout.confidential_address = ct_addr; @@ -1312,9 +1310,27 @@ pub struct ConfidentialTxData { impl Default for ConfidentialTxData { fn default() -> ConfidentialTxData { + // default txid: version=2, locktime=0 + let txid_value = + match Txid::from_str("c7e8a6e4ebd4981c43ff919703d54e91b4b3cb2325caf102dfc384bcad455c6f") { + Ok(_txid) => _txid, + _ => Txid::default(), + }; + let wit_hash = + match Txid::from_str("d8a93718eaf9feba4362d2c091d4e58ccabe9f779957336269b4b917be9856da") { + Ok(_txid) => _txid, + _ => Txid::default(), + }; ConfidentialTxData { - tx_data: TxData::default(), - wit_hash: Txid::default(), + tx_data: TxData { + txid: txid_value.clone(), + wtxid: txid_value, + size: 11, + vsize: 11, + weight: 44, + ..TxData::default() + }, + wit_hash, } } } @@ -1501,7 +1517,7 @@ impl Default for ConfidentialTxIn { fn default() -> ConfidentialTxIn { ConfidentialTxIn { outpoint: OutPoint::default(), - sequence: SEQUENCE_LOCK_TIME_DISABLE, + sequence: SEQUENCE_LOCK_TIME_FINAL, script_sig: Script::default(), issuance: Issuance::default(), script_witness: ScriptWitness::default(), @@ -1531,8 +1547,10 @@ impl ConfidentialTxOut { } else { &item.locking_script }; - let mut txout = ConfidentialTxOut::default(); - txout.locking_script = script.clone(); + let mut txout = ConfidentialTxOut { + locking_script: script.clone(), + ..ConfidentialTxOut::default() + }; if let Ok(value) = ConfidentialValue::from_amount(item.amount) { txout.value = value; } @@ -2031,6 +2049,7 @@ impl ConfidentialTransaction { true => ByteData::default(), _ => value.as_byte_data(), }, + ..SigHashOption::default() }; ope.create_sighash( &hex_from_bytes(&self.tx), @@ -2085,6 +2104,7 @@ impl ConfidentialTransaction { true => ByteData::default(), _ => value.as_byte_data(), }, + ..SigHashOption::default() }; ope.create_sighash( &hex_from_bytes(&self.tx), @@ -2204,6 +2224,7 @@ impl ConfidentialTransaction { true => ByteData::default(), _ => value.as_byte_data(), }, + ..SigHashOption::default() }; let tx = ope.sign_with_privkey(&tx_hex, outpoint, hash_type, &key, &option, true)?; let new_tx_hex = ope.get_last_tx(); @@ -2462,6 +2483,7 @@ impl ConfidentialTransaction { true => ByteData::default(), _ => value.as_byte_data(), }, + ..SigHashOption::default() }; let key = HashTypeData::from_pubkey(pubkey); ope.verify_signature( @@ -2522,6 +2544,7 @@ impl ConfidentialTransaction { true => ByteData::default(), _ => value.as_byte_data(), }, + ..SigHashOption::default() }; let key = HashTypeData::new(pubkey, redeem_script); ope.verify_signature( @@ -2577,6 +2600,7 @@ impl ConfidentialTransaction { true => ByteData::default(), _ => value.as_byte_data(), }, + ..SigHashOption::default() }; ope.verify_sign( &hex_from_bytes(&self.tx), @@ -2633,6 +2657,7 @@ impl ConfidentialTransaction { true => ByteData::default(), _ => value.as_byte_data(), }, + ..SigHashOption::default() }; ope.verify_sign( &hex_from_bytes(&self.tx), @@ -2751,14 +2776,11 @@ impl FromStr for ConfidentialTransaction { impl Default for ConfidentialTransaction { fn default() -> ConfidentialTransaction { - match ConfidentialTransaction::new(2, 0) { - Ok(tx) => tx, - _ => ConfidentialTransaction { - tx: [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].to_vec(), - data: ConfidentialTxData::default(), - txin_list: vec![], - txout_list: vec![], - }, + ConfidentialTransaction { + tx: [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].to_vec(), + data: ConfidentialTxData::default(), + txin_list: vec![], + txout_list: vec![], } } } @@ -2816,7 +2838,7 @@ impl ConfidentialTxOperation { // set_blind_tx_option let tx_str = alloc_c_string(tx)?; let empty_str = alloc_c_string("")?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut blind_handle: *mut c_void = ptr::null_mut(); let error_code = unsafe { CfdInitializeBlindTx(handle.as_handle(), &mut blind_handle) }; let result = match error_code { @@ -3023,7 +3045,7 @@ impl ConfidentialTxOperation { ) -> Result { let tx_str = alloc_c_string(tx)?; let privkey = alloc_c_string(&blinding_key.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut asset_value: c_longlong = 0; let mut asset: *mut c_char = ptr::null_mut(); let mut asset_abf: *mut c_char = ptr::null_mut(); @@ -3067,7 +3089,7 @@ impl ConfidentialTxOperation { let tx_str = alloc_c_string(tx)?; let asset_key = alloc_c_string(&asset_blinding_key.to_hex())?; let token_key = alloc_c_string(&token_blinding_key.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut asset_value: c_longlong = 0; let mut token_value: c_longlong = 0; let mut asset: *mut c_char = ptr::null_mut(); @@ -3137,7 +3159,7 @@ impl ConfidentialTxOperation { let entropy_hex = alloc_c_string(&entropy.to_hex())?; let address = alloc_c_string(send_address)?; let empty_str = alloc_c_string("")?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut asset: *mut c_char = ptr::null_mut(); let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { @@ -3194,7 +3216,7 @@ impl ConfidentialTxOperation { amount: i64, ) -> Result, CfdError> { let tx_str = alloc_c_string(tx)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdUpdateTxOutAmount( @@ -3220,7 +3242,7 @@ impl ConfidentialTxOperation { pub fn update_fee_amount(&mut self, tx: &str, amount: i64) -> Result, CfdError> { let tx_str = alloc_c_string(tx)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let result = { let index = { let mut index: c_uint = 0; @@ -3266,7 +3288,7 @@ impl ConfidentialTxOperation { } pub fn get_all_data(&mut self, tx: &str) -> Result { - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let result = { let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; let tx_result = { @@ -3312,7 +3334,7 @@ impl ConfidentialTxOperation { } pub fn get_tx_data(&self, tx: &str) -> Result { - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let result = self.get_tx_data_internal(&handle, &TxDataHandle::empty(), tx); handle.free_handle(); result @@ -3370,7 +3392,7 @@ impl ConfidentialTxOperation { tx: &str, outpoint: &OutPoint, ) -> Result { - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let result = { let tx_data_handle = TxDataHandle::new(&handle, &self.network, tx)?; let list_result = { @@ -3573,7 +3595,7 @@ impl ConfidentialTxOperation { let value_commitment = alloc_c_string(&option.value_byte.to_hex())?; let amount = option.amount; let sighash_type = option.sighash_type; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdCreateConfidentialSighash( @@ -3616,7 +3638,7 @@ impl ConfidentialTxOperation { let pubkey_hex = alloc_c_string(&key.to_pubkey().to_hex())?; let privkey_hex = alloc_c_string(&key.to_privkey().to_hex())?; let value_hex = alloc_c_string(&option.value_byte.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let sighash_type = option.sighash_type; let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { @@ -3657,7 +3679,7 @@ impl ConfidentialTxOperation { ) -> Result { let tx_str = alloc_c_string(tx)?; let fee_asset = alloc_c_string(&option.fee_asset.get_unblind_asset()?)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut fee_handle: *mut c_void = ptr::null_mut(); let error_code = unsafe { CfdInitializeEstimateFee( @@ -3748,7 +3770,7 @@ impl ConfidentialTxOperation { fee_param: &FeeOption, ) -> Result { let fee_asset = alloc_c_string(&fee_param.fee_asset.get_unblind_asset()?)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut coin_handle: *mut c_void = ptr::null_mut(); let error_code = unsafe { CfdInitializeCoinSelection( @@ -3921,7 +3943,7 @@ impl ConfidentialTxOperation { }; let tx_hex = alloc_c_string(tx)?; let fee_asset = alloc_c_string(&fee_param.fee_asset.get_unblind_asset()?)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut fund_handle: *mut c_void = ptr::null_mut(); let error_code = unsafe { CfdInitializeFundRawTx( @@ -4179,7 +4201,7 @@ impl ConfidentialTxOperation { txout_list: &[ConfidentialTxOutData], ) -> Result, CfdError> { let tx_str = alloc_c_string(tx)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut create_handle: *mut c_void = ptr::null_mut(); let error_code = unsafe { CfdInitializeTransaction( diff --git a/src/descriptor.rs b/src/descriptor.rs index 3a69577..99faa0b 100644 --- a/src/descriptor.rs +++ b/src/descriptor.rs @@ -224,20 +224,22 @@ pub struct DescriptorScriptData { impl DescriptorScriptData { pub fn from_raw_script(depth: u32, redeem_script: &Script) -> DescriptorScriptData { - let mut obj = DescriptorScriptData::default(); - obj.script_type = DescriptorScriptType::Raw; - obj.depth = depth; - obj.redeem_script = redeem_script.clone(); - obj + DescriptorScriptData { + script_type: DescriptorScriptType::Raw, + depth, + redeem_script: redeem_script.clone(), + ..DescriptorScriptData::default() + } } pub fn from_address(depth: u32, hash_type: &HashType, address: &Address) -> DescriptorScriptData { - let mut obj = DescriptorScriptData::default(); - obj.script_type = DescriptorScriptType::Addr; - obj.depth = depth; - obj.hash_type = *hash_type; - obj.address = address.clone(); - obj + DescriptorScriptData { + script_type: DescriptorScriptType::Addr, + depth, + hash_type: *hash_type, + address: address.clone(), + ..DescriptorScriptData::default() + } } pub fn from_pubkey( @@ -247,13 +249,14 @@ impl DescriptorScriptData { address: Address, key_data: KeyData, ) -> DescriptorScriptData { - let mut obj = DescriptorScriptData::default(); - obj.script_type = script_type; - obj.depth = depth; - obj.hash_type = hash_type; - obj.address = address; - obj.key_data = key_data; - obj + DescriptorScriptData { + script_type, + depth, + hash_type, + address, + key_data, + ..DescriptorScriptData::default() + } } pub fn from_script( @@ -263,13 +266,14 @@ impl DescriptorScriptData { address: Address, script: Script, ) -> DescriptorScriptData { - let mut obj = DescriptorScriptData::default(); - obj.script_type = script_type; - obj.depth = depth; - obj.hash_type = hash_type; - obj.address = address; - obj.redeem_script = script; - obj + DescriptorScriptData { + script_type, + depth, + hash_type, + address, + redeem_script: script, + ..DescriptorScriptData::default() + } } pub fn from_key_and_script( @@ -656,7 +660,7 @@ impl Descriptor { ) -> Result { let descriptor_str = alloc_c_string(descriptor)?; let bip32_path_str = alloc_c_string(bip32_path)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut max_num: c_uint = 0; let mut descriptor_handle: *mut c_void = ptr::null_mut(); let error_code = unsafe { @@ -883,10 +887,12 @@ impl Descriptor { /// ``` pub fn has_script_hash(&self) -> bool { match self.root_data.script_type { - DescriptorScriptType::Sh | DescriptorScriptType::Wsh => match self.root_data.hash_type { - HashType::P2sh | HashType::P2wsh | HashType::P2shP2wsh => true, - _ => false, - }, + DescriptorScriptType::Sh | DescriptorScriptType::Wsh => { + matches!( + self.root_data.hash_type, + HashType::P2sh | HashType::P2wsh | HashType::P2shP2wsh + ) + } _ => false, } } @@ -917,10 +923,12 @@ impl Descriptor { DescriptorScriptType::Sh | DescriptorScriptType::Pkh | DescriptorScriptType::Wpkh - | DescriptorScriptType::Combo => match self.root_data.hash_type { - HashType::P2pkh | HashType::P2wpkh | HashType::P2shP2wpkh => true, - _ => false, - }, + | DescriptorScriptType::Combo => { + matches!( + self.root_data.hash_type, + HashType::P2pkh | HashType::P2wpkh | HashType::P2shP2wpkh + ) + } _ => false, } } @@ -965,7 +973,7 @@ impl Descriptor { fn append_checksum(descriptor: &str, network_type: &Network) -> Result { let descriptor_str = alloc_c_string(descriptor)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut desc_added_checksum: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdGetDescriptorChecksum( diff --git a/src/hdwallet.rs b/src/hdwallet.rs index d516dec..a5acff1 100644 --- a/src/hdwallet.rs +++ b/src/hdwallet.rs @@ -67,7 +67,7 @@ pub struct ExtKey { fn generate_pubkey(extkey: &str, network_type: Network) -> Result { let extkey_str = alloc_c_string(extkey)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut pubkey_hex: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdGetPubkeyFromExtkey( @@ -90,7 +90,7 @@ fn generate_pubkey(extkey: &str, network_type: Network) -> Result Result { let extkey_str = alloc_c_string(extkey)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut privkey_hex: *mut c_char = ptr::null_mut(); let mut wif: *mut c_char = ptr::null_mut(); let error_code = unsafe { @@ -117,7 +117,7 @@ fn generate_privkey(extkey: &str, network_type: Network) -> Result Result { let extkey_str = alloc_c_string(extkey)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut version: *mut c_char = ptr::null_mut(); let mut fingerprint: *mut c_char = ptr::null_mut(); let mut chain_code: *mut c_char = ptr::null_mut(); @@ -176,7 +176,7 @@ impl ExtKey { key_type: &ExtKeyType, ) -> Result { let extkey_str = alloc_c_string(&self.extkey)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut extkey_hex: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdCreateExtkeyFromParent( @@ -222,7 +222,7 @@ impl ExtKey { ) -> Result { let extkey_str = alloc_c_string(&self.extkey)?; let path_str = alloc_c_string(path)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut extkey_hex: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdCreateExtkeyFromParentPath( @@ -324,7 +324,7 @@ impl ExtPrivkey { /// ``` pub fn from_seed(seed: &[u8], network_type: &Network) -> Result { let seed_str = alloc_c_string(&hex_from_bytes(seed))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut extkey_hex: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdCreateExtkeyFromSeed( @@ -448,7 +448,7 @@ impl ExtPrivkey { /// ``` pub fn get_ext_pubkey(&self) -> Result { let extkey_str = alloc_c_string(&self.extkey.to_str())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut ext_pubkey_hex: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdCreateExtPubkey( @@ -739,7 +739,7 @@ impl ExtPubkey { }?; let pubkey_hex = alloc_c_string(&pubkey.to_hex())?; let chain_code_hex = alloc_c_string(&chain_code.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut extkey: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdCreateExtkey( @@ -975,7 +975,7 @@ impl HDWallet { /// ``` pub fn mnemonic_word_list(lang: MnemonicLanguage) -> Result, CfdError> { let language = alloc_c_string(&lang.to_str())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut max_num: c_uint = 0; let mut mnemonic_handle: *mut c_void = ptr::null_mut(); let error_code = unsafe { @@ -1047,7 +1047,7 @@ impl HDWallet { pub fn mnemonic_from_entropy(entropy: &[u8], lang: MnemonicLanguage) -> Result { let entropy_hex = alloc_c_string(&hex_from_bytes(&entropy))?; let language = alloc_c_string(&lang.to_str())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut mnemonic: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdConvertEntropyToMnemonic( @@ -1088,7 +1088,7 @@ impl HDWallet { let passphrase = alloc_c_string("")?; let language = alloc_c_string(&lang.to_str())?; let mnemonic_str = alloc_c_string(&tmp_mnemonic)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut seed: *mut c_char = ptr::null_mut(); let mut entropy: *mut c_char = ptr::null_mut(); let error_code = unsafe { @@ -1160,7 +1160,7 @@ impl HDWallet { let passphrase = alloc_c_string(passphrase)?; let language = alloc_c_string(&lang.to_str())?; let mnemonic_str = alloc_c_string(&tmp_mnemonic)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut seed: *mut c_char = ptr::null_mut(); let mut entropy: *mut c_char = ptr::null_mut(); let error_code = unsafe { diff --git a/src/key.rs b/src/key.rs index 77c917f..dab46fc 100644 --- a/src/key.rs +++ b/src/key.rs @@ -13,9 +13,9 @@ use std::result::Result::{Err, Ok}; use std::str::FromStr; use self::cfd_sys::{ - CfdAddCombinePubkey, CfdCalculateEcSignature, CfdCompressPubkey, CfdCreateKeyPair, - CfdDecodeSignatureFromDer, CfdEncodeSignatureByDer, CfdFinalizeCombinePubkey, - CfdFreeCombinePubkeyHandle, CfdGetPrivkeyWif, CfdGetPubkeyFromPrivkey, + CfdAddCombinePubkey, CfdAddSighashTypeInSchnorrSignature, CfdCalculateEcSignature, + CfdCompressPubkey, CfdCreateKeyPair, CfdDecodeSignatureFromDer, CfdEncodeSignatureByDer, + CfdFinalizeCombinePubkey, CfdFreeCombinePubkeyHandle, CfdGetPrivkeyWif, CfdGetPubkeyFromPrivkey, CfdInitializeCombinePubkey, CfdNegatePrivkey, CfdNegatePubkey, CfdNormalizeSignature, CfdParsePrivkeyWif, CfdPrivkeyTweakAdd, CfdPrivkeyTweakMul, CfdPubkeyTweakAdd, CfdPubkeyTweakMul, CfdUncompressPubkey, CfdVerifyEcSignature, @@ -100,7 +100,7 @@ impl Privkey { /// ``` pub fn from_wif(wif: &str) -> Result { let wif_str = alloc_c_string(wif)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut privkey_hex: *mut c_char = ptr::null_mut(); let mut is_compressed = true; let mut network_type: c_int = 0; @@ -142,7 +142,7 @@ impl Privkey { /// let key = Privkey::generate(&Network::Mainnet, true).expect("Fail"); /// ``` pub fn generate(network_type: &Network, is_compressed: bool) -> Result { - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut pubkey_hex: *mut c_char = ptr::null_mut(); let mut privkey_hex: *mut c_char = ptr::null_mut(); let mut wif: *mut c_char = ptr::null_mut(); @@ -191,7 +191,7 @@ impl Privkey { pub fn tweak_add(&self, data: &[u8]) -> Result { let privkey = alloc_c_string(&hex_from_bytes(&self.key))?; let tweak = alloc_c_string(&hex_from_bytes(data))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut tweak_privkey: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdPrivkeyTweakAdd( @@ -230,7 +230,7 @@ impl Privkey { pub fn tweak_mul(&self, data: &[u8]) -> Result { let privkey = alloc_c_string(&hex_from_bytes(&self.key))?; let tweak = alloc_c_string(&hex_from_bytes(data))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut tweak_privkey: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdPrivkeyTweakMul( @@ -264,7 +264,7 @@ impl Privkey { /// ``` pub fn negate(&self) -> Result { let privkey = alloc_c_string(&hex_from_bytes(&self.key))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut negate_privkey: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdNegatePrivkey(handle.as_handle(), privkey.as_ptr(), &mut negate_privkey) }; @@ -362,7 +362,7 @@ impl Privkey { return Ok(self.to_wif().to_string()); } let privkey = alloc_c_string(&self.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut wif: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdGetPrivkeyWif( @@ -410,7 +410,7 @@ impl Privkey { /// ``` pub fn generate_pubkey(key: &[u8], is_compressed: bool) -> Result { let privkey = alloc_c_string(&hex_from_bytes(key))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let wif: *const i8 = ptr::null(); let mut pubkey: *mut c_char = ptr::null_mut(); let error_code = unsafe { @@ -455,7 +455,7 @@ impl Privkey { ) -> Result { let signature_hash = alloc_c_string(&hex_from_bytes(sighash))?; let privkey = alloc_c_string(&hex_from_bytes(&self.key))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let wif: *const i8 = ptr::null(); let network_type: c_int = 0; let mut signature_hex: *mut c_char = ptr::null_mut(); @@ -641,7 +641,7 @@ impl Pubkey { /// ``` pub fn compress(&self) -> Result { let pubkey = alloc_c_string(&self.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut compressed_pubkey: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdCompressPubkey(handle.as_handle(), pubkey.as_ptr(), &mut compressed_pubkey) }; @@ -671,7 +671,7 @@ impl Pubkey { /// ``` pub fn uncompress(&self) -> Result { let pubkey = alloc_c_string(&self.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut decompressed_pubkey: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdUncompressPubkey( @@ -711,7 +711,7 @@ impl Pubkey { pub fn tweak_add(&self, data: &[u8]) -> Result { let pubkey = alloc_c_string(&hex_from_bytes(&self.key))?; let tweak = alloc_c_string(&hex_from_bytes(data))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut tweak_pubkey: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdPubkeyTweakAdd( @@ -750,7 +750,7 @@ impl Pubkey { pub fn tweak_mul(&self, data: &[u8]) -> Result { let pubkey = alloc_c_string(&hex_from_bytes(&self.key))?; let tweak = alloc_c_string(&hex_from_bytes(data))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut tweak_pubkey: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdPubkeyTweakMul( @@ -784,7 +784,7 @@ impl Pubkey { /// ``` pub fn negate(&self) -> Result { let pubkey = alloc_c_string(&hex_from_bytes(&self.key))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut negate_pubkey: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdNegatePubkey(handle.as_handle(), pubkey.as_ptr(), &mut negate_pubkey) }; @@ -824,7 +824,7 @@ impl Pubkey { return Ok(pubkey_list[0].clone()); } - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut combine_handle: *mut c_void = ptr::null_mut(); let error_code: i32 = unsafe { CfdInitializeCombinePubkey(handle.as_handle(), &mut combine_handle) }; @@ -887,7 +887,7 @@ impl Pubkey { let pubkey = alloc_c_string(&self.to_hex())?; let sighash_hex = alloc_c_string(&hex_from_bytes(sighash))?; let signature_hex = alloc_c_string(&hex_from_bytes(signature))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let error_code = unsafe { CfdVerifyEcSignature( handle.as_handle(), @@ -938,10 +938,7 @@ impl Pubkey { /// ``` #[inline] pub fn valid(&self) -> bool { - match self.compress() { - Ok(_result) => true, - _ => false, - } + matches!(self.compress(), Ok(_result)) } } @@ -1020,6 +1017,8 @@ impl Default for KeyPair { /// An enumeration definition of signature hash type. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum SigHashType { + /// SigHashType::Default + Default, /// SigHashType::All All, /// SigHashType::None @@ -1056,12 +1055,14 @@ impl SigHashType { SigHashType::Single | SigHashType::SinglePlusAnyoneCanPay => { SigHashType::SinglePlusAnyoneCanPay } + 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, } } } @@ -1081,12 +1082,13 @@ impl SigHashType { SigHashType::AllPlusAnyoneCanPay | SigHashType::NonePlusAnyoneCanPay | SigHashType::SinglePlusAnyoneCanPay => true, - SigHashType::All | SigHashType::None | SigHashType::Single => false, + SigHashType::All | SigHashType::None | SigHashType::Single | SigHashType::Default => false, } } pub(in crate) fn from_c_value(sighash_type: c_int) -> SigHashType { match sighash_type { + 0 => SigHashType::Default, 1 => SigHashType::All, 2 => SigHashType::None, 3 => SigHashType::Single, @@ -1096,6 +1098,7 @@ impl SigHashType { 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, @@ -1106,6 +1109,7 @@ impl SigHashType { impl fmt::Display for SigHashType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let _ = match *self { + 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"), @@ -1143,7 +1147,7 @@ impl SignParameter { pub fn from_slice(data: &[u8]) -> SignParameter { SignParameter { data: data.to_vec(), - sighash_type: SigHashType::All, + sighash_type: SigHashType::Default, pubkey: Pubkey::default(), use_der_encode: false, } @@ -1287,7 +1291,7 @@ impl SignParameter { /// ``` pub fn normalize(&self) -> Result { let signature_hex = alloc_c_string(&hex_from_bytes(&self.data))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut normalize_signature: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdNormalizeSignature( @@ -1318,15 +1322,19 @@ impl SignParameter { /// let der_encoded_sig = signature.to_der_encode().expect("Fail"); /// ``` pub fn to_der_encode(&self) -> Result { + let sighashtype = match self.sighash_type { + SigHashType::Default => SigHashType::All, + _ => self.sighash_type, + }; let signature_hex = alloc_c_string(&hex_from_bytes(&self.data))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut der_signature: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdEncodeSignatureByDer( handle.as_handle(), signature_hex.as_ptr(), - self.sighash_type.to_c_value(), - self.sighash_type.is_anyone_can_pay(), + sighashtype.to_c_value(), + sighashtype.is_anyone_can_pay(), &mut der_signature, ) }; @@ -1334,7 +1342,7 @@ impl SignParameter { 0 => { let der_encoded = unsafe { collect_cstring_and_free(der_signature) }?; let mut sig = SignParameter::from_vec(byte_from_hex_unsafe(&der_encoded)); - sig.sighash_type = self.sighash_type; + sig.sighash_type = sighashtype; Ok(sig) } _ => Err(handle.get_error(error_code)), @@ -1355,7 +1363,7 @@ impl SignParameter { /// ``` pub fn to_der_decode(&self) -> Result { let signature_hex = alloc_c_string(&hex_from_bytes(&self.data))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut signature: *mut c_char = ptr::null_mut(); let mut sighash_type_value: c_int = 0; let mut is_anyone_can_pay = false; @@ -1380,6 +1388,36 @@ impl SignParameter { handle.free_handle(); result } + + pub fn append_taproot_sighash_type(&self) -> Result { + if self.data.len() != 64 || self.sighash_type == SigHashType::Default { + Ok(self.clone()) + } else { + let signature_hex = alloc_c_string(&hex_from_bytes(&self.data))?; + let mut handle = ErrorHandle::new()?; + let mut added_signature: *mut c_char = ptr::null_mut(); + let error_code = unsafe { + CfdAddSighashTypeInSchnorrSignature( + handle.as_handle(), + signature_hex.as_ptr(), + self.sighash_type.to_c_value(), + self.sighash_type.is_anyone_can_pay(), + &mut added_signature, + ) + }; + let result = match error_code { + 0 => { + let sig = unsafe { collect_cstring_and_free(added_signature) }?; + let mut sig = SignParameter::from_vec(byte_from_hex_unsafe(&sig)); + sig.sighash_type = self.sighash_type; + Ok(sig) + } + _ => Err(handle.get_error(error_code)), + }; + handle.free_handle(); + result + } + } } impl fmt::Display for SignParameter { diff --git a/src/lib.rs b/src/lib.rs index 57af338..0006ffd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,6 +82,10 @@ pub use schnorr::SCHNORR_NONCE_SIZE; pub use schnorr::SCHNORR_SIGNATURE_SIZE; pub use script::Script; +pub use script::TapBranch; +pub use script::TAPROOT_HASH_SIZE; +pub use script::TAPSCRIPT_LEAF_VERSION; + pub use transaction::CoinSelectionData; pub use transaction::FeeData; pub use transaction::FeeOption; @@ -97,5 +101,7 @@ pub use transaction::TxOut; pub use transaction::TxOutData; pub use transaction::Txid; pub use transaction::UtxoData; -pub use transaction::SEQUENCE_LOCK_TIME_DISABLE; +pub use transaction::CODE_SEPARATOR_POSITION_FINAL; pub use transaction::SEQUENCE_LOCK_TIME_ENABLE_MAX; +pub use transaction::SEQUENCE_LOCK_TIME_FINAL; +pub use transaction::TXID_SIZE; diff --git a/src/schnorr.rs b/src/schnorr.rs index 04ebe2e..f1aff1f 100644 --- a/src/schnorr.rs +++ b/src/schnorr.rs @@ -6,7 +6,7 @@ use crate::common::{ alloc_c_string, byte_from_hex, collect_cstring_and_free, collect_multi_cstring_and_free, copy_array_32byte, hex_from_bytes, ByteData, CfdError, ErrorHandle, }; -use crate::key::{Privkey, Pubkey}; +use crate::key::{Privkey, Pubkey, SigHashType, SignParameter}; use std::fmt; use std::ptr; use std::result::Result::{Err, Ok}; @@ -235,7 +235,7 @@ impl EcdsaAdaptorUtil { let msg_hex = alloc_c_string(&msg.to_hex())?; let sk_hex = alloc_c_string(&secret_key.to_hex())?; let adaptor_hex = alloc_c_string(&adaptor.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut signature: *mut c_char = ptr::null_mut(); let mut proof: *mut c_char = ptr::null_mut(); let error_code = unsafe { @@ -287,7 +287,7 @@ impl EcdsaAdaptorUtil { ) -> Result { let sig_hex = alloc_c_string(&adaptor_signature.to_hex())?; let sk_hex = alloc_c_string(&adaptor_secret.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut signature: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdAdaptEcdsaAdaptor( @@ -335,7 +335,7 @@ impl EcdsaAdaptorUtil { let adaptor_sig_hex = alloc_c_string(&adaptor_signature.to_hex())?; let sig_hex = alloc_c_string(&signature.to_hex())?; let adaptor_hex = alloc_c_string(&adaptor.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut secret: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdExtractEcdsaAdaptorSecret( @@ -392,7 +392,7 @@ impl EcdsaAdaptorUtil { let adaptor_hex = alloc_c_string(&adaptor.to_hex())?; let msg_hex = alloc_c_string(&msg.to_hex())?; let pubkey_hex = alloc_c_string(&pubkey.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let error_code = unsafe { CfdVerifyEcdsaAdaptor( handle.as_handle(), @@ -463,7 +463,7 @@ impl SchnorrSignature { )); } let signature_hex = alloc_c_string(&hex_from_bytes(&data))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut nonce_hex: *mut c_char = ptr::null_mut(); let mut key_hex: *mut c_char = ptr::null_mut(); let error_code = unsafe { @@ -511,6 +511,23 @@ impl SchnorrSignature { pub fn as_key(&self) -> &Privkey { &self.key } + + /// Get sign parameter. + /// + /// # Arguments + /// * `sighash_type` - A signature hash type. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{SchnorrSignature, SigHashType}; + /// let bytes = [2; 64]; + /// let sig = SchnorrSignature::from_vec(bytes.to_vec()).expect("Fail"); + /// let sign_param = sig.get_sign_parameter(&SigHashType::All); + /// ``` + pub fn get_sign_parameter(&self, sighash_type: &SigHashType) -> SignParameter { + SignParameter::from_slice(&self.data).set_signature_hash(sighash_type) + } } impl fmt::Display for SchnorrSignature { @@ -596,7 +613,7 @@ impl SchnorrPubkey { /// ``` pub fn from_privkey(key: &Privkey) -> Result<(SchnorrPubkey, bool), CfdError> { let key_hex = alloc_c_string(&key.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut pubkey_hex: *mut c_char = ptr::null_mut(); let mut parity = false; let error_code = unsafe { @@ -635,7 +652,7 @@ impl SchnorrPubkey { /// ``` pub fn from_pubkey(key: &Pubkey) -> Result<(SchnorrPubkey, bool), CfdError> { let key_hex = alloc_c_string(&key.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut pubkey_hex: *mut c_char = ptr::null_mut(); let mut parity = false; let error_code = unsafe { @@ -680,7 +697,7 @@ impl SchnorrPubkey { ) -> Result<(SchnorrPubkey, bool, Privkey), CfdError> { let key_hex = alloc_c_string(&key.to_hex())?; let tweak_hex = alloc_c_string(&hex_from_bytes(data))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut pubkey_hex: *mut c_char = ptr::null_mut(); let mut privkey_hex: *mut c_char = ptr::null_mut(); let mut parity = false; @@ -744,7 +761,7 @@ impl SchnorrPubkey { pub fn tweak_add(&self, data: &[u8]) -> Result<(SchnorrPubkey, bool), CfdError> { let key_hex = alloc_c_string(&self.to_hex())?; let tweak_hex = alloc_c_string(&hex_from_bytes(data))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut pubkey_hex: *mut c_char = ptr::null_mut(); let mut parity = false; let error_code = unsafe { @@ -792,7 +809,7 @@ impl SchnorrPubkey { let key_hex = alloc_c_string(&self.to_hex())?; let base_key_hex = alloc_c_string(&base_pubkey.to_hex())?; let tweak_hex = alloc_c_string(&hex_from_bytes(data))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let error_code = unsafe { CfdCheckTweakAddFromSchnorrPubkey( handle.as_handle(), @@ -873,7 +890,7 @@ impl SchnorrUtil { let msg_hex = alloc_c_string(&msg.to_hex())?; let sk_hex = alloc_c_string(&secret_key.to_hex())?; let rand_hex = alloc_c_string(&aux_rand.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut signature: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdSignSchnorr( @@ -922,7 +939,7 @@ impl SchnorrUtil { let msg_hex = alloc_c_string(&msg.to_hex())?; let sk_hex = alloc_c_string(&secret_key.to_hex())?; let nonce_hex = alloc_c_string(&nonce.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut signature: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdSignSchnorrWithNonce( @@ -971,7 +988,7 @@ impl SchnorrUtil { let msg_hex = alloc_c_string(&msg.to_hex())?; let nonce_hex = alloc_c_string(&nonce.to_hex())?; let pubkey_hex = alloc_c_string(&pubkey.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut sig_point: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdComputeSchnorrSigPoint( @@ -1020,7 +1037,7 @@ impl SchnorrUtil { let sig_hex = alloc_c_string(&signature.to_hex())?; let msg_hex = alloc_c_string(&msg.to_hex())?; let pubkey_hex = alloc_c_string(&pubkey.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let error_code = unsafe { CfdVerifySchnorr( handle.as_handle(), diff --git a/src/script.rs b/src/script.rs index ae13784..2d16a69 100644 --- a/src/script.rs +++ b/src/script.rs @@ -1,25 +1,37 @@ extern crate cfd_sys; extern crate libc; -use self::libc::{c_char, c_int, c_uint, c_void}; +use self::libc::{c_char, c_int, c_uchar, c_uint, c_void}; use crate::common::{ alloc_c_string, byte_from_hex, byte_from_hex_unsafe, collect_cstring_and_free, - collect_multi_cstring_and_free, hex_from_bytes, ErrorHandle, + collect_multi_cstring_and_free, copy_array_32byte, hex_from_bytes, ErrorHandle, }; use crate::{ - common::{ByteData, CfdError}, - key::Pubkey, + address::Address, + common::{ByteData, CfdError, Network}, + key::{Privkey, Pubkey}, + schnorr::SchnorrPubkey, }; use std::fmt; use std::ptr; use std::result::Result::{Err, Ok}; +use std::str::FromStr; use self::cfd_sys::{ - CfdAddMultisigScriptData, CfdConvertScriptAsmToHex, CfdFinalizeMultisigScript, - CfdFreeMultisigScriptHandle, CfdFreeScriptItemHandle, CfdGetScriptItem, - CfdInitializeMultisigScript, CfdParseScript, + CfdAddMultisigScriptData, CfdAddTapBranchByScriptTreeString, CfdConvertScriptAsmToHex, + CfdFinalizeMultisigScript, CfdFreeMultisigScriptHandle, CfdFreeScriptItemHandle, + CfdFreeTaprootScriptTreeHandle, CfdGetBaseTapLeaf, CfdGetScriptItem, CfdGetTapBranchCount, + CfdGetTapBranchData, CfdGetTapBranchHandle, CfdGetTaprootScriptTreeHash, + CfdGetTaprootScriptTreeSrting, CfdGetTaprootTweakedPrivkey, CfdInitializeMultisigScript, + CfdInitializeTaprootScriptTree, CfdParseScript, CfdSetScriptTreeFromString, + CfdSetTapScriptByWitnessStack, }; +/// taproot hash size. +pub const TAPROOT_HASH_SIZE: usize = 32; +/// tapscript leaf version +pub const TAPSCRIPT_LEAF_VERSION: u8 = 0xc0; + /// A container that stores a bitcoin script. #[derive(Debug, PartialEq, Eq, Clone)] pub struct Script { @@ -43,7 +55,7 @@ impl Script { #[inline] pub fn from_slice(data: &[u8]) -> Result { let script_hex = alloc_c_string(&hex_from_bytes(data))?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut script_handle: *mut c_void = ptr::null_mut(); let mut script_item_num: c_uint = 0; let error_code = unsafe { @@ -140,7 +152,7 @@ impl Script { #[inline] pub fn from_asm(asm: &str) -> Result { let asm_str = alloc_c_string(asm)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut script_hex: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdConvertScriptAsmToHex(handle.as_handle(), asm_str.as_ptr(), &mut script_hex) }; @@ -270,7 +282,7 @@ impl Script { )); } - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let network_type: c_int = 0; // mainnet let hash_type: c_int = 1; // p2sh let mut multisig_handle: *mut c_void = ptr::null_mut(); @@ -347,3 +359,724 @@ impl fmt::Display for Script { write!(f, "Script[{}]", &self.asm) } } + +/// A container that stores a taproot script tree branch. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TapBranch { + /// top branch hash. + hash: [u8; TAPROOT_HASH_SIZE], + /// root tapscript. + tapscript: Script, + /// tapscript tree string (cfd format). + tree_str: String, + /// tapscript control node list. + target_nodes: Vec<[u8; TAPROOT_HASH_SIZE]>, +} + +impl TapBranch { + /// Create TapBranch from tapscript. (tapleaf) + /// + /// # Arguments + /// * `tapscript` - A tapscript. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{Script, TapBranch}; + /// use std::str::FromStr; + /// let script = Script::from_asm("OP_TRUE").expect("Fail"); + /// let tree = TapBranch::from_tapscript(&script).expect("Fail"); + /// ``` + pub fn from_tapscript(tapscript: &Script) -> Result { + let arr: Vec<[u8; TAPROOT_HASH_SIZE]> = vec![]; + TapBranch::from_string_by_tapscript(&format!("tl({})", tapscript.to_hex()), &tapscript, &arr) + } + + /// Create TapBranch from branch hash only. + /// This object is branch only. (not tapleaf) + /// + /// # Arguments + /// * `hash` - A tapbranch hash. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{ByteData, TapBranch}; + /// use std::str::FromStr; + /// let hash = ByteData::from_str("06b46c960d6824f0da5af71d9ecc55714de5b2d2da51be60bd12c77df20a20df").expect("Fail").to_32byte_array(); + /// let tree = TapBranch::from_branch_hash(&hash); + /// ``` + pub fn from_branch_hash(hash: &[u8; TAPROOT_HASH_SIZE]) -> TapBranch { + TapBranch { + hash: *hash, + tree_str: hex_from_bytes(hash), + ..TapBranch::default() + } + } + + /// Create TapBranch from tree string. + /// This object is branch only. (not tapleaf) + /// + /// # Arguments + /// * `tree_str` - A script tree string. (cfd format) + /// + /// # Example + /// + /// ``` + /// use cfd_rust::TapBranch; + /// let tree = TapBranch::from_string(&"{tl(51),{tl(204a7af8660f2b0bdb92d2ce8b88ab30feb916343228d2e7bd15da02e1f6a31d47ac),tl(2000d134c42fd51c90fa82c6cfdaabd895474d979118525362c0cd236c857e29d9ac)}}").expect("Fail"); + /// ``` + pub fn from_string(tree_str: &str) -> Result { + let arr: Vec<[u8; TAPROOT_HASH_SIZE]> = vec![]; + TapBranch::from_string_by_tapscript(tree_str, &Script::default(), &arr) + } + + /// Create TapBranch from control block and tapscript. (tapleaf) + /// + /// # Arguments + /// * `control_block` - A control block. + /// * `tapscript` - A tapscript. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{ByteData, TapBranch, Script}; + /// use std::str::FromStr; + /// let control_block = ByteData::from_str("c01777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb06b46c960d6824f0da5af71d9ecc55714de5b2d2da51be60bd12c77df20a20df").expect("Fail"); + /// let script = Script::from_asm("OP_TRUE").expect("Fail"); + /// let tree = TapBranch::from_control_block(&control_block.to_slice(), &script); + /// ``` + pub fn from_control_block( + control_block: &[u8], + tapscript: &Script, + ) -> Result<(TapBranch, SchnorrPubkey), CfdError> { + let handle = ScriptTreeHandle::new()?; + let script_str = alloc_c_string(&tapscript.to_hex())?; + let control_block_str = alloc_c_string(&hex_from_bytes(control_block))?; + let mut output: *mut c_char = ptr::null_mut(); + let error_code = unsafe { + CfdSetTapScriptByWitnessStack( + handle.as_handle(), + handle.as_tree_handle(), + control_block_str.as_ptr(), + script_str.as_ptr(), + &mut output, + ) + }; + match error_code { + 0 => { + let pubkey_str = unsafe { collect_cstring_and_free(output) }?; + let schnorr_pubkey = SchnorrPubkey::from_str(&pubkey_str)?; + let nodes: Vec<[u8; 32]> = vec![]; + let branch = Self::get_branch_data(&handle, &tapscript, &nodes)?; + Ok((branch, schnorr_pubkey)) + } + _ => Err(handle.get_error(error_code)), + } + } + + /// Create TapBranch from tapscript and tree string. (tapleaf) + /// + /// # Arguments + /// * `tree_str` - A script tree string. (cfd format) + /// * `tapscript` - A tapscript. + /// * `target_nodes` - A tapscript target node list. (branch hash list) + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{TapBranch, Script, ByteData}; + /// use std::str::FromStr; + /// let tree_str = "{tl(51),{tl(204a7af8660f2b0bdb92d2ce8b88ab30feb916343228d2e7bd15da02e1f6a31d47ac),tl(2000d134c42fd51c90fa82c6cfdaabd895474d979118525362c0cd236c857e29d9ac)}}"; + /// let hash_bytes = ByteData::from_str("aaf9ea4cbd2f4606a31a35d563fa371bc630d9d7bcc50f62d064a3d84e0e3086").expect("Fail"); + /// let script = Script::from_asm("OP_TRUE").expect("Fail"); + /// let tree = TapBranch::from_string_by_tapscript( + /// tree_str, &script, &[hash_bytes.to_32byte_array()]).expect("Fail"); + /// ``` + pub fn from_string_by_tapscript( + tree_str: &str, + tapscript: &Script, + target_nodes: &[[u8; TAPROOT_HASH_SIZE]], + ) -> Result { + let handle = ScriptTreeHandle::new()?; + Self::load_by_tree_string(&handle, tree_str, tapscript, target_nodes)?; + let nodes: Vec<[u8; 32]> = vec![]; + Self::get_branch_data(&handle, &tapscript, &nodes) + } + + fn load_by_tree_string( + handle: &ScriptTreeHandle, + tree_str: &str, + tapscript: &Script, + target_nodes: &[[u8; TAPROOT_HASH_SIZE]], + ) -> Result<(), CfdError> { + let mut target_nodes_str = String::default(); + for node in target_nodes { + target_nodes_str += &hex_from_bytes(node); + } + let leaf_version: u8 = TAPSCRIPT_LEAF_VERSION; + let script_str = alloc_c_string(&tapscript.to_hex())?; + let tree_string = alloc_c_string(&tree_str)?; + let control_nodes = alloc_c_string(&target_nodes_str)?; + let error_code = unsafe { + CfdSetScriptTreeFromString( + handle.as_handle(), + handle.as_tree_handle(), + tree_string.as_ptr(), + script_str.as_ptr(), + leaf_version, + control_nodes.as_ptr(), + ) + }; + match error_code { + 0 => Ok(()), + _ => Err(handle.get_error(error_code)), + } + } + + /// Add tapleaf to tapbranch. + /// + /// # Arguments + /// * `tapscript` - A tapscript. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{TapBranch, Script}; + /// let tree_str = "{tl(204a7af8660f2b0bdb92d2ce8b88ab30feb916343228d2e7bd15da02e1f6a31d47ac),tl(2000d134c42fd51c90fa82c6cfdaabd895474d979118525362c0cd236c857e29d9ac)}"; + /// let mut tree = TapBranch::from_string(tree_str).expect("Fail"); + /// let script = Script::from_asm("OP_TRUE").expect("Fail"); + /// tree.add_by_tapleaf(&script).expect("Fail"); + /// ``` + pub fn add_by_tapleaf(&mut self, tapscript: &Script) -> Result<(), CfdError> { + self.add_by_tree_string(&format!("tl({})", tapscript.to_hex())) + } + + /// Add tapbranch to tapbranch. + /// + /// # Arguments + /// * `branch` - A tapbranch. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::TapBranch; + /// let tree_str = "tl(51)"; + /// let mut tree = TapBranch::from_string(tree_str).expect("Fail"); + /// let brach_str = "{tl(204a7af8660f2b0bdb92d2ce8b88ab30feb916343228d2e7bd15da02e1f6a31d47ac),tl(2000d134c42fd51c90fa82c6cfdaabd895474d979118525362c0cd236c857e29d9ac)}"; + /// let branch = TapBranch::from_string(brach_str).expect("Fail"); + /// tree.add_by_tapbranch(&branch).expect("Fail"); + /// ``` + pub fn add_by_tapbranch(&mut self, branch: &TapBranch) -> Result<(), CfdError> { + self.add_by_tree_string(branch.to_str()) + } + + /// Add tapbranch hash to tapbranch. + /// + /// # Arguments + /// * `hash` - A tapbranch hash. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{TapBranch, ByteData}; + /// use std::str::FromStr; + /// let tree_str = "tl(51)"; + /// let mut tree = TapBranch::from_string(tree_str).expect("Fail"); + /// let hash_bytes = ByteData::from_str("aaf9ea4cbd2f4606a31a35d563fa371bc630d9d7bcc50f62d064a3d84e0e3086").expect("Fail"); + /// tree.add_by_tapbranch_hash(&hash_bytes.to_32byte_array()).expect("Fail"); + /// ``` + pub fn add_by_tapbranch_hash(&mut self, hash: &[u8; TAPROOT_HASH_SIZE]) -> Result<(), CfdError> { + self.add_by_tree_string(&hex_from_bytes(hash)) + } + + /// Add tree string to tapbranch. + /// + /// # Arguments + /// * `tree_str` - A tree string. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::TapBranch; + /// let tree_str = "tl(51)"; + /// let mut tree = TapBranch::from_string(tree_str).expect("Fail"); + /// let brach_str = "{tl(204a7af8660f2b0bdb92d2ce8b88ab30feb916343228d2e7bd15da02e1f6a31d47ac),tl(2000d134c42fd51c90fa82c6cfdaabd895474d979118525362c0cd236c857e29d9ac)}"; + /// tree.add_by_tree_string(&brach_str).expect("Fail"); + /// ``` + pub fn add_by_tree_string(&mut self, tree_str: &str) -> Result<(), CfdError> { + let handle = ScriptTreeHandle::new()?; + Self::load_by_tree_string(&handle, &self.tree_str, &self.tapscript, &self.target_nodes)?; + let tree_string = alloc_c_string(&tree_str)?; + let error_code = unsafe { + CfdAddTapBranchByScriptTreeString( + handle.as_handle(), + handle.as_tree_handle(), + tree_string.as_ptr(), + ) + }; + match error_code { + 0 => { + let nodes = self.target_nodes.clone(); + let branch = Self::get_branch_data(&handle, &self.tapscript, &nodes)?; + self.hash = branch.hash; + self.tree_str = branch.tree_str; + if !self.tapscript.is_empty() { + self.target_nodes = branch.target_nodes; + } + Ok(()) + } + _ => Err(handle.get_error(error_code)), + } + } + + /// Get branch data. + /// + /// # Arguments + /// * `index` - A branch index (from leaf). start is zero. maximum is `get_branch_count() - 1`. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{TapBranch, Script, ByteData}; + /// use std::str::FromStr; + /// let tree_str = "{tl(51),{tl(204a7af8660f2b0bdb92d2ce8b88ab30feb916343228d2e7bd15da02e1f6a31d47ac),tl(2000d134c42fd51c90fa82c6cfdaabd895474d979118525362c0cd236c857e29d9ac)}}"; + /// let hash_bytes = ByteData::from_str("aaf9ea4cbd2f4606a31a35d563fa371bc630d9d7bcc50f62d064a3d84e0e3086").expect("Fail"); + /// let script = Script::from_asm("OP_TRUE").expect("Fail"); + /// let tree = TapBranch::from_string_by_tapscript( + /// tree_str, &script, &[hash_bytes.to_32byte_array()]).expect("Fail"); + /// let max_count = tree.get_branch_count().expect("Fail"); + /// let branch = tree.get_branch(max_count - 1).expect("Fail"); + /// ``` + pub fn get_branch(&self, index: u8) -> Result { + let handle = ScriptTreeHandle::new()?; + Self::load_by_tree_string(&handle, &self.tree_str, &self.tapscript, &self.target_nodes)?; + let branch_handle = ScriptTreeHandle::create_sub_branch(&handle, index)?; + let nodes: Vec<[u8; 32]> = vec![]; + Self::get_branch_data(&branch_handle, &Script::default(), &nodes) + } + + /// Get the count of contains branch. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{TapBranch, Script, ByteData}; + /// use std::str::FromStr; + /// let tree_str = "{tl(51),{tl(204a7af8660f2b0bdb92d2ce8b88ab30feb916343228d2e7bd15da02e1f6a31d47ac),tl(2000d134c42fd51c90fa82c6cfdaabd895474d979118525362c0cd236c857e29d9ac)}}"; + /// let hash_bytes = ByteData::from_str("aaf9ea4cbd2f4606a31a35d563fa371bc630d9d7bcc50f62d064a3d84e0e3086").expect("Fail"); + /// let script = Script::from_asm("OP_TRUE").expect("Fail"); + /// let tree = TapBranch::from_string_by_tapscript( + /// tree_str, &script, &[hash_bytes.to_32byte_array()]).expect("Fail"); + /// let max_count = tree.get_branch_count().expect("Fail"); + /// ``` + pub fn get_branch_count(&self) -> Result { + if !self.tapscript.is_empty() { + return Ok(self.target_nodes.len() as u8); + } + let handle = ScriptTreeHandle::new()?; + Self::load_by_tree_string(&handle, &self.tree_str, &self.tapscript, &self.target_nodes)?; + Self::get_branch_count_internal(&handle) + } + + /// Get a tapleaf hash. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{TapBranch, Script, ByteData}; + /// use std::str::FromStr; + /// let tree_str = "{tl(51),{tl(204a7af8660f2b0bdb92d2ce8b88ab30feb916343228d2e7bd15da02e1f6a31d47ac),tl(2000d134c42fd51c90fa82c6cfdaabd895474d979118525362c0cd236c857e29d9ac)}}"; + /// let hash_bytes = ByteData::from_str("aaf9ea4cbd2f4606a31a35d563fa371bc630d9d7bcc50f62d064a3d84e0e3086").expect("Fail"); + /// let script = Script::from_asm("OP_TRUE").expect("Fail"); + /// let tree = TapBranch::from_string_by_tapscript( + /// tree_str, &script, &[hash_bytes.to_32byte_array()]).expect("Fail"); + /// let tapleaf_hash = tree.get_tapleaf_hash().expect("Fail"); + /// ``` + pub fn get_tapleaf_hash(&self) -> Result<[u8; 32], CfdError> { + if self.tapscript.is_empty() { + return Err(CfdError::IllegalArgument( + "This branch has not tapscript.".to_string(), + )); + } + let handle = ScriptTreeHandle::new()?; + Self::load_by_tree_string(&handle, &self.tree_str, &self.tapscript, &self.target_nodes)?; + + let mut tapscript: *mut c_char = ptr::null_mut(); + let mut tap_leaf_hash: *mut c_char = ptr::null_mut(); + let mut leaf_version: c_uchar = 0; + let error_code = unsafe { + CfdGetBaseTapLeaf( + handle.as_handle(), + handle.as_tree_handle(), + &mut leaf_version, + &mut tapscript, + &mut tap_leaf_hash, + ) + }; + match error_code { + 0 => { + let str_list = unsafe { collect_multi_cstring_and_free(&[tapscript, tap_leaf_hash]) }?; + let hash = copy_array_32byte(&byte_from_hex_unsafe(&str_list[1])); + Ok(hash) + } + _ => Err(handle.get_error(error_code)), + } + } + + /// Get a tweaked pubkey for taproot. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{TapBranch, Script, ByteData, SchnorrPubkey, Network}; + /// use std::str::FromStr; + /// let schnorr_pubkey = SchnorrPubkey::from_str("1777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb").expect("Fail"); + /// let tree_str = "{tl(51),{tl(204a7af8660f2b0bdb92d2ce8b88ab30feb916343228d2e7bd15da02e1f6a31d47ac),tl(2000d134c42fd51c90fa82c6cfdaabd895474d979118525362c0cd236c857e29d9ac)}}"; + /// let hash_bytes = ByteData::from_str("aaf9ea4cbd2f4606a31a35d563fa371bc630d9d7bcc50f62d064a3d84e0e3086").expect("Fail"); + /// let script = Script::from_asm("OP_TRUE").expect("Fail"); + /// let tree = TapBranch::from_string_by_tapscript( + /// tree_str, &script, &[hash_bytes.to_32byte_array()]).expect("Fail"); + /// let (witness_program_hash, address, control_block) = tree.get_tweaked_pubkey( + /// &schnorr_pubkey, &Network::Mainnet).expect("Fail"); + /// ``` + pub fn get_tweaked_pubkey( + &self, + schnorr_pubkey: &SchnorrPubkey, + network: &Network, + ) -> Result<(SchnorrPubkey, Address, ByteData), CfdError> { + let handle = ScriptTreeHandle::new()?; + Self::load_by_tree_string(&handle, &self.tree_str, &self.tapscript, &self.target_nodes)?; + let internal_pubkey = alloc_c_string(&schnorr_pubkey.to_hex())?; + let mut hash: *mut c_char = ptr::null_mut(); + let mut tap_leaf_hash: *mut c_char = ptr::null_mut(); + let mut control_block: *mut c_char = ptr::null_mut(); + let error_code = unsafe { + CfdGetTaprootScriptTreeHash( + handle.as_handle(), + handle.as_tree_handle(), + internal_pubkey.as_ptr(), + &mut hash, + &mut tap_leaf_hash, + &mut control_block, + ) + }; + match error_code { + 0 => { + let str_list = + unsafe { collect_multi_cstring_and_free(&[hash, tap_leaf_hash, control_block]) }?; + let pubkey = SchnorrPubkey::from_str(&str_list[0])?; + let control_block_data = ByteData::from_str(&str_list[2])?; + let addr = Address::taproot(&pubkey, network)?; + Ok((pubkey, addr, control_block_data)) + } + _ => Err(handle.get_error(error_code)), + } + } + + /// Get a tweaked pubkey for taproot. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{TapBranch, Script, ByteData, Privkey, Network}; + /// use std::str::FromStr; + /// let privkey = Privkey::from_str("305e293b010d29bf3c888b617763a438fee9054c8cab66eb12ad078f819d9f27").expect("Fail"); + /// let tree_str = "{tl(51),{tl(204a7af8660f2b0bdb92d2ce8b88ab30feb916343228d2e7bd15da02e1f6a31d47ac),tl(2000d134c42fd51c90fa82c6cfdaabd895474d979118525362c0cd236c857e29d9ac)}}"; + /// let hash_bytes = ByteData::from_str("aaf9ea4cbd2f4606a31a35d563fa371bc630d9d7bcc50f62d064a3d84e0e3086").expect("Fail"); + /// let script = Script::from_asm("OP_TRUE").expect("Fail"); + /// let tree = TapBranch::from_string_by_tapscript( + /// tree_str, &script, &[hash_bytes.to_32byte_array()]).expect("Fail"); + /// let tweaked_privkey = tree.get_tweaked_privkey(&privkey).expect("Fail"); + /// ``` + pub fn get_tweaked_privkey(&self, privkey: &Privkey) -> Result { + let handle = ScriptTreeHandle::new()?; + Self::load_by_tree_string(&handle, &self.tree_str, &self.tapscript, &self.target_nodes)?; + let internal_privkey = alloc_c_string(&privkey.to_hex())?; + let mut tweaked_privkey: *mut c_char = ptr::null_mut(); + let error_code = unsafe { + CfdGetTaprootTweakedPrivkey( + handle.as_handle(), + handle.as_tree_handle(), + internal_privkey.as_ptr(), + &mut tweaked_privkey, + ) + }; + match error_code { + 0 => { + let privkey_str = unsafe { collect_cstring_and_free(tweaked_privkey) }?; + Privkey::from_str(&privkey_str) + } + _ => Err(handle.get_error(error_code)), + } + } + + pub fn get_top_branch_hash(&self) -> &[u8; TAPROOT_HASH_SIZE] { + &self.hash + } + + pub fn get_top_branch_hash_data(&self) -> ByteData { + ByteData::from_slice(&self.hash) + } + + pub fn get_tapscript(&self) -> &Script { + &self.tapscript + } + + pub fn to_str(&self) -> &str { + &self.tree_str + } + + pub fn get_target_nodes(&self) -> &Vec<[u8; TAPROOT_HASH_SIZE]> { + &self.target_nodes + } + + fn get_branch_count_internal(handle: &ScriptTreeHandle) -> Result { + let mut count: c_uint = 0; + let error_code = + unsafe { CfdGetTapBranchCount(handle.as_handle(), handle.as_tree_handle(), &mut count) }; + match error_code { + 0 => Ok(count as u8), + _ => Err(handle.get_error(error_code)), + } + } + + fn get_branch_data( + handle: &ScriptTreeHandle, + root_tapscript: &Script, + nodes: &[[u8; 32]], + ) -> Result { + let mut branch = TapBranch::default(); + let count = Self::get_branch_count_internal(&handle)?; + if count == 0 { + let mut tapscript: *mut c_char = ptr::null_mut(); + let mut tap_leaf_hash: *mut c_char = ptr::null_mut(); + let mut leaf_version: c_uchar = 0; + let error_code = unsafe { + CfdGetBaseTapLeaf( + handle.as_handle(), + handle.as_tree_handle(), + &mut leaf_version, + &mut tapscript, + &mut tap_leaf_hash, + ) + }; + match error_code { + 0 => { + let str_list = unsafe { collect_multi_cstring_and_free(&[tapscript, tap_leaf_hash]) }?; + if !str_list[1].is_empty() { + branch.hash = copy_array_32byte(&byte_from_hex_unsafe(&str_list[1])); + } + Ok(()) + } + _ => Err(handle.get_error(error_code)), + }?; + } else { + let mut branch_hash: *mut c_char = ptr::null_mut(); + let mut tapscript: *mut c_char = ptr::null_mut(); + let mut leaf_version: c_uchar = 0; + let mut depth_by_leaf_or_end: c_uchar = 0; + let index = count - 1; + let error_code = unsafe { + CfdGetTapBranchData( + handle.as_handle(), + handle.as_tree_handle(), + index, + true, + &mut branch_hash, + &mut leaf_version, + &mut tapscript, + &mut depth_by_leaf_or_end, + ) + }; + match error_code { + 0 => { + let str_list = unsafe { collect_multi_cstring_and_free(&[branch_hash, tapscript]) }?; + if !str_list[0].is_empty() { + branch.hash = copy_array_32byte(&byte_from_hex_unsafe(&str_list[0])); + } + Ok(()) + } + _ => Err(handle.get_error(error_code)), + }?; + } + if count != 0 && !root_tapscript.is_empty() { + let mut index: u8 = nodes.len() as u8; + for node in nodes { + branch.target_nodes.push(*node); + } + while index < count { + let mut branch_hash: *mut c_char = ptr::null_mut(); + let mut tapscript: *mut c_char = ptr::null_mut(); + let mut leaf_version: c_uchar = 0; + let mut depth_by_leaf_or_end: c_uchar = 0; + let error_code = unsafe { + CfdGetTapBranchData( + handle.as_handle(), + handle.as_tree_handle(), + index, + false, + &mut branch_hash, + &mut leaf_version, + &mut tapscript, + &mut depth_by_leaf_or_end, + ) + }; + match error_code { + 0 => { + let str_list = unsafe { collect_multi_cstring_and_free(&[branch_hash, tapscript]) }?; + if !str_list[0].is_empty() { + branch + .target_nodes + .push(copy_array_32byte(&byte_from_hex_unsafe(&str_list[0]))); + } + Ok(()) + } + _ => Err(handle.get_error(error_code)), + }?; + index += 1; + } + } + + let mut tree_str: *mut c_char = ptr::null_mut(); + let error_code = unsafe { + CfdGetTaprootScriptTreeSrting(handle.as_handle(), handle.as_tree_handle(), &mut tree_str) + }; + match error_code { + 0 => { + branch.tree_str = unsafe { collect_cstring_and_free(tree_str) }?; + branch.tapscript = root_tapscript.clone(); + Ok(branch) + } + _ => Err(handle.get_error(error_code)), + } + } +} + +impl Default for TapBranch { + fn default() -> TapBranch { + TapBranch { + hash: [0; TAPROOT_HASH_SIZE], + tapscript: Script::default(), + tree_str: String::default(), + target_nodes: vec![], + } + } +} + +impl fmt::Display for TapBranch { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TapBranch[{}]", &self.tree_str) + } +} + +impl FromStr for TapBranch { + type Err = CfdError; + fn from_str(string: &str) -> Result { + TapBranch::from_string(string) + } +} + +/// A container that tree handler. +#[derive(Debug)] +pub(in crate) struct ScriptTreeHandle { + handle: ErrorHandle, + tree_handle: *mut c_void, +} + +impl ScriptTreeHandle { + pub fn new() -> Result { + let mut tree_hdl = ScriptTreeHandle { + handle: ErrorHandle::new()?, + tree_handle: ptr::null_mut(), + }; + let mut tree_handle: *mut c_void = ptr::null_mut(); + let error_code = + unsafe { CfdInitializeTaprootScriptTree(tree_hdl.handle.as_handle(), &mut tree_handle) }; + match error_code { + 0 => { + tree_hdl.tree_handle = tree_handle; + Ok(tree_hdl) + } + _ => { + let err = tree_hdl.handle.get_error(error_code); + tree_hdl.handle.free_handle(); + Err(err) + } + } + } + + pub fn create_sub_branch( + handle: &ScriptTreeHandle, + index: u8, + ) -> Result { + let mut tree_hdl = ScriptTreeHandle { + handle: ErrorHandle::new()?, + tree_handle: ptr::null_mut(), + }; + let mut tree_handle: *mut c_void = ptr::null_mut(); + let mut branch_hash: *mut c_char = ptr::null_mut(); + let error_code = unsafe { + CfdGetTapBranchHandle( + tree_hdl.handle.as_handle(), + handle.as_tree_handle(), + index, + &mut branch_hash, + &mut tree_handle, + ) + }; + match error_code { + 0 => { + if !branch_hash.is_null() { + unsafe { + libc::free(branch_hash as *mut libc::c_void); + }; + } + tree_hdl.tree_handle = tree_handle; + Ok(tree_hdl) + } + _ => { + let err = tree_hdl.handle.get_error(error_code); + tree_hdl.handle.free_handle(); + Err(err) + } + } + } + + #[inline] + pub fn as_tree_handle(&self) -> *const c_void { + self.tree_handle + } + + #[inline] + pub fn as_handle(&self) -> *const c_void { + self.handle.as_handle() + } + + pub fn get_error(&self, error_code: c_int) -> CfdError { + self.handle.get_error(error_code) + } + + pub fn free_handle(&mut self) { + if !self.tree_handle.is_null() { + unsafe { + CfdFreeTaprootScriptTreeHandle(self.handle.as_handle(), self.tree_handle); + } + self.tree_handle = ptr::null_mut(); + self.handle.free_handle(); + } + } + + #[inline] + pub fn is_null(&self) -> bool { + self.tree_handle.is_null() + } +} + +impl Drop for ScriptTreeHandle { + fn drop(&mut self) { + if !self.is_null() { + self.free_handle(); + } + } +} diff --git a/src/transaction.rs b/src/transaction.rs index e5ae5ab..be8e6a6 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -3,7 +3,6 @@ extern crate libc; // use self::cfd_sys as ffi; use self::libc::{c_char, c_int, c_uint, c_void}; -use crate::address::{Address, AddressType, HashType}; use crate::common::{ alloc_c_string, byte_from_hex_unsafe, collect_cstring_and_free, collect_multi_cstring_and_free, hex_from_bytes, Amount, ByteData, CfdError, ErrorHandle, Network, ReverseContainer, @@ -12,7 +11,11 @@ use crate::confidential_address::ConfidentialAddress; use crate::confidential_transaction::ConfidentialAsset; use crate::descriptor::Descriptor; use crate::key::{KeyPair, Privkey, Pubkey, SigHashType, SignParameter}; -use crate::script::Script; +use crate::script::{Script, TAPROOT_HASH_SIZE}; +use crate::{ + address::{Address, AddressType, HashType}, + SchnorrPubkey, +}; use std::fmt; use std::ptr; use std::result::Result::{Err, Ok}; @@ -21,19 +24,21 @@ use std::str::FromStr; use self::cfd_sys::{ CfdAddCoinSelectionAmount, CfdAddCoinSelectionUtxoTemplate, CfdAddMultisigSignData, CfdAddMultisigSignDataToDer, CfdAddPubkeyHashSign, CfdAddScriptHashSign, - CfdAddSignWithPrivkeySimple, CfdAddTargetAmountForFundRawTx, CfdAddTransactionInput, - CfdAddTransactionOutput, CfdAddTxInTemplateForEstimateFee, CfdAddTxInTemplateForFundRawTx, - CfdAddTxSign, CfdAddUtxoTemplateForFundRawTx, CfdCreateSighash, CfdFinalizeCoinSelection, - CfdFinalizeEstimateFee, CfdFinalizeFundRawTx, CfdFinalizeMultisigSign, CfdFinalizeTransaction, - CfdFreeCoinSelectionHandle, CfdFreeEstimateFeeHandle, CfdFreeFundRawTxHandle, - CfdFreeMultisigSignHandle, CfdFreeTransactionHandle, CfdFreeTxDataHandle, + CfdAddSignWithPrivkeyByHandle, CfdAddSignWithPrivkeySimple, CfdAddTaprootSignByHandle, + CfdAddTargetAmountForFundRawTx, CfdAddTransactionInput, CfdAddTransactionOutput, + CfdAddTxInTemplateForEstimateFee, CfdAddTxInTemplateForFundRawTx, CfdAddTxSign, + CfdAddTxSignByHandle, CfdAddUtxoTemplateForFundRawTx, CfdCreateSighash, CfdCreateSighashByHandle, + CfdFinalizeCoinSelection, CfdFinalizeEstimateFee, CfdFinalizeFundRawTx, CfdFinalizeMultisigSign, + CfdFinalizeTransaction, CfdFreeCoinSelectionHandle, CfdFreeEstimateFeeHandle, + CfdFreeFundRawTxHandle, CfdFreeMultisigSignHandle, CfdFreeTxDataHandle, CfdGetAppendTxOutFundRawTx, CfdGetSelectedCoinIndex, CfdGetTxInByHandle, CfdGetTxInCountByHandle, CfdGetTxInIndex, CfdGetTxInIndexByHandle, CfdGetTxInWitnessByHandle, CfdGetTxInWitnessCountByHandle, CfdGetTxInfoByHandle, CfdGetTxOutByHandle, CfdGetTxOutCountByHandle, CfdGetTxOutIndexByHandle, CfdInitializeCoinSelection, CfdInitializeEstimateFee, CfdInitializeFundRawTx, CfdInitializeMultisigSign, - CfdInitializeTransaction, CfdInitializeTxDataHandle, CfdSetOptionFundRawTx, CfdUpdateTxOutAmount, - CfdVerifySignature, CfdVerifyTxSign, DEFAULT_BLIND_MINIMUM_BITS, FUND_OPT_DUST_FEE_RATE, + 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, }; @@ -42,12 +47,17 @@ use self::cfd_sys::{ // const OPT_LONG_TERM_FEE_RATE: i32 = 3; // const OPT_KNAPSACK_MIN_CHANGE: i32 = 4; -/// disable locktime +/// locktime final +pub const SEQUENCE_LOCK_TIME_FINAL: u32 = 0xffffffff; +/// disable locktime (deprecated) +#[deprecated(since = "0.3.0", note = "Please use the SEQUENCE_LOCK_TIME_FINAL")] pub const SEQUENCE_LOCK_TIME_DISABLE: u32 = 0xffffffff; /// enable locktime (maximum time) pub const SEQUENCE_LOCK_TIME_ENABLE_MAX: u32 = 0xfffffffe; /// array size of txid. pub const TXID_SIZE: usize = 32; +/// OP_CODESEPARATOR position final +pub const CODE_SEPARATOR_POSITION_FINAL: u32 = 0xffffffff; /// A container that stores a txid. #[derive(Debug, PartialEq, Eq, Clone, Hash)] @@ -301,6 +311,72 @@ impl UtxoData { } } + /// Create from address. + /// + /// # Arguments + /// * `outpoint` - A txid string. + /// * `amount` - A satoshi amount. + /// * `address` - An address. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{Network, OutPoint, UtxoData, Address, Pubkey}; + /// use std::str::FromStr; + /// let outpoint = OutPoint::from_str( + /// "0202020202020202020202020202020202020202020202020202020202020202", + /// 1).expect("Fail"); + /// let amount: i64 = 50000; + /// let pubkey = Pubkey::from_str(&"02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5").expect("Fail"); + /// let address = Address::p2pkh(&pubkey, &Network::Testnet).expect("Fail"); + /// let utxo = UtxoData::from_address(&outpoint, amount, &address); + /// ``` + pub fn from_address( + outpoint: &OutPoint, + amount: i64, + address: &Address, + ) -> Result { + Ok(UtxoData { + outpoint: outpoint.clone(), + amount, + descriptor: Descriptor::address(address)?, + scriptsig_template: Script::default(), + }) + } + + /// Create from locking script. + /// + /// # Arguments + /// * `outpoint` - A txid string. + /// * `amount` - A satoshi amount. + /// * `locking_script` - A locking script. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{Network, OutPoint, UtxoData, Script}; + /// use std::str::FromStr; + /// let outpoint = OutPoint::from_str( + /// "0202020202020202020202020202020202020202020202020202020202020202", + /// 1).expect("Fail"); + /// let amount: i64 = 50000; + /// let script = Script::from_hex("5120c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5").expect("Fail"); + /// let utxo = UtxoData::from_locking_script(&outpoint, amount, &script, &Network::Testnet); + /// ``` + pub fn from_locking_script( + outpoint: &OutPoint, + amount: i64, + locking_script: &Script, + network: &Network, + ) -> Result { + Ok(UtxoData { + outpoint: outpoint.clone(), + amount, + descriptor: Descriptor::raw_script(locking_script, network)?, + scriptsig_template: Script::default(), + }) + } + /// Create object. /// /// # Arguments @@ -563,9 +639,15 @@ pub struct TxData { impl Default for TxData { fn default() -> TxData { + // default txid: version=2, locktime=0 + let txid_value = + match Txid::from_str("4ebd325a4b394cff8c57e8317ccf5a8d0e2bdf1b8526f8aad6c8e43d8240621a") { + Ok(_txid) => _txid, + _ => Txid::default(), + }; TxData { - txid: Txid::default(), - wtxid: Txid::default(), + txid: txid_value.clone(), + wtxid: txid_value, size: 10, vsize: 10, weight: 40, @@ -587,7 +669,7 @@ impl TxInData { pub fn new(outpoint: &OutPoint) -> TxInData { TxInData { outpoint: outpoint.clone(), - sequence: SEQUENCE_LOCK_TIME_DISABLE, + sequence: SEQUENCE_LOCK_TIME_FINAL, script_sig: Script::default(), } } @@ -595,7 +677,7 @@ impl TxInData { pub fn from_utxo(utxo: &UtxoData) -> TxInData { TxInData { outpoint: utxo.outpoint.clone(), - sequence: SEQUENCE_LOCK_TIME_DISABLE, + sequence: SEQUENCE_LOCK_TIME_FINAL, script_sig: Script::default(), } } @@ -650,7 +732,7 @@ impl Default for TxIn { fn default() -> TxIn { TxIn { outpoint: OutPoint::default(), - sequence: SEQUENCE_LOCK_TIME_DISABLE, + sequence: SEQUENCE_LOCK_TIME_FINAL, script_sig: Script::default(), script_witness: ScriptWitness::default(), } @@ -699,6 +781,7 @@ pub struct Transaction { data: TxData, txin_list: Vec, txout_list: Vec, + txin_utxo_list: Vec, } impl Transaction { @@ -798,6 +881,7 @@ impl Transaction { data, txin_list: TxIn::from_data_list(txin_list), txout_list: TxOut::from_data_list(txout_list), + ..Transaction::default() }) } @@ -836,6 +920,51 @@ impl Transaction { data, txin_list: ope2.get_txin_list_cache().to_vec(), txout_list: ope2.get_txout_list_cache().to_vec(), + txin_utxo_list: self.txin_utxo_list.clone(), + }) + } + + /// Append utxo data to transaction. + /// + /// # Arguments + /// * `utxo_list` - Transaction input's utxo list. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{Address, OutPoint, Transaction, TxInData, TxOutData, UtxoData}; + /// let tx = Transaction::new(2, 0).expect("Fail"); + /// 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 txout_list = [TxOutData::from_address(amount, &addr)]; + /// let tx2 = tx.append_data(&txin_list, &txout_list).expect("Fail"); + /// let utxo = UtxoData::from_address(&outpoint, amount, &addr).expect("Fail"); + /// let tx3 = tx2.append_utxo_list(&[utxo]).expect("Fail"); + /// ``` + pub fn append_utxo_list(&self, utxo_list: &[UtxoData]) -> Result { + let mut utxos = self.txin_utxo_list.clone(); + for utxo in utxo_list.to_vec() { + let mut is_find = false; + for txin in &self.txin_list { + if txin.outpoint == utxo.outpoint { + is_find = true; + break; + } + } + if is_find { + utxos.push(utxo); + } + } + Ok(Transaction { + tx: self.tx.clone(), + data: self.data.clone(), + txin_list: self.txin_list.clone(), + txout_list: self.txout_list.clone(), + txin_utxo_list: utxos, }) } @@ -868,6 +997,7 @@ impl Transaction { data, txin_list: self.txin_list.clone(), txout_list: self.txout_list.clone(), + txin_utxo_list: self.txin_utxo_list.clone(), }; tx_obj.txout_list[index as usize].amount = amount; Ok(tx_obj) @@ -982,6 +1112,231 @@ impl Transaction { ) } + /// Get signature hash by pubkey. + /// + /// # Arguments + /// * `outpoint` - A transaction input out-point. + /// * `pubkey` - A public key. + /// * `sighash_type` - A transaction input sighash-type. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{Amount, HashType, OutPoint, Pubkey, SigHashType, Transaction, Descriptor, UtxoData, Network, Address}; + /// use std::str::FromStr; + /// let tx_str = "0200000002bdebed9413554bb95fffbdf436112c923c334a6850509ae7794d410524b061740000000000ffffffffc16d35d26589dfd54634181aa4a290cb9e06a716ea68620be05fbc46f1e197140100000000ffffffff0200e1f50500000000160014751e76e8199196d454941c45d1b3a323f1433bd620544771000000001600144dc2412fe3dc759e3830b6fb360264c8ce0abe3800000000"; + /// let pubkey1 = Pubkey::from_str("03d34d21d3017acdfb033e010574fb73dc83639f97145d83965fe1b19a4c8e2b6b").expect("Fail"); + /// let outpoint1 = OutPoint::from_str( + /// "7461b02405414d79e79a5050684a333c922c1136f4bdff5fb94b551394edebbd", + /// 0).expect("Fail"); + /// let outpoint2 = OutPoint::from_str( + /// "1497e1f146bc5fe00b6268ea16a7069ecb90a2a41a183446d5df8965d2356dc1", + /// 1).expect("Fail"); + /// let tx = Transaction::from_str(tx_str).expect("Fail"); + /// let addr2 = Address::from_string("bc1q7jm5vw5cunpy3lkvwdl3sr3qfm794xd4jcdzrv").expect("Fail"); + /// let amount1: i64 = 2000000000; + /// let amount2: i64 = 505000; + /// let desc1 = Descriptor::p2wpkh(&pubkey1, &Network::Mainnet).expect("Fail"); + /// let utxo1 = UtxoData::from_descriptor(&outpoint1, amount1, &desc1); + /// let utxo2 = UtxoData::from_address(&outpoint2, amount2, &addr2).expect("Fail"); + /// let tx2 = tx.append_utxo_list(&[utxo1, utxo2]).expect("Fail"); + /// let sighash = tx2.get_sighash_by_pubkey(&outpoint1, &pubkey1, &SigHashType::All).expect("Fail"); + /// ``` + pub fn get_sighash_by_pubkey( + &self, + outpoint: &OutPoint, + pubkey: &Pubkey, + sighash_type: &SigHashType, + ) -> Result, CfdError> { + if self.txin_utxo_list.is_empty() { + return Err(CfdError::IllegalState( + "Empty txin utxo list. Please call append_utxo_list".to_string(), + )); + } + let ope = TransactionOperation::create_instance(&Network::Mainnet, &self.txin_utxo_list); + let option = SigHashOption::new(*sighash_type, 0); + ope.get_sighash( + &hex_from_bytes(&self.tx), + outpoint, + &pubkey.to_hex(), + &Script::default(), + &option, + ) + } + + /// Get signature hash by schnorr pubkey. + /// + /// # Arguments + /// * `outpoint` - A transaction input out-point. + /// * `schnorr_pubkey` - A schnorr public key. + /// * `sighash_type` - A transaction input sighash-type. + /// * `annex` - An annex bytes. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{Amount, HashType, OutPoint, Pubkey, SchnorrPubkey, SigHashType, Transaction, Descriptor, UtxoData, Network, Address}; + /// use std::str::FromStr; + /// let tx_str = "0200000002bdebed9413554bb95fffbdf436112c923c334a6850509ae7794d410524b061740000000000ffffffffc16d35d26589dfd54634181aa4a290cb9e06a716ea68620be05fbc46f1e197140100000000ffffffff0200e1f50500000000160014751e76e8199196d454941c45d1b3a323f1433bd620544771000000001600144dc2412fe3dc759e3830b6fb360264c8ce0abe3800000000"; + /// let pubkey1 = Pubkey::from_str("03d34d21d3017acdfb033e010574fb73dc83639f97145d83965fe1b19a4c8e2b6b").expect("Fail"); + /// let pubkey2 = SchnorrPubkey::from_str("d34d21d3017acdfb033e010574fb73dc83639f97145d83965fe1b19a4c8e2b6b").expect("Fail"); + /// let outpoint1 = OutPoint::from_str( + /// "7461b02405414d79e79a5050684a333c922c1136f4bdff5fb94b551394edebbd", + /// 0).expect("Fail"); + /// let outpoint2 = OutPoint::from_str( + /// "1497e1f146bc5fe00b6268ea16a7069ecb90a2a41a183446d5df8965d2356dc1", + /// 1).expect("Fail"); + /// let tx = Transaction::from_str(tx_str).expect("Fail"); + /// let addr2 = Address::taproot(&pubkey2, &Network::Mainnet).expect("Fail"); + /// let amount1: i64 = 2000000000; + /// let amount2: i64 = 505000; + /// let desc1 = Descriptor::p2wpkh(&pubkey1, &Network::Mainnet).expect("Fail"); + /// let utxo1 = UtxoData::from_descriptor(&outpoint1, amount1, &desc1); + /// let utxo2 = UtxoData::from_address(&outpoint2, amount2, &addr2).expect("Fail"); + /// let tx2 = tx.append_utxo_list(&[utxo1, utxo2]).expect("Fail"); + /// let annex: Vec = vec![]; + /// let sighash = tx2.get_sighash_by_schnorr_pubkey(&outpoint2, &pubkey2, &SigHashType::Default, &annex).expect("Fail"); + /// ``` + pub fn get_sighash_by_schnorr_pubkey( + &self, + outpoint: &OutPoint, + schnorr_pubkey: &SchnorrPubkey, + sighash_type: &SigHashType, + annex: &[u8], + ) -> Result, CfdError> { + if self.txin_utxo_list.is_empty() { + return Err(CfdError::IllegalState( + "Empty txin utxo list. Please call append_utxo_list".to_string(), + )); + } + let ope = TransactionOperation::create_instance(&Network::Mainnet, &self.txin_utxo_list); + let mut option = SigHashOption::new(*sighash_type, 0); + option.annex = annex.to_vec(); + ope.get_sighash( + &hex_from_bytes(&self.tx), + outpoint, + &schnorr_pubkey.to_hex(), + &Script::default(), + &option, + ) + } + + /// Get signature hash by redeem script. + /// + /// # Arguments + /// * `outpoint` - A transaction input out-point. + /// * `redeem_script` - A redeem script. + /// * `sighash_type` - A transaction input sighash-type. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{Amount, HashType, OutPoint, Pubkey, Script, SigHashType, Transaction, Descriptor, UtxoData, Network, Address}; + /// use std::str::FromStr; + /// let tx_str = "0200000002bdebed9413554bb95fffbdf436112c923c334a6850509ae7794d410524b061740000000000ffffffffc16d35d26589dfd54634181aa4a290cb9e06a716ea68620be05fbc46f1e197140100000000ffffffff0200e1f50500000000160014751e76e8199196d454941c45d1b3a323f1433bd620544771000000001600144dc2412fe3dc759e3830b6fb360264c8ce0abe3800000000"; + /// let pubkey1 = Pubkey::from_str("03d34d21d3017acdfb033e010574fb73dc83639f97145d83965fe1b19a4c8e2b6b").expect("Fail"); + /// let script = Script::from_hex("512103d34d21d3017acdfb033e010574fb73dc83639f97145d83965fe1b19a4c8e2b6b51ae").expect("Fail"); + /// let outpoint1 = OutPoint::from_str( + /// "7461b02405414d79e79a5050684a333c922c1136f4bdff5fb94b551394edebbd", + /// 0).expect("Fail"); + /// let outpoint2 = OutPoint::from_str( + /// "1497e1f146bc5fe00b6268ea16a7069ecb90a2a41a183446d5df8965d2356dc1", + /// 1).expect("Fail"); + /// let tx = Transaction::from_str(tx_str).expect("Fail"); + /// let addr2 = Address::p2wsh(&script, &Network::Mainnet).expect("Fail"); + /// let amount1: i64 = 2000000000; + /// let amount2: i64 = 505000; + /// let desc1 = Descriptor::p2wpkh(&pubkey1, &Network::Mainnet).expect("Fail"); + /// let utxo1 = UtxoData::from_descriptor(&outpoint1, amount1, &desc1); + /// let utxo2 = UtxoData::from_address(&outpoint2, amount2, &addr2).expect("Fail"); + /// let tx2 = tx.append_utxo_list(&[utxo1, utxo2]).expect("Fail"); + /// let sighash = tx2.get_sighash_by_script(&outpoint2, &script, &SigHashType::Default).expect("Fail"); + /// ``` + pub fn get_sighash_by_script( + &self, + outpoint: &OutPoint, + redeem_script: &Script, + sighash_type: &SigHashType, + ) -> Result, CfdError> { + if self.txin_utxo_list.is_empty() { + return Err(CfdError::IllegalState( + "Empty txin utxo list. Please call append_utxo_list".to_string(), + )); + } + let ope = TransactionOperation::create_instance(&Network::Mainnet, &self.txin_utxo_list); + let option = SigHashOption::new(*sighash_type, 0); + ope.get_sighash( + &hex_from_bytes(&self.tx), + outpoint, + &String::default(), + &redeem_script, + &option, + ) + } + + /// Get signature hash by tapscript. + /// + /// # Arguments + /// * `outpoint` - A transaction input out-point. + /// * `tapscript` - A tapscript. + /// * `sighash_type` - A transaction input sighash-type. + /// * `code_separator_position` - A OP_CODESEPARATOR position. default is `CODE_SEPARATOR_POSITION_FINAL`. + /// * `annex` - An annex bytes. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{Amount, HashType, OutPoint, Pubkey, Script, SigHashType, Transaction, Descriptor, UtxoData, Network, Address, SchnorrPubkey, TapBranch, CODE_SEPARATOR_POSITION_FINAL}; + /// use std::str::FromStr; + /// let tx_str = "0200000002bdebed9413554bb95fffbdf436112c923c334a6850509ae7794d410524b061740000000000ffffffffc16d35d26589dfd54634181aa4a290cb9e06a716ea68620be05fbc46f1e197140100000000ffffffff0200e1f50500000000160014751e76e8199196d454941c45d1b3a323f1433bd620544771000000001600144dc2412fe3dc759e3830b6fb360264c8ce0abe3800000000"; + /// let pubkey1 = Pubkey::from_str("03d34d21d3017acdfb033e010574fb73dc83639f97145d83965fe1b19a4c8e2b6b").expect("Fail"); + /// let schnorr_pubkey = SchnorrPubkey::from_str("1777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb").expect("Fail"); + /// let script = Script::from_asm(&format!("{} OP_CHECKSIG", schnorr_pubkey.to_hex())).expect("Fail"); + /// let outpoint1 = OutPoint::from_str( + /// "7461b02405414d79e79a5050684a333c922c1136f4bdff5fb94b551394edebbd", + /// 0).expect("Fail"); + /// let outpoint2 = OutPoint::from_str( + /// "1497e1f146bc5fe00b6268ea16a7069ecb90a2a41a183446d5df8965d2356dc1", + /// 1).expect("Fail"); + /// let tree = TapBranch::from_tapscript(&script).expect("Fail"); + /// let tx = Transaction::from_str(tx_str).expect("Fail"); + /// let (_, addr2, _) = tree.get_tweaked_pubkey(&schnorr_pubkey, &Network::Mainnet).expect("Fail"); + /// let tapleaf_hash = tree.get_tapleaf_hash().expect("Fail"); + /// let amount1: i64 = 2000000000; + /// let amount2: i64 = 505000; + /// let desc1 = Descriptor::p2wpkh(&pubkey1, &Network::Mainnet).expect("Fail"); + /// let utxo1 = UtxoData::from_descriptor(&outpoint1, amount1, &desc1); + /// let utxo2 = UtxoData::from_address(&outpoint2, amount2, &addr2).expect("Fail"); + /// let tx2 = tx.append_utxo_list(&[utxo1, utxo2]).expect("Fail"); + /// let sighash = tx2.get_sighash_by_tapscript(&outpoint2, &tapleaf_hash, &SigHashType::Default, CODE_SEPARATOR_POSITION_FINAL, &[]).expect("Fail"); + /// ``` + pub fn get_sighash_by_tapscript( + &self, + outpoint: &OutPoint, + tapleaf_hash: &[u8; TAPROOT_HASH_SIZE], + sighash_type: &SigHashType, + code_separator_position: u32, + annex: &[u8], + ) -> Result, CfdError> { + if self.txin_utxo_list.is_empty() { + return Err(CfdError::IllegalState( + "Empty txin utxo list. Please call append_utxo_list".to_string(), + )); + } + let ope = TransactionOperation::create_instance(&Network::Mainnet, &self.txin_utxo_list); + let mut option = SigHashOption::new(*sighash_type, 0); + option.annex = annex.to_vec(); + option.code_separator_pos = code_separator_position; + option.tap_leaf_hash = *tapleaf_hash; + ope.get_sighash( + &hex_from_bytes(&self.tx), + outpoint, + &String::default(), + &Script::default(), + &option, + ) + } + /// Add signature and pubkey into the transaction. /// /// # Arguments @@ -1038,11 +1393,69 @@ impl Transaction { data, txin_list: self.txin_list.clone(), txout_list: self.txout_list.clone(), + txin_utxo_list: self.txin_utxo_list.clone(), }; tx_obj.txin_list[index as usize] = new_txin; Ok(tx_obj) } + /// Add taproot schnorr signature into the transaction. + /// + /// # Arguments + /// * `outpoint` - A transaction input out-point. + /// * `signature` - A schnorr signature. Please set SigHashType. + /// * `annex` - An annex bytes. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{Amount, HashType, OutPoint, Privkey, SchnorrPubkey, SigHashType, Transaction, SchnorrUtil, ByteData}; + /// use std::str::FromStr; + /// let tx_str = "020000000116d975e4c2cea30f72f4f5fe528f5a0727d9ea149892a50c030d44423088ea2f0000000000ffffffff0130f1029500000000160014164e985d0fc92c927a66c0cbaf78e6ea389629d500000000"; + /// let pubkey = SchnorrPubkey::from_str("1777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb").expect("Fail"); + /// let outpoint = OutPoint::from_str( + /// "2fea883042440d030ca5929814ead927075a8f52fef5f4720fa3cec2e475d916", + /// 0).expect("Fail"); + /// let tx = Transaction::from_str(tx_str).expect("Fail"); + /// let sighash_type = SigHashType::All; + /// let sighash = ByteData::from_str("e5b11ddceab1e4fc49a8132ae589a39b07acf49cabb2b0fbf6104bc31da12c02").expect("Fail"); + /// let privkey = Privkey::from_str("305e293b010d29bf3c888b617763a438fee9054c8cab66eb12ad078f819d9f27").expect("Fail"); + /// let util = SchnorrUtil::new(); + /// let aux_rand = ByteData::from_str("f5f4720fa3ead927075a8f52fecec2e40d030ca575d9162fea88304244929814").expect("Fail"); + /// let sig = util.sign(&sighash, &privkey, &aux_rand).expect("Fail"); + /// let signature = sig.get_sign_parameter(&sighash_type); + /// let signed_tx = tx.add_taproot_signature(&outpoint, &signature, &[]).expect("Fail"); + /// ``` + pub fn add_taproot_signature( + &self, + outpoint: &OutPoint, + signature: &SignParameter, + annex: &[u8], + ) -> Result { + let mut ope = TransactionOperation::new(&Network::Mainnet); + let tx_hex = hex_from_bytes(&self.tx); + let list = vec![signature.clone()]; + let tx = ope.add_taproot_sign( + &tx_hex, + outpoint, + &list, + &Script::default(), + &ByteData::default(), + annex, + )?; + let index = ope.get_last_txin_index(); + let data = ope.get_last_tx_data().clone(); + let mut tx_obj = Transaction { + tx, + data, + txin_list: self.txin_list.clone(), + txout_list: self.txout_list.clone(), + txin_utxo_list: self.txin_utxo_list.clone(), + }; + tx_obj.txin_list[index as usize] = ope.get_txin_list_cache()[0].clone(); + Ok(tx_obj) + } + /// Sign with privkey. /// /// # Arguments @@ -1095,47 +1508,108 @@ impl Transaction { data, txin_list: self.txin_list.clone(), txout_list: self.txout_list.clone(), + txin_utxo_list: self.txin_utxo_list.clone(), }; tx_obj.txin_list[index as usize] = new_txin; Ok(tx_obj) } - /// Add multisig signatures and redeem script into the transaction. + /// Sign with privkey by utxo data. /// /// # Arguments /// * `outpoint` - A transaction input out-point. - /// * `hash_type` - A transaction input hash type. (script hash only) - /// * `redeem_script` - A redeem script using sign. - /// * `signature_list` - Multiple signature. + /// * `privkey` - A private key using sign. + /// * `sighash_type` - A transaction input sighash-type. + /// * `aux_rand` - An aux random bytes. (32-byte) + /// * `annex` - An annex bytes. /// /// # Example /// /// ``` - /// use cfd_rust::{Amount, HashType, OutPoint, Privkey, Pubkey, Script, SigHashType, Transaction}; + /// use cfd_rust::{Amount, Address, Network, OutPoint, Privkey, SchnorrPubkey, SigHashType, Transaction, UtxoData}; /// use std::str::FromStr; - /// let tx_str = "0200000002bdebed9413554bb95fffbdf436112c923c334a6850509ae7794d410524b061740000000000ffffffffc16d35d26589dfd54634181aa4a290cb9e06a716ea68620be05fbc46f1e197140100000000ffffffff0200e1f50500000000160014751e76e8199196d454941c45d1b3a323f1433bd620544771000000001600144dc2412fe3dc759e3830b6fb360264c8ce0abe3800000000"; - /// let script = Script::from_hex("512103d34d21d3017acdfb033e010574fb73dc83639f97145d83965fe1b19a4c8e2b6b51ae").expect("Fail"); - /// let pubkey = Pubkey::from_str("03d34d21d3017acdfb033e010574fb73dc83639f97145d83965fe1b19a4c8e2b6b").expect("Fail"); + /// let privkey = + /// Privkey::from_str("305e293b010d29bf3c888b617763a438fee9054c8cab66eb12ad078f819d9f27") + /// .expect("Fail"); + /// let (pubkey, _) = SchnorrPubkey::from_privkey(&privkey).expect("Fail"); + /// let tx_hex = "020000000116d975e4c2cea30f72f4f5fe528f5a0727d9ea149892a50c030d44423088ea2f0000000000ffffffff0130f1029500000000160014164e985d0fc92c927a66c0cbaf78e6ea389629d500000000"; + /// let addr = Address::taproot(&pubkey, &Network::Testnet).expect("Fail"); /// let outpoint = OutPoint::from_str( - /// "1497e1f146bc5fe00b6268ea16a7069ecb90a2a41a183446d5df8965d2356dc1", - /// 1).expect("Fail"); - /// let tx = Transaction::from_str(tx_str).expect("Fail"); + /// "2fea883042440d030ca5929814ead927075a8f52fef5f4720fa3cec2e475d916", 0).expect("Fail"); + /// let utxos = [UtxoData::from_locking_script( + /// &outpoint, 2499999000, addr.get_locking_script(), &Network::Mainnet).expect("Fail"), + /// ]; /// let sighash_type = SigHashType::All; - /// let sighash = tx.create_sighash_by_script( - /// &outpoint, - /// &HashType::P2wsh, - /// &script, - /// &sighash_type, - /// &Amount::new(60000)).expect("Fail"); - /// let privkey = Privkey::from_wif("cUCCL2wBhCHVwiRpfUVd1rjWUSB4QCnGBczhCW5neLFTQkxZimeG").expect("Fail"); - /// let mut signature = privkey.calculate_ec_signature(&sighash, true).expect("Fail"); - /// signature = signature.set_signature_hash(&sighash_type) - /// .set_related_pubkey(&pubkey); - /// let signature_list = [signature]; - /// let signed_tx = tx.add_multisig_sign( - /// &outpoint, - /// &HashType::P2wsh, - /// &script, + /// let mut tx = Transaction::from_str(tx_hex).expect("Fail"); + /// tx = tx.append_utxo_list(&utxos).expect("Fail"); + /// tx = tx.sign_with_privkey_by_utxo_list(&outpoint, &privkey, &sighash_type, &[], &[]).expect("Fail"); + /// ``` + pub fn sign_with_privkey_by_utxo_list( + &self, + outpoint: &OutPoint, + privkey: &Privkey, + sighash_type: &SigHashType, + aux_rand: &[u8], + annex: &[u8], + ) -> Result { + let mut ope = TransactionOperation::create_instance(&Network::Mainnet, &self.txin_utxo_list); + let tx_hex = hex_from_bytes(&self.tx); + let mut option = SigHashOption::new(*sighash_type, 0); + option.annex = annex.to_vec(); + option.aux_rand = aux_rand.to_vec(); + let tx = ope.sign_with_privkey_by_utxo_list(&tx_hex, outpoint, &privkey, &option, true)?; + let new_tx_hex = ope.get_last_tx(); + let mut ope2 = ope.clone(); + let new_txin = ope2.get_txin_by_outpoint(&new_tx_hex, outpoint)?; + let index = ope2.get_last_txin_index(); + let data = ope2.get_last_tx_data().clone(); + let mut tx_obj = Transaction { + tx, + data, + txin_list: self.txin_list.clone(), + txout_list: self.txout_list.clone(), + txin_utxo_list: self.txin_utxo_list.clone(), + }; + tx_obj.txin_list[index as usize] = new_txin; + Ok(tx_obj) + } + + /// Add multisig signatures and redeem script into the transaction. + /// + /// # Arguments + /// * `outpoint` - A transaction input out-point. + /// * `hash_type` - A transaction input hash type. (script hash only) + /// * `redeem_script` - A redeem script using sign. + /// * `signature_list` - Multiple signature. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{Amount, HashType, OutPoint, Privkey, Pubkey, Script, SigHashType, Transaction}; + /// use std::str::FromStr; + /// let tx_str = "0200000002bdebed9413554bb95fffbdf436112c923c334a6850509ae7794d410524b061740000000000ffffffffc16d35d26589dfd54634181aa4a290cb9e06a716ea68620be05fbc46f1e197140100000000ffffffff0200e1f50500000000160014751e76e8199196d454941c45d1b3a323f1433bd620544771000000001600144dc2412fe3dc759e3830b6fb360264c8ce0abe3800000000"; + /// let script = Script::from_hex("512103d34d21d3017acdfb033e010574fb73dc83639f97145d83965fe1b19a4c8e2b6b51ae").expect("Fail"); + /// let pubkey = Pubkey::from_str("03d34d21d3017acdfb033e010574fb73dc83639f97145d83965fe1b19a4c8e2b6b").expect("Fail"); + /// let outpoint = OutPoint::from_str( + /// "1497e1f146bc5fe00b6268ea16a7069ecb90a2a41a183446d5df8965d2356dc1", + /// 1).expect("Fail"); + /// let tx = Transaction::from_str(tx_str).expect("Fail"); + /// let sighash_type = SigHashType::All; + /// let sighash = tx.create_sighash_by_script( + /// &outpoint, + /// &HashType::P2wsh, + /// &script, + /// &sighash_type, + /// &Amount::new(60000)).expect("Fail"); + /// let privkey = Privkey::from_wif("cUCCL2wBhCHVwiRpfUVd1rjWUSB4QCnGBczhCW5neLFTQkxZimeG").expect("Fail"); + /// let mut signature = privkey.calculate_ec_signature(&sighash, true).expect("Fail"); + /// signature = signature.set_signature_hash(&sighash_type) + /// .set_related_pubkey(&pubkey); + /// let signature_list = [signature]; + /// let signed_tx = tx.add_multisig_sign( + /// &outpoint, + /// &HashType::P2wsh, + /// &script, /// &signature_list, /// ).expect("Fail"); /// ``` @@ -1164,6 +1638,7 @@ impl Transaction { data, txin_list: self.txin_list.clone(), txout_list: self.txout_list.clone(), + txin_utxo_list: self.txin_utxo_list.clone(), }; tx_obj.txin_list[index as usize] = new_txin; Ok(tx_obj) @@ -1222,6 +1697,7 @@ impl Transaction { data, txin_list: self.txin_list.clone(), txout_list: self.txout_list.clone(), + txin_utxo_list: self.txin_utxo_list.clone(), }; tx_obj.txin_list[index as usize] = new_txin; Ok(tx_obj) @@ -1292,11 +1768,69 @@ impl Transaction { data, txin_list: self.txin_list.clone(), txout_list: self.txout_list.clone(), + txin_utxo_list: self.txin_utxo_list.clone(), }; tx_obj.txin_list[index as usize] = new_txin; Ok(tx_obj) } + /// Add tapscript with sign. + /// + /// # Arguments + /// * `outpoint` - A transaction input out-point. + /// * `sign_list` - A transaction sign parameter list. + /// * `tapscript` - A tapscript. + /// * `control_block` - A control block. + /// * `annex` - An annex bytes. + /// + /// # Example + /// + /// ``` + /// use cfd_rust::{OutPoint, Script, Transaction, Network, SchnorrPubkey, TapBranch}; + /// use std::str::FromStr; + /// let tx_str = "0200000002bdebed9413554bb95fffbdf436112c923c334a6850509ae7794d410524b061740000000000ffffffffc16d35d26589dfd54634181aa4a290cb9e06a716ea68620be05fbc46f1e197140100000000ffffffff0200e1f50500000000160014751e76e8199196d454941c45d1b3a323f1433bd620544771000000001600144dc2412fe3dc759e3830b6fb360264c8ce0abe3800000000"; + /// let schnorr_pubkey = SchnorrPubkey::from_str("1777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb").expect("Fail"); + /// let script = Script::from_asm("OP_TRUE").expect("Fail"); + /// let outpoint = OutPoint::from_str( + /// "7461b02405414d79e79a5050684a333c922c1136f4bdff5fb94b551394edebbd", + /// 0).expect("Fail"); + /// let tree = TapBranch::from_tapscript(&script).expect("Fail"); + /// let (_, _, control_block) = tree.get_tweaked_pubkey(&schnorr_pubkey, &Network::Mainnet).expect("Fail"); + /// let tapleaf_hash = tree.get_tapleaf_hash().expect("Fail"); + /// let mut tx = Transaction::from_str(tx_str).expect("Fail"); + /// tx = tx.add_tapscript_sign(&outpoint, &[], &script, &control_block, &[]).expect("Fail"); + /// ``` + pub fn add_tapscript_sign( + &self, + outpoint: &OutPoint, + sign_list: &[SignParameter], + tapscript: &Script, + control_block: &ByteData, + annex: &[u8], + ) -> Result { + let mut ope = TransactionOperation::new(&Network::Mainnet); + let tx_hex = hex_from_bytes(&self.tx); + let tx = ope.add_taproot_sign( + &tx_hex, + outpoint, + sign_list, + tapscript, + control_block, + annex, + )?; + let index = ope.get_last_txin_index(); + let data = ope.get_last_tx_data().clone(); + let mut tx_obj = Transaction { + tx, + data, + txin_list: self.txin_list.clone(), + txout_list: self.txout_list.clone(), + txin_utxo_list: self.txin_utxo_list.clone(), + }; + tx_obj.txin_list[index as usize] = ope.get_txin_list_cache()[0].clone(); + Ok(tx_obj) + } + /// Verify signature with pubkey. /// /// # Arguments @@ -1495,6 +2029,12 @@ impl Transaction { ) } + pub fn verify_sign_by_utxo_list(&self, outpoint: &OutPoint) -> Result<(), CfdError> { + let ope = + TransactionOperation::create_instance(&Network::Mainnet, &self.txin_utxo_list.clone()); + ope.verify_sign_by_utxo_list(&hex_from_bytes(&self.tx), outpoint) + } + /// Estimate fee on the transaction. /// /// # Arguments @@ -1556,16 +2096,18 @@ impl Transaction { fee_param: &FeeOption, fund_data: &mut FundTransactionData, ) -> Result { - let mut ope = TransactionOperation::new(&Network::Mainnet); - let fund_result = ope.fund_raw_transaction( - txin_list, - utxo_list, - &hex_from_bytes(&self.tx), - target_data, - fee_param, - )?; - let tx = ope.get_last_tx(); - let tx_obj = Transaction::from_str(tx)?; + let (fund_result, tx) = { + let mut ope = TransactionOperation::new(&Network::Mainnet); + let (fund_ret, tx_str) = ope.fund_raw_transaction( + txin_list, + utxo_list, + &hex_from_bytes(&self.tx), + target_data, + fee_param, + )?; + Ok((fund_ret, tx_str)) + }?; + let tx_obj = Transaction::from_str(&tx)?; *fund_data = fund_result; Ok(tx_obj) } @@ -1584,20 +2126,19 @@ impl FromStr for Transaction { data, txin_list: ope2.get_txin_list_cache().to_vec(), txout_list: ope2.get_txout_list_cache().to_vec(), + txin_utxo_list: ope2.txin_utxo_list.clone(), }) } } impl Default for Transaction { fn default() -> Transaction { - match Transaction::new(2, 0) { - Ok(tx) => tx, - _ => Transaction { - tx: [2, 0, 0, 0, 0, 0, 0, 0, 0, 0].to_vec(), - data: TxData::default(), - txin_list: vec![], - txout_list: vec![], - }, + Transaction { + tx: [2, 0, 0, 0, 0, 0, 0, 0, 0, 0].to_vec(), + data: TxData::default(), + txin_list: vec![], + txout_list: vec![], + txin_utxo_list: vec![], } } } @@ -1607,6 +2148,10 @@ pub(in crate) struct SigHashOption { pub sighash_type: SigHashType, pub amount: i64, pub value_byte: ByteData, + pub aux_rand: Vec, + pub annex: Vec, + pub tap_leaf_hash: [u8; TAPROOT_HASH_SIZE], + pub code_separator_pos: u32, } impl SigHashOption { @@ -1614,15 +2159,28 @@ impl SigHashOption { SigHashOption { sighash_type, amount, - value_byte: ByteData::default(), + ..SigHashOption::default() } } pub fn from_amount(amount: i64) -> SigHashOption { SigHashOption { - sighash_type: SigHashType::All, amount, + ..SigHashOption::default() + } + } +} + +impl Default for SigHashOption { + fn default() -> SigHashOption { + SigHashOption { + sighash_type: SigHashType::All, + amount: 0, value_byte: ByteData::default(), + aux_rand: vec![], + annex: vec![], + tap_leaf_hash: [0; 32], + code_separator_pos: 0xffffffff, } } } @@ -1656,6 +2214,7 @@ pub(in crate) struct TransactionOperation { last_tx: String, txin_list: Vec, txout_list: Vec, + txin_utxo_list: Vec, tx_data: TxData, last_txin_index: u32, } @@ -1664,11 +2223,15 @@ impl TransactionOperation { pub fn new(network: &Network) -> TransactionOperation { TransactionOperation { network: *network, - last_tx: String::default(), - txin_list: vec![], - txout_list: vec![], - tx_data: TxData::default(), - last_txin_index: 0, + ..TransactionOperation::default() + } + } + + pub fn create_instance(network: &Network, utxo_list: &[UtxoData]) -> TransactionOperation { + TransactionOperation { + network: *network, + txin_utxo_list: utxo_list.to_vec(), + ..TransactionOperation::default() } } @@ -1710,7 +2273,7 @@ impl TransactionOperation { amount: i64, ) -> Result, CfdError> { let tx_str = alloc_c_string(tx)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdUpdateTxOutAmount( @@ -1735,21 +2298,10 @@ impl TransactionOperation { } pub fn get_all_data(&mut self, tx: &str) -> Result { - let handle = ErrorHandle::new()?; + 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 = Self::get_count_internal(&self.network, &handle, &tx_handle, tx, true)?; - let out_count = Self::get_count_internal(&self.network, &handle, &tx_handle, tx, false)?; - let in_indexes = TransactionOperation::create_index_list(in_count); - let out_indexes = TransactionOperation::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 }; @@ -1757,6 +2309,65 @@ impl TransactionOperation { 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_data_handle, tx)?; + let in_count = Self::get_count_internal(&self.network, &handle, &tx_data_handle, tx, true)?; + let out_count = Self::get_count_internal(&self.network, &handle, &tx_data_handle, tx, false)?; + let in_indexes = TransactionOperation::create_index_list(in_count); + let out_indexes = TransactionOperation::create_index_list(out_count); + let in_data = self.get_tx_input_list_internal(&handle, &tx_data_handle, tx, &in_indexes)?; + let out_data = + self.get_tx_output_list_internal(&handle, &tx_data_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 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 + } + fn create_index_list(index_count: u32) -> Vec { let mut indexes: Vec = vec![]; if index_count == 0 { @@ -1779,7 +2390,7 @@ impl TransactionOperation { } pub fn get_tx_data(&self, tx: &str) -> Result { - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let result = self.get_tx_data_internal(&handle, &TxDataHandle::empty(), tx); handle.free_handle(); result @@ -1829,7 +2440,7 @@ impl TransactionOperation { } pub fn get_txin_by_outpoint(&mut self, tx: &str, outpoint: &OutPoint) -> Result { - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let result = { let tx_data_handle = TxDataHandle::new(&handle, &self.network, tx)?; let list_result = { @@ -1870,36 +2481,53 @@ impl TransactionOperation { result } - /* - pub fn get_tx_input(&mut self, tx: &str, index: u32) -> Result { - let indexes = vec![index]; - let list = self.get_tx_input_list(tx, &indexes)?; - if list.is_empty() { - Err(CfdError::Internal("Failed to empty list.".to_string())) - } else { - Ok(list[0].clone()) - } - } + 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.txid.to_hex())?; + let error_code = unsafe { + CfdGetTxInIndexByHandle( + handle.as_handle(), + tx_data_handle.as_handle(), + txid.as_ptr(), + outpoint.vout, + &mut index, + ) + }; + match error_code { + 0 => Ok(index), + _ => Err(handle.get_error(error_code)), + } + }?; - pub fn get_tx_input_list(&mut self, tx: &str, indexes: &[u32]) -> Result, CfdError> { - let handle = ErrorHandle::new()?; - let result = { - let tx_data_handle = TxDataHandle::new(&handle, &self.network, tx)?; - let list_result = { - 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; - Ok(list_result) - }; - tx_data_handle.free_handle(&handle); - list_result + 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()) + } }; - handle.free_handle(); - result + if tx_handle.is_null() { + tx_data_handle.free_handle(&handle); + } + list_result } - */ pub fn get_tx_input_list_internal( &self, @@ -1967,33 +2595,6 @@ impl TransactionOperation { result } - /* - pub fn get_tx_output(&self, tx: &str, index: u32) -> Result { - let indexes = vec![index]; - let result = self.get_tx_output_list(tx, &indexes); - if let Err(e) = result { - Err(e) - } else { - let list = result.unwrap(); - if list.is_empty() { - Err(CfdError::Internal("Failed to empty list.".to_string())) - } else { - Ok(list[0].clone()) - } - } - } - */ - - /* - pub fn get_tx_output_list(&self, tx: &str, indexes: &[u32]) -> Result, CfdError> { - let handle = ErrorHandle::new()?; - let result = self.get_tx_output_list_internal( - &handle, &TxDataHandle::empty(), tx, indexes); - handle.free_handle(); - result - } - */ - fn get_tx_output_list_internal( &self, handle: &ErrorHandle, @@ -2052,7 +2653,7 @@ impl TransactionOperation { /* pub fn get_count(&self, tx: &str, is_target_input: bool) -> Result { - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let result = self.get_count_internal( &handle, &TxDataHandle::empty(), tx, is_target_input); handle.free_handle(); @@ -2092,7 +2693,7 @@ impl TransactionOperation { pub fn get_txin_index_by_outpoint(&self, tx: &str, outpoint: &OutPoint) -> Result { let tx_str = alloc_c_string(tx)?; let txid = alloc_c_string(&outpoint.txid.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut index: c_uint = 0; let error_code = unsafe { CfdGetTxInIndex( @@ -2113,7 +2714,7 @@ impl TransactionOperation { } pub fn get_txout_index_by_address(&self, tx: &str, address: &Address) -> Result { - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let result = { let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; let result = Self::get_txout_index(&handle, &tx_handle, address, &Script::default()); @@ -2129,7 +2730,7 @@ impl TransactionOperation { tx: &str, locking_script: &Script, ) -> Result { - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let result = { let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; let result = Self::get_txout_index(&handle, &tx_handle, &Address::default(), locking_script); @@ -2155,7 +2756,7 @@ impl TransactionOperation { let script_str = alloc_c_string(&redeem_script.to_hex())?; let amount = option.amount; let sighash_type = option.sighash_type; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { CfdCreateSighash( @@ -2184,6 +2785,57 @@ impl TransactionOperation { result } + pub fn get_sighash( + &self, + tx: &str, + outpoint: &OutPoint, + pubkey: &str, + redeem_script: &Script, + option: &SigHashOption, + ) -> Result, CfdError> { + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let result = { + self.set_utxo_list(&handle, &tx_handle, &String::default())?; + let txid = alloc_c_string(&outpoint.txid.to_hex())?; + let pubkey_str = alloc_c_string(&pubkey)?; + let script_str = alloc_c_string(&redeem_script.to_hex())?; + let tapleaf_hash = alloc_c_string(&hex_from_bytes(&option.tap_leaf_hash))?; + let annex = alloc_c_string(&ByteData::from_slice(&option.annex).to_hex())?; + let sighash_type = option.sighash_type; + let mut output: *mut c_char = ptr::null_mut(); + let error_code = unsafe { + CfdCreateSighashByHandle( + handle.as_handle(), + tx_handle.as_handle(), + txid.as_ptr(), + outpoint.vout, + sighash_type.to_c_value(), + sighash_type.is_anyone_can_pay(), + pubkey_str.as_ptr(), + script_str.as_ptr(), + tapleaf_hash.as_ptr(), + option.code_separator_pos, + annex.as_ptr(), + &mut output, + ) + }; + match error_code { + 0 => { + let output_obj = unsafe { collect_cstring_and_free(output) }?; + Ok(byte_from_hex_unsafe(&output_obj)) + } + _ => Err(handle.get_error(error_code)), + } + }; + tx_handle.free_handle(&handle); + result + }; + handle.free_handle(); + result + } + pub fn add_sign( &mut self, tx: &str, @@ -2195,7 +2847,7 @@ impl TransactionOperation { let tx_str = alloc_c_string(tx)?; let txid = alloc_c_string(&outpoint.txid.to_hex())?; let sign_data_hex = alloc_c_string(&sign_data.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let sighash_type = sign_data.get_sighash_type(); let use_der_encoded = match sign_data.to_slice().len() <= 65 { true => sign_data.can_use_der_encode(), @@ -2242,7 +2894,7 @@ impl TransactionOperation { let txid = alloc_c_string(&outpoint.txid.to_hex())?; let pubkey_hex = alloc_c_string(&pubkey.to_hex())?; let signature_hex = alloc_c_string(&signature.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let sighash_type = signature.get_sighash_type(); let use_der_encoded = match signature.to_slice().len() <= 65 { true => signature.can_use_der_encode(), @@ -2290,7 +2942,7 @@ impl TransactionOperation { let mut temp_clear_stack = clear_stack; let txid = alloc_c_string(&outpoint.txid.to_hex())?; let script_hex = alloc_c_string(&redeem_script.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let result = { let mut tx_hex; for sign_data in sign_list { @@ -2355,6 +3007,85 @@ impl TransactionOperation { result } + pub fn add_taproot_sign( + &mut self, + tx: &str, + outpoint: &OutPoint, + sign_list: &[SignParameter], + tapscript: &Script, + control_block: &ByteData, + annex: &[u8], + ) -> Result, CfdError> { + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let result = { + let txid = alloc_c_string(&outpoint.txid.to_hex())?; + let mut signature = String::default(); + if tapscript.is_empty() && sign_list.len() == 1 { + let sig = sign_list[0].append_taproot_sighash_type()?; + signature = sig.to_hex(); + } else { + let mut clear_flag = true; + for sign_data in sign_list { + { + let sig = sign_data.append_taproot_sighash_type()?; + let sig_str = alloc_c_string(&sig.to_hex())?; + let error_code = unsafe { + CfdAddTxSignByHandle( + handle.as_handle(), + tx_handle.as_handle(), + txid.as_ptr(), + outpoint.vout, + HashType::Taproot.to_c_value(), + sig_str.as_ptr(), + false, + 0, + false, + clear_flag, + ) + }; + clear_flag = false; + match error_code { + 0 => Ok(()), + _ => Err(handle.get_error(error_code)), + } + }? + } + } + { + let sig_str = alloc_c_string(&signature)?; + let script_str = alloc_c_string(&tapscript.to_hex())?; + let control_block_str = alloc_c_string(&control_block.to_hex())?; + let annex_str = alloc_c_string(&ByteData::from_slice(annex).to_hex())?; + let error_code = unsafe { + CfdAddTaprootSignByHandle( + handle.as_handle(), + tx_handle.as_handle(), + txid.as_ptr(), + outpoint.vout, + sig_str.as_ptr(), + script_str.as_ptr(), + control_block_str.as_ptr(), + annex_str.as_ptr(), + ) + }; + if error_code != 0 { + Err(handle.get_error(error_code)) + } else { + Ok(()) + } + }?; + 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); + result + }; + handle.free_handle(); + result + } + pub fn sign_with_privkey( &mut self, tx: &str, @@ -2368,7 +3099,7 @@ impl TransactionOperation { let txid = alloc_c_string(&outpoint.txid.to_hex())?; let pubkey_hex = alloc_c_string(&key.to_pubkey().to_hex())?; let privkey_hex = alloc_c_string(&key.to_privkey().to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let sighash_type = option.sighash_type; let mut output: *mut c_char = ptr::null_mut(); let error_code = unsafe { @@ -2400,6 +3131,54 @@ impl TransactionOperation { result } + pub fn sign_with_privkey_by_utxo_list( + &mut self, + tx: &str, + outpoint: &OutPoint, + key: &Privkey, + option: &SigHashOption, + has_grind_r: bool, + ) -> Result, CfdError> { + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let result = { + self.set_utxo_list(&handle, &tx_handle, &String::default())?; + { + let txid = alloc_c_string(&outpoint.txid.to_hex())?; + let privkey_hex = alloc_c_string(&key.to_hex())?; + let annex = alloc_c_string(&ByteData::from_slice(&option.annex).to_hex())?; + let aux_rand = alloc_c_string(&ByteData::from_slice(&option.aux_rand).to_hex())?; + let sighash_type = option.sighash_type; + let error_code = unsafe { + CfdAddSignWithPrivkeyByHandle( + handle.as_handle(), + tx_handle.as_handle(), + txid.as_ptr(), + outpoint.vout, + privkey_hex.as_ptr(), + sighash_type.to_c_value(), + sighash_type.is_anyone_can_pay(), + has_grind_r, + aux_rand.as_ptr(), + annex.as_ptr(), + ) + }; + match error_code { + 0 => Ok(()), + _ => Err(handle.get_error(error_code)), + } + }?; + 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); + result + }; + handle.free_handle(); + result + } + pub fn add_multisig_sign( &mut self, tx: &str, @@ -2411,7 +3190,7 @@ impl TransactionOperation { let tx_str = alloc_c_string(tx)?; let txid = alloc_c_string(&outpoint.txid.to_hex())?; let script_hex = alloc_c_string(&redeem_script.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut multisig_handle: *mut c_void = ptr::null_mut(); let error_code = unsafe { CfdInitializeMultisigSign(handle.as_handle(), &mut multisig_handle) }; let result = match error_code { @@ -2507,7 +3286,7 @@ impl TransactionOperation { let pubkey_hex = alloc_c_string(&key.pubkey.to_hex())?; let script_hex = alloc_c_string(&key.script.to_hex())?; let value_byte_hex = alloc_c_string(&option.value_byte.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let sighash_type = sign_data.get_sighash_type(); let error_code = unsafe { CfdVerifySignature( @@ -2549,7 +3328,7 @@ impl TransactionOperation { let address_str = alloc_c_string(address.to_str())?; let script_hex = alloc_c_string(&locking_script.to_hex())?; let value_byte_hex = alloc_c_string(&option.value_byte.to_hex())?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let error_code = unsafe { CfdVerifyTxSign( handle.as_handle(), @@ -2572,6 +3351,100 @@ impl TransactionOperation { result } + pub fn verify_sign_by_utxo_list(&self, tx: &str, outpoint: &OutPoint) -> Result<(), CfdError> { + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::new(&handle, &self.network, tx)?; + let result = { + self.set_utxo_list(&handle, &tx_handle, &String::default())?; + self.verify_sign_by_handle(&handle, &tx_handle, &String::default(), outpoint) + }; + tx_handle.free_handle(&handle); + result + }; + handle.free_handle(); + result + } + + fn set_utxo_list( + &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 result = { + for utxo in self.txin_utxo_list.as_slice() { + { + let txid = alloc_c_string(&utxo.outpoint.txid.to_hex())?; + let commitment = alloc_c_string(&String::default())?; + let descriptor = alloc_c_string(utxo.descriptor.to_str())?; + let address = alloc_c_string(&String::default())?; + let asset = alloc_c_string(&String::default())?; + let script_template = alloc_c_string(&utxo.scriptsig_template.to_hex())?; + let error_code = unsafe { + CfdSetTransactionUtxoData( + handle.as_handle(), + tx_data_handle.as_handle(), + txid.as_ptr(), + utxo.outpoint.vout, + utxo.amount, + commitment.as_ptr(), + descriptor.as_ptr(), + address.as_ptr(), + asset.as_ptr(), + script_template.as_ptr(), + false, + ) + }; + match error_code { + 0 => Ok(()), + _ => Err(handle.get_error(error_code)), + } + }?; + } + Ok(()) + }; + if tx_handle.is_null() { + tx_data_handle.free_handle(&handle); + } + result + } + + fn verify_sign_by_handle( + &self, + handle: &ErrorHandle, + tx_handle: &TxDataHandle, + tx: &str, + outpoint: &OutPoint, + ) -> Result<(), CfdError> { + let tx_data_handle = match tx_handle.is_null() { + false => tx_handle.clone(), + _ => TxDataHandle::new(&handle, &self.network, tx)?, + }; + let txid = alloc_c_string(&outpoint.txid.to_hex())?; + let error_code = unsafe { + CfdVerifyTxSignByHandle( + handle.as_handle(), + tx_data_handle.as_handle(), + txid.as_ptr(), + outpoint.vout, + ) + }; + let result = 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 estimate_fee( &self, tx: &str, @@ -2580,7 +3453,7 @@ impl TransactionOperation { ) -> Result { let tx_str = alloc_c_string(tx)?; let empty_str = alloc_c_string("")?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut fee_handle: *mut c_void = ptr::null_mut(); let error_code = unsafe { CfdInitializeEstimateFee( @@ -2656,7 +3529,7 @@ impl TransactionOperation { fee_param: &FeeOption, ) -> Result { let empty_str = alloc_c_string("")?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut coin_handle: *mut c_void = ptr::null_mut(); let error_code = unsafe { CfdInitializeCoinSelection( @@ -2783,7 +3656,7 @@ impl TransactionOperation { tx: &str, target_data: &FundTargetOption, fee_param: &FeeOption, - ) -> Result { + ) -> Result<(FundTransactionData, String), CfdError> { let network = match target_data.reserved_address.valid() { true => match target_data.reserved_address.get_network_type() { Network::Mainnet | Network::Testnet | Network::Regtest => { @@ -2795,7 +3668,7 @@ impl TransactionOperation { }; let empty_str = alloc_c_string("")?; let tx_hex = alloc_c_string(tx)?; - let handle = ErrorHandle::new()?; + let mut handle = ErrorHandle::new()?; let mut fund_handle: *mut c_void = ptr::null_mut(); let error_code = unsafe { CfdInitializeFundRawTx( @@ -2937,8 +3810,8 @@ impl TransactionOperation { used_addr_list.push(address); index += 1; } - self.last_tx = output_tx; - Ok(FundTransactionData::new(used_addr_list, tx_fee)) + self.last_tx = output_tx.clone(); + Ok((FundTransactionData::new(used_addr_list, tx_fee), output_tx)) }; unsafe { CfdFreeFundRawTxHandle(handle.as_handle(), fund_handle); @@ -3039,85 +3912,73 @@ impl TransactionOperation { txin_list: &[TxInData], txout_list: &[TxOutData], ) -> Result, CfdError> { - let tx_str = alloc_c_string(tx)?; - let handle = ErrorHandle::new()?; - let mut create_handle: *mut c_void = ptr::null_mut(); - let error_code = unsafe { - CfdInitializeTransaction( - handle.as_handle(), - self.network.to_c_value(), - version, - locktime, - tx_str.as_ptr(), - &mut create_handle, - ) - }; - let result = match error_code { - 0 => { - let ret = { - for input in txin_list { - let _err = { - let txid = alloc_c_string(&input.outpoint.txid.to_hex())?; - let error_code = unsafe { - CfdAddTransactionInput( - handle.as_handle(), - create_handle, - txid.as_ptr(), - input.outpoint.vout, - input.sequence, - ) - }; - match error_code { - 0 => Ok(()), - _ => Err(handle.get_error(error_code)), - } - }?; - } - for output in txout_list { - let _err = { - let address = alloc_c_string(output.address.to_str())?; - let script = alloc_c_string(&output.locking_script.to_hex())?; - let asset = alloc_c_string(&output.asset)?; - let error_code = unsafe { - CfdAddTransactionOutput( - handle.as_handle(), - create_handle, - output.amount, - address.as_ptr(), - script.as_ptr(), - asset.as_ptr(), - ) - }; - match error_code { - 0 => Ok(()), - _ => Err(handle.get_error(error_code)), - } - }?; - } - let mut output: *mut c_char = ptr::null_mut(); - let error_code = - unsafe { CfdFinalizeTransaction(handle.as_handle(), create_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)) + let mut handle = ErrorHandle::new()?; + let result = { + let tx_handle = TxDataHandle::create(&handle, &self.network, version, locktime, tx)?; + let ret = { + for input in txin_list { + let _err = { + let txid = alloc_c_string(&input.outpoint.txid.to_hex())?; + let error_code = unsafe { + CfdAddTransactionInput( + handle.as_handle(), + tx_handle.as_handle(), + txid.as_ptr(), + input.outpoint.vout, + input.sequence, + ) + }; + match error_code { + 0 => Ok(()), + _ => Err(handle.get_error(error_code)), } - _ => Err(handle.get_error(error_code)), - } - }; - unsafe { - CfdFreeTransactionHandle(handle.as_handle(), create_handle); + }?; } - ret - } - _ => Err(handle.get_error(error_code)), + for output in txout_list { + let _err = { + let address = alloc_c_string(output.address.to_str())?; + let script = alloc_c_string(&output.locking_script.to_hex())?; + let asset = alloc_c_string(&output.asset)?; + let error_code = unsafe { + CfdAddTransactionOutput( + handle.as_handle(), + tx_handle.as_handle(), + output.amount, + address.as_ptr(), + script.as_ptr(), + asset.as_ptr(), + ) + }; + match error_code { + 0 => Ok(()), + _ => Err(handle.get_error(error_code)), + } + }?; + } + self.get_tx_internal(&handle, &tx_handle, &String::default()) + }; + tx_handle.free_handle(&handle); + ret }; handle.free_handle(); result } } +impl Default for TransactionOperation { + fn default() -> TransactionOperation { + TransactionOperation { + network: Network::Mainnet, + last_tx: String::default(), + txin_list: vec![], + txout_list: vec![], + txin_utxo_list: vec![], + tx_data: TxData::default(), + last_txin_index: 0, + } + } +} + /// A container that tx data handler. #[derive(Debug, Clone)] pub(in crate) struct TxDataHandle { @@ -3142,6 +4003,31 @@ impl TxDataHandle { } } + pub fn create( + handle: &ErrorHandle, + network: &Network, + version: u32, + locktime: u32, + tx: &str, + ) -> Result { + let tx_str = alloc_c_string(tx)?; + let mut tx_handle: *mut c_void = ptr::null_mut(); + let error_code = unsafe { + CfdInitializeTransaction( + handle.as_handle(), + network.to_c_value(), + version, + locktime, + tx_str.as_ptr(), + &mut tx_handle, + ) + }; + match error_code { + 0 => Ok(TxDataHandle { tx_handle }), + _ => Err(handle.get_error(error_code)), + } + } + #[inline] pub fn as_handle(&self) -> *const c_void { self.tx_handle diff --git a/tests/address_test.rs b/tests/address_test.rs index 664cf3f..3efafef 100644 --- a/tests/address_test.rs +++ b/tests/address_test.rs @@ -2,7 +2,7 @@ extern crate cfd_rust; #[cfg(test)] mod tests { - use cfd_rust::{Address, AddressType, Network, Pubkey, Script}; + use cfd_rust::{Address, AddressType, Network, Pubkey, SchnorrPubkey, Script, WitnessVersion}; use std::str::FromStr; #[test] @@ -146,5 +146,37 @@ mod tests { "tb1q35e8e0lppzr5c322quaujy9tcg3wd89wrxremfk6val0f6y232cs93nx0t", addr_p2wsh.to_str() ); + + // taproot + let addr_taproot1 = + Address::from_str("tb1pzamhq9jglfxaj0r5ahvatr8uc77u973s5tm04yytdltsey5r8naskf8ee6") + .expect("Fail"); + assert_eq!( + "tb1pzamhq9jglfxaj0r5ahvatr8uc77u973s5tm04yytdltsey5r8naskf8ee6", + addr_taproot1.to_str() + ); + assert_eq!(&Network::Testnet, addr_taproot1.get_network_type()); + assert_eq!( + &AddressType::TaprootAddress, + addr_taproot1.get_address_type() + ); + assert_eq!( + WitnessVersion::Version1, + addr_taproot1.get_witness_version() + ); + assert_eq!( + "51201777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb", + addr_taproot1.get_locking_script().to_hex() + ); + assert_eq!( + "1777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb", + addr_taproot1.get_hash().to_hex() + ); + let spk = SchnorrPubkey::from_slice(addr_taproot1.get_hash().to_slice()).expect("Fail"); + let addr_taproot2 = Address::taproot(&spk, &Network::Testnet).expect("Fail"); + assert_eq!( + "tb1pzamhq9jglfxaj0r5ahvatr8uc77u973s5tm04yytdltsey5r8naskf8ee6", + addr_taproot2.to_str() + ); } } diff --git a/tests/confidential_transaction_test.rs b/tests/confidential_transaction_test.rs index 5980eb2..e4d43ff 100644 --- a/tests/confidential_transaction_test.rs +++ b/tests/confidential_transaction_test.rs @@ -1223,14 +1223,14 @@ mod elements_tests { .expect("Fail"); let used_addr = data.reserved_address_list; - assert_eq!("0200000000030f231181a6d8fa2c5f7020948464110fbcc925f94d673d5752ce66d00250a1570100008000ffffffffd8bbe31bc590cbb6a47d2e53a956ec25d8890aefd60dcfc93efd34727554890b0683fe0819a4f9770c8a7cd5824e82975c825e017aff8ba0d6a5eb4959cf9c6f010000000023c34600000bfa8774c5f753ce2f801a8106413b470af94edbff5b4242ed4c5a26d20e72b90000000000ffffffff040b0000000000000000000000000000000000000000000000000000000000000000000000ffffffff07010bad521bafdac767421d45b71b29a349c7b2ca2a06b5d8e3b5898c91df2769ed010000000029b92700001976a9146c22e209d36612e0d9d2a20b814d7d8648cc7a7788ac01cdb0ed311810e61036ac9255674101497850f5eee5e4320be07479c05473cbac010000000023c34600001976a9149bdcb18911fa9faad6632ca43b81739082b0a19588ac0100000000000000000000000000000000000000000000000000000000000000aa010000000000989680001600144352a1a6e86311f22274f7ebb2746de21b09b15d0100000000000000000000000000000000000000000000000000000000000000bb01000000000007a120001600148beaaac4654cf4ebd8e46ca5062b0e7fb3e7470c0100000000000000000000000000000000000000000000000000000000000000aa01000000000000037300000100000000000000000000000000000000000000000000000000000000000000bb010000000001124c1e00160014a53be40113bb50f2b8b2d0bfea1e823e75632b5f0100000000000000000000000000000000000000000000000000000000000000aa0100000000004b57eb0016001478eb9fc2c9e1cdf633ecb646858ba862b21384ab00000000", + assert_eq!("0200000000030f231181a6d8fa2c5f7020948464110fbcc925f94d673d5752ce66d00250a1570100008000ffffffffd8bbe31bc590cbb6a47d2e53a956ec25d8890aefd60dcfc93efd34727554890b0683fe0819a4f9770c8a7cd5824e82975c825e017aff8ba0d6a5eb4959cf9c6f010000000023c34600000bfa8774c5f753ce2f801a8106413b470af94edbff5b4242ed4c5a26d20e72b90000000000ffffffff040b0000000000000000000000000000000000000000000000000000000000000000000000ffffffff07010bad521bafdac767421d45b71b29a349c7b2ca2a06b5d8e3b5898c91df2769ed010000000029b92700001976a9146c22e209d36612e0d9d2a20b814d7d8648cc7a7788ac01cdb0ed311810e61036ac9255674101497850f5eee5e4320be07479c05473cbac010000000023c34600001976a9149bdcb18911fa9faad6632ca43b81739082b0a19588ac0100000000000000000000000000000000000000000000000000000000000000aa010000000000989680001600144352a1a6e86311f22274f7ebb2746de21b09b15d0100000000000000000000000000000000000000000000000000000000000000bb01000000000007a120001600148beaaac4654cf4ebd8e46ca5062b0e7fb3e7470c0100000000000000000000000000000000000000000000000000000000000000aa01000000000000037200000100000000000000000000000000000000000000000000000000000000000000bb010000000001124c1e00160014a53be40113bb50f2b8b2d0bfea1e823e75632b5f0100000000000000000000000000000000000000000000000000000000000000aa0100000000004b57ec0016001478eb9fc2c9e1cdf633ecb646858ba862b21384ab00000000", tx.to_str()); assert_eq!(2, used_addr.len()); if used_addr.len() == 2 { assert_eq!(addr2.to_str(), used_addr[0].to_str()); assert_eq!(addr1.to_str(), used_addr[1].to_str()); } - assert_eq!(883, data.fee_amount); + assert_eq!(882, data.fee_amount); // calc fee let fee_utxos = [input_utxo, utxos[5].clone(), utxos[9].clone()]; diff --git a/tests/script_test.rs b/tests/script_test.rs index 4a2a991..9748347 100644 --- a/tests/script_test.rs +++ b/tests/script_test.rs @@ -3,7 +3,7 @@ extern crate sha2; #[cfg(test)] mod tests { - use cfd_rust::{Pubkey, Script}; + use cfd_rust::{ByteData, Privkey, Pubkey, SchnorrPubkey, Script, TapBranch}; use std::str::FromStr; #[test] @@ -49,4 +49,86 @@ mod tests { let multisig = Script::multisig(2, &pubkey_list).expect("Fail"); assert_eq!(multisig_script.to_asm(), multisig.to_asm()); } + + #[test] + fn tapscript_tree_test1() { + let privkey = + Privkey::from_str("305e293b010d29bf3c888b617763a438fee9054c8cab66eb12ad078f819d9f27") + .expect("Fail"); + let (pubkey, _) = SchnorrPubkey::from_privkey(&privkey).expect("Fail"); + assert_eq!( + "1777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb", + pubkey.to_hex() + ); + + let script_checksig = + Script::from_asm(&format!("{} OP_CHECKSIG", pubkey.to_hex())).expect("Fail"); + let script_op_true = Script::from_asm("OP_TRUE").expect("Fail"); + let script_checksig2 = Script::from_asm( + "ac52f50b28cdd4d3bcb7f0d5cb533f232e4c4ef12fbf3e718420b84d4e3c3440 OP_CHECKSIG", + ) + .expect("Fail"); + + let mut tree1 = TapBranch::from_tapscript(&script_checksig).expect("Fail"); + assert_eq!( + "tl(201777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfbac)", + tree1.to_str() + ); + tree1.add_by_tapleaf(&script_op_true).expect("Fail"); + assert_eq!( + "{tl(51),tl(201777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfbac)}", + tree1.to_str() + ); + tree1.add_by_tapleaf(&script_checksig2).expect("Fail"); + assert_eq!("{tl(20ac52f50b28cdd4d3bcb7f0d5cb533f232e4c4ef12fbf3e718420b84d4e3c3440ac),{tl(51),tl(201777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfbac)}}", tree1.to_str()); + let node_list1 = tree1.get_target_nodes(); + assert_eq!(2, node_list1.len()); + let count1 = tree1.get_branch_count().expect("Fail"); + assert_eq!(2, count1); + + // deserialize + let tree_str = "{{{tl(51),{tl(204a7af8660f2b0bdb92d2ce8b88ab30feb916343228d2e7bd15da02e1f6a31d47ac),tl(2000d134c42fd51c90fa82c6cfdaabd895474d979118525362c0cd236c857e29d9ac)}},{{tl(20ac52f50b28cdd4d3bcb7f0d5cb533f232e4c4ef12fbf3e718420b84d4e3c3440ac),{tl(2057bf643684f6c5c75e1cdf45990036502a0d897394013210858cdabcbb95a05aac),tl(51)}},tl(2057bf643684f6c5c75e1cdf45990036502a0d897394013210858cdabcbb95a05aad205bec1a08fa3443176edd0a08e2a64642f45e57543b62bffe43ec350edc33dc22ac)}},tl(2008f8280d68e02e807ccffee141c4a6b7ac31d3c283ae0921892d95f691742c44ad20b0f8ce3e1df406514a773414b5d9e5779d8e68ce816e9db39b8e53255ac3b406ac)}"; + let control_nodes: Vec<[u8; 32]> = vec![ + ByteData::from_str("06b46c960d6824f0da5af71d9ecc55714de5b2d2da51be60bd12c77df20a20df") + .expect("Fail") + .to_32byte_array(), + ByteData::from_str("4691fbb1196f4675241c8958a7ab6378a63aa0cc008ed03d216fd038357f52fd") + .expect("Fail") + .to_32byte_array(), + ByteData::from_str("e47f58011f27e9046b8195d0ab6a2acbc68ce281437a8d5132dadf389b2a5ebb") + .expect("Fail") + .to_32byte_array(), + ByteData::from_str("32a0a039ec1412be2803fd7b5f5444c03d498e5e8e107ee431a9597c7b5b3a7c") + .expect("Fail") + .to_32byte_array(), + ByteData::from_str("d7b0b8d070638ff4f0b7e7d2aa930c58ec2d39853fd04c29c4c6688fdcb2ae75") + .expect("Fail") + .to_32byte_array(), + ]; + let tree2 = TapBranch::from_string_by_tapscript(&tree_str, &script_op_true, &control_nodes) + .expect("Fail"); + assert_eq!(5, tree2.get_branch_count().expect("Fail")); + assert_eq!(tree_str, tree2.to_str()); + let node_list = tree2.get_target_nodes(); + assert_eq!(5, node_list.len()); + if node_list.len() == 5 { + let mut index = 0; + while index < node_list.len() { + assert_eq!(control_nodes[index], node_list[index]); + index += 1; + } + } + let branch = tree2.get_branch(3).expect("Fail"); + assert_eq!("{tl(51),{tl(204a7af8660f2b0bdb92d2ce8b88ab30feb916343228d2e7bd15da02e1f6a31d47ac),tl(2000d134c42fd51c90fa82c6cfdaabd895474d979118525362c0cd236c857e29d9ac)}}", branch.to_str()); + + let empty_nodes: Vec<[u8; 32]> = vec![]; + let mut tree3 = TapBranch::from_string_by_tapscript( + "{{tl(20ac52f50b28cdd4d3bcb7f0d5cb533f232e4c4ef12fbf3e718420b84d4e3c3440ac),{tl(2057bf643684f6c5c75e1cdf45990036502a0d897394013210858cdabcbb95a05aac),tl(51)}},tl(2057bf643684f6c5c75e1cdf45990036502a0d897394013210858cdabcbb95a05aad205bec1a08fa3443176edd0a08e2a64642f45e57543b62bffe43ec350edc33dc22ac)}", + &&script_op_true, + &&empty_nodes, + ).expect("Fail"); + tree3.add_by_tapbranch(&branch).expect("Fail"); + tree3.add_by_tree_string("tl(2008f8280d68e02e807ccffee141c4a6b7ac31d3c283ae0921892d95f691742c44ad20b0f8ce3e1df406514a773414b5d9e5779d8e68ce816e9db39b8e53255ac3b406ac)").expect("Fail"); + assert_eq!(tree2.to_str(), tree3.to_str()); + } } diff --git a/tests/transaction_test.rs b/tests/transaction_test.rs index 63854d1..a30fe83 100644 --- a/tests/transaction_test.rs +++ b/tests/transaction_test.rs @@ -3,9 +3,10 @@ extern crate cfd_rust; #[cfg(test)] mod tests { use cfd_rust::{ - Address, Amount, Descriptor, ExtPrivkey, ExtPubkey, FeeOption, FundTargetOption, - FundTransactionData, HashType, Network, OutPoint, Pubkey, Script, SigHashType, SignParameter, - Transaction, TxInData, TxOutData, UtxoData, + Address, Amount, ByteData, Descriptor, ExtPrivkey, ExtPubkey, FeeOption, FundTargetOption, + FundTransactionData, HashType, Network, OutPoint, Privkey, Pubkey, SchnorrPubkey, SchnorrUtil, + Script, SigHashType, SignParameter, TapBranch, Transaction, TxInData, TxOutData, Txid, + UtxoData, CODE_SEPARATOR_POSITION_FINAL, }; use std::str::FromStr; @@ -601,8 +602,8 @@ mod tests { let fee_data = tx .estimate_fee(&[utxos[1].clone(), utxos[2].clone()], 10.0) .expect("Fail"); - assert_eq!(720, fee_data.txout_fee); - assert_eq!(1800, fee_data.utxo_fee); + assert_eq!(740, fee_data.txout_fee); + assert_eq!(1830, fee_data.utxo_fee); } #[test] @@ -645,10 +646,10 @@ mod tests { .fund_raw_transaction(&[], &utxos, &target_data, &fee_option, &mut fund_data) .expect("Fail"); - assert_eq!("02000000010af4768e14f820cb9063f55833b5999119e53390ecf4bf181842909b11d0974d0000000000ffffffff0380969800000000001600144352a1a6e86311f22274f7ebb2746de21b09b15d00093d00000000001600148beaaac4654cf4ebd8e46ca5062b0e7fb3e7470ce47f19000000000016001478eb9fc2c9e1cdf633ecb646858ba862b21384ab00000000", + assert_eq!("02000000010af4768e14f820cb9063f55833b5999119e53390ecf4bf181842909b11d0974d0000000000ffffffff0380969800000000001600144352a1a6e86311f22274f7ebb2746de21b09b15d00093d00000000001600148beaaac4654cf4ebd8e46ca5062b0e7fb3e7470c947f19000000000016001478eb9fc2c9e1cdf633ecb646858ba862b21384ab00000000", fund_tx.to_str()); assert_eq!(addr1.to_str(), fund_data.reserved_address_list[0].to_str()); - assert_eq!(3860, fund_data.fee_amount); + assert_eq!(3940, fund_data.fee_amount); } #[test] @@ -670,7 +671,7 @@ mod tests { // amount = 85062500 + 39062500 = 124125000 let amount1 = 100000000; let amount2 = 100000000; - let tx = Transaction::create_tx( + let mut tx = Transaction::create_tx( 2, 0, &[ @@ -697,7 +698,7 @@ mod tests { fee_option.dust_fee_rate = -1.0; fee_option.knapsack_min_change = -1; let mut fund_data = FundTransactionData::default(); - let fund_tx = tx + tx = tx .fund_raw_transaction( &input_utxos, &utxos, @@ -707,18 +708,18 @@ mod tests { ) .expect("Fail"); - assert_eq!("02000000030a9a33750a810cd384ca5d93b09513f1eb5d93c669091b29eef710d2391ff7300000000000ffffffff0a9bf51e0ac499391efd9426e2c909901edd74a97d2378b49c8832c491ad1e9e0000000000ffffffff0a503dbd4f8f2b064c70e048b21f93fe4584174478abf5f44747932cd21da87c0000000000ffffffff0300e1f505000000001600144352a1a6e86311f22274f7ebb2746de21b09b15d00e1f505000000001600148beaaac4654cf4ebd8e46ca5062b0e7fb3e7470c0831b8040000000016001478eb9fc2c9e1cdf633ecb646858ba862b21384ab00000000", - fund_tx.to_str()); + assert_eq!("02000000030a9a33750a810cd384ca5d93b09513f1eb5d93c669091b29eef710d2391ff7300000000000ffffffff0a9bf51e0ac499391efd9426e2c909901edd74a97d2378b49c8832c491ad1e9e0000000000ffffffff0a503dbd4f8f2b064c70e048b21f93fe4584174478abf5f44747932cd21da87c0000000000ffffffff0300e1f505000000001600144352a1a6e86311f22274f7ebb2746de21b09b15d00e1f505000000001600148beaaac4654cf4ebd8e46ca5062b0e7fb3e7470c9030b8040000000016001478eb9fc2c9e1cdf633ecb646858ba862b21384ab00000000", + tx.to_str()); assert_eq!(addr1.to_str(), fund_data.reserved_address_list[0].to_str()); - assert_eq!(7460, fund_data.fee_amount); + assert_eq!(7580, fund_data.fee_amount); let fee_utxos = [utxos[1].clone(), utxos[2].clone(), utxos[0].clone()]; - let fee_data = fund_tx + let fee_data = tx .estimate_fee(&fee_utxos, fee_option.fee_rate) .expect("Fail"); - assert_eq!(7460, fee_data.txout_fee + fee_data.utxo_fee); - assert_eq!(2060, fee_data.txout_fee); - assert_eq!(5400, fee_data.utxo_fee); + assert_eq!(7580, fee_data.txout_fee + fee_data.utxo_fee); + assert_eq!(2100, fee_data.txout_fee); + assert_eq!(5480, fee_data.utxo_fee); } #[test] @@ -761,10 +762,10 @@ mod tests { .fund_raw_transaction(&[], &utxos, &target_data, &fee_option, &mut fund_data) .expect("Fail"); - assert_eq!("02000000010af4768e14f820cb9063f55833b5999119e53390ecf4bf181842909b11d0974d0000000000ffffffff0380969800000000001600144352a1a6e86311f22274f7ebb2746de21b09b15d00093d00000000001600148beaaac4654cf4ebd8e46ca5062b0e7fb3e7470ce47f19000000000016001478eb9fc2c9e1cdf633ecb646858ba862b21384ab00000000", + assert_eq!("02000000010af4768e14f820cb9063f55833b5999119e53390ecf4bf181842909b11d0974d0000000000ffffffff0380969800000000001600144352a1a6e86311f22274f7ebb2746de21b09b15d00093d00000000001600148beaaac4654cf4ebd8e46ca5062b0e7fb3e7470c947f19000000000016001478eb9fc2c9e1cdf633ecb646858ba862b21384ab00000000", fund_tx.to_str()); assert_eq!(addr1.to_str(), fund_data.reserved_address_list[0].to_str()); - assert_eq!(3860, fund_data.fee_amount); + assert_eq!(3940, fund_data.fee_amount); } fn get_bitcoin_bnb_utxo_list(network: &Network) -> Vec { @@ -920,7 +921,7 @@ mod tests { let selected_list = Transaction::select_coins(&utxos, 1500, 39059180, &fee_param).expect("Fail"); assert_eq!(1, selected_list.select_utxo_list.len()); - assert_eq!(1360, selected_list.utxo_fee_amount); + assert_eq!(1380, selected_list.utxo_fee_amount); assert_eq!(39062500, selected_list.get_total_amount()); assert_eq!(39062500, selected_list.select_utxo_list[0].amount); } @@ -934,7 +935,7 @@ mod tests { let selected_list = Transaction::select_coins(&utxos, 1500, 119154360, &fee_param).expect("Fail"); assert_eq!(1, selected_list.select_utxo_list.len()); - assert_eq!(1360, selected_list.utxo_fee_amount); + assert_eq!(1380, selected_list.utxo_fee_amount); assert_eq!(156250000, selected_list.get_total_amount()); assert_eq!(156250000, selected_list.select_utxo_list[0].amount); } @@ -948,7 +949,7 @@ mod tests { let selected_list = Transaction::select_coins(&utxos, 1500, 120000000, &fee_param).expect("Fail"); assert_eq!(1, selected_list.select_utxo_list.len()); - assert_eq!(1360, selected_list.utxo_fee_amount); + assert_eq!(1380, selected_list.utxo_fee_amount); assert_eq!(156250000, selected_list.get_total_amount()); assert_eq!(156250000, selected_list.select_utxo_list[0].amount); } @@ -962,7 +963,7 @@ mod tests { let selected_list = Transaction::select_coins(&utxos, 1500, 220000000, &fee_param).expect("Fail"); assert_eq!(2, selected_list.select_utxo_list.len()); - assert_eq!(2720, selected_list.utxo_fee_amount); + assert_eq!(2760, selected_list.utxo_fee_amount); assert_eq!(234375000, selected_list.get_total_amount()); assert_eq!(156250000, selected_list.select_utxo_list[0].amount); assert_eq!(78125000, selected_list.select_utxo_list[1].amount); @@ -977,7 +978,7 @@ mod tests { let selected_list = Transaction::select_coins(&utxos, 1500, 460000000, &fee_param).expect("Fail"); assert_eq!(2, selected_list.select_utxo_list.len()); - assert_eq!(2720, selected_list.utxo_fee_amount); + assert_eq!(2760, selected_list.utxo_fee_amount); assert_eq!(468750000, selected_list.get_total_amount()); assert_eq!(312500000, selected_list.select_utxo_list[0].amount); assert_eq!(156250000, selected_list.select_utxo_list[1].amount); @@ -992,7 +993,7 @@ mod tests { let selected_list = Transaction::select_coins(&utxos, 1500, 468700000, &fee_param).expect("Fail"); assert_eq!(1, selected_list.select_utxo_list.len()); - assert_eq!(1360, selected_list.utxo_fee_amount); + assert_eq!(1380, selected_list.utxo_fee_amount); assert_eq!(1250000000, selected_list.get_total_amount()); assert_eq!(1250000000, selected_list.select_utxo_list[0].amount); } @@ -1007,7 +1008,7 @@ mod tests { let selected_list = Transaction::select_coins(&utxos, 1500, 468700000, &fee_param).expect("Fail"); assert_eq!(2, selected_list.select_utxo_list.len()); - assert_eq!(2720, selected_list.utxo_fee_amount); + assert_eq!(2760, selected_list.utxo_fee_amount); assert_eq!(468750000, selected_list.get_total_amount()); assert_eq!(312500000, selected_list.select_utxo_list[0].amount); assert_eq!(156250000, selected_list.select_utxo_list[1].amount); @@ -1022,7 +1023,7 @@ mod tests { let selected_list = Transaction::select_coins(&utxos, 1500, 99998500, &fee_param).expect("Fail"); assert_eq!(2, selected_list.select_utxo_list.len()); - assert_eq!(360, selected_list.utxo_fee_amount); + assert_eq!(368, selected_list.utxo_fee_amount); assert_eq!(100001090, selected_list.get_total_amount()); assert_eq!(85062500, selected_list.select_utxo_list[0].amount); assert_eq!(14938590, selected_list.select_utxo_list[1].amount); @@ -1037,7 +1038,7 @@ mod tests { let selected_list = Transaction::select_coins(&utxos, 1500, 155060800, &fee_param).expect("Fail"); assert_eq!(1, selected_list.select_utxo_list.len()); - assert_eq!(180, selected_list.utxo_fee_amount); + assert_eq!(184, selected_list.utxo_fee_amount); assert_eq!(155062500, selected_list.get_total_amount()); assert_eq!(155062500, selected_list.select_utxo_list[0].amount); } @@ -1051,7 +1052,7 @@ mod tests { let selected_list = Transaction::select_coins(&utxos, 1500, 114040000, &fee_param).expect("Fail"); assert_eq!(3, selected_list.select_utxo_list.len()); - assert_eq!(270, selected_list.utxo_fee_amount); + assert_eq!(276, selected_list.utxo_fee_amount); assert_eq!(115063590, selected_list.get_total_amount()); assert_eq!(61062500, selected_list.select_utxo_list[0].amount); assert_eq!(39062500, selected_list.select_utxo_list[1].amount); @@ -1090,4 +1091,239 @@ mod tests { err_msg ); } + + #[test] + fn taproot_schnorr_sign_test01() { + let privkey = + Privkey::from_str("305e293b010d29bf3c888b617763a438fee9054c8cab66eb12ad078f819d9f27") + .expect("Fail"); + let (pubkey, _) = SchnorrPubkey::from_privkey(&privkey).expect("Fail"); + assert_eq!( + "1777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb", + pubkey.to_hex() + ); + + let addr = Address::taproot(&pubkey, &Network::Testnet).expect("Fail"); + assert_eq!( + "tb1pzamhq9jglfxaj0r5ahvatr8uc77u973s5tm04yytdltsey5r8naskf8ee6", + addr.to_str() + ); + + let tx_hex = "020000000116d975e4c2cea30f72f4f5fe528f5a0727d9ea149892a50c030d44423088ea2f0000000000ffffffff0130f1029500000000160014164e985d0fc92c927a66c0cbaf78e6ea389629d500000000"; + + let utxos = [UtxoData::from_locking_script( + &OutPoint::new( + &Txid::from_str("2fea883042440d030ca5929814ead927075a8f52fef5f4720fa3cec2e475d916") + .expect("Fail"), + 0, + ), + 2499999000, + addr.get_locking_script(), + &Network::Mainnet, + ) + .expect("Fail")]; + let outpoint = utxos[0].outpoint.clone(); + + let mut tx = Transaction::from_str(tx_hex).expect("Fail"); + let fee_data = tx.estimate_fee(&utxos, 2.0).expect("Fail"); + assert_eq!(202, fee_data.get_total_fee()); + + tx = tx.append_utxo_list(&utxos).expect("Fail"); + + let sighash_type = SigHashType::All; + let annex = vec![]; + let sighash = tx + .get_sighash_by_schnorr_pubkey(&outpoint, &pubkey, &sighash_type, &annex) + .expect("Fail"); + let sighash_bytes = ByteData::from_slice(&sighash); + assert_eq!( + "e5b11ddceab1e4fc49a8132ae589a39b07acf49cabb2b0fbf6104bc31da12c02", + sighash_bytes.to_hex() + ); + + let util = SchnorrUtil::new(); + let aux_rand = ByteData::default(); + let signature = util + .sign(&sighash_bytes, &privkey, &aux_rand) + .expect("Fail"); + let sig = signature.get_sign_parameter(&sighash_type); + assert_eq!("61f75636003a870b7a1685abae84eedf8c9527227ac70183c376f7b3a35b07ebcbea14749e58ce1a87565b035b2f3963baa5ae3ede95e89fd607ab7849f20872", sig.to_hex()); + + tx = tx + .add_taproot_signature(&outpoint, &sig, &annex) + .expect("Fail"); + assert_eq!("0200000000010116d975e4c2cea30f72f4f5fe528f5a0727d9ea149892a50c030d44423088ea2f0000000000ffffffff0130f1029500000000160014164e985d0fc92c927a66c0cbaf78e6ea389629d5014161f75636003a870b7a1685abae84eedf8c9527227ac70183c376f7b3a35b07ebcbea14749e58ce1a87565b035b2f3963baa5ae3ede95e89fd607ab7849f208720100000000", tx.to_str()); + + tx.verify_sign_by_utxo_list(&outpoint).expect("Fail"); + + // verify signature + let is_verify = util + .verify(&signature, &sighash_bytes, &pubkey) + .expect("Fail"); + assert_eq!(true, is_verify); + } + + #[test] + fn taproot_schnorr_sign_test02() { + let privkey = + Privkey::from_str("305e293b010d29bf3c888b617763a438fee9054c8cab66eb12ad078f819d9f27") + .expect("Fail"); + let (pubkey, _) = SchnorrPubkey::from_privkey(&privkey).expect("Fail"); + assert_eq!( + "1777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb", + pubkey.to_hex() + ); + + let tx_hex = "020000000116d975e4c2cea30f72f4f5fe528f5a0727d9ea149892a50c030d44423088ea2f0000000000ffffffff0130f1029500000000160014164e985d0fc92c927a66c0cbaf78e6ea389629d500000000"; + + let addr = Address::taproot(&pubkey, &Network::Testnet).expect("Fail"); + assert_eq!( + "tb1pzamhq9jglfxaj0r5ahvatr8uc77u973s5tm04yytdltsey5r8naskf8ee6", + addr.to_str() + ); + + let utxos = [UtxoData::from_locking_script( + &OutPoint::new( + &Txid::from_str("2fea883042440d030ca5929814ead927075a8f52fef5f4720fa3cec2e475d916") + .expect("Fail"), + 0, + ), + 2499999000, + addr.get_locking_script(), + &Network::Mainnet, + ) + .expect("Fail")]; + let outpoint = utxos[0].outpoint.clone(); + + let mut tx = Transaction::from_str(tx_hex).expect("Fail"); + + tx = tx.append_utxo_list(&utxos).expect("Fail"); + + let sighash_type = SigHashType::All; + tx = tx + .sign_with_privkey_by_utxo_list(&outpoint, &privkey, &sighash_type, &[], &[]) + .expect("Fail"); + assert_eq!("0200000000010116d975e4c2cea30f72f4f5fe528f5a0727d9ea149892a50c030d44423088ea2f0000000000ffffffff0130f1029500000000160014164e985d0fc92c927a66c0cbaf78e6ea389629d5014161f75636003a870b7a1685abae84eedf8c9527227ac70183c376f7b3a35b07ebcbea14749e58ce1a87565b035b2f3963baa5ae3ede95e89fd607ab7849f208720100000000", tx.to_str()); + + tx.verify_sign_by_utxo_list(&outpoint).expect("Fail"); + } + + #[test] + fn tapscript_sign_test01() { + let privkey = + Privkey::from_str("305e293b010d29bf3c888b617763a438fee9054c8cab66eb12ad078f819d9f27") + .expect("Fail"); + let (pubkey, _) = SchnorrPubkey::from_privkey(&privkey).expect("Fail"); + assert_eq!( + "1777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb", + pubkey.to_hex() + ); + + let script_checksig = + Script::from_asm(&format!("{} OP_CHECKSIG", pubkey.to_hex())).expect("Fail"); + + let mut tree = TapBranch::from_tapscript(&script_checksig).expect("Fail"); + tree + .add_by_tapbranch_hash( + &ByteData::from_str("4d18084bb47027f47d428b2ed67e1ccace5520fdc36f308e272394e288d53b6d") + .expect("Fail") + .to_32byte_array(), + ) + .expect("Fail"); + tree + .add_by_tapbranch_hash( + &ByteData::from_str("dc82121e4ff8d23745f3859e8939ecb0a38af63e6ddea2fff97a7fd61a1d2d54") + .expect("Fail") + .to_32byte_array(), + ) + .expect("Fail"); + + let (tweaked_pubkey, addr, control_block) = tree + .get_tweaked_pubkey(&pubkey, &Network::Mainnet) + .expect("Fail"); + assert_eq!( + "3dee5a5387a2b57902f3a6e9da077726d19c6cc8c8c7b04bcf5a197b2a9b01d2", + tweaked_pubkey.to_hex() + ); + assert_eq!( + "bc1p8hh955u8526hjqhn5m5a5pmhymgecmxgerrmqj70tgvhk25mq8fqw77n40", + addr.to_str() + ); + assert_eq!( + "c01777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb4d18084bb47027f47d428b2ed67e1ccace5520fdc36f308e272394e288d53b6ddc82121e4ff8d23745f3859e8939ecb0a38af63e6ddea2fff97a7fd61a1d2d54", + control_block.to_hex()); + let tapleaf_hash = tree.get_tapleaf_hash().expect("Fail"); + assert_eq!( + "dfc43ba9fc5f8a9e1b6d6a50600c704bb9e41b741d9ed6de6559a53d2f38e513", + ByteData::from_slice(&tapleaf_hash).to_hex() + ); + let tweaked_privkey = tree.get_tweaked_privkey(&privkey).expect("Fail"); + assert_eq!( + "a7d17bee0b6313cf864a1ac6f203aafd74a40703ffc050f66517e4f83ff41a03", + tweaked_privkey.to_hex() + ); + + let tx_hex = "02000000015b80a1af0e00c700bee9c8e4442bec933fcdc0c686dac2dc336caaaf186c5d190000000000ffffffff0130f1029500000000160014164e985d0fc92c927a66c0cbaf78e6ea389629d500000000"; + let utxos = [UtxoData::from_locking_script( + &OutPoint::new( + &Txid::from_str("195d6c18afaa6c33dcc2da86c6c0cd3f93ec2b44e4c8e9be00c7000eafa1805b") + .expect("Fail"), + 0, + ), + 2499999000, + addr.get_locking_script(), + &Network::Mainnet, + ) + .expect("Fail")]; + let outpoint = utxos[0].outpoint.clone(); + + let mut tx = Transaction::from_str(tx_hex).expect("Fail"); + let fee_data = tx.estimate_fee(&utxos, 2.0).expect("Fail"); + assert_eq!(202, fee_data.get_total_fee()); + + tx = tx.append_utxo_list(&utxos).expect("Fail"); + + let sighash_type = SigHashType::All; + let annex = vec![]; + let sighash = tx + .get_sighash_by_tapscript( + &outpoint, + &tapleaf_hash, + &sighash_type, + CODE_SEPARATOR_POSITION_FINAL, + &annex, + ) + .expect("Fail"); + let sighash_bytes = ByteData::from_slice(&sighash); + assert_eq!( + "80e53eaee13048aee9c6c13fa5a8529aad7fe2c362bfc16f1e2affc71f591d36", + sighash_bytes.to_hex() + ); + + let util = SchnorrUtil::new(); + let aux_rand = ByteData::default(); + let signature = util + .sign(&sighash_bytes, &privkey, &aux_rand) + .expect("Fail"); + let sig = signature.get_sign_parameter(&sighash_type); + assert_eq!("f5aa6b260f9df687786cd3813ba83b476e195041bccea800f2571212f4aae9848a538b6175a4f8ea291d38e351ea7f612a3d700dca63cd3aff05d315c5698ee9", sig.to_hex()); + + tx = tx + .add_tapscript_sign(&outpoint, &[sig], &&script_checksig, &control_block, &annex) + .expect("Fail"); + assert_eq!("020000000001015b80a1af0e00c700bee9c8e4442bec933fcdc0c686dac2dc336caaaf186c5d190000000000ffffffff0130f1029500000000160014164e985d0fc92c927a66c0cbaf78e6ea389629d50341f5aa6b260f9df687786cd3813ba83b476e195041bccea800f2571212f4aae9848a538b6175a4f8ea291d38e351ea7f612a3d700dca63cd3aff05d315c5698ee90122201777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfbac61c01777701648fa4dd93c74edd9d58cfcc7bdc2fa30a2f6fa908b6fd70c92833cfb4d18084bb47027f47d428b2ed67e1ccace5520fdc36f308e272394e288d53b6ddc82121e4ff8d23745f3859e8939ecb0a38af63e6ddea2fff97a7fd61a1d2d5400000000", tx.to_str()); + + let err_info = tx.verify_sign_by_utxo_list(&outpoint).expect_err("Fail"); + assert_eq!( + "[IllegalState]: The script analysis of tapscript is not supported.", + err_info.to_string() + ); + // The script analysis of tapscript is not supported. + + // verify signature + let is_verify = util + .verify(&signature, &sighash_bytes, &pubkey) + .expect("Fail"); + assert_eq!(true, is_verify); + } }