Recently I have been looking at the Naivecoin tutorial, and trying to implement it in Go to get an idea of how blockchains really work, and to learn some more Go as well. The tutorial code is in Javascript, and translating it to Go has been mostly straightforward. However, porting the third part with transactions was giving me some issues. I had trouble figuring how to port the signature part. This is tutorial the code in Javascript:
const key = ec.keyFromPrivate(privateKey, 'hex'); const signature: string = toHexString(key.sign(dataToSign).toDER());
This is nice and simple, just suitable for a high-level framework and tutorial. However, to implement it myself in Go, …
The JS code above takes the private key as a hex formatted string, and parses that into a Javascript PrivateKey object. This key object is then used to sign the “dataToSign”, the signature is formatted as something called “DER”, and the result is formatted as a hex string. What does all that mean?
The tutorial refers to Elliptic Curve Digital Signature Algorithm (ECDSA). The data to sign in this case is the SHA256 hash of the transaction ID. So how to do this in Go? Go has an ecdsa package with private keys, public keys, and functions to sign and verify data. Sounds good. But the documentation is quite sparse, so how do I know how to properly use it?
To try it, I decided to first write a program in Java using ECDSA signatures, and use it to compare to the results of the Go version I would write. This way I would have another point of reference to compare my results to, and to understand if I did something wrong. I seemed to find more information about the Java implementation, and since I am more familiar with Java in general..
So first to generate the keys to use for signatures in Java:
public static String generateKey() throws Exception { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); keyGen.initialize(256, random); //256 bit key size KeyPair pair = keyGen.generateKeyPair(); ECPrivateKey priv = (ECPrivateKey) pair.getPrivate(); PublicKey pub = pair.getPublic(); //actually also need public key, but lets get to that later... return priv; }
Above code starts with getting an “EC” key-pair generator, EC referring to Elliptic Curve. Then get a secure random number generator instance, in this case one based on SHA1 hash algorithm. Apparently this is fine, even if SHA1 is not recommended for everything these days. Not quite sure about the key size of 256 given, but maybe have to look at that later.. First to get this working.
The “priv.Encoded()” part turns the private key into a standard encoding format as a byte array. Base64 encode it for character representation, to copy to the Go version..
Next, to sign the data (or message, or whatever we want to sign..):
public static byte[] signMsg(String msg, PrivateKey priv) throws Exception { Signature ecdsa = Signature.getInstance("SHA1withECDSA"); ecdsa.initSign(priv); byte[] strByte = msg.getBytes("UTF-8"); ecdsa.update(strByte); byte[] realSig = ecdsa.sign(); System.out.println("Signature: " + new BigInteger(1, realSig).toString(16)); return realSig; }
Above starts with gettings a Java instance of the ECDSA signature algorithm, with type “SHA1withECDSA”. I spent a good moment wondering what all this means, to be able to copy the functionality into the Go version. So long story short, first the data is hashed with SHA1 and then this hash is signed with ECDSA. Finally, the code above prints the signature bytes as a hexadecimal string (byte array->BigInteger->base 16 string). I can then simply copy-paste this hex-string to Go to see if I can get signature verification to work in Go vs Java. Brilliant.
First I tried to see that I can get the signature verification to work in Java:
private static boolean verifySignature(PublicKey pubKey,String msg, byte[] signature) throws Exception { byte[] message = msg.getBytes("UTF-8"); Signature ecdsa = Signature.getInstance("SHA1withECDSA"); ecdsa.initVerify(pubKey); ecdsa.update(message); return ecdsa.verify(signature); }
The code above takes the public key associated with the private key that was used to sign the data (called “msg” here). It creates the same type of ECDSA signature instance as the signature creation previously. This is used to verify the signature is valid for the given message (data). So signed with the private key, verified with the public key. And yes, it returns true for the signed message string, and false otherwise, so it works. So now knowing I got this to work, I can try the same in Go, using the signature, public key, and private key that was used in Java. But again, the question. How do I move these over?
Java seems to provide functions such as key.getEncoded(). This gives a byte array. We can then Base64 encode it to get a string (I believe Bitcoin etc. use Base56 but the same idea). So something like this:
//https://stackoverflow.com/questions/5355466/converting-secret-key-into-a-string-and-vice-versa byte[] pubEncoded = pub.getEncoded(); String encodedPublicKey = Base64.getEncoder().encodeToString(pubEncoded); String encodedPrivateKey = Base64.getEncoder().encodeToString(priv.getEncoded()); System.out.println(encodedPrivateKey); System.out.println(encodedPublicKey);
Maybe I could then take the output I just printed, and decode that into the key in Go? But what is the encoding? Well, the JDK docs say getEncoded() “Returns the key in its primary encoding format”. And what might that be? Well some internet searching and debugger runs later I come up with this (which works to re-create the keys in Java):
public static PrivateKey base64ToPrivateKey(String encodedKey) throws Exception { byte[] decodedKey = Base64.getDecoder().decode(encodedKey); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decodedKey); KeyFactory factory = KeyFactory.getInstance("EC"); PrivateKey privateKey = factory.generatePrivate(spec); return privateKey; } public static PublicKey base64ToPublicKey(String encodedKey) throws Exception { byte[] decodedKey = Base64.getDecoder().decode(encodedKey); X509EncodedKeySpec spec = new X509EncodedKeySpec(decodedKey); KeyFactory factory = KeyFactory.getInstance("EC"); return publicKey; }
So the JDK encodes the private key in PKCS8 format, and the public key in some kind of X509 format. X509 seems to be related to certificates, and PKCS refers to “Public Key Cryptography Standards”, of which there are several. Both of these seem a bit complicated, as I was just looking to transfer the keys over. Since people can post those online for various crypto tools as short strings, it cannot be that difficult, can it?
I tried to look for ways to take PKCS8 and X509 data into Go and transform those into private and public keys. Did not get me too far with that. Instead, I figured there must be only a small part of the keys that is needed to reproduce them.
So I found that the private key has a single large number that is the important bit, and the public key can be calculated from the private key. And the public key in itself consists of two parameters, the x and y coordinates of a point (I assume on the elliptic curve). I browsed all over the internet trying to figure this all out, but did not keep records of all the sites I visited, so my references are kind of lost. However, here is one description that just so states the integer and point part. Anyway, please let me know of any good references for a non-mathematician like me to understand it if you have any.
To get the private key value into suitable format to pass around in Java:
//https://stackoverflow.com/questions/40552688/generating-a-ecdsa-private-key-in-bouncy-castle-returns-a-public-key private static String getPrivateKeyAsHex(PrivateKey privateKey) { ECPrivateKey ecPrivateKey = (ECPrivateKey) privateKey; byte[] privateKeyBytes = ecPrivateKey.getS().toByteArray(); String hex = bytesToHex(privateKeyBytes); return hex; }
The “hex” string in the above code is the big integer value that forms the basis of the private key. This can now be passed, backed up, or whatever we desire. Of course, it should be kept private so no posting it on the internet.
For the public key:
private static String getPublicKeyAsHex(PublicKey publicKey) { ECPublicKey ecPublicKey = (ECPublicKey) publicKey; ECPoint ecPoint = ecPublicKey.getW(); byte[] affineXBytes = ecPoint.getAffineX().toByteArray(); byte[] affineYBytes = ecPoint.getAffineY().toByteArray(); String hexX = bytesToHex(affineXBytes); String hexY = bytesToHex(affineYBytes); return hexX+":"+hexY; }
The above code takes the X and Y coordinates that make up the public key, combines them, and thus forms a single string that can be passed to get the X and Y for public key. A more sensible option would likely just create a single byte array with the length of the first part as first byte or two. Something like [byte count for X][bytes of X][bytes of Y]. But the string concatenation works for my simple example to try to understand it.
And then there is one more thing that needs to be encoded and passed between the implementations, which is the signature. Far above, I wrote the “signMsg()” method to build the signature. I also printed the signature bytes out as a hex-string. But what format is the signature in, and how do you translate it to another platform and verify it? It turns out Java gives the signatures in ASN.1 format. There is a good description of the format here. It’s not too complicated but how would I import that into Go again? I did not find any mention of this in the ECDSA package for Go. By searching with ASN.1 I did finally find an ASN.1 package for Go. But is there a way to do that without these (poorly documented) encodings?
Well, it turns out that ECDSA signatures can also be described by using just two large integers, which I refer to here as R and S. To get these in Java:
public static byte[] signMsg(String msg, PrivateKey priv) throws Exception { Signature ecdsa = Signature.getInstance("SHA1withECDSA"); ecdsa.initSign(priv); byte[] strByte = msg.getBytes("UTF-8"); ecdsa.update(strByte); byte[] realSig = ecdsa.sign(); System.out.println("R: "+extractR(realSig)); System.out.println("S: "+extractS(realSig)); return realSig; } //https://stackoverflow.com/questions/48783809/ecdsa-sign-with-bouncycastle-and-verify-with-crypto public static BigInteger extractR(byte[] signature) throws Exception { int startR = (signature[1] & 0x80) != 0 ? 3 : 2; int lengthR = signature[startR + 1]; return new BigInteger(Arrays.copyOfRange(signature, startR + 2, startR + 2 + lengthR)); } public static BigInteger extractS(byte[] signature) throws Exception { int startR = (signature[1] & 0x80) != 0 ? 3 : 2; int lengthR = signature[startR + 1]; int startS = startR + 2 + lengthR; int lengthS = signature[startS + 1]; return new BigInteger(Arrays.copyOfRange(signature, startS + 2, startS + 2 + lengthS)); }
Above code takes the byte array of the signature, and parses the R and S from it as matching the ASN.1 specification I linked above. So with that, another alternative is again to just turn the R and S into hex-strings or Base56 encoded strings, combine them as a single byte-array and hex-string or base56 that, or whatever. But just those two values need to be passed to capture the signature.
Now, finally to parse all this data in Go and to verify the signature. First to get the private key from the hex-string:
func hexToPrivateKey(hexStr string) *ecdsa.PrivateKey { bytes, err := hex.DecodeString(hexStr) print(err) k := new(big.Int) k.SetBytes(bytes) priv := new(ecdsa.PrivateKey) curve := elliptic.P256() priv.PublicKey.Curve = curve priv.D = k priv.PublicKey.X, priv.PublicKey.Y = curve.ScalarBaseMult(k.Bytes()) //this print can be used to verify if we got the same parameters as in Java version fmt.Printf("X: %d, Y: %d", priv.PublicKey.X, priv.PublicKey.Y) println() return priv }
The above code takes the hex-string, parses it into a byte array, creates a Go big integer from that, and sets the result as the value into the private key. The other part that is needed is the elliptic curve definition. In practice, one of a predefined set of curves is usually used, and the same curve is used for a specific purpose. So it can be defined as a constant, whichever is selected for the blockchain. In this case it is always defined as the P256 curve, both in the Java and Go versions. For example, Bitcoin uses the Secp256k1 curve. So I just set the curve and the big integer to create the private key. The public key (X and Y parameters) is calculated here from the private key, by using a multiplier function on the private key’s big integer.
To build the public key straight from the X and Y values passed in as hex-strings:
func hexToPublicKey(xHex string, yHex string) *ecdsa.PublicKey { xBytes, _ := hex.DecodeString(xHex) x := new(big.Int) x.SetBytes(xBytes) yBytes, _ := hex.DecodeString(yHex) y := new(big.Int) y.SetBytes(yBytes) pub := new(ecdsa.PublicKey) pub.X = x pub.Y = y pub.Curve = elliptic.P256() return pub }
Again, base56 or similar would likely be more efficient representation. So the above code allows just to pass around the public key and not the private key, which is how it should be done. With the parameters X and Y passed, and the curve defined as a constant choice.
To create and verify the signature from the passed values:
type ecdsaSignature struct { R, S *big.Int } func verifyMySig(pub *ecdsa.PublicKey, msg string, sig []byte) bool { //https://github.com/gtank/cryptopasta/blob/master/sign.go digest := sha1.Sum([]byte(msg)) var esig ecdsaSignature asn1.Unmarshal(sig, &esig) //we can use these prints to compare to what we had in Java... fmt.Printf("R: %d , S: %d", esig.R, esig.S) println() return ecdsa.Verify(pub, digest[:], esig.R, esig.S) }
The above version reads the actual ASN.1 encoded signature that is produced by the Java default signature encoding. To get the functionality matching the Java “SHA1withECDSA” algorithm, I first have to hash the input data with SHA1 as done here. Since the Java version is a bit of a black box with just that string definition, I spent a good moment wondering about that. I would guess the same approach would apply for other choices such as “SHA256withECDSA” by just replacing the hash function with another. Alternatively, I can also just pass in directly the R and S values of the signature:
func verifyMySig(pub *ecdsa.PublicKey, msg string, sig []byte) bool { //https://github.com/gtank/cryptopasta/blob/master/sign.go digest := sha1.Sum([]byte(msg)) var esig ecdsaSignature esig.R.SetString("89498588918986623250776516710529930937349633484023489594523498325650057801271", 0) esig.S.SetString("67852785826834317523806560409094108489491289922250506276160316152060290646810", 0) fmt.Printf("R: %d , S: %d", esig.R, esig.S) println() return ecdsa.Verify(pub, digest[:], esig.R, esig.S) }
So in the above, the R and S are actually set from numbers passed in. Which normally would be encoded more efficiently, and given as parameters. However, this works to demonstrate. The two long strings are the integers for the R and S I printed out in the Java version.
Strangely, printing the R and S using the ASN.1 and the direct passing of the numbers gives a different value for R and S. Which is a bit odd. But they both verify the signature fine. I read somewhere that some transformations can be done on the signature numbers while keeping it valid. Maybe this is done as part of the encoding or something? I have no idea. But it works. Much trust such crypto skills I have.
func TestSigning(t *testing.T) { xHexStr := "4bc55d002653ffdbb53666a2424d0a223117c626b19acef89eefe9b3a6cfd0eb" yHexStr := "d8308953748596536b37e4b10ab0d247f6ee50336a1c5f9dc13e3c1bb0435727" ePubKey = hexToPublicKey(xHexStr, yHexStr) sig := "3045022071f06054f450f808aa53294d34f76afd288a23749628cc58add828e8b8f2b742022100f82dcb51cc63b29f4f8b0b838c6546be228ba11a7c23dc102c6d9dcba11a8ff2" sigHex, _ := hex.DecodeString(sig) ok := verifyMySig(ePubKey, "This is string to sign", sigHex) println(ok) }
And finally, it works! Great 🙂
Great article! I would recommend switching from hex-encoding to base64 encoding of the publicKey and encoding of bytearrays in general as this is the more common thing to do when sending binary data over http/any format these days.
I would also recommend looking into the x509 package for serializing public keys 🙂