We show here how we can reproduce a raw Bitcoin transaction using the elliptic curve C# methods in CryptoSys PKI Pro.
This example is taken from http://bitcoin.stackexchange.com/questions/32628/redeeming-a-raw-transaction-step-by-step-example-required.
Thanks to the Wizard of Ozzie who actually used some of his own Bitcoins to make a transaction and then published the private key used to make it (the account is now empty, so there's no risk to him in publishing it).
We can use the information on the blockchain page together with the private key to carry out some of the raw calculations involved in the transaction. Our quick-and-dirty source code to do this in C# is given below.
The transaction can be viewed at blockchain.info https://blockchain.info/tx/211b8fb30990632751a83d1dc4f0323ff7d2fd3cad88084de13c9be2ae1c6426.
From this page we can get both the signature value and the public key used to sign it. These are under the Input Scripts heading. (If you can't see it, make sure you select Show scripts & coinbase.)
3045022100da43201760bda697222002f56266bf65023fef2094519e13077f777baed553b102205ce35d05eabda58cd50a67977a65706347cc25ef43153e309ff210a134722e9e 01and the second hex string is the public key in X9.63 uncompressed form
042daa93315eebbe2cb9b5c3505df4c6fb6caca8b756786098567550d4820c09db988fe9997d049d687292f815ccd6e7fb5c1b1a91137999818d17c73d0f80aef9
Normally we don't have the corresponding private key for a transaction, but in this case we do.
It is 0ecd20654c2e2be708495853e8da35c664247040c00bd10b9b13e5e86e6a808d
.
We can verify that this matches the given public key by reading in the hex representation of the private key to an "internal" key string
and then querying that for the public key.
// READ IN PRIVATE KEY IN (HEX,CURVENAME) FORMAT string hexKey = "0ecd20654c2e2be708495853e8da35c664247040c00bd10b9b13e5e86e6a808d"; Ecc.CurveName curveName = Ecc.CurveName.Secp256k1; Console.WriteLine("KEYHEX: " + hexKey); Console.WriteLine("CURVE: " + curveName.ToString()); string internalKey = Ecc.ReadKeyByCurve(hexKey, curveName); // Derive public key in hex form from given private key and check against known value string s = Ecc.QueryKey(internalKey, "publicKey"); Console.WriteLine("Ecc.QueryKey('publicKey')=\n" + s);
KEYHEX: 0ecd20654c2e2be708495853e8da35c664247040c00bd10b9b13e5e86e6a808d CURVE: Secp256k1 Ecc.QueryKey('publicKey')= 042daa93315eebbe2cb9b5c3505df4c6fb6caca8b756786098567550d4820c09db988fe9997d049d 687292f815ccd6e7fb5c1b1a91137999818d17c73d0f80aef9
To enable us to verify the signature later, we read in the public key to an internal key string and make some checks.
string pubkeyhex = "042daa93315eebbe2cb9b5c3505df4c6fb6caca8b756786098567550d4820c09d" + "b988fe9997d049d687292f815ccd6e7fb5c1b1a91137999818d17c73d0f80aef9"; string internalPubKey = Ecc.ReadKeyByCurve(pubkeyhex, curveName); Console.WriteLine("keyBits=" + Ecc.QueryKey(internalPubKey, "keyBits")); Console.WriteLine("isPrivate=" + Ecc.QueryKey(internalPubKey, "isPrivate"));
keyBits=256 isPrivate=0
We can also compute the HASH160 digest value of the public key, which we will see used below as part of the input to the signature.
HASH160 is Bitcoinese for RIPEMD-160(SHA-256(data))
.
Note that this is a hash of the sequence of bytes (0x04,0x2D,0xAA,...,0xF9
),
not a hash of the string containing the hex representation.
We can use the Hash.HexFromHex()
method here to compute this directly.
string pubkeyhex = "042daa93315eebbe2cb9b5c3505df4c6fb6caca8b756786098567550d4820c09d" + "b988fe9997d049d687292f815ccd6e7fb5c1b1a91137999818d17c73d0f80aef9"; // Compute the HASH160 digest of the public key s = Hash.HexFromHex(pubkeyhex, HashAlgorithm.Bitcoin160); Console.WriteLine("Bitcoin160('publicKey')=" + s);
Bitcoin160('publicKey')=dd6cce9f255a8cc17bda8ba0373df8e861cb866e
To get the final transaction in hex, add ?format=hex
to the blockchain link above
(full link).
After adding some whitespace and newlines to show the fields for clarity, the final transaction can be reproduced as follows:
01000000 01 be66e10da854e7aea9338c1f91cd489768d1d6d7189f586d7a3613f2a24d5396 00000000 8b 48 3045022100da43201760bda697222002f56266bf65023fef2094519e13077f777baed55 3b102205ce35d05eabda58cd50a67977a65706347cc25ef43153e309ff210a134722e9e 01 41 042daa93315eebbe2cb9b5c3505df4c6fb6caca8b756786098567550d4820c09d b988fe9997d049d687292f815ccd6e7fb5c1b1a91137999818d17c73d0f80aef9 ffffffff 01 23ce010000000000 19 76 a9 14 2bc89c2702e0e618db7d59eb5ce2f0f147b40754 88 ac 00000000
You can see the signature in the above (3045022100da...722e9e
),
the public key (042daa93315e...80aef9
),
the previous output hash (be66e1...5396
),
and the output public key hash (2bc89c...0754
).
The amount of the transaction 0.00118307 BTC (=118307=0x01ce23
Satoshis) can be seen as the little-endian-encoded integer
23ce010000000000
.
The rules to derive the bytes of the raw transaction that is actually signed from the output above are explained in wonderful detail by Ken Shirriff at Bitcoins the hard way: Using the raw Bitcoin protocol.
The end result for the raw data-to-be-signed in this case is the 114-byte sequence
01000000 01 be66e10da854e7aea9338c1f91cd489768d1d6d7189f586d7a3613f2a24d5396 00000000 19 76 a9 14 dd6cce9f255a8cc17bda8ba0373df8e861cb866e 88 ac ffffffff 01 23ce010000000000 19 76 a9 14 2bc89c2702e0e618db7d59eb5ce2f0f147b40754 88 ac 00000000 01000000
Note the HASH160 digest of the public key (dd6cce...866e
) is included here.
We can compute the HASH256 digest of the input data. This is the SHA-256 message digest algorithm applied twice,
SHA-256(SHA-256(data))
.
string datahex = "0100000001be66e10da854e7aea9338c1f91cd489768d1d6d7189f586d7a3613f2a24d539600" + "0000001976a914dd6cce9f255a8cc17bda8ba0373df8e861cb866e88acffffffff0123ce0100" + "000000001976a9142bc89c2702e0e618db7d59eb5ce2f0f147b4075488ac0000000001000000"; byte[] data = Cnv.FromHex(datahex); Console.WriteLine("RAW_TX:\n" + Cnv.ToHex(data)); // COMPUTE THE DOUBLE SHA-256 DIGEST OF THE RAW DATA byte[] digest = Hash.Double(data, HashAlgorithm.Sha256); Console.WriteLine("SHA256(SHA256(RAW_TX)):\n" + Cnv.ToHex(digest)); Console.WriteLine("Same hash value but reversed:\n" + Cnv.ToHex(Cnv.ReverseBytes(digest)));
RAW_TX: 0100000001BE66E10DA854E7AEA9338C1F91CD489768D1D6D7189F586D7A3613F2A24D5396000000 001976A914DD6CCE9F255A8CC17BDA8BA0373DF8E861CB866E88ACFFFFFFFF0123CE010000000000 1976A9142BC89C2702E0E618DB7D59EB5CE2F0F147B4075488AC0000000001000000 SHA256(SHA256(RAW_TX)): 30F10A6468B7D98257AF63FB40DFCF2CEFE991346FD37C67CF7B51FF8D4404D3 Same hash value but reversed: D304448DFF517BCF677CD36F3491E9EF2CCFDF40FB63AF5782D9B768640AF130
Some applications display the double SHA-256 digest value in reverse order, so we show it above for interest. However, we always use the "correct" order shown first.
We can now verify the given signature against the double-digest value we computed above.
string sigweb = "3045022100da43201760bda697222002f56266bf65023fef2094519e13077f777baed55" + "3b102205ce35d05eabda58cd50a67977a65706347cc25ef43153e309ff210a134722e9e"; // VERIFY THE SIGNATURE PUBLISHED ON THE WEB int r1 = Sig.VerifyDigest(sigweb, digest, internalPubKey, SigAlgorithm.Ecdsa_Sha256); Console.WriteLine("Sig.VerifyDigest returns " + r1 + " (expected 0)");
Sig.VerifyDigest returns 0 (expected 0)
Note: Strictly speaking, the ECDSA signature is computed over the single SHA-256 digest of the input data using
the "ecdsaWithSHA256" algorithm, which in turn computes the SHA-256 digest of its input.
Hence the double hash.
That's why we use the Sig.VerifyDigest()
method, not Sig.VerifyData()
.
One of the problems with the ECDSA signature scheme is that it uses a different secret random value (usually denoted as k) each time. So every time you recompute the signature you will get a different result, and you can never reproduce the same value.
For reference, we can compute a replicable signature over the same data using the deterministic digital signature generation procedure of [RFC6979]. This will always give the same result and can also be verified using the same public key. This procedure has been suggested as a standard for Bitcoin transactions.
We use the private key we read in above to sign the input data as follows.
string sigdet = Sig.SignDigest(digest, internalKey, "", SigAlgorithm.Ecdsa_Sha256,
Sig.SigOptions.UseDeterministic | Sig.SigOptions.Asn1DERStructure, Sig.Encoding.Base16);
Console.WriteLine("SIG (DET): " + sigdet);
SIG (DET): 30450220587ce0cf0252e2db3a7c3c91b355aa8f3385e128227cd8727c5f7777877ad 772022100edc508b7c14891ed15ab38c687019d7ebaf5c12908cf21a83e8ae57e8c47e95c
That is, an equivalent and replicable signature for this transaction is
30450220587ce0cf0252e2db3a7c3c91b355aa8f3385e128227cd8727c5f7777877ad772022100edc508b7c14891ed15ab38c687019d7ebaf5c12908cf21a83e8ae57e8c47e95c
We can verify this new signature over the same input data using the same public key.
// VERIFY THE NEW SIGNATURE r2 = Sig.VerifyDigest(sigdet, digest, internalPubKey, SigAlgorithm.Ecdsa_Sha256); Console.WriteLine("Sig.VerifyDigest(sigdet) returns " + r2 + " (expected 0)");
Sig.VerifyDigest(sigdet) returns 0 (expected 0)
Here is the quick-and-dirty source code in C# to carry out all of the above (zipped 2.9 kB).
You will need to install CryptoSys PKI Pro on your system if you haven't already done so.
A free trial edition is available here.
Add a reference in your project to diCrSysPKINet.dll
which you should find in
C:\Program Files (x86)\CryptoSysPKI\DotNet
For more information, or to comment on this page, please send us a message.
This page last updated 1 July 2020