Generate public/private keys and sign messages

Hello,

I am trying write elixir code to recreate the behavior from this python script.

import bech32
import ecdsa
from ecdsa.curves import SECP256k1
from ecdsa.util import sigencode_der

private_key = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1)
private_key_bytes = private_key.to_string()
print("Private Key: ", private_key_bytes.hex())

verifying_key = private_key.get_verifying_key()
public_key_compressed_bytes = verifying_key.to_string("compressed")
print("Public Key: ", public_key_compressed_bytes.hex())

readdr_bytes = b"\x04" + public_key_compressed_bytes
readdr_bytes5 = bech32.convertbits(readdr_bytes, 8, 5)
wallet_address = bech32.bech32_encode("rdx", readdr_bytes5)
print("Wallet Address: ", wallet_address)

blob_to_sign = "42244f5ac531a551a8ffd6f6c9e63e481fe1cf30b7bc42676840c3149695445b"
signature_der = private_key.sign_digest(
    bytearray.fromhex(blob_to_sign), sigencode=sigencode_der).hex()
print("SignatureDER: ", signature_der)

I have this code written so far but am not able to get the same signatureDER.

defmodule Keypair do

  def sign(data, private_key) do
    {:ok, private_key} = Curvy.Util.decode(private_key, :hex)
    Curvy.sign(data, private_key, encoding: :hex)
  end

  def from_private_key(private_key) do
    {:ok, private_key} = Curvy.Util.decode(private_key, :hex)

    private_key
    |> Curvy.Key.from_privkey()
    |> format_keys()
  end

  defp format_keys(keypair) do
    public_key = Curvy.Key.to_pubkey(keypair)
    radix_address = Bech32.encode("rdx", <<4>> <> public_key)
    public_key = Curvy.Util.encode(public_key, :hex)

    private_key =
      keypair
      |> Curvy.Key.to_privkey()
      |> Curvy.Util.encode(:hex)

    %{radix_address: radix_address, public_key: public_key, private_key: private_key}
  end
end

Im using Curvy for generating keys and signing Curvy — Curvy v0.3.0 and bech32 | Hex to convert the public key to the desired address format.

Below is the console print outs.

PYTHON OUTPUT

Private Key:  2574132b032927dad619dcfe3508646e54400b6a9437ba501e78e51e488be1be
Public Key:  027d637e3a6f331ef1ff00912dd1cddf246f887522f3d805136bf1d377296e35f7
Wallet Address:  rdx1qsp86cm78fhnx8h3luqfztw3eh0jgmugw5308kq9zd4lr5mh99hrtac2dmtpr
SignatureDER:  304502210086d9d7a8a9d5f47a4bd6947cd083622c0a08bd430b6feb0e5364a9663423d16a0220479fcdcbeb6d80e697fd71846008cfa659fa724f27d5632ee1267cc8ab54cdf2

ELIXIR OUTPUT

iex(1)> Keypair.from_private_key("2574132b032927dad619dcfe3508646e54400b6a9437ba501e78e51e488be1be")
%{
  private_key: "2574132b032927dad619dcfe3508646e54400b6a9437ba501e78e51e488be1be",
  public_key: "027d637e3a6f331ef1ff00912dd1cddf246f887522f3d805136bf1d377296e35f7",
  radix_address: "rdx1qsp86cm78fhnx8h3luqfztw3eh0jgmugw5308kq9zd4lr5mh99hrtac2dmtpr"
}

iex(2)>   Keypair.sign("42244f5ac531a551a8ffd6f6c9e63e481fe1cf30b7bc42676840c3149695445b","2574132b032927dad619dcfe3508646e54400b6a9437ba501e78e51e488be1be")
"304402201b1151bff7543ef6ed3e0f76d84f22327bd6d8a34743db1f15b9f56339a9177002202be2797a2d541713aa35f5f7ddbe07974dac00e5cf4624f47d496e90338b1bac"

I also tried the Elixir sign function like this but it did not produce the same signature.

  def sign(data, private_key) do
    {:ok, private_key} = Curvy.Util.decode(private_key, :hex)
    {:ok, data} = Curvy.Util.decode(data, :hex)
    Curvy.sign(data, private_key, encoding: :hex)
  end

For more context - this endeavor started from these two doc pages.
Here he creates an address Creating a Radix Wallet Address for Development Purposes - RadixPool Technical
Here he creates an address and signs a message with it Unregister a Validator Node using the Keystore File - RadixPool Technical

I believe the issue is in how Curvy does signing? I’m leaning towards using libsecp256k1 | Hex but cant find docs on how to use it. What do y’all think? Could my goals be achieved by using libsecp256k1?

Thank you for taking the time to help :call_me_hand:

Does this yield the same result if executed twice?

It looks like the implementation uses some entropy during signing.
You should use sign_digest_deterministic in python to get a stable result.

The Curvy implementation does that by default.

ECDSA signatures are non-deterministic, so comparing the output is not a good way to check your implementation. Instead you should feed your signature into a known-good signature verification function and see if it produces the expected result

Changing the sign function to this worked!

  def sign(data, private_key) do
    {:ok, private_key} = Curvy.Util.decode(private_key, :hex)
    {:ok, data} = Curvy.Util.decode(data, :hex)
    Curvy.sign(data, private_key, encoding: :hex, hash: :none)
  end

Sorry, I noticed that you were missing this bit and I did it when debugging your code, but the output is not the same as of your Python version, that is why I thought it must have been something else.

signature was: "30440220795b8cc1792a3535d17336fd378f57793de82d4471a85a34c1dd00c10eb3572e022041f8da5eb95c2f3f0c7933659cc33541fab786700e9a0dd2d1f2e8cff033fe7a"