2023-02-26 07:08:08 -05:00
NIP-XX
--
Nostr-Specific Private Keys from Deterministic Wallet Signatures (Sign-In-With-X)
2023-02-17 07:27:57 -05:00
--
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-03-10 06:24:00 -05:00
This specification provides an optional method for Nostr clients and NIP-07 providers and coin wallet providers to generate deterministic private keys from Chain Agnostic `Sign-in-With-X` [(CAIP-122) ](https://github.com/ChainAgnostic/CAIPs/pull/122 ) signature. Nostr-specific private key is derived from HKDF-SHA-256 using NIP-02/NIP-05 names, [CAIP-02: Blockchain ID Specification ](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md ), [CAIP-10: Account ID Specification ](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md ) identifiers, and deterministic signature from connected coin-wallet as inputs.
2023-02-11 15:26:38 -05:00
2023-02-17 07:27:57 -05:00
## Terminology
2023-02-26 07:08:08 -05:00
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` is a NIP-02 compatible name,
2023-02-26 07:08:08 -05:00
- `petname@example.com` is a NIP-05 identifier,
- `example.com` is NIP-05 identifier `_@example.com` ,
- `sub.example.com` is NIP-05 identifier `_@sub.example.com` .
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-26 07:08:08 -05:00
//...
let salt = await sha256(`${caip10}:${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
2023-02-26 07:08:08 -05:00
Deterministic message to be signed by coin-wallet provider.
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-26 07:08:08 -05:00
2023-02-19 02:39:48 -05:00
### d) Signature
2023-02-26 07:08:08 -05:00
RFC6979 compatible deterministic signature from coin-wallet provider.
2023-02-11 15:26:38 -05:00
```js
2023-02-26 07:08:08 -05:00
let signature = wallet.signMessage(message);
2023-02-17 07:27:57 -05:00
```
2023-02-26 07:08:08 -05:00
2023-03-10 06:24:00 -05:00
### e) Blockchain and Address Identifier
2023-02-26 07:08:08 -05:00
Chain Agnostic [CAIP-02: Blockchain ID Specification ](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md ) and [CAIP-10: Account ID Specification ](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md ) are used to generate blockchain and address identifiers.
```
let caip02 =
2023-03-10 06:24:00 -05:00
`eip155:<evm_chain_id>` ||
2023-02-26 07:08:08 -05:00
`cosmos:<hub_id_name>` ||
`bip122:<16 bytes genesis/fork hash>` ;
let caip10 = `${caip02}:<checksum_address>` ;
```
### f) HKDF
2023-02-19 02:39:48 -05:00
HKDF-SHA-256 is used to derive the 42 bytes long hash key: `hkdf(sha256, inputKey, salt, info, dkLen = 42)`
2023-02-26 07:08:08 -05:00
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-26 07:08:08 -05:00
- `Info` is CAIP10 and NIP02/NIP05 identifier string formatted as :
2023-02-17 07:27:57 -05:00
```js
2023-02-26 07:08:08 -05:00
let info = `${caip10}:${username}` ;
2023-02-17 07:27:57 -05:00
```
2023-02-19 02:57:46 -05:00
2023-02-26 07:08:08 -05:00
- `Salt` is SHA-256 hash of the `info` , optional password and last 32 bytes of signature string formatted as :
2023-02-17 07:27:57 -05:00
```js
2023-02-26 07:08:08 -05:00
let salt = await sha256(`${info}:${password?password:""}:${signature.slice(68)}`);
2023-02-17 07:27:57 -05:00
```
2023-02-26 07:08:08 -05:00
where, `signature.slice(68)` last 32 bytes of deterministic signature.
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-26 07:08:08 -05:00
FIPS 186/4 B.4.1 requires hashkey length to be `>= n + 8` . Where n = 32 bytes length of final `secp256k1` private key, such that `42 >= 32 + 8` .
- `hashToPrivateKey` function is FIPS 186-4 B.4.1 implementation to convert hashkey derived using HKDF to valid `secp256k1` private keys. This function is implemented in JavaScript library `@noble/secp256k1` as `hashToPrivateKey()` .
2023-02-20 10:19:23 -05:00
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.
2023-02-26 07:08:08 -05:00
- HKDF salt MUST be generated as SHA-256 hash of string `${info}:${username}:${password?password:""}:${signature.slice(68)}` .
2023-02-17 07:27:57 -05:00
- HKDF derived key length MUST be 42.
2023-02-26 07:08:08 -05:00
- HKDF info MUST be string formatted as `${CAIP_10}:${address}:${username}` .
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
2023-02-26 07:08:08 -05:00
import * as secp256k1 from '@noble/secp256k1'
import {hkdf} from '@noble/hashes/hkdf'
import {sha256} from '@noble/hashes/sha256'
import {queryProfile} from './nip05'
import {getPublicKey} from './keys'
import {ProfilePointer} from './nip19'
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-26 07:08:08 -05:00
let username = "me@example.com"
2023-02-17 07:27:57 -05:00
let chainId = wallet.getChainId(); // get chainid from connected wallet
let address = wallet.getAddress(); // get address from wallet
2023-02-26 07:08:08 -05:00
let caip10 = `eip155:${chainId}:${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${caip10}`
2023-02-17 07:27:57 -05:00
let signature = wallet.signMessage(message); // request signature from wallet
let password = "horse staple battery"
2023-02-11 15:26:38 -05:00
2023-02-26 07:08:08 -05:00
/**
2023-03-10 06:24:00 -05:00
*
2023-02-26 07:08:08 -05:00
* @param username nip02/nip05 identifier
* @param caip10 CAIP identifier for the blockchain account
* @param sig Deterministic signature from X-wallet provider
* @param password Optional password
* @returns Deterministic private key as hex string
*/
export async function privateKeyFromX(
username: string,
caip10: string,
sig: string,
password: string | undefined
): Promise < string > {
if (sig.length < 64 )
throw new Error("Signature too short");
let inputKey = await sha256(secp256k1.utils.hexToBytes(sig.toLowerCase().startsWith("0x") ? sig.slice(2) : sig))
let info = `${caip10}:${username}`
let salt = await sha256(`${info}:${password?password:""}:${sig.slice(-64)}`)
let hashKey = await hkdf(sha256, inputKey, salt, info, 42)
return secp256k1.utils.bytesToHex(secp256k1.utils.hashToPrivateKey(hashKey))
}
/**
2023-03-10 06:24:00 -05:00
*
2023-02-26 07:08:08 -05:00
* @param username nip02/nip05 identifier
* @param caip10 CAIP identifier for the blockchain account
* @param sig Deterministic signature from X-wallet provider
* @param password Optional password
2023-03-10 06:24:00 -05:00
* @returns
2023-02-26 07:08:08 -05:00
*/
export async function signInWithX(
username: string,
caip10: string,
sig: string,
password: string | undefined
): Promise < {
petname: string,
profile: ProfilePointer | null,
privkey: string
} > {
let profile = null
let petname = username
if (username.includes(".")) {
try {
profile = await queryProfile(username)
} catch (e) {
console.log(e)
throw new Error("Nostr Profile Not Found")
}
if(profile == null){
throw new Error("Nostr Profile Not Found")
2023-03-10 06:24:00 -05:00
}
2023-02-26 07:08:08 -05:00
petname = (username.split("@").length == 2) ? username.split("@")[0] : username.split(".")[0]
}
let privkey = await privateKeyFromX(username, caip10, sig, password)
let pubkey = getPublicKey(privkey)
if (profile?.pubkey & & pubkey !== profile.pubkey) {
throw new Error("Invalid Signature/Password")
}
return {
petname,
profile,
privkey
}
}
2023-02-12 06:23:29 -05:00
```
2023-03-10 06:24:00 -05:00
## Implementations
1) Nostr Tools : [Sign-In-With-X ](https://github.com/dostr-eth/nostr-tools/tree/sign-in-with-x ) ([Pull Request #132 ](https://github.com/nbd-wtf/nostr-tools/pull/132))
2) Nostr Client: [Dostr Client ](https://github.com/dostr-eth/dostr-client )
2023-02-26 07:08:08 -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 )
2023-02-26 07:08:08 -05:00
- [CAIP-02: Blockchain ID Specification ](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md )
- [CAIP-10: Account ID Specification ](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md )
2023-03-10 06:24:00 -05:00
- [CAIP-122: Sign-in-With-X) ](https://github.com/ChainAgnostic/CAIPs/pull/122 )
2023-02-26 07:08:08 -05:00
2023-02-17 07:27:57 -05:00
- [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-03-10 06:24:00 -05:00
- [@noble/secp256k1 ](https://github.com/paulmillr/noble-secp256k1 )