Skip to content

Commit

Permalink
Basic FFI library (#22)
Browse files Browse the repository at this point in the history
This PR adds a very barebones FFI support to the crate.

* All memory managed by caller!
* No types exported across the FFI boundary!

A demo program and Makefile is presented to see how it all fits
together.
  • Loading branch information
CjS77 committed Dec 1, 2020
1 parent b9ffb9b commit 63fc9b4
Show file tree
Hide file tree
Showing 11 changed files with 395 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Expand Up @@ -26,3 +26,8 @@ report
Cargo.lock
*.log

# Generated during build
/libtari/tari_crypto.h

# C-demo output
bin/
8 changes: 6 additions & 2 deletions Cargo.toml
Expand Up @@ -29,22 +29,26 @@ rmp-serde = "0.13.7"
serde = "1.0.89"
serde_json = "1.0"
lazy_static = "1.3.0"

libc = { version = "0.2", optional = true }
wasm-bindgen = { version = "^0.2", features = ["serde-serialize"], optional = true }

[dev-dependencies]
criterion = "0.2"
bincode = "1.1.4"

[build-dependencies]
cbindgen = "0.6.6"

[features]
default = []
avx2 = ["curve25519-dalek/avx2_backend", "bulletproofs/avx2_backend"]
wasm = ["wasm-bindgen", "clear_on_drop/no_cc", "rand/wasm-bindgen", "rand/getrandom"]
ffi = ["libc"]

[lib]
# Disable benchmarks to allow Criterion to take over
bench = false
crate-type = ["lib", "cdylib"]
crate-type = ["lib", "cdylib", "staticlib"]

[[bench]]
name = "benches"
Expand Down
33 changes: 33 additions & 0 deletions Makefile
@@ -0,0 +1,33 @@
ifeq ($(shell uname),Darwin)
LDFLAGS := -Ltarget/release/
else
LDFLAGS := -Ltarget/release/
endif

SRC = libtari
BIN = bin
PWD = $(shell pwd)

CC=cc

CFLAGS =

clean:
rm $(SRC)/tari_crypto.h
rm $(BIN)/demo

$(LIB)/tari_crypto.h target/release/libtari_crypto.a:
cargo build --features=ffi --release

target/debug/libtari_crypto.a:
cargo build --features=ffi

$(BIN)/demo: $(LIB)/tari_crypto.h target/release/libtari_crypto.a
mkdir -p $(BIN)
$(CC) $(SRC)/demo.c $(LDFLAGS) -ltari_crypto -o $@

demo: $(BIN)/demo

ffi: target/debug/libtari_crypto.a

ffi-release: target/release/libtari_crypto.a
15 changes: 15 additions & 0 deletions README.md
Expand Up @@ -64,3 +64,18 @@ To run the benchmarks with SIMD instructions:
* Breaking change: `KeyRing.sign` doesn't take a nonce any more. It's not needed, and why risk someone re-using it?
* New method: `key_utils.sign` to sign keys not in the key ring
* New module: Commitments

# Building the C FFI module

To build the C bindings, you can run

make ffi

To build the release version (recommended):

make ffi-release

To run the small demo:

make demo
./bin/demo
39 changes: 39 additions & 0 deletions build.rs
@@ -0,0 +1,39 @@
// Copyright 2020. The Tari Project
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
// following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
// disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
// products derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use cbindgen::Config;
use std::{env, path::Path};

fn main() {
let needs_ffi = env::var("CARGO_FEATURE_FFI").is_ok();
if needs_ffi {
generate_ffi_header();
}
generate_ffi_header();
}

fn generate_ffi_header() {
let crate_env = env::var("CARGO_MANIFEST_DIR").unwrap();
let crate_path = Path::new(&crate_env);
let config = Config::from_root_or_default(crate_path);
cbindgen::Builder::new()
.with_crate(crate_path.to_str().unwrap())
.with_config(config)
.generate()
.expect("Unable to generate bindings")
.write_to_file("libtari/tari_crypto.h");
}
5 changes: 5 additions & 0 deletions cbindgen.toml
@@ -0,0 +1,5 @@
language = "C"
include_version = true

[parse]
parse_deps = false
68 changes: 68 additions & 0 deletions libtari/demo.c
@@ -0,0 +1,68 @@
#include "tari_crypto.h"
#include <stdio.h>

void print_key(uint8_t key[]) {
int i;
for (i = 0; i < KEY_LENGTH; i++) {
printf("%02X", key[i]);
}
printf("\n");
}

/*
* This demo generates a key pair, signs a message and then validates the signature.
* All memory in this FFI is managed by the caller. In this demo, the data is kept on the stack, and so explicit
* memory management is not done, but in general, you have to allocate and free memory yourself.
*/
int main() {
const char *ver = version();
printf("Tari Crypto (v%s)\n", ver);

uint8_t pub_key[KEY_LENGTH], priv_key[KEY_LENGTH];

int code = random_keypair(&priv_key, &pub_key);
if (code) {
printf("Error code: %d\n", code);
return code;
}
printf("Keys generated\n");
print_key(priv_key);
print_key(pub_key);

// Sign and verify message
const char msg[] = "Hello world\0";
const char invalid[] = "Hullo world\0";

uint8_t r[KEY_LENGTH], sig[KEY_LENGTH];

code = sign(&priv_key, &msg[0], &r, &sig);
if (code) {
printf("Error code: %d\n", code);
return code;
}

// Demonstrate error handling
char *err_msg = malloc( sizeof(char) * ( 128 + 1 ) );
lookup_error_message(-1, &err_msg[0], 128);
printf("The error message for code -1 is \"%s\"\n", err_msg);

printf("Signed message\n");
print_key(r);
print_key(sig);

printf("Check (invalid) signature..");
if (verify(&pub_key, &invalid[0], &r, &sig, &code)) {
printf("Oh no. This should have failed\n");
} else {
printf("The signature is invalid, as expected\n");
}

printf("Check signature..");
if (verify(&pub_key, &msg[0], &r, &sig, &code)) {
printf("SUCCESS\n");
} else {
printf("FAILED\n");
}
return code;
}

66 changes: 66 additions & 0 deletions src/ffi/error.rs
@@ -0,0 +1,66 @@
// Copyright 2020. The Tari Project
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
// following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
// disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
// products derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use std::{
os::raw::{c_char, c_int},
ptr,
slice,
};

/// Looks up the error message associated with the given error code.
///
/// This function returns 0 on successful execution, or an error code on a failure.
#[no_mangle]
pub unsafe extern "C" fn lookup_error_message(code: c_int, buffer: *mut c_char, length: c_int) -> c_int {
if buffer.is_null() {
return NULL_POINTER;
}

let error_message = get_error_message(code).to_string();
let buffer = slice::from_raw_parts_mut(buffer as *mut u8, length as usize);

if error_message.len() >= buffer.len() {
return BUFFER_TOO_SMALL;
}

ptr::copy_nonoverlapping(error_message.as_ptr(), buffer.as_mut_ptr(), error_message.len());

// Add a trailing null so people using the string as a `char *` don't
// accidentally read into garbage.
buffer[error_message.len()] = 0;

error_message.len() as c_int
}

pub const OK: i32 = 0;
pub const NULL_POINTER: i32 = -1;
pub const BUFFER_TOO_SMALL: i32 = -2;
pub const INVALID_SECRET_KEY_SER: i32 = -1000;
pub const SIGNING_ERROR: i32 = -1100;
pub const STR_CONV_ERR: i32 = -2000;

pub fn get_error_message(code: i32) -> &'static str {
match code {
OK => "The operation completed without errors.",
NULL_POINTER => "A null pointer was passed as an input pointer",
BUFFER_TOO_SMALL => "The provided buffer was too small",
INVALID_SECRET_KEY_SER => "Invalid secret key representation.",
SIGNING_ERROR => "Error creating signature",
STR_CONV_ERR => "String conversion error",
_ => "Unknown error code.",
}
}
124 changes: 124 additions & 0 deletions src/ffi/keys.rs
@@ -0,0 +1,124 @@
// Copyright 2020. The Tari Project
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
// following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
// disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
// products derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use crate::{
ffi::error::{INVALID_SECRET_KEY_SER, NULL_POINTER, OK, SIGNING_ERROR, STR_CONV_ERR},
hash::blake2::Blake256,
keys::{PublicKey, SecretKey},
ristretto::{RistrettoPublicKey, RistrettoSchnorr, RistrettoSecretKey},
};
use digest::Digest;
use libc::c_char;
use rand::rngs::OsRng;
use std::{ffi::CStr, os::raw::c_int};
use tari_utilities::ByteArray;

const KEY_LENGTH: usize = 32;

type KeyArray = [u8; KEY_LENGTH];

/// Generate a new key pair and copies the values into the provided arrays.
///
/// If `pub_key` is null, then only a private key is generated.
/// The *caller* must manage memory for the results. Besides checking for null values, this function assumes that at
/// least `KEY_LENGTH` bytes have been allocated in `priv_key` and `pub_key`.
#[no_mangle]
pub unsafe extern "C" fn random_keypair(priv_key: *mut KeyArray, pub_key: *mut KeyArray) -> c_int {
if priv_key.is_null() && pub_key.is_null() {
return NULL_POINTER;
}
if pub_key.is_null() {
let k = RistrettoSecretKey::random(&mut OsRng);
(*priv_key).copy_from_slice(k.as_bytes());
} else {
let (k, p) = RistrettoPublicKey::random_keypair(&mut OsRng);
(*priv_key).copy_from_slice(k.as_bytes());
(*pub_key).copy_from_slice(p.as_bytes());
}
OK
}

#[no_mangle]
pub unsafe extern "C" fn sign(
priv_key: *const KeyArray,
msg: *const c_char,
nonce: *mut KeyArray,
signature: *mut KeyArray,
) -> c_int
{
if nonce.is_null() || signature.is_null() || priv_key.is_null() || msg.is_null() {
return NULL_POINTER;
}
let k = match RistrettoSecretKey::from_bytes(&(*priv_key)) {
Ok(k) => k,
_ => return INVALID_SECRET_KEY_SER,
};
let r = RistrettoSecretKey::random(&mut OsRng);
let msg = match CStr::from_ptr(msg).to_str() {
Ok(s) => s,
_ => return STR_CONV_ERR,
};
let challenge = Blake256::digest(msg.as_bytes()).to_vec();
let sig = match RistrettoSchnorr::sign(k, r, &challenge) {
Ok(sig) => sig,
_ => return SIGNING_ERROR,
};
(*nonce).copy_from_slice(sig.get_public_nonce().as_bytes());
(*signature).copy_from_slice(sig.get_signature().as_bytes());
OK
}

#[no_mangle]
pub unsafe extern "C" fn verify(
pub_key: *const KeyArray,
msg: *const c_char,
pub_nonce: *mut KeyArray,
signature: *mut KeyArray,
err_code: *mut c_int,
) -> bool
{
if pub_key.is_null() || msg.is_null() || pub_nonce.is_null() || signature.is_null() {
*err_code = NULL_POINTER;
return false;
}
let pk = match RistrettoPublicKey::from_bytes(&(*pub_key)) {
Ok(k) => k,
_ => {
*err_code = INVALID_SECRET_KEY_SER;
return false;
},
};
let r_pub = match RistrettoPublicKey::from_bytes(&(*pub_nonce)) {
Ok(r) => r,
_ => return false,
};
let sig = match RistrettoSecretKey::from_bytes(&(*signature)) {
Ok(s) => s,
_ => return false,
};
let msg = match CStr::from_ptr(msg).to_str() {
Ok(s) => s,
_ => return false,
};
let sig = RistrettoSchnorr::new(r_pub, sig);
let challenge = Blake256::digest(msg.as_bytes());
let challenge = match RistrettoSecretKey::from_bytes(challenge.as_slice()) {
Ok(e) => e,
_ => return false,
};
sig.verify(&pk, &challenge)
}

0 comments on commit 63fc9b4

Please sign in to comment.