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 theSERVICENET_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
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.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"
}'
{
"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"
}
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.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.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()
Sign the raw
challenge string exactly as returned in the response — do not base64-decode it first. The node verifies challenge.as_bytes() directly.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.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>"
}'
Fetching a challenge by ID
If you need to inspect an issued challenge — for example, to check itsexpires_at or provider_id — retrieve it by ID:
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:
Challenge request fields
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.The operation you intend to complete after signing. Must be
"register" or "rotate_key".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.