Remove crypter, crypter-ffi modules, point deps to sphinx-rs

This commit is contained in:
decentclock
2022-08-23 08:16:50 -06:00
parent fa9ed05bd8
commit 79884ed562
22 changed files with 17 additions and 1528 deletions

View File

@@ -10,7 +10,5 @@ members = [
exclude = [
"sphinx-key",
"crypter",
"crypter-ffi",
"persister",
]

View File

@@ -1,2 +0,0 @@
notes.md
src/crypter.uniffi.rs

View File

@@ -1,26 +0,0 @@
[package]
name = "crypter-ffi"
version = "0.1.0"
authors = ["Evan Feenstra <evanfeenstra@gmail.com>"]
edition = "2018"
[lib]
name = "crypter"
crate-type = ["staticlib", "cdylib"]
[dependencies]
sphinx-key-crypter = { path = "../crypter" }
uniffi = "0.19.2"
hex = "0.4.3"
thiserror = "1.0.31"
[build-dependencies]
uniffi_build = "0.19.2"
[profile.release]
opt-level = 'z' # Optimize for size.
lto = true # Enable Link Time Optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
# panic = 'abort' # Abort on panic
debug = true # Enable debug symbols. For example, we can use `dwarfdump` to check crash traces.

View File

@@ -1,38 +0,0 @@
echo "=> creating C FFI scaffolding"
uniffi-bindgen scaffolding src/crypter.udl
echo "=> creating kotlin bindings"
uniffi-bindgen generate src/crypter.udl --language kotlin
echo "=> renaming uniffi_crypter to crypter"
sed -i '' 's/return "uniffi_crypter"/return "crypter"/' src/uniffi/crypter/crypter.kt
echo "=> building i686-linux-android"
cross build --target i686-linux-android --release
echo "=> building aarch64-linux-android"
cross build --target aarch64-linux-android --release
echo "=> building arm-linux-androideabi"
cross build --target arm-linux-androideabi --release
echo "=> building armv7-linux-androideabi"
cross build --target armv7-linux-androideabi --release
echo "=> building x86_64-linux-android"
cross build --target x86_64-linux-android --release
echo "=> renaming files"
mkdir -p target/out
mkdir -p target/out/x86
mkdir -p target/out/arm64-v8a
mkdir -p target/out/armeabi
mkdir -p target/out/armeabi-v7a
mkdir -p target/out/x86_64
mv target/i686-linux-android/release/libcrypter.so target/out/x86/libcrypter.so
mv target/aarch64-linux-android/release/libcrypter.so target/out/arm64-v8a/libcrypter.so
mv target/arm-linux-androideabi/release/libcrypter.so target/out/armeabi/libcrypter.so
mv target/armv7-linux-androideabi/release/libcrypter.so target/out/armeabi-v7a/libcrypter.so
mv target/x86_64-linux-android/release/libcrypter.so target/out/x86_64/libcrypter.so
zip -r target/kotlin-libraries.zip target/out
echo "=> done!"

View File

@@ -1,18 +0,0 @@
echo "=> creating C FFI scaffolding"
uniffi-bindgen scaffolding src/crypter.udl
echo "=> creating swift bindings"
uniffi-bindgen generate src/crypter.udl --language swift
echo "=> creating swift bindings"
sed -i '' 's/module\ crypterFFI/framework\ module\ crypterFFI/' src/crypterFFI.modulemap
echo "=> building x86_64-apple-ios"
cross build --target=x86_64-apple-ios --release
echo "=> building aarch64-apple-ios"
cross build --target=aarch64-apple-ios --release
echo "=> combining into a universal lib"
lipo -create target/x86_64-apple-ios/release/libcrypter.a target/aarch64-apple-ios/release/libcrypter.a -output target/universal-crypter.a
echo "=> done!"

View File

@@ -1,23 +0,0 @@
uniffi-bindgen --version
should match the uniffi version in Cargo.toml
### build the C ffi
uniffi-bindgen scaffolding src/crypter.udl
### kotlin
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android arm-linux-androideabi
./build-kotlin.sh
### swift
rustup target add aarch64-apple-ios x86_64-apple-ios
armv7-apple-ios
armv7s-apple-ios
i386-apple-ios
./build-swift.sh

View File

@@ -1,512 +0,0 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
import Foundation
// Depending on the consumer's build setup, the low-level FFI code
// might be in a separate module, or it might be compiled inline into
// this module. This is a bit of light hackery to work with both.
#if canImport(crypterFFI)
import crypterFFI
#endif
fileprivate extension RustBuffer {
// Allocate a new buffer, copying the contents of a `UInt8` array.
init(bytes: [UInt8]) {
let rbuf = bytes.withUnsafeBufferPointer { ptr in
RustBuffer.from(ptr)
}
self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data)
}
static func from(_ ptr: UnsafeBufferPointer<UInt8>) -> RustBuffer {
try! rustCall { ffi_crypter_9c38_rustbuffer_from_bytes(ForeignBytes(bufferPointer: ptr), $0) }
}
// Frees the buffer in place.
// The buffer must not be used after this is called.
func deallocate() {
try! rustCall { ffi_crypter_9c38_rustbuffer_free(self, $0) }
}
}
fileprivate extension ForeignBytes {
init(bufferPointer: UnsafeBufferPointer<UInt8>) {
self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress)
}
}
// For every type used in the interface, we provide helper methods for conveniently
// lifting and lowering that type from C-compatible data, and for reading and writing
// values of that type in a buffer.
// Helper classes/extensions that don't change.
// Someday, this will be in a libray of its own.
fileprivate extension Data {
init(rustBuffer: RustBuffer) {
// TODO: This copies the buffer. Can we read directly from a
// Rust buffer?
self.init(bytes: rustBuffer.data!, count: Int(rustBuffer.len))
}
}
// A helper class to read values out of a byte buffer.
fileprivate class Reader {
let data: Data
var offset: Data.Index
init(data: Data) {
self.data = data
self.offset = 0
}
// Reads an integer at the current offset, in big-endian order, and advances
// the offset on success. Throws if reading the integer would move the
// offset past the end of the buffer.
func readInt<T: FixedWidthInteger>() throws -> T {
let range = offset..<offset + MemoryLayout<T>.size
guard data.count >= range.upperBound else {
throw UniffiInternalError.bufferOverflow
}
if T.self == UInt8.self {
let value = data[offset]
offset += 1
return value as! T
}
var value: T = 0
let _ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0, from: range)})
offset = range.upperBound
return value.bigEndian
}
// Reads an arbitrary number of bytes, to be used to read
// raw bytes, this is useful when lifting strings
func readBytes(count: Int) throws -> Array<UInt8> {
let range = offset..<(offset+count)
guard data.count >= range.upperBound else {
throw UniffiInternalError.bufferOverflow
}
var value = [UInt8](repeating: 0, count: count)
value.withUnsafeMutableBufferPointer({ buffer in
data.copyBytes(to: buffer, from: range)
})
offset = range.upperBound
return value
}
// Reads a float at the current offset.
@inlinable
func readFloat() throws -> Float {
return Float(bitPattern: try readInt())
}
// Reads a float at the current offset.
@inlinable
func readDouble() throws -> Double {
return Double(bitPattern: try readInt())
}
// Indicates if the offset has reached the end of the buffer.
@inlinable
func hasRemaining() -> Bool {
return offset < data.count
}
}
// A helper class to write values into a byte buffer.
fileprivate class Writer {
var bytes: [UInt8]
var offset: Array<UInt8>.Index
init() {
self.bytes = []
self.offset = 0
}
func writeBytes<S>(_ byteArr: S) where S: Sequence, S.Element == UInt8 {
bytes.append(contentsOf: byteArr)
}
// Writes an integer in big-endian order.
//
// Warning: make sure what you are trying to write
// is in the correct type!
func writeInt<T: FixedWidthInteger>(_ value: T) {
var value = value.bigEndian
withUnsafeBytes(of: &value) { bytes.append(contentsOf: $0) }
}
@inlinable
func writeFloat(_ value: Float) {
writeInt(value.bitPattern)
}
@inlinable
func writeDouble(_ value: Double) {
writeInt(value.bitPattern)
}
}
// Protocol for types that transfer other types across the FFI. This is
// analogous go the Rust trait of the same name.
fileprivate protocol FfiConverter {
associatedtype FfiType
associatedtype SwiftType
static func lift(_ value: FfiType) throws -> SwiftType
static func lower(_ value: SwiftType) -> FfiType
static func read(from buf: Reader) throws -> SwiftType
static func write(_ value: SwiftType, into buf: Writer)
}
// Types conforming to `Primitive` pass themselves directly over the FFI.
fileprivate protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType { }
extension FfiConverterPrimitive {
static func lift(_ value: FfiType) throws -> SwiftType {
return value
}
static func lower(_ value: SwiftType) -> FfiType {
return value
}
}
// Types conforming to `FfiConverterRustBuffer` lift and lower into a `RustBuffer`.
// Used for complex types where it's hard to write a custom lift/lower.
fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {}
extension FfiConverterRustBuffer {
static func lift(_ buf: RustBuffer) throws -> SwiftType {
let reader = Reader(data: Data(rustBuffer: buf))
let value = try read(from: reader)
if reader.hasRemaining() {
throw UniffiInternalError.incompleteData
}
buf.deallocate()
return value
}
static func lower(_ value: SwiftType) -> RustBuffer {
let writer = Writer()
write(value, into: writer)
return RustBuffer(bytes: writer.bytes)
}
}
// An error type for FFI errors. These errors occur at the UniFFI level, not
// the library level.
fileprivate enum UniffiInternalError: LocalizedError {
case bufferOverflow
case incompleteData
case unexpectedOptionalTag
case unexpectedEnumCase
case unexpectedNullPointer
case unexpectedRustCallStatusCode
case unexpectedRustCallError
case unexpectedStaleHandle
case rustPanic(_ message: String)
public var errorDescription: String? {
switch self {
case .bufferOverflow: return "Reading the requested value would read past the end of the buffer"
case .incompleteData: return "The buffer still has data after lifting its containing value"
case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1"
case .unexpectedEnumCase: return "Raw enum value doesn't match any cases"
case .unexpectedNullPointer: return "Raw pointer value was null"
case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code"
case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified"
case .unexpectedStaleHandle: return "The object in the handle map has been dropped already"
case let .rustPanic(message): return message
}
}
}
fileprivate let CALL_SUCCESS: Int8 = 0
fileprivate let CALL_ERROR: Int8 = 1
fileprivate let CALL_PANIC: Int8 = 2
fileprivate extension RustCallStatus {
init() {
self.init(
code: CALL_SUCCESS,
errorBuf: RustBuffer.init(
capacity: 0,
len: 0,
data: nil
)
)
}
}
private func rustCall<T>(_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T {
try makeRustCall(callback, errorHandler: {
$0.deallocate()
return UniffiInternalError.unexpectedRustCallError
})
}
private func rustCallWithError<T, F: FfiConverter>
(_ errorFfiConverter: F.Type, _ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T
where F.SwiftType: Error, F.FfiType == RustBuffer
{
try makeRustCall(callback, errorHandler: { return try errorFfiConverter.lift($0) })
}
private func makeRustCall<T>(_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T, errorHandler: (RustBuffer) throws -> Error) throws -> T {
var callStatus = RustCallStatus.init()
let returnedVal = callback(&callStatus)
switch callStatus.code {
case CALL_SUCCESS:
return returnedVal
case CALL_ERROR:
throw try errorHandler(callStatus.errorBuf)
case CALL_PANIC:
// 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 callStatus.errorBuf.len > 0 {
throw UniffiInternalError.rustPanic(try FfiConverterString.lift(callStatus.errorBuf))
} else {
callStatus.errorBuf.deallocate()
throw UniffiInternalError.rustPanic("Rust panic")
}
default:
throw UniffiInternalError.unexpectedRustCallStatusCode
}
}
// Public interface members begin here.
fileprivate struct FfiConverterString: FfiConverter {
typealias SwiftType = String
typealias FfiType = RustBuffer
static func lift(_ value: RustBuffer) throws -> String {
defer {
value.deallocate()
}
if value.data == nil {
return String()
}
let bytes = UnsafeBufferPointer<UInt8>(start: value.data!, count: Int(value.len))
return String(bytes: bytes, encoding: String.Encoding.utf8)!
}
static func lower(_ value: String) -> RustBuffer {
return value.utf8CString.withUnsafeBufferPointer { ptr in
// The swift string gives us int8_t, we want uint8_t.
ptr.withMemoryRebound(to: UInt8.self) { ptr in
// The swift string gives us a trailing null byte, we don't want it.
let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1))
return RustBuffer.from(buf)
}
}
}
static func read(from buf: Reader) throws -> String {
let len: Int32 = try buf.readInt()
return String(bytes: try buf.readBytes(count: Int(len)), encoding: String.Encoding.utf8)!
}
static func write(_ value: String, into buf: Writer) {
let len = Int32(value.utf8.count)
buf.writeInt(len)
buf.writeBytes(value.utf8)
}
}
public enum CrypterError {
// Simple error enums only carry a message
case DerivePublicKey(message: String)
// Simple error enums only carry a message
case DeriveSharedSecret(message: String)
// Simple error enums only carry a message
case Encrypt(message: String)
// Simple error enums only carry a message
case Decrypt(message: String)
// Simple error enums only carry a message
case BadPubkey(message: String)
// Simple error enums only carry a message
case BadSecret(message: String)
// Simple error enums only carry a message
case BadNonce(message: String)
// Simple error enums only carry a message
case BadCiper(message: String)
}
fileprivate struct FfiConverterTypeCrypterError: FfiConverterRustBuffer {
typealias SwiftType = CrypterError
static func read(from buf: Reader) throws -> CrypterError {
let variant: Int32 = try buf.readInt()
switch variant {
case 1: return .DerivePublicKey(
message: try FfiConverterString.read(from: buf)
)
case 2: return .DeriveSharedSecret(
message: try FfiConverterString.read(from: buf)
)
case 3: return .Encrypt(
message: try FfiConverterString.read(from: buf)
)
case 4: return .Decrypt(
message: try FfiConverterString.read(from: buf)
)
case 5: return .BadPubkey(
message: try FfiConverterString.read(from: buf)
)
case 6: return .BadSecret(
message: try FfiConverterString.read(from: buf)
)
case 7: return .BadNonce(
message: try FfiConverterString.read(from: buf)
)
case 8: return .BadCiper(
message: try FfiConverterString.read(from: buf)
)
default: throw UniffiInternalError.unexpectedEnumCase
}
}
static func write(_ value: CrypterError, into buf: Writer) {
switch value {
case let .DerivePublicKey(message):
buf.writeInt(Int32(1))
FfiConverterString.write(message, into: buf)
case let .DeriveSharedSecret(message):
buf.writeInt(Int32(2))
FfiConverterString.write(message, into: buf)
case let .Encrypt(message):
buf.writeInt(Int32(3))
FfiConverterString.write(message, into: buf)
case let .Decrypt(message):
buf.writeInt(Int32(4))
FfiConverterString.write(message, into: buf)
case let .BadPubkey(message):
buf.writeInt(Int32(5))
FfiConverterString.write(message, into: buf)
case let .BadSecret(message):
buf.writeInt(Int32(6))
FfiConverterString.write(message, into: buf)
case let .BadNonce(message):
buf.writeInt(Int32(7))
FfiConverterString.write(message, into: buf)
case let .BadCiper(message):
buf.writeInt(Int32(8))
FfiConverterString.write(message, into: buf)
}
}
}
extension CrypterError: Equatable, Hashable {}
extension CrypterError: Error { }
public func pubkeyFromSecretKey(mySecretKey: String) throws -> String {
return try FfiConverterString.lift(
try
rustCallWithError(FfiConverterTypeCrypterError.self) {
crypter_9c38_pubkey_from_secret_key(
FfiConverterString.lower(mySecretKey), $0)
}
)
}
public func deriveSharedSecret(theirPubkey: String, mySecretKey: String) throws -> String {
return try FfiConverterString.lift(
try
rustCallWithError(FfiConverterTypeCrypterError.self) {
crypter_9c38_derive_shared_secret(
FfiConverterString.lower(theirPubkey),
FfiConverterString.lower(mySecretKey), $0)
}
)
}
public func encrypt(plaintext: String, secret: String, nonce: String) throws -> String {
return try FfiConverterString.lift(
try
rustCallWithError(FfiConverterTypeCrypterError.self) {
crypter_9c38_encrypt(
FfiConverterString.lower(plaintext),
FfiConverterString.lower(secret),
FfiConverterString.lower(nonce), $0)
}
)
}
public func decrypt(ciphertext: String, secret: String) throws -> String {
return try FfiConverterString.lift(
try
rustCallWithError(FfiConverterTypeCrypterError.self) {
crypter_9c38_decrypt(
FfiConverterString.lower(ciphertext),
FfiConverterString.lower(secret), $0)
}
)
}
/**
* Top level initializers and tear down methods.
*
* This is generated by uniffi.
*/
public enum CrypterLifecycle {
/**
* Initialize the FFI and Rust library. This should be only called once per application.
*/
func initialize() {
}
}

View File

@@ -1,22 +0,0 @@
[Error]
enum CrypterError {
"DerivePublicKey",
"DeriveSharedSecret",
"Encrypt",
"Decrypt",
"BadPubkey",
"BadSecret",
"BadNonce",
"BadCiper",
};
namespace crypter {
[Throws=CrypterError]
string pubkey_from_secret_key(string my_secret_key);
[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);
};

View File

@@ -1,80 +0,0 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
#pragma once
#include <stdbool.h>
#include <stdint.h>
// The following structs are used to implement the lowest level
// of the FFI, and thus useful to multiple uniffied crates.
// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H.
#ifdef UNIFFI_SHARED_H
// We also try to prevent mixing versions of shared uniffi header structs.
// If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4
#ifndef UNIFFI_SHARED_HEADER_V4
#error Combining helper code from multiple versions of uniffi is not supported
#endif // ndef UNIFFI_SHARED_HEADER_V4
#else
#define UNIFFI_SHARED_H
#define UNIFFI_SHARED_HEADER_V4
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
typedef struct RustBuffer
{
int32_t capacity;
int32_t len;
uint8_t *_Nullable data;
} RustBuffer;
typedef int32_t (*ForeignCallback)(uint64_t, int32_t, RustBuffer, RustBuffer *_Nonnull);
typedef struct ForeignBytes
{
int32_t len;
const uint8_t *_Nullable data;
} ForeignBytes;
// Error definitions
typedef struct RustCallStatus {
int8_t code;
RustBuffer errorBuf;
} RustCallStatus;
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
#endif // def UNIFFI_SHARED_H
RustBuffer crypter_9c38_pubkey_from_secret_key(
RustBuffer my_secret_key,
RustCallStatus *_Nonnull out_status
);
RustBuffer crypter_9c38_derive_shared_secret(
RustBuffer their_pubkey,RustBuffer my_secret_key,
RustCallStatus *_Nonnull out_status
);
RustBuffer crypter_9c38_encrypt(
RustBuffer plaintext,RustBuffer secret,RustBuffer nonce,
RustCallStatus *_Nonnull out_status
);
RustBuffer crypter_9c38_decrypt(
RustBuffer ciphertext,RustBuffer secret,
RustCallStatus *_Nonnull out_status
);
RustBuffer ffi_crypter_9c38_rustbuffer_alloc(
int32_t size,
RustCallStatus *_Nonnull out_status
);
RustBuffer ffi_crypter_9c38_rustbuffer_from_bytes(
ForeignBytes bytes,
RustCallStatus *_Nonnull out_status
);
void ffi_crypter_9c38_rustbuffer_free(
RustBuffer buf,
RustCallStatus *_Nonnull out_status
);
RustBuffer ffi_crypter_9c38_rustbuffer_reserve(
RustBuffer buf,int32_t additional,
RustCallStatus *_Nonnull out_status
);

View File

@@ -1,6 +0,0 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
framework module crypterFFI {
header "crypterFFI.h"
export *
}

View File

@@ -1,121 +0,0 @@
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;
use sphinx_key_crypter::secp256k1::{PublicKey, Secp256k1, SecretKey};
include!("crypter.uniffi.rs");
pub type Result<T> = std::result::Result<T, CrypterError>;
#[derive(Debug, thiserror::Error)]
pub enum CrypterError {
#[error("Failed to derive public key")]
DerivePublicKey,
#[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,
}
pub fn pubkey_from_secret_key(my_secret_key: String) -> Result<String> {
let secret_key = parse::parse_secret_string(my_secret_key)?;
let sk = match SecretKey::from_slice(&secret_key[..]) {
Ok(s) => s,
Err(_) => return Err(CrypterError::BadSecret),
};
let ctx = Secp256k1::new();
let pk = PublicKey::from_secret_key(&ctx, &sk).serialize();
Ok(hex::encode(pk))
}
// 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, pubkey_from_secret_key, 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(())
}
#[test]
fn test_derive_pubkey() -> Result<()> {
let sk1 = "86c8977989592a97beb409bc27fde76e981ce3543499fd61743755b832e92a3e";
let pk1 = "0362a684901b8d065fb034bc44ea972619a409aeafc2a698016a74f6eee1008aca";
let pk = pubkey_from_secret_key(sk1.to_string())?;
assert_eq!(pk, pk1);
Ok(())
}
}

View File

@@ -1,65 +0,0 @@
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)
}

View File

@@ -1,435 +0,0 @@
// 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_9c38_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_9c38_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 "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_9c38_pubkey_from_secret_key(`mySecretKey`: RustBuffer.ByValue,
_uniffi_out_err: RustCallStatus
): RustBuffer.ByValue
fun crypter_9c38_derive_shared_secret(`theirPubkey`: RustBuffer.ByValue,`mySecretKey`: RustBuffer.ByValue,
_uniffi_out_err: RustCallStatus
): RustBuffer.ByValue
fun crypter_9c38_encrypt(`plaintext`: RustBuffer.ByValue,`secret`: RustBuffer.ByValue,`nonce`: RustBuffer.ByValue,
_uniffi_out_err: RustCallStatus
): RustBuffer.ByValue
fun crypter_9c38_decrypt(`ciphertext`: RustBuffer.ByValue,`secret`: RustBuffer.ByValue,
_uniffi_out_err: RustCallStatus
): RustBuffer.ByValue
fun ffi_crypter_9c38_rustbuffer_alloc(`size`: Int,
_uniffi_out_err: RustCallStatus
): RustBuffer.ByValue
fun ffi_crypter_9c38_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,
_uniffi_out_err: RustCallStatus
): RustBuffer.ByValue
fun ffi_crypter_9c38_rustbuffer_free(`buf`: RustBuffer.ByValue,
_uniffi_out_err: RustCallStatus
): Unit
fun ffi_crypter_9c38_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 DerivePublicKey(message: String) : CrypterException(message)
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.DerivePublicKey(FfiConverterString.read(buf))
2 -> CrypterException.DeriveSharedSecret(FfiConverterString.read(buf))
3 -> CrypterException.Encrypt(FfiConverterString.read(buf))
4 -> CrypterException.Decrypt(FfiConverterString.read(buf))
5 -> CrypterException.BadPubkey(FfiConverterString.read(buf))
6 -> CrypterException.BadSecret(FfiConverterString.read(buf))
7 -> CrypterException.BadNonce(FfiConverterString.read(buf))
8 -> 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 `pubkeyFromSecretKey`(`mySecretKey`: String): String {
return FfiConverterString.lift(
rustCallWithError(CrypterException) { _status ->
_UniFFILib.INSTANCE.crypter_9c38_pubkey_from_secret_key(FfiConverterString.lower(`mySecretKey`), _status)
})
}
@Throws(CrypterException::class)
fun `deriveSharedSecret`(`theirPubkey`: String, `mySecretKey`: String): String {
return FfiConverterString.lift(
rustCallWithError(CrypterException) { _status ->
_UniFFILib.INSTANCE.crypter_9c38_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_9c38_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_9c38_decrypt(FfiConverterString.lower(`ciphertext`), FfiConverterString.lower(`secret`), _status)
})
}

View File

@@ -1,17 +0,0 @@
[package]
name = "sphinx-key-crypter"
version = "0.1.0"
authors = ["Evan Feenstra <evanfeenstra@gmail.com>"]
edition = "2018"
[dependencies]
anyhow = {version = "1", features = ["backtrace"]}
secp256k1 = { version = "0.24.0", features = ["std", "rand-std", "lowmemory"] }
rand = "0.8.5"
[dependencies.lightning]
git = "https://github.com/lightningdevkit/rust-lightning.git"
rev = "ea5b62fff69847941434fb51562e302eb4e7ff4b"
default-features = false
features = ["std", "grind_signatures"]

View File

@@ -1,59 +0,0 @@
use anyhow::anyhow;
use lightning::util::chacha20poly1305rfc::ChaCha20Poly1305RFC;
pub const MSG_LEN: usize = 32;
pub const KEY_LEN: usize = 32;
pub const NONCE_END_LEN: usize = 8;
pub const TAG_LEN: usize = 16;
pub const CIPHER_LEN: usize = MSG_LEN + NONCE_END_LEN + TAG_LEN;
pub fn encrypt(
plaintext: [u8; MSG_LEN],
key: [u8; KEY_LEN],
nonce_end: [u8; NONCE_END_LEN],
) -> anyhow::Result<[u8; CIPHER_LEN]> {
let mut nonce = [0; 4 + NONCE_END_LEN];
nonce[4..].copy_from_slice(&nonce_end);
let mut chacha = ChaCha20Poly1305RFC::new(&key, &nonce, &[0; 0]);
let mut res = [0; MSG_LEN];
let mut tag = [0; TAG_LEN];
chacha.encrypt(&plaintext[..], &mut res[0..plaintext.len()], &mut tag);
let mut ret = [0; CIPHER_LEN];
ret[..MSG_LEN].copy_from_slice(&res);
ret[MSG_LEN..MSG_LEN + NONCE_END_LEN].copy_from_slice(&nonce_end);
ret[MSG_LEN + NONCE_END_LEN..].copy_from_slice(&tag);
Ok(ret)
}
pub fn decrypt(ciphertext: [u8; CIPHER_LEN], key: [u8; KEY_LEN]) -> anyhow::Result<[u8; MSG_LEN]> {
let mut nonce = [0; 4 + NONCE_END_LEN];
nonce[4..].copy_from_slice(&ciphertext[MSG_LEN..MSG_LEN + NONCE_END_LEN]);
let mut tag = [0; TAG_LEN];
tag.copy_from_slice(&ciphertext[MSG_LEN + NONCE_END_LEN..]);
let mut chacha2 = ChaCha20Poly1305RFC::new(&key, &nonce, &[0; 0]);
let mut dec = [0; MSG_LEN];
let ok = chacha2.decrypt(&ciphertext[..MSG_LEN], &mut dec, &tag);
if ok {
Ok(dec)
} else {
Err(anyhow!("failed chacha authentication"))
}
}
#[cfg(test)]
mod tests {
use crate::chacha::{decrypt, encrypt, KEY_LEN, MSG_LEN, NONCE_END_LEN};
use rand::{rngs::OsRng, RngCore};
#[test]
fn test_chacha() -> anyhow::Result<()> {
let key = [9; KEY_LEN];
let plaintext = [1; MSG_LEN];
let mut nonce_end = [0; NONCE_END_LEN];
OsRng.fill_bytes(&mut nonce_end);
let cipher = encrypt(plaintext, key, nonce_end)?;
let plain = decrypt(cipher, key)?;
assert_eq!(plaintext, plain);
Ok(())
}
}

View File

@@ -1,47 +0,0 @@
use secp256k1::ecdh::SharedSecret;
use secp256k1::{SecretKey, PublicKey};
use anyhow::Result;
pub const PUBLIC_KEY_LEN: usize = 33;
pub const PRIVATE_KEY_LEN: usize = 32;
pub const SECRET_LEN: usize = 32;
pub fn derive_shared_secret_from_slice(their_public_key: [u8; PUBLIC_KEY_LEN], my_private_key: [u8; PRIVATE_KEY_LEN]) -> Result<[u8; SECRET_LEN]> {
let public_key = PublicKey::from_slice(&their_public_key[..])?;
let private_key = SecretKey::from_slice(&my_private_key[..])?;
Ok(derive_shared_secret(&public_key, &private_key).secret_bytes())
}
pub fn derive_shared_secret(their_public_key: &PublicKey, my_private_key: &SecretKey) -> SharedSecret {
SharedSecret::new(their_public_key, my_private_key)
}
#[cfg(test)]
mod tests {
use crate::ecdh::{derive_shared_secret, derive_shared_secret_from_slice};
use rand::thread_rng;
use secp256k1::Secp256k1;
#[test]
fn test_ecdh() -> anyhow::Result<()> {
let s = Secp256k1::new();
let (sk1, pk1) = s.generate_keypair(&mut thread_rng());
let (sk2, pk2) = s.generate_keypair(&mut thread_rng());
let sec1 = derive_shared_secret(&pk2, &sk1);
let sec2 = derive_shared_secret(&pk1, &sk2);
assert_eq!(sec1, sec2);
Ok(())
}
#[test]
fn test_ecdh_from_slice() -> anyhow::Result<()> {
let s = Secp256k1::new();
let (sk1, pk1) = s.generate_keypair(&mut thread_rng());
let (sk2, pk2) = s.generate_keypair(&mut thread_rng());
let sec1 = derive_shared_secret_from_slice(pk2.serialize(), sk1.secret_bytes())?;
let sec2 = derive_shared_secret_from_slice(pk1.serialize(), sk2.secret_bytes())?;
assert_eq!(sec1, sec2);
Ok(())
}
}

View File

@@ -1,38 +0,0 @@
pub mod chacha;
pub mod ecdh;
pub use secp256k1;
#[cfg(test)]
mod tests {
use crate::chacha::{decrypt, encrypt, MSG_LEN, NONCE_END_LEN};
use crate::ecdh::derive_shared_secret_from_slice;
use secp256k1::rand::{rngs::OsRng, thread_rng, RngCore};
use secp256k1::Secp256k1;
#[test]
fn test_crypter() -> anyhow::Result<()> {
// two keypairs
let s = Secp256k1::new();
let (sk1, pk1) = s.generate_keypair(&mut thread_rng());
let (sk2, pk2) = s.generate_keypair(&mut thread_rng());
// derive shared secrets
let sec1 = derive_shared_secret_from_slice(pk2.serialize(), sk1.secret_bytes())?;
let sec2 = derive_shared_secret_from_slice(pk1.serialize(), sk2.secret_bytes())?;
assert_eq!(sec1, sec2);
// encrypt plaintext with sec1
let plaintext = [1; MSG_LEN];
let mut nonce_end = [0; NONCE_END_LEN];
OsRng.fill_bytes(&mut nonce_end);
let cipher = encrypt(plaintext, sec1, nonce_end)?;
// decrypt with sec2
let plain = decrypt(cipher, sec2)?;
assert_eq!(plaintext, plain);
println!("PLAINTEXT MATCHES!");
Ok(())
}
}

View File

@@ -23,7 +23,7 @@ no_persist = []
bitflags = "1.3.2"
esp-idf-sys = { version = "0.31.6", features = ["binstart"] }
sphinx-key-signer = { path = "../signer", optional = true }
sphinx-key-crypter = { path = "../crypter" }
sphinx-crypter = { git = "https://github.com/stakwork/sphinx-rs.git" }
embedded-svc = "0.22.1"
esp-idf-svc = "0.42.1"
esp-idf-hal = "0.38.0"

View File

@@ -10,10 +10,10 @@ use embedded_svc::wifi::*;
use esp_idf_svc::nvs::*;
use esp_idf_svc::wifi::*;
use sphinx_key_crypter::chacha::{decrypt, CIPHER_LEN};
use sphinx_key_crypter::ecdh::{derive_shared_secret_from_slice, PUBLIC_KEY_LEN};
use sphinx_key_crypter::secp256k1::rand::thread_rng;
use sphinx_key_crypter::secp256k1::{Secp256k1, SecretKey, PublicKey};
use sphinx_crypter::chacha::{decrypt, PAYLOAD_LEN};
use sphinx_crypter::ecdh::{derive_shared_secret_from_slice, PUBLIC_KEY_LEN};
use sphinx_crypter::secp256k1::rand::thread_rng;
use sphinx_crypter::secp256k1::{Secp256k1, SecretKey, PublicKey};
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Config {
@@ -56,7 +56,7 @@ pub fn decrypt_seed(dto: ConfigDTO, sk1: SecretKey) -> Result<Config> {
derive_shared_secret_from_slice(their_pk_bytes, sk1.secret_bytes())?;
// decrypt seed
let cipher_seed = hex::decode(dto.seed)?;
let cipher: [u8; CIPHER_LEN] = cipher_seed[..CIPHER_LEN].try_into()?;
let cipher: [u8; PAYLOAD_LEN] = cipher_seed[..PAYLOAD_LEN].try_into()?;
let seed = decrypt(cipher, shared_secret)?;
Ok(Config {

View File

@@ -7,7 +7,7 @@ edition = "2018"
[dependencies]
sphinx-key-signer = { path = "../signer" }
sphinx-key-parser = { path = "../parser" }
sphinx-key-crypter = { path = "../crypter" }
sphinx-crypter = { git = "https://github.com/stakwork/sphinx-rs.git" }
anyhow = {version = "1", features = ["backtrace"]}
log = "0.4"
rumqttc = "0.12.0"

View File

@@ -1,9 +1,9 @@
use dotenv::dotenv;
use rand::{rngs::OsRng, thread_rng, RngCore};
use serde::{Deserialize, Serialize};
use sphinx_key_crypter::chacha::{encrypt, MSG_LEN, NONCE_END_LEN};
use sphinx_key_crypter::ecdh::{derive_shared_secret_from_slice, PUBLIC_KEY_LEN};
use sphinx_key_crypter::secp256k1::Secp256k1;
use sphinx_crypter::chacha::{encrypt, MSG_LEN, NONCE_LEN};
use sphinx_crypter::ecdh::{derive_shared_secret_from_slice, PUBLIC_KEY_LEN};
use sphinx_crypter::secp256k1::Secp256k1;
use std::convert::TryInto;
use std::env;
use std::time::Duration;
@@ -70,7 +70,7 @@ async fn main() -> anyhow::Result<()> {
let their_pk_bytes: [u8; PUBLIC_KEY_LEN] = their_pk[..PUBLIC_KEY_LEN].try_into()?;
let shared_secret = derive_shared_secret_from_slice(their_pk_bytes, sk1.secret_bytes())?;
let mut nonce_end = [0; NONCE_END_LEN];
let mut nonce_end = [0; NONCE_LEN];
OsRng.fill_bytes(&mut nonce_end);
let cipher = encrypt(seed, shared_secret, nonce_end)?;

View File

@@ -5,11 +5,11 @@ use rocket::State;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use sphinx_key_crypter::chacha::{decrypt, CIPHER_LEN};
use sphinx_key_crypter::ecdh::{derive_shared_secret_from_slice, PUBLIC_KEY_LEN};
use sphinx_key_crypter::secp256k1::rand::thread_rng;
use sphinx_key_crypter::secp256k1::Secp256k1;
use sphinx_key_crypter::secp256k1::{PublicKey, SecretKey};
use sphinx_crypter::chacha::{decrypt, PAYLOAD_LEN};
use sphinx_crypter::ecdh::{derive_shared_secret_from_slice, PUBLIC_KEY_LEN};
use sphinx_crypter::secp256k1::rand::thread_rng;
use sphinx_crypter::secp256k1::Secp256k1;
use sphinx_crypter::secp256k1::{PublicKey, SecretKey};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConfigBody {
@@ -54,7 +54,7 @@ pub fn decrypt_seed(dto: ConfigBody, sk1: SecretKey) -> anyhow::Result<Config> {
let shared_secret = derive_shared_secret_from_slice(their_pk_bytes, sk1.secret_bytes())?;
// decrypt seed
let cipher_seed = hex::decode(dto.seed)?;
let cipher: [u8; CIPHER_LEN] = cipher_seed[..CIPHER_LEN].try_into()?;
let cipher: [u8; PAYLOAD_LEN] = cipher_seed[..PAYLOAD_LEN].try_into()?;
let seed = decrypt(cipher, shared_secret)?;
Ok(Config {