Skip to content

Commit

Permalink
Preprod reshard integration test
Browse files Browse the repository at this point in the history
  • Loading branch information
r-n-o committed Nov 13, 2024
1 parent dd9cc8d commit cf8fbaa
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 0 deletions.
Binary file not shown.
1 change: 1 addition & 0 deletions src/integration/fixtures/preprod/evm-parser/quorum_key.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0448c07c3724c9a4fa9b002f12cf326136433625b27f35da8d992a4f4b5688627fbf3705da7042d1bb8c74b719d62f89c889bb45876efcc7faced3f28f5e54969804545f5da84824570e3710dc88c0f1303db91e4b7ff0ee17fe41c66c954d8bcddc4f74910117620565061c7e84a05d13b681e75c90d7ddc0b369a7000cef85367c
Binary file not shown.
1 change: 1 addition & 0 deletions src/integration/fixtures/preprod/notarizer/quorum_key.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
04bff189bf2177cca7618f09acef5129064d43956d334bedf61cd117c996e5c17f66937dc07f0f18c7d47d2af937e1bd56bd10f71e513dc8d1d218cc39fdeaae3304c849c5e8bb24962b8339cf566ce6853faa754cffa3886919defde706d875d27903808b53677813d27dd1bc985c1d38d21181568adb2ae3c8a7789e9e37e6577f
Binary file not shown.
1 change: 1 addition & 0 deletions src/integration/fixtures/preprod/signer/quorum_key.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
048e92f6cdcc0b375505980a298d9b79201db1f08b1f135360d2864af1a67186ec0dbeb570d396a456226b0844be93dbc0180abbf7e2e4c9cfde8d5da4e3f8a49004f3422b8afbe425d6ece77b8d2469954715a2ff273ab7ac89f1ed70e0a9325eaa1698b4351fd1b23734e65c0b6a86b62dd49d70b37c94606aac402cbd84353212
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
04603e5bd2e15714e162b5adad657cc0bd26b8d92a1c8d255b148911a834ac1be5c9a76720aac5ec80a49b6b26c48c4bcecf828f9986e1550c829250566ae14fa40445a0e4b7e2672556492c493b2cc0b9894bee5ca4ee37bae0b717930b0e970cd4922417c326c348a669863c4385dd0676cc8f53c8b6f3692e6c31481ac022735d
Binary file not shown.
1 change: 1 addition & 0 deletions src/integration/fixtures/preprod/ump/quorum_key.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
04296d80de8593982b0a70a8cc03f4ae664433df5e091c5555d6f49311b4dba1f5c8073565f6b1f2a17ad274b0e7f50f826431f3d5e47ad1cb5e64ea893ce6409f04044ece64c3cea982c7859398ad7d79462d8e35db1173deac68fe173e9585b8acdd43c24902e4e965f6f45f4ba37ce15394e863d4a20c2094ddeea573c66d3554
170 changes: 170 additions & 0 deletions src/integration/tests/preprod_sharding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//! Utility script to reshard old dev shares into 2 pieces.
//! This lets us test "true" quorum settings instead of relying on a 1-out-of-1
//! quorum.
use std::{
fs,
path::Path,
process::Command,
time::{SystemTime, UNIX_EPOCH},
};

use qos_crypto::shamir::shares_generate;
use qos_p256::{
derive_secret, encrypt::P256EncryptPair, P256Pair, P256_ENCRYPT_DERIVE_PATH,
};

// Note: the dev secret is NOT checked in QOS. It can be found in our keys repo
// (tkhq/keys:deployment/preprod/evm-parser/manifest-set/dev.secret) This secret
// is not security sensitive since it belongs to our dev/preprod environment but
// I want to avoid duplicating it in case there's drift for whatever reason.
// However I've choosen to commit the old encrypted shares and quorum public
// keys because they're useful anchors for this tests. The quorum public keys
// and old dev shares for each enclaves are committed in
// ./fixtures/preprod/$ENCLAVE_NAME/
const OLD_DEV_SECRET_PATH: &str = "./fixtures/preprod/old_dev.secret";

#[test]
fn preprod_reshard_ceremony() {
// Global setup: our test will write to a new folder in `/tmp`
let unix_timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let tmp_dir =
format!("/tmp/preprod-reshard-{}", unix_timestamp.as_millis());
fs::create_dir_all(&tmp_dir).unwrap();

let tmp_path = |file: &str| -> String { format!("{}/{file}", tmp_dir) };

let dev_users_dir = tmp_path("dev-users");
fs::create_dir_all(dev_users_dir.clone()).unwrap();

let enclaves_dir = tmp_path("enclaves");
fs::create_dir_all(enclaves_dir.clone()).unwrap();

let user_dir = |user: &str| format!("{}/{}", dev_users_dir, user);
let enclave_dir = |enclave: &str| format!("{}/{}", enclaves_dir, enclave);
let get_key_paths =
|user: &str| (format!("{user}.secret"), format!("{user}.pub"));

let user1 = "1";
let (user1_private_path, user1_public_path) = get_key_paths(user1);

let user2 = "2";
let (user2_private_path, user2_public_path) = get_key_paths(user2);

// Generate user directories and keys
for (user, private, public) in [
(&user1, &user1_private_path, &user1_public_path),
(&user2, &user2_private_path, &user2_public_path),
] {
fs::create_dir_all(user_dir(user)).unwrap();

let master_seed_path = format!("{}/{}", user_dir(user), private);
let public_path = format!("{}/{}", user_dir(user), public);
assert!(Command::new("../target/debug/qos_client")
.args([
"generate-file-key",
"--master-seed-path",
&master_seed_path,
"--pub-path",
&public_path,
])
.spawn()
.unwrap()
.wait()
.unwrap()
.success());

// Assert both public and private key paths now exist
assert!(Path::new(&*user_dir(user)).join(public).is_file());
assert!(Path::new(&*user_dir(user)).join(private).is_file());
}

// Load previous dev secret (1/1 setting)
let dev_secret_utf8_bytes = fs::read(OLD_DEV_SECRET_PATH).expect("Cannot read old dev secret. Did you add it to at ./fixtures/preprod/old_dev.secret?");
let dev_secret_hex_bytes =
qos_hex::decode(std::str::from_utf8(&dev_secret_utf8_bytes).unwrap())
.unwrap();
let dev_key = P256Pair::from_master_seed(
&dev_secret_hex_bytes.clone().try_into().unwrap(),
)
.unwrap();

// For each of the enclaves...
for enclave_name in
["ump", "evm-parser", "notarizer", "signer", "tls-fetcher"]
{
// Decrypt the old dev share and assert that the resulting quorum key
// has the right public key. Decrypted dev shares are _basically_ master
// seeds. They're just have a "01" prefix because it's the one and only
// "share" in a 1/1 SSS sharing.
let encrypted_old_dev_share = fs::read(format!(
"./fixtures/preprod/{}/old_dev.share.keep",
enclave_name
))
.unwrap();
let mut decrypted_dev_share =
dev_key.decrypt(&encrypted_old_dev_share).unwrap();
let removed_byte = decrypted_dev_share.remove(0);
assert_eq!(removed_byte, 1);

let pk = P256Pair::from_master_seed(
&decrypted_dev_share.clone().try_into().unwrap(),
)
.unwrap()
.public_key();
let expected_quorum_public_key = fs::read(format!(
"./fixtures/preprod/{}/quorum_key.pub",
enclave_name
))
.unwrap();
assert_eq!(
qos_hex::encode(&pk.to_bytes()),
std::str::from_utf8(&expected_quorum_public_key).unwrap()
);

// Now we have the proper quorum key, we're ready to shard it in two
// pieces, to our two new users.
let new_shares = shares_generate(&decrypted_dev_share, 2, 2).unwrap(); // (threshold, total)
assert_eq!(new_shares.len(), 2);

for (user, share) in
[(&user1, &new_shares[0]), (&user2, &new_shares[1])]
{
// Load the key pair for this user
let user_secret_path =
format!("{}/{}.secret", user_dir(user), user);
let user_key_pair =
P256Pair::from_hex_file(user_secret_path.clone()).unwrap();
// Encrypt the new share to it
let encrypted_share =
user_key_pair.public_key().encrypt(share).unwrap();

// And write the resulting file
fs::create_dir_all(enclave_dir(enclave_name)).unwrap();
let encrypted_share_path =
format!("{}/{}.share", enclave_dir(enclave_name), user);
fs::write(encrypted_share_path.clone(), encrypted_share).unwrap();

// Just to make sure: can the user decrypt the share we just created
// with their secret?
assert_can_decrypt(user_secret_path, encrypted_share_path);
}
}

println!("success, reshard complete. Outputs are in {}", tmp_dir);
}

// Helper function to assert a given user secret (1st arg) can decrypt a share
// (2nd arg)
fn assert_can_decrypt(user_secret_path: String, sharepath: String) {
let share = fs::read(sharepath).unwrap();
let master_seed_hex_bytes = fs::read(user_secret_path).unwrap();
let master_seed_utf8 = std::str::from_utf8(&master_seed_hex_bytes).unwrap();
let master_seed = qos_hex::decode(master_seed_utf8).unwrap();
let encryption_pair_secret = derive_secret(
&master_seed.try_into().unwrap(),
P256_ENCRYPT_DERIVE_PATH,
)
.unwrap();
let pair = P256EncryptPair::from_bytes(&encryption_pair_secret).unwrap();
assert!(pair.decrypt(&share).is_ok());
}

0 comments on commit cf8fbaa

Please sign in to comment.