在这一部分,咱们将学习如何使用C#管理以太坊帐户,这包括:web
以太坊做为一个去中心化的系统,必然不会采用中心化的帐户管理 方案 —— 没有一个中心数据库来保存以太坊平台上的全部帐户信息。 事实上,以太坊使用非对称密钥技术来进行身份识别,一个以太坊 帐户对应着一对密钥:算法
在这一部分的内容里,咱们将使用Nethereum.Signer命名空间 中的类来管理密钥、帐户和钱包。数据库
私钥、公钥与地址json
以太坊使用非对称密钥对来进行身份识别,每个帐户都有 对应的私钥和公钥 —— 私钥用来签名、公钥则用来验证签名 —— 从而 在非可信的去中心化环境中实现身份验证。数组
事实上,在以太坊上帐户仅仅是对应于特定非对称密钥对中公钥的20字节 哈希安全
从私钥能够获得公钥,而后进一步获得帐户地址,而反之则无效。 显然,以太坊不须要一个中心化的帐户管理系统,咱们能够根据以太坊约定 的算法自由地生成帐户。服务器
在C#中,可使用EthECKey类来生成密钥对和帐户地址。一个EthECKey 实例封装一个私钥,同时也提供了访问公钥和地址的方法:async
例如,下面的代码首先使用EthECKey的静态方法GenerateKey()建立一个 随机私钥并返回EthECKey实例,而后经过相应的实例方法读取私钥、公钥 和帐户地址:学习
EthECKey keyPair = EthECKey.GenerateKey(); string privateKey = keyPair.GetPrivateKey(); byte[] publicKey = keyPair.GetPubKey(); string address = keyPair.GetPublicAddress(); Console.WriteLine("Private Key => " + privateKey); Console.WriteLine("Public Key => " + publicKey.ToHex(true)); Console.WriteLine("Address => " + address); Console.ReadLine();
GetPubKey()方法返回的是一个byte[]类型的字节数组,所以咱们使用 静态类HexByteConvertorExtensions的静态方法ToHex()将其转换为16进制 字符串,参数true表示附加0x前缀。 ToHex()的原型以下:测试
注意HexByteConvertorExtensions是静态类并且ToHex()的第一个参数为 byte[]类型,所以byte[]类型的对象能够直接调用ToHex()方法。
namespace KeyAndAddressDemo { class KeyAndAddress { public void Run() { EthECKey keyPair = EthECKey.GenerateKey(); string privateKey = keyPair.GetPrivateKey(); byte[] publicKey = keyPair.GetPubKey(); string address = keyPair.GetPublicAddress(); Console.WriteLine("Private Key => " + privateKey); Console.WriteLine("Public Key => " + publicKey.ToHex(true)); Console.WriteLine("Address => " + address); Console.ReadLine(); } } }
class Program { static void Main(string[] args) { Console.WriteLine("cuiyw-test"); Console.WriteLine("Key and Address"); KeyAndAddress demo = new KeyAndAddress(); demo.Run(); Console.ReadLine(); } }
导入私钥
咱们已经知道,只有私钥是最关键的,公钥和帐户均可以从私钥一步步 推导出来。
假如你以前已经经过其余方式有了一个帐户,例如使用Metamask建立的钱包,那么能够把该帐户导入C#应用,从新生成公钥和帐户地址:
using Nethereum.Signer; using System; namespace ImportKeyDemo { class Program { static void Main(string[] args) { Console.WriteLine("cuiyw-test"); EthECKey keyPair = EthECKey.GenerateKey(); string privateKey = keyPair.GetPrivateKey(); string address = keyPair.GetPublicAddress(); Console.WriteLine("Original Address => " + address); //import EthECKey recovered = new EthECKey(privateKey); Console.WriteLine("Recoverd Address => " + recovered.GetPublicAddress()); Console.ReadLine(); } } }
keystore钱包
鉴于私钥的重要性,咱们须要以一种安全地方式保存和迁移,而不是简单地 以明文保存到一个文件里。
keystore容许你用加密的方式存储密钥。这是安全性(一个攻击者须要 keystore 文件和你的钱包口令才能盗取你的资金)和可用性(你只须要keystore 文件和钱包口令就能用你的钱了)二者之间完美的权衡。
下图是一个keystore文件的内容:
从图中能够看出,keystore的生成使用了两重算法:首先使用你指定的钱包口令 采用kpf参数约定的算法生成一个用于AES算法的密钥,而后使用该密钥 结合ASE算法参数iv对要保护的私钥进行加密。
因为采用对称加密算法,当咱们须要从keystore中恢复私钥时,只须要 使用生成该钱包的密码,并结合keystore文件中的算法参数,便可进行 解密出你的私钥。
KeyStoreService
KeyStoreService类提供了两个方法,用于私钥和keystore格式的json之间的转换:
下面的代码建立一个新的私钥,而后使用口令7878生成keystore格式 的json对象并存入keystore目录:
EthECKey keyPair = EthECKey.GenerateKey(); string privateKey = keyPair.GetPrivateKey(); Console.WriteLine("Original Key => " + privateKey); KeyStoreService ksService = new KeyStoreService(); string password = "7878"; string json = ksService.EncryptAndGenerateDefaultKeyStoreAsJson(password, keyPair.GetPrivateKeyAsBytes(), keyPair.GetPublicAddress()); EnsureDirectory("keystore"); string fn = string.Format("keystore/{0}.json", ksService.GenerateUTCFileName(keyPair.GetPublicAddress())); File.WriteAllText(fn, json); Console.WriteLine("Keystore Saved => " + fn);
尽管能够从私钥推导出帐户地址,但EncryptAndGenerateDefaultStoreAsJson()方法 仍是要求咱们同时传入帐户地址,所以其三个参数依次是:私钥口令、私钥、对应的地址。
GenerateUTCFileName()方法用来生成UTC格式的keystore文件名,其构成以下:
解码keystore
在另外一个方向,使用DecryptKeyStoreFromJson()方法,能够从keystore 来恢复出私钥。例如,下面的代码使用同一口令从钱包文件恢复出私钥并重建密钥对:
byte[] recoveredPrivateKey = ksService.DecryptKeyStoreFromJson(password, json); Console.WriteLine("Recovered Key => " + recoveredPrivateKey.ToHex(true));
using Nethereum.Hex.HexConvertors.Extensions; using Nethereum.KeyStore; using Nethereum.Signer; using System; using System.IO; namespace KeystoreDemo { class Program { static void Main(string[] args) { Console.WriteLine("cuiyw-test"); EthECKey keyPair = EthECKey.GenerateKey(); string privateKey = keyPair.GetPrivateKey(); Console.WriteLine("Original Key => " + privateKey); KeyStoreService ksService = new KeyStoreService(); string password = "7878"; string json = ksService.EncryptAndGenerateDefaultKeyStoreAsJson(password, keyPair.GetPrivateKeyAsBytes(), keyPair.GetPublicAddress()); EnsureDirectory("keystore"); string fn = string.Format("keystore/{0}.json", ksService.GenerateUTCFileName(keyPair.GetPublicAddress())); File.WriteAllText(fn, json); Console.WriteLine("Keystore Saved => " + fn); byte[] recoveredPrivateKey = ksService.DecryptKeyStoreFromJson(password, json); Console.WriteLine("Recovered Key => " + recoveredPrivateKey.ToHex(true)); Console.ReadLine(); } private static void EnsureDirectory(string path) { if (Directory.Exists(path)) return; Directory.CreateDirectory(path); } } }
{ "crypto": { "cipher": "aes-128-ctr", "ciphertext": "38a0299356d70c3cd54eda1c5f8f58d3b84d0a7c377295b4c6a630f81dbf610a", "cipherparams": { "iv": "2aefcf10a52376f9456992e470ec3234" }, "kdf": "scrypt", "mac": "feba237a6258625be86b46fc44d09f4fc3e4e7ea4cc6ce7db4bce47508ab627f", "kdfparams": { "n": 262144, "r": 1, "p": 8, "dklen": 32, "salt": "7e6ff7ae6ae7e83c1f5d8f229458a7e102e55023f567e1f03cd88780bdc18272" } }, "id": "cb7b8d03-c87a-446a-b41e-cede3d936b59", "address": "0x78E4a47804743Cc673Ba79DaF2EB03368e4be145", "version": 3 }
离线帐户与节点管理的帐户
在以太坊中,一般咱们会接触到两种类型的帐户:离线帐户和节点管理的帐户。
在前面的课程中,咱们使用EthECKey建立的帐户就是离线帐户 —— 不须要 链接到一个以太坊节点,就能够自由地建立这些帐户 —— 所以被称为离线帐户。 离线帐户的私钥由咱们(应用)来管理和控制。
另外一种类型就是由节点建立或管理的帐户,例如ganache自动随机生成的帐户, 或者在geth这样的节点软件中建立的帐户。这些帐户的私钥由节点管理,一般 咱们只须要保管好帐户的口令,在须要交易的时候用口令解锁帐户便可。ganache仿真器的帐户不须要口令即自动解锁。所以当使用ganache做为节点 时,在须要传入帐户解锁口令的地方,传入空字符串便可。
对于这两种不一样的帐户类型,Nethereum提供了不一样的类来封装,这两种 不一样的类将影响后续的交易操做:
离线帐户:Account
Account类对应于离线帐户,所以在实例化时须要传入私钥:
BigInteger chainId = new BigInteger(1234); Account account = new Account(privateKey, chainId);
参数chainId用来声明所链接的的是哪个链,例如公链对应于1,Ropsten 测试链对应于4,RinkeBy测试链对应于5...对于ganache,咱们能够随意指定 一个数值。
另外一种实例化Account类的方法是使用keystore文件。例以下面的代码 从指定的文件载入keystore,而后调用Account类的静态方法
string privateKey = "0x197b09426db81c7ebaefbcea4ab09c9379c23628c73e20c5475b0f13e7eacaba"; BigInteger chainId = new BigInteger(1234); Account account = new Account(privateKey, chainId);
节点管理帐户:ManagedAccount
节点管理帐户对应的封装类为ManagedAccount,实例化一个节点管理帐户 只须要指定帐户地址和帐户口令:
Web3 web3 = new Web3("http://localhost:7545"); string[] accounts = await web3.Eth.Accounts.SendRequestAsync(); ManagedAccount account = new ManagedAccount(accounts[0], "");
Nethereum提供这两种不一样帐户封装类的目的,是为了在交易中可使用 一个抽象的IAccount接口,来屏蔽交易执行方式的不一样。
using Nethereum.Web3; using Nethereum.Web3.Accounts; using Nethereum.Web3.Accounts.Managed; using System; using System.IO; using System.Numerics; using System.Threading.Tasks; namespace AccountDemo { class Program { static void Main(string[] args) { Console.WriteLine("cuiyw-test"); CreateAccountFromKey(); CreateAccountFromKeyStore(); CreateManagedAccount().Wait(); Console.ReadLine(); } public static void CreateAccountFromKey() { Console.WriteLine("create offline account from private key..."); string privateKey = "0x197b09426db81c7ebaefbcea4ab09c9379c23628c73e20c5475b0f13e7eacaba"; BigInteger chainId = new BigInteger(1234); Account account = new Account(privateKey, chainId); Console.WriteLine(" Address => " + account.Address); Console.WriteLine(" TransactionManager => " + account.TransactionManager); } public static void CreateAccountFromKeyStore() { Console.WriteLine("create offline account from keystore..."); string fn = "keystore/UTC--2019-04-21T08-15-35.6963027Z--78E4a47804743Cc673Ba79DaF2EB03368e4be145.json"; string json = File.ReadAllText(fn); string password = "7878"; BigInteger chainId = new BigInteger(1234); Account account = Account.LoadFromKeyStore(json, password, chainId); Console.WriteLine(" Address => " + account.Address); Console.WriteLine(" TransactionManager => " + account.TransactionManager); } public static async Task CreateManagedAccount() { Console.WriteLine("create online account ..."); Web3 web3 = new Web3("http://localhost:7545"); string[] accounts = await web3.Eth.Accounts.SendRequestAsync(); ManagedAccount account = new ManagedAccount(accounts[0], ""); Console.WriteLine(" Address => " + account.Address); Console.WriteLine(" TransactionManager => " + account.TransactionManager); } } }
为网站增长以太币支付功能
在应用中生成密钥对和帐户有不少用处,例如,用户能够用以太币 在咱们的网站上购买商品或服务 —— 为每一笔订单生成一个新的以太坊 地址,让用户支付到该地址,而后咱们检查该地址余额便可了解订单 的支付状况,进而执行后续的流程。
为何不让用户直接支付到咱们的主帐户?
稍微思考一下你就明白,建立一个新地址的目的是为了将支付与订单 关联起来。若是让用户支付到主帐户,那么除非用户支付时在交易数据 里留下对应的订单号,不然你没法简单的肯定收到的交易与订单之间的 关系,而不是全部的钱包软件—— 例如coinbase —— 都支持将留言包含 在交易里发送到链上。
解决方案以下图所示:
当用户选择使用以太币支付一个订单时,web服务器将根据该订单的订单号 提取或生成对应的以太坊地址,而后在支付页面中展现该收款地址。为了 方便使用手机钱包的用户,能够同时在支付页面中展现该收款地址的二维码。
用户使用本身的以太坊钱包向该收款地址支付以太币。因为网站的支付处理 进程在周期性地检查该收款地址的余额,一旦收到足额款项,支付处理进程 就能够根据收款地址将对应的订单结束,并为用户开通对应的服务。