From f763269d2aa5330d50eda23a3680ba5464b3d909 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Fri, 6 Dec 2024 00:16:19 +0100 Subject: [PATCH 01/29] scalar --- .gitignore | 6 +- Cargo.lock | 342 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 9 ++ src/lib.rs | 1 + src/secp.rs | 116 ++++++++++++++++++ 5 files changed, 473 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/secp.rs diff --git a/.gitignore b/.gitignore index 7ae7da3..2df413c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ .vscode __pycache__ src/__pycache__ -src/merlin/extension \ No newline at end of file +src/merlin/extension + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..85a82c7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,342 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cashu-kvac" +version = "0.1.0" +dependencies = [ + "once_cell", + "rug", + "secp256k1", +] + +[[package]] +name = "cc" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gmp-mpfr-sys" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0205cd82059bc63b63cf516d714352a30c44f2c74da9961dfda2617ae6b5918" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "libc" +version = "0.2.167" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rug" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ae2c1089ec0575193eb9222881310cc1ed8bce3646ef8b81b44b518595b79d" +dependencies = [ + "az", + "gmp-mpfr-sys", + "libc", + "libm", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0895145 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "cashu-kvac" +version = "0.1.0" +edition = "2021" + +[dependencies] +once_cell = "1.20.2" +rug = "1.26.1" +secp256k1 = {version="^0.30.0", features=["rand", "serde", "std"]} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8b06c62 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod secp; diff --git a/src/secp.rs b/src/secp.rs new file mode 100644 index 0000000..7b3bcdd --- /dev/null +++ b/src/secp.rs @@ -0,0 +1,116 @@ +use secp256k1::{rand, All, Error, PublicKey, Secp256k1, SecretKey}; +use std::ops::{Add, Sub, Mul, Neg}; +use once_cell::sync::Lazy; + +pub const SCALAR_ZERO: [u8; 32] = [0; 32]; + +/// Secp256k1 global context +pub static SECP256K1: Lazy> = Lazy::new(|| { + let mut ctx = Secp256k1::new(); + let mut rng = rand::thread_rng(); + ctx.randomize(&mut rng); + ctx +}); + +pub struct Scalar { + inner: Option, + is_zero: bool, +} + +impl Scalar{ + pub fn new(data: &[u8; 32]) -> Result { + if *data == SCALAR_ZERO { + Ok(Scalar { + inner: None, + is_zero: true, + }) + } else { + let inner = SecretKey::from_byte_array(data).expect("no"); + Ok(Scalar { inner: Some(inner), is_zero: false }) + } + } + + pub fn random() -> Result { + let inner = SecretKey::new(&mut rand::thread_rng()); + Ok(Scalar { inner: Some(inner), is_zero: false }) + } + + pub fn clone(&self) -> Self { + Scalar { + inner: self.inner.clone(), + is_zero: self.is_zero, + } + } +} + +impl Add for Scalar{ + type Output = Result; + + fn add(self, other: Scalar) -> Result { + if other.is_zero { + Ok(self.clone()) + } else if self.is_zero { + Ok(other.clone()) + } else { + let t_other = secp256k1::Scalar::from_be_bytes(other.inner.unwrap().secret_bytes()).unwrap(); + let result = self.clone(); + result.inner.unwrap().add_tweak(&t_other)?; + Ok(result) + } + } +} + +impl Neg for Scalar{ + type Output = Result; + + fn neg(self) -> Result { + if self.is_zero { + Ok(self.clone()) + } else { + let result = self.clone(); + let _ = result.inner.unwrap().negate(); + Ok(result) + } + } +} + +impl Sub for Scalar{ + type Output = Result; + + fn sub(self, other: Scalar) -> Result { + if other.is_zero { + Ok(self.clone()) + } else if self.is_zero { + -other + } else { + self + (-other)? + } + } +} + +impl Mul for Scalar{ + type Output = Result; + + fn mul(self, other: Scalar) -> Result { + if other.is_zero || self.is_zero { + Scalar::new(&SCALAR_ZERO) + } else { + // Masked multiplication (constant time) + let r = Scalar::random()?; + let b = secp256k1::Scalar::from_be_bytes(other.inner.unwrap().secret_bytes())?; + let a_r_times_b = (r+self)?.inner.unwrap().mul_tweak(&b)?; + r.inner.unwrap().mul_tweak(&b); + a_r_times_b - r + } + } +} + + + + + + + + + + From efdf34a651395376fd1d2dbde618d15fad9bf2a2 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Fri, 6 Dec 2024 11:23:44 +0100 Subject: [PATCH 02/29] arithmetic and equality for Scalars --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/secp.rs | 245 +++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 214 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85a82c7..f4d6b90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,7 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" name = "cashu-kvac" version = "0.1.0" dependencies = [ + "hex", "once_cell", "rug", "secp256k1", @@ -81,6 +82,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hex-conservative" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 0895145..fa7b463 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +hex = "0.4.3" once_cell = "1.20.2" rug = "1.26.1" secp256k1 = {version="^0.30.0", features=["rand", "serde", "std"]} diff --git a/src/secp.rs b/src/secp.rs index 7b3bcdd..1141cc8 100644 --- a/src/secp.rs +++ b/src/secp.rs @@ -1,16 +1,9 @@ -use secp256k1::{rand, All, Error, PublicKey, Secp256k1, SecretKey}; +use secp256k1::{rand, All, PublicKey, Secp256k1, SecretKey}; use std::ops::{Add, Sub, Mul, Neg}; -use once_cell::sync::Lazy; +use std::cmp::PartialEq; pub const SCALAR_ZERO: [u8; 32] = [0; 32]; - -/// Secp256k1 global context -pub static SECP256K1: Lazy> = Lazy::new(|| { - let mut ctx = Secp256k1::new(); - let mut rng = rand::thread_rng(); - ctx.randomize(&mut rng); - ctx -}); +pub const SCALAR_ONE: [u8; 32] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; pub struct Scalar { inner: Option, @@ -18,21 +11,21 @@ pub struct Scalar { } impl Scalar{ - pub fn new(data: &[u8; 32]) -> Result { + pub fn new(data: &[u8; 32]) -> Self { if *data == SCALAR_ZERO { - Ok(Scalar { + Scalar { inner: None, is_zero: true, - }) + } } else { let inner = SecretKey::from_byte_array(data).expect("no"); - Ok(Scalar { inner: Some(inner), is_zero: false }) + Scalar { inner: Some(inner), is_zero: false } } } - pub fn random() -> Result { + pub fn random() -> Self { let inner = SecretKey::new(&mut rand::thread_rng()); - Ok(Scalar { inner: Some(inner), is_zero: false }) + Scalar { inner: Some(inner), is_zero: false } } pub fn clone(&self) -> Self { @@ -41,71 +34,245 @@ impl Scalar{ is_zero: self.is_zero, } } -} -impl Add for Scalar{ - type Output = Result; + pub fn tweak_mul(&self, other: &Scalar) -> Self { + if other.is_zero || self.is_zero { + return self.clone(); + } + let b = secp256k1::Scalar::from_be_bytes(other.inner.unwrap().secret_bytes()).unwrap(); + let result = self.clone(); + let _ = result.inner.unwrap().mul_tweak(&b); + result + } - fn add(self, other: Scalar) -> Result { + pub fn tweak_add(&self, other: &Scalar) -> Self { if other.is_zero { - Ok(self.clone()) + self.clone() } else if self.is_zero { - Ok(other.clone()) + other.clone() } else { let t_other = secp256k1::Scalar::from_be_bytes(other.inner.unwrap().secret_bytes()).unwrap(); let result = self.clone(); - result.inner.unwrap().add_tweak(&t_other)?; - Ok(result) + let _ = result.inner.unwrap().add_tweak(&t_other); + result + } + } + + /* + pub fn invert(&self) -> Self { + if self.is_zero { + self.clone() + } else { + } + + } + */ +} + +impl Add for Scalar{ + type Output = Scalar; + + fn add(self, other: Scalar) -> Scalar { + self.tweak_add(&other) } } impl Neg for Scalar{ - type Output = Result; + type Output = Scalar; - fn neg(self) -> Result { + fn neg(self) -> Scalar { if self.is_zero { - Ok(self.clone()) + self.clone() } else { let result = self.clone(); let _ = result.inner.unwrap().negate(); - Ok(result) + result } } } impl Sub for Scalar{ - type Output = Result; + type Output = Scalar; - fn sub(self, other: Scalar) -> Result { + fn sub(self, other: Scalar) -> Scalar { if other.is_zero { - Ok(self.clone()) + self.clone() } else if self.is_zero { -other } else { - self + (-other)? + self + (-other) } } } impl Mul for Scalar{ - type Output = Result; + type Output = Scalar; - fn mul(self, other: Scalar) -> Result { + fn mul(self, other: Scalar) -> Scalar { if other.is_zero || self.is_zero { Scalar::new(&SCALAR_ZERO) } else { // Masked multiplication (constant time) - let r = Scalar::random()?; - let b = secp256k1::Scalar::from_be_bytes(other.inner.unwrap().secret_bytes())?; - let a_r_times_b = (r+self)?.inner.unwrap().mul_tweak(&b)?; - r.inner.unwrap().mul_tweak(&b); - a_r_times_b - r + let r = Scalar::random(); + let a_plus_r = self.tweak_add(&r); + let a_plus_r_times_b = a_plus_r.tweak_mul(&r); + let r_times_b = r.tweak_mul(&other); + a_plus_r_times_b - r_times_b + } + } +} + +impl PartialEq for Scalar { + fn eq(&self, other: &Self) -> bool { + if self.is_zero && other.is_zero { + return true; + } + if self.is_zero || other.is_zero { + return false; + } + let mut b = 0u8; + for (x, y) in self.inner.as_ref().unwrap().secret_bytes().iter().zip(other.inner.as_ref().unwrap().secret_bytes().iter()) { + b |= x ^ y; } + b == 0 } } +impl From for Scalar { + fn from(value: u64) -> Self { + let mut bytes = [0u8; 32]; + bytes[31] = (value >> 56) as u8; + bytes[30] = (value >> 48) as u8; + bytes[29] = (value >> 40) as u8; + bytes[28] = (value >> 32) as u8; + bytes[27] = (value >> 24) as u8; + bytes[26] = (value >> 16) as u8; + bytes[25] = (value >> 8) as u8; + bytes[24] = value as u8; + Scalar::new(&bytes) + } +} + +impl From<&str> for Scalar { + fn from(hex_string: &str) -> Self { + let bytes = hex::decode(hex_string).expect("Invalid hex string"); + if bytes.len() > 32 { + panic!("Hex string is too long"); + } + let mut padded_bytes = [0u8; 32]; + padded_bytes[32 - bytes.len()..].copy_from_slice(&bytes); + Scalar::new(&padded_bytes) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_scalar() { + let data = [1u8; 32]; + let scalar = Scalar::new(&data); + assert!(!scalar.is_zero); + } + + #[test] + fn test_new_zero_scalar() { + let scalar = Scalar::new(&SCALAR_ZERO); + assert!(scalar.is_zero); + } + + #[test] + fn test_random_scalar() { + let scalar = Scalar::random(); + assert!(!scalar.is_zero); + } + + #[test] + fn test_clone_scalar() { + let scalar = Scalar::random(); + let cloned_scalar = scalar.clone(); + assert_eq!(scalar.inner, cloned_scalar.inner); + assert_eq!(scalar.is_zero, cloned_scalar.is_zero); + } + + #[test] + fn test_tweak_mul() { + let scalar1 = Scalar::random(); + let scalar2 = Scalar::random(); + let result = scalar1.tweak_mul(&scalar2); + assert!(!result.is_zero); + } + + #[test] + fn test_tweak_add() { + let scalar1 = Scalar::random(); + let scalar2 = Scalar::random(); + let result = scalar1.tweak_add(&scalar2); + assert!(!result.is_zero); + } + + #[test] + fn test_add() { + let scalar1 = Scalar::random(); + let scalar2 = Scalar::random(); + let result = scalar1 + scalar2; + assert!(!result.is_zero); + } + + #[test] + fn test_neg() { + let scalar = Scalar::random(); + let neg_scalar = -scalar; + assert!(!neg_scalar.is_zero); + } + + #[test] + fn test_sub() { + let scalar1 = Scalar::random(); + let scalar2 = Scalar::random(); + let result = scalar1 - scalar2; + assert!(!result.is_zero); + } + + #[test] + fn test_mul() { + let scalar1 = Scalar::random(); + let scalar2 = Scalar::random(); + let result = scalar1 * scalar2; + assert!(!result.is_zero); + } + + #[test] + fn test_mul_zero() { + let scalar1 = Scalar::random(); + let scalar2 = Scalar::new(&SCALAR_ZERO); + let result = scalar1 * scalar2; + assert!(result.is_zero); + } + + #[test] + fn test_mul_by_zero() { + let scalar1 = Scalar::new(&SCALAR_ZERO); + let scalar2 = Scalar::random(); + let result = scalar1 * scalar2; + assert!(result.is_zero); + } + + #[test] + fn test_mul_cmp() { + let a = Scalar::random(); + let b = Scalar::random(); + let c = a.tweak_mul(&b); + let c_ = a*b; + assert!(c == c_); + } +} + + + From 5e5e22a3a49f51c395ad233b0d2e72d8ee99c423 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Fri, 6 Dec 2024 20:03:57 +0100 Subject: [PATCH 03/29] trying modular inversion --- src/secp.py | 5 -- src/secp.rs | 146 +++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 128 insertions(+), 23 deletions(-) diff --git a/src/secp.py b/src/secp.py index 6ffd241..ba95067 100644 --- a/src/secp.py +++ b/src/secp.py @@ -10,7 +10,6 @@ def div2(M, x): """Helper routine to compute x/2 mod M (where M is odd).""" - assert M & 1 if x & 1: # If x is odd, make it even by adding M. x += M # x must be even now, so a clean division by 2 is possible. @@ -30,10 +29,6 @@ def modinv(M, x): delta, f, g, d, e = 1 + delta, f, (g + f) // 2, d, div2(M, e + d) else: delta, f, g, d, e = 1 + delta, f, (g ) // 2, d, div2(M, e ) - # Verify that the invariants d=f/x mod M, e=g/x mod M are maintained. - assert f % M == (d * x) % M - assert g % M == (e * x) % M - assert f == 1 or f == -1 # |f| is the GCD, it must be 1 # Because of invariant d = f/x (mod M), 1/x = d/f (mod M). As |f|=1, d/f = d*f. return (d * f) % M diff --git a/src/secp.rs b/src/secp.rs index 1141cc8..52a4043 100644 --- a/src/secp.rs +++ b/src/secp.rs @@ -1,6 +1,8 @@ -use secp256k1::{rand, All, PublicKey, Secp256k1, SecretKey}; +use secp256k1::constants::CURVE_ORDER; +use secp256k1::{rand, PublicKey, SecretKey}; use std::ops::{Add, Sub, Mul, Neg}; use std::cmp::PartialEq; +use rug::Integer; pub const SCALAR_ZERO: [u8; 32] = [0; 32]; pub const SCALAR_ONE: [u8; 32] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; @@ -10,6 +12,50 @@ pub struct Scalar { is_zero: bool, } +fn div2(m: &Integer, mut x: Integer) -> Integer { + if x.is_odd() { + x += m; + } + x >> 1 +} + +fn constant_time_modinv(m: &Integer, x: &Integer) -> Integer { + assert!(m.is_odd(), "M must be odd"); + let mut delta = 1; + let mut f = m.clone(); + let mut g = x.clone(); + let mut d = Integer::from(0); + let mut e = Integer::from(1); + + while !g.is_zero() { + if delta > 0 && g.is_odd() { + let tmp_g = g.clone(); + g = (g - &f) >> 1; + f = tmp_g; + let tmp_d = d.clone(); + d = div2(m, e - &d); + e = tmp_d; + delta = -delta + 1; + } else if g.is_odd() { + g = (g + &f) >> 1; + e = div2(m, e + &d); + delta = delta + 1; + } else { + g >>= 1; + d = div2(m, d + &e); + delta = delta + 1; + } + } + + // Result: (d * f) % m + assert!(f == 1 || f == -1); + if f.is_negative() ^ d.is_negative() { + d + m + } else { + d + } +} + impl Scalar{ pub fn new(data: &[u8; 32]) -> Self { if *data == SCALAR_ZERO { @@ -58,16 +104,19 @@ impl Scalar{ } } - /* pub fn invert(&self) -> Self { if self.is_zero { - self.clone() + panic!("Scalar 0 doesn't have an inverse") } else { - + let x = Integer::from_digits(&self.inner.unwrap().secret_bytes(), rug::integer::Order::Msf); + let q = Integer::from_digits(&CURVE_ORDER, rug::integer::Order::Msf); + let x_inv = constant_time_modinv(&q, &x); + let mut data = [0u8; 32]; + let vec = x_inv.to_digits(rug::integer::Order::Msf); + data.copy_from_slice(&vec[0..32]); + Scalar::new(&data) } - } - */ } impl Add for Scalar{ @@ -123,22 +172,25 @@ impl Mul for Scalar{ } } -impl PartialEq for Scalar { - fn eq(&self, other: &Self) -> bool { - if self.is_zero && other.is_zero { - return true; - } - if self.is_zero || other.is_zero { - return false; - } - let mut b = 0u8; - for (x, y) in self.inner.as_ref().unwrap().secret_bytes().iter().zip(other.inner.as_ref().unwrap().secret_bytes().iter()) { - b |= x ^ y; +impl Into> for Scalar { + fn into(self) -> Vec { + if self.is_zero { + SCALAR_ZERO.to_vec() + } else { + self.inner.unwrap().secret_bytes().to_vec() } - b == 0 } } +impl Into for Scalar { + fn into(self) -> String { + if self.is_zero { + hex::encode(SCALAR_ZERO) + } else { + hex::encode(self.inner.unwrap().secret_bytes()) + } + } +} impl From for Scalar { fn from(value: u64) -> Self { @@ -167,6 +219,23 @@ impl From<&str> for Scalar { } } +impl PartialEq for Scalar { + fn eq(&self, other: &Self) -> bool { + if self.is_zero && other.is_zero { + return true; + } + if self.is_zero || other.is_zero { + return false; + } + let mut b = 0u8; + for (x, y) in self.inner.as_ref().unwrap().secret_bytes().iter().zip(other.inner.as_ref().unwrap().secret_bytes().iter()) { + b |= x ^ y; + } + b == 0 + } +} + + #[cfg(test)] mod tests { use super::*; @@ -269,6 +338,47 @@ mod tests { let c_ = a*b; assert!(c == c_); } + + #[test] + fn test_scalar_into_vec() { + let scalar = Scalar::random(); + let bytes: Vec = scalar.clone().into(); + assert_eq!(bytes.len(), 32); + assert!(bytes.iter().any(|&b| b != 0)); // Ensure it's not all zeros + } + + #[test] + fn test_zero_scalar_into_vec() { + let scalar = Scalar::new(&SCALAR_ZERO); + let bytes: Vec = scalar.into(); + assert_eq!(bytes, SCALAR_ZERO.to_vec()); + } + + #[test] + fn test_scalar_into_string() { + let scalar = Scalar::random(); + let hex_str: String = scalar.into(); + assert_eq!(hex_str.len(), 64); + assert!(hex::decode(&hex_str).is_ok()); + } + + #[test] + fn test_zero_scalar_into_string() { + let scalar = Scalar::new(&SCALAR_ZERO); + let hex_str: String = scalar.into(); + assert_eq!(hex_str, hex::encode(SCALAR_ZERO)); + } + + #[test] + fn test_scalar_modular_inversion() { + let one = Scalar::new(&SCALAR_ONE); + let scalar = Scalar::random(); + let scalar_inv = scalar.invert(); + let prod = scalar*scalar_inv; + let prod_hex: String = prod.clone().into(); + println!("x_inv (Display): {}", prod_hex); + assert!(one == prod); + } } From 922bb0f48217146c1e620fc9ebf8944125d039ba Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sat, 7 Dec 2024 01:13:15 +0100 Subject: [PATCH 04/29] fix arithmetic --- src/secp.py | 9 ++++- src/secp.rs | 102 ++++++++++++++++++++++++++-------------------------- 2 files changed, 60 insertions(+), 51 deletions(-) diff --git a/src/secp.py b/src/secp.py index ba95067..6f657e9 100644 --- a/src/secp.py +++ b/src/secp.py @@ -192,4 +192,11 @@ def to_data(self): return [self.public_key.data[i] for i in range(64)] scalar_one = Scalar(int(1).to_bytes(32, 'big')) -scalar_zero = Scalar(SCALAR_ZERO) \ No newline at end of file +scalar_zero = Scalar(SCALAR_ZERO) + +if __name__ == '__main__': + s = Scalar(bytes.fromhex("00"*28+"deadbeef")) + s_inv = s.invert() + s_inv_num = int.from_bytes(s_inv.to_bytes(), "big") + print(f"{s_inv_num = }") + print(f"{s_inv.serialize() = }") \ No newline at end of file diff --git a/src/secp.rs b/src/secp.rs index 52a4043..2093dc1 100644 --- a/src/secp.rs +++ b/src/secp.rs @@ -35,11 +35,11 @@ fn constant_time_modinv(m: &Integer, x: &Integer) -> Integer { let tmp_d = d.clone(); d = div2(m, e - &d); e = tmp_d; - delta = -delta + 1; + delta = 1 - delta; } else if g.is_odd() { g = (g + &f) >> 1; e = div2(m, e + &d); - delta = delta + 1; + delta = 1 + delta; } else { g >>= 1; d = div2(m, d + &e); @@ -49,10 +49,10 @@ fn constant_time_modinv(m: &Integer, x: &Integer) -> Integer { // Result: (d * f) % m assert!(f == 1 || f == -1); - if f.is_negative() ^ d.is_negative() { + if d.is_negative() ^ f.is_negative() { d + m } else { - d + d * f } } @@ -76,7 +76,7 @@ impl Scalar{ pub fn clone(&self) -> Self { Scalar { - inner: self.inner.clone(), + inner: Some(self.inner.unwrap().clone()), is_zero: self.is_zero, } } @@ -87,8 +87,8 @@ impl Scalar{ } let b = secp256k1::Scalar::from_be_bytes(other.inner.unwrap().secret_bytes()).unwrap(); let result = self.clone(); - let _ = result.inner.unwrap().mul_tweak(&b); - result + let result = result.inner.unwrap().mul_tweak(&b).unwrap(); + Scalar{inner: Some(result), is_zero: false} } pub fn tweak_add(&self, other: &Scalar) -> Self { @@ -99,8 +99,18 @@ impl Scalar{ } else { let t_other = secp256k1::Scalar::from_be_bytes(other.inner.unwrap().secret_bytes()).unwrap(); let result = self.clone(); - let _ = result.inner.unwrap().add_tweak(&t_other); - result + let result_key = result.inner.unwrap().add_tweak(&t_other).unwrap(); + Scalar{ inner:Some(result_key), is_zero: false } + } + } + + pub fn tweak_neg(&self) -> Self { + if self.is_zero { + self.clone() + } else { + let result = self.clone(); + let result_key = result.inner.unwrap().negate(); + Scalar{inner:Some(result_key), is_zero: false} } } @@ -110,7 +120,9 @@ impl Scalar{ } else { let x = Integer::from_digits(&self.inner.unwrap().secret_bytes(), rug::integer::Order::Msf); let q = Integer::from_digits(&CURVE_ORDER, rug::integer::Order::Msf); - let x_inv = constant_time_modinv(&q, &x); + //let x_inv = constant_time_modinv(&q, &x); + let x_inv = x.clone().invert(&q).unwrap(); + println!("x_inv = {}", x_inv); let mut data = [0u8; 32]; let vec = x_inv.to_digits(rug::integer::Order::Msf); data.copy_from_slice(&vec[0..32]); @@ -131,13 +143,7 @@ impl Neg for Scalar{ type Output = Scalar; fn neg(self) -> Scalar { - if self.is_zero { - self.clone() - } else { - let result = self.clone(); - let _ = result.inner.unwrap().negate(); - result - } + self.tweak_neg() } } @@ -165,7 +171,7 @@ impl Mul for Scalar{ // Masked multiplication (constant time) let r = Scalar::random(); let a_plus_r = self.tweak_add(&r); - let a_plus_r_times_b = a_plus_r.tweak_mul(&r); + let a_plus_r_times_b = a_plus_r.tweak_mul(&other); let r_times_b = r.tweak_mul(&other); a_plus_r_times_b - r_times_b } @@ -214,7 +220,7 @@ impl From<&str> for Scalar { panic!("Hex string is too long"); } let mut padded_bytes = [0u8; 32]; - padded_bytes[32 - bytes.len()..].copy_from_slice(&bytes); + padded_bytes[32-bytes.len()..32].copy_from_slice(&bytes); Scalar::new(&padded_bytes) } } @@ -269,49 +275,47 @@ mod tests { #[test] fn test_tweak_mul() { - let scalar1 = Scalar::random(); - let scalar2 = Scalar::random(); - let result = scalar1.tweak_mul(&scalar2); - assert!(!result.is_zero); + let scalar1 = Scalar::from("02"); + let scalar2 = Scalar::from("03"); + let result = Scalar::from("06"); + let result_ = scalar1.tweak_mul(&scalar2); + assert!(result_ == result); } #[test] fn test_tweak_add() { - let scalar1 = Scalar::random(); - let scalar2 = Scalar::random(); - let result = scalar1.tweak_add(&scalar2); - assert!(!result.is_zero); + let scalar1 = Scalar::from("02"); + let scalar2 = Scalar::from("03"); + let result = Scalar::from("05"); + let result_ = scalar1.tweak_add(&scalar2); + assert!(result == result_); } #[test] fn test_add() { - let scalar1 = Scalar::random(); - let scalar2 = Scalar::random(); - let result = scalar1 + scalar2; - assert!(!result.is_zero); - } - - #[test] - fn test_neg() { - let scalar = Scalar::random(); - let neg_scalar = -scalar; - assert!(!neg_scalar.is_zero); + let scalar1 = Scalar::from("02"); + let scalar2 = Scalar::from("03"); + let result = Scalar::from("05"); + let result_ = scalar1 + scalar2; + assert!(result_ == result); } #[test] fn test_sub() { - let scalar1 = Scalar::random(); - let scalar2 = Scalar::random(); - let result = scalar1 - scalar2; - assert!(!result.is_zero); + let scalar1 = Scalar::from("10"); + let scalar2 = Scalar::from("02"); + let result = Scalar::from("0e"); + let result_ = scalar1 - scalar2; + assert!(result == result_); } #[test] fn test_mul() { - let scalar1 = Scalar::random(); - let scalar2 = Scalar::random(); - let result = scalar1 * scalar2; - assert!(!result.is_zero); + let scalar1 = Scalar::from("02"); + let scalar2 = Scalar::from("03"); + let result = Scalar::from("06"); + let result_ = scalar1 * scalar2; + assert!(result_ == result); } #[test] @@ -372,11 +376,9 @@ mod tests { #[test] fn test_scalar_modular_inversion() { let one = Scalar::new(&SCALAR_ONE); - let scalar = Scalar::random(); + let scalar = Scalar::from("deadbeef"); let scalar_inv = scalar.invert(); - let prod = scalar*scalar_inv; - let prod_hex: String = prod.clone().into(); - println!("x_inv (Display): {}", prod_hex); + let prod = scalar * scalar_inv; assert!(one == prod); } } From 6bbe139ccf0048863d2fea73973d195840da8885 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sat, 7 Dec 2024 20:16:07 +0100 Subject: [PATCH 05/29] fix const modular inversion --- src/lib.rs | 0 src/secp.rs | 37 +++++++++++++++++++++++-------------- 2 files changed, 23 insertions(+), 14 deletions(-) mode change 100644 => 100755 src/lib.rs mode change 100644 => 100755 src/secp.rs diff --git a/src/lib.rs b/src/lib.rs old mode 100644 new mode 100755 diff --git a/src/secp.rs b/src/secp.rs old mode 100644 new mode 100755 index 2093dc1..2f441be --- a/src/secp.rs +++ b/src/secp.rs @@ -1,3 +1,4 @@ +use rug::ops::RemRounding; use secp256k1::constants::CURVE_ORDER; use secp256k1::{rand, PublicKey, SecretKey}; use std::ops::{Add, Sub, Mul, Neg}; @@ -32,9 +33,9 @@ fn constant_time_modinv(m: &Integer, x: &Integer) -> Integer { let tmp_g = g.clone(); g = (g - &f) >> 1; f = tmp_g; - let tmp_d = d.clone(); - d = div2(m, e - &d); - e = tmp_d; + let tmp_e = e.clone(); + e = div2(m, e - &d); + d = tmp_e; delta = 1 - delta; } else if g.is_odd() { g = (g + &f) >> 1; @@ -42,18 +43,13 @@ fn constant_time_modinv(m: &Integer, x: &Integer) -> Integer { delta = 1 + delta; } else { g >>= 1; - d = div2(m, d + &e); - delta = delta + 1; + e = div2(m, e); + delta = 1 + delta; } } // Result: (d * f) % m - assert!(f == 1 || f == -1); - if d.is_negative() ^ f.is_negative() { - d + m - } else { - d * f - } + (d * f).rem_euc(m) } impl Scalar{ @@ -120,9 +116,8 @@ impl Scalar{ } else { let x = Integer::from_digits(&self.inner.unwrap().secret_bytes(), rug::integer::Order::Msf); let q = Integer::from_digits(&CURVE_ORDER, rug::integer::Order::Msf); - //let x_inv = constant_time_modinv(&q, &x); - let x_inv = x.clone().invert(&q).unwrap(); - println!("x_inv = {}", x_inv); + let x_inv = constant_time_modinv(&q, &x); + //let x_inv = x.clone().invert(&q).unwrap(); let mut data = [0u8; 32]; let vec = x_inv.to_digits(rug::integer::Order::Msf); data.copy_from_slice(&vec[0..32]); @@ -373,6 +368,20 @@ mod tests { assert_eq!(hex_str, hex::encode(SCALAR_ZERO)); } + #[test] + fn test_div2_even() { + let m = Integer::from(29); + let x = Integer::from(20); + assert_eq!(div2(&m, x), Integer::from(10)); + } + + #[test] + fn test_div2_odd() { + let m = Integer::from(29); + let x = Integer::from(21); + assert_eq!(div2(&m, x), Integer::from(25)); + } + #[test] fn test_scalar_modular_inversion() { let one = Scalar::new(&SCALAR_ONE); From ddd4be7f82b5cbd8f7eef00c77499ef9d3164441 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sat, 7 Dec 2024 21:17:50 +0100 Subject: [PATCH 06/29] modinv --- src/secp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/secp.rs b/src/secp.rs index 2f441be..62d65ed 100755 --- a/src/secp.rs +++ b/src/secp.rs @@ -20,7 +20,7 @@ fn div2(m: &Integer, mut x: Integer) -> Integer { x >> 1 } -fn constant_time_modinv(m: &Integer, x: &Integer) -> Integer { +fn modinv(m: &Integer, x: &Integer) -> Integer { assert!(m.is_odd(), "M must be odd"); let mut delta = 1; let mut f = m.clone(); @@ -116,7 +116,7 @@ impl Scalar{ } else { let x = Integer::from_digits(&self.inner.unwrap().secret_bytes(), rug::integer::Order::Msf); let q = Integer::from_digits(&CURVE_ORDER, rug::integer::Order::Msf); - let x_inv = constant_time_modinv(&q, &x); + let x_inv = modinv(&q, &x); //let x_inv = x.clone().invert(&q).unwrap(); let mut data = [0u8; 32]; let vec = x_inv.to_digits(rug::integer::Order::Msf); From a662fedecd505e880f9ba20cf36e73c282f79f53 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sun, 8 Dec 2024 00:28:33 +0100 Subject: [PATCH 07/29] `GroupElement` + clone-less arithmetic --- src/secp.rs | 257 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 196 insertions(+), 61 deletions(-) diff --git a/src/secp.rs b/src/secp.rs index 62d65ed..f269719 100755 --- a/src/secp.rs +++ b/src/secp.rs @@ -1,18 +1,33 @@ +use once_cell::sync::Lazy; use rug::ops::RemRounding; use secp256k1::constants::CURVE_ORDER; -use secp256k1::{rand, PublicKey, SecretKey}; +use secp256k1::{rand, All, PublicKey, Secp256k1, SecretKey}; use std::ops::{Add, Sub, Mul, Neg}; use std::cmp::PartialEq; use rug::Integer; pub const SCALAR_ZERO: [u8; 32] = [0; 32]; pub const SCALAR_ONE: [u8; 32] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; +pub const GROUP_ELEMENT_ZERO: [u8; 33] = [0; 33]; + +/// Secp256k1 global context +pub static SECP256K1: Lazy> = Lazy::new(|| { + let mut ctx = Secp256k1::new(); + let mut rng = rand::thread_rng(); + ctx.randomize(&mut rng); + ctx +}); pub struct Scalar { inner: Option, is_zero: bool, } +pub struct GroupElement { + inner: Option, + is_zero: bool, +} + fn div2(m: &Integer, mut x: Integer) -> Integer { if x.is_odd() { x += m; @@ -60,7 +75,7 @@ impl Scalar{ is_zero: true, } } else { - let inner = SecretKey::from_byte_array(data).expect("no"); + let inner = SecretKey::from_byte_array(data).expect("Could not instantiate Scalar"); Scalar { inner: Some(inner), is_zero: false } } } @@ -77,40 +92,46 @@ impl Scalar{ } } - pub fn tweak_mul(&self, other: &Scalar) -> Self { + pub fn tweak_mul(&mut self, other: &Scalar) -> &Self { if other.is_zero || self.is_zero { - return self.clone(); + self.is_zero = true; + self.inner = None; + return self; } let b = secp256k1::Scalar::from_be_bytes(other.inner.unwrap().secret_bytes()).unwrap(); - let result = self.clone(); - let result = result.inner.unwrap().mul_tweak(&b).unwrap(); - Scalar{inner: Some(result), is_zero: false} + let result = self.inner.unwrap().mul_tweak(&b).expect("Could not multiply Scalars"); + self.inner = Some(result); + self } - pub fn tweak_add(&self, other: &Scalar) -> Self { + pub fn tweak_add(&mut self, other: &Scalar) -> &Self { if other.is_zero { - self.clone() + self } else if self.is_zero { - other.clone() + self.inner = Some(other.inner.unwrap().clone()); + self.is_zero = false; + self } else { - let t_other = secp256k1::Scalar::from_be_bytes(other.inner.unwrap().secret_bytes()).unwrap(); - let result = self.clone(); - let result_key = result.inner.unwrap().add_tweak(&t_other).unwrap(); - Scalar{ inner:Some(result_key), is_zero: false } + let b = secp256k1::Scalar::from_be_bytes( + other.inner.unwrap().secret_bytes() + ).unwrap(); + let result_key = self.inner.unwrap().add_tweak(&b).expect("Could not add to Scalar"); + self.inner = Some(result_key); + self } } - pub fn tweak_neg(&self) -> Self { + pub fn tweak_neg(&mut self) -> &Self { if self.is_zero { - self.clone() + self } else { - let result = self.clone(); - let result_key = result.inner.unwrap().negate(); - Scalar{inner:Some(result_key), is_zero: false} + let result = self.inner.unwrap().negate(); + self.inner = Some(result); + self } } - pub fn invert(&self) -> Self { + pub fn invert(&mut self) -> &Self { if self.is_zero { panic!("Scalar 0 doesn't have an inverse") } else { @@ -121,54 +142,124 @@ impl Scalar{ let mut data = [0u8; 32]; let vec = x_inv.to_digits(rug::integer::Order::Msf); data.copy_from_slice(&vec[0..32]); - Scalar::new(&data) + let inner = SecretKey::from_byte_array(&data).expect("Could not instantiate Scalar"); + self.inner = Some(inner); + self + } + } +} + +impl GroupElement { + pub fn new(data: &[u8; 33]) -> Self { + if *data == GROUP_ELEMENT_ZERO { + GroupElement { + inner: None, + is_zero: true, + } + } else { + let inner = PublicKey::from_byte_array_compressed(data).expect("Cannot create GroupElement"); + GroupElement { inner: Some(inner), is_zero: false } + } + } + + pub fn clone(&self) -> Self { + GroupElement { + inner: Some(self.inner.unwrap().clone()), + is_zero: self.is_zero, + } + } + + pub fn combine_add(&mut self, other: &GroupElement) -> &Self { + if other.is_zero { + self + } else if self.is_zero { + self.inner = other.inner.clone(); + self.is_zero = other.is_zero; + self + } else { + let result = self.inner.unwrap() + .combine(&other.inner.unwrap()) + .expect("Error combining GroupElements"); + self.inner = Some(result); + self + } + } + + pub fn multiply(&mut self, scalar: &Scalar) -> &Self { + if scalar.is_zero || self.is_zero { + self.is_zero = true; + self.inner = None; + self + } else { + let b = secp256k1::Scalar::from_be_bytes(scalar.inner.unwrap().secret_bytes()).unwrap(); + let result = self.inner.unwrap() + .mul_tweak(&SECP256K1, &b) + .expect("Could not multiply Scalar to GroupElement"); + self.inner = Some(result); + self + } + } + + pub fn negate(&mut self) -> &Self { + if self.is_zero { + self + } else { + let result = self.inner.unwrap() + .negate(&SECP256K1); + self.inner = Some(result); + self } } } -impl Add for Scalar{ +impl std::ops::Add<&Scalar> for Scalar{ type Output = Scalar; - fn add(self, other: Scalar) -> Scalar { - self.tweak_add(&other) + fn add(mut self, other: &Scalar) -> Scalar { + self.tweak_add(&other); + self } } -impl Neg for Scalar{ +impl std::ops::Neg for Scalar{ type Output = Scalar; - fn neg(self) -> Scalar { - self.tweak_neg() + fn neg(mut self) -> Scalar { + self.tweak_neg(); + self } } -impl Sub for Scalar{ +impl std::ops::Sub<&Scalar> for Scalar{ type Output = Scalar; - fn sub(self, other: Scalar) -> Scalar { + fn sub(self, other: &Scalar) -> Scalar { if other.is_zero { - self.clone() + self } else if self.is_zero { - -other + -(other.clone()) } else { - self + (-other) + let other_neg = -(other.clone()); + self + &other_neg } } } -impl Mul for Scalar{ +impl std::ops::Mul<&Scalar> for Scalar{ type Output = Scalar; - fn mul(self, other: Scalar) -> Scalar { + fn mul(mut self, other: &Scalar) -> Scalar { if other.is_zero || self.is_zero { - Scalar::new(&SCALAR_ZERO) + self.inner = None; + self.is_zero = true; + self } else { // Masked multiplication (constant time) - let r = Scalar::random(); - let a_plus_r = self.tweak_add(&r); - let a_plus_r_times_b = a_plus_r.tweak_mul(&other); - let r_times_b = r.tweak_mul(&other); - a_plus_r_times_b - r_times_b + let mut r = Scalar::random(); + self.tweak_add(&r); + self.tweak_mul(&other); + r.tweak_mul(&other); + self - &r } } } @@ -183,6 +274,16 @@ impl Into> for Scalar { } } +impl Into<[u8; 32]> for Scalar { + fn into(self) -> [u8; 32] { + if self.is_zero { + SCALAR_ZERO + } else { + self.inner.unwrap().secret_bytes() + } + } +} + impl Into for Scalar { fn into(self) -> String { if self.is_zero { @@ -236,6 +337,38 @@ impl PartialEq for Scalar { } } +impl std::ops::Add<&GroupElement> for GroupElement { + type Output = GroupElement; + + fn add(mut self, other: &GroupElement) -> GroupElement { + self.combine_add(other); + self + } +} + +impl std::ops::Neg for GroupElement { + type Output = GroupElement; + + fn neg(mut self) -> GroupElement { + self.negate(); + self + } +} + +impl std::ops::Sub<&GroupElement> for GroupElement { + type Output = GroupElement; + + fn sub(self, other: &GroupElement) -> GroupElement { + if other.is_zero { + self + } else if self.is_zero { + -(other.clone()) + } else { + let other_neg = -(other.clone()); + self + &other_neg + } + } +} #[cfg(test)] mod tests { @@ -269,63 +402,63 @@ mod tests { } #[test] - fn test_tweak_mul() { - let scalar1 = Scalar::from("02"); + fn test_scalar_tweak_mul() { + let mut scalar1 = Scalar::from("02"); let scalar2 = Scalar::from("03"); let result = Scalar::from("06"); let result_ = scalar1.tweak_mul(&scalar2); - assert!(result_ == result); + assert!(*result_ == result); } #[test] - fn test_tweak_add() { - let scalar1 = Scalar::from("02"); + fn test_scalar_tweak_add() { + let mut scalar1 = Scalar::from("02"); let scalar2 = Scalar::from("03"); let result = Scalar::from("05"); let result_ = scalar1.tweak_add(&scalar2); - assert!(result == result_); + assert!(result == *result_); } #[test] - fn test_add() { + fn test_scalar_add() { let scalar1 = Scalar::from("02"); let scalar2 = Scalar::from("03"); let result = Scalar::from("05"); - let result_ = scalar1 + scalar2; + let result_ = scalar1 + &scalar2; assert!(result_ == result); } #[test] - fn test_sub() { + fn test_scalar_sub() { let scalar1 = Scalar::from("10"); let scalar2 = Scalar::from("02"); let result = Scalar::from("0e"); - let result_ = scalar1 - scalar2; + let result_ = scalar1 - &scalar2; assert!(result == result_); } #[test] - fn test_mul() { + fn test_scalar_mul() { let scalar1 = Scalar::from("02"); let scalar2 = Scalar::from("03"); let result = Scalar::from("06"); - let result_ = scalar1 * scalar2; + let result_ = scalar1 * &scalar2; assert!(result_ == result); } #[test] - fn test_mul_zero() { + fn test_scalar_mul_zero() { let scalar1 = Scalar::random(); let scalar2 = Scalar::new(&SCALAR_ZERO); - let result = scalar1 * scalar2; + let result = scalar1 * &scalar2; assert!(result.is_zero); } #[test] - fn test_mul_by_zero() { + fn test_scalar_mul_by_zero() { let scalar1 = Scalar::new(&SCALAR_ZERO); let scalar2 = Scalar::random(); - let result = scalar1 * scalar2; + let result = scalar1 * &scalar2; assert!(result.is_zero); } @@ -333,9 +466,10 @@ mod tests { fn test_mul_cmp() { let a = Scalar::random(); let b = Scalar::random(); - let c = a.tweak_mul(&b); - let c_ = a*b; - assert!(c == c_); + let mut a_clone = a.clone(); + let c = a_clone.tweak_mul(&b); + let c_ = a*&b; + assert!(*c == c_); } #[test] @@ -386,8 +520,9 @@ mod tests { fn test_scalar_modular_inversion() { let one = Scalar::new(&SCALAR_ONE); let scalar = Scalar::from("deadbeef"); - let scalar_inv = scalar.invert(); - let prod = scalar * scalar_inv; + let mut scalar_inv = scalar.clone(); + scalar_inv.invert(); + let prod = scalar * &scalar_inv; assert!(one == prod); } } From 6f20af6b74913d18189517cc1da73d541423da94 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sun, 8 Dec 2024 00:58:13 +0100 Subject: [PATCH 08/29] remove unused imports + cargo fmt --- src/secp.rs | 93 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 37 deletions(-) diff --git a/src/secp.rs b/src/secp.rs index f269719..edd9734 100755 --- a/src/secp.rs +++ b/src/secp.rs @@ -1,13 +1,14 @@ use once_cell::sync::Lazy; use rug::ops::RemRounding; +use rug::Integer; use secp256k1::constants::CURVE_ORDER; use secp256k1::{rand, All, PublicKey, Secp256k1, SecretKey}; -use std::ops::{Add, Sub, Mul, Neg}; use std::cmp::PartialEq; -use rug::Integer; pub const SCALAR_ZERO: [u8; 32] = [0; 32]; -pub const SCALAR_ONE: [u8; 32] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; +pub const SCALAR_ONE: [u8; 32] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, +]; pub const GROUP_ELEMENT_ZERO: [u8; 33] = [0; 33]; /// Secp256k1 global context @@ -67,7 +68,7 @@ fn modinv(m: &Integer, x: &Integer) -> Integer { (d * f).rem_euc(m) } -impl Scalar{ +impl Scalar { pub fn new(data: &[u8; 32]) -> Self { if *data == SCALAR_ZERO { Scalar { @@ -76,13 +77,19 @@ impl Scalar{ } } else { let inner = SecretKey::from_byte_array(data).expect("Could not instantiate Scalar"); - Scalar { inner: Some(inner), is_zero: false } + Scalar { + inner: Some(inner), + is_zero: false, + } } } pub fn random() -> Self { let inner = SecretKey::new(&mut rand::thread_rng()); - Scalar { inner: Some(inner), is_zero: false } + Scalar { + inner: Some(inner), + is_zero: false, + } } pub fn clone(&self) -> Self { @@ -99,7 +106,11 @@ impl Scalar{ return self; } let b = secp256k1::Scalar::from_be_bytes(other.inner.unwrap().secret_bytes()).unwrap(); - let result = self.inner.unwrap().mul_tweak(&b).expect("Could not multiply Scalars"); + let result = self + .inner + .unwrap() + .mul_tweak(&b) + .expect("Could not multiply Scalars"); self.inner = Some(result); self } @@ -112,10 +123,12 @@ impl Scalar{ self.is_zero = false; self } else { - let b = secp256k1::Scalar::from_be_bytes( - other.inner.unwrap().secret_bytes() - ).unwrap(); - let result_key = self.inner.unwrap().add_tweak(&b).expect("Could not add to Scalar"); + let b = secp256k1::Scalar::from_be_bytes(other.inner.unwrap().secret_bytes()).unwrap(); + let result_key = self + .inner + .unwrap() + .add_tweak(&b) + .expect("Could not add to Scalar"); self.inner = Some(result_key); self } @@ -135,7 +148,10 @@ impl Scalar{ if self.is_zero { panic!("Scalar 0 doesn't have an inverse") } else { - let x = Integer::from_digits(&self.inner.unwrap().secret_bytes(), rug::integer::Order::Msf); + let x = Integer::from_digits( + &self.inner.unwrap().secret_bytes(), + rug::integer::Order::Msf, + ); let q = Integer::from_digits(&CURVE_ORDER, rug::integer::Order::Msf); let x_inv = modinv(&q, &x); //let x_inv = x.clone().invert(&q).unwrap(); @@ -157,8 +173,12 @@ impl GroupElement { is_zero: true, } } else { - let inner = PublicKey::from_byte_array_compressed(data).expect("Cannot create GroupElement"); - GroupElement { inner: Some(inner), is_zero: false } + let inner = + PublicKey::from_byte_array_compressed(data).expect("Cannot create GroupElement"); + GroupElement { + inner: Some(inner), + is_zero: false, + } } } @@ -177,7 +197,9 @@ impl GroupElement { self.is_zero = other.is_zero; self } else { - let result = self.inner.unwrap() + let result = self + .inner + .unwrap() .combine(&other.inner.unwrap()) .expect("Error combining GroupElements"); self.inner = Some(result); @@ -192,7 +214,9 @@ impl GroupElement { self } else { let b = secp256k1::Scalar::from_be_bytes(scalar.inner.unwrap().secret_bytes()).unwrap(); - let result = self.inner.unwrap() + let result = self + .inner + .unwrap() .mul_tweak(&SECP256K1, &b) .expect("Could not multiply Scalar to GroupElement"); self.inner = Some(result); @@ -204,15 +228,14 @@ impl GroupElement { if self.is_zero { self } else { - let result = self.inner.unwrap() - .negate(&SECP256K1); + let result = self.inner.unwrap().negate(&SECP256K1); self.inner = Some(result); self } } } -impl std::ops::Add<&Scalar> for Scalar{ +impl std::ops::Add<&Scalar> for Scalar { type Output = Scalar; fn add(mut self, other: &Scalar) -> Scalar { @@ -221,7 +244,7 @@ impl std::ops::Add<&Scalar> for Scalar{ } } -impl std::ops::Neg for Scalar{ +impl std::ops::Neg for Scalar { type Output = Scalar; fn neg(mut self) -> Scalar { @@ -230,7 +253,7 @@ impl std::ops::Neg for Scalar{ } } -impl std::ops::Sub<&Scalar> for Scalar{ +impl std::ops::Sub<&Scalar> for Scalar { type Output = Scalar; fn sub(self, other: &Scalar) -> Scalar { @@ -245,7 +268,7 @@ impl std::ops::Sub<&Scalar> for Scalar{ } } -impl std::ops::Mul<&Scalar> for Scalar{ +impl std::ops::Mul<&Scalar> for Scalar { type Output = Scalar; fn mul(mut self, other: &Scalar) -> Scalar { @@ -316,7 +339,7 @@ impl From<&str> for Scalar { panic!("Hex string is too long"); } let mut padded_bytes = [0u8; 32]; - padded_bytes[32-bytes.len()..32].copy_from_slice(&bytes); + padded_bytes[32 - bytes.len()..32].copy_from_slice(&bytes); Scalar::new(&padded_bytes) } } @@ -330,7 +353,14 @@ impl PartialEq for Scalar { return false; } let mut b = 0u8; - for (x, y) in self.inner.as_ref().unwrap().secret_bytes().iter().zip(other.inner.as_ref().unwrap().secret_bytes().iter()) { + for (x, y) in self + .inner + .as_ref() + .unwrap() + .secret_bytes() + .iter() + .zip(other.inner.as_ref().unwrap().secret_bytes().iter()) + { b |= x ^ y; } b == 0 @@ -466,9 +496,9 @@ mod tests { fn test_mul_cmp() { let a = Scalar::random(); let b = Scalar::random(); - let mut a_clone = a.clone(); + let mut a_clone = a.clone(); let c = a_clone.tweak_mul(&b); - let c_ = a*&b; + let c_ = a * &b; assert!(*c == c_); } @@ -526,14 +556,3 @@ mod tests { assert!(one == prod); } } - - - - - - - - - - - From 2fc3dc967825f863cb329722f428801b1a15ab9c Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sun, 8 Dec 2024 18:14:19 +0100 Subject: [PATCH 09/29] generators + hash_to_curve --- Cargo.lock | 149 ++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 4 +- src/errors.rs | 12 ++++ src/generators.rs | 35 +++++++++++ src/lib.rs | 2 + src/secp.rs | 40 +++++++++---- 6 files changed, 225 insertions(+), 17 deletions(-) create mode 100644 src/errors.rs create mode 100644 src/generators.rs diff --git a/Cargo.lock b/Cargo.lock index f4d6b90..12b991a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,12 +14,72 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bitcoin" +version = "0.32.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6bc65742dea50536e35ad42492b234c27904a27f0abdcbce605015cb4ea026" +dependencies = [ + "base58ck", + "base64", + "bech32", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes", + "hex-conservative", + "hex_lit", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] + [[package]] name = "bitcoin-io" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals", + "serde", +] + [[package]] name = "bitcoin_hashes" version = "0.14.0" @@ -28,6 +88,7 @@ checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" dependencies = [ "bitcoin-io", "hex-conservative", + "serde", ] [[package]] @@ -40,17 +101,19 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" name = "cashu-kvac" version = "0.1.0" dependencies = [ + "bitcoin", "hex", + "merlin", "once_cell", "rug", - "secp256k1", + "thiserror", ] [[package]] name = "cc" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" dependencies = [ "shlex", ] @@ -61,6 +124,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -97,6 +169,21 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "libc" version = "0.2.167" @@ -109,6 +196,18 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core", + "zeroize", +] + [[package]] name = "once_cell" version = "1.20.2" @@ -186,9 +285,9 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.30.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "bitcoin_hashes", "rand", @@ -242,6 +341,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643caef17e3128658ff44d85923ef2d28af81bb71e0d67bbfe1d76f19a73e053" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "995d0bbc9995d1f19d28b7215a9352b0fc3cd3a2d2ec95c2cadc485cdedbcdde" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.14" @@ -347,3 +466,23 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index fa7b463..7e8801a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] +bitcoin = { version= "0.32.2", features = ["base64", "serde", "rand", "rand-std"] } hex = "0.4.3" +merlin = "3.0.0" once_cell = "1.20.2" rug = "1.26.1" -secp256k1 = {version="^0.30.0", features=["rand", "serde", "std"]} +thiserror = "2.0.5" diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..f63a33f --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,12 @@ + +use bitcoin::secp256k1; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Cannot map hash to a valid point on the curve")] + InvalidPoint, + /// Secp256k1 error + #[error(transparent)] + Secp256k1(#[from] secp256k1::Error), +} \ No newline at end of file diff --git a/src/generators.rs b/src/generators.rs new file mode 100644 index 0000000..707ed64 --- /dev/null +++ b/src/generators.rs @@ -0,0 +1,35 @@ +use crate::errors::Error; +use crate::secp::GroupElement; +use bitcoin::hashes::sha256::Hash as Sha256Hash; +use bitcoin::hashes::Hash; +use bitcoin::secp256k1::PublicKey; + +const DOMAIN_SEPARATOR: &[u8; 28] = b"Secp256k1_HashToCurve_Cashu_"; + +pub fn hash_to_curve(message: &[u8]) -> Result { + let msg_to_hash: Vec = [DOMAIN_SEPARATOR, message].concat(); + + let msg_hash: [u8; 32] = Sha256Hash::hash(&msg_to_hash).to_byte_array(); + + let mut counter: u32 = 0; + while counter < 2_u32.pow(16) { + let mut bytes_to_hash: Vec = Vec::with_capacity(36); + bytes_to_hash.extend_from_slice(&msg_hash); + bytes_to_hash.extend_from_slice(&counter.to_le_bytes()); + let mut hash: [u8; 33] = [0; 33]; + hash[0] = 0x02; + hash[1..33].copy_from_slice(&Sha256Hash::hash(&bytes_to_hash).to_byte_array()[0..32]); + + // Try to parse public key + match PublicKey::from_slice(&hash) { + Ok(_) => { + return Ok(GroupElement::new(&hash)) + } + Err(_) => { + counter += 1; + } + } + } + + Err(Error::InvalidPoint) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 8b06c62..fdf78df 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,3 @@ pub mod secp; +pub mod generators; +pub mod errors; \ No newline at end of file diff --git a/src/secp.rs b/src/secp.rs index edd9734..061ddf4 100755 --- a/src/secp.rs +++ b/src/secp.rs @@ -1,8 +1,8 @@ use once_cell::sync::Lazy; use rug::ops::RemRounding; use rug::Integer; -use secp256k1::constants::CURVE_ORDER; -use secp256k1::{rand, All, PublicKey, Secp256k1, SecretKey}; +use bitcoin::secp256k1::constants::CURVE_ORDER; +use bitcoin::secp256k1::{rand, All, PublicKey, Secp256k1, SecretKey, Scalar as SecpScalar}; use std::cmp::PartialEq; pub const SCALAR_ZERO: [u8; 32] = [0; 32]; @@ -76,7 +76,7 @@ impl Scalar { is_zero: true, } } else { - let inner = SecretKey::from_byte_array(data).expect("Could not instantiate Scalar"); + let inner = SecretKey::from_slice(data).expect("Could not instantiate Scalar"); Scalar { inner: Some(inner), is_zero: false, @@ -105,7 +105,7 @@ impl Scalar { self.inner = None; return self; } - let b = secp256k1::Scalar::from_be_bytes(other.inner.unwrap().secret_bytes()).unwrap(); + let b = SecpScalar::from_be_bytes(other.inner.unwrap().secret_bytes()).unwrap(); let result = self .inner .unwrap() @@ -123,7 +123,7 @@ impl Scalar { self.is_zero = false; self } else { - let b = secp256k1::Scalar::from_be_bytes(other.inner.unwrap().secret_bytes()).unwrap(); + let b = SecpScalar::from_be_bytes(other.inner.unwrap().secret_bytes()).unwrap(); let result_key = self .inner .unwrap() @@ -155,10 +155,8 @@ impl Scalar { let q = Integer::from_digits(&CURVE_ORDER, rug::integer::Order::Msf); let x_inv = modinv(&q, &x); //let x_inv = x.clone().invert(&q).unwrap(); - let mut data = [0u8; 32]; let vec = x_inv.to_digits(rug::integer::Order::Msf); - data.copy_from_slice(&vec[0..32]); - let inner = SecretKey::from_byte_array(&data).expect("Could not instantiate Scalar"); + let inner = SecretKey::from_slice(&vec).expect("Could not instantiate Scalar"); self.inner = Some(inner); self } @@ -174,7 +172,7 @@ impl GroupElement { } } else { let inner = - PublicKey::from_byte_array_compressed(data).expect("Cannot create GroupElement"); + PublicKey::from_slice(data).expect("Cannot create GroupElement"); GroupElement { inner: Some(inner), is_zero: false, @@ -213,7 +211,7 @@ impl GroupElement { self.inner = None; self } else { - let b = secp256k1::Scalar::from_be_bytes(scalar.inner.unwrap().secret_bytes()).unwrap(); + let b = bitcoin::secp256k1::Scalar::from_be_bytes(scalar.inner.unwrap().secret_bytes()).unwrap(); let result = self .inner .unwrap() @@ -277,7 +275,7 @@ impl std::ops::Mul<&Scalar> for Scalar { self.is_zero = true; self } else { - // Masked multiplication (constant time) + // Multiplication is masked with random `r` let mut r = Scalar::random(); self.tweak_add(&r); self.tweak_mul(&other); @@ -400,6 +398,26 @@ impl std::ops::Sub<&GroupElement> for GroupElement { } } +impl std::ops::Mul<&Scalar> for GroupElement { + type Output = GroupElement; + + fn mul(mut self, other: &Scalar) -> GroupElement { + if self.is_zero || other.is_zero { + self.is_zero = true; + self.inner = None; + self + } else { + // Multiplication is masked with random `r` + let r = Scalar::random(); + let r_copy = r.clone(); + let mut self_copy = self.clone(); + self.multiply(&(r + other)); + self_copy.multiply(&r_copy); + self - &self_copy + } + } +} + #[cfg(test)] mod tests { use super::*; From 4b1422db5c6f4599c44222b2a3d9b58ec73132bd Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Mon, 9 Dec 2024 12:14:41 +0100 Subject: [PATCH 10/29] generators --- src/errors.rs | 3 +- src/generators.py | 15 ++++++- src/generators.rs | 79 +++++++++++++++++++++++++++++++--- src/lib.rs | 7 ++-- src/models.rs | 1 + src/secp.rs | 105 +++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 src/models.rs diff --git a/src/errors.rs b/src/errors.rs index f63a33f..d305b38 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,4 +1,3 @@ - use bitcoin::secp256k1; use thiserror::Error; @@ -9,4 +8,4 @@ pub enum Error { /// Secp256k1 error #[error(transparent)] Secp256k1(#[from] secp256k1::Error), -} \ No newline at end of file +} diff --git a/src/generators.py b/src/generators.py index 22acb77..13f4206 100644 --- a/src/generators.py +++ b/src/generators.py @@ -17,7 +17,7 @@ def hash_to_curve(message: bytes) -> GroupElement: raise ValueError("No valid point found") # Generators drawn with NUMS -W, W_, X0, X1, Gz_mac, Gz_attribute, Gz_script, G_amount, G_script, G_blind, G_serial = ( +W, W_, X0, X1, Gz_mac, Gz_attribute, Gz_script, G_amount, G_script, G_blind = ( hash_to_curve(b"W"), hash_to_curve(b"W_"), hash_to_curve(b"X0"), @@ -28,8 +28,19 @@ def hash_to_curve(message: bytes) -> GroupElement: hash_to_curve(b"G_amount"), hash_to_curve(b"G_script"), hash_to_curve(b"G_blind"), - hash_to_curve(b"G_serial"), ) # Point at infinity O = GroupElement(ELEMENT_ZERO) + +if __name__ == '__main__': + print(f"{W.serialize(True).hex() = }\n") + print(f"{W_.serialize(True).hex() = }\n") + print(f"{X0.serialize(True).hex() = }\n") + print(f"{X1.serialize(True).hex() = }\n") + print(f"{Gz_mac.serialize(True).hex() = }\n") + print(f"{Gz_attribute.serialize(True).hex() = }\n") + print(f"{Gz_script.serialize(True).hex() = }\n") + print(f"{G_amount.serialize(True).hex() = }\n") + print(f"{G_script.serialize(True).hex() = }\n") + print(f"{G_blind.serialize(True).hex() = }\n") diff --git a/src/generators.rs b/src/generators.rs index 707ed64..ba705ae 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -3,6 +3,7 @@ use crate::secp::GroupElement; use bitcoin::hashes::sha256::Hash as Sha256Hash; use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; +use once_cell::sync::Lazy; const DOMAIN_SEPARATOR: &[u8; 28] = b"Secp256k1_HashToCurve_Cashu_"; @@ -22,14 +23,82 @@ pub fn hash_to_curve(message: &[u8]) -> Result { // Try to parse public key match PublicKey::from_slice(&hash) { - Ok(_) => { - return Ok(GroupElement::new(&hash)) - } + Ok(_) => return Ok(GroupElement::new(&hash)), Err(_) => { counter += 1; } - } + } } Err(Error::InvalidPoint) -} \ No newline at end of file +} + +pub struct Generators { + w: GroupElement, + w_: GroupElement, + x0: GroupElement, + x1: GroupElement, + gz_mac: GroupElement, + gz_attribute: GroupElement, + gz_script: GroupElement, + g_amount: GroupElement, + g_script: GroupElement, + g_blind: GroupElement, +} + +impl Generators { + fn new() -> Self { + let w = hash_to_curve(b"W").expect("Failed to hash to curve"); + let w_ = hash_to_curve(b"W_").expect("Failed to hash to curve"); + let x0 = hash_to_curve(b"X0").expect("Failed to hash to curve"); + let x1 = hash_to_curve(b"X1").expect("Failed to hash to curve"); + let gz_mac = hash_to_curve(b"Gz_mac").expect("Failed to hash to curve"); + let gz_attribute = hash_to_curve(b"Gz_attribute").expect("Failed to hash to curve"); + let gz_script = hash_to_curve(b"Gz_script").expect("Failed to hash to curve"); + let g_amount = hash_to_curve(b"G_amount").expect("Failed to hash to curve"); + let g_script = hash_to_curve(b"G_script").expect("Failed to hash to curve"); + let g_blind = hash_to_curve(b"G_blind").expect("Failed to hash to curve"); + + Generators { + w, + w_, + x0, + x1, + gz_mac, + gz_attribute, + gz_script, + g_amount, + g_script, + g_blind, + } + } +} + +pub static GENERATORS: Lazy = Lazy::new(|| Generators::new()); + +#[cfg(test)] +mod tests { + use crate::secp::GroupElement; + + use super::hash_to_curve; + + #[test] + fn test_hash_to_curve() { + let msg = b"G_amount"; + let g_amount = GroupElement::from( + "024e76426e405fa7f7d3403ea8671fe11b8bec2da6dcda5583ce1ac37ed0de9b04", + ); + let g_amount_ = hash_to_curve(msg).expect("Couldn't map hash to groupelement"); + assert!(g_amount == g_amount_) + } + + #[test] + fn test_hash_to_curve_2() { + let msg = b"G_blind"; + let g_blind = GroupElement::from( + "0264f39fbee428ab6165e907b5d463a17e315b9f06f6200ed7e9c4bcbe0df73383", + ); + let g_blind_ = hash_to_curve(msg).expect("Couldn't map hash to groupelement"); + assert!(g_blind == g_blind_) + } +} diff --git a/src/lib.rs b/src/lib.rs index fdf78df..4500bf2 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ -pub mod secp; -pub mod generators; -pub mod errors; \ No newline at end of file +pub mod errors; +mod generators; +pub mod models; +mod secp; diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..0fda8fc --- /dev/null +++ b/src/models.rs @@ -0,0 +1 @@ +pub const RANGE_LIMIT: u64 = 32_u64; diff --git a/src/secp.rs b/src/secp.rs index 061ddf4..251de4b 100755 --- a/src/secp.rs +++ b/src/secp.rs @@ -1,8 +1,8 @@ +use bitcoin::secp256k1::constants::CURVE_ORDER; +use bitcoin::secp256k1::{rand, All, PublicKey, Scalar as SecpScalar, Secp256k1, SecretKey}; use once_cell::sync::Lazy; use rug::ops::RemRounding; use rug::Integer; -use bitcoin::secp256k1::constants::CURVE_ORDER; -use bitcoin::secp256k1::{rand, All, PublicKey, Secp256k1, SecretKey, Scalar as SecpScalar}; use std::cmp::PartialEq; pub const SCALAR_ZERO: [u8; 32] = [0; 32]; @@ -171,8 +171,7 @@ impl GroupElement { is_zero: true, } } else { - let inner = - PublicKey::from_slice(data).expect("Cannot create GroupElement"); + let inner = PublicKey::from_slice(data).expect("Cannot create GroupElement"); GroupElement { inner: Some(inner), is_zero: false, @@ -211,7 +210,8 @@ impl GroupElement { self.inner = None; self } else { - let b = bitcoin::secp256k1::Scalar::from_be_bytes(scalar.inner.unwrap().secret_bytes()).unwrap(); + let b = bitcoin::secp256k1::Scalar::from_be_bytes(scalar.inner.unwrap().secret_bytes()) + .unwrap(); let result = self .inner .unwrap() @@ -409,7 +409,7 @@ impl std::ops::Mul<&Scalar> for GroupElement { } else { // Multiplication is masked with random `r` let r = Scalar::random(); - let r_copy = r.clone(); + let r_copy = r.clone(); let mut self_copy = self.clone(); self.multiply(&(r + other)); self_copy.multiply(&r_copy); @@ -418,6 +418,51 @@ impl std::ops::Mul<&Scalar> for GroupElement { } } +impl PartialEq for GroupElement { + fn eq(&self, other: &Self) -> bool { + if self.is_zero && other.is_zero { + return true; + } + if self.is_zero || other.is_zero { + return false; + } else { + self.inner.unwrap().eq(&other.inner.unwrap()) + } + } +} + +impl From<&str> for GroupElement { + fn from(hex_string: &str) -> Self { + let bytes = hex::decode(hex_string).expect("Invalid hex string"); + if bytes.len() > 33 { + panic!("Hex string is too long"); + } + let mut padded_bytes = [0u8; 33]; + padded_bytes[33 - bytes.len()..33].copy_from_slice(&bytes); + GroupElement::new(&padded_bytes) + } +} + +impl Into<[u8; 33]> for GroupElement { + fn into(self) -> [u8; 33] { + if self.is_zero { + GROUP_ELEMENT_ZERO + } else { + self.inner.unwrap().serialize() + } + } +} + +impl Into for GroupElement { + fn into(self) -> String { + if self.is_zero { + hex::encode(GROUP_ELEMENT_ZERO) + } else { + hex::encode(self.inner.unwrap().serialize()) + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -573,4 +618,52 @@ mod tests { let prod = scalar * &scalar_inv; assert!(one == prod); } + + #[test] + fn test_ge_from_hex() { + let g = GroupElement::from( + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + ); + assert!(!g.is_zero) + } + + #[test] + fn test_ge_into() { + let hex_str = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"; + let g = GroupElement::from(hex_str); + let g_string: String = g.into(); + assert!(hex_str == g_string) + } + + #[test] + fn test_cmp_neq() { + let g1 = GroupElement::from( + "0264f39fbee428ab6165e907b5d463a17e315b9f06f6200ed7e9c4bcbe0df73383", + ); + let g2 = GroupElement::from( + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + ); + assert!(g1 != g2); + } + + #[test] + fn test_ge_add_mul() { + let g = GroupElement::from( + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + ); + let scalar_2 = Scalar::from("02"); + let result = g.clone() + &g; + let result_ = g * &scalar_2; + assert!(result == result_) + } + + #[test] + fn test_ge_sub_mul() { + let g = GroupElement::from( + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + ); + let scalar_2 = Scalar::from("02"); + let result = g.clone() * &scalar_2 - &g; + assert!(result == g) + } } From 8a25b99999bc9226ea8a400a0f7a045a2e7f77d4 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sat, 14 Dec 2024 18:22:09 +0100 Subject: [PATCH 11/29] init models --- src/generators.rs | 20 +++++------ src/models.rs | 87 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 10 deletions(-) diff --git a/src/generators.rs b/src/generators.rs index ba705ae..06f8ac3 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -34,16 +34,16 @@ pub fn hash_to_curve(message: &[u8]) -> Result { } pub struct Generators { - w: GroupElement, - w_: GroupElement, - x0: GroupElement, - x1: GroupElement, - gz_mac: GroupElement, - gz_attribute: GroupElement, - gz_script: GroupElement, - g_amount: GroupElement, - g_script: GroupElement, - g_blind: GroupElement, + pub w: GroupElement, + pub w_: GroupElement, + pub x0: GroupElement, + pub x1: GroupElement, + pub gz_mac: GroupElement, + pub gz_attribute: GroupElement, + pub gz_script: GroupElement, + pub g_amount: GroupElement, + pub g_script: GroupElement, + pub g_blind: GroupElement, } impl Generators { diff --git a/src/models.rs b/src/models.rs index 0fda8fc..1cf658d 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1 +1,88 @@ +use crate::{generators::GENERATORS, secp::{GroupElement, Scalar}}; +use bitcoin::hashes::sha256::Hash as Sha256Hash; +use bitcoin::hashes::Hash; + pub const RANGE_LIMIT: u64 = 32_u64; + +pub struct MintPrivateKey { + pub w: Scalar, + pub w_: Scalar, + pub x0: Scalar, + pub x1: Scalar, + pub ya: Scalar, + pub ys: Scalar, + + // Public parameters + pub cw: Option, + pub i: Option +} + +impl MintPrivateKey { + + pub fn from_scalars(scalars: [Scalar; 6]) -> Self { + let [w, w_, x0, x1, ya, ys] = scalars; + MintPrivateKey { + w, + w_, + x0, + x1, + ya, + ys, + cw: None, + i: None, + } + } + + pub fn to_scalars(&self) -> Vec { + vec![self.w.clone(), self.w_.clone(), self.x0.clone(), self.x1.clone(), self.ya.clone(), self.ys.clone()] + } + + pub fn pubkey(&mut self) -> Vec { + if !self.cw.is_some() { + self.cw = Some(GENERATORS.w.clone()*&self.w + &(GENERATORS.w_.clone()*&self.w_)); + } + if !self.i.is_some() { + self.i = Some( + GENERATORS.gz_mac.clone() - &( + GENERATORS.x0.clone()*&self.x0 + + &( + GENERATORS.x1.clone()*&self.x1 + + &( + GENERATORS.gz_attribute.clone()*&self.ya + + &( + GENERATORS.gz_script.clone()*&self.ys + ) + ) + ) + ) + ); + } + vec![ + self.cw.as_ref().expect("Expected Cw").clone(), + self.i.as_ref().expect("Expected I").clone(), + ] + } +} + + +pub struct ZKP { + pub s: Vec, + pub c: Scalar +} + +pub struct ScriptAttribute { + r: Scalar, + s: Scalar, + Ms: Option, +} + +/* +impl ScriptAttribute { + pub fn new(script: &[u8], blinding_factor: Option<&[u8]>) -> Self { + let s = Scalar::new(&Sha256Hash::hash(&script).to_byte_array()); + if let b_factor = Some(blinding_factor) { + + } + } +} +*/ \ No newline at end of file From 27cd82f018cae80a3cee84570eaae28a18bc6cff Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sun, 15 Dec 2024 00:07:53 +0100 Subject: [PATCH 12/29] MAC --- src/generators.rs | 41 +++++++------- src/models.rs | 140 ++++++++++++++++++++++++++++++++++++++-------- src/secp.rs | 16 +++--- 3 files changed, 146 insertions(+), 51 deletions(-) diff --git a/src/generators.rs b/src/generators.rs index 06f8ac3..390f049 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -33,17 +33,18 @@ pub fn hash_to_curve(message: &[u8]) -> Result { Err(Error::InvalidPoint) } +#[allow(non_snake_case)] pub struct Generators { - pub w: GroupElement, - pub w_: GroupElement, - pub x0: GroupElement, - pub x1: GroupElement, - pub gz_mac: GroupElement, - pub gz_attribute: GroupElement, - pub gz_script: GroupElement, - pub g_amount: GroupElement, - pub g_script: GroupElement, - pub g_blind: GroupElement, + pub W: GroupElement, + pub W_: GroupElement, + pub X0: GroupElement, + pub X1: GroupElement, + pub Gz_mac: GroupElement, + pub Gz_attribute: GroupElement, + pub Gz_script: GroupElement, + pub G_amount: GroupElement, + pub G_script: GroupElement, + pub G_blind: GroupElement, } impl Generators { @@ -60,16 +61,16 @@ impl Generators { let g_blind = hash_to_curve(b"G_blind").expect("Failed to hash to curve"); Generators { - w, - w_, - x0, - x1, - gz_mac, - gz_attribute, - gz_script, - g_amount, - g_script, - g_blind, + W: w, + W_: w_, + X0: x0, + X1: x1, + Gz_mac: gz_mac, + Gz_attribute: gz_attribute, + Gz_script: gz_script, + G_amount: g_amount, + G_script: g_script, + G_blind: g_blind, } } } diff --git a/src/models.rs b/src/models.rs index 1cf658d..58e0bbe 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,9 +1,10 @@ -use crate::{generators::GENERATORS, secp::{GroupElement, Scalar}}; +use crate::{errors::Error, generators::{hash_to_curve, GENERATORS}, secp::{GroupElement, Scalar, GROUP_ELEMENT_ZERO}}; use bitcoin::hashes::sha256::Hash as Sha256Hash; use bitcoin::hashes::Hash; -pub const RANGE_LIMIT: u64 = 32_u64; +pub const RANGE_LIMIT: u64 = std::u32::MAX as u64; +#[allow(non_snake_case)] pub struct MintPrivateKey { pub w: Scalar, pub w_: Scalar, @@ -13,8 +14,8 @@ pub struct MintPrivateKey { pub ys: Scalar, // Public parameters - pub cw: Option, - pub i: Option + pub Cw: Option, + pub I: Option } impl MintPrivateKey { @@ -28,8 +29,8 @@ impl MintPrivateKey { x1, ya, ys, - cw: None, - i: None, + Cw: None, + I: None, } } @@ -38,19 +39,19 @@ impl MintPrivateKey { } pub fn pubkey(&mut self) -> Vec { - if !self.cw.is_some() { - self.cw = Some(GENERATORS.w.clone()*&self.w + &(GENERATORS.w_.clone()*&self.w_)); + if !self.Cw.is_some() { + self.Cw = Some(GENERATORS.W.clone()*&self.w + &(GENERATORS.W_.clone()*&self.w_)); } - if !self.i.is_some() { - self.i = Some( - GENERATORS.gz_mac.clone() - &( - GENERATORS.x0.clone()*&self.x0 + if !self.I.is_some() { + self.I = Some( + GENERATORS.Gz_mac.clone() - &( + GENERATORS.X0.clone()*&self.x0 + &( - GENERATORS.x1.clone()*&self.x1 + GENERATORS.X1.clone()*&self.x1 + &( - GENERATORS.gz_attribute.clone()*&self.ya + GENERATORS.Gz_attribute.clone()*&self.ya + &( - GENERATORS.gz_script.clone()*&self.ys + GENERATORS.Gz_script.clone()*&self.ys ) ) ) @@ -58,8 +59,8 @@ impl MintPrivateKey { ); } vec![ - self.cw.as_ref().expect("Expected Cw").clone(), - self.i.as_ref().expect("Expected I").clone(), + self.Cw.as_ref().expect("Expected Cw").clone(), + self.I.as_ref().expect("Expected I").clone(), ] } } @@ -70,19 +71,112 @@ pub struct ZKP { pub c: Scalar } +#[allow(non_snake_case)] pub struct ScriptAttribute { - r: Scalar, - s: Scalar, + pub r: Scalar, + pub s: Scalar, Ms: Option, } -/* + impl ScriptAttribute { - pub fn new(script: &[u8], blinding_factor: Option<&[u8]>) -> Self { + pub fn new(script: &[u8], blinding_factor: Option<&[u8; 32]>) -> Self { let s = Scalar::new(&Sha256Hash::hash(&script).to_byte_array()); - if let b_factor = Some(blinding_factor) { + if let Some(b_factor) = blinding_factor { + let r = Scalar::new(b_factor); + + ScriptAttribute { r: r, s: s, Ms: None } + } else { + let r = Scalar::random(); + + ScriptAttribute { r: r, s: s, Ms: None } + } + } + + pub fn commitment(&mut self) -> GroupElement { + if !self.Ms.is_some() { + self.Ms = Some( + GENERATORS.G_script.clone() * &self.s + &( + GENERATORS.G_blind.clone() * &self.r + ) + ) + } + self.Ms.as_ref().expect("Couldn't get ScriptAttribute Commitment").clone() + } +} + +#[allow(non_snake_case)] +pub struct AmountAttribute { + pub a: Scalar, + pub r: Scalar, + Ma: Option, +} + +impl AmountAttribute { + pub fn new(amount: u64, blinding_factor: Option<&[u8; 32]>) -> Self { + let a = Scalar::from(amount); + if let Some(b_factor) = blinding_factor { + let r = Scalar::new(b_factor); + AmountAttribute { r: r, a: a, Ma: None } + } else { + let r = Scalar::random(); + + AmountAttribute { r: r, a: a, Ma: None } } } + + pub fn commitment(&mut self) -> GroupElement{ + if !self.Ma.is_some() { + self.Ma = Some( + GENERATORS.G_script.clone() * &self.a + &( + GENERATORS.G_blind.clone() * &self.r + ) + ) + } + self.Ma.as_ref().expect("Couldn't get ScriptAttribute Commitment").clone() + } +} + +#[allow(non_snake_case)] +pub struct MAC { + pub t: Scalar, + pub V: GroupElement, } -*/ \ No newline at end of file + +impl MAC { + #[allow(non_snake_case)] + pub fn generate( + privkey: &MintPrivateKey, + amount_commitment: &GroupElement, + script_commitment: Option<&GroupElement>, + t_tag: Option<&[u8; 32]>, + ) -> Result { + let t: Scalar; + if let Some(t_tag_bytes) = t_tag { + t = Scalar::new(t_tag_bytes); + } else { + t = Scalar::random(); + } + let t_bytes: [u8; 32] = t.clone().into(); + let U = hash_to_curve(&t_bytes)?; + let Ma = amount_commitment.clone(); + let Ms: GroupElement; + if let Some(com) = script_commitment { + Ms = com.clone(); + } else { + Ms = GroupElement::new(&GROUP_ELEMENT_ZERO); + } + let V = + GENERATORS.W.clone() * &privkey.w + &( + U.clone() * &privkey.x0 + &( + U.clone() * &(t.clone() * &privkey.x1) + &( + Ma * &(privkey.ya) + &( + Ms * &(privkey.ys) + ) + ) + ) + ); + Ok(MAC { t, V }) + } +} \ No newline at end of file diff --git a/src/secp.rs b/src/secp.rs index 251de4b..9b31ed3 100755 --- a/src/secp.rs +++ b/src/secp.rs @@ -318,14 +318,14 @@ impl Into for Scalar { impl From for Scalar { fn from(value: u64) -> Self { let mut bytes = [0u8; 32]; - bytes[31] = (value >> 56) as u8; - bytes[30] = (value >> 48) as u8; - bytes[29] = (value >> 40) as u8; - bytes[28] = (value >> 32) as u8; - bytes[27] = (value >> 24) as u8; - bytes[26] = (value >> 16) as u8; - bytes[25] = (value >> 8) as u8; - bytes[24] = value as u8; + bytes[24] = (value >> 56) as u8; + bytes[25] = (value >> 48) as u8; + bytes[26] = (value >> 40) as u8; + bytes[27] = (value >> 32) as u8; + bytes[28] = (value >> 24) as u8; + bytes[29] = (value >> 16) as u8; + bytes[30] = (value >> 8) as u8; + bytes[31] = value as u8; Scalar::new(&bytes) } } From 8173a3e938f910024f5cea50544c7a67bdc1ad6a Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sun, 15 Dec 2024 00:08:17 +0100 Subject: [PATCH 13/29] more MAC --- src/models.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/models.rs b/src/models.rs index 58e0bbe..6b0761d 100644 --- a/src/models.rs +++ b/src/models.rs @@ -150,11 +150,11 @@ impl MAC { privkey: &MintPrivateKey, amount_commitment: &GroupElement, script_commitment: Option<&GroupElement>, - t_tag: Option<&[u8; 32]>, + t_tag: Option<&Scalar>, ) -> Result { let t: Scalar; - if let Some(t_tag_bytes) = t_tag { - t = Scalar::new(t_tag_bytes); + if let Some(t_tag_some) = t_tag { + t = t_tag_some.clone(); } else { t = Scalar::random(); } From 6af86316eeea66757e069ca6b6d0a7c5d2ed0d68 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sun, 15 Dec 2024 00:28:15 +0100 Subject: [PATCH 14/29] typo --- src/models.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models.rs b/src/models.rs index 6b0761d..510bf93 100644 --- a/src/models.rs +++ b/src/models.rs @@ -129,7 +129,7 @@ impl AmountAttribute { pub fn commitment(&mut self) -> GroupElement{ if !self.Ma.is_some() { self.Ma = Some( - GENERATORS.G_script.clone() * &self.a + &( + GENERATORS.G_amount.clone() * &self.a + &( GENERATORS.G_blind.clone() * &self.r ) ) From d7faa447eac4e1bad67e1c48f8d016dc430e286c Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sun, 15 Dec 2024 02:13:32 +0100 Subject: [PATCH 15/29] RandomizedCredentials --- src/models.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/models.rs b/src/models.rs index 510bf93..ebdf467 100644 --- a/src/models.rs +++ b/src/models.rs @@ -179,4 +179,33 @@ impl MAC { ); Ok(MAC { t, V }) } -} \ No newline at end of file +} + +#[allow(non_snake_case)] +pub struct RandomizedCredentials { + /// Randomized Attribute Commitment + pub Ca: GroupElement, + /// Randomized Script Commitment + pub Cs: GroupElement, + /// Randomized MAC-specific Generator "U" + pub Cx0: GroupElement, + /// Randomized tag commitment + pub Cx1: GroupElement, + /// Randomized MAC + pub Cv: GroupElement, +} + +/* +impl RandomizedCredentials { + #[allow(non_snake_case)] + pub fn new( + mac: &MAC, + amount_attribute: &AmountAttribute, + script_attribute: &Option, + reveal_script: bool, + ) -> Self { + let t = mac.t.clone(); + let V = mac.V.clone(); + } +} +*/ \ No newline at end of file From 9660c349aeeb4062c12ff79b745391cee532418b Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sun, 15 Dec 2024 12:15:39 +0100 Subject: [PATCH 16/29] AsRef for Scalar and GroupElement + adjustments. --- src/models.rs | 35 ++++++++++++++++++++++++++++------- src/secp.rs | 36 ++++++++++++++++++++++++++---------- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/models.rs b/src/models.rs index ebdf467..c8f40c2 100644 --- a/src/models.rs +++ b/src/models.rs @@ -158,7 +158,7 @@ impl MAC { } else { t = Scalar::random(); } - let t_bytes: [u8; 32] = t.clone().into(); + let t_bytes: [u8; 32] = t.as_ref().into(); let U = hash_to_curve(&t_bytes)?; let Ma = amount_commitment.clone(); let Ms: GroupElement; @@ -195,17 +195,38 @@ pub struct RandomizedCredentials { pub Cv: GroupElement, } -/* + impl RandomizedCredentials { #[allow(non_snake_case)] pub fn new( mac: &MAC, - amount_attribute: &AmountAttribute, - script_attribute: &Option, + amount_attribute: &mut AmountAttribute, + script_attribute: Option<&mut ScriptAttribute>, reveal_script: bool, - ) -> Self { + ) -> Result { let t = mac.t.clone(); - let V = mac.V.clone(); + let V = mac.V.as_ref(); + let t_bytes: [u8; 32] = (&mac.t).into(); + let U = hash_to_curve(&t_bytes)?; + let Ma = amount_attribute.commitment(); + let r = &amount_attribute.r; + let Ms: GroupElement; + if let Some(attr) = script_attribute { + if reveal_script { + Ms = GENERATORS.G_blind.clone() * &attr.r; + } else { + Ms = attr.commitment(); + } + } else { + Ms = GroupElement::new(&GROUP_ELEMENT_ZERO); + } + + let Ca = GENERATORS.Gz_attribute.clone() * r + &Ma; + let Cs = GENERATORS.Gz_script.clone() * r + &Ms; + let Cx0 = GENERATORS.X0.clone() * r + &U; + let Cx1 = GENERATORS.X1.clone() * r + &(U * &t); + let Cv = GENERATORS.Gz_mac.clone() * r + V; + + Ok(RandomizedCredentials { Ca, Cs, Cx0, Cx1, Cv }) } } -*/ \ No newline at end of file diff --git a/src/secp.rs b/src/secp.rs index 9b31ed3..ba534df 100755 --- a/src/secp.rs +++ b/src/secp.rs @@ -295,17 +295,17 @@ impl Into> for Scalar { } } -impl Into<[u8; 32]> for Scalar { +impl Into<[u8; 32]> for &Scalar { fn into(self) -> [u8; 32] { if self.is_zero { SCALAR_ZERO } else { - self.inner.unwrap().secret_bytes() + self.inner.as_ref().expect("Expected inner Scalar").secret_bytes() } } } -impl Into for Scalar { +impl Into for &Scalar { fn into(self) -> String { if self.is_zero { hex::encode(SCALAR_ZERO) @@ -342,6 +342,12 @@ impl From<&str> for Scalar { } } +impl AsRef for Scalar { + fn as_ref(&self) -> &Scalar { + self + } +} + impl PartialEq for Scalar { fn eq(&self, other: &Self) -> bool { if self.is_zero && other.is_zero { @@ -443,26 +449,36 @@ impl From<&str> for GroupElement { } } -impl Into<[u8; 33]> for GroupElement { +impl Into<[u8; 33]> for &GroupElement { fn into(self) -> [u8; 33] { if self.is_zero { GROUP_ELEMENT_ZERO } else { - self.inner.unwrap().serialize() + self.inner.as_ref().expect("Expected inner PublicKey").serialize() } } } -impl Into for GroupElement { +impl Into for &GroupElement { fn into(self) -> String { if self.is_zero { hex::encode(GROUP_ELEMENT_ZERO) } else { - hex::encode(self.inner.unwrap().serialize()) + hex::encode(self.inner + .as_ref() + .expect("Expected inner PublicKey") + .serialize() + ) } } } +impl AsRef for GroupElement { + fn as_ref(&self) -> &GroupElement { + self + } +} + #[cfg(test)] mod tests { use super::*; @@ -583,7 +599,7 @@ mod tests { #[test] fn test_scalar_into_string() { let scalar = Scalar::random(); - let hex_str: String = scalar.into(); + let hex_str: String = scalar.as_ref().into(); assert_eq!(hex_str.len(), 64); assert!(hex::decode(&hex_str).is_ok()); } @@ -591,7 +607,7 @@ mod tests { #[test] fn test_zero_scalar_into_string() { let scalar = Scalar::new(&SCALAR_ZERO); - let hex_str: String = scalar.into(); + let hex_str: String = scalar.as_ref().into(); assert_eq!(hex_str, hex::encode(SCALAR_ZERO)); } @@ -631,7 +647,7 @@ mod tests { fn test_ge_into() { let hex_str = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"; let g = GroupElement::from(hex_str); - let g_string: String = g.into(); + let g_string: String = g.as_ref().into(); assert!(hex_str == g_string) } From b473715af788c23bad0ce53f7d0a8a765f1307ec Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Mon, 16 Dec 2024 14:06:27 +0100 Subject: [PATCH 17/29] `Coin` and `RandomizedCoin` --- src/models.rs | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/models.rs b/src/models.rs index c8f40c2..e59bbec 100644 --- a/src/models.rs +++ b/src/models.rs @@ -78,7 +78,6 @@ pub struct ScriptAttribute { Ms: Option, } - impl ScriptAttribute { pub fn new(script: &[u8], blinding_factor: Option<&[u8; 32]>) -> Self { let s = Scalar::new(&Sha256Hash::hash(&script).to_byte_array()); @@ -181,8 +180,24 @@ impl MAC { } } +pub struct Coin { + pub amount_attribute: AmountAttribute, + pub script_attribute: Option, + pub mac: MAC, +} + +impl Coin { + pub fn new( + amount_attribute: AmountAttribute, + script_attribute: Option, + mac: MAC, + ) -> Self { + Coin { amount_attribute, script_attribute, mac } + } +} + #[allow(non_snake_case)] -pub struct RandomizedCredentials { +pub struct RandomizedCoin { /// Randomized Attribute Commitment pub Ca: GroupElement, /// Randomized Script Commitment @@ -195,23 +210,20 @@ pub struct RandomizedCredentials { pub Cv: GroupElement, } - -impl RandomizedCredentials { +impl RandomizedCoin { #[allow(non_snake_case)] pub fn new( - mac: &MAC, - amount_attribute: &mut AmountAttribute, - script_attribute: Option<&mut ScriptAttribute>, + coin: &mut Coin, reveal_script: bool, ) -> Result { - let t = mac.t.clone(); - let V = mac.V.as_ref(); - let t_bytes: [u8; 32] = (&mac.t).into(); + let t = coin.mac.t.clone(); + let V = coin.mac.V.as_ref(); + let t_bytes: [u8; 32] = (&coin.mac.t).into(); let U = hash_to_curve(&t_bytes)?; - let Ma = amount_attribute.commitment(); - let r = &amount_attribute.r; + let Ma = coin.amount_attribute.commitment(); + let r = &coin.amount_attribute.r; let Ms: GroupElement; - if let Some(attr) = script_attribute { + if let Some(attr) = &mut coin.script_attribute { if reveal_script { Ms = GENERATORS.G_blind.clone() * &attr.r; } else { @@ -227,6 +239,6 @@ impl RandomizedCredentials { let Cx1 = GENERATORS.X1.clone() * r + &(U * &t); let Cv = GENERATORS.Gz_mac.clone() * r + V; - Ok(RandomizedCredentials { Ca, Cs, Cx0, Cx1, Cv }) + Ok(RandomizedCoin { Ca, Cs, Cx0, Cx1, Cv }) } } From c232e5cfd2ae46f9db6b2bc7e74b5702c0e3dd5b Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Mon, 16 Dec 2024 19:18:23 +0100 Subject: [PATCH 18/29] SchnorrProver + SchnorrVerifier + MerlinTranscripts + models --- src/errors.rs | 2 + src/kvac.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 ++- src/models.rs | 14 +++++++ src/secp.rs | 6 ++- src/transcript.rs | 29 +++++++++++++ 6 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 src/kvac.rs create mode 100644 src/transcript.rs diff --git a/src/errors.rs b/src/errors.rs index d305b38..dc646ac 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -3,6 +3,8 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum Error { + #[error("Cannot instantiate SchnorrProver")] + InvalidConfiguration, #[error("Cannot map hash to a valid point on the curve")] InvalidPoint, /// Secp256k1 error diff --git a/src/kvac.rs b/src/kvac.rs new file mode 100644 index 0000000..836f86f --- /dev/null +++ b/src/kvac.rs @@ -0,0 +1,104 @@ + +use crate::models::{Statement, ZKP}; +use crate::transcript::CashuTranscript; +use crate::secp::{GroupElement, Scalar, GROUP_ELEMENT_ZERO}; + +pub struct SchnorrProver<'a> { + random_terms: Vec, + secrets: Vec, + transcript: &'a mut CashuTranscript, +} + +impl<'a> SchnorrProver<'a> { + pub fn new( + transcript: &'a mut CashuTranscript, + secrets: Vec, + ) -> Self { + SchnorrProver { + random_terms: vec![Scalar::random(); secrets.len()], + secrets, + transcript, + } + } + + #[allow(non_snake_case)] + pub fn add_statement(&mut self, statement: Statement) { + // Append proof-specific domain separator to the transcript + self.transcript.domain_sep(&statement.domain_separator); + + for equation in statement.equations.into_iter() { + + let mut R = GroupElement::new(&GROUP_ELEMENT_ZERO); + let V = equation.lhs; + + for row in equation.rhs.into_iter() { + for (k, P) in self.random_terms.iter().zip(row.into_iter()) { + R = R + (P * k).as_ref(); + } + } + + self.transcript.append_element(b"R_", &R); + self.transcript.append_element(b"V_", &V); + } + } + + #[allow(non_snake_case)] + pub fn prove(self) -> ZKP { + // Draw a challenge from the running transcript + let c = self.transcript.get_challenge(b"chall_"); + + // Create responses to the challenge + let mut responses: Vec = vec![]; + for (k, s) in self.random_terms.into_iter().zip(self.secrets.into_iter()) { + responses.push( + k + (s * c.as_ref()).as_ref() + ); + } + + ZKP { s: responses, c } + } +} + +pub struct SchnorrVerifier<'a> { + responses: Vec, + challenge: Scalar, + transcript: &'a mut CashuTranscript, +} + +impl<'a> SchnorrVerifier<'a> { + pub fn new( + transcript: &'a mut CashuTranscript, + proof: ZKP, + ) -> Self { + SchnorrVerifier { + responses: proof.s, + challenge: proof.c, + transcript, + } + } + + #[allow(non_snake_case)] + pub fn add_statement( + &mut self, + statement: Statement + ) { + // Append proof-specific domain separator to the transcript + self.transcript.domain_sep(&statement.domain_separator); + + for equation in statement.equations.into_iter() { + + let mut R = GroupElement::new(&GROUP_ELEMENT_ZERO); + let V = equation.lhs; + + for row in equation.rhs.into_iter() { + for (s, P) in self.responses.iter().zip(row.into_iter()) { + R = R + (P * s).as_ref(); + } + } + R = R - (V.clone() * self.challenge.as_ref()).as_ref(); + + self.transcript.append_element(b"R_", &R); + self.transcript.append_element(b"V_", &V); + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 4500bf2..8e3add1 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ pub mod errors; -mod generators; +pub mod generators; pub mod models; -mod secp; +pub mod secp; +pub mod transcript; +pub mod kvac; \ No newline at end of file diff --git a/src/models.rs b/src/models.rs index e59bbec..247571f 100644 --- a/src/models.rs +++ b/src/models.rs @@ -242,3 +242,17 @@ impl RandomizedCoin { Ok(RandomizedCoin { Ca, Cs, Cx0, Cx1, Cv }) } } + +pub struct Equation { + /// Left-hand side of the equation (public input) + pub lhs: GroupElement, + /// Right-hand side of the equation (construction of the relation) + pub rhs: Vec>, +} + +pub struct Statement { + /// Domain Separator of the proof + pub domain_separator: Vec, + /// Relations + pub equations: Vec +} diff --git a/src/secp.rs b/src/secp.rs index ba534df..87ab44c 100755 --- a/src/secp.rs +++ b/src/secp.rs @@ -19,6 +19,7 @@ pub static SECP256K1: Lazy> = Lazy::new(|| { ctx }); +#[derive(Clone)] pub struct Scalar { inner: Option, is_zero: bool, @@ -454,7 +455,10 @@ impl Into<[u8; 33]> for &GroupElement { if self.is_zero { GROUP_ELEMENT_ZERO } else { - self.inner.as_ref().expect("Expected inner PublicKey").serialize() + self.inner + .as_ref() + .expect("Expected inner PublicKey") + .serialize() } } } diff --git a/src/transcript.rs b/src/transcript.rs new file mode 100644 index 0000000..194ec08 --- /dev/null +++ b/src/transcript.rs @@ -0,0 +1,29 @@ +use merlin::Transcript; + +use crate::secp::{GroupElement, Scalar}; + +pub struct CashuTranscript { + inner: Transcript +} + +impl CashuTranscript { + pub fn new() -> Self { + let inner = Transcript::new(b"Secp256k1_Cashu_"); + CashuTranscript { inner } + } + + pub fn domain_sep(&mut self, message: &[u8]) { + self.inner.append_message(b"dom-sep", message); + } + + pub fn append_element(&mut self, label: &'static [u8], element: &GroupElement) { + let element_bytes_compressed: [u8; 33] = element.into(); + self.inner.append_message(label, &element_bytes_compressed); + } + + pub fn get_challenge(&mut self, label: &'static [u8]) -> Scalar { + let mut challenge: [u8; 32] = [0; 32]; + self.inner.challenge_bytes(label, &mut challenge); + Scalar::new(&challenge) + } +} \ No newline at end of file From 29acd1550db59740e0a8079d57c1f4ae45e20de1 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 17 Dec 2024 00:08:13 +0100 Subject: [PATCH 19/29] BootstrapStatement + IParamsStatement --- src/kvac.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/models.rs | 8 +++--- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/kvac.rs b/src/kvac.rs index 836f86f..f6cd375 100644 --- a/src/kvac.rs +++ b/src/kvac.rs @@ -1,5 +1,6 @@ -use crate::models::{Statement, ZKP}; +use crate::generators::{hash_to_curve, GENERATORS}; +use crate::models::{AmountAttribute, Coin, Equation, MintPrivateKey, Statement, ZKP}; use crate::transcript::CashuTranscript; use crate::secp::{GroupElement, Scalar, GROUP_ELEMENT_ZERO}; @@ -101,4 +102,70 @@ impl<'a> SchnorrVerifier<'a> { self.transcript.append_element(b"V_", &V); } } +} + +pub struct BootstrapStatement {} + +impl BootstrapStatement { + pub fn new(amount_attribute: &mut AmountAttribute) -> Statement { + Statement { + domain_separator: b"Bootstrap_Statement_", + equations: vec![ + Equation { // Ma = r*G_blind + lhs: amount_attribute.commitment(), + rhs: vec![vec![GENERATORS.G_blind.clone()]] + } + ] + } + } +} + +pub struct IParamsStatement; + +impl IParamsStatement { + #[allow(non_snake_case)] + pub fn new(mint_privkey: &mut MintPrivateKey, coin: &mut Coin) -> Statement { + let O = GroupElement::new(&GROUP_ELEMENT_ZERO); + let t_tag_bytes: [u8; 32] = coin.mac.t.as_ref().into(); + let t = coin.mac.t.as_ref(); + let U = hash_to_curve(&t_tag_bytes).expect("Couldn't get map MAC tag to GroupElement"); + let V = coin.mac.V.clone(); + let (Cw, I) = mint_privkey.pubkey(); + let Ma = coin.amount_attribute.commitment(); + let mut Ms = O.clone(); + if let Some(scr_attr) = &mut coin.script_attribute { + Ms = scr_attr.commitment(); + } + Statement { + domain_separator: b"Iparams_Statement_", + equations: vec![ + Equation { // Cw = w*W + w_*W_ + lhs: Cw, + rhs: vec![vec![GENERATORS.W.clone()]] + }, + Equation { // I = Gz_mac - x0*X0 - x1*X1 - ya*Gz_attribute - ys*Gz_script + lhs: I - &GENERATORS.Gz_mac, + rhs: vec![vec![ + O.clone(), + O.clone(), + -GENERATORS.X0.clone(), + -GENERATORS.X1.clone(), + -GENERATORS.Gz_attribute.clone(), + -GENERATORS.Gz_script.clone(), + ]] + }, + Equation { // V = w*W + x0*U + x1*t*U + ya*Ma + ys*Ms + lhs: V, + rhs: vec![vec![ + GENERATORS.W.clone(), + O, + U.clone(), + U * t.as_ref(), + Ma, + Ms, + ]] + } + ] + } + } } \ No newline at end of file diff --git a/src/models.rs b/src/models.rs index 247571f..bdcebc4 100644 --- a/src/models.rs +++ b/src/models.rs @@ -38,7 +38,7 @@ impl MintPrivateKey { vec![self.w.clone(), self.w_.clone(), self.x0.clone(), self.x1.clone(), self.ya.clone(), self.ys.clone()] } - pub fn pubkey(&mut self) -> Vec { + pub fn pubkey(&mut self) -> (GroupElement, GroupElement) { if !self.Cw.is_some() { self.Cw = Some(GENERATORS.W.clone()*&self.w + &(GENERATORS.W_.clone()*&self.w_)); } @@ -58,10 +58,10 @@ impl MintPrivateKey { ) ); } - vec![ + ( self.Cw.as_ref().expect("Expected Cw").clone(), self.I.as_ref().expect("Expected I").clone(), - ] + ) } } @@ -252,7 +252,7 @@ pub struct Equation { pub struct Statement { /// Domain Separator of the proof - pub domain_separator: Vec, + pub domain_separator: &'static [u8], /// Relations pub equations: Vec } From 93fe23b91707d7abd4829891ce552f72f6011e4a Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 17 Dec 2024 17:51:03 +0100 Subject: [PATCH 20/29] iparamsproof --- src/kvac.rs | 107 ++++++++++++++++++++++++++++++++++++++++------ src/models.rs | 14 +++--- src/transcript.rs | 6 +++ 3 files changed, 108 insertions(+), 19 deletions(-) diff --git a/src/kvac.rs b/src/kvac.rs index f6cd375..3cf2ce7 100644 --- a/src/kvac.rs +++ b/src/kvac.rs @@ -1,4 +1,6 @@ +use bitcoin::{amount, transaction}; + use crate::generators::{hash_to_curve, GENERATORS}; use crate::models::{AmountAttribute, Coin, Equation, MintPrivateKey, Statement, ZKP}; use crate::transcript::CashuTranscript; @@ -23,7 +25,7 @@ impl<'a> SchnorrProver<'a> { } #[allow(non_snake_case)] - pub fn add_statement(&mut self, statement: Statement) { + pub fn add_statement(self, statement: Statement) -> Self { // Append proof-specific domain separator to the transcript self.transcript.domain_sep(&statement.domain_separator); @@ -41,6 +43,7 @@ impl<'a> SchnorrProver<'a> { self.transcript.append_element(b"R_", &R); self.transcript.append_element(b"V_", &V); } + self } #[allow(non_snake_case)] @@ -80,9 +83,9 @@ impl<'a> SchnorrVerifier<'a> { #[allow(non_snake_case)] pub fn add_statement( - &mut self, + self, statement: Statement - ) { + ) -> Self { // Append proof-specific domain separator to the transcript self.transcript.domain_sep(&statement.domain_separator); @@ -101,36 +104,59 @@ impl<'a> SchnorrVerifier<'a> { self.transcript.append_element(b"R_", &R); self.transcript.append_element(b"V_", &V); } + self + } + + pub fn verify(&mut self) -> bool { + let challenge_ = self.transcript.get_challenge(b"chall_"); + challenge_ == self.challenge } } -pub struct BootstrapStatement {} +pub struct BootstrapProof; -impl BootstrapStatement { - pub fn new(amount_attribute: &mut AmountAttribute) -> Statement { +impl BootstrapProof { + + pub fn statement(amount_commitment: &GroupElement) -> Statement { Statement { domain_separator: b"Bootstrap_Statement_", equations: vec![ Equation { // Ma = r*G_blind - lhs: amount_attribute.commitment(), + lhs: amount_commitment.clone(), rhs: vec![vec![GENERATORS.G_blind.clone()]] } ] } } + + pub fn create(amount_attribute: &mut AmountAttribute, transcript: &mut CashuTranscript) -> ZKP { + let statement = BootstrapProof::statement(amount_attribute.commitment().as_ref()); + SchnorrProver::new(transcript, vec![amount_attribute.r.clone()]) + .add_statement(statement) + .prove() + } + + pub fn verify(amount_commitment: &GroupElement, proof: ZKP, transcript: &mut CashuTranscript) -> bool { + let statement = BootstrapProof::statement(amount_commitment); + SchnorrVerifier::new(transcript, proof) + .add_statement(statement) + .verify() + } + } -pub struct IParamsStatement; +pub struct IParamsProof; -impl IParamsStatement { - #[allow(non_snake_case)] - pub fn new(mint_privkey: &mut MintPrivateKey, coin: &mut Coin) -> Statement { +#[allow(non_snake_case)] +impl IParamsProof { + + pub fn statement(mint_publickey: (GroupElement, GroupElement), coin: &mut Coin) -> Statement { let O = GroupElement::new(&GROUP_ELEMENT_ZERO); let t_tag_bytes: [u8; 32] = coin.mac.t.as_ref().into(); let t = coin.mac.t.as_ref(); let U = hash_to_curve(&t_tag_bytes).expect("Couldn't get map MAC tag to GroupElement"); let V = coin.mac.V.clone(); - let (Cw, I) = mint_privkey.pubkey(); + let (Cw, I) = mint_publickey; let Ma = coin.amount_attribute.commitment(); let mut Ms = O.clone(); if let Some(scr_attr) = &mut coin.script_attribute { @@ -168,4 +194,61 @@ impl IParamsStatement { ] } } + + pub fn new(mint_privkey: &mut MintPrivateKey, coin: &mut Coin, transcript: &mut CashuTranscript) -> ZKP { + let mint_pubkey = mint_privkey.pubkey(); + let statement = IParamsProof::statement(mint_pubkey, coin); + SchnorrProver::new(transcript, mint_privkey.to_scalars()) + .add_statement(statement) + .prove() + } + + pub fn verify(&self, + mint_publickey: (GroupElement, GroupElement), + coin: &mut Coin, + proof: ZKP, + transcript: &mut CashuTranscript, + ) -> bool { + let statement = IParamsProof::statement(mint_publickey, coin); + SchnorrVerifier::new(transcript, proof) + .add_statement(statement) + .verify() + } +} + +#[cfg(test)] +mod tests{ + use bitcoin::{amount, Amount}; + + use crate::{models::{AmountAttribute, MintPrivateKey}, secp::Scalar, transcript::CashuTranscript}; + + use super::BootstrapProof; + + fn transcripts() -> (CashuTranscript, CashuTranscript) { + let mint_transcript = CashuTranscript::new(); + let client_transcript = CashuTranscript::new(); + (mint_transcript, client_transcript) + } + + #[test] + fn test_bootstrap() { + let (mut mint_transcript, mut client_transcript) = transcripts(); + let mut bootstrap_attr = AmountAttribute::new(0, None); + let proof = BootstrapProof::create(&mut bootstrap_attr, client_transcript.as_mut()); + assert!(BootstrapProof::verify(bootstrap_attr.commitment().as_ref(), proof, &mut mint_transcript)) + } + + #[test] + fn test_wrong_bootstrap() { + let (mut mint_transcript, mut client_transcript) = transcripts(); + let mut bootstrap_attr = AmountAttribute::new(1, None); + let proof = BootstrapProof::create(&mut bootstrap_attr, client_transcript.as_mut()); + assert!(!BootstrapProof::verify(bootstrap_attr.commitment().as_ref(), proof, &mut mint_transcript)) + } + + #[test] + fn test_iparams() { + let (mut mint_transcript, mut client_transcript) = transcripts(); + let amount_attr = AmountAttribute::new(12, None); + } } \ No newline at end of file diff --git a/src/models.rs b/src/models.rs index bdcebc4..5abcd6f 100644 --- a/src/models.rs +++ b/src/models.rs @@ -20,15 +20,15 @@ pub struct MintPrivateKey { impl MintPrivateKey { - pub fn from_scalars(scalars: [Scalar; 6]) -> Self { + pub fn from_scalars(scalars: &[Scalar; 6]) -> Self { let [w, w_, x0, x1, ya, ys] = scalars; MintPrivateKey { - w, - w_, - x0, - x1, - ya, - ys, + w: w.clone(), + w_: w_.clone(), + x0: x0.clone(), + x1: x1.clone(), + ya: ya.clone(), + ys: ys.clone(), Cw: None, I: None, } diff --git a/src/transcript.rs b/src/transcript.rs index 194ec08..a5c1bac 100644 --- a/src/transcript.rs +++ b/src/transcript.rs @@ -26,4 +26,10 @@ impl CashuTranscript { self.inner.challenge_bytes(label, &mut challenge); Scalar::new(&challenge) } +} + +impl AsMut for CashuTranscript { + fn as_mut(&mut self) -> &mut Self { + self + } } \ No newline at end of file From f4a425fdbf394df119e60b7804ca124fa1e4fa43 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 17 Dec 2024 19:48:45 +0100 Subject: [PATCH 21/29] tests --- src/kvac.rs | 28 ++++++++++++++++++++++------ src/models.rs | 2 +- src/secp.rs | 13 ++++++++++--- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/kvac.rs b/src/kvac.rs index 3cf2ce7..82d10ce 100644 --- a/src/kvac.rs +++ b/src/kvac.rs @@ -167,7 +167,7 @@ impl IParamsProof { equations: vec![ Equation { // Cw = w*W + w_*W_ lhs: Cw, - rhs: vec![vec![GENERATORS.W.clone()]] + rhs: vec![vec![GENERATORS.W.clone(), GENERATORS.W_.clone()]] }, Equation { // I = Gz_mac - x0*X0 - x1*X1 - ya*Gz_attribute - ys*Gz_script lhs: I - &GENERATORS.Gz_mac, @@ -203,7 +203,7 @@ impl IParamsProof { .prove() } - pub fn verify(&self, + pub fn verify( mint_publickey: (GroupElement, GroupElement), coin: &mut Coin, proof: ZKP, @@ -218,11 +218,10 @@ impl IParamsProof { #[cfg(test)] mod tests{ - use bitcoin::{amount, Amount}; - use crate::{models::{AmountAttribute, MintPrivateKey}, secp::Scalar, transcript::CashuTranscript}; + use crate::{models::{AmountAttribute, Coin, MintPrivateKey, MAC}, secp::Scalar, transcript::CashuTranscript}; - use super::BootstrapProof; + use super::{BootstrapProof, IParamsProof}; fn transcripts() -> (CashuTranscript, CashuTranscript) { let mint_transcript = CashuTranscript::new(); @@ -230,6 +229,18 @@ mod tests{ (mint_transcript, client_transcript) } + fn privkey() -> MintPrivateKey { + let scalars = [ + Scalar::random(), + Scalar::random(), + Scalar::random(), + Scalar::random(), + Scalar::random(), + Scalar::random() + ]; + MintPrivateKey::from_scalars(&scalars) + } + #[test] fn test_bootstrap() { let (mut mint_transcript, mut client_transcript) = transcripts(); @@ -249,6 +260,11 @@ mod tests{ #[test] fn test_iparams() { let (mut mint_transcript, mut client_transcript) = transcripts(); - let amount_attr = AmountAttribute::new(12, None); + let mut mint_privkey = privkey(); + let mut amount_attr = AmountAttribute::new(12, None); + let mac = MAC::generate(&mint_privkey, &amount_attr.commitment(), None, None).expect("Couldn't generate MAC"); + let mut coin = Coin::new(amount_attr, None, mac); + let proof = IParamsProof::new(&mut mint_privkey, &mut coin, &mut client_transcript); + assert!(IParamsProof::verify(mint_privkey.pubkey(), &mut coin, proof, &mut mint_transcript)); } } \ No newline at end of file diff --git a/src/models.rs b/src/models.rs index 5abcd6f..6c6808d 100644 --- a/src/models.rs +++ b/src/models.rs @@ -212,7 +212,7 @@ pub struct RandomizedCoin { impl RandomizedCoin { #[allow(non_snake_case)] - pub fn new( + pub fn from_coin( coin: &mut Coin, reveal_script: bool, ) -> Result { diff --git a/src/secp.rs b/src/secp.rs index 87ab44c..de20257 100755 --- a/src/secp.rs +++ b/src/secp.rs @@ -181,9 +181,16 @@ impl GroupElement { } pub fn clone(&self) -> Self { - GroupElement { - inner: Some(self.inner.unwrap().clone()), - is_zero: self.is_zero, + if self.is_zero { + GroupElement { + inner: None, + is_zero: true, + } + } else { + GroupElement { + inner: Some(self.inner.unwrap().clone()), + is_zero: self.is_zero, + } } } From cb73673d9190cc27f83ca45cef9e28fef342a806 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 17 Dec 2024 20:16:56 +0100 Subject: [PATCH 22/29] test_wrong_iparams --- src/kvac.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/kvac.rs b/src/kvac.rs index 82d10ce..6a674bc 100644 --- a/src/kvac.rs +++ b/src/kvac.rs @@ -267,4 +267,16 @@ mod tests{ let proof = IParamsProof::new(&mut mint_privkey, &mut coin, &mut client_transcript); assert!(IParamsProof::verify(mint_privkey.pubkey(), &mut coin, proof, &mut mint_transcript)); } + + #[test] + fn test_wrong_iparams() { + let (mut mint_transcript, mut client_transcript) = transcripts(); + let mut mint_privkey = privkey(); + let mut mint_privkey_1 = privkey(); + let mut amount_attr = AmountAttribute::new(12, None); + let mac = MAC::generate(&mint_privkey, &amount_attr.commitment(), None, None).expect("Couldn't generate MAC"); + let mut coin = Coin::new(amount_attr, None, mac); + let proof = IParamsProof::new(&mut mint_privkey, &mut coin, &mut client_transcript); + assert!(!IParamsProof::verify(mint_privkey_1.pubkey(), &mut coin, proof, &mut mint_transcript)) + } } \ No newline at end of file From b90442838dd4829b359f942a71f821d8b18fee4f Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 17 Dec 2024 21:40:50 +0100 Subject: [PATCH 23/29] MacProof --- src/kvac.rs | 110 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 5 deletions(-) diff --git a/src/kvac.rs b/src/kvac.rs index 6a674bc..45d6a73 100644 --- a/src/kvac.rs +++ b/src/kvac.rs @@ -1,10 +1,9 @@ - -use bitcoin::{amount, transaction}; - use crate::generators::{hash_to_curve, GENERATORS}; -use crate::models::{AmountAttribute, Coin, Equation, MintPrivateKey, Statement, ZKP}; +use crate::models::{AmountAttribute, Coin, Equation, MintPrivateKey, RandomizedCoin, Statement, ZKP}; use crate::transcript::CashuTranscript; -use crate::secp::{GroupElement, Scalar, GROUP_ELEMENT_ZERO}; +use crate::secp::{GroupElement, Scalar, GROUP_ELEMENT_ZERO, SCALAR_ZERO}; +use bitcoin::hashes::sha256::Hash as Sha256Hash; +use bitcoin::hashes::Hash; pub struct SchnorrProver<'a> { random_terms: Vec, @@ -145,6 +144,107 @@ impl BootstrapProof { } +pub struct MacProof; + +#[allow(non_snake_case)] +impl MacProof { + + pub fn statement(Z: GroupElement, I: GroupElement, randomized_coin: &RandomizedCoin) -> Statement { + let Cx0 = randomized_coin.Cx0.clone(); + let Cx1 = randomized_coin.Cx1.clone(); + let Ca = randomized_coin.Ca.clone(); + let O = GroupElement::new(&GROUP_ELEMENT_ZERO); + Statement { + domain_separator: b"MAC_Statement_", + equations: vec![ // Z = r*I + Equation { + lhs: Z, + rhs: vec![vec![I]], + }, + Equation { // Cx1 = t*Cx0 + (-tr)*X0 + r*X1 + lhs: Cx1, + rhs: vec![ + vec![GENERATORS.X1.clone(), GENERATORS.X0.clone(), Cx0,] + ] + }, + Equation { // Ca = r_a*Gz_attribute + r_a*G_blind + a*G_amount + // MULTI-ROW: `r` witness is used twice for Gz_amount and G_blind + lhs: Ca, + rhs: vec![ + vec![GENERATORS.Gz_attribute.clone(), O.clone(), O.clone(), GENERATORS.G_amount.clone()], + vec![GENERATORS.G_blind.clone()] + ] + }, + ] + } + } + + pub fn new( + mint_publickey: (GroupElement, GroupElement), + coin: &Coin, + randomized_coin: &RandomizedCoin, + transcript: &mut CashuTranscript, + ) -> ZKP { + let r_a = coin.amount_attribute.r.clone(); + let a = coin.amount_attribute.a.clone(); + let t = coin.mac.t.clone(); + let r0 = r_a.clone()*t.as_ref(); + let (_Cw, I) = mint_publickey; + let Z = I.clone() * &coin.amount_attribute.r; + let statement = MacProof::statement(Z, I, randomized_coin); + SchnorrProver::new( + transcript, + vec![ + r_a, r0, t, a + ], + ).add_statement(statement).prove() + } + + pub fn verify( + mint_privkey: &mut MintPrivateKey, + randomized_coin: &RandomizedCoin, + script: Option<&[u8]>, + proof: ZKP, + transcript: &mut CashuTranscript, + ) -> bool { + let (w, x0, x1, ya, ys) = ( + &mint_privkey.w, + &mint_privkey.x0, + &mint_privkey.x1, + &mint_privkey.ya, + &mint_privkey.ys + ); + let (Cx0, Cx1, Ca, Cs, Cv) = ( + randomized_coin.Cx0.clone(), + randomized_coin.Cx1.clone(), + randomized_coin.Ca.clone(), + randomized_coin.Cs.clone(), + randomized_coin.Cv.clone(), + ); + let mut S = GroupElement::new(&GROUP_ELEMENT_ZERO); + if let Some(scr) = script { + let s = Scalar::new(&Sha256Hash::hash(&scr).to_byte_array()); + S = GENERATORS.G_script.clone()*s.as_ref(); + } + let Z = Cv - &( + GENERATORS.W.clone() * w + &( + Cx0 * x0 + &( + Cx1 * x1 + &( + Ca * ya + &( + (Cs + &S) * ys + ) + ) + ) + ) + ); + let (_Cw, I) = mint_privkey.pubkey(); + let statement = MacProof::statement(Z, I, randomized_coin); + SchnorrVerifier::new(transcript, proof) + .add_statement(statement) + .verify() + } +} + pub struct IParamsProof; #[allow(non_snake_case)] From ada652d3d44e106c0c7f05085ace6cb56ddb61e0 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 17 Dec 2024 22:25:12 +0100 Subject: [PATCH 24/29] test mac proof --- src/kvac.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/kvac.rs b/src/kvac.rs index 45d6a73..f81f1b6 100644 --- a/src/kvac.rs +++ b/src/kvac.rs @@ -188,7 +188,7 @@ impl MacProof { let r_a = coin.amount_attribute.r.clone(); let a = coin.amount_attribute.a.clone(); let t = coin.mac.t.clone(); - let r0 = r_a.clone()*t.as_ref(); + let r0 = -r_a.clone()*t.as_ref(); let (_Cw, I) = mint_publickey; let Z = I.clone() * &coin.amount_attribute.r; let statement = MacProof::statement(Z, I, randomized_coin); @@ -319,9 +319,9 @@ impl IParamsProof { #[cfg(test)] mod tests{ - use crate::{models::{AmountAttribute, Coin, MintPrivateKey, MAC}, secp::Scalar, transcript::CashuTranscript}; + use crate::{models::{AmountAttribute, Coin, MintPrivateKey, RandomizedCoin, MAC}, secp::Scalar, transcript::CashuTranscript}; - use super::{BootstrapProof, IParamsProof}; + use super::{BootstrapProof, IParamsProof, MacProof}; fn transcripts() -> (CashuTranscript, CashuTranscript) { let mint_transcript = CashuTranscript::new(); @@ -379,4 +379,16 @@ mod tests{ let proof = IParamsProof::new(&mut mint_privkey, &mut coin, &mut client_transcript); assert!(!IParamsProof::verify(mint_privkey_1.pubkey(), &mut coin, proof, &mut mint_transcript)) } + + #[test] + fn test_mac() { + let (mut mint_transcript, mut client_transcript) = transcripts(); + let mut mint_privkey = privkey(); + let mut amount_attr = AmountAttribute::new(12, None); + let mac = MAC::generate(&mint_privkey, &amount_attr.commitment(), None, None).expect("Couldn't generate MAC"); + let mut coin = Coin::new(amount_attr, None, mac); + let randomized_coin = RandomizedCoin::from_coin(&mut coin, false).expect("Expected a randomized coin"); + let proof = MacProof::new(mint_privkey.pubkey(), &coin, &randomized_coin, &mut client_transcript); + assert!(MacProof::verify(&mut mint_privkey, &randomized_coin, None, proof, &mut mint_transcript)); + } } \ No newline at end of file From a56a22005a72c27b57fb241e7a030fec40378a24 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 17 Dec 2024 23:52:50 +0100 Subject: [PATCH 25/29] test wrong mac --- src/kvac.rs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/kvac.rs b/src/kvac.rs index f81f1b6..50990b9 100644 --- a/src/kvac.rs +++ b/src/kvac.rs @@ -319,7 +319,7 @@ impl IParamsProof { #[cfg(test)] mod tests{ - use crate::{models::{AmountAttribute, Coin, MintPrivateKey, RandomizedCoin, MAC}, secp::Scalar, transcript::CashuTranscript}; + use crate::{errors::Error, generators::{hash_to_curve, GENERATORS}, models::{AmountAttribute, Coin, MintPrivateKey, RandomizedCoin, MAC}, secp::{GroupElement, Scalar, GROUP_ELEMENT_ZERO}, transcript::CashuTranscript}; use super::{BootstrapProof, IParamsProof, MacProof}; @@ -391,4 +391,36 @@ mod tests{ let proof = MacProof::new(mint_privkey.pubkey(), &coin, &randomized_coin, &mut client_transcript); assert!(MacProof::verify(&mut mint_privkey, &randomized_coin, None, proof, &mut mint_transcript)); } + + #[test] + fn test_wrong_mac() { + #[allow(non_snake_case)] + fn generate_custom_rand(coin: &mut Coin) -> Result { + let t = coin.mac.t.clone(); + let V = coin.mac.V.as_ref(); + let t_bytes: [u8; 32] = (&coin.mac.t).into(); + let U = hash_to_curve(&t_bytes)?; + let Ma = coin.amount_attribute.commitment(); + // We try and randomize differently. + let z = Scalar::random(); + let Ms: GroupElement = GroupElement::new(&GROUP_ELEMENT_ZERO); + + let Ca = GENERATORS.Gz_attribute.clone() * z.as_ref() + &Ma; + let Cs = GENERATORS.Gz_script.clone() * z.as_ref() + &Ms; + let Cx0 = GENERATORS.X0.clone() * z.as_ref() + &U; + let Cx1 = GENERATORS.X1.clone() * z.as_ref() + &(U * &t); + let Cv = GENERATORS.Gz_mac.clone() * z.as_ref() + V; + + Ok(RandomizedCoin { Ca, Cs, Cx0, Cx1, Cv }) + } + + let (mut mint_transcript, mut client_transcript) = transcripts(); + let mut mint_privkey = privkey(); + let mut amount_attr = AmountAttribute::new(12, None); + let mac = MAC::generate(&mint_privkey, &amount_attr.commitment(), None, None).expect("Couldn't generate MAC"); + let mut coin = Coin::new(amount_attr, None, mac); + let randomized_coin = generate_custom_rand(&mut coin).expect("Expected a randomized coin"); + let proof = MacProof::new(mint_privkey.pubkey(), &coin, &randomized_coin, &mut client_transcript); + assert!(!MacProof::verify(&mut mint_privkey, &randomized_coin, None, proof, &mut mint_transcript)); + } } \ No newline at end of file From 42712d72d1fae759195b0060bdd4e75d585ff8ca Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Wed, 18 Dec 2024 00:57:40 +0100 Subject: [PATCH 26/29] balanceproof + `UnsignedCoin` --- src/kvac.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/models.rs | 32 ++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/kvac.rs b/src/kvac.rs index 50990b9..dd6510d 100644 --- a/src/kvac.rs +++ b/src/kvac.rs @@ -1,5 +1,5 @@ use crate::generators::{hash_to_curve, GENERATORS}; -use crate::models::{AmountAttribute, Coin, Equation, MintPrivateKey, RandomizedCoin, Statement, ZKP}; +use crate::models::{AmountAttribute, Coin, Equation, MintPrivateKey, RandomizedCoin, Statement, UnsignedCoin, ZKP}; use crate::transcript::CashuTranscript; use crate::secp::{GroupElement, Scalar, GROUP_ELEMENT_ZERO, SCALAR_ZERO}; use bitcoin::hashes::sha256::Hash as Sha256Hash; @@ -316,6 +316,71 @@ impl IParamsProof { } } +pub struct BalanceProof; + +#[allow(non_snake_case)] +impl BalanceProof { + + pub fn statement(B: GroupElement) -> Statement { + Statement { + domain_separator: b"Balance_Statement_", + equations: vec![ + Equation { + lhs: B, + rhs: vec![vec![GENERATORS.Gz_attribute.clone(), GENERATORS.G_blind.clone()]] + } + ] + } + } + + pub fn new( + inputs: Vec<&Coin>, + outputs: Vec<&Coin>, + transcript: &mut CashuTranscript) -> ZKP { + let mut r_sum = Scalar::new(&SCALAR_ZERO); + for input in inputs.into_iter() { + r_sum = r_sum + &input.amount_attribute.r; + } + let mut r_sum_ = Scalar::new(&SCALAR_ZERO); + for output in outputs.into_iter() { + r_sum_ = r_sum_ + &output.amount_attribute.r; + } + let delta_r = (-r_sum_) + r_sum.as_ref(); + let B = GENERATORS.Gz_attribute.clone() * r_sum.as_ref() + ( + GENERATORS.G_blind.clone() * delta_r.as_ref() + ).as_ref(); + let statement = BalanceProof::statement(B); + SchnorrProver::new(transcript, vec![r_sum, delta_r]) + .add_statement(statement) + .prove() + } + + pub fn verify( + inputs: Vec<&RandomizedCoin>, + outputs: Vec<&UnsignedCoin>, + delta_amount: i64, + proof: ZKP, + transcript: &mut CashuTranscript + ) -> bool { + let delta_a = Scalar::from(delta_amount.abs() as u64); + let mut B = GENERATORS.G_amount.clone() * &delta_a; + if delta_amount >= 0 { + B.negate(); + } + for input in inputs.into_iter() { + B = B + input.Ca.as_ref(); + } + for output in outputs.into_iter() { + B = B - output.amount_commitment.as_ref(); + } + let statement = BalanceProof::statement(B); + SchnorrVerifier::new(transcript, proof) + .add_statement(statement) + .verify() + + } +} + #[cfg(test)] mod tests{ diff --git a/src/models.rs b/src/models.rs index 6c6808d..1b25003 100644 --- a/src/models.rs +++ b/src/models.rs @@ -180,6 +180,36 @@ impl MAC { } } +/// Contains the amount and script commitments +/// for a `Coin` which has not yet been signed by the Mint +pub struct UnsignedCoin { + pub amount_commitment: GroupElement, + pub script_commitment: Option, +} + +impl UnsignedCoin { + pub fn from_attributes( + mut amount_attribute: AmountAttribute, + script_attribute: Option + ) -> Self { + + if let Some( mut script_attr) = script_attribute { + UnsignedCoin { + amount_commitment: amount_attribute.commitment(), + script_commitment: Some(script_attr.commitment()) + } + } else { + UnsignedCoin { + amount_commitment: amount_attribute.commitment(), + script_commitment: None, + } + } + } +} + +/// Spendable coin. +/// Contains `AmountAttribute`, `ScriptAttribute` +/// and the `MAC` approval by the Mint. pub struct Coin { pub amount_attribute: AmountAttribute, pub script_attribute: Option, @@ -196,6 +226,8 @@ impl Coin { } } +/// Contains randomized commitments of a `Coin`. +/// Used for unlinkable multi-show. #[allow(non_snake_case)] pub struct RandomizedCoin { /// Randomized Attribute Commitment From a83fa2e14768f3a98072fd565aab40af48144cbb Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Wed, 18 Dec 2024 11:46:22 +0100 Subject: [PATCH 27/29] test balance proof --- src/kvac.rs | 66 ++++++++++++++++++++++++++++++++++++++++++--------- src/models.rs | 27 --------------------- 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/src/kvac.rs b/src/kvac.rs index dd6510d..7951105 100644 --- a/src/kvac.rs +++ b/src/kvac.rs @@ -1,5 +1,5 @@ use crate::generators::{hash_to_curve, GENERATORS}; -use crate::models::{AmountAttribute, Coin, Equation, MintPrivateKey, RandomizedCoin, Statement, UnsignedCoin, ZKP}; +use crate::models::{AmountAttribute, Coin, Equation, MintPrivateKey, RandomizedCoin, Statement, ZKP}; use crate::transcript::CashuTranscript; use crate::secp::{GroupElement, Scalar, GROUP_ELEMENT_ZERO, SCALAR_ZERO}; use bitcoin::hashes::sha256::Hash as Sha256Hash; @@ -334,16 +334,16 @@ impl BalanceProof { } pub fn new( - inputs: Vec<&Coin>, - outputs: Vec<&Coin>, + inputs: &Vec, + outputs: &Vec, transcript: &mut CashuTranscript) -> ZKP { let mut r_sum = Scalar::new(&SCALAR_ZERO); for input in inputs.into_iter() { - r_sum = r_sum + &input.amount_attribute.r; + r_sum = r_sum + &input.r; } let mut r_sum_ = Scalar::new(&SCALAR_ZERO); for output in outputs.into_iter() { - r_sum_ = r_sum_ + &output.amount_attribute.r; + r_sum_ = r_sum_ + &output.r; } let delta_r = (-r_sum_) + r_sum.as_ref(); let B = GENERATORS.Gz_attribute.clone() * r_sum.as_ref() + ( @@ -356,8 +356,8 @@ impl BalanceProof { } pub fn verify( - inputs: Vec<&RandomizedCoin>, - outputs: Vec<&UnsignedCoin>, + inputs: &Vec, + outputs: &Vec, delta_amount: i64, proof: ZKP, transcript: &mut CashuTranscript @@ -367,11 +367,11 @@ impl BalanceProof { if delta_amount >= 0 { B.negate(); } - for input in inputs.into_iter() { + for input in inputs.iter() { B = B + input.Ca.as_ref(); } - for output in outputs.into_iter() { - B = B - output.amount_commitment.as_ref(); + for output in outputs.iter() { + B = B - output.as_ref(); } let statement = BalanceProof::statement(B); SchnorrVerifier::new(transcript, proof) @@ -386,7 +386,7 @@ mod tests{ use crate::{errors::Error, generators::{hash_to_curve, GENERATORS}, models::{AmountAttribute, Coin, MintPrivateKey, RandomizedCoin, MAC}, secp::{GroupElement, Scalar, GROUP_ELEMENT_ZERO}, transcript::CashuTranscript}; - use super::{BootstrapProof, IParamsProof, MacProof}; + use super::{BalanceProof, BootstrapProof, IParamsProof, MacProof}; fn transcripts() -> (CashuTranscript, CashuTranscript) { let mint_transcript = CashuTranscript::new(); @@ -488,4 +488,48 @@ mod tests{ let proof = MacProof::new(mint_privkey.pubkey(), &coin, &randomized_coin, &mut client_transcript); assert!(!MacProof::verify(&mut mint_privkey, &randomized_coin, None, proof, &mut mint_transcript)); } + + #[test] + fn test_balance() { + let (mut mint_transcript, mut client_transcript) = transcripts(); + let privkey = privkey(); + let mut inputs = vec![AmountAttribute::new(12, None), AmountAttribute::new(11, None)]; + let outputs = vec![AmountAttribute::new(23, None)]; + // We assume the inputs were already attributed a MAC previously + let macs: Vec = inputs + .iter_mut() + .map(|input| MAC::generate(&privkey, &input.commitment(), None, None).expect("MAC expected")) + .collect(); + let proof = BalanceProof::new(&inputs, &outputs, &mut client_transcript); + let mut coins: Vec = macs.into_iter().zip(inputs.into_iter()) + .map(|(mac, input)| Coin::new(input, None, mac)) + .collect(); + let randomized_coins: Vec = coins.iter_mut() + .map(|coin| RandomizedCoin::from_coin(coin, false).expect("RandomzedCoin expected")) + .collect(); + let outputs: Vec = outputs.into_iter().map(|mut output| output.commitment()).collect(); + assert!(BalanceProof::verify(&randomized_coins, &outputs, 0, proof, &mut mint_transcript)); + } + + #[test] + fn test_wrong_balance() { + let (mut mint_transcript, mut client_transcript) = transcripts(); + let privkey = privkey(); + let mut inputs = vec![AmountAttribute::new(12, None), AmountAttribute::new(11, None)]; + let outputs = vec![AmountAttribute::new(23, None)]; + // We assume the inputs were already attributed a MAC previously + let macs: Vec = inputs + .iter_mut() + .map(|input| MAC::generate(&privkey, &input.commitment(), None, None).expect("MAC expected")) + .collect(); + let proof = BalanceProof::new(&inputs, &outputs, &mut client_transcript); + let mut coins: Vec = macs.into_iter().zip(inputs.into_iter()) + .map(|(mac, input)| Coin::new(input, None, mac)) + .collect(); + let randomized_coins: Vec = coins.iter_mut() + .map(|coin| RandomizedCoin::from_coin(coin, false).expect("RandomzedCoin expected")) + .collect(); + let outputs: Vec = outputs.into_iter().map(|mut output| output.commitment()).collect(); + assert!(!BalanceProof::verify(&randomized_coins, &outputs, 1, proof, &mut mint_transcript)); + } } \ No newline at end of file diff --git a/src/models.rs b/src/models.rs index 1b25003..b8f0822 100644 --- a/src/models.rs +++ b/src/models.rs @@ -180,33 +180,6 @@ impl MAC { } } -/// Contains the amount and script commitments -/// for a `Coin` which has not yet been signed by the Mint -pub struct UnsignedCoin { - pub amount_commitment: GroupElement, - pub script_commitment: Option, -} - -impl UnsignedCoin { - pub fn from_attributes( - mut amount_attribute: AmountAttribute, - script_attribute: Option - ) -> Self { - - if let Some( mut script_attr) = script_attribute { - UnsignedCoin { - amount_commitment: amount_attribute.commitment(), - script_commitment: Some(script_attr.commitment()) - } - } else { - UnsignedCoin { - amount_commitment: amount_attribute.commitment(), - script_commitment: None, - } - } - } -} - /// Spendable coin. /// Contains `AmountAttribute`, `ScriptAttribute` /// and the `MAC` approval by the Mint. From 83b742296eb5b60e28a4f5c28a699ae071fceff0 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Wed, 18 Dec 2024 18:52:59 +0100 Subject: [PATCH 28/29] ScriptEqualityProof --- src/errors.rs | 4 ++ src/generators.rs | 6 ++- src/kvac.rs | 114 +++++++++++++++++++++++++++++++++++++++++++++- src/secp.rs | 1 + 4 files changed, 122 insertions(+), 3 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index dc646ac..da25676 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -3,6 +3,10 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum Error { + #[error("No script in this was found")] + NoScriptProvided, + #[error("Cannot create proofs of empty")] + EmptyList, #[error("Cannot instantiate SchnorrProver")] InvalidConfiguration, #[error("Cannot map hash to a valid point on the curve")] diff --git a/src/generators.rs b/src/generators.rs index 390f049..f14f208 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -1,5 +1,5 @@ use crate::errors::Error; -use crate::secp::GroupElement; +use crate::secp::{GroupElement, GROUP_ELEMENT_ZERO}; use bitcoin::hashes::sha256::Hash as Sha256Hash; use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; @@ -35,6 +35,9 @@ pub fn hash_to_curve(message: &[u8]) -> Result { #[allow(non_snake_case)] pub struct Generators { + // Point at infinity (ZERO) + pub O: GroupElement, + pub W: GroupElement, pub W_: GroupElement, pub X0: GroupElement, @@ -61,6 +64,7 @@ impl Generators { let g_blind = hash_to_curve(b"G_blind").expect("Failed to hash to curve"); Generators { + O: GroupElement::new(&GROUP_ELEMENT_ZERO), W: w, W_: w_, X0: x0, diff --git a/src/kvac.rs b/src/kvac.rs index 7951105..3cff928 100644 --- a/src/kvac.rs +++ b/src/kvac.rs @@ -1,5 +1,6 @@ +use crate::errors::Error; use crate::generators::{hash_to_curve, GENERATORS}; -use crate::models::{AmountAttribute, Coin, Equation, MintPrivateKey, RandomizedCoin, Statement, ZKP}; +use crate::models::{AmountAttribute, Coin, Equation, MintPrivateKey, RandomizedCoin, ScriptAttribute, Statement, ZKP}; use crate::transcript::CashuTranscript; use crate::secp::{GroupElement, Scalar, GROUP_ELEMENT_ZERO, SCALAR_ZERO}; use bitcoin::hashes::sha256::Hash as Sha256Hash; @@ -280,7 +281,7 @@ impl IParamsProof { -GENERATORS.Gz_script.clone(), ]] }, - Equation { // V = w*W + x0*U + x1*t*U + ya*Ma + ys*Ms + Equation { // V = w*W + x0*U + x1*t*U + ya*Ma + ys*Ms lhs: V, rhs: vec![vec![ GENERATORS.W.clone(), @@ -381,6 +382,115 @@ impl BalanceProof { } } + +pub struct ScriptEqualityProof; + +#[allow(non_snake_case)] +impl ScriptEqualityProof { + pub fn statement( + inputs: &Vec, + outputs: &Vec<(GroupElement, GroupElement)> + ) -> Statement { + let O: GroupElement = GENERATORS.O.clone(); + let mut equations: Vec = Vec::new(); + + for (i, zcoin) in inputs.iter().enumerate() { + let construction = vec![ + vec![GENERATORS.G_script.clone()], + vec![O.clone(); i], + vec![GENERATORS.Gz_script.clone()], + vec![O.clone(); inputs.len() - 1], + vec![GENERATORS.G_blind.clone()], + ] + .into_iter() + .flatten() + .collect::>(); + + equations.push(Equation { + lhs: zcoin.Cs.clone(), + rhs: vec![construction], + }); + } + for (i, commitments) in outputs.iter().enumerate() { + let construction = vec![ + vec![GENERATORS.G_script.clone()], + vec![O.clone(); 2*inputs.len()+i], + vec![GENERATORS.G_blind.clone()], + ] + .into_iter() + .flatten() + .collect::>(); + + let (_Ma, Ms) = commitments; + equations.push(Equation { + lhs: Ms.clone(), + rhs: vec![construction], + }); + } + Statement { + domain_separator: b"Script_Equality_Statement_", + equations, + } + } + + pub fn new( + inputs: &Vec, + randomized_inputs: &Vec, + outputs: &mut Vec<(AmountAttribute, ScriptAttribute)>, + transcript: &mut CashuTranscript, + ) -> Result { + if inputs.is_empty() || + randomized_inputs.is_empty() || + outputs.is_empty() + { + return Err(Error::EmptyList); + } + let commitments: Vec<(GroupElement, GroupElement)> = outputs + .iter_mut() + .map(|(aa, sa)| (aa.commitment(), sa.commitment())) + .collect(); + let statement = ScriptEqualityProof::statement(randomized_inputs, &commitments); + let s = inputs[0].script_attribute.as_ref().ok_or(Error::NoScriptProvided)?.s.clone(); + let r_a_list = inputs + .iter() + .map(|coin| coin.amount_attribute.r.clone()) + .collect(); + let r_s_list = inputs + .iter() + .map(|coin| coin.script_attribute.as_ref().expect("Expected Script Attribute").r.clone()) + .collect(); + let new_r_s_list = outputs + .iter() + .map(|(_, script_attr)| script_attr.r.clone()) + .collect(); + Ok(SchnorrProver::new( + transcript, + vec![vec![s], r_a_list, r_s_list, new_r_s_list] + .into_iter() + .flatten() + .collect() + ).add_statement(statement).prove() + ) + } + + pub fn verify( + randomized_inputs: &Vec, + outputs: &Vec<(GroupElement, GroupElement)>, + proof: ZKP, + transcript: &mut CashuTranscript, + ) -> bool { + if randomized_inputs.is_empty() || + outputs.is_empty() + { + return false; + } + let statement = ScriptEqualityProof::statement(randomized_inputs, outputs); + SchnorrVerifier::new(transcript, proof) + .add_statement(statement) + .verify() + } +} + #[cfg(test)] mod tests{ diff --git a/src/secp.rs b/src/secp.rs index de20257..d25fcfd 100755 --- a/src/secp.rs +++ b/src/secp.rs @@ -25,6 +25,7 @@ pub struct Scalar { is_zero: bool, } +#[derive(Clone)] pub struct GroupElement { inner: Option, is_zero: bool, From d7e3fe27e700e77ddf2bce282366ebba0243a532 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Wed, 18 Dec 2024 22:23:42 +0100 Subject: [PATCH 29/29] script equality test --- src/kvac.rs | 144 ++++++++++++++++++++++++++++++++++++++------------ src/models.rs | 109 +++++++++++++++++--------------------- 2 files changed, 160 insertions(+), 93 deletions(-) diff --git a/src/kvac.rs b/src/kvac.rs index 3cff928..6260a58 100644 --- a/src/kvac.rs +++ b/src/kvac.rs @@ -181,7 +181,7 @@ impl MacProof { } pub fn new( - mint_publickey: (GroupElement, GroupElement), + mint_publickey: (&GroupElement, &GroupElement), coin: &Coin, randomized_coin: &RandomizedCoin, transcript: &mut CashuTranscript, @@ -192,7 +192,7 @@ impl MacProof { let r0 = -r_a.clone()*t.as_ref(); let (_Cw, I) = mint_publickey; let Z = I.clone() * &coin.amount_attribute.r; - let statement = MacProof::statement(Z, I, randomized_coin); + let statement = MacProof::statement(Z, I.clone(), randomized_coin); SchnorrProver::new( transcript, vec![ @@ -202,7 +202,7 @@ impl MacProof { } pub fn verify( - mint_privkey: &mut MintPrivateKey, + mint_privkey: &MintPrivateKey, randomized_coin: &RandomizedCoin, script: Option<&[u8]>, proof: ZKP, @@ -239,7 +239,7 @@ impl MacProof { ) ); let (_Cw, I) = mint_privkey.pubkey(); - let statement = MacProof::statement(Z, I, randomized_coin); + let statement = MacProof::statement(Z, I.clone(), randomized_coin); SchnorrVerifier::new(transcript, proof) .add_statement(statement) .verify() @@ -251,27 +251,27 @@ pub struct IParamsProof; #[allow(non_snake_case)] impl IParamsProof { - pub fn statement(mint_publickey: (GroupElement, GroupElement), coin: &mut Coin) -> Statement { + pub fn statement(mint_publickey: (&GroupElement, &GroupElement), coin: &Coin) -> Statement { let O = GroupElement::new(&GROUP_ELEMENT_ZERO); let t_tag_bytes: [u8; 32] = coin.mac.t.as_ref().into(); let t = coin.mac.t.as_ref(); let U = hash_to_curve(&t_tag_bytes).expect("Couldn't get map MAC tag to GroupElement"); let V = coin.mac.V.clone(); let (Cw, I) = mint_publickey; - let Ma = coin.amount_attribute.commitment(); + let Ma = coin.amount_attribute.commitment().clone(); let mut Ms = O.clone(); - if let Some(scr_attr) = &mut coin.script_attribute { - Ms = scr_attr.commitment(); + if let Some(scr_attr) = &coin.script_attribute { + Ms = scr_attr.commitment().clone(); } Statement { domain_separator: b"Iparams_Statement_", equations: vec![ Equation { // Cw = w*W + w_*W_ - lhs: Cw, + lhs: Cw.clone(), rhs: vec![vec![GENERATORS.W.clone(), GENERATORS.W_.clone()]] }, Equation { // I = Gz_mac - x0*X0 - x1*X1 - ya*Gz_attribute - ys*Gz_script - lhs: I - &GENERATORS.Gz_mac, + lhs: I.clone() - &GENERATORS.Gz_mac, rhs: vec![vec![ O.clone(), O.clone(), @@ -305,8 +305,8 @@ impl IParamsProof { } pub fn verify( - mint_publickey: (GroupElement, GroupElement), - coin: &mut Coin, + mint_publickey: (&GroupElement, &GroupElement), + coin: &Coin, proof: ZKP, transcript: &mut CashuTranscript, ) -> bool { @@ -436,7 +436,7 @@ impl ScriptEqualityProof { pub fn new( inputs: &Vec, randomized_inputs: &Vec, - outputs: &mut Vec<(AmountAttribute, ScriptAttribute)>, + outputs: &Vec<(AmountAttribute, ScriptAttribute)>, transcript: &mut CashuTranscript, ) -> Result { if inputs.is_empty() || @@ -446,8 +446,8 @@ impl ScriptEqualityProof { return Err(Error::EmptyList); } let commitments: Vec<(GroupElement, GroupElement)> = outputs - .iter_mut() - .map(|(aa, sa)| (aa.commitment(), sa.commitment())) + .iter() + .map(|(aa, sa)| (aa.commitment().clone(), sa.commitment().clone())) .collect(); let statement = ScriptEqualityProof::statement(randomized_inputs, &commitments); let s = inputs[0].script_attribute.as_ref().ok_or(Error::NoScriptProvided)?.s.clone(); @@ -494,9 +494,9 @@ impl ScriptEqualityProof { #[cfg(test)] mod tests{ - use crate::{errors::Error, generators::{hash_to_curve, GENERATORS}, models::{AmountAttribute, Coin, MintPrivateKey, RandomizedCoin, MAC}, secp::{GroupElement, Scalar, GROUP_ELEMENT_ZERO}, transcript::CashuTranscript}; + use crate::{errors::Error, generators::{hash_to_curve, GENERATORS}, models::{AmountAttribute, Coin, MintPrivateKey, RandomizedCoin, ScriptAttribute, MAC}, secp::{GroupElement, Scalar, GROUP_ELEMENT_ZERO}, transcript::CashuTranscript}; - use super::{BalanceProof, BootstrapProof, IParamsProof, MacProof}; + use super::{BalanceProof, BootstrapProof, IParamsProof, MacProof, ScriptEqualityProof}; fn transcripts() -> (CashuTranscript, CashuTranscript) { let mint_transcript = CashuTranscript::new(); @@ -536,7 +536,7 @@ mod tests{ fn test_iparams() { let (mut mint_transcript, mut client_transcript) = transcripts(); let mut mint_privkey = privkey(); - let mut amount_attr = AmountAttribute::new(12, None); + let amount_attr = AmountAttribute::new(12, None); let mac = MAC::generate(&mint_privkey, &amount_attr.commitment(), None, None).expect("Couldn't generate MAC"); let mut coin = Coin::new(amount_attr, None, mac); let proof = IParamsProof::new(&mut mint_privkey, &mut coin, &mut client_transcript); @@ -547,8 +547,8 @@ mod tests{ fn test_wrong_iparams() { let (mut mint_transcript, mut client_transcript) = transcripts(); let mut mint_privkey = privkey(); - let mut mint_privkey_1 = privkey(); - let mut amount_attr = AmountAttribute::new(12, None); + let mint_privkey_1 = privkey(); + let amount_attr = AmountAttribute::new(12, None); let mac = MAC::generate(&mint_privkey, &amount_attr.commitment(), None, None).expect("Couldn't generate MAC"); let mut coin = Coin::new(amount_attr, None, mac); let proof = IParamsProof::new(&mut mint_privkey, &mut coin, &mut client_transcript); @@ -558,19 +558,19 @@ mod tests{ #[test] fn test_mac() { let (mut mint_transcript, mut client_transcript) = transcripts(); - let mut mint_privkey = privkey(); - let mut amount_attr = AmountAttribute::new(12, None); + let mint_privkey = privkey(); + let amount_attr = AmountAttribute::new(12, None); let mac = MAC::generate(&mint_privkey, &amount_attr.commitment(), None, None).expect("Couldn't generate MAC"); - let mut coin = Coin::new(amount_attr, None, mac); - let randomized_coin = RandomizedCoin::from_coin(&mut coin, false).expect("Expected a randomized coin"); + let coin = Coin::new(amount_attr, None, mac); + let randomized_coin = RandomizedCoin::from_coin(&coin, false).expect("Expected a randomized coin"); let proof = MacProof::new(mint_privkey.pubkey(), &coin, &randomized_coin, &mut client_transcript); - assert!(MacProof::verify(&mut mint_privkey, &randomized_coin, None, proof, &mut mint_transcript)); + assert!(MacProof::verify(&mint_privkey, &randomized_coin, None, proof, &mut mint_transcript)); } #[test] fn test_wrong_mac() { #[allow(non_snake_case)] - fn generate_custom_rand(coin: &mut Coin) -> Result { + fn generate_custom_rand(coin: &Coin) -> Result { let t = coin.mac.t.clone(); let V = coin.mac.V.as_ref(); let t_bytes: [u8; 32] = (&coin.mac.t).into(); @@ -580,7 +580,7 @@ mod tests{ let z = Scalar::random(); let Ms: GroupElement = GroupElement::new(&GROUP_ELEMENT_ZERO); - let Ca = GENERATORS.Gz_attribute.clone() * z.as_ref() + &Ma; + let Ca = GENERATORS.Gz_attribute.clone() * z.as_ref() + Ma; let Cs = GENERATORS.Gz_script.clone() * z.as_ref() + &Ms; let Cx0 = GENERATORS.X0.clone() * z.as_ref() + &U; let Cx1 = GENERATORS.X1.clone() * z.as_ref() + &(U * &t); @@ -591,7 +591,7 @@ mod tests{ let (mut mint_transcript, mut client_transcript) = transcripts(); let mut mint_privkey = privkey(); - let mut amount_attr = AmountAttribute::new(12, None); + let amount_attr = AmountAttribute::new(12, None); let mac = MAC::generate(&mint_privkey, &amount_attr.commitment(), None, None).expect("Couldn't generate MAC"); let mut coin = Coin::new(amount_attr, None, mac); let randomized_coin = generate_custom_rand(&mut coin).expect("Expected a randomized coin"); @@ -603,12 +603,12 @@ mod tests{ fn test_balance() { let (mut mint_transcript, mut client_transcript) = transcripts(); let privkey = privkey(); - let mut inputs = vec![AmountAttribute::new(12, None), AmountAttribute::new(11, None)]; + let inputs = vec![AmountAttribute::new(12, None), AmountAttribute::new(11, None)]; let outputs = vec![AmountAttribute::new(23, None)]; // We assume the inputs were already attributed a MAC previously let macs: Vec = inputs - .iter_mut() - .map(|input| MAC::generate(&privkey, &input.commitment(), None, None).expect("MAC expected")) + .iter() + .map(|input| MAC::generate(&privkey, input.commitment(), None, None).expect("MAC expected")) .collect(); let proof = BalanceProof::new(&inputs, &outputs, &mut client_transcript); let mut coins: Vec = macs.into_iter().zip(inputs.into_iter()) @@ -617,7 +617,7 @@ mod tests{ let randomized_coins: Vec = coins.iter_mut() .map(|coin| RandomizedCoin::from_coin(coin, false).expect("RandomzedCoin expected")) .collect(); - let outputs: Vec = outputs.into_iter().map(|mut output| output.commitment()).collect(); + let outputs: Vec = outputs.into_iter().map(|output| output.commitment().clone()).collect(); assert!(BalanceProof::verify(&randomized_coins, &outputs, 0, proof, &mut mint_transcript)); } @@ -639,7 +639,85 @@ mod tests{ let randomized_coins: Vec = coins.iter_mut() .map(|coin| RandomizedCoin::from_coin(coin, false).expect("RandomzedCoin expected")) .collect(); - let outputs: Vec = outputs.into_iter().map(|mut output| output.commitment()).collect(); + let outputs: Vec = outputs.into_iter().map(|output| output.commitment().clone()).collect(); assert!(!BalanceProof::verify(&randomized_coins, &outputs, 1, proof, &mut mint_transcript)); } + + #[test] + fn test_script_equality() { + let (mut mint_transcript, mut client_transcript) = transcripts(); + let script = b"testscript"; + let privkey = privkey(); + let inputs = vec![ + (AmountAttribute::new(12, None), ScriptAttribute::new(script, None)), + (AmountAttribute::new(11, None), ScriptAttribute::new(script, None)) + ]; + let outputs = vec![ + (AmountAttribute::new(6, None), ScriptAttribute::new(script, None)), + (AmountAttribute::new(11, None), ScriptAttribute::new(script, None)), + (AmountAttribute::new(12, None), ScriptAttribute::new(script, None)), + ]; + let macs: Vec = inputs + .iter() + .map(|(amount_attr, script_attr)| + MAC::generate(&privkey, &amount_attr.commitment(), Some(&script_attr.commitment()), None).expect("")) + .collect(); + let coins: Vec = inputs.into_iter().zip(macs.into_iter()) + .map(|((aa, sa), mac)| Coin::new(aa, Some(sa), mac)) + .collect(); + let randomized_coins: Vec = coins + .iter() + .map(|coin| RandomizedCoin::from_coin(coin, false).expect("")) + .collect(); + let proof = ScriptEqualityProof::new( + &coins, + &randomized_coins, + &outputs, + client_transcript.as_mut(), + ).expect(""); + let outputs = outputs + .into_iter() + .map(|(aa, sa)| (aa.commitment().clone(), sa.commitment().clone())) + .collect(); + assert!(ScriptEqualityProof::verify(&randomized_coins, &outputs, proof, mint_transcript.as_mut())) + } + + #[test] + fn test_script_inequality() { + let (mut mint_transcript, mut client_transcript) = transcripts(); + let script = b"testscript"; + let privkey = privkey(); + let inputs = vec![ + (AmountAttribute::new(12, None), ScriptAttribute::new(script, None)), + (AmountAttribute::new(11, None), ScriptAttribute::new(script, None)) + ]; + let outputs = vec![ + (AmountAttribute::new(6, None), ScriptAttribute::new(b"testscript_", None)), + (AmountAttribute::new(11, None), ScriptAttribute::new(script, None)), + (AmountAttribute::new(12, None), ScriptAttribute::new(script, None)), + ]; + let macs: Vec = inputs + .iter() + .map(|(amount_attr, script_attr)| + MAC::generate(&privkey, &amount_attr.commitment(), Some(&script_attr.commitment()), None).expect("")) + .collect(); + let coins: Vec = inputs.into_iter().zip(macs.into_iter()) + .map(|((aa, sa), mac)| Coin::new(aa, Some(sa), mac)) + .collect(); + let randomized_coins: Vec = coins + .iter() + .map(|coin| RandomizedCoin::from_coin(coin, false).expect("")) + .collect(); + let proof = ScriptEqualityProof::new( + &coins, + &randomized_coins, + &outputs, + client_transcript.as_mut(), + ).expect(""); + let outputs = outputs + .into_iter() + .map(|(aa, sa)| (aa.commitment().clone(), sa.commitment().clone())) + .collect(); + assert!(!ScriptEqualityProof::verify(&randomized_coins, &outputs, proof, mint_transcript.as_mut())) + } } \ No newline at end of file diff --git a/src/models.rs b/src/models.rs index b8f0822..dfaf8f0 100644 --- a/src/models.rs +++ b/src/models.rs @@ -14,14 +14,29 @@ pub struct MintPrivateKey { pub ys: Scalar, // Public parameters - pub Cw: Option, - pub I: Option + pub Cw: GroupElement, + pub I: GroupElement } +#[allow(non_snake_case)] impl MintPrivateKey { pub fn from_scalars(scalars: &[Scalar; 6]) -> Self { let [w, w_, x0, x1, ya, ys] = scalars; + let Cw = GENERATORS.W.clone()*w + &(GENERATORS.W_.clone()*w_); + let I = + GENERATORS.Gz_mac.clone() - &( + GENERATORS.X0.clone()*x0 + + &( + GENERATORS.X1.clone()*x1 + + &( + GENERATORS.Gz_attribute.clone()*ya + + &( + GENERATORS.Gz_script.clone()*ys + ) + ) + ) + ); MintPrivateKey { w: w.clone(), w_: w_.clone(), @@ -29,8 +44,8 @@ impl MintPrivateKey { x1: x1.clone(), ya: ya.clone(), ys: ys.clone(), - Cw: None, - I: None, + Cw, + I, } } @@ -38,30 +53,8 @@ impl MintPrivateKey { vec![self.w.clone(), self.w_.clone(), self.x0.clone(), self.x1.clone(), self.ya.clone(), self.ys.clone()] } - pub fn pubkey(&mut self) -> (GroupElement, GroupElement) { - if !self.Cw.is_some() { - self.Cw = Some(GENERATORS.W.clone()*&self.w + &(GENERATORS.W_.clone()*&self.w_)); - } - if !self.I.is_some() { - self.I = Some( - GENERATORS.Gz_mac.clone() - &( - GENERATORS.X0.clone()*&self.x0 - + &( - GENERATORS.X1.clone()*&self.x1 - + &( - GENERATORS.Gz_attribute.clone()*&self.ya - + &( - GENERATORS.Gz_script.clone()*&self.ys - ) - ) - ) - ) - ); - } - ( - self.Cw.as_ref().expect("Expected Cw").clone(), - self.I.as_ref().expect("Expected I").clone(), - ) + pub fn pubkey(&self) -> (&GroupElement, &GroupElement) { + (self.Cw.as_ref(), self.I.as_ref()) } } @@ -75,32 +68,30 @@ pub struct ZKP { pub struct ScriptAttribute { pub r: Scalar, pub s: Scalar, - Ms: Option, + Ms: GroupElement, } +#[allow(non_snake_case)] impl ScriptAttribute { pub fn new(script: &[u8], blinding_factor: Option<&[u8; 32]>) -> Self { let s = Scalar::new(&Sha256Hash::hash(&script).to_byte_array()); if let Some(b_factor) = blinding_factor { let r = Scalar::new(b_factor); - - ScriptAttribute { r: r, s: s, Ms: None } + let Ms = GENERATORS.G_script.clone() * &s + &( + GENERATORS.G_blind.clone() * &r + ); + ScriptAttribute { r, s, Ms } } else { let r = Scalar::random(); - - ScriptAttribute { r: r, s: s, Ms: None } + let Ms = GENERATORS.G_script.clone() * &s + &( + GENERATORS.G_blind.clone() * &r + ); + ScriptAttribute { r, s, Ms } } } - pub fn commitment(&mut self) -> GroupElement { - if !self.Ms.is_some() { - self.Ms = Some( - GENERATORS.G_script.clone() * &self.s + &( - GENERATORS.G_blind.clone() * &self.r - ) - ) - } - self.Ms.as_ref().expect("Couldn't get ScriptAttribute Commitment").clone() + pub fn commitment(&self) -> &GroupElement { + self.Ms.as_ref() } } @@ -108,32 +99,30 @@ impl ScriptAttribute { pub struct AmountAttribute { pub a: Scalar, pub r: Scalar, - Ma: Option, + Ma: GroupElement, } +#[allow(non_snake_case)] impl AmountAttribute { pub fn new(amount: u64, blinding_factor: Option<&[u8; 32]>) -> Self { let a = Scalar::from(amount); if let Some(b_factor) = blinding_factor { let r = Scalar::new(b_factor); - - AmountAttribute { r: r, a: a, Ma: None } + let Ma = GENERATORS.G_amount.clone() * &a + &( + GENERATORS.G_blind.clone() * &r + ); + AmountAttribute { r, a, Ma } } else { let r = Scalar::random(); - - AmountAttribute { r: r, a: a, Ma: None } + let Ma = GENERATORS.G_amount.clone() * &a + &( + GENERATORS.G_blind.clone() * &r + ); + AmountAttribute { r, a, Ma } } } - pub fn commitment(&mut self) -> GroupElement{ - if !self.Ma.is_some() { - self.Ma = Some( - GENERATORS.G_amount.clone() * &self.a + &( - GENERATORS.G_blind.clone() * &self.r - ) - ) - } - self.Ma.as_ref().expect("Couldn't get ScriptAttribute Commitment").clone() + pub fn commitment(&self) -> &GroupElement { + self.Ma.as_ref() } } @@ -218,7 +207,7 @@ pub struct RandomizedCoin { impl RandomizedCoin { #[allow(non_snake_case)] pub fn from_coin( - coin: &mut Coin, + coin: &Coin, reveal_script: bool, ) -> Result { let t = coin.mac.t.clone(); @@ -228,11 +217,11 @@ impl RandomizedCoin { let Ma = coin.amount_attribute.commitment(); let r = &coin.amount_attribute.r; let Ms: GroupElement; - if let Some(attr) = &mut coin.script_attribute { + if let Some(attr) = &coin.script_attribute { if reveal_script { - Ms = GENERATORS.G_blind.clone() * &attr.r; + Ms = GENERATORS.G_blind.clone() * attr.r.as_ref(); } else { - Ms = attr.commitment(); + Ms = attr.commitment().clone(); } } else { Ms = GroupElement::new(&GROUP_ELEMENT_ZERO);