//https://gist.github.com/gavinandresen/3966071 public void CanPartiallySignTransaction() { var privKeys = new[]{"5JaTXbAUmfPYZFRwrYaALK48fN6sFJp4rHqq2QSXs8ucfpE4yQU", "5Jb7fCeh1Wtm4yBBg3q3XbT6B525i17kVhy3vMC9AqfR6FH2qGk", "5JFjmGo5Fww9p8gvx48qBYDJNAzR9pmH5S389axMtDyPT8ddqmw"} .Select(k => new BitcoinSecret(k).PrivateKey).ToArray(); //First: combine the three keys into a multisig address var redeem = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, privKeys.Select(k => k.PubKey).ToArray()); var scriptAddress = redeem.Hash.GetAddress(Network.Main); Assert.Equal("3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC", scriptAddress.ToString()); // Next, create a transaction to send funds into that multisig. Transaction d6f72... is // an unspent transaction in my wallet (which I got from the 'listunspent' RPC call): // Taken from example var fundingTransaction = Transaction.Parse("010000000189632848f99722915727c5c75da8db2dbf194342a0429828f66ff88fab2af7d6000000008b483045022100abbc8a73fe2054480bda3f3281da2d0c51e2841391abd4c09f4f908a2034c18d02205bc9e4d68eafb918f3e9662338647a4419c0de1a650ab8983f1d216e2a31d8e30141046f55d7adeff6011c7eac294fe540c57830be80e9355c83869c9260a4b8bf4767a66bacbd70b804dc63d5beeb14180292ad7f3b083372b1d02d7a37dd97ff5c9effffffff0140420f000000000017a914f815b036d9bbbce5e9f2a00abd1bf3dc91e955108700000000"); // Create the spend-from-multisig transaction. Since the fund-the-multisig transaction // hasn't been sent yet, I need to give txid, scriptPubKey and redeemScript: var spendTransaction = new Transaction(); spendTransaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(fundingTransaction.GetHash(), 0), }); spendTransaction.Outputs.Add(new TxOut() { Value = "0.01000000", ScriptPubKey = new Script("OP_DUP OP_HASH160 ae56b4db13554d321c402db3961187aed1bbed5b OP_EQUALVERIFY OP_CHECKSIG") }); spendTransaction.Inputs[0].ScriptSig = redeem; //The redeem should be in the scriptSig before signing var partiallySigned = spendTransaction.Clone(); //... Now I can partially sign it using one private key: partiallySigned.Sign(privKeys[0], true); //the other private keys (note the "hex" result getting longer): partiallySigned.Sign(privKeys[1], true); AssertCorrectlySigned(partiallySigned, fundingTransaction.Outputs[0].ScriptPubKey, allowHighS); //Verify the transaction from the gist is also correctly signed var gistTransaction = Transaction.Parse("0100000001aca7f3b45654c230e0886a57fb988c3044ef5e8f7f39726d305c61d5e818903c00000000fd5d010048304502200187af928e9d155c4b1ac9c1c9118153239aba76774f775d7c1f9c3e106ff33c0221008822b0f658edec22274d0b6ae9de10ebf2da06b1bbdaaba4e50eb078f39e3d78014730440220795f0f4f5941a77ae032ecb9e33753788d7eb5cb0c78d805575d6b00a1d9bfed02203e1f4ad9332d1416ae01e27038e945bc9db59c732728a383a6f1ed2fb99da7a4014cc952410491bba2510912a5bd37da1fb5b1673010e43d2c6d812c514e91bfa9f2eb129e1c183329db55bd868e209aac2fbc02cb33d98fe74bf23f0c235d6126b1d8334f864104865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac09ef122b1a986818a7cb624532f062c1d1f8722084861c5c3291ccffef4ec687441048d2455d2403e08708fc1f556002f1b6cd83f992d085097f9974ab08a28838f07896fbab08f39495e15fa6fad6edbfb1e754e35fa1c7844c41f322a1863d4621353aeffffffff0140420f00000000001976a914ae56b4db13554d321c402db3961187aed1bbed5b88ac00000000"); AssertCorrectlySigned(gistTransaction, fundingTransaction.Outputs[0].ScriptPubKey, allowHighS); //One sig in the hard code tx is high //Can sign out of order partiallySigned = spendTransaction.Clone(); partiallySigned.Sign(privKeys[2], true); partiallySigned.Sign(privKeys[0], true); AssertCorrectlySigned(partiallySigned, fundingTransaction.Outputs[0].ScriptPubKey); //Can sign multiple inputs partiallySigned = spendTransaction.Clone(); partiallySigned.Inputs.Add(new TxIn() { PrevOut = new OutPoint(fundingTransaction.GetHash(), 1), }); partiallySigned.Inputs[1].ScriptSig = redeem; //The redeem should be in the scriptSig before signing partiallySigned.Sign(privKeys[2], true); partiallySigned.Sign(privKeys[0], true); }
public void CanBuildTransaction() { var keys = Enumerable.Range(0, 5).Select(i => new Key()).ToArray(); var multiSigPubKey = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, keys.Select(k => k.PubKey).Take(3).ToArray()); var pubKeyPubKey = PayToPubkeyTemplate.Instance.GenerateScriptPubKey(keys[4].PubKey); var pubKeyHashPubKey = PayToPubkeyHashTemplate.Instance.GenerateScriptPubKey(keys[4].PubKey.Hash); var scriptHashPubKey1 = PayToScriptHashTemplate.Instance.GenerateScriptPubKey(multiSigPubKey.Hash); var scriptHashPubKey2 = PayToScriptHashTemplate.Instance.GenerateScriptPubKey(pubKeyPubKey.Hash); var scriptHashPubKey3 = PayToScriptHashTemplate.Instance.GenerateScriptPubKey(pubKeyHashPubKey.Hash); var coins = new[] { multiSigPubKey, pubKeyPubKey, pubKeyHashPubKey }.Select((script, i) => new Coin ( new OutPoint(Rand(), i), new TxOut(new Money((i + 1) * Money.COIN), script) )).ToList(); var scriptCoins = new[] { scriptHashPubKey1, scriptHashPubKey2, scriptHashPubKey3 } .Zip(new[] { multiSigPubKey, pubKeyPubKey, pubKeyHashPubKey }, (script, redeem) => new { script, redeem }) .Select((_, i) => new ScriptCoin ( new OutPoint(Rand(), i), new TxOut(new Money((i + 1) * Money.COIN), _.script), _.redeem )).ToList(); var witCoins = new[] { scriptHashPubKey1, scriptHashPubKey2, scriptHashPubKey3 } .Zip(new[] { multiSigPubKey, pubKeyPubKey, pubKeyHashPubKey }, (script, redeem) => new { script, redeem }) .Select((_, i) => new WitScriptCoin ( new OutPoint(Rand(), i), new TxOut(new Money((i + 1) * Money.COIN), _.redeem.WitHash.ScriptPubKey.Hash), _.redeem )).ToList(); var a = witCoins.Select(c => c.Amount).Sum(); var allCoins = coins.Concat(scriptCoins).Concat(witCoins).ToArray(); var destinations = keys.Select(k => k.PubKey.GetAddress(Network.Main)).ToArray(); var txBuilder = new TransactionBuilder(0); txBuilder.StandardTransactionPolicy = EasyPolicy; var tx = txBuilder .AddCoins(allCoins) .AddKeys(keys) .Send(destinations[0], Money.Parse("6") * 2) .Send(destinations[2], Money.Parse("5")) .Send(destinations[2], Money.Parse("0.9999")) .SendFees(Money.Parse("0.0001")) .SetChange(destinations[3]) .BuildTransaction(true); Assert.True(txBuilder.Verify(tx, "0.0001")); Assert.Equal(3, tx.Outputs.Count); txBuilder = new TransactionBuilder(0); txBuilder.StandardTransactionPolicy = EasyPolicy; tx = txBuilder .AddCoins(allCoins) .AddKeys(keys) .SetGroupName("test") .Send(destinations[0], Money.Parse("6") * 2) .Send(destinations[2], Money.Parse("5")) .Send(destinations[2], Money.Parse("0.9998")) .SendFees(Money.Parse("0.0001")) .SetChange(destinations[3]) .BuildTransaction(true); Assert.Equal(4, tx.Outputs.Count); //+ Change txBuilder.Send(destinations[4], Money.Parse("1")); var ex = Assert.Throws<NotEnoughFundsException>(() => txBuilder.BuildTransaction(true)); Assert.True(ex.Group == "test"); Assert.True((Money)ex.Missing == Money.Parse("0.9999")); //Can sign partially txBuilder = new TransactionBuilder(0); txBuilder.StandardTransactionPolicy = EasyPolicy; tx = txBuilder .AddCoins(allCoins) .AddKeys(keys.Skip(2).ToArray()) //One of the multi key missing .Send(destinations[0], Money.Parse("6") * 2) .Send(destinations[2], Money.Parse("5")) .Send(destinations[2], Money.Parse("0.9998")) .SendFees(Money.Parse("0.0001")) .SetChange(destinations[3]) .Shuffle() .BuildTransaction(true); Assert.False(txBuilder.Verify(tx, "0.0001")); txBuilder = new TransactionBuilder(0); tx = txBuilder .AddKeys(keys[0]) .AddCoins(allCoins) .SignTransaction(tx); Assert.True(txBuilder.Verify(tx)); //Test if signing separatly txBuilder = new TransactionBuilder(0); txBuilder.StandardTransactionPolicy = EasyPolicy; tx = txBuilder .AddCoins(allCoins) .AddKeys(keys.Skip(2).ToArray()) //One of the multi key missing .Send(destinations[0], Money.Parse("6") * 2) .Send(destinations[2], Money.Parse("5")) .Send(destinations[2], Money.Parse("0.9998")) .SendFees(Money.Parse("0.0001")) .SetChange(destinations[3]) .Shuffle() .BuildTransaction(false); var signed1 = txBuilder.SignTransaction(tx); txBuilder = new TransactionBuilder(0); var signed2 = txBuilder .AddKeys(keys[0]) .AddCoins(allCoins) .SignTransaction(tx); Assert.False(txBuilder.Verify(signed1)); Assert.False(txBuilder.Verify(signed2)); txBuilder = new TransactionBuilder(0); txBuilder.StandardTransactionPolicy = EasyPolicy; tx = txBuilder .AddCoins(allCoins) .CombineSignatures(signed1, signed2); Assert.True(txBuilder.Verify(tx)); //Check if can deduce scriptPubKey from P2SH and P2SPKH scriptSig allCoins = new[] { RandomCoin(Money.Parse("1.0"), keys[0].PubKey.Hash.ScriptPubKey, false), RandomCoin(Money.Parse("1.0"), keys[0].PubKey.Hash.ScriptPubKey, false), RandomCoin(Money.Parse("1.0"), keys[1].PubKey.Hash.ScriptPubKey, false) }; txBuilder = new TransactionBuilder(0); txBuilder.StandardTransactionPolicy = EasyPolicy; tx = txBuilder.AddCoins(allCoins) .Send(destinations[0], Money.Parse("3.0")) .BuildTransaction(false); signed1 = new TransactionBuilder(0) .AddCoins(allCoins) .AddKeys(keys[0]) .SignTransaction(tx); signed2 = new TransactionBuilder(0) .AddCoins(allCoins) .AddKeys(keys[1]) .SignTransaction(tx); Assert.False(txBuilder.Verify(signed1)); Assert.False(txBuilder.Verify(signed2)); tx = new TransactionBuilder(0) .CombineSignatures(signed1, signed2); Assert.True(txBuilder.Verify(tx)); //Using the same set of coin in 2 group should not use two times the sames coins for(int i = 0 ; i < 3 ; i++) { txBuilder = new TransactionBuilder(); txBuilder.StandardTransactionPolicy = EasyPolicy; tx = txBuilder .AddCoins(allCoins) .AddKeys(keys) .Send(destinations[0], Money.Parse("2.0")) .Then() .AddCoins(allCoins) .AddKeys(keys) .Send(destinations[0], Money.Parse("1.0")) .BuildTransaction(true); Assert.True(txBuilder.Verify(tx)); } }