Security analysis

Security analysis of the current implementation.

This page is a technical walkthrough of what version 0.7 is intended to protect, how each mechanism contributes to that protection, and where the current implementation still has known gaps. It describes the code as currently deployed, not the target design in the abstract.

Scope

What this system is, and what it is not.

Keylay is a coordination layer for remote multisig participants. It is not a wallet, not a signer, and not a custody service. Its job is narrower: move coordination data across distance while reducing how much trust must be placed in the relay and surrounding infrastructure.

The relay is intended to be a dumb message forwarder. It routes traffic on a channel and assigns simple roles, but it is not supposed to learn plaintext, hold session keys, or participate in signing. The central security question is therefore whether the relay can read, alter, or substitute coordination data without being detected or without already knowing the shared code.

Security goals

  • Relay should not learn message plaintext
  • Relay should not be able to substitute handshake keys unless it already knows the code
  • Later compromise of the code should not reveal prior session plaintext
  • Private keys should remain outside Keylay entirely

Non-goals

  • Endpoint compromise is not solved here
  • Code knowledge is treated as membership
  • Identity beyond the code is not established by the current protocol
  • Replay protection against the relay is provided; counter-based replay within a compromised endpoint is not
Threat model

What adversary is being considered.

The current design assumes the relay may be observable and potentially malicious. It may record traffic, attempt to read message contents, attempt handshake substitution, or replay previously seen ciphertext. The current implementation is intended to prevent plaintext disclosure to the relay and to resist relay MITM by binding handshake messages to knowledge of the shared code. It does not prevent an attacker who already knows the code from acting as a legitimate participant.

Endpoint malware, compromised browsers, compromised phones, insecure out-of-band sharing of the code, and wallet-side verification failures remain outside what the protocol itself can solve. That limitation should be stated plainly, not hidden behind transport security claims.

Protocol walk-through

How the current implementation produces its security properties.

The security design of this protocol is described step by step. Each step contributes a limited property. No single step does all the work. Standard cryptographic protocols are referred to by abbreviation without elaboration.

1

Code entry

The join flow accepts any code of 10 or more characters. Generated codes use a 31-character alphabet and default to length 10, but manually entered codes allow a broader character set. That means effective security depends on the actual code used, not just the generator defaults. A technical reader should therefore treat session-code strength as a parameterized question, not a fixed constant.

2

Channel derivation

The code is SHA-256 hashed, and the first 32 hex characters are used as the WebSocket channel name. This gives the relay a room identifier derived from the code without exposing the raw code directly on the wire as the room name. It does not, by itself, authenticate peers or encrypt anything. Its role is channel identification only.

3

HMAC key derivation from the code

The code is stretched with PBKDF2-SHA256 using salt 'keylay-v1-hmac' and 300,000 iterations to derive an HMAC key. In the current implementation, this HMAC key is used to prove knowledge of the code during handshake. It is not the AES session key, and message confidentiality does not depend directly on PBKDF2 output. This matters because offline guessing cost against the code is not the same thing as decryption cost against past traffic.

4

Ephemeral X25519 key generation

Each participant generates an ephemeral X25519 key pair for the session. These keys are session-scoped, not long-lived identity keys. Their purpose is to produce a fresh Diffie-Hellman shared secret for each session. This is the basis of forward secrecy: if the ephemeral private key is discarded after handshake, later compromise of the code does not reveal old session keys.

5

Signed public-key exchange

Each peer exports its ephemeral public key, base64 encodes it, and signs that public key with the HMAC key derived from the code. The peer then sends the pair {pubkey, sig} inside a hello payload. The security contribution here is narrow but important: the relay cannot substitute a different public key unless it can also forge a valid HMAC, which requires knowing the code. Without this step, the relay could perform key substitution and place itself in the middle of the exchange.

6

X25519 shared secret and HKDF session key

Once a peer receives a valid foreign public key, it computes X25519 shared bits with its own ephemeral private key. Those bits are then imported as HKDF material. The HKDF info field binds the resulting AES key to the sorted pair of public keys, so both sides derive the same session key and that key is tied to the exact handshake participants. No session key is ever transmitted.

7

AES-GCM message protection

Messages are encrypted under AES-GCM with a random 12-byte initialization vector (IV) per message. The additional authenticated data (AAD) is 'keylay-v1|' + counter, where counter is a monotonically increasing integer included in the message. This binds each ciphertext to a specific sequence position: modifying the counter field in transit causes GCM tag failure. The receiver enforces strict monotonic ordering and rejects any message whose counter is not strictly greater than the last accepted value, so a previously recorded ciphertext cannot be replayed.

8

Handshake completion and reconnect behavior

The state machine moves from IDLE to HELLO_SENT to ACTIVE. Duplicate retries are ignored. If a different peer public key appears after activation, the current session is wiped and a full handshake restart is triggered. This is a limited but useful defense against silent peer-key replacement on reconnect.

What each step guarantees

No single mechanism secures the system by itself.

Channel hash

Gives a room identifier derived from the code. It does not provide authentication or encryption.

HMAC on public keys

Proves knowledge of the code during handshake and blocks relay key substitution unless the relay also knows the code.

Ephemeral X25519

Produces fresh shared secret material per session and enables forward secrecy once ephemeral private keys are discarded.

HKDF with bound public keys

Derives the same AES session key on both sides and binds that key to the exact participant keypair combination.

AES-GCM

Protects message confidentiality and integrity under the current session key. Counter-bound AAD and strict receive-side counter enforcement prevent replay of recorded ciphertext.

State reset on peer-key change

Detects reconnect with a different peer public key and forces a new handshake instead of continuing silently.

Security properties

What the protocol does and does not guarantee.

Protections the current implementation provides

  • Relay plaintext resistance — relay sees ciphertext and channel hash, not plaintext messages
  • Relay MITM resistance without code — relay cannot forge valid handshake signatures without knowledge of the code
  • Forward secrecy against later code compromise — past sessions are not decrypted merely by learning the code later, because session keys come from ephemeral X25519, not directly from the code
  • No session key transmission — both peers derive the key locally from the shared secret
  • No custody of wallet secrets — Keylay is outside the signing path

Protections the current implementation does not provide

  • No in-session protection if code is known — code knowledge is membership; an attacker who knows the code can join and participate
  • No endpoint compromise protection — malware on user devices can access plaintext before encryption or after decryption
  • No identity beyond code knowledge — protocol authentication is proof of code possession, not proof of real-world identity
  • No memory-hard password defense — PBKDF2 raises brute-force cost but is not memory-hard; Argon2 or scrypt would be stronger
  • No server-side rate limiting or size enforcement — the relay does not enforce connection rate limits, message frequency limits, or payload size caps
What Keylay does not protect against

Some risks remain outside the protocol.

Endpoint compromise

  • Compromised browser or operating system
  • Malware on the online coordination device
  • Wallet-side verification failures
  • Exfiltration before encryption or after decryption

Out-of-band failures

  • Insecure sharing of the session code
  • Human confusion about who is actually on the other end
  • Weak manually chosen codes
  • Operational mistakes outside the protocol itself
Session code analysis

What the session code protects, and how much protection it provides.

The session code does not encrypt your messages. That is the job of a session key generated fresh for every connection and discarded when it ends — meaning that even if your code were compromised later, past sessions cannot be decrypted.

The code's job is narrower: it authenticates the handshake. When two browsers exchange the ephemeral keys needed to start a session, the code proves to each side that the other party is legitimate and that no one has substituted different keys in transit. The real threat is not "can an attacker decrypt my messages by guessing the code?" but rather: can an attacker break the handshake authentication fast enough to intercept a live session, or prove after the fact that they could have?

An attacker who has captured the handshake transcript — which requires access to the relay server — can attempt every possible code on their own hardware, bypassing any server-side rate limiting. The system uses PBKDF2 with 300,000 iterations to make each guess expensive. On a single modern GPU cluster, this permits roughly one million guesses per second. The tables below reflect that assumption.

Crack time by alphabet and length

Average time to exhaust half the keyspace at 1M guesses per second.

Numeric only (0–9)

Not suitable at any practical length. A 12-digit numeric code falls within 6 days on a single GPU cluster. A parallelized attack reduces this to hours.

LengthAvg. crack time
6 digits< 1 second
8 digits~50 seconds
10 digits~1.4 hours
12 digits~5.8 days

Lowercase alphanumeric (a–z, 0–9)

The system minimum of 10 characters requires ~58 years on a single cluster — structurally infeasible as a real-time attack. No attacker can crack the code before a session completes.

LengthAvg. crack time
6 characters~18 minutes
8 characters~16.3 days
10 characters~57.9 years
12 characters~75,100 years

Extended alphabet (48 characters)

Adding special characters from a safe set — those that don't break URLs or copy-paste — raises the 10-character crack time to ~1029 years and pushes 12 characters to ~2.4 million years.

LengthAvg. crack time
6 characters~1.7 hours
8 characters~163 days
10 characters~1,029 years
12 characters~2.37 million years
Why 10 characters

The minimum is the point where real-time interception becomes impossible.

A handshake completes in seconds. An attacker would need to crack the code before the session ends to mount a live man-in-the-middle attack. Six lowercase alphanumeric characters fall in 18 minutes — well within an active session window. At the 10-character minimum, cracking requires ~58 years on a single GPU cluster. No current hardware configuration can break the handshake before a session closes.

The 58-year figure is also the cost of a retrospective attack: proving after the fact that interception was theoretically possible. Forward secrecy means past message content remains safe regardless, but a broken handshake undermines trust in the session's integrity. Ten characters is the minimum that makes this retrospective case economically unattractive for a single-cluster attacker.

All generated codes use a 31-character unambiguous alphabet (abcdefghjkmnpqrstuvwxyz23456789) with rejection sampling to eliminate modular bias. Human-chosen codes of any length are weaker than randomly generated ones — people choose predictably. Use the generator.

Assumptions

What these estimates rest on.

  • Single GPU cluster at 1M guesses/second. A parallelized attacker reduces crack times proportionally. For high-stakes use, treat these figures as optimistic and choose longer codes.
  • 300,000 PBKDF2 iterations. Raising this to 1,000,000 would push the 8-character baseline from ~16.3 days to ~54.3 days, at the cost of slightly slower session setup (~0.3–1 second).
  • Requires the handshake transcript. This attack requires access to the relay server. A passive network observer cannot attempt it because traffic between browser and server is protected by TLS.
  • Code knowledge equals access. No cryptographic length compensates for a code disclosed through other means — shoulder surfing, insecure transmission, or a compromised device.
Implementation review

What a line-by-line review of the deployed code confirmed and found.

The source code was reviewed against every specific security claim on this page. Each claim was traced to the corresponding function or variable and evaluated independently. The review also covered the relay server, session isolation logic, and server-side logging. This is a manual technical review with AI-assisted analysis, not a formal third-party security audit.

Confirmed

Claims that check out against the deployed code.

Relay plaintext resistance

Confirmed. Every payload sent over the channel is AES-GCM ciphertext. The channel identifier visible to the relay is SHA-256(code) truncated to 32 hex characters. The raw code never appears on the wire.

HMAC handshake authentication

Confirmed. Each peer signs its ephemeral public key with an HMAC-SHA256 key derived from the session code via PBKDF2 (300,000 iterations). The relay cannot forge a valid signature without knowing the code.

Forward secrecy

Confirmed. The ephemeral X25519 private key is set to null immediately after session key derivation. The session key is not derivable from the code alone; it requires the private key that was discarded at handshake completion.

No session key transmission

Confirmed. Both peers derive the AES-256-GCM key locally from X25519 shared bits processed through HKDF. The HKDF info field binds the derived key to the sorted pair of ephemeral public keys.

Replay protection

Confirmed. AES-GCM additional authenticated data includes a monotonic counter per message. The receiver tracks the last accepted counter and rejects any message that does not strictly advance it. The counter is advanced only after successful decryption.

Peer key change detection

Confirmed. If a different ephemeral public key arrives while a session is active, the incoming hello is rejected and the active session continues unchanged. A legitimate peer reconnecting with a new key must reload to restart the handshake from the beginning. This prevents a rogue peer from repeatedly forcing session resets by submitting fresh keys — a denial-of-service vector present in earlier versions.

Two-peer session enforcement

Confirmed at the server. A third connection attempt to an active session hash is refused and the socket is closed immediately.

Unencrypted data rejected

Confirmed. The client checks the encrypted flag on every incoming data message and silently discards any message that does not carry it.

Web Crypto only

Confirmed. All cryptographic operations — key generation, PBKDF2, HKDF, HMAC, AES-GCM — use the browser's native crypto.subtle implementation. No third-party cryptographic primitives are in the protocol path.

Server-side rate limiting

Confirmed. The relay enforces connection rate limits, message frequency limits, and payload size caps. The client-side pre-handshake message buffer also has a size cap. No straightforward denial-of-service path against the relay through message volume or size remains.

Content Security Policy

Confirmed. A Content-Security-Policy is set for the application. The policy restricts where the page can send data: network connections are limited to the same origin and the canonical relay, so a successful XSS injection cannot exfiltrate data to an arbitrary external host. Object embedding, form actions, and base-URI changes are all blocked. Note: the current policy includes 'unsafe-inline' in script-src because the app ships as a single inline-script bundle. This means an injected inline script could still read in-memory state. Replacing 'unsafe-inline' with explicit per-block hash entries is planned and will tighten this further.

No full IP addresses logged

Confirmed. The server logs truncated IP addresses only — the first two octets are retained for operational purposes; the remainder is zeroed before the log entry is written. No full visitor IP addresses are stored at any layer. No session content, session codes, or coordination data appear in any log.

Issues found and resolved

Problems identified in review and fixed before this version shipped.

No server-side rate limiting or size enforcement

The relay server imposed no restrictions on connection rate, message frequency, or payload size. The client-side pre-handshake buffer also had no size cap. This created a straightforward denial-of-service path against the relay independent of the cryptographic protocol.

Resolved in v0.7. Rate limiting, message frequency caps, payload size limits, and a pre-handshake buffer cap added to the relay.

No Content Security Policy

The page set no Content-Security-Policy header. In the absence of a CSP, a successful XSS injection would have had unrestricted access to the DOM, WebSocket connection, session key variables, and all decrypted payloads.

Resolved in v0.7. CSP added, restricting script sources and resource loading.

Hardening gaps

Known areas for future improvement.

These are not bugs in the current implementation. They are areas where the security posture could be strengthened beyond what is deployed today.

PBKDF2 is not memory-hard

PBKDF2-SHA256 at 300,000 iterations raises brute-force cost but is not memory-hard. Argon2id or scrypt would be significantly more resistant to GPU-accelerated offline attacks against the session code. Upgrading the KDF is a planned improvement.

Session limit is policy, not cryptographic

The relay enforces a two-participant limit per session hash at the server level. A compromised or replaced relay could bypass this enforcement. There is no in-protocol mechanism that cryptographically prevents a third party from joining if the relay permits it.

No out-of-band code channel verification

The protocol has no mechanism to verify the integrity of the channel used to share the session code. An adversary who can intercept or substitute the code during out-of-band sharing — by controlling the communication channel — can join as a legitimate participant. No in-band remedy exists for a fully compromised code-sharing channel.

No formal third-party audit

This review is thorough and AI-assisted, but it is not an independent third-party cryptographic audit. A formal audit by an external security firm is on the roadmap and will be conducted before the project moves out of alpha.

Open issues

No open issues remain in the current implementation.

All problems identified in prior reviews have been resolved. Known areas for future improvement are listed above under Hardening Gaps.

Review and versioning

Scope and version of this review.

Security claims on this page are specific to the version of the implementation they were reviewed against and may not apply to earlier or later versions. Each report documents a line-by-line review of the deployed code: what was confirmed, what was found, what was fixed, and what changed between revisions. This is a manual technical review with AI-assisted analysis, not a formal third-party security audit.

Report App version Date
Security Review R3 Current v0.7 April 2026 PDF
Security Review R2 v0.6 April 2026 PDF
Security Review R1 v0.6 April 2026 PDF