I am building a encrypted messaging app over tor network and currently I'm struggling on using tor generated ed25519 private key to sign and verify any message.

Below piece of code works with a 32 bytes key however after skipping 32 header bytes of hs_ed25519_secret_key it fails to verify the signature on below cases:

1 - secret: left half of the remaining 64 bytes, public: right half
2 - secret: left half of the remaining 64 bytes, public: last 32 bytes of hs_ed25519_public_key after removing the header
3 - secret: all 64 bytes, public: last 32 bytes of hs_ed25519_public_key

I found a python library that seems to do this PyNaCl however i not familiar with py too much.

Is there something i am doing wrong or bouncycastle does not support expanded 64 bytes private keys

import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import java.nio.charset.StandardCharsets;
public class ED25519 {
    public static void main(String[] args) throws Exception {
        byte[] message = "a msg to be signed".getBytes(StandardCharsets.UTF_8);
        Signer signer = new Ed25519Signer();
        signer.init(true,  new Ed25519PrivateKeyParameters(KeysUtil.myPrivKey, 0));
        signer.update(message, 0, message.length);
        Signer verifier = new Ed25519Signer();
        verifier.init(false, new Ed25519PublicKeyParameters(KeysUtil.myPubKey, 0));
        verifier.update(message, 0, message.length);
        boolean validSig = verifier.verifySignature(signer.generateSignature());


1 Answer 1


Your code is fine if you ask BouncyCastle to generate the key pair:

var gen = new Ed25519KeyPairGenerator();
gen.init(new Ed25519KeyGenerationParameters(new SecureRandom()));
var keyPair = gen.generateKeyPair();
var sk = new byte[32];
((Ed25519PrivateKeyParameters) keyPair.getPrivate()).encode(sk, 0);
var pk = ((Ed25519PublicKeyParameters) keyPair.getPublic()).getEncoded();

The reason it's not working for your Tor key pair is because BouncyCastle isn't calculating the public key by scalar multiplication of the base point with your little-endian secret key.

Instead, it's following RFC 8032 and doing SHA-512 on your initial key material, clearing and setting certain bits, and then using that as the secret key to determine the public key.

It's doing this:

MessageDigest md = MessageDigest.getInstance("SHA-512");
byte [] digest = md.digest(initialKeyMaterial);
var sk = subarray(digest, 0, 32);
sk[0] &= 248;
sk[31] &= 127;
sk[31] |= 64;
var pk = basePoint.scalarMultiply(sk);

If the secret key you are specifying has already been through this SHA-512 transformation, then your code is failing because it's now doing SHA-512 twice. Since you can't reverse the SHA-512 operation, then if you must use BouncyCastle, you'll need to generate your secret key in BouncyCastle and then use that with tor, instead of the other way around.

However, since you probably need to start with the Tor key pair, you might consider modifying the BouncyCastle code to not perform the SHA-512 transformation prior to using it as a signing key.

  • $\begingroup$ Hmm, thanks for the advice, I'll try to rework the signing process since i cannot use BC's generated keypairs when later I'll need to use a quick vanity address with mkp224o that is generating an 64 bytes expanded key like tor. $\endgroup$
    – Miliano
    Apr 13, 2022 at 6:57
  • $\begingroup$ thank you @knaccc. Could you please explain why the expansion is needed? tor.stackexchange.com/questions/23849/… $\endgroup$ Nov 1, 2023 at 17:32
  • 1
    $\begingroup$ @SergeyPonomarev I'm not familiar with how Tor works, so this is my assumption: It's common in cryptographic protocols to have one source of randomness (the random "nonce" and the optional secret "s"), and then use that single source of randomness to derive keys for different purposes. This is what is described in the spec document linked to in that question in section A.2. $\endgroup$
    – knaccc
    Nov 1, 2023 at 17:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.