mirror of
https://github.com/stakwork/sphinx-key.git
synced 2025-12-17 07:14:23 +01:00
crypter ffi bindings for kotlin
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,4 +6,4 @@ Cargo.lock
|
|||||||
sphinx-key/Cargo.lock
|
sphinx-key/Cargo.lock
|
||||||
notes.md
|
notes.md
|
||||||
test-flash
|
test-flash
|
||||||
.env
|
.env
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ members = [
|
|||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
"sphinx-key",
|
"sphinx-key",
|
||||||
"crypter"
|
"crypter",
|
||||||
|
"crypter-ffi"
|
||||||
]
|
]
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
|||||||
25
crypter-ffi/Cargo.toml
Normal file
25
crypter-ffi/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "crypter-ffi"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Evan Feenstra <evanfeenstra@gmail.com>"]
|
||||||
|
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
crypter-ffi/build.rs
Normal file
3
crypter-ffi/build.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
uniffi_build::generate_scaffolding("./src/crypter.udl").unwrap();
|
||||||
|
}
|
||||||
10
crypter-ffi/readme.md
Normal file
10
crypter-ffi/readme.md
Normal file
@@ -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
crypter-ffi/src/crypter.udl
Normal file
19
crypter-ffi/src/crypter.udl
Normal file
@@ -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
crypter-ffi/src/lib.rs
Normal file
98
crypter-ffi/src/lib.rs
Normal file
@@ -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
crypter-ffi/src/parse.rs
Normal file
65
crypter-ffi/src/parse.rs
Normal file
@@ -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)
|
||||||
|
}
|
||||||
419
crypter-ffi/src/uniffi/crypter/crypter.kt
Normal file
419
crypter-ffi/src/uniffi/crypter/crypter.kt
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
// This file was autogenerated by some hot garbage in the `uniffi` crate.
|
||||||
|
// Trust me, you don't want to mess with it!
|
||||||
|
|
||||||
|
@file:Suppress("NAME_SHADOWING")
|
||||||
|
|
||||||
|
package uniffi.crypter;
|
||||||
|
|
||||||
|
// Common helper code.
|
||||||
|
//
|
||||||
|
// Ideally this would live in a separate .kt file where it can be unittested etc
|
||||||
|
// in isolation, and perhaps even published as a re-useable package.
|
||||||
|
//
|
||||||
|
// However, it's important that the detils of how this helper code works (e.g. the
|
||||||
|
// way that different builtin types are passed across the FFI) exactly match what's
|
||||||
|
// expected by the Rust code on the other side of the interface. In practice right
|
||||||
|
// now that means coming from the exact some version of `uniffi` that was used to
|
||||||
|
// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin
|
||||||
|
// helpers directly inline like we're doing here.
|
||||||
|
|
||||||
|
import com.sun.jna.Library
|
||||||
|
import com.sun.jna.Native
|
||||||
|
import com.sun.jna.Pointer
|
||||||
|
import com.sun.jna.Structure
|
||||||
|
import com.sun.jna.ptr.ByReference
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
|
// This is a helper for safely working with byte buffers returned from the Rust code.
|
||||||
|
// A rust-owned buffer is represented by its capacity, its current length, and a
|
||||||
|
// pointer to the underlying data.
|
||||||
|
|
||||||
|
@Structure.FieldOrder("capacity", "len", "data")
|
||||||
|
open class RustBuffer : Structure() {
|
||||||
|
@JvmField var capacity: Int = 0
|
||||||
|
@JvmField var len: Int = 0
|
||||||
|
@JvmField var data: Pointer? = null
|
||||||
|
|
||||||
|
class ByValue : RustBuffer(), Structure.ByValue
|
||||||
|
class ByReference : RustBuffer(), Structure.ByReference
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
internal fun alloc(size: Int = 0) = rustCall() { status ->
|
||||||
|
_UniFFILib.INSTANCE.ffi_crypter_b428_rustbuffer_alloc(size, status).also {
|
||||||
|
if(it.data == null) {
|
||||||
|
throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun free(buf: RustBuffer.ByValue) = rustCall() { status ->
|
||||||
|
_UniFFILib.INSTANCE.ffi_crypter_b428_rustbuffer_free(buf, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("TooGenericExceptionThrown")
|
||||||
|
fun asByteBuffer() =
|
||||||
|
this.data?.getByteBuffer(0, this.len.toLong())?.also {
|
||||||
|
it.order(ByteOrder.BIG_ENDIAN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The equivalent of the `*mut RustBuffer` type.
|
||||||
|
* Required for callbacks taking in an out pointer.
|
||||||
|
*
|
||||||
|
* Size is the sum of all values in the struct.
|
||||||
|
*/
|
||||||
|
class RustBufferByReference : ByReference(16) {
|
||||||
|
/**
|
||||||
|
* Set the pointed-to `RustBuffer` to the given value.
|
||||||
|
*/
|
||||||
|
fun setValue(value: RustBuffer.ByValue) {
|
||||||
|
// NOTE: The offsets are as they are in the C-like struct.
|
||||||
|
val pointer = getPointer()
|
||||||
|
pointer.setInt(0, value.capacity)
|
||||||
|
pointer.setInt(4, value.len)
|
||||||
|
pointer.setPointer(8, value.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a helper for safely passing byte references into the rust code.
|
||||||
|
// It's not actually used at the moment, because there aren't many things that you
|
||||||
|
// can take a direct pointer to in the JVM, and if we're going to copy something
|
||||||
|
// then we might as well copy it into a `RustBuffer`. But it's here for API
|
||||||
|
// completeness.
|
||||||
|
|
||||||
|
@Structure.FieldOrder("len", "data")
|
||||||
|
open class ForeignBytes : Structure() {
|
||||||
|
@JvmField var len: Int = 0
|
||||||
|
@JvmField var data: Pointer? = null
|
||||||
|
|
||||||
|
class ByValue : ForeignBytes(), Structure.ByValue
|
||||||
|
}
|
||||||
|
// The FfiConverter interface handles converter types to and from the FFI
|
||||||
|
//
|
||||||
|
// All implementing objects should be public to support external types. When a
|
||||||
|
// type is external we need to import it's FfiConverter.
|
||||||
|
public interface FfiConverter<KotlinType, FfiType> {
|
||||||
|
// Convert an FFI type to a Kotlin type
|
||||||
|
fun lift(value: FfiType): KotlinType
|
||||||
|
|
||||||
|
// Convert an Kotlin type to an FFI type
|
||||||
|
fun lower(value: KotlinType): FfiType
|
||||||
|
|
||||||
|
// Read a Kotlin type from a `ByteBuffer`
|
||||||
|
fun read(buf: ByteBuffer): KotlinType
|
||||||
|
|
||||||
|
// Calculate bytes to allocate when creating a `RustBuffer`
|
||||||
|
//
|
||||||
|
// This must return at least as many bytes as the write() function will
|
||||||
|
// write. It can return more bytes than needed, for example when writing
|
||||||
|
// Strings we can't know the exact bytes needed until we the UTF-8
|
||||||
|
// encoding, so we pessimistically allocate the largest size possible (3
|
||||||
|
// bytes per codepoint). Allocating extra bytes is not really a big deal
|
||||||
|
// because the `RustBuffer` is short-lived.
|
||||||
|
fun allocationSize(value: KotlinType): Int
|
||||||
|
|
||||||
|
// Write a Kotlin type to a `ByteBuffer`
|
||||||
|
fun write(value: KotlinType, buf: ByteBuffer)
|
||||||
|
|
||||||
|
// Lower a value into a `RustBuffer`
|
||||||
|
//
|
||||||
|
// This method lowers a value into a `RustBuffer` rather than the normal
|
||||||
|
// FfiType. It's used by the callback interface code. Callback interface
|
||||||
|
// returns are always serialized into a `RustBuffer` regardless of their
|
||||||
|
// normal FFI type.
|
||||||
|
fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue {
|
||||||
|
val rbuf = RustBuffer.alloc(allocationSize(value))
|
||||||
|
try {
|
||||||
|
val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also {
|
||||||
|
it.order(ByteOrder.BIG_ENDIAN)
|
||||||
|
}
|
||||||
|
write(value, bbuf)
|
||||||
|
rbuf.writeField("len", bbuf.position())
|
||||||
|
return rbuf
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
RustBuffer.free(rbuf)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lift a value from a `RustBuffer`.
|
||||||
|
//
|
||||||
|
// This here mostly because of the symmetry with `lowerIntoRustBuffer()`.
|
||||||
|
// It's currently only used by the `FfiConverterRustBuffer` class below.
|
||||||
|
fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType {
|
||||||
|
val byteBuf = rbuf.asByteBuffer()!!
|
||||||
|
try {
|
||||||
|
val item = read(byteBuf)
|
||||||
|
if (byteBuf.hasRemaining()) {
|
||||||
|
throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!")
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
} finally {
|
||||||
|
RustBuffer.free(rbuf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FfiConverter that uses `RustBuffer` as the FfiType
|
||||||
|
public interface FfiConverterRustBuffer<KotlinType>: FfiConverter<KotlinType, RustBuffer.ByValue> {
|
||||||
|
override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value)
|
||||||
|
override fun lower(value: KotlinType) = lowerIntoRustBuffer(value)
|
||||||
|
}
|
||||||
|
// A handful of classes and functions to support the generated data structures.
|
||||||
|
// This would be a good candidate for isolating in its own ffi-support lib.
|
||||||
|
// Error runtime.
|
||||||
|
@Structure.FieldOrder("code", "error_buf")
|
||||||
|
internal open class RustCallStatus : Structure() {
|
||||||
|
@JvmField var code: Int = 0
|
||||||
|
@JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue()
|
||||||
|
|
||||||
|
fun isSuccess(): Boolean {
|
||||||
|
return code == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isError(): Boolean {
|
||||||
|
return code == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isPanic(): Boolean {
|
||||||
|
return code == 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InternalException(message: String) : Exception(message)
|
||||||
|
|
||||||
|
// Each top-level error class has a companion object that can lift the error from the call status's rust buffer
|
||||||
|
interface CallStatusErrorHandler<E> {
|
||||||
|
fun lift(error_buf: RustBuffer.ByValue): E;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers for calling Rust
|
||||||
|
// In practice we usually need to be synchronized to call this safely, so it doesn't
|
||||||
|
// synchronize itself
|
||||||
|
|
||||||
|
// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err
|
||||||
|
private inline fun <U, E: Exception> rustCallWithError(errorHandler: CallStatusErrorHandler<E>, callback: (RustCallStatus) -> U): U {
|
||||||
|
var status = RustCallStatus();
|
||||||
|
val return_value = callback(status)
|
||||||
|
if (status.isSuccess()) {
|
||||||
|
return return_value
|
||||||
|
} else if (status.isError()) {
|
||||||
|
throw errorHandler.lift(status.error_buf)
|
||||||
|
} else if (status.isPanic()) {
|
||||||
|
// when the rust code sees a panic, it tries to construct a rustbuffer
|
||||||
|
// with the message. but if that code panics, then it just sends back
|
||||||
|
// an empty buffer.
|
||||||
|
if (status.error_buf.len > 0) {
|
||||||
|
throw InternalException(FfiConverterString.lift(status.error_buf))
|
||||||
|
} else {
|
||||||
|
throw InternalException("Rust panic")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw InternalException("Unknown rust call status: $status.code")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
|
||||||
|
object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> {
|
||||||
|
override fun lift(error_buf: RustBuffer.ByValue): InternalException {
|
||||||
|
RustBuffer.free(error_buf)
|
||||||
|
return InternalException("Unexpected CALL_ERROR")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call a rust function that returns a plain value
|
||||||
|
private inline fun <U> rustCall(callback: (RustCallStatus) -> U): U {
|
||||||
|
return rustCallWithError(NullCallStatusErrorHandler, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains loading, initialization code,
|
||||||
|
// and the FFI Function declarations in a com.sun.jna.Library.
|
||||||
|
@Synchronized
|
||||||
|
private fun findLibraryName(componentName: String): String {
|
||||||
|
val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride")
|
||||||
|
if (libOverride != null) {
|
||||||
|
return libOverride
|
||||||
|
}
|
||||||
|
return "uniffi_crypter"
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified Lib : Library> loadIndirect(
|
||||||
|
componentName: String
|
||||||
|
): Lib {
|
||||||
|
return Native.load<Lib>(findLibraryName(componentName), Lib::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A JNA Library to expose the extern-C FFI definitions.
|
||||||
|
// This is an implementation detail which will be called internally by the public API.
|
||||||
|
|
||||||
|
internal interface _UniFFILib : Library {
|
||||||
|
companion object {
|
||||||
|
internal val INSTANCE: _UniFFILib by lazy {
|
||||||
|
loadIndirect<_UniFFILib>(componentName = "crypter")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun crypter_b428_derive_shared_secret(`theirPubkey`: RustBuffer.ByValue,`mySecretKey`: RustBuffer.ByValue,
|
||||||
|
_uniffi_out_err: RustCallStatus
|
||||||
|
): RustBuffer.ByValue
|
||||||
|
|
||||||
|
fun crypter_b428_encrypt(`plaintext`: RustBuffer.ByValue,`secret`: RustBuffer.ByValue,`nonce`: RustBuffer.ByValue,
|
||||||
|
_uniffi_out_err: RustCallStatus
|
||||||
|
): RustBuffer.ByValue
|
||||||
|
|
||||||
|
fun crypter_b428_decrypt(`ciphertext`: RustBuffer.ByValue,`secret`: RustBuffer.ByValue,
|
||||||
|
_uniffi_out_err: RustCallStatus
|
||||||
|
): RustBuffer.ByValue
|
||||||
|
|
||||||
|
fun ffi_crypter_b428_rustbuffer_alloc(`size`: Int,
|
||||||
|
_uniffi_out_err: RustCallStatus
|
||||||
|
): RustBuffer.ByValue
|
||||||
|
|
||||||
|
fun ffi_crypter_b428_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,
|
||||||
|
_uniffi_out_err: RustCallStatus
|
||||||
|
): RustBuffer.ByValue
|
||||||
|
|
||||||
|
fun ffi_crypter_b428_rustbuffer_free(`buf`: RustBuffer.ByValue,
|
||||||
|
_uniffi_out_err: RustCallStatus
|
||||||
|
): Unit
|
||||||
|
|
||||||
|
fun ffi_crypter_b428_rustbuffer_reserve(`buf`: RustBuffer.ByValue,`additional`: Int,
|
||||||
|
_uniffi_out_err: RustCallStatus
|
||||||
|
): RustBuffer.ByValue
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public interface members begin here.
|
||||||
|
|
||||||
|
|
||||||
|
public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
|
||||||
|
// Note: we don't inherit from FfiConverterRustBuffer, because we use a
|
||||||
|
// special encoding when lowering/lifting. We can use `RustBuffer.len` to
|
||||||
|
// store our length and avoid writing it out to the buffer.
|
||||||
|
override fun lift(value: RustBuffer.ByValue): String {
|
||||||
|
try {
|
||||||
|
val byteArr = ByteArray(value.len)
|
||||||
|
value.asByteBuffer()!!.get(byteArr)
|
||||||
|
return byteArr.toString(Charsets.UTF_8)
|
||||||
|
} finally {
|
||||||
|
RustBuffer.free(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(buf: ByteBuffer): String {
|
||||||
|
val len = buf.getInt()
|
||||||
|
val byteArr = ByteArray(len)
|
||||||
|
buf.get(byteArr)
|
||||||
|
return byteArr.toString(Charsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun lower(value: String): RustBuffer.ByValue {
|
||||||
|
val byteArr = value.toByteArray(Charsets.UTF_8)
|
||||||
|
// Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us
|
||||||
|
// to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`.
|
||||||
|
val rbuf = RustBuffer.alloc(byteArr.size)
|
||||||
|
rbuf.asByteBuffer()!!.put(byteArr)
|
||||||
|
return rbuf
|
||||||
|
}
|
||||||
|
|
||||||
|
// We aren't sure exactly how many bytes our string will be once it's UTF-8
|
||||||
|
// encoded. Allocate 3 bytes per unicode codepoint which will always be
|
||||||
|
// enough.
|
||||||
|
override fun allocationSize(value: String): Int {
|
||||||
|
val sizeForLength = 4
|
||||||
|
val sizeForString = value.length * 3
|
||||||
|
return sizeForLength + sizeForString
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(value: String, buf: ByteBuffer) {
|
||||||
|
val byteArr = value.toByteArray(Charsets.UTF_8)
|
||||||
|
buf.putInt(byteArr.size)
|
||||||
|
buf.put(byteArr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
sealed class CrypterException(message: String): Exception(message) {
|
||||||
|
// Each variant is a nested class
|
||||||
|
// Flat enums carries a string error message, so no special implementation is necessary.
|
||||||
|
class DeriveSharedSecret(message: String) : CrypterException(message)
|
||||||
|
class Encrypt(message: String) : CrypterException(message)
|
||||||
|
class Decrypt(message: String) : CrypterException(message)
|
||||||
|
class BadPubkey(message: String) : CrypterException(message)
|
||||||
|
class BadSecret(message: String) : CrypterException(message)
|
||||||
|
class BadNonce(message: String) : CrypterException(message)
|
||||||
|
class BadCiper(message: String) : CrypterException(message)
|
||||||
|
|
||||||
|
|
||||||
|
companion object ErrorHandler : CallStatusErrorHandler<CrypterException> {
|
||||||
|
override fun lift(error_buf: RustBuffer.ByValue): CrypterException = FfiConverterTypeCrypterError.lift(error_buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object FfiConverterTypeCrypterError : FfiConverterRustBuffer<CrypterException> {
|
||||||
|
override fun read(buf: ByteBuffer): CrypterException {
|
||||||
|
|
||||||
|
return when(buf.getInt()) {
|
||||||
|
1 -> CrypterException.DeriveSharedSecret(FfiConverterString.read(buf))
|
||||||
|
2 -> CrypterException.Encrypt(FfiConverterString.read(buf))
|
||||||
|
3 -> CrypterException.Decrypt(FfiConverterString.read(buf))
|
||||||
|
4 -> CrypterException.BadPubkey(FfiConverterString.read(buf))
|
||||||
|
5 -> CrypterException.BadSecret(FfiConverterString.read(buf))
|
||||||
|
6 -> CrypterException.BadNonce(FfiConverterString.read(buf))
|
||||||
|
7 -> CrypterException.BadCiper(FfiConverterString.read(buf))
|
||||||
|
else -> throw RuntimeException("invalid error enum value, something is very wrong!!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
override fun allocationSize(value: CrypterException): Int {
|
||||||
|
throw RuntimeException("Writing Errors is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
override fun write(value: CrypterException, buf: ByteBuffer) {
|
||||||
|
throw RuntimeException("Writing Errors is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@Throws(CrypterException::class)
|
||||||
|
|
||||||
|
fun `deriveSharedSecret`(`theirPubkey`: String, `mySecretKey`: String): String {
|
||||||
|
return FfiConverterString.lift(
|
||||||
|
rustCallWithError(CrypterException) { _status ->
|
||||||
|
_UniFFILib.INSTANCE.crypter_b428_derive_shared_secret(FfiConverterString.lower(`theirPubkey`), FfiConverterString.lower(`mySecretKey`), _status)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Throws(CrypterException::class)
|
||||||
|
|
||||||
|
fun `encrypt`(`plaintext`: String, `secret`: String, `nonce`: String): String {
|
||||||
|
return FfiConverterString.lift(
|
||||||
|
rustCallWithError(CrypterException) { _status ->
|
||||||
|
_UniFFILib.INSTANCE.crypter_b428_encrypt(FfiConverterString.lower(`plaintext`), FfiConverterString.lower(`secret`), FfiConverterString.lower(`nonce`), _status)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Throws(CrypterException::class)
|
||||||
|
|
||||||
|
fun `decrypt`(`ciphertext`: String, `secret`: String): String {
|
||||||
|
return FfiConverterString.lift(
|
||||||
|
rustCallWithError(CrypterException) { _status ->
|
||||||
|
_UniFFILib.INSTANCE.crypter_b428_decrypt(FfiConverterString.lower(`ciphertext`), FfiConverterString.lower(`secret`), _status)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -5,17 +5,17 @@ authors = ["Evan Feenstra <evanfeenstra@gmail.com>"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rand = "0.8"
|
|
||||||
anyhow = {version = "1", features = ["backtrace"]}
|
anyhow = {version = "1", features = ["backtrace"]}
|
||||||
log = "0.4"
|
|
||||||
base64 = { version = "0.13.0" }
|
|
||||||
secp256k1 = { version = "0.22.0", features = ["std", "rand-std"] }
|
secp256k1 = { version = "0.22.0", features = ["std", "rand-std"] }
|
||||||
|
rand = "0.8.5"
|
||||||
|
|
||||||
[dependencies.lightning]
|
[dependencies.lightning]
|
||||||
version = "0.0.108"
|
version = "0.0.108"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["std", "grind_signatures"]
|
features = ["std", "grind_signatures"]
|
||||||
|
|
||||||
|
# [dev-dependencies]
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
getrandom = { version = "0.2", git = "https://github.com/esp-rs-compat/getrandom.git" }
|
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" }
|
secp256k1 = { git = "https://github.com/Evanfeenstra/rust-secp256k1", branch = "v0.22.0-new-rand" }
|
||||||
|
|||||||
Reference in New Issue
Block a user