CRYPTOGRAPHIC HANDSHAKE

IslandAccord & The Key Hierarchy

How C6P v1 establishes authenticated sessions through IslandAccord handshake and derives hierarchical keys for perfect forward secrecy.

3DH+OTP
Key Agreement
Ed25519
Authentication
3-Tier
Key Hierarchy
HKDF
Derivation

What is IslandAccord v1?

An authenticated prekey handshake protocol providing mutual authentication and session establishment.

IslandAccord v1 is C6P's canonical authenticated prekey handshake. It combines X25519 Diffie-Hellman key agreement with Ed25519 signatures to provide:

Server Blindness

Server never learns session secrets — all cryptography happens client-side

Initiator Authentication

Responder cryptographically verifies initiator identity via transcript-bound Ed25519 signature

Explicit Key Confirmation

Both parties prove they derived identical secrets before session activation

Downgrade Resistance

Protocol version and cipher suite are transcript-bound and signature-bound

Production Status

IslandAccord v1 is PRODUCTION/CANONICAL/NORMATIVE. All implementations must follow the specification exactly. Any deviation breaks interoperability and security guarantees.

The DH Set: 3DH + Optional OTP

IslandAccord uses three mandatory Diffie-Hellman operations, plus an optional fourth for enhanced security.

Key Material Overview

Each party maintains long-term and ephemeral keys:

Party Key Type Purpose
Alice (Init) IK_dh (X25519) Long-term identity DH key
Alice IK_sig (Ed25519) Long-term signing key for authentication
Alice EK (X25519) Fresh ephemeral per-session
Bob (Resp) IK_dh (X25519) Long-term identity DH key
Bob SPK (X25519) Signed prekey (rotated periodically)
Bob OTP (X25519) One-time prekey (single-use, optional)

DH Computations

// Mandatory DHs (3DH)
DH1 = X25519(A_IK_dh_priv, B_SPK_pub)
↳ Long-term initiator × rotating responder prekey
DH2 = X25519(A_EK_priv, B_IK_dh_pub)
↳ Ephemeral initiator × long-term responder
DH3 = X25519(A_EK_priv, B_SPK_pub)
↳ Ephemeral initiator × rotating responder prekey
// Optional 4th DH (when OTP is used)
DH4 = X25519(A_EK_priv, B_OTP_pub)
↳ Ephemeral initiator × one-time responder prekey
// Input Key Material
IKM = DH1 || DH2 || DH3 || [DH4]
↳ Length: 96 bytes (no OTP) or 128 bytes (with OTP)
Security Property

The 3DH set ensures that compromise of any single long-term key (IK_dh or SPK) doesn't compromise session keys. The optional OTP adds an extra layer: even if both long-term keys are compromised, past sessions with OTP remain secure.

Transcript Binding & Authentication

Every handshake parameter is bound into a canonical transcript, protecting against server splicing and downgrade attacks.

Canonical Transcript Construction

The transcript is the single source of truth for binding. It includes (in exact order):

Canonical Transcript Fields (Exact Order)
1. "ISLAND_ACCORD_V1" (label)
2. IA_VERSION (u8 = 0x01)
3. C6P_VERSION (u8 = 0x01)
4. suite_id (u8)
5. sessionId (8 bytes)
6. A_deviceId (16 bytes)
7. B_deviceId (16 bytes)
8. A_IK_dh_pub (32 bytes)
9. A_IK_sig_pub (32 bytes)
10. A_EK_pub (32 bytes)
11. B_IK_dh_pub (32 bytes)
12. B_IK_sig_pub (32 bytes)
13. spkId (8 bytes)
14. spkPub (32 bytes)
15. spkSig (64 bytes)
16. otpFlag (1 byte: 0x01 or 0x00)
17. otpId + otpPub (if flag = 0x01)
transcript_hash = SHA256(T)

Initiator Authentication

The initiator proves their identity by signing a hash of the transcript and session parameters:

Offer Signature (Initiator Authentication)
sig_input = SHA256(
    "IA_OFFER_SIG_V1" ||
    transcript_hash(32) ||
    U8(C6P_VERSION) ||
    U8(suite_id) ||
    sessionId(8) ||
    A_deviceId(16) ||
    B_deviceId(16)
)

offer_signature = Ed25519.Sign(A_IK_sig_priv, sig_input)
Fail-Closed Requirement

If transcript hashes don't match or signature verification fails, the handshake MUST abort immediately. The responder MUST NOT create an active session. Any mismatch indicates either an active attack or implementation bug.

Three-Tier Key Hierarchy

Root → Chain → Message: a hierarchical derivation ensuring forward secrecy and domain separation.

Tier 1: Root Key Derivation

Root Key (from IslandAccord IKM)
CTX = session_id(8) || initiator_device_id(16) || responder_device_id(16)

salt_root = SHA256("C6P_ROOT_V1" || CTX || transcript_hash)

prk_root = HKDF-Extract(salt=salt_root, ikm=IKM)

root_key = HKDF-Expand(prk_root,
                       info="C6P_ROOT_V1" || U8(0x01),
                       L=32)

Tier 2: Stream Chain Keys

Each session has two independent directional chain keys:

Chain Keys (i2r and r2i streams)
STREAM_CTX = U8(stream_id) || U8(message_type) || U8(suite_id)

salt_chain = SHA256("C6P_CHAIN_V1" || CTX || transcript_hash)

prk_chain = HKDF-Extract(salt=salt_chain, ikm=root_key)

// For stream_id ∈ {0x01 i2r, 0x02 r2i}
CK_stream = HKDF-Expand(prk_chain,
                        info="C6P_CHAIN_V1" || U8(0x01) || STREAM_CTX,
                        L=32)

Tier 3: Per-Message Keys

Message Key Material + Ratchet Step
salt_msg = SHA256("C6P_MSG_V1" || CTX || transcript_hash ||
                   STREAM_CTX || BE64(counter))

prk_msg = HKDF-Extract(salt=salt_msg, ikm=chain_key)

// Message key material (32 bytes)
mk_material = HKDF-Expand(prk_msg,
                          info="C6P_MSG_V1" || U8(0x01) || STREAM_CTX,
                          L=32)

// Next chain key (ratchet forward)
next_chain_key = HKDF-Expand(prk_msg,
                             info="C6P_CHAIN_V1" || U8(0x01) ||
                                  STREAM_CTX || BE64(counter),
                             L=32)

Key Hierarchy Visualization

📦 IslandAccord IKM (DH1||DH2||DH3||[DH4])
└─ 🔑 Root Key (32 bytes, session-bound)
├─ 🔗 Chain Key i2r (initiator → responder)
│ ├─ 📨 Message Key counter=0
│ ├─ 📨 Message Key counter=1
│ └─ 📨 Message Key counter=2...
└─ 🔗 Chain Key r2i (responder → initiator)
├─ 📨 Message Key counter=0
├─ 📨 Message Key counter=1
└─ 📨 Message Key counter=2...

Domain Separation & Nonces

Strict domain labels prevent key reuse across contexts. Deterministic nonces are safe by design.

Domain Separation Labels

Every derivation uses a unique ASCII label to prevent cross-context key reuse:

Label Purpose
C6P_ROOT_V1 Root key derivation from IKM
C6P_CHAIN_V1 Stream chain keys and ratcheting
C6P_MSG_V1 Per-message key material
C6P_NONCE_V1 Deterministic nonce derivation
C6P_KC_V1 Key confirmation tags
C6P_REKEY_V1 Reserved for future rekey operations
C6P_EXPORT_V1 Reserved for app-level export keys

Deterministic Nonce Derivation

C6P uses deterministic nonces, which is safe because each message uses a unique ephemeral key:

Nonce Derivation (Suite-Specific Length)
session_binding = SHA256("C6P_BIND_V1" || session_id(8) ||
                           initiator_device_id(16) ||
                           responder_device_id(16))

prk_nonce = HKDF-Extract(salt=session_binding, ikm=mk_material)

nonce_info = "C6P_NONCE_V1" ||
             U8(0x01) ||
             U8(suite_id) ||
             U8(message_type) ||
             U8(stream_id) ||
             session_id(8) ||
             BE64(counter)

nonce = HKDF-Expand(prk_nonce, info=nonce_info, L=nonce_len)

// Nonce lengths:
// ChaCha20-Poly1305:   12 bytes
// XChaCha20-Poly1305:  24 bytes
// AEGIS-128L:          16 bytes
Why Deterministic Nonces Are Safe

Deterministic nonces are permitted because: (1) every message uses a unique mk_material derived from (session, stream, counter), (2) nonce derivation binds all context including counter, and (3) replay detection rejects duplicate counters even if AEAD verifies.

Test Vectors & Validation

Canonical test vectors ensure bit-for-bit compatibility across Rust, Swift, and Node implementations.

Handshake Test Vectors

IslandAccord v1 includes comprehensive test vectors for cross-implementation validation:

island_accord_offer_vectors.json

Offer construction, SPK signature, transcript hash, offer signature

initiator_derive_vectors.json

DH computation, IKM, root/KC derivation, KC1

responder_derive_vectors.json

Mirrored DH, KC1 verification, KC2 computation

negative_vectors.json

Invalid signatures, transcript mismatches, encoding errors

Crypto Core Test Vectors

Key schedule and AEAD operations have dedicated vectors:

key_schedule_vectors.json

Root, chain, and per-message key derivations

nonce_vectors.json

Deterministic nonce for all suites (ChaCha20, XChaCha20, AEGIS)

aead_vectors_*.json

End-to-end seal/open with AAD for each suite

replay_skipwindow_vectors.json

Skip-window acceptance and replay rejection scenarios

Generating Test Vectors
$ cd rust
$ cargo run --package c6p-test-vectors --bin c6p-gen-vectors \
--output test-vectors --force --verbose
Output: rust/test-vectors/{handshake,crypto}/test-vectors/v1/
CI Enforcement

Hard rule: All implementations (Rust/Swift/Node) MUST pass test vectors bit-for-bit. CI MUST fail on any mismatch. This ensures identical cryptographic behavior across platforms.

Key Takeaways

IslandAccord provides mutual authentication without server learning session secrets

3DH + optional OTP ensures compromise of single long-term key doesn't break sessions

Three-tier key hierarchy (root → chain → message) enables perfect forward secrecy

Domain separation labels prevent key reuse across different cryptographic contexts

Deterministic nonces are safe because every message uses unique ephemeral keys

Test vectors ensure interoperability across Rust, Swift, and Node implementations