You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-26 17:03:12 +03:00
Moar typescriptification
This commit is contained in:
@@ -114,7 +114,7 @@ import url from "url";
|
||||
import { randomString } from "./randomstring";
|
||||
import { ReadStream } from "fs";
|
||||
import { WebStorageSessionStore } from "./store/session/webstorage";
|
||||
import { BackupManager, IKeyBackupCheck, TrustInfo } from "./crypto/backup";
|
||||
import { BackupManager, IKeyBackupCheck, IPreparedKeyBackupVersion, TrustInfo } from "./crypto/backup";
|
||||
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from "./models/MSC3089TreeSpace";
|
||||
|
||||
export type Store = StubStore | MemoryStore | LocalIndexedDBStoreBackend | RemoteIndexedDBStoreBackend;
|
||||
@@ -2180,7 +2180,7 @@ export class MatrixClient extends EventEmitter {
|
||||
public async prepareKeyBackupVersion(
|
||||
password: string,
|
||||
opts: IKeyBackupPrepareOpts = { secureSecretStorage: false },
|
||||
): Promise<IKeyBackupVersion> {
|
||||
): Promise<Pick<IPreparedKeyBackupVersion, "algorithm" | "auth_data" | "recovery_key">> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
@@ -2198,7 +2198,7 @@ export class MatrixClient extends EventEmitter {
|
||||
algorithm,
|
||||
auth_data,
|
||||
recovery_key,
|
||||
} as any; // TODO: Types
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2313,7 +2313,7 @@ export class MatrixClient extends EventEmitter {
|
||||
* Back up session keys to the homeserver.
|
||||
* @param {string} roomId ID of the room that the keys are for Optional.
|
||||
* @param {string} sessionId ID of the session that the keys are for Optional.
|
||||
* @param {integer} version backup version Optional.
|
||||
* @param {number} version backup version Optional.
|
||||
* @param {object} data Object keys to send
|
||||
* @return {Promise} a promise that will resolve when the keys
|
||||
* are uploaded
|
||||
@@ -2388,7 +2388,7 @@ export class MatrixClient extends EventEmitter {
|
||||
* @param {string} recoveryKey The recovery key
|
||||
* @return {Uint8Array} key backup key
|
||||
*/
|
||||
public keyBackupKeyFromRecoveryKey(recoveryKey: string): Uint8Array {
|
||||
public keyBackupKeyFromRecoveryKey(recoveryKey: string): ArrayLike<number> {
|
||||
return decodeRecoveryKey(recoveryKey);
|
||||
}
|
||||
|
||||
@@ -2478,9 +2478,7 @@ export class MatrixClient extends EventEmitter {
|
||||
opts: IKeyBackupRestoreOpts,
|
||||
): Promise<IKeyBackupRestoreResult> {
|
||||
const privKey = decodeRecoveryKey(recoveryKey);
|
||||
return this.restoreKeyBackup(
|
||||
privKey, targetRoomId, targetSessionId, backupInfo, opts,
|
||||
);
|
||||
return this.restoreKeyBackup(privKey, targetRoomId, targetSessionId, backupInfo, opts);
|
||||
}
|
||||
|
||||
// TODO: Types
|
||||
@@ -2498,7 +2496,7 @@ export class MatrixClient extends EventEmitter {
|
||||
}
|
||||
|
||||
private async restoreKeyBackup(
|
||||
privKey: Uint8Array,
|
||||
privKey: ArrayLike<number>,
|
||||
targetRoomId: string,
|
||||
targetSessionId: string,
|
||||
backupInfo: IKeyBackupVersion,
|
||||
|
||||
@@ -296,7 +296,7 @@ export class CrossSigningInfo extends EventEmitter {
|
||||
}
|
||||
|
||||
const privateKeys: Record<string, Uint8Array> = {};
|
||||
const keys: Record<string, object> = {};
|
||||
const keys: Record<string, any> = {}; // TODO types
|
||||
let masterSigning;
|
||||
let masterPub;
|
||||
|
||||
@@ -719,12 +719,12 @@ export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: O
|
||||
);
|
||||
}
|
||||
const pickleKey = Buffer.from(olmDevice._pickleKey);
|
||||
key = await encryptAES(encodeBase64(key), pickleKey, type);
|
||||
const encryptedKey = await encryptAES(encodeBase64(key), pickleKey, type);
|
||||
return store.doTxn(
|
||||
'readwrite',
|
||||
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||
(txn) => {
|
||||
store.storeSecretStorePrivateKey(txn, type, key);
|
||||
store.storeSecretStorePrivateKey(txn, type, encryptedKey);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import type { BinaryLike } from "crypto";
|
||||
|
||||
import { getCrypto } from '../utils';
|
||||
import { decodeBase64, encodeBase64 } from './olmlib';
|
||||
|
||||
@@ -23,6 +25,12 @@ const subtleCrypto = (typeof window !== "undefined" && window.crypto) ?
|
||||
// salt for HKDF, with 8 bytes of zeros
|
||||
const zeroSalt = new Uint8Array(8);
|
||||
|
||||
interface IEncryptedPayload {
|
||||
iv: string;
|
||||
ciphertext: string;
|
||||
mac: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* encrypt a string in Node.js
|
||||
*
|
||||
@@ -31,7 +39,7 @@ const zeroSalt = new Uint8Array(8);
|
||||
* @param {string} name the name of the secret
|
||||
* @param {string} ivStr the initialization vector to use
|
||||
*/
|
||||
async function encryptNode(data: string, key: Uint8Array, name: string, ivStr?: string) {
|
||||
async function encryptNode(data: string, key: Uint8Array, name: string, ivStr?: string): Promise<IEncryptedPayload> {
|
||||
const crypto = getCrypto();
|
||||
if (!crypto) {
|
||||
throw new Error("No usable crypto implementation");
|
||||
@@ -77,7 +85,7 @@ async function encryptNode(data: string, key: Uint8Array, name: string, ivStr?:
|
||||
* @param {Uint8Array} key the encryption key to use
|
||||
* @param {string} name the name of the secret
|
||||
*/
|
||||
async function decryptNode(data: IData, key: Uint8Array, name: string) {
|
||||
async function decryptNode(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
|
||||
const crypto = getCrypto();
|
||||
if (!crypto) {
|
||||
throw new Error("No usable crypto implementation");
|
||||
@@ -100,10 +108,9 @@ async function decryptNode(data: IData, key: Uint8Array, name: string) {
|
||||
+ decipher.final("utf8");
|
||||
}
|
||||
|
||||
function deriveKeysNode(key, name) {
|
||||
function deriveKeysNode(key: BinaryLike, name: string): [Buffer, Buffer] {
|
||||
const crypto = getCrypto();
|
||||
const prk = crypto.createHmac("sha256", zeroSalt)
|
||||
.update(key).digest();
|
||||
const prk = crypto.createHmac("sha256", zeroSalt).update(key).digest();
|
||||
|
||||
const b = Buffer.alloc(1, 1);
|
||||
const aesKey = crypto.createHmac("sha256", prk)
|
||||
@@ -123,7 +130,7 @@ function deriveKeysNode(key, name) {
|
||||
* @param {string} name the name of the secret
|
||||
* @param {string} ivStr the initialization vector to use
|
||||
*/
|
||||
async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr?: string) {
|
||||
async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr?: string): Promise<IEncryptedPayload> {
|
||||
let iv;
|
||||
if (ivStr) {
|
||||
iv = decodeBase64(ivStr);
|
||||
@@ -163,12 +170,6 @@ async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr
|
||||
};
|
||||
}
|
||||
|
||||
interface IData {
|
||||
ciphertext: string;
|
||||
iv: string;
|
||||
mac: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* decrypt a string in the browser
|
||||
*
|
||||
@@ -179,7 +180,7 @@ interface IData {
|
||||
* @param {Uint8Array} key the encryption key to use
|
||||
* @param {string} name the name of the secret
|
||||
*/
|
||||
async function decryptBrowser(data: IData, key: Uint8Array, name: string) {
|
||||
async function decryptBrowser(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
|
||||
const [aesKey, hmacKey] = await deriveKeysBrowser(key, name);
|
||||
|
||||
const ciphertext = decodeBase64(data.ciphertext);
|
||||
@@ -206,7 +207,7 @@ async function decryptBrowser(data: IData, key: Uint8Array, name: string) {
|
||||
return new TextDecoder().decode(new Uint8Array(plaintext));
|
||||
}
|
||||
|
||||
async function deriveKeysBrowser(key, name) {
|
||||
async function deriveKeysBrowser(key: Uint8Array, name: string): Promise<[CryptoKey, CryptoKey]> {
|
||||
const hkdfkey = await subtleCrypto.importKey(
|
||||
'raw',
|
||||
key,
|
||||
@@ -252,11 +253,11 @@ async function deriveKeysBrowser(key, name) {
|
||||
return await Promise.all([aesProm, hmacProm]);
|
||||
}
|
||||
|
||||
export function encryptAES(data: string, key: Uint8Array, name: string, ivStr?: string) {
|
||||
export function encryptAES(data: string, key: Uint8Array, name: string, ivStr?: string): Promise<IEncryptedPayload> {
|
||||
return subtleCrypto ? encryptBrowser(data, key, name, ivStr) : encryptNode(data, key, name, ivStr);
|
||||
}
|
||||
|
||||
export function decryptAES(data: IData, key: Uint8Array, name: string) {
|
||||
export function decryptAES(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
|
||||
return subtleCrypto ? decryptBrowser(data, key, name) : decryptNode(data, key, name);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,16 +29,11 @@ import { keyFromPassphrase } from './key_passphrase';
|
||||
import { sleep } from "../utils";
|
||||
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
|
||||
import { encodeRecoveryKey } from './recoverykey';
|
||||
import { IKeyBackupVersion } from "./keybackup";
|
||||
|
||||
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
|
||||
|
||||
type AuthData = Record<string, any>;
|
||||
|
||||
type BackupInfo = {
|
||||
algorithm: string,
|
||||
auth_data: AuthData, // eslint-disable-line camelcase
|
||||
[properties: string]: any,
|
||||
};
|
||||
type AuthData = IKeyBackupVersion["auth_data"];
|
||||
|
||||
type SigInfo = {
|
||||
deviceId: string,
|
||||
@@ -54,13 +49,22 @@ export type TrustInfo = {
|
||||
};
|
||||
|
||||
export interface IKeyBackupCheck {
|
||||
backupInfo: BackupInfo;
|
||||
backupInfo: IKeyBackupVersion;
|
||||
trustInfo: TrustInfo;
|
||||
}
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
export interface IPreparedKeyBackupVersion {
|
||||
algorithm: string;
|
||||
auth_data: AuthData;
|
||||
recovery_key: string;
|
||||
privateKey: Uint8Array;
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
/** A function used to get the secret key for a backup.
|
||||
*/
|
||||
type GetKey = () => Promise<Uint8Array>;
|
||||
type GetKey = () => Promise<ArrayLike<number>>;
|
||||
|
||||
interface BackupAlgorithmClass {
|
||||
algorithmName: string;
|
||||
@@ -77,7 +81,7 @@ interface BackupAlgorithm {
|
||||
encryptSession(data: Record<string, any>): Promise<any>;
|
||||
decryptSessions(ciphertexts: Record<string, any>): Promise<Record<string, any>[]>;
|
||||
authData: AuthData;
|
||||
keyMatches(key: Uint8Array): Promise<boolean>;
|
||||
keyMatches(key: ArrayLike<number>): Promise<boolean>;
|
||||
free(): void;
|
||||
}
|
||||
|
||||
@@ -86,7 +90,7 @@ interface BackupAlgorithm {
|
||||
*/
|
||||
export class BackupManager {
|
||||
private algorithm: BackupAlgorithm | undefined;
|
||||
public backupInfo: BackupInfo | undefined; // The info dict from /room_keys/version
|
||||
public backupInfo: IKeyBackupVersion | undefined; // The info dict from /room_keys/version
|
||||
public checkedForBackup: boolean; // Have we checked the server for a backup we can use?
|
||||
private sendingBackups: boolean; // Are we currently sending backups?
|
||||
constructor(private readonly baseApis: MatrixClient, public readonly getKey: GetKey) {
|
||||
@@ -98,7 +102,7 @@ export class BackupManager {
|
||||
return this.backupInfo && this.backupInfo.version;
|
||||
}
|
||||
|
||||
public static async makeAlgorithm(info: BackupInfo, getKey: GetKey): Promise<BackupAlgorithm> {
|
||||
public static async makeAlgorithm(info: IKeyBackupVersion, getKey: GetKey): Promise<BackupAlgorithm> {
|
||||
const Algorithm = algorithmsByName[info.algorithm];
|
||||
if (!Algorithm) {
|
||||
throw new Error("Unknown backup algorithm");
|
||||
@@ -106,7 +110,7 @@ export class BackupManager {
|
||||
return await Algorithm.init(info.auth_data, getKey);
|
||||
}
|
||||
|
||||
public async enableKeyBackup(info: BackupInfo): Promise<void> {
|
||||
public async enableKeyBackup(info: IKeyBackupVersion): Promise<void> {
|
||||
this.backupInfo = info;
|
||||
if (this.algorithm) {
|
||||
this.algorithm.free();
|
||||
@@ -145,7 +149,8 @@ export class BackupManager {
|
||||
public async prepareKeyBackupVersion(
|
||||
key?: string | Uint8Array | null,
|
||||
algorithm?: string | undefined,
|
||||
): Promise<BackupInfo> {
|
||||
// eslint-disable-next-line camelcase
|
||||
): Promise<IPreparedKeyBackupVersion> {
|
||||
const Algorithm = algorithm ? algorithmsByName[algorithm] : DefaultAlgorithm;
|
||||
if (!Algorithm) {
|
||||
throw new Error("Unknown backup algorithm");
|
||||
@@ -161,7 +166,7 @@ export class BackupManager {
|
||||
};
|
||||
}
|
||||
|
||||
public async createKeyBackupVersion(info: BackupInfo): Promise<void> {
|
||||
public async createKeyBackupVersion(info: IKeyBackupVersion): Promise<void> {
|
||||
this.algorithm = await BackupManager.makeAlgorithm(info, this.getKey);
|
||||
}
|
||||
|
||||
@@ -171,14 +176,14 @@ export class BackupManager {
|
||||
* one of the user's verified devices, start backing up
|
||||
* to it.
|
||||
*/
|
||||
public async checkAndStart(): Promise<{backupInfo: BackupInfo, trustInfo: TrustInfo}> {
|
||||
public async checkAndStart(): Promise<IKeyBackupCheck> {
|
||||
logger.log("Checking key backup status...");
|
||||
if (this.baseApis.isGuest()) {
|
||||
logger.log("Skipping key backup check since user is guest");
|
||||
this.checkedForBackup = true;
|
||||
return null;
|
||||
}
|
||||
let backupInfo: BackupInfo;
|
||||
let backupInfo: IKeyBackupVersion;
|
||||
try {
|
||||
backupInfo = await this.baseApis.getKeyBackupVersion();
|
||||
} catch (e) {
|
||||
@@ -255,7 +260,7 @@ export class BackupManager {
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
public async isKeyBackupTrusted(backupInfo: BackupInfo): Promise<TrustInfo> {
|
||||
public async isKeyBackupTrusted(backupInfo: IKeyBackupVersion): Promise<TrustInfo> {
|
||||
const ret = {
|
||||
usable: false,
|
||||
trusted_locally: false,
|
||||
@@ -569,7 +574,7 @@ export class Curve25519 implements BackupAlgorithm {
|
||||
): Promise<[Uint8Array, AuthData]> {
|
||||
const decryption = new global.Olm.PkDecryption();
|
||||
try {
|
||||
const authData: AuthData = {};
|
||||
const authData: Partial<AuthData> = {};
|
||||
if (!key) {
|
||||
authData.public_key = decryption.generate_key();
|
||||
} else if (key instanceof Uint8Array) {
|
||||
@@ -585,7 +590,7 @@ export class Curve25519 implements BackupAlgorithm {
|
||||
|
||||
return [
|
||||
decryption.get_private_key(),
|
||||
authData,
|
||||
authData as AuthData,
|
||||
];
|
||||
} finally {
|
||||
decryption.free();
|
||||
|
||||
@@ -138,11 +138,6 @@ interface IDeviceVerificationUpgrade {
|
||||
* could be established
|
||||
*/
|
||||
|
||||
interface IOlmSessionResult {
|
||||
device: DeviceInfo;
|
||||
sessionId?: string;
|
||||
}
|
||||
|
||||
interface IUserOlmSession {
|
||||
deviceIdKey: string;
|
||||
sessions: {
|
||||
@@ -1079,17 +1074,17 @@ export class Crypto extends EventEmitter {
|
||||
* @param {Uint8Array} key the private key
|
||||
* @returns {Promise} so you can catch failures
|
||||
*/
|
||||
public async storeSessionBackupPrivateKey(key: Uint8Array): Promise<void> {
|
||||
public async storeSessionBackupPrivateKey(key: ArrayLike<number>): Promise<void> {
|
||||
if (!(key instanceof Uint8Array)) {
|
||||
throw new Error(`storeSessionBackupPrivateKey expects Uint8Array, got ${key}`);
|
||||
}
|
||||
const pickleKey = Buffer.from(this.olmDevice._pickleKey);
|
||||
key = await encryptAES(olmlib.encodeBase64(key), pickleKey, "m.megolm_backup.v1");
|
||||
const encryptedKey = await encryptAES(olmlib.encodeBase64(key), pickleKey, "m.megolm_backup.v1");
|
||||
return this.cryptoStore.doTxn(
|
||||
'readwrite',
|
||||
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||
(txn) => {
|
||||
this.cryptoStore.storeSecretStorePrivateKey(txn, "m.megolm_backup.v1", key);
|
||||
this.cryptoStore.storeSecretStorePrivateKey(txn, "m.megolm_backup.v1", encryptedKey);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -2573,7 +2568,7 @@ export class Crypto extends EventEmitter {
|
||||
* an Object mapping from userId to deviceId to
|
||||
* {@link module:crypto~OlmSessionResult}
|
||||
*/
|
||||
ensureOlmSessionsForUsers(users: string[]): Promise<IOlmSessionResult> {
|
||||
ensureOlmSessionsForUsers(users: string[]): Promise<Record<string, Record<string, olmlib.IOlmSessionResult>>> {
|
||||
const devicesByUser = {};
|
||||
|
||||
for (let i = 0; i < users.length; ++i) {
|
||||
@@ -2598,9 +2593,7 @@ export class Crypto extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
return olmlib.ensureOlmSessionsForDevices(
|
||||
this.olmDevice, this.baseApis, devicesByUser,
|
||||
);
|
||||
return olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, devicesByUser);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3259,9 +3252,7 @@ export class Crypto extends EventEmitter {
|
||||
}
|
||||
const devicesByUser = {};
|
||||
devicesByUser[sender] = [device];
|
||||
await olmlib.ensureOlmSessionsForDevices(
|
||||
this.olmDevice, this.baseApis, devicesByUser, true,
|
||||
);
|
||||
await olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, devicesByUser, true);
|
||||
|
||||
this.lastNewSessionForced[sender][deviceKey] = Date.now();
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ export interface IKeyBackupVersion {
|
||||
count: number;
|
||||
etag: string;
|
||||
version: string; // number contained within
|
||||
recovery_key: string;
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
|
||||
@@ -52,6 +52,11 @@ export const MEGOLM_ALGORITHM = Algorithm.Megolm;
|
||||
*/
|
||||
export const MEGOLM_BACKUP_ALGORITHM = Algorithm.MegolmBackup;
|
||||
|
||||
export interface IOlmSessionResult {
|
||||
device: DeviceInfo;
|
||||
sessionId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt an event payload for an Olm device
|
||||
*
|
||||
@@ -209,11 +214,11 @@ export async function ensureOlmSessionsForDevices(
|
||||
olmDevice: OlmDevice,
|
||||
baseApis: MatrixClient,
|
||||
devicesByUser: Record<string, DeviceInfo[]>,
|
||||
force: boolean,
|
||||
otkTimeout: number,
|
||||
failedServers: string[],
|
||||
log: Logger,
|
||||
) {
|
||||
force = false,
|
||||
otkTimeout?: number,
|
||||
failedServers?: string[],
|
||||
log: Logger = logger,
|
||||
): Promise<Record<string, Record<string, IOlmSessionResult>>> {
|
||||
if (typeof force === "number") {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - backwards compatibility
|
||||
@@ -224,9 +229,6 @@ export async function ensureOlmSessionsForDevices(
|
||||
otkTimeout = force;
|
||||
force = false;
|
||||
}
|
||||
if (!log) {
|
||||
log = logger;
|
||||
}
|
||||
|
||||
const devicesWithoutSession = [
|
||||
// [userId, deviceId], ...
|
||||
@@ -439,9 +441,9 @@ async function _verifyKeyAndStartSession(
|
||||
return sid;
|
||||
}
|
||||
|
||||
interface IObject {
|
||||
unsigned: object;
|
||||
signatures: object;
|
||||
export interface IObject {
|
||||
unsigned?: object;
|
||||
signatures?: object;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -555,7 +557,7 @@ export function pkVerify(obj: IObject, pubKey: string, userId: string) {
|
||||
* @param {Uint8Array} uint8Array The data to encode.
|
||||
* @return {string} The base64.
|
||||
*/
|
||||
export function encodeBase64(uint8Array: ArrayBuffer): string {
|
||||
export function encodeBase64(uint8Array: ArrayBuffer | Uint8Array): string {
|
||||
return Buffer.from(uint8Array).toString("base64");
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export function encodeRecoveryKey(key: ArrayLike<number>): string {
|
||||
return base58key.match(/.{1,4}/g).join(" ");
|
||||
}
|
||||
|
||||
export function decodeRecoveryKey(recoveryKey: string): ArrayLike<number> {
|
||||
export function decodeRecoveryKey(recoveryKey: string): Uint8Array {
|
||||
const result = bs58.decode(recoveryKey.replace(/ /g, ''));
|
||||
|
||||
let parity = 0;
|
||||
|
||||
Reference in New Issue
Block a user