Ruby bindings for the Confium open-source framework for multi-stakeholder threshold cryptography.

Confium supports three deployment modes:

  • Mode 1 — Peer-to-peer threshold cryptography: nodes do TC directly (MPC, distributed custody, BFT)

  • Mode 2 — TC PKI replacement: drop-in for existing PKI consumers (PKCS#11 server, OpenSSL 3.0 provider, JCE)

  • Mode 3 — TC Certificate PKI: institutional deployments with custom certificate formats (OIML CNML, BIPM, pharma, accreditation)

This gem wraps the high-value Confium subsystems via a pure-Rust native extension (rb_sys + magnus, no C dependencies). Pre-compiled platform gems are published for Linux (glibc + musl), macOS, and Windows, so gem install needs no Rust toolchain there; other platforms build from source at install time.

Installation

Add to your Gemfile:

gem "confium", "~> 0.7"

Or install directly:

$ gem install confium

Prerequisites

  • Ruby ≥ 3.1

  • Nothing else on the pre-compiled platforms: x86_64-linux, aarch64-linux, x86_64-linux-musl, aarch64-linux-musl, x86_64-darwin, arm64-darwin, x64-mingw-ucrt (each gem carries one extension per Ruby C-ABI window)

Source builds (other platforms, or installing from the repo) also need the Rust stable toolchain (rustup default stable) — and nothing else: the extension has no C dependencies to satisfy.

Quick start

require "confium"

# Transparency log
tree = Confium::Transparency::MerkleTree.new
seq = tree.append(artifact_hash_bytes_32)
root = tree.root  # binary String, 32 bytes
proof = tree.inclusion_proof(seq)
proof.verify(root)  # => true

# Composite signature (PQ migration)
kp = Confium::Composite.generate_ed25519_keypair
component = Confium::Composite.sign_ed25519(kp["private_key"], "message")
sig = Confium::Composite::Signature.new([component])
result = sig.verify("message")
result.all_verified?  # => true

# Attribute-based threshold policy
pred = Confium::Attributes.parse(%q{and(min_count("role:director", 3), min_distinct("region", 3))})
alice = Confium::Attributes::Signer.new
alice.add("role:director", "yes")
alice.add("region", "europe")
# ... bob, carol similarly
pred.satisfied_by?([alice, bob, carol])  # => true

# X.509 certificate
cert = Confium::PKI::Certificate.from_pem(File.read("cert.pem"))
puts cert.fingerprint_sha256
puts cert.valid_at?(Time.now.utc.iso8601)  # => true

# Real threshold cryptography: P-256 Shamir
kp = Confium::TC::FrostP256.generate_keypair
shares = Confium::TC::FrostP256.split_secret(kp["private_key"], 3, 5)
recovered = Confium::TC::FrostP256.recover_secret(shares.first(3).map { |s| { "x" => s.x, "y" => s.y_bytes } })
recovered == kp["private_key"]  # => true

# Distributed key generation + 2-of-3 FROST-ed25519 signing (each party
# holds its own Confium::TC::Session and only exchanges round messages)
parties = %w[alice bob carol]
sessions = parties.each_with_index.map do |id, i|
  Confium::TC::Session.new("FROST-ed25519-dkg", parties: parties, threshold: 2, this_party_idx: i)
end
# run rounds: session.round_step(messages_in) -> {"outgoing" => [...], "complete" => bool}
# broadcast "outgoing" payloads between sessions until every session is complete
dkg_share = sessions.first.result  # this party's share (embeds the group public key)

signers = sessions.first(2).map.with_index do |s, i|
  Confium::TC::Session.new("FROST-ed25519",
                           parties: parties.first(2), threshold: 2, this_party_idx: i,
                           local_share: dkg_share, message: "authentic message")
end
# run rounds as above; the completed session's #result is an RFC 8032
# Ed25519 signature verifiable under the group public key

API surface (v0.7)

Confium::Transparency

  • MerkleTree.new / #append(artifact_hash) / #root / #length / #empty? / #inclusion_proof(seq)

  • InclusionProof#sequence / #steps / #verify(root)

  • Ots.stamp(hash) / Ots.verify(proof) / Ots.upgrade(proof) — real OpenTimestamps anchoring over the wire protocol (calendar HTTP, servers tried in order; the network round trip releases the GVL). Ots::Client.new(servers) for a custom pool; Ots::Proof#verify replays the op tree and classifies attestations. Network failures raise — there is no silent nil

Confium::Composite — PQ migration

  • .generate_ed25519_keypair{ private_key:, public_key: }

  • .sign_ed25519(private_key, message) → component Hash

  • Signature.new(components) / #verify(message) / #component_count / #algorithms / #to_json

  • .from_json(json) / .components_to_json(components) / .canonical_json(json, data)

  • VerificationResult#all_verified? / #per_component

Confium::Attributes — threshold policy DSL

  • .parse(dsl_expr)Predicate

  • Predicate#satisfied_by?(signers)

  • Signer.new / #add(key, value) / #has?(key) / #values(key)

  • DSL: min_count("attr", n), min_distinct("attr", n), any("attr"), all("attr"), none("attr"), and(…​), or(…​), not(p)

Confium::PKI

  • Certificate.from_der(bytes) / .from_pem(str) / #to_der / #to_pem / #fingerprint_sha256 / #serial_hex / #not_before / #not_after / #valid_at?(iso8601) / #public_key_bytes; DER parse failures carry a byte offset in the error details

  • CertificateBuilder / CMS::SignedDataBuilder — build certificates and signed data in Ruby

  • PathValidator.validate(leaf, intermediates, root, now_iso8601 = nil)PathValidationResult (chain validation leaf → root at a point in time)

  • CSR.from_der / .from_pem / #to_der / #to_pem

  • CMS::SignedData.from_json / #to_json / #signer_count / #content_type / #content / #certificate_count / #certificate_at(i)

  • CMS::Content#bytes / #length

  • XMLDSig.canonicalize(xml) / .canonicalize_exclusive(xml) (Canonical XML RFC 3076 + Exclusive C14N)

  • Cnml — institutional certificate workflow helpers

Confium::Identity — actor roles

  • .actor_types["manufacturer", "testing_lab", "issuing_authority_officer", "biml_director", "quorum_coordinator", "verifier"]

  • Actor.from_json(json) / #to_json / #actor_id / #actor_type / #quorum_id / #registered_at / #expires_at / #certificate_count

Confium::Config — deployment manifest

  • Manifest.from_toml(toml_str) / #deployment_name / #operator / #manifest_version / #tier_count / #tier_name_at(i) / #quorum_count / #validate / #valid?

Confium::TC::FrostP256 — real P-256 Shamir + ECDSA

  • .generate_keypair{ private_key: (32 bytes), public_key: (65 bytes SEC1) }

  • .split_secret(secret, t, n) → array of Share with #x, #y_bytes

  • .recover_secret([{x:, y:}, …​]) → secret bytes

  • .sign(private_key, message){ der:, fixed: }

Confium::TC::ElGamalP256 — threshold ElGamal-P256 KEM

  • .encapsulate(public_key_bytes){ ciphertext: { c1:, c2: }, shared_secret: }

  • .partial_decrypt(party_index, share_bytes, ciphertext){ party_index:, bytes: }

  • .aggregate_partials(partials, threshold, ciphertext) → shared_secret bytes

Confium::TC::Cmp20 / Confium::TC::Gg18 — threshold ECDSA protocols

  • .keygen(threshold, parties){ public_key, shares }

  • .sign(shares, public_key, message) → DER ECDSA signature

Confium::TC::Session — per-party threshold sessions (real FROST)

  • Session.new(scheme, parties:, threshold:, this_party_idx:, local_share: nil, message: nil)"FROST-ed25519-dkg" for distributed key generation, "FROST-ed25519" for signing (pass the DKG share and the message)

  • #scheme_name / #threshold / #party_count / #this_party_idx / #round / #complete?

  • #round_step(messages){ "outgoing" ⇒ [{from, to, round, payload}…​], "complete" ⇒ bool } — feed each round’s incoming messages, broadcast the outgoing ones

  • #result — after DKG: share blob (group public key + party share); after signing: RFC 8032 Ed25519 signature

Confium::TC::Coordinator — quorum signing orchestration

  • Coordinator.new(quorum_id:) / #create_session(message:, threshold:, scheme: "CMP20-ECDSA-P256") / #submit_commitment / #submit_share / #aggregate / #session_state

  • SigningSession#add_commitment / #add_share / #threshold_met? / #aggregate — the seam every transport adapts to

  • NetworkCoordinator — TCP/NDJSON server (#start / #stop / #running?); SignerClient — remote submit + aggregate with typed RemoteError

  • ShareFile — encrypted share-file persistence

Confium::Store::Keystore — key handles and remote signing

  • Keystore.new(backend, **options) / .backends

  • #sign(key_id, algorithm, message) → signature bytes (sign-with-handle contract; cloud KMS backends aws-kms / gcp-kms / azure-keyvault via cargo features)

Confium::Audit — structured audit trail

  • Audit.sink = / Audit.sink / Audit.enabled? / Audit.record(…​)

  • Sink (base) / MemorySink / StderrSink / FileSink

  • OtlpSink.new(endpoint:, headers:, service_name:, …​) — OTLP/HTTP export with retry and #dropped counter

Confium::OpenPGP

  • .armor(data, type) / .dearmor(data) — RFC 9580 §6 ASCII armor with CRC-24 (pure Ruby)

  • .verify_detached(message, signature, keys) / .verify(…​) — real signature verification when the extension is built with the pgp feature; PGP_AVAILABLE reports it

Confium::Transport — coordinator clients over any transport

  • SignerClient.new(url) — connect over any registry transport: tcp://host:port (local/trusted) or noise://host:port?key=<hex>&pinned=<hex> (Noise_XX encrypted, stable identity + peer pinning)

  • #register(signer_id, quorum_id) / #create_session(quorum_id, scheme, message, threshold, num_parties) → session id

  • #submit_commitment(session_id, signer_id, bytes) / #submit_share(session_id, signer_id, bytes)

  • CoordinatorServer.new(url) — serve coordinator sessions over any linked scheme (test/in-process use)

Confium::ERS — evidence records

  • EvidenceRecord.build_initial(…​) / #renew(…​) / #renewal_count (RFC 4998 / RFC 6283 archival)

Confium::Policy / Confium::SecureBytes

  • Policy.jurisdiction = :eu — jurisdictional algorithm policy (EU/US profiles; SM2/SM3/SM4 for CN planned)

  • SecureBytes.wrap(raw) — zeroize-on-clear wrapper for private keys, shares, and secrets (#bytes non-destructive read, #clear)

Errors

  • Typed error hierarchy (ParseError, ThresholdError, VerificationError, PolicyViolationError, …​) — native failures surface positional details hashes (including DER byte offsets for parse errors)

Development

After checking out the repo:

$ bundle install
$ bundle exec rake compile       # build the Rust extension
$ bundle exec rspec              # 226 specs

Architecture

The native extension lives at ext/confium_native/ as a Cargo workspace. It depends on the confium-* crates from crates.io (not on the local Rust workspace). The Ruby-side API is defined entirely in the extension via magnus — lib/confium.rb just requires the compiled .bundle.

License

BSD-2-Clause, same as the rest of the Confium workspace.