Alert Source Discuss
🚧 Stagnant Standards Track: Interface

EIP-6051: 私钥封装

定义私钥封装的规范。

Authors Base Labs (@Base-Labs), Weiji Guo (@weiji-cryptonatty)
Created 2022-11-21
Discussion Link https://ethereum-magicians.org/t/private-key-encapsulation-to-move-around-securely-without-entering-seed/11604

摘要

本 EIP 提出了一种封装私钥的机制,以便将其安全地重新定位到另一个应用程序,而无需提供种子。本 EIP 结合了 ECIES (椭圆曲线集成加密方案) 和可选的签名验证,在各种选择下确保私钥被封装给已知或受信任的方。

动机

在各种情况下,我们可能希望从一个更安全但不那么方便的钱包中导出多个私钥中的一个,该钱包由种子或密码控制。

  1. 我们可能会为消息传递目的专门使用多个私钥中的一个,并且该私钥可能以不太安全的方式进行管理;
  2. 我们可能希望从硬件钱包中导出多个私钥中的一个,并使用 MPC 技术对其进行拆分,以便第三方服务可以帮助我们识别潜在的欺诈或已知的恶意地址,强制执行 2FA 等,同时我们可以从具有更好用户体验的移动设备发起交易,而无需携带硬件钱包。

在这两种情况下,不提供控制整个钱包的种子更安全,因为种子可能包含多个链中的许多地址。

本 EIP 旨在启用此类用例。

规范

发送者和接收者

我们在此定义:

  • 发送者是指持有要封装的私钥的一方;发送者应用程序是指所述发送者用于发送封装私钥的客户端应用程序。

  • 接收者是指接受封装的私钥,解包,然后使用它的一方;接收者应用程序是指接收者用于接收封装的私钥的客户端应用程序。

核心算法

基本思想是用 ECIES 封装私钥。为了确保封装私钥的临时公钥确实是由受信任方生成的,并且没有被篡改,我们还在本标准中提供了一个选项来签署该临时公钥。

应该有一个强制性的 version 参数。这允许根据安全考虑或偏好采用各种密钥封装机制。该列表应简短,以最大限度地减少不同供应商之间的兼容性问题。

除了 version 参数外,还涉及以下密钥和函数:

  1. 发送者的私钥 sk,它将被封装到接收者,以及相应的地址 account
  2. 临时的接收者密钥对 (r, R),使得 R = [r]GG 表示椭圆曲线的基点,[r]G 表示标量乘法。可选地,R 可以被签名,然后提供 signerPubKeysignature 供发送者验证 R 是否可信。
  3. 临时的发送者密钥对 (s, S),使得 S = [s]G
  4. 根据 ECDH 的共享密钥 ss := [s]R = [r]S。请注意,对于 secp256k1,此 EIP 遵循 RFC5903 并使用紧凑表示,这意味着 使用 x 坐标作为共享密钥。对于 Curve25519,此 EIP 遵循 RFC7748。
  5. 带外数据 oob,可选。这可以是用户输入的数字或字母数字字符串。
  6. derivedKey := HKDF(hash=SHA256, ikm=ss, info=oob, salt, length)。HKDF 在 RFC5869 中定义。length 应由 skeyIV 要求确定,使得对称密钥 skey = derivedKey[0:keySize],并且 IV = derivedKey[keySize:length]keySize 表示底层对称算法的密钥大小,例如,AES-128 为 16 (字节),Chacha20 为 32 (字节)。有关 salt 的使用,请参阅安全考虑因素
  7. cipher := authenticated_encryption(symAlg, skey, IV, data=sk)。对称加密算法 symAlg 和认证方案由 version 参数决定。不使用额外的认证数据 aad

一个简化的例子,不包含签名和验证的流程是:

  1. 接收者应用程序生成 (r, R)
  2. 用户将 R 输入到发送者应用程序,以及一个六位数的代码“123456”作为 oob
  3. 发送者应用程序生成 (s, S),并计算 cipher,然后返回 S || cipher
  4. 接收者应用程序扫描读取 Scipher。用户将“123456”作为 oob 输入到接收者应用程序
  5. 接收者应用程序解密 cipher 以获得 sk
  6. 接收者应用程序导出与 sk 对应的地址,以便用户可以确认正确性。

通过签名和验证,singerPubKeyR 的签名将附加到 RsignerPubKey 本身可能已被 trustedPubKey 签名,并且该签名将附加到 signerPubKey。请注意,签名应用于字节数组数据,而不是其字符串表示形式,这可能会导致混淆和互操作性问题(例如十六进制或 base64,小写 v.s. 大写等)。有关进一步的说明和示例,请参见请求测试用例

请求

数据和消息的编码

  • 原始字节以十六进制编码,并以 ‘0x’ 为前缀。
  • 除非另有说明,否则所有参数和返回值都是十六进制编码的字节。
  • cipher 被编码成一个字节缓冲区:[IV || encrypted_sk || tag]
  • 如果适用,RSsignerPubKeytrustedPubKey 会被压缩。
  • RsignerPubKey 之后可以跟一个签名:[pub || sig]。请注意,对于 secp256k1 曲线,签名只有 64 个字节,没有在典型的 Ethereum 签名中发现的 v 指示符。

R1. 请求接收者生成临时密钥对

request({
	method: 'eth_generateEphemeralKeyPair',
	params: [version, signerPubKey],
})
// 预期返回值: R

signerPubKey 是可选的。如果提供,则假定该实现具有相应的私钥,并且该实现必须对临时公钥进行签名(以要返回的形式)。签名算法由 version 参数的曲线部分确定,即,secp256k1 使用 ECDSA,Curve25519 使用 Ed25519。在这种情况下,应该是 发送者 信任 signerPubKey,无论如何维护这种信任。否则,下一个请求将被 发送者应用程序拒绝。另请参阅安全考虑因素

然后,该实现必须使用密码学安全的随机数生成器 (CSRNG) 生成随机私钥 r,并导出临时公钥 R = [r]G。该实现应根据具体情况以安全的方式保存生成的密钥对 (r, R),并且应仅在有限的时间内保存它,但具体时间留给各个实现。如果给定相应的公钥 R,则该实现应能够在上述时间内检索 r

返回值是 R,如果适用则压缩。如果提供了 signerPubKey,则将 signature 附加到 R,并进行十六进制编码。

或者,可以单独计算 signature,然后将其附加到返回的数据中。

R2. 请求发送者封装私钥

request({
	method: 'eth_encapsulatePrivateKey',
	params: [
		version,
		recipient, // 公钥,可能后跟其签名,请参见 signerPubKey
		signerPubKey,
		oob,
		salt,
		account
	],
})
// 预期返回值: S || cipher

recipient 是调用生成临时密钥对的返回值,可以选择附加 signature,无论是作为返回还是单独附加。

oobsalt 只是字节数组。

account 用于标识要封装的私钥。对于 Ethereum,它是一个地址。另请参阅 数据和消息的编码

如果提供了 signerPubKeyrecipient 包含 signature 数据,则该实现必须执行签名验证。缺少数据或格式不正确必须导致调用失败或导致空返回和可选的错误日志。

signerPubKey 可能已被另一个 发送者应用程序 信任的密钥对 (trusted, trustedPubKey) 进一步签名。在这种情况下,signerPubKey 附加有相应的签名数据,该签名数据应针对 trustedPubKey 进行验证。有关更多说明,请参见测试用例

然后,该实现应继续检索与 account 对应的私钥 sk,并按照核心算法对其进行加密。

返回数据是一个字节数组,该字节数组首先包含 发送者 的临时公钥 S(如果适用则压缩),然后包含 cipher,包括任何身份验证标签,即 S || cipher

R3. 请求接收者解包并接收私钥

request({
	method: 'eth_intakePrivateKey',
	params: [
		version,
		recipientPublicKey, // 这次没有签名
		oob,
		salt,
		data
	],
})
// 预期返回值: account

这次 recipientPublicKey 只是先前在接收者端生成的临时公钥 R,仅用于实现检索相应的私钥 rdata 是调用封装私钥的返回值,即 S || cipher

成功解密封装的私钥 sk 后,该实现可以根据指定的目的进一步处理它。应遵循一些通用安全指南,例如,不要记录该值,在使用后安全地擦除它,依此类推。

返回值是 sk 对应的 Ethereum 地址,如果出现任何错误,则为空。

选项和参数

可用的椭圆曲线是:

  • secp256k1 (强制性)
  • Curve25519

可用的身份验证加密方案是:

  • AES-128-GCM (强制性)
  • AES-256-GCM
  • Chacha20-Poly1305

版本字符串只是椭圆曲线和 AE 方案的串联,例如,secp256k1-AES-128-GCM。以上列表允许六种不同的具体方案的组合。鼓励实现将与曲线相关的逻辑与身份验证加密方案分开实现,以避免重复并提高互操作性。

每条曲线的签名算法是:

  • secp256k1 –> ECDSA
  • Curve25519 –> Ed25519

原理

EIP-6051EIP-5630 之间的关键区别在于,由于密钥封装的目的是安全地传输私钥,因此密钥接收者的公钥应该是临时的,并且主要只使用一次。虽然在 EIP-5630 设置中,消息接收者的公钥应保持稳定一段时间,以便消息发送者可以加密消息而无需每次都进行密钥发现。

这种差异对安全性有影响,包括完全前向保密。我们的目标是通过每次在双方生成临时密钥对来实现完全前向保密:

1) 首先 接收者 应该生成一个临时密钥对,安全地保留私钥,并导出公钥; 2) 然后 发送者 可以使用另一个临时密钥对以 ECIES 安全地包装私钥,然后安全地销毁临时密钥; 3) 最后 接收者 可以解包私钥,然后安全地销毁其临时密钥对。经过这些步骤后,恶意第三方拦截的传输中的密文将不再可解密。

向后兼容性

此新提案没有向后兼容性问题。

互操作性

为了最大限度地减少应用程序(包括硬件钱包)之间潜在的兼容性问题,此 EIP 要求必须支持版本 secp256k1-AES-128-GCM。

该版本可以由用户决定,也可以由双方协商。当没有用户输入或协商时,假定为 secp256k1-AES-128-GCM。

预计实现会单独涵盖曲线支持和加密支持,也就是说,所有可以从受支持的曲线和受支持的加密方案派生的版本都应起作用。

RsignerPubKey 的签名应用于字节数组值,而不是编码的字符串。

UX 推荐

salt 和/或 oob 数据:两者都是 HKDF 函数的输入(oob 作为“信息”参数)。为了获得更好的用户体验,我们建议仅要求用户提供其中之一,但这取决于实现。

假定 接收者应用程序 足够强大。发送者应用程序 可能具有非常有限的计算能力和用户交互功能。

测试用例

为了进行审核,生成测试向量的程序是开源的,并在相应的讨论线程中提供。

数据固定

在整个测试用例中,我们固定了以下数据的值:

  • sk,要封装的私钥,固定为:0xf8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315。对应的地址为 0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9,称为 account。请注意,这些值来自 Andreas M. Antonopoulos 和 Gavin Wood 的书 Mastering Ethereum
  • r,接收者的私钥,固定为 0x6f2dd2a7804705d2d536bee92221051865a639efa23f5ca7c810e77048253a79
  • s,发送者的私钥,固定为 0x28fa2db9f916e44fcc88370bedaf5eb3ec45632f040f4c1450c0f101e1e8bac8
  • signer,用于签署临时公钥的私钥,固定为 0xac304db075d1685284ba5e10c343f2324ee32df3394fc093c98932517d36e344。但是,当用于 Ed25519 签名时,此值充当 seed,而实际的私钥计算为 SHA512(seed)[:32]。或者换句话说,公钥是哈希私钥与基点的标量乘法。trusted 也是如此。
  • trusted,用于签署 signerPubKey 的私钥,固定为 0xda6649d68fc03b807e444e0034b3b59ec60716212007d72c9ddbfd33e25d38d1
  • oob,固定为 0x313233343536 (字符串值: 123456)
  • salt,固定为 0x6569703a2070726976617465206b657920656e63617073756c6174696f6e (字符串值: eip: private key encapsulation)

案例 1

使用 version 作为 secp256k1-AES-128-GCMR1 提供如下:

request({
	method: 'eth_generateEphemeralKeyPair',
	params: [
		version: 'secp256k1-AES-128-GCM',
		signerPubKey: '0x035a5ca16997f9b9ead9572c9bde36c5dab584b17bc965cdd7c2945c776e981b0b'
	],
})

假设该实现生成一个临时密钥对 (r, R)

r: '0x6f2dd2a7804705d2d536bee92221051865a639efa23f5ca7c810e77048253a79',
R: '0x039ef98feddb39664450c3876878093c70652caba7e3fd04333c0558ffdf798d09'

返回值可能是:

'0x039ef98feddb39664450c3876878093c70652caba7e3fd04333c0558ffdf798d09536da06b8d9207040ada179dc2c38f701a1a21c9ab5a7d52f5da50ea438e8ccf47dac77547fbdde194f71db52860b9e10ca2b089646f133d172124504ac1996a'

请注意,R 是压缩的,并且 R 位于返回值的前面:R || sig

因此 R2 可以提供为:

request({
	method: 'eth_encapsulatePrivateKey',
	params: [
		version: 'secp256k1-AES-128-GCM',
		recipient: '0x039ef98feddb39664450c3876878093c70652caba7e3fd04333c0558ffdf798d09536da06b8d9207040ada179dc2c38f701a1a21c9ab5a7d52f5da50ea438e8ccf47dac77547fbdde194f71db52860b9e10ca2b089646f133d172124504ac1996a',
		signerPubKey: '0x035a5ca16997f9b9ead9572c9bde36c5dab584b17bc965cdd7c2945c776e981b0b5bd427c527b7f1012b8edfd179b9002a7f2d7fc326bb6ae9aaf38b44eb93c397631fd8bb05fd78fa16ecca1eb19652b200f9048611265bc81f485cf60f29d6de',
		oob: '0x313233343536',
		salt: '0x6569703a2070726976617465206b657920656e63617073756c6174696f6e',
		account: '0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9'
	],
})

发送者应用程序 首先验证第一层签名,即 secp256k1 上的 ECDSA:

// 要签名的实际消息应该是解码后的字节数组
msg: '0x039ef98feddb39664450c3876878093c70652caba7e3fd04333c0558ffdf798d09',
sig: '0x536da06b8d9207040ada179dc2c38f701a1a21c9ab5a7d52f5da50ea438e8ccf47dac77547fbdde194f71db52860b9e10ca2b089646f133d172124504ac1996a',
//signerPubKey
pub: '0x035a5ca16997f9b9ead9572c9bde36c5dab584b17bc965cdd7c2945c776e981b0b'

然后,它继续验证第二层签名,也是 secp256k1 上的 ECDSA:

// 要签名的实际消息应该是解码后的字节数组
msg: '0x035a5ca16997f9b9ead9572c9bde36c5dab584b17bc965cdd7c2945c776e981b0b',
sig: '0x5bd427c527b7f1012b8edfd179b9002a7f2d7fc326bb6ae9aaf38b44eb93c397631fd8bb05fd78fa16ecca1eb19652b200f9048611265bc81f485cf60f29d6de',
//trustedPubKey
pub: '0x027fb72176f1f9852ce7dd9dc3aa4711675d3d8dc5102b86d758d853002137e839'

由于 发送者应用程序 信任 trustedPubKey,因此签名验证成功。

假设该实现生成一个临时密钥对 (s, S),如下所示:

s: '0x28fa2db9f916e44fcc88370bedaf5eb3ec45632f040f4c1450c0f101e1e8bac8',
S: '0x02ced2278d9ebb193f166d4ee5bbbc5ab8ca4b9ddf23c4172ad11185c079944c02'

共享密钥,对称密钥和 IV 应该是:

ss: '0x8e83bc5a9c77b11afc12c9a8262b16e899678d1720459e3b73ca2abcfed1fca3',
skey: '0x6ccc02a61aa16d6c66a1277e5e2434b8',
IV: '0x9c7a0f870d17ced2d2c3d1cf'

那么返回值应该是:

'0x02ced2278d9ebb193f166d4ee5bbbc5ab8ca4b9ddf23c4172ad11185c079944c02abff407e8901bb37d13d724a2e3a8a1a5af300adc286aa2ec65ef2a38c10c5cec68a949d0a20dbad2a8e5dfd7a14bbcb'

压缩公钥 S 领先于 cipher,而 cipher 又是(添加前缀 ‘0x’):

'0xabff407e8901bb37d13d724a2e3a8a1a5af300adc286aa2ec65ef2a38c10c5cec68a949d0a20dbad2a8e5dfd7a14bbcb'

然后 R3 提供为:

request({
	method: 'eth_intakePrivateKey',
	params: [
		version: 'secp256k1-AES-128-GCM',
		recipientPublicKey: '0x039ef98feddb39664450c3876878093c70652caba7e3fd04333c0558ffdf798d09',
		oob: '0x313233343536',
		salt: '0x6569703a2070726976617465206b657920656e63617073756c6174696f6e',
		data: '0x02ced2278d9ebb193f166d4ee5bbbc5ab8ca4b9ddf23c4172ad11185c079944c02abff407e8901bb37d13d724a2e3a8a1a5af300adc286aa2ec65ef2a38c10c5cec68a949d0a20dbad2a8e5dfd7a14bbcb'
	],
})

返回值应该是 0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9。这与 R2 中的 account 参数匹配。

案例 2

使用 version 作为 secp256k1-AES-256-GCM。计算出的对称密钥 skeyIVcipher 将有所不同。R1 提供如下:

request({
	method: 'eth_generateEphemeralKeyPair',
	params: [
		version: 'secp256k1-AES-256-GCM',
		signerPubKey: '0x035a5ca16997f9b9ead9572c9bde36c5dab584b17bc965cdd7c2945c776e981b0b'
	],
})

请注意,只有 version 不同(AES 密钥大小)。我们继续使用相同的 (r, R)(这只是一个测试向量)。

因此,R2 提供如下:

request({
	method: 'eth_encapsulatePrivateKey',
	params: [
		version: 'secp256k1-AES-256-GCM',
		recipient: '0x039ef98feddb39664450c3876878093c70652caba7e3fd04333c0558ffdf798d09536da06b8d9207040ada179dc2c38f701a1a21c9ab5a7d52f5da50ea438e8ccf47dac77547fbdde194f71db52860b9e10ca2b089646f133d172124504ac1996a',
		signerPubKey: '0x035a5ca16997f9b9ead9572c9bde36c5dab584b17bc965cdd7c2945c776e981b0b5bd427c527b7f1012b8edfd179b9002a7f2d7fc326bb6ae9aaf38b44eb93c397631fd8bb05fd78fa16ecca1eb19652b200f9048611265bc81f485cf60f29d6de',
		oob: '0x313233343536',
		salt: '0x6569703a2070726976617465206b657920656e63617073756c6174696f6e',
		account: '0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9'
	],
})

假设该实现生成与 案例 1 相同的 (s, S)。共享密钥,对称密钥和 IV 应该是:

ss: '0x8e83bc5a9c77b11afc12c9a8262b16e899678d1720459e3b73ca2abcfed1fca3',
skey: '0x6ccc02a61aa16d6c66a1277e5e2434b89c7a0f870d17ced2d2c3d1cfd0e6f199',
IV: '0x3369b9570b9d207a0a8ebe27'

共享密钥 ss案例 1 保持一致,对称密钥 skey 包含 案例 1 中的 skeyIV。IV 已更改。

那么返回值应如下所示,S 部分与 案例 1 相同,而 cipher 部分不同:

'0x02ced2278d9ebb193f166d4ee5bbbc5ab8ca4b9ddf23c4172ad11185c079944c0293910a91270b5deb0a645cc33604ed91668daf72328739d52a5af5a4760c4f3a9592b8f6d9b3ebe25127e7bf1c43b839'

然后 R3 提供为:

request({
	method: 'eth_intakePrivateKey',
	params: [
		version: 'secp256k1-AES-256-GCM',
		recipientPublicKey: '0x039ef98feddb39664450c3876878093c70652caba7e3fd04333c0558ffdf798d09',
		oob: '0x313233343536',
		salt: '0x6569703a2070726976617465206b657920656e63617073756c6174696f6e',
		data: '0x02ced2278d9ebb193f166d4ee5bbbc5ab8ca4b9ddf23c4172ad11185c079944c0293910a91270b5deb0a645cc33604ed91668daf72328739d52a5af5a4760c4f3a9592b8f6d9b3ebe25127e7bf1c43b839'
	],
})

返回值应该是 0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9。这与 R2 中的 account 参数匹配。

案例 3

使用 version 作为:Curve-25519-Chacha20-Poly1305R1 提供如下:

request({
	method: 'eth_generateEphemeralKeyPair',
	params: [
		version: 'Curve25519-Chacha20-Poly1305',
		signerPubKey: '0xe509fb840f6d5a69333ef68d69b86de55b9b905e45b16e3591912c097ba69938'
	],
})

请注意,对于 Curve25519,公钥和私钥的大小均为 32(字节)。并且公钥没有压缩。signerPubKey 的计算如下:

//signer 是 '0xac304db075d1685284ba5e1```javascript
request({
	method: 'eth_intakePrivateKey',
	params: [
		version: 'Curve250-Chacha20-Poly1305',
		recipientPublicKey: '0xc0ea3514b0ab83b2fe4f44ef96159cda8fa836ce549ef09569b901eef0723bf79',
		oob: '0x313233343536',
		salt: '0x6569703a207072697617465206b657920656e63617073756c6174696f6e',
		data: '0xd2fd6fcaac231d08363e736e61edb7e7696b13a727e3d2a239415cb8dc6ee2786a7e2e40efb86dc68f44f3e032bbedb1259fa820e548ac5adbf191784c568d4f642ca5b60c0b2142189dff6ee464b95c'
	],
})

返回值应为 0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9。这与 R2 中的 account 参数匹配。

安全注意事项

完全正向保密

PFS 通过在双方使用临时密钥对来实现。

可选签名和可信公钥

可以对 R 进行签名,以便 发送者应用程序 可以验证 R 是否可信。这涉及到签名验证以及签名者是否可信。虽然签名验证本身非常简单,但后者应该谨慎管理。为了方便解决这个信任管理问题,可以进一步签署 signerPubKey,从而创建一个双层信任结构:

R <-- signerPubKey <-- trustedPubKey

这允许各种策略来管理信任。例如:

  • 一个硬件钱包供应商非常重视品牌声誉和客户的资金安全,可以选择只信任自己的公钥,即所有 trustedPubKey 的实例。这些公钥仅对来自选定合作伙伴的 signerPubKey 进行签名。
  • MPC 服务可以在线发布其 signerPubKey,以便 发送者应用程序 不会针对错误或伪造的公钥验证签名。

请注意,建议在每条曲线上使用单独的密钥对进行签名。

安全级别

  1. 我们不考虑后量子安全性。如果量子计算机成为一种实际威胁,以太坊和其他 L1 链的底层密码将被替换,那么这个 EIP 将会过时(因为 ECIES 的 EC 部分也已损坏)。
  2. 安全级别应与底层链使用的椭圆曲线的安全级别相匹配。使用 AES-256 来保护 secp256k1 私钥没有多大意义,但实现可以选择自由。
  3. 话虽如此,一个密钥可能会在多个链中使用。因此,安全级别应涵盖最苛刻的要求和潜在的未来发展。

提供了 AES-128、AES-256 和 ChaCha20。

随机性

rs 必须使用密码学安全的随机数生成器(CSRNG)生成。

salt 可以是与 rs 以相同方式生成的随机字节。 salt 可以是任意长度,但通常的建议是 12 或 16,这可以通过某些硬件钱包的屏幕显示为 QR 码(以便另一个应用程序可以扫描读取)。如果未提供 salt,则此 EIP 使用默认值 EIP-6051

带外数据

oob 数据是可选的。当非空时,其内容是用户提供的数字或字母数字字符串。发送者应用程序 可以强制用户提供 oob

版权

版权及相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Base Labs (@Base-Labs), Weiji Guo (@weiji-cryptonatty), "EIP-6051: 私钥封装 [DRAFT]," Ethereum Improvement Proposals, no. 6051, November 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6051.