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.