NBitcoin:全部构建






4.96/5 (32投票s)
使用 TransactionBuilder 构建所有交易:P2SH、P2PK、P2PKH、多重签名、隐秘支付、彩色币
引言
本文假设您已经了解比特币的工作原理。我写了这篇文章,以及这篇来解释一些基础知识。
在比特币中,签名和构建交易在概念上很容易理解,但在细节上却很混乱。
每次您想花费一枚币时,都必须通过对其花费的交易进行签名来证明对其的所有权或部分所有权。具体过程很复杂,您可以在此处获得预览。
因此,我的目标是让构建和签名任何类型的标准交易都变得容易。
TransactionBuilder 更进一步,支持Open Asset协议(因此您可以在比特币网络中发行和交易资产,而无需金融中介)以及隐秘支付(用于保护您的隐私)。
您可以在本文链接的易于运行的控制台应用程序中找到所有示例。
Content
- 设计
- P2PK、P2PKH、多重签名支付
- Alice 支付给 Satoshi
- Alice 和 Bob 支付给 Satoshi
- AliceBobNico 公司支付给 Satoshi
- P2SH 支付
- AliceBobNico 公司支付给 Satoshi
- 隐秘支付
- DarkSatoshi 向 DarkBob 和 Alice 发送资金
- 彩色币支付
- GoldGuy 向 Nico 和 Satoshi 发行黄金,SilverGuy 向 Alice 发行白银
- Nico 将黄金发送给 Satoshi。
- Satoshi 和 Alice 希望用黄金兑换白银和比特币。
- 地狱交易(仅限勇士)
- GoldGuy 和 SilverGuy 同意向 Satoshi 发行白金。
- Satoshi 希望用白金兑换 Alice 的白银、Nico 的黄金、DarkBob 的比特币以及 AliceBobNico 公司的比特币。
- 结论
设计
TransactionBuilder 只需要知道以下信息:
- 它可以花费哪些币
- 它知道哪些私钥
- 它将资金发送到哪里
关于第一点,一个人可以花费的币有多种类型。
对于 P2PK、P2PKH 和多重签名币,您只需要 Coin 类,即它的 Outpoint(ID)和 TxOut(金额以及支付给谁)。
ScriptCoin 也是如此,对于 P2SH 支付,您需要知道 Redeem。
StealthCoin 与 Coin 相同,但您必须知道它附加到的 BitcoinStealthAddress 以及 StealthMetadata。(交易中的特殊 OP_RETURN,正如我在这里所解释的)
然后我们有其他类别的币:彩色币。
ColoredCoin 的 Amount 属性是资产的数量,由 AssetId 标识。
Bearer 是附加了比特币的底层 Coin(很可能是微尘)。
IssuanceCoin 只能由资产发行者花费,其目的是发行新资产。
ColoredCoin 代表附加到 Coin 的资产(例如黄金)。
一旦您获得要花费的币,就可以使用 TransactionBuilder 及其链式接口。
您使用 AddCoins 来告知可以花费哪些币。
您使用 AddKeys 来告知您知道哪些私钥。
您使用 Send 来发送比特币,使用 SendAsset 来发送彩色币,或使用 IssueAsset 来发行资产。
SetChange,正如我在上一篇文章中所解释的,是应将任何找零发送到此处。
SendFees,向矿工支付一些费用,以便您的交易能更快地得到验证。
BuildTransaction 将根据您的意愿选择币,然后可选地(部分)签名您的交易。
SetCoinSelector 允许您自定义选择币以支付款项的算法。
SignTransaction 使用 TransactionBuilder 已知的密钥对交易进行签名。
Shuffle 将您迄今为止所做的一切进行混淆,这样区块链观察者就无法轻易找到找零地址,或从 TxOut 顺序猜测其他信息。
Verify 将验证一个交易是否已完全签名并准备好发送到网络。
尽管方法数量不多,但您会发现所有您梦想过的交易都可以无限地表达和带来乐趣。
P2PK、P2PKH、多重签名支付
让我向您介绍 Alice、Bob、Satoshi 和 Nico,以及他们的私钥。
BitcoinSecret alice = new BitcoinSecret("KyJTjvFpPF6DDX4fnT56d2eATPfxjdUPXFFUb85psnCdh34iyXRQ");
BitcoinSecret bob = new BitcoinSecret("KysJMPCkFP4SLsEQAED9CzCurJBkeVvAa4jeN1BBtYS7P5LocUBQ");
BitcoinSecret nico = new BitcoinSecret("L2uC8xNjmcfwje6eweucYvFsmKASbMDALy4rCJBAg8wofpH6barj");
BitcoinSecret satoshi = new BitcoinSecret("L1CpAon5d8zroENbkiMbk3dtd3kcbms6QGF5x475KKTMmXVaJXh3");
Alice 支付给 Satoshi
Alice 在过去的交易中收到了两个币。
{ "hash": "920edde4ef144b8242d9bc82cef7186d552e06b30443aef1888ac6e4eb868ebf", "out": [ { "value": "0.45000000", "scriptPubKey": "OP_DUP OP_HASH160 d4339bbd21720095c6842af39ae64c39666bf65c OP_EQUALVERIFY OP_CHECKSIG" }, { "value": "0.80000000", "scriptPubKey": "036d1b36a3a779b12f18c637e7c3ecaf0dfa08699eee276b61d169fc3521f391e6 OP_CHECKSIG" } ] }
您可以看出其中一个币是 P2PKH,另一个是 P2PK。您需要知道的是,只要 Alice 保管好她的私钥,这两个币都可以花费。
我们需要将这些交易输出转换为 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();
现在 Alice 想向 Satoshi 发送 1.00 BTC,并支付 0.001 BTC 作为矿工费用。
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)); //check fully signed
这得到了以下交易:
{ "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" } ] }
从 2 个输入来看,您可以看到 Alice 的两个币都被花费了(1.25 BTC)。
您还可以看到 0.249 被退还给她(找零),1 发送给 Satoshi,这意味着 0.001 是费用。(签名是确定性的,因此通过运行源代码,您将得到相同的结果)
Alice 和 Bob 支付给 Satoshi
所以,首先,让我们为 Bob 创建一些币,就像我为 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();
现在,假设 Alice 想发送 0.8 BTC,Bob 想发送 0.2 BTC,他们都发送给 Satoshi。他们想分摊费用,每人支付 0.0005 BTC。这得到了:
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)); //check fully signed
注意 Then 方法,它允许您独立于 Bob 的部分构建 Alice 交易的第一部分:每个部分都有自己的找零和费用。
这是结果:
{ "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 公司支付给 Satoshi
Alice、Bob 和 Nico 是 AliceBobNico 公司的创始人。他们同意将公司的资金存储在多重签名中,因此至少需要 2 位创始人同意才能花费任何资金。
让我们创建 AliceBobNico 的 scriptPubKey,资金将发送到这里。
Script AliceBobNicoCorp = PayToMultiSigTemplate
.Instance
.GenerateScriptPubKey(2, new[] { alice.Key.PubKey, bob.Key.PubKey, nico.Key.PubKey });
这就得到了存储公司资金的多重签名脚本:
2 036d1b36a3a779b12f18c637e7c3ecaf0dfa08699eee276b61d169fc3521f391e6 02e49a9a8f28dc8ef8f9987d8e04d8294abadee5db1fef3b7db699b4b7c0964d28 028c018fdcaec8cef2d02ae0af19e4d9bf14cf6845c0445741bef5a79c44313f41 3 OP_CHECKMULTISIG
让我们像之前为 Bob 和 Alice 一样,用一些币为这个地址充值。
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();
所以现在,AliceBobNico 想向 Satoshi 发送资金。但是,Bob 不在办公室,Nico 在旅行,只有 Alice 留在办公室。所以 Alice 别无选择,只能部分签署交易,然后通过电子邮件发送给 Nico,以便他也能签名。
txBuilder = new TransactionBuilder();
tx = txBuilder
.AddCoins(corpCoins)
.AddKeys(alice.Key)
.Send(satoshi.GetAddress(), "4.5")
.SetChange(AliceBobNicoCorp)
.BuildTransaction(true);
Assert(!txBuilder.Verify(tx)); //Well, only one signature on the two required...
//Alice sends to Nico
txBuilder = new TransactionBuilder();
tx = txBuilder
.AddCoins(corpCoins)
.AddKeys(nico.Key)
.SignTransaction(tx);
Assert(txBuilder.Verify(tx));
关键在于,TransactionBuilder 会尽最大努力用它知道的密钥来签名所有它能签名的部分。因此,您可以将所有类型的花费混合在同一笔交易中,只需要求 TransactionBuilder 尽其所能地解决混乱。
P2SH 支付
AliceBobNico 公司支付给 Satoshi
AliceBobNico 的大多数客户不支持支付给之前的原生多重签名脚本。
为了将资金支付给 AliceBobNico,客户宁愿有一个简单的地址,而不是一个钱包不支持的复杂比特币脚本。
因此,AliceBobNico 将他们的脚本转换为 ScriptAddress,并将其提供给客户。
var aliceBobNicoAddress = AliceBobNicoCorp.GetScriptAddress(Network.Main);
这就得到了 3FNCyS4ugTCV8XZWV7SRENUG31fe8uUkFU。让我们用客户发送的币为 AliceBobNico 充值:
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();
请注意,我使用的是 ScriptCoin 而不是简单的 Coin。原因是 TransactionBuilder 需要 ScriptAddress 背后的真实脚本(称为 Redeem Script)才能正确签名。(在我们的例子中,就是前一部分的多重签名脚本)
AliceBobNico 想向 Satoshi 发送 45 BTC。因此,它将需要前一部分的原生多重签名币,以及刚从客户那里收到的 P2SH 多重签名的 40 BTC。
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));
这就得到了以下交易,结合了 P2SH 和原生多重签名!
{ "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" } ] }
隐秘支付
DarkSatoshi 向 DarkBob 和 Alice 发送资金
出于在上一篇文章中解释的原因,DarkSatoshi 希望有一种方法来接收和花费 StealthCoin,这样区块链的外部观察者就不可能污染(识别)构成其钱包的地址。
TransactionBuilder 也支持多重签名隐秘地址。
使用 DarkAliceBobNico Corp. 的情况留作练习!(也包括部分签名)
所以,您看,我向您介绍 DarkBob 和 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);
让我们为 DarkSatoshi 充值。
Transaction darkSatoshiFunding = new Transaction();
darkSatoshiAddress.CreatePayment().AddToTransaction(darkSatoshiFunding, "2.5");
这得到了一个隐秘交易。
{ "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" } ] }
任何拥有 BitcoinStealthAddress 和 ScanKey 的人都可以从中提取隐秘币。
var darkSatoshiCoin = StealthCoin.Find(darkSatoshiFunding, darkSatoshiAddress, darkSatoshiScan.Key);
现在 DarkSatoshi 将向 DarkBob 和 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()) //Well… maybe not the best idea since it leaks some privacy
.SendFees("0.01")
.BuildTransaction(true);
Assert(txBuilder.Verify(tx));
轮到他了,DarkBob 可以通过他的扫描密钥和地址获取此交易的 StealthCoin。
var darkBobCoin = StealthCoin.Find(tx, darkBobAddress, darkBobScan.Key);
Assert(darkBobCoin != null);
彩色币支付
GoldGuy 向 Nico 和 Satoshi 发行黄金,SilverGuy 向 Alice 发行白银
在 Open Asset 中可以进行的两个基本操作是发行资产和转移资产。
但在转移之前,我们需要发行。
我向您介绍 GoldGuy 和 SilverGuy,他们是具有神一般力量的杰出权威,能够凭空创造黄金和白银(……好吧,更准确地说,是从稀薄的尘埃中,如您将看到的)。
var goldGuy = new BitcoinSecret("KyuzoVnpsqW529yzozkzP629wUDBsPmm4QEkh9iKnvw3Dy5JJiNg");
var silverGuy = new BitcoinSecret("L4KvjpqDtdGEn7Lw6HdDQjbg74MwWRrFZMQTgJozeHAKJw5rQ2Kn");
要发行资产,GoldGuy(以及 SilverGuy)必须花费我称之为 IssuanceCoin 的币,通常这种币的比特币金额更接近 600 Satoshis,不到一美分。
这种币的唯一目的是证明资产发行权的归属。
IssuanceCoin 的 AssetId 是从其 ScriptPubKey 的哈希派生而来的,如规范中所述。
这是我创建我的 IssuanceCoins 以及一些普通 Coin 的方式,供 Satoshi 和 Nico 稍后使用。
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;
给定一个交易,如果您想知道传输了哪些资产以及发行了哪些资产,您需要知道所有祖先直到发行币交易。
这就是为什么我将在 TransactionRepository 中跟踪这些交易。
var txRepo = new NoSqlTransactionRepository();
txRepo.Put(issuanceCoinsTransaction.GetHash(), issuanceCoinsTransaction);
然而,由于我们不想每次请求交易的 ColoredTransaction 时都评估所有祖先,我们还使用 ColoredTransactionRepository 来跟踪已处理的 ColoredTransaction。
var ctxRepo = new NoSqlColoredTransactionRepository(txRepo);
好了,现在让我们向 Nico 和 Satoshi 发行一些 Gold,看看是否正确发行了资产。
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);
这得到了以下交易。
{ "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" } ] }
这代表了以下 ColoredTransaction。
{ "inputs": [], "issuances": [ { "index": 0, "asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1", "quantity": 20 }, { "index": 1, "asset": "AeRDfBJh5cXudMWu7XeHabopTB4fDkDph1", "quantity": 30 } ], "transfers": [], "destructions": [] }
因此,第一个 TxOut 携带 20 Gold,第二个携带 30 Gold。
所以,Satoshi 和 Nico 现在将收到他们的 ColoredCoin,他们以后可以花费。
var coloredCoins = ColoredCoin.Find(tx, ctx).ToArray();
var satoshiGold = coloredCoins[0];
var nicoGold = coloredCoins[1];
现在让我们向 Alice 发行 Silver。
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 将黄金发送给 Satoshi
Nico 想向 Satoshi 发送 1 Gold。
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);
这得到了以下 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 退还给 Nico,1 发送给 Satoshi。
Satoshi 和 Alice 希望用黄金兑换白银和比特币
Satoshi 想要 Alice 的 Silver:10 Gold 兑换 9 Silver + 0.5 BTC。
这样的例子在一行中完成,但 Satoshi 和 Alice 也可以像我前面提到的多重签名支付一样,独立地签署交易。
普通的 Coin 对于支付带有彩色币的 TxOut 的 Dust 是必需的。
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);
结果是以下 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": [] }
对于以下交易:
{ "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" } ] }
正如您所见,资产和比特币的找零已正确发送回各自的所有者。
地狱交易(仅限勇士)
GoldGuy 和 SilverGuy 同意向 Satoshi 发行 Platinium。
Satoshi 希望用白金兑换 Alice 的白银、Nico 的黄金、DarkBob 的比特币以及 AliceBobNico 公司的比特币。
首先,为了发行 Platinium,我们将需要 GoldGuy 和 SilverGuy 之间的多重签名发行。
首先让我们为他们创建一个 P2SH 地址。
var platiniumRedeem = PayToMultiSigTemplate.Instance
.GenerateScriptPubKey(2, new[] { goldGuy.Key.PubKey, silverGuy.Key.PubKey });
var platiniumAddress = platiniumRedeem.GetScriptAddress(Network.Main);
让我们用一些 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;
让我们向 Satoshi 发行 15 Platinium。
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();
现在,让我们来进行交易:
Satoshi 希望用 5 Platinium 兑换 Alice 的 3 Silver、Nico 的 2 Gold、DarkBob 的 0.5 BTC 以及 AliceBobNico 公司的 2 BTC。
同样,交易可以先构建,然后由所有参与者独立地稍后签名。但对于这篇文章,我将在一行中完成所有操作。
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);
这就得到了预期的 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": [] }
以及预期的 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" } ] }
完成!!
结论
TransactionBuilder 的创建灵感来自bitcoinjs。
我相信一套重要的工具集可以降低创业者进入比特币领域的门槛。交易签名一直是一个困难的过程,我希望我已经为您解决了这个问题!
一如既往,如果您希望我将披萨和咖啡变成代码,请向 15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe 发送小费!