Skip to main content
When a node requires ownership challenges, you must prove that you control the private key behind your did:key DID before the node accepts a registration or key rotation. The node issues a short-lived challenge — a random nonce — that you sign with your Ed25519 private key. The signed challenge is then included in the registration or rotation request, and the node verifies the signature against the public key encoded in your DID.

When challenges are required

The node operator controls whether challenges are required via the SERVICENET_REQUIRE_PROVIDER_OWNERSHIP_CHALLENGES environment variable. Set it to 1 to enforce challenges, or leave it unset to allow open registration.
If you are connecting to a production or shared node, assume challenges are enforced — node operators may require them regardless of the default setting.

Challenge flow

1
Request a challenge
2
POST to /v1/providers/ownership-challenges with your provider_did and the operation you intend to perform — either "register" for a new provider or "rotate_key" to update an existing provider’s DID.
3
curl -X POST http://127.0.0.1:8042/v1/providers/ownership-challenges \
  -H 'content-type: application/json' \
  -d '{
    "provider_did": "did:key:z6MkhaXgBZDvotD1X9gRrYkM5Xq9jYQqK6d8r8bQdE1mV2Xa",
    "operation": "register"
  }'
4
The response contains the challenge you must sign:
5
{
  "challenge_id": "a3f1c2d4-e5b6-7890-abcd-ef1234567890",
  "provider_id": "prv_8f3a1b2c4d5e6f7a8b9c0d1e2f3a4b5c",
  "provider_did": "did:key:z6MkhaXgBZDvotD1X9gRrYkM5Xq9jYQqK6d8r8bQdE1mV2Xa",
  "operation": "register",
  "challenge": "base64encodedRandomNonce==",
  "issued_at": "2025-01-15T10:00:00Z",
  "expires_at": "2025-01-15T10:05:00Z"
}
6
If you omit provider_id from the challenge request, the node automatically generates a unique ID prefixed with prv_. Use the provider_id from the challenge response when you submit the registration request.
7
Sign the challenge with your Ed25519 private key
8
Sign the challenge string (UTF-8 encoded) using the Ed25519 private key that corresponds to your provider_did. Encode the resulting 64-byte signature as standard base64.
9
import base64
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

# Load your Ed25519 private key
private_key = Ed25519PrivateKey.from_private_bytes(your_key_bytes)

# Sign the challenge string (UTF-8 encoded)
challenge_bytes = challenge_string.encode('utf-8')
signature = private_key.sign(challenge_bytes)
ownership_signature = base64.b64encode(signature).decode()
10
Sign the raw challenge string exactly as returned in the response — do not base64-decode it first. The node verifies challenge.as_bytes() directly.
11
Register with the signed challenge
12
POST to /v1/providers/register and include ownership_challenge_id and ownership_signature alongside your provider details. Use the provider_id from the challenge response.
13
curl -X POST http://127.0.0.1:8042/v1/providers/register \
  -H 'content-type: application/json' \
  -d '{
    "provider_id": "<PROVIDER_ID_FROM_STEP_1>",
    "provider_did": "did:key:z6MkhaXgBZDvotD1X9gRrYkM5Xq9jYQqK6d8r8bQdE1mV2Xa",
    "display_name": "Acme Labs",
    "ownership_challenge_id": "<CHALLENGE_ID_FROM_STEP_1>",
    "ownership_signature": "<BASE64_SIGNATURE_FROM_STEP_2>"
  }'
14
A successful registration returns 201 Created with the new ProviderRecord. Each challenge can only be consumed once — the node records completed_at on the challenge after it is verified and rejects any attempt to reuse it.

Fetching a challenge by ID

If you need to inspect an issued challenge — for example, to check its expires_at or provider_id — retrieve it by ID:
curl http://127.0.0.1:8042/v1/providers/ownership-challenges/<CHALLENGE_ID>
The response is the same ProviderOwnershipChallenge object returned when the challenge was created.

Key rotation challenges

The same challenge flow applies when rotating a provider’s DID key. Request a challenge with "operation": "rotate_key" and include the provider_id of the existing provider you are updating:
curl -X POST http://127.0.0.1:8042/v1/providers/ownership-challenges \
  -H 'content-type: application/json' \
  -d '{
    "provider_id": "acme-labs",
    "provider_did": "did:key:z6MkNewKeyABCDEF...",
    "operation": "rotate_key"
  }'
Sign the returned challenge with the new key’s private key, then include the challenge ID and signature in the rotation request. See Rotate or Revoke a Provider’s DID Key for the complete rotation flow.

Challenge request fields

provider_did
string
required
The Ed25519 did:key DID you want to associate with the provider. For rotate_key operations, this is the new DID you are rotating to.
operation
string
required
The operation you intend to complete after signing. Must be "register" or "rotate_key".
provider_id
string
The provider ID to associate with this challenge. Optional for register operations — if omitted, the node generates a unique ID. Required for rotate_key operations to identify the existing provider being updated.
Challenges expire after 5 minutes (configurable via SERVICENET_PROVIDER_CHALLENGE_TTL_SECS on the node). If your challenge expires before you complete the registration or rotation, request a new one. Expired challenges are rejected with 400 Bad Request.