Skip to content

Commit dbf722e

Browse files
committed
refactor: migrate from ed25519 to sr25519 for Substrate/Bittensor compatibility
- Update challenge-sdk decrypt_api_key() to use HKDF-based decryption - Update challenge-sdk verify_signature() to use sr25519 - Remove ed25519-dalek, x25519-dalek, curve25519-dalek from challenge-sdk - Update bins/utils to use sr25519 for key generation - All 50 challenge-sdk tests passing
1 parent 8aa071f commit dbf722e

File tree

12 files changed

+170
-147
lines changed

12 files changed

+170
-147
lines changed

Cargo.lock

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

bins/csudo/src/main.rs

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,14 +1260,15 @@ async fn main() -> Result<()> {
12601260
}
12611261
},
12621262

1263-
Commands::Refresh(cmd) => match cmd {
1264-
RefreshCommands::All => {
1265-
println!(
1266-
"{}",
1267-
"Requesting all validators to re-pull and restart challenges..."
1268-
.bright_yellow()
1269-
);
1270-
if Confirm::with_theme(&ColorfulTheme::default())
1263+
Commands::Refresh(cmd) => {
1264+
match cmd {
1265+
RefreshCommands::All => {
1266+
println!(
1267+
"{}",
1268+
"Requesting all validators to re-pull and restart challenges..."
1269+
.bright_yellow()
1270+
);
1271+
if Confirm::with_theme(&ColorfulTheme::default())
12711272
.with_prompt("This will restart all challenge containers on all validators. Continue?")
12721273
.default(true)
12731274
.interact()?
@@ -1279,53 +1280,54 @@ async fn main() -> Result<()> {
12791280
)
12801281
.await?;
12811282
}
1282-
}
1283-
RefreshCommands::Challenge { id } => {
1284-
let state = fetch_chain_state(&args.rpc).await?;
1283+
}
1284+
RefreshCommands::Challenge { id } => {
1285+
let state = fetch_chain_state(&args.rpc).await?;
12851286

1286-
let challenge = if let Some(id) = id {
1287-
state
1288-
.challenges
1289-
.iter()
1290-
.find(|c| c.id.starts_with(&id))
1291-
.ok_or_else(|| anyhow::anyhow!("Challenge not found: {}", id))?
1292-
} else {
1293-
if state.challenges.is_empty() {
1294-
println!("{}", "No challenges to refresh.".yellow());
1295-
return Ok(());
1296-
}
1287+
let challenge = if let Some(id) = id {
1288+
state
1289+
.challenges
1290+
.iter()
1291+
.find(|c| c.id.starts_with(&id))
1292+
.ok_or_else(|| anyhow::anyhow!("Challenge not found: {}", id))?
1293+
} else {
1294+
if state.challenges.is_empty() {
1295+
println!("{}", "No challenges to refresh.".yellow());
1296+
return Ok(());
1297+
}
12971298

1298-
let options: Vec<String> = state
1299-
.challenges
1300-
.iter()
1301-
.map(|c| format!("{} ({})", c.name, &c.id[..8]))
1302-
.collect();
1299+
let options: Vec<String> = state
1300+
.challenges
1301+
.iter()
1302+
.map(|c| format!("{} ({})", c.name, &c.id[..8]))
1303+
.collect();
13031304

1304-
let selection = FuzzySelect::with_theme(&ColorfulTheme::default())
1305-
.with_prompt("Select challenge to refresh")
1306-
.items(&options)
1307-
.interact()?;
1305+
let selection = FuzzySelect::with_theme(&ColorfulTheme::default())
1306+
.with_prompt("Select challenge to refresh")
1307+
.items(&options)
1308+
.interact()?;
13081309

1309-
&state.challenges[selection]
1310-
};
1310+
&state.challenges[selection]
1311+
};
13111312

1312-
println!(
1313-
"Refreshing challenge: {} ({})",
1314-
challenge.name.green(),
1315-
&challenge.id[..8]
1316-
);
1313+
println!(
1314+
"Refreshing challenge: {} ({})",
1315+
challenge.name.green(),
1316+
&challenge.id[..8]
1317+
);
13171318

1318-
let challenge_id = ChallengeId(uuid::Uuid::parse_str(&challenge.id)?);
1319-
submit_action(
1320-
&args.rpc,
1321-
&keypair,
1322-
SudoAction::RefreshChallenges {
1323-
challenge_id: Some(challenge_id),
1324-
},
1325-
)
1326-
.await?;
1319+
let challenge_id = ChallengeId(uuid::Uuid::parse_str(&challenge.id)?);
1320+
submit_action(
1321+
&args.rpc,
1322+
&keypair,
1323+
SudoAction::RefreshChallenges {
1324+
challenge_id: Some(challenge_id),
1325+
},
1326+
)
1327+
.await?;
1328+
}
13271329
}
1328-
},
1330+
}
13291331
}
13301332

13311333
Ok(())

bins/utils/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ version = "0.1.0"
44
edition = "2021"
55

66
[dependencies]
7-
ed25519-dalek = "2.1"
7+
sp-core = { version = "31.0", default-features = false, features = ["std"] }
88
hex = "0.4"

bins/utils/src/main.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
use sp_core::{sr25519, Pair};
2+
13
fn main() {
24
let secret_hex = "0000000000000000000000000000000000000000000000000000000000000001";
35
let bytes = hex::decode(secret_hex).expect("Invalid hex");
46
let mut arr = [0u8; 32];
57
arr.copy_from_slice(&bytes);
68

7-
let signing_key = ed25519_dalek::SigningKey::from_bytes(&arr);
8-
let verifying_key = signing_key.verifying_key();
9-
println!("{}", hex::encode(verifying_key.to_bytes()));
9+
// Use sr25519 (Substrate/Bittensor standard)
10+
let pair = sr25519::Pair::from_seed(&arr);
11+
let public = pair.public();
12+
println!("{}", hex::encode(public.0));
1013
}

bins/validator-node/src/main.rs

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ async fn main() -> Result<()> {
863863

864864
let mut skipped_low_stake = 0;
865865
let mut add_failed = 0;
866-
866+
867867
for neuron in metagraph.neurons.values() {
868868
// Convert AccountId32 hotkey to our Hotkey type
869869
let hotkey_bytes: &[u8; 32] = neuron.hotkey.as_ref();
@@ -873,7 +873,8 @@ async fn main() -> Result<()> {
873873
// Use total_stake from metagraph - this is the ACTUAL stake used in consensus
874874
// It includes: alpha stake + (tao stake * tao_weight)
875875
// The runtime API calculates this including parent inheritance
876-
let stake_rao = neuron.total_stake.min(u64::MAX as u128) as u64;
876+
let stake_rao =
877+
neuron.total_stake.min(u64::MAX as u128) as u64;
877878

878879
// ALWAYS cache stake in protection for debugging
879880
// This allows us to show actual stake when rejecting low-stake validators
@@ -915,7 +916,7 @@ async fn main() -> Result<()> {
915916
v.stake = Stake::new(stake_rao);
916917
}
917918
}
918-
919+
919920
if skipped_low_stake > 0 {
920921
debug!(
921922
"Skipped {} neurons with stake below {} TAO threshold",
@@ -1021,7 +1022,11 @@ async fn main() -> Result<()> {
10211022
let mut network = NetworkNode::with_hotkey(node_config.clone(), Some(&our_hotkey_hex)).await?;
10221023
let mut event_rx = network.take_event_receiver().unwrap();
10231024

1024-
info!("Local peer ID: {} (hotkey: {})", network.local_peer_id(), keypair.ss58_address());
1025+
info!(
1026+
"Local peer ID: {} (hotkey: {})",
1027+
network.local_peer_id(),
1028+
keypair.ss58_address()
1029+
);
10251030

10261031
// Start network
10271032
network.start(&node_config).await?;
@@ -1032,7 +1037,8 @@ async fn main() -> Result<()> {
10321037
// Spawn network event loop in a separate task
10331038
tokio::spawn(async move {
10341039
// Bootstrap retry interval - reconnect to bootnode if disconnected
1035-
let mut bootstrap_retry_interval = tokio::time::interval(std::time::Duration::from_secs(30));
1040+
let mut bootstrap_retry_interval =
1041+
tokio::time::interval(std::time::Duration::from_secs(30));
10361042
bootstrap_retry_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
10371043

10381044
loop {
@@ -1160,21 +1166,26 @@ async fn main() -> Result<()> {
11601166
info!("Challenge container '{}' started successfully", config.name);
11611167

11621168
// Get actual endpoint from orchestrator and update the endpoints map
1163-
let endpoint = if let Some(instance) = orch.get_challenge(&config.challenge_id) {
1169+
let endpoint = if let Some(instance) =
1170+
orch.get_challenge(&config.challenge_id)
1171+
{
11641172
instance.endpoint.clone()
11651173
} else {
11661174
// Fallback to constructed URL if instance not found
11671175
let container_name = config.name.to_lowercase().replace(' ', "-");
11681176
format!("http://challenge-{}:8080", container_name)
11691177
};
1170-
1178+
11711179
// Update endpoints map for HTTP proxying (store by both UUID and name)
11721180
{
11731181
let mut eps = endpoints_for_orch.write();
11741182
eps.insert(config.challenge_id.to_string(), endpoint.clone());
11751183
eps.insert(config.name.clone(), endpoint.clone());
11761184
}
1177-
info!("Updated endpoint for challenge '{}' ({}): {}", config.name, config.challenge_id, endpoint);
1185+
info!(
1186+
"Updated endpoint for challenge '{}' ({}): {}",
1187+
config.name, config.challenge_id, endpoint
1188+
);
11781189

11791190
// Discover routes from the container via /.well-known/routes
11801191
if let Some(ref routes) = routes_map {
@@ -1325,12 +1336,17 @@ async fn main() -> Result<()> {
13251336

13261337
for config in configs {
13271338
// Get actual endpoint from orchestrator (includes validator suffix in dev mode)
1328-
let routes_url = if let Some(instance) = orch_for_discovery.get_challenge(&config.challenge_id) {
1339+
let routes_url = if let Some(instance) =
1340+
orch_for_discovery.get_challenge(&config.challenge_id)
1341+
{
13291342
format!("{}/.well-known/routes", instance.endpoint)
13301343
} else {
13311344
// Fallback to constructed URL if instance not found
13321345
let container_name = config.name.to_lowercase().replace(' ', "-");
1333-
format!("http://challenge-{}:8080/.well-known/routes", container_name)
1346+
format!(
1347+
"http://challenge-{}:8080/.well-known/routes",
1348+
container_name
1349+
)
13341350
};
13351351

13361352
info!(
@@ -1875,7 +1891,7 @@ async fn main() -> Result<()> {
18751891
NetworkEvent::PeerIdentified { peer_id, hotkey, agent_version } => {
18761892
let peer_str = peer_id.to_string();
18771893
let should_validate_stake = protection.config().validate_stake;
1878-
1894+
18791895
if let Some(ref hk) = hotkey {
18801896
// Convert hex hotkey to SS58 for display
18811897
let ss58 = if let Ok(bytes) = hex::decode(hk) {
@@ -2222,7 +2238,7 @@ async fn main() -> Result<()> {
22222238
let hotkey_bytes: &[u8; 32] = neuron.hotkey.as_ref();
22232239
let hotkey = Hotkey(*hotkey_bytes);
22242240
let hotkey_hex = hotkey.to_hex();
2225-
2241+
22262242
// Use total_stake from metagraph (includes parent inheritance + TAO weight)
22272243
let stake_rao = neuron.total_stake.min(u64::MAX as u128) as u64;
22282244

@@ -2455,14 +2471,22 @@ async fn handle_message(
24552471
error!("Failed to start challenge container: {}", e);
24562472
} else {
24572473
info!("Challenge container started: {}", config.name);
2458-
2474+
24592475
// Update endpoints map with actual container endpoint
24602476
if let Some(endpoints) = challenge_endpoints {
2461-
if let Some(instance) = orchestrator.get_challenge(&config.challenge_id) {
2477+
if let Some(instance) =
2478+
orchestrator.get_challenge(&config.challenge_id)
2479+
{
24622480
let mut eps = endpoints.write();
2463-
eps.insert(config.challenge_id.to_string(), instance.endpoint.clone());
2481+
eps.insert(
2482+
config.challenge_id.to_string(),
2483+
instance.endpoint.clone(),
2484+
);
24642485
eps.insert(config.name.clone(), instance.endpoint.clone());
2465-
info!("Updated endpoint for challenge '{}': {}", config.name, instance.endpoint);
2486+
info!(
2487+
"Updated endpoint for challenge '{}': {}",
2488+
config.name, instance.endpoint
2489+
);
24662490
}
24672491
}
24682492
}
@@ -2605,14 +2629,21 @@ async fn handle_message(
26052629
error!("Failed to start challenge container from P2P: {}", e);
26062630
} else {
26072631
info!("Challenge container '{}' started from P2P", config.name);
2608-
2632+
26092633
// Update endpoints map with actual container endpoint
26102634
if let Some(endpoints) = challenge_endpoints {
2611-
if let Some(instance) = orchestrator.get_challenge(&config.challenge_id) {
2635+
if let Some(instance) = orchestrator.get_challenge(&config.challenge_id)
2636+
{
26122637
let mut eps = endpoints.write();
2613-
eps.insert(config.challenge_id.to_string(), instance.endpoint.clone());
2638+
eps.insert(
2639+
config.challenge_id.to_string(),
2640+
instance.endpoint.clone(),
2641+
);
26142642
eps.insert(config.name.clone(), instance.endpoint.clone());
2615-
info!("Updated endpoint for challenge '{}' (P2P): {}", config.name, instance.endpoint);
2643+
info!(
2644+
"Updated endpoint for challenge '{}' (P2P): {}",
2645+
config.name, instance.endpoint
2646+
);
26162647
}
26172648
}
26182649
}

crates/challenge-orchestrator/src/docker.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,10 @@ impl DockerClient {
304304
// Create persistent data directory for challenge state (survives restarts)
305305
let challenge_data_dir = format!("/tmp/platform-challenges/{}/data", container_name);
306306
if let Err(e) = std::fs::create_dir_all(&challenge_data_dir) {
307-
warn!("Failed to create challenge data dir {}: {}", challenge_data_dir, e);
307+
warn!(
308+
"Failed to create challenge data dir {}: {}",
309+
challenge_data_dir, e
310+
);
308311
}
309312

310313
// Build host config with resource limits
@@ -320,7 +323,7 @@ impl DockerClient {
320323
"/var/run/docker.sock:/var/run/docker.sock:rw".to_string(),
321324
"/tmp/platform-tasks:/app/data/tasks:rw".to_string(), // Override internal tasks
322325
"/tmp/platform-tasks:/tmp/platform-tasks:rw".to_string(), // For DinD path mapping
323-
format!("{}:/data:rw", challenge_data_dir), // Persistent challenge state
326+
format!("{}:/data:rw", challenge_data_dir), // Persistent challenge state
324327
]),
325328
..Default::default()
326329
};

crates/challenge-sdk/Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,7 @@ sp-core = { workspace = true }
2727
parity-scale-codec = { workspace = true }
2828
aes-gcm = "0.10"
2929
rand = { workspace = true }
30-
ed25519-dalek = { version = "2.1", features = ["rand_core"] }
31-
x25519-dalek = { version = "2.0", features = ["static_secrets"] }
3230
chacha20poly1305 = "0.10"
33-
curve25519-dalek = "4.1"
3431

3532
# Utils
3633
uuid = { workspace = true }

0 commit comments

Comments
 (0)