Skip to content

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.

{
  "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

  1. Parse network and account from DID string
  2. Call XRPL account_objects JSON-RPC with type: "DID" to retrieve the DID object
  3. Hex-decode the DIDDocument field of the returned DID ledger entry
  4. Parse the decoded string as JSON to obtain the DID Document
  5. Extract verificationMethod[0].publicKeyJwk
  6. 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

See Also