Added RuntimePublic for ecdsa public key. (#6029)

* add generate ecdsa, etc to keystore

* impl ecdsa needed traits

* add ecdsa to sr_io

* add ecdsa to application-crypto

* add ecdsa to test-utils

* add ecdsa debug derive

* fix ecdsa public
This commit is contained in:
satellitex
2020-05-16 00:04:38 +09:00
committed by GitHub
parent f36f57b0bf
commit 302c543b49
11 changed files with 364 additions and 14 deletions
+32 -1
View File
@@ -23,7 +23,7 @@ use sp_core::{
traits::{BareCryptoStore, BareCryptoStoreError as TraitError},
Encode,
};
use sp_application_crypto::{AppKey, AppPublic, AppPair, ed25519, sr25519};
use sp_application_crypto::{AppKey, AppPublic, AppPair, ed25519, sr25519, ecdsa};
use parking_lot::RwLock;
/// Keystore pointer
@@ -308,6 +308,7 @@ impl BareCryptoStore for Store {
.fold(Vec::new(), |mut v, k| {
v.push(CryptoTypePublicPair(sr25519::CRYPTO_ID, k.clone()));
v.push(CryptoTypePublicPair(ed25519::CRYPTO_ID, k.clone()));
v.push(CryptoTypePublicPair(ecdsa::CRYPTO_ID, k.clone()));
v
}))
}
@@ -343,6 +344,13 @@ impl BareCryptoStore for Store {
.key_pair_by_type::<sr25519::Pair>(&pub_key, id)
.map_err(|e| TraitError::from(e))?;
Ok(key_pair.sign(msg).encode())
},
ecdsa::CRYPTO_ID => {
let pub_key = ecdsa::Public::from_slice(key.1.as_slice());
let key_pair: ecdsa::Pair = self
.key_pair_by_type::<ecdsa::Pair>(&pub_key, id)
.map_err(|e| TraitError::from(e))?;
Ok(key_pair.sign(msg).encode())
}
_ => Err(TraitError::KeyNotSupported(id))
}
@@ -394,6 +402,29 @@ impl BareCryptoStore for Store {
Ok(pair.public())
}
fn ecdsa_public_keys(&self, key_type: KeyTypeId) -> Vec<ecdsa::Public> {
self.raw_public_keys(key_type)
.map(|v| {
v.into_iter()
.map(|k| ecdsa::Public::from_slice(k.as_slice()))
.collect()
})
.unwrap_or_default()
}
fn ecdsa_generate_new(
&mut self,
id: KeyTypeId,
seed: Option<&str>,
) -> std::result::Result<ecdsa::Public, TraitError> {
let pair = match seed {
Some(seed) => self.insert_ephemeral_from_seed_by_type::<ecdsa::Pair>(seed, id),
None => self.generate_by_type::<ecdsa::Pair>(id),
}.map_err(|e| -> TraitError { e.into() })?;
Ok(pair.public())
}
fn insert_unknown(&mut self, key_type: KeyTypeId, suri: &str, public: &[u8])
-> std::result::Result<(), ()>
{
@@ -0,0 +1,61 @@
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Ecdsa crypto types.
use crate::{RuntimePublic, KeyTypeId};
use sp_std::vec::Vec;
pub use sp_core::ecdsa::*;
mod app {
use sp_core::testing::ECDSA;
crate::app_crypto!(super, ECDSA);
impl crate::traits::BoundToRuntimeAppPublic for Public {
type Public = Self;
}
}
pub use app::{Public as AppPublic, Signature as AppSignature};
#[cfg(feature = "full_crypto")]
pub use app::Pair as AppPair;
impl RuntimePublic for Public {
type Signature = Signature;
fn all(key_type: KeyTypeId) -> crate::Vec<Self> {
sp_io::crypto::ecdsa_public_keys(key_type)
}
fn generate_pair(key_type: KeyTypeId, seed: Option<Vec<u8>>) -> Self {
sp_io::crypto::ecdsa_generate(key_type, seed)
}
fn sign<M: AsRef<[u8]>>(&self, key_type: KeyTypeId, msg: &M) -> Option<Self::Signature> {
sp_io::crypto::ecdsa_sign(key_type, self, msg.as_ref())
}
fn verify<M: AsRef<[u8]>>(&self, msg: &M, signature: &Self::Signature) -> bool {
sp_io::crypto::ecdsa_verify(&signature, msg.as_ref(), self)
}
fn to_raw_vec(&self) -> Vec<u8> {
sp_core::crypto::Public::to_raw_vec(self)
}
}
@@ -42,6 +42,7 @@ pub use sp_std::{
pub mod ed25519;
pub mod sr25519;
pub mod ecdsa;
mod traits;
pub use traits::*;
@@ -0,0 +1,42 @@
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Integration tests for ecdsa
use sp_runtime::generic::BlockId;
use sp_core::{
crypto::Pair,
testing::{KeyStore, ECDSA},
};
use substrate_test_runtime_client::{
TestClientBuilder, DefaultTestClientBuilderExt, TestClientBuilderExt,
runtime::TestAPI,
};
use sp_api::ProvideRuntimeApi;
use sp_application_crypto::ecdsa::{AppPair, AppPublic};
#[test]
fn ecdsa_works_in_runtime() {
let keystore = KeyStore::new();
let test_client = TestClientBuilder::new().set_keystore(keystore.clone()).build();
let (signature, public) = test_client.runtime_api()
.test_ecdsa_crypto(&BlockId::Number(0))
.expect("Tests `ecdsa` crypto.");
let supported_keys = keystore.read().keys(ECDSA).unwrap();
assert!(supported_keys.contains(&public.clone().into()));
assert!(AppPair::verify(&signature, "ecdsa", &AppPublic::from(public)));
}
@@ -20,4 +20,6 @@
#[cfg(test)]
mod ed25519;
#[cfg(test)]
mod sr25519;
mod sr25519;
#[cfg(test)]
mod ecdsa;
+30 -7
View File
@@ -38,6 +38,7 @@ use crate::crypto::Ss58Codec;
#[cfg(feature = "std")]
use serde::{de, Serializer, Serialize, Deserializer, Deserialize};
use crate::crypto::{Public as TraitPublic, CryptoTypePublicPair, UncheckedFrom, CryptoType, Derive, CryptoTypeId};
use sp_runtime_interface::pass_by::PassByInner;
#[cfg(feature = "full_crypto")]
use secp256k1::{PublicKey, SecretKey};
@@ -51,7 +52,7 @@ pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecds");
type Seed = [u8; 32];
/// The ECDSA compressed public key.
#[derive(Clone, Encode, Decode)]
#[derive(Clone, Encode, Decode, PassByInner)]
pub struct Public([u8; 33]);
impl PartialOrd for Public {
@@ -125,6 +126,18 @@ impl TraitPublic for Public {
}
}
impl From<Public> for CryptoTypePublicPair {
fn from(key: Public) -> Self {
(&key).into()
}
}
impl From<&Public> for CryptoTypePublicPair {
fn from(key: &Public) -> Self {
CryptoTypePublicPair(CRYPTO_ID, key.to_raw_vec())
}
}
impl Derive for Public {}
impl Default for Public {
@@ -178,12 +191,17 @@ impl std::fmt::Display for Public {
}
}
#[cfg(feature = "std")]
impl std::fmt::Debug for Public {
impl sp_std::fmt::Debug for Public {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let s = self.to_ss58check();
write!(f, "{} ({}...)", crate::hexdisplay::HexDisplay::from(&self.as_ref()), &s[0..8])
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}
#[cfg(feature = "std")]
@@ -209,7 +227,7 @@ impl sp_std::hash::Hash for Public {
}
/// A signature (a 512-bit value, plus 8 bits for recovery ID).
#[derive(Encode, Decode)]
#[derive(Encode, Decode, PassByInner)]
pub struct Signature([u8; 65]);
impl sp_std::convert::TryFrom<&[u8]> for Signature {
@@ -289,11 +307,16 @@ impl AsMut<[u8]> for Signature {
}
}
#[cfg(feature = "std")]
impl std::fmt::Debug for Signature {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
impl sp_std::fmt::Debug for Signature {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "{}", crate::hexdisplay::HexDisplay::from(&self.0))
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}
#[cfg(feature = "full_crypto")]
+49 -1
View File
@@ -21,7 +21,7 @@ use crate::crypto::KeyTypeId;
#[cfg(feature = "std")]
use crate::{
crypto::{Pair, Public, CryptoTypePublicPair},
ed25519, sr25519,
ed25519, sr25519, ecdsa,
traits::BareCryptoStoreError
};
#[cfg(feature = "std")]
@@ -30,6 +30,8 @@ use std::collections::HashSet;
pub const ED25519: KeyTypeId = KeyTypeId(*b"ed25");
/// Key type for generic Sr 25519 key.
pub const SR25519: KeyTypeId = KeyTypeId(*b"sr25");
/// Key type for generic Sr 25519 key.
pub const ECDSA: KeyTypeId = KeyTypeId(*b"ecds");
/// A keystore implementation usable in tests.
#[cfg(feature = "std")]
@@ -62,6 +64,14 @@ impl KeyStore {
)
}
fn ecdsa_key_pair(&self, id: KeyTypeId, pub_key: &ecdsa::Public) -> Option<ecdsa::Pair> {
self.keys.get(&id)
.and_then(|inner|
inner.get(pub_key.as_slice())
.map(|s| ecdsa::Pair::from_string(s, None).expect("`ecdsa` seed slice is valid"))
)
}
}
#[cfg(feature = "std")]
@@ -74,6 +84,7 @@ impl crate::traits::BareCryptoStore for KeyStore {
.fold(Vec::new(), |mut v, k| {
v.push(CryptoTypePublicPair(sr25519::CRYPTO_ID, k.clone()));
v.push(CryptoTypePublicPair(ed25519::CRYPTO_ID, k.clone()));
v.push(CryptoTypePublicPair(ecdsa::CRYPTO_ID, k.clone()));
v
}))
})
@@ -142,6 +153,37 @@ impl crate::traits::BareCryptoStore for KeyStore {
}
}
fn ecdsa_public_keys(&self, id: KeyTypeId) -> Vec<ecdsa::Public> {
self.keys.get(&id)
.map(|keys|
keys.values()
.map(|s| ecdsa::Pair::from_string(s, None).expect("`ecdsa` seed slice is valid"))
.map(|p| p.public())
.collect()
)
.unwrap_or_default()
}
fn ecdsa_generate_new(
&mut self,
id: KeyTypeId,
seed: Option<&str>,
) -> Result<ecdsa::Public, BareCryptoStoreError> {
match seed {
Some(seed) => {
let pair = ecdsa::Pair::from_string(seed, None)
.map_err(|_| BareCryptoStoreError::ValidationError("Generates an `ecdsa` pair.".to_owned()))?;
self.keys.entry(id).or_default().insert(pair.public().to_raw_vec(), seed.into());
Ok(pair.public())
},
None => {
let (pair, phrase, _) = ecdsa::Pair::generate_with_phrase(None);
self.keys.entry(id).or_default().insert(pair.public().to_raw_vec(), phrase);
Ok(pair.public())
}
}
}
fn insert_unknown(&mut self, id: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> {
self.keys.entry(id).or_default().insert(public.to_owned(), suri.to_string());
Ok(())
@@ -187,6 +229,12 @@ impl crate::traits::BareCryptoStore for KeyStore {
.ok_or(BareCryptoStoreError::PairNotFound("sr25519".to_owned()))?;
return Ok(key_pair.sign(msg).encode());
}
ecdsa::CRYPTO_ID => {
let key_pair: ecdsa::Pair = self
.ecdsa_key_pair(id, &ecdsa::Public::from_slice(key.1.as_slice()))
.ok_or(BareCryptoStoreError::PairNotFound("ecdsa".to_owned()))?;
return Ok(key_pair.sign(msg).encode());
}
_ => Err(BareCryptoStoreError::KeyNotSupported(id))
}
}
+13 -1
View File
@@ -19,7 +19,7 @@
use crate::{
crypto::{KeyTypeId, CryptoTypePublicPair},
ed25519, sr25519,
ed25519, sr25519, ecdsa,
};
use std::{
@@ -77,6 +77,18 @@ pub trait BareCryptoStore: Send + Sync {
id: KeyTypeId,
seed: Option<&str>,
) -> Result<ed25519::Public, BareCryptoStoreError>;
/// Returns all ecdsa public keys for the given key type.
fn ecdsa_public_keys(&self, id: KeyTypeId) -> Vec<ecdsa::Public>;
/// Generate a new ecdsa key pair for the given key type and an optional seed.
///
/// If the given seed is `Some(_)`, the key pair will only be stored in memory.
///
/// Returns the public key of the generated key pair.
fn ecdsa_generate_new(
&mut self,
id: KeyTypeId,
seed: Option<&str>,
) -> Result<ecdsa::Public, BareCryptoStoreError>;
/// Insert a new key. This doesn't require any known of the crypto; but a public key must be
/// manually provided.
+24 -1
View File
@@ -17,7 +17,7 @@
//! Batch/parallel verification.
use sp_core::{ed25519, sr25519, crypto::Pair, traits::CloneableSpawn};
use sp_core::{ed25519, sr25519, ecdsa, crypto::Pair, traits::CloneableSpawn};
use std::sync::{Arc, atomic::{AtomicBool, Ordering as AtomicOrdering}};
use futures::{future::FutureExt, task::FutureObj, channel::oneshot};
@@ -124,6 +124,29 @@ impl BatchVerifier {
true
}
/// Push ecdsa signature to verify.
///
/// Returns false if some of the pushed signatures before already failed the check
/// (in this case it won't verify anything else)
pub fn push_ecdsa(
&mut self,
signature: ecdsa::Signature,
pub_key: ecdsa::Public,
message: Vec<u8>,
) -> bool {
if self.invalid.load(AtomicOrdering::Relaxed) { return false; }
if self.spawn_verification_task(move || ecdsa::Pair::verify(&signature, &message, &pub_key)).is_err() {
log::debug!(
target: "runtime",
"Batch-verification returns false because failed to spawn background task.",
);
return false;
}
true
}
fn verify_sr25519_batch(items: Vec<Sr25519BatchItem>) -> bool {
let messages = items.iter().map(|item| &item.message[..]).collect();
let signatures = items.iter().map(|item| &item.signature).collect();
+75 -1
View File
@@ -42,7 +42,7 @@ use sp_core::{
};
use sp_core::{
crypto::KeyTypeId, ed25519, sr25519, H256, LogLevel,
crypto::KeyTypeId, ed25519, sr25519, ecdsa, H256, LogLevel,
offchain::{
Timestamp, HttpRequestId, HttpRequestStatus, HttpError, StorageKind, OpaqueNetworkState,
},
@@ -545,6 +545,80 @@ pub trait Crypto {
sr25519::Pair::verify_deprecated(sig, msg, pubkey)
}
/// Returns all `ecdsa` public keys for the given key id from the keystore.
fn ecdsa_public_keys(&mut self, id: KeyTypeId) -> Vec<ecdsa::Public> {
self.extension::<KeystoreExt>()
.expect("No `keystore` associated for the current context!")
.read()
.ecdsa_public_keys(id)
}
/// Generate an `ecdsa` key for the given key type using an optional `seed` and
/// store it in the keystore.
///
/// The `seed` needs to be a valid utf8.
///
/// Returns the public key.
fn ecdsa_generate(&mut self, id: KeyTypeId, seed: Option<Vec<u8>>) -> ecdsa::Public {
let seed = seed.as_ref().map(|s| std::str::from_utf8(&s).expect("Seed is valid utf8!"));
self.extension::<KeystoreExt>()
.expect("No `keystore` associated for the current context!")
.write()
.ecdsa_generate_new(id, seed)
.expect("`ecdsa_generate` failed")
}
/// Sign the given `msg` with the `ecdsa` key that corresponds to the given public key and
/// key type in the keystore.
///
/// Returns the signature.
fn ecdsa_sign(
&mut self,
id: KeyTypeId,
pub_key: &ecdsa::Public,
msg: &[u8],
) -> Option<ecdsa::Signature> {
self.extension::<KeystoreExt>()
.expect("No `keystore` associated for the current context!")
.read()
.sign_with(id, &pub_key.into(), msg)
.map(|sig| ecdsa::Signature::from_slice(sig.as_slice()))
.ok()
}
/// Verify `ecdsa` signature.
///
/// Returns `true` when the verification is either successful or batched.
/// If no batching verification extension registered, this will return the result
/// of verification immediately. If batching verification extension is registered
/// caller should call `crypto::finish_batch_verify` to actualy check all submitted
/// signatures.
fn ecdsa_verify(
sig: &ecdsa::Signature,
msg: &[u8],
pub_key: &ecdsa::Public,
) -> bool {
// TODO: see #5554, this is used outside of externalities context/runtime, thus this manual
// `with_externalities`.
//
// This `with_externalities(..)` block returns Some(Some(result)) if signature verification was successfully
// batched, everything else (Some(None)/None) means it was not batched and needs to be verified.
let evaluated = sp_externalities::with_externalities(|mut instance|
instance.extension::<VerificationExt>().map(
|extension| extension.push_ecdsa(
sig.clone(),
pub_key.clone(),
msg.to_vec(),
)
)
);
match evaluated {
Some(Some(val)) => val,
_ => ecdsa::Pair::verify(sig, msg, pub_key),
}
}
/// Verify and recover a SECP256k1 ECDSA signature.
///
/// - `sig` is passed in RSV format. V should be either `0/1` or `27/28`.
+34 -1
View File
@@ -27,7 +27,7 @@ use sp_std::{prelude::*, marker::PhantomData};
use codec::{Encode, Decode, Input, Error};
use sp_core::{OpaqueMetadata, RuntimeDebug, ChangesTrieConfiguration};
use sp_application_crypto::{ed25519, sr25519, RuntimeAppPublic};
use sp_application_crypto::{ed25519, sr25519, ecdsa, RuntimeAppPublic};
use trie_db::{TrieMut, Trie};
use sp_trie::PrefixedMemoryDB;
use sp_trie::trie_types::{TrieDB, TrieDBMut};
@@ -305,6 +305,10 @@ cfg_if! {
///
/// Returns the signature generated for the message `sr25519`.
fn test_sr25519_crypto() -> (sr25519::AppSignature, sr25519::AppPublic);
/// Test that `ecdsa` crypto works in the runtime.
///
/// Returns the signature generated for the message `ecdsa`.
fn test_ecdsa_crypto() -> (ecdsa::AppSignature, ecdsa::AppPublic);
/// Run various tests against storage.
fn test_storage();
}
@@ -347,6 +351,10 @@ cfg_if! {
///
/// Returns the signature generated for the message `sr25519`.
fn test_sr25519_crypto() -> (sr25519::AppSignature, sr25519::AppPublic);
/// Test that `ecdsa` crypto works in the runtime.
///
/// Returns the signature generated for the message `ecdsa`.
fn test_ecdsa_crypto() -> (ecdsa::AppSignature, ecdsa::AppPublic);
/// Run various tests against storage.
fn test_storage();
}
@@ -485,6 +493,7 @@ impl_opaque_keys! {
pub struct SessionKeys {
pub ed25519: ed25519::AppPublic,
pub sr25519: sr25519::AppPublic,
pub ecdsa: ecdsa::AppPublic,
}
}
@@ -620,6 +629,10 @@ cfg_if! {
test_sr25519_crypto()
}
fn test_ecdsa_crypto() -> (ecdsa::AppSignature, ecdsa::AppPublic) {
test_ecdsa_crypto()
}
fn test_storage() {
test_read_storage();
test_read_child_storage();
@@ -837,6 +850,10 @@ cfg_if! {
test_sr25519_crypto()
}
fn test_ecdsa_crypto() -> (ecdsa::AppSignature, ecdsa::AppPublic) {
test_ecdsa_crypto()
}
fn test_storage() {
test_read_storage();
test_read_child_storage();
@@ -929,6 +946,22 @@ fn test_sr25519_crypto() -> (sr25519::AppSignature, sr25519::AppPublic) {
(signature, public0)
}
fn test_ecdsa_crypto() -> (ecdsa::AppSignature, ecdsa::AppPublic) {
let public0 = ecdsa::AppPublic::generate_pair(None);
let public1 = ecdsa::AppPublic::generate_pair(None);
let public2 = ecdsa::AppPublic::generate_pair(None);
let all = ecdsa::AppPublic::all();
assert!(all.contains(&public0));
assert!(all.contains(&public1));
assert!(all.contains(&public2));
let signature = public0.sign(&"ecdsa").expect("Generates a valid `ecdsa` signature.");
assert!(public0.verify(&"ecdsa", &signature));
(signature, public0)
}
fn test_read_storage() {
const KEY: &[u8] = b":read_storage";
sp_io::storage::set(KEY, b"test");