Merge #164: Add JoinMarket Clientserver

dd882753e6 joinmarket: add usage documentation (nixbitcoin)
d0701f518c joinmarket: automatically generate wallet (nixbitcoin)
d6d3e8ff62 joinmarket: add tests (nixbitcoin)
cce27da2ec backups: add joinmarket datadir to includelist (nixbitcoin)
173891fa5b joinmarket: add module (nixbitcoin)
263525d724 nix-bitcoin-services: add nb-services.privileged helper (nixbitcoin)
f00d1d24c5 joinmarket: add pkg and local dependencies (nixbitcoin)

Pull request description:

ACKs for top commit:
  jonasnick:
    ACK dd882753e6

Tree-SHA512: ad7bf56314877045bc8dc6037f966535dc3607d9e941cd03d19b610ee789307eac07447df7f93569dfa3e7553e8fc6d9757bdf8542fb123c5a2e2adec8f907a2
This commit is contained in:
Jonas Nick 2020-09-22 17:16:01 +00:00
commit 4cf31f8612
No known key found for this signature in database
GPG Key ID: 4861DBF262123605
29 changed files with 764 additions and 4 deletions

View File

@ -34,6 +34,8 @@ env:
- PKG=liquid-swap STABLE=1
- PKG=lightning-loop STABLE=0
- PKG=nixops19_09 STABLE=1
- PKG=joinmarket STABLE=1
- PKG=joinmarket STABLE=0
script:
- printf '%s (%s)\n' "$NIX_PATH" "$VER"
- |

View File

@ -221,3 +221,120 @@ Initialize a Trezor for Bitcoin Core's Hardware Wallet Interface
```
8. Follow Bitcoin Core's instructions on [Using Bitcoin Core with Hardware Wallets](https://github.com/bitcoin-core/HWI/blob/master/docs/bitcoin-core-usage.md) to use your Trezor with `bitcoin-cli` on your nix-bitcoin node
JoinMarket
---
## Diff to regular JoinMarket usage
For clarity reasons, nix-bitcoin renames all scripts to `jm-*` without `.py`, for
example `wallet-tool.py` becomes `jm-wallet-tool`. The rest of this section
details nix-bitcoin specific workflows for JoinMarket.
## Initialize JoinMarket Wallet
By default, nix-bitcoin's JoinMarket module automatically generates a wallet for
you. If however, you want to manually initialize your wallet, follow these steps.
1. Enable JoinMarket in your node configuration
```
services.joinmarket.enable = true;
```
2. Move the automatically generated `wallet.jmdat`
```console
rm /var/lib/joinmarket/wallet.jmdat /var/lib/joinmarket/bak.jmdat
```
3. Generate wallet on your node
```console
jm-wallet-tool generate
```
Follow the on-screen instructions and write down your seed.
In order to use nix-bitcoin's `joinmarket.yieldgenerator`, use the password
from `/secrets/jm-wallet-password` and use the suggested default wallet name
`wallet.jmdat`. If you want to use your own `jm-wallet-password`, simply
replace the password string in your local secrets directory.
## Run the tumbler
The tumbler needs to be able to run in the background for a long time, use screen
to run it accross ssh sessions. You can also use tmux in the same fashion.
1. Add screen to your `environment.systemPackages`, for example
```
environment.systemPackages = with pkgs; [
vim
screen
];
```
2. Start the screen session
```console
screen -S "tumbler"
```
2. Start the tumbler
Example: Tumbling into your wallet after buying from an exchange to improve privacy:
```console
jm-tumbler wallet.jmdat <addr1> <addr2> <addr3>
```
After tumbling your bitcoin end up in these three addresses. You can now
spend them without the exchange collecting data on your purchases.
Get more information [here](https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/docs/tumblerguide.md)
3. Detach the screen session to leave the tumbler running in the background
```
Ctrl-a d or Ctrl-a Ctrl-d
```
4. Re-attach to the screen session
```console
screen -r tumbler
```
5. End screen session
Type exit when tumbler is done
```console
exit
```
## Run a "maker" or "yield generator"
The maker/yield generator in nix-bitcoin is implemented using a systemd service.
See [here](https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/docs/YIELDGENERATOR.md) for more yield generator information.
1. Enable yield generator bot in your node configuration
```
services.joinmarket.yieldgenerator.enable = true;
# Optional: Add custom parameters
services.joinmarket.yieldgenerator.customParameters = ''
txfee = 200
cjfee_a = 300
'';
```
2. Check service status
```console
systemctl status joinmarket-yieldgenerator
```
3. Profit

View File

@ -172,6 +172,16 @@
# and electrs data directory, enable
# services.backups.with-bulk-data = true;
### JOINMARKET
# Enable this module to allow using JoinMarket's user interactive scripts (including
# tumbler.py).
# Note: JoinMarket has full access to bitcoind, including its wallet functionality.
# services.joinmarket.enable = true;
# Enable this option to enable the JoinMarket Yield Generator Bot. You will be able to
# earn sats by providing CoinJoin liquidity. This makes it impossible to use other
# scripts that access your wallet.
# services.joinmarket.yieldgenerator.enable = true;
# FIXME: Define your hostname.
networking.hostName = "nix-bitcoin";
time.timeZone = "UTC";

View File

@ -18,6 +18,8 @@ let
${config.services.lightning-charge.dataDir}
${config.services.nbxplorer.dataDir}
${config.services.btcpayserver.dataDir}
${config.services.joinmarket.dataDir}
/secrets/jm-wallet-seed
/var/lib/tor
# Extra files
${cfg.extraFiles}

View File

@ -12,4 +12,5 @@
spark-wallet = ./spark-wallet.nix;
recurring-donations = ./recurring-donations.nix;
lnd = ./lnd.nix;
joinmarket = ./joinmarket.nix;
}

219
modules/joinmarket.nix Normal file
View File

@ -0,0 +1,219 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.joinmarket;
inherit (config) nix-bitcoin-services;
secretsDir = config.nix-bitcoin.secretsDir;
torAddress = builtins.head (builtins.split ":" config.services.tor.client.socksListenAddress);
configFile = builtins.toFile "config" ''
# Based on https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/jmclient/jmclient/configure.py
[DAEMON]
no_daemon = 0
daemon_port = 27183
daemon_host = localhost
use_ssl = false
[BLOCKCHAIN]
blockchain_source = bitcoin-rpc
network = mainnet
rpc_host = ${builtins.elemAt config.services.bitcoind.rpcbind 0}
rpc_port = 8332
rpc_user = ${config.services.bitcoind.rpc.users.privileged.name}
@@RPC_PASSWORD@@
[MESSAGING:server1]
host = darksci3bfoka7tw.onion
channel = joinmarket-pit
port = 6697
usessl = true
socks5 = true
socks5_host = ${torAddress}
socks5_port = 9050
[MESSAGING:server2]
host = ncwkrwxpq2ikcngxq3dy2xctuheniggtqeibvgofixpzvrwpa77tozqd.onion
channel = joinmarket-pit
port = 6667
usessl = false
socks5 = true
socks5_host = ${torAddress}
socks5_port = 9050
[LOGGING]
console_log_level = INFO
color = false
[POLICY]
segwit = true
native = false
merge_algorithm = default
tx_fees = 3
absurd_fee_per_kb = 350000
tx_broadcast = self
minimum_makers = 4
max_sats_freeze_reuse = -1
taker_utxo_retries = 3
taker_utxo_age = 5
taker_utxo_amtpercent = 20
accept_commitment_broadcasts = 1
commit_file_location = cmtdata/commitments.json
'';
# The jm scripts create a 'logs' dir in the working dir,
# so run them inside dataDir.
cli = pkgs.runCommand "joinmarket-cli" {} ''
mkdir -p $out/bin
jm=${pkgs.nix-bitcoin.joinmarket}/bin
cd $jm
for bin in jm-*; do
{
echo "#!${pkgs.bash}/bin/bash";
echo "cd '${cfg.dataDir}' && ${cfg.cliExec} sudo -u ${cfg.user} $jm/$bin --datadir='${cfg.dataDir}' \"\$@\"";
} > $out/bin/$bin
done
chmod -R +x $out/bin
'';
in {
options.services.joinmarket = {
enable = mkEnableOption "JoinMarket";
yieldgenerator = {
enable = mkEnableOption "yield generator bot";
customParameters = mkOption {
type = types.str;
default = "";
example = ''
txfee = 200
cjfee_a = 300
'';
description = ''
Python code to define custom yield generator parameters, as described in
https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/docs/YIELDGENERATOR.md
'';
};
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/joinmarket";
description = "The data directory for JoinMarket.";
};
user = mkOption {
type = types.str;
default = "joinmarket";
description = "The user as which to run JoinMarket.";
};
group = mkOption {
type = types.str;
default = cfg.user;
description = "The group as which to run JoinMarket.";
};
cli = mkOption {
default = cli;
};
inherit (nix-bitcoin-services) cliExec;
};
config = mkIf cfg.enable (mkMerge [{
environment.systemPackages = [
(hiPrio cfg.cli)
];
users.users.${cfg.user} = {
description = "joinmarket User";
group = "${cfg.group}";
home = cfg.dataDir;
};
users.groups.${cfg.group} = {};
systemd.tmpfiles.rules = [
"d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -"
];
services.bitcoind.disablewallet = false;
# Joinmarket is TOR-only
services.tor = {
enable = true;
client.enable = true;
};
systemd.services.joinmarket = {
description = "JoinMarket Daemon";
wantedBy = [ "multi-user.target" ];
requires = [ "bitcoind.service" ];
after = [ "bitcoind.service" ];
path = [ pkgs.sudo ];
serviceConfig = nix-bitcoin-services.defaultHardening // {
ExecStartPre = nix-bitcoin-services.privileged ''
install -o '${cfg.user}' -g '${cfg.group}' -m 640 ${configFile} ${cfg.dataDir}/joinmarket.cfg
sed -i \
"s|@@RPC_PASSWORD@@|rpc_password = $(cat ${secretsDir}/bitcoin-rpcpassword-privileged)|" \
'${cfg.dataDir}/joinmarket.cfg'
'';
ExecStartPost = nix-bitcoin-services.privileged ''
walletname=wallet.jmdat
pw=$(cat "${secretsDir}"/jm-wallet-password)
mnemonic=${secretsDir}/jm-wallet-seed
if [[ ! -f ${cfg.dataDir}/wallets/$walletname ]]; then
echo Create joinmarket wallet
# Use bash variables so commands don't proceed on previous failures
# (like with pipes)
cd ${cfg.dataDir} && \
out=$(sudo -u ${cfg.user} \
${pkgs.nix-bitcoin.joinmarket}/bin/jm-genwallet \
--datadir=${cfg.dataDir} $walletname $pw)
recoveryseed=$(echo "$out" | grep 'recovery_seed')
echo "$recoveryseed" | cut -d ':' -f2 > $mnemonic
fi
'';
ExecStart = "${pkgs.nix-bitcoin.joinmarket}/bin/joinmarketd";
WorkingDirectory = "${cfg.dataDir}"; # The service creates 'commitmentlist' in the working dir
User = "${cfg.user}";
Restart = "on-failure";
RestartSec = "10s";
ReadWritePaths = "${cfg.dataDir}";
} // nix-bitcoin-services.allowTor;
};
}
(mkIf cfg.yieldgenerator.enable {
nix-bitcoin.secrets.jm-wallet-password.user = cfg.user;
systemd.services.joinmarket-yieldgenerator = let
ygDefault = "${pkgs.nix-bitcoin.joinmarket}/bin/jm-yg-privacyenhanced";
ygBinary = if cfg.yieldgenerator.customParameters == "" then
ygDefault
else
pkgs.runCommand "jm-yieldgenerator-custom" {
inherit (cfg.yieldgenerator) customParameters;
} ''
substitute ${ygDefault} $out \
--replace "# end of settings customization" "$customParameters"
chmod +x $out
'';
in {
description = "CoinJoin maker bot to gain privacy and passively generate income";
wantedBy = [ "joinmarket.service" ];
requires = [ "joinmarket.service" ];
after = [ "joinmarket.service" ];
preStart = let
start = ''
exec ${ygBinary} --datadir='${cfg.dataDir}' --wallet-password-stdin wallet.jmdat
'';
in ''
pw=$(cat "${secretsDir}"/jm-wallet-password)
echo "echo -n $pw | ${start}" > $RUNTIME_DIRECTORY/start
'';
serviceConfig = nix-bitcoin-services.defaultHardening // rec {
RuntimeDirectory = "joinmarket-yieldgenerator"; # Only used to create start script
RuntimeDirectoryMode = "700";
WorkingDirectory = "${cfg.dataDir}"; # The service creates dir 'logs' in the working dir
ExecStart = "${pkgs.bash}/bin/bash /run/${RuntimeDirectory}/start";
User = "${cfg.user}";
ReadWritePaths = "${cfg.dataDir}";
} // nix-bitcoin-services.allowTor;
};
})
]);
}

View File

@ -19,6 +19,7 @@
./security.nix
./backups.nix
./btcpayserver.nix
./joinmarket.nix
];
disabledModules = [ "services/networking/bitcoind.nix" ];

View File

@ -131,6 +131,7 @@ in {
${ip} link del nb-br
'';
};
} //
(let
makeNetnsServices = n: v: let
@ -242,6 +243,10 @@ in {
++ optional (config.services.btcpayserver.lightningBackend == "lnd") "lnd";
# communicates with clightning over rpc socket
};
joinmarket = {
id = 25;
connections = [ "bitcoind" ];
};
};
services.bitcoind = {
@ -314,6 +319,9 @@ in {
services.nbxplorer.bind = netns.nbxplorer.address;
services.btcpayserver.bind = netns.btcpayserver.address;
services.joinmarket.cliExec = mkCliExec "joinmarket";
systemd.services.joinmarket-yieldgenerator.serviceConfig.NetworkNamespacePath = "/var/run/netns/nb-joinmarket";
}
]);
}

View File

@ -4,7 +4,7 @@
lib: pkgs:
with lib;
{
let self = {
# These settings roughly follow systemd's "strict" security profile
defaultHardening = {
PrivateTmp = "true";
@ -56,10 +56,13 @@ with lib;
${src}
'';
# Used for ExecStart*
privileged = src: "+${self.script src}";
cliExec = mkOption {
# Used by netns-isolation to execute the cli in the service's private netns
internal = true;
type = types.str;
default = "exec";
};
}
}; in self

View File

@ -171,7 +171,8 @@ in {
++ (optionals cfg.lnd.enable [ "lnd" ])
++ (optionals cfg.liquidd.enable [ cfg.liquidd.group ])
++ (optionals (cfg.hardware-wallets.ledger || cfg.hardware-wallets.trezor)
[ cfg.hardware-wallets.group ]);
[ cfg.hardware-wallets.group ])
++ (optionals cfg.joinmarket.enable [ cfg.joinmarket.group ]);
openssh.authorizedKeys.keys = config.users.users.root.openssh.authorizedKeys.keys;
};
nix-bitcoin.netns-isolation.allowedUser = operatorName;
@ -182,6 +183,9 @@ in {
security.sudo.configFile =
(optionalString cfg.lnd.enable ''
${operatorName} ALL=(lnd) NOPASSWD: ALL
'') +
(optionalString cfg.joinmarket.enable ''
${operatorName} ALL=(${cfg.joinmarket.user}) NOPASSWD: ALL
'');
# Enable nixops ssh for operator (`nixops ssh operator@mynode`) on nixops-vbox deployments

View File

@ -8,6 +8,7 @@ let self = {
hwi = pkgs.callPackage ./hwi { };
pylightning = pkgs.python3Packages.callPackage ./pylightning { };
liquid-swap = pkgs.python3Packages.callPackage ./liquid-swap { };
joinmarket = pkgs.callPackage ./joinmarket { };
generate-secrets = pkgs.callPackage ./generate-secrets { };
nixops19_09 = pkgs.callPackage ./nixops { };
netns-exec = pkgs.callPackage ./netns-exec { };

View File

@ -18,6 +18,7 @@ makePasswordSecret liquid-rpcpassword
makePasswordSecret lightning-charge-token
makePasswordSecret spark-wallet-password
makePasswordSecret backup-encryption-password
makePasswordSecret jm-wallet-password
[[ -e bitcoin-HMAC-privileged ]] || makeHMAC privileged
[[ -e bitcoin-HMAC-public ]] || makeHMAC public

View File

@ -0,0 +1,22 @@
{ lib, buildPythonPackage, fetchurl, cython, pytest, coverage }:
buildPythonPackage rec {
pname = "bencoder.pyx";
version = "2.0.1";
src = fetchurl {
url = "https://github.com/whtsky/bencoder.pyx/archive/v${version}.tar.gz";
sha256 = "f3ff92ac706a7e4692bed5e6cbe205963327f3076f55e408eb948659923eac72";
};
nativeBuildInputs = [ cython ];
checkInputs = [ pytest coverage ];
meta = with lib; {
description = "A fast bencode implementation in Cython";
homepage = "https://github.com/whtsky/bencoder.pyx";
maintainers = with maintainers; [ nixbitcoin ];
license = licenses.bsd3;
};
}

View File

@ -0,0 +1,24 @@
{ lib, buildPythonPackage, fetchFromGitHub, colorama, future, six }:
buildPythonPackage rec {
pname = "chromalog";
version = "1.0.5";
src = fetchFromGitHub {
owner = "freelan-developers";
repo = "chromalog";
rev = "${version}";
sha256 = "0pj4s52rgwlvwkzrj85y92c5r9c84pz8gga45jl5spysrv41y9p0";
};
propagatedBuildInputs = [ colorama future six ];
# enable when https://github.com/freelan-developers/chromalog/issues/6 is resolved
doCheck = false;
meta = with lib; {
description = "Enhance Python with colored logging";
homepage = "https://github.com/freelan-developers/chromalog";
maintainers = with maintainers; [ nixbitcoin ];
license = licenses.mit;
};
}

View File

@ -0,0 +1,25 @@
{ lib, buildPythonPackage, fetchPypi, asn1crypto, cffi, pkg-config, libtool, libffi, requests, gmp }:
buildPythonPackage rec {
pname = "coincurve";
version = "13.0.0";
src = fetchPypi {
inherit pname version;
sha256 = "1x8dpbq6bwswfyi1g4r421hnswp904l435rf7n6fj7y8q1yn51cr";
};
nativeBuildInputs = [ pkg-config libtool libffi gmp ];
propagatedBuildInputs = [ asn1crypto cffi requests ];
# enable when https://github.com/ofek/coincurve/issues/47 is resolved
doCheck = false;
meta = with lib; {
description = "Cross-platform Python CFFI bindings for libsecp256k1";
homepage = "https://github.com/ofek/coincurve";
maintainers = with maintainers; [ nixbitcoin ];
license = licenses.asl20;
};
}

View File

@ -0,0 +1,71 @@
{ stdenv, fetchurl, python3, pkgs }:
let
version = "0.7.0";
src = fetchurl {
url = "https://github.com/JoinMarket-Org/joinmarket-clientserver/archive/v${version}.tar.gz";
sha256 = "0ha73n3y5lykyj3pl97a619sxd2zz0lb32s5c61wm0l1h47v9l1g";
};
python = python3.override {
packageOverrides = self: super: let
joinmarketPkg = pkg: self.callPackage pkg { inherit version src; };
in {
joinmarketbase = joinmarketPkg ./jmbase;
joinmarketclient = joinmarketPkg ./jmclient;
joinmarketbitcoin = joinmarketPkg ./jmbitcoin;
joinmarketdaemon = joinmarketPkg ./jmdaemon;
chromalog = self.callPackage ./chromalog {};
bencoderpyx = self.callPackage ./bencoderpyx {};
coincurve = self.callPackage ./coincurve {};
urldecode = self.callPackage ./urldecode {};
python-bitcointx = self.callPackage ./python-bitcointx {};
secp256k1 = self.callPackage ./secp256k1 {};
};
};
runtimePackages = with python.pkgs; [
joinmarketbase
joinmarketclient
joinmarketbitcoin
joinmarketdaemon
];
genwallet = pkgs.writeScriptBin "genwallet" (builtins.readFile ./genwallet/genwallet.py);
pythonEnv = python.withPackages (_: runtimePackages);
in
stdenv.mkDerivation {
pname = "joinmarket";
inherit version src genwallet;
buildInputs = [ pythonEnv ];
buildCommand = ''
mkdir -p $src-unpacked $out/bin
tar xzf $src --strip 1 -C $src-unpacked
# add-utxo.py -> bin/jm-add-utxo
cpBin() {
cp $src-unpacked/scripts/$1 $out/bin/jm-''${1%.py}
}
cp $src-unpacked/scripts/joinmarketd.py $out/bin/joinmarketd
cpBin add-utxo.py
cpBin convert_old_wallet.py
cpBin receive-payjoin.py
cpBin sendpayment.py
cpBin sendtomany.py
cpBin tumbler.py
cpBin wallet-tool.py
cpBin yg-privacyenhanced.py
cp $genwallet/bin/genwallet $out/bin/jm-genwallet
chmod +x -R $out/bin
patchShebangs $out/bin
'';
passthru = {
inherit python runtimePackages pythonEnv;
};
}

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
"""
Prototype: demonstrate you can automatically generate a wallet
"""
import sys
import os
from optparse import OptionParser
from jmclient import load_program_config, add_base_options, SegwitLegacyWallet, create_wallet, jm_single
from jmbase.support import get_log, jmprint
log = get_log()
def main():
parser = OptionParser(
usage='usage: %prog [options] wallet_file_name password',
description='Create a wallet with the given wallet name and password.')
add_base_options(parser)
(options, args) = parser.parse_args()
if options.wallet_password_stdin:
stdin = sys.stdin.read()
password = stdin.encode("utf-8")
else:
assert len(args) > 1, "must provide password via stdin (see --help), or as second argument."
password = args[1].encode("utf-8")
load_program_config(config_path=options.datadir)
wallet_root_path = os.path.join(jm_single().datadir, "wallets")
wallet_name = os.path.join(wallet_root_path, args[0])
wallet = create_wallet(wallet_name, password, 4, SegwitLegacyWallet)
jmprint("recovery_seed:{}"
.format(wallet.get_mnemonic_words()[0]), "important")
wallet.close()
if __name__ == "__main__":
main()

25
pkgs/joinmarket/get-sha256.sh Executable file
View File

@ -0,0 +1,25 @@
#! /usr/bin/env nix-shell
#! nix-shell -i bash -p git gnupg
set -euo pipefail
TMPDIR="$(mktemp -d -p /tmp)"
trap "rm -rf $TMPDIR" EXIT
cd $TMPDIR
echo "Fetching latest release"
git clone https://github.com/joinmarket-org/joinmarket-clientserver 2> /dev/null
cd joinmarket-clientserver
latest=$(git describe --tags `git rev-list --tags --max-count=1`)
echo "Latest release is ${latest}"
# GPG verification
export GNUPGHOME=$TMPDIR
echo "Fetching Adam Gibson's key"
gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys 2B6FC204D9BF332D062B461A141001A1AF77F20B 2> /dev/null
echo "Verifying latest release"
git verify-tag ${latest}
echo "tag: ${latest}"
# The prefix option is necessary because GitHub prefixes the archive contents in this format
echo "sha256: $(nix-hash --type sha256 --flat --base32 \
<(git archive --format tar.gz --prefix=joinmarket-clientserver-"${latest//v}"/ ${latest}))"

View File

@ -0,0 +1,16 @@
{ version, src, lib, buildPythonPackage, fetchurl, future, twisted, service-identity, chromalog }:
buildPythonPackage rec {
pname = "joinmarketbase";
inherit version src;
postUnpack = "sourceRoot=$sourceRoot/jmbase";
propagatedBuildInputs = [ future twisted service-identity chromalog ];
meta = with lib; {
homepage = "https://github.com/Joinmarket-Org/joinmarket-clientserver";
maintainers = with maintainers; [ nixbitcoin ];
license = licenses.gpl3;
};
}

View File

@ -0,0 +1,18 @@
{ version, src, lib, buildPythonPackage, fetchurl, future, coincurve, urldecode, pyaes, python-bitcointx, secp256k1, joinmarketbase }:
buildPythonPackage rec {
pname = "joinmarketbitcoin";
inherit version src;
postUnpack = "sourceRoot=$sourceRoot/jmbitcoin";
propagatedBuildInputs = [ future coincurve urldecode pyaes python-bitcointx secp256k1 ];
checkInputs = [ joinmarketbase ];
meta = with lib; {
homepage = "https://github.com/Joinmarket-Org/joinmarket-clientserver";
maintainers = with maintainers; [ nixbitcoin ];
license = licenses.gpl3;
};
}

View File

@ -0,0 +1,20 @@
{ version, src, lib, buildPythonPackage, fetchurl, future, configparser, joinmarketbase, mnemonic, argon2_cffi, bencoderpyx, pyaes, joinmarketbitcoin, txtorcon }:
buildPythonPackage rec {
pname = "joinmarketclient";
inherit version src;
postUnpack = "sourceRoot=$sourceRoot/jmclient";
checkInputs = [ joinmarketbitcoin txtorcon ];
# configparser may need to be compiled with python_version<"3.2"
propagatedBuildInputs = [ future configparser joinmarketbase mnemonic argon2_cffi bencoderpyx pyaes ];
meta = with lib; {
description = "Client library for Bitcoin coinjoins";
homepage = "https://github.com/Joinmarket-Org/joinmarket-clientserver";
maintainers = with maintainers; [ nixbitcoin ];
license = licenses.gpl3;
};
}

View File

@ -0,0 +1,17 @@
{ version, src, lib, buildPythonPackage, fetchurl, future, txtorcon, pyopenssl, libnacl, joinmarketbase }:
buildPythonPackage rec {
pname = "joinmarketdaemon";
inherit version src;
postUnpack = "sourceRoot=$sourceRoot/jmdaemon";
propagatedBuildInputs = [ future txtorcon pyopenssl libnacl joinmarketbase ];
meta = with lib; {
description = "Client library for Bitcoin coinjoins";
homepage = "https://github.com/Joinmarket-Org/joinmarket-clientserver";
maintainers = with maintainers; [ nixbitcoin ];
license = licenses.gpl3;
};
}

View File

@ -0,0 +1,27 @@
{ lib, buildPythonPackage, fetchurl, secp256k1, openssl }:
buildPythonPackage rec {
pname = "python-bitcointx";
version = "1.1.1";
src = fetchurl {
url = "https://github.com/Simplexum/${pname}/archive/${pname}-v${version}.tar.gz";
sha256 = "35edd694473517508367338888633954eaa91b2622b3caada8fd3030ddcacba2";
};
patchPhase = ''
for path in core/secp256k1.py tests/test_load_secp256k1.py; do
substituteInPlace "bitcointx/$path" \
--replace "ctypes.util.find_library('secp256k1')" "'${secp256k1}/lib/libsecp256k1.so'"
done
substituteInPlace bitcointx/core/key.py \
--replace "ctypes.util.find_library('ssl')" "'${openssl.out}/lib/libssl.so'"
'';
meta = with lib; {
description = "Interface to Bitcoin transaction data structures";
homepage = "https://github.com/Simplexum/python-bitcointx";
maintainers = with maintainers; [ nixbitcoin ];
license = licenses.gpl3;
};
}

View File

@ -0,0 +1,24 @@
#! /usr/bin/env nix-shell
#! nix-shell -i bash -p git gnupg
set -euo pipefail
TMPDIR="$(mktemp -d -p /tmp)"
trap "rm -rf $TMPDIR" EXIT
cd $TMPDIR
echo "Fetching latest release"
git clone https://github.com/simplexum/python-bitcointx 2> /dev/null
cd python-bitcointx
latest=$(git describe --tags `git rev-list --tags --max-count=1`)
echo "Latest release is ${latest}"
# GPG verification
export GNUPGHOME=$TMPDIR
echo "Fetching Dimitry Pethukov's Key"
gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys B17A35BBA187395784E2A6B32301D26BDC15160D 2> /dev/null
echo "Verifying latest release"
git verify-commit ${latest}
echo "tag: ${latest}"
# The prefix option is necessary because GitHub prefixes the archive contents in this format
echo "sha256: $(git archive --format tar.gz --prefix=python-bitcointx-"${latest}"/ ${latest} | sha256sum | cut -d\ -f1)"

View File

@ -0,0 +1,28 @@
{ stdenv, fetchFromGitHub, autoreconfHook }:
let inherit (stdenv.lib) optionals; in
stdenv.mkDerivation {
pname = "secp256k1";
version = "2019-10-11";
src = fetchFromGitHub {
owner = "bitcoin-core";
repo = "secp256k1";
rev = "0d9540b13ffcd7cd44cc361b8744b93d88aa76ba";
sha256 = "05zwhv8ffzrfdzqbsb4zm4kjdbjxqy5jh9r83fic0qpk2mkvc2i2";
};
nativeBuildInputs = [ autoreconfHook ];
configureFlags = ["--enable-module-recovery" "--disable-jni" "--enable-experimental" "--enable-module-ecdh" "--enable-benchmark=no" ];
meta = with stdenv.lib; {
description = "Optimized C library for EC operations on curve secp256k1";
homepage = "https://github.com/bitcoin-core/secp256k1";
license = with licenses; [ mit ];
maintainers = with maintainers; [ nixbitcoin ];
platforms = with platforms; unix;
};
}

View File

@ -0,0 +1,16 @@
{ lib, buildPythonPackage, fetchPypi }:
buildPythonPackage rec {
pname = "urldecode";
version = "0.1";
src = fetchPypi {
inherit pname version;
sha256 = "0w8my7kdwxppsfzzi1b2cxhypm6r1fsrnb2hnd752axq4gfsddjj";
};
meta = with lib; {
description = "A simple function to decode an encoded url";
homepage = "https://github.com/jennyq/urldecode";
maintainers = with maintainers; [ nixbitcoin ];
};
}

View File

@ -13,7 +13,8 @@ static char *allowed_netns[] = {
"nb-lnd",
"nb-lightning-loop",
"nb-bitcoind",
"nb-liquidd"
"nb-liquidd",
"nb-joinmarket"
};
int is_netns_allowed(char *netns) {

View File

@ -102,6 +102,14 @@ def run_tests(extra_tests):
assert_running("onion-chef")
assert_running("joinmarket")
machine.wait_until_succeeds(
log_has_string("joinmarket", "P2EPDaemonServerProtocolFactory starting on 27184")
)
machine.wait_until_succeeds(
log_has_string("joinmarket-yieldgenerator", "Failure to get blockheight",)
)
# FIXME: use 'wait_for_unit' because 'create-web-index' always fails during startup due
# to incomplete unit dependencies.
# 'create-web-index' implicitly tests 'nodeinfo'.
@ -150,6 +158,10 @@ def run_tests(extra_tests):
"export $(cat /secrets/backup-encryption-env); duplicity list-current-files 'file:///var/lib/localBackups'",
"secrets/lnd-seed-mnemonic",
)
assert_matches(
"export $(cat /secrets/backup-encryption-env); duplicity list-current-files 'file:///var/lib/localBackups'",
"secrets/jm-wallet-seed",
)
assert_matches(
"export $(cat /secrets/backup-encryption-env); duplicity list-current-files 'file:///var/lib/localBackups'",
"var/lib/bitcoind/wallet.dat",

View File

@ -56,6 +56,15 @@ import ./make-test.nix rec {
services.btcpayserver.lightningBackend = "lnd";
# needed to test macaroon creation
environment.systemPackages = with pkgs; [ openssl xxd ];
services.joinmarket.enable = true;
services.joinmarket.yieldgenerator = {
enable = true;
customParameters = ''
txfee = 200
cjfee_a = 300
'';
};
# to test that unused secrets are made inaccessible by 'setup-secrets'
systemd.services.generate-secrets.postStart = ''