Skip to main content

lox_library/
migration_table.rs

1/*! The migration table.
2
3This is a table listing pairs of (from_bucket_id, to_bucket_id).  A pair
4in this table indicates that a user with a Lox credential containing
5from_bucket_id (and possibly meeting other conditions as well) is
6entitled to exchange their credential for one with to_bucket_id.  (Note
7that the credentials contain the bucket attributes, which include both
8the id and the bucket decrytpion key, but the table just contains the
9bucket ids.) */
10
11use curve25519_dalek::ristretto::CompressedRistretto;
12#[cfg(feature = "bridgeauth")]
13use curve25519_dalek::ristretto::RistrettoBasepointTable;
14use curve25519_dalek::ristretto::RistrettoPoint;
15use curve25519_dalek::scalar::Scalar;
16
17use sha2::Digest;
18use sha2::Sha256;
19
20use aes_gcm::aead::{generic_array::GenericArray, Aead};
21use aes_gcm::{Aes128Gcm, KeyInit};
22#[cfg(feature = "bridgeauth")]
23use rand::RngCore;
24
25use std::collections::HashMap;
26
27#[cfg(feature = "bridgeauth")]
28use serde::{Deserialize, Serialize};
29
30#[cfg(feature = "bridgeauth")]
31use super::bridge_table;
32use super::cred::Migration;
33#[cfg(feature = "bridgeauth")]
34use super::IssuerPrivKey;
35#[cfg(feature = "bridgeauth")]
36use super::CMZ_B_TABLE;
37
38/// Each (plaintext) entry in the returned migration table is serialized
39/// into this many bytes
40pub const MIGRATION_BYTES: usize = 96;
41
42/// The size of an encrypted entry in the returned migration table
43pub const ENC_MIGRATION_BYTES: usize = MIGRATION_BYTES + 12 + 16;
44
45/// The type of migration table: TrustUpgrade is for migrations from
46/// untrusted (level 0) 1-bridge buckets to trusted (level 1) 3-bridge
47/// buckets.  Blockage is for migrations that drop you down two levels
48/// (level 3 to 1, level 4 to 2) because the bridges in your current
49/// bucket were blocked.
50pub enum MigrationType {
51    TrustUpgrade,
52    Blockage,
53}
54
55impl From<MigrationType> for Scalar {
56    /// Convert a MigrationType into the Scalar value that represents
57    /// it in the Migration credential
58    fn from(m: MigrationType) -> Self {
59        match m {
60            MigrationType::TrustUpgrade => 0u32,
61            MigrationType::Blockage => 1u32,
62        }
63        .into()
64    }
65}
66
67/// The migration table
68#[derive(Default, Debug, Serialize, Deserialize)]
69#[cfg(feature = "bridgeauth")]
70pub struct MigrationTable {
71    pub table: HashMap<u32, u32>,
72    pub migration_type: Scalar,
73}
74
75/// Create an encrypted Migration credential for returning to the user
76/// in the trust promotion protocol.
77///
78/// Given the attributes of a Migration credential, produce a serialized
79/// version (containing only the to_bucket and the MAC, since the
80/// receiver will already know the id and from_bucket), encrypted with
81/// H2(id, from_bucket, Qk), for the Qk portion of the MAC on the
82/// corresponding Migration Key credential (with fixed Pk, given as a
83/// precomputed multiplication table).  Return the label H1(id,
84/// from_attr_i, Qk_i) and the encrypted Migration credential.  H1 and
85/// H2 are the first 16 bytes and the second 16 bytes respectively of
86/// the SHA256 hash of the input.
87#[cfg(feature = "bridgeauth")]
88pub fn encrypt_cred(
89    id: &Scalar,
90    from_bucket: &Scalar,
91    to_bucket: &Scalar,
92    migration_type: &Scalar,
93    Pktable: &RistrettoBasepointTable,
94    migration_priv: &IssuerPrivKey,
95    migrationkey_priv: &IssuerPrivKey,
96) -> ([u8; 16], [u8; ENC_MIGRATION_BYTES]) {
97    let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
98
99    let mut rng = rand::thread_rng();
100
101    // Compute the Migration Key credential MAC Qk
102    let Qk = &(migrationkey_priv.x[0]
103        + migrationkey_priv.x[1] * id
104        + migrationkey_priv.x[2] * from_bucket)
105        * Pktable;
106
107    // Compute a MAC (P, Q) on the Migration credential
108    let b = Scalar::random(&mut rng);
109    let P = &b * Btable;
110    let Q = &(b
111        * (migration_priv.x[0]
112            + migration_priv.x[1] * id
113            + migration_priv.x[2] * from_bucket
114            + migration_priv.x[3] * to_bucket
115            + migration_priv.x[4] * migration_type))
116        * Btable;
117
118    // Serialize (to_bucket, P, Q)
119    let mut credbytes: [u8; MIGRATION_BYTES] = [0; MIGRATION_BYTES];
120    credbytes[0..32].copy_from_slice(to_bucket.as_bytes());
121    credbytes[32..64].copy_from_slice(P.compress().as_bytes());
122    credbytes[64..].copy_from_slice(Q.compress().as_bytes());
123
124    // Pick a random nonce
125    let mut noncebytes: [u8; 12] = [0; 12];
126    rng.fill_bytes(&mut noncebytes);
127    let nonce = GenericArray::from_slice(&noncebytes);
128
129    // Compute the hash of (id, from_bucket, Qk)
130    let mut hasher = Sha256::new();
131    hasher.update(id.as_bytes());
132    hasher.update(from_bucket.as_bytes());
133    hasher.update(Qk.compress().as_bytes());
134    let fullhash = hasher.finalize();
135
136    // Create the encryption key from the 2nd half of the hash
137    let aeskey = GenericArray::from_slice(&fullhash[16..]);
138    // Encrypt
139    let cipher = Aes128Gcm::new(aeskey);
140    let ciphertext: Vec<u8> = cipher.encrypt(nonce, credbytes.as_ref()).unwrap();
141    let mut enccredbytes: [u8; ENC_MIGRATION_BYTES] = [0; ENC_MIGRATION_BYTES];
142    enccredbytes[..12].copy_from_slice(&noncebytes);
143    enccredbytes[12..].copy_from_slice(ciphertext.as_slice());
144
145    // Use the first half of the above hash as the label
146    let mut label: [u8; 16] = [0; 16];
147    label[..].copy_from_slice(&fullhash[..16]);
148
149    (label, enccredbytes)
150}
151
152/// Create an encrypted Migration credential for returning to the user
153/// in the trust promotion protocol, given the ids of the from and to
154/// buckets, and the migration type, and using a BridgeTable to get the
155/// bucket keys.
156///
157/// Otherwise the same as encrypt_cred, above, except it returns an
158/// Option in case the passed ids were invalid.
159#[cfg(feature = "bridgeauth")]
160#[allow(clippy::too_many_arguments)]
161pub fn encrypt_cred_ids(
162    id: &Scalar,
163    from_id: u32,
164    to_id: u32,
165    migration_type: &Scalar,
166    bridgetable: &bridge_table::BridgeTable,
167    Pktable: &RistrettoBasepointTable,
168    migration_priv: &IssuerPrivKey,
169    migrationkey_priv: &IssuerPrivKey,
170) -> Option<([u8; 16], [u8; ENC_MIGRATION_BYTES])> {
171    // Look up the bucket keys and form the attributes (Scalars)
172    let fromkey = bridgetable.keys.get(&from_id)?;
173    let tokey = bridgetable.keys.get(&to_id)?;
174    Some(encrypt_cred(
175        id,
176        &bridge_table::to_scalar(from_id, fromkey),
177        &bridge_table::to_scalar(to_id, tokey),
178        migration_type,
179        Pktable,
180        migration_priv,
181        migrationkey_priv,
182    ))
183}
184
185#[cfg(feature = "bridgeauth")]
186impl MigrationTable {
187    /// Create a MigrationTable of the given MigrationType
188    pub fn new(table_type: MigrationType) -> Self {
189        Self {
190            table: Default::default(),
191            migration_type: table_type.into(),
192        }
193    }
194
195    /// For each entry in the MigrationTable, use encrypt_cred_ids to
196    /// produce an entry in an output HashMap (from labels to encrypted
197    /// Migration credentials).
198    pub fn encrypt_table(
199        &self,
200        id: &Scalar,
201        bridgetable: &bridge_table::BridgeTable,
202        Pktable: &RistrettoBasepointTable,
203        migration_priv: &IssuerPrivKey,
204        migrationkey_priv: &IssuerPrivKey,
205    ) -> HashMap<[u8; 16], [u8; ENC_MIGRATION_BYTES]> {
206        self.table
207            .iter()
208            .filter_map(|(from_id, to_id)| {
209                encrypt_cred_ids(
210                    id,
211                    *from_id,
212                    *to_id,
213                    &self.migration_type,
214                    bridgetable,
215                    Pktable,
216                    migration_priv,
217                    migrationkey_priv,
218                )
219            })
220            .collect()
221    }
222}
223
224/// Decrypt an encrypted Migration credential given Qk, the known
225/// attributes id and from_bucket for the Migration credential as well
226/// as the known migration type, and a HashMap mapping labels to
227/// ciphertexts.
228pub fn decrypt_cred(
229    Qk: &RistrettoPoint,
230    lox_id: &Scalar,
231    from_bucket: &Scalar,
232    migration_type: MigrationType,
233    enc_migration_table: &HashMap<[u8; 16], [u8; ENC_MIGRATION_BYTES]>,
234) -> Option<Migration> {
235    // Compute the hash of (id, from_bucket, Qk)
236    let mut hasher = Sha256::new();
237    hasher.update(lox_id.as_bytes());
238    hasher.update(from_bucket.as_bytes());
239    hasher.update(Qk.compress().as_bytes());
240    let fullhash = hasher.finalize();
241
242    // Use the first half of the above hash as the label
243    let mut label: [u8; 16] = [0; 16];
244    label[..].copy_from_slice(&fullhash[..16]);
245
246    // Look up the label in the HashMap
247    let ciphertext = enc_migration_table.get(&label)?;
248
249    // Create the decryption key from the 2nd half of the hash
250    let aeskey = GenericArray::from_slice(&fullhash[16..]);
251
252    // Decrypt
253    let nonce = GenericArray::from_slice(&ciphertext[..12]);
254    let cipher = Aes128Gcm::new(aeskey);
255    let plaintext: Vec<u8> = match cipher.decrypt(nonce, ciphertext[12..].as_ref()) {
256        Ok(v) => v,
257        Err(_) => return None,
258    };
259    let plaintextbytes = plaintext.as_slice();
260    let mut to_bucket_bytes: [u8; 32] = [0; 32];
261    to_bucket_bytes.copy_from_slice(&plaintextbytes[..32]);
262    let to_bucket = Scalar::from_bytes_mod_order(to_bucket_bytes);
263    let P = CompressedRistretto::from_slice(&plaintextbytes[32..64])
264        .expect("Unable to extract P from bucket")
265        .decompress()?;
266    let Q = CompressedRistretto::from_slice(&plaintextbytes[64..])
267        .expect("Unable to extract Q from bucket")
268        .decompress()?;
269
270    Some(Migration {
271        P,
272        Q,
273        lox_id: *lox_id,
274        from_bucket: *from_bucket,
275        to_bucket,
276        migration_type: migration_type.into(),
277    })
278}