diff --git a/src/integration/fixtures/preprod/evm-parser/old_dev.share.keep b/src/integration/fixtures/preprod/evm-parser/old_dev.share.keep new file mode 100644 index 00000000..9ae599fc Binary files /dev/null and b/src/integration/fixtures/preprod/evm-parser/old_dev.share.keep differ diff --git a/src/integration/fixtures/preprod/evm-parser/quorum_key.pub b/src/integration/fixtures/preprod/evm-parser/quorum_key.pub new file mode 100644 index 00000000..cd0087ea --- /dev/null +++ b/src/integration/fixtures/preprod/evm-parser/quorum_key.pub @@ -0,0 +1 @@ +0448c07c3724c9a4fa9b002f12cf326136433625b27f35da8d992a4f4b5688627fbf3705da7042d1bb8c74b719d62f89c889bb45876efcc7faced3f28f5e54969804545f5da84824570e3710dc88c0f1303db91e4b7ff0ee17fe41c66c954d8bcddc4f74910117620565061c7e84a05d13b681e75c90d7ddc0b369a7000cef85367c \ No newline at end of file diff --git a/src/integration/fixtures/preprod/notarizer/old_dev.share.keep b/src/integration/fixtures/preprod/notarizer/old_dev.share.keep new file mode 100644 index 00000000..b59507f8 Binary files /dev/null and b/src/integration/fixtures/preprod/notarizer/old_dev.share.keep differ diff --git a/src/integration/fixtures/preprod/notarizer/quorum_key.pub b/src/integration/fixtures/preprod/notarizer/quorum_key.pub new file mode 100644 index 00000000..baf0589b --- /dev/null +++ b/src/integration/fixtures/preprod/notarizer/quorum_key.pub @@ -0,0 +1 @@ +04bff189bf2177cca7618f09acef5129064d43956d334bedf61cd117c996e5c17f66937dc07f0f18c7d47d2af937e1bd56bd10f71e513dc8d1d218cc39fdeaae3304c849c5e8bb24962b8339cf566ce6853faa754cffa3886919defde706d875d27903808b53677813d27dd1bc985c1d38d21181568adb2ae3c8a7789e9e37e6577f \ No newline at end of file diff --git a/src/integration/fixtures/preprod/signer/old_dev.share.keep b/src/integration/fixtures/preprod/signer/old_dev.share.keep new file mode 100644 index 00000000..082594f5 Binary files /dev/null and b/src/integration/fixtures/preprod/signer/old_dev.share.keep differ diff --git a/src/integration/fixtures/preprod/signer/quorum_key.pub b/src/integration/fixtures/preprod/signer/quorum_key.pub new file mode 100644 index 00000000..b2920781 --- /dev/null +++ b/src/integration/fixtures/preprod/signer/quorum_key.pub @@ -0,0 +1 @@ +048e92f6cdcc0b375505980a298d9b79201db1f08b1f135360d2864af1a67186ec0dbeb570d396a456226b0844be93dbc0180abbf7e2e4c9cfde8d5da4e3f8a49004f3422b8afbe425d6ece77b8d2469954715a2ff273ab7ac89f1ed70e0a9325eaa1698b4351fd1b23734e65c0b6a86b62dd49d70b37c94606aac402cbd84353212 \ No newline at end of file diff --git a/src/integration/fixtures/preprod/tls-fetcher/old_dev.share.keep b/src/integration/fixtures/preprod/tls-fetcher/old_dev.share.keep new file mode 100644 index 00000000..230dbecb Binary files /dev/null and b/src/integration/fixtures/preprod/tls-fetcher/old_dev.share.keep differ diff --git a/src/integration/fixtures/preprod/tls-fetcher/quorum_key.pub b/src/integration/fixtures/preprod/tls-fetcher/quorum_key.pub new file mode 100644 index 00000000..d205a17c --- /dev/null +++ b/src/integration/fixtures/preprod/tls-fetcher/quorum_key.pub @@ -0,0 +1 @@ +04603e5bd2e15714e162b5adad657cc0bd26b8d92a1c8d255b148911a834ac1be5c9a76720aac5ec80a49b6b26c48c4bcecf828f9986e1550c829250566ae14fa40445a0e4b7e2672556492c493b2cc0b9894bee5ca4ee37bae0b717930b0e970cd4922417c326c348a669863c4385dd0676cc8f53c8b6f3692e6c31481ac022735d \ No newline at end of file diff --git a/src/integration/fixtures/preprod/ump/old_dev.share.keep b/src/integration/fixtures/preprod/ump/old_dev.share.keep new file mode 100644 index 00000000..df321014 Binary files /dev/null and b/src/integration/fixtures/preprod/ump/old_dev.share.keep differ diff --git a/src/integration/fixtures/preprod/ump/quorum_key.pub b/src/integration/fixtures/preprod/ump/quorum_key.pub new file mode 100644 index 00000000..2dd01426 --- /dev/null +++ b/src/integration/fixtures/preprod/ump/quorum_key.pub @@ -0,0 +1 @@ +04296d80de8593982b0a70a8cc03f4ae664433df5e091c5555d6f49311b4dba1f5c8073565f6b1f2a17ad274b0e7f50f826431f3d5e47ad1cb5e64ea893ce6409f04044ece64c3cea982c7859398ad7d79462d8e35db1173deac68fe173e9585b8acdd43c24902e4e965f6f45f4ba37ce15394e863d4a20c2094ddeea573c66d3554 \ No newline at end of file diff --git a/src/integration/tests/preprod_sharding.rs b/src/integration/tests/preprod_sharding.rs new file mode 100644 index 00000000..e9b89f00 --- /dev/null +++ b/src/integration/tests/preprod_sharding.rs @@ -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()); +}