API for adding bridges and marking them as unreachable
This commit is contained in:
parent
cf19bf80ee
commit
0664dd7b45
|
@ -18,7 +18,7 @@ use curve25519_dalek::ristretto::CompressedRistretto;
|
|||
use curve25519_dalek::ristretto::RistrettoBasepointTable;
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
use rand::RngCore;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::convert::TryInto;
|
||||
use subtle::ConstantTimeEq;
|
||||
|
||||
|
@ -92,7 +92,7 @@ impl BridgeLine {
|
|||
/// credential if appropriate
|
||||
pub fn bucket_encode(
|
||||
bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET],
|
||||
reachable: &HashSet<BridgeLine>,
|
||||
reachable: &HashMap<BridgeLine, Vec<(u32, usize)>>,
|
||||
today: u32,
|
||||
bucket_attr: &Scalar,
|
||||
reachability_priv: &IssuerPrivKey,
|
||||
|
@ -102,7 +102,7 @@ impl BridgeLine {
|
|||
let mut num_reachable: usize = 0;
|
||||
for bridge in bucket {
|
||||
res[pos..pos + BRIDGE_BYTES].copy_from_slice(&bridge.encode());
|
||||
if reachable.contains(bridge) {
|
||||
if reachable.contains_key(bridge) {
|
||||
num_reachable += 1;
|
||||
}
|
||||
pos += BRIDGE_BYTES;
|
||||
|
@ -208,7 +208,13 @@ pub struct BridgeTable {
|
|||
pub keys: Vec<[u8; 16]>,
|
||||
pub buckets: Vec<[BridgeLine; MAX_BRIDGES_PER_BUCKET]>,
|
||||
pub encbuckets: Vec<[u8; ENC_BUCKET_BYTES]>,
|
||||
pub reachable: HashSet<BridgeLine>,
|
||||
/// Individual bridges that are reachable
|
||||
pub reachable: HashMap<BridgeLine, Vec<(u32, usize)>>,
|
||||
/// bucket ids of "hot spare" buckets. These buckets are not handed
|
||||
/// to users, nor do they have any Migration credentials pointing to
|
||||
/// them. When a new Migration credential is needed, a bucket is
|
||||
/// removed from this set and used for that purpose.
|
||||
pub spares: HashSet<u32>,
|
||||
/// The date the buckets were last encrypted to make the encbucket.
|
||||
///
|
||||
/// The encbucket must be rebuilt each day so that the Bucket
|
||||
|
@ -225,20 +231,27 @@ impl BridgeTable {
|
|||
self.buckets.len()
|
||||
}
|
||||
|
||||
/// Append a new bucket to the bridge table
|
||||
pub fn new_bucket(&mut self, bucket: [BridgeLine; MAX_BRIDGES_PER_BUCKET]) {
|
||||
/// Append a new bucket to the bridge table, returning its index
|
||||
pub fn new_bucket(&mut self, bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET]) -> u32 {
|
||||
// Pick a random key to encrypt this bucket
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut key: [u8; 16] = [0; 16];
|
||||
rng.fill_bytes(&mut key);
|
||||
self.keys.push(key);
|
||||
self.buckets.push(bucket);
|
||||
self.buckets.push(*bucket);
|
||||
let bucketnum: u32 = (self.buckets.len() - 1).try_into().unwrap();
|
||||
// Mark the new bridges as available
|
||||
for b in bucket.iter() {
|
||||
for (i, b) in bucket.iter().enumerate() {
|
||||
if b.port > 0 {
|
||||
self.reachable.insert(*b);
|
||||
if let Some(v) = self.reachable.get_mut(b) {
|
||||
v.push((bucketnum, i));
|
||||
} else {
|
||||
let v = vec![(bucketnum, i)];
|
||||
self.reachable.insert(*b, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
bucketnum
|
||||
}
|
||||
|
||||
/// Create the vector of encrypted buckets from the keys and buckets
|
||||
|
@ -321,7 +334,7 @@ mod tests {
|
|||
for _ in 0..20 {
|
||||
let bucket: [BridgeLine; 3] =
|
||||
[BridgeLine::random(), Default::default(), Default::default()];
|
||||
btable.new_bucket(bucket);
|
||||
btable.new_bucket(&bucket);
|
||||
}
|
||||
// And 20 more with three random bridges each
|
||||
for _ in 0..20 {
|
||||
|
@ -330,7 +343,7 @@ mod tests {
|
|||
BridgeLine::random(),
|
||||
BridgeLine::random(),
|
||||
];
|
||||
btable.new_bucket(bucket);
|
||||
btable.new_bucket(&bucket);
|
||||
}
|
||||
let today: u32 = time::OffsetDateTime::now_utc()
|
||||
.date()
|
||||
|
|
|
@ -25,7 +25,7 @@ pub mod migration_table;
|
|||
use sha2::Sha512;
|
||||
|
||||
use rand::rngs::OsRng;
|
||||
use rand::RngCore;
|
||||
use rand::Rng;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use curve25519_dalek::constants as dalek_constants;
|
||||
|
@ -38,6 +38,13 @@ use curve25519_dalek::traits::IsIdentity;
|
|||
use ed25519_dalek::{Keypair, PublicKey, Signature, SignatureError, Signer, Verifier};
|
||||
use subtle::ConstantTimeEq;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use bridge_table::{
|
||||
BridgeLine, BridgeTable, ENC_BUCKET_BYTES, MAX_BRIDGES_PER_BUCKET, MIN_BUCKET_REACHABILITY,
|
||||
};
|
||||
use migration_table::{MigrationTable, MigrationType};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
|
@ -104,8 +111,8 @@ pub struct BridgeDb {
|
|||
keypair: Keypair,
|
||||
/// The public key for verifying open invitations
|
||||
pub pubkey: PublicKey,
|
||||
/// The number of open-invitation buckets
|
||||
num_openinv_buckets: u32,
|
||||
/// The set of open-invitation buckets
|
||||
openinv_buckets: HashSet<u32>,
|
||||
}
|
||||
|
||||
/// An open invitation is a [u8; OPENINV_LENGTH] where the first 32
|
||||
|
@ -119,17 +126,27 @@ pub const OPENINV_LENGTH: usize = 32 // the length of the random
|
|||
|
||||
impl BridgeDb {
|
||||
/// Create the BridgeDb.
|
||||
pub fn new(num_openinv_buckets: u32) -> Self {
|
||||
pub fn new() -> Self {
|
||||
let mut csprng = OsRng {};
|
||||
let keypair = Keypair::generate(&mut csprng);
|
||||
let pubkey = keypair.public;
|
||||
Self {
|
||||
keypair,
|
||||
pubkey,
|
||||
num_openinv_buckets,
|
||||
openinv_buckets: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert an open-invitation bucket into the set
|
||||
pub fn insert_openinv(&mut self, bucket: u32) {
|
||||
self.openinv_buckets.insert(bucket);
|
||||
}
|
||||
|
||||
/// Remove an open-invitation bucket from the set
|
||||
pub fn remove_openinv(&mut self, bucket: u32) {
|
||||
self.openinv_buckets.remove(&bucket);
|
||||
}
|
||||
|
||||
/// Produce an open invitation. In this example code, we just
|
||||
/// choose a random open-invitation bucket.
|
||||
pub fn invite(&self) -> [u8; OPENINV_LENGTH] {
|
||||
|
@ -138,9 +155,10 @@ impl BridgeDb {
|
|||
// Choose a random invitation id (a Scalar) and serialize it
|
||||
let id = Scalar::random(&mut rng);
|
||||
res[0..32].copy_from_slice(&id.to_bytes());
|
||||
// Choose a random bucket number (mod num_openinv_buckets) and
|
||||
// serialize it
|
||||
let bucket_num = rng.next_u32() % self.num_openinv_buckets;
|
||||
// Choose a random bucket number (from the set of open
|
||||
// invitation buckets) and serialize it
|
||||
let openinv_vec: Vec<&u32> = self.openinv_buckets.iter().collect();
|
||||
let bucket_num = *openinv_vec[rng.gen_range(0, openinv_vec.len())];
|
||||
res[32..(32 + 4)].copy_from_slice(&bucket_num.to_le_bytes());
|
||||
// Sign the first 36 bytes and serialize it
|
||||
let sig = self.keypair.sign(&res[0..(32 + 4)]);
|
||||
|
@ -171,6 +189,12 @@ impl BridgeDb {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for BridgeDb {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// The bridge authority. This will typically be a singleton object.
|
||||
#[derive(Debug)]
|
||||
pub struct BridgeAuth {
|
||||
|
@ -199,10 +223,11 @@ pub struct BridgeAuth {
|
|||
pub bridgedb_pub: PublicKey,
|
||||
|
||||
/// The bridge table
|
||||
bridge_table: bridge_table::BridgeTable,
|
||||
bridge_table: BridgeTable,
|
||||
|
||||
/// The migration table
|
||||
migration_table: migration_table::MigrationTable,
|
||||
/// The migration tables
|
||||
trustup_migration_table: MigrationTable,
|
||||
blockage_migration_table: MigrationTable,
|
||||
|
||||
/// Duplicate filter for open invitations
|
||||
openinv_filter: dup_filter::DupFilter<Scalar>,
|
||||
|
@ -245,7 +270,8 @@ impl BridgeAuth {
|
|||
invitation_pub,
|
||||
bridgedb_pub,
|
||||
bridge_table: Default::default(),
|
||||
migration_table: Default::default(),
|
||||
trustup_migration_table: MigrationTable::new(MigrationType::TrustUpgrade),
|
||||
blockage_migration_table: MigrationTable::new(MigrationType::Blockage),
|
||||
openinv_filter: Default::default(),
|
||||
id_filter: Default::default(),
|
||||
inv_id_filter: Default::default(),
|
||||
|
@ -254,6 +280,117 @@ impl BridgeAuth {
|
|||
}
|
||||
}
|
||||
|
||||
/// Insert a set of open invitation bridges.
|
||||
///
|
||||
/// Each of the bridges will be given its own open invitation
|
||||
/// bucket, and the BridgeDb will be informed. A single bucket
|
||||
/// containing all of the bridges will also be created, with a trust
|
||||
/// upgrade migration from each of the single-bridge buckets.
|
||||
pub fn add_openinv_bridges(
|
||||
&mut self,
|
||||
bridges: [BridgeLine; MAX_BRIDGES_PER_BUCKET],
|
||||
bdb: &mut BridgeDb,
|
||||
) {
|
||||
let bnum = self.bridge_table.new_bucket(&bridges);
|
||||
let mut single = [BridgeLine::default(); MAX_BRIDGES_PER_BUCKET];
|
||||
for b in bridges.iter() {
|
||||
single[0] = *b;
|
||||
let snum = self.bridge_table.new_bucket(&single);
|
||||
bdb.insert_openinv(snum);
|
||||
println!("Adding {} -> {}", snum, bnum);
|
||||
self.trustup_migration_table.table.insert(snum, bnum);
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a hot spare bucket of bridges
|
||||
pub fn add_spare_bucket(&mut self, bucket: [BridgeLine; MAX_BRIDGES_PER_BUCKET]) {
|
||||
let bnum = self.bridge_table.new_bucket(&bucket);
|
||||
self.bridge_table.spares.insert(bnum);
|
||||
}
|
||||
|
||||
/// Mark a bridge as unreachable
|
||||
///
|
||||
/// This bridge will be removed from each of the buckets that
|
||||
/// contains it. If any of those are open-invitation buckets, the
|
||||
/// trust upgrade migration for that bucket will be removed and the
|
||||
/// BridgeDb will be informed to stop handing out that bridge. If
|
||||
/// any of those are trusted buckets where the number of reachable
|
||||
/// bridges has fallen below the threshold, a blockage migration
|
||||
/// from that bucket to a spare bucket will be added, and the spare
|
||||
/// bucket will be removed from the list of hot spares. In
|
||||
/// addition, if the blocked bucket was the _target_ of a blockage
|
||||
/// migration, change the target to the new (formerly spare) bucket.
|
||||
/// Returns true if sucessful, or false if it needed a hot spare but
|
||||
/// there was none available.
|
||||
pub fn bridge_unreachable(&mut self, bridge: &BridgeLine, bdb: &mut BridgeDb) -> bool {
|
||||
let mut res: bool = true;
|
||||
let positions = self.bridge_table.reachable.get(bridge);
|
||||
if let Some(v) = positions {
|
||||
for (bucketnum, offset) in v.iter() {
|
||||
// Count how many bridges in this bucket are reachable
|
||||
let numreachable = self.bridge_table.buckets[*bucketnum as usize]
|
||||
.iter()
|
||||
.filter(|br| self.bridge_table.reachable.get(br).is_some())
|
||||
.count();
|
||||
|
||||
// Remove the bridge from the bucket
|
||||
assert!(self.bridge_table.buckets[*bucketnum as usize][*offset] == *bridge);
|
||||
self.bridge_table.buckets[*bucketnum as usize][*offset] = BridgeLine::default();
|
||||
|
||||
// Is this bucket an open-invitation bucket?
|
||||
if bdb.openinv_buckets.contains(bucketnum) {
|
||||
bdb.openinv_buckets.remove(bucketnum);
|
||||
self.trustup_migration_table.table.remove(bucketnum);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Does this removal cause the bucket to go below the
|
||||
// threshold?
|
||||
if numreachable != MIN_BUCKET_REACHABILITY {
|
||||
// No
|
||||
continue;
|
||||
}
|
||||
|
||||
// This bucket is now unreachable. Get a spare bucket
|
||||
if self.bridge_table.spares.is_empty() {
|
||||
// Uh, oh. No spares available. Just delete any
|
||||
// migrations leading to this bucket.
|
||||
res = false;
|
||||
self.trustup_migration_table
|
||||
.table
|
||||
.retain(|_, &mut v| v != *bucketnum);
|
||||
self.blockage_migration_table
|
||||
.table
|
||||
.retain(|_, &mut v| v != *bucketnum);
|
||||
} else {
|
||||
// Get the first spare and remove it from the spares
|
||||
// set.
|
||||
let spare = *self.bridge_table.spares.iter().next().unwrap();
|
||||
self.bridge_table.spares.remove(&spare);
|
||||
// Add a blockage migration from this bucket to the spare
|
||||
self.blockage_migration_table
|
||||
.table
|
||||
.insert(*bucketnum, spare);
|
||||
// Remove any trust upgrade migrations to this
|
||||
// bucket
|
||||
self.trustup_migration_table
|
||||
.table
|
||||
.retain(|_, &mut v| v != *bucketnum);
|
||||
// Change any blockage migrations with this bucket
|
||||
// as the destination to the spare
|
||||
for (_, v) in self.blockage_migration_table.table.iter_mut() {
|
||||
if *v == *bucketnum {
|
||||
*v = spare;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.bridge_table.reachable.remove(bridge);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// For testing only: manually advance the day by 1 day
|
||||
pub fn advance_day(&mut self) {
|
||||
|
@ -282,7 +419,7 @@ impl BridgeAuth {
|
|||
/// 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]> {
|
||||
pub fn enc_bridge_table(&mut self) -> &Vec<[u8; ENC_BUCKET_BYTES]> {
|
||||
let today = self.today();
|
||||
if self.bridge_table.date_last_enc != today {
|
||||
self.bridge_table
|
||||
|
|
|
@ -34,10 +34,33 @@ pub const MIGRATION_BYTES: usize = 96;
|
|||
/// The size of an encrypted entry in the returned migration table
|
||||
pub const ENC_MIGRATION_BYTES: usize = MIGRATION_BYTES + 12 + 16;
|
||||
|
||||
/// The type of migration table: TrustUpgrade is for migrations from
|
||||
/// untrusted (level 0) 1-bridge buckets to trusted (level 1) 3-bridge
|
||||
/// buckets. Blockage is for migrations that drop you down two levels
|
||||
/// (level 3 to 1, level 4 to 2) because the bridges in your current
|
||||
/// bucket were blocked.
|
||||
pub enum MigrationType {
|
||||
TrustUpgrade,
|
||||
Blockage,
|
||||
}
|
||||
|
||||
impl From<MigrationType> for Scalar {
|
||||
/// Convert a MigrationType into the Scalar value that represents
|
||||
/// it in the Migration credential
|
||||
fn from(m: MigrationType) -> Self {
|
||||
match m {
|
||||
MigrationType::TrustUpgrade => 0u32,
|
||||
MigrationType::Blockage => 1u32,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// The migration table
|
||||
#[derive(Default, Debug)]
|
||||
pub struct MigrationTable {
|
||||
pub table: HashMap<u32, u32>,
|
||||
pub migration_type: Scalar,
|
||||
}
|
||||
|
||||
/// Create an encrypted Migration credential for returning to the user
|
||||
|
@ -143,6 +166,14 @@ pub fn encrypt_cred_ids(
|
|||
}
|
||||
|
||||
impl MigrationTable {
|
||||
/// Create a MigrationTable of the given MigrationType
|
||||
pub fn new(table_type: MigrationType) -> Self {
|
||||
Self {
|
||||
table: Default::default(),
|
||||
migration_type: table_type.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// For each entry in the MigrationTable, use encrypt_cred_ids to
|
||||
/// produce an entry in an output HashMap (from labels to encrypted
|
||||
/// Migration credentials).
|
||||
|
|
|
@ -517,7 +517,7 @@ impl BridgeAuth {
|
|||
Ok(Response {
|
||||
Pk,
|
||||
EncQk,
|
||||
enc_migration_table: self.migration_table.encrypt_table(
|
||||
enc_migration_table: self.trustup_migration_table.encrypt_table(
|
||||
&req.id,
|
||||
&self.bridge_table,
|
||||
&Pktable,
|
||||
|
|
|
@ -13,30 +13,27 @@ struct TestHarness {
|
|||
impl TestHarness {
|
||||
fn new() -> Self {
|
||||
// Create a BridegDb
|
||||
let bdb = BridgeDb::new(15);
|
||||
let mut bdb = BridgeDb::new();
|
||||
// Create a BridgeAuth
|
||||
let mut ba = BridgeAuth::new(bdb.pubkey);
|
||||
|
||||
// Make 15 buckets with one random bridge each
|
||||
for _ in 0..15 {
|
||||
let bucket: [BridgeLine; 3] =
|
||||
[BridgeLine::random(), Default::default(), Default::default()];
|
||||
ba.bridge_table.new_bucket(bucket);
|
||||
}
|
||||
// Make 5 more buckets, each containing 3 of the previously
|
||||
// created bridges
|
||||
for i in 0u32..5 {
|
||||
let iusize = i as usize;
|
||||
let bucket: [BridgeLine; 3] = [
|
||||
ba.bridge_table.buckets[3 * iusize][0],
|
||||
ba.bridge_table.buckets[3 * iusize + 1][0],
|
||||
ba.bridge_table.buckets[3 * iusize + 2][0],
|
||||
// Make 15 open invitation bridges, in 5 sets of 3
|
||||
for _ in 0..5 {
|
||||
let bucket = [
|
||||
BridgeLine::random(),
|
||||
BridgeLine::random(),
|
||||
BridgeLine::random(),
|
||||
];
|
||||
ba.bridge_table.new_bucket(bucket);
|
||||
// Add the allowed migrations to the migration table
|
||||
ba.migration_table.table.insert(3 * i, 15 + i);
|
||||
ba.migration_table.table.insert(3 * i + 1, 15 + i);
|
||||
ba.migration_table.table.insert(3 * i + 2, 15 + i);
|
||||
ba.add_openinv_bridges(bucket, &mut bdb);
|
||||
}
|
||||
// Add 5 more hot spare buckets
|
||||
for _ in 0..5 {
|
||||
let bucket = [
|
||||
BridgeLine::random(),
|
||||
BridgeLine::random(),
|
||||
BridgeLine::random(),
|
||||
];
|
||||
ba.add_spare_bucket(bucket);
|
||||
}
|
||||
// Create the encrypted bridge table
|
||||
ba.enc_bridge_table();
|
||||
|
@ -303,3 +300,53 @@ fn test_redeem_invite() {
|
|||
assert!(th.ba.verify_lox(&bob_cred));
|
||||
println!("bob_cred = {:?}", bob_cred);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mark_unreachable() {
|
||||
let mut th = TestHarness::new();
|
||||
|
||||
println!("spares = {:?}", th.ba.bridge_table.spares);
|
||||
println!("tmig = {:?}", th.ba.trustup_migration_table.table);
|
||||
println!("bmig = {:?}", th.ba.blockage_migration_table.table);
|
||||
println!("openinv = {:?}\n", th.bdb.openinv_buckets);
|
||||
|
||||
// Mark a bridge in an untrusted bucket as unreachable
|
||||
let b6 = th.ba.bridge_table.buckets[6][0];
|
||||
th.ba.bridge_unreachable(&b6, &mut th.bdb);
|
||||
|
||||
println!("spares = {:?}", th.ba.bridge_table.spares);
|
||||
println!("tmig = {:?}", th.ba.trustup_migration_table.table);
|
||||
println!("bmig = {:?}", th.ba.blockage_migration_table.table);
|
||||
println!("openinv = {:?}\n", th.bdb.openinv_buckets);
|
||||
|
||||
// Mark another bridge grouped to the same trusted bucket as
|
||||
// unreachable
|
||||
let b7 = th.ba.bridge_table.buckets[7][0];
|
||||
th.ba.bridge_unreachable(&b7, &mut th.bdb);
|
||||
|
||||
println!("spares = {:?}", th.ba.bridge_table.spares);
|
||||
println!("tmig = {:?}", th.ba.trustup_migration_table.table);
|
||||
println!("bmig = {:?}", th.ba.blockage_migration_table.table);
|
||||
println!("openinv = {:?}\n", th.bdb.openinv_buckets);
|
||||
|
||||
// That will have introduced a blockage migration. Get the target
|
||||
let target: u32 = *th
|
||||
.ba
|
||||
.blockage_migration_table
|
||||
.table
|
||||
.iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.1;
|
||||
|
||||
// Block two of the bridges in that target bucket
|
||||
let bt1 = th.ba.bridge_table.buckets[target as usize][1];
|
||||
let bt2 = th.ba.bridge_table.buckets[target as usize][2];
|
||||
th.ba.bridge_unreachable(&bt1, &mut th.bdb);
|
||||
th.ba.bridge_unreachable(&bt2, &mut th.bdb);
|
||||
|
||||
println!("spares = {:?}", th.ba.bridge_table.spares);
|
||||
println!("tmig = {:?}", th.ba.trustup_migration_table.table);
|
||||
println!("bmig = {:?}", th.ba.blockage_migration_table.table);
|
||||
println!("openinv = {:?}\n", th.bdb.openinv_buckets);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,10 @@ use curve25519_dalek::scalar::Scalar;
|
|||
|
||||
#[test]
|
||||
fn test_bridgedb() {
|
||||
let bdb = BridgeDb::new(20);
|
||||
let mut bdb = BridgeDb::new();
|
||||
for i in &[1u32, 5, 7, 12, 19, 20, 22] {
|
||||
bdb.insert_openinv(*i);
|
||||
}
|
||||
let inv = bdb.invite();
|
||||
println!("{:?}", inv);
|
||||
let res = BridgeDb::verify(inv, bdb.pubkey);
|
||||
|
|
Loading…
Reference in New Issue