Package Exports
- react-native-altcha
- react-native-altcha/package.json
Readme
react-native-altcha
React Native widget for ALTCHA — supports PoW v2 with SHA, PBKDF2, Argon2id, and Scrypt algorithms, Human Interaction Signature (HIS) collection, and server-side verification.
Requirements
- React Native 0.75+
- Expo SDK 50+
react-native-quick-crypto— native crypto (required on iOS/Android)
Benchmarks
Because of sub-optimal PBKDF2 performance in react-native-quick-crypto on iOS, we recommend using Argon2id if your project targets both platforms.
| Platform | 1 Worker | 4 Workers |
|---|---|---|
| Android (PBKDF2) | 7.2s (~0.4x) | 1.8s (~1.4x) |
| Android (Argon2id) | 5.0s (~1.6x) | 1.7s (~2.0x) |
| iOS (PBKDF2) | 6.3s (~0.5x) | 2.5s (~0.5x) |
| iOS (Argon2id) | 2.3s (~2.2x) | 0.9s (~2.4x) |
Measurements were taken using PBKDF2/SHA-256 (cost=5000, counter=5000) and Argon2id (cost=2, memoryCost=32768, counter=100). Multipliers represent performance relative to the WebCrypto baseline on the same device.
All algorithms use crypto.subtle or the Node.js-style crypto module exposed by react-native-quick-crypto. On web, SHA-* and PBKDF2/* use the browser's native crypto.subtle.
Installation
npm install react-native-altcha
expo install expo-audio expo-localization react-native-svg
expo install react-native-quick-crypto react-native-nitro-modules react-native-quick-base64
expo prebuildCrypto setup
Call install() in your app entry point before anything else renders. This polyfills global.crypto with a native C++ implementation (OpenSSL on Android, CommonCrypto on iOS):
// index.js
import { Platform } from 'react-native';
if (Platform.OS !== 'web') {
require('react-native-quick-crypto').install();
}
import { registerRootComponent } from 'expo';
import App from './src/App';
registerRootComponent(App);On web, crypto.subtle is available natively in the browser — no setup required.
Basic usage
import { useRef } from 'react';
import { AltchaWidget } from 'react-native-altcha';
import type { AltchaWidgetRef } from 'react-native-altcha';
export default function App() {
const ref = useRef<AltchaWidgetRef>(null);
return (
<AltchaWidget
ref={ref}
challenge="..."
onVerified={(payload) => {
// Send payload to your backend
console.log('Verified:', payload);
}}
/>
);
}Human Interaction Signature (HIS)
Some ALTCHA configurations require HIS data — touch patterns, scroll, keyboard focus — to distinguish humans from bots. The server signals this by responding to the challenge request with { his: { url: "..." } } instead of a challenge.
For best coverage, attach the HIS collector at the app root so all touches are captured globally:
import { useMemo } from 'react';
import { View } from 'react-native';
import { HisCollector } from 'react-native-altcha';
export default function App() {
const hisProps = useMemo(() => HisCollector.attach(), []);
return (
// Spread on the outermost View — captures all touches in the subtree
<View style={{ flex: 1 }} {...hisProps}>
{/* your app */}
</View>
);
}To also capture scroll events, pass getScrollHandler() to your ScrollView:
<ScrollView
onScroll={HisCollector.shared.getScrollHandler()}
scrollEventThrottle={50}
>Props
AltchaWidget
| Prop | Type | Default | Description |
|---|---|---|---|
challenge |
string | Challenge |
— | URL to fetch the challenge from, or a pre-fetched Challenge object |
origin |
string |
— | App identifier sent as Origin/Referer headers (e.g. "com.example.myapp") |
onVerified |
(payload: string) => void |
required | Called with base64-encoded payload on success |
onFailed |
(error: string) => void |
— | Called if verification fails |
onServerVerification |
(data: ServerSignatureVerificationData) => void |
— | Called after successful server verification |
colorScheme |
'light' | 'dark' |
system | Override color scheme |
themes |
{ light?: Partial<AltchaTheme>, dark?: Partial<AltchaTheme> } |
— | Theme overrides |
style |
ViewStyle & { color?, fontSize? } |
— | Widget container style |
hideLogo |
boolean |
false |
Hide the ALTCHA logo |
hideFooter |
boolean |
false |
Hide the "Protected by ALTCHA" footer |
locale |
string |
system locale | Language code ('en', 'de', 'es', 'fr', 'it', 'pt') |
customTranslations |
Record<string, Partial<Translation>> |
— | Override UI strings per locale |
workers |
number |
1 |
Concurrent solver chains — values > 1 parallelise across CPU cores |
minDuration |
number |
— | Minimum solving time (ms) — pads short solves to avoid instant UI flicker |
debug |
boolean |
false |
Log requests and HIS data to console |
fetch |
typeof fetch |
global fetch |
Custom fetch implementation |
httpHeaders |
Record<string, string> |
— | Extra HTTP headers added to all requests |
AltchaWidget ref methods
ref.current?.reset(); // Reset widget to unverified state
ref.current?.verify(); // Manually trigger verificationTheming
import { AltchaWidget, defaultThemes } from 'react-native-altcha';
<AltchaWidget
themes={{
light: {
...defaultThemes.light,
primaryColor: '#6200ea',
primaryContentColor: '#ffffff',
},
dark: {
...defaultThemes.dark,
primaryColor: '#bb86fc',
},
}}
challenge="..."
onVerified={...}
/>AltchaTheme fields
| Field | Default (light) | Default (dark) |
|---|---|---|
backgroundColor |
#ffffff |
#1a1a1a |
borderColor |
#cccccc |
#444444 |
primaryColor |
#007AFF |
#007AFF |
primaryContentColor |
#ffffff |
#ffffff |
textColor |
#000000 |
#ffffff |
errorColor |
#ff0000 |
#ff0000 |
Internationalization
Built-in languages: English, German, Spanish, French, Italian, Portuguese.
Override individual strings:
<AltchaWidget
customTranslations={{
en: { label: 'Prove you are human' },
}}
challenge="..."
onVerified={...}
/>Advanced: direct solver API
import {
solveChallenge,
solveChallengeWorkers,
hasSubtleCrypto,
hasArgon2Support,
hasScryptSupport,
} from 'react-native-altcha';
// Single-threaded
const solution = await solveChallenge(challenge);
// Multi-threaded (N concurrent chains)
const solution = await solveChallengeWorkers(challenge, 4);Advanced: HIS collector static API
// Attach the global singleton and get root View props
const hisProps = HisCollector.attach(options?); // { maxSamples?, sampleInterval? }
// Stop collection
HisCollector.detach();
// Access the shared instance
HisCollector.shared.export(); // { focus, pointer, scroll, touch, maxTouchPoints, time }
HisCollector.shared.getScrollHandler(); // onScroll handler for ScrollViewLicense
MIT