Skip to content

Commit

Permalink
Implement persistent commitments (argumentcomputer#543)
Browse files Browse the repository at this point in the history
* meta commit and hide

* progress on fetch

* resolve lifetime issue

* finish fetch

* improve Commitment implementations; open meta command that prints data

* add pub(crate)

* enclose non-wasm code in particular modules

* improved remarks on private data

* better generalizations in lurk_proof

* safer extraction of FieldData

* cleaner user feedback on fetch

* remove unnecessary allow(dead_code) flag

* clean up paths.rs

* review suggestions + revamp

* encoding proof claim as Lurk data

* clean up

* document field_data

* add tests for field_data

* change constructor names on Enum2 for a better POC test

* fix docstring for LurkProofMeta
  • Loading branch information
arthurpaulino authored Jul 20, 2023
1 parent 4116c32 commit 4a92ae0
Show file tree
Hide file tree
Showing 11 changed files with 625 additions and 257 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ rand_xorshift = "0.3.0"
rayon = "1.7.0"
rustyline-derive = "0.8.0"
serde = { workspace = true, features = ["derive"] }
serde_bytes = "0.11.12"
serde_json = { workspace = true }
serde_repr = "0.1.10"
tap = "1.0.1"
Expand Down
2 changes: 1 addition & 1 deletion fcomm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ impl<F: LurkField + Serialize + DeserializeOwned> LurkPtr<F> {
LurkPtr::ZStorePtr(z_store_ptr) => {
let z_store = &z_store_ptr.z_store;
let z_ptr = z_store_ptr.z_ptr;
s.intern_z_expr_ptr(z_ptr, z_store)
s.intern_z_expr_ptr(&z_ptr, z_store)
.expect("failed to intern z_ptr")
}
}
Expand Down
50 changes: 50 additions & 0 deletions src/cli/commitment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use lurk::{field::LurkField, z_ptr::ZExprPtr, z_store::ZStore};
use serde::{Deserialize, Serialize};

use super::field_data::HasFieldModulus;

/// Holds data for commitments.
///
/// **Warning**: holds private data. The `ZStore` contains the secret used to
/// hide the original payload.
#[derive(Serialize, Deserialize)]
pub struct Commitment<F: LurkField> {
pub(crate) hidden: ZExprPtr<F>,
pub(crate) zstore: ZStore<F>,
}

impl<F: LurkField> HasFieldModulus for Commitment<F> {
fn field_modulus() -> String {
F::MODULUS.to_owned()
}
}

#[cfg(not(target_arch = "wasm32"))]
mod non_wasm {
use anyhow::Result;
use lurk::{field::LurkField, ptr::Ptr, store::Store, z_store::ZStore};
use serde::Serialize;

use crate::cli::{field_data::non_wasm::dump, paths::non_wasm::commitment_path};

use super::Commitment;

impl<F: LurkField> Commitment<F> {
pub fn new(secret: F, payload: Ptr<F>, store: &mut Store<F>) -> Result<Self> {
let hidden = store.hide(secret, payload);
let mut zstore = Some(ZStore::<F>::default());
let hidden = store.get_z_expr(&hidden, &mut zstore)?.0;
Ok(Self {
hidden,
zstore: zstore.unwrap(),
})
}
}

impl<F: LurkField + Serialize> Commitment<F> {
#[inline]
pub fn persist(self, hash: &str) -> Result<()> {
dump(self, commitment_path(hash))
}
}
}
169 changes: 169 additions & 0 deletions src/cli/field_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
use anyhow::Result;

use serde::{de::DeserializeOwned, Deserialize, Serialize};

// This module implements a 2-step serde protocol for data that is parametrized
// on an arithmetic field in order to be properly deserialized.
//
// First, we serialize it to a vector of bytes. Then, we wrap the vector with a
// struct that contains the field modulus, which, in turn, is serialized to the
// final vector of bytes.
//
// When deserializing, we unwrap the vector of bytes and double check the field
// modulus for consistency. If everything goes well, we further unwrap the second
// vector of bytes.

pub(crate) trait HasFieldModulus {
fn field_modulus() -> String;
}

#[allow(dead_code)]
pub(crate) fn ser<T: Serialize + HasFieldModulus>(t: T) -> Result<Vec<u8>> {
Ok(bincode::serialize(&FieldData(t))?)
}

#[allow(dead_code)]
pub(crate) fn de<T: DeserializeOwned + HasFieldModulus>(bytes: &[u8]) -> Result<T> {
let FieldData(data) = bincode::deserialize(bytes)?;
Ok(data)
}

#[cfg(not(target_arch = "wasm32"))]
pub mod non_wasm {
use super::{de, ser, HasFieldModulus};
use anyhow::Result;
use serde::{de::DeserializeOwned, Serialize};
use std::path::PathBuf;

pub(crate) fn dump<T: Serialize + HasFieldModulus>(t: T, path: PathBuf) -> Result<()> {
Ok(std::fs::write(path, ser(t)?)?)
}

pub(crate) fn load<T: DeserializeOwned + HasFieldModulus>(path: PathBuf) -> Result<T> {
de(&std::fs::read(path)?)
}
}

#[derive(Debug, PartialEq, Eq)]
struct FieldData<T>(T);

#[derive(Deserialize, Serialize)]
struct FieldDataWrap {
field_modulus: String,
#[serde(with = "serde_bytes")]
bytes: Vec<u8>,
}

impl<'de, T: DeserializeOwned + HasFieldModulus> Deserialize<'de> for FieldData<T> {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let fdw = FieldDataWrap::deserialize(deserializer)?;
if fdw.field_modulus != T::field_modulus() {
return Err(serde::de::Error::custom("Field mismatch"));
};
let t: T = bincode::deserialize(&fdw.bytes).map_err(serde::de::Error::custom)?;
Ok(FieldData(t))
}
}

impl<T: Serialize + HasFieldModulus> Serialize for FieldData<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let fdw = FieldDataWrap {
field_modulus: T::field_modulus(),
bytes: bincode::serialize(&self.0).map_err(serde::ser::Error::custom)?,
};
fdw.serialize(serializer)
}
}

#[cfg(test)]
mod tests {
use ff::Field;
use lurk::field::LurkField;
use pasta_curves::Fq;
use serde::{Deserialize, Serialize};

use super::{de, ser, HasFieldModulus};

#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
struct Struct<F: LurkField> {
str: String,
int: i32,
ff: F,
}

#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
enum Enum1<F: LurkField> {
CaseStr(String),
CaseInt(i32),
CaseFF(F),
}

impl<F: LurkField> HasFieldModulus for Struct<F> {
fn field_modulus() -> String {
F::MODULUS.to_string()
}
}

impl<F: LurkField> HasFieldModulus for Enum1<F> {
fn field_modulus() -> String {
F::MODULUS.to_string()
}
}

#[test]
fn struct_roundtrips() {
let s = Struct {
str: "hi".into(),
int: 42,
ff: Fq::double(&Fq::ONE),
};
assert_eq!(s, de(&ser(s.clone()).unwrap()).unwrap())
}

#[test]
fn enum1_roundtrips() {
let e11 = Enum1::CaseStr("bye".into());
let e12 = Enum1::CaseInt(11);
let e13 = Enum1::CaseFF(Fq::double(&Fq::double(&Fq::ONE)));
for e in [e11, e12, e13] {
assert_eq!(e, de(&ser(e.clone()).unwrap()).unwrap());
}
}

/// An enum can be deserialized to another, if the enum constructor has the
/// same index and uses the same inner data
#[test]
fn stable_enum() {
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
enum Enum2<F: LurkField> {
CaseStr2(String),
CaseInt2(i32),
CaseFF2(F),
Foo,
}

impl<F: LurkField> HasFieldModulus for Enum2<F> {
fn field_modulus() -> String {
F::MODULUS.to_string()
}
}
let e11 = Enum1::CaseStr("bye".into());
let e12 = Enum1::CaseInt(11);
let e13 = Enum1::CaseFF(Fq::double(&Fq::double(&Fq::ONE)));

let e21 = Enum2::CaseStr2("bye".into());
let e22 = Enum2::CaseInt2(11);
let e23 = Enum2::CaseFF2(Fq::double(&Fq::double(&Fq::ONE)));

for (e1, e2) in [(e11, e21), (e12, e22), (e13, e23)] {
assert_eq!(e2.clone(), de(&ser(e1.clone()).unwrap()).unwrap());
assert_eq!(e1, de(&ser(e2).unwrap()).unwrap());
}
}
}
Loading

0 comments on commit 4a92ae0

Please sign in to comment.