From a1b3ed1fff170f9120c0370ddec6c2854f91211e Mon Sep 17 00:00:00 2001 From: peg Date: Thu, 4 Apr 2024 15:17:51 +0200 Subject: [PATCH 01/35] Add HKDF for getting signing and encryption keys --- Cargo.lock | 7 +-- crates/threshold-signature-server/Cargo.toml | 3 +- .../src/helpers/launch.rs | 44 +++++++++---------- .../src/helpers/validator.rs | 43 ++++++++++++++---- .../src/user/errors.rs | 2 + 5 files changed, 64 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8b53d935..5c475216b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3059,6 +3059,7 @@ dependencies = [ "futures", "hex", "hex-literal", + "hkdf", "hostname", "lazy_static", "more-asserts", @@ -4281,9 +4282,9 @@ checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac 0.12.1", ] @@ -6464,7 +6465,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate 2.0.2", "proc-macro2", "quote", "syn 2.0.43", diff --git a/crates/threshold-signature-server/Cargo.toml b/crates/threshold-signature-server/Cargo.toml index 27d0b1dd1..4333e8762 100644 --- a/crates/threshold-signature-server/Cargo.toml +++ b/crates/threshold-signature-server/Cargo.toml @@ -68,7 +68,8 @@ sha3 ="0.10.8" async-trait ="0.1.76" hostname ="0.3" sha1 ="0.10.6" -sha2 ="0.10.8" +sha2 = "0.10.8" +hkdf = "0.12.4" [dev-dependencies] entropy-testing-utils={ path="../testing-utils" } diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs index 0f008277b..6e3d76d8e 100644 --- a/crates/threshold-signature-server/src/helpers/launch.rs +++ b/crates/threshold-signature-server/src/helpers/launch.rs @@ -30,7 +30,10 @@ use subxt::ext::sp_core::{ sr25519, Pair, }; -use crate::validation::{derive_static_secret, mnemonic_to_pair, new_mnemonic}; +use crate::{ + helpers::validator::{get_signer, get_x25519_keypair}, + validation::new_mnemonic, +}; pub const DEFAULT_MNEMONIC: &str = "alarm mutual concert decrease hurry invest culture survey diagram crash snap click"; @@ -189,10 +192,20 @@ pub async fn setup_mnemonic( } .expect("Issue creating Mnemonic"); - let phrase = mnemonic.to_string(); - let pair = mnemonic_to_pair(&mnemonic).expect("Issue deriving Mnemonic"); - let static_secret = derive_static_secret(&pair); - let dh_public = x25519_dalek::PublicKey::from(&static_secret); + // Update the value in the kvdb + let reservation = kv + .kv() + .reserve_key(FORBIDDEN_KEYS[0].to_string()) + .await + .expect("Issue reserving mnemonic"); + kv.kv() + .put(reservation, mnemonic.to_string().as_bytes().to_vec()) + .await + .expect("failed to update mnemonic"); + + let pair = get_signer(&kv).await.expect("Cannot derive signing keypair"); + let (static_secret, x25519_public_key) = + get_x25519_keypair(&kv).await.expect("Cannot derive encryption keypair"); let ss_reservation = kv .kv() @@ -210,31 +223,16 @@ pub async fn setup_mnemonic( .await .expect("Issue reserving DH key"); - let converted_dh_public = dh_public.to_bytes().to_vec(); - kv.kv() - .put(dh_reservation, converted_dh_public.clone()) - .await - .expect("failed to update dh"); + kv.kv().put(dh_reservation, x25519_public_key.to_vec()).await.expect("failed to update dh"); - let formatted_dh_public = format!("{converted_dh_public:?}").replace('"', ""); + let formatted_dh_public = format!("{x25519_public_key:?}").replace('"', ""); fs::write(".entropy/public_key", formatted_dh_public) .expect("Failed to write public key file"); - let id = AccountId32::new(pair.public().0); + let id = AccountId32::new(pair.signer().public().0); fs::write(".entropy/account_id", format!("{id}")).expect("Failed to write account_id file"); - // Update the value in the kvdb - let reservation = kv - .kv() - .reserve_key(FORBIDDEN_KEYS[0].to_string()) - .await - .expect("Issue reserving mnemonic"); - kv.kv() - .put(reservation, phrase.as_bytes().to_vec()) - .await - .expect("failed to update mnemonic"); - tracing::debug!("Starting process with account ID: `{id}`"); } Ok(()) diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs index 2d1ede24e..7ddeed64f 100644 --- a/crates/threshold-signature-server/src/helpers/validator.rs +++ b/crates/threshold-signature-server/src/helpers/validator.rs @@ -16,25 +16,52 @@ //! Utilites relating to [crate::validator] use bip39::{Language, Mnemonic}; use entropy_kvdb::kv_manager::KvManager; +use hkdf::Hkdf; +use sha2::Sha256; use subxt::{ ext::sp_core::{sr25519, Pair}, tx::PairSigner, }; +use x25519_dalek::{PublicKey, StaticSecret}; +use zeroize::Zeroize; use crate::{chain_api::EntropyConfig, user::UserErr}; +/// Get the key derivation struct to derive secret keys from a mnemonic stored in the KVDB +async fn get_hkdf(kv: &KvManager) -> Result, UserErr> { + let _ = kv.kv().exists("MNEMONIC").await?; + let raw_m = kv.kv().get("MNEMONIC").await?; + let secret = core::str::from_utf8(&raw_m)?; + let mnemonic = Mnemonic::parse_in_normalized(Language::English, secret) + .map_err(|e| UserErr::Mnemonic(e.to_string()))?; + + Ok(Hkdf::::new(None, &mnemonic.to_seed(""))) +} + /// Returns PairSigner for this nodes threshold server. /// The PairSigner is stored as an encrypted mnemonic in the kvdb and /// is used for PKE and to submit extrensics on chain. pub async fn get_signer( kv: &KvManager, ) -> Result, UserErr> { - let _ = kv.kv().exists("MNEMONIC").await?; - let raw_m = kv.kv().get("MNEMONIC").await?; - let secret = core::str::from_utf8(&raw_m)?; - let mnemonic = Mnemonic::parse_in_normalized(Language::English, secret) - .map_err(|e| UserErr::Mnemonic(e.to_string()))?; - let pair = ::from_phrase(&mnemonic.to_string(), None) - .map_err(|_| UserErr::SecretString("Secret String Error"))?; - Ok(PairSigner::::new(pair.0)) + let hkdf = get_hkdf(kv).await?; + + let mut sr25519_seed = [0u8; 64]; + hkdf.expand(b"sr25519-threshold-account", &mut sr25519_seed) + .expect("Cannot get 64 byte output from sha256"); + let pair = sr25519::Pair::from_seed_slice(&sr25519_seed)?; + sr25519_seed.zeroize(); + Ok(PairSigner::::new(pair)) +} + +/// Get the x25519 encryption keypair for this threshold server +pub async fn get_x25519_keypair(kv: &KvManager) -> Result<(StaticSecret, [u8; 32]), UserErr> { + let hkdf = get_hkdf(kv).await?; + + let mut secret = [0u8; 32]; + hkdf.expand(b"X25519-keypair", &mut secret).expect("Cannot get 32 byte output from sha256"); + let static_secret = StaticSecret::from(secret); + // TODO zeroize seed + let public_key = PublicKey::from(&static_secret).to_bytes(); + Ok((static_secret, public_key)) } diff --git a/crates/threshold-signature-server/src/user/errors.rs b/crates/threshold-signature-server/src/user/errors.rs index 4567f9223..eaf193447 100644 --- a/crates/threshold-signature-server/src/user/errors.rs +++ b/crates/threshold-signature-server/src/user/errors.rs @@ -151,6 +151,8 @@ pub enum UserErr { KeyShareRejected(String), #[error("Custom hash choice out of bounds")] CustomHashOutOfBounds, + #[error("Error creating sr25519 keypair from seed: {0}")] + SpCoreSecretString(#[from] sp_core::crypto::SecretStringError), } impl IntoResponse for UserErr { From 5e3da0140a36566aa464fac99b7afb1301b03fb6 Mon Sep 17 00:00:00 2001 From: peg Date: Thu, 4 Apr 2024 15:32:00 +0200 Subject: [PATCH 02/35] Taplo --- crates/threshold-signature-server/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/threshold-signature-server/Cargo.toml b/crates/threshold-signature-server/Cargo.toml index 4333e8762..07e28e913 100644 --- a/crates/threshold-signature-server/Cargo.toml +++ b/crates/threshold-signature-server/Cargo.toml @@ -68,8 +68,8 @@ sha3 ="0.10.8" async-trait ="0.1.76" hostname ="0.3" sha1 ="0.10.6" -sha2 = "0.10.8" -hkdf = "0.12.4" +sha2 ="0.10.8" +hkdf ="0.12.4" [dev-dependencies] entropy-testing-utils={ path="../testing-utils" } From 6dc7629458175da1ef84506110398691a6161765 Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 5 Apr 2024 14:45:33 +0200 Subject: [PATCH 03/35] Update sign_and_encrypt to take x25519 key as input rather than derive it --- crates/protocol/src/sign_and_encrypt/mod.rs | 81 +++++++-------------- crates/protocol/src/user/mod.rs | 10 ++- 2 files changed, 32 insertions(+), 59 deletions(-) diff --git a/crates/protocol/src/sign_and_encrypt/mod.rs b/crates/protocol/src/sign_and_encrypt/mod.rs index d1cb20308..f994cc461 100644 --- a/crates/protocol/src/sign_and_encrypt/mod.rs +++ b/crates/protocol/src/sign_and_encrypt/mod.rs @@ -19,46 +19,14 @@ mod hpke; #[cfg(feature = "wasm")] pub mod wasm; -use blake2::{Blake2s256, Digest}; use entropy_shared::X25519PublicKey; use hpke::HpkeMessage; -use hpke_rs::{HpkeError, HpkeKeyPair, HpkePrivateKey, HpkePublicKey}; -use rand_core::{OsRng, RngCore}; +use hpke_rs::{HpkeError, HpkePrivateKey, HpkePublicKey}; +use rand_core::OsRng; use serde::{Deserialize, Serialize}; use sp_core::{crypto::AccountId32, sr25519, Bytes, Pair}; use thiserror::Error; use x25519_dalek::StaticSecret; -use zeroize::Zeroize; - -/// Given a sr25519 secret signing key, derive an x25519 public key -pub fn derive_x25519_public_key(sk: &sr25519::Pair) -> Result { - let (_, hpke_public_key) = derive_hpke_keypair(sk)?; - let mut x25519_public_key: [u8; 32] = [0; 32]; - x25519_public_key.copy_from_slice(hpke_public_key.as_slice()); - Ok(x25519_public_key) -} - -/// Given a sr25519 secret signing key, derive an x25519 secret key -pub fn derive_x25519_static_secret(sk: &sr25519::Pair) -> StaticSecret { - let mut hasher = Blake2s256::new(); - hasher.update(&sk.to_raw_vec()); - let mut hash = hasher.finalize(); - - let mut buffer: [u8; 32] = [0; 32]; - buffer.copy_from_slice(&hash); - hash.zeroize(); - StaticSecret::from(buffer) -} - -/// Given a sr25519 secret signing key, derive an x25519 keypair -fn derive_hpke_keypair(sk: &sr25519::Pair) -> Result<(HpkePrivateKey, HpkePublicKey), HpkeError> { - let static_secret = derive_x25519_static_secret(sk); - let x25519_public_key = x25519_dalek::PublicKey::from(&static_secret); - let keypair = - HpkeKeyPair::new(static_secret.to_bytes().to_vec(), x25519_public_key.to_bytes().to_vec()) - .into_keys(); - Ok(keypair) -} /// Encrypted wire message #[derive(Debug, Deserialize, Serialize, Clone)] @@ -124,11 +92,11 @@ impl EncryptedSignedMessage { /// Decrypt an incoming message pub fn decrypt( &self, - sk: &sr25519::Pair, + x25519_sk: &StaticSecret, associated_data: &[u8], ) -> Result { - let (sk, _pk) = derive_hpke_keypair(sk)?; - let plaintext = self.hpke_message.decrypt(&sk, associated_data)?; + let hpke_sk = HpkePrivateKey::new(x25519_sk.to_bytes().to_vec()); + let plaintext = self.hpke_message.decrypt(&hpke_sk, associated_data)?; let signed_message: SignedMessage = serde_json::from_slice(&plaintext).unwrap(); if !signed_message.verify() { return Err(EncryptedSignedMessageErr::BadSignature); @@ -143,15 +111,12 @@ impl EncryptedSignedMessage { message: Vec, recipient: &X25519PublicKey, associated_data: &[u8], - ) -> Result<(Self, sr25519::Pair), EncryptedSignedMessageErr> { - let response_secret_key = { - let mut seed: [u8; 32] = [0; 32]; - OsRng.fill_bytes(seed.as_mut()); - sr25519::Pair::from_seed(&seed) - }; - let response_public_key = derive_x25519_public_key(&response_secret_key)?; + ) -> Result<(Self, StaticSecret), EncryptedSignedMessageErr> { + let response_secret_key = StaticSecret::random_from_rng(OsRng); + let response_public_key = x25519_dalek::PublicKey::from(&response_secret_key); - let signed_message = SignedMessage::new(message, sender, Some(response_public_key)); + let signed_message = + SignedMessage::new(message, sender, Some(response_public_key.to_bytes())); let serialized_signed_message = serde_json::to_vec(&signed_message).unwrap(); Ok(( @@ -223,22 +188,23 @@ mod tests { fn test_encrypt() { let plaintext = b"Its nice to be important but its more important to be nice".to_vec(); - let alice = Keyring::Alice.pair(); - let bob = Keyring::Bob.pair(); - - let bob_x25519_pk = derive_x25519_public_key(&bob).unwrap(); + let alice_sr25519 = Keyring::Alice.pair(); + let bob_x25519_sk = StaticSecret::random_from_rng(OsRng); + let bob_x25519_pk = x25519_dalek::PublicKey::from(&bob_x25519_sk).to_bytes(); let aad = b"Some additional context"; let ciphertext = - EncryptedSignedMessage::new(&alice, plaintext.clone(), &bob_x25519_pk, aad).unwrap(); + EncryptedSignedMessage::new(&alice_sr25519, plaintext.clone(), &bob_x25519_pk, aad) + .unwrap(); - let decrypted_signed_message = ciphertext.decrypt(&bob, aad).unwrap(); + let decrypted_signed_message = ciphertext.decrypt(&bob_x25519_sk, aad).unwrap(); assert_eq!(decrypted_signed_message.message, Bytes(plaintext.clone())); assert_ne!(ciphertext.hpke_message.ciphertext.0, plaintext); - assert!(ciphertext.decrypt(&Keyring::Eve.pair(), aad).is_err()); + let mallory = StaticSecret::random_from_rng(OsRng); + assert!(ciphertext.decrypt(&mallory, aad).is_err()); } #[test] @@ -248,7 +214,8 @@ mod tests { let alice = Keyring::Alice.pair(); let bob = Keyring::Bob.pair(); - let bob_x25519_pk = derive_x25519_public_key(&bob).unwrap(); + let bob_x25519_sk = StaticSecret::random_from_rng(OsRng); + let bob_x25519_pk = x25519_dalek::PublicKey::from(&bob_x25519_sk).to_bytes(); let aad = b"Some additional context"; @@ -260,11 +227,12 @@ mod tests { ) .unwrap(); - let decrypted_signed_message = ciphertext.decrypt(&bob, aad).unwrap(); + let decrypted_signed_message = ciphertext.decrypt(&bob_x25519_sk, aad).unwrap(); assert_eq!(decrypted_signed_message.message, Bytes(plaintext.clone())); assert_ne!(ciphertext.hpke_message.ciphertext.0, plaintext); - assert!(ciphertext.decrypt(&Keyring::Eve.pair(), aad).is_err()); + let mallory = StaticSecret::random_from_rng(OsRng); + assert!(ciphertext.decrypt(&mallory, aad).is_err()); // Now make a response using the public key from the request let ciphertext_response = EncryptedSignedMessage::new( @@ -280,6 +248,7 @@ mod tests { assert_eq!(decrypted_signed_message_response.message, Bytes(plaintext.clone())); assert_ne!(ciphertext_response.hpke_message.ciphertext.0, plaintext); - assert!(ciphertext_response.decrypt(&Keyring::Eve.pair(), aad).is_err()); + let mallory = StaticSecret::random_from_rng(OsRng); + assert!(ciphertext_response.decrypt(&mallory, aad).is_err()); } } diff --git a/crates/protocol/src/user/mod.rs b/crates/protocol/src/user/mod.rs index 2d023092c..5561b0aec 100644 --- a/crates/protocol/src/user/mod.rs +++ b/crates/protocol/src/user/mod.rs @@ -26,6 +26,7 @@ use tokio::spawn; use tokio::sync::{broadcast, mpsc}; #[cfg(feature = "wasm")] use wasm_bindgen_futures::spawn_local as spawn; +use x25519_dalek::StaticSecret; use crate::{ errors::UserRunningProtocolErr, @@ -34,7 +35,6 @@ use crate::{ noise::noise_handshake_initiator, open_ws_connection, ws_to_channels, Broadcaster, SubscribeMessage, ThreadSafeWsConnection, WsChannels, }, - sign_and_encrypt::derive_x25519_static_secret, KeyParams, PartyId, RecoverableSignature, SessionId, SigningSessionInfo, ValidatorInfo, }; @@ -44,6 +44,7 @@ pub async fn user_participates_in_signing_protocol( key_share: &KeyShare, validators_info: Vec, user_signing_keypair: &sr25519::Pair, + user_x25519_private_key: StaticSecret, message_hash: [u8; 32], ) -> Result { let verifying_key = key_share.verifying_key().to_encoded_point(true).as_bytes().to_vec(); @@ -59,6 +60,7 @@ pub async fn user_participates_in_signing_protocol( &session_id, validators_info, user_signing_keypair, + user_x25519_private_key, ) .await?; @@ -83,6 +85,7 @@ pub async fn user_participates_in_signing_protocol( pub async fn user_participates_in_dkg_protocol( validators_info: Vec, user_signing_keypair: &sr25519::Pair, + user_x25519_private_key: StaticSecret, block_number: u32, ) -> Result, UserRunningProtocolErr> { // Make WS connections to the given set of TSS servers @@ -93,6 +96,7 @@ pub async fn user_participates_in_dkg_protocol( &session_id, validators_info, user_signing_keypair, + user_x25519_private_key, ) .await?; @@ -109,13 +113,13 @@ async fn user_connects_to_validators( session_id: &SessionId, validators_info: Vec, user_signing_keypair: &sr25519::Pair, + user_x25519_private_key: StaticSecret, ) -> Result<(Channels, Vec), UserRunningProtocolErr> where F: Fn(String) -> Fut, Fut: Future>, W: ThreadSafeWsConnection, { - let x25519_private_key = derive_x25519_static_secret(user_signing_keypair); // Set up channels for communication between the protocol and the other parties let (tx, _rx) = broadcast::channel(1000); let (tx_to_others, rx_to_others) = mpsc::channel(1000); @@ -138,7 +142,7 @@ where let mut encrypted_connection = noise_handshake_initiator( ws_stream, - &x25519_private_key, + &user_x25519_private_key, validator_info.x25519_public_key, subscribe_message_vec, ) From 2559081e4c233ab6a5baeed9b1ebf15ddfa6e75a Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 5 Apr 2024 15:44:14 +0200 Subject: [PATCH 04/35] Update tss for changes to sign_and_encrypt api --- .../src/helpers/launch.rs | 11 ++--- .../src/helpers/signing.rs | 10 ++-- .../src/helpers/user.rs | 7 +-- .../src/helpers/validator.rs | 48 +++++++++++-------- crates/threshold-signature-server/src/lib.rs | 5 +- .../src/signing_client/api.rs | 13 +++-- .../src/signing_client/protocol_transport.rs | 8 ++-- .../src/user/api.rs | 17 ++++--- .../src/validation/mod.rs | 3 +- .../src/validator/api.rs | 9 ++-- 10 files changed, 73 insertions(+), 58 deletions(-) diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs index 6e3d76d8e..97af324f9 100644 --- a/crates/threshold-signature-server/src/helpers/launch.rs +++ b/crates/threshold-signature-server/src/helpers/launch.rs @@ -30,10 +30,7 @@ use subxt::ext::sp_core::{ sr25519, Pair, }; -use crate::{ - helpers::validator::{get_signer, get_x25519_keypair}, - validation::new_mnemonic, -}; +use crate::{helpers::validator::get_signer_and_x25519_secret, validation::new_mnemonic}; pub const DEFAULT_MNEMONIC: &str = "alarm mutual concert decrease hurry invest culture survey diagram crash snap click"; @@ -203,9 +200,9 @@ pub async fn setup_mnemonic( .await .expect("failed to update mnemonic"); - let pair = get_signer(&kv).await.expect("Cannot derive signing keypair"); - let (static_secret, x25519_public_key) = - get_x25519_keypair(&kv).await.expect("Cannot derive encryption keypair"); + let (pair, static_secret) = + get_signer_and_x25519_secret(&kv).await.expect("Cannot derive keypairs"); + let x25519_public_key = x25519_dalek::PublicKey::from(&static_secret).to_bytes(); let ss_reservation = kv .kv() diff --git a/crates/threshold-signature-server/src/helpers/signing.rs b/crates/threshold-signature-server/src/helpers/signing.rs index 81413f6e4..34c516155 100644 --- a/crates/threshold-signature-server/src/helpers/signing.rs +++ b/crates/threshold-signature-server/src/helpers/signing.rs @@ -24,7 +24,7 @@ use tokio::time::timeout; use crate::{ chain_api::EntropyConfig, - get_signer, + get_signer_and_x25519_secret, sign_init::SignInit, signing_client::{ protocol_execution::{Channels, ThresholdSigningService}, @@ -32,7 +32,6 @@ use crate::{ Listener, ProtocolErr, }, user::api::{increment_or_wipe_request_limit, UserSignatureRequest}, - validation::derive_x25519_static_secret, AppState, }; @@ -53,12 +52,11 @@ pub async fn do_signing( let info = SignInit::new(user_signature_request.clone(), signing_session_info.clone()); let signing_service = ThresholdSigningService::new(state, kv_manager); - let pair_signer = - get_signer(kv_manager).await.map_err(|e| ProtocolErr::UserError(e.to_string()))?; + let (pair_signer, x25519_secret_key) = get_signer_and_x25519_secret(kv_manager) + .await + .map_err(|e| ProtocolErr::UserError(e.to_string()))?; let signer = pair_signer.signer(); - let x25519_secret_key = derive_x25519_static_secret(signer); - let account_id = AccountId32(signer.public().0); // set up context for signing protocol execution diff --git a/crates/threshold-signature-server/src/helpers/user.rs b/crates/threshold-signature-server/src/helpers/user.rs index d8f2c5162..3e480b645 100644 --- a/crates/threshold-signature-server/src/helpers/user.rs +++ b/crates/threshold-signature-server/src/helpers/user.rs @@ -31,6 +31,7 @@ use sp_core::{sr25519, Pair}; use subxt::{backend::legacy::LegacyRpcMethods, tx::PairSigner, utils::AccountId32, OnlineClient}; use synedrion::KeyShare; use tokio::time::timeout; +use x25519_dalek::StaticSecret; use crate::{ chain_api::{ @@ -39,12 +40,13 @@ use crate::{ helpers::substrate::{get_program, query_chain}, signing_client::{protocol_transport::open_protocol_connections, Listener, ListenerState}, user::{api::UserRegistrationInfo, errors::UserErr}, - validation::{derive_x25519_static_secret, EncryptedSignedMessage}, + validation::EncryptedSignedMessage, }; /// complete the dkg process for a new user pub async fn do_dkg( validators_info: &Vec, signer: &PairSigner, + x25519_secret_key: &StaticSecret, state: &ListenerState, sig_request_account: AccountId32, key_visibility: KeyVisibility, @@ -89,13 +91,12 @@ pub async fn do_dkg( .map_err(|_| UserErr::SessionError("Error getting lock".to_string()))? .insert(session_id.clone(), listener); - let x25519_secret_key = derive_x25519_static_secret(signer.signer()); open_protocol_connections( &converted_validator_info, &session_id, signer.signer(), state, - &x25519_secret_key, + x25519_secret_key, ) .await?; let channels = { diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs index 7ddeed64f..4555d69f0 100644 --- a/crates/threshold-signature-server/src/helpers/validator.rs +++ b/crates/threshold-signature-server/src/helpers/validator.rs @@ -22,21 +22,13 @@ use subxt::{ ext::sp_core::{sr25519, Pair}, tx::PairSigner, }; -use x25519_dalek::{PublicKey, StaticSecret}; +use x25519_dalek::StaticSecret; use zeroize::Zeroize; use crate::{chain_api::EntropyConfig, user::UserErr}; -/// Get the key derivation struct to derive secret keys from a mnemonic stored in the KVDB -async fn get_hkdf(kv: &KvManager) -> Result, UserErr> { - let _ = kv.kv().exists("MNEMONIC").await?; - let raw_m = kv.kv().get("MNEMONIC").await?; - let secret = core::str::from_utf8(&raw_m)?; - let mnemonic = Mnemonic::parse_in_normalized(Language::English, secret) - .map_err(|e| UserErr::Mnemonic(e.to_string()))?; - - Ok(Hkdf::::new(None, &mnemonic.to_seed(""))) -} +const KDF_SR25519: &[u8] = b"ssr25519-threshold-account"; +const KDF_X25519: &[u8] = b"X25519-keypair"; /// Returns PairSigner for this nodes threshold server. /// The PairSigner is stored as an encrypted mnemonic in the kvdb and @@ -47,21 +39,39 @@ pub async fn get_signer( let hkdf = get_hkdf(kv).await?; let mut sr25519_seed = [0u8; 64]; - hkdf.expand(b"sr25519-threshold-account", &mut sr25519_seed) - .expect("Cannot get 64 byte output from sha256"); + hkdf.expand(KDF_SR25519, &mut sr25519_seed).expect("Cannot get 64 byte output from sha256"); let pair = sr25519::Pair::from_seed_slice(&sr25519_seed)?; sr25519_seed.zeroize(); Ok(PairSigner::::new(pair)) } -/// Get the x25519 encryption keypair for this threshold server -pub async fn get_x25519_keypair(kv: &KvManager) -> Result<(StaticSecret, [u8; 32]), UserErr> { +/// Get the PairSigner as above, and also the x25519 encryption keypair for +/// this threshold server +pub async fn get_signer_and_x25519_secret( + kv: &KvManager, +) -> Result<(PairSigner, StaticSecret), UserErr> { let hkdf = get_hkdf(kv).await?; let mut secret = [0u8; 32]; - hkdf.expand(b"X25519-keypair", &mut secret).expect("Cannot get 32 byte output from sha256"); + hkdf.expand(KDF_X25519, &mut secret).expect("Cannot get 32 byte output from sha256"); let static_secret = StaticSecret::from(secret); - // TODO zeroize seed - let public_key = PublicKey::from(&static_secret).to_bytes(); - Ok((static_secret, public_key)) + secret.zeroize(); + + let mut sr25519_seed = [0u8; 64]; + hkdf.expand(KDF_SR25519, &mut sr25519_seed).expect("Cannot get 64 byte output from sha256"); + let pair = sr25519::Pair::from_seed_slice(&sr25519_seed)?; + sr25519_seed.zeroize(); + + Ok((PairSigner::::new(pair), static_secret)) +} + +/// Get the key derivation struct to derive secret keys from a mnemonic stored in the KVDB +async fn get_hkdf(kv: &KvManager) -> Result, UserErr> { + let _ = kv.kv().exists("MNEMONIC").await?; + let raw_m = kv.kv().get("MNEMONIC").await?; + let secret = core::str::from_utf8(&raw_m)?; + let mnemonic = Mnemonic::parse_in_normalized(Language::English, secret) + .map_err(|e| UserErr::Mnemonic(e.to_string()))?; + + Ok(Hkdf::::new(None, &mnemonic.to_seed(""))) } diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs index 8fcc55699..2328c84c7 100644 --- a/crates/threshold-signature-server/src/lib.rs +++ b/crates/threshold-signature-server/src/lib.rs @@ -159,7 +159,10 @@ use crate::{ validator::api::sync_kvdb, }; pub use crate::{ - helpers::{launch, validator::get_signer}, + helpers::{ + launch, + validator::{get_signer, get_signer_and_x25519_secret}, + }, validator::api::sync_validator, }; diff --git a/crates/threshold-signature-server/src/signing_client/api.rs b/crates/threshold-signature-server/src/signing_client/api.rs index c091833e7..ac498a8a7 100644 --- a/crates/threshold-signature-server/src/signing_client/api.rs +++ b/crates/threshold-signature-server/src/signing_client/api.rs @@ -47,6 +47,7 @@ use subxt::{ }; use synedrion::KeyShare; use tokio::time::timeout; +use x25519_dalek::StaticSecret; use crate::{ chain_api::{ @@ -60,14 +61,13 @@ use crate::{ return_all_addresses_of_subgroup, }, user::{check_in_registration_group, send_key}, - validator::get_signer, + validator::get_signer_and_x25519_secret, }, signing_client::{ protocol_transport::{handle_socket, open_protocol_connections}, Listener, ListenerState, ProtocolErr, }, user::api::UserRegistrationInfo, - validation::derive_x25519_static_secret, AppState, }; @@ -88,8 +88,10 @@ pub async fn proactive_refresh( let ocw_data = OcwMessageProactiveRefresh::decode(&mut encoded_data.as_ref())?; let api = get_api(&app_state.configuration.endpoint).await?; let rpc = get_rpc(&app_state.configuration.endpoint).await?; - let signer = - get_signer(&app_state.kv_store).await.map_err(|e| ProtocolErr::UserError(e.to_string()))?; + let (signer, x25519_secret_key) = get_signer_and_x25519_secret(&app_state.kv_store) + .await + .map_err(|e| ProtocolErr::UserError(e.to_string()))?; + check_in_registration_group(&ocw_data.validators_info, signer.account_id()) .map_err(|e| ProtocolErr::UserError(e.to_string()))?; validate_proactive_refresh(&api, &rpc, &app_state.kv_store, &ocw_data).await?; @@ -128,6 +130,7 @@ pub async fn proactive_refresh( let new_key_share = do_proactive_refresh( &ocw_data.validators_info, &signer, + &x25519_secret_key, &app_state.listener_state, encoded_key, deserialized_old_key, @@ -187,6 +190,7 @@ async fn handle_socket_result(socket: WebSocket, app_state: AppState) { pub async fn do_proactive_refresh( validators_info: &Vec, signer: &PairSigner, + x25519_secret_key: &StaticSecret, state: &ListenerState, verifying_key: Vec, old_key: KeyShare, @@ -223,7 +227,6 @@ pub async fn do_proactive_refresh( .lock() .map_err(|_| ProtocolErr::SessionError("Error getting lock".to_string()))? .insert(session_id.clone(), listener); - let x25519_secret_key = derive_x25519_static_secret(signer.signer()); open_protocol_connections( &converted_validator_info, diff --git a/crates/threshold-signature-server/src/signing_client/protocol_transport.rs b/crates/threshold-signature-server/src/signing_client/protocol_transport.rs index 6b2940381..a6a047db5 100644 --- a/crates/threshold-signature-server/src/signing_client/protocol_transport.rs +++ b/crates/threshold-signature-server/src/signing_client/protocol_transport.rs @@ -32,9 +32,8 @@ use tokio_tungstenite::connect_async; use super::ProtocolErr; use crate::{ - get_signer, + get_signer_and_x25519_secret, signing_client::{SessionId, SubscribeErr}, - validation::derive_x25519_static_secret, AppState, ListenerState, SUBSCRIBE_TIMEOUT_SECONDS, }; @@ -111,8 +110,9 @@ pub async fn open_protocol_connections( /// Handle an incoming websocket connection pub async fn handle_socket(socket: WebSocket, app_state: AppState) -> Result<(), WsError> { - let signer = get_signer(&app_state.kv_store).await.map_err(|_| WsError::SignerFromAppState)?; - let x25519_secret_key = derive_x25519_static_secret(signer.signer()); + let (_signer, x25519_secret_key) = get_signer_and_x25519_secret(&app_state.kv_store) + .await + .map_err(|_| WsError::SignerFromAppState)?; let (mut encrypted_connection, serialized_signed_message) = noise_handshake_responder(socket, &x25519_secret_key) diff --git a/crates/threshold-signature-server/src/user/api.rs b/crates/threshold-signature-server/src/user/api.rs index 5b0c0160e..d0e6ff918 100644 --- a/crates/threshold-signature-server/src/user/api.rs +++ b/crates/threshold-signature-server/src/user/api.rs @@ -55,6 +55,7 @@ use subxt::{ }; use synedrion::KeyShare; use tracing::instrument; +use x25519_dalek::StaticSecret; use zeroize::Zeroize; use super::{ParsedUserInputPartyInfo, ProgramError, UserErr, UserInputPartyInfo}; @@ -72,7 +73,7 @@ use crate::{ return_all_addresses_of_subgroup, submit_transaction, }, user::{check_in_registration_group, compute_hash, do_dkg, send_key}, - validator::get_signer, + validator::{get_signer, get_signer_and_x25519_secret}, }, signing_client::{ListenerState, ProtocolErr}, validation::{check_stale, EncryptedSignedMessage}, @@ -128,12 +129,12 @@ pub async fn sign_tx( Json(encrypted_msg): Json, ) -> Result<(StatusCode, StreamBody>>), UserErr> { - let signer = get_signer(&app_state.kv_store).await?; + let (signer, x25519_secret) = get_signer_and_x25519_secret(&app_state.kv_store).await?; let api = get_api(&app_state.configuration.endpoint).await?; let rpc = get_rpc(&app_state.configuration.endpoint).await?; - let signed_message = encrypted_msg.decrypt(signer.signer(), &[])?; + let signed_message = encrypted_msg.decrypt(&x25519_secret, &[])?; let request_author = SubxtAccountId32(*signed_message.account_id().as_ref()); tracing::Span::current().record("request_author", signed_message.account_id().to_string()); @@ -270,13 +271,13 @@ pub async fn new_user( } let api = get_api(&app_state.configuration.endpoint).await?; let rpc = get_rpc(&app_state.configuration.endpoint).await?; - let signer = get_signer(&app_state.kv_store).await?; + let (signer, x25519_secret_key) = get_signer_and_x25519_secret(&app_state.kv_store).await?; check_in_registration_group(&data.validators_info, signer.account_id())?; validate_new_user(&data, &api, &rpc, &app_state.kv_store).await?; // Do the DKG protocol in another task, so we can already respond tokio::spawn(async move { - if let Err(err) = setup_dkg(api, &rpc, signer, data, app_state).await { + if let Err(err) = setup_dkg(api, &rpc, signer, &x25519_secret_key, data, app_state).await { // TODO here we would check the error and if it relates to a misbehaving node, // use the slashing mechanism tracing::error!("User registration failed {:?}", err); @@ -298,6 +299,7 @@ async fn setup_dkg( api: OnlineClient, rpc: &LegacyRpcMethods, signer: PairSigner, + x25519_secret_key: &StaticSecret, data: OcwMessageDkg, app_state: AppState, ) -> Result<(), UserErr> { @@ -326,6 +328,7 @@ async fn setup_dkg( let key_share = do_dkg( &data.validators_info, &signer, + x25519_secret_key, &app_state.listener_state, sig_request_address.clone(), *user_details.key_visibility, @@ -379,8 +382,8 @@ pub async fn receive_key( State(app_state): State, Json(encrypted_message): Json, ) -> Result { - let signer = get_signer(&app_state.kv_store).await?; - let signed_message = encrypted_message.decrypt(signer.signer(), &[])?; + let (signer, x25519_secret_key) = get_signer_and_x25519_secret(&app_state.kv_store).await?; + let signed_message = encrypted_message.decrypt(&x25519_secret_key, &[])?; let signing_address = signed_message.account_id(); tracing::Span::current().record("signing_address", signing_address.to_string()); diff --git a/crates/threshold-signature-server/src/validation/mod.rs b/crates/threshold-signature-server/src/validation/mod.rs index 515712922..1416a662c 100644 --- a/crates/threshold-signature-server/src/validation/mod.rs +++ b/crates/threshold-signature-server/src/validation/mod.rs @@ -17,8 +17,7 @@ use std::time::{Duration, SystemTime}; use bip39::Mnemonic; pub use entropy_protocol::sign_and_encrypt::{ - derive_x25519_public_key, derive_x25519_static_secret, EncryptedSignedMessage, - EncryptedSignedMessageErr, SignedMessage, + EncryptedSignedMessage, EncryptedSignedMessageErr, SignedMessage, }; use rand_core::{OsRng, RngCore}; use subxt::ext::sp_core::{sr25519, Pair}; diff --git a/crates/threshold-signature-server/src/validator/api.rs b/crates/threshold-signature-server/src/validator/api.rs index b2cf173f8..57ce5fc87 100644 --- a/crates/threshold-signature-server/src/validator/api.rs +++ b/crates/threshold-signature-server/src/validator/api.rs @@ -30,7 +30,7 @@ use crate::{ entropy::{self, runtime_types::pallet_staking_extension::pallet::ServerInfo}, get_api, get_rpc, EntropyConfig, }, - get_signer, + get_signer, get_signer_and_x25519_secret, helpers::{ launch::FORBIDDEN_KEYS, substrate::{get_stash_address, get_subgroup, query_chain, submit_transaction}, @@ -130,8 +130,8 @@ pub async fn sync_kvdb( let api = get_api(&app_state.configuration.endpoint).await?; let rpc = get_rpc(&app_state.configuration.endpoint).await?; - let signer = get_signer(&app_state.kv_store).await?; - let decrypted_message = encrypted_msg.decrypt(signer.signer(), &[])?; + let (signer, x25519_secret_key) = get_signer_and_x25519_secret(&app_state.kv_store).await?; + let decrypted_message = encrypted_msg.decrypt(&x25519_secret_key, &[])?; tracing::Span::current().record("signing_address", decrypted_message.account_id().to_string()); let sender_account_id = SubxtAccountId32(decrypted_message.sender.into()); @@ -238,6 +238,7 @@ pub async fn get_and_store_values( signer: &PairSigner, ) -> Result<(), ValidatorErr> { let url = String::from_utf8(recip_server_info.endpoint)?; + let (_, x25519_secret) = get_signer_and_x25519_secret(&kv).await?; let mut keys_stored = 0; while keys_stored < all_keys.len() { let mut keys_to_send_slice = batch_size + keys_stored; @@ -280,7 +281,7 @@ pub async fn get_and_store_values( let reservation = kv.kv().reserve_key(remaining_keys[i].clone()).await?; let key = { let signed_message = encrypted_key - .decrypt(signer.signer(), &[]) + .decrypt(&x25519_secret, &[]) .map_err(|e| ValidatorErr::Decryption(e.to_string()))?; if signed_message.sender.0 != recip_server_info.tss_account.0 { return Err(ValidatorErr::Authentication); From 23624d8a3de771d3c059d881985976110540cfce Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 5 Apr 2024 15:47:43 +0200 Subject: [PATCH 05/35] Clippy --- crates/threshold-signature-server/src/helpers/launch.rs | 2 +- crates/threshold-signature-server/src/signing_client/api.rs | 2 +- crates/threshold-signature-server/src/validator/api.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs index 97af324f9..e9c1699b0 100644 --- a/crates/threshold-signature-server/src/helpers/launch.rs +++ b/crates/threshold-signature-server/src/helpers/launch.rs @@ -201,7 +201,7 @@ pub async fn setup_mnemonic( .expect("failed to update mnemonic"); let (pair, static_secret) = - get_signer_and_x25519_secret(&kv).await.expect("Cannot derive keypairs"); + get_signer_and_x25519_secret(kv).await.expect("Cannot derive keypairs"); let x25519_public_key = x25519_dalek::PublicKey::from(&static_secret).to_bytes(); let ss_reservation = kv diff --git a/crates/threshold-signature-server/src/signing_client/api.rs b/crates/threshold-signature-server/src/signing_client/api.rs index ac498a8a7..cccdf335a 100644 --- a/crates/threshold-signature-server/src/signing_client/api.rs +++ b/crates/threshold-signature-server/src/signing_client/api.rs @@ -233,7 +233,7 @@ pub async fn do_proactive_refresh( &session_id, signer.signer(), state, - &x25519_secret_key, + x25519_secret_key, ) .await?; let channels = { diff --git a/crates/threshold-signature-server/src/validator/api.rs b/crates/threshold-signature-server/src/validator/api.rs index 57ce5fc87..c8647e8b1 100644 --- a/crates/threshold-signature-server/src/validator/api.rs +++ b/crates/threshold-signature-server/src/validator/api.rs @@ -238,7 +238,7 @@ pub async fn get_and_store_values( signer: &PairSigner, ) -> Result<(), ValidatorErr> { let url = String::from_utf8(recip_server_info.endpoint)?; - let (_, x25519_secret) = get_signer_and_x25519_secret(&kv).await?; + let (_, x25519_secret) = get_signer_and_x25519_secret(kv).await?; let mut keys_stored = 0; while keys_stored < all_keys.len() { let mut keys_to_send_slice = batch_size + keys_stored; From 17ff32318ce4b0d9968f56381e8145d24e5a41ab Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 5 Apr 2024 16:05:24 +0200 Subject: [PATCH 06/35] Update test client --- crates/testing-utils/src/test_client/mod.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/crates/testing-utils/src/test_client/mod.rs b/crates/testing-utils/src/test_client/mod.rs index 50a9266c9..860fc08c3 100644 --- a/crates/testing-utils/src/test_client/mod.rs +++ b/crates/testing-utils/src/test_client/mod.rs @@ -15,10 +15,7 @@ //! Simple test client pub use crate::chain_api::{get_api, get_rpc}; -pub use entropy_protocol::{ - sign_and_encrypt::{derive_x25519_static_secret, EncryptedSignedMessage}, - KeyParams, -}; +pub use entropy_protocol::{sign_and_encrypt::EncryptedSignedMessage, KeyParams}; use entropy_shared::HashingAlgorithm; pub use entropy_shared::{KeyVisibility, SIGNING_PARTY_SIZE}; pub use synedrion::KeyShare; @@ -54,6 +51,7 @@ use subxt::{ Config, OnlineClient, }; use synedrion::k256::ecdsa::{RecoveryId, Signature as k256Signature, VerifyingKey}; +use x25519_dalek::StaticSecret; /// Register an account. /// @@ -76,6 +74,7 @@ pub async fn register( program_account: SubxtAccountId32, key_visibility: KeyVisibility, programs_data: BoundedVec, + x25519_secret_key: Option, ) -> anyhow::Result<(RegisteredInfo, Option>)> { // Send register transaction put_register_request_on_chain( @@ -91,6 +90,10 @@ pub async fn register( // If registering with private key visibility, participate in the DKG protocol let keyshare_option = match key_visibility { KeyVisibility::Private(_x25519_pk) => { + let x25519_secret_key = x25519_secret_key + .ok_or(anyhow!("In private mode, an x25519 secret key must be given"))?; + // TODO ensure!(the public key matches that from key_visibility) + let block_number = rpc .chain_get_header(None) .await? @@ -103,6 +106,7 @@ pub async fn register( user_participates_in_dkg_protocol( validators_info, &signature_request_keypair, + x25519_secret_key, block_number, ) .await?, @@ -151,7 +155,7 @@ pub async fn sign( user_keypair: sr25519::Pair, signature_verifying_key: Vec, message: Vec, - private: Option>, + private: Option<(KeyShare, StaticSecret)>, auxilary_data: Option>, ) -> anyhow::Result { let message_hash = Hasher::keccak(&message); @@ -197,13 +201,14 @@ pub async fn sign( .collect::>(); // If we have a keyshare, connect to TSS servers - let results = if let Some(keyshare) = private { + let results = if let Some((keyshare, x25519_secret_key)) = private { let (validator_results, _own_result) = future::join( future::try_join_all(submit_transaction_requests), user_participates_in_signing_protocol( &keyshare, validators_info_clone, &user_keypair, + x25519_secret_key, message_hash, ), ) From 86413e43064b58b27de559197d573ddc9ade1f5e Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 8 Apr 2024 11:45:20 +0200 Subject: [PATCH 07/35] Update test cli --- crates/test-cli/src/main.rs | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/crates/test-cli/src/main.rs b/crates/test-cli/src/main.rs index 24693aa33..bf4b580ab 100644 --- a/crates/test-cli/src/main.rs +++ b/crates/test-cli/src/main.rs @@ -33,17 +33,18 @@ use entropy_testing_utils::{ }, constants::TEST_PROGRAM_WASM_BYTECODE, test_client::{ - derive_x25519_static_secret, get_accounts, get_api, get_programs, get_rpc, register, sign, - store_program, update_programs, KeyParams, KeyShare, KeyVisibility, + get_accounts, get_api, get_programs, get_rpc, register, sign, store_program, + update_programs, KeyParams, KeyShare, KeyVisibility, }, }; -use sp_core::{sr25519, Hasher, Pair}; +use sp_core::{sr25519, DeriveJunction, Hasher, Pair}; use sp_runtime::traits::BlakeTwo256; use subxt::{ backend::legacy::LegacyRpcMethods, utils::{AccountId32 as SubxtAccountId32, H256}, OnlineClient, }; +use x25519_dalek::StaticSecret; #[derive(Parser, Debug, Clone)] #[clap( @@ -209,13 +210,13 @@ async fn run_command() -> anyhow::Result { let program_account = SubxtAccountId32(program_keypair.public().0); println!("Program account: {}", program_keypair.public()); - let key_visibility_converted = match key_visibility { + let (key_visibility_converted, x25519_secret) = match key_visibility { Visibility::Private => { let x25519_secret = derive_x25519_static_secret(&signature_request_keypair); let x25519_public = x25519_dalek::PublicKey::from(&x25519_secret); - KeyVisibility::Private(x25519_public.to_bytes()) + (KeyVisibility::Private(x25519_public.to_bytes()), Some(x25519_secret)) }, - Visibility::Public => KeyVisibility::Public, + Visibility::Public => (KeyVisibility::Public, None), }; let mut programs_info = vec![]; @@ -232,6 +233,7 @@ async fn run_command() -> anyhow::Result { program_account, key_visibility_converted, BoundedVec(programs_info), + x25519_secret, ) .await?; @@ -252,13 +254,18 @@ async fn run_command() -> anyhow::Result { // If we have a keyshare file for this account, get it let private_keyshare = KeyShareFile::new(user_keypair.public()).read().ok(); + let private_details = private_keyshare.map(|keyshare| { + let x25519_secret = derive_x25519_static_secret(&user_keypair); + (keyshare, x25519_secret) + }); + let recoverable_signature = sign( &api, &rpc, user_keypair, signature_verifying_key, message.as_bytes().to_vec(), - private_keyshare, + private_details, auxilary_data, ) .await?; @@ -517,3 +524,14 @@ impl Program { } } } + +/// Derive a x25519 secret from a sr25519 pair. In production we should not do this, +/// but for this test-cli which anyway uses insecure keypairs it is convenient +fn derive_x25519_static_secret(sr25519_pair: &sr25519::Pair) -> StaticSecret { + let (derived_sr25519_pair, _) = sr25519_pair + .derive([DeriveJunction::hard(b"x25519")].into_iter(), None) + .expect("Cannot derive keypair"); + let mut secret: [u8; 32] = [0; 32]; + secret.copy_from_slice(&derived_sr25519_pair.to_raw_vec()); + secret.into() +} From 48fcc77a63834e27904aeef55462a0b9d90a0009 Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 8 Apr 2024 11:46:30 +0200 Subject: [PATCH 08/35] Update tests and make a helper for getting x25519 keys directly from mnemonic --- crates/testing-utils/src/constants.rs | 9 +++++++ .../src/helpers/validator.rs | 23 ++++++++++++++++ .../src/user/tests.rs | 26 ++++++++++--------- .../src/validator/tests.rs | 16 +++++++----- .../threshold-signature-server/tests/sign.rs | 11 +++++--- 5 files changed, 63 insertions(+), 22 deletions(-) diff --git a/crates/testing-utils/src/constants.rs b/crates/testing-utils/src/constants.rs index e5c0ad62d..b57eb2683 100644 --- a/crates/testing-utils/src/constants.rs +++ b/crates/testing-utils/src/constants.rs @@ -43,6 +43,15 @@ lazy_static! { ]; } +pub const EVE_X25519_SECRET_KEY: [u8; 32] = [ + 58, 47, 10, 154, 181, 71, 222, 205, 42, 186, 181, 1, 55, 107, 46, 200, 226, 62, 42, 137, 142, + 3, 101, 208, 129, 168, 22, 236, 116, 159, 8, 55, +]; +pub const FERDIE_X25519_SECRET_KEY: [u8; 32] = [ + 5, 221, 127, 62, 254, 131, 37, 194, 88, 126, 130, 15, 97, 249, 170, 40, 201, 135, 77, 213, 55, + 87, 243, 127, 175, 77, 251, 75, 157, 119, 41, 180, +]; + /// The following constants are values used for integration testing specific to the /// `example_barebones_with_auxilary.wasm` from the `programs` repo. pub const TEST_PROGRAM_WASM_BYTECODE: &[u8] = diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs index 4555d69f0..2fd8acedc 100644 --- a/crates/threshold-signature-server/src/helpers/validator.rs +++ b/crates/threshold-signature-server/src/helpers/validator.rs @@ -75,3 +75,26 @@ async fn get_hkdf(kv: &KvManager) -> Result, UserErr> { Ok(Hkdf::::new(None, &mnemonic.to_seed(""))) } + +/// For testing where we sometimes don't have access to the kvdb, derive directly from the mnemnic +#[cfg(test)] +pub fn get_signer_and_x25519_secret_from_mnemonic( + mnemonic: &str, +) -> Result<(PairSigner, StaticSecret), UserErr> { + let mnemonic = Mnemonic::parse_in_normalized(Language::English, mnemonic) + .map_err(|e| UserErr::Mnemonic(e.to_string()))?; + + let hkdf = Hkdf::::new(None, &mnemonic.to_seed("")); + + let mut secret = [0u8; 32]; + hkdf.expand(KDF_X25519, &mut secret).expect("Cannot get 32 byte output from sha256"); + let static_secret = StaticSecret::from(secret); + secret.zeroize(); + + let mut sr25519_seed = [0u8; 64]; + hkdf.expand(KDF_SR25519, &mut sr25519_seed).expect("Cannot get 64 byte output from sha256"); + let pair = sr25519::Pair::from_seed_slice(&sr25519_seed)?; + sr25519_seed.zeroize(); + + Ok((PairSigner::::new(pair), static_secret)) +} diff --git a/crates/threshold-signature-server/src/user/tests.rs b/crates/threshold-signature-server/src/user/tests.rs index d494f9105..e2e91597c 100644 --- a/crates/threshold-signature-server/src/user/tests.rs +++ b/crates/threshold-signature-server/src/user/tests.rs @@ -45,9 +45,9 @@ use entropy_testing_utils::{ }, constants::{ ALICE_STASH_ADDRESS, AUXILARY_DATA_SHOULD_FAIL, AUXILARY_DATA_SHOULD_SUCCEED, - PREIMAGE_SHOULD_FAIL, PREIMAGE_SHOULD_SUCCEED, TEST_BASIC_TRANSACTION, - TEST_INFINITE_LOOP_BYTECODE, TEST_PROGRAM_CUSTOM_HASH, TEST_PROGRAM_WASM_BYTECODE, - TSS_ACCOUNTS, X25519_PUBLIC_KEYS, + EVE_X25519_SECRET_KEY, FERDIE_X25519_SECRET_KEY, PREIMAGE_SHOULD_FAIL, + PREIMAGE_SHOULD_SUCCEED, TEST_BASIC_TRANSACTION, TEST_INFINITE_LOOP_BYTECODE, + TEST_PROGRAM_CUSTOM_HASH, TEST_PROGRAM_WASM_BYTECODE, TSS_ACCOUNTS, X25519_PUBLIC_KEYS, }, substrate_context::{ test_context_stationary, test_node_process_testing_state, testing_context, @@ -122,9 +122,7 @@ use crate::{ }, UserErr, }, - validation::{ - derive_x25519_static_secret, mnemonic_to_pair, new_mnemonic, EncryptedSignedMessage, - }, + validation::{mnemonic_to_pair, new_mnemonic, EncryptedSignedMessage}, validator::api::get_random_server_info, }; @@ -277,7 +275,6 @@ async fn test_sign_tx_no_chain() { let (ws_stream, _response) = connect_async(ws_endpoint).await.unwrap(); let ferdie_pair = AccountKeyring::Ferdie.pair(); - let ferdie_x25519_sk = derive_x25519_static_secret(&ferdie_pair); // create a SubscribeMessage from a party who is not in the signing commitee let subscribe_message_vec = @@ -286,7 +283,7 @@ async fn test_sign_tx_no_chain() { // Attempt a noise handshake including the subscribe message in the payload let mut encrypted_connection = noise_handshake_initiator( ws_stream, - &ferdie_x25519_sk, + &FERDIE_X25519_SECRET_KEY.into(), validator_ip_and_key.1, subscribe_message_vec, ) @@ -1171,6 +1168,7 @@ async fn test_sign_tx_user_participates() { &users_keyshare_option.clone().unwrap(), validators_info.clone(), &one.pair(), + EVE_X25519_SECRET_KEY.into(), message_should_succeed_hash, ), ) @@ -1228,7 +1226,6 @@ async fn test_sign_tx_user_participates() { let (ws_stream, _response) = connect_async(ws_endpoint).await.unwrap(); let ferdie_pair = AccountKeyring::Ferdie.pair(); - let ferdie_x25519_sk = derive_x25519_static_secret(&ferdie_pair); // create a SubscribeMessage from a party who is not in the signing commitee let subscribe_message_vec = @@ -1237,7 +1234,7 @@ async fn test_sign_tx_user_participates() { // Attempt a noise handshake including the subscribe message in the payload let mut encrypted_connection = noise_handshake_initiator( ws_stream, - &ferdie_x25519_sk, + &FERDIE_X25519_SECRET_KEY.into(), validator_ip_and_key.1, subscribe_message_vec, ) @@ -1391,7 +1388,7 @@ async fn test_register_with_private_key_visibility() { let block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number + 1; - let one_x25519_sk = derive_x25519_static_secret(&one.pair()); + let one_x25519_sk = StaticSecret::random_from_rng(rand_core::OsRng); let x25519_public_key = PublicKey::from(&one_x25519_sk).to_bytes(); put_register_request_on_chain( @@ -1439,7 +1436,12 @@ async fn test_register_with_private_key_visibility() { .post("http://127.0.0.1:3002/user/new") .body(onchain_user_request.clone().encode()) .send(), - user_participates_in_dkg_protocol(validators_info.clone(), &one.pair(), block_number), + user_participates_in_dkg_protocol( + validators_info.clone(), + &one.pair(), + one_x25519_sk, + block_number, + ), ) .await; diff --git a/crates/threshold-signature-server/src/validator/tests.rs b/crates/threshold-signature-server/src/validator/tests.rs index 63b6d69be..e8eb215e7 100644 --- a/crates/threshold-signature-server/src/validator/tests.rs +++ b/crates/threshold-signature-server/src/validator/tests.rs @@ -41,11 +41,9 @@ use crate::{ }, substrate::{get_registered_details, get_stash_address, get_subgroup, query_chain}, tests::{create_clients, initialize_test_logger}, + validator::get_signer_and_x25519_secret_from_mnemonic, }, - validation::{ - derive_x25519_public_key, mnemonic_to_pair, new_mnemonic, EncryptedSignedMessage, - TIME_BUFFER, - }, + validation::{mnemonic_to_pair, new_mnemonic, EncryptedSignedMessage, TIME_BUFFER}, validator::errors::ValidatorErr, }; @@ -103,7 +101,11 @@ async fn test_sync_kvdb() { &Mnemonic::parse_in_normalized(Language::English, DEFAULT_BOB_MNEMONIC).unwrap(), ) .unwrap(); - let recip = derive_x25519_public_key(&b_usr_sk).unwrap(); + + let (_, bob_x25519_secret) = + get_signer_and_x25519_secret_from_mnemonic(DEFAULT_BOB_MNEMONIC).unwrap(); + let recip = x25519_dalek::PublicKey::from(&bob_x25519_secret).to_bytes(); + let values = vec![vec![10], vec![11], vec![12]]; let port = 3001; @@ -132,7 +134,9 @@ async fn test_sync_kvdb() { // return no error (status code 200). assert_eq!(result.status(), 200); - let sender = derive_x25519_public_key(&a_usr_sk).unwrap(); + let (_, alice_x25519_secret) = + get_signer_and_x25519_secret_from_mnemonic(DEFAULT_ALICE_MNEMONIC).unwrap(); + let sender = x25519_dalek::PublicKey::from(&alice_x25519_secret).to_bytes(); let enc_keys_failed_decrypt = EncryptedSignedMessage::new(&b_usr_sk, serde_json::to_vec(&keys).unwrap(), &sender, &[]) diff --git a/crates/threshold-signature-server/tests/sign.rs b/crates/threshold-signature-server/tests/sign.rs index 1001b2879..32899be3b 100644 --- a/crates/threshold-signature-server/tests/sign.rs +++ b/crates/threshold-signature-server/tests/sign.rs @@ -106,8 +106,8 @@ async fn integration_test_sign_private() { let substrate_context = test_context_stationary().await; let api = get_api(&substrate_context.node_proc.ws_url).await.unwrap(); let rpc = get_rpc(&substrate_context.node_proc.ws_url).await.unwrap(); - let verifying_key = - keyshare_option.clone().unwrap().verifying_key().to_encoded_point(true).as_bytes().to_vec(); + let keyshare = keyshare_option.unwrap(); + let verifying_key = keyshare.clone().verifying_key().to_encoded_point(true).as_bytes().to_vec(); let program_pointer = test_client::store_program( &api, @@ -132,13 +132,16 @@ async fn integration_test_sign_private() { let message_should_succeed_hash = Hasher::keccak(PREIMAGE_SHOULD_SUCCEED); + // TODO + let x25519_secret: [u8; 32] = [0; 32]; + let recoverable_signature = test_client::sign( &api, &rpc, pre_registered_user.pair(), verifying_key, PREIMAGE_SHOULD_SUCCEED.to_vec(), - keyshare_option.clone(), + Some((keyshare.clone(), x25519_secret.into())), Some(AUXILARY_DATA_SHOULD_SUCCEED.to_vec()), ) .await @@ -150,5 +153,5 @@ async fn integration_test_sign_private() { recoverable_signature.recovery_id, ) .unwrap(); - assert_eq!(keyshare_option.clone().unwrap().verifying_key(), recovery_key_from_sig); + assert_eq!(keyshare.verifying_key(), recovery_key_from_sig); } From ec7d1eb4085346b79fad26ff7ae1170764d714f9 Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 8 Apr 2024 14:22:37 +0200 Subject: [PATCH 09/35] Fix wasm tests --- crates/protocol/nodejs-test/index.js | 4 +-- crates/protocol/src/sign_and_encrypt/wasm.rs | 27 ++++++++++--------- crates/protocol/src/user/wasm.rs | 23 +++++++++++++--- .../src/helpers/validator.rs | 18 ++++++------- .../tests/protocol_wasm.rs | 26 +++++++++++------- 5 files changed, 61 insertions(+), 37 deletions(-) diff --git a/crates/protocol/nodejs-test/index.js b/crates/protocol/nodejs-test/index.js index 31bcd5edc..c16bdc924 100644 --- a/crates/protocol/nodejs-test/index.js +++ b/crates/protocol/nodejs-test/index.js @@ -33,7 +33,7 @@ try { switch (process.argv[2].toLowerCase()) { case 'register': - protocol.runDkgProtocol(input.validators_info, input.user_sig_req_secret_key, input.block_number).then((keyShare) => { + protocol.runDkgProtocol(input.validators_info, input.user_sig_req_secret_key, input.user_x25519_secret_key, input.block_number).then((keyShare) => { console.log(keyShare.toString()) }).catch((err) => { console.error('ERR', err) @@ -41,7 +41,7 @@ switch (process.argv[2].toLowerCase()) { break case 'sign': const keyShare = protocol.KeyShare.fromString(input.key_share) - protocol.runSigningProtocol(keyShare, input.message_hash, input.validators_info, input.user_sig_req_secret_key) + protocol.runSigningProtocol(keyShare, input.message_hash, input.validators_info, input.user_sig_req_secret_key, input.user_x25519_secret_key) .then((output) => { console.log(output) }).catch((err) => { diff --git a/crates/protocol/src/sign_and_encrypt/wasm.rs b/crates/protocol/src/sign_and_encrypt/wasm.rs index 4985926fa..2bc48fd03 100644 --- a/crates/protocol/src/sign_and_encrypt/wasm.rs +++ b/crates/protocol/src/sign_and_encrypt/wasm.rs @@ -1,10 +1,9 @@ //! Wasm bindings to the [EncryptedSignedMessage] API, as well as some helper functions -use super::{derive_x25519_static_secret, EncryptedSignedMessage}; +use super::EncryptedSignedMessage; use js_sys::Error; use schnorrkel::{MiniSecretKey, SecretKey}; use sp_core::sr25519; use wasm_bindgen::prelude::*; -use x25519_dalek::PublicKey; const HEX_PREFIX: [u8; 2] = [48, 120]; @@ -29,14 +28,14 @@ impl Hpke { Ok(sk.to_bytes().to_vec()) } - /// Derives a public DH key from a static DH secret. - /// secret_key must be 64 bytes in length or an error will be returned. - #[wasm_bindgen(js_name = publicKeyFromSecret)] - pub fn public_key_from_secret(secret_key: Vec) -> Result, Error> { - let pair = sr25519_keypair_from_secret_key(secret_key)?; - let x25519_secret = derive_x25519_static_secret(&pair); - Ok(PublicKey::from(&x25519_secret).as_bytes().to_vec()) - } + // /// Derives a public DH key from a static DH secret. + // /// secret_key must be 64 bytes in length or an error will be returned. + // #[wasm_bindgen(js_name = publicKeyFromSecret)] + // pub fn public_key_from_secret(secret_key: Vec) -> Result, Error> { + // let pair = sr25519_keypair_from_secret_key(secret_key)?; + // let x25519_secret = derive_x25519_static_secret(&pair); + // Ok(PublicKey::from(&x25519_secret).as_bytes().to_vec()) + // } /// Encrypts, signs, and serializes an `EncryptedSignedMessage` to JSON. #[wasm_bindgen(js_name = encryptAndSign)] @@ -69,10 +68,12 @@ impl Hpke { let encrypted_message: EncryptedSignedMessage = serde_json::from_str(message.as_str()).map_err(|err| Error::new(&err.to_string()))?; - let pair = sr25519_keypair_from_secret_key(secret_key)?; + let secret_key: [u8; 32] = + secret_key.try_into().map_err(|_| Error::new("X25519 secret key must be 32 bytes"))?; - let signed_message = - encrypted_message.decrypt(&pair, &[]).map_err(|err| Error::new(&err.to_string()))?; + let signed_message = encrypted_message + .decrypt(&secret_key.into(), &[]) + .map_err(|err| Error::new(&err.to_string()))?; // TODO here we keep the API as it was before - but really this is bad because there is no // way for the called to check the public key of the signer - we should be returning that as diff --git a/crates/protocol/src/user/wasm.rs b/crates/protocol/src/user/wasm.rs index ab6a7c0dc..2f28bff8d 100644 --- a/crates/protocol/src/user/wasm.rs +++ b/crates/protocol/src/user/wasm.rs @@ -28,16 +28,25 @@ use crate::KeyParams; pub async fn run_dkg_protocol( validators_info_js: ValidatorInfoArray, user_signing_secret_key: Vec, + user_x25519_secret_key: Vec, block_number: u32, ) -> Result { let validators_info = parse_validator_info(validators_info_js)?; let user_signing_keypair = sr25519_keypair_from_secret_key(user_signing_secret_key)?; - let key_share = - user_participates_in_dkg_protocol(validators_info, &user_signing_keypair, block_number) - .await - .map_err(|err| Error::new(&format!("{}", err)))?; + let x25519_secret: [u8; 32] = user_x25519_secret_key + .try_into() + .map_err(|_| Error::new("x25519 secret key must be 32 bytes"))?; + + let key_share = user_participates_in_dkg_protocol( + validators_info, + &user_signing_keypair, + x25519_secret.into(), + block_number, + ) + .await + .map_err(|err| Error::new(&format!("{}", err)))?; Ok(KeyShare(key_share)) } @@ -50,6 +59,7 @@ pub async fn run_signing_protocol( message_hash: Vec, validators_info_js: ValidatorInfoArray, user_signing_secret_key: Vec, + user_x25519_secret_key: Vec, ) -> Result { let validators_info = parse_validator_info(validators_info_js)?; @@ -58,10 +68,15 @@ pub async fn run_signing_protocol( let user_signing_keypair = sr25519_keypair_from_secret_key(user_signing_secret_key)?; + let x25519_secret: [u8; 32] = user_x25519_secret_key + .try_into() + .map_err(|_| Error::new("x25519 secret key must be 32 bytes"))?; + let signature = user_participates_in_signing_protocol( &key_share.0, validators_info, &user_signing_keypair, + x25519_secret.into(), message_hash, ) .await diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs index 2fd8acedc..59c0ee741 100644 --- a/crates/threshold-signature-server/src/helpers/validator.rs +++ b/crates/threshold-signature-server/src/helpers/validator.rs @@ -38,9 +38,9 @@ pub async fn get_signer( ) -> Result, UserErr> { let hkdf = get_hkdf(kv).await?; - let mut sr25519_seed = [0u8; 64]; - hkdf.expand(KDF_SR25519, &mut sr25519_seed).expect("Cannot get 64 byte output from sha256"); - let pair = sr25519::Pair::from_seed_slice(&sr25519_seed)?; + let mut sr25519_seed = [0u8; 32]; + hkdf.expand(KDF_SR25519, &mut sr25519_seed).expect("Cannot get 32 byte output from sha256"); + let pair = sr25519::Pair::from_seed(&sr25519_seed); sr25519_seed.zeroize(); Ok(PairSigner::::new(pair)) } @@ -57,9 +57,9 @@ pub async fn get_signer_and_x25519_secret( let static_secret = StaticSecret::from(secret); secret.zeroize(); - let mut sr25519_seed = [0u8; 64]; - hkdf.expand(KDF_SR25519, &mut sr25519_seed).expect("Cannot get 64 byte output from sha256"); - let pair = sr25519::Pair::from_seed_slice(&sr25519_seed)?; + let mut sr25519_seed = [0u8; 32]; + hkdf.expand(KDF_SR25519, &mut sr25519_seed).expect("Cannot get 32 byte output from sha256"); + let pair = sr25519::Pair::from_seed(&sr25519_seed); sr25519_seed.zeroize(); Ok((PairSigner::::new(pair), static_secret)) @@ -91,9 +91,9 @@ pub fn get_signer_and_x25519_secret_from_mnemonic( let static_secret = StaticSecret::from(secret); secret.zeroize(); - let mut sr25519_seed = [0u8; 64]; - hkdf.expand(KDF_SR25519, &mut sr25519_seed).expect("Cannot get 64 byte output from sha256"); - let pair = sr25519::Pair::from_seed_slice(&sr25519_seed)?; + let mut sr25519_seed = [0u8; 32]; + hkdf.expand(KDF_SR25519, &mut sr25519_seed).expect("Cannot get 32 byte output from sha256"); + let pair = sr25519::Pair::from_seed(&sr25519_seed); sr25519_seed.zeroize(); Ok((PairSigner::::new(pair), static_secret)) diff --git a/crates/threshold-signature-server/tests/protocol_wasm.rs b/crates/threshold-signature-server/tests/protocol_wasm.rs index b5bf8bf56..573683d79 100644 --- a/crates/threshold-signature-server/tests/protocol_wasm.rs +++ b/crates/threshold-signature-server/tests/protocol_wasm.rs @@ -24,10 +24,7 @@ mod helpers; use axum::http::StatusCode; use entropy_kvdb::clean_tests; -use entropy_protocol::{ - sign_and_encrypt::{derive_x25519_static_secret, EncryptedSignedMessage}, - KeyParams, ValidatorInfo, -}; +use entropy_protocol::{sign_and_encrypt::EncryptedSignedMessage, KeyParams, ValidatorInfo}; use entropy_shared::{HashingAlgorithm, KeyVisibility, OcwMessageDkg, EVE_VERIFYING_KEY}; use entropy_testing_utils::{ chain_api::{ @@ -35,8 +32,8 @@ use entropy_testing_utils::{ entropy::runtime_types::pallet_registry::pallet::ProgramInstance, }, constants::{ - AUXILARY_DATA_SHOULD_SUCCEED, PREIMAGE_SHOULD_SUCCEED, TEST_PROGRAM_WASM_BYTECODE, - TSS_ACCOUNTS, X25519_PUBLIC_KEYS, + AUXILARY_DATA_SHOULD_SUCCEED, EVE_X25519_SECRET_KEY, PREIMAGE_SHOULD_SUCCEED, + TEST_PROGRAM_WASM_BYTECODE, TSS_ACCOUNTS, X25519_PUBLIC_KEYS, }, substrate_context::test_context_stationary, test_client::{put_register_request_on_chain, store_program, update_programs}, @@ -55,7 +52,7 @@ use subxt::{ Config, OnlineClient, }; use synedrion::KeyShare; -use x25519_dalek::PublicKey; +use x25519_dalek::{PublicKey, StaticSecret}; use entropy_tss::{ chain_api::{ @@ -176,6 +173,7 @@ async fn test_wasm_sign_tx_user_participates() { &message_should_succeed_hash, validators_info.clone(), one.pair().to_raw_vec(), + EVE_X25519_SECRET_KEY.to_vec(), ), ) .await; @@ -232,7 +230,7 @@ async fn test_wasm_register_with_private_key_visibility() { let block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number + 1; - let one_x25519_sk = derive_x25519_static_secret(&one.pair()); + let one_x25519_sk = StaticSecret::random_from_rng(rand_core::OsRng); let x25519_public_key = PublicKey::from(&one_x25519_sk).to_bytes(); put_register_request_on_chain( @@ -275,6 +273,9 @@ async fn test_wasm_register_with_private_key_visibility() { }) .collect(); + let sk: StaticSecret = EVE_X25519_SECRET_KEY.into(); + println!("EVE {:?}", PublicKey::from(&sk).to_bytes()); + // Call the `user/new` endpoint, and connect and participate in the protocol let (new_user_response_result, user_keyshare_json) = future::join( client @@ -284,13 +285,14 @@ async fn test_wasm_register_with_private_key_visibility() { spawn_user_participates_in_dkg_protocol( validators_info.clone(), one.pair().to_raw_vec(), + EVE_X25519_SECRET_KEY.to_vec(), block_number, ), ) .await; let response = new_user_response_result.unwrap(); - assert_eq!(response.status(), StatusCode::OK); + // assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.text().await.unwrap(), ""); let verifying_key = wait_for_register_confirmation(one.to_account_id(), api, rpc).await; @@ -307,6 +309,7 @@ async fn test_wasm_register_with_private_key_visibility() { #[derive(Debug, Clone, Serialize, Deserialize)] struct UserParticipatesInSigningProtocolArgs { user_sig_req_secret_key: Vec, + user_x25519_secret_key: Vec, message_hash: Vec, key_share: String, validators_info: Vec, @@ -315,6 +318,7 @@ struct UserParticipatesInSigningProtocolArgs { #[derive(Debug, Clone, Serialize, Deserialize)] struct UserParticipatesInDkgProtocolArgs { user_sig_req_secret_key: Vec, + user_x25519_secret_key: Vec, validators_info: Vec, block_number: u32, } @@ -333,10 +337,12 @@ async fn spawn_user_participates_in_signing_protocol( message_hash: &[u8; 32], validators_info: Vec, user_sig_req_secret_key: Vec, + user_x25519_secret_key: Vec, ) -> String { let args = UserParticipatesInSigningProtocolArgs { message_hash: message_hash.to_vec(), user_sig_req_secret_key, + user_x25519_secret_key, validators_info: validators_info .into_iter() .map(|validator_info| ValidatorInfoParsed { @@ -357,10 +363,12 @@ async fn spawn_user_participates_in_signing_protocol( async fn spawn_user_participates_in_dkg_protocol( validators_info: Vec, user_sig_req_secret_key: Vec, + user_x25519_secret_key: Vec, block_number: u32, ) -> String { let args = UserParticipatesInDkgProtocolArgs { user_sig_req_secret_key, + user_x25519_secret_key, validators_info: validators_info .into_iter() .map(|validator_info| ValidatorInfoParsed { From 2fdfb956fa58958391401233a872479312f944d7 Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 8 Apr 2024 14:23:08 +0200 Subject: [PATCH 10/35] Update Eves x25519 public key in chain spec --- node/cli/src/chain_spec/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/cli/src/chain_spec/mod.rs b/node/cli/src/chain_spec/mod.rs index 87407892e..f76958c93 100644 --- a/node/cli/src/chain_spec/mod.rs +++ b/node/cli/src/chain_spec/mod.rs @@ -110,8 +110,8 @@ pub mod tss_x25519_public_key { /// Not sure what mnemonic is used to derive the following public key. /// Mnemonic: "????" pub const EVE: [u8; 32] = [ - 28, 63, 144, 84, 78, 147, 195, 214, 190, 234, 111, 101, 117, 133, 9, 198, 96, 96, 76, 140, - 152, 251, 255, 28, 167, 38, 157, 185, 192, 42, 201, 82, + 142, 113, 91, 59, 177, 104, 208, 23, 219, 170, 47, 145, 200, 139, 188, 28, 14, 199, 116, + 86, 193, 144, 10, 18, 74, 157, 138, 202, 115, 99, 229, 55, ]; } From e1fb51253072bfb0178f5649eb2b51e65b2fa5e7 Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 8 Apr 2024 14:54:26 +0200 Subject: [PATCH 11/35] Update alice and bobs public x25519 keys in the chainspec --- node/cli/src/chain_spec/mod.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/node/cli/src/chain_spec/mod.rs b/node/cli/src/chain_spec/mod.rs index f76958c93..31b94c4b9 100644 --- a/node/cli/src/chain_spec/mod.rs +++ b/node/cli/src/chain_spec/mod.rs @@ -89,19 +89,20 @@ pub mod tss_x25519_public_key { /// The `DEFAULT_ALICE_MNEMONIC` is used to derive the public key. /// Mnemonic: "alarm mutual concert decrease hurry invest culture survey diagram crash snap click" pub const ALICE: [u8; 32] = [ - 10, 192, 41, 240, 184, 83, 178, 59, 237, 101, 45, 109, 13, 230, 155, 124, 195, 141, 148, - 249, 55, 50, 238, 252, 133, 181, 134, 30, 144, 247, 58, 34, + 8, 22, 19, 230, 107, 217, 249, 190, 14, 142, 155, 252, 156, 229, 120, 11, 180, 35, 83, 245, + 222, 11, 153, 201, 162, 29, 153, 13, 123, 126, 128, 32, ]; /// The `DEFAULT_BOB_MNEMONIC` is used to derive the public key. /// Mnemonic: "where sight patient orphan general short empower hope party hurt month voice" pub const BOB: [u8; 32] = [ - 225, 48, 135, 211, 227, 213, 170, 21, 1, 189, 118, 158, 255, 87, 245, 89, 36, 170, 169, - 181, 68, 201, 210, 178, 237, 247, 101, 80, 153, 136, 102, 10, + 196, 53, 98, 10, 160, 169, 139, 48, 194, 230, 69, 64, 165, 48, 133, 110, 38, 64, 184, 113, + 255, 201, 253, 212, 217, 21, 252, 57, 253, 78, 0, 56, ]; /// The `DEFAULT_CHARLIE_MNEMONIC` is used to derive the public key. /// Mnemonic: "lake carry still awful point mention bike category tornado plate brass lock" + /// TODO pub const CHARLIE: [u8; 32] = [ 245, 222, 99, 201, 89, 227, 119, 236, 142, 217, 74, 171, 58, 162, 140, 165, 7, 104, 210, 36, 196, 227, 208, 254, 175, 100, 226, 50, 239, 84, 141, 13, From 4b8f497d910008298a7218a785bbdc75938ebe8f Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 8 Apr 2024 14:56:32 +0200 Subject: [PATCH 12/35] Update alice and bobs public x25519 keys in testing-utils --- crates/testing-utils/src/constants.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/testing-utils/src/constants.rs b/crates/testing-utils/src/constants.rs index b57eb2683..def57c735 100644 --- a/crates/testing-utils/src/constants.rs +++ b/crates/testing-utils/src/constants.rs @@ -29,14 +29,14 @@ lazy_static! { ]; pub static ref X25519_PUBLIC_KEYS: Vec<[u8; 32]> = vec![ vec![ - 10, 192, 41, 240, 184, 83, 178, 59, 237, 101, 45, 109, 13, 230, 155, 124, 195, 141, - 148, 249, 55, 50, 238, 252, 133, 181, 134, 30, 144, 247, 58, 34, + 8, 22, 19, 230, 107, 217, 249, 190, 14, 142, 155, 252, 156, 229, 120, 11, 180, 35, 83, 245, + 222, 11, 153, 201, 162, 29, 153, 13, 123, 126, 128, 32, ] .try_into() .unwrap(), vec![ - 225, 48, 135, 211, 227, 213, 170, 21, 1, 189, 118, 158, 255, 87, 245, 89, 36, 170, 169, - 181, 68, 201, 210, 178, 237, 247, 101, 80, 153, 136, 102, 10, + 196, 53, 98, 10, 160, 169, 139, 48, 194, 230, 69, 64, 165, 48, 133, 110, 38, 64, 184, 113, + 255, 201, 253, 212, 217, 21, 252, 57, 253, 78, 0, 56, ] .try_into() .unwrap(), From 19297f9542da59ceb2d5b034afc3fa52e5e4584d Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 9 Apr 2024 11:01:15 +0200 Subject: [PATCH 13/35] Update alice and bobs TSS accounts in chain spec --- node/cli/src/chain_spec/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/cli/src/chain_spec/mod.rs b/node/cli/src/chain_spec/mod.rs index 31b94c4b9..9093976e8 100644 --- a/node/cli/src/chain_spec/mod.rs +++ b/node/cli/src/chain_spec/mod.rs @@ -63,12 +63,12 @@ pub mod tss_account_id { /// The `DEFAULT_ALICE_MNEMONIC` is used to derive the following `AccountId`. /// Mnemonic: "alarm mutual concert decrease hurry invest culture survey diagram crash snap click" pub static ref ALICE: sp_runtime::AccountId32 = - super::hex!["e0543c102def9f6ef0e8b8ffa31aa259167a9391566929fd718a1ccdaabdb876"].into(); + super::hex!["306bdb49cbbe7104e3621abab3c9d31698b159f48dafe567abb7ea5d872ed329"].into(); /// The `DEFAULT_BOB_MNEMONIC` is used to derive the following `AccountId`. /// Mnemonic: "where sight patient orphan general short empower hope party hurt month voice" pub static ref BOB: sp_runtime::AccountId32 = - super::hex!["2a8200850770290c7ea3b50a8ff64c6761c882ff8393dc95fccb5d1475eff17f"].into(); + super::hex!["2cbc68e8bf0fbc1c28c282d1263fc9d29267dc12a1044fb730e8b65abc37524c"].into(); /// The `DEFAULT_CHARLIE_MNEMONIC` is used to derive the following `AccountId`. /// Mnemonic: "lake carry still awful point mention bike category tornado plate brass lock" From 41c3fae450be10e20b44f704810631123d6e088d Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 9 Apr 2024 11:01:35 +0200 Subject: [PATCH 14/35] Update alice and bobs TSS accounts in testing-utils constants --- crates/testing-utils/src/constants.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/testing-utils/src/constants.rs b/crates/testing-utils/src/constants.rs index def57c735..ec3938ea7 100644 --- a/crates/testing-utils/src/constants.rs +++ b/crates/testing-utils/src/constants.rs @@ -24,8 +24,8 @@ lazy_static! { pub static ref BOB_STASH_ADDRESS: AccountId32 = hex!["fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e"].into(); // subkey inspect //Bob//stash pub static ref TSS_ACCOUNTS: Vec = vec![ - hex!["e0543c102def9f6ef0e8b8ffa31aa259167a9391566929fd718a1ccdaabdb876"].into(), - hex!["2a8200850770290c7ea3b50a8ff64c6761c882ff8393dc95fccb5d1475eff17f"].into() + hex!["306bdb49cbbe7104e3621abab3c9d31698b159f48dafe567abb7ea5d872ed329"].into(), + hex!["2cbc68e8bf0fbc1c28c282d1263fc9d29267dc12a1044fb730e8b65abc37524c"].into() ]; pub static ref X25519_PUBLIC_KEYS: Vec<[u8; 32]> = vec![ vec![ From 541c5acc38168e34ba310934b68569aca30b456a Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 9 Apr 2024 11:02:08 +0200 Subject: [PATCH 15/35] typo --- crates/threshold-signature-server/src/helpers/validator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs index 59c0ee741..cc42a4165 100644 --- a/crates/threshold-signature-server/src/helpers/validator.rs +++ b/crates/threshold-signature-server/src/helpers/validator.rs @@ -27,7 +27,7 @@ use zeroize::Zeroize; use crate::{chain_api::EntropyConfig, user::UserErr}; -const KDF_SR25519: &[u8] = b"ssr25519-threshold-account"; +const KDF_SR25519: &[u8] = b"sr25519-threshold-account"; const KDF_X25519: &[u8] = b"X25519-keypair"; /// Returns PairSigner for this nodes threshold server. From 7a466b1ac1f1e5223ca13dbead57b561f78f6863 Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 9 Apr 2024 11:33:30 +0200 Subject: [PATCH 16/35] Fix validate test helper --- crates/threshold-signature-server/src/user/tests.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/threshold-signature-server/src/user/tests.rs b/crates/threshold-signature-server/src/user/tests.rs index e2e91597c..27a653ec7 100644 --- a/crates/threshold-signature-server/src/user/tests.rs +++ b/crates/threshold-signature-server/src/user/tests.rs @@ -1515,12 +1515,10 @@ pub async fn verify_signature( ) .unwrap(); assert_eq!(keyshare_option.clone().unwrap().verifying_key(), recovery_key_from_sig); - let mnemonic = if i == 0 { DEFAULT_MNEMONIC } else { DEFAULT_BOB_MNEMONIC }; - let sk = ::from_string(mnemonic, None).unwrap(); let sig_recovery = ::verify( &signing_result.clone().unwrap().1, base64::decode(signing_result.unwrap().0).unwrap(), - &sr25519::Public(sk.public().0), + &sr25519::Public(TSS_ACCOUNTS[i].0), ); assert!(sig_recovery); i += 1; From eea72f9e961c30548a85580acf5ed6163281deff Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 9 Apr 2024 11:41:03 +0200 Subject: [PATCH 17/35] Fix tests --- crates/threshold-signature-server/src/user/tests.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/threshold-signature-server/src/user/tests.rs b/crates/threshold-signature-server/src/user/tests.rs index 27a653ec7..2572a678e 100644 --- a/crates/threshold-signature-server/src/user/tests.rs +++ b/crates/threshold-signature-server/src/user/tests.rs @@ -845,8 +845,7 @@ async fn test_send_and_receive_keys() { proactive_refresh: false, }; - let p_alice = ::from_string(DEFAULT_MNEMONIC, None).unwrap(); - let signer_alice = PairSigner::::new(p_alice); + let (signer_alice, _) = get_signer_and_x25519_secret_from_mnemonic(DEFAULT_MNEMONIC).unwrap(); // First try sending a keyshare for a user who is not registering - should fail let result = send_key( @@ -1049,8 +1048,10 @@ async fn test_recover_key() { .send() .await .unwrap(); - let p_alice = ::from_string(DEFAULT_CHARLIE_MNEMONIC, None).unwrap(); - let signer_alice = PairSigner::::new(p_alice); + + let (signer_alice, _) = + get_signer_and_x25519_secret_from_mnemonic(DEFAULT_CHARLIE_MNEMONIC).unwrap(); + recover_key(&api, &rpc, &bob_kv, &signer_alice, unsafe_query.key.clone()).await.unwrap(); let value = bob_kv.kv().get(&unsafe_query.key).await.unwrap(); @@ -1649,8 +1650,8 @@ async fn test_mutiple_confirm_done() { BoundedVec(vec![ProgramInstance { program_pointer: program_hash, program_config: vec![] }]), ) .await; - let p_alice = ::from_string(DEFAULT_MNEMONIC, None).unwrap(); - let signer_alice = PairSigner::::new(p_alice); + + let (signer_alice, _) = get_signer_and_x25519_secret_from_mnemonic(DEFAULT_MNEMONIC).unwrap(); confirm_registered( &api, From edee3afd24e3050e725a8674ff52c35cd5a8e6ae Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 9 Apr 2024 11:46:40 +0200 Subject: [PATCH 18/35] Fix tests --- crates/threshold-signature-server/src/user/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/threshold-signature-server/src/user/tests.rs b/crates/threshold-signature-server/src/user/tests.rs index 2572a678e..950afbd10 100644 --- a/crates/threshold-signature-server/src/user/tests.rs +++ b/crates/threshold-signature-server/src/user/tests.rs @@ -110,6 +110,7 @@ use crate::{ remove_program, run_to_block, setup_client, spawn_testing_validators, }, user::{compute_hash, send_key}, + validator::get_signer_and_x25519_secret_from_mnemonic, }, new_user, r#unsafe::api::UnsafeQuery, From 5855e43d67315804f1f6e3c9333e4aa32d070672 Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 9 Apr 2024 13:01:51 +0200 Subject: [PATCH 19/35] Update tests --- .../src/helpers/tests.rs | 7 +++--- .../src/validator/api.rs | 4 ++-- .../src/validator/tests.rs | 22 ++++++------------- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/crates/threshold-signature-server/src/helpers/tests.rs b/crates/threshold-signature-server/src/helpers/tests.rs index 287c36435..5c9c4f9cf 100644 --- a/crates/threshold-signature-server/src/helpers/tests.rs +++ b/crates/threshold-signature-server/src/helpers/tests.rs @@ -35,6 +35,7 @@ use crate::{ logger::Instrumentation, logger::Logger, substrate::{get_subgroup, query_chain, submit_transaction}, + validator::get_signer_and_x25519_secret_from_mnemonic, }, signing_client::ListenerState, AppState, @@ -262,13 +263,11 @@ async fn test_get_signing_group() { let api = get_api(&cxt.node_proc.ws_url).await.unwrap(); let rpc = get_rpc(&cxt.node_proc.ws_url).await.unwrap(); - let p_alice = ::from_string(DEFAULT_MNEMONIC, None).unwrap(); - let signer_alice = PairSigner::::new(p_alice); + let (signer_alice, _) = get_signer_and_x25519_secret_from_mnemonic(DEFAULT_MNEMONIC).unwrap(); let result_alice = get_subgroup(&api, &rpc, &signer_alice.account_id()).await.unwrap(); assert_eq!(result_alice, 0); - let p_bob = ::from_string(DEFAULT_BOB_MNEMONIC, None).unwrap(); - let signer_bob = PairSigner::::new(p_bob); + let (signer_bob, _) = get_signer_and_x25519_secret_from_mnemonic(DEFAULT_BOB_MNEMONIC).unwrap(); let result_bob = get_subgroup(&api, &rpc, &signer_bob.account_id()).await.unwrap(); assert_eq!(result_bob, 1); diff --git a/crates/threshold-signature-server/src/validator/api.rs b/crates/threshold-signature-server/src/validator/api.rs index c8647e8b1..f559a182a 100644 --- a/crates/threshold-signature-server/src/validator/api.rs +++ b/crates/threshold-signature-server/src/validator/api.rs @@ -235,10 +235,10 @@ pub async fn get_and_store_values( batch_size: usize, dev: bool, recip_server_info: ServerInfo, - signer: &PairSigner, + _signer: &PairSigner, ) -> Result<(), ValidatorErr> { let url = String::from_utf8(recip_server_info.endpoint)?; - let (_, x25519_secret) = get_signer_and_x25519_secret(kv).await?; + let (signer, x25519_secret) = get_signer_and_x25519_secret(kv).await?; let mut keys_stored = 0; while keys_stored < all_keys.len() { let mut keys_to_send_slice = batch_size + keys_stored; diff --git a/crates/threshold-signature-server/src/validator/tests.rs b/crates/threshold-signature-server/src/validator/tests.rs index e8eb215e7..e8bdb28bd 100644 --- a/crates/threshold-signature-server/src/validator/tests.rs +++ b/crates/threshold-signature-server/src/validator/tests.rs @@ -15,7 +15,6 @@ use std::{net::TcpListener, time::SystemTime}; -use bip39::{Language, Mnemonic}; use entropy_kvdb::clean_tests; use entropy_shared::{DAVE_VERIFYING_KEY, EVE_VERIFYING_KEY, FERDIE_VERIFYING_KEY, MIN_BALANCE}; use entropy_testing_utils::{ @@ -92,20 +91,15 @@ async fn test_sync_kvdb() { hex::encode(FERDIE_VERIFYING_KEY.to_vec()), ]; - let a_usr_sk = mnemonic_to_pair( - &Mnemonic::parse_in_normalized(Language::English, DEFAULT_ALICE_MNEMONIC).unwrap(), - ) - .unwrap(); - - let b_usr_sk = mnemonic_to_pair( - &Mnemonic::parse_in_normalized(Language::English, DEFAULT_BOB_MNEMONIC).unwrap(), - ) - .unwrap(); + let (a_signer, _) = get_signer_and_x25519_secret_from_mnemonic(DEFAULT_ALICE_MNEMONIC).unwrap(); - let (_, bob_x25519_secret) = + let (b_signer, bob_x25519_secret) = get_signer_and_x25519_secret_from_mnemonic(DEFAULT_BOB_MNEMONIC).unwrap(); let recip = x25519_dalek::PublicKey::from(&bob_x25519_secret).to_bytes(); + let a_usr_sk = a_signer.signer(); + let b_usr_sk = b_signer.signer(); + let values = vec![vec![10], vec![11], vec![12]]; let port = 3001; @@ -261,8 +255,7 @@ async fn test_get_and_store_values() { let api = get_api(&cxt.ws_url).await.unwrap(); let rpc = get_rpc(&cxt.ws_url).await.unwrap(); - let p_alice = ::from_string(DEFAULT_MNEMONIC, None).unwrap(); - let signer_alice = PairSigner::::new(p_alice); + let (signer_alice, _) = get_signer_and_x25519_secret_from_mnemonic(DEFAULT_MNEMONIC).unwrap(); let mut recip_server_info = { let alice_stash_address = @@ -326,8 +319,7 @@ async fn test_get_random_server_info() { let api = get_api(&cxt.node_proc.ws_url).await.unwrap(); let rpc = get_rpc(&cxt.node_proc.ws_url).await.unwrap(); - let p_alice = ::from_string(DEFAULT_MNEMONIC, None).unwrap(); - let signer_alice = PairSigner::::new(p_alice); + let (signer_alice, _) = get_signer_and_x25519_secret_from_mnemonic(DEFAULT_MNEMONIC).unwrap(); let my_subgroup = get_subgroup(&api, &rpc, &signer_alice.account_id()).await.unwrap(); let validator_address = get_stash_address(&api, &rpc, &signer_alice.account_id()).await.unwrap(); From e34afcf5e4b42b38426fd36a5ad49663e407842a Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 9 Apr 2024 14:26:11 +0200 Subject: [PATCH 20/35] Add charlie updated keys to chainspec --- node/cli/src/chain_spec/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/node/cli/src/chain_spec/mod.rs b/node/cli/src/chain_spec/mod.rs index 9093976e8..20e3c0d8e 100644 --- a/node/cli/src/chain_spec/mod.rs +++ b/node/cli/src/chain_spec/mod.rs @@ -73,7 +73,7 @@ pub mod tss_account_id { /// The `DEFAULT_CHARLIE_MNEMONIC` is used to derive the following `AccountId`. /// Mnemonic: "lake carry still awful point mention bike category tornado plate brass lock" pub static ref CHARLIE: sp_runtime::AccountId32 = - super::hex!["14d223daeec68671f07298c66c9458980a48bb89fb8a85d5df31131acad8d611"].into(); + super::hex!["946140d3d5ddb980c74ffa1bb64353b5523d2d77cdf3dc617fd63de9d3b66338"].into(); /// Not sure what mnemonic is used to derive the following `AccountId`. /// Mnemonic: "????" @@ -102,10 +102,9 @@ pub mod tss_x25519_public_key { /// The `DEFAULT_CHARLIE_MNEMONIC` is used to derive the public key. /// Mnemonic: "lake carry still awful point mention bike category tornado plate brass lock" - /// TODO pub const CHARLIE: [u8; 32] = [ - 245, 222, 99, 201, 89, 227, 119, 236, 142, 217, 74, 171, 58, 162, 140, 165, 7, 104, 210, - 36, 196, 227, 208, 254, 175, 100, 226, 50, 239, 84, 141, 13, + 131, 8, 162, 77, 237, 245, 226, 179, 250, 79, 121, 250, 174, 181, 227, 122, 205, 181, 188, + 4, 37, 87, 150, 250, 210, 151, 203, 137, 188, 134, 124, 108, ]; /// Not sure what mnemonic is used to derive the following public key. From 56ac5a6350458698a32d790048ea30c83754649c Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 9 Apr 2024 14:26:22 +0200 Subject: [PATCH 21/35] Fix remaining tests --- .../src/user/api.rs | 20 +++++++++++++++---- .../src/user/tests.rs | 6 ++++-- .../src/validator/api.rs | 12 +++++++---- .../src/validator/tests.rs | 17 ++++++++++++---- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/crates/threshold-signature-server/src/user/api.rs b/crates/threshold-signature-server/src/user/api.rs index d0e6ff918..b2052e6c9 100644 --- a/crates/threshold-signature-server/src/user/api.rs +++ b/crates/threshold-signature-server/src/user/api.rs @@ -218,7 +218,8 @@ pub async fn sign_tx( let has_key = check_for_key(&string_verifying_key, &app_state.kv_store).await?; if !has_key { - recover_key(&api, &rpc, &app_state.kv_store, &signer, string_verifying_key).await? + recover_key(&api, &rpc, &app_state.kv_store, &signer, &x25519_secret, string_verifying_key) + .await? } let (mut response_tx, response_rx) = mpsc::channel(1); @@ -627,16 +628,19 @@ pub async fn validate_new_user( Ok(()) } +/// Check if a given key is present in the given key-value store pub async fn check_for_key(account: &str, kv: &KvManager) -> Result { let exists_result = kv.kv().exists(account).await?; Ok(exists_result) } +/// Get and store a keyshare associated with a given verifying key pub async fn recover_key( api: &OnlineClient, rpc: &LegacyRpcMethods, kv_store: &KvManager, signer: &PairSigner, + x25519_secret: &StaticSecret, verifying_key: String, ) -> Result<(), UserErr> { let subgroup = get_subgroup(api, rpc, signer.account_id()).await?; @@ -644,9 +648,17 @@ pub async fn recover_key( let key_server_info = get_random_server_info(api, rpc, subgroup, stash_address) .await .map_err(|_| UserErr::ValidatorError("Error getting server".to_string()))?; - get_and_store_values(vec![verifying_key], kv_store, 1, false, key_server_info, signer) - .await - .map_err(|e| UserErr::ValidatorError(e.to_string()))?; + get_and_store_values( + vec![verifying_key], + kv_store, + 1, + false, + key_server_info, + signer, + x25519_secret, + ) + .await + .map_err(|e| UserErr::ValidatorError(e.to_string()))?; Ok(()) } diff --git a/crates/threshold-signature-server/src/user/tests.rs b/crates/threshold-signature-server/src/user/tests.rs index 950afbd10..f770d4583 100644 --- a/crates/threshold-signature-server/src/user/tests.rs +++ b/crates/threshold-signature-server/src/user/tests.rs @@ -1050,10 +1050,12 @@ async fn test_recover_key() { .await .unwrap(); - let (signer_alice, _) = + let (signer_alice, x25519_alice) = get_signer_and_x25519_secret_from_mnemonic(DEFAULT_CHARLIE_MNEMONIC).unwrap(); - recover_key(&api, &rpc, &bob_kv, &signer_alice, unsafe_query.key.clone()).await.unwrap(); + recover_key(&api, &rpc, &bob_kv, &signer_alice, &x25519_alice, unsafe_query.key.clone()) + .await + .unwrap(); let value = bob_kv.kv().get(&unsafe_query.key).await.unwrap(); assert_eq!(value, unsafe_query.value); diff --git a/crates/threshold-signature-server/src/validator/api.rs b/crates/threshold-signature-server/src/validator/api.rs index f559a182a..5da65a693 100644 --- a/crates/threshold-signature-server/src/validator/api.rs +++ b/crates/threshold-signature-server/src/validator/api.rs @@ -24,13 +24,14 @@ use subxt::{ backend::legacy::LegacyRpcMethods, ext::sp_core::sr25519, tx::PairSigner, utils::AccountId32 as SubxtAccountId32, OnlineClient, }; +use x25519_dalek::StaticSecret; use crate::{ chain_api::{ entropy::{self, runtime_types::pallet_staking_extension::pallet::ServerInfo}, get_api, get_rpc, EntropyConfig, }, - get_signer, get_signer_and_x25519_secret, + get_signer_and_x25519_secret, helpers::{ launch::FORBIDDEN_KEYS, substrate::{get_stash_address, get_subgroup, query_chain, submit_transaction}, @@ -81,7 +82,9 @@ pub async fn sync_validator(sync: bool, dev: bool, endpoint: &str, kv_store: &Kv thread::sleep(sleep_time); } } - let signer = get_signer(kv_store).await.expect("Issue acquiring threshold signer key"); + let (signer, x25519_secret) = get_signer_and_x25519_secret(kv_store) + .await + .expect("Issue acquiring threshold keypairs"); let has_fee_balance = check_balance_for_fees(&api, &rpc, signer.account_id(), MIN_BALANCE) .await .expect("Issue checking chain for signer balance"); @@ -112,6 +115,7 @@ pub async fn sync_validator(sync: bool, dev: bool, endpoint: &str, kv_store: &Kv dev, key_server_info, &signer, + &x25519_secret, ) .await .expect("failed to get and store all values"); @@ -235,10 +239,10 @@ pub async fn get_and_store_values( batch_size: usize, dev: bool, recip_server_info: ServerInfo, - _signer: &PairSigner, + signer: &PairSigner, + x25519_secret: &StaticSecret, ) -> Result<(), ValidatorErr> { let url = String::from_utf8(recip_server_info.endpoint)?; - let (signer, x25519_secret) = get_signer_and_x25519_secret(kv).await?; let mut keys_stored = 0; while keys_stored < all_keys.len() { let mut keys_to_send_slice = batch_size + keys_stored; diff --git a/crates/threshold-signature-server/src/validator/tests.rs b/crates/threshold-signature-server/src/validator/tests.rs index e8bdb28bd..8845bc150 100644 --- a/crates/threshold-signature-server/src/validator/tests.rs +++ b/crates/threshold-signature-server/src/validator/tests.rs @@ -255,7 +255,8 @@ async fn test_get_and_store_values() { let api = get_api(&cxt.ws_url).await.unwrap(); let rpc = get_rpc(&cxt.ws_url).await.unwrap(); - let (signer_alice, _) = get_signer_and_x25519_secret_from_mnemonic(DEFAULT_MNEMONIC).unwrap(); + let (signer_alice, x25519_alice) = + get_signer_and_x25519_secret_from_mnemonic(DEFAULT_MNEMONIC).unwrap(); let mut recip_server_info = { let alice_stash_address = @@ -298,9 +299,17 @@ async fn test_get_and_store_values() { // We are 'being' bob (using bob's kv), but we authenticate as alice, because otherwise we will // fail the subgroup check. We can't properly test this function because we don't have two tss // servers in the same subgroup with only two subgroups. - get_and_store_values(keys.clone(), &bob_kv, 9, false, recip_server_info, &signer_alice) - .await - .unwrap(); + get_and_store_values( + keys.clone(), + &bob_kv, + 9, + false, + recip_server_info, + &signer_alice, + &x25519_alice, + ) + .await + .unwrap(); for (i, key) in keys.iter().enumerate() { tracing::info!("!! -> -> RECEIVED KEY at IDX {i} of value {key:?}"); let val = bob_kv.kv().get(key).await; From 5265d7845a2873d019a3bc33968dcc92516043c2 Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 9 Apr 2024 14:40:32 +0200 Subject: [PATCH 22/35] Clippy --- crates/threshold-signature-server/src/validator/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/threshold-signature-server/src/validator/api.rs b/crates/threshold-signature-server/src/validator/api.rs index 5da65a693..9125d12ea 100644 --- a/crates/threshold-signature-server/src/validator/api.rs +++ b/crates/threshold-signature-server/src/validator/api.rs @@ -285,7 +285,7 @@ pub async fn get_and_store_values( let reservation = kv.kv().reserve_key(remaining_keys[i].clone()).await?; let key = { let signed_message = encrypted_key - .decrypt(&x25519_secret, &[]) + .decrypt(x25519_secret, &[]) .map_err(|e| ValidatorErr::Decryption(e.to_string()))?; if signed_message.sender.0 != recip_server_info.tss_account.0 { return Err(ValidatorErr::Authentication); From 70f77583735b95a810386bb16148f61f618daea5 Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 9 Apr 2024 15:47:35 +0200 Subject: [PATCH 23/35] Refactor --- .../src/helpers/validator.rs | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs index cc42a4165..c3667e042 100644 --- a/crates/threshold-signature-server/src/helpers/validator.rs +++ b/crates/threshold-signature-server/src/helpers/validator.rs @@ -37,12 +37,7 @@ pub async fn get_signer( kv: &KvManager, ) -> Result, UserErr> { let hkdf = get_hkdf(kv).await?; - - let mut sr25519_seed = [0u8; 32]; - hkdf.expand(KDF_SR25519, &mut sr25519_seed).expect("Cannot get 32 byte output from sha256"); - let pair = sr25519::Pair::from_seed(&sr25519_seed); - sr25519_seed.zeroize(); - Ok(PairSigner::::new(pair)) + get_signer_from_hkdf(&hkdf) } /// Get the PairSigner as above, and also the x25519 encryption keypair for @@ -51,18 +46,20 @@ pub async fn get_signer_and_x25519_secret( kv: &KvManager, ) -> Result<(PairSigner, StaticSecret), UserErr> { let hkdf = get_hkdf(kv).await?; + let pair_signer = get_signer_from_hkdf(&hkdf)?; + let static_secret = get_x25519_secret_from_hkdf(&hkdf)?; + Ok((pair_signer, static_secret)) +} - let mut secret = [0u8; 32]; - hkdf.expand(KDF_X25519, &mut secret).expect("Cannot get 32 byte output from sha256"); - let static_secret = StaticSecret::from(secret); - secret.zeroize(); - - let mut sr25519_seed = [0u8; 32]; - hkdf.expand(KDF_SR25519, &mut sr25519_seed).expect("Cannot get 32 byte output from sha256"); - let pair = sr25519::Pair::from_seed(&sr25519_seed); - sr25519_seed.zeroize(); - - Ok((PairSigner::::new(pair), static_secret)) +/// For testing where we sometimes don't have access to the kvdb, derive directly from the mnemnic +#[cfg(test)] +pub fn get_signer_and_x25519_secret_from_mnemonic( + mnemonic: &str, +) -> Result<(PairSigner, StaticSecret), UserErr> { + let hkdf = get_hkdf_from_mnemonic(mnemonic)?; + let pair_signer = get_signer_from_hkdf(&hkdf)?; + let static_secret = get_x25519_secret_from_hkdf(&hkdf)?; + Ok((pair_signer, static_secret)) } /// Get the key derivation struct to derive secret keys from a mnemonic stored in the KVDB @@ -70,31 +67,32 @@ async fn get_hkdf(kv: &KvManager) -> Result, UserErr> { let _ = kv.kv().exists("MNEMONIC").await?; let raw_m = kv.kv().get("MNEMONIC").await?; let secret = core::str::from_utf8(&raw_m)?; - let mnemonic = Mnemonic::parse_in_normalized(Language::English, secret) - .map_err(|e| UserErr::Mnemonic(e.to_string()))?; - - Ok(Hkdf::::new(None, &mnemonic.to_seed(""))) + get_hkdf_from_mnemonic(secret) } -/// For testing where we sometimes don't have access to the kvdb, derive directly from the mnemnic -#[cfg(test)] -pub fn get_signer_and_x25519_secret_from_mnemonic( - mnemonic: &str, -) -> Result<(PairSigner, StaticSecret), UserErr> { +/// Given a mnemonic, setup hkdf +fn get_hkdf_from_mnemonic(mnemonic: &str) -> Result, UserErr> { let mnemonic = Mnemonic::parse_in_normalized(Language::English, mnemonic) .map_err(|e| UserErr::Mnemonic(e.to_string()))?; + Ok(Hkdf::::new(None, &mnemonic.to_seed(""))) +} - let hkdf = Hkdf::::new(None, &mnemonic.to_seed("")); - - let mut secret = [0u8; 32]; - hkdf.expand(KDF_X25519, &mut secret).expect("Cannot get 32 byte output from sha256"); - let static_secret = StaticSecret::from(secret); - secret.zeroize(); - +fn get_signer_from_hkdf( + hkdf: &Hkdf, +) -> Result, UserErr> { let mut sr25519_seed = [0u8; 32]; + //TODO hkdf.expand(KDF_SR25519, &mut sr25519_seed).expect("Cannot get 32 byte output from sha256"); let pair = sr25519::Pair::from_seed(&sr25519_seed); sr25519_seed.zeroize(); - Ok((PairSigner::::new(pair), static_secret)) + Ok(PairSigner::::new(pair)) +} + +fn get_x25519_secret_from_hkdf(hkdf: &Hkdf) -> Result { + let mut secret = [0u8; 32]; + hkdf.expand(KDF_X25519, &mut secret).expect("Cannot get 32 byte output from sha256"); + let static_secret = StaticSecret::from(secret); + secret.zeroize(); + Ok(static_secret) } From 51348b59a83d0a4c78c71d32e00c6eba34dba52c Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 9 Apr 2024 16:04:21 +0200 Subject: [PATCH 24/35] Fix private mode tests --- crates/threshold-signature-server/tests/protocol_wasm.rs | 3 --- crates/threshold-signature-server/tests/sign.rs | 8 +++----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/crates/threshold-signature-server/tests/protocol_wasm.rs b/crates/threshold-signature-server/tests/protocol_wasm.rs index 573683d79..89eaeb329 100644 --- a/crates/threshold-signature-server/tests/protocol_wasm.rs +++ b/crates/threshold-signature-server/tests/protocol_wasm.rs @@ -273,9 +273,6 @@ async fn test_wasm_register_with_private_key_visibility() { }) .collect(); - let sk: StaticSecret = EVE_X25519_SECRET_KEY.into(); - println!("EVE {:?}", PublicKey::from(&sk).to_bytes()); - // Call the `user/new` endpoint, and connect and participate in the protocol let (new_user_response_result, user_keyshare_json) = future::join( client diff --git a/crates/threshold-signature-server/tests/sign.rs b/crates/threshold-signature-server/tests/sign.rs index 32899be3b..23285e025 100644 --- a/crates/threshold-signature-server/tests/sign.rs +++ b/crates/threshold-signature-server/tests/sign.rs @@ -21,7 +21,8 @@ use entropy_testing_utils::{ entropy::runtime_types::pallet_registry::pallet::ProgramInstance, }, constants::{ - AUXILARY_DATA_SHOULD_SUCCEED, PREIMAGE_SHOULD_SUCCEED, TEST_PROGRAM_WASM_BYTECODE, + AUXILARY_DATA_SHOULD_SUCCEED, EVE_X25519_SECRET_KEY, PREIMAGE_SHOULD_SUCCEED, + TEST_PROGRAM_WASM_BYTECODE, }, substrate_context::test_context_stationary, test_client, @@ -132,16 +133,13 @@ async fn integration_test_sign_private() { let message_should_succeed_hash = Hasher::keccak(PREIMAGE_SHOULD_SUCCEED); - // TODO - let x25519_secret: [u8; 32] = [0; 32]; - let recoverable_signature = test_client::sign( &api, &rpc, pre_registered_user.pair(), verifying_key, PREIMAGE_SHOULD_SUCCEED.to_vec(), - Some((keyshare.clone(), x25519_secret.into())), + Some((keyshare.clone(), EVE_X25519_SECRET_KEY.into())), Some(AUXILARY_DATA_SHOULD_SUCCEED.to_vec()), ) .await From 6fdeb96f419b5c3c7f289a5a09e00201ccf5bd2b Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 9 Apr 2024 18:47:25 +0200 Subject: [PATCH 25/35] Fix wasm tests --- crates/protocol/nodejs-test/index.test.js | 13 ++++--- crates/protocol/nodejs-test/yarn.lock | 14 ++++---- crates/protocol/src/sign_and_encrypt/wasm.rs | 38 +++++++++++++++----- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/crates/protocol/nodejs-test/index.test.js b/crates/protocol/nodejs-test/index.test.js index d79a7bd16..4398172d7 100644 --- a/crates/protocol/nodejs-test/index.test.js +++ b/crates/protocol/nodejs-test/index.test.js @@ -14,30 +14,29 @@ test('Convert Uint8Array to and from hex', function (t) { test('Encrypt, decrypt with HPKE', function (t) { t.plan(2) - const { generateSigningKey, publicKeyFromSecret, encryptAndSign, decryptAndVerify } = protocol.Hpke + const { generateSigningKey, encryptAndSign, decryptAndVerify } = protocol.Hpke const aliceSk = generateSigningKey() - const bobSk = generateSigningKey() - const bobPk = publicKeyFromSecret(bobSk) + const bobX25519Keypair = protocol.X25519Keypair.generate() const plaintext = new Uint8Array(32) // Alice encrypts and signs the message to bob. - const encryptedAndSignedMessage = encryptAndSign(aliceSk, plaintext, bobPk) + const encryptedAndSignedMessage = encryptAndSign(aliceSk, plaintext, bobX25519Keypair.publicKey()) // Bob decrypts the message. - const decryptedPlaintext = decryptAndVerify(bobSk, encryptedAndSignedMessage) + const decryptedPlaintext = decryptAndVerify(bobX25519Keypair.secretKey(), encryptedAndSignedMessage) // Check the original plaintext equals the decrypted plaintext. t.true(protocol.constantTimeEq(decryptedPlaintext, plaintext)) - const mallorySk = generateSigningKey() + const malloryX25519Keypair = protocol.X25519Keypair.generate() // Malloy cannot decrypt the message. let error try { - decryptAndVerify(mallorySk, encryptedAndSignedMessage) + decryptAndVerify(malloryX25519Keypair.secretKey(), encryptedAndSignedMessage) } catch (e) { error = e.toString() } diff --git a/crates/protocol/nodejs-test/yarn.lock b/crates/protocol/nodejs-test/yarn.lock index 4822f5185..58213295c 100644 --- a/crates/protocol/nodejs-test/yarn.lock +++ b/crates/protocol/nodejs-test/yarn.lock @@ -171,9 +171,9 @@ dotignore@^0.1.2: version "0.1.0" es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0: - version "1.23.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.2.tgz#693312f3940f967b8dd3eebacb590b01712622e0" - integrity sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w== + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== dependencies: array-buffer-byte-length "^1.0.1" arraybuffer.prototype.slice "^1.0.3" @@ -214,11 +214,11 @@ es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0: safe-regex-test "^1.0.3" string.prototype.trim "^1.2.9" string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.7" + string.prototype.trimstart "^1.0.8" typed-array-buffer "^1.0.2" typed-array-byte-length "^1.0.1" typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.5" + typed-array-length "^1.0.6" unbox-primitive "^1.0.2" which-typed-array "^1.1.15" @@ -733,7 +733,7 @@ string.prototype.trimend@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string.prototype.trimstart@^1.0.7: +string.prototype.trimstart@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== @@ -807,7 +807,7 @@ typed-array-byte-offset@^1.0.2: has-proto "^1.0.3" is-typed-array "^1.1.13" -typed-array-length@^1.0.5: +typed-array-length@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== diff --git a/crates/protocol/src/sign_and_encrypt/wasm.rs b/crates/protocol/src/sign_and_encrypt/wasm.rs index 2bc48fd03..bfda46617 100644 --- a/crates/protocol/src/sign_and_encrypt/wasm.rs +++ b/crates/protocol/src/sign_and_encrypt/wasm.rs @@ -1,12 +1,41 @@ //! Wasm bindings to the [EncryptedSignedMessage] API, as well as some helper functions use super::EncryptedSignedMessage; use js_sys::Error; +use rand_core::OsRng; use schnorrkel::{MiniSecretKey, SecretKey}; use sp_core::sr25519; use wasm_bindgen::prelude::*; +use x25519_dalek::StaticSecret; const HEX_PREFIX: [u8; 2] = [48, 120]; +#[wasm_bindgen] +pub struct X25519Keypair { + secret_key: StaticSecret, + public_key: x25519_dalek::PublicKey, +} + +#[wasm_bindgen] +impl X25519Keypair { + /// Generate an x25519 encryption keypair + #[wasm_bindgen(js_name = generate)] + pub fn generate() -> Result { + let secret_key = StaticSecret::random_from_rng(OsRng); + let public_key = x25519_dalek::PublicKey::from(&secret_key); + Ok(X25519Keypair { secret_key, public_key }) + } + + #[wasm_bindgen(js_name = secretKey)] + pub fn secret_key(&self) -> Vec { + self.secret_key.as_bytes().to_vec() + } + + #[wasm_bindgen(js_name = publicKey)] + pub fn public_key(&self) -> Vec { + self.public_key.as_bytes().to_vec() + } +} + /// Functions for creating and using `EncryptedSignedMessage`s which use HPKE for chacha20poly1305 /// encryption and x25519 key agreement and sr25519 for signing. #[wasm_bindgen] @@ -28,15 +57,6 @@ impl Hpke { Ok(sk.to_bytes().to_vec()) } - // /// Derives a public DH key from a static DH secret. - // /// secret_key must be 64 bytes in length or an error will be returned. - // #[wasm_bindgen(js_name = publicKeyFromSecret)] - // pub fn public_key_from_secret(secret_key: Vec) -> Result, Error> { - // let pair = sr25519_keypair_from_secret_key(secret_key)?; - // let x25519_secret = derive_x25519_static_secret(&pair); - // Ok(PublicKey::from(&x25519_secret).as_bytes().to_vec()) - // } - /// Encrypts, signs, and serializes an `EncryptedSignedMessage` to JSON. #[wasm_bindgen(js_name = encryptAndSign)] pub fn encrypt_and_sign( From e36f95ba3e67c1effa216fc681d339dbd36c8cdb Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 9 Apr 2024 21:57:59 +0200 Subject: [PATCH 26/35] Fix wasm protocol tests --- crates/threshold-signature-server/tests/helpers/mod.rs | 7 +++---- crates/threshold-signature-server/tests/protocol_wasm.rs | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/threshold-signature-server/tests/helpers/mod.rs b/crates/threshold-signature-server/tests/helpers/mod.rs index 1b0086bc4..f7a2914df 100644 --- a/crates/threshold-signature-server/tests/helpers/mod.rs +++ b/crates/threshold-signature-server/tests/helpers/mod.rs @@ -15,7 +15,7 @@ //! Helper functions for integration tests use entropy_protocol::KeyParams; -use entropy_tss::launch::{DEFAULT_BOB_MNEMONIC, DEFAULT_MNEMONIC}; +use entropy_testing_utils::constants::TSS_ACCOUNTS; use synedrion::{ k256::ecdsa::{RecoveryId, Signature as k256Signature, VerifyingKey}, KeyShare, @@ -49,12 +49,11 @@ pub async fn verify_signature( ) .unwrap(); assert_eq!(keyshare_option.clone().unwrap().verifying_key(), recovery_key_from_sig); - let mnemonic = if i == 0 { DEFAULT_MNEMONIC } else { DEFAULT_BOB_MNEMONIC }; - let sk = ::from_string(mnemonic, None).unwrap(); + let sig_recovery = ::verify( &signing_result.clone().unwrap().1, base64::decode(signing_result.unwrap().0).unwrap(), - &sr25519::Public(sk.public().0), + &sr25519::Public(TSS_ACCOUNTS[i].0), ); assert!(sig_recovery); i += 1; diff --git a/crates/threshold-signature-server/tests/protocol_wasm.rs b/crates/threshold-signature-server/tests/protocol_wasm.rs index 89eaeb329..cba3403db 100644 --- a/crates/threshold-signature-server/tests/protocol_wasm.rs +++ b/crates/threshold-signature-server/tests/protocol_wasm.rs @@ -282,14 +282,14 @@ async fn test_wasm_register_with_private_key_visibility() { spawn_user_participates_in_dkg_protocol( validators_info.clone(), one.pair().to_raw_vec(), - EVE_X25519_SECRET_KEY.to_vec(), + one_x25519_sk.as_bytes().to_vec(), block_number, ), ) .await; let response = new_user_response_result.unwrap(); - // assert_eq!(response.status(), StatusCode::OK); + assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.text().await.unwrap(), ""); let verifying_key = wait_for_register_confirmation(one.to_account_id(), api, rpc).await; From 2ff2d3a57c46293dfe19c8e3a343d3a7d2b213cf Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 10 Apr 2024 10:01:54 +0200 Subject: [PATCH 27/35] JS api and docs --- crates/protocol/js-README.md | 25 +++++- crates/protocol/src/sign_and_encrypt/wasm.rs | 80 ++++++++++++-------- 2 files changed, 73 insertions(+), 32 deletions(-) diff --git a/crates/protocol/js-README.md b/crates/protocol/js-README.md index f49f75ba0..918455181 100644 --- a/crates/protocol/js-README.md +++ b/crates/protocol/js-README.md @@ -8,6 +8,7 @@ side when using private access mode. Exposed to JS: - [`Hpke`](#Hpke) - for signing and encrypting / decrypting +- [`X25519Keypair`](#X25519Keypair) - for creating encryption keypairs - [`runDkgProtocol`](#registering-in-private-access-mode) - for registering in private access mode - [`runSigningProtocol`](#signing-in-private-access-mode) - for signing in private access mode - `ValidatorInfo` - details of a TSS node @@ -37,7 +38,7 @@ serialized string. ### `Hpke.decryptAndVerify` -Decrypt and verify an `EncryptedSignedMessage`. Takes an sr25519 secret key given as a `Uint8Array`, +Decrypt and verify an `EncryptedSignedMessage`. Takes an x25519 secret key given as a `Uint8Array`, and a JSON serialized `SignedMessage` containing the encrypted payload. On successful decryption and signature verification it will return the decrypted payload as a `Uint8Array`. @@ -46,6 +47,26 @@ and signature verification it will return the decrypted payload as a `Uint8Array Generates a secret sr25519 signing key and returns it as a Uint8Array. This is really only exposed for testing purposes, as you can also use Polkadot-JS to generate sr25519 keypairs. +## `X25519Keypair` + +### `X25519Keypair.generate` + +Constructor to randomly generate an `X25519Keypair`. + +### `X25519Keypair.fromSecretKey` + +Constructor to create an `X25519Keypair` from a secret key given as a 32 byte `Uint8Array`. + +### `X25519Keypair.secretKey` + +Returns the secret key as a `Uint8Array`. Note that this is a getter method, not a public property +of the object. + +### `X25519Keypair.publicKey` + +Returns the public key as a `Uint8Array`. Note that this is a getter method, not a public property +of the object. + ## Registering in private access mode To register in private access mode a register transaction must be @@ -89,7 +110,7 @@ same as the those used in a `UserSignatureRequest`. making the `user/sign_tx` http requests, for example by using `Promise.all`. `runSigningProtocol` also takes the user's `KeyShare` as an argument, as well as the message hash, and the user's private -sr25519 signing key. +sr25519 signing key and x25519 encryption key. `runSigningProtocol` returns a promise which if successful will resolve to an ECDSA signature with recovery bit, encoded as a base64 string. diff --git a/crates/protocol/src/sign_and_encrypt/wasm.rs b/crates/protocol/src/sign_and_encrypt/wasm.rs index bfda46617..a70405ea3 100644 --- a/crates/protocol/src/sign_and_encrypt/wasm.rs +++ b/crates/protocol/src/sign_and_encrypt/wasm.rs @@ -9,33 +9,6 @@ use x25519_dalek::StaticSecret; const HEX_PREFIX: [u8; 2] = [48, 120]; -#[wasm_bindgen] -pub struct X25519Keypair { - secret_key: StaticSecret, - public_key: x25519_dalek::PublicKey, -} - -#[wasm_bindgen] -impl X25519Keypair { - /// Generate an x25519 encryption keypair - #[wasm_bindgen(js_name = generate)] - pub fn generate() -> Result { - let secret_key = StaticSecret::random_from_rng(OsRng); - let public_key = x25519_dalek::PublicKey::from(&secret_key); - Ok(X25519Keypair { secret_key, public_key }) - } - - #[wasm_bindgen(js_name = secretKey)] - pub fn secret_key(&self) -> Vec { - self.secret_key.as_bytes().to_vec() - } - - #[wasm_bindgen(js_name = publicKey)] - pub fn public_key(&self) -> Vec { - self.public_key.as_bytes().to_vec() - } -} - /// Functions for creating and using `EncryptedSignedMessage`s which use HPKE for chacha20poly1305 /// encryption and x25519 key agreement and sr25519 for signing. #[wasm_bindgen] @@ -58,6 +31,7 @@ impl Hpke { } /// Encrypts, signs, and serializes an `EncryptedSignedMessage` to JSON. + /// Takes a secret sr25519 siging key. #[wasm_bindgen(js_name = encryptAndSign)] pub fn encrypt_and_sign( sr25519_secret_key: Vec, @@ -82,14 +56,19 @@ impl Hpke { } /// Deserializes, verifies and decrypts a json encoded `EncryptedSignedMessage`. + /// Takes a x25519 secret encryption key. /// Returns the plaintext. #[wasm_bindgen(js_name = decryptAndVerify)] - pub fn decrypt_and_verify(secret_key: Vec, message: String) -> Result, Error> { + pub fn decrypt_and_verify( + x25519_secret_key: Vec, + message: String, + ) -> Result, Error> { let encrypted_message: EncryptedSignedMessage = serde_json::from_str(message.as_str()).map_err(|err| Error::new(&err.to_string()))?; - let secret_key: [u8; 32] = - secret_key.try_into().map_err(|_| Error::new("X25519 secret key must be 32 bytes"))?; + let secret_key: [u8; 32] = x25519_secret_key + .try_into() + .map_err(|_| Error::new("X25519 secret key must be 32 bytes"))?; let signed_message = encrypted_message .decrypt(&secret_key.into(), &[]) @@ -102,6 +81,47 @@ impl Hpke { } } +/// An x25519 encryption keypair +#[wasm_bindgen] +pub struct X25519Keypair { + secret_key: StaticSecret, + public_key: x25519_dalek::PublicKey, +} + +#[wasm_bindgen] +impl X25519Keypair { + /// Constructor to randomly generate an x25519 encryption keypair + #[wasm_bindgen(js_name = generate)] + pub fn generate() -> Result { + let secret_key = StaticSecret::random_from_rng(OsRng); + let public_key = x25519_dalek::PublicKey::from(&secret_key); + Ok(X25519Keypair { secret_key, public_key }) + } + + /// Constructor to create a keypair from a given secret key + #[wasm_bindgen(js_name = fromSecretKey)] + pub fn from_secret_key(secret_key: Vec) -> Result { + let secret_key: [u8; 32] = + secret_key.try_into().map_err(|_| Error::new("X25519 secret key must be 32 bytes"))?; + + let secret_key: StaticSecret = secret_key.into(); + let public_key = x25519_dalek::PublicKey::from(&secret_key); + Ok(X25519Keypair { secret_key, public_key }) + } + + /// Getter for the secret key + #[wasm_bindgen(js_name = secretKey)] + pub fn secret_key(&self) -> Vec { + self.secret_key.as_bytes().to_vec() + } + + /// Getter for the public key + #[wasm_bindgen(js_name = publicKey)] + pub fn public_key(&self) -> Vec { + self.public_key.as_bytes().to_vec() + } +} + /// Convert a Vec to a hex encoded string #[wasm_bindgen(js_name = toHex)] pub fn to_hex(v: Vec) -> String { From 22cc07b0f959f2bddbfa25aee24c4730971716d2 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 10 Apr 2024 10:15:57 +0200 Subject: [PATCH 28/35] Changelog --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 782198ff8..33ff97331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,23 @@ At the moment this project **does not** adhere to ## [[Unreleased]](https://github.com/entropyxyz/entropy-core/compare/release/v0.0.11...master) +### Breaking Changes + +- In [#709](https://github.com/entropyxyz/entropy-core/pull/709) 'Derive the threshold account + keypair and x25519 keypair from mnemonic using HKDF' the JS `entropy-protocol` bindings have + changed. `Hpke.DecryptAndVerify` now takes a secret x25519 encryption key rather than a secret + sr25519 signing key. The `runDkgProtocol` and `runSigningProtocol` functions now both take a + secret x25519 key as an additional argument, since these are no longer derived from the given + signing secret key. Similarly in the rust API, `EncryptedSignedMessage` no longer derives x25519 + keypairs internally and so the decrypt method now takes a x25519 secret key. Also, the method by + which keypairs are derived from a mnemonic has changed, which means existing validators x25119 + and sr25519 keypairs will be different what they were before. + +### Added + +### Changed +- Derive the threshold account keypair and x25519 keypair from mnemonic using HKDF ([#709](https://github.com/entropyxyz/entropy-core/pull/709)) + ## [0.0.11](https://github.com/entropyxyz/entropy-core/compare/release/v0.0.10...release/v0.0.11) - 2024-04-XX ### Breaking Changes @@ -83,6 +100,7 @@ At the moment this project **does not** adhere to - Rename `pallet_relayer` to `pallet_registry` ([#661](https://github.com/entropyxyz/entropy-core/pull/661)) - Remove permissioned access type ([#666](https://github.com/entropyxyz/entropy-core/pull/666)) - Use SessionID in shared randomness ([#676](https://github.com/entropyxyz/entropy-core/pull/676)) +- Derive the threshold account keypair and x25519 keypair from mnemonic using HKDF ### Removed - Remove `pallet-free-tx` ([#662](https://github.com/entropyxyz/entropy-core/pull/662)) From b3274b1aeb2b06d2e24400ffd41fd080ab1428c3 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 10 Apr 2024 10:17:51 +0200 Subject: [PATCH 29/35] Standard formatting for JS tests --- crates/protocol/nodejs-test/index.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/protocol/nodejs-test/index.test.js b/crates/protocol/nodejs-test/index.test.js index 4398172d7..f34b88af8 100644 --- a/crates/protocol/nodejs-test/index.test.js +++ b/crates/protocol/nodejs-test/index.test.js @@ -36,9 +36,9 @@ test('Encrypt, decrypt with HPKE', function (t) { // Malloy cannot decrypt the message. let error try { - decryptAndVerify(malloryX25519Keypair.secretKey(), encryptedAndSignedMessage) + decryptAndVerify(malloryX25519Keypair.secretKey(), encryptedAndSignedMessage) } catch (e) { - error = e.toString() + error = e.toString() } t.equals(error, 'Error: Hpke: HPKE Error: OpenError') }) From 37d6b025adfa6d6f05549cd6fe6c9ec56df4ccda Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 10 Apr 2024 10:42:37 +0200 Subject: [PATCH 30/35] Tidy, error handling --- crates/testing-utils/src/constants.rs | 3 +++ crates/testing-utils/src/test_client/mod.rs | 10 ++++++++-- .../src/helpers/validator.rs | 4 ++-- crates/threshold-signature-server/src/user/errors.rs | 8 ++++++++ node/cli/src/chain_spec/mod.rs | 4 ++-- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/crates/testing-utils/src/constants.rs b/crates/testing-utils/src/constants.rs index ec3938ea7..45aec2760 100644 --- a/crates/testing-utils/src/constants.rs +++ b/crates/testing-utils/src/constants.rs @@ -43,10 +43,13 @@ lazy_static! { ]; } +/// This is a random secret key for Eve. The corresponding public key is in the development +/// chainspec as Eve is a pre-registered private user pub const EVE_X25519_SECRET_KEY: [u8; 32] = [ 58, 47, 10, 154, 181, 71, 222, 205, 42, 186, 181, 1, 55, 107, 46, 200, 226, 62, 42, 137, 142, 3, 101, 208, 129, 168, 22, 236, 116, 159, 8, 55, ]; +/// This is a random secret key for Ferdie used in some negative tests pub const FERDIE_X25519_SECRET_KEY: [u8; 32] = [ 5, 221, 127, 62, 254, 131, 37, 194, 88, 126, 130, 15, 97, 249, 170, 40, 201, 135, 77, 213, 55, 87, 243, 127, 175, 77, 251, 75, 157, 119, 41, 180, diff --git a/crates/testing-utils/src/test_client/mod.rs b/crates/testing-utils/src/test_client/mod.rs index 48c479584..e662b7fa4 100644 --- a/crates/testing-utils/src/test_client/mod.rs +++ b/crates/testing-utils/src/test_client/mod.rs @@ -90,10 +90,16 @@ pub async fn register( // If registering with private key visibility, participate in the DKG protocol let keyshare_option = match key_visibility { - KeyVisibility::Private(_x25519_pk) => { + KeyVisibility::Private(x25519_public_key) => { let x25519_secret_key = x25519_secret_key .ok_or(anyhow!("In private mode, an x25519 secret key must be given"))?; - // TODO ensure!(the public key matches that from key_visibility) + + let x25519_public_key_check = + x25519_dalek::PublicKey::from(&x25519_secret_key).to_bytes(); + ensure!( + x25519_public_key_check == x25519_public_key, + "Given x25519 secret key does not match that from key visibility" + ); let block_number = rpc .chain_get_header(None) diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs index c3667e042..4a0de5a32 100644 --- a/crates/threshold-signature-server/src/helpers/validator.rs +++ b/crates/threshold-signature-server/src/helpers/validator.rs @@ -27,6 +27,7 @@ use zeroize::Zeroize; use crate::{chain_api::EntropyConfig, user::UserErr}; +/// Constants used in the derivation path const KDF_SR25519: &[u8] = b"sr25519-threshold-account"; const KDF_X25519: &[u8] = b"X25519-keypair"; @@ -81,8 +82,7 @@ fn get_signer_from_hkdf( hkdf: &Hkdf, ) -> Result, UserErr> { let mut sr25519_seed = [0u8; 32]; - //TODO - hkdf.expand(KDF_SR25519, &mut sr25519_seed).expect("Cannot get 32 byte output from sha256"); + hkdf.expand(KDF_SR25519, &mut sr25519_seed)?; let pair = sr25519::Pair::from_seed(&sr25519_seed); sr25519_seed.zeroize(); diff --git a/crates/threshold-signature-server/src/user/errors.rs b/crates/threshold-signature-server/src/user/errors.rs index d7d5e7068..d3951a3e0 100644 --- a/crates/threshold-signature-server/src/user/errors.rs +++ b/crates/threshold-signature-server/src/user/errors.rs @@ -153,6 +153,14 @@ pub enum UserErr { CustomHashOutOfBounds, #[error("Error creating sr25519 keypair from seed: {0}")] SpCoreSecretString(#[from] sp_core::crypto::SecretStringError), + #[error("Cannot get output from hasher in HKDF {0}")] + Hkdf(hkdf::InvalidLength), +} + +impl From for UserErr { + fn from(invalid_length: hkdf::InvalidLength) -> UserErr { + UserErr::Hkdf(invalid_length) + } } impl IntoResponse for UserErr { diff --git a/node/cli/src/chain_spec/mod.rs b/node/cli/src/chain_spec/mod.rs index 20e3c0d8e..bb8397661 100644 --- a/node/cli/src/chain_spec/mod.rs +++ b/node/cli/src/chain_spec/mod.rs @@ -107,8 +107,8 @@ pub mod tss_x25519_public_key { 4, 37, 87, 150, 250, 210, 151, 203, 137, 188, 134, 124, 108, ]; - /// Not sure what mnemonic is used to derive the following public key. - /// Mnemonic: "????" + /// Eve is not a TSS node but a pre-registered user. Her corresponding secret key is in + /// `entropy_testing_utils::constants` pub const EVE: [u8; 32] = [ 142, 113, 91, 59, 177, 104, 208, 23, 219, 170, 47, 145, 200, 139, 188, 28, 14, 199, 116, 86, 193, 144, 10, 18, 74, 157, 138, 202, 115, 99, 229, 55, From 7584579063827f93efcadabd4bb6d48291020c34 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 10 Apr 2024 10:43:08 +0200 Subject: [PATCH 31/35] Error handling --- crates/threshold-signature-server/src/helpers/validator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs index 4a0de5a32..cb4a288b9 100644 --- a/crates/threshold-signature-server/src/helpers/validator.rs +++ b/crates/threshold-signature-server/src/helpers/validator.rs @@ -91,7 +91,7 @@ fn get_signer_from_hkdf( fn get_x25519_secret_from_hkdf(hkdf: &Hkdf) -> Result { let mut secret = [0u8; 32]; - hkdf.expand(KDF_X25519, &mut secret).expect("Cannot get 32 byte output from sha256"); + hkdf.expand(KDF_X25519, &mut secret)?; let static_secret = StaticSecret::from(secret); secret.zeroize(); Ok(static_secret) From 595d6c26a0d33fbee6f27105e66c54a79fbfeece Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 10 Apr 2024 10:47:38 +0200 Subject: [PATCH 32/35] Comments --- crates/threshold-signature-server/src/helpers/validator.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs index cb4a288b9..41911e887 100644 --- a/crates/threshold-signature-server/src/helpers/validator.rs +++ b/crates/threshold-signature-server/src/helpers/validator.rs @@ -31,9 +31,9 @@ use crate::{chain_api::EntropyConfig, user::UserErr}; const KDF_SR25519: &[u8] = b"sr25519-threshold-account"; const KDF_X25519: &[u8] = b"X25519-keypair"; -/// Returns PairSigner for this nodes threshold server. +/// Returns a PairSigner for this node's threshold server. /// The PairSigner is stored as an encrypted mnemonic in the kvdb and -/// is used for PKE and to submit extrensics on chain. +/// is used to sign encrypted messages and to submit extrinsics on chain. pub async fn get_signer( kv: &KvManager, ) -> Result, UserErr> { @@ -78,6 +78,7 @@ fn get_hkdf_from_mnemonic(mnemonic: &str) -> Result, UserErr> { Ok(Hkdf::::new(None, &mnemonic.to_seed(""))) } +/// Derive signing keypair fn get_signer_from_hkdf( hkdf: &Hkdf, ) -> Result, UserErr> { @@ -89,6 +90,7 @@ fn get_signer_from_hkdf( Ok(PairSigner::::new(pair)) } +/// Derive x25519 secret fn get_x25519_secret_from_hkdf(hkdf: &Hkdf) -> Result { let mut secret = [0u8; 32]; hkdf.expand(KDF_X25519, &mut secret)?; From ab6fc64060a1b302d203c7b975b56d82457e79e5 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 10 Apr 2024 13:21:29 +0200 Subject: [PATCH 33/35] Revert generic-array to 0.14.6 --- crates/testing-utils/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/testing-utils/Cargo.toml b/crates/testing-utils/Cargo.toml index 27f92aa18..e7d004f2c 100644 --- a/crates/testing-utils/Cargo.toml +++ b/crates/testing-utils/Cargo.toml @@ -34,7 +34,7 @@ serde_json ="1.0" thiserror ="1.0.55" x25519-dalek ={ version="2.0.1", features=["static_secrets"] } zeroize ="1.5.7" -generic-array ="1.0.0" +generic-array ="0.14.6" entropy-protocol={ path="../protocol" } reqwest ={ version="0.12.3", features=["json", "stream"] } sha3 ="0.10.8" From 6885d8fdc7c9c4d8447b2f65f11c09b328b406bd Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 10 Apr 2024 13:21:41 +0200 Subject: [PATCH 34/35] Revert generic-array to 0.14.6 --- Cargo.lock | 13 ++----------- crates/protocol/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da5f6fa3b..18e2e8f9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2610,7 +2610,7 @@ dependencies = [ "blake2 0.10.6", "entropy-shared", "futures", - "generic-array 1.0.0", + "generic-array 0.14.7", "getrandom 0.2.13", "gloo-net", "hex", @@ -2772,7 +2772,7 @@ dependencies = [ "entropy-shared", "entropy-tss", "futures", - "generic-array 1.0.0", + "generic-array 0.14.7", "hex", "hex-literal", "lazy_static", @@ -3796,15 +3796,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "generic-array" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe739944a5406424e080edccb6add95685130b9f160d5407c639c7df0c5836b0" -dependencies = [ - "typenum", -] - [[package]] name = "gethostname" version = "0.2.3" diff --git a/crates/protocol/Cargo.toml b/crates/protocol/Cargo.toml index 7c40b6c21..490e6b4bf 100644 --- a/crates/protocol/Cargo.toml +++ b/crates/protocol/Cargo.toml @@ -28,7 +28,7 @@ tracing ="0.1.37" bincode ="1.3.3" serde_json ="1.0" zeroize ="1.5.7" -generic-array ="1.0.0" +generic-array ="0.14.6" hpke-rs ="0.2.0" hpke-rs-crypto ="0.2.0" hpke-rs-rust-crypto="0.2.0" From e98a7e66b64ca28d4a1fc853e9a76d14401b3c5d Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 15 Apr 2024 11:26:36 +0200 Subject: [PATCH 35/35] Mv test fn to bottom of file --- .../src/helpers/validator.rs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs index 41911e887..52b82fc0b 100644 --- a/crates/threshold-signature-server/src/helpers/validator.rs +++ b/crates/threshold-signature-server/src/helpers/validator.rs @@ -52,17 +52,6 @@ pub async fn get_signer_and_x25519_secret( Ok((pair_signer, static_secret)) } -/// For testing where we sometimes don't have access to the kvdb, derive directly from the mnemnic -#[cfg(test)] -pub fn get_signer_and_x25519_secret_from_mnemonic( - mnemonic: &str, -) -> Result<(PairSigner, StaticSecret), UserErr> { - let hkdf = get_hkdf_from_mnemonic(mnemonic)?; - let pair_signer = get_signer_from_hkdf(&hkdf)?; - let static_secret = get_x25519_secret_from_hkdf(&hkdf)?; - Ok((pair_signer, static_secret)) -} - /// Get the key derivation struct to derive secret keys from a mnemonic stored in the KVDB async fn get_hkdf(kv: &KvManager) -> Result, UserErr> { let _ = kv.kv().exists("MNEMONIC").await?; @@ -98,3 +87,14 @@ fn get_x25519_secret_from_hkdf(hkdf: &Hkdf) -> Result Result<(PairSigner, StaticSecret), UserErr> { + let hkdf = get_hkdf_from_mnemonic(mnemonic)?; + let pair_signer = get_signer_from_hkdf(&hkdf)?; + let static_secret = get_x25519_secret_from_hkdf(&hkdf)?; + Ok((pair_signer, static_secret)) +}