文章目录
- 一、账户基本概念
-
- [1.1 外部账户](#1.1 外部账户)
- [1.2 合约账户](#1.2 合约账户)
- [1.3 差异对比](#1.3 差异对比)
- 二、帐户创建
-
- [2.1 外部账户创建](#2.1 外部账户创建)
- [2.2 合约账户创建](#2.2 合约账户创建)
- 三、账户数据结构
-
- [3.1 账户状态](#3.1 账户状态)
- [3.2 账户状态结构](#3.2 账户状态结构)
对比比特币的 "UTXO" 余额模型,以太坊使用"账户"余额模型。 以太坊丰富了账户内容,除余额外还能自定义存放任意多数据。 并利用账户数据的可维护性,构建智能合约账户。
实际上以太坊是为了实现智能合约而提炼的账户模型。 以账户为单位,安全隔离数据。 账户间信息相互独立,互不干扰。 再配合以太坊虚拟机,让智能合约沙盒运行。
以太坊作为智能合约操作平台,将账户划分为两类:外部账户(EOAs)和合约账户(contract account)。
一、账户基本概念
1.1 外部账户
外部账户(external owned accouts)是由我们通过私钥创建的账户。 是真实世界的金融账户的映射,拥有该账户私钥的任何人都可以控制该账户。 如同银行卡,到ATM机取款时只需要密码输入正确即可交易。 这也是人类与以太坊账本沟通的唯一媒介,因为以太坊中的交易需要签名, 而只能使用拥有私有外部账户签名。
外部账户特点总结:
- 拥有以太余额。
- 能发送交易,包括转账和执行合约代码。
- 被私钥控制。
- 没有相关的可执行代码。
1.2 合约账户
合约账户则是含有合约代码的账户。 被外部账户或者合约创建,合约在创建时被自动分配到一个账户地址, 用于存储合约代码以及合约部署或执行过程中产生的存储数据。 合约账户地址是通过SHA3哈希算法产生,而非私钥。 因无私钥,因此无人可以拿合约账户当做外部账户使用。 只能通过外部账户来驱动合约执行合约代码。
合约账户特点总结:
- 拥有以太余额。
- 有相关的可执行代码(合约代码)。
- 合约代码能够被交易或者其他合约消息调用。
- 合约代码被执行时可再调用其他合约代码。
- 合约代码被执行时可执行复杂运算,可永久地改变合约内部的数据存储。
1.3 差异对比
综上,下面表格列出两类账户差异,合约账户更优于外部账户。 但外部账户是人们和以太坊沟通的唯一媒介,和合约账户相辅相成。
比较项 | 外部账户 | 合约账户 |
---|---|---|
私钥 private Key | 有 | 无 |
余额 balance | 有 | 有 |
代码 code | 无 | 有 |
多重签名 | 无 | 有 |
控制方式 | 私钥控制 | 通过外部账户执行合约 |
上面有列出多重签名,是因为以太坊外部账户只由一个独立私钥创建,无法进行多签。 但合约具有可编程性,可编写符合多重签名的逻辑,实现一个支持多签的账户。
二、帐户创建
2.1 外部账户创建
当你想要创建一个帐户时,大多数库将生成一个随机的私钥。
私钥由 64 个十六进制字符组成,可以用密码加密保存。
例如:
fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036415f
使用椭圆曲线数字签名算法从私钥生成公钥。 通过获取公钥 Keccak-256 哈希的最后 20 个字节并校验码前面添加 0x,可以为帐户获取公共地址。
// PubkeyToAddress 公钥转地址方法
func (p *PublicKey) ToAddress() Address {
pubBytes := p.FromECDSAPub()
i := sha3.Keccak256(pubBytes[1:])[12:]
return BytesToAddress(i)
}
地址具体生成细节可参考: 以太坊地址生成
下面是使用 GETH 的 personal_newAccount 在控制台中创建一个帐户的例子
> personal.newAccount()
Passphrase: 输入账户密码
Repeat passphrase: 再次输入账户密码
"0x0dd26b4f63f9362314b708816a6aa18a04442679" 得到账户地址
> personal.newAccount(123456)
"0x5942bd5c577b569e27dd789b50b7d8824fe399ea"
geth指令详情可查看 geth文档
2.2 合约账户创建
下面是合约地址生成算法:Keccak256(rlp([sender,nonce])[12:]
// https://github.com/ethereum/go-ethereum/blob/master/crypto/crypto.go
func CreateAddress(b common.Address, nonce uint64) common.Address {
data, _ := rlp.EncodeToBytes([]interface{}{b, nonce})
return common.BytesToAddress(Keccak256(data)[12:])
}
因为合约由其他账户创建,因此将创建者地址和该交易的随机数进行哈希后截取部分生成。
特别需要注意的是,在EIP1014中提出的另一种生成合约地址的算法。 其目的是为状态通道提供便利,通过确定内容输出稳定的合约地址。 在部署合约前就可以知道确切的合约地址。下面是算法方法:
keccak256( 0xff ++ address ++ salt ++ keccak256(init_code))[12:]
。
// https://github.com/ethereum/go-ethereum/blob/master/crypto/crypto.go
// CreateAddress2 creates an ethereum address given the address bytes, initial
// contract code hash and a salt.
func CreateAddress2(b common.Address, salt [32]byte, inithash []byte) common.Address {
return common.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt[:], inithash)[12:])
}
三、账户数据结构
3.1 账户状态
账户的状态(Acccount State)描述了一个账户当前的情况。以太坊公链时时刻刻跟踪并维护着每一个账户的状态。 一个账户在初次接收或者发出交易后,都会形成初始状态。随着时间的推移,每次针对该账户的交易将不断修改其状态。
注:总结而言,每一个账户在数据结构上具有两个元素:一个公开地址,一个与该地址关联的状态。
在程序逻辑上两类账户的数据结构一致,包含四大元素:
-
nonce:已执行交易总数,用来标示该账户发出的交易数量;
-
balance:持币数量,记录用户的以太币余额;
-
storageRoot hash:存储区的哈希值,指向智能合约账户的存储数据区;
-
code hash:代码区的哈希值,指向智能合约账户存储的智能合约代码。
//github.com/ethereum/go-ethereum/core/types/account.go
type Account struct {
Nonce uint64
Balance *big.Int
Root common.Hash
CodeHash []byte
}
以太坊数据以账户为单位组织,账户数据的变更引起账户状态变化。 从而引起以太坊状态变化。
以太坊状态数据:
基于状态机模型,以太坊网络已变成一个依靠矿工维护的去中心化的大型状态机。在任意时刻,只会处于一个状态中,全世界唯一的状态。我们把这个状态机,称之为以太坊世界状态,代表着以太坊网络的全局状态。
世界状态(state)由无数的账户信息组成,每个账户均存在一个唯一的账户信息。账户信息中存储着账户余额、Nonce、合约哈希、账户状态等内容,每个账户信息通过账户地址影射。 从创世状态开始,随着将交易作为输入信息,在预设协议标准(条件)下将世界态推进到下一个新的状态中。
世界状态中存储了哪些内容:
首先,以太坊中有两种级别的状态,一个是顶级的世界状态,另一个是账户级的账户状态。账户状态中存储账户信息:
- nonce: 这个值等于由此账户发出的交易数量,或者由这个账户所创建的合约数量(当这个账户有关联代码时)。
- balance: 表示这个账户账户余额。
- storageRoot: 表示保存了账户存储内容的 MPT 树的根节点的哈希值。
- codeHash: 表示账户的 EVM 代码哈希值,当这个地址接收到一个消息调用时,这些代码会被执行; 它和其它字段不同,创建后不可更改。如果 codeHash 为空,则说明该账户是一个简单的外部账户,只存在 nonce 和 balance。
在程序逻辑上两类账户的数据结构一致:
//github.com/ethereum/go-ethereum/core/types/account.go
type Account struct {
Nonce uint64
Balance *big.Int
Root common.Hash
CodeHash []byte
}
但在数据存储上稍有不同, 因为外部账户无内部存储数据和合约代码,因此外部账户数据中 StateRootHash
和 CodeHash
是一个空默认值。 一旦属于空默认值,则不会存储对应物理数据库中。 在程序逻辑上,存在code
则为合约账户。 即 CodeHash
为空值时,账户是一个外部账户,否则是合约账户。
3.2 账户状态结构
下图显示了外部账户与合约账户的账户状态。