import { RSA, KeyPair } from 'react-native-rsa-native';
import { NativeModules } from 'react-native';
import type { Aes as AesType } from 'react-native-aes-crypto';
import { encode as btoa } from 'base-64';
import { toByteArray, fromByteArray } from 'base64-js';
import * as sss from './secrets.js';
const Aes: any = NativeModules.Aes;
const EPHEMERAL_KEY_SIZE = 16;
export async function split(
return (await sss.split(secret, numShare, threshold)).map(share => share.substring(1));
export function combine(shares: string[]): string {
return sss.combine(shares.map(share => BITS + share));
export interface SymCiphertext {
export interface HybridCiphertext {
encryptedMessage: SymCiphertext;
encryptedEphemeralKey: string;
export function randomBytes(length: number): Promise<string> {
return Aes.randomKey(length);
function generateRsaKeyPair(keySize: number = 4096): Promise<KeyPair> {
return RSA.generateKeys(keySize);
export function generateEncryptionKeyPair(
options: { type: 'rsa'; keySize: number } = { type: 'rsa', keySize: 4096 },
if (options.type === 'rsa') {
return generateRsaKeyPair(options.keySize);
throw new Error(`Unsupported type ${options.type}`);
export function generateSigningKeyPair(
options: { type: 'rsa'; keySize: number } = { type: 'rsa', keySize: 4096 },
return generateEncryptionKeyPair(options);
export async function encryptSym(
algorithm: AesType.Algorithms = 'aes-128-cbc',
const iv = await Aes.randomKey(16);
const ciphertext = await Aes.encrypt(data, symKey, iv, algorithm);
export function decryptSym(
ciphertext: SymCiphertext,
algorithm: AesType.Algorithms = 'aes-128-cbc',
const iv = base64ToHex(ciphertext.iv);
return Aes.decrypt(ciphertext.ciphertext, symKey, iv, algorithm);
export async function encryptAsym(
): Promise<string | HybridCiphertext> {
return await RSA.encrypt64(data, encKey);
// if error indicates plaintext too big
// avoid infinite recursive calls
if (data.length <= EPHEMERAL_KEY_SIZE) {
'Ephemeral key size is too large for the encryption key type',
return encryptHybrid(data, encKey);
export function decryptAsym(
ciphertext: string | HybridCiphertext,
if (typeof ciphertext === 'string') {
return RSA.decrypt64(ciphertext, decKey);
return decryptHybrid(ciphertext, decKey);
export async function encryptHybrid(
): Promise<HybridCiphertext> {
const ephemeralKey = await Aes.randomKey(EPHEMERAL_KEY_SIZE);
const encryptedMessage = await encryptSym(data, ephemeralKey);
const encryptedEphemeralKey = await encryptAsym(ephemeralKey, encKey);
if (typeof encryptedEphemeralKey !== 'string') {
'Ephemeral key size is too large for the encryption key type',
// encryptedEphemeralKey must be returned by crypto.publicEncrypt, i.e. a Uint8Array
return { encryptedMessage, encryptedEphemeralKey };
export async function decryptHybrid(
ciphertext: HybridCiphertext,
const { encryptedEphemeralKey } = ciphertext;
const ephemeralKey = await decryptAsym(encryptedEphemeralKey, decKey);
const { encryptedMessage } = ciphertext;
return decryptSym(encryptedMessage, ephemeralKey);
export function sign(data: string, signingKey: string): Promise<string> {
return RSA.sign(data, signingKey);
return RSA.verify(signature, data, verificationKey);
export function hash(...data: string[]): Promise<string> {
return Aes.sha256(data.join(''));
export function xor(a: string, b: string): string {
const bChunks = split2(b);
// eslint-disable-next-line no-bitwise
(parseInt(byteHex, 16) ^ parseInt(bChunks[i], 16))
function split2(str: string): string[] {
const chunks = str.match(/\w{2}/g);
throw new Error('Invalid encoding');
export function hexToBase64(hex: string): string {
const chunks = split2(hex);
chunks.map(byteHex => String.fromCharCode(parseInt(byteHex, 16))).join(''),
export function base64ToHex(b64: string): string {
return binToHex(toByteArray(b64));
export function hexToBin(hex: string): Uint8Array {
const chunks = split2(hex);
return Uint8Array.from(chunks.map(byteHex => parseInt(byteHex, 16)));
export function binToHex(bin: Uint8Array): string {
(str, byte) => str + byte.toString(16).padStart(2, '0'),
export { toByteArray as base64ToBin, fromByteArray as BinToBase64 };
export function hexToUtf8(hex: string): string {
export function utf8ToHex(a: string): string {
export function binToUtf8(bin: Uint8Array): string {
return String.fromCharCode(...bin);
export function utf8ToBin(a: string): Uint8Array {
return Uint8Array.from(a.split('').map(byte => byte.charCodeAt(0)));
export function base64ToUtf8(b64: string): string {
return binToUtf8(toByteArray(b64));
export function utf8ToBase64(a: string): string {
return fromByteArray(utf8ToBin(a));