/
symmetric.ts
206 lines (183 loc) · 5.56 KB
/
symmetric.ts
1
2
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import { Decoder as DecoderV0 } from "@waku/core/lib/message/version_0";
import {
type EncoderOptions as BaseEncoderOptions,
DefaultPubsubTopic,
type IDecoder,
type IEncoder,
type IMessage,
type IMetaSetter,
type IProtoMessage,
type PubsubTopic,
type SingleShardInfo
} from "@waku/interfaces";
import { WakuMessage } from "@waku/proto";
import { determinePubsubTopic, Logger } from "@waku/utils";
import { generateSymmetricKey } from "./crypto/utils.js";
import { DecodedMessage } from "./decoded_message.js";
import {
decryptSymmetric,
encryptSymmetric,
postCipher,
preCipher
} from "./encryption.js";
import { OneMillion, Version } from "./misc.js";
export {
decryptSymmetric,
encryptSymmetric,
postCipher,
preCipher,
generateSymmetricKey
};
const log = new Logger("message-encryption:symmetric");
class Encoder implements IEncoder {
constructor(
public pubsubTopic: PubsubTopic,
public contentTopic: string,
private symKey: Uint8Array,
private sigPrivKey?: Uint8Array,
public ephemeral: boolean = false,
public metaSetter?: IMetaSetter
) {
if (!contentTopic || contentTopic === "") {
throw new Error("Content topic must be specified");
}
}
async toWire(message: IMessage): Promise<Uint8Array | undefined> {
const protoMessage = await this.toProtoObj(message);
if (!protoMessage) return;
return WakuMessage.encode(protoMessage);
}
async toProtoObj(message: IMessage): Promise<IProtoMessage | undefined> {
const timestamp = message.timestamp ?? new Date();
const preparedPayload = await preCipher(message.payload, this.sigPrivKey);
const payload = await encryptSymmetric(preparedPayload, this.symKey);
const protoMessage = {
payload,
version: Version,
contentTopic: this.contentTopic,
timestamp: BigInt(timestamp.valueOf()) * OneMillion,
meta: undefined,
rateLimitProof: message.rateLimitProof,
ephemeral: this.ephemeral
};
if (this.metaSetter) {
const meta = this.metaSetter(protoMessage);
return { ...protoMessage, meta };
}
return protoMessage;
}
}
export interface EncoderOptions extends BaseEncoderOptions {
/** The symmetric key to encrypt the payload with. */
symKey: Uint8Array;
/** An optional private key to be used to sign the payload before encryption. */
sigPrivKey?: Uint8Array;
}
/**
* Creates an encoder that encrypts messages using symmetric encryption for the
* given key, as defined in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/).
*
* An encoder is used to encode messages in the [`14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/)
* format to be sent over the Waku network. The resulting encoder can then be
* pass to { @link @waku/interfaces!ISender.send } to automatically encrypt
* and encode outgoing messages.
*
* The payload can optionally be signed with the given private key as defined
* in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/).
*/
export function createEncoder({
pubsubTopic = DefaultPubsubTopic,
pubsubTopicShardInfo,
contentTopic,
symKey,
sigPrivKey,
ephemeral = false,
metaSetter
}: EncoderOptions): Encoder {
return new Encoder(
determinePubsubTopic(contentTopic, pubsubTopic ?? pubsubTopicShardInfo),
contentTopic,
symKey,
sigPrivKey,
ephemeral,
metaSetter
);
}
class Decoder extends DecoderV0 implements IDecoder<DecodedMessage> {
constructor(
pubsubTopic: PubsubTopic,
contentTopic: string,
private symKey: Uint8Array
) {
super(pubsubTopic, contentTopic);
}
async fromProtoObj(
pubsubTopic: string,
protoMessage: IProtoMessage
): Promise<DecodedMessage | undefined> {
const cipherPayload = protoMessage.payload;
if (protoMessage.version !== Version) {
log.error(
"Failed to decrypt due to incorrect version, expected:",
Version,
", actual:",
protoMessage.version
);
return;
}
let payload;
try {
payload = await decryptSymmetric(cipherPayload, this.symKey);
} catch (e) {
log.error(
`Failed to decrypt message using asymmetric decryption for contentTopic: ${this.contentTopic}`,
e
);
return;
}
if (!payload) {
log.error(
`Failed to decrypt payload for contentTopic ${this.contentTopic}`
);
return;
}
const res = postCipher(payload);
if (!res) {
log.error(
`Failed to decode payload for contentTopic ${this.contentTopic}`
);
return;
}
log.info("Message decrypted", protoMessage);
return new DecodedMessage(
pubsubTopic,
protoMessage,
res.payload,
res.sig?.signature,
res.sig?.publicKey
);
}
}
/**
* Creates a decoder that decrypts messages using symmetric encryption, using
* the given key as defined in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/).
*
* A decoder is used to decode messages from the [14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/)
* format when received from the Waku network. The resulting decoder can then be
* pass to { @link @waku/interfaces!IReceiver.subscribe } to automatically decrypt and
* decode incoming messages.
*
* @param contentTopic The resulting decoder will only decode messages with this content topic.
* @param symKey The symmetric key used to decrypt the message.
*/
export function createDecoder(
contentTopic: string,
symKey: Uint8Array,
pubsubTopicShardInfo: SingleShardInfo | PubsubTopic = DefaultPubsubTopic
): Decoder {
return new Decoder(
determinePubsubTopic(contentTopic, pubsubTopicShardInfo),
contentTopic,
symKey
);
}