NBitcoin:最完整的比特币港口(第1部分:加密)

时间:2022-05-03
本文章向大家介绍NBitcoin:最完整的比特币港口(第1部分:加密),主要内容包括比特币简介、经济学家的看法、开发者的观点、举报人的观点、黑暗的观点、说起来很简单,给我看看代码、比特币地址、保护隐私、使用比特币密钥对进行身份验证、底层实现、共享所有权、结论、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

内容

  1. 比特币简介
    1. 企业家的观点
    2. 经济学家的观点
    3. 开发者的观点
    4. 举报人的观点
    5. 黑暗世界的观点
  2. 让我看看代码
    1. 入门
    2. 比特币地址
    3. 隐私保护
    4. 身份验证
    5. 底层实现
    6. 共享所有权
  3. 结论

比特币简介

Codeproject的研究员们,我很高兴发布系列的第一篇文章。[NEW:第2部分在这里 ]我最近从C++到C#移植了一部分很棒比特币源代码。我导入了几乎所有的单元测试。NBitcoin有大约70个测试可供你用来玩耍和发现。这是一个很棒的学习经历,我将会与你分享。但对于比特币来说,比特币的技术方面篇幅太长,以至于很难在仅仅一篇文章中就解释清楚。我将从不同的角度来看待比特币,讲述导致其诞生的原因,然后向您展示代码。对于第一部分,我将仅介绍比特币的线下部分。换句话说,我将省略所有的协议细节,并谈论高级加密部分。

企业家的看法

因为PayPal的规则,还有官僚主义所带来的的负担,费用,透明度的缺失以及缺乏私密性以进行任何货币转移的我们的银行体系,当我厌倦了PayPal随意的冻结我的钱时,比特币就抓住了我的注意力。

作为一家初创公司,将支付提供商整合到网站上是一个巨大的负担和成本中心。在使用paypal之前,我尝试了很多服务。现在paypal每次交易都很乐意收取5%的费用,并且保留我在取悦他们时他们冻结我的钱的权利。他们有律师,所以他们永远是对的。

为什么这件事如此痛苦?为什么我无法轻松转移我的资金而无需任何中介?只有我和客户没有中间人。比特币使它成为可能。这是我作为企业家的看法。

经济学家的看法

对于经济学家凯恩斯来说,比特币是一种货币,其中央银行(FED或欧洲央行)无法控制供应,他们可怜的蜥蜴脑袋活跃着并提醒他们大萧条的恐慌。届时,将美元转化为法定货币(不受黄金支持,但由中央银行控制)被视为摆脱危机的最明智决定。从这一天起,美联储(联邦储备),银行家的银行,最不起眼的银行都发生了变化。在大萧条之前,美联储为银行提供了黄金储备。之后,美联储成为一个大型的印钞机。

取消黄金标准似乎是一个好主意,直​​到米尔顿弗里德曼正确解释导致大萧条的原因,导致本伯南克在2002年承认。随后美联储的一位管理者说,联邦储备委员会是大萧条的罪魁祸首。换句话说,问题不在于美元是由黄金支撑的,而是由于美联储的创建导致银行相信他们将获得救助而承担风险。由美联储引发的这场大萧条,讽刺地导致美联储变得更加强大,因为它免于黄金负担。(这里有很长的故事

对于米尔顿弗里德曼来说,美联储创建的“银行家的银行”是一个错误,它导致大银行承担风险,因为他们知道无论任何情况,他们都会获得救助。然而我们的学校没有教给过我们这个故事。

经济学家的观点很重要。比特币是由中本聪创造的,正是因为他深信米尔顿弗里德曼理论,比特币的诞生大约在2009年1月3日,第一块,创始块,用嵌入其中的下面的信息证明了它。“03 / 01 / 2009 总理在第二次解救银行的边缘“。

开发者的观点

比特币是来自上帝的礼物,无论你心中的上帝是什么。这就好像整个货币体系是所有人都可以看到和学习​​的开放资源。如果你想给我一些比特币,中本聪保佑你。这是我的地址15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe。是的,这是你需要知道的所有事情,如果你想给我钱。作为开发者,我们没有什么可以为了获得报酬而做的。

问题是,我相信很多人对于查看C ++代码并没有真正的兴趣,并且在Linux上用命令行等一些奇怪的巫毒教进行编译。你喜欢Visual Studio吗?你喜欢C#吗?你崇拜安德斯?那么你来对地方了。我如何获得我的比特币地址?我怎么知道你给我发了钱?我该如何支付你的费用?这是本文的红线。(并感谢NBitcoin,它的长度是4行)

对于更复杂的情况,如自动月度支付或自动付款和交付处理,所有这些都是可能的,但将成为下一篇(或下两篇)文章的主题。(剧透:这只是JSON调用的问题)

举报人的观点

你可以喜欢或鄙视维基解密或斯诺登所做的事情,但你不能忽视它们,就像很久以前的苹果广告一样。但是暂时,想象你是一个举报人。你住在瑞典,暴露了一些腐败的官员和政府官员的非法活动。现在,在这些官员的干预下,官员们没有经过任何审判,就冻结了你的所有支付媒介。没有paypal,没有电汇,没有信用卡,没有visa,什么都没有,他们会因为你的所作所为而将你活活饿死,在没有任何审判的情况下。但无论他们做什么,你都可以依靠比特币,任何实体都不能颠覆比特币,甚至比特币的创造者也不行。

黑暗的观点

现在,让我们承认,比特币不仅被用于出于勇敢的原因。但我可以说,目前犯罪分子可以通过信件或通过一些黑社会主导的银行寄钱。普通账单可以像比特币一样方便地用于洗钱。在媒体上,当一篇文章说:“在纽约洗钱”,没有人会关注。如果一篇文章说:“用比特币在纽约洗钱”,这肯定会引起人们的注意。

比特币并不黑暗,也不是黑手党领导的,它是这个星球上存在的最开放和最民主的货币形式,你可以证明它是开源的。但让我们面对现实:作为我们心爱的美元账单,犯罪分子也会使用它。(稍后我会谈论比特币的民主部分,社区有一个有趣的比特币“投票”功能)然而,比特币更容易追溯到通过黑暗银行的钱。(比照汇丰银行

比特币银行已经倒闭,被称为MtGox,他们的客户损失了钱。Mt Gox说这是因为黑客攻击,我认为这是因为银行抢劫。没有人可以证明戈克斯山是对的,或者说我是对的。无论如何,如果你与比特币打交道,请记住下面的引用,并将其传播给每个人。

如果你不拥有私钥。你就不拥有比特币。

Mt Gox是一家银行,他们为你保管私人钥匙,但没有给你,并声称保证你的钱安全。但是,如果你没有私钥,你就没有办法证明他们真的保留了你的钱,你也没有权利直接在比特币网络上使用它。再来一次。

如果你不拥有私钥。你不拥有比特币。

说起来很简单,给我看看代码

入门

首先,代码在GitHub上。其次,二进制文件在Nuget上。所以让我们开始,新的控制台项目,然后在nuget包NBitcoin上添加一个参考。

对于依赖关系,BouncyCastle被用于加密部分,比特币使用ECDSA非对称密钥,我不想自己实现它。在C ++代码中,使用OpenSSL代替。

Mono.NATSQLite仅在您打算创建自己的比特币节点时使用(下一篇文章)。Mono.NAT将使用UPNP在您的网关上打开比特币端口来运行您的节点。SQLite是节点服务器用来存储事务,块和对等体的嵌入式数据库。它适用于x86和x64架构。如果您不打算与比特币网络通信,则可以将其删除。

比特币地址

正如我所说的,如果你想给我一些比特币,把它发送到15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe。但是,作为开发人员,代表这个地址的是什么?正式规范在这里,但让我们用NBitcoin来探索它。该字符串是base58字符串编码的字节数组。让我们看看它的十六进制表示下的地址。

static void Main(string[] args)
{
    byte[] byteArray = Encoders.Base58.DecodeData("15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe");
    string hex = Encoders.Hex.EncodeData(byteArray);
    Console.WriteLine(hex);
}

它给了我们:00356facdac5f5bcae995d13e667bb5864fd1e7d59fb69d021。00:标识比特币数据结构的类型的前缀,在这种情况下它是公钥哈希(见后面) 356facdac5f5bcae995d13e667bb5864fd1e7d59:实际公钥哈希(20字节)fb69d021:所有先前数据的校验(检测输入错误)

在比特币中,一个地址属于一个网络。我们有两个网络:MainTest。您可以在测试网络上免费获得比特币以进行测试。但是,每个网络都有一个不同的前缀来标识公钥哈希。在主网络上00是公钥哈希的前缀。但对于测试网络它的前缀是6f。这样做是为了使您不能将资金发送到属于另一个网络的地址。

您可以使用BitcoinAddress类型以多种方式为您进行转换。

static void Main(string[] args)
{
    BitcoinAddress address = (BitcoinAddress)Network.CreateFromBase58Data("15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe");
    //BitcoinAddress address = Network.Main.CreateBitcoinAddress("15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe");
    //BitcoinAddress address = new BitcoinAddress("15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe", Network.Main);
    Console.WriteLine(address.ID);
    //Print 356facdac5f5bcae995d13e667bb5864fd1e7d59
}

但是我在哪里得到了这个“ 公钥散列 ”?当有人用比特币给你发钱时,他们向网络发送交易。所有事务包含一个或多个TxIn(交易输入)和一个或多个TxOut(交易输出)。任何包含您的公钥散列的TxOut都是您可以使用的。

当轮到你付钱时,你会向网络发送一个新的交易,但是这次你将包含一个TxIn并引用你想要花费的TxOut(我们将这样的引用称为OutPoint)。然而,您将使用与您想要使用的TxOut中存在的公钥哈希相关联的私钥对交易进行签名。从而向您的网络证明您的所有权。

这是创建一个新的密钥对的过程。

static void Main(string[] args)
{
    Key privateKey = new Key(); //创建私钥
    BitcoinAddress address = privateKey.PubKey.GetAddress(Network.Main); //获得公钥,并导出主网络上的地址
    Console.WriteLine(address); //打印我的地址: 15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe
}

表面上看,如果你想存储私钥,

Key privateKey = new Key(); //创建私钥
BitcoinSecret secret = privateKey.GetBitcoinSecret(Network.Main);
Console.Write(secret); //打印base58编码的字符串

以下是所有类的小图,您可以看到BitcoinAddress不过是公钥哈希,并且不允许您推断PubKey

保护隐私

如果每笔交易都向网络广播,这意味着任何人都应该能够跟踪我收到多少,这引发了一些隐私问题。这是真的,你可以看到有人寄给我0.0026 BTC

你可以创建你想要的私钥。如果你想保留私钥,你可以在几个地址中分配你的接收。(一组私钥被称为钱包)如果你是一个企业,并需要自动化付款,你有另一种选择:每个商业交易创建一个不同的比特币地址。

但是,有两个问题:

  • 您需要维护业务交易和您使用的私钥之间的映射关系,
  • 如果这样的数据库被盗用或私钥被盗,你就会失去钱。

该解决方案称为Hierarchical Deterministic Wallet。由于这一点,您可以给付款服务器生成公钥的权利,而无需提供私钥。如果支付数据库被盗或被盗用,你就什么也没有了。

在NBitcoin中,该功能由两个类实现:ExtKeyExtPubKey。ExtKey将为相应的ID生成一个Key,ExtPubKey将为相应的ID生成一个PubKey

ExtKey privateKey = new ExtKey();
ExtPubKey pubKey = privateKey.Neuter();
//现在,把pubkey发送给您的付款服务器
//....
//付款服务器收到一个订单,注意服务器不需要私钥来生成地址
uint orderID = 1001;
BitcoinAddress address = pubKey.Derive(orderID).PubKey.GetAddress(Network.Main);
Console.WriteLine(address);

//现在,在有权限访问私钥的服务器上,你从订单ID得到了私钥
Key key = privateKey.Derive(orderID).Key;
BitcoinSecret secret = key.GetBitcoinSecret(Network.Main);
Console.WriteLine(secret); //打印一个很棒的秘钥字符串

在这个例子中,我们只使用一个orderId来生成Key/Pubkey,但实际上你可以使用更多的数据,这就是为什么我们需要一个分层次的钱包。

例如

pubKey.Derive(departmentID).Derive(orderID).PubKey

这样,您可以为每个部门生成一个专用私钥。(如果你拥有管理员ExtKey,也可以使用他们的资金)

使用比特币密钥对进行身份验证

亲爱的读者,我怎么能证明,我确实是15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe的私钥所有者?

这里是证明:

消息:“我是Nicolas Dorier,这个地址的所有者15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe”。

签名:“IGkC4NKGXOXJ6CNT8T6Dx0egqaiSb8rAlBdsmanStOhbVfILmY + 3p88Z / Fhb / jSkUhHFhsbcxFZydoPrh / 2LNY0 =”

我是如何产生这个签名的?

BitcoinSecret secret = new BitcoinSecret("base58 private key", Network.Main);
string signature = secret.Key.SignMessage("I am Nicolas Dorier, owner of this address 15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe");
Console.WriteLine(signature);

在你身边,你如何验证?

string message = "I am Nicolas Dorier, owner of this address 15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe";
string signature = "IGkC4NKGXOXJ6CNT8T6Dx0egqaiSb8rAlBdsmanStOhbVfILmY+3p88Z/Fhb/jSkUhHFhsbcxFZydoPrh/2LNY0=";
BitcoinAddress address = new BitcoinAddress("15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe", Network.Main);
bool verified = address.VerifyMessage(message, signature);
Console.WriteLine(verified);

底层实现

为了简化我的解释,我做了一些捷径来解释什么是比特币地址。我说,一个TXOUT汇款到一个比特币地址,当你想花钱时,在您引用该TXOUT的交易中添加一个TxIn,并使用您的私钥来签署。

但比特币比这更灵活。 TxOut不一定包含您的地址。TxOut包含一个我们称之为ScriptPubKey的脚本。这个脚本就像一个算法,用来说明你需要做些什么来使用这个TxOut。看起来,TxIn有一个脚本叫做ScriptSig,这个脚本做ScriptPubKey想要使用TxOut来做的事。这些脚本是一种没有任何循环堆栈语言

SigPubKey连接到ScriptSig时,该算法被执行,其结果被压入堆栈。

直到现在,如果你想给我打钱,ScriptPubKey看起来就像那样。(以可读形式)

BitcoinAddress to = new BitcoinAddress("15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe", Network.Main);
PayToPubkeyHashScriptTemplate template = new PayToPubkeyHashScriptTemplate();
Script script = template.GenerateOutputScript(to);
Console.WriteLine(script);

OP_DUP OP_HASH160 356facdac5f5bcae995d13e667bb5864fd1e7d59(公钥散列) OP_EQUALVERIFY OP_CHECKSIG

另一方面,如果我想使用这些资金,我的脚本将会是

BitcoinSecret secret = new BitcoinSecret("base58 secret key", Network.Main);
byte[] transactionSignature = secret.Key.Sign(transactionHash);
PayToPubkeyHashScriptTemplate template = new PayToPubkeyHashScriptTemplate();
Script scriptSig = template.GenerateInputScript(new TransactionSignature(transactionSignature, SigHash.All), secret.Key.PubKey);
Console.WriteLine(scriptSig);

由此产生的脚本将在栈上推入两个值:事务签名和公钥。

<交易签名> <公钥>

如果我们连接我们得到的两个脚本

<事务签名> <公钥> OP_DUP OP_HASH160 <公钥密码> OP_EQUALVERIFY OP_CHECKSIG

它表示

Push <事务的签名>

Push <公钥(1)>

再次push <公钥(2)>

Hash <公钥(2)>,pop <公钥(2)>,push <公钥哈希(1)>

Push <公钥哈希(2)>

验证<公钥哈希(1)>等于<公钥哈希(2)>,pop<公钥哈希(1)>,pop<公钥哈希(2)>

使用Push <公钥 (1)>, pop <交易签名>, pop <公钥(1)>检查签名<交易签名>,push true 如果验证成功,若验证失败,push false

共享所有权

为什么脚本系统如此重要?因为它允许新的所有权方式,当有人向N个钱包中的一个M发送资金时,有一个特殊的脚本称为“N个钱包的M”,这意味着该花费者需要提供M个可能地址的N个签名。

通过这种方式,您可以推动3人中的2人应该同意花费来强制达成共识的事实。如果你想与某人分享钱财,你可以使用两人中的一人。这与共享私钥的区别是不可否认的。当你使用“2个钱包中的一个”时,你可以证明该支付者的身份。这样的脚本可以这样生成

PayToMultiSigScriptTemplate template = new PayToMultiSigScriptTemplate();
Script scriptPubKey = template.GenerateOutputScript(1, new PubKey[] { nicoPubKey, bobPubKey });

01 <nico hash pub key> <bob hash pub key> 02 OP_CHECKMULTISIG

还有其他标准脚本,我一定会在下一篇文章中谈谈。这是他们的类图。

结论

最后,这篇文章比我想像的要长......下一次我将更多地介绍比特币协议的内部,以及讨论其他标准脚本。请让我知道你是否喜欢这篇文章。

[新文章 : Part 2 Here]