aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.appveyor.yml2
-rwxr-xr-xci/test/00_setup_env_arm.sh2
-rwxr-xr-xci/test/00_setup_env_i686_centos.sh2
-rwxr-xr-xci/test/00_setup_env_mac.sh2
-rwxr-xr-xci/test/00_setup_env_mac_host.sh2
-rwxr-xr-xci/test/00_setup_env_native_asan.sh2
-rwxr-xr-xci/test/00_setup_env_native_fuzz.sh2
-rwxr-xr-xci/test/00_setup_env_native_multiprocess.sh2
-rwxr-xr-xci/test/00_setup_env_native_nowallet.sh2
-rwxr-xr-xci/test/00_setup_env_native_qt5.sh2
-rwxr-xr-xci/test/00_setup_env_native_tsan.sh2
-rwxr-xr-xci/test/00_setup_env_s390x.sh2
-rw-r--r--configure.ac24
-rwxr-xr-xcontrib/devtools/symbol-check.py27
-rwxr-xr-xcontrib/devtools/test-symbol-check.py35
-rw-r--r--contrib/gitian-descriptors/gitian-linux.yml2
-rw-r--r--contrib/gitian-descriptors/gitian-osx.yml2
-rw-r--r--contrib/gitian-descriptors/gitian-win.yml2
-rwxr-xr-xcontrib/guix/guix-attest169
-rwxr-xr-xcontrib/guix/guix-verify109
-rwxr-xr-xcontrib/guix/libexec/build.sh23
-rwxr-xr-xcontrib/guix/libexec/codesign.sh15
-rw-r--r--contrib/guix/manifest.scm6
-rw-r--r--contrib/signet/README.md33
-rwxr-xr-xcontrib/signet/miner24
-rw-r--r--src/Makefile.test.include6
-rw-r--r--src/addrdb.cpp22
-rw-r--r--src/bitcoin-tx.cpp3
-rw-r--r--src/bitcoin-util.cpp99
-rw-r--r--src/core_read.cpp1
-rw-r--r--src/external_signer.cpp8
-rw-r--r--src/external_signer.h16
-rw-r--r--src/interfaces/node.h2
-rw-r--r--src/key.cpp21
-rw-r--r--src/key.h12
-rw-r--r--src/net.cpp6
-rw-r--r--src/node/interfaces.cpp13
-rw-r--r--src/node/psbt.cpp6
-rw-r--r--src/protocol.h83
-rw-r--r--src/psbt.cpp31
-rw-r--r--src/psbt.h11
-rw-r--r--src/pubkey.cpp4
-rw-r--r--src/pubkey.h10
-rw-r--r--src/qt/addressbookpage.cpp8
-rw-r--r--src/qt/coincontroldialog.cpp12
-rw-r--r--src/qt/createwalletdialog.cpp7
-rw-r--r--src/qt/createwalletdialog.h9
-rw-r--r--src/qt/optionsdialog.cpp5
-rw-r--r--src/qt/qrimagewidget.cpp4
-rw-r--r--src/qt/receivecoinsdialog.cpp10
-rw-r--r--src/qt/receiverequestdialog.cpp2
-rw-r--r--src/qt/rpcconsole.cpp12
-rw-r--r--src/qt/sendcoinsdialog.cpp4
-rw-r--r--src/qt/transactionview.cpp20
-rw-r--r--src/qt/walletcontroller.cpp3
-rw-r--r--src/rpc/rawtransaction.cpp6
-rw-r--r--src/script/descriptor.cpp4
-rw-r--r--src/script/interpreter.cpp14
-rw-r--r--src/script/interpreter.h5
-rw-r--r--src/script/sign.cpp195
-rw-r--r--src/script/sign.h9
-rw-r--r--src/script/signingprovider.cpp13
-rw-r--r--src/script/signingprovider.h4
-rw-r--r--src/script/standard.cpp57
-rw-r--r--src/script/standard.h46
-rw-r--r--src/test/fuzz/base_encode_decode.cpp7
-rw-r--r--src/test/fuzz/crypto.cpp6
-rw-r--r--src/test/fuzz/deserialize.cpp47
-rw-r--r--src/test/fuzz/process_message.cpp12
-rw-r--r--src/test/key_tests.cpp42
-rw-r--r--src/util/system.cpp6
-rw-r--r--src/util/system.h2
-rw-r--r--src/wallet/external_signer_scriptpubkeyman.cpp8
-rw-r--r--src/wallet/external_signer_scriptpubkeyman.h5
-rw-r--r--src/wallet/rpcwallet.cpp12
-rw-r--r--src/wallet/scriptpubkeyman.cpp9
-rw-r--r--src/wallet/scriptpubkeyman.h6
-rw-r--r--src/wallet/test/psbt_wallet_tests.cpp2
-rw-r--r--src/wallet/wallet.cpp18
-rw-r--r--src/wallet/wallet.h1
-rwxr-xr-xtest/functional/feature_block.py21
-rwxr-xr-xtest/functional/feature_rbf.py170
-rwxr-xr-xtest/functional/p2p_invalid_block.py19
-rwxr-xr-xtest/functional/test_framework/messages.py2
-rw-r--r--test/functional/test_framework/wallet.py10
-rwxr-xr-xtest/functional/test_runner.py19
-rwxr-xr-xtest/functional/wallet_taproot.py126
87 files changed, 1297 insertions, 551 deletions
diff --git a/.appveyor.yml b/.appveyor.yml
index 131b69e4c3..3ca7818eca 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -1,6 +1,6 @@
version: '{branch}.{build}'
skip_tags: true
-image: Visual Studio 2019 Preview
+image: Visual Studio 2019
configuration: Release
platform: x64
clone_depth: 5
diff --git a/ci/test/00_setup_env_arm.sh b/ci/test/00_setup_env_arm.sh
index 07f099b85c..8d2b70e549 100755
--- a/ci/test/00_setup_env_arm.sh
+++ b/ci/test/00_setup_env_arm.sh
@@ -25,4 +25,4 @@ export RUN_FUNCTIONAL_TESTS=false
export GOAL="install"
# -Wno-psabi is to disable ABI warnings: "note: parameter passing for argument of type ... changed in GCC 7.1"
# This could be removed once the ABI change warning does not show up by default
-export BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports CXXFLAGS=-Wno-psabi --enable-external-signer"
+export BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports CXXFLAGS=-Wno-psabi"
diff --git a/ci/test/00_setup_env_i686_centos.sh b/ci/test/00_setup_env_i686_centos.sh
index 05c724fc0b..2ddb932907 100755
--- a/ci/test/00_setup_env_i686_centos.sh
+++ b/ci/test/00_setup_env_i686_centos.sh
@@ -11,6 +11,6 @@ export CONTAINER_NAME=ci_i686_centos_8
export DOCKER_NAME_TAG=centos:8
export DOCKER_PACKAGES="gcc-c++ glibc-devel.x86_64 libstdc++-devel.x86_64 glibc-devel.i686 libstdc++-devel.i686 ccache libtool make git python3 python3-zmq which patch lbzip2 dash rsync coreutils bison"
export GOAL="install"
-export BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-reduce-exports --enable-external-signer"
+export BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-reduce-exports"
export CONFIG_SHELL="/bin/dash"
export TEST_RUNNER_ENV="LC_ALL=en_US.UTF-8"
diff --git a/ci/test/00_setup_env_mac.sh b/ci/test/00_setup_env_mac.sh
index 196394e908..73ac09c1de 100755
--- a/ci/test/00_setup_env_mac.sh
+++ b/ci/test/00_setup_env_mac.sh
@@ -15,4 +15,4 @@ export XCODE_BUILD_ID=12A7403
export RUN_UNIT_TESTS=false
export RUN_FUNCTIONAL_TESTS=false
export GOAL="deploy"
-export BITCOIN_CONFIG="--with-gui --enable-reduce-exports --enable-external-signer"
+export BITCOIN_CONFIG="--with-gui --enable-reduce-exports"
diff --git a/ci/test/00_setup_env_mac_host.sh b/ci/test/00_setup_env_mac_host.sh
index 898c1530a1..c0d951a041 100755
--- a/ci/test/00_setup_env_mac_host.sh
+++ b/ci/test/00_setup_env_mac_host.sh
@@ -9,7 +9,7 @@ export LC_ALL=C.UTF-8
export HOST=x86_64-apple-darwin18
export PIP_PACKAGES="zmq lief"
export GOAL="install"
-export BITCOIN_CONFIG="--with-gui --enable-reduce-exports --enable-external-signer"
+export BITCOIN_CONFIG="--with-gui --enable-reduce-exports"
export CI_OS_NAME="macos"
export NO_DEPENDS=1
export OSX_SDK=""
diff --git a/ci/test/00_setup_env_native_asan.sh b/ci/test/00_setup_env_native_asan.sh
index 92af98aa9b..ab185b6e71 100755
--- a/ci/test/00_setup_env_native_asan.sh
+++ b/ci/test/00_setup_env_native_asan.sh
@@ -11,4 +11,4 @@ export PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libevent-
export DOCKER_NAME_TAG=ubuntu:hirsute
export NO_DEPENDS=1
export GOAL="install"
-export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' --with-sanitizers=address,integer,undefined CC=clang CXX=clang++ --enable-external-signer"
+export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' --with-sanitizers=address,integer,undefined CC=clang CXX=clang++"
diff --git a/ci/test/00_setup_env_native_fuzz.sh b/ci/test/00_setup_env_native_fuzz.sh
index bedd0cf9aa..58388fa928 100755
--- a/ci/test/00_setup_env_native_fuzz.sh
+++ b/ci/test/00_setup_env_native_fuzz.sh
@@ -14,5 +14,5 @@ export RUN_UNIT_TESTS=false
export RUN_FUNCTIONAL_TESTS=false
export RUN_FUZZ_TESTS=true
export GOAL="install"
-export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer,address,undefined,integer CC=clang CXX=clang++ --enable-external-signer"
+export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer,address,undefined,integer CC=clang CXX=clang++"
export CCACHE_SIZE=200M
diff --git a/ci/test/00_setup_env_native_multiprocess.sh b/ci/test/00_setup_env_native_multiprocess.sh
index 37d714400b..1418dfbc51 100755
--- a/ci/test/00_setup_env_native_multiprocess.sh
+++ b/ci/test/00_setup_env_native_multiprocess.sh
@@ -11,7 +11,7 @@ export DOCKER_NAME_TAG=ubuntu:20.04
export PACKAGES="cmake python3 python3-pip llvm clang"
export DEP_OPTS="DEBUG=1 MULTIPROCESS=1"
export GOAL="install"
-export BITCOIN_CONFIG="--enable-external-signer --enable-debug CC=clang CXX=clang++" # Use clang to avoid OOM
+export BITCOIN_CONFIG="--enable-debug CC=clang CXX=clang++" # Use clang to avoid OOM
export TEST_RUNNER_ENV="BITCOIND=bitcoin-node"
export RUN_SECURITY_TESTS="true"
export PIP_PACKAGES="lief"
diff --git a/ci/test/00_setup_env_native_nowallet.sh b/ci/test/00_setup_env_native_nowallet.sh
index a496b5af6e..d167c9198a 100755
--- a/ci/test/00_setup_env_native_nowallet.sh
+++ b/ci/test/00_setup_env_native_nowallet.sh
@@ -11,4 +11,4 @@ export DOCKER_NAME_TAG=ubuntu:18.04 # Use bionic to have one config run the tes
export PACKAGES="python3-zmq clang-5.0 llvm-5.0" # Use clang-5 to test C++17 compatibility, see doc/dependencies.md
export DEP_OPTS="NO_WALLET=1"
export GOAL="install"
-export BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports CC=clang-5.0 CXX=clang++-5.0 --enable-external-signer"
+export BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports CC=clang-5.0 CXX=clang++-5.0"
diff --git a/ci/test/00_setup_env_native_qt5.sh b/ci/test/00_setup_env_native_qt5.sh
index 61948ab221..9c57eba62a 100755
--- a/ci/test/00_setup_env_native_qt5.sh
+++ b/ci/test/00_setup_env_native_qt5.sh
@@ -16,4 +16,4 @@ export RUN_UNIT_TESTS="false"
export GOAL="install"
export PREVIOUS_RELEASES_TO_DOWNLOAD="v0.15.2 v0.16.3 v0.17.2 v0.18.1 v0.19.1"
export BITCOIN_CONFIG="--enable-zmq --with-libs=no --with-gui=qt5 --enable-glibc-back-compat --enable-reduce-exports
---enable-debug --disable-fuzz-binary CFLAGS=\"-g0 -O2 -funsigned-char\" CXXFLAGS=\"-g0 -O2 -funsigned-char\" --enable-external-signer"
+--enable-debug --disable-fuzz-binary CFLAGS=\"-g0 -O2 -funsigned-char\" CXXFLAGS=\"-g0 -O2 -funsigned-char\""
diff --git a/ci/test/00_setup_env_native_tsan.sh b/ci/test/00_setup_env_native_tsan.sh
index 33f63fa9ba..a5082bdaab 100755
--- a/ci/test/00_setup_env_native_tsan.sh
+++ b/ci/test/00_setup_env_native_tsan.sh
@@ -11,4 +11,4 @@ export DOCKER_NAME_TAG=ubuntu:hirsute
export PACKAGES="clang llvm libc++abi-dev libc++-dev python3-zmq"
export DEP_OPTS="CC=clang CXX='clang++ -stdlib=libc++'"
export GOAL="install"
-export BITCOIN_CONFIG="--enable-zmq --with-gui=no CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' CXXFLAGS='-g' --with-sanitizers=thread CC=clang CXX='clang++ -stdlib=libc++' --enable-external-signer"
+export BITCOIN_CONFIG="--enable-zmq --with-gui=no CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' CXXFLAGS='-g' --with-sanitizers=thread CC=clang CXX='clang++ -stdlib=libc++'"
diff --git a/ci/test/00_setup_env_s390x.sh b/ci/test/00_setup_env_s390x.sh
index 88b431f3c7..51a0fd9117 100755
--- a/ci/test/00_setup_env_s390x.sh
+++ b/ci/test/00_setup_env_s390x.sh
@@ -23,4 +23,4 @@ export RUN_UNIT_TESTS=true
export TEST_RUNNER_ENV="LC_ALL=C"
export RUN_FUNCTIONAL_TESTS=true
export GOAL="install"
-export BITCOIN_CONFIG="--enable-reduce-exports --with-incompatible-bdb --enable-external-signer"
+export BITCOIN_CONFIG="--enable-reduce-exports --with-incompatible-bdb"
diff --git a/configure.ac b/configure.ac
index aaa932b093..8cfa2d39e1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -333,9 +333,9 @@ AC_ARG_ENABLE([werror],
[enable_werror=no])
AC_ARG_ENABLE([external-signer],
- [AS_HELP_STRING([--enable-external-signer],[compile external signer support (default is no, requires Boost::Process)])],
+ [AS_HELP_STRING([--enable-external-signer],[compile external signer support (default is yes, requires Boost::Process)])],
[use_external_signer=$enableval],
- [use_external_signer=no])
+ [use_external_signer=yes])
AC_LANG_PUSH([C++])
@@ -475,7 +475,9 @@ if test "x$CXXFLAGS_overridden" = "xno"; then
AX_CHECK_COMPILE_FLAG([-Wself-assign],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-self-assign"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wunused-local-typedef],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-unused-local-typedef"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wimplicit-fallthrough],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-implicit-fallthrough"],,[[$CXXFLAG_WERROR]])
- AX_CHECK_COMPILE_FLAG([-Wdeprecated-copy],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-deprecated-copy"],,[[$CXXFLAG_WERROR]])
+ if test x$suppress_external_warnings != xyes ; then
+ AX_CHECK_COMPILE_FLAG([-Wdeprecated-copy],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-deprecated-copy"],,[[$CXXFLAG_WERROR]])
+ fi
fi
dnl Don't allow extended (non-ASCII) symbols in identifiers. This is easier for code review.
@@ -1357,13 +1359,15 @@ if test x$enable_wallet != xno; then
fi
if test x$use_ebpf != xno; then
- AC_CHECK_HEADER([sys/sdt.h], [have_sdt=yes], [have_sdt=no])
-else
- have_sdt=no
-fi
-
-if test x$have_sdt = xyes; then
- AC_DEFINE([ENABLE_TRACING], [1], [Define to 1 to enable eBPF user static defined tracepoints])
+ AC_MSG_CHECKING([whether eBPF tracepoints are supported])
+ AC_COMPILE_IFELSE([
+ AC_LANG_PROGRAM(
+ [#include <sys/sdt.h>],
+ [DTRACE_PROBE("context", "event");]
+ )],
+ [AC_MSG_RESULT(yes); have_sdt=yes; AC_DEFINE([ENABLE_TRACING], [1], [Define to 1 to enable eBPF user static defined tracepoints])],
+ [AC_MSG_RESULT(no); have_sdt=no;]
+ )
fi
dnl Check for libminiupnpc (optional)
diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py
index 3679c4b5db..56e4313d78 100755
--- a/contrib/devtools/symbol-check.py
+++ b/contrib/devtools/symbol-check.py
@@ -215,6 +215,18 @@ def check_MACHO_libraries(filename) -> bool:
ok = False
return ok
+def check_MACHO_min_os(filename) -> bool:
+ binary = lief.parse(filename)
+ if binary.build_version.minos == [10,14,0]:
+ return True
+ return False
+
+def check_MACHO_sdk(filename) -> bool:
+ binary = lief.parse(filename)
+ if binary.build_version.sdk == [10, 15, 6]:
+ return True
+ return False
+
def check_PE_libraries(filename) -> bool:
ok: bool = True
binary = lief.parse(filename)
@@ -224,6 +236,14 @@ def check_PE_libraries(filename) -> bool:
ok = False
return ok
+def check_PE_subsystem_version(filename) -> bool:
+ binary = lief.parse(filename)
+ major: int = binary.optional_header.major_subsystem_version
+ minor: int = binary.optional_header.minor_subsystem_version
+ if major == 6 and minor == 1:
+ return True
+ return False
+
CHECKS = {
'ELF': [
('IMPORTED_SYMBOLS', check_imported_symbols),
@@ -231,10 +251,13 @@ CHECKS = {
('LIBRARY_DEPENDENCIES', check_ELF_libraries)
],
'MACHO': [
- ('DYNAMIC_LIBRARIES', check_MACHO_libraries)
+ ('DYNAMIC_LIBRARIES', check_MACHO_libraries),
+ ('MIN_OS', check_MACHO_min_os),
+ ('SDK', check_MACHO_sdk),
],
'PE' : [
- ('DYNAMIC_LIBRARIES', check_PE_libraries)
+ ('DYNAMIC_LIBRARIES', check_PE_libraries),
+ ('SUBSYSTEM_VERSION', check_PE_subsystem_version),
]
}
diff --git a/contrib/devtools/test-symbol-check.py b/contrib/devtools/test-symbol-check.py
index 106dfd2c5a..6ce2fa3560 100755
--- a/contrib/devtools/test-symbol-check.py
+++ b/contrib/devtools/test-symbol-check.py
@@ -98,7 +98,7 @@ class TestSymbolChecks(unittest.TestCase):
self.assertEqual(call_symbol_check(cc, source, executable, ['-lexpat']),
(1, 'libexpat.1.dylib is not in ALLOWED_LIBRARIES!\n' +
- executable + ': failed DYNAMIC_LIBRARIES'))
+ f'{executable}: failed DYNAMIC_LIBRARIES MIN_OS SDK'))
source = 'test2.c'
executable = 'test2'
@@ -114,7 +114,20 @@ class TestSymbolChecks(unittest.TestCase):
''')
self.assertEqual(call_symbol_check(cc, source, executable, ['-framework', 'CoreGraphics']),
- (0, ''))
+ (1, f'{executable}: failed MIN_OS SDK'))
+
+ source = 'test3.c'
+ executable = 'test3'
+ with open(source, 'w', encoding="utf8") as f:
+ f.write('''
+ int main()
+ {
+ return 0;
+ }
+ ''')
+
+ self.assertEqual(call_symbol_check(cc, source, executable, ['-mmacosx-version-min=10.14']),
+ (1, f'{executable}: failed SDK'))
def test_PE(self):
source = 'test1.c'
@@ -132,12 +145,26 @@ class TestSymbolChecks(unittest.TestCase):
}
''')
- self.assertEqual(call_symbol_check(cc, source, executable, ['-lpdh']),
+ self.assertEqual(call_symbol_check(cc, source, executable, ['-lpdh', '-Wl,--major-subsystem-version', '-Wl,6', '-Wl,--minor-subsystem-version', '-Wl,1']),
(1, 'pdh.dll is not in ALLOWED_LIBRARIES!\n' +
executable + ': failed DYNAMIC_LIBRARIES'))
source = 'test2.c'
executable = 'test2.exe'
+
+ with open(source, 'w', encoding="utf8") as f:
+ f.write('''
+ int main()
+ {
+ return 0;
+ }
+ ''')
+
+ self.assertEqual(call_symbol_check(cc, source, executable, ['-Wl,--major-subsystem-version', '-Wl,9', '-Wl,--minor-subsystem-version', '-Wl,9']),
+ (1, executable + ': failed SUBSYSTEM_VERSION'))
+
+ source = 'test3.c'
+ executable = 'test3.exe'
with open(source, 'w', encoding="utf8") as f:
f.write('''
#include <windows.h>
@@ -149,7 +176,7 @@ class TestSymbolChecks(unittest.TestCase):
}
''')
- self.assertEqual(call_symbol_check(cc, source, executable, ['-lole32']),
+ self.assertEqual(call_symbol_check(cc, source, executable, ['-lole32', '-Wl,--major-subsystem-version', '-Wl,6', '-Wl,--minor-subsystem-version', '-Wl,1']),
(0, ''))
diff --git a/contrib/gitian-descriptors/gitian-linux.yml b/contrib/gitian-descriptors/gitian-linux.yml
index bed3531720..e6dce7a8c6 100644
--- a/contrib/gitian-descriptors/gitian-linux.yml
+++ b/contrib/gitian-descriptors/gitian-linux.yml
@@ -99,7 +99,7 @@ script: |
done
}
- pip3 install lief==0.11.4
+ pip3 install lief==0.11.5
# Faketime for depends so intermediate results are comparable
export PATH_orig=${PATH}
diff --git a/contrib/gitian-descriptors/gitian-osx.yml b/contrib/gitian-descriptors/gitian-osx.yml
index 1d4506e3c2..a39618adb7 100644
--- a/contrib/gitian-descriptors/gitian-osx.yml
+++ b/contrib/gitian-descriptors/gitian-osx.yml
@@ -78,7 +78,7 @@ script: |
done
}
- pip3 install lief==0.11.4
+ pip3 install lief==0.11.5
# Faketime for depends so intermediate results are comparable
export PATH_orig=${PATH}
diff --git a/contrib/gitian-descriptors/gitian-win.yml b/contrib/gitian-descriptors/gitian-win.yml
index 03eba71366..ffe228a032 100644
--- a/contrib/gitian-descriptors/gitian-win.yml
+++ b/contrib/gitian-descriptors/gitian-win.yml
@@ -86,7 +86,7 @@ script: |
done
}
- pip3 install lief==0.11.4
+ pip3 install lief==0.11.5
# Faketime for depends so intermediate results are comparable
export PATH_orig=${PATH}
diff --git a/contrib/guix/guix-attest b/contrib/guix/guix-attest
index 081d1c0465..c8cf73d400 100755
--- a/contrib/guix/guix-attest
+++ b/contrib/guix/guix-attest
@@ -99,24 +99,34 @@ fi
# We should be able to find at least one output
################
-echo "Looking for build output directories in ${OUTDIR_BASE}"
+echo "Looking for build output SHA256SUMS fragments in ${OUTDIR_BASE}"
shopt -s nullglob
-OUTDIRS=( "${OUTDIR_BASE}"/* ) # This expands to an array of directories...
+sha256sum_fragments=( "$OUTDIR_BASE"/*/SHA256SUMS.part ) # This expands to an array of directories...
shopt -u nullglob
-if (( ${#OUTDIRS[@]} )); then
- echo "Found build output directories:"
- for outdir in "${OUTDIRS[@]}"; do
+noncodesigned_fragments=()
+codesigned_fragments=()
+
+if (( ${#sha256sum_fragments[@]} )); then
+ echo "Found build output SHA256SUMS fragments:"
+ for outdir in "${sha256sum_fragments[@]}"; do
echo " '$outdir'"
+ case "$outdir" in
+ "$OUTDIR_BASE"/*-codesigned/SHA256SUMS.part)
+ codesigned_fragments+=("$outdir")
+ ;;
+ *)
+ noncodesigned_fragments+=("$outdir")
+ ;;
+ esac
done
echo
else
- echo "ERR: Could not find any build output directories in ${OUTDIR_BASE}"
+ echo "ERR: Could not find any build output SHA256SUMS fragments in ${OUTDIR_BASE}"
exit 1
fi
-
##############
## Attest ##
##############
@@ -126,82 +136,105 @@ fi
# HOST: The output directory being attested
#
out_name() {
- basename "$1"
+ basename "$(dirname "$1")"
}
-# Usage: out_sig_dir $outdir
-#
-# outdir: The output directory being attested
-#
-out_sig_dir() {
- echo "$GUIX_SIGS_REPO/$VERSION/$(out_name "$1")/$signer_name"
-}
+shasum_already_exists() {
+cat <<EOF
+--
-# Accumulate a list of signature directories that already exist...
-outdirs_already_attested_to=()
+ERR: An ${1} file already exists for '${VERSION}' and attests
+ differently. You likely previously attested to a partial build (e.g. one
+ where you specified the HOST environment variable).
-echo "Attesting to build outputs for version: '${VERSION}'"
-echo ""
+ See the diff above for more context.
-# MAIN LOGIC: Loop through each output for VERSION and attest to output in
-# GUIX_SIGS_REPO as SIGNER, if attestation does not exist
-for outdir in "${OUTDIRS[@]}"; do
- if [ -e "${outdir}/SKIPATTEST.TAG" ]; then
- echo "${outname}: SKIPPING: Output directory marked with SKIPATTEST.TAG file"
- continue
- fi
- outname="$(out_name "$outdir")"
- outsigdir="$(out_sig_dir "$outdir")"
- if [ -e "$outsigdir" ]; then
- echo "${outname}: SKIPPING: Signature directory already exists in the specified guix.sigs repository"
- outdirs_already_attested_to+=("$outdir")
- else
- # Clean up incomplete sigdir if something fails (likely gpg)
- trap 'rm -rf "$outsigdir"' ERR
+Hint: You may wish to remove the existing attestations and their signatures by
+ invoking:
- mkdir -p "$outsigdir"
+ rm '${PWD}/${1}'{,.asc}
- (
- cd "$outdir"
+ Then try running this script again.
- if [ -e inputs.SHA256SUMS ]; then
- echo "${outname}: Including existent input SHA256SUMS"
- cat inputs.SHA256SUMS >> "$outsigdir"/SHA256SUMS
- fi
+EOF
+}
- echo "${outname}: Hashing build outputs to produce SHA256SUMS"
- files="$(find -L . -type f ! -iname '*.SHA256SUMS')"
- if [ -n "$files" ]; then
- cut -c3- <<< "$files" | env LC_ALL=C sort | xargs sha256sum >> "$outsigdir"/SHA256SUMS
+echo "Attesting to build outputs for version: '${VERSION}'"
+echo ""
+
+outsigdir="$GUIX_SIGS_REPO/$VERSION/$signer_name"
+mkdir -p "$outsigdir"
+(
+ cd "$outsigdir"
+
+ temp_noncodesigned="$(mktemp)"
+ trap 'rm -rf -- "$temp_noncodesigned"' EXIT
+
+ if (( ${#noncodesigned_fragments[@]} )); then
+ cat "${noncodesigned_fragments[@]}" \
+ | sort -u \
+ | sort -k2 \
+ > "$temp_noncodesigned"
+ if [ -e noncodesigned.SHA256SUMS ]; then
+ # The SHA256SUMS already exists, make sure it's exactly what we
+ # expect, error out if not
+ if diff -u noncodesigned.SHA256SUMS "$temp_noncodesigned"; then
+ echo "A noncodesigned.SHA256SUMS file already exists for '${VERSION}' and is up-to-date."
else
- echo "ERR: ${outname}: No outputs found in '${outdir}'"
+ shasum_already_exists noncodesigned.SHA256SUMS
exit 1
fi
- )
- if [ -z "$NO_SIGN" ]; then
- echo "${outname}: Signing SHA256SUMS to produce SHA256SUMS.asc"
- gpg --detach-sign --local-user "$gpg_key_name" --armor --output "$outsigdir"/SHA256SUMS.asc "$outsigdir"/SHA256SUMS
else
- echo "${outname}: Not signing SHA256SUMS as \$NO_SIGN is not empty"
+ mv "$temp_noncodesigned" noncodesigned.SHA256SUMS
fi
- echo ""
-
- trap - ERR # Reset ERR trap
+ else
+ echo "ERR: No noncodesigned outputs found for '${VERSION}', exiting..."
+ exit 1
fi
-done
-
-if (( ${#outdirs_already_attested_to[@]} )); then
-# ...so that we can print them out nicely in a warning message
-cat << EOF
-WARN: Signature directories from '$signer_name' already exist in the specified
- guix.sigs repository for the following output directories and were
- skipped:
+ temp_codesigned="$(mktemp)"
+ trap 'rm -rf -- "$temp_codesigned"' EXIT
+
+ if (( ${#codesigned_fragments[@]} )); then
+ # Note: all.SHA256SUMS attests to all of $sha256sum_fragments, but is
+ # not needed if there are no $codesigned_fragments
+ cat "${sha256sum_fragments[@]}" \
+ | sort -u \
+ | sort -k2 \
+ > "$temp_codesigned"
+ if [ -e codesigned.SHA256SUMS ]; then
+ # The SHA256SUMS already exists, make sure it's exactly what we
+ # expect, error out if not
+ if diff -u all.SHA256SUMS "$temp_codesigned"; then
+ echo "An all.SHA256SUMS file already exists for '${VERSION}' and is up-to-date."
+ else
+ shasum_already_exists all.SHA256SUMS
+ exit 1
+ fi
+ else
+ mv "$temp_codesigned" codesigned.SHA256SUMS
+ fi
+ else
+ # It is fine to have the codesigned outputs be missing (perhaps the
+ # detached codesigs have not been published yet), just print a log
+ # message instead of erroring out
+ echo "INFO: No codesigned outputs found for '${VERSION}', skipping..."
+ fi
-EOF
-for outdir in "${outdirs_already_attested_to[@]}"; do
- echo " '${outdir}'"
- echo " Corresponds to: '$(out_sig_dir "$outdir")'"
+ if [ -z "$NO_SIGN" ]; then
+ echo "Signing SHA256SUMS to produce SHA256SUMS.asc"
+ for i in *.SHA256SUMS; do
+ if [ ! -e "$i".asc ]; then
+ gpg --detach-sign \
+ --local-user "$gpg_key_name" \
+ --armor \
+ --output "$i".asc "$i"
+ else
+ echo "Signature already there"
+ fi
+ done
+ else
+ echo "Not signing SHA256SUMS as \$NO_SIGN is not empty"
+ fi
echo ""
-done
-fi
+)
diff --git a/contrib/guix/guix-verify b/contrib/guix/guix-verify
index 629050956c..a6e2c4065e 100755
--- a/contrib/guix/guix-verify
+++ b/contrib/guix/guix-verify
@@ -56,58 +56,87 @@ cmd_usage
exit 1
fi
-################
-# We should be able to find at least one output
-################
+##############
+## Verify ##
+##############
OUTSIGDIR_BASE="${GUIX_SIGS_REPO}/${VERSION}"
-echo "Looking for output signature directories in '${OUTSIGDIR_BASE}'"
+echo "Looking for signature directories in '${OUTSIGDIR_BASE}'"
+echo ""
+
+# Usage: verify compare_manifest current_manifest
+verify() {
+ local compare_manifest="$1"
+ local current_manifest="$2"
+ if ! gpg --quiet --batch --verify "$current_manifest".asc "$current_manifest" 1>&2; then
+ echo "ERR: Failed to verify GPG signature in '${current_manifest}'"
+ echo ""
+ echo "Hint: Either the signature is invalid or the public key is missing"
+ echo ""
+ elif ! diff --report-identical "$compare_manifest" "$current_manifest" 1>&2; then
+ echo "ERR: The SHA256SUMS attestation in these two directories differ:"
+ echo " '${compare_manifest}'"
+ echo " '${current_manifest}'"
+ echo ""
+ else
+ echo "Verified: '${current_manifest}'"
+ echo ""
+ fi
+}
shopt -s nullglob
-OUTSIGDIRS=( "$OUTSIGDIR_BASE"/* ) # This expands to an array of directories...
+all_noncodesigned=( "$OUTSIGDIR_BASE"/*/noncodesigned.SHA256SUMS )
shopt -u nullglob
-if (( ${#OUTSIGDIRS[@]} )); then
- echo "Found output signature directories:"
- for outsigdir in "${OUTSIGDIRS[@]}"; do
- echo " '$outsigdir'"
+echo "--------------------"
+echo ""
+if (( ${#all_noncodesigned[@]} )); then
+ compare_noncodesigned="${all_noncodesigned[0]}"
+
+ for current_manifest in "${all_noncodesigned[@]}"; do
+ verify "$compare_noncodesigned" "$current_manifest"
done
- echo
+
+ echo "DONE: Checking output signatures for noncodesigned.SHA256SUMS"
+ echo ""
else
- echo "ERR: Could not find any output signature directories in ${OUTSIGDIR_BASE}"
- exit 1
+ echo "WARN: No signature directories with noncodesigned.SHA256SUMS found"
+ echo ""
fi
+shopt -s nullglob
+all_all=( "$OUTSIGDIR_BASE"/*/all.SHA256SUMS )
+shopt -u nullglob
-##############
-## Verify ##
-##############
+echo "--------------------"
+echo ""
+if (( ${#all_all[@]} )); then
+ compare_all="${all_all[0]}"
-# MAIN LOGIC: Loop through each output for VERSION and check that the SHA256SUMS
-# and SHA256SUMS.asc file match between signers, using the first
-# available signer as the arbitrary comparison base.
-for outsigdir in "${OUTSIGDIRS[@]}"; do
- echo "BEGIN: Checking output signatures for $(basename "$outsigdir")"
- echo ""
- signer_dirs=( "$outsigdir"/* ) # This expands to an array of directories...
- compare_signer_dir="${signer_dirs[0]}" # ...we just want the first one
- for current_signer_dir in "${signer_dirs[@]}"; do
- if ! gpg --quiet --batch --verify "$current_signer_dir"/SHA256SUMS.asc "$current_signer_dir"/SHA256SUMS; then
- echo "ERR: Failed to verify GPG signature in '${current_signer_dir}/SHA256SUMS.asc'"
- echo ""
- echo "Hint: Either the signature is invalid or the public key is missing"
- echo ""
- elif ! diff --report-identical "$compare_signer_dir"/SHA256SUMS "$current_signer_dir"/SHA256SUMS; then
- echo "ERR: The SHA256SUMS attestation in these two directories differ:"
- echo " '${compare_signer_dir}'"
- echo " '${current_signer_dir}'"
- echo ""
- else
- echo "Verified: '${current_signer_dir}'"
- echo ""
- fi
+ for current_manifest in "${all_all[@]}"; do
+ verify "$compare_all" "$current_manifest"
done
- echo "DONE: Checking output signatures for $(basename "$outsigdir")"
+
+ # Sanity check: there should be no entries that exist in
+ # noncodesigned.SHA256SUMS that doesn't exist in all.SHA256SUMS
+ if [[ "$(comm -23 <(sort "$compare_noncodesigned") <(sort "$compare_all") | wc -c)" -ne 0 ]]; then
+ echo "ERR: There are unique lines in noncodesigned.SHA256SUMS which"
+ echo " do not exist in all.SHA256SUMS, something went very wrong."
+ exit 1
+ fi
+
+ echo "DONE: Checking output signatures for all.SHA256SUMS"
echo ""
+else
+ echo "WARN: No signature directories with all.SHA256SUMS found"
+ echo ""
+fi
+
+echo "===================="
+echo ""
+if (( ${#all_noncodesigned[@]} + ${#all_all[@]} == 0 )); then
+ echo "ERR: Unable to perform any verifications as no signature directories"
+ echo " were found"
echo ""
-done
+ exit 1
+fi
diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh
index 3073b41baf..6741328473 100755
--- a/contrib/guix/libexec/build.sh
+++ b/contrib/guix/libexec/build.sh
@@ -230,20 +230,7 @@ if [ ! -e "$GIT_ARCHIVE" ]; then
git archive --prefix="${DISTNAME}/" --output="$GIT_ARCHIVE" HEAD
fi
-# tmpdir="$(mktemp -d)"
-# (
-# cd "$tmpdir"
-# mkdir -p inputs
-# ln -sf --target-directory=inputs "$GIT_ARCHIVE"
-
-# mkdir -p "$OUTDIR"
-# find -L inputs -type f -print0 | xargs -0 sha256sum > "${OUTDIR}/inputs.SHA256SUMS"
-# )
-
mkdir -p "$OUTDIR"
-cat << EOF > "$OUTDIR"/inputs.SHA256SUMS
-$(sha256sum "$GIT_ARCHIVE" | cut -d' ' -f1) inputs/$(basename "$GIT_ARCHIVE")
-EOF
###########################
# Binary Tarball Building #
@@ -450,3 +437,13 @@ mkdir -p "$DISTSRC"
rm -rf "$ACTUAL_OUTDIR"
mv --no-target-directory "$OUTDIR" "$ACTUAL_OUTDIR" \
|| ( rm -rf "$ACTUAL_OUTDIR" && exit 1 )
+
+(
+ cd /outdir-base
+ {
+ echo "$GIT_ARCHIVE"
+ find "$ACTUAL_OUTDIR" -type f
+ } | xargs realpath --relative-base="$PWD" \
+ | xargs sha256sum \
+ | sponge "$ACTUAL_OUTDIR"/SHA256SUMS.part
+)
diff --git a/contrib/guix/libexec/codesign.sh b/contrib/guix/libexec/codesign.sh
index 1822da7ca4..b1eec686ec 100755
--- a/contrib/guix/libexec/codesign.sh
+++ b/contrib/guix/libexec/codesign.sh
@@ -55,10 +55,6 @@ if [ ! -e "$CODESIGNATURE_GIT_ARCHIVE" ]; then
fi
mkdir -p "$OUTDIR"
-cat << EOF > "$OUTDIR"/inputs.SHA256SUMS
-$(sha256sum "$UNSIGNED_TARBALL" | cut -d' ' -f1) inputs/$(basename "$UNSIGNED_TARBALL")
-$(sha256sum "$CODESIGNATURE_GIT_ARCHIVE" | cut -d' ' -f1) inputs/$(basename "$CODESIGNATURE_GIT_ARCHIVE")
-EOF
mkdir -p "$DISTSRC"
(
@@ -103,3 +99,14 @@ mkdir -p "$DISTSRC"
rm -rf "$ACTUAL_OUTDIR"
mv --no-target-directory "$OUTDIR" "$ACTUAL_OUTDIR" \
|| ( rm -rf "$ACTUAL_OUTDIR" && exit 1 )
+
+(
+ cd /outdir-base
+ {
+ echo "$UNSIGNED_TARBALL"
+ echo "$CODESIGNATURE_GIT_ARCHIVE"
+ find "$ACTUAL_OUTDIR" -type f
+ } | xargs realpath --relative-base="$PWD" \
+ | xargs sha256sum \
+ | sponge "$ACTUAL_OUTDIR"/SHA256SUMS.part
+)
diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm
index 12eab27a3e..ba168a2a4a 100644
--- a/contrib/guix/manifest.scm
+++ b/contrib/guix/manifest.scm
@@ -22,6 +22,7 @@
(gnu packages linux)
(gnu packages llvm)
(gnu packages mingw)
+ (gnu packages moreutils)
(gnu packages perl)
(gnu packages pkg-config)
(gnu packages python)
@@ -205,7 +206,7 @@ chain for " target " development."))
(define-public lief
(package
(name "python-lief")
- (version "0.11.4")
+ (version "0.11.5")
(source
(origin
(method git-fetch)
@@ -215,7 +216,7 @@ chain for " target " development."))
(file-name (git-file-name name version))
(sha256
(base32
- "0h4kcwr9z478almjqhmils8imfpflzk0r7d05g4xbkdyknn162qf"))))
+ "0qahjfg1n0x76ps2mbyljvws1l3qhkqvmxqbahps4qgywl2hbdkj"))))
(build-system python-build-system)
(native-inputs
`(("cmake" ,cmake)))
@@ -572,6 +573,7 @@ inspecting signatures in Mach-O binaries.")
patch
gawk
sed
+ moreutils
;; Compression and archiving
tar
bzip2
diff --git a/contrib/signet/README.md b/contrib/signet/README.md
index 71dc2f9638..706b296c54 100644
--- a/contrib/signet/README.md
+++ b/contrib/signet/README.md
@@ -21,27 +21,28 @@ accept one claim per day. See `--password` above.
miner
=====
-To mine the first block in your custom chain, you can run:
+You will first need to pick a difficulty target. Since signet chains are primarily protected by a signature rather than proof of work, there is no need to spend as much energy as possible mining, however you may wish to choose to spend more time than the absolute minimum. The calibrate subcommand can be used to pick a target appropriate for your hardware, eg:
cd src/
- CLI="./bitcoin-cli -conf=mysignet.conf"
- MINER="..contrib/signet/miner"
+ MINER="../contrib/signet/miner"
GRIND="./bitcoin-util grind"
- ADDR=$($CLI -signet getnewaddress)
- $MINER --cli="$CLI" generate --grind-cmd="$GRIND" --address="$ADDR" --set-block-time=-1
-
-This will mine a block with the current timestamp. If you want to backdate the chain, you can give a different timestamp to --set-block-time.
-
-You will then need to pick a difficulty target. Since signet chains are primarily protected by a signature rather than proof of work, there is no need to spend as much energy as possible mining, however you may wish to choose to spend more time than the absolute minimum. The calibrate subcommand can be used to pick a target, eg:
-
$MINER calibrate --grind-cmd="$GRIND"
nbits=1e00f403 for 25s average mining time
It defaults to estimating an nbits value resulting in 25s average time to find a block, but the --seconds parameter can be used to pick a different target, or the --nbits parameter can be used to estimate how long it will take for a given difficulty.
-Using the --ongoing parameter will then cause the signet miner to create blocks indefinitely. It will pick the time between blocks so that difficulty is adjusted to match the provided --nbits value.
+To mine the first block in your custom chain, you can run:
- $MINER --cli="$CLI" generate --grind-cmd="$GRIND" --address="$ADDR" --nbits=1e00f403 --ongoing
+ CLI="./bitcoin-cli -conf=mysignet.conf"
+ ADDR=$($CLI -signet getnewaddress)
+ NBITS=1e00f403
+ $MINER --cli="$CLI" generate --grind-cmd="$GRIND" --address="$ADDR" --nbits=$NBITS
+
+This will mine a single block with a backdated timestamp designed to allow 100 blocks to be mined as quickly as possible, so that it is possible to do transactions.
+
+Adding the --ongoing parameter will then cause the signet miner to create blocks indefinitely. It will pick the time between blocks so that difficulty is adjusted to match the provided --nbits value.
+
+ $MINER --cli="$CLI" generate --grind-cmd="$GRIND" --address="$ADDR" --nbits=$NBITS --ongoing
Other options
-------------
@@ -50,9 +51,11 @@ The --debug and --quiet options are available to control how noisy the signet mi
Instead of specifying --ongoing, you can specify --max-blocks=N to mine N blocks and stop.
-Instead of using a single address, a ranged descriptor may be provided instead (via the --descriptor parameter), with the reward for the block at height H being sent to the H'th address generated from the descriptor.
+The --set-block-time option is available to manually move timestamps forward or backward (subject to the rules that blocktime must be greater than mediantime, and dates can't be more than two hours in the future). It can only be used when mining a single block (ie, not when using --ongoing or --max-blocks greater than 1).
+
+Instead of using a single address, a ranged descriptor may be provided via the --descriptor parameter, with the reward for the block at height H being sent to the H'th address generated from the descriptor.
-Instead of calculating a specific nbits value, --min-nbits can be specified instead, in which case the mininmum signet difficulty will be targeted.
+Instead of calculating a specific nbits value, --min-nbits can be specified instead, in which case the minimum signet difficulty will be targeted. Signet's minimum difficulty corresponds to --nbits=1e0377ae.
By default, the signet miner mines blocks at fixed intervals with minimal variation. If you want blocks to appear more randomly, as they do in mainnet, specify the --poisson option.
@@ -76,5 +79,5 @@ These steps can instead be done explicitly:
$MINER --cli="$CLI" solvepsbt --grind-cmd="$GRIND" |
$CLI -signet -stdin submitblock
-This is intended to allow you to replace part of the pipeline for further experimentation, if desired.
+This is intended to allow you to replace part of the pipeline for further experimentation (eg, to sign the block with a hardware wallet).
diff --git a/contrib/signet/miner b/contrib/signet/miner
index a3fba49d0e..f1360818ae 100755
--- a/contrib/signet/miner
+++ b/contrib/signet/miner
@@ -428,10 +428,13 @@ def do_generate(args):
action_time = now
is_mine = True
elif bestheader["height"] == 0:
- logging.error("When mining first block in a new signet, must specify --set-block-time")
- return 1
+ time_delta = next_block_delta(int(bestheader["bits"], 16), bci["bestblockhash"], ultimate_target, args.poisson)
+ time_delta *= 100 # 100 blocks
+ logging.info("Backdating time for first block to %d minutes ago" % (time_delta/60))
+ mine_time = now - time_delta
+ action_time = now
+ is_mine = True
else:
-
time_delta = next_block_delta(int(bestheader["bits"], 16), bci["bestblockhash"], ultimate_target, args.poisson)
mine_time = bestheader["time"] + time_delta
@@ -520,12 +523,11 @@ def do_calibrate(args):
sys.stderr.write("Can only specify one of --nbits or --seconds\n")
return 1
if args.nbits is not None and len(args.nbits) != 8:
- sys.stderr.write("Must specify 8 hex digits for --nbits")
+ sys.stderr.write("Must specify 8 hex digits for --nbits\n")
return 1
TRIALS = 600 # gets variance down pretty low
TRIAL_BITS = 0x1e3ea75f # takes about 5m to do 600 trials
- #TRIAL_BITS = 0x1e7ea75f # XXX
header = CBlockHeader()
header.nBits = TRIAL_BITS
@@ -533,23 +535,14 @@ def do_calibrate(args):
start = time.time()
count = 0
- #CHECKS=[]
for i in range(TRIALS):
header.nTime = i
header.nNonce = 0
headhex = header.serialize().hex()
cmd = args.grind_cmd.split(" ") + [headhex]
newheadhex = subprocess.run(cmd, stdout=subprocess.PIPE, input=b"", check=True).stdout.strip()
- #newhead = FromHex(CBlockHeader(), newheadhex.decode('utf8'))
- #count += newhead.nNonce
- #if (i+1) % 100 == 0:
- # CHECKS.append((i+1, count, time.time()-start))
-
- #print("checks =", [c*1.0 / (b*targ*2**-256) for _,b,c in CHECKS])
avg = (time.time() - start) * 1.0 / TRIALS
- #exp_count = 2**256 / targ * TRIALS
- #print("avg =", avg, "count =", count, "exp_count =", exp_count)
if args.nbits is not None:
want_targ = nbits_to_target(int(args.nbits,16))
@@ -590,7 +583,6 @@ def main():
generate.add_argument("--nbits", default=None, type=str, help="Target nBits (specify difficulty)")
generate.add_argument("--min-nbits", action="store_true", help="Target minimum nBits (use min difficulty)")
generate.add_argument("--poisson", action="store_true", help="Simulate randomised block times")
- #generate.add_argument("--signcmd", default=None, type=str, help="Alternative signing command")
generate.add_argument("--multiminer", default=None, type=str, help="Specify which set of blocks to mine (eg: 1-40/100 for the first 40%%, 2/3 for the second 3rd)")
generate.add_argument("--backup-delay", default=300, type=int, help="Seconds to delay before mining blocks reserved for other miners (default=300)")
generate.add_argument("--standby-delay", default=0, type=int, help="Seconds to delay before mining blocks (default=0)")
@@ -605,7 +597,7 @@ def main():
sp.add_argument("--descriptor", default=None, type=str, help="Descriptor for block reward payment")
for sp in [solvepsbt, generate, calibrate]:
- sp.add_argument("--grind-cmd", default=None, type=str, help="Command to grind a block header for proof-of-work")
+ sp.add_argument("--grind-cmd", default=None, type=str, required=(sp==calibrate), help="Command to grind a block header for proof-of-work")
args = parser.parse_args(sys.argv[1:])
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 105d09f730..fc2fd80166 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -35,11 +35,12 @@ BITCOIN_TEST_SUITE = \
$(TEST_UTIL_H)
FUZZ_SUITE_LD_COMMON = \
+ $(LIBTEST_UTIL) \
+ $(LIBTEST_FUZZ) \
$(LIBBITCOIN_SERVER) \
+ $(LIBBITCOIN_WALLET) \
$(LIBBITCOIN_COMMON) \
$(LIBBITCOIN_UTIL) \
- $(LIBTEST_UTIL) \
- $(LIBTEST_FUZZ) \
$(LIBBITCOIN_CONSENSUS) \
$(LIBBITCOIN_CRYPTO) \
$(LIBBITCOIN_CLI) \
@@ -160,7 +161,6 @@ BITCOIN_TESTS += \
wallet/test/scriptpubkeyman_tests.cpp
FUZZ_SUITE_LD_COMMON +=\
- $(LIBBITCOIN_WALLET) \
$(SQLITE_LIBS) \
$(BDB_LIBS)
diff --git a/src/addrdb.cpp b/src/addrdb.cpp
index c376aced10..bf2f6c7614 100644
--- a/src/addrdb.cpp
+++ b/src/addrdb.cpp
@@ -23,7 +23,7 @@ bool SerializeDB(Stream& stream, const Data& data)
{
// Write and commit header, data
try {
- CHashWriter hasher(SER_DISK, CLIENT_VERSION);
+ CHashWriter hasher(stream.GetType(), stream.GetVersion());
stream << Params().MessageStart() << data;
hasher << Params().MessageStart() << data;
stream << hasher.GetHash();
@@ -35,7 +35,7 @@ bool SerializeDB(Stream& stream, const Data& data)
}
template <typename Data>
-bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data)
+bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data, int version)
{
// Generate random temporary filename
uint16_t randv = 0;
@@ -45,7 +45,7 @@ bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data
// open temp output file, and associate with CAutoFile
fs::path pathTmp = gArgs.GetDataDirNet() / tmpfn;
FILE *file = fsbridge::fopen(pathTmp, "wb");
- CAutoFile fileout(file, SER_DISK, CLIENT_VERSION);
+ CAutoFile fileout(file, SER_DISK, version);
if (fileout.IsNull()) {
fileout.fclose();
remove(pathTmp);
@@ -106,11 +106,11 @@ bool DeserializeDB(Stream& stream, Data& data, bool fCheckSum = true)
}
template <typename Data>
-bool DeserializeFileDB(const fs::path& path, Data& data)
+bool DeserializeFileDB(const fs::path& path, Data& data, int version)
{
// open input file, and associate with CAutoFile
FILE* file = fsbridge::fopen(path, "rb");
- CAutoFile filein(file, SER_DISK, CLIENT_VERSION);
+ CAutoFile filein(file, SER_DISK, version);
if (filein.IsNull()) {
LogPrintf("Missing or invalid file %s\n", path.string());
return false;
@@ -125,12 +125,12 @@ CBanDB::CBanDB(fs::path ban_list_path) : m_ban_list_path(std::move(ban_list_path
bool CBanDB::Write(const banmap_t& banSet)
{
- return SerializeFileDB("banlist", m_ban_list_path, banSet);
+ return SerializeFileDB("banlist", m_ban_list_path, banSet, CLIENT_VERSION);
}
bool CBanDB::Read(banmap_t& banSet)
{
- return DeserializeFileDB(m_ban_list_path, banSet);
+ return DeserializeFileDB(m_ban_list_path, banSet, CLIENT_VERSION);
}
CAddrDB::CAddrDB()
@@ -140,12 +140,12 @@ CAddrDB::CAddrDB()
bool CAddrDB::Write(const CAddrMan& addr)
{
- return SerializeFileDB("peers", pathAddr, addr);
+ return SerializeFileDB("peers", pathAddr, addr, CLIENT_VERSION);
}
bool CAddrDB::Read(CAddrMan& addr)
{
- return DeserializeFileDB(pathAddr, addr);
+ return DeserializeFileDB(pathAddr, addr, CLIENT_VERSION);
}
bool CAddrDB::Read(CAddrMan& addr, CDataStream& ssPeers)
@@ -161,13 +161,13 @@ bool CAddrDB::Read(CAddrMan& addr, CDataStream& ssPeers)
void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& anchors)
{
LOG_TIME_SECONDS(strprintf("Flush %d outbound block-relay-only peer addresses to anchors.dat", anchors.size()));
- SerializeFileDB("anchors", anchors_db_path, anchors);
+ SerializeFileDB("anchors", anchors_db_path, anchors, CLIENT_VERSION | ADDRV2_FORMAT);
}
std::vector<CAddress> ReadAnchors(const fs::path& anchors_db_path)
{
std::vector<CAddress> anchors;
- if (DeserializeFileDB(anchors_db_path, anchors)) {
+ if (DeserializeFileDB(anchors_db_path, anchors, CLIENT_VERSION | ADDRV2_FORMAT)) {
LogPrintf("Loaded %i addresses from %s\n", anchors.size(), anchors_db_path.filename());
} else {
anchors.clear();
diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp
index 93ac4b8f7e..3fc87ae1ff 100644
--- a/src/bitcoin-tx.cpp
+++ b/src/bitcoin-tx.cpp
@@ -506,11 +506,12 @@ static void MutateTxDelOutput(CMutableTransaction& tx, const std::string& strOut
tx.vout.erase(tx.vout.begin() + outIdx);
}
-static const unsigned int N_SIGHASH_OPTS = 6;
+static const unsigned int N_SIGHASH_OPTS = 7;
static const struct {
const char *flagStr;
int flags;
} sighashOptions[N_SIGHASH_OPTS] = {
+ {"DEFAULT", SIGHASH_DEFAULT},
{"ALL", SIGHASH_ALL},
{"NONE", SIGHASH_NONE},
{"SINGLE", SIGHASH_SINGLE},
diff --git a/src/bitcoin-util.cpp b/src/bitcoin-util.cpp
index af07b28d3d..3f273e75f6 100644
--- a/src/bitcoin-util.cpp
+++ b/src/bitcoin-util.cpp
@@ -43,6 +43,8 @@ static void SetupBitcoinUtilArgs(ArgsManager &argsman)
argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+ argsman.AddCommand("grind", "Perform proof of work on hex header string", OptionsCategory::COMMANDS);
+
SetupChainParamsBaseOptions(argsman);
}
@@ -57,15 +59,7 @@ static int AppInitUtil(int argc, char* argv[])
return EXIT_FAILURE;
}
- // Check for chain settings (Params() calls are only valid after this clause)
- try {
- SelectParams(gArgs.GetChainName());
- } catch (const std::exception& e) {
- tfm::format(std::cerr, "Error: %s\n", e.what());
- return EXIT_FAILURE;
- }
-
- if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
+ if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
// First part of help message is specific to this utility
std::string strUsage = PACKAGE_NAME " bitcoin-util utility version " + FormatFullVersion() + "\n";
if (!gArgs.IsArgSet("-version")) {
@@ -82,6 +76,15 @@ static int AppInitUtil(int argc, char* argv[])
}
return EXIT_SUCCESS;
}
+
+ // Check for chain settings (Params() calls are only valid after this clause)
+ try {
+ SelectParams(gArgs.GetChainName());
+ } catch (const std::exception& e) {
+ tfm::format(std::cerr, "Error: %s\n", e.what());
+ return EXIT_FAILURE;
+ }
+
return CONTINUE_EXECUTION;
}
@@ -111,17 +114,17 @@ static void grind_task(uint32_t nBits, CBlockHeader& header_orig, uint32_t offse
}
}
-static int Grind(int argc, char* argv[], std::string& strPrint)
+static int Grind(std::vector<std::string> args, std::string& strPrint)
{
- if (argc != 1) {
+ if (args.size() != 1) {
strPrint = "Must specify block header to grind";
- return 1;
+ return EXIT_FAILURE;
}
CBlockHeader header;
- if (!DecodeHexBlockHeader(header, argv[0])) {
+ if (!DecodeHexBlockHeader(header, args[0])) {
strPrint = "Could not decode block header";
- return 1;
+ return EXIT_FAILURE;
}
uint32_t nBits = header.nBits;
@@ -137,49 +140,13 @@ static int Grind(int argc, char* argv[], std::string& strPrint)
}
if (!found) {
strPrint = "Could not satisfy difficulty target";
- return 1;
+ return EXIT_FAILURE;
}
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << header;
strPrint = HexStr(ss);
- return 0;
-}
-
-static int CommandLineUtil(int argc, char* argv[])
-{
- if (argc <= 1) return 1;
-
- std::string strPrint;
- int nRet = 0;
-
- try {
- while (argc > 1 && IsSwitchChar(argv[1][0]) && (argv[1][1] != 0)) {
- --argc;
- ++argv;
- }
-
- char* command = argv[1];
- if (strcmp(command, "grind") == 0) {
- nRet = Grind(argc-2, argv+2, strPrint);
- } else {
- strPrint = strprintf("Unknown command %s", command);
- nRet = 1;
- }
- }
- catch (const std::exception& e) {
- strPrint = std::string("error: ") + e.what();
- nRet = EXIT_FAILURE;
- }
- catch (...) {
- PrintExceptionContinue(nullptr, "CommandLineUtil()");
- throw;
- }
-
- if (strPrint != "") {
- tfm::format(nRet == 0 ? std::cout : std::cerr, "%s\n", strPrint);
- }
- return nRet;
+ return EXIT_SUCCESS;
}
#ifdef WIN32
@@ -199,8 +166,7 @@ int main(int argc, char* argv[])
int ret = AppInitUtil(argc, argv);
if (ret != CONTINUE_EXECUTION)
return ret;
- }
- catch (const std::exception& e) {
+ } catch (const std::exception& e) {
PrintExceptionContinue(&e, "AppInitUtil()");
return EXIT_FAILURE;
} catch (...) {
@@ -208,14 +174,29 @@ int main(int argc, char* argv[])
return EXIT_FAILURE;
}
+ const auto cmd = gArgs.GetCommand();
+ if (!cmd) {
+ tfm::format(std::cerr, "Error: must specify a command\n");
+ return EXIT_FAILURE;
+ }
+
int ret = EXIT_FAILURE;
+ std::string strPrint;
try {
- ret = CommandLineUtil(argc, argv);
- }
- catch (const std::exception& e) {
- PrintExceptionContinue(&e, "CommandLineUtil()");
+ if (cmd->command == "grind") {
+ ret = Grind(cmd->args, strPrint);
+ } else {
+ assert(false); // unknown command should be caught earlier
+ }
+ } catch (const std::exception& e) {
+ strPrint = std::string("error: ") + e.what();
} catch (...) {
- PrintExceptionContinue(nullptr, "CommandLineUtil()");
+ strPrint = "unknown error";
+ }
+
+ if (strPrint != "") {
+ tfm::format(ret == 0 ? std::cout : std::cerr, "%s\n", strPrint);
}
+
return ret;
}
diff --git a/src/core_read.cpp b/src/core_read.cpp
index b5fc93886d..6108961010 100644
--- a/src/core_read.cpp
+++ b/src/core_read.cpp
@@ -260,6 +260,7 @@ int ParseSighashString(const UniValue& sighash)
int hash_type = SIGHASH_ALL;
if (!sighash.isNull()) {
static std::map<std::string, int> map_sighash_values = {
+ {std::string("DEFAULT"), int(SIGHASH_DEFAULT)},
{std::string("ALL"), int(SIGHASH_ALL)},
{std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)},
{std::string("NONE"), int(SIGHASH_NONE)},
diff --git a/src/external_signer.cpp b/src/external_signer.cpp
index f16d21fa60..d6388b759a 100644
--- a/src/external_signer.cpp
+++ b/src/external_signer.cpp
@@ -13,9 +13,7 @@
#include <string>
#include <vector>
-#ifdef ENABLE_EXTERNAL_SIGNER
-
-ExternalSigner::ExternalSigner(const std::string& command, const std::string& fingerprint, const std::string chain, const std::string name): m_command(command), m_fingerprint(fingerprint), m_chain(chain), m_name(name) {}
+ExternalSigner::ExternalSigner(const std::string& command, const std::string chain, const std::string& fingerprint, const std::string name): m_command(command), m_chain(chain), m_fingerprint(fingerprint), m_name(name) {}
const std::string ExternalSigner::NetworkArg() const
{
@@ -55,7 +53,7 @@ bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalS
if (model_field.isStr() && model_field.getValStr() != "") {
name += model_field.getValStr();
}
- signers.push_back(ExternalSigner(command, fingerprintStr, chain, name));
+ signers.push_back(ExternalSigner(command, chain, fingerprintStr, name));
}
return true;
}
@@ -116,5 +114,3 @@ bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::str
return true;
}
-
-#endif // ENABLE_EXTERNAL_SIGNER
diff --git a/src/external_signer.h b/src/external_signer.h
index b3b202091a..e40fd7f010 100644
--- a/src/external_signer.h
+++ b/src/external_signer.h
@@ -11,8 +11,6 @@
#include <string>
#include <vector>
-#ifdef ENABLE_EXTERNAL_SIGNER
-
struct PartiallySignedTransaction;
//! Enables interaction with an external signing device or service, such as
@@ -23,24 +21,24 @@ private:
//! The command which handles interaction with the external signer.
std::string m_command;
+ //! Bitcoin mainnet, testnet, etc
+ std::string m_chain;
+
+ const std::string NetworkArg() const;
+
public:
//! @param[in] command the command which handles interaction with the external signer
//! @param[in] fingerprint master key fingerprint of the signer
//! @param[in] chain "main", "test", "regtest" or "signet"
//! @param[in] name device name
- ExternalSigner(const std::string& command, const std::string& fingerprint, const std::string chain, const std::string name);
+ ExternalSigner(const std::string& command, const std::string chain, const std::string& fingerprint, const std::string name);
//! Master key fingerprint of the signer
std::string m_fingerprint;
- //! Bitcoin mainnet, testnet, etc
- std::string m_chain;
-
//! Name of signer
std::string m_name;
- const std::string NetworkArg() const;
-
//! Obtain a list of signers. Calls `<command> enumerate`.
//! @param[in] command the command which handles interaction with the external signer
//! @param[in,out] signers vector to which new signers (with a unique master key fingerprint) are added
@@ -65,6 +63,4 @@ public:
bool SignTransaction(PartiallySignedTransaction& psbt, std::string& error);
};
-#endif // ENABLE_EXTERNAL_SIGNER
-
#endif // BITCOIN_EXTERNAL_SIGNER_H
diff --git a/src/interfaces/node.h b/src/interfaces/node.h
index 35b6160cea..77129423db 100644
--- a/src/interfaces/node.h
+++ b/src/interfaces/node.h
@@ -111,10 +111,8 @@ public:
//! Disconnect node by id.
virtual bool disconnectById(NodeId id) = 0;
-#ifdef ENABLE_EXTERNAL_SIGNER
//! List external signers
virtual std::vector<ExternalSigner> externalSigners() = 0;
-#endif
//! Get total bytes recv.
virtual int64_t getTotalBytesRecv() = 0;
diff --git a/src/key.cpp b/src/key.cpp
index 5666adebb8..dcad386e77 100644
--- a/src/key.cpp
+++ b/src/key.cpp
@@ -7,10 +7,13 @@
#include <crypto/common.h>
#include <crypto/hmac_sha512.h>
+#include <hash.h>
#include <random.h>
#include <secp256k1.h>
+#include <secp256k1_extrakeys.h>
#include <secp256k1_recovery.h>
+#include <secp256k1_schnorrsig.h>
static secp256k1_context* secp256k1_context_sign = nullptr;
@@ -258,6 +261,24 @@ bool CKey::SignCompact(const uint256 &hash, std::vector<unsigned char>& vchSig)
return true;
}
+bool CKey::SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root, const uint256* aux) const
+{
+ assert(sig.size() == 64);
+ secp256k1_keypair keypair;
+ if (!secp256k1_keypair_create(secp256k1_context_sign, &keypair, begin())) return false;
+ if (merkle_root) {
+ secp256k1_xonly_pubkey pubkey;
+ if (!secp256k1_keypair_xonly_pub(secp256k1_context_sign, &pubkey, nullptr, &keypair)) return false;
+ unsigned char pubkey_bytes[32];
+ if (!secp256k1_xonly_pubkey_serialize(secp256k1_context_sign, pubkey_bytes, &pubkey)) return false;
+ uint256 tweak = XOnlyPubKey(pubkey_bytes).ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root);
+ if (!secp256k1_keypair_xonly_tweak_add(GetVerifyContext(), &keypair, tweak.data())) return false;
+ }
+ bool ret = secp256k1_schnorrsig_sign(secp256k1_context_sign, sig.data(), hash.data(), &keypair, secp256k1_nonce_function_bip340, aux ? (void*)aux->data() : nullptr);
+ memory_cleanse(&keypair, sizeof(keypair));
+ return ret;
+}
+
bool CKey::Load(const CPrivKey &seckey, const CPubKey &vchPubKey, bool fSkipCheck=false) {
if (!ec_seckey_import_der(secp256k1_context_sign, (unsigned char*)begin(), seckey.data(), seckey.size()))
return false;
diff --git a/src/key.h b/src/key.h
index 3ee49a778b..d47e54800c 100644
--- a/src/key.h
+++ b/src/key.h
@@ -128,6 +128,18 @@ public:
*/
bool SignCompact(const uint256& hash, std::vector<unsigned char>& vchSig) const;
+ /**
+ * Create a BIP-340 Schnorr signature, for the xonly-pubkey corresponding to *this,
+ * optionally tweaked by *merkle_root. Additional nonce entropy can be provided through
+ * aux.
+ *
+ * When merkle_root is not nullptr, this results in a signature with a modified key as
+ * specified in BIP341:
+ * - If merkle_root->IsNull(): key + H_TapTweak(pubkey)*G
+ * - Otherwise: key + H_TapTweak(pubkey || *merkle_root)
+ */
+ bool SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root = nullptr, const uint256* aux = nullptr) const;
+
//! Derive BIP32 child key.
bool Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
diff --git a/src/net.cpp b/src/net.cpp
index 4d7c181330..60059249ed 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -2213,6 +2213,7 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai
void CConnman::ThreadMessageHandler()
{
+ FastRandomContext rng;
while (!flagInterruptMsgProc)
{
std::vector<CNode*> vNodesCopy;
@@ -2226,6 +2227,11 @@ void CConnman::ThreadMessageHandler()
bool fMoreWork = false;
+ // Randomize the order in which we process messages from/to our peers.
+ // This prevents attacks in which an attacker exploits having multiple
+ // consecutive connections in the vNodes list.
+ Shuffle(vNodesCopy.begin(), vNodesCopy.end(), rng);
+
for (CNode* pnode : vNodesCopy)
{
if (pnode->fDisconnect)
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index 2d05f9d5fb..fce3c1809c 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -6,6 +6,7 @@
#include <banman.h>
#include <chain.h>
#include <chainparams.h>
+#include <external_signer.h>
#include <init.h>
#include <interfaces/chain.h>
#include <interfaces/handler.h>
@@ -170,16 +171,24 @@ public:
}
return false;
}
-#ifdef ENABLE_EXTERNAL_SIGNER
std::vector<ExternalSigner> externalSigners() override
{
+#ifdef ENABLE_EXTERNAL_SIGNER
std::vector<ExternalSigner> signers = {};
const std::string command = gArgs.GetArg("-signer", "");
if (command == "") return signers;
ExternalSigner::Enumerate(command, signers, Params().NetworkIDString());
return signers;
+#else
+ // This result is undistinguisable from a succesful call that returns
+ // no signers. For the current GUI this doesn't matter, because the wallet
+ // creation dialog disables the external signer checkbox in both
+ // cases. The return type could be changed to std::optional<std::vector>
+ // (or something that also includes error messages) if this distinction
+ // becomes important.
+ return {};
+#endif // ENABLE_EXTERNAL_SIGNER
}
-#endif
int64_t getTotalBytesRecv() override { return m_context->connman ? m_context->connman->GetTotalBytesRecv() : 0; }
int64_t getTotalBytesSent() override { return m_context->connman ? m_context->connman->GetTotalBytesSent() : 0; }
size_t getMempoolSize() override { return m_context->mempool ? m_context->mempool->size() : 0; }
diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp
index c189018268..b013b6d579 100644
--- a/src/node/psbt.cpp
+++ b/src/node/psbt.cpp
@@ -23,6 +23,8 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
result.inputs.resize(psbtx.tx->vin.size());
+ const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
+
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
PSBTInput& input = psbtx.inputs[i];
PSBTInputAnalysis& input_analysis = result.inputs[i];
@@ -61,7 +63,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
// Figure out what is missing
SignatureData outdata;
- bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, &outdata);
+ bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, 1, &outdata);
// Things are missing
if (!complete) {
@@ -121,7 +123,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
PSBTInput& input = psbtx.inputs[i];
Coin newcoin;
- if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true) || !psbtx.GetInputUTXO(newcoin.out, i)) {
+ if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, 1) || !psbtx.GetInputUTXO(newcoin.out, i)) {
success = false;
break;
} else {
diff --git a/src/protocol.h b/src/protocol.h
index aaa9f1df40..f9248899dc 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -13,6 +13,7 @@
#include <netaddress.h>
#include <primitives/transaction.h>
#include <serialize.h>
+#include <streams.h>
#include <uint256.h>
#include <version.h>
@@ -358,6 +359,31 @@ class CAddress : public CService
{
static constexpr uint32_t TIME_INIT{100000000};
+ /** Historically, CAddress disk serialization stored the CLIENT_VERSION, optionally OR'ed with
+ * the ADDRV2_FORMAT flag to indicate V2 serialization. The first field has since been
+ * disentangled from client versioning, and now instead:
+ * - The low bits (masked by DISK_VERSION_IGNORE_MASK) store the fixed value DISK_VERSION_INIT,
+ * (in case any code exists that treats it as a client version) but are ignored on
+ * deserialization.
+ * - The high bits (masked by ~DISK_VERSION_IGNORE_MASK) store actual serialization information.
+ * Only 0 or DISK_VERSION_ADDRV2 (equal to the historical ADDRV2_FORMAT) are valid now, and
+ * any other value triggers a deserialization failure. Other values can be added later if
+ * needed.
+ *
+ * For disk deserialization, ADDRV2_FORMAT in the stream version signals that ADDRV2
+ * deserialization is permitted, but the actual format is determined by the high bits in the
+ * stored version field. For network serialization, the stream version having ADDRV2_FORMAT or
+ * not determines the actual format used (as it has no embedded version number).
+ */
+ static constexpr uint32_t DISK_VERSION_INIT{220000};
+ static constexpr uint32_t DISK_VERSION_IGNORE_MASK{0b00000000'00000111'11111111'11111111};
+ /** The version number written in disk serialized addresses to indicate V2 serializations.
+ * It must be exactly 1<<29, as that is the value that historical versions used for this
+ * (they used their internal ADDRV2_FORMAT flag here). */
+ static constexpr uint32_t DISK_VERSION_ADDRV2{1 << 29};
+ static_assert((DISK_VERSION_INIT & ~DISK_VERSION_IGNORE_MASK) == 0, "DISK_VERSION_INIT must be covered by DISK_VERSION_IGNORE_MASK");
+ static_assert((DISK_VERSION_ADDRV2 & DISK_VERSION_IGNORE_MASK) == 0, "DISK_VERSION_ADDRV2 must not be covered by DISK_VERSION_IGNORE_MASK");
+
public:
CAddress() : CService{} {};
CAddress(CService ipIn, ServiceFlags nServicesIn) : CService{ipIn}, nServices{nServicesIn} {};
@@ -365,22 +391,48 @@ public:
SERIALIZE_METHODS(CAddress, obj)
{
- SER_READ(obj, obj.nTime = TIME_INIT);
- int nVersion = s.GetVersion();
+ // CAddress has a distinct network serialization and a disk serialization, but it should never
+ // be hashed (except through CHashWriter in addrdb.cpp, which sets SER_DISK), and it's
+ // ambiguous what that would mean. Make sure no code relying on that is introduced:
+ assert(!(s.GetType() & SER_GETHASH));
+ bool use_v2;
+ bool store_time;
if (s.GetType() & SER_DISK) {
- READWRITE(nVersion);
- }
- if ((s.GetType() & SER_DISK) ||
- (nVersion != INIT_PROTO_VERSION && !(s.GetType() & SER_GETHASH))) {
+ // In the disk serialization format, the encoding (v1 or v2) is determined by a flag version
+ // that's part of the serialization itself. ADDRV2_FORMAT in the stream version only determines
+ // whether V2 is chosen/permitted at all.
+ uint32_t stored_format_version = DISK_VERSION_INIT;
+ if (s.GetVersion() & ADDRV2_FORMAT) stored_format_version |= DISK_VERSION_ADDRV2;
+ READWRITE(stored_format_version);
+ stored_format_version &= ~DISK_VERSION_IGNORE_MASK; // ignore low bits
+ if (stored_format_version == 0) {
+ use_v2 = false;
+ } else if (stored_format_version == DISK_VERSION_ADDRV2 && (s.GetVersion() & ADDRV2_FORMAT)) {
+ // Only support v2 deserialization if ADDRV2_FORMAT is set.
+ use_v2 = true;
+ } else {
+ throw std::ios_base::failure("Unsupported CAddress disk format version");
+ }
+ store_time = true;
+ } else {
+ // In the network serialization format, the encoding (v1 or v2) is determined directly by
+ // the value of ADDRV2_FORMAT in the stream version, as no explicitly encoded version
+ // exists in the stream.
+ assert(s.GetType() & SER_NETWORK);
+ use_v2 = s.GetVersion() & ADDRV2_FORMAT;
// The only time we serialize a CAddress object without nTime is in
// the initial VERSION messages which contain two CAddress records.
// At that point, the serialization version is INIT_PROTO_VERSION.
// After the version handshake, serialization version is >=
// MIN_PEER_PROTO_VERSION and all ADDR messages are serialized with
// nTime.
- READWRITE(obj.nTime);
+ store_time = s.GetVersion() != INIT_PROTO_VERSION;
}
- if (nVersion & ADDRV2_FORMAT) {
+
+ SER_READ(obj, obj.nTime = TIME_INIT);
+ if (store_time) READWRITE(obj.nTime);
+ // nServices is serialized as CompactSize in V2; as uint64_t in V1.
+ if (use_v2) {
uint64_t services_tmp;
SER_WRITE(obj, services_tmp = obj.nServices);
READWRITE(Using<CompactSizeFormatter<false>>(services_tmp));
@@ -388,13 +440,22 @@ public:
} else {
READWRITE(Using<CustomUintFormatter<8>>(obj.nServices));
}
- READWRITEAS(CService, obj);
+ // Invoke V1/V2 serializer for CService parent object.
+ OverrideStream<Stream> os(&s, s.GetType(), use_v2 ? ADDRV2_FORMAT : 0);
+ SerReadWriteMany(os, ser_action, ReadWriteAsHelper<CService>(obj));
}
- // disk and network only
+ //! Always included in serialization, except in the network format on INIT_PROTO_VERSION.
uint32_t nTime{TIME_INIT};
-
+ //! Serialized as uint64_t in V1, and as CompactSize in V2.
ServiceFlags nServices{NODE_NONE};
+
+ friend bool operator==(const CAddress& a, const CAddress& b)
+ {
+ return a.nTime == b.nTime &&
+ a.nServices == b.nServices &&
+ static_cast<const CService&>(a) == static_cast<const CService&>(b);
+ }
};
/** getdata message type flags */
diff --git a/src/psbt.cpp b/src/psbt.cpp
index a849b2ea53..5445bc8aa1 100644
--- a/src/psbt.cpp
+++ b/src/psbt.cpp
@@ -59,12 +59,15 @@ bool PartiallySignedTransaction::AddOutput(const CTxOut& txout, const PSBTOutput
bool PartiallySignedTransaction::GetInputUTXO(CTxOut& utxo, int input_index) const
{
- PSBTInput input = inputs[input_index];
+ const PSBTInput& input = inputs[input_index];
uint32_t prevout_index = tx->vin[input_index].prevout.n;
if (input.non_witness_utxo) {
if (prevout_index >= input.non_witness_utxo->vout.size()) {
return false;
}
+ if (input.non_witness_utxo->GetHash() != tx->vin[input_index].prevout.hash) {
+ return false;
+ }
utxo = input.non_witness_utxo->vout[prevout_index];
} else if (!input.witness_utxo.IsNull()) {
utxo = input.witness_utxo;
@@ -227,7 +230,24 @@ void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransactio
psbt_out.FromSignatureData(sigdata);
}
-bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash, SignatureData* out_sigdata, bool use_dummy)
+PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt)
+{
+ const CMutableTransaction& tx = *psbt.tx;
+ bool have_all_spent_outputs = true;
+ std::vector<CTxOut> utxos(tx.vin.size());
+ for (size_t idx = 0; idx < tx.vin.size(); ++idx) {
+ if (!psbt.GetInputUTXO(utxos[idx], idx)) have_all_spent_outputs = false;
+ }
+ PrecomputedTransactionData txdata;
+ if (have_all_spent_outputs) {
+ txdata.Init(tx, std::move(utxos), true);
+ } else {
+ txdata.Init(tx, {}, true);
+ }
+ return txdata;
+}
+
+bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata)
{
PSBTInput& input = psbt.inputs.at(index);
const CMutableTransaction& tx = *psbt.tx;
@@ -267,10 +287,10 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
sigdata.witness = false;
bool sig_complete;
- if (use_dummy) {
+ if (txdata == nullptr) {
sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata);
} else {
- MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, sighash);
+ MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, txdata, sighash);
sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata);
}
// Verify that a witness signature was produced in case one was required.
@@ -302,8 +322,9 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx)
// PartiallySignedTransaction did not understand them), this will combine them into a final
// script.
bool complete = true;
+ const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
- complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, SIGHASH_ALL);
+ complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL);
}
return complete;
diff --git a/src/psbt.h b/src/psbt.h
index 96ae39fdb8..f6b82b43de 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -567,11 +567,18 @@ enum class PSBTRole {
std::string PSBTRoleName(PSBTRole role);
+/** Compute a PrecomputedTransactionData object from a psbt. */
+PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt);
+
/** Checks whether a PSBTInput is already signed. */
bool PSBTInputSigned(const PSBTInput& input);
-/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */
-bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool use_dummy = false);
+/** Signs a PSBTInput, verifying that all provided data matches what is being signed.
+ *
+ * txdata should be the output of PrecomputePSBTData (which can be shared across
+ * multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created.
+ **/
+bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr);
/** Counts the unsigned inputs of a PSBT. */
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt);
diff --git a/src/pubkey.cpp b/src/pubkey.cpp
index 51cc826b00..175a39b805 100644
--- a/src/pubkey.cpp
+++ b/src/pubkey.cpp
@@ -373,3 +373,7 @@ ECCVerifyHandle::~ECCVerifyHandle()
secp256k1_context_verify = nullptr;
}
}
+
+const secp256k1_context* GetVerifyContext() {
+ return secp256k1_context_verify;
+}
diff --git a/src/pubkey.h b/src/pubkey.h
index 152a48dd18..eec34a89c2 100644
--- a/src/pubkey.h
+++ b/src/pubkey.h
@@ -234,6 +234,10 @@ public:
* fail. */
bool IsFullyValid() const;
+ /** Test whether this is the 0 key (the result of default construction). This implies
+ * !IsFullyValid(). */
+ bool IsNull() const { return m_keydata.IsNull(); }
+
/** Construct an x-only pubkey from exactly 32 bytes. */
explicit XOnlyPubKey(Span<const unsigned char> bytes);
@@ -312,4 +316,10 @@ public:
~ECCVerifyHandle();
};
+typedef struct secp256k1_context_struct secp256k1_context;
+
+/** Access to the internal secp256k1 context used for verification. Only intended to be used
+ * by key.cpp. */
+const secp256k1_context* GetVerifyContext();
+
#endif // BITCOIN_PUBKEY_H
diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp
index 7024fc7654..c31f0aceea 100644
--- a/src/qt/addressbookpage.cpp
+++ b/src/qt/addressbookpage.cpp
@@ -114,12 +114,12 @@ AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode,
// Build context menu
contextMenu = new QMenu(this);
- contextMenu->addAction(tr("Copy Address"), this, &AddressBookPage::on_copyAddress_clicked);
- contextMenu->addAction(tr("Copy Label"), this, &AddressBookPage::onCopyLabelAction);
- contextMenu->addAction(tr("Edit"), this, &AddressBookPage::onEditAction);
+ contextMenu->addAction(tr("&Copy Address"), this, &AddressBookPage::on_copyAddress_clicked);
+ contextMenu->addAction(tr("Copy &Label"), this, &AddressBookPage::onCopyLabelAction);
+ contextMenu->addAction(tr("&Edit"), this, &AddressBookPage::onEditAction);
if (tab == SendingTab) {
- contextMenu->addAction(tr("Delete"), this, &AddressBookPage::on_deleteAddress_clicked);
+ contextMenu->addAction(tr("&Delete"), this, &AddressBookPage::on_deleteAddress_clicked);
}
connect(ui->tableView, &QWidget::customContextMenuRequested, this, &AddressBookPage::contextualMenu);
diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp
index 8ae0648141..2360fa9b37 100644
--- a/src/qt/coincontroldialog.cpp
+++ b/src/qt/coincontroldialog.cpp
@@ -52,13 +52,13 @@ CoinControlDialog::CoinControlDialog(CCoinControl& coin_control, WalletModel* _m
// context menu
contextMenu = new QMenu(this);
- contextMenu->addAction(tr("Copy address"), this, &CoinControlDialog::copyAddress);
- contextMenu->addAction(tr("Copy label"), this, &CoinControlDialog::copyLabel);
- contextMenu->addAction(tr("Copy amount"), this, &CoinControlDialog::copyAmount);
- copyTransactionHashAction = contextMenu->addAction(tr("Copy transaction ID"), this, &CoinControlDialog::copyTransactionHash);
+ contextMenu->addAction(tr("&Copy address"), this, &CoinControlDialog::copyAddress);
+ contextMenu->addAction(tr("Copy &label"), this, &CoinControlDialog::copyLabel);
+ contextMenu->addAction(tr("Copy &amount"), this, &CoinControlDialog::copyAmount);
+ copyTransactionHashAction = contextMenu->addAction(tr("Copy transaction &ID"), this, &CoinControlDialog::copyTransactionHash);
contextMenu->addSeparator();
- lockAction = contextMenu->addAction(tr("Lock unspent"), this, &CoinControlDialog::lockCoin);
- unlockAction = contextMenu->addAction(tr("Unlock unspent"), this, &CoinControlDialog::unlockCoin);
+ lockAction = contextMenu->addAction(tr("L&ock unspent"), this, &CoinControlDialog::lockCoin);
+ unlockAction = contextMenu->addAction(tr("&Unlock unspent"), this, &CoinControlDialog::unlockCoin);
connect(ui->treeWidget, &QWidget::customContextMenuRequested, this, &CoinControlDialog::showMenu);
// clipboard actions
diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp
index e593697b46..dc24bbc6a6 100644
--- a/src/qt/createwalletdialog.cpp
+++ b/src/qt/createwalletdialog.cpp
@@ -31,8 +31,9 @@ CreateWalletDialog::CreateWalletDialog(QWidget* parent) :
// Disable the disable_privkeys_checkbox and external_signer_checkbox when isEncryptWalletChecked is
// set to true, enable it when isEncryptWalletChecked is false.
ui->disable_privkeys_checkbox->setEnabled(!checked);
+#ifdef ENABLE_EXTERNAL_SIGNER
ui->external_signer_checkbox->setEnabled(!checked);
-
+#endif
// When the disable_privkeys_checkbox is disabled, uncheck it.
if (!ui->disable_privkeys_checkbox->isEnabled()) {
ui->disable_privkeys_checkbox->setChecked(false);
@@ -112,8 +113,7 @@ CreateWalletDialog::~CreateWalletDialog()
delete ui;
}
-#ifdef ENABLE_EXTERNAL_SIGNER
-void CreateWalletDialog::setSigners(std::vector<ExternalSigner>& signers)
+void CreateWalletDialog::setSigners(const std::vector<ExternalSigner>& signers)
{
if (!signers.empty()) {
ui->external_signer_checkbox->setEnabled(true);
@@ -132,7 +132,6 @@ void CreateWalletDialog::setSigners(std::vector<ExternalSigner>& signers)
ui->external_signer_checkbox->setEnabled(false);
}
}
-#endif
QString CreateWalletDialog::walletName() const
{
diff --git a/src/qt/createwalletdialog.h b/src/qt/createwalletdialog.h
index 585b1461f7..25ddf97585 100644
--- a/src/qt/createwalletdialog.h
+++ b/src/qt/createwalletdialog.h
@@ -7,11 +7,8 @@
#include <QDialog>
-class WalletModel;
-
-#ifdef ENABLE_EXTERNAL_SIGNER
class ExternalSigner;
-#endif
+class WalletModel;
namespace Ui {
class CreateWalletDialog;
@@ -27,9 +24,7 @@ public:
explicit CreateWalletDialog(QWidget* parent);
virtual ~CreateWalletDialog();
-#ifdef ENABLE_EXTERNAL_SIGNER
- void setSigners(std::vector<ExternalSigner>& signers);
-#endif
+ void setSigners(const std::vector<ExternalSigner>& signers);
QString walletName() const;
bool isEncryptWalletChecked() const;
diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp
index 6ad8db4348..b12fe96567 100644
--- a/src/qt/optionsdialog.cpp
+++ b/src/qt/optionsdialog.cpp
@@ -92,6 +92,11 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) :
ui->thirdPartyTxUrls->setVisible(false);
}
+#ifndef ENABLE_EXTERNAL_SIGNER
+ //: "External signing" means using devices such as hardware wallets.
+ ui->externalSignerPath->setToolTip(tr("Compiled without external signing support (required for external signing)"));
+ ui->externalSignerPath->setEnabled(false);
+#endif
/* Display elements init */
QDir translations(":translations");
diff --git a/src/qt/qrimagewidget.cpp b/src/qt/qrimagewidget.cpp
index f5200bb5c0..7cdd568644 100644
--- a/src/qt/qrimagewidget.cpp
+++ b/src/qt/qrimagewidget.cpp
@@ -27,8 +27,8 @@ QRImageWidget::QRImageWidget(QWidget *parent):
QLabel(parent), contextMenu(nullptr)
{
contextMenu = new QMenu(this);
- contextMenu->addAction(tr("Save Image…"), this, &QRImageWidget::saveImage);
- contextMenu->addAction(tr("Copy Image"), this, &QRImageWidget::copyImage);
+ contextMenu->addAction(tr("&Save Image…"), this, &QRImageWidget::saveImage);
+ contextMenu->addAction(tr("&Copy Image"), this, &QRImageWidget::copyImage);
}
bool QRImageWidget::setQR(const QString& data, const QString& text)
diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp
index 3f4d7f85e6..d47ee95826 100644
--- a/src/qt/receivecoinsdialog.cpp
+++ b/src/qt/receivecoinsdialog.cpp
@@ -44,11 +44,11 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWid
// context menu
contextMenu = new QMenu(this);
- contextMenu->addAction(tr("Copy URI"), this, &ReceiveCoinsDialog::copyURI);
- contextMenu->addAction(tr("Copy address"), this, &ReceiveCoinsDialog::copyAddress);
- copyLabelAction = contextMenu->addAction(tr("Copy label"), this, &ReceiveCoinsDialog::copyLabel);
- copyMessageAction = contextMenu->addAction(tr("Copy message"), this, &ReceiveCoinsDialog::copyMessage);
- copyAmountAction = contextMenu->addAction(tr("Copy amount"), this, &ReceiveCoinsDialog::copyAmount);
+ contextMenu->addAction(tr("Copy &URI"), this, &ReceiveCoinsDialog::copyURI);
+ contextMenu->addAction(tr("&Copy address"), this, &ReceiveCoinsDialog::copyAddress);
+ copyLabelAction = contextMenu->addAction(tr("Copy &label"), this, &ReceiveCoinsDialog::copyLabel);
+ copyMessageAction = contextMenu->addAction(tr("Copy &message"), this, &ReceiveCoinsDialog::copyMessage);
+ copyAmountAction = contextMenu->addAction(tr("Copy &amount"), this, &ReceiveCoinsDialog::copyAmount);
connect(ui->recentRequestsView, &QWidget::customContextMenuRequested, this, &ReceiveCoinsDialog::showMenu);
connect(ui->clearButton, &QPushButton::clicked, this, &ReceiveCoinsDialog::clear);
diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp
index abe7de8f89..41f22e9c34 100644
--- a/src/qt/receiverequestdialog.cpp
+++ b/src/qt/receiverequestdialog.cpp
@@ -90,7 +90,7 @@ void ReceiveRequestDialog::setInfo(const SendCoinsRecipient &_info)
ui->wallet_content->hide();
}
- ui->btnVerify->setVisible(this->model->wallet().hasExternalSigner());
+ ui->btnVerify->setVisible(model->wallet().hasExternalSigner());
connect(ui->btnVerify, &QPushButton::clicked, [this] {
model->displayAddress(info.address.toStdString());
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 9579e6dc24..ff4bfb16f6 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -675,11 +675,11 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_
// create peer table context menu
peersTableContextMenu = new QMenu(this);
- peersTableContextMenu->addAction(tr("Disconnect"), this, &RPCConsole::disconnectSelectedNode);
- peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 hour"), [this] { banSelectedNode(60 * 60); });
- peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 day"), [this] { banSelectedNode(60 * 60 * 24); });
- peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 week"), [this] { banSelectedNode(60 * 60 * 24 * 7); });
- peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 year"), [this] { banSelectedNode(60 * 60 * 24 * 365); });
+ peersTableContextMenu->addAction(tr("&Disconnect"), this, &RPCConsole::disconnectSelectedNode);
+ peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &hour"), [this] { banSelectedNode(60 * 60); });
+ peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 d&ay"), [this] { banSelectedNode(60 * 60 * 24); });
+ peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &week"), [this] { banSelectedNode(60 * 60 * 24 * 7); });
+ peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &year"), [this] { banSelectedNode(60 * 60 * 24 * 365); });
connect(ui->peerWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showPeersTableContextMenu);
// peer table signal handling - update peer details when selecting new node
@@ -701,7 +701,7 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_
// create ban table context menu
banTableContextMenu = new QMenu(this);
- banTableContextMenu->addAction(tr("Unban"), this, &RPCConsole::unbanSelectedNode);
+ banTableContextMenu->addAction(tr("&Unban"), this, &RPCConsole::unbanSelectedNode);
connect(ui->banlistWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showBanTableContextMenu);
// ban table signal handling - clear peer details when clicking a peer in the ban table
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index 6a5ec435cd..c9bf757dfc 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -200,12 +200,14 @@ void SendCoinsDialog::setModel(WalletModel *_model)
ui->optInRBF->setCheckState(Qt::Checked);
if (model->wallet().hasExternalSigner()) {
+ //: "device" usually means a hardware wallet
ui->sendButton->setText(tr("Sign on device"));
if (gArgs.GetArg("-signer", "") != "") {
ui->sendButton->setEnabled(true);
ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
} else {
ui->sendButton->setEnabled(false);
+ //: "External signer" means using devices such as hardware wallets.
ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
}
} else if (model->wallet().privateKeysDisabled()) {
@@ -426,11 +428,13 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
return;
}
if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
+ //: "External signer" means using devices such as hardware wallets.
QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found");
send_failure = true;
return;
}
if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
+ //: "External signer" means using devices such as hardware wallets.
QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure");
send_failure = true;
return;
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp
index 1e8e012dcf..4b1a546c7c 100644
--- a/src/qt/transactionview.cpp
+++ b/src/qt/transactionview.cpp
@@ -163,19 +163,19 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
contextMenu = new QMenu(this);
contextMenu->setObjectName("contextMenu");
- copyAddressAction = contextMenu->addAction(tr("Copy address"), this, &TransactionView::copyAddress);
- copyLabelAction = contextMenu->addAction(tr("Copy label"), this, &TransactionView::copyLabel);
- contextMenu->addAction(tr("Copy amount"), this, &TransactionView::copyAmount);
- contextMenu->addAction(tr("Copy transaction ID"), this, &TransactionView::copyTxID);
- contextMenu->addAction(tr("Copy raw transaction"), this, &TransactionView::copyTxHex);
- contextMenu->addAction(tr("Copy full transaction details"), this, &TransactionView::copyTxPlainText);
- contextMenu->addAction(tr("Show transaction details"), this, &TransactionView::showDetails);
+ copyAddressAction = contextMenu->addAction(tr("&Copy address"), this, &TransactionView::copyAddress);
+ copyLabelAction = contextMenu->addAction(tr("Copy &label"), this, &TransactionView::copyLabel);
+ contextMenu->addAction(tr("Copy &amount"), this, &TransactionView::copyAmount);
+ contextMenu->addAction(tr("Copy transaction &ID"), this, &TransactionView::copyTxID);
+ contextMenu->addAction(tr("Copy &raw transaction"), this, &TransactionView::copyTxHex);
+ contextMenu->addAction(tr("Copy full transaction &details"), this, &TransactionView::copyTxPlainText);
+ contextMenu->addAction(tr("&Show transaction details"), this, &TransactionView::showDetails);
contextMenu->addSeparator();
- bumpFeeAction = contextMenu->addAction(tr("Increase transaction fee"));
+ bumpFeeAction = contextMenu->addAction(tr("Increase transaction &fee"));
GUIUtil::ExceptionSafeConnect(bumpFeeAction, &QAction::triggered, this, &TransactionView::bumpFee);
bumpFeeAction->setObjectName("bumpFeeAction");
- abandonAction = contextMenu->addAction(tr("Abandon transaction"), this, &TransactionView::abandonTx);
- contextMenu->addAction(tr("Edit address label"), this, &TransactionView::editLabel);
+ abandonAction = contextMenu->addAction(tr("A&bandon transaction"), this, &TransactionView::abandonTx);
+ contextMenu->addAction(tr("&Edit address label"), this, &TransactionView::editLabel);
connect(dateWidget, qOverload<int>(&QComboBox::activated), this, &TransactionView::chooseDate);
connect(typeWidget, qOverload<int>(&QComboBox::activated), this, &TransactionView::chooseType);
diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp
index 7e5790fd87..3cceb5ca5a 100644
--- a/src/qt/walletcontroller.cpp
+++ b/src/qt/walletcontroller.cpp
@@ -11,6 +11,7 @@
#include <qt/guiutil.h>
#include <qt/walletmodel.h>
+#include <external_signer.h>
#include <interfaces/handler.h>
#include <interfaces/node.h>
#include <util/string.h>
@@ -295,7 +296,6 @@ void CreateWalletActivity::create()
{
m_create_wallet_dialog = new CreateWalletDialog(m_parent_widget);
-#ifdef ENABLE_EXTERNAL_SIGNER
std::vector<ExternalSigner> signers;
try {
signers = node().externalSigners();
@@ -303,7 +303,6 @@ void CreateWalletActivity::create()
QMessageBox::critical(nullptr, tr("Can't list signers"), e.what());
}
m_create_wallet_dialog->setSigners(signers);
-#endif
m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal);
m_create_wallet_dialog->show();
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 414c6637a5..ccb3123714 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -753,7 +753,8 @@ static RPCHelpMan signrawtransactionwithkey()
},
},
},
- {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type. Must be one of:\n"
+ {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT"}, "The signature hash type. Must be one of:\n"
+ " \"DEFAULT\"\n"
" \"ALL\"\n"
" \"NONE\"\n"
" \"SINGLE\"\n"
@@ -1655,6 +1656,7 @@ static RPCHelpMan utxoupdatepsbt()
}
// Fill the inputs
+ const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
PSBTInput& input = psbtx.inputs.at(i);
@@ -1671,7 +1673,7 @@ static RPCHelpMan utxoupdatepsbt()
// Update script/keypath information using descriptor data.
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures
// we don't actually care about those here, in fact.
- SignPSBTInput(public_provider, psbtx, i, /* sighash_type */ 1);
+ SignPSBTInput(public_provider, psbtx, i, &txdata, /* sighash_type */ 1);
}
// Update script/keypath information using descriptor data.
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 51cf8a7d62..84a8b06c5c 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -843,7 +843,9 @@ protected:
XOnlyPubKey xpk(keys[0]);
if (!xpk.IsFullyValid()) return {};
builder.Finalize(xpk);
- return Vector(GetScriptForDestination(builder.GetOutput()));
+ WitnessV1Taproot output = builder.GetOutput();
+ out.tr_spenddata[output].Merge(builder.GetSpendData());
+ return Vector(GetScriptForDestination(output));
}
bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, bool priv, bool normalized) const override
{
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
index 3c3c3ac1a8..2dd173ee20 100644
--- a/src/script/interpreter.cpp
+++ b/src/script/interpreter.cpp
@@ -1420,7 +1420,7 @@ uint256 GetSpentScriptsSHA256(const std::vector<CTxOut>& outputs_spent)
} // namespace
template <class T>
-void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent_outputs)
+void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent_outputs, bool force)
{
assert(!m_spent_outputs_ready);
@@ -1431,9 +1431,9 @@ void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent
}
// Determine which precomputation-impacting features this transaction uses.
- bool uses_bip143_segwit = false;
- bool uses_bip341_taproot = false;
- for (size_t inpos = 0; inpos < txTo.vin.size(); ++inpos) {
+ bool uses_bip143_segwit = force;
+ bool uses_bip341_taproot = force;
+ for (size_t inpos = 0; inpos < txTo.vin.size() && !(uses_bip143_segwit && uses_bip341_taproot); ++inpos) {
if (!txTo.vin[inpos].scriptWitness.IsNull()) {
if (m_spent_outputs_ready && m_spent_outputs[inpos].scriptPubKey.size() == 2 + WITNESS_V1_TAPROOT_SIZE &&
m_spent_outputs[inpos].scriptPubKey[0] == OP_1) {
@@ -1478,8 +1478,8 @@ PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo)
}
// explicit instantiation
-template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector<CTxOut>&& spent_outputs);
-template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector<CTxOut>&& spent_outputs);
+template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector<CTxOut>&& spent_outputs, bool force);
+template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector<CTxOut>&& spent_outputs, bool force);
template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo);
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
@@ -1711,7 +1711,7 @@ bool GenericTransactionSignatureChecker<T>::CheckSchnorrSignature(Span<const uns
if (hashtype == SIGHASH_DEFAULT) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE);
}
uint256 sighash;
- assert(this->txdata);
+ if (!this->txdata) return HandleMissingData(m_mdb);
if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, *this->txdata, m_mdb)) {
return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE);
}
diff --git a/src/script/interpreter.h b/src/script/interpreter.h
index fa4ee83e04..ced5c28bc1 100644
--- a/src/script/interpreter.h
+++ b/src/script/interpreter.h
@@ -168,7 +168,7 @@ struct PrecomputedTransactionData
PrecomputedTransactionData() = default;
template <class T>
- void Init(const T& tx, std::vector<CTxOut>&& spent_outputs);
+ void Init(const T& tx, std::vector<CTxOut>&& spent_outputs, bool force = false);
template <class T>
explicit PrecomputedTransactionData(const T& tx);
@@ -260,6 +260,9 @@ enum class MissingDataBehavior
FAIL, //!< Just act as if the signature was invalid
};
+template<typename T>
+bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache, MissingDataBehavior mdb);
+
template <class T>
class GenericTransactionSignatureChecker : public BaseSignatureChecker
{
diff --git a/src/script/sign.cpp b/src/script/sign.cpp
index da0092f9e3..65276f641f 100644
--- a/src/script/sign.cpp
+++ b/src/script/sign.cpp
@@ -11,13 +11,28 @@
#include <script/signingprovider.h>
#include <script/standard.h>
#include <uint256.h>
+#include <util/vector.h>
typedef std::vector<unsigned char> valtype;
-MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn, MissingDataBehavior::FAIL) {}
+MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn)
+ : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn, MissingDataBehavior::FAIL),
+ m_txdata(nullptr)
+{
+}
+
+MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData* txdata, int nHashTypeIn)
+ : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn),
+ checker(txdata ? MutableTransactionSignatureChecker(txTo, nIn, amount, *txdata, MissingDataBehavior::FAIL) :
+ MutableTransactionSignatureChecker(txTo, nIn, amount, MissingDataBehavior::FAIL)),
+ m_txdata(txdata)
+{
+}
bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const
{
+ assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0);
+
CKey key;
if (!provider.GetKey(address, key))
return false;
@@ -26,13 +41,61 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid
if (sigversion == SigVersion::WITNESS_V0 && !key.IsCompressed())
return false;
- // Signing for witness scripts needs the amount.
- if (sigversion == SigVersion::WITNESS_V0 && amount < 0) return false;
+ // Signing without known amount does not work in witness scripts.
+ if (sigversion == SigVersion::WITNESS_V0 && !MoneyRange(amount)) return false;
+
+ // BASE/WITNESS_V0 signatures don't support explicit SIGHASH_DEFAULT, use SIGHASH_ALL instead.
+ const int hashtype = nHashType == SIGHASH_DEFAULT ? SIGHASH_ALL : nHashType;
- uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion);
+ uint256 hash = SignatureHash(scriptCode, *txTo, nIn, hashtype, amount, sigversion, m_txdata);
if (!key.Sign(hash, vchSig))
return false;
- vchSig.push_back((unsigned char)nHashType);
+ vchSig.push_back((unsigned char)hashtype);
+ return true;
+}
+
+bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const
+{
+ assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
+
+ CKey key;
+ {
+ // For now, use the old full pubkey-based key derivation logic. As it indexed by
+ // Hash160(full pubkey), we need to try both a version prefixed with 0x02, and one
+ // with 0x03.
+ unsigned char b[33] = {0x02};
+ std::copy(pubkey.begin(), pubkey.end(), b + 1);
+ CPubKey fullpubkey;
+ fullpubkey.Set(b, b + 33);
+ CKeyID keyid = fullpubkey.GetID();
+ if (!provider.GetKey(keyid, key)) {
+ b[0] = 0x03;
+ fullpubkey.Set(b, b + 33);
+ CKeyID keyid = fullpubkey.GetID();
+ if (!provider.GetKey(keyid, key)) return false;
+ }
+ }
+
+ // BIP341/BIP342 signing needs lots of precomputed transaction data. While some
+ // (non-SIGHASH_DEFAULT) sighash modes exist that can work with just some subset
+ // of data present, for now, only support signing when everything is provided.
+ if (!m_txdata || !m_txdata->m_bip341_taproot_ready || !m_txdata->m_spent_outputs_ready) return false;
+
+ ScriptExecutionData execdata;
+ execdata.m_annex_init = true;
+ execdata.m_annex_present = false; // Only support annex-less signing for now.
+ if (sigversion == SigVersion::TAPSCRIPT) {
+ execdata.m_codeseparator_pos_init = true;
+ execdata.m_codeseparator_pos = 0xFFFFFFFF; // Only support non-OP_CODESEPARATOR BIP342 signing for now.
+ if (!leaf_hash) return false; // BIP342 signing needs leaf hash.
+ execdata.m_tapleaf_hash_init = true;
+ execdata.m_tapleaf_hash = *leaf_hash;
+ }
+ uint256 hash;
+ if (!SignatureHashSchnorr(hash, execdata, *txTo, nIn, nHashType, sigversion, *m_txdata, MissingDataBehavior::FAIL)) return false;
+ sig.resize(64);
+ if (!key.SignSchnorr(hash, sig, merkle_root, nullptr)) return false;
+ if (nHashType) sig.push_back(nHashType);
return true;
}
@@ -92,6 +155,86 @@ static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdat
return false;
}
+static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector<unsigned char>& sig_out, const XOnlyPubKey& pubkey, const uint256& leaf_hash, SigVersion sigversion)
+{
+ auto lookup_key = std::make_pair(pubkey, leaf_hash);
+ auto it = sigdata.taproot_script_sigs.find(lookup_key);
+ if (it != sigdata.taproot_script_sigs.end()) {
+ sig_out = it->second;
+ }
+ if (creator.CreateSchnorrSig(provider, sig_out, pubkey, &leaf_hash, nullptr, sigversion)) {
+ sigdata.taproot_script_sigs[lookup_key] = sig_out;
+ return true;
+ }
+ return false;
+}
+
+static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatureCreator& creator, SignatureData& sigdata, int leaf_version, const CScript& script, std::vector<valtype>& result)
+{
+ // Only BIP342 tapscript signing is supported for now.
+ if (leaf_version != TAPROOT_LEAF_TAPSCRIPT) return false;
+ SigVersion sigversion = SigVersion::TAPSCRIPT;
+
+ uint256 leaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(leaf_version) << script).GetSHA256();
+
+ // <xonly pubkey> OP_CHECKSIG
+ if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) {
+ XOnlyPubKey pubkey(MakeSpan(script).subspan(1, 32));
+ std::vector<unsigned char> sig;
+ if (CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion)) {
+ result = Vector(std::move(sig));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCreator& creator, const WitnessV1Taproot& output, SignatureData& sigdata, std::vector<valtype>& result)
+{
+ TaprootSpendData spenddata;
+
+ // Gather information about this output.
+ if (provider.GetTaprootSpendData(output, spenddata)) {
+ sigdata.tr_spenddata.Merge(spenddata);
+ }
+
+ // Try key path spending.
+ {
+ std::vector<unsigned char> sig;
+ if (sigdata.taproot_key_path_sig.size() == 0) {
+ if (creator.CreateSchnorrSig(provider, sig, spenddata.internal_key, nullptr, &spenddata.merkle_root, SigVersion::TAPROOT)) {
+ sigdata.taproot_key_path_sig = sig;
+ }
+ }
+ if (sigdata.taproot_key_path_sig.size()) {
+ result = Vector(sigdata.taproot_key_path_sig);
+ return true;
+ }
+ }
+
+ // Try script path spending.
+ std::vector<std::vector<unsigned char>> smallest_result_stack;
+ for (const auto& [key, control_blocks] : sigdata.tr_spenddata.scripts) {
+ const auto& [script, leaf_ver] = key;
+ std::vector<std::vector<unsigned char>> result_stack;
+ if (SignTaprootScript(provider, creator, sigdata, leaf_ver, script, result_stack)) {
+ result_stack.emplace_back(std::begin(script), std::end(script)); // Push the script
+ result_stack.push_back(*control_blocks.begin()); // Push the smallest control block
+ if (smallest_result_stack.size() == 0 ||
+ GetSerializeSize(result_stack, PROTOCOL_VERSION) < GetSerializeSize(smallest_result_stack, PROTOCOL_VERSION)) {
+ smallest_result_stack = std::move(result_stack);
+ }
+ }
+ }
+ if (smallest_result_stack.size() != 0) {
+ result = std::move(smallest_result_stack);
+ return true;
+ }
+
+ return false;
+}
+
/**
* Sign scriptPubKey using signature made with creator.
* Signatures are returned in scriptSigRet (or returns false if scriptPubKey can't be signed),
@@ -113,7 +256,6 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
case TxoutType::NONSTANDARD:
case TxoutType::NULL_DATA:
case TxoutType::WITNESS_UNKNOWN:
- case TxoutType::WITNESS_V1_TAPROOT:
return false;
case TxoutType::PUBKEY:
if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]), scriptPubKey, sigversion)) return false;
@@ -175,6 +317,9 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
// Could not find witnessScript, add to missing
sigdata.missing_witness_script = uint256(vSolutions[0]);
return false;
+
+ case TxoutType::WITNESS_V1_TAPROOT:
+ return SignTaproot(provider, creator, WitnessV1Taproot(XOnlyPubKey{vSolutions[0]}), sigdata, ret);
} // no default case, so the compiler can warn about missing cases
assert(false);
}
@@ -205,7 +350,6 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato
bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE, sigdata);
bool P2SH = false;
CScript subscript;
- sigdata.scriptWitness.stack.clear();
if (solved && whichType == TxoutType::SCRIPTHASH)
{
@@ -238,10 +382,17 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato
sigdata.scriptWitness.stack = result;
sigdata.witness = true;
result.clear();
+ } else if (whichType == TxoutType::WITNESS_V1_TAPROOT && !P2SH) {
+ sigdata.witness = true;
+ if (solved) {
+ sigdata.scriptWitness.stack = std::move(result);
+ }
+ result.clear();
} else if (solved && whichType == TxoutType::WITNESS_UNKNOWN) {
sigdata.witness = true;
}
+ if (!sigdata.witness) sigdata.scriptWitness.stack.clear();
if (P2SH) {
result.push_back(std::vector<unsigned char>(subscript.begin(), subscript.end()));
}
@@ -402,6 +553,7 @@ class DummySignatureChecker final : public BaseSignatureChecker
public:
DummySignatureChecker() {}
bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return true; }
+ bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror) const override { return true; }
};
const DummySignatureChecker DUMMY_CHECKER;
@@ -427,6 +579,11 @@ public:
vchSig[6 + m_r_len + m_s_len] = SIGHASH_ALL;
return true;
}
+ bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* tweak, SigVersion sigversion) const override
+ {
+ sig.assign(64, '\000');
+ return true;
+ }
};
}
@@ -476,6 +633,26 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
// Use CTransaction for the constant parts of the
// transaction to avoid rehashing.
const CTransaction txConst(mtx);
+
+ PrecomputedTransactionData txdata;
+ std::vector<CTxOut> spent_outputs;
+ spent_outputs.resize(mtx.vin.size());
+ bool have_all_spent_outputs = true;
+ for (unsigned int i = 0; i < mtx.vin.size(); i++) {
+ CTxIn& txin = mtx.vin[i];
+ auto coin = coins.find(txin.prevout);
+ if (coin == coins.end() || coin->second.IsSpent()) {
+ have_all_spent_outputs = false;
+ } else {
+ spent_outputs[i] = CTxOut(coin->second.out.nValue, coin->second.out.scriptPubKey);
+ }
+ }
+ if (have_all_spent_outputs) {
+ txdata.Init(txConst, std::move(spent_outputs), true);
+ } else {
+ txdata.Init(txConst, {}, true);
+ }
+
// Sign what we can:
for (unsigned int i = 0; i < mtx.vin.size(); i++) {
CTxIn& txin = mtx.vin[i];
@@ -490,7 +667,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
SignatureData sigdata = DataFromTransaction(mtx, i, coin->second.out);
// Only sign SIGHASH_SINGLE if there's a corresponding output:
if (!fHashSingle || (i < mtx.vout.size())) {
- ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata);
+ ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, &txdata, nHashType), prevPubKey, sigdata);
}
UpdateInput(txin, sigdata);
@@ -502,7 +679,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
}
ScriptError serror = SCRIPT_ERR_OK;
- if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, MissingDataBehavior::FAIL), &serror)) {
+ if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, txdata, MissingDataBehavior::FAIL), &serror)) {
if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) {
// Unable to sign input and verification failed (possible attempt to partially sign).
input_errors[i] = "Unable to sign input, invalid stack size (possibly missing key)";
diff --git a/src/script/sign.h b/src/script/sign.h
index a1cfe1574d..b4e7318892 100644
--- a/src/script/sign.h
+++ b/src/script/sign.h
@@ -11,13 +11,13 @@
#include <pubkey.h>
#include <script/interpreter.h>
#include <script/keyorigin.h>
+#include <script/standard.h>
#include <span.h>
#include <streams.h>
class CKey;
class CKeyID;
class CScript;
-class CScriptID;
class CTransaction;
class SigningProvider;
@@ -31,6 +31,7 @@ public:
/** Create a singular (non-script) signature. */
virtual bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const =0;
+ virtual bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const =0;
};
/** A signature creator for transactions. */
@@ -40,11 +41,14 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator {
int nHashType;
CAmount amount;
const MutableTransactionSignatureChecker checker;
+ const PrecomputedTransactionData* m_txdata;
public:
MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn = SIGHASH_ALL);
+ MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData* txdata, int nHashTypeIn = SIGHASH_ALL);
const BaseSignatureChecker& Checker() const override { return checker; }
bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override;
+ bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const override;
};
/** A signature creator that just produces 71-byte empty signatures. */
@@ -64,8 +68,11 @@ struct SignatureData {
CScript redeem_script; ///< The redeemScript (if any) for the input
CScript witness_script; ///< The witnessScript (if any) for the input. witnessScripts are used in P2WSH outputs.
CScriptWitness scriptWitness; ///< The scriptWitness of an input. Contains complete signatures or the traditional partial signatures format. scriptWitness is part of a transaction input per BIP 144.
+ TaprootSpendData tr_spenddata; ///< Taproot spending data.
std::map<CKeyID, SigPair> signatures; ///< BIP 174 style partial signatures for the input. May contain all signatures necessary for producing a final scriptSig or scriptWitness.
std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> misc_pubkeys;
+ std::vector<unsigned char> taproot_key_path_sig; /// Schnorr signature for key path spending
+ std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> taproot_script_sigs; ///< (Partial) schnorr signatures, indexed by XOnlyPubKey and leaf_hash.
std::vector<CKeyID> missing_pubkeys; ///< KeyIDs of pubkeys which could not be found
std::vector<CKeyID> missing_sigs; ///< KeyIDs of pubkeys for signatures which could not be found
uint160 missing_redeem_script; ///< ScriptID of the missing redeemScript (if any)
diff --git a/src/script/signingprovider.cpp b/src/script/signingprovider.cpp
index 9781ec32af..b80fbe22ce 100644
--- a/src/script/signingprovider.cpp
+++ b/src/script/signingprovider.cpp
@@ -44,6 +44,11 @@ bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& inf
return m_provider->GetKeyOrigin(keyid, info);
}
+bool HidingSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const
+{
+ return m_provider->GetTaprootSpendData(output_key, spenddata);
+}
+
bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); }
bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); }
bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const
@@ -54,6 +59,10 @@ bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info)
return ret;
}
bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); }
+bool FlatSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const
+{
+ return LookupHelper(tr_spenddata, output_key, spenddata);
+}
FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b)
{
@@ -66,6 +75,10 @@ FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvide
ret.keys.insert(b.keys.begin(), b.keys.end());
ret.origins = a.origins;
ret.origins.insert(b.origins.begin(), b.origins.end());
+ ret.tr_spenddata = a.tr_spenddata;
+ for (const auto& [output_key, spenddata] : b.tr_spenddata) {
+ ret.tr_spenddata[output_key].Merge(spenddata);
+ }
return ret;
}
diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h
index 76f31d2f6f..939ae10622 100644
--- a/src/script/signingprovider.h
+++ b/src/script/signingprovider.h
@@ -25,6 +25,7 @@ public:
virtual bool GetKey(const CKeyID &address, CKey& key) const { return false; }
virtual bool HaveKey(const CKeyID &address) const { return false; }
virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; }
+ virtual bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const { return false; }
};
extern const SigningProvider& DUMMY_SIGNING_PROVIDER;
@@ -42,6 +43,7 @@ public:
bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override;
bool GetKey(const CKeyID& keyid, CKey& key) const override;
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
+ bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override;
};
struct FlatSigningProvider final : public SigningProvider
@@ -50,11 +52,13 @@ struct FlatSigningProvider final : public SigningProvider
std::map<CKeyID, CPubKey> pubkeys;
std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> origins;
std::map<CKeyID, CKey> keys;
+ std::map<XOnlyPubKey, TaprootSpendData> tr_spenddata; /** Map from output key to spend data. */
bool GetCScript(const CScriptID& scriptid, CScript& script) const override;
bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override;
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
bool GetKey(const CKeyID& keyid, CKey& key) const override;
+ bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override;
};
FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b);
diff --git a/src/script/standard.cpp b/src/script/standard.cpp
index a4b11cc0a9..748f00dda5 100644
--- a/src/script/standard.cpp
+++ b/src/script/standard.cpp
@@ -377,6 +377,16 @@ bool IsValidDestination(const CTxDestination& dest) {
/*static*/ TaprootBuilder::NodeInfo TaprootBuilder::Combine(NodeInfo&& a, NodeInfo&& b)
{
NodeInfo ret;
+ /* Iterate over all tracked leaves in a, add b's hash to their Merkle branch, and move them to ret. */
+ for (auto& leaf : a.leaves) {
+ leaf.merkle_branch.push_back(b.hash);
+ ret.leaves.emplace_back(std::move(leaf));
+ }
+ /* Iterate over all tracked leaves in b, add a's hash to their Merkle branch, and move them to ret. */
+ for (auto& leaf : b.leaves) {
+ leaf.merkle_branch.push_back(a.hash);
+ ret.leaves.emplace_back(std::move(leaf));
+ }
/* Lexicographically sort a and b's hash, and compute parent hash. */
if (a.hash < b.hash) {
ret.hash = (CHashWriter(HASHER_TAPBRANCH) << a.hash << b.hash).GetSHA256();
@@ -386,6 +396,21 @@ bool IsValidDestination(const CTxDestination& dest) {
return ret;
}
+void TaprootSpendData::Merge(TaprootSpendData other)
+{
+ // TODO: figure out how to better deal with conflicting information
+ // being merged.
+ if (internal_key.IsNull() && !other.internal_key.IsNull()) {
+ internal_key = other.internal_key;
+ }
+ if (merkle_root.IsNull() && !other.merkle_root.IsNull()) {
+ merkle_root = other.merkle_root;
+ }
+ for (auto& [key, control_blocks] : other.scripts) {
+ scripts[key].merge(std::move(control_blocks));
+ }
+}
+
void TaprootBuilder::Insert(TaprootBuilder::NodeInfo&& node, int depth)
{
assert(depth >= 0 && (size_t)depth <= TAPROOT_CONTROL_MAX_NODE_COUNT);
@@ -435,13 +460,14 @@ void TaprootBuilder::Insert(TaprootBuilder::NodeInfo&& node, int depth)
return branch.size() == 0 || (branch.size() == 1 && branch[0]);
}
-TaprootBuilder& TaprootBuilder::Add(int depth, const CScript& script, int leaf_version)
+TaprootBuilder& TaprootBuilder::Add(int depth, const CScript& script, int leaf_version, bool track)
{
assert((leaf_version & ~TAPROOT_LEAF_MASK) == 0);
if (!IsValid()) return *this;
- /* Construct NodeInfo object with leaf hash. */
+ /* Construct NodeInfo object with leaf hash and (if track is true) also leaf information. */
NodeInfo node;
node.hash = (CHashWriter{HASHER_TAPLEAF} << uint8_t(leaf_version) << script).GetSHA256();
+ if (track) node.leaves.emplace_back(LeafInfo{script, leaf_version, {}});
/* Insert into the branch. */
Insert(std::move(node), depth);
return *this;
@@ -464,8 +490,33 @@ TaprootBuilder& TaprootBuilder::Finalize(const XOnlyPubKey& internal_key)
m_internal_key = internal_key;
auto ret = m_internal_key.CreateTapTweak(m_branch.size() == 0 ? nullptr : &m_branch[0]->hash);
assert(ret.has_value());
- std::tie(m_output_key, std::ignore) = *ret;
+ std::tie(m_output_key, m_parity) = *ret;
return *this;
}
WitnessV1Taproot TaprootBuilder::GetOutput() { return WitnessV1Taproot{m_output_key}; }
+
+TaprootSpendData TaprootBuilder::GetSpendData() const
+{
+ TaprootSpendData spd;
+ spd.merkle_root = m_branch.size() == 0 ? uint256() : m_branch[0]->hash;
+ spd.internal_key = m_internal_key;
+ if (m_branch.size()) {
+ // If any script paths exist, they have been combined into the root m_branch[0]
+ // by now. Compute the control block for each of its tracked leaves, and put them in
+ // spd.scripts.
+ for (const auto& leaf : m_branch[0]->leaves) {
+ std::vector<unsigned char> control_block;
+ control_block.resize(TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * leaf.merkle_branch.size());
+ control_block[0] = leaf.leaf_version | (m_parity ? 1 : 0);
+ std::copy(m_internal_key.begin(), m_internal_key.end(), control_block.begin() + 1);
+ if (leaf.merkle_branch.size()) {
+ std::copy(leaf.merkle_branch[0].begin(),
+ leaf.merkle_branch[0].begin() + TAPROOT_CONTROL_NODE_SIZE * leaf.merkle_branch.size(),
+ control_block.begin() + TAPROOT_CONTROL_BASE_SIZE);
+ }
+ spd.scripts[{leaf.script, leaf.leaf_version}].insert(std::move(control_block));
+ }
+ }
+ return spd;
+}
diff --git a/src/script/standard.h b/src/script/standard.h
index d7ea5cef27..285dd4c116 100644
--- a/src/script/standard.h
+++ b/src/script/standard.h
@@ -11,6 +11,7 @@
#include <uint256.h>
#include <util/hash_type.h>
+#include <map>
#include <string>
#include <variant>
@@ -209,15 +210,50 @@ CScript GetScriptForRawPubKey(const CPubKey& pubkey);
/** Generate a multisig script. */
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
+struct ShortestVectorFirstComparator
+{
+ bool operator()(const std::vector<unsigned char>& a, const std::vector<unsigned char>& b) const
+ {
+ if (a.size() < b.size()) return true;
+ if (a.size() > b.size()) return false;
+ return a < b;
+ }
+};
+
+struct TaprootSpendData
+{
+ /** The BIP341 internal key. */
+ XOnlyPubKey internal_key;
+ /** The Merkle root of the script tree (0 if no scripts). */
+ uint256 merkle_root;
+ /** Map from (script, leaf_version) to (sets of) control blocks.
+ * The control blocks are sorted by size, so that the signing logic can
+ * easily prefer the cheapest one. */
+ std::map<std::pair<CScript, int>, std::set<std::vector<unsigned char>, ShortestVectorFirstComparator>> scripts;
+ /** Merge other TaprootSpendData (for the same scriptPubKey) into this. */
+ void Merge(TaprootSpendData other);
+};
+
/** Utility class to construct Taproot outputs from internal key and script tree. */
class TaprootBuilder
{
private:
+ /** Information about a tracked leaf in the Merkle tree. */
+ struct LeafInfo
+ {
+ CScript script; //!< The script.
+ int leaf_version; //!< The leaf version for that script.
+ std::vector<uint256> merkle_branch; //!< The hashing partners above this leaf.
+ };
+
/** Information associated with a node in the Merkle tree. */
struct NodeInfo
{
/** Merkle hash of this node. */
uint256 hash;
+ /** Tracked leaves underneath this node (either from the node itself, or its children).
+ * The merkle_branch field for each is the partners to get to *this* node. */
+ std::vector<LeafInfo> leaves;
};
/** Whether the builder is in a valid state so far. */
bool m_valid = true;
@@ -260,7 +296,8 @@ private:
std::vector<std::optional<NodeInfo>> m_branch;
XOnlyPubKey m_internal_key; //!< The internal key, set when finalizing.
- XOnlyPubKey m_output_key; //!< The output key, computed when finalizing. */
+ XOnlyPubKey m_output_key; //!< The output key, computed when finalizing.
+ bool m_parity; //!< The tweak parity, computed when finalizing.
/** Combine information about a parent Merkle tree node from its child nodes. */
static NodeInfo Combine(NodeInfo&& a, NodeInfo&& b);
@@ -269,8 +306,9 @@ private:
public:
/** Add a new script at a certain depth in the tree. Add() operations must be called
- * in depth-first traversal order of binary tree. */
- TaprootBuilder& Add(int depth, const CScript& script, int leaf_version);
+ * in depth-first traversal order of binary tree. If track is true, it will be included in
+ * the GetSpendData() output. */
+ TaprootBuilder& Add(int depth, const CScript& script, int leaf_version, bool track = true);
/** Like Add(), but for a Merkle node with a given hash to the tree. */
TaprootBuilder& AddOmitted(int depth, const uint256& hash);
/** Finalize the construction. Can only be called when IsComplete() is true.
@@ -285,6 +323,8 @@ public:
WitnessV1Taproot GetOutput();
/** Check if a list of depths is legal (will lead to IsComplete()). */
static bool ValidDepths(const std::vector<int>& depths);
+ /** Compute spending data (after Finalize()). */
+ TaprootSpendData GetSpendData() const;
};
#endif // BITCOIN_SCRIPT_STANDARD_H
diff --git a/src/test/fuzz/base_encode_decode.cpp b/src/test/fuzz/base_encode_decode.cpp
index 4470e13a61..2b4f15115b 100644
--- a/src/test/fuzz/base_encode_decode.cpp
+++ b/src/test/fuzz/base_encode_decode.cpp
@@ -14,7 +14,12 @@
#include <string>
#include <vector>
-FUZZ_TARGET(base_encode_decode)
+void initialize_base_encode_decode()
+{
+ static const ECCVerifyHandle verify_handle;
+}
+
+FUZZ_TARGET_INIT(base_encode_decode, initialize_base_encode_decode)
{
const std::string random_encoded_string(buffer.begin(), buffer.end());
diff --git a/src/test/fuzz/crypto.cpp b/src/test/fuzz/crypto.cpp
index eeeac18968..f83747e424 100644
--- a/src/test/fuzz/crypto.cpp
+++ b/src/test/fuzz/crypto.cpp
@@ -19,6 +19,10 @@
FUZZ_TARGET(crypto)
{
+ // Hashing is expensive with sanitizers enabled, so limit the number of
+ // calls
+ int limit_max_ops{30};
+
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
std::vector<uint8_t> data = ConsumeRandomLengthByteVector(fuzzed_data_provider);
if (data.empty()) {
@@ -36,7 +40,7 @@ FUZZ_TARGET(crypto)
SHA3_256 sha3;
CSipHasher sip_hasher{fuzzed_data_provider.ConsumeIntegral<uint64_t>(), fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
- while (fuzzed_data_provider.ConsumeBool()) {
+ while (--limit_max_ops >= 0 && fuzzed_data_provider.ConsumeBool()) {
CallOneOf(
fuzzed_data_provider,
[&] {
diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp
index 1290c78712..721e4360d0 100644
--- a/src/test/fuzz/deserialize.cpp
+++ b/src/test/fuzz/deserialize.cpp
@@ -53,9 +53,9 @@ struct invalid_fuzzing_input_exception : public std::exception {
};
template <typename T>
-CDataStream Serialize(const T& obj, const int version = INIT_PROTO_VERSION)
+CDataStream Serialize(const T& obj, const int version = INIT_PROTO_VERSION, const int ser_type = SER_NETWORK)
{
- CDataStream ds(SER_NETWORK, version);
+ CDataStream ds(ser_type, version);
ds << obj;
return ds;
}
@@ -69,9 +69,9 @@ T Deserialize(CDataStream ds)
}
template <typename T>
-void DeserializeFromFuzzingInput(FuzzBufferType buffer, T& obj, const std::optional<int> protocol_version = std::nullopt)
+void DeserializeFromFuzzingInput(FuzzBufferType buffer, T& obj, const std::optional<int> protocol_version = std::nullopt, const int ser_type = SER_NETWORK)
{
- CDataStream ds(buffer, SER_NETWORK, INIT_PROTO_VERSION);
+ CDataStream ds(buffer, ser_type, INIT_PROTO_VERSION);
if (protocol_version) {
ds.SetVersion(*protocol_version);
} else {
@@ -92,9 +92,9 @@ void DeserializeFromFuzzingInput(FuzzBufferType buffer, T& obj, const std::optio
}
template <typename T>
-void AssertEqualAfterSerializeDeserialize(const T& obj, const int version = INIT_PROTO_VERSION)
+void AssertEqualAfterSerializeDeserialize(const T& obj, const int version = INIT_PROTO_VERSION, const int ser_type = SER_NETWORK)
{
- assert(Deserialize<T>(Serialize(obj, version)) == obj);
+ assert(Deserialize<T>(Serialize(obj, version, ser_type)) == obj);
}
} // namespace
@@ -136,8 +136,7 @@ FUZZ_TARGET_DESERIALIZE(partial_merkle_tree_deserialize, {
FUZZ_TARGET_DESERIALIZE(pub_key_deserialize, {
CPubKey pub_key;
DeserializeFromFuzzingInput(buffer, pub_key);
- // TODO: The following equivalence should hold for CPubKey? Fix.
- // AssertEqualAfterSerializeDeserialize(pub_key);
+ AssertEqualAfterSerializeDeserialize(pub_key);
})
FUZZ_TARGET_DESERIALIZE(script_deserialize, {
CScript script;
@@ -251,9 +250,37 @@ FUZZ_TARGET_DESERIALIZE(messageheader_deserialize, {
DeserializeFromFuzzingInput(buffer, mh);
(void)mh.IsCommandValid();
})
-FUZZ_TARGET_DESERIALIZE(address_deserialize, {
+FUZZ_TARGET_DESERIALIZE(address_deserialize_v1_notime, {
CAddress a;
- DeserializeFromFuzzingInput(buffer, a);
+ DeserializeFromFuzzingInput(buffer, a, INIT_PROTO_VERSION);
+ // A CAddress without nTime (as is expected under INIT_PROTO_VERSION) will roundtrip
+ // in all 5 formats (with/without nTime, v1/v2, network/disk)
+ AssertEqualAfterSerializeDeserialize(a, INIT_PROTO_VERSION);
+ AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION);
+ AssertEqualAfterSerializeDeserialize(a, 0, SER_DISK);
+ AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION | ADDRV2_FORMAT);
+ AssertEqualAfterSerializeDeserialize(a, ADDRV2_FORMAT, SER_DISK);
+})
+FUZZ_TARGET_DESERIALIZE(address_deserialize_v1_withtime, {
+ CAddress a;
+ DeserializeFromFuzzingInput(buffer, a, PROTOCOL_VERSION);
+ // A CAddress in V1 mode will roundtrip in all 4 formats that have nTime.
+ AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION);
+ AssertEqualAfterSerializeDeserialize(a, 0, SER_DISK);
+ AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION | ADDRV2_FORMAT);
+ AssertEqualAfterSerializeDeserialize(a, ADDRV2_FORMAT, SER_DISK);
+})
+FUZZ_TARGET_DESERIALIZE(address_deserialize_v2, {
+ CAddress a;
+ DeserializeFromFuzzingInput(buffer, a, PROTOCOL_VERSION | ADDRV2_FORMAT);
+ // A CAddress in V2 mode will roundtrip in both V2 formats, and also in the V1 formats
+ // with time if it's V1 compatible.
+ if (a.IsAddrV1Compatible()) {
+ AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION);
+ AssertEqualAfterSerializeDeserialize(a, 0, SER_DISK);
+ }
+ AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION | ADDRV2_FORMAT);
+ AssertEqualAfterSerializeDeserialize(a, ADDRV2_FORMAT, SER_DISK);
})
FUZZ_TARGET_DESERIALIZE(inv_deserialize, {
CInv i;
diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp
index 7b99193ad0..c4e4d4c785 100644
--- a/src/test/fuzz/process_message.cpp
+++ b/src/test/fuzz/process_message.cpp
@@ -58,7 +58,19 @@ void initialize_process_message()
static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
g_setup = testing_setup.get();
+
+ // Temporary debug for https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=35027
+ {
+ LOCK(::cs_main);
+ assert(CheckDiskSpace(gArgs.GetDataDirNet()));
+ assert(CheckDiskSpace(gArgs.GetDataDirNet(), 48 * 2 * 2 * g_setup->m_node.chainman->ActiveChainstate().CoinsTip().GetCacheSize()));
+ }
for (int i = 0; i < 2 * COINBASE_MATURITY; i++) {
+ {
+ LOCK(::cs_main);
+ assert(CheckDiskSpace(gArgs.GetDataDirNet()));
+ assert(CheckDiskSpace(gArgs.GetDataDirNet(), 48 * 2 * 2 * g_setup->m_node.chainman->ActiveChainstate().CoinsTip().GetCacheSize()));
+ }
MineBlock(g_setup->m_node, CScript() << OP_TRUE);
}
SyncWithValidationInterfaceQueue();
diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp
index cb66d5164e..b915982d98 100644
--- a/src/test/key_tests.cpp
+++ b/src/test/key_tests.cpp
@@ -300,6 +300,48 @@ BOOST_AUTO_TEST_CASE(bip340_test_vectors)
auto sig = ParseHex(test.first[2]);
BOOST_CHECK_EQUAL(XOnlyPubKey(pubkey).VerifySchnorr(uint256(msg), sig), test.second);
}
+
+ static const std::vector<std::array<std::string, 5>> SIGN_VECTORS = {
+ {{"0000000000000000000000000000000000000000000000000000000000000003", "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0"}},
+ {{"B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF", "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "0000000000000000000000000000000000000000000000000000000000000001", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A"}},
+ {{"C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9", "DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", "C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906", "7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C", "5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7"}},
+ {{"0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710", "25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3"}},
+ };
+
+ for (const auto& [sec_hex, pub_hex, aux_hex, msg_hex, sig_hex] : SIGN_VECTORS) {
+ auto sec = ParseHex(sec_hex);
+ auto pub = ParseHex(pub_hex);
+ uint256 aux256(ParseHex(aux_hex));
+ uint256 msg256(ParseHex(msg_hex));
+ auto sig = ParseHex(sig_hex);
+ unsigned char sig64[64];
+
+ // Run the untweaked test vectors above, comparing with exact expected signature.
+ CKey key;
+ key.Set(sec.begin(), sec.end(), true);
+ XOnlyPubKey pubkey(key.GetPubKey());
+ BOOST_CHECK(std::equal(pubkey.begin(), pubkey.end(), pub.begin(), pub.end()));
+ bool ok = key.SignSchnorr(msg256, sig64, nullptr, &aux256);
+ BOOST_CHECK(ok);
+ BOOST_CHECK(std::vector<unsigned char>(sig64, sig64 + 64) == sig);
+ // Verify those signatures for good measure.
+ BOOST_CHECK(pubkey.VerifySchnorr(msg256, sig64));
+
+ // Do 10 iterations where we sign with a random Merkle root to tweak,
+ // and compare against the resulting tweaked keys, with random aux.
+ // In iteration i=0 we tweak with empty Merkle tree.
+ for (int i = 0; i < 10; ++i) {
+ uint256 merkle_root;
+ if (i) merkle_root = InsecureRand256();
+ auto tweaked = pubkey.CreateTapTweak(i ? &merkle_root : nullptr);
+ BOOST_CHECK(tweaked);
+ XOnlyPubKey tweaked_key = tweaked->first;
+ aux256 = InsecureRand256();
+ bool ok = key.SignSchnorr(msg256, sig64, &merkle_root, &aux256);
+ BOOST_CHECK(ok);
+ BOOST_CHECK(tweaked_key.VerifySchnorr(msg256, sig64));
+ }
+ }
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/util/system.cpp b/src/util/system.cpp
index 13ccf7463e..5ea0139275 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -1243,9 +1243,9 @@ void runCommand(const std::string& strCommand)
}
#endif
-#ifdef ENABLE_EXTERNAL_SIGNER
UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in)
{
+#ifdef ENABLE_EXTERNAL_SIGNER
namespace bp = boost::process;
UniValue result_json;
@@ -1277,8 +1277,10 @@ UniValue RunCommandParseJSON(const std::string& str_command, const std::string&
if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result);
return result_json;
-}
+#else
+ throw std::runtime_error("Compiled without external signing support (required for external signing).");
#endif // ENABLE_EXTERNAL_SIGNER
+}
void SetupEnvironment()
{
diff --git a/src/util/system.h b/src/util/system.h
index c4317c62d0..ea9870a343 100644
--- a/src/util/system.h
+++ b/src/util/system.h
@@ -102,7 +102,6 @@ std::string ShellEscape(const std::string& arg);
#if HAVE_SYSTEM
void runCommand(const std::string& strCommand);
#endif
-#ifdef ENABLE_EXTERNAL_SIGNER
/**
* Execute a command which returns JSON, and parse the result.
*
@@ -111,7 +110,6 @@ void runCommand(const std::string& strCommand);
* @return parsed JSON
*/
UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in="");
-#endif // ENABLE_EXTERNAL_SIGNER
/**
* Most paths passed as configuration arguments are treated as relative to
diff --git a/src/wallet/external_signer_scriptpubkeyman.cpp b/src/wallet/external_signer_scriptpubkeyman.cpp
index fe2c810afa..efef1ec754 100644
--- a/src/wallet/external_signer_scriptpubkeyman.cpp
+++ b/src/wallet/external_signer_scriptpubkeyman.cpp
@@ -13,8 +13,6 @@
#include <utility>
#include <vector>
-#ifdef ENABLE_EXTERNAL_SIGNER
-
bool ExternalSignerScriptPubKeyMan::SetupDescriptor(std::unique_ptr<Descriptor> desc)
{
LOCK(cs_desc_man);
@@ -62,10 +60,10 @@ bool ExternalSignerScriptPubKeyMan::DisplayAddress(const CScript scriptPubKey, c
}
// If sign is true, transaction must previously have been filled
-TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const
+TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const
{
if (!sign) {
- return DescriptorScriptPubKeyMan::FillPSBT(psbt, sighash_type, false, bip32derivs, n_signed);
+ return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, sighash_type, false, bip32derivs, n_signed);
}
// Already complete if every input is now signed
@@ -84,5 +82,3 @@ TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransact
FinalizePSBT(psbt); // This won't work in a multisig setup
return TransactionError::OK;
}
-
-#endif
diff --git a/src/wallet/external_signer_scriptpubkeyman.h b/src/wallet/external_signer_scriptpubkeyman.h
index 1786958912..8eed947b7b 100644
--- a/src/wallet/external_signer_scriptpubkeyman.h
+++ b/src/wallet/external_signer_scriptpubkeyman.h
@@ -5,7 +5,6 @@
#ifndef BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H
#define BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H
-#ifdef ENABLE_EXTERNAL_SIGNER
#include <wallet/scriptpubkeyman.h>
#include <memory>
@@ -29,8 +28,6 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
bool DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const;
- TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override;
+ TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override;
};
-#endif
-
#endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 534c974178..ab34af2329 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -3320,7 +3320,8 @@ RPCHelpMan signrawtransactionwithwallet()
},
},
},
- {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type. Must be one of\n"
+ {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT"}, "The signature hash type. Must be one of\n"
+ " \"DEFAULT\"\n"
" \"ALL\"\n"
" \"NONE\"\n"
" \"SINGLE\"\n"
@@ -3542,7 +3543,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
} else {
PartiallySignedTransaction psbtx(mtx);
bool complete = false;
- const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */);
+ const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, false /* sign */, true /* bip32derivs */);
CHECK_NONFATAL(err == TransactionError::OK);
CHECK_NONFATAL(!complete);
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
@@ -4175,8 +4176,8 @@ static RPCHelpMan send()
// First fill transaction with our data without signing,
// so external signers are not asked sign more than once.
bool complete;
- pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false, true);
- const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, true, false);
+ pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, false, true);
+ const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, true, false);
if (err != TransactionError::OK) {
throw JSONRPCTransactionError(err);
}
@@ -4291,7 +4292,8 @@ static RPCHelpMan walletprocesspsbt()
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"},
{"sign", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also sign the transaction when updating"},
- {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n"
+ {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n"
+ " \"DEFAULT\"\n"
" \"ALL\"\n"
" \"NONE\"\n"
" \"SINGLE\"\n"
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 2eb9ca5c6d..c8baa0665e 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -13,7 +13,6 @@
#include <util/system.h>
#include <util/time.h>
#include <util/translation.h>
-#include <external_signer.h>
#include <wallet/scriptpubkeyman.h>
#include <optional>
@@ -597,7 +596,7 @@ SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, con
return SigningResult::SIGNING_FAILED;
}
-TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const
+TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const
{
if (n_signed) {
*n_signed = 0;
@@ -626,7 +625,7 @@ TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psb
}
SignatureData sigdata;
input.FillSignatureData(sigdata);
- SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, sighash_type);
+ SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, &txdata, sighash_type);
bool signed_one = PSBTInputSigned(input);
if (n_signed && (signed_one || !sign)) {
@@ -2083,7 +2082,7 @@ SigningResult DescriptorScriptPubKeyMan::SignMessage(const std::string& message,
return SigningResult::OK;
}
-TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const
+TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const
{
if (n_signed) {
*n_signed = 0;
@@ -2133,7 +2132,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
}
}
- SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, sighash_type);
+ SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, &txdata, sighash_type);
bool signed_one = PSBTInputSigned(input);
if (n_signed && (signed_one || !sign)) {
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index b8e34fbac3..3c4603608c 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -235,7 +235,7 @@ public:
/** Sign a message with the given script */
virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; };
/** Adds script and derivation path information to a PSBT, and optionally signs it. */
- virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const { return TransactionError::INVALID_PSBT; }
+ virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const { return TransactionError::INVALID_PSBT; }
virtual uint256 GetID() const { return uint256(); }
@@ -394,7 +394,7 @@ public:
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const override;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
- TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override;
+ TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override;
uint256 GetID() const override;
@@ -605,7 +605,7 @@ public:
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const override;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
- TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override;
+ TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override;
uint256 GetID() const override;
diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp
index ce7e661b67..1cefa386b7 100644
--- a/src/wallet/test/psbt_wallet_tests.cpp
+++ b/src/wallet/test/psbt_wallet_tests.cpp
@@ -71,7 +71,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Try to sign the mutated input
SignatureData sigdata;
- BOOST_CHECK(spk_man->FillPSBT(psbtx, SIGHASH_ALL, true, true) != TransactionError::OK);
+ BOOST_CHECK(spk_man->FillPSBT(psbtx, PrecomputePSBTData(psbtx), SIGHASH_ALL, true, true) != TransactionError::OK);
}
BOOST_AUTO_TEST_CASE(parse_hd_keypath)
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 4b6630de3c..256faf2b23 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -8,6 +8,7 @@
#include <chain.h>
#include <consensus/consensus.h>
#include <consensus/validation.h>
+#include <external_signer.h>
#include <fs.h>
#include <interfaces/chain.h>
#include <interfaces/wallet.h>
@@ -1807,7 +1808,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const
coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.n], wtx.m_confirm.block_height, wtx.IsCoinBase());
}
std::map<int, std::string> input_errors;
- return SignTransaction(tx, coins, SIGHASH_ALL, input_errors);
+ return SignTransaction(tx, coins, SIGHASH_DEFAULT, input_errors);
}
bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const
@@ -1830,6 +1831,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
if (n_signed) {
*n_signed = 0;
}
+ const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
LOCK(cs_wallet);
// Get all of the previous transactions
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
@@ -1856,7 +1858,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
// Fill in information from ScriptPubKeyMans
for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) {
int n_signed_this_spkm = 0;
- TransactionError res = spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs, &n_signed_this_spkm);
+ TransactionError res = spk_man->FillPSBT(psbtx, txdata, sighash_type, sign, bip32derivs, &n_signed_this_spkm);
if (res != TransactionError::OK) {
return res;
}
@@ -2215,7 +2217,6 @@ void ReserveDestination::ReturnDestination()
bool CWallet::DisplayAddress(const CTxDestination& dest)
{
-#ifdef ENABLE_EXTERNAL_SIGNER
CScript scriptPubKey = GetScriptForDestination(dest);
const auto spk_man = GetScriptPubKeyMan(scriptPubKey);
if (spk_man == nullptr) {
@@ -2227,9 +2228,6 @@ bool CWallet::DisplayAddress(const CTxDestination& dest)
}
ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
return signer_spk_man->DisplayAddress(scriptPubKey, signer);
-#else
- return false;
-#endif
}
void CWallet::LockCoin(const COutPoint& output)
@@ -3063,12 +3061,8 @@ void CWallet::ConnectScriptPubKeyManNotifiers()
void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc)
{
if (IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
-#ifdef ENABLE_EXTERNAL_SIGNER
auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, desc));
m_spk_managers[id] = std::move(spk_manager);
-#else
- throw std::runtime_error(std::string(__func__) + ": Compiled without external signing support (required for external signing)");
-#endif
} else {
auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
m_spk_managers[id] = std::move(spk_manager);
@@ -3108,7 +3102,6 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
}
}
} else {
-#ifdef ENABLE_EXTERNAL_SIGNER
ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
// TODO: add account parameter
@@ -3135,9 +3128,6 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
AddActiveScriptPubKeyMan(id, t, internal);
}
}
-#else
- throw std::runtime_error(std::string(__func__) + ": Compiled without external signing support (required for external signing)");
-#endif // ENABLE_EXTERNAL_SIGNER
}
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index d0e26c416c..66f39edb4d 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -21,7 +21,6 @@
#include <validationinterface.h>
#include <wallet/coinselection.h>
#include <wallet/crypter.h>
-#include <external_signer.h>
#include <wallet/receive.h>
#include <wallet/scriptpubkeyman.h>
#include <wallet/spend.h>
diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py
index 158efb52c9..389db73bc9 100755
--- a/test/functional/feature_block.py
+++ b/test/functional/feature_block.py
@@ -591,6 +591,8 @@ class FullBlockTest(BitcoinTestFramework):
b44.hashPrevBlock = self.tip.sha256
b44.nBits = 0x207fffff
b44.vtx.append(coinbase)
+ tx = self.create_and_sign_transaction(out[14], 1)
+ b44.vtx.append(tx)
b44.hashMerkleRoot = b44.calc_merkle_root()
b44.solve()
self.tip = b44
@@ -678,7 +680,7 @@ class FullBlockTest(BitcoinTestFramework):
# Test block timestamps
# -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15)
# \-> b54 (15)
- #
+ # -> b44 (14)\-> b48 ()
self.move_tip(43)
b53 = self.next_block(53, spend=out[14])
self.send_blocks([b53], False)
@@ -698,6 +700,21 @@ class FullBlockTest(BitcoinTestFramework):
self.send_blocks([b55], True)
self.save_spendable_output()
+ # The block which was previously rejected because of being "too far(3 hours)" must be accepted 2 hours later.
+ # The new block is only 1 hour into future now and we must reorg onto to the new longer chain.
+ # The new bestblock b48p is invalidated manually.
+ # -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15)
+ # \-> b54 (15)
+ # -> b44 (14)\-> b48 () -> b48p ()
+ self.log.info("Accept a previously rejected future block at a later time")
+ node.setmocktime(int(time.time()) + 2*60*60)
+ self.move_tip(48)
+ self.block_heights[b48.sha256] = self.block_heights[b44.sha256] + 1 # b48 is a parent of b44
+ b48p = self.next_block("48p")
+ self.send_blocks([b48, b48p], success=True) # Reorg to the longer chain
+ node.invalidateblock(b48p.hash) # mark b48p as invalid
+ node.setmocktime(0)
+
# Test Merkle tree malleability
#
# -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57p2 (16)
@@ -1308,7 +1325,7 @@ class FullBlockTest(BitcoinTestFramework):
return create_tx_with_script(spend_tx, n, amount=value, script_pub_key=script)
# sign a transaction, using the key we know about
- # this signs input 0 in tx, which is assumed to be spending output n in spend_tx
+ # this signs input 0 in tx, which is assumed to be spending output 0 in spend_tx
def sign_tx(self, tx, spend_tx):
scriptPubKey = bytearray(spend_tx.vout[0].scriptPubKey)
if (scriptPubKey[0] == OP_TRUE): # an anyone-can-spend
diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py
index 0bb04ae267..b99b64e16d 100755
--- a/test/functional/feature_rbf.py
+++ b/test/functional/feature_rbf.py
@@ -7,17 +7,20 @@
from decimal import Decimal
from test_framework.blocktools import COINBASE_MATURITY
-from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut
+from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut, BIP125_SEQUENCE_NUMBER
from test_framework.script import CScript, OP_DROP
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round
from test_framework.script_util import DUMMY_P2WPKH_SCRIPT, DUMMY_2_P2WPKH_SCRIPT
+from test_framework.wallet import MiniWallet
MAX_REPLACEMENT_LIMIT = 100
+
def txToHex(tx):
return tx.serialize().hex()
+
def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
"""Create a txout with a given amount and scriptPubKey
@@ -26,12 +29,12 @@ def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
confirmed - txouts created will be confirmed in the blockchain;
unconfirmed otherwise.
"""
- fee = 1*COIN
- while node.getbalance() < satoshi_round((amount + fee)/COIN):
+ fee = 1 * COIN
+ while node.getbalance() < satoshi_round((amount + fee) / COIN):
node.generate(COINBASE_MATURITY)
new_addr = node.getnewaddress()
- txid = node.sendtoaddress(new_addr, satoshi_round((amount+fee)/COIN))
+ txid = node.sendtoaddress(new_addr, satoshi_round((amount + fee) / COIN))
tx1 = node.getrawtransaction(txid, 1)
txid = int(txid, 16)
i, _ = next(filter(lambda vout: new_addr == vout[1]['scriptPubKey']['address'], enumerate(tx1['vout'])))
@@ -78,10 +81,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.skip_if_no_wallet()
def run_test(self):
- # Leave IBD
- self.nodes[0].generate(1)
-
- make_utxo(self.nodes[0], 1*COIN)
+ make_utxo(self.nodes[0], 1 * COIN)
# Ensure nodes are synced
self.sync_all()
@@ -123,7 +123,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_simple_doublespend(self):
"""Simple doublespend"""
- tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+ tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
# make_utxo may have generated a bunch of blocks, so we need to sync
# before we can spend the coins generated, or else the resulting
@@ -165,14 +165,14 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_doublespend_chain(self):
"""Doublespend of a long chain"""
- initial_nValue = 50*COIN
+ initial_nValue = 50 * COIN
tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
prevout = tx0_outpoint
remaining_value = initial_nValue
chain_txids = []
- while remaining_value > 10*COIN:
- remaining_value -= 1*COIN
+ while remaining_value > 10 * COIN:
+ remaining_value -= 1 * COIN
tx = CTransaction()
tx.vin = [CTxIn(prevout, nSequence=0)]
tx.vout = [CTxOut(remaining_value, CScript([1, OP_DROP] * 15 + [1]))]
@@ -205,10 +205,10 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_doublespend_tree(self):
"""Doublespend of a big tree of transactions"""
- initial_nValue = 50*COIN
+ initial_nValue = 50 * COIN
tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
- def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001*COIN, _total_txs=None):
+ def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001 * COIN, _total_txs=None):
if _total_txs is None:
_total_txs = [0]
if _total_txs[0] >= max_txs:
@@ -239,7 +239,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
_total_txs=_total_txs):
yield x
- fee = int(0.0001*COIN)
+ fee = int(0.0001 * COIN)
n = MAX_REPLACEMENT_LIMIT
tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
assert_equal(len(tree_txs), n)
@@ -267,8 +267,8 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# Try again, but with more total transactions than the "max txs
# double-spent at once" anti-DoS limit.
- for n in (MAX_REPLACEMENT_LIMIT+1, MAX_REPLACEMENT_LIMIT*2):
- fee = int(0.0001*COIN)
+ for n in (MAX_REPLACEMENT_LIMIT + 1, MAX_REPLACEMENT_LIMIT * 2):
+ fee = int(0.0001 * COIN)
tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
assert_equal(len(tree_txs), n)
@@ -286,7 +286,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_replacement_feeperkb(self):
"""Replacement requires fee-per-KB to be higher"""
- tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+ tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
tx1a = CTransaction()
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
@@ -298,7 +298,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# rejected.
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1b.vout = [CTxOut(int(0.001*COIN), CScript([b'a'*999000]))]
+ tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 999000]))]
tx1b_hex = txToHex(tx1b)
# This will raise an exception due to insufficient fee
@@ -306,8 +306,8 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_spends_of_conflicting_outputs(self):
"""Replacements that spend conflicting tx outputs are rejected"""
- utxo1 = make_utxo(self.nodes[0], int(1.2*COIN))
- utxo2 = make_utxo(self.nodes[0], 3*COIN)
+ utxo1 = make_utxo(self.nodes[0], int(1.2 * COIN))
+ utxo2 = make_utxo(self.nodes[0], 3 * COIN)
tx1a = CTransaction()
tx1a.vin = [CTxIn(utxo1, nSequence=0)]
@@ -346,8 +346,8 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_new_unconfirmed_inputs(self):
"""Replacements that add new unconfirmed inputs are rejected"""
- confirmed_utxo = make_utxo(self.nodes[0], int(1.1*COIN))
- unconfirmed_utxo = make_utxo(self.nodes[0], int(0.1*COIN), False)
+ confirmed_utxo = make_utxo(self.nodes[0], int(1.1 * COIN))
+ unconfirmed_utxo = make_utxo(self.nodes[0], int(0.1 * COIN), False)
tx1 = CTransaction()
tx1.vin = [CTxIn(confirmed_utxo)]
@@ -369,13 +369,13 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# transactions
# Start by creating a single transaction with many outputs
- initial_nValue = 10*COIN
+ initial_nValue = 10 * COIN
utxo = make_utxo(self.nodes[0], initial_nValue)
- fee = int(0.0001*COIN)
- split_value = int((initial_nValue-fee)/(MAX_REPLACEMENT_LIMIT+1))
+ fee = int(0.0001 * COIN)
+ split_value = int((initial_nValue - fee) / (MAX_REPLACEMENT_LIMIT + 1))
outputs = []
- for _ in range(MAX_REPLACEMENT_LIMIT+1):
+ for _ in range(MAX_REPLACEMENT_LIMIT + 1):
outputs.append(CTxOut(split_value, CScript([1])))
splitting_tx = CTransaction()
@@ -387,7 +387,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
txid = int(txid, 16)
# Now spend each of those outputs individually
- for i in range(MAX_REPLACEMENT_LIMIT+1):
+ for i in range(MAX_REPLACEMENT_LIMIT + 1):
tx_i = CTransaction()
tx_i.vin = [CTxIn(COutPoint(txid, i), nSequence=0)]
tx_i.vout = [CTxOut(split_value - fee, DUMMY_P2WPKH_SCRIPT)]
@@ -397,9 +397,9 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# Now create doublespend of the whole lot; should fail.
# Need a big enough fee to cover all spending transactions and have
# a higher fee rate
- double_spend_value = (split_value-100*fee)*(MAX_REPLACEMENT_LIMIT+1)
+ double_spend_value = (split_value - 100 * fee) * (MAX_REPLACEMENT_LIMIT + 1)
inputs = []
- for i in range(MAX_REPLACEMENT_LIMIT+1):
+ for i in range(MAX_REPLACEMENT_LIMIT + 1):
inputs.append(CTxIn(COutPoint(txid, i), nSequence=0))
double_tx = CTransaction()
double_tx.vin = inputs
@@ -418,7 +418,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_opt_in(self):
"""Replacing should only work if orig tx opted in"""
- tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+ tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
# Create a non-opting in transaction
tx1a = CTransaction()
@@ -439,7 +439,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# This will raise an exception
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
- tx1_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+ tx1_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
# Create a different non-opting in transaction
tx2a = CTransaction()
@@ -467,7 +467,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx3a = CTransaction()
tx3a.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0xffffffff),
CTxIn(COutPoint(tx2a_txid, 0), nSequence=0xfffffffd)]
- tx3a.vout = [CTxOut(int(0.9*COIN), CScript([b'c'])), CTxOut(int(0.9*COIN), CScript([b'd']))]
+ tx3a.vout = [CTxOut(int(0.9 * COIN), CScript([b'c'])), CTxOut(int(0.9 * COIN), CScript([b'd']))]
tx3a_hex = txToHex(tx3a)
tx3a_txid = self.nodes[0].sendrawtransaction(tx3a_hex, 0)
@@ -495,7 +495,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# correctly used by replacement logic
# 1. Check that feeperkb uses modified fees
- tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+ tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
tx1a = CTransaction()
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
@@ -506,14 +506,14 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# Higher fee, but the actual fee per KB is much lower.
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1b.vout = [CTxOut(int(0.001*COIN), CScript([b'a'*740000]))]
+ tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 740000]))]
tx1b_hex = txToHex(tx1b)
# Verify tx1b cannot replace tx1a.
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
# Use prioritisetransaction to set tx1a's fee to 0.
- self.nodes[0].prioritisetransaction(txid=tx1a_txid, fee_delta=int(-0.1*COIN))
+ self.nodes[0].prioritisetransaction(txid=tx1a_txid, fee_delta=int(-0.1 * COIN))
# Now tx1b should be able to replace tx1a
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
@@ -521,7 +521,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
assert tx1b_txid in self.nodes[0].getrawmempool()
# 2. Check that absolute fee checks use modified fee.
- tx1_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+ tx1_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
tx2a = CTransaction()
tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)]
@@ -540,7 +540,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b_hex, 0)
# Now prioritise tx2b to have a higher modified fee
- self.nodes[0].prioritisetransaction(txid=tx2b.hash, fee_delta=int(0.1*COIN))
+ self.nodes[0].prioritisetransaction(txid=tx2b.hash, fee_delta=int(0.1 * COIN))
# tx2b should now be accepted
tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, 0)
@@ -550,11 +550,11 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_rpc(self):
us0 = self.nodes[0].listunspent()[0]
ins = [us0]
- outs = {self.nodes[0].getnewaddress() : Decimal(1.0000000)}
+ outs = {self.nodes[0].getnewaddress(): Decimal(1.0000000)}
rawtx0 = self.nodes[0].createrawtransaction(ins, outs, 0, True)
rawtx1 = self.nodes[0].createrawtransaction(ins, outs, 0, False)
- json0 = self.nodes[0].decoderawtransaction(rawtx0)
- json1 = self.nodes[0].decoderawtransaction(rawtx1)
+ json0 = self.nodes[0].decoderawtransaction(rawtx0)
+ json1 = self.nodes[0].decoderawtransaction(rawtx1)
assert_equal(json0["vin"][0]["sequence"], 4294967293)
assert_equal(json1["vin"][0]["sequence"], 4294967295)
@@ -562,74 +562,68 @@ class ReplaceByFeeTest(BitcoinTestFramework):
frawtx2a = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": True})
frawtx2b = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": False})
- json0 = self.nodes[0].decoderawtransaction(frawtx2a['hex'])
- json1 = self.nodes[0].decoderawtransaction(frawtx2b['hex'])
+ json0 = self.nodes[0].decoderawtransaction(frawtx2a['hex'])
+ json1 = self.nodes[0].decoderawtransaction(frawtx2b['hex'])
assert_equal(json0["vin"][0]["sequence"], 4294967293)
assert_equal(json1["vin"][0]["sequence"], 4294967294)
def test_no_inherited_signaling(self):
- # Send tx from which to conflict outputs later
- base_txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
- self.nodes[0].generate(1)
- self.sync_blocks()
+ wallet = MiniWallet(self.nodes[0])
+ wallet.scan_blocks(start=76, num=1)
+ confirmed_utxo = wallet.get_utxo()
# Create an explicitly opt-in parent transaction
- optin_parent_tx = self.nodes[0].createrawtransaction([{
- 'txid': base_txid,
- 'vout': 0,
- "sequence": 0xfffffffd,
- }], {self.nodes[0].getnewaddress(): Decimal("9.99998")})
-
- optin_parent_tx = self.nodes[0].signrawtransactionwithwallet(optin_parent_tx)
-
- # Broadcast parent tx
- optin_parent_txid = self.nodes[0].sendrawtransaction(hexstring=optin_parent_tx["hex"], maxfeerate=0)
- assert optin_parent_txid in self.nodes[0].getrawmempool()
-
- replacement_parent_tx = self.nodes[0].createrawtransaction([{
- 'txid': base_txid,
- 'vout': 0,
- "sequence": 0xfffffffd,
- }], {self.nodes[0].getnewaddress(): Decimal("9.90000")})
-
- replacement_parent_tx = self.nodes[0].signrawtransactionwithwallet(replacement_parent_tx)
+ optin_parent_tx = wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=confirmed_utxo,
+ sequence=BIP125_SEQUENCE_NUMBER,
+ fee_rate=Decimal('0.01'),
+ )
+ assert_equal(True, self.nodes[0].getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable'])
+
+ replacement_parent_tx = wallet.create_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=confirmed_utxo,
+ sequence=BIP125_SEQUENCE_NUMBER,
+ fee_rate=Decimal('0.02'),
+ )
# Test if parent tx can be replaced.
- res = self.nodes[0].testmempoolaccept(rawtxs=[replacement_parent_tx['hex']], maxfeerate=0)[0]
+ res = self.nodes[0].testmempoolaccept(rawtxs=[replacement_parent_tx['hex']])[0]
# Parent can be replaced.
assert_equal(res['allowed'], True)
# Create an opt-out child tx spending the opt-in parent
- optout_child_tx = self.nodes[0].createrawtransaction([{
- 'txid': optin_parent_txid,
- 'vout': 0,
- "sequence": 0xffffffff,
- }], {self.nodes[0].getnewaddress(): Decimal("9.99990")})
-
- optout_child_tx = self.nodes[0].signrawtransactionwithwallet(optout_child_tx)
-
- # Broadcast child tx
- optout_child_txid = self.nodes[0].sendrawtransaction(hexstring=optout_child_tx["hex"], maxfeerate=0)
- assert optout_child_txid in self.nodes[0].getrawmempool()
-
- replacement_child_tx = self.nodes[0].createrawtransaction([{
- 'txid': optin_parent_txid,
- 'vout': 0,
- "sequence": 0xffffffff,
- }], {self.nodes[0].getnewaddress(): Decimal("9.00000")})
-
- replacement_child_tx = self.nodes[0].signrawtransactionwithwallet(replacement_child_tx)
+ parent_utxo = wallet.get_utxo(txid=optin_parent_tx['txid'])
+ optout_child_tx = wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=parent_utxo,
+ sequence=0xffffffff,
+ fee_rate=Decimal('0.01'),
+ )
+
+ # Reports true due to inheritance
+ assert_equal(True, self.nodes[0].getmempoolentry(optout_child_tx['txid'])['bip125-replaceable'])
+
+ replacement_child_tx = wallet.create_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=parent_utxo,
+ sequence=0xffffffff,
+ fee_rate=Decimal('0.02'),
+ mempool_valid=False,
+ )
# Broadcast replacement child tx
# BIP 125 :
# 1. The original transactions signal replaceability explicitly or through inheritance as described in the above
# Summary section.
- # The original transaction (`optout_child_tx`) doesn't signal RBF but its parent (`optin_parent_txid`) does.
+ # The original transaction (`optout_child_tx`) doesn't signal RBF but its parent (`optin_parent_tx`) does.
# The replacement transaction (`replacement_child_tx`) should be able to replace the original transaction.
# See CVE-2021-31876 for further explanations.
- assert optin_parent_txid in self.nodes[0].getrawmempool()
+ assert_equal(True, self.nodes[0].getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable'])
assert_raises_rpc_error(-26, 'txn-mempool-conflict', self.nodes[0].sendrawtransaction, replacement_child_tx["hex"], 0)
+
if __name__ == '__main__':
ReplaceByFeeTest().main()
diff --git a/test/functional/p2p_invalid_block.py b/test/functional/p2p_invalid_block.py
index 483f25f48c..91666d0f08 100755
--- a/test/functional/p2p_invalid_block.py
+++ b/test/functional/p2p_invalid_block.py
@@ -9,8 +9,11 @@ In this test we connect to one node over p2p, and test block requests:
2) Invalid block with duplicated transaction should be re-requested.
3) Invalid block with bad coinbase value should be rejected and not
re-requested.
+4) Invalid block due to future timestamp is later accepted when that timestamp
+becomes valid.
"""
import copy
+import time
from test_framework.blocktools import create_block, create_coinbase, create_tx_with_script
from test_framework.messages import COIN
@@ -18,6 +21,9 @@ from test_framework.p2p import P2PDataStore
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
+MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60
+
+
class InvalidBlockRequestTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
@@ -133,5 +139,18 @@ class InvalidBlockRequestTest(BitcoinTestFramework):
self.log.info("Test inflation by duplicating input")
peer.send_blocks_and_test([block4], node, success=False, reject_reason='bad-txns-inputs-duplicate')
+ self.log.info("Test accepting identical block after rejecting it due to a future timestamp.")
+ t = int(time.time())
+ node.setmocktime(t)
+ # Set block time +1 second past max future validity
+ block = create_block(tip, create_coinbase(height), t + MAX_FUTURE_BLOCK_TIME + 1)
+ block.hashMerkleRoot = block.calc_merkle_root()
+ block.solve()
+ # Need force_send because the block will get rejected without a getdata otherwise
+ peer.send_blocks_and_test([block], node, force_send=True, success=False, reject_reason='time-too-new')
+ node.setmocktime(t + 1)
+ peer.send_blocks_and_test([block], node, success=True)
+
+
if __name__ == '__main__':
InvalidBlockRequestTest().main()
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index 5a9736a7a3..3d79174356 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -39,7 +39,7 @@ MAX_BLOOM_HASH_FUNCS = 50
COIN = 100000000 # 1 btc in satoshis
MAX_MONEY = 21000000 * COIN
-BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is BIP 125 opt-in and BIP 68-opt-out
+BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is rbf-opt-in (BIP 125) and csv-opt-out (BIP 68)
MAX_PROTOCOL_MESSAGE_LENGTH = 4000000 # Maximum length of incoming protocol messages
MAX_HEADERS_RESULTS = 2000 # Number of headers sent in one getheaders result
diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py
index 0962a5cb54..ad58a62e28 100644
--- a/test/functional/test_framework/wallet.py
+++ b/test/functional/test_framework/wallet.py
@@ -124,13 +124,13 @@ class MiniWallet:
else:
return self._utxos[index]
- def send_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, locktime=0):
+ def send_self_transfer(self, **kwargs):
"""Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
- tx = self.create_self_transfer(fee_rate=fee_rate, from_node=from_node, utxo_to_spend=utxo_to_spend)
- self.sendrawtransaction(from_node=from_node, tx_hex=tx['hex'])
+ tx = self.create_self_transfer(**kwargs)
+ self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx['hex'])
return tx
- def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True, locktime=0):
+ def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0):
"""Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
self._utxos = sorted(self._utxos, key=lambda k: k['value'])
utxo_to_spend = utxo_to_spend or self._utxos.pop() # Pick the largest utxo (if none provided) and hope it covers the fee
@@ -140,7 +140,7 @@ class MiniWallet:
assert send_value > 0
tx = CTransaction()
- tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']))]
+ tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=sequence)]
tx.vout = [CTxOut(int(send_value * COIN), self._scriptPubKey)]
tx.nLockTime = locktime
if not self._address:
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index c9a8cc5611..76347b052d 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -19,6 +19,7 @@ import datetime
import os
import time
import shutil
+import signal
import subprocess
import sys
import tempfile
@@ -548,9 +549,11 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=
all_passed = all(map(lambda test_result: test_result.was_successful, test_results)) and coverage_passed
- # This will be a no-op unless failfast is True in which case there may be dangling
- # processes which need to be killed.
- job_queue.kill_and_join()
+ # Clean up dangling processes if any. This may only happen with --failfast option.
+ # Killing the process group will also terminate the current process but that is
+ # not an issue
+ if len(job_queue.jobs):
+ os.killpg(os.getpgid(0), signal.SIGKILL)
sys.exit(not all_passed)
@@ -647,16 +650,6 @@ class TestHandler:
print('.', end='', flush=True)
dot_count += 1
- def kill_and_join(self):
- """Send SIGKILL to all jobs and block until all have ended."""
- procs = [i[2] for i in self.jobs]
-
- for proc in procs:
- proc.kill()
-
- for proc in procs:
- proc.wait()
-
class TestResult():
def __init__(self, name, status, time):
diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py
index 5803c0cf54..1547a90125 100755
--- a/test/functional/wallet_taproot.py
+++ b/test/functional/wallet_taproot.py
@@ -6,6 +6,7 @@
import random
+from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
from test_framework.descriptors import descsum_create
@@ -233,20 +234,85 @@ class WalletTaprootTest(BitcoinTestFramework):
# tr descriptors cannot be imported when Taproot is not active
result = self.privs_tr_enabled.importdescriptors([{"desc": desc, "timestamp": "now"}])
assert(result[0]["success"])
- result = self.privs_tr_disabled.importdescriptors([{"desc": desc, "timestamp": "now"}])
- assert(not result[0]["success"])
- assert_equal(result[0]["error"]["code"], -4)
- assert_equal(result[0]["error"]["message"], "Cannot import tr() descriptor when Taproot is not active")
result = self.pubs_tr_enabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}])
assert(result[0]["success"])
- result = self.pubs_tr_disabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}])
- assert(not result[0]["success"])
- assert_equal(result[0]["error"]["code"], -4)
- assert_equal(result[0]["error"]["message"], "Cannot import tr() descriptor when Taproot is not active")
+ if desc.startswith("tr"):
+ result = self.privs_tr_disabled.importdescriptors([{"desc": desc, "timestamp": "now"}])
+ assert(not result[0]["success"])
+ assert_equal(result[0]["error"]["code"], -4)
+ assert_equal(result[0]["error"]["message"], "Cannot import tr() descriptor when Taproot is not active")
+ result = self.pubs_tr_disabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}])
+ assert(not result[0]["success"])
+ assert_equal(result[0]["error"]["code"], -4)
+ assert_equal(result[0]["error"]["message"], "Cannot import tr() descriptor when Taproot is not active")
+
+ def do_test_sendtoaddress(self, comment, pattern, privmap, treefn, keys_pay, keys_change):
+ self.log.info("Testing %s through sendtoaddress" % comment)
+ desc_pay = self.make_desc(pattern, privmap, keys_pay)
+ desc_change = self.make_desc(pattern, privmap, keys_change)
+ desc_pay_pub = self.make_desc(pattern, privmap, keys_pay, True)
+ desc_change_pub = self.make_desc(pattern, privmap, keys_change, True)
+ assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub)
+ assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub)
+ result = self.rpc_online.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}])
+ assert(result[0]['success'])
+ result = self.rpc_online.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}])
+ assert(result[0]['success'])
+ for i in range(4):
+ addr_g = self.rpc_online.getnewaddress(address_type='bech32')
+ if treefn is not None:
+ addr_r = self.make_addr(treefn, keys_pay, i)
+ assert_equal(addr_g, addr_r)
+ boring_balance = int(self.boring.getbalance() * 100000000)
+ to_amnt = random.randrange(1000000, boring_balance)
+ self.boring.sendtoaddress(address=addr_g, amount=Decimal(to_amnt) / 100000000, subtractfeefromamount=True)
+ self.nodes[0].generatetoaddress(1, self.boring.getnewaddress())
+ test_balance = int(self.rpc_online.getbalance() * 100000000)
+ ret_amnt = random.randrange(100000, test_balance)
+ res = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True)
+ self.nodes[0].generatetoaddress(1, self.boring.getnewaddress())
+ assert(self.rpc_online.gettransaction(res)["confirmations"] > 0)
+
+ def do_test_psbt(self, comment, pattern, privmap, treefn, keys_pay, keys_change):
+ self.log.info("Testing %s through PSBT" % comment)
+ desc_pay = self.make_desc(pattern, privmap, keys_pay, False)
+ desc_change = self.make_desc(pattern, privmap, keys_change, False)
+ desc_pay_pub = self.make_desc(pattern, privmap, keys_pay, True)
+ desc_change_pub = self.make_desc(pattern, privmap, keys_change, True)
+ assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub)
+ assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub)
+ result = self.psbt_online.importdescriptors([{"desc": desc_pay_pub, "active": True, "timestamp": "now"}])
+ assert(result[0]['success'])
+ result = self.psbt_online.importdescriptors([{"desc": desc_change_pub, "active": True, "timestamp": "now", "internal": True}])
+ assert(result[0]['success'])
+ result = self.psbt_offline.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}])
+ assert(result[0]['success'])
+ result = self.psbt_offline.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}])
+ assert(result[0]['success'])
+ for i in range(4):
+ addr_g = self.psbt_online.getnewaddress(address_type='bech32')
+ if treefn is not None:
+ addr_r = self.make_addr(treefn, keys_pay, i)
+ assert_equal(addr_g, addr_r)
+ boring_balance = int(self.boring.getbalance() * 100000000)
+ to_amnt = random.randrange(1000000, boring_balance)
+ self.boring.sendtoaddress(address=addr_g, amount=Decimal(to_amnt) / 100000000, subtractfeefromamount=True)
+ self.nodes[0].generatetoaddress(1, self.boring.getnewaddress())
+ test_balance = int(self.psbt_online.getbalance() * 100000000)
+ ret_amnt = random.randrange(100000, test_balance)
+ psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0]})['psbt']
+ res = self.psbt_offline.walletprocesspsbt(psbt)
+ assert(res['complete'])
+ rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex']
+ txid = self.nodes[0].sendrawtransaction(rawtx)
+ self.nodes[0].generatetoaddress(1, self.boring.getnewaddress())
+ assert(self.psbt_online.gettransaction(txid)['confirmations'] > 0)
def do_test(self, comment, pattern, privmap, treefn, nkeys):
- keys = self.rand_keys(nkeys)
- self.do_test_addr(comment, pattern, privmap, treefn, keys)
+ keys = self.rand_keys(nkeys * 4)
+ self.do_test_addr(comment, pattern, privmap, treefn, keys[0:nkeys])
+ self.do_test_sendtoaddress(comment, pattern, privmap, treefn, keys[0:nkeys], keys[nkeys:2*nkeys])
+ self.do_test_psbt(comment, pattern, privmap, treefn, keys[2*nkeys:3*nkeys], keys[3*nkeys:4*nkeys])
def run_test(self):
self.log.info("Creating wallets...")
@@ -258,8 +324,20 @@ class WalletTaprootTest(BitcoinTestFramework):
self.pubs_tr_enabled = self.nodes[0].get_wallet_rpc("pubs_tr_enabled")
self.nodes[2].createwallet(wallet_name="pubs_tr_disabled", descriptors=True, blank=True, disable_private_keys=True)
self.pubs_tr_disabled=self.nodes[2].get_wallet_rpc("pubs_tr_disabled")
+ self.nodes[0].createwallet(wallet_name="boring")
self.nodes[0].createwallet(wallet_name="addr_gen", descriptors=True, disable_private_keys=True, blank=True)
+ self.nodes[0].createwallet(wallet_name="rpc_online", descriptors=True, blank=True)
+ self.nodes[0].createwallet(wallet_name="psbt_online", descriptors=True, disable_private_keys=True, blank=True)
+ self.nodes[1].createwallet(wallet_name="psbt_offline", descriptors=True, blank=True)
+ self.boring = self.nodes[0].get_wallet_rpc("boring")
self.addr_gen = self.nodes[0].get_wallet_rpc("addr_gen")
+ self.rpc_online = self.nodes[0].get_wallet_rpc("rpc_online")
+ self.psbt_online = self.nodes[0].get_wallet_rpc("psbt_online")
+ self.psbt_offline = self.nodes[1].get_wallet_rpc("psbt_offline")
+
+ self.log.info("Mining blocks...")
+ gen_addr = self.boring.getnewaddress()
+ self.nodes[0].generatetoaddress(101, gen_addr)
self.do_test(
"tr(XPRV)",
@@ -276,6 +354,13 @@ class WalletTaprootTest(BitcoinTestFramework):
1
)
self.do_test(
+ "wpkh(XPRV)",
+ "wpkh($1/*)",
+ [True],
+ None,
+ 1
+ )
+ self.do_test(
"tr(XPRV,{H,{H,XPUB}})",
"tr($1/*,{pk($H),{pk($H),pk($2/*)}})",
[True, False],
@@ -283,6 +368,13 @@ class WalletTaprootTest(BitcoinTestFramework):
2
)
self.do_test(
+ "wsh(multi(1,XPRV,XPUB))",
+ "wsh(multi(1,$1/*,$2/*))",
+ [True, False],
+ None,
+ 2
+ )
+ self.do_test(
"tr(XPUB,{{H,{H,XPUB}},{H,{H,{H,XPRV}}}})",
"tr($1/*,{{pk($H),{pk($H),pk($2/*)}},{pk($H),{pk($H),{pk($H),pk($3/*)}}}})",
[False, False, True],
@@ -290,5 +382,19 @@ class WalletTaprootTest(BitcoinTestFramework):
3
)
+ self.log.info("Sending everything back...")
+
+ txid = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=self.rpc_online.getbalance(), subtractfeefromamount=True)
+ self.nodes[0].generatetoaddress(1, self.boring.getnewaddress())
+ assert(self.rpc_online.gettransaction(txid)["confirmations"] > 0)
+
+ psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): self.psbt_online.getbalance()}], None, {"subtractFeeFromOutputs": [0]})['psbt']
+ res = self.psbt_offline.walletprocesspsbt(psbt)
+ assert(res['complete'])
+ rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex']
+ txid = self.nodes[0].sendrawtransaction(rawtx)
+ self.nodes[0].generatetoaddress(1, self.boring.getnewaddress())
+ assert(self.psbt_online.gettransaction(txid)['confirmations'] > 0)
+
if __name__ == '__main__':
WalletTaprootTest().main()