Skip to content

Commit

Permalink
♻️ refactor crypto module
Browse files Browse the repository at this point in the history
  • Loading branch information
AbdelStark committed Sep 23, 2024
1 parent a939fb5 commit ae96fe9
Show file tree
Hide file tree
Showing 6 changed files with 412 additions and 401 deletions.
4 changes: 2 additions & 2 deletions lib/gakimint/core/mint.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Gakimint.Mint do
"""

use GenServer
alias Gakimint.{Crypto, Repo}
alias Gakimint.{Crypto.BDHKE, Repo}
alias Gakimint.Schema.{Key, Keyset, MintConfiguration}
import Ecto.Query

Expand Down Expand Up @@ -90,7 +90,7 @@ defmodule Gakimint.Mint do

defp derive_mint_key(seed) do
private_key = :crypto.hash(:sha256, seed)
{private_key, public_key} = Crypto.generate_keypair(private_key)
{private_key, public_key} = BDHKE.generate_keypair(private_key)
{private_key, public_key}
end

Expand Down
180 changes: 180 additions & 0 deletions lib/gakimint/crypto/bdhke.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
defmodule Gakimint.Crypto.BDHKE do
@moduledoc """
Cryptographic functions for the Gakimint mint, including BDHKE implementation.
"""
require Logger

alias ExSecp256k1
alias Gakimint.Crypto.Secp256k1Utils

# Cashu parameters
@domain_separator "Secp256k1_HashToCurve_Cashu_"

# secp256k1 parameters
@secp256k1_n 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

@doc """
Generate a new keypair.
Takes private key as input or generates a new one if not provided.
"""
def generate_keypair(private_key \\ nil) do
private_key =
case private_key do
nil -> :crypto.strong_rand_bytes(32)
<<0, key::binary-size(32)>> -> key
<<key::binary-size(32)>> -> key
_ -> raise "Invalid private key format"
end

{:ok, public_key} = ExSecp256k1.create_public_key(private_key)
{:ok, compressed_public_key} = ExSecp256k1.public_key_compress(public_key)
{private_key, compressed_public_key}
end

@doc """
Hash to curve function.
"""
def hash_to_curve(message) do
msg_hash = :crypto.hash(:sha256, @domain_separator <> message)
hash_to_curve_loop(msg_hash, 0)
end

defp hash_to_curve_loop(msg_hash, counter) when counter < 65_536 do
to_hash = msg_hash <> <<counter::little-32>>
hash = :crypto.hash(:sha256, to_hash)

case ExSecp256k1.create_public_key(hash) do
{:ok, public_key} ->
{:ok, compressed_key} = ExSecp256k1.public_key_compress(public_key)
compressed_key

_ ->
hash_to_curve_loop(msg_hash, counter + 1)
end
end

defp hash_to_curve_loop(_, _), do: raise("No valid point found")

@doc """
Alice's step 1: Blind the message
"""
def step1_alice(secret_msg, blinding_factor \\ nil) do
y = hash_to_curve(secret_msg)
r = blinding_factor || generate_keypair() |> elem(0)
{:ok, r_pub} = ExSecp256k1.create_public_key(r)
{:ok, r_pub_compressed} = ExSecp256k1.public_key_compress(r_pub)
{:ok, b_prime} = Secp256k1Utils.point_add(y, r_pub_compressed)
{b_prime, r}
end

@doc """
Bob's step 2: Sign the blinded message
"""
def step2_bob(b_prime, a) do
with {:ok, c_prime} <- Secp256k1Utils.point_mul(b_prime, a),
{e, s} <- step2_bob_dleq(b_prime, a) do
{c_prime, e, s}
else
error ->
error
end
end

@doc """
Alice's step 3: Unblind the signature
"""
def step3_alice(c_prime, r, a_pub) do
with {:ok, r_a_pub} <- Secp256k1Utils.point_mul(a_pub, r),
{:ok, c} <- Secp256k1Utils.point_sub(c_prime, r_a_pub) do
c
else
error -> error
end
end

@doc """
Verify the signature
"""
def verify(a, c, secret_msg) do
y = hash_to_curve(secret_msg)

with {:ok, a_y} <- Secp256k1Utils.point_mul(y, a),
true <- Secp256k1Utils.point_equal?(c, a_y) do
true
else
_ -> false
end
end

@doc """
Generate DLEQ proof
"""
def step2_bob_dleq(b_prime, a, p \\ nil) do
p = p || :crypto.strong_rand_bytes(32)
{:ok, r1} = ExSecp256k1.create_public_key(p)
{:ok, r1_compressed} = ExSecp256k1.public_key_compress(r1)
{:ok, r2} = Secp256k1Utils.point_mul(b_prime, p)
{:ok, a_pub} = ExSecp256k1.create_public_key(a)
{:ok, a_pub_compressed} = ExSecp256k1.public_key_compress(a_pub)
{:ok, c_prime} = Secp256k1Utils.point_mul(b_prime, a)

e = hash_e(r1_compressed, r2, a_pub_compressed, c_prime)
e_scalar = :binary.decode_unsigned(e)
a_times_e = Secp256k1Utils.mod_mul(:binary.decode_unsigned(a), e_scalar, @secp256k1_n)
s = Secp256k1Utils.mod_add(:binary.decode_unsigned(p), a_times_e, @secp256k1_n)
s_bin = :binary.encode_unsigned(s) |> Secp256k1Utils.pad_left(32)
{e, s_bin}
end

@doc """
Alice verifies DLEQ proof
"""
def alice_verify_dleq(b_prime, c_prime, e, s, a_pub) do
with {:ok, s_g} <- ExSecp256k1.create_public_key(s),
{:ok, s_g_compressed} <- ExSecp256k1.public_key_compress(s_g),
{:ok, e_a} <- Secp256k1Utils.point_mul(a_pub, e),
{:ok, r1} <- Secp256k1Utils.point_sub(s_g_compressed, e_a),
{:ok, s_b_prime} <- Secp256k1Utils.point_mul(b_prime, s),
{:ok, e_c_prime} <- Secp256k1Utils.point_mul(c_prime, e),
{:ok, r2} <- Secp256k1Utils.point_sub(s_b_prime, e_c_prime),
computed_e <- hash_e(r1, r2, a_pub, c_prime),
true <- computed_e == e do
true
else
_ -> false
end
end

@doc """
Carol verifies DLEQ proof
"""
def carol_verify_dleq(secret_msg, r, c, e, s, a_pub) do
y = hash_to_curve(secret_msg)

with {:ok, r_pub} <- ExSecp256k1.create_public_key(r),
{:ok, r_pub_compressed} <- ExSecp256k1.public_key_compress(r_pub),
{:ok, r_a_pub} <- Secp256k1Utils.point_mul(a_pub, r),
{:ok, c_prime} <- Secp256k1Utils.point_add(c, r_a_pub),
{:ok, b_prime} <- Secp256k1Utils.point_add(y, r_pub_compressed),
true <- alice_verify_dleq(b_prime, c_prime, e, s, a_pub) do
true
else
_ -> false
end
end

@doc """
Hash function for DLEQ
"""
def hash_e(r1, r2, a, c_prime) do
keys = [r1, r2, a, c_prime]

data =
Enum.map_join(keys, "", fn key ->
{:ok, uncompressed} = ExSecp256k1.public_key_decompress(key)
uncompressed
end)

:crypto.hash(:sha256, data)
end
end
Loading

0 comments on commit ae96fe9

Please sign in to comment.