Merge pull request #221 from status-im/waku-msg-version-1

This commit is contained in:
Franck Royer 2021-07-09 16:35:47 +10:00 committed by GitHub
commit d0958c1c57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1377 additions and 211 deletions

View File

@ -9,6 +9,7 @@
"bitauth",
"bufbuild",
"cimg",
"ciphertext",
"circleci",
"codecov",
"commitlint",
@ -17,7 +18,9 @@
"Dlazy",
"Dout",
"Dscore",
"ecies",
"editorconfig",
"ephem",
"esnext",
"ethersproject",
"execa",
@ -55,9 +58,12 @@
"protobuf",
"protoc",
"reactjs",
"recid",
"rlnrelay",
"sandboxed",
"secio",
"seckey",
"secp",
"staticnode",
"statusim",
"submodule",

View File

@ -10,12 +10,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- `WakuRelay.deleteObserver` to allow removal of observers, useful when a React component add observers when mounting and needs to delete it when unmounting.
- Keep alive feature that pings host regularly, reducing the chance of connections being dropped due to idle.
Can be disabled or default frequency (10s) can be changed when calling `Waku.create`.
Can be disabled or default frequency (10s) can be changed when calling `Waku.create`.
- New `lib/utils` module for easy, dependency-less hex/bytes conversions.
### Changed
- **Breaking**: Auto select peer if none provided for store and light push protocols.
- Upgrade to `libp2p@0.31.7` and `libp2p-gossipsub@0.10.0` to avoid `TextEncoder` errors in ReactJS tests.
- Disable keep alive by default as latest nim-waku release does not support ping protocol.
- **Breaking**: Optional parameters for `WakuMessage.fromBytes` and `WakuMessage.fromUtf8String` are now passed in a single `Options` object.
- **Breaking**: `WakuMessage` static functions are now async to allow for encryption and decryption.
- **Breaking**: `WakuMessage` constructor is now private, `from*` and `decode*` function should be used.
- `WakuMessage` version 1 is partially supported, enabling asymmetrical encryption and signature of messages;
this can be done by passing keys to `WakuMessage.from*` and `WakuMessage.decode*` methods.
Note: this is not yet compatible with nim-waku.
### Fixed
- Disable `keepAlive` if set to `0`.

View File

@ -104,7 +104,10 @@ export default async function startChat(): Promise<void> {
rl.prompt();
const chatMessage = ChatMessage.fromUtf8String(new Date(), nick, line);
const msg = WakuMessage.fromBytes(chatMessage.encode(), ChatContentTopic);
const msg = await WakuMessage.fromBytes(chatMessage.encode(), {
contentTopic: ChatContentTopic,
timestamp: new Date(),
});
if (opts.lightPush) {
await waku.lightPush.push(msg);
} else {

View File

@ -25,18 +25,30 @@ export default function BroadcastPublicKey({
if (!waku) return;
if (publicKeyMsg) {
const wakuMsg = encodePublicKeyWakuMessage(publicKeyMsg);
waku.lightPush.push(wakuMsg).catch((e) => {
console.error('Failed to send Public Key Message', e);
});
encodePublicKeyWakuMessage(publicKeyMsg)
.then((wakuMsg) => {
waku.lightPush.push(wakuMsg).catch((e) => {
console.error('Failed to send Public Key Message', e);
});
})
.catch((e) => {
console.log('Failed to encode Public Key Message in Waku Message');
});
} else {
createPublicKeyMessage(signer, ethDmKeyPair.publicKey)
.then((msg) => {
setPublicKeyMsg(msg);
const wakuMsg = encodePublicKeyWakuMessage(msg);
waku.lightPush.push(wakuMsg).catch((e) => {
console.error('Failed to send Public Key Message', e);
});
encodePublicKeyWakuMessage(msg)
.then((wakuMsg) => {
waku.lightPush.push(wakuMsg).catch((e) => {
console.error('Failed to send Public Key Message', e);
});
})
.catch((e) => {
console.log(
'Failed to encode Public Key Message in Waku Message'
);
});
})
.catch((e) => {
console.error('Failed to create public key message', e);
@ -56,9 +68,11 @@ export default function BroadcastPublicKey({
);
}
function encodePublicKeyWakuMessage(
async function encodePublicKeyWakuMessage(
publicKeyMessage: PublicKeyMessage
): WakuMessage {
): Promise<WakuMessage> {
const payload = publicKeyMessage.encode();
return WakuMessage.fromBytes(payload, PublicKeyContentTopic);
return await WakuMessage.fromBytes(payload, {
contentTopic: PublicKeyContentTopic,
});
}

View File

@ -4,7 +4,7 @@ import * as EthCrypto from 'eth-crypto';
import { ethers } from 'ethers';
import { Signer } from '@ethersproject/abstract-signer';
import { DirectMessage, PublicKeyMessage } from './messaging/wire';
import { byteArrayToHex, hexToBuf } from './utils';
import { hexToBuf, equalByteArrays, bufToHex } from 'js-waku/lib/utils';
export interface KeyPair {
privateKey: string;
@ -49,15 +49,7 @@ export function validatePublicKeyMessage(msg: PublicKeyMessage): boolean {
const formattedMsg = formatPublicKeyForSignature(msg.ethDmPublicKey);
try {
const sigAddress = ethers.utils.verifyMessage(formattedMsg, msg.signature);
const sigAddressBytes = hexToBuf(sigAddress);
// Compare the actual byte arrays instead of strings that may differ in casing or prefixing.
const cmp = sigAddressBytes.compare(new Buffer(msg.ethAddress));
console.log(
`Buffer comparison result: ${cmp} for (signature address, message address)`,
sigAddressBytes,
msg.ethAddress
);
return cmp === 0;
return equalByteArrays(sigAddress, msg.ethAddress);
} catch (e) {
console.log(
'Failed to verify signature for Public Key Message',
@ -77,7 +69,7 @@ export function validatePublicKeyMessage(msg: PublicKeyMessage): boolean {
*/
function formatPublicKeyForSignature(ethDmPublicKey: Uint8Array): string {
return JSON.stringify({
ethDmPublicKey: byteArrayToHex(ethDmPublicKey),
ethDmPublicKey: bufToHex(ethDmPublicKey),
});
}

View File

@ -1,4 +1,5 @@
import { KeyPair } from '../crypto';
import { bufToHex, hexToBuf } from 'js-waku/lib/utils';
/**
* Save keypair to storage, encrypted with password
@ -10,9 +11,9 @@ export async function saveKeyPairToStorage(
const { salt, iv, cipher } = await encryptKey(ethDmKeyPair, password);
const data = {
salt: new Buffer(salt).toString('hex'),
iv: new Buffer(iv).toString('hex'),
cipher: new Buffer(cipher).toString('hex'),
salt: bufToHex(salt),
iv: bufToHex(iv),
cipher: bufToHex(cipher),
};
localStorage.setItem('cipherEthDmKeyPair', JSON.stringify(data));
@ -28,9 +29,9 @@ export async function loadKeyPairFromStorage(
if (!str) return;
const data = JSON.parse(str);
const salt = new Buffer(data.salt, 'hex');
const iv = new Buffer(data.iv, 'hex');
const cipher = new Buffer(data.cipher, 'hex');
const salt = hexToBuf(data.salt);
const iv = hexToBuf(data.iv);
const cipher = hexToBuf(data.cipher);
return await decryptKey(salt, iv, cipher, password);
}

View File

@ -117,7 +117,9 @@ async function encodeEncryptedWakuMessage(
};
const payload = encode(directMsg);
return WakuMessage.fromBytes(payload, DirectMessageContentTopic);
return WakuMessage.fromBytes(payload, {
contentTopic: DirectMessageContentTopic,
});
}
function sendMessage(

View File

@ -3,7 +3,7 @@ import { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku';
import { decode, DirectMessage, PublicKeyMessage } from './messaging/wire';
import { decryptMessage, validatePublicKeyMessage } from './crypto';
import { Message } from './messaging/Messages';
import { byteArrayToHex, equalByteArrays } from './utils';
import { bufToHex, equalByteArrays } from 'js-waku/lib/utils';
export const PublicKeyContentTopic = '/eth-dm/1/public-key/proto';
export const DirectMessageContentTopic = '/eth-dm/1/direct-message/json';
@ -41,7 +41,7 @@ export function handlePublicKeyMessage(
if (!msg.payload) return;
const publicKeyMsg = PublicKeyMessage.decode(msg.payload);
if (!publicKeyMsg) return;
const ethDmPublicKey = byteArrayToHex(publicKeyMsg.ethDmPublicKey);
const ethDmPublicKey = bufToHex(publicKeyMsg.ethDmPublicKey);
console.log(ethDmPublicKey, myAddress);
if (myAddress && equalByteArrays(publicKeyMsg.ethAddress, myAddress)) return;
@ -50,7 +50,7 @@ export function handlePublicKeyMessage(
if (res) {
setter((prevPks: Map<string, string>) => {
prevPks.set(byteArrayToHex(publicKeyMsg.ethAddress), ethDmPublicKey);
prevPks.set(bufToHex(publicKeyMsg.ethAddress), ethDmPublicKey);
return new Map(prevPks);
});
}

View File

@ -55,11 +55,10 @@ async function handleMessage(
} else {
const timestamp = new Date();
const chatMessage = ChatMessage.fromUtf8String(timestamp, nick, message);
const wakuMsg = WakuMessage.fromBytes(
chatMessage.encode(),
ChatContentTopic,
timestamp
);
const wakuMsg = await WakuMessage.fromBytes(chatMessage.encode(), {
contentTopic: ChatContentTopic,
timestamp,
});
return messageSender(wakuMsg);
}
}

446
package-lock.json generated
View File

@ -10,8 +10,10 @@
"dependencies": {
"axios": "^0.21.1",
"debug": "^4.3.1",
"ecies-parity": "^0.1.1",
"it-concat": "^2.0.0",
"it-length-prefixed": "^5.0.2",
"js-sha3": "^0.8.0",
"libp2p": "^0.31.7",
"libp2p-gossipsub": "^0.10.0",
"libp2p-mplex": "^0.10.3",
@ -19,6 +21,7 @@
"libp2p-tcp": "^0.15.4",
"libp2p-websockets": "^0.15.6",
"multiaddr": "^9.0.1",
"secp256k1": "^4.0.2",
"ts-proto": "^1.79.7",
"uuid": "^8.3.2"
},
@ -30,6 +33,7 @@
"@types/google-protobuf": "^3.7.4",
"@types/mocha": "^8.2.2",
"@types/node": "^14.14.31",
"@types/secp256k1": "^4.0.2",
"@types/tail": "^2.0.0",
"@types/uuid": "^8.3.0",
"@typescript-eslint/eslint-plugin": "^4.0.1",
@ -3586,6 +3590,15 @@
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="
},
"node_modules/@types/secp256k1": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.2.tgz",
"integrity": "sha512-QMg+9v0bbNJ2peLuHRWxzmy0HRJIG6gFZNhaRSp7S3ggSbCCxiqQB2/ybvhXyhHOCequpNkrx7OavNhrWOsW0A==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/tail": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@types/tail/-/tail-2.2.0.tgz",
@ -4545,6 +4558,15 @@
"file-uri-to-path": "1.0.0"
}
},
"node_modules/bip66": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
"integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=",
"optional": true,
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/bl": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-5.0.0.tgz",
@ -4617,6 +4639,20 @@
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
"dev": true
},
"node_modules/browserify-aes": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
"optional": true,
"dependencies": {
"buffer-xor": "^1.0.3",
"cipher-base": "^1.0.0",
"create-hash": "^1.1.0",
"evp_bytestokey": "^1.0.3",
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/browserslist": {
"version": "4.16.6",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz",
@ -4676,6 +4712,12 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"node_modules/buffer-xor": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
"integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
"optional": true
},
"node_modules/bufio": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/bufio/-/bufio-1.0.7.tgz",
@ -4934,6 +4976,16 @@
"npm": ">=3.0.0"
}
},
"node_modules/cipher-base": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
"optional": true,
"dependencies": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/class-is": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz",
@ -6544,6 +6596,33 @@
"node": ">=10"
}
},
"node_modules/create-hash": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
"optional": true,
"dependencies": {
"cipher-base": "^1.0.1",
"inherits": "^2.0.1",
"md5.js": "^1.3.4",
"ripemd160": "^2.0.1",
"sha.js": "^2.4.0"
}
},
"node_modules/create-hmac": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
"optional": true,
"dependencies": {
"cipher-base": "^1.0.3",
"create-hash": "^1.1.0",
"inherits": "^2.0.1",
"ripemd160": "^2.0.0",
"safe-buffer": "^5.0.1",
"sha.js": "^2.4.8"
}
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
@ -7191,6 +7270,20 @@
"node": ">=6"
}
},
"node_modules/drbg.js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz",
"integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=",
"optional": true,
"dependencies": {
"browserify-aes": "^1.0.6",
"create-hash": "^1.1.2",
"create-hmac": "^1.1.4"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@ -7205,6 +7298,71 @@
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
},
"node_modules/ecies-parity": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ecies-parity/-/ecies-parity-0.1.1.tgz",
"integrity": "sha512-eq95uTiNkQYr9orLJZVD/aGfcvRiC6tORZvB2JiieEYX4OwbJV74ahDhDm9d/4NkO7lPoi19DWcEQ8SKh+kQ6A==",
"hasInstallScript": true,
"dependencies": {
"acorn": "7.1.0",
"elliptic": "6.5.1",
"es6-promise": "^4.2.4",
"nan": "2.14.0"
},
"optionalDependencies": {
"secp256k1": "3.7.1"
}
},
"node_modules/ecies-parity/node_modules/acorn": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
"integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ecies-parity/node_modules/elliptic": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz",
"integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==",
"dependencies": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.0"
}
},
"node_modules/ecies-parity/node_modules/nan": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
"integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
},
"node_modules/ecies-parity/node_modules/secp256k1": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.7.1.tgz",
"integrity": "sha512-1cf8sbnRreXrQFdH6qsg2H71Xw91fCCS9Yp021GnUNJzWJS/py96fS4lHbnTnouLp08Xj6jBoBB6V78Tdbdu5g==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
"bindings": "^1.5.0",
"bip66": "^1.1.5",
"bn.js": "^4.11.8",
"create-hash": "^1.2.0",
"drbg.js": "^1.0.1",
"elliptic": "^6.4.1",
"nan": "^2.14.0",
"safe-buffer": "^5.1.2"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -7390,6 +7548,11 @@
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
"dev": true
},
"node_modules/es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"node_modules/es6-promisify": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.1.1.tgz",
@ -7866,6 +8029,16 @@
"node": ">=0.8.x"
}
},
"node_modules/evp_bytestokey": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
"integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
"optional": true,
"dependencies": {
"md5.js": "^1.3.4",
"safe-buffer": "^5.1.1"
}
},
"node_modules/exec-sh": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz",
@ -9928,6 +10101,40 @@
"node": ">=0.10.0"
}
},
"node_modules/hash-base": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
"integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
"optional": true,
"dependencies": {
"inherits": "^2.0.4",
"readable-stream": "^3.6.0",
"safe-buffer": "^5.2.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/hash-base/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"optional": true
},
"node_modules/hash.js": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
@ -12400,6 +12607,17 @@
"node": ">= 8.16.2"
}
},
"node_modules/md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
"optional": true,
"dependencies": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1",
"safe-buffer": "^5.1.2"
}
},
"node_modules/memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
@ -16227,6 +16445,16 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ripemd160": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
"optional": true,
"dependencies": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1"
}
},
"node_modules/rsvp": {
"version": "4.8.5",
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
@ -16792,6 +17020,19 @@
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
"peer": true
},
"node_modules/sha.js": {
"version": "2.4.11",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
"optional": true,
"dependencies": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
},
"bin": {
"sha.js": "bin.js"
}
},
"node_modules/shallow-clone": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
@ -22021,6 +22262,15 @@
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="
},
"@types/secp256k1": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.2.tgz",
"integrity": "sha512-QMg+9v0bbNJ2peLuHRWxzmy0HRJIG6gFZNhaRSp7S3ggSbCCxiqQB2/ybvhXyhHOCequpNkrx7OavNhrWOsW0A==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/tail": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@types/tail/-/tail-2.2.0.tgz",
@ -22736,6 +22986,15 @@
"file-uri-to-path": "1.0.0"
}
},
"bip66": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
"integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=",
"optional": true,
"requires": {
"safe-buffer": "^5.0.1"
}
},
"bl": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-5.0.0.tgz",
@ -22802,6 +23061,20 @@
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
"dev": true
},
"browserify-aes": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
"optional": true,
"requires": {
"buffer-xor": "^1.0.3",
"cipher-base": "^1.0.0",
"create-hash": "^1.1.0",
"evp_bytestokey": "^1.0.3",
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"browserslist": {
"version": "4.16.6",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz",
@ -22837,6 +23110,12 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"buffer-xor": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
"integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
"optional": true
},
"bufio": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/bufio/-/bufio-1.0.7.tgz",
@ -23030,6 +23309,16 @@
"uint8arrays": "^2.1.3"
}
},
"cipher-base": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
"optional": true,
"requires": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"class-is": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz",
@ -24307,6 +24596,33 @@
"yaml": "^1.10.0"
}
},
"create-hash": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
"optional": true,
"requires": {
"cipher-base": "^1.0.1",
"inherits": "^2.0.1",
"md5.js": "^1.3.4",
"ripemd160": "^2.0.1",
"sha.js": "^2.4.0"
}
},
"create-hmac": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
"optional": true,
"requires": {
"cipher-base": "^1.0.3",
"create-hash": "^1.1.0",
"inherits": "^2.0.1",
"ripemd160": "^2.0.0",
"safe-buffer": "^5.0.1",
"sha.js": "^2.4.8"
}
},
"create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
@ -24807,6 +25123,17 @@
}
}
},
"drbg.js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz",
"integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=",
"optional": true,
"requires": {
"browserify-aes": "^1.0.6",
"create-hash": "^1.1.2",
"create-hmac": "^1.1.4"
}
},
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@ -24823,6 +25150,60 @@
}
}
},
"ecies-parity": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ecies-parity/-/ecies-parity-0.1.1.tgz",
"integrity": "sha512-eq95uTiNkQYr9orLJZVD/aGfcvRiC6tORZvB2JiieEYX4OwbJV74ahDhDm9d/4NkO7lPoi19DWcEQ8SKh+kQ6A==",
"requires": {
"acorn": "7.1.0",
"elliptic": "6.5.1",
"es6-promise": "^4.2.4",
"nan": "2.14.0",
"secp256k1": "3.7.1"
},
"dependencies": {
"acorn": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
"integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ=="
},
"elliptic": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz",
"integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==",
"requires": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.0"
}
},
"nan": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
"integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
},
"secp256k1": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.7.1.tgz",
"integrity": "sha512-1cf8sbnRreXrQFdH6qsg2H71Xw91fCCS9Yp021GnUNJzWJS/py96fS4lHbnTnouLp08Xj6jBoBB6V78Tdbdu5g==",
"optional": true,
"requires": {
"bindings": "^1.5.0",
"bip66": "^1.1.5",
"bn.js": "^4.11.8",
"create-hash": "^1.2.0",
"drbg.js": "^1.0.1",
"elliptic": "^6.4.1",
"nan": "^2.14.0",
"safe-buffer": "^5.1.2"
}
}
}
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -24978,6 +25359,11 @@
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
"dev": true
},
"es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"es6-promisify": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.1.1.tgz",
@ -25338,6 +25724,16 @@
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
},
"evp_bytestokey": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
"integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
"optional": true,
"requires": {
"md5.js": "^1.3.4",
"safe-buffer": "^5.1.1"
}
},
"exec-sh": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz",
@ -26899,6 +27295,25 @@
}
}
},
"hash-base": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
"integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
"optional": true,
"requires": {
"inherits": "^2.0.4",
"readable-stream": "^3.6.0",
"safe-buffer": "^5.2.0"
},
"dependencies": {
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"optional": true
}
}
},
"hash.js": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
@ -28899,6 +29314,17 @@
"integrity": "sha512-BJXxkuIfJchcXOJWTT2DOL+yFWifFv2yGYOUzvXg8Qz610QKw+sHCvTMYwA+qWGhlA2uivBezChZ/pBy1tWdkQ==",
"dev": true
},
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
"optional": true,
"requires": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1",
"safe-buffer": "^5.1.2"
}
},
"memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
@ -31889,6 +32315,16 @@
"glob": "^7.1.3"
}
},
"ripemd160": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
"optional": true,
"requires": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1"
}
},
"rsvp": {
"version": "4.8.5",
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
@ -32347,6 +32783,16 @@
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
"peer": true
},
"sha.js": {
"version": "2.4.11",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
"optional": true,
"requires": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"shallow-clone": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",

View File

@ -57,8 +57,10 @@
"dependencies": {
"axios": "^0.21.1",
"debug": "^4.3.1",
"ecies-parity": "^0.1.1",
"it-concat": "^2.0.0",
"it-length-prefixed": "^5.0.2",
"js-sha3": "^0.8.0",
"libp2p": "^0.31.7",
"libp2p-gossipsub": "^0.10.0",
"libp2p-mplex": "^0.10.3",
@ -66,6 +68,7 @@
"libp2p-tcp": "^0.15.4",
"libp2p-websockets": "^0.15.6",
"multiaddr": "^9.0.1",
"secp256k1": "^4.0.2",
"ts-proto": "^1.79.7",
"uuid": "^8.3.2"
},
@ -77,6 +80,7 @@
"@types/google-protobuf": "^3.7.4",
"@types/mocha": "^8.2.2",
"@types/node": "^14.14.31",
"@types/secp256k1": "^4.0.2",
"@types/tail": "^2.0.0",
"@types/uuid": "^8.3.0",
"@typescript-eslint/eslint-plugin": "^4.0.1",

View File

@ -1,5 +1,7 @@
export { getStatusFleetNodes, Environment, Protocol } from './lib/discover';
export * as utils from './lib/utils';
export { Waku } from './lib/waku';
export { WakuMessage } from './lib/waku_message';

View File

@ -1,10 +1,10 @@
export function byteArrayToHex(bytes: Uint8Array): string {
const buf = new Buffer(bytes);
return buf.toString('hex');
export function hexToBuf(str: string): Buffer {
return Buffer.from(str.replace(/^0x/i, ''), 'hex');
}
export function hexToBuf(str: string): Buffer {
return Buffer.from(str.replace(/0x/, ''), 'hex');
export function bufToHex(buf: Uint8Array | Buffer | ArrayBuffer): string {
const _buf = Buffer.from(buf);
return _buf.toString('hex');
}
export function equalByteArrays(

View File

@ -76,7 +76,7 @@ export class Waku {
this.lightPush = lightPush;
this.keepAliveTimers = {};
const keepAlive = options.keepAlive !== undefined ? options.keepAlive : 0;
const keepAlive = options.keepAlive || 0;
if (keepAlive !== 0) {
libp2p.connectionManager.on('peer:connect', (connection: Connection) => {

View File

@ -33,7 +33,7 @@ describe('Waku Light Push', () => {
});
const messageText = 'Light Push works!';
const message = WakuMessage.fromUtf8String(messageText);
const message = await WakuMessage.fromUtf8String(messageText);
const pushResponse = await waku.lightPush.push(message);
expect(pushResponse?.isSuccess).to.be.true;
@ -73,7 +73,7 @@ describe('Waku Light Push', () => {
const nimPeerId = await nimWaku.getPeerId();
const messageText = 'Light Push works!';
const message = WakuMessage.fromUtf8String(messageText);
const message = await WakuMessage.fromUtf8String(messageText);
const pushResponse = await waku.lightPush.push(message, {
peerId: nimPeerId,

View File

@ -1,29 +0,0 @@
import { expect } from 'chai';
import fc from 'fast-check';
import { WakuMessage } from './waku_message';
describe('Waku Message', function () {
it('Waku message round trip binary serialization', function () {
fc.assert(
fc.property(fc.string(), (s) => {
const msg = WakuMessage.fromUtf8String(s);
const binary = msg.encode();
const actual = WakuMessage.decode(binary);
expect(actual).to.deep.equal(msg);
})
);
});
it('Payload to utf-8', function () {
fc.assert(
fc.property(fc.string(), (s) => {
const msg = WakuMessage.fromUtf8String(s);
const utf8 = msg.payloadAsUtf8;
return utf8 === s;
})
);
});
});

View File

@ -1,85 +0,0 @@
// Ensure that this class matches the proto interface while
import { Reader } from 'protobufjs/minimal';
// Protecting the user from protobuf oddities
import * as proto from '../proto/waku/v2/message';
export const DefaultContentTopic = '/waku/2/default-content/proto';
const DefaultVersion = 0;
export class WakuMessage {
public constructor(public proto: proto.WakuMessage) {}
/**
* Create Message with a utf-8 string as payload.
*/
static fromUtf8String(
utf8: string,
contentTopic: string = DefaultContentTopic,
timestamp: Date = new Date()
): WakuMessage {
const payload = Buffer.from(utf8, 'utf-8');
return new WakuMessage({
payload,
version: DefaultVersion,
contentTopic,
timestamp: timestamp.valueOf() / 1000,
});
}
/**
* Create Message with a byte array as payload.
*/
static fromBytes(
payload: Uint8Array,
contentTopic: string = DefaultContentTopic,
timestamp: Date = new Date()
): WakuMessage {
return new WakuMessage({
payload,
timestamp: timestamp.valueOf() / 1000,
version: DefaultVersion,
contentTopic,
});
}
static decode(bytes: Uint8Array): WakuMessage {
const wakuMsg = proto.WakuMessage.decode(Reader.create(bytes));
return new WakuMessage(wakuMsg);
}
encode(): Uint8Array {
return proto.WakuMessage.encode(this.proto).finish();
}
get payloadAsUtf8(): string {
if (!this.proto.payload) {
return '';
}
return Array.from(this.proto.payload)
.map((char) => {
return String.fromCharCode(char);
})
.join('');
}
get payload(): Uint8Array | undefined {
return this.proto.payload;
}
get contentTopic(): string | undefined {
return this.proto.contentTopic;
}
get version(): number | undefined {
return this.proto.version;
}
get timestamp(): Date | undefined {
if (this.proto.timestamp) {
return new Date(this.proto.timestamp * 1000);
}
return;
}
}

View File

@ -0,0 +1,175 @@
import { expect } from 'chai';
import debug from 'debug';
import fc from 'fast-check';
import TCP from 'libp2p-tcp';
import {
makeLogFileName,
NimWaku,
NOISE_KEY_1,
WakuRelayMessage,
} from '../../test_utils';
import { delay } from '../delay';
import { hexToBuf } from '../utils';
import { Waku } from '../waku';
import { generatePrivateKey, getPublicKey } from './version_1';
import { DefaultContentTopic, WakuMessage } from './index';
const dbg = debug('waku:test:message');
describe('Waku Message', function () {
it('Waku message round trip binary serialization [clear]', async function () {
await fc.assert(
fc.asyncProperty(fc.string(), async (s) => {
const msg = await WakuMessage.fromUtf8String(s);
const binary = msg.encode();
const actual = await WakuMessage.decode(binary);
expect(actual).to.deep.equal(msg);
})
);
});
it('Payload to utf-8', async function () {
await fc.assert(
fc.asyncProperty(fc.string(), async (s) => {
const msg = await WakuMessage.fromUtf8String(s);
const utf8 = msg.payloadAsUtf8;
return utf8 === s;
})
);
});
it('Waku message round trip binary encryption [asymmetric, no signature]', async function () {
await fc.assert(
fc.asyncProperty(
fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
async (payload, privKey) => {
const publicKey = getPublicKey(privKey);
const msg = await WakuMessage.fromBytes(payload, {
encPublicKey: publicKey,
});
const wireBytes = msg.encode();
const actual = await WakuMessage.decode(wireBytes, [privKey]);
expect(actual?.payload).to.deep.equal(payload);
}
)
);
});
it('Waku message round trip binary encryption [asymmetric, signature]', async function () {
await fc.assert(
fc.asyncProperty(
fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
async (payload, sigPrivKey, encPrivKey) => {
const sigPubKey = getPublicKey(sigPrivKey);
const encPubKey = getPublicKey(encPrivKey);
const msg = await WakuMessage.fromBytes(payload, {
encPublicKey: encPubKey,
sigPrivKey: sigPrivKey,
});
const wireBytes = msg.encode();
const actual = await WakuMessage.decode(wireBytes, [encPrivKey]);
expect(actual?.payload).to.deep.equal(payload);
expect(actual?.signaturePublicKey).to.deep.equal(sigPubKey);
}
)
);
});
});
describe.skip('Interop: Nim', function () {
let waku: Waku;
let nimWaku: NimWaku;
beforeEach(async function () {
this.timeout(30_000);
waku = await Waku.create({
staticNoiseKey: NOISE_KEY_1,
libp2p: {
addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] },
modules: { transport: [TCP] },
},
});
const multiAddrWithId = waku.getLocalMultiaddrWithID();
nimWaku = new NimWaku(makeLogFileName(this));
await nimWaku.start({ staticnode: multiAddrWithId, rpcPrivate: true });
await new Promise((resolve) =>
waku.libp2p.pubsub.once('gossipsub:heartbeat', resolve)
);
});
afterEach(async function () {
nimWaku ? nimWaku.stop() : null;
waku ? await waku.stop() : null;
});
it('JS decrypts nim message [asymmetric, no signature]', async function () {
this.timeout(10000);
await delay(200);
const messageText = 'Here is an encrypted message.';
const message: WakuRelayMessage = {
contentTopic: DefaultContentTopic,
payload: Buffer.from(messageText, 'utf-8').toString('hex'),
};
const privateKey = generatePrivateKey();
waku.relay.addDecryptionPrivateKey(privateKey);
const receivedMsgPromise: Promise<WakuMessage> = new Promise((resolve) => {
waku.relay.addObserver(resolve);
});
const publicKey = getPublicKey(privateKey);
dbg('Post message');
await nimWaku.postAsymmetricMessage(message, publicKey);
const receivedMsg = await receivedMsgPromise;
expect(receivedMsg.contentTopic).to.eq(message.contentTopic);
expect(receivedMsg.version).to.eq(1);
expect(receivedMsg.payloadAsUtf8).to.eq(messageText);
});
it('Js encrypts message for nim [asymmetric, no signature]', async function () {
this.timeout(5000);
const keyPair = await nimWaku.getAsymmetricKeyPair();
const privateKey = hexToBuf(keyPair.privateKey);
const publicKey = hexToBuf(keyPair.publicKey);
const messageText = 'This is a message I am going to encrypt';
const message = await WakuMessage.fromUtf8String(messageText, {
encPublicKey: publicKey,
});
await waku.relay.send(message);
let msgs: WakuRelayMessage[] = [];
while (msgs.length === 0) {
await delay(200);
msgs = await nimWaku.getAsymmetricMessages(privateKey);
}
expect(msgs[0].contentTopic).to.equal(message.contentTopic);
expect(hexToBuf(msgs[0].payload).toString('utf-8')).to.equal(messageText);
});
});

View File

@ -0,0 +1,209 @@
// Ensure that this class matches the proto interface while
import { Buffer } from 'buffer';
import debug from 'debug';
import { Reader } from 'protobufjs/minimal';
// Protecting the user from protobuf oddities
import * as proto from '../../proto/waku/v2/message';
import * as version_1 from './version_1';
export const DefaultContentTopic = '/waku/2/default-content/proto';
const DefaultVersion = 0;
const dbg = debug('waku:message');
export interface Options {
contentTopic?: string;
timestamp?: Date;
encPublicKey?: Uint8Array;
sigPrivKey?: Uint8Array;
}
export class WakuMessage {
private constructor(
public proto: proto.WakuMessage,
private _signaturePublicKey?: Uint8Array,
private _signature?: Uint8Array
) {}
/**
* Create Message with a utf-8 string as payload.
*/
static async fromUtf8String(
utf8: string,
opts?: Options
): Promise<WakuMessage> {
const payload = Buffer.from(utf8, 'utf-8');
return WakuMessage.fromBytes(payload, opts);
}
/**
* Create a Waku Message with the given payload.
*
* By default, the payload is kept clear (version 0).
* If `opts.encPublicKey` is passed, the payload is encrypted using
* asymmetric encryption (version 1).
*
* If `opts.sigPrivKey` is passed and version 1 is used, the payload is signed
* before encryption.
*/
static async fromBytes(
payload: Uint8Array,
opts?: Options
): Promise<WakuMessage> {
const { timestamp, contentTopic, encPublicKey, sigPrivKey } = Object.assign(
{ timestamp: new Date(), contentTopic: DefaultContentTopic },
opts ? opts : {}
);
let _payload = payload;
let version = DefaultVersion;
let sig;
if (encPublicKey) {
const enc = version_1.clearEncode(_payload, sigPrivKey);
_payload = await version_1.encryptAsymmetric(enc.payload, encPublicKey);
sig = enc.sig;
version = 1;
}
return new WakuMessage(
{
payload: _payload,
timestamp: timestamp.valueOf() / 1000,
version,
contentTopic,
},
sig?.publicKey,
sig?.signature
);
}
/**
* Decode a byte array into Waku Message.
*
* If the payload is encrypted, then `decPrivateKey` is used for decryption.
*/
static async decode(
bytes: Uint8Array,
decPrivateKeys?: Uint8Array[]
): Promise<WakuMessage | undefined> {
const protoBuf = proto.WakuMessage.decode(Reader.create(bytes));
return WakuMessage.decodeProto(protoBuf, decPrivateKeys);
}
/**
* Decode a Waku Message Protobuf Object into Waku Message.
*
* If the payload is encrypted, then `decPrivateKey` is used for decryption.
*/
static async decodeProto(
protoBuf: proto.WakuMessage,
decPrivateKeys?: Uint8Array[]
): Promise<WakuMessage | undefined> {
if (protoBuf.payload === undefined) {
dbg('Payload is undefined');
return;
}
const payload = protoBuf.payload;
let signaturePublicKey;
let signature;
if (protoBuf.version === 1 && protoBuf.payload) {
if (decPrivateKeys === undefined) {
dbg('Payload is encrypted but no private keys have been provided.');
return;
}
// Returns a bunch of `undefined` and hopefully one decrypted result
const allResults = await Promise.all(
decPrivateKeys.map(async (privateKey) => {
try {
return await version_1.decryptAsymmetric(payload, privateKey);
} catch (e) {
dbg('Failed to decrypt asymmetric message', e);
return;
}
})
);
const isDefined = (dec: Uint8Array | undefined): dec is Uint8Array => {
return !!dec;
};
const decodedResults = allResults.filter(isDefined);
if (decodedResults.length === 0) {
dbg('Failed to decrypt payload.');
return;
}
const dec = decodedResults[0];
const res = await version_1.clearDecode(dec);
if (!res) {
dbg('Failed to decode payload.');
return;
}
Object.assign(protoBuf, { payload: res.payload });
signaturePublicKey = res.sig?.publicKey;
signature = res.sig?.signature;
}
return new WakuMessage(protoBuf, signaturePublicKey, signature);
}
encode(): Uint8Array {
return proto.WakuMessage.encode(this.proto).finish();
}
get payloadAsUtf8(): string {
if (!this.proto.payload) {
return '';
}
return Array.from(this.proto.payload)
.map((char) => {
return String.fromCharCode(char);
})
.join('');
}
get payload(): Uint8Array | undefined {
return this.proto.payload;
}
get contentTopic(): string | undefined {
return this.proto.contentTopic;
}
get version(): number | undefined {
return this.proto.version;
}
get timestamp(): Date | undefined {
if (this.proto.timestamp) {
return new Date(this.proto.timestamp * 1000);
}
return;
}
/**
* The public key used to sign the message.
*
* MAY be present if the message is version 1.
*/
get signaturePublicKey(): Uint8Array | undefined {
return this._signaturePublicKey;
}
/**
* The signature of the message.
*
* MAY be present if the message is version 1.
*/
get signature(): Uint8Array | undefined {
return this._signature;
}
}

View File

@ -0,0 +1,57 @@
import { expect } from 'chai';
import fc from 'fast-check';
import {
clearDecode,
clearEncode,
decryptAsymmetric,
encryptAsymmetric,
getPublicKey,
} from './version_1';
describe('Waku Message Version 1', function () {
it('Sign & Recover', function () {
fc.assert(
fc.property(
fc.uint8Array(),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
(message, privKey) => {
const enc = clearEncode(message, privKey);
const res = clearDecode(enc.payload);
const pubKey = getPublicKey(privKey);
expect(res?.payload).deep.equal(
message,
'Payload was not encrypted then decrypted correctly'
);
expect(res?.sig?.publicKey).deep.equal(
pubKey,
'signature Public key was not recovered from encrypted then decrypted signature'
);
expect(enc?.sig?.publicKey).deep.equal(
pubKey,
'Incorrect signature public key was returned when signing the payload'
);
}
)
);
});
it('Asymmetric encrypt & Decrypt', async function () {
await fc.assert(
fc.asyncProperty(
fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
async (message, privKey) => {
const publicKey = getPublicKey(privKey);
const enc = await encryptAsymmetric(message, publicKey);
const res = await decryptAsymmetric(enc, privKey);
expect(res).deep.equal(message);
}
)
);
});
});

View File

@ -0,0 +1,210 @@
import { Buffer } from 'buffer';
import * as crypto from 'crypto';
import ecies from 'ecies-parity';
import { keccak256 } from 'js-sha3';
import * as secp256k1 from 'secp256k1';
import { hexToBuf } from '../utils';
const FlagsLength = 1;
const FlagMask = 3; // 0011
const IsSignedMask = 4; // 0100
const PaddingTarget = 256;
const SignatureLength = 65;
/**
* Encode the payload pre-encryption.
*
* @internal
* @param messagePayload: The payload to include in the message
* @param sigPrivKey: If set, a signature using this private key is added.
* @returns The encoded payload, ready for encryption using {@link encryptAsymmetric}
* or {@link encryptSymmetric}.
*/
export function clearEncode(
messagePayload: Uint8Array,
sigPrivKey?: Uint8Array
): { payload: Uint8Array; sig?: Signature } {
let envelope = Buffer.from([0]); // No flags
envelope = addPayloadSizeField(envelope, messagePayload);
envelope = Buffer.concat([envelope, messagePayload]);
// Calculate padding:
let rawSize =
FlagsLength +
getSizeOfPayloadSizeField(messagePayload) +
messagePayload.length;
if (sigPrivKey) {
rawSize += SignatureLength;
}
const remainder = rawSize % PaddingTarget;
const paddingSize = PaddingTarget - remainder;
const pad = randomBytes(paddingSize);
if (!validateDataIntegrity(pad, paddingSize)) {
throw new Error('failed to generate random padding of size ' + paddingSize);
}
envelope = Buffer.concat([envelope, pad]);
let sig;
if (sigPrivKey) {
envelope[0] |= IsSignedMask;
const hash = keccak256(envelope);
const s = secp256k1.ecdsaSign(hexToBuf(hash), sigPrivKey);
envelope = Buffer.concat([envelope, s.signature, Buffer.from([s.recid])]);
sig = {
signature: Buffer.from(s.signature),
publicKey: getPublicKey(sigPrivKey),
};
}
return { payload: envelope, sig };
}
export type Signature = {
signature: Uint8Array;
publicKey: Uint8Array;
};
/**
* Decode a decrypted payload.
*
* @internal
*/
export function clearDecode(
message: Uint8Array | Buffer
): { payload: Uint8Array; sig?: Signature } | undefined {
const buf = Buffer.from(message);
let start = 1;
let sig;
const sizeOfPayloadSizeField = buf.readUIntLE(0, 1) & FlagMask;
if (sizeOfPayloadSizeField === 0) return;
const payloadSize = buf.readUIntLE(start, sizeOfPayloadSizeField);
start += sizeOfPayloadSizeField;
const payload = buf.slice(start, start + payloadSize);
const isSigned = (buf.readUIntLE(0, 1) & IsSignedMask) == IsSignedMask;
if (isSigned) {
const signature = getSignature(buf);
const hash = getHash(buf, isSigned);
const publicKey = ecRecoverPubKey(hash, signature);
sig = { signature, publicKey };
}
return { payload, sig };
}
/**
* Proceed with Asymmetric encryption of the data as per [26/WAKU-PAYLOAD](rfc.vac.dev/spec/26/).
* The data MUST be flags | payload-length | payload | [signature].
* The returned result can be set to `WakuMessage.payload`.
*
* @internal
*/
export async function encryptAsymmetric(
data: Uint8Array | Buffer,
publicKey: Uint8Array | Buffer
): Promise<Uint8Array> {
return ecies.encrypt(Buffer.from(publicKey), Buffer.from(data));
}
export async function decryptAsymmetric(
payload: Uint8Array | Buffer,
privKey: Uint8Array | Buffer
): Promise<Uint8Array> {
return ecies.decrypt(Buffer.from(privKey), Buffer.from(payload));
}
/**
* Generate a new private key
*/
export function generatePrivateKey(): Uint8Array {
return randomBytes(32);
}
/**
* Return the public key for the given private key
*/
export function getPublicKey(privateKey: Uint8Array | Buffer): Uint8Array {
return secp256k1.publicKeyCreate(privateKey, false);
}
/**
* Computes the flags & auxiliary-field as per [26/WAKU-PAYLOAD](rfc.vac.dev/spec/26/).
*/
function addPayloadSizeField(msg: Buffer, payload: Uint8Array): Buffer {
const fieldSize = getSizeOfPayloadSizeField(payload);
let field = Buffer.alloc(4);
field.writeUInt32LE(payload.length, 0);
field = field.slice(0, fieldSize);
msg = Buffer.concat([msg, field]);
msg[0] |= fieldSize;
return msg;
}
/**
* Returns the size of the auxiliary-field which in turns contains the payload size
*/
function getSizeOfPayloadSizeField(payload: Uint8Array): number {
let s = 1;
for (let i = payload.length; i >= 256; i /= 256) {
s++;
}
return s;
}
function validateDataIntegrity(
value: Uint8Array,
expectedSize: number
): boolean {
if (value.length !== expectedSize) {
return false;
}
if (
expectedSize > 3 &&
Buffer.from(value).equals(Buffer.alloc(value.length))
) {
return false;
}
return true;
}
function getSignature(message: Buffer): Buffer {
return message.slice(message.length - SignatureLength, message.length);
}
function getHash(message: Buffer, isSigned: boolean): string {
if (isSigned) {
return keccak256(message.slice(0, message.length - SignatureLength));
}
return keccak256(message);
}
function ecRecoverPubKey(messageHash: string, signature: Buffer): Uint8Array {
const recovery = signature.slice(64).readIntBE(0, 1);
return secp256k1.ecdsaRecover(
signature.slice(0, 64),
recovery,
hexToBuf(messageHash),
false
);
}
function randomBytes(length: number): Uint8Array {
if (typeof window !== 'undefined' && window && window.crypto) {
const array = new Uint8Array(length);
window.crypto.getRandomValues(array);
return array;
} else {
return crypto.randomBytes(length);
}
}

View File

@ -79,11 +79,9 @@ describe('Waku Relay', () => {
const messageText = 'JS to JS communication works';
const messageTimestamp = new Date('1995-12-17T03:24:00');
const message = WakuMessage.fromUtf8String(
messageText,
undefined,
messageTimestamp
);
const message = await WakuMessage.fromUtf8String(messageText, {
timestamp: messageTimestamp,
});
const receivedMsgPromise: Promise<WakuMessage> = new Promise(
(resolve) => {
@ -108,8 +106,12 @@ describe('Waku Relay', () => {
const fooMessageText = 'Published on content topic foo';
const barMessageText = 'Published on content topic bar';
const fooMessage = WakuMessage.fromUtf8String(fooMessageText, 'foo');
const barMessage = WakuMessage.fromUtf8String(barMessageText, 'bar');
const fooMessage = await WakuMessage.fromUtf8String(fooMessageText, {
contentTopic: 'foo',
});
const barMessage = await WakuMessage.fromUtf8String(barMessageText, {
contentTopic: 'bar',
});
const receivedBarMsgPromise: Promise<WakuMessage> = new Promise(
(resolve) => {
@ -144,10 +146,9 @@ describe('Waku Relay', () => {
const messageText =
'Published on content topic with added then deleted observer';
const message = WakuMessage.fromUtf8String(
messageText,
'added-then-deleted-observer'
);
const message = await WakuMessage.fromUtf8String(messageText, {
contentTopic: 'added-then-deleted-observer',
});
// The promise **fails** if we receive a message on this observer.
const receivedMsgPromise: Promise<WakuMessage> = new Promise(
@ -204,7 +205,7 @@ describe('Waku Relay', () => {
]);
const messageText = 'Communicating using a custom pubsub topic';
const message = WakuMessage.fromUtf8String(messageText);
const message = await WakuMessage.fromUtf8String(messageText);
const waku2ReceivedMsgPromise: Promise<WakuMessage> = new Promise(
(resolve) => {
@ -275,7 +276,7 @@ describe('Waku Relay', () => {
this.timeout(5000);
const messageText = 'This is a message';
const message = WakuMessage.fromUtf8String(messageText);
const message = await WakuMessage.fromUtf8String(messageText);
await waku.relay.send(message);
@ -294,7 +295,7 @@ describe('Waku Relay', () => {
it('Nim publishes to js', async function () {
this.timeout(5000);
const messageText = 'Here is another message.';
const message = WakuMessage.fromUtf8String(messageText);
const message = await WakuMessage.fromUtf8String(messageText);
const receivedMsgPromise: Promise<WakuMessage> = new Promise(
(resolve) => {
@ -360,7 +361,7 @@ describe('Waku Relay', () => {
this.timeout(30000);
const messageText = 'This is a message';
const message = WakuMessage.fromUtf8String(messageText);
const message = await WakuMessage.fromUtf8String(messageText);
await delay(1000);
await waku.relay.send(message);
@ -381,7 +382,7 @@ describe('Waku Relay', () => {
await delay(200);
const messageText = 'Here is another message.';
const message = WakuMessage.fromUtf8String(messageText);
const message = await WakuMessage.fromUtf8String(messageText);
const receivedMsgPromise: Promise<WakuMessage> = new Promise(
(resolve) => {
@ -463,7 +464,7 @@ describe('Waku Relay', () => {
).to.be.false;
const msgStr = 'Hello there!';
const message = WakuMessage.fromUtf8String(msgStr);
const message = await WakuMessage.fromUtf8String(msgStr);
const waku2ReceivedMsgPromise: Promise<WakuMessage> = new Promise(
(resolve) => {

View File

@ -1,3 +1,4 @@
import debug from 'debug';
import Libp2p from 'libp2p';
import Gossipsub from 'libp2p-gossipsub';
import { AddrInfo, MessageIdFunction } from 'libp2p-gossipsub/src/interfaces';
@ -24,6 +25,8 @@ import { DefaultPubsubTopic, RelayCodec } from './constants';
import { getRelayPeers } from './get_relay_peers';
import { RelayHeartbeat } from './relay_heartbeat';
const dbg = debug('waku:relay');
export { RelayCodec, DefaultPubsubTopic };
/**
@ -60,9 +63,15 @@ export interface GossipOptions {
export class WakuRelay extends Gossipsub {
heartbeat: RelayHeartbeat;
pubsubTopic: string;
/**
* Decryption private keys to use to attempt decryption of incoming messages.
*/
public decPrivateKeys: Set<Uint8Array>;
/**
* observers called when receiving new message.
* Observers under key "" are always called.
* Observers under key `""` are always called.
*/
public observers: {
[contentTopic: string]: Set<(message: WakuMessage) => void>;
@ -82,16 +91,13 @@ export class WakuRelay extends Gossipsub {
this.heartbeat = new RelayHeartbeat(this);
this.observers = {};
this.decPrivateKeys = new Set();
const multicodecs = [constants.RelayCodec];
Object.assign(this, { multicodecs });
if (options?.pubsubTopic) {
this.pubsubTopic = options.pubsubTopic;
} else {
this.pubsubTopic = constants.DefaultPubsubTopic;
}
this.pubsubTopic = options?.pubsubTopic || constants.DefaultPubsubTopic;
}
/**
@ -114,7 +120,23 @@ export class WakuRelay extends Gossipsub {
*/
public async send(message: WakuMessage): Promise<void> {
const msg = message.encode();
await super.publish(this.pubsubTopic, new Buffer(msg));
await super.publish(this.pubsubTopic, Buffer.from(msg));
}
/**
* Register a decryption private key to attempt decryption of messages of
* the given content topic.
*/
addDecryptionPrivateKey(privateKey: Uint8Array): void {
this.decPrivateKeys.add(privateKey);
}
/**
* Delete a decryption private key to attempt decryption of messages of
* the given content topic.
*/
deleteDecryptionPrivateKey(privateKey: Uint8Array): void {
this.decPrivateKeys.delete(privateKey);
}
/**
@ -123,6 +145,7 @@ export class WakuRelay extends Gossipsub {
* @param callback called when a new message is received via waku relay
* @param contentTopics Content Topics for which the callback with be called,
* all of them if undefined, [] or ["",..] is passed.
* @param decPrivateKeys Private keys used to decrypt incoming Waku Messages.
* @returns {void}
*/
addObserver(
@ -185,19 +208,30 @@ export class WakuRelay extends Gossipsub {
*/
subscribe(pubsubTopic: string): void {
this.on(pubsubTopic, (event) => {
const wakuMsg = WakuMessage.decode(event.data);
if (this.observers['']) {
this.observers[''].forEach((callbackFn) => {
callbackFn(wakuMsg);
dbg(`Message received on ${pubsubTopic}`);
WakuMessage.decode(event.data, Array.from(this.decPrivateKeys))
.then((wakuMsg) => {
if (!wakuMsg) {
dbg('Failed to decode Waku Message');
return;
}
if (this.observers['']) {
this.observers[''].forEach((callbackFn) => {
callbackFn(wakuMsg);
});
}
if (wakuMsg.contentTopic) {
if (this.observers[wakuMsg.contentTopic]) {
this.observers[wakuMsg.contentTopic].forEach((callbackFn) => {
callbackFn(wakuMsg);
});
}
}
})
.catch((e) => {
dbg('Failed to decode Waku Message', e);
});
}
if (wakuMsg.contentTopic) {
if (this.observers[wakuMsg.contentTopic]) {
this.observers[wakuMsg.contentTopic].forEach((callbackFn) => {
callbackFn(wakuMsg);
});
}
}
});
super.subscribe(pubsubTopic);

View File

@ -24,7 +24,9 @@ describe('Waku Store', () => {
for (let i = 0; i < 2; i++) {
expect(
await nimWaku.sendMessage(WakuMessage.fromUtf8String(`Message ${i}`))
await nimWaku.sendMessage(
await WakuMessage.fromUtf8String(`Message ${i}`)
)
).to.be.true;
}
@ -58,7 +60,9 @@ describe('Waku Store', () => {
for (let i = 0; i < 15; i++) {
expect(
await nimWaku.sendMessage(WakuMessage.fromUtf8String(`Message ${i}`))
await nimWaku.sendMessage(
await WakuMessage.fromUtf8String(`Message ${i}`)
)
).to.be.true;
}
@ -98,7 +102,7 @@ describe('Waku Store', () => {
for (let i = 0; i < 2; i++) {
expect(
await nimWaku.sendMessage(
WakuMessage.fromUtf8String(`Message ${i}`),
await WakuMessage.fromUtf8String(`Message ${i}`),
customPubSubTopic
)
).to.be.true;

View File

@ -114,19 +114,24 @@ export class WakuStore {
return messages;
}
const pageMessages = response.messages.map((protoMsg) => {
return new WakuMessage(protoMsg);
});
const pageMessages: WakuMessage[] = [];
await Promise.all(
response.messages.map(async (protoMsg) => {
const msg = await WakuMessage.decodeProto(protoMsg);
if (msg) {
messages.push(msg);
pageMessages.push(msg);
}
})
);
if (opts.callback) {
// TODO: Test the callback feature
// TODO: Change callback to take individual messages
opts.callback(pageMessages);
}
pageMessages.forEach((wakuMessage) => {
messages.push(wakuMessage);
});
const responsePageSize = response.pagingInfo?.pageSize;
const queryPageSize = historyRpcQuery.query?.pagingInfo?.pageSize;
if (

View File

@ -41,6 +41,7 @@ export interface Args {
persistMessages?: boolean;
lightpush?: boolean;
topics?: string;
rpcPrivate?: boolean;
}
export enum LogLevel {
@ -53,6 +54,16 @@ export enum LogLevel {
Fatal = 'fatal',
}
export interface KeyPair {
privateKey: string;
publicKey: string;
}
export interface WakuRelayMessage {
payload: string;
contentTopic?: string;
}
export class NimWaku {
private process?: ChildProcess;
private pid?: number;
@ -180,9 +191,64 @@ export class NimWaku {
async messages(): Promise<WakuMessage[]> {
this.checkProcess();
return this.rpcCall<proto.WakuMessage[]>('get_waku_v2_relay_v1_messages', [
DefaultPubsubTopic,
]).then((msgs) => msgs.map((protoMsg) => new WakuMessage(protoMsg)));
const isDefined = (msg: WakuMessage | undefined): msg is WakuMessage => {
return !!msg;
};
const protoMsgs = await this.rpcCall<proto.WakuMessage[]>(
'get_waku_v2_relay_v1_messages',
[DefaultPubsubTopic]
);
const msgs = await Promise.all(
protoMsgs.map(async (protoMsg) => await WakuMessage.decodeProto(protoMsg))
);
return msgs.filter(isDefined);
}
async getAsymmetricKeyPair(): Promise<KeyPair> {
this.checkProcess();
const { seckey, pubkey } = await this.rpcCall<{
seckey: string;
pubkey: string;
}>('get_waku_v2_private_v1_asymmetric_keypair', []);
return { privateKey: seckey, publicKey: pubkey };
}
async postAsymmetricMessage(
message: WakuRelayMessage,
publicKey: Uint8Array,
pubsubTopic?: string
): Promise<boolean> {
this.checkProcess();
if (!message.payload) {
throw 'Attempting to send empty message';
}
return this.rpcCall<boolean>('post_waku_v2_private_v1_asymmetric_message', [
pubsubTopic ? pubsubTopic : DefaultPubsubTopic,
message,
'0x' + bufToHex(publicKey),
]);
}
async getAsymmetricMessages(
privateKey: Uint8Array,
pubsubTopic?: string
): Promise<WakuRelayMessage[]> {
this.checkProcess();
return await this.rpcCall<WakuRelayMessage[]>(
'get_waku_v2_private_v1_asymmetric_messages',
[
pubsubTopic ? pubsubTopic : DefaultPubsubTopic,
'0x' + bufToHex(privateKey),
]
);
}
async getPeerId(): Promise<PeerId> {

43
src/types/ecies-parity.d.ts vendored Normal file
View File

@ -0,0 +1,43 @@
// TypeScript Version: 2.1
/// <reference types="node" />
declare module 'ecies-parity' {
// Compute the public key for a given private key.
export function getPublic(privateKey: Buffer): Buffer;
// Compute the compressed public key for a given private key.
export function getPublicCompressed(privateKey: Buffer): Buffer;
// Create an ECDSA signature.
export function sign(key: Buffer, msg: Buffer): Promise<Buffer>;
// Verify an ECDSA signature.
export function verify(
publicKey: Buffer,
msg: Buffer,
sig: Buffer
): Promise<null>;
// Derive shared secret for given private and public keys.
export function derive(
privateKeyA: Buffer,
publicKeyB: Buffer
): Promise<Buffer>;
// Input/output structure for ECIES operations.
export interface Ecies {
iv: Buffer;
ephemPublicKey: Buffer;
ciphertext: Buffer;
mac: Buffer;
}
// Encrypt message for given recipient's public key.
export function encrypt(
publicKeyTo: Buffer,
msg: Buffer,
opts?: { iv?: Buffer; ephemPrivateKey?: Buffer }
): Promise<Buffer>;
// Decrypt message using given private key.
export function decrypt(privateKey: Buffer, payload: Buffer): Promise<Buffer>;
}

View File

@ -41,7 +41,7 @@
// "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
// "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */,
"lib": ["es2017"],
"lib": ["es2017", "dom"],
"types": ["node", "mocha"],
"typeRoots": ["node_modules/@types", "src/types"]
},