通过加密的比特币 ECDSA 签名。sign/4 产生了错误的签名(不兼容 RFC6979)

Bitcoin ECDSA signing via crypto.sign/4 is producing wrong signature (not RFC6979 compatible)

我正在尝试对比特币交易进行签名,部分过程是使用 sha256 和私钥对其进行签名。

这是我使用的 Elixir/Erlang 代码:

signed_data = :crypto.sign(:ecdsa, :sha256, data, [prk, :secp256k1]) |> Base.encode16

产生以下字符串:

3046022100AFF0CCC46F08C1A2D304483C0D9B97348EEEB7D415E394A512B87A73CB69AA660221009DB8FD52692FD0ADF16F0BB8EA870C9424F1BCD22EB6755388883DE62FBC01BD

问题是这个签名在我用于 BlockCypher 的参考实现上失败了。

他们在示例中提供的签名是这样的:

3044022045734b7593ed805dd95d3dfb86658afdb647b6693a740ffc9b2aa3d37cc6c06e0220324e5024acc51550e287564c308e280b8978305198d49cb2df8d07e7822b2563

所以对于相同的 data/privateKey 输入参数,我的签名长了 2 个字节。我在K部分多了一个字节,在S部分多了1个字节。

我怀疑 Erlang 加密库没有遵循比特币期望的 RFC6979。

这是一个 link 有效的签名者实现:https://github.com/blockcypher/btcutils/tree/master/signer

问题是:有没有办法让 Erlang 加密为此工作,或者有没有我应该使用的替代 Erlang/Elixir 库。

我能找到的最接近的是:https://github.com/trustatom-oss/erlang-secp256k1

它不是原生的 Erlang 代码,而是使用 NIF 和纯 C。但总比没有好。

将它留在这里以防万一有人执行本机 Erlang/Elixir 实现。如果你这样做,请 ping 我。

ECDSA 签名描述了椭圆曲线内的一个点。在比特币的例子中,曲线是 secp256k1。对于实数,这样的曲线看起来或多或少是这样的:

曲线内的点由两个值或 "coordinates" 明确指定:R 和 S。对于单个 R 值,有两个可能的有效 S 值。

2015 年 10 月,有人开始利用这种双重性,一次又一次地操纵已经广播的交易,以淹没比特币网络并导致其故障。

由于这种可延展性的潜在风险,Blockcypher 和比特币领域的其他著名成员 immediately decided to embrace 到 BIP-0062。

BIP-0062 强制执行 "low S values"。这意味着必须丢弃包含 S 分量大于特定值的签名的交易。这样的最高值是:

0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0

如果您想确保您的交易始终包含有效的 S 值,您需要检查生成的 S 值是否大于最高值。如果是这样,只需将当前的S值删除为以下值:

0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

这是在 Elixir 中进行此类转换的函数示例。

def enforce_low_s sig do
  len  = sig |> :binary.at(1)
  rlen = sig |> :binary.at(3)
  r    = sig |> :binary.part(4, rlen)
  slen = sig |> :binary.at(rlen + 5)
  s    = sig |> :binary.part(rlen + 6, slen)
  sint = s   |> :binary.decode_unsigned
  if sint > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 do
    s = (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - sint) |> :binary.encode_unsigned
    slen = s |> :binary.bin_to_list |> Enum.count
    len = rlen + slen + 4
  end
  <<48, len, 2>> <> <<rlen>> <> r <> <<2, slen>> <> s
end

因此,完整的签名过程如下所示:

priv_key = "yourhexprivatekey"
    |> Base.decode16!
msg = "Lorem Ipsum dolor sit amet..."
sig = :crypto.sign(:sha256, msg, [priv_key, :secp256k1])
    |> enforce_low_s
    |> Base.encode16