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.
| Item | Description |
|---|---|
| Docker image | ghcr.io/ahnlabio/abc-mpc-client-node-customer:<tag> (GitHub Container Registry public) |
| WAAS_API_URL | https://api.waas.myabcwallet.com |
| Access Token issuance method | WaaS 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 from GitHub Packages (Recommended)
# 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
| Tag | Description |
|---|---|
latest | Latest 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
| Type | Image 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.
| Variable | Description |
|---|---|
APP_PORT | Service port (default 9091) |
| WAAS_API_URL | https://api.waas.myabcwallet.com (MPC Node communication, user/key lookup, token issuance, address conversion) |
WAAS_API_URLis 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
| Feature | Method | Path |
|---|---|---|
| Health check | GET | / |
| Generate key share | POST | /v3/wallet/generate |
| Recover key share | POST | /v3/wallet/recover |
| Sign | POST | /v3/wallet/sign |
| Get public key | POST | /v3/wallet/publickey |
| Validate password | POST | /v3/wallet/validate/password |
| Validate key share | POST | /v3/wallet/validate/share |
| Get registered keys | GET | /v3/wallet/key |
| Issue MPC token | GET | /v3/wallet/token |
| Get user information | GET | /v3/wallet/user |
| Get wallet information | GET | /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.
| Field | Description |
|---|---|
key_id | Key identifier (64-character hex). Used for signing requests |
encrypted_share | Encrypted key share. Core signing data |
secret_store | Secret data required to decrypt the key share |
public_key | Compressed public key (hex, including 0x prefix) |
curve | Curve 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.
| Curve | Internal Signing Method | Description |
|---|---|---|
| secp256k1 | MTA signing | Security-enhanced signing based on the Multiplicative-to-Additive protocol |
| ed25519 | Default signing | Standard 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"
}'
messageis the hash value of the data to sign (hex string, without the0xprefix).
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 Code | Description | Example Message |
|---|---|---|
| 200 | Success | - |
| 400 | Invalid request parameter | Invalid curve |
| 401 | Authentication failed (missing/expired token) | Authentication failed: {"code":"757","message":"JWT_EXPIRED"} |
| 404 | Resource not found | - |
| 409 | Conflict (for example, a key with the same curve already exists) | secp256k1 key already exists for this user |
| 500 | Internal server error | Upstream 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
- Stop the existing container
docker stop abc-mpc-client-node
docker rm abc-mpc-client-node
- 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
- 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>
- 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
| Log | Meaning |
|---|---|
INFO request: finished processing request latency=XXX ms status=200 | Request 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.
- Check the
WAAS_API_URLenvironment variable. - 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, andsecret_storevalues 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 (
versionfield fromcurl http://localhost:9091/) - Container logs (
docker logs abc-mpc-client-node --tail 200) - API path and status code of the failing request
- Reproduction steps