Hi, awesome work you're doing on ethers. Just wanted to mention that !
So one thing that was missing for me was the ability to be able to encrypt and decrypt messages off chain
I use bip39 module to generate a mnemonic and then generate a HD wallet with the base path from that using ethers. I then found this module: https://github.com/pubkey/eth-crypto
Wallet compatibility was a bit of an issue, there is no compressed publicKey on the wallet file from ethersjs so I had to unfreeze the object in wallet.js and add the property
In wallet/wallet.js I added
utils.defineProperty(this, 'publicKey', signingKey.publicKey)
in wallet/signing-key.js I removed the '0x' prefix for the publicKey.
utils.defineProperty(this, 'publicKey', keyPair.getPublic(true, 'hex'))
in utils/properties.js I changed writeable to true for defineProperty()
_(Probably not the best way to add it, suggestions welcome)_
function defineProperty(object, name, value) {
Object.defineProperty(object, name, {
enumerable: true,
value: value,
writable: true,
});
}
Et voila, compatible with eth-crypto. I believe this doesn't affect any other ethersJS functionality either, would you be able to confirm this?
Thank you !
We certainly cannot change the writable to true, as that adds a lot of security and safety issues, which is the purpose of it in the first place. ;)
We also cannot remove the 0x. That is a hard requirement I enforce across the board.
I will look more into eth-crypto, and see what can be done, but I don't want to break a fundamental convention in ethers for it.
You could for now, just use the utility function:
const secp256k1 = require("ethers/utils/secp256k1");
let compressed = true;
let publicKey = sec256k1.computePublicKey(someKey, compressed);
Maybe if I exposed the computePublicKey, that would suffice? I exported it in its own file, but didn't re-exported of the utils mainly because I didn't think anyone would need/use it, but it takes in any key (private, public compressed or public uncompressed) and an compressed boolean and returns the public key. I will expose it in the next beta version.
Is there functionality in ethers.js to encrypt/decrypt arbitrary message with public/private key of the account. That functionality would open great number of new usecases.
Currently I'm using
https://github.com/pubkey/eth-crypto
for that.
No currently not, only for encrypting wallets with AES, not messages with something like an ECIES scheme. I will try to rework the encrypt/decrypt functions to work with ethersjs later today and submit a PR if I can get it to work.
Ok so we do not have to change writeableto true , idk why I did that. Probably I forgot to change it back from an earlier approach.
I don't understand why we're adding 0x to the publicKey in ethers though? I always thought uncompressed public keys had 04 as prefix and compressed keys 02 or 03.
I can make it work with 0x04 , 0x02 or 0x03 but then it's just an extra step of removing the prefix from the keys for use with any EC library.
@ricmoo I can't seem to find secp256k1 in utils?
I need this function https://github.com/cryptocoinjs/secp256k1-node/blob/master/API.md#publickeyconvertbuffer-publickey--boolean-compressed--true---buffer
Oh, you should probably be looking at the v4 (TypeScript branch) too, it will be made the production branch this week. Otherwise, you are right all the secp256k1 stuff in the v3 (master branch) is in SigningKeys. But it is a lot easier to deal with in the updated branch.
I have been considering exposing ECDH, since it is pretty much free given we already include all of secp256k1 anyways. What’s an extra 9 lines of code? :)
All hex strings in ethers require a 0x. It marks a clear divide between “this is intended to be binary data” and “this is some string, I hope the function does the right thing with it”.
Since sometimes it is required or changes how something is interpreted, it is safer and easier to make it always required, otherwise you end up having to look up in the documentation every time you use a call whether it is required in a given case. And if functions didn’t return it, then you would have to check and possibly add it yourself.
Sorry, this answer is longer (and doesn’t really get to the meat) than I intended; maybe I’ll just stick with, it didn’t used to be that way and it caused lots of bugs and a lot of extra random code weird place the developer had to deal with. Web3 has something like stripPrefix and addPrefix, that you will see scattered throughout an application, and in general a lot of dangerous auto-magic guessing of what a string was intended to be... The ethers library aims to be more explicit. I should add a rationale section to the hex string section of the documentation.
Does eth-crypto accept Uint8Array for keys? You can use utils.arrayify(hexString).
Hey,
This is what I came up with without using the eth-crypto wrapper and just using secp256k1 and eccrypto.
Eccrypto is small but unmaintained https://github.com/bitchan/eccrypto/blob/master/index.js
The only change that would need to happen is adding pubKey to wallet.
utils.defineProperty(this, 'publicKey', signingKey.publicKey)
import {encrypt, decrypt} from 'eccrypto'
import { publicKeyConvert} from 'secp256k1';
/**
* @method encryptWithPublicKey
* @param {String} pubKey - Compressed 33byte public key starting with 0x03 or 0x02
* @param {Object} message - message object to encrypt
* @returns {String} - Stringified cipher
*/
function encryptWithPublicKey(pubKey, message) {
pubKey = pubKey.substring(2)
pubKey = publicKeyConvert(new Buffer(pubKey, 'hex'), false).toString('hex')
pubKey = new Buffer(pubKey, 'hex')
return encrypt(
pubKey,
Buffer(message)
).then(encryptedBuffers => {
const cipher = {
iv: encryptedBuffers.iv.toString('hex'),
ephemPublicKey: encryptedBuffers.ephemPublicKey.toString('hex'),
ciphertext: encryptedBuffers.ciphertext.toString('hex'),
mac: encryptedBuffers.mac.toString('hex')
};
// use compressed key because it's smaller
const compressedKey = publicKeyConvert(new Buffer(cipher.ephemPublicKey, 'hex'), true).toString('hex')
const ret = Buffer.concat([
new Buffer(cipher.iv, 'hex'), // 16bit
new Buffer(compressedKey, 'hex'), // 33bit
new Buffer(cipher.mac, 'hex'), // 32bit
new Buffer(cipher.ciphertext, 'hex') // var bit
]).toString('hex')
return ret
});
}
/**
* @method decryptWithPrivateKey decript an EC publicKey encrypted message with the associated private key
* @param {String} privateKey - the privatekey to decrypt with, including '0x' prefix
* @param {String} encrypted - the stringified cipher to decrypt
* @returns {Object} - the decrypted message
*/
function decryptWithPrivateKey(privateKey, encrypted) {
const buf = new Buffer(encrypted, 'hex');
encrypted = {
iv: buf.toString('hex', 0, 16),
ephemPublicKey: buf.toString('hex', 16, 49),
mac: buf.toString('hex', 49, 81),
ciphertext: buf.toString('hex', 81, buf.length)
};
// decompress publicKey
encrypted.ephemPublicKey = publicKeyConvert(new Buffer(encrypted.ephemPublicKey, 'hex'), false).toString('hex')
const twoStripped = privateKey.substring(2)
const encryptedBuffer = {
iv: new Buffer(encrypted.iv, 'hex'),
ephemPublicKey: new Buffer(encrypted.ephemPublicKey, 'hex'),
ciphertext: new Buffer(encrypted.ciphertext, 'hex'),
mac: new Buffer(encrypted.mac, 'hex')
};
return decrypt(
new Buffer(twoStripped, 'hex'),
encryptedBuffer
).then(decryptedBuffer => decryptedBuffer.toString());
}
You may also find the SigningKey.prototype.computeSharedSecret useful as well, as it computes the ECDH for you, which was introduced in 4.0.0-beta.14. It allows for far more secure schemes to encrypt and decrypt data.
I'm closing this now, as I think the ECDH solves most of these problems? A sub-package in v5 will be an extension for adding native support for encrypting and decrypting messages. Please feel free to re-open if you still have questions though.
Thanks! :)
Yes, I should have followed up on this to mention that ECDH was more than sufficient for my use case for now.
Thank you!
@ricmoo did the message encryption/decryption sub-package ever get built?
@kyriediculous what was the final iteration for your code to encrypt/decrypt arbitrary messages? Is the code above still what you're currently using?
You can find necessary API functions here https://docs.ethers.io/v5/api/utils/signing-key/#SigningKey
Most helpful comment
Is there functionality in ethers.js to encrypt/decrypt arbitrary message with public/private key of the account. That functionality would open great number of new usecases.
Currently I'm using
https://github.com/pubkey/eth-crypto
for that.