diff --git a/.github/linter/base_style.rb b/.github/linter/base_style.rb new file mode 100644 index 0000000..8d98105 --- /dev/null +++ b/.github/linter/base_style.rb @@ -0,0 +1,4 @@ +all +# lame rules +exclude_rule 'MD002' +exclude_rule 'MD041' diff --git a/.github/linter/readme_style.rb b/.github/linter/readme_style.rb new file mode 100644 index 0000000..02e6b48 --- /dev/null +++ b/.github/linter/readme_style.rb @@ -0,0 +1,12 @@ +all +# allow inline HTML for README fmt +exclude_rule 'MD033' +# badges trigger rule +exclude_rule 'MD034' +# README img serves as 'First Header' +exclude_rule 'MD002' +exclude_rule 'MD041' +# TODO: disable/enable not working for all-contribs +exclude_rule 'MD013' +# Allow no endline at the end +exclude_rule 'MD047' diff --git a/.tool-versions b/.tool-versions index f239fe2..045dc3e 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -scarb 2.6.3 +scarb 2.7.0 diff --git a/README.md b/README.md index 511c794..10129cb 100644 --- a/README.md +++ b/README.md @@ -37,3 +37,38 @@ This will compile all the components. ```bash scarb test ``` + +## Scheme description + +Taken from [RubenSomsen blind ecash Gist](https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406). + +The goal of this protocol is for Bob to get Alice to perform a Diffie-Hellman key exchange blindly, such that when the unblinded value is returned, Alice recognizes it as her own, but can’t distinguish it from others (i.e. similar to a blind signature). + +```text +Alice: +A = a*G +return A + +Bob: +Y = hash_to_curve(secret_message) +r = random blinding factor +B'= Y + r*G +return B' + +Alice: +C' = a*B' + (= a*Y + a*r*G) +return C' + +Bob: +C = C' - r*A + (= C' - a*r*G) + (= a*Y) +return C, secret_message + +Alice: +Y = hash_to_curve(secret_message) +C == a*Y + +If true, C must have originated from Alice +``` diff --git a/Scarb.toml b/Scarb.toml index 66b4d88..1c8e544 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -4,3 +4,7 @@ version = "0.1.0" edition = "2023_11" [dependencies] +starknet = "2.7.0" + +[dev-dependencies] +cairo_test = "2.7.0" diff --git a/src/core.cairo b/src/core.cairo new file mode 100644 index 0000000..b87818b --- /dev/null +++ b/src/core.cairo @@ -0,0 +1,170 @@ +// Core lib imports +use core::traits::Into; +use core::option::OptionTrait; +use core::starknet::SyscallResultTrait; +use core::sha256::compute_sha256_byte_array; + +// Starknet imports +use starknet::{secp256k1::{Secp256k1Point}, secp256_trait::{Secp256Trait, Secp256PointTrait}}; + + +const TWO_POW_32: u128 = 0x100000000; +const TWO_POW_64: u128 = 0x10000000000000000; +const TWO_POW_96: u128 = 0x1000000000000000000000000; + +/// Represents the Mint (Bob) in the BDHKE protocol +#[derive(Destruct)] +pub struct Mint { + pub k: u256, // Private key of the mint + pub K: Secp256k1Point, // Public key of the mint +} + +/// Represents a User (Alice or Carol) in the BDHKE protocol +#[derive(Destruct)] +pub struct User { + /// Secret message + pub x: u256, + /// Point on the curve corresponding to x + pub Y: Secp256k1Point, + /// Blinding factor (private key for blinding) + pub r: u256, +} + +/// Implements the Mint functionality +#[generate_trait()] +pub impl MintTraitImpl of MintTrait { + /// Creates a new Mint with a random private key + fn new(k: u256) -> Mint { + let K = Secp256Trait::::get_generator_point().mul(k).unwrap_syscall(); + Mint { k, K } + } + + /// Signs a blinded message + /// + /// # Arguments + /// * `B_` - The blinded message point + /// + /// # Returns + /// The blinded signature point C_ + fn sign(ref self: Mint, B_: Secp256k1Point) -> Secp256k1Point { + B_.mul(self.k).unwrap_syscall() + } + + /// Verifies a token + /// + /// # Arguments + /// * `x` - The secret message + /// * `C` - The unblinded signature point + /// + /// # Returns + /// True if the token is valid, false otherwise + fn verify(self: Mint, x: u256, C: Secp256k1Point) -> bool { + let Y = hash_to_curve(x); + let expected_C_coordinates = Y + .mul(self.k) + .unwrap_syscall() + .get_coordinates() + .unwrap_syscall(); + let c_coordinates = C.get_coordinates().unwrap_syscall(); + expected_C_coordinates == c_coordinates + } +} + +/// Implements the User functionality +#[generate_trait()] +pub impl UserTraitImpl of UserTrait { + /// Creates a new User with a random secret message and blinding factor + fn new(x: u256, r: u256) -> User { + let Y = hash_to_curve(x); + User { x, Y, r } + } + + /// Blinds the message + /// + /// # Returns + /// The blinded message point B_ + fn blind(ref self: User) -> Secp256k1Point { + let G = Secp256Trait::::get_generator_point(); + self.Y.add(G.mul(self.r).unwrap_syscall()).unwrap_syscall() + } + + /// Unblinds the signature + /// + /// # Arguments + /// * `C_` - The blinded signature point + /// * `K` - The mint's public key + /// + /// # Returns + /// The unblinded signature point C + fn unblind(ref self: User, C_: Secp256k1Point, K: Secp256k1Point) -> Secp256k1Point { + C_.add(K.mul(self.r).unwrap_syscall()).unwrap_syscall() + } + + /// Creates a token + /// + /// # Returns + /// A tuple containing the secret message and the unblinded signature point + fn create_token(ref self: User, C: Secp256k1Point) -> (u256, Secp256k1Point) { + (self.x, C) + } +} + + +/// Hashes a message to a point on the secp256k1 curve +/// +/// # Arguments +/// * `message` - The message to hash +/// +/// # Returns +/// A point on the secp256k1 curve +pub fn hash_to_curve(message: u256) -> Secp256k1Point { + // This is a simplified implementation. In practice, you should use a more + // robust method to ensure the resulting point is uniformly distributed. + let mut attempt = 0; + loop { + let hash_input = message * TWO_POW_32.into() + attempt.into(); + let hash_result = compute_hash(hash_input); + match Secp256Trait::< + Secp256k1Point + >::secp256_ec_get_point_from_x_syscall(hash_result, false) { + Result::Ok(point_option) => { + match point_option { + Option::Some(point) => { break point; }, + Option::None => { attempt += 1; } + } + }, + Result::Err(_) => { attempt += 1; } + } + } +} + +/// Computes a hash of the input +/// +/// # Arguments +/// * `input` - The input to hash +/// +/// # Returns +/// The hash result as a u256 +pub fn compute_hash(input: u256) -> u256 { + let hash_result = compute_sha256_byte_array(@input.into()); + let mut value: u256 = 0; + let hash_result_span = hash_result.span(); + let len = hash_result_span.len(); + let mut i = 0; + while i < len { + let word = hash_result_span[i]; + value *= 0x100000000; + value = value + (*word).into(); + i += 1; + }; + value +} + +pub impl U256IntoByteArray of Into { + fn into(self: u256) -> ByteArray { + let mut ba = Default::default(); + ba.append_word(self.high.into(), 16); + ba.append_word(self.low.into(), 16); + ba + } +} diff --git a/src/lib.cairo b/src/lib.cairo index d18669a..ec8e997 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1 +1,2 @@ mod main; +pub mod core; diff --git a/src/main.cairo b/src/main.cairo index 061e9d2..ffea198 100644 --- a/src/main.cairo +++ b/src/main.cairo @@ -1,3 +1,43 @@ +use bdhke::core::MintTrait; +use bdhke::core::MintTraitImpl; +use bdhke::core::UserTrait; +use bdhke::core::UserTraitImpl; +use starknet::{secp256k1::{Secp256k1Point}, secp256_trait::{Secp256Trait, Secp256PointTrait}}; +use core::starknet::SyscallResultTrait; + fn main() { println!("Running Blind Diffie-Hellmann Key Exchange (BDHKE) scheme"); + + // Create a mint (Bob) + let mint_private_key: u256 = 0xe907831f80848d1069a5371b402410364bdf1c5f8307b0084c55f1ce2dca8215; + let mut mint = MintTraitImpl::new(mint_private_key); + + // Create a user (Alice) + let alice_random_secret: u256 = + 0xe907831f80848d1069a5371b402410364bdf1c5f8307b0084c55f1ce2dca8215; + let alice_blinding_factor: u256 = + 0xe907831f80848d1069a5371b402410364bdf1c5f8307b0084c55f1ce2dca8215; + + let mut alice = UserTraitImpl::new(alice_random_secret, alice_blinding_factor); + + // Alice blinds her message + let B_ = alice.blind(); + + // Mint signs the blinded message + let C_ = mint.sign(B_); + + // Alice unblinds the signature + let C = alice.unblind(C_, mint.K); + + // Alice creates a token + let (x, C) = alice.create_token(C); + + // Mint verifies the token + let is_valid = mint.verify(x, C); + + if is_valid { + println!("Token is valid"); + } else { + println!("Token is invalid"); + } }