Skip to content

Instantly share code, notes, and snippets.

@calvinchengx
Last active May 26, 2024 19:52
Show Gist options
  • Save calvinchengx/67393a3b06ea9f3c2cb417bf7f611bfc to your computer and use it in GitHub Desktop.
Save calvinchengx/67393a3b06ea9f3c2cb417bf7f611bfc to your computer and use it in GitHub Desktop.
Threshold Signature Scheme
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);
}
#[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));
}
}
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;
}
}
@calvinchengx
Copy link
Author

calvinchengx commented Jun 15, 2023

The computed shared secret serves as the collective secret key in a threshold signature scheme. It is used to perform distributed key signing, where multiple participants collaboratively generate a signature without any single participant possessing the full signing capability. The shared secret allows for distributed and secure signing of messages.

Here's a high-level overview of how the computed shared secret is used in the threshold signature scheme:

  1. Distributed Key Generation: The participants collectively compute the shared secret by securely combining their individual secret keys using secure multi-party computation (SMPC) techniques. Each participant contributes their secret key, and the shared secret is derived without revealing the individual secret keys.

  2. Signature Generation: To generate a threshold signature, each participant uses their partial signing capability. They take the message to be signed, the shared secret, and their individual secret key to compute a partial signature. The participants then combine their partial signatures using a distributed signature aggregation protocol.

  3. Signature Verification: The resulting threshold signature is verified by anyone in possession of the participants' public keys. The verification process ensures the authenticity and integrity of the signature. If the verification is successful, it confirms that the signature was generated by a quorum of participants and that the message has not been tampered with.

  4. Encryption and Decryption: Additionally, the encrypt_message method uses the participant's secret key directly for encryption, while the decrypt_message method uses the shared secret as the decryption key. The secret key and shared secret are converted to byte arrays and used as the encryption/decryption keys for AES-256 in CBC mode. Please note that using the participant's secret key directly for encryption and decryption might have security implications, as the secret key is typically intended for signing rather than encryption.

By leveraging the shared secret in the signature generation process, the threshold signature scheme provides several benefits:

  1. Security: The distributed nature of the shared secret ensures that no single participant can perform the signing operation alone, enhancing the security of the signature generation process.

  2. Resilience: The threshold signature scheme remains operational even if some participants are unavailable or compromised. As long as a sufficient number of participants collaborate, the signature can be generated.

  3. Non-repudiation: The threshold signature provides non-repudiation, meaning that a participant cannot deny their involvement in the signature generation process. This property is crucial in various applications, such as digital contracts, financial transactions, and secure communication protocols.

Please note that the usage of the pallier crate in this example assumes that the participants have access to the same modulus n and key pair (p, q, g). Adjustments may be required depending on your specific requirements and use case.

@calvinchengx
Copy link
Author

calvinchengx commented Jun 15, 2023

Using the secret key for encryption and decryption may have the following security implications:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

@calvinchengx
Copy link
Author

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