用Go來做以太坊開發(fā)②以太坊賬戶

賬戶

以太坊上的賬戶要么是錢包地址要么是智能合約地址赴背。它們看起來像是0x71c7656ec7ab88b098defb751b7401b5f6d8976f锈锤,它們用于將ETH發(fā)送到另一個用戶固以,并且還用于在需要和區(qū)塊鏈交互時指一個智能合約杆勇。它們是唯一的,且是從私鑰導(dǎo)出的馆衔。我們將在后面的章節(jié)更深入地介紹公私鑰對井誉。

要使用go-ethereun的賬戶地址蕉扮,您必須先將它們轉(zhuǎn)化為go-ethereum中的common.Address類型。

address := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f")

fmt.Println(address.Hex()) // 0x71C7656EC7ab88b098defB751B7401B5f6d8976F

您可以在幾乎任何地方使用這種類型颗圣,您可以將以太坊地址傳遞給go-ethereum的方法喳钟。既然您已經(jīng)了解賬戶和地址的基礎(chǔ)知識,那么讓我們在下一節(jié)中學(xué)習(xí)如何檢索ETH賬戶余額在岂。

完整代碼

address.go

package main

import (
    "fmt"

    "github.com/ethereum/go-ethereum/common"
)

func main() {
    address := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f")

    fmt.Println(address.Hex())        // 0x71C7656EC7ab88b098defB751B7401B5f6d8976F
    fmt.Println(address.Hash().Hex()) // 0x00000000000000000000000071c7656ec7ab88b098defb751b7401b5f6d8976f
    fmt.Println(address.Bytes())      // [113 199 101 110 199 171 136 176 152 222 251 117 27 116 1 181 246 216 151 111]
}

賬戶余額

讀取一個賬戶的余額相當簡單奔则。調(diào)用客戶端的BalanceAt方法,給它傳遞賬戶地址和可選的區(qū)塊號蔽午。將區(qū)塊號設(shè)置為nil將返回最新的余額易茬。

account := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f")
balance, err := client.BalanceAt(context.Background(), account, nil)
if err != nil {
  log.Fatal(err)
}

fmt.Println(balance) // 25893180161173005034

傳區(qū)塊號能讓您讀取該區(qū)塊時的賬戶余額。區(qū)塊號必須是big.Int類型及老。

blockNumber := big.NewInt(5532993)
balance, err := client.BalanceAt(context.Background(), account, blockNumber)
if err != nil {
  log.Fatal(err)
}

fmt.Println(balance) // 25729324269165216042

以太坊中的數(shù)字是使用盡可能小的單位來處理的抽莱,因為它們是定點精度绊起,在ETH中它是wei银锻。要讀取ETH值,您必須做計算wei/10^18狰腌。因為我們正在處理大數(shù)僧鲁,我們得導(dǎo)入原生的Gomathmath/big包虐呻。這是您做的轉(zhuǎn)換。

fbalance := new(big.Float)
fbalance.SetString(balance.String())
ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18)))

fmt.Println(ethValue) // 25.729324269165216041

待處理的余額

有時您想知道待處理的賬戶余額是多少寞秃,例如斟叼,在提交或等待交易確認后⊥筛茫客戶端提供了類似BalanceAt的方法犁柜,名為PendingBalanceAt洲鸠,它接收賬戶地址作為參數(shù)堂淡。

pendingBalance, err := client.PendingBalanceAt(context.Background(), account)
fmt.Println(pendingBalance) // 25729324269165216042

完整代碼

account_balance.go

package main

import (
    "context"
    "fmt"
    "log"
    "math"
    "math/big"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethclient"
)

func main() {
    client, err := ethclient.Dial("https://mainnet.infura.io")
    if err != nil {
        log.Fatal(err)
    }

    account := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f")
    balance, err := client.BalanceAt(context.Background(), account, nil)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(balance) // 25893180161173005034

    blockNumber := big.NewInt(5532993)
    balanceAt, err := client.BalanceAt(context.Background(), account, blockNumber)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(balanceAt) // 25729324269165216042

    fbalance := new(big.Float)
    fbalance.SetString(balanceAt.String())
    ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18)))
    fmt.Println(ethValue) // 25.729324269165216041

    pendingBalance, err := client.PendingBalanceAt(context.Background(), account)
    fmt.Println(pendingBalance) // 25729324269165216042
}


賬戶代幣余額

要學(xué)習(xí)如何讀取賬戶代幣(ECR20)余額,請前往ECR20代幣智能合約章節(jié)扒腕。


生成新錢包

要首先生成一個新的錢包绢淀,我們需要導(dǎo)入go-ethereumcrypto包,該包提供用于生成隨機私鑰的GenerateKey方法瘾腰。

privateKey, err := crypto.GenerateKey()
if err != nil {
  log.Fatal(err)
}

然后我們可以通過導(dǎo)入golangcrypto/ecdsa包并使用FromECDSA方法將其轉(zhuǎn)換為字節(jié)皆的。

privateKeyBytes := crypto.FromECDSA(privateKey)

我們現(xiàn)在可以使用go-ethereumhexutil包將它轉(zhuǎn)換為十六進制字符串,該包提供了一個帶有字節(jié)切片的Encode方法蹋盆。 然后我們在十六進制編碼之后刪除“0x”费薄。

fmt.Println(hexutil.Encode(privateKeyBytes)[2:]) // fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19

這就是用于簽署交易的私鑰硝全,將被視為密碼,永遠不應(yīng)該被共享給別人楞抡,因為誰擁有它可以訪問你的所有資產(chǎn)伟众。

由于公鑰是從私鑰派生的,因此go-ethereum的加密私鑰具有一個返回公鑰的Public方法召廷。

publicKey := privateKey.Public()

將其轉(zhuǎn)換為十六進制的過程與我們使用轉(zhuǎn)化私鑰的過程類似凳厢。 我們剝離了0x和前2個字符04,它始終是EC前綴竞慢,不是必需的先紫。

publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
  log.Fatal("error casting public key to ECDSA")
}

publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA)
fmt.Println(hexutil.Encode(publicKeyBytes)[4:]) // 9a7df67f79246283fdc93af76d4f8cdd62c4886e8cd870944e817dd0b97934fdd7719d0810951e03418205868a5c1b40b192451367f28e0088dd75e15de40c05

現(xiàn)在我們擁有公鑰,就可以輕松生成你經(jīng)吵镏螅看到的公共地址遮精。 為了做到這一點,go-ethereum加密包有一個PubkeyToAddress方法败潦,它接受一個ECDSA公鑰仑鸥,并返回公共地址。

address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
fmt.Println(address) // 0x96216849c49358B10257cb55b28eA603c874b05E

公共地址其實就是公鑰的Keccak-256哈希变屁,然后我們?nèi)∽詈?0個字符(20個字節(jié))并用“0x”作為前綴眼俊。 以下是使用go-ethereum的crypto/sha3 Keccak256函數(shù)手動完成的方法。

hash := sha3.NewKeccak256()
hash.Write(publicKeyBytes[1:])
fmt.Println(hexutil.Encode(hash.Sum(nil)[12:])) // 0x96216849c49358b10257cb55b28ea603c874b05e

完整代碼

generate_wallet.go

package main

import (
    "crypto/ecdsa"
    "fmt"
    "log"

    "github.com/ethereum/go-ethereum/common/hexutil"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/crypto/sha3"
)

func main() {
    privateKey, err := crypto.GenerateKey()
    if err != nil {
        log.Fatal(err)
    }

    privateKeyBytes := crypto.FromECDSA(privateKey)
    fmt.Println(hexutil.Encode(privateKeyBytes)[2:]) // 0xfad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19

    publicKey := privateKey.Public()
    publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
    if !ok {
        log.Fatal("error casting public key to ECDSA")
    }

    publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA)
    fmt.Println(hexutil.Encode(publicKeyBytes)[4:]) // 0x049a7df67f79246283fdc93af76d4f8cdd62c4886e8cd870944e817dd0b97934fdd7719d0810951e03418205868a5c1b40b192451367f28e0088dd75e15de40c05

    address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
    fmt.Println(address) // 0x96216849c49358B10257cb55b28eA603c874b05E

    hash := sha3.NewKeccak256()
    hash.Write(publicKeyBytes[1:])
    fmt.Println(hexutil.Encode(hash.Sum(nil)[12:])) // 0x96216849c49358b10257cb55b28ea603c874b05e
}


密鑰庫 Keystores

keystore是一個包含經(jīng)過加密了的錢包私鑰粟关。go-ethereum中的keystore疮胖,每個文件只能包含一個錢包密鑰對。要生成keystore闷板,首先您必須調(diào)用NewKeyStore澎灸,給它提供保存keystore的目錄路徑。然后遮晚,您可調(diào)用NewAccount方法創(chuàng)建新的錢包性昭,并給它傳入一個用于加密的口令。您每次調(diào)用NewAccount县遣,它將在磁盤上生成新的keystore文件糜颠。

這是一個完整的生成新的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

現(xiàn)在要導(dǎo)入您的keystore萧求,您基本上像往常一樣再次調(diào)用NewKeyStore其兴,然后調(diào)用Import方法,該方法接收keystore的JSON數(shù)據(jù)作為字節(jié)夸政。第二個參數(shù)是用于加密私鑰的口令元旬。第三個參數(shù)是指定一個新的加密口令,但我們在示例中使用一樣的口令。導(dǎo)入賬戶將允許您按期訪問該賬戶匀归,但它將生成新keystore文件坑资!有兩個相同的事物是沒有意義的,所以我們將刪除舊的穆端。

這是一個導(dǎo)入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.go

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

關(guān)于創(chuàng)建或使用一個分層確定性(HD)錢包,請參考Go包: https://github.com/miguelmota/go-ethereum-hdwallet


地址檢查

本節(jié)將介紹如何確認一個地址并確定其是否為智能合約地址徙赢。

檢查地址是否有效

我們可以使用簡單的正則表達式來檢查以太坊地址是否有效:

We can use a simple regular expression to check if the ethereum address is valid:

re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$")

fmt.Printf("is valid: %v\n", re.MatchString("0x323b5d4c32345ced77393b3530b1eed0f346429d")) // is valid: true
fmt.Printf("is valid: %v\n", re.MatchString("0xZYXb5d4c32345ced77393b3530b1eed0f346429d")) // is valid: false

檢查地址是否為賬戶或智能合約

我們可以確定字柠,若在該地址存儲了字節(jié)碼,該地址是智能合約狡赐。這是一個示例窑业,在例子中,我們獲取一個代幣智能合約的字節(jié)碼并檢查其長度以驗證它是一個智能合約:

// 0x Protocol Token (ZRX) smart contract address
address := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498")
bytecode, err := client.CodeAt(context.Background(), address, nil) // nil is latest block
if err != nil {
  log.Fatal(err)
}

isContract := len(bytecode) > 0

fmt.Printf("is contract: %v\n", isContract) // is contract: true

當?shù)刂飞蠜]有字節(jié)碼時枕屉,我們知道它不是一個智能合約常柄,它是一個標準的以太坊賬戶。

// a random user account address
address := common.HexToAddress("0x8e215d06ea7ec1fdb4fc5fd21768f4b34ee92ef4")
bytecode, err := client.CodeAt(context.Background(), address, nil) // nil is latest block
if err != nil {
  log.Fatal(err)
}

isContract = len(bytecode) > 0

fmt.Printf("is contract: %v\n", isContract) // is contract: false

完整代碼

address_check.go

package main

import (
    "context"
    "fmt"
    "log"
    "regexp"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethclient"
)

func main() {
    re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$")

    fmt.Printf("is valid: %v\n", re.MatchString("0x323b5d4c32345ced77393b3530b1eed0f346429d")) // is valid: true
    fmt.Printf("is valid: %v\n", re.MatchString("0xZYXb5d4c32345ced77393b3530b1eed0f346429d")) // is valid: false

    client, err := ethclient.Dial("https://mainnet.infura.io")
    if err != nil {
        log.Fatal(err)
    }

    // 0x Protocol Token (ZRX) smart contract address
    address := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498")
    bytecode, err := client.CodeAt(context.Background(), address, nil) // nil is latest block
    if err != nil {
        log.Fatal(err)
    }

    isContract := len(bytecode) > 0

    fmt.Printf("is contract: %v\n", isContract) // is contract: true

    // a random user account address
    address = common.HexToAddress("0x8e215d06ea7ec1fdb4fc5fd21768f4b34ee92ef4")
    bytecode, err = client.CodeAt(context.Background(), address, nil) // nil is latest block
    if err != nil {
        log.Fatal(err)
    }

    isContract = len(bytecode) > 0

    fmt.Printf("is contract: %v\n", isContract) // is contract: false
}

文章不定期更新搀擂,小編微信:grey0805西潘,歡迎交流

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市哨颂,隨后出現(xiàn)的幾起案子喷市,更是在濱河造成了極大的恐慌,老刑警劉巖威恼,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件品姓,死亡現(xiàn)場離奇詭異,居然都是意外死亡箫措,警方通過查閱死者的電腦和手機腹备,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來斤蔓,“玉大人植酥,你說我怎么就攤上這事∠夷担” “怎么了友驮?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長喇伯。 經(jīng)常有香客問我喊儡,道長拨与,這世上最難降的妖魔是什么稻据? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上捻悯,老公的妹妹穿的比我還像新娘匆赃。我一直安慰自己,他們只是感情好今缚,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布算柳。 她就那樣靜靜地躺著,像睡著了一般姓言。 火紅的嫁衣襯著肌膚如雪瞬项。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天何荚,我揣著相機與錄音囱淋,去河邊找鬼。 笑死餐塘,一個胖子當著我的面吹牛妥衣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播戒傻,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼税手,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了需纳?” 一聲冷哼從身側(cè)響起芦倒,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎不翩,沒想到半個月后熙暴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡慌盯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年周霉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亚皂。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡俱箱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出灭必,到底是詐尸還是另有隱情狞谱,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布禁漓,位于F島的核電站跟衅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏播歼。R本人自食惡果不足惜伶跷,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧叭莫,春花似錦蹈集、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至靖诗,卻和暖如春郭怪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刊橘。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工移盆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伤为。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓咒循,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绞愚。 傳聞我的和親對象是個殘疾皇子叙甸,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 安東尼奧·皮雷利和基拉·桑塞恩是艾爾文森林里的兩個旅行商人裆蒸,他們是一對令人羨慕的夫妻,而這里糖驴,是他們剛認識時的故事...
    黑館長閱讀 715評論 0 0
  • 在生活中僚祷,不外乎柴米油鹽的瑣事。有的人能過的風(fēng)生水起贮缕,有的人卻悲觀失望辙谜;有的人把日子過成了詩,有的人卻把日子過成了...
    史麗芬閱讀 579評論 1 4
  • 下午接到媽媽的電話感昼,第一次聽到電話那頭媽媽慌亂装哆、帶著哭聲說:“我摔跤了,手撐在地上的定嗓,腫起來了蜕琴,現(xiàn)在很疼,我想去醫(yī)...
    愛yu自由閱讀 165評論 2 3
  • 你能成為什么樣的人宵溅,取決于你想成為什么樣的人凌简。 我是一名普通的不能在普通的大學(xué)生,沒有漂亮的臉蛋恃逻、傲人的身材雏搂、驚人...
    遇見不同的自己閱讀 249評論 0 0
  • 不吃香蕉的麻麻有個愛吃香蕉的寶寶?
    么么茶63閱讀 359評論 0 0