Skip to content

Commit

Permalink
Merge pull request #44 from stakwork/crypter-ffi
Browse files Browse the repository at this point in the history
crypter ffi bindings for kotlin
  • Loading branch information
Evanfeenstra authored Jul 6, 2022
2 parents d9b7ddb + aa91284 commit 910e703
Show file tree
Hide file tree
Showing 10 changed files with 645 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ Cargo.lock
sphinx-key/Cargo.lock
notes.md
test-flash
.env
.env
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ members = [

exclude = [
"sphinx-key",
"crypter"
"crypter",
"crypter-ffi"
]

[patch.crates-io]
Expand Down
25 changes: 25 additions & 0 deletions crypter-ffi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "crypter-ffi"
version = "0.1.0"
authors = ["Evan Feenstra <[email protected]>"]
edition = "2018"

[lib]
crate-type = ["cdylib"]
name = "crypter"

[dependencies]
sphinx-key-crypter = { path = "../crypter" }
uniffi = "0.19.2"
hex = "0.4.3"
thiserror = "1.0.31"
uniffi_macros = "0.11.0"

[build-dependencies]
uniffi_build = "0.19.2"

[patch.crates-io]
getrandom = { version = "0.2", git = "https://github.com/esp-rs-compat/getrandom.git" }
secp256k1 = { git = "https://github.com/Evanfeenstra/rust-secp256k1", branch = "v0.22.0-new-rand" }
lightning = { git = "https://github.com/Evanfeenstra/rust-lightning", branch = "v0.0.108-branch" }

3 changes: 3 additions & 0 deletions crypter-ffi/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
uniffi_build::generate_scaffolding("./src/crypter.udl").unwrap();
}
10 changes: 10 additions & 0 deletions crypter-ffi/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
uniffi-bindgen --version
should match the uniffi version in Cargo.toml

uniffi-bindgen generate src/crypter.udl --language kotlin

uniffi-bindgen generate src/crypter.udl --language swift

### manually build the C ffi

uniffi-bindgen scaffolding src/crypter.udl
19 changes: 19 additions & 0 deletions crypter-ffi/src/crypter.udl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[Error]
enum CrypterError {
"DeriveSharedSecret",
"Encrypt",
"Decrypt",
"BadPubkey",
"BadSecret",
"BadNonce",
"BadCiper",
};

namespace crypter {
[Throws=CrypterError]
string derive_shared_secret(string their_pubkey, string my_secret_key);
[Throws=CrypterError]
string encrypt(string plaintext, string secret, string nonce);
[Throws=CrypterError]
string decrypt(string ciphertext, string secret);
};
98 changes: 98 additions & 0 deletions crypter-ffi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
mod parse;

use sphinx_key_crypter::chacha::{decrypt as chacha_decrypt, encrypt as chacha_encrypt};
use sphinx_key_crypter::ecdh::derive_shared_secret_from_slice;

uniffi_macros::include_scaffolding!("crypter");

pub type Result<T> = std::result::Result<T, CrypterError>;

#[derive(Debug, thiserror::Error)]
pub enum CrypterError {
#[error("Failed to derive shared secret")]
DeriveSharedSecret,
#[error("Failed to encrypt")]
Encrypt,
#[error("Failed to decrypt")]
Decrypt,
#[error("Bad pubkey")]
BadPubkey,
#[error("Bad secret")]
BadSecret,
#[error("Bad nonce")]
BadNonce,
#[error("Bad cipher")]
BadCiper,
}

// their_pubkey: 33 bytes
// my_secret_key: 32 bytes
// return shared secret: 32 bytes
pub fn derive_shared_secret(their_pubkey: String, my_secret_key: String) -> Result<String> {
let pubkey = parse::parse_public_key_string(their_pubkey)?;
let secret_key = parse::parse_secret_string(my_secret_key)?;
let secret = match derive_shared_secret_from_slice(pubkey, secret_key) {
Ok(s) => s,
Err(_) => return Err(CrypterError::DeriveSharedSecret),
};
Ok(hex::encode(secret))
}

// plaintext: 32 bytes
// secret: 32 bytes
// nonce: 8 bytes
// return ciphertext: 56 bytes
pub fn encrypt(plaintext: String, secret: String, nonce: String) -> Result<String> {
let plain = parse::parse_secret_string(plaintext)?;
let sec = parse::parse_secret_string(secret)?;
let non = parse::parse_nonce_string(nonce)?;
let cipher = match chacha_encrypt(plain, sec, non) {
Ok(c) => c,
Err(_) => return Err(CrypterError::Encrypt),
};
Ok(hex::encode(cipher))
}

// ciphertext: 56 bytes
// secret: 32 bytes
// return plaintext: 32 bytes
pub fn decrypt(ciphertext: String, secret: String) -> Result<String> {
let cipher = parse::parse_cipher_string(ciphertext)?;
let sec = parse::parse_secret_string(secret)?;
let plain = match chacha_decrypt(cipher, sec) {
Ok(c) => c,
Err(_) => return Err(CrypterError::Decrypt),
};
Ok(hex::encode(plain))
}

#[cfg(test)]
mod tests {
use crate::{decrypt, derive_shared_secret, encrypt, Result};

#[test]
fn test_crypter() -> Result<()> {
let sk1 = "86c8977989592a97beb409bc27fde76e981ce3543499fd61743755b832e92a3e";
let pk1 = "0362a684901b8d065fb034bc44ea972619a409aeafc2a698016a74f6eee1008aca";

let sk2 = "21c2d41c7394b0a87dae89576bee2552aedb54a204cdcdbf5cdceb0b4c1c2a17";
let pk2 = "027dd6297aff570a409fe05032b6e1dab39f309daa8c438a65c32e3d7b4722b7c3";

// derive shared secrets
let sec1 = derive_shared_secret(pk2.to_string(), sk1.to_string())?;
let sec2 = derive_shared_secret(pk1.to_string(), sk2.to_string())?;
assert_eq!(sec1, sec2);

// encrypt plaintext with sec1
let plaintext = "59ff446bec1d96dc7d1a69232cd69ca409e069294e983df7f1e3e5fb3c95c41c";
let nonce = "0da01cc0c0a73ad3";
let cipher = encrypt(plaintext.to_string(), sec1, nonce.to_string())?;

// decrypt with sec2
let plain = decrypt(cipher, sec2)?;
assert_eq!(plaintext, plain);

println!("PLAINTEXT MATCHES!");
Ok(())
}
}
65 changes: 65 additions & 0 deletions crypter-ffi/src/parse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use crate::{Result, CrypterError};

use sphinx_key_crypter::ecdh::PUBLIC_KEY_LEN;
use sphinx_key_crypter::chacha::{NONCE_END_LEN, KEY_LEN, CIPHER_LEN};
use std::convert::TryInto;

pub(crate) fn parse_secret_string(sk: String) -> Result<[u8; KEY_LEN]> {
if sk.len() != KEY_LEN * 2 {
return Err(CrypterError::BadSecret)
}
let secret_key_bytes: Vec<u8> = match hex::decode(sk) {
Ok(sk) => sk,
Err(_) => return Err(CrypterError::BadSecret),
};
let secret_key: [u8; KEY_LEN] = match secret_key_bytes.try_into() {
Ok(sk) => sk,
Err(_) => return Err(CrypterError::BadSecret),
};
Ok(secret_key)
}

pub(crate) fn parse_public_key_string(pk: String) -> Result<[u8; PUBLIC_KEY_LEN]> {
if pk.len() != PUBLIC_KEY_LEN * 2 {
return Err(CrypterError::BadPubkey)
}
let pubkey_bytes: Vec<u8> = match hex::decode(pk) {
Ok(pk) => pk,
Err(_) => return Err(CrypterError::BadPubkey),
};
let pubkey: [u8; PUBLIC_KEY_LEN] = match pubkey_bytes.try_into() {
Ok(pk) => pk,
Err(_) => return Err(CrypterError::BadPubkey),
};
Ok(pubkey)
}

pub(crate) fn parse_nonce_string(n: String) -> Result<[u8; NONCE_END_LEN]> {
if n.len() != NONCE_END_LEN * 2 {
return Err(CrypterError::BadNonce)
}
let nonce_bytes: Vec<u8> = match hex::decode(n) {
Ok(n) => n,
Err(_) => return Err(CrypterError::BadNonce),
};
let nonce: [u8; NONCE_END_LEN] = match nonce_bytes.try_into() {
Ok(n) => n,
Err(_) => return Err(CrypterError::BadNonce),
};
Ok(nonce)
}

pub(crate) fn parse_cipher_string(c: String) -> Result<[u8; CIPHER_LEN]> {
if c.len() != CIPHER_LEN * 2 {
return Err(CrypterError::BadCiper)
}
let cipher_bytes: Vec<u8> = match hex::decode(c) {
Ok(n) => n,
Err(_) => return Err(CrypterError::BadCiper),
};
let cipher: [u8; CIPHER_LEN] = match cipher_bytes.try_into() {
Ok(n) => n,
Err(_) => return Err(CrypterError::BadCiper),
};
Ok(cipher)
}
Loading

0 comments on commit 910e703

Please sign in to comment.