Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Proof of Concept for Payment Channels
// 0) User and AP negotiate how much to escrow, who pays the fees, and how far in the future nLockTime
// will be set (how long user’s funds will be tied if AP doesn’t close the channel)
long apUnspent = Util.Amount("0.27");
long userUnspent = Util.Amount("6.123");
long escrowAmount = Util.Amount("5");
long payerFee = 0;
long apFee = Util.Amount(".0005");
long userChange = userUnspent - escrowAmount - payerFee;
long apChange = apUnspent - apFee;
uint lockTime = Util.LockTime(DateTime.Now.AddMinutes(15));
// BIP32 -- User/0'
byte[] entropy = Util.DoubleSHA(Encoding.ASCII.GetBytes("Payment Channel User"));
HdNode userWallet = HdNode.Create(entropy, mainNet: false).GetSecretChild(0);
// BIP32 -- AP/0'
entropy = Util.DoubleSHA(Encoding.ASCII.GetBytes("Payment Channel AP"));
HdNode apWallet = HdNode.Create(entropy, mainNet: false).GetSecretChild(0);
// The TxID on Test-Net funding some unspent balances into our HD wallets
byte[] unspentTxId = Util.HexToBytes("96f9ac822c0259b8e55067141c3a438f4ea65492244149aea306a4197b638468");
Array.Reverse(unspentTxId);
// Prep the User's unspent
Script userUnspentScriptPubKey = TxOut.Addr(userUnspent, userWallet.GetPublicChild(0, 0).PublicKey).ScriptPubKey;
TxIn userUnspentTxIn = new TxIn(unspentTxId, 2); // Output '2' of 'unspentTxId' is the users
// Prep a AP's unspent
// Spend from AP/0'/0/0
Script apUnspentScriptPubKey = TxOut.Addr(apUnspent, apWallet.GetPublicChild(0, 0).PublicKey).ScriptPubKey;
TxIn apUnspentTxIn = new TxIn(unspentTxId, 0); // Output '0' of 'unspentTxId' is the users
// 1) User creates an unsigned TX1 with 1 or more inputs from user’s ‘listunspent’,
// change going back to user (if any), and a single output of ‘FundAmount’
// with scriptPubKey of ‘2 PK1 OP_0 2 CHECKMULTISIG’, and sends to the AP
Transaction tx1 = new Transaction();
tx1.Vin.Add(userUnspentTxIn);
// MULTISIG is ‘2 PK1 OP_0 2 CHECKMULTISIG’
// Setup PK1 from User/0'/0/1
tx1.Vout.Add(TxOut.MultiSig(escrowAmount, 2, 2, userWallet.GetPublicChild(0, 1).PublicKey));
// Output change back to User/0'/1/0
tx1.Vout.Add(TxOut.Addr(userChange, userWallet.GetPublicChild(1, 0).PublicKey));
Console.WriteLine("Step 1) Unsigned Funding Transaction:");
Console.WriteLine(" Raw: " + Util.BytesToHex(tx1.Serialize()));
Console.WriteLine(" TxID: " + Util.BytesToHexAsBigEndian(tx1.TxID));
// 2) AP adds to TX1; their inputs (if any), their change (if any), replaces OP_0 in the scriptPubKey
// with a PK they control, signs SIGHASH_ALL, and returns TX1 to User.
tx1.Vin.Add(apUnspentTxIn);
// Change back to AP/0'/1/0
tx1.Vout.Add(TxOut.Addr(apChange, apWallet.GetPublicChild(1, 0).PublicKey));
// Replace OP_0 in slot 2 with PK2 from AP/0'/0/1
tx1.Vout[0].ScriptPubKey.SetOp(2, apWallet.GetPublicChild(0, 1).PublicKey);
// AP signs Input 1 with AP/0'/0/0, and push the corresponding public key
tx1.Sign(apWallet.GetPublicChild(0, 0).PrivateKey, apUnspentScriptPubKey, 1, SigHash.All);
tx1.Vin[1].ScriptSig.Push(apWallet.GetPublicChild(0, 0).PublicKey);
Console.WriteLine("Step 2) AP Signed Funding Transaction: ");
Console.WriteLine(" Raw: " + Util.BytesToHex(tx1.Serialize()));
Console.WriteLine(" TxID: " + Util.BytesToHexAsBigEndian(tx1.TxID));
// 3) User verifies TX1 is as agreed, and signs it SIGHASH_ALL, but keeps it to himself.
// User, having completed TX1, knows its TxID and can now create TX2-Locked spending TX1 back to themselves.
// User sets nLockTime to the agreed point, signs SIGHASH_ALL, and sends TX2-Locked to AP.
// User signs Input 0 with User/0'/0/0, and push the corresponding public key
tx1.Sign(userWallet.GetPublicChild(0, 0).PrivateKey, userUnspentScriptPubKey, 0, SigHash.All); // User Signs Input 0
tx1.Vin[0].ScriptSig.Push(userWallet.GetPublicChild(0, 0).PublicKey);
Console.WriteLine("Step 3a) Fully Signed Funding Transaction: ");
Console.WriteLine(" Raw: " + Util.BytesToHex(tx1.Serialize()));
Console.WriteLine(" TxID: " + Util.BytesToHexAsBigEndian(tx1.TxID));
// Verify TX1 is complete given the scriptPubKeys from the input transactions
Assert.IsTrue(tx1.Verify(userUnspentScriptPubKey, apUnspentScriptPubKey));
// Create the Refund TX2 with SEQ = 0 and LockTime set, spending TX1
// Amount spend will be full amount minus a fee
// Send output to User/0'/0/2
Transaction tx2Locked = new Transaction();
tx2Locked.Vin.Add(new TxIn(tx1.TxID, 0));
tx2Locked.Vin[0].Sequence = 0;
tx2Locked.LockTime = lockTime;
tx2Locked.Vout.Add(TxOut.Addr(escrowAmount - Util.Amount(".0005"), userWallet.GetPublicChild(0, 2).PublicKey));
// Workaround historic Bitcoin bug...
tx2Locked.Vin[0].ScriptSig = Script.Parse(new byte[] { (byte)Script.OpCode.OP_0 });
// Add a signature to MULTISIG Input with User/0'/0/1
tx2Locked.Sign(userWallet.GetPublicChild(0, 1).PrivateKey, tx1.Vout[0].ScriptPubKey, 0, SigHash.All);
Console.WriteLine("Step 3b) User Signed TX2-Locked Refund Transaction: ");
Console.WriteLine(" Raw: " + Util.BytesToHex(tx2Locked.Serialize()));
Console.WriteLine(" TxID: " + Util.BytesToHexAsBigEndian(tx2Locked.TxID));
// 4) AP verifies TX2-Locked and adds their signature, and returns TX2-Locked to User.
// User can now broadcast TX1 and TX2-Locked.
// Add a signature to MULTISIG Input with AP/0'/0/1
tx2Locked.Sign(apWallet.GetPublicChild(0, 1).PrivateKey, tx1.Vout[0].ScriptPubKey, 0, SigHash.All);
Console.WriteLine("Step 4) AP Signed TX2-Locked Refund Transaction: ");
Console.WriteLine(" Raw: " + Util.BytesToHex(tx2Locked.Serialize()));
Console.WriteLine(" TxID: " + Util.BytesToHexAsBigEndian(tx2Locked.TxID));
// Verify Refund TX2 is complete given the input's scriptPubKey (MULTISIG)
Assert.IsTrue(tx2Locked.Verify(tx1.Vout[0].ScriptPubKey));
// 5) At each payment milestone, user creates TX2-Final; TX1 as input, final nSequence, no lock time,
// with a single output going back to to the user, and an amount equal to the remaining balance
// of the channel. User signs with SIGHASH_SINGLE and sends to the AP.
// TX2 with 3BTC back to the User/0'/0/2
Transaction tx2Final = new Transaction();
tx2Final.Vin.Add(new TxIn(tx1.TxID, 0));
tx2Final.Vout.Add(TxOut.Addr(Util.Amount("3"), userWallet.GetPublicChild(0, 2).PublicKey));
// Workaround historic Bitcoin bug...
tx2Final.Vin[0].ScriptSig = Script.Parse(new byte[] { (byte)Script.OpCode.OP_0 });
// Add a signature to MULTISIG Input with User/0'/0/1, SIGHASH_SINGLE
tx2Final.Sign(userWallet.GetPublicChild(0, 1).PrivateKey, tx1.Vout[0].ScriptPubKey, 0, SigHash.Single);
Console.WriteLine("Step 5) Partial 2BTC Release from User: ");
Console.WriteLine(" Raw: " + Util.BytesToHex(tx2Final.Serialize()));
Console.WriteLine(" TxID: " + Util.BytesToHexAsBigEndian(tx2Final.TxID));
// 6) AP can add an output to TX2-Final sending their portion of the coins where ever they want,
// sign it SIGHASH_ALL, and broadcast it at any point before nLockTime, closing the channel.
// Pay the balance to AP/0'/0/2, minus 0.0005 fee
tx2Final.Vout.Add(TxOut.Addr(Util.Amount("1.9995"), apWallet.GetPublicChild(0, 2).PublicKey));
// Add a signature to MULTISIG Input with AP/0'/0/1, SIGHASH_ALL
tx2Final.Sign(apWallet.GetPublicChild(0, 1).PrivateKey, tx1.Vout[0].ScriptPubKey, 0, SigHash.All);
Console.WriteLine("Step 6) AP Closes Payment Channel: ");
Console.WriteLine(" Raw: " + Util.BytesToHex(tx2Final.Serialize()));
Console.WriteLine(" TxID: " + Util.BytesToHexAsBigEndian(tx2Final.TxID));
Assert.IsTrue(tx2Final.Verify(tx1.Vout[0].ScriptPubKey));
Step 1) Unsigned Funding Transaction:
Raw: 01000000016884637B19A406A3AE4941249254A64E8F433A1C146750E5B859022C82ACF9960200000000FFFFFFFF020065CD1D00000000265221035F3CC5A102F06F4FFBD8364DE0E224BDCA0C72263252BC1748742223624492820052AEE08FB106000000001976A9148C6DF2178FABB7467C81A741EAEAB4ACEF0B0E2388AC00000000
TxID: B8345F01EF074F4A6BA8B16E81FA5BEDA5617A356F88CAECAF9403EA9B28F018
Step 2) AP Signed Funding Transaction:
Raw: 01000000026884637B19A406A3AE4941249254A64E8F433A1C146750E5B859022C82ACF9960200000000FFFFFFFF6884637B19A406A3AE4941249254A64E8F433A1C146750E5B859022C82ACF996000000006B483045022100D3A13758BA268A9CB22409B70EBFF855CFE1B17F05B767BE0004AEFBC9E53DCF0220685C5A88A1795C6C977D7699DD91697114FD6B36FFD3A81E08D2D95D751BE2EB012103DF6BC1C448E327881C039EA151FE1BC356C8DE9E0698CB6B303C922DBBD975C9FFFFFFFF030065CD1D00000000475221035F3CC5A102F06F4FFBD8364DE0E224BDCA0C72263252BC1748742223624492822102ED8994D8920B72B924D758DAF85DBE4F7032B44FE119708143E31BA7813D2F7A52AEE08FB106000000001976A9148C6DF2178FABB7467C81A741EAEAB4ACEF0B0E2388AC70399B01000000001976A914849C3894294EF13A9FF7017FAB9F24A20AB51AE588AC00000000
TxID: 030F4EBAC4D977DEFFC65AA1D92D8C3543FE6C54565A6864E206A65232F0C7A9
Step 3a) Fully Signed Funding Transaction:
Raw: 01000000026884637B19A406A3AE4941249254A64E8F433A1C146750E5B859022C82ACF996020000006A473044022038D71C5BE10340A1C2185FBCD4519E1100634903814C09068E0AFA0D79DE8EC002206C00CC767B5227DFE83E28051624A6934223015D3B6AA8BEA68FA0B4E4CC45C20121025DD355F911396ECB07543FD4DA617D9E7C9752E1EC482139E43B5B47D8809614FFFFFFFF6884637B19A406A3AE4941249254A64E8F433A1C146750E5B859022C82ACF996000000006B483045022100D3A13758BA268A9CB22409B70EBFF855CFE1B17F05B767BE0004AEFBC9E53DCF0220685C5A88A1795C6C977D7699DD91697114FD6B36FFD3A81E08D2D95D751BE2EB012103DF6BC1C448E327881C039EA151FE1BC356C8DE9E0698CB6B303C922DBBD975C9FFFFFFFF030065CD1D00000000475221035F3CC5A102F06F4FFBD8364DE0E224BDCA0C72263252BC1748742223624492822102ED8994D8920B72B924D758DAF85DBE4F7032B44FE119708143E31BA7813D2F7A52AEE08FB106000000001976A9148C6DF2178FABB7467C81A741EAEAB4ACEF0B0E2388AC70399B01000000001976A914849C3894294EF13A9FF7017FAB9F24A20AB51AE588AC00000000
TxID: C4358D6D2A663878DBD0DAADC833D81F3165A8B27F079521695CDDF1F5F3AF5D
Step 3b) User Signed TX2-Locked Refund Transaction:
Raw: 01000000015DAFF3F5F1DD5C692195077FB2A865311FD833C8ADDAD0DB7838662A6D8D35C4000000004A004830450220286CDB2145C03832798AF23060CF4BD22210ADA781B4C502A7518E7F89F64963022100F593EC8678DE3AD1D151DDD1CB41FF16D14E5480B096F9E2EF1A636909E29B3C010000000001B0A1CC1D000000001976A9146B36A2619F64221C2D24C5BCA32301728EA5439288AC63067751
TxID: 24D60000CE8EE112B321DC68ADF57B1EFCD1C5AEE5932EFBB083B1F3CE299A0A
Step 4) AP Signed TX2-Locked Refund Transaction:
Raw: 01000000015DAFF3F5F1DD5C692195077FB2A865311FD833C8ADDAD0DB7838662A6D8D35C40000000092004830450220286CDB2145C03832798AF23060CF4BD22210ADA781B4C502A7518E7F89F64963022100F593EC8678DE3AD1D151DDD1CB41FF16D14E5480B096F9E2EF1A636909E29B3C0147304402204343DF2F601E6DFB85D3784B980ABAB243B5E94BF4035D48D2ABCCAC4E20D17A0220593D4F5AED4BB000943650112C69CA39B68286CFF0A83B9E2E102412FAE17BCC010000000001B0A1CC1D000000001976A9146B36A2619F64221C2D24C5BCA32301728EA5439288AC63067751
TxID: 2B85DF59F75C044A17E4680F58F3F7449933C7279FB42314159AD4F35E603784
Step 5) Partial 2BTC Release from User:
Raw: 01000000015DAFF3F5F1DD5C692195077FB2A865311FD833C8ADDAD0DB7838662A6D8D35C400000000490047304402204F833B0FB58C63AEF62BC30474332DBD4A4DB31BEBF2532DFADAD0F11535B830022039637C254AC29FA0CEE941F461CA7AF262128C2546EF6208FA66E739E911755803FFFFFFFF0100A3E111000000001976A9146B36A2619F64221C2D24C5BCA32301728EA5439288AC00000000
TxID: 7CA4EF10FAB27584D89BF8DE4E17DD038C2094BAE1ACDD76002A0EF6E9B96EB0
Step 6) AP Closes Payment Channel:
Raw: 01000000015DAFF3F5F1DD5C692195077FB2A865311FD833C8ADDAD0DB7838662A6D8D35C400000000910047304402204F833B0FB58C63AEF62BC30474332DBD4A4DB31BEBF2532DFADAD0F11535B830022039637C254AC29FA0CEE941F461CA7AF262128C2546EF6208FA66E739E91175580347304402207AC76A2EB31FE15242BC602395258B2D3CC9E36B6E6DA1819CB1F81794E242F6022034228D0EA3D84C20F29B9BC20455BB8EF666A925151008B7D449D5C277CC39B001FFFFFFFF0200A3E111000000001976A9146B36A2619F64221C2D24C5BCA32301728EA5439288ACB0FEEA0B000000001976A9142F8872AA0F6918FF45BC61272D9A68AC3EF0C54388AC00000000
TxID: 2F6B75669CE595CA3BB13458A7E848F113A96F7761E10FD235C4CFBE605DBFCD
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment