MoltProtocol Specification
Version: 1.0.0-draft
Status: Draft
Date: 2026-03-03
Authors: MoltPhone Contributors
Abstract
MoltProtocol is a telephony signaling protocol for AI agents. It defines
how agents authenticate, route tasks, verify carrier deliveries, and
establish trust β layered on top of Google's Agent-to-Agent (A2A)
protocol as the wire format.
The relationship to A2A is analogous to SIP on TCP/IP: A2A provides
message transport and task lifecycle; MoltProtocol adds identity
verification, carrier routing, inbound policies, call forwarding,
presence, and a STIR/SHAKEN-inspired carrier attestation framework.
This document specifies the protocol in full: Ed25519 authentication,
canonical signing formats, carrier identity headers, the two-level
certificate chain, MoltUA client compliance levels, Agent Card
extensions, and MoltSIM credential profiles.
Table of Contents
- Conventions
- Introduction
- Terminology
- Protocol Overview
- Task Model
- Agent Authentication
- Carrier Routing
- Carrier Identity
- Certificate Chain
- MoltUA Compliance
- Agent Card
- MoltSIM Profile
- Direct Connections
- Presence
- Error Codes
- Security Considerations
- IANA Considerations
- References
- Appendix A β Canonical String Examples
- Appendix B β Design Rationale
1. Conventions
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in [RFC 2119].
2. Introduction
Existing agent communication standards (A2A, MCP, ACP) define message
formats and task lifecycles but do not address telephony concerns:
- Who is calling? How does the target verify the caller's identity?
- Who may call? How does an agent restrict inbound traffic?
- Who delivered this? How does an endpoint verify that a request
- Where is the agent? How does activity-based presence work?
- What if the agent is busy? How are tasks queued, forwarded, or
MoltProtocol answers these questions. It is designed for a world where
AI agents have phone numbers ([MoltNumbers][MoltNumber Spec]), make and
receive calls (tasks), and operate through carriers β but where the
underlying wire format is A2A, not SIP.
2.1 Layering
βββββββββββββββββββββββββββββββββββββββββββββββ
β Application Layer β
β (Agent business logic, LLM, tools) β
βββββββββββββββββββββββββββββββββββββββββββββββ€
β MoltProtocol Layer β
β (Identity, routing, policy, presence) β
βββββββββββββββββββββββββββββββββββββββββββββββ€
β A2A Transport Layer β
β (JSON-RPC 2.0, task lifecycle, SSE) β
βββββββββββββββββββββββββββββββββββββββββββββββ€
β HTTPS β
βββββββββββββββββββββββββββββββββββββββββββββββ
| Layer | Analogy | Defines |
|---|---|---|
| Application | Phone app | What the agent does |
| MoltProtocol | SIP | Who, where, how (signaling) |
| A2A | TCP/IP | Message format and delivery |
| HTTPS | Physical layer | Encrypted transport |
2.2 Scope
MoltProtocol defines:
- Ed25519 canonical signing format for agent authentication
- Carrier identity headers (STIR/SHAKEN-inspired attestation)
- Two-level certificate chain (Root β Carrier β Agent)
- MoltUA client compliance levels
- Agent Card
x-moltextensions - MoltSIM credential profiles
- Task routing semantics (policy, forwarding, DND, busy)
- Presence heartbeats
- Direct connection upgrade handshake
- Error codes (SIP-inspired)
MoltProtocol does NOT define:
- MoltNumber format or derivation (see [MoltNumber Spec])
- A2A message format or task lifecycle (see [A2A Protocol])
- Application-level agent behavior
- Billing or metering
2.3 Goals
- Carrier-mediated by default. All traffic flows through the
- Cryptographic identity. Ed25519 signatures prove caller identity
- Defense in depth. Carrier identity verification makes leaked
- Interoperable. Any standard A2A client can call a MoltProtocol
A2A agents by URL.
- Telephony-flavored. Concepts map to familiar telephony: calls,
3. Terminology
Carrier
: An implementation that mediates agent-to-agent communication.
Analogous to a telephone carrier. Example: MoltPhone.
Caller
: The agent initiating a task.
Target (or Callee)
: The agent receiving a task.
Task
: A unit of agent-to-agent communication, corresponding to an A2A task.
Tasks have an intent (call or text) and a lifecycle (Section 5).
Intent
: The communication mode: call (multi-turn, streaming) or text
(fire-and-forget, single message).
MoltNumber
: A self-certifying agent identifier as defined in the [MoltNumber
Spec]. Format: NATION-AAAA-BBBB-CCCC-DDDD.
MoltSIM
: A machine-readable credential profile containing everything an
autonomous client needs to operate as an agent (Section 12).
MoltUA
: A MoltProtocol User Agent β any software that operates as a
MoltProtocol agent endpoint. Named after SIP User Agents (RFC 3261
Β§6). See Section 10.
Agent Card
: An A2A discovery document extended with MoltProtocol-specific fields
in the x-molt namespace (Section 11).
Inbound Policy
: An agent's access control rule for incoming tasks (Section 7.2).
Attestation Level
: The carrier's confidence in the caller's identity, modeled on
STIR/SHAKEN (Section 8.3).
Dial Route
: A carrier endpoint for sending tasks to an agent, hosted on a
dedicated subdomain. Format: https://call.{carrier}/{moltnumber}/...
4. Protocol Overview
4.1 Carrier as Mediating Proxy
The carrier receives standard A2A requests on its call routes, applies
MoltProtocol telephony logic (authentication, policy, forwarding, DND),
and forwards as standard A2A to the target agent's webhook endpoint.
ββββββββββ A2A + Molt headers ββββββββββββ A2A + Identity headers ββββββββββ
β Caller β βββββββββββββββββββββββββββΆ β Carrier β βββββββββββββββββββββββββββββΆ β Target β
β Agent β β (proxy) β β Agent β
ββββββββββ ββββββββββββ ββββββββββ
X-Molt-Caller X-Molt-Identity
X-Molt-Signature X-Molt-Identity-Attest
X-Molt-Timestamp X-Molt-Identity-Timestamp
X-Molt-Nonce
The caller authenticates to the carrier using Ed25519 signatures
(Section 6). The carrier authenticates to the target using carrier
identity headers (Section 8). The target's endpointUrl is NEVER
exposed in any public surface.
4.2 Dial Routes
All carrier endpoints for agent communication live on a dedicated
subdomain, separate from the carrier's web UI:
https://call.{carrier}/{moltnumber}/...
| Route | Method | Description |
|---|---|---|
/{number}/agent.json | GET | Agent Card (A2A discovery) |
/{number}/tasks/send | POST | Send a task |
/{number}/tasks/sendSubscribe | POST | Send + subscribe (SSE stream) |
/{number}/tasks | GET | Poll inbox (authenticated) |
/{number}/tasks/{id}/reply | POST | Reply to a queued task |
/{number}/tasks/{id}/cancel | POST | Cancel / hang up |
/{number}/presence/heartbeat | POST | Presence heartbeat |
The {number} parameter is a raw MoltNumber (URL-safe, no encoding
needed per [MoltNumber Spec] Section 4.4).
4.3 A2A Wire Format
All task-related requests use the A2A JSON-RPC 2.0 format:
{
"jsonrpc": "2.0",
"method": "tasks/send",
"params": {
"id": "task-uuid",
"message": {
"role": "user",
"parts": [{ "type": "text", "text": "Hello" }]
},
"metadata": {
"molt.intent": "call",
"molt.caller": "SOLR-12AB-C3D4-EF56"
}
}
}
MoltProtocol uses the metadata object with the molt. prefix
namespace for protocol-specific fields (Section 5.4).
5. Task Model
5.1 Intent
Every task has an intent that determines its communication semantics:
| Intent | A2A Behavior | Telephony Analogy |
|---|---|---|
call | Multi-turn conversation | Phone call |
text | Single message, no reply | SMS |
The intent is declared in task metadata as molt.intent and is
required. Omitting it returns a 400 Bad Request error.
A call intent task cycles between working and input-required
states until one party sends completed or canceled. A text intent
task transitions directly to completed after delivery.
5.2 Task States
MoltProtocol maps A2A task states to telephony semantics:
| A2A Status | MoltProtocol Meaning | Telephony Analogy |
|---|---|---|
submitted | Ringing / queued in inbox | Ringing |
working | Connected, agent is responding | Active call |
input-required | Agent's turn (multi-turn) | Hold / your turn |
completed | Hung up normally | Call ended |
canceled | Caller hung up | Caller hang-up |
failed | Error (see Section 15) | Call failed |
5.3 Messages
Each task contains an ordered sequence of messages. Each message has a
role (user for caller, agent for target) and an array of typed
parts:
| Part Type | Fields | Description |
|---|---|---|
text | type, text | Plain text content |
data | type, data | Structured JSON data |
file | type, mimeType, uri | File reference |
5.4 Metadata Namespace
MoltProtocol reserves the molt. prefix in A2A task metadata for
protocol-specific fields:
| Key | Type | Description |
|---|---|---|
molt.intent | string | call or text (Section 5.1) |
molt.caller | string | Caller MoltNumber |
molt.signature | string | Ed25519 signature (base64url) |
molt.forwarding_hops | number | Number of forwarding hops so far |
molt.propose_direct | bool | Propose direct connection upgrade |
molt.accept_direct | bool | Accept direct connection upgrade |
molt.upgrade_token | string | One-time token for direct upgrade |
Implementations MUST NOT use the molt. prefix for application-level
metadata. Implementations MUST ignore unrecognized molt.* keys.
6. Agent Authentication
6.1 Ed25519 Keypair
Each agent has an Ed25519 keypair generated at registration. The public
key is stored by the carrier and published in the Agent Card. The
private key is returned in the MoltSIM (shown once).
Key encoding:
| Key | Format | Encoding |
|---|---|---|
| Public | SPKI DER | base64url |
| Private | PKCS#8 DER | base64url |
The SPKI DER encoding includes the algorithm identifier (OID), which
makes the format algorithm-agnostic β the same signing protocol works
with Ed25519, ML-DSA, or any future scheme whose keys can be encoded
as SPKI/PKCS#8.
6.2 Canonical Signing Format
To authenticate a request, the caller constructs a canonical string and
signs it with Ed25519. The canonical string is deterministic β
identical inputs always produce the same string.
canonical-string = method LF path LF caller LF target LF
timestamp LF nonce LF body-hash
method = "GET" / "POST" / "PUT" / "PATCH" / "DELETE"
path = <URI path component, no query string>
caller = moltnumber ; caller's MoltNumber
target = moltnumber ; target's MoltNumber
timestamp = 1*DIGIT ; Unix seconds (UTC)
nonce = 1*( ALPHA / DIGIT / "-" ) ; random, unique
body-hash = 64HEXDIG ; SHA-256 of request body (hex, lowercase)
LF = %x0A ; newline
Construction procedure:
1. method β HTTP method (uppercase)
- path β URL pathname (e.g., "/MOLT-XXXX-.../tasks/send")
- caller β Caller's MoltNumber
- target β Target's MoltNumber
- timestamp β Current time as Unix seconds (UTC)
- nonce β Cryptographically random string
- body-hash β SHA-256(request body UTF-8), lowercase hex
For empty bodies: SHA-256("") =
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
- canonical β Join fields 1β7 with newline (LF, U+000A)
- signature β Ed25519.sign(private_key, canonical)
- Encode signature as base64url (no padding)
6.3 Request Headers
The caller sends four headers with each authenticated request:
| Header | Value | Required |
|---|---|---|
X-Molt-Caller | Caller's MoltNumber | Yes |
X-Molt-Timestamp | Unix timestamp (seconds, UTC) | Yes |
X-Molt-Nonce | Random nonce string | Yes |
X-Molt-Signature | Ed25519 signature (base64url) | Yes |
All four headers MUST be present for authenticated requests. For
public inbound policy agents, the carrier MAY accept requests without
authentication headers (Section 7.2).
6.4 Verification Procedure
The carrier verifies signatures as follows:
1. Extract X-Molt-Caller, X-Molt-Timestamp, X-Molt-Nonce,
X-Molt-Signature from request headers.
- Look up the caller agent by MoltNumber. Retrieve the stored
public key.
- Verify timestamp window: |now - timestamp| β€ 300 seconds.
If outside window β reject (replay / clock skew).
- Check nonce: look up nonce in the replay store
(keyed by caller:nonce). If present β reject (replay).
- Reconstruct the canonical string from the request.
- Verify: Ed25519.verify(public_key, canonical, signature).
If invalid β reject (forgery / tampering).
- Record the nonce with a TTL of 600 seconds (10 minutes).
- Request is authenticated.
6.5 Constants
| Constant | Value | Description |
|---|---|---|
TIMESTAMP_WINDOW_SECONDS | 300 | Maximum clock skew tolerance (Β±5min) |
NONCE_TTL_SECONDS | 600 | Nonce replay window (10min) |
The nonce TTL MUST be at least 2 Γ TIMESTAMP_WINDOW_SECONDS to ensure
that any valid timestamp within the window has its nonce protected.
7. Carrier Routing
When the carrier receives a task for a target agent, it applies the
following routing logic in order. Each step either continues to the
next or terminates with an error response.
7.1 Block Check
The carrier checks two levels of blocks before any other logic:
- Carrier-wide blocks. Administrative blocks by agent ID, phone
per-agent logic.
- Per-agent blocks. The target agent's owner may block specific
If blocked, the carrier MUST return error code 403 (Section 15).
7.2 Inbound Policy Enforcement
Each agent declares an inbound policy that controls who may send tasks:
| Policy | Requirement |
|---|---|
public | No authentication required. Anyone may send tasks. |
registered_only | Caller MUST provide X-Molt-Caller with a valid MoltNumber. Ed25519 signature verified if present. |
allowlist | Caller MUST be authenticated AND present in the agent's allowlistAgentIds array. |
For registered_only and allowlist policies, the carrier MUST verify
the caller's Ed25519 signature (Section 6.4) before proceeding.
7.3 Call Forwarding
When forwarding is enabled, the carrier redirects inbound tasks to
another agent based on a condition:
| Condition | Triggers when |
|---|---|
always | Every inbound task |
when_offline | Target's lastSeenAt > 5 minutes ago |
when_busy | Target has hit maxConcurrentCalls |
when_dnd | Target has dndEnabled = true |
Forwarding rules:
- The carrier MUST follow forwarding chains up to a maximum of
MAX_FORWARDING_HOPS).
- The carrier MUST detect loops (an agent appearing twice in the
- The carrier MUST record the forwarding path in the task's
forwardingHops array for audit purposes.
- Policy enforcement (Section 7.2) is applied at the original
7.4 Do Not Disturb (DND)
If the final target has dndEnabled = true:
- The task is created with status
submitted(queued in inbox). - The carrier returns error code 487 with the agent's
awayMessage
task_id.
- A push notification MAY be sent to the agent if configured.
7.5 Busy (Concurrent Task Limit)
If the final target has reached maxConcurrentCalls active tasks
(status working):
- Stale tasks (status
workingwith no activity for 30 minutes)
completed before counting.
- If still at capacity, the task is created with status
submitted. - The carrier returns error code 486 with the agent's
awayMessage
task_id.
7.6 Online Delivery
If the final target is online (Section 14) and has an endpointUrl:
- The carrier validates the webhook URL against SSRF protections.
- The carrier signs the delivery with carrier identity headers
- The carrier forwards the A2A request to the webhook with a ring
- If the webhook returns 2xx within the timeout, the task transitions
working (for call intent) or completed (for text intent).
- If the webhook fails or times out, the task is queued as
submitted
7.7 Offline Queuing
If the final target is offline or has no endpointUrl:
- The task is created with status
submitted(queued in inbox). - The carrier returns error code 480 with the agent's
awayMessage
task_id.
- The agent retrieves queued tasks via inbox polling (Section 7.8).
7.8 Inbox
There is no separate voicemail concept. When an inbound task cannot be
delivered in real-time, it remains in submitted status. Pending tasks
are the inbox.
Poll inbox:
GET /{number}/tasks
Authenticated via Ed25519 (Section 6). Returns all pending tasks
(status submitted), ordered oldest-first. Also updates the agent's
lastSeenAt (acts as a presence heartbeat).
Reply to task:
POST /{number}/tasks/{id}/reply
{
"message": {
"role": "agent",
"parts": [{ "type": "text", "text": "Thanks for reaching out!" }]
}
}
Cancel task:
POST /{number}/tasks/{id}/cancel
7.9 Routing Flow Summary
Inbound task
β
βΌ
βββββββββββββββββββ βββββββββββββββββββ
β Carrier-wide ββββββΆβ Per-agent β
β block check β ok β block check β
ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ
β blocked β blocked
βΌ βΌ
403 error 403 error
β ok
βΌ
βββββββββββββββββββ
β Inbound policy β
β enforcement β
ββββββββββ¬βββββββββ
β ok
βΌ
βββββββββββββββββββ
β Call forwarding βββββ up to 3 hops
β resolution β
ββββββββββ¬βββββββββ
β
βΌ
ββββββ Final agent ββββββ
β β
ββββββ΄βββββ βββββββ΄ββββββ
β DND? β β Busy? β
β β 487 β β β 486 β
βββββββββββ βββββββββββββ
β no
βΌ
βββββββββββββββββββ
β Online + β
β endpoint? β
ββββββββββ¬βββββββββ
yes β no
βΌ βΌ
webhook queue
delivery (480)
8. Carrier Identity
MoltProtocol implements carrier-signed delivery authentication inspired
by the STIR/SHAKEN framework ([RFC 8224] / [RFC 8225]). The carrier
signs every webhook delivery with its Ed25519 key. Compliant MoltUA
implementations (Section 10) verify this signature to reject
unauthorized direct calls.
8.1 Analogy to STIR/SHAKEN
| STIR/SHAKEN (SIP) | MoltProtocol |
|---|---|
| Authentication Service | Carrier private key signs deliveries |
| SIP Identity header | X-Molt-Identity header |
| PASSporT token (RFC 8225) | Carrier Identity canonical string |
| Certificate / trust anchor | Carrier public key in MoltSIM |
| Verification Service | MoltUA verifyInboundDelivery() |
8.2 Canonical Signing Format
The carrier constructs a canonical string for each delivery:
carrier-identity = carrier-domain LF attestation LF
orig-number LF dest-number LF
timestamp LF body-hash
carrier-domain = 1*( ALPHA / DIGIT / "." / "-" )
attestation = "A" / "B" / "C"
orig-number = moltnumber / "anonymous"
dest-number = moltnumber
timestamp = 1*DIGIT ; Unix seconds (UTC)
body-hash = 64HEXDIG ; SHA-256 of body (hex, lowercase)
LF = %x0A
Construction procedure:
1. carrier-domain β Carrier's domain (e.g., "moltphone.ai")
- attestation β Attestation level (Section 8.3)
- orig-number β Caller's MoltNumber, or "anonymous"
- dest-number β Target's MoltNumber
- timestamp β Current time as Unix seconds (UTC)
- body-hash β SHA-256(request body UTF-8), lowercase hex
- canonical β Join fields 1β6 with newline (LF)
- signature β Ed25519.sign(carrier_private_key, canonical)
- Encode signature as base64url (no padding)
8.3 Attestation Levels
The carrier asserts its confidence in the caller's identity, following
the STIR/SHAKEN attestation model:
| Level | Name | Meaning |
|---|---|---|
| A | Full | Carrier verified caller via Ed25519 signature |
| B | Partial | Caller is registered (valid MoltNumber) but not signature-verified |
| C | Gateway | External or anonymous caller |
The carrier MUST set attestation to A only when the caller's Ed25519
signature has been cryptographically verified. B indicates the caller
provided a valid MoltNumber but did not sign the request (e.g., public
policy). C indicates an unknown or external caller.
8.4 Delivery Headers
Every webhook delivery from the carrier to a target agent MUST include
these headers:
| Header | Value |
|---|---|
X-Molt-Identity | Ed25519 signature (base64url) |
X-Molt-Identity-Carrier | Carrier domain (e.g., moltphone.ai) |
X-Molt-Identity-Attest | Attestation level (A, B, C) |
X-Molt-Identity-Timestamp | Unix seconds (UTC) |
8.5 Verification
A MoltUA verifies an inbound delivery as follows:
1. Extract X-Molt-Identity, X-Molt-Identity-Carrier,
X-Molt-Identity-Attest, X-Molt-Identity-Timestamp from headers.
- Verify the carrier domain matches the expected carrier
(from MoltSIM carrier_domain field).
- Verify timestamp window: |now - timestamp| β€ 300 seconds.
- Reconstruct the canonical string from the delivery.
- Verify: Ed25519.verify(carrier_public_key, canonical, signature).
The carrier_public_key is sourced from the MoltSIM.
- If all checks pass β delivery is trusted.
8.6 Carrier Keypair Management
- Production: Carrier private and public keys are loaded from
CARRIER_PRIVATE_KEY, CARRIER_PUBLIC_KEY).
These MUST be stable β rotating them invalidates all existing
MoltSIMs.
- Development: An ephemeral keypair MAY be auto-generated per
- Distribution: The carrier's public key is included in every
carrier_public_key.
9. Certificate Chain
MoltProtocol implements a multi-level certificate chain for offline
trust verification, analogous to TLS certificate chains:
Root Authority ββsignsβββΆ Carrier ββsignsβββΆ Agent
(moltprotocol.org) (moltphone.ai) (MOLT-XXXX-...)
β²
Nation (org/carrier) ββdelegatesββββ (optional, for org/carrier nations)
All certificates are Ed25519 signatures over deterministic canonical
strings. No X.509, no ASN.1, no JWTs β raw Ed25519 over plain text.
9.1 Carrier Certificate (Root β Carrier)
The root authority signs a statement that a carrier's public key is
authorized to operate under a given domain. Anyone with the root
public key can verify offline that a carrier is legitimate.
Canonical signing format:
carrier-cert-canonical = "CARRIER_CERT" LF "1" LF
carrier-domain LF carrier-public-key LF
issued-at LF expires-at LF issuer
carrier-domain = 1*( ALPHA / DIGIT / "." / "-" )
carrier-public-key = base64url ; SPKI DER
issued-at = 1*DIGIT ; Unix seconds
expires-at = 1*DIGIT ; Unix seconds
issuer = 1*( ALPHA / DIGIT / "." / "-" )
LF = %x0A
Certificate structure (JSON):
{
"version": "1",
"carrier_domain": "moltphone.ai",
"carrier_public_key": "<base64url SPKI DER>",
"issued_at": 1719936000,
"expires_at": 1751472000,
"issuer": "moltprotocol.org",
"signature": "<base64url Ed25519>"
}
Verification:
- Verify
issuermatches the expected root authority. - Verify the certificate is within validity period
issued_at β€ now β€ expires_at).
- Reconstruct canonical string from certificate fields.
- Verify Ed25519 signature using the root authority's public key.
9.2 Registration Certificate (Carrier β Agent)
When an agent is registered (or re-provisioned), the carrier signs a
statement binding the agent's MoltNumber, public key, and nation code
to the carrier. Anyone with the carrier's public key can verify offline
that the agent was registered.
Canonical signing format:
reg-cert-canonical = "REGISTRATION_CERT" LF "1" LF
phone-number LF agent-public-key LF
nation-code LF carrier-domain LF issued-at
phone-number = moltnumber
agent-public-key = base64url ; SPKI DER
nation-code = 4ALPHA
carrier-domain = 1*( ALPHA / DIGIT / "." / "-" )
issued-at = 1*DIGIT ; Unix seconds
LF = %x0A
Certificate structure (JSON):
{
"version": "1",
"molt_number": "SOLR-12AB-C3D4-EF56",
"agent_public_key": "<base64url SPKI DER>",
"nation_code": "SOLR",
"carrier_domain": "moltphone.ai",
"issued_at": 1719936000,
"signature": "<base64url Ed25519>"
}
Verification:
- Optionally verify
carrier_domainmatches expectations. - Reconstruct canonical string from certificate fields.
- Verify Ed25519 signature using the carrier's public key.
9.3 Delegation Certificate (Nation β Carrier)
For org and carrier type nations (see [MoltNumber Spec] Section 9),
the nation owner MAY sign a delegation certificate authorizing a carrier
to register agents under their nation code. This enables multi-carrier
organizational namespaces.
Canonical signing format:
delegation-cert-canonical = "DELEGATION_CERT" LF "1" LF
nation-code LF nation-public-key LF
carrier-domain LF carrier-public-key LF
issued-at LF expires-at
nation-code = 4ALPHA
nation-public-key = base64url ; SPKI DER
carrier-domain = 1*( ALPHA / DIGIT / "." / "-" )
carrier-public-key = base64url ; SPKI DER
issued-at = 1*DIGIT ; Unix seconds
expires-at = 1*DIGIT / "" ; Unix seconds, or empty for no expiry
LF = %x0A
Certificate structure (JSON):
{
"version": "1",
"nation_code": "ACME",
"nation_public_key": "<base64url SPKI DER>",
"carrier_domain": "moltphone.ai",
"carrier_public_key": "<base64url SPKI DER>",
"issued_at": 1719936000,
"expires_at": null,
"signature": "<base64url Ed25519>"
}
Verification:
- Verify the nation public key in the certificate matches the expected
- Verify the certificate is within validity period (if
expires_atis
issued_at β€ now β€ expires_at).
- Reconstruct canonical string from certificate fields.
- Verify Ed25519 signature using the nation owner's public key.
Delegation certificates are OPTIONAL. They are only relevant for org
and carrier type nations where the nation owner delegates authority.
For open nations, no delegation is needed.
9.4 Full Chain Verification
To fully verify an agent's identity offline, a verifier performs the
following checks in order:
1. Self-certifying check β hash the agent's public key, confirm it
matches the MoltNumber. (Needs no keys β per MoltNumber Spec Β§6.)
- Registration certificate β verify the carrier signed the agent's
registration. (Needs carrier public key.)
- Carrier certificate β verify the root signed the carrier's
authorization. (Needs root public key.)
- Delegation certificate (org/carrier nations only) β verify the
nation owner authorized this carrier. (Needs nation public key.)
If all checks pass: the number matches the key, the carrier registered
it, the root authorized the carrier, and (for org nations) the
organization authorized the carrier.
9.5 Certificate Distribution
| Surface | Registration Cert | Carrier Cert | Delegation Cert |
|---|---|---|---|
Agent Card (x-molt) | β | via carrier well-known | β (org/carrier nations) |
| MoltSIM profile | β | β | β |
| Agent creation response | β | β | β |
The root authority's public key is distributed out-of-band (hardcoded
in implementations or fetched from the root's well-known endpoint).
10. MoltUA Compliance
MoltUA is the client compliance layer of MoltProtocol, named after the
SIP User Agent ([RFC 3261] Β§6). It defines what a conforming client
implementation MUST, SHOULD, and MAY implement when receiving
carrier-delivered tasks.
10.1 Compliance Levels
| Level | Name | Requirements |
|---|---|---|
| 1 | Baseline | MUST verify carrier identity signature on inbound deliveries. MUST reject unsigned or invalid signatures. |
| 2 | Standard | Level 1 + SHOULD verify caller Ed25519 signatures. SHOULD sign outbound requests. SHOULD implement presence heartbeats and inbox polling. |
| 3 | Full | Level 2 + MAY support direct connection upgrades (Section 13), SSE streaming, and push notifications. |
10.2 Level 1 β Baseline
A Level 1 compliant MoltUA MUST:
- Verify the
X-Molt-Identitycarrier signature on every inbound
- Reject requests without valid carrier identity headers (in strict
- Reject requests with timestamps outside the Β±300 second window.
- Use the
carrier_public_keyfrom the MoltSIM as trust anchor.
With Level 1 compliance alone, leaked endpoint URLs become
unexploitable β an attacker cannot forge the carrier's signature.
10.3 Level 2 β Standard
A Level 2 compliant MoltUA SHOULD additionally:
- Verify caller Ed25519 signatures (
X-Molt-Signature) when present. - Validate attestation levels from the carrier identity headers.
- Sign all outbound requests with the agent's Ed25519 private key.
- Send periodic presence heartbeats (Section 14).
- Poll the inbox for queued tasks.
10.4 Level 3 β Full
A Level 3 compliant MoltUA MAY additionally:
- Support direct connection upgrade handshakes (Section 13).
- Verify upgrade tokens against the carrier.
- Implement SSE streaming for multi-turn conversations
tasks/sendSubscribe).
- Support push notification handling.
10.5 Defense in Depth
| Layer | What | Cost | Solves |
|---|---|---|---|
| 1 | MoltUA carrier signature check | Free | Leaked endpoints unexploitable |
| 2 | carrier_only relay mode | Paid | Topology hiding + audit trail |
11. Agent Card
Each agent has an auto-generated [A2A Agent Card][A2A Protocol] served
at GET /{number}/agent.json on the carrier's call subdomain.
11.1 Standard A2A Fields
| Field | Source |
|---|---|
name | Agent displayName |
description | Agent description |
url | https://call.{carrier}/{number}/tasks/send |
provider | Carrier organization and URL |
version | Protocol version |
capabilities | Streaming, push notifications, state history |
skills | Agent's declared skills |
authentication | Scheme and required flag |
The url field MUST always point to the carrier's call route,
never the agent's real webhook endpoint.
11.2 x-molt Extension
MoltProtocol extends the Agent Card with an x-molt object containing
protocol-specific fields:
{
"x-molt": {
"molt_number": "SOLR-12AB-C3D4-EF56",
"nation": "SOLR",
"public_key": "<Ed25519 public key, base64url SPKI DER>",
"inbound_policy": "public",
"timestamp_window_seconds": 300,
"direct_connection_policy": "direct_on_consent",
"registration_certificate": {
"version": "1",
"molt_number": "SOLR-12AB-C3D4-EF56",
"agent_public_key": "<base64url>",
"nation_code": "SOLR",
"carrier_domain": "moltphone.ai",
"issued_at": 1719936000,
"signature": "<base64url>"
}
}
}
| Field | Type | Required | Description |
|---|---|---|---|
molt_number | string | Yes | Agent's MoltNumber |
nation | string | Yes | Nation code |
public_key | string | Yes | Ed25519 public key (base64url) |
inbound_policy | string | Yes | public, registered_only, allowlist |
timestamp_window_seconds | number | Yes | Accepted clock skew (seconds) |
direct_connection_policy | string | No | Privacy tier (Section 13) |
nation_type | string | No | Nation type (open, org, carrier) |
carrier_certificate_url | string | No | URL to carrier's .well-known/molt-carrier.json |
lexicon_url | string | No | URL to fetch this agent's Lexicon Pack |
registration_certificate | object | No | Carrier-signed registration |
delegation_certificate | object | No | NationβCarrier delegation cert (org/carrier nations) |
previous_numbers | string[] | No | Previous MoltNumbers after key rotation or porting |
11.3 Access Control
The Agent Card itself is access-controlled by the agent's inbound
policy:
public: Anyone may fetch the Agent Card.registered_only/allowlist: The GET request MUST include
verifies the caller's identity before returning the card.
11.4 Complete Example
{
"schema": "https://moltprotocol.org/a2a/agent-card/v1",
"name": "Solar Inspector",
"description": "An autonomous solar panel inspector",
"url": "https://call.moltphone.ai/SOLR-12AB-C3D4-EF56/tasks/send",
"provider": {
"organization": "MoltPhone",
"url": "https://moltphone.ai"
},
"version": "1.0",
"capabilities": {
"streaming": false,
"pushNotifications": false,
"stateTransitionHistory": true
},
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"skills": [
{ "id": "call", "name": "Call" },
{ "id": "text", "name": "Text" }
],
"authentication": {
"schemes": ["Ed25519"],
"required": false
},
"status": "online",
"x-molt": {
"molt_number": "SOLR-12AB-C3D4-EF56",
"nation": "SOLR",
"public_key": "MCowBQYDK2VwAyEA...",
"inbound_policy": "public",
"timestamp_window_seconds": 300,
"direct_connection_policy": "direct_on_consent",
"registration_certificate": {
"version": "1",
"molt_number": "SOLR-12AB-C3D4-EF56",
"agent_public_key": "MCowBQYDK2VwAyEA...",
"nation_code": "SOLR",
"carrier_domain": "moltphone.ai",
"issued_at": 1719936000,
"signature": "..."
}
}
}
12. MoltSIM Profile
A MoltSIM is a machine-readable credential that contains everything an
autonomous client needs to operate as an agent. It is analogous to a
physical SIM card: the MoltSIM is the credential, the MoltUA is the
phone.
12.1 Profile Structure
{
"version": "1",
"carrier": "moltphone.ai",
"agent_id": "<cuid>",
"molt_number": "SOLR-12AB-C3D4-EF56",
"nation_type": "open",
"public_key": "<Ed25519 public key, base64url SPKI DER>",
"private_key": "<Ed25519 private key, base64url PKCS#8 DER>",
"carrier_public_key": "<Ed25519 public key, base64url SPKI DER>",
"carrier_call_base": "https://call.moltphone.ai",
"inbox_url": "https://call.moltphone.ai/SOLR-12AB-C3D4-EF56/tasks",
"task_reply_url": "https://call.moltphone.ai/SOLR-12AB-C3D4-EF56/tasks/:id/reply",
"task_cancel_url": "https://call.moltphone.ai/SOLR-12AB-C3D4-EF56/tasks/:id/cancel",
"presence_url": "https://call.moltphone.ai/SOLR-12AB-C3D4-EF56/presence/heartbeat",
"signature_algorithm": "Ed25519",
"canonical_string": "METHOD\\nPATH\\nCALLER_AGENT_ID\\nTARGET_AGENT_ID\\nTIMESTAMP\\nNONCE\\nBODY_SHA256_HEX",
"timestamp_window_seconds": 300,
"registration_certificate": { "..." },
"carrier_certificate": { "..." }
}
12.2 Field Definitions
| Field | Type | Required | Description |
|---|---|---|---|
version | string | Yes | Profile format version ("1") |
carrier | string | Yes | Carrier domain |
agent_id | string | Yes | Carrier-internal agent identifier |
molt_number | string | Yes | Agent's MoltNumber |
nation_type | string | No | Nation type (open, org, carrier) |
public_key | string | Yes | Agent's Ed25519 public key (base64url SPKI) |
private_key | string | Yes | Ed25519 private key (base64url PKCS#8) |
carrier_public_key | string | Yes | Carrier's public key for delivery verification |
carrier_call_base | string | Yes | Base URL for this agent's call routes |
inbox_url | string | No | Full URL for inbox polling |
task_reply_url | string | No | URL template for task replies (:id placeholder) |
task_cancel_url | string | No | URL template for task cancellation (:id placeholder) |
presence_url | string | No | Full URL for presence heartbeats |
signature_algorithm | string | Yes | Signing algorithm identifier |
canonical_string | string | No | Template for canonical string construction |
timestamp_window_seconds | number | No | Accepted clock skew |
registration_certificate | object | No | Carrier-signed registration (Section 9.2) |
carrier_certificate | object | No | Root-signed carrier cert (Section 9.1) |
12.3 Lifecycle
- Generation. A MoltSIM is generated at agent creation or
NOT be stored by the carrier after delivery.
- Re-provisioning. Generating a new MoltSIM rotates the Ed25519
MoltSIM is instantly revoked β signatures from the old key will
fail verification.
- QR Code. Carriers MAY provide the MoltSIM as a QR code for
12.4 MoltSIM vs Agent Card
| Aspect | MoltSIM (private) | Agent Card (public) |
|---|---|---|
| Audience | The agent itself | Other agents / clients |
| Contains | Private key, carrier endpoints | Name, skills, inbound URL |
| Shown | Once, at creation or re-prov. | Always, via agent.json |
| Purpose | Operate as the agent | Discover and contact agent |
| Shared field | molt_number | molt_number |
13. Direct Connections
Initial contact between agents always flows through the carrier. After
mutual consent, agents MAY upgrade to direct A2A connections β
bypassing the carrier for subsequent communication.
13.1 Direct Connection Policy
Each agent sets a directConnectionPolicy:
| Policy | Behavior |
|---|---|
direct_on_consent | Default. Both parties agree β carrier shares endpoints. |
direct_on_accept | Target opts in to receive direct connection offers. |
carrier_only | All traffic always through carrier. Endpoint never shared. |
13.2 Upgrade Handshake
1. Caller sends task with molt.propose_direct = true in metadata.
- Target responds with molt.accept_direct = true and a one-time
molt.upgrade_token in metadata.
- Carrier validates the token and shares the target's endpointUrl
with the caller.
- Post-upgrade: agents communicate directly via A2A. The carrier
is out of the loop.
The endpointUrl is NEVER included in any public response (Agent Card,
MoltPage, or API). It is only visible in the agent owner's settings
and during the upgrade handshake.
13.3 Security Considerations for Direct Connections
- Direct connections bypass carrier identity verification. Both agents
- The upgrade token is one-time use β replay MUST be rejected.
- Agents with
carrier_onlypolicy MUST NOT participate in the
carrier_onlyrelay traffic is a paid feature β the target's owner
14. Presence
Agents signal liveness by sending periodic heartbeats to the carrier.
14.1 Heartbeat
POST /{number}/presence/heartbeat
Authenticated via Ed25519 (Section 6). The carrier updates the agent's
lastSeenAt timestamp.
14.2 Online Threshold
An agent is considered online if:
now - lastSeenAt β€ PRESENCE_ONLINE_SECONDS
| Constant | Value | Description |
|---|---|---|
PRESENCE_ONLINE_SECONDS | 300 | Online threshold (5 minutes) |
Agents SHOULD send heartbeats at intervals shorter than the online
threshold (RECOMMENDED: every 60 seconds).
14.3 Implicit Heartbeats
Inbox polling (GET /{number}/tasks) also updates lastSeenAt. An
agent that regularly polls its inbox does not need separate heartbeats.
15. Error Codes
MoltProtocol uses structured error codes modeled on SIP response codes.
Errors are returned as JSON-RPC 2.0 error objects:
{
"jsonrpc": "2.0",
"error": {
"code": 404,
"message": "Agent not found"
},
"id": null
}
15.1 Client Errors (4xx)
| Code | Constant | Meaning | SIP Analog |
|---|---|---|---|
| 400 | MOLT_BAD_REQUEST | Malformed request | 400 |
| 401 | MOLT_AUTH_REQUIRED | Authentication needed | 401 |
| 403 | MOLT_POLICY_DENIED | Policy blocked | 403 |
| 404 | MOLT_NOT_FOUND | Number not found | 404 |
| 409 | MOLT_CONFLICT | State conflict | β |
| 410 | MOLT_DECOMMISSIONED | Number deactivated | 410 |
| 429 | MOLT_RATE_LIMITED | Too many requests | β |
15.2 Target Unavailable (4xx, SIP-Inspired)
| Code | Constant | Meaning | SIP Analog |
|---|---|---|---|
| 480 | MOLT_OFFLINE | Agent offline (queued) | 480 |
| 486 | MOLT_BUSY | Max concurrent (queued) | 486 |
| 487 | MOLT_DND | Do Not Disturb (queued) | 487 |
| 488 | MOLT_FORWARDING_FAILED | Forwarding chain failed | 488 |
Codes 480, 486, and 487 indicate that the task has been queued β
the caller receives a task_id and MAY poll for status. These are not
terminal failures.
15.3 Server Errors (5xx)
| Code | Constant | Meaning | SIP Analog |
|---|---|---|---|
| 500 | MOLT_INTERNAL_ERROR | Carrier error | 500 |
| 502 | MOLT_WEBHOOK_FAILED | Webhook delivery fail | 502 |
| 504 | MOLT_WEBHOOK_TIMEOUT | Webhook timed out | 504 |
15.4 Error Response Format
All error responses MUST include:
codeβ One of the constants above.messageβ Human-readable description.
Error responses MAY include:
dataβ Structured additional information (e.g.,task_id,
away_message, balance).
16. Security Considerations
16.1 Threat Model
MoltProtocol assumes:
- Carriers are trusted mediators (like telephone carriers).
- The network (HTTPS) provides transport confidentiality and integrity.
- Agents' private keys may be compromised (mitigated by re-provisioning).
- Webhook endpoint URLs may be leaked (mitigated by carrier identity).
16.2 Ed25519 Signature Security
Ed25519 provides 128-bit security against classical attacks. Each
signature is computed over a deterministic canonical string that
includes the HTTP method, path, both party identities, timestamp,
nonce, and body hash. This prevents:
- Replay attacks: Nonces are stored for 10 minutes and rejected
- Cross-method attacks: The HTTP method is part of the canonical
- Cross-target attacks: Both caller and target MoltNumbers are
replayed against agent B.
- Body tampering: The SHA-256 body hash is signed β the body
16.3 Carrier Identity Security
The carrier signs every webhook delivery with its own Ed25519 key. This
provides:
- Delivery authenticity: Only the carrier can produce valid
X-Molt-Identity signatures.
- Leaked endpoint protection: Knowing a webhook URL is useless
MoltUA compliance, leaked endpoints are unexploitable.
- Attestation transparency: The attestation level tells the target
16.4 Certificate Chain Security
The two-level certificate chain (Root β Carrier β Agent) enables
offline trust verification without contacting the carrier or root:
- The self-certifying MoltNumber check requires only the public key.
- The registration certificate requires only the carrier's public key.
- The carrier certificate requires only the root's public key.
Certificate expiry on carrier certificates ensures that compromised
carrier keys have a bounded trust window. Registration certificates
do not expire β they are implicitly revoked when the agent
re-provisions (new keypair = new registration certificate).
16.5 SSRF Protection
All webhook URLs (endpointUrl) MUST be validated before the carrier
dispatches requests. Private and internal IP ranges (RFC 1918, RFC 4193,
loopback, link-local) MUST be blocked.
16.6 Replay Protection
| Mechanism | Window | Protects Against |
|---|---|---|
| Timestamp window | Β±300s | Old/future requests |
| Nonce replay store | 600s TTL | Exact request replay within window |
| Body hash | Per-request | Body substitution |
The nonce TTL (600s) is exactly 2 Γ TIMESTAMP_WINDOW_SECONDS (300s),
ensuring full coverage: any timestamp within the valid window will have
its nonce tracked for the entire duration the timestamp remains valid.
16.7 Key Rotation
Re-provisioning a MoltSIM generates a new Ed25519 keypair. The old
public key is overwritten in the database, instantly revoking the old
MoltSIM. This is the only key rotation mechanism β there is no
multi-key support.
The MoltNumber changes when the keypair changes (since MoltNumbers are
derived from public keys). This is by design: identity IS the key.
16.8 Quantum Computing Considerations
Ed25519 is vulnerable to Shor's algorithm on a sufficiently large
quantum computer. MoltProtocol's signing format is algorithm-agnostic
(keys are SPKI/PKCS#8 encoded with algorithm OIDs), so migration to
post-quantum schemes (e.g., ML-DSA per FIPS 204) requires no protocol
format changes β only key generation and signing/verification
implementations change.
The canonical string format, header names, certificate structures, and
MoltUA verification procedures remain identical regardless of the
underlying signature algorithm.
See [MoltNumber Spec] Section 10.9 (Cryptographic Agility) and
Section 10.10 (Quantum Computing Considerations) for the numbering
layer implications.
17. IANA Considerations
17.1 HTTP Header Fields
This specification defines the following HTTP header fields for
provisional registration in the "Message Headers" registry:
| Header Name | Protocol | Status | Reference |
|---|---|---|---|
X-Molt-Caller | http | provisional | Section 6.3 |
X-Molt-Timestamp | http | provisional | Section 6.3 |
X-Molt-Nonce | http | provisional | Section 6.3 |
X-Molt-Signature | http | provisional | Section 6.3 |
X-Molt-Identity | http | provisional | Section 8.4 |
X-Molt-Identity-Carrier | http | provisional | Section 8.4 |
X-Molt-Identity-Attest | http | provisional | Section 8.4 |
X-Molt-Identity-Timestamp | http | provisional | Section 8.4 |
17.2 Metadata Namespace
This specification reserves the molt. prefix in A2A task metadata for
MoltProtocol-specific fields (Section 5.4).
18. References
18.1 Normative References
- [RFC 2119] Bradner, S., "Key words for use in RFCs to Indicate
- [RFC 3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform
January 2005.
- [RFC 5234] Crocker, D. and P. Overell, "Augmented BNF for Syntax
- [RFC 8032] Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital
- [FIPS 180-4] NIST, "Secure Hash Standard (SHS)", FIPS PUB 180-4,
- [FIPS 204] NIST, "Module-Lattice-Based Digital Signature Standard",
- [MoltNumber Spec] MoltPhone Contributors, "MoltNumber Specification",
18.2 Informative References
- [RFC 3261] Rosenberg, J. et al., "SIP: Session Initiation Protocol",
- [RFC 8224] Peterson, J. et al., "Authenticated Identity Management in
- [RFC 8225] Wendt, C. and J. Peterson, "PASSporT: Personal Assertion
- [A2A Protocol] Google, "Agent-to-Agent Protocol",
- [E.164] ITU-T, "The international public telecommunication numbering
Appendix A β Canonical String Examples
A.1 Agent Authentication (Section 6)
A caller with MoltNumber SOLR-12AB-C3D4-EF56 sends a task to
MOLT-YQZZ-23ND-Q5KW-17VA:
POST
/MOLT-YQZZ-23ND-Q5KW-17VA/tasks/send
SOLR-12AB-C3D4-EF56
MOLT-YQZZ-23ND-Q5KW-17VA
1719936000
a1b2c3d4e5f6
7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730
Fields (one per line):
- HTTP method
- URL path
- Caller MoltNumber
- Target MoltNumber
- Unix timestamp
- Random nonce
- SHA-256 of request body (lowercase hex)
A.2 Carrier Identity (Section 8)
The carrier moltphone.ai delivers a task from SOLR-12AB-C3D4-EF56
to MOLT-YQZZ-23ND-Q5KW-17VA with full attestation:
moltphone.ai
A
SOLR-12AB-C3D4-EF56
MOLT-YQZZ-23ND-Q5KW-17VA
1719936000
7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730
Fields (one per line):
- Carrier domain
- Attestation level
- Originating MoltNumber (or
anonymous) - Destination MoltNumber
- Unix timestamp
- SHA-256 of request body (lowercase hex)
A.3 Carrier Certificate (Section 9.1)
CARRIER_CERT
1
moltphone.ai
MCowBQYDK2VwAyEA...
1719936000
1751472000
moltprotocol.org
A.4 Registration Certificate (Section 9.2)
REGISTRATION_CERT
1
SOLR-12AB-C3D4-EF56
MCowBQYDK2VwAyEA...
SOLR
moltphone.ai
1719936000
Appendix B β Design Rationale
B.1 Why Carrier-Mediated?
Direct agent-to-agent communication exposes endpoint URLs, creating a
large attack surface. A carrier-mediated model:
- Hides endpoints behind the carrier's infrastructure.
- Enables policy enforcement, rate limiting, and block lists.
- Provides a natural point for attestation and audit.
- Mirrors the telephone network model that has scaled for a century.
Agents MAY opt into direct connections (Section 13) after mutual
consent, but carrier-mediated is the safe default.
B.2 Why Ed25519?
- Deterministic signatures. No random nonce in signing β identical
eliminates a class of implementation bugs.
- Small keys. 32 bytes for both public and private keys.
- Fast. Verification is ~3Γ faster than ECDSA P-256.
- Widely supported. Node.js
crypto, Web Crypto API, Go, Rust,
- Algorithm-agnostic encoding. SPKI/PKCS#8 DER encoding includes
without format changes.
B.3 Why STIR/SHAKEN-Inspired (Not STIR/SHAKEN Itself)?
STIR/SHAKEN (RFC 8224/8225) is designed for SIP over the PSTN:
PASSporTs are JWTs with X.509 certificate chains. This is heavy
machinery for an HTTP-native protocol:
- JWTs add parsing complexity and a dependency on JWT libraries.
- X.509 certificates are verbose and hard to verify offline.
- The PSTN trust model (certificate authorities, CPS, governance)
MoltProtocol keeps the core insight β **carrier-signed attestation of
caller identity** β but implements it with raw Ed25519 signatures over
canonical strings. This is simpler, faster, and aligned with the
Ed25519-native identity model.
B.4 Why Not JWTs?
JWTs (JSON Web Tokens) are a common choice for signed assertions.
MoltProtocol avoids them for several reasons:
- Canonicalization. JWTs require JSON serialization with specific
error-prone across implementations. Plain text canonical strings are
trivially deterministic.
- Algorithm confusion. JWT
algheader attacks are a well-known
the protocol, not in a per-message header.
- Size. A JWT carrying the same information as a MoltProtocol
- Dependencies. JWT parsing requires a dedicated library. Canonical
crypto.sign/verify.
B.5 Why No Separate Voicemail?
In MoltProtocol, pending tasks ARE the inbox. There is no separate
voicemail concept because:
- Tasks are already structured data (not audio recordings).
- The inbox polling mechanism (
GET /tasks) handles retrieval. - Reply-to-task (
POST /tasks/{id}/reply) handles responses. - This eliminates a redundant concept and simplifies the protocol.
B.6 Why Two-Level Certificates (Not Three)?
A three-level chain (Root β Regional β Carrier β Agent) would mirror
the TLS CA hierarchy but adds complexity without clear benefit in the
current deployment model. Two levels suffice:
- Root β Carrier establishes carrier legitimacy.
- Carrier β Agent establishes agent registration.
If regional governance becomes necessary (e.g., nation-specific
certificate authorities), an intermediate level can be added by
introducing a Regional Certificate between Root and Carrier. The
canonical string format supports this via version bumping.
_End of specification._
MoltNumber Specification
This is where the MoltNumber spec begins. MoltProtocol defines signaling, routing, and trust. MoltNumber defines the numbering and self-certifying identity layer used by the protocol.
Version: 1.0.0-draft
Status: Draft
Date: 2026-03-03
Authors: MoltPhone Contributors
Abstract
MoltNumber is a self-certifying numbering standard that assigns globally
unique, URL-safe identifiers to AI agents. Each MoltNumber is
cryptographically derived from a public key β the holder of the
corresponding private key is, by definition, the owner of the number. No
certificate authority, registry, or carrier is required for identity
verification.
MoltNumber is the numbering layer of the [MoltProtocol] telephony
standard: MoltProtocol defines signaling and routing; MoltNumber defines
addressing and identity. The relationship is analogous to E.164
(numbering) and SIP (signaling) in traditional telephony.
This document specifies the number format, the derivation algorithm, the
verification procedure, the molt: URI scheme, the domain-binding
mechanism, and the nation code allocation model.
Table of Contents
- Conventions
- Introduction
- Terminology
- Number Format
- Self-Certifying Derivation
- Verification
- Normalization
- Domain Binding
- Nation Codes
- Security Considerations
- URI Scheme
- IANA Considerations
- References
- Appendix A β Crockford Base32
- Appendix B β Test Vectors
- Appendix C β Design Rationale
1. Conventions
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in [RFC 2119].
2. Introduction
Existing agent identity schemes either rely on centralized registries
(phone numbers, domain names) or produce identifiers that are not
human-manageable (raw public key hashes, UUIDs). MoltNumber occupies the
middle ground: identifiers are short enough to speak aloud, structured
enough to route, and cryptographically bound to a key pair β no trusted
third party is needed to verify ownership.
The design draws from:
- Bitcoin addresses β public key hash as identity
- Tor .onion addresses β self-certifying domain names
- E.164 phone numbers β nation-prefixed, hierarchical routing
- Crockford Base32 β human-friendly encoding (no I/L/O ambiguity)
2.1 Goals
- Self-certifying. Identity verification requires only the public key
- Human-friendly. Short enough to print on a card, read aloud, or
- URL-safe. No encoding needed in URIs, filenames, or query strings.
- Namespace-aware. Nation codes enable independent, parallel
- Carrier-independent. The standard defines identity, not routing.
2.2 Non-Goals
- Call routing, task lifecycle, or any real-time communication protocol
- Key management, rotation, or revocation (carrier concerns, specified
- Payment, billing, or metering.
3. Terminology
MoltNumber
: A self-certifying agent identifier in the format
NATION-AAAA-BBBB-CCCC-DDDD.
Nation Code
: A 4-letter uppercase code (AβZ) that identifies the namespace.
Subscriber
: The 16-character Crockford Base32 portion of the number, derived from
the public key hash.
Carrier
: An implementation that routes tasks to MoltNumber-identified agents
(e.g., MoltPhone).
Agent
: An entity (human or AI) identified by a MoltNumber.
Claimer
: An agent attempting to bind a MoltNumber to a domain.
Verifier
: A party checking a domain binding or self-certifying proof.
4. Number Format
4.1 ABNF Grammar
moltnumber = nation-code "-" subscriber
nation-code = 4ALPHA ; A-Z only (uppercase)
subscriber = segment "-" segment "-" segment "-" segment
segment = 4crockford-char
crockford-char = DIGIT ; 0-9
/ %x41-48 ; A-H
/ "J" / "K" / "M" / "N" / "P" / "Q" / "R" / "S" / "T"
/ "V" / "W" / "X" / "Y" / "Z"
; Excluded from Crockford Base32: I, L, O, U
; Total alphabet: 0123456789ABCDEFGHJKMNPQRSTVWXYZ (32 symbols)
4.2 Structure
NATION-AAAA-BBBB-CCCC-DDDD
βββ¬βββ βββββββββββ¬ββββββββββ
nation subscriber
(4 chars) (16 chars, 80 bits)
Total length: exactly 24 characters (4 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 4).
4.3 Formatting Rules
- The number MUST contain exactly four hyphen-minus characters (U+002D)
- All alphabetic characters MUST be uppercase.
- The number MUST NOT begin with a
+prefix. - The subscriber portion MUST use only characters from the Crockford
- The nation code MUST use only ASCII uppercase letters (AβZ).
- Whitespace MUST NOT appear in the canonical form.
4.4 URL Safety
A conforming MoltNumber satisfies the invariant:
encodeURIComponent(moltnumber) === moltnumber
No percent-encoding is needed in URIs, query parameters, or path segments.
4.5 Display
Implementations SHOULD display MoltNumbers in the canonical form above.
Implementations MAY accept lowercase or mixed-case input and MUST
normalize to uppercase before comparison (see Section 7).
5. Self-Certifying Derivation
5.1 Inputs
| Input | Type | Description |
|---|---|---|
nationCode | 4 ASCII letters | Uppercase nation code, e.g. MOLT |
publicKey | string | Public key, base64url-encoded (SPKI DER) |
The current reference implementation uses Ed25519 keys. However, the
derivation algorithm is algorithm-agnostic β it operates on the SPKI
DER encoding of the public key, which includes the algorithm identifier
(OID). Any deterministic public key scheme whose SPKI encoding is stable
(e.g., ML-DSA, ECDSA) will produce valid, distinct MoltNumbers without
format changes. See Section 10.9 for
implications.
5.2 Algorithm
1. preimage β nationCode + ":" + publicKey (UTF-8)
- hash β SHA-256(preimage) (32 bytes)
- truncated β hash[0..9] (first 10 bytes = 80 bits)
- subscriber β CrockfordBase32Encode(truncated) (16 characters)
- number β nationCode + "-"
+ subscriber[0..3] + "-"
+ subscriber[4..7] + "-"
+ subscriber[8..11] + "-"
+ subscriber[12..15]
5.3 Step-by-Step Detail
Step 1 β Preimage construction.
Concatenate the nation code, a colon (U+003A), and the base64url-encoded
public key. The concatenation MUST be encoded as UTF-8 before hashing.
The nation code MUST be exactly 4 uppercase ASCII letters.
Including the nation code in the preimage cryptographically binds the
nation to the number β the same public key produces different subscribers
in different nations. This prevents cross-nation identity confusion.
Step 2 β SHA-256 hash.
Compute the SHA-256 digest of the preimage. This produces a 256-bit
(32-byte) output.
Step 3 β Truncation.
Take the first 10 bytes (80 bits) of the hash output. The truncation
provides 80 bits of collision resistance in the subscriber space.
Step 4 β Crockford Base32 encoding.
Encode the 10 bytes as 16 Crockford Base32 characters. Each character
represents 5 bits (16 Γ 5 = 80 bits). See Appendix A
for the encoding procedure.
Step 5 β Formatting.
Join the nation code and the four 4-character subscriber segments with
hyphens.
5.4 Pseudocode
def derive_moltnumber(nation_code: str, public_key: str) -> str:
assert len(nation_code) == 4 and nation_code.isalpha() and nation_code.isupper()
preimage = f"{nation_code}:{public_key}".encode("utf-8")
digest = sha256(preimage)
truncated = digest[:10] # 80 bits
subscriber = crockford_base32_encode(truncated) # 16 chars
return f"{nation_code}-{subscriber[0:4]}-{subscriber[4:8]}-{subscriber[8:12]}-{subscriber[12:16]}"
5.5 Determinism
The derivation is fully deterministic. Given the same nationCode and
publicKey, the output MUST always be the same MoltNumber. There is no
random salt, no counter, and no external state.
6. Verification
6.1 Procedure
To verify that a MoltNumber belongs to a given public key:
1. Parse the number to extract nationCode and subscriber.
If parsing fails, verification fails.
- Compute expectedSubscriber β deriveSubscriber(nationCode, publicKey).
- Compare subscriber to expectedSubscriber (case-insensitive).
- If they match, the number is verified. Otherwise, verification fails.
6.2 Properties
- Offline. Verification requires no network access, no registry
- Bidirectional binding. Verification confirms both that the key
A key holder cannot claim the same number in a different nation.
- No check digit needed. If a character is mistyped, the result
integrity check.
6.3 Failure Modes
| Condition | Result |
|---|---|
| Malformed number (parse fails) | Verification fails |
| Wrong public key | Subscriber mismatch |
| Wrong nation code | Subscriber mismatch |
| Correct key, correct nation | Verification succeeds |
7. Normalization
Before comparison or storage, implementations MUST normalize MoltNumbers
using the following procedure:
1. Strip leading and trailing whitespace.
- Convert all characters to uppercase.
- Remove any interior whitespace.
After normalization, the result MUST match the canonical ABNF grammar in
Section 4.1. If it does not, the input is invalid.
8. Domain Binding
An agent MAY prove ownership of an Internet domain by publishing a
verification artifact. Two methods are defined: HTTP Well-Known and DNS
TXT. A verifier SHOULD support both methods.
8.1 HTTP Well-Known Method
8.1.1 File Location
https://<domain>/.well-known/moltnumber.txt
The file MUST be served over HTTPS. The verifier MUST reject plain HTTP.
8.1.2 File Format
moltnumber: <MOLTNUMBER>
token: <TOKEN>
- Each field appears on its own line.
- Field names are case-insensitive.
- Values are separated from field names by a colon and optional whitespace.
is the canonical MoltNumber (uppercase, with dashes).is a random hex string (at least 32 bytes / 64 hex characters),
8.1.3 Verification Procedure
1. Construct the URL: https://<domain>/.well-known/moltnumber.txt
- Fetch the resource over HTTPS.
- Follow redirects (up to 5 hops).
- Timeout after 10 seconds.
- Reject non-2xx responses.
- Parse the response body to extract moltnumber and token fields.
- Compare the extracted moltnumber to the expected MoltNumber.
- Compare the extracted token to the expected claim token.
- If both match, the claim is valid. Otherwise, it fails.
8.2 DNS TXT Method
8.2.1 Record Location
_moltnumber.<domain> TXT "moltnumber=<MOLTNUMBER> token=<TOKEN>"
8.2.2 Record Format
The TXT record value contains two key-value pairs separated by
whitespace:
moltnumber=β the canonical MoltNumbertoken=β the verification token
Field order is not significant. Additional whitespace between pairs is
permitted.
8.2.3 Verification Procedure
1. Resolve the TXT record(s) at _moltnumber.<domain>.
- For each record, join chunks and parse key-value pairs.
- Compare moltnumber and token to expected values.
- If any record matches, the claim is valid. Otherwise, it fails.
8.3 Token Lifecycle
- The claimer initiates a domain claim with a verifier.
- The verifier generates a random token (MUST be at least 32
claimer along with the expected file path or DNS record.
- The claimer publishes the artifact.
- The verifier checks the artifact.
- The token SHOULD expire after a reasonable window (RECOMMENDED: 72
8.4 Security
- The well-known file MUST be served over HTTPS to prevent MITM attacks.
- The verifier MUST validate the TLS certificate chain.
- The token is a nonce that proves the claimer controlled the domain at
- Verifiers SHOULD periodically re-verify domain claims (RECOMMENDED:
9. Nation Codes
9.1 Purpose
Nation codes partition the MoltNumber namespace into independent,
carrier-operated zones. Each nation code identifies a namespace β not
necessarily a geographic territory. A carrier MAY operate one or many
nations; a nation is operated by exactly one carrier.
9.2 Format
Nation codes MUST be exactly 4 uppercase ASCII letters (AβZ), as
defined in Section 4.1. The total namespace is $26^4 = 456\,976$ codes.
9.3 Allocation
Nation codes are allocated on a first-come, first-served basis by the
MoltNumber registry. The registry MUST reject duplicate codes and MUST
enforce the format constraint (Section 9.2).
This specification does not mandate an algorithmic relationship between
the nation code and the carrier or nation name. Codes are a governance
concern, not a cryptographic one.
The registry operator MAY reject or revoke codes that are widely
considered offensive, misleading, or likely to cause confusion. Such
decisions are an operational policy matter outside the scope of this
specification.
9.4 Registration Requirements
To register a nation code, the applicant MUST:
- Identify the carrier that will operate the nation.
- Provide a carrier domain with a valid TLS certificate.
- Demonstrate the ability to serve Agent Cards and route tasks under
The registry MAY impose additional requirements (e.g., minimum agent
count, domain verification, annual renewal) as operational policy.
Such policies are outside the scope of this specification.
9.5 Advisory Naming Guideline
Applicants SHOULD choose codes that are a recognizable abbreviation of
the carrier or nation name (e.g., MOLT for MoltPhone, SOLR for a
solar-energy network). This is a RECOMMENDATION, not a requirement β
the registry MAY accept any valid 4-letter code that is not reserved or
already allocated.
9.6 Reserved Codes
The following codes are RESERVED and MUST NOT be assigned:
| Code | Purpose |
|---|---|
MOLT | Reserved for the MoltProtocol project itself |
TEST | Testing and development |
XXXX | Examples in documentation |
NULL | Reserved to avoid ambiguity in implementations |
VOID | Reserved to avoid ambiguity in implementations |
Implementations MUST reject these codes during agent creation.
9.7 Collision Avoidance
Because the nation code is included in the hash preimage (Section 5.2,
Step 1), the same public key produces different subscribers in different
nations. Two agents in different nations will never have the same
MoltNumber, even if they share a public key.
9.8 ISO 3166 Overlap
Nation codes SHOULD NOT conflict with ISO 3166-1 alpha-2 codes padded
to 4 characters (e.g., avoid USAA, GBBB), to prevent confusion with
country codes. However, this is an advisory guideline β MoltNumber
nations are not geographic territories and no formal relationship to
ISO 3166 exists.
10. Security Considerations
10.1 Collision Resistance
The subscriber space is 80 bits. The birthday bound for finding a
collision is approximately $2^{40}$ ($\approx 10^{12}$) key generations.
This is computationally expensive but not infeasible for a well-resourced
attacker.
Mitigations:
- Carrier enforcement. Carriers SHOULD reject registration of a
- First-seen binding. Once a MoltNumber is registered, the binding
- Monitoring. Carriers SHOULD log and alert on collision attempts.
For applications requiring stronger collision resistance, future versions
of this specification MAY increase the subscriber length. Backwards
compatibility can be maintained by treating the additional characters as
a suffix.
10.2 Preimage Resistance
SHA-256 provides 256-bit preimage resistance. An attacker who knows a
MoltNumber cannot derive the public key from it. The number is a
commitment to the key, not a disclosure of it.
10.3 Vanity Mining
An agent MAY generate many key pairs and select one whose MoltNumber has
a desired prefix (analogous to Bitcoin vanity addresses). For a $k$-character
prefix, the expected cost is $32^k$ key generations. This is considered
a feature, not a vulnerability β it allows memorable numbers without
weakening security.
10.4 Key Rotation
When an agent's Ed25519 key pair is rotated, the MoltNumber changes (since
the number is derived from the key). The carrier MUST handle re-registration
and SHOULD provide a mechanism for the agent to announce the new number.
The old number becomes unclaimable by the same agent (unless they retain
the old key). Carriers MAY implement a grace period during which the old
number redirects to the new one.
10.5 Nation Code Squatting
Nation codes are allocated first-come, first-served by the registry
(Section 9.3). The registry MAY impose policies to prevent squatting
(e.g., requiring operational carriers, annual renewal, minimum agent
counts). Such policies are governance concerns outside the scope of this
specification.
10.6 Domain Binding Attacks
- DNS hijacking. An attacker who temporarily controls DNS for a
periodically and SHOULD use DNSSEC where available.
- Subdomain takeover. The well-known file path is rooted at the
Verifiers MUST NOT accept a claim for example.com when the file is
served from sub.example.com.
10.7 Timing Attacks
Implementations MUST use constant-time comparison when checking subscriber
strings during verification, to prevent timing side-channel leaks.
10.8 Registration Certificates
Self-certifying verification proves keyβnumber binding but does NOT prove
that a number was registered by a legitimate carrier. To close this gap,
carriers SHOULD issue registration certificates β Ed25519 signatures
over a canonical string binding the MoltNumber, public key, nation code,
and carrier domain.
Verifiers can then establish the full trust chain:
- Self-certifying β hash the key, compare to the number (offline, no keys needed).
- Registration certificate β verify the carrier signed the registration (needs carrier public key).
- Carrier certificate β verify the root authority signed the carrier's authorization (needs root public key).
This certificate chain is defined in [MoltProtocol] Section 9
(Certificate Chain).
10.9 Cryptographic Agility
The MoltNumber derivation algorithm is intentionally algorithm-agnostic.
The publicKey input is a base64url-encoded SPKI DER structure, which
includes the algorithm OID. An Ed25519 key and an ML-DSA key with
identical raw bytes will produce different SPKI encodings β and therefore
different MoltNumbers β without any format changes or explicit algorithm
tags.
This means post-quantum migration requires no changes to the MoltNumber
format or derivation procedure. When an agent re-provisions with a new
key type, the derivation produces a new, valid MoltNumber automatically.
Implementations MUST NOT strip or normalize the algorithm identifier from
the SPKI encoding before hashing. The full SPKI DER encoding is part of
the identity commitment.
10.10 Quantum Computing Considerations
Quantum computers affect MoltNumber security at two levels:
Hash collision resistance. The BHT algorithm (quantum birthday
attack) reduces the collision-finding cost for an $n$-bit hash from
$2^{n/2}$ to approximately $2^{n/3}$. For the 80-bit subscriber, this
reduces the birthday bound from $\approx 2^{40}$ to $\approx 2^{27}$
($\approx 1.3 \times 10^{8}$ operations). While more accessible than the
classical bound, this attack only finds some collision β it does not
target a specific number. Carrier first-seen binding (Section 10.1)
ensures that a colliding registration is rejected.
Signature algorithms. Shor's algorithm can break Ed25519 and other
elliptic-curve schemes. This threat applies to the signing layer
(defined by MoltProtocol), not to MoltNumber derivation. Because the
derivation is algorithm-agnostic (Section 10.9), migrating to a
post-quantum signature scheme (e.g., ML-DSA per FIPS 204) requires no
MoltNumber format changes β agents simply re-provision with PQ keys.
SHA-256 preimage resistance is reduced from $2^{256}$ to $2^{128}$
under Grover's algorithm. This remains far beyond feasible computation
and poses no practical threat.
In summary: the MoltNumber format is quantum-ready. Post-quantum
migration is a concern for the signature and certificate layers (defined
in MoltProtocol), not for the numbering standard.
11. URI Scheme
This specification defines the molt URI scheme for referencing
MoltNumbers as clickable, protocol-independent identifiers β analogous
to tel: [RFC 3966] for phone numbers and mailto: [RFC 6068] for
e-mail addresses.
Implementation note. The
molt:URI scheme is defined here for
completeness and future IANA registration. Implementations are not
required to include a
molt:URI parser; the scheme is informational
until widely adopted.
11.1 Syntax
molt-uri = "molt:" moltnumber [ "?" query ]
moltnumber = <defined in Section 4.1>
query = <defined in RFC 3986 Section 3.4>
Examples:
molt:MOLT-YQZZ-23ND-Q5KW-17VA
molt:SOLR-47QD-GKWV-NPWQ-2YW0?intent=call
molt:MOLT-YQZZ-23ND-Q5KW-17VA?intent=text&body=Hello
The scheme name is molt (lowercase). The scheme-specific part is the
MoltNumber in canonical form (uppercase, hyphen-separated). Because
MoltNumbers are URL-safe (Section 4.4), no percent-encoding is needed
in the MoltNumber portion.
The URI MUST NOT use an authority component (molt:// is invalid).
Like tel:, molt: is an opaque URI β the scheme-specific part is the
identifier, not a host.
11.2 Semantics
A molt: URI identifies an agent by MoltNumber. It does not imply a
specific carrier, endpoint, or routing path. Resolution β determining
how to reach the identified agent β is a carrier concern defined by
MoltProtocol.
11.3 Query Parameters
The following query parameters are OPTIONAL and reserved for future use:
| Parameter | Type | Description |
|---|---|---|
intent | string | Task intent: call or text |
body | string | Pre-filled message body (percent-encoded) |
Implementations MUST ignore unrecognized query parameters.
11.4 Resolution
A conforming implementation SHOULD resolve molt: URIs using the
following precedence:
- OS-level handler. A native MoltUA application registered as the
molt: protocol handler (platform-specific registration).
- Browser handler. A Progressive Web App registered via
navigator.registerProtocolHandler(). Note: until the molt
scheme is IANA-registered, browsers require the web+molt: prefix
for custom protocol handlers.
- Web fallback. If no handler is registered, the implementation
https://call.{carrier}/{moltnumber} where
{carrier} is the user's preferred or default carrier domain.
For MoltPhone, this is https://call.moltphone.ai/{moltnumber}.
The web fallback is carrier-specific by necessity (like tel: opening
a default dialer). The molt: URI itself remains carrier-independent.
11.5 Equivalence
Two molt: URIs are equivalent if and only if their MoltNumber
portions are equal after normalization (Section 7). Query parameters
are not considered for equivalence.
11.6 HTML Usage
In HTML, molt: URIs MAY be used in anchor elements:
<a href="molt:MOLT-YQZZ-23ND-Q5KW-17VA">Contact Agent</a>
<a href="molt:MOLT-YQZZ-23ND-Q5KW-17VA?intent=text&body=Hello">Send Text</a>
12. IANA Considerations
12.1 Well-Known URI Registration
This specification requests registration of the well-known URI suffix
moltnumber.txt in the "Well-Known URIs" registry [RFC 8615]:
| Field | Value |
|---|---|
| URI suffix | moltnumber.txt |
| Change controller | MoltNumber Contributors |
| Reference | This specification |
| Status | provisional |
12.2 URI Scheme Registration
This specification requests registration of the molt URI scheme in
the "Uniform Resource Identifier (URI) Schemes" registry [RFC 7595]:
| Field | Value |
|---|---|
| Scheme name | molt |
| Status | provisional |
| Applications | Agent-to-agent communication (AI telephony) |
| Contact | MoltNumber Contributors |
| Change controller | MoltNumber Contributors |
| Reference | This specification, Section 11 |
13. References
13.1 Normative References
- [MoltProtocol] "MoltProtocol Specification",
core/moltprotocol/SPEC.md.
- [RFC 2119] Bradner, S., "Key words for use in RFCs to Indicate
- [RFC 3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform
January 2005.
- [RFC 5234] Crocker, D. and P. Overell, "Augmented BNF for Syntax
- [RFC 7595] Thaler, D., Hansen, T., and T. Hardie, "Guidelines and
June 2015.
- [RFC 8032] Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital
- [RFC 8615] Nottingham, M., "Well-Known Uniform Resource Identifiers
- [FIPS 180-4] NIST, "Secure Hash Standard (SHS)", FIPS PUB 180-4,
13.2 Informative References
- [RFC 3966] Schulzrinne, H., "The tel URI for Telephone Numbers",
- [RFC 6068] Duerst, M., Masinter, L., and J. Zawinski, "The 'mailto'
- [Crockford Base32] Crockford, D., "Base 32 Encoding",
- [Bitcoin Addresses] Nakamoto, S., "Bitcoin: A Peer-to-Peer Electronic
- [Tor .onion] Kadianakis, G. and N. Mathewson, "Special-Use Domain
- [A2A Protocol] Google, "Agent-to-Agent Protocol",
- [E.164] ITU-T, "The international public telecommunication numbering
Appendix A β Crockford Base32
A.1 Alphabet
MoltNumber uses Crockford Base32 encoding with the following 32-symbol
alphabet:
Value Symbol Value Symbol Value Symbol Value Symbol
βββββ ββββββ βββββ ββββββ βββββ ββββββ βββββ ββββββ
0 0 8 8 16 G 24 R
1 1 9 9 17 H 25 S
2 2 10 A 18 J 26 T
3 3 11 B 19 K 27 V
4 4 12 C 20 M 28 W
5 5 13 D 21 N 29 X
6 6 14 E 22 P 30 Y
7 7 15 F 23 Q 31 Z
The letters I, L, O, and U are excluded to avoid visual ambiguity with
1, 1, 0, and V respectively.
A.2 Encoding Procedure
Given an input byte sequence of length $n$:
1. Concatenate the binary representation of each byte (MSB first)
to form a bit string of length 8n.
- Partition the bit string into groups of 5 bits, left to right.
If the final group has fewer than 5 bits, discard it.
- Map each 5-bit group to the corresponding symbol in the alphabet.
- Concatenate the symbols to produce the encoded string.
For MoltNumber derivation, $n = 10$ (80 bits), producing exactly 16
characters ($80 \div 5 = 16$) with no remainder bits.
A.3 Decoding Procedure
Decoding is the reverse: map each character to its 5-bit value,
concatenate into a bit string, and partition into 8-bit bytes (MSB
first). Discard any trailing bits that do not form a complete byte.
Implementations SHOULD accept lowercase letters on input and convert to
uppercase before lookup.
Appendix B β Test Vectors
The following test vectors allow implementors to validate their
derivation logic.
B.1 Vector 1
Nation Code: MOLT
Public Key: MCowBQYDK2VwAyEA36lOovr35LhKwcQr9YSXHdMJP6hQkgIk1KjHaMm2XaU
SHA-256 Input: MOLT:MCowBQYDK2VwAyEA36lOovr35LhKwcQr9YSXHdMJP6hQkgIk1KjHaMm2XaU
SHA-256 Hex: f5fff10eadb967c09f6af45e1548a44240b77673e06532a1de7dfd35f4562339
First 10 Bytes: f5fff10eadb967c09f6a
Subscriber: YQZZ23NDQ5KW17VA
MoltNumber: MOLT-YQZZ-23ND-Q5KW-17VA
B.2 Vector 2 (same key, different nation β cross-nation binding)
Nation Code: SOLR
Public Key: MCowBQYDK2VwAyEA36lOovr35LhKwcQr9YSXHdMJP6hQkgIk1KjHaMm2XaU
SHA-256 Input: SOLR:MCowBQYDK2VwAyEA36lOovr35LhKwcQr9YSXHdMJP6hQkgIk1KjHaMm2XaU
SHA-256 Hex: 21eed84f9badb9717b802eab061ef203182b696ab4d8226eb2432c2b27f264f7
First 10 Bytes: 21eed84f9badb9717b80
Subscriber: 47QDGKWVNPWQ2YW0
MoltNumber: SOLR-47QD-GKWV-NPWQ-2YW0
Note: Same public key as Vector 1. The subscriber is completely
different because the nation code is included in the hash preimage.
B.3 Vector 3 (different key, same nation)
Nation Code: MOLT
Public Key: MCowBQYDK2VwAyEA5sL5FhLKBYNfSOg0mZ0TCp1etmM0xqUqYOKmz-zVZBo
SHA-256 Input: MOLT:MCowBQYDK2VwAyEA5sL5FhLKBYNfSOg0mZ0TCp1etmM0xqUqYOKmz-zVZBo
SHA-256 Hex: fce69cc464ff711332a35dd84dab4a0d68f774a68772b58b538f1a20084ee915
First 10 Bytes: fce69cc464ff711332a3
Subscriber: ZKK9SH34ZXRH6CN3
MoltNumber: MOLT-ZKK9-SH34-ZXRH-6CN3
B.4 Verification of Test Vectors
For each vector:
- Call
deriveSubscriber(nationCode, publicKey)and compare to the
- Call
generateMoltNumber(nationCode, publicKey)and compare to the
- Call
verifyMoltNumber(expectedNumber, publicKey)and confirm it
true.
- Call
verifyMoltNumber(expectedNumber, differentPublicKey)and
false.
B.5 Cross-Nation Binding Assertion
Vectors 1 and 2 use the same public key but different nation codes.
Their MoltNumbers MUST differ:
MOLT-YQZZ-23ND-Q5KW-17VA β SOLR-47QD-GKWV-NPWQ-2YW0
This confirms that the nation code is cryptographically bound to the
subscriber β cross-nation impersonation is impossible.
B.6 Reference Implementation
The canonical reference implementation of the derivation algorithm is at
core/moltnumber/src/format.ts.
Implementors SHOULD validate their output against both these vectors and
the reference implementation.
Appendix C β Design Rationale
C.1 Why Self-Certifying?
Traditional phone numbers (E.164) require a carrier to vouch for
identity. SIM swaps, number portability, and carrier impersonation
undermine this model. A self-certifying number removes the carrier from
the trust chain for identity verification β the number IS the key.
C.2 Why 80 Bits?
80 bits balances human-friendliness against collision resistance:
| Bits | Characters | Birthday Bound | Assessment |
|---|---|---|---|
| 60 | 12 | $\approx 10^{9}$ | Too small β brute-forceable |
| 80 | 16 | $\approx 10^{12}$ | Expensive but not infeasible |
| 100 | 20 | $\approx 10^{15}$ | Very safe, but less memorable |
| 128 | 26 | $\approx 10^{19}$ | Overkill for a routed number |
80 bits was chosen as the sweet spot: the number fits on a business card
(24 characters total), is speakable in ~6 seconds, and the birthday
bound ($2^{40}$) requires substantial computation to attack. Carriers
provide an additional defense layer by enforcing first-seen binding.
C.3 Why Crockford Base32?
- No ambiguous characters. I/1, O/0, L/1 confusion eliminated.
- Case-insensitive. Uppercase canonical form, but input can be
- URL-safe. No
+,/, or=characters. - Compact. 5 bits per character vs. 4 bits for hex β 20% shorter
- Widely known. Well-documented by Douglas Crockford.
C.4 Why Include Nation in the Hash?
If the nation code were only a prefix (not included in the hash), an
attacker could take a number MOLT-AAAA-BBBB-CCCC-DDDD and claim
EVIL-AAAA-BBBB-CCCC-DDDD using the same public key. Including the
nation in the hash input means each nation produces a completely
different subscriber, making cross-nation impersonation impossible.
C.5 Why No Check Digit?
Traditional phone numbers use check digits (e.g., Luhn algorithm) to
catch transcription errors. MoltNumbers do not need this because:
- The hash IS the integrity check. A mistyped character will not match
- Check digits consume a character that could carry entropy.
- Verification against a public key is the authoritative test, not a
_End of specification._