Skip to content

Commit

Permalink
Add back some helpers used by xmtp-rust-swift, expose via ffi (#229)
Browse files Browse the repository at this point in the history
  • Loading branch information
nakajima authored Sep 27, 2023
1 parent b5cd33e commit 1f36206
Show file tree
Hide file tree
Showing 13 changed files with 819 additions and 10 deletions.
7 changes: 5 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"rust-analyzer.linkedProjects": ["bindings_ffi/Cargo.toml", "examples/cli/Cargo.toml"]
}
"rust-analyzer.linkedProjects": [
"bindings_ffi/Cargo.toml",
"examples/cli/Cargo.toml"
]
}
81 changes: 81 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 5 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,17 @@ members = [
"xmtp",
"xmtp_cryptography",
"xmtp_networking",
# TODO: replace ^ with:
# "xmtp_api_grpc",
# "xmtp_api_grpc_gateway",
# TODO: replace ^ with:
# "xmtp_api_grpc",
# "xmtp_api_grpc_gateway",
"xmtp_proto",
"xmtp_v2",
]

# Exclude since
# 1) no reason to share profile with other core crates
# 2) moreover, bindings_swift and xmtp_dh need their own size-optimized profile
exclude = [
"bindings_ffi",
"bindings_js",
"bindings_wasm",
]
exclude = ["bindings_ffi", "bindings_js", "bindings_wasm"]

# Make the feature resolver explicit.
# See https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html#details
Expand Down
1 change: 1 addition & 0 deletions bindings_ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs", rev = "cae8edc45
xmtp = { path = "../xmtp", features = ["grpc", "native"] }
xmtp_cryptography = { path = "../xmtp_cryptography" }
xmtp_networking = { path = "../xmtp_networking" }
xmtp_v2 = { path = "../xmtp_v2" }

[build_dependencies]
uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "cae8edc45ba5b56bfcbf35b60c1ab6a97d1bf9da", features = [
Expand Down
1 change: 1 addition & 0 deletions bindings_ffi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod inbox_owner;
pub mod logger;
mod v2;

use std::convert::TryInto;

Expand Down
61 changes: 61 additions & 0 deletions bindings_ffi/src/v2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate::{stringify_error_chain, GenericError};

#[uniffi::export]
pub fn recover_address(
signature_bytes: Vec<u8>,
predigest_message: String,
) -> Result<String, GenericError> {
let signature =
xmtp_cryptography::signature::RecoverableSignature::Eip191Signature(signature_bytes);
let recovered = signature
.recover_address(&predigest_message)
.map_err(|e| stringify_error_chain(&e))?;

return Ok(recovered);
}

#[uniffi::export]
pub fn diffie_hellman_k256(
private_key_bytes: Vec<u8>,
public_key_bytes: Vec<u8>,
) -> Result<Vec<u8>, GenericError> {
let shared_secret = xmtp_v2::k256_helper::diffie_hellman_byte_params(
private_key_bytes.as_slice(),
public_key_bytes.as_slice(),
)?;

Ok(shared_secret)
}

#[uniffi::export]
pub fn verify_k256_sha256(
signed_by: Vec<u8>,
message: Vec<u8>,
signature: Vec<u8>,
recovery_id: u8,
) -> Result<bool, GenericError> {
let result = xmtp_v2::k256_helper::verify_sha256(
signed_by.as_slice(),
message.as_slice(),
signature.as_slice(),
recovery_id,
)?;

Ok(result)
}

#[cfg(test)]
mod tests {
// Try a query on a test topic, and make sure we get a response
#[tokio::test]
async fn test_recover_public_key_keccak256() {
// This test was generated using Etherscans Signature tool: https://etherscan.io/verifySig/18959
let addr = "0x1B2a516d691aBb8f08a75B2C73c95c62A1632431";
let msg = "TestVector1";
let sig_hash = "19d6bec562518e365d07ba3cce26d08a5fffa2cbb1e7fe03c1f2d6a722fd3a5e544097b91f8f8cd11d43b032659f30529139ab1a9ecb6c81ed4a762179e87db81c";

let sig_bytes = ethers_core::utils::hex::decode(sig_hash).unwrap();
let recovered_addr = crate::v2::recover_address(sig_bytes, msg.to_string()).unwrap();
assert_eq!(recovered_addr, addr.to_lowercase());
}
}
29 changes: 29 additions & 0 deletions xmtp_v2/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "xmtp_v2"
version = "0.1.0"
edition = "2021"
rust-version = "1.64"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.8.4"
hkdf = "0.12.3"
sha2 = "0.10.6"
k256 = { version = "0.12.0", features = ["ecdh"] }
ecdsa = "0.15.1"
sha3 = "0.10.6"
hex = "0.4"
rlp = "0.5.2"
aes-gcm = "0.10.1"
generic-array = "0.14.6"
getrandom = { version = "0.2.8", features = ["js"] }
chrono = "0.4.23"
ethers-core = "2.0.4"
thiserror = "1.0.40"
rand_chacha = "0.3.1"
serde = "1.0.163"

[dev-dependencies]
ethers = "2.0.4"
tokio = { version = "1.28.1", features = ["rt", "macros"] }
92 changes: 92 additions & 0 deletions xmtp_v2/src/encryption.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use hkdf::Hkdf;
use rand::Rng;
use sha2::Sha256;

use generic_array::GenericArray;

use aes_gcm::{
aead::{Aead, KeyInit, Payload},
Aes256Gcm, Nonce,
};

// Lightweight ciphertext holder
pub struct Ciphertext {
pub payload: Vec<u8>,
pub hkdf_salt: Vec<u8>,
pub gcm_nonce: Vec<u8>,
}

pub fn hkdf(secret: &[u8], salt: &[u8]) -> Result<[u8; 32], String> {
let hk = Hkdf::<Sha256>::new(Some(salt), secret);
let mut okm = [0u8; 42];
let res = hk.expand(&[], &mut okm);
if res.is_err() {
return Err(res.err().unwrap().to_string());
}
okm[0..32]
.try_into()
.map_err(|_| "hkdf failed to fit in 32 bytes".to_string())
}

pub fn decrypt(
ciphertext_bytes: &[u8],
salt_bytes: &[u8],
nonce_bytes: &[u8],
secret_bytes: &[u8],
additional_data: Option<&[u8]>,
) -> Result<Vec<u8>, String> {
// Form a Payload struct from ciphertext_bytes and additional_data if it's present
let mut payload = Payload::from(ciphertext_bytes);
if let Some(aad_data) = additional_data {
payload.aad = aad_data;
}
decrypt_raw(payload, salt_bytes, nonce_bytes, secret_bytes)
}

// Decrypt but using associated data
fn decrypt_raw(
payload: Payload,
salt_bytes: &[u8],
nonce_bytes: &[u8],
secret_bytes: &[u8],
) -> Result<Vec<u8>, String> {
let derived_key = hkdf(secret_bytes, salt_bytes)?;
let key = Aes256Gcm::new(GenericArray::from_slice(&derived_key));
let nonce = Nonce::from_slice(nonce_bytes);
let res = key.decrypt(nonce, payload);
if res.is_err() {
return Err(res.err().unwrap().to_string());
}
Ok(res.unwrap())
}

pub fn encrypt(
plaintext_bytes: &[u8],
secret_bytes: &[u8],
additional_data: Option<&[u8]>,
) -> Result<Ciphertext, String> {
// Form a Payload struct from plaintext_bytes and additional_data if it's present
let mut payload = Payload::from(plaintext_bytes);
if let Some(aad_data) = additional_data {
payload.aad = aad_data;
}
encrypt_raw(payload, secret_bytes)
}

fn encrypt_raw(payload: Payload, secret_bytes: &[u8]) -> Result<Ciphertext, String> {
let salt_bytes = rand::thread_rng().gen::<[u8; 32]>();
let nonce_bytes = rand::thread_rng().gen::<[u8; 12]>();
let derived_key = hkdf(secret_bytes, &salt_bytes)?;
let key = Aes256Gcm::new(GenericArray::from_slice(&derived_key));
let nonce = Nonce::from_slice(&nonce_bytes);
let res = key.encrypt(nonce, payload);
if res.is_err() {
return Err(res.err().unwrap().to_string());
}
let ciphertext_bytes = res.unwrap();
Ok(Ciphertext {
payload: ciphertext_bytes,
hkdf_salt: salt_bytes.to_vec(),
gcm_nonce: nonce_bytes.to_vec(),
})
}
21 changes: 21 additions & 0 deletions xmtp_v2/src/hashes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use sha2::Digest;
use sha2::Sha256;
use sha3::Keccak256;

pub fn sha256(data: &[u8]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(data);
let result = hasher.finalize();
let mut digest = [0u8; 32];
digest.copy_from_slice(&result);
digest
}

pub fn keccak256(data: &[u8]) -> [u8; 32] {
let mut hasher = Keccak256::new();
hasher.update(data);
let result = hasher.finalize();
let mut digest = [0u8; 32];
digest.copy_from_slice(&result);
digest
}
Loading

0 comments on commit 1f36206

Please sign in to comment.