Skip to content

Commit

Permalink
feat: Added rogue key attack mitigation (#169)
Browse files Browse the repository at this point in the history
* Added rogue key attack mitigation
  • Loading branch information
ChaoticTempest authored Jun 21, 2023
1 parent ed8b3ef commit c12b4e9
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 25 deletions.
153 changes: 128 additions & 25 deletions mpc-recovery/src/sign_node/aggregate_signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,7 @@ impl Committed {
message,
our_key: our_key.clone(),
};
let sc = SignedCommitment::create(
AggrCommitment(commit.commitment),
node_key,
&our_key.public_key,
)?;
let sc = SignedCommitment::create(AggrCommitment(commit.commitment), node_key, our_key)?;
Ok((sc, s))
}

Expand Down Expand Up @@ -264,24 +260,31 @@ pub struct SignedCommitment {
/// This is the public key we're currently signing with,
/// not the node public key that generated the signature
pub signing_public_key: Point<Ed25519>,
/// Signature used to verify the signing public key in case a rogue one is
/// inserted later on.
pub signing_public_key_sig: Signature,
/// This is signed with the node public key
pub signature: Signature,
}

impl SignedCommitment {
pub fn create(
commitment: AggrCommitment,
node_private_key: &protocols::ExpandedKeyPair,
signing_public_key: &Point<Ed25519>,
node_key: &protocols::ExpandedKeyPair,
signing_keys: &protocols::ExpandedKeyPair,
) -> Result<Self, String> {
let to_sign = Self::serialize(&commitment, signing_public_key)?;
let to_sign = Self::serialize(&commitment, &signing_keys.public_key)?;
let signature = aggsig::sign_single(b"", signing_keys);
let signing_public_key_sig = to_dalek_signature(&signature).map_err(|e| e.to_string())?;

// This is awkward, we should move more stuff over to dalek later on
let signature = aggsig::sign_single(&to_sign, node_private_key);
let signature = aggsig::sign_single(&to_sign, node_key);
let signature = to_dalek_signature(&signature).map_err(|e| e.to_string())?;
Ok(SignedCommitment {
commitment,
signing_public_key: signing_public_key.clone(),
signing_public_key: signing_keys.public_key.clone(),
signature,
signing_public_key_sig,
})
}

Expand All @@ -294,6 +297,13 @@ impl SignedCommitment {
public_key
.verify(&message, &self.signature)
.map_err(|e| e.to_string())?;

let signing_public_key =
to_dalek_public_key(&self.signing_public_key).map_err(|e| e.to_string())?;
signing_public_key
.verify(b"", &self.signing_public_key_sig)
.map_err(|e| e.to_string())?;

Ok((self.commitment.clone(), self.signing_public_key.clone()))
}

Expand Down Expand Up @@ -329,28 +339,121 @@ pub fn check_commitment(

#[cfg(test)]
mod tests {
use crate::transaction::from_dalek_signature;

use super::*;
use curv::elliptic::curves::{Ed25519, Point};
use ed25519_dalek::{SignatureError, Verifier};
use multi_party_eddsa::protocols::ExpandedKeyPair;

fn verify_dalek(
pk: &Point<Ed25519>,
sig: &protocols::Signature,
msg: &[u8],
) -> Result<(), SignatureError> {
let mut sig_bytes = [0u8; 64];
sig_bytes[..32].copy_from_slice(&sig.R.to_bytes(true));
sig_bytes[32..].copy_from_slice(&sig.s.to_bytes());

let dalek_pub = ed25519_dalek::PublicKey::from_bytes(&pk.to_bytes(true)).unwrap();
let dalek_sig = ed25519_dalek::Signature::from_bytes(&sig_bytes).unwrap();

dalek_pub.verify(msg, &dalek_sig)
}

fn create_rogue_commit(message: &[u8], commitments: &[SignedCommitment]) -> SignedCommitment {
let ks = || (ExpandedKeyPair::create(), ExpandedKeyPair::create());
// Also generate a public key for the rogue key
let (rogue_node_key, rogue_key) = ks();

// Create rogue commitments to be inserted in to the list
let rogue_pk = commitments
.iter()
.fold(rogue_key.public_key.clone(), |acc, c| {
acc - &c.signing_public_key
});

let rogue_sig = aggsig::sign_single(b"", &rogue_key);
let rogue_sig = commitments
.iter()
.map(|c| from_dalek_signature(c.signing_public_key_sig).unwrap())
.fold(rogue_sig, |acc, s| protocols::Signature {
R: acc.R - s.R,
s: acc.s - s.s,
});

let (_ephemeral_key, commit, _our_signature) =
aggsig::create_ephemeral_key_and_commit_rng(&rogue_key, message, &mut OsRng);
let mut rogue_commit = SignedCommitment::create(
AggrCommitment(commit.commitment),
&rogue_node_key,
&rogue_key,
)
.unwrap();
rogue_commit.signing_public_key = rogue_pk;
rogue_commit.signing_public_key_sig = to_dalek_signature(&rogue_sig).unwrap();
rogue_commit
}

#[tokio::test]
async fn aggregate_signatures() {
pub fn verify_dalek(
pk: &Point<Ed25519>,
sig: &protocols::Signature,
msg: &[u8],
) -> Result<(), SignatureError> {
let mut sig_bytes = [0u8; 64];
sig_bytes[..32].copy_from_slice(&sig.R.to_bytes(true));
sig_bytes[32..].copy_from_slice(&sig.s.to_bytes());

let dalek_pub = ed25519_dalek::PublicKey::from_bytes(&pk.to_bytes(true)).unwrap();
let dalek_sig = ed25519_dalek::Signature::from_bytes(&sig_bytes).unwrap();

dalek_pub.verify(msg, &dalek_sig)
}
async fn rogue_attack() {
// Generate node keys and signing keys
let ks = || (ExpandedKeyPair::create(), ExpandedKeyPair::create());
let (n1, k1) = ks();
let (n2, k2) = ks();
let (n3, k3) = ks();

let nodes_public_keys = vec![
n1.public_key.clone(),
n2.public_key.clone(),
n3.public_key.clone(),
];

let ni = |n| NodeInfo::new(n, Some(nodes_public_keys.clone()));

// Set up nodes with that config
let mut s1 = SigningState::new();
let mut s2 = SigningState::new();
let mut s3 = SigningState::new();

let message = b"message in a bottle".to_vec();

let mut commitments = vec![
s1.get_commitment(&k1, &n1, message.clone()).unwrap(),
s2.get_commitment(&k2, &n2, message.clone()).unwrap(),
s3.get_commitment(&k3, &n3, message.clone()).unwrap(),
];
// Insert in the rogue key to be detected later.
commitments.push(create_rogue_commit(&message, &commitments));

let reveals = vec![
s1.get_reveal(ni(0), commitments.clone()).await.unwrap(),
s2.get_reveal(ni(1), commitments.clone()).await.unwrap(),
s3.get_reveal(ni(2), commitments.clone()).await.unwrap(),
];

let sig_shares = vec![
s1.get_signature_share(ni(0), reveals.clone()).unwrap(),
s2.get_signature_share(ni(1), reveals.clone()).unwrap(),
s3.get_signature_share(ni(2), reveals).unwrap(),
];

let signing_keys: Vec<_> = commitments
.iter()
.map(|c| c.signing_public_key.clone())
.collect();
let aggrigate_key = KeyAgg::key_aggregation_n(&signing_keys, 0);

let signature = aggsig::add_signature_parts(&sig_shares);
let verified = verify_dalek(&aggrigate_key.apk, &signature, &message);
assert!(
verified.is_err(),
"Expected rogue key to be detected and create an invalid proof"
);
}

#[tokio::test]
async fn aggregate_signatures() {
// Generate node keys and signing keys
let ks = || (ExpandedKeyPair::create(), ExpandedKeyPair::create());
let (n1, k1) = ks();
Expand Down
8 changes: 8 additions & 0 deletions mpc-recovery/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,14 @@ pub async fn call_all_nodes<Req: Serialize, Res: DeserializeOwned>(
future::join_all(responses).await.into_iter().collect()
}

pub fn from_dalek_signature(sig: ed25519_dalek::Signature) -> anyhow::Result<protocols::Signature> {
let bytes = sig.to_bytes();
Ok(protocols::Signature {
R: Point::from_bytes(&bytes[..32])?,
s: curv::elliptic::curves::Scalar::from_bytes(&bytes[32..])?,
})
}

pub fn to_dalek_signature(sig: &protocols::Signature) -> anyhow::Result<ed25519_dalek::Signature> {
let mut sig_bytes = [0u8; 64];
sig_bytes[..32].copy_from_slice(&sig.R.to_bytes(true));
Expand Down

0 comments on commit c12b4e9

Please sign in to comment.