omq-blake3zmq 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ad881032a9ee2f62f9109d025e7eed78115dd86b2360f40a1edfab3f66a9fba
4
- data.tar.gz: a4632fbf7ee8346a84042064d917d46e6af3573779ee44a126a234be9895e782
3
+ metadata.gz: f303db5348da7fbd1c223fc9159414f530a10db03de06a6b51ce0708731262c0
4
+ data.tar.gz: 697ef532c927f8c4e1f0d380e5a023c50004ba1e995b91b4152ead7be46eabc3
5
5
  SHA512:
6
- metadata.gz: 56c18fb8e7f70d70a7ba1abfaa3e23a2874f4cf56b26c49ad35117634c23a6cd77093be4b06fb00f39371dc1ffa8cf51d9ccd80c839fd64ac23f68530ce5525e
7
- data.tar.gz: 4cd8f8f1b27fcfcb1e6c209ded268c064f370d77d6dff5748d4eee324d5b5ed092b8876dc418b86c64e6cb17f87fdd74c07566dba95bb129bca89bbbbb690fc2
6
+ metadata.gz: 477e5dca0140c73bc6bb312a0bd3c053462a989a035539b54ebd54ac933e5113916755de2869499532d4d2b52c9890a67415ae2fc80dd06c254ff3f58a0d9edc
7
+ data.tar.gz: 3fcc887fb323cd0bd8cfd0cdc5bd59519e6627a3874a5fa63ca4a430021531d5149b02619c72f8c17b903c0b792310570d559c71e8c2dbacf000692bd7dcec3d
data/README.md CHANGED
@@ -62,7 +62,7 @@ server.mechanism = Protocol::ZMTP::Mechanism::Blake3.server(
62
62
  )
63
63
  server.bind("tcp://127.0.0.1:9999")
64
64
 
65
- # Client socket (keys optional omit for anonymous/ephemeral identity)
65
+ # Client socket (keys optional; omit for anonymous/ephemeral identity)
66
66
  client = OMQ::REQ.new
67
67
  client.mechanism = Protocol::ZMTP::Mechanism::Blake3.client(
68
68
  server_key: server_pk,
@@ -72,6 +72,75 @@ client.mechanism = Protocol::ZMTP::Mechanism::Blake3.client(
72
72
  client.connect("tcp://127.0.0.1:9999")
73
73
  ```
74
74
 
75
+ ## Wire format
76
+
77
+ ### Handshake
78
+
79
+ Four ZMTP command frames over plain TCP:
80
+
81
+ ```
82
+ Client Server
83
+ | |
84
+ |-- HELLO (C', hello_box) ---------> | 232 bytes
85
+ | |
86
+ | <-- WELCOME (welcome_box) -------- | 224 bytes
87
+ | |
88
+ |-- INITIATE (cookie, init_box) ---> | variable
89
+ | |
90
+ | <-- READY (ready_box) ------------ | variable
91
+ | |
92
+ |====== encrypted data phase ========|
93
+ ```
94
+
95
+ HELLO carries the client's ephemeral public key `C'` and a box proving
96
+ knowledge of the server's permanent public key `S`. WELCOME returns the
97
+ server's ephemeral key `S'` inside a stateless cookie. INITIATE sends
98
+ back the cookie plus the client's permanent key `C` and a vouch binding
99
+ `C'` to `C`. READY confirms the session.
100
+
101
+ ### Data phase
102
+
103
+ Every post-handshake frame (data and command) is AEAD-encrypted:
104
+
105
+ ```
106
+ +-----------+----------------+------------------+-----------+
107
+ | flags | length | ciphertext | tag |
108
+ | (1 byte) | (1 or 8 bytes) | (N bytes) | (32 bytes)|
109
+ +-----------+----------------+------------------+-----------+
110
+ ```
111
+
112
+ The `flags` byte and `length` bytes are authenticated as AAD. No counter
113
+ is sent on the wire; both peers derive per-message nonces from a
114
+ synchronized monotonic counter and a session-unique nonce prefix.
115
+
116
+ Unlike CurveZMQ, SUBSCRIBE/CANCEL/JOIN/LEAVE/PING/PONG/ERROR are all
117
+ AEAD-encrypted. CurveZMQ sends these in plaintext, leaking subscription
118
+ topics, group memberships, and heartbeat content.
119
+
120
+ ## Constants
121
+
122
+ | Constant | Value |
123
+ |---|---|
124
+ | Key size | 32 bytes |
125
+ | Nonce size | 24 bytes |
126
+ | Tag size | 32 bytes |
127
+ | Cookie size | 152 bytes (24 nonce + 96 payload + 32 tag) |
128
+ | Mechanism name | `BLAKE3` (6 octets, null-padded to 20) |
129
+ | Protocol ID | `BLAKE3ZMQ-1.0` |
130
+ | HELLO body | 232 bytes |
131
+ | WELCOME body | 224 bytes |
132
+
133
+ ## Per-message overhead
134
+
135
+ | | BLAKE3ZMQ | CurveZMQ |
136
+ |---|---:|---:|
137
+ | Tag size | 32 bytes | 16 bytes |
138
+ | Counter on wire | 0 bytes | 8 bytes |
139
+ | Command wrapper | none | 17 bytes |
140
+ | **Total** | **32 bytes** | **41 bytes** |
141
+
142
+ BLAKE3ZMQ has 9 bytes less overhead per message despite a larger tag.
143
+
75
144
  ## Benchmarks
76
145
 
77
146
  CurveZMQ (RbNaCl/libsodium) vs BLAKE3ZMQ (Rust native ChaCha20-BLAKE3 + C native X25519).
@@ -113,15 +182,6 @@ Run benchmarks yourself:
113
182
  OMQ_DEV=1 bundle exec ruby bench/throughput.rb
114
183
  ```
115
184
 
116
- ## Per-message overhead
117
-
118
- | | BLAKE3ZMQ | CurveZMQ |
119
- |---|---:|---:|
120
- | Tag size | 32 bytes | 16 bytes |
121
- | Counter on wire | 0 bytes | 8 bytes |
122
- | Command wrapper | none | 17 bytes |
123
- | **Total** | **32 bytes** | **41 bytes** |
124
-
125
185
  ## Development
126
186
 
127
187
  ```
@@ -13,7 +13,7 @@ module OMQ
13
13
  CryptoError = ChaCha20Blake3::DecryptionError
14
14
  TAG_SIZE = ChaCha20Blake3::TAG_SIZE
15
15
  Cipher = ChaCha20Blake3::Cipher
16
- Stream = ChaCha20Blake3::Stream
16
+ Session = ChaCha20Blake3::Session
17
17
 
18
18
 
19
19
  # X25519 public key wrapper.
@@ -19,7 +19,7 @@ module Protocol
19
19
  # backend::Cipher.new(key)
20
20
  # #encrypt(nonce, plaintext, aad:) -> ciphertext+tag
21
21
  # #decrypt(nonce, ciphertext+tag, aad:) -> plaintext
22
- # backend::Stream.new(key, nonce)
22
+ # backend::Session.new(enc_key, auth_key, nonce)
23
23
  # #encrypt(plaintext, aad:) -> ciphertext+tag
24
24
  # #decrypt(ciphertext+tag, aad:) -> plaintext
25
25
  # backend::Hash.digest(input) -> 32 bytes
@@ -100,8 +100,8 @@ module Protocol
100
100
  end
101
101
  end
102
102
 
103
- @send_stream = nil
104
- @recv_stream = nil
103
+ @send_session = nil
104
+ @recv_session = nil
105
105
  end
106
106
 
107
107
 
@@ -110,8 +110,8 @@ module Protocol
110
110
  # @param source [Blake3] the original mechanism being duplicated
111
111
  def initialize_dup(source)
112
112
  super
113
- @send_stream = nil
114
- @recv_stream = nil
113
+ @send_session = nil
114
+ @recv_session = nil
115
115
  end
116
116
 
117
117
 
@@ -183,7 +183,7 @@ module Protocol
183
183
  end
184
184
  aad = wire_flags + length_bytes
185
185
 
186
- ct = @send_stream.encrypt(body, aad: aad)
186
+ ct = @send_session.encrypt(body, aad: aad)
187
187
 
188
188
  wire = String.new(encoding: Encoding::BINARY, capacity: aad.bytesize + frame_size)
189
189
  wire << aad << ct
@@ -215,7 +215,7 @@ module Protocol
215
215
  aad = wire_flags + length_bytes
216
216
 
217
217
  begin
218
- pt = @recv_stream.decrypt(frame.body, aad: aad)
218
+ pt = @recv_session.decrypt(frame.body, aad: aad)
219
219
  rescue @crypto::CryptoError
220
220
  raise Error, "decryption failed"
221
221
  end
@@ -616,17 +616,19 @@ module Protocol
616
616
  def derive_session_keys!(h4, dh2, as_client:)
617
617
  ikm = h4 + dh2
618
618
 
619
- c2s_key = kdf("#{PROTOCOL_ID} client->server key", ikm)
620
- c2s_nonce = kdf24("#{PROTOCOL_ID} client->server nonce", ikm)
621
- s2c_key = kdf("#{PROTOCOL_ID} server->client key", ikm)
622
- s2c_nonce = kdf24("#{PROTOCOL_ID} server->client nonce", ikm)
619
+ c2s_enc_key = kdf("#{PROTOCOL_ID} client->server enc key", ikm)
620
+ c2s_auth_key = kdf("#{PROTOCOL_ID} client->server auth key", ikm)
621
+ c2s_nonce = kdf8("#{PROTOCOL_ID} client->server nonce", ikm)
622
+ s2c_enc_key = kdf("#{PROTOCOL_ID} server->client enc key", ikm)
623
+ s2c_auth_key = kdf("#{PROTOCOL_ID} server->client auth key", ikm)
624
+ s2c_nonce = kdf8("#{PROTOCOL_ID} server->client nonce", ikm)
623
625
 
624
626
  if as_client
625
- @send_stream = @crypto::Stream.new(c2s_key, c2s_nonce)
626
- @recv_stream = @crypto::Stream.new(s2c_key, s2c_nonce)
627
+ @send_session = @crypto::Session.new(c2s_enc_key, c2s_auth_key, c2s_nonce)
628
+ @recv_session = @crypto::Session.new(s2c_enc_key, s2c_auth_key, s2c_nonce)
627
629
  else
628
- @send_stream = @crypto::Stream.new(s2c_key, s2c_nonce)
629
- @recv_stream = @crypto::Stream.new(c2s_key, c2s_nonce)
630
+ @send_session = @crypto::Session.new(s2c_enc_key, s2c_auth_key, s2c_nonce)
631
+ @recv_session = @crypto::Session.new(c2s_enc_key, c2s_auth_key, c2s_nonce)
630
632
  end
631
633
  end
632
634
 
@@ -645,6 +647,11 @@ module Protocol
645
647
  end
646
648
 
647
649
 
650
+ def kdf8(context, material)
651
+ @crypto::Hash.derive_key(context, material, size: 8)
652
+ end
653
+
654
+
648
655
  def kdf24(context, material)
649
656
  @crypto::Hash.derive_key(context, material, size: NONCE_SIZE)
650
657
  end
@@ -3,6 +3,6 @@
3
3
  module OMQ
4
4
  # BLAKE3-based encryption mechanism for ZMTP connections.
5
5
  module Blake3ZMQ
6
- VERSION = "0.3.0"
6
+ VERSION = "0.4.0"
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omq-blake3zmq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
@@ -43,14 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: '0.2'
46
+ version: '0.3'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: '0.2'
53
+ version: '0.3'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: x25519
56
56
  requirement: !ruby/object:Gem::Requirement