RFC: Hierarchical Deterministic Wallet Derivation Function

https://ethresear.ch/t/rfc-hierarchical-deterministic-wallet-derivation-function/15214

Summary

This RFC defines a way for dApps to deterministically generate Ethereum accounts, similar to BIP-32, out of the Ethereum signature of an Ethereum account.

Constraints

We want dApps to generate “stealth” identities for an user, which we will call Bob, connected through “Sign-in with Ethereum” with his main identity, which we will call M.

Let’s define this process by the function f(X) where X is the identity to use.

Private

We also want this process to be as private as possible, we don’t want Bob to make a transaction with his main identity, we don’t want him to make any RPC requests. This process should ideally be offline, except if using network-based connection e.g. WalletConnect. Ethereum signature is a good candidate for this as it’s offline.

Hierarchical

We want to be able to generate multiple identities based on an index, similarly to BIP-32.

Let’s add a parameter i to the function f(X, i) for the index of such account.

Bob can generate address A = f(M, i), he can also generate address B = f(M, i + 1), without having generated A in the first place (generating B only requires M)

Deterministic

We want this process to be deterministic. If Bob goes to a dApp on his computer, generates an address A = f(M, i), then goes to the same dApp on his phone, and generate an address B = f(M, i), then both identities MUST be the same

i1 = i2 => A = B

i1 != i2 => A != B

Salted

We also want this process to be salted. The function on the dApp A will have a salt, and the function on dApp B a different salt.

Let’s add a parameter s to the function f(X, i, s)

If Bob goes to dApp A, generates an address A = f(M, x, s1), then goes to dApp B, generates an address B = g(M, x, s2), they MUST NOT be the same.

s1 = s2 => A = B

s1 != s2 => A != B

Recursive

We want the process to be recursive. Bob can generate the address AA = f(A, i, s) where A = f(M, i, s). By only having A and not M.

Defining f

With all constraints defined, we will call f a (Crypto Secure) (Private) Hierarchical Deterministic (Salted) Wallet Derivation Function, or just HDWDF

Ethereum signatures, or more specifically secp256k1 signatures are good candidates for such function

Proposal 1 with inner salt

f(wallet, index, salt) = secp256k1(sign(wallet, HMAC("Ethereum Wallet Derivation: " + index, salt)))

Where

  • HMAC can be HMAC-SHA256 or HMAC-KECCAK256; I would be in favor or SHA since it’s compatible with WebCrypto

  • sign(X, m) is the process of signing a message m with identity X, with e.g. eth_signMessage

  • secp256k1(seed) is derivating a secp256k1 curve point from a crypto secure seed, with a KDF if necessary

  • salt is a public crypto secure random value

The problem with such function is that the message to be signed is not human readable since it’s a HMAC output

Proposal 2 with outer salt

We could use HMAC outside the signature

g(wallet, index, salt) = secp256k1(HMAC(sign(X, "Ethereum Wallet Derivation: " + index), salt))

It has the advantage of being human readable, but the downside of being more easily craftable, a malicious dApp could make the user sign the message and then use the salt of another dApp

Proposal 3 with human-readable name

One solution could be to use the dApp name in the message, and remove the HMAC

h(wallet, index, name) = secp256k1(sign(X, "Ethereum Wallet Derivation for " + name + " at index " + index))

This message could also be formatted using eth_signTypedMessage

Let me know what you think about it!

1 post – 1 participant

Read full topic