Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plain ECDSA implementation #21

Merged
merged 5 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 113 additions & 22 deletions src/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,38 @@ use crate::{
shares::{NatShares, PointShares, Shares},
};

/// Run a ECDSA protocol
// TODO: remame to threshold instead of plain.

/// Run a ECDSA protocol with BeDOZa
///
/// Uses the protocol from Securing DNSSEC Keys via Threshold ECDSA From Generic MPC
///
/// 1. Sign message
/// 2. Verify signature
/// 3. PROFIT!
pub fn run_ecdsa(message: Nat) {
pub fn run_ecdsa_bedoza(message: Nat) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a big fan of this bedoza / plain naming scheme -

Can we use threshold_sign and sign instead (I've been keeping this to myself, but I also generally think including names of parameters as part of a function name, such as fn sign_message(message: str) is an anti-pattern :))? And perhaps run_threshold_ecdsa for the runner?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to spend a lot of energy arguing naming though, so just view this as a suggestion and mark the comment as resolved :)

// Generate a keys
let (_, sk_shared, pk) = keygen();

// Sign message
let signature = sign_message_bedoza(message, sk_shared);

// Verify signature
assert!(verify_signature(message, signature, pk));
}

/// Run a ECDSA protocol without BeDOZa
/// Used for benchmarking
/// Uses the protocol from https://cryptobook.nakov.com/digital-signatures/ecdsa-sign-verify-messages
#[allow(dead_code)]
pub fn run_ecdsa_plain(message: Nat) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're only using this for benchmarking, couldn't we just put it in the test module? I.e under

#[cfg(test)]
mod test_plain { ... }

// Generate a keys
let (sk_shared, pk) = keygen();
let (sk, _, pk) = keygen();

// Sign message
let signature = sign_message(message, sk_shared);
let signature = sign_message_plain(message, sk);

// Verify signature
assert!(verify_signature(message, signature, pk));
}

/// Sign a message
/// Sign a message with BeDOZa
///
/// Using ABB+ protocol from Securing DNSSEC Keys via Threshold ECDSA From Generic MPC
///
Expand All @@ -43,7 +56,7 @@ pub fn run_ecdsa(message: Nat) {
/// 2. Generate circuit
/// 3. Evaluate circuit
/// 3. Return signature: (r, s)
fn sign_message(m: Nat, sk_shared: NatShares) -> (Nat, Nat) {
fn sign_message_bedoza(m: Nat, sk_shared: NatShares) -> (Nat, Nat) {
// User independent preprocessing
let preprocessed_tuple = user_independent_preprocessing(&curve::nonzero_order());

Expand Down Expand Up @@ -103,7 +116,7 @@ fn verify_signature(m: Nat, signature: (Nat, Nat), pk: Point) -> bool {
/// Generate public key
///
/// Returns a point on the curve
/// pk = Open(Convert(\[sk\]))
/// `pk = Open(Convert([sk]))` or `pk = sk * G`
fn generate_public_key(sk_shared: NatShares) -> Point {
let sk_convert = PointShares::convert(sk_shared);
let pk = Shares::Point(sk_convert).open().point();
Expand Down Expand Up @@ -192,11 +205,11 @@ fn ecdsa_circuit(
}

/// Generate ECDSA keypair where sk is secret shared
fn keygen() -> (NatShares, Point) {
fn keygen() -> (Nat, NatShares, Point) {
let sk = curve::rand_mod_order();
let sk_shared = NatShares::new(&sk, curve::nonzero_order());
let pk = generate_public_key(sk_shared.clone());
(sk_shared, pk)
(sk, sk_shared, pk)
}

/// Compute H(m) = sha256(m)
Expand All @@ -211,38 +224,116 @@ fn hash_message(m: Nat) -> Nat {
return Nat::from_le_slice(&result[..]);
}

/// Sign a message with plain ECDSA (no BeDOZa)
///
/// Based on https://cryptobook.nakov.com/digital-signatures/ecdsa-sign-verify-messages
#[allow(dead_code)]
fn sign_message_plain(message: Nat, sk: Nat) -> (Nat, Nat) {
// Calculate the hash of the message
let h = hash_message(message);

// Generate a random number k
let k = curve::rand_mod_order();

// Calculate random point R = k * G and take its x coordinate r_x = R.x
let r_x = FieldBytesEncoding::decode_field_bytes(
&AffinePoint::from(Point::mul_by_generator(
&curve::Scalar::from_uint_unchecked(k),
))
.x(),
);

// Calculate the modular inverse of k
let (k_inv, k_inv_exists) = k.inv_mod(&curve::nonzero_order());
if !bool::from(k_inv_exists) {
panic!("k inverse does not exist")
}

// Calculate the signature proof s = (h + r_x * sk) * k_inv
let h_plus_r_x_sk =
mul_mod(&r_x, &sk, &curve::nonzero_order()).add_mod(&h, &curve::nonzero_order());
let s = mul_mod(&h_plus_r_x_sk, &k_inv, &curve::nonzero_order());

(r_x, s)
}

/// Read message after "ecdsa" from command line arguments
pub fn read_args_message(args: env::Args) -> Nat {
let args: Vec<String> = args.collect();
let m = Nat::from(args.get(2).unwrap().parse::<u128>().unwrap());
m
}

#[cfg(test)]
mod tests {
mod tests_bedoza {
use super::*;

#[test]
fn test_run_ecdsa() {
run_ecdsa_bedoza(Nat::from_u16(1337));
}

#[test]
fn test_threshold_ecdsa_bedoza_positive() {
// Test that sign/verify of 100 random messages
let mut i = 0;
while i < 100 {
let message = curve::rand_mod_order();

let (_, sk_shared, pk) = keygen();
let s = sign_message_bedoza(message, sk_shared);
assert!(verify_signature(message, s, pk));
i = i + 1;
}
run_ecdsa_bedoza(Nat::from_u16(1337));
}

#[test]
fn test_threshold_ecdsa_bedoza_negative() {
// Test that sign/verify of 100 random messages
// m1 and m2 where m1 != m2
let mut i = 0;
while i < 100 {
let m1 = curve::rand_mod_order();
let m2 = curve::rand_mod_order();
if m1 == m2 {
continue;
}

let (_, sk_shared, pk) = keygen();
let s = sign_message_bedoza(m1, sk_shared);
assert!(!verify_signature(m2, s, pk));
i = i + 1;
}
}
}

#[cfg(test)]
mod tests_plain {
use super::*;

#[test]
fn test_run_ecdsa() {
run_ecdsa(Nat::from_u16(1337));
run_ecdsa_plain(Nat::from_u16(1337));
}

#[test]
fn test_threshold_ecdsa_positive() {
fn test_threshold_ecdsa_plain_positive() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... Continuing the comment on the naming: I think of 'threshold signing' as signing using MPC and thus 'test_threshold_ecdsa_plain' doesn't make much sense IMO :)

// Test that sign/verify of 100 random messages
let mut i = 0;
while i < 100 {
let message = curve::rand_mod_order();

let (sk_shared, pk) = keygen();
let s = sign_message(message, sk_shared);
let (sk, _, pk) = keygen();
let s = sign_message_plain(message, sk);
assert!(verify_signature(message, s, pk));
i = i + 1;
}
run_ecdsa(Nat::from_u16(1337));
run_ecdsa_plain(Nat::from_u16(1337));
}

#[test]
fn test_threshold_ecdsa_negative() {
fn test_threshold_ecdsa_plain_negative() {
// Test that sign/verify of 100 random messages
// m1 and m2 where m1 != m2
let mut i = 0;
Expand All @@ -253,8 +344,8 @@ mod tests {
continue;
}

let (sk_shared, pk) = keygen();
let s = sign_message(m1, sk_shared);
let (sk, _, pk) = keygen();
let s = sign_message_plain(m1, sk);
assert!(!verify_signature(m2, s, pk));
i = i + 1;
}
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn main() {

ECDSA => {
let m = ecdsa::read_args_message(args());
ecdsa::run_ecdsa(m)
ecdsa::run_ecdsa_bedoza(m)
}
_ => panic!(
"Use one of the following commands: \"{cmds}\"",
Expand Down