Module: Longfellow

Defined in:
lib/longfellow.rb,
lib/longfellow/c.rb,
lib/longfellow/errors.rb,
lib/longfellow/version.rb,
lib/longfellow/zk_spec.rb,
lib/longfellow/attribute.rb

Overview

Ruby bindings for Google’s longfellow-zk library (github.com/google/longfellow-zk): zero-knowledge proofs over ISO mdoc / mDL verifiable credentials.

The typical flow is:

spec    = Longfellow.zk_specs.last                 # pick a ZK specification
circuit = Longfellow.generate_circuit(spec)        # once; cache the result
proof   = Longfellow.prove(circuit:, zk_spec:, ...)   # prover side
Longfellow.verify(circuit:, zk_spec:, proof:, ...)    # verifier side

Defined Under Namespace

Modules: C, Errors Classes: Attribute, CircuitGenerationError, Error, NativeError, ProverError, VerifierError, ZkSpec

Constant Summary collapse

DEFAULT_DOC_TYPE =

Default mdoc document type (“org.iso.18013.5.1.mDL”).

"org.iso.18013.5.1.mDL"
VERSION =
"0.1.0"

Class Method Summary collapse

Class Method Details

.circuit_id(circuit, zk_spec) ⇒ String

Compute the 32-byte identifier (SHA-256 over the two sub-circuit ids) of a compressed circuit bundle.

Parameters:

Returns:

  • (String)

    32 binary bytes

Raises:



56
57
58
59
60
61
62
63
# File 'lib/longfellow.rb', line 56

def circuit_id(circuit, zk_spec)
  out = FFI::MemoryPointer.new(:uint8, C::CIRCUIT_ID_SIZE)
  data = byte_pointer(circuit)
  rc = C.circuit_id(out, data, circuit.bytesize, zk_spec.to_ptr)
  raise Error, "circuit_id failed (circuit could not be parsed)" if rc != 1

  out.read_bytes(C::CIRCUIT_ID_SIZE)
end

.find_zk_spec(system_name, circuit_hash) ⇒ Longfellow::ZkSpec?

Look up a ZK specification by system name and circuit hash.

Returns:



32
33
34
35
36
37
# File 'lib/longfellow.rb', line 32

def find_zk_spec(system_name, circuit_hash)
  ptr = C.find_zk_spec(system_name, circuit_hash)
  return nil if ptr.null?

  ZkSpec.new(C::ZkSpecStruct.new(ptr))
end

.generate_circuit(zk_spec) ⇒ String

Produce the compressed circuit bytes for the given specification. This is deterministic and can be cached and reused by provers and verifiers.

Parameters:

Returns:

  • (String)

    binary circuit bytes



43
44
45
46
47
48
49
# File 'lib/longfellow.rb', line 43

def generate_circuit(zk_spec)
  cb = FFI::MemoryPointer.new(:pointer)
  clen = FFI::MemoryPointer.new(:size_t)
  code = C.generate_circuit(zk_spec.to_ptr, cb, clen)
  Errors.check_circuit!(code)
  read_and_free(cb, clen)
end

.prove(circuit:, mdoc:, public_key_x:, public_key_y:, transcript:, attributes:, now:, zk_spec:) ⇒ String

Generate a zero-knowledge proof opening the requested attributes of an mdoc. Raises ProverError on failure (e.g. invalid mdoc, time outside the validity window, attribute not present).

Parameters:

  • circuit (String)

    compressed circuit bytes (see #generate_circuit)

  • mdoc (String)

    the full mdoc/mDL bytes

  • public_key_x (String)

    issuer public key X coordinate (string rep)

  • public_key_y (String)

    issuer public key Y coordinate (string rep)

  • transcript (String)

    session transcript bytes

  • attributes (Array<Longfellow::Attribute, Hash>)

    claims to open

  • now (String)

    current time, e.g. “2023-11-02T09:00:00Z”

  • zk_spec (Longfellow::ZkSpec)

Returns:

  • (String)

    binary proof bytes



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/longfellow.rb', line 78

def prove(circuit:, mdoc:, public_key_x:, public_key_y:, transcript:,
          attributes:, now:, zk_spec:)
  attrs_ptr, count = build_attributes(attributes)
  prf = FFI::MemoryPointer.new(:pointer)
  plen = FFI::MemoryPointer.new(:size_t)

  code = C.run_mdoc_prover(
    byte_pointer(circuit), circuit.bytesize,
    byte_pointer(mdoc), mdoc.bytesize,
    public_key_x.to_s, public_key_y.to_s,
    byte_pointer(transcript), transcript.bytesize,
    attrs_ptr, count,
    now.to_s,
    prf, plen, zk_spec.to_ptr
  )
  Errors.check_prover!(code)
  read_and_free(prf, plen)
end

.verify(circuit:, public_key_x:, public_key_y:, transcript:, attributes:, now:, proof:, zk_spec:, doc_type: DEFAULT_DOC_TYPE) ⇒ true

Verify a zero-knowledge proof against the requested attributes. Returns true on success, raises VerifierError otherwise.

Parameters:

  • doc_type (String) (defaults to: DEFAULT_DOC_TYPE)

    mdoc document type (defaults to DEFAULT_DOC_TYPE)

Returns:

  • (true)


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/longfellow.rb', line 102

def verify(circuit:, public_key_x:, public_key_y:, transcript:, attributes:,
           now:, proof:, zk_spec:, doc_type: DEFAULT_DOC_TYPE)
  attrs_ptr, count = build_attributes(attributes)

  code = C.run_mdoc_verifier(
    byte_pointer(circuit), circuit.bytesize,
    public_key_x.to_s, public_key_y.to_s,
    byte_pointer(transcript), transcript.bytesize,
    attrs_ptr, count,
    now.to_s,
    byte_pointer(proof), proof.bytesize,
    doc_type.to_s, zk_spec.to_ptr
  )
  Errors.check_verifier!(code)
  true
end

.zk_specsArray<Longfellow::ZkSpec>

All ZK specifications hardcoded into the native library.

Returns:



26
27
28
# File 'lib/longfellow.rb', line 26

def zk_specs
  @zk_specs ||= C.zk_specs.map { |s| ZkSpec.new(s) }.freeze
end