Skip to main content

API authentication and TLS

The node protects its owner-facing surfaces with HTTP Basic against a secret file on disk. The wallet Owner API v3 uses a token obtained with the wallet password. The foreign surfaces take no credential, because an interactive transfer needs the receiving side reachable by a stranger. TLS termination is left to the operator.

The surfaces

SurfaceDefault portCredentialWho is meant to call it
Node /v1/*3413Basic, api_secret_path, across the whole prefixYou, from the same machine
Node /v2/owner3413Basic, api_secret_pathYou, from the same machine
Node /v2/foreign3413Optional Basic, foreign_api_secret_pathAnything that reads the chain
Wallet Owner /v3/owner3420Token from open_wallet, inside the encrypted envelopeYou, from the same machine
Wallet Owner /v2/owner3420None. Superseded by /v3/ownerNothing. Bind the listener to loopback
Wallet Foreign /v2/foreign3415NoneA counterparty paying you

The wallet's Foreign API receives a slate, returns a partially signed one, builds a coinbase for a miner configured to pay this wallet, and reports its version. Restrict access to it at the proxy.

The node's /v1 credential covers the whole prefix, including the chain reads that are open on /v2/foreign. See the v1 REST surface.

api_secret_path names a different file in each binary's configuration. Both node surfaces read the node's, which is .api_secret.

Owner access on the wallet

/v3/owner is gated on a token, not on an HTTP credential. Every method in the v3 trait takes one (api/src/owner_rpc_s.rs:78), and getting one means completing the ECDH handshake and then calling open_wallet with the wallet password. The password is the gate. See the encrypted handshake.

The first call establishes the shared key and carries no credential:

curl -s \
-d '{"jsonrpc":"2.0","method":"init_secure_api","params":{"ecdh_pubkey":"<your compressed public key, hex>"},"id":1}' \
http://127.0.0.1:3420/v3/owner

On top of that credential, every method on /v3/owner takes a token, and a token comes from open_wallet, which needs the wallet password. owner_api_listen_interface defaults to 127.0.0.1; for remote access, tunnel to loopback. The listener is controller/src/controller.rs:123.

The full method list, the encrypted envelope, and which calls move value are on the wallet Owner API page.

The node credential

The node's /v1 prefix and /v2/owner use HTTP Basic with the username literally epic and a secret file's contents as the password:

Authorization: Basic base64("epic:" + <secret file contents>)

The node creates ~/.epic/<network>/.api_secret on first run and points api_secret_path at it, so this works without configuration:

curl -u epic:$(cat ~/.epic/main/.api_secret) \
-d '{"jsonrpc":"2.0","method":"get_status","params":[],"id":1}' \
http://127.0.0.1:3413/v2/owner

Secret files hold one 20-character random alphanumeric string with no trailing newline (config/src/config.rs:84), and the comparison is constant time (api/src/auth.rs:74). The realm is EpicAPI on the owner surface and EpicForeignAPI on the foreign one. A rejected call, on either binary, returns 401 with a WWW-Authenticate header and a JSON-RPC error body:

{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Unauthorized"}, "id": null}
  • With api_secret_path commented out, the listener runs with no credential.
  • An empty .api_secret is regenerated with a new value rather than read as "no secret", so emptying the file does not switch authentication off (config/src/config.rs:96).
  • OPTIONS is passed through before the credential check, which is what makes CORS preflight work.

Optional credential on the node's foreign surface

foreign_api_secret_path makes the node's /v2/foreign require a credential too. Create the secret yourself and give it an absolute path:

head -c 40 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 20 > ~/.epic/main/.foreign_api_secret
[server]
foreign_api_secret_path = "/home/you/.epic/main/.foreign_api_secret"

The realm is EpicForeignAPI and the username is still epic. Verify with:

curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:3413/v2/foreign
# 401 once the secret is in place

The credential the wallet presents to the node

node_api_secret_path points the other way: it is what the wallet sends when calling the node, and it defaults to <wallet dir>/.api_secret. Left at defaults, node and wallet share ~/.epic/<network>/ and resolve to the same file, so they agree with no configuration. Change the node's secret, move either directory, or point the wallet at another node, and this is the key to update.

TLS

Both binaries take the same pair, both commented out by default:

tls_certificate_file = "/path/to/fullchain.pem"
tls_certificate_key = "/path/to/privkey.pem"

Setting the certificate switches the listener from HTTP to HTTPS. Setting it without the key is a startup error. Certificates are read as a PEM chain; keys may be PKCS#1, PKCS#8 or SEC1 PEM. A file that cannot be opened, cannot be parsed, or does not match its certificate stops the process rather than falling back to HTTP, so a bad pair fails loudly at startup.

The bundled epic client subcommands address the node over http:// (src/bin/cmd/client.rs:160). Use curl against a TLS listener.

Next