2022-06-17 10:48:15 +10:00
|
|
|
import "@ethersproject/shims";
|
|
|
|
|
|
|
|
import { PublicKeyMessage } from "./messaging/wire";
|
|
|
|
import { generatePrivateKey, getPublicKey, utils } from "js-waku";
|
|
|
|
import { PublicKeyContentTopic } from "./waku";
|
2022-08-20 18:10:05 +10:00
|
|
|
import { keccak256, _TypedDataEncoder, recoverAddress } from "ethers/lib/utils";
|
2022-06-17 10:48:15 +10:00
|
|
|
import { equals } from "uint8arrays/equals";
|
2022-08-29 15:16:41 +10:00
|
|
|
import type { TypedDataSigner } from "@ethersproject/abstract-signer";
|
2022-06-17 10:48:15 +10:00
|
|
|
|
|
|
|
export const PublicKeyMessageEncryptionKey = utils.hexToBytes(
|
2022-08-20 18:10:05 +10:00
|
|
|
keccak256(utils.utf8ToBytes(PublicKeyContentTopic))
|
2022-06-17 10:48:15 +10:00
|
|
|
);
|
|
|
|
|
|
|
|
export interface KeyPair {
|
|
|
|
privateKey: Uint8Array;
|
|
|
|
publicKey: Uint8Array;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-08-08 12:08:54 +10:00
|
|
|
* Generate new encryption key pair.
|
2022-06-17 10:48:15 +10:00
|
|
|
*/
|
|
|
|
export async function generateEncryptionKeyPair(): Promise<KeyPair> {
|
|
|
|
const privateKey = generatePrivateKey();
|
|
|
|
const publicKey = getPublicKey(privateKey);
|
|
|
|
return { privateKey, publicKey };
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sign the encryption public key with Web3. This can then be published to let other
|
|
|
|
* users know to use this encryption public key to encrypt messages for the
|
|
|
|
* Ethereum Address holder.
|
|
|
|
*/
|
|
|
|
export async function createPublicKeyMessage(
|
|
|
|
address: string,
|
|
|
|
encryptionPublicKey: Uint8Array,
|
2022-08-29 15:16:41 +10:00
|
|
|
signer: TypedDataSigner
|
2022-06-17 10:48:15 +10:00
|
|
|
): Promise<PublicKeyMessage> {
|
|
|
|
const signature = await signEncryptionKey(
|
|
|
|
encryptionPublicKey,
|
|
|
|
address,
|
2022-08-29 15:16:41 +10:00
|
|
|
signer
|
2022-06-17 10:48:15 +10:00
|
|
|
);
|
|
|
|
|
|
|
|
console.log("Asking wallet to sign Public Key Message");
|
|
|
|
console.log("Public Key Message signed");
|
|
|
|
|
|
|
|
return new PublicKeyMessage({
|
|
|
|
encryptionPublicKey: encryptionPublicKey,
|
|
|
|
ethAddress: utils.hexToBytes(address),
|
|
|
|
signature: utils.hexToBytes(signature),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildMsgParams(encryptionPublicKey: Uint8Array, fromAddress: string) {
|
2022-08-20 18:10:05 +10:00
|
|
|
return {
|
2022-06-17 10:48:15 +10:00
|
|
|
domain: {
|
|
|
|
name: "Ethereum Private Message over Waku",
|
|
|
|
version: "1",
|
|
|
|
},
|
2022-08-29 15:16:41 +10:00
|
|
|
value: {
|
2022-06-17 10:48:15 +10:00
|
|
|
message:
|
|
|
|
"By signing this message you certify that messages addressed to `ownerAddress` must be encrypted with `encryptionPublicKey`",
|
|
|
|
encryptionPublicKey: utils.bytesToHex(encryptionPublicKey),
|
|
|
|
ownerAddress: fromAddress,
|
|
|
|
},
|
|
|
|
// Refers to the keys of the *types* object below.
|
|
|
|
primaryType: "PublishEncryptionPublicKey",
|
|
|
|
types: {
|
|
|
|
PublishEncryptionPublicKey: [
|
|
|
|
{ name: "message", type: "string" },
|
|
|
|
{ name: "encryptionPublicKey", type: "string" },
|
|
|
|
{ name: "ownerAddress", type: "string" },
|
|
|
|
],
|
|
|
|
},
|
2022-08-20 18:10:05 +10:00
|
|
|
};
|
2022-06-17 10:48:15 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
export async function signEncryptionKey(
|
|
|
|
encryptionPublicKey: Uint8Array,
|
|
|
|
fromAddress: string,
|
2022-08-29 15:16:41 +10:00
|
|
|
signer: TypedDataSigner
|
2022-06-17 10:48:15 +10:00
|
|
|
): Promise<Uint8Array> {
|
2022-08-29 15:16:41 +10:00
|
|
|
const { domain, types, value } = buildMsgParams(
|
|
|
|
encryptionPublicKey,
|
|
|
|
fromAddress
|
2022-08-20 18:10:05 +10:00
|
|
|
);
|
2022-06-17 10:48:15 +10:00
|
|
|
|
2022-08-29 15:16:41 +10:00
|
|
|
const result = await signer._signTypedData(domain, types, value);
|
2022-06-17 10:48:15 +10:00
|
|
|
|
|
|
|
console.log("TYPED SIGNED:" + JSON.stringify(result));
|
|
|
|
|
|
|
|
return utils.hexToBytes(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Validate that the Encryption Public Key was signed by the holder of the given Ethereum address.
|
|
|
|
*/
|
|
|
|
export function validatePublicKeyMessage(msg: PublicKeyMessage): boolean {
|
2022-08-29 15:16:41 +10:00
|
|
|
const { domain, types, value } = buildMsgParams(
|
2022-08-20 18:10:05 +10:00
|
|
|
msg.encryptionPublicKey,
|
|
|
|
"0x" + utils.bytesToHex(msg.ethAddress)
|
|
|
|
);
|
|
|
|
|
|
|
|
try {
|
2022-08-29 15:16:41 +10:00
|
|
|
const hash = _TypedDataEncoder.hash(domain, types, value);
|
|
|
|
|
2022-08-20 18:10:05 +10:00
|
|
|
const recovered = recoverAddress(hash, msg.signature);
|
|
|
|
console.log("Recovered", recovered);
|
|
|
|
console.log("ethAddress", "0x" + utils.bytesToHex(msg.ethAddress));
|
2022-06-17 10:48:15 +10:00
|
|
|
|
2022-08-29 15:16:41 +10:00
|
|
|
return equals(utils.hexToBytes(recovered.toLowerCase()), msg.ethAddress);
|
2022-08-20 18:10:05 +10:00
|
|
|
} catch (e) {
|
2022-08-29 15:16:41 +10:00
|
|
|
console.error("Could not recover public key from signature", e);
|
2022-08-20 18:10:05 +10:00
|
|
|
return false;
|
|
|
|
}
|
2022-06-17 10:48:15 +10:00
|
|
|
}
|