mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
core: handle inbox item sync & decryption (#8733)
* core: handle inbox item sync & decryption Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> * core: minor refactors in handling inbox item sync Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> * core: use inbox item salt Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> * core: check inbox item version Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
This commit is contained in:
@@ -25,7 +25,12 @@ import {
|
|||||||
IKVStore
|
IKVStore
|
||||||
} from "./key-value";
|
} from "./key-value";
|
||||||
import { NNCrypto } from "./nncrypto";
|
import { NNCrypto } from "./nncrypto";
|
||||||
import type { Cipher, SerializedKey } from "@notesnook/crypto";
|
import type {
|
||||||
|
AsymmetricCipher,
|
||||||
|
Cipher,
|
||||||
|
SerializedKey,
|
||||||
|
SerializedKeyPair
|
||||||
|
} from "@notesnook/crypto";
|
||||||
import { isFeatureSupported } from "../utils/feature-check";
|
import { isFeatureSupported } from "../utils/feature-check";
|
||||||
import { IKeyStore } from "./key-store";
|
import { IKeyStore } from "./key-store";
|
||||||
import { User } from "@notesnook/core";
|
import { User } from "@notesnook/core";
|
||||||
@@ -160,6 +165,14 @@ export class NNStorage implements IStorage {
|
|||||||
return NNCrypto.decryptMulti(key, items, "text");
|
return NNCrypto.decryptMulti(key, items, "text");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decryptAsymmetric(
|
||||||
|
keyPair: SerializedKeyPair,
|
||||||
|
cipherData: AsymmetricCipher<"base64">
|
||||||
|
): Promise<string> {
|
||||||
|
cipherData.format = "base64";
|
||||||
|
return NNCrypto.decryptAsymmetric(keyPair, cipherData, "base64");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import Constants from "../../utils/constants.js";
|
|||||||
import TokenManager from "../token-manager.js";
|
import TokenManager from "../token-manager.js";
|
||||||
import Collector from "./collector.js";
|
import Collector from "./collector.js";
|
||||||
import { type HubConnection } from "@microsoft/signalr";
|
import { type HubConnection } from "@microsoft/signalr";
|
||||||
import Merger from "./merger.js";
|
import Merger, { handleInboxItems } from "./merger.js";
|
||||||
import { AutoSync } from "./auto-sync.js";
|
import { AutoSync } from "./auto-sync.js";
|
||||||
import { logger } from "../../logger.js";
|
import { logger } from "../../logger.js";
|
||||||
import { Mutex } from "async-mutex";
|
import { Mutex } from "async-mutex";
|
||||||
@@ -49,6 +49,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
SYNC_COLLECTIONS_MAP,
|
SYNC_COLLECTIONS_MAP,
|
||||||
SyncableItemType,
|
SyncableItemType,
|
||||||
|
SyncInboxItem,
|
||||||
SyncTransferItem
|
SyncTransferItem
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
import { DownloadableFile } from "../../database/fs.js";
|
import { DownloadableFile } from "../../database/fs.js";
|
||||||
@@ -233,7 +234,19 @@ class Sync {
|
|||||||
async fetch(deviceId: string, options: SyncOptions) {
|
async fetch(deviceId: string, options: SyncOptions) {
|
||||||
await this.checkConnection();
|
await this.checkConnection();
|
||||||
|
|
||||||
await this.connection?.invoke("RequestFetchV2", deviceId);
|
try {
|
||||||
|
await this.connection?.invoke("RequestFetchV3", deviceId);
|
||||||
|
} catch (error) {
|
||||||
|
if (
|
||||||
|
error instanceof Error &&
|
||||||
|
error.message.includes("HubException: Method does not exist")
|
||||||
|
) {
|
||||||
|
this.logger.warn(
|
||||||
|
"RequestFetchV3 failed, falling back to RequestFetchV2"
|
||||||
|
);
|
||||||
|
await this.connection?.invoke("RequestFetchV2", deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.conflictedNoteIds.length > 0) {
|
if (this.conflictedNoteIds.length > 0) {
|
||||||
await this.db
|
await this.db
|
||||||
@@ -478,6 +491,19 @@ class Sync {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.connection.on(
|
||||||
|
"SendInboxItems",
|
||||||
|
async (inboxItems: SyncInboxItem[]) => {
|
||||||
|
if (this.connection?.state !== HubConnectionState.Connected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await handleInboxItems(inboxItems, this.db);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getKey() {
|
private async getKey() {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
MaybeDeletedItem,
|
MaybeDeletedItem,
|
||||||
isDeleted
|
isDeleted
|
||||||
} from "../../types.js";
|
} from "../../types.js";
|
||||||
|
import { ParsedInboxItem, SyncInboxItem } from "./types.js";
|
||||||
|
|
||||||
const THRESHOLD = process.env.NODE_ENV === "test" ? 6 * 1000 : 60 * 1000;
|
const THRESHOLD = process.env.NODE_ENV === "test" ? 6 * 1000 : 60 * 1000;
|
||||||
class Merger {
|
class Merger {
|
||||||
@@ -146,3 +147,81 @@ export function isContentConflicted(
|
|||||||
return "merge";
|
return "merge";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function handleInboxItems(
|
||||||
|
inboxItems: SyncInboxItem[],
|
||||||
|
db: Database
|
||||||
|
) {
|
||||||
|
const inboxKeys = await db.user.getInboxKeys();
|
||||||
|
if (!inboxKeys) {
|
||||||
|
logger.error("No inbox keys found, cannot process inbox items.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of inboxItems) {
|
||||||
|
try {
|
||||||
|
if (await db.notes.exists(item.id)) {
|
||||||
|
logger.info("Inbox item already exists, skipping.", {
|
||||||
|
inboxItemId: item.id
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const decryptedKey = await db.storage().decryptAsymmetric(inboxKeys, {
|
||||||
|
alg: item.key.alg,
|
||||||
|
cipher: item.key.cipher,
|
||||||
|
format: "base64",
|
||||||
|
length: item.key.length
|
||||||
|
});
|
||||||
|
const decryptedItem = await db.storage().decrypt(
|
||||||
|
{ key: decryptedKey },
|
||||||
|
{
|
||||||
|
alg: item.alg,
|
||||||
|
iv: item.iv,
|
||||||
|
cipher: item.cipher,
|
||||||
|
format: "base64",
|
||||||
|
length: item.length,
|
||||||
|
salt: item.salt
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const parsed = JSON.parse(decryptedItem) as ParsedInboxItem;
|
||||||
|
if (parsed.type !== "note") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (parsed.version !== 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.notes.add({
|
||||||
|
id: item.id,
|
||||||
|
title: parsed.title,
|
||||||
|
favorite: parsed.favorite,
|
||||||
|
pinned: parsed.pinned,
|
||||||
|
readonly: parsed.readonly,
|
||||||
|
content: {
|
||||||
|
data: parsed?.content?.data ?? "",
|
||||||
|
type: "tiptap"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (parsed.archived !== undefined) {
|
||||||
|
await db.notes.archive(parsed.archived, item.id);
|
||||||
|
}
|
||||||
|
for (const notebookId of parsed.notebookIds || []) {
|
||||||
|
if (!(await db.notebooks.exists(notebookId))) continue;
|
||||||
|
await db.notes.addToNotebook(notebookId, item.id);
|
||||||
|
}
|
||||||
|
for (const tagId of parsed.tagIds || []) {
|
||||||
|
if (!(await db.tags.exists(tagId))) continue;
|
||||||
|
await db.relations.add(
|
||||||
|
{ type: "tag", id: tagId },
|
||||||
|
{ type: "note", id: item.id }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e, "Failed to process inbox item.", {
|
||||||
|
inboxItem: item
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,3 +48,24 @@ export type SyncTransferItem = {
|
|||||||
items: SyncItem[];
|
items: SyncItem[];
|
||||||
type: SyncableItemType;
|
type: SyncableItemType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SyncInboxItem = Omit<SyncItem, "format"> & {
|
||||||
|
key: Omit<Cipher<"base64">, "format" | "salt" | "iv">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ParsedInboxItem = {
|
||||||
|
title: string;
|
||||||
|
pinned?: boolean;
|
||||||
|
favorite?: boolean;
|
||||||
|
readonly?: boolean;
|
||||||
|
archived?: boolean;
|
||||||
|
notebookIds?: string[];
|
||||||
|
tagIds?: string[];
|
||||||
|
type: "note";
|
||||||
|
source: string;
|
||||||
|
version: 1;
|
||||||
|
content?: {
|
||||||
|
type: "html";
|
||||||
|
data: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AsymmetricCipher,
|
||||||
Cipher,
|
Cipher,
|
||||||
DataFormat,
|
DataFormat,
|
||||||
SerializedKey,
|
SerializedKey,
|
||||||
@@ -62,6 +63,10 @@ export interface IStorage {
|
|||||||
key: SerializedKey,
|
key: SerializedKey,
|
||||||
items: Cipher<"base64">[]
|
items: Cipher<"base64">[]
|
||||||
): Promise<string[]>;
|
): Promise<string[]>;
|
||||||
|
decryptAsymmetric(
|
||||||
|
keyPair: SerializedKeyPair,
|
||||||
|
cipherData: AsymmetricCipher<"base64">
|
||||||
|
): Promise<string>;
|
||||||
deriveCryptoKey(credentials: SerializedKey): Promise<void>;
|
deriveCryptoKey(credentials: SerializedKey): Promise<void>;
|
||||||
hash(
|
hash(
|
||||||
password: string,
|
password: string,
|
||||||
|
|||||||
@@ -19,12 +19,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import { base64_variants, ISodium } from "@notesnook/sodium";
|
import { base64_variants, ISodium } from "@notesnook/sodium";
|
||||||
import KeyUtils from "./keyutils.js";
|
import KeyUtils from "./keyutils.js";
|
||||||
import { Cipher, Output, DataFormat, SerializedKey } from "./types.js";
|
import {
|
||||||
|
Cipher,
|
||||||
|
Output,
|
||||||
|
DataFormat,
|
||||||
|
SerializedKey,
|
||||||
|
SerializedKeyPair,
|
||||||
|
AsymmetricCipher
|
||||||
|
} from "./types.js";
|
||||||
|
|
||||||
export default class Decryption {
|
export default class Decryption {
|
||||||
private static transformInput(
|
private static transformInput(
|
||||||
sodium: ISodium,
|
sodium: ISodium,
|
||||||
cipherData: Cipher<DataFormat>
|
cipherData: Cipher<DataFormat> | AsymmetricCipher<DataFormat>
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
let input: Uint8Array | null = null;
|
let input: Uint8Array | null = null;
|
||||||
if (
|
if (
|
||||||
@@ -55,7 +62,6 @@ export default class Decryption {
|
|||||||
): Output<TOutputFormat> {
|
): Output<TOutputFormat> {
|
||||||
if (!key.salt && cipherData.salt) key.salt = cipherData.salt;
|
if (!key.salt && cipherData.salt) key.salt = cipherData.salt;
|
||||||
const encryptionKey = KeyUtils.transform(sodium, key);
|
const encryptionKey = KeyUtils.transform(sodium, key);
|
||||||
|
|
||||||
const input = this.transformInput(sodium, cipherData);
|
const input = this.transformInput(sodium, cipherData);
|
||||||
const plaintext = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
|
const plaintext = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
|
||||||
null,
|
null,
|
||||||
@@ -74,6 +80,28 @@ export default class Decryption {
|
|||||||
) as Output<TOutputFormat>;
|
) as Output<TOutputFormat>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static decryptAsymmetric<TOutputFormat extends DataFormat>(
|
||||||
|
sodium: ISodium,
|
||||||
|
keyPair: SerializedKeyPair,
|
||||||
|
cipherData: AsymmetricCipher<DataFormat>,
|
||||||
|
outputFormat: TOutputFormat = "text" as TOutputFormat
|
||||||
|
): Output<TOutputFormat> {
|
||||||
|
const input = this.transformInput(sodium, cipherData);
|
||||||
|
const plaintext = sodium.crypto_box_seal_open(
|
||||||
|
input,
|
||||||
|
sodium.from_base64(keyPair.publicKey),
|
||||||
|
sodium.from_base64(keyPair.privateKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
outputFormat === "base64"
|
||||||
|
? sodium.to_base64(plaintext, base64_variants.URLSAFE_NO_PADDING)
|
||||||
|
: outputFormat === "text"
|
||||||
|
? sodium.to_string(plaintext)
|
||||||
|
: plaintext
|
||||||
|
) as Output<TOutputFormat>;
|
||||||
|
}
|
||||||
|
|
||||||
static createStream(
|
static createStream(
|
||||||
sodium: ISodium,
|
sodium: ISodium,
|
||||||
header: string,
|
header: string,
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ import {
|
|||||||
DataFormat,
|
DataFormat,
|
||||||
SerializedKey,
|
SerializedKey,
|
||||||
SerializedKeyPair,
|
SerializedKeyPair,
|
||||||
EncryptionKeyPair
|
EncryptionKeyPair,
|
||||||
|
AsymmetricCipher
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
|
||||||
export class NNCrypto implements INNCrypto {
|
export class NNCrypto implements INNCrypto {
|
||||||
@@ -96,6 +97,20 @@ export class NNCrypto implements INNCrypto {
|
|||||||
return decryptedItems;
|
return decryptedItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async decryptAsymmetric<TOutputFormat extends DataFormat>(
|
||||||
|
keyPair: SerializedKeyPair,
|
||||||
|
cipherData: AsymmetricCipher<DataFormat>,
|
||||||
|
outputFormat: TOutputFormat = "text" as TOutputFormat
|
||||||
|
): Promise<Output<TOutputFormat>> {
|
||||||
|
await this.init();
|
||||||
|
return Decryption.decryptAsymmetric(
|
||||||
|
this.sodium,
|
||||||
|
keyPair,
|
||||||
|
cipherData,
|
||||||
|
outputFormat
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async hash(password: string, salt: string): Promise<string> {
|
async hash(password: string, salt: string): Promise<string> {
|
||||||
await this.init();
|
await this.init();
|
||||||
return Password.hash(this.sodium, password, salt);
|
return Password.hash(this.sodium, password, salt);
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ import {
|
|||||||
Output,
|
Output,
|
||||||
Input,
|
Input,
|
||||||
EncryptionKeyPair,
|
EncryptionKeyPair,
|
||||||
SerializedKeyPair
|
SerializedKeyPair,
|
||||||
|
AsymmetricCipher
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
|
||||||
export interface IStreamable {
|
export interface IStreamable {
|
||||||
@@ -61,6 +62,12 @@ export interface INNCrypto {
|
|||||||
outputFormat?: TOutputFormat
|
outputFormat?: TOutputFormat
|
||||||
): Promise<Output<TOutputFormat>[]>;
|
): Promise<Output<TOutputFormat>[]>;
|
||||||
|
|
||||||
|
decryptAsymmetric<TOutputFormat extends DataFormat>(
|
||||||
|
keyPair: SerializedKeyPair,
|
||||||
|
cipherData: AsymmetricCipher<DataFormat>,
|
||||||
|
outputFormat?: TOutputFormat
|
||||||
|
): Promise<Output<TOutputFormat>>;
|
||||||
|
|
||||||
hash(password: string, salt: string): Promise<string>;
|
hash(password: string, salt: string): Promise<string>;
|
||||||
|
|
||||||
deriveKey(password: string, salt?: string): Promise<EncryptionKey>;
|
deriveKey(password: string, salt?: string): Promise<EncryptionKey>;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ISodium } from "@notesnook/sodium";
|
import { base64_variants, ISodium } from "@notesnook/sodium";
|
||||||
import {
|
import {
|
||||||
EncryptionKey,
|
EncryptionKey,
|
||||||
EncryptionKeyPair,
|
EncryptionKeyPair,
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ export type Cipher<TFormat extends DataFormat> = {
|
|||||||
length: number;
|
length: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AsymmetricCipher<TFormat extends DataFormat> = Omit<
|
||||||
|
Cipher<TFormat>,
|
||||||
|
"iv" | "salt"
|
||||||
|
>;
|
||||||
|
|
||||||
export type Output<TFormat extends DataFormat> =
|
export type Output<TFormat extends DataFormat> =
|
||||||
TFormat extends StringOutputFormat ? string : Uint8Array;
|
TFormat extends StringOutputFormat ? string : Uint8Array;
|
||||||
export type Input<TFormat extends DataFormat> = Output<TFormat>;
|
export type Input<TFormat extends DataFormat> = Output<TFormat>;
|
||||||
|
|||||||
@@ -121,6 +121,9 @@ export class Sodium implements ISodium {
|
|||||||
get crypto_box_keypair() {
|
get crypto_box_keypair() {
|
||||||
return sodium.crypto_box_keypair;
|
return sodium.crypto_box_keypair;
|
||||||
}
|
}
|
||||||
|
get crypto_box_seal_open() {
|
||||||
|
return sodium.crypto_box_seal_open;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertVariant(variant: base64_variants): sodium.base64_variants {
|
function convertVariant(variant: base64_variants): sodium.base64_variants {
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ import {
|
|||||||
crypto_secretstream_xchacha20poly1305_TAG_MESSAGE,
|
crypto_secretstream_xchacha20poly1305_TAG_MESSAGE,
|
||||||
crypto_box_keypair as sodium_native_crypto_box_keypair,
|
crypto_box_keypair as sodium_native_crypto_box_keypair,
|
||||||
crypto_box_PUBLICKEYBYTES,
|
crypto_box_PUBLICKEYBYTES,
|
||||||
crypto_box_SECRETKEYBYTES
|
crypto_box_SECRETKEYBYTES,
|
||||||
|
crypto_box_seal_open as sodium_native_crypto_box_seal_open,
|
||||||
|
crypto_box_SEALBYTES
|
||||||
} from "sodium-native";
|
} from "sodium-native";
|
||||||
import { Buffer } from "node:buffer";
|
import { Buffer } from "node:buffer";
|
||||||
import { base64_variants, ISodium } from "./types";
|
import { base64_variants, ISodium } from "./types";
|
||||||
@@ -377,6 +379,38 @@ function crypto_box_keypair(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function crypto_box_seal_open(
|
||||||
|
ciphertext: string | Uint8Array,
|
||||||
|
publicKey: Uint8Array,
|
||||||
|
privateKey: Uint8Array,
|
||||||
|
outputFormat?: Uint8ArrayOutputFormat | null
|
||||||
|
): Uint8Array;
|
||||||
|
function crypto_box_seal_open(
|
||||||
|
ciphertext: string | Uint8Array,
|
||||||
|
publicKey: Uint8Array,
|
||||||
|
privateKey: Uint8Array,
|
||||||
|
outputFormat: StringOutputFormat
|
||||||
|
): string;
|
||||||
|
function crypto_box_seal_open(
|
||||||
|
ciphertext: string | Uint8Array,
|
||||||
|
publicKey: Uint8Array,
|
||||||
|
privateKey: Uint8Array,
|
||||||
|
outputFormat?: StringOutputFormat | Uint8ArrayOutputFormat | null
|
||||||
|
): string | Uint8Array {
|
||||||
|
const cipher = toBuffer(ciphertext);
|
||||||
|
return wrap(
|
||||||
|
cipher.byteLength - crypto_box_SEALBYTES,
|
||||||
|
(message) =>
|
||||||
|
sodium_native_crypto_box_seal_open(
|
||||||
|
message,
|
||||||
|
cipher,
|
||||||
|
toBuffer(publicKey),
|
||||||
|
toBuffer(privateKey)
|
||||||
|
),
|
||||||
|
outputFormat
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function randombytes_buf(
|
function randombytes_buf(
|
||||||
length: number,
|
length: number,
|
||||||
outputFormat?: Uint8ArrayOutputFormat | null
|
outputFormat?: Uint8ArrayOutputFormat | null
|
||||||
@@ -594,6 +628,9 @@ export class Sodium implements ISodium {
|
|||||||
get crypto_box_keypair() {
|
get crypto_box_keypair() {
|
||||||
return crypto_box_keypair;
|
return crypto_box_keypair;
|
||||||
}
|
}
|
||||||
|
get crypto_box_seal_open() {
|
||||||
|
return crypto_box_seal_open;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { base64_variants, type ISodium };
|
export { base64_variants, type ISodium };
|
||||||
|
|||||||
@@ -62,4 +62,5 @@ export interface ISodium {
|
|||||||
get crypto_secretstream_xchacha20poly1305_TAG_FINAL(): typeof sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL;
|
get crypto_secretstream_xchacha20poly1305_TAG_FINAL(): typeof sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL;
|
||||||
get crypto_secretstream_xchacha20poly1305_TAG_MESSAGE(): typeof sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
|
get crypto_secretstream_xchacha20poly1305_TAG_MESSAGE(): typeof sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
|
||||||
get crypto_box_keypair(): typeof sodium.crypto_box_keypair;
|
get crypto_box_keypair(): typeof sodium.crypto_box_keypair;
|
||||||
|
get crypto_box_seal_open(): typeof sodium.crypto_box_seal_open;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user