From 14f086bfe485a79f555f6355d6d34932fa858f01 Mon Sep 17 00:00:00 2001 From: chris-belcher Date: Mon, 18 May 2020 02:01:45 +0100 Subject: [PATCH] Add usage guide for fidelity bond wallets --- docs/fidelity-bonds.md | 271 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 docs/fidelity-bonds.md diff --git a/docs/fidelity-bonds.md b/docs/fidelity-bonds.md new file mode 100644 index 0000000..d6c2c64 --- /dev/null +++ b/docs/fidelity-bonds.md @@ -0,0 +1,271 @@ +# Fidelity bonds + +Fidelity bonds are a feature of JoinMarket which improves the resistance to +sybil attacks, and therefore improves the privacy of the system. + +## This feature is incomplete and so is disabled for now + +A fidelity bond is a mechanism where bitcoin value is deliberately sacrificed +to make a cryptographic identity expensive to obtain. The sacrifice is done in +a way that can be proven to a third party. Takers in JoinMarket will +have a higher probability to create coinjoins with makers who publish more +valuable fidelity bonds. This has the effect of making the system much more +expensive to sybil attack, because an attacker would have to sacrifice a lot of +value in order to be chosen very often by takers when creating coinjoin. + +As a maker you can take part in many more coinjoins and therefore earn more +fees if you sacrifice bitcoin value to create a fidelity bond. The most +practical way to create a fidelity bond is to send bitcoins to a time-locked +address which uses the opcode [OP_CHECKLOCKTIMEVERIFY](https://en.bitcoin.it/wiki/Timelock#CheckLockTimeVerify). +The valuable thing being sacrificed is the time-value-of-money. Note that a +long-term holder (or hodler) of bitcoins can buy time-locked fidelity bonds +essentially for free, assuming they never intended to transact with their coins +anyway. + +Another way to create fidelity bonds is to destroy coins by sending them to a +[OP_RETURN](https://en.bitcoin.it/wiki/Script#Provably_Unspendable.2FPrunable_Outputs) +output. + +The private keys to fidelity bonds can be kept in [cold storage](https://en.bitcoin.it/wiki/Cold_storage) +for added security. + +For a more detailed explanation of how fidelity bonds work see these documents: + +* [Design for improving JoinMarket's resistance to sybil attacks using fidelity +bonds](https://gist.github.com/chris-belcher/18ea0e6acdb885a2bfbdee43dcd6b5af/) + +* [Financial mathematics of JoinMarket fidelity bonds](https://gist.github.com/chris-belcher/87ebbcbb639686057a389acb9ab3e25b) + +#### Note on privacy + +Bitcoin outputs which create fidelity bonds will be published to the entire +world, so before and after creating them make sure the outputs are not linked +to your identity in any way. Perhaps mix with JoinMarket before and after. + +### Creating a JoinMarket wallet which supports fidelity bonds + +When generating a JoinMarket wallet on the command line, supporting versions +will offer an option to make the wallet support fidelity bonds. + + (jmvenv) $ python3 wallet-tool.py generate + Would you like to use a two-factor mnemonic recovery phrase? write 'n' if you don't know what this is (y/n): n + Not using mnemonic extension + Enter wallet file encryption passphrase: + Reenter wallet file encryption passphrase: + Input wallet file name (default: wallet.jmdat): testfidelity.jmdat + Would you like this wallet to support fidelity bonds? write 'n' if you don't know what this is (y/n): y + Write down this wallet recovery mnemonic + + evidence initial knee image inspire plug dad midnight blast awkward clean between + + Generated wallet OK + +As always, it is crucially important to write down the 12-word [seed phrase](https://en.bitcoin.it/wiki/Seed_phrase) +as a backup. It is also recommended to write down the name of the creating wallet +"JoinMarket" and that the fidelity bond option was enabled. Writing the wallet +creation date is also useful as it can help with rescanning. + +### Obtaining time-locked addresses + +The `wallet-tool.py` script supports a new method `gettimelockaddress` used for +obtaining time-locked addresses. If coins are sent to these addresses they will +be locked up until the timelock date passes. Only mixdepth zero can have +fidelity bonds in it. + +This example creates an address which locks any coins sent to it until April 2020. + + (jmvenv) $ python3 wallet-tool.py testfidelity.jmdat gettimelockaddress 2020-4 + Enter wallet decryption passphrase: + path = m/49'/1'/0'/2/3:1585699200 + Coins sent to this address will be not be spendable until April 2020. Full date: 2020-04-01 00:00:00 + bcrt1qrc2qu3m2l2spayu5kr0k0rnn9xgjz46zsxmruh87a3h3f5zmnkaqlfx7v5 + +If coins are sent to these addresses they will appear in the usual `wallet-tool.py` +display: + + (jmvenv) $ python3 wallet-tool.py -m 0 testfidelity.jmdat + Enter wallet decryption passphrase: + JM wallet + mixdepth 0 fbonds-mpk-tpubDDCbCPdf5wJVGYWB4mZr3E3Lys4NBcEKysrrUrLfhG6sekmrvs6KZNe4i5p5z3FyfwRmKMqB9NWEcEUiTS4LwqfrKPQzhKj6aLihu2EejaU + external addresses m/49'/1'/0'/0 tpubDEGdmPwmQRcZmGKhaudjch9Fgw4J5yP4bYw5B8LoSDkMdmhBxM4ndEQXHK4r1TPexGjLidxdpeEzsJcdXEe7khWToxCZuN6JiLzvUoHAki2 + m/49'/1'/0'/0/0 2N8jHuQaApgFtQ8UKxKbREAvNxKn4BGX4x2 0.00000000 new + m/49'/1'/0'/0/1 2Mx5CwDoNcuCT38EDmgenQxv9skHbZfXFdo 0.00000000 new + m/49'/1'/0'/0/2 2N1tNTTwNyucGGmfDWNVk3AUi3i5S8jVKqn 0.00000000 new + m/49'/1'/0'/0/3 2N8eBEU5wpWb6kS1gvbRgewtxsmXsMkShV6 0.00000000 new + m/49'/1'/0'/0/4 2MuHgeSgMsvkcn6aGNW2uk2UXP3xVVnkfh2 0.00000000 new + m/49'/1'/0'/0/5 2NA8d8um5KmBNNR8dadhbEDYGiTJPFCdjMB 0.00000000 new + Balance: 0.00000000 + internal addresses m/49'/1'/0'/1 + Balance: 0.00000000 + internal addresses m/49'/1'/0'/2 tpubDEGdmPwmQRcZrzjRmUFqXXyLdRedwxCWQviAFqDe6sXJeZzRNTwmwqMfxN6Ka3v7hEebstrU5kqUNoHsFKaA3RoB2vopL6kLHVo1EQq6USw + m/49'/1'/0'/2/0:1585699200 bcrt1qrc2qu3m2l2spayu5kr0k0rnn9xgjz46zsxmruh87a3h3f5zmnkaqlfx7v5 0.15000000 2020-04-01 [LOCKED] + Balance: 0.15000000 + Balance for mixdepth 0: 0.15000000 + Total balance: 0.15000000 + +#### Spending time-locked coins + +Once the time-lock of an address expires the coins can be spent with JoinMarket. + +Coins living on time-locked addresses are automatically frozen with +JoinMarket's coin control feature, so before spending you need to unfreeze the +coins using `python3 wallet-tool.py -m 0 freeze`. + +Once unfrozen and untimelocked the coins can be spent normally with the scripts +`sendpayment.py`, `tumber.py`, or yield generator. + +### Burning coins + +Coins can be burned with a special method of the `sendpayment.py` script. Set +the destination to be `BURN`. Transactions which burn coins must only have one +input and one output, so use coin control to freeze all coins in the zeroth +mixdepth except one. + + $ python3 sendpayment.py -N 0 testfidelity3.jmdat 0 BURN + User data location: . + 2020-04-07 20:45:25,658 [INFO] Using this min relay fee as tx fee floor: 1000 sat/vkB (1.0 sat/vB) + Enter wallet decryption passphrase: + 2020-04-07 20:46:50,449 [INFO] Estimated miner/tx fees for this coinjoin amount: 0.0% + 2020-04-07 20:46:50,452 [INFO] Using this min relay fee as tx fee floor: 1000 sat/vkB (1.0 sat/vB) + 2020-04-07 20:46:50,452 [INFO] Using a fee of : 0.00000200 BTC (200 sat). + 2020-04-07 20:46:50,454 [INFO] Got signed transaction: + + 2020-04-07 20:46:50,455 [INFO] {'ins': [{'outpoint': {'hash': '61d69b4e7abe0ef8a5a9cbabb05463259c3b497a142130a56f81a9259f048cb0', + 'index': 0}, + 'script': '160014295beb4eba9b35896683d5b5ff455ee2c646054c', + 'sequence': 4294967294, + 'txinwitness': ['3045022100939de908e30015c6b22d2c0f25153e395268466ce44eeeb4ec03a8920440e87b0220155d1c43dedb3fc2654205541bb2821dd5211180e2d7f93d67301470652830d401', + '03ec0f8b267f99ff5259195ce63813d58f38ffbaada894ce06af0c0303c74bbf82']}], + 'locktime': 1361, + 'outs': [{'script': '6a147631c805d8ad9239677b8d7530353fda3fec07ca', + 'value': 11999800}], + 'version': 2} + 2020-04-07 20:46:50,455 [INFO] In serialized form (for copy-paste): + 2020-04-07 20:46:50,455 [INFO] 02000000000101b08c049f25a9816fa53021147a493b9c256354b0abcba9a5f80ebe7a4e9bd6610000000017160014295beb4eba9b35896683d5b5ff455ee2c646054cfeffffff01381ab70000000000166a147631c805d8ad9239677b8d7530353fda3fec07ca02483045022100939de908e30015c6b22d2c0f25153e395268466ce44eeeb4ec03a8920440e87b0220155d1c43dedb3fc2654205541bb2821dd5211180e2d7f93d67301470652830d4012103ec0f8b267f99ff5259195ce63813d58f38ffbaada894ce06af0c0303c74bbf8251050000 + 2020-04-07 20:46:50,456 [INFO] Sends: 0.11999800 BTC (11999800 sat) to destination: BURNER OUTPUT embedding pubkey at m/49'/1'/0'/3/0 + + WARNING: This transaction if broadcasted will PERMANENTLY DESTROY your bitcoins + + Would you like to push to the network? (y/n):y + 2020-04-07 20:47:52,047 [DEBUG] rpc: sendrawtransaction ['02000000000101b08c049f25a9816fa53021147a493b9c256354b0abcba9a5f80ebe7a4e9bd6610000000017160014295beb4eba9b35896683d5b5ff455ee2c646054cfeffffff01381ab70000000000166a147631c805d8ad9239677b8d7530353fda3fec07ca02483045022100939de908e30015c6b22d2c0f25153e395268466ce44eeeb4ec03a8920440e87b0220155d1c43dedb3fc2654205541bb2821dd5211180e2d7f93d67301470652830d4012103ec0f8b267f99ff5259195ce63813d58f38ffbaada894ce06af0c0303c74bbf8251050000'] + 2020-04-07 20:47:52,049 [WARNING] Connection had broken pipe, attempting reconnect. + 2020-04-07 20:47:52,440 [INFO] Transaction sent: 656bb4538f14f2cc874043915907b6c9c46a807ef9818bde771d07630d54b0f7 + done + +Embedded in the `OP_RETURN` output is the hash of a pubkey from the wallet. + +Now `OP_RETURN` outputs are not addresses, and because of technical reasons the +first time they are synchronized the flag `--recoversync` must be used. When +this is done the burn output will appear in the `wallet-tool.py` display. +`--recoversync` only needs to be used once, and after that the burner output is +saved in the `wallet.jmdat` file which can be accesses by all future +synchronizations. + + $ python3 wallet-tool.py --datadir=. --recoversync testfidelity2.jmdat + Enter wallet decryption passphrase: + 2020-04-07 23:09:54,644 [INFO] Found a burner transaction txid=656bb4538f14f2cc874043915907b6c9c46a807ef9818bde771d07630d54b0f7 path = m/49'/1'/0'/3/0 + 2020-04-07 23:09:54,769 [WARNING] Merkle branch not available, use wallet-tool `addtxoutproof` + 2020-04-07 23:09:55,420 [INFO] Found a burner transaction txid=656bb4538f14f2cc874043915907b6c9c46a807ef9818bde771d07630d54b0f7 path = m/49'/1'/0'/3/0 + 2020-04-07 23:09:55,422 [WARNING] Merkle branch not available, use wallet-tool `addtxoutproof` + JM wallet + mixdepth 0 fbonds-mpk-tpubDDCXgSpdxuVbXgzRYBggFeMRNeV9eH24jJuQNunyqwYtDFiB7ZS63LhXwHkf7o9ZcZW4qUz7uvD6yk4BkkF3bBPmJRPv7RBTEA5hHwEdV2f + external addresses m/49'/1'/0'/0 tpubDEJGorVywRb6LoLQbaqWZh2gYwpdZqViCNZ2ejB5kpBuUp16LHpK6ESFaJLixidtbmmjcDwVZ4QjnAbKmypfuGaEk3ifgonPv4vsugqgp9G + m/49'/1'/0'/0/2 2MviB2FfLKZjFb3W2dJ8kXcQBj3jqMJg7TL 0.00000000 new + m/49'/1'/0'/0/3 2MtsAQhE2u9VGV3aZ7XM4PzwWGHXr4PAhqP 0.00000000 new + m/49'/1'/0'/0/4 2N3iXNjS4vkTXzy5Jidnovc6FJeNQJx5Fnt 0.00000000 new + m/49'/1'/0'/0/5 2MtT4XAjwQQ7PBbrTxv7qcQMMmz4Rs5XrE3 0.00000000 new + m/49'/1'/0'/0/6 2NEuG23BQESuZTqSDtab9zYsd1Jb4KfMULB 0.00000000 new + m/49'/1'/0'/0/7 2N6NGJRX6KYQWtYWK8iHFuJNpZRs8NbUAC9 0.00000000 new + Balance: 0.00000000 + internal addresses m/49'/1'/0'/1 + Balance: 0.00000000 + internal addresses m/49'/1'/0'/2 tpubDEJGorVywRb6T34X7ZAEz9hQYn6CCEhrcFa8kA2mqNau2DvoggZP2QTtXRe8t9NSfMkx3ye8QDzqCE9gEqso6fw5ALk5xycWLFwTRLSqSUV + Balance: 0.00000000 + internal addresses m/49'/1'/0'/3 tpubDEJGorVywRb6V5em9Q7LFJ9eLEAEZmZxUdDmkknrKNUs7vKcCWPKwP8YPjuxFCCXk2F1wJnubNbmgbtWed5yiE3D1qxzLonVuXT6QEZPaof + m/49'/1'/0'/3/0 BURN-7631c805d8ad9239677b8d7530353fda3fec07ca 0.11999800 656bb4538f14f2cc874043915907b6c9c46a807ef9818bde771d07630d54b0f7 [NO MERKLE PROOF] + Balance: 0.11999800 + Balance for mixdepth 0: 0.11999800 + Total balance: 0.11999800 + +#### Adding the merkle proof of a burn transaction if missing + +In order to prove a burn output exists, a merkle proof is needed. If the Core +node is pruned and the block deleted then JoinMarket will not be able to obtain +the merkle proof (as in the above example). In this case the proof can be +added separately. + +Any other unpruned Core node can trustlessly obtain the proof and give it to +the user with the RPC call `gettxoutproof`. + +First obtain the merkle proof: + + $ bitcoin-cli gettxoutproof "[\"656bb4538f14f2cc874043915907b6c9c46a807ef9818bde771d07630d54b0f7\"]" 4cce28f4eb1ea1762ec4ceb90529b3ab28c0423ac630c6292319e2b2712daada + 0000002056e4050f54084a1d6e6944b209cce76ebe2da4b37f3aa47ab6c612831d3220471015e80d3050cf0ee05b216036030fe3d4906221196943a30e828741ad4cfaeb05d98c5effff7f20010000000200000002a5910e5cf4e6cb6d55e1e2ca979987772a482e8a8f30b7a6cab8c5671f5c161df7b0540d63071d77de8b81f97e806ac4c9b6075991434087ccf2148f53b46b650105 + +Then add it to the JoinMarket wallet: + + $ python3 wallet-tool.py -H "m/49'/1'/0'/3/0" testfidelity2.jmdat addtxoutproof 0000002056e4050f54084a1d6e6944b209cce76ebe2da4b37f3aa47ab6c612831d3220471015e80d3050cf0ee05b216036030fe3d4906221196943a30e828741ad4cfaeb05d98c5effff7f20010000000200000002a5910e5cf4e6cb6d55e1e2ca979987772a482e8a8f30b7a6cab8c5671f5c161df7b0540d63071d77de8b81f97e806ac4c9b6075991434087ccf2148f53b46b650105 + Enter wallet decryption passphrase: + Done + +The `-H` flag must point to the path containing the burn output. + +Then synchronizing the wallet won't output the no-merkle-proof warning. + +### Creating watch-only fidelity bond wallets + +Fidelity bonds can be held on an offline computer in +[cold storage](https://en.bitcoin.it/wiki/Cold_storage). To do this we create +a watch-only fidelity bond wallet. + +When fidelity bonds are displayed in `wallet-tool.py`, their master public key +is highlighted with a prefix `fbonds-mpk-`. + +This master public key can be used to create a watch-only wallet using +`wallet-tool.py`. + + $ python3 wallet-tool.py createwatchonly fbonds-mpk-tpubDDCbCPdf5wJVGYWB4mZr3E3Lys4NBcEKysrrUrLfhG6sekmrvs6KZNe4i5p5z3FyfwRmKMqB9NWEcEUiTS4LwqfrKPQzhKj6aLihu2EejaU + Input wallet file name (default: watchonly.jmdat): watchfidelity.jmdat + Enter wallet file encryption passphrase: + Reenter wallet file encryption passphrase: + Done + +Then the wallet can be displayed like a regular wallet, although only the zeroth +mixdepth will be shown. + + $ python3 wallet-tool.py watchfidelity.jmdat + User data location: . + Enter wallet decryption passphrase: + JM wallet + mixdepth 0 fbonds-mpk-tpubDDCbCPdf5wJVGYWB4mZr3E3Lys4NBcEKysrrUrLfhG6sekmrvs6KZNe4i5p5z3FyfwRmKMqB9NWEcEUiTS4LwqfrKPQzhKj6aLihu2EejaU + external addresses m/49'/1'/0'/0 tpubDEGdmPwmQRcZmGKhaudjch9Fgw4J5yP4bYw5B8LoSDkMdmhBxM4ndEQXHK4r1TPexGjLidxdpeEzsJcdXEe7khWToxCZuN6JiLzvUoHAki2 + m/49'/1'/0'/0/0 2N8jHuQaApgFtQ8UKxKbREAvNxKn4BGX4x2 0.00000000 used + m/49'/1'/0'/0/1 2Mx5CwDoNcuCT38EDmgenQxv9skHbZfXFdo 0.00000000 new + m/49'/1'/0'/0/2 2N1tNTTwNyucGGmfDWNVk3AUi3i5S8jVKqn 0.00000000 new + m/49'/1'/0'/0/3 2N8eBEU5wpWb6kS1gvbRgewtxsmXsMkShV6 0.00000000 new + m/49'/1'/0'/0/4 2MuHgeSgMsvkcn6aGNW2uk2UXP3xVVnkfh2 0.00000000 new + m/49'/1'/0'/0/5 2NA8d8um5KmBNNR8dadhbEDYGiTJPFCdjMB 0.00000000 new + m/49'/1'/0'/0/6 2NG76BAHPccfyy6sH68EHrB9QJBycx3FKb6 0.00000000 new + Balance: 0.25000000 + internal addresses m/49'/1'/0'/1 + Balance: 0.00000000 + internal addresses m/49'/1'/0'/2 tpubDEGdmPwmQRcZrzjRmUFqXXyLdRedwxCWQviAFqDe6sXJeZzRNTwmwqMfxN6Ka3v7hEebstrU5kqUNoHsFKaA3RoB2vopL6kLHVo1EQq6USw + m/49'/1'/0'/0/3:1585699200 bcrt1qrc2qu3m2l2spayu5kr0k0rnn9xgjz46zsxmruh87a3h3f5zmnkaqlfx7v5 0.15000000 2020-04-01 [UNLOCKED] + Balance: 0.15000000 + internal addresses m/49'/1'/0'/3 tpubDEGdmPwmQRcZuX3uNrCouu5bRgp2GJcoQTvhkFAJMTA3yxhKmQyeGwecbnkms4DYmBhCJn2fGTuejTe3g8oyJW3qKcfB4b3Swj2hDk1h4Y2 + Balance: 0.00000000 + Balance for mixdepth 0: 0.15000000 + Total balance: 0.15000000 + +### BIP32 Paths + +Fidelity bond wallets extend the BIP32 path format to include the locktime +values. In this example we've got `m/49'/1'/0'/2/0:1583020800` where the +number after the colon is the locktime value in Unix time. + +This path can be passed to certain wallet methods like `dumpprivkey`. + + $ python3 wallet-tool.py -H "m/49'/1'/0'/2/0:1583020800" testfidelity.jmdat dumpprivkey + Enter wallet decryption passphrase: + cNEuE5ypNTxVFCyC5iH7u5AQTrddamcUHRPNweiLvmHUWd6XXDkz +