以太坊之帐户管理

在这一部分,咱们将学习如何使用C#管理以太坊帐户,这包括:web

  1. 了解私钥、公钥和帐户的关系
  2. 离线建立以太坊帐户
  3. 导入其余帐户私钥
  4. 建立和使用钱包
  5. 建立和使用帐户凭证

以太坊做为一个去中心化的系统,必然不会采用中心化的帐户管理 方案 —— 没有一个中心数据库来保存以太坊平台上的全部帐户信息。 事实上,以太坊使用非对称密钥技术来进行身份识别,一个以太坊 帐户对应着一对密钥:算法

在这一部分的内容里,咱们将使用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服务器将根据该订单的订单号 提取或生成对应的以太坊地址,而后在支付页面中展现该收款地址。为了 方便使用手机钱包的用户,能够同时在支付页面中展现该收款地址的二维码。

用户使用本身的以太坊钱包向该收款地址支付以太币。因为网站的支付处理 进程在周期性地检查该收款地址的余额,一旦收到足额款项,支付处理进程 就能够根据收款地址将对应的订单结束,并为用户开通对应的服务。

相关文章
相关标签/搜索