I could not find an implementation of HKDF in Erlang on Hex.pm, so I figured why not make one myself.
There is one on github, but it is
a) as noted, not published on Hex.pm
b) uses the old crypto
api (actually so does the `hkdf`` library for Elixir I believe).
I think this is what I am looking for, but not sure what to do once I have the OKM.
Is it possible to use this to create a EC256 public private keypair?
I don’t know much about ECC yet to be quite honest, the best I can probably do is point towards this lovely book on cryptography that might give an answer.
It is certainly possible, though doing so in Erlang/Elixir may not be so easy.
A P-256 private key is essentially a random number d that satisfies 0 < d < n where n is the order of the curve. You can get the order of P256 from :crypto
using:
{{:prime_field, p}, {a, b, s}, g, n, h} = :crypto.ec_curve(:secp256r1)
Since a random 256-bit number may or may not be within the required range, you’re going to have to either run the KDF multiple times (e.g. with a sequence number as part of the IKM) until you get a value you can use, or use the NIST FIPS-186-4 B.4.1 procedure of producing 64 bits extra for the input and then calculating:
d =
input # 40 bytes pseudo random binary
|> :binary.decode_unsigned()
|> rem(:binary.decode_unsigned(n) - 1)
|> Kernel.+(1)
|> :binary.encode_unsigned()
You can then wrap this in a ECPrivateKey
record:
{:ECPrivateKey, 1, d, {:namedCurve, : secp256r1}, public_key_goes_here}
To calculate the public key, however, requires calculation of dG, where G is the base point of the curve. Unfortunately Erlang’s :crypto
does not provide a function to multiply points on a curve. The closest thing would be to use :public_key.compute_key/2
on d and G, which will give you the x coordinate of the point. You could then calculate the two candidates for y be evaluating the curve equation for x. I guess you could then use trial and error to see which of {x, y}
and {x, p - y}
is the valid public key for your private key…? Or you’d have to implement point multiplication in Elixir, which is not going to be terribly efficient
Anyway, TL;DR, it is theoretically possible, but you should probably not do it in Erlang/Elixir with custom code (including the code shown above) because implementing crypto from scratch is a very bad idea™ for most of us…
Just for fun, here’s a working module in pure Elixir, which works with HKDF:
iex(1)> priv = :hkdf.derive(:sha384, <<"Never gonna give you up">>, 42) |> EC.from_kdf(:secp256r1)
{:ECPrivateKey, 1,
<<152, 241, 110, 3, 198, 241, 200, 2, 25, 201, 250, 119, 187, 21, 134, 203,
162, 76, 191, 189, 65, 114, 109, 178, 62, 15, 94, 78, 182, 234, 125, 166>>,
{:namedCurve, :secp256r1},
<<4, 218, 207, 201, 92, 80, 255, 49, 227, 206, 196, 95, 97, 157, 68, 28, 156,
56, 238, 125, 99, 119, 54, 64, 197, 154, 8, 210, 27, 211, 249, 114, 241, 102,
48, 248, 183, 26, 218, 155, 3, 159, 251, 230, 229, ...>>}
iex(2)> pub = {{:ECPoint, elem(priv, 4)}, elem(priv, 3)}
{{:ECPoint,
<<4, 218, 207, 201, 92, 80, 255, 49, 227, 206, 196, 95, 97, 157, 68, 28, 156,
56, 238, 125, 99, 119, 54, 64, 197, 154, 8, 210, 27, 211, 249, 114, 241,
102, 48, 248, 183, 26, 218, 155, 3, 159, 251, 230, 229, 6, 156, ...>>},
{:namedCurve, :secp256r1}}
iex(3)> :public_key.sign("Hello, world!", :sha256, priv)
<<48, 70, 2, 33, 0, 154, 43, 221, 141, 160, 193, 248, 50, 65, 153, 117, 251, 67,
236, 144, 109, 211, 170, 249, 88, 160, 109, 126, 155, 194, 214, 28, 108, 237,
109, 148, 232, 2, 33, 0, 163, 198, 59, 141, 205, 103, 159, 179, 180, 37, ...>>
iex(4)> :public_key.verify("Hello, world!", :sha256, v(), pub)
true
But don’t use it!
I was looking at this post https://blog.lelonek.me/bitcoin-extended-keys-773ab100c59f Section: " Master private key and chain code"
Which suggested it was as simple as the following.
<<private_key::binary-32, chain_code::binary-32>> = hashed_seed
{private_key, chain_code}
{public_key, _} = :crypto.generate_key(:ecdh, :secp256k1, private_key)
I guess I’m missing some key details
Using :crypto.generate_key/3
to derive a public key from a private key would certainly save a lot of work. It seems to work for SECP 256k1, but not 256r1 (NIST P-256). Edit: actually, it does work, I’ll update the gist…
Also, it seems to assume the private key value passed in is indeed a valid private key, but a random 32-bit value would have to be clamped to the curve’s order. Failing to do so would result in a significant bias towards lower scalar values, as values larger than the order would effectively wrap around.