update:draft

This commit is contained in:
0xc0de4c0ffee 2023-02-17 18:12:57 +05:45
parent 7d80aecff5
commit 299c8b992e

167
xx.md
View File

@ -1,87 +1,118 @@
NIP-XX NIP-XX
====== --
Nostr specific keys from Ethereum wallet signatures
----------------------------------------------------
Nostr-Specific Deterministic Private Key Generation from Ethereum Wallet Signature
--
`draft` `optional` `author:0xc0de4c0ffee` `author:sshmatrix` `draft` `optional` `author:0xc0de4c0ffee` `author:sshmatrix`
This NIP ?is optional specification for Nostr clients to generate private key from deterministic Ethereum wallet signatures. ## Abstract
This specification provides an optional method for Nostr clients to generate deterministic private keys from Ethereum wallet signatures.
## Terminology
### a) `username`
Username can be either of the following:
`username` or `user@domain.eth.limo` or `domain.eth.limo` or `sub.domain.eth.limo`, where,
- `username` is a [NIP-02](https://github.com/nostr-protocol/nips/blob/master/05.md) compatible name,
- `name@domain.eth.limo` is a [NIP-05](https://github.com/nostr-protocol/nips/blob/master/05.md) compatible name,
- `domain.eth.limo` is [NIP-05](https://github.com/nostr-protocol/nips/blob/master/05.md) equivalent of
`_@domain.eth.limo`,
- `sub.domain.eth.limo` is [NIP-05](https://github.com/nostr-protocol/nips/blob/master/05.md) equivalent of
`_@sub.domain.eth.limo`.
> Note : `sub@domain.eth.limo` and `sub.domain.eth.limo` are NOT same ID as their signatures will be different.
## A) Username : ### b) `password`
``` Password is optional string value used in HKDF salt,
let username = "mypetname" || "mynip05@domain.eth.limo" || "domain.eth.limo" || "sub.domain.eth.limo"
```
1) `mypetname` is NIP02 compatible name
2) `mynip05@domain.eth.limo` is NIP05 compatible ID
3) `domain.eth.limo` is NIP05 equivalent to `_@domain.eth.limo`
4) `sub.domain.eth.limo` is NIP05 equivalent to `_@sub.domain.eth.limo`
~~Implementing clients should verify if generated public keys match NIP05 and NIP02 records.~~
## B) Password :
Password is optional value used in HKDF salt:
```js ```js
let username = "name@domain.eth.limo"
let password = "horse staple battery" let password = "horse staple battery"
let signature = wallet.signMessage(message) let salt = await sha256(`eip155:${chainId}:${username}:${password?password:""}:${signature.slice(68)}`);
let salt = sha256(`nostr:${username}:${password}:${signature.slice(68)}`);
if(!password || password == ""){
salt = sha256(`nostr:${username}:${signature.slice(68)}`)
}
``` ```
### c) `message`
## C) Message :
```js ```js
let message = `Login to Nostr as ${username}\n\nWARNING : DO NOT SIGN THIS REQUEST FROM UNTRUSTED NOSTR CLIENTS.\n${checksum(wallet.address)}` 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}`
``` ```
## D) Signature : ### d) `signature`
Signature is Deterministic signature from connected wallet. Signatures are 65 bytes long, `bytes1(v)+bytes32(r)+bytes32(s)`.
## C) HKDF :
We use HKDF with SHA256
```js ```js
import { hkdf } from '@noble/hashes/hkdf'; let signature = wallet.signMessage(message);
import { sha256 } from '@noble/hashes/sha256'; ```
import * as secp256k1 from '@noble/secp256k1'; ### e) `HKDF`
HKDF-SHA-256 is used to derive 42 bytes long hash key.
//let username = 'name@domain.eth.limo'; `hkdf(sha256, inputKey, salt, info, dkLen = 42)`
//let optPassword = "horse staple battery"; - `Input key` is SHA-256 hash of signature bytes.
// optional pin/password ```js
let inputKey = await sha256(hexToBytes(signature.slice(2)));
//let address = wallet.getAddress(); ```
// get checksum'd address from eth wallet - `Salt` is SHA-256 hash of following identifier string. `signature.slice(68)` is hex `s` value of signature, last 32 bytes.
//let signature = wallet.signMessage(message); ```js
// request signature from eth wallet (v,r,s) let salt = await sha256(`eip155:${chainId}:${username}:${password?password:""}:${signature.slice(68)}`);
// 0x + bytes1(v) + bytes32(r) + bytes32(s) ```
// 2 + (2 + 64 + 64) = 132 String length - `Info` is string with following format.
```js
let inputKey = sha256(signature); let info = `eip155:${chainId}:${username}:${address}`;
let salt = sha256(`nostr:${username}:${optPassword}:${signature.slice(68)}`); //68~132 ```
// nostr:${username}:${signature.slice(68)} - `Derived Key Length` is set to 42. FIPS 186/4 B.4.1 require hash length to be >=n+8 where, n is length of final private key. (42 >= 32 + 8)
```js
let info = `nostr:${username}:${address}`; let dkLen = 42;
```
let dkLen = 42; // 32 + 8 + 2 - `hashToPrivateKey` function is FIPS 186-4 B.4.1 implementation to convert derived hash keys from `HKDF`to valid `secp256k1` private keys. This function is implemented in js `@noble/secp256k1` as `hashToPrivateKey`.
//FIPS 186 B.4.1 limit must be >=keylen+8 ```js
let hashKey = hkdf(sha256, inputKey, salt, info, dkLen=42);
let hashKey = hkdf(sha256, inputKey, salt, info, dkLen); let privKey = secp256k1.utils.hashToPrivateKey(hashKey);
let privKey = nobleSecp256k1.hashToPrivateKey(hashKey); let pubKey = secp256k1.schnorr.getPublicKey(privKey);
//FIPS 186 B.4.1 @noble/secp256k1
let pubKey = nobleSecp256k1.getPublicKey(privKey)
``` ```
## Reference : ## Implementation Requirements
1) ERC-191: Signed Data Standard - https://eips.ethereum.org/EIPS/eip-191 - Connected Ethereum Wallet signer MUST be EIP191 and RFC6979 compatible.
- 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.
- HKDF info MUST be string formatted as `eip155:${chainId}:${username}:${address}`
2) RFC6979: Deterministic Usage of the DSA and ECDSA - https://datatracker.ietf.org/doc/html/rfc6979 ## JS Example
```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');
3) RFC5869: HMAC-based Extract-and-Expand Key Derivation Function (HKDF) - https://datatracker.ietf.org/doc/html/rfc5869 // const wallet = // connected ethereum wallet with ethers.js
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}`;
4) FIPS 186 B.4.1 - https://csrc.nist.gov/publications/detail/fips/186/4/final 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
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)}`);
let dkLen = 42;
let hashKey = await hkdf(sha256, inputKey, salt, info, dkLen);
let privKey = secp256k1.utils.hashToPrivateKey(hashKey);
let pubKey = secp256k1.schnorr.getPublicKey(privKey);
```
## Security Considerations
- Users should always verify the integrity and authenticity of the Nostr client before signing the message.
- Users should ensure that they only input their Nostr username and password in trusted and secure clients.
- Implementing clients should ensure ~~..private key/security~~
## 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)
- [ERC191: Signed Data Standard](https://eips.ethereum.org/EIPS/eip-191)
- [EIP155: Simple replay attack protection](https://eips.ethereum.org/EIPS/eip-155)
- [@noble/hashes](https://github.com/paulmillr/noble-hashes)
- [@noble/secp256k1](https://github.com/paulmillr/noble-secp256k1)