Skip to content

Commit

Permalink
feat: make cacao verification a feature (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
heilhead authored Mar 27, 2023
1 parent b454c5b commit d27ecc2
Show file tree
Hide file tree
Showing 9 changed files with 47 additions and 45 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ jobs:
rust: nightly
- name: "Tests"
cmd: nextest
args: run --workspace --retries 3
args: run --workspace --all-features --features cacao --retries 3
rust: stable
- name: "Documentation Tests"
cmd: test
args: --workspace --doc
args: --workspace --doc --all-features
rust: stable
env:
RUST_BACKTRACE: full
Expand Down
8 changes: 5 additions & 3 deletions relay_rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name = "relay_rpc"
version = "0.1.0"
edition = "2021"

[features]
cacao = ["dep:k256", "dep:sha3"]

[dependencies]
bs58 = "0.4"
data-encoding = "2.3"
Expand All @@ -17,6 +20,5 @@ chrono = { version = "0.4", default-features = false, features = ["std", "clock"
regex = "1.7"
once_cell = "1.16"
jsonwebtoken = "8.1"
k256 = "0.13.0"
sha3 = "0.10.6"
hex = "0.4.3"
k256 = { version = "0.13", optional = true }
sha3 = { version = "0.10", optional = true }
1 change: 1 addition & 0 deletions relay_rpc/src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#[cfg(test)]
mod tests;

#[cfg(feature = "cacao")]
pub mod cacao;
pub mod did;

Expand Down
38 changes: 22 additions & 16 deletions relay_rpc/src/auth/cacao/mod.rs → relay_rpc/src/auth/cacao.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ use {
self::{header::Header, payload::Payload, signature::Signature},
core::fmt::Debug,
serde::{Deserialize, Serialize},
std::fmt::{Display, Write as _},
thiserror::Error as ThisError,
std::fmt::{Display, Write},
};

pub mod header;
pub mod payload;
pub mod signature;

/// Errors that can occur during JWT verification
#[derive(Debug, ThisError)]
/// Errors that can occur during Cacao verification.
#[derive(Debug, thiserror::Error)]
pub enum CacaoError {
#[error("Invalid header")]
Header,
Expand All @@ -29,6 +28,12 @@ pub enum CacaoError {
Verification,
}

impl From<std::fmt::Error> for CacaoError {
fn from(_: std::fmt::Error) -> Self {
CacaoError::PayloadResources
}
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Version {
V1 = 1,
Expand Down Expand Up @@ -73,8 +78,8 @@ impl Cacao {
const ETHEREUM: &'static str = "Ethereum";

pub fn verify(&self) -> Result<bool, CacaoError> {
self.p.is_valid()?;
self.h.is_valid()?;
self.p.validate()?;
self.h.validate()?;
self.s.verify(self)
}

Expand All @@ -91,37 +96,38 @@ impl Cacao {
);

if let Some(statement) = &self.p.statement {
let _ = write!(message, "\n{}\n", statement);
write!(message, "\n{}\n", statement)?;
}

let _ = write!(
write!(
message,
"\nURI: {}\nVersion: {}\nChain ID: {}\nNonce: {}\nIssued At: {}",
self.p.aud,
self.p.version,
self.p.chain_id()?,
self.p.nonce,
self.p.iat
);
)?;

if let Some(exp) = &self.p.exp {
let _ = write!(message, "\nExpiration Time: {}", exp);
write!(message, "\nExpiration Time: {}", exp)?;
}

if let Some(nbf) = &self.p.nbf {
let _ = write!(message, "\nNot Before: {}", nbf);
write!(message, "\nNot Before: {}", nbf)?;
}

if let Some(request_id) = &self.p.request_id {
let _ = write!(message, "\nRequest ID: {}", request_id);
write!(message, "\nRequest ID: {}", request_id)?;
}

if let Some(resources) = &self.p.resources {
if !resources.is_empty() {
let _ = write!(message, "\nResources:");
resources.iter().for_each(|resource| {
let _ = write!(message, "\n- {}", resource);
});
write!(message, "\nResources:")?;

for resource in resources {
write!(message, "\n- {}", resource)?;
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion relay_rpc/src/auth/cacao/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub struct Header {
}

impl Header {
pub fn is_valid(&self) -> Result<(), CacaoError> {
pub fn validate(&self) -> Result<(), CacaoError> {
match self.t.as_str() {
"eip4361" => Ok(()),
_ => Err(CacaoError::Header),
Expand Down
4 changes: 2 additions & 2 deletions relay_rpc/src/auth/cacao/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ impl Payload {
const ISS_POSITION_OF_REFERENCE: usize = 3;

/// TODO: write valdation
pub fn is_valid(&self) -> Result<bool, CacaoError> {
Ok(true)
pub fn validate(&self) -> Result<(), CacaoError> {
Ok(())
}

pub fn address(&self) -> Result<String, CacaoError> {
Expand Down
19 changes: 8 additions & 11 deletions relay_rpc/src/auth/cacao/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,16 @@ impl Eip191 {
sha3::{Digest, Keccak256},
};

let signature_bytes = hex::decode(guarantee_no_hex_prefix(signature))
let signature_bytes = data_encoding::HEXLOWER_PERMISSIVE
.decode(strip_hex_prefix(signature).as_bytes())
.map_err(|_| CacaoError::Verification)?;

let sig = Sig::try_from(&signature_bytes[..64]).map_err(|_| CacaoError::Verification)?;
let recovery_id = RecoveryId::try_from(&signature_bytes[64] % 27)
.map_err(|_| CacaoError::Verification)?;

let recovered_key = VerifyingKey::recover_from_digest(
Keccak256::new_with_prefix(&self.eip191_bytes(message)),
Keccak256::new_with_prefix(self.eip191_bytes(message)),
&sig,
recovery_id,
)
Expand All @@ -55,21 +56,17 @@ impl Eip191 {
.chain_update(&recovered_key.to_encoded_point(false).as_bytes()[1..])
.finalize()[12..];

let address_encoded = hex::encode(add);
let address_encoded = data_encoding::HEXLOWER_PERMISSIVE.encode(add);

if address_encoded.to_lowercase() != guarantee_no_hex_prefix(address).to_lowercase() {
if address_encoded.to_lowercase() != strip_hex_prefix(address).to_lowercase() {
Err(CacaoError::Verification)
} else {
Ok(true)
}
}
}

/// Remove the 0x prefix from a hex string
fn guarantee_no_hex_prefix(s: &str) -> &str {
if let Some(stripped) = s.strip_prefix("0x") {
stripped
} else {
s
}
/// Remove the "0x" prefix from a hex string.
fn strip_hex_prefix(s: &str) -> &str {
s.strip_prefix("0x").unwrap_or(s)
}
4 changes: 2 additions & 2 deletions relay_rpc/src/auth/cacao/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::auth::cacao::Cacao;

/// Test that we can verify a Cacao
#[test]
fn cacao_verify_success() {
fn verify_success() {
let cacao_serialized = r#"{
"h": {
"t": "eip4361"
Expand Down Expand Up @@ -34,7 +34,7 @@ fn cacao_verify_success() {

/// Test that we can verify a Cacao with uppercase address
#[test]
fn cacao_without_lowercase_address_verify_success() {
fn without_lowercase_address_verify_success() {
let cacao_serialized = r#"{"h":{"t":"eip4361"},"p":{"iss":"did:pkh:eip155:1:0xbD4D1935165012e7D29919dB8717A5e670a1a5b1","domain":"https://staging.keys.walletconnect.com","aud":"https://staging.keys.walletconnect.com","version":"1","nonce":"07487c09be5535dcbc341d8e35e5c9b4d3539a802089c42c5b1172dd9ed63c64","iat":"2023-01-25T15:08:36.846Z","statement":"Test","resources":["did:key:451cf9b97c64fcca05fbb0d4c40b886c94133653df5a2b6bd97bd29a0bbcdb37"]},"s":{"t":"eip191","s":"0x8496ad1dd1ddd5cb78ac26b62a6bd1c6cfff703ea3b11a9da29cfca112357ace75cac8ee28d114f9e166a6935ee9ed83151819a9e0ee738a0547116b1d978e351b"}}"#;
let cacao: Cacao = serde_json::from_str(cacao_serialized).unwrap();
let result = cacao.verify();
Expand Down
12 changes: 4 additions & 8 deletions relay_rpc/src/auth/did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ pub const DID_PREFIX: &str = "did";
pub const DID_METHOD_KEY: &str = "key";
pub const DID_METHOD_PKH: &str = "pkh";

use thiserror::Error as ThisError;

#[derive(Debug, ThisError)]
#[derive(Debug, thiserror::Error)]
pub enum DidError {
#[error("Invalid issuer DID prefix")]
Prefix,
Expand All @@ -17,15 +15,13 @@ pub enum DidError {
Format,
}

pub fn extract_did_data<'a>(did: &'a str, method: &'a str) -> Result<&'a str, DidError> {
let data = did
.strip_prefix(DID_PREFIX)
pub fn extract_did_data<'a>(did: &'a str, method: &str) -> Result<&'a str, DidError> {
did.strip_prefix(DID_PREFIX)
.ok_or(DidError::Prefix)?
.strip_prefix(DID_DELIMITER)
.ok_or(DidError::Format)?
.strip_prefix(method)
.ok_or(DidError::Method)?
.strip_prefix(DID_DELIMITER)
.ok_or(DidError::Format)?;
Ok(data)
.ok_or(DidError::Format)
}

0 comments on commit d27ecc2

Please sign in to comment.