An open API service indexing awesome lists of open source software.

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.

Awesome Lists containing this project

README

          

# @validpay/react-native-sdk

[![Tests](https://github.com/ValidPay-io/validpay-react-native-sdk/actions/workflows/test.yml/badge.svg)](https://github.com/ValidPay-io/validpay-react-native-sdk/actions/workflows/test.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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).