Skip to content

altcha-org/altcha-lib-dart

ALTCHA Dart Library

A Dart implementation of the ALTCHA Proof-of-Work mechanism v2 for server-side challenge creation and verification.

Installation

dart pub add altcha_lib

Or add manually to your pubspec.yaml:

dependencies:
  altcha_lib: ^0.3.0

Examples

Usage

Create a challenge

import 'dart:math';
import 'package:altcha_lib/altcha_lib.dart';
import 'package:altcha_lib/src/algorithms/pbkdf2.dart' as pbkdf2;

const hmacSecret = 'your-secret-key';

final challenge = await createChallenge(
  algorithm: 'PBKDF2/SHA-256',
  cost: 5000,
  counter: 5000 + Random.secure().nextInt(5000),
  deriveKey: pbkdf2.deriveKey,
  hmacSignatureSecret: hmacSecret,
);

// Serialize to JSON and send to the client.
print(challenge.toJson());

Verify a solution

import 'dart:convert';
import 'package:altcha_lib/altcha_lib.dart';
import 'package:altcha_lib/src/algorithms/pbkdf2.dart' as pbkdf2;

const hmacSecret = 'your-secret-key';

// Decode the base64 payload sent by the ALTCHA widget.
final payloadJson = jsonDecode(utf8.decode(base64Decode(widgetPayload)));
final payload = Payload.fromJson(payloadJson);

final result = await verifySolution(
  challenge: payload.challenge,
  solution: payload.solution,
  deriveKey: pbkdf2.deriveKey,
  hmacSignatureSecret: hmacSecret,
);

if (result.verified) {
  print('Challenge verified!');
} else if (result.expired) {
  print('Challenge expired.');
} else if (result.invalidSignature == true) {
  print('Invalid signature — possible tampering.');
} else {
  print('Invalid solution.');
}

Challenge with expiry

final challenge = await createChallenge(
  algorithm: 'PBKDF2/SHA-256',
  cost: 5000,
  deriveKey: pbkdf2.deriveKey,
  hmacSignatureSecret: hmacSecret,
  expiresAt: DateTime.now().add(const Duration(minutes: 10)),
);

Server signature verification (ALTCHA Sentinel)

import 'package:altcha_lib/altcha_lib.dart';

final result = await verifyServerSignature(
  payload: ServerSignaturePayload.fromJson(payloadJson),
  hmacSecret: hmacSecret,
);

if (result.verified) {
  final data = result.verificationData;
  print('Score: ${data?['score']}');
}

Solving challenges in parallel (Flutter / isolates)

solveChallengeIsolates spawns multiple Dart Isolates that race to solve the challenge in parallel, keeping the UI thread free. Each isolate tests an interleaved subset of counter values; the first to find a solution wins and all others are killed immediately.

import 'package:altcha_lib/altcha_lib.dart';
import 'package:altcha_lib/src/algorithms/pbkdf2.dart' as pbkdf2;

final solution = await solveChallengeIsolates(
  challenge: challenge,
  deriveKey: pbkdf2.deriveKey,
  concurrency: 4, // number of isolates; defaults to 1
);

Note: deriveKey must be a top-level or static function. All built-in algorithm deriveKey functions qualify. Closures cannot be sent across isolate boundaries.

Fields hash verification

final valid = await verifyFieldsHash(
  formData: {'email': 'user@example.com', 'message': 'Hello'},
  fields: ['email', 'message'],
  fieldsHash: receivedHash,
);

Algorithms

Algorithm identifier Notes
PBKDF2/SHA-256 Recommended default
PBKDF2/SHA-384 Higher security
PBKDF2/SHA-512 Higher security
SHA-256 Fast, low cost — use higher iteration count
SHA-384 Fast hash-chain variant
SHA-512 Fast hash-chain variant
SCRYPT Memory-hard; requires memoryCost and parallelism
ARGON2ID Memory-hard; requires memoryCost and optionally parallelism

Import the algorithm you need:

import 'package:altcha_lib/src/algorithms/pbkdf2.dart' as pbkdf2;
import 'package:altcha_lib/src/algorithms/sha.dart' as sha;
import 'package:altcha_lib/src/algorithms/scrypt.dart' as scrypt;
import 'package:altcha_lib/src/algorithms/argon2id.dart' as argon2id;

Or use adaptiveDeriveKey to dispatch automatically based on ChallengeParameters.algorithm:

import 'package:altcha_lib/algorithms.dart' show adaptiveDeriveKey;

final solution = await solveChallengeIsolates(
  challenge: challenge,
  deriveKey: adaptiveDeriveKey, // dispatches to PBKDF2, SHA, SCRYPT, or ARGON2ID
  concurrency: 4,
);

adaptiveDeriveKey is a top-level function and crosses isolate boundaries safely.

Scrypt requires additional parameters:

final challenge = await createChallenge(
  algorithm: 'SCRYPT',
  cost: 4096,       // N (must be power of 2)
  memoryCost: 8,    // r (block size)
  parallelism: 1,   // p
  deriveKey: scrypt.deriveKey,
  hmacSignatureSecret: hmacSecret,
);

Argon2id requires memoryCost (in KiB) and uses cost as the iteration count:

final challenge = await createChallenge(
  algorithm: 'ARGON2ID',
  cost: 2,          // iterations (time cost)
  memoryCost: 65536, // memory in KiB (64 MiB)
  parallelism: 1,   // lanes
  deriveKey: argon2id.deriveKey,
  hmacSignatureSecret: hmacSecret,
);

API Reference

adaptiveDeriveKey

Dispatches to the correct KDF based on parameters.algorithm. Import from package:altcha_lib/algorithms.dart.

Future<DeriveKeyResult> adaptiveDeriveKey(
  ChallengeParameters parameters,
  List<int> salt,
  List<int> password,
)

Supported algorithms (case-insensitive): PBKDF2/SHA-256, PBKDF2/SHA-384, PBKDF2/SHA-512, SHA-256, SHA-384, SHA-512, SCRYPT, ARGON2ID. Throws UnsupportedError for unknown values.

createChallenge

Future<Challenge> createChallenge({
  required String algorithm,
  required int cost,
  required DeriveKeyFunction deriveKey,
  int? counter,
  CounterMode counterMode,       // CounterMode.uint32 (default) or CounterMode.string
  Map<String, Object?>? data,
  Object? expiresAt,             // DateTime or int (Unix seconds)
  HmacAlgorithm hmacAlgorithm,   // default: HmacAlgorithm.sha256
  String? hmacKeySignatureSecret,
  String? hmacSignatureSecret,
  int keyLength,                 // default: 32
  String keyPrefix,              // default: '00'
  int? keyPrefixLength,
  int? memoryCost,
  int? parallelism,
})

solveChallenge

Future<Solution?> solveChallenge({
  required Challenge challenge,
  required DeriveKeyFunction deriveKey,
  CounterMode counterMode,
  int counterStart,              // default: 0
  int counterStep,               // default: 1
  Duration timeout,              // default: 90 seconds
  Stream<void>? abortSignal,
})

Returns null on timeout or abort.

solveChallengeIsolates

Future<Solution?> solveChallengeIsolates({
  required Challenge challenge,
  required DeriveKeyFunction deriveKey, // must be a top-level or static function
  int concurrency,                      // default: 1, max: 16
  CounterMode counterMode,
  Duration timeout,                     // default: 90 seconds
})

Returns null if all isolates time out without finding a solution.

verifySolution

Future<VerifySolutionResult> verifySolution({
  required Challenge challenge,
  required Solution solution,
  required DeriveKeyFunction deriveKey,
  required String hmacSignatureSecret,
  CounterMode counterMode,
  HmacAlgorithm hmacAlgorithm,
  String? hmacKeySignatureSecret,
})

verifyServerSignature

Future<VerifyServerSignatureResult> verifyServerSignature({
  required ServerSignaturePayload payload,
  required String hmacSecret,
})

verifyFieldsHash

Future<bool> verifyFieldsHash({
  required Map<String, dynamic> formData,
  required List<String> fields,
  required String fieldsHash,
  String algorithm,              // default: 'SHA-256'
})

Requirements

License

MIT

About

A Dart implementation of the ALTCHA Proof-of-Work mechanism v2 for server-side challenge creation and verification.

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages