如何仅从比特币签名中获得ECDSA公钥? ... SEC1 4.1.6密钥恢复,用于(mod p)字段上的曲线 [英] How do I get an ECDSA public key from just a Bitcoin signature? ... SEC1 4.1.6 key recovery for curves over (mod p)-fields

查看:325
本文介绍了如何仅从比特币签名中获得ECDSA公钥? ... SEC1 4.1.6密钥恢复,用于(mod p)字段上的曲线的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Update: Partial solution available on Git

可以在 https://github.com/makerofthings7/Bitcoin上获得其编译版本. -MessageSignerVerifier

请注意,要验证的消息必须以Bitcoin Signed Message:\n作为前缀. Source1

Please note that the message to be verified must have Bitcoin Signed Message:\n as a prefix. Source1 Source2

我可能可以从此Python实现

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

It seems to have a problem with actually coming up with the correct Base 58 address.

我在下面有以下消息,签名和Base58地址.我打算从签名中提取密钥,对该密钥进行哈希处理,然后比较Base58哈希值.

I have the following message, signature, and Base58 address below. I intend to extract the key from the signature, hash that key, and compare the Base58 hashes.

我的问题是:如何从签名中提取密钥? (编辑我在底部找到了c ++代码的帖子,需要在Bouncy Castle中/或C#)

My problem is: How do I extract the key from the signature? (Edit I found the c++ code at the bottom of this post, need it in Bouncy Castle / or C#)

消息

StackOverflow test 123

签名

IB7XjSi9TdBbB3dVUK4+Uzqf2Pqk71XkZ5PUsVUN+2gnb3TaZWJwWW2jt0OjhHc4B++yYYRy1Lg2kl+WaiF+Xsc=

Base58比特币地址哈希"

1Kb76YK9a4mhrif766m321AMocNvzeQxqV

由于Base58比特币地址只是一个哈希,因此我无法将其用于验证比特币消息.但是,可以从签名中提取公钥.

Since the Base58 Bitcoin address is just a hash, I can't use it for validation of a Bitcoin message. However, it is possible to extract the public key from a signature.

我要强调的是,我是从签名本身而不是从Base58公共密钥哈希派生出公共密钥.如果我想要(并且实际上确实想要),我应该能够将这些公钥位转换为Base58哈希.我不需要这样做,只需要提取公共密钥位并验证签名即可.

I'm emphasizing that I'm deriving the Public key from the signature itself, and not from the Base58 public key hash. If I want to (and I actually do want to) I should be able to convert these public key bits into the Base58 hash. I don't need assistance in doing this, I just need help in extracting the public key bits and verifying the signature.

问题

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

如何在Bouncy Castle中提取公钥?

how do I extract the public key in Bouncy Castle?

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

What is the correct way to verify the signature? (assume that I already know how to convert the Public Key bits into a hash that equals the Bitcoin hash above)

先前的研究

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

This link describes how to use ECDSA curves, and the following code will allow me to convert a public key into a BC object, but I'm unsure on how to get the point Q from the signature.

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

In the sample below Q is the hard coded value

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


其他研究:

THIS 是用于验证的比特币来源一条消息.

THIS is the Bitcoin source that verifies a message.

解码签名的Base64后, RecoverCompact(邮件的哈希,签名)被调用.我不是C ++程序员,所以我假设我需要弄清楚key.Recover的工作方式.那个key.GetPubKey

After decoding the Base64 of the signature, the RecoverCompact(hash of message, signature) is called. I'm not a C++ programmer so I'm assuming I need to figure out how key.Recover works. That or key.GetPubKey

这是我认为在C#中需要的C ++代码,理想情况下在充气城堡中...但是我会采取一切可行的措施.

This is the C++ code that I think I need in C#, ideally in bouncy castle... but I'll take anything that works.

// 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;
}

... ECDSA_SIG_recover_key_GFp的代码在这里

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

此答案表示有4种可能的公钥可以产生签名,并且该公钥被编码在较新的签名.

This answer says there are 4 possible public keys that can produce a signature, and this is encoded in the newer signatures.

推荐答案

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

After referencing BitcoinJ, it appears some of these code samples are missing proper preparation of the message, double-SHA256 hashing, and possible compressed encoding of the recovered public point that is input to the address calculation.

以下代码只需要BouncyCastle(可能不确定,您需要github的最新版本).它从BitcoinJ借用了一些东西,并且做了足够的工作来使小的示例正常工作,请参阅内联注释以了解消息大小限制.

The following code should only need BouncyCastle (probably you'll need recent version from github, not sure). It borrows a few things from BitcoinJ, and does just does enough to get small examples working, see inline comments for message size restrictions.

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

It only calculates up to the RIPEMD-160 hash, and I used http://gobittest.appspot.com/Address to check the final address that results (unfortunately that website doesn't seem to support entering a compressed encoding for the public key).

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

问题中初始数据的样本输出:

Sample output for the initial data in the question:

Q: 0283437893b491218348bf5ff149325e47eb628ce36f73a1a927ae6cb6021c7ac4
RIPEMD-160(SHA-256(Q)): cbe57ebe20ad59518d14926f8ab47fecc984af49
Signature verified correctly: True

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

If we plug the RIPEMD-160 value into the address checker, it returns

1Kb76YK9a4mhrif766m321AMocNvzeQxqV

根据问题给出.

这篇关于如何仅从比特币签名中获得ECDSA公钥? ... SEC1 4.1.6密钥恢复,用于(mod p)字段上的曲线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆