以太坊之账户管理

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

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

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

在这一部分的内容里,我们将使用Nethereum.Signer命名空间 中的类来管理密钥、账户和钱包。

私钥、公钥与地址

以太坊使用非对称密钥对来进行身份识别,每一个账户都有 对应的私钥和公钥 —— 私钥用来签名、公钥则用来验证签名 —— 从而 在非可信的去中心化环境中实现身份验证。

事实上,在以太坊上账户仅仅是对应于特定非对称密钥对中公钥的20字节 哈希

从私钥可以得到公钥,然后进一步得到账户地址,而反之则无效。 显然,以太坊不需要一个中心化的账户管理系统,我们可以根据以太坊约定 的算法自由地生成账户。

在C#中,可以使用EthECKey类来生成密钥对和账户地址。一个EthECKey 实例封装一个私钥,同时也提供了访问公钥和地址的方法:

例如,下面的代码首先使用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服务器将根据该订单的订单号 提取或生成对应的以太坊地址,然后在支付页面中展示该收款地址。为了 方便使用手机钱包的用户,可以同时在支付页面中展示该收款地址的二维码。

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

 

转载于:https://www.cnblogs.com/5ishare/p/10745479.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值