Skip to content

Commit 4c7d38a

Browse files
committed
[key-server] handle committee config and rotation mode
1 parent 2054f39 commit 4c7d38a

File tree

10 files changed

+519
-76
lines changed

10 files changed

+519
-76
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ checkpoints_dir/*
8080
light_client.yaml
8181
docs/content/references/sui-api/sui-graphql/*
8282
docs/content/references/framework/**
83-
83+
*.example
8484
lcov.info
8585

8686
**/build/**

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/key-server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ hyper-util = "0.1.10"
5050
http-body-util = "0.1.2"
5151
futures = "0.3"
5252
seal-sdk = { path = "../seal-sdk" }
53+
seal-committee = { path = "../seal-committee" }
5354

5455
# sui grpc dep
5556
sui-rpc = { workspace = true }

crates/key-server/key-server-config.yaml

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
# network: Devnet
77
# network: !Custom
88
# node_url: 'url_to_node_endpoint' # Optional - see below
9-
# use_default_mainnet_for_mvr: true # Optional, default to true if not present,
10-
set this to false for mvr to resolve on testnet.
9+
# use_default_mainnet_for_mvr: true # Optional, default to true if not present, set this to false for mvr to resolve on testnet.
1110
#
1211
# Use Custom for production deployments. You must provide the node URL
1312
# in exactly one of these ways:
@@ -19,10 +18,12 @@ set this to false for mvr to resolve on testnet.
1918
network: Testnet
2019

2120

22-
# Server mode. The server can either work in Open/permissionless mode that
23-
# supports all packages, or in Permissioned mode that only supports
24-
# whitelisted packages .
21+
# Server mode:
22+
# 1) Open: Supports all policy packages.
23+
# 2) Permissioned: Supports only allowed policy packages.
24+
# 3) Committee: Supports all packages. Requires DKG setup for master key share.
2525
#
26+
# #### OPEN MODE ####
2627
# For the Open mode, set the registered key server object ID to be used, e.g.,:
2728
# server_mode: !Open
2829
# key_server_object_id: '0x0000000000000000000000000000000000000000000000000000000000000000'
@@ -31,7 +32,7 @@ network: Testnet
3132
server_mode: !Open
3233
key_server_object_id: '0x0000000000000000000000000000000000000000000000000000000000000000'
3334

34-
#
35+
# #### PERMISSIONED MODE ####
3536
# For the Permissioned mode, set the allowed clients and their configurations.
3637
# A client config contains:
3738
# - Client name - for debugging purposes only, can always be modified.
@@ -75,6 +76,26 @@ server_mode: !Open
7576
# package_ids:
7677
# - "0x3333333333333333333333333333333333333333333333333333333333333333"
7778

79+
# #### COMMITTEE MODE ####
80+
# See step 7 in the member runbook for fresh DKG and key rotation in details.
81+
# - For active mode, only MASTER_SHARE is set, the key_serverion_version in config is expected to
82+
# be the same as the version onchain in Committee V2 key server.
83+
# - For key rotation, both MASTER_SHARE and NEXT_MASTER_SHARE are set, the key_server_version in
84+
# config is expected to be exactly 1 greater than the version onchain. The MASTER_SHARE is used to
85+
# serve traffic, and when onchain version is updated to the same as the config version, the
86+
# NEXT_MASTER_SHARE will be used to serve traffic.
87+
#
88+
# Example for a fresh DKG (MASTER_SHARE is set as environment variable):
89+
# server_mode: !Committee
90+
# member_address: '<MY_ADDRESS>'
91+
# key_server_obj_id: '<KEY_SERVER_OBJ_ID>'
92+
# key_server_version: 0
93+
#
94+
# Example for key rotation (both MASTER_SHARE and NEXT_MASTER_SHARE are set in environment variables):
95+
# server_mode: !Committee
96+
# member_address: '<MY_ADDRESS>'
97+
# key_server_obj_id: '<KEY_SERVER_OBJ_ID>'
98+
# key_server_version: <KEY_SERVER_VERSION>
7899

79100
#### Optional advanced configurations ####
80101
#

crates/key-server/src/key_server_options.rs

Lines changed: 119 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use duration_str::deserialize_duration;
99
use semver::VersionReq;
1010
use serde::{Deserialize, Serialize};
1111
use std::time::Duration;
12+
use sui_sdk_types::Address;
1213
use sui_types::base_types::ObjectID;
1314
use tracing::info;
1415

@@ -47,6 +48,14 @@ pub enum ServerMode {
4748
// Master key is expected to by 32 byte HKDF seed
4849
client_configs: Vec<ClientConfig>,
4950
},
51+
Committee {
52+
member_address: Address,
53+
key_server_obj_id: Address,
54+
/// The target key server version set during rotation. During rotation, this can be exactly
55+
/// 1 greater than the onchain version. Onchain rotation is completed, the onchain version
56+
/// matches this version, NEXT_MASTER_SHARE will be used.
57+
target_key_server_version: u32,
58+
},
5059
}
5160

5261
/// Configuration for the RPC client.
@@ -255,26 +264,6 @@ impl KeyServerOptions {
255264
}
256265
Ok(())
257266
}
258-
259-
pub(crate) fn get_supported_key_server_object_ids(&self) -> Vec<ObjectID> {
260-
match &self.server_mode {
261-
ServerMode::Open {
262-
key_server_object_id,
263-
} => {
264-
vec![*key_server_object_id]
265-
}
266-
ServerMode::Permissioned { client_configs } => client_configs
267-
.iter()
268-
.filter(|c| {
269-
matches!(
270-
c.client_master_key,
271-
ClientKeyType::Derived { .. } | ClientKeyType::Imported { .. }
272-
)
273-
})
274-
.map(|c| c.key_server_object_id)
275-
.collect(),
276-
}
277-
}
278267
}
279268

280269
fn default_checkpoint_update_interval() -> Duration {
@@ -378,9 +367,17 @@ server_mode: !Open
378367
}
379368
}
380369

381-
#[test]
382-
fn test_parse_permissioned_config() {
370+
#[tokio::test]
371+
async fn test_parse_permissioned_config() {
372+
use crate::master_keys::MasterKeys;
373+
use crate::types::IbeMasterKey;
374+
use crate::DefaultEncoding;
375+
use crate::Server;
376+
use fastcrypto::encoding::Encoding;
377+
use fastcrypto::groups::GroupElement;
378+
use seal_committee::create_grpc_client;
383379
use std::str::FromStr;
380+
use temp_env::async_with_vars;
384381

385382
let valid_configuration = r#"
386383
network: Mainnet
@@ -416,19 +413,37 @@ session_key_ttl_max: '60s'
416413
let options: KeyServerOptions =
417414
serde_yaml::from_str(valid_configuration).expect("Failed to parse valid configuration");
418415

419-
assert_eq!(
420-
options.get_supported_key_server_object_ids(),
421-
vec![
422-
ObjectID::from_str(
423-
"0xaaaa000000000000000000000000000000000000000000000000000000000001"
424-
)
425-
.unwrap(),
426-
ObjectID::from_str(
427-
"0xbbbb000000000000000000000000000000000000000000000000000000000002"
428-
)
429-
.unwrap(),
430-
]
431-
);
416+
let seed_encoded = DefaultEncoding::encode([1u8; 32]);
417+
let bob_key = IbeMasterKey::generator();
418+
let bob_key_encoded = DefaultEncoding::encode(bcs::to_bytes(&bob_key).unwrap());
419+
420+
async_with_vars(
421+
[
422+
("MASTER_KEY", Some(&seed_encoded)),
423+
("BOB_BLS_KEY", Some(&bob_key_encoded)),
424+
],
425+
async {
426+
let master_keys = MasterKeys::load(&options).expect("Failed to load master keys");
427+
let grpc_client = create_grpc_client(&seal_committee::Network::Testnet).unwrap();
428+
let map = Server::build_key_server_pop_map(&options, &master_keys, grpc_client)
429+
.await
430+
.unwrap();
431+
assert_eq!(map.keys().len(), 2);
432+
assert!(map.contains_key(
433+
&ObjectID::from_str(
434+
"0xaaaa000000000000000000000000000000000000000000000000000000000001"
435+
)
436+
.unwrap()
437+
));
438+
assert!(map.contains_key(
439+
&ObjectID::from_str(
440+
"0xbbbb000000000000000000000000000000000000000000000000000000000002"
441+
)
442+
.unwrap()
443+
));
444+
},
445+
)
446+
.await;
432447
}
433448

434449
#[test]
@@ -569,3 +584,72 @@ server_mode: !Permissioned
569584
assert_eq!(result.unwrap_err().to_string(), expected_error);
570585
}
571586
}
587+
588+
#[test]
589+
fn test_committee_mode_master_keys() {
590+
use crate::master_keys::MasterKeys;
591+
use crate::types::IbeMasterKey;
592+
use crate::DefaultEncoding;
593+
use fastcrypto::encoding::Encoding;
594+
use fastcrypto::groups::GroupElement;
595+
use temp_env::with_vars;
596+
597+
// master_share with version 0 (fresh DKG), no next_master_share expected.
598+
let config_fresh_dkg = r#"
599+
network: Mainnet
600+
server_mode: !Committee
601+
member_address: '0x0000000000000000000000000000000000000000000000000000000000000000'
602+
key_server_obj_id: '0x0000000000000000000000000000000000000000000000000000000000000001'
603+
target_key_server_version: 0
604+
"#;
605+
let options: KeyServerOptions =
606+
serde_yaml::from_str(config_fresh_dkg).expect("Failed to parse configuration");
607+
608+
let master_share = IbeMasterKey::generator();
609+
let master_share_encoded = DefaultEncoding::encode(bcs::to_bytes(&master_share).unwrap());
610+
611+
with_vars([("MASTER_SHARE", Some(&master_share_encoded))], || {
612+
assert!(MasterKeys::load(&options).is_ok());
613+
});
614+
615+
// master_share and next_master_share with version > 0 (rotation), both shares expected.
616+
let config_rotation = r#"
617+
network: Mainnet
618+
server_mode: !Committee
619+
member_address: '0x0000000000000000000000000000000000000000000000000000000000000000'
620+
key_server_obj_id: '0x0000000000000000000000000000000000000000000000000000000000000001'
621+
target_key_server_version: 1
622+
"#;
623+
let options_rotation: KeyServerOptions =
624+
serde_yaml::from_str(config_rotation).expect("Failed to parse configuration");
625+
626+
let next_master_share = IbeMasterKey::generator();
627+
let next_master_share_encoded =
628+
DefaultEncoding::encode(bcs::to_bytes(&next_master_share).unwrap());
629+
630+
with_vars(
631+
[
632+
("MASTER_SHARE", Some(&master_share_encoded)),
633+
("NEXT_MASTER_SHARE", Some(&next_master_share_encoded)),
634+
],
635+
|| {
636+
assert!(MasterKeys::load(&options_rotation).is_ok());
637+
},
638+
);
639+
640+
// next_master_share present but version is 0 (fresh DKG), should fail.
641+
with_vars(
642+
[
643+
("MASTER_SHARE", Some(&master_share_encoded)),
644+
("NEXT_MASTER_SHARE", Some(&next_master_share_encoded)),
645+
],
646+
|| {
647+
assert!(MasterKeys::load(&options).is_err());
648+
},
649+
);
650+
651+
// next_master_share absent but version > 0 (rotation complete), should succeed.
652+
with_vars([("MASTER_SHARE", Some(&master_share_encoded))], || {
653+
assert!(MasterKeys::load(&options_rotation).is_ok());
654+
});
655+
}

0 commit comments

Comments
 (0)