Skip to content

feat: add encryption capabilities #102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 2, 2025
Merged

feat: add encryption capabilities #102

merged 6 commits into from
Apr 2, 2025

Conversation

mirceanis
Copy link
Contributor

@mirceanis mirceanis commented Dec 12, 2024

What's new

This preinstalled snap allows an instance of MetaMask to sign messages using an SRP derived key.
We really need an SRP bound encryption system too.

This PR introduces an ERC1024 compatible encryption system, to be compatible1 with the existing but deprecated eth_getEncryptionPublicKey/eth_decrypt implementation in MetaMask.

Example usage

Alice, the user of this functionality would call the getPublicEncryptionKey method and obtain a hex encoded X25519 public key, which they would broadcast to Bob.

const encryptionPublicKeyHex: string = await window.ethereum.request({
  method: "wallet_invokeSnap",
  params: {
    snapId: "npm:@metamask/message-signing-snap",
    request: {
      method: "getEncryptionPublicKey",
    },
  },
});

Bob would then use a piece of code similar to:

import { encrypt } from `@metamask/eth-sig-util`;

const encrypted = encrypt({
  encryptionPublicKeyHex,
  data,
  version: 'x25519-xsalsa20-poly1305',
});

.. and obtain an object such as:

// encrypted data from Bob
{
  version: 'x25519-xsalsa20-poly1305',
  nonce: '8jFlr2cYdkyBIookw6akE8lA0f+odanN',
  ephemPublicKey: 'UOPWFRaPdY6kKDEoM/ovCBCT2p4PSx6MMdOgNzA2gC0=',
  ciphertext: '2UNvHmxnmOHtI9vhf7gfeD/J7Q/q6vqjEQqY',
}

Bob would send this object to Alice who can then call the decryptMessage method of the snap to get the message.

const decryptedMessage: string = await window.ethereum.request({
  method: "wallet_invokeSnap",
  params: {
    snapId: "npm:@metamask/message-signing-snap",
    request: {
      method: "decryptMessage",
      params: {
        version: "x25519-xsalsa20-poly1305",
        nonce: "8jFlr2cYdkyBIookw6akE8lA0f+odanN",
        ephemPublicKey: "UOPWFRaPdY6kKDEoM/ovCBCT2p4PSx6MMdOgNzA2gC0=",
        ciphertext: "2UNvHmxnmOHtI9vhf7gfeD/J7Q/q6vqjEQqY",
      },
    },
  },
}

Implementation details

The encryption secret key is derived from the SRP-bound entropy using an additional salt, to avoid key reuse between signing and encryption. The entropy vs origin logic is the same as for the signing functionality. Each origin gets different keys and origins can't decrypt each other's messages.

The encryption layer is implemented using the pre-existing @noble/ciphers/@noble/curves dependencies.
The tests for the encryption layer represent the vast majority of additions to this PR.
Technically, the new ERC1024 implementation can be replaced with @metamask/eth-sig-util, however, that library is designed for nodejs and throws some warnings when used in a snap.
We could also pull that out as an independent library.

Related issues

Fixes #101
link back to jira


Footnotes

  1. The compatibility is on the data format, not on the actual public key, so messages encrypted for the snap will not be decryptable by any of the MM accounts, nor vice-versa.

@mirceanis mirceanis force-pushed the 101-add-encryption branch 2 times, most recently from b4e09ae to fca207d Compare December 12, 2024 17:58
@mirceanis mirceanis marked this pull request as ready for review December 13, 2024 11:49
@mirceanis mirceanis requested a review from a team as a code owner December 13, 2024 11:49
@mirceanis
Copy link
Contributor Author

@metamaskbot publish-preview

@mirceanis mirceanis self-assigned this Dec 17, 2024
Copy link
Contributor

@mathieuartu mathieuartu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty damn good to me! Waiting for your feedback on mobile tests, and also left some comments!

@mirceanis
Copy link
Contributor Author

mirceanis commented Apr 2, 2025

Waiting for your feedback on mobile tests

For the moment this snap can't be tested on our mobile app as we don't have SIP30 support there and some methods and params are missing.

BUT I did run some benchmarks of the ERC1024 code directly in the mobile app:

   Starting benchmark using 1000 iterations
   generate 1000 private keys
   generate private keys took 27ms

   compute the 1000 public keys
   computing 1000 public keys took 11224ms; 11.224ms per key

   Encrypting 1000 messages
   Mean encryption time: 23.549ms
   Median encryption time: 23ms
   Minimum encryption time: 22ms
   Maximum encryption time: 53ms
   90th percentile encryption time: 24ms
   
   Decrypting 1000 messages
   Mean decryption time: 12.602ms
   Median decryption time: 12ms
   Max decryption time: 30ms
   Min decryption time: 11ms
   90th percentile decryption time: 13ms

These numbers are from a modern android device (pixel 9).

Edit: I also later tested on a Nokia 6 (2017), and the numbers for encryption/decryption are about 10 times larger. For the old phone, the react-native experience in general is very slow as every user action takes about 2-4 seconds to process so I would argue we're still in relatively acceptable ranges with the performance of the encryption.

Copy link
Contributor

@mathieuartu mathieuartu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

@mirceanis mirceanis merged commit c8c36e0 into main Apr 2, 2025
14 checks passed
@mirceanis mirceanis deleted the 101-add-encryption branch April 2, 2025 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Proposal] add encryption capabilities
4 participants