How is a public key derived?

I'm trying to derive a public key from a private one, but am failing to understand the documentation at Integration Guides - The Basics - Nano Documentation :

It is derived from an account private key by using the ED25519 curve using Blake2b-512 as the hash function (instead of SHA-512).

Until now I thought that a public key in elliptic curve cryptography was just the private key multiplied with the base point of the curve (that's what I know from Monero), but apparently this is not true with Nano. Nevertheless I had opened an issue at my edwards25519 library of choice here, but was educated, that hash functions are only involved with Ed25519 signatures. Unfortunately I'm still failing to understand what's happening. Is a Nano public key not a public key in the traditional sense, but a signature instead? Could someone explain to me in simple terms how a Nano public key is derived?

Sorry for the potentially uneducated question, I'm not too familiar with cryptography, but want to try and write my own Nano wallet for fun. Once I understand what's happening, I'd happily help improving the documentation.

The issue that you linked is on a Golang library, so I think you are using Golang. In any case, you can take a look on the code:

The original code (mentioned above) uses SHA-512, that is what must be replaced for Blake2b. You can see the Blake2b version at blakEd25519/blakEd25519.go at master · Inkeliz/blakEd25519 · GitHub. That second link is compatible with Nano.

The only change is the hash-function.

Also, beware that Nano uses Ed25519 wrongly. So... Everytime that you sign the transaction-block you must sign the hash of the block, instead of the block content itself. So, instead of Sign(Block_Content) you should do Sign(Blake2b(Block_Content)).

1 Like

I think we have a misunderstanding: You have linked code, that resides in a function called Sign, but I'm interested in public key generation. I did, however, find a function called GenerateKey in the source file you mentioned. It seems like the magic is happening here and in the lines after that: blakEd25519/blakEd25519.go at ee465f5830e928a6772a050b25b4f4feb8edb4d1 · Inkeliz/blakEd25519 · GitHub

Why are bytes 0 and 31 manipulated here? Is this documented somewhere? edwards25519.GeScalarMultBase only seems to work with the first 32 bytes of the Blake2b hash - do you know why the 512bit variant was chosen over the 256bit one, if half of the bits are discarded anyway?

I'm thankful for your reply and don't mean to be rude, but I was looking for a "higher level" explanation of the public key derivation algorithm, instead of a link to an implementation. If I don't get such an explanation here, I'll have to make due with interpreting implementations, but I would prefer it if someone could tell me what is done here and ideally also why it is done this way.

After reading your code @Inkeliz I have tinkered a bit more and think I know understand the algorithm:

  1. Take the 512 bit (64 byte) Blake2b hash of the private key.
  2. Remove the last 32 bytes of the hash, so that you are left with the first 32 bytes.
  3. Take these first 32 bytes of the hash modulus l (a constant of the edwards25519 curve; this process is also referred to as "clamping").
  4. Multiply these clamped first 32 bytes of the hash with the base point of the edwards25519 curve.

In pseudo/goish-code this could look like this:

hashBytes := blake2b.Sum512(privateKeyBytes)
scalar := edwards25519.NewScalar().SetBytesWithClamping(hashBytes[:32])
publicKeyBytes := edwards25519.NewIdentityPoint().ScalarBaseMult(s).Bytes()

I'm still uncertain why the private key is hashed before doing the calculations on the curve, and why a 512 bit hash is generated instead of a 256 bit one, so if anyone knows more about this, please let me know!