EIPs/EIPS/eip-6051.md
Weiji Guo e666b5b02c
Add EIP-6051: Private Key Encapsulation (#6051)
* added EIP draft for private key encapsulation

* minor updates to spec: intake function shall return the Ethereum address of the private key

* added test vector #1

* minor formatting

* minor edits

* added test vector #2 and #3, added signature verification data to #1

* changed signature to against byte values

* added test vector generator

* renamed file to assigned EIP number

* fixed file header

* updated default value for salt

* fixed offending links etc.

* fixed typo

Co-authored-by: xinbenlv <zzn@zzn.im>

* updated based on review comments

* replaced json formatting with none for better rendering

* fixed grammar

Co-authored-by: xinbenlv <zzn@zzn.im>

* fixed grammar

Co-authored-by: xinbenlv <zzn@zzn.im>

* revision suggestions taken with gratitudes

Co-authored-by: xinbenlv <zzn@zzn.im>

* revision suggestions taken with gratitudes

Co-authored-by: xinbenlv <zzn@zzn.im>

* fixed grammar

Co-authored-by: xinbenlv <zzn@zzn.im>

* fixed grammar

Co-authored-by: xinbenlv <zzn@zzn.im>

* fixed grammar

Co-authored-by: xinbenlv <zzn@zzn.im>

* fixed grammar

Co-authored-by: xinbenlv <zzn@zzn.im>

* fixed grammar

Co-authored-by: xinbenlv <zzn@zzn.im>

* fixed grammar

Co-authored-by: xinbenlv <zzn@zzn.im>

* revision suggestions taken with gratitudes

Co-authored-by: xinbenlv <zzn@zzn.im>

* fixed grammar as suggested

Co-authored-by: xinbenlv <zzn@zzn.im>

* revision suggestions taken with gratitudes

Co-authored-by: xinbenlv <zzn@zzn.im>

* fixed grammar as suggested

Co-authored-by: xinbenlv <zzn@zzn.im>

* fixed grammar as suggested

Co-authored-by: xinbenlv <zzn@zzn.im>

* fixed grammar as suggested

* fixed based on grammarly.com suggestions

* Update EIPS/eip-6051.md

Co-authored-by: Pandapip1 <45835846+Pandapip1@users.noreply.github.com>

* Update EIPS/eip-6051.md

Co-authored-by: Pandapip1 <45835846+Pandapip1@users.noreply.github.com>

* Update EIPS/eip-6051.md

Co-authored-by: Pandapip1 <45835846+Pandapip1@users.noreply.github.com>

* replacing bold fonts with links as suggested

* fixed dead links

* fixed markdown linter errors

Co-authored-by: xinbenlv <zzn@zzn.im>
Co-authored-by: Pandapip1 <45835846+Pandapip1@users.noreply.github.com>
2022-12-27 06:58:25 -08:00

524 lines
27 KiB
Markdown

---
eip: 6051
title: Private Key Encapsulation
description: defines a specification for encapsulating private keys.
author: Base Labs (@Base-Labs), Weiji Guo (@weiji-cryptonatty)
discussions-to: https://ethereum-magicians.org/t/private-key-encapsulation-to-move-around-securely-without-entering-seed/11604
status: Draft
type: Standards Track
category: Interface
created: 2022-11-21
---
## Abstract
This EIP proposes a mechanism to encapsulate a private key so that it could be securely relocated to another application without providing the seed. This EIP combines `ECIES` (Elliptic Curve Integrated Encryption Scheme) and optional signature verification under various choices to ensure that the private key is encapsulated for a known or trusted party.
## Motivation
There are various cases in which we might want to export one of many private keys from a much more secure but less convenient wallet, which is controlled with a seed or passphrase.
1. We might dedicate one of many private keys for messaging purposes, and that private key is probably managed in a not-so-secure manner;
2. We might want to export one of many private keys from a hardware wallet, and split it with MPC technology so that a 3rd party service could help us identify potential frauds or known bad addresses, enforce 2FA, etc., meanwhile we can initiate transactions from a mobile device with much better UX and without carrying a hardware wallet.
In both cases, it is safer not to provide the seed which controls the whole wallet and might contains many addresses in multiple chains.
This EIP aims to enable such use cases.
## Specification
### Sender and Recipient
We hereby define:
- *Sender* as the party who holds in custody the private key to be encapsulated; *Sender Application* as the client-side application that said *Sender* uses to send the encapsulated private key.
- *Recipient* as the party who accepts the encapsulated private key, unwraps, and then uses it; *Recipient Application* as the client-side application that *Recipient* uses to receive the encapsulated private key.
### Core Algorithms
The basic idea is to encapsulate the private key with ECIES. To ensure that the ephemeral public key to encapsulate the private key is indeed generated from a trusted party and has not been tampered with, we also provided an option to sign that ephemeral public key in this standard.
There should be a mandatory `version` parameter. This allows various kinds of Key Encapsulation Mechanisms to be adopted depending on security considerations or preferences. The list shall be short to minimize compatibility issues among different vendors.
In addition to a `version` parameter, the following keys and functions are involved:
1. The Sender's private key `sk`, which is to be encapsulated to the Recipient, and the corresponding address `account`.
2. The ephemeral Recipient key pair `(r, R)` such that `R = [r]G`. `G` denotes the base point of the elliptic curve, and `[r]G` denotes scalar multiplication. Optionally, `R` could be signed, and `signerPubKey` and `signature` are then provided for Sender to verify if `R` could be trusted or not.
3. The ephemeral Sender key pair `(s, S)` such that `S = [s]G`.
4. The share secret `ss := [s]R = [r]S` according to ECDH. Note that for secp256k1 this EIP follows RFC5903 and uses compact representation, which means to use *only* the `x` coordinate as the shared secret. For Curve25519 this EIP follows RFC7748.
5. The out-of-band data `oob`, optional. This could be digits or an alpha-numeric string entered by the user.
6. Let `derivedKey := HKDF(hash=SHA256, ikm=ss, info=oob, salt, length)`. HKDF is defined in RFC5869. The `length` should be determined by `skey` and `IV` requirements such that the symmetric key `skey = derivedKey[0:keySize]`, and `IV = derivedKey[keySize:length]`. `keySize` denotes the key size of the underlying symmetric algorithm, for example, 16 (bytes) for AES-128, and 32 (bytes) for Chacha20. See **Security Considerations** for the use of `salt`.
7. Let `cipher := authenticated_encryption(symAlg, skey, IV, data=sk)`. The symmetric cipher algorithm `symAlg` and authentication scheme are decided by the version parameter. No additional authentication data `aad` is used.
A much-simplified example flow without signature and verification is:
1. *Recipient Application* generates `(r, R)`.
2. User inputs `R` to *Sender Application*, along with a six-digit code “123456” as `oob`.
3. *Sender Application* generates `(s, S)`, and computes `cipher`, then returns `S || cipher`.
4. *Recipient Application* scans to read `S` and `cipher`. The user enters “123456” as `oob` to *Recipient Application*.
5. *Recipient Application* decrypts `cipher` to get `sk`.
6. *Recipient Application* derives the address corresponding to `sk` so that the user can confirm the correctness.
With signature and verification, the signature to `R` by `singerPubKey` is appended to `R`. `signerPubKey` itself could have been already signed by `trustedPubKey`, and that signature is appended to `signerPubKey`. Note that the signature is applied to the byte array data instead of its string representation, which might lead to confusion and interoperability issues (such as hex or base64, lower case v.s. upper case, etc.). See [Requests](#requests) and [Test Cases](#test-cases) for further clarification and examples.
### Requests
#### Encoding of data and messages
- Raw bytes are encoded in hex and prefixed with '0x'.
- Unless specified otherwise, all parameters and return values are hex-encoded bytes.
- `cipher` is encoded into a single byte buffer as: `[IV || encrypted_sk || tag]`.
- `R`, `S`, `signerPubKey`, and `trustedPubKey` are compressed if applicable.
- `R` or `signerPubKey` could be followed by a signature to it: `[pub || sig]`. Note that for the secp256k1 curve, the signature is just 64 bytes without the `v` indicator as found in a typical Ethereum signature.
#### R1. Request for Recipient to generate ephemeral key pair
```javascript
request({
method: 'eth_generateEphemeralKeyPair',
params: [version, signerPubKey],
})
// expected return value: R
```
`signerPubKey` is optional. If provided, it is assumed that the implementation has the corresponding private key and the implementation MUST sign the ephemeral public key (in the form of what is to be returned). The signature algorithm is determined by the curve part of the `version` parameter, that is, ECDSA for secp256k1, and Ed25519 for Curve25519. And in this situation, it should be the case that *Sender* trusts `signerPubKey`, no matter how this trust is maintained. If not, the next request WILL be rejected by *Sender Application*. Also, see [Security Considerations](#security-considerations).
The implementation then MUST generate random private key `r` with a cryptographic secure random number generator (CSRNG), and derive ephemeral public key `R = [r]G`. The implementation SHOULD keep the generated key pair `(r, R)` in a secure manner in accordance with the circumstances, and SHOULD keep it only for a limited duration, but the specific duration is left to individual implementations. The implementation SHOULD be able to retrieve `r` when given back the corresponding public key `R` if within the said duration.
The return value is `R`, compressed if applicable. If `signerPubKey` is provided, then the `signature` is appended to `R`, also hex-encoded.
Alternatively, `signature` could be calculated separately, and then appended to the returned data.
#### R2. Request for Sender to encapsulate the private key
```javascript
request({
method: 'eth_encapsulatePrivateKey',
params: [
version,
recipient, // public key, may be followed by its signature, see signerPubKey
signerPubKey,
oob,
salt,
account
],
})
// expected return value: S || cipher
```
`recipient` is the return value from the call to generate ephemeral key pair, with the optional `signature` appended either as returned or separately.
`oob` and `salt` are just byte arrays.
`account` is used to identify which private key to be encapsulated. With Ethereum, it is an address. Also, see [Encoding of data and messages](#encoding-of-data-and-messages).
If `signerPubKey` is provided or `recipient` contains `signature` data, the implementation MUST perform signature verification. Missing data or incorrect format MUST either fail the call or result in an empty return and optional error logs.
`signerPubKey` could have been further signed by another key pair `(trusted, trustedPubKey)`, which is trusted by *Sender Application*. In that case, `signerPubKey` is appended with the corresponding signature data, which SHOULD be verified against `trustedPubKey`. See [Test Cases](#test-cases) for further clarification.
The implementation shall then proceed to retrieve the private key `sk` corresponding to `account`, and follow the [Core Algorithms](#core-algorithms) to encrypt it.
The return data is a byte array that contains first *Sender*'s ephemeral public key `S` (compressed if applicable), then `cipher` including any authentication tag, that is, `S || cipher`.
#### R3. Request for Recipient to unwrap and intake the private key
```javascript
request({
method: 'eth_intakePrivateKey',
params: [
version,
recipientPublicKey, // no signature this time
oob,
salt,
data
],
})
// expected return value: account
```
This time `recipientPublicKey` is only the ephemeral public key `R` generated earlier in the Recipient side, just for the implementation to retrieve the corresponding private key `r`. `data` is the return value from the call to encapsulate private key, which is `S || cipher`.
When the encapsulated private key `sk` is decrypted successfully, the implementation can process it further according to the designated purposes. Some general security guidelines SHALL be followed, for example, do *not* log the value, do securely wipe it after use, etc.
The return value is the corresponding Ethereum address for `sk`, or empty if any error.
### Options and Parameters
Available elliptic curves are:
- secp256k1 (mandatory)
- Curve25519
Available authenticated encryption schemes are:
- AES-128-GCM (mandatory)
- AES-256-GCM
- Chacha20-Poly1305
The version string is simply the concatenation of the elliptic curve and AE scheme, for example, secp256k1-AES-128-GCM. The above lists allow a combination of six different concrete schemes. Implementations are encouraged to implement curve-related logic separately from authenticated encryption schemes to avoid duplication and to promote interoperability.
Signature algorithms for each curve are:
- secp256k1 --> ECDSA
- Curve25519 --> Ed25519
## Rationale
A critical difference between this [EIP-6051](./eip-6051.md) with [EIP-5630](./eip-5630.md) is that, as the purpose of key encapsulation is to transport a private key securely, the public key from the key recipient should be ephemeral, and mostly used only one-time. While in EIP-5630 settings, the public key of the message recipient shall be stable for a while so that message senders can encrypt messages without key discovery every time.
There is security implication to this difference, including perfect forward secrecy. We aim to achieve perfect forward secrecy by generating ephemeral key pairs on both sides every time:
1) first *Recipient* shall generate an ephemeral key pair, retain the private key securely, and export the public key;
2) then *Sender* can securely wrap the private key in ECIES, with another ephemeral key pair, then destroy the ephemeral key securely;
3) finally *Recipient* can unwrap the private key, then destroy its ephemeral key pair securely. After these steps, the cipher text in transport intercepted by a malicious 3rd party is no longer decryptable.
## Backwards Compatibility
No backward compatibility issues for this new proposal.
### Interoperability
To minimize potential compatibility issues among applications (including hardware wallets), this EIP requires that version secp256k1-AES-128-GCM MUST be supported.
The version could be decided by the user or negotiated by both sides. When there is no user input or negotiation, secp256k1-AES-128-GCM is assumed.
It is expected that implementations cover curve supports separately from encryption support, that is, all the versions that could be derived from the supported curve and supported encryption scheme should work.
Signatures to `R` and `signerPubKey` are applied to byte array values instead of the encoded string.
### UX Recommendations
`salt` and/or `oob` data: both are inputs to the HKDF function (`oob` as “info” parameter). For better user experiences we suggest to require from users only one of them but this is up to the implementation.
*Recipient Application* is assumed to be powerful enough. *Sender Application* could have very limited computing power and user interaction capabilities.
## Test Cases
For review purposes, the program to generate the test vectors is open-sourced and provided in the corresponding discussion thread.
### Data Fixation
Throughout the test cases, we fix values for the below data:
- `sk`, the private key to be encapsulated, fixed to: `0xf8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315`. The corresponding address is `0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9`, called `account`. Note that these values come from the book *Mastering Ethereum* by Andreas M. Antonopoulos and Gavin Wood.
- `r`, the Recipient private key, fixed to `0x6f2dd2a7804705d2d536bee92221051865a639efa23f5ca7c810e77048253a79`
- `s`, the Sender private key, fixed to `0x28fa2db9f916e44fcc88370bedaf5eb3ec45632f040f4c1450c0f101e1e8bac8`
- `signer`, the private key to sign the ephemeral public key, fixed to `0xac304db075d1685284ba5e10c343f2324ee32df3394fc093c98932517d36e344`. When used for Ed25519 signing, however, this value acts as `seed`, while the actual private key is calculated as `SHA512(seed)[:32]`. Or put another way, the public key is the scalar multiplication of hashed private key to the base point. Same for `trusted`.
- `trusted`, the private key to sign `signerPubKey`, fixed to `0xda6649d68fc03b807e444e0034b3b59ec60716212007d72c9ddbfd33e25d38d1`
- `oob`, fixed to `0x313233343536` (string value: `123456`)
- `salt`, fixed to `0x6569703a2070726976617465206b657920656e63617073756c6174696f6e` (string value: `eip: private key encapsulation`)
### Case 1
Use `version` as `secp256k1-AES-128-GCM`. **R1** is provided as:
```javascript
request({
method: 'eth_generateEphemeralKeyPair',
params: [
version: 'secp256k1-AES-128-GCM',
signerPubKey: '0x035a5ca16997f9b9ead9572c9bde36c5dab584b17bc965cdd7c2945c776e981b0b'
],
})
```
Suppose the implementation generates an ephemeral key pair `(r, R)`:
```
r: '0x6f2dd2a7804705d2d536bee92221051865a639efa23f5ca7c810e77048253a79',
R: '0x039ef98feddb39664450c3876878093c70652caba7e3fd04333c0558ffdf798d09'
```
The return value could be:
```
'0x039ef98feddb39664450c3876878093c70652caba7e3fd04333c0558ffdf798d09536da06b8d9207040ada179dc2c38f701a1a21c9ab5a7d52f5da50ea438e8ccf47dac77547fbdde194f71db52860b9e10ca2b089646f133d172124504ac1996a'
```
Note that `R` is compressed and `R` leads the return value: `R || sig`.
Therefore **R2** could be provided as:
```javascript
request({
method: 'eth_encapsulatePrivateKey',
params: [
version: 'secp256k1-AES-128-GCM',
recipient: '0x039ef98feddb39664450c3876878093c70652caba7e3fd04333c0558ffdf798d09536da06b8d9207040ada179dc2c38f701a1a21c9ab5a7d52f5da50ea438e8ccf47dac77547fbdde194f71db52860b9e10ca2b089646f133d172124504ac1996a',
signerPubKey: '0x035a5ca16997f9b9ead9572c9bde36c5dab584b17bc965cdd7c2945c776e981b0b5bd427c527b7f1012b8edfd179b9002a7f2d7fc326bb6ae9aaf38b44eb93c397631fd8bb05fd78fa16ecca1eb19652b200f9048611265bc81f485cf60f29d6de',
oob: '0x313233343536',
salt: '0x6569703a2070726976617465206b657920656e63617073756c6174696f6e',
account: '0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9'
],
})
```
*Sender Application* first verifies first layer signature as ECDSA over secp256k1:
```
// actual message to be signed should be the decoded byte array
msg: '0x039ef98feddb39664450c3876878093c70652caba7e3fd04333c0558ffdf798d09',
sig: '0x536da06b8d9207040ada179dc2c38f701a1a21c9ab5a7d52f5da50ea438e8ccf47dac77547fbdde194f71db52860b9e10ca2b089646f133d172124504ac1996aaf4a811661741a43587dd458858b75c582ca7db82fa77b',
//signerPubKey
pub: '0x035a5ca16997f9b9ead9572c9bde36c5dab584b17bc965cdd7c2945c776e981b0b'
```
Then it proceeds to verify the second layer signature, also as ECDSA over secp256k1:
```
// actual message to be signed should be the decoded byte array
msg: '0x035a5ca16997f9b9ead9572c9bde36c5dab584b17bc965cdd7c2945c776e981b0b',
sig: '0x5bd427c527b7f1012b8edfd179b9002a7f2d7fc326bb6ae9aaf38b44eb93c397631fd8bb05fd78fa16ecca1eb19652b200f9048611265bc81f485cf60f29d6de',
//trustedPubKey
pub: '0x027fb72176f1f9852ce7dd9dc3aa4711675d3d8dc5102b86d758d853002137e839'
```
Since *Sender Application* trusts `trustedPubKey`, the signature verification succeeds.
Suppose the implementation generates an ephemeral key pair `(s, S)` as:
```
s: '0x28fa2db9f916e44fcc88370bedaf5eb3ec45632f040f4c1450c0f101e1e8bac8',
S: '0x02ced2278d9ebb193f166d4ee5bbbc5ab8ca4b9ddf23c4172ad11185c079944c02'
```
The shared secret, symmetric key, and IV should be:
```
ss: '0x8e83bc5a9c77b11afc12c9a8262b16e899678d1720459e3b73ca2abcfed1fca3',
skey: '0x6ccc02a61aa16d6c66a1277e5e2434b8',
IV: '0x9c7a0f870d17ced2d2c3d1cf'
```
Then the return value should be:
```
'0x02ced2278d9ebb193f166d4ee5bbbc5ab8ca4b9ddf23c4172ad11185c079944c02abff407e8901bb37d13d724a2e3a8a1a5af300adc286aa2ec65ef2a38c10c5cec68a949d0a20dbad2a8e5dfd7a14bbcb'
```
With compressed public key `S` leading `cipher`, which in turn is (added prefix '0x'):
```
'0xabff407e8901bb37d13d724a2e3a8a1a5af300adc286aa2ec65ef2a38c10c5cec68a949d0a20dbad2a8e5dfd7a14bbcb'
```
Then **R3** is provided as:
```javascript
request({
method: 'eth_intakePrivateKey',
params: [
version: 'secp256k1-AES-128-GCM',
recipientPublicKey: '0x039ef98feddb39664450c3876878093c70652caba7e3fd04333c0558ffdf798d09',
oob: '0x313233343536',
salt: '0x6569703a2070726976617465206b657920656e63617073756c6174696f6e',
data: '0x02ced2278d9ebb193f166d4ee5bbbc5ab8ca4b9ddf23c4172ad11185c079944c02abff407e8901bb37d13d724a2e3a8a1a5af300adc286aa2ec65ef2a38c10c5cec68a949d0a20dbad2a8e5dfd7a14bbcb'
],
})
```
The return value should be `0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9`. This matches the `account` parameter in **R2**.
### Case 2
Use `version` as `secp256k1-AES-256-GCM`. The calculated symmetric key `skey`, `IV`, and `cipher` will be different. **R1** is provided as:
```javascript
request({
method: 'eth_generateEphemeralKeyPair',
params: [
version: 'secp256k1-AES-256-GCM',
signerPubKey: '0x035a5ca16997f9b9ead9572c9bde36c5dab584b17bc965cdd7c2945c776e981b0b'
],
})
```
Note that only the `version` is different (AES key size). We keep using the same `(r, R)` (this is just a test vector).
Therefore **R2** is provided as:
```javascript
request({
method: 'eth_encapsulatePrivateKey',
params: [
version: 'secp256k1-AES-256-GCM',
recipient: '0x039ef98feddb39664450c3876878093c70652caba7e3fd04333c0558ffdf798d09536da06b8d9207040ada179dc2c38f701a1a21c9ab5a7d52f5da50ea438e8ccf47dac77547fbdde194f71db52860b9e10ca2b089646f133d172124504ac1996a',
signerPubKey: '0x035a5ca16997f9b9ead9572c9bde36c5dab584b17bc965cdd7c2945c776e981b0b5bd427c527b7f1012b8edfd179b9002a7f2d7fc326bb6ae9aaf38b44eb93c397631fd8bb05fd78fa16ecca1eb19652b200f9048611265bc81f485cf60f29d6de',
oob: '0x313233343536',
salt: '0x6569703a2070726976617465206b657920656e63617073756c6174696f6e',
account: '0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9'
],
})
```
Suppose the implementation generates the same `(s, S)` as [Case 1](#case-1). The shared secret, symmetric key, and IV should be:
```
ss: '0x8e83bc5a9c77b11afc12c9a8262b16e899678d1720459e3b73ca2abcfed1fca3',
skey: '0x6ccc02a61aa16d6c66a1277e5e2434b89c7a0f870d17ced2d2c3d1cfd0e6f199',
IV: '0x3369b9570b9d207a0a8ebe27'
```
With shared secret `ss` remaining the same as [Case 1](#case-1), symmetric key `skey` contains both the `skey` and `IV` from [Case 1](#case-1). IV is changed.
Then the return value should be the following, with the `S` part the same as [Case 1](#case-1) and the `cipher` part different:
```
'0x02ced2278d9ebb193f166d4ee5bbbc5ab8ca4b9ddf23c4172ad11185c079944c0293910a91270b5deb0a645cc33604ed91668daf72328739d52a5af5a4760c4f3a9592b8f6d9b3ebe25127e7bf1c43b839'
```
Then **R3** is provided as:
```javascript
request({
method: 'eth_intakePrivateKey',
params: [
version: 'secp256k1-AES-256-GCM',
recipientPublicKey: '0x039ef98feddb39664450c3876878093c70652caba7e3fd04333c0558ffdf798d09',
oob: '0x313233343536',
salt: '0x6569703a2070726976617465206b657920656e63617073756c6174696f6e',
data: '0x02ced2278d9ebb193f166d4ee5bbbc5ab8ca4b9ddf23c4172ad11185c079944c0293910a91270b5deb0a645cc33604ed91668daf72328739d52a5af5a4760c4f3a9592b8f6d9b3ebe25127e7bf1c43b839'
],
})
```
The return value should be `0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9`. This matches the `account` parameter in **R2**.
### Case 3
Use `version` as: `Curve-25519-Chacha20-Poly1305`. **R1** is provided as:
```javascript
request({
method: 'eth_generateEphemeralKeyPair',
params: [
version: 'Curve25519-Chacha20-Poly1305',
signerPubKey: '0xe509fb840f6d5a69333ef68d69b86de55b9b905e45b16e3591912c097ba69938'
],
})
```
Note that with Curve25519 the size is 32 (bytes) for both the public key and private key. And there is no compression for the public key. `signerPubKey` is calculated as:
```
//signer is '0xac304db075d1685284ba5e10c343f2324ee32df3394fc093c98932517d36e344'
s := SHA512(signer)[:32]
signerPubKey := Curve25519.ScalarBaseMult(s).ToHex()
```
The same technique applies to `trustedPubKey`. With `r` the same as in [Case 1](#case-1) and [Case 2](#case-2) and the curve being changed, the return value is `R = [r]G || sig`:
```
R = '0xc0ea3514b0ab83b2fe4f4ef96159cda8fa836ce549ef09569b901eef0723bf79cac06de279ec7f65f6b75f6bee740496df0650a6de61da5e691d7c5da1c7cb1ece61c669dd588a1029c38f11ad1714c1c9742232f9562ca6bbc7bad57882da04'
```
**R2** is provided as:
```javascript
request({
method: 'eth_encapsulatePrivateKey',
params: [
version: 'Curve25519-Chacha20-Poly1305',
recipient: '0xc0ea3514b0ab83b2fe4f4ef96159cda8fa836ce549ef09569b901eef0723bf79879d900f04a955078ff6ae86f1d1b69b3e1265370e64bf064adaecb895c51effa3bdae7964bf8f9a6bfaef3b66306c1bc36afa5607a51b9768aa42ac2c961f02',
signerPubKey: '0xe509fb840f6d5a69333ef68d69b86de55b9b905e45b16e3591912c097ba69938d43e06a0f32c9e5ddb39fce34fac2b6f5314a1b1583134f27426d50af7094b0c101e848737e7f717da8c8497be06bab2a9536856c56eee194e89e94fd1bba509',
oob: '0x313233343536',
salt: '0x6569703a2070726976617465206b657920656e63617073756c6174696f6e',
account: '0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9'
],
})
```
Both `recipient` and `signerPubKey` have been signed in Ed25519. Verifying signature to `R` is carried out as:
```
// actual message to be signed should be the decoded byte array
msg: '0xc0ea3514b0ab83b2fe4f4ef96159cda8fa836ce549ef09569b901eef0723bf79',
sig: '0x879d900f04a955078ff6ae86f1d1b69b3e1265370e64bf064adaecb895c51effa3bdae7964bf8f9a6bfaef3b66306c1bc36afa5607a51b9768aa42ac2c961f02',
//signerPubKey
pub: '0xe509fb840f6d5a69333ef68d69b86de55b9b905e45b16e3591912c097ba69938'
```
After successfully verifying the signature (and the one by `trustedPubKey`), the implementation then generates ephemeral key pair `(s, S)` in Curve25519:
```
// s same as Case 1 and Case 2
s = '0x28fa2db9f916e44fcc88370bedaf5eb3ec45632f040f4c1450c0f101e1e8bac8',
S = '0xd2fd6fcaac231d08363e736e61edb7e7696b13a727e3d2a239415cb8dc6ee278'
```
The shared secret, symmetric key, and IV should be:
```
ss: '0xe0b36f56cdb63c27e933a5a67a5e97db4b566c9276a36aeee5dc6e87da118867',
skey: '0x7c6fa749e6df13c8578dc44cb24cdf46a44cb163e1e570c2e590c720aed5783f',
IV: '0x3c98ef6fc34b0d6e7e16bd78'
```
Then the return value should be `S || cipher`:
```
'0xd2fd6fcaac231d08363e736e61edb7e7696b13a727e3d2a239415cb8dc6ee2786a7e2e40efb86dc68f44f3e032bbedb1259fa820e548ac5adbf191784c568d4f642ca5b60c0b2142189dff6ee464b95c'
```
Then **R3** is provided as:
```javascript
request({
method: 'eth_intakePrivateKey',
params: [
version: 'Curve25519-Chacha20-Poly1305',
recipientPublicKey: '0xc0ea3514b0ab83b2fe4f4ef96159cda8fa836ce549ef09569b901eef0723bf79',
oob: '0x313233343536',
salt: '0x6569703a2070726976617465206b657920656e63617073756c6174696f6e',
data: '0xd2fd6fcaac231d08363e736e61edb7e7696b13a727e3d2a239415cb8dc6ee2786a7e2e40efb86dc68f44f3e032bbedb1259fa820e548ac5adbf191784c568d4f642ca5b60c0b2142189dff6ee464b95c'
],
})
```
The return value should be `0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9`. This matches the `account` parameter in **R2**.
## Security Considerations
### Perfect Forward Secrecy
PFS is achieved by using ephemeral key pairs on both sides.
### Optional Signature and Trusted Public Keys
`R` could be signed so that *Sender Application* can verify if `R` could be trusted or not. This involves both signature verification and if the signer could be trusted or not. While signature verification is quite straightforward in itself, the latter should be managed with care. To facilitate this trust management issue, `signerPubKey` could be further signed, creating a dual-layer trust structure:
```
R <-- signerPubKey <-- trustedPubKey
```
This allows various strategies to manage trust. For example:
- A hardware wallet vendor which takes it very seriously about the brand reputation and the fund safety for its customers, could choose to trust only its own public keys, all instances of `trustedPubKey`. These public keys only sign `signerPubKey` from selected partners.
- A MPC service could publish its `signerPubKey` online so that *Sender Application* won't verify the signature against a wrong or fake public key.
Note that it is advised that a separate key pair should be used for signing on each curve.
### Security Level
1. We are not considering post-quantum security. If the quantum computer becomes a materialized threat, the underlying cipher of Ethereum and other L1 chains would have been replaced, and this EIP will be outdated then (as the EC part of ECIES is also broken).
2. The security level shall match that of the elliptic curve used by the underlying chains. It does not make much sense to use AES-256 to safeguard a secp256k1 private key but implementations could choose freely.
3. That being said, a key might be used in multiple chains. So the security level shall cover the most demanding requirement and potential future developments.
AES-128, AES-256, and ChaCha20 are provided.
### Randomness
`r` and `s` must be generated with a cryptographic secure random number generator (CSRNG).
`salt` could be random bytes generated the same way as `r` or `s`. `salt` could be in any length but the general suggestion is 12 or 16, which could be displayed as a QR code by the screen of some hardware wallet (so that another application could scan to read). If `salt` is not provided, this EIP uses the default value as `EIP-6051`.
### Out of Band Data
`oob` data is optional. When non-empty, its content is digits or an alpha-numeric string from the user. *Sender Application* may mandate `oob` from the user.
## Copyright
Copyright and related rights waived via [CC0](../LICENSE.md).