diff --git a/README.md b/README.md index 417047b62..280a41ffd 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,16 @@ For a quick introduction to Joinmarket you can watch [this demonstration](https: ### Wallet features -* Segwit addresses in the backward compatible form (start with `3`) +* Segwit addresses (native bech32 ('bc1') by default; p2sh wrapped ('3') optionally). * Multiple "mixdepths" or pockets (by default 5) for better coin isolation -* Ability to spend directly, or with coinjoin; export private keys; BIP49 compatible seed (Trezor, Samourai etc.) and mnemonic extension option +* Ability to spend directly, or with coinjoin; export private keys; BIP84/49 compatible seed (Trezor, Samourai etc.) and mnemonic extension option * Fine-grained control over bitcoin transaction fees * Basic coin control - can freeze individual utxos to stop them being spent in any transaction * Can run sequence of coinjoins in automated form, either auto-generated (see `tumbler.py`) or self-generated sequence. * Can specify exact amount of coinjoin (figures from 0.01 to 30.0 btc and higher are practical), can choose time and number of counterparties * Can run passively to receive small payouts for taking part in coinjoins (see "Maker" and "yield-generator" in docs) * GUI to support Taker role, including tumbler/automated coinjoin sequence. -* PayJoin - both [BIP78](https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki) to pay users of other wallets (e.g. merchants), as well as between two JM wallet users. This is a way to boost fungibility/privacy while paying. +* PayJoin - [BIP78](https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki) to pay users of other wallets (e.g. merchants), as well as between two compatible wallet users (Joinmarket, Wasabi, others). This is a way to boost fungibility/privacy while paying. * Protection from [forced address reuse](https://en.bitcoin.it/wiki/Privacy#Forced_address_reuse) attacks. ### Quickstart - RECOMMENDED INSTALLATION METHOD (Linux and macOS only) @@ -62,7 +62,7 @@ If you are new, follow and read the links in the [usage guide](docs/USAGE.md). If you are running Joinmarket-Qt, you can instead use the [walkthrough](docs/JOINMARKET-QT-GUIDE.md) to start. -If you used the old version of Joinmarket, the notes in the [scripts readme](scripts/README.md) help to understand what has and hasn't changed about the scripts. +If you used the old version of Joinmarket, the notes in the [scripts readme](scripts/README.md) help to understand what has and hasn't changed about the scripts (warning: this refers to changes from several years ago, so may be slightly outdated). If you are looking for the available makers, run the [orderbook](docs/orderbook.md). diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 0992e68a1..bc0965fb4 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -8,14 +8,10 @@ (You can ignore this whole section if starting from scratch). -#### Upgrading to new segwit version (0.3.0+ of this repo) - -See [here](SEGWIT-UPGRADE.md). - #### Notes on upgrading versions generally: If you just want the latest version in a new directory, go to the [releases](https://github.com/AdamISZ/joinmarket-clientserver/releases) page. -Binary executables are not currently being built; that may change in the future. +Binary executables (of JoinmarketQt) are currently being built only for Windows, please verify the signature on the .exe file attached to the release. Otherwise: if you are upgrading from an older version, just update using git: `git pull origin master`, or `git fetch; git checkout tags/` for a specific tagged release, then rerun the installation @@ -134,6 +130,9 @@ There are currently two choices for installing on Windows; one, directly install #### Installation directly on Windows +As per the note above, binaries for JoinmarketQt are being offered with releases as of 0.7.0+. +This section is for doing a full command line install, on Windows. + First, if you have not done so, install [Python](https://www.python.org/downloads/windows/) - specifically, the latest Python 3 version. Make sure to choose to install `pip` during the installation (it should be included automatically, but you can choose the custom installation option to choose it). Be sure to choose the option that allows `python` to be in your PATH variable (you are prompted to do this at the end of the install). diff --git a/docs/JOINMARKET-QT-GUIDE.md b/docs/JOINMARKET-QT-GUIDE.md index fe8581065..fcd3cf152 100644 --- a/docs/JOINMARKET-QT-GUIDE.md +++ b/docs/JOINMARKET-QT-GUIDE.md @@ -24,6 +24,7 @@ The github commits to the main repo, above, are signed. * [Exporting private keys](#export-private-keys) +(*Before starting, note that the screenshots here use testnet p2sh addresses (start with '2'), but by default Joinmarket mainnet addresses are native segwit ('bc1')*). ### Walkthrough diff --git a/docs/PAYJOIN.md b/docs/PAYJOIN.md index 5e891abea..05ba820f7 100644 --- a/docs/PAYJOIN.md +++ b/docs/PAYJOIN.md @@ -16,13 +16,13 @@ For that, see [this](https://joinmarket.me/blog/blog/payjoin/) post. 4. [Doing a PayJoin payment](#doing) - a. [Using BIP78 payjoins to pay a merchant](#bip78) + a. [Using BIP78 payjoins to pay a merchant or other user's wallet](#bip78) b. [Using Joinmarket-wallet-to-Joinmarket-wallet payjoins](#jmtojm) c. [About fees](#fees) -5. [What if I wanted bech32 native segwit addresses?](#native) +5. [What if I wanted p2sh segwit addresses?](#p2sh) 6. [Receiving a BIP78 Payjoin payment](#receiving) @@ -117,10 +117,10 @@ So now we know that, let's continue doing the `generate` command to make a new w Continue/complete the wallet generation with the above (`generate`) method. -(But wait again! Before you finish: want a bech32 wallet? you probably don't, -but read [this](#what-if-i-wanted-bech32-native-segwit-addresses) if you do.) +(But wait again! Before you finish: want a p2sh segwit wallet? you probably don't, +but read [this](#what-if-i-wanted-p2sh-segwit-addresses) if you do.) -The wallet you create is (if not bech32) BIP49 by default, using BIP39 12 word seed, +The wallet you create is (if not p2sh) BIP84 by default, using BIP39 12 word seed, mnemonic extension optional (simplest to leave it out if you're not sure). Once the `generate` method run has completed, successfully, you need to look at the wallet contents. Use @@ -132,8 +132,7 @@ python wallet-tool.py wallet-name-you-chose.dat [display] ("display" is optional because default; use `python wallet-tool.py -h` to see all possible methods). -Below is [an example](#sample) of what the wallet looks like (although -yours will be mainnet, so the addresses will start with '3' not '2'). +Below is [an example](#sample) of what the wallet looks like. Joinmarket by default uses *5* accounts, not only 1 as some other wallets (e.g. Electrum), this is to help with coin isolation. Try to move coins from one account to another *only* via coinjoins; or, you can just @@ -143,7 +142,7 @@ that if you don't bother to do anything special, the coins in those two mixdepth **Fund** the wallet by choosing one or more addresses in the *external* section of the first account (called "Mixdepth 0" here). When you fund, fund to the external addresses. The internals will be used for change. -(The new standard (BIP49) *should* be compatible with TREZOR, Ledger, Electrum, Samourai and some others, +(The new standard (BIP84) *should* be compatible with TREZOR, Ledger, Electrum, Samourai and some others, including the 12 word seed, although consider privacy concerns when sending addresses to remote servers!). @@ -165,9 +164,9 @@ The process here is to use the syntax of sendpayment.py: Notes on this: * Payjoins BIP78 style are done using the `sendpayment` script, or by entering the BIP21 URI into the "Recipient" field in JoinmarketQt. * They are done using BIP21 URIs. These can be copy/pasted from a website (e.g. a btcpayserver invoice page), note that double quotes are required (on the command line) because the string contains special characters. Note also that you must see `pj=` in the URI, otherwise payjoin is not supported by that server. -* If the url in `pj=` is `****.onion` it means you must be using Tor, remember to have Tor running on your system and change the configuration (see below) for sock5 port if necessary. If you are running the Tor browser the port is 9150 instead of 9050. +* If the url in `pj=` is `****.onion` it means you must be using Tor, remember to have Tor running on your system and change the configuration (see below) for socks5 port if necessary. If you are running the Tor browser the port is 9150 instead of 9050. * Don't forget to specify the mixdepth you are spending from with `-m 0`. The payment amount is of course in the URI, along with the address. -* Pay attention to address type; this point is complicated, but: some servers will not be able to match the address type of the sender, and so won't be able to construct sensible Payjoin transactions. In that case they may fallback to the non-Payjoin payment (which is not a disaster). If you want to do a Payjoin with a server that only supports bech32, you will have to create a new Joinmarket wallet, specifying `native=true` in the `POLICY` section of `joinmarket.cfg` before you generate the wallet. +* Pay attention to address type; this point is complicated, but: some servers will not be able to match the address type of the sender, and so won't be able to construct sensible Payjoin transactions. In that case they may fallback to the non-Payjoin payment (which is not a disaster). If you want to do a Payjoin with a server that only supports p2sh, not bech32, you will have to create a new Joinmarket wallet, specifying `native=false` in the `POLICY` section of `joinmarket.cfg` before you generate the wallet. Before you do such coinjoins, you may want to: * regenerate `joinmarket.cfg`. First, rename your current `joinmarket.cfg` (in `~/.joinmarket` on Linux), then run a script once to have it regenerated from defaults. Then reapply your custom changes. @@ -203,6 +202,11 @@ max_additional_fee_contribution = default # transaction; note it is decimal, not integer. min_fee_rate = 1.1 +# for payjoin onion service creation, the tor control configuration: +tor_control_host = localhost +# or, to use a UNIX socket +# control_host = unix:/var/run/tor/control +tor_control_port = 9051 # for payjoins to hidden service endpoints, the socks5 configuration: onion_socks5_host = localhost @@ -244,31 +248,29 @@ BIP78 itself has various controls around fees - essentially it tries to let the As a spender in the BIP78 protocol, you will usually see the following: a small reduction in the size of your change output as a result of the extra 1 input. Unless the payment is very small, this probably won't be significant. - + -#### What if I wanted bech32 native segwit addresses? +#### What if I wanted p2sh segwit addresses? You can do this, but bear in mind: PayJoin only gives its full effect if you and your receiver are using the same kind of addresses; so do this only if you and your receiver(s)/sender(s) agree on it - most BIP78 receivers at least for now will only engage in the protocol if they can provide inputs of the same type as yours. As was noted in the BIP78 section, it may be therefore that you *need* to do this (albeit that the worst that can happen is a fallback to non-payjoin payment, which isn't a disaster). -Also note: you *cannot* do Joinmarket coinjoins if you choose a bech32 wallet (this may change in future, see [this PR](https://github.com/JoinMarket-Org/joinmarket-clientserver/pull/656)). - In the configuration file `joinmarket.cfg` (which was created in the preparatory step above), go to the POLICY section and set: ``` [POLICY] -native = true +native = false ``` Note that this must be done *before* generating the wallet, as the wallet internally, of course, stores which type of addresses it manages, and it can only be of two -types currently (ignoring legacy upgrades): bech32 or p2sh-segwit (starting with '3'), the latter being -the default (and the one used in Joinmarket itself). +types currently (ignoring legacy upgrades): bech32 or p2sh-segwit (starting with '3'), the former being +the default in Joinmarket as of 0.8.0+. -Note that the bech32 style wallet is written to conform to [BIP84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki), -analogous to the BIP49 case for p2sh. +Note that the p2sh-wrapped-segwit style wallet is written to conform to [BIP49](https://github.com/bitcoin/bips/blob/master/bip-0040.mediawiki), +analogous to the BIP84 case for native/bech32. @@ -296,7 +298,7 @@ bitcoin:bc1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx?amount=0.32000000&pj=http://p53l Keep this process running until the payment is received. ``` -... which should be self explanatory. The sender may be using Joinmarket, or a different wallet that supports Payjoin, like Wasabi. As previously noted, Payjoins will not work if the address type in use by the two wallets is different, so for Wasabi it would be necessary to use a bech32 Joinmarket wallet (as was discussed [here](#native)). +... which should be self explanatory. The sender may be using Joinmarket, or a different wallet that supports Payjoin, like Wasabi. As previously noted, Payjoins will not work if the address type in use by the two wallets is different, so for Wasabi it would be necessary to use a bech32 Joinmarket wallet (the default). When the payment goes through you will see a chunk of logging text (most of which is serialized PSBTs being exchanged). If Payjoin was effected as intended, you will see: @@ -383,73 +385,70 @@ https://video.autizmo.xyz/videos/watch/7081ae10-dce0-491e-9717-389ccc3aad0d -#### Sample testnet wallet display output +#### Sample wallet display output ``` JM wallet -mixdepth 0 tpubDC4qk8DsyiFYY85uktoKaiR1srWLSNRxZ2A4MXYg5LHy9XHKSTcF2tNtFpGz4LzXPcDH1kNkiww7MwipNqNSps6HSyjPTqTB18J7C4jrFMi -external addresses m/49'/1'/0'/0 tpubDFFCJfi4y6xPKquA6H6aP5EBZPupb6A9tJz8sErW8GN6D7D3s9MABPt1BczpQ3n8rBv7VLSVpu3ddvb7xEKMfNX2sMZ7XxiBD4J6kpfF7Ag -m/49'/1'/0'/0/0 2NBT6npWKxBEG8fkDjSFLDZJ7fNda4kYnAx 2.00000000 deposit -m/49'/1'/0'/0/1 2Mt7dBFikYwCQTDU129VTF9ahKWxeJjEUuF 0.00000000 new -m/49'/1'/0'/0/2 2N94bte8xWojSaX6yq2th3R3mUhvKzZqDJ9 0.00000000 new -m/49'/1'/0'/0/3 2MvSBTkCKwPHLdNPRXbovPXUM8oAfjUFPYc 0.00000000 new -m/49'/1'/0'/0/4 2MzVn3Nc3RRyN7shiVajA225xCTcaGn1PRw 0.00000000 new -m/49'/1'/0'/0/5 2MxJwv2dmkMupDuBLsEMa4HgG6GAieWHiXr 0.00000000 new -m/49'/1'/0'/0/6 2MwhdkeAcnCkam1LdVBQJ7un8syyoYB1HVH 0.00000000 new -Balance: 2.00000000 -internal addresses m/49'/1'/0'/1 +mixdepth 0 xpub6CMAJ67vZWVXuzjzYXUoJgWrmuvFRiqiUG4dwoXNFmJtpTH3WgviANNxGyZYo27zxbMuqhDDym6fnBxmGaYoxr6LHgNDo1eEghkXHTX4Jnx +external addresses m/84'/0'/0'/0 xpub6FFUn4AxdqFbnTH2fyPrkLreEkStNnMFb6R1PyAykZ4fzN3LzvkRB4VF8zWrks4WhJroMYeFbCcfeooEVM6n2YRy1EAYUvUxZe6JET6XYaW +m/84'/0'/0'/0/0 bc1qt493axn3wl4gzjxvfg03vkacre0m6f2gzfhv5t 0.00000000 new +m/84'/0'/0'/0/1 bc1q2av9emer8k2j567yzv6ey6muqkuew4nh4rl85q 0.00000000 new +m/84'/0'/0'/0/2 bc1qggpg0q7cn4mpe98t29wte2rfn2rzjtn3zdmqye 0.00000000 new +m/84'/0'/0'/0/3 bc1qnnkqz8vcdjan7ztcpr68tyec7dw2yk8gjnr9ze 0.00000000 new +m/84'/0'/0'/0/4 bc1qud5s2ln88ktg83hkr6gv9s576zvt249qn2lepx 0.00000000 new +m/84'/0'/0'/0/5 bc1qw0lhq7xlhj7ww2jdaknv23vcyhnz6qxg23uthy 0.00000000 new +Balance: 0.00000000 +internal addresses m/84'/0'/0'/1 Balance: 0.00000000 -Balance for mixdepth 0: 2.00000000 -mixdepth 1 tpubDC4qk8DsyiFYba4t8cSpadjoLYUdPwV5dAtBpzpPzgaDKPfSP42xNJq48QUKEVGHQRfFej6DCUjQqCKD8TtcqN932f27jmyjXaxVMpksos4 -external addresses m/49'/1'/1'/0 tpubDEnijtftQiJpVgezdRNyKVVWGr9xKV9RgPQWHYDHpQ5utdHF7Sqh7xMUyNHcpdeKcKdQ753hFbyccRZNEHTUkHLnDaVqoXRo9XkATPtHhCp -m/49'/1'/1'/0/0 2NEsN45waxdkqjP5EnsP3K8YjMeZJh2RLx6 2.00000000 deposit -m/49'/1'/1'/0/1 2Mzhewfg6fr5jR122txdTShmLi7rH9ZscTm 2.00000000 deposit -m/49'/1'/1'/0/2 2N1vn44cv5m6PhRJMbZ1mR8dAJgHybnsT5u 2.00000000 deposit -m/49'/1'/1'/0/3 2NFwBsHkQ8mxCJWRJhkxAY28Tj8YuUJio4t 0.00000000 new -m/49'/1'/1'/0/4 2NA1YAa2VqHMSH9b2GYGBA8waUMxZTZwW5Z 0.00000000 new -m/49'/1'/1'/0/5 2MvLLp4cnP8ZVuWWDZzRCLFM2YcTfo9ALec 0.00000000 new -m/49'/1'/1'/0/6 2Mux3ZUHGmaMBiMsPDbQS56gRGGN26jMGzd 0.00000000 new -m/49'/1'/1'/0/7 2N3MWFiSHyRY3QLZgb63Vfcp4BGSFs6Y3bV 0.00000000 new -m/49'/1'/1'/0/8 2N3z5zKJPTfPc41esRRvMvjdKaSv6D6jqvY 0.00000000 new -Balance: 6.00000000 -internal addresses m/49'/1'/1'/1 +Balance for mixdepth 0: 0.00000000 +mixdepth 1 xpub6CMAJ67vZWVXyTJEaZndxZy9ACUufsmNuJwp9k5dHHKa22zQdsgALxXvMRFSwtvB8BRJzsd8h17pKqoAyHtkBrAoSqC9AUcXB1cPrSYATsZ +external addresses m/84'/0'/1'/0 xpub6FNSLcHuGnoUbaiKgwXuKpfcbR63ybrjaqHCudrma13NDqMfKgBtZRiPZaHjSbCY3P3cgEEcdzZCwrLKXeT5jeuk8erdSmBuRgJJzfNnVjj +m/84'/0'/1'/0/0 bc1qhrvm7kd9hxv3vxs8mw2arcrsl9w37a7d6ccwe4 0.00000000 new +m/84'/0'/1'/0/1 bc1q0sccdfrsnjtgfytul5zulst46wxgahtcf44tcw 0.00000000 new +m/84'/0'/1'/0/2 bc1qst6p8hr8yg280zcpvvkxahv42ecvdzq63t75su 0.00000000 new +m/84'/0'/1'/0/3 bc1q0gkarwg8y3nc2mcusuaw9zsn3gvzwe8mp3ac9h 0.00000000 new +m/84'/0'/1'/0/4 bc1qkf5wlcla2qlg5g5sym9gk6q4l4k5c98vvyj33u 0.00000000 new +m/84'/0'/1'/0/5 bc1qz6zptlh3cqty2tqyspjk6ksqelnvrrrvmyqa5v 0.00000000 new Balance: 0.00000000 -Balance for mixdepth 1: 6.00000000 -mixdepth 2 tpubDC4qk8DsyiFYdQBNNzo3vHq63Gag4eUJT29UaTVNHM89hJk6CshZ9WGemQNDh2LGDXCud8anAQ4UR7n7tSWiJtviR8WJuTB78ZbEHpFNcLH -external addresses m/49'/1'/2'/0 tpubDEY4sVvs1TX82DftUUB51Agg4Ln7BksoRGESNWtrWTDntV2fCs5wNrqiPENcXBAEHtnaY9ZaK48PRFEw1GLhcTxdDNHUyuqDd2YyNYKoVAo -m/49'/1'/2'/0/0 2NBSnSB3nVN4TA7EcNkhcRsmrdyXSQepDFE 0.00000000 new -m/49'/1'/2'/0/1 2NFtrtpdvmCRXMy1fV8w1eLWpF8MC3nre7n 0.00000000 new -m/49'/1'/2'/0/2 2NFHJvGLWU7KuNkbo8rzPwfGtCS5RtNDw7c 0.00000000 new -m/49'/1'/2'/0/3 2NGJcmeScFnTSzZzSRNHiLL8zjYeA9ngx5A 0.00000000 new -m/49'/1'/2'/0/4 2NBUAxKrNQtp49xYYHh9f6YHfo2FvYBP1NL 0.00000000 new -m/49'/1'/2'/0/5 2MyQfzKyPTfYT4vTe2d3fyGvQMoGX6GAhcr 0.00000000 new +internal addresses m/84'/0'/1'/1 Balance: 0.00000000 -internal addresses m/49'/1'/2'/1 +Balance for mixdepth 1: 0.00000000 +mixdepth 2 xpub6CMAJ67vZWVY2cq5fmVxXw92fcgTchphGNFxweSiupYH1xYfjBiK6dj5wEEVAQeA4JcGDQGm2xcuz2UsMnDkzVmi2ESZ3xey63mQMY4x2w2 +external addresses m/84'/0'/2'/0 xpub6DqkbMG3tj2oixGYniEQTFamLCHTZx9CeAbUdBttiGuYwgfGZbrLMor8LWeJBUqTpsa81JcJqAUXuDxhXdLpKDxJAEqKMqPgJyXstj5dp3o +m/84'/0'/2'/0/0 bc1qwtdgg928wma8jz32upkje7e4cegtef7yrv233l 0.00000000 new +m/84'/0'/2'/0/1 bc1qhkuk2gny4gumrxcaw999uq3jm3fjrjvcwz7lz3 0.00000000 new +m/84'/0'/2'/0/2 bc1qvu753lkltc8akfasclnq89tdv8yylu2alyg76y 0.00000000 new +m/84'/0'/2'/0/3 bc1qal3r040k26cw2f08337huzcf00hrnws5rhfrz3 0.00000000 new +m/84'/0'/2'/0/4 bc1qpv4nm7wwtxesgwsr0g0slxls33u0w02gqx2euk 0.00000000 new +m/84'/0'/2'/0/5 bc1qk3ekjzlvw3uythw738z7nvwe2sg93w2rtuy6ml 0.00000000 new +Balance: 0.00000000 +internal addresses m/84'/0'/2'/1 Balance: 0.00000000 Balance for mixdepth 2: 0.00000000 -mixdepth 3 tpubDC4qk8DsyiFYgXoNb3UmiaG2veSTdrLCxEBUsTEQnDgQLCaC8Yg1z1Bcwn4ZQivNhgBxHEWH7j8hbRx7rab2kYLy4r4PXxor4Ho3A5AJvVH -external addresses m/49'/1'/3'/0 tpubDEnwog2atqaSLe9xVTTdqxY5ynysqeQPsXuaKrZ6HaYCJcFPY7LmhepmwFTJYTkqf1w5jgLQmoZvREyk9qiq4P2A2fSGTyj62WUE4VjXK76 -m/49'/1'/3'/0/0 2MtckYQLD6bJZiPXffW7m5rryMao7u4ktng 0.00000000 new -m/49'/1'/3'/0/1 2N9KrH5Bi35ZH3DVUwD67eh8MtQD1LLhgCa 0.00000000 new -m/49'/1'/3'/0/2 2N6zLabMeXTXfyzQttr6PBV2JdbZPrVd9i8 0.00000000 new -m/49'/1'/3'/0/3 2MzYTWMjXcv4NBce5PKcDAceT7gMuYrGovc 0.00000000 new -m/49'/1'/3'/0/4 2N6tYykyCZLtvP1RJZvgZ9a7c6xbQaqpE66 0.00000000 new -m/49'/1'/3'/0/5 2N2o3eJ9h2BC5q3TzPVqhn9gmgWBjaL67Hu 0.00000000 new +mixdepth 3 xpub6CMAJ67vZWVY3uty61M6jeGheGU5ni5mQmqMW2QLQbEa8ZQXuBw1K2umKFZsmU8EMEafJZKQkGS1trtWE5dtz4XmDbvLvUccAPn26ZC5i2o +external addresses m/84'/0'/3'/0 xpub6EvT4QFPRdkt2sji3QdLLZjkJQmk7G2y3umT99ceomKTXGYvZ5S9TLaGos6cEugXEuxS6s9kvSUj1Xvpiu65dn5yzK7CgdZLzXawpKC9Mpe +m/84'/0'/3'/0/0 bc1q9ph5l2gknjezcmzv84rnhu4df566jgputzef7l 0.00000000 new +m/84'/0'/3'/0/1 bc1qrlvmmxfuryr3mfhssjv45h0fl6s43g3vzrkwca 0.00000000 new +m/84'/0'/3'/0/2 bc1q40xkajgv9q42ve92zstwjc9v4jgauxme9su6uc 0.00000000 new +m/84'/0'/3'/0/3 bc1q38pfk8yfnu97v4mckkuk2dhk9u8geuyzu9c0hc 0.00000000 new +m/84'/0'/3'/0/4 bc1q2qzxyw56em9qdxc5z5s5xjz3j6s2qlzn3juvtu 0.00000000 new +m/84'/0'/3'/0/5 bc1qd2f8f3dau5pfjqu7dpuvt6fahj36w4rgl3xevr 0.00000000 new Balance: 0.00000000 -internal addresses m/49'/1'/3'/1 +internal addresses m/84'/0'/3'/1 Balance: 0.00000000 Balance for mixdepth 3: 0.00000000 -mixdepth 4 tpubDC4qk8DsyiFYj7GF4LV97c9f7Yff1mrwnxkpi5twGSLmesmPyM4xXBWsxMw9ZLFycVVC4TeeX1ESjNP4rVVrJEDVCm7C3UMvZH9vs6srsAi -external addresses m/49'/1'/4'/0 tpubDFA3XKgf2ZiusZHr4we5utkrQ9toN5s7QGKndNMrdQFQfjQU6yiiMT65tmXFCPduSc7muLFegAi36pv4LCdRnhpRYp2QUpm1izyrboWSjzV -m/49'/1'/4'/0/0 2N18HGRJyaaUwLQFTyfjqZoXGCV8Yv5rbwD 0.00000000 new -m/49'/1'/4'/0/1 2Mt94Y4iguYLkBAhsXT5a1P8VMTQ4kxdZJD 0.00000000 new -m/49'/1'/4'/0/2 2N5vYyG1gx8ht2JCKkuVqow3Qn51cHrwaxh 0.00000000 new -m/49'/1'/4'/0/3 2N44ZtKYu21p1qN4DBoTCYZL2sms6YBeWVW 0.00000000 new -m/49'/1'/4'/0/4 2NF9p2b2PfHvXiPmPP5JPq4CRxKn47LPZrW 0.00000000 new -m/49'/1'/4'/0/5 2MwgfAvbPc4ASD84WdBYo2FM5bXBVG1rRG9 0.00000000 new +mixdepth 4 xpub6CMAJ67vZWVY7gT4oJQBMc1fhbausT57yNVLCLCMwaGed5spHKaQY1EMQxvL2vTgDfhEimuAy7bzBE1qx5uY6D7cpUjQtXPHpyJzFuUtQPN +external addresses m/84'/0'/4'/0 xpub6EQWpKsBTG3N9TFU4v6WtCcBJuLAeTZTcUwVJTxYUAsHeVPFdey4qT1dg4G7MqvnFFgHZDxqTo37S81UWUA2BqKKoTff1pcHTcSFzxyp5JG +m/84'/0'/4'/0/0 bc1qdpjh3ewm367jm5eazqdf8wfrm09py50wn47urs 0.00000000 new +m/84'/0'/4'/0/1 bc1q2x0fmtms5nr3wz3x3660c8wampg7t22e6m30t8 0.00000000 new +m/84'/0'/4'/0/2 bc1q23595yg3dkj8gd3jrgup0hyzslhzf9skrg50r5 0.00000000 new +m/84'/0'/4'/0/3 bc1qw48asjpkwm3k2w8cketqhrre0uwq9f7ypwzmxl 0.00000000 new +m/84'/0'/4'/0/4 bc1qf3wljw44utyv7qd0z57zvdkfl20y470mva0nes 0.00000000 new +m/84'/0'/4'/0/5 bc1qz3f80rtv0ux85d7rc06ldtvmpqyfx6ly48c9pa 0.00000000 new Balance: 0.00000000 -internal addresses m/49'/1'/4'/1 +internal addresses m/84'/0'/4'/1 Balance: 0.00000000 Balance for mixdepth 4: 0.00000000 -Total balance: 8.00000000 +Total balance: 0.00000000 + ``` diff --git a/docs/SEGWIT-UPGRADE.md b/docs/SEGWIT-UPGRADE.md deleted file mode 100644 index 6e8c23e5d..000000000 --- a/docs/SEGWIT-UPGRADE.md +++ /dev/null @@ -1,196 +0,0 @@ -### What if I don't want to migrate to segwit yet? - -If, for whatever reason, you want to keep using the non-segwit version of Joinmarket, -you can do so by adding the following entry in your `joinmarket.cfg` file: - -``` -[POLICY] -segwit = false -``` - -(obviously, add it *into* the `POLICY` section, don't create a duplicate of that -section. Also make sure to enter exactly `false` in lower case, otherwise it -will be ignored. The default is segwit). - -If you do this, the pre-existing Joinmarket wallet should load fine and you should -be able to do coinjoins just as before. - -Note, however, that most are migrating to segwit, and we strongly encourage it -(it's better not to have a split into two groups), not least because segwit -transactions are cheaper. - -The rest of this document will assume you *do* want to migrate. - ---- - -### Migrating to a new segwit based wallet. - -To do joinmarket coinjoins with other participants using segwit, you need to use -a wallet with segwit addresses. These addresses are P2SH (start with '3' on mainnet); -note they are *not* multisig, however, they are yours only (technically we are using -address type P2SH/P2WPKH). - -(Some parts of this are a repeat of what's in the [usage guide](USAGE.md)). - -#### If you don't have an existing Joinmarket wallet: - -You can go into the `scripts/` directory, which contains the `wallet-tool.py` script, -and follow the instructions as in the first half of: - -https://github.com/JoinMarket-Org/joinmarket/wiki/Using-the-JoinMarket-internal-wallet - -You will notice the main difference from what's shown there is that after using -the `generate` command, your new wallet has addresses starting with '3' not '1'. -Also, the BIP32 paths are different, see the bottom of this page for some details on that. - -#### If you do have an existing Joinmarket wallet: - -Migrating coins to this new-style wallet can be done in these steps: - -* Install this new version (0.3.0+), use "Quickstart" from the [readme](../README.md) or see [here](INSTALL.md) - -* Generate a new wallet; go into the `scripts/` directory and do the same as before: - - python wallet-tool.py generate - -Once you've written down the 12 word seed and saved the new .json file, check the -addresses with: - - python wallet-tool.py newwallet.json - -(or whatever you called the file). You should see a set of '3' addresses (or '2' on testnet) instead of '1'. Also the -spacing/layout may look a bit different, but it's the same information. - -Note down at least 3 addresses from mixdepth 0 if you plan to use as a Taker; if as -a Maker (yieldgenerator), best to note down one address from each mixdepth. - -Next, load your old wallet; you have two options - either (1) use your old Joinmarket -installation, or (2) use this one, and go into joinmarket.cfg and add: - - segwit = false - -to the `[POLICY]` section. This will allow you to run `python wallet-tool.py` and it -will find the old wallet. Whether using method (1) or (2), the next step is the same: - -Use `python sendpayment.py -N 0 -m [mixdepth] oldwalletname 0 destaddr` to send coins from -your old wallet, mixdepth 0, into the new one you've created, with a sweep from that -mixdepth. The -N 0 means using direct-send, i.e. not using joinmarket/IRC. -Don't forget to reset `tx_fees` in your joinmarket.cfg -if you want to adjust the projected fee. - -If you've used (2), then delete the line `segwit = false` from joinmarket.cfg, and -reload the new wallet with `python wallet-tool.py newwalletname.json` - -Once you're sure it's working you can repeat this process for any number of mixdepths -for which you want to transfer coins. - -#### Typical wallet output (testnet) for reference - -``` -JM wallet -mixdepth 0 tpubDC3ynQKo14bet1kCgg5ms7d5ABHVkrWLKLqbMQTiDY5T3nd4dUCCLNzJFyj78FvcQbDsJCk96AJfdATnS7Cf1VaM2JsqM73i2VyBeoQVSwa -external addresses m/49'/1'/0'/0 tpubDEdFHGLtyru2nSRdv4F3GXw8MQBx5mVaJQeTP7Am6okGeQLfqt9ysD2npE9eFQXNBfcamxooyJ4nKfb2rQzG97zNsG9tex4YRUKRwRMBvR5 -m/49'/1'/0'/0/000 2NGQED4c5BZL1RNVgzJHM7BAThPyT1GXXdf 2.00000000 used -m/49'/1'/0'/0/001 2N2xYemrtcTHdVxejvAKgYcbx27pJbWcC7D 0.00000000 new -m/49'/1'/0'/0/002 2N6D25kEWEgN78rG8i8soBo2N4kJ3U6jKhK 0.00000000 new -m/49'/1'/0'/0/003 2Mzerss9QDXi3PyyxiTwSN2xV3977EBmRK9 0.00000000 new -m/49'/1'/0'/0/004 2MtFEA9H43ptf2MUcZoA9WFQvg6XF5iVgsk 0.00000000 new -m/49'/1'/0'/0/005 2MzJTsaZFBc3HDktYKvd8rkTGeDMCmaMWPn 0.00000000 new -m/49'/1'/0'/0/006 2Mu2NezBjuzXKaJKrPAh7a9TcWa7REDXNXt 0.00000000 new -Balance: 2.00000000 -internal addresses m/49'/1'/0'/1 -Balance: 0.00000000 -Balance for mixdepth 0: 2.00000000 -mixdepth 1 tpubDC3ynQKo14bevgk8tW8uX6TbdThjGcLkULcZUtMkeH7QVK5wXXF9nLF8dYUrvVkVcbUpsSgCZyQDajHNJzcg8f6FDHSFgS4ask5BgdeTock -external addresses m/49'/1'/1'/0 tpubDFRo71SzBY98U2suh1CVgFV8s7SbdTUYj3xgg4Zk9XrPAFfXg7krxCLsVmq8pYW7bWcnRMPrRP9uVrgLSztKGvHJE2Re64CgNPNddpqzArm -m/49'/1'/1'/0/000 2N42BUaNAGuEsVxXtgR2i2XRWwFyrcku7xJ 2.00000000 used -m/49'/1'/1'/0/001 2N6wivgfa9SNAmLkNLTA5tjLa6RbteiJ9NP 2.00000000 used -m/49'/1'/1'/0/002 2N8fsY6cPigbkyAUPoYuqXrvwSTNbDuSs38 2.00000000 used -m/49'/1'/1'/0/003 2N7p1vw9yB5GRyX2rDMfUGu6szVtNFrsJ4J 0.00000000 new -m/49'/1'/1'/0/004 2N8ezrbwLe4HVRKupxGDxqqah6QWvpafCMr 0.00000000 new -m/49'/1'/1'/0/005 2MshrDThyZD6irvPzqjPm7UENwptL9jwWMu 0.00000000 new -m/49'/1'/1'/0/006 2Mw8zcteMmzdEqtVDjWrJ425wSog3TsSzgt 0.00000000 new -m/49'/1'/1'/0/007 2MukpuXRWAa1oSZnvcodybvnEz9L2EF9bLv 0.00000000 new -m/49'/1'/1'/0/008 2N6Zhsg7E6KjEY3PUaLsZqpzpfLrqiTkUaE 0.00000000 new -Balance: 6.00000000 -internal addresses m/49'/1'/1'/1 -Balance: 0.00000000 -Balance for mixdepth 1: 6.00000000 -mixdepth 2 tpubDC3ynQKo14bf1LnhHPQQ2ReJCZzV24AViKCKGW99SBwFr3DLSio24mwjNcHZwDqdGUejr6K3WEhv7DdASLNrFsZhx8rgbjzTtYGjeuZLPtf -external addresses m/49'/1'/2'/0 tpubDEunZR6GYohjUiAsNecPvm6zA63apCuKe4J8bJz9tjJAuNy485e4LQFAmuF3YVzHFbUvbDpGz1SvGx1tLvoYrExo5Cmhbnw9N4RcJfQVcy8 -m/49'/1'/2'/0/000 2MujbkR3UsUfYf53e4TNErWqpxLEFeX7CjF 0.00000000 new -m/49'/1'/2'/0/001 2N6c1979MmfdZPhGQHpPx4upZoh9A66tpXx 0.00000000 new -m/49'/1'/2'/0/002 2N12iWBShn3NhbqiJVrERJ1ArE2aNbJLiuC 0.00000000 new -m/49'/1'/2'/0/003 2N2WRcxDYkaNHxnv4upAiqCCVRz9YyMqvCA 0.00000000 new -m/49'/1'/2'/0/004 2ND2k7Bsh21dNqC52dK5GtXeXhfQDUfpuc6 0.00000000 new -m/49'/1'/2'/0/005 2N9PvkbGu93vE52v433maZwjkjMQvcXgguc 0.00000000 new -Balance: 0.00000000 -internal addresses m/49'/1'/2'/1 -Balance: 0.00000000 -Balance for mixdepth 2: 0.00000000 -mixdepth 3 tpubDC3ynQKo14bf3T8XrYA9CgKLmpr1wsAZf2VuGhku36bXQMaCRYges3NWd9AW7GoFTiCAwyPWTTz4V99A3VDSKryPgG7US1vaNLB8daxFWXh -external addresses m/49'/1'/3'/0 tpubDEf2oHNW6QCmUF4moLgMpjhgmsnJgDGdfrihxBFGAXncbnvhHBAWD8EFe6hphPpBxtoEaHVaSvqTQW52QJ282vMQxvDNPDzPrnrDu8Hem6p -m/49'/1'/3'/0/000 2N5Ybuqi3a8Wfg8gZfjN5CVNBUYPirsNda6 0.00000000 new -m/49'/1'/3'/0/001 2MxQgX1Ykcj1fJzeVZDJ7xmcdRZjxnB5PHp 0.00000000 new -m/49'/1'/3'/0/002 2N7JwhZEo2CjphzU8FRVJm7CU8UF2FVRYPF 0.00000000 new -m/49'/1'/3'/0/003 2N5MJvtRGTdQSipDsBgDLHQzc3hzwMdk6BK 0.00000000 new -m/49'/1'/3'/0/004 2NGUERANYNAZamXWGMtYxJy49WvQra69FPP 0.00000000 new -m/49'/1'/3'/0/005 2MvxVh7hpxCGfojwmtW32K9QkkahP2bvSyZ 0.00000000 new -Balance: 0.00000000 -internal addresses m/49'/1'/3'/1 -Balance: 0.00000000 -Balance for mixdepth 3: 0.00000000 -mixdepth 4 tpubDC3ynQKo14bf5z1LUdPiEFaJ3omV4y2VasyT5PUL8H9JhShXMvn24zRhRdpMA5oAVwheLmvL2J6r2NFoUdoSVhZjCWd1aUxsUfa332cnEpk -external addresses m/49'/1'/4'/0 tpubDEW6kkqjuEdqiNCpPixL2ToQWsRNojGHzWBfXpUDp9cyix8en1HY2ZwndU3KCNBbdpac5GiUwkqR2jiZjtRedAAbnNHWL86cEDMsMoFXiTH -m/49'/1'/4'/0/000 2NEraA2d5cV83qQkg7oebhx2ugGT4eF9QY5 0.00000000 new -m/49'/1'/4'/0/001 2N5RKKMTFXA8Punt6H9JrPQFnNJFz3DSe9W 0.00000000 new -m/49'/1'/4'/0/002 2NBzFW263ohqGyK53HuFcB7kSLkvBSV38gc 0.00000000 new -m/49'/1'/4'/0/003 2NCLjgm6Dgc3q4QSNiRUMKbt2aQSidGNfrX 0.00000000 new -m/49'/1'/4'/0/004 2MvQkFkhZeymz8r1XtSUT3fz3hdaZp2dxt5 0.00000000 new -m/49'/1'/4'/0/005 2N9zM5Jv4tjwJ2ezKMQi4ECjvheWugWajhj 0.00000000 new -Balance: 0.00000000 -internal addresses m/49'/1'/4'/1 -Balance: 0.00000000 -Balance for mixdepth 4: 0.00000000 -Total balance: 8.00000000 -``` - -### Information about the new wallet type and compatibility. - -First, the seed phrase is now based on BIP39, but this is transparent: it's still -a 12 word seed, but based on a new dictionary. -It uses the [mnemonic](https://github.com/trezor/python-mnemonic) package/implementation. - -Second, this new wallet type is not compatible with the old; that doesn't make sense - even if it generated the same private keys, -you would still have to transfer to new segwit style outputs. - -Third, the new type is based on [BIP49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki), -which is currently supported by TREZOR, Ledger, Electrum (via custom HD path option, only one account at a time), Samourai, Eclair to my knowledge, and probably others. -So these Joinmarket wallets could be loaded into those wallets, but be wary of privacy issues if querying your addresses over some external server. - -BIP49 is a modification of BIP44 for segwit, but really it's just a new BIP32 HD path. -(To get even further into the technical weeds, these standards (44/49) use hardened derivation except for keys below the account level). - - -### Fees - -Segwit doesn't have much direct technical import for Joinmarket, since coinjoin -(at least when properly implemented) isn't subject to any dangers from transaction -malleability. - -However, there can be significant, if not huge, fee savings based on the change from 'size' -to 'weight' in block limits. Instead of paying (fee/kB) * kB of tx size, you pay -for (non-witness + 0.25 * witness)/4 * (fee/kB) (very rough explanation), which rough -calculations suggests might give ~ 25-30% savings over a non-segwit tx in a typical scenario. -Somewhat higher fee savings may be achieved if we switched to a "native" segwit address in future, -rather than a P2SH-wrapped one. - -#### Note for Makers - -Since segwit-style joinmarket must be done "all-in-one" for privacy (otherwise a Taker -risks creating a single '1' address output), the yieldgenerator in this implementation -makes `swreloffer` and `swabsoffer` offer types, and won't also offer original-type offers. -However, the parameters of the offer are the same, and are still set in the (now much -simplified) `yield-generator-basic.py` or `yg-privacyenhanced.py` scripts. - -Other variants may be added later subject to anyone getting around to doing it. \ No newline at end of file diff --git a/docs/USAGE.md b/docs/USAGE.md index 02c9837e1..0dc688b81 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -174,72 +174,75 @@ Notice that here you **don't** need to rescan, because you are not "recovering a ``` JM wallet -mixdepth 0 xpub6Crt4fcfpnrNxW45MzuV626z4fjddsuFGwRn1DXdpwnDkBMR12GKdBzW8euDqLSqRRv2eZmcJy8FSQLWEosC6wFZAZEv3FJMtvZ7W1CkQDi -external addresses m/49'/0'/0'/0 xpub6FQFAscJgwd8MXCcAT8A1hgx9vigrgVoXVNTAKHj2aK3NR2Zf1CbFNXD8G8X9dspGXLY9eiEzBWaypr24owJ8r1aTKgMbUZoTnQ36bBwQB3 -m/49'/0'/0'/0/0 35SrGbUt9FpfA6xqKMpNaiTNyeuXagBi7Y 0.00000000 new -m/49'/0'/0'/0/1 39hc2xfA6i9kWZdXMwH4Pd9dWUvDKocGd3 0.00000000 new -m/49'/0'/0'/0/2 371MJcjFG4cEpz8RVdYb1L8PkA9tZYySGZ 0.00000000 new -m/49'/0'/0'/0/3 39eTy635wLCyBbphUTNnSB2V9LnvgdndNo 0.00000000 new -m/49'/0'/0'/0/4 33T8eNr54maWNZYQjoZwpLA2HGk7RJaLVb 0.00000000 new -m/49'/0'/0'/0/5 35kJoTSxHtQbKUg2jvjDVqcY9iXoH2cTqD 0.00000000 new +mixdepth 0 xpub6CMAJ67vZWVXuzjzYXUoJgWrmuvFRiqiUG4dwoXNFmJtpTH3WgviANNxGyZYo27zxbMuqhDDym6fnBxmGaYoxr6LHgNDo1eEghkXHTX4Jnx +external addresses m/84'/0'/0'/0 xpub6FFUn4AxdqFbnTH2fyPrkLreEkStNnMFb6R1PyAykZ4fzN3LzvkRB4VF8zWrks4WhJroMYeFbCcfeooEVM6n2YRy1EAYUvUxZe6JET6XYaW +m/84'/0'/0'/0/0 bc1qt493axn3wl4gzjxvfg03vkacre0m6f2gzfhv5t 0.00000000 new +m/84'/0'/0'/0/1 bc1q2av9emer8k2j567yzv6ey6muqkuew4nh4rl85q 0.00000000 new +m/84'/0'/0'/0/2 bc1qggpg0q7cn4mpe98t29wte2rfn2rzjtn3zdmqye 0.00000000 new +m/84'/0'/0'/0/3 bc1qnnkqz8vcdjan7ztcpr68tyec7dw2yk8gjnr9ze 0.00000000 new +m/84'/0'/0'/0/4 bc1qud5s2ln88ktg83hkr6gv9s576zvt249qn2lepx 0.00000000 new +m/84'/0'/0'/0/5 bc1qw0lhq7xlhj7ww2jdaknv23vcyhnz6qxg23uthy 0.00000000 new Balance: 0.00000000 -internal addresses m/49'/0'/0'/1 +internal addresses m/84'/0'/0'/1 Balance: 0.00000000 Balance for mixdepth 0: 0.00000000 -mixdepth 1 xpub6Crt4fcfpnrP2GZnVuT1qi9aw9cpCuXcnGEjAKhaoWAHq5M4pWX64DNio3XHirY5uTCZCi6vTmaLjU5YQXbVsTjyEdCE2zn3S2fzBNFjxs8 -external addresses m/49'/0'/1'/0 xpub6E2EnYy6yBXvE9U1nR5sSH58YiwbsKFZzaMkgMY5jrt2XFe4D5HVwikeTWyjuoczjQhJNezkwxrKAbUPMEDYHmbiaaiEAeXcL1yAcEAqtd7 -m/49'/0'/1'/0/0 3DdhEr9GCoMDVRLNGAwi9rb8F4HQX8newY 0.00000000 new -m/49'/0'/1'/0/1 342XPkCQYzZkdUaB9TGPfVhf1iX55yE4gH 0.00000000 new -m/49'/0'/1'/0/2 33RaQJTn1P8KNUvNnPRFM19zHjPhzuyCqc 0.00000000 new -m/49'/0'/1'/0/3 3LydaxypMyYrbDFFp61f9rnZRcAJnZ5sv5 0.00000000 new -m/49'/0'/1'/0/4 36u2ykPy6Y9tg811B8XjYoPjpTtEg98RPd 0.00000000 new -m/49'/0'/1'/0/5 3AfSFczJEUN5RRbXirf8Pc74ve3ZaBVF8r 0.00000000 new +mixdepth 1 xpub6CMAJ67vZWVXyTJEaZndxZy9ACUufsmNuJwp9k5dHHKa22zQdsgALxXvMRFSwtvB8BRJzsd8h17pKqoAyHtkBrAoSqC9AUcXB1cPrSYATsZ +external addresses m/84'/0'/1'/0 xpub6FNSLcHuGnoUbaiKgwXuKpfcbR63ybrjaqHCudrma13NDqMfKgBtZRiPZaHjSbCY3P3cgEEcdzZCwrLKXeT5jeuk8erdSmBuRgJJzfNnVjj +m/84'/0'/1'/0/0 bc1qhrvm7kd9hxv3vxs8mw2arcrsl9w37a7d6ccwe4 0.00000000 new +m/84'/0'/1'/0/1 bc1q0sccdfrsnjtgfytul5zulst46wxgahtcf44tcw 0.00000000 new +m/84'/0'/1'/0/2 bc1qst6p8hr8yg280zcpvvkxahv42ecvdzq63t75su 0.00000000 new +m/84'/0'/1'/0/3 bc1q0gkarwg8y3nc2mcusuaw9zsn3gvzwe8mp3ac9h 0.00000000 new +m/84'/0'/1'/0/4 bc1qkf5wlcla2qlg5g5sym9gk6q4l4k5c98vvyj33u 0.00000000 new +m/84'/0'/1'/0/5 bc1qz6zptlh3cqty2tqyspjk6ksqelnvrrrvmyqa5v 0.00000000 new Balance: 0.00000000 -internal addresses m/49'/0'/1'/1 +internal addresses m/84'/0'/1'/1 Balance: 0.00000000 Balance for mixdepth 1: 0.00000000 -mixdepth 2 xpub6Crt4fcfpnrP2Tjc95HKmSgaCpi9P9EM54B12XAUfdnyAyoftFpNyikSK4pBtsvqZAnj8grDFf61xDmAHYimQXvaQkoTY3h9G5BAxHuYgvn -external addresses m/49'/0'/2'/0 xpub6DrRZxJu5zEgxVMcrXmKGGeEQMgfh1MeZXvDXVYndqsqoFaJQUA94GbD1JX2p7Yw5NLcCUgg3WQXtXk5eP4vnNjGkDwA3FJoFFkE4PytauC -m/49'/0'/2'/0/0 39uPqzuW6CiyRSUvdrBYfaqSD2AtH2k4wf 0.00000000 new -m/49'/0'/2'/0/1 3FVYzJWE6g6kGj3hF7B5e7QpDQafBcUdnx 0.00000000 new -m/49'/0'/2'/0/2 3HjYatHB5tZFGcC2SUCBqT1zCM7AwgGE5r 0.00000000 new -m/49'/0'/2'/0/3 3CDco5iVa2iyEHGjXcAojsod6QDxeNedFg 0.00000000 new -m/49'/0'/2'/0/4 3LKaYFENU16ix8FngQk6m9VmQFGaATEeCu 0.00000000 new -m/49'/0'/2'/0/5 3B3TtgU6VgLF6BzQeG5znKZHpr3zgoftZC 0.00000000 new +mixdepth 2 xpub6CMAJ67vZWVY2cq5fmVxXw92fcgTchphGNFxweSiupYH1xYfjBiK6dj5wEEVAQeA4JcGDQGm2xcuz2UsMnDkzVmi2ESZ3xey63mQMY4x2w2 +external addresses m/84'/0'/2'/0 xpub6DqkbMG3tj2oixGYniEQTFamLCHTZx9CeAbUdBttiGuYwgfGZbrLMor8LWeJBUqTpsa81JcJqAUXuDxhXdLpKDxJAEqKMqPgJyXstj5dp3o +m/84'/0'/2'/0/0 bc1qwtdgg928wma8jz32upkje7e4cegtef7yrv233l 0.00000000 new +m/84'/0'/2'/0/1 bc1qhkuk2gny4gumrxcaw999uq3jm3fjrjvcwz7lz3 0.00000000 new +m/84'/0'/2'/0/2 bc1qvu753lkltc8akfasclnq89tdv8yylu2alyg76y 0.00000000 new +m/84'/0'/2'/0/3 bc1qal3r040k26cw2f08337huzcf00hrnws5rhfrz3 0.00000000 new +m/84'/0'/2'/0/4 bc1qpv4nm7wwtxesgwsr0g0slxls33u0w02gqx2euk 0.00000000 new +m/84'/0'/2'/0/5 bc1qk3ekjzlvw3uythw738z7nvwe2sg93w2rtuy6ml 0.00000000 new Balance: 0.00000000 -internal addresses m/49'/0'/2'/1 +internal addresses m/84'/0'/2'/1 Balance: 0.00000000 Balance for mixdepth 2: 0.00000000 -mixdepth 3 xpub6Crt4fcfpnrP5B7ysPePnY98sKaLAdu9yCbHpkodb6evSKhr4BWvpB7nQquPdzncTuhMxmUhEcVNuYpXQf9i6VN9DFYu3PgPMckuu4P7EeQ -external addresses m/49'/0'/3'/0 xpub6En2j7xGy2ihoYhpmTyDL1eER5B6rdas1HotoBiLbSXuXBMwZnjJTRefyJKVLUTfwDMgyATqZAwbZdKb8gQ8Fbno4XhUMPe6XBuN4LSsXN2 -m/49'/0'/3'/0/0 3LhThkjSvYmmXLNLMcXghbvaoGgDitwfmi 0.00000000 new -m/49'/0'/3'/0/1 3LTwvukpZqsf9ghqnNQVu8szgScjoVnLdh 0.00000000 new -m/49'/0'/3'/0/2 35FRiSaZ6Yotr3YB3yX9JgqAbsxCnuTBfm 0.00000000 new -m/49'/0'/3'/0/3 3H7S5ZjYaWgSTXA1RFwGNytS2zsK1PfXoN 0.00000000 new -m/49'/0'/3'/0/4 33b8j2nPCFCWXb7wDHPRggUFoPJMwGQYYt 0.00000000 new -m/49'/0'/3'/0/5 3PE7fen989oPZn7XaRSAu3fvGN1P57SB9W 0.00000000 new +mixdepth 3 xpub6CMAJ67vZWVY3uty61M6jeGheGU5ni5mQmqMW2QLQbEa8ZQXuBw1K2umKFZsmU8EMEafJZKQkGS1trtWE5dtz4XmDbvLvUccAPn26ZC5i2o +external addresses m/84'/0'/3'/0 xpub6EvT4QFPRdkt2sji3QdLLZjkJQmk7G2y3umT99ceomKTXGYvZ5S9TLaGos6cEugXEuxS6s9kvSUj1Xvpiu65dn5yzK7CgdZLzXawpKC9Mpe +m/84'/0'/3'/0/0 bc1q9ph5l2gknjezcmzv84rnhu4df566jgputzef7l 0.00000000 new +m/84'/0'/3'/0/1 bc1qrlvmmxfuryr3mfhssjv45h0fl6s43g3vzrkwca 0.00000000 new +m/84'/0'/3'/0/2 bc1q40xkajgv9q42ve92zstwjc9v4jgauxme9su6uc 0.00000000 new +m/84'/0'/3'/0/3 bc1q38pfk8yfnu97v4mckkuk2dhk9u8geuyzu9c0hc 0.00000000 new +m/84'/0'/3'/0/4 bc1q2qzxyw56em9qdxc5z5s5xjz3j6s2qlzn3juvtu 0.00000000 new +m/84'/0'/3'/0/5 bc1qd2f8f3dau5pfjqu7dpuvt6fahj36w4rgl3xevr 0.00000000 new Balance: 0.00000000 -internal addresses m/49'/0'/3'/1 +internal addresses m/84'/0'/3'/1 Balance: 0.00000000 Balance for mixdepth 3: 0.00000000 -mixdepth 4 xpub6Crt4fcfpnrP9pZBowaYjC7595HXETHFw2YnqtukLnqpfb4PWbhWwt5jPdskLo8ZZbHBnccexTJUArt4V8C7592K3LvsrYkrvsQMJFiPx8z -external addresses m/49'/0'/4'/0 xpub6E37s5W8C63FxsgcpMbH43ssUi8CQzfo3PrfSWfn9jKTYZgR4QZBCymJ8TPw3Vx5zoQ7aSgkqrEKr1NEerZuN8okV7w1JhNg2hoYEWohtT4 -m/49'/0'/4'/0/0 39n1tYKnPQ47bgCmsPWDHxuk2Mxo6GE4LZ 0.00000000 new -m/49'/0'/4'/0/1 3BoLqHDSHdMHyrSEAP31bBzzw45bFwZhy7 0.00000000 new -m/49'/0'/4'/0/2 31mo7D9UDmoStafYcyHpwV9sWY7oP9jUVQ 0.00000000 new -m/49'/0'/4'/0/3 3JMVGEyZ5nyJR9NVsfNY93c34xx9rEtzmq 0.00000000 new -m/49'/0'/4'/0/4 3AdD86dw59Q5EGHVupkHzR8pM4sW45bKqX 0.00000000 new -m/49'/0'/4'/0/5 3Gu7jTxcmh5dHJXgCE7Z5fdMgUWuenE2GE 0.00000000 new +mixdepth 4 xpub6CMAJ67vZWVY7gT4oJQBMc1fhbausT57yNVLCLCMwaGed5spHKaQY1EMQxvL2vTgDfhEimuAy7bzBE1qx5uY6D7cpUjQtXPHpyJzFuUtQPN +external addresses m/84'/0'/4'/0 xpub6EQWpKsBTG3N9TFU4v6WtCcBJuLAeTZTcUwVJTxYUAsHeVPFdey4qT1dg4G7MqvnFFgHZDxqTo37S81UWUA2BqKKoTff1pcHTcSFzxyp5JG +m/84'/0'/4'/0/0 bc1qdpjh3ewm367jm5eazqdf8wfrm09py50wn47urs 0.00000000 new +m/84'/0'/4'/0/1 bc1q2x0fmtms5nr3wz3x3660c8wampg7t22e6m30t8 0.00000000 new +m/84'/0'/4'/0/2 bc1q23595yg3dkj8gd3jrgup0hyzslhzf9skrg50r5 0.00000000 new +m/84'/0'/4'/0/3 bc1qw48asjpkwm3k2w8cketqhrre0uwq9f7ypwzmxl 0.00000000 new +m/84'/0'/4'/0/4 bc1qf3wljw44utyv7qd0z57zvdkfl20y470mva0nes 0.00000000 new +m/84'/0'/4'/0/5 bc1qz3f80rtv0ux85d7rc06ldtvmpqyfx6ly48c9pa 0.00000000 new Balance: 0.00000000 -internal addresses m/49'/0'/4'/1 +internal addresses m/84'/0'/4'/1 Balance: 0.00000000 Balance for mixdepth 4: 0.00000000 Total balance: 0.00000000 + ``` The [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) xpub keys of each external branch are shown in case that is needed. -The BIP32 derivation paths are also shown; for Joinmarket they are defined by [BIP49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki); for more on this see [below](#structure). +The BIP32 derivation paths are also shown; for Joinmarket they are defined by [BIP84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki); for more on this see [below](#structure). + +If you set `native = false` in the `[POLICY]` section of your `~/.joinmarket/joinmarket.cfg` file, you will create a wallet with '3' addresses, which is p2sh wrapped segwit, instead of 'bc1' addresses; this uses BIP49 instead of BIP84, but is recoverable similarly (it is slightly more expensive in terms of tx fees; this was the default for Joinmarket pre-0.8.0). **Bitcoins should be sent to empty external addresses** (sometimes known as receive addresses). You'll notice in the above that while there are fields also for *internal addresses*, they are empty. That's because zero-balance internal addresses (also known as change addresses) are hidden by default. Using 'displayall' as the second argument will show all addresses in wallet, including empty used ones. @@ -292,34 +295,34 @@ The `showseed` command will display the mnemonic for an existing wallet file, in ### Recovering Private Keys -It is possible to recover a Joinmarket wallet in Trezor, Electrum or a number of other wallets, because it uses BIP49. But note that this is not ideal for privacy reasons (if you contact a server with your addresses), and also that because Joinmarket uses multiple accounts (=mixdepths), it may be more difficult than expected. +It is possible to recover a Joinmarket wallet in Trezor, Electrum or a number of other wallets, because it uses BIP84. But note that this is not ideal for privacy reasons (if you contact a server with your addresses), and also that because Joinmarket uses multiple accounts (=mixdepths), it may be more difficult than expected. In difficult recovery situations, the `-p` command line flag can be used to print out private keys. Be very careful and consider this advanced usage: ``` ... JM wallet - mixdepth 0 xpub6Crt4fcfpnrNxW45MzuV626z4fjddsuFGwRn1DXdpwnDkBMR12GKdBzW8euDqLSqRRv2eZmcJy8FSQLWEosC6wFZAZEv3FJMtvZ7W1CkQDi - external addresses m/49'/0'/0'/0 xpub6FQFAscJgwd8MXCcAT8A1hgx9vigrgVoXVNTAKHj2aK3NR2Zf1CbFNXD8G8X9dspGXLY9eiEzBWaypr24owJ8r1aTKgMbUZoTnQ36bBwQB3 - m/49'/0'/0'/0/0 35SrGbUt9FpfA6xqKMpNaiTNyeuXagBi7Y 0.00000000 new L4TDPLYgd77GcHWdLTCEhqqd3GM15Wp7PX7dw1YeyCeRb326hM8K - m/49'/0'/0'/0/1 39hc2xfA6i9kWZdXMwH4Pd9dWUvDKocGd3 0.00000000 new L26Y8v6fU3Nx8yaRhnkKAq3wi4TkaR5BgEXuDQNmR2nv2v7kVCd9 - m/49'/0'/0'/0/2 371MJcjFG4cEpz8RVdYb1L8PkA9tZYySGZ 0.00000000 new KyLza1kdozCyGwt6rkwkhshaBHS8hhciB6mnneczrxtFYXTNeyYV - m/49'/0'/0'/0/3 39eTy635wLCyBbphUTNnSB2V9LnvgdndNo 0.00000000 new KyKugtGLD1RpA8ZURCPqJ74kWyh26JBnfPG1TtjzmBoS5E3wzWJK - m/49'/0'/0'/0/4 33T8eNr54maWNZYQjoZwpLA2HN4UJB2Gk7RJaLVb 0.00000000 new L3yjfeVyBCwTgUXpdPNKPK7RBQ6Xvc17LThvF3zKM2pQkWpEEcbo - m/49'/0'/0'/0/5 35kJoTSxHtQbKUg2jvjDVqcY9iXoH2cTqD 0.00000000 new L1QXi1KjVyTFQZGWeybjnnSN3sieYGeTQtTiYrwLJmJvhQ7EBzVL - Balance: 0.00000000 - internal addresses m/49'/0'/0'/1 - Balance: 0.00000000 - Balance for mixdepth 0: 0.00000000 - mixdepth 1 xpub6Crt4fcfpnrP2GZnVuT1qi9aw9cpCuXcnGEjAKhaoWAHq5M4pWX64DNio3XHirY5uTCZCi6vTmaLjU5YQXbVsTjyEdCE2zn3S2fzBNFjxs8 - external addresses m/49'/0'/1'/0 xpub6E2EnYy6yBXvE9U1nR5sSH58YiwbsKFZzaMkgMY5jrt2XFe4D5HVwikeTWyjuoczjQhJNezkwxrKAbUPMEDYHmbiaaiEAeXcL1yAcEAqtd7 - m/49'/0'/1'/0/0 3DdhEr9GCoMDVRLNGAwi9rb8F4HQX8newY 0.00000000 new KzNosXo1sDfSJr7JcyqwYqGzTKTgAeQ9iEfiWA5YVE3Y6Tn3iCJs +mixdepth 0 xpub6CMAJ67vZWVXuzjzYXUoJgWrmuvFRiqiUG4dwoXNFmJtpTH3WgviANNxGyZYo27zxbMuqhDDym6fnBxmGaYoxr6LHgNDo1eEghkXHTX4Jnx +external addresses m/84'/0'/0'/0 xpub6FFUn4AxdqFbnTH2fyPrkLreEkStNnMFb6R1PyAykZ4fzN3LzvkRB4VF8zWrks4WhJroMYeFbCcfeooEVM6n2YRy1EAYUvUxZe6JET6XYaW +m/84'/0'/0'/0/0 bc1qt493axn3wl4gzjxvfg03vkacre0m6f2gzfhv5t 0.00000000 new Kyx53Zaq35EEPgCkA8bCf2GkmtMjSt261LznWJACb9NzwL8gE9zF +m/84'/0'/0'/0/1 bc1q2av9emer8k2j567yzv6ey6muqkuew4nh4rl85q 0.00000000 new KwY2ZANdevBVhdV1KxuadFe9tWoHvZGB2o1qLzgWB9uDgaZQhfPj +m/84'/0'/0'/0/2 bc1qggpg0q7cn4mpe98t29wte2rfn2rzjtn3zdmqye 0.00000000 new L5R9TD3c9NyV2Skjxhc58Gem3fhorfRrSTmMxd1JxEByFZifiuKX +m/84'/0'/0'/0/3 bc1qnnkqz8vcdjan7ztcpr68tyec7dw2yk8gjnr9ze 0.00000000 new Kxmj5YQ6V4j4jMjr3uK8kHnaDLSCuLao8Yyvn2e5pS4SR4ueCEJ6 +m/84'/0'/0'/0/4 bc1qud5s2ln88ktg83hkr6gv9s576zvt249qn2lepx 0.00000000 new L2MZPx36cVTQCntDzwJF3AAYJroHEySCfBTG3o2bMCH1aDPjZS3y +m/84'/0'/0'/0/5 bc1qw0lhq7xlhj7ww2jdaknv23vcyhnz6qxg23uthy 0.00000000 new L3zrKnqxYDRDHLS3ey4a3BYkMtYPKj2eNAruiJ8SSRDA9tqceHSZ +Balance: 0.00000000 +internal addresses m/84'/0'/0'/1 +Balance: 0.00000000 +Balance for mixdepth 0: 0.00000000 +mixdepth 1 xpub6CMAJ67vZWVXyTJEaZndxZy9ACUufsmNuJwp9k5dHHKa22zQdsgALxXvMRFSwtvB8BRJzsd8h17pKqoAyHtkBrAoSqC9AUcXB1cPrSYATsZ +external addresses m/84'/0'/1'/0 xpub6FNSLcHuGnoUbaiKgwXuKpfcbR63ybrjaqHCudrma13NDqMfKgBtZRiPZaHjSbCY3P3cgEEcdzZCwrLKXeT5jeuk8erdSmBuRgJJzfNnVjj +m/84'/0'/1'/0/0 bc1qhrvm7kd9hxv3vxs8mw2arcrsl9w37a7d6ccwe4 0.00000000 new KxpBewNsVCSBktvFUPhZLEaXB4pcMwpzWdaEZ1BYRtVK9waeNLbU .... etc ``` The above method (`-p`) still requires synchronizing the JoinMarket wallet. In the case where this isn't possible, individual private keys can still be exported: - (jmvenv)$python wallet-tool.py -H "m/49'/0'/4'/0/0" wallet.jmdat dumpprivkey + (jmvenv)$python wallet-tool.py -H "m/84'/0'/4'/0/0" wallet.jmdat dumpprivkey Enter wallet decryption passphrase: L1YPrEGNMwwfnvzBfAiPiPC4zb5s6Urpqnk88zNHgsYLHrq2Umss @@ -381,11 +384,11 @@ An example of the different identities being used is to not leak a lower limit o #### BIP32 Structure m - generated from seed -m/49' - purpose: this is specified by [BIP49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki) for P2SH-wrapped segwit P2WPKH addresses -m/49'/0' - coin type 0 : see [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) which specifies this as the coin type for Bitcoin mainnet -m/49'/0'/n' - nth mixing depth (nth account) (as per BIP44) -m/49'/0'/n'/0/k - kth external address, for mixing depth n -m/49'/0'/n'/1/k - kth internal address, for mixing depth n +m/84' - purpose: this is specified by [BIP84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) for p2wpkh native segwit addresses +m/84'/0' - coin type 0 : see [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) which specifies this as the coin type for Bitcoin mainnet +m/84'/0'/n' - nth mixing depth (nth account) (as per BIP44) +m/84'/0'/n'/0/k - kth external address, for mixing depth n +m/84'/0'/n'/1/k - kth internal address, for mixing depth n Note that the quote (') indicates hardened derivation. See [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) for technical details. diff --git a/docs/YIELDGENERATOR.md b/docs/YIELDGENERATOR.md index f4981af5d..f0127fd79 100644 --- a/docs/YIELDGENERATOR.md +++ b/docs/YIELDGENERATOR.md @@ -24,14 +24,14 @@ Open one of the two built-in yield generators: `yield-generator-basic.py` or `yg txfee = 1000 cjfee_a = 2000 cjfee_r = '0.0002' # 0.02% fee - ordertype = 'swreloffer' + ordertype = 'reloffer' minsize = int(1.2 * txfee / float(cjfee)) #minimum size is such that you always net profit at least 20% of the miner fee Meaning of fields: + txfee. The yield generators contribution to the miner fee. -+ ordertype. One of 'swreloffer' or 'swabsoffer'. -+ cjfee_r. Your offered coinjoin fee as a fraction of the coinjoin amount (for 'swreloffer'). Takers pay this to you. -+ cjfee_a. Your offered coinjoin fee as an absolute number of satoshis (for 'swabsoffer') ++ ordertype. One of 'reloffer', 'absoffer' ++ cjfee_r. Your offered coinjoin fee as a fraction of the coinjoin amount (for 'reloffer'). Takers pay this to you. ++ cjfee_a. Your offered coinjoin fee as an absolute number of satoshis (for 'absoffer'). ## Keeping Track of Returns diff --git a/docs/tumblerguide.md b/docs/tumblerguide.md index 705677a2e..41dc4834b 100644 --- a/docs/tumblerguide.md +++ b/docs/tumblerguide.md @@ -204,7 +204,7 @@ This tweaking process is repeated as many times as necessary until the transacti ## How often do retries occur? -This is hardcoded currently to `20 * maker_timeout_sec`, the figure 20 being hardcoded is due to me not wanting yet another config variable, although that could be done of course. This is the rate at which the stall monitor wakes up in the client protocol, the setting is in the code [here](https://github.com/AdamISZ/joinmarket-clientserver/blob/master/jmclient/jmclient/client_protocol.py#L87). Note that by default this is fairly slow, 10 minutes. +This is hardcoded currently to `20 * maker_timeout_sec`, the figure 20 being hardcoded is due to me not wanting yet another config variable, although that could be done of course. This is the rate at which the stall monitor wakes up in the client protocol, the setting is in the code [here](https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/acc00fc6f5a1cd1f21052c5af06cd06e78c6edda/jmclient/jmclient/client_protocol.py#L359-L363). Note that by default this is fairly slow, 10 minutes. @@ -221,7 +221,7 @@ This can of course be implemented in, say, a shell script (just add --restart to ## Possible failure vectors - crash or shutdown * **Failure to source commitment** - if there is no *unused* PoDLE commitment available, the script terminates as even with tweaks this condition will not change. This *could* be changed to allow dynamic update of the `commitments.json` file (adding external utxos), but I didn't judge that to be the right choice for now. On the other hand, as was noted above, if the commitments are simply too young, the script will keep tweaking and retrying. I recommend using the `add-utxo.py` script to prepare external commitments in advance of the run for more robustness, although it shouldn't be necessary for success. -* **Network errors** - this is the biggest unknown for now; since this has not been tested in a sufficiently wide variety of network conditions, it's possible that the IRC reconnection fails in case of drop, or perhaps even crashes. +* **Network errors** - this should not cause a problem. Joinmarket handles network interruptions to its IRC communications quite robustly. * **Insufficient liquidity**. This is a tricky one - particulary for sweeps, if the number of potential counterparties is low, and if some of them are deliberately non-responsive, you may run out of counterparties. Currently the script will simply keep retrying indefinitely. **Use a reasonably high -N value** - I think going much below 5 is starting to introduce risk, so values like `-N 6 1` should be OK, but `-N 3 1` is dubious. Force-quitting after a very long timeout is conceivable, but obviously a slightly tricky/impractical proposition. Note that various other failure vectors will not actually cause a problem, such as the infamous "txn-mempool-conflict"; tweaking handles these cases. diff --git a/docs/wallet-upgrade.md b/docs/wallet-upgrade.md deleted file mode 100644 index 8fe479dc8..000000000 --- a/docs/wallet-upgrade.md +++ /dev/null @@ -1,14 +0,0 @@ -A new wallet format has been introduced. Old wallets require conversion. -In order to convert your existing wallet to the new format you can use the -included conversion tool at `scripts/convert_old_wallet.py`. - -usage: - - python convert_old_wallet.py full/path/to/wallets/wallet.json - -This will place the newly converted `wallet.jmdat` file in the existing -joinmarket `wallets/` directory. The wallet name will be adopted accordingly -if it differs from `wallet`. - -There is no need to move funds to the new wallet. All your funds, addresses, -private keys, history and also your seed will be retained. \ No newline at end of file diff --git a/jmbitcoin/jmbitcoin/secp256k1_transaction.py b/jmbitcoin/jmbitcoin/secp256k1_transaction.py index 91bb4f50b..a59e3d36c 100644 --- a/jmbitcoin/jmbitcoin/secp256k1_transaction.py +++ b/jmbitcoin/jmbitcoin/secp256k1_transaction.py @@ -236,6 +236,7 @@ def return_err(e): # https://github.com/Simplexum/python-bitcointx/blob/648ad8f45ff853bf9923c6498bfa0648b3d7bcbd/bitcointx/core/scripteval.py#L1250-L1252 flags.add(SCRIPT_VERIFY_P2SH) + flags.add(SCRIPT_VERIFY_WITNESS) if native and native != "p2wpkh": scriptCode = native @@ -255,10 +256,9 @@ def return_err(e): sig = ecdsa_raw_sign(sighash, priv, rawmsg=True) + bytes([hashcode]) except Exception as e: return return_err(e) - if native: - flags.add(SCRIPT_VERIFY_WITNESS) - else: + if not native: tx.vin[i].scriptSig = CScript([input_scriptPubKey]) + input_scriptPubKey = pubkey_to_p2sh_p2wpkh_script(pub) if native and native != "p2wpkh": witness = [sig, scriptCode] @@ -320,12 +320,11 @@ def make_shuffled_tx(ins, outs, version=1, locktime=0): random.shuffle(outs) return mktx(ins, outs, version=version, locktime=locktime) -def verify_tx_input(tx, i, scriptSig, scriptPubKey, amount=None, - witness=None, native=False): +def verify_tx_input(tx, i, scriptSig, scriptPubKey, amount=None, witness=None): flags = set([SCRIPT_VERIFY_STRICTENC]) if witness: + # https://github.com/Simplexum/python-bitcointx/blob/648ad8f45ff853bf9923c6498bfa0648b3d7bcbd/bitcointx/core/scripteval.py#L1250-L1252 flags.add(SCRIPT_VERIFY_P2SH) - if native: flags.add(SCRIPT_VERIFY_WITNESS) try: VerifyScript(scriptSig, scriptPubKey, tx, i, diff --git a/jmclient/jmclient/configure.py b/jmclient/jmclient/configure.py index bb1bf96e5..1dc9dee86 100644 --- a/jmclient/jmclient/configure.py +++ b/jmclient/jmclient/configure.py @@ -185,10 +185,11 @@ def jm_single(): # Only set to false for old wallets, Joinmarket is now segwit only. segwit = true -# Use native segwit (bech32) wallet. This is NOT -# currently supported in Joinmarket coinjoins. Only set to "true" -# if specifically advised to do so. -native = false +# Use native segwit (bech32) wallet. If set to false, p2sh-p2wkh +# will be used when generating the addresses for this wallet. +# Notes: 1. The default joinmarket pit is native segwit. +# 2. You cannot change the type of a pre-existing wallet. +native = true # for dust sweeping, try merge_algorithm = gradual # for more rapid dust sweeping, try merge_algorithm = greedy diff --git a/jmclient/jmclient/maker.py b/jmclient/jmclient/maker.py index 631a51166..934f89a0e 100644 --- a/jmclient/jmclient/maker.py +++ b/jmclient/jmclient/maker.py @@ -156,29 +156,19 @@ def on_tx_received(self, nick, tx_from_taker, offerinfo): success, msg = self.wallet_service.sign_tx(tx, our_inputs) assert success, msg for index in our_inputs: - sigmsg = tx.vin[index].scriptSig - if tx.has_witness(): - # Note that this flag only implies that the transaction - # *as a whole* is using segwit serialization; it doesn't - # imply that this specific input is segwit type (to be - # fully general, we allow that even our own wallet's - # inputs might be of mixed type). So, we catch the EngineError - # which is thrown by non-segwit types. This way the sigmsg - # will only contain the scriptCode field if the wallet object - # decides it's necessary/appropriate for this specific input - # If it is segwit, we prepend the witness data since we want - # (sig, pub, witnessprogram=scriptSig - note we could, better, - # pass scriptCode here, but that is not backwards compatible, - # as the taker uses this third field and inserts it into the - # transaction scriptSig), else (non-sw) the !sig message remains - # unchanged as (sig, pub). - try: - sig, pub = [a for a in iter(tx.wit.vtxinwit[index].scriptWitness)] - scriptCode = btc.pubkey_to_p2wpkh_script(pub) - sigmsg = btc.CScript([sig]) + btc.CScript(pub) + scriptCode - except Exception as e: - #the sigmsg was already set before the segwit check - pass + # The second case here is kept for backwards compatibility. + if self.wallet_service.get_txtype() == 'p2pkh': + sigmsg = tx.vin[index].scriptSig + elif self.wallet_service.get_txtype() == 'p2sh-p2wpkh': + sig, pub = [a for a in iter(tx.wit.vtxinwit[index].scriptWitness)] + scriptCode = btc.pubkey_to_p2wpkh_script(pub) + sigmsg = btc.CScript([sig]) + btc.CScript(pub) + scriptCode + elif self.wallet_service.get_txtype() == 'p2wpkh': + sig, pub = [a for a in iter(tx.wit.vtxinwit[index].scriptWitness)] + sigmsg = btc.CScript([sig]) + btc.CScript(pub) + else: + jlog.error("Taker has unknown wallet type") + sys.exit(EXIT_FAILURE) sigs.append(base64.b64encode(sigmsg).decode('ascii')) return (True, sigs) diff --git a/jmclient/jmclient/support.py b/jmclient/jmclient/support.py index f2d8cef14..3f1b971cd 100644 --- a/jmclient/jmclient/support.py +++ b/jmclient/jmclient/support.py @@ -174,9 +174,9 @@ def select_one_utxo(unspent, value): def calc_cj_fee(ordertype, cjfee, cj_amount): - if ordertype in ['swabsoffer', 'absoffer']: + if ordertype in ['sw0absoffer', 'swabsoffer', 'absoffer']: real_cjfee = int(cjfee) - elif ordertype in ['swreloffer', 'reloffer']: + elif ordertype in ['sw0reloffer', 'swreloffer', 'reloffer']: real_cjfee = int((Decimal(cjfee) * Decimal(cj_amount)).quantize(Decimal( 1))) else: @@ -235,7 +235,7 @@ def check_max_fee(fee): def choose_orders(offers, cj_amount, n, chooseOrdersBy, ignored_makers=None, - pick=False, allowed_types=["swreloffer", "swabsoffer"], + pick=False, allowed_types=["sw0reloffer", "sw0absoffer"], max_cj_fee=(1, float('inf'))): is_within_max_limits = _get_is_within_max_limits( max_cj_fee[0], max_cj_fee[1], cj_amount) @@ -301,7 +301,7 @@ def choose_sweep_orders(offers, n, chooseOrdersBy, ignored_makers=None, - allowed_types=['swreloffer', 'swabsoffer'], + allowed_types=['sw0reloffer', 'sw0absoffer'], max_cj_fee=(1, float('inf'))): """ choose an order given that we want to be left with no change @@ -325,9 +325,9 @@ def calc_zero_change_cj_amount(ordercombo): sumtxfee_contribution = 0 for order in ordercombo: sumtxfee_contribution += order['txfee'] - if order['ordertype'] in ['swabsoffer', 'absoffer']: + if order['ordertype'] in ['sw0absoffer', 'swabsoffer', 'absoffer']: sumabsfee += int(order['cjfee']) - elif order['ordertype'] in ['swreloffer', 'reloffer']: + elif order['ordertype'] in ['sw0reloffer', 'swreloffer', 'reloffer']: sumrelfee += Decimal(order['cjfee']) #this is unreachable since calc_cj_fee must already have been called else: #pragma: no cover diff --git a/jmclient/jmclient/taker.py b/jmclient/jmclient/taker.py index 3669cd64e..6858dc40d 100644 --- a/jmclient/jmclient/taker.py +++ b/jmclient/jmclient/taker.py @@ -250,8 +250,12 @@ def filter_orderbook(self, orderbook, sweep=False): if sweep: self.orderbook = orderbook #offers choosing deferred to next step else: - allowed_types = ["reloffer", "absoffer"] if jm_single().config.get( - "POLICY", "segwit") == "false" else ["swreloffer", "swabsoffer"] + if jm_single().config.get("POLICY", "segwit") == "false": + allowed_types = ["reloffer", "absoffer"] + elif jm_single().config.get("POLICY", "native") == "false": + allowed_types = ["swreloffer", "swabsoffer"] + else: + allowed_types = ["sw0reloffer", "sw0absoffer"] self.orderbook, self.total_cj_fee = choose_orders( orderbook, self.cjamount, self.n_counterparties, self.order_chooser, self.ignored_makers, allowed_types=allowed_types, @@ -320,8 +324,12 @@ def prepare_my_bitcoin_data(self): txtype=self.wallet_service.get_txtype()) jlog.debug("We have a fee estimate: "+str(self.total_txfee)) total_value = sum([va['value'] for va in self.input_utxos.values()]) - allowed_types = ["reloffer", "absoffer"] if jm_single().config.get( - "POLICY", "segwit") == "false" else ["swreloffer", "swabsoffer"] + if jm_single().config.get("POLICY", "segwit") == "false": + allowed_types = ["reloffer", "absoffer"] + elif jm_single().config.get("POLICY", "native") == "false": + allowed_types = ["swreloffer", "swabsoffer"] + else: + allowed_types = ["sw0reloffer", "sw0absoffer"] self.orderbook, self.cjamount, self.total_cj_fee = choose_sweep_orders( self.orderbook, total_value, self.total_txfee, self.n_counterparties, self.order_chooser, @@ -498,7 +506,19 @@ def receive_utxos(self, ioauth_data): self.utxo_tx = [u for u in sum(self.utxos.values(), [])] self.outputs.append({'address': self.coinjoin_address(), 'value': self.cjamount}) - self.latest_tx = btc.make_shuffled_tx(self.utxo_tx, self.outputs) + # pre-Nov-2020/v0.8.0: transactions used ver 1 and nlocktime 0 + # so only the new "pit" (using native segwit) will use the updated + # version 2 and nlocktime ~ current block as per normal payments. + # TODO makers do not check this; while there is no security risk, + # it might be better for them to sanity check. + if self.wallet_service.get_txtype() == "p2wpkh": + n_version = 2 + locktime = compute_tx_locktime() + else: + n_version = 1 + locktime = 0 + self.latest_tx = btc.make_shuffled_tx(self.utxo_tx, self.outputs, + version=n_version, locktime=locktime) jlog.info('obtained tx\n' + btc.human_readable_transaction( self.latest_tx)) @@ -577,42 +597,38 @@ def on_sig(self, nick, sigb64): jlog.debug("Junk signature: " + str(sig_deserialized) + \ ", not attempting to verify") break + # The second case here is kept for backwards compatibility. if len(sig_deserialized) == 2: ver_sig, ver_pub = sig_deserialized - scriptCode = None elif len(sig_deserialized) == 3: - ver_sig, ver_pub, scriptCode = sig_deserialized + ver_sig, ver_pub, _ = sig_deserialized else: jlog.debug("Invalid signature message - not 2 or 3 items") break - ver_amt = utxo_data[i]['value'] if scriptCode else None + scriptPubKey = btc.CScript(utxo_data[i]['script']) + is_witness_input = scriptPubKey.is_p2sh() or scriptPubKey.is_witness_v0_keyhash() + ver_amt = utxo_data[i]['value'] if is_witness_input else None witness = btc.CScriptWitness( - [ver_sig, ver_pub]) if scriptCode else None + [ver_sig, ver_pub]) if is_witness_input else None # don't attempt to parse `pub` as pubkey unless it's valid. - if scriptCode: + if scriptPubKey.is_p2sh(): try: s = btc.pubkey_to_p2wpkh_script(ver_pub) except: jlog.debug("Junk signature message, invalid pubkey, ignoring.") break - scriptSig = btc.CScript([ver_sig, ver_pub]) if not scriptCode else btc.CScript([s]) - # Pre-Feb 2020, we used the third field scriptCode differently in - # pre- and post-0.5.0; now the scriptCode is implicit (i.e. calculated - # by underlying library, so that exceptional case is covered. - sig_good = btc.verify_tx_input(self.latest_tx, u[0], scriptSig, - btc.CScript(utxo_data[i]['script']), amount=ver_amt, witness=witness) + if scriptPubKey.is_witness_v0_keyhash(): + scriptSig = btc.CScript(b'') + elif scriptPubKey.is_p2sh(): + scriptSig = btc.CScript([s]) + else: + scriptSig = btc.CScript([ver_sig, ver_pub]) - # verification for the native case is functionally identical but - # adds another flag; so we can allow it here: - if not sig_good: - sig_good = btc.verify_tx_input(self.latest_tx, u[0], scriptSig, - btc.CScript(utxo_data[i]['script']), amount=ver_amt, - witness=witness, native=True) - # if passes, below code executes, and we should change for native: - scriptSig = btc.CScript([b""]) + sig_good = btc.verify_tx_input(self.latest_tx, u[0], scriptSig, + scriptPubKey, amount=ver_amt, witness=witness) if sig_good: jlog.debug('found good sig at index=%d' % (u[0])) @@ -622,7 +638,7 @@ def on_sig(self, nick, sigb64): # there is an assumption of p2sh-p2wpkh or p2wpkh, for the segwit # case. self.latest_tx.vin[u[0]].scriptSig = scriptSig - if ver_amt: + if is_witness_input: self.latest_tx.wit.vtxinwit[u[0]] = btc.CTxInWitness( btc.CScriptWitness(witness)) inserted_sig = True @@ -647,8 +663,9 @@ def on_sig(self, nick, sigb64): # other guy sent a failed signature tx_signed = True - for ins in self.latest_tx.vin: - if ins.scriptSig == b"": + for input, witness in zip(self.latest_tx.vin, self.latest_tx.wit.vtxinwit): + if input.scriptSig == b"" \ + and witness == btc.CTxInWitness(btc.CScriptWitness([])): tx_signed = False if not tx_signed: return False @@ -774,7 +791,8 @@ def self_sign(self): utxo = (ins.prevout.hash[::-1], ins.prevout.n) if utxo not in self.input_utxos.keys(): continue - script = self.input_utxos[utxo]["script"] + self.latest_tx.vin[index].scriptSig = btc.CScript(b'') + script = self.input_utxos[utxo]['script'] amount = self.input_utxos[utxo]['value'] our_inputs[index] = (script, amount) success, msg = self.wallet_service.sign_tx(self.latest_tx, our_inputs) diff --git a/jmclient/jmclient/wallet_utils.py b/jmclient/jmclient/wallet_utils.py index 07d00c79d..0c77d1f35 100644 --- a/jmclient/jmclient/wallet_utils.py +++ b/jmclient/jmclient/wallet_utils.py @@ -1001,7 +1001,7 @@ def wallet_importprivkey(wallet, mixdepth, key_type): "word mnemonic phrase. Make sure you have backups.", "warning") jmprint("WARNING: Make sure that the type of the public address previously " "derived from this private key matches the wallet type you are " - "currently using.") + "currently using.", "warning") jmprint("WARNING: Handling of raw ECDSA bitcoin private keys can lead to " "non-intuitive behaviour and loss of funds.\n Recommended instead " "is to use the \'sweep\' feature of sendpayment.py.", "warning") @@ -1263,7 +1263,7 @@ def create_wallet(path, password, max_mixdepth, wallet_cls, **kwargs): def open_test_wallet_maybe(path, seed, max_mixdepth, - test_wallet_cls=SegwitLegacyWallet, wallet_password_stdin=False, **kwargs): + test_wallet_cls=SegwitWallet, wallet_password_stdin=False, **kwargs): """ Create a volatile test wallet if path is a hex-encoded string of length 64, otherwise run open_wallet(). @@ -1279,8 +1279,8 @@ def open_test_wallet_maybe(path, seed, max_mixdepth, """ # If the native flag is set in the config, it overrides the argument # test_wallet_cls - if jm_single().config.get("POLICY", "native") == "true": - test_wallet_cls = SegwitWallet + if jm_single().config.get("POLICY", "native") == "false": + test_wallet_cls = SegwitLegacyWallet if len(seed) == test_wallet_cls.ENTROPY_BYTES * 2: try: seed = binascii.unhexlify(seed) diff --git a/jmclient/jmclient/yieldgenerator.py b/jmclient/jmclient/yieldgenerator.py index b816a6b14..34eb24a3a 100644 --- a/jmclient/jmclient/yieldgenerator.py +++ b/jmclient/jmclient/yieldgenerator.py @@ -83,13 +83,13 @@ def create_my_orders(self): max_mix = max(mix_balance, key=mix_balance.get) f = '0' - if self.ordertype in ('reloffer', 'swreloffer'): + if self.ordertype in ('reloffer', 'swreloffer', 'sw0reloffer'): f = self.cjfee_r #minimum size bumped if necessary such that you always profit #least 50% of the miner fee self.minsize = max(int(1.5 * self.txfee / float(self.cjfee_r)), self.minsize) - elif self.ordertype in ('absoffer', 'swabsoffer'): + elif self.ordertype in ('absoffer', 'swabsoffer', 'sw0absoffer'): f = str(self.txfee + self.cjfee_a) order = {'oid': 0, 'ordertype': self.ordertype, @@ -188,7 +188,7 @@ def select_output_address(self, input_mixdepth, offer, amount): return self.wallet_service.get_internal_addr(cjoutmix) -def ygmain(ygclass, txfee=1000, cjfee_a=200, cjfee_r=0.002, ordertype='swreloffer', +def ygmain(ygclass, txfee=1000, cjfee_a=200, cjfee_r=0.002, ordertype='reloffer', nickserv_password='', minsize=100000, gaplimit=6): import sys @@ -222,19 +222,19 @@ def ygmain(ygclass, txfee=1000, cjfee_a=200, cjfee_r=0.002, ordertype='swreloffe wallet_name = args[0] ordertype = options.ordertype txfee = options.txfee - if ordertype in ('reloffer', 'swreloffer'): + if ordertype == 'reloffer': if options.cjfee != '': cjfee_r = options.cjfee # minimum size is such that you always net profit at least 20% #of the miner fee minsize = max(int(1.2 * txfee / float(cjfee_r)), options.minsize) - elif ordertype in ('absoffer', 'swabsoffer'): + elif ordertype == 'absoffer': if options.cjfee != '': cjfee_a = int(options.cjfee) minsize = options.minsize else: parser.error('You specified an incorrect offer type which ' +\ - 'can be either swreloffer or swabsoffer') + 'can be either reloffer or absoffer') sys.exit(EXIT_ARGERROR) nickserv_password = options.password @@ -256,8 +256,22 @@ def ygmain(ygclass, txfee=1000, cjfee_a=200, cjfee_r=0.002, ordertype='swreloffe wallet_service.sync_wallet(fast=not options.recoversync) wallet_service.startService() + txtype = wallet_service.get_txtype() + if txtype == "p2wpkh": + prefix = "sw0" + elif txtype == "p2sh-p2wpkh": + prefix = "sw" + elif txtype == "p2pkh": + prefix = "" + else: + jlog.error("Unsupported wallet type for yieldgenerator: " + txtype) + sys.exit(EXIT_ARGERROR) + + ordertype = prefix + ordertype + jlog.debug("Set the offer type string to: " + ordertype) + maker = ygclass(wallet_service, [options.txfee, cjfee_a, cjfee_r, - options.ordertype, options.minsize]) + ordertype, options.minsize]) jlog.info('starting yield generator') clientfactory = JMClientProtocolFactory(maker, proto_type="MAKER") diff --git a/jmclient/test/commontest.py b/jmclient/test/commontest.py index f4c849c26..ed6387273 100644 --- a/jmclient/test/commontest.py +++ b/jmclient/test/commontest.py @@ -11,7 +11,7 @@ from jmclient import ( jm_single, open_test_wallet_maybe, estimate_tx_fee, BlockchainInterface, BIP32Wallet, - SegwitLegacyWallet, WalletService, BTC_P2SH_P2WPKH) + SegwitWallet, WalletService, BTC_P2SH_P2WPKH) from jmbase.support import chunks import jmbitcoin as btc @@ -177,7 +177,7 @@ def make_wallets(n, sdev_amt=0, start_index=0, fixed_seeds=None, - wallet_cls=SegwitLegacyWallet, + wallet_cls=SegwitWallet, mixdepths=5, populate_internal=False): '''n: number of wallets to be created diff --git a/jmclient/test/taker_test_data.py b/jmclient/test/taker_test_data.py index 0ee3c092c..ec65a7754 100644 --- a/jmclient/test/taker_test_data.py +++ b/jmclient/test/taker_test_data.py @@ -1,15 +1,15 @@ #orderbook -t_orderbook = [{u'counterparty': u'J6FA1Gj7Ln4vSGne', u'ordertype': u'swreloffer', u'oid': 0, +t_orderbook = [{u'counterparty': u'J6FA1Gj7Ln4vSGne', u'ordertype': u'sw0reloffer', u'oid': 0, u'minsize': 7500000, u'txfee': 1000, u'maxsize': 599972700, u'cjfee': u'0.0002'}, - {u'counterparty': u'J6CFffuuewjG44UJ', u'ordertype': u'swreloffer', u'oid': 0, + {u'counterparty': u'J6CFffuuewjG44UJ', u'ordertype': u'sw0reloffer', u'oid': 0, u'minsize': 7500000, u'txfee': 1000, u'maxsize': 599972700, u'cjfee': u'0.0002'}, - {u'counterparty': u'J65z23xdjxJjC7er', u'ordertype': u'swreloffer', u'oid': 0, + {u'counterparty': u'J65z23xdjxJjC7er', u'ordertype': u'sw0reloffer', u'oid': 0, u'minsize': 7500000, u'txfee': 1000, u'maxsize': 599972700, u'cjfee': u'0.0002'}, - {u'counterparty': u'J64Ghp5PXCdY9H3t', u'ordertype': u'swreloffer', u'oid': 0, + {u'counterparty': u'J64Ghp5PXCdY9H3t', u'ordertype': u'sw0reloffer', u'oid': 0, u'minsize': 7500000, u'txfee': 1000, u'maxsize': 599972700, u'cjfee': u'0.0002'}, - {u'counterparty': u'J659UPUSLLjHJpaB', u'ordertype': u'swreloffer', u'oid': 0, + {u'counterparty': u'J659UPUSLLjHJpaB', u'ordertype': u'sw0reloffer', u'oid': 0, u'minsize': 7500000, u'txfee': 1000, u'maxsize': 599972700, u'cjfee': u'0.0002'}, - {u'counterparty': u'J6cBx1FwUVh9zzoO', u'ordertype': u'swreloffer', u'oid': 0, + {u'counterparty': u'J6cBx1FwUVh9zzoO', u'ordertype': u'sw0reloffer', u'oid': 0, u'minsize': 7500000, u'txfee': 1000, u'maxsize': 599972700, u'cjfee': u'0.0002'}] t_dest_addr = "mvw1NazKDRbeNufFANqpYNAANafsMC2zVU" @@ -19,21 +19,21 @@ u'maxsize': 599972700, u'minsize': 7500000, u'oid': 0, - u'ordertype': u'swreloffer', + u'ordertype': u'sw0reloffer', u'txfee': 1000}, u'J65z23xdjxJjC7er': {u'cjfee': u'0.0002', u'counterparty': u'J65z23xdjxJjC7er', u'maxsize': 599972700, u'minsize': 7500000, u'oid': 0, - u'ordertype': u'swreloffer', + u'ordertype': u'sw0reloffer', u'txfee': 1000}, u'J6CFffuuewjG44UJ': {u'cjfee': u'0.0002', u'counterparty': u'J6CFffuuewjG44UJ', u'maxsize': 599972700, u'minsize': 7500000, u'oid': 0, - u'ordertype': u'swreloffer', + u'ordertype': u'sw0reloffer', u'txfee': 1000}} """ diff --git a/jmclient/test/test_client_protocol.py b/jmclient/test/test_client_protocol.py index 96ae0c309..d16f8dff7 100644 --- a/jmclient/test/test_client_protocol.py +++ b/jmclient/test/test_client_protocol.py @@ -118,7 +118,7 @@ def create_my_orders(self): 'maxsize': 100000000, 'minsize': 75000, 'oid': 0, - 'ordertype': 'swreloffer', + 'ordertype': 'sw0reloffer', 'txfee': 0 }] diff --git a/jmclient/test/test_coinjoin.py b/jmclient/test/test_coinjoin.py index 0c851b8ed..54edb8407 100644 --- a/jmclient/test/test_coinjoin.py +++ b/jmclient/test/test_coinjoin.py @@ -11,7 +11,7 @@ from jmbase import get_log, hextobin from jmclient import load_test_config, jm_single,\ - YieldGeneratorBasic, Taker, LegacyWallet, SegwitLegacyWallet,\ + YieldGeneratorBasic, Taker, LegacyWallet, SegwitLegacyWallet, SegwitWallet,\ NO_ROUNDING from jmclient.podle import set_commitment_file from commontest import make_wallets, default_max_cj_fee @@ -118,7 +118,7 @@ def do_tx_signing(taker, makers, active_orders, txdata): return taker_final_result -@pytest.mark.parametrize('wallet_cls', (LegacyWallet, SegwitLegacyWallet)) +@pytest.mark.parametrize('wallet_cls', (LegacyWallet, SegwitLegacyWallet, SegwitWallet)) def test_simple_coinjoin(monkeypatch, tmpdir, setup_cj, wallet_cls): def raise_exit(i): raise Exception("sys.exit called") @@ -137,7 +137,7 @@ def raise_exit(i): makers = [YieldGeneratorBasic( wallet_services[i], - [0, 2000, 0, 'swabsoffer', 10**7]) for i in range(MAKER_NUM)] + [0, 2000, 0, 'sw0absoffer', 10**7]) for i in range(MAKER_NUM)] create_orders(makers) orderbook = create_orderbook(makers) @@ -182,7 +182,7 @@ def raise_exit(i): cj_fee = 2000 makers = [YieldGeneratorBasic( wallet_services[i], - [0, cj_fee, 0, 'swabsoffer', 10**7]) for i in range(MAKER_NUM)] + [0, cj_fee, 0, 'sw0absoffer', 10**7]) for i in range(MAKER_NUM)] create_orders(makers) orderbook = create_orderbook(makers) @@ -238,7 +238,7 @@ def raise_exit(i): cj_fee = 2000 makers = [YieldGeneratorBasic( wallet_services[i], - [0, cj_fee, 0, 'swabsoffer', 10**7]) for i in range(MAKER_NUM)] + [0, cj_fee, 0, 'sw0absoffer', 10**7]) for i in range(MAKER_NUM)] create_orders(makers) orderbook = create_orderbook(makers) assert len(orderbook) == MAKER_NUM diff --git a/jmclient/test/test_maker.py b/jmclient/test/test_maker.py index 28750ec38..031779a73 100644 --- a/jmclient/test/test_maker.py +++ b/jmclient/test/test_maker.py @@ -77,7 +77,7 @@ def get_address_generator(script_pre, script_post, p2sh=False): def create_tx_and_offerlist(cj_addr, cj_change_addr, other_output_addrs, - offertype='swreloffer'): + offertype='sw0reloffer'): assert len(other_output_addrs) % 2 == 0, "bug in test" cj_value = 100000000 diff --git a/jmclient/test/test_snicker.py b/jmclient/test/test_snicker.py index 7eeca551b..cafe11e35 100644 --- a/jmclient/test/test_snicker.py +++ b/jmclient/test/test_snicker.py @@ -9,7 +9,7 @@ import jmbitcoin as btc from jmbase import get_log, bintohex from jmclient import (load_test_config, estimate_tx_fee, SNICKERReceiver, - direct_send) + direct_send, SegwitLegacyWallet) TEST_PROPOSALS_FILE = "test_proposals.txt" log = get_log() @@ -31,7 +31,10 @@ def test_snicker_e2e(setup_snicker, nw, wallet_structures, LoopingCall) and processes them. 5. Check for valid final transaction with broadcast. """ - wallets = make_wallets(nw, wallet_structures, mean_amt, sdev_amt) + + # TODO: Make this test work with native segwit wallets + wallets = make_wallets(nw, wallet_structures, mean_amt, sdev_amt, + wallet_cls=SegwitLegacyWallet) for w in wallets.values(): w['wallet'].sync_wallet(fast=True) print(wallets) diff --git a/jmclient/test/test_support.py b/jmclient/test/test_support.py index cbd93b64a..01322f36d 100644 --- a/jmclient/test/test_support.py +++ b/jmclient/test/test_support.py @@ -55,6 +55,8 @@ def test_random_funcs(): def test_calc_cjfee(): assert calc_cj_fee("swabsoffer", 3000, 200000000) == 3000 assert calc_cj_fee("swreloffer", "0.01", 100000000) == 1000000 + assert calc_cj_fee("sw0absoffer", 3000, 200000000) == 3000 + assert calc_cj_fee("sw0reloffer", "0.01", 100000000) == 1000000 with pytest.raises(RuntimeError) as e_info: calc_cj_fee("dummyoffer", 2, 3) @@ -100,7 +102,7 @@ def test_choose_orders(): #(b) add an unrecognized ordertype (does not raise, ignores) #(c) put an order with wrong minsize orderbook.append({u'counterparty': u'fake', - u'ordertype': u'swabsoffer', u'oid': 0, + u'ordertype': u'sw0absoffer', u'oid': 0, u'minsize': 7500000, u'txfee': 1000, u'maxsize': 599972700, u'cjfee': 9000}) result, cjamount, total_fee = choose_sweep_orders(orderbook, 50000000, @@ -132,4 +134,4 @@ def test_choose_orders(): None) assert result == None assert cjamount == 0 - assert total_fee == 0 \ No newline at end of file + assert total_fee == 0 diff --git a/jmclient/test/test_tx_creation.py b/jmclient/test/test_tx_creation.py index 4c0aafe56..af5bcfeba 100644 --- a/jmclient/test/test_tx_creation.py +++ b/jmclient/test/test_tx_creation.py @@ -68,9 +68,9 @@ def test_verify_tx_input(setup_tx_creation): scriptSig = tx.vin[0].scriptSig tx2 = bitcoin.mktx(ins, outs) res = bitcoin.verify_tx_input(tx2, 0, scriptSig, - bitcoin.pubkey_to_p2sh_p2wpkh_script(pub), + bitcoin.pubkey_to_p2wpkh_script(pub), amount = bitcoin.coins_to_satoshi(1), - witness = bitcoin.CScript([sig, pub])) + witness = bitcoin.CScriptWitness([sig, pub])) assert res def test_absurd_fees(setup_tx_creation): diff --git a/jmclient/test/test_yieldgenerator.py b/jmclient/test/test_yieldgenerator.py index 1622baf57..c3acc83d0 100644 --- a/jmclient/test/test_yieldgenerator.py +++ b/jmclient/test/test_yieldgenerator.py @@ -85,10 +85,10 @@ def test_abs_fee(self): def test_rel_fee(self): jm_single().DUST_THRESHOLD = 10 yg = create_yg_basic([0, 2000000, 1000000], txfee=1000, cjfee_r=0.1, - ordertype='swreloffer', minsize=10) + ordertype='sw0reloffer', minsize=10) self.assertEqual(yg.create_my_orders(), [ {'oid': 0, - 'ordertype': 'swreloffer', + 'ordertype': 'sw0reloffer', 'minsize': 15000, 'maxsize': 1999000, 'txfee': 1000, diff --git a/jmdaemon/jmdaemon/orderbookwatch.py b/jmdaemon/jmdaemon/orderbookwatch.py index 566ffb54f..62edcb7f0 100644 --- a/jmdaemon/jmdaemon/orderbookwatch.py +++ b/jmdaemon/jmdaemon/orderbookwatch.py @@ -105,7 +105,7 @@ def on_order_seen(self, counterparty, oid, ordertype, minsize, maxsize, "from {}").format log.debug(fmt(minsize, maxsize, counterparty)) return - if ordertype in ['swabsoffer', 'absoffer']\ + if ordertype in ['sw0absoffer', 'swabsoffer', 'absoffer']\ and not isinstance(cjfee, Integral): try: cjfee = int(cjfee) diff --git a/jmdaemon/jmdaemon/protocol.py b/jmdaemon/jmdaemon/protocol.py index 9920e2cfc..7d531aad4 100644 --- a/jmdaemon/jmdaemon/protocol.py +++ b/jmdaemon/jmdaemon/protocol.py @@ -12,6 +12,10 @@ "swreloffer": [(int, "oid"), (int, "minsize"), (int, "maxsize"), (int, "txfee"), (float, "cjfee")], "swabsoffer": [(int, "oid"), (int, "minsize"), (int, "maxsize"), + (int, "txfee"), (int, "cjfee")], + "sw0reloffer": [(int, "oid"), (int, "minsize"), (int, "maxsize"), + (int, "txfee"), (float, "cjfee")], + "sw0absoffer": [(int, "oid"), (int, "minsize"), (int, "maxsize"), (int, "txfee"), (int, "cjfee")]} offername_list = list(offertypes.keys()) diff --git a/scripts/joinmarket-qt.py b/scripts/joinmarket-qt.py index a05a7a90b..3e1269ed4 100755 --- a/scripts/joinmarket-qt.py +++ b/scripts/joinmarket-qt.py @@ -892,10 +892,10 @@ def checkOffers(self, offers_fee, cjamount): mbinfo.append("Counterparties chosen:") mbinfo.append('Name, Order id, Coinjoin fee (sat.)') for k, o in iteritems(offers): - if o['ordertype'] in ['swreloffer', 'reloffer']: + if o['ordertype'] in ['sw0reloffer', 'swreloffer', 'reloffer']: display_fee = int(self.taker.cjamount * float(o['cjfee'])) - int(o['txfee']) - elif o['ordertype'] in ['swabsoffer', 'absoffer']: + elif o['ordertype'] in ['sw0absoffer', 'swabsoffer', 'absoffer']: display_fee = int(o['cjfee']) - int(o['txfee']) else: log.debug("Unsupported order type: " + str(o['ordertype']) + diff --git a/scripts/obwatch/ob-watcher.py b/scripts/obwatch/ob-watcher.py index 6bb8b32e2..408e47adb 100755 --- a/scripts/obwatch/ob-watcher.py +++ b/scripts/obwatch/ob-watcher.py @@ -41,11 +41,11 @@ from jmdaemon.protocol import * #Initial state: allow only SW offer types -swoffers = list(filter(lambda x: x[0:2] == 'sw', offername_list)) -pkoffers = list(filter(lambda x: x[0:2] != 'sw', offername_list)) -filtered_offername_list = swoffers +sw0offers = list(filter(lambda x: x[0:3] == 'sw0', offername_list)) +swoffers = list(filter(lambda x: x[0:3] == 'swa' or x[0:3] == 'swr', offername_list)) +filtered_offername_list = sw0offers -toggleSWform = '
' +rotateObform = '
' refresh_orderbook_form = '
' sorted_units = ('BTC', 'mBTC', 'μBTC', 'satoshi') unit_to_power = {'BTC': 8, 'mBTC': 5, 'μBTC': 2, 'satoshi': 0} @@ -70,15 +70,15 @@ def do_nothing(arg, order, btc_unit, rel_unit): def ordertype_display(ordertype, order, btc_unit, rel_unit): - ordertypes = {'swabsoffer': 'SW Absolute Fee', 'swreloffer': 'SW Relative Fee', - 'absoffer': 'Absolute Fee', 'reloffer': 'Relative Fee'} + ordertypes = {'sw0absoffer': 'Native SW Absolute Fee', 'sw0reloffer': 'Native SW Relative Fee', + 'swabsoffer': 'SW Absolute Fee', 'swreloffer': 'SW Relative Fee'} return ordertypes[ordertype] def cjfee_display(cjfee, order, btc_unit, rel_unit): - if order['ordertype'] in ['absoffer', 'swabsoffer']: + if order['ordertype'] in ['swabsoffer', 'sw0absoffer']: return satoshi_to_unit(cjfee, order, btc_unit, rel_unit) - elif order['ordertype'] in ['reloffer', 'swreloffer']: + elif order['ordertype'] in ['reloffer', 'swreloffer', 'sw0reloffer']: return str(Decimal(cjfee) * rel_unit_to_factor[rel_unit]) + rel_unit @@ -145,8 +145,11 @@ def create_orderbook_obj(self): for row in rows: o = dict(row) if 'cjfee' in o: - o['cjfee'] = int(o['cjfee']) if o['ordertype']\ - == 'swabsoffer' else str(Decimal(o['cjfee'])) + if o['ordertype'] == 'swabsoffer'\ + or o['ordertype'] == 'sw0absoffer': + o['cjfee'] = int(o['cjfee']) + else: + o['cjfee'] = str(Decimal(o['cjfee'])) result.append(o) return result @@ -305,7 +308,7 @@ def do_GET(self): (str(ordercount) + ' orders found by ' + self.get_counterparty_count() + ' counterparties' + alert_msg), 'MAINBODY': ( - toggleSWform + refresh_orderbook_form + choose_units_form + + rotateObform + refresh_orderbook_form + choose_units_form + table_heading + ordertable + '\n') } elif self.path == '/ordersize': @@ -346,7 +349,7 @@ def do_GET(self): def do_POST(self): global filtered_offername_list - pages = ['/refreshorderbook', '/toggleSW'] + pages = ['/refreshorderbook', '/rotateOb'] if self.path not in pages: return if self.path == '/refreshorderbook': @@ -354,11 +357,13 @@ def do_POST(self): time.sleep(5) self.path = '/' self.do_GET() - elif self.path == '/toggleSW': - if filtered_offername_list == swoffers: - filtered_offername_list = pkoffers - else: + elif self.path == '/rotateOb': + if filtered_offername_list == sw0offers: + log.debug('Showing nested segwit orderbook') filtered_offername_list = swoffers + elif filtered_offername_list == swoffers: + log.debug('Showing native segwit orderbook') + filtered_offername_list = sw0offers self.path = '/' self.do_GET() diff --git a/scripts/sendpayment.py b/scripts/sendpayment.py index 8901ed327..8d8912df3 100755 --- a/scripts/sendpayment.py +++ b/scripts/sendpayment.py @@ -147,13 +147,6 @@ def main(): if int(options.txfee) > 0: jm_single().config.set("POLICY", "tx_fees", str(options.txfee)) - # Dynamically estimate a realistic fee. - # At this point we do not know even the number of our own inputs, so - # we guess conservatively with 2 inputs and 2 outputs each. - fee_per_cp_guess = estimate_tx_fee(2, 2, txtype="p2sh-p2wpkh") - log.debug("Estimated miner/tx fee for each cj participant: " + str( - fee_per_cp_guess)) - maxcjfee = (1, float('inf')) if not options.pickorders and options.makercount != 0: maxcjfee = get_max_cj_fee_values(jm_single().config, options) @@ -179,6 +172,12 @@ def main(): # the sync call here will now be a no-op: wallet_service.startService() + # Dynamically estimate a realistic fee. + # At this point we do not know even the number of our own inputs, so + # we guess conservatively with 2 inputs and 2 outputs each. + fee_per_cp_guess = estimate_tx_fee(2, 2, txtype=wallet_service.get_txtype()) + log.debug("Estimated miner/tx fee for each cj participant: " + str( + fee_per_cp_guess)) # From the estimated tx fees, check if the expected amount is a # significant value compared the the cj amount; currently enabled diff --git a/scripts/tumbler.py b/scripts/tumbler.py index 5a918bb59..c6239e433 100755 --- a/scripts/tumbler.py +++ b/scripts/tumbler.py @@ -125,7 +125,7 @@ def main(): # Dynamically estimate an expected tx fee for the whole tumbling run. # This is very rough: we guess with 2 inputs and 2 outputs each. - fee_per_cp_guess = estimate_tx_fee(2, 2, txtype="p2sh-p2wpkh") + fee_per_cp_guess = estimate_tx_fee(2, 2, txtype=wallet_service.get_txtype()) log.debug("Estimated miner/tx fee for each cj participant: " + str( fee_per_cp_guess)) diff --git a/scripts/yg-privacyenhanced.py b/scripts/yg-privacyenhanced.py index 54a623c6e..7c1f28a68 100755 --- a/scripts/yg-privacyenhanced.py +++ b/scripts/yg-privacyenhanced.py @@ -15,14 +15,14 @@ """THESE SETTINGS CAN SIMPLY BE EDITED BY HAND IN THIS FILE: """ -ordertype = 'swreloffer' # [string, 'swreloffer' or 'swabsoffer'] / which fee type to actually use +ordertype = 'reloffer' # [string, 'reloffer' or 'absoffer'], which fee type to actually use cjfee_a = 500 # [satoshis, any integer] / absolute offer fee you wish to receive for coinjoins (cj) -cjfee_r = '0.00002' # [percent, any str between 0-1] / relative offer fee you wish to receive based on a cj's amount -cjfee_factor = 0.1 # [percent, 0-1] / variance around the average fee. Ex: 200 fee, 0.2 var = fee is btw 160-240 +cjfee_r = '0.00002' # [fraction, any str between 0-1] / relative offer fee you wish to receive based on a cj's amount +cjfee_factor = 0.1 # [fraction, 0-1] / variance around the average fee. Ex: 200 fee, 0.2 var = fee is btw 160-240 txfee = 100 # [satoshis, any integer] / the average transaction fee you're adding to coinjoin transactions -txfee_factor = 0.3 # [percent, 0-1] / variance around the average fee. Ex: 1000 fee, 0.2 var = fee is btw 800-1200 +txfee_factor = 0.3 # [fraction, 0-1] / variance around the average fee. Ex: 1000 fee, 0.2 var = fee is btw 800-1200 minsize = 100000 # [satoshis, any integer] / minimum size of your cj offer. Lower cj amounts will be disregarded -size_factor = 0.1 # [percent, 0-1] / variance around all offer sizes. Ex: 500k minsize, 0.1 var = 450k-550k +size_factor = 0.1 # [fraction, 0-1] / variance around all offer sizes. Ex: 500k minsize, 0.1 var = 450k-550k gaplimit = 6 # end of settings customization @@ -39,9 +39,9 @@ def create_my_orders(self): # We publish ONLY the maximum amount and use minsize for lower bound; # leave it to oid_to_order to figure out the right depth to use. f = '0' - if ordertype == 'swreloffer': + if self.ordertype in ['swreloffer', 'sw0reloffer']: f = self.cjfee_r - elif ordertype == 'swabsoffer': + elif self.ordertype in ['swabsoffer', 'sw0absoffer']: f = str(self.txfee + self.cjfee_a) mix_balance = dict([(m, b) for m, b in iteritems(mix_balance) if b > self.minsize]) @@ -60,7 +60,7 @@ def create_my_orders(self): randomize_maxsize = int(random.uniform(possible_maxsize * (1 - float(size_factor)), possible_maxsize)) - if ordertype == 'swabsoffer': + if self.ordertype == 'swabsoffer': randomize_cjfee = int(random.uniform(float(cjfee_a) * (1 - float(cjfee_factor)), float(cjfee_a) * (1 + float(cjfee_factor)))) randomize_cjfee = randomize_cjfee + randomize_txfee @@ -80,7 +80,7 @@ def create_my_orders(self): assert order['minsize'] >= 0 assert order['maxsize'] > 0 assert order['minsize'] <= order['maxsize'] - if order['ordertype'] == 'swreloffer': + if order['ordertype'] in ['swreloffer', 'sw0reloffer']: while order['txfee'] >= (float(order['cjfee']) * order['minsize']): order['txfee'] = int(order['txfee'] / 2) jlog.info('Warning: too high txfee to be profitable, halfing it to: ' + str(order['txfee'])) diff --git a/scripts/yield-generator-basic.py b/scripts/yield-generator-basic.py index 406b0d0be..6f42389cf 100755 --- a/scripts/yield-generator-basic.py +++ b/scripts/yield-generator-basic.py @@ -5,17 +5,18 @@ """THESE SETTINGS CAN SIMPLY BE EDITED BY HAND IN THIS FILE: """ -txfee = 100 -cjfee_a = 500 -cjfee_r = '0.00002' -ordertype = 'swreloffer' #'swreloffer' or 'swabsoffer' + +ordertype = 'reloffer' # [string, 'reloffer' or 'absoffer'], which fee type to actually use +cjfee_a = 500 # [satoshis, any integer] / absolute offer fee you wish to receive for coinjoins (cj) +cjfee_r = '0.00002' # [fraction, any str between 0-1] / relative offer fee you wish to receive based on a cj's amount +txfee = 100 # [satoshis, any integer] / the average transaction fee you're adding to coinjoin transactions nickserv_password = '' -max_minsize = 100000 +minsize = 100000 # [satoshis, any integer] / minimum size of your cj offer. Lower cj amounts will be disregarded gaplimit = 6 if __name__ == "__main__": ygmain(YieldGeneratorBasic, txfee=txfee, cjfee_a=cjfee_a, cjfee_r=cjfee_r, ordertype=ordertype, nickserv_password=nickserv_password, - minsize=max_minsize, gaplimit=gaplimit) + minsize=minsize, gaplimit=gaplimit) jmprint('done', "success") diff --git a/test/common.py b/test/common.py index 971eb7953..34f3ea104 100644 --- a/test/common.py +++ b/test/common.py @@ -11,7 +11,7 @@ sys.path.insert(0, os.path.join(data_dir)) from jmbase import get_log -from jmclient import open_test_wallet_maybe, BIP32Wallet, SegwitLegacyWallet, \ +from jmclient import open_test_wallet_maybe, BIP32Wallet, SegwitWallet, \ estimate_tx_fee, jm_single, WalletService import jmbitcoin as btc from jmbase import chunks @@ -64,7 +64,7 @@ def make_wallets(n, fixed_seeds=None, test_wallet=False, passwords=None, - walletclass=SegwitLegacyWallet, + walletclass=SegwitWallet, mixdepths=5): '''n: number of wallets to be created wallet_structure: array of n arrays , each subarray diff --git a/test/ygrunner.py b/test/ygrunner.py index 544a7a50b..08210d986 100644 --- a/test/ygrunner.py +++ b/test/ygrunner.py @@ -120,7 +120,7 @@ def test_start_ygs(setup_ygrunner, num_ygs, wallet_structures, mean_amt, txfee = 1000 cjfee_a = 4200 cjfee_r = '0.001' - ordertype = 'swreloffer' + ordertype = 'sw0reloffer' minsize = 100000 ygclass = YieldGeneratorBasic if malicious: