|
| 1 | +--- |
| 2 | +title: "Notes on payjoin v1 infrastructure" |
| 3 | +description: "A guide on setting up a payjoin receiver on signet" |
| 4 | +date: "2024-05-22" |
| 5 | +authors: |
| 6 | + - spacebear |
| 7 | +tags: |
| 8 | + - PDK |
| 9 | + - Infrastructure |
| 10 | +--- |
| 11 | + |
| 12 | +<br/> |
| 13 | + |
| 14 | +Payjoin v1 coordinates payjoins over a public server endpoint secured by either TLS or Tor hidden service hosted by the receiver. This requires setting up either a HTTPS proxy or a Tor proxy when testing payjoins across different implementations. |
| 15 | + |
| 16 | +## Setting up a HTTPS payjoin server with nginx |
| 17 | + |
| 18 | +This guide requires a dedicated server that you can `ssh` into, with the ability to `sudo`, and a domain name pointing to that server. |
| 19 | + |
| 20 | +### Configure a nginx proxy |
| 21 | + |
| 22 | +First, ensure nginx is installed on the server or [install nginx](https://nginx.org/en/docs/install.html). |
| 23 | + |
| 24 | +Then, we'll edit `/etc/nginx/nginx.conf` to proxy traffic to the payjoin server (more on that later) by adding the following block: |
| 25 | + |
| 26 | +``` |
| 27 | +# nginx.conf |
| 28 | + server { |
| 29 | + server_name pj.example.com; # Replace this with your domain name |
| 30 | +
|
| 31 | + location / { |
| 32 | + proxy_pass http://localhost:3000; # This is the port on which we'll run the payjoin server |
| 33 | +
|
| 34 | + proxy_set_header Host $host; |
| 35 | + proxy_set_header X-Real-IP $remote_addr; |
| 36 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| 37 | + proxy_set_header X-Forwarded-Proto $scheme; |
| 38 | + } |
| 39 | + } |
| 40 | +``` |
| 41 | + |
| 42 | +Note that the above configuration uses the `pj` subdomain to avoid conflicts with anything that may already be running on the root domain. |
| 43 | + |
| 44 | +### Get a certificate |
| 45 | + |
| 46 | +Next, we'll need to obtain a valid TLS certificate from a Certificate Authority. There are many ways to do this, but one free and relatively easy option is to use [certbot](https://certbot.eff.org/instructions), an open-source tool by [letsencrypt.org](https://letsencrypt.org). |
| 47 | + |
| 48 | +Once certbot is installed, we can obtain a certificate and automatically update the nginx configuration: |
| 49 | + |
| 50 | +```sudo certbot -d <server_name from the nginx.conf above> --nginx``` |
| 51 | + |
| 52 | +`/etc/nginx/nginx.conf` should now look something like this: |
| 53 | + |
| 54 | +``` |
| 55 | +# nginx.conf |
| 56 | + server { |
| 57 | + server_name pj.example.com; |
| 58 | +
|
| 59 | + location / { |
| 60 | + proxy_pass http://localhost:3000; |
| 61 | +
|
| 62 | + proxy_set_header Host $host; |
| 63 | + proxy_set_header X-Real-IP $remote_addr; |
| 64 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| 65 | + proxy_set_header X-Forwarded-Proto $scheme; |
| 66 | + } |
| 67 | +
|
| 68 | + listen 443 ssl; # managed by Certbot |
| 69 | + ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot |
| 70 | + ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot |
| 71 | + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot |
| 72 | + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot |
| 73 | + } |
| 74 | +``` |
| 75 | + |
| 76 | +Verify the installation with `curl`: |
| 77 | + |
| 78 | +``` |
| 79 | +[ec2-user@ip-172-31-94-70 ~]$ curl -v https://<server_name> |
| 80 | +* Host pj.example.com:443 was resolved. |
| 81 | +* IPv6: (none) |
| 82 | +* IPv4: 54.156.128.153 |
| 83 | +* Trying 54.156.128.153:443... |
| 84 | +* Connected to pj.example.com (54.156.128.153) port 443 |
| 85 | +* ALPN: curl offers h2,http/1.1 |
| 86 | +* TLSv1.3 (OUT), TLS handshake, Client hello (1): |
| 87 | +* CAfile: /etc/pki/tls/certs/ca-bundle.crt |
| 88 | +* CApath: none |
| 89 | +* TLSv1.3 (IN), TLS handshake, Server hello (2): |
| 90 | +* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): |
| 91 | +* TLSv1.3 (IN), TLS handshake, Certificate (11): |
| 92 | +* TLSv1.3 (IN), TLS handshake, CERT verify (15): |
| 93 | +* TLSv1.3 (IN), TLS handshake, Finished (20): |
| 94 | +* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): |
| 95 | +* TLSv1.3 (OUT), TLS handshake, Finished (20): |
| 96 | +* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / id-ecPublicKey |
| 97 | +* ALPN: server accepted http/1.1 |
| 98 | +* Server certificate: |
| 99 | +* subject: CN=spacebear.dev |
| 100 | +* start date: Apr 18 01:37:16 2024 GMT |
| 101 | +* expire date: Jul 17 01:37:15 2024 GMT |
| 102 | +* subjectAltName: host "pj.example.com" matched cert's "pj.example.com" |
| 103 | +* issuer: C=US; O=Let's Encrypt; CN=R3 |
| 104 | +* SSL certificate verify ok. |
| 105 | +
|
| 106 | +... |
| 107 | +
|
| 108 | +<html> |
| 109 | +<head><title>502 Bad Gateway</title></head> |
| 110 | +<body> |
| 111 | +<center><h1>502 Bad Gateway</h1></center> |
| 112 | +<hr><center>nginx/1.25.0</center> |
| 113 | +</body> |
| 114 | +</html> |
| 115 | +``` |
| 116 | + |
| 117 | +If everything worked, we should see "SSL certificate verify ok."! We now have a legit HTTPS server proxying traffic to port 3000, but as indicated by the 502 error there is nothing running there. The next step is to setup a payjoin receiver to run on that port. |
| 118 | + |
| 119 | +#### (Optional) Make a cronjob to auto-renew the certificate on a schedule |
| 120 | + |
| 121 | +``` |
| 122 | +echo "0 0,12 * * * root /opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && sudo certbot renew -q" | sudo tee -a /etc/crontab > /dev/null |
| 123 | +``` |
| 124 | + |
| 125 | +### Set up Bitcoin Core on signet |
| 126 | + |
| 127 | +Because we're testing between wallets on different machines, regtest won't work (at least not trivially). We need a "real" Bitcoin network like signet. [Install Bitcoin Core](https://bitcoincore.org/) and edit `~/.bitcoin/bitcoin.conf`: |
| 128 | + |
| 129 | +``` |
| 130 | +# bitcoin.conf |
| 131 | +chain=signet |
| 132 | +server=1 |
| 133 | +rpcuser=payjoin |
| 134 | +rpcpassword=payjoin |
| 135 | +``` |
| 136 | + |
| 137 | +`bitcoind` will take a few minutes to sync. In the meantime, let's create `sender` and `receiver` wallets and fund them. Use a signet faucet like https://signetfaucet.com/ if you don't have any signet coins on hand. |
| 138 | + |
| 139 | +### Install and run payjoin-cli receiver |
| 140 | + |
| 141 | +Finally, we'll install (or build from source) [payjoin-cli](https://github.com/payjoin/rust-payjoin/tree/master/payjoin-cli#install-payjoin-cli) and make a `config.toml` in the directory we plan on running payjoin-cli from: |
| 142 | + |
| 143 | +``` |
| 144 | +# config.toml |
| 145 | +bitcoind_rpcuser = "payjoin" |
| 146 | +bitcoind_rpcpass = "payjoin" |
| 147 | +bitcoind_rpchost = "http://localhost:38332/wallet/receiver" |
| 148 | +``` |
| 149 | + |
| 150 | +We can now run the receiver: |
| 151 | + |
| 152 | +``` |
| 153 | +$ payjoin-cli receive 10000 |
| 154 | +Listening at 0.0.0.0:3000. Configured to accept payjoin at BIP 21 Payjoin Uri: |
| 155 | +bitcoin:tb1q9e5qgztf6w4zz2m3ts3w2zp3psdqpgmtdkf7y0?amount=0.0001&pj=https://localhost:3000/&pjos=0 |
| 156 | +``` |
| 157 | + |
| 158 | +### Send payjoin! |
| 159 | + |
| 160 | +Send a payjoin to the BIP21 Uri generated above, taking care to replace `localhost:3000` in the `pj=` parameter with the domain name where your server is running. This should work from any wallet that implements payjoin support, from any machine. |
| 161 | + |
| 162 | +E.g. sending from joinmarket: |
| 163 | + |
| 164 | +``` |
| 165 | +(jmvenv) $ sendpayment.py -m 0 wallet.jmdat "bitcoin:tb1q9e5qgztf6w4zz2m3ts3w2zp3psdqpgmtdkf7y0?amount=0.0001&pj=https://pj.example.com/&pjos=0" |
| 166 | +``` |
0 commit comments