Last active
May 26, 2024 19:52
-
-
Save calvinchengx/67393a3b06ea9f3c2cb417bf7f611bfc to your computer and use it in GitHub Desktop.
Threshold Signature Scheme
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use aes::Aes256; | |
use block_modes::block_padding::Pkcs7; | |
use block_modes::{BlockMode, Cbc}; | |
use bls_signatures::{AggregateSignature, Signature}; | |
use pallier::Pallier; | |
use rand::Rng; | |
use rug::Integer; | |
type Aes256Cbc = Cbc<Aes256, Pkcs7>; | |
// Structure representing a participant in the DKG protocol and threshold signature scheme | |
struct Participant { | |
id: usize, // Participant ID | |
secret_key: Integer, // Secret key of the participant | |
public_key: Integer, // Public key of the participant | |
commitment: Integer, // Commitment of the participant | |
received_commitments: Vec<(usize, Integer)>, // Received commitments from other participants | |
} | |
impl Participant { | |
// Generate a random secret key and compute the public key | |
fn generate_key_pair() -> (Integer, Integer) { | |
let mut rng = rand::thread_rng(); | |
let secret_key = Integer::from(rng.gen_range(1..100)); // Random secret key | |
let public_key = Integer::from(2) * &secret_key; // Compute public key | |
(secret_key, public_key) | |
} | |
// Generate an encryption key for the participant | |
fn generate_encryption_key() -> Vec<u8> { | |
let mut rng = rand::thread_rng(); | |
let mut key = vec![0u8; 32]; | |
rng.fill_bytes(&mut key); | |
key | |
} | |
// Generate a cryptographic commitment to the secret key | |
fn generate_commitment(&self) -> Integer { | |
let mut rng = rand::thread_rng(); | |
let r = Integer::from(rng.gen_range(1..100)); // Random blinding factor | |
let commitment = (&self.secret_key + &r) % Integer::from(100); // Commitment computation | |
commitment | |
} | |
// Broadcast the commitment to all participants | |
fn broadcast_commitment(&self, participants: &[Participant]) { | |
for participant in participants { | |
// Skip broadcasting to oneself | |
if participant.id == self.id { | |
continue; | |
} | |
// Simulate broadcasting the commitment to participant | |
// You can replace this with actual network communication | |
println!("Participant {} broadcasts commitment {} to participant {}", self.id, self.commitment, participant.id); | |
} | |
} | |
// Verify the commitments received from other participants | |
fn verify_commitments(&mut self, participants: &[Participant]) -> bool { | |
for (participant_id, commitment) in &self.received_commitments { | |
let participant = participants | |
.iter() | |
.find(|p| p.id == *participant_id) | |
.unwrap(); | |
let expected_commitment = participant.generate_commitment(); | |
if commitment != &expected_commitment { | |
return false; | |
} | |
} | |
true | |
} | |
// Perform secure multi-party computation to compute the shared secret | |
fn compute_shared_secret(&self, participants: &[Participant]) -> Integer { | |
let mut shared_secret = Integer::from(0); | |
// Create a Pallier instance | |
let pallier = Pallier::new(); | |
// Encrypt the secret key | |
let encrypted_secret_key = pallier.encrypt(&self.secret_key); | |
for participant in participants { | |
if participant.id != self.id { | |
// Decrypt the received encrypted secret key | |
let decrypted_secret_key = pallier.decrypt(&encrypted_secret_key); | |
// Perform secure addition of secret keys | |
shared_secret += &decrypted_secret_key; | |
} | |
} | |
shared_secret | |
} | |
// Encrypt a message | |
// converted to byte arrays and used as the encryption/decryption keys for AES-256 in CBC mode | |
fn encrypt_message(&self, message: &[u8]) -> Vec<u8> { | |
let cipher = Aes256Cbc::new_var(&self.encryption_key, &[0; 16]).unwrap(); | |
let ciphertext = cipher.encrypt_vec(message); | |
ciphertext | |
} | |
// Decrypt a message | |
// converted to byte arrays and used as the encryption/decryption keys for AES-256 in CBC mode | |
fn decrypt_message(&self, ciphertext: &[u8]) -> Vec<u8> { | |
let cipher = Aes256Cbc::new_var(&self.encryption_key, &[0; 16]).unwrap(); | |
let decrypted_message = cipher.decrypt_vec(ciphertext).unwrap(); | |
decrypted_message | |
} | |
// Sign a message using the secret key | |
fn sign_message(&self, message: &[u8]) -> Signature { | |
let secret_key_bytes = self.secret_key.to_string().into_bytes(); | |
let secret_key = bls_signatures::PrivateKey::from_bytes(&secret_key_bytes).unwrap(); | |
let signature = secret_key.sign(message); | |
signature | |
} | |
} | |
fn main() { | |
// Number of participants | |
let num_participants = 5; | |
// Generate keys and commitments for all participants | |
let mut participants: Vec<Participant> = Vec::new(); | |
for id in 0..num_participants { | |
let (secret_key, public_key) = Participant::generate_key_pair(); | |
let commitment = Participant::generate_commitment(&secret_key); | |
// Generate encryption key | |
let mut encryption_key = [0u8; 32]; | |
rand::thread_rng().fill(&mut encryption_key); | |
let participant = Participant { | |
id, | |
secret_key, | |
public_key, | |
commitment, | |
received_commitments: Vec::new(), | |
encryption_key, | |
}; | |
participants.push(participant); | |
} | |
// Commitment Phase | |
for participant in &participants { | |
// Store the commitment in the participant | |
// You can replace this with actual network communication to broadcast the commitment | |
// and receive commitments from other participants | |
participants[participant.id].commitment = participant.commitment; | |
// Print the commitment for demonstration purposes | |
println!("Participant {} commitment: {}", participant.id, participant.commitment); | |
} | |
// Broadcast the commitments to all participants | |
for participant in &participants { | |
participant.broadcast_commitment(&participants); | |
} | |
// Verification Phase | |
for participant in &mut participants { | |
let verified = participant.verify_commitments(&participants); | |
if verified { | |
println!("Participant {} commitments verified", participant.id); | |
} else { | |
println!("Participant {} commitments verification failed", participant.id); | |
} | |
} | |
// Compute the shared secret | |
for participant in &participants { | |
let shared_secret = participant.compute_shared_secret(&participants); | |
println!("Participant {} shared secret: {}", participant.id, shared_secret); | |
} | |
// Sign a message using the secret keys | |
let message = b"Hello, world!"; | |
let mut signatures: Vec<Signature> = Vec::new(); | |
for participant in &participants { | |
let signature = participant.sign_message(message); | |
signatures.push(signature); | |
} | |
// Aggregate the signatures from all participants | |
let aggregated_signature = AggregateSignature::from_signatures(&signatures).unwrap(); | |
// Verify the aggregated signature using the public keys of all participants | |
let public_keys: Vec<bls_signatures::PublicKey> = participants | |
.iter() | |
.map(|p| { | |
let secret_key_bytes = p.secret_key.to_string().into_bytes(); | |
let secret_key = bls_signatures::PrivateKey::from_bytes(&secret_key_bytes).unwrap(); | |
secret_key.public_key() | |
}) | |
.collect(); | |
let is_valid = aggregated_signature.verify(message, &public_keys); | |
if is_valid { | |
println!("Aggregated signature is valid"); | |
} else { | |
println!("Aggregated signature is invalid"); | |
} | |
// Encrypt and decrypt a message using the encryption keys | |
let encrypted_message = participants[0].encrypt_message(message); | |
println!("Encrypted message: {:?}", encrypted_message); | |
let decrypted_message = participants[0].decrypt_message(&encrypted_message); | |
println!("Decrypted message: {:?}", decrypted_message); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn test_generate_key_pair() { | |
let (secret_key, public_key) = Participant::generate_key_pair(); | |
// Check if secret_key is not zero | |
assert_ne!(secret_key, Integer::from(0)); | |
// Check if public_key is twice the value of secret_key | |
assert_eq!(public_key, Integer::from(2) * &secret_key); | |
} | |
#[test] | |
fn test_generate_encryption_key() { | |
let encryption_key = Participant::generate_encryption_key(); | |
// Check if encryption_key length is 32 bytes | |
assert_eq!(encryption_key.len(), 32); | |
} | |
#[test] | |
fn test_generate_commitment() { | |
let participant = Participant { | |
id: 0, | |
secret_key: Integer::from(42), | |
public_key: Integer::from(84), | |
commitment: Integer::from(0), | |
received_commitments: Vec::new(), | |
}; | |
let commitment = participant.generate_commitment(); | |
// Check if commitment is within the range [0, 99] | |
assert!(commitment >= Integer::from(0) && commitment <= Integer::from(99)); | |
} | |
#[test] | |
fn test_verify_commitments() { | |
let participant1 = Participant { | |
id: 0, | |
secret_key: Integer::from(42), | |
public_key: Integer::from(84), | |
commitment: Integer::from(0), | |
received_commitments: Vec::new(), | |
}; | |
let participant2 = Participant { | |
id: 1, | |
secret_key: Integer::from(50), | |
public_key: Integer::from(100), | |
commitment: Integer::from(0), | |
received_commitments: Vec::new(), | |
}; | |
participant1.commitment = participant1.generate_commitment(); | |
participant2.commitment = participant2.generate_commitment(); | |
participant1.received_commitments.push((1, participant2.commitment)); | |
participant2.received_commitments.push((0, participant1.commitment)); | |
let participants = vec![participant1, participant2]; | |
let mut participant1_clone = participants[0].clone(); | |
let mut participant2_clone = participants[1].clone(); | |
// Check if commitments are verified successfully | |
assert!(participant1_clone.verify_commitments(&participants)); | |
assert!(participant2_clone.verify_commitments(&participants)); | |
// Modify participant1's commitment | |
participant1_clone.commitment = Integer::from(99); | |
// Check if commitments verification fails when a commitment is modified | |
assert!(!participant1_clone.verify_commitments(&participants)); | |
assert!(!participant2_clone.verify_commitments(&participants)); | |
} | |
#[test] | |
fn test_compute_shared_secret() { | |
let participant1 = Participant { | |
id: 0, | |
secret_key: Integer::from(42), | |
public_key: Integer::from(84), | |
commitment: Integer::from(0), | |
received_commitments: Vec::new(), | |
}; | |
let participant2 = Participant { | |
id: 1, | |
secret_key: Integer::from(50), | |
public_key: Integer::from(100), | |
commitment: Integer::from(0), | |
received_commitments: Vec::new(), | |
}; | |
participant1.commitment = participant1.generate_commitment(); | |
participant2.commitment = participant2.generate_commitment(); | |
let participants = vec![participant1, participant2]; | |
let participant1_shared_secret = participants[0].compute_shared_secret(&participants); | |
let participant2_shared_secret = participants[1].compute_shared_secret(&participants); | |
// Check if shared secrets are equal when secret keys are added securely | |
assert_eq!(participant1_shared_secret, participant2_shared_secret); | |
// Modify participant1's secret key | |
let mut participant1_clone = participants[0].clone(); | |
participant1_clone.secret_key = Integer::from(99); | |
let participant1_modified_shared_secret = | |
participant1_clone.compute_shared_secret(&participants); | |
// Check if shared secret changes when a participant's secret key is modified | |
assert_ne!(participant1_shared_secret, participant1_modified_shared_secret); | |
} | |
#[test] | |
fn test_encrypt_decrypt_message() { | |
let participant = Participant { | |
id: 0, | |
secret_key: Integer::from(42), | |
public_key: Integer::from(84), | |
commitment: Integer::from(0), | |
received_commitments: Vec::new(), | |
}; | |
let message = b"Hello, world!"; | |
let encrypted_message = participant.encrypt_message(message); | |
let decrypted_message = participant.decrypt_message(&encrypted_message); | |
// Check if decrypted message matches the original message | |
assert_eq!(decrypted_message, message); | |
} | |
#[test] | |
fn test_sign_message() { | |
let participant = Participant { | |
id: 0, | |
secret_key: Integer::from(42), | |
public_key: Integer::from(84), | |
commitment: Integer::from(0), | |
received_commitments: Vec::new(), | |
}; | |
let message = b"Hello, world!"; | |
let signature = participant.sign_message(message); | |
// Check if the signature is valid for the message using the participant's secret key | |
assert!(signature.verify(message, &participant.secret_key)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
truct Participant { | |
// ... | |
ws: Option<tungstenite::WebSocket<tokio::net::TcpStream>>, | |
} | |
impl Participant { | |
// ... | |
// Send the commitment to another participant over WebSocket | |
async fn send_commitment(&self, participant_id: usize, commitment: Integer) { | |
if let Some(ws) = &self.ws { | |
let message = Message::Text(format!("{}:{}", participant_id, commitment)); | |
if let Err(err) = ws.send(message).await { | |
eprintln!("Failed to send commitment to participant {}: {}", participant_id, err); | |
} | |
} | |
} | |
fn broadcast_commitment(&self, participants: &[Participant]) { | |
for participant in participants { | |
// Skip broadcasting to oneself | |
if participant.id == self.id { | |
continue; | |
} | |
// Get the WebSocket connection of the participant | |
if let Some(ws) = &self.ws { | |
// Serialize and send the commitment | |
let message = Message::Text(format!("{}:{}", self.id, self.commitment)); | |
if let Err(err) = ws.send(message) { | |
eprintln!("Failed to send commitment to participant {}: {}", participant.id, err); | |
} | |
} | |
} | |
} | |
// Establish a WebSocket connection with the given server address | |
async fn connect(&mut self, server_address: &str) -> Result<(), tungstenite::Error> { | |
let (ws_stream, _) = TcpStream::connect(server_address).await?; | |
let (mut ws, _) = tungstenite::client_async(server_address, ws_stream).await?; | |
self.ws = Some(ws); | |
Ok(()) | |
} | |
} | |
#[tokio::main] | |
async fn main() { | |
// ... | |
// Commitment Phase | |
for participant in &participants { | |
// ... | |
// Establish WebSocket connections with all participants | |
let mut participant_clone = participant.clone(); | |
let server_address = "127.0.0.1:8080"; // Change this to the actual server address | |
if let Err(err) = participant_clone.connect(server_address).await { | |
eprintln!("Failed to connect to participant {}: {}", participant.id, err); | |
} | |
// Store the commitment in the participant | |
participant_clone.commitment = participant.commitment; | |
} | |
// ... | |
// WebSocket server address | |
let server_address = "127.0.0.1:8080"; // Change this to the desired server address | |
// Start the WebSocket server | |
let listener = TcpListener::bind(server_address).await.unwrap(); | |
let server = listener | |
.incoming() | |
.for_each(|stream| { | |
let addr = stream.peer_addr().unwrap(); | |
let ws_stream = tokio_tungstenite::accept_async(stream).map(move |ws| { | |
handle_connection(ws, addr, participants.clone()); | |
}); | |
tokio::spawn(ws_stream); | |
future::ready(()) | |
}); | |
if let Err(err) = server.await { | |
eprintln!("WebSocket server error: {:?}", err); | |
} | |
} | |
#[tokio::main] | |
async fn main() { | |
// ... | |
// Commitment Phase | |
for participant in &participants { | |
// ... | |
// Establish WebSocket connections with all participants | |
let mut participant_clone = participant.clone(); | |
let server_address = "127.0.0.1:8080"; // Change this to the actual server address | |
if let Err(err) = participant_clone.connect(server_address).await { | |
eprintln!("Failed to connect to participant {}: {}", participant.id, err); | |
} | |
// Store the commitment in the participant | |
participant_clone.commitment = participant.commitment; | |
} | |
// ... | |
// WebSocket server address | |
let server_address = "127.0.0.1:8080"; // Change this to the desired server address | |
// Start the WebSocket server | |
let listener = TcpListener::bind(server_address).await.unwrap(); | |
let server = listener | |
.incoming() | |
.for_each(|stream| { | |
let addr = stream.peer_addr().unwrap(); | |
let ws_stream = tokio_tungstenite::accept_async(stream).map(move |ws| { | |
handle_connection(ws, addr, participants.clone()); | |
}); | |
tokio::spawn(ws_stream); | |
future::ready(()) | |
}); | |
if let Err(err) = server.await { | |
eprintln!("WebSocket server error: {:?}", err); | |
} | |
} | |
async fn handle_connection( | |
ws: tungstenite::WebSocket<tokio::net::TcpStream>, | |
addr: std::net::SocketAddr, | |
participants: Arc<Vec<Participant>>, | |
) { | |
// Find the participant based on the WebSocket connection | |
let participant_id = participants | |
.iter() | |
.position(|p| p.ws.as_ref().unwrap().peer_addr().unwrap() == addr); | |
if let Some(participant_id) = participant_id { | |
let participant = &participants[participant_id]; | |
// Store the WebSocket connection in the participant | |
participants[participant_id].ws = Some(ws); | |
while let Some(message) = participant.ws.as_ref().unwrap().next().await { | |
match message { | |
Ok(Message::Text(text)) => { | |
// Extract the participant ID and commitment from the received message | |
let mut parts = text.splitn(2, ':'); | |
if let (Some(participant_id_str), Some(commitment_str)) = (parts.next(), parts.next()) { | |
if let (Ok(participant_id), Ok(commitment)) = ( | |
participant_id_str.parse::<usize>(), | |
commitment_str.parse::<Integer>(), | |
) { | |
// Store the received commitment in the participant | |
participants[participant_id].received_commitments.push((participant.id, commitment)); | |
// Verify the commitments if all commitments are received | |
if participants[participant_id].received_commitments.len() == participants.len() - 1 { | |
let verified = participants[participant_id].verify_commitments(&participants); | |
if verified { | |
println!("Participant {} commitments verified", participant_id); | |
} else { | |
println!("Participant {} commitments verification failed", participant_id); | |
} | |
} | |
} | |
} | |
} | |
Ok(_) => { | |
eprintln!("Unexpected message format received from participant {}: {:?}", participant_id, message); | |
} | |
Err(err) => { | |
eprintln!("WebSocket error from participant {}: {:?}", participant_id, err); | |
break; | |
} | |
} | |
} | |
// Remove the WebSocket connection from the participant | |
participants[participant_id].ws = None; | |
} | |
} |
Related example using TSS' shared secret to handle encryption and decryption - https://github.com/poanetwork/threshold_crypto
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Using the secret key for encryption and decryption may have the following security implications:
Encryption Key Reuse: The secret key is primarily designed for signing operations in the context of threshold signatures. Reusing the secret key for encryption purposes violates the principle of key separation. It is generally recommended to use separate key pairs for different cryptographic operations to mitigate potential vulnerabilities. Reusing the secret key for encryption may weaken the security of the threshold signature scheme and expose the system to attacks.
Key Length and Security: Threshold signature schemes typically require larger key sizes compared to encryption schemes. Secret keys used for threshold signatures are generally generated with the aim of providing sufficient security against attacks such as brute-force or factorization. However, encryption schemes have different key length requirements and security considerations. The secret key used for threshold signatures may not meet the recommended key length for encryption, potentially weakening the security of the encrypted data.
Key Exposure: When using the secret key for encryption and decryption, there is a higher likelihood of key exposure. Encryption keys are typically used in a wider range of operations and may be more susceptible to accidental or intentional exposure. If the secret key used for threshold signatures is exposed through the encryption process, it can compromise the security of the entire system, including the integrity and authenticity of the signatures.
Compatibility and Interoperability: Using the secret key for encryption and decryption may introduce compatibility and interoperability challenges. Encryption algorithms, modes, and protocols may have specific requirements and expectations for the encryption key format and size. The secret key used in threshold signatures may not adhere to these requirements, leading to potential compatibility issues with existing encryption libraries or systems.
To maintain strong security and minimize potential vulnerabilities, it is generally recommended to use dedicated and separate key pairs for different cryptographic operations. This ensures proper key separation, enables the use of algorithms and key lengths specifically designed for each operation, and reduces the risk of key exposure and compatibility issues.