Introduction
This article assumes that you already know how Bitcoin works. I wrote this article, and this one to explain some basics.
Signing and building a transaction in Bitcoin is conceptually simple to understand but messy in the details.
Every time you want to spend a coin, you have to prove ownership or partial ownership of it, by signing the transaction that spends it. The exact process is complicated, and you can get a preview here.
So, my goal was to make any type of standard transaction easy to build and sign.
TransactionBuilder goes further by supporting the Open Asset protocol (so you can emit and transact assets within the Bitcoin network, without the need for a financial intermediary) as well as Stealth Payment (for protecting your privacy).
You can find all examples in an easy to run console application linked to this article.
Content
- Design
- P2PK, P2PKH, Multi Sig payment
- Alice sends to Satoshi
- Alice then Bob sends to Satoshi
- AliceBobNico corp. sends to Satoshi
- P2SH payment
- AliceBobNico corp. sends to Satoshi
- Stealth Payment
- DarkSatoshi sends money to DarkBob and Alice
- Colored coin Payment
- GoldGuy emits gold to Nico and Satoshi, SilverGuy emits silver to Alice
- Nico sends gold to Satoshi.
- Satoshi and Alice wants to swap gold against silver and BTC.
- The Transaction of the Hell (only for warriors)
- GoldGuy and SilverGuy agrees to emit Platinium to Satoshi.
- Satoshi wants to trade Platinium against the Silver of Alice, the Gold of Nico, BTC from DarkBob and BTC from AliceBobNico corp.
- Conclusion
Design
TransactionBuilder only needs to know the following :
- What Coins it can spend
- What Private Keys he knows
- Where does it send the money
For the first point, there is multiple type of Coin someone can spend.
For P2PK, P2PKH and Multi Sig coins, you will only need the Coin class, that is, it’s Outpoint (id), and it’s TxOut (how much and to who it is sent).
ScriptCoin is the same thing, for P2SH payment, you need to know the Redeem.
StealthCoin is the same thing as Coin, but you have to know the BitcoinStealthAddress it is attached to, as well as the StealthMetadata. (A special OP_RETURN in the transaction, as I explained here)
Then we have other category of Coins : The Colored Coins.
The Amount property of a ColoredCoin, is the amount of Asset, identified by AssetId, it bears.
The Bearer is the underlying Coin with BTC attached to it (most likely dust)
IssuanceCoin can be spent only by the issuer of the asset, and its purpose is to issue new asset.
ColoredCoin represents an asset (gold for example) attached to a Coin.
Once you get the coins you want to spend, then the TransactionBuilder with its fluent interface.
You use AddCoins to tell what coins you can spend.
You use AddKeys to tell what private key you know.
You use Send to send bitcoins, SendAsset to send colored coins, or IssueAsset to issue assets.
SetChange , as explained in my precedent article is where any change should be sent.
SendFees, give some fees to the miners so your transaction is validated more quickly.
BuildTransaction will select the coins based on your wish, then optionally signing (potentially partially) your transaction.
SetCoinSelector allows you to customize the algorithm that select coins to cover the sends.
SignTransaction signs a transaction with the keys the TransactionBuilder knows.
Shuffle will shuffle all what you did until now, so a blockchain observer can't easily find change addresses, or guess other information from TxOut ordering.
Verify will verify that a Transaction is fully signed and ready to send to the network.
Despite the few number of methods, you will find endless expressiveness and fun for all transactions you ever dreamed.
P2PK, P2PKH, Multi Sig payment
Let’s me introduce you, Alice, Bob, Satoshi, and Nico with their private keys.
BitcoinSecret alice = new BitcoinSecret("KyJTjvFpPF6DDX4fnT56d2eATPfxjdUPXFFUb85psnCdh34iyXRQ");
BitcoinSecret bob = new BitcoinSecret("KysJMPCkFP4SLsEQAED9CzCurJBkeVvAa4jeN1BBtYS7P5LocUBQ");
BitcoinSecret nico = new BitcoinSecret("L2uC8xNjmcfwje6eweucYvFsmKASbMDALy4rCJBAg8wofpH6barj");
BitcoinSecret satoshi = new BitcoinSecret("L1CpAon5d8zroENbkiMbk3dtd3kcbms6QGF5x475KKTMmXVaJXh3");
Alice sends to Satoshi
Alice received two coins in the past in the following transaction.
{
"hash": "920edde4ef144b8242d9bc82cef7186d552e06b30443aef1888ac6e4eb868ebf",
"out": [
{
"value": "0.45000000",
"scriptPubKey": "OP_DUP OP_HASH160 d4339bbd21720095c6842af39ae64c39666bf65c OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.80000000",
"scriptPubKey": "036d1b36a3a779b12f18c637e7c3ecaf0dfa08699eee276b61d169fc3521f391e6 OP_CHECKSIG"
}
]
}
You can recognize that one of the coin is a P2PKH, and the other P2PK one. The only thing you need to know is that both are spendable if Alice keeps her private key.
We need to turn that transaction outputs to Coin.
Transaction aliceFunding = new Transaction()
{
Outputs =
{
new TxOut("0.45", alice.GetAddress()),
new TxOut("0.8", alice.Key.PubKey)
}
};
Coin[] aliceCoins = aliceFunding
.Outputs
.Select((o, i) => new Coin(new OutPoint(aliceFunding.GetHash(), i), o))
.ToArray();
Now Alice wants to send 1.00 BTC to Satoshi, with 0.001 BTC fees for miners.
var txBuilder = new TransactionBuilder();
var tx = txBuilder
.AddCoins(aliceCoins)
.AddKeys(alice.Key)
.Send(satoshi.GetAddress(), "1.00")
.SendFees("0.001")
.SetChange(alice.GetAddress())
.BuildTransaction(true);
Assert(txBuilder.Verify(tx));
This give me the following transaction
{
"hash": "e66bb75f274aa0b0d345f6d81a7ec3d8e945bb7bee27c4d12df6116197effe9f",
"in": [
{
"prev_out": {
"hash": "920edde4ef144b8242d9bc82cef7186d552e06b30443aef1888ac6e4eb868ebf",
"n": 0
},
"scriptSig": "304402205a72ca5732613578ded3e83231d2d06dcf72af31f3a69c03b65b42f98ca2f24c02206abdcfcbe95740c0290af25a7db8b68ef91978de9e8192508aa9d0852ef3b6a601 036d1b36a3a779b12f18c637e7c3ecaf0dfa08699eee276b61d169fc3521f391e6"
},
{
"prev_out": {
"hash": "920edde4ef144b8242d9bc82cef7186d552e06b30443aef1888ac6e4eb868ebf",
"n": 1
},
"scriptSig": "3044022011186ae03d8caba31f67e66195b4ca1424887465449fc1920759c0f1743fd07702203352e77fbc67da23a1af744bc2741dcf27f75fc90e7f167397f807aa1ac9195601"
}
],
"out": [
{
"value": "0.24900000",
"scriptPubKey": "OP_DUP OP_HASH160 d4339bbd21720095c6842af39ae64c39666bf65c OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "1.00000000",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
}
]
}
Judging from the 2 inputs, you can see that Alice’s two coins got spent. (1.25 BTC)
You can also see that 0.249 is sent back to her (change), 1 sent to Satoshi, and so this means 0.001 of fees. (Signatures are deterministic, so you will get the same by running the source code)
Alice then Bob sends to Satoshi
So first, let’s create some coins to Bob, as I did for Alice.
Transaction bobFunding = new Transaction()
{
Outputs =
{
new TxOut("0.1", bob.GetAddress()),
new TxOut("1.8", bob.Key.PubKey)
}
};
Coin[] bobCoins = bobFunding
.Outputs
.Select((o, i) => new Coin(new OutPoint(bobFunding .GetHash(), i), o))
.ToArray();
Now, let’s says that Alice wants to send 0.8 BTC, Bob wants to send 0.2 BTC both to Satoshi. They want to share the Fees for 0.0005 each. This give us :
txBuilder = new TransactionBuilder();
tx = txBuilder
.AddCoins(aliceCoins)
.AddKeys(alice.Key)
.Send(satoshi.GetAddress(), "0.8")
.SetChange(alice.GetAddress())
.SendFees("0.0005")
.Then()
.AddCoins(bobCoins)
.AddKeys(bob.Key)
.Send(satoshi.GetAddress(), "0.2")
.SetChange(bob.GetAddress())
.SendFees("0.0005")
.BuildTransaction(true);
Assert(txBuilder.Verify(tx));
Notice the Then method that allows you to build the first part of the transaction of Alice independently of Bob’s part : each with their own change and fees.
Here is the result :
{
"hash": "80e477b5b22258cee77783d39e2509c5956cd69e141ad387265fe142032209db",
"in": [
{
"prev_out": {
"hash": "920edde4ef144b8242d9bc82cef7186d552e06b30443aef1888ac6e4eb868ebf",
"n": 0
},
"scriptSig": "3045022100b3176e8c1164f95e99b6efdb51785662858f30aed6464b25302aa8202654854d02205d765b07b3261d600762ab71d5f527e6dad075ff40dd5f74089687ed2489bb8f01 036d1b36a3a779b12f18c637e7c3ecaf0dfa08699eee276b61d169fc3521f391e6"
},
{
"prev_out": {
"hash": "920edde4ef144b8242d9bc82cef7186d552e06b30443aef1888ac6e4eb868ebf",
"n": 1
},
"scriptSig": "3044022003925cc41117dc8a690821cd98c3941fe54756ac9b8b558f017725457a9e9650022078f1eba5aeac6d656b4cce6f13f8f56815568eed66c6ea00597d0c1f42ed222a01"
},
{
"prev_out": {
"hash": "bb82e395bcbee95c7180929f879459e5997b768ea353f6e2528008819668cf5b",
"n": 1
},
"scriptSig": "3045022100cd49a8d8d1383a53a0c43eb3b5472b819847c8886caf163e3a4d23d53be7eadb02206c85c344281ffc17b0d0bdd8f4b3be3a14cf96375d3e1b9173395918e288ebf901"
}
],
"out": [
{
"value": "0.44950000",
"scriptPubKey": "OP_DUP OP_HASH160 d4339bbd21720095c6842af39ae64c39666bf65c OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.80000000",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "1.59950000",
"scriptPubKey": "OP_DUP OP_HASH160 495b82baffbe66589faa94cc1edee46aa8272032 OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.20000000",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
}
]
}
AliceBobNico corp. send to Satoshi
Alice, Bob, Nico are the founders of AliceBobNico corp. They agreed to store the company’s funds on multi sig so at least 2 of the funders needs to agree to spend any money.
Let’s create AliceBobNico’s scriptPubKey, where funds are sent.
Script AliceBobNicoCorp = PayToMultiSigTemplate
.Instance
.GenerateScriptPubKey(2, new[] { alice.Key.PubKey, bob.Key.PubKey, nico.Key.PubKey });
Which gives us the multi sig script storing the funds of the company :
2 036d1b36a3a779b12f18c637e7c3ecaf0dfa08699eee276b61d169fc3521f391e6 02e49a9a8f28dc8ef8f9987d8e04d8294abadee5db1fef3b7db699b4b7c0964d28 028c018fdcaec8cef2d02ae0af19e4d9bf14cf6845c0445741bef5a79c44313f41 3 OP_CHECKMULTISIG
Let’s funds this address with some coins, as we did for Bob and Alice earlier.
Transaction corpFunding = new Transaction()
{
Outputs =
{
new TxOut("10", AliceBobNicoCorp),
new TxOut("12", AliceBobNicoCorp),
new TxOut("20", AliceBobNicoCorp)
}
};
Coin[] corpCoins = corpFunding
.Outputs
.Select((o, i) => new Coin(new OutPoint(corpFunding.GetHash(), i), o))
.ToArray();
So now, AliceBobNico wants to send money to Satoshi. However, Bob is absent from office, Nico is traveling, and only Alice is left to the office. So Alice has no other way than partially signing the transaction, and then send it by email to Nico so he can also sign.
txBuilder = new TransactionBuilder();
tx = txBuilder
.AddCoins(corpCoins)
.AddKeys(alice.Key)
.Send(satoshi.GetAddress(), "4.5")
.SetChange(AliceBobNicoCorp)
.BuildTransaction(true);
Assert(!txBuilder.Verify(tx));
txBuilder = new TransactionBuilder();
tx = txBuilder
.AddCoins(corpCoins)
.AddKeys(nico.Key)
.SignTransaction(tx);
Assert(txBuilder.Verify(tx));
The point is that the TransactionBuilder will do its best to sign all he can with the keys it knows. So you can mix all type of spending in the same transaction and just ask to the TransactionBuilder to solve the mess as best as it can.
P2SH Payment
AliceBobNico corp. sends to Satoshi
Most of AliceBobNico’s customers do not support payment to the previous native multi signature script.
To send money to AliceBobNico, customers prefer having a simple address, not a complicated Bitcoin script that no wallet supports.
So AliceBobNico transforms their Script to a ScriptAddress, and give that to the customer.
var aliceBobNicoAddress = AliceBobNicoCorp.GetScriptAddress(Network.Main);
Which gives us 3FNCyS4ugTCV8XZWV7SRENUG31fe8uUkFU.
Let’s fund AliceBobNico with a coin sent by the customer :
Transaction corpFundingP2SH = new Transaction()
{
Outputs =
{
new TxOut("40", aliceBobNicoAddress)
}
};
Coin[] corpCoinsP2SH = corpFundingP2SH
.Outputs
.Select((o, i) => new ScriptCoin(new OutPoint(corpFundingP2SH.GetHash(), i), o, AliceBobNicoCorp))
.ToArray();
Note that I am using a ScriptCoin instead of a simple Coin, the reason is that the TransactionBuilder needs the real Script (called the Redeem Script) behind the ScriptAddress to correctly sign. (In our case it is the multi sig script in the previous part)
AliceBobNico wants to send 45 BTC to Satoshi. So it will need native multi sig coins from the previous part, but also the 40 BTC of the P2SH multi sig just received from the customer.
txBuilder = new TransactionBuilder();
tx = txBuilder
.AddCoins(corpCoins)
.AddCoins(corpCoinsP2SH)
.AddKeys(alice.Key, bob.Key)
.Send(satoshi.GetAddress(), "45")
.SetChange(aliceBobNicoAddress)
.BuildTransaction(true);
Assert(txBuilder.Verify(tx));
Which gives us the following transaction, combining P2SH and native multi sig !
{
"hash": "1beb8e6b4b7b48c055a320017d3e170f37c8b5f0496c5e12e15e3f116e76ea22",
"in": [
{
"prev_out": {
"hash": "46f6fa184e6389ef59d66bb541513f38f15ff4a17fb16980e9cff98a6a623e7f",
"n": 0
},
"scriptSig": "0 304402205c15f56d7bb08d1ba46da2a97ecd59e28a712627153c4d52efd717e2781d786902206c8d720ddc795348f1c05efc98143fb4df1319ba271a12580ae58c7e0b8dbc2d01 30440220591760c20ae69de43a301ae89e9208f33e95074bb1412af7c3fd07f9d31e017e022059ddef2f8d4b916b6df1851083b93d38059a0563ec393f4361a5cec4d8ac2dd501"
},
{
"prev_out": {
"hash": "46f6fa184e6389ef59d66bb541513f38f15ff4a17fb16980e9cff98a6a623e7f",
"n": 1
},
"scriptSig": "0 3045022100edc9a6937f543790e25133b868c0578b1e3811434c375fe79009e9f5a8c5953202205db08237cbd6e97263117b40cf85067fe19a0cfa082a86b7ef1afe3e0a53425a01 3045022100941c219799b83315adaba7bd6720883791e657c8436d647ab4d562ccd97845a802202a3af553b810a30f880f9a5beb7d8369c8f96a084610cce3a986ee7b9ea6480701"
},
{
"prev_out": {
"hash": "46f6fa184e6389ef59d66bb541513f38f15ff4a17fb16980e9cff98a6a623e7f",
"n": 2
},
"scriptSig": "0 304402200737866cda75fff1eb17ae1127e8a6c60ea7754a839f406e103bbf72582a5cdd0220178b706008a666433bffee63de7fb719a4997c6b027d0390b5707071a49299c801 3044022030d27da1c0115a11477c60bcffca7b0038baaea50835e33e2e5f8f18a876e35a02207e3b88d1a0070f5a7a8fb322849aa56604f7797cb51967830c215de670caa54301"
},
{
"prev_out": {
"hash": "81aefa18ce1e1aded705638eae772a149dc66eb8e44cf9ee30c5adcb43e06216",
"n": 0
},
"scriptSig": "0 3045022100b49bcad7ac66b6f60606ab07c516000561d9816fe1f8f5fd54948a6a549204d002204bd25b5f8611f189ca404d537f65dac938122879d4b282d412f0c8b2d32285c201 3045022100e697655f6824906ea8adb935bbeffa301a20d5dbc4ae8ac24d568b44801bdda002200d01a493810bc19a5d6b269321e9a05047f7c520ad3609986681da2e67537c1601 5221036d1b36a3a779b12f18c637e7c3ecaf0dfa08699eee276b61d169fc3521f391e62102e49a9a8f28dc8ef8f9987d8e04d8294abadee5db1fef3b7db699b4b7c0964d2821028c018fdcaec8cef2d02ae0af19e4d9bf14cf6845c0445741bef5a79c44313f4153ae"
}
],
"out": [
{
"value": "37.00000000",
"scriptPubKey": "OP_HASH160 960314e508ba47cf7331e25482b6adab2e1b7fe7 OP_EQUAL"
},
{
"value": "45.00000000",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
}
]
}
Stealth Payment
DarkSatoshi sends money to DarkBob and Alice
For reason explained in a previous article DarkSatoshi wants a way to receive and spends StealthCoin, so an external observer of the blockchain can’t possibly taint (identify) addresses composing his wallet.
TransactionBuilder also support multi sig Stealth Address.
Using a DarkAliceBobNico Corp. is left as an exercise ! (with partial signing as well)
So, here you go, I present you DarkBob and DarkSatoshi.
var darkSatoshiScan = new BitcoinSecret("KzosPd4rMJvZCfkP6nSfb1NEiC5mqMLZ5wUTv4CX4t3k73AtieyS");
var darkSatoshiKey = new BitcoinSecret("KxAWjXqVkGuGduYmc1ESBbBiCsTQiw56br1p5RnwLSKknfykCmwr");
var darkSatoshiAddress =
new BitcoinStealthAddress(
darkSatoshiScan.Key.PubKey,
new[] { darkSatoshiKey.Key.PubKey },
1,
new BitField(3, 3),
Network.Main);
var darkBobScan = new BitcoinSecret("L2KMQVEv6SJMQiHKPaugoAbNCLJk3faAcMr9JZaPoVMRL2XAVGXj");
var darkBobKey = new BitcoinSecret("L2kPJ2rduUTABExr3D1kPF1DrLP4b3dp1FmcxKhE3kd8iagbCq5Q");
var darkBobAddress =
new BitcoinStealthAddress(
darkBobScan.Key.PubKey,
new[] { darkBobKey.Key.PubKey },
1,
new BitField(3, 3),
Network.Main);
Let’s fund DarkSatoshi.
Transaction darkSatoshiFunding = new Transaction();
darkSatoshiAddress.CreatePayment().AddToTransaction(darkSatoshiFunding, "2.5");
This give us a stealth transaction.
{
"hash": "f94b81bfc90f735b1284959cb6c1afd12c1d5cc01e19c31b6832b0acb9d21e20",
"ver": 1,
"vin_sz": 0,
"vout_sz": 2,
"lock_time": 0,
"size": 93,
"in": [],
"out": [
{
"value": "0.00000000",
"scriptPubKey": "OP_RETURN 0601000000026eb83a8483614c4670de94364cbcd794c9bc6315a26b8c8b756a7f13e49f814a"
},
{
"value": "2.50000000",
"scriptPubKey": "OP_DUP OP_HASH160 55965be7f26e287d6e09af43188e2fb1b62e0d15 OP_EQUALVERIFY OP_CHECKSIG"
}
]
}
Whoever in possession of the BitcoinStealthAddress and the ScanKey can extract the stealth coin out of it
var darkSatoshiCoin = StealthCoin.Find(darkSatoshiFunding, darkSatoshiAddress, darkSatoshiScan.Key);
Now DarkSatoshi will send money to DarkBob and Alice.
txBuilder = new TransactionBuilder();
tx = txBuilder
.AddCoins(darkSatoshiCoin)
.AddKeys(darkSatoshiKey.Key, darkSatoshiScan.Key)
.Send(darkBobAddress, "1.0")
.Send(alice.GetAddress(), "0.5")
.SetChange(satoshi.GetAddress()) .SendFees("0.01")
.BuildTransaction(true);
Assert(txBuilder.Verify(tx));
And in his turn, DarkBob can get the StealthCoin of this transaction thanks to his scan key and address.
var darkBobCoin = StealthCoin.Find(tx, darkBobAddress, darkBobScan.Key);
Assert(darkBobCoin != null);
Colored coin Payment
GoldGuy emits gold to Nico and Satoshi, SilverGuy emits silver to Alice
The two basics operations you can do in Open Asset is issuing assets, and transferring them.
But before transferring, we need to emit.
I present you GoldGuy and SilverGuy, eminent authority with the god-like power to create gold and silver out of thin air (… well more precisely, out of thin dust as you will see).
var goldGuy = new BitcoinSecret("KyuzoVnpsqW529yzozkzP629wUDBsPmm4QEkh9iKnvw3Dy5JJiNg");
var silverGuy = new BitcoinSecret("L4KvjpqDtdGEn7Lw6HdDQjbg74MwWRrFZMQTgJozeHAKJw5rQ2Kn");
For issuing an asset, GoldGuy (and SilverGuy alike) must spend what I call an IssuanceCoin, typically such coin has a BTC amount more or less equals to 600 Satoshi, which is less than a cent.
The sole purpose of such coin is to prove ownership of asset issuance right.
The AssetId of the IssuanceCoin is derived from the hash of its ScriptPubKey as explained in the specification.
Here is how I create my IssuanceCoins, as well as some normal Coin, for Satoshi and Nico we’ll use later.
var issuanceCoinsTransaction
= new Transaction()
{
Outputs =
{
new TxOut("1.0", goldGuy.Key.PubKey),
new TxOut("1.0", silverGuy.Key.PubKey),
new TxOut("1.0", nico.Key.PubKey),
new TxOut("1.0", satoshi.Key.PubKey),
}
};
IssuanceCoin[] issuanceCoins = issuanceCoinsTransaction
.Outputs
.Take(2)
.Select((o, i) => new Coin(new OutPoint(issuanceCoinsTransaction.GetHash(), i), o))
.Select(c => new IssuanceCoin(c))
.ToArray();
var goldIssuanceCoin = issuanceCoins[0];
var silverIssuanceCoin = issuanceCoins[1];
var nicoCoin = new Coin(new OutPoint(issuanceCoinsTransaction, 2), issuanceCoinsTransaction.Outputs[2]);
var satoshiCoin = new Coin(new OutPoint(issuanceCoinsTransaction, 3), issuanceCoinsTransaction.Outputs[3]);
var goldId = goldIssuanceCoin.AssetId;
var silverId = silverIssuanceCoin.AssetId;
Given a transaction, if you want to know which asset is transferred and issued, you need to know all the ancestors until the issuanceCoinsTransaction.
This is why I will keep track of these transations in a TransactionRepository.
var txRepo = new NoSqlTransactionRepository();
txRepo.Put(issuanceCoinsTransaction.GetHash(), issuanceCoinsTransaction);
However, since we don’t want to evaluate all the ancestors every times we requests a ColoredTransaction of a transaction, we also use a ColoredTransactionRepository, to keep track of ColoredTransaction already processed.
var ctxRepo = new NoSqlColoredTransactionRepository(txRepo);
Ok, so now let’s issue some Gold to Nico and Satoshi, and see if we correctly emitted assets.
txBuilder = new TransactionBuilder();
tx = txBuilder
.AddKeys(goldGuy.Key)
.AddCoins(goldIssuanceCoin)
.IssueAsset(satoshi.GetAddress(), new Asset(goldId, 20))
.IssueAsset(nico.GetAddress(), new Asset(goldId, 30))
.SetChange(goldGuy.Key.PubKey)
.BuildTransaction(true);
Assert(txBuilder.Verify(tx));
txRepo.Put(tx.GetHash(), tx);
var ctx = tx.GetColoredTransaction(ctxRepo);
Which gives us the following transaction.
{
"hash": "9384466f999c00e359e30bc382ac08145b77fe3229f19f6794c8c4bb58adec42",
"ver": 1,
"vin_sz": 1,
"vout_sz": 4,
"lock_time": 0,
"size": 255,
"in": [
{
"prev_out": {
"hash": "bc74b0b15c8b1c526209632a7611930f72980641d94394d7dc3d07549d311a0e",
"n": 0
},
"scriptSig": "3045022100ed0ce9377d9780256795a4b10d90f4ef0c2d82be6865835979784b7ccca0c8290220025e6121e7e8a2872ce36a83644921ec978db4474cd861e58ac700ac6f48cd2a01"
}
],
"out": [
{
"value": "0.00000600",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.00000600",
"scriptPubKey": "OP_DUP OP_HASH160 1a9ba6e78130d5f1bcb6fc79e371511ccd19117b OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.99998800",
"scriptPubKey": "031c99ad4cdfdd46b1867a9bef22d61b31ad6ca69ba1795285f7462e6a2c4eb357 OP_CHECKSIG"
},
{
"value": "0.00000000",
"scriptPubKey": "OP_RETURN 4f41010002141e00"
}
]
}
Which represent the following ColoredTransaction.
{
"inputs": [],
"issuances": [
{
"index": 0,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 20
},
{
"index": 1,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 30
}
],
"transfers": [],
"destructions": []
}
So the first TxOut bears 20 Gold, the second 30 Gold.
So, Satoshi and Nico, will now get their ColoredCoin that they can spend later.
var coloredCoins = ColoredCoin.Find(tx, ctx).ToArray();
var satoshiGold = coloredCoins[0];
var nicoGold = coloredCoins[1];
Now let’s emit Silver to Alice.
txBuilder = new TransactionBuilder();
tx = txBuilder
.AddKeys(silverGuy.Key)
.AddCoins(silverIssuanceCoin)
.IssueAsset(alice.GetAddress(), new Asset(silverId, 10))
.SetChange(silverGuy.Key.PubKey)
.BuildTransaction(true);
Assert(txBuilder.Verify(tx));
txRepo.Put(tx.GetHash(), tx);
var aliceSilver = ColoredCoin.Find(tx, ctxRepo).First();
Nico sends Gold to Satoshi
Nico wants to send 1 Gold to Satoshi.
txBuilder = new TransactionBuilder();
tx = txBuilder
.AddCoins(nicoGold)
.AddKeys(nico.Key)
.SendAsset(satoshi.GetAddress(), new Asset(goldId, 1))
.SetChange(nico.GetAddress())
.BuildTransaction(true);
Assert(txBuilder.Verify(tx));
txRepo.Put(tx.GetHash(), tx);
ctx = tx.GetColoredTransaction(ctxRepo);
Which gives us the following ColoredTransaction.
{
"inputs": [
{
"index": 0,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 30
}
],
"issuances": [],
"transfers": [
{
"index": 1,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 29
},
{
"index": 2,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 1
}
],
"destructions": []
}
29 Gold are returned to Nico, 1 sent to Satoshi.
Satoshi and Alice wants to swap gold against silver and BTC
Satoshi wants the Silver of Alice : 10 Gold against 9 Silver + 0.5 BTC.
Such example is done in one line, but Satoshi and Alice can also independently sign the Transaction with the same process as a multi sig payment that I talked earlier.
Normal Coin are necessary to pay the Dust of TxOut bearing Colored Coins.
txBuilder = new TransactionBuilder();
tx = txBuilder
.AddCoins(aliceSilver)
.AddCoins(aliceCoins)
.AddKeys(alice.Key)
.SendAsset(satoshi.GetAddress(), new Asset(silverId, 9))
.Send(satoshi.GetAddress(), "0.5")
.SetChange(alice.GetAddress())
.Then()
.AddCoins(satoshiGold)
.AddCoins(satoshiCoin)
.AddKeys(satoshi.Key)
.SendAsset(alice.GetAddress(), new Asset(goldId, 10))
.SetChange(satoshi.GetAddress())
.BuildTransaction(true);
Assert(txBuilder.Verify(tx));
txRepo.Put(tx.GetHash(), tx);
ctx = tx.GetColoredTransaction(ctxRepo);
The result is the following ColoredTransaction.
{
"inputs": [
{
"index": 0,
"asset": "ATLqDUahLMs1Jj5yPVmyxEJH2WkkHZeFT5",
"quantity": 10
},
{
"index": 2,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 20
}
],
"issuances": [],
"transfers": [
{
"index": 1,
"asset": "ATLqDUahLMs1Jj5yPVmyxEJH2WkkHZeFT5",
"quantity": 1
},
{
"index": 2,
"asset": "ATLqDUahLMs1Jj5yPVmyxEJH2WkkHZeFT5",
"quantity": 9
},
{
"index": 5,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 10
},
{
"index": 6,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 10
}
],
"destructions": []
}
For the following transaction:
{
"hash": "d678dc8f1595d064990e9d49dfa25260ab414b8b26a87a12e1f15c52a79a2d05",
"ver": 1,
"vin_sz": 4,
"vout_sz": 8,
"lock_time": 0,
"size": 792,
"in": [
{
"prev_out": {
"hash": "ef6dbcacb3aae5c676a9fcefd31ba5c5c8d2df4c604b1b98393ded3a06ea2031",
"n": 0
},
"scriptSig": "30440220392a85cfe9f394bcaf4814fc7f8de055db9e028690313c95fc59537de3f2bff8022057b6c61b75c80dbf88d91aa0769e3630d9d1248ea5103b4df9f9a3f5288b4cdb01 036d1b36a3a779b12f18c637e7c3ecaf0dfa08699eee276b61d169fc3521f391e6"
},
{
"prev_out": {
"hash": "920edde4ef144b8242d9bc82cef7186d552e06b30443aef1888ac6e4eb868ebf",
"n": 1
},
"scriptSig": "3044022024db80a8c9fb5033d6369f9ec1ff8893c32a8cc6235f84cb06fc93c7e256853802207a30cff0a7a7a5939724158d5a08f084c4139ec80ce80488b38e084427c0c0e101"
},
{
"prev_out": {
"hash": "9384466f999c00e359e30bc382ac08145b77fe3229f19f6794c8c4bb58adec42",
"n": 0
},
"scriptSig": "3045022100f209aa198f13651f84f2287e037d64f9a8b2a413f6072b034e0bdd8e5ed47b2f02206eb5b32803e4c12234a58762b84732b4b84148a71b01e13e1dc62feac52e5ad901 0211f452e6ed4daf193c3f1b3652e289ad57567e5e816f050e2116541a32098f15"
},
{
"prev_out": {
"hash": "bc74b0b15c8b1c526209632a7611930f72980641d94394d7dc3d07549d311a0e",
"n": 3
},
"scriptSig": "3044022015c9ebf6a1f6361075b3f5dddd9feb7722d1f3a7f3e605580523e1fc195a553702207106a382416d03b67e5a8ef46f3b63c06d6d732756deb866fc071803449b207a01"
}
],
"out": [
{
"value": "0.00000000",
"scriptPubKey": "OP_RETURN 4f41010006010900000a0a00"
},
{
"value": "0.00000600",
"scriptPubKey": "OP_DUP OP_HASH160 d4339bbd21720095c6842af39ae64c39666bf65c OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.00000600",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.29999400",
"scriptPubKey": "OP_DUP OP_HASH160 d4339bbd21720095c6842af39ae64c39666bf65c OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.50000000",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.00000600",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.00000600",
"scriptPubKey": "OP_DUP OP_HASH160 d4339bbd21720095c6842af39ae64c39666bf65c OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.99999400",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
}
]
}
As you can see, the change of Asset and BTC is correctly sent back to each owner.
The Transaction of the Hell (only for warriors)
GoldGuy and SilverGuy agrees to emit Platinium to Satoshi.
Satoshi wants to trade Platinium against the Silver of Alice, the Gold of Nico, BTC from DarkBob and BTC from AliceBobNico corp.
First, for emitting Platinium we will need multi sig issuance between GoldGuy and SilverGuy.
First let’s create a P2SH address for them.
var platiniumRedeem = PayToMultiSigTemplate.Instance
.GenerateScriptPubKey(2, new[] { goldGuy.Key.PubKey, silverGuy.Key.PubKey });
var platiniumAddress = platiniumRedeem.GetScriptAddress(Network.Main);
Let’s funds them some IssuanceCoin.
var issuancePlatiniumCoinsTransaction
= new Transaction()
{
Outputs =
{
new TxOut("1.0", platiniumAddress),
}
};
txRepo.Put(issuancePlatiniumCoinsTransaction.GetHash(), issuancePlatiniumCoinsTransaction);
IssuanceCoin issuancePlatiniumCoin = issuanceCoinsTransaction
.Outputs
.Select((o, i) => new Coin(new OutPoint(issuanceCoinsTransaction.GetHash(), i), o))
.Select(c => new IssuanceCoin(c))
.First();
var platiniumId = issuancePlatiniumCoin.AssetId;
Let’s emit 15 Platinium to Satoshi.
txBuilder = new TransactionBuilder();
tx = txBuilder
.AddKeys(goldGuy.Key, silverGuy.Key)
.AddCoins(issuancePlatiniumCoin)
.IssueAsset(satoshi.GetAddress(), new Asset(platiniumId, 10))
.SetChange(platiniumAddress)
.BuildTransaction(true);
Assert(txBuilder.Verify(tx));
txRepo.Put(tx.GetHash(), tx);
ctx = tx.GetColoredTransaction(ctxRepo);
var satoshiPlatinium = ColoredCoin.Find(tx, ctx).First();
Now, let’s make the trade :
Satoshi wants to trade 5 Platinium against 3 Silver of Alice, 2 Gold of Nico, 0.5 BTC from DarkBob and 2 BTC from AliceBobNico corp.
Once again, the transaction can be built, then signed later by all participant, independently. But for this article, I do it all in one line.
txBuilder = new TransactionBuilder();
tx = txBuilder
.AddKeys(satoshi.Key)
.AddCoins(satoshiPlatinium)
.AddCoins(satoshiCoin)
.SendAsset(nico.GetAddress(), new Asset(platiniumId, 3))
.SendAsset(alice.GetAddress(), new Asset(platiniumId, 2))
.SetChange(satoshi.GetAddress())
.Then()
.AddKeys(alice.Key)
.AddCoins(aliceSilver)
.AddCoins(aliceCoins)
.SendAsset(satoshi.GetAddress(), new Asset(silverId, 3))
.SetChange(alice.GetAddress())
.Then()
.AddCoins(nicoGold)
.AddCoins(nicoCoin)
.SendAsset(satoshi.GetAddress(), new Asset(goldId, 2))
.SetChange(nico.GetAddress())
.Then()
.AddCoins(darkBobCoin)
.AddKeys(darkBobKey, darkBobScan)
.Send(satoshi.GetAddress(), "0.5")
.SetChange(bob.GetAddress())
.Then()
.AddCoins(corpCoins)
.AddKeys(nico.Key, alice.Key)
.Send(satoshi.GetAddress(), "2.0")
.SetChange(AliceBobNicoCorp)
.BuildTransaction(true);
txRepo.Put(tx.GetHash(), tx);
ctx = tx.GetColoredTransaction(ctxRepo);
Which give us the expected ColoredTransaction;
{
"inputs": [
{
"index": 0,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 10
},
{
"index": 2,
"asset": "ATLqDUahLMs1Jj5yPVmyxEJH2WkkHZeFT5",
"quantity": 10
},
{
"index": 4,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 30
}
],
"issuances": [],
"transfers": [
{
"index": 1,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 5
},
{
"index": 2,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 3
},
{
"index": 3,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 2
},
{
"index": 5,
"asset": "ATLqDUahLMs1Jj5yPVmyxEJH2WkkHZeFT5",
"quantity": 7
},
{
"index": 6,
"asset": "ATLqDUahLMs1Jj5yPVmyxEJH2WkkHZeFT5",
"quantity": 3
},
{
"index": 8,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 28
},
{
"index": 9,
"asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1",
"quantity": 2
}
],
"destructions": []
}
And the expected Transaction.
{
"hash": "a6c42499c91575815d026a12ce2bb54e2078c86941ac3202656e117a20a32d36",
"ver": 1,
"vin_sz": 8,
"vout_sz": 15,
"lock_time": 0,
"size": 1742,
"in": [
{
"prev_out": {
"hash": "15e9a39a67f00d1a74830d60dc49732719ecc1d19038e594ea7f317fc3cea119",
"n": 0
},
"scriptSig": "304402201d097ec16ea6d0cbcae662f34ec21350246f6d879b485511bb6ba6e382be6abe022016b1d645cdce32b482efdc25007daeca5af292f9c8bda3a85670edf44db182b401 0211f452e6ed4daf193c3f1b3652e289ad57567e5e816f050e2116541a32098f15"
},
{
"prev_out": {
"hash": "bc74b0b15c8b1c526209632a7611930f72980641d94394d7dc3d07549d311a0e",
"n": 3
},
"scriptSig": "30450221009f60c5d42ad8c0e53e5e0f569305bb64bd075f8ef33157172821c411853b2b2f02203f312d61667f736c6eeb77054990a03204f12d824f5535117cfc20f2278a945401"
},
{
"prev_out": {
"hash": "ef6dbcacb3aae5c676a9fcefd31ba5c5c8d2df4c604b1b98393ded3a06ea2031",
"n": 0
},
"scriptSig": "3045022100df1ab213838f819ff43e74a3c1972aeed92ab17b0e27a04df8ec5b3b1eadd9ea02205fcefe55a4db243d1aac546839f33bdf3120041f9086bc218d60814f457221f501 036d1b36a3a779b12f18c637e7c3ecaf0dfa08699eee276b61d169fc3521f391e6"
},
{
"prev_out": {
"hash": "920edde4ef144b8242d9bc82cef7186d552e06b30443aef1888ac6e4eb868ebf",
"n": 0
},
"scriptSig": "304402200a2d4206b24a8276ff30aaffd2a81443241ec810370c4fbdc70b288cfa7c3488022071cea67bee407a9d13014504c4df5966b011da5c9f7dfbca8281b4d200531eff01 036d1b36a3a779b12f18c637e7c3ecaf0dfa08699eee276b61d169fc3521f391e6"
},
{
"prev_out": {
"hash": "9384466f999c00e359e30bc382ac08145b77fe3229f19f6794c8c4bb58adec42",
"n": 1
},
"scriptSig": "30440220549292b1f9f0df4956f575832433ff0b2c4de922686ee611b2fc4491617fdb6f02205ebfc38907d9717944ca655b075188be359191a0b038a2e53e0835870ed9565b01 028c018fdcaec8cef2d02ae0af19e4d9bf14cf6845c0445741bef5a79c44313f41"
},
{
"prev_out": {
"hash": "bc74b0b15c8b1c526209632a7611930f72980641d94394d7dc3d07549d311a0e",
"n": 2
},
"scriptSig": "304402201902dc60304b1c49ace591edc35380d970b5f426084147ad252ed567b66c323902206d54089cc85a1ee0474f993451a0e862ab88854fa4ea220b4a642d2c79ca596601"
},
{
"prev_out": {
"hash": "718f4a3d91ad1a914bce857183ac910d5d6440016f5e72b910db99de5e11ed97",
"n": 2
},
"scriptSig": "3044022044cf065a256cfadf505e1e96f225d6952a4293a89f748fd889370847280f9ce5022008d8755d237b6b7cc9ed4b8e909dc27db8afb5ee52e2ce8532e494f90ca0ed5501 0227fa95b82a3ee4036f7581859afaa852a5ad1da34015b0ef09948b1babf4675a"
},
{
"prev_out": {
"hash": "46f6fa184e6389ef59d66bb541513f38f15ff4a17fb16980e9cff98a6a623e7f",
"n": 0
},
"scriptSig": "0 30450221008acce661bdd08d3125473e87bfe002424ec8ea02e3932a0a78e3c701857846f502207e5207009a84b90ee9a281aef85a0d85e53ef39b56c403f1de717f5ec08962d801 304402200170c4b392e99cdd75321d2c02f5a32b78f7aa6f1ba555c40cb6a79ad0943d59022002299a2c785fa5fd683a227234bc567ffcc2ed8d265a64ae13ccce4c488a850901"
}
],
"out": [
{
"value": "0.00000000",
"scriptPubKey": "OP_RETURN 4f41010009050302000703001c0200"
},
{
"value": "0.00000600",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.00000600",
"scriptPubKey": "OP_DUP OP_HASH160 1a9ba6e78130d5f1bcb6fc79e371511ccd19117b OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.00000600",
"scriptPubKey": "OP_DUP OP_HASH160 d4339bbd21720095c6842af39ae64c39666bf65c OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.99998800",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.00000600",
"scriptPubKey": "OP_DUP OP_HASH160 d4339bbd21720095c6842af39ae64c39666bf65c OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.00000600",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.44999400",
"scriptPubKey": "OP_DUP OP_HASH160 d4339bbd21720095c6842af39ae64c39666bf65c OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.00000600",
"scriptPubKey": "OP_DUP OP_HASH160 1a9ba6e78130d5f1bcb6fc79e371511ccd19117b OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.00000600",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.99999400",
"scriptPubKey": "OP_DUP OP_HASH160 1a9ba6e78130d5f1bcb6fc79e371511ccd19117b OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.50000000",
"scriptPubKey": "OP_DUP OP_HASH160 495b82baffbe66589faa94cc1edee46aa8272032 OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "0.50000000",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": "8.00000000",
"scriptPubKey": "2 036d1b36a3a779b12f18c637e7c3ecaf0dfa08699eee276b61d169fc3521f391e6 02e49a9a8f28dc8ef8f9987d8e04d8294abadee5db1fef3b7db699b4b7c0964d28 028c018fdcaec8cef2d02ae0af19e4d9bf14cf6845c0445741bef5a79c44313f41 3 OP_CHECKMULTISIG"
},
{
"value": "2.00000000",
"scriptPubKey": "OP_DUP OP_HASH160 e4ac93b6d222020bd87b0e810b363f1ed0342ccb OP_EQUALVERIFY OP_CHECKSIG"
}
]
}
Done !!
Conclusion
The creation of TransactionBuilder has been inspired from bitcoinjs.
I believe that an important toolset can lower the barrier of entry for entrepreneurs in the Bitcoin space. Transaction signing has always been a difficult process, and I hope I took care of that for you !
As always, if you want me to turn Pizza and Coffee into code, send me a tip to 15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe !