2023-02-19 02:39:48 -05:00
# NIP-XX
2023-02-17 07:27:57 -05:00
Nostr-Specific Deterministic Private Key Generation from Ethereum Wallet Signature
--
2023-02-11 15:26:38 -05:00
`draft` `optional` `author:0xc0de4c0ffee` `author:sshmatrix`
2023-02-17 07:27:57 -05:00
## Abstract
2023-02-11 15:26:38 -05:00
2023-02-20 10:19:23 -05:00
This specification provides an optional method for Nostr clients to generate deterministic private keys from Ethereum wallet signatures. This NIP proposes HMAC Key Derivation Function (HKDF) coupled with SHA-256 from ECDSA signatures (EIP-191) as an alternative to the Schnorr signatures (BIP-340), allowing Nostr to interact with Ethereum ecosystem, and vice-versa.
2023-02-11 15:26:38 -05:00
2023-02-17 07:27:57 -05:00
## Terminology
2023-02-19 02:39:48 -05:00
### a) Username
2023-02-17 07:27:57 -05:00
Username can be either of the following:
2023-02-19 02:39:48 -05:00
`petname` or `petname@domain.eth.limo` or `domain.eth.limo` or `sub.domain.eth.limo` , where
2023-02-11 15:26:38 -05:00
2023-02-19 02:39:48 -05:00
- `petname` is a NIP-02 compatible name,
- `petname@domain.eth.limo` is a NIP-05 compatible name,
- `domain.eth.limo` is NIP-05 equivalent of `_@domain.eth.limo` ,
- `sub.domain.eth.limo` is NIP-05 equivalent of `_@sub.domain.eth.limo` .
> a) `sub@domain.eth.limo` and `sub.domain.eth.limo` are NOT equivalent as their signatures will be different
2023-02-11 15:26:38 -05:00
2023-02-19 02:39:48 -05:00
> b) `petname` can be the same as `domain`
2023-02-11 15:26:38 -05:00
2023-02-19 02:39:48 -05:00
### b) Password
Password is an optional string value used in HKDF salt,
2023-02-12 06:23:29 -05:00
```js
let password = "horse staple battery"
2023-02-17 07:27:57 -05:00
let salt = await sha256(`eip155:${chainId}:${username}:${password?password:""}:${signature.slice(68)}`);
2023-02-11 15:26:38 -05:00
```
2023-02-12 06:23:29 -05:00
2023-02-19 02:39:48 -05:00
### c) Message
Message is text on screen that should warn the users to not sign messages indiscriminately,
2023-02-12 06:23:29 -05:00
```js
2023-02-17 07:27:57 -05:00
let message = `Login to Nostr as ${username}\n\nImportant: Please verify the integrity and authenticity of your Nostr client before signing this message.\n${info}`
2023-02-11 15:26:38 -05:00
```
2023-02-19 02:39:48 -05:00
### d) Signature
Signature is the deterministic signature from connected Ethereum wallet. Ethereum signatures `(v,r,s)` are 65 bytes long, i.e. `bytes1(v) + bytes32(r) + bytes32(s)` ,
2023-02-11 15:26:38 -05:00
```js
2023-02-20 10:19:23 -05:00
let signature = wallet.signMessage(message); // implements ethereum-native ECDSA signatures in format (v,r,s)
2023-02-17 07:27:57 -05:00
```
2023-02-19 02:39:48 -05:00
### e) HKDF (HMAC Key Derivation Function)
HKDF-SHA-256 is used to derive the 42 bytes long hash key: `hkdf(sha256, inputKey, salt, info, dkLen = 42)`
2023-02-17 07:27:57 -05:00
- `Input key` is SHA-256 hash of signature bytes.
```js
let inputKey = await sha256(hexToBytes(signature.slice(2)));
```
2023-02-19 02:39:48 -05:00
- `Salt` is SHA-256 hash of the following identifier string:
2023-02-17 07:27:57 -05:00
```js
let salt = await sha256(`eip155:${chainId}:${username}:${password?password:""}:${signature.slice(68)}`);
```
2023-02-19 02:39:48 -05:00
where, `signature.slice(68)` is hex `s` value of Ethereum signature, i.e. the last 32 bytes.
2023-02-19 02:57:46 -05:00
2023-02-19 02:39:48 -05:00
- `Info` is a string with the following format:
2023-02-17 07:27:57 -05:00
```js
let info = `eip155:${chainId}:${username}:${address}` ;
```
2023-02-19 02:39:48 -05:00
- Derived Key Length `dkLen` is set to 42.
2023-02-17 07:27:57 -05:00
```js
2023-02-19 02:39:48 -05:00
let dkLen = 42;
2023-02-17 07:27:57 -05:00
```
2023-02-20 10:19:23 -05:00
FIPS 186/4 B.4.1 requires hash length to be `>= n + 8` , where n is the length of final private key, such that `42 >= 32 + 8` .
2023-02-19 02:39:48 -05:00
- `hashToPrivateKey` function is FIPS 186-4 B.4.1 implementation to convert hash keys derived using HKDF to valid `secp256k1` private keys. This function is implemented in JavaScript library `@noble/secp256k1` as `hashToPrivateKey()` .
2023-02-17 07:27:57 -05:00
```js
let hashKey = hkdf(sha256, inputKey, salt, info, dkLen=42);
let privKey = secp256k1.utils.hashToPrivateKey(hashKey);
let pubKey = secp256k1.schnorr.getPublicKey(privKey);
```
## Implementation Requirements
2023-02-19 02:39:48 -05:00
- Connected Ethereum wallet signer MUST be EIP191 and RFC6979 compatible.
2023-02-17 07:27:57 -05:00
- The message MUST be string formatted as `Login to Nostr as ${username}\n\nImportant: Please verify the integrity and authenticity of your Nostr client before signing this message.\n${info}` .
- HKDF input key MUST be generated as the SHA-256 hash of 65 bytes signature.
- HKDF salt MUST be generated as SHA-256 hash of string `eip155:${chainID}:${username}:${password?password:""}:${signature.slice(68)}` .
- HKDF derived key length MUST be 42.
2023-02-19 02:39:48 -05:00
- HKDF info MUST be string formatted as `eip155:${chainId}:${username}:${address}` .
2023-02-17 07:27:57 -05:00
2023-02-19 02:39:48 -05:00
## JS Example
2023-02-17 07:27:57 -05:00
```js
const secp256k1 = require('@noble/secp256k1');
const {hexToBytes, bytesToHex} = require('@noble/hashes/utils');
const {hkdf} = require('@noble/hashes/hkdf');
const {sha256} = require('@noble/hashes/sha256');
2023-02-11 15:26:38 -05:00
2023-02-19 02:39:48 -05:00
// const wallet = connected ethereum wallet with ethers.js
2023-02-17 07:27:57 -05:00
let username = "me@domain.eth.limo"
let chainId = wallet.getChainId(); // get chainid from connected wallet
let address = wallet.getAddress(); // get address from wallet
let info = `eip155:${chainId}:${username}:${address}` ;
2023-02-11 15:26:38 -05:00
2023-02-17 07:27:57 -05:00
let message = `Login to Nostr as ${username}\n\nImportant: Please verify the integrity and authenticity of your Nostr client before signing this message.\n${info}`
let signature = wallet.signMessage(message); // request signature from wallet
2023-02-11 15:26:38 -05:00
2023-02-17 07:27:57 -05:00
let password = "horse staple battery"
let inputKey = await sha256(hexToBytes(signature.slice(2))); //skip "0x"
let salt = await sha256(`eip155:${chainId}:${username}:${password?password:""}:${signature.slice(68)}`);
2023-02-19 02:39:48 -05:00
let dkLen = 42;
2023-02-11 15:26:38 -05:00
2023-02-17 07:27:57 -05:00
let hashKey = await hkdf(sha256, inputKey, salt, info, dkLen);
let privKey = secp256k1.utils.hashToPrivateKey(hashKey);
let pubKey = secp256k1.schnorr.getPublicKey(privKey);
2023-02-12 06:23:29 -05:00
```
2023-02-17 07:27:57 -05:00
## Security Considerations
2023-02-12 06:23:29 -05:00
2023-02-17 07:27:57 -05:00
- Users should always verify the integrity and authenticity of the Nostr client before signing the message.
2023-02-19 02:39:48 -05:00
- Users should ensure that they only input their Nostr Username and Password in trusted and secure clients.
2023-02-12 06:23:29 -05:00
2023-02-17 07:27:57 -05:00
## References:
- [RFC6979: Deterministic Usage of the DSA and ECDSA ](https://datatracker.ietf.org/doc/html/rfc6979 )
- [RFC5869: HKDF (HMAC-based Extract-and-Expand Key Derivation Function) ](https://datatracker.ietf.org/doc/html/rfc5869 )
- [Digital Signature Standard (DSS), FIPS 186-4 B.4.1 ](https://csrc.nist.gov/publications/detail/fips/186/4/final )
2023-02-19 02:39:48 -05:00
- [BIP340: Schnorr Signature Standard ](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki )
2023-02-17 07:27:57 -05:00
- [ERC191: Signed Data Standard ](https://eips.ethereum.org/EIPS/eip-191 )
- [EIP155: Simple replay attack protection ](https://eips.ethereum.org/EIPS/eip-155 )
2023-02-19 02:39:48 -05:00
- [NIP-02: Contact List and Petnames ](https://github.com/nostr-protocol/nips/blob/master/02.md )
- [NIP-05: Mapping Nostr keys to DNS-based internet identifiers ](https://github.com/nostr-protocol/nips/blob/master/05.md )
2023-02-17 07:27:57 -05:00
- [@noble/hashes ](https://github.com/paulmillr/noble-hashes )
2023-02-19 02:39:48 -05:00
- [@noble/secp256k1 ](https://github.com/paulmillr/noble-secp256k1 )