使用带有曲线 secp224k1 的私钥签署 ECDSA

Posted

技术标签:

【中文标题】使用带有曲线 secp224k1 的私钥签署 ECDSA【英文标题】:Signing ECDSA with private key with curve secp224k1 【发布时间】:2019-10-17 05:47:11 【问题描述】:

我想得到 ECDSA secp224k1 的签名。我无法从手册中获得与 CoinFLEX Authentication Process 示例中相同的签名。我正在使用 C# BouncyCastle。

为什么我的代码无法获取手册页的签名?

// manual page's step 7
byte[] fortyByteMessage  = fromHexStringToByteArr("0x00000000 00000001").Concat(fromHexStringToByteArr("0x6b347302 2e6b9b5a f2fe5d1d ae7cf5bf")).Concat(fromHexStringToByteArr("0xf08c98ca f1fd82e8 cea9825d bff04fd0")).ToArray();

// manual page's step 9
byte[] privateKey = fromHexStringToByteArr("0xb89ea7fc d22cc059 c2673dc2 4ff40b97 83074646 86560d0a d7561b83");

// manual page's step 10
X9ECParameters spec = ECNamedCurveTable.GetByName("secp224k1");
ECDomainParameters domain = new ECDomainParameters(spec.Curve, spec.G, spec.N);
ECDsaSigner signer = new ECDsaSigner(new HMacDsaKCalculator(new Sha224Digest()));
signer.Init(true, new ECPrivateKeyParameters(new BigInteger(privateKey), domain));
BigInteger[] signature = signer.GenerateSignature(fortyByteMessage);
byte[] r = signature[0].ToByteArray().SkipWhile(b => b == 0x00).Reverse().ToArray(); // (r) r should be 0x3fb77a9d 7b5b2a68 209e76f6 872078c5 791340d5 989854ad a3ab735e, but not.

Console.WriteLine(BitConverter.ToString(r).Replace("-", string.Empty).ToLower());

预期的 byteArr(第 10 步 r 值):

r = 0x3fb77a9d 7b5b2a68 209e76f6 872078c5 791340d5 989854ad a3ab735e<br>

我的 byteArr (这是错误的值,因为它与 step 10 r 值不同)

r = 0x1e3b3f4f 7401ff9d 827b7222 47823919 452d3adb effa7aa4 52a0879e<br>

另一个功能:

static byte[] fromHexStringToByteArr(string paramHexString)

    string hexString = paramHexString.Substring(2).Replace(" ", "");
    byte[] result = new byte[hexString.Length / 2];

    int cur = 0;

    for (int i = 0; i < hexString.Length; i = i + 2)
    
        string w = hexString.Substring(i, 2);
        result[cur] = Convert.ToByte(w, 16);
        cur++;
    
    return result;

【问题讨论】:

【参考方案1】:

根据instructions 的第 10 步,应该签名的不是 40 字节消息,而是该消息的 SHA224-hash:客户端签署 40 的 28 字节 SHA-224 摘要-字节消息...。请注意,GenerateSignature 方法不会自动对数据进行哈希处理,即必须显式地对数据进行哈希处理,另请参阅here 和这些examples。

Org.BouncyCastle.Math.BigInteger.ToByteArray-方法(在 C# 代码中使用)以大端格式输出字节数组(与 .Net 不同,其System.Numerics.BigInteger.ToByteArray-方法使用小端格式)。因此,不需要反转字节顺序(使用Reverse-方法)。

通过这些修改,签名是:

  r = 0x1781ff4997b48d389f518df75001c4b6564082956228d74dd0321656
  s = 0x0aadc68cf78dc75d44fb300f200465e72a70826ec2d5577d49b62e59

然而,它仍然与说明中显示的签名不同。

在 C# 代码中,ECDsaSigner-instance 使用 HMacDsaKCalculator-instance 创建,基于 RFC6979 生成 确定性 签名。当使用ECDSA 创建签名时,k 参数是随机选择的,用于非确定性 ECDSA,而在确定性变体中,它是根据特定算法从消息和私钥创建的(在 RFC6979 中描述) ,见here。因此,确定性变体为相同的消息和私钥生成相同的签名,而非确定性变体生成不同的签名。

可能签名之间的差异是由 CoinFLEX 使用非确定性变体引起的。不幸的是,这些说明没有详细说明所使用的 ECDSA 程序。

更新:

两种变体,确定性和非确定性,都提供有效 ECDSA 签名!在确定性变体(RFC6979 来自 2013 年 8 月)之前,只有非确定性变体,请参阅 here。

我在 Linux (Debian) 机器上安装并测试了 sign_secp224k1-tool。正如怀疑的那样,该工具为 same 私钥和 same 消息生成 不同 签名,显然使用了非确定性变体。这也可以从源代码中轻松验证:签名使用ecp_sign-方法计算,该方法随机使用/dev/urandom 确定k-值。

因此很明显,C# 代码使用确定性变体生成的签名通常无法匹配sign_secp224k1-工具使用非确定性变体生成的签名。

【讨论】:

这是一个用于认证的签名。所以我认为这不是随机的。以下网址是规范,但我很难阅读。 github.com/coinflex-exchange/libecp#sign_secp224k1 非确定性变体生成 有效 ECDSA 签名,就像确定性变体一样!我还测试了sign_secp224k1-tool,确实它使用了 non-deterministic 变体,请参阅我的答案中的更新部分。 当我运行上面提供的相同代码时,我得到异常 System.ArgumentException: 'Scalar is not in the interval [1, n - 1] Parameter name: d' mecause my value of new BigInteger(privateKey)是-7517217624485429887806670580524002717199604937162964340004446921853 你是怎么跑的? new BigInteger(1, privateKey) 修复了我的崩溃问题,但 api 仍然返回“您发送的签名不正确”。错误。 kyounoii,你设法让它运行了吗? @LukAss741 - 您应该在 SO 上发布一个新问题,准确描述您的问题以及解决问题所需的所有信息。如果需要,您可以在此处参考此问题/答案。

以上是关于使用带有曲线 secp224k1 的私钥签署 ECDSA的主要内容,如果未能解决你的问题,请参考以下文章

生成 256 位 ECDSA 私钥

Secp256k1 曲线签名接近预期签名但缺少 2 段

如何从 32 字节原始私钥中获取 java.security.PrivateKey ? (Secp256k1 算法)

密码学系列 - 国密SM2为什么不支持恢复公钥

密码学系列 - 国密SM2为什么不支持恢复公钥

如何使用椭圆曲线私钥和 ECDSA 算法签署证书?