Skip to content

sync branches: dev <- main - #29

Merged
igmunv merged 22 commits into
devfrom
main
Aug 24, 2026
Merged

sync branches: dev <- main#29
igmunv merged 22 commits into
devfrom
main

Conversation

@igmunv

@igmunv igmunv commented Aug 24, 2026

Copy link
Copy Markdown
Owner

No description provided.

igmunv and others added 22 commits July 21, 2026 01:13
used brotli for compress data
Message latency was dominated by polling loops, not by cryptography. On a
zero-latency channel a single message took 1100 ms end to end, while the
entire crypto path (brotli + AES-GCM + ECDSA + WordCoder) accounts for
0.36 ms of that. Measurements were taken with two real CryptoLayer peers
connected over an in-memory channel.

Changes:

- base: use queue.Queue instead of a list guarded by a lock and polled
  every 0.1 s. A waiting worker now wakes the moment data arrives rather
  than on the next tick, which removes eight level traversals worth of
  sleep from every message. The worker also runs outside the lock, so a
  transport send that blocks on acknowledgments no longer stalls the
  levels above it.

- transport: wait for acknowledgments on a threading.Event instead of
  polling every 0.5 s. The retry path is now a bounded loop (6 attempts,
  5 s apart, the same ~30 s budget as before) rather than a recursive
  call that grew the stack on every timeout and never gave up.

- transport: send the chunks of one stream concurrently through a bounded
  pool. Waiting for each acknowledgment in turn cost a full round trip per
  chunk; the receiver already reassembles by chunk_id, so arrival order
  within a stream does not matter.

- crypto_layer: wait for handshake data on events instead of polling, and
  read the send buffers with qsize() now that they are queues.

Three reassembly bugs are fixed in the same change, because the higher
throughput and the new retransmission path turn them from rare into
routine:

- WAITING_STREAMS was never cleared while stream_id wraps at 256, so the
  257th stream appended its chunks to a completed one. Streams are now
  removed once delivered.
- A retransmitted chunk was appended again, so chunk_count could be
  reached with a chunk still missing. Chunks are now keyed by chunk_id.
- A retransmission arriving after its stream completed would rebuild and
  redeliver it. Recently completed packets are remembered in a bounded
  window, acknowledged again, and ignored.

The wire format is unchanged: the same number of channel messages with the
same byte layout, so existing peers stay compatible.
The project had no tests. These run two real CryptoLayer peers over an
in-memory channel, so the whole stack is exercised without a network.

- tests/test_pipeline.py asserts that what comes out is exactly what went
  in across payload sizes, chunk boundaries, unicode, multi-chunk streams,
  30% packet loss and stream-id wraparound, and that the plaintext never
  reaches the channel. Plain asserts, no test framework needed.
- bench/bench.py times each transform stage separately, which is how the
  polling overhead was identified as the real cost.
- bench/e2e.py reports handshake time, per-message latency, throughput and
  channel cost, with configurable channel latency and payload entropy.
- bench/loopback.py is the in-memory module the two above share. It can
  drop messages and record what reaches the channel.

Requires the cryptolayer-module-interface dependency already declared in
pyproject.toml.
The companion node id arrives from the network and is joined with the
known_nodes directory path without any validation, so a peer (or the
messenger relaying the session) could send "../../name" and make
CryptoLayer probe and overwrite a file outside data_dir. The node id is
sent before signature verification is enabled, so no authentication is
needed to reach this.

Accept a node id only if it has the expected shape - 64 lowercase hex
characters, two uuid4 values - and verify that the resulting path stays
inside known_nodes. The same check replaces the "length >= 64" test used
when reading the local node id file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
perf: убрать опрос из конвейера, ускорив доставку сообщения с 1100 мс до 1 мс
docs: фикс отсутствия английского README.md но при этом его упоминание, а также добавление английской версии документации. (#24)
…-node-id

fix: validate companion node id before using it as a file name
The application level processed every service packet regardless of the
handshake state, so MY_PUBLIC_KEY was accepted while signature checking
was still disabled (DO_SIGN is turned on only after the user confirms the
companion signature). An active attacker in the channel could therefore
replace the companion ECDH key and read one direction of the traffic,
even when both users verified their fingerprints correctly.

Add an explicit handshake state machine to the application level:
service packets are accepted only at their own stage, MY_PUBLIC_KEY only
after signature verification is enabled, and text/disconnect packets only
after the handshake is complete. Data that reached the upper levels
before verification was enabled is dropped, and the ECDH key is sent
again when the companion key is accepted, so a key dropped by the peer
does not stall the handshake.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rebased onto main. The Queue rework in #19 already wraps the worker calls,
so a failure no longer kills a level thread - what is left is not sending
packets that cannot be sent yet.

Transitional.sworker signs every outgoing packet, but SIGN_PRIVATE_KEY is
only set in signatures_setup, which runs after init_module. A messenger
module can block in create_session for a long time (login, confirmation
code), the transport ping timer fires after 30 seconds and pushes a packet
down to a level that cannot sign it. Drop such packets explicitly instead
of raising and logging a traceback every 30 seconds.

The ping itself is now enabled by the core once init() is done, as
requested in the review. Before that there is nothing to ping: the stack
cannot send, and the handshake itself proves the companion is there.
Without the flag, send_with_pending_ping could report a ping timeout to
the UI during a slow start or a slow signature check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs: фикс того что я недоглядел (#24)
…chine

fix: accept companion ECDH key only after signature verification is on
fix: keep level threads alive when a worker raises (#17)
- bind the ECDH shared secret to both public keys and a protocol label via HKDF-SHA256 instead of using the raw secret as the AES key
- replace the 8-byte signature fingerprint with a 160-bit SHA-256 fingerprint computed over the whole key
- enable companion signature trust only when the signature is actually accepted, so a user rejection no longer sets DO_SIGN

Closes #27
…t / #major

fix: аутентифицированный вывод AES-ключа и полноценный отпечаток подписи
@igmunv
igmunv merged commit 4bbc4c1 into dev Aug 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants