terl / lazysodium-java

A Java implementation of the Libsodium crypto library. For the lazy dev.
https://github.com/terl/lazysodium-java/wiki
Mozilla Public License 2.0
135 stars 47 forks source link

lazysodium cryptoSignDetached returning incorrect sig #40

Closed adkessell closed 5 years ago

adkessell commented 5 years ago

When executing the lazysodium cryptoSignDeteached on Ubuntu 18.04 either Jave 8 or 11, it returns the incorrect sig.

The hashBytes is correct and the privateKeyBytes is correct, but the sig for some reason isn't.

adkessell commented 5 years ago

testlazysodium.zip

Am using Maven to compile and Eclipse IDE.

Any help would be appreciated.

gurpreet- commented 5 years ago

Hi @adkessell,

Just glanced through your code. I think the issue is what you pass through to the function. Instead of:

long[] sigLen = new long[] {sig.length};
boolean r = lazysodium.cryptoSignDetached(sig, sigLen, hashBytes, hashBytes.length, privateKeyBytes);

It should be:

boolean r = lazysodium.cryptoSignDetached(sig, null, hashBytes, hashBytes.length, privateKeyBytes);

As you can see the sigLength param has been nullified. Libsodium docs say that "The actual length of the signature is put into siglen if siglen is not NULL." Which means you don't need to pass the signature's length into it.

Let me know if the above works for you.

adkessell commented 5 years ago

Gurpreet,

Thanks for the quick response. I had it set to null first and didn't work that way either.

So not sure what is causing this not to work, as I have been trying to figure this out for a few days now.

If you can take the code and run it against your libraries and see if it works for you, then send the result back would be appreciated.

Thanks Anthony

gurpreet- commented 5 years ago

Hi Anthony,

I appreciate your patience, I am running the tests now and trying to emulate your environment. Is it a 64 or 32 bit machine you are working on?

gurpreet- commented 5 years ago

Ah I think I found your problem. Your secret key must be 64 bytes long. Currently your keys are 54 bytes long.

byte[] publicKey = randomBytesBuf(Sign.PUBLICKEYBYTES); // 32 bytes
byte[] secretKey = randomBytesBuf(Sign.SECRETKEYBYTES); // 64 bytes
if (!cryptoSignKeypair(publicKey, secretKey)) { // Use this method to help you
   throw new SodiumException("Could not generate a signing keypair.");
}
Key pk = Key.fromBytes(publicKey);
Key sk = Key.fromBytes(secretKey);

String signed = lazySodium.cryptoSignDetached(msg, sk); // Can also use Native method
boolean verification = lazySodium.cryptoSignVerifyDetached(signed, msg, pk); // true

I know that you already have generated a secret and public key but they both must be generated using the algorithm that powers cryptoSign(). (Or you can use cryptoSignSeedKeypair() to generate a deterministic key pair).

Hope that helps 👍

adkessell commented 5 years ago

I am using 64bit machine.

Interesting, as these keys were generated by the tezos blockchain sandbox wallet and I have written some python code to utilize them and they work perfectly fine. I am calling blake2b, ed25519 and has256 within my code without issues.

So in my java code I decode them to hex and they are 64bytes long, so by padding them out will change the keys or using the sodium key generator will not work with the code I am using as the keys are generated and stored.

I would thought that this should be able to take any 64byte keys generated by a wallet.

I will try your approach, also try libsodium in python to see what result I get and see where it goes but I think libsodium should be able to take keys generated by any wallet.

Thanks will let you know.

adkessell commented 5 years ago
       byte[] edskPrefix = {(byte) 43, (byte) 246, (byte) 78, (byte) 7};
       byte[] decodeSK = base58code.decode(skey);
       byte[] privateKeyBytes = Arrays.copyOfRange(decodeSK, edskPrefix.length, decodeSK.length);

This code decodes it to 64bytes, if you printout the hex of privateKeyBytes and the length it is 64bytes. The edsk has to be stripped off as it is a tezos marker.

adkessell commented 5 years ago

How has the lazysodium library been built, Deterministic or non-deterministic? As everytime I run it I get a different result even though the inputs are the same.

gurpreet- commented 5 years ago

How has the lazysodium library been built, Deterministic or non-deterministic? As everytime I run it I get a different result even though the inputs are the same.

It's been built deterministically.

if you printout the hex of privateKeyBytes and the length it is 64bytes

Yes but that's the hex and not the actual bytes. Hex encoding is just a way for us humans to conveniently read the byte array (or store it as a string somewhere). The code below shows you the hexLen and the actual length. Both keys return 32 bytes as the actual length.

Key privKey = Key.fromBytes(privateKeyBytes);
Key pubKey = Key.fromBytes(publicKeyBytes);
System.out.println("privateKeyHex=" + privKey.getAsHexString() + " hexLen=" + privKey.getAsHexString().length() + " length=" + privKey.getAsBytes().length);
System.out.println("publicKeyHex=" + pubKey.getAsHexString() + " hexLen=" + pubKey.getAsHexString().length() + " length=" + pubKey.getAsBytes().length);

How does Tezos generate its public and private keys (i.e how are pkey and skey generated)? Is there some sort of algorithm?

If I can understand how those are generated, then maybe we can see how the two keys relate to each other and so help you input the correct values for cryptoSignDetached().

adkessell commented 5 years ago

Not sure how Tezos generated the keys, but here is the python code I have used to generate the signature.

bin_skey=base58check.b58decode(skey).hex()[8:72]

bin_operation=binascii.unhexlify(operation) seed=binascii.unhexlify(bin_skey)

from pyblake2 import blake2b h=blake2b(digest_size=32) h.update(b'\x03'+bin_operation) digest=h.digest()

sk=ed25519.SigningKey(seed) sig=sk.sign(digest)

edsig_sig='09f5cd8612'+sig.hex() m1=hashlib.sha256(binascii.unhexlify(edsig_sig)) m2=hashlib.sha256(m1.digest()) checksum=m2.hexdigest()[:8] final_hex=edsig_sig+checksum final_encode=base58check.b58encode(binascii.unhexlify(final_hex)) final=final_encode.decode('ascii')

gurpreet- commented 5 years ago

I spent a lot of time looking through the source code of Tezos as to how it generates keys. Your secret key is the wrong size, it must be 64 bytes long. The code below works.

public class Test {

    public static void main(String[] args) throws Exception {

        LazySodiumJava lazysodium = new LazySodiumJava(new SodiumJava(), StandardCharsets.UTF_8);

        // Generate correct keys.
        String publicKey = "edpktgy221Yw1AbERrMVbKpeGCdrS36TAK5wM6b4Pq2w1rjvBCxBQN";
        String secretKey = "edskRtpgZd7LVA6yZvVPkpDnoUiwDu7Zum9t4KX69iFerhu3yDNe33d9hMwqftELnui5ohHNa2JQi4VRqXEUt5URzdS3whmG6q";
        String msg = "yo";

        // Base 58 decode the secret key.
        byte[] base58SKey = base58check.decode(secretKey);
        byte[] base58PKey = base58check.decode(publicKey);

        // Print out the base58 decoded keys.
        System.out.println("Base58 decoded keys");
        printKey(Key.fromBytes(base58SKey));
        printKey(Key.fromBytes(base58PKey));

        // Remove first 4 bytes which is the prefix.
        byte[] removedBytesSk = Arrays.copyOfRange(base58SKey, 4, base58SKey.length);
        byte[] removedBytesPk = Arrays.copyOfRange(base58PKey, 4, base58PKey.length);

        // Convert to keys.
        Key properSk = Key.fromBytes(removedBytesSk);
        Key properPk = Key.fromBytes(removedBytesPk);

        // Check if the bytes have been removed.
        System.out.println();
        System.out.println("Hex keys");
        printKey(properSk);
        printKey(properPk);

        // Hash or do whatever you want to the message.
        String hashed = lazysodium.cryptoGenericHash(msg);
        byte[] hashBytes = lazysodium.sodiumHex2Bin(hashed);

        // Now sign it and verify it.
        byte[] sig = new byte[Sign.BYTES];
        lazysodium.cryptoSignDetached(sig, null, hashBytes, hashBytes.length, properSk.getAsBytes());
        boolean correct = lazysodium.cryptoSignVerifyDetached(sig, hashBytes, hashBytes.length, properPk.getAsBytes());

        System.out.println(correct);
    }

    public static void printKey(Key key) {
        System.out.println("Length = " + key.getAsBytes().length + ". " + LazySodium.toHex(key.getAsBytes()));
    }

}
adkessell commented 5 years ago

Interesting as the keys I have been using is what was supplied by them for their sandbox.

Thanks for taking the time to go through this, appreciate your help.

Have a Happy New Year.

From: Gurpreet Paul notifications@github.com Reply-To: terl/lazysodium-java reply@reply.github.com Date: Sunday, December 30, 2018 at 8:30 AM To: terl/lazysodium-java lazysodium-java@noreply.github.com Cc: Anthony Kessell anthony@kessell.com.au, Mention mention@noreply.github.com Subject: Re: [terl/lazysodium-java] lazysodium cryptoSignDetached returning incorrect sig (#40)

I spent a lot of time looking through the source code of Tezos as to how it generates keys. Your secret key is the wrong size, it must be 64 bytes long. The code below works.

public class Test {

    public static void main(String[] args) throws Exception {

        LazySodiumJava lazysodium = new LazySodiumJava(new SodiumJava(), StandardCharsets.UTF_8);

        // Generate correct keys.         String publicKey = "edpktgy221Yw1AbERrMVbKpeGCdrS36TAK5wM6b4Pq2w1rjvBCxBQN";         String secretKey = "edskRtpgZd7LVA6yZvVPkpDnoUiwDu7Zum9t4KX69iFerhu3yDNe33d9hMwqftELnui5ohHNa2JQi4VRqXEUt5URzdS3whmG6q";         String msg = "yo";

        // Base 58 decode the secret key.         byte[] base58SKey = base58check.decode(secretKey);         byte[] base58PKey = base58check.decode(publicKey);

        // Print out the base58 decoded keys.         System.out.println("Base58 decoded keys");         printKey(Key.fromBytes(base58SKey));         printKey(Key.fromBytes(base58PKey));

        // Remove first 4 bytes which is the prefix.         byte[] removedBytesSk = Arrays.copyOfRange(base58SKey, 4, base58SKey.length);         byte[] removedBytesPk = Arrays.copyOfRange(base58PKey, 4, base58PKey.length);

        // Convert to keys.         Key properSk = Key.fromBytes(removedBytesSk);         Key properPk = Key.fromBytes(removedBytesPk);

        // Check if the bytes have been removed.         System.out.println();         System.out.println("Hex keys");         printKey(properSk);         printKey(properPk);

        // Hash or do whatever you want to the message.         String hashed = lazysodium.cryptoGenericHash(msg);         byte[] hashBytes = lazysodium.sodiumHex2Bin(hashed);

        // Now sign it and verify it.         byte[] sig = new byte[Sign.BYTES];         lazysodium.cryptoSignDetached(sig, null, hashBytes, hashBytes.length, properSk.getAsBytes());         boolean correct = lazysodium.cryptoSignVerifyDetached(sig, hashBytes, hashBytes.length, properPk.getAsBytes());

        System.out.println(correct);     }

    public static void printKey(Key key) {         System.out.println("Length = " + key.getAsBytes().length + ". " + LazySodium.toHex(key.getAsBytes()));     }

} — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

gurpreet- commented 5 years ago

Hey @adkessell,

No problem. By the way, the source code of this project really helped.

Happy coding 👍