Merge pull request #75 from waku-org/upgrade-js-waku-eth-pm

This commit is contained in:
fryorcraken.eth 2022-08-29 16:06:04 +10:00 committed by GitHub
commit 878b37e761
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1869 additions and 1932 deletions

View File

@ -5,7 +5,8 @@
- Private Messaging - Private Messaging
- React/TypeScript - React/TypeScript
- Waku Light Push - Waku Light Push
- Signature with Web3 - Waku Filter
- Signature with Web3 (EIP-712, sign typed data)
- Asymmetric Encryption - Asymmetric Encryption
- Symmetric Encryption - Symmetric Encryption

View File

@ -1,60 +0,0 @@
const webpack = require('webpack');
module.exports = {
dev: (config) => {
// Override webpack 5 config from react-scripts to load polyfills
if (!config.resolve) config.resolve = {};
if (!config.resolve.fallback) config.resolve.fallback = {};
Object.assign(config.resolve.fallback, {
buffer: require.resolve('buffer'),
crypto: false,
stream: require.resolve('stream-browserify'),
});
if (!config.plugins) config.plugins = [];
config.plugins.push(
new webpack.DefinePlugin({
'process.env.ENV': JSON.stringify('dev'),
})
);
config.plugins.push(
new webpack.ProvidePlugin({
process: 'process/browser.js',
Buffer: ['buffer', 'Buffer'],
})
);
if (!config.ignoreWarnings) config.ignoreWarnings = [];
config.ignoreWarnings.push(/Failed to parse source map/);
return config;
},
prod: (config) => {
// Override webpack 5 config from react-scripts to load polyfills
if (!config.resolve) config.resolve = {};
if (!config.resolve.fallback) config.resolve.fallback = {};
Object.assign(config.resolve.fallback, {
buffer: require.resolve('buffer'),
crypto: false,
stream: require.resolve('stream-browserify'),
});
if (!config.plugins) config.plugins = [];
config.plugins.push(
new webpack.DefinePlugin({
'process.env.ENV': JSON.stringify('prod'),
})
);
config.plugins.push(
new webpack.ProvidePlugin({
process: 'process/browser.js',
Buffer: ['buffer', 'Buffer'],
})
);
if (!config.ignoreWarnings) config.ignoreWarnings = [];
config.ignoreWarnings.push(/Failed to parse source map/);
return config;
},
};

View File

@ -2,25 +2,24 @@
"name": "eth-pm", "name": "eth-pm",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"homepage": "/examples/eth-pm", "homepage": "/eth-pm",
"dependencies": { "dependencies": {
"@ethersproject/providers": "^5.6.8", "@ethersproject/abstract-signer": "5.7.0",
"@ethersproject/providers": "5.7.0",
"@material-ui/core": "^4.12.3", "@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2", "@material-ui/icons": "^4.11.2",
"eth-sig-util": "^3.0.1", "ethers": "5.7.0",
"ethers": "^5.5.4",
"fontsource-roboto": "^4.0.0", "fontsource-roboto": "^4.0.0",
"js-waku": "^0.24.0", "js-waku": "0.24.0-cdd0752",
"protobufjs": "^6.11.2", "protobufjs": "^6.11.2",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"uint8arrays": "^3.1.0" "uint8arrays": "^3.1.0"
}, },
"scripts": { "scripts": {
"start": "cra-webpack-rewired start", "start": "react-scripts start",
"build": "run-s build:*", "build": "react-scripts build",
"build:react": "cra-webpack-rewired build", "eject": "react-scripts eject",
"eject": "cra-webpack-rewired eject",
"fix": "run-s fix:*", "fix": "run-s fix:*",
"test": "run-s build test:*", "test": "run-s build test:*",
"test:lint": "eslint src --ext .ts --ext .tsx", "test:lint": "eslint src --ext .ts --ext .tsx",
@ -55,16 +54,11 @@
"@types/node": "^17.0.19", "@types/node": "^17.0.19",
"@types/react": "^17.0.39", "@types/react": "^17.0.39",
"@types/react-dom": "^17.0.11", "@types/react-dom": "^17.0.11",
"assert": "^2.0.0",
"buffer": "^6.0.3",
"cra-webpack-rewired": "^1.0.1",
"cspell": "^6.0.0", "cspell": "^6.0.0",
"eslint": "^8.9.0", "eslint": "^8.9.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"process": "^0.11.10",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"stream-browserify": "^3.0.0",
"typescript": "^4.5.5" "typescript": "^4.5.5"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,7 @@ import {
} from "./waku"; } from "./waku";
import { Web3Provider } from "@ethersproject/providers/src.ts/web3-provider"; import { Web3Provider } from "@ethersproject/providers/src.ts/web3-provider";
import ConnectWallet from "./ConnectWallet"; import ConnectWallet from "./ConnectWallet";
import { waku_message } from "js-waku";
const theme = createMuiTheme({ const theme = createMuiTheme({
palette: { palette: {
@ -77,10 +78,10 @@ function App() {
const [messages, setMessages] = useState<Message[]>([]); const [messages, setMessages] = useState<Message[]>([]);
const [address, setAddress] = useState<string>(); const [address, setAddress] = useState<string>();
const [peerStats, setPeerStats] = useState<{ const [peerStats, setPeerStats] = useState<{
relayPeers: number; filterPeers: number;
lightPushPeers: number; lightPushPeers: number;
}>({ }>({
relayPeers: 0, filterPeers: 0,
lightPushPeers: 0, lightPushPeers: 0,
}); });
@ -88,15 +89,15 @@ function App() {
// Waku initialization // Waku initialization
useEffect(() => { useEffect(() => {
if (waku) return; (async () => {
initWaku() if (waku) return;
.then((_waku) => {
console.log("waku: ready"); const _waku = await initWaku();
setWaku(_waku); console.log("waku: ready");
}) setWaku(_waku);
.catch((e) => { })().catch((e) => {
console.error("Failed to initiate Waku", e); console.error("Failed to initiate Waku", e);
}); });
}, [waku]); }, [waku]);
useEffect(() => { useEffect(() => {
@ -108,16 +109,34 @@ function App() {
setPublicKeys setPublicKeys
); );
waku.relay.addDecryptionKey(PublicKeyMessageEncryptionKey); let unsubscribe: undefined | (() => Promise<void>);
waku.relay.addObserver(observerPublicKeyMessage, [PublicKeyContentTopic]);
waku.filter.addDecryptionKey(PublicKeyMessageEncryptionKey, {
method: waku_message.DecryptionMethod.Symmetric,
contentTopics: [PublicKeyContentTopic],
});
waku.filter
.subscribe(observerPublicKeyMessage, [PublicKeyContentTopic])
.then(
(_unsubscribe) => {
console.log("subscribed to ", PublicKeyContentTopic);
unsubscribe = _unsubscribe;
},
(e) => {
console.error("Failed to subscribe", e);
}
);
return function cleanUp() { return function cleanUp() {
if (!waku) return; if (!waku) return;
waku.filter.deleteDecryptionKey(PublicKeyMessageEncryptionKey);
waku.relay.deleteDecryptionKey(PublicKeyMessageEncryptionKey); if (typeof unsubscribe === "undefined") return;
waku.relay.deleteObserver(observerPublicKeyMessage, [ unsubscribe().then(
PublicKeyContentTopic, () => {
]); console.log("unsubscribed to ", PublicKeyContentTopic);
},
(e) => console.error("Failed to unsubscribe", e)
);
}; };
}, [waku, address]); }, [waku, address]);
@ -125,13 +144,16 @@ function App() {
if (!waku) return; if (!waku) return;
if (!encryptionKeyPair) return; if (!encryptionKeyPair) return;
waku.relay.addDecryptionKey(encryptionKeyPair.privateKey); waku.filter.addDecryptionKey(encryptionKeyPair.privateKey, {
method: waku_message.DecryptionMethod.Asymmetric,
contentTopics: [PrivateMessageContentTopic],
});
return function cleanUp() { return function cleanUp() {
if (!waku) return; if (!waku) return;
if (!encryptionKeyPair) return; if (!encryptionKeyPair) return;
waku.relay.deleteDecryptionKey(encryptionKeyPair.privateKey); waku.filter.deleteDecryptionKey(encryptionKeyPair.privateKey);
}; };
}, [waku, encryptionKeyPair]); }, [waku, encryptionKeyPair]);
@ -146,16 +168,23 @@ function App() {
address address
); );
waku.relay.addObserver(observerPrivateMessage, [ let unsubscribe: undefined | (() => Promise<void>);
PrivateMessageContentTopic,
]); waku.filter
.subscribe(observerPrivateMessage, [PrivateMessageContentTopic])
.then(
(_unsubscribe) => {
unsubscribe = _unsubscribe;
},
(e) => {
console.error("Failed to subscribe", e);
}
);
return function cleanUp() { return function cleanUp() {
if (!waku) return; if (!waku) return;
if (!observerPrivateMessage) return; if (typeof unsubscribe === "undefined") return;
waku.relay.deleteObserver(observerPrivateMessage, [ unsubscribe().catch((e) => console.error("Failed to unsubscribe", e));
PrivateMessageContentTopic,
]);
}; };
}, [waku, address, encryptionKeyPair]); }, [waku, address, encryptionKeyPair]);
@ -163,15 +192,12 @@ function App() {
if (!waku) return; if (!waku) return;
const interval = setInterval(async () => { const interval = setInterval(async () => {
let lightPushPeers = 0; const lightPushPeers = await waku.store.peers();
// eslint-disable-next-line @typescript-eslint/no-unused-vars const filterPeers = await waku.filter.peers();
for await (const _peer of waku.store.peers) {
lightPushPeers++;
}
setPeerStats({ setPeerStats({
relayPeers: waku.relay.getPeers().size, filterPeers: filterPeers.length,
lightPushPeers, lightPushPeers: lightPushPeers.length,
}); });
}, 1000); }, 1000);
return () => clearInterval(interval); return () => clearInterval(interval);
@ -199,7 +225,7 @@ function App() {
/> />
</IconButton> </IconButton>
<Typography className={classes.peers} aria-label="connected-peers"> <Typography className={classes.peers} aria-label="connected-peers">
Peers: {peerStats.relayPeers} relay, {peerStats.lightPushPeers}{" "} Peers: {peerStats.filterPeers} filter, {peerStats.lightPushPeers}{" "}
light push light push
</Typography> </Typography>
<Typography variant="h6" className={classes.title}> <Typography variant="h6" className={classes.title}>
@ -228,7 +254,7 @@ function App() {
address={address} address={address}
EncryptionKeyPair={encryptionKeyPair} EncryptionKeyPair={encryptionKeyPair}
waku={waku} waku={waku}
providerRequest={provider?.provider?.request} signer={provider?.getSigner()}
/> />
</fieldset> </fieldset>
<fieldset> <fieldset>

View File

@ -8,21 +8,20 @@ import {
import { PublicKeyMessage } from "./messaging/wire"; import { PublicKeyMessage } from "./messaging/wire";
import { WakuMessage, Waku } from "js-waku"; import { WakuMessage, Waku } from "js-waku";
import { PublicKeyContentTopic } from "./waku"; import { PublicKeyContentTopic } from "./waku";
import type { TypedDataSigner } from "@ethersproject/abstract-signer";
interface Props { interface Props {
EncryptionKeyPair: KeyPair | undefined; EncryptionKeyPair: KeyPair | undefined;
waku: Waku | undefined; waku: Waku | undefined;
address: string | undefined; address: string | undefined;
providerRequest: signer: TypedDataSigner | undefined;
| ((request: { method: string; params?: Array<any> }) => Promise<any>)
| undefined;
} }
export default function BroadcastPublicKey({ export default function BroadcastPublicKey({
EncryptionKeyPair, EncryptionKeyPair,
waku, waku,
address, address,
providerRequest, signer,
}: Props) { }: Props) {
const [publicKeyMsg, setPublicKeyMsg] = useState<PublicKeyMessage>(); const [publicKeyMsg, setPublicKeyMsg] = useState<PublicKeyMessage>();
@ -30,7 +29,7 @@ export default function BroadcastPublicKey({
if (!EncryptionKeyPair) return; if (!EncryptionKeyPair) return;
if (!address) return; if (!address) return;
if (!waku) return; if (!waku) return;
if (!providerRequest) return; if (!signer) return;
if (publicKeyMsg) { if (publicKeyMsg) {
encodePublicKeyWakuMessage(publicKeyMsg) encodePublicKeyWakuMessage(publicKeyMsg)
@ -43,11 +42,7 @@ export default function BroadcastPublicKey({
console.log("Failed to encode Public Key Message in Waku Message", e); console.log("Failed to encode Public Key Message in Waku Message", e);
}); });
} else { } else {
createPublicKeyMessage( createPublicKeyMessage(address, EncryptionKeyPair.publicKey, signer)
address,
EncryptionKeyPair.publicKey,
providerRequest
)
.then((msg) => { .then((msg) => {
setPublicKeyMsg(msg); setPublicKeyMsg(msg);
encodePublicKeyWakuMessage(msg) encodePublicKeyWakuMessage(msg)
@ -77,7 +72,7 @@ export default function BroadcastPublicKey({
variant="contained" variant="contained"
color="primary" color="primary"
onClick={broadcastPublicKey} onClick={broadcastPublicKey}
disabled={!EncryptionKeyPair || !waku || !address || !providerRequest} disabled={!EncryptionKeyPair || !waku || !address || !signer}
> >
Broadcast Encryption Public Key Broadcast Encryption Public Key
</Button> </Button>

View File

@ -11,17 +11,14 @@ interface Props {
} }
export default function ConnectWallet({ setAddress, setProvider }: Props) { export default function ConnectWallet({ setAddress, setProvider }: Props) {
const connectWallet = () => { const connectWallet = async () => {
try { try {
window.ethereum const provider = new ethers.providers.Web3Provider(window.ethereum);
.request({ method: "eth_requestAccounts" }) const accounts = await provider.send("eth_requestAccounts", []);
.then((accounts: string[]) => { setAddress(accounts[0]);
const _provider = new ethers.providers.Web3Provider(window.ethereum); setProvider(provider);
setAddress(accounts[0]);
setProvider(_provider);
});
} catch (e) { } catch (e) {
console.error("No web3 provider available"); console.error("No web3 provider available", e);
} }
}; };

View File

@ -2,13 +2,13 @@ import "@ethersproject/shims";
import { PublicKeyMessage } from "./messaging/wire"; import { PublicKeyMessage } from "./messaging/wire";
import { generatePrivateKey, getPublicKey, utils } from "js-waku"; import { generatePrivateKey, getPublicKey, utils } from "js-waku";
import * as sigUtil from "eth-sig-util";
import { PublicKeyContentTopic } from "./waku"; import { PublicKeyContentTopic } from "./waku";
import { keccak256 } from "ethers/lib/utils"; import { keccak256, _TypedDataEncoder, recoverAddress } from "ethers/lib/utils";
import { equals } from "uint8arrays/equals"; import { equals } from "uint8arrays/equals";
import type { TypedDataSigner } from "@ethersproject/abstract-signer";
export const PublicKeyMessageEncryptionKey = utils.hexToBytes( export const PublicKeyMessageEncryptionKey = utils.hexToBytes(
keccak256(Buffer.from(PublicKeyContentTopic, "utf-8")) keccak256(utils.utf8ToBytes(PublicKeyContentTopic))
); );
export interface KeyPair { export interface KeyPair {
@ -33,15 +33,12 @@ export async function generateEncryptionKeyPair(): Promise<KeyPair> {
export async function createPublicKeyMessage( export async function createPublicKeyMessage(
address: string, address: string,
encryptionPublicKey: Uint8Array, encryptionPublicKey: Uint8Array,
providerRequest: (request: { signer: TypedDataSigner
method: string;
params?: Array<any>;
}) => Promise<any>
): Promise<PublicKeyMessage> { ): Promise<PublicKeyMessage> {
const signature = await signEncryptionKey( const signature = await signEncryptionKey(
encryptionPublicKey, encryptionPublicKey,
address, address,
providerRequest signer
); );
console.log("Asking wallet to sign Public Key Message"); console.log("Asking wallet to sign Public Key Message");
@ -55,12 +52,12 @@ export async function createPublicKeyMessage(
} }
function buildMsgParams(encryptionPublicKey: Uint8Array, fromAddress: string) { function buildMsgParams(encryptionPublicKey: Uint8Array, fromAddress: string) {
return JSON.stringify({ return {
domain: { domain: {
name: "Ethereum Private Message over Waku", name: "Ethereum Private Message over Waku",
version: "1", version: "1",
}, },
message: { value: {
message: message:
"By signing this message you certify that messages addressed to `ownerAddress` must be encrypted with `encryptionPublicKey`", "By signing this message you certify that messages addressed to `ownerAddress` must be encrypted with `encryptionPublicKey`",
encryptionPublicKey: utils.bytesToHex(encryptionPublicKey), encryptionPublicKey: utils.bytesToHex(encryptionPublicKey),
@ -69,35 +66,26 @@ function buildMsgParams(encryptionPublicKey: Uint8Array, fromAddress: string) {
// Refers to the keys of the *types* object below. // Refers to the keys of the *types* object below.
primaryType: "PublishEncryptionPublicKey", primaryType: "PublishEncryptionPublicKey",
types: { types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
],
PublishEncryptionPublicKey: [ PublishEncryptionPublicKey: [
{ name: "message", type: "string" }, { name: "message", type: "string" },
{ name: "encryptionPublicKey", type: "string" }, { name: "encryptionPublicKey", type: "string" },
{ name: "ownerAddress", type: "string" }, { name: "ownerAddress", type: "string" },
], ],
}, },
}); };
} }
export async function signEncryptionKey( export async function signEncryptionKey(
encryptionPublicKey: Uint8Array, encryptionPublicKey: Uint8Array,
fromAddress: string, fromAddress: string,
providerRequest: (request: { signer: TypedDataSigner
method: string;
params?: Array<any>;
from?: string;
}) => Promise<any>
): Promise<Uint8Array> { ): Promise<Uint8Array> {
const msgParams = buildMsgParams(encryptionPublicKey, fromAddress); const { domain, types, value } = buildMsgParams(
encryptionPublicKey,
fromAddress
);
const result = await providerRequest({ const result = await signer._signTypedData(domain, types, value);
method: "eth_signTypedData_v4",
params: [fromAddress, msgParams],
from: fromAddress,
});
console.log("TYPED SIGNED:" + JSON.stringify(result)); console.log("TYPED SIGNED:" + JSON.stringify(result));
@ -108,18 +96,21 @@ export async function signEncryptionKey(
* Validate that the Encryption Public Key was signed by the holder of the given Ethereum address. * Validate that the Encryption Public Key was signed by the holder of the given Ethereum address.
*/ */
export function validatePublicKeyMessage(msg: PublicKeyMessage): boolean { export function validatePublicKeyMessage(msg: PublicKeyMessage): boolean {
const recovered = sigUtil.recoverTypedSignature_v4({ const { domain, types, value } = buildMsgParams(
data: JSON.parse( msg.encryptionPublicKey,
buildMsgParams( "0x" + utils.bytesToHex(msg.ethAddress)
msg.encryptionPublicKey, );
"0x" + utils.bytesToHex(msg.ethAddress)
)
),
sig: "0x" + utils.bytesToHex(msg.signature),
});
console.log("Recovered", recovered); try {
console.log("ethAddress", "0x" + utils.bytesToHex(msg.ethAddress)); const hash = _TypedDataEncoder.hash(domain, types, value);
return equals(utils.hexToBytes(recovered), msg.ethAddress); const recovered = recoverAddress(hash, msg.signature);
console.log("Recovered", recovered);
console.log("ethAddress", "0x" + utils.bytesToHex(msg.ethAddress));
return equals(utils.hexToBytes(recovered.toLowerCase()), msg.ethAddress);
} catch (e) {
console.error("Could not recover public key from signature", e);
return false;
}
} }

View File

@ -133,7 +133,7 @@ function sendMessage(
.push(msg) .push(msg)
.then((res) => { .then((res) => {
console.log("Message sent", res); console.log("Message sent", res);
callback(res ? res.isSuccess : false); callback(res?.isSuccess ?? false);
}) })
.catch((e) => { .catch((e) => {
console.error("Failed to send message", e); console.error("Failed to send message", e);

View File

@ -29,9 +29,7 @@ export class PublicKeyMessage {
return PublicKeyMessage.Type.encode(message).finish(); return PublicKeyMessage.Type.encode(message).finish();
} }
public static decode( public static decode(bytes: Uint8Array): PublicKeyMessage | undefined {
bytes: Uint8Array | Buffer
): PublicKeyMessage | undefined {
const payload = PublicKeyMessage.Type.decode( const payload = PublicKeyMessage.Type.decode(
bytes bytes
) as unknown as PublicKeyMessagePayload; ) as unknown as PublicKeyMessagePayload;
@ -80,7 +78,7 @@ export class PrivateMessage {
return PrivateMessage.Type.encode(message).finish(); return PrivateMessage.Type.encode(message).finish();
} }
public static decode(bytes: Uint8Array | Buffer): PrivateMessage | undefined { public static decode(bytes: Uint8Array): PrivateMessage | undefined {
const payload = PrivateMessage.Type.decode( const payload = PrivateMessage.Type.decode(
bytes bytes
) as unknown as PrivateMessagePayload; ) as unknown as PrivateMessagePayload;

View File

@ -1,26 +1,35 @@
import { Dispatch, SetStateAction } from "react"; import { Dispatch, SetStateAction } from "react";
import { utils, Waku, WakuMessage } from "js-waku"; import {
Protocols,
utils,
waitForRemotePeer,
Waku,
WakuMessage,
} from "js-waku";
import { PrivateMessage, PublicKeyMessage } from "./messaging/wire"; import { PrivateMessage, PublicKeyMessage } from "./messaging/wire";
import { validatePublicKeyMessage } from "./crypto"; import { validatePublicKeyMessage } from "./crypto";
import { Message } from "./messaging/Messages"; import { Message } from "./messaging/Messages";
import { equals } from "uint8arrays/equals"; import { equals } from "uint8arrays/equals";
import { createWaku } from "js-waku/lib/create_waku";
import { PeerDiscoveryStaticPeers } from "js-waku/lib/peer_discovery_static_list";
import {
getPredefinedBootstrapNodes,
Fleet,
} from "js-waku/lib/predefined_bootstrap_nodes";
export const PublicKeyContentTopic = "/eth-pm/1/public-key/proto"; export const PublicKeyContentTopic = "/eth-pm/1/public-key/proto";
export const PrivateMessageContentTopic = "/eth-pm/1/private-message/proto"; export const PrivateMessageContentTopic = "/eth-pm/1/private-message/proto";
export async function initWaku(): Promise<Waku> { export async function initWaku(): Promise<Waku> {
const waku = await Waku.create({ bootstrap: { default: true } }); const waku = await createWaku({
libp2p: {
// Wait to be connected to at least one peer peerDiscovery: [
await new Promise((resolve, reject) => { new PeerDiscoveryStaticPeers(getPredefinedBootstrapNodes(Fleet.Test)),
// If we are not connected to any peer within 10sec let's just reject ],
// As we are not implementing connection management in this example },
setTimeout(reject, 10000);
waku.libp2p.connectionManager.on("peer:connect", () => {
resolve(null);
});
}); });
await waku.start();
await waitForRemotePeer(waku, [Protocols.Filter, Protocols.LightPush]);
return waku; return waku;
} }