Comment puis-je obtenir une clé publique ECDSA à partir d'une simple signature Bitcoin? ... SEC1 4.1.6 récupération des clés pour les courbes sur (mod p)-champs
mise à jour: solution partielle disponible sur Git
modifier: une version compilée de ce est disponible à https://github.com/makerofthings7/Bitcoin-MessageSignerVerifier
veuillez noter que le message à vérifier doit avoir Bitcoin Signed Message:n
comme préfixe. Source1 Source2
il y a quelque chose qui ne va pas dans l'implémentation C# que je peux probablement corriger à partir de cette implémentation Python
il semble avoir un problème avec réellement venir avec la bonne adresse de base 58.
j'ai le message, la signature et l'adresse de Base58 suivants ci-dessous. J'ai l'intention d'extraire la clé de la signature, de la hachurer et de comparer les hachures de Base58.
mon problème est: comment extraire la clé de la signature? (Edit j'ai trouvé le code c++ au bas de ce post, en ont besoin dans le Château Gonflable / ou C#)
Message
StackOverflow test 123
Signature
IB7XjSi9TdBbB3dVUK4+Uzqf2Pqk71XkZ5PUsVUN+2gnb3TaZWJwWW2jt0OjhHc4B++yYYRy1Lg2kl+WaiF+Xsc=
Base58 adresse Bitcoin "hash"
1Kb76YK9a4mhrif766m321AMocNvzeQxqV
depuis le Base58 Bitcoin address est juste un hachage, Je ne peux pas l'utiliser pour la validation d'un message Bitcoin. Toutefois, il est possible d'extraire la clé publique d'une signature .
Edit: j'insiste sur le fait que je tire la clé publique de la signature elle-même, et non du hachage de la clé publique Base58. Si je le veux (et je le veux en fait), je devrais pouvoir convertir ces bits de clés publiques en hash Base58. Je n'ai pas besoin pour ce faire, j'ai juste besoin d'aide pour extraire les bits de clé publique et vérifier la signature.
Question
-
dans la Signature ci-dessus, Quel est le format de cette signature? PKCS10? (Réponse: non, il est propriétaire comme décrit ici )
-
comment extraire la clé publique du château gonflable?
-
Quelle est la bonne façon de vérifier la signature? (supposons que je sais déjà comment convertir les bits de la clé publique en un hachage qui égale le hachage Bitcoin ci-dessus)
recherches préalables
ce lien décrit comment utiliser les courbes ECDSA, et le code suivant me permettra de convertir une clé publique en un objet BC, mais je ne suis pas sûr sur la façon d'obtenir le point Q
à partir de la signature.
dans L'échantillon ci-dessous Q est la valeur codée
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);
autres recherches:
CE est le Bitcoin source qui vérifie un message.
après décodage de la Base64 de la signature, le RecoverCompact(hachage du message, signature) s'appelle. Je ne suis pas un programmeur C++, donc je suppose que je dois comprendre comment fonctionne key.Recover
. Cela ou key.GetPubKey
C'est le code C++ dont j'ai besoin en C#, idéalement au château gonflable... mais je prendrai tout ce qui marche.
// 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;
}
... le code pour ECDSA_SIG_recover_key_GFp est ici
format de signature personnalisé en Bitcoin
cette réponse dit qu'il y a 4 possibles clés publiques qui peuvent produire une signature, et ceci est encodé dans les signatures plus récentes.
2 réponses
après avoir référencé BitcoinJ, il semble que certains de ces échantillons de code manquent la bonne préparation du message, le hachage double-SHA256, et l'encodage comprimé possible du point public récupéré qui est entré dans le calcul d'adresse.
le code suivant ne devrait avoir besoin que de BouncyCastle (vous aurez probablement besoin d'une version récente de github, pas sûr). Il emprunte quelques choses à BitcoinJ, et fait juste assez pour obtenir de petits exemples de travail, voir en ligne commentaires pour les restrictions de taille de message.
il calcule seulement jusqu'à la RIPEMD-160 hash, et j'ai utilisé http://gobittest.appspot.com/Address pour vérifier l'adresse finale qui en résulte (malheureusement ce site ne semble pas supporter d'entrer un encodage comprimé pour la clé publique).
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();
}
sortie D'échantillon pour les données initiales dans la question:
Q: 0283437893b491218348bf5ff149325e47eb628ce36f73a1a927ae6cb6021c7ac4 RIPEMD-160(SHA-256(Q)): cbe57ebe20ad59518d14926f8ab47fecc984af49 Signature verified correctly: True
Si on branche la valeur RIPEMD-160 dans le vérificateur d'adresses, il retourne
1Kb76YK9a4mhrif766m321AMocNvzeQxqV
, comme indiqué dans la question.
j'ai bien peur qu'il y ait des problèmes avec vos données d'échantillon. Tout d'abord votre exemple Q est de 61 octets de long, mais les clés publiques Bitcoin (en utilisant la courbe secp256k1) devraient être de 65 octets dans leur forme non compressée. Le Q Que vous avez fourni ne vérifie pas le message correctement, mais le Q Que j'ai calculé semble le vérifier.
j'ai écrit un code qui calcule la clé publique correcte pour la chaîne" StackOverflow test 123 " et le vérifie en utilisant ECDsaSigner. Toutefois, la valeur de hachage pour ce la clé publique est 1HRDe7G7tn925iNxQaeD7R2ZkZiKowN8NW
au lieu de 1Kb76YK9a4mhrif766m321AMocNvzeQxqV
.
pouvez-vous s'il vous plaît vérifier que vos données sont correctes et peut-être donner le hachage exact de la chaîne de message afin que nous puissions essayer de déboguer, un hachage incorrect peut tout gâcher. Le code que j'ai utilisé est le suivant:
using System;
using System.Text;
using System.Security.Cryptography;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Utilities.Encoders;
public class Bitcoin
{
public static ECPoint Recover(byte[] hash, byte[] sigBytes, int rec)
{
BigInteger r = new BigInteger(1, sigBytes, 0, 32);
BigInteger s = new BigInteger(1, sigBytes, 32, 32);
BigInteger[] sig = new BigInteger[]{ r, s };
ECPoint Q = ECDSA_SIG_recover_key_GFp(sig, hash, rec, true);
return Q;
}
public static ECPoint ECDSA_SIG_recover_key_GFp(BigInteger[] sig, byte[] hash, int recid, bool check)
{
X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1");
int i = recid / 2;
Console.WriteLine("r: "+ToHex(sig[0].ToByteArrayUnsigned()));
Console.WriteLine("s: "+ToHex(sig[1].ToByteArrayUnsigned()));
BigInteger order = ecParams.N;
BigInteger field = (ecParams.Curve as FpCurve).Q;
BigInteger x = order.Multiply(new BigInteger(i.ToString())).Add(sig[0]);
if (x.CompareTo(field) >= 0) throw new Exception("X too large");
Console.WriteLine("Order: "+ToHex(order.ToByteArrayUnsigned()));
Console.WriteLine("Field: "+ToHex(field.ToByteArrayUnsigned()));
byte[] compressedPoint = new Byte[x.ToByteArrayUnsigned().Length+1];
compressedPoint[0] = (byte) (0x02+(recid%2));
Buffer.BlockCopy(x.ToByteArrayUnsigned(), 0, compressedPoint, 1, compressedPoint.Length-1);
ECPoint R = ecParams.Curve.DecodePoint(compressedPoint);
Console.WriteLine("R: "+ToHex(R.GetEncoded()));
if (check)
{
ECPoint O = R.Multiply(order);
if (!O.IsInfinity) throw new Exception("Check failed");
}
int n = (ecParams.Curve as FpCurve).Q.ToByteArrayUnsigned().Length*8;
BigInteger e = new BigInteger(1, hash);
if (8*hash.Length > n)
{
e = e.ShiftRight(8-(n & 7));
}
e = BigInteger.Zero.Subtract(e).Mod(order);
BigInteger rr = sig[0].ModInverse(order);
BigInteger sor = sig[1].Multiply(rr).Mod(order);
BigInteger eor = e.Multiply(rr).Mod(order);
ECPoint Q = ecParams.G.Multiply(eor).Add(R.Multiply(sor));
Console.WriteLine("n: "+n);
Console.WriteLine("e: "+ToHex(e.ToByteArrayUnsigned()));
Console.WriteLine("rr: "+ToHex(rr.ToByteArrayUnsigned()));
Console.WriteLine("sor: "+ToHex(sor.ToByteArrayUnsigned()));
Console.WriteLine("eor: "+ToHex(eor.ToByteArrayUnsigned()));
Console.WriteLine("Q: "+ToHex(Q.GetEncoded()));
return Q;
}
public static bool VerifySignature(byte[] pubkey, byte[] hash, byte[] sigBytes)
{
X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1");
ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve,
ecParams.G, ecParams.N, ecParams.H,
ecParams.GetSeed());
BigInteger r = new BigInteger(1, sigBytes, 0, 32);
BigInteger s = new BigInteger(1, sigBytes, 32, 32);
ECPublicKeyParameters publicKey = new ECPublicKeyParameters(ecParams.Curve.DecodePoint(pubkey), domainParameters);
ECDsaSigner signer = new ECDsaSigner();
signer.Init(false, publicKey);
return signer.VerifySignature(hash, r, s);
}
public static void Main()
{
string msg = "StackOverflow test 123";
string sig = "IB7XjSi9TdBbB3dVUK4+Uzqf2Pqk71XkZ5PUsVUN+2gnb3TaZWJwWW2jt0OjhHc4B++yYYRy1Lg2kl+WaiF+Xsc=";
string pubkey = "045894609CCECF9A92533F630DE713A958E96C97CCB8F5ABB5A688A238DEED6DC2D9D0C94EBFB7D526BA6A61764175B99CB6011E2047F9F067293F57F5";
SHA256Managed sha256 = new SHA256Managed();
byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(msg), 0, Encoding.UTF8.GetByteCount(msg));
Console.WriteLine("Hash: "+ToHex(hash));
byte[] tmpBytes = Convert.FromBase64String(sig);
byte[] sigBytes = new byte[tmpBytes.Length-1];
Buffer.BlockCopy(tmpBytes, 1, sigBytes, 0, sigBytes.Length);
int rec = (tmpBytes[0] - 27) & ~4;
Console.WriteLine("Rec {0}", rec);
ECPoint Q = Recover(hash, sigBytes, rec);
string qstr = ToHex(Q.GetEncoded());
Console.WriteLine("Q is same as supplied: "+qstr.Equals(pubkey));
Console.WriteLine("Signature verified correctly: "+VerifySignature(Q.GetEncoded(), hash, sigBytes));
}
public static string ToHex(byte[] data)
{
return BitConverter.ToString(data).Replace("-","");
}
}
EDIT Je vois ce n'est pas encore commenté ou accepté, donc j'ai écrit un test complet qui génère une clé privée et une clé publique, puis génère une signature valide en utilisant la clé privée. Après cela, il récupère la clé publique de la signature et le hachage et utilise cette clé publique pour vérifier la signature du message. Veuillez voir ci-dessous, si il y a encore quelques questions s'il vous plaît laissez-moi savoir.
public static void FullSignatureTest(byte[] hash)
{
X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1");
ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve,
ecParams.G, ecParams.N, ecParams.H,
ecParams.GetSeed());
ECKeyGenerationParameters keyGenParams =
new ECKeyGenerationParameters(domainParameters, new SecureRandom());
AsymmetricCipherKeyPair keyPair;
ECKeyPairGenerator generator = new ECKeyPairGenerator();
generator.Init(keyGenParams);
keyPair = generator.GenerateKeyPair();
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) keyPair.Private;
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) keyPair.Public;
Console.WriteLine("Generated private key: " + ToHex(privateKey.D.ToByteArrayUnsigned()));
Console.WriteLine("Generated public key: " + ToHex(publicKey.Q.GetEncoded()));
ECDsaSigner signer = new ECDsaSigner();
signer.Init(true, privateKey);
BigInteger[] sig = signer.GenerateSignature(hash);
int recid = -1;
for (int rec=0; rec<4; rec++) {
try
{
ECPoint Q = ECDSA_SIG_recover_key_GFp(sig, hash, rec, true);
if (ToHex(publicKey.Q.GetEncoded()).Equals(ToHex(Q.GetEncoded())))
{
recid = rec;
break;
}
}
catch (Exception)
{
continue;
}
}
if (recid < 0) throw new Exception("Did not find proper recid");
byte[] fullSigBytes = new byte[65];
fullSigBytes[0] = (byte) (27+recid);
Buffer.BlockCopy(sig[0].ToByteArrayUnsigned(), 0, fullSigBytes, 1, 32);
Buffer.BlockCopy(sig[1].ToByteArrayUnsigned(), 0, fullSigBytes, 33, 32);
Console.WriteLine("Generated full signature: " + Convert.ToBase64String(fullSigBytes));
byte[] sigBytes = new byte[64];
Buffer.BlockCopy(sig[0].ToByteArrayUnsigned(), 0, sigBytes, 0, 32);
Buffer.BlockCopy(sig[1].ToByteArrayUnsigned(), 0, sigBytes, 32, 32);
ECPoint genQ = ECDSA_SIG_recover_key_GFp(sig, hash, recid, false);
Console.WriteLine("Generated signature verifies: " + VerifySignature(genQ.GetEncoded(), hash, sigBytes));
}