npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fintoda/react-native-crypto-lib

v0.9.3

Published

Comprehensive cryptography library for React Native, backed by trezor-crypto C core via synchronous JSI / Turbo Module

Readme

@fintoda/react-native-crypto-lib

A comprehensive cryptography library for React Native, backed by a vendored trezor-crypto C core and exposed as a synchronous JSI / Turbo Module. Zero-copy ArrayBuffer transfers, no base64 bridge hops, no promises for operations that don't need them.

  • Hashes: SHA-1/256/384/512, SHA3-256/512, Keccak-256/512, RIPEMD-160, BLAKE-256, BLAKE2b, BLAKE2s, Groestl-512, SHA-256d, Hash160.
  • MAC / KDF: HMAC-SHA256/512, PBKDF2-SHA256/512, HKDF-SHA256/512.
  • RNG: cryptographically secure random bytes backed by the OS CSPRNG (arc4random_buf on iOS / Android NDK).
  • ECDSA on secp256k1 and nist256p1: deterministic RFC 6979 signing, low-S normalisation, public-key recovery, ECDH, DER encode/decode.
  • Schnorr / BIP-340: sign, verify, x-only keys, taproot tweak.
  • Ed25519 (RFC 8032) and X25519 key exchange.
  • AES-256: CBC (PKCS#7 or no padding), CTR, GCM with AAD.
  • BIP-39 mnemonics and BIP-32 / SLIP-10 HD derivation on secp256k1, nist256p1, and ed25519.
  • SLIP-39 Shamir secret sharing: split a master secret into threshold-of-N mnemonic shares (single or multi-group), recover from shares, passphrase encryption, RS1024 checksum validation.
  • [email protected] adapter so bitcoinjs-lib, bip32, ecpair work out of the box without a WASM build.
  • WebCrypto getRandomValues polyfill for packages that expect a browser-style crypto global (@noble/*, uuid, ethers, …).

Requirements

  • React Native with the new architecture enabled — the library is implemented as a C++ Turbo Module and does not register under the old bridge.
  • Hermes JS engine.
  • Developed and tested against React Native 0.85. Earlier versions down to 0.76 (the first release with stable C++ Turbo Module codegen) may work but are not tested; older than 0.76 definitely will not.
  • iOS and Android minimums are whatever the host React Native version requires — this library adds no extra floor on top.

Installation

yarn add @fintoda/react-native-crypto-lib
cd ios && pod install

No extra Metro / Babel configuration needed.

Quick start

import {
  hash,
  ecdsa,
  bip39,
  bip32,
  installCryptoPolyfill,
} from '@fintoda/react-native-crypto-lib';

// Install the WebCrypto polyfill once, at app startup, so any library
// that pokes at globalThis.crypto.getRandomValues keeps working.
installCryptoPolyfill();

// BIP-39 → BIP-32 → ECDSA signing:
const mnemonic = bip39.generate(128);
const seed = bip39.toSeed(mnemonic, '');
const root = bip32.fromSeed(seed, 'secp256k1');
const leaf = bip32.derive(root, "m/44'/0'/0'/0/0");

const msg = hash.sha256(new TextEncoder().encode('hello'));
const { signature, recId } = ecdsa.sign(leaf.privateKey!, msg);
const ok = ecdsa.verify(leaf.publicKey, signature, msg);

All Uint8Array inputs are consumed zero-copy when they cover the whole underlying buffer; otherwise the wrapper makes one defensive slice.

Table of contents


hash

One-shot digest functions. Each returns a fresh Uint8Array.

import { hash } from '@fintoda/react-native-crypto-lib';

| function | output bytes | notes | |---|---|---| | hash.sha1(data) | 20 | legacy; not recommended | | hash.sha256(data) | 32 | | | hash.sha384(data) | 48 | | | hash.sha512(data) | 64 | | | hash.sha3_256(data) | 32 | NIST SHA3 | | hash.sha3_512(data) | 64 | NIST SHA3 | | hash.keccak_256(data) | 32 | pre-NIST Keccak (Ethereum) | | hash.keccak_512(data) | 64 | pre-NIST Keccak | | hash.ripemd160(data) | 20 | | | hash.blake256(data) | 32 | | | hash.blake2b(data) | 64 | | | hash.blake2s(data) | 32 | | | hash.groestl512(data) | 64 | | | hash.sha256d(data) | 32 | SHA256(SHA256(x)), Bitcoin | | hash.hash160(data) | 20 | RIPEMD160(SHA256(x)) |

All take a Uint8Array and return a Uint8Array.

mac

import { mac } from '@fintoda/react-native-crypto-lib';

| function | output | |---|---| | mac.hmac_sha256(key, msg) | 32 bytes | | mac.hmac_sha512(key, msg) | 64 bytes |

kdf

import { kdf } from '@fintoda/react-native-crypto-lib';
  • kdf.pbkdf2_sha256(password, salt, iterations, length)Uint8Array(length)
  • kdf.pbkdf2_sha512(password, salt, iterations, length)Uint8Array(length)
  • kdf.hkdf_sha256(ikm, salt, info, length)Uint8Array(length)
  • kdf.hkdf_sha512(ikm, salt, info, length)Uint8Array(length)

length is capped at 255 * hashLen per RFC 5869 / the PBKDF2 reference impl. iterations is capped at 10,000,000 as a sanity check.

rng

import { rng } from '@fintoda/react-native-crypto-lib';
  • rng.bytes(count)Uint8Array — cryptographically secure random bytes. count is capped at 1 MiB per call.
  • rng.uint32()number — unsigned 32-bit integer.
  • rng.uniform(max)number — uniform random integer in [0, max), no modulo bias. max must be a positive integer.

ecdsa

import { ecdsa, type Curve } from '@fintoda/react-native-crypto-lib';

Curve is 'secp256k1' | 'nist256p1'. All functions default to secp256k1 when the argument is omitted.

  • ecdsa.randomPrivate(curve?)Uint8Array(32) — uniform in [1, n-1].
  • ecdsa.validatePrivate(priv, curve?)boolean.
  • ecdsa.getPublic(priv, compact = true, curve?)Uint8Array (33 bytes compressed or 65 bytes uncompressed).
  • ecdsa.readPublic(pub, compact = true, curve?) → re-serialises a public key into the requested form. Validates it on the way.
  • ecdsa.validatePublic(pub, curve?)boolean.
  • ecdsa.sign(priv, digest, curve?){ signature: Uint8Array(64), recId: 0 | 1 | 2 | 3 }. RFC 6979 deterministic, output is low-S.
  • ecdsa.verify(pub, sig64, digest, curve?)boolean. Accepts both low-S and high-S signatures; use tinySecp256k1.verify(..., true) for strict BIP-62 low-S enforcement.
  • ecdsa.recover(sig64, digest, recId, curve?)Uint8Array(65) uncompressed.
  • ecdsa.ecdh(priv, pub, curve?)Uint8Array(33) compressed shared point. If you want the legacy SHA256(x) behaviour, do hash.sha256(ecdh(priv, pub).slice(1)).
  • ecdsa.sigToDer(sig64) / ecdsa.sigFromDer(der).

schnorr

BIP-340 Schnorr on secp256k1, x-only keys.

import { schnorr } from '@fintoda/react-native-crypto-lib';
  • schnorr.getPublic(priv)Uint8Array(32) x-only pubkey.
  • schnorr.verifyPublic(pub32)boolean.
  • schnorr.sign(priv, digest, aux?)Uint8Array(64). aux is the optional 32-byte auxiliary randomness; when omitted, 32 zero bytes are used (spec-compliant).
  • schnorr.verify(pub32, sig64, digest)boolean.
  • schnorr.tweakPublic(pub32, merkleRoot?){ pub: Uint8Array(32), parity: 0 | 1 }. Implements the BIP-341 TapTweak: if merkleRoot is omitted or zero-length, the key-spend tweak H_TapTweak(pub) is used.
  • schnorr.tweakPrivate(priv, merkleRoot?)Uint8Array(32).

ed25519 / x25519

Vanilla Ed25519 (RFC 8032, SHA-512) and X25519 ECDH.

import { ed25519, x25519 } from '@fintoda/react-native-crypto-lib';
  • ed25519.getPublic(priv32)Uint8Array(32) pubkey from a 32-byte seed.
  • ed25519.sign(priv32, msg)Uint8Array(64) signature over the raw message (Ed25519 hashes the message internally).
  • ed25519.verify(pub32, sig64, msg)boolean.
  • x25519.getPublic(priv32)Uint8Array(32).
  • x25519.scalarmult(priv32, pub32)Uint8Array(32) shared secret.

ecc

Low-level secp256k1 point / scalar primitives used by the tinySecp256k1 adapter but also exported directly. All return null on operations that collapse to the point at infinity / an out-of-range scalar; malformed inputs throw.

import { ecc } from '@fintoda/react-native-crypto-lib';
  • ecc.pointAdd(a, b, compressed = true)Uint8Array | null.
  • ecc.pointAddScalar(p, tweak, compressed = true)Uint8Array | null.
  • ecc.pointMultiply(p, tweak, compressed = true)Uint8Array | null.
  • ecc.privateAdd(d, tweak)Uint8Array | null.
  • ecc.privateSub(d, tweak)Uint8Array | null.
  • ecc.privateNegate(d)Uint8Array.
  • ecc.xOnlyPointAddTweak(p32, tweak32){ parity: 0 | 1, xOnlyPubkey: Uint8Array(32) } | null. This is the bare scalar tweak, not the BIP-341 TapTweak — use schnorr.tweakPublic for the latter.

tinySecp256k1

Drop-in implementation of the TinySecp256k1Interface consumed by bitcoinjs-lib, ecpair and bip32. Wire it up wherever those packages expect an eccLib:

import { tinySecp256k1 } from '@fintoda/react-native-crypto-lib';
import BIP32Factory from 'bip32';
import ECPairFactory from 'ecpair';

const bip32Factory = BIP32Factory(tinySecp256k1);
const ECPair = ECPairFactory(tinySecp256k1);

Full method list ([email protected]):

  • Validation: isPoint, isPointCompressed, isXOnlyPoint, isPrivate.
  • Point ops: pointAdd, pointAddScalar, pointMultiply, pointFromScalar, pointCompress.
  • X-only: xOnlyPointFromScalar, xOnlyPointFromPoint, xOnlyPointAddTweak, xOnlyPointAddTweakCheck.
  • Scalars: privateAdd, privateSub, privateNegate.
  • ECDSA: sign(h, d, e?), signRecoverable(h, d, e?), verify(h, Q, sig, strict?), recover(h, sig, recId, compressed?).
  • Schnorr: signSchnorr(h, d, e?), verifySchnorr(h, Q, sig).

Notes:

  • The optional e / extra-entropy argument on ECDSA/Schnorr is ignored for ECDSA (we're RFC 6979 deterministic) and forwarded as aux_rand for Schnorr.
  • verify(..., strict = true) enforces BIP-62 low-S; the default (false) accepts high-S, matching tiny-secp256k1.

aes

AES-256 with caller-provided IV / nonce. Key is always 32 bytes.

import { aes, type CbcPadding } from '@fintoda/react-native-crypto-lib';

CBC

  • aes.cbc.encrypt(key32, iv16, data, padding = 'pkcs7') → ciphertext.
  • aes.cbc.decrypt(key32, iv16, data, padding = 'pkcs7') → plaintext. Throws on invalid PKCS#7 padding.
  • padding is 'pkcs7' | 'none'. With 'none' the input length must be a multiple of 16.

CTR

  • aes.ctr.crypt(key32, iv16, data) → same-length buffer. Symmetric: the same call encrypts and decrypts.

GCM

  • aes.gcm.encrypt(key32, nonce, plaintext, aad?)Uint8Array(plaintext.length + 16). The trailing 16 bytes are the authentication tag (WebCrypto / node:crypto layout).
  • aes.gcm.decrypt(key32, nonce, sealed, aad?) → plaintext. Throws aes_256_gcm_decrypt: authentication failed if the tag does not match.
  • nonce length is validated as non-empty; 12 bytes is recommended.

bip39

import { bip39, type Bip39Strength } from '@fintoda/react-native-crypto-lib';
  • bip39.generate(strength = 128)string. strength is one of 128 | 160 | 192 | 224 | 256 (12 / 15 / 18 / 21 / 24 words).
  • bip39.fromEntropy(entropy)string. Entropy length must be 16, 20, 24, 28 or 32 bytes.
  • bip39.validate(mnemonic)boolean. Verifies the checksum and wordlist membership.
  • bip39.toSeed(mnemonic, passphrase = '')Uint8Array(64). PBKDF2-HMAC-SHA512, 2048 rounds, salt = "mnemonic" + passphrase.

bip32

BIP-32 / SLIP-10 HD key derivation on three curves. The JS HDNode carries a 108-byte opaque raw blob that all native derive calls take as input — one JSI hop per full path:

import { bip32, type Bip32Curve, type HDNode } from '@fintoda/react-native-crypto-lib';
  • bip32.fromSeed(seed, curve = 'secp256k1')HDNode. curve is 'secp256k1' | 'nist256p1' | 'ed25519'.
  • bip32.derive(node, path)HDNode. path is either a BIP-32 string ("m/44'/0'/0'/0/0") or a numeric index array (hardened indices must have the 0x80000000 bit set).
  • bip32.derivePublic(node, path)HDNode — public-only derivation; throws on hardened indices and on ed25519 (SLIP-10 public derivation is undefined for ed25519).
  • bip32.neuter(node)HDNode — returns a copy with the private key stripped.
  • bip32.serialize(node, version, isPrivate) → xprv / xpub string. Typical Bitcoin mainnet versions: 0x0488ADE4 (xprv), 0x0488B21E (xpub).
  • bip32.deserialize(str, version, curve, isPrivate)HDNode.
  • bip32.fingerprint(node)number — this node's own fingerprint.
  • bip32.HARDENED_OFFSET = 0x80000000.

HDNode shape:

type HDNode = {
  curve: Bip32Curve;
  depth: number;
  parentFingerprint: number;
  childNumber: number;
  chainCode: Uint8Array;   // 32 bytes
  privateKey: Uint8Array | null; // 32 bytes or null when neutered
  publicKey: Uint8Array;   // 33 bytes, compressed (or SLIP-10 ed25519 pub)
  raw: Uint8Array;         // 108-byte opaque blob passed back to derive()
};

SLIP-10 notes

  • On ed25519, every child must be hardened. Non-hardened derivation throws.
  • On ed25519, the 33-byte publicKey has a leading 0x00 tag byte followed by 32 bytes of the Ed25519 public key — the same convention trezor-crypto uses. You typically pass privateKey into ed25519.sign rather than using the 33-byte form directly.

slip39

SLIP-39 Shamir secret sharing — split a master secret into mnemonic shares that can be distributed for safekeeping.

import { slip39, type Slip39Group } from '@fintoda/react-native-crypto-lib';

Single group (threshold-of-N)

const secret = new Uint8Array(16); // 16–32 bytes, even length
// Split into 5 shares, any 3 recover the secret
const shares: string[] = slip39.generate(secret, 'passphrase', 3, 5);

// Recover from any 3 shares
const recovered: Uint8Array = slip39.combine(
  [shares[0], shares[2], shares[4]],
  'passphrase',
);

Multi-group

// 2-of-3 groups; each group has its own member threshold
const groups: string[][] = slip39.generateGroups(secret, 'passphrase', 2, [
  { threshold: 2, count: 3 }, // group 0: 2-of-3
  { threshold: 3, count: 5 }, // group 1: 3-of-5
  { threshold: 1, count: 1 }, // group 2: 1-of-1 (backup)
]);

// Recover with shares from 2 groups
const recovered = slip39.combine(
  [...groups[0].slice(0, 2), groups[2][0]],
  'passphrase',
);

API

  • slip39.generate(masterSecret, passphrase?, threshold, shareCount, iterationExponent? = 1)string[]. Returns shareCount SLIP-39 mnemonics. masterSecret must be 16–32 bytes (even). Passphrase encrypts the secret via a 4-round Feistel cipher with PBKDF2-HMAC-SHA256 (10 000 × 2^exp iterations per round).
  • slip39.generateGroups(masterSecret, passphrase?, groupThreshold, groups, iterationExponent? = 1)string[][]. Two-level Shamir: groups is an array of { threshold, count }.
  • slip39.combine(mnemonics, passphrase?)Uint8Array. Recover the master secret from enough shares (single or multi-group).
  • slip39.validateMnemonic(mnemonic)boolean. Wordlist + RS1024 checksum validation.

webcrypto

React Native / Hermes doesn't ship globalThis.crypto.getRandomValues, which breaks any library that expects it (@noble/*, uuid@v4, ethers, bitcoinjs-lib in some paths, tweetnacl, …). This module plugs the hole:

import {
  getRandomValues,
  installCryptoPolyfill,
} from '@fintoda/react-native-crypto-lib';
  • installCryptoPolyfill()boolean. Assigns getRandomValues onto globalThis.crypto when it's missing. Idempotent — if a native crypto.getRandomValues already exists, it is not overwritten. Returns true if the polyfill was installed. Call this once at app startup, before importing any package that touches crypto.
  • getRandomValues(typedArray) → the same typed array, filled with CSPRNG bytes. Throws a QuotaExceededError-equivalent on requests larger than 65,536 bytes (the WebCrypto cap). Accepts any integer typed array view (Uint8Array, Int32Array, …).
// index.js (very top of the app)
import { installCryptoPolyfill } from '@fintoda/react-native-crypto-lib';
installCryptoPolyfill();

Compatibility notes

  • All public APIs are synchronous. No promises, no awaits.
  • Inputs are always Uint8Array; outputs are always fresh Uint8Array views. Nothing is base64 at the edge.
  • Key formats match the wider ecosystem: compressed (33 B) and uncompressed (65 B) for secp256k1 / nist256p1, x-only (32 B) for BIP-340 Schnorr, raw 32-byte seeds for Ed25519 / X25519.
  • The library is not a complete drop-in for earlier CryptoLib versions: function names are grouped into namespaces (hash.*, ecdsa.*, bip32.* …), signing is sync, HDNode fields use Uint8Array instead of base64 strings. A mechanical migration is straightforward.

Contributing

License

MIT. Vendored trezor-crypto is under its own MIT license; see vendor/trezor-crypto/crypto/LICENSE.