archy/docs/bitcoin-rpc-relay.md
2026-06-12 03:00:15 -04:00

7.9 KiB

Bitcoin RPC Relay for External Wallets

This note captures the pattern used to let an external wallet, such as Wasabi, use an Archipelago Bitcoin node for transaction relay without exposing the node's admin RPC credentials.

Goal

Expose a public HTTPS JSON-RPC endpoint that can broadcast transactions and read basic chain/mempool state, while preventing wallet and admin RPC access.

The endpoint should be fronted by nginx or another TLS reverse proxy:

wallet client -> https://<subdomain>/ -> reverse proxy -> Archipelago node nginx -> bitcoind RPC

Do not expose Bitcoin RPC credentials with wallet/admin access to external users.

Restricted RPC User

Create a separate RPC user, currently named txrelay, with an rpcauth secret and a Bitcoin RPC whitelist.

Allowed RPC methods:

sendrawtransaction
submitpackage
testmempoolaccept
getmempoolinfo
getrawmempool
getmempoolentry
getnetworkinfo
getblockchaininfo
getblockcount
getblockhash
getblockheader
getrawtransaction
gettxout
decoderawtransaction
decodescript
estimatesmartfee

Wallet/admin access is denied by setting -rpcwhitelistdefault=0 and giving the txrelay user only the method whitelist above.

Secrets live under:

/var/lib/archipelago/secrets/bitcoin-rpc-txrelay-password
/var/lib/archipelago/secrets/bitcoin-rpc-txrelay-rpcauth
/var/lib/archipelago/secrets/bitcoin-rpc-txrelay-client.env

Do not commit these files or paste them into docs.

Archipelago UI/API Flow

The productized flow is managed from the Bitcoin Core/Knots custom UI in the Transaction Relay Sharing panel.

Implemented RPC methods:

bitcoin.relay-status
bitcoin.relay-update-settings
bitcoin.relay-request-peer
bitcoin.relay-approve-request
bitcoin.relay-reject-request
bitcoin.relay-create-tor-service

When peer sharing is enabled, bitcoin.relay-update-settings automatically provisions the restricted txrelay password, rpcauth, and client env file if they do not already exist. If those files were just generated, restart Bitcoin Core/Knots so bitcoind reloads the txrelay rpcauth and whitelist flags.

The UI shows:

HTTP / HTTPS / Tor relay endpoint settings
local sync status
restricted credential readiness, without printing the password
trusted peer dropdown, disabled until the local node is synchronized
incoming relay requests with approve/reject actions
outbound relay requests and approval status

Approving an incoming peer request sends the selected endpoint plus restricted txrelay credentials through the existing encrypted peer-message path. On the requesting node, approved peer credentials are stored in a per-peer secret env file:

/var/lib/archipelago/secrets/bitcoin-relay-peer-<peer-pubkey-prefix>.env

The UI returns the credential secret path and approved endpoint metadata, but it does not display the raw password.

For dev review, the mock server exposes the Bitcoin UI at:

http://localhost:8102/app/bitcoin-ui/

Bitcoin Startup Flags

The Bitcoin Knots app should add the restricted user only when the secret exists:

RPC_TXRELAY_AUTH="$(printenv BITCOIN_RPC_TXRELAY_RPCAUTH || true)"
RPC_TXRELAY_FLAGS="-rpcwhitelistdefault=0"
if [ -n "$RPC_TXRELAY_AUTH" ]; then
  RPC_TXRELAY_FLAGS="$RPC_TXRELAY_FLAGS -rpcauth=$RPC_TXRELAY_AUTH -rpcwhitelist=txrelay:sendrawtransaction,submitpackage,testmempoolaccept,getmempoolinfo,getrawmempool,getmempoolentry,getnetworkinfo,getblockchaininfo,getblockcount,getblockhash,getblock,getblockheader,getrawtransaction,gettxout,gettxspendingprevout,decoderawtransaction,decodescript,estimatesmartfee,uptime,ping,getconnectioncount,getpeerinfo,getindexinfo,getdeploymentinfo,getchaintips"
fi

Then include $RPC_TXRELAY_FLAGS in the bitcoind command. Keep the local archipelago RPC user unrestricted for internal services by using -rpcwhitelistdefault=0 and only setting a whitelist for txrelay.

The current implementation touches:

apps/bitcoin-knots/manifest.yml
scripts/container-specs.sh

Node nginx

The Archipelago node can expose a host-based nginx vhost that proxies to local Bitcoin RPC:

limit_req_zone $binary_remote_addr zone=bitcoin_rpc_ext:10m rate=5r/s;

server {
    listen 80;
    server_name rpc.example.com;

    client_max_body_size 2m;

    location / {
        limit_req zone=bitcoin_rpc_ext burst=20 nodelay;
        limit_req_status 429;

        proxy_pass http://127.0.0.1:8332;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 5s;
        proxy_send_timeout 120s;
        proxy_read_timeout 120s;
        proxy_buffering off;
    }
}

If another public reverse proxy terminates TLS, point it at:

http://<archipelago-lan-ip>:80

For the tested node the LAN upstream was:

http://192.168.1.116:80

The public proxy should serve a valid TLS certificate for the chosen subdomain.

DNS and Routing

Use a subdomain that resolves to the public reverse proxy:

Type: A
Host/Name: <subdomain-only>
Value: <public-ip>

For example, if the desired hostname is rpc.example.com, the DNS host/name field is usually only rpc, not the full rpc.example.com. Entering the full hostname in some DNS panels can accidentally create:

rpc.example.com.example.com

The public proxy should forward:

TCP 443 -> TLS reverse proxy for the subdomain
TCP 80  -> optional, needed for HTTP-01 certificate issuance or redirects

If the public proxy is separate from the Archipelago node, configure it with:

server_name: <subdomain>
scheme: http
upstream host: <archipelago-lan-ip>
upstream port: 80

Verification

Check authoritative DNS:

dig @<authoritative-dns-ip> <subdomain> A +noall +answer +authority
dig @1.1.1.1 +short <subdomain> A

Check TLS:

openssl s_client -connect <subdomain>:443 -servername <subdomain> </dev/null

Check the public RPC path:

. /var/lib/archipelago/secrets/bitcoin-rpc-txrelay-client.env

curl -sS --user "$BITCOIN_RPC_TXRELAY_USER:$BITCOIN_RPC_TXRELAY_PASSWORD" \
  --data-binary '{"jsonrpc":"1.0","id":"check","method":"getblockchaininfo","params":[]}' \
  "<relay-endpoint-url>"

Check that transaction broadcast reaches Bitcoin RPC, without needing a real transaction:

curl -sS --user "$BITCOIN_RPC_TXRELAY_USER:$BITCOIN_RPC_TXRELAY_PASSWORD" \
  --data-binary '{"jsonrpc":"1.0","id":"badtx","method":"sendrawtransaction","params":["00"]}' \
  "<relay-endpoint-url>"

Expected result is a Bitcoin RPC validation error such as TX decode failed, which confirms the request reached sendrawtransaction.

If a wallet verifies the connection but reports RPC Forbidden during broadcast, the credentials authenticated but the broadcast method was outside the loaded txrelay whitelist. Restart the active Bitcoin backend after updating the whitelist, then test both sendrawtransaction and, for newer package-relay clients, submitpackage. Also confirm the public reverse proxy passes the wallet's Authorization header through to 127.0.0.1:8332; do not point public wallet traffic at the Bitcoin UI /bitcoin-rpc/ helper, because that helper injects the local dashboard credential.

Check that wallet/admin RPC is blocked:

curl -sS -o /tmp/txrelay-deny.json -w '%{http_code}\n' \
  --user "$BITCOIN_RPC_TXRELAY_USER:$BITCOIN_RPC_TXRELAY_PASSWORD" \
  --data-binary '{"jsonrpc":"1.0","id":"deny","method":"listwallets","params":[]}' \
  "<relay-endpoint-url>"

Expected result:

403

Tested Outcome

The working endpoint used in this setup was:

https://shard.tx1138.com/

It was verified with:

DNS resolves
TLS certificate is valid
txrelay credentials authenticate
getblockchaininfo returns chain=main
sendrawtransaction reaches Bitcoin RPC
listwallets is blocked for txrelay