From f00d1d24c57c482eabb6feb6cd42a318f6a73587 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Mon, 5 Aug 2019 09:50:55 +0200 Subject: [PATCH 1/7] joinmarket: add pkg and local dependencies --- pkgs/default.nix | 1 + pkgs/joinmarket/bencoderpyx/default.nix | 22 ++++++ pkgs/joinmarket/chromalog/default.nix | 24 +++++++ pkgs/joinmarket/coincurve/default.nix | 25 +++++++ pkgs/joinmarket/default.nix | 68 +++++++++++++++++++ pkgs/joinmarket/get-sha256.sh | 25 +++++++ pkgs/joinmarket/jmbase/default.nix | 16 +++++ pkgs/joinmarket/jmbitcoin/default.nix | 18 +++++ pkgs/joinmarket/jmclient/default.nix | 20 ++++++ pkgs/joinmarket/jmdaemon/default.nix | 17 +++++ pkgs/joinmarket/python-bitcointx/default.nix | 27 ++++++++ .../joinmarket/python-bitcointx/get-sha256.sh | 24 +++++++ pkgs/joinmarket/secp256k1/default.nix | 28 ++++++++ pkgs/joinmarket/urldecode/default.nix | 16 +++++ 14 files changed, 331 insertions(+) create mode 100644 pkgs/joinmarket/bencoderpyx/default.nix create mode 100644 pkgs/joinmarket/chromalog/default.nix create mode 100644 pkgs/joinmarket/coincurve/default.nix create mode 100644 pkgs/joinmarket/default.nix create mode 100755 pkgs/joinmarket/get-sha256.sh create mode 100644 pkgs/joinmarket/jmbase/default.nix create mode 100644 pkgs/joinmarket/jmbitcoin/default.nix create mode 100644 pkgs/joinmarket/jmclient/default.nix create mode 100644 pkgs/joinmarket/jmdaemon/default.nix create mode 100644 pkgs/joinmarket/python-bitcointx/default.nix create mode 100755 pkgs/joinmarket/python-bitcointx/get-sha256.sh create mode 100644 pkgs/joinmarket/secp256k1/default.nix create mode 100644 pkgs/joinmarket/urldecode/default.nix diff --git a/pkgs/default.nix b/pkgs/default.nix index b01c915..b026f3f 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -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 { }; diff --git a/pkgs/joinmarket/bencoderpyx/default.nix b/pkgs/joinmarket/bencoderpyx/default.nix new file mode 100644 index 0000000..cc76623 --- /dev/null +++ b/pkgs/joinmarket/bencoderpyx/default.nix @@ -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; + }; +} diff --git a/pkgs/joinmarket/chromalog/default.nix b/pkgs/joinmarket/chromalog/default.nix new file mode 100644 index 0000000..44589a8 --- /dev/null +++ b/pkgs/joinmarket/chromalog/default.nix @@ -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; + }; +} diff --git a/pkgs/joinmarket/coincurve/default.nix b/pkgs/joinmarket/coincurve/default.nix new file mode 100644 index 0000000..67e9a57 --- /dev/null +++ b/pkgs/joinmarket/coincurve/default.nix @@ -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; + }; +} diff --git a/pkgs/joinmarket/default.nix b/pkgs/joinmarket/default.nix new file mode 100644 index 0000000..ea8f04e --- /dev/null +++ b/pkgs/joinmarket/default.nix @@ -0,0 +1,68 @@ +{ stdenv, fetchurl, python3 }: + +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 + ]; + + pythonEnv = python.withPackages (_: runtimePackages); +in +stdenv.mkDerivation { + pname = "joinmarket"; + inherit version src; + + 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 + + chmod +x -R $out/bin + patchShebangs $out/bin + ''; + + passthru = { + inherit python runtimePackages pythonEnv; + }; +} diff --git a/pkgs/joinmarket/get-sha256.sh b/pkgs/joinmarket/get-sha256.sh new file mode 100755 index 0000000..6bdb149 --- /dev/null +++ b/pkgs/joinmarket/get-sha256.sh @@ -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}))" diff --git a/pkgs/joinmarket/jmbase/default.nix b/pkgs/joinmarket/jmbase/default.nix new file mode 100644 index 0000000..d1fdfe6 --- /dev/null +++ b/pkgs/joinmarket/jmbase/default.nix @@ -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; + }; +} diff --git a/pkgs/joinmarket/jmbitcoin/default.nix b/pkgs/joinmarket/jmbitcoin/default.nix new file mode 100644 index 0000000..b61228d --- /dev/null +++ b/pkgs/joinmarket/jmbitcoin/default.nix @@ -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; + }; +} diff --git a/pkgs/joinmarket/jmclient/default.nix b/pkgs/joinmarket/jmclient/default.nix new file mode 100644 index 0000000..dd8d4a3 --- /dev/null +++ b/pkgs/joinmarket/jmclient/default.nix @@ -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; + }; +} diff --git a/pkgs/joinmarket/jmdaemon/default.nix b/pkgs/joinmarket/jmdaemon/default.nix new file mode 100644 index 0000000..0a7493e --- /dev/null +++ b/pkgs/joinmarket/jmdaemon/default.nix @@ -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; + }; +} diff --git a/pkgs/joinmarket/python-bitcointx/default.nix b/pkgs/joinmarket/python-bitcointx/default.nix new file mode 100644 index 0000000..213f5d8 --- /dev/null +++ b/pkgs/joinmarket/python-bitcointx/default.nix @@ -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; + }; +} diff --git a/pkgs/joinmarket/python-bitcointx/get-sha256.sh b/pkgs/joinmarket/python-bitcointx/get-sha256.sh new file mode 100755 index 0000000..34ab4c1 --- /dev/null +++ b/pkgs/joinmarket/python-bitcointx/get-sha256.sh @@ -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)" diff --git a/pkgs/joinmarket/secp256k1/default.nix b/pkgs/joinmarket/secp256k1/default.nix new file mode 100644 index 0000000..a52adb3 --- /dev/null +++ b/pkgs/joinmarket/secp256k1/default.nix @@ -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; + }; +} diff --git a/pkgs/joinmarket/urldecode/default.nix b/pkgs/joinmarket/urldecode/default.nix new file mode 100644 index 0000000..93f1a76 --- /dev/null +++ b/pkgs/joinmarket/urldecode/default.nix @@ -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 ]; + }; +} From 263525d72429211251c55153b621b03235042aed Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Tue, 8 Sep 2020 12:25:33 +0000 Subject: [PATCH 2/7] nix-bitcoin-services: add nb-services.privileged helper --- modules/nix-bitcoin-services.nix | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/nix-bitcoin-services.nix b/modules/nix-bitcoin-services.nix index e8e2f9a..24d0099 100644 --- a/modules/nix-bitcoin-services.nix +++ b/modules/nix-bitcoin-services.nix @@ -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 From 173891fa5be940372aff4c7e558ae935008fd15d Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Thu, 23 Apr 2020 18:18:47 +0200 Subject: [PATCH 3/7] joinmarket: add module --- examples/configuration.nix | 10 ++ modules/default.nix | 1 + modules/joinmarket.nix | 202 ++++++++++++++++++++++ modules/modules.nix | 1 + modules/netns-isolation.nix | 8 + modules/presets/secure-node.nix | 6 +- pkgs/generate-secrets/generate-secrets.sh | 1 + pkgs/netns-exec/src/main.c | 3 +- 8 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 modules/joinmarket.nix diff --git a/examples/configuration.nix b/examples/configuration.nix index 08ea8bf..25abdb2 100644 --- a/examples/configuration.nix +++ b/examples/configuration.nix @@ -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"; diff --git a/modules/default.nix b/modules/default.nix index 2ca1ebe..d41d032 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -12,4 +12,5 @@ spark-wallet = ./spark-wallet.nix; recurring-donations = ./recurring-donations.nix; lnd = ./lnd.nix; + joinmarket = ./joinmarket.nix; } diff --git a/modules/joinmarket.nix b/modules/joinmarket.nix new file mode 100644 index 0000000..80c2593 --- /dev/null +++ b/modules/joinmarket.nix @@ -0,0 +1,202 @@ +{ 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" ]; + 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 ${config.nix-bitcoin.secretsDir}/bitcoin-rpcpassword-privileged)|" \ + '${cfg.dataDir}/joinmarket.cfg' + ''; + 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; + }; + }) + ]); +} diff --git a/modules/modules.nix b/modules/modules.nix index e86772d..967053b 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -19,6 +19,7 @@ ./security.nix ./backups.nix ./btcpayserver.nix + ./joinmarket.nix ]; disabledModules = [ "services/networking/bitcoind.nix" ]; diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index 7b05139..d456fa4 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -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"; } ]); } diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index f1e80e1..dd1839b 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -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 diff --git a/pkgs/generate-secrets/generate-secrets.sh b/pkgs/generate-secrets/generate-secrets.sh index 87bdaa4..06b3f69 100755 --- a/pkgs/generate-secrets/generate-secrets.sh +++ b/pkgs/generate-secrets/generate-secrets.sh @@ -18,6 +18,7 @@ makePasswordSecret liquid-rpcpassword makePasswordSecret lightning-charge-token makePasswordSecret spark-wallet-password makePasswordSecret backup-encryption-password +touch jm-wallet-password [[ -e bitcoin-HMAC-privileged ]] || makeHMAC privileged [[ -e bitcoin-HMAC-public ]] || makeHMAC public diff --git a/pkgs/netns-exec/src/main.c b/pkgs/netns-exec/src/main.c index 60cc85a..67c75b2 100644 --- a/pkgs/netns-exec/src/main.c +++ b/pkgs/netns-exec/src/main.c @@ -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) { From cce27da2ec29bcf23e7d50b3e8be3a16a11e740a Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Tue, 1 Sep 2020 14:50:55 +0000 Subject: [PATCH 4/7] backups: add joinmarket datadir to includelist --- modules/backups.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/backups.nix b/modules/backups.nix index 60ef87f..cb7b5e2 100644 --- a/modules/backups.nix +++ b/modules/backups.nix @@ -18,6 +18,7 @@ let ${config.services.lightning-charge.dataDir} ${config.services.nbxplorer.dataDir} ${config.services.btcpayserver.dataDir} + ${config.services.joinmarket.dataDir} /var/lib/tor # Extra files ${cfg.extraFiles} From d6d3e8ff620b49ceb632f88cffb25585dd6330e7 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Mon, 18 May 2020 09:51:18 +0000 Subject: [PATCH 5/7] joinmarket: add tests --- .travis.yml | 2 ++ test/base.py | 8 ++++++++ test/test.nix | 9 +++++++++ 3 files changed, 19 insertions(+) diff --git a/.travis.yml b/.travis.yml index d2806e1..d50feb1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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" - | diff --git a/test/base.py b/test/base.py index d250eb2..97b2571 100644 --- a/test/base.py +++ b/test/base.py @@ -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", "Failed to open wallet",) + ) + # FIXME: use 'wait_for_unit' because 'create-web-index' always fails during startup due # to incomplete unit dependencies. # 'create-web-index' implicitly tests 'nodeinfo'. diff --git a/test/test.nix b/test/test.nix index 86252b3..23a6c26 100644 --- a/test/test.nix +++ b/test/test.nix @@ -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 = '' From d0701f518cde9532f9a396aa3b4f07e039690933 Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Fri, 11 Sep 2020 11:53:12 +0000 Subject: [PATCH 6/7] joinmarket: automatically generate wallet --- modules/backups.nix | 1 + modules/joinmarket.nix | 19 +++++++++++- pkgs/generate-secrets/generate-secrets.sh | 2 +- pkgs/joinmarket/default.nix | 7 +++-- pkgs/joinmarket/genwallet/genwallet.py | 36 +++++++++++++++++++++++ test/base.py | 6 +++- 6 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 pkgs/joinmarket/genwallet/genwallet.py diff --git a/modules/backups.nix b/modules/backups.nix index cb7b5e2..4915067 100644 --- a/modules/backups.nix +++ b/modules/backups.nix @@ -19,6 +19,7 @@ let ${config.services.nbxplorer.dataDir} ${config.services.btcpayserver.dataDir} ${config.services.joinmarket.dataDir} + /secrets/jm-wallet-seed /var/lib/tor # Extra files ${cfg.extraFiles} diff --git a/modules/joinmarket.nix b/modules/joinmarket.nix index 80c2593..7ce8b53 100644 --- a/modules/joinmarket.nix +++ b/modules/joinmarket.nix @@ -143,13 +143,30 @@ in { 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 ${config.nix-bitcoin.secretsDir}/bitcoin-rpcpassword-privileged)|" \ + "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}"; diff --git a/pkgs/generate-secrets/generate-secrets.sh b/pkgs/generate-secrets/generate-secrets.sh index 06b3f69..831b235 100755 --- a/pkgs/generate-secrets/generate-secrets.sh +++ b/pkgs/generate-secrets/generate-secrets.sh @@ -18,7 +18,7 @@ makePasswordSecret liquid-rpcpassword makePasswordSecret lightning-charge-token makePasswordSecret spark-wallet-password makePasswordSecret backup-encryption-password -touch jm-wallet-password +makePasswordSecret jm-wallet-password [[ -e bitcoin-HMAC-privileged ]] || makeHMAC privileged [[ -e bitcoin-HMAC-public ]] || makeHMAC public diff --git a/pkgs/joinmarket/default.nix b/pkgs/joinmarket/default.nix index ea8f04e..fcaaedb 100644 --- a/pkgs/joinmarket/default.nix +++ b/pkgs/joinmarket/default.nix @@ -1,4 +1,4 @@ -{ stdenv, fetchurl, python3 }: +{ stdenv, fetchurl, python3, pkgs }: let version = "0.7.0"; @@ -32,11 +32,13 @@ let joinmarketdaemon ]; + genwallet = pkgs.writeScriptBin "genwallet" (builtins.readFile ./genwallet/genwallet.py); + pythonEnv = python.withPackages (_: runtimePackages); in stdenv.mkDerivation { pname = "joinmarket"; - inherit version src; + inherit version src genwallet; buildInputs = [ pythonEnv ]; @@ -57,6 +59,7 @@ stdenv.mkDerivation { 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 diff --git a/pkgs/joinmarket/genwallet/genwallet.py b/pkgs/joinmarket/genwallet/genwallet.py new file mode 100644 index 0000000..51de87f --- /dev/null +++ b/pkgs/joinmarket/genwallet/genwallet.py @@ -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() diff --git a/test/base.py b/test/base.py index 97b2571..b7db2ae 100644 --- a/test/base.py +++ b/test/base.py @@ -107,7 +107,7 @@ def run_tests(extra_tests): log_has_string("joinmarket", "P2EPDaemonServerProtocolFactory starting on 27184") ) machine.wait_until_succeeds( - log_has_string("joinmarket-yieldgenerator", "Failed to open wallet",) + log_has_string("joinmarket-yieldgenerator", "Failure to get blockheight",) ) # FIXME: use 'wait_for_unit' because 'create-web-index' always fails during startup due @@ -158,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", From dd882753e6e7782372980c75e9f537904eab6c4e Mon Sep 17 00:00:00 2001 From: nixbitcoin Date: Fri, 24 Apr 2020 17:34:13 +0200 Subject: [PATCH 7/7] joinmarket: add usage documentation --- docs/usage.md | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/docs/usage.md b/docs/usage.md index 88a0f4e..e1b072a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -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 + ``` + + 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