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

前端 未结 2 1772
陌清茗
陌清茗 2021-02-03 23:49

Update: Partial solution available on Git

EDIT: A compiled version of this is available at https://github.com/makerofthings

2条回答
  •  灰色年华
    2021-02-04 00:23

    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.

    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.

    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
    

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

    1Kb76YK9a4mhrif766m321AMocNvzeQxqV
    

    as given in the question.

提交回复
热议问题