@waku/message-encryption 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +65 -0
- package/bundle/ecies.js +1 -0
- package/bundle/index-80756c1f.js +15281 -0
- package/bundle/index.js +1 -13514
- package/bundle/symmetric.js +1 -0
- package/dist/crypto/ecies.d.ts +17 -0
- package/dist/crypto/ecies.js +126 -0
- package/dist/crypto/ecies.js.map +1 -0
- package/dist/{crypto.d.ts → crypto/index.d.ts} +0 -0
- package/dist/{crypto.js → crypto/index.js} +2 -2
- package/dist/crypto/index.js.map +1 -0
- package/dist/crypto/symmetric.d.ts +3 -0
- package/dist/crypto/symmetric.js +18 -0
- package/dist/crypto/symmetric.js.map +1 -0
- package/dist/ecies.d.ts +45 -11
- package/dist/ecies.js +97 -112
- package/dist/ecies.js.map +1 -1
- package/dist/index.d.ts +7 -82
- package/dist/index.js +6 -312
- package/dist/index.js.map +1 -1
- package/dist/symmetric.d.ts +51 -3
- package/dist/symmetric.js +107 -14
- package/dist/symmetric.js.map +1 -1
- package/dist/waku_payload.d.ts +53 -0
- package/dist/waku_payload.js +178 -0
- package/dist/waku_payload.js.map +1 -0
- package/package.json +10 -2
- package/src/crypto/ecies.ts +194 -0
- package/src/{crypto.ts → crypto/index.ts} +1 -1
- package/src/crypto/symmetric.ts +33 -0
- package/src/ecies.ts +147 -173
- package/src/index.ts +12 -440
- package/src/symmetric.ts +161 -27
- package/src/waku_payload.ts +239 -0
- package/dist/crypto.js.map +0 -1
package/src/ecies.ts
CHANGED
@@ -1,194 +1,168 @@
|
|
1
|
-
import
|
2
|
-
import {
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
1
|
+
import { Decoder as DecoderV0, proto } from "@waku/core/lib/message/version_0";
|
2
|
+
import type {
|
3
|
+
IDecoder,
|
4
|
+
IEncoder,
|
5
|
+
IMessage,
|
6
|
+
IProtoMessage,
|
7
|
+
} from "@waku/interfaces";
|
8
|
+
import debug from "debug";
|
9
|
+
|
10
|
+
import {
|
11
|
+
decryptAsymmetric,
|
12
|
+
encryptAsymmetric,
|
13
|
+
postCipher,
|
14
|
+
preCipher,
|
15
|
+
} from "./waku_payload.js";
|
16
|
+
|
17
|
+
import {
|
18
|
+
DecodedMessage,
|
19
|
+
generatePrivateKey,
|
20
|
+
getPublicKey,
|
21
|
+
OneMillion,
|
22
|
+
Version,
|
23
|
+
} from "./index.js";
|
24
|
+
|
25
|
+
export { DecodedMessage, generatePrivateKey, getPublicKey };
|
26
|
+
|
27
|
+
const log = debug("waku:message-encryption:ecies");
|
28
|
+
|
29
|
+
class Encoder implements IEncoder {
|
30
|
+
constructor(
|
31
|
+
public contentTopic: string,
|
32
|
+
private publicKey: Uint8Array,
|
33
|
+
private sigPrivKey?: Uint8Array,
|
34
|
+
public ephemeral: boolean = false
|
35
|
+
) {}
|
36
|
+
|
37
|
+
async toWire(message: IMessage): Promise<Uint8Array | undefined> {
|
38
|
+
const protoMessage = await this.toProtoObj(message);
|
39
|
+
if (!protoMessage) return;
|
40
|
+
|
41
|
+
return proto.WakuMessage.encode(protoMessage);
|
30
42
|
}
|
31
|
-
return willBeResult;
|
32
|
-
}
|
33
|
-
|
34
|
-
function aesCtrEncrypt(
|
35
|
-
counter: Uint8Array,
|
36
|
-
key: ArrayBufferLike,
|
37
|
-
data: ArrayBufferLike
|
38
|
-
): Promise<Uint8Array> {
|
39
|
-
return getSubtle()
|
40
|
-
.importKey("raw", key, "AES-CTR", false, ["encrypt"])
|
41
|
-
.then((cryptoKey) =>
|
42
|
-
getSubtle().encrypt(
|
43
|
-
{ name: "AES-CTR", counter: counter, length: 128 },
|
44
|
-
cryptoKey,
|
45
|
-
data
|
46
|
-
)
|
47
|
-
)
|
48
|
-
.then((bytes) => new Uint8Array(bytes));
|
49
|
-
}
|
50
|
-
|
51
|
-
function aesCtrDecrypt(
|
52
|
-
counter: Uint8Array,
|
53
|
-
key: ArrayBufferLike,
|
54
|
-
data: ArrayBufferLike
|
55
|
-
): Promise<Uint8Array> {
|
56
|
-
return getSubtle()
|
57
|
-
.importKey("raw", key, "AES-CTR", false, ["decrypt"])
|
58
|
-
.then((cryptoKey) =>
|
59
|
-
getSubtle().decrypt(
|
60
|
-
{ name: "AES-CTR", counter: counter, length: 128 },
|
61
|
-
cryptoKey,
|
62
|
-
data
|
63
|
-
)
|
64
|
-
)
|
65
|
-
.then((bytes) => new Uint8Array(bytes));
|
66
|
-
}
|
67
43
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
getSubtle().verify(algorithm, cryptoKey, sig, msg)
|
88
|
-
);
|
44
|
+
async toProtoObj(message: IMessage): Promise<IProtoMessage | undefined> {
|
45
|
+
const timestamp = message.timestamp ?? new Date();
|
46
|
+
if (!message.payload) {
|
47
|
+
log("No payload to encrypt, skipping: ", message);
|
48
|
+
return;
|
49
|
+
}
|
50
|
+
const preparedPayload = await preCipher(message.payload, this.sigPrivKey);
|
51
|
+
|
52
|
+
const payload = await encryptAsymmetric(preparedPayload, this.publicKey);
|
53
|
+
|
54
|
+
return {
|
55
|
+
payload,
|
56
|
+
version: Version,
|
57
|
+
contentTopic: this.contentTopic,
|
58
|
+
timestamp: BigInt(timestamp.valueOf()) * OneMillion,
|
59
|
+
rateLimitProof: message.rateLimitProof,
|
60
|
+
ephemeral: this.ephemeral,
|
61
|
+
};
|
62
|
+
}
|
89
63
|
}
|
90
64
|
|
91
65
|
/**
|
92
|
-
*
|
66
|
+
* Creates an encoder that encrypts messages using ECIES for the given public,
|
67
|
+
* as defined in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/).
|
68
|
+
*
|
69
|
+
* An encoder is used to encode messages in the [`14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/)
|
70
|
+
* format to be sent over the Waku network. The resulting encoder can then be
|
71
|
+
* pass to { @link @waku/interfaces.LightPush.push } or
|
72
|
+
* { @link @waku/interfaces.Relay.send } to automatically encrypt
|
73
|
+
* and encode outgoing messages.
|
93
74
|
*
|
94
|
-
*
|
95
|
-
*
|
96
|
-
*
|
97
|
-
* @
|
75
|
+
* The payload can optionally be signed with the given private key as defined
|
76
|
+
* in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/).
|
77
|
+
*
|
78
|
+
* @param contentTopic The content topic to set on outgoing messages.
|
79
|
+
* @param publicKey The public key to encrypt the payload for.
|
80
|
+
* @param sigPrivKey An optional private key to used to sign the payload before encryption.
|
81
|
+
* @param ephemeral An optional flag to mark message as ephemeral, ie, not to be stored by Waku Store nodes.
|
98
82
|
*/
|
99
|
-
function
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
`Bad public key, it should be 65 bytes but it's actually ${publicKeyB.length} bytes long`
|
107
|
-
);
|
108
|
-
} else if (publicKeyB[0] !== 4) {
|
109
|
-
throw new Error("Bad public key, a valid public key would begin with 4");
|
110
|
-
} else {
|
111
|
-
const px = secp.getSharedSecret(privateKeyA, publicKeyB, true);
|
112
|
-
// Remove the compression prefix
|
113
|
-
return new Uint8Array(hexToBytes(px).slice(1));
|
114
|
-
}
|
83
|
+
export function createEncoder(
|
84
|
+
contentTopic: string,
|
85
|
+
publicKey: Uint8Array,
|
86
|
+
sigPrivKey?: Uint8Array,
|
87
|
+
ephemeral = false
|
88
|
+
): Encoder {
|
89
|
+
return new Encoder(contentTopic, publicKey, sigPrivKey, ephemeral);
|
115
90
|
}
|
116
91
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
* @param msg The message being encrypted
|
122
|
-
* @return A promise that resolves with the ECIES structure serialized
|
123
|
-
*/
|
124
|
-
export async function encrypt(
|
125
|
-
publicKeyTo: Uint8Array,
|
126
|
-
msg: Uint8Array
|
127
|
-
): Promise<Uint8Array> {
|
128
|
-
const ephemPrivateKey = randomBytes(32);
|
92
|
+
class Decoder extends DecoderV0 implements IDecoder<DecodedMessage> {
|
93
|
+
constructor(contentTopic: string, private privateKey: Uint8Array) {
|
94
|
+
super(contentTopic);
|
95
|
+
}
|
129
96
|
|
130
|
-
|
97
|
+
async fromProtoObj(
|
98
|
+
protoMessage: IProtoMessage
|
99
|
+
): Promise<DecodedMessage | undefined> {
|
100
|
+
const cipherPayload = protoMessage.payload;
|
101
|
+
|
102
|
+
if (protoMessage.version !== Version) {
|
103
|
+
log(
|
104
|
+
"Failed to decrypt due to incorrect version, expected:",
|
105
|
+
Version,
|
106
|
+
", actual:",
|
107
|
+
protoMessage.version
|
108
|
+
);
|
109
|
+
return;
|
110
|
+
}
|
131
111
|
|
132
|
-
|
112
|
+
let payload;
|
113
|
+
if (!cipherPayload) {
|
114
|
+
log(`No payload to decrypt for contentTopic ${this.contentTopic}`);
|
115
|
+
return;
|
116
|
+
}
|
133
117
|
|
134
|
-
|
135
|
-
|
136
|
-
|
118
|
+
try {
|
119
|
+
payload = await decryptAsymmetric(cipherPayload, this.privateKey);
|
120
|
+
} catch (e) {
|
121
|
+
log(
|
122
|
+
`Failed to decrypt message using asymmetric decryption for contentTopic: ${this.contentTopic}`,
|
123
|
+
e
|
124
|
+
);
|
125
|
+
return;
|
126
|
+
}
|
137
127
|
|
138
|
-
|
128
|
+
if (!payload) {
|
129
|
+
log(`Failed to decrypt payload for contentTopic ${this.contentTopic}`);
|
130
|
+
return;
|
131
|
+
}
|
139
132
|
|
140
|
-
|
141
|
-
const hmac = await hmacSha256Sign(macKey, ivCipherText);
|
142
|
-
const ephemPublicKey = secp.getPublicKey(ephemPrivateKey, false);
|
133
|
+
const res = await postCipher(payload);
|
143
134
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
}
|
135
|
+
if (!res) {
|
136
|
+
log(`Failed to decode payload for contentTopic ${this.contentTopic}`);
|
137
|
+
return;
|
138
|
+
}
|
149
139
|
|
150
|
-
|
140
|
+
log("Message decrypted", protoMessage);
|
141
|
+
return new DecodedMessage(
|
142
|
+
protoMessage,
|
143
|
+
res.payload,
|
144
|
+
res.sig?.signature,
|
145
|
+
res.sig?.publicKey
|
146
|
+
);
|
147
|
+
}
|
148
|
+
}
|
151
149
|
|
152
150
|
/**
|
153
|
-
*
|
151
|
+
* Creates a decoder that decrypts messages using ECIES, using the given private
|
152
|
+
* key as defined in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/).
|
153
|
+
*
|
154
|
+
* A decoder is used to decode messages from the [14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/)
|
155
|
+
* format when received from the Waku network. The resulting decoder can then be
|
156
|
+
* pass to { @link @waku/interfaces.Filter.subscribe } or
|
157
|
+
* { @link @waku/interfaces.Relay.subscribe } to automatically decrypt and
|
158
|
+
* decode incoming messages.
|
154
159
|
*
|
155
|
-
* @param
|
156
|
-
* @param
|
157
|
-
* @returns The clear text
|
158
|
-
* @throws Error If decryption fails
|
160
|
+
* @param contentTopic The resulting decoder will only decode messages with this content topic.
|
161
|
+
* @param privateKey The private key used to decrypt the message.
|
159
162
|
*/
|
160
|
-
export
|
161
|
-
|
162
|
-
|
163
|
-
):
|
164
|
-
|
165
|
-
throw new Error(
|
166
|
-
`Invalid Ciphertext. Data is too small. It should ba at least ${metaLength} bytes`
|
167
|
-
);
|
168
|
-
} else if (encrypted[0] !== 4) {
|
169
|
-
throw new Error(
|
170
|
-
`Not a valid ciphertext. It should begin with 4 but actually begin with ${encrypted[0]}`
|
171
|
-
);
|
172
|
-
} else {
|
173
|
-
// deserialize
|
174
|
-
const ephemPublicKey = encrypted.slice(0, 65);
|
175
|
-
const cipherTextLength = encrypted.length - metaLength;
|
176
|
-
const iv = encrypted.slice(65, 65 + 16);
|
177
|
-
const cipherAndIv = encrypted.slice(65, 65 + 16 + cipherTextLength);
|
178
|
-
const ciphertext = cipherAndIv.slice(16);
|
179
|
-
const msgMac = encrypted.slice(65 + 16 + cipherTextLength);
|
180
|
-
|
181
|
-
// check HMAC
|
182
|
-
const px = derive(privateKey, ephemPublicKey);
|
183
|
-
const hash = await kdf(px, 32);
|
184
|
-
const [encryptionKey, macKey] = await sha256(hash.slice(16)).then(
|
185
|
-
(macKey) => [hash.slice(0, 16), macKey]
|
186
|
-
);
|
187
|
-
|
188
|
-
if (!(await hmacSha256Verify(macKey, cipherAndIv, msgMac))) {
|
189
|
-
throw new Error("Incorrect MAC");
|
190
|
-
}
|
191
|
-
|
192
|
-
return aesCtrDecrypt(iv, encryptionKey, ciphertext);
|
193
|
-
}
|
163
|
+
export function createDecoder(
|
164
|
+
contentTopic: string,
|
165
|
+
privateKey: Uint8Array
|
166
|
+
): Decoder {
|
167
|
+
return new Decoder(contentTopic, privateKey);
|
194
168
|
}
|