NIP-XX: Nostr-specific Private Key from Deterministic Wallet Signature (Sign-in-With-X)
Note : this is a draft pull request for feedback.
NIP-XX: Deterministic Private Key Generation from Ethereum Wallet Signature
Example code :
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 = "[email protected]"
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);
Seems a bit risky that the web app would have the private key, unless this got implemented in, e.g., Metamask. But if we are going down that road, perhaps Metamask could just implement NIP-07, and use this method under the hood.
In any case, do we really want to open the door to putting various key generation methods into their own NIPs? What about a NIP to turn a BTC private key into a nostr private key, and a NIP to turn a Monero private key into a nostr private key, and a NIP to turn a NANO private key into a nostr private key, etc...
On a further thought, perhaps each app can just implement their own method of: <insert private key format> to <nostr private key> and then offer an "export" function which allows the nostr private key to be exported. Perhaps using something like #133
See also #258 for the inverse problem.
I agree the only way for this to work would be if this was done on Metamask directly, and Metamask implemented NIP-07.
Thanks for feedback @barkyq @fiatjaf 🙏
Seems a bit risky that the web app would have the private key, unless this got implemented in, e.g., metamask. But if we are going down that road, perhaps metamask could just implement NIP-07, and use this method under the hood.
It's less $ risker compared to reusing same ETH keys in Nostr clients. This is optional specs for web/mobile Nostr clients and window.nostr/window.ethereum providers to generate multiple ID/keys with deterministic signature, username and optional password.
In any case, do we really want to open the door to putting various key generation methods into their own NIPs? What about a NIP to turn a BTC private key into a nostr private key.....
We can update this as Chain Agnostic Improvement Proposals (CAIP) approach to support ALL coins/chains compatible with RFC6979 like deterministic signature.
Example using BIP44 (SLIP44) coin type and EIP155 chain ID.
let info1 = `bip44:${coin_type}:${username}:${address}`;
let info2 = `eip155:${chain_id}:${username}:${address}`;
let info3 = `cosmos:${hub_id}:${username}:${address}`; // not sure about this, have to lookup
//...
let salt1 = await sha256(`bip44:${coin_type}:${username}:${password?password:""}:${last_32bytes_ofSignatureHex}`);
let salt2 = await sha256(`eip155:${chain_id}:${username}:${password?password:""}:${last_32bytes_ofSignatureHex}`);
//...
Login with Nostr #154 is our inverse-brah 😆
We're working on different BIP47 "like" stealth address generation too, more details at https://github.com/nostr-protocol/nips/pull/258#issuecomment-1437199109
I agree the only way for this to work would be if this was done on Metamask directly, and Metamask implemented NIP-07.
There's "Metamask Flask" plugin for Nostr, Schnorr Snap. MM-Flask is experimental wallet not ready for average users, it will take more time for major coin-wallets to implement Nostr+schnorr feature. Can't wait for major wallet provider to implement that, & hope they don't reuse their coin-keys for Nostr again.
This specification is for Nostr + X compatible app/clients to sign Nostr stuffs with window.nostr and X-chain stuffs with window.ethereum like wallet providers. CAIP-122: Sign in with X feature is used to generate app-specific deterministic keys for Nostr app/clients and compatible NIP07 providers.
Updating chain and account identifiers to as defined in CAIP-02:Blockchain ID Specification and CAIP-10:Account ID Specification.
Note : We were exploring SLIP/BIP44
coin_typeas chain identifier but CAIPs are using that for assets type inside BIP122, so we're using that BIP122:.. leaving BIP44/:coin_type as assets type for future. 🙏
Example: CAIP-10, CAIP-02 with NIP02/NIP05 user identifiers.
# Bitcoin mainnet (BIP122)
bip122:<first 16 bytes of genesis/fork hash>:address:username
bip122:000000000019d6689c085ae165831e93:address:username
# EVM chains (EIP155)
eip155:<chain_id>:address:username
eip155:1:address:username
# Cosmos Hub (Tendermint + Cosmos SDK/Binance)
cosmos:<hub/ChainName>:address:username
cosmos:cosmoshub-2:address:username
cosmos:Binance-Chain-Tigris:address:username
cosmos:iov-mainnet:address:username
...
# Dummy max length (8+1+32 = 41 chars/bytes) +":address:username"
chainstd:8c3444cf8970a9e41a706fab93e7a6c4
Still WIP, Example
let nostr = widow.nostr || nostrTools ;
let coinWallet = await connectEthers(window.ethereum);
let username = '[email protected]';
let password = 'opt#password';
let pubkey = await nostr.nipxx.newIdentity(username, password="", chainType="eip155", coinWallet="");
// wrapped as nipxx.loginWithX(...) function.
// registration: set new nip02 and nip05 records for this pubkey.
// login: verify if there's matching nip02 and nip05 records.
Dear @fiatjaf @barkyq,
We would like to report major updates to our NIP-XX proposal and again seek your feedback on the following:
- We have added significant amount of explanatory text to the NIP-XX proposal including some useful diagrams and flowcharts. For instance, the Schnorr key generation algorithm from ECDSA/Ethereum keys is outlined below:

-
The codebase for the underlying NIP-XX implementation is also complete and we have requested second review of that as well here.
-
The client for the NIP-XX implementation is also ready at Dostr: Ethereum-flavoured Nostr; please feel free to give it a swing.
- We'd like to request NIP identifier 60 or 111 for NIP-XX, i.e. NIP-60 or NIP-111.
We'd love to hear back from you with your thoughts and comments on this PR!
Thank you!
Is this method of generating keys a standard among altcoins?
Is this method of generating keys a standard among altcoins?
No, I wouldn't say so. But it is standard and well-known in general Cryptography. I think @0xc0de4c0ffee might be more aware of previous implementations of it, if any, in cryptosphere. I hope he will comment here on it.
Is this method of generating keys a standard among altcoins?
It's fully chain agnostic sign-in-with-x CAIP122 used to generate deterministic keys.
There are few Ethereum dapps using straight sha256(half_signature) to derive app-specific private keys from signatures. We're adding chain and address identifiers (CAIP02+CAIP10) format, NIP05/NIP02 user info (+optional password) with HKDF to generate deterministic keys.
Example from umbra.js stealth payment codes.
https://github.com/ScopeLift/umbra-protocol/blob/master/umbra-js/src/classes/Umbra.ts#L549
We're signing stuffs from both window.nostr & window.ethereum.. for basic e2ee msgs and stealth payments, so we don't want to reuse or mix ETH keys with Nostr keys.
** err updated typo
Hi @fiatjaf,
Hope you are doing good. Dostr client is nearing its official launch (Mar 31). In reality, the client is already online at:
- on web3 at dostr.eth.limo, and
- on web2 at dostr.xyz,
for earlybirds. There won't be any drastic changes to the client from now on other than cosmetic fine-tuning of the UI/UX.
With respect to the NIP:
- We have changed the numbering of the NIP in PR to NIP-111 as its our "favourite" number.
- More importantly, we have finalised the format of the message to eth-Sign to generate Nostr keys.
This NIP is in its final boss form from our side 😊
@sshmatrix are you still refactoring the signing message? I like this idea and I am implementing at https://github.com/digi-monkey/flycat-web/pull/82, I will hope the message can be finalized and not changed again since I am going to merge the PR and rollout this feature
@sshmatrix are you still refactoring the signing message? I like this idea and I am implementing at digi-monkey/flycat-web#82, I will hope the message can be finalized and not changed again since I am going to merge the PR and rollout this feature
Hi @digi-monkey, the message contained in the present draft is "final". It won't be changed, we hope. We have adopted the same on Dostr client and frozen it. There was a typo in the draft however and we have fixed it. It didn't affect the implementation apart from a mislabeling of caip10 variable in the statement.
@bumblefudge Implementing CAIP-122 as is won't work in this case, since we need the final key output to be deterministic. Adding the nonce and timestamp will add variable salt to the key derivation function resulting in variable keys. Deterministic key generation requires deterministic message and this led to the current choice of message to sign.
timestamp can be zero (i.e. midnight 1/1/1970) and nonce can be any string. in fact, CASA member @skgbafa demo'd a usage of SIWE for deterministic key generation at a CASA event, so I see no reason a more conformant version couldn't be used for that use-case.
apologies that I missed this notification and wasn't able to make these suggestions before final status! if SIWX conformance is of use, maybe it's worth a superceding spec or an extension spec that puts the same inputs into a conformant message value, so that individual implementations can choose to implement that variant instead if taking advantage of wallet special-handling of SIWX messages is worth the extra work to them?
apologies that I missed this notification and wasn't able to make these suggestions before final status! if SIWX conformance is of use, maybe it's worth a superceding spec or an extension spec that puts the same inputs into a conformant
messagevalue, so that individual implementations can choose to implement that variant instead if taking advantage of wallet special-handling of SIWX messages is worth the extra work to them?
I was thinking precisely the same thing! Perhaps it is worth an extension spec that provides deterministic usage of SIWx. We have CAIP-111 in the works (pretty much ready) and will open a PR soon. I will have a discussion with my co-dev about message formatting and report back. Thanks for your comments! Much appreciated
@digi-monkey You might want to hold your horses there 🤭
Oh! Actually I meant an extension NIP, but hey, maybe an extension-CAIP* might make just as much sense. I would also add that if the NIPs elaborate enough complexity and cohesion that there are nostr-enabled wallets, signing methods, etc, it might some day make sense to create a Nostr namespace, where you can define a profile for CAIP-10 (syntax of a URN scheme for CAIP-10 encodings of nostr accounts/pubkeys), CAIP-122 (if a Nostr app which held a private key wanted to "sign in with nostr" to another service), etc etc.
* = note, it wouldn't be CAIP-111, unfortunately, because CAIPs are numbered to the PR# that creates them, so if you opened a PR today it would be CAIP-222 --- which is a funny coincidence, hurry up before someone else nabs that number!
Oh! Actually I meant an extension NIP, but hey, maybe an extension-CAIP* might make just as much sense...
*= note, it wouldn't be CAIP-111, unfortunately, because CAIPs are numbered to the PR# that...
Hi @bumblefudge, linking the answer to your questions here: https://github.com/nostr-protocol/nips/pull/268#discussion_r1161227145. GitHub notifications are unreliable on multiple threads
