confium 0.4.1 → 0.6.0

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.
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'socket'
5
+ require_relative 'coordinator'
6
+
7
+ module Confium
8
+ module TC
9
+ # Networked threshold-signing coordinator service.
10
+ #
11
+ # Wraps Confium::TC::Coordinator (real CMP20/GG18 combine) behind
12
+ # a TCP socket so signers on separate machines submit commitments
13
+ # and shares from their own processes:
14
+ #
15
+ # service = Confium::TC::NetworkCoordinator.new(quorum_id: 'root')
16
+ # service.start # binds 127.0.0.1:<ephemeral>
17
+ # client = Confium::TC::SignerClient.new(port: service.port)
18
+ # sid = client.create_session(message: data, threshold: 3)
19
+ # client.submit_share(sid, 'signer-1', share_blob)
20
+ # client.aggregate(sid) # => 64-byte signature
21
+ #
22
+ # Protocol: one JSON object per line (NDJSON); binary fields are
23
+ # hex-encoded. Unknown operations and Coordinator errors come
24
+ # back as {"error": ..., "message": ...} lines.
25
+ #
26
+ # Transport security: none yet — plain TCP intended for loopback
27
+ # or private networks. The upstream noise-transport session
28
+ # protocol replaces this wholesale (see
29
+ # TODO.full/01-multi-host-threshold-signing.md).
30
+ class NetworkCoordinator
31
+ HANDLERS = {
32
+ 'create' => lambda do |coord, req|
33
+ session_id = coord.create_session(
34
+ message: [req.fetch('message_hex')].pack('H*'),
35
+ threshold: req.fetch('threshold'),
36
+ scheme: req['scheme'] || 'CMP20-ECDSA-P256'
37
+ )
38
+ { 'session_id' => session_id }
39
+ end,
40
+ 'commitment' => lambda do |coord, req|
41
+ session_id = req.fetch('session_id')
42
+ coord.submit_commitment(
43
+ session_id, req.fetch('signer_id'),
44
+ [req.fetch('bytes_hex')].pack('H*')
45
+ )
46
+ { 'ok' => true, 'state' => coord.session_state(session_id).to_s }
47
+ end,
48
+ 'share' => lambda do |coord, req|
49
+ coord.submit_share(
50
+ req.fetch('session_id'), req.fetch('signer_id'),
51
+ [req.fetch('bytes_hex')].pack('H*')
52
+ )
53
+ { 'ok' => true }
54
+ end,
55
+ 'aggregate' => lambda do |coord, req|
56
+ signature = coord.aggregate(req.fetch('session_id'))
57
+ { 'signature_hex' => signature.unpack1('H*') }
58
+ end
59
+ }.freeze
60
+
61
+ class RequestError < StandardError; end
62
+
63
+ attr_reader :quorum_id, :port
64
+
65
+ def initialize(quorum_id:, host: '127.0.0.1', port: 0, coordinator: nil)
66
+ @quorum_id = quorum_id
67
+ @host = host
68
+ @port = port
69
+ @coordinator = coordinator || Coordinator.new(quorum_id: quorum_id)
70
+ @server = nil
71
+ @accept_thread = nil
72
+ @mutex = Mutex.new
73
+ end
74
+
75
+ def start
76
+ raise 'already started' if @server
77
+
78
+ # RBS socket lib lacks the (host, port) overload for new
79
+ @server = TCPServer.new(@host.to_s, @port) # steep:ignore
80
+ @port = @server.addr[1]
81
+ @accept_thread = Thread.new { accept_loop }
82
+ self
83
+ end
84
+
85
+ def stop
86
+ return unless @server
87
+
88
+ server = @server
89
+ @server = nil
90
+ server.close
91
+ @accept_thread&.join(5)
92
+ nil
93
+ end
94
+
95
+ def running?
96
+ !@server.nil?
97
+ end
98
+
99
+ private
100
+
101
+ def accept_loop
102
+ loop do
103
+ server = @server
104
+ break unless server
105
+
106
+ begin
107
+ socket = server.accept
108
+ rescue IOError, Errno::EBADF, Errno::EINVAL
109
+ break # listener closed by #stop
110
+ end
111
+ Thread.new(socket) { |conn| serve(conn) }
112
+ end
113
+ end
114
+
115
+ def serve(socket)
116
+ socket.each_line do |line|
117
+ request = JSON.parse(line)
118
+ response = @mutex.synchronize { dispatch(request) }
119
+ socket.write("#{JSON.generate(response)}\n")
120
+ end
121
+ rescue JSON::ParserError => e
122
+ write_error(socket, 'RequestError', "malformed JSON: #{e.message}")
123
+ rescue StandardError => e
124
+ write_error(socket, e.class.name, e.message)
125
+ ensure
126
+ socket.close
127
+ end
128
+
129
+ def dispatch(request)
130
+ op = request['op'] or raise RequestError, 'missing "op"'
131
+ handler = HANDLERS[op] or raise RequestError, "unknown op: #{op}"
132
+
133
+ handler.call(@coordinator, request)
134
+ end
135
+
136
+ def write_error(socket, klass, message)
137
+ socket.write("#{JSON.generate({ 'error' => klass, 'message' => message })}\n")
138
+ rescue IOError
139
+ nil
140
+ end
141
+ end
142
+
143
+ # Client side for NetworkCoordinator. One instance per signer
144
+ # process; each call opens its own connection, so signers never
145
+ # share state.
146
+ class SignerClient
147
+ class RemoteError < StandardError
148
+ attr_reader :remote_class
149
+
150
+ def initialize(remote_class, message)
151
+ @remote_class = remote_class
152
+ super("#{remote_class}: #{message}")
153
+ end
154
+ end
155
+
156
+ def initialize(port:, host: '127.0.0.1')
157
+ @host = host
158
+ @port = port
159
+ end
160
+
161
+ def create_session(message:, threshold:, scheme: 'CMP20-ECDSA-P256')
162
+ response = call(
163
+ 'op' => 'create',
164
+ 'message_hex' => message.to_s.unpack1('H*'),
165
+ 'threshold' => threshold,
166
+ 'scheme' => scheme
167
+ )
168
+ response.fetch('session_id')
169
+ end
170
+
171
+ def submit_commitment(session_id, signer_id, commitment_bytes)
172
+ call(
173
+ 'op' => 'commitment',
174
+ 'session_id' => session_id,
175
+ 'signer_id' => signer_id,
176
+ 'bytes_hex' => commitment_bytes.unpack1('H*')
177
+ ).fetch('ok')
178
+ end
179
+
180
+ def submit_share(session_id, signer_id, share_bytes)
181
+ call(
182
+ 'op' => 'share',
183
+ 'session_id' => session_id,
184
+ 'signer_id' => signer_id,
185
+ 'bytes_hex' => share_bytes.unpack1('H*')
186
+ ).fetch('ok')
187
+ end
188
+
189
+ def aggregate(session_id)
190
+ response = call('op' => 'aggregate', 'session_id' => session_id)
191
+ [response.fetch('signature_hex')].pack('H*')
192
+ end
193
+
194
+ private
195
+
196
+ def call(request)
197
+ TCPSocket.open(@host, @port) do |socket|
198
+ socket.write("#{JSON.generate(request)}\n")
199
+ line = socket.readline
200
+ response = JSON.parse(line)
201
+ raise RemoteError.new(response['error'], response['message']) if response['error']
202
+
203
+ response
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Confium
4
+ module TC
5
+ # One threshold-signing session: the state machine and the combine
6
+ # behind every coordinator surface. Coordinator (in-process) and
7
+ # NetworkCoordinator (TCP/NDJSON) are adapters over this class —
8
+ # they own registries and framing; the session owns the semantics:
9
+ #
10
+ # pending → commitments_collected (T distinct commitments)
11
+ # → completed (aggregate runs the scheme combine)
12
+ #
13
+ # Aggregate may be called again after completion — the combine
14
+ # randomizes its nonce, so each call yields a fresh (verifiable)
15
+ # signature over the same shares, which is what a network client
16
+ # retrying after a dropped response needs.
17
+ #
18
+ # Signer identity is load-bearing: one signer submitting two
19
+ # shares must never count twice toward the threshold, so
20
+ # commitments and shares are keyed by signer_id and duplicates
21
+ # raise.
22
+ class SigningSession
23
+ DEFAULT_UNLOCK_WINDOW = 14_400
24
+
25
+ SCHEMES = {
26
+ 'CMP20-ECDSA-P256' => ->(shares, threshold, message) { Cmp20.sign(shares, threshold, message) },
27
+ 'GG18-ECDSA-P256' => ->(shares, threshold, message) { Gg18.sign(shares, threshold, message) }
28
+ }.freeze
29
+
30
+ attr_reader :message, :threshold, :unlock_window, :scheme, :state
31
+
32
+ def initialize(message:, threshold:, unlock_window: DEFAULT_UNLOCK_WINDOW, scheme: 'CMP20-ECDSA-P256')
33
+ raise ArgumentError, "unknown scheme: #{scheme} (known: #{SCHEMES.keys.join(', ')})" unless SCHEMES.key?(scheme)
34
+
35
+ @message = message
36
+ @threshold = threshold
37
+ @unlock_window = unlock_window
38
+ @scheme = scheme
39
+ @state = :pending
40
+ # @type ivar @commitments: Hash[String, String]
41
+ @commitments = {}
42
+ # @type ivar @shares: Hash[String, String]
43
+ @shares = {}
44
+ end
45
+
46
+ def add_commitment(signer_id, commitment_bytes)
47
+ record(@commitments, signer_id, commitment_bytes)
48
+ @state = :commitments_collected if @commitments.length >= @threshold
49
+ self
50
+ end
51
+
52
+ def add_share(signer_id, share_bytes)
53
+ record(@shares, signer_id, share_bytes)
54
+ self
55
+ end
56
+
57
+ def commitment_count
58
+ @commitments.length
59
+ end
60
+
61
+ def share_count
62
+ @shares.length
63
+ end
64
+
65
+ def threshold_met?
66
+ @shares.length >= @threshold
67
+ end
68
+
69
+ def aggregate
70
+ unless threshold_met?
71
+ raise ThresholdError.new('Threshold not met', have_count: @shares.length, need_count: @threshold)
72
+ end
73
+
74
+ signature = SCHEMES.fetch(@scheme).call(@shares.values, @threshold, @message)
75
+ @state = :completed
76
+ signature
77
+ end
78
+
79
+ private
80
+
81
+ def record(store, signer_id, bytes)
82
+ if store.key?(signer_id)
83
+ raise ValidationError.new(
84
+ "duplicate submission from signer #{signer_id}",
85
+ param: :signer_id, expected: 'one submission per signer', actual: signer_id
86
+ )
87
+ end
88
+
89
+ store[signer_id] = bytes
90
+ end
91
+ end
92
+ end
93
+ end
data/lib/confium/tc.rb CHANGED
@@ -11,7 +11,12 @@
11
11
  # for the full interface specification.
12
12
  module Confium
13
13
  module TC
14
- autoload :Session, 'confium/tc/session'
15
- autoload :Coordinator, 'confium/tc/coordinator'
14
+ # The native extension defines Confium::TC; this namespace file
15
+ # is eager-required from confium.rb, so autoloads never fire —
16
+ # the pure-Ruby companions load with the namespace directly.
17
+ require_relative 'tc/signing_session'
18
+ require_relative 'tc/coordinator'
19
+ require_relative 'tc/network_coordinator'
20
+ require_relative 'tc/share_file'
16
21
  end
17
22
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Confium
4
- VERSION = '0.4.1'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/confium.rb CHANGED
@@ -10,21 +10,14 @@
10
10
  # are loaded lazily via autoload — see lib/confium/<name>.rb.
11
11
 
12
12
  require_relative 'confium/version'
13
+ require_relative 'confium/native_windows'
13
14
 
14
15
  begin
15
- # Pre-built platform gems ship one extension per C-ABI window
16
- # (Ruby 3.2 broke the 3.1 ABI — object shapes — and rb-sys
17
- # references the VM pointer libruby stopped exporting in 3.3, so
18
- # 3.1 and 3.2 get exact-minor builds while 3.3 covers 3.3+).
19
- # Source builds install the extension flat, without a version
20
- # directory.
21
- minor = RUBY_VERSION[/\A\d+\.\d+/]
16
+ # Pre-built platform gems ship one extension per ABI window (the
17
+ # window rules live in Confium::NativeWindows). Source builds
18
+ # install the extension flat, without a version directory.
22
19
  dlext = RbConfig::CONFIG['DLEXT'] || 'so'
23
- candidates = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.3') ? [minor, '3.3'].uniq : [minor]
24
- # Windows gems carry an exact-minor window per Ruby (a PE import
25
- # names the version-specific ruby DLL); other platforms share the
26
- # 3.3 window for 3.3+. Prefer the exact minor when present.
27
- windowed = candidates.filter_map do |w|
20
+ windowed = Confium::NativeWindows.candidates(RUBY_VERSION).filter_map do |w|
28
21
  path = File.expand_path("confium_native/#{w}/confium_native.#{dlext}", __dir__ || '.')
29
22
  w if File.exist?(path)
30
23
  end
@@ -92,7 +85,8 @@ require_relative 'confium/transparency'
92
85
  # of that module.
93
86
  require_relative 'confium/audit'
94
87
 
95
- # Eager-load the TC ShareFile Ruby companion. The native extension
96
- # defines `Confium::TC` as a Ruby module; this file adds the
97
- # `ShareFile` class for filesystem-backed share persistence.
98
- require_relative 'confium/tc/share_file'
88
+ # Eager-load the TC namespace file: `Confium::TC` is native-defined,
89
+ # so an autoload here would never fire (the PKI pattern). It
90
+ # registers the pure-Ruby Session/Coordinator companions and the
91
+ # ShareFile class for filesystem-backed share persistence.
92
+ require_relative 'confium/tc'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: confium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Open
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-24 00:00:00.000000000 Z
11
+ date: 2026-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb_sys
@@ -121,6 +121,7 @@ files:
121
121
  - ext/confium_native/src/deployment.rs
122
122
  - ext/confium_native/src/ers.rs
123
123
  - ext/confium_native/src/lib.rs
124
+ - ext/confium_native/src/openpgp_verify.rs
124
125
  - ext/confium_native/src/path.rs
125
126
  - ext/confium_native/src/pki.rs
126
127
  - ext/confium_native/src/tc.rs
@@ -128,10 +129,8 @@ files:
128
129
  - ext/confium_native/src/util.rs
129
130
  - lib/confium.rb
130
131
  - lib/confium/audit.rb
131
- - lib/confium/cfm.rb
132
+ - lib/confium/audit/otlp_sink.rb
132
133
  - lib/confium/composite.rb
133
- - lib/confium/crypto.rb
134
- - lib/confium/digest.rb
135
134
  - lib/confium/errors.rb
136
135
  - lib/confium/errors/coerce.rb
137
136
  - lib/confium/errors/crypto_error.rb
@@ -143,8 +142,7 @@ files:
143
142
  - lib/confium/errors/unresolved_signer_error.rb
144
143
  - lib/confium/errors/validation_error.rb
145
144
  - lib/confium/errors/verification_error.rb
146
- - lib/confium/ffi.rb
147
- - lib/confium/lib.rb
145
+ - lib/confium/native_windows.rb
148
146
  - lib/confium/openpgp.rb
149
147
  - lib/confium/pki.rb
150
148
  - lib/confium/pki/certificate_builder.rb
@@ -155,9 +153,9 @@ files:
155
153
  - lib/confium/secure_bytes.rb
156
154
  - lib/confium/tc.rb
157
155
  - lib/confium/tc/coordinator.rb
158
- - lib/confium/tc/session.rb
159
- - lib/confium/tc/session_stub.rb
156
+ - lib/confium/tc/network_coordinator.rb
160
157
  - lib/confium/tc/share_file.rb
158
+ - lib/confium/tc/signing_session.rb
161
159
  - lib/confium/transparency.rb
162
160
  - lib/confium/transparency/ots.rb
163
161
  - lib/confium/version.rb
data/lib/confium/cfm.rb DELETED
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'ffi'
4
-
5
- module Confium
6
- class CFM
7
- attr_reader :ptr
8
-
9
- def initialize
10
- pptr = ::FFI::MemoryPointer.new(:pointer)
11
- Confium.call_ffi(:cfm_create, pptr)
12
- @ptr = ::FFI::AutoPointer.new(pptr.read_pointer, self.class.method(:destroy))
13
- end
14
-
15
- def self.destroy(ptr)
16
- Confium::Lib.cfm_destroy(ptr)
17
- end
18
-
19
- def load_plugin(name, path)
20
- Confium.call_ffi(:cfm_plugin_load, @ptr, name, path, nil, nil)
21
- end
22
- end
23
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # {Confium::Crypto} is the registry namespace for Confium's cryptographic
4
- # interfaces.
5
- #
6
- # Mirroring the Rust plugin-interface registry (TODO #02), each Ruby
7
- # interface module (Digest, and future Cipher, AEAD, KDF, RNG, Signature,
8
- # KEM, ...) registers itself here on load rather than being enumerated in
9
- # a central case statement. This keeps the registry open for extension
10
- # (OCP) and makes the set of available interfaces a single source of
11
- # truth.
12
- #
13
- # Example:
14
- #
15
- # module Confium
16
- # module Digest
17
- # Confium::Crypto.register(:hash, self)
18
- # end
19
- # end
20
- #
21
- # Confium::Crypto.lookup(:hash) # => Confium::Digest
22
- module Confium
23
- module Crypto
24
- # @type ivar @interfaces: Hash[Symbol, untyped]
25
- @interfaces = {}
26
-
27
- class << self
28
- # Register an interface +klass+ under the symbolic +name+. Adding a
29
- # new interface is a one-line registration; no central switch needs
30
- # editing.
31
- def register(name, klass)
32
- @interfaces[name] = klass
33
- klass
34
- end
35
-
36
- # Resolve a registered interface by +name+. Raises ArgumentError if
37
- # nothing has been registered under that name.
38
- def lookup(name)
39
- @interfaces.fetch(name) do
40
- raise ArgumentError, "unknown interface #{name.inspect}"
41
- end
42
- end
43
-
44
- # Enumerate the names of every registered interface. Primarily for
45
- # introspection and tooling.
46
- def interfaces
47
- @interfaces.keys.dup
48
- end
49
- end
50
- end
51
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'ffi'
4
- require 'digest'
5
-
6
- module Confium
7
- class Digest < ::Digest::Class
8
- attr_reader :name, :ptr
9
-
10
- def initialize(cfm, name)
11
- @name = name
12
- pptr = ::FFI::MemoryPointer.new(:pointer)
13
- Confium.call_ffi(:cfm_hash_create, cfm.ptr, pptr, name, nil, nil, nil)
14
- ptr = pptr.read_pointer
15
- raise if ptr.null?
16
-
17
- @ptr = ::FFI::AutoPointer.new(ptr, self.class.method(:destroy))
18
- end
19
-
20
- def initialize_copy(source)
21
- @name = source.name
22
- pptr = ::FFI::MemoryPointer.new(:pointer)
23
- Confium.call_ffi(:cfm_hash_clone, source.ptr, pptr)
24
- ptr = pptr.read_pointer
25
- @ptr = ::FFI::AutoPointer.new(ptr, self.class.method(:destroy))
26
- end
27
-
28
- def self.destroy(ptr)
29
- Confium::Lib.cfm_hash_destroy(ptr)
30
- end
31
-
32
- def block_length
33
- plength = ::FFI::MemoryPointer.new(:uint32)
34
- Confium.call_ffi(:cfm_hash_block_size, @ptr, plength)
35
- plength.read(:uint32)
36
- end
37
-
38
- def digest_length
39
- plength = ::FFI::MemoryPointer.new(:uint32)
40
- Confium.call_ffi(:cfm_hash_output_size, @ptr, plength)
41
- plength.read(:uint32)
42
- end
43
-
44
- def update(data)
45
- Confium.call_ffi(:cfm_hash_update, @ptr, data, data.bytesize)
46
- self
47
- end
48
-
49
- def reset
50
- Confium.call_ffi(:cfm_hash_reset, @ptr)
51
- self
52
- end
53
-
54
- def finish
55
- buf = ::FFI::MemoryPointer.new(:uint8, digest_length)
56
- Confium.call_ffi(:cfm_hash_finalize, @ptr, buf, buf.size)
57
- buf.read_bytes(buf.size)
58
- end
59
-
60
- alias << update
61
- end
62
- end
data/lib/confium/ffi.rb DELETED
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'ffi'
4
-
5
- # {Confium::FFI} is the namespace for everything that touches the native
6
- # Confium shared library through Ruby-FFI.
7
- #
8
- # This file registers autoload entries for the planned FFI helper modules
9
- # (Library, Error, Options). They are listed in the TODO #14 architecture
10
- # (see `TODO.finalize/14-ruby-bindings-architecture.md`) and will be added
11
- # as the bindings grow; until then the autoloads are inert — they only
12
- # trigger a load when the corresponding constant is first referenced.
13
- #
14
- # Note: the legacy FFI library wrapper still lives at `Confium::Lib`
15
- # (file `confium/lib.rb`) and is autoloaded from `confium.rb`. It will be
16
- # migrated into `Confium::FFI::Library` in a follow-up.
17
- module Confium
18
- module FFI
19
- autoload :Library, 'confium/ffi/library'
20
- autoload :Error, 'confium/ffi/error'
21
- autoload :Options, 'confium/ffi/options'
22
- end
23
- end
data/lib/confium/lib.rb DELETED
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'ffi'
4
-
5
- module Confium
6
- module Lib
7
- extend ::FFI::Library
8
-
9
- FFI_LAYOUT = {
10
- cfm_create: [%i[pointer], :uint32],
11
- cfm_destroy: [%i[pointer], :uint32],
12
- cfm_plugin_load: [%i[pointer string string pointer pointer], :uint32],
13
- cfm_hash_create: [%i[pointer pointer pointer pointer pointer pointer], :uint32],
14
- cfm_hash_output_size: [%i[pointer pointer], :uint32],
15
- cfm_hash_block_size: [%i[pointer pointer], :uint32],
16
- cfm_hash_update: [%i[pointer pointer uint32], :uint32],
17
- cfm_hash_reset: [%i[pointer], :uint32],
18
- cfm_hash_clone: [%i[pointer pointer], :uint32],
19
- cfm_hash_finalize: [%i[pointer pointer uint32], :uint32],
20
- cfm_hash_destroy: [%i[pointer], :void]
21
- }.freeze
22
-
23
- ffi_lib([ENV.fetch('CONFIUM_LIB', nil), 'confium', 'libconfium'].compact)
24
-
25
- FFI_LAYOUT.each do |func, ary|
26
- class_eval do
27
- attach_function(func, ary.first, ary.last)
28
- end
29
- rescue ::FFI::NotFoundError
30
- # that's okay
31
- end
32
- end
33
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'confium/lib'
4
-
5
- # Confium::TC::Session wraps a threshold cryptography session.
6
- #
7
- # A session represents one signing or decryption operation involving
8
- # T-of-N parties. The session goes through states: pending →
9
- # commitments_collected → shares_collected → completed.
10
- #
11
- # Usage:
12
- # session = Confium::TC::Session.new(scheme: "FROST-ed25519",
13
- # threshold: 3,
14
- # num_parties: 5,
15
- # party_index: 0)
16
- # session.set_local_share(share_bytes)
17
- # session.round(incoming_messages) # returns outgoing messages
18
- # result = session.result if session.complete?
19
- module Confium
20
- module TC
21
- class Session
22
- attr_reader :scheme, :threshold, :num_parties, :party_index, :state
23
-
24
- def initialize(scheme:, threshold:, num_parties:, party_index:)
25
- @scheme = scheme
26
- @threshold = threshold
27
- @num_parties = num_parties
28
- @party_index = party_index
29
- @state = :pending
30
- @local_share = nil
31
- @result = nil
32
- end
33
-
34
- def set_local_share(share_bytes)
35
- raise ArgumentError, 'share must be a String' unless share_bytes.is_a?(String)
36
-
37
- @local_share = share_bytes
38
- end
39
-
40
- def complete?
41
- @state == :completed
42
- end
43
-
44
- def result
45
- raise 'Session not complete' unless complete?
46
-
47
- @result
48
- end
49
- end
50
- end
51
- end