Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

python-bitcointx backend for jmbitcoin + bip78 and snicker. #536

Merged
merged 15 commits into from
Jul 10, 2020
Merged

Conversation

AdamISZ
Copy link
Member

@AdamISZ AdamISZ commented Mar 8, 2020

(See second comment for overview of what this is)

Replaces core transaction, address, serialization
and sign functionality for Bitcoin with
python-bitcointx backend.

Removes bech32 and btscript
modules from jmbitcoin. Removes all string,
hex, binary conversion routines. A generic
hex/binary conversion now is added to jmbase.
Removes all transaction serialization and
deserialization routines. Removes the now
irrelevant test modules.

Remaining functions in jmbitcoin remove any parsing of
hex format, requiring callers to use binary only.

One additional test added, testing the remaining
function in secp256k1_transaction.py: the signing
of transactions. Deserialized form is now
bitcointx.CMutableTransaction.

For jmbase, in addition to the above, generic conversions
for utxos to and from strings is added, and a dynamic conversion
for AMP messages to binary-only. Within the code, utxos are
now only in (binarytxid, int) form, except where converted
for communcation.

Tthe largest part of the changes are
the modifications to jmbitcoin calls in jmclient;
as well as different encapsulation with CMutableTransaction,
there is also a removal of some but not all hex parsing;
it remains for rpc calls to Core and for AMP message
parsing. Backwards compatibility must be ensured so some
joinmarket protocol messages still use hex, and it is
also preserved in persistence of PoDLE data.
As part of this, some significant simplification of
certain legacy functions within the wallet has been done.

jmdaemon is entirely unaltered (save for one test which
simulates jmclient code).

@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 8, 2020

Summary for reviewers:

Motivation

We need the bitcoin code underlying the project to be robust and flexible, to support future new transaction types (including not just native segwit, which was already supported, but to support future segwit v1 and multisig of various types (might be potentially important for coinjoinxt or variants etc), and things like psbt). We also want it to support proper script verification, albeit that is not critical for basic coinjoin.
What existed before this PR doesn't fit that description; it started from python-bitcointools and morphed over a long period but ended up very messy w.r.t formatting (see below) and also the transaction serializing/deserializing was too complex to reason about as a result of not being built cleanly from the ground up.

The above explains the desire to move to python-bitcoinlib; the reason for choosing the fork python-bitcointx (repo ; I recommend reading the README) instead is:

  • the latter is actively maintained, the former has not been for some time (most specifically, the former has a commit for segwit which has not been included in a release)
  • the latter was designed to address all the things we need, including specifically segwit, psbt, and bip32 (though see below for why that isn't yet used), and not p2p layer bitcoin (irrelevant for us).

The other element of the motivation for this PR is touched on in the above - formatting mess. This specifically means mixed use of hex and binary across the codebase, but also the difficult to reason about serialization and deserialization routines in jmbitcoin. This PR attempts to address these and I think does so, but it does not remove all such complexity, for reasons explained below.

Also, not a completely trivial point, but a simple reduction in the amount of code is a motivation - jmbitcoin in particular is, unsurprisingly, drastically reduced in size.

Caveat

python-bitcointx seems to fit our needs very well, but as they note, it is "opinionated" in one sense - it requires Python 3.6 . A question to those knowledgeable about such things - is this viable for us?

Implementation

  • jmbase - a couple of generic formatting routines are added here, as they can be reused across the codebase. In particular the hex/bin conversion routines. Notably also there is a decorator which can be used to convert function arguments from those used in AMP (hex, strings for utxos) to the codebase format (binary, utxos as (binarytxid, n)).
  • jmdaemon - as mentioned, this is entirely unaltered, as per design - jmdaemon was designed to know nothing about bitcoin, but just pass messages.
  • jmbitcoin - a lot of files are just removed since their function is now provided by python-bitcointx (the latter is of course added as a dependency in setup.py). Transaction construction and verification is still there but now wraps the basic functions from the lib. Not every processing feature is removed, and the reason is as follows:
    • I wasn't quite ready to take on the entire key management architecture from python-bitcointx(/tools). There is complex openssl parsing there, although I believe it can be avoided entirely by using libsecp, as we do. But it's another chunk of work. Instead, I've kept the simple private key to pubkey directly from the coincurve binding for now. Another obscure reason for wanting to keep that binding - we still need a multiplicative tweak function to do PoDLE, whereas (if I'm correct) python-bitcointx only exposes an additive tweak (we need both).
    • Very minor: we still need base58 encoding without check, for our nick construction, so a very simple wrapper function is included for that.

jmclient changes

This is where the biggest chunk of work was needed. The most important thing to clear up was, where is hex format used instead of binary (or strings instead of (txid, n) for utxos):

  • rpc calls in blockchaininterface.py use hex in and out; this format change now is done within that module, and callers use binary in and out.
  • AMP messages between client and daemon still use hex (can this change? haven't investigated) and strings, we use the @hexbin decorator in jmbase mentioned above so that the calls to functions in maker and taker automatically become binary format, thus removing/reducing the need for conversions in those modules.
  • Joinmarket's inter-participant protocol messages obviously can't change (including signatures) so, at least within client-protocol.py, this required attention to make sure we were still using hex in the same places as before.
  • output to files in logging and PoDLE commitments persistence is still in hex.

More testing is needed to check that user output always looks sensible after these changes.
There are, expectedly, a huge number of changes needed to the test files to correct all these formatting changes.
Notably the wallet code in particular is meaningfully simplified by no longer having to worry about utxos in different formats.

Still TODO:

  • payjoin code needs updating done
  • test/test_segwit.py needs updating done
  • some user level scripts will need changes (haven't started looking) done
  • cross-version tests done for command line sendpayment and tumbler

@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 14, 2020

Update on status: apart from a huge mess around testing, which actually has nothing to do with this PR, and which I'm working on fixing right now, the status here is:

  • last commit is actually adding a test of privkeys that I forgot to include (ignore title)
  • once I get back to working on this, I'll be testing out Qt, as it will doubtless need some changes before being ready. Pretty much everything else is, though, and my cross-version tests seem so far to indicate that there is no compatibility bug.

If people want to do other testing that'd be appreciated. I realise that code review is a bit daunting but you could always just review a small part (I originally intended to separate this into multiple functionally separate commits but it proved impossible to make work).
Also a great area of help would be to ensure that we can somehow reuse one libsecp installation between coincurve and python-bitcointx. (Yes I know it's annoying having two libs but the rather boring reasons for not dropping the former are explained above.)

@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 15, 2020

Rebased on master as of 73d1039

Qt tests are working so far but more will be done.

@AdamISZ
Copy link
Member Author

AdamISZ commented Apr 18, 2020

Rebased on master again as of 2a10e86

@AdamISZ
Copy link
Member Author

AdamISZ commented Apr 21, 2020

PSBT support as of 8bf0346 ; commit notes tell most of the story. This is a beginning only, but it is still probably the bulk of the work in terms of what we need. I see the main goal as manual coinjoins, but it of course will also allow the SNICKER code to work, and also the Payjoin proposal to work, although for the latter in particular, I'm going to have to extend considerably to make sure that all input and output script types are functioning correctly.
Next stage I will write some tests and extend script types supported.

@AdamISZ
Copy link
Member Author

AdamISZ commented Apr 23, 2020

Now that the PSBT code is included, it was much cleaner and easier for me to implement SNICKER. The base code is in jmbitcoin, and the basic functionality (create a half-signed proposal, receive a proposal and complete) is now in a SNICKERWalletMixin in jmclient/wallet.py. An end to end test of the workflow is in jmclient/test/test_snicker.py. Consequently I have closed #403.

@AdamISZ
Copy link
Member Author

AdamISZ commented Apr 23, 2020

381423b supports the first element of the PayJoin workflow: the direct_send function can now optionally output a PSBT of the created transaction (without broadcasting, of course), instead of a raw transaction and broadcast.

@kristapsk
Copy link
Member

direct_send function can now optionally output a PSBT of the created transaction (without broadcasting, of course)

If such thing is added for coinjoins too, this will make #414 obsolete and can potentially be useful in future for LN channel opening and other more complicated stuff.

@AdamISZ
Copy link
Member Author

AdamISZ commented Apr 23, 2020

direct_send function can now optionally output a PSBT of the created transaction (without broadcasting, of course)

If such thing is added for coinjoins too, this will make #414 obsolete and can potentially be useful in future for LN channel opening and other more complicated stuff.

Yes. To be clear, I am being cautious in not adding the most obvious use-case yet: as you say, to create a partially signed coinjoin using the wallet, or co-sign an existing PSBT, because there are security concerns if you do that the wrong way. So I'll need to add a few checks before supporting that. But I don't mean that it's difficult, it will be fairly easy to add this.

(For now the workflow is just: add --psbt to sendpayment and if you are using -N 0 it will just output a base64 encoded PSBT which is already finalized. Not very useful - except for Payjoin, which can call direct_send as a function and get the PSBT returned.)

@kristapsk
Copy link
Member

it requires Python 3.6 . A question to those knowledgeable about such things - is this viable for us?

For this I think we need to look what is latest "stable" in most popular Linux distributions and also macOS. For Gentoo, which I use, it's 3.7.7, so this is fine, but I'm minority here. Feedback from others on this would definitely no hurt.

@AdamISZ
Copy link
Member Author

AdamISZ commented Apr 24, 2020

If such thing is added for coinjoins too, this will make #414 obsolete and can potentially be useful in future for LN channel opening and other more complicated stuff.

More on this - I actually read #414 again and reminded myself of the details. So ... for a single coinjoin, on the command line only for now (Qt later), I agree we could do this here. But is PSBT actually preferable for that use-case? I guess not, as you're developing essentially a testing workflow, so you just need the tx itself (considering that it is fully signed), to pass across via inter-process-communication, or manually, or whatever suits your test. The PSBT way of doing things will be more useful for manual coinjoin than this, right? Or did I miss something.

@AdamISZ
Copy link
Member Author

AdamISZ commented Apr 25, 2020

@kristapsk so as of 9cc0333 I think we now have everything we need to write a BIP79++ payjoin script (and indeed on Qt). I'll review your #560 now and then we can start doing that. Once we have the basic script working we can start testing it against btcpayserver server-side implementation.

@AdamISZ
Copy link
Member Author

AdamISZ commented Apr 29, 2020

See Simplexum/python-bitcointx#31 - this combined with 30350f0 (which may need some touchups later) allows automatic installation of a local libsecp256k1 library without touching the system installation (so no sudo, which we've been managing to avoid so far in our installation).

@dgpv
Copy link

dgpv commented Apr 29, 2020

it is "opinionated" in one sense

python-bitcointx may be opinionated in more than one sense. For example PSBT_Input vs PSBTInput. I prefer the former (being influenced by Ada), but Core uses the later, and also the second option is more pythonic. I may add an alias though if this becomes an issue. There may be other places where something is done how I see it best, and the code might deviate from mimicking Core (being close to Core code for easy reference is one feature of bitcoinlib, and bitcointx to a slightly lesser extent)

There is complex openssl parsing there, although I believe it can be avoided entirely by using libsecp, as we do.

openssl in python-bitcointx is optional, and used only in CPubKey.verify_nonstrict()

we still need a multiplicative tweak function to do PoDLE, whereas (if I'm correct) python-bitcointx only exposes an additive tweak (we need both)

Correct. PR welcome, though (IIUC that no extra dependencies is needed for that, if this is possible with libsecp256k1. Additive tweaks were obvious to do via libsecp, so I added them, but didn't look into multiplicative)

@decentclock
Copy link
Contributor

decentclock commented Jul 6, 2020

I am currently attempting to run the test suite at d34c53b, and I am getting the error below. This also causes python to crash.

My machine is mac os 10.15.5.

It looks to me like this is a problem with loading the openssl library.

@dgpv Adam told me this is something you might be able to help with?

I do have openssl installed.

stack trace jmbitcoin/test/test_tx_signing.py Fatal Python error: Aborted

Current thread 0x00000001163c1dc0 (most recent call first):
File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/ctypes/init.py", line 348 in init
File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/ctypes/init.py", line 426 in LoadLibrary
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/bitcointx/core/key.py", line 86 in load_openssl_library
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/bitcointx/core/key.py", line 488 in verify_nonstrict
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/bitcointx/core/scripteval.py", line 487 in _CheckSig
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/bitcointx/core/scripteval.py", line 871 in _EvalScript
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/bitcointx/core/scripteval.py", line 1122 in EvalScript
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/bitcointx/core/scripteval.py", line 405 in VerifyWitnessProgram
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/bitcointx/core/scripteval.py", line 1193 in VerifyScript
File "/Users/barbac/joinmarket-clientserver/jmbitcoin/jmbitcoin/secp256k1_transaction.py", line 270 in sign
File "/Users/barbac/joinmarket-clientserver/jmbitcoin/test/test_tx_signing.py", line 52 in test_sign_standard_txs
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/_pytest/python.py", line 167 in pytest_pyfunc_call
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/callers.py", line 187 in _multicall
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/manager.py", line 87 in
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/hooks.py", line 286 in call
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/_pytest/python.py", line 1445 in runtest
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/_pytest/runner.py", line 134 in pytest_runtest_call
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/callers.py", line 187 in _multicall
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/manager.py", line 87 in
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/hooks.py", line 286 in call
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/_pytest/runner.py", line 210 in
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/_pytest/runner.py", line 237 in from_call
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/_pytest/runner.py", line 210 in call_runtest_hook
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/_pytest/runner.py", line 185 in call_and_report
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/_pytest/runner.py", line 99 in runtestprotocol
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/_pytest/runner.py", line 84 in pytest_runtest_protocol
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/callers.py", line 187 in _multicall
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/manager.py", line 87 in
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/hooks.py", line 286 in call
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/_pytest/main.py", line 271 in pytest_runtestloop
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/callers.py", line 187 in _multicall
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/manager.py", line 87 in
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/hooks.py", line 286 in call
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/_pytest/main.py", line 247 in _main
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/_pytest/main.py", line 197 in wrap_session
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/_pytest/main.py", line 240 in pytest_cmdline_main
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/callers.py", line 187 in _multicall
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/manager.py", line 87 in
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/manager.py", line 93 in _hookexec
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/pluggy/hooks.py", line 286 in call
File "/Users/barbac/jmvenv/lib/python3.7/site-packages/_pytest/config/init.py", line 93 in main
File "/Users/barbac/jmvenv/bin/pytest", line 8 in
zsh: abort pytest --btcconf=/Users/barbac/Application\ Support/Bitcoin/bitcoin.conf

@dgpv
Copy link

dgpv commented Jul 7, 2020

This is a known issue with OSX Catalina release: Simplexum/python-bitcointx#32 Homebrew/homebrew-core#44996 <-- (this issue in Homebrew has some workarounds that can be done on the host machine)

This issue can occur in python-bitcointx if key.verify_nonstrict() is used, which loads openssl to be able to verify historical signatures, which can have non-strict encoding. To avoid calling key.verify_nonstrict() and enforce the use of key.verify(), which always uses secp256k1, it is enough to pass one of (SCRIPT_VERIFY_DERSIG, SCRIPT_VERIFY_LOW_S, SCRIPT_VERIFY_STRICTENC) flags to VerifyScript(). I guess the SCRIPT_VERIFY_STRICTENC would be the most appropriate in this case. As the jmbitcoin code should not generate non-strict signatures, forcing strict signature validation should be OK. I think changing flags = set() to flags = set([SCRIPT_VERIFY_STRICTENC]) in jmbitcoin/jmbitcoin/secp256k1_transaction.py around line 206 should do the trick (but there might be other places where VerifyScript is called).

Without this flag set, the python-bitcointx Script
verification will use openssl for non-strict encoding,
which requires a libopenssl dependency. Moreover non-
strict encoding is now out of consensus so is not needed
for our purpose.
@AdamISZ
Copy link
Member Author

AdamISZ commented Jul 7, 2020

Thanks @dgpv , that's what I was hoping for :) Indeed we don't of course need the non-strict version of verify, so added the SCRIPT_VERIFY_STRICTENC flag as you mention.
@Jules23 please check if the problem is fixed after 6e6bf0a . But also could you record your findings, for MacOS, on libsecp install (e.g. were you able to follow the same process as in the install.sh for Linux?).

@decentclock
Copy link
Contributor

decentclock commented Jul 7, 2020

@dgpv @AdamISZ The test suite passed with 6e6bf0a! Thanks for your help.

@AdamISZ for the libsecp install I followed the exact same process as in install.sh except for the make install step line 264.

I'm currently working on ironing out the install process.

@AdamISZ
Copy link
Member Author

AdamISZ commented Jul 7, 2020

@Jules23 great that sounds perfect. i guess some simple bundling of the libsecp install in another shell script might fit the bill. So far we've just provided that 4 or 5 step recipe for Mac users, so I don't think another step like that will hurt (although clearly it'd be nicer if we had everything in a one-step runnable script like we do for Linux).

@decentclock
Copy link
Contributor

decentclock commented Jul 7, 2020

@AdamISZ I can provide a script for the libsecp install. How do you want me to provide it? Branch off your pblwrap?
Then in the next month or so, I can try to create a homebrew formula so that mac users can just run brew install python libsodium libsecp256k1

@AdamISZ
Copy link
Member Author

AdamISZ commented Jul 7, 2020

Something like that sounds great, sure.

@decentclock
Copy link
Contributor

ok! definitely i agree the end goal is a the one script like in linux. I'll be working towards that.

@kristapsk
Copy link
Member

Re-tested:

  • install.sh works for me on Gentoo Linux;
  • all test cases pased (test/run_tests.sh);
  • received testnet coins in JoinMarketQt;
  • tried BIP21 parsing in JoinMarketQt, worked;
  • did testnet payjoin attempt to Kukks testnet BTCPay Server site, resulted in Service Unavailable, continued with ordinary payment as expected.

scripts/sendpayment.py Outdated Show resolved Hide resolved
scripts/sendpayment.py Outdated Show resolved Hide resolved
@AdamISZ
Copy link
Member Author

AdamISZ commented Jul 9, 2020

did testnet payjoin attempt to Kukks testnet BTCPay Server site, resulted in Service Unavailable, continued with ordinary payment as expected.

As per above comment, I'm doing the BIP78 changes now, I'll commit that new code once I get it passing on regtest and against Kukks' server.

This is now tested as compatible with BIP78 as
implemented by BTCPayServer.
An additional config section [PAYJOIN] is added to
manage settings for fee control in payjoin as
described in the BIP. These settings are marked as
advanced usage as they're rather complex for users
to understand and the defaults should be very safe.
@AdamISZ AdamISZ merged commit adc9b9e into master Jul 10, 2020
@AdamISZ AdamISZ deleted the pblwrap branch July 10, 2020 15:00
kristapsk added a commit that referenced this pull request Jul 18, 2020
73b0edc Update macOS installation instructions (Jules Comte)

Pull request description:

  This should be merged after #536, and is a followup to #629

  It includes build instructions for libsecp256k1 for those who don't use `install.sh`

  @AdamISZ I was wrong, it turns out that `install.sh` does need some modifications to work on mac os from scratch.

  My biggest question at this point is whether I have the list of darwin dependencies correct in `install.sh: line 44`

  With the current list, I was able to build, and pass the testsuite on fresh installations of Mac OS Catalina with Apple Command Line Tools and Homebrew installed.

Top commit has no ACKs.

Tree-SHA512: f9feed9d6052cefd043a025212e27347669aa6a082a4543d2f19a64f74ceb51694fa33efb5a92b79f7fc15a70efead4ac32cee3ab78acb6badd894f8b616aede
AdamISZ added a commit that referenced this pull request Aug 27, 2020
Prior to this commit, users setting the POLICY config
option `tx_broadcast` to anything other than `self` would
cause a crash after the merge of #536 due to a bin/hex
conversion failure (before this merge, the tx would simply
fail to broadcast).
This commit adds a `JMTXBroadcast` AMP command so that makers
can send arbitrary transactions from daemon to client, for
broadcast via the blockchain interface. This allows the
existing code in `taker.push()` to function correctly, after
fixing the bin/hex conversion bug. Hence users can now select
`random-peer` or `not-self` and the transaction will be
broadcast as expected according to the comments, and the
WalletService will react to the broadcast just as it does
currently for self-broadcast.
Note that this change will be ineffective if the counterparties
do not support it; the transaction will simply remain un-broadcast.
@AdamISZ AdamISZ mentioned this pull request Aug 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants