[C#] 產生 TRX 錢包,使用 ETH 錢包轉換 透過 Nethereum

2021-11-15


最近在弄關於 TRX 的項目,因為最近 ETH 的 GAS 真的太高了,所以目標先鎖定 TRX

之後有時間我也會順便把 BSC 也研究一下,這篇開始我們作一系列的文章。



波場幣 (TRX) 就是Tron的區塊鏈代幣,但通常大家會接觸Tron的原因是因為TRC20傳輸通道在傳送USDT時手續費很少,所以現在幾乎都會使用 TRC20 來傳送 USDT

今天,主要是分享怎麼產生 TRX 的錢包。

其實,研究後才發現,之前那篇文章Ethereum 以太坊產生高清錢包 (HD Wallet)

我們透過 Nethereum.HdWallet  就可以產出高清錢包,後來發現,其實 TRON 錢包算是魔改 ERC20 的錢包,所以透過 產出乙太錢包,也可以產出 TRON 錢包

C# Code :

public void CreateWallet() { //Code reference : https://www.796t.com/article.php?id=22311 //Code reference : https://blog.no2don.com/2021/10/c-ethereum-hd-wallet.html var words = "會不會有人知道當麻在這個孤獨的星球曾這樣的活過過"; var password = "password"; for (var index = 0; index <= 10; index++) { var walletAccount = GetHDWalletInfoByIndex(string.Join(" ", words.ToArray()), password, index); Result += ("編號:" + index + " , 錢包位置:" + GetBase58CheckAddress(walletAccount.Address) + " ,私鑰:" + walletAccount.PrivateKey); Result += ("<br>"); } } public static Nethereum.Web3.Accounts.Account GetHDWalletInfoByIndex(string words, string password, int index) { return new Wallet(words, password).GetAccount(index); } public static string GetBase58CheckAddress(string ethAddress) { string fixaddress = "0x41" + ethAddress.RemoveHexPrefix(); byte[] addressBytes = fixaddress.HexToByteArray(); byte[] hash0 = SHA256(addressBytes); byte[] hash1 = SHA256(hash0); var checkSum = hash1.Take(4).ToArray(); return MetaUtil.Base58Encoding.Encode(addressBytes.Concat(checkSum).ToArray()); } public static byte[] SHA256(byte[] data) { using (var sha256 = new SHA256Managed()) { return sha256.ComputeHash(data); } }

這其中

他其實就是將 0x 拿掉後改成 0x41 這樣就可以確保產出的錢包是 T 開頭的,所以波場錢包都是 T 開頭的錢包

( TRON 地址為大寫字母 T 開頭的 34 字元地址,以太坊地址前附加位元組 0x41 後執行 Base58check 操作所得)

網路上你會看到許多 ETH 轉成 TRX 範例 ,基本上跟我大同小異,但是我這邊不一樣的事我改用另一個 lib ( https://gist.github.com/donma/03d4ed4f0092e749aa7eae6720c004e2/edit )


這邊也附上程式碼 :

using System; using System.Diagnostics.Contracts; using System.Linq; namespace Merkator.Tools { public class ArrayHelpers { public static T[] ConcatArrays<T>(params T[][] arrays) { //Contract.Requires(arrays != null); //Contract.Requires(Contract.ForAll(arrays, (arr) => arr != null)); //Contract.Ensures(Contract.Result<T[]>() != null); //Contract.Ensures(Contract.Result<T[]>().Length == arrays.Sum(arr => arr.Length)); var result = new T[arrays.Sum(arr => arr.Length)]; int offset = 0; for (int i = 0; i < arrays.Length; i++) { var arr = arrays[i]; Buffer.BlockCopy(arr, 0, result, offset, arr.Length); offset += arr.Length; } return result; } public static T[] ConcatArrays<T>(T[] arr1, T[] arr2) { /* Contract.Requires(arr1 != null); Contract.Requires(arr2 != null); Contract.Ensures(Contract.Result<T[]>() != null); Contract.Ensures(Contract.Result<T[]>().Length == arr1.Length + arr2.Length); */ var result = new T[arr1.Length + arr2.Length]; Buffer.BlockCopy(arr1, 0, result, 0, arr1.Length); Buffer.BlockCopy(arr2, 0, result, arr1.Length, arr2.Length); return result; } public static T[] SubArray<T>(T[] arr, int start, int length) { /* Contract.Requires(arr != null); Contract.Requires(start >= 0); Contract.Requires(length >= 0); Contract.Requires(start + length <= arr.Length); Contract.Ensures(Contract.Result<T[]>() != null); Contract.Ensures(Contract.Result<T[]>().Length == length); */ var result = new T[length]; Buffer.BlockCopy(arr, start, result, 0, length); return result; } public static T[] SubArray<T>(T[] arr, int start) { /* Contract.Requires(arr != null); Contract.Requires(start >= 0); Contract.Requires(start <= arr.Length); Contract.Ensures(Contract.Result<T[]>() != null); Contract.Ensures(Contract.Result<T[]>().Length == arr.Length - start); */ return SubArray(arr, start, arr.Length - start); } } } using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; using System.Numerics; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using Merkator.Tools; namespace Merkator.BitCoin { // Implements https://en.bitcoin.it/wiki/Base58Check_encoding public static class Base58Encoding { public const int CheckSumSizeInBytes = 4; public static byte[] AddCheckSum(byte[] data) { //Contract.Requires<ArgumentNullException>(data != null); //Contract.Ensures(Contract.Result<byte[]>().Length == data.Length + CheckSumSizeInBytes); byte[] checkSum = GetCheckSum(data); byte[] dataWithCheckSum = ArrayHelpers.ConcatArrays(data, checkSum); return dataWithCheckSum; } //Returns null if the checksum is invalid public static byte[] VerifyAndRemoveCheckSum(byte[] data) { //Contract.Requires<ArgumentNullException>(data != null); //Contract.Ensures(Contract.Result<byte[]>() == null || Contract.Result<byte[]>().Length + CheckSumSizeInBytes == data.Length); byte[] result = ArrayHelpers.SubArray(data, 0, data.Length - CheckSumSizeInBytes); byte[] givenCheckSum = ArrayHelpers.SubArray(data, data.Length - CheckSumSizeInBytes); byte[] correctCheckSum = GetCheckSum(result); if (givenCheckSum.SequenceEqual(correctCheckSum)) return result; else return null; } private const string Digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; public static string Encode(byte[] data) { //Contract.Requires<ArgumentNullException>(data != null); //Contract.Ensures(Contract.Result<string>() != null); // Decode byte[] to BigInteger BigInteger intData = 0; for (int i = 0; i < data.Length; i++) { intData = intData * 256 + data[i]; } // Encode BigInteger to Base58 string string result = ""; while (intData > 0) { int remainder = (int)(intData % 58); intData /= 58; result = Digits[remainder] + result; } // Append `1` for each leading 0 byte for (int i = 0; i < data.Length && data[i] == 0; i++) { result = '1' + result; } return result; } public static string EncodeWithCheckSum(byte[] data) { //Contract.Requires<ArgumentNullException>(data != null); //Contract.Ensures(Contract.Result<string>() != null); return Encode(AddCheckSum(data)); } public static byte[] Decode(string s) { //Contract.Requires<ArgumentNullException>(s != null); //Contract.Ensures(Contract.Result<byte[]>() != null); // Decode Base58 string to BigInteger BigInteger intData = 0; for (int i = 0; i < s.Length; i++) { int digit = Digits.IndexOf(s[i]); //Slow if (digit < 0) throw new FormatException(string.Format("Invalid Base58 character `{0}` at position {1}", s[i], i)); intData = intData * 58 + digit; } // Encode BigInteger to byte[] // Leading zero bytes get encoded as leading `1` characters int leadingZeroCount = s.TakeWhile(c => c == '1').Count(); var leadingZeros = Enumerable.Repeat((byte)0, leadingZeroCount); var bytesWithoutLeadingZeros = intData.ToByteArray() .Reverse()// to big endian .SkipWhile(b => b == 0);//strip sign byte var result = leadingZeros.Concat(bytesWithoutLeadingZeros).ToArray(); return result; } // Throws `FormatException` if s is not a valid Base58 string, or the checksum is invalid public static byte[] DecodeWithCheckSum(string s) { //Contract.Requires<ArgumentNullException>(s != null); //Contract.Ensures(Contract.Result<byte[]>() != null); var dataWithCheckSum = Decode(s); var dataWithoutCheckSum = VerifyAndRemoveCheckSum(dataWithCheckSum); if (dataWithoutCheckSum == null) throw new FormatException("Base58 checksum is invalid"); return dataWithoutCheckSum; } private static byte[] GetCheckSum(byte[] data) { //Contract.Requires<ArgumentNullException>(data != null); //Contract.Ensures(Contract.Result<byte[]>() != null); SHA256 sha256 = new SHA256Managed(); byte[] hash1 = sha256.ComputeHash(data); byte[] hash2 = sha256.ComputeHash(hash1); var result = new byte[CheckSumSizeInBytes]; Buffer.BlockCopy(hash2, 0, result, 0, result.Length); return result; } } } using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Merkator.BitCoin.Tests { [TestClass] public class Base58EncodingTests { // Test cases from https://github.com/bitcoin/bitcoin/blob/master/src/test/base58_tests.cpp Tuple<string, byte[]>[] testCases = new Tuple<string, byte[]>[]{ Tuple.Create("",new byte[]{}), Tuple.Create("1112",new byte[]{0x00, 0x00, 0x00, 0x01}), Tuple.Create("2g",new byte[]{0x61}), Tuple.Create("a3gV",new byte[]{0x62,0x62,0x62}), Tuple.Create("aPEr",new byte[]{0x63,0x63,0x63}), Tuple.Create("2cFupjhnEsSn59qHXstmK2ffpLv2",new byte[]{0x73,0x69,0x6d,0x70,0x6c,0x79,0x20,0x61,0x20,0x6c,0x6f,0x6e,0x67,0x20,0x73,0x74,0x72,0x69,0x6e,0x67}), Tuple.Create("1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L",new byte[]{0x00,0xeb,0x15,0x23,0x1d,0xfc,0xeb,0x60,0x92,0x58,0x86,0xb6,0x7d,0x06,0x52,0x99,0x92,0x59,0x15,0xae,0xb1,0x72,0xc0,0x66,0x47}), Tuple.Create("ABnLTmg",new byte[]{0x51,0x6b,0x6f,0xcd,0x0f}), Tuple.Create("3SEo3LWLoPntC",new byte[]{0xbf,0x4f,0x89,0x00,0x1e,0x67,0x02,0x74,0xdd}), Tuple.Create("3EFU7m",new byte[]{0x57,0x2e,0x47,0x94}), Tuple.Create("EJDM8drfXA6uyA",new byte[]{0xec,0xac,0x89,0xca,0xd9,0x39,0x23,0xc0,0x23,0x21}), Tuple.Create("Rt5zm",new byte[]{0x10,0xc8,0x51,0x1e}), Tuple.Create("1111111111",new byte[]{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}) }; [TestMethod] public void Encode() { foreach (var tuple in testCases) { var bytes = tuple.Item2; var expectedText = tuple.Item1; var actualText = Base58Encoding.Encode(bytes); Assert.AreEqual(expectedText, actualText); } } [TestMethod] public void Decode() { foreach (var tuple in testCases) { var text = tuple.Item1; var expectedBytes = tuple.Item2; var actualBytes = Base58Encoding.Decode(text); Assert.AreEqual(BitConverter.ToString(expectedBytes), BitConverter.ToString(actualBytes)); } } [TestMethod] [ExpectedException(typeof(FormatException))] public void DecodeInvalidChar() { Base58Encoding.Decode("ab0"); } // Example address from https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses byte[] addressBytes = new byte[] { 0x00, 0x01, 0x09, 0x66, 0x77, 0x60, 0x06, 0x95, 0x3D, 0x55, 0x67, 0x43, 0x9E, 0x5E, 0x39, 0xF8, 0x6A, 0x0D, 0x27, 0x3B, 0xEE }; string addressText = "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM"; string brokenAddressText = "16UwLl9Risc3QfPqBUvKofHmBQ7wMtjvM"; [TestMethod] public void EncodeBitcoinAddress() { var actualText = Base58Encoding.EncodeWithCheckSum(addressBytes); Assert.AreEqual(addressText, actualText); } [TestMethod] public void DecodeBitcoinAddress() { var actualBytes = Base58Encoding.DecodeWithCheckSum(addressText); Assert.AreEqual(BitConverter.ToString(addressBytes), BitConverter.ToString(actualBytes)); } [TestMethod] [ExpectedException(typeof(FormatException))] public void DecodeBrokenBitcoinAddress() { var actualBytes = Base58Encoding.DecodeWithCheckSum(brokenAddressText); Assert.AreEqual(BitConverter.ToString(addressBytes), BitConverter.ToString(actualBytes)); } } }


原文參考中,他轉 base 58 跟做 SHA 的時候,是使用 Tron.Net.Client,但是因為我覺得還是能夠在自己專案中就少用 lib ,所以網路上看到一個不錯的就使用,用起來也是正常。


結果當然我們就是要安裝 tronlink 來試試看, tronlink 安裝連結 : https://chrome.google.com/webstore/detail/tronlink%EF%BC%88%E6%B3%A2%E5%AE%9D%E9%92%B1%E5%8C%85%EF%BC%89/ibnejdfjmmkpcnlpebklmnkoeoihofec


result:

編號:0 , 錢包位置:TNWFKAodrxankYgHg9nJmNbYTsqi8MtCWx ,私鑰:0x8ca224fec50afaffc70e445ae2d01434e666268298548f67597068735562ad10
編號:1 , 錢包位置:TLhTDxNcm9XaSq35mghq6Az35LeHdcn77S ,私鑰:0xcf893594f8c20fc51ec5452169be4691e58f9abe624ba803c95692d5514c08d6
編號:2 , 錢包位置:TWTzoadibCZiCph6y878bTPB7ta8WXaMFR ,私鑰:0x02f49fd0f312f53a35e092f9da6329374acfdbe45bfb8c32de2f4fe3943e90d7
編號:3 , 錢包位置:TEyYFGGYTBSyLfsrbhTMqdqMkM9TTAPsyE ,私鑰:0x018fe3b25668c393bbc4953ef9f8e22201e769f422f6493049b0cafb0c2d2fde
編號:4 , 錢包位置:TDSZ64gn4FV2mQwG7BAqvK2WhZbujukbuJ ,私鑰:0x3881ed87be2ec87069466cd4316087828e0712e592965eaed83ed84fde26dbb0
編號:5 , 錢包位置:TRza1rbWRhgnRKvTSxnkzkz7RP1Xem6TFJ ,私鑰:0x6984206e9e1af8090a120774fd657d88b278f8bde685ef8532dabe9d61f557bc
編號:6 , 錢包位置:TFuvzv1kHX9Kw6kTZtv6p7PUMWvqRKZwYF ,私鑰:0xa93e86896010eabb2dbbdf788cb183630ab3391c85f16e3dacc9beb53faef911
編號:7 , 錢包位置:TBG4p2hMxmdj3a1n2qGGH589uPVj5n29Bm ,私鑰:0x1d7617dcc35e942710b495764681279a19caf5c9c26d824cf093b9162ccef608
編號:8 , 錢包位置:TBa6tz95W5baRro9pXRRRntZNdpvtg3fTw ,私鑰:0x65da4a4e30a8174388d97650869ab6c88f0ff539b1d5f66c369f1554fd5ba01f
編號:9 , 錢包位置:TUbwHugEn5FXZgWiMtCNSa3d9wz1sVqner ,私鑰:0x010e0f160a3c8f3f891858d29dceecedccede8d87a6d29e164d2f42cfc0c1aee
編號:10 , 錢包位置:TQgCvX6NViUZ4p8WqAuAowZSjnujsXmWQ9 ,私鑰:0xfc76bc0ac79f66c982a638b6eeff6c60bad9f5e554394b3f140a281cb3317fb0



只要夠成功加入錢包到 tronlink 就是代表他是一個正常的錢包了。


這是開始,之後我會再弄寫其它應用,至少這是重要的開始

reference:

https://andelf.gitbook.io/tron/introduction/tron-basics

https://www.796t.com/article.php?id=22311


當麻許的碎念筆記 2014 | Donma Hsu Design.