Can I encrypt a file with multiple public address such that each of them can decrypt the file using their private keys? Thus achieving a system to securely store file on IPFS which can be accessed by only a selected number of personals. How exactly can I achieve this? How can I achieve this in Solidity and JS.
2 Answers
You should create a symmetric key for encryption and encrypt IPFS file with it, and then encrypt this symmetric key with different public keys so that each recipient can decrypt the symmetric key and in turn decrypt IPFS file.
This script lets you generate a PGP KeyPair from an Ethereum Private Key using npm install openpgp
:
const openpgp = require('openpgp');
const ethereumPrivateKey = 'e9ee206196eb26b92b937b83b7a44974b1c3d995b9e61ca65d2c5760a97250af';
const buffKey = Buffer.from(ethereumPrivateKey);
const privateKey = openpgp.util.Uint8Array_to_str(buffKey);
const options = {
curve: 'secp256k1',
userIds: {name: 'Tiago', email: '[email protected]'},
numBits: 2048,
material: {
key: privateKey,
}
};
openpgp.generateKey(options).then(function(key) {
console.log(key.privateKeyArmored);
}).catch(function(error) {
console.log(error);
});
We are going to assume you generated two different keys key1
and key2
using that method and now you would like to encrypt some data with key1.publicKeyArmored
and key2.publicKeyArmored
(Stack syntax highlighting is pretty bad right now, but it works, I promise) :
const openpgp = require('openpgp');
openpgp.initWorker({
path: 'openpgp.worker.js'
});
const keys = [`-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: OpenPGP.js v4.4.10
Comment: https://openpgpjs.org
xk8EXJ8SUxMFK4EEAAoCAwQRvMNE18QPFsBLIw0yWHfNZUVtOyPVXplzqhPy
DslTKxjj3PE2s15vbVDNNOpmmzgIlRUEbyYFcDfhdCCXXscxzRtUaWFnbyA8
Z2l0aHViQHRsb3JpYXRvLmNvbT7CdwQQEwgAHwUCXJ8SUwYLCQcIAwIEFQgK
AgMWAgECGQECGwMCHgEACgkQnCIDAkHMdM8czwEA2JOPG6zaNsa1D9M2a3U8
gl5R8S1F1lOELJtlVSH1tWAA/RRPREctK8pxkCKs5NuICnx9FIQ1WUsyulG+
08xZY5CZzlMEXJ8SUxIFK4EEAAoCAwQ9aBjd/ntB+uqOUZgpzaNdtE6O2cQd
l3DnZ3k2tEngIWKve+5d7woMd6idEdk2AkAvyhmqYMeiaCkI3LuNzjr3AwEI
B8JhBBgTCAAJBQJcnxJTAhsMAAoJEJwiAwJBzHTP1IABAMNLXoEyrW7+5/ty
bG8u5uIGP/0M+EDnELpDpUWMBa8dAP9MtynJ0qHbov6so3+9jA5Nug0s7Ywq
8NDcfsq8u367Tg==
=67V7
-----END PGP PUBLIC KEY BLOCK-----`,
`-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: OpenPGP.js v4.4.10
Comment: https://openpgpjs.org
xk8EXJ8SKRMFK4EEAAoCAwTlZPr0xaQXWs3+2Q2at+OgE1VevOeyq0TXOMw2
BhFUTOF49sY3mJ9tA0jtHYeRG/BwXJlO7ycC6pc8a04LHeYWzRtUaWFnbyA8
Z2l0aHViQHRsb3JpYXRvLmNvbT7CdwQQEwgAHwUCXJ8SKQYLCQcIAwIEFQgK
AgMWAgECGQECGwMCHgEACgkQ0ZFQ34vxIBgpCgD9Hbz/W+mQDLFAUulQ0pgO
UBKuNdPraXpU419kIXZzl3QA/RTaX8cbQTHiuDy63tiwRZqZSiiuBiYZaZ0G
S9p+k2UWzlMEXJ8SKRIFK4EEAAoCAwQu4w+JVtCeXxuaZIWg+r6FvjURLMqN
gK9czwBYZlsWHZgXiiAI7aVgDtoPVcm1wYByMV7Ut6eUvzaDOdTwS/KrAwEI
B8JhBBgTCAAJBQJcnxIpAhsMAAoJENGRUN+L8SAYsoYA/ilPvNJmlbpC187c
LJXqKxdppUlb6DISS8gq4Moin/BdAQClCGwqtyovA7Et7MjkCxiW0P5xiaTn
qENz9YJFSRBEAQ==
=bUeW
-----END PGP PUBLIC KEY BLOCK-----`
];
openpgp.key.readArmored(keys[0])
.then((firstKey) => {
openpgp.key.readArmored(keys[1])
.then((secondKey) => {
const options = {
message: openpgp.message.fromText(`I just discovered the keys to Satoshi's Wallet! Meet @ Liberty Sq., 20:30pm`), // You can also call fromFile()!
publicKeys: [firstKey.keys[0], secondKey.keys[0]]
};
openpgp.encrypt(options).then(ciphertext => {
encrypted = ciphertext.data;
console.log(encrypted);
});
});
});
Now I assume that Ethereum comes into play? You want like to verify the PGP Public Keys with Ethereum Holders in Solidity. You can do it from two "keys", you can ask them to sign:
- The PGP Fingerprint (way smaller, but you have to get the full key somewhere)
- The PGP Public Key (bigger, so more expansive)
First you need your participants to sign the one of the above keys:
const ethers = require('ethers');
// The message...
const message = PGP_PUBLIC_KEY||PG_FINGERPRINT;
// Sign the message (this could also come from eth_signMessage)
const wallet = new ethers.Wallet(privateKey);
const signature = wallet.signMessage(message)
// Split the signature into its r, s and v (Solidity's format)
const sig = ethers.utils.splitSignature(signature);
// Call the contract with the message and signature
const promise = contract.verifyMessage(message, sig.v, sig.r, sig.s);
promise.then(function(signer) {
// Check the computed signer matches the actual signer
console.log(signer === wallet.address);
});
Now you just have to create your Solidity Contract to verify a certain message:
contract Verifier {
// Returns the address that signed a given string message
function verifyString(string message, uint8 v, bytes32 r,
bytes32 s) public pure returns (address signer) {
// The message header; we will fill in the length next
string memory header = "\x19Ethereum Signed Message:\n000000";
uint256 lengthOffset;
uint256 length;
assembly {
// The first word of a string is its length
length := mload(message)
// The beginning of the base-10 message length in the prefix
lengthOffset := add(header, 57)
}
// Maximum length we support
require(length <= 999999);
// The length of the message's length in base-10
uint256 lengthLength = 0;
// The divisor to get the next left-most message length digit
uint256 divisor = 100000;
// Move one digit of the message length to the right at a time
while (divisor != 0) {
// The place value at the divisor
uint256 digit = length / divisor;
if (digit == 0) {
// Skip leading zeros
if (lengthLength == 0) {
divisor /= 10;
continue;
}
}
// Found a non-zero digit or non-leading zero digit
lengthLength++;
// Remove this digit from the message length's current value
length -= digit * divisor;
// Shift our base-10 divisor over
divisor /= 10;
// Convert the digit to its ASCII representation (man ascii)
digit += 0x30;
// Move to the next character and write the digit
lengthOffset++;
assembly {
mstore8(lengthOffset, digit)
}
}
// The null string requires exactly 1 zero (unskip 1 leading 0)
if (lengthLength == 0) {
lengthLength = 1 + 0x19 + 1;
} else {
lengthLength += 1 + 0x19;
}
// Truncate the tailing zeros from the header
assembly {
mstore(header, lengthLength)
}
// Perform the elliptic curve recover operation
bytes32 check = keccak256(header, message);
return ecrecover(check, v, r, s);
}
}
Solidity's part with the help of RicMoo
-
numBits: 2048
could (and should) have been 4096 Commented Mar 30, 2019 at 7:51 -
This doesn't answer the question, OP wants to use public keys to encrypt Commented Apr 4, 2019 at 11:22
-
The only way it seems possible to me is doing so through PGP Public Keys. If they derive from the same private keys than an ethereum address, it makes them a public key to me. Commented Apr 4, 2019 at 11:37