在 Go-Ethereum 源代码中的 account文件夹包含与以太坊账户管理相关的功能。
以太坊的钱包提供了 keyStore 模式 、usb两种钱包。
以太坊合约的 ABI 的代码也放在了 account/abi 目录。

一、accounts支持的钱包类型

在accounts中总共支持两大类共3种钱包类型。

keystore usbwallet
私钥加密存储 私钥明文存储 ledger trenzer
  1. keystore:本地文件夹
  • keystore 类型的钱包其实是一个本地文件夹目录。在这个目录下可以存放多个文件,每个文件都存储着一个私钥信息。这些文件都是json格式,其中的私钥可以是加密的,也可以是非加密的明文。但非加密的格式已经被废弃了。
  • keystore的目录路径可以在配置文件中指定,默认路径是/keystore。
  • keystore中的每一个文件有固定文件命名格式:UTC--<created_at UTC ISO8601>--。
  • keystore目录和目录内的文件是可以直接拷贝到其它电脑的keystore目录。
json格式的私钥信息

2、HD:分层确定性(Hierarchical Deterministic)钱包

这个概念的中文名称叫做“分层确定性”,这是一种 key的派生方式,它可以在只使用一个主公钥的情况下,生成任意多个子公钥,而这些子公钥都是可以被主私钥控制的。
HD的概念最早是从比特币的BIP-32提案中提出来的。每一个key都有自己的路径,即是是一个派生的 key,这一点和 keystore类型是一样的。

在accounts模块中共支持两种HD钱包:Ledger和Trezor (在usbWallet文件中)

二、目录结构

1、accounts.go

accounts.go定义了accounts模块对外导出的一些结构体和接口,包括Account结构体、Wallet接口和Backend接口。其中Account由一个以太坊地址和钱包路径组成;而各种类型的钱包需要实现Wallet和Backend接口来接入账入管理。

1) account

//帐户表示位于定义的特定位置的以太坊帐户
//通过可选的URL字段。
type Account struct {
	Address common.Address `json:"address"` //从密钥派生的以太坊帐户地址
	URL     URL            `json:"url"`     //后端中的可选资源定位器
}

2) 钱包--钱包应该是这里面最重要的一个接口了

//Wallet表示可能包含一个或多个软件或硬件钱包账户(源自同一种子)。
type Wallet interface {
	//URL检索可访问此钱包的规范路径。它是用户按上层定义多个钱包的排序顺序后端。
	URL() URL

	//状态返回文本状态以帮助用户处于钱包。
	Status() (string, error)

	//open初始化对钱包实例的访问。它不是用来解锁或解密帐户密钥,而不是简单地建立到硬件的连接钱包和/或获取衍生种子。
	//passphrase参数可能被特定钱包实例。
	//请注意,如果您打开一个钱包,您必须关闭它以释放任何分配的资源(使用硬件钱包时尤其重要)。
	Open(passphrase string) error

	//关闭释放打开钱包实例持有的任何资源。
	Close() error

	//帐户检索钱包当前识别的签名帐户列表的。
	Accounts() []Account

	//包含返回帐户是否属于此特定钱包的一部分。
	Contains(account Account) bool

	//派生尝试在处显式派生层次确定性帐户指定的派生路径。如果请求,将添加派生帐户到钱包的跟踪帐户列表。
	Derive(path DerivationPath, pin bool) (Account, error)

	//SelfDerive设置钱包尝试的基本帐户派生路径发现非零帐户并自动将其添加到跟踪的列表账户。
	//您可以通过使用nil调用selfderive来禁用自动帐户发现链状态读取器。
	SelfDerive(base DerivationPath, chain ethereum.ChainStateReader)

	//sign hash请求钱包对给定的hash进行签名。它只通过包含在中的地址查找指定的帐户,或者可以借助嵌入的URL字段中的任何位置元数据。
	SignHash(account Account, hash []byte) ([]byte, error)

	//signtx请求钱包签署给定的交易。它只通过包含在中的地址查找指定的帐户,或者可以借助嵌入的URL字段中的任何位置元数据。
	SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)

	//signhashwithpassphrase请求钱包使用提供作为额外身份验证信息的密码。它只通过包含在中的地址查找指定的帐户,或者可以借助嵌入的URL字段中的任何位置元数据。
	SignHashWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)

//signtxwithpassphrase请求钱包签署给定的交易,使用提供作为额外身份验证信息的密码。它只通过包含在中的地址查找指定的帐户,或者可以借助嵌入的URL字段中的任何位置元数据。
	SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
}

3) 后端 Backend

// Backend是一个钱包提供器。 可以包含一批账号。他们可以根据请求签署交易,这样做。
type Backend interface {
	
	// Wallets获取当前能够查找到的钱包,所生成的钱包列表将根据后端分配的内部URL按字母顺序排序。由于钱包(特别是硬件钱包)可能会打开和关闭,所以在随后的检索过程中,相同的钱包可能会出现在列表中的不同位置。
	Wallets() []Wallet


	// 订阅创建异步订阅,以便在后端检测到钱包的到达或离开时接收通知。
	Subscribe(sink chan<- WalletEvent) event.Subscription
}

Backend 接口是一个钱包 provider,它包含一个钱包列表,在检测到钱包开启或关闭时可以接收到通知,可以用来请求签名交易。其中 Wallets() 返回当前可用的钱包,按字母顺序排序,Subscribe() 创建异步订阅的方法,当钱包发生变动时通过 chan 接收消息。

4) TextHash 和 TextAndHash

func TextHash(data []byte) []byte {
	hash, _ := TextAndHash(data)
	return hash
}

func TextAndHash(data []byte) ([]byte, string) {
	msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data))
	hasher := sha3.NewLegacyKeccak256()
	hasher.Write([]byte(msg))
	return hasher.Sum(nil), msg
}

这段代码是以太坊中用于生成签名消息的辅助函数。它们用于创建一个可以安全用于计算签名的消息哈希。

TextHash 函数和 TextAndHash 函数都执行相同的操作,但 TextHash 只返回了哈希值,而 TextAndHash 返回了哈希值和生成该哈希值的消息。

这些函数创建了一个特定格式的消息,这个格式提供了消息的上下文,并且会被用于消息的签名。生成的消息格式是:

keccak256("\x19Ethereum Signed Message:\n"${message length}${message})

其中 ${message length} 是消息长度,${message} 是实际消息内容。

TextAndHash 函数返回了计算得到的哈希值和创建该哈希值的消息。这有助于在创建签名时了解签名的消息内容。

这种消息格式的创建目的在于为消息提供上下文,同时也用于防止对交易进行签名。这种处理方式是为了增加消息的安全性和可靠性,确保消息的签名能够被正确验证。

5) WalletEvent

WalletEventType 是一个整数类型,代表钱包订阅系统中可以触发的不同事件类型。它定义了以下常量:

  • WalletArrived: 当通过USB或者密钥库中的文件系统事件检测到新钱包时触发。
  • WalletOpened: 当成功打开钱包并开始后台进程(例如自动密钥派生)时触发。
  • WalletDropped: 暂时未提供具体注释或描述该事件类型的用途。

WalletEvent 结构体是由账户后端(account backend)触发的事件,当检测到钱包的到来或离开时使用。它包含以下字段:

  • Wallet: 表示到来或离开的钱包实例。
  • Kind: 表示发生在系统中的事件类型,使用 WalletEventType 中的值来描述具体事件。

type WalletEventType int

const (
	WalletArrived WalletEventType = iota
	WalletOpened
	WalletDropped
)

type WalletEvent struct {
	Wallet Wallet         
	Kind   WalletEventType
}

这段代码定义了一个钱包事件系统,用于表示钱包订阅系统中可能触发的不同事件类型和事件结构。

这种事件系统用于监控钱包的状态变化,当新钱包被检测到、钱包成功打开或者其他钱包状态变化时,系统可以触发相应的事件。这对于后台进程、自动化任务或者用户通知非常有用,能够及时响应钱包状态的变化。

2、hd.go

hd.go中定义了HD类型的钱包的路径解析等函数。

3、manager.go

manager.go中定义了Manager结构及其方法。这是accounts模块对外导出的主要的结构和方法之一。其它模块(比如cmd/geth中)通过这个结构体提供的方法对钱包进行管理。

1)常量和配置

  • managerSubBufferSize: 用于确定账户管理器在其通道中缓冲的钱包事件数量的常量,限定了通道的缓冲大小为50个事件。
  • Config 结构体: 包含全局账户管理器的设置。其中InsecureUnlockAllowed: 布尔值,表示是否允许在不安全的环境中解锁账户。

2)结构体和通道

newBackendEvent 结构体: 用于让管理器知道应该追踪给定后端以获取钱包更新。

  • backend: 要追踪的后端。
  • processed: 通道,用于通知事件发出者后端已经被整合。
type newBackendEvent struct {
	backend   Backend
	processed chan struct{} // Informs event emitter that backend has been integrated
}

Manager 结构体: 是一个整体的账户管理器。该结构体可以注册不同的后端以跟踪和管理钱包,同时还可以进行订阅以获取钱包的变更事件。这个结构体管理了一个全局的账户状态,可以订阅钱包变更事件、缓存钱包信息,并允许注册新的后端以追踪其钱包。

  • config: 指向 Config 结构体的指针,保存了全局账户管理器的配置。
  • backends: 存储已注册后端的映射,将后端类型映射到对应的后端实例切片。
  • updaters: 存储针对所有后端的钱包更新订阅。
  • updates: 用于接收后端钱包变化的订阅接收器。
  • newBackends: 用于接收要被管理器追踪的新后端。
  • wallets: 缓存了所有已注册后端中的所有钱包信息。
  • feed: 用于通知到达或离开的钱包的钱包更新通道。
  • quit: 通道,用于接收错误退出的信号。
  • term: 用于通知更新循环终止的信号。
  • lock: 读写锁,用于保护账户管理器内部状态的并发访问。
type Manager struct {
	config      *Config               
	backends    map[reflect.Type][]Backend 
	updaters    []event.Subscription       
	updates     chan WalletEvent          
	newBackends chan newBackendEvent       
	wallets     []Wallet                  
	feed event.Feed
	quit chan chan error
	term chan struct{} 
	lock sync.RWMutex
}

2) 创建Manager

func NewManager(config *Config, backends ...Backend) *Manager {
	// Retrieve the initial list of wallets from the backends and sort by URL
	var wallets []Wallet
	for _, backend := range backends {
		wallets = merge(wallets, backend.Wallets()...)
	}
	// Subscribe to wallet notifications from all backends
	updates := make(chan WalletEvent, managerSubBufferSize)

	subs := make([]event.Subscription, len(backends))
	for i, backend := range backends {
		subs[i] = backend.Subscribe(updates)
	}
	// Assemble the account manager and return
	am := &Manager{
		config:      config,
		backends:    make(map[reflect.Type][]Backend),
		updaters:    subs,
		updates:     updates,
		newBackends: make(chan newBackendEvent),
		wallets:     wallets,
		quit:        make(chan chan error),
		term:        make(chan struct{}),
	}
	for _, backend := range backends {
		kind := reflect.TypeOf(backend)
		am.backends[kind] = append(am.backends[kind], backend)
	}
	go am.update()

	return am
}

NewManager 会将所有 backends 的 wallets 收集起来,获取所有的 backends 的时间订阅,然后根据这些参数创建新的 manager。

3) update

update在 NewManager 作为一个 goroutine 被调用,一直运行,监控所有 backend 触发的更新消息,发给 feed 用来进行进一步的处理。

func (am *Manager) update() {
	// Close all subscriptions when the manager terminates
	defer func() {
		am.lock.Lock()
		for _, sub := range am.updaters {
			sub.Unsubscribe()
		}
		am.updaters = nil
		am.lock.Unlock()
	}()

	// Loop until termination
	for {
		select {
		case event := <-am.updates:
			// Wallet event arrived, update local cache
			am.lock.Lock()
			switch event.Kind {
			case WalletArrived:
				am.wallets = merge(am.wallets, event.Wallet)
			case WalletDropped:
				am.wallets = drop(am.wallets, event.Wallet)
			}
			am.lock.Unlock()

			// Notify any listeners of the event
			am.feed.Send(event)
		case event := <-am.newBackends:
			am.lock.Lock()
			// Update caches
			backend := event.backend
			am.wallets = merge(am.wallets, backend.Wallets()...)
			am.updaters = append(am.updaters, backend.Subscribe(am.updates))
			kind := reflect.TypeOf(backend)
			am.backends[kind] = append(am.backends[kind], backend)
			am.lock.Unlock()
			close(event.processed)
		case errc := <-am.quit:
			// Manager terminating, return
			errc <- nil
			// Signals event emitters the loop is not receiving values
			// to prevent them from getting stuck.
			close(am.term)
			return
		}
	}
}

4) backend

返回backend

func (am *Manager) Backends(kind reflect.Type) []Backend {
	am.lock.RLock()
	defer am.lock.RUnlock()

	return am.backends[kind]
}

5)账户管理器(Manager)的几个方法,用于操作和管理账户及其相关后端。

  • Close() error: 这个方法用于终止账户管理器内部的通知流程。它将一个错误通道传递给 quit 通道,并且等待返回的错误信号。一旦 Close() 被调用,账户管理器的内部通知流程将会被关闭。
  • Config() *Config: 这个方法用于获取账户管理器的配置信息,返回一个指向 Config 结构体的指针。它允许外部访问账户管理器的配置参数。
  • AddBackend(backend Backend): 这个方法用于启动对额外后端的追踪,以便进行钱包更新。它创建了一个空的信号通道 done,并通过 newBackends 通道发送一个 newBackendEvent 结构体,通知账户管理器要开始追踪一个新的后端。在发送通知后,方法会等待并阻塞,直到收到了 done 信号,这表示新的后端已经被成功整合到了账户管理器中。
func (am *Manager) Close() error {
	errc := make(chan error)
	am.quit <- errc
	return <-errc
}

func (am *Manager) Config() *Config {
	return am.config
}

func (am *Manager) AddBackend(backend Backend) {
	done := make(chan struct{})
	am.newBackends <- newBackendEvent{backend, done}
	<-done
}

这些方法组成了账户管理器的一部分,允许对账户管理器进行关闭、获取配置信息,并添加新的后端以进行钱包更新的跟踪。

6) subscribe

订阅消息

// Subscribe creates an async subscription to receive notifications when the
// manager detects the arrival or departure of a wallet from any of its backends.
func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription {
	return am.feed.Subscribe(sink)
}

7) wallets

一下方法属于账户管理器 (Manager),用于管理和操作账户、钱包以及钱包地址。

Wallets() 方法

  • Wallets() []Wallet: 返回所有在此账户管理器下注册的签名账户。这个方法获取了读取锁 (RLock),并在获取后立即释放锁 (RUnlock),然后调用了 walletsNoLock() 方法来获取所有已注册的钱包。
func (am *Manager) Wallets() []Wallet {
	am.lock.RLock()
	defer am.lock.RUnlock()

	return am.walletsNoLock()
}

walletsNoLock() 方法

  • walletsNoLock() []Wallet: 在不获取锁的情况下返回所有已注册的钱包。由于这个方法被 Wallets() 方法调用,它被限制在拥有读取锁的情况下调用。
func (am *Manager) walletsNoLock() []Wallet {
	cpy := make([]Wallet, len(am.wallets))
	copy(cpy, am.wallets)
	return cpy
}

Wallet() 方法

  • Wallet(url string) (Wallet, error): 根据特定 URL 检索与之相关联的钱包。这个方法先使用 parseURL() 函数解析传入的 URL,然后在已注册的钱包列表中查找匹配的钱包,找到匹配的则返回钱包实例,找不到则返回 ErrUnknownWallet 错误。
func (am *Manager) Wallet(url string) (Wallet, error) {
	am.lock.RLock()
	defer am.lock.RUnlock()

	parsed, err := parseURL(url)
	if err != nil {
		return nil, err
	}
	for _, wallet := range am.walletsNoLock() {
		if wallet.URL() == parsed {
			return wallet, nil
		}
	}
	return nil, ErrUnknownWallet
}

Accounts() 方法

  • Accounts() []common.Address: 返回账户管理器中所有钱包的所有账户地址。它获取读取锁,然后遍历所有已注册的钱包,并收集它们的账户地址,最终返回一个地址切片。
func (am *Manager) Accounts() []common.Address {
	am.lock.RLock()
	defer am.lock.RUnlock()

	addresses := make([]common.Address, 0) // return [] instead of nil if empty
	for _, wallet := range am.wallets {
		for _, account := range wallet.Accounts() {
			addresses = append(addresses, account.Address)
		}
	}
	return addresses
}

Find() 方法

  • Find(account Account) (Wallet, error): 通过指定的账户来寻找其对应的钱包。它获取读取锁,然后遍历所有已注册的钱包来查找包含指定账户的钱包。如果找到匹配的钱包,则返回该钱包实例,否则返回 ErrUnknownAccount 错误。
func (am *Manager) Find(account Account) (Wallet, error) {
	am.lock.RLock()
	defer am.lock.RUnlock()

	for _, wallet := range am.wallets {
		if wallet.Contains(account) {
			return wallet, nil
		}
	}
	return nil, ErrUnknownAccount
}

Subscribe() 方法

  • Subscribe(sink chan<- WalletEvent) event.Subscription: 创建一个异步订阅,用于接收账户管理器检测到的钱包到来或离开的通知。它返回一个事件订阅接口。
func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription {
	return am.feed.Subscribe(sink)
}

merge()drop() 方法

这两个方法是用于处理钱包切片的工具函数。merge() 函数按顺序插入新的钱包,并保持原始列表的顺序。drop() 函数从排序缓存中移除指定的钱包。这些方法主要用于在管理器中添加或移除钱包时维护钱包切片的有序性。

func merge(slice []Wallet, wallets ...Wallet) []Wallet {
	for _, wallet := range wallets {
		n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 })
		if n == len(slice) {
			slice = append(slice, wallet)
			continue
		}
		slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...)
	}
	return slice
}

func drop(slice []Wallet, wallets ...Wallet) []Wallet {
	for _, wallet := range wallets {
		n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 })
		if n == len(slice) {
			// Wallet not found, may happen during startup
			continue
		}
		slice = append(slice[:n], slice[n+1:]...)
	}
	return slice
}

4、url.go

这个文件中的代码定义了代表以太坊钱包路径的URL结构体及相关函数。与hd.go中不同的是,URL结构体中保存了钱包的类型(scheme)和钱包路径的字符串形式的表示;而hd.go中定义了HD钱包路径的类型(非字符串类型)的解析及字符串转换等方法。

5、keystore

这是一个子目录,此目录下的代码实现了keystore类型的钱包。

  • account_cache.go

此文件中的代码实现了accountCache结构体及方法。accountCache的功能是在内存中缓存keystore钱包目录下所有账号信息。无论keystore目录中的文件如何变动(新建、删除、修改),accountCache都可以在扫描目录时将变动更新到内存中。

  • file_cache.go

此文件中的代码实现了fileCache结构体及相关代码。与account_cache.go类似,file_cache.go中实现了对keystore目录下所有文件的信息的缓存。accountCache就是通过fileCache来获取文件变动的信息,进而得到账号变动信息的。

  • key.go

key.go主要定义了Key结构体及其json格式的marshal/unmarshal方式。另外这个文件中还定义了通过keyStore接口将Key写入文件中的函数。keyStore接口中定义了Key被写入文件的具体细节,在passphrase.go和plain.go中都有实现。

  • keystore.go

这个文件里的代码定义了KeyStore结构体及其方法。KeyStore结构体实现了Backend接口,是keystore类型的钱包的后端实现。同时它也实现了keystore类型钱包的大多数功能。

  • passphrase.go

passphrase.go中定义了keyStorePassphrase结构体及其方法。keyStorePassphrase结构体是对keyStore接口(在key.go文件中)的一种实现方式,它会要求调用者提供一个密码,从而使用aes加密算法加密私钥后,将加密数据写入文件中。

  • plain.go

这个文件中的代码定义了keyStorePlain结构体及其方法。keyStorePlain与keyStorePassphrase类似,也是对keyStore接口的实现。不同的是,keyStorePlain直接将密码明文存储在文件中。目前这种方式已被标记弃用且整个以太坊项目中都没有调用这个文件里的函数的地方,确实谁也不想将自己的私钥明文存在本地磁盘上。

  • wallet.go

wallet.go中定义了keystoreWallet结构体及其方法。keystoreWallet是keystore类型的钱包的实现,但其功能基本都是调用KeyStore对象实现的。

  • watch.go

watch.go中定义了watcher结构体及其方法。watcher用来监控keystore目录下的文件,如果文件发生变化,则立即调用account_cache.go中的代码重新扫描账户信息。但watcher只在某些系统下有效,这是文件的build注释:// +build darwin,!ios freebsd linux,!arm64 netbsd solaris

6、usbwallet

这是一个子目录,此目录下的代码实现了对通过usb接入的硬件钱包的访问。

  • hub.go

hub.go中定义了Hub结构体及其方法。Hub结构体实现了Backend接口,是usbwallet类型的钱包的后端实现。

  • ledger.go

ledger.go中定义了ledgerDriver结构体及其方法。ledgerDriver结构体是driver接口的实现,它实现了与ledger类型的硬件钱包通信协议和代码。

  • trezor.go

trezor.go中定义了trezorDriver结构体及其方法。与ledgerDriver类似,trezorDriver结构体也是driver接口的实现,它实现了与trezor类型的硬件钱包的通信协议和代码。

  • wallet.go

wallet.go中定义了wallet结构体。wallet结构体实现了Wallet接口,是硬件钱包的具体实现。但它内部其实主要调用硬件钱包的driver实现相关功能。

7、scwallet

这个文件夹是关于不同account之间的互相安全通信(secure wallet),通过定义会话秘钥、二级秘钥来确保通话双方的信息真实、不被篡改、利用。 尤其是转账信息更不能被利用、被他人打开、和被篡改。

8、abi

ABI是Application Binary Interface的缩写,字面意思 应用二进制接口,可以通俗的理解为合约的接口说明。当合约被编译后,那么它的abi也就确定了。abi主要是处理智能合约与账户的交互。