diff --git a/API.md b/API.md index 548a3476..08fc8272 100644 --- a/API.md +++ b/API.md @@ -7,8 +7,7 @@ The update operation causes either the hashchain for an existing entry to be upd ```bash curl -X POST http://localhost:8080/update-entry \ -H "Content-Type: application/json" \ - -d '{ "id": "YOUR_ID", \ - "public_key": "YOUR_PUBLIC_KEY", \ + -d '{ "public_key": "YOUR_PUBLIC_KEY", \ "signed_message": "YOUR_SIGNED_MESSAGE"}' ``` diff --git a/src/node_types/mod.rs b/src/node_types/mod.rs index 245bd44d..1a833550 100644 --- a/src/node_types/mod.rs +++ b/src/node_types/mod.rs @@ -13,7 +13,7 @@ pub trait NodeType { #[cfg(test)] mod tests { - use crate::{storage::UpdateEntryJson, utils::verify_signature}; + use crate::{utils::verify_signature, webserver::UpdateEntryJson}; use base64::{engine::general_purpose, Engine as _}; fn setup_signature(valid_signature: bool) -> UpdateEntryJson { @@ -25,8 +25,7 @@ mod tests { let id_public_key = "CosRXOoSLG7a8sCGx78KhtfLEuiyNY7L4ksFt78mp2M=".to_string(); UpdateEntryJson { - id: id_public_key.clone(), - signed_message, + signed_incoming_entry: signed_message, public_key: id_public_key, } } @@ -61,7 +60,7 @@ mod tests { let short_message = general_purpose::STANDARD.encode("this is a short message"); let signature_with_key = UpdateEntryJson { - signed_message: short_message, + signed_incoming_entry: short_message, ..signature_with_key }; diff --git a/src/node_types/sequencer.rs b/src/node_types/sequencer.rs index f82389e0..8ac2e3bd 100644 --- a/src/node_types/sequencer.rs +++ b/src/node_types/sequencer.rs @@ -20,8 +20,9 @@ use crate::{ da::{DataAvailabilityLayer, EpochJson}, error::{DeimosError, GeneralError}, node_types::NodeType, - storage::{ChainEntry, Database, IncomingEntry, Operation, UpdateEntryJson}, + storage::{ChainEntry, Database, IncomingEntry, Operation}, utils::verify_signature, + webserver::UpdateEntryJson, webserver::WebServer, zk_snark::BatchMerkleProofCircuit, }; @@ -359,25 +360,20 @@ impl Sequencer { /// /// # Arguments /// - /// * `operation` - An `Operation` enum variant representing the type of operation to be performed (Add or Revoke). - /// * `incoming_entry` - A reference to an `IncomingEntry` struct containing the key and the entry data to be updated. - /// * `signature` - A `Signature` struct representing the signature. - pub fn update_entry(&self, signature: &UpdateEntryJson) -> DeimosResult<()> { - debug!( - "updating entry for uid {} with msg {}", - signature.id, signature.signed_message - ); - let signed_content = match verify_signature(signature, Some(signature.public_key.clone())) { - Ok(content) => content, - Err(_) => { - // TODO(@distractedm1nd): Add to error instead of logging - error!( - "updating entry for uid {}: invalid signature with pubkey {} on msg {}", - signature.id, signature.public_key, signature.signed_message - ); - return Err(GeneralError::InvalidSignature.into()); - } - }; + /// * `signed_entry` - A `UpdateEntryJson` object. + pub fn update_entry(&self, signed_entry: &UpdateEntryJson) -> DeimosResult<()> { + let signed_content = + match verify_signature(signed_entry, Some(signed_entry.public_key.clone())) { + Ok(content) => content, + Err(_) => { + // TODO(@distractedm1nd): Add to error instead of logging + error!( + "updating entry: invalid signature with pubkey {} on msg {}", + signed_entry.public_key, signed_entry.signed_incoming_entry + ); + return Err(GeneralError::InvalidSignature.into()); + } + }; let message_obj: IncomingEntry = match serde_json::from_str(&signed_content) { Ok(obj) => obj, @@ -386,14 +382,16 @@ impl Sequencer { } }; + let id = message_obj.id.clone(); + // check with given key if the signature is valid let incoming_entry = IncomingEntry { - id: signature.id.clone(), + id: id.clone(), operation: message_obj.operation, value: message_obj.value, }; // add a new key to an existing id ( type for the value retrieved from the database explicitly set to string) - match self.db.get_hashchain(&signature.id) { + match self.db.get_hashchain(&id) { Ok(value) => { // hashchain already exists let mut current_chain = value.clone(); @@ -402,7 +400,7 @@ impl Sequencer { None => { return Err(DatabaseError::NotFoundError(format!( "last value in hashchain for incoming entry with id {}", - signature.id.clone() + id.clone() )) .into()); } @@ -469,7 +467,7 @@ impl Sequencer { None => { return Err(DatabaseError::ReadError(format!( "last value in hashchain for incoming entry with id {}", - signature.id.clone() + id.clone() )) .into()); } diff --git a/src/storage.rs b/src/storage.rs index 1934c2c3..e15d1d00 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,5 +1,3 @@ -use base64::engine::{general_purpose, Engine as _}; -use ed25519::Signature; use indexed_merkle_tree::{node::Node, sha256_mod, tree::Proof}; use mockall::{predicate::*, *}; use redis::{Client, Commands, Connection}; @@ -16,7 +14,7 @@ use std::{ use crate::{ cfg::RedisConfig, error::{DatabaseError, DeimosError, DeimosResult, GeneralError}, - utils::{parse_json_to_proof, Signable}, + utils::parse_json_to_proof, }; #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] @@ -61,68 +59,6 @@ pub struct IncomingEntry { pub value: String, } -#[derive(Deserialize, Debug)] -pub struct UpdateEntryJson { - pub id: String, - pub signed_message: String, - pub public_key: String, -} - -fn decode_signed_message(signed_message: &String) -> DeimosResult> { - let signed_message_bytes = general_purpose::STANDARD - .decode(signed_message) - .map_err(|e| { - DeimosError::General(GeneralError::DecodingError(format!( - "signed message: {}", - e - ))) - })?; - - // check if the signed message is (at least) 64 bytes long - if signed_message_bytes.len() < 64 { - Err(GeneralError::ParsingError(format!( - "signed message is too short: {} < 64", - signed_message_bytes.len(), - )) - .into()) - } else { - Ok(signed_message_bytes) - } -} - -impl Signable for UpdateEntryJson { - fn get_signature(&self) -> DeimosResult { - let signed_message_bytes = decode_signed_message(&self.signed_message)?; - - // extract the first 64 bytes from the signed message which are the signature - let signature_bytes: &[u8; 64] = match signed_message_bytes.get(..64) { - Some(array_section) => match array_section.try_into() { - Ok(array) => array, - Err(e) => Err(DeimosError::General(GeneralError::DecodingError(format!( - "signed message to array: {}", - e - ))))?, - }, - None => Err(DeimosError::General(GeneralError::DecodingError(format!( - "extracting signature from signed message: {}", - &self.signed_message - ))))?, - }; - - Ok(Signature::from_bytes(signature_bytes)) - } - - fn get_content_to_sign(&self) -> DeimosResult { - let signed_message_bytes = decode_signed_message(&self.signed_message)?; - let message_bytes = &signed_message_bytes[64..]; - Ok(String::from_utf8_lossy(message_bytes).to_string()) - } - - fn get_public_key(&self) -> DeimosResult { - Ok(self.public_key.clone()) - } -} - pub struct RedisConnections { pub main_dict: Mutex, // clear text key with hashchain pub derived_dict: Mutex, // hashed key with last hashchain entry hash diff --git a/src/utils.rs b/src/utils.rs index 1bd6c641..f5538e80 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -115,6 +115,26 @@ pub trait Signable { fn get_public_key(&self) -> DeimosResult; } +pub fn decode_signed_message(signed_message: &String) -> DeimosResult> { + let signed_message_bytes = engine.decode(signed_message).map_err(|e| { + DeimosError::General(GeneralError::DecodingError(format!( + "signed message: {}", + e + ))) + })?; + + // check if the signed message is (at least) 64 bytes long + if signed_message_bytes.len() < 64 { + Err(GeneralError::ParsingError(format!( + "signed message is too short: {} < 64", + signed_message_bytes.len(), + )) + .into()) + } else { + Ok(signed_message_bytes) + } +} + // verifies the signature of a given signable item and returns the content of the item if the signature is valid pub fn verify_signature( item: &T, diff --git a/src/webserver.rs b/src/webserver.rs index 60955e70..024b6fa0 100644 --- a/src/webserver.rs +++ b/src/webserver.rs @@ -1,3 +1,10 @@ +use crate::{ + cfg::WebServerConfig, + error::{DeimosError, DeimosResult, GeneralError}, + node_types::sequencer::Sequencer, + storage::{ChainEntry, IncomingEntry}, + utils::{decode_signed_message, is_not_revoked, Signable}, +}; use actix_cors::Cors; use actix_web::{ dev::Server, @@ -5,20 +12,12 @@ use actix_web::{ web::{self, Data}, App as ActixApp, HttpResponse, HttpServer, Responder, }; +use ed25519::Signature; use indexed_merkle_tree::{sha256_mod, tree::Proof}; use serde::{Deserialize, Serialize}; use serde_json::{self, json, Value}; - use std::sync::Arc; -use crate::{ - cfg::WebServerConfig, - error::DeimosResult, - node_types::sequencer::Sequencer, - storage::{ChainEntry, UpdateEntryJson}, - utils::is_not_revoked, -}; - pub struct WebServer { pub cfg: WebServerConfig, } @@ -31,6 +30,45 @@ pub struct EpochData { proofs: Vec, } +#[derive(Deserialize, Debug)] +pub struct UpdateEntryJson { + pub signed_incoming_entry: String, + pub public_key: String, +} + +impl Signable for UpdateEntryJson { + fn get_signature(&self) -> DeimosResult { + let signed_message_bytes = decode_signed_message(&self.signed_incoming_entry)?; + + // extract the first 64 bytes from the signed message which are the signature + let signature_bytes: &[u8; 64] = match signed_message_bytes.get(..64) { + Some(array_section) => match array_section.try_into() { + Ok(array) => array, + Err(e) => Err(DeimosError::General(GeneralError::DecodingError(format!( + "signed message to array: {}", + e + ))))?, + }, + None => Err(DeimosError::General(GeneralError::DecodingError(format!( + "extracting signature from signed message: {}", + &self.signed_incoming_entry + ))))?, + }; + + Ok(Signature::from_bytes(signature_bytes)) + } + + fn get_content_to_sign(&self) -> DeimosResult { + let signed_message_bytes = decode_signed_message(&self.signed_incoming_entry)?; + let message_bytes = &signed_message_bytes[64..]; + Ok(String::from_utf8_lossy(message_bytes).to_string()) + } + + fn get_public_key(&self) -> DeimosResult { + Ok(self.public_key.clone()) + } +} + impl WebServer { pub fn new(cfg: WebServerConfig) -> Self { WebServer { cfg } @@ -69,9 +107,7 @@ impl WebServer { /// /// * `req_body` - A JSON string containing the information needed to update or insert an entry in the dictionary. /// The JSON string should have the following fields: -/// - `operation`: An `Operation` enum indicating whether the operation is an add or revoke operation. -/// - `incoming_entry`: An `IncomingEntry` object containing the id and the public key. -/// - `private_key`: A string representing the private key used to sign the incoming entry. (TODO! bessere Lösung finden) +/// - `signed_message`: An `UpdateEntryJson` object containing the id, operation, and value, signed by the public key. /// /// # Returns /// @@ -91,6 +127,23 @@ async fn update_entry( } }; + let incoming_entry_json = match signature_with_key.get_content_to_sign() { + Ok(entry) => entry, + Err(e) => { + return HttpResponse::BadRequest().json(format!( + "Error retrieving content from UpdateEntryJson: {}", + e + )) + } + }; + + let incoming_entry: IncomingEntry = match serde_json::from_str(&incoming_entry_json) { + Ok(entry) => entry, + Err(e) => { + return HttpResponse::BadRequest().json(format!("Error decoding signed content: {}", e)) + } + }; + let epoch = match session.db.get_epoch() { Ok(e) => e, Err(e) => { @@ -113,7 +166,7 @@ async fn update_entry( } }; - let result: DeimosResult> = session.db.get_hashchain(&signature_with_key.id); + let result: DeimosResult> = session.db.get_hashchain(&incoming_entry.id); let update_proof = result.is_ok(); match session.update_entry(&signature_with_key) { @@ -125,7 +178,7 @@ async fn update_entry( .json(format!("Error creating new tree: {}", e)) } }; - let hashed_id = sha256_mod(&signature_with_key.id); + let hashed_id = sha256_mod(&incoming_entry.id); let mut node = match new_tree.find_leaf_by_label(&hashed_id) { Some(n) => n, None => return HttpResponse::InternalServerError().json("Error finding leaf"),