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#verifyreplays 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 byteoffsetin 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 ofSharewith#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 typedRemoteError -
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 backendsaws-kms/gcp-kms/azure-keyvaultvia 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#droppedcounter
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 thepgpfeature;PGP_AVAILABLEreports it
Confium::Transport — coordinator clients over any transport
-
SignerClient.new(url)— connect over any registry transport:tcp://host:port(local/trusted) ornoise://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 (#bytesnon-destructive read,#clear)
Errors
-
Typed error hierarchy (
ParseError,ThresholdError,VerificationError,PolicyViolationError, …) — native failures surface positionaldetailshashes (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.