HBitcoin:C#高级比特币钱包库 - 保护您的财产安全
背景
在几年前,我有写一个叫HiddenBitcoin的钱包库,那是我第一次接触跟比特币相关的项目。多年来我一直在复用这个项目中的代码并不停的改进优化它,虽然到最后我任然没能成功发布一个稳定的版本。可以说这次的HBitcoin就是HiddenBitcoin
的后继者了,当初我写HiddenBitcoin
的动机是为了学习,然而现在我做HBitcoin
是因为某种需求。我一直在不同的Bitcoin
项目中重用我的代码。这已经发展到以至于我在这里公布的主类有6个不同的版本。因此,现在是时候把这些代码整理好,并将它们打包成一个我可以随时通过NuGet包安装使用的Bitcoin
库。
介绍
该HBitcoin库是在NBitcoin的基础上写的。它介于HBitcoin API
和Blockchain API
之间。它的灵活性要比后者高,但比前者少。如果您要深入的学习Bitcoin
,您很有必要去翻阅The C#Bitcoin Book(比特币C#)。
在这学完了这三个部分教程后,您将能够创建一个比特币钱包。
提示 :虽然使用API编程可以帮助开发人员快速开发程序,但开发人员的创新也会被局限于API中。
Nicolas Dorier 是Bitcoin
的核心开发者,NBitcoin
和C# Bitcoin
库的创建者。在使用本文章的代码库时请记住这个提示。
一个比特币钱包能做什么?
一个比特币钱包有三个关键功能:
- 安全地存储密钥并管理对密钥的访问
- 监视这些密钥的交易
- 创建交易并将它们广播到网络
如何存储密钥?
比特币地址,私钥
比特币地址你应该很熟悉了,你可以往里面存比特币,并且你可以使用相应的私钥来花掉这些比特币。
动态地址
你可能也熟悉钱比特币包并知道他们会为不同的交易生成不同的动态地址。你可能会问为什么这些交易不只使用一个地址?这是为了保护隐私,区块链是公开记账,因此任何人都可以很容易地看到一个比特币地址的收入和支出。因此,避免地址重用是一个更好的主意,尽管这并不能完全解决隐私问题,但这无疑提高了安全性。
HD(Hierarchical Deterministic)钱包(分层确定性钱包)
那么问题又来了,我们如何管理这么多的密钥?存储,监控并花费它们?请注意使用多个密钥会大大地提高比特币钱包的复杂性。
好在HD钱包结构使我们能够只存储一个密钥并从中派生出其他密钥。为了保持我们对NBitcion
术语的一致性,我们称之为"BitcoinExtKey"。
关于比特币私钥管理的优化建议
有四种BIP(Bitcoin Improvement Proposal,比特币改进建议)是我们要注意的,分别是BIP32,BIP38,BIP43,BIP44。为了简单起见,你可以把BIP32和BIP38视为相同的BIP。它们定义了一些底层的东西,比如如何派生和加密密钥。这些在NBitcoin中都实现了。BIP43和BIP44建立在BIP32-38的基础上之上,并定义了更多的东西,如关于如何组织和使用密钥的结构。有几种钱包实现了BIP43-44。一开始我也在尝试实现它们,但随后我就决定不用它们了,因为它们不仅让我的项目变得过于复杂,而且我也无法将它应用到以后的一些边缘案例中来。那么这样一来,我就可以为你们提供更简洁的接口了。
多说无益,让我们在代码中见真章!
项目设置
- 启动一个新的.NET Core项目
- 添加NuGet中的
HBitcoin
包
var network = Network.Main;
//下面输入一个强壮的密码,如"password"
var password = "password";
//用一个密码在指定的网络中的指定路径中创建Safe类
//Safe类用来管理你的私钥种子
//Safe可以自动处理序列
//创建一个Safe类后,它会自动存储在指定的路径中
Mnemonic mnemonic;
Safe safe = Safe.Create(out mnemonic, password, @"WalletsWallet.json", network);
//创建Safe类时会同时创建mnemonic(助记符)类,你可以用它来恢复(或复制)Safe类
//你只会在一开始的时候才会接触到mnemonic类
Console.WriteLine(mnemonic);
// 把Safe类恢复到另一个路径中
Safe recoveredSafe = Safe.Recover(mnemonic, password, @"WalletsSameWallet.json", network);
// 解密/加载一个现存的Safe/钱包文件
Safe loadedSafe = Safe.Load(password, @"WalletshiddenWallet.hid");
// 在加载了一个safe后我们最好检查一下它的网络是否正确
if (network != loadedSafe.Network)
throw new Exception("Wrong network");
// 列出10个地址
for (var i = 0; i < 10; i++)
{
Console.WriteLine(safe.GetAddress(i));
}
如何解密钱包?
你只需要获得(密码
和助记符
(mnemonic
))或(密码
和钱包文件
)
然后,您就能调用Recover of Load了。
谁知道密码?用户。
谁知道助记符(mnemonic)?正常情况下,用户可以把它写在一张纸上放在家里作为备份。
谁知道钱包文件?正常情况下,它存储在用户的硬盘上。
其他钱包
通常,只用助记符来恢复一个钱包就够了。但我觉得这不安全,所以我们的这个钱包不能像那样。
创建时间
var safe2 = Safe.Recover(mnemonic, password, "Wallet.json", network,
creationTime: DateTimeOffset.ParseExact("2017-02-20",
"yyyy-MM-dd", CultureInfo.InvariantCulture));
Console.WriteLine(safe.CreationTime);
当你创建一个钱包时,它也会自动保存它的创建时间,这对于编写一些SPV钱包来说非常方便。因此,当您恢复钱包时,您还可以使用创建时间作为参数,如果您没有不填写这个参数,它会默认为可能的最早创建时间,这个时间是硬编码的:
public static DateTimeOffset EarliestPossibleCreationTime
=> DateTimeOffset.ParseExact("2017-02-19",
"yyyy-MM-dd", CultureInfo.InvariantCulture);
这是我第一次介绍创建时间这个概念的日期。如果您没有尝试使用早些的创建时间来恢复钱包,则系统会默认使用EarliestPossibleCreationTime。
安全账户和Hd路径类型
var alice = new SafeAccount(id: 2);
safe.GetAddress(index: 1, hdPathType: HdPathType.Receive, account: alice);
safe.GetPrivateKey(index: 1, hdPathType: HdPathType.Receive, account: alice);
你可以随意地创建账户。在上面的代码中,我创建了id = 2的Alice帐号并恢复了一些她的密钥。
你也可以注意到我指定了HdPathType
为receive。如果你没有指定其他参数的话将会用这个作为默认值。请注意,在某些术语中使用了“external
(外)”和“internal
(内)”来接收和更改地址。当您收钱和消费您钱包的资金时,这是很重要的。在接收资金时,一定要用HdPathType.Receive
参数;在花费资金时一定要用HdPathType.Change
参数来改变地址。这将使您的钱包更加安全。当然你还可以反复使用一个相同的地址来交易,我当然反对这么做。
结语
我建议你不要应用你自己的密钥存储方案,除非你真的胸有成竹。如果由于某种原因,我的钱包方案的不足以满足您的需求,你可以在GitHub发问,我会尽我所能帮助你。
- 编程思想 之「异常及错误处理」
- nyoj-----284坦克大战(带权值的图搜索)
- nyoj-----42一笔画问题
- hdu-------1081To The Max
- nyoj------170网络的可靠性
- HDUOJ-------1052Tian Ji -- The Horse Racing(田忌赛马)
- 初学Java之Pattern与Matcher类
- 初学java之StringBuffer类的常用方法
- 初学java之大数处理
- hdu---1024Max Sum Plus Plus(动态规划)
- Go语言异步服务器框架原理和实现
- nyoj------布线问题(kruscal+求最小值)
- nyoj-----127星际之门(一)
- nyoj------20吝啬的国度
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Python 3.8 新功能大揭秘
- SpringBoot Redis简单理解
- SpringBoot前端 —— thymeleaf 简单理解
- MyBatis XML简单理解
- Arraylist 与 LinkedList面试题
- Linux服务器权限管理之sudo高级应用
- java各个时间类总结归纳,最全一篇
- 线程池ThreadPoolExecutor源码分析
- 如何基于SSM框架,快速搭建maven后台项目?
- 6种解决跨域方案,今天全告诉你了
- ArrayList源码分析
- Linux系统基础之磁盘介绍
- jstack分析多线程死锁,来吧老铁们
- 一文搞懂CAS,ABA问题分析
- 企业实战项目之服务器用户权限集中管理