转发请注明出处:https://blog.youkuaiyun.com/ahy231/article/details/114086566
序
网上关于 go
语言开发 DApp
的教程较少,因此我只能通过官方文档来系统学习 go
语言的 DApp
开发。这篇文章是我对 https://geth.ethereum.org/docs/dapp/native-accounts 这篇文档的翻译,如有不符之处,欢迎斧正。
译文
Go 语言账户管理
要为您的本机应用程序提供以太坊集成,您首先感兴趣的应该是账户管理。
尽管目前所有领先的以太坊实现都提供内置帐户管理,但将帐户保留在多个应用程序或多人共享的任何地方都是不明智的。同样,您不应该将您的登录凭据委托给您的ISP(毕竟是您进入internet的网关);您也不应该将您的凭据委托给以太坊节点(作为您进入以太坊网络的网关)。
在本机应用程序中处理用户帐户的正确方法是进行客户端帐户管理,所有内容都包含在您自己的应用程序中。通过这种方式,您可以确保在必要时对敏感数据具有细粒度(或粗粒度)的访问权限,而不依赖任何第三方应用程序的功能和/或漏洞。
为了支持这一点,go-ethereum
提供了一个简单而全面的 accounts
包,它为您提供了所有工具,通过加密的密钥库和受密码保护的帐户来进行适当的安全帐户管理。您可以利用 go
以太坊加密实现的所有安全性,同时在自己的应用程序中运行所有内容。
加密钥匙仓库
尽管在本地处理应用程序的帐户确实提供了某些安全保障,但以太坊帐户的访问密钥决不能以明文形式存在。因此,我们提供了一个加密的密钥库,它为您提供了适当的安全保证,而不需要您彻底了解相关的加密协议。
使用加密密钥库时需要知道的重要一点是,其中使用的加密协议可以在标准模式或轻型模式下运行。前者以增加计算负担和资源消耗为代价提供了更高级别的安全性:
- 标准模式需要256MB内存和一个现代CPU通过1秒钟的处理来访问一个密钥
- 轻型模式需要4MB的内存和一个现代CPU通过100毫秒的处理来访问一个密匙
因此,标准模式更适合于本机应用程序,但是您应该注意权衡,以防您针对的是资源受限的环境。
对于那些对加密或实现细节感兴趣的人,密钥存储使用 secp256k1
椭圆曲线,如高效加密标准中所定义,由 libsecp256k
库实现,并由 github.com/ethereum/go-ethereum/accounts
。帐户以 Web3
秘密存储格式存储在磁盘上。
Go 的密钥库
加密的密钥库被 github.com/ethereum/go-ethereum/accounts
包中的 accounts.Manager
结构体实现,其中还包含上述标准或轻型安全模式的配置常量。因此,要从 Go
执行客户端帐户管理,您只需将 accounts
包导入到代码中:
import "github.com/ethereum/go-ethereum/accounts"
import "github.com/ethereum/go-ethereum/accounts/keystore"
import "github.com/ethereum/go-ethereum/common"
之后,您可以通过以下方式创建新的加密帐户管理器:
ks := keystore.NewKeyStore("/path/to/keystore", keystore.StandardScryptN, keystore.StandardScryptP)
am := accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: false}, ks)
keystore
文件夹的路径必须是本地用户可写但其他系统用户不可读的位置(显然出于安全原因),因此我们建议将其放置在用户的主目录中,或者对于后端应用程序更为锁定。
keystore.NewKeyStore
的最后两个参数是定义密钥库加密的资源密集程度的加密参数。你可以选择 keystore.StandardScryptN
, keystore.StandardScryptP
, keystore.LightScryptN
, keystore.LightScryptP
或者指定您自己的数字(请确保您了解这方面的基本密码)。我们建议使用标准版本。
账户生命周期
在为您的以太坊帐户创建了加密密钥库之后,您可以使用此帐户管理器满足本机应用程序的整个帐户生命周期要求。这包括创建新帐户和删除现有帐户的基本功能;以及更新访问凭证、导出现有帐户和在其他设备上导入这些帐户的更高级功能。
尽管密钥库定义了用于存储帐户的加密强度,但没有全局主密码可以授予对所有帐户的访问权限。相反,每个帐户都是单独维护的,并以其加密格式单独存储在磁盘上,从而确保凭证的分离更加干净和严格。
但是,这意味着任何需要访问帐户的操作都需要以密码短语的形式为该特定帐户提供必要的身份验证凭据:
- 创建新帐户时,调用者必须提供密码短语以加密帐户。任何后续访问都需要此密码短语,缺少此密码短语将使新创建的帐户永久丢失。
- 删除现有帐户时,调用者必须提供密码短语以验证帐户的所有权。这在密码上不是必要的,而是一种防止账户意外丢失的保护措施。
- 更新现有帐户时,调用者必须同时提供当前密码和新密码。完成操作后,将无法再通过旧密码访问该帐户。
- 导出现有帐户时,调用者必须提供用于解密帐户的当前密码短语,以及用于在将密钥文件返回给用户之前对其重新加密的导出密码短语。这是允许在计算机和应用程序之间移动帐户而不共享原始凭据所必需的。
- 导入新帐户时,调用者必须提供要导入的密钥文件的加密密码短语,以及用于存储帐户的新密码短语。这是允许使用不同的凭据存储帐户所必需的,而不是用于移动它们。
请注意,密码短语丢失没有恢复机制。加密密钥库的加密属性(如果使用提供的参数)保证在任何有意义的时间内都不能强制使用帐户凭据。
Go 语言账户操作
一个以太坊帐户是被 github.com/ethereum/go-ethereum/accounts
包的 accounts.Account
结构体实现的。假设我们已经在代码中定义了一个 accounts.Manager
实例 am
,我们可以通过少量的函数调用(省略错误处理)轻松地执行所描述的所有生命周期操作。
// Create a new account with the specified encryption passphrase.
newAcc, _ := ks.NewAccount("Creation password")
fmt.Println(newAcc)
// Export the newly created account with a different passphrase. The returned
// data from this method invocation is a JSON encoded, encrypted key-file.
jsonAcc, _ := ks.Export(newAcc, "Creation password", "Export password")
// Update the passphrase on the account created above inside the local keystore.
_ = ks.Update(newAcc, "Creation password", "Update password")
// Delete the account updated above from the local keystore.
_ = ks.Delete(newAcc, "Update password")
// Import back the account we've exported (and then deleted) above with yet
// again a fresh passphrase.
impAcc, _ := ks.Import(jsonAcc, "Export password", "Import password")
译者注:不知道为什么示例代码中没有 am
对象,可能是文档错把 ks
当成了 am
。
尽管 accounts.Account
实例可用于访问有关特定以太坊帐户的各种信息,它们不包含任何敏感数据(如密码短语或私钥),而仅作为客户端代码和密钥库的标识符。
数字签名
如上所述,account
对象并不持有相关以太坊帐户的敏感私钥,而仅仅是用以标识加密密钥的占位符。所有需要授权的操作(如交易签名)都由客户经理在授予其访问私钥的权限后执行。
有几种不同的方法可以授权客户经理执行签名操作,每种方法都有其优点和缺点。由于不同的方法具有完全不同的安全保证,因此必须明确每种方法的工作原理:
- 单一授权:通过帐户管理器对事务进行签名的最简单方法是,每次需要签名时提供帐户的密码短语,这将临时解密私钥,执行签名操作并立即丢弃解密的密钥。缺点是每次都需要从用户那里查询密码短语,如果经常这样做,可能会变得烦人;或者应用程序需要将密码短语保存在内存中,如果做得不好,可能会产生安全后果;并且取决于密钥库的配置强度,不断解密密钥会导致不可忽略的资源需求。
- 多重授权:通过帐户管理器对事务进行签名的一种更为复杂的方式是通过其密码短语对帐户进行一次解锁,并允许帐户管理器缓存解密的私钥,从而使所有后续签名请求在没有密码短语的情况下完成。可以手动(通过显式锁定帐户备份)或自动(通过在解锁期间提供超时)管理缓存私钥的生存期。这种机制对于用户可能需要签署许多事务或应用程序需要签署而不需要用户输入的情况非常有用。需要记住的关键方面是,任何有权访问帐户管理器的人都可以在特定帐户解锁时签署交易(例如,运行不受信任代码的应用程序)。
注意,在这里,创建事务超出了叙述范围,因此本节的其余部分将假设我们已经有了要签名的事务哈希,并且将只关注于创建授权它的加密签名。稍后将介绍如何创建实际事务并将授权签名注入其中。
Go 语言签名
假设我们已经在代码中定义 accounts.Manager
实例 am
,我们可以创建一个新帐户,通过已经演示过的 NewAccount
方法来签署交易;为了避免现在进入交易创建的部分,我们可以指定一个随机 common.Hash
作为签名。
// Create a new account to sign transactions with
signer, _ := ks.NewAccount("Signer password")
txHash := common.HexToHash("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
在文件实例结束后,我们现在可以使用上面描述的授权机制对事务进行签名:
// Sign a transaction with a single authorization
signature, _ := ks.SignHashWithPassphrase(signer, "Signer password", txHash.Bytes())
// Sign a transaction with multiple manually cancelled authorizations
_ = ks.Unlock(signer, "Signer password")
signature, _ = ks.SignHash(signer, txHash.Bytes())
_ = ks.Lock(signer.Address)
// Sign a transaction with multiple automatically cancelled authorizations
_ = ks.TimedUnlock(signer, "Signer password", time.Second)
signature, _ = ks.SignHash(signer, txHash.Bytes())
你可能想知道为什么 SignWithPassphrase
函数需要 accounts.Account
作为签名者,而签名只需要 commom.Address
原因是 accounts.Account
对象还可能包含自定义密钥路径,允许 SignWithPassphrase
函数使用密钥库之外的帐户进行签名;但是,Sign
依赖于密钥库中已解锁的帐户,因此它无法指定自定义路径。