Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 386855b

Browse files
authored
Remove external crypto trait bounds (#207)
* BeefyKeystore newtype * WIP * remove mod ecdsa * WIP * fix tests * some polishing
1 parent 31043a7 commit 386855b

File tree

17 files changed

+456
-249
lines changed

17 files changed

+456
-249
lines changed

client/beefy/rpc/src/lib.rs

+6-8
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#![warn(missing_docs)]
2020

2121
use beefy_gadget::notification::BeefySignedCommitmentStream;
22-
use codec::Encode;
2322
use futures::{StreamExt, TryStreamExt};
2423
use jsonrpc_core::futures::{
2524
future::{Executor as Executor01, Future as Future01},
@@ -63,14 +62,14 @@ pub trait BeefyApi<Notification, Hash> {
6362
}
6463

6564
/// Implements the BeefyApi RPC trait for interacting with BEEFY.
66-
pub struct BeefyRpcHandler<Block: BlockT, Signature> {
67-
signed_commitment_stream: BeefySignedCommitmentStream<Block, Signature>,
65+
pub struct BeefyRpcHandler<Block: BlockT> {
66+
signed_commitment_stream: BeefySignedCommitmentStream<Block>,
6867
manager: SubscriptionManager,
6968
}
7069

71-
impl<Block: BlockT, Signature> BeefyRpcHandler<Block, Signature> {
70+
impl<Block: BlockT> BeefyRpcHandler<Block> {
7271
/// Creates a new BeefyRpcHandler instance.
73-
pub fn new<E>(signed_commitment_stream: BeefySignedCommitmentStream<Block, Signature>, executor: E) -> Self
72+
pub fn new<E>(signed_commitment_stream: BeefySignedCommitmentStream<Block>, executor: E) -> Self
7473
where
7574
E: Executor01<Box<dyn Future01<Item = (), Error = ()> + Send>> + Send + Sync + 'static,
7675
{
@@ -82,10 +81,9 @@ impl<Block: BlockT, Signature> BeefyRpcHandler<Block, Signature> {
8281
}
8382
}
8483

85-
impl<Block, Signature> BeefyApi<notification::SignedCommitment, Block> for BeefyRpcHandler<Block, Signature>
84+
impl<Block> BeefyApi<notification::SignedCommitment, Block> for BeefyRpcHandler<Block>
8685
where
8786
Block: BlockT,
88-
Signature: Clone + Encode + Send + 'static,
8987
{
9088
type Metadata = sc_rpc::Metadata;
9189

@@ -97,7 +95,7 @@ where
9795
let stream = self
9896
.signed_commitment_stream
9997
.subscribe()
100-
.map(|x| Ok::<_, ()>(notification::SignedCommitment::new::<Block, Signature>(x)))
98+
.map(|x| Ok::<_, ()>(notification::SignedCommitment::new::<Block>(x)))
10199
.map_err(|e| warn!("Notification stream error: {:?}", e))
102100
.compat();
103101

client/beefy/rpc/src/notification.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,9 @@ use sp_runtime::traits::Block as BlockT;
2626
pub struct SignedCommitment(sp_core::Bytes);
2727

2828
impl SignedCommitment {
29-
pub fn new<Block, Signature>(
30-
signed_commitment: beefy_gadget::notification::SignedCommitment<Block, Signature>,
31-
) -> Self
29+
pub fn new<Block>(signed_commitment: beefy_gadget::notification::SignedCommitment<Block>) -> Self
3230
where
3331
Block: BlockT,
34-
Signature: Encode,
3532
{
3633
SignedCommitment(signed_commitment.encode().into())
3734
}

client/beefy/src/error.rs

+6-11
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,10 @@
2020
2121
use std::fmt::Debug;
2222

23-
use sp_core::crypto::Public;
24-
25-
/// Crypto related errors
26-
#[derive(Debug, thiserror::Error)]
27-
pub(crate) enum Crypto<Id: Public + Debug> {
28-
/// Check signature error
29-
#[error("Message signature {0} by {1:?} is invalid.")]
30-
InvalidSignature(String, Id),
31-
/// Sign commitment error
32-
#[error("Failed to sign comitment using key: {0:?}. Reason: {1}")]
33-
CannotSign(Id, String),
23+
#[derive(Debug, thiserror::Error, PartialEq)]
24+
pub enum Error {
25+
#[error("Keystore error: {0}")]
26+
Keystore(String),
27+
#[error("Signature error: {0}")]
28+
Signature(String),
3429
}

client/beefy/src/gossip.rs

+16-19
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1616

1717
/// The maximum number of live gossip rounds allowed, i.e. we will expire messages older than this.
18-
use std::{fmt::Debug, marker::PhantomData};
19-
2018
use codec::{Decode, Encode};
2119
use log::debug;
2220
use parking_lot::RwLock;
@@ -27,10 +25,14 @@ use sc_network_gossip::{
2725
ValidatorContext as GossipValidatorContext,
2826
};
2927

30-
use sp_core::Pair;
3128
use sp_runtime::traits::{Block, Hash, Header, NumberFor};
3229

33-
use beefy_primitives::{MmrRootHash, VoteMessage};
30+
use beefy_primitives::{
31+
crypto::{Public, Signature},
32+
MmrRootHash, VoteMessage,
33+
};
34+
35+
use crate::keystore::BeefyKeystore;
3436

3537
// Limit BEEFY gossip by keeping only a bound number of voting rounds alive.
3638
const MAX_LIVE_GOSSIP_ROUNDS: usize = 5;
@@ -51,24 +53,22 @@ where
5153
/// rejected/expired.
5254
///
5355
///All messaging is handled in a single BEEFY global topic.
54-
pub(crate) struct BeefyGossipValidator<B, P>
56+
pub(crate) struct BeefyGossipValidator<B>
5557
where
5658
B: Block,
5759
{
5860
topic: B::Hash,
5961
live_rounds: RwLock<Vec<NumberFor<B>>>,
60-
_pair: PhantomData<P>,
6162
}
6263

63-
impl<B, P> BeefyGossipValidator<B, P>
64+
impl<B> BeefyGossipValidator<B>
6465
where
6566
B: Block,
6667
{
67-
pub fn new() -> BeefyGossipValidator<B, P> {
68+
pub fn new() -> BeefyGossipValidator<B> {
6869
BeefyGossipValidator {
6970
topic: topic::<B>(),
7071
live_rounds: RwLock::new(Vec::new()),
71-
_pair: PhantomData,
7272
}
7373
}
7474

@@ -91,21 +91,18 @@ where
9191
}
9292
}
9393

94-
impl<B, P> GossipValidator<B> for BeefyGossipValidator<B, P>
94+
impl<B> GossipValidator<B> for BeefyGossipValidator<B>
9595
where
9696
B: Block,
97-
P: Pair,
98-
P::Public: Debug + Decode,
99-
P::Signature: Debug + Decode,
10097
{
10198
fn validate(
10299
&self,
103100
_context: &mut dyn GossipValidatorContext<B>,
104101
sender: &sc_network::PeerId,
105102
mut data: &[u8],
106103
) -> GossipValidationResult<B::Hash> {
107-
if let Ok(msg) = VoteMessage::<MmrRootHash, NumberFor<B>, P::Public, P::Signature>::decode(&mut data) {
108-
if P::verify(&msg.signature, &msg.commitment.encode(), &msg.id) {
104+
if let Ok(msg) = VoteMessage::<MmrRootHash, NumberFor<B>, Public, Signature>::decode(&mut data) {
105+
if BeefyKeystore::verify(&msg.id, &msg.signature, &msg.commitment.encode()) {
109106
return GossipValidationResult::ProcessAndKeep(self.topic);
110107
} else {
111108
// TODO: report peer
@@ -119,25 +116,25 @@ where
119116
fn message_expired<'a>(&'a self) -> Box<dyn FnMut(B::Hash, &[u8]) -> bool + 'a> {
120117
let live_rounds = self.live_rounds.read();
121118
Box::new(move |_topic, mut data| {
122-
let message = match VoteMessage::<MmrRootHash, NumberFor<B>, P::Public, P::Signature>::decode(&mut data) {
119+
let message = match VoteMessage::<MmrRootHash, NumberFor<B>, Public, Signature>::decode(&mut data) {
123120
Ok(vote) => vote,
124121
Err(_) => return true,
125122
};
126123

127-
!BeefyGossipValidator::<B, P>::is_live(&live_rounds, message.commitment.block_number)
124+
!BeefyGossipValidator::<B>::is_live(&live_rounds, message.commitment.block_number)
128125
})
129126
}
130127

131128
#[allow(clippy::type_complexity)]
132129
fn message_allowed<'a>(&'a self) -> Box<dyn FnMut(&PeerId, MessageIntent, &B::Hash, &[u8]) -> bool + 'a> {
133130
let live_rounds = self.live_rounds.read();
134131
Box::new(move |_who, _intent, _topic, mut data| {
135-
let message = match VoteMessage::<MmrRootHash, NumberFor<B>, P::Public, P::Signature>::decode(&mut data) {
132+
let message = match VoteMessage::<MmrRootHash, NumberFor<B>, Public, Signature>::decode(&mut data) {
136133
Ok(vote) => vote,
137134
Err(_) => return true,
138135
};
139136

140-
BeefyGossipValidator::<B, P>::is_live(&live_rounds, message.commitment.block_number)
137+
BeefyGossipValidator::<B>::is_live(&live_rounds, message.commitment.block_number)
141138
})
142139
}
143140
}

client/beefy/src/keystore.rs

+206
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
// This program is free software: you can redistribute it and/or modify
2+
// it under the terms of the GNU General Public License as published by
3+
// the Free Software Foundation, either version 3 of the License, or
4+
// (at your option) any later version.
5+
6+
// This program is distributed in the hope that it will be useful,
7+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
8+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9+
// GNU General Public License for more details.
10+
11+
// You should have received a copy of the GNU General Public License
12+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
13+
14+
use std::convert::TryInto;
15+
16+
use sp_application_crypto::RuntimeAppPublic;
17+
use sp_core::keccak_256;
18+
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
19+
20+
use beefy_primitives::{
21+
crypto::{Public, Signature},
22+
KEY_TYPE,
23+
};
24+
25+
use crate::error;
26+
27+
/// A BEEFY specific keystore implemented as a `Newtype`. This is basically a
28+
/// wrapper around [`sp_keystore::SyncCryptoStore`] and allows to customize
29+
/// common cryptographic functionality.
30+
pub(crate) struct BeefyKeystore(Option<SyncCryptoStorePtr>);
31+
32+
impl BeefyKeystore {
33+
/// Check if the keystore contains a private key for one of the public keys
34+
/// contained in `keys`. A public key with a matching private key is known
35+
/// as a local authority id.
36+
///
37+
/// Return the public key for which we also do have a private key. If no
38+
/// matching private key is found, `None` will be returned.
39+
pub fn authority_id(&self, keys: &[Public]) -> Option<Public> {
40+
let store = self.0.clone()?;
41+
42+
for key in keys {
43+
if SyncCryptoStore::has_keys(&*store, &[(key.to_raw_vec(), KEY_TYPE)]) {
44+
return Some(key.clone());
45+
}
46+
}
47+
48+
None
49+
}
50+
51+
/// Sign `message` with the `public` key.
52+
///
53+
/// Note that `message` usually will be pre-hashed before being singed.
54+
///
55+
/// Return the message signature or an error in case of failure.
56+
pub fn sign(&self, public: &Public, message: &[u8]) -> Result<Signature, error::Error> {
57+
let store = if let Some(store) = self.0.clone() {
58+
store
59+
} else {
60+
return Err(error::Error::Keystore("no Keystore".to_string()));
61+
};
62+
63+
let msg = keccak_256(message);
64+
let public = public.as_ref();
65+
66+
let sig = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, public, &msg)
67+
.map_err(|e| error::Error::Keystore(e.to_string()))?
68+
.ok_or_else(|| error::Error::Signature("ecdsa_sign_prehashed() failed".to_string()))?;
69+
70+
// check that `sig` has the expected result type
71+
let sig = sig
72+
.clone()
73+
.try_into()
74+
.map_err(|_| error::Error::Signature(format!("invalid signature {:?} for key {:?}", sig, public)))?;
75+
76+
Ok(sig)
77+
}
78+
79+
/// Use the `public` key to verify that `sig` is a valid signature for `message`.
80+
///
81+
/// Return `true` if the signature is authentic, `false` otherwise.
82+
pub fn verify(public: &Public, sig: &Signature, message: &[u8]) -> bool {
83+
let msg = keccak_256(message);
84+
let sig = sig.as_ref();
85+
let public = public.as_ref();
86+
87+
sp_core::ecdsa::Pair::verify_prehashed(sig, &msg, public)
88+
}
89+
}
90+
91+
impl From<Option<SyncCryptoStorePtr>> for BeefyKeystore {
92+
fn from(store: Option<SyncCryptoStorePtr>) -> BeefyKeystore {
93+
BeefyKeystore(store)
94+
}
95+
}
96+
97+
#[cfg(test)]
98+
mod tests {
99+
#![allow(clippy::unit_cmp)]
100+
101+
use sp_core::{keccak_256, Pair};
102+
use sp_keystore::{testing::KeyStore, SyncCryptoStore, SyncCryptoStorePtr};
103+
104+
use beefy_primitives::{crypto, KEY_TYPE};
105+
106+
use super::BeefyKeystore;
107+
use crate::error::Error;
108+
109+
#[test]
110+
fn authority_id_works() {
111+
let store: SyncCryptoStorePtr = KeyStore::new().into();
112+
113+
let alice = crypto::Pair::from_string("//Alice", None).unwrap();
114+
let _ = SyncCryptoStore::insert_unknown(&*store, KEY_TYPE, "//Alice", alice.public().as_ref()).unwrap();
115+
116+
let bob = crypto::Pair::from_string("//Bob", None).unwrap();
117+
let charlie = crypto::Pair::from_string("//Charlie", None).unwrap();
118+
119+
let store: BeefyKeystore = Some(store).into();
120+
121+
let mut keys = vec![bob.public(), charlie.public()];
122+
123+
let id = store.authority_id(keys.as_slice());
124+
assert!(id.is_none());
125+
126+
keys.push(alice.public());
127+
128+
let id = store.authority_id(keys.as_slice()).unwrap();
129+
assert_eq!(id, alice.public());
130+
}
131+
132+
#[test]
133+
fn sign_works() {
134+
let store: SyncCryptoStorePtr = KeyStore::new().into();
135+
136+
let suri = "//Alice";
137+
let pair = sp_core::ecdsa::Pair::from_string(suri, None).unwrap();
138+
139+
let res = SyncCryptoStore::insert_unknown(&*store, KEY_TYPE, suri, pair.public().as_ref()).unwrap();
140+
assert_eq!((), res);
141+
142+
let beefy_store: BeefyKeystore = Some(store.clone()).into();
143+
144+
let msg = b"are you involved or commited?";
145+
let sig1 = beefy_store.sign(&pair.public().into(), msg).unwrap();
146+
147+
let msg = keccak_256(b"are you involved or commited?");
148+
let sig2 = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, &pair.public(), &msg)
149+
.unwrap()
150+
.unwrap();
151+
152+
assert_eq!(sig1, sig2.into());
153+
}
154+
155+
#[test]
156+
fn sign_error() {
157+
let store: SyncCryptoStorePtr = KeyStore::new().into();
158+
159+
let bob = crypto::Pair::from_string("//Bob", None).unwrap();
160+
let res = SyncCryptoStore::insert_unknown(&*store, KEY_TYPE, "//Bob", bob.public().as_ref()).unwrap();
161+
assert_eq!((), res);
162+
163+
let alice = crypto::Pair::from_string("//Alice", None).unwrap();
164+
165+
let store: BeefyKeystore = Some(store).into();
166+
167+
let msg = b"are you involved or commited?";
168+
let sig = store.sign(&alice.public(), msg).err().unwrap();
169+
let err = Error::Signature("ecdsa_sign_prehashed() failed".to_string());
170+
assert_eq!(sig, err);
171+
}
172+
173+
#[test]
174+
fn sign_no_keystore() {
175+
let store: BeefyKeystore = None.into();
176+
177+
let alice = crypto::Pair::from_string("//Alice", None).unwrap();
178+
let msg = b"are you involved or commited";
179+
180+
let sig = store.sign(&alice.public(), msg).err().unwrap();
181+
let err = Error::Keystore("no Keystore".to_string());
182+
assert_eq!(sig, err);
183+
}
184+
185+
#[test]
186+
fn verify_works() {
187+
let store: SyncCryptoStorePtr = KeyStore::new().into();
188+
189+
let suri = "//Alice";
190+
let pair = crypto::Pair::from_string(suri, None).unwrap();
191+
192+
let res = SyncCryptoStore::insert_unknown(&*store, KEY_TYPE, suri, pair.public().as_ref()).unwrap();
193+
assert_eq!((), res);
194+
195+
let store: BeefyKeystore = Some(store).into();
196+
197+
// `msg` and `sig` match
198+
let msg = b"are you involved or commited?";
199+
let sig = store.sign(&pair.public(), msg).unwrap();
200+
assert!(BeefyKeystore::verify(&pair.public(), &sig, msg));
201+
202+
// `msg and `sig` don't match
203+
let msg = b"you are just involved";
204+
assert!(!BeefyKeystore::verify(&pair.public(), &sig, msg));
205+
}
206+
}

0 commit comments

Comments
 (0)