mirror of
https://github.com/waku-org/js-noise.git
synced 2025-02-24 00:48:19 +00:00
refactor: constructors and QR
This commit is contained in:
parent
f2d2f891ab
commit
c911333ef1
@ -6,7 +6,10 @@
|
|||||||
"Waku",
|
"Waku",
|
||||||
"keypair",
|
"keypair",
|
||||||
"nwaku",
|
"nwaku",
|
||||||
"Nametag, ciphertext",
|
"wakunoise",
|
||||||
|
"HMACDRBG",
|
||||||
|
"Nametag",
|
||||||
|
"ciphertext",
|
||||||
"unpad",
|
"unpad",
|
||||||
"blocksize",
|
"blocksize",
|
||||||
"Nametag",
|
"Nametag",
|
||||||
|
@ -34,7 +34,8 @@ export class NoiseHandshakeEncoder implements Encoder {
|
|||||||
|
|
||||||
export class NoiseHandshakeMessage extends MessageV0 implements Message {
|
export class NoiseHandshakeMessage extends MessageV0 implements Message {
|
||||||
get payloadV2(): PayloadV2 {
|
get payloadV2(): PayloadV2 {
|
||||||
return PayloadV2.deserialize(this.payload!);
|
if (!this.payload) throw new Error("no payload available");
|
||||||
|
return PayloadV2.deserialize(this.payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,14 +38,14 @@ export class HandshakeStepResult {
|
|||||||
// The recipient static key rs and handshake hash values h are stored to address some possible future applications (channel-binding, session management, etc.).
|
// The recipient static key rs and handshake hash values h are stored to address some possible future applications (channel-binding, session management, etc.).
|
||||||
// However, are not required by Noise specifications and are thus optional
|
// However, are not required by Noise specifications and are thus optional
|
||||||
export class HandshakeResult {
|
export class HandshakeResult {
|
||||||
csOutbound?: CipherState;
|
|
||||||
csInbound?: CipherState;
|
|
||||||
// Optional fields:
|
// Optional fields:
|
||||||
nametagsInbound: MessageNametagBuffer = new MessageNametagBuffer();
|
nametagsInbound: MessageNametagBuffer = new MessageNametagBuffer();
|
||||||
nametagsOutbound: MessageNametagBuffer = new MessageNametagBuffer();
|
nametagsOutbound: MessageNametagBuffer = new MessageNametagBuffer();
|
||||||
rs: bytes32 = new Uint8Array();
|
rs: bytes32 = new Uint8Array();
|
||||||
h: bytes32 = new Uint8Array();
|
h: bytes32 = new Uint8Array();
|
||||||
|
|
||||||
|
constructor(private csOutbound: CipherState, private csInbound: CipherState) {}
|
||||||
|
|
||||||
// Noise specification, Section 5:
|
// Noise specification, Section 5:
|
||||||
// Transport messages are then encrypted and decrypted by calling EncryptWithAd()
|
// Transport messages are then encrypted and decrypted by calling EncryptWithAd()
|
||||||
// and DecryptWithAd() on the relevant CipherState with zero-length associated data.
|
// and DecryptWithAd() on the relevant CipherState with zero-length associated data.
|
||||||
@ -70,7 +70,7 @@ export class HandshakeResult {
|
|||||||
// We pad the transport message
|
// We pad the transport message
|
||||||
const paddedTransportMessage = pkcs7.pad(transportMessage, NoisePaddingBlockSize);
|
const paddedTransportMessage = pkcs7.pad(transportMessage, NoisePaddingBlockSize);
|
||||||
// Encryption is done with zero-length associated data as per specification
|
// Encryption is done with zero-length associated data as per specification
|
||||||
payload2.transportMessage = this.csOutbound!.encryptWithAd(payload2.messageNametag, paddedTransportMessage);
|
payload2.transportMessage = this.csOutbound.encryptWithAd(payload2.messageNametag, paddedTransportMessage);
|
||||||
|
|
||||||
return payload2;
|
return payload2;
|
||||||
}
|
}
|
||||||
@ -97,7 +97,7 @@ export class HandshakeResult {
|
|||||||
// On application level we decide to discard messages which fail decryption, without raising an error
|
// On application level we decide to discard messages which fail decryption, without raising an error
|
||||||
try {
|
try {
|
||||||
// Decryption is done with messageNametag as associated data
|
// Decryption is done with messageNametag as associated data
|
||||||
const paddedMessage = this.csInbound!.decryptWithAd(readPayload2.messageNametag, readPayload2.transportMessage);
|
const paddedMessage = this.csInbound.decryptWithAd(readPayload2.messageNametag, readPayload2.transportMessage);
|
||||||
// We unpad the decrypted message
|
// We unpad the decrypted message
|
||||||
message = pkcs7.unpad(paddedMessage);
|
message = pkcs7.unpad(paddedMessage);
|
||||||
// The message successfully decrypted, we can delete the first element of the inbound Message Nametag Buffer
|
// The message successfully decrypted, we can delete the first element of the inbound Message Nametag Buffer
|
||||||
@ -245,7 +245,7 @@ export class Handshake {
|
|||||||
|
|
||||||
// Finalizes the handshake by calling Split and assigning the proper Cipher States to users
|
// Finalizes the handshake by calling Split and assigning the proper Cipher States to users
|
||||||
finalizeHandshake(): HandshakeResult {
|
finalizeHandshake(): HandshakeResult {
|
||||||
const hsResult = new HandshakeResult();
|
let hsResult: HandshakeResult;
|
||||||
|
|
||||||
// Noise specification, Section 5:
|
// Noise specification, Section 5:
|
||||||
// Processing the final handshake message returns two CipherState objects,
|
// Processing the final handshake message returns two CipherState objects,
|
||||||
@ -260,14 +260,12 @@ export class Handshake {
|
|||||||
|
|
||||||
// We assign the proper Cipher States
|
// We assign the proper Cipher States
|
||||||
if (this.hs.initiator) {
|
if (this.hs.initiator) {
|
||||||
hsResult.csOutbound = cs1;
|
hsResult = new HandshakeResult(cs1, cs2);
|
||||||
hsResult.csInbound = cs2;
|
|
||||||
// and nametags secrets
|
// and nametags secrets
|
||||||
hsResult.nametagsInbound.secret = nms1;
|
hsResult.nametagsInbound.secret = nms1;
|
||||||
hsResult.nametagsOutbound.secret = nms2;
|
hsResult.nametagsOutbound.secret = nms2;
|
||||||
} else {
|
} else {
|
||||||
hsResult.csOutbound = cs2;
|
hsResult = new HandshakeResult(cs2, cs1);
|
||||||
hsResult.csInbound = cs1;
|
|
||||||
// and nametags secrets
|
// and nametags secrets
|
||||||
hsResult.nametagsInbound.secret = nms2;
|
hsResult.nametagsInbound.secret = nms2;
|
||||||
hsResult.nametagsOutbound.secret = nms1;
|
hsResult.nametagsOutbound.secret = nms1;
|
||||||
@ -277,8 +275,10 @@ export class Handshake {
|
|||||||
hsResult.nametagsInbound.initNametagsBuffer();
|
hsResult.nametagsInbound.initNametagsBuffer();
|
||||||
hsResult.nametagsOutbound.initNametagsBuffer();
|
hsResult.nametagsOutbound.initNametagsBuffer();
|
||||||
|
|
||||||
|
if (!this.hs.rs) throw new Error("invalid handshake state");
|
||||||
|
|
||||||
// We store the optional fields rs and h
|
// We store the optional fields rs and h
|
||||||
hsResult.rs = new Uint8Array(this.hs.rs!);
|
hsResult.rs = new Uint8Array(this.hs.rs);
|
||||||
hsResult.h = new Uint8Array(this.hs.ss.h);
|
hsResult.h = new Uint8Array(this.hs.ss.h);
|
||||||
|
|
||||||
return hsResult;
|
return hsResult;
|
||||||
|
@ -24,7 +24,7 @@ import {
|
|||||||
} from "./patterns";
|
} from "./patterns";
|
||||||
import { MessageNametagBuffer } from "./payload";
|
import { MessageNametagBuffer } from "./payload";
|
||||||
import { ChaChaPolyCipherState, NoisePublicKey } from "./publickey";
|
import { ChaChaPolyCipherState, NoisePublicKey } from "./publickey";
|
||||||
import { fromQr, toQr } from "./utils";
|
import { QR } from "./qr";
|
||||||
|
|
||||||
export { Handshake, HandshakeParameters, HandshakeResult, HandshakeStepResult, StepHandshakeParameters };
|
export { Handshake, HandshakeParameters, HandshakeResult, HandshakeStepResult, StepHandshakeParameters };
|
||||||
export { generateX25519KeyPair, generateX25519KeyPairFromSeed };
|
export { generateX25519KeyPair, generateX25519KeyPairFromSeed };
|
||||||
@ -41,4 +41,4 @@ export {
|
|||||||
export { ChaChaPolyCipherState, NoisePublicKey };
|
export { ChaChaPolyCipherState, NoisePublicKey };
|
||||||
export { MessageNametagBuffer };
|
export { MessageNametagBuffer };
|
||||||
export { NoiseHandshakeDecoder, NoiseHandshakeEncoder, NoiseSecureTransferDecoder, NoiseSecureTransferEncoder };
|
export { NoiseHandshakeDecoder, NoiseHandshakeEncoder, NoiseSecureTransferDecoder, NoiseSecureTransferEncoder };
|
||||||
export { fromQr, toQr };
|
export { QR };
|
||||||
|
@ -156,11 +156,10 @@ function hashProtocol(name: string): Uint8Array {
|
|||||||
// Contains a Cipher State cs, the chaining key ck and the handshake hash value h
|
// Contains a Cipher State cs, the chaining key ck and the handshake hash value h
|
||||||
export class SymmetricState {
|
export class SymmetricState {
|
||||||
cs: CipherState;
|
cs: CipherState;
|
||||||
ck: bytes32; // chaining key
|
|
||||||
h: bytes32; // handshake hash
|
h: bytes32; // handshake hash
|
||||||
hsPattern: HandshakePattern;
|
private ck: bytes32; // chaining key
|
||||||
|
|
||||||
constructor(hsPattern: HandshakePattern) {
|
constructor(private readonly hsPattern: HandshakePattern) {
|
||||||
this.h = hashProtocol(hsPattern.name);
|
this.h = hashProtocol(hsPattern.name);
|
||||||
this.ck = this.h;
|
this.ck = this.h;
|
||||||
this.cs = new CipherState();
|
this.cs = new CipherState();
|
||||||
|
@ -21,13 +21,7 @@ export enum MessageDirection {
|
|||||||
// The pre message pattern consisting of a message direction and some Noise tokens, if any.
|
// The pre message pattern consisting of a message direction and some Noise tokens, if any.
|
||||||
// (if non empty, only tokens e and s are allowed: http://www.noiseprotocol.org/noise.html#handshake-pattern-basics)
|
// (if non empty, only tokens e and s are allowed: http://www.noiseprotocol.org/noise.html#handshake-pattern-basics)
|
||||||
export class PreMessagePattern {
|
export class PreMessagePattern {
|
||||||
direction: MessageDirection;
|
constructor(public readonly direction: MessageDirection, public readonly tokens: Array<NoiseTokens>) {}
|
||||||
tokens: Array<NoiseTokens>;
|
|
||||||
|
|
||||||
constructor(direction: MessageDirection, tokens: Array<NoiseTokens>) {
|
|
||||||
this.direction = direction;
|
|
||||||
this.tokens = tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
equals(b: PreMessagePattern): boolean {
|
equals(b: PreMessagePattern): boolean {
|
||||||
return (
|
return (
|
||||||
@ -41,13 +35,7 @@ export class PreMessagePattern {
|
|||||||
// The message pattern consisting of a message direction and some Noise tokens
|
// The message pattern consisting of a message direction and some Noise tokens
|
||||||
// All Noise tokens are allowed
|
// All Noise tokens are allowed
|
||||||
export class MessagePattern {
|
export class MessagePattern {
|
||||||
direction: MessageDirection;
|
constructor(public readonly direction: MessageDirection, public readonly tokens: Array<NoiseTokens>) {}
|
||||||
tokens: Array<NoiseTokens>;
|
|
||||||
|
|
||||||
constructor(direction: MessageDirection, tokens: Array<NoiseTokens>) {
|
|
||||||
this.direction = direction;
|
|
||||||
this.tokens = tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
equals(b: MessagePattern): boolean {
|
equals(b: MessagePattern): boolean {
|
||||||
return (
|
return (
|
||||||
@ -60,15 +48,11 @@ export class MessagePattern {
|
|||||||
|
|
||||||
// The handshake pattern object. It stores the handshake protocol name, the handshake pre message patterns and the handshake message patterns
|
// The handshake pattern object. It stores the handshake protocol name, the handshake pre message patterns and the handshake message patterns
|
||||||
export class HandshakePattern {
|
export class HandshakePattern {
|
||||||
name: string;
|
constructor(
|
||||||
preMessagePatterns: Array<PreMessagePattern>;
|
public readonly name: string,
|
||||||
messagePatterns: Array<MessagePattern>;
|
public readonly preMessagePatterns: Array<PreMessagePattern>,
|
||||||
|
public readonly messagePatterns: Array<MessagePattern>
|
||||||
constructor(name: string, preMessagePatterns: Array<PreMessagePattern>, messagePatterns: Array<MessagePattern>) {
|
) {}
|
||||||
this.name = name;
|
|
||||||
this.preMessagePatterns = preMessagePatterns;
|
|
||||||
this.messagePatterns = messagePatterns;
|
|
||||||
}
|
|
||||||
|
|
||||||
equals(b: HandshakePattern): boolean {
|
equals(b: HandshakePattern): boolean {
|
||||||
if (this.preMessagePatterns.length != b.preMessagePatterns.length) return false;
|
if (this.preMessagePatterns.length != b.preMessagePatterns.length) return false;
|
||||||
|
@ -19,8 +19,8 @@ export function toMessageNametag(input: Uint8Array): MessageNametag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MessageNametagBuffer {
|
export class MessageNametagBuffer {
|
||||||
buffer: Array<MessageNametag> = new Array<MessageNametag>(MessageNametagBufferSize);
|
private buffer: Array<MessageNametag> = new Array<MessageNametag>(MessageNametagBufferSize);
|
||||||
counter = 0;
|
private counter = 0;
|
||||||
secret?: Uint8Array;
|
secret?: Uint8Array;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -87,8 +87,8 @@ export class MessageNametagBuffer {
|
|||||||
if (this.secret) {
|
if (this.secret) {
|
||||||
// We rotate left the array by n
|
// We rotate left the array by n
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
const first = this.buffer.shift()!;
|
const first = this.buffer.shift();
|
||||||
this.buffer.push(first);
|
if (first) this.buffer.push(first);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
|
@ -50,13 +50,7 @@ export class ChaChaPolyCipherState {
|
|||||||
// or the encryption of the X coordinate concatenated with the authorization tag, if encrypted (this implies flag = 1)
|
// or the encryption of the X coordinate concatenated with the authorization tag, if encrypted (this implies flag = 1)
|
||||||
// Note: besides encryption, flag can be used to distinguish among multiple supported Elliptic Curves
|
// Note: besides encryption, flag can be used to distinguish among multiple supported Elliptic Curves
|
||||||
export class NoisePublicKey {
|
export class NoisePublicKey {
|
||||||
flag: number;
|
constructor(public readonly flag: number, public readonly pk: Uint8Array) {}
|
||||||
pk: Uint8Array;
|
|
||||||
|
|
||||||
constructor(flag: number, pk: Uint8Array) {
|
|
||||||
this.flag = flag;
|
|
||||||
this.pk = pk;
|
|
||||||
}
|
|
||||||
|
|
||||||
clone(): NoisePublicKey {
|
clone(): NoisePublicKey {
|
||||||
return new NoisePublicKey(this.flag, new Uint8Array(this.pk));
|
return new NoisePublicKey(this.flag, new Uint8Array(this.pk));
|
||||||
|
44
src/qr.ts
Normal file
44
src/qr.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { decode, encode, fromUint8Array, toUint8Array } from "js-base64";
|
||||||
|
|
||||||
|
import { bytes32 } from "./@types/basic";
|
||||||
|
|
||||||
|
export class QR {
|
||||||
|
constructor(
|
||||||
|
public readonly applicationName: string,
|
||||||
|
public readonly applicationVersion: string,
|
||||||
|
public readonly shardId: string,
|
||||||
|
public readonly ephemeralKey: bytes32,
|
||||||
|
public readonly committedStaticKey: bytes32
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// Serializes input parameters to a base64 string for exposure through QR code (used by WakuPairing)
|
||||||
|
toString(): string {
|
||||||
|
let qr = encode(this.applicationName) + ":";
|
||||||
|
qr += encode(this.applicationVersion) + ":";
|
||||||
|
qr += encode(this.shardId) + ":";
|
||||||
|
qr += fromUint8Array(this.ephemeralKey) + ":";
|
||||||
|
qr += fromUint8Array(this.committedStaticKey);
|
||||||
|
|
||||||
|
return qr;
|
||||||
|
}
|
||||||
|
|
||||||
|
toByteArray(): Uint8Array {
|
||||||
|
const enc = new TextEncoder();
|
||||||
|
return enc.encode(this.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserializes input string in base64 to the corresponding (applicationName, applicationVersion, shardId, ephemeralKey, committedStaticKey)
|
||||||
|
static fromString(qrString: string): QR {
|
||||||
|
const values = qrString.split(":");
|
||||||
|
|
||||||
|
if (values.length != 5) throw new Error("invalid qr string");
|
||||||
|
|
||||||
|
const applicationName = decode(values[0]);
|
||||||
|
const applicationVersion = decode(values[1]);
|
||||||
|
const shardId = decode(values[2]);
|
||||||
|
const ephemeralKey = toUint8Array(values[3]);
|
||||||
|
const committedStaticKey = toUint8Array(values[4]);
|
||||||
|
|
||||||
|
return new QR(applicationName, applicationVersion, shardId, ephemeralKey, committedStaticKey);
|
||||||
|
}
|
||||||
|
}
|
42
src/utils.ts
42
src/utils.ts
@ -1,9 +1,5 @@
|
|||||||
// Adapted from https://github.com/feross/buffer
|
// Adapted from https://github.com/feross/buffer
|
||||||
|
|
||||||
import { decode, encode, fromUint8Array, toUint8Array } from "js-base64";
|
|
||||||
|
|
||||||
import { bytes32 } from "./@types/basic";
|
|
||||||
|
|
||||||
function checkInt(buf: Uint8Array, value: number, offset: number, ext: number, max: number, min: number): void {
|
function checkInt(buf: Uint8Array, value: number, offset: number, ext: number, max: number, min: number): void {
|
||||||
if (value > max || value < min) throw new RangeError('"value" argument is out of bounds');
|
if (value > max || value < min) throw new RangeError('"value" argument is out of bounds');
|
||||||
if (offset + ext > buf.length) throw new RangeError("Index out of range");
|
if (offset + ext > buf.length) throw new RangeError("Index out of range");
|
||||||
@ -53,41 +49,3 @@ export function readUIntLE(buf: Uint8Array, offset: number, byteLength: number,
|
|||||||
|
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serializes input parameters to a base64 string for exposure through QR code (used by WakuPairing)
|
|
||||||
export function toQr(
|
|
||||||
applicationName: string,
|
|
||||||
applicationVersion: string,
|
|
||||||
shardId: string,
|
|
||||||
ephemeralKey: bytes32,
|
|
||||||
committedStaticKey: bytes32
|
|
||||||
): string {
|
|
||||||
let qr = encode(applicationName) + ":";
|
|
||||||
qr += encode(applicationVersion) + ":";
|
|
||||||
qr += encode(shardId) + ":";
|
|
||||||
qr += fromUint8Array(ephemeralKey) + ":";
|
|
||||||
qr += fromUint8Array(committedStaticKey);
|
|
||||||
|
|
||||||
return qr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deserializes input string in base64 to the corresponding (applicationName, applicationVersion, shardId, ephemeralKey, committedStaticKey)
|
|
||||||
export function fromQr(qr: string): {
|
|
||||||
applicationName: string;
|
|
||||||
applicationVersion: string;
|
|
||||||
shardId: string;
|
|
||||||
ephemeralKey: bytes32;
|
|
||||||
committedStaticKey: bytes32;
|
|
||||||
} {
|
|
||||||
const values = qr.split(":");
|
|
||||||
|
|
||||||
if (values.length != 5) throw new Error("invalid qr string");
|
|
||||||
|
|
||||||
const applicationName = decode(values[0]);
|
|
||||||
const applicationVersion = decode(values[1]);
|
|
||||||
const shardId = decode(values[2]);
|
|
||||||
const ephemeralKey = toUint8Array(values[3]);
|
|
||||||
const committedStaticKey = toUint8Array(values[4]);
|
|
||||||
|
|
||||||
return { applicationName, applicationVersion, shardId, ephemeralKey, committedStaticKey };
|
|
||||||
}
|
|
||||||
|
@ -14,7 +14,7 @@ import { Handshake } from "./handshake";
|
|||||||
import { NoiseHandshakePatterns } from "./patterns";
|
import { NoiseHandshakePatterns } from "./patterns";
|
||||||
import { MessageNametagBufferSize, MessageNametagLength } from "./payload";
|
import { MessageNametagBufferSize, MessageNametagLength } from "./payload";
|
||||||
import { NoisePublicKey } from "./publickey";
|
import { NoisePublicKey } from "./publickey";
|
||||||
import { fromQr, toQr } from "./utils";
|
import { QR } from "./qr";
|
||||||
|
|
||||||
describe("Waku Noise Sessions", () => {
|
describe("Waku Noise Sessions", () => {
|
||||||
const rng = new HMACDRBG();
|
const rng = new HMACDRBG();
|
||||||
@ -48,12 +48,10 @@ describe("Waku Noise Sessions", () => {
|
|||||||
// Out-of-band Communication
|
// Out-of-band Communication
|
||||||
|
|
||||||
// Bob prepares the QR and sends it out-of-band to Alice
|
// Bob prepares the QR and sends it out-of-band to Alice
|
||||||
const qr = toQr(applicationName, applicationVersion, shardId, bobEphemeralKey.publicKey, bobCommittedStaticKey);
|
const qr = new QR(applicationName, applicationVersion, shardId, bobEphemeralKey.publicKey, bobCommittedStaticKey);
|
||||||
const enc = new TextEncoder();
|
|
||||||
const qrBytes = enc.encode(qr);
|
|
||||||
|
|
||||||
// Alice deserializes the QR code
|
// Alice deserializes the QR code
|
||||||
const readQR = fromQr(qr);
|
const readQR = QR.fromString(qr.toString());
|
||||||
|
|
||||||
// We check if QR serialization/deserialization works
|
// We check if QR serialization/deserialization works
|
||||||
expect(readQR.applicationName).to.be.equals(applicationName);
|
expect(readQR.applicationName).to.be.equals(applicationName);
|
||||||
@ -76,7 +74,7 @@ describe("Waku Noise Sessions", () => {
|
|||||||
hsPattern,
|
hsPattern,
|
||||||
ephemeralKey: aliceEphemeralKey,
|
ephemeralKey: aliceEphemeralKey,
|
||||||
staticKey: aliceStaticKey,
|
staticKey: aliceStaticKey,
|
||||||
prologue: qrBytes,
|
prologue: qr.toByteArray(),
|
||||||
preMessagePKs,
|
preMessagePKs,
|
||||||
initiator: true,
|
initiator: true,
|
||||||
});
|
});
|
||||||
@ -84,7 +82,7 @@ describe("Waku Noise Sessions", () => {
|
|||||||
hsPattern,
|
hsPattern,
|
||||||
ephemeralKey: bobEphemeralKey,
|
ephemeralKey: bobEphemeralKey,
|
||||||
staticKey: bobStaticKey,
|
staticKey: bobStaticKey,
|
||||||
prologue: qrBytes,
|
prologue: qr.toByteArray(),
|
||||||
preMessagePKs,
|
preMessagePKs,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user