概述
@metamask/keyring-controller
是 MetaMask 生态系统的核心组件,负责管理用户身份和密钥,处理各种签名操作。它是连接用户私钥和区块链交互的桥梁,确保私钥的安全存储和正确使用。
源码位置 : github.com/MetaMask/co...
主要功能
- 密钥管理:安全存储和管理各种类型的密钥
- 身份管理:管理用户账户和地址
- 签名操作:处理消息、交易和用户操作的签名
- 加密存储:使用强加密保护敏感数据
- 并发控制:确保操作的安全性和一致性
总体架构图
%% KeyringController 架构图
graph TD
%% ========== 核心组件 ==========
A[KeyringController] --> B[Vault]
A --> C[Keyring 管理器]
A --> D[加密器]
A --> E[消息系统]
%% ========== Vault 结构 ==========
B --> B1[加密状态存储]
B1 -->|AES-256-GCM| B2["加密数据 (JSON)"]
B2 --> B3["{
keyrings: [...],
options: {...},
salt: '...'
}"]
%% ========== Keyring 类型 ==========
C --> C1[HD Keyring]
C --> C2[Simple Keyring]
C --> C3[Hardware Keyring]
C --> C4[Snap Keyring]
C1 -->|BIP-32/BIP-44| C11["助记词 → 派生路径"]
C2 -->|单私钥| C21["0x私钥直接存储"]
C3 -->|USB/HID| C31["Ledger/Trezor"]
%% ========== 加密流程 ==========
D --> D1[PBKDF2]
D --> D2[AES-GCM]
D --> D3[盐值管理]
%% ========== 外部交互 ==========
E --> E1[UI 界面]
E --> E2[其他Controller]
E --> E3[DApp 请求]
%% ========== 安全层 ==========
F[安全机制] --> F1[双互斥锁]
F --> F2[原子操作]
F --> F3[内存清理]
F --> F4[超时锁定]
A --> F
核心概念
1. 密钥环 (Keyring)
密钥环是管理一组相关密钥的容器,每个密钥环负责特定类型的密钥管理:
typescript
interface KeyringObject {
accounts: string[]; // 关联的账户地址
type: string; // 密钥环类型
metadata: KeyringMetadata; // 元数据信息
}
解析:
accounts
存储该密钥环管理的所有区块链地址(如以太坊0x地址)type
标识密钥环类型(如"HD Key Tree"、"Simple Key Pair"等),决定密钥生成方式metadata
包含密钥环的扩展信息(如硬件钱包的设备ID、HD路径等)
2. Vault (保险库)
Vault 是加密存储所有密钥环数据的容器:
typescript
interface KeyringControllerState {
vault?: string; // 加密的密钥环数据
isUnlocked: boolean; // 是否已解锁
keyrings: KeyringObject[]; // 管理的密钥环列表
encryptionKey?: string; // 加密密钥(可选)
encryptionSalt?: string; // 加密盐值
}
解析:
vault
是加密后的JSON字符串,包含所有密钥环的敏感数据(加密算法通常为AES-256-GCM)isUnlocked
是核心安全标志,为false时禁止所有密钥操作encryptionKey
只在内存中存在,由用户密码通过PBKDF2派生得到encryptionSalt
确保相同密码每次派生的密钥不同
3. 加密器 (Encryptor)
负责数据的加密和解密操作:
typescript
interface GenericEncryptor {
encrypt(password: string, object: Json): Promise<string>;
decrypt(password: string, encryptedString: string): Promise<unknown>;
isVaultUpdated?(vault: string, params?: any): boolean;
}
解析:
-
encrypt
方法执行流程:- 使用PBKDF2从密码派生密钥(迭代次数>10,000)
- 生成随机IV(初始化向量)
- 用AES-GCM加密数据
- 返回IV+密文+认证标签的Base64组合
-
decrypt
会验证认证标签确保数据完整性 -
isVaultUpdated
用于检测加密参数变更(如迭代次数升级)
密钥环类型
1. HD 密钥环 (HD Key Tree)
基于 BIP-32/BIP-44 标准的层级确定性钱包:
typescript
async addNewKeyring(type: KeyringTypes | string, opts?: unknown): Promise<KeyringMetadata> {
this.#assertIsUnlocked()
const metadata = await this.#persistOrRollback(async () => {
const keyring = await this.#createKeyring(type, opts)
this.#keyrings.push({ keyring, metadata: getDefaultKeyringMetadata() })
return this.#getKeyringMetadata(keyring)
})
return metadata
}
解析:
- 底层使用
bip32
/bip39
库实现 - 首次创建时会生成24个助记词(BIP-39)
- 每个账户对应不同的派生路径(如第一个账户是
m/44'/60'/0'/0/0
) - 所有密钥由根种子派生,无需单独备份私钥
特点:
- 从单个种子生成无限数量的密钥对
- 支持助记词备份和恢复
- 符合 BIP-39 标准
2. 简单密钥环 (Simple Key Pair)
管理单个私钥的密钥环:
typescript
// 导入私钥
async importAccountWithStrategy(strategy: 'privateKey'|'json', args: any[]): Promise<string> {
this.#assertIsUnlocked()
// 原子变更:导入失败不污染现有 keyrings
return this.#persistOrRollback(async () => {
let privateKey: Hex
if (strategy === 'privateKey') {
// 严格校验私钥格式和长度:避免垃圾输入污染 vault
const [k] = args
if (!k) throw new Error('Cannot import an empty key.')
const prefixed = add0x(k)
const bytes = hexToBytes(prefixed)
if (!isValidPrivate(bytes) || getBinarySize(prefixed) !== 66) throw new Error('Invalid')
privateKey = remove0x(prefixed)
} else {
// JSON 路径:依次兼容 EtherWallet/V3;解析私钥
const [input, password] = args
const wallet = tryEtherWalletThenV3(input, password)
privateKey = bytesToHex(wallet.getPrivateKey())
}
// 以 Simple Key Pair 的独立 keyring 注入(与 HD 隔离)
const simple = (await this.#newKeyring(KeyringTypes.simple, [privateKey])) as EthKeyring
const accounts = await simple.getAccounts()
return accounts[0] // 返回导入账户地址
})
}
解析:
- 私钥直接存储在加密vault中
- 适合导入MetaMask外部生成的密钥
- 每个密钥环仅管理单个私钥
- 与HD密钥环不同,不支持助记词恢复
特点:
- 适合导入现有私钥
- 不支持助记词
- 每个密钥环管理一个私钥
基本操作
1. 初始化密钥控制器
typescript
import { KeyringController } from '@metamask/keyring-controller';
const keyringController = new KeyringController({
messenger: restrictedMessenger,
state: {
vault: encryptedVaultData, // 可选的初始加密数据
},
cacheEncryptionKey: true, // 可选:缓存加密密钥以提高性能
});
解析:
messenger
使用权限系统控制跨组件通信state
支持服务端持久化存储(如IndexedDB)cacheEncryptionKey
为true时,解锁后密钥保留在内存中
2. 创建新钱包
typescript
// 创建新的 HD 钱包
async createNewVaultAndKeychain(password: string): Promise<void> {
// 原子变更:仅当没有任何账户时创建全新 vault
return this.#persistOrRollback(async () => {
const existing = await this.#getAccountsFromKeyrings()
if (!existing.length) {
await this.#createNewVaultWithKeyring(password, { type: KeyringTypes.hd })
}
})
}
// 从助记词恢复钱包
async createNewVaultAndRestore(password: string, seed: Uint8Array): Promise<void> {
// 用指定助记词创建全新 vault,并默认派生 1 个账号
return this.#persistOrRollback(async () => {
await this.#createNewVaultWithKeyring(password, {
type: KeyringTypes.hd,
opts: { mnemonic: seed, numberOfAccounts: 1 },
})
})
}
// 内部:创建并解锁新 vault + 第一个账户
async #createNewVaultWithKeyring(password: string, keyring: { type: string; opts?: unknown }) {
this.#assertControllerMutexIsLocked() // 必须在互斥环境下修改关键状态
if (typeof password !== 'string') throw new TypeError('WrongPasswordType')
// 清理缓存的导出密钥,避免旧态污染新 vault
this.update(s => { delete s.encryptionKey; delete s.encryptionSalt })
this.#password = password
await this.#clearKeyrings() // 清空内存中的 keyrings
await this.#createKeyringWithFirstAccount(keyring.type, keyring.opts) // 创建首账号
this.#setUnlocked() // 仅修改内存解锁态
}
解析:
-
createNewVaultAndKeychain
内部流程:- 调用
bip39.generateMnemonic()
- 用密码加密助记词
- 初始化HD密钥环
- 调用
-
恢复时会验证助记词有效性(通过
bip39.validateMnemonic
)
3. 解锁钱包
typescript
async submitPassword(password: string): Promise<void> {
// 解锁路径:使用用户密码解密 vault + 反序列化 keyrings
const { newMetadata } = await this.#withRollback(async () => {
const res = await this.#unlockKeyrings(password) // 可能返回新 metadata(老数据升级时)
this.#setUnlocked()
return res
})
try {
// 若有新 metadata 或加密参数升级,解锁后写回升级后的 vault
if (newMetadata || this.#isNewEncryptionAvailable()) await this.#updateVault()
} catch {}
}
async submitEncryptionKey(encryptionKey: string, encryptionSalt?: string): Promise<void> {
// 解锁路径:使用缓存导出密钥(无须密码)解密 vault
const { newMetadata } = await this.#withRollback(async () => {
const res = await this.#unlockKeyrings(undefined, encryptionKey, encryptionSalt)
this.#setUnlocked()
return res
})
try { if (newMetadata) await this.#updateVault() } catch {}
}
4. 账户管理
typescript
// 获取所有账户
async getAccounts(): Promise<string[]> {
this.#assertIsUnlocked()
// 读取展示层 keyrings 合并地址,不触碰敏感数据
return this.state.keyrings.reduce<string[]>((acc, kr) => acc.concat(kr.accounts), [])
}
// 移除账户
async removeAccount(address: string): Promise<void> {
this.#assertIsUnlocked()
// 原子变更:禁止删除"主密钥环"中的最后一个账号
await this.#persistOrRollback(async () => {
const keyring = await this.getKeyringForAccount(address)
const idx = this.state.keyrings.findIndex(kr => kr.accounts.includes(address))
const isPrimary = idx === 0
const onlyOne = (await keyring.getAccounts()).length === 1
if (isPrimary && onlyOne) throw new Error('LastAccountInPrimaryKeyring')
if (!keyring.removeAccount) throw new Error('UnsupportedRemoveAccount')
keyring.removeAccount(address as Hex)
if (onlyOne) await this.#removeEmptyKeyrings() // 空 keyring 清理
})
// 通知订阅者(UI 等)
this.messagingSystem.publish('KeyringController:accountRemoved', address)
}
5. 导出私钥/助记词/加密密钥
typescript
async exportAccount(password: string, address: string): Promise<string> {
await this.verifyPassword(password) // 强校验,避免被劫持导出
const keyring = await this.getKeyringForAccount(address)
if (!keyring.exportAccount) throw new Error('UnsupportedExportAccount')
return keyring.exportAccount(ethNormalize(address) as Hex)
}
async exportSeedPhrase(password: string, keyringId?: string): Promise<Uint8Array> {
this.#assertIsUnlocked()
await this.verifyPassword(password) // 保护助记词导出
const kr = this.#getKeyringByIdOrDefault(keyringId)
if (!kr) throw new Error('Keyring not found')
if (kr.type !== KeyringTypes.hd) throw new Error('UnsupportedVerifySeedPhrase')
assertHasUint8ArrayMnemonic(kr)
return kr.mnemonic
}
async exportEncryptionKey(): Promise<string> {
this.#assertIsUnlocked()
// 仅在 cacheEncryptionKey=true 时可用;避免空态返回
return this.#withControllerLock(async () => {
const { encryptionKey } = this.state
if (!encryptionKey) throw new Error('EncryptionKeyNotSet')
return encryptionKey
})
}
高级功能
1. 消息签名
typescript
// 签名消息
async signMessage({ from, data }: { from: string; data: string }): Promise<string> {
this.#assertIsUnlocked()
if (!data) throw new Error("Can't sign an empty message")
const keyring = await this.getKeyringForAccount(ethNormalize(from) as Hex)
if (!keyring.signMessage) throw new Error('UnsupportedSignMessage')
return keyring.signMessage(ethNormalize(from) as Hex, data) // ECDSA/secp256k1
}
// 签名个人消息
async signPersonalMessage({ from, data }: { from: string; data: string }): Promise<string> {
this.#assertIsUnlocked()
const keyring = await this.getKeyringForAccount(ethNormalize(from) as Hex)
if (!keyring.signPersonalMessage) throw new Error('UnsupportedSignPersonalMessage')
// 统一十六进制格式,兼容 EVM
return keyring.signPersonalMessage(ethNormalize(from) as Hex, normalize(data) as Hex)
}
// 签名EIP-712消息
async signTypedMessage(messageParams: TypedMessageParams, version: 'V1'|'V3'|'V4'): Promise<string> {
this.#assertIsUnlocked()
if (!['V1','V3','V4'].includes(version)) throw new Error(`Unexpected version`)
const from = ethNormalize(messageParams.from) as Hex
const keyring = await this.getKeyringForAccount(from)
if (!keyring.signTypedData) throw new Error('UnsupportedSignTypedMessage')
// V3/V4 需要结构化数据;字符串时先 JSON.parse
const data = version !== 'V1' && typeof messageParams.data === 'string'
? JSON.parse(messageParams.data) : messageParams.data
return keyring.signTypedData(from, data, { version }) // EIP-712 规范
}
2. 交易签名
typescript
async signTransaction(tx: TypedTransaction, from: string, opts?: Record<string, unknown>) {
this.#assertIsUnlocked()
const address = ethNormalize(from) as Hex
const keyring = await this.getKeyringForAccount(address)
if (!keyring.signTransaction) throw new Error('UnsupportedSignTransaction')
// 支持 EIP-2718(EIP-1559/2930/Legacy),由 keyring 完成 r,s,v
return keyring.signTransaction(address, tx, opts)
}
解析:
-
支持所有EIP-2718交易类型(Legacy/EIP-1559/EIP-2930)
-
签名流程:
- 根据fromAddress找到对应密钥环
- 序列化交易数据
- 用私钥生成ECDSA签名(secp256k1曲线)
- 返回RLP编码的签名交易
3. 用户操作签名 (EIP-4337)
typescript
// 准备用户操作
async prepareUserOperation(from: string, txs: EthBaseTransaction[], ctx: KeyringExecutionContext) {
this.#assertIsUnlocked()
const addr = ethNormalize(from) as Hex
const keyring = await this.getKeyringForAccount(addr)
if (!keyring.prepareUserOperation) throw new Error('UnsupportedPrepareUserOperation')
// 生成 pseudo-UserOperation(打包多交易),供 bundler/PM 使用
return keyring.prepareUserOperation(addr, txs, ctx)
}
// 修补用户操作
async patchUserOperation(from: string, userOp: EthUserOperation, ctx: KeyringExecutionContext) {
this.#assertIsUnlocked()
const addr = ethNormalize(from) as Hex
const keyring = await this.getKeyringForAccount(addr)
if (!keyring.patchUserOperation) throw new Error('UnsupportedPatchUserOperation')
// 例如增补 paymasterAndData
return keyring.patchUserOperation(addr, userOp, ctx)
}
// 签名用户操作
async signUserOperation(from: string, userOp: EthUserOperation, ctx: KeyringExecutionContext) {
this.#assertIsUnlocked()
const addr = ethNormalize(from) as Hex
const keyring = await this.getKeyringForAccount(addr)
if (!keyring.signUserOperation) throw new Error('UnsupportedSignUserOperation')
// 计算 userOpHash 并签名,兼容 ERC-1271 验证
return keyring.signUserOperation(addr, userOp, ctx)
}
解析:
- 专为EIP-4337账户抽象设计
- 签名前会计算
userOpHash
(包含所有字段的Keccak哈希) - 最终签名兼容ERC-1271验证标准
4. 密码管理
typescript
async verifyPassword(password: string) {
// 仅验证当前 vault 能否被解密;不修改任何状态
if (!this.state.vault) throw new Error('VaultError')
await this.#encryptor.decrypt(password, this.state.vault)
}
async changePassword(password: string) {
this.#assertIsUnlocked()
if (this.#password === password) return // 幂等
// 原子变更:仅更新内存密码和缓存密钥;写回由 #persistOrRollback 触发
await this.#persistOrRollback(async () => {
assertIsValidPassword(password)
this.#password = password
if (this.#cacheEncryptionKey) {
this.update(s => { delete s.encryptionKey; delete s.encryptionSalt }) // 强制下次重加密
}
})
}
async setLocked() {
this.#assertIsUnlocked()
// 原子变更:清理内存中的敏感对象并发布锁定事件
return this.#withRollback(async () => {
this.#unsubscribeFromQRKeyringsEvents()
this.#password = undefined
await this.#clearKeyrings() // 调用 destroy 清理事件/桥接 iframe 等
this.update(s => {
s.isUnlocked = false; s.keyrings = []
delete s.encryptionKey; delete s.encryptionSalt
})
this.messagingSystem.publish('KeyringController:lock')
})
}
安全机制
1. 并发控制
密钥控制器使用双重互斥锁确保操作安全:
typescript
// 控制器操作锁 - 保护所有状态变更操作
readonly #controllerOperationMutex = new Mutex();
// Vault 操作锁 - 保护加密存储操作
readonly #vaultOperationMutex = new Mutex();
2. 原子操作
所有状态变更操作都是原子的,支持回滚:
typescript
async #persistOrRollback(callback) {
// 变更前后做 session 快照(keyrings+password);有变更才写回 vault
return this.#withRollback(async ({ releaseLock }) => {
const oldState = JSON.stringify(await this.#getSessionState())
const result = await callback({ releaseLock })
const newState = JSON.stringify(await this.#getSessionState())
if (!isEqual(oldState, newState)) await this.#updateVault() // 统一持久化出口
return result
})
}
async #withRollback(callback) {
// 控制器级互斥 + 异常回滚(恢复 keyrings/password 快照)
return this.#withControllerLock(async ({ releaseLock }) => {
const snapshot = await this.#getSerializedKeyrings()
const savedPwd = this.#password
try {
return await callback({ releaseLock })
} catch (e) {
this.#password = savedPwd
await this.#restoreSerializedKeyrings(snapshot)
throw e
}
})
}
3. 状态验证
typescript
// 验证解锁状态
#assertIsUnlocked() {
if (!this.state.isUnlocked) {
throw new Error('Controller is locked');
}
}
// 验证互斥锁状态
#assertControllerMutexIsLocked() {
if (!this.#controllerOperationMutex.isLocked()) {
throw new Error('Controller lock required');
}
}
4. 重复账户检查
typescript
// 确保没有重复账户
async #assertNoDuplicateAccounts(additionalKeyrings = []) {
const accounts = await this.#getAccountsFromKeyrings(additionalKeyrings);
if (new Set(accounts).size !== accounts.length) {
throw new Error('Duplicate accounts found');
}
}
实际应用示例
1. 完整的钱包初始化流程
typescript
class WalletManager {
private keyringController: KeyringController;
async initializeWallet(password: string, seedPhrase?: Uint8Array) {
try {
if (seedPhrase) {
// 从助记词恢复
await this.keyringController.createNewVaultAndRestore(password, seedPhrase);
} else {
// 创建新钱包
await this.keyringController.createNewVaultAndKeychain(password);
}
// 解锁钱包
await this.keyringController.submitPassword(password);
// 获取账户
const accounts = await this.keyringController.getAccounts();
return {
success: true,
accounts,
isNewWallet: !seedPhrase
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
}
2. 多签名钱包集成
typescript
class MultiSigWallet {
async signTransaction(transaction: TypedTransaction, signers: string[]) {
const signatures = [];
for (const signer of signers) {
const signature = await this.keyringController.signTransaction(
transaction,
signer
);
signatures.push(signature);
}
return this.combineSignatures(transaction, signatures);
}
}
3. 硬件钱包集成
typescript
class HardwareWalletManager {
async connectLedger() {
// 添加 Ledger 密钥环
const ledgerKeyring = await this.keyringController.addNewKeyring(
KeyringTypes.ledger
);
// 连接设备并获取账户
const accounts = await this.keyringController.withKeyring(
{ type: KeyringTypes.ledger },
async ({ keyring }) => {
return await keyring.getAccounts();
}
);
return accounts;
}
}
4. 消息签名服务
typescript
class MessageSigningService {
async signEIP712Message(
from: string,
domain: any,
types: any,
message: any
) {
const typedData = {
types,
primaryType: 'Message',
domain,
message
};
return await this.keyringController.signTypedMessage(
{
from,
data: typedData
},
SignTypedDataVersion.V4
);
}
}
最佳实践
1. 错误处理
typescript
async function safeKeyringOperation(operation: () => Promise<any>) {
try {
return await operation();
} catch (error) {
if (error.message.includes('Controller is locked')) {
// 处理锁定状态
throw new Error('Wallet is locked. Please unlock first.');
} else if (error.message.includes('Wrong password')) {
// 处理密码错误
throw new Error('Incorrect password provided.');
} else {
// 处理其他错误
console.error('Keyring operation failed:', error);
throw error;
}
}
}
2. 状态管理
typescript
class WalletStateManager {
private keyringController: KeyringController;
async getWalletState() {
const state = this.keyringController.state;
const accounts = await this.keyringController.getAccounts();
return {
isUnlocked: state.isUnlocked,
accounts,
keyringCount: state.keyrings.length,
hasVault: !!state.vault
};
}
async waitForUnlock(): Promise<void> {
return new Promise((resolve) => {
const checkUnlock = () => {
if (this.keyringController.isUnlocked()) {
resolve();
} else {
setTimeout(checkUnlock, 100);
}
};
checkUnlock();
});
}
}
3. 性能优化
typescript
class OptimizedKeyringController {
private encryptionKeyCache: string | null = null;
async unlockWithCachedKey(password: string) {
// 首次解锁时缓存加密密钥
if (!this.encryptionKeyCache) {
await this.keyringController.submitPassword(password);
this.encryptionKeyCache = await this.keyringController.exportEncryptionKey();
} else {
// 后续解锁使用缓存的密钥
await this.keyringController.submitEncryptionKey(this.encryptionKeyCache);
}
}
}
4. 安全措施
- 密码强度:确保用户使用强密码
- 定期备份:定期导出助记词和加密密钥
- 环境隔离:在生产环境中隔离敏感操作
- 审计日志:记录所有关键操作
- 超时机制:实现自动锁定机制
typescript
class SecurityManager {
private autoLockTimer: NodeJS.Timeout | null = null;
private readonly AUTO_LOCK_DELAY = 5 * 60 * 1000; // 5分钟
startAutoLockTimer() {
this.autoLockTimer = setTimeout(async () => {
await this.keyringController.setLocked();
}, this.AUTO_LOCK_DELAY);
}
resetAutoLockTimer() {
if (this.autoLockTimer) {
clearTimeout(this.autoLockTimer);
}
this.startAutoLockTimer();
}
}
学习交流请添加vx: gh313061
下期预告:构建批准控制器(ApprovalController)