From 301b2dbce3fec12cfb53813470a91f69839c6863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cain=C3=A3=20Costa?= Date: Fri, 26 Jul 2024 10:43:11 -0300 Subject: [PATCH] chore(core): remove commitment, re-organize types again --- core/src/crypto/commitment.rs | 175 ------------------------- core/src/crypto/dh.rs | 40 +++--- core/src/crypto/mod.rs | 32 +---- core/src/crypto/schnorr.rs | 22 +--- core/src/error.rs | 2 +- core/src/lib.rs | 14 +- core/src/testing.rs | 4 +- core/src/types/delegate.rs | 2 +- core/src/types/mod.rs | 22 +++- core/src/types/{wallet.rs => note.rs} | 10 +- core/src/types/{op => request}/mod.rs | 0 core/src/types/{op => request}/swap.rs | 28 ++-- 12 files changed, 78 insertions(+), 273 deletions(-) delete mode 100644 core/src/crypto/commitment.rs rename core/src/types/{wallet.rs => note.rs} (82%) rename core/src/types/{op => request}/mod.rs (100%) rename core/src/types/{op => request}/swap.rs (68%) diff --git a/core/src/crypto/commitment.rs b/core/src/crypto/commitment.rs deleted file mode 100644 index 8d78b6b..0000000 --- a/core/src/crypto/commitment.rs +++ /dev/null @@ -1,175 +0,0 @@ -use std::collections::HashMap; - -use bulletproofs::{BulletproofGens, PedersenGens, RangeProof}; -use curve25519_dalek::scalar::Scalar; -use merlin::Transcript; - -use crate::crypto::*; -use crate::error::Error; -use crate::types::op::swap::{Atom, Swap}; -use crate::Hash; - -const MAX_INPUTS: usize = 16; -const MAX_OUTPUTS: usize = 16; - -#[derive(Debug, Clone)] -pub struct TransactionCommitment { - pub bulletproof: RangeProof, - pub asset_commitments: HashMap, -} - -#[cfg(test)] -impl proptest::arbitrary::Arbitrary for TransactionCommitment { - type Parameters = (); - type Strategy = proptest::strategy::BoxedStrategy; - - fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { - use proptest::{collection::vec, prelude::*}; - - (vec(any::(), 1..=8), vec(any::(), 1..=8)) - .prop_map(|(inputs, outputs)| commit(&inputs, &outputs).unwrap()) - .boxed() - } -} - -fn create_transcript(label: &'static [u8]) -> Transcript { - let mut transcript = Transcript::new(label); - transcript.append_message(b"protocol-name", COMMITMENT_TRANSCRIPT_LABEL); - transcript -} - -pub fn commit(inputs: &[Atom], outputs: &[Atom]) -> Result { - if inputs.len() > MAX_INPUTS { - return Err(Error::TooManyInputs(inputs.len())); - } - - if outputs.len() > MAX_OUTPUTS { - return Err(Error::TooManyOutputs(outputs.len())); - } - - let mut amounts = Vec::new(); - let mut blindings = Vec::new(); - let mut asset_balances: HashMap = HashMap::new(); - let mut asset_commitments: HashMap = HashMap::new(); - - let pc_gens = PedersenGens::default(); - let bp_gens = BulletproofGens::new(64, MAX_INPUTS + MAX_OUTPUTS); - - // Process inputs and outputs - for (is_input, atom) in inputs - .iter() - .map(|a| (true, a)) - .chain(outputs.iter().map(|a| (false, a))) - { - if atom.amount == 0 { - return Err(Error::ZeroAmount); - } - - amounts.push(atom.amount); - let blinding = Scalar::random(&mut rand::thread_rng()); - blindings.push(blinding); - - let balance = asset_balances.entry(atom.asset_id).or_insert(0i128); - *balance += if is_input { - atom.amount as i128 - } else { - -(atom.amount as i128) - }; - - let h_a = hash_to_curve(&atom.asset_id); - let blinded_asset_id = (h_a + pc_gens.B_blinding * blinding).compress(); - let commitment = - (pc_gens.B * Scalar::from(atom.amount) + pc_gens.B_blinding * blinding + h_a) - .compress(); - - asset_commitments - .entry(blinded_asset_id) - .and_modify(|e| { - *e = (e.decompress().unwrap() - + commitment.decompress().unwrap() - * if is_input { Scalar::ONE } else { -Scalar::ONE }) - .compress() - }) - .or_insert(commitment); - } - - // Check balance for each asset - for (_, balance) in asset_balances { - if balance != 0 { - return Err(Error::BalanceCheckFailed); - } - } - - let mut transcript = create_transcript(COMMITMENT_TRANSCRIPT_LABEL); - - // Append all asset commitments to the transcript - for (blinded_asset_id, commitment) in &asset_commitments { - transcript.append_message(b"blinded_asset_id", blinded_asset_id.as_bytes()); - transcript.append_message(b"commitment", commitment.as_bytes()); - } - - // Create a single aggregated range proof for all amounts - let (bulletproof, _) = RangeProof::prove_multiple( - &bp_gens, - &pc_gens, - &mut transcript, - &amounts.iter().map(|&a| a as u64).collect::>(), - &blindings, - 64, - ) - .map_err(|e| { - Error::RangeProofError(format!("Failed to create aggregated range proof: {:?}", e)) - })?; - - Ok(TransactionCommitment { - bulletproof, - asset_commitments, - }) -} - -pub fn verify(swap: &Swap) -> Result<(), Error> { - if swap.inputs.len() > MAX_INPUTS { - return Err(Error::TooManyInputs(swap.inputs.len())); - } - - if swap.outputs.len() > MAX_OUTPUTS { - return Err(Error::TooManyOutputs(swap.outputs.len())); - } - - let pc_gens = PedersenGens::default(); - let bp_gens = BulletproofGens::new(64, MAX_INPUTS + MAX_OUTPUTS); - - let mut verifier_transcript = create_transcript(COMMITMENT_VERIFIER_LABEL); - - // Append all asset commitments to the transcript - for (blinded_asset_id, commitment) in &swap.commitment.asset_commitments { - verifier_transcript.append_message(b"blinded_asset_id", blinded_asset_id.as_bytes()); - verifier_transcript.append_message(b"commitment", commitment.as_bytes()); - } - - // Verify the bulletproof - swap.commitment - .bulletproof - .verify_multiple( - &bp_gens, - &pc_gens, - &mut verifier_transcript, - &swap - .commitment - .asset_commitments - .values() - .cloned() - .collect::>(), - 64, - ) - .map_err(|_| Error::InvalidCommitment)?; - - // Verify that all asset commitments are non-zero - for commitment in swap.commitment.asset_commitments.values() { - if commitment == &CompressedRistretto::identity() { - return Err(Error::ZeroAmount); - } - } - - Ok(()) -} diff --git a/core/src/crypto/dh.rs b/core/src/crypto/dh.rs index fa183d7..1ffd338 100644 --- a/core/src/crypto/dh.rs +++ b/core/src/crypto/dh.rs @@ -1,10 +1,7 @@ -use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT; use rand::rngs::OsRng; use super::*; -use crate::error::Error; - -pub use curve25519_dalek::{ristretto::RistrettoPoint, scalar::Scalar}; +use crate::{error::Error, types::*, G}; #[derive(Debug, Clone)] #[cfg_attr(test, derive(test_strategy::Arbitrary))] @@ -15,23 +12,20 @@ pub struct DLEQProof { s: Scalar, } -pub fn blind(secret_message: &[u8]) -> (RistrettoPoint, Scalar, RistrettoPoint) { +pub fn blind(secret_message: &[u8]) -> (Point, Scalar, Point) { let y = hash_to_curve(secret_message); let r = Scalar::random(&mut OsRng); - let b_prime = y + (RISTRETTO_BASEPOINT_POINT * r); + let b_prime = y + (*G * r); (y, r, b_prime) } -pub fn sign_blinded( - private_key: &Scalar, - blinded_point: &RistrettoPoint, -) -> (RistrettoPoint, DLEQProof) { +pub fn sign_blinded(private_key: &Scalar, blinded_point: &Point) -> (Point, DLEQProof) { let signed_point = blinded_point * private_key; - let public_key = RISTRETTO_BASEPOINT_POINT * private_key; + let public_key = *G * private_key; // Generate DLEQ proof let r = Scalar::random(&mut OsRng); - let r1 = RISTRETTO_BASEPOINT_POINT * r; + let r1 = *G * r; let r2 = blinded_point * r; let e = hash_to_scalar(&[ r1.compress().as_bytes(), @@ -45,12 +39,12 @@ pub fn sign_blinded( } pub fn verify_dleq_proof( - public_key: &RistrettoPoint, - blinded_point: &RistrettoPoint, - signed_point: &RistrettoPoint, + public_key: &Point, + blinded_point: &Point, + signed_point: &Point, proof: &DLEQProof, ) -> Result<(), Error> { - let r1 = (RISTRETTO_BASEPOINT_POINT * proof.s) - (public_key * proof.e); + let r1 = (*G * proof.s) - (public_key * proof.e); let r2 = (blinded_point * proof.s) - (signed_point * proof.e); let e = hash_to_scalar(&[ r1.compress().as_bytes(), @@ -67,12 +61,12 @@ pub fn verify_dleq_proof( } pub fn unblind_and_verify_signature( - signed_point: &RistrettoPoint, + signed_point: &Point, blinding_factor: &Scalar, - public_key: &RistrettoPoint, + public_key: &Point, proof: &DLEQProof, - blinded_point: &RistrettoPoint, -) -> Result { + blinded_point: &Point, +) -> Result { verify_dleq_proof(public_key, blinded_point, signed_point, proof)?; Ok(signed_point - (public_key * blinding_factor)) @@ -81,7 +75,7 @@ pub fn unblind_and_verify_signature( pub fn verify_unblinded_point( private_key: &Scalar, message: &[u8], - unblinded_point: &RistrettoPoint, + unblinded_point: &Point, ) -> Result<(), Error> { let y = hash_to_curve(message); @@ -100,7 +94,7 @@ mod tests { #[proptest] fn test_blind_diffie_hellman_flow( - #[strategy(keypair())] a: (Scalar, RistrettoPoint), + #[strategy(keypair())] a: (Scalar, Point), secret_message: Vec, ) { // Alice initializes @@ -122,7 +116,7 @@ mod tests { #[proptest] #[should_panic] fn test_schnorr_signature_tampering( - #[strategy(keypair())] a: (Scalar, RistrettoPoint), + #[strategy(keypair())] a: (Scalar, Point), secret_message: Vec, ) { // Alice initializes diff --git a/core/src/crypto/mod.rs b/core/src/crypto/mod.rs index 9dbd451..3b21b7f 100644 --- a/core/src/crypto/mod.rs +++ b/core/src/crypto/mod.rs @@ -1,34 +1,10 @@ -pub mod commitment; pub mod dh; pub mod schnorr; use blake2::{Blake2b, Digest}; -use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT; -use lazy_static::lazy_static; use rand::rngs::OsRng; -pub use bulletproofs::RangeProof; -pub use curve25519_dalek::{ - ristretto::{CompressedRistretto, RistrettoPoint}, - scalar::Scalar, - traits::*, -}; - -pub use schnorr::Signature; - -pub const DOMAIN_SEPARATOR: &[u8] = b"MUGRAPH_V1_CURVE_25519_HASH_TO_CURVE_"; -pub const DLEQ_DOMAIN_SEPARATOR: &[u8] = b"MUGRAPH_V1_CURVE_25519_DLEQ_PROOF_"; -pub const COMMITMENT_TRANSCRIPT_LABEL: &[u8] = b"MUGRAPH_V1_CURVE_25519_COMMITMENT_"; -pub const COMMITMENT_VERIFIER_LABEL: &[u8] = b"MUGRAPH_V1_CURVE_25519_COMMITMENT_VERIFIER_"; -pub const RANGE_PROOF_DOMAIN_SEPARATOR: &[u8] = b"MUGRAPH_V1_CURVE_25519_RANGE_PROOF_"; - -pub type PublicKey = RistrettoPoint; -pub type SecretKey = Scalar; - -lazy_static! { - pub static ref G: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; - pub static ref H: RistrettoPoint = RistrettoPoint::random(&mut OsRng); -} +use crate::{types::*, DOMAIN_SEPARATOR, G}; pub fn hash_to_scalar(data: &[&[u8]]) -> Scalar { let mut hasher = Blake2b::new(); @@ -40,13 +16,13 @@ pub fn hash_to_scalar(data: &[&[u8]]) -> Scalar { Scalar::from_bytes_mod_order(hasher.finalize().into()) } -pub fn hash_to_curve(message: &[u8]) -> RistrettoPoint { +pub fn hash_to_curve(message: &[u8]) -> Point { let scalar = hash_to_scalar(&[DOMAIN_SEPARATOR, message]); - RISTRETTO_BASEPOINT_POINT * scalar + *G * scalar } pub fn generate_keypair() -> (SecretKey, PublicKey) { let privkey = Scalar::random(&mut OsRng); - let pubkey = RISTRETTO_BASEPOINT_POINT * privkey; + let pubkey = *G * privkey; (privkey, pubkey) } diff --git a/core/src/crypto/schnorr.rs b/core/src/crypto/schnorr.rs index 89087a9..6e62787 100644 --- a/core/src/crypto/schnorr.rs +++ b/core/src/crypto/schnorr.rs @@ -1,34 +1,20 @@ -use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar}; use rand::rngs::OsRng; use super::hash_to_scalar; -use crate::error::Error; - -#[derive(Debug, Clone)] -#[cfg_attr(test, derive(test_strategy::Arbitrary))] -pub struct Signature { - #[cfg_attr(test, strategy(crate::testing::point()))] - pub r: RistrettoPoint, - #[cfg_attr(test, strategy(crate::testing::scalar()))] - pub s: Scalar, -} +use crate::{error::Error, types::*, G}; pub fn sign(private_key: &Scalar, message: &[u8]) -> Signature { let k = Scalar::random(&mut OsRng); - let r = RISTRETTO_BASEPOINT_POINT * k; + let r = *G * k; let e = hash_to_scalar(&[&r.compress().to_bytes(), message]); let s = k + e * private_key; Signature { r, s } } -pub fn verify( - public_key: &RistrettoPoint, - signature: &Signature, - message: &[u8], -) -> Result<(), Error> { +pub fn verify(public_key: &Point, signature: &Signature, message: &[u8]) -> Result<(), Error> { let e = hash_to_scalar(&[&signature.r.compress().to_bytes(), message]); - let lhs = RISTRETTO_BASEPOINT_POINT * signature.s; + let lhs = *G * signature.s; let rhs = signature.r + public_key * e; if lhs == rhs { diff --git a/core/src/error.rs b/core/src/error.rs index a257b74..ee62f94 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -1,7 +1,7 @@ use miette::Diagnostic; use thiserror::Error; -use crate::Hash; +use crate::types::Hash; #[derive(Error, Debug, Diagnostic)] pub enum Error { diff --git a/core/src/lib.rs b/core/src/lib.rs index d3516a0..e3d71f2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,3 +1,6 @@ +use lazy_static::lazy_static; +use rand::rngs::OsRng; + pub mod crypto; pub mod error; pub mod types; @@ -5,4 +8,13 @@ pub mod types; #[cfg(test)] pub mod testing; -pub type Hash = [u8; 32]; +pub const DOMAIN_SEPARATOR: &[u8] = b"MUGRAPH_V1_CURVE_25519_HASH_TO_CURVE_"; +pub const DLEQ_DOMAIN_SEPARATOR: &[u8] = b"MUGRAPH_V1_CURVE_25519_DLEQ_PROOF_"; +pub const COMMITMENT_TRANSCRIPT_LABEL: &[u8] = b"MUGRAPH_V1_CURVE_25519_COMMITMENT_"; +pub const COMMITMENT_VERIFIER_LABEL: &[u8] = b"MUGRAPH_V1_CURVE_25519_COMMITMENT_VERIFIER_"; +pub const RANGE_PROOF_DOMAIN_SEPARATOR: &[u8] = b"MUGRAPH_V1_CURVE_25519_RANGE_PROOF_"; + +lazy_static! { + pub static ref G: types::Point = curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT; + pub static ref H: types::Point = types::Point::random(&mut OsRng); +} diff --git a/core/src/testing.rs b/core/src/testing.rs index 62be8bd..5fb2f99 100644 --- a/core/src/testing.rs +++ b/core/src/testing.rs @@ -1,11 +1,11 @@ -use crate::crypto::*; +use crate::{types::*, G}; use proptest::prelude::*; pub fn scalar() -> impl Strategy { any::<[u8; 32]>().prop_map(|bytes| Scalar::from_bytes_mod_order(bytes)) } -pub fn point() -> impl Strategy { +pub fn point() -> impl Strategy { scalar().prop_map(|s| s * *G) } diff --git a/core/src/types/delegate.rs b/core/src/types/delegate.rs index 4a37a65..db686b2 100644 --- a/core/src/types/delegate.rs +++ b/core/src/types/delegate.rs @@ -1,4 +1,4 @@ -use crate::crypto::*; +use crate::types::*; pub struct Settings { pub public_key: PublicKey, diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index 10ac870..3c9b095 100644 --- a/core/src/types/mod.rs +++ b/core/src/types/mod.rs @@ -1,3 +1,21 @@ pub mod delegate; -pub mod op; -pub mod wallet; +pub mod note; +pub mod request; + +pub use curve25519_dalek::traits::*; + +pub type Hash = [u8; 32]; +pub type Point = curve25519_dalek::ristretto::RistrettoPoint; +pub type Scalar = curve25519_dalek::scalar::Scalar; +pub type PublicKey = Point; +pub type SecretKey = Scalar; +pub type CompressedPoint = curve25519_dalek::ristretto::CompressedRistretto; + +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(test_strategy::Arbitrary))] +pub struct Signature { + #[cfg_attr(test, strategy(crate::testing::point()))] + pub r: Point, + #[cfg_attr(test, strategy(crate::testing::scalar()))] + pub s: Scalar, +} diff --git a/core/src/types/wallet.rs b/core/src/types/note.rs similarity index 82% rename from core/src/types/wallet.rs rename to core/src/types/note.rs index e0810fc..0491618 100644 --- a/core/src/types/wallet.rs +++ b/core/src/types/note.rs @@ -1,14 +1,16 @@ -use crate::{crypto::*, Hash}; - -use self::schnorr::Signature; +use crate::types::*; +#[derive(Debug)] pub struct Note { /// The ID for the asset in the Cardano blockchain pub asset_id: Hash, + /// The amount included in this note pub amount: u128, + /// The secret for this note, used to prevent double-spend on the server - pub nullifier: RistrettoPoint, + pub nullifier: Point, + /// Unblinded signature from the server from this note creation /// /// Equivalent to C in the protocol, returned by the server after minting or swapping diff --git a/core/src/types/op/mod.rs b/core/src/types/request/mod.rs similarity index 100% rename from core/src/types/op/mod.rs rename to core/src/types/request/mod.rs diff --git a/core/src/types/op/swap.rs b/core/src/types/request/swap.rs similarity index 68% rename from core/src/types/op/swap.rs rename to core/src/types/request/swap.rs index ad7298d..0f37520 100644 --- a/core/src/types/op/swap.rs +++ b/core/src/types/request/swap.rs @@ -1,7 +1,9 @@ -use crate::{ - crypto::{commitment::TransactionCommitment, dh::DLEQProof, *}, - Hash, -}; +use crate::crypto::dh::DLEQProof; +use crate::types::*; + +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(test_strategy::Arbitrary))] +pub struct Commitment; #[derive(Debug, Clone)] #[cfg_attr(test, derive(test_strategy::Arbitrary))] @@ -10,7 +12,8 @@ pub struct Input { /// /// Corresponds to x in the protocol. #[cfg_attr(test, strategy(crate::testing::point()))] - pub nullifier: RistrettoPoint, + pub nullifier: Point, + /// A proof sent by the delegate that the input blinded transaction was /// generated correctly. pub dleq_proof: DLEQProof, @@ -23,18 +26,7 @@ pub struct Output { /// /// Corresponds to B' in the protocol. #[cfg_attr(test, strategy(crate::testing::point()))] - pub blinded_secret: RistrettoPoint, -} - -/// An atom for a swap, the input that is used to generate the RangeProof for -/// the transaction. -#[derive(Debug, Clone)] -#[cfg_attr(test, derive(test_strategy::Arbitrary))] -pub struct Atom { - pub asset_id: Hash, - pub amount: u128, - #[cfg_attr(test, strategy(crate::testing::point()))] - pub nullifier: RistrettoPoint, + pub blinded_secret: Point, } #[derive(Debug, Clone)] @@ -42,7 +34,7 @@ pub struct Atom { pub struct Swap { pub inputs: Vec, pub outputs: Vec, - pub commitment: TransactionCommitment, + pub commitment: Commitment, /// The unblided signatures for each included Input pub witnesses: Vec,