confium 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +152 -0
- data/Cargo.lock +2634 -0
- data/Cargo.toml +9 -0
- data/README.adoc +114 -14
- data/Rakefile +11 -6
- data/confium.gemspec +50 -29
- data/ext/confium_native/Cargo.toml +63 -0
- data/ext/confium_native/build.rs +72 -0
- data/ext/confium_native/extconf.rb +10 -0
- data/ext/confium_native/src/attributes.rs +88 -0
- data/ext/confium_native/src/audit.rs +169 -0
- data/ext/confium_native/src/composite.rs +302 -0
- data/ext/confium_native/src/deployment.rs +176 -0
- data/ext/confium_native/src/ers.rs +93 -0
- data/ext/confium_native/src/lib.rs +56 -0
- data/ext/confium_native/src/openpgp.rs +55 -0
- data/ext/confium_native/src/path.rs +118 -0
- data/ext/confium_native/src/pki.rs +431 -0
- data/ext/confium_native/src/tc.rs +420 -0
- data/ext/confium_native/src/transparency.rs +343 -0
- data/ext/confium_native/src/util.rs +201 -0
- data/lib/confium/audit.rb +125 -0
- data/lib/confium/cfm.rb +4 -5
- data/lib/confium/crypto.rb +50 -0
- data/lib/confium/digest.rb +11 -9
- data/lib/confium/errors/coerce.rb +47 -0
- data/lib/confium/errors/crypto_error.rb +15 -0
- data/lib/confium/errors/index_error.rb +15 -0
- data/lib/confium/errors/not_found_error.rb +15 -0
- data/lib/confium/errors/parse_error.rb +15 -0
- data/lib/confium/errors/policy_violation_error.rb +15 -0
- data/lib/confium/errors/threshold_error.rb +16 -0
- data/lib/confium/errors/unresolved_signer_error.rb +14 -0
- data/lib/confium/errors/validation_error.rb +17 -0
- data/lib/confium/errors/verification_error.rb +15 -0
- data/lib/confium/errors.rb +26 -0
- data/lib/confium/ffi.rb +23 -0
- data/lib/confium/lib.rb +18 -56
- data/lib/confium/openpgp.rb +34 -0
- data/lib/confium/pki/certificate_builder.rb +60 -0
- data/lib/confium/pki/cms/signed_data_builder.rb +92 -0
- data/lib/confium/pki/cms.rb +15 -0
- data/lib/confium/pki/cnml.rb +80 -0
- data/lib/confium/pki.rb +13 -0
- data/lib/confium/policy.rb +138 -0
- data/lib/confium/secure_bytes.rb +126 -0
- data/lib/confium/tc/coordinator.rb +68 -0
- data/lib/confium/tc/session.rb +51 -0
- data/lib/confium/tc/session_stub.rb +43 -0
- data/lib/confium/tc/share_file.rb +87 -0
- data/lib/confium/tc.rb +17 -0
- data/lib/confium/transparency/ots.rb +63 -0
- data/lib/confium/version.rb +1 -1
- data/lib/confium.rb +50 -20
- metadata +142 -25
- data/CODE_OF_CONDUCT.md +0 -84
- data/Gemfile +0 -10
- data/sig/confium.rbs +0 -4
data/Cargo.toml
ADDED
data/README.adoc
CHANGED
|
@@ -1,39 +1,139 @@
|
|
|
1
1
|
= Confium Ruby bindings
|
|
2
|
+
:toc: macro
|
|
3
|
+
:toclevels: 2
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
Ruby bindings for the Confium open-source framework for multi-stakeholder threshold cryptography.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Confium supports three deployment modes:
|
|
8
|
+
|
|
9
|
+
* **Mode 1 — Peer-to-peer threshold cryptography**: nodes do TC directly (MPC, distributed custody, BFT)
|
|
10
|
+
* **Mode 2 — TC PKI replacement**: drop-in for existing PKI consumers (PKCS#11 server, OpenSSL 3.0 provider, JCE)
|
|
11
|
+
* **Mode 3 — TC Certificate PKI**: institutional deployments with custom certificate formats (OIML CNML, BIPM, pharma, accreditation)
|
|
12
|
+
|
|
13
|
+
This gem wraps the high-value Confium subsystems via a Rust native extension (built at `gem install` time with `rb_sys` + `magnus`). No separate C ABI library to install; everything is statically linked into the extension.
|
|
6
14
|
|
|
7
15
|
== Installation
|
|
8
16
|
|
|
9
|
-
|
|
17
|
+
Add to your Gemfile:
|
|
10
18
|
|
|
11
|
-
[source,
|
|
19
|
+
[source,ruby]
|
|
12
20
|
----
|
|
13
|
-
|
|
21
|
+
gem "confium", "~> 0.1"
|
|
14
22
|
----
|
|
15
23
|
|
|
16
|
-
|
|
24
|
+
Or install directly:
|
|
17
25
|
|
|
18
26
|
[source,sh]
|
|
19
27
|
----
|
|
20
28
|
$ gem install confium
|
|
21
29
|
----
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
=== Prerequisites
|
|
32
|
+
|
|
33
|
+
* Ruby ≥ 3.1
|
|
34
|
+
* Rust stable toolchain (`rustup default stable`)
|
|
35
|
+
* C toolchain (clang/gcc/Xcode CLT)
|
|
36
|
+
|
|
37
|
+
The gem compiles a Rust cdylib at install time and links it into Ruby as a native extension. There is no separate `libconfium` to install.
|
|
38
|
+
|
|
39
|
+
== Quick start
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
[source,ruby]
|
|
42
|
+
----
|
|
43
|
+
require "confium"
|
|
44
|
+
|
|
45
|
+
# Transparency log
|
|
46
|
+
tree = Confium::Transparency::MerkleTree.new
|
|
47
|
+
seq = tree.append(artifact_hash_bytes_32)
|
|
48
|
+
root = tree.root # binary String, 32 bytes
|
|
49
|
+
proof = tree.inclusion_proof(seq)
|
|
50
|
+
proof.verify(root) # => true
|
|
51
|
+
|
|
52
|
+
# Composite signature (PQ migration)
|
|
53
|
+
kp = Confium::Composite.generate_ed25519_keypair
|
|
54
|
+
component = Confium::Composite.sign_ed25519(kp["private_key"], "message")
|
|
55
|
+
sig = Confium::Composite::Signature.new([component])
|
|
56
|
+
result = sig.verify("message")
|
|
57
|
+
result.all_verified? # => true
|
|
58
|
+
|
|
59
|
+
# Attribute-based threshold policy
|
|
60
|
+
pred = Confium::Attributes.parse(%q{and(min_count("role:director", 3), min_distinct("region", 3))})
|
|
61
|
+
alice = Confium::Attributes::Signer.new
|
|
62
|
+
alice.add("role:director", "yes")
|
|
63
|
+
alice.add("region", "europe")
|
|
64
|
+
# ... bob, carol similarly
|
|
65
|
+
pred.satisfied_by?([alice, bob, carol]) # => true
|
|
66
|
+
|
|
67
|
+
# X.509 certificate
|
|
68
|
+
cert = Confium::PKI::Certificate.from_pem(File.read("cert.pem"))
|
|
69
|
+
puts cert.fingerprint_sha256
|
|
70
|
+
puts cert.valid_at?(Time.now.utc.iso8601) # => true
|
|
71
|
+
|
|
72
|
+
# Real threshold cryptography: P-256 Shamir
|
|
73
|
+
kp = Confium::TC::FrostP256.generate_keypair
|
|
74
|
+
shares = Confium::TC::FrostP256.split_secret(kp["private_key"], 3, 5)
|
|
75
|
+
recovered = Confium::TC::FrostP256.recover_secret(shares.first(3).map { |s| { "x" => s.x, "y" => s.y_bytes } })
|
|
76
|
+
recovered == kp["private_key"] # => true
|
|
77
|
+
----
|
|
78
|
+
|
|
79
|
+
== API surface (v0.1.0)
|
|
80
|
+
|
|
81
|
+
=== `Confium::Transparency`
|
|
82
|
+
- `MerkleTree.new` / `#append(artifact_hash)` / `#root` / `#length` / `#empty?` / `#inclusion_proof(seq)`
|
|
83
|
+
- `InclusionProof#sequence` / `#steps` / `#verify(root)`
|
|
84
|
+
|
|
85
|
+
=== `Confium::Composite` — PQ migration
|
|
86
|
+
- `.generate_ed25519_keypair` → `{ private_key:, public_key: }`
|
|
87
|
+
- `.sign_ed25519(private_key, message)` → component Hash
|
|
88
|
+
- `Signature.new(components)` / `#verify(message)` / `#component_count` / `#algorithms`
|
|
89
|
+
- `VerificationResult#all_verified?` / `#per_component`
|
|
90
|
+
|
|
91
|
+
=== `Confium::Attributes` — threshold policy DSL
|
|
92
|
+
- `.parse(dsl_expr)` → `Predicate`
|
|
93
|
+
- `Predicate#satisfied_by?(signers)`
|
|
94
|
+
- `Signer.new` / `#add(key, value)` / `#has?(key)` / `#values(key)`
|
|
95
|
+
- DSL: `min_count("attr", n)`, `min_distinct("attr", n)`, `any("attr")`, `all("attr")`, `none("attr")`, `and(...)`, `or(...)`, `not(p)`
|
|
96
|
+
|
|
97
|
+
=== `Confium::PKI`
|
|
98
|
+
- `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`
|
|
99
|
+
- `CSR.from_der` / `.from_pem` / `#to_der` / `#to_pem`
|
|
100
|
+
- `CMS::SignedData.from_json` / `#to_json` / `#signer_count` / `#content_type` / `#content` / `#certificate_count` / `#certificate_at(i)`
|
|
101
|
+
- `CMS::Content#bytes` / `#length`
|
|
102
|
+
- `XMLDSig.canonicalize(xml)` / `.canonicalize_exclusive(xml)` (Canonical XML RFC 3076 + Exclusive C14N)
|
|
103
|
+
|
|
104
|
+
=== `Confium::Identity` — actor roles
|
|
105
|
+
- `.actor_types` → `["manufacturer", "testing_lab", "issuing_authority_officer", "biml_director", "quorum_coordinator", "verifier"]`
|
|
106
|
+
- `Actor.from_json(json)` / `#to_json` / `#actor_id` / `#actor_type` / `#quorum_id` / `#registered_at` / `#expires_at` / `#certificate_count`
|
|
107
|
+
|
|
108
|
+
=== `Confium::Config` — deployment manifest
|
|
109
|
+
- `Manifest.from_toml(toml_str)` / `#deployment_name` / `#operator` / `#manifest_version` / `#tier_count` / `#tier_name_at(i)` / `#quorum_count` / `#validate` / `#valid?`
|
|
110
|
+
|
|
111
|
+
=== `Confium::TC::FrostP256` — real P-256 Shamir + ECDSA
|
|
112
|
+
- `.generate_keypair` → `{ private_key: (32 bytes), public_key: (65 bytes SEC1) }`
|
|
113
|
+
- `.split_secret(secret, t, n)` → array of `Share` with `#x`, `#y_bytes`
|
|
114
|
+
- `.recover_secret([{x:, y:}, ...])` → secret bytes
|
|
115
|
+
- `.sign(private_key, message)` → `{ der:, fixed: }`
|
|
116
|
+
|
|
117
|
+
=== `Confium::TC::ElGamalP256` — threshold ElGamal-P256 KEM
|
|
118
|
+
- `.encapsulate(public_key_bytes)` → `{ ciphertext: { c1:, c2: }, shared_secret: }`
|
|
119
|
+
- `.partial_decrypt(party_index, share_bytes, ciphertext)` → `{ party_index:, bytes: }`
|
|
120
|
+
- `.aggregate_partials(partials, threshold, ciphertext)` → shared_secret bytes
|
|
26
121
|
|
|
27
122
|
== Development
|
|
28
123
|
|
|
29
|
-
After checking out the repo
|
|
124
|
+
After checking out the repo:
|
|
30
125
|
|
|
31
|
-
|
|
126
|
+
[source,sh]
|
|
127
|
+
----
|
|
128
|
+
$ bundle install
|
|
129
|
+
$ bundle exec rake compile # build the Rust extension
|
|
130
|
+
$ bundle exec rspec # 75+ specs
|
|
131
|
+
----
|
|
32
132
|
|
|
33
|
-
==
|
|
133
|
+
== Architecture
|
|
34
134
|
|
|
35
|
-
|
|
135
|
+
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`.
|
|
36
136
|
|
|
37
|
-
==
|
|
137
|
+
== License
|
|
38
138
|
|
|
39
|
-
|
|
139
|
+
BSD-2-Clause, same as the rest of the Confium workspace.
|
data/Rakefile
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rspec/core/rake_task'
|
|
6
5
|
RSpec::Core::RakeTask.new(:spec)
|
|
7
6
|
|
|
8
|
-
require
|
|
9
|
-
|
|
7
|
+
require 'rubocop/rake_task'
|
|
10
8
|
RuboCop::RakeTask.new
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
require 'rb_sys/extensiontask'
|
|
11
|
+
gemspec = Gem::Specification.load('confium.gemspec')
|
|
12
|
+
RbSys::ExtensionTask.new('confium_native', gemspec) do |ext|
|
|
13
|
+
ext.lib_dir = 'lib/confium_native'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
task compile: :clean
|
|
17
|
+
task default: %i[compile spec rubocop]
|
data/confium.gemspec
CHANGED
|
@@ -1,35 +1,56 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative 'lib/confium/version'
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name =
|
|
6
|
+
spec.name = 'confium'
|
|
7
7
|
spec.version = Confium::VERSION
|
|
8
|
-
spec.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
spec.
|
|
12
|
-
|
|
13
|
-
spec.
|
|
14
|
-
spec.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
spec.
|
|
19
|
-
spec.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
spec.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
8
|
+
spec.platform = Gem::Platform::RUBY
|
|
9
|
+
|
|
10
|
+
spec.authors = ['Ribose Open']
|
|
11
|
+
spec.email = ['open.source@ribose.com']
|
|
12
|
+
|
|
13
|
+
spec.summary = 'Ruby bindings for the Confium multi-stakeholder threshold cryptography framework.'
|
|
14
|
+
spec.description = 'Confium provides threshold cryptography for Ruby: FROST / CMP20 / GG18 ' \
|
|
15
|
+
'signing sessions, X.509 certificate issuance (CNML-ready), composite ' \
|
|
16
|
+
'PQ-migration signatures, and transparency-log anchoring. Powered by a ' \
|
|
17
|
+
'Rust native extension (magnus + rb_sys) — no separate C ABI to install.'
|
|
18
|
+
spec.homepage = 'https://www.confium.org'
|
|
19
|
+
spec.license = 'BSD-2-Clause'
|
|
20
|
+
|
|
21
|
+
spec.metadata = {
|
|
22
|
+
'bug_tracker_uri' => 'https://github.com/confium/confium-ruby/issues',
|
|
23
|
+
'changelog_uri' => 'https://github.com/confium/confium-ruby/blob/main/CHANGELOG.md',
|
|
24
|
+
'homepage_uri' => spec.homepage,
|
|
25
|
+
'source_code_uri' => 'https://github.com/confium/confium-ruby',
|
|
26
|
+
'rubygems_mfa_required' => 'true'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Rust extension — compiled at gem install via rb_sys.
|
|
30
|
+
spec.extensions = ['ext/confium_native/extconf.rb']
|
|
31
|
+
|
|
32
|
+
spec.files = Dir.glob('{lib,ext}/**/*') + %w[
|
|
33
|
+
CHANGELOG.md
|
|
34
|
+
LICENSE.txt
|
|
35
|
+
Rakefile
|
|
36
|
+
README.adoc
|
|
37
|
+
confium.gemspec
|
|
38
|
+
Cargo.toml
|
|
39
|
+
Cargo.lock
|
|
40
|
+
]
|
|
41
|
+
spec.files.reject! { |f| File.directory?(f) }
|
|
42
|
+
spec.files.reject! { |f| f =~ /\.(dll|so|dylib|lib|bundle)\Z/ }
|
|
43
|
+
spec.require_paths = ['lib']
|
|
44
|
+
|
|
45
|
+
spec.required_ruby_version = '>= 3.1.0'
|
|
46
|
+
|
|
47
|
+
# Required for the Rust extension.
|
|
48
|
+
spec.add_dependency 'rb_sys', '~> 0.9.39'
|
|
49
|
+
|
|
50
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
51
|
+
spec.add_development_dependency 'rake-compiler', '~> 1.2.0'
|
|
52
|
+
spec.add_development_dependency 'rake-compiler-dock', '~> 1.3'
|
|
53
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
54
|
+
spec.add_development_dependency 'rubocop', '~> 1.0'
|
|
55
|
+
spec.add_development_dependency 'steep', '~> 1.5'
|
|
35
56
|
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# confium_native — Rust native extension for the confium Ruby gem.
|
|
2
|
+
#
|
|
3
|
+
# Compiles to a Ruby-loadable cdylib (`confium_native.bundle` / `.so` / `.dll`)
|
|
4
|
+
# via `rb_sys` + `magnus`. The Ruby side loads it through `ext/confium_native/extconf.rb`
|
|
5
|
+
# (invoked by `gem install` or `bundle exec rake compile`).
|
|
6
|
+
|
|
7
|
+
[package]
|
|
8
|
+
name = "confium_native"
|
|
9
|
+
version = "0.3.0"
|
|
10
|
+
edition = "2024"
|
|
11
|
+
rust-version = "1.85"
|
|
12
|
+
publish = false
|
|
13
|
+
|
|
14
|
+
[lib]
|
|
15
|
+
crate-type = ["cdylib"]
|
|
16
|
+
|
|
17
|
+
build = "build.rs"
|
|
18
|
+
|
|
19
|
+
[dependencies]
|
|
20
|
+
# rb-sys for Ruby C API access.
|
|
21
|
+
rb-sys = { version = "0.9.124", features = ["global-allocator"] }
|
|
22
|
+
|
|
23
|
+
# magnus for idiomatic Ruby <-> Rust bindings.
|
|
24
|
+
magnus = { version = "0.8", features = ["bytes"] }
|
|
25
|
+
|
|
26
|
+
# Confium crates — all from crates.io so gem install works without workspace checkout.
|
|
27
|
+
confium-core = "0.2"
|
|
28
|
+
confium-transparency = "0.4"
|
|
29
|
+
confium-composite = "0.3.1"
|
|
30
|
+
confium-attributes = "0.3.1"
|
|
31
|
+
confium-pki = "0.3.1"
|
|
32
|
+
confium-deployment = "0.3"
|
|
33
|
+
confium-tc = "0.3.1"
|
|
34
|
+
confium-tc-frost-p256 = "0.3"
|
|
35
|
+
confium-tc-elgamal-p256 = "0.3"
|
|
36
|
+
confium-tc-cmp20 = "0.3.1"
|
|
37
|
+
confium-tc-gg18 = "0.3.1"
|
|
38
|
+
|
|
39
|
+
# Newly published shared crypto crates (confium product restructuring).
|
|
40
|
+
confium-tc-core = "0.3"
|
|
41
|
+
confium-crypto-vss = "0.3"
|
|
42
|
+
confium-crypto-zk = "0.3"
|
|
43
|
+
confium-privacy = "0.3"
|
|
44
|
+
confium-observability = "0.3"
|
|
45
|
+
|
|
46
|
+
# P-256 scalar/point types used by the TC surface.
|
|
47
|
+
p256 = { version = "0.13", features = ["ecdsa"] }
|
|
48
|
+
|
|
49
|
+
ed25519-dalek = { version = "2", features = ["rand_core"] }
|
|
50
|
+
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
|
|
51
|
+
|
|
52
|
+
sha2 = "0.10"
|
|
53
|
+
subtle = "2"
|
|
54
|
+
chrono = "0.4"
|
|
55
|
+
hex = "0.4"
|
|
56
|
+
serde_json = "1"
|
|
57
|
+
|
|
58
|
+
# RNP (OpenPGP, RFC 9580) — hard-bundled with vendored feature so
|
|
59
|
+
# librnp is compiled from source via rnp-src. No system librnp needed.
|
|
60
|
+
rnp = { package = "rnp-rs", version = "0.1.10", features = ["vendored"] }
|
|
61
|
+
|
|
62
|
+
[profile.release]
|
|
63
|
+
lto = "off"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// build.rs — read the version of confium-core from Cargo.lock and emit
|
|
2
|
+
// it as a CARGO_RUSTC_ENV so lib.rs can read it at compile time.
|
|
3
|
+
//
|
|
4
|
+
// Note: cargo invokes build scripts with a custom cwd (the staging dir),
|
|
5
|
+
// so we cannot rely on `Path::new("Cargo.lock")` being present. Instead
|
|
6
|
+
// we walk up from CARGO_MANIFEST_DIR looking for a Cargo.toml that
|
|
7
|
+
// declares a [workspace] section. That Cargo.lock is the one we need.
|
|
8
|
+
|
|
9
|
+
use std::fs;
|
|
10
|
+
use std::path::PathBuf;
|
|
11
|
+
|
|
12
|
+
fn main() {
|
|
13
|
+
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
14
|
+
let workspace_dir = find_workspace_root(&manifest_dir);
|
|
15
|
+
let lockfile_path = workspace_dir.join("Cargo.lock");
|
|
16
|
+
let lockfile = fs::read_to_string(&lockfile_path).expect("could not read workspace Cargo.lock");
|
|
17
|
+
|
|
18
|
+
let version = lockfile
|
|
19
|
+
.lines()
|
|
20
|
+
// find each `[[package]]` block then the `name = "..."` line inside
|
|
21
|
+
.scan(String::new(), |state, line| {
|
|
22
|
+
if line.trim().starts_with("[[package]]") {
|
|
23
|
+
*state = String::new();
|
|
24
|
+
} else {
|
|
25
|
+
state.push_str(line);
|
|
26
|
+
state.push('\n');
|
|
27
|
+
}
|
|
28
|
+
Some(state.clone())
|
|
29
|
+
})
|
|
30
|
+
.filter_map(|block| {
|
|
31
|
+
let name = block.lines().find_map(|l| {
|
|
32
|
+
let trimmed = l.trim();
|
|
33
|
+
trimmed.strip_prefix("name = \"")?.strip_suffix('"').map(String::from)
|
|
34
|
+
})?;
|
|
35
|
+
let version = block.lines().find_map(|l| {
|
|
36
|
+
let trimmed = l.trim();
|
|
37
|
+
trimmed.strip_prefix("version = \"")?.strip_suffix('"').map(String::from)
|
|
38
|
+
})?;
|
|
39
|
+
Some((name, version))
|
|
40
|
+
})
|
|
41
|
+
.find(|(name, _)| name == "confium-core")
|
|
42
|
+
.map(|(_, v)| v);
|
|
43
|
+
|
|
44
|
+
match version {
|
|
45
|
+
Some(v) => {
|
|
46
|
+
println!("cargo:rerun-if-changed={}", lockfile_path.display());
|
|
47
|
+
println!("cargo:rustc-env=CONFIUM_CORE_VERSION={v}");
|
|
48
|
+
}
|
|
49
|
+
None => panic!("confium-core not found in Cargo.lock"),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Walk up the directory tree from `start` until we find a Cargo.toml
|
|
54
|
+
/// containing a `[workspace]` section. That directory is the workspace
|
|
55
|
+
/// root; its Cargo.lock is what we need.
|
|
56
|
+
fn find_workspace_root(start: &std::path::Path) -> PathBuf {
|
|
57
|
+
let mut current = start.to_path_buf();
|
|
58
|
+
loop {
|
|
59
|
+
let candidate = current.join("Cargo.toml");
|
|
60
|
+
if candidate.exists() {
|
|
61
|
+
if let Ok(contents) = fs::read_to_string(&candidate) {
|
|
62
|
+
if contents.contains("[workspace]") {
|
|
63
|
+
return current;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
match current.parent() {
|
|
68
|
+
Some(parent) => current = parent.to_path_buf(),
|
|
69
|
+
None => panic!("no workspace root found above {}", start.display()),
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mkmf'
|
|
4
|
+
require 'rb_sys/mkmf'
|
|
5
|
+
|
|
6
|
+
create_rust_makefile('confium_native') do |r|
|
|
7
|
+
r.profile = ENV.fetch('RB_SYS_CARGO_PROFILE', :dev).to_sym
|
|
8
|
+
r.use_stable_api_compiled_fallback = true
|
|
9
|
+
r.force_install_rust_toolchain = false
|
|
10
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//! Confium::Attributes — Ruby surface for `confium_attributes`.
|
|
2
|
+
//!
|
|
3
|
+
//! Exposes the predicate DSL + evaluator for threshold-signing attribute
|
|
4
|
+
//! policies (e.g., "5-of-9 directors, spanning 3 regions").
|
|
5
|
+
//!
|
|
6
|
+
//! - `Confium::Attributes.parse(expr)` — parse a DSL string into a
|
|
7
|
+
//! `Confium::Attributes::Predicate`.
|
|
8
|
+
//! - `Confium::Attributes::Predicate#satisfied_by?(signers)` — evaluate
|
|
9
|
+
//! against a list of `Confium::Attributes::Signer`.
|
|
10
|
+
//! - `Confium::Attributes::Signer` — a signer's attribute map, with
|
|
11
|
+
//! `#add(key, value)`, `#has?(key)`, `#values(key)`.
|
|
12
|
+
|
|
13
|
+
use confium_attributes::{evaluate, parse as dsl_parse, Predicate, SignerAttributes};
|
|
14
|
+
use magnus::{
|
|
15
|
+
exception, function, method, typed_data::Obj, DataTypeFunctions, Error, Module,
|
|
16
|
+
Object, Ruby, TryConvert, TypedData, Value,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
#[derive(TypedData, DataTypeFunctions)]
|
|
20
|
+
#[magnus(class = "Confium::Attributes::Predicate", size)]
|
|
21
|
+
pub struct PredicateWrap {
|
|
22
|
+
pub inner: Predicate,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl PredicateWrap {
|
|
26
|
+
fn satisfied_by(&self, signers_value: Value) -> Result<bool, Error> {
|
|
27
|
+
let arr = magnus::RArray::try_convert(signers_value)?;
|
|
28
|
+
let mut owned: Vec<SignerAttributes> = Vec::with_capacity(arr.len());
|
|
29
|
+
for v in arr.each() {
|
|
30
|
+
let signer_wrap = Obj::<SignerWrap>::try_convert(v?)?;
|
|
31
|
+
owned.push(signer_wrap.inner.borrow().clone());
|
|
32
|
+
}
|
|
33
|
+
let refs: Vec<&SignerAttributes> = owned.iter().collect();
|
|
34
|
+
Ok(evaluate(&self.inner, &refs))
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#[derive(TypedData, DataTypeFunctions)]
|
|
39
|
+
#[magnus(class = "Confium::Attributes::Signer", size)]
|
|
40
|
+
pub struct SignerWrap {
|
|
41
|
+
pub inner: std::cell::RefCell<SignerAttributes>,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
impl SignerWrap {
|
|
45
|
+
fn new() -> Self {
|
|
46
|
+
Self {
|
|
47
|
+
inner: std::cell::RefCell::new(SignerAttributes::new()),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fn add(&self, key: String, value: String) {
|
|
52
|
+
self.inner.borrow_mut().add(key, value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fn has(&self, key: String) -> bool {
|
|
56
|
+
self.inner.borrow().has(&key)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fn values(&self, key: String) -> Vec<String> {
|
|
60
|
+
self.inner.borrow().values(&key)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn parse(expr: String) -> Result<Obj<PredicateWrap>, Error> {
|
|
65
|
+
let predicate = dsl_parse(&expr)
|
|
66
|
+
.map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
|
|
67
|
+
let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
|
|
68
|
+
Ok(ruby.obj_wrap(PredicateWrap { inner: predicate }))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
pub fn init(ruby: &Ruby, parent: magnus::RModule) -> Result<(), Error> {
|
|
72
|
+
let attributes = parent.define_module("Attributes")?;
|
|
73
|
+
attributes.define_module_function("parse", function!(parse, 1))?;
|
|
74
|
+
|
|
75
|
+
let pred_class = attributes.define_class("Predicate", ruby.class_object())?;
|
|
76
|
+
pred_class.define_method(
|
|
77
|
+
"satisfied_by?",
|
|
78
|
+
method!(PredicateWrap::satisfied_by, 1),
|
|
79
|
+
)?;
|
|
80
|
+
|
|
81
|
+
let signer_class = attributes.define_class("Signer", ruby.class_object())?;
|
|
82
|
+
signer_class.define_singleton_method("new", function!(SignerWrap::new, 0))?;
|
|
83
|
+
signer_class.define_method("add", method!(SignerWrap::add, 2))?;
|
|
84
|
+
signer_class.define_method("has?", method!(SignerWrap::has, 1))?;
|
|
85
|
+
signer_class.define_method("values", method!(SignerWrap::values, 1))?;
|
|
86
|
+
|
|
87
|
+
Ok(())
|
|
88
|
+
}
|