diff --git a/crates/key-server/src/tests/server.rs b/crates/key-server/src/tests/server.rs index 088225a5c..c6533f9e0 100644 --- a/crates/key-server/src/tests/server.rs +++ b/crates/key-server/src/tests/server.rs @@ -20,8 +20,7 @@ use crate::errors::InternalError::Failure; use crate::signed_message::signed_request; use crate::tests::externals::get_key; use crate::tests::test_utils::{ - build_partial_key_servers, create_committee_key_server_onchain, create_test_server, - execute_programmable_transaction, + add_partial_key_server, create_test_server, execute_programmable_transaction, }; use crate::tests::whitelist::{add_user_to_whitelist, create_whitelist, whitelist_create_ptb}; use crate::{app, time, Certificate, DefaultEncoding, FetchKeyRequest}; @@ -53,6 +52,7 @@ use shared_crypto::intent::IntentMessage; use std::str::FromStr; use std::time::Duration; use sui_rpc::client::Client as SuiGrpcClient; +use sui_sdk::rpc_types::ObjectChange; use sui_sdk_types::Address; use sui_types::base_types::{ObjectID, SuiAddress}; use sui_types::crypto::Signature; @@ -376,7 +376,7 @@ async fn test_fetch_key() { #[tokio::test] async fn test_committee_server_hot_reload_and_verify_pop() { - let tc = SealTestCluster::new(0, "seal_testnet").await; + let tc = SealTestCluster::new(0, "seal").await; let (seal_package, _) = tc.publish("seal").await; let (package_id, _) = tc.registry; @@ -398,18 +398,58 @@ async fn test_committee_server_hot_reload_and_verify_pop() { let master_pk = G2Element::zero(); let member_address = tc.test_cluster().get_address_0(); + let member2_address = tc.test_cluster().get_address_1(); - // Create on-chain a committee mode KeyServer with one partial key server (party_id_0, partial_pk_0). - let key_server_id = create_committee_key_server_onchain( - tc.test_cluster(), + // Create on-chain a committee mode KeyServer with 2 partial key servers. + let mut builder = ProgrammableTransactionBuilder::new(); + let partial_key_servers = add_partial_key_server( + &mut builder, package_id, + None, // Create new VecMap member_address, &partial_pk_0, party_id_0, - &master_pk, - 1, // threshold - ) - .await; + ); + let partial_key_servers = add_partial_key_server( + &mut builder, + package_id, + Some(partial_key_servers), + member2_address, + &partial_pk_0, // Use test partial pk + party_id_1, + ); + + // Create committee + let name = builder.pure("test_committee".to_string()).unwrap(); + let threshold_arg = builder.pure(2u16).unwrap(); + let master_pk_bytes = builder.pure(master_pk.to_byte_array().to_vec()).unwrap(); + let key_server = builder.programmable_move_call( + package_id, + sui_types::Identifier::new("key_server").unwrap(), + sui_types::Identifier::new("create_committee_v2").unwrap(), + vec![], + vec![name, threshold_arg, master_pk_bytes, partial_key_servers], + ); + builder.transfer_arg(member_address, key_server); + + let response = execute_programmable_transaction(&tc, member_address, builder.finish()).await; + let key_server_id = response + .object_changes + .unwrap() + .into_iter() + .find_map(|change| { + if let ObjectChange::Created { + object_type, + object_id, + .. + } = change + && object_type.name.as_str() == "KeyServer" + { + return Some(object_id); + } + None + }) + .expect("KeyServer object not found in transaction response"); // Get object version and digest for later update. let key_server_obj = tc @@ -457,15 +497,25 @@ async fn test_committee_server_hot_reload_and_verify_pop() { // Current version is 0. assert_eq!(current_version.load(Ordering::Relaxed), 0); - // Update partial key servers on-chain to new partial key server (party_id_1, partial_pk_1). + // Update partial key servers on-chain with 2 servers. let mut builder = ProgrammableTransactionBuilder::new(); - let partial_key_servers = build_partial_key_servers( + + let partial_key_servers = add_partial_key_server( &mut builder, package_id, + None, // Create new VecMap member_address, &partial_pk_1, party_id_1, ); + let partial_key_servers = add_partial_key_server( + &mut builder, + package_id, + Some(partial_key_servers), + member2_address, + &partial_pk_1, + party_id_0, + ); let key_server_obj = builder .obj(sui_types::transaction::ObjectArg::ImmOrOwnedObject(( @@ -475,7 +525,7 @@ async fn test_committee_server_hot_reload_and_verify_pop() { ))) .unwrap(); - let threshold = builder.pure(1u16).unwrap(); + let threshold = builder.pure(2u16).unwrap(); builder.programmable_move_call( package_id, sui_types::Identifier::new("key_server").unwrap(), diff --git a/crates/key-server/src/tests/test_utils.rs b/crates/key-server/src/tests/test_utils.rs index de32f7955..037963a66 100644 --- a/crates/key-server/src/tests/test_utils.rs +++ b/crates/key-server/src/tests/test_utils.rs @@ -24,7 +24,7 @@ use std::{ time::Duration, }; use sui_rpc::client::Client as SuiGrpcClient; -use sui_sdk::rpc_types::{ObjectChange, SuiTransactionBlockResponse}; +use sui_sdk::rpc_types::SuiTransactionBlockResponse; use sui_sdk::SuiClient; use sui_sdk_types::Address; use sui_types::base_types::{ObjectID, SuiAddress}; @@ -34,7 +34,6 @@ use sui_types::transaction::{ TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE, }; use sui_types::{Identifier, TypeTag}; -use test_cluster::TestCluster; /// Helper function to create a test server with any ServerMode. pub(crate) async fn create_test_server( @@ -163,20 +162,22 @@ pub(crate) async fn execute_programmable_transaction( response } -/// Helper function to create a VecMap of one member (address, partial_pk, url, party_id). -pub(crate) fn build_partial_key_servers( +/// Helper function to add a partial key server to an existing VecMap or create a new one. +/// If vec_map is None, creates a new empty VecMap first. +pub(crate) fn add_partial_key_server( builder: &mut ProgrammableTransactionBuilder, package_id: ObjectID, + vec_map: Option, member_address: SuiAddress, partial_pk: &G2Element, party_id: u16, ) -> Argument { + // Create partial key server let partial_pk_bytes = builder.pure(partial_pk.to_byte_array().to_vec()).unwrap(); let url_arg = builder.pure("testurl.com".to_string()).unwrap(); let name_arg = builder.pure("testserver".to_string()).unwrap(); let party_id_arg = builder.pure(party_id).unwrap(); - - let partial_key_server_arg = builder.programmable_move_call( + let partial_key_server = builder.programmable_move_call( package_id, Identifier::new("key_server").unwrap(), Identifier::new("create_partial_key_server").unwrap(), @@ -192,90 +193,25 @@ pub(crate) fn build_partial_key_servers( type_params: vec![], })); - // Create a vecmap and insert the member. - let vec_map = builder.programmable_move_call( - vec_map_module, - Identifier::new("vec_map").unwrap(), - Identifier::new("empty").unwrap(), - vec![TypeTag::Address, partial_key_server_type.clone()], - vec![], - ); + // Create new VecMap if needed + let vec_map = vec_map.unwrap_or_else(|| { + builder.programmable_move_call( + vec_map_module, + Identifier::new("vec_map").unwrap(), + Identifier::new("empty").unwrap(), + vec![TypeTag::Address, partial_key_server_type.clone()], + vec![], + ) + }); + + // Insert into VecMap let member_addr_arg = builder.pure(member_address).unwrap(); builder.programmable_move_call( vec_map_module, Identifier::new("vec_map").unwrap(), Identifier::new("insert").unwrap(), vec![TypeTag::Address, partial_key_server_type], - vec![vec_map, member_addr_arg, partial_key_server_arg], + vec![vec_map, member_addr_arg, partial_key_server], ); vec_map } - -/// Helper function to create a committee KeyServer on-chain and return its ObjectID. -pub(crate) async fn create_committee_key_server_onchain( - cluster: &TestCluster, - package_id: ObjectID, - member_address: SuiAddress, - partial_pk: &G2Element, - party_id: u16, - master_pk: &G2Element, - threshold: u16, -) -> ObjectID { - let mut builder = ProgrammableTransactionBuilder::new(); - let partial_key_servers = build_partial_key_servers( - &mut builder, - package_id, - member_address, - partial_pk, - party_id, - ); - - let name = builder.pure("test_committee".to_string()).unwrap(); - let threshold_arg = builder.pure(threshold).unwrap(); - let master_pk_bytes = builder.pure(master_pk.to_byte_array().to_vec()).unwrap(); - let key_server = builder.programmable_move_call( - package_id, - Identifier::new("key_server").unwrap(), - Identifier::new("create_committee_v2").unwrap(), - vec![], - vec![name, threshold_arg, master_pk_bytes, partial_key_servers], - ); - builder.transfer_arg(member_address, key_server); - - let pt = builder.finish(); - let test_builder = cluster - .test_transaction_builder_with_sender(member_address) - .await; - let gas_object = test_builder.gas_object(); - let gas_price = cluster.get_reference_gas_price().await; - let gas_budget = gas_price * TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE; - let tx_data = TransactionData::new_programmable( - member_address, - vec![gas_object], - pt, - gas_budget, - gas_price, - ); - - let response = cluster.sign_and_execute_transaction(&tx_data).await; - assert!(response.status_ok().unwrap()); - - // Extract the created KeyServer object ID. - response - .object_changes - .unwrap() - .into_iter() - .find_map(|change| { - if let ObjectChange::Created { - object_type, - object_id, - .. - } = change - && object_type.name.as_str() == "KeyServer" - { - return Some(object_id); - } - None - }) - .expect("KeyServer object not found in transaction response") -} diff --git a/move/committee/Move.toml b/move/committee/Move.toml index 2a7cfc454..6af224b2a 100644 --- a/move/committee/Move.toml +++ b/move/committee/Move.toml @@ -3,4 +3,4 @@ name = "seal_committee" edition = "2024" [dependencies] -seal_testnet = { local = "../seal_testnet" } \ No newline at end of file +seal = { local = "../seal" } \ No newline at end of file diff --git a/move/committee/sources/committee.move b/move/committee/sources/committee.move index c7eda540a..126e809e7 100644 --- a/move/committee/sources/committee.move +++ b/move/committee/sources/committee.move @@ -7,12 +7,7 @@ module seal_committee::seal_committee; -use seal_testnet::key_server::{ - KeyServer, - create_partial_key_server, - create_committee_v2, - PartialKeyServer -}; +use seal::key_server::{KeyServer, create_partial_key_server, create_committee_v2, PartialKeyServer}; use std::string::String; use sui::{ bls12381::{g1_from_bytes, g2_from_bytes}, @@ -254,8 +249,7 @@ public fun propose_for_rotation( ) { committee.check_rotation_consistency(&old_committee); let old_committee_id = object::id(&old_committee); - let key_server = dof::remove(&mut old_committee.id, old_committee_id); - key_server.assert_committee_server_v2(); + let key_server: KeyServer = dof::remove(&mut old_committee.id, old_committee_id); committee.propose_internal(partial_pks, *key_server.pk(), ctx); committee.try_finalize_for_rotation(old_committee, key_server); } diff --git a/move/committee/tests/committee_tests.move b/move/committee/tests/committee_tests.move index 2b6323612..d551fbd3e 100644 --- a/move/committee/tests/committee_tests.move +++ b/move/committee/tests/committee_tests.move @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -#[allow(unused_mut_ref, unused_variable, dead_code)] module seal_committee::seal_committee_tests; +use seal::key_server::KeyServer; use seal_committee::seal_committee::{ Self, Committee, @@ -18,7 +18,6 @@ use seal_committee::seal_committee::{ commit_upgrade, reset_proposal }; -use seal_testnet::key_server::KeyServer; use std::string; use sui::{bls12381::{g1_generator, g2_generator}, package, test_scenario::{Self, Scenario}}; diff --git a/move/patterns/sources/voting.move b/move/patterns/sources/voting.move index 038f80fab..634674df4 100644 --- a/move/patterns/sources/voting.move +++ b/move/patterns/sources/voting.move @@ -125,7 +125,7 @@ public fun cast_vote(vote: &mut Vote, encrypted_vote: vector, ctx: &mut TxCo // Check that the encryptions were created for this vote. assert!(encrypted_vote.id() == vote.id(), EInvalidVote); - assert!(encrypted_vote.package_id() == @patterns, EInvalidVote); + assert!(encrypted_vote.package_id() == @0x0, EInvalidVote); // This aborts if the sender is not a voter. let index = vote.voters.find_index!(|voter| voter == ctx.sender()).destroy_some(); @@ -157,7 +157,7 @@ public fun finalize_vote( // Verify the derived keys let verified_derived_keys: vector = verify_derived_keys( &derived_keys.map_ref!(|k| g1_from_bytes(k)), - @patterns, + @0x0, vote.id(), &key_servers .map_ref!(|ks1| vote.key_servers.find_index!(|ks2| ks1 == ks2).destroy_some()) @@ -191,7 +191,11 @@ public fun finalize_vote( #[test] fun test_vote() { - use seal::key_server::{create_and_transfer_v1, KeyServer, destroy_for_testing as ks_destroy}; + use seal::key_server::{ + create_and_transfer_v2_independent_server, + KeyServer, + destroy_for_testing as ks_destroy, + }; use sui::test_scenario::{Self, next_tx, ctx}; let addr1 = @0xA; @@ -200,7 +204,7 @@ fun test_vote() { // Setup key servers. let pk0 = x"a58bfa576a8efe2e2730bc664b3dbe70257d8e35106e4af7353d007dba092d722314a0aeb6bca5eed735466bbf471aef01e4da8d2efac13112c51d1411f6992b8604656ea2cf6a33ec10ce8468de20e1d7ecbfed8688a281d462f72a41602161"; - create_and_transfer_v1( + create_and_transfer_v2_independent_server( b"mysten0".to_string(), b"https://mysten-labs.com".to_string(), 0, @@ -212,7 +216,7 @@ fun test_vote() { let pk1 = x"a9ce55cfa7009c3116ea29341151f3c40809b816f4ad29baa4f95c1bb23085ef02a46cf1ae5bd570d99b0c6e9faf525306224609300b09e422ae2722a17d2a969777d53db7b52092e4d12014da84bffb1e845c2510e26b3c259ede9e42603cd6"; - create_and_transfer_v1( + create_and_transfer_v2_independent_server( b"mysten1".to_string(), b"https://mysten-labs.com".to_string(), 0, @@ -224,7 +228,7 @@ fun test_vote() { let pk2 = x"93b3220f4f3a46fb33074b590cda666c0ebc75c7157d2e6492c62b4aebc452c29f581361a836d1abcbe1386268a5685103d12dec04aadccaebfa46d4c92e2f2c0381b52d6f2474490d02280a9e9d8c889a3fce2753055e06033f39af86676651"; - create_and_transfer_v1( + create_and_transfer_v2_independent_server( b"mysten2".to_string(), b"https://mysten-labs.com".to_string(), 0, diff --git a/move/seal/sources/key_server.move b/move/seal/sources/key_server.move index d51e08d1e..5f89e2487 100644 --- a/move/seal/sources/key_server.move +++ b/move/seal/sources/key_server.move @@ -1,20 +1,39 @@ // Copyright (c), Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -// Permissionless registration of a key server: -// - Key server should expose an endpoint /service that returns the official object id of its key server (to prevent -// impersonation) and a PoP(key=IBE key, m=[key_server_id | IBE public key]). -// - Key server should expose an endpoint /fetch_key that allows users to request a key from the key server. +// Implementation for Seal key server onchain registration. +// +// Key server is a top level object that contains dynamic field objects for versioned key server. +// +// V1: Supports only independent key servers. A V1 server can upgrade to a V2 independent server. +// V2: Supports both independent and committee key servers. A committee based V2 key server holds a +// map of partial key servers that contains the member's partial public key, party ID and URL. The +// partial public keys and party IDs can be updated while the key server public key is unchanged. +// +// A key server can be registered permissionlessly onchain. The server allows users to request a +// key for a given Seal policy. module seal::key_server; use std::string::String; -use sui::{bls12381::{G2, g2_from_bytes}, dynamic_field as df, group_ops::Element}; +use sui::{ + bls12381::{G2, g2_from_bytes}, + dynamic_field as df, + group_ops::Element, + vec_map::VecMap, + vec_set +}; +const KEY_TYPE_BONEH_FRANKLIN_BLS12381: u8 = 0; const EInvalidKeyType: u64 = 1; const EInvalidVersion: u64 = 2; +const EInvalidServerType: u64 = 3; +const EInvalidThreshold: u64 = 4; +const EInvalidPartyId: u64 = 5; +const ENotMember: u64 = 6; -const KEY_TYPE_BONEH_FRANKLIN_BLS12381: u8 = 0; +const V1: u64 = 1; +const V2: u64 = 2; /// KeyServer should always be guarded as it's a capability /// on its own. It should either be an owned object, wrapped object, @@ -25,6 +44,8 @@ public struct KeyServer has key, store { last_version: u64, } +// ===== V1 Structs ===== + public struct KeyServerV1 has store { name: String, url: String, @@ -32,7 +53,151 @@ public struct KeyServerV1 has store { pk: vector, } -// Helper function to register a key server object and transfer it to the caller. +// ===== V2 Structs ===== + +/// KeyServerV2, supports both single and committee-based key servers. +public struct KeyServerV2 has store { + name: String, + key_type: u8, + pk: vector, + server_type: ServerType, +} + +/// Server types for KeyServerV2. +public enum ServerType has drop, store { + Independent { + url: String, + }, + Committee { + /// Incremented on every rotation of the committee. + version: u32, + threshold: u16, + /// Maps of current members' addresses to their partial key server info. Updated on every + /// rotation of the committee. + partial_key_servers: VecMap, + }, +} + +/// PartialKeyServer struct for a committee member. +public struct PartialKeyServer has copy, drop, store { + /// Unique name of the partial key server. + name: String, + /// Server URL, can be updated by the owning member. + url: String, + /// Partial public key (G2 element). + partial_pk: vector, + /// Party ID in the DKG committee. + party_id: u16, +} + +// ===== V2 Public Functions ===== + +/// Create a key server object with df of KeyServerV2 of committee server type. +public fun create_committee_v2( + name: String, + threshold: u16, + pk: vector, + partial_key_servers: VecMap, + ctx: &mut TxContext, +): KeyServer { + let _ = g2_from_bytes(&pk); + validate_partial_key_servers(threshold, &partial_key_servers); + + // Key server version starts at 2. + let mut key_server = KeyServer { + id: object::new(ctx), + first_version: V2, + last_version: V2, + }; + + // Committee version starts at 0. + let key_server_v2 = KeyServerV2 { + name, + key_type: KEY_TYPE_BONEH_FRANKLIN_BLS12381, + pk, + server_type: ServerType::Committee { version: 0, threshold, partial_key_servers }, + }; + + // Add KeyServerV2 as df. + df::add(&mut key_server.id, V2, key_server_v2); + key_server +} + +/// Upgrade the current key server's to v2 by adding a df to KeyServerV2, still a single owner object. +public fun upgrade_v1_to_independent_v2(ks: &mut KeyServer) { + assert!(ks.first_version == V1, EInvalidVersion); + assert!(ks.last_version == V1, EInvalidVersion); + assert!(!ks.has_v2(), EInvalidVersion); + + let v1 = ks.v1(); + let key_server_v2 = KeyServerV2 { + name: v1.name, + key_type: v1.key_type, + pk: v1.pk, + server_type: ServerType::Independent { url: v1.url }, + }; + + df::add(&mut ks.id, V2, key_server_v2); + ks.last_version = V2; +} + +/// Create a PartialKeyServer with respective fields. +public fun create_partial_key_server( + name: String, + url: String, + partial_pk: vector, + party_id: u16, +): PartialKeyServer { + let _ = g2_from_bytes(&partial_pk); + PartialKeyServer { + name, + url, + partial_pk, + party_id, + } +} + +/// Updates threshold and VecMap of partial key servers and increments version. Can only be called +/// on V2 committee server type KeyServer. +public fun update_partial_key_servers( + s: &mut KeyServer, + threshold: u16, + partial_key_servers: VecMap, +) { + validate_partial_key_servers(threshold, &partial_key_servers); + s.assert_committee_server_v2(); + + let v2: &mut KeyServerV2 = df::borrow_mut(&mut s.id, V2); + match (&mut v2.server_type) { + ServerType::Committee { partial_key_servers: value, threshold: t, version: v } => { + *value = partial_key_servers; + *t = threshold; + *v = *v + 1; + }, + _ => abort EInvalidServerType, + } +} + +/// Updates URL for a partial key server for a given member. Can only be called on V2 committee +/// server type KeyServer. +public fun update_member_url(s: &mut KeyServer, url: String, member: address) { + s.assert_committee_server_v2(); + + let v2: &mut KeyServerV2 = df::borrow_mut(&mut s.id, V2); + match (&mut v2.server_type) { + ServerType::Committee { partial_key_servers, .. } => { + assert!(partial_key_servers.contains(&member), ENotMember); + let partial_key_server = partial_key_servers.get_mut(&member); + partial_key_server.url = url; + }, + _ => abort EInvalidServerType, + } +} + +// ===== V1 Public Functions ===== + +// Entry function to register a key server v1 object and transfer it to the caller. +#[deprecated(note = b"Use `key_server::create_and_transfer_v2_independent_server` instead")] entry fun create_and_transfer_v1( name: String, url: String, @@ -44,43 +209,184 @@ entry fun create_and_transfer_v1( transfer::transfer(key_server, ctx.sender()); } +// Entry function to register a key server v2 object as independent mode and transfer it to the caller. +entry fun create_and_transfer_v2_independent_server( + name: String, + url: String, + key_type: u8, + pk: vector, + ctx: &mut TxContext, +) { + assert!(key_type == KEY_TYPE_BONEH_FRANKLIN_BLS12381, EInvalidKeyType); + let _ = g2_from_bytes(&pk); + + let mut key_server = KeyServer { + id: object::new(ctx), + first_version: 2, + last_version: 2, + }; + + let key_server_v2 = KeyServerV2 { + name, + key_type, + pk, + server_type: ServerType::Independent { url }, + }; + df::add(&mut key_server.id, V2, key_server_v2); + transfer::transfer(key_server, ctx.sender()); +} + +/// Update server URL. Can only be called on V1 or V2 independent server type KeyServer. +public fun update(s: &mut KeyServer, url: String) { + if (s.has_v2()) { + let v2: &mut KeyServerV2 = df::borrow_mut(&mut s.id, V2); + match (&mut v2.server_type) { + ServerType::Independent { url: value } => { + *value = url; + }, + _ => abort EInvalidServerType, + } + } else if (s.has_v1()) { + let v1: &mut KeyServerV1 = df::borrow_mut(&mut s.id, V1); + v1.url = url; + } else { + abort EInvalidVersion + } +} + +/// Get KeyServerV1 from KeyServer. public fun v1(s: &KeyServer): &KeyServerV1 { - assert!(df::exists_(&s.id, 1u64), EInvalidVersion); - df::borrow(&s.id, 1u64) + assert!(s.has_v1(), EInvalidVersion); + df::borrow(&s.id, V1) } +/// Get name of key server. Supports both V1 and V2. public fun name(s: &KeyServer): String { - s.v1().name + if (s.has_v2()) { + s.v2().name + } else if (s.has_v1()) { + s.v1().name + } else { + abort EInvalidVersion + } } +/// Get URL of key server. Supports V1 and V2 independent server type only. public fun url(s: &KeyServer): String { - s.v1().url + if (s.has_v2()) { + let v2 = s.v2(); + match (&v2.server_type) { + ServerType::Independent { url } => *url, + _ => abort EInvalidServerType, + } + } else if (s.has_v1()) { + s.v1().url + } else { + abort EInvalidVersion + } } +/// Get key type. Supports both V1 and V2. public fun key_type(s: &KeyServer): u8 { - s.v1().key_type + if (s.has_v2()) { + s.v2().key_type + } else if (s.has_v1()) { + s.v1().key_type + } else { + abort EInvalidVersion + } } +/// Get public key. Supports both V1 and V2. public fun pk(s: &KeyServer): &vector { - &s.v1().pk + if (s.has_v2()) { + &s.v2().pk + } else if (s.has_v1()) { + &s.v1().pk + } else { + abort EInvalidVersion + } } +/// Get the ID of the KeyServer. Supports both V1 and V2. public fun id(s: &KeyServer): address { s.id.to_address() } +/// Get public key as BLS12-381 element. Supports both V1 and V2. public fun pk_as_bf_bls12381(s: &KeyServer): Element { - let v1 = s.v1(); - assert!(v1.key_type == KEY_TYPE_BONEH_FRANKLIN_BLS12381, EInvalidKeyType); - g2_from_bytes(&v1.pk) + if (s.has_v2()) { + let v2 = s.v2(); + assert!(v2.key_type == KEY_TYPE_BONEH_FRANKLIN_BLS12381, EInvalidKeyType); + g2_from_bytes(&v2.pk) + } else if (s.has_v1()) { + let v1 = s.v1(); + assert!(v1.key_type == KEY_TYPE_BONEH_FRANKLIN_BLS12381, EInvalidKeyType); + g2_from_bytes(&v1.pk) + } else { + abort EInvalidVersion + } } -public fun update(s: &mut KeyServer, url: String) { - assert!(df::exists_(&s.id, 1u64), EInvalidVersion); - let v1: &mut KeyServerV1 = df::borrow_mut(&mut s.id, 1u64); - v1.url = url; +// ===== V2 Internal Functions ===== + +/// Validates threshold and party IDs are unique and in range. +fun validate_partial_key_servers( + threshold: u16, + partial_key_servers: &VecMap, +) { + assert!(threshold > 1, EInvalidThreshold); + + let n = partial_key_servers.length() as u16; + assert!(n >= threshold, EInvalidThreshold); + + let mut party_ids = vec_set::empty(); + partial_key_servers.keys().do_ref!(|key| { + let partial_key_server = partial_key_servers.get(key); + + // Validate party ID is in valid range and unique. + let party_id = partial_key_server.party_id; + assert!(party_id < n, EInvalidPartyId); + assert!(!party_ids.contains(&party_id), EInvalidPartyId); + party_ids.insert(party_id); + }); } +/// Check if KeyServer is v2 and is a committee server type. +fun assert_committee_server_v2(s: &KeyServer) { + assert!(s.first_version <= V2 && s.last_version >= V2, EInvalidVersion); + assert!(df::exists_(&s.id, V2), EInvalidVersion); + + let v2: &KeyServerV2 = df::borrow(&s.id, V2); + assert!( + match (&v2.server_type) { + ServerType::Committee { .. } => true, + _ => false, + }, + EInvalidServerType, + ); +} + +/// Check if KeyServer has v2. +fun has_v2(s: &KeyServer): bool { + df::exists_(&s.id, V2) +} + +/// Get KeyServerV2 of a key server. +fun v2(s: &KeyServer): &KeyServerV2 { + assert!(s.has_v2(), EInvalidVersion); + assert!(s.first_version <= V2 && s.last_version >= V2, EInvalidVersion); + df::borrow(&s.id, V2) +} + +/// Check if KeyServer has v1. +fun has_v1(s: &KeyServer): bool { + df::exists_(&s.id, V1) +} + +// ==== V1 Internal Functions ==== + +/// Internal function to create a KeyServerV1 object. fun create_v1( name: String, url: String, @@ -104,44 +410,61 @@ fun create_v1( key_type, pk, }; - df::add(&mut key_server.id, 1u64, key_server_v1); + df::add(&mut key_server.id, V1, key_server_v1); key_server } +// ==== Test Only Functions ==== + +/// Get the partial key server object corresponding to the member. #[test_only] -public fun destroy_for_testing(v: KeyServer) { - let KeyServer { id, .. } = v; - id.delete(); +public fun partial_key_server_for_member(s: &KeyServer, member: address): PartialKeyServer { + s.assert_committee_server_v2(); + let v2: &KeyServerV2 = df::borrow(&s.id, V2); + match (&v2.server_type) { + ServerType::Committee { partial_key_servers, .. } => { + *partial_key_servers.get(&member) + }, + _ => abort EInvalidServerType, + } } -#[test] -fun test_flow() { - use sui::test_scenario::{Self, next_tx, ctx}; - use sui::bls12381::{g2_generator}; - use std::unit_test::assert_eq; +/// Get URL for PartialKeyServer. +#[test_only] +public fun partial_ks_url(partial: &PartialKeyServer): String { + partial.url +} - let addr1 = @0xA; - let mut scenario = test_scenario::begin(addr1); +/// Get partial PK for PartialKeyServer. +#[test_only] +public fun partial_ks_pk(partial: &PartialKeyServer): vector { + partial.partial_pk +} - let pk = g2_generator(); - let pk_bytes = *pk.bytes(); - create_and_transfer_v1( - b"mysten".to_string(), - b"https::/mysten-labs.com".to_string(), - 0, - pk_bytes, - scenario.ctx(), - ); - scenario.next_tx(addr1); +/// Get party ID for PartialKeyServer. +#[test_only] +public fun partial_ks_party_id(partial: &PartialKeyServer): u16 { + partial.party_id +} - let mut s: KeyServer = scenario.take_from_sender(); - assert_eq!(s.name(), b"mysten".to_string()); - assert_eq!(s.url(), b"https::/mysten-labs.com".to_string()); - assert_eq!(*s.pk(), *pk.bytes()); +/// Get the committee version and threshold for a committee-based KeyServer. +#[test_only] +public fun committee_version_and_threshold(s: &KeyServer): (u32, u16) { + s.assert_committee_server_v2(); + let v2: &KeyServerV2 = df::borrow(&s.id, V2); + match (&v2.server_type) { + ServerType::Committee { version, threshold, .. } => (*version, *threshold), + _ => abort EInvalidServerType, + } +} - s.update(b"https::/mysten-labs2.com".to_string()); - assert_eq!(s.url(), b"https::/mysten-labs2.com".to_string()); +#[test_only] +public fun last_version(s: &KeyServer): u64 { + s.last_version +} - destroy_for_testing(s); - scenario.end(); +#[test_only] +public fun destroy_for_testing(self: KeyServer) { + let KeyServer { id, first_version: _, last_version: _ } = self; + id.delete(); } diff --git a/move/seal/tests/key_server_tests.move b/move/seal/tests/key_server_tests.move new file mode 100644 index 000000000..0bc19b3f0 --- /dev/null +++ b/move/seal/tests/key_server_tests.move @@ -0,0 +1,269 @@ +// Copyright (c), Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module seal::key_server_tests; + +use seal::key_server::{ + Self, + KeyServer, + EInvalidThreshold, + EInvalidPartyId, + ENotMember, + EInvalidServerType, + EInvalidVersion, + pk +}; +use std::unit_test::assert_eq; +use sui::{bls12381::g2_generator, test_scenario::{Self, ctx}, vec_map}; + +// ==== Test Helper Functions ==== + +/// Helper function to create a committee v2 server with 2 members for testing. +public fun create_2_of_2_committee_server_v2( + addr1: address, + addr2: address, + threshold: u16, + ctx: &mut TxContext, +): KeyServer { + let pk = g2_generator(); + let pk_bytes = *pk.bytes(); + + let mut partial_key_servers = vec_map::empty(); + partial_key_servers.insert( + addr1, + key_server::create_partial_key_server( + b"server1".to_string(), + b"https://server1.com".to_string(), + pk_bytes, + 0, + ), + ); + partial_key_servers.insert( + addr2, + key_server::create_partial_key_server( + b"server2".to_string(), + b"https://server2.com".to_string(), + pk_bytes, + 1, + ), + ); + + key_server::create_committee_v2( + b"committee".to_string(), + threshold, + pk_bytes, + partial_key_servers, + ctx, + ) +} + +// ==== Tests ==== + +#[test] +#[allow(deprecated_usage)] +fun independent_server() { + let addr1 = @0xA; + let mut scenario = test_scenario::begin(addr1); + + // Test v1 create. + let pk = g2_generator(); + key_server::create_and_transfer_v1( + b"mysten".to_string(), + b"https:/mysten-labs.com".to_string(), + 0, + *pk.bytes(), + scenario.ctx(), + ); + scenario.next_tx(addr1); + + let mut s: KeyServer = scenario.take_from_sender(); + assert_eq!(s.name(), b"mysten".to_string()); + assert_eq!(s.url(), b"https:/mysten-labs.com".to_string()); + assert_eq!(*s.pk(), *pk.bytes()); + + // Verify url update. + s.update(b"https:/mysten-labs2.com".to_string()); + assert_eq!(s.url(), b"https:/mysten-labs2.com".to_string()); + + // Test v1 upgrade to v2 independent. + s.upgrade_v1_to_independent_v2(); + assert_eq!(s.last_version(), 2); + + s.update(b"https:/mysten-labs3.com".to_string()); + assert_eq!(s.url(), b"https:/mysten-labs3.com".to_string()); + + s.destroy_for_testing(); + + // Test fresh v2 independent create. + let pk = g2_generator(); + key_server::create_and_transfer_v2_independent_server( + b"mysten_v2".to_string(), + b"https://mysten-labs-v2.com".to_string(), + 0, + *pk.bytes(), + scenario.ctx(), + ); + scenario.next_tx(addr1); + + let mut s: KeyServer = scenario.take_from_sender(); + assert_eq!(s.name(), b"mysten_v2".to_string()); + assert_eq!(s.url(), b"https://mysten-labs-v2.com".to_string()); + assert_eq!(*s.pk(), *pk.bytes()); + assert_eq!(s.key_type(), 0); + assert_eq!(s.last_version(), 2); + + // Verify url update. + s.update(b"https://mysten-labs-updated.com".to_string()); + assert_eq!(s.url(), b"https://mysten-labs-updated.com".to_string()); + + s.destroy_for_testing(); + scenario.end(); +} + +#[test] +fun committee_v2_server() { + let addr1 = @0xA; + let addr2 = @0xB; + let mut scenario = test_scenario::begin(addr1); + + let s = create_2_of_2_committee_server_v2(addr1, addr2, 2, scenario.ctx()); + let pk = g2_generator(); + + assert_eq!(s.name(), b"committee".to_string()); + assert_eq!(*s.pk(), *pk.bytes()); + assert_eq!(s.key_type(), 0); + + let (version, threshold) = s.committee_version_and_threshold(); + assert_eq!(version, 0); + assert_eq!(threshold, 2); + + let partial1 = s.partial_key_server_for_member(addr1); + assert_eq!(partial1.partial_ks_url(), b"https://server1.com".to_string()); + assert_eq!(partial1.partial_ks_party_id(), 0); + + let partial2 = s.partial_key_server_for_member(addr2); + assert_eq!(partial2.partial_ks_url(), b"https://server2.com".to_string()); + assert_eq!(partial2.partial_ks_party_id(), 1); + + s.destroy_for_testing(); + scenario.end(); +} + +#[test, expected_failure(abort_code = EInvalidThreshold)] +fun create_committee_v2_invalid_threshold() { + let addr1 = @0xA; + let addr2 = @0xB; + let mut scenario = test_scenario::begin(addr1); + + // Threshold of 1 should fail. + let s = create_2_of_2_committee_server_v2(addr1, addr2, 1, scenario.ctx()); + s.destroy_for_testing(); + scenario.end(); +} + +#[test, expected_failure(abort_code = EInvalidPartyId)] +fun create_committee_v2_duplicate_party_ids() { + let addr1 = @0xA; + let addr2 = @0xB; + let mut scenario = test_scenario::begin(addr1); + + let pk = g2_generator(); + let pk_bytes = *pk.bytes(); + + let mut partial_key_servers = vec_map::empty(); + partial_key_servers.insert( + addr1, + key_server::create_partial_key_server( + b"server1".to_string(), + b"https://server1.com".to_string(), + pk_bytes, + 0, + ), + ); + partial_key_servers.insert( + addr2, + key_server::create_partial_key_server( + b"server2".to_string(), + b"https://server2.com".to_string(), + pk_bytes, + 0, // Duplicate party ID + ), + ); + + let s = key_server::create_committee_v2( + b"committee".to_string(), + 2, + pk_bytes, + partial_key_servers, + scenario.ctx(), + ); + s.destroy_for_testing(); + scenario.end(); +} + +#[test, expected_failure(abort_code = ENotMember)] +fun update_member_url_not_member() { + let addr1 = @0xA; + let addr2 = @0xB; + let addr3 = @0xC; + let mut scenario = test_scenario::begin(addr1); + + let mut s = create_2_of_2_committee_server_v2(addr1, addr2, 2, scenario.ctx()); + + // Try to update URL for non-member should fail. + s.update_member_url(b"https://server3.com".to_string(), addr3); + s.destroy_for_testing(); + scenario.end(); +} + +#[test, expected_failure(abort_code = EInvalidServerType)] +fun update_member_url_on_independent_server() { + let addr1 = @0xA; + let mut scenario = test_scenario::begin(addr1); + + let pk = g2_generator(); + let pk_bytes = *pk.bytes(); + + key_server::create_and_transfer_v2_independent_server( + b"mysten_v2".to_string(), + b"https://mysten-labs-v2.com".to_string(), + 0, + pk_bytes, + scenario.ctx(), + ); + scenario.next_tx(addr1); + + // Try to update member URL on independent server should fail + let mut s: KeyServer = scenario.take_from_sender(); + s.update_member_url(b"https://newurl.com".to_string(), addr1); + s.destroy_for_testing(); + scenario.end(); +} + +#[test, expected_failure(abort_code = EInvalidVersion)] +#[allow(deprecated_usage)] +fun upgrade_v1_to_v2_twice() { + let addr1 = @0xA; + let mut scenario = test_scenario::begin(addr1); + + let pk = g2_generator(); + let pk_bytes = *pk.bytes(); + + key_server::create_and_transfer_v1( + b"mysten".to_string(), + b"https:/mysten-labs.com".to_string(), + 0, + pk_bytes, + scenario.ctx(), + ); + scenario.next_tx(addr1); + + let mut s: KeyServer = scenario.take_from_sender(); + s.upgrade_v1_to_independent_v2(); + + // Try to upgrade again should fail. + s.upgrade_v1_to_independent_v2(); + s.destroy_for_testing(); + scenario.end(); +} diff --git a/move/seal_testnet/Move.toml b/move/seal_testnet/Move.toml deleted file mode 100644 index ae6667aee..000000000 --- a/move/seal_testnet/Move.toml +++ /dev/null @@ -1,5 +0,0 @@ -[package] -name = "seal_testnet" -edition = "2024" -# license = "" # e.g., "MIT", "GPL", "Apache 2.0" -# authors = ["..."] # e.g., ["Joe Smith (joesmith@noemail.com)", "John Snow (johnsnow@noemail.com)"] diff --git a/move/seal_testnet/Published.toml b/move/seal_testnet/Published.toml deleted file mode 100644 index 9dd894fdd..000000000 --- a/move/seal_testnet/Published.toml +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Move -# This file contains metadata about published versions of this package in different environments -# This file SHOULD be committed to source control - -[published.testnet] -chain-id = "4c78adac" -published-at = "0x41fee5f043ef36787add1f5747c3a484fb8c5dad74011667d5d406e6ff2d0b22" -original-id = "0x41fee5f043ef36787add1f5747c3a484fb8c5dad74011667d5d406e6ff2d0b22" -version = 1 -toolchain-version = "1.64.0" -build-config = { flavor = "sui", edition = "2024" } -upgrade-capability = "0x2a90db342de257b5e14a80d3f8c9f170e75781e8ff8f59d560789a92395a5477" \ No newline at end of file diff --git a/move/seal_testnet/sources/key_server.move b/move/seal_testnet/sources/key_server.move deleted file mode 100644 index 82028e9b4..000000000 --- a/move/seal_testnet/sources/key_server.move +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright (c), Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -// Key server is a top level object that maps to versioned key server. -// V1: Supports only independent key servers. A V1 server can upgrade to a V2 independent server. -// V2: Supports both independent and committee-based key servers. A committee based V2 server can -// be created afresh, that holds map of partial key servers that contains the member's partial -// public key, party ID and URL. When a committee rotates, the partial public keys and party IDs -// are updated. -// -// Permissionless registration of a key server: -// - Key server should expose an endpoint /service: For V1 or V2 independent server, it returns the -// official object id of its key server (to prevent impersonation) and a PoP(key=IBE key, -// m=[key_server_id | IBE public key]). For V2 committee based server it returns the key server -// object ID for the committee it belongs to, and a PoP(key=IBE key, m=[key_server_id | party_id | -// partial public key]). -// -// - Key server should expose an endpoint /fetch_key that allows users to request a key from the key -// server. - -module seal_testnet::key_server; - -use std::string::String; -use sui::{bls12381::{G2, g2_from_bytes}, dynamic_field as df, group_ops::Element, vec_map::VecMap}; - -const KeyTypeBonehFranklinBLS12381: u8 = 0; -const EInvalidKeyType: u64 = 1; -const EInvalidVersion: u64 = 2; -const EInvalidServerType: u64 = 3; -const EInvalidThreshold: u64 = 4; - -/// KeyServer should always be guarded as it's a capability -/// on its own. It should either be an owned object, wrapped object, -/// or TTO'd object (where access to it is controlled externally). -public struct KeyServer has key, store { - id: UID, - first_version: u64, - last_version: u64, -} - -// ===== V1 Structs ===== - -public struct KeyServerV1 has store { - name: String, - url: String, - key_type: u8, - pk: vector, -} - -// ===== V2 Structs ===== - -/// KeyServerV2, supports both single and committee-based key servers. -public struct KeyServerV2 has store { - name: String, - key_type: u8, - pk: vector, - server_type: ServerType, -} - -/// Server types for KeyServerV2. -public enum ServerType has drop, store { - Independent { - url: String, - }, - Committee { - /// Incremented on every rotation of the committee. - version: u32, - threshold: u16, - partial_key_servers: VecMap, - }, -} - -/// PartialKeyServer, holds the partial pk, party ID, URL, and name for a committee member. -public struct PartialKeyServer has copy, drop, store { - name: String, // Partial key server name. - url: String, // Partial key server URL. - partial_pk: vector, // Partial public key (G2 element). - party_id: u16, // Party ID in the DKG. -} - -// ===== V2 Functions ===== - -/// Create a committee-owned KeyServer. -public fun create_committee_v2( - name: String, - threshold: u16, - pk: vector, - partial_key_servers: VecMap, - ctx: &mut TxContext, -): KeyServer { - assert!(threshold > 0, EInvalidThreshold); - assert!(partial_key_servers.length() as u16 >= threshold, EInvalidThreshold); - - let _ = g2_from_bytes(&pk); - - let mut key_server = KeyServer { - id: object::new(ctx), - first_version: 2, - last_version: 2, - }; - - let key_server_v2 = KeyServerV2 { - name, - key_type: KeyTypeBonehFranklinBLS12381, - pk, - server_type: ServerType::Committee { version: 0, threshold, partial_key_servers }, - }; - - df::add(&mut key_server.id, 2u64, key_server_v2); - key_server -} - -/// Upgrade the current key server to v2, still a single owner object. -public fun upgrade_to_independent_v2(ks: &mut KeyServer) { - if (ks.has_v2()) { return }; - - let v1 = ks.v1(); - assert!(ks.last_version == 1, EInvalidVersion); - - let key_server_v2 = KeyServerV2 { - name: v1.name, - key_type: v1.key_type, - pk: v1.pk, - server_type: ServerType::Independent { url: v1.url }, - }; - - df::add(&mut ks.id, 2u64, key_server_v2); - ks.last_version = 2; -} - -/// Create a PartialKeyServer with respective fields. -public fun create_partial_key_server( - name: String, - url: String, - partial_pk: vector, - party_id: u16, -): PartialKeyServer { - let _ = g2_from_bytes(&partial_pk); - PartialKeyServer { - name, - url, - partial_pk, - party_id, - } -} - -/// Update the VecMap of partial key servers for a committee based KeyServerV2 and increment version. -public fun update_partial_key_servers( - s: &mut KeyServer, - threshold: u16, - partial_key_servers: VecMap, -) { - assert!(threshold > 0, EInvalidThreshold); - assert!(partial_key_servers.length() as u16 >= threshold, EInvalidThreshold); - - s.assert_committee_server_v2(); - let v2: &mut KeyServerV2 = df::borrow_mut(&mut s.id, 2u64); - match (&mut v2.server_type) { - ServerType::Committee { partial_key_servers: value, threshold: t, version: v } => { - *value = partial_key_servers; - *t = threshold; - *v = *v + 1; - }, - _ => abort EInvalidServerType, - } -} - -/// Update URL for a member's partial key server in a committee based KeyServerV2. -public fun update_member_url(s: &mut KeyServer, url: String, member: address) { - s.assert_committee_server_v2(); - let v2: &mut KeyServerV2 = df::borrow_mut(&mut s.id, 2u64); - match (&mut v2.server_type) { - ServerType::Committee { partial_key_servers, .. } => { - let partial_key_server = partial_key_servers.get_mut(&member); - partial_key_server.url = url; - }, - _ => abort EInvalidServerType, - } -} - -/// Get the v2 struct of a key server. -public fun v2(s: &KeyServer): &KeyServerV2 { - assert!(s.has_v2(), EInvalidVersion); - df::borrow(&s.id, 2u64) -} - -/// Check if KeyServer has v2. -public fun has_v2(s: &KeyServer): bool { - df::exists_(&s.id, 2u64) -} - -/// Check if KeyServer is v2 and is a committee server. -public fun assert_committee_server_v2(s: &KeyServer) { - assert!(s.has_v2(), EInvalidVersion); - assert!( - match (&s.v2().server_type) { - ServerType::Committee { .. } => true, - _ => false, - }, - EInvalidServerType, - ); -} - -// ===== V1 functions ===== - -// Entry function to register a key server v1 object and transfer it to the caller. -entry fun create_and_transfer_v1( - name: String, - url: String, - key_type: u8, - pk: vector, - ctx: &mut TxContext, -) { - let key_server = create_v1(name, url, key_type, pk, ctx); - transfer::transfer(key_server, ctx.sender()); -} - -/// Update URL for v1 or v2 independent server. -public fun update(s: &mut KeyServer, url: String) { - if (s.has_v2()) { - let v2: &mut KeyServerV2 = df::borrow_mut(&mut s.id, 2u64); - match (&mut v2.server_type) { - ServerType::Independent { url: value } => { - *value = url; - }, - _ => abort EInvalidServerType, - } - } else if (df::exists_(&s.id, 1u64)) { - let v1: &mut KeyServerV1 = df::borrow_mut(&mut s.id, 1u64); - v1.url = url; - } else { - abort EInvalidVersion - } -} - -/// Get the v1 struct of a key server. -public fun v1(s: &KeyServer): &KeyServerV1 { - assert!(df::exists_(&s.id, 1u64), EInvalidVersion); - df::borrow(&s.id, 1u64) -} - -/// Get name, supports both v1 and v2. -public fun name(s: &KeyServer): String { - if (s.has_v2()) { - s.v2().name - } else { - s.v1().name - } -} - -/// Get URL, supports v1 and v2 independent server only. -public fun url(s: &KeyServer): String { - if (s.has_v2()) { - let v2 = s.v2(); - match (&v2.server_type) { - ServerType::Independent { url } => *url, - _ => abort EInvalidServerType, - } - } else { - s.v1().url - } -} - -/// Get key type, supports both v1 and v2. -public fun key_type(s: &KeyServer): u8 { - if (s.has_v2()) { - s.v2().key_type - } else { - s.v1().key_type - } -} - -/// Get public key, supports both v1 and v2. -public fun pk(s: &KeyServer): &vector { - if (s.has_v2()) { - &s.v2().pk - } else { - &s.v1().pk - } -} - -/// Get the ID of the KeyServer, supports both v1 and v2. -public fun id(s: &KeyServer): address { - s.id.to_address() -} - -/// Get public key as BLS12-381 element, supports both v1 and v2. -public fun pk_as_bf_bls12381(s: &KeyServer): Element { - if (s.has_v2()) { - let v2 = s.v2(); - g2_from_bytes(&v2.pk) - } else { - let v1 = s.v1(); - assert!(v1.key_type == KeyTypeBonehFranklinBLS12381, EInvalidKeyType); - g2_from_bytes(&v1.pk) - } -} - -/// Internal function to create a KeyServerV1 object. -fun create_v1( - name: String, - url: String, - key_type: u8, - pk: vector, - ctx: &mut TxContext, -): KeyServer { - // Currently only BLS12-381 is supported. - assert!(key_type == KeyTypeBonehFranklinBLS12381, EInvalidKeyType); - let _ = g2_from_bytes(&pk); - - let mut key_server = KeyServer { - id: object::new(ctx), - first_version: 1, - last_version: 1, - }; - - let key_server_v1 = KeyServerV1 { - name, - url, - key_type, - pk, - }; - df::add(&mut key_server.id, 1u64, key_server_v1); - key_server -} - -/// Get the partial key server object corresponding to the member. -#[test_only] -public fun partial_key_server_for_member(s: &KeyServer, member: address): PartialKeyServer { - s.assert_committee_server_v2(); - let v2: &KeyServerV2 = df::borrow(&s.id, 2u64); - match (&v2.server_type) { - ServerType::Committee { partial_key_servers, .. } => { - *partial_key_servers.get(&member) - }, - _ => abort EInvalidServerType, - } -} - -/// Get URL for PartialKeyServer. -#[test_only] -public fun partial_ks_url(partial: &PartialKeyServer): String { - partial.url -} - -/// Get partial PK for PartialKeyServer. -#[test_only] -public fun partial_ks_pk(partial: &PartialKeyServer): vector { - partial.partial_pk -} - -/// Get party ID for PartialKeyServer. -#[test_only] -public fun partial_ks_party_id(partial: &PartialKeyServer): u16 { - partial.party_id -} - -/// Get the committee version and threshold for a committee-based KeyServer. -#[test_only] -public fun committee_version_and_threshold(s: &KeyServer): (u32, u16) { - s.assert_committee_server_v2(); - let v2: &KeyServerV2 = df::borrow(&s.id, 2u64); - match (&v2.server_type) { - ServerType::Committee { version, threshold, .. } => (*version, *threshold), - _ => abort EInvalidServerType, - } -} - -#[test_only] -public fun destroy_for_testing(v: KeyServer) { - let KeyServer { id, .. } = v; - id.delete(); -} - -#[test] -fun test_flow() { - use sui::test_scenario::{Self, next_tx, ctx}; - use sui::bls12381::{g2_generator}; - - let addr1 = @0xA; - let mut scenario = test_scenario::begin(addr1); - - let pk = g2_generator(); - let pk_bytes = *pk.bytes(); - create_and_transfer_v1( - b"mysten".to_string(), - b"https:/mysten-labs.com".to_string(), - 0, - pk_bytes, - scenario.ctx(), - ); - scenario.next_tx(addr1); - - let mut s: KeyServer = scenario.take_from_sender(); - assert!(s.name() == b"mysten".to_string(), 0); - assert!(s.url() == b"https:/mysten-labs.com".to_string(), 0); - assert!(*s.pk() == *pk.bytes(), 0); - s.update(b"https:/mysten-labs2.com".to_string()); - assert!(s.url() == b"https:/mysten-labs2.com".to_string(), 0); - - s.upgrade_to_independent_v2(); - s.update(b"https:/mysten-labs3.com".to_string()); - assert!(s.url() == b"https:/mysten-labs3.com".to_string(), 0); - - s.destroy_for_testing(); - scenario.end(); -}