以太坊账户详解

文章目录

  • 一、账户基本概念
    • [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. 能发送交易,包括转账和执行合约代码。
  3. 被私钥控制。
  4. 没有相关的可执行代码。

1.2 合约账户

合约账户则是含有合约代码的账户。 被外部账户或者合约创建,合约在创建时被自动分配到一个账户地址, 用于存储合约代码以及合约部署或执行过程中产生的存储数据。 合约账户地址是通过SHA3哈希算法产生,而非私钥。 因无私钥,因此无人可以拿合约账户当做外部账户使用。 只能通过外部账户来驱动合约执行合约代码。

合约账户特点总结:

  1. 拥有以太余额。
  2. 有相关的可执行代码(合约代码)。
  3. 合约代码能够被交易或者其他合约消息调用。
  4. 合约代码被执行时可再调用其他合约代码。
  5. 合约代码被执行时可执行复杂运算,可永久地改变合约内部的数据存储。

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)描述了一个账户当前的情况。以太坊公链时时刻刻跟踪并维护着每一个账户的状态。 一个账户在初次接收或者发出交易后,都会形成初始状态。随着时间的推移,每次针对该账户的交易将不断修改其状态。

注:总结而言,每一个账户在数据结构上具有两个元素:一个公开地址,一个与该地址关联的状态。

在程序逻辑上两类账户的数据结构一致,包含四大元素:

  1. nonce:已执行交易总数,用来标示该账户发出的交易数量;

  2. balance:持币数量,记录用户的以太币余额;

  3. storageRoot hash:存储区的哈希值,指向智能合约账户的存储数据区;

  4. 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、合约哈希、账户状态等内容,每个账户信息通过账户地址影射。 从创世状态开始,随着将交易作为输入信息,在预设协议标准(条件)下将世界态推进到下一个新的状态中。

世界状态中存储了哪些内容:

首先,以太坊中有两种级别的状态,一个是顶级的世界状态,另一个是账户级的账户状态。账户状态中存储账户信息:

  1. nonce: 这个值等于由此账户发出的交易数量,或者由这个账户所创建的合约数量(当这个账户有关联代码时)。
  2. balance: 表示这个账户账户余额。
  3. storageRoot: 表示保存了账户存储内容的 MPT 树的根节点的哈希值。
  4. 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
}

但在数据存储上稍有不同, 因为外部账户无内部存储数据和合约代码,因此外部账户数据中 StateRootHashCodeHash 是一个空默认值。 一旦属于空默认值,则不会存储对应物理数据库中。 在程序逻辑上,存在code则为合约账户。 即 CodeHash 为空值时,账户是一个外部账户,否则是合约账户。

3.2 账户状态结构

下图显示了外部账户与合约账户的账户状态。

相关推荐
howard20056 天前
搭建私有链
以太坊·私有链
howard20058 天前
初试以太坊Geth客户端
以太坊·geth客户端
天涯学馆1 个月前
以太坊基础知识结构详解
web3·以太坊
Daniel_1872 个月前
区块链技术与应用-PKU 学习笔记
区块链·以太坊·比特币
花花花13 个月前
如何使用智能合约铸造 NFT —— 以 NftMarket 合约为例
去中心化·区块链·智能合约·以太坊·nft·sepolia
bighu3 个月前
区块链应用与以太坊的交互
以太坊·l2
bighu4 个月前
以太坊Rollup方案之 arbitrum(1)
区块链·以太坊·arbitrum·l2
Mindfulness code4 个月前
以太坊开发环境
区块链·以太坊
代码与野兽4 个月前
既有比特币,为什么还要有以太坊?聊聊以太坊
web3·区块链·以太坊