diff --git a/ci/README.md b/ci/README.md index 3c5f04c39e..de798607df 100644 --- a/ci/README.md +++ b/ci/README.md @@ -8,8 +8,7 @@ Be aware that the tests will be built and run in-place, so please run at your ow If the repository is not a fresh git clone, you might have to clean files from previous builds or test runs first. The ci needs to perform various sysadmin tasks such as installing packages or writing to the user's home directory. -While most of the actions are done inside a docker container, this is not possible for all. Thus, cache directories, -such as the depends cache, previous release binaries, or ccache, are mounted as read-write into the docker container. While it should be fine to run +While it should be fine to run the ci system locally on you development box, the ci scripts can generally be assumed to have received less review and testing compared to other parts of the codebase. If you want to keep the work tree clean, you might want to run the ci system in a virtual machine with a Linux operating system of your choice. diff --git a/ci/test/00_setup_env.sh b/ci/test/00_setup_env.sh index 07c20f632d..ab830b8ec0 100755 --- a/ci/test/00_setup_env.sh +++ b/ci/test/00_setup_env.sh @@ -8,11 +8,10 @@ export LC_ALL=C.UTF-8 # The root dir. # The ci system copies this folder. -# This is where the depends build is done. BASE_ROOT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../../ >/dev/null 2>&1 && pwd ) export BASE_ROOT_DIR # The depends dir. -# This folder exists on the ci host and ci guest. Changes are propagated back and forth. +# This folder exists only on the ci guest, and on the ci host as a volume. export DEPENDS_DIR=${DEPENDS_DIR:-$BASE_ROOT_DIR/depends} # A folder for the ci system to put temporary files (ccache, datadirs for tests, ...) # This folder only exists on the ci host. @@ -58,12 +57,14 @@ export CCACHE_SIZE=${CCACHE_SIZE:-100M} export CCACHE_TEMPDIR=${CCACHE_TEMPDIR:-/tmp/.ccache-temp} export CCACHE_COMPRESS=${CCACHE_COMPRESS:-1} # The cache dir. -# This folder exists on the ci host and ci guest. Changes are propagated back and forth. +# This folder exists only on the ci guest, and on the ci host as a volume. export CCACHE_DIR=${CCACHE_DIR:-$BASE_SCRATCH_DIR/.ccache} # Folder where the build result is put (bin and lib). export BASE_OUTDIR=${BASE_OUTDIR:-$BASE_SCRATCH_DIR/out/$HOST} # Folder where the build is done (dist and out-of-tree build). export BASE_BUILD_DIR=${BASE_BUILD_DIR:-$BASE_SCRATCH_DIR/build} +# The folder for previous release binaries. +# This folder exists only on the ci guest, and on the ci host as a volume. export PREVIOUS_RELEASES_DIR=${PREVIOUS_RELEASES_DIR:-$BASE_ROOT_DIR/releases/$HOST} export SDK_URL=${SDK_URL:-https://bitcoincore.org/depends-sources/sdks} export CI_BASE_PACKAGES=${CI_BASE_PACKAGES:-build-essential libtool autotools-dev automake pkg-config bsdmainutils curl ca-certificates ccache python3 rsync git procps bison} diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh index 05bef79a3d..62bc3a963d 100755 --- a/ci/test/04_install.sh +++ b/ci/test/04_install.sh @@ -39,6 +39,9 @@ if [ -z "$DANGER_RUN_CI_ON_HOST" ]; then --build-arg "FILE_ENV=${FILE_ENV}" \ --tag="${CONTAINER_NAME}" \ "${BASE_ROOT_DIR}" + docker volume create "${CONTAINER_NAME}_ccache" || true + docker volume create "${CONTAINER_NAME}_depends" || true + docker volume create "${CONTAINER_NAME}_previous_releases" || true if [ -n "${RESTART_CI_DOCKER_BEFORE_RUN}" ] ; then echo "Restart docker before run to stop and clear all containers started with --rm" @@ -48,9 +51,9 @@ if [ -z "$DANGER_RUN_CI_ON_HOST" ]; then # shellcheck disable=SC2086 CI_CONTAINER_ID=$(docker run $CI_CONTAINER_CAP --rm --interactive --detach --tty \ --mount type=bind,src=$BASE_ROOT_DIR,dst=/ro_base,readonly \ - --mount type=bind,src=$CCACHE_DIR,dst=$CCACHE_DIR \ - --mount type=bind,src=$DEPENDS_DIR,dst=$DEPENDS_DIR \ - --mount type=bind,src=$PREVIOUS_RELEASES_DIR,dst=$PREVIOUS_RELEASES_DIR \ + --mount "type=volume,src=${CONTAINER_NAME}_ccache,dst=$CCACHE_DIR" \ + --mount "type=volume,src=${CONTAINER_NAME}_depends,dst=$DEPENDS_DIR" \ + --mount "type=volume,src=${CONTAINER_NAME}_previous_releases,dst=$PREVIOUS_RELEASES_DIR" \ -w $BASE_ROOT_DIR \ --env-file /tmp/env \ --name $CONTAINER_NAME \ diff --git a/configure.ac b/configure.ac index dacc65a7f1..586db4dc69 100644 --- a/configure.ac +++ b/configure.ac @@ -1418,14 +1418,15 @@ if test "$use_upnp" != "no"; then [AC_CHECK_LIB([miniupnpc], [upnpDiscover], [MINIUPNPC_LIBS="$MINIUPNPC_LIBS -lminiupnpc"], [have_miniupnpc=no], [$MINIUPNPC_LIBS])], [have_miniupnpc=no] ) - dnl The minimum supported miniUPnPc API version is set to 10. This keeps compatibility - dnl with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. + + dnl The minimum supported miniUPnPc API version is set to 17. This excludes + dnl versions with known vulnerabilities. if test "$have_miniupnpc" != "no"; then AC_MSG_CHECKING([whether miniUPnPc API version is supported]) AC_PREPROC_IFELSE([AC_LANG_PROGRAM([[ @%:@include ]], [[ - #if MINIUPNPC_API_VERSION >= 10 + #if MINIUPNPC_API_VERSION >= 17 // Everything is okay #else # error miniUPnPc API version is too old @@ -1434,7 +1435,7 @@ if test "$use_upnp" != "no"; then AC_MSG_RESULT([yes]) ],[ AC_MSG_RESULT([no]) - AC_MSG_WARN([miniUPnPc API version < 10 is unsupported, disabling UPnP support.]) + AC_MSG_WARN([miniUPnPc API version < 17 is unsupported, disabling UPnP support.]) have_miniupnpc=no ]) fi diff --git a/contrib/verify-commits/allow-revsig-commits b/contrib/verify-commits/allow-revsig-commits index 0bb299b8fa..0c43d9cce5 100644 --- a/contrib/verify-commits/allow-revsig-commits +++ b/contrib/verify-commits/allow-revsig-commits @@ -643,3 +643,178 @@ b7365f0545b1a6862e3277b2b2139ee0d5aee1cf 4bd0e9b90a39c5c6a016b83882ae44cb4d28f1f8 7438ceac716fdfe6621728c05e718eaa89dd89aa 4e3efd47e0d50c6cd1dc81ccc9669a5b2658f495 +5ab6a942764bf6577ae311f2551153dde3d4830c +b04f42efe31e23e15cc945efe0de906ed2eadb2b +ceae0eb7e31f9d3495a13a23df7372e5e870b572 +5bf65ec66e5986c9188e3f6234f1c5c0f8dc7f90 +55c9e2d790fa2e137ccd0d91e6cf3e2d0bff4813 +ba29911e21c88f49780c6c87f94ff8ed6e764a9d +fffff0abb9c71f0af83a7925db3c293b3bb12158 +aaeb315ff0f7956449a92736160795f0140369e3 +0dd34773334c7f4db7b05df30ee61b011795b46d +2598720d6c1ef15288045599de7e65f8707d09bc +bc83710fdcc09d8e427e77457df107acc9db1be5 +ddd7a39aa960ee3639ef1e59b2e53852e0862c52 +0808c88d7bd992d5c9ded0009c9563f6177b4035 +a085a554913ae8f4ed83afac830ce6dc39c9cc65 +b1a824dd06aa58618947783edee2dd891b5204dc +a4e066af8573dcefb11dff120e1c09e8cf7f40c2 +58b9d6cf9e9b801be9c677a3ae121e5d2950ce66 +7377ed778c6d832ecd291e65b2789af7bac2ae2b +c3a41ad980cc5149de3f9ec8414962c183b1fed9 +5884a47c367f6ff1aff3ae1ef6894881c5a5e0b7 +1d39c9ca0672e7ad4c1f0959f9d58d2fcc7dc46b +e16f6441044fc2123e0cbdcbd8a5842ec3aae7a0 +6c6cc7989cac79450bf83b932ca82d390a37e17b +bc28ca3afb7f6656a0bf50038a5e383ee7f9b219 +57a491bee17af88f75c2cea8c109d93b1cdbc9a8 +f8586b25f6a4f1e30a54e58f45dd28aaf580bbc6 +e5df0ba0d97e5f8cfd748f09d6ed77b7bfc45471 +1b0469199bdaedfd452eea718268be7fd50db3c0 +015717e2b873b7a2ce433bd3be2328a782aa5d91 +3b3c66f85959f3393a3a9e87a29004b526f91b93 +874529665c1c326fc86fc0d0d6c3512fab087ab8 +7f2c983e1cfdb58b6f84eabe5ff6a16f143f39aa +0ea92cad5274f3939f09d6890da31a21b8481282 +489b5876698f9bb2d93b1b1d62d514148b31effd +faf25b09d9e78f2ff129e25b90f67930d2fc1c4f +df933596e7e9aa17f7e5cd6e1c850520f5b56f1b +9e4fbebcc8e497016563e46de4c64fa094edab2d +1557014378cc5a6234a9244fa60132955206fd27 +c5fbcf5f8d7b36bee54ac80d1027d0dccea2aa75 +cccbc5fe3ea5ae52426203f4485b11071fbe4b6e +5174a139c92c1238f9700d06e362dc628d81a0a9 +9dae9f5f1e2bf29f58d3f49b0c612063d883b8b3 +e282764e049523439bc8adaadc002a1420122830 +d8ae5044488248d5eb134aa7c0a15c813a2f8715 +06ea2783a2c11e7b171e2809c3211bb3091d894d +00ce8543f16f4357926eb6dc701ac6229142be80 +1f63b460a8506675ccacb4647941f07d391735e3 +a100c42a136da5ddfd09aa442543ec2190f24faf +636991d0c0f969968c790d490c82c1d2fa4e8047 +dd52f79a73eca18301db1569d517197160018dbb +e157b98640c7cfb94cae7e0faca3bcffc2dde990 +ad9e5eaf77bf7e19a926a43407c88386a8a1e58f +c5e67be03bb06a5d7885c55db1f016fbf2333fe3 +48eec32347494da781f26478fa488b28336afbd2 +c324b07a541a04698954ece94e5879ae7131c1c7 +4901631dac6a883c6ddd0d4e5e3edd08b10d7609 +cacbdbaa95317b45cf2100702bca92410fb43b9a +b4f686952a60bbadc7ed2250651d0d6af0959f4d +90e49c1ececd6296c6ec6109cea525a208c0626e +700808754884919916a5518e7ecfdabadef956d8 +0cd1a2eff9e0020ec1052a931f3863794d1a95d9 +51527ec1ec4264f7e24ef548bb049db07a89fc7f +ed4eeafbb6e2e73ff9fb9c03bd66bbb049b8aacd +d4475ea7ae70ad1a1f9374b88c68f01590a88d54 +5e1aacab576b8d8918da129097a9ac0816b6ead2 +fe6a299fc0020cd62156d4b7dd9c8dac358c69c5 +0047d9b89b9fa6be660c363961cf0af72fa62ecf +037c5e511fe2185d244049cae25a98f99b878787 +8730bd3fc87c8a7d44347267e1ff3c7a8674201b +47b8256da872722953693c4037d1b9e07caadcb1 +85aea18ae660b5edf7b6c1415f033cfcb15307f9 +132d5f8c2f2397a4600a42203f413dafdb6bcc37 +23ebd7a8027f12e722834d214113892fe8561fe1 +a19f641a80cb2841ca9f593e9be589dbc4b4ae7c +1e7db37e76c510d373c4404eea2b97508b367aca +16fa967d3cca66eef0f17b41fd8aaee6a1420fbc +9eedbe98c86ff2a9214c24c37f6524ce67fd129b +0342ae1d395ca82614f6d3b8fabb6a44403baf2a +777b89b3008e53374eb13fdee70db315cd61a703 +8b686776ef5cbd6ef9d5281c3136eded25ea35a4 +c90b42bcdb594638c5759ef5ef0773314d0a1379 +7134327be5c1bdcef7919ed735049a6bbfc457ec +e88a52e9a2fda971d34425bb80e42ad2d6623d68 +173c79626867e9f89d49be7dcbb0c2042c480553 +2513499348fa955d0e4b0970b08ba9e715e6316e +43bb10661360d9f35d921d493a1f94ac95df00e2 +6f55ab57cbfa414d57a8e9fb9a47f9bcd8c836d7 +6300b9556ec927a61371053fafe1a4045f5afb00 +f8b2e9bcfc76fede05f5e12f7b15f0d9c9d0add5 +b297b945f7610772434817181ad12067b2832565 +57a73d71a36ce212977607d3e94de6ef55521bfc +5fdf37e14bb3b66264a7e6868250c2084ac39054 +3059d4dd72af73b654077d9f72019c47edd47674 +333a41882c5ccd5f0c7f884f97d25449bdeec07b +7da4f65a00a8d96da2119de613ed7fbee2a28a0d +e14f0fa6a346afecbb1d5470aef5226a8cc33e57 +cf0a8b9c4870cc88254a757286140d9632e7b70c +b69fd5eaa99f84b62a49d7c7f48d8cee1227592a +1e3ed01faa77215a7c36308237280aaa58895532 +6c9bc14a3f2cfa50144607c820ebab5288f9571c +8e3c266a4f02093d57d563f32ba73d3ab4b5f208 +decde9bba6f9d3671bdf0af4fe6ff4bf28992d1d +9b7eb584ade2ce73dbfcda080935172c3857b758 +3bbc46ddafb61f68785c7e581817db952f99d93a +bbb83f0b2b2671980c06453fd243b1f2801a1cc4 +6c9460edaeb6c89692b71f51be7b7ee386f4f5c4 +b3072799248fae8fc16f910b642edb9c5acf8bac +696d39410fc3372d120a6e89695c1543ac2fc052 +c5c4fb31828107a5ded88627632e19e05b2c7e83 +9ce1c506a3a5d20b1bf254235bfae48af592d86c +fe66dad8a779ed928b1c2fc0c3accf594b042877 +f421de5be611f874a027392d5fee7e113dce4f54 +d492dc1cdaabdc52b0766bf4cba4bd73178325d0 +6348bc61b533705a229f2c2ddcff2bdd98849d07 +83b26cb97cb46516aa4fdee3bcbfa751d28c1233 +afac75f140a3e7d89877f03420e1bc64a8d8c6cd +171f6f2699dc27e77843318be2fefdfcd9e589fb +50c806f001d66e20f314777b9fa7fefa01dc6893 +bdbabc50ba6c87ded97ea2bbacd3605c59cd12d0 +9e32adbb5c543885b2c01a984bf1e4b80e8cec16 +7c08d81e119570792648fe95bbacddbb1d5f9ae2 +65e9ca22785f4a799cbcff6d95cbe1ce4b4a6bd2 +2948d6dea098bf722828b969838668f833c2cb00 +deb847b75710d600e5b0d3d5c77fa5166d80808a +05e5af5a6c884d2ade3d7acc766ad5380cb85b64 +cba41db327a241f992f9329b214d9070888255b8 +f6d335e82822ed8f2da56c1bcaddf7f99acd4997 +30308cc380a8176a5ec0e0bd2beed8b9c482ccf7 +8b6cd42c6226dea28c182a48a214d1c091b9b5bb +267917f5632a99bb51fc3fe516d8308e79d31ed1 +ba11eb354b9f3420ebb8608227062fb639a07496 +848b11615b67a3c49f76ebbcaa241a322d8014d8 +25290071c434638e2719a99784572deef44542ad +159f89c118645c0f9e23e453f01cede84abac795 +37637bea3a9a48c0d52d68d3f78f154f8249a009 +0a76bf848c72211f986a6cc5b1c13de820b861dd +358fe779cbb2681666ae5ab23a19662db21a2c46 +c44e734dca64a15fae92255a5d848c04adaad2fa +8add59d77dd621be57059229f378822e4b707318 +922c49a1389531d9fba30168257c466bd413f625 +df0825046acc7cb496c47666e36af18118beb030 +c23bf06492dddacfc0eece3d4dd12cce81496dd0 +3eec29ed3aa1c8eb293a7a7a6be356fc014f8813 +a7e80449c0811b361cdaea39b6bab78ca5fbf668 +5e8e0b3d7f6055e326bda61e60712b530e8920f0 +a5edd191be93aff8f9c0f60f04e711e2e78ecc77 +515200298b555845696a07ae2bc0a84a5dd02ae4 +e8a3882e20f0ffeeb9b3964c2c09d8aa5eb53fd4 +c545a7aeb1d559377933c7b2e6edc2d4a37b33fb +df669230cf2001dd869e897bb4f2d9c46f9accd9 +56a0fbf8365343d73cdff2b0a0e16542294d7577 +196b4599201dbce3e0317e9b98753fa6a244b82d +cf5bb048e80d4cde8828787b266b7f5f2e3b6d7b +b94d0c7af11bd91dad4f180ce2a2ffa09e4b5668 +792d0d8d512cf8ddca200317b74ce550c1a1a428 +767ee2e3a1082468b4e2248bac3ef8bd54bb2ddb +31db3dd874dfbba88616c96a5767e2c9861d9a7a +018fd9620293582f0ce43d344ac3110e19c4dedc +801aaac2b39564aa14009785146ba26d2506fb53 +121d47afe3e67ff7f94d26e08a39573dccf652aa +af7fba3af788e91a460582351d40f8f5e2118760 +8f1c28a609b203e0d0a844d9cc5ada9eb9160a5e +8319c4e906e6df5f2048e7c048942fde285a93a2 +66be456d93a66526322b7f36fd734a8dbd5e5524 +c006ab29ceec9274dc85a0de7f7d0502021a4b87 +1220af5e6d1072ea306f6ecaaa7effe3d386c379 +14ba286556faad794f288ef38493c540382897cb +784a21d35466736a7a372364498ed94482a12a2a +4ad59042b359f473d5888ecee0c9288dcf98f1c9 +fee16b15fa3425871670239c25d4e61ae961e0c5 +216f4ca9e7ccb1f0fcb9bab0f9940992a87ae55f +2d0bdb2089644f5904629413423cdc897911b081 +50c502f54abd9eb15c8ddca013f0fdfae3d132a9 +c840ab0231bc29057172179f005001c9ab299554 +aab5e48d422d396aec09bd6389de68613b19def5 diff --git a/contrib/verify-commits/trusted-git-root b/contrib/verify-commits/trusted-git-root index 1c42195961..efb6b9f7b4 100644 --- a/contrib/verify-commits/trusted-git-root +++ b/contrib/verify-commits/trusted-git-root @@ -1 +1 @@ -577bd51a4b8de066466a445192c1c653872657e2 +8ef096d4f8e08ac691502e3fd34721a8bdfa9044 diff --git a/contrib/verify-commits/trusted-keys b/contrib/verify-commits/trusted-keys index 5ca65e7b0d..eeafcdf205 100644 --- a/contrib/verify-commits/trusted-keys +++ b/contrib/verify-commits/trusted-keys @@ -1,4 +1,3 @@ -71A3B16735405025D447E8F274810B012346C9A6 B8B3F1C0E58C15DB6A81D30C3648A882F4316B9B E777299FC265DD04793070EB944D35F9AC3DB76A D1DBF2C4B96F2DEBF4C16654410108112E7EA81F diff --git a/contrib/verifybinaries/README.md b/contrib/verifybinaries/README.md index c50d4bef71..ab831eea28 100644 --- a/contrib/verifybinaries/README.md +++ b/contrib/verifybinaries/README.md @@ -1,16 +1,5 @@ ### Verify Binaries -#### Preparation: - -Make sure you obtain the proper release signing key and verify the fingerprint with several independent sources. - -```sh -$ gpg --fingerprint "Bitcoin Core binary release signing key" -pub 4096R/36C2E964 2015-06-24 [expires: YYYY-MM-DD] - Key fingerprint = 01EA 5486 DE18 A882 D4C2 6845 90C8 019E 36C2 E964 -uid Wladimir J. van der Laan (Bitcoin Core binary release signing key) -``` - #### Usage: This script attempts to download the signature file `SHA256SUMS.asc` from https://bitcoin.org. diff --git a/doc/build-freebsd.md b/doc/build-freebsd.md index d45e9c4d0d..aa10e4a891 100644 --- a/doc/build-freebsd.md +++ b/doc/build-freebsd.md @@ -36,13 +36,30 @@ pkg install sqlite3 ``` ###### Legacy Wallet Support -`db5` is only required to support legacy wallets. -Skip if you don't intend to use legacy wallets. +BerkeleyDB is only required if legacy wallet support is required. + +It is required to use Berkeley DB 4.8. You **cannot** use the BerkeleyDB library +from ports. However, you can build DB 4.8 yourself [using depends](/depends). -```bash -pkg install db5 ``` ---- +gmake -C depends NO_BOOST=1 NO_LIBEVENT=1 NO_QT=1 NO_SQLITE=1 NO_NATPMP=1 NO_UPNP=1 NO_ZMQ=1 NO_USDT=1 +``` + +When the build is complete, the Berkeley DB installation location will be displayed: + +``` +to: /path/to/bitcoin/depends/x86_64-unknown-freebsd[release-number] +``` + +Finally, set `BDB_PREFIX` to this path according to your shell: + +``` +csh: setenv BDB_PREFIX [path displayed above] +``` + +``` +sh/bash: export BDB_PREFIX=[path displayed above] +``` #### GUI Dependencies ###### Qt5 @@ -91,12 +108,12 @@ This explicitly enables the GUI and disables legacy wallet support, assuming `sq ##### Descriptor & Legacy Wallet. No GUI: This enables support for both wallet types and disables the GUI, assuming -`sqlite3` and `db5` are both installed. +`sqlite3` and `db4` are both installed. ```bash ./autogen.sh -./configure --with-gui=no --with-incompatible-bdb \ - BDB_LIBS="-ldb_cxx-5" \ - BDB_CFLAGS="-I/usr/local/include/db5" \ +./configure --with-gui=no \ + BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx-4.8" \ + BDB_CFLAGS="-I${BDB_PREFIX}/include" \ MAKE=gmake ``` diff --git a/doc/dependencies.md b/doc/dependencies.md index 3349c81c46..ec205e4b51 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -36,7 +36,7 @@ You can find installation instructions in the `build-*.md` file for your platfor | Dependency | Releases | Version used | Minimum required | Runtime | | --- | --- | --- | --- | --- | | [libnatpmp](../depends/packages/libnatpmp.mk) | [link](https://github.com/miniupnp/libnatpmp/) | commit [07004b9...](https://github.com/bitcoin/bitcoin/pull/25917) | | No | -| [MiniUPnPc](../depends/packages/miniupnpc.mk) | [link](https://miniupnp.tuxfamily.org/) | [2.2.2](https://github.com/bitcoin/bitcoin/pull/20421) | 1.9 | No | +| [MiniUPnPc](../depends/packages/miniupnpc.mk) | [link](https://miniupnp.tuxfamily.org/) | [2.2.2](https://github.com/bitcoin/bitcoin/pull/20421) | 2.1 | No | ### Notifications | Dependency | Releases | Version used | Minimum required | Runtime | diff --git a/share/rpcauth/rpcauth.py b/share/rpcauth/rpcauth.py index d441d5f21d..cc7bba1f8b 100755 --- a/share/rpcauth/rpcauth.py +++ b/share/rpcauth/rpcauth.py @@ -4,22 +4,20 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. from argparse import ArgumentParser -from base64 import urlsafe_b64encode from getpass import getpass -from os import urandom - +from secrets import token_hex, token_urlsafe import hmac def generate_salt(size): """Create size byte hex salt""" - return urandom(size).hex() + return token_hex(size) def generate_password(): """Create 32 byte b64 password""" - return urlsafe_b64encode(urandom(32)).decode('utf-8') + return token_urlsafe(32) def password_to_hmac(salt, password): - m = hmac.new(bytearray(salt, 'utf-8'), bytearray(password, 'utf-8'), 'SHA256') + m = hmac.new(salt.encode('utf-8'), password.encode('utf-8'), 'SHA256') return m.hexdigest() def main(): @@ -38,8 +36,8 @@ def main(): password_hmac = password_to_hmac(salt, args.password) print('String to be appended to bitcoin.conf:') - print('rpcauth={0}:{1}${2}'.format(args.username, salt, password_hmac)) - print('Your password:\n{0}'.format(args.password)) + print(f'rpcauth={args.username}:{salt}${password_hmac}') + print(f'Your password:\n{args.password}') if __name__ == '__main__': main() diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 4d867fdc2f..fa77e28736 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -162,7 +162,8 @@ BITCOIN_TESTS =\ test/validation_flush_tests.cpp \ test/validation_tests.cpp \ test/validationinterface_tests.cpp \ - test/versionbits_tests.cpp + test/versionbits_tests.cpp \ + test/xoroshiro128plusplus_tests.cpp if ENABLE_WALLET BITCOIN_TESTS += \ @@ -248,6 +249,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/chain.cpp \ test/fuzz/checkqueue.cpp \ test/fuzz/coins_view.cpp \ + test/fuzz/coinscache_sim.cpp \ test/fuzz/connman.cpp \ test/fuzz/crypto.cpp \ test/fuzz/crypto_aes256.cpp \ diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include index 8496b3698a..ae77b79b8b 100644 --- a/src/Makefile.test_util.include +++ b/src/Makefile.test_util.include @@ -19,7 +19,8 @@ TEST_UTIL_H = \ test/util/str.h \ test/util/transaction_utils.h \ test/util/txmempool.h \ - test/util/validation.h + test/util/validation.h \ + test/util/xoroshiro128plusplus.h if ENABLE_WALLET TEST_UTIL_H += wallet/test/util.h diff --git a/src/bench/chacha20.cpp b/src/bench/chacha20.cpp index 656fb833e7..115cd064bd 100644 --- a/src/bench/chacha20.cpp +++ b/src/bench/chacha20.cpp @@ -14,9 +14,9 @@ static const uint64_t BUFFER_SIZE_LARGE = 1024*1024; static void CHACHA20(benchmark::Bench& bench, size_t buffersize) { std::vector key(32,0); - ChaCha20 ctx(key.data(), key.size()); + ChaCha20 ctx(key.data()); ctx.SetIV(0); - ctx.Seek(0); + ctx.Seek64(0); std::vector in(buffersize,0); std::vector out(buffersize,0); bench.batch(in.size()).unit("byte").run([&] { diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index e6e33007d5..df8fb7cece 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -55,7 +55,10 @@ static constexpr int DEFAULT_WAIT_CLIENT_TIMEOUT = 0; static const bool DEFAULT_NAMED=false; static const int CONTINUE_EXECUTION=-1; static constexpr int8_t UNKNOWN_NETWORK{-1}; -static constexpr std::array NETWORKS{"ipv4", "ipv6", "onion", "i2p", "cjdns"}; +// See GetNetworkName() in netbase.cpp +static constexpr std::array NETWORKS{"not_publicly_routable", "ipv4", "ipv6", "onion", "i2p", "cjdns", "internal"}; +static constexpr std::array NETWORK_SHORT_NAMES{"npr", "ipv4", "ipv6", "onion", "i2p", "cjdns", "int"}; +static constexpr std::array UNREACHABLE_NETWORK_IDS{/*not_publicly_routable*/0, /*internal*/6}; /** Default number of blocks to generate for RPC generatetoaddress. */ static const std::string DEFAULT_NBLOCKS = "1"; @@ -289,7 +292,7 @@ class AddrinfoRequestHandler : public BaseRequestHandler // Prepare result to return to user. UniValue result{UniValue::VOBJ}, addresses{UniValue::VOBJ}; uint64_t total{0}; // Total address count - for (size_t i = 0; i < NETWORKS.size(); ++i) { + for (size_t i = 1; i < NETWORKS.size() - 1; ++i) { addresses.pushKV(NETWORKS[i], counts.at(i)); total += counts.at(i); } @@ -506,7 +509,7 @@ class NetinfoRequestHandler : public BaseRequestHandler const bool is_addr_relay_enabled{peer["addr_relay_enabled"].isNull() ? false : peer["addr_relay_enabled"].get_bool()}; const bool is_bip152_hb_from{peer["bip152_hb_from"].get_bool()}; const bool is_bip152_hb_to{peer["bip152_hb_to"].get_bool()}; - m_peers.push_back({addr, sub_version, conn_type, network, age, min_ping, ping, addr_processed, addr_rate_limited, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_addr_relay_enabled, is_bip152_hb_from, is_bip152_hb_to, is_outbound, is_tx_relay}); + m_peers.push_back({addr, sub_version, conn_type, NETWORK_SHORT_NAMES[network_id], age, min_ping, ping, addr_processed, addr_rate_limited, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_addr_relay_enabled, is_bip152_hb_from, is_bip152_hb_to, is_outbound, is_tx_relay}); m_max_addr_length = std::max(addr.length() + 1, m_max_addr_length); m_max_addr_processed_length = std::max(ToString(addr_processed).length(), m_max_addr_processed_length); m_max_addr_rate_limited_length = std::max(ToString(addr_rate_limited).length(), m_max_addr_rate_limited_length); @@ -571,6 +574,13 @@ class NetinfoRequestHandler : public BaseRequestHandler reachable_networks.push_back(network_id); } }; + + for (const size_t network_id : UNREACHABLE_NETWORK_IDS) { + if (m_counts.at(2).at(network_id) == 0) continue; + result += strprintf("%8s", NETWORK_SHORT_NAMES.at(network_id)); // column header + reachable_networks.push_back(network_id); + } + result += " total block"; if (m_manual_peers_count) result += " manual"; @@ -636,7 +646,7 @@ class NetinfoRequestHandler : public BaseRequestHandler " \"manual\" - peer we manually added using RPC addnode or the -addnode/-connect config options\n" " \"feeler\" - short-lived connection for testing addresses\n" " \"addr\" - address fetch; short-lived connection for requesting addresses\n" - " net Network the peer connected through (\"ipv4\", \"ipv6\", \"onion\", \"i2p\", or \"cjdns\")\n" + " net Network the peer connected through (\"ipv4\", \"ipv6\", \"onion\", \"i2p\", \"cjdns\", or \"npr\" (not publicly routable))\n" " mping Minimum observed ping time, in milliseconds (ms)\n" " ping Last observed ping time, in milliseconds (ms)\n" " send Time since last message sent to the peer, in seconds\n" diff --git a/src/coins.cpp b/src/coins.cpp index e98bf816ab..5a6ae525a7 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -32,7 +32,10 @@ bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, std::unique_ptr CCoinsViewBacked::Cursor() const { return base->Cursor(); } size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); } -CCoinsViewCache::CCoinsViewCache(CCoinsView* baseIn) : CCoinsViewBacked(baseIn) {} +CCoinsViewCache::CCoinsViewCache(CCoinsView* baseIn, bool deterministic) : + CCoinsViewBacked(baseIn), m_deterministic(deterministic), + cacheCoins(0, SaltedOutpointHasher(/*deterministic=*/deterministic)) +{} size_t CCoinsViewCache::DynamicMemoryUsage() const { return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage; @@ -311,7 +314,24 @@ void CCoinsViewCache::ReallocateCache() // Cache should be empty when we're calling this. assert(cacheCoins.size() == 0); cacheCoins.~CCoinsMap(); - ::new (&cacheCoins) CCoinsMap(); + ::new (&cacheCoins) CCoinsMap(0, SaltedOutpointHasher(/*deterministic=*/m_deterministic)); +} + +void CCoinsViewCache::SanityCheck() const +{ + size_t recomputed_usage = 0; + for (const auto& [_, entry] : cacheCoins) { + unsigned attr = 0; + if (entry.flags & CCoinsCacheEntry::DIRTY) attr |= 1; + if (entry.flags & CCoinsCacheEntry::FRESH) attr |= 2; + if (entry.coin.IsSpent()) attr |= 4; + // Only 5 combinations are possible. + assert(attr != 2 && attr != 4 && attr != 7); + + // Recompute cachedCoinsUsage. + recomputed_usage += entry.coin.DynamicMemoryUsage(); + } + assert(recomputed_usage == cachedCoinsUsage); } static const size_t MIN_TRANSACTION_OUTPUT_WEIGHT = WITNESS_SCALE_FACTOR * ::GetSerializeSize(CTxOut(), PROTOCOL_VERSION); diff --git a/src/coins.h b/src/coins.h index 710b8c7c83..dd336b210a 100644 --- a/src/coins.h +++ b/src/coins.h @@ -211,6 +211,9 @@ class CCoinsViewBacked : public CCoinsView /** CCoinsView that adds a memory cache for transactions to another CCoinsView */ class CCoinsViewCache : public CCoinsViewBacked { +private: + const bool m_deterministic; + protected: /** * Make mutable so that we can "fill the cache" even from Get-methods @@ -223,7 +226,7 @@ class CCoinsViewCache : public CCoinsViewBacked mutable size_t cachedCoinsUsage{0}; public: - CCoinsViewCache(CCoinsView *baseIn); + CCoinsViewCache(CCoinsView *baseIn, bool deterministic = false); /** * By deleting the copy constructor, we prevent accidentally using it when one intends to create a cache on top of a base cache. @@ -320,6 +323,9 @@ class CCoinsViewCache : public CCoinsViewBacked //! See: https://stackoverflow.com/questions/42114044/how-to-release-unordered-map-memory void ReallocateCache(); + //! Run an internal sanity check on the cache data structure. */ + void SanityCheck() const; + private: /** * @note this is marked const, but may actually append to `cacheCoins`, increasing diff --git a/src/crypto/chacha20.cpp b/src/crypto/chacha20.cpp index 25d7baa8cc..6934cef163 100644 --- a/src/crypto/chacha20.cpp +++ b/src/crypto/chacha20.cpp @@ -8,6 +8,7 @@ #include #include +#include #include constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | (v >> (32 - c)); } @@ -20,95 +21,69 @@ constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | ( #define REPEAT10(a) do { {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; } while(0) -static const unsigned char sigma[] = "expand 32-byte k"; -static const unsigned char tau[] = "expand 16-byte k"; - -void ChaCha20::SetKey(const unsigned char* k, size_t keylen) +void ChaCha20Aligned::SetKey32(const unsigned char* k) { - const unsigned char *constants; - - input[4] = ReadLE32(k + 0); - input[5] = ReadLE32(k + 4); - input[6] = ReadLE32(k + 8); - input[7] = ReadLE32(k + 12); - if (keylen == 32) { /* recommended */ - k += 16; - constants = sigma; - } else { /* keylen == 16 */ - constants = tau; - } - input[8] = ReadLE32(k + 0); - input[9] = ReadLE32(k + 4); - input[10] = ReadLE32(k + 8); - input[11] = ReadLE32(k + 12); - input[0] = ReadLE32(constants + 0); - input[1] = ReadLE32(constants + 4); - input[2] = ReadLE32(constants + 8); - input[3] = ReadLE32(constants + 12); - input[12] = 0; - input[13] = 0; - input[14] = 0; - input[15] = 0; + input[0] = ReadLE32(k + 0); + input[1] = ReadLE32(k + 4); + input[2] = ReadLE32(k + 8); + input[3] = ReadLE32(k + 12); + input[4] = ReadLE32(k + 16); + input[5] = ReadLE32(k + 20); + input[6] = ReadLE32(k + 24); + input[7] = ReadLE32(k + 28); + input[8] = 0; + input[9] = 0; + input[10] = 0; + input[11] = 0; } -ChaCha20::ChaCha20() +ChaCha20Aligned::ChaCha20Aligned() { memset(input, 0, sizeof(input)); } -ChaCha20::ChaCha20(const unsigned char* k, size_t keylen) +ChaCha20Aligned::ChaCha20Aligned(const unsigned char* key32) { - SetKey(k, keylen); + SetKey32(key32); } -void ChaCha20::SetIV(uint64_t iv) +void ChaCha20Aligned::SetIV(uint64_t iv) { - input[14] = iv; - input[15] = iv >> 32; + input[10] = iv; + input[11] = iv >> 32; } -void ChaCha20::Seek(uint64_t pos) +void ChaCha20Aligned::Seek64(uint64_t pos) { - input[12] = pos; - input[13] = pos >> 32; + input[8] = pos; + input[9] = pos >> 32; } -void ChaCha20::Keystream(unsigned char* c, size_t bytes) +inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks) { uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; - uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; - unsigned char *ctarget = nullptr; - unsigned char tmp[64]; - unsigned int i; - - if (!bytes) return; - - j0 = input[0]; - j1 = input[1]; - j2 = input[2]; - j3 = input[3]; - j4 = input[4]; - j5 = input[5]; - j6 = input[6]; - j7 = input[7]; - j8 = input[8]; - j9 = input[9]; - j10 = input[10]; - j11 = input[11]; - j12 = input[12]; - j13 = input[13]; - j14 = input[14]; - j15 = input[15]; + uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + + if (!blocks) return; + + j4 = input[0]; + j5 = input[1]; + j6 = input[2]; + j7 = input[3]; + j8 = input[4]; + j9 = input[5]; + j10 = input[6]; + j11 = input[7]; + j12 = input[8]; + j13 = input[9]; + j14 = input[10]; + j15 = input[11]; for (;;) { - if (bytes < 64) { - ctarget = c; - c = tmp; - } - x0 = j0; - x1 = j1; - x2 = j2; - x3 = j3; + x0 = 0x61707865; + x1 = 0x3320646e; + x2 = 0x79622d32; + x3 = 0x6b206574; x4 = j4; x5 = j5; x6 = j6; @@ -134,10 +109,10 @@ void ChaCha20::Keystream(unsigned char* c, size_t bytes) QUARTERROUND( x3, x4, x9,x14); ); - x0 += j0; - x1 += j1; - x2 += j2; - x3 += j3; + x0 += 0x61707865; + x1 += 0x3320646e; + x2 += 0x79622d32; + x3 += 0x6b206574; x4 += j4; x5 += j5; x6 += j6; @@ -171,59 +146,41 @@ void ChaCha20::Keystream(unsigned char* c, size_t bytes) WriteLE32(c + 56, x14); WriteLE32(c + 60, x15); - if (bytes <= 64) { - if (bytes < 64) { - for (i = 0;i < bytes;++i) ctarget[i] = c[i]; - } - input[12] = j12; - input[13] = j13; + if (blocks == 1) { + input[8] = j12; + input[9] = j13; return; } - bytes -= 64; + blocks -= 1; c += 64; } } -void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) +inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, size_t blocks) { uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; - uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; - unsigned char *ctarget = nullptr; - unsigned char tmp[64]; - unsigned int i; - - if (!bytes) return; - - j0 = input[0]; - j1 = input[1]; - j2 = input[2]; - j3 = input[3]; - j4 = input[4]; - j5 = input[5]; - j6 = input[6]; - j7 = input[7]; - j8 = input[8]; - j9 = input[9]; - j10 = input[10]; - j11 = input[11]; - j12 = input[12]; - j13 = input[13]; - j14 = input[14]; - j15 = input[15]; + uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + + if (!blocks) return; + + j4 = input[0]; + j5 = input[1]; + j6 = input[2]; + j7 = input[3]; + j8 = input[4]; + j9 = input[5]; + j10 = input[6]; + j11 = input[7]; + j12 = input[8]; + j13 = input[9]; + j14 = input[10]; + j15 = input[11]; for (;;) { - if (bytes < 64) { - // if m has fewer than 64 bytes available, copy m to tmp and - // read from tmp instead - for (i = 0;i < bytes;++i) tmp[i] = m[i]; - m = tmp; - ctarget = c; - c = tmp; - } - x0 = j0; - x1 = j1; - x2 = j2; - x3 = j3; + x0 = 0x61707865; + x1 = 0x3320646e; + x2 = 0x79622d32; + x3 = 0x6b206574; x4 = j4; x5 = j5; x6 = j6; @@ -249,10 +206,10 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) QUARTERROUND( x3, x4, x9,x14); ); - x0 += j0; - x1 += j1; - x2 += j2; - x3 += j3; + x0 += 0x61707865; + x1 += 0x3320646e; + x2 += 0x79622d32; + x3 += 0x6b206574; x4 += j4; x5 += j5; x6 += j6; @@ -303,16 +260,65 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) WriteLE32(c + 56, x14); WriteLE32(c + 60, x15); - if (bytes <= 64) { - if (bytes < 64) { - for (i = 0;i < bytes;++i) ctarget[i] = c[i]; - } - input[12] = j12; - input[13] = j13; + if (blocks == 1) { + input[8] = j12; + input[9] = j13; return; } - bytes -= 64; + blocks -= 1; c += 64; m += 64; } } + +void ChaCha20::Keystream(unsigned char* c, size_t bytes) +{ + if (!bytes) return; + if (m_bufleft) { + unsigned reuse = std::min(m_bufleft, bytes); + memcpy(c, m_buffer + 64 - m_bufleft, reuse); + m_bufleft -= reuse; + bytes -= reuse; + c += reuse; + } + if (bytes >= 64) { + size_t blocks = bytes / 64; + m_aligned.Keystream64(c, blocks); + c += blocks * 64; + bytes -= blocks * 64; + } + if (bytes) { + m_aligned.Keystream64(m_buffer, 1); + memcpy(c, m_buffer, bytes); + m_bufleft = 64 - bytes; + } +} + +void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) +{ + if (!bytes) return; + if (m_bufleft) { + unsigned reuse = std::min(m_bufleft, bytes); + for (unsigned i = 0; i < reuse; i++) { + c[i] = m[i] ^ m_buffer[64 - m_bufleft + i]; + } + m_bufleft -= reuse; + bytes -= reuse; + c += reuse; + m += reuse; + } + if (bytes >= 64) { + size_t blocks = bytes / 64; + m_aligned.Crypt64(m, c, blocks); + c += blocks * 64; + m += blocks * 64; + bytes -= blocks * 64; + } + if (bytes) { + m_aligned.Keystream64(m_buffer, 1); + for (unsigned i = 0; i < bytes; i++) { + c[i] = m[i] ^ m_buffer[i]; + } + m_bufleft = 64 - bytes; + } +} diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h index 624c083191..b286ef59fe 100644 --- a/src/crypto/chacha20.h +++ b/src/crypto/chacha20.h @@ -8,19 +8,69 @@ #include #include -/** A class for ChaCha20 256-bit stream cipher developed by Daniel J. Bernstein - https://cr.yp.to/chacha/chacha-20080128.pdf */ +// classes for ChaCha20 256-bit stream cipher developed by Daniel J. Bernstein +// https://cr.yp.to/chacha/chacha-20080128.pdf */ + +/** ChaCha20 cipher that only operates on multiples of 64 bytes. */ +class ChaCha20Aligned +{ +private: + uint32_t input[12]; + +public: + ChaCha20Aligned(); + + /** Initialize a cipher with specified 32-byte key. */ + ChaCha20Aligned(const unsigned char* key32); + + /** set 32-byte key. */ + void SetKey32(const unsigned char* key32); + + /** set the 64-bit nonce. */ + void SetIV(uint64_t iv); + + /** set the 64bit block counter (pos seeks to byte position 64*pos). */ + void Seek64(uint64_t pos); + + /** outputs the keystream of size <64*blocks> into */ + void Keystream64(unsigned char* c, size_t blocks); + + /** enciphers the message of length <64*blocks> and write the enciphered representation into + * Used for encryption and decryption (XOR) + */ + void Crypt64(const unsigned char* input, unsigned char* output, size_t blocks); +}; + +/** Unrestricted ChaCha20 cipher. */ class ChaCha20 { private: - uint32_t input[16]; + ChaCha20Aligned m_aligned; + unsigned char m_buffer[64] = {0}; + unsigned m_bufleft{0}; public: - ChaCha20(); - ChaCha20(const unsigned char* key, size_t keylen); - void SetKey(const unsigned char* key, size_t keylen); //!< set key with flexible keylength; 256bit recommended */ - void SetIV(uint64_t iv); // set the 64bit nonce - void Seek(uint64_t pos); // set the 64bit block counter + ChaCha20() = default; + + /** Initialize a cipher with specified 32-byte key. */ + ChaCha20(const unsigned char* key32) : m_aligned(key32) {} + + /** set 32-byte key. */ + void SetKey32(const unsigned char* key32) + { + m_aligned.SetKey32(key32); + m_bufleft = 0; + } + + /** set the 64-bit nonce. */ + void SetIV(uint64_t iv) { m_aligned.SetIV(iv); } + + /** set the 64bit block counter (pos seeks to byte position 64*pos). */ + void Seek64(uint64_t pos) + { + m_aligned.Seek64(pos); + m_bufleft = 0; + } /** outputs the keystream of size into */ void Keystream(unsigned char* c, size_t bytes); diff --git a/src/crypto/chacha_poly_aead.cpp b/src/crypto/chacha_poly_aead.cpp index 6511f46adc..119ad6902f 100644 --- a/src/crypto/chacha_poly_aead.cpp +++ b/src/crypto/chacha_poly_aead.cpp @@ -36,8 +36,9 @@ ChaCha20Poly1305AEAD::ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_ assert(K_1_len == CHACHA20_POLY1305_AEAD_KEY_LEN); assert(K_2_len == CHACHA20_POLY1305_AEAD_KEY_LEN); - m_chacha_header.SetKey(K_1, CHACHA20_POLY1305_AEAD_KEY_LEN); - m_chacha_main.SetKey(K_2, CHACHA20_POLY1305_AEAD_KEY_LEN); + static_assert(CHACHA20_POLY1305_AEAD_KEY_LEN == 32); + m_chacha_header.SetKey32(K_1); + m_chacha_main.SetKey32(K_2); // set the cached sequence number to uint64 max which hints for an unset cache. // we can't hit uint64 max since the rekey rule (which resets the sequence number) is 1GB @@ -62,7 +63,7 @@ bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int // block counter 0 for the poly1305 key // use lower 32bytes for the poly1305 key // (throws away 32 unused bytes (upper 32) from this ChaCha20 round) - m_chacha_main.Seek(0); + m_chacha_main.Seek64(0); m_chacha_main.Crypt(poly_key, poly_key, sizeof(poly_key)); // if decrypting, verify the tag prior to decryption @@ -85,7 +86,7 @@ bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int if (m_cached_aad_seqnr != seqnr_aad) { m_cached_aad_seqnr = seqnr_aad; m_chacha_header.SetIV(seqnr_aad); - m_chacha_header.Seek(0); + m_chacha_header.Seek64(0); m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT); } // crypt the AAD (3 bytes message length) with given position in AAD cipher instance keystream @@ -94,7 +95,7 @@ bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int dest[2] = src[2] ^ m_aad_keystream_buffer[aad_pos + 2]; // Set the playload ChaCha instance block counter to 1 and crypt the payload - m_chacha_main.Seek(1); + m_chacha_main.Seek64(1); m_chacha_main.Crypt(src + CHACHA20_POLY1305_AEAD_AAD_LEN, dest + CHACHA20_POLY1305_AEAD_AAD_LEN, src_len - CHACHA20_POLY1305_AEAD_AAD_LEN); // If encrypting, calculate and append tag @@ -117,7 +118,7 @@ bool ChaCha20Poly1305AEAD::GetLength(uint32_t* len24_out, uint64_t seqnr_aad, in // we need to calculate the 64 keystream bytes since we reached a new aad sequence number m_cached_aad_seqnr = seqnr_aad; m_chacha_header.SetIV(seqnr_aad); // use LE for the nonce - m_chacha_header.Seek(0); // block counter 0 + m_chacha_header.Seek64(0); // block counter 0 m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT); // write keystream to the cache } diff --git a/src/crypto/muhash.cpp b/src/crypto/muhash.cpp index 26f0248663..471ee6af97 100644 --- a/src/crypto/muhash.cpp +++ b/src/crypto/muhash.cpp @@ -299,7 +299,7 @@ Num3072 MuHash3072::ToNum3072(Span in) { unsigned char tmp[Num3072::BYTE_SIZE]; uint256 hashed_in{(HashWriter{} << in).GetSHA256()}; - ChaCha20(hashed_in.data(), hashed_in.size()).Keystream(tmp, Num3072::BYTE_SIZE); + ChaCha20Aligned(hashed_in.data()).Keystream64(tmp, Num3072::BYTE_SIZE / 64); Num3072 out{tmp}; return out; diff --git a/src/fs.cpp b/src/fs.cpp index 0429b8cd0f..64411fe41f 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -60,36 +60,20 @@ FileLock::~FileLock() } } -static bool IsWSL() -{ - struct utsname uname_data; - return uname(&uname_data) == 0 && std::string(uname_data.version).find("Microsoft") != std::string::npos; -} - bool FileLock::TryLock() { if (fd == -1) { return false; } - // Exclusive file locking is broken on WSL using fcntl (issue #18622) - // This workaround can be removed once the bug on WSL is fixed - static const bool is_wsl = IsWSL(); - if (is_wsl) { - if (flock(fd, LOCK_EX | LOCK_NB) == -1) { - reason = GetErrorReason(); - return false; - } - } else { - struct flock lock; - lock.l_type = F_WRLCK; - lock.l_whence = SEEK_SET; - lock.l_start = 0; - lock.l_len = 0; - if (fcntl(fd, F_SETLK, &lock) == -1) { - reason = GetErrorReason(); - return false; - } + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + if (fcntl(fd, F_SETLK, &lock) == -1) { + reason = GetErrorReason(); + return false; } return true; diff --git a/src/mapport.cpp b/src/mapport.cpp index e6a473c185..84b889f22d 100644 --- a/src/mapport.cpp +++ b/src/mapport.cpp @@ -27,9 +27,9 @@ #include #include #include -// The minimum supported miniUPnPc API version is set to 10. This keeps compatibility -// with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. -static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed"); +// The minimum supported miniUPnPc API version is set to 17. This excludes +// versions with known vulnerabilities. +static_assert(MINIUPNPC_API_VERSION >= 17, "miniUPnPc API version >= 17 assumed"); #endif // USE_UPNP #include @@ -159,11 +159,7 @@ static bool ProcessUpnp() char lanaddr[64]; int error = 0; -#if MINIUPNPC_API_VERSION < 14 - devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error); -#else devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error); -#endif struct UPNPUrls urls; struct IGDdatas data; diff --git a/src/net.cpp b/src/net.cpp index 6370833fa7..f585d88803 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -825,7 +825,13 @@ size_t CConnman::SocketSendData(CNode& node) const if (!node.m_sock) { break; } - nBytes = node.m_sock->Send(reinterpret_cast(data.data()) + node.nSendOffset, data.size() - node.nSendOffset, MSG_NOSIGNAL | MSG_DONTWAIT); + int flags = MSG_NOSIGNAL | MSG_DONTWAIT; +#ifdef MSG_MORE + if (it + 1 != node.vSendMsg.end()) { + flags |= MSG_MORE; + } +#endif + nBytes = node.m_sock->Send(reinterpret_cast(data.data()) + node.nSendOffset, data.size() - node.nSendOffset, flags); } if (nBytes > 0) { node.m_last_send = GetTime(); diff --git a/src/psbt.cpp b/src/psbt.cpp index 50ccd9e2c0..fe45f2318c 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -132,6 +132,18 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) { sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin); } + for (const auto& [hash, preimage] : ripemd160_preimages) { + sigdata.ripemd160_preimages.emplace(std::vector(hash.begin(), hash.end()), preimage); + } + for (const auto& [hash, preimage] : sha256_preimages) { + sigdata.sha256_preimages.emplace(std::vector(hash.begin(), hash.end()), preimage); + } + for (const auto& [hash, preimage] : hash160_preimages) { + sigdata.hash160_preimages.emplace(std::vector(hash.begin(), hash.end()), preimage); + } + for (const auto& [hash, preimage] : hash256_preimages) { + sigdata.hash256_preimages.emplace(std::vector(hash.begin(), hash.end()), preimage); + } } void PSBTInput::FromSignatureData(const SignatureData& sigdata) diff --git a/src/psbt.h b/src/psbt.h index d848c9dd49..c497584f36 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -1164,7 +1164,7 @@ struct PartiallySignedTransaction // Make sure that we got an unsigned tx if (!tx) { - throw std::ios_base::failure("No unsigned transcation was provided"); + throw std::ios_base::failure("No unsigned transaction was provided"); } // Read input data diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 761351468e..7f95d527f0 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -59,7 +59,7 @@ static const char* SettingName(OptionsModel::OptionID option) } /** Call node.updateRwSetting() with Bitcoin 22.x workaround. */ -static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID option, const util::SettingsValue& value) +static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID option, const std::string& suffix, const util::SettingsValue& value) { if (value.isNum() && (option == OptionsModel::DatabaseCache || @@ -73,9 +73,9 @@ static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID optio // in later releases by https://github.com/bitcoin/bitcoin/pull/24498. // If new numeric settings are added, they can be written as numbers // instead of strings, because bitcoin 22.x will not try to read these. - node.updateRwSetting(SettingName(option), value.getValStr()); + node.updateRwSetting(SettingName(option) + suffix, value.getValStr()); } else { - node.updateRwSetting(SettingName(option), value); + node.updateRwSetting(SettingName(option) + suffix, value); } } @@ -131,13 +131,6 @@ void OptionsModel::addOverriddenOption(const std::string &option) bool OptionsModel::Init(bilingual_str& error) { // Initialize display settings from stored settings. - m_prune_size_gb = PruneSizeGB(node().getPersistentSetting("prune")); - ProxySetting proxy = ParseProxyString(SettingToString(node().getPersistentSetting("proxy"), GetDefaultProxyAddress().toStdString())); - m_proxy_ip = proxy.ip; - m_proxy_port = proxy.port; - ProxySetting onion = ParseProxyString(SettingToString(node().getPersistentSetting("onion"), GetDefaultProxyAddress().toStdString())); - m_onion_ip = onion.ip; - m_onion_port = onion.port; language = QString::fromStdString(SettingToString(node().getPersistentSetting("lang"), "")); checkAndMigrate(); @@ -320,8 +313,6 @@ void OptionsModel::SetPruneTargetGB(int prune_target_gb) const util::SettingsValue cur_value = node().getPersistentSetting("prune"); const util::SettingsValue new_value = PruneSetting(prune_target_gb > 0, prune_target_gb); - m_prune_size_gb = prune_target_gb; - // Force setting to take effect. It is still safe to change the value at // this point because this function is only called after the intro screen is // shown, before the node starts. @@ -334,7 +325,12 @@ void OptionsModel::SetPruneTargetGB(int prune_target_gb) PruneSizeGB(cur_value) != PruneSizeGB(new_value)) { // Call UpdateRwSetting() instead of setOption() to avoid setting // RestartRequired flag - UpdateRwSetting(node(), Prune, new_value); + UpdateRwSetting(node(), Prune, "", new_value); + } + + // Keep previous pruning size, if pruning was disabled. + if (PruneEnabled(cur_value)) { + UpdateRwSetting(node(), Prune, "-prev", PruneEnabled(new_value) ? util::SettingsValue{} : cur_value); } } @@ -362,9 +358,9 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in return successful; } -QVariant OptionsModel::getOption(OptionID option) const +QVariant OptionsModel::getOption(OptionID option, const std::string& suffix) const { - auto setting = [&]{ return node().getPersistentSetting(SettingName(option)); }; + auto setting = [&]{ return node().getPersistentSetting(SettingName(option) + suffix); }; QSettings settings; switch (option) { @@ -391,19 +387,30 @@ QVariant OptionsModel::getOption(OptionID option) const // default proxy case ProxyUse: + case ProxyUseTor: return ParseProxyString(SettingToString(setting(), "")).is_set; case ProxyIP: - return m_proxy_ip; + case ProxyIPTor: { + ProxySetting proxy = ParseProxyString(SettingToString(setting(), "")); + if (proxy.is_set) { + return proxy.ip; + } else if (suffix.empty()) { + return getOption(option, "-prev"); + } else { + return ParseProxyString(GetDefaultProxyAddress().toStdString()).ip; + } + } case ProxyPort: - return m_proxy_port; - - // separate Tor proxy - case ProxyUseTor: - return ParseProxyString(SettingToString(setting(), "")).is_set; - case ProxyIPTor: - return m_onion_ip; - case ProxyPortTor: - return m_onion_port; + case ProxyPortTor: { + ProxySetting proxy = ParseProxyString(SettingToString(setting(), "")); + if (proxy.is_set) { + return proxy.port; + } else if (suffix.empty()) { + return getOption(option, "-prev"); + } else { + return ParseProxyString(GetDefaultProxyAddress().toStdString()).port; + } + } #ifdef ENABLE_WALLET case SpendZeroConfChange: @@ -428,7 +435,9 @@ QVariant OptionsModel::getOption(OptionID option) const case Prune: return PruneEnabled(setting()); case PruneSize: - return m_prune_size_gb; + return PruneEnabled(setting()) ? PruneSizeGB(setting()) : + suffix.empty() ? getOption(option, "-prev") : + DEFAULT_PRUNE_TARGET_GB; case DatabaseCache: return qlonglong(SettingToInt(setting(), nDefaultDbCache)); case ThreadsScriptVerif: @@ -444,10 +453,10 @@ QVariant OptionsModel::getOption(OptionID option) const } } -bool OptionsModel::setOption(OptionID option, const QVariant& value) +bool OptionsModel::setOption(OptionID option, const QVariant& value, const std::string& suffix) { - auto changed = [&] { return value.isValid() && value != getOption(option); }; - auto update = [&](const util::SettingsValue& value) { return UpdateRwSetting(node(), option, value); }; + auto changed = [&] { return value.isValid() && value != getOption(option, suffix); }; + auto update = [&](const util::SettingsValue& value) { return UpdateRwSetting(node(), option, suffix, value); }; bool successful = true; /* set to false on parse error */ QSettings settings; @@ -485,52 +494,60 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) // default proxy case ProxyUse: if (changed()) { - update(ProxyString(value.toBool(), m_proxy_ip, m_proxy_port)); - setRestartRequired(true); + if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); + update(ProxyString(value.toBool(), getOption(ProxyIP).toString(), getOption(ProxyPort).toString())); + if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); + if (suffix.empty()) setRestartRequired(true); } break; case ProxyIP: if (changed()) { - m_proxy_ip = value.toString(); - if (getOption(ProxyUse).toBool()) { - update(ProxyString(true, m_proxy_ip, m_proxy_port)); - setRestartRequired(true); + if (suffix.empty() && !getOption(ProxyUse).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, value.toString(), getOption(ProxyPort).toString())); } + if (suffix.empty() && getOption(ProxyUse).toBool()) setRestartRequired(true); } break; case ProxyPort: if (changed()) { - m_proxy_port = value.toString(); - if (getOption(ProxyUse).toBool()) { - update(ProxyString(true, m_proxy_ip, m_proxy_port)); - setRestartRequired(true); + if (suffix.empty() && !getOption(ProxyUse).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, getOption(ProxyIP).toString(), value.toString())); } + if (suffix.empty() && getOption(ProxyUse).toBool()) setRestartRequired(true); } break; // separate Tor proxy case ProxyUseTor: if (changed()) { - update(ProxyString(value.toBool(), m_onion_ip, m_onion_port)); - setRestartRequired(true); + if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); + update(ProxyString(value.toBool(), getOption(ProxyIPTor).toString(), getOption(ProxyPortTor).toString())); + if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); + if (suffix.empty()) setRestartRequired(true); } break; case ProxyIPTor: if (changed()) { - m_onion_ip = value.toString(); - if (getOption(ProxyUseTor).toBool()) { - update(ProxyString(true, m_onion_ip, m_onion_port)); - setRestartRequired(true); + if (suffix.empty() && !getOption(ProxyUseTor).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, value.toString(), getOption(ProxyPortTor).toString())); } + if (suffix.empty() && getOption(ProxyUseTor).toBool()) setRestartRequired(true); } break; case ProxyPortTor: if (changed()) { - m_onion_port = value.toString(); - if (getOption(ProxyUseTor).toBool()) { - update(ProxyString(true, m_onion_ip, m_onion_port)); - setRestartRequired(true); + if (suffix.empty() && !getOption(ProxyUseTor).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, getOption(ProxyIPTor).toString(), value.toString())); } + if (suffix.empty() && getOption(ProxyUseTor).toBool()) setRestartRequired(true); } break; @@ -584,17 +601,20 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) break; case Prune: if (changed()) { - update(PruneSetting(value.toBool(), m_prune_size_gb)); - setRestartRequired(true); + if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); + update(PruneSetting(value.toBool(), getOption(PruneSize).toInt())); + if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); + if (suffix.empty()) setRestartRequired(true); } break; case PruneSize: if (changed()) { - m_prune_size_gb = ParsePruneSizeGB(value); - if (getOption(Prune).toBool()) { - update(PruneSetting(true, m_prune_size_gb)); - setRestartRequired(true); + if (suffix.empty() && !getOption(Prune).toBool()) { + setOption(option, value, "-prev"); + } else { + update(PruneSetting(true, ParsePruneSizeGB(value))); } + if (suffix.empty() && getOption(Prune).toBool()) setRestartRequired(true); } break; case DatabaseCache: diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 848966574d..a5da4dfaeb 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -82,8 +82,8 @@ class OptionsModel : public QAbstractListModel int rowCount(const QModelIndex & parent = QModelIndex()) const override; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override; - QVariant getOption(OptionID option) const; - bool setOption(OptionID option, const QVariant& value); + QVariant getOption(OptionID option, const std::string& suffix="") const; + bool setOption(OptionID option, const QVariant& value, const std::string& suffix=""); /** Updates current unit in memory, settings and emits displayUnitChanged(new_unit) signal */ void setDisplayUnit(const QVariant& new_unit); @@ -123,15 +123,6 @@ class OptionsModel : public QAbstractListModel bool m_enable_psbt_controls; bool m_mask_values; - //! In-memory settings for display. These are stored persistently by the - //! bitcoin node but it's also nice to store them in memory to prevent them - //! getting cleared when enable/disable toggles are used in the GUI. - int m_prune_size_gb; - QString m_proxy_ip; - QString m_proxy_port; - QString m_onion_ip; - QString m_onion_port; - /* settings that were overridden by command-line */ QString strOverriddenByCommandLine; diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index cb8491e27a..3c69d46b7e 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -477,13 +477,6 @@ WalletModel::UnlockContext::~UnlockContext() } } -void WalletModel::UnlockContext::CopyFrom(UnlockContext&& rhs) -{ - // Transfer context; old object no longer relocks wallet - *this = rhs; - rhs.relock = false; -} - bool WalletModel::bumpFee(uint256 hash, uint256& new_hash) { CCoinControl coin_control; diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 604a9e03c8..17a39349f3 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -111,7 +111,7 @@ class WalletModel : public QObject bool setWalletLocked(bool locked, const SecureString &passPhrase=SecureString()); bool changePassphrase(const SecureString &oldPass, const SecureString &newPass); - // RAI object for unlocking wallet, returned by requestUnlock() + // RAII object for unlocking wallet, returned by requestUnlock() class UnlockContext { public: @@ -120,18 +120,16 @@ class WalletModel : public QObject bool isValid() const { return valid; } - // Copy constructor is disabled. + // Disable unused copy/move constructors/assignments explicitly. UnlockContext(const UnlockContext&) = delete; - // Move operator and constructor transfer the context - UnlockContext(UnlockContext&& obj) { CopyFrom(std::move(obj)); } - UnlockContext& operator=(UnlockContext&& rhs) { CopyFrom(std::move(rhs)); return *this; } + UnlockContext(UnlockContext&&) = delete; + UnlockContext& operator=(const UnlockContext&) = delete; + UnlockContext& operator=(UnlockContext&&) = delete; + private: WalletModel *wallet; - bool valid; - mutable bool relock; // mutable, as it can be set to false by copying - - UnlockContext& operator=(const UnlockContext&) = default; - void CopyFrom(UnlockContext&& rhs); + const bool valid; + const bool relock; }; UnlockContext requestUnlock(); diff --git a/src/random.cpp b/src/random.cpp index 23ea9ba6b7..5f50c001cd 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -599,18 +599,15 @@ uint256 GetRandHash() noexcept void FastRandomContext::RandomSeed() { uint256 seed = GetRandHash(); - rng.SetKey(seed.begin(), 32); + rng.SetKey32(seed.begin()); requires_seed = false; } uint256 FastRandomContext::rand256() noexcept { - if (bytebuf_size < 32) { - FillByteBuffer(); - } + if (requires_seed) RandomSeed(); uint256 ret; - memcpy(ret.begin(), bytebuf + 64 - bytebuf_size, 32); - bytebuf_size -= 32; + rng.Keystream(ret.data(), ret.size()); return ret; } @@ -624,9 +621,9 @@ std::vector FastRandomContext::randbytes(size_t len) return ret; } -FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), bytebuf_size(0), bitbuf_size(0) +FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), bitbuf_size(0) { - rng.SetKey(seed.begin(), 32); + rng.SetKey32(seed.begin()); } bool Random_SanityCheck() @@ -675,25 +672,22 @@ bool Random_SanityCheck() return true; } -FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), bytebuf_size(0), bitbuf_size(0) +FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), bitbuf_size(0) { if (!fDeterministic) { return; } uint256 seed; - rng.SetKey(seed.begin(), 32); + rng.SetKey32(seed.begin()); } FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexcept { requires_seed = from.requires_seed; rng = from.rng; - std::copy(std::begin(from.bytebuf), std::end(from.bytebuf), std::begin(bytebuf)); - bytebuf_size = from.bytebuf_size; bitbuf = from.bitbuf; bitbuf_size = from.bitbuf_size; from.requires_seed = true; - from.bytebuf_size = 0; from.bitbuf_size = 0; return *this; } diff --git a/src/random.h b/src/random.h index bb8b5539a3..49c0dff5bf 100644 --- a/src/random.h +++ b/src/random.h @@ -146,23 +146,11 @@ class FastRandomContext bool requires_seed; ChaCha20 rng; - unsigned char bytebuf[64]; - int bytebuf_size; - uint64_t bitbuf; int bitbuf_size; void RandomSeed(); - void FillByteBuffer() - { - if (requires_seed) { - RandomSeed(); - } - rng.Keystream(bytebuf, sizeof(bytebuf)); - bytebuf_size = sizeof(bytebuf); - } - void FillBitBuffer() { bitbuf = rand64(); @@ -186,10 +174,10 @@ class FastRandomContext /** Generate a random 64-bit integer. */ uint64_t rand64() noexcept { - if (bytebuf_size < 8) FillByteBuffer(); - uint64_t ret = ReadLE64(bytebuf + 64 - bytebuf_size); - bytebuf_size -= 8; - return ret; + if (requires_seed) RandomSeed(); + unsigned char buf[8]; + rng.Keystream(buf, 8); + return ReadLE64(buf); } /** Generate a random (bits)-bit integer. */ diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 15b8e1dcd0..3ba930f84f 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -21,12 +21,8 @@ #include #include -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional rbf) +void AddInputs(CMutableTransaction& rawTx, const UniValue& inputs_in, std::optional rbf) { - if (outputs_in.isNull()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null"); - } - UniValue inputs; if (inputs_in.isNull()) { inputs = UniValue::VARR; @@ -34,18 +30,6 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal inputs = inputs_in.get_array(); } - const bool outputs_is_obj = outputs_in.isObject(); - UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array(); - - CMutableTransaction rawTx; - - if (!locktime.isNull()) { - int64_t nLockTime = locktime.getInt(); - if (nLockTime < 0 || nLockTime > LOCKTIME_MAX) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); - rawTx.nLockTime = nLockTime; - } - for (unsigned int idx = 0; idx < inputs.size(); idx++) { const UniValue& input = inputs[idx]; const UniValue& o = input.get_obj(); @@ -84,6 +68,16 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal rawTx.vin.push_back(in); } +} + +void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in) +{ + if (outputs_in.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null"); + } + + const bool outputs_is_obj = outputs_in.isObject(); + UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array(); if (!outputs_is_obj) { // Translate array of key-value pairs into dict @@ -132,6 +126,21 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal rawTx.vout.push_back(out); } } +} + +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional rbf) +{ + CMutableTransaction rawTx; + + if (!locktime.isNull()) { + int64_t nLockTime = locktime.getInt(); + if (nLockTime < 0 || nLockTime > LOCKTIME_MAX) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); + rawTx.nLockTime = nLockTime; + } + + AddInputs(rawTx, inputs_in, rbf); + AddOutputs(rawTx, outputs_in); if (rbf.has_value() && rbf.value() && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option"); diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index 0c3823bc1e..a863432b7a 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -38,6 +38,13 @@ void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const */ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map& coins); + +/** Normalize univalue-represented inputs and add them to the transaction */ +void AddInputs(CMutableTransaction& rawTx, const UniValue& inputs_in, bool rbf); + +/** Normalize univalue-represented outputs and add them to the transaction */ +void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in); + /** Create a transaction from univalue parameters */ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional rbf); diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 8991bda340..857fee1818 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -1014,7 +1014,7 @@ class MiniscriptDescriptor final : public DescriptorImpl return false; } - bool IsSolvable() const override { return false; } // For now, mark these descriptors as non-solvable (as we don't have signing logic for them). + bool IsSolvable() const override { return true; } bool IsSingleType() const final { return true; } }; diff --git a/src/script/descriptor.h b/src/script/descriptor.h index cb3b366acf..39b1a37f9a 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -35,7 +35,7 @@ class DescriptorCache { /** Retrieve a cached parent xpub * * @param[in] key_exp_pos Position of the key expression within the descriptor - * @param[in] xpub The CExtPubKey to get from cache + * @param[out] xpub The CExtPubKey to get from cache */ bool GetCachedParentExtPubKey(uint32_t key_exp_pos, CExtPubKey& xpub) const; /** Cache an xpub derived at an index @@ -49,7 +49,7 @@ class DescriptorCache { * * @param[in] key_exp_pos Position of the key expression within the descriptor * @param[in] der_index Derivation index of the xpub - * @param[in] xpub The CExtPubKey to get from cache + * @param[out] xpub The CExtPubKey to get from cache */ bool GetCachedDerivedExtPubKey(uint32_t key_exp_pos, uint32_t der_index, CExtPubKey& xpub) const; /** Cache a last hardened xpub @@ -61,7 +61,7 @@ class DescriptorCache { /** Retrieve a cached last hardened xpub * * @param[in] key_exp_pos Position of the key expression within the descriptor - * @param[in] xpub The CExtPubKey to get from cache + * @param[out] xpub The CExtPubKey to get from cache */ bool GetCachedLastHardenedExtPubKey(uint32_t key_exp_pos, CExtPubKey& xpub) const; diff --git a/src/script/miniscript.cpp b/src/script/miniscript.cpp index 5e471cbe89..3937638cf8 100644 --- a/src/script/miniscript.cpp +++ b/src/script/miniscript.cpp @@ -172,8 +172,8 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector::max(); + has_sig = false; + malleable = false; + non_canon = false; + } + return *this; +} + +InputStack& InputStack::SetWithSig() { + has_sig = true; + return *this; +} + +InputStack& InputStack::SetNonCanon() { + non_canon = true; + return *this; +} + +InputStack& InputStack::SetMalleable(bool x) { + malleable = x; + return *this; +} + +InputStack operator+(InputStack a, InputStack b) { + a.stack = Cat(std::move(a.stack), std::move(b.stack)); + if (a.available != Availability::NO && b.available != Availability::NO) a.size += b.size; + a.has_sig |= b.has_sig; + a.malleable |= b.malleable; + a.non_canon |= b.non_canon; + if (a.available == Availability::NO || b.available == Availability::NO) { + a.SetAvailable(Availability::NO); + } else if (a.available == Availability::MAYBE || b.available == Availability::MAYBE) { + a.SetAvailable(Availability::MAYBE); + } + return a; +} + +InputStack operator|(InputStack a, InputStack b) { + // If only one is invalid, pick the other one. If both are invalid, pick an arbitrary one. + if (a.available == Availability::NO) return b; + if (b.available == Availability::NO) return a; + // If only one of the solutions has a signature, we must pick the other one. + if (!a.has_sig && b.has_sig) return a; + if (!b.has_sig && a.has_sig) return b; + if (!a.has_sig && !b.has_sig) { + // If neither solution requires a signature, the result is inevitably malleable. + a.malleable = true; + b.malleable = true; + } else { + // If both options require a signature, prefer the non-malleable one. + if (b.malleable && !a.malleable) return a; + if (a.malleable && !b.malleable) return b; + } + // Between two malleable or two non-malleable solutions, pick the smaller one between + // YESes, and the bigger ones between MAYBEs. Prefer YES over MAYBE. + if (a.available == Availability::YES && b.available == Availability::YES) { + return std::move(a.size <= b.size ? a : b); + } else if (a.available == Availability::MAYBE && b.available == Availability::MAYBE) { + return std::move(a.size >= b.size ? a : b); + } else if (a.available == Availability::YES) { + return a; + } else { + return b; + } +} + std::optional> DecomposeScript(const CScript& script) { std::vector out; diff --git a/src/script/miniscript.h b/src/script/miniscript.h index 3a3f724f03..c42b530c4d 100644 --- a/src/script/miniscript.h +++ b/src/script/miniscript.h @@ -223,6 +223,11 @@ enum class Fragment { // WRAP_U(X) is represented as OR_I(X,0) }; +enum class Availability { + NO, + YES, + MAYBE, +}; namespace internal { @@ -235,6 +240,62 @@ size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_ //! A helper sanitizer/checker for the output of CalcType. Type SanitizeType(Type x); +//! An object representing a sequence of witness stack elements. +struct InputStack { + /** Whether this stack is valid for its intended purpose (satisfaction or dissatisfaction of a Node). + * The MAYBE value is used for size estimation, when keys/preimages may actually be unavailable, + * but may be available at signing time. This makes the InputStack structure and signing logic, + * filled with dummy signatures/preimages usable for witness size estimation. + */ + Availability available = Availability::YES; + //! Whether this stack contains a digital signature. + bool has_sig = false; + //! Whether this stack is malleable (can be turned into an equally valid other stack by a third party). + bool malleable = false; + //! Whether this stack is non-canonical (using a construction known to be unnecessary for satisfaction). + //! Note that this flag does not affect the satisfaction algorithm; it is only used for sanity checking. + bool non_canon = false; + //! Serialized witness size. + size_t size = 0; + //! Data elements. + std::vector> stack; + //! Construct an empty stack (valid). + InputStack() {} + //! Construct a valid single-element stack (with an element up to 75 bytes). + InputStack(std::vector in) : size(in.size() + 1), stack(Vector(std::move(in))) {} + //! Change availability + InputStack& SetAvailable(Availability avail); + //! Mark this input stack as having a signature. + InputStack& SetWithSig(); + //! Mark this input stack as non-canonical (known to not be necessary in non-malleable satisfactions). + InputStack& SetNonCanon(); + //! Mark this input stack as malleable. + InputStack& SetMalleable(bool x = true); + //! Concatenate two input stacks. + friend InputStack operator+(InputStack a, InputStack b); + //! Choose between two potential input stacks. + friend InputStack operator|(InputStack a, InputStack b); +}; + +/** A stack consisting of a single zero-length element (interpreted as 0 by the script interpreter in numeric context). */ +static const auto ZERO = InputStack(std::vector()); +/** A stack consisting of a single malleable 32-byte 0x0000...0000 element (for dissatisfying hash challenges). */ +static const auto ZERO32 = InputStack(std::vector(32, 0)).SetMalleable(); +/** A stack consisting of a single 0x01 element (interpreted as 1 by the script interpreted in numeric context). */ +static const auto ONE = InputStack(Vector((unsigned char)1)); +/** The empty stack. */ +static const auto EMPTY = InputStack(); +/** A stack representing the lack of any (dis)satisfactions. */ +static const auto INVALID = InputStack().SetAvailable(Availability::NO); + +//! A pair of a satisfaction and a dissatisfaction InputStack. +struct InputResult { + InputStack nsat, sat; + + template + InputResult(A&& in_nsat, B&& in_sat) : nsat(std::forward(in_nsat)), sat(std::forward(in_sat)) {} +}; + //! Class whose objects represent the maximum of a list of integers. template struct MaxInt { @@ -785,6 +846,226 @@ struct Node { assert(false); } + template + internal::InputResult ProduceInput(const Ctx& ctx) const { + using namespace internal; + + // Internal function which is invoked for every tree node, constructing satisfaction/dissatisfactions + // given those of its subnodes. + auto helper = [&ctx](const Node& node, Span subres) -> InputResult { + switch (node.fragment) { + case Fragment::PK_K: { + std::vector sig; + Availability avail = ctx.Sign(node.keys[0], sig); + return {ZERO, InputStack(std::move(sig)).SetWithSig().SetAvailable(avail)}; + } + case Fragment::PK_H: { + std::vector key = ctx.ToPKBytes(node.keys[0]), sig; + Availability avail = ctx.Sign(node.keys[0], sig); + return {ZERO + InputStack(key), (InputStack(std::move(sig)).SetWithSig() + InputStack(key)).SetAvailable(avail)}; + } + case Fragment::MULTI: { + // sats[j] represents the best stack containing j valid signatures (out of the first i keys). + // In the loop below, these stacks are built up using a dynamic programming approach. + // sats[0] starts off being {0}, due to the CHECKMULTISIG bug that pops off one element too many. + std::vector sats = Vector(ZERO); + for (size_t i = 0; i < node.keys.size(); ++i) { + std::vector sig; + Availability avail = ctx.Sign(node.keys[i], sig); + // Compute signature stack for just the i'th key. + auto sat = InputStack(std::move(sig)).SetWithSig().SetAvailable(avail); + // Compute the next sats vector: next_sats[0] is a copy of sats[0] (no signatures). All further + // next_sats[j] are equal to either the existing sats[j], or sats[j-1] plus a signature for the + // current (i'th) key. The very last element needs all signatures filled. + std::vector next_sats; + next_sats.push_back(sats[0]); + for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back(sats[j] | (std::move(sats[j - 1]) + sat)); + next_sats.push_back(std::move(sats[sats.size() - 1]) + std::move(sat)); + // Switch over. + sats = std::move(next_sats); + } + // The dissatisfaction consists of k+1 stack elements all equal to 0. + InputStack nsat = ZERO; + for (size_t i = 0; i < node.k; ++i) nsat = std::move(nsat) + ZERO; + assert(node.k <= sats.size()); + return {std::move(nsat), std::move(sats[node.k])}; + } + case Fragment::THRESH: { + // sats[k] represents the best stack that satisfies k out of the *last* i subexpressions. + // In the loop below, these stacks are built up using a dynamic programming approach. + // sats[0] starts off empty. + std::vector sats = Vector(EMPTY); + for (size_t i = 0; i < subres.size(); ++i) { + // Introduce an alias for the i'th last satisfaction/dissatisfaction. + auto& res = subres[subres.size() - i - 1]; + // Compute the next sats vector: next_sats[0] is sats[0] plus res.nsat (thus containing all dissatisfactions + // so far. next_sats[j] is either sats[j] + res.nsat (reusing j earlier satisfactions) or sats[j-1] + res.sat + // (reusing j-1 earlier satisfactions plus a new one). The very last next_sats[j] is all satisfactions. + std::vector next_sats; + next_sats.push_back(sats[0] + res.nsat); + for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + res.nsat) | (std::move(sats[j - 1]) + res.sat)); + next_sats.push_back(std::move(sats[sats.size() - 1]) + std::move(res.sat)); + // Switch over. + sats = std::move(next_sats); + } + // At this point, sats[k].sat is the best satisfaction for the overall thresh() node. The best dissatisfaction + // is computed by gathering all sats[i].nsat for i != k. + InputStack nsat = INVALID; + for (size_t i = 0; i < sats.size(); ++i) { + // i==k is the satisfaction; i==0 is the canonical dissatisfaction; + // the rest are non-canonical (a no-signature dissatisfaction - the i=0 + // form - is always available) and malleable (due to overcompleteness). + // Marking the solutions malleable here is not strictly necessary, as they + // should already never be picked in non-malleable solutions due to the + // availability of the i=0 form. + if (i != 0 && i != node.k) sats[i].SetMalleable().SetNonCanon(); + // Include all dissatisfactions (even these non-canonical ones) in nsat. + if (i != node.k) nsat = std::move(nsat) | std::move(sats[i]); + } + assert(node.k <= sats.size()); + return {std::move(nsat), std::move(sats[node.k])}; + } + case Fragment::OLDER: { + return {INVALID, ctx.CheckOlder(node.k) ? EMPTY : INVALID}; + } + case Fragment::AFTER: { + return {INVALID, ctx.CheckAfter(node.k) ? EMPTY : INVALID}; + } + case Fragment::SHA256: { + std::vector preimage; + Availability avail = ctx.SatSHA256(node.data, preimage); + return {ZERO32, InputStack(std::move(preimage)).SetAvailable(avail)}; + } + case Fragment::RIPEMD160: { + std::vector preimage; + Availability avail = ctx.SatRIPEMD160(node.data, preimage); + return {ZERO32, InputStack(std::move(preimage)).SetAvailable(avail)}; + } + case Fragment::HASH256: { + std::vector preimage; + Availability avail = ctx.SatHASH256(node.data, preimage); + return {ZERO32, InputStack(std::move(preimage)).SetAvailable(avail)}; + } + case Fragment::HASH160: { + std::vector preimage; + Availability avail = ctx.SatHASH160(node.data, preimage); + return {ZERO32, InputStack(std::move(preimage)).SetAvailable(avail)}; + } + case Fragment::AND_V: { + auto& x = subres[0], &y = subres[1]; + // As the dissatisfaction here only consist of a single option, it doesn't + // actually need to be listed (it's not required for reasoning about malleability of + // other options), and is never required (no valid miniscript relies on the ability + // to satisfy the type V left subexpression). It's still listed here for + // completeness, as a hypothetical (not currently implemented) satisfier that doesn't + // care about malleability might in some cases prefer it still. + return {(y.nsat + x.sat).SetNonCanon(), y.sat + x.sat}; + } + case Fragment::AND_B: { + auto& x = subres[0], &y = subres[1]; + // Note that it is not strictly necessary to mark the 2nd and 3rd dissatisfaction here + // as malleable. While they are definitely malleable, they are also non-canonical due + // to the guaranteed existence of a no-signature other dissatisfaction (the 1st) + // option. Because of that, the 2nd and 3rd option will never be chosen, even if they + // weren't marked as malleable. + return {(y.nsat + x.nsat) | (y.sat + x.nsat).SetMalleable().SetNonCanon() | (y.nsat + x.sat).SetMalleable().SetNonCanon(), y.sat + x.sat}; + } + case Fragment::OR_B: { + auto& x = subres[0], &z = subres[1]; + // The (sat(Z) sat(X)) solution is overcomplete (attacker can change either into dsat). + return {z.nsat + x.nsat, (z.nsat + x.sat) | (z.sat + x.nsat) | (z.sat + x.sat).SetMalleable().SetNonCanon()}; + } + case Fragment::OR_C: { + auto& x = subres[0], &z = subres[1]; + return {INVALID, std::move(x.sat) | (z.sat + x.nsat)}; + } + case Fragment::OR_D: { + auto& x = subres[0], &z = subres[1]; + return {z.nsat + x.nsat, std::move(x.sat) | (z.sat + x.nsat)}; + } + case Fragment::OR_I: { + auto& x = subres[0], &z = subres[1]; + return {(x.nsat + ONE) | (z.nsat + ZERO), (x.sat + ONE) | (z.sat + ZERO)}; + } + case Fragment::ANDOR: { + auto& x = subres[0], &y = subres[1], &z = subres[2]; + return {(y.nsat + x.sat).SetNonCanon() | (z.nsat + x.nsat), (y.sat + x.sat) | (z.sat + x.nsat)}; + } + case Fragment::WRAP_A: + case Fragment::WRAP_S: + case Fragment::WRAP_C: + case Fragment::WRAP_N: + return std::move(subres[0]); + case Fragment::WRAP_D: { + auto &x = subres[0]; + return {ZERO, x.sat + ONE}; + } + case Fragment::WRAP_J: { + auto &x = subres[0]; + // If a dissatisfaction with a nonzero top stack element exists, an alternative dissatisfaction exists. + // As the dissatisfaction logic currently doesn't keep track of this nonzeroness property, and thus even + // if a dissatisfaction with a top zero element is found, we don't know whether another one with a + // nonzero top stack element exists. Make the conservative assumption that whenever the subexpression is weakly + // dissatisfiable, this alternative dissatisfaction exists and leads to malleability. + return {InputStack(ZERO).SetMalleable(x.nsat.available != Availability::NO && !x.nsat.has_sig), std::move(x.sat)}; + } + case Fragment::WRAP_V: { + auto &x = subres[0]; + return {INVALID, std::move(x.sat)}; + } + case Fragment::JUST_0: return {EMPTY, INVALID}; + case Fragment::JUST_1: return {INVALID, EMPTY}; + } + assert(false); + return {INVALID, INVALID}; + }; + + auto tester = [&helper](const Node& node, Span subres) -> InputResult { + auto ret = helper(node, subres); + + // Do a consistency check between the satisfaction code and the type checker + // (the actual satisfaction code in ProduceInputHelper does not use GetType) + + // For 'z' nodes, available satisfactions/dissatisfactions must have stack size 0. + if (node.GetType() << "z"_mst && ret.nsat.available != Availability::NO) assert(ret.nsat.stack.size() == 0); + if (node.GetType() << "z"_mst && ret.sat.available != Availability::NO) assert(ret.sat.stack.size() == 0); + + // For 'o' nodes, available satisfactions/dissatisfactions must have stack size 1. + if (node.GetType() << "o"_mst && ret.nsat.available != Availability::NO) assert(ret.nsat.stack.size() == 1); + if (node.GetType() << "o"_mst && ret.sat.available != Availability::NO) assert(ret.sat.stack.size() == 1); + + // For 'n' nodes, available satisfactions/dissatisfactions must have stack size 1 or larger. For satisfactions, + // the top element cannot be 0. + if (node.GetType() << "n"_mst && ret.sat.available != Availability::NO) assert(ret.sat.stack.size() >= 1); + if (node.GetType() << "n"_mst && ret.nsat.available != Availability::NO) assert(ret.nsat.stack.size() >= 1); + if (node.GetType() << "n"_mst && ret.sat.available != Availability::NO) assert(!ret.sat.stack.back().empty()); + + // For 'd' nodes, a dissatisfaction must exist, and they must not need a signature. If it is non-malleable, + // it must be canonical. + if (node.GetType() << "d"_mst) assert(ret.nsat.available != Availability::NO); + if (node.GetType() << "d"_mst) assert(!ret.nsat.has_sig); + if (node.GetType() << "d"_mst && !ret.nsat.malleable) assert(!ret.nsat.non_canon); + + // For 'f'/'s' nodes, dissatisfactions/satisfactions must have a signature. + if (node.GetType() << "f"_mst && ret.nsat.available != Availability::NO) assert(ret.nsat.has_sig); + if (node.GetType() << "s"_mst && ret.sat.available != Availability::NO) assert(ret.sat.has_sig); + + // For non-malleable 'e' nodes, a non-malleable dissatisfaction must exist. + if (node.GetType() << "me"_mst) assert(ret.nsat.available != Availability::NO); + if (node.GetType() << "me"_mst) assert(!ret.nsat.malleable); + + // For 'm' nodes, if a satisfaction exists, it must be non-malleable. + if (node.GetType() << "m"_mst && ret.sat.available != Availability::NO) assert(!ret.sat.malleable); + + // If a non-malleable satisfaction exists, it must be canonical. + if (ret.sat.available != Availability::NO && !ret.sat.malleable) assert(!ret.sat.non_canon); + + return ret; + }; + + return TreeEval(tester); + } + public: /** Update duplicate key information in this Node. * @@ -877,6 +1158,47 @@ struct Node { }); } + //! Determine whether a Miniscript node is satisfiable. fn(node) will be invoked for all + //! key, time, and hashing nodes, and should return their satisfiability. + template + bool IsSatisfiable(F fn) const + { + // TreeEval() doesn't support bool as NodeType, so use int instead. + return TreeEval([&fn](const Node& node, Span subs) -> bool { + switch (node.fragment) { + case Fragment::JUST_0: + return false; + case Fragment::JUST_1: + return true; + case Fragment::PK_K: + case Fragment::PK_H: + case Fragment::MULTI: + case Fragment::AFTER: + case Fragment::OLDER: + case Fragment::HASH256: + case Fragment::HASH160: + case Fragment::SHA256: + case Fragment::RIPEMD160: + return bool{fn(node)}; + case Fragment::ANDOR: + return (subs[0] && subs[1]) || subs[2]; + case Fragment::AND_V: + case Fragment::AND_B: + return subs[0] && subs[1]; + case Fragment::OR_B: + case Fragment::OR_C: + case Fragment::OR_D: + case Fragment::OR_I: + return subs[0] || subs[1]; + case Fragment::THRESH: + return std::count(subs.begin(), subs.end(), true) >= node.k; + default: // wrappers + assert(subs.size() == 1); + return subs[0]; + } + }); + } + //! Check whether this node is valid at all. bool IsValid() const { return !(GetType() == ""_mst) && ScriptSize() <= MAX_STANDARD_P2WSH_SCRIPT_SIZE; } @@ -904,6 +1226,18 @@ struct Node { //! Check whether this node is safe as a script on its own. bool IsSane() const { return IsValidTopLevel() && IsSaneSubexpression() && NeedsSignature(); } + //! Produce a witness for this script, if possible and given the information available in the context. + //! The non-malleable satisfaction is guaranteed to be valid if it exists, and ValidSatisfaction() + //! is true. If IsSane() holds, this satisfaction is guaranteed to succeed in case the node's + //! conditions are satisfied (private keys and hash preimages available, locktimes satsified). + template + Availability Satisfy(const Ctx& ctx, std::vector>& stack, bool nonmalleable = true) const { + auto ret = ProduceInput(ctx); + if (nonmalleable && (ret.sat.malleable || !ret.sat.has_sig)) return Availability::NO; + stack = std::move(ret.sat.stack); + return ret.sat.available; + } + //! Equality testing. bool operator==(const Node& arg) const { return Compare(*this, arg) == 0; } diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 70df9ee62c..85589fe86b 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -10,6 +10,7 @@ #include #include #include