1

Update (2022.03.25)

From @Ismael's comment and MetaMask github issue:

  1. The key from MetaMask's eth_getEncryptionPublicKey rpc is NOT the Public Key of my account, but a different key calculated from my private key with the nacl implementation of the X25519_XSalsa20_Poly1305 algorithm, which is different from Ethereum's default algorithm.
  2. MetaMask holds the private key, but it doesn't provide a function to decrypt messages with the private key.

Update (2022.03.15)

Now message encrypting works. But message decrypting still doesn't work with public key from eth_getEncryptionPublicKey + eth_decrypt. I've updated the Gist


Preface:

  1. I have a project that requires encrypting / decrypting multiple messages.
  2. Calling ethereum.request() for encrypting and decrypting triggers many MetaMask pop-ups and requires many user interactions.
  3. I found eth-crypto which seems to be able to reduce user interactions by calling it's built-in encrypt/decrypt functions.

What I found:

  1. With the same account (private key) in MetaMask, I got different pubkey if retrieving/recovering in different ways.
  2. Those pubkeys look different, in different length.
  3. Those keys seem not compatible to each other. When using them to encrypt message for example:
    • Works: Use EthCrypto with EthCrypto recovered public key, Use Ethereum (MetaMask) with EthCrypto recovered public key, Use Ethereum with Ethereum public key.
    • Doesn't work: Use EthCrypto with Ethereum (MetaMask) public key. (I want this most.)

Question:

  1. Are they totally different things?
  2. Is there any way to convert from one to another?
  3. What do I need to do to make the pubkey from eth_getEncryptionPublicKey works with EthCrypto.encryptWithPublicKey()?

Sample codes:

Link to gist (my test codes)

  1. Method 1: Get pubkey from web3/MetaMask directly
let pubkey_eth = await window.ethereum.request({
    "method": "eth_getEncryptionPublicKey",
    "params": [selectedAccount], // you must have access to the specified account
});
console.log(Buffer.from(pubkey_eth, "base64").toString("hex"));

bbfbb7e44ad3f14dfdf7089eab8a2a8407efddc5dbfeb3b00e15d2f996d1ab06 (string length 64)

let pubkey_eth_decompressed_prefix02 = EthCrypto.publicKey.decompress("02" + Buffer.from(pubkey_eth, "base64").toString("hex"));
let pubkey_eth_decompressed_prefix03 = EthCrypto.publicKey.decompress("03" + Buffer.from(pubkey_eth, "base64").toString("hex"));
// results are different, both are not usable by encryption function from eth-crypto

window.Ethereum: My public key decompressed (prefix 02) from Ethereum is bbfbb7e44ad3f14dfdf7089eab8a2a8407efddc5dbfeb3b00e15d2f996d1ab069936428a47bcf50da95add19994b7d747b2dc6b3e18fd4034bd551fdd938314c (string length 128)
window.Ethereum: My public key decompressed (prefix 03) from Ethereum is bbfbb7e44ad3f14dfdf7089eab8a2a8407efddc5dbfeb3b00e15d2f996d1ab0666c9bd75b8430af256a522e666b4828b84d2394c1e702bfcb42aae0126c7cae3 (string length 128)

  1. Method 2: Recover pubkey from MetaMask signed message, using eth-crypto
let secretMsgSignature = await web3.eth.sign(
    EthCrypto.hash.keccak256("Secret message"),
    selectedAccount
);
const secretMsgSignature_personal = await new Promise((resolve, reject) => {
    web3.currentProvider.sendAsync({
        "method": "personal_sign",
        "params": [web3.utils.utf8ToHex("Secret message"), selectedAccount],
        "from": selectedAccount,
    }, (err, response) => {
        if (err) {return reject(err);}
        resolve(response.result);
    });
});

Signature is 0xdb2fe9c70f4bfdc69299bf0dabe823b7e405dafdaae5ca0ae4eef8ef1b3abd1e5a7b777cb71608bfc80e4d33c3fdf1d6bdbeeac861a14d718e16621263b1b23e1c
Signature (personal) is 0x0a42e36b34adbaa8c7ac926b0fb78d1b91bc1f19ae2cf0cb06a8330958f868130ec68ed47ae25474fe1f1cae202c0fb5e85f4e6baeaf1ac7f609002d81f6fc981b
Both length 132

let pubkey_ethcrypto = EthCrypto.recoverPublicKey(
    secretMsgSignature,
    EthCrypto.hash.keccak256("Secret message")
);
let pubkey_ethcrypto_compressed = EthCrypto.publicKey.compress(pubkey_ethcrypto);

// -------

const pubkey_ethcrypto_personal = EthCrypto.recoverPublicKey(
    secretMsgSignature_personal,
    web3.eth.accounts.hashMessage("Secret message")
);
let pubkey_ethcrypto_compressed_personal = EthCrypto.publicKey.compress(pubkey_ethcrypto_personal);
// none of the two is the same as the pubkey we got directly from metamask

EthCrypto: My public key is f4ce65a8d150c0434e16cf4de4c22fa1870b7eeb963ce9a503c504b21f8fe52f74bd3a005bd3d07eb0ea39e048452178f22cb02214810f538962bef40104ff68 (string length 128)
EthCrypto: My public key compressed is 02f4ce65a8d150c0434e16cf4de4c22fa1870b7eeb963ce9a503c504b21f8fe52f (string length 66)
EthCrypto: My public key (personal) is f4ce65a8d150c0434e16cf4de4c22fa1870b7eeb963ce9a503c504b21f8fe52f74bd3a005bd3d07eb0ea39e048452178f22cb02214810f538962bef40104ff68 (string length 128)
EthCrypto: My public key (personal) compressed is 02f4ce65a8d150c0434e16cf4de4c22fa1870b7eeb963ce9a503c504b21f8fe52f (string length 66)

5
  • It is likely related to different HD derivate paths. medium.com/myetherwallet/… - the only way to be sure if to read the source code of relevant libraries. Mar 12, 2022 at 8:17
  • @MikkoOhtamaa In the article, it says when HD derivate path changes we will get different addresses, but in my case the address did not change. I'll try to dig deeper, thanks for the info.
    – BlueT
    Mar 14, 2022 at 19:44
  • I've updated the gist, now message encrypting works. But message decrypting still doesn't work with public key from eth_getEncryptionPublicKey + eth_decrypt. Gist gist.github.com/bluet/e944003d6d2069666799cb398d48bb01
    – BlueT
    Mar 14, 2022 at 19:55
  • 1
    @BlueT The public key returned by eth_getEncryptionPublicKey is not the one used for transactions and messages. From their docs The public key is computed from entropy associated with the specified user account, using the nacl implementation of the X25519_XSalsa20_Poly1305 algorithm. eth-sig-util has encryption and decryption that shouldn't require metamask interaction. Looking at EthCrypto package it uses ECCrypto which uses a different algorithm for encryption.
    – Ismael
    Mar 23, 2022 at 4:23
  • Thanks @Ismael , now it becomes clear that I cannot use MetaMask's eth_decrypt to decrypt messages encrypted with Ethereum public key. I didn't realize that until I saw the issue on MetaMask github github.com/MetaMask/metamask-extension/issues/… .
    – BlueT
    Mar 25, 2022 at 4:19

1 Answer 1

1

From @Ismael's comment and MetaMask github issue:

  1. The key from MetaMask's eth_getEncryptionPublicKey rpc is NOT the Public Key of my account, but a different key calculated from my private key with the nacl implementation of the X25519_XSalsa20_Poly1305 algorithm, which is different from Ethereum's default algorithm.
  2. MetaMask holds the private key, but it doesn't provide a function to decrypt messages with the private key.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.