Skip to main content

pir_types/
lib.rs

1//! Shared types and constants for the PIR subsystem.
2//!
3//! Wire types are serialized over HTTP between `pir-server` and `pir-client`.
4//! Tier-layout constants define the data-format contract shared by all crates
5//! (export, server, client, test).
6//!
7//! The default feature set is lightweight (only `serde`). Enable the `reader`
8//! feature to get tier-data parsers ([`tier0::Tier0Data`], [`tier1::Tier1Row`],
9//! [`tier2::Tier2Row`]) and Fp serialization helpers ([`fp_utils`]).
10
11use serde::{Deserialize, Serialize};
12
13#[cfg(feature = "reader")]
14pub mod fp_utils;
15#[cfg(feature = "reader")]
16pub mod tier0;
17#[cfg(feature = "reader")]
18pub mod tier1;
19#[cfg(feature = "reader")]
20pub mod tier2;
21
22// ── Tier-layout constants ────────────────────────────────────────────────────
23
24/// Depth of the PIR Merkle tree.
25///
26/// With punctured-range leaves (K=2), each leaf covers two gaps, halving the
27/// leaf count compared to K=1. Depth 25 supports 2^25 = 33,554,432 leaf
28/// slots, enough for ~25.5M punctured ranges from ~51M nullifiers.
29pub const PIR_DEPTH: usize = 25;
30
31/// Number of layers in Tier 0 (root at depth 0 down to subtree records at depth 9).
32pub const TIER0_LAYERS: usize = 9;
33
34/// Number of layers in each Tier 1 subtree (depth 9 to depth 15).
35pub const TIER1_LAYERS: usize = 6;
36
37/// Number of layers in each Tier 2 subtree (depth 15 to depth 25).
38pub const TIER2_LAYERS: usize = 10;
39
40/// Number of Tier 1 rows (one per depth-9 subtree).
41pub const TIER1_ROWS: usize = 1 << TIER0_LAYERS; // 512
42
43/// Number of Tier 2 rows (one per depth-15 subtree).
44pub const TIER2_ROWS: usize = 1 << (TIER0_LAYERS + TIER1_LAYERS); // 32,768
45
46/// Number of leaves per Tier 1 subtree (at relative depth 6 = global depth 15).
47pub const TIER1_LEAVES: usize = 1 << TIER1_LAYERS; // 64
48
49/// Number of leaves per Tier 2 subtree (at relative depth 10 = global depth 25).
50pub const TIER2_LEAVES: usize = 1 << TIER2_LAYERS; // 1,024
51
52/// YPIR SimplePIR requires at least 2048 rows (`poly_len`). When TIER1_ROWS
53/// is smaller, the YPIR database is padded with zero rows up to this minimum.
54pub const YPIR_MIN_ROWS: usize = 2048;
55
56/// Number of rows in the Tier 1 YPIR database (padded to YPIR minimum).
57pub const TIER1_YPIR_ROWS: usize = if TIER1_ROWS >= YPIR_MIN_ROWS { TIER1_ROWS } else { YPIR_MIN_ROWS }; // 2,048
58
59/// Byte size of each Tier 2 leaf record: 3 field elements for punctured range
60/// `[nf_lo, nf_mid, nf_hi]`.
61pub const TIER2_LEAF_BYTES: usize = 96;
62
63/// Byte size of one Tier 1 row: 64 × 64 (leaf records only).
64pub const TIER1_ROW_BYTES: usize = TIER1_LEAVES * 64; // 4,096
65
66/// Byte size of one Tier 2 row: 1,024 × 96 (leaf records only).
67pub const TIER2_ROW_BYTES: usize = TIER2_LEAVES * TIER2_LEAF_BYTES; // 98,304
68
69/// Tier 1 item size in bits (for YPIR parameter setup).
70pub const TIER1_ITEM_BITS: usize = TIER1_ROW_BYTES * 8;
71
72/// Tier 2 item size in bits (for YPIR parameter setup).
73pub const TIER2_ITEM_BITS: usize = TIER2_ROW_BYTES * 8;
74
75// ── Metadata ─────────────────────────────────────────────────────────────────
76
77/// Metadata written to `pir_root.json` alongside the tier files.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct PirMetadata {
80    /// Hex-encoded depth-25 Merkle root (PIR tree root for K=2).
81    pub root25: String,
82    /// Hex-encoded depth-29 Merkle root (circuit-compatible).
83    pub root29: String,
84    /// Number of populated leaf ranges in the tree.
85    pub num_ranges: usize,
86    /// PIR tree depth.
87    pub pir_depth: usize,
88    /// Tier 0 size in bytes.
89    pub tier0_bytes: usize,
90    /// Number of Tier 1 rows.
91    pub tier1_rows: usize,
92    /// Tier 1 row size in bytes.
93    pub tier1_row_bytes: usize,
94    /// Number of Tier 2 rows.
95    pub tier2_rows: usize,
96    /// Tier 2 row size in bytes.
97    pub tier2_row_bytes: usize,
98    /// Block height the tree was built from (if known).
99    pub height: Option<u64>,
100}
101
102// ── Wire types ───────────────────────────────────────────────────────────────
103
104/// Parameters describing a YPIR database scenario.
105///
106/// Serialized as JSON over HTTP so the client can reconstruct matching
107/// YPIR parameters locally without knowing the tier layout constants.
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct YpirScenario {
110    pub num_items: usize,
111    pub item_size_bits: usize,
112}
113
114/// Root hash and metadata returned by `GET /root`.
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct RootInfo {
117    pub root29: String,
118    pub root25: String,
119    pub num_ranges: usize,
120    pub pir_depth: usize,
121    pub height: Option<u64>,
122}
123
124/// Health check response returned by `GET /health`.
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct HealthInfo {
127    pub status: String,
128    pub tier1_rows: usize,
129    pub tier2_rows: usize,
130    pub tier1_row_bytes: usize,
131    pub tier2_row_bytes: usize,
132}
133
134const U64_BYTES: usize = std::mem::size_of::<u64>();
135
136/// Serialize a YPIR SimplePIR query into the wire format expected by `pir-server`.
137///
138/// Layout: `[8-byte LE pqr_byte_len][pqr as LE u64s][pub_params as LE u64s]`
139pub fn serialize_ypir_query(pqr: &[u64], pub_params: &[u64]) -> Vec<u8> {
140    let pqr_byte_len = pqr.len() * U64_BYTES;
141    let mut payload = Vec::with_capacity(U64_BYTES + (pqr.len() + pub_params.len()) * U64_BYTES);
142    payload.extend_from_slice(&(pqr_byte_len as u64).to_le_bytes());
143    for &v in pqr {
144        payload.extend_from_slice(&v.to_le_bytes());
145    }
146    for &v in pub_params {
147        payload.extend_from_slice(&v.to_le_bytes());
148    }
149    payload
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn serialize_ypir_query_empty() {
158        let result = serialize_ypir_query(&[], &[]);
159        assert_eq!(result.len(), U64_BYTES);
160        assert_eq!(u64::from_le_bytes(result[..8].try_into().unwrap()), 0);
161    }
162
163    #[test]
164    fn serialize_ypir_query_round_trip_layout() {
165        let pqr = vec![1u64, 2, 3];
166        let pp = vec![100u64, 200];
167        let payload = serialize_ypir_query(&pqr, &pp);
168
169        let expected_len = U64_BYTES + (pqr.len() + pp.len()) * U64_BYTES;
170        assert_eq!(payload.len(), expected_len);
171
172        let pqr_byte_len = u64::from_le_bytes(payload[..8].try_into().unwrap()) as usize;
173        assert_eq!(pqr_byte_len, pqr.len() * U64_BYTES);
174
175        for (i, &expected) in pqr.iter().enumerate() {
176            let offset = U64_BYTES + i * U64_BYTES;
177            let val = u64::from_le_bytes(payload[offset..offset + U64_BYTES].try_into().unwrap());
178            assert_eq!(val, expected);
179        }
180
181        for (i, &expected) in pp.iter().enumerate() {
182            let offset = U64_BYTES + pqr_byte_len + i * U64_BYTES;
183            let val = u64::from_le_bytes(payload[offset..offset + U64_BYTES].try_into().unwrap());
184            assert_eq!(val, expected);
185        }
186    }
187
188    #[test]
189    fn serialize_ypir_query_length_prefix_correctness() {
190        let pqr = vec![42u64];
191        let pp = vec![99u64];
192        let payload = serialize_ypir_query(&pqr, &pp);
193
194        let pqr_byte_len = u64::from_le_bytes(payload[..8].try_into().unwrap()) as usize;
195        assert_eq!(pqr_byte_len, 8);
196
197        let remaining = payload.len() - U64_BYTES - pqr_byte_len;
198        assert_eq!(remaining, pp.len() * U64_BYTES);
199    }
200}