Last active
August 19, 2017 12:30
-
-
Save jspilman/8396495 to your computer and use it in GitHub Desktop.
Working implementation of stealth payments in Bitcoin using OP_RETURN! See TxID: 6e8576d6f65947b249a19402b6359c5a490abf67d50869a18518ccae41f94419 on Test NeT
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// BIP32 Wallet | |
byte[] entropy = Util.DoubleSHA256(Encoding.ASCII.GetBytes("Stealth Address")); | |
HdNode wallet = HdNode.Create(entropy, mainNet: false).GetSecretChild(0); | |
// A TxID on Test-Net with 1BTC in Vout[1], spendable by wallet/0'/0' | |
byte[] unspentPubKey = wallet.GetSecretChild(0, 0).PublicKey; | |
byte[] unspentPrivKey = wallet.GetSecretChild(0, 0).PrivateKey; | |
string unspentAddr = Util.PubKeyToAddress(unspentPubKey, mainNet: false); | |
byte[] unspentTxId = Util.HexToBytes("4b8fd9c4f5cb233c687e3e883f7c284f9abc2698dd08f1ec6770f488a27a9704"); | |
TxOut unspentTxOut = TxOut.PayToPubKeyHash(Util.Amount("1"), unspentPubKey); | |
Array.Reverse(unspentTxId); | |
// Two static public keys -- wallet/1'/0', and wallet/1'/1' | |
byte[] d1 = wallet.GetSecretChild(1, 0).PrivateKey; | |
byte[] d2 = wallet.GetSecretChild(1, 1).PrivateKey; | |
byte[] Q1 = wallet.GetSecretChild(1, 0).PublicKey; | |
byte[] Q2 = wallet.GetSecretChild(1, 1).PublicKey; | |
Console.WriteLine("Q1: " + Util.BytesToHex(Q1)); | |
Console.WriteLine("Q2: " + Util.BytesToHex(Q2)); | |
Console.WriteLine(); | |
// Generate an Ephemeral private and public key | |
// NOTE: Not quite randomly generated for now, to make it easier to reproduce this | |
//byte[] e = EC.NewPrivateKey(); | |
//byte[] P = EC.GetPublicKey(e, compressed: true); | |
byte[] e = wallet.GetSecretChild(1, 2).PrivateKey; | |
byte[] P = wallet.GetSecretChild(1, 2).PublicKey; | |
Console.WriteLine("e: " + Util.BytesToHex(e)); | |
Console.WriteLine("P: " + Util.BytesToHex(P)); | |
Console.WriteLine(); | |
// Calculated shared secrets with Q2 (from Payee perspective) | |
byte[] S = EC.DH(e, Q2); | |
Console.WriteLine("S: " + Util.BytesToHex(S)); | |
Console.WriteLine(); | |
// Recalculate shared secrets with d2 (from Payer perspective) | |
byte[] PayeeS = EC.DH(d2, P); | |
CollectionAssert.AreEqual(S, PayeeS); | |
// Calculate stealth/derived public keys from Q1 -> Q1' and Q2 -> Q2' | |
// Q1' = Q + (SHA256(S || 1) * G) | |
// Q2' = Q + (SHA256(S || 2) * G) | |
var key1 = Util.SingleSHA256(S.Concat(new byte[] { 1 })); | |
byte[] q1New = EC.PointAdd(Q1, key1); | |
Console.WriteLine("Key 1: " + Util.BytesToHex(key1)); | |
Console.WriteLine("Q1': " + Util.BytesToHex(q1New)); | |
Console.WriteLine(); | |
var key2 = Util.SingleSHA256(S.Concat(new byte[] { 2 })); | |
byte[] q2New = EC.PointAdd(Q2, key2); | |
Console.WriteLine("Key 2: " + Util.BytesToHex(key2)); | |
Console.WriteLine("Q2': " + Util.BytesToHex(q2New)); | |
Console.WriteLine(); | |
// Prep the unspent as a TxIn | |
Script unspentScriptPubKey = unspentTxOut.ScriptPubKey; | |
TxIn userUnspentTxIn = new TxIn(unspentTxId, nIn: 1); | |
// Build the transaction | |
Transaction stealthTx = new Transaction(); | |
stealthTx.Vin.Add(userUnspentTxIn); | |
stealthTx.Vout.Add(TxOut.PayToMultiSig(Util.Amount(".995"), 2, 2, q1New, q2New)); | |
stealthTx.Vout.Add(TxOut.OpReturn(P)); | |
// Sign the transaction -- ScriptSig for Vin[0] will be <Sig> <PubKey> | |
stealthTx.Sign(unspentPrivKey, unspentScriptPubKey, 0, SigHash.All); | |
stealthTx.Vin[0].ScriptSig.Push(unspentPubKey); | |
// Verify TX is complete given the scriptPubKeys from the input transactions | |
Assert.IsTrue(stealthTx.Verify(unspentScriptPubKey)); | |
Console.WriteLine("Stealth TX: " + Util.BytesToHex(stealthTx.Serialize())); | |
Console.WriteLine("Stealth TxID: " + stealthTx.TxIdStr); // reversed bytes to follow convention | |
Console.WriteLine(); | |
// An alternative transaction construction... | |
stealthTx = new Transaction(); | |
stealthTx.Vin.Add(userUnspentTxIn); | |
stealthTx.Vout.Add(TxOut.PayToPubKeyHash(Util.Amount(".995"), q1New)); | |
stealthTx.Vout.Add(TxOut.OpReturn(P)); | |
// Sign the transaction -- ScriptSig for Vin[0] will be <Sig> <PubKey> | |
stealthTx.Vin[0].ScriptSig = Script.Empty; // reset the signature which we added above | |
stealthTx.Sign(unspentPrivKey, unspentScriptPubKey, 0, SigHash.All); | |
stealthTx.Vin[0].ScriptSig.Push(unspentPubKey); | |
// Verify TX is complete given the scriptPubKeys from the input transactions | |
Assert.IsTrue(stealthTx.Verify(unspentScriptPubKey)); | |
Console.WriteLine("Stealth TX: " + Util.BytesToHex(stealthTx.Serialize())); | |
Console.WriteLine("Stealth TxID: " + stealthTx.TxIdStr); // reversed bytes to follow convention |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static byte[] DH(byte[] privKey, byte[] pubKeyBytes) | |
{ | |
var pubKey = new OpenSSL.Crypto.EC.Point(EcGroup, pubKeyBytes); | |
var result = pubKey.Multiply(OpenSSL.Core.BigNumber.FromArray(privKey), EcBnContext); | |
OpenSSL.Core.BigNumber x = new OpenSSL.Core.BigNumber(); | |
OpenSSL.Core.BigNumber y = new OpenSSL.Core.BigNumber(); | |
result.GetAffineCoordinatesGFp(x, y, EcBnContext); | |
byte[] xBytes = new byte[x.Bytes]; | |
x.ToBytes(xBytes); | |
return xBytes; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Test Name: StealthPayment | |
Test FullName: BitcoinUnitTests.TransactionTests.StealthPayment | |
Test Source: c:\Source\BitPostage\BitPostage\BitcoinUnitTests\Transaction.cs : line 264 | |
Test Outcome: Passed | |
Test Duration: 0:00:01.2505994 | |
Result StandardOutput: | |
Q1: 025A7B91C4E08284DFA7701BCD2B0BBA4A755CB7BDFDB75308E48CD570BF917510 | |
Q2: 03AEF1BD2BCFDAF5092CED87B0F19B95A604C0176ADF06542FCA4D2771FCF64118 | |
e: 7B381283DDCBA6C447F3EE76716722F58C83E448AF26B14F10C6A064924413AD | |
P: 02FDF5C5E6B5D3419E42CC0E12A53B3BD42795A2BDD937739E5EB6ECA5CF542206 | |
S: 51DD1F217272B5CB4542A62452AFAFDA3745EB573F27541402E45F9A3883F5EC | |
Key 1: B31BD7791E48B225D978B40A38EB1695CCA6A52EAF425339B6B30C9E02F1310A | |
Q1': 023CDFDF2DC653B5C33646CDFF36DBD899DEA37A6B431CD412631D5112507D1D5C | |
Key 2: B3A49A0C1C7A1B8AB8892E0A549E56BD8BD5BC38714DBA6A479EF3C2E42DAA8F | |
Q2': 03305DDDEC8FE97CBAF4A38BB41CB6AF6DD5F44E4B68EBD733F634230EB810731C | |
-- Note: The following TX is an example MULTISIG, I didn't actually put this one on the blockchain | |
Stealth TX: 010000000104977AA288F47067ECF108DD9826BC9A4F287C3F883E7E683C23CBF5C4D98F4B010000006A4730440220344AA64AE604E910B14BCD96BB653C6AB6EF70BF81B0C84D7F6B9EB445A0C8C002205A65D3AEF383E28741EA21F1299319B38CB87746D6CA2B8E26117EA205B849C4012102FD1B4F1954D282CF243576885CA28AF5CC15D5A66443226021FE5986BB58EDFBFFFFFFFF02E03FEE0500000000475221023CDFDF2DC653B5C33646CDFF36DBD899DEA37A6B431CD412631D5112507D1D5C2103305DDDEC8FE97CBAF4A38BB41CB6AF6DD5F44E4B68EBD733F634230EB810731C52AE0000000000000000246A4C2102FDF5C5E6B5D3419E42CC0E12A53B3BD42795A2BDD937739E5EB6ECA5CF54220600000000 | |
Stealth TxID: 773e1623fd0ebcd3e1e66e0778ed7fba55519e0f8f0aa976775e6782bcaf25e0 | |
-- NOTE: This following TX is an example pay-to-pubKey, this one IS actually on the block chain | |
-- The payment ends up being sent to address n3wq8rDH4nfqWhK6XmQnh9BupJ4mAZK2F5 which | |
-- is unlinkable to Q1 without having d2 | |
Stealth TX: 010000000104977AA288F47067ECF108DD9826BC9A4F287C3F883E7E683C23CBF5C4D98F4B010000006B4830450221009E416387571B5C5A9307C34695B848A938BC35152C73D4FE8B33DD83FFB7865802203629F866A7A6A6516A49E9AA71B87A305C8E5F117C458F1AA2DE3AE038044EBA012102FD1B4F1954D282CF243576885CA28AF5CC15D5A66443226021FE5986BB58EDFBFFFFFFFF02E03FEE05000000001976A914F60730C4E1E0ACF6B7B5C2B6C4E801D18F1B291488AC0000000000000000246A4C2102FDF5C5E6B5D3419E42CC0E12A53B3BD42795A2BDD937739E5EB6ECA5CF54220600000000 | |
Stealth TxID: 6e8576d6f65947b249a19402b6359c5a490abf67d50869a18518ccae41f94419 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// BIP32 Wallet | |
byte[] entropy = Util.DoubleSHA256(Encoding.ASCII.GetBytes("Stealth Address")); | |
HdNode wallet = HdNode.Create(entropy, mainNet: false).GetSecretChild(0); | |
// Two static keys -- wallet/1'/0', and wallet/1'/1' | |
byte[] d1 = wallet.GetSecretChild(1, 0).PrivateKey; | |
byte[] d2 = wallet.GetSecretChild(1, 1).PrivateKey; | |
byte[] Q1 = wallet.GetSecretChild(1, 0).PublicKey; | |
byte[] Q2 = wallet.GetSecretChild(1, 1).PublicKey; | |
Console.WriteLine("Q1: " + Util.BytesToHex(Q1)); | |
Console.WriteLine("Q2: " + Util.BytesToHex(Q2)); | |
// Decode the transaction that we sent above... this is on the TestNet BlockChain | |
Transaction stealthTx = Transaction.Parse(Util.HexToBytes("010000000104977AA288F47067ECF108DD9826BC9A4F287C3F883E7E683C23CBF5C4D98F4B010000006B4830450221009E416387571B5C5A9307C34695B848A938BC35152C73D4FE8B33DD83FFB7865802203629F866A7A6A6516A49E9AA71B87A305C8E5F117C458F1AA2DE3AE038044EBA012102FD1B4F1954D282CF243576885CA28AF5CC15D5A66443226021FE5986BB58EDFBFFFFFFFF02E03FEE05000000001976A914F60730C4E1E0ACF6B7B5C2B6C4E801D18F1B291488AC0000000000000000246A4C2102FDF5C5E6B5D3419E42CC0E12A53B3BD42795A2BDD937739E5EB6ECA5CF54220600000000")); | |
Console.WriteLine("Stealth TxID: " + stealthTx.TxIdStr); | |
Console.WriteLine(); | |
// Vout[1] has a ScriptPubKey of 'OP_RETURN PUSHDATA1 <DATA>' | |
byte[] TxP = stealthTx.Vout[1].ScriptPubKey.Ops[1].Data.ToArray(); | |
Console.WriteLine("Stealth Tx Vout[1]: " + Util.BytesToHex(stealthTx.Vout[1].ScriptPubKey.Raw)); | |
Console.WriteLine("Stealth Tx P: " + Util.BytesToHex(TxP)); | |
Console.WriteLine(); | |
// Recalculate shared secrets with d2 (using 'P' from the Tx) | |
byte[] S = EC.DH(d2, TxP); | |
Console.WriteLine("Shared Secret: " + Util.BytesToHex(S)); | |
Console.WriteLine(); | |
// First lets DETECT this payment as if we only had d2 and Q1 pubKey | |
var key = Util.SingleSHA256(S.Concat(new byte[] { 1 })); | |
byte[] q1New = EC.PointAdd(Q1, key); | |
Console.WriteLine("Key 1: " + Util.BytesToHex(key)); | |
Console.WriteLine("Q1': " + Util.BytesToHex(q1New)); | |
// We paid to HASH160 of Q1' | |
// StealthTX looks like: OP_DUP OP_HASH160 <Hash160(Q1')> OP_EQUALVERIFY OP_CHECKSIG | |
CollectionAssert.AreEqual(Util.Hash160(q1New), stealthTx.Vout[0].ScriptPubKey.Ops[2].Data.ToArray()); | |
Console.WriteLine("Hash160(Q1'): " + Util.BytesToHex(Util.Hash160(q1New))); | |
Console.WriteLine(); | |
// They matched! So a payment was detected using d2 and Q1, but we can't spend it without d1 | |
// Now let's spend it... | |
byte[] d1New = EC.AddModN(d1, key); | |
// Spend the stealth payment with our derived private keys | |
Transaction spendStealth = new Transaction(); | |
spendStealth.Vin.Add(new TxIn(stealthTx.TxID, 0)); | |
spendStealth.Vout.Add(TxOut.PayToPubKeyHash(Util.Amount(".990"), wallet.GetSecretChild(0, 1).PublicKey)); | |
// Sign the transaction -- ScriptSig for Vin[0] will be <sig> <pubKey> | |
spendStealth.Sign(d1New, stealthTx.Vout[0].ScriptPubKey, 0, SigHash.All); | |
spendStealth.Vin[0].ScriptSig.Push(q1New); | |
Assert.IsTrue(spendStealth.Verify(stealthTx.Vout[0].ScriptPubKey)); | |
Console.WriteLine("Spend Stealth TX: " + Util.BytesToHex(spendStealth.Serialize())); | |
Console.WriteLine("Spend Stealth TxID: " + spendStealth.TxIdStr); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Test Name: SpendStealth | |
Test FullName: BitcoinUnitTests.TransactionTests.SpendStealth | |
Test Source: c:\Source\BitPostage\BitPostage\BitcoinUnitTests\Transaction.cs : line 362 | |
Test Outcome: Passed | |
Test Duration: 0:00:01.2757872 | |
Result StandardOutput: | |
Q1: 025A7B91C4E08284DFA7701BCD2B0BBA4A755CB7BDFDB75308E48CD570BF917510 | |
Q2: 03AEF1BD2BCFDAF5092CED87B0F19B95A604C0176ADF06542FCA4D2771FCF64118 | |
Stealth TxID: 6e8576d6f65947b249a19402b6359c5a490abf67d50869a18518ccae41f94419 | |
Stealth Tx Vout[1]: 6A4C2102FDF5C5E6B5D3419E42CC0E12A53B3BD42795A2BDD937739E5EB6ECA5CF542206 | |
Stealth Tx P: 02FDF5C5E6B5D3419E42CC0E12A53B3BD42795A2BDD937739E5EB6ECA5CF542206 | |
Shared Secret: 51DD1F217272B5CB4542A62452AFAFDA3745EB573F27541402E45F9A3883F5EC | |
Key 1: B31BD7791E48B225D978B40A38EB1695CCA6A52EAF425339B6B30C9E02F1310A | |
Q1': 023CDFDF2DC653B5C33646CDFF36DBD899DEA37A6B431CD412631D5112507D1D5C | |
Hash160(Q1'): F60730C4E1E0ACF6B7B5C2B6C4E801D18F1B2914 | |
Spend Stealth TX: 01000000011944F941AECC1885A16908D567BF0A495A9C35B60294A149B24759F6D676856E000000006B483045022063F5F9C78F83F0204DA66FB9E957585BF983ED21A617BCF8BC61895D8444F3D9022100F75C2A17EFEE9D13440683978285FC0FD53EE9652F225F0DE0CBFBA64D5F2DD50121023CDFDF2DC653B5C33646CDFF36DBD899DEA37A6B431CD412631D5112507D1D5CFFFFFFFF01C09EE605000000001976A9147DE4593F6CCF83A3B21F1EB7AA4321FC1CE598C088AC00000000 | |
Spend Stealth TxID: 95dd90fe34586f1c499ad3e8e6f6d08b2fcbea933624bf746a25f4f18a046d89 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment