Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>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.</p> <p>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.</p> <p>It only calculates up to the RIPEMD-160 hash, and I used <a href="http://gobittest.appspot.com/Address" rel="noreferrer">http://gobittest.appspot.com/Address</a> to check the final address that results (unfortunately that website doesn't seem to support entering a compressed encoding for the public key).</p> <pre><code> 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 &amp; 4) != 0; int rec = first &amp; 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 &amp; 2) != 0) { x = x.Add(order); } if (x.CompareTo(curve.Q) &gt;= 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 &amp; 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 &lt; 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 (&lt; 253 bytes) bos.WriteByte((byte)messageBytes.Length); bos.Write(messageBytes, 0, messageBytes.Length); return bos.ToArray(); } </code></pre> <p>Sample output for the initial data in the question:</p> <blockquote> <pre><code>Q: 0283437893b491218348bf5ff149325e47eb628ce36f73a1a927ae6cb6021c7ac4 RIPEMD-160(SHA-256(Q)): cbe57ebe20ad59518d14926f8ab47fecc984af49 Signature verified correctly: True </code></pre> </blockquote> <p>If we plug the RIPEMD-160 value into the address checker, it returns</p> <blockquote> <pre><code>1Kb76YK9a4mhrif766m321AMocNvzeQxqV </code></pre> </blockquote> <p>as given in the question.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload