├── .gitignore ├── README.md ├── Scarb.toml ├── architecture.png ├── differential-privacy-summary.png ├── scratch.py └── src ├── core.cairo ├── lib.cairo └── util.cairo /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 𝛿z 2 | The first implementation of zero-knowledge differential privacy. 3 | 4 | Delta Z is the first Zero Knowledge [Differential Privacy](https://en.wikipedia.org/wiki/Differential_privacy) (ZKDP) protocol. 5 | 6 | In 2009, [Netflix identified an opportunity to share their customer database](https://en.wikipedia.org/wiki/Differential_privacy) with the ML community to improve their recommender engine. All data was pseudoanonymous yet hackers were [able to reconstruct private information](https://www.wired.com/2010/03/netflix-cancels-contest/) about individuals using statistical methods. This is a fundamental limitation given formally by [The Fundamental Law of Information Recovery](https://aircloak.com/diffix-and-the-fundamental-law-of-information-recovery/) / [Reconstruction Attack](https://en.wikipedia.org/wiki/Reconstruction_attack). 7 | 8 | Clearly, pseudoanonymity is flawed. 9 | 10 | Differential privacy [provides mathematical guarantees](https://www.cis.upenn.edu/~aaroth/Papers/privacybook.pdf) that no personal information can ever be reconstructed from query results. However, it relies on a trusted data curator which is a single point-of-failure for [corruption](https://fortune.com/2023/07/12/harvard-business-school-francesca-gino-retractions-fabricated-data-dishonesty/) in a way that users are none-the-wiser. 11 | 12 | Delta Z is a completely trustless DP solution. We have have [implemented key algorithms](https://github.com/manuj-mishra/zkdiffpriv) from differential privacy literature as a Cairo-based smart contracts deployed on StarkNet (goerli). This allows users to submit queries as normal but receive results with verifiably correct noise that doesn't hinder their analysis. 13 | 14 | Architecture Diagram: 15 | 16 | ![Architecture Diagram](./architecture.png) 17 | 18 | Differential Privacy summarised in one image: 19 | 20 | ![Differential Privacy summarised in one image](./differential-privacy-summary.png) 21 | -------------------------------------------------------------------------------- /Scarb.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zkdiffpriv" 3 | version = "0.1.0" 4 | 5 | # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest 6 | 7 | [dependencies] 8 | orion = { git = "https://github.com/gizatechxyz/onnx-cairo" } 9 | # alexandria = { git = "https://github.com/keep-starknet-strange/alexandria.git" } 10 | 11 | 12 | # [[bin]] 13 | # name = "zkdiffprivlib" 14 | # path = "src/lib.cairo" 15 | -------------------------------------------------------------------------------- /architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuj-mishra/zkdiffpriv/d41933b3a7ac0e3a2820bb9cc3604a37c20306cd/architecture.png -------------------------------------------------------------------------------- /differential-privacy-summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuj-mishra/zkdiffpriv/d41933b3a7ac0e3a2820bb9cc3604a37c20306cd/differential-privacy-summary.png -------------------------------------------------------------------------------- /scratch.py: -------------------------------------------------------------------------------- 1 | def qnumber_to_uint28(qnumber): 2 | # Extract the integer and fractional parts 3 | integer_part = (qnumber >> 23) & 0xFF 4 | print(integer_part) 5 | fractional_part = qnumber & 0x7FFFFF 6 | print(fractional_part) 7 | 8 | # Shift the fractional part back to its original position 9 | normal_uint28 = (integer_part << 23) | (fractional_part >> 23) 10 | 11 | return normal_uint28 12 | 13 | # Example usage 14 | qnumber_input = 8388608 # Replace this with your Qnumber input 15 | result = qnumber_to_uint28(qnumber_input) 16 | print(f"Qnumber (8.23) input: 0x{qnumber_input:08X}") 17 | print(f"Normal uint28 output: {result}") 18 | -------------------------------------------------------------------------------- /src/core.cairo: -------------------------------------------------------------------------------- 1 | use orion::numbers::fixed_point::core::{FixedType, FixedTrait}; 2 | use orion::numbers::fixed_point::implementations::impl_16x16::{ONE, PI}; 3 | use orion::numbers::fixed_point::implementations::impl_16x16::{ 4 | FP16x16Impl, FP16x16Add, FP16x16AddEq, FP16x16Into, FP16x16Print, FP16x16PartialEq, FP16x16Sub, 5 | FP16x16SubEq, FP16x16Mul, FP16x16MulEq, FP16x16Div, FP16x16DivEq, FP16x16PartialOrd, FP16x16Neg 6 | }; 7 | 8 | use traits::{Into, TryInto}; 9 | use debug::PrintTrait; 10 | 11 | 12 | const TWO: u128 = 16777216; 13 | 14 | 15 | // CDF of a Gaussian distribution 16 | fn normcdf(x: FixedType, mu: FixedType, std: FixedType) -> FixedType { 17 | let z = (x - mu) / (std * FixedTrait::new_unscaled(2_u128, false).sqrt()); 18 | let phi = (FixedTrait::new(ONE, false) + erf(z)) / FixedTrait::new_unscaled(2_u128, false); 19 | return phi; 20 | } 21 | 22 | 23 | // Approximation of the Gauss error function 24 | fn erf(z: FixedType) -> FixedType { 25 | let coef = FixedTrait::new_unscaled(2_u128, false) / FixedTrait::new(PI, false).sqrt(); 26 | let taylor = _erf_sum(z, 5, FixedTrait::new(0, false)); 27 | return coef * taylor; 28 | } 29 | 30 | // Helper function to calculate Taylor series of erf function 31 | fn _erf_sum(z: FixedType, n: u128, acc: FixedType) -> FixedType { 32 | let coef = z / FixedTrait::new_unscaled((2_u128 * n) + 1_u128, false); 33 | let prod = _erf_prod(z, 3, FixedTrait::new(ONE, false)); 34 | let new_acc = acc + (coef * prod); 35 | 36 | if (n == 0_u128) { 37 | return new_acc; 38 | } 39 | 40 | return _erf_sum(z, n - 1_u128, new_acc); 41 | } 42 | 43 | // Helper function to calculate inner product of erf function 44 | // TODO: this always returns 0 45 | fn _erf_prod(z: FixedType, k: u128, acc: FixedType) -> FixedType { 46 | let neg_z_sq = - (z * z); 47 | let new_acc = (acc * neg_z_sq) / FixedTrait::new_unscaled(k, false); 48 | 49 | if (k == 1_u128) { 50 | return new_acc; 51 | } 52 | 53 | return _erf_prod(z, k - 1_u128, new_acc); 54 | } 55 | 56 | fn C(a: FixedType, b: FixedType, s: FixedType, sigma: FixedType) -> FixedType { 57 | 58 | let diff = normcdf(b, s, sigma) - normcdf(a, s, sigma); 59 | //TODO: this should not be manual fix 60 | if diff == (FixedTrait::new(0, false)) { 61 | return FixedTrait::new_unscaled(10000_u128, false); 62 | } 63 | // return FixedTrait::new(ONE, false) / diff; 64 | return FixedTrait::new(ONE, false); 65 | } 66 | 67 | fn delta_C(a: FixedType, b: FixedType, delta_Q: FixedType, sigma: FixedType) -> FixedType { 68 | assert(a != b, 'a cannot equal b'); 69 | if delta_Q <= ((b - a) / FixedTrait::new(TWO, false)) { 70 | return C(a, b, a, sigma) / C(a, b, a + delta_Q, sigma); 71 | } 72 | return C(a, b, a, sigma) / C(a, b, (b + a) / FixedTrait::new(TWO, false), sigma); 73 | } 74 | 75 | fn init_priv_param(a: FixedType, b: FixedType, delta_Q: FixedType, epsilon: FixedType) -> FixedType { 76 | return ((((b - a) + (delta_Q / FixedTrait::new(TWO, false))) * delta_Q) / epsilon).sqrt(); 77 | } 78 | 79 | fn optimal_priv_param(a: FixedType, b: FixedType, delta_Q: FixedType, epsilon: FixedType) -> FixedType { 80 | let sigma_0 = init_priv_param(a, b, delta_Q, epsilon); 81 | let mut left = sigma_0 * sigma_0; 82 | let mut right = (((b - a) + (delta_Q / FixedTrait::new(TWO, false))) * delta_Q) / (epsilon - delta_C(a, b, delta_Q, sigma_0).ln()); 83 | 84 | let mut intervalSize = (left + right) / FixedTrait::new(TWO, false); 85 | 86 | return optimal_priv_param_loop(a, b, delta_Q, epsilon, left, right, intervalSize, sigma_0); 87 | } 88 | 89 | fn optimal_priv_param_loop(a: FixedType, b: FixedType, delta_Q: FixedType, epsilon: FixedType, mut left: FixedType, mut right: FixedType, mut intervalSize: FixedType, mut sigma_star_sq: FixedType) -> FixedType { 90 | if intervalSize <= (right - left) { 91 | return sigma_star_sq; 92 | } 93 | 94 | intervalSize = right - left; 95 | sigma_star_sq = (left + right) / FixedTrait::new(TWO, false); 96 | 97 | let arg_cm = (((b - a) + (delta_Q / FixedTrait::new(TWO, false))) * delta_Q) / (epsilon - delta_C(a, b, delta_Q, sigma_star_sq.sqrt()).ln()); 98 | 99 | if arg_cm >= sigma_star_sq { 100 | left = sigma_star_sq; 101 | } 102 | if arg_cm <= sigma_star_sq { 103 | right = sigma_star_sq; 104 | } 105 | 106 | return optimal_priv_param_loop(a, b, delta_Q, epsilon, left, right, intervalSize, sigma_star_sq); 107 | } -------------------------------------------------------------------------------- /src/lib.cairo: -------------------------------------------------------------------------------- 1 | mod core; 2 | mod util; 3 | 4 | use array::ArrayTrait; 5 | use array::SpanTrait; 6 | use debug::PrintTrait; 7 | 8 | use orion::numbers::fixed_point::core::{FixedType, FixedTrait}; 9 | use orion::numbers::fixed_point::implementations::impl_16x16::{FP16x16Impl, ONE, PI, FP16x16Add, FP16x16AddEq, FP16x16Sub, FP16x16Mul, FP16x16Div, FP16x16PartialOrd, FP16x16PartialEq, FP16x16Print}; 10 | use orion::numbers::signed_integer::{integer_trait::IntegerTrait}; 11 | 12 | use util::{sum_array}; 13 | use core::{optimal_priv_param}; 14 | 15 | #[test] 16 | #[available_gas(1000000000)] 17 | fn test_lib() { 18 | 19 | let mut arr = ArrayTrait::new(); 20 | arr.append(FixedTrait::new_unscaled(1_u128, false)); 21 | arr.append(FixedTrait::new_unscaled(5_u128, false)); 22 | arr.append(FixedTrait::new_unscaled(2_u128, false)); 23 | arr.append(FixedTrait::new_unscaled(3_u128, false)); 24 | arr.append(FixedTrait::new_unscaled(6_u128, false)); 25 | arr.append(FixedTrait::new_unscaled(2_u128, false)); 26 | arr.append(FixedTrait::new_unscaled(3_u128, false)); 27 | arr.append(FixedTrait::new_unscaled(8_u128, false)); 28 | 29 | let q = get_q(arr); 30 | 31 | let a = FixedTrait::new_unscaled(1_u128, false); 32 | let b = FixedTrait::new_unscaled(11_u128, false); 33 | let del_q = b - a; 34 | let eps = FixedTrait::new_unscaled(1_u128, false) / FixedTrait::new_unscaled(1000_u128, false); 35 | 36 | let p = optimal_priv_param(a, b, del_q, eps).sqrt(); 37 | 38 | 'This is sigma'.print(); 39 | p.mag.print() 40 | } 41 | 42 | 43 | // We use the 'sum' query as an example 44 | fn get_q(arr: Array) -> FixedType { 45 | return sum_array(arr); 46 | } 47 | 48 | // fn main() -> u128 { 49 | // return 0; 50 | // } -------------------------------------------------------------------------------- /src/util.cairo: -------------------------------------------------------------------------------- 1 | use orion::operators::tensor::implementations::impl_tensor_u32::Tensor_u32; 2 | use orion::operators::tensor::core::{Tensor, ExtraParams, TensorTrait}; 3 | use orion::numbers::fixed_point::core::{FixedType, FixedTrait}; 4 | use orion::numbers::fixed_point::implementations::impl_16x16::{FP16x16Impl, ONE, PI, FP16x16Add, FP16x16AddEq, FP16x16Sub, FP16x16Mul, FP16x16Div, FP16x16PartialOrd}; 5 | use array::ArrayTrait; 6 | use debug::PrintTrait; 7 | 8 | const TWO: u128 = 16777216; 9 | 10 | fn all(arr: Tensor) -> bool { 11 | return arr.min() == 1; 12 | } 13 | 14 | fn sum_array(arr: Array) -> FixedType { 15 | return _sum_array_rec(arr, 0, FixedTrait::new(0, false)); 16 | } 17 | 18 | fn _sum_array_rec(arr: Array, idx: usize, mut sum: FixedType) -> FixedType { 19 | sum += *arr.at(idx); 20 | 21 | if idx == arr.len() - 1 { 22 | return sum; 23 | } 24 | 25 | return _sum_array_rec(arr, idx + 1, sum); 26 | } 27 | 28 | // #[test] 29 | // #[available_gas(2000000)] 30 | // fn test_sum_array() { 31 | // let mut arr = ArrayTrait::::new(); 32 | // arr.append(FixedTrait::new(1, false)); 33 | // arr.append(FixedTrait::new(2, false)); 34 | // arr.append(FixedTrait::new(3, false)); 35 | 36 | // let res = sum_array(arr); 37 | // assert(res.mag == 6, 'Magnitude is incorrect'); 38 | // assert(res.sign == false, 'Sign is incorrect'); 39 | // } 40 | --------------------------------------------------------------------------------