Feature/msgpack argument passing (#9)

* adding rmpv crate

* plumb in msgpack

* setting up mpack basics

* setting up mpack basics

* setting up mpack basics

* setting up mpack basics

* updates

* chunks

* chunks

* chunks

* add mpack proof func

* add mpack proof func - tests infra

* add mpack proof func - tests infra remove

* add mpack proof func - split mpack

* rework funcs

* rework funcs

* rework funcs

* rework funcs

* rework funcs

* rework funcs

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor - read orig for testing

* refactor - read orig for testing

* setting up tests

* setting up tests

* setting up tests

* setting up tests

* setting rest of data

* setting rest of data

* setting rest of data

* setting rest of data

* re-add original prove for comparison

* re-add original prove for comparison

* re-add original prove for comparison

* cleanup

* refactor

* refactor

* pass tests

* initial setup to build as a nim package

* initial setup

* update build setup

* update build setup

* update build setup

* add nim ffi and genffi build task

* add nim ffi and genffi build task

* add nim ffi and genffi build task

* update init to remove redundant pointers

* update init to remove redundant pointers

* update init to remove redundant pointers

* update init to remove redundant pointers

* save mpack

* save mpack

* update ffi

* update ffi

* add example ffi test

* add example ffi test

* updates

* fix tests

* adding git ignore

* rename

* run testament

* fix stuffs

* fix stuffs

* fix stuffs

* update build

* update build
This commit is contained in:
Jaremy Creechley 2024-01-17 22:04:47 +02:00 committed by GitHub
parent dc7e8f13de
commit 24930233f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1617 additions and 43 deletions

7
.gitignore vendored
View File

@ -1,4 +1,8 @@
node_modules/
*
!*/
!*.*
nim.cfg
# Added by cargo
@ -16,3 +20,6 @@ test/circuits/artifacts
out.log
src/circuit_tests/artifacts/*
!src/circuit_tests/artifacts/.keep
codex_storage_proofs
test/tffi
node_modules

View File

@ -6,6 +6,10 @@ edition = "2021"
[profile.dev]
opt-level = 3
[profile.release]
strip = true
opt-level = 2
[lib]
crate-type = [
"staticlib", # Ensure it gets compiled as a (static) C library
@ -37,3 +41,4 @@ serde_json = "1.0.94"
num-traits = "0.2.15"
ark-relations = { version = "0.4.0", features = ["std", "tracing-subscriber"] }
rs-poseidon = {git = "https://github.com/status-im/rs-poseidon" }
rmpv = "1.0.1"

18
build.nims Normal file
View File

@ -0,0 +1,18 @@
import std/os
task genffi, "update the nim ffi bindings":
exec "cargo install nbindgen"
exec "nbindgen --crate codex-storage-proofs --output codex_proofs_ffi.nim"
task compileCircuits, "compile test circuits":
exec "npm i"
exec "circom src/circuit_tests/poseidon-digest-test.circom --r1cs --wasm -o src/circuit_tests/artifacts"
exec "circom src/circuit_tests/poseidon-hash-test.circom --r1cs --wasm -o src/circuit_tests/artifacts"
exec "circom src/circuit_tests/storer-test.circom --r1cs --wasm -o src/circuit_tests/artifacts"
task tests, "run unit tests":
let storerR1cs = fileExists "src/circuit_tests/artifacts/storer-test.r1cs"
let storerWasm = fileExists "src/circuit_tests/artifacts/storer-test_js/storer-test.wasm"
if not storerR1cs or not storerWasm:
compileCircuitsTask()
exec "nim c -r tests/tffi.nim"

57
codex_proofs_ffi.nim Normal file
View File

@ -0,0 +1,57 @@
const EXT_ID_U256_BE* = 51
const EXT_ID_U256_LE* = 50
type StorageProofs* {.incompleteStruct.} = object
type Buffer* = object
data: ptr uint8
len: uint
type ProofCtx* = object
proof: Buffer
public_inputs: Buffer
## # Safety
#
# Use on a valid pointer to ProofCtx or panics
proc free_proof_ctx*(ctx: ptr ProofCtx) {.importc: "free_proof_ctx".}
## # Safety
#
# Use on a valid pointer to StorageProofs or panics
proc free_prover*(prover: ptr StorageProofs) {.importc: "free_prover".}
## # Safety
#
# Construct a StorageProofs object
proc init_storage_proofs*(r1cs: Buffer,
wasm: Buffer,
zkey: ptr Buffer): (ptr StorageProofs) {.importc: "init_storage_proofs".}
## # Safety
#
# Use after constructing a StorageProofs object with init
proc prove*(prover_ptr: ptr StorageProofs,
chunks: ptr Buffer,
siblings: ptr Buffer,
hashes: ptr Buffer,
path: ptr int32,
path_len: uint,
pubkey: ptr Buffer,
root: ptr Buffer,
salt: ptr Buffer): (ptr ProofCtx) {.importc: "prove".}
## # Safety
#
# Use after constructing a StorageProofs object with init
proc prove_mpack_ext*(prover_ptr: ptr StorageProofs,
args: ptr Buffer): (ptr ProofCtx) {.importc: "prove_mpack_ext".}
## # Safety
#
# Should be called on a valid proof and public inputs previously generated by prove
proc verify*(prover_ptr: ptr StorageProofs,
proof: ptr Buffer,
public_inputs: ptr Buffer): bool {.importc: "verify".}

38
codex_storage_proofs.nim Normal file
View File

@ -0,0 +1,38 @@
import std/os
import std/strutils
import std/macros
const
currentDir = currentSourcePath().parentDir()
libDir* = currentDir/"target"/"release"
libPath* = libDir/"libcodex_storage_proofs.a"
static:
let cmd = "cargo build --release"
warning "\nBuilding codex-storage-proofs: " & cmd
let (output, exitCode) = gorgeEx cmd
for ln in output.splitLines():
warning("cargo> " & ln)
if exitCode != 0:
raise (ref Defect)(msg: "Failed to build codex-storage-proofs")
{.passl: "-lcodex_storage_proofs" & " -L" & libDir.}
include codex_proofs_ffi
proc len*(buff: Buffer): int =
buff.len.int
template unsafeBufferPath*(path: var string): Buffer =
assert path.len() > 0
Buffer(data: cast[ptr uint8](path.cstring),
len: path.len().uint)
template unsafeBufferFromFile*(path: string): Buffer =
assert path.len() > 0
let entireFile = readFile(path)
Buffer(data: cast[ptr uint8](entireFile.cstring),
len: entireFile.len().uint)

View File

@ -0,0 +1,11 @@
version = "0.1.0"
author = "Jaremy Creechley"
description = "Codex Storage Proofs runner for Rust Circom library"
license = "MIT"
srcDir = "."
# Dependencies
requires "nim >= 1.6.14"
include "build.nims"

1
config.nims Normal file
View File

@ -0,0 +1 @@
include "build.nims"

BIN
proof_test.mpack Normal file

Binary file not shown.

View File

@ -130,24 +130,24 @@ mod test {
];
let root = treehash(hashes.as_slice());
let proof_bytes = &mut Vec::new();
let public_inputs_bytes = &mut Vec::new();
// let proof_bytes = &mut Vec::new();
// let public_inputs_bytes = &mut Vec::new();
prover
.prove(
chunks.as_slice(),
siblings,
hashes.as_slice(),
path.as_slice(),
root,
root, // random salt - block hash
proof_bytes,
public_inputs_bytes,
)
.unwrap();
// prover
// .prove(
// chunks.as_slice(),
// siblings,
// hashes.as_slice(),
// path.as_slice(),
// root,
// root, // random salt - block hash
// proof_bytes,
// public_inputs_bytes,
// )
// .unwrap();
assert!(prover
.verify(proof_bytes.as_slice(), public_inputs_bytes.as_slice())
.is_ok());
// assert!(prover
// .verify(proof_bytes.as_slice(), public_inputs_bytes.as_slice())
// .is_ok());
}
}

View File

@ -36,33 +36,26 @@ impl ProofCtx {
///
/// Construct a StorageProofs object
#[no_mangle]
pub unsafe extern "C" fn init(
r1cs: *const &Buffer,
wasm: *const &Buffer,
zkey: *const &Buffer,
pub unsafe extern "C" fn init_storage_proofs(
r1cs: Buffer,
wasm: Buffer,
zkey: *const Buffer,
) -> *mut StorageProofs {
let r1cs = {
if r1cs.is_null() {
return std::ptr::null_mut();
}
let slice = std::slice::from_raw_parts((*r1cs).data, (*r1cs).len);
str::from_utf8(slice).unwrap().to_string()
let slice = std::slice::from_raw_parts((r1cs).data, (r1cs).len);
str::from_utf8(slice).unwrap().to_string().to_owned()
};
let wasm = {
if wasm.is_null() {
return std::ptr::null_mut();
}
let slice = std::slice::from_raw_parts((*wasm).data, (*wasm).len);
str::from_utf8(slice).unwrap().to_string()
let slice = std::slice::from_raw_parts((wasm).data, (wasm).len);
str::from_utf8(slice).unwrap().to_string().to_owned()
};
let zkey = {
if !zkey.is_null() {
let slice = std::slice::from_raw_parts((*zkey).data, (*zkey).len);
Some(str::from_utf8(slice).unwrap().to_string())
Some(str::from_utf8(slice).unwrap().to_string().to_owned())
} else {
None
}
@ -93,6 +86,10 @@ pub unsafe extern "C" fn prove(
.map(|c| U256::try_from_le_slice(c).unwrap())
.collect::<Vec<U256>>()
};
// println!("prove:args: {}", "chunks");
// for n in chunks {
// println!("\t{}", n);
// }
let siblings = {
let slice = std::slice::from_raw_parts((*siblings).data, (*siblings).len);
@ -115,7 +112,7 @@ pub unsafe extern "C" fn prove(
slice.to_vec()
};
let pubkey =
let _pubkey =
U256::try_from_le_slice(std::slice::from_raw_parts((*pubkey).data, (*pubkey).len)).unwrap();
let root =
@ -144,6 +141,31 @@ pub unsafe extern "C" fn prove(
Box::into_raw(Box::new(ProofCtx::new(proof_bytes, public_inputs_bytes)))
}
/// # Safety
///
/// Use after constructing a StorageProofs object with init
#[no_mangle]
pub unsafe extern "C" fn prove_mpack_ext(
prover_ptr: *mut StorageProofs,
args: *const Buffer,
) -> *mut ProofCtx {
let inputs = std::slice::from_raw_parts((*args).data, (*args).len);
let proof_bytes = &mut Vec::new();
let public_inputs_bytes = &mut Vec::new();
let mut _prover = &mut *prover_ptr;
_prover
.prove_mpack(
inputs,
proof_bytes,
public_inputs_bytes,
)
.unwrap();
Box::into_raw(Box::new(ProofCtx::new(proof_bytes, public_inputs_bytes)))
}
#[no_mangle]
/// # Safety
///
@ -185,15 +207,205 @@ pub unsafe extern "C" fn free_proof_ctx(ctx: *mut ProofCtx) {
#[cfg(test)]
mod tests {
use ark_std::rand::{distributions::Alphanumeric, rngs::ThreadRng, Rng};
use std::fs::File;
use std::io::prelude::*;
use ark_std::rand::{distributions::Alphanumeric, rngs::StdRng, Rng, SeedableRng};
use rs_poseidon::poseidon::hash;
use ruint::aliases::U256;
use crate::{
circuit_tests::utils::{digest, treehash},
circuit_tests::utils::{digest, treehash}, storage_proofs::EXT_ID_U256_LE, ffi::prove_mpack_ext
};
use super::{init, prove, Buffer};
use super::{init_storage_proofs, prove, Buffer};
use rmpv::Value;
use rmpv::encode::write_value;
use rmpv::decode::read_value;
#[test]
fn test_mpack() {
let mut buf = Vec::new();
let _val = Value::from("le message");
// example of serializing the random chunk data
// we build them up in mpack Value enums
let data = (0..4)
.map(|_| {
let rng = StdRng::seed_from_u64(42);
let preimages: Vec<U256> = rng
.sample_iter(Alphanumeric)
.take(256)
.map(|c| U256::from(c))
.collect();
let hash = digest(&preimages, Some(16));
(preimages, hash)
})
.collect::<Vec<(Vec<U256>, U256)>>();
let chunks = data.iter()
.map(|c| {
let x = c.0.iter()
.map(|c| Value::Ext(EXT_ID_U256_LE, c.to_le_bytes_vec()))
.collect::<Vec<Value>>();
Value::Array(x)
})
.collect::<Vec<Value>>();
let chunks = Value::Array(chunks);
let data = Value::Map(vec![(Value::String("chunks".into()), chunks.clone() )]);
println!("Debug: chunks: {:?}", chunks[0][0]);
// Serialize the value types to an array pointer
write_value(&mut buf, &data).unwrap();
let mut rd: &[u8] = &buf[..];
let args = read_value(&mut rd).unwrap();
assert!(Value::is_map(&args));
assert!(Value::is_array(&args["chunks"]));
assert!(Value::is_array(&args["chunks"][0]));
let mut arg_chunks: Vec<Vec<U256>> = Vec::new();
// deserialize the data back into u256's
// instead of this, we'll want to use `builder.push_input`
args["chunks"]
.as_array()
.unwrap()
.iter()
.for_each(|c| {
if let Some(x) = c.as_array() {
let mut vals: Vec<U256> = Vec::new();
x.iter().for_each(|n| {
let b = n.as_ext().unwrap();
// ensure it's a LE uin256 which we've set as ext 50
assert_eq!(b.0, 50);
vals.push(U256::try_from_le_slice(b.1).unwrap());
// TODO: change to use
// builder.push_input("hashes", *c)
});
arg_chunks.push(vals);
} else {
panic!("unhandled type!");
}
});
assert_eq!(arg_chunks.len(), 4);
assert_eq!(arg_chunks[0].len(), 256);
}
fn u256_to_mpack(n: &U256) -> Value {
Value::Ext(EXT_ID_U256_LE, n.to_le_bytes_vec())
}
#[test]
fn test_storer_ffi_mpack() {
let mut buf = Vec::new();
let _val = Value::from("le message");
// example of serializing the random chunk data
// we build them up in mpack Value enums
let data = (0..4)
.map(|_| {
let rng = StdRng::seed_from_u64(42);
let preimages: Vec<U256> = rng
.sample_iter(Alphanumeric)
.take(256)
.map(|c| U256::from(c))
.collect();
let hash = digest(&preimages, Some(16));
(preimages, hash)
})
.collect::<Vec<(Vec<U256>, U256)>>();
let chunks = data.iter()
.map(|c| {
let x = c.0.iter().map(u256_to_mpack).collect::<Vec<Value>>();
Value::Array(x)
})
.collect::<Vec<Value>>();
let chunks = Value::Array(chunks);
println!("Debug: chunks: {:?}", chunks[0][0]);
let hashes: Vec<U256> = data.iter().map(|c| c.1).collect();
let hashes_mpk = Value::Array(hashes.iter().map(u256_to_mpack).collect());
let path = [0, 1, 2, 3];
let path_mpk = Value::Array(path.iter().map(|i| rmpv::Value::from(*i)).collect());
let parent_hash_l = hash(&[hashes[0], hashes[1]]);
let parent_hash_r = hash(&[hashes[2], hashes[3]]);
let sibling_hashes = &[
hashes[1],
parent_hash_r,
hashes[0],
parent_hash_r,
hashes[3],
parent_hash_l,
hashes[2],
parent_hash_l,
];
let siblings_mpk: Value = Value::Array(sibling_hashes
.iter()
.map(u256_to_mpack)
.collect::<Vec<Value>>());
let root = treehash(hashes.as_slice());
// let root_bytes: [u8; U256::BYTES] = root.to_le_bytes();
let root_mpk = u256_to_mpack(&root);
// Serialize the value types to an array pointer
let mpk_data = Value::Map(vec![
(Value::String("chunks".into()), chunks.clone() ),
(Value::String("siblings".into()), siblings_mpk.clone() ),
(Value::String("hashes".into()), hashes_mpk.clone() ),
(Value::String("path".into()), path_mpk.clone() ),
(Value::String("root".into()), root_mpk.clone() ),
(Value::String("salt".into()), root_mpk.clone() ),
]);
write_value(&mut buf, &mpk_data ).unwrap();
let rd: &[u8] = &buf[..];
let mut file = File::create("proof_test.mpack").unwrap();
file.write_all(rd).unwrap();
let args_buff = Buffer {
data: rd.as_ptr() as *const u8,
len: rd.len(),
};
let r1cs_path = "src/circuit_tests/artifacts/storer-test.r1cs";
let wasm_path = "src/circuit_tests/artifacts/storer-test_js/storer-test.wasm";
let r1cs = Buffer {
data: r1cs_path.as_ptr(),
len: r1cs_path.len(),
};
let wasm = Buffer {
data: wasm_path.as_ptr(),
len: wasm_path.len(),
};
let prover_ptr = unsafe { init_storage_proofs(r1cs, wasm, std::ptr::null()) };
let prove_ctx: *mut crate::ffi::ProofCtx = unsafe {
prove_mpack_ext(
prover_ptr,
&args_buff as *const Buffer,
)
};
assert!(prove_ctx.is_null() == false);
}
#[test]
fn test_storer_ffi() {
@ -201,7 +413,7 @@ mod tests {
// and hash is the hash of each vector generated using the digest function
let data = (0..4)
.map(|_| {
let rng = ThreadRng::default();
let rng = StdRng::seed_from_u64(42);
let preimages: Vec<U256> = rng
.sample_iter(Alphanumeric)
.take(256)
@ -272,18 +484,18 @@ mod tests {
let r1cs_path = "src/circuit_tests/artifacts/storer-test.r1cs";
let wasm_path = "src/circuit_tests/artifacts/storer-test_js/storer-test.wasm";
let r1cs = &Buffer {
let r1cs = Buffer {
data: r1cs_path.as_ptr(),
len: r1cs_path.len(),
};
let wasm = &Buffer {
let wasm = Buffer {
data: wasm_path.as_ptr(),
len: wasm_path.len(),
};
let prover_ptr = unsafe { init(&r1cs, &wasm, std::ptr::null()) };
let prove_ctx = unsafe {
let prover_ptr = unsafe { init_storage_proofs(r1cs, wasm, std::ptr::null()) };
let prove_ctx: *mut crate::ffi::ProofCtx = unsafe {
prove(
prover_ptr,
&chunks_buff as *const Buffer,

View File

@ -1,7 +1,7 @@
use std::fs::File;
use ark_bn254::{Bn254, Fr};
use ark_circom::{read_zkey, CircomBuilder, CircomConfig};
use ark_circom::{read_zkey, CircomBuilder, CircomConfig, CircomCircuit};
use ark_groth16::{
create_random_proof as prove, generate_random_parameters, prepare_verifying_key, verify_proof,
Proof, ProvingKey,
@ -10,6 +10,15 @@ use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read};
use ark_std::rand::rngs::ThreadRng;
use ruint::aliases::U256;
use rmpv;
use rmpv::decode::read_value;
type Params256Ty = ark_ec::bn::Bn<ark_bn254::Parameters>;
pub const EXT_ID_U256_LE: i8 = 50;
pub const EXT_ID_U256_BE: i8 = 51;
#[derive(Debug, Clone)]
pub struct StorageProofs {
builder: CircomBuilder<Bn254>,
@ -41,6 +50,36 @@ impl StorageProofs {
}
}
pub fn prove_mpack(
&mut self,
inputs: &[u8],
proof_bytes: &mut Vec<u8>,
public_inputs_bytes: &mut Vec<u8>,
) -> Result<(), String> {
let mut builder: CircomBuilder<Params256Ty> = self.builder.clone();
parse_mpack_args(&mut builder, inputs)?;
let circuit: CircomCircuit<Params256Ty> = builder.build()
.map_err(|e| e.to_string())?;
let inputs = circuit
.get_public_inputs()
.ok_or("Unable to get public inputs!")?;
let proof =
prove(circuit, &self.params, &mut self.rng)
.map_err(|e| e.to_string())?;
proof
.serialize(proof_bytes)
.map_err(|e| e.to_string())?;
inputs
.serialize(public_inputs_bytes)
.map_err(|e| e.to_string())?;
Ok(())
}
pub fn prove(
&mut self,
chunks: &[U256],
@ -96,3 +135,95 @@ impl StorageProofs {
Ok(())
}
}
fn decode_number(val: &rmpv::Value) -> Result<U256, String> {
match val {
rmpv::Value::Ext(id, val) => {
match *id {
EXT_ID_U256_LE =>
match U256::try_from_le_slice(val) {
Some(i) => Ok(i),
None => Err("error parsing 256".to_string()),
}
num => return Err(format!("unhandled ext id {}", num)),
}
},
rmpv::Value::Integer(val) => {
if let Some(val) = val.as_u64() {
return Ok(U256::from(val));
} else if let Some(val) = val.as_i64() {
return Ok(U256::from(val));
} else {
return Err("unexpected integer kind".to_string());
}
}
_ => return Err("expected ext mpack kind or integer".to_string()),
}
}
fn parse_mpack_arrays(
builder: &mut CircomBuilder<Params256Ty>,
name: &str,
array: &Vec<rmpv::Value>
) -> Result<(), String> {
println!("deserde: array: {} size: {}", name, array.len());
if array.len() > 0 && array[0].is_array() {
println!("deserde: arrayOfArrays: {}", name);
for element in array {
match element .as_array() {
Some(element ) => {
parse_mpack_arrays(builder, name, element)?;
},
_ => {
print!("error expected array: {}", name);
return Err("expected inner array of u256".to_string())
},
}
}
} else {
println!("deserde: name: {}", name);
for val in array {
let n = decode_number(val)?;
println!("\t{}", n);
builder.push_input(name, n);
}
println!("done: name: {}", name);
}
Ok(())
}
fn parse_mpack_args(
builder: &mut CircomBuilder<Params256Ty>,
mut inputs: &[u8]
) -> Result<(), String> {
let values: rmpv::Value = read_value(&mut inputs).map_err(|e| e.to_string())?;
let args: &Vec<(rmpv::Value, rmpv::Value)> = match values.as_map() {
Some(args) => args,
None => return Err("args must be a map of string to arrays".to_string()),
};
for (key, val) in args {
let name = match key.as_str() {
Some(n) => n,
None => return Err(format!("expected string value")),
};
match val {
// add a (name, Vec<u256>) or (name, Vev<Vec<u256>>) arrays
rmpv::Value::Array(vals) => {
parse_mpack_arrays(builder, name, vals)?;
},
// directly add a (name,u256) arg pair
rmpv::Value::Ext(_, _) => {
let n = decode_number(val)?;
println!("deserde: name: {} u256: {}", name, n);
builder.push_input(name, n);
},
_ => return Err("unhandled argument kind".to_string()),
}
}
println!("parse_mpack_args DONE!");
Ok(())
}

1
tests/config.nims Normal file
View File

@ -0,0 +1 @@
--path:"../"

BIN
tests/proof_test.mpack Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

33
tests/tffi.nim Normal file
View File

@ -0,0 +1,33 @@
import std/os
import unittest2
import codex_storage_proofs
suite "storage proofs ffi":
test "basic ffi circuit":
var
r1csPath = "src/circuit_tests/artifacts/storer-test.r1cs".absolutePath()
wasmPath = "src/circuit_tests/artifacts/storer-test_js/storer-test.wasm".absolutePath()
if not r1csPath.fileExists():
raise newException(ValueError, "missing expected r1cs file: " & r1csPath)
if not wasmPath.fileExists():
raise newException(ValueError, "missing expected wasm file: " & wasmPath)
let
r1cs_buff = unsafeBufferPath(r1csPath)
wasm_buff = unsafeBufferPath(wasmPath)
let storage_ctx = init_storage_proofs(r1cs_buff, wasm_buff, nil)
echo "storage_ctx: ", storage_ctx.repr
check storage_ctx != nil
var
mpack_arg_path = "tests/proof_test.mpack"
proofBuff = unsafeBufferFromFile(mpack_arg_path)
echo "proofArgs:size: ", proofBuff.len()
let res = prove_mpack_ext(storage_ctx, addr proofBuff)
echo "result: ", res.repr
check res != nil