برای ساختن یک کیف پول جدید، اول باید پکیج crypto از go-ethereum رو ایمپورت کنیم، داخل این پکیج متد GenerateKey هست که یه کلید خصوصی رندوم تولید میکنه.
privateKey, err := crypto.GenerateKey()
بعدش میتونیم این کلید رو به بایت تبدیل کنیم. برای این کار باید پکیج crypto/ecdsa رو ایمپورت کنیم و از متد FromECDSA استفاده کنیم.
privateKeyBytes := crypto.FromECDSA(privateKey)
حالا میتونیم این بایتها رو به یک string مبنای ۱۶ تبدیل کنیم. برای این کار از پکیج hexutil از go-ethereum استفاده میکنیم که متد Encode توش هست و یه آرایه بایت میگیره. بعد از Encode کردن به مبنای ۱۶، 0x اول رشته یا دو کاراکتر اولش رو حذف میکنیم.
fmt.Println(hexutil.Encode(privateKeyBytes)[2:]) // fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19
این کلید خصوصی هست که برای امضای تراکنشها استفاده میشه و باید مثل رمزعبور حفاظت بشه و هرگز به اشتراک گذاشته نشه، چون هر کسی که به این کلید دسترسی داشته باشه، میتونه به تمام موجودی شما دسترسی پیدا کنه.
از اونجایی که کلید عمومی از کلید خصوصی به دست میاد، کلید خصوصی توی go-ethereum یه متد به اسم Public داره که کلید عمومی رو برمیگردونه.
publicKey := privateKey.Public()
تبدیل این کلید عمومی به مبنای 16 مشابه فرایندی هست که برای کلید خصوصی انجام دادیم. اول نوعش رو به ecdsa.PublicKey تغییر میدیم، 0x اول رشته و 2 کاراکتر دیگه 04 که پیشوند EC هست و نیازی بهشون نداریم رو حذف میکنیم.
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") } publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA) fmt.Println(hexutil.Encode(publicKeyBytes)[4:]) // 9a7df67f79246283fdc93af76d4f8cdd62c4886e8cd870944e817dd0b97934fdd7719d0810951e03418205868a5c1b40b192451367f28e0088dd75e15de40c05
حالا که کلید عمومی رو داریم، میتونیم به راحتی آدرس عمومی رو هم تولید کنیم، این آدرس همون چیزیه که معمولا عادت داریم ببینیم. برای این کار، پکیج crypto توی go-ethereum یه متد PubkeyToAddress داره که کلید عمومی ECDSA میگیره و آدرس عمومی مربوط به اون رو برمیگردونه.
address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex() fmt.Println(address) // 0x96216849c49358B10257cb55b28eA603c874b05E
آدرس عمومی فقط هش Keccak-256 کلید عمومی هست که از اون 40 کاراکتر آخر (20 بایت) رو انتخاب میکنیم و 0x رو به اولش اضافه میکنیم. اینجا نحوه انجام دستی این کار با استفاده از تابع keccak256 تو پکیج crypto/sha3 رو نشون دادیم.
hash := sha3.NewLegacyKeccak256() hash.Write(publicKeyBytes[1:]) fmt.Println(hexutil.Encode(hash.Sum(nil)[12:])) // 0x96216849c49358b10257cb55b28ea603c874b05e
کد کامل
package main import ( "crypto/ecdsa" "fmt" "log" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "golang.org/x/crypto/sha3" ) func main() { privateKey, err := crypto.GenerateKey() if err != nil { log.Fatal(err) } privateKeyBytes := crypto.FromECDSA(privateKey) fmt.Println(hexutil.Encode(privateKeyBytes)[2:]) // fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19 publicKey := privateKey.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") } publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA) fmt.Println( hexutil.Encode(publicKeyBytes)[4:], ) // 9a7df67f79246283fdc93af76d4f8cdd62c4886e8cd870944e817dd0b97934fdd7719d0810951e03418205868a5c1b40b192451367f28e0088dd75e15de40c05 address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex() fmt.Println(address) // 0x96216849c49358B10257cb55b28eA603c874b05E hash := sha3.NewLegacyKeccak256() hash.Write(publicKeyBytes[1:]) fmt.Println(hexutil.Encode(hash.Sum(nil)[12:])) // 0x96216849c49358b10257cb55b28ea603c874b05e }
با استفاده از go-ethereum یه پکیج مدیریت حساب ساده ولی کامل در اختیار داریم که تمام ابزارای لازم برای پیادهسازی امن عملیات رمزنگاری تو یه برنامه بومی Go رو داره. مدیریت حسابها سمت کلاینت انجام میشه و همه دادههای حساس تو خود برنامه نگهداری میشن. این باعث میشه کنترل دسترسیها بدون وابستگی به شخص ثالثی دست خود کاربر باشه. این Keystore ها به توسعه دهنده امکان ذخیره سازی امن کلیدها و اطلاعات حساس حساب ها رو میدن.
هر فایل Keystore متشکل از کلید خصوصی رمزنگاری شده یک کیف پوله. Keystore ها توی go-ethereum فقط میتونن حاوی یک جفت کلید کیف پول در هر فایل باشن. برای تولید Keystore اول باید متد NewKeyStore رو صدا بزنیم و مسیر ذخیره Keystore ها رو بهش بدیم. بعد از اون میتونیم با صدا زدن متد NewAccount و پاس دادن یک رمزعبور برای رمزگذاری اطلاعات، یک کیف پول جدید تولید کنیم. هربار که NewAccount صدا زده بشه، یک فایل Keystore جدید روی دیسک ذخیره میشه.
این یک مثال کامل از تولید یک حساب کیف پول جدیده:
ks := keystore.NewKeyStore("./wallets", keystore.StandardScryptN, keystore.StandardScryptP) password := "secret" account, err := ks.NewAccount(password) if err != nil { log.Fatal(err) } fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3
دو آرگیومنت آخر متد NewKeyStore پارامترهای رمزنگاری هستن که مشخص میکنن رمزگذاری keystore چقدر منابع مصرف کنه. گزینههایی که میشه استفاده کرد StandardScryptN و StandardScryptP، همینطور LightScryptN و LightScryptP یا مقادیر سفارشی هستن. به طور کلی نسخه استاندارد توصیه میشه.
حالا برای ایمپورت کردن Keystore فقط کافیه دوباره مثل قبل NewKeyStore رو صدا بزنیم و بعد متد Import رو استفاده کنیم که محتوای جیسون Keystore رو به شکل بایت دریافت میکنه. آرگیومنت دوم هم همون رمزعبوری هست که برای رمزگذاری استفاده شده تا Keystore رو رمزگشایی کنه. آرگیومنت سوم هم برای مشخص کردن یک رمزعبور رمزگذاری جدید هست ولی توی این مثال از همون رمز قبلی استفاده میکنیم. با ایمپورت کردن Keystore دسترسی مورد نظر به حساب رو پیدا میکنیم ولی یک فایل Keystore جدید هم تولید میشه! نگه داشتن دوتا فایل یکسان بیمعنیه، پس فایل قدیمی رو حذف میکنیم.
اینجا هم یک مثال از ایمپورت کردن Keystore و دسترسی به حساب رو داریم:
file := "./wallets/UTC--2018-07-04T09-58-30.122808598Z--20f8d42fb0f667f2e53930fed426f225752453b3" ks := keystore.NewKeyStore("./tmp", keystore.StandardScryptN, keystore.StandardScryptP) jsonBytes, err := ioutil.ReadFile(file) if err != nil { log.Fatal(err) } password := "secret" account, err := ks.Import(jsonBytes, password, password) if err != nil { log.Fatal(err) } fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3 if err := os.Remove(file); err != nil { log.Fatal(err) }
برای اطلاعات بیشتر در مورد Keystore ها و چرخه استفادشون میتونید به این لینک از داکیومنتیشن اصلی مراجعه کنید.
کد کامل
package main import ( "fmt" "io/ioutil" "log" "os" "github.com/ethereum/go-ethereum/accounts/keystore" ) func createKs() { ks := keystore.NewKeyStore("./tmp", keystore.StandardScryptN, keystore.StandardScryptP) password := "secret" account, err := ks.NewAccount(password) if err != nil { log.Fatal(err) } fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3 } func importKs() { file := "./tmp/UTC--2018-07-04T09-58-30.122808598Z--20f8d42fb0f667f2e53930fed426f225752453b3" ks := keystore.NewKeyStore("./tmp", keystore.StandardScryptN, keystore.StandardScryptP) jsonBytes, err := ioutil.ReadFile(file) if err != nil { log.Fatal(err) } password := "secret" account, err := ks.Import(jsonBytes, password, password) if err != nil { log.Fatal(err) } fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3 if err := os.Remove(file); err != nil { log.Fatal(err) } } func main() { createKs() //importKs() }
راه حل دیگه ای که برای ایجاد کیف پول اتریوم داریم استفاده از HD wallet هست که احتمال زیاد باهاش برخورد کردید. برای ایجاد یا استفاده از کیف پول HD wallet، میتونید به پکیج Go زیر مراجعه کنید:
https://github.com/miguelmota/go-ethereum-hdwallet
توی بخش بعدی درباره این صحبت میکنیم که چطور تراکنش های اتریوم رو کوئری بزنیم و تراکنش جدید ایجاد کنیم.