Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to vsss-rs for share generation and reconstruction #491

Merged
merged 8 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 175 additions & 49 deletions src/Cargo.lock

Large diffs are not rendered by default.

206 changes: 176 additions & 30 deletions src/init/Cargo.lock

Large diffs are not rendered by default.

Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
041d1f67e987e6f4c742c1f35c07f1184e950e534e12db50ff468688471aee48def99ebf3fbbbd2e72040627fa6c948adef6caaa8ceb862a5eb655eb6b674bbf21042a5e726d5e85f865599c26949fc672d2660c89824a6d9cecc8a7e8679dd89e56e3f506b35bfaddfb2c0a5f5aad256040160269c0f3cff72737348b97cb44dfe9
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0448c07c3724c9a4fa9b002f12cf326136433625b27f35da8d992a4f4b5688627fbf3705da7042d1bb8c74b719d62f89c889bb45876efcc7faced3f28f5e54969804545f5da84824570e3710dc88c0f1303db91e4b7ff0ee17fe41c66c954d8bcddc4f74910117620565061c7e84a05d13b681e75c90d7ddc0b369a7000cef85367c
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
04bff189bf2177cca7618f09acef5129064d43956d334bedf61cd117c996e5c17f66937dc07f0f18c7d47d2af937e1bd56bd10f71e513dc8d1d218cc39fdeaae3304c849c5e8bb24962b8339cf566ce6853faa754cffa3886919defde706d875d27903808b53677813d27dd1bc985c1d38d21181568adb2ae3c8a7789e9e37e6577f
1 change: 1 addition & 0 deletions src/integration/fixtures/preprod/old_dev.secret.keep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
79d88e2069d49dd24452b39cf17eb07581eb5350aa55e918cede5e4e7f171c70
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
2 changes: 2 additions & 0 deletions src/integration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ pub const LOCAL_HOST: &str = "127.0.0.1";
pub const PCR3: &str = "78fce75db17cd4e0a3fb8dad3ad128ca5e77edbb2b2c7f75329dccd99aa5f6ef4fc1f1a452e315b9e98f9e312e6921e6";
/// QOS dist directory.
pub const QOS_DIST_DIR: &str = "./mock/dist";
/// Mock pcr3 pre-image.
pub const PCR3_PRE_IMAGE_PATH: &str = "./mock/namespaces/pcr3-preimage.txt";

const MSG: &str = "msg";

Expand Down
9 changes: 5 additions & 4 deletions src/integration/tests/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use std::{

use borsh::de::BorshDeserialize;
use integration::{
LOCAL_HOST, PIVOT_OK2_PATH, PIVOT_OK2_SUCCESS_FILE, QOS_DIST_DIR,
LOCAL_HOST, PCR3_PRE_IMAGE_PATH, PIVOT_OK2_PATH, PIVOT_OK2_SUCCESS_FILE,
QOS_DIST_DIR,
};
use qos_core::protocol::{
services::{
Expand Down Expand Up @@ -81,7 +82,7 @@ async fn standard_boot_e2e() {
"--qos-release-dir",
QOS_DIST_DIR,
"--pcr3-preimage-path",
"./mock/namespaces/pcr3-preimage.txt",
PCR3_PRE_IMAGE_PATH,
"--manifest-path",
&cli_manifest_path,
"--pivot-args",
Expand Down Expand Up @@ -157,7 +158,7 @@ async fn standard_boot_e2e() {
"--manifest-approvals-dir",
&*boot_dir,
"--pcr3-preimage-path",
"./mock/namespaces/pcr3-preimage.txt",
PCR3_PRE_IMAGE_PATH,
"--pivot-hash-path",
PIVOT_HASH_PATH,
"--qos-release-dir",
Expand Down Expand Up @@ -361,7 +362,7 @@ async fn standard_boot_e2e() {
"--manifest-envelope-path",
&manifest_envelope_path,
"--pcr3-preimage-path",
"./mock/namespaces/pcr3-preimage.txt",
PCR3_PRE_IMAGE_PATH,
"--manifest-set-dir",
"./mock/keys/manifest-set",
"--alias",
Expand Down
9 changes: 6 additions & 3 deletions src/integration/tests/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{
};

use borsh::de::BorshDeserialize;
use integration::{LOCAL_HOST, QOS_DIST_DIR};
use integration::{LOCAL_HOST, PCR3_PRE_IMAGE_PATH, QOS_DIST_DIR};
use qos_core::protocol::services::genesis::GenesisOutput;
use qos_crypto::{sha_512, shamir::shares_reconstruct};
use qos_nsm::nitro::unsafe_attestation_doc_from_der;
Expand Down Expand Up @@ -153,7 +153,7 @@ async fn genesis_e2e() {
"--qos-release-dir",
QOS_DIST_DIR,
"--pcr3-preimage-path",
"./mock/pcr3-preimage.txt",
PCR3_PRE_IMAGE_PATH,
"--dr-key-path",
DR_KEY_PUBLIC_PATH,
"--unsafe-skip-attestation"
Expand Down Expand Up @@ -199,7 +199,10 @@ async fn genesis_e2e() {
// Try recovering from a random permutation
decrypted_shares.shuffle(&mut thread_rng());
let master_secret: [u8; qos_p256::MASTER_SEED_LEN] =
shares_reconstruct(&decrypted_shares[0..threshold]).try_into().unwrap();
shares_reconstruct(&decrypted_shares[0..threshold])
.unwrap()
.try_into()
.unwrap();
let reconstructed = P256Pair::from_master_seed(&master_secret).unwrap();
assert!(
reconstructed.public_key()
Expand Down
12 changes: 7 additions & 5 deletions src/integration/tests/key.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::{fs, process::Command};

use integration::{LOCAL_HOST, PIVOT_LOOP_PATH, QOS_DIST_DIR};
use integration::{
LOCAL_HOST, PCR3_PRE_IMAGE_PATH, PIVOT_LOOP_PATH, QOS_DIST_DIR,
};
use qos_crypto::sha_256;
use qos_p256::{P256Pair, P256Public};
use qos_test_primitives::{ChildWrapper, PathWrapper};
Expand Down Expand Up @@ -158,7 +160,7 @@ fn generate_manifest_envelope() {
"--restart-policy",
"always",
"--pcr3-preimage-path",
"./mock/namespaces/pcr3-preimage.txt",
PCR3_PRE_IMAGE_PATH,
"--pivot-hash-path",
PIVOT_HASH_PATH,
"--qos-release-dir",
Expand Down Expand Up @@ -196,7 +198,7 @@ fn generate_manifest_envelope() {
"--manifest-approvals-dir",
BOOT_DIR,
"--pcr3-preimage-path",
"./mock/namespaces/pcr3-preimage.txt",
PCR3_PRE_IMAGE_PATH,
"--pivot-hash-path",
PIVOT_HASH_PATH,
"--qos-release-dir",
Expand Down Expand Up @@ -293,7 +295,7 @@ fn boot_old_enclave(old_host_port: u16) -> (ChildWrapper, ChildWrapper) {
"--host-ip",
LOCAL_HOST,
"--pcr3-preimage-path",
"./mock/namespaces/pcr3-preimage.txt",
PCR3_PRE_IMAGE_PATH,
"--unsafe-skip-attestation",
])
.spawn()
Expand Down Expand Up @@ -343,7 +345,7 @@ fn boot_old_enclave(old_host_port: u16) -> (ChildWrapper, ChildWrapper) {
"--manifest-envelope-path",
MANIFEST_ENVELOPE_PATH,
"--pcr3-preimage-path",
"./mock/namespaces/pcr3-preimage.txt",
PCR3_PRE_IMAGE_PATH,
"--manifest-set-dir",
"./mock/keys/manifest-set",
"--alias",
Expand Down
169 changes: 169 additions & 0 deletions src/integration/tests/preprod_sharding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
//! 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 can also 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 I've also chosen 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.keep";

#[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).unwrap();
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", "deploy-test"]
{
// 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());
}
Loading