vc_zkp 1.1.0 copy "vc_zkp: ^1.1.0" to clipboard
vc_zkp: ^1.1.0 copied to clipboard

VCs for ZKPs — Dart. Native Poseidon/EdDSA via prebuilt Rust binaries (macOS, iOS, Android). App developers do not need Rust installed.

example/example.dart

// Run from the repository root:
//   dart run example/example.dart
//
// Reference Circom sources for field layout and public signals live under:
//   example/circuits/
//
// For in-app proof generation on mobile, you can combine witness calculation with a
// fast Groth16 prover, for example:
//   - https://github.com/iden3/flutter-rapidsnark — Flutter wrapper around rapidsnark
//   - https://github.com/iden3/circom-witnesscalc — witness calculator (CVM / native)
//   - https://github.com/iden3/rapidsnark — C++ Groth16 prover for circom/snarkjs artifacts
//
// Typical flow: compile circuits → trusted setup → generate witness (circom-witnesscalc
// or WASM calculator) → prove (rapidsnark / snarkjs) → verify off-device or on-chain.
//
// E2E boundary note:
// - This example covers document signing and deterministic circuit input preparation.
// - Next step is to generate a ZKP against your Circom circuit using external tooling
//   (for Dart/Flutter apps, see the libraries listed above).
// - Verifier then verifies the resulting proof with the same proving stack/tooling.

import 'dart:convert';
import 'dart:math';

import 'package:vc_zkp/src/rust_eddsa_helper_ffi.dart';
import 'package:vc_zkp/vc_zkp.dart';

/// BN254 field prime (same as `lib/src/commitments.dart`).
final _bn254Prime = BigInt.parse(
  '21888242871839275222246405745257275088548364400416034343698204186575808495617',
);

String _randomPrivateKeyHex(Random random) {
  return List<int>.generate(32, (_) => random.nextInt(256))
      .map((b) => b.toRadixString(16).padLeft(2, '0'))
      .join();
}

/// Uniform random non-zero field element as a decimal string (for `blinder_factor`).
String _randomBlinderFactor(Random random) {
  while (true) {
    final bytes = List<int>.generate(31, (_) => random.nextInt(256));
    var v = BigInt.zero;
    for (final b in bytes) {
      v = (v << 8) + BigInt.from(b);
    }
    v = v % _bn254Prime;
    if (v > BigInt.zero) {
      return v.toString();
    }
  }
}

Future<void> main() async {
  final random = Random.secure();
  final issuerPrivateKeyHex = _randomPrivateKeyHex(random);
  final holderPrivateKeyHex = _randomPrivateKeyHex(random);
  final blinderFactor = _randomBlinderFactor(random);

  final crypto = RustEddsaHelperFfi();
  final keyDerivation = VcKeyDerivation(crypto: crypto);

  // Derive BabyJub public keys from private keys via package API.
  final issuerPub = await keyDerivation.derivePublicKey(
    privateKeyHex: issuerPrivateKeyHex,
  );
  final holderPub = await keyDerivation.derivePublicKey(
    privateKeyHex: holderPrivateKeyHex,
  );

  final header = <String, Object?>{
    'version': '1',
    'issued_at': 1700000000,
    'expires_at': 1900000000,
    'issuer': 'did:untraceable:example-issuer',
    'holderAx': holderPub.ax,
    'holderAy': holderPub.ay,
    'schema': 'schema-hash-for-example',
  };

  final disclosures = <Disclosure>[
    const Disclosure(field: 'claim_0', value: 0),
    const Disclosure(field: 'claim_1', value: 1),
    const Disclosure(field: 'claim_2', value: 2),
    const Disclosure(field: 'claim_3', value: 'a'),
    const Disclosure(field: 'claim_4', value: 'b'),
  ];

  final issuer = VcIssuer(crypto: crypto);
  final document = await issuer.createSignedDocument(
    header: header,
    disclosures: disclosures,
    issuerPrivateKeyHex: issuerPrivateKeyHex,
  );

  final holder = VcHolder(crypto: crypto);
  final holderInputs = await holder.prepareForCircuit(document);

  final challengeNonce = List<int>.generate(32, (_) => random.nextInt(256));
  final challengeNonceHex = challengeNonce
      .map((b) => b.toRadixString(16).padLeft(2, '0'))
      .join();
  final challengeNonceBi = BigInt.parse(challengeNonceHex, radix: 16);
  final challengeDigest = await crypto.poseidonHashFieldElements(
    // Domain-tagged two-input challenge hash to avoid [x] vs [x,0] ambiguity.
    <String>['1', challengeNonceBi.toString()],
  );

  final challengeSig = await holder.signPreparedDigest(
    digest: challengeDigest,
    privateKeyHex: holderPrivateKeyHex,
  );

  final sig = document.signature;
  final circuitInputs = <String, Object?>{
    'header_commitments': holderInputs.headerCommitments,
    'payload_commitments': holderInputs.payloadCommitments,
    'issuerAx': issuerPub.ax,
    'issuerAy': issuerPub.ay,
    'documentR8x': sig.r8[0],
    'documentR8y': sig.r8[1],
    'documentS': sig.s,
    'holderAx': holderInputs.holderAx,
    'holderAy': holderInputs.holderAy,
    'challengeDigest': challengeDigest,
    'challengeR8x': challengeSig.r8[0],
    'challengeR8y': challengeSig.r8[1],
    'challengeS': challengeSig.s,
    'blinder_factor': blinderFactor,
  };

  final bundle = <String, Object?>{
    'circuitInputs': circuitInputs,
    'disclosures': disclosures.map((d) => d.toJson()).toList(),
    '_exampleOnly': <String, Object?>{
      'note':
          'Secrets for local experimentation only — remove before sharing logs or artifacts.',
      'issuerPrivateKeyHex': issuerPrivateKeyHex,
      'holderPrivateKeyHex': holderPrivateKeyHex,
      'challengeNonceHex': challengeNonceHex,
    },
  };

  // ignore: avoid_print
  print(const JsonEncoder.withIndent('  ').convert(bundle));
}
0
likes
0
points
0
downloads

Publisher

verified publisheraffinidi.com

Weekly Downloads

VCs for ZKPs — Dart. Native Poseidon/EdDSA via prebuilt Rust binaries (macOS, iOS, Android). App developers do not need Rust installed.

Repository (GitHub)
View/report issues

Topics

#dart

License

unknown (license)

Dependencies

code_assets, crypto, ffi, hooks, path

More

Packages that depend on vc_zkp