Skip to content
This repository has been archived by the owner on Nov 21, 2024. It is now read-only.

Commit

Permalink
This is a fix to hash_to_curve and hash_e functions (#27)
Browse files Browse the repository at this point in the history
* Fix to hash_to_curve and hash_e with a bit of code improvement with port of crypto test from nutshell project.

* Fix formating

* Served the credo master :)
  • Loading branch information
alkhulaifi authored Oct 21, 2024
1 parent c02037e commit a3dc907
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 57 deletions.
85 changes: 46 additions & 39 deletions lib/cashubrew/NUTs/NUT-00/bdhke.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ defmodule Cashubrew.Nuts.Nut00.BDHKE do
alias ExSecp256k1

# Cashu parameters
defp domain_separator do
"Secp256k1_HashToCurve_Cashu_"
end
@domain_separator "Secp256k1_HashToCurve_Cashu_"

# secp256k1 parameters
defp secp256k1_n do
Expand All @@ -28,17 +26,31 @@ defmodule Cashubrew.Nuts.Nut00.BDHKE do
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}
with {:ok, private_key} <- generate_private_key(private_key),
{:ok, public_key} <- generate_public_key(private_key) do
{:ok, {private_key, public_key}}
end
end

def generate_private_key(private_key \\ nil) do
case private_key do
nil -> {:ok, :crypto.strong_rand_bytes(32)}
<<0, key::binary-size(32)>> -> {:ok, key}
<<key::binary-size(32)>> -> {:ok, key}
_ -> {:error, "Invalid private key format"}
end
end

def generate_public_key(private_key) do
with {:ok, public_key} <- ExSecp256k1.create_public_key(private_key) do
ExSecp256k1.public_key_compress(public_key)
end
end

def load_public_key(hash) do
with {:ok, public_key} <- ExSecp256k1.public_key_decompress(hash) do
ExSecp256k1.public_key_compress(public_key)
end
end

@doc """
Expand All @@ -51,25 +63,23 @@ defmodule Cashubrew.Nuts.Nut00.BDHKE do
/// never happen in practice).
"""
def hash_to_curve(message) do
msg_hash = :crypto.hash(:sha256, domain_separator() <> message)
hash_to_curve_loop(msg_hash, 0)
:crypto.hash(:sha256, @domain_separator <> message)
|> find_valid_point()
end

defp hash_to_curve_loop(msg_hash, counter) when counter < 65_536 do
defp find_valid_point(msg_hash, counter \\ 0)

defp find_valid_point(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)
case load_public_key(<<2>> <> hash) do
{:ok, public_key} -> {:ok, public_key}
_ -> find_valid_point(msg_hash, counter + 1)
end
end

defp hash_to_curve_loop(_, _), do: raise("No valid point found")
defp find_valid_point(_, _), do: {:error, "No valid point found"}

@doc """
Alice's step 1: Blind the message.
Expand All @@ -79,13 +89,13 @@ defmodule Cashubrew.Nuts.Nut00.BDHKE do
This operation is called blinding.
"""
def step1_alice(secret_msg, blinding_factor \\ nil) do
y = hash_to_curve(secret_msg)
r = blinding_factor || generate_keypair() |> elem(0)
{:ok, y} = hash_to_curve(secret_msg)
r = blinding_factor || generate_private_key() |> elem(1)
{:ok, r_pub} = ExSecp256k1.create_public_key(r)
{:ok, r_pub_compressed} = ExSecp256k1.public_key_compress(r_pub)
# B_ = Y + rG
{:ok, b_prime} = Secp256k1Utils.point_add(y, r_pub_compressed)
{b_prime, r}
{:ok, {b_prime, r}}
end

@doc """
Expand All @@ -95,8 +105,8 @@ defmodule Cashubrew.Nuts.Nut00.BDHKE do
"""
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}
{:ok, {e, s}} <- step2_bob_dleq(b_prime, a) do
{:ok, {c_prime, e, s}}
else
error ->
error
Expand All @@ -109,11 +119,8 @@ defmodule Cashubrew.Nuts.Nut00.BDHKE do
This operation is called unblinding.
"""
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
with {:ok, r_a_pub} <- Secp256k1Utils.point_mul(a_pub, r) do
Secp256k1Utils.point_sub(c_prime, r_a_pub)
end
end

Expand All @@ -123,7 +130,7 @@ defmodule Cashubrew.Nuts.Nut00.BDHKE do
This operation is called verification.
"""
def verify(a, c, secret_msg) do
y = hash_to_curve(secret_msg)
{:ok, y} = hash_to_curve(secret_msg)

with {:ok, a_y} <- Secp256k1Utils.point_mul(y, a),
true <- Secp256k1Utils.point_equal?(c, a_y) do
Expand All @@ -150,7 +157,7 @@ defmodule Cashubrew.Nuts.Nut00.BDHKE do
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}
{:ok, {e, s_bin}}
end

@doc """
Expand All @@ -176,7 +183,7 @@ defmodule Cashubrew.Nuts.Nut00.BDHKE do
Carol verifies DLEQ proof
"""
def carol_verify_dleq(secret_msg, r, c, e, s, a_pub) do
y = hash_to_curve(secret_msg)
{:ok, 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),
Expand All @@ -199,7 +206,7 @@ defmodule Cashubrew.Nuts.Nut00.BDHKE do
data =
Enum.map_join(keys, "", fn key ->
{:ok, uncompressed} = ExSecp256k1.public_key_decompress(key)
uncompressed
uncompressed |> Base.encode16(case: :lower)
end)

:crypto.hash(:sha256, data)
Expand Down
2 changes: 1 addition & 1 deletion lib/cashubrew/NUTs/NUT-02/keysets.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ defmodule Cashubrew.Nuts.Nut02.Keyset do
private_key = BlockKeys.Encoding.decode_extended_key(child_key)
private_key_bytes = :binary.part(private_key.key, 1, 32)

{_, public_key} = BDHKE.generate_keypair(private_key_bytes)
{:ok, {_, public_key}} = BDHKE.generate_keypair(private_key_bytes)

%{
amount: amount,
Expand Down
6 changes: 3 additions & 3 deletions lib/cashubrew/core/mint.ex
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ defmodule Cashubrew.Mint do

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

Expand Down Expand Up @@ -188,7 +188,7 @@ defmodule Cashubrew.Mint do
privkey = amount_key.private_key
# Bob (mint) signs the blinded message
b_prime = Base.decode16!(bm."B_", case: :lower)
{c_prime, _e, _s} = BDHKE.step2_bob(b_prime, privkey)
{:ok, {c_prime, _e, _s}} = BDHKE.step2_bob(b_prime, privkey)

%BlindSignature{
amount: bm.amount,
Expand Down Expand Up @@ -253,7 +253,7 @@ defmodule Cashubrew.Mint do
def generate_promises(repo, keyset_id, verified_outputs) do
Enum.reduce(verified_outputs, [], fn output, acc ->
key = get_key_for_amount(repo, keyset_id, output.amount)
{c_prime, e, s} = BDHKE.step2_bob(output."B_", key.private_key)
{:ok, {c_prime, e, s}} = BDHKE.step2_bob(output."B_", key.private_key)

[
%Schema.Promises{
Expand Down
10 changes: 5 additions & 5 deletions lib/cashubrew/mix/tasks/bench.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ defmodule Mix.Tasks.Bench do
Mix.Task.run("app.start")

# Setup before benchmark
{a, a_pub} = BDHKE.generate_keypair(<<1::256>>)
{:ok, {a, a_pub}} = BDHKE.generate_keypair(<<1::256>>)
secret_msg = "test_message"
{r, _r_pub} = BDHKE.generate_keypair(<<1::256>>)
{:ok, {r, _r_pub}} = BDHKE.generate_keypair(<<1::256>>)

if Code.ensure_loaded?(Benchee) do
# Run the BDHKE benchmark
Expand All @@ -38,13 +38,13 @@ defmodule Mix.Tasks.Bench do

def run_bdhke_flow(a, a_pub, secret_msg, r) do
# STEP 1: Alice blinds the message
{b_prime, _} = BDHKE.step1_alice(secret_msg, r)
{:ok, {b_prime, _}} = BDHKE.step1_alice(secret_msg, r)

# STEP 2: Bob signs the blinded message
{c_prime, _, _} = BDHKE.step2_bob(b_prime, a)
{:ok, {c_prime, _, _}} = BDHKE.step2_bob(b_prime, a)

# STEP 3: Alice unblinds the signature
c = BDHKE.step3_alice(c_prime, r, a_pub)
{:ok, c} = BDHKE.step3_alice(c_prime, r, a_pub)

# CAROL VERIFY: Carol verifies the unblinded signature
carol_verification = BDHKE.verify(a, c, secret_msg)
Expand Down
6 changes: 3 additions & 3 deletions lib/cashubrew/wallet/cli.ex
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@ defmodule Cashubrew.Wallet.CLI do
with {:ok, keyset_id} <- get_active_keyset_id() do
amounts = split_amount(amount)
secrets = Enum.map(amounts, fn _ -> :crypto.strong_rand_bytes(32) end)
rs = Enum.map(amounts, fn _ -> BDHKE.generate_keypair() end)
rs = Enum.map(amounts, fn _ -> BDHKE.generate_keypair() |> elem(1) end)

blinded_messages =
Enum.zip([amounts, secrets, rs])
|> Enum.map(fn {amt, secret, {r_priv, _r_pub}} ->
{b_prime, _r} = BDHKE.step1_alice(secret, r_priv)
{:ok, {b_prime, _r}} = BDHKE.step1_alice(secret, r_priv)

%BlindedMessage{
amount: amt,
Expand Down Expand Up @@ -150,7 +150,7 @@ defmodule Cashubrew.Wallet.CLI do
Enum.zip([signatures, secrets, rs])
|> Enum.map(fn {signature, secret, {r_priv, _r_pub}} ->
c_prime = Base.decode16!(signature["C_"], case: :lower)
c = BDHKE.step3_alice(c_prime, r_priv, a_pub)
{:ok, c} = BDHKE.step3_alice(c_prime, r_priv, a_pub)

%Proof{
amount: signature["amount"],
Expand Down
2 changes: 1 addition & 1 deletion lib/cashubrew/wallet/wallet.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ defmodule Cashubrew.Wallet do
end

def generate_wallet do
{private_key, public_key} = BDHKE.generate_keypair()
{:ok, {private_key, public_key}} = BDHKE.generate_keypair()

wallet = %WalletStruct{
private_key: Base.encode16(private_key, case: :lower),
Expand Down
10 changes: 5 additions & 5 deletions test/bdhke_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule BDHKETest do
# INIT: Setting up Alice's keys
Logger.info("\n***********************************************************")
Logger.info("INIT: Setting up Alice's keys")
{a, a_pub} = BDHKE.generate_keypair(<<1::256>>)
{:ok, {a, a_pub}} = BDHKE.generate_keypair(<<1::256>>)
Logger.info("Alice's private key (a): #{Base.encode16(a, case: :lower)}")
Logger.info("Alice's public key (A): #{Base.encode16(a_pub, case: :lower)}")

Expand All @@ -26,7 +26,7 @@ defmodule BDHKETest do
Logger.info("\n***********************************************************")
Logger.info("PREPARE: Preparing secret message and blinding factor")
secret_msg = "test_message"
{r, r_pub} = BDHKE.generate_keypair(<<1::256>>)
{:ok, {r, r_pub}} = BDHKE.generate_keypair(<<1::256>>)
Logger.info("Secret message: #{secret_msg}")
Logger.info("r private key: #{Base.encode16(r, case: :lower)}")
Logger.info("Blinding factor (r): #{Base.encode16(r_pub, case: :lower)}")
Expand All @@ -35,7 +35,7 @@ defmodule BDHKETest do
# STEP 1: Alice blinds the message
Logger.info("\n***********************************************************")
Logger.info("STEP 1: Alice blinds the message")
{b_prime, _} = BDHKE.step1_alice(secret_msg, r)
{:ok, {b_prime, _}} = BDHKE.step1_alice(secret_msg, r)
Logger.info("Blinded message (B_): #{Base.encode16(b_prime, case: :lower)}")
{x, y} = Secp256k1Utils.pubkey_to_xy(b_prime)
Logger.info("S1_Blinded_message_x: #{:binary.decode_unsigned(x)}")
Expand All @@ -45,7 +45,7 @@ defmodule BDHKETest do
# STEP 2: Bob signs the blinded message
Logger.info("\n***********************************************************")
Logger.info("STEP 2: Bob signs the blinded message")
{c_prime, e, s} = BDHKE.step2_bob(b_prime, a)
{:ok, {c_prime, e, s}} = BDHKE.step2_bob(b_prime, a)
Logger.info("Blinded signature (C_): #{inspect(c_prime)}")
Logger.info("DLEQ proof - e: #{inspect(e)}")
Logger.info("DLEQ proof - s: #{inspect(s)}")
Expand All @@ -62,7 +62,7 @@ defmodule BDHKETest do
# STEP 3: Alice unblinds the signature
Logger.info("\n***********************************************************")
Logger.info("STEP 3: Alice unblinds the signature")
c = BDHKE.step3_alice(c_prime, r, a_pub)
{:ok, c} = BDHKE.step3_alice(c_prime, r, a_pub)
Logger.info("Unblinded signature (C): #{Base.encode16(c, case: :lower)}")
Logger.info("***********************************************************\n")

Expand Down
Loading

0 comments on commit a3dc907

Please sign in to comment.