我如何在 golang 与 typescript 中正确签署 bitclout tx?

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

我在 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');
  }

并转换为 golang 我写道:

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)
}

我觉得很接近了。在提交和尝试调整内容时,我收到了各种有趣的错误消息。但它不像打字稿那样工作。有人看到任何错误吗?仅供参考,私钥是 *btcec.PrivateKey,即 ecdsa.PrivateKey,签名序列化来自 https://github.com/btcsuite/btcd/blob/master/btcec/signature.go#L40

找到解决方案:

https://github.com/indutny/elliptic/blob/master/lib/elliptic/ec/signature.js#L139

^ toDER 是正确的并且这个:

https://github.com/btcsuite/btcd/blob/master/btcec/signature.go#L40

不是现在我有:

https://github.com/andrewarrow/clout-cli/blob/main/keys/hdkey.go#L48

我从 JavaScript 移植过来,它正在运行!

该签名错误是因为签名是 X9.62,它使用 ASN.1 对值 R 和 S 进行编码。如果随机值 R 或签名 S 以 1 位开头,它将被解释为负数根据 ASN.1 INTEGER 的 BER/DER 编码的值。

由于 R 是随机的并且 S 依赖于它,因此当整数编码不正确时,它们有时会出错。如果第一个/最左边和最高有效位为 1,则应在其前面添加一个 00 字节作为前缀,因此它会产生一个带正符号的大端值。

请注意,编码整数值的大小因此也是动态的,使用太多字节是不正确的。


要从静态大小的大端 R 和 S 值出发,您应该首先从左侧删除所有 00 值字节,然后如果第一个字节值为 0x80 或更高(显然假设无符号字节)。此函数通常称为 O2ISP(八位字节串到整数原语),取自 RSA PKCS#1 规范,其中对其进行了数学描述。 Here on Cryptography is a helpful qustion that is a bit more in depth