如何从比特币签名中获取ECDSA公钥?... SEC1 4.1.6曲线超过(mod p) - 场的关键恢复

goo*_*ate 24 c# python openssl bouncycastle elliptic-curve

更新: Git上提供的部分解决方案

编辑:这个的编译版本可以在https://github.com/makerofthings7/Bitcoin-MessageSignerVerifier上找到

请注意,要验证的邮件必须Bitcoin Signed Message:\n作为前缀. Source1 Source2

在C#实现中有一些错误,我可以从这个Python实现中纠正


实际上提出正确的Base 58地址似乎有问题.

我在下面有以下消息,签名和Base58地址.我打算从签名中提取密钥,散列该密钥,并比较Base58哈希值.

我的问题是:如何从签名中提取密钥?(编辑在这篇文章的底部发现了c ++代码,需要它在Bouncy Castle /或C#中)

信息

StackOverflow test 123
Run Code Online (Sandbox Code Playgroud)

签名

IB7XjSi9TdBbB3dVUK4+Uzqf2Pqk71XkZ5PUsVUN+2gnb3TaZWJwWW2jt0OjhHc4B++yYYRy1Lg2kl+WaiF+Xsc=
Run Code Online (Sandbox Code Playgroud)

Base58比特币地址"哈希"

1Kb76YK9a4mhrif766m321AMocNvzeQxqV
Run Code Online (Sandbox Code Playgroud)

由于Base58比特币地址只是一个哈希,我不能用它来验证比特币消息.但是,可以从签名中提取公钥.

编辑:我强调我是从签名本身派生公钥,而不是从Base58公钥哈希派生.如果我想(并且我确实想要),我应该能够将这些公钥位转换为Base58哈希.这样做我不需要帮助,我只需要帮助提取公钥位并验证签名.

  1. 在上面的签名中,此签名的格式是什么?PKCS10?(答案:不,这是专有的,如此处所述)

  2. 如何在Bouncy Castle中提取公钥?

  3. 验证签名的正确方法是什么?(假设我已经知道如何将公钥位转换为等于上面的比特币哈希的哈希)

之前的研究

此链接描述了如何使用ECDSA曲线,以下代码将允许我将公钥转换为BC对象,但我不确定如何Q从签名中获取点.

在下面的示例中,Q是硬编码值

  Org.BouncyCastle.Asn1.X9.X9ECParameters ecp = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1");
  ECDomainParameters params = new ECDomainParameters(ecp.Curve, ecp.G, ecp.N, ecp.H);
  ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
  ecp .curve.decodePoint(Hex.decode("045894609CCECF9A92533F630DE713A958E96C97CCB8F5ABB5A688A238DEED6DC2D9D0C94EBFB7D526BA6A61764175B99CB6011E2047F9F067293F57F5")), // Q
        params);
  PublicKey  pubKey = f.generatePublic(pubKeySpec);


 var signer = SignerUtilities.GetSigner("ECDSA"); // possibly similar to SHA-1withECDSA
 signer.Init(false, pubKey);
 signer.BlockUpdate(plainTextAsBytes, 0, plainTextAsBytes.Length);
 return signer.VerifySignature(signature);
Run Code Online (Sandbox Code Playgroud)

其他研究:

是验证消息的比特币来源.

在解码签名的Base64之后,调用RecoverCompact(消息的散列,签名).我不是C++程序员所以我假设我需要弄清楚它是如何key.Recover工作的.那或key.GetPubKey

这是我认为我需要在C#中使用的C++代码,理想情况下是在充气城堡......但我会采取任何有效的方法.

// reconstruct public key from a compact signature
// This is only slightly more CPU intensive than just verifying it.
// If this function succeeds, the recovered public key is guaranteed to be valid
// (the signature is a valid signature of the given data for that key)
bool Recover(const uint256 &hash, const unsigned char *p64, int rec)
{
    if (rec<0 || rec>=3)
        return false;
    ECDSA_SIG *sig = ECDSA_SIG_new();
    BN_bin2bn(&p64[0],  32, sig->r);
    BN_bin2bn(&p64[32], 32, sig->s);
    bool ret = ECDSA_SIG_recover_key_GFp(pkey, sig, (unsigned char*)&hash, sizeof(hash), rec, 0) == 1;
    ECDSA_SIG_free(sig);
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

... ECDSA_SIG_recover_key_GFp的代码在这里

比特币中的自定义签名格式

这个答案说有4种可能产生签名的公钥,并且这是在较新的签名中编码的.

Pet*_*man 9

在引用BitcoinJ之后,看起来这些代码示例中的一些缺少正确的消息准备,双SHA256散列以及输入到地址计算的恢复公共点的可能压缩编码.

以下代码应该只需要BouncyCastle(可能你需要github的最新版本,不确定).它从BitcoinJ借用了一些东西,并且只做足以让小例子工作,看到内容注释的消息大小限制.

它只计算RIPEMD-160哈希,我使用http://gobittest.appspot.com/Address检查结果的最终地址(不幸的是,该网站似乎不支持输入公钥的压缩编码).

    public static void CheckSignedMessage(string message, string sig64)
    {
        byte[] sigBytes = Convert.FromBase64String(sig64);
        byte[] msgBytes = FormatMessageForSigning(message);

        int first = (sigBytes[0] - 27);
        bool comp = (first & 4) != 0;
        int rec = first & 3;

        BigInteger[] sig = ParseSig(sigBytes, 1);
        byte[] msgHash = DigestUtilities.CalculateDigest("SHA-256", DigestUtilities.CalculateDigest("SHA-256", msgBytes));

        ECPoint Q = Recover(msgHash, sig, rec, true);

        byte[] qEnc = Q.GetEncoded(comp);
        Console.WriteLine("Q: " + Hex.ToHexString(qEnc));

        byte[] qHash = DigestUtilities.CalculateDigest("RIPEMD-160", DigestUtilities.CalculateDigest("SHA-256", qEnc));
        Console.WriteLine("RIPEMD-160(SHA-256(Q)): " + Hex.ToHexString(qHash));

        Console.WriteLine("Signature verified correctly: " + VerifySignature(Q, msgHash, sig));
    }

    public static BigInteger[] ParseSig(byte[] sigBytes, int sigOff)
    {
        BigInteger r = new BigInteger(1, sigBytes, sigOff, 32);
        BigInteger s = new BigInteger(1, sigBytes, sigOff + 32, 32);
        return new BigInteger[] { r, s };
    }

    public static ECPoint Recover(byte[] hash, BigInteger[] sig, int recid, bool check)
    {
        X9ECParameters x9 = SecNamedCurves.GetByName("secp256k1");

        BigInteger r = sig[0], s = sig[1];

        FpCurve curve = x9.Curve as FpCurve;
        BigInteger order = x9.N;

        BigInteger x = r;
        if ((recid & 2) != 0)
        {
            x = x.Add(order);
        }

        if (x.CompareTo(curve.Q) >= 0) throw new Exception("X too large");

        byte[] xEnc = X9IntegerConverter.IntegerToBytes(x, X9IntegerConverter.GetByteLength(curve));

        byte[] compEncoding = new byte[xEnc.Length + 1];
        compEncoding[0] = (byte)(0x02 + (recid & 1));
        xEnc.CopyTo(compEncoding, 1);
        ECPoint R = x9.Curve.DecodePoint(compEncoding);

        if (check)
        {
            //EC_POINT_mul(group, O, NULL, R, order, ctx))
            ECPoint O = R.Multiply(order);
            if (!O.IsInfinity) throw new Exception("Check failed");
        }

        BigInteger e = CalculateE(order, hash);

        BigInteger rInv = r.ModInverse(order);
        BigInteger srInv = s.Multiply(rInv).Mod(order);
        BigInteger erInv = e.Multiply(rInv).Mod(order);

        return ECAlgorithms.SumOfTwoMultiplies(R, srInv, x9.G.Negate(), erInv);
    }

    public static bool VerifySignature(ECPoint Q, byte[] hash, BigInteger[] sig)
    {
        X9ECParameters x9 = SecNamedCurves.GetByName("secp256k1");
        ECDomainParameters ec = new ECDomainParameters(x9.Curve, x9.G, x9.N, x9.H, x9.GetSeed());
        ECPublicKeyParameters publicKey = new ECPublicKeyParameters(Q, ec);
        return VerifySignature(publicKey, hash, sig);
    }

    public static bool VerifySignature(ECPublicKeyParameters publicKey, byte[] hash, BigInteger[] sig)
    {
        ECDsaSigner signer = new ECDsaSigner();
        signer.Init(false, publicKey);
        return signer.VerifySignature(hash, sig[0], sig[1]);
    }

    private static BigInteger CalculateE(
        BigInteger n,
        byte[] message)
    {
        int messageBitLength = message.Length * 8;
        BigInteger trunc = new BigInteger(1, message);

        if (n.BitLength < messageBitLength)
        {
            trunc = trunc.ShiftRight(messageBitLength - n.BitLength);
        }

        return trunc;
    }

    public static byte[] FormatMessageForSigning(String message)
    {
        MemoryStream bos = new MemoryStream();
        bos.WriteByte((byte)BITCOIN_SIGNED_MESSAGE_HEADER_BYTES.Length);
        bos.Write(BITCOIN_SIGNED_MESSAGE_HEADER_BYTES, 0, BITCOIN_SIGNED_MESSAGE_HEADER_BYTES.Length);
        byte[] messageBytes = Encoding.UTF8.GetBytes(message);

        //VarInt size = new VarInt(messageBytes.length);
        //bos.write(size.encode());
        // HACK only works for short messages (< 253 bytes)
        bos.WriteByte((byte)messageBytes.Length);

        bos.Write(messageBytes, 0, messageBytes.Length);
        return bos.ToArray();
    }
Run Code Online (Sandbox Code Playgroud)

问题中初始数据的示例输出:

Q: 0283437893b491218348bf5ff149325e47eb628ce36f73a1a927ae6cb6021c7ac4
RIPEMD-160(SHA-256(Q)): cbe57ebe20ad59518d14926f8ab47fecc984af49
Signature verified correctly: True
Run Code Online (Sandbox Code Playgroud)

如果我们将RIPEMD-160值插入地址检查器,它将返回

1Kb76YK9a4mhrif766m321AMocNvzeQxqV
Run Code Online (Sandbox Code Playgroud)

在问题中给出.


juh*_*ovh 7

恐怕您的样本数据存在一些问题。首先,您的示例 Q 长为 61 字节,但比特币公钥(使用 secp256k1 曲线)在未压缩形式下应为 65 字节。您提供的 Q 未正确验证该消息,但计算出的 QI 似乎确实验证了该消息。

\n\n

我编写了计算字符串“StackOverflow test 123”的正确公钥并使用 ECDsaSigner 进行验证的代码。但是,该公钥的哈希值1HRDe7G7tn925iNxQaeD7R2ZkZiKowN8NW不是1Kb76YK9a4mhrif766m321AMocNvzeQxqV

\n\n

您能否验证您的数据是否正确,并可能给出消息字符串的准确哈希值,以便我们尝试调试,不正确的哈希值可能会使事情变得非常糟糕。我使用的代码如下:

\n\n
using System;\nusing System.Text;\nusing System.Security.Cryptography;\n\nusing Org.BouncyCastle.Math;\nusing Org.BouncyCastle.Math.EC;\nusing Org.BouncyCastle.Asn1.X9;\nusing Org.BouncyCastle.Crypto.Signers;\nusing Org.BouncyCastle.Crypto.Parameters;\nusing Org.BouncyCastle.Utilities.Encoders;\n\npublic class Bitcoin\n{\n  public static ECPoint Recover(byte[] hash, byte[] sigBytes, int rec)\n  {\n    BigInteger r = new BigInteger(1, sigBytes, 0, 32);\n    BigInteger s = new BigInteger(1, sigBytes, 32, 32);\n    BigInteger[] sig = new BigInteger[]{ r, s };\n    ECPoint Q = ECDSA_SIG_recover_key_GFp(sig, hash, rec, true);\n    return Q;\n  }\n\n  public static ECPoint ECDSA_SIG_recover_key_GFp(BigInteger[] sig, byte[] hash, int recid, bool check)\n  {\n    X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1");\n    int i = recid / 2;\n\n    Console.WriteLine("r: "+ToHex(sig[0].ToByteArrayUnsigned()));\n    Console.WriteLine("s: "+ToHex(sig[1].ToByteArrayUnsigned()));\n\n    BigInteger order = ecParams.N;\n    BigInteger field = (ecParams.Curve as FpCurve).Q;\n    BigInteger x = order.Multiply(new BigInteger(i.ToString())).Add(sig[0]);\n    if (x.CompareTo(field) >= 0) throw new Exception("X too large");\n\n    Console.WriteLine("Order: "+ToHex(order.ToByteArrayUnsigned()));\n    Console.WriteLine("Field: "+ToHex(field.ToByteArrayUnsigned()));\n\n    byte[] compressedPoint = new Byte[x.ToByteArrayUnsigned().Length+1];\n    compressedPoint[0] = (byte) (0x02+(recid%2));\n    Buffer.BlockCopy(x.ToByteArrayUnsigned(), 0, compressedPoint, 1, compressedPoint.Length-1);\n    ECPoint R = ecParams.Curve.DecodePoint(compressedPoint);\n\n    Console.WriteLine("R: "+ToHex(R.GetEncoded()));\n\n    if (check)\n    {\n      ECPoint O = R.Multiply(order);\n      if (!O.IsInfinity) throw new Exception("Check failed");\n    }\n\n    int n = (ecParams.Curve as FpCurve).Q.ToByteArrayUnsigned().Length*8;\n    BigInteger e = new BigInteger(1, hash);\n    if (8*hash.Length > n)\n    {\n      e = e.ShiftRight(8-(n & 7));\n    }\n    e = BigInteger.Zero.Subtract(e).Mod(order);\n    BigInteger rr = sig[0].ModInverse(order);\n    BigInteger sor = sig[1].Multiply(rr).Mod(order);\n    BigInteger eor = e.Multiply(rr).Mod(order);\n    ECPoint Q = ecParams.G.Multiply(eor).Add(R.Multiply(sor));\n\n    Console.WriteLine("n: "+n);\n    Console.WriteLine("e: "+ToHex(e.ToByteArrayUnsigned()));\n    Console.WriteLine("rr: "+ToHex(rr.ToByteArrayUnsigned()));\n    Console.WriteLine("sor: "+ToHex(sor.ToByteArrayUnsigned()));\n    Console.WriteLine("eor: "+ToHex(eor.ToByteArrayUnsigned()));\n    Console.WriteLine("Q: "+ToHex(Q.GetEncoded()));\n\n    return Q;\n  }\n\n  public static bool VerifySignature(byte[] pubkey, byte[] hash, byte[] sigBytes)\n  {\n    X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1");\n    ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve,\n                                                                 ecParams.G, ecParams.N, ecParams.H,\n                                                                 ecParams.GetSeed());\n\n    BigInteger r = new BigInteger(1, sigBytes, 0, 32);\n    BigInteger s = new BigInteger(1, sigBytes, 32, 32);\n    ECPublicKeyParameters publicKey = new ECPublicKeyParameters(ecParams.Curve.DecodePoint(pubkey), domainParameters);\n\n    ECDsaSigner signer = new ECDsaSigner();\n    signer.Init(false, publicKey);\n    return signer.VerifySignature(hash, r, s);\n  }\n\n\n\n  public static void Main()\n  {\n    string msg = "StackOverflow test 123";\n    string sig = "IB7XjSi9TdBbB3dVUK4+Uzqf2Pqk71XkZ5PUsVUN+2gnb3TaZWJwWW2jt0OjhHc4B++yYYRy1Lg2kl+WaiF+Xsc=";\n    string pubkey = "045894609CCECF9A92533F630DE713A958E96C97CCB8F5ABB5A688A238DEED6DC2D9D0C94EBFB7D526BA6A61764175B99CB6011E2047F9F067293F57F5";\n\n    SHA256Managed sha256 = new SHA256Managed();\n    byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(msg), 0, Encoding.UTF8.GetByteCount(msg));\n    Console.WriteLine("Hash: "+ToHex(hash));\n\n    byte[] tmpBytes = Convert.FromBase64String(sig);\n    byte[] sigBytes = new byte[tmpBytes.Length-1];\n    Buffer.BlockCopy(tmpBytes, 1, sigBytes, 0, sigBytes.Length);\n\n    int rec = (tmpBytes[0] - 27) & ~4;\n    Console.WriteLine("Rec {0}", rec);\n\n    ECPoint Q = Recover(hash, sigBytes, rec);\n    string qstr = ToHex(Q.GetEncoded());\n    Console.WriteLine("Q is same as supplied: "+qstr.Equals(pubkey));\n\n    Console.WriteLine("Signature verified correctly: "+VerifySignature(Q.GetEncoded(), hash, sigBytes));\n  }\n\n  public static string ToHex(byte[] data)\n  {\n    return BitConverter.ToString(data).Replace("-","");\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

编辑\n我发现这仍然没有评论或接受,所以我编写了一个完整的测试来生成私钥和公钥,然后使用私钥生成有效的签名。之后,它从签名和哈希中恢复公钥,并使用该公钥来验证消息的签名。请参阅下文,如果仍有问题请告诉我。

\n\n
  public static void FullSignatureTest(byte[] hash)\n  {\n    X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1");\n    ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve,\n                                                                 ecParams.G, ecParams.N, ecParams.H,\n                                                                 ecParams.GetSeed());\n    ECKeyGenerationParameters keyGenParams =\n      new ECKeyGenerationParameters(domainParameters, new SecureRandom());\n\n    AsymmetricCipherKeyPair keyPair;\n    ECKeyPairGenerator generator = new ECKeyPairGenerator();\n    generator.Init(keyGenParams);\n    keyPair = generator.GenerateKeyPair();\n\n    ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) keyPair.Private;\n    ECPublicKeyParameters publicKey = (ECPublicKeyParameters) keyPair.Public;\n\n    Console.WriteLine("Generated private key: " + ToHex(privateKey.D.ToByteArrayUnsigned()));\n    Console.WriteLine("Generated public key: " + ToHex(publicKey.Q.GetEncoded()));\n\n    ECDsaSigner signer = new ECDsaSigner();\n    signer.Init(true, privateKey);\n    BigInteger[] sig = signer.GenerateSignature(hash);\n\n    int recid = -1;\n    for (int rec=0; rec<4; rec++) {\n      try\n      {\n        ECPoint Q = ECDSA_SIG_recover_key_GFp(sig, hash, rec, true);\n        if (ToHex(publicKey.Q.GetEncoded()).Equals(ToHex(Q.GetEncoded())))\n        {\n          recid = rec;\n          break;\n        }\n      }\n      catch (Exception)\n      {\n        continue;\n      }\n    }\n    if (recid < 0) throw new Exception("Did not find proper recid");\n\n    byte[]\xc2\xa0fullSigBytes = new byte[65];\n    fullSigBytes[0] = (byte) (27+recid);\n    Buffer.BlockCopy(sig[0].ToByteArrayUnsigned(), 0, fullSigBytes, 1, 32);\n    Buffer.BlockCopy(sig[1].ToByteArrayUnsigned(), 0, fullSigBytes, 33, 32);\n\n    Console.WriteLine("Generated full signature: " + Convert.ToBase64String(fullSigBytes));\n\n    byte[]\xc2\xa0sigBytes = new byte[64];\n    Buffer.BlockCopy(sig[0].ToByteArrayUnsigned(), 0, sigBytes, 0, 32);\n    Buffer.BlockCopy(sig[1].ToByteArrayUnsigned(), 0, sigBytes, 32, 32);\n\n    ECPoint genQ = ECDSA_SIG_recover_key_GFp(sig, hash, recid, false);\n    Console.WriteLine("Generated signature verifies: " + VerifySignature(genQ.GetEncoded(), hash, sigBytes));\n  }\n
Run Code Online (Sandbox Code Playgroud)\n