How it works
The generating enclave signs with CoinCover’s private signing key. You verify with the matching public key, which we publish as a JSON Web Key Set (JWKS). The signature is ECDSA over P-256 with SHA-256 (ES256). It is base64-encoded DER, not a compact JWS. Verify it with a standard ECDSA verifier; do not treat it as a JWT.
What the signature covers depends on the request:
signed_payload always includes public_key alongside the fields you named. Object keys are sorted. Do not re-serialise the JSON before verifying — use the string we returned.
JWKS endpoints
CoinCover publishes a JWKS per environment. Use the document that matches the API you are calling — production signatures will not verify against the UAT keys, and the reverse is also true.UAT is the non-production environment these guides use for integration. Develop against it when you want to verify signatures and exercise backup flows without initiating production recoveries. It is the environment the rest of this site currently calls sandbox, and the UAT name is due to change to sandbox — until that rename ships, the UAT hosts above are the ones to use.
kid enclave-signing-key-v1 but the key material is different, so a cache keyed only on kid will mix environments.
Response format
The shape is the same in every environment. This example is production; UAT publishes differentx and y values (and can share the same kid string).
Only accept a JWKS entry that is
EC / P-256 / ES256 / use: sig, includes assignment-sig in coincover:purposes, and has a validFrom in the past. Other purposes (for example policy-sig) are not valid for key-assignment signatures unless they also list assignment-sig.
Verifying a signature
1
Load CoinCover's signing keys
Fetch the JWKS for the environment you called, or use a cache indexed by environment and
kid (see Caching JWKS). Keep only keys that match the assignment-signing constraints above.2
Select the verification key
Look the key up by
kid when you have one. Otherwise, use the eligible assignment-signing keys from the cache. Never verify with the public_key in the key-assignment response — that is the recovery key, not CoinCover’s attestation key.3
Decode the signature
Decode
signature from standard Base64 to raw bytes.4
Choose the signed bytes
If
signed_payload is present, verify over those exact UTF-8 bytes. Otherwise verify over the UTF-8 bytes of the hex public_key string.5
Verify ECDSA
Verify ECDSA P-256 / SHA-256 over those bytes with the JWKS public key. Libraries that distinguish encodings should be told the signature is DER, not IEEE P1363.
6
Accept or reject
If verification fails, reject the response. Do not retry with a reconstructed payload.
Checking the binding
When you sentsign_with, also check that the attested values match the assignment you just made:
signed_fieldsis exactly the list you requested, in that order.- The parsed
signed_payloadincludespublic_keymatching the response. - Any other bound field you asked for (
key_id,user_id,external_customer_id, and so on) matches the assignment you just made. - If you bound
key_fingerprint, it equals the SHA-256 of the public-key bytes (hex-decodepublic_key, then hash), not the SHA-256 of the hex string.
Caching JWKS
Each JWKS endpoint returnsCache-Control: max-age=600, must-revalidate (ten minutes). Cache the document for that environment and index the keys by kid. Lookups should hit that map rather than refetching on every key assignment. Keep a separate cache (or a composite key of environment + kid) for UAT and production.
During rotation the document can contain more than one key. kid is how you pick the right one within that environment. If verification fails because the kid is unknown, refresh the JWKS for that environment and retry once as the signing key may have rotated. Do not pin a single key forever.
Respect max-age. A cache that never expires will miss rotation; a cache that never stores will add latency and a runtime dependency on the JWKS host for every assignment.