-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
367 additions
and
170 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.