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

feat : DKG pallet should handle both edcsa and Schnorr signatures #341

Merged
merged 6 commits into from
Dec 12, 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
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,6 @@ ethereum = { version = "0.14.0", default-features = false }
evm-gasometer = { git = "https://github.com/rust-blockchain/evm", rev = "b7b82c7e1fc57b7449d6dfa6826600de37cc1e65", default-features = false }
evm-runtime = { git = "https://github.com/rust-blockchain/evm", rev = "b7b82c7e1fc57b7449d6dfa6826600de37cc1e65", default-features = false }

# XCM dependencies
xcm = { package = "staging-xcm", git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false }

# RPC related dependencies
jsonrpsee = { version = "0.16.2", features = ["server"] }
pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0" }
Expand Down
1 change: 1 addition & 0 deletions node/src/distributions/mainnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use super::testnet::{get_git_root, read_contents, read_contents_to_evm_accounts}
fn read_contents_to_substrate_accounts(path_str: &str) -> BTreeMap<AccountId, f64> {
let mut path = get_git_root();
path.push(path_str);
println!("Path {:?}", path_str);
let json = read_contents(&path);
let json_obj = json.as_object().expect("should be an object");
let mut accounts_map = BTreeMap::new();
Expand Down
2 changes: 2 additions & 0 deletions node/src/distributions/testnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub fn read_contents(path: &Path) -> Value {
pub fn read_contents_to_evm_accounts(path_str: &str) -> Vec<H160> {
let mut path = get_git_root();
path.push(path_str);
println!("Path {:?}", path_str);
let json = read_contents(&path);
let mut accounts = Vec::new();
for address in json.as_array().expect("should be an object") {
Expand All @@ -47,6 +48,7 @@ pub fn read_contents_to_evm_accounts(path_str: &str) -> Vec<H160> {
fn read_contents_to_substrate_accounts(path_str: &str) -> Vec<AccountId> {
let mut path = get_git_root();
path.push(path_str);
println!("Path {:?}", path_str);
let json = read_contents(&path);
let mut accounts = Vec::new();
for address in json.as_array().expect("should be an object") {
Expand Down
12 changes: 3 additions & 9 deletions pallets/dkg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ license = { workspace = true }
repository = { workspace = true }

[dependencies]
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
parity-scale-codec = { workspace = true }
Expand All @@ -31,19 +30,14 @@ default = ["std"]
std = [
"scale-info/std",
"sp-runtime/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"sp-core/std",
"sp-std/std",
"tangle-primitives/std",
"pallet-balances/std",
"sp-io/std",
"sp-application-crypto/std",
"sp-keystore/std"
]
try-runtime = ["frame-support/try-runtime"]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
]
189 changes: 166 additions & 23 deletions pallets/dkg/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ use crate::types::BalanceOf;
use frame_support::{ensure, pallet_prelude::DispatchResult, sp_runtime::Saturating};
use frame_system::pallet_prelude::BlockNumberFor;
use parity_scale_codec::Encode;
use sp_core::ecdsa;
use sp_io::{hashing::keccak_256, EcdsaVerifyError};
use sp_core::{ecdsa, sr25519};
use sp_io::{crypto::sr25519_verify, hashing::keccak_256, EcdsaVerifyError};
use sp_std::{default::Default, vec::Vec};
use tangle_primitives::jobs::*;

/// Expected signature length
pub const SIGNATURE_LENGTH: usize = 65;
/// Expected key length for ecdsa
const ECDSA_KEY_LENGTH: usize = 33;
/// Expected key length for sr25519
const SCHNORR_KEY_LENGTH: usize = 32;

impl<T: Config> Pallet<T> {
/// Calculates the fee for a given job submission based on the provided fee information.
Expand Down Expand Up @@ -75,10 +77,31 @@ impl<T: Config> Pallet<T> {
}
}

/// Verifies the generated DKG key based on the provided DKG verification information.
/// Verifies a generated DKG (Distributed Key Generation) key based on the provided DKG result.
///
/// The verification process includes generating required signers, validating signatures, and
/// ensuring a sufficient number of unique signers are present.
/// The verification process depends on the key type specified in the DKG result.
/// It dispatches the verification to the appropriate function for the specified key type (ECDSA
/// or Schnorr).
///
/// # Arguments
///
/// * `data` - The DKG result containing participants, keys, and signatures.
///
/// # Returns
///
/// Returns a `DispatchResult` indicating whether the DKG key verification was successful
/// or encountered an error.
fn verify_generated_dkg_key(data: DKGResult) -> DispatchResult {
match data.key_type {
DkgKeyType::Ecdsa => Self::verify_generated_dkg_key_ecdsa(data),
DkgKeyType::Schnorr => Self::verify_generated_dkg_key_schnorr(data),
}
}

/// Verifies the generated DKG key for ECDSA signatures.
///
/// This function includes generating required signers, validating signatures, and ensuring a
/// sufficient number of unique signers are present.
///
/// # Arguments
///
Expand All @@ -88,48 +111,124 @@ impl<T: Config> Pallet<T> {
///
/// Returns a `DispatchResult` indicating whether the DKG key verification was successful or
/// encountered an error.
fn verify_generated_dkg_key(data: DKGResult) -> DispatchResult {
// generate the required signers
fn verify_generated_dkg_key_ecdsa(data: DKGResult) -> DispatchResult {
// Ensure participants and signatures are not empty
ensure!(!data.participants.is_empty(), Error::<T>::NoParticipantsFound);
ensure!(!data.signatures.is_empty(), Error::<T>::NoSignaturesFound);

// Generate the required ECDSA signers
let maybe_signers = data
.participants
.iter()
.map(|x| {
ecdsa::Public(
Self::to_slice_33(&x.encode())
Self::to_slice_33(x)
.unwrap_or_else(|| panic!("Failed to convert input to ecdsa public key")),
)
})
.collect::<Vec<ecdsa::Public>>();

ensure!(!maybe_signers.is_empty(), Error::<T>::NoParticipantsFound);
ensure!(!data.keys_and_signatures.is_empty(), Error::<T>::NoSignaturesFound);

let mut known_signers: Vec<ecdsa::Public> = Default::default();
let signed_pub_key: Vec<u8> =
data.keys_and_signatures.first().expect("Cannot be empty").clone().0;

for (key, signature) in data.keys_and_signatures {
// ensure the required signer signature exists
for signature in data.signatures {
// Ensure the required signer signature exists
let (maybe_authority, success) =
Self::verify_signer_from_set_ecdsa(maybe_signers.clone(), &key, &signature);
Self::verify_signer_from_set_ecdsa(maybe_signers.clone(), &data.key, &signature);

if success {
// sanity check, everyone signed the same key
ensure!(key == signed_pub_key, Error::<T>::InvalidSignatureData);

let authority = maybe_authority.ok_or(Error::<T>::CannotRetreiveSigner)?;

// Ensure no duplicate signatures
ensure!(!known_signers.contains(&authority), Error::<T>::DuplicateSignature);

known_signers.push(authority);
}
}

// Ensure a sufficient number of unique signers are present
ensure!(known_signers.len() > data.threshold.into(), Error::<T>::NotEnoughSigners);

Ok(())
}

/// Verifies the generated DKG key for Schnorr signatures.
///
/// This function includes generating required signers, validating signatures, and ensuring a
/// sufficient number of unique signers are present.
///
/// # Arguments
///
/// * `data` - The DKG verification information containing participants, keys, and signatures.
///
/// # Returns
///
/// Returns a `DispatchResult` indicating whether the DKG key verification was successful or
/// encountered an error.
fn verify_generated_dkg_key_schnorr(data: DKGResult) -> DispatchResult {
// Ensure participants and signatures are not empty
ensure!(!data.participants.is_empty(), Error::<T>::NoParticipantsFound);
ensure!(!data.signatures.is_empty(), Error::<T>::NoSignaturesFound);

// Generate the required Schnorr signers
let maybe_signers = data
.participants
.iter()
.map(|x| {
sr25519::Public(
Self::to_slice_32(x)
.unwrap_or_else(|| panic!("Failed to convert input to sr25519 public key")),
)
})
.collect::<Vec<sr25519::Public>>();

ensure!(!maybe_signers.is_empty(), Error::<T>::NoParticipantsFound);

let mut known_signers: Vec<sr25519::Public> = Default::default();

for signature in data.signatures {
// Convert the signature from bytes to sr25519::Signature
let signature: sr25519::Signature =
signature.as_slice().try_into().map_err(|_| Error::<T>::CannotRetreiveSigner)?;

let msg = data.key.encode();
let hash = keccak_256(&msg);

for signer in maybe_signers.clone() {
// Verify the Schnorr signature
if sr25519_verify(&signature, &hash, &signer) {
ensure!(!known_signers.contains(&signer), Error::<T>::DuplicateSignature);

known_signers.push(signer);
}
}
}

// Ensure a sufficient number of unique signers are present
ensure!(known_signers.len() > data.threshold.into(), Error::<T>::NotEnoughSigners);

Ok(())
}

/// Verifies a DKG (Distributed Key Generation) signature based on the provided DKG signature
/// result.
///
/// The verification process depends on the key type specified in the DKG signature result.
/// It dispatches the verification to the appropriate function for the specified key type (ECDSA
/// or Schnorr).
///
/// # Arguments
///
/// * `data` - The DKG signature result containing the message data, signature, signing key, and
/// key type.
fn verify_dkg_signature(data: DKGSignatureResult) -> DispatchResult {
match data.key_type {
DkgKeyType::Ecdsa => Self::verify_dkg_signature_ecdsa(data),
DkgKeyType::Schnorr => Self::verify_dkg_signature_schnorr(data),
}
}

/// Verifies the DKG signature result by recovering the ECDSA public key from the provided data
/// and signature.
///
Expand All @@ -139,25 +238,59 @@ impl<T: Config> Pallet<T> {
/// # Arguments
///
/// * `data` - The DKG signature result containing the message data and ECDSA signature.
///
/// # Returns
///
/// Returns a `DispatchResult` indicating whether the DKG signature verification was successful
/// or encountered an error.
fn verify_dkg_signature(data: DKGSignatureResult) -> DispatchResult {
fn verify_dkg_signature_ecdsa(data: DKGSignatureResult) -> DispatchResult {
// Recover the ECDSA public key from the provided data and signature
let recovered_key = Self::recover_ecdsa_pub_key(&data.data, &data.signature)
.map_err(|_| Error::<T>::InvalidSignature)?;

// Extract the expected key from the provided signing key
let expected_key: Vec<_> = data.signing_key.iter().skip(1).cloned().collect();
// The recovered key is 64 bytes uncompressed. The first 32 bytes represent the compressed
// portion of the key.
let signer = &recovered_key[..32];

// Ensure that the recovered key matches the expected signing key
ensure!(expected_key == signer, Error::<T>::SigningKeyMismatch);

Ok(())
}

/// Verifies the DKG signature result for Schnorr signatures.
///
/// This function uses the Schnorr signature algorithm to verify the provided signature
/// based on the message data, signature, and signing key in the DKG signature result.
///
/// # Arguments
///
/// * `data` - The DKG signature result containing the message data, Schnorr signature, and
/// signing key.
fn verify_dkg_signature_schnorr(data: DKGSignatureResult) -> DispatchResult {
// Convert the signature from bytes to sr25519::Signature
let signature: sr25519::Signature = data
.signature
.as_slice()
.try_into()
.map_err(|_| Error::<T>::CannotRetreiveSigner)?;

// Encode the message data and compute its keccak256 hash
let msg = data.data.encode();
let hash = keccak_256(&msg);

// Verify the Schnorr signature using sr25519_verify
if !sr25519_verify(
&signature,
&hash,
&sr25519::Public(
Self::to_slice_32(&data.signing_key)
.unwrap_or_else(|| panic!("Failed to convert input to sr25519 public key")),
),
) {
return Err(Error::<T>::InvalidSignature.into())
}

Ok(())
}

/// Recovers the ECDSA public key from a given message and signature.
///
/// # Arguments
Expand Down Expand Up @@ -235,7 +368,17 @@ impl<T: Config> Pallet<T> {

return Some(key)
}
None
}

/// Utility function to create slice of fixed size
pub fn to_slice_32(val: &[u8]) -> Option<[u8; 32]> {
if val.len() == SCHNORR_KEY_LENGTH {
let mut key = [0u8; SCHNORR_KEY_LENGTH];
key[..SCHNORR_KEY_LENGTH].copy_from_slice(val);

return Some(key)
}
None
}
}
Loading
Loading