gpt4 book ai didi

c# - 如何仅从比特币签名中获取 ECDSA 公钥? ... SEC1 4.1.6 (mod p) 域上曲线的 key 恢复

转载 作者:IT老高 更新时间:2023-10-28 20:22:59 27 4
gpt4 key购买 nike

Update: Partial solution available on Git

编辑:此版本的编译版本可在 https://github.com/makerofthings7/Bitcoin-MessageSignerVerifier 获得。

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

C# 实现中有一些错误,我可能可以从 this Python implementation 进行更正


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

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

我的问题是:如何从签名中提取 key ? (编辑 I found the c++ code 在这篇文章的底部,在 Bouncy CaSTLe/或 C# 中需要它)

留言

StackOverflow test 123

签名

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

Base58 比特币地址“哈希”

1Kb76YK9a4mhrif766m321AMocNvzeQxqV

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

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

问题

  1. 在上面的签名中,这个签名是什么格式的? PKCS10? (答案:不,它是专有的 as described here )

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

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

先前的研究

This link描述了如何使用 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);

其他研究:

THIS是验证消息的比特币来源。

解码签名的Base64后,RecoverCompact(hash of message, signature)叫做。我不是 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;
}

... ECDSA_SIG_recover_key_GFp is here 的代码

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

This answer says there are 4 possible可以产生签名的公钥,并在较新的签名中进行编码。

最佳答案

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

以下代码应该只需要 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();
}

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

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

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

1Kb76YK9a4mhrif766m321AMocNvzeQxqV

如问题中所述。

关于c# - 如何仅从比特币签名中获取 ECDSA 公钥? ... SEC1 4.1.6 (mod p) 域上曲线的 key 恢复,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19665491/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com