mirror of
https://github.com/nostr-protocol/nips.git
synced 2024-11-13 23:39:08 -05:00
5.5 KiB
5.5 KiB
NIP-XX
Nostr-Specific Deterministic Private Key Generation from Ethereum Wallet Signature
draft
optional
author:0xc0de4c0ffee
author:sshmatrix
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 compatible name,name@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
.
Note :
sub@domain.eth.limo
andsub.domain.eth.limo
are NOT same ID as their signatures will be different.
b) password
Password is optional string value used in HKDF salt,
let password = "horse staple battery"
let salt = await sha256(`eip155:${chainId}:${username}:${password?password:""}:${signature.slice(68)}`);
c) message
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
Deterministic signature from connected wallet. Signatures are 65 bytes long, bytes1(v)+bytes32(r)+bytes32(s)
.
let signature = wallet.signMessage(message);
e) HKDF
HKDF-SHA-256 is used to derive 42 bytes long hash key.
hkdf(sha256, inputKey, salt, info, dkLen = 42)
Input key
is SHA-256 hash of signature bytes.let inputKey = await sha256(hexToBytes(signature.slice(2)));
Salt
is SHA-256 hash of following identifier string.signature.slice(68)
is hexs
value of signature, last 32 bytes.let salt = await sha256(`eip155:${chainId}:${username}:${password?password:""}:${signature.slice(68)}`);
Info
is string with following format.let info = `eip155:${chainId}:${username}:${address}`;
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)let dkLen = 42;
hashToPrivateKey
function is FIPS 186-4 B.4.1 implementation to convert derived hash keys fromHKDF
to validsecp256k1
private keys. This function is implemented in js@noble/secp256k1
ashashToPrivateKey
.let hashKey = hkdf(sha256, inputKey, salt, info, dkLen=42); let privKey = secp256k1.utils.hashToPrivateKey(hashKey); let pubKey = secp256k1.schnorr.getPublicKey(privKey);
Implementation Requirements
- 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}
JS Example
const secp256k1 = require('@noble/secp256k1');
const {hexToBytes, bytesToHex} = require('@noble/hashes/utils');
const {hkdf} = require('@noble/hashes/hkdf');
const {sha256} = require('@noble/hashes/sha256');
// 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}`;
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