Longfellow

Ruby bindings for Google's longfellow-zk, the zero-knowledge library for identity protocols. This gem lets you generate and verify zero-knowledge proofs over ISO mdoc / mDL verifiable credentials directly from Ruby.

The bindings load the upstream C ABI (run_mdoc_prover, run_mdoc_verifier, generate_circuit, ...) through Ruby-FFI. The native library is compiled from the vendored upstream sources at install time.

Supported version

This gem vendors google/longfellow-zk v0.9, pinned as a git submodule (vendor/longfellow-zk). The native library is built from exactly that revision, and the ZK system it exposes is longfellow-libzk-v1.

Only the ISO mdoc / mDL C ABI (circuits/mdoc/mdoc_zk.h) is wrapped. JWT and W3C Verifiable Credentials are not supported, because upstream provides no C ABI for them (they exist only as experimental C++ circuit templates).

Requirements

The native library is built from source during installation, so the build toolchain must be available:

  • A C++17 compiler (clang++ preferred, g++ works)
  • CMake >= 3.13
  • OpenSSL (libcrypto) development headers
  • zstd (libzstd) development headers

On Debian/Ubuntu:

sudo apt-get install build-essential cmake clang libssl-dev libzstd-dev

Installation

This gem vendors longfellow-zk as a git submodule, so a source checkout must initialize submodules before building:

git clone https://github.com/azuchi/longfellow.git
cd longfellow
git submodule update --init --recursive
bundle install
bundle exec rake compile

Add it to a project's Gemfile from git:

gem "longfellow", git: "https://github.com/azuchi/longfellow.git", submodules: true

Usage

require "longfellow"

# 1. Pick a ZK specification (these are hardcoded in the native library).
#    Each spec fixes the circuit format and the number of attributes it opens.
spec = Longfellow.zk_specs.first        # 1 attribute, longfellow-libzk v7
# or look one up by system name + circuit hash that a peer advertised:
# spec = Longfellow.find_zk_spec("longfellow-libzk-v1", "8d0792...")

# 2. Generate the circuit bytes for that spec. This is deterministic and can be
#    cached and shared between provers and verifiers.
circuit = Longfellow.generate_circuit(spec)
Longfellow.circuit_id(circuit, spec).unpack1("H*")  # == spec.circuit_hash

# 3. Describe the claims to open.
attribute = Longfellow::Attribute.new(
  namespace_id: "org.iso.18013.5.1",
  id:           "age_over_18",
  cbor_value:   "\xF5".b              # CBOR true
)

# 4. Prover side: produce a proof.
proof = Longfellow.prove(
  circuit:      circuit,
  mdoc:         mdoc_bytes,           # the full mdoc/mDL
  public_key_x: issuer_pkx,           # string representation of the issuer key
  public_key_y: issuer_pky,
  transcript:   session_transcript,
  attributes:   [attribute],
  now:          "2024-01-30T09:00:00Z",
  zk_spec:      spec
)

# 5. Verifier side: check it. Returns true, or raises Longfellow::VerifierError.
Longfellow.verify(
  circuit:      circuit,
  public_key_x: issuer_pkx,
  public_key_y: issuer_pky,
  transcript:   session_transcript,
  attributes:   [attribute],
  now:          "2024-01-30T09:00:00Z",
  proof:        proof,
  doc_type:     Longfellow::DEFAULT_DOC_TYPE,
  zk_spec:      spec
)

Attributes may also be passed as plain hashes:

attributes: [{ namespace_id: "org.iso.18013.5.1", id: "age_over_18", cbor_value: "\xF5".b }]

API

Method Description
Longfellow.zk_specs All ZK specifications compiled into the library.
Longfellow.find_zk_spec(system, hash) Look up a spec by system name and circuit hash (nil if unknown).
Longfellow.generate_circuit(spec) Compressed circuit bytes for a spec.
Longfellow.circuit_id(circuit, spec) 32-byte SHA-256 identifier of a circuit bundle.
Longfellow.prove(...) Generate a proof; raises Longfellow::ProverError on failure.
Longfellow.verify(...) Verify a proof; returns true or raises Longfellow::VerifierError.

Errors carry a stable #symbol and the raw native #code:

begin
  Longfellow.verify(...)
rescue Longfellow::VerifierError => e
  e.symbol  # e.g. :general_failure
  e.code    # the raw MdocVerifierErrorCode integer
end

Development

bundle exec rake compile          # build the native library
bundle exec rspec --tag ~slow     # fast unit specs
bundle exec rspec --tag slow      # full prove/verify round trip (~13s)

The slow integration spec exercises a real prover → verifier round trip against an mdoc test vector extracted from the upstream examples.

License

This gem is available under the MIT License.

The vendored upstream library, google/longfellow-zk, is distributed under the Apache License 2.0; see vendor/longfellow-zk/LICENSE.