mirror of
https://github.com/vacp2p/stealth-address-kit.git
synced 2026-01-09 13:38:01 -05:00
feat: add more curves
This commit is contained in:
26
Cargo.lock
generated
26
Cargo.lock
generated
@@ -48,6 +48,29 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-bls12-377"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f"
|
||||
dependencies = [
|
||||
"ark-ec",
|
||||
"ark-ff",
|
||||
"ark-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-bls12-381"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
|
||||
dependencies = [
|
||||
"ark-ec",
|
||||
"ark-ff",
|
||||
"ark-serialize",
|
||||
"ark-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-bn254"
|
||||
version = "0.4.0"
|
||||
@@ -736,6 +759,8 @@ dependencies = [
|
||||
name = "erc-5564-bn254"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ark-bls12-377",
|
||||
"ark-bls12-381",
|
||||
"ark-bn254",
|
||||
"ark-ec",
|
||||
"ark-ff",
|
||||
@@ -746,6 +771,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"rln",
|
||||
"serde_json",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -11,7 +11,7 @@ crate-type = ["staticlib"]
|
||||
[features]
|
||||
ffi = []
|
||||
include_rln_ffi = []
|
||||
default = ["ffi"]
|
||||
default = []
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -22,6 +22,9 @@ num-bigint = "0.4.3"
|
||||
num-traits = "0.2.15"
|
||||
ark-ff = "0.4.1"
|
||||
ark-bn254 = "0.4.0"
|
||||
ark-bls12-381 = "0.4.0"
|
||||
ark-bls12-377 = "0.4.0"
|
||||
tiny-keccak = { version = "=2.0.2", features = ["keccak"] }
|
||||
ark-ec = "0.4.1"
|
||||
ark-serialize = "0.4.1"
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
# erc-5564-bn254
|
||||
# erc-5564-rs
|
||||
|
||||
Uses the [arkworks-rs](https://github.com/arkworks-rs/curves) suite of libraries, and utilities from [rln](https://github.com/vacp2p/zerokit)
|
||||
|
||||
## Existing Implementations
|
||||
|
||||
1. `ark_bn254`
|
||||
2. `ark_bls_12_381`
|
||||
3. `ark_bls_12_377`
|
||||
|
||||
## Usage
|
||||
|
||||
Note: this scheme should be used with the fork of [circom-rln](https://github.com/rymnc/circom-rln-erc5564).
|
||||
|
||||
118
src/bls12_377_impl.rs
Normal file
118
src/bls12_377_impl.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use crate::stealth_commitments::{AffineWrapper, RawFr, StealthAddressOnCurve};
|
||||
use ark_bls12_377::g1::{G1_GENERATOR_X, G1_GENERATOR_Y};
|
||||
use ark_bls12_377::{Fq, Fr, G1Affine, G1Projective};
|
||||
use ark_ff::PrimeField;
|
||||
use tiny_keccak::{Hasher, Keccak};
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct Bls12_377_G1Affine(G1Affine);
|
||||
impl AffineWrapper for Bls12_377_G1Affine {
|
||||
type Fq = Fq;
|
||||
fn new(x: Self::Fq, y: Self::Fq) -> Self {
|
||||
Bls12_377_G1Affine(G1Affine::new(x, y))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bls12_377_G1Affine> for G1Projective {
|
||||
fn from(value: Bls12_377_G1Affine) -> Self {
|
||||
G1Projective::from(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl RawFr for Fr {
|
||||
type Fr = Fr;
|
||||
fn as_u64(&self) -> u64 {
|
||||
self.0 .0[0]
|
||||
}
|
||||
}
|
||||
|
||||
impl StealthAddressOnCurve for ark_bls12_377::Bls12_377 {
|
||||
type Projective = G1Projective;
|
||||
type Affine = Bls12_377_G1Affine;
|
||||
type Fr = Fr;
|
||||
|
||||
fn derive_public_key(private_key: &Self::Fr) -> Self::Projective {
|
||||
let g1_generator_affine = Self::Affine::new(G1_GENERATOR_X, G1_GENERATOR_Y);
|
||||
(Self::Projective::from(g1_generator_affine)) * *private_key
|
||||
}
|
||||
|
||||
fn hash_to_fr(input: &[u8]) -> Self::Fr {
|
||||
let mut hash = [0; 32];
|
||||
let mut hasher = Keccak::v256();
|
||||
hasher.update(input);
|
||||
hasher.finalize(&mut hash);
|
||||
|
||||
// We export the hash as a field element
|
||||
Self::Fr::from_le_bytes_mod_order(hash.as_slice())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ark_ec::CurveGroup;
|
||||
|
||||
type Curve = ark_bls12_377::Bls12_377;
|
||||
|
||||
#[test]
|
||||
fn test_random_keypair() {
|
||||
let (key, pub_key) = Curve::random_keypair();
|
||||
// Check the derived key matches the one generated from original key
|
||||
assert_eq!(Curve::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!(Curve::hash_to_fr(input_1), Curve::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) = Curve::random_keypair();
|
||||
let (key2, pub_key2) = Curve::random_keypair();
|
||||
|
||||
let shared1 = Curve::compute_shared_point(key1, pub_key2);
|
||||
let shared2 = Curve::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) = Curve::random_keypair();
|
||||
let (viewing_key, viewing_public_key) = Curve::random_keypair();
|
||||
|
||||
// generate ephemeral keypair
|
||||
let (ephemeral_private_key, ephemeral_public_key) = Curve::random_keypair();
|
||||
|
||||
let (stealth_commitment, view_tag) = Curve::generate_stealth_commitment(
|
||||
viewing_public_key,
|
||||
spending_public_key,
|
||||
ephemeral_private_key,
|
||||
);
|
||||
|
||||
let stealth_private_key_opt = Curve::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 = Curve::derive_public_key(&stealth_private_key_opt.unwrap());
|
||||
assert_eq!(derived_commitment, stealth_commitment);
|
||||
}
|
||||
}
|
||||
118
src/bls12_381_impl.rs
Normal file
118
src/bls12_381_impl.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use crate::stealth_commitments::{AffineWrapper, RawFr, StealthAddressOnCurve};
|
||||
use ark_bls12_381::g1::{G1_GENERATOR_X, G1_GENERATOR_Y};
|
||||
use ark_bls12_381::{Fq, Fr, G1Affine, G1Projective};
|
||||
use ark_ff::PrimeField;
|
||||
use tiny_keccak::{Hasher, Keccak};
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct Bls12_381_G1Affine(G1Affine);
|
||||
impl AffineWrapper for Bls12_381_G1Affine {
|
||||
type Fq = Fq;
|
||||
fn new(x: Self::Fq, y: Self::Fq) -> Self {
|
||||
Bls12_381_G1Affine(G1Affine::new(x, y))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bls12_381_G1Affine> for G1Projective {
|
||||
fn from(value: Bls12_381_G1Affine) -> Self {
|
||||
G1Projective::from(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl RawFr for Fr {
|
||||
type Fr = Fr;
|
||||
fn as_u64(&self) -> u64 {
|
||||
self.0 .0[0]
|
||||
}
|
||||
}
|
||||
|
||||
impl StealthAddressOnCurve for ark_bls12_381::Bls12_381 {
|
||||
type Projective = G1Projective;
|
||||
type Affine = Bls12_381_G1Affine;
|
||||
type Fr = Fr;
|
||||
|
||||
fn derive_public_key(private_key: &Self::Fr) -> Self::Projective {
|
||||
let g1_generator_affine = Self::Affine::new(G1_GENERATOR_X, G1_GENERATOR_Y);
|
||||
(Self::Projective::from(g1_generator_affine)) * *private_key
|
||||
}
|
||||
|
||||
fn hash_to_fr(input: &[u8]) -> Self::Fr {
|
||||
let mut hash = [0; 32];
|
||||
let mut hasher = Keccak::v256();
|
||||
hasher.update(input);
|
||||
hasher.finalize(&mut hash);
|
||||
|
||||
// We export the hash as a field element
|
||||
Self::Fr::from_le_bytes_mod_order(hash.as_slice())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ark_ec::CurveGroup;
|
||||
|
||||
type Curve = ark_bls12_381::Bls12_381;
|
||||
|
||||
#[test]
|
||||
fn test_random_keypair() {
|
||||
let (key, pub_key) = Curve::random_keypair();
|
||||
// Check the derived key matches the one generated from original key
|
||||
assert_eq!(Curve::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!(Curve::hash_to_fr(input_1), Curve::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) = Curve::random_keypair();
|
||||
let (key2, pub_key2) = Curve::random_keypair();
|
||||
|
||||
let shared1 = Curve::compute_shared_point(key1, pub_key2);
|
||||
let shared2 = Curve::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) = Curve::random_keypair();
|
||||
let (viewing_key, viewing_public_key) = Curve::random_keypair();
|
||||
|
||||
// generate ephemeral keypair
|
||||
let (ephemeral_private_key, ephemeral_public_key) = Curve::random_keypair();
|
||||
|
||||
let (stealth_commitment, view_tag) = Curve::generate_stealth_commitment(
|
||||
viewing_public_key,
|
||||
spending_public_key,
|
||||
ephemeral_private_key,
|
||||
);
|
||||
|
||||
let stealth_private_key_opt = Curve::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 = Curve::derive_public_key(&stealth_private_key_opt.unwrap());
|
||||
assert_eq!(derived_commitment, stealth_commitment);
|
||||
}
|
||||
}
|
||||
161
src/bn254_impl.rs
Normal file
161
src/bn254_impl.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use crate::stealth_commitments::{AffineWrapper, RawFr, StealthAddressOnCurve};
|
||||
use ark_bn254::g1::{G1_GENERATOR_X, G1_GENERATOR_Y};
|
||||
use ark_bn254::{Fq, Fr, G1Affine, G1Projective};
|
||||
use rln::hashers::{hash_to_field, poseidon_hash};
|
||||
|
||||
impl AffineWrapper for G1Affine {
|
||||
type Fq = Fq;
|
||||
fn new(x: Self::Fq, y: Self::Fq) -> Self {
|
||||
G1Affine::new(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
impl RawFr for Fr {
|
||||
type Fr = Fr;
|
||||
fn as_u64(&self) -> u64 {
|
||||
self.0 .0[0]
|
||||
}
|
||||
}
|
||||
|
||||
impl StealthAddressOnCurve for ark_bn254::Bn254 {
|
||||
type Projective = G1Projective;
|
||||
type Affine = G1Affine;
|
||||
type Fr = Fr;
|
||||
|
||||
fn derive_public_key(private_key: &Self::Fr) -> Self::Projective {
|
||||
let g1_generator_affine = Self::Affine::new(G1_GENERATOR_X, G1_GENERATOR_Y);
|
||||
(Self::Projective::from(g1_generator_affine)) * *private_key
|
||||
}
|
||||
|
||||
fn hash_to_fr(input: &[u8]) -> Self::Fr {
|
||||
poseidon_hash(&[hash_to_field(input)])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_std::rand::thread_rng;
|
||||
use ark_std::UniformRand;
|
||||
use color_eyre::{Report, Result};
|
||||
use rln::public::RLN;
|
||||
use rln::utils::fr_to_bytes_le;
|
||||
use serde_json::json;
|
||||
use std::io::Cursor;
|
||||
|
||||
type Curve = ark_bn254::Bn254;
|
||||
|
||||
#[test]
|
||||
fn test_random_keypair() {
|
||||
let (key, pub_key) = Curve::random_keypair();
|
||||
// Check the derived key matches the one generated from original key
|
||||
assert_eq!(Curve::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!(Curve::hash_to_fr(input_1), Curve::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) = Curve::random_keypair();
|
||||
let (key2, pub_key2) = Curve::random_keypair();
|
||||
|
||||
let shared1 = Curve::compute_shared_point(key1, pub_key2);
|
||||
let shared2 = Curve::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) = Curve::random_keypair();
|
||||
let (viewing_key, viewing_public_key) = Curve::random_keypair();
|
||||
|
||||
// generate ephemeral keypair
|
||||
let (ephemeral_private_key, ephemeral_public_key) = Curve::random_keypair();
|
||||
|
||||
let (stealth_commitment, view_tag) = Curve::generate_stealth_commitment(
|
||||
viewing_public_key,
|
||||
spending_public_key,
|
||||
ephemeral_private_key,
|
||||
);
|
||||
|
||||
let stealth_private_key_opt = Curve::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 = Curve::derive_public_key(&stealth_private_key_opt.unwrap());
|
||||
assert_eq!(derived_commitment, stealth_commitment);
|
||||
}
|
||||
|
||||
// this can only be tested for bn254 since that is the curve supported by RLN
|
||||
#[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) = Curve::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) = Curve::random_keypair();
|
||||
let (alice_stealth_commitment, view_tag) = Curve::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 = Curve::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!(
|
||||
Curve::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(())
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
use crate::ffi::CErrorCode::{
|
||||
NoError, SerializationErrorInvalidData, SerializationErrorIoError,
|
||||
SerializationErrorNotEnoughSpace, SerializationErrorUnexpectedFlags,
|
||||
};
|
||||
use crate::stealth_commitments::{
|
||||
derive_public_key, generate_random_fr, generate_stealth_commitment,
|
||||
generate_stealth_private_key, random_keypair,
|
||||
@@ -6,10 +10,6 @@ use ark_bn254::{Fr, G1Projective};
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError};
|
||||
use num_traits::Zero;
|
||||
use std::ops::Add;
|
||||
use crate::ffi::CErrorCode::{
|
||||
NoError, SerializationErrorInvalidData, SerializationErrorIoError,
|
||||
SerializationErrorNotEnoughSpace, SerializationErrorUnexpectedFlags,
|
||||
};
|
||||
// we import this to prevent using multiple static libs
|
||||
#[cfg(feature = "include_rln_ffi")]
|
||||
#[allow(unused_imports)]
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
mod stealth_commitments;
|
||||
|
||||
mod bls12_381_impl;
|
||||
mod bn254_impl;
|
||||
#[cfg(feature = "ffi")]
|
||||
mod ffi;
|
||||
mod bls12_377_impl;
|
||||
|
||||
@@ -1,184 +1,76 @@
|
||||
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};
|
||||
use ark_std::UniformRand;
|
||||
use std::fmt::Display;
|
||||
use std::ops::{Add, Mul};
|
||||
|
||||
pub fn derive_public_key(private_key: Fr) -> G1Projective {
|
||||
let g1_generator_affine = G1Affine::new(G1_GENERATOR_X, G1_GENERATOR_Y);
|
||||
(G1Projective::from(g1_generator_affine)) * private_key
|
||||
pub trait AffineWrapper {
|
||||
type Fq: ark_ff::PrimeField;
|
||||
fn new(x: Self::Fq, y: Self::Fq) -> Self;
|
||||
}
|
||||
|
||||
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 trait RawFr {
|
||||
type Fr;
|
||||
fn as_u64(&self) -> u64;
|
||||
}
|
||||
|
||||
pub fn generate_random_fr() -> Fr {
|
||||
let mut rng = OsRng;
|
||||
Fr::rand(&mut rng)
|
||||
}
|
||||
pub trait StealthAddressOnCurve {
|
||||
type Projective: Display
|
||||
+ Add<Output = Self::Projective>
|
||||
+ Mul<Self::Fr, Output = Self::Projective>;
|
||||
type Affine: AffineWrapper;
|
||||
type Fr: Add<Self::Fr, Output = Self::Fr> + ark_ff::PrimeField + RawFr;
|
||||
|
||||
pub fn hash_to_fr(input: &[u8]) -> Fr {
|
||||
poseidon_hash(&[hash_to_field(input)])
|
||||
}
|
||||
fn derive_public_key(private_key: &Self::Fr) -> Self::Projective;
|
||||
|
||||
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
|
||||
fn random_keypair() -> (Self::Fr, Self::Projective) {
|
||||
let private_key = Self::generate_random_fr();
|
||||
let public_key = Self::derive_public_key(&private_key);
|
||||
(private_key, public_key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_std::rand::thread_rng;
|
||||
use color_eyre::{Report, Result};
|
||||
use rln::public::RLN;
|
||||
use rln::utils::fr_to_bytes_le;
|
||||
use serde_json::json;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[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);
|
||||
fn generate_random_fr() -> Self::Fr {
|
||||
let mut rng = OsRng;
|
||||
Self::Fr::rand(&mut rng)
|
||||
}
|
||||
fn hash_to_fr(input: &[u8]) -> Self::Fr;
|
||||
fn compute_shared_point(
|
||||
private_key: Self::Fr,
|
||||
public_key: Self::Projective,
|
||||
) -> Self::Projective {
|
||||
public_key * private_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));
|
||||
fn generate_stealth_commitment(
|
||||
viewing_public_key: Self::Projective,
|
||||
spending_public_key: Self::Projective,
|
||||
ephemeral_private_key: Self::Fr,
|
||||
) -> (Self::Projective, u64) {
|
||||
let q = Self::compute_shared_point(ephemeral_private_key, viewing_public_key);
|
||||
let inputs = q.to_string();
|
||||
let q_hashed = Self::hash_to_fr(inputs.as_bytes());
|
||||
|
||||
let q_hashed_in_g1 = Self::derive_public_key(&q_hashed);
|
||||
let view_tag = q_hashed.as_u64();
|
||||
(q_hashed_in_g1 + spending_public_key, view_tag)
|
||||
}
|
||||
|
||||
#[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();
|
||||
fn generate_stealth_private_key(
|
||||
ephemeral_public_key: Self::Projective,
|
||||
viewing_key: Self::Fr,
|
||||
spending_key: Self::Fr,
|
||||
expected_view_tag: u64,
|
||||
) -> Option<Self::Fr> {
|
||||
let q_receiver = Self::compute_shared_point(viewing_key, ephemeral_public_key);
|
||||
|
||||
let shared1 = compute_shared_point(key1, pub_key2);
|
||||
let shared2 = compute_shared_point(key2, pub_key1);
|
||||
let inputs_receiver = q_receiver.to_string();
|
||||
let q_receiver_hashed = Self::hash_to_fr(inputs_receiver.as_bytes());
|
||||
|
||||
// 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");
|
||||
// Check if retrieved view tag matches the expected view tag
|
||||
let view_tag: u64 = q_receiver_hashed.as_u64();
|
||||
if view_tag == expected_view_tag {
|
||||
let stealth_private_key = spending_key + q_receiver_hashed;
|
||||
Some(stealth_private_key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user