Initial vote signing service implementation (#1996)

* Initial vote signing service implementation

- Does not use enclave for secure signing

* fix clippy errors

* added some tests

* more tests

* Address review comments + more tests
This commit is contained in:
Pankaj Garg 2018-12-04 11:10:57 -08:00 committed by GitHub
parent da44b0f0f6
commit 2112c87e13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 386 additions and 25 deletions

1
Cargo.lock generated
View File

@ -1912,6 +1912,7 @@ dependencies = [
"bs58 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "bs58 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
"solana-jsonrpc-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "solana-jsonrpc-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"solana-jsonrpc-http-server 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "solana-jsonrpc-http-server 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"solana-jsonrpc-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "solana-jsonrpc-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -10,6 +10,7 @@ license = "Apache-2.0"
bs58 = "0.2.0" bs58 = "0.2.0"
clap = "2.31" clap = "2.31"
log = "0.4.2" log = "0.4.2"
serde_json = "1.0.10"
solana-sdk = { path = "../sdk", version = "0.11.0" } solana-sdk = { path = "../sdk", version = "0.11.0" }
solana-metrics = { path = "../metrics", version = "0.11.0" } solana-metrics = { path = "../metrics", version = "0.11.0" }
solana-jsonrpc-core = "0.3.0" solana-jsonrpc-core = "0.3.0"

View File

@ -8,3 +8,6 @@ extern crate solana_jsonrpc_http_server as jsonrpc_http_server;
extern crate solana_sdk; extern crate solana_sdk;
#[macro_use] #[macro_use]
extern crate solana_jsonrpc_macros as jsonrpc_macros; extern crate solana_jsonrpc_macros as jsonrpc_macros;
#[cfg(test)]
#[macro_use]
extern crate serde_json;

View File

@ -4,11 +4,12 @@ use bs58;
use jsonrpc_core::*; use jsonrpc_core::*;
use jsonrpc_http_server::*; use jsonrpc_http_server::*;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Signature; use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
use std::collections::HashMap;
use std::mem; use std::mem;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::{Arc, RwLock};
use std::thread::{self, sleep, Builder, JoinHandle}; use std::thread::{self, sleep, Builder, JoinHandle};
use std::time::Duration; use std::time::Duration;
@ -32,7 +33,6 @@ impl VoteSignerRpcService {
let server = let server =
ServerBuilder::with_meta_extractor(io, move |_req: &hyper::Request<hyper::Body>| Meta { ServerBuilder::with_meta_extractor(io, move |_req: &hyper::Request<hyper::Body>| Meta {
request_processor: request_processor.clone(), request_processor: request_processor.clone(),
rpc_addr,
}).threads(4) }).threads(4)
.cors(DomainsValidation::AllowOnly(vec![ .cors(DomainsValidation::AllowOnly(vec![
AccessControlAllowOrigin::Any, AccessControlAllowOrigin::Any,
@ -69,7 +69,6 @@ impl VoteSignerRpcService {
#[derive(Clone)] #[derive(Clone)]
pub struct Meta { pub struct Meta {
pub request_processor: VoteSignRequestProcessor, pub request_processor: VoteSignRequestProcessor,
pub rpc_addr: SocketAddr,
} }
impl Metadata for Meta {} impl Metadata for Meta {}
@ -78,13 +77,13 @@ build_rpc_trait! {
type Metadata; type Metadata;
#[rpc(meta, name = "registerNode")] #[rpc(meta, name = "registerNode")]
fn register(&self, Self::Metadata, String) -> Result<Pubkey>; fn register(&self, Self::Metadata, String, Signature, Vec<u8>) -> Result<Pubkey>;
#[rpc(meta, name = "signVote")] #[rpc(meta, name = "signVote")]
fn sign(&self, Self::Metadata, String) -> Result<Signature>; fn sign(&self, Self::Metadata, String, Signature, Vec<u8>) -> Result<Signature>;
#[rpc(meta, name = "deregisterNode")] #[rpc(meta, name = "deregisterNode")]
fn deregister(&self, Self::Metadata, String) -> Result<()>; fn deregister(&self, Self::Metadata, String, Signature, Vec<u8>) -> Result<()>;
} }
} }
@ -92,49 +91,102 @@ pub struct VoteSignerRpcImpl;
impl VoteSignerRpc for VoteSignerRpcImpl { impl VoteSignerRpc for VoteSignerRpcImpl {
type Metadata = Meta; type Metadata = Meta;
fn register(&self, meta: Self::Metadata, id: String) -> Result<Pubkey> { fn register(
&self,
meta: Self::Metadata,
id: String,
sig: Signature,
signed_msg: Vec<u8>,
) -> Result<Pubkey> {
info!("register rpc request received: {:?}", id); info!("register rpc request received: {:?}", id);
let pubkey = get_pubkey(id)?; let pubkey = verify_pubkey(id)?;
verify_signature(&sig, &pubkey, &signed_msg)?;
meta.request_processor.register(pubkey) meta.request_processor.register(pubkey)
} }
fn sign(&self, meta: Self::Metadata, id: String) -> Result<Signature> { fn sign(
&self,
meta: Self::Metadata,
id: String,
sig: Signature,
signed_msg: Vec<u8>,
) -> Result<Signature> {
info!("sign rpc request received: {:?}", id); info!("sign rpc request received: {:?}", id);
let pubkey = get_pubkey(id)?; let pubkey = verify_pubkey(id)?;
meta.request_processor.sign(pubkey) verify_signature(&sig, &pubkey, &signed_msg)?;
meta.request_processor.sign(pubkey, &signed_msg)
} }
fn deregister(&self, meta: Self::Metadata, id: String) -> Result<()> { fn deregister(
&self,
meta: Self::Metadata,
id: String,
sig: Signature,
signed_msg: Vec<u8>,
) -> Result<()> {
info!("deregister rpc request received: {:?}", id); info!("deregister rpc request received: {:?}", id);
let pubkey = get_pubkey(id)?; let pubkey = verify_pubkey(id)?;
verify_signature(&sig, &pubkey, &signed_msg)?;
meta.request_processor.deregister(pubkey) meta.request_processor.deregister(pubkey)
} }
} }
#[derive(Clone, Default)] fn verify_signature(sig: &Signature, pubkey: &Pubkey, msg: &[u8]) -> Result<()> {
pub struct VoteSignRequestProcessor {} if sig.verify(pubkey.as_ref(), msg) {
Ok(())
} else {
Err(Error::invalid_request())
}
}
#[derive(Clone)]
pub struct VoteSignRequestProcessor {
nodes: Arc<RwLock<HashMap<Pubkey, Keypair>>>,
}
impl VoteSignRequestProcessor { impl VoteSignRequestProcessor {
/// Process JSON-RPC request items sent via JSON-RPC. /// Process JSON-RPC request items sent via JSON-RPC.
pub fn register(&self, pubkey: Pubkey) -> Result<Pubkey> { pub fn register(&self, pubkey: Pubkey) -> Result<Pubkey> {
Ok(pubkey) {
if let Some(voting_keypair) = self.nodes.read().unwrap().get(&pubkey) {
return Ok(voting_keypair.pubkey());
}
}
let voting_keypair = Keypair::new();
let voting_pubkey = voting_keypair.pubkey();
self.nodes.write().unwrap().insert(pubkey, voting_keypair);
Ok(voting_pubkey)
} }
pub fn sign(&self, _pubkey: Pubkey) -> Result<Signature> { pub fn sign(&self, pubkey: Pubkey, msg: &[u8]) -> Result<Signature> {
let signature = [0u8; 16]; match self.nodes.read().unwrap().get(&pubkey) {
Ok(Signature::new(&signature)) Some(voting_keypair) => {
let sig = Signature::new(&voting_keypair.sign(&msg).as_ref());
Ok(sig)
}
None => Err(Error::invalid_request()),
}
} }
pub fn deregister(&self, _pubkey: Pubkey) -> Result<()> { pub fn deregister(&self, pubkey: Pubkey) -> Result<()> {
self.nodes.write().unwrap().remove(&pubkey);
Ok(()) Ok(())
} }
} }
fn get_pubkey(input: String) -> Result<Pubkey> { impl Default for VoteSignRequestProcessor {
fn default() -> Self {
VoteSignRequestProcessor {
nodes: Arc::new(RwLock::new(HashMap::new())),
}
}
}
fn verify_pubkey(input: String) -> Result<Pubkey> {
let pubkey_vec = bs58::decode(input).into_vec().map_err(|err| { let pubkey_vec = bs58::decode(input).into_vec().map_err(|err| {
info!("get_pubkey: invalid input: {:?}", err); info!("verify_pubkey: invalid input: {:?}", err);
Error::invalid_request() Error::invalid_request()
})?; })?;
if pubkey_vec.len() != mem::size_of::<Pubkey>() { if pubkey_vec.len() != mem::size_of::<Pubkey>() {
info!( info!(
"get_pubkey: invalid pubkey_vec length: {}", "verify_pubkey: invalid pubkey_vec length: {}",
pubkey_vec.len() pubkey_vec.len()
); );
Err(Error::invalid_request()) Err(Error::invalid_request())
@ -144,4 +196,308 @@ fn get_pubkey(input: String) -> Result<Pubkey> {
} }
#[cfg(test)] #[cfg(test)]
mod tests {} mod tests {
use super::*;
use jsonrpc_core::Response;
use solana_sdk::signature::{Keypair, KeypairUtil};
fn start_rpc_handler() -> (MetaIoHandler<Meta>, Meta) {
let request_processor = VoteSignRequestProcessor::default();
let mut io = MetaIoHandler::default();
let rpc = VoteSignerRpcImpl;
io.extend_with(rpc.to_delegate());
let meta = Meta { request_processor };
(io, meta)
}
#[test]
fn test_rpc_register_node() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let sig = Signature::new(&node_keypair.sign(msg.as_bytes()).as_ref());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "registerNode",
"params": [node_pubkey.to_string(), sig, msg.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Success(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
assert!(succ.result.is_array());
assert_eq!(
succ.result.as_array().unwrap().len(),
mem::size_of::<Pubkey>()
);
} else {
assert!(false);
}
} else {
assert!(false);
}
}
#[test]
fn test_rpc_register_node_invalid_sig() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let msg1 = "This is a Test1";
let sig = Signature::new(&node_keypair.sign(msg.as_bytes()).as_ref());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "registerNode",
"params": [node_pubkey.to_string(), sig, msg1.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Failure(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
} else {
assert!(false);
}
} else {
assert!(false);
}
}
#[test]
fn test_rpc_deregister_node() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let sig = Signature::new(&node_keypair.sign(msg.as_bytes()).as_ref());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "deregisterNode",
"params": [node_pubkey.to_string(), sig, msg.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Success(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
} else {
assert!(false);
}
} else {
assert!(false);
}
}
#[test]
fn test_rpc_deregister_node_invalid_sig() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let msg1 = "This is a Test1";
let sig = Signature::new(&node_keypair.sign(msg.as_bytes()).as_ref());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "deregisterNode",
"params": [node_pubkey.to_string(), sig, msg1.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Failure(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
} else {
assert!(false);
}
} else {
assert!(false);
}
}
#[test]
fn test_rpc_sign_vote() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let sig = Signature::new(&node_keypair.sign(msg.as_bytes()).as_ref());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "registerNode",
"params": [node_pubkey.to_string(), sig, msg.as_bytes()],
});
let _res = io.handle_request_sync(&req.to_string(), meta.clone());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "signVote",
"params": [node_pubkey.to_string(), sig, msg.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Success(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
} else {
assert!(false);
}
} else {
assert!(false);
}
}
#[test]
fn test_rpc_sign_vote_before_register() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let sig = Signature::new(&node_keypair.sign(msg.as_bytes()).as_ref());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "signVote",
"params": [node_pubkey.to_string(), sig, msg.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Failure(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
} else {
assert!(false);
}
} else {
assert!(false);
}
}
#[test]
fn test_rpc_sign_vote_after_deregister() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let sig = Signature::new(&node_keypair.sign(msg.as_bytes()).as_ref());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "registerNode",
"params": [node_pubkey.to_string(), sig, msg.as_bytes()],
});
let _res = io.handle_request_sync(&req.to_string(), meta.clone());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "deregisterNode",
"params": [node_pubkey.to_string(), sig, msg.as_bytes()],
});
let _res = io.handle_request_sync(&req.to_string(), meta.clone());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "signVote",
"params": [node_pubkey.to_string(), sig, msg.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Failure(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
} else {
assert!(false);
}
} else {
assert!(false);
}
}
#[test]
fn test_rpc_sign_vote_invalid_sig() {
let (io, meta) = start_rpc_handler();
let node_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let msg = "This is a test";
let msg1 = "This is a Test";
let sig = Signature::new(&node_keypair.sign(msg.as_bytes()).as_ref());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "registerNode",
"params": [node_pubkey.to_string(), sig, msg.as_bytes()],
});
let _res = io.handle_request_sync(&req.to_string(), meta.clone());
let req = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "signVote",
"params": [node_pubkey.to_string(), sig, msg1.as_bytes()],
});
let res = io.handle_request_sync(&req.to_string(), meta);
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
if let Response::Single(out) = result {
if let Output::Failure(succ) = out {
assert_eq!(succ.jsonrpc.unwrap(), Version::V2);
assert_eq!(succ.id, Id::Num(1));
} else {
assert!(false);
}
} else {
assert!(false);
}
}
}