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

Add signature verification endpoints #209

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 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: 1 addition & 0 deletions src/did/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ log = { workspace = true }
num = { workspace = true }
rlp = { workspace = true }
serde = { workspace = true }
serde_bytes = { workspace = true }
serde_json = { workspace = true }
serde_with = { workspace = true }
sha2 = { workspace = true }
Expand Down
20 changes: 20 additions & 0 deletions src/did/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::borrow::Cow;

use candid::{CandidType, Deserialize};
use ethers_core::types::SignatureError;
use jsonrpc_core::{Error, ErrorCode};
use rlp::DecoderError;
use serde::Serialize;
Expand Down Expand Up @@ -244,3 +245,22 @@ pub enum SignatureVerificationError {
#[error("unauthorized principal")]
Unauthorized,
}

impl From<SignatureError> for SignatureVerificationError {
fn from(value: SignatureError) -> Self {
match value {
SignatureError::InvalidLength(len) => {
Self::InternalError(format!("invalid length: {len}"))
}
SignatureError::DecodingError(e) => Self::InternalError(format!("decoding error: {e}")),
SignatureError::VerificationError(expected, recovered) => Self::RecoveryError {
expected: expected.into(),
recovered: recovered.into(),
},
SignatureError::K256Error(e) => Self::InternalError(format!("K256 error: {e}")),
SignatureError::RecoveryError => {
Self::InternalError("internal signature recovery error".into())
}
}
}
}
147 changes: 147 additions & 0 deletions src/did/src/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use std::borrow::Cow;
use std::collections::HashMap;

use candid::CandidType;
use jsonrpc_core::{Error, Failure, Id, Version};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;

// A HTTP response.
#[derive(Clone, Debug, CandidType, Deserialize)]
pub struct HttpResponse {
ufoscout marked this conversation as resolved.
Show resolved Hide resolved
/// The HTTP status code.
pub status_code: u16,
/// The response header map.
pub headers: HashMap<Cow<'static, str>, Cow<'static, str>>,
/// The response body.
pub body: ByteBuf,
/// Whether the query call should be upgraded to an update call.
pub upgrade: Option<bool>,
}

impl HttpResponse {
pub fn new(
status_code: u16,
headers: HashMap<Cow<'static, str>, Cow<'static, str>>,
body: ByteBuf,
upgrade: Option<bool>,
) -> Self {
Self {
status_code,
headers,
body,
upgrade,
}
}

pub fn new_failure(
jsonrpc: Option<Version>,
id: Id,
error: Error,
status_code: HttpStatusCode,
) -> Self {
let failure = Failure { jsonrpc, error, id };
let body = match serde_json::to_vec(&failure) {
Ok(bytes) => ByteBuf::from(&bytes[..]),
Err(e) => ByteBuf::from(e.to_string().as_bytes()),
};

Self::new(
status_code as u16,
HashMap::from([("content-type".into(), "application/json".into())]),
body,
None,
)
}

/// Returns a new `HttpResponse` intended to be used for internal errors.
pub fn internal_error(e: String) -> Self {
let body = match serde_json::to_vec(&e) {
Ok(bytes) => ByteBuf::from(&bytes[..]),
Err(e) => ByteBuf::from(e.to_string().as_bytes()),
};

Self {
status_code: 500,
headers: HashMap::from([("content-type".into(), "application/json".into())]),
body,
upgrade: None,
}
}

/// Returns an OK response with the given body.
pub fn ok(body: ByteBuf) -> Self {
Self::new(
HttpStatusCode::Ok as u16,
HashMap::from([("content-type".into(), "application/json".into())]),
body,
None,
)
}

/// Upgrade response to update call.
pub fn upgrade_response() -> Self {
Self::new(204, HashMap::default(), ByteBuf::default(), Some(true))
}
}

/// The important components of an HTTP request.
#[derive(Clone, Debug, CandidType, Deserialize)]
pub struct HttpRequest {
/// The HTTP method string.
pub method: Cow<'static, str>,
/// The URL that was visited.
pub url: Cow<'static, str>,
/// The request headers.
pub headers: HashMap<Cow<'static, str>, Cow<'static, str>>,
/// The request body.
pub body: ByteBuf,
}

impl HttpRequest {
pub fn new<T: ?Sized + Serialize>(data: &T) -> Self {
let mut headers = HashMap::new();
headers.insert("content-type".into(), "application/json".into());
Self {
method: "POST".into(),
url: "".into(),
headers,
body: ByteBuf::from(serde_json::to_vec(&data).unwrap()),
}
}

pub fn decode_body<T>(&self) -> Result<T, Box<HttpResponse>>
where
T: DeserializeOwned,
{
serde_json::from_slice::<T>(&self.body).map_err(|_| {
Box::new(HttpResponse::new_failure(
Some(Version::V2),
Id::Null,
Error::parse_error(),
HttpStatusCode::BadRequest,
))
})
}

/// Returns an header value by matching the key with a case-insensitive comparison.
/// As IC HTTP headers are lowercased, this method cost is usually O(1) for matching lowercase inputs, and O(n) in any other case.
pub fn get_header_ignore_case(&self, header_name: &str) -> Option<&Cow<'static, str>> {
match self.headers.get(header_name) {
Some(ip) => Some(ip),
None => self
.headers
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(header_name))
.map(|(_, v)| v),
}
}
}

#[repr(u16)]
pub enum HttpStatusCode {
Ok = 200,
BadRequest = 400,
InternalServerError = 500,
}
12 changes: 10 additions & 2 deletions src/did/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::time::Duration;

use candid::{CandidType, Nat, Principal};
use ic_log::LogSettings;
use serde::Deserialize;
use serde::{Deserialize, Serialize};

use crate::permission::Permission;
use crate::{H160, U256};
Expand Down Expand Up @@ -47,4 +47,12 @@ impl Default for EvmCanisterInitData {
}

/// These are the arguments which are taken by the signature verification canister init fn
pub type SignatureVerificationCanisterInitData = Vec<Principal>;
#[derive(Debug, Clone, Serialize, CandidType, Deserialize)]
pub struct SignatureVerificationCanisterInitData {
/// Access list of principals that are allowed to send transactions to the EVM canisters
pub access_list: Vec<Principal>,
/// EVM canisters that receive transactions from the signature verification canister
pub evm_canisters: Vec<Principal>,
ufoscout marked this conversation as resolved.
Show resolved Hide resolved
/// Interval for pushing transactions to the EVM canisters
pub pushing_timer_interval: Duration,
}
1 change: 1 addition & 0 deletions src/did/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub mod state;
pub mod transaction;

pub mod fees;
pub mod http;
#[cfg(test)]
mod test_utils;

Expand Down
3 changes: 3 additions & 0 deletions src/did/src/permission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pub enum Permission {
UpdateLogsConfiguration,
/// Allows caller to reset the EVM state
ResetEvmState,
/// Allows the signature verification canister to send transaction to
/// the EVM Canister
PrivilegedSendTransaction,
}

#[derive(Debug, Clone, Default, CandidType, Deserialize, PartialEq, Eq, serde::Serialize)]
Expand Down
45 changes: 2 additions & 43 deletions src/ethereum-json-rpc-client/src/canister_client.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;

use anyhow::Context;
use candid::{CandidType, Deserialize};
use did::http::{HttpRequest, HttpResponse};
use ic_canister_client::CanisterClient;
use jsonrpc_core::{Call, Request, Response};
use serde::Serialize;
use serde_bytes::ByteBuf;

use crate::{Client, ETH_SEND_RAW_TRANSACTION_METHOD};

Expand All @@ -33,7 +30,7 @@ impl<T: CanisterClient + Sync + 'static> Client for T {
_ => false,
};

let args = HttpRequest::new(&request)?;
let args = HttpRequest::new(&request);

let http_response: Result<HttpResponse, _> = if is_update_call {
client.update("http_request_update", (args,)).await
Expand All @@ -60,44 +57,6 @@ impl<T: CanisterClient + Sync + 'static> Client for T {
}
}

/// The important components of an HTTP request.
#[derive(Clone, Debug, CandidType)]
struct HttpRequest {
/// The HTTP method string.
pub method: &'static str,
/// The URL method string.
pub url: &'static str,
/// The request headers.
pub headers: HashMap<&'static str, &'static str>,
/// The request body.
pub body: ByteBuf,
}

impl HttpRequest {
pub fn new<T: ?Sized + Serialize>(data: &T) -> anyhow::Result<Self> {
let mut headers = HashMap::new();
headers.insert("content-type", "application/json");
Ok(Self {
method: "POST",
headers,
url: "",
body: ByteBuf::from(
serde_json::to_vec(data).context("failed to serialize RPC request")?,
),
})
}
}

#[derive(Clone, Debug, CandidType, Deserialize)]
pub struct HttpResponse {
/// The HTTP status code.
pub status_code: u16,
/// The response header map.
pub headers: HashMap<String, String>,
/// The response body.
pub body: ByteBuf,
}

#[inline]
fn is_update_call(method: &str) -> bool {
method.eq(ETH_SEND_RAW_TRANSACTION_METHOD)
Expand Down
10 changes: 10 additions & 0 deletions src/evm-canister-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -844,4 +844,14 @@ impl<C: CanisterClient> EvmCanisterClient<C> {
) -> CanisterClientResult<Result<()>> {
self.client.update("revert_to_block", (args,)).await
}

/// Receive a batch of Transactions and append them to the blockchain.
pub async fn receive_transactions(
&self,
transactions: Vec<Transaction>,
) -> CanisterClientResult<Result<()>> {
self.client
.update("receive_transactions", (transactions,))
.await
}
}
43 changes: 40 additions & 3 deletions src/signature-verification-canister-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@ impl<C: CanisterClient> SignatureVerificationCanisterClient<C> {
&self,
principal: Principal,
) -> CanisterClientResult<SignatureVerificationResult<()>> {
self.client.update("add_access", (principal,)).await
self.client.update("admin_add_access", (principal,)).await
}

/// Remove principal from the access control list
pub async fn remove_principal_from_access_list(
&self,
principal: Principal,
) -> CanisterClientResult<SignatureVerificationResult<()>> {
self.client.update("remove_access", (principal,)).await
self.client
.update("admin_remove_access", (principal,))
.await
}

/// Get the owner of the canister
Expand All @@ -60,7 +62,7 @@ impl<C: CanisterClient> SignatureVerificationCanisterClient<C> {
&self,
principal: Principal,
) -> CanisterClientResult<SignatureVerificationResult<()>> {
self.client.update("set_owner", (principal,)).await
self.client.update("admin_set_owner", (principal,)).await
}

/// Get the access control list
Expand All @@ -72,4 +74,39 @@ impl<C: CanisterClient> SignatureVerificationCanisterClient<C> {
pub async fn get_canister_build_data(&self) -> CanisterClientResult<BuildData> {
self.client.query("get_canister_build_data", ()).await
}

/// Add evm canister to the access control list
pub async fn add_evm_canister_to_access_list(
&self,
principal: Principal,
) -> CanisterClientResult<SignatureVerificationResult<()>> {
self.client
.update("admin_add_evm_canister", (principal,))
.await
}

/// Remove evm canister from the access control list
pub async fn remove_evm_canister_from_access_list(
&self,
principal: Principal,
) -> CanisterClientResult<SignatureVerificationResult<()>> {
self.client
.update("admin_remove_evm_canister", (principal,))
.await
}

/// Interval for pushing transactions to the evm canisters
pub async fn get_pushing_timer_interval(&self) -> CanisterClientResult<u64> {
self.client.query("pushing_timer_interval", ()).await
}

/// Set the interval for pushing transactions to the evm canisters
pub async fn set_pushing_timer_interval(
&self,
interval: u64,
) -> CanisterClientResult<SignatureVerificationResult<()>> {
self.client
.update("admin_set_pushing_timer_interval", (interval,))
.await
}
}
Loading