Lyn_Lee
(Lyn Lee)
27 July 2022 06:41
1
I have a test demo by using bouncyCastle.
@Test
public void testBouncyCastleSecp256k1() {
X9ECParameters p = SECNamedCurves.getByName("secp256k1");
ECDomainParameters params = new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH());
BigInteger privKey = new BigInteger("b77e91f2b6905bd0cb7295c529b20152181b5e9e18f62c02b3c2d75747a73f1e", 16);
BigInteger pubKey = new BigInteger("02d7482187387eef4d462864fe0f41a6780cfb25d7376b5535b6af8640d97281d3", 16);
String msg = "041f2b145c7fa7d8b49de0e9de59fb96a270b3b1869b1d1cbf2059a560542b2c";
ECPrivateKeyParameters privParams = new ECPrivateKeyParameters(privKey, params);
byte[] privateKeyBytes = ECKeyUtils.adjustArray(privParams.getD().toByteArray(), 32);
System.out.println("privKey : " + Utils.HEX.encode(privateKeyBytes));
byte[] messageBytes = HashUtils.sha256(msg.getBytes()).asBytes();
ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
signer.init(true, privParams);
BigInteger[] signature = signer.generateSignature(messageBytes);
byte[] derBytes = ECKeyUtils.toUnrecoverableDERBytes(signature[0], signature[1]);
System.out.println("Signature(DER format): " + Utils.HEX.encode(derBytes));
BouncyCastleKeyHandler handler = new BouncyCastleKeyHandler(CustomNamedCurves.getByName("secp256k1"));
if (handler.verify(messageBytes, signature[0], signature[1], pubKey.toByteArray())) {
System.out.println("verifySignature successfully");
} else {
System.out.println("verifySignature failed");
}
}
And it returns the signature in DER format :Signature(DER format): 30440220079ad26fdbf47ef41659e32a39b1984b486ff609205e8b4914e41bc51fa47a2902207f9dd5f5c06aee3f4946e6db7160b7f7099b9b2fbb0c632259cea1ea12a577a8
But it always return the error “Unable to calculate V byte for public key” when I use this signature to finalize transaction .
When I use the same private key to sign the transaction by python lib and finalize transaction, it returns the correct signed transaction and transaction identifier info.
Mleekko
(Oleh Koval)
27 July 2022 13:45
2
Where do BouncyCastleKeyHandler
, ECKeyUtils
and HashUtils
come from? I can’t find them in BouncyCastle.
Mleekko
(Oleh Koval)
27 July 2022 13:55
3
Also, the DER signature differs from what I get (UPD: the signature might vary because it is non-deterministic):
package live.radix.example;
import com.radixdlt.crypto.ECDSASignature;
import com.radixdlt.crypto.ECKeyPair;
import com.radixdlt.crypto.ECKeyUtils;
import com.radixdlt.crypto.exception.PrivateKeyException;
import com.radixdlt.crypto.exception.PublicKeyException;
import com.radixdlt.utils.Bytes;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1OutputStream;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import static org.junit.Assert.assertEquals;
public class SignerTest {
@Test
public void testBouncyCastleSecp256k1_2() throws PrivateKeyException, PublicKeyException, DecoderException, IOException {
BigInteger privKey = new BigInteger("b77e91f2b6905bd0cb7295c529b20152181b5e9e18f62c02b3c2d75747a73f1e", 16);
String msg = "041f2b145c7fa7d8b49de0e9de59fb96a270b3b1869b1d1cbf2059a560542b2c";
ECPrivateKeyParameters privParams = new ECPrivateKeyParameters(privKey, ECKeyUtils.domain());
byte[] privateKeyBytes = ECKeyUtils.adjustArray(privParams.getD().toByteArray(), 32);
ECKeyPair keyPair = ECKeyPair.fromPrivateKey(privateKeyBytes);
assertEquals("02d7482187387eef4d462864fe0f41a6780cfb25d7376b5535b6af8640d97281d3", keyPair.getPublicKey().toHex());
byte[] bytes = Hex.decodeHex(msg);
byte[] derSignature = toDerSignature(keyPair.sign(bytes));
System.out.println(Bytes.toHexString(derSignature));
}
private byte[] toDerSignature(ECDSASignature sign) throws IOException {
var os = new ByteArrayOutputStream();
var asn1OutputStream = ASN1OutputStream.create(os);
asn1OutputStream.writeObject(
new DLSequence(
new ASN1Encodable[]{new ASN1Integer(sign.getR()), new ASN1Integer(sign.getS())}));
return os.toByteArray();
}
}
(requires the below dependency)
<!-- https://mvnrepository.com/artifact/live.radix/radixdlt-java-common -->
<dependency>
<groupId>live.radix</groupId>
<artifactId>radixdlt-java-common</artifactId>
<version>1.2.1</version>
</dependency>
Lyn_Lee
(Lyn Lee)
27 July 2022 14:27
4
This is toUnrecoverableDERBytes function detail in my test.
public static byte[] toUnrecoverableDERBytes(BigInteger r, BigInteger s) {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
final ASN1OutputStream asn1OutputStream = ASN1OutputStream.create(os);
try {
asn1OutputStream.writeObject(
new DLSequence(new ASN1Encodable[]{new ASN1Integer(r), new ASN1Integer(s)}));
} catch (IOException e) {
throw new RuntimeException(e);
}
return os.toByteArray();
}
r and s are both generate by ECDSA signing progess, so I passed them in directly as parameters
var signer =
new ECDSASigner(
useDeterministicSignatures
? new HMacDSAKCalculator(new SHA256Digest())
: new RandomDSAKCalculator());
signer.init(true, new ECPrivateKeyParameters(new BigInteger(1, privateKey), domain));
var components = signer.generateSignature(hash);
var r = components[0];
var s = components[1];
My private key and public key is not genrated by ECKeyPair in radixdlt-java-common, but I think it should not effect.
Mleekko
(Oleh Koval)
27 July 2022 15:10
5
ok, so the signing part looks ok, can you please share the code that you use to build the TransactionFinalizeRequest
to the API ? in particular creating the signature
param.
Lyn_Lee
(Lyn Lee)
27 July 2022 15:11
6
Mleekko:
BouncyCastleKeyHandler
BouncyCastleKeyHandler
, ECKeyUtils
and HashUtils
are a part I extracted from radix-java-common
Lyn_Lee
(Lyn Lee)
27 July 2022 15:11
7
Mleekko:
signature
This is my API request send to url : {{dev_node}}/transaction/finalize
{
"network_identifier": {
"network": "mainnet"
},
"unsigned_transaction": "079da6d7ef3f9465e0e3260986098c6c50fe40fe0a1b0fc0d4c2c101821ce563b300000001010021000000000000000000000000000000000000000000000000000094df774aab000002004506000402f01ceafea2166eac118485daf14e2afa75caddfe716fc92ef6a1eaa41953561e01000000000000000000000000000000000000000000000000e23a3d3a66a157850008000002004506000402f01ceafea2166eac118485daf14e2afa75caddfe716fc92ef6a1eaa41953561e01000000000000000000000000000000000000000000000000e2363fdd968c2785020045060004024fa1f2474d91251ae938b46e35a6242ccb8f6d5f55e585f0e72a41afadba999b010000000000000000000000000000000000000000000000000003fd5cd015300000",
"signature": {
"public_key": {
"hex": "02f01ceafea2166eac118485daf14e2afa75caddfe716fc92ef6a1eaa41953561e"
},
"bytes": "3045022100df285555c4734f081648e3e35298abba672fb84900103bf24102e8b7cef352cc02201722c268e8e8989fa30e10993cc1c739e6dbd4578063211d6a672b9e6da01e7e"
},
"submit": "false"
}
Mleekko
(Oleh Koval)
27 July 2022 15:26
8
public_key.hex
here differs from your above example. is this request for a different address?
Lyn_Lee
(Lyn Lee)
27 July 2022 15:41
9
Yes, this is a recent request
Lyn_Lee
(Lyn Lee)
27 July 2022 16:22
10
Thank you for your reply .
I use the wrong decode function and one more time sha256 operation.
byte[] messageBytes = HashUtils.sha256(msg.getBytes()).asBytes();
should change to
byte[] bytes = Hex.decodeHex(msg);
2 Likes