Skip to main content

Client Node (Docker) Guide

ABC WaaS Client Node provides server-side support for key share generation, recovery, and signing for MPC-based wallets. The Client Node is distributed as a Docker image and is deployed in the customer backend environment, where it is called through REST APIs.

Using the service requires a valid Access Token. The customer backend issues the Access Token through the WaaS v3 authentication API, then passes it in the Authorization: Bearer <access_token> header when calling the Client Node API.


Architecture

Client Node is stateless and does not store key data itself. All communication goes through the ABC WaaS Gateway, so the customer environment only needs to configure WAAS_API_URL.


Prerequisites

Before deployment, obtain the following information from ABC.

ItemDescription
Docker imageghcr.io/ahnlabio/abc-mpc-client-node-customer:<tag> (GitHub Container Registry public)
WAAS_API_URLhttps://api.waas.myabcwallet.com
Access Token issuance methodWaaS v3 authentication API guide

Installation

Get the Docker Image

The Docker image is publicly available from GitHub Container Registry (ghcr.io). You can pull it directly without separate authentication.

Distribution repository: https://github.com/ahnlabio/abc-mpc-client-node-customer

Package page: https://github.com/ahnlabio/abc-mpc-client-node-customer/pkgs/container/abc-mpc-client-node-customer

# Pull the latest version (authentication not required)
docker pull ghcr.io/ahnlabio/abc-mpc-client-node-customer:latest

# Pull a specific version
docker pull ghcr.io/ahnlabio/abc-mpc-client-node-customer:0.1.3

# Add a shorter tag for convenience (optional)
docker tag ghcr.io/ahnlabio/abc-mpc-client-node-customer:0.1.3 abc-mpc-client-node-customer:latest

Available Tags

TagDescription
latestLatest release
<version>Specific version (for example, 0.1.3)
<version>-<git-sha>Specific commit-based build (for example, 0.1.3-abc1234)

Load a tar File (Temporary/Offline)

For closed-network environments where GitHub cannot be accessed, ABC can provide the image as a tar file.

docker load -i abc-mpc-client-node-customer.tar

Latest Version

TypeImage Tag
Client Node (Customer)ghcr.io/ahnlabio/abc-mpc-client-node-customer:0.1.3

Run

docker run -d \
--name abc-mpc-client-node \
--restart=unless-stopped \
-p 9091:9091 \
-e WAAS_API_URL=`https://api.waas.myabcwallet.com` \
ghcr.io/ahnlabio/abc-mpc-client-node-customer:latest

Environment Variables

Set the URL provided by ABC as an environment variable.

VariableDescription
APP_PORTService port (default 9091)
WAAS_API_URLhttps://api.waas.myabcwallet.com (MPC Node communication, user/key lookup, token issuance, address conversion)

WAAS_API_URL is required. Make sure to set the URL provided by ABC. If it is not set, the service will not operate correctly.


Check Operation

Health Check

curl http://localhost:9091/

Example Response

{
"name": "abc-mpc-client-node-customer",
"version": "0.1.0_1_abc1234",
"build_type": "customer",
"time": "2026-04-13T06:50:00.000000000+00:00"
}

API Docs (Swagger UI)

Open http://localhost:9091/api/docs in a browser to view the full API documentation and test UI.


Authentication

All protected Client Node APIs require the Authorization: Bearer <access_token> header.

Customer server -> WaaS v3 authentication API -> Issue Access Token
|
Call Client Node API (Bearer: access_token)
|
Client Node -> MPC Node / WaaS API

For an expired or invalid token, the API returns HTTP 401 Unauthorized with an error message in the form {"error":"Authentication failed: ..."}. The customer backend should detect this response, reissue the token, and retry the request.

For details about issuing Access Tokens, refer to the WaaS v3 authentication API documentation.


API Overview

FeatureMethodPath
Health checkGET/
Generate key sharePOST/v3/wallet/generate
Recover key sharePOST/v3/wallet/recover
SignPOST/v3/wallet/sign
Get public keyPOST/v3/wallet/publickey
Validate passwordPOST/v3/wallet/validate/password
Validate key sharePOST/v3/wallet/validate/share
Get registered keysGET/v3/wallet/key
Issue MPC tokenGET/v3/wallet/token
Get user informationGET/v3/wallet/user
Get wallet informationGET/v3/wallet

Generate Key Share

Generates an MPC key share. Supported curves are secp256k1 and ed25519.

When generation is complete, the following fields are returned.

FieldDescription
key_idKey identifier (64-character hex). Used for signing requests
encrypted_shareEncrypted key share. Core signing data
secret_storeSecret data required to decrypt the key share
public_keyCompressed public key (hex, including 0x prefix)
curveCurve used

Security recommendation: The returned values must be encrypted and stored on the customer server. Exposure can create security risks, so manage storage and access permissions carefully.

Request Example

curl -X POST http://localhost:9091/v3/wallet/generate \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"curve": "secp256k1",
"password": "user_password"
}'

Response Example

{
"key_id": "0f43f4e5fc30e66c2846d528dbaab885b09b914cbfd338c47d366d78f1f383af",
"encrypted_share": "GQmxNOxIYYdzSYXinfBC1LFo...",
"secret_store": "3Nt0bVPz70zjopUyDEZUPEviUgD+122mzC6HbIuoC+Ce1NbUZfHSxKdcEnXRxKf0X8KgiY2RGocS24d9W+DavQ==",
"curve": "secp256k1",
"public_key": "0x031714762cfcca743ace9b52bc2e3971ae131cae72610d85d90ebf4b4c417dbd72"
}

If a key with the same curve already exists, the API returns 409 Conflict.


Recover Key Share

If a stored key share is lost due to a device change, data loss, or similar event, you can recover the key share based on the key information registered on the server.

During recovery, a new key_id is issued, but the recovered key share uses the same public key as the existing key. Blockchain addresses and assets are not affected.

Request Example

curl -X POST http://localhost:9091/v3/wallet/recover \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"curve": "secp256k1",
"password": "user_password"
}'

Response Example

{
"key_id": "f846acc0dc22ced1d66adfc15782fdbad1807a307d7d01aa7b4633185fd4bb5b",
"encrypted_share": "+6y0q83McPzjnWq7vMfx3340W+uWwEGVmutk...",
"secret_store": "iShfqMwgMEO/Z1JLqk4kzWVrVAn/k3jNjM0uhCq48CM+/RdL2LGfiMPYBxXu/z02m7wAxa7RoYJ5+/6GmO1neg==",
"curve": "secp256k1",
"public_key": "0x031714762cfcca743ace9b52bc2e3971ae131cae72610d85d90ebf4b4c417dbd72"
}

Sign

Client Node automatically selects the appropriate signing method based on the curve. External callers use the single /v3/wallet/sign endpoint, and the request is handled internally as follows.

CurveInternal Signing MethodDescription
secp256k1MTA signingSecurity-enhanced signing based on the Multiplicative-to-Additive protocol
ed25519Default signingStandard EdDSA signing

The returned signature is a hex-encoded string.

  • secp256k1 (MTA): r(32bytes) + s(32bytes) + v(1byte) = 65 bytes, 130-character hex
  • ed25519: 64 bytes, 128-character hex

Request Example

curl -X POST http://localhost:9091/v3/wallet/sign \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"key_id": "0f43f4e5fc30e66c2846d528dbaab885b09b914cbfd338c47d366d78f1f383af",
"encrypted_share": "GQmxNOxIYYdzSYXinfBC1LFo...",
"secret_store": "3Nt0bVPz70zjopUyDEZUPEviUgD+122mzC6HbIuoC+Ce1NbUZfHSxKdcEnXRxKf0X8KgiY2RGocS24d9W+DavQ==",
"curve": "secp256k1",
"message": "2caedcc9efe11bfb2aaaa1474a9d1c803bce969c692fe890ebb2c8fd4db00bae"
}'

message is the hash value of the data to sign (hex string, without the 0x prefix).

Response Example

{
"signature": "50247596f121b126ffa7a0439aba0e70a68abead61b680700ec8c23ebd7b7a8c3157f6b60cc6acde5681f8c64692b988db03940c324823411005d0ff690f13701b"
}

Concurrent signing limit: MTA signing uses a multi-round protocol, so avoid sending multiple signing requests for the same key at the same time. Process requests sequentially with a queue or lock per key_id.


Get Public Key

Calculates and returns the public key from the key share. This is used to generate blockchain addresses.

curl -X POST http://localhost:9091/v3/wallet/publickey \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"key_id": "0f43f4e5fc30e66c2846d528dbaab885b09b914cbfd338c47d366d78f1f383af",
"encrypted_share": "GQmxNOxIYYdzSYXinfBC1LFo...",
"secret_store": "3Nt0bVPz70zjopUyDEZUPEviUgD+122mzC6HbIuoC+Ce1NbUZfHSxKdcEnXRxKf0X8KgiY2RGocS24d9W+DavQ==",
"curve": "secp256k1",
"password": "user_password"
}'

Response Example

{ "public_key": "0x031714762cfcca743ace9b52bc2e3971ae131cae72610d85d90ebf4b4c417dbd72" }

Validation

You can validate whether the key share and password to use for signing are valid in advance. Running validation before signing helps prevent signing failures caused by an incorrect password or a damaged key share.

Validate Password

curl -X POST http://localhost:9091/v3/wallet/validate/password \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"password": "user_password",
"secret_store": "3Nt0bVPz70zjopUyDEZUPEviUgD+122mzC6HbIuoC+Ce1NbUZfHSxKdcEnXRxKf0X8KgiY2RGocS24d9W+DavQ=="
}'

Validate Key Share

curl -X POST http://localhost:9091/v3/wallet/validate/share \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"encrypted_share": "GQmxNOxIYYdzSYXinfBC1LFo...",
"secret_store": "3Nt0bVPz70zjopUyDEZUPEviUgD+122mzC6HbIuoC+Ce1NbUZfHSxKdcEnXRxKf0X8KgiY2RGocS24d9W+DavQ=="
}'

Both APIs return the following response format.

{ "result": true }

Get Wallet Information

Get Wallet Information

Retrieves the user's wallet list and blockchain addresses for each wallet.

curl http://localhost:9091/v3/wallet \
-H "Authorization: Bearer $ACCESS_TOKEN"

Response Example

{
"user_id": "470021fdc945471396a9e16ab320c017",
"wallets": [
{
"key": {
"curve": "secp256k1",
"public_key": "0x031714762cfcca743ace9b52bc2e3971ae131cae72610d85d90ebf4b4c417dbd72"
},
"address": {
"evm": "0x6F8bbD9b3FABa14b3b1F70d3e6B6c11b5cD1FBde",
"btc": "bc1q8nn2d8z87uq0f0tll74q9cjucrychpt3959ly6",
"aptos": "0x7da291585cc019f3da0270d345c00fec781f796306714f5f400a714ad6df5453",
"tron": "TL91QhiMbY87kTYSnUhktuqA3V62JMxuK7",
"xrp": "raZriQmumrngJhBb271q2zUU8x11Mx2ax3"
}
},
{
"key": {
"curve": "ed25519",
"public_key": "0xb0786dda97a85a0bbd1c2fddfc3e01cad0946a1a2183fa06808645af35aefbb7"
},
"address": {
"solana": "CssGFVGgZH54VESheuf3TzzpHGmLmq63N87QL46DMcoY"
}
}
]
}

Get Key List

curl http://localhost:9091/v3/wallet/key \
-H "Authorization: Bearer $ACCESS_TOKEN"

Get User Information

curl http://localhost:9091/v3/wallet/user \
-H "Authorization: Bearer $ACCESS_TOKEN"

Issue MPC Token

Issues a JWT token for MPC Node calls for a specific key_id.

curl "http://localhost:9091/v3/wallet/token?id=<key_id>" \
-H "Authorization: Bearer $ACCESS_TOKEN"

Error Responses

When an error occurs, a JSON response in the following format is returned.

{ "error": "error message" }
Status CodeDescriptionExample Message
200Success-
400Invalid request parameterInvalid curve
401Authentication failed (missing/expired token)Authentication failed: {"code":"757","message":"JWT_EXPIRED"}
404Resource not found-
409Conflict (for example, a key with the same curve already exists)secp256k1 key already exists for this user
500Internal server errorUpstream returned 503 Service Unavailable: ...

When a token expires, the API returns 401. The customer backend should catch this response, reissue the Access Token, and retry the request.


docker-compose Example

version: "3.8"

services:
abc-mpc-client-node:
image: ghcr.io/ahnlabio/abc-mpc-client-node-customer:latest
container_name: abc-mpc-client-node
restart: unless-stopped
ports:
- "9091:9091"
environment:
APP_PORT: "9091"
WAAS_API_URL: "https://api.waas.myabcwallet.com"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9091/"]
interval: 30s
timeout: 5s
retries: 3

Run:

docker-compose up -d

Client Node is stateless, so instances can be scaled horizontally.


Network Configuration Recommendations

Allow Internal Access Only

Client Node access should be restricted to the customer backend only. Do not expose it directly to the public internet.


Upgrade

  1. Stop the existing container
docker stop abc-mpc-client-node
docker rm abc-mpc-client-node
  1. Pull the new image (recommended) or load the tar file
# Pull from GitHub Packages
docker pull ghcr.io/ahnlabio/abc-mpc-client-node-customer:<new-version>

# Or, if you received a tar file
docker load -i abc-mpc-client-node-customer-<new-version>.tar
  1. Run the new container with the same environment variables
docker run -d \
--name abc-mpc-client-node \
--restart=unless-stopped \
-p 9091:9091 \
-e APP_PORT="9091" \
-e WAAS_API_URL="https://api.waas.myabcwallet.com" \
ghcr.io/ahnlabio/abc-mpc-client-node-customer:<new-version>
  1. Check operation with a health check
curl http://localhost:9091/

Monitoring

Health Check

Call GET / periodically to check the service status.

# Health check every 30 seconds
while true; do
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:9091/)
echo "$(date): status=$status"
sleep 30
done

Check Logs

docker logs -f abc-mpc-client-node

Key Log Patterns

LogMeaning
INFO request: finished processing request latency=XXX ms status=200Request processed normally
ERROR status:401 Unauthorized, error: Authentication failed: ...Token expired or invalid
ERROR status:500 Internal Server Error, error: Upstream returned ...WaaS API / MPC Node communication error

Troubleshooting

502/504 Errors

This is likely a network connectivity issue with the MPC Node or WaaS API.

  1. Check the WAAS_API_URL environment variable.
  2. Check whether the Client Node container can access that URL.
docker exec abc-mpc-client-node sh -c "curl -s -o /dev/null -w '%{http_code}' $WAAS_API_URL/"

401 Unauthorized

The Access Token is expired or invalid.

{"error":"Authentication failed: {\"code\":\"757\",\"message\":\"JWT_EXPIRED\"}"}

Reissue the Access Token from the customer backend and retry the request.

409 Conflict (When Generating a Key)

The user already has a key with the same curve. In this case, use key share recovery (POST /v3/wallet/recover).

MTA Signing Failure / Timeout

Concurrent signing requests for the same key may be the cause. Apply a queue or lock in the customer backend so requests are processed sequentially per key_id.


Operational Notes

  • Client Node communicates with MPC Nodes through the WaaS Gateway, so it is recommended to place it in an environment with low network latency. MPC signing is a multi-round protocol, so network latency directly affects signing time.
  • Restrict the Client Node port (default: 9091) so that it is accessible only from the customer backend. Do not allow direct external access.
  • The key_id, encrypted_share, and secret_store values returned during key generation/recovery are critical data required for signing. They must be encrypted and stored on the customer server, and must not be exposed.
  • Do not send multiple signing requests for the same key at the same time because MTA signing uses a multi-round protocol.

Security Notes

  • No private key exposure: A complete private key never exists in a single place at any stage of the MPC protocol.
  • Independently operated nodes: MPC Node 1 (ABC) and Node 2 (third-party institution) are operated independently, so a compromise of one organization does not expose the key.
  • Client-side encryption: Key shares are encrypted with the user password before being returned. Client Node only handles encrypted data.
  • Stateless: Client Node does not store key data itself, so instances can be scaled horizontally as traffic increases.

Support

If you encounter issues during deployment, contact the ABC support team. Include the following information in the issue report to speed up troubleshooting.

  • Image version (version field from curl http://localhost:9091/)
  • Container logs (docker logs abc-mpc-client-node --tail 200)
  • API path and status code of the failing request
  • Reproduction steps