以太坊之帳戶管理

在這一部分,咱們將學習如何使用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服務器將根據該訂單的訂單號 提取或生成對應的以太坊地址,而後在支付頁面中展現該收款地址。爲了 方便使用手機錢包的用戶,能夠同時在支付頁面中展現該收款地址的二維碼。

用戶使用本身的以太坊錢包向該收款地址支付以太幣。因爲網站的支付處理 進程在週期性地檢查該收款地址的餘額,一旦收到足額款項,支付處理進程 就能夠根據收款地址將對應的訂單結束,併爲用戶開通對應的服務。

相關文章
相關標籤/搜索