Key Resolution
Part of the Machine Payment Control Protocol (MPCP).
Overview
MPCP artifacts carry two fields that identify the signing authority:
issuer— identifies the authority (domain, HTTPS URL, or DID)issuerKeyId— selects the specific key within that authority's key set
Verifiers use these two fields to retrieve the public key required to validate the artifact signature.
MPCP defines HTTPS well-known as the baseline key resolution mechanism. All conforming implementations MUST support it. DID (W3C DID Core) resolution and Verifiable Credentials (W3C VC Data Model) are optional higher-level mechanisms that deployments MAY use in addition.
Canonical Key Format: JWK
MPCP uses JSON Web Key (JWK) format (RFC 7517) as the canonical representation for public keys.
All key set documents MUST express public keys as JWK objects. Verifiers MUST accept keys in JWK format.
Ed25519 (recommended)
{
"kid": "policy-auth-key-1",
"use": "sig",
"kty": "OKP",
"crv": "Ed25519",
"x": "base64url-encoded-32-byte-public-key"
}
secp256k1
{
"kid": "payment-auth-key-1",
"use": "sig",
"kty": "EC",
"crv": "secp256k1",
"x": "base64url-encoded-x-coordinate",
"y": "base64url-encoded-y-coordinate"
}
Required JWK fields for MPCP keys:
| Field | Description |
|---|---|
kid |
Key identifier. MUST match issuerKeyId in the artifact. |
use |
MUST be "sig". |
kty |
Key type. "OKP" for Ed25519; "EC" for secp256k1. |
crv |
Curve. "Ed25519" or "secp256k1". |
x |
Public key material (base64url-encoded). |
y |
y-coordinate for EC keys (base64url-encoded). |
Private key material (d) MUST NOT be present in key set documents.
HTTPS Well-Known Endpoint
Endpoint
GET https://{issuer-domain}/.well-known/mpcp-keys.json
The endpoint MUST be served over HTTPS. Verifiers MUST validate the TLS certificate. Plaintext HTTP MUST NOT be used.
Deriving the URL from issuer
issuer format |
Derived URL |
|---|---|
Bare domain: operator.example.com |
https://operator.example.com/.well-known/mpcp-keys.json |
HTTPS URL: https://operator.example.com |
https://operator.example.com/.well-known/mpcp-keys.json |
did:web:operator.example.com |
https://operator.example.com/.well-known/mpcp-keys.json |
did:web:operator.example.com:path:to:key |
https://operator.example.com/path/to/key/.well-known/mpcp-keys.json |
Key Set Document Format
{
"version": "1.0",
"keys": [
{
"kid": "policy-auth-key-1",
"use": "sig",
"kty": "OKP",
"crv": "Ed25519",
"x": "base64url..."
},
{
"kid": "policy-auth-key-2",
"use": "sig",
"kty": "EC",
"crv": "secp256k1",
"x": "base64url...",
"y": "base64url..."
}
]
}
The document is a plain JSON object. The keys array contains one or more JWK entries. An authority MAY publish multiple keys to support key rotation.
HTTP Response Requirements
| Requirement | Value |
|---|---|
| Content-Type | application/json |
| Status | 200 OK for valid responses |
| Encoding | UTF-8 |
Verifiers SHOULD cache key set responses according to standard HTTP cache semantics (Cache-Control, ETag). Verifiers MUST revalidate cached responses before use when they have expired.
Resolution Algorithm
Verifiers MUST attempt resolution in the following priority order:
function resolvePublicKey(issuer, issuerKeyId):
# 1. Trust Bundle lookup (offline / pre-distributed)
jwk = resolveFromTrustBundle(issuer, issuerKeyId)
if jwk: return jwk
# 2. HTTPS well-known (online)
url = deriveKeySetUrl(issuer)
keySetDoc = httpGet(url) # HTTPS required; TLS validated
keys = parseKeySet(keySetDoc)
jwk = keys.find(k => k.kid == issuerKeyId)
if jwk: return jwk
# 3. DID resolution (optional, online)
if issuer starts with "did:" and not "did:web:":
jwk = resolveDid(issuer, issuerKeyId) # method-specific; see DID section below
if jwk: return jwk
raise KEY_NOT_FOUND
If no resolution path succeeds, the verifier MUST fail the signature check and reject the artifact.
Trust Bundle Resolution
Trust Bundles are the primary resolution mechanism for deployments that operate without network access at verification time.
A Trust Bundle is a signed, distributable document containing the public keys of approved issuers for a given scope. Verifiers load one or more Trust Bundles at startup (or after periodic refresh) and consult them before attempting any live network call.
function resolveFromTrustBundle(issuer, issuerKeyId):
for each bundle in loadedBundles (sorted by expiresAt desc):
if bundle is expired: continue
if issuer not in bundle.approvedIssuers: continue
entry = bundle.issuers.find(e => e.issuer == issuer)
if entry is null: continue # approved but no embedded keys — fall through
jwk = entry.keys.find(k => k.kid == issuerKeyId)
if jwk: return jwk
return null
Trust Bundles are optional. If no Trust Bundle is loaded, resolution proceeds directly to the HTTPS well-known step.
See Trust Bundles for the full specification including signing, lifecycle, and offline revocation considerations.
Pre-Configured Keys
Individual keys MAY be pre-configured directly on a verifier, identified by issuer + issuerKeyId and expressed as JWK objects. When a matching entry is found, the verifier uses it without fetching the well-known endpoint.
Trust Bundles are the recommended mechanism for distributing pre-configured keys at scale. Direct pre-configuration is appropriate for single-key pinning or testing environments.
This supports:
- offline and air-gapped machine wallets
- embedded deployments where network access is unavailable
- pinned deployments where key mutation must be prevented
Pre-configured keys MUST still be expressed in JWK format.
DID and Verifiable Credentials (Optional)
Deployments MAY use decentralized identifiers (DIDs) or Verifiable Credentials (VCs) as a higher-level key binding mechanism.
When the issuer field carries a did: URI other than did:web:, verifiers MAY use DID resolution to retrieve the key material. Resolved key material MUST still be expressed and validated as JWK.
VC-based key discovery is outside the core protocol.
DID resolution and VC verification are never required for MPCP compliance. Implementations that support only HTTPS well-known key resolution are fully conformant.
DID Resolution — Example: did:xrpl
The following shows how a W3C DID can be resolved to obtain signing keys. The did:xrpl method
is used as a concrete example; other DID methods (e.g. did:hedera, did:key, did:web)
follow the same general pattern with method-specific resolution logic as defined by the
W3C DID Core specification.
DID Format
did:xrpl:{network}:{rAddress}
Examples:
- did:xrpl:mainnet:rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh
- did:xrpl:testnet:rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe
Network RPC Defaults
| Network | Default RPC Endpoint |
|---|---|
mainnet |
https://xrplcluster.com |
testnet |
https://s.altnet.rippletest.net:51234 |
Resolution Algorithm
- Parse network and account from DID string
- Call XRPL
account_objectsJSON-RPC withtype: "DID"to retrieve the DID object - Hex-decode the
DIDDocumentfield of the returned DID ledger entry - Parse the decoded string as JSON to obtain the DID Document
- Extract
verificationMethod[0].publicKeyJwk - Return the JWK
function resolveXrplDid(did):
(network, account) = parseDid(did)
rpcUrl = networkRpc(network)
response = xrplRpc(rpcUrl, "account_objects", { account, type: "DID" })
didObject = response.result.account_objects[0]
didDocumentJson = hexDecode(didObject.DIDDocument)
didDocument = JSON.parse(didDocumentJson)
return didDocument.verificationMethod[0].publicKeyJwk
Reference Implementation
import { resolveXrplDid } from "mpcp-service/sdk";
const result = await resolveXrplDid(
"did:xrpl:mainnet:rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
{ rpcUrl: "https://xrplcluster.com", timeoutMs: 5000 },
);
if ("error" in result) {
// resolution failed
} else {
const jwk = result.jwk; // JsonWebKey
}
Error Codes
| Code | Condition |
|---|---|
invalid_did_format |
DID does not match did:xrpl:{network}:{rAddress} |
unknown_xrpl_network |
Network is not mainnet or testnet and no rpcUrl provided |
xrpl_rpc_fetch_failed |
Network error calling the XRPL JSON-RPC endpoint |
xrpl_rpc_http_error |
Non-200 HTTP response from the RPC endpoint |
xrpl_did_not_found |
No DID object found for the account |
xrpl_did_document_missing |
DIDDocument field absent or empty |
xrpl_did_document_invalid_hex |
DIDDocument field is not valid hex |
xrpl_did_document_invalid_json |
Decoded DIDDocument is not valid JSON |
xrpl_did_no_verification_method |
DID Document has no verificationMethod entries |
xrpl_did_no_public_key_jwk |
verificationMethod[0] has no publicKeyJwk |
Error Codes
| Code | Condition |
|---|---|
KEY_NOT_FOUND |
issuerKeyId not present in the resolved key set |
KEY_SET_FETCH_FAILED |
Well-known endpoint unreachable or returned non-200 |
KEY_SET_INVALID |
Key set document could not be parsed |
KEY_FORMAT_INVALID |
Key entry is not a valid JWK |