| Internet-Draft | SGCP | August 2026 |
| Karthik | Expires 16 February 2027 | [Page] |
This document specifies the State Graph Cryptographic Protocol (SGCP), a communication-security framework in which a client and server establish a cryptographically protected session and maintain a synchronized state graph throughout the lifetime of the communication session.¶
SGCP combines device identity, port context, socket context, session identity, ECDH-based shared-secret establishment, cryptographic key derivation, epochs, packet sequence numbers, authenticated state transitions, state-dependent packet transformation, replay protection, continuous context verification, controlled reauthentication, and session recovery.¶
The central principle is that both endpoints independently derive the same cryptographic state from a common authenticated session secret and deterministic state information. The state graph defines which communication states are valid and which transitions are permitted.¶
This document also illustrates the protocol with a worked example of a full-duplex binary media file transfer (an MP3 audio file) showing the complete packet-level exchange.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 16 February 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document.¶
Modern communication security protocols, such as TLS [RFC8446] and DTLS [RFC9147], establish shared secrets and derive traffic keys but treat each packet largely independently from the perspective of the state machine governing what constitutes a valid packet at a given point in the session. Session context (the device, the port, the socket) is typically authenticated only at the handshake and not continuously bound to individual packet keys.¶
The State Graph Cryptographic Protocol (SGCP) addresses this limitation by introducing the concept of a cryptographic state graph: a deterministically derived directed graph in which each node represents a valid cryptographic communication state (keyed to the current epoch, sequence number, and context binding) and each edge represents a cryptographically authenticated transition between two such states.¶
SGCP provides the following security properties:¶
This document specifies the protocol in full and illustrates its operation with a worked example involving the full-duplex transfer of a sample MP3 audio file between two SGCP endpoints.¶
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, NOT RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
The overall SGCP architecture is shown in Figure 1.¶
+----------------------+
| CLIENT |
| Device Identity |
| Private Key |
| Certificate |
| Context Monitor |
+----------+-----------+
|
| Handshake
v
+----------+-----------+
| AUTHENTICATION |
| Identity Validation |
| Certificate Check |
| Challenge/Response |
+----------+-----------+
|
v
+----------+-----------+
| KEY ESTABLISHMENT |
| ECDH / DH |
| Optional ML-KEM |
+----------+-----------+
|
v
+----------+-----------+
| ROOT SECRET |
| (Session Secret) |
+----------+-----------+
|
+------------------+------------------+
| | |
v v v
Device Context Port Context Socket Context
| | |
+------------------+------------------+
v
Context Binding
|
v
+----------+-----------+
| STATE ENGINE |
| Epoch / Sequence |
| Direction / Key ID |
| Context / Trust |
+----------+-----------+
|
v
+----------+-----------+
| STATE GRAPH |
| Nodes + Auth Edges |
| + Hash Chain |
+----------+-----------+
|
v
+----------+-----------+
| PACKET ENGINE |
| Key Derivation |
| AEAD Encryption |
| Transformation |
+----------+-----------+
|
v
PACKET
|
v
+----------+-----------+
| SERVER |
| State Reconstruction|
| Verification |
| Decryption |
| Trust Evaluation |
+----------------------+
SGCP uses X.509 [RFC5280] certificates exclusively to establish the authenticated identities of both endpoints during the handshake. X.509 defines the certificate format and the identity-binding mechanism; SGCP defines how those certificates are used and how the authentication outcome feeds into the cryptographic state graph.¶
The separation of concerns is:¶
X.509 certificates do NOT contain the SGCP state graph, epoch state, or packet keys. They exist only at the authentication boundary.¶
The full stack, from identity layer to packet protection, is:¶
SGCP
|
+---------+---------+
| |
X.509 Certificate ECDH
(via OpenSSL) Exchange
| |
v v
Authenticate Shared Secret
| |
+---------+---------+
|
v
KDF / Key Tree
(HKDF, Section 8)
|
v
SGCP Root Secret
|
v
SGCP State Graph
|
+---------+---------+
v v
State Node State Edge
| |
+---------+---------+
|
v
Packet Protection
(Epoch Key -> Packet Key -> AEAD)
Stated as a linear stack:¶
OpenSSL
|
v
X.509 Certificates (identity and public-key binding)
|
v
SGCP Authentication (challenge-response, certificate validation)
|
v
ECDH / ML-KEM (shared secret establishment)
|
v
KDF (HKDF-Extract/Expand with domain labels)
|
v
SGCP Root Secret (session-level cryptographic anchor)
|
v
SGCP State Graph (epoch, sequence, context-bound nodes/edges)
|
v
Per-Packet Key (AEAD + state-dependent transformation)
|
v
Wire Packet
¶
Each SGCP endpoint MUST possess:¶
client.crt (X.509 end-entity certificate, DER or PEM) client.key (private key corresponding to the certificate) server.crt server.key¶
Certificates MUST:¶
The certificate hash used in Root Secret derivation is:¶
Certificate_Hash = SHA-256(DER-encoded certificate)¶
This binds the Root Secret to the specific certificates exchanged during this handshake, preventing certificate substitution attacks.¶
Implementations MAY use OpenSSL (or a compatible TLS library) to perform the X.509 certificate operations during the SGCP handshake. The following OpenSSL operations are involved:¶
Certificate loading:¶
SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM); SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM);¶
Certificate verification:¶
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback); SSL_CTX_load_verify_locations(ctx, "ca-chain.crt", NULL);¶
Certificate inspection (Device_ID from SAN/CN):¶
X509 *cert = SSL_get_peer_certificate(ssl); X509_NAME *subj = X509_get_subject_name(cert); X509_NAME_get_text_by_NID(subj, NID_commonName, buf, sizeof(buf));¶
ECDH key generation (for SGCP's own DH exchange):¶
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL); EVP_PKEY_keygen_init(ctx); EVP_PKEY_keygen(ctx, &pkey);¶
IMPORTANT: When OpenSSL is used as an underlying library, the SGCP handshake runs ON TOP OF the X.509 certificate exchange. SGCP does not delegate its state graph, epoch management, or packet key derivation to OpenSSL. OpenSSL is used only for certificate parsing, signature verification, and ECDH primitives.¶
The complete handshake, showing certificate exchange and the transition from X.509-authenticated identity into SGCP state:¶
CLIENT SERVER
(client.crt + client.key) (server.crt + server.key)
|-- ClientHello --------------------------->|
| + Client X.509 Certificate |
| + Client DH Public Key |
| + Client KEM Public Key (if PQ) |
| + Client_Nonce |
Verify client certificate chain
Extract Device_ID from SAN/CN
Lookup Device_ID in device registry
Verify certificate not revoked
Generate Server_Challenge
|<-- ServerHello ---------------------------|
| + Server X.509 Certificate |
| + Server DH Public Key |
| + Server KEM Ciphertext (if PQ) |
| + Server_Challenge |
| + ServerHello_Signature |
Verify server certificate chain
Verify ServerHello_Signature
Compute DH_Secret (X25519)
Decapsulate KEM_Secret (if PQ)
|
v
Root_Secret = HKDF-Extract(
"SGCP_ROOT_PQ_v1",
DH_Secret || KEM_Secret
|| Client_Nonce || Server_Nonce
|| SHA-256(client.crt)
|| SHA-256(server.crt))
|-- ClientFinish -------------------------->|
| + Challenge_Response |
| (signed with client.key) |
| + ClientFinish_MAC |
Verify Challenge_Response
Compute DH_Secret, Root_Secret
|
v
SGCP takes over:
|
v
Session_ID = H("SGCP_SID" || ...)
|
v
Epoch_0_Key = HKDF-Expand(...)
|
v
Initial State N0
|
v
N0 -> N1 -> N2 -> N3 -> ...
After ClientFinish: X.509 is no longer involved.
Both sides enter the state graph at N0.
| X.509 Establishes | SGCP Establishes |
|---|---|
| Identity binding | Session Root Secret |
| Public key authenticity | Epoch key hierarchy |
| Certificate validity | Per-packet unique keys |
| Chain of trust to CA | State graph (N0, N1, N2, ...) |
| Revocation status | Context binding |
| Signature algorithms | Replay protection windows |
| Epoch transitions | |
| Authenticated state edges | |
| Hash chain linking nodes |
In summary: X.509 answers "who are you?" SGCP answers "what cryptographic state governs each byte of our ongoing session?"¶
The process begins before any network communication. The client possesses:¶
The client's private key MUST never be transmitted to the server or any third party.¶
The server maintains a device registry containing at minimum:¶
Device_ID Public_Key Certificate_ID Status Policy_ID Key_Version Trust_State¶
Only Device_IDs with Status = AUTHORIZED MAY initiate a new session.¶
The client establishes a transport connection to the server. The communication context is the tuple:¶
Context = (Device_ID, Protocol, Local_Port, Remote_Port,
Socket_ID, Session_Nonce)
¶
SGCP does not treat IP address, MAC address, port number, or socket identifiers as secret. These are context attributes whose purpose is to bind the cryptographic session to its communication environment.¶
The client SHOULD compute:¶
Prelim_Context_Hash =
H("SGCP_CTX_v1" || Device_ID || Protocol
|| Local_Port || Remote_Port || Socket_ID)
¶
SGCP performs mutual authentication via a three-message challenge-response exchange.¶
Message 1 (Client Hello):¶
ClientHello {
ProtocolVersion,
SupportedCipherSuites,
Device_ID,
Certificate,
Client_Nonce,
Client_DH_Public_Key,
Client_KEM_Public_Key (if ML-KEM is negotiated)
}
¶
Message 2 (Server Hello):¶
ServerHello {
SelectedCipherSuite,
Server_Certificate,
Server_Nonce,
Server_DH_Public_Key,
Server_KEM_Ciphertext, (if ML-KEM is negotiated)
Server_Challenge,
ServerHello_Signature
}
ServerHello_Signature =
Sign(Server_Private_Key,
H("SGCP_SHELLO" || ClientHello || ServerHello_data))
¶
Message 3 (Client Finish):¶
ClientFinish {
Challenge_Response,
ClientFinish_MAC
}
Challenge_Response =
Sign(Client_Private_Key,
H("SGCP_CHALLENGE" || Server_Challenge || Client_Nonce))
ClientFinish_MAC =
HMAC(Handshake_Key,
H("SGCP_CFIN" || ClientHello || ServerHello))
¶
If any validation step fails, the server MUST terminate the connection with an authentication-failure alert.¶
The recommended primitive is X25519 [RFC7748] or P-256. The raw shared secret MUST NOT be used directly as a traffic key. Instead:¶
Root_Secret =
HKDF-Extract("SGCP_ROOT_v1",
DH_Secret || Client_Nonce || Server_Nonce
|| Client_Certificate_Hash
|| Server_Certificate_Hash)
¶
If post-quantum protection is required, a hybrid construction is used:¶
Root_Secret =
HKDF-Extract("SGCP_ROOT_PQ_v1",
DH_Secret || KEM_Secret
|| Client_Nonce || Server_Nonce
|| Client_Certificate_Hash
|| Server_Certificate_Hash)
¶
The post-quantum KEM MUST be a standardized algorithm such as ML-KEM-768 [FIPS203]. Custom or proprietary KEMs MUST NOT be used.¶
Both endpoints independently create the initial session state:¶
Session_ID = H("SGCP_SID" || Root_Secret || Client_Nonce
|| Server_Nonce)
Epoch = 0
Sequence = 0
Direction = Client->Server (C->S channel)
Server->Client (S->C channel)
Node_ID = N0
¶
SGCP is a full-duplex protocol. Each direction maintains independent sequence numbering and per-direction key derivation.¶
The Context Binding is computed as:¶
Context_Binding =
H("SGCP_CTX_BIND_v1"
|| Version || Device_ID || Protocol
|| Local_Port || Remote_Port
|| Socket_ID || Session_ID)
¶
The Context Binding becomes an input to all epoch-key derivations, thereby cryptographically binding every packet key to the communication environment.¶
The first node of the state graph is:¶
N0 = {
Node_ID : H("SGCP_NODE" || Session_ID || 0 || 0),
Session_ID : <from Phase 5>,
Epoch : 0,
Sequence : 0,
Context_Binding: <from Phase 6>,
Key_ID : H(Session_ID || 0 || "C->S" || 0),
Direction : Client->Server,
State_Status : ACTIVE,
Chain_Hash : H("SGCP_CHAIN_GENESIS" || Session_ID)
}
¶
Subsequent nodes form the hash chain:¶
N(k+1).Chain_Hash =
H("SGCP_CHAIN" || N(k).Chain_Hash || N(k).Node_ID
|| Sequence(k+1) || Epoch)
¶
Normal graph growth:¶
N0 --E0--> N1 --E1--> N2 --E2--> N3 --E3--> N4 ...¶
For every outgoing packet, the sender performs the following sequence:¶
Derive Epoch_Key and Packet_Key:¶
Epoch_Key =
HKDF-Expand(Root_Secret,
"SGCP_EPOCH" || Epoch || Context_Binding,
key_length)
Packet_Key =
HKDF-Expand(Epoch_Key,
"SGCP_PACKET" || Direction || Sequence,
key_length)
¶
AEAD-Encrypt:¶
(Ciphertext, Tag) =
AEAD-Encrypt(Packet_Key, Nonce, Plaintext, AAD)
Nonce = Epoch || Sequence || Direction
¶
Derive Transform_State and apply wire transformation:¶
Transform_State =
HKDF-Expand(Epoch_Key,
"SGCP_TRANSFORM" || Sequence || Direction,
transform_length)
Wire_Payload = Transform(Ciphertext || Tag, Transform_State)
¶
Upon receipt of a packet the receiver performs:¶
The receiver MUST NOT attempt multiple candidate transformations or keys. Only one deterministically derived state is tested per packet.¶
After successful packet verification, both sender and receiver independently advance their local state:¶
Sequence += 1
Node_ID = H("SGCP_NODE" || Session_ID || Epoch || Sequence)
¶
Each endpoint maintains a replay window per (Session_ID, Epoch, Direction) tuple with Window_High and a Window_Bits bitmap covering [Window_High - W + 1, Window_High] where W is the configured window size (RECOMMENDED: W >= 64).¶
Upon receipt of a packet with Sequence S:¶
A new epoch resets the replay window for that direction.¶
SGCP does not permanently trust a session once established. Both endpoints SHOULD continuously monitor device, network, port, socket, session, and behavioral context.¶
Context Change
|
v
Trust Engine
|
+-----------+-----------+
v v v
ACCEPT REAUTH REVOKE
| | |
v v v
Continue Verify Terminate
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Version (8) | Packet_Type(8)| Flags (8) | Reserved (8) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Session_ID (128 bits) | | | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Epoch (32 bits) | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | Sequence Number (64 bits) | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | Direction | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Key_ID (64 bits) | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Payload Length (32) | Header Checksum (16) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0x01 = CLIENT_HELLO 0x02 = SERVER_HELLO 0x03 = CLIENT_FINISH 0x04 = DATA 0x05 = CONTEXT_CHANGE 0x06 = KEY_ROTATION 0x07 = REAUTH_REQUEST 0x08 = REAUTH_RESPONSE 0x09 = RECOVERY_INIT 0x0A = RECOVERY_ACK 0x0B = ALERT 0x0C = SESSION_CLOSE 0x10 = MEDIA_SEGMENT 0x11 = MEDIA_ACK¶
Bit 0 = FULL_DUPLEX Bit 1 = EPOCH_BOUNDARY Bit 2 = CONTEXT_CHANGED Bit 3 = KEY_ROTATION_PENDING Bits 4-7 = Reserved¶
For MEDIA_SEGMENT (0x10) packets, an additional media header precedes the segment data within the encrypted plaintext:¶
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Media_Type (8)| Codec (8) | Segment_Index (32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Total_Segments (32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Byte_Offset_in_File (64) | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Segment_Byte_Length (32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | File_Hash (SHA-256, 32 bytes) | | | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Segment_Data (variable) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Media_Type: 0x01 = AUDIO. Codec: 0x01 = MP3, 0x02 = AAC, 0x03 = OPUS, 0xFF = RAW. File_Hash is SHA-256 over the complete unencrypted file, enabling end-to-end integrity verification after reassembly.¶
Node = {
Node_ID : H("SGCP_NODE" || Session_ID
|| Epoch || Sequence),
Session_ID : <session identifier>,
Epoch : <epoch number>,
Sequence : <sequence number>,
Context_Binding : <from Section 4.6>,
Key_ID : <non-secret key identifier>,
Direction : <C->S | S->C>,
State_Status : ACTIVE | SUSPENDED | TERMINATED,
Chain_Hash : H("SGCP_CHAIN" || Prev_Chain_Hash
|| Node_ID || Sequence || Epoch)
}
¶
Secret keys MUST NOT be stored inside the node record; they are derived on demand from the key hierarchy.¶
Edge = {
Previous_Node : Node_ID(k),
Next_Node : Node_ID(k+1),
Sequence : k,
Event : <event type>,
Context_Version : <Context_Binding at time of edge>,
Transition_Type : NORMAL | EPOCH_BOUNDARY
| REAUTH | RECOVERY | REVOCATION,
Authentication_Proof: AEAD_Tag(Packet(k))
}
¶
The graph is reproducible from authenticated state; it does not need to be transmitted as a secret object.¶
Node_Key =
HKDF-Expand(Root_Secret,
"SGCP_NODEKEY" || Session_ID || Epoch || Node_ID,
key_length)
Packet_Key =
HKDF-Expand(Epoch_Key,
"SGCP_PACKET" || Direction || Sequence,
key_length)
¶
The Root Secret is the sole ancestor of all cryptographic keys. It MUST be protected in memory and erased when the session terminates.¶
Epoch_Key =
HKDF-Expand(Root_Secret,
"SGCP_EPOCH" || uint32(Epoch)
|| Context_Binding,
key_length)
¶
Old Epoch Keys SHOULD be erased after epoch transition to maintain forward secrecy within the session.¶
Packet_Key =
HKDF-Expand(Epoch_Key,
"SGCP_PACKET" || Direction || uint64(Sequence),
key_length)
¶
Transform_State =
HKDF-Expand(Epoch_Key,
"SGCP_TRANSFORM" || uint64(Sequence)
|| Direction,
transform_length)
¶
The following labels MUST be used as-is (ASCII, no null terminator) in all HKDF-Expand info parameters:¶
"SGCP_ROOT_v1" "SGCP_ROOT_PQ_v1" "SGCP_SID" "SGCP_HANDSHAKE" "SGCP_EPOCH" "SGCP_PACKET" "SGCP_TRANSFORM" "SGCP_NODEKEY" "SGCP_CHAIN" "SGCP_CHAIN_GENESIS" "SGCP_CTX_BIND_v1" "SGCP_RECOVERY"¶
The state-dependent transformation is a deterministic, invertible byte-level operation applied to the AEAD ciphertext+tag before the packet is placed on the wire.¶
The transformation MUST satisfy:¶
The transformation MUST NOT be used as a substitute for encryption. Confidentiality and integrity are guaranteed by the AEAD construction.¶
A session is divided into one or more epochs. Each epoch possesses a unique epoch number, a distinct Epoch Key, an independent sequence-number space starting from 0, and an independent replay window.¶
SESSION S1
Epoch 0: N0 -> N1 -> N2 -> N3
|
| Context Change
v
Epoch 1: N0' -> N1' -> N2' -> N3'
|
| Key Rotation
v
Epoch 2: N0'' -> N1'' -> N2''
When a context-change event is detected:¶
Proactive key rotation SHOULD occur after a configurable number of packets (e.g., every 2^32 packets) within one epoch, after a configurable time interval, or upon receipt of a KEY_ROTATION packet from the peer.¶
PACKET_ACCEPTED KEY_ROTATION CONTEXT_CHANGED REAUTHENTICATED SESSION_RECOVERY TIMEOUT POLICY_CHANGE REVOCATION¶
Normal:
N100 -- PACKET_ACCEPTED --> N101
Key Rotation:
N101 -- KEY_ROTATION --> N0(Epoch+1)
Context Change + Reauth:
N200 -- CONTEXT_CHANGED --> T0
T0 -- REAUTHENTICATED --> N0(Epoch+1)
Revocation:
N300 -- REVOCATION --> TERMINATED
¶
The server MUST NOT accept a claimed Session_ID without completing the challenge-response. Old traffic keys MUST NOT be reused after a recovery event.¶
CLIENT SERVER
| |
|--- RECOVERY_INIT ----------------->|
| (Session_ID_Hint, Device_ID, |
| Client_Nonce_R) |
| |
|<-- Recovery_Challenge -------------|
| |
|--- RECOVERY_ACK ------------------>|
| (Challenge_Response_R, |
| Client_DH_Public_Key_R) |
| |
|<-- Server_DH_Public_Key_R ---------|
| |
| Recovery_Secret derived |
| New Epoch(N), Sequence = 0 |
Both endpoints derive the same state without transmitting any secret. Because state is derived from the sequence number (not from the previous packet's decryption result), the receiver CAN reconstruct the expected state for any received sequence number within the current epoch, supporting out-of-order delivery:¶
State(N) = F(Epoch_Key, N, Direction)¶
When the receiver detects irrecoverable state divergence, it MUST initiate session recovery or terminate the session.¶
The Client-to-Server direction transfers the MP3 file in segments. The Server-to-Client direction sends per-segment acknowledgements and real-time processing status. Both channels are active simultaneously (full-duplex operation).¶
File: podcast_episode_42.mp3
Size: 7,340,032 bytes (7 MB exactly)
SHA-256 Hash: A3F182B0C44179D35E6F2A9104BC8D7E
F2034A5687CD19E023B456A789F01C2D
4E7890ABCD1234EF567890BC12DE9C7D
Bit Rate: 128 kbps
Duration: ~7 min 41 sec
Sample Rate: 44100 Hz
Channels: Stereo (2)
MPEG Version: MPEG-1 Layer III
ID3 Tag Length: 10,240 bytes (prepended)
Segment Size: 8,192 bytes
Total Segments: 896 (= 7,340,032 / 8,192)
¶
The following message sequence uses illustrative (non-real) cryptographic values. All multi-octet fields are in network byte order (big-endian).¶
Header:
Version = 0x01
Packet_Type = 0x01 (CLIENT_HELLO)
Flags = 0x01 (FULL_DUPLEX)
Session_ID = 0x00..00 (zero, pre-session)
Epoch = 0x00000000
Sequence = 0x0000000000000000
Key_ID = 0x0000000000000000
Payload (cleartext TLV):
ProtocolVersion = 0x0001
SupportedSuites = [
SGCP_X25519_AESGCM256_SHA256,
SGCP_X25519_MLKEM768_AESGCM256_SHA256
]
Device_ID = "DEV-AUDIO-CLIENT-42"
Certificate = <DER-encoded X.509>
Client_Nonce = 0x8A3F...1B2C (32 bytes)
Client_DH_Pub = 0x5C2D...7F4A (32 bytes, X25519)
Client_KEM_Pub = 0x3A1B...E9D2 (1184 bytes, ML-KEM-768)
¶
Payload (cleartext TLV):
SelectedSuite = SGCP_X25519_MLKEM768_AESGCM256_SHA256
Server_Certificate = <DER-encoded X.509>
Server_Nonce = 0x2F7C...4E8D (32 bytes)
Server_DH_Pub = 0xA1B2...CD3E (32 bytes, X25519)
Server_KEM_CT = 0xB2C3...EF40 (1088 bytes, ML-KEM-768 CT)
Server_Challenge = 0xD4E5...1F60 (32 bytes)
Signature = 0x3045...8A21 (ECDSA-P256)
Client computes:
DH_Secret = X25519(Client_DH_Priv, Server_DH_Pub)
= 0x9F2A...7C3B
KEM_Secret = ML-KEM-768.Decapsulate(Client_KEM_Priv,
Server_KEM_CT)
= 0xE1D2...4C5B
Root_Secret = HKDF-Extract("SGCP_ROOT_PQ_v1",
DH_Secret || KEM_Secret
|| Client_Nonce || Server_Nonce
|| H(Client_Cert) || H(Server_Cert))
= 0x7B4A...F9CE
¶
Session_ID =
H("SGCP_SID" || Root_Secret || Client_Nonce || Server_Nonce)
= 0xC3D4...A5B6 (16 bytes)
Payload (Handshake_Key-AEAD encrypted):
Challenge_Response =
ECDSA-P256-Sign(Client_Private_Key,
H("SGCP_CHALLENGE" || Server_Challenge
|| Client_Nonce))
= 0x3046...9B72
ClientFinish_MAC =
HMAC-SHA256(Handshake_Key,
H("SGCP_CFIN" || ClientHello || ServerHello))
= 0xA2B3...7C8D
Context_Binding =
H("SGCP_CTX_BIND_v1"
|| 0x0001 (Version)
|| "DEV-AUDIO-CLIENT-42"
|| 0x06 (TCP)
|| 0xC822 (Port 51234)
|| 0x20FB (Port 8443)
|| Socket_ID
|| Session_ID)
= 0xF1A2...B3C4
Epoch_0_Key =
HKDF-Expand(Root_Secret,
"SGCP_EPOCH" || 0x00000000 || Context_Binding,
32)
= 0x4D5E...6F70
Handshake complete at T+0.022s.
Both sides: Epoch = 0, Sequence = 0, State = ACTIVE.
¶
Plaintext media header + data:
Media_Type = 0x01 (AUDIO)
Codec = 0x01 (MP3)
Segment_Index = 0x00000000
Total_Segments = 0x00000380 (896)
Byte_Offset = 0x0000000000000000
Segment_Byte_Len = 0x00002000 (8192)
File_Hash = A3F182B0...9C7D (32 bytes)
Segment_Data = [bytes 0..8191 of podcast_episode_42.mp3]
Packet_Key_0 =
HKDF-Expand(Epoch_0_Key,
"SGCP_PACKET" || 0x01 || 0x0000000000000000,
32)
= 0x1A2B...3C4D
Nonce_0 = Epoch(0) || Seq(0) || Dir(0x01)
AAD_0 = Session_ID || 0x00000000 || 0x0000000000000000
|| 0x01 || Context_Binding || Key_ID_0
(Ciphertext_0, Tag_0) =
AES-256-GCM-Encrypt(Packet_Key_0, Nonce_0,
Plaintext_0, AAD_0)
Transform_State_0 =
HKDF-Expand(Epoch_0_Key,
"SGCP_TRANSFORM" || 0x0000000000000000 || 0x01,
8208) // 8192 + 16 (AEAD tag)
Wire_Payload_0 =
XOR(Ciphertext_0 || Tag_0, Transform_State_0)
Wire Header (illustrative hex):
01 10 01 00 ; Ver=1 Type=MEDIA_SEGMENT Flags=FD
C3D4A5B6C7D8E9F0 ; Session_ID [0..7]
A1B2C3D4E5F6A7B8 ; Session_ID [8..15]
00000000 ; Epoch = 0
0000000000000000 ; Sequence = 0
01 ; Direction = C->S
00 ; Reserved
[Key_ID_0: 8 bytes]
00002010 ; Payload Length = 8208
[CRC-16]
[Wire_Payload_0: 8208 bytes]
State after Seg 0: Sequence_C2S = 1
¶
Seg 1 (T+0.030s): Byte_Offset = 8192 Packet_Key differs Seg 2 (T+0.035s): Byte_Offset = 16384 Packet_Key differs Seg 3 (T+0.040s): Byte_Offset = 24576 Packet_Key differs State Graph (C->S) after 4 segments: N0 --[Pkt0]--> N1 --[Pkt1]--> N2 --[Pkt2]--> N3 --[Pkt3]--> N4¶
Time Client->Server Server->Client ------- -------------------------- --------------------------- T+0.025 MEDIA_SEGMENT Seq=0 (8KB) T+0.030 MEDIA_SEGMENT Seq=1 (8KB) T+0.033 MEDIA_ACK Seq=0 T+0.035 MEDIA_SEGMENT Seq=2 (8KB) T+0.038 MEDIA_ACK Seq=1 T+0.040 MEDIA_SEGMENT Seq=3 (8KB) T+0.043 MEDIA_ACK Seq=2 T+0.045 MEDIA_SEGMENT Seq=4 (8KB) T+0.048 MEDIA_ACK Seq=3 T+0.050 MEDIA_SEGMENT Seq=5 (8KB) T+0.055 STATUS Seq=4 (decode OK) ... T+0.880 MEDIA_SEGMENT Seq=895(8KB) (final segment) T+0.883 MEDIA_ACK Seq=895 T+0.885 STATUS (complete, PASS)
The S->C Packet Key is derived independently from the C->S key:¶
Packet_Key_S2C_0 =
HKDF-Expand(Epoch_0_Key,
"SGCP_PACKET" || 0x02 || 0x0000000000000000,
32)
= 0x7E8F...9A0B (entirely different from any C->S key)
¶
After 512 segments (Sequence = 512 on C->S), the client initiates proactive key rotation at T+0.453s.¶
KEY_ROTATION packet (C->S, Seq=512, Epoch=0):
Packet_Type = 0x06 (KEY_ROTATION)
Flags = 0x09 (FULL_DUPLEX | KEY_ROTATION_PENDING)
Payload (encrypted):
New_Epoch = 1
Reason = PROACTIVE_ROTATION
MAC = HMAC-SHA256(Epoch_0_Key,
"SGCP_KEYROT" || Session_ID || 1)
Server verifies, sends KEY_ROTATION_ACK (S->C, Seq=256, Epoch=0).
Both sides derive Epoch 1:
Epoch_1_Key =
HKDF-Expand(Root_Secret,
"SGCP_EPOCH" || 0x00000001 || Context_Binding,
32)
= 0x5F6A...7B8C
Epoch_0_Key erased from memory.
C->S: Sequence reset to 0, Epoch = 1
S->C: Sequence reset to 0, Epoch = 1
State Graph at boundary:
Epoch 0 (C->S): N0 -> N1 -> ... -> N512
|
| KEY_ROTATION (E512)
v
Epoch 1 (C->S): N0' -> N1' -> ... (Seg 512 onwards)
First packet in Epoch 1 (T+0.457s):
Epoch = 0x00000001
Seq = 0x0000000000000000
Flags = 0x03 (FULL_DUPLEX | EPOCH_BOUNDARY)
Packet_Key_E1_C2S_0 =
HKDF-Expand(Epoch_1_Key,
"SGCP_PACKET" || 0x01 || 0x0000000000000000,
32)
= 0x2C3D...4E5F
¶
Server reassembles all 896 segments and verifies integrity:¶
Received_File_Hash = SHA-256(reassembled 7,340,032 bytes)
Verify: Received_File_Hash == A3F1...9C7D -> PASS
Transfer Summary:
C->S Packets: 896 MEDIA_SEGMENT + 1 KEY_ROTATION
+ 1 SESSION_CLOSE = 898 packets
S->C Packets: 896 MEDIA_ACK + ~179 STATUS
+ 1 KEY_ROTATION_ACK = ~1077 packets
Epochs used: 2 (Epoch 0: segs 0-511; Epoch 1: segs 512-895)
Total data: 7,340,032 bytes
Integrity: PASS (end-to-end SHA-256 match)
Key uniqueness: Every packet used a unique derived key.
¶
STATE = {
Session_ID, // 128-bit session identifier
Epoch, // 32-bit epoch counter
Sequence, // 64-bit per-direction sequence counter
Direction, // C->S or S->C
Node_ID, // current graph node identifier
Key_ID, // current non-secret key identifier
Context_Binding, // 256-bit context hash
Device_State, // device trust/posture
Port_State, // port binding
Socket_State, // socket binding
Trust_State, // session trust level
Replay_State, // (Window_High, Window_Bits[W])
Policy_Version, // applicable policy revision
State_Version, // monotonic state-version counter
Chain_Hash // hash-chain pointer to current node
}
¶
Secret keys are NOT stored in the state record; they are derived from the Root Secret on demand from a separate protected memory region.¶
| Property | Threat Mitigated |
|---|---|
| Mutual authentication | Impersonation, MITM |
| ECDH key establishment | Passive eavesdropping |
| Per-packet unique keys | Key compromise propagation |
| Context binding | Session transplantation |
| Authenticated state edges | Fake state injection |
| Replay window | Replay attacks |
| Epoch rotation | Long-term key exposure |
| Session recovery auth | Session token replay |
| Hash chain | State history tampering |
| Deterministic state rule | Oracle attacks on transformation |
| ML-KEM hybridization | Quantum-computer attacks |
The replay window rejects packets with a sequence number that has already been accepted within the current epoch. The AEAD construction ensures that a replayed packet cannot be modified to appear as a new packet (the AAD includes the sequence number and epoch). An epoch boundary resets the sequence space; old-epoch packets MUST NOT be accepted in a new epoch.¶
Implementations MUST NOT attempt to brute-force candidate transform states or packet keys. When the receiver detects irrecoverable state divergence, it MUST initiate session recovery or terminate the session.¶
The receiver MUST derive exactly ONE expected state for a received packet and test it. If the single test fails, the packet is discarded. The receiver MUST NOT iterate over multiple candidate states or transformations. Violating this rule creates an oracle that could allow an adversary to learn information about the key hierarchy.¶
This document requests the following IANA registrations. Values not listed in a registry are unassigned; new values MUST be registered via the Specification Required policy [RFC8126].¶
| Value | Name |
|---|---|
| 0x0001 | SGCP_X25519_AESGCM256_SHA256 |
| 0x0002 | SGCP_P256_AESGCM256_SHA256 |
| 0x0003 | SGCP_X25519_MLKEM768_AESGCM256_SHA256 |
| 0x0004 | SGCP_P256_MLKEM1024_AESGCM256_SHA384 |
| Value | Name |
|---|---|
| 0x01 | CLIENT_HELLO |
| 0x02 | SERVER_HELLO |
| 0x03 | CLIENT_FINISH |
| 0x04 | DATA |
| 0x05 | CONTEXT_CHANGE |
| 0x06 | KEY_ROTATION |
| 0x07 | REAUTH_REQUEST |
| 0x08 | REAUTH_RESPONSE |
| 0x09 | RECOVERY_INIT |
| 0x0A | RECOVERY_ACK |
| 0x0B | ALERT |
| 0x0C | SESSION_CLOSE |
| 0x10 | MEDIA_SEGMENT |
| 0x11 | MEDIA_ACK |
| Value | Name |
|---|---|
| 0x01 | AUTH_FAILURE |
| 0x02 | DECRYPTION_FAILURE |
| 0x03 | REPLAY_DETECTED |
| 0x04 | STATE_MISMATCH |
| 0x05 | CONTEXT_VIOLATION |
| 0x06 | POLICY_VIOLATION |
| 0x07 | SESSION_EXPIRED |
| 0x08 | REVOCATION |
| 0x09 | INTERNAL_ERROR |
| 0xFF | CLOSE_NOTIFY |
CLIENT (C->S) SERVER ==================================== ============================ Epoch=0, Seq=0, Node=N0_C2S Epoch=0, Seq=0, Node=N0_C2S | | |--- MEDIA_SEGMENT(Seq=0) ----------->| | | verify -> N1_C2S | |--- MEDIA_ACK(S2C,Seq=0) ->| |--- MEDIA_SEGMENT(Seq=1) ----------->| | -> N2_C2S | verify -> N2_C2S ...¶
Epoch=0, Seq=512, Node=N512_C2S | |--- KEY_ROTATION(Seq=512) ---------->| | |--- KEY_ROTATION_ACK ------>| Epoch=1, Seq=0, Node=N0'_C2S (Epoch_0_Key erased) |--- MEDIA_SEGMENT(Epoch=1,Seq=0) --->|¶
CONNECTION LOST | |--- RECOVERY_INIT ------------------>| |<-- Recovery_Challenge --------------| |--- RECOVERY_ACK ------------------->| |<-- Server_DH_Public_Key_R ----------| | Epoch=N+1, Seq=0 (fresh keys) |--- MEDIA_SEGMENT(Epoch=N+1) ------->|¶
The following values are illustrative only and do not constitute real cryptographic test vectors. Implementors MUST generate test vectors using real implementations of the specified primitives.¶
Session parameters:
Device_ID = "DEV-TEST-01"
Client_Nonce = 32 bytes of 0xAA
Server_Nonce = 32 bytes of 0xBB
DH_Secret = 32 bytes of 0xCC (hypothetical)
KEM_Secret = 32 bytes of 0xDD (hypothetical)
Root_Secret (illustrative):
HKDF-Extract("SGCP_ROOT_PQ_v1",
0xCC...CC || 0xDD...DD
|| 0xAA...AA || 0xBB...BB
|| H("DEV-TEST-01-CERT")
|| H("SRV-TEST-01-CERT"))
= <32 bytes>
Session_ID (illustrative):
HKDF-Expand(Root_Secret, "SGCP_SID" || ..., 16)
= <16 bytes>
Real test vectors will be provided in a future revision of this
document following independent implementation and verification.
¶
A reference implementation of SGCP is available as an open-source
Python package on the Python Package Index (PyPI) under the package
name sgcp.¶
PyPI: https://pypi.org/project/sgcp/¶
Installation:¶
pip install sgcp¶
The reference implementation covers all normative sections of this document, including:¶
The implementation provides two high-level endpoint classes:
SGCPClient and SGCPServer, both exposing a
unified receive() method that automatically dispatches on
packet type (DATA 0x04 vs MEDIA_SEGMENT 0x10), returning an
SGCPMessage envelope containing the decrypted payload.¶
Source repository: https://github.com/sgcp-wg/sgcp-protocol¶
IETF Datatracker: https://datatracker.ietf.org/doc/draft-sgcp-state-graph-cryptographic-protocol/¶