https://github.com/validpay-io/validpay-react-native-sdk
Official React Native SDK for ValidPay CVCP — QR-based cryptographic document verification for mobile apps.
https://github.com/validpay-io/validpay-react-native-sdk
check-fraud cryptography document-verification mobile-sdk qr-verification react-native
Last synced: 10 days ago
JSON representation
Official React Native SDK for ValidPay CVCP — QR-based cryptographic document verification for mobile apps.
- Host: GitHub
- URL: https://github.com/validpay-io/validpay-react-native-sdk
- Owner: ValidPay-io
- License: mit
- Created: 2026-05-03T18:31:23.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-06-10T22:55:47.000Z (14 days ago)
- Last Synced: 2026-06-11T01:22:20.551Z (14 days ago)
- Topics: check-fraud, cryptography, document-verification, mobile-sdk, qr-verification, react-native
- Language: TypeScript
- Homepage: https://validpay.io
- Size: 76.2 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# @validpay/react-native-sdk
[](https://github.com/ValidPay-io/validpay-react-native-sdk/actions/workflows/test.yml)
[](LICENSE)
Official React Native SDK for the [ValidPay](https://validpay.com) document
verification API. AES-256-GCM client-side encryption, eight patented
protections, and zero runtime dependencies.
The wire format is byte-compatible with the [Python SDK][python-sdk] and the
[Node SDK][node-sdk] — an intent created with any ValidPay SDK can be
verified with any other.
[python-sdk]: https://github.com/ValidPay-io/validpay-python-sdk
[node-sdk]: https://github.com/ValidPay-io/validpay-node-sdk
## Installation
```bash
npm install @validpay/react-native-sdk
```
## Setup — pluggable crypto backend
React Native does not include Web Crypto. Apps choose their own AES-256-GCM
implementation (`expo-crypto`, `react-native-quick-crypto`, Web Crypto on
RN-future, etc.) and inject it once at startup. The SDK uses your
implementation everywhere — there's no native module bundled.
```ts
import { configure, CryptoProvider } from '@validpay/react-native-sdk';
import * as ExpoCrypto from 'expo-crypto';
class ExpoCryptoProvider implements CryptoProvider {
async getRandomBytes(length: number): Promise {
return ExpoCrypto.getRandomBytes(length);
}
async encrypt(plaintext: Uint8Array, key: Uint8Array): Promise {
// AES-256-GCM with a fresh 12-byte IV. Return base64 string of:
// iv (12 bytes) || authTag (16 bytes) || ciphertext
// ...consult expo-crypto / react-native-quick-crypto docs for the call.
}
async decrypt(ciphertext: string, key: Uint8Array): Promise { /* … */ }
async sha256(data: Uint8Array): Promise {
return ExpoCrypto.digestStringAsync(
ExpoCrypto.CryptoDigestAlgorithm.SHA256,
Buffer.from(data).toString('base64'),
{ encoding: ExpoCrypto.CryptoEncoding.HEX },
);
}
}
configure({ crypto: new ExpoCryptoProvider() });
```
If you use `splitKey()`, also import a synchronous random-bytes polyfill
once at app entry:
```ts
import 'react-native-get-random-values';
```
This provides `globalThis.crypto.getRandomValues`, which `splitKey()` needs
synchronously.
## Quick start
```ts
import { ValidPayClient } from '@validpay/react-native-sdk';
const client = new ValidPayClient({ apiKey: 'vp_live_…' });
// Issue
const result = await client.createIntent('check', {
payee: 'Alice',
amount: 1500,
});
console.log(result.retrievalId, result.key);
// → vp_abc123… (embed in QR)
// → base64 AES key — print on document, never store
// Verify (no API key required)
const verified = await client.verifyIntent(result.retrievalId, result.key);
console.log(verified.payload); // { payee: 'Alice', amount: 1500 }
console.log(verified.integrityVerified); // true
```
## Features
Eight patented protections, all live in this SDK:
| Patent | Feature | Method |
| ------ | ----------------------------- | --------------------------------------------------- |
| A | Multi-Hash Verification | `computeCommitmentHash`, automatic on verify |
| B | Blind Content Escrow | `encrypt` / `decrypt` (client-side AES-256-GCM) |
| C | Split-Key Verification | `createSplitKeyIntent` / `verifySplitKeyIntent` |
| D | Time-Locked Verification | `validFrom` / `validUntil` on all create methods |
| E | Selective Field Disclosure | `createSelectiveIntent` / `verifySelectiveIntent` |
| F | Chain-of-Custody Tracking | `getRevocationHistory` |
| G | Physical Medium Binding | `computeBindingHash`, `createBoundIntent` |
| H | Blind Revocation | `revokeIntent` / `reinstateIntent` |
## API reference
### `new ValidPayClient(options)`
| Option | Type | Default | Notes |
| ----------- | -------- | ----------------------------- | ---------------------------------------- |
| `apiKey` | `string` | required | Bearer key from the issuer dashboard. |
| `baseUrl` | `string` | `https://api.validpay.com` | Override for staging. |
| `timeoutMs` | `number` | `10000` | Per-request timeout (AbortController). |
### Core methods
```ts
client.createIntent(documentType, payload, opts?): Promise
client.createIntentBatch(intents): Promise // ≤100 items
client.verifyIntent(retrievalId, key): Promise
```
### Split-key (Patent C)
```ts
client.createSplitKeyIntent(documentType, payload, opts?): Promise
client.verifySplitKeyIntent(retrievalId, shareA): Promise
```
`createSplitKeyIntent` returns Share A as the `key` field — embed it on the
document as you would the regular key. Share B stays on the server and is
fetched at verify time.
### Selective disclosure (Patent E)
```ts
client.createSelectiveIntent(
documentType,
payload, // { fieldName: value }
policy, // { roleName: ['fieldName', …] }
opts?, // { splitKey?, validFrom?, validUntil? }
): Promise
client.verifySelectiveIntent(
retrievalId,
key,
role = 'full', // 'full' decrypts everything
): Promise
```
A `full` role with every field key is added to the policy automatically —
that's the issuer view.
### Time-locked verification (Patent D)
`validFrom` / `validUntil` (ISO-8601 strings) are accepted on every create
method. Every verify result includes `timeLockStatus` (`'valid'`,
`'not_yet_valid'`, `'expired'`, or `null`). The SDK never withholds the
payload — the caller decides whether to surface the status to the user.
```ts
const result = await client.createIntent('check', payload, {
validFrom: '2026-06-01T00:00:00Z',
validUntil: '2026-12-31T23:59:59Z',
});
const verified = await client.verifyIntent(result.retrievalId, result.key);
// verified.timeLockStatus → 'valid' | 'not_yet_valid' | 'expired'
```
### Revocation (Patent H)
```ts
client.revokeIntent(retrievalId, reason?): Promise<…>
client.reinstateIntent(retrievalId, reason?): Promise<…>
client.getRevocationHistory(retrievalId): Promise>
```
Revocation is "blind" — the server flips a status bit and stops returning
the ciphertext. It never decrypts the payload.
### Physical medium binding (Patent G)
```ts
import { computeBindingHash, compareBindingHashes } from '@validpay/react-native-sdk';
// Pre-process the binding-zone image to 8×8 grayscale with your image lib
// (expo-image-manipulator, react-native-image-resizer, etc.) and pass the
// raw bytes — this SDK doesn't bundle a JPEG/PNG decoder.
const imageBytes = new Uint8Array(64); // 8×8 grayscale
const hash = await computeBindingHash(imageBytes);
const result = compareBindingHashes(hashA, hashB, /* threshold */ 10);
result.match; // boolean
result.distance; // Hamming distance
```
`createBoundIntent` issues a binding-aware intent in one call:
```ts
const result = await client.createBoundIntent(
'check',
{ payee: 'Alice', amount: 1500 },
bindingZoneImage,
{ threshold: 10 },
);
```
### Errors
Every SDK and API failure is thrown as `ValidPayError`:
```ts
import { ValidPayError } from '@validpay/react-native-sdk';
try {
await client.verifyIntent('vp_missing', 'key');
} catch (e) {
if (e instanceof ValidPayError) {
e.code; // 'not_found' | 'unauthorized' | 'integrity_failure' | …
e.status; // HTTP status, when the error came from the API
e.details; // raw error body / extra context
}
}
```
## Cross-SDK compatibility
The wire format is identical across the Python, Node, and React Native SDKs:
```
base64(iv[12 bytes] || authTag[16 bytes] || ciphertext)
```
An intent created with `validpay` (Python) can be verified with this SDK,
and vice versa. Tests in `__tests__/crypto.test.ts` decrypt a hand-crafted
blob in the Python wire layout to enforce this guarantee.
## License
MIT — see [LICENSE](LICENSE).