forked from DecentralizedClimateFoundation/DCIPs
524 lines
27 KiB
Markdown
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).
|