Create Bucket Reachability credentials and put them in buckets as appropriate

This commit is contained in:
Ian Goldberg 2021-05-01 17:12:03 -04:00
parent 33f3de3b68
commit ba13545b3c
4 changed files with 228 additions and 57 deletions

View File

@ -8,11 +8,17 @@ buckets. Users will either download the whole encrypted bucket list or
use PIR to download a piece of it, so that the bridge authority does not use PIR to download a piece of it, so that the bridge authority does not
learn which bucket the user has access to. */ learn which bucket the user has access to. */
use super::cred;
use super::IssuerPrivKey;
use super::CMZ_B_TABLE;
use aes_gcm::aead; use aes_gcm::aead;
use aes_gcm::aead::{generic_array::GenericArray, Aead, NewAead}; use aes_gcm::aead::{generic_array::GenericArray, Aead, NewAead};
use aes_gcm::Aes128Gcm; use aes_gcm::Aes128Gcm;
use curve25519_dalek::ristretto::CompressedRistretto;
use curve25519_dalek::ristretto::RistrettoBasepointTable;
use curve25519_dalek::scalar::Scalar; use curve25519_dalek::scalar::Scalar;
use rand::RngCore; use rand::RngCore;
use std::collections::HashSet;
use std::convert::TryInto; use std::convert::TryInto;
use subtle::ConstantTimeEq; use subtle::ConstantTimeEq;
@ -22,14 +28,14 @@ pub const BRIDGE_BYTES: usize = 220;
/// The max number of bridges per bucket /// The max number of bridges per bucket
pub const MAX_BRIDGES_PER_BUCKET: usize = 3; pub const MAX_BRIDGES_PER_BUCKET: usize = 3;
/// The size of a plaintext bucket /// The minimum number of bridges in a bucket that must be reachable for
pub const BUCKET_BYTES: usize = BRIDGE_BYTES * MAX_BRIDGES_PER_BUCKET; /// the bucket to get a Bucket Reachability credential that will allow
/// users of that bucket to gain trust levels (once they are already at
/// The size of an encrypted bucket /// level 1)
pub const ENC_BUCKET_BYTES: usize = BUCKET_BYTES + 12 + 16; pub const MIN_BUCKET_REACHABILITY: usize = 2;
/// A bridge information line /// A bridge information line
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
pub struct BridgeLine { pub struct BridgeLine {
/// IPv4 or IPv6 address /// IPv4 or IPv6 address
pub addr: [u8; 16], pub addr: [u8; 16],
@ -40,6 +46,20 @@ pub struct BridgeLine {
pub info: [u8; BRIDGE_BYTES - 18], pub info: [u8; BRIDGE_BYTES - 18],
} }
/// A bucket contains MAX_BRIDGES_PER_BUCKET bridges plus the
/// information needed to construct a Bucket Reachability credential,
/// which is a 4-byte date, and a (P,Q) MAC
type Bucket = (
[BridgeLine; MAX_BRIDGES_PER_BUCKET],
Option<cred::BucketReachability>,
);
/// The size of a plaintext bucket
pub const BUCKET_BYTES: usize = BRIDGE_BYTES * MAX_BRIDGES_PER_BUCKET + 4 + 32 + 32;
/// The size of an encrypted bucket
pub const ENC_BUCKET_BYTES: usize = BUCKET_BYTES + 12 + 16;
impl Default for BridgeLine { impl Default for BridgeLine {
/// An "empty" BridgeLine is represented by all zeros /// An "empty" BridgeLine is represented by all zeros
fn default() -> Self { fn default() -> Self {
@ -68,25 +88,79 @@ impl BridgeLine {
res.info.copy_from_slice(&data[18..]); res.info.copy_from_slice(&data[18..]);
res res
} }
/// Encode a bucket to a byte array /// Encode a bucket to a byte array, including a Bucket Reachability
pub fn bucket_encode(bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET]) -> [u8; BUCKET_BYTES] { /// credential if appropriate
pub fn bucket_encode(
bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET],
reachable: &HashSet<BridgeLine>,
today: u32,
bucket_attr: &Scalar,
reachability_priv: &IssuerPrivKey,
) -> [u8; BUCKET_BYTES] {
let mut res: [u8; BUCKET_BYTES] = [0; BUCKET_BYTES]; let mut res: [u8; BUCKET_BYTES] = [0; BUCKET_BYTES];
let mut pos: usize = 0; let mut pos: usize = 0;
let mut num_reachable: usize = 0;
for bridge in bucket { for bridge in bucket {
res[pos..pos + BRIDGE_BYTES].copy_from_slice(&bridge.encode()); res[pos..pos + BRIDGE_BYTES].copy_from_slice(&bridge.encode());
if reachable.contains(bridge) {
num_reachable += 1;
}
pos += BRIDGE_BYTES; pos += BRIDGE_BYTES;
} }
if num_reachable >= MIN_BUCKET_REACHABILITY {
// Construct a Bucket Reachability credential for this
// bucket and today's date
let today_attr: Scalar = today.into();
let mut rng = rand::thread_rng();
let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
let b = Scalar::random(&mut rng);
let P = &b * Btable;
let Q = &(b
* (reachability_priv.x[0]
+ reachability_priv.x[1] * today_attr
+ reachability_priv.x[2] * bucket_attr))
* Btable;
res[pos..pos + 4].copy_from_slice(&today.to_le_bytes());
res[pos + 4..pos + 36].copy_from_slice(&P.compress().as_bytes()[..]);
res[pos + 36..].copy_from_slice(&Q.compress().as_bytes()[..]);
}
res res
} }
/// Decode a bucket from a byte array /// Decode a bucket from a byte array, yielding the array of
pub fn bucket_decode(data: &[u8; BUCKET_BYTES]) -> [BridgeLine; MAX_BRIDGES_PER_BUCKET] { /// BridgeLine entries and an optional Bucket Reachability
/// credential
fn bucket_decode(data: &[u8; BUCKET_BYTES], bucket_attr: &Scalar) -> Bucket {
let mut pos: usize = 0; let mut pos: usize = 0;
let mut res: [BridgeLine; MAX_BRIDGES_PER_BUCKET] = Default::default(); let mut bridges: [BridgeLine; MAX_BRIDGES_PER_BUCKET] = Default::default();
for bridge in res.iter_mut().take(MAX_BRIDGES_PER_BUCKET) { for bridge in bridges.iter_mut().take(MAX_BRIDGES_PER_BUCKET) {
*bridge = BridgeLine::decode(data[pos..pos + BRIDGE_BYTES].try_into().unwrap()); *bridge = BridgeLine::decode(data[pos..pos + BRIDGE_BYTES].try_into().unwrap());
pos += BRIDGE_BYTES; pos += BRIDGE_BYTES;
} }
res // See if there's a nonzero date in the Bucket Reachability
// Credential
let date = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap());
let (optP, optQ) = if date > 0 {
(
CompressedRistretto::from_slice(&data[pos + 4..pos + 36]).decompress(),
CompressedRistretto::from_slice(&data[pos + 36..]).decompress(),
)
} else {
(None, None)
};
if let (Some(P), Some(Q)) = (optP, optQ) {
let date_attr: Scalar = date.into();
(
bridges,
Some(cred::BucketReachability {
P,
Q,
date: date_attr,
bucket: *bucket_attr,
}),
)
} else {
(bridges, None)
}
} }
/// Create a random BridgeLine for testing /// Create a random BridgeLine for testing
#[cfg(test)] #[cfg(test)]
@ -134,6 +208,12 @@ pub struct BridgeTable {
pub keys: Vec<[u8; 16]>, pub keys: Vec<[u8; 16]>,
pub buckets: Vec<[BridgeLine; MAX_BRIDGES_PER_BUCKET]>, pub buckets: Vec<[BridgeLine; MAX_BRIDGES_PER_BUCKET]>,
pub encbuckets: Vec<[u8; ENC_BUCKET_BYTES]>, pub encbuckets: Vec<[u8; ENC_BUCKET_BYTES]>,
pub reachable: HashSet<BridgeLine>,
/// The date the buckets were last encrypted to make the encbucket.
///
/// The encbucket must be rebuilt each day so that the Bucket
/// Reachability credentials in the buckets can be refreshed.
pub date_last_enc: u32,
} }
// Invariant: the lengths of the keys and buckets vectors are the same. // Invariant: the lengths of the keys and buckets vectors are the same.
@ -153,18 +233,35 @@ impl BridgeTable {
rng.fill_bytes(&mut key); rng.fill_bytes(&mut key);
self.keys.push(key); self.keys.push(key);
self.buckets.push(bucket); self.buckets.push(bucket);
// Mark the new bridges as available
for b in bucket.iter() {
if b.port > 0 {
self.reachable.insert(*b);
}
}
} }
/// Create the vector of encrypted buckets from the keys and buckets /// Create the vector of encrypted buckets from the keys and buckets
/// in the BridgeTable. All of the entries will be (randomly) /// in the BridgeTable. All of the entries will be (randomly)
/// re-encrypted, so it will be hidden whether any individual bucket /// re-encrypted, so it will be hidden whether any individual bucket
/// has changed (except for entirely new buckets, of course). /// has changed (except for entirely new buckets, of course).
pub fn encrypt_table(&mut self) { /// Bucket Reachability credentials are added to the buckets when
/// enough (at least MIN_BUCKET_REACHABILITY) bridges in the bucket
/// are reachable.
pub fn encrypt_table(&mut self, today: u32, reachability_priv: &IssuerPrivKey) {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
self.encbuckets.clear(); self.encbuckets.clear();
for (key, bucket) in self.keys.iter().zip(self.buckets.iter()) { // We want id to be a u32, so we use .zip(0u32..) instead of
// enumerate()
for ((key, bucket), id) in self.keys.iter().zip(self.buckets.iter()).zip(0u32..) {
let mut encbucket: [u8; ENC_BUCKET_BYTES] = [0; ENC_BUCKET_BYTES]; let mut encbucket: [u8; ENC_BUCKET_BYTES] = [0; ENC_BUCKET_BYTES];
let plainbucket: [u8; BUCKET_BYTES] = BridgeLine::bucket_encode(bucket); let plainbucket: [u8; BUCKET_BYTES] = BridgeLine::bucket_encode(
bucket,
&self.reachable,
today,
&to_scalar(id, key),
reachability_priv,
);
// Set the AES key // Set the AES key
let aeskey = GenericArray::from_slice(key); let aeskey = GenericArray::from_slice(key);
// Pick a random nonce // Pick a random nonce
@ -178,13 +275,16 @@ impl BridgeTable {
encbucket[12..].copy_from_slice(ciphertext.as_slice()); encbucket[12..].copy_from_slice(ciphertext.as_slice());
self.encbuckets.push(encbucket); self.encbuckets.push(encbucket);
} }
self.date_last_enc = today;
} }
/// Decrypt an individual encrypted bucket, given its key /// Decrypt an individual encrypted bucket, given its id, key, and
/// the encrypted bucket itself
pub fn decrypt_bucket( pub fn decrypt_bucket(
id: u32,
key: &[u8; 16], key: &[u8; 16],
encbucket: &[u8; ENC_BUCKET_BYTES], encbucket: &[u8; ENC_BUCKET_BYTES],
) -> Result<[BridgeLine; MAX_BRIDGES_PER_BUCKET], aead::Error> { ) -> Result<Bucket, aead::Error> {
// Set the nonce and the key // Set the nonce and the key
let nonce = GenericArray::from_slice(&encbucket[0..12]); let nonce = GenericArray::from_slice(&encbucket[0..12]);
let aeskey = GenericArray::from_slice(key); let aeskey = GenericArray::from_slice(key);
@ -194,17 +294,14 @@ impl BridgeTable {
// Convert the plaintext bytes to an array of BridgeLines // Convert the plaintext bytes to an array of BridgeLines
Ok(BridgeLine::bucket_decode( Ok(BridgeLine::bucket_decode(
plaintext.as_slice().try_into().unwrap(), plaintext.as_slice().try_into().unwrap(),
&to_scalar(id, key),
)) ))
} }
/// Decrypt an individual encrypted bucket, given its id and key /// Decrypt an individual encrypted bucket, given its id and key
pub fn decrypt_bucket_id( pub fn decrypt_bucket_id(&self, id: u32, key: &[u8; 16]) -> Result<Bucket, aead::Error> {
&self,
id: u32,
key: &[u8; 16],
) -> Result<[BridgeLine; MAX_BRIDGES_PER_BUCKET], aead::Error> {
let encbucket = self.encbuckets[id as usize]; let encbucket = self.encbuckets[id as usize];
BridgeTable::decrypt_bucket(key, &encbucket) BridgeTable::decrypt_bucket(id, key, &encbucket)
} }
} }
@ -216,6 +313,8 @@ mod tests {
#[test] #[test]
fn test_bridge_table() -> Result<(), aead::Error> { fn test_bridge_table() -> Result<(), aead::Error> {
// Create private keys for the Bucket Reachability credentials
let reachability_priv = IssuerPrivKey::new(2);
// Create an empty bridge table // Create an empty bridge table
let mut btable: BridgeTable = Default::default(); let mut btable: BridgeTable = Default::default();
// Make 20 buckets with one random bridge each // Make 20 buckets with one random bridge each
@ -233,8 +332,13 @@ mod tests {
]; ];
btable.new_bucket(bucket); btable.new_bucket(bucket);
} }
let today: u32 = time::OffsetDateTime::now_utc()
.date()
.julian_day()
.try_into()
.unwrap();
// Create the encrypted bridge table // Create the encrypted bridge table
btable.encrypt_table(); btable.encrypt_table(today, &reachability_priv);
// Try to decrypt a 1-bridge bucket // Try to decrypt a 1-bridge bucket
let key7 = btable.keys[7]; let key7 = btable.keys[7];
let bucket7 = btable.decrypt_bucket_id(7, &key7)?; let bucket7 = btable.decrypt_bucket_id(7, &key7)?;

View File

@ -7,9 +7,10 @@ zero-knowledge proof of its correctness (as it does at issuing time). */
use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::ristretto::RistrettoPoint;
use curve25519_dalek::scalar::Scalar; use curve25519_dalek::scalar::Scalar;
/// A migration credential. This credential authorizes the holder of /// A migration credential.
/// the Lox credential with the given id to switch from bucket ///
/// from_bucket to bucket to_bucket. /// This credential authorizes the holder of the Lox credential with the
/// given id to switch from bucket from_bucket to bucket to_bucket.
#[derive(Debug)] #[derive(Debug)]
pub struct Migration { pub struct Migration {
pub P: RistrettoPoint, pub P: RistrettoPoint,
@ -19,15 +20,17 @@ pub struct Migration {
pub to_bucket: Scalar, pub to_bucket: Scalar,
} }
/// The main user credential in the Lox system. Its id is jointly /// The main user credential in the Lox system.
/// generated by the user and the BA (bridge authority), but known only ///
/// to the user. The level_since date is the Julian date of when this /// Its id is jointly generated by the user and the BA (bridge
/// user was changed to the current trust level. (P_noopmigration, /// authority), but known only to the user. The level_since date is the
/// Q_noopmigration) are the MAC on the implicit no-op migration /// Julian date of when this user was changed to the current trust
/// credential formed by the attributes (id, bucket, bucket), which /// level. (P_noopmigration, Q_noopmigration) are the MAC on the
/// authorizes the user to switch from its current bucket to the same /// implicit no-op migration credential formed by the attributes (id,
/// bucket (i.e., a no-op). This can be useful for hiding from the BA /// bucket, bucket), which authorizes the user to switch from its
/// whether or not the user is performing a bucket migration. /// current bucket to the same bucket (i.e., a no-op). This can be
/// useful for hiding from the BA whether or not the user is performing
/// a bucket migration.
#[derive(Debug)] #[derive(Debug)]
pub struct Lox { pub struct Lox {
pub P: RistrettoPoint, pub P: RistrettoPoint,
@ -42,18 +45,40 @@ pub struct Lox {
pub Q_noopmigration: RistrettoPoint, pub Q_noopmigration: RistrettoPoint,
} }
// The migration key credential is never actually instantiated. It is /// The migration key credential.
// an implicit credential with the following attributes: ///
// - lox_id: Scalar, /// This credential is never actually instantiated. It is an implicit
// - from_bucket: Scalar /// credential on attributes lox_id and from_bucket. This credential
// Plus the usual (P,Q) MAC. This credential type does have an /// type does have an associated private and public key, however. The
// associated private and public key, however. The idea is that if a /// idea is that if a user proves (in zero knowledge) that their Lox
// user proves (in zero knowledge) that their Lox credential entitles /// credential entitles them to migrate from one bucket to another, the
// them to migrate from one bucket to another, the BA will issue a /// BA will issue a (blinded, so the BA will not know the values of the
// (blinded, so the BA will not know the values of the attributes or of /// attributes or of Q) MAC on this implicit credential. The Q value
// Q) MAC on this implicit credential. The Q value will then be used /// will then be used (actually, a hash of lox_id, from_bucket, and Q)
// (actually, a hash of lox_id, from_bucket, and Q) to encrypt the /// to encrypt the to_bucket, P, and Q fields of a Migration credential.
// to_bucket, P, and Q fields of a Migration credential. That way, /// That way, people entitled to migrate buckets can receive a Migration
// people entitled to migrate buckets can receive a Migration credential /// credential with their new bucket, without the BA learning either
// with their new bucket, without the BA learning either their old or /// their old or new buckets.
// new buckets. #[derive(Debug)]
pub struct MigrationKey {
pub P: RistrettoPoint,
pub Q: RistrettoPoint,
pub lox_id: Scalar,
pub from_bucket: Scalar,
}
/// The Bucket Reachability credential.
///
/// Each day, a credential of this type is put in each bucket that has
/// at least a (configurable) threshold number of bridges that have not
/// been blocked as of the given date. Users can present this
/// credential (in zero knowledge) with today's date to prove that the
/// bridges in their bucket have not been blocked, in order to gain a
/// trust level.
#[derive(Debug)]
pub struct BucketReachability {
pub P: RistrettoPoint,
pub Q: RistrettoPoint,
pub date: Scalar,
pub bucket: Scalar,
}

View File

@ -186,6 +186,10 @@ pub struct BridgeAuth {
migrationkey_priv: IssuerPrivKey, migrationkey_priv: IssuerPrivKey,
/// The public key for migration key credentials /// The public key for migration key credentials
pub migrationkey_pub: IssuerPubKey, pub migrationkey_pub: IssuerPubKey,
/// The private key for bucket reachability credentials
reachability_priv: IssuerPrivKey,
/// The public key for bucket reachability credentials
pub reachability_pub: IssuerPubKey,
/// The public key of the BridgeDb issuing open invitations /// The public key of the BridgeDb issuing open invitations
pub bridgedb_pub: PublicKey, pub bridgedb_pub: PublicKey,
@ -218,6 +222,8 @@ impl BridgeAuth {
let migration_pub = IssuerPubKey::new(&migration_priv); let migration_pub = IssuerPubKey::new(&migration_priv);
let migrationkey_priv = IssuerPrivKey::new(2); let migrationkey_priv = IssuerPrivKey::new(2);
let migrationkey_pub = IssuerPubKey::new(&migrationkey_priv); let migrationkey_pub = IssuerPubKey::new(&migrationkey_priv);
let reachability_priv = IssuerPrivKey::new(2);
let reachability_pub = IssuerPubKey::new(&reachability_priv);
Self { Self {
lox_priv, lox_priv,
lox_pub, lox_pub,
@ -225,6 +231,8 @@ impl BridgeAuth {
migration_pub, migration_pub,
migrationkey_priv, migrationkey_priv,
migrationkey_pub, migrationkey_pub,
reachability_priv,
reachability_pub,
bridgedb_pub, bridgedb_pub,
bridge_table: Default::default(), bridge_table: Default::default(),
migration_table: Default::default(), migration_table: Default::default(),
@ -258,6 +266,20 @@ impl BridgeAuth {
.unwrap() .unwrap()
} }
/// Get a reference to the encrypted bridge table.
///
/// Be sure to call this function when you want the latest version
/// of the table, since it will put fresh Bucket Reachability
/// credentials in the buckets each day.
pub fn enc_bridge_table(&mut self) -> &Vec<[u8; bridge_table::ENC_BUCKET_BYTES]> {
let today = self.today();
if self.bridge_table.date_last_enc != today {
self.bridge_table
.encrypt_table(today, &self.reachability_priv);
}
&self.bridge_table.encbuckets
}
#[cfg(test)] #[cfg(test)]
/// Verify the two MACs on a Lox credential /// Verify the two MACs on a Lox credential
pub fn verify_lox(&self, cred: &cred::Lox) -> bool { pub fn verify_lox(&self, cred: &cred::Lox) -> bool {
@ -297,6 +319,20 @@ impl BridgeAuth {
* cred.P; * cred.P;
return Q == cred.Q; return Q == cred.Q;
} }
#[cfg(test)]
/// Verify the MAC on a Bucket Reachability credential
pub fn verify_reachability(&self, cred: &cred::BucketReachability) -> bool {
if cred.P.is_identity() {
return false;
}
let Q = (self.reachability_priv.x[0]
+ cred.date * self.reachability_priv.x[1]
+ cred.bucket * self.reachability_priv.x[2])
* cred.P;
return Q == cred.Q;
}
} }
/// Try to extract a u64 from a Scalar /// Try to extract a u64 from a Scalar

View File

@ -28,7 +28,7 @@ fn test_open_invite() {
ba.bridge_table.new_bucket(bucket); ba.bridge_table.new_bucket(bucket);
} }
// Create the encrypted bridge table // Create the encrypted bridge table
ba.bridge_table.encrypt_table(); ba.enc_bridge_table();
// Issue an open invitation // Issue an open invitation
let inv = bdb.invite(); let inv = bdb.invite();
@ -40,7 +40,9 @@ fn test_open_invite() {
// Check that we can use the credential to read a bucket // Check that we can use the credential to read a bucket
let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap(); let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
let bucket = ba.bridge_table.decrypt_bucket_id(id, &key).unwrap(); let encbuckets = ba.enc_bridge_table();
let bucket =
bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets[id as usize]).unwrap();
println!("cred = {:?}", cred); println!("cred = {:?}", cred);
println!("bucket = {:?}", bucket); println!("bucket = {:?}", bucket);
assert!(ba.verify_lox(&cred)); assert!(ba.verify_lox(&cred));
@ -74,7 +76,7 @@ fn setup() -> (BridgeDb, BridgeAuth) {
ba.migration_table.table.insert(3 * i + 2, 15 + i); ba.migration_table.table.insert(3 * i + 2, 15 + i);
} }
// Create the encrypted bridge table // Create the encrypted bridge table
ba.bridge_table.encrypt_table(); ba.enc_bridge_table();
(bdb, ba) (bdb, ba)
} }
@ -109,7 +111,9 @@ fn test_trust_promotion() {
// Check that we can use the to_bucket in the Migration credenital // Check that we can use the to_bucket in the Migration credenital
// to read a bucket // to read a bucket
let (id, key) = bridge_table::from_scalar(migcred.to_bucket).unwrap(); let (id, key) = bridge_table::from_scalar(migcred.to_bucket).unwrap();
let bucket = ba.bridge_table.decrypt_bucket_id(id, &key).unwrap(); let encbuckets = ba.enc_bridge_table();
let bucket =
bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets[id as usize]).unwrap();
println!("bucket = {:?}", bucket); println!("bucket = {:?}", bucket);
} }
@ -128,6 +132,8 @@ fn test_level0_migration() {
println!("newloxcred = {:?}", newloxcred); println!("newloxcred = {:?}", newloxcred);
// Check that we can use the credenital to read a bucket // Check that we can use the credenital to read a bucket
let (id, key) = bridge_table::from_scalar(newloxcred.bucket).unwrap(); let (id, key) = bridge_table::from_scalar(newloxcred.bucket).unwrap();
let bucket = ba.bridge_table.decrypt_bucket_id(id, &key).unwrap(); let encbuckets = ba.enc_bridge_table();
let bucket =
bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets[id as usize]).unwrap();
println!("bucket = {:?}", bucket); println!("bucket = {:?}", bucket);
} }