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

KES implementation #48

Merged
merged 12 commits into from
Jul 25, 2023
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"spectrum-ledger",
"spectrum-crypto",
"spectrum-vrf",
"spectrum-kes",
"spectrum-move",
"algebra-core",
"futures-util"
Expand Down
12 changes: 12 additions & 0 deletions spectrum-kes/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "spectrum-kes"
version = "0.1.0"
edition = "2021"
rust-version = "1.65.0"

[dependencies]
spectrum-crypto = { version = "0.1.0", path = "../spectrum-crypto" }
blake2 = "0.10.6"
ecdsa = "0.16.7"
elliptic-curve = "0.13.*"
k256 = "0.13.*"
22 changes: 22 additions & 0 deletions spectrum-kes/src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Key Evolving Signature (KES)

The Key Evolving Signature mechanism prevents an attacker from generating signatures for
messages that were created in the past. It also allows any protocol
participant to verify that a given signature was generated with the legal signing key for a
particular slot.
The security guarantees are achieved by evolving the secret key after each signature
in a way that the actual secret key was used to sign the previous message
cannot be recovered.

* [2001/034](https://eprint.iacr.org/2001/034)
* [2017/573](https://cseweb.ucsd.edu/~daniele/papers/MMM.pdf)

# Data required in the BlockHeader

Each block must include the leaders' signature.
| Header | Type |
| ------------- | ------------- |
| `Signature` | `(Signature, PublicKey, Vec<PublicKey>)` |

There are `2^N` Secret Keys that can be securely restored using this scheme. Number of `PublicKeys` that must be stored
in the `Vec<PublicKey>` is `N`.
99 changes: 99 additions & 0 deletions spectrum-kes/src/composition_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use std::any::Any;
use std::fmt::Debug;
use std::ops::Range;

use elliptic_curve::point::PointCompression;
use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint};
use elliptic_curve::{CurveArithmetic, PrimeCurve, PublicKey, SecretKey};

use spectrum_crypto::digest::Sha2Digest256;

use crate::kes::KeSignature;
use crate::utils::{double_seed, key_pair_gen, merge_public_keys};

#[derive(Debug)]
pub struct Error;

pub fn get_left_merkle_tree_branch<TCurve: CurveArithmetic>(
merkle_tree_high: &u32,
seed: &Sha2Digest256,
) -> Result<(SecretKey<TCurve>, PublicKey<TCurve>, Vec<Sha2Digest256>), Error> {
let mut branch_seeds = Vec::new();
let mut actual_seed = (*seed).clone();
let mut h = (*merkle_tree_high).clone();

loop {
let (seed_0, seed_1) = double_seed(&actual_seed);
branch_seeds.push(seed_1.clone());
if h == 1 {
let (sk, pk) = key_pair_gen::<TCurve>(&seed_0);
return Ok((sk, pk, branch_seeds));
} else {
actual_seed = seed_0;
}
h -= 1;
}
}

pub fn sum_composition_pk_gen<TCurve: CurveArithmetic + PointCompression>(
merkle_tree_high: &u32,
seed: &Sha2Digest256,
) -> Result<PublicKey<TCurve>, Error>
where
<TCurve as CurveArithmetic>::AffinePoint: FromEncodedPoint<TCurve>,
<TCurve as elliptic_curve::Curve>::FieldBytesSize: ModulusSize,
<TCurve as CurveArithmetic>::AffinePoint: ToEncodedPoint<TCurve>,
{
if *merkle_tree_high == 0 {
return Ok(key_pair_gen::<TCurve>(seed).1);
}

let (_, pk0, branch_seeds) = get_left_merkle_tree_branch::<TCurve>(merkle_tree_high, seed).unwrap();

let mut pk = pk0.clone();
let mut high = 0;

for seed in branch_seeds.iter().rev() {
let pk_right = if high == 0 {
key_pair_gen::<TCurve>(&seed).1
} else {
sum_composition_pk_gen::<TCurve>(&high, &seed).unwrap()
};
high += 1;
pk = merge_public_keys::<TCurve>(&pk, &pk_right);
}
Ok(pk)
}

pub fn calculate_scheme_pk_from_signature<TCurve: CurveArithmetic + PrimeCurve + PointCompression>(
signature: &KeSignature<TCurve>,
signing_period: &u32,
) -> PublicKey<TCurve>
where
<TCurve as CurveArithmetic>::AffinePoint: FromEncodedPoint<TCurve>,
<TCurve as elliptic_curve::Curve>::FieldBytesSize: ModulusSize,
<TCurve as CurveArithmetic>::AffinePoint: ToEncodedPoint<TCurve>,
{
let mut scheme_pk = (*signature).pk_actual;
for i in (0..(*signature).scheme_public_keys.len()).rev() {
let pk_ = (*signature).scheme_public_keys[i].clone();
let right = (*signing_period & (1 << (*signature).scheme_public_keys.len() - i - 1)) != 0;
if right {
scheme_pk = merge_public_keys::<TCurve>(&pk_, &scheme_pk);
} else {
scheme_pk = merge_public_keys::<TCurve>(&scheme_pk, &pk_);
}
}
scheme_pk
}

pub fn insert_in_vec<T: Any + Clone>(
mut source_vec: Vec<T>,
cloned_vec: Vec<T>,
inds: Range<usize>,
) -> Vec<T> {
for i in inds {
source_vec.push(cloned_vec[i].clone());
}
source_vec
}
242 changes: 242 additions & 0 deletions spectrum-kes/src/kes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
use std::ops::Add;

use ecdsa::hazmat::SignPrimitive;
use ecdsa::signature::{Signer, SignerMut, Verifier};
use ecdsa::{Signature, SigningKey, VerifyingKey};
use elliptic_curve::generic_array::ArrayLength;
use elliptic_curve::point::PointCompression;
use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint};
use elliptic_curve::{CurveArithmetic, PublicKey, SecretKey};

use spectrum_crypto::digest::{sha256_hash, Sha2Digest256};

use crate::composition_utils::{
calculate_scheme_pk_from_signature, get_left_merkle_tree_branch, insert_in_vec, sum_composition_pk_gen,
};
use crate::utils::{associate_message_with_slot, key_pair_gen, merge_public_keys, projective_point_to_bytes};

#[derive(Debug)]
pub struct Error;

#[derive(Debug)]
pub struct KesSecret<TCurve: CurveArithmetic> {
initial_merkle_tree_high: u32,
n_sk_actual_updates: u32,
sk_actual: SecretKey<TCurve>,
pk_actual: PublicKey<TCurve>,
merkle_seeds: Vec<Sha2Digest256>,
merkle_public_keys: Vec<(PublicKey<TCurve>, PublicKey<TCurve>)>,
}

pub struct KeSignature<TCurve: CurveArithmetic + ecdsa::PrimeCurve> {
pub(crate) sig: Signature<TCurve>,
pub(crate) pk_actual: PublicKey<TCurve>,
pub(crate) scheme_public_keys: Vec<PublicKey<TCurve>>,
}

pub fn kes_gen<TCurve: CurveArithmetic + PointCompression>(
merkle_tree_high: &u32,
seed: &Sha2Digest256,
) -> Result<(KesSecret<TCurve>, PublicKey<TCurve>), Error>
where
<TCurve as CurveArithmetic>::AffinePoint: FromEncodedPoint<TCurve>,
<TCurve as elliptic_curve::Curve>::FieldBytesSize: ModulusSize,
<TCurve as CurveArithmetic>::AffinePoint: ToEncodedPoint<TCurve>,
{
let (sk_actual, pk_actual, pk_all_scheme, merkle_seeds, merkle_public_keys) = {
if *merkle_tree_high == 0 {
let (sk_, pk_) = key_pair_gen::<TCurve>(seed);
(sk_, pk_, pk_.clone(), vec![], vec![])
} else {
// Generate all keys for the left branch and save seeds from the right branch
let (sk_0, pk_0, merkle_seeds_) =
get_left_merkle_tree_branch::<TCurve>(merkle_tree_high, seed).unwrap();

// Aggregate main Merkle tree Public Key and Public Keys of the related leafs
let mut pk_all_scheme_ = pk_0.clone();
let mut merkle_public_keys_ = Vec::new();

let mut high = 0;
for i in (0..merkle_seeds_.len()).rev() {
let seed = merkle_seeds_[i].clone();
let pk_right = if *merkle_tree_high == 0 {
key_pair_gen::<TCurve>(&seed).1
} else {
sum_composition_pk_gen::<TCurve>(&high, &seed).unwrap()
};
high += 1;
merkle_public_keys_.push((pk_all_scheme_, pk_right));
pk_all_scheme_ = merge_public_keys::<TCurve>(&pk_all_scheme_, &pk_right);
}
merkle_public_keys_.reverse();
(sk_0, pk_0, pk_all_scheme_, merkle_seeds_, merkle_public_keys_)
}
};

assert_eq!(merkle_public_keys.len(), *merkle_tree_high as usize);
assert_eq!(merkle_seeds.len(), *merkle_tree_high as usize);

let sk_sum = KesSecret {
initial_merkle_tree_high: *merkle_tree_high,
n_sk_actual_updates: 0,
pk_actual,
sk_actual,
merkle_seeds,
merkle_public_keys,
};
Ok((sk_sum, pk_all_scheme))
}

pub fn kes_update<TCurve: CurveArithmetic + PointCompression>(
secret: KesSecret<TCurve>,
) -> Result<KesSecret<TCurve>, Error>
where
<TCurve as CurveArithmetic>::AffinePoint: FromEncodedPoint<TCurve>,
<TCurve as elliptic_curve::Curve>::FieldBytesSize: ModulusSize,
<TCurve as CurveArithmetic>::AffinePoint: ToEncodedPoint<TCurve>,
{
let last_seed_ind = secret.merkle_seeds.len() - 1;

let mut merkle_public_keys_new = Vec::new();
let mut merkle_seeds_new = Vec::new();

let (sk_new, pk_new) = {
// Count number of anchors between the consequent leafs
let anc_num = usize::count_ones(
secret.n_sk_actual_updates as usize ^ (secret.n_sk_actual_updates.clone() as usize + 1),
);
// Repair next Secret Key
if anc_num == 1 {
if secret.merkle_seeds.len() >= 1 {
// Remove last Seed
merkle_seeds_new =
insert_in_vec(merkle_seeds_new, secret.merkle_seeds.clone(), 0..last_seed_ind);
merkle_public_keys_new = insert_in_vec(
merkle_public_keys_new,
secret.merkle_public_keys.clone(),
0..secret.initial_merkle_tree_high as usize,
);
}
key_pair_gen::<TCurve>(&secret.merkle_seeds[last_seed_ind])
} else {
let seed = secret.merkle_seeds[last_seed_ind].clone();

// Get the child branch
let (secret_child, pk_child) = kes_gen::<TCurve>(&(anc_num - 1), &seed).unwrap();

if secret.merkle_public_keys.len() > 1 {
let ind = (secret.initial_merkle_tree_high as i32 - anc_num as i32) as usize;

assert_eq!(secret.merkle_public_keys[ind].1, pk_child);

merkle_seeds_new = insert_in_vec(
merkle_seeds_new,
secret.merkle_seeds.clone(),
0..secret.merkle_seeds.len() - 1,
);
}

// Remove (High - anc_num) Public Keys and insert from the child
merkle_public_keys_new = insert_in_vec(
merkle_public_keys_new,
secret.merkle_public_keys.clone(),
0..(secret.merkle_public_keys.len() - secret_child.merkle_public_keys.len()),
);
merkle_public_keys_new = insert_in_vec(
merkle_public_keys_new,
secret_child.merkle_public_keys.clone(),
0..secret_child.merkle_public_keys.len(),
);
merkle_seeds_new = insert_in_vec(
merkle_seeds_new,
secret_child.merkle_seeds.clone(),
0..secret_child.merkle_public_keys.len(),
);

assert_eq!(
merkle_public_keys_new.len() as u32,
secret.initial_merkle_tree_high
);

(secret_child.sk_actual, secret_child.pk_actual)
}
};

Ok(KesSecret::<TCurve> {
initial_merkle_tree_high: secret.initial_merkle_tree_high.clone(),
n_sk_actual_updates: secret.n_sk_actual_updates.clone() + 1,
sk_actual: sk_new,
pk_actual: pk_new,
merkle_seeds: merkle_seeds_new,
merkle_public_keys: merkle_public_keys_new,
})
}

pub fn kes_sign<TCurve>(
message: &Sha2Digest256,
secret: &KesSecret<TCurve>,
current_slot: &u32,
) -> Result<KeSignature<TCurve>, Error>
where
TCurve: CurveArithmetic + elliptic_curve::PrimeCurve,
<TCurve as CurveArithmetic>::Scalar: SignPrimitive<TCurve>,
<<TCurve as elliptic_curve::Curve>::FieldBytesSize as Add>::Output: ArrayLength<u8>,
SigningKey<TCurve>: Signer<Signature<TCurve>>,
SigningKey<TCurve>: SignerMut<Signature<TCurve>>,
{
// Sign the message with an actual Secret Key (message is associated with slot)
let signing_key = SigningKey::from(&secret.sk_actual);
let sig = signing_key.sign(&associate_message_with_slot(&current_slot, &message));

let mut leaf_related_public_keys = Vec::new();
let mut h = current_slot.clone();

// Aggregate the corresponding leafs' Public Key
for i in 0..secret.merkle_public_keys.len() {
let leaf_high = secret.initial_merkle_tree_high.clone() - i as u32;
let actual_high = (2 as u32).pow((leaf_high as u32 - 1) as u32);
if h >= actual_high {
h -= actual_high;
leaf_related_public_keys.push(secret.merkle_public_keys[i].0.clone());
} else {
leaf_related_public_keys.push(secret.merkle_public_keys[i].1.clone());
}
}

Ok(KeSignature {
sig,
pk_actual: secret.pk_actual.clone(),
scheme_public_keys: leaf_related_public_keys,
})
}

pub fn kes_verify<TCurve: CurveArithmetic + ecdsa::PrimeCurve + PointCompression>(
signature: &KeSignature<TCurve>,
message: &Sha2Digest256,
all_scheme_pk: &PublicKey<TCurve>,
signing_slot: &u32,
) -> Result<bool, Error>
where
<TCurve as CurveArithmetic>::AffinePoint: FromEncodedPoint<TCurve>,
<TCurve as elliptic_curve::Curve>::FieldBytesSize: ModulusSize,
<TCurve as CurveArithmetic>::AffinePoint: ToEncodedPoint<TCurve>,
VerifyingKey<TCurve>: Verifier<Signature<TCurve>>,
{
// Verify message
let ver_key: VerifyingKey<TCurve> = VerifyingKey::from(signature.pk_actual.clone());
let message_is_verified = match ver_key.verify(
&associate_message_with_slot(&signing_slot, &message),
&signature.sig,
) {
Ok(_) => true,
Err(_) => false,
};

// Verify scheme Public Key
let sig_scheme_pk = calculate_scheme_pk_from_signature(signature, signing_slot);
let pk_is_verified =
sha256_hash(&projective_point_to_bytes::<TCurve>(&sig_scheme_pk.to_projective()).as_slice())
== sha256_hash(&projective_point_to_bytes::<TCurve>(&all_scheme_pk.to_projective()).as_slice());

Ok(message_is_verified && pk_is_verified)
}
6 changes: 6 additions & 0 deletions spectrum-kes/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
extern crate core;

mod composition_utils;
mod kes;
mod tests;
mod utils;