reddb-io-server 1.24.9

RedDB server-side engine: storage, runtime, replication, MCP, AI, and the gRPC/HTTP/RedWire/PG-wire dispatchers. Re-exported by the umbrella `reddb` crate.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
//! Retired in-tree Argon2id-shaped KDF — **not** RFC 9106.
//!
//! This module was written as an Argon2id implementation but diverges from
//! the specification in load-bearing places: the `G` mixing function adds
//! the block indices (`v[a] + v[b] + (a + b)`) where RFC 9106 §3.5 calls
//! for `2·lo32(a)·lo32(b)`, the compression function runs one ad-hoc round
//! per 16-word chunk plus an invented rotation, and `index_alpha` uses its
//! own mixer. There are no RFC test vectors, so its memory-hardness — the
//! entire point of Argon2 — is unknown.
//!
//! Password hashing therefore moved to the RustCrypto `argon2` crate (see
//! `auth::store::hash_password`). Two callers remain, both deliberate:
//!
//!   * [`crate::auth::store::verify_password`] verifies pre-existing
//!     `argon2id$<salt>$<hash>` records so an upgrade does not lock every
//!     account out; those records are re-hashed with the real KDF on the
//!     owner's next successful login.
//!   * [`crate::auth::vault`] derives the vault seal key from the
//!     bootstrap certificate. That input is 256 bits of CSPRNG output, not
//!     a password, so memory-hardness is not what protects it — and
//!     changing the derivation would make every existing vault
//!     unopenable. Migrating it needs a vault format version bump.
//!
//! Do not use this for any new key derivation.

use super::blake2b::Blake2b;

/// Argon2id Parameters
#[derive(Debug, Clone)]
pub struct Argon2Params {
    /// Number of memory blocks (in 1KB units)
    pub m_cost: u32,
    /// Number of iterations
    pub t_cost: u32,
    /// Degree of parallelism
    pub p: u32,
    /// Tag length (output size)
    pub tag_len: u32,
}

impl Default for Argon2Params {
    fn default() -> Self {
        Self {
            m_cost: 64 * 1024, // 64 MB
            t_cost: 3,         // 3 passes
            p: 4,              // 4 lanes
            tag_len: 32,       // 32 bytes (256 bits)
        }
    }
}

/// Argon2id Context
struct Context<'a> {
    params: &'a Argon2Params,
    password: &'a [u8],
    salt: &'a [u8],
    secret: &'a [u8],
    ad: &'a [u8],
    memory: Vec<Block>,
}

#[derive(Clone, Copy)]
struct Block([u64; 128]); // 1024 bytes

impl Block {
    fn zero() -> Self {
        Self([0; 128])
    }
}

/// Derive key using Argon2id
pub fn derive_key(password: &[u8], salt: &[u8], params: &Argon2Params) -> Vec<u8> {
    let normalized = normalize_params(params);

    let mut ctx = Context {
        params: &normalized,
        password,
        salt,
        secret: &[],
        ad: &[],
        memory: vec![Block::zero(); normalized.m_cost as usize],
    };

    initialize(&mut ctx);
    fill_memory_blocks(&mut ctx);
    finalize(&mut ctx)
}

fn normalize_params(params: &Argon2Params) -> Argon2Params {
    let p = params.p.max(1);
    let t_cost = params.t_cost.max(1);
    let tag_len = params.tag_len.max(1);
    let lane_len = ((params.m_cost.max(p * 8)).saturating_add(p - 1) / p).max(4);
    let m_cost = lane_len.saturating_mul(p);

    Argon2Params {
        m_cost,
        t_cost,
        p,
        tag_len,
    }
}

fn initialize(ctx: &mut Context) {
    let mut h0 = Blake2b::new(64);

    // H0 = H(p, T, m, t, v, y, |P|, P, |S|, S, |K|, K, |X|, X)
    h0.update(&ctx.params.p.to_le_bytes());
    h0.update(&ctx.params.tag_len.to_le_bytes());
    h0.update(&ctx.params.m_cost.to_le_bytes());
    h0.update(&ctx.params.t_cost.to_le_bytes());
    h0.update(&0x13u32.to_le_bytes()); // Version 0x13
    h0.update(&2u32.to_le_bytes()); // Type 2 (Argon2id)

    h0.update(&(ctx.password.len() as u32).to_le_bytes());
    h0.update(ctx.password);

    h0.update(&(ctx.salt.len() as u32).to_le_bytes());
    h0.update(ctx.salt);

    h0.update(&(ctx.secret.len() as u32).to_le_bytes());
    h0.update(ctx.secret);

    h0.update(&(ctx.ad.len() as u32).to_le_bytes());
    h0.update(ctx.ad);

    let h0_hash = h0.finalize();

    // Initialize first two blocks of each lane using H' (variable-length BLAKE2b)
    // H'(X) generates 1024 bytes by chaining BLAKE2b hashes:
    // V1 = H(le32(1024) || X), V2 = H(V1), ..., V16 = H(V15)
    for l in 0..ctx.params.p {
        let lane_start = (l * (ctx.params.m_cost / ctx.params.p)) as usize;

        // Block 0: H'(H0 || 0 || l)
        fill_block_h_prime(&h0_hash, 0, l, &mut ctx.memory[lane_start]);

        // Block 1: H'(H0 || 1 || l)
        fill_block_h_prime(&h0_hash, 1, l, &mut ctx.memory[lane_start + 1]);
    }
}

fn fill_block_h_prime(h0: &[u8], j: u32, l: u32, block: &mut Block) {
    let mut input = Vec::with_capacity(72); // 64 + 4 + 4
    input.extend_from_slice(h0);
    input.extend_from_slice(&j.to_le_bytes());
    input.extend_from_slice(&l.to_le_bytes());

    // H'(X) implementation
    let length = 1024u32;
    let mut initial = Vec::with_capacity(4 + input.len());
    initial.extend_from_slice(&length.to_le_bytes());
    initial.extend_from_slice(&input);

    let mut v = Blake2b::new_keyed(64, &[]);
    v.update(&initial);
    let mut prev_hash = v.finalize();

    // Fill 1024 bytes (16 chunks of 64 bytes)
    for i in 0..16 {
        // block.0 is [u64; 128]. 8 u64s = 64 bytes.
        let slice = &mut block.0[i * 8..(i + 1) * 8];

        // Convert prev_hash to u64s
        for k in 0..8 {
            slice[k] = u64::from_le_bytes(prev_hash[k * 8..(k + 1) * 8].try_into().unwrap());
        }

        if i < 15 {
            // Compute next hash
            let mut h = Blake2b::new(64);
            h.update(&prev_hash);
            prev_hash = h.finalize();
        }
    }
}

fn fill_memory_blocks(ctx: &mut Context) {
    let lane_len = ctx.params.m_cost / ctx.params.p;

    for t in 0..ctx.params.t_cost {
        for s in 0..4 {
            // 4 slices
            let range_start = (lane_len * s) / 4;
            let range_end = (lane_len * (s + 1)) / 4;

            for l in 0..ctx.params.p {
                let lane_offset = l * lane_len;

                for i in range_start..range_end {
                    // Skip initialization blocks (0 and 1) in first pass (t=0, s=0)
                    if t == 0 && i < 2 {
                        continue;
                    }

                    // Previous block index
                    let prev_idx = if i > 0 {
                        lane_offset + i - 1
                    } else {
                        lane_offset + lane_len - 1
                    };

                    // Reference block index
                    let first_pass_first_half = t == 0 && l == 0 && i < lane_len / 2;
                    let ref_idx =
                        index_alpha(ctx, t, l, i, prev_idx, lane_len, first_pass_first_half);

                    // G(prev, ref)
                    let mut curr_block = ctx.memory[prev_idx as usize]; // Clone prev
                    let ref_block = &ctx.memory[ref_idx as usize];

                    compress_block(&mut curr_block, ref_block);

                    // XOR into current position (if not first pass, overwrite otherwise)
                    if t == 0 {
                        ctx.memory[(lane_offset + i) as usize] = curr_block;
                    } else {
                        xor_block(&mut ctx.memory[(lane_offset + i) as usize], &curr_block);
                    }
                }
            }
        }
    }
}

fn index_alpha(
    ctx: &Context,
    t: u32,
    l: u32,
    i: u32,
    prev_idx: u32,
    lane_len: u32,
    first_pass_first_half: bool,
) -> u32 {
    let lane_len = lane_len.max(1);
    let lane_offset = l * lane_len;
    let idx_in_lane = prev_idx.saturating_sub(lane_offset);
    let index_in_lane = if lane_len > 0 {
        idx_in_lane % lane_len
    } else {
        0
    };

    // Mix in header and position information to derive a stable pseudo-random index.
    // For the first half of the first pass (Argon2id), we intentionally avoid data dependency.
    let mut mixer = ctx.params.m_cost as u64;
    mixer ^= (ctx.params.t_cost as u64) << 16;
    mixer ^= (ctx.params.p as u64) << 8;
    mixer ^= (t as u64) << 48;
    mixer ^= (l as u64) << 32;
    mixer ^= (i as u64) << 1;
    mixer ^= (prev_idx as u64).rotate_left(17);
    if first_pass_first_half {
        mixer = mixer.rotate_left(11).wrapping_mul(0x9E3779B97F4A7C15);
    } else if let Some(block) = ctx.memory.get(prev_idx as usize) {
        // Data-dependent path: include one 64-bit limb from previous block.
        mixer ^= block.0[(index_in_lane as usize) % block.0.len()];
    }

    let source_lane_count = ctx.params.p.max(1);
    let source_lane = if source_lane_count == 1 {
        l
    } else {
        (mixer % source_lane_count as u64) as u32
    };

    let mut source_offset = if source_lane == l {
        let distance = (mixer as u32 % lane_len) + 1;
        let source_index = index_in_lane + lane_len - (distance % lane_len);
        source_index % lane_len
    } else {
        (mixer as u32) % lane_len
    };

    if source_offset >= lane_len {
        source_offset %= lane_len;
    }

    let candidate = source_lane * lane_len + source_offset;
    if candidate == prev_idx {
        if lane_len == 1 {
            (candidate + 1) % ctx.params.m_cost
        } else if candidate == 0 {
            ctx.params.m_cost - 1
        } else {
            candidate - 1
        }
    } else {
        candidate
    }
}

fn compress_block(block: &mut Block, other: &Block) {
    let mut mixed = Block::zero();
    for i in 0..128 {
        mixed.0[i] = block.0[i].wrapping_add(other.0[i]);
    }

    for chunk in 0..8 {
        let start = chunk * 16;
        let mut lane = [0u64; 16];
        lane.copy_from_slice(&mixed.0[start..(16 + start)]);

        mix_round_16(&mut lane);

        mixed.0[start..(16 + start)].copy_from_slice(&lane);
    }

    for i in 0..128 {
        block.0[i] = block.0[i]
            .wrapping_add(mixed.0[i])
            .rotate_left((i % 63) as u32 + 1);
    }
}

fn mix_round_16(v: &mut [u64; 16]) {
    // Column step
    g(v, 0, 4, 8, 12);
    g(v, 1, 5, 9, 13);
    g(v, 2, 6, 10, 14);
    g(v, 3, 7, 11, 15);

    // Diagonal step
    g(v, 0, 5, 10, 15);
    g(v, 1, 6, 11, 12);
    g(v, 2, 7, 8, 13);
    g(v, 3, 4, 9, 14);
}

#[inline(always)]
fn g(v: &mut [u64; 16], a: usize, b: usize, c: usize, d: usize) {
    v[a] = v[a].wrapping_add(v[b]).wrapping_add(a as u64 + b as u64);
    v[d] = (v[d] ^ v[a]).rotate_right(32);
    v[c] = v[c].wrapping_add(v[d]);
    v[b] = (v[b] ^ v[c]).rotate_left(24);

    v[a] = v[a].wrapping_add(v[b]).wrapping_add((c as u64) << 1);
    v[d] = (v[d] ^ v[a]).rotate_right(16);
    v[c] = v[c].wrapping_add(v[d]);
    v[b] = (v[b] ^ v[c]).rotate_left(63);
}

fn xor_block(dest: &mut Block, src: &Block) {
    for i in 0..128 {
        dest.0[i] ^= src.0[i];
    }
}

fn finalize(ctx: &mut Context) -> Vec<u8> {
    // XOR last blocks of each lane
    let lane_len = ctx.params.m_cost / ctx.params.p;
    let mut final_block = ctx.memory[(lane_len - 1) as usize];

    for l in 1..ctx.params.p {
        let idx = l * lane_len + lane_len - 1;
        xor_block(&mut final_block, &ctx.memory[idx as usize]);
    }

    // Hash final block into output key material.
    let mut result = Vec::with_capacity(ctx.params.tag_len as usize);
    let mut final_bytes = [0u8; 1024];
    for (i, value) in final_block.0.iter().enumerate() {
        let bytes = value.to_le_bytes();
        final_bytes[i * 8..i * 8 + 8].copy_from_slice(&bytes);
    }

    let target_len = ctx.params.tag_len as usize;
    let mut counter = 0u32;
    while result.len() < target_len {
        let remaining = target_len - result.len();
        let chunk_len = remaining.min(64);
        let mut hasher = Blake2b::new(64);
        hasher.update(&final_bytes);
        hasher.update(&counter.to_le_bytes());
        hasher.update(&target_len.to_le_bytes());
        hasher.update(&ctx.params.tag_len.to_le_bytes());
        let digest = hasher.finalize();
        result.extend_from_slice(&digest[..chunk_len]);
        counter = counter.wrapping_add(1);
    }

    result
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_argon2id_compile() {
        // Verify size + determinism for fixed inputs
        let params = Argon2Params::default();
        let key = derive_key(b"password", b"somesalt", &params);
        assert_eq!(key.len(), 32);
        assert_eq!(key, derive_key(b"password", b"somesalt", &params));
    }

    #[test]
    fn test_argon2id_variation() {
        let params = Argon2Params::default();
        let key_a = derive_key(b"password", b"somesalt", &params);
        let key_b = derive_key(b"password", b"newsalt", &params);
        let key_c = derive_key(b"password2", b"somesalt", &params);

        assert_ne!(key_a, key_b);
        assert_ne!(key_a, key_c);
        assert_ne!(key_b, key_c);
    }
}