CryptoSys Home > PKI > Elliptic curve cryptography: Reproducing a raw Bitcoin transaction

Elliptic curve cryptography: Reproducing a raw Bitcoin transaction using CryptoSys PKI Pro


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.

[Go to top]

The transaction

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.)

input scripts

The first hex string is the ECDSA signature in ASN1-DER structure followed by "01"
3045022100da43201760bda697222002f56266bf65023fef2094519e13077f777baed553b102205ce35d05eabda58cd50a67977a65706347cc25ef43153e309ff210a134722e9e
01
and the second hex string is the public key in X9.63 uncompressed form
042daa93315eebbe2cb9b5c3505df4c6fb6caca8b756786098567550d4820c09db988fe9997d049d687292f815ccd6e7fb5c1b1a91137999818d17c73d0f80aef9

[Go to top]

Validating the key values

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

[Go to top]

Verifying the final transaction

To verify the signature, we need four things:
  1. the signature value itself
  2. the signature algorithm used
  3. the public key, and
  4. either the original data that was signed or its digest value.
We have the signature value and the public key, and we know the signature algorithm is ecdsaWithSHA256, but we need to obtain the exact sequence of bytes of the original data that was signed. To do this we start with the final transaction in hex format.

[Go to top]

The final transaction

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.

[Go to top]

The raw transaction to be signed

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.

[Go to top]

Verifying the signature

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().

[Go to top]

Computing a reference signature using the deterministic procedure

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)

[Go to top]

The source code

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

[Go to top]

Contact

For more information, or to comment on this page, please send us a message.

This page last updated 1 July 2020

[Go to top]