Skip to content

Commit

Permalink
feat: ffi
Browse files Browse the repository at this point in the history
  • Loading branch information
rymnc committed Dec 20, 2023
1 parent 41d9250 commit 21a7f5c
Show file tree
Hide file tree
Showing 6 changed files with 367 additions and 170 deletions.
8 changes: 8 additions & 0 deletions .idea/toolchains.xml

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

1 change: 1 addition & 0 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ edition = "2021"
[lib]
name = "erc_5564_bn254"
path = "src/lib.rs"
crate-type = ["staticlib", "rlib"]

[features]
ffi = []
default = []

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand All @@ -17,6 +22,7 @@ num-traits = "0.2.15"
ark-ff = "0.4.1"
ark-bn254 = "0.4.0"
ark-ec = "0.4.1"
ark-serialize = "0.4.1"

[dev-dependencies]
serde_json = "1.0.96"
Expand Down
178 changes: 178 additions & 0 deletions src/ffi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
use std::ops::Add;
use std::str::FromStr;
use crate::stealth_commitments::{derive_public_key, generate_random_fr, generate_stealth_commitment, generate_stealth_private_key, random_keypair};
use ark_bn254::{Fq, Fr, G1Projective, G1Affine};
use ark_ff::{BigInt, BigInteger, Field, PrimeField, ToConstraintField};
use num_traits::{Zero};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};

#[repr(C)]
#[derive(Debug)]
pub struct CFr([u8; 32]);

impl Add for CFr {
type Output = Self;

fn add(self, rhs: Self) -> Self::Output {
CFr::from(Fr::from(self).add(Fr::from(rhs)))
}
}

impl Zero for CFr {
fn zero() -> Self {
CFr::from(Fr::from(0))
}

fn is_zero(&self) -> bool {
Fr::is_zero(&Fr::from(self))
}
}

impl From<Fr> for CFr {
fn from(value: Fr) -> Self {
let mut buf = Vec::new();
value.serialize_compressed(&mut buf).unwrap();
let mut res = [0u8; 32];
res.copy_from_slice(&buf);
CFr(res)
}
}

impl From<CFr> for Fr {
fn from(value: CFr) -> Self {
Fr::deserialize_compressed(value.0.as_slice()).unwrap()
}
}

impl From<&CFr> for Fr {
fn from(value: &CFr) -> Self {
Fr::deserialize_compressed(value.0.as_slice()).unwrap()
}
}

#[repr(C)]
#[derive(Debug, PartialOrd, PartialEq)]
pub struct CG1Projective([u8; 32]);

impl From<G1Projective> for CG1Projective {
fn from(value: G1Projective) -> Self {
let mut buf = Vec::new();
value.serialize_compressed(&mut buf).unwrap();
let mut result = [0u8; 32];
result.copy_from_slice(&buf);
CG1Projective(result)
}
}
impl From<CG1Projective> for G1Projective {
fn from(value: CG1Projective) -> Self {
G1Projective::deserialize_compressed(value.0.as_slice()).expect("TODO: panic message")
}
}

#[repr(C)]
#[derive(Debug)]
pub struct CKeyPair {
private_key: CFr,
public_key: CG1Projective
}

#[repr(C)]
#[derive(Debug)]
pub struct CStealthCommitment {
stealth_commitment: CG1Projective,
view_tag: u64
}

impl From<(G1Projective, u64)> for CStealthCommitment {
fn from(value: (G1Projective, u64)) -> Self {
CStealthCommitment {
stealth_commitment: CG1Projective::from(value.0),
view_tag: value.1
}
}
}

impl Into<(G1Projective, u64)> for CStealthCommitment {
fn into(self) -> (G1Projective, u64) {
(self.stealth_commitment.into(), self.view_tag)
}
}

#[no_mangle]
pub extern "C" fn ffi_generate_random_fr() -> CFr {
CFr::from(generate_random_fr())
}

#[no_mangle]
pub extern "C" fn ffi_derive_public_key(private_key: CFr) -> CG1Projective {
CG1Projective::from(derive_public_key(private_key.into()))
}


pub extern "C" fn ffi_random_keypair() -> CKeyPair {
let (private_key, public_key) = random_keypair();
CKeyPair {
private_key: CFr::from(private_key),
public_key: CG1Projective::from(public_key)
}
}

pub extern "C" fn ffi_generate_stealth_commitment(viewing_public_key: CG1Projective, spending_public_key: CG1Projective, ephemeral_private_key: CFr) -> CStealthCommitment {
CStealthCommitment::from(generate_stealth_commitment(viewing_public_key.into(), spending_public_key.into(), ephemeral_private_key.into()))
}

pub extern "C" fn ffi_generate_stealth_private_key(ephemeral_public_key: CG1Projective, spending_key: CFr, viewing_key: CFr, view_tag: u64) -> CFr {
match generate_stealth_private_key(ephemeral_public_key.into(), spending_key.into(), viewing_key.into(), view_tag) {
Some(v) => CFr::from(v),
None => CFr::zero()
}
}

#[cfg(test)]
mod tests {
use ark_ff::ToConstraintField;
use super::*;
use crate::stealth_commitments::{derive_public_key};
use ark_ec::{AffineRepr, CurveGroup};


#[test]
fn test_ffi_generate_random_fr() {
let _ = ffi_generate_random_fr();
}

#[test]
fn test_ffi_random_keypair() {
let keypair = ffi_random_keypair();
let private_key = Fr::from(keypair.private_key);
let public_key = G1Projective::from(keypair.public_key);
assert!(public_key.into_affine().is_on_curve());
// Check the derived key matches the one generated from original key
assert_eq!(derive_public_key(private_key), public_key);
}

#[test]
fn test_ffi_generate_stealth_commitment() {
let spending_key = ffi_random_keypair();
let viewing_key = ffi_random_keypair();

// generate ephemeral keypair
let ephemeral_key = ffi_random_keypair();

let stealth_commitment_payload = ffi_generate_stealth_commitment(
viewing_key.public_key,
spending_key.public_key,
ephemeral_key.private_key,
);

let stealth_private_key =
ffi_generate_stealth_private_key(ephemeral_key.public_key, viewing_key.private_key, spending_key.private_key, stealth_commitment_payload.view_tag);

if stealth_private_key.is_zero() {
panic!("View tags did not match");
}

let derived_commitment = ffi_derive_public_key(stealth_private_key);
assert_eq!(derived_commitment, stealth_commitment_payload.stealth_commitment);
}
}
173 changes: 3 additions & 170 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,171 +1,4 @@
use ark_bn254::g1::{G1_GENERATOR_X, G1_GENERATOR_Y};
use ark_bn254::{Fr, G1Affine, G1Projective};
use ark_ff::UniformRand;
use ark_std::rand::rngs::OsRng;
use rln::hashers::{hash_to_field, poseidon_hash};
mod stealth_commitments;

fn derive_public_key(private_key: Fr) -> G1Projective {
let g1_generator_affine = G1Affine::new_unchecked(G1_GENERATOR_X, G1_GENERATOR_Y);
(G1Projective::from(g1_generator_affine)) * private_key
}

pub fn random_keypair() -> (Fr, G1Projective) {
let private_key = generate_random_fr();
let public_key = derive_public_key(private_key);
(private_key, public_key)
}

pub fn generate_random_fr() -> Fr {
let mut rng = OsRng;
Fr::rand(&mut rng)
}

pub fn hash_to_fr(input: &[u8]) -> Fr {
poseidon_hash(&[hash_to_field(input)])
}

pub fn compute_shared_point(private_key: Fr, other_public_key: G1Projective) -> G1Projective {
other_public_key * private_key
}

pub fn generate_stealth_commitment(
viewing_public_key: G1Projective,
spending_public_key: G1Projective,
ephemeral_private_key: Fr,
) -> (G1Projective, u64) {
let q = compute_shared_point(ephemeral_private_key, viewing_public_key);
let inputs = q.to_string();
let q_hashed = hash_to_fr(inputs.as_bytes());

let q_hashed_in_g1 = derive_public_key(q_hashed);
let view_tag = q_hashed.0 .0[0];
(q_hashed_in_g1 + spending_public_key, view_tag)
}

pub fn generate_stealth_private_key(
ephemeral_public_key: G1Projective,
viewing_key: Fr,
spending_key: Fr,
expected_view_tag: u64,
) -> Option<Fr> {
let q_receiver = compute_shared_point(viewing_key, ephemeral_public_key);

let inputs_receiver = q_receiver.to_string();
let q_receiver_hashed = hash_to_fr(inputs_receiver.as_bytes());

// Check if retrieved view tag matches the expected view tag
let view_tag = q_receiver_hashed.0 .0[0];
if view_tag == expected_view_tag {
let stealth_private_key = spending_key + q_receiver_hashed;
Some(stealth_private_key)
} else {
None
}
}

#[cfg(test)]
mod tests {
use super::*;
use ark_ec::CurveGroup;
use rln::public::RLN;
use std::io::Cursor;
use ark_std::rand::thread_rng;
use color_eyre::{Report, Result};
use rln::utils::fr_to_bytes_le;
use serde_json::json;

#[test]
fn test_random_keypair() {
let (key, pub_key) = random_keypair();
// Check the derived key matches the one generated from original key
assert_eq!(derive_public_key(key), pub_key);
}

#[test]
fn test_hash_to_fr() {
// Test that hash_to_fr(input_1) != hash_to_fr(input_2) when input_1 != input_2
let input_1 = b"input_1";
let input_2 = b"input_2";
assert_ne!(hash_to_fr(input_1), hash_to_fr(input_2));
}

#[test]
fn test_compute_shared_point() {
// In a multiple participant scenario, any participant's public key
// combined with any other participant's private key should arrive at the same shared key
let (key1, pub_key1) = random_keypair();
let (key2, pub_key2) = random_keypair();

let shared1 = compute_shared_point(key1, pub_key2);
let shared2 = compute_shared_point(key2, pub_key1);

// Convert Projective to Affine for equality comparison
let shared1_affine = shared1.into_affine();
let shared2_affine = shared2.into_affine();

assert_eq!(shared1_affine.x, shared2_affine.x);
assert_eq!(shared1_affine.y, shared2_affine.y);
}

#[test]
fn test_stealth_commitment_generation() {
let (spending_key, spending_public_key) = random_keypair();
let (viewing_key, viewing_public_key) = random_keypair();

// generate ephemeral keypair
let (ephemeral_private_key, ephemeral_public_key) = random_keypair();

let (stealth_commitment, view_tag) = generate_stealth_commitment(
viewing_public_key,
spending_public_key,
ephemeral_private_key,
);

let stealth_private_key_opt =
generate_stealth_private_key(ephemeral_public_key, viewing_key, spending_key, view_tag);

if stealth_private_key_opt.is_none() {
panic!("View tags did not match");
}

let derived_commitment = derive_public_key(stealth_private_key_opt.unwrap());
assert_eq!(derived_commitment, stealth_commitment);
}

#[test]
fn apply_stealth_membership_from_one_tree_to_another() -> Result<()> {
let test_tree_height = 20;
let resources = Cursor::new(json!({"resources_folder": "tree_height_20"}).to_string());
let mut rln = RLN::new(test_tree_height, resources.clone())?;

let alice_leaf = Fr::rand(&mut thread_rng());
let (alice_known_spending_sk, alice_known_spending_pk) = random_keypair();
let alice_leaf_buffer = Cursor::new(fr_to_bytes_le(&alice_leaf));
rln.set_leaf(0, alice_leaf_buffer)?;

// now the application sees that a user has been inserted into the tree
let mut rln_app_tree = RLN::new(test_tree_height, resources)?;
// the application generates a stealth commitment for alice
let (ephemeral_private_key, ephemeral_public_key) = random_keypair();
let (alice_stealth_commitment, view_tag) = generate_stealth_commitment(alice_known_spending_pk, alice_known_spending_pk, ephemeral_private_key);

let parts = [alice_stealth_commitment.x, alice_stealth_commitment.y];
let fr_parts = parts.map(|x| Fr::from(x.0));
let alice_stealth_commitment_buffer = Cursor::new(fr_to_bytes_le(&poseidon_hash(&fr_parts)));
rln_app_tree.set_leaf(0, alice_stealth_commitment_buffer)?;

// now alice's stealth commitment has been inserted into the tree, but alice has not
// yet derived the secret for it -
let alice_stealth_private_key_opt = generate_stealth_private_key(ephemeral_public_key, alice_known_spending_sk, alice_known_spending_sk, view_tag);
if alice_stealth_private_key_opt.is_none() {
return Err(Report::msg("Invalid view tag"));
}
let alice_stealth_private_key = alice_stealth_private_key_opt.unwrap();

assert_eq!(derive_public_key(alice_stealth_private_key), alice_stealth_commitment);

// now alice may generate valid rln proofs for the rln app tree, using a commitment
// derived from her commitment on the other tree
Ok(())
}
}
#[cfg(feature = "ffi")]
mod ffi;
Loading

0 comments on commit 21a7f5c

Please sign in to comment.