confium 0.6.3 → 0.7.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.
@@ -0,0 +1,125 @@
1
+ //! Confium::Transport — transport-level coordinator clients.
2
+ //!
3
+ //! Binds the registry-transport coordinator surface: a signer dials
4
+ //! the coordinator over any URL scheme the native library links
5
+ //! (tcp:// for local/trusted, noise:// for encrypted sessions with
6
+ //! key=/pinned= parameters), and a CoordinatorServer serves any
7
+ //! linked scheme. This is the transport half of multi-host signing;
8
+ //! the protocol half is Confium::TC::Session.
9
+
10
+ use magnus::{prelude::*, DataTypeFunctions, Error, Ruby, TypedData};
11
+
12
+ // Force the noise transport crate (and its `register_transport!`
13
+ // static) into the cdylib so the noise:// scheme resolves — the
14
+ // binding itself never names a symbol from it.
15
+ extern crate confium_net_noise as _noise_link;
16
+ extern crate confium_net_tcp as _tcp_link;
17
+
18
+ use confium_coordinator::coordinator::client::SignerClient as RustSignerClient;
19
+ use confium_coordinator::coordinator::net_server::CoordinatorServer as RustCoordinatorServer;
20
+
21
+ fn io_error(e: std::io::Error, operation: &str) -> Error {
22
+ magnus::Error::new(
23
+ magnus::exception::io_error(),
24
+ format!("{operation}: {e}"),
25
+ )
26
+ }
27
+
28
+ /// Confium::Transport::SignerClient — a coordinator connection over a
29
+ /// registry transport URL.
30
+ #[derive(TypedData, DataTypeFunctions)]
31
+ #[magnus(class = "Confium::Transport::SignerClient", size)]
32
+ pub struct SignerClient {
33
+ inner: std::cell::RefCell<RustSignerClient>,
34
+ }
35
+
36
+ impl SignerClient {
37
+ fn initialize(ruby: &Ruby, url: String) -> Result<Self, Error> {
38
+ let _ = ruby;
39
+ let inner = RustSignerClient::connect_url(&url).map_err(|e| io_error(e, "SignerClient.new"))?;
40
+ Ok(Self { inner: std::cell::RefCell::new(inner) })
41
+ }
42
+
43
+ fn register(&self, signer_id: String, quorum_id: String) -> Result<(), Error> {
44
+ self.inner
45
+ .borrow_mut()
46
+ .register(&signer_id, &quorum_id)
47
+ .map_err(|e| io_error(e, "register"))
48
+ }
49
+
50
+ fn create_session(
51
+ &self,
52
+ quorum_id: String,
53
+ scheme: String,
54
+ message: String,
55
+ threshold: u32,
56
+ num_parties: u32,
57
+ ) -> Result<String, Error> {
58
+ self.inner
59
+ .borrow_mut()
60
+ .create_session(&quorum_id, &scheme, message.as_bytes(), threshold, num_parties)
61
+ .map_err(|e| io_error(e, "create_session"))
62
+ }
63
+
64
+ fn submit_commitment(&self, args: &[magnus::Value]) -> Result<(), Error> {
65
+ let scanned = magnus::scan_args::scan_args::<(String, String, Vec<u8>), (), (), (), (), ()>(args)
66
+ .map_err(|e| magnus::Error::new(magnus::exception::arg_error(), e.to_string()))?;
67
+ let (session_id, signer_id, commitment) = scanned.required;
68
+ self.inner
69
+ .borrow_mut()
70
+ .submit_commitment(&session_id, &signer_id, &commitment)
71
+ .map_err(|e| io_error(e, "submit_commitment"))
72
+ }
73
+
74
+ fn submit_share(&self, args: &[magnus::Value]) -> Result<Option<Vec<u8>>, Error> {
75
+ let scanned = magnus::scan_args::scan_args::<(String, String, Vec<u8>), (), (), (), (), ()>(args)
76
+ .map_err(|e| magnus::Error::new(magnus::exception::arg_error(), e.to_string()))?;
77
+ let (session_id, signer_id, share) = scanned.required;
78
+ self.inner
79
+ .borrow_mut()
80
+ .submit_share(&session_id, &signer_id, &share)
81
+ .map_err(|e| io_error(e, "submit_share"))
82
+ }
83
+ }
84
+
85
+ /// Confium::Transport::CoordinatorServer — serves coordinator sessions over
86
+ /// any linked transport scheme. Held in a Ruby object; the server
87
+ /// thread runs until the process exits.
88
+ #[derive(TypedData, DataTypeFunctions)]
89
+ #[magnus(class = "Confium::Transport::CoordinatorServer", size)]
90
+ pub struct CoordinatorServer {
91
+ _bound: String,
92
+ }
93
+
94
+ impl CoordinatorServer {
95
+ fn initialize(ruby: &Ruby, url: String) -> Result<Self, Error> {
96
+ let _ = ruby;
97
+ let server = RustCoordinatorServer::new(&url);
98
+ let bound = server
99
+ .start_url(&url)
100
+ .map_err(|e| io_error(e, "CoordinatorServer.new"))?;
101
+ Ok(Self { _bound: bound })
102
+ }
103
+ }
104
+
105
+ pub fn init(ruby: &Ruby, parent: magnus::RModule) -> Result<(), Error> {
106
+ let net = parent.define_module("Transport")?;
107
+
108
+ let client = net.define_class("SignerClient", ruby.class_object())?;
109
+ client.define_singleton_method("new", magnus::function!(SignerClient::initialize, 1))?;
110
+ client.define_method("register", magnus::method!(SignerClient::register, 2))?;
111
+ client.define_method(
112
+ "create_session",
113
+ magnus::method!(SignerClient::create_session, 5),
114
+ )?;
115
+ client.define_method(
116
+ "submit_commitment",
117
+ magnus::method!(SignerClient::submit_commitment, -1),
118
+ )?;
119
+ client.define_method("submit_share", magnus::method!(SignerClient::submit_share, -1))?;
120
+
121
+ let server = net.define_class("CoordinatorServer", ruby.class_object())?;
122
+ server.define_singleton_method("new", magnus::function!(CoordinatorServer::initialize, 1))?;
123
+
124
+ Ok(())
125
+ }
@@ -0,0 +1,183 @@
1
+ //! Confium::Transparency::OTS — OpenTimestamps calendar client.
2
+ //!
3
+ //! Binds confium-transparency's real wire-protocol client (0.9):
4
+ //! `stamp` POSTs the 32-byte digest to a calendar server and parses
5
+ //! the returned partial proof (op stream + pending attestation);
6
+ //! `upgrade` fetches a more complete proof; `verify` replays the op
7
+ //! tree from the digest and classifies the attestations. Network
8
+ //! errors surface as Ruby exceptions — there is no silent nil.
9
+
10
+ use std::cell::RefCell;
11
+
12
+ use confium_transparency::ots::OtsClient;
13
+ use confium_transparency::ots::wire;
14
+ use magnus::prelude::*;
15
+ use magnus::Error;
16
+ use magnus::RHash;
17
+ use magnus::RString;
18
+ use magnus::Ruby;
19
+ use magnus::TypedData;
20
+ use magnus::DataTypeFunctions;
21
+
22
+ /// An OTS proof: the raw .ots file bytes plus the digest it anchors.
23
+ #[derive(TypedData, DataTypeFunctions)]
24
+ #[magnus(class = "Confium::Transparency::OTS::Proof", size)]
25
+ pub struct OtsProof {
26
+ digest: Vec<u8>,
27
+ bytes: Vec<u8>,
28
+ }
29
+
30
+ fn digest_arg_error() -> Error {
31
+ Error::new(magnus::exception::arg_error(), "digest must be exactly 32 bytes")
32
+ }
33
+
34
+ impl OtsProof {
35
+ fn initialize(_ruby: &Ruby, digest: RString, bytes: RString) -> Result<Self, Error> {
36
+ let digest = digest.to_bytes().to_vec();
37
+ if digest.len() != 32 {
38
+ return Err(digest_arg_error());
39
+ }
40
+ Ok(Self {
41
+ digest,
42
+ bytes: bytes.to_bytes().to_vec(),
43
+ })
44
+ }
45
+
46
+ fn digest(&self) -> RString {
47
+ RString::from_slice(&self.digest)
48
+ }
49
+
50
+ fn to_bytes(&self) -> RString {
51
+ RString::from_slice(&self.bytes)
52
+ }
53
+
54
+ /// Replay the proof tree and classify attestations. Returns a Hash:
55
+ /// { pending: [uri, ...], bitcoin: [height, ...],
56
+ /// litecoin: [height, ...], anchored: bool }
57
+ fn verify(&self) -> Result<RHash, Error> {
58
+ let ruby = Ruby::get().map_err(|_| Error::new(magnus::exception::runtime_error(), "not on Ruby thread"))?;
59
+ let file = wire::parse(&self.digest, &self.bytes)
60
+ .map_err(|e| Error::new(parse_error_class(&ruby), format!("invalid OTS proof: {e}")))?;
61
+ let summary = wire::verify(&file)
62
+ .map_err(|e| Error::new(ruby.exception_runtime_error(), format!("replay failed: {e}")))?;
63
+
64
+ let out = RHash::new();
65
+ let pending: Vec<String> = summary
66
+ .pending
67
+ .iter()
68
+ .map(|(_, uri)| uri.clone())
69
+ .collect();
70
+ out.aset(
71
+ "pending",
72
+ ruby.into_value(ruby.ary_from_vec(pending)),
73
+ )?;
74
+ let bitcoin: Vec<i64> = summary.bitcoin.iter().map(|(_, h)| i64::from(*h)).collect();
75
+ out.aset(
76
+ "bitcoin",
77
+ ruby.into_value(ruby.ary_from_vec(bitcoin)),
78
+ )?;
79
+ let litecoin: Vec<i64> = summary.litecoin.iter().map(|(_, h)| i64::from(*h)).collect();
80
+ out.aset(
81
+ "litecoin",
82
+ ruby.into_value(ruby.ary_from_vec(litecoin)),
83
+ )?;
84
+ // Bitcoin/Litecoin attestations are on-chain anchors; pending
85
+ // means recorded at a calendar awaiting confirmation.
86
+ let anchored = !summary.bitcoin.is_empty() || !summary.litecoin.is_empty();
87
+ out.aset("anchored", ruby.into_value(anchored))?;
88
+ Ok(out)
89
+ }
90
+ }
91
+
92
+ /// A calendar client bound to a server pool.
93
+ #[derive(TypedData, DataTypeFunctions)]
94
+ #[magnus(class = "Confium::Transparency::OTS::Client", size)]
95
+ pub struct Client {
96
+ inner: RefCell<OtsClient>,
97
+ }
98
+
99
+ impl Client {
100
+ fn initialize(_ruby: &Ruby, servers: Vec<String>) -> Result<Self, Error> {
101
+ Ok(Self {
102
+ inner: RefCell::new(OtsClient::with_servers(servers)),
103
+ })
104
+ }
105
+
106
+ /// Submit a 32-byte digest to the calendar pool. Returns a Proof
107
+ /// carrying the returned partial proof (pending attestation).
108
+ fn stamp(&self, digest: RString) -> Result<OtsProof, Error> {
109
+ let ruby = Ruby::get().map_err(|_| Error::new(magnus::exception::runtime_error(), "not on Ruby thread"))?;
110
+ let digest = digest.to_bytes().to_vec();
111
+ if digest.len() != 32 {
112
+ return Err(digest_arg_error());
113
+ }
114
+ let arr: [u8; 32] = digest.as_slice().try_into().map_err(|_| digest_arg_error())?;
115
+ let mut inner = self.inner.borrow_mut();
116
+ // Release the GVL for the network round trip: calendars take
117
+ // seconds-to-tens-of-seconds, and holding the VM would freeze
118
+ // every Ruby thread (including any in-process peer).
119
+ let stamped = unsafe {
120
+ crate::gvl::without_gvl(|| inner.stamp_wire(arr))
121
+ };
122
+ let file = stamped
123
+ .map_err(|e| Error::new(ruby.exception_io_error(), format!("calendar stamp failed: {e}")))?;
124
+ let bytes = wire::serialize(&file).map_err(|e| {
125
+ Error::new(
126
+ ruby.exception_runtime_error(),
127
+ format!("calendar returned an unserializable proof: {e}"),
128
+ )
129
+ })?;
130
+ Ok(OtsProof { digest, bytes })
131
+ }
132
+
133
+ /// Fetch a more complete proof for a pending attestation. Returns
134
+ /// the upgraded Proof (replaces the pending one).
135
+ fn upgrade(&self, proof: &OtsProof) -> Result<OtsProof, Error> {
136
+ let ruby = Ruby::get().map_err(|_| Error::new(magnus::exception::runtime_error(), "not on Ruby thread"))?;
137
+ let file = wire::parse(&proof.digest, &proof.bytes)
138
+ .map_err(|e| Error::new(parse_error_class(&ruby), format!("invalid OTS proof: {e}")))?;
139
+ let mut inner = self.inner.borrow_mut();
140
+ let upgraded_result = unsafe { crate::gvl::without_gvl(|| inner.upgrade(&file)) };
141
+ let upgraded = upgraded_result
142
+ .map_err(|e| Error::new(ruby.exception_io_error(), format!("calendar upgrade failed: {e}")))?;
143
+ let bytes = wire::serialize(&upgraded).map_err(|e| {
144
+ Error::new(
145
+ ruby.exception_runtime_error(),
146
+ format!("calendar returned an unserializable proof: {e}"),
147
+ )
148
+ })?;
149
+ Ok(OtsProof {
150
+ digest: proof.digest.clone(),
151
+ bytes,
152
+ })
153
+ }
154
+ }
155
+
156
+ pub fn init(ruby: &Ruby, parent: magnus::RModule) -> Result<(), Error> {
157
+ let ots = parent.define_module("OTS")?;
158
+ let parse_error = ots.define_error("ParseError", ruby.exception_standard_error())?;
159
+ ots.const_set("PARSE_ERROR", parse_error)?;
160
+
161
+ let proof = ots.define_class("Proof", ruby.class_object())?;
162
+ proof.define_singleton_method("new", magnus::function!(OtsProof::initialize, 2))?;
163
+ proof.define_method("digest", magnus::method!(OtsProof::digest, 0))?;
164
+ proof.define_method("to_bytes", magnus::method!(OtsProof::to_bytes, 0))?;
165
+ proof.define_method("verify", magnus::method!(OtsProof::verify, 0))?;
166
+
167
+ let client = ots.define_class("Client", ruby.class_object())?;
168
+ client.define_singleton_method("new", magnus::function!(Client::initialize, 1))?;
169
+ client.define_method("stamp", magnus::method!(Client::stamp, 1))?;
170
+ client.define_method("upgrade", magnus::method!(Client::upgrade, 1))?;
171
+
172
+ Ok(())
173
+ }
174
+
175
+ fn parse_error_class(ruby: &Ruby) -> magnus::ExceptionClass {
176
+ ruby
177
+ .class_object()
178
+ .const_get::<_, magnus::RModule>("Confium")
179
+ .and_then(|m| m.const_get::<_, magnus::RModule>("Transparency"))
180
+ .and_then(|m| m.const_get::<_, magnus::RModule>("OTS"))
181
+ .and_then(|m| m.const_get::<_, magnus::ExceptionClass>("PARSE_ERROR"))
182
+ .unwrap_or_else(|_| ruby.exception_runtime_error())
183
+ }