Search code examples
typescriptgocryptographybitcoin

how do I properly sign a bitclout tx in golang vs typescript?


I have a working example in typescript:

  signTransaction(seedHex: string, transactionHex: string): string {
    const privateKey = this.cryptoService.seedHexToPrivateKey(seedHex);

    const transactionBytes = new Buffer(transactionHex, 'hex');
    const transactionHash = new Buffer(sha256.x2(transactionBytes), 'hex');
    const signature = privateKey.sign(transactionHash);
    const signatureBytes = new Buffer(signature.toDER());
    const signatureLength = uvarint64ToBuf(signatureBytes.length);

    const signedTransactionBytes = Buffer.concat([
      // This slice is bad. We need to remove the existing signature length field prior to appending the new one.
      // Once we have frontend transaction construction we won't need to do this.
      transactionBytes.slice(0, -1),
      signatureLength,
      signatureBytes,
    ]);

    return signedTransactionBytes.toString('hex');
  }

and converting to golang I wrote:

func signTransaction(seedHex, transactionHex string) string {
  privateKey := seedHexToPrivateKey(seedHex)
  transactionBytes, _ := hex.DecodeString(hexString)
  first := sha256.Sum256(transactionBytes)
  transactionHash := fmt.Sprintf("%x", sha256.Sum256(first[:]))

  signature, _ := privateKey.Sign([]byte(transactionHash))
  signatureBytes := signature.Serialize()

  signatureLength := make([]byte, 8)
  binary.LittleEndian.PutUint64(signatureLength, uint64(len(signatureBytes)))

  buff := []byte{}
  buff = append(buff, transactionBytes[0:len(transactionBytes)-1]...)
  buff = append(buff, signatureLength...)
  buff = append(buff, signatureBytes...)

  return fmt.Sprintf("%x", buff)
}

It's very close I think. I get back all sorts of interesting error messages when submitting and trying to adjust stuff. But it's not working like the typescript one is. Anyone see any errors? FYI privateKey is *btcec.PrivateKey which is ecdsa.PrivateKey and the signture Serialized is from https://github.com/btcsuite/btcd/blob/master/btcec/signature.go#L40


Solution

  • That signature error is because the signature is X9.62 which uses ASN.1 to encode both value R and S. If the random value R or the signature S starts with a 1 bit it will be interpreted as a negative value according to the BER/DER encoding of an ASN.1 INTEGER.

    As R is randomized and S depends on it they will sometimes error when the integer is not encoded correctly. If the first / leftmost and most significant bit is 1 then a 00 byte should be prefixed to it, so it makes a positive signed big endian value.

    Note that the size of the encoded integer value is therefore dynamic as well, it is incorrect to use too many bytes for it.


    To go from a statically sized, big endian R and S value you should first remove all 00 valued bytes from the left hand side, and then add one back again if the first byte value is 0x80 or higher (assuming unsigned bytes, obviously). This function is commonly referred to as O2ISP (octet-string to integer primitive), taken from the RSA PKCS#1 specifications where it is mathematically described. Here on Cryptography is a helpful qustion that is a bit more in depth