confium 0.7.0 → 0.7.2
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 +59 -0
- data/Cargo.lock +222 -515
- data/README.adoc +1 -1
- data/ext/confium_native/Cargo.toml +10 -13
- data/ext/confium_native/src/gvl.rs +56 -0
- data/ext/confium_native/src/lib.rs +5 -0
- data/ext/confium_native/src/ots.rs +183 -0
- data/ext/confium_native/src/tc.rs +1 -0
- data/ext/confium_native/src/tc_mta.rs +378 -0
- data/lib/confium/transparency/ots.rb +42 -39
- data/lib/confium/transparency.rb +5 -5
- data/lib/confium/version.rb +1 -1
- metadata +5 -2
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
//! Confium::TC::Cmp20::Mta — the proved MtA sub-protocol binding.
|
|
2
|
+
//!
|
|
3
|
+
//! Wraps `confium-tc-cmp20`'s `paillier_mta`: the GG18/GG20 §3 +
|
|
4
|
+
//! Appendix A multiplicative-to-additive conversion with zero-knowledge
|
|
5
|
+
//! range proofs on every ciphertext. `full` runs the whole exchange
|
|
6
|
+
//! in-process; `party_i_init` / `party_j_respond` / `party_i_finish`
|
|
7
|
+
//! expose the three passes with messages as plain Hashes of hex
|
|
8
|
+
//! integers so rounds can be JSON-encoded onto a transport.
|
|
9
|
+
//!
|
|
10
|
+
//! Trust model: coordinator. The finish step decrypts the response
|
|
11
|
+
//! under the RESPONDER's Paillier key — that is the shape the upstream
|
|
12
|
+
//! crate implements — so the process calling `party_i_finish` (or
|
|
13
|
+
//! `full`) must hold the keypair, exactly like the in-process signing
|
|
14
|
+
//! drivers. Splitting the passes across machines needs the upstream
|
|
15
|
+
//! per-party state machine; until it exists, these are message-level
|
|
16
|
+
//! building blocks under a trusted coordinator.
|
|
17
|
+
|
|
18
|
+
use confium_tc::paillier::{
|
|
19
|
+
generate_keypair as generate_paillier_keypair, PaillierKeypair, PaillierPrivateKey,
|
|
20
|
+
PaillierPublicKey,
|
|
21
|
+
};
|
|
22
|
+
use confium_tc_cmp20::mta_proofs::{
|
|
23
|
+
generate_commitment_key, p256_order, CommitmentKey, RangeProof, RespondentProof,
|
|
24
|
+
};
|
|
25
|
+
use confium_tc_cmp20::paillier_mta::{
|
|
26
|
+
full_mta_proved, party_i_finish_proved, party_i_init_proved, party_j_respond_proved,
|
|
27
|
+
MtaProofError, ProvedMessage1, ProvedMessage2,
|
|
28
|
+
};
|
|
29
|
+
use magnus::prelude::*;
|
|
30
|
+
use magnus::{Error, RHash, RString, Ruby, TryConvert};
|
|
31
|
+
use num_bigint::BigUint;
|
|
32
|
+
use num_traits::Num;
|
|
33
|
+
|
|
34
|
+
const ALGORITHM: &str = "CMP20-ECDSA-P256";
|
|
35
|
+
|
|
36
|
+
fn parse_hex(value: &str, what: &str) -> Result<BigUint, Error> {
|
|
37
|
+
BigUint::from_str_radix(value.trim(), 16)
|
|
38
|
+
.map_err(|_| crate::util::arg_error(format!("{what} must be a hex integer")))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fn hex_string(ruby: &Ruby, value: &BigUint) -> RString {
|
|
42
|
+
ruby.str_from_slice(value.to_str_radix(16).as_bytes())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fn string_field(hash: &RHash, key: &str) -> Result<String, Error> {
|
|
46
|
+
let value: magnus::Value = hash
|
|
47
|
+
.get(key)
|
|
48
|
+
.ok_or_else(|| crate::util::arg_error(format!("missing field {key:?}")))?;
|
|
49
|
+
String::try_convert(value)
|
|
50
|
+
.map_err(|_| crate::util::arg_error(format!("field {key:?} must be a String")))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn hash_field(hash: &RHash, key: &str) -> Result<RHash, Error> {
|
|
54
|
+
let value: magnus::Value = hash
|
|
55
|
+
.get(key)
|
|
56
|
+
.ok_or_else(|| crate::util::arg_error(format!("missing field {key:?}")))?;
|
|
57
|
+
RHash::try_convert(value)
|
|
58
|
+
.map_err(|_| crate::util::arg_error(format!("field {key:?} must be a Hash")))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fn set_hex(ruby: &Ruby, hash: &RHash, key: &str, value: &BigUint) -> Result<(), Error> {
|
|
62
|
+
hash.aset(key, hex_string(ruby, value))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn check_prime_bits(prime_bits: i64) -> Result<u32, Error> {
|
|
66
|
+
if !(64..=8192).contains(&prime_bits) {
|
|
67
|
+
return Err(crate::util::arg_error(format!(
|
|
68
|
+
"prime_bits must be between 64 and 8192, got {prime_bits}"
|
|
69
|
+
)));
|
|
70
|
+
}
|
|
71
|
+
Ok(prime_bits as u32)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ---- key codecs ---------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
fn public_from_hash(hash: &RHash) -> Result<PaillierPublicKey, Error> {
|
|
77
|
+
Ok(PaillierPublicKey {
|
|
78
|
+
n: parse_hex(&string_field(hash, "n")?, "public key n")?,
|
|
79
|
+
n_squared: parse_hex(&string_field(hash, "n_squared")?, "public key n_squared")?,
|
|
80
|
+
g: parse_hex(&string_field(hash, "g")?, "public key g")?,
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn private_from_hash(hash: &RHash) -> Result<PaillierPrivateKey, Error> {
|
|
85
|
+
Ok(PaillierPrivateKey {
|
|
86
|
+
lambda: parse_hex(&string_field(hash, "lambda")?, "private key lambda")?,
|
|
87
|
+
mu: parse_hex(&string_field(hash, "mu")?, "private key mu")?,
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fn public_to_hash(ruby: &Ruby, public: &PaillierPublicKey) -> Result<RHash, Error> {
|
|
92
|
+
let out = ruby.hash_new();
|
|
93
|
+
set_hex(ruby, &out, "n", &public.n)?;
|
|
94
|
+
set_hex(ruby, &out, "n_squared", &public.n_squared)?;
|
|
95
|
+
set_hex(ruby, &out, "g", &public.g)?;
|
|
96
|
+
Ok(out)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fn keypair_to_hash(ruby: &Ruby, kp: &PaillierKeypair) -> Result<RHash, Error> {
|
|
100
|
+
let out = ruby.hash_new();
|
|
101
|
+
out.aset("public", public_to_hash(ruby, &kp.public)?)?;
|
|
102
|
+
let private = ruby.hash_new();
|
|
103
|
+
set_hex(ruby, &private, "lambda", &kp.private.lambda)?;
|
|
104
|
+
set_hex(ruby, &private, "mu", &kp.private.mu)?;
|
|
105
|
+
out.aset("private", private)?;
|
|
106
|
+
Ok(out)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fn commitment_key_from_hash(hash: &RHash) -> Result<CommitmentKey, Error> {
|
|
110
|
+
Ok(CommitmentKey {
|
|
111
|
+
n_tilde: parse_hex(&string_field(hash, "n_tilde")?, "commitment key n_tilde")?,
|
|
112
|
+
h1: parse_hex(&string_field(hash, "h1")?, "commitment key h1")?,
|
|
113
|
+
h2: parse_hex(&string_field(hash, "h2")?, "commitment key h2")?,
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fn commitment_key_to_hash(ruby: &Ruby, ck: &CommitmentKey) -> Result<RHash, Error> {
|
|
118
|
+
let out = ruby.hash_new();
|
|
119
|
+
set_hex(ruby, &out, "n_tilde", &ck.n_tilde)?;
|
|
120
|
+
set_hex(ruby, &out, "h1", &ck.h1)?;
|
|
121
|
+
set_hex(ruby, &out, "h2", &ck.h2)?;
|
|
122
|
+
Ok(out)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---- message codecs -----------------------------------------------------
|
|
126
|
+
|
|
127
|
+
fn range_proof_from_hash(hash: &RHash) -> Result<RangeProof, Error> {
|
|
128
|
+
Ok(RangeProof {
|
|
129
|
+
z: parse_hex(&string_field(hash, "z")?, "range proof z")?,
|
|
130
|
+
u: parse_hex(&string_field(hash, "u")?, "range proof u")?,
|
|
131
|
+
w: parse_hex(&string_field(hash, "w")?, "range proof w")?,
|
|
132
|
+
s: parse_hex(&string_field(hash, "s")?, "range proof s")?,
|
|
133
|
+
s1: parse_hex(&string_field(hash, "s1")?, "range proof s1")?,
|
|
134
|
+
s2: parse_hex(&string_field(hash, "s2")?, "range proof s2")?,
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fn range_proof_to_hash(ruby: &Ruby, proof: &RangeProof) -> Result<RHash, Error> {
|
|
139
|
+
let out = ruby.hash_new();
|
|
140
|
+
set_hex(ruby, &out, "z", &proof.z)?;
|
|
141
|
+
set_hex(ruby, &out, "u", &proof.u)?;
|
|
142
|
+
set_hex(ruby, &out, "w", &proof.w)?;
|
|
143
|
+
set_hex(ruby, &out, "s", &proof.s)?;
|
|
144
|
+
set_hex(ruby, &out, "s1", &proof.s1)?;
|
|
145
|
+
set_hex(ruby, &out, "s2", &proof.s2)?;
|
|
146
|
+
Ok(out)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fn respondent_proof_from_hash(hash: &RHash) -> Result<RespondentProof, Error> {
|
|
150
|
+
Ok(RespondentProof {
|
|
151
|
+
z: parse_hex(&string_field(hash, "z")?, "respondent proof z")?,
|
|
152
|
+
z_prime: parse_hex(&string_field(hash, "z_prime")?, "respondent proof z_prime")?,
|
|
153
|
+
t: parse_hex(&string_field(hash, "t")?, "respondent proof t")?,
|
|
154
|
+
v: parse_hex(&string_field(hash, "v")?, "respondent proof v")?,
|
|
155
|
+
w: parse_hex(&string_field(hash, "w")?, "respondent proof w")?,
|
|
156
|
+
s: parse_hex(&string_field(hash, "s")?, "respondent proof s")?,
|
|
157
|
+
s1: parse_hex(&string_field(hash, "s1")?, "respondent proof s1")?,
|
|
158
|
+
s2: parse_hex(&string_field(hash, "s2")?, "respondent proof s2")?,
|
|
159
|
+
t1: parse_hex(&string_field(hash, "t1")?, "respondent proof t1")?,
|
|
160
|
+
t2: parse_hex(&string_field(hash, "t2")?, "respondent proof t2")?,
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fn respondent_proof_to_hash(ruby: &Ruby, proof: &RespondentProof) -> Result<RHash, Error> {
|
|
165
|
+
let out = ruby.hash_new();
|
|
166
|
+
set_hex(ruby, &out, "z", &proof.z)?;
|
|
167
|
+
set_hex(ruby, &out, "z_prime", &proof.z_prime)?;
|
|
168
|
+
set_hex(ruby, &out, "t", &proof.t)?;
|
|
169
|
+
set_hex(ruby, &out, "v", &proof.v)?;
|
|
170
|
+
set_hex(ruby, &out, "w", &proof.w)?;
|
|
171
|
+
set_hex(ruby, &out, "s", &proof.s)?;
|
|
172
|
+
set_hex(ruby, &out, "s1", &proof.s1)?;
|
|
173
|
+
set_hex(ruby, &out, "s2", &proof.s2)?;
|
|
174
|
+
set_hex(ruby, &out, "t1", &proof.t1)?;
|
|
175
|
+
set_hex(ruby, &out, "t2", &proof.t2)?;
|
|
176
|
+
Ok(out)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
fn msg1_from_hash(hash: &RHash) -> Result<ProvedMessage1, Error> {
|
|
180
|
+
Ok(ProvedMessage1 {
|
|
181
|
+
ciphertext: parse_hex(&string_field(hash, "ciphertext")?, "message 1 ciphertext")?,
|
|
182
|
+
range_proof: range_proof_from_hash(&hash_field(hash, "range_proof")?)?,
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
fn msg1_to_hash(ruby: &Ruby, msg: &ProvedMessage1) -> Result<RHash, Error> {
|
|
187
|
+
let out = ruby.hash_new();
|
|
188
|
+
set_hex(ruby, &out, "ciphertext", &msg.ciphertext)?;
|
|
189
|
+
out.aset("range_proof", range_proof_to_hash(ruby, &msg.range_proof)?)?;
|
|
190
|
+
Ok(out)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
fn msg2_from_hash(hash: &RHash) -> Result<ProvedMessage2, Error> {
|
|
194
|
+
Ok(ProvedMessage2 {
|
|
195
|
+
ciphertext: parse_hex(&string_field(hash, "ciphertext")?, "message 2 ciphertext")?,
|
|
196
|
+
respondent_proof: respondent_proof_from_hash(&hash_field(hash, "respondent_proof")?)?,
|
|
197
|
+
beta: parse_hex(&string_field(hash, "beta")?, "message 2 beta")?,
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
fn msg2_to_hash(ruby: &Ruby, msg: &ProvedMessage2) -> Result<RHash, Error> {
|
|
202
|
+
let out = ruby.hash_new();
|
|
203
|
+
set_hex(ruby, &out, "ciphertext", &msg.ciphertext)?;
|
|
204
|
+
out.aset(
|
|
205
|
+
"respondent_proof",
|
|
206
|
+
respondent_proof_to_hash(ruby, &msg.respondent_proof)?,
|
|
207
|
+
)?;
|
|
208
|
+
set_hex(ruby, &out, "beta", &msg.beta)?;
|
|
209
|
+
Ok(out)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ---- ops ----------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
fn mta_error(ruby: &Ruby, operation: &str, e: MtaProofError) -> Error {
|
|
215
|
+
let message = e.to_string();
|
|
216
|
+
crate::audit::fire_event(
|
|
217
|
+
operation,
|
|
218
|
+
"failure",
|
|
219
|
+
Some(ALGORITHM),
|
|
220
|
+
None,
|
|
221
|
+
Some(&message),
|
|
222
|
+
);
|
|
223
|
+
Error::new(mta_error_class(ruby), message)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
fn mta_generate_keypair(ruby: &Ruby, prime_bits: i64) -> Result<RHash, Error> {
|
|
227
|
+
let bits = check_prime_bits(prime_bits)?;
|
|
228
|
+
// Safe-prime search is CPU-bound and takes seconds at production
|
|
229
|
+
// sizes — release the GVL so sibling Ruby threads keep running.
|
|
230
|
+
let kp = unsafe { crate::gvl::without_gvl(|| generate_paillier_keypair(bits)) };
|
|
231
|
+
crate::audit::fire_event("tc_cmp20_mta_keypair", "success", Some(ALGORITHM), None, None);
|
|
232
|
+
keypair_to_hash(ruby, &kp)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
fn mta_generate_commitment_key(ruby: &Ruby, prime_bits: i64) -> Result<RHash, Error> {
|
|
236
|
+
let bits = check_prime_bits(prime_bits)?;
|
|
237
|
+
let ck = unsafe { crate::gvl::without_gvl(|| generate_commitment_key(bits)) };
|
|
238
|
+
crate::audit::fire_event("tc_cmp20_mta_commitment_key", "success", Some(ALGORITHM), None, None);
|
|
239
|
+
commitment_key_to_hash(ruby, &ck)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fn mta_party_i_init(
|
|
243
|
+
ruby: &Ruby,
|
|
244
|
+
j_public: RHash,
|
|
245
|
+
ck_j: RHash,
|
|
246
|
+
q: String,
|
|
247
|
+
k_i: String,
|
|
248
|
+
) -> Result<RHash, Error> {
|
|
249
|
+
let public = public_from_hash(&j_public)?;
|
|
250
|
+
let ck = commitment_key_from_hash(&ck_j)?;
|
|
251
|
+
let q = parse_hex(&q, "q")?;
|
|
252
|
+
let k_i = parse_hex(&k_i, "k_i")?;
|
|
253
|
+
let msg = party_i_init_proved(&public, &ck, &q, &k_i).map_err(|e| mta_error(ruby, "tc_cmp20_mta_init", e))?;
|
|
254
|
+
crate::audit::fire_event("tc_cmp20_mta_init", "success", Some(ALGORITHM), None, None);
|
|
255
|
+
msg1_to_hash(ruby, &msg)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
fn mta_party_j_respond(
|
|
259
|
+
ruby: &Ruby,
|
|
260
|
+
j_public: RHash,
|
|
261
|
+
j_private: RHash,
|
|
262
|
+
ck_i: RHash,
|
|
263
|
+
ck_j: RHash,
|
|
264
|
+
q: String,
|
|
265
|
+
msg1: RHash,
|
|
266
|
+
x_j: String,
|
|
267
|
+
) -> Result<magnus::RArray, Error> {
|
|
268
|
+
let public = public_from_hash(&j_public)?;
|
|
269
|
+
let private = private_from_hash(&j_private)?;
|
|
270
|
+
let ck_i = commitment_key_from_hash(&ck_i)?;
|
|
271
|
+
let ck_j = commitment_key_from_hash(&ck_j)?;
|
|
272
|
+
let q = parse_hex(&q, "q")?;
|
|
273
|
+
let msg1 = msg1_from_hash(&msg1)?;
|
|
274
|
+
let x_j = parse_hex(&x_j, "x_j")?;
|
|
275
|
+
let (msg2, beta) = party_j_respond_proved(
|
|
276
|
+
&PaillierKeypair {
|
|
277
|
+
public,
|
|
278
|
+
private,
|
|
279
|
+
},
|
|
280
|
+
&ck_i,
|
|
281
|
+
&ck_j,
|
|
282
|
+
&q,
|
|
283
|
+
&msg1,
|
|
284
|
+
&x_j,
|
|
285
|
+
)
|
|
286
|
+
.map_err(|e| mta_error(ruby, "tc_cmp20_mta_respond", e))?;
|
|
287
|
+
crate::audit::fire_event("tc_cmp20_mta_respond", "success", Some(ALGORITHM), None, None);
|
|
288
|
+
let out = ruby.ary_new_capa(2);
|
|
289
|
+
out.push(msg2_to_hash(ruby, &msg2)?)?;
|
|
290
|
+
out.push(hex_string(ruby, &beta))?;
|
|
291
|
+
Ok(out)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
fn mta_party_i_finish(
|
|
295
|
+
ruby: &Ruby,
|
|
296
|
+
j_public: RHash,
|
|
297
|
+
j_private: RHash,
|
|
298
|
+
ck_i: RHash,
|
|
299
|
+
q: String,
|
|
300
|
+
msg1_ciphertext: String,
|
|
301
|
+
msg2: RHash,
|
|
302
|
+
) -> Result<RString, Error> {
|
|
303
|
+
let public = public_from_hash(&j_public)?;
|
|
304
|
+
let private = private_from_hash(&j_private)?;
|
|
305
|
+
let ck_i = commitment_key_from_hash(&ck_i)?;
|
|
306
|
+
let q = parse_hex(&q, "q")?;
|
|
307
|
+
let ciphertext = parse_hex(&msg1_ciphertext, "message 1 ciphertext")?;
|
|
308
|
+
let msg2 = msg2_from_hash(&msg2)?;
|
|
309
|
+
let alpha = party_i_finish_proved(&public, &ck_i, &q, &ciphertext, &private, &msg2)
|
|
310
|
+
.map_err(|e| mta_error(ruby, "tc_cmp20_mta_finish", e))?;
|
|
311
|
+
crate::audit::fire_event("tc_cmp20_mta_finish", "success", Some(ALGORITHM), None, None);
|
|
312
|
+
Ok(hex_string(ruby, &alpha))
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
fn mta_full(
|
|
316
|
+
ruby: &Ruby,
|
|
317
|
+
j_public: RHash,
|
|
318
|
+
j_private: RHash,
|
|
319
|
+
ck_i: RHash,
|
|
320
|
+
ck_j: RHash,
|
|
321
|
+
q: String,
|
|
322
|
+
k_i: String,
|
|
323
|
+
x_j: String,
|
|
324
|
+
) -> Result<magnus::RArray, Error> {
|
|
325
|
+
let public = public_from_hash(&j_public)?;
|
|
326
|
+
let private = private_from_hash(&j_private)?;
|
|
327
|
+
let ck_i = commitment_key_from_hash(&ck_i)?;
|
|
328
|
+
let ck_j = commitment_key_from_hash(&ck_j)?;
|
|
329
|
+
let q = parse_hex(&q, "q")?;
|
|
330
|
+
let k_i = parse_hex(&k_i, "k_i")?;
|
|
331
|
+
let x_j = parse_hex(&x_j, "x_j")?;
|
|
332
|
+
let (alpha, beta) = full_mta_proved(
|
|
333
|
+
&PaillierKeypair {
|
|
334
|
+
public,
|
|
335
|
+
private,
|
|
336
|
+
},
|
|
337
|
+
&ck_i,
|
|
338
|
+
&ck_j,
|
|
339
|
+
&q,
|
|
340
|
+
&k_i,
|
|
341
|
+
&x_j,
|
|
342
|
+
)
|
|
343
|
+
.map_err(|e| mta_error(ruby, "tc_cmp20_mta_full", e))?;
|
|
344
|
+
crate::audit::fire_event("tc_cmp20_mta_full", "success", Some(ALGORITHM), None, None);
|
|
345
|
+
let out = ruby.ary_new_capa(2);
|
|
346
|
+
out.push(hex_string(ruby, &alpha))?;
|
|
347
|
+
out.push(hex_string(ruby, &beta))?;
|
|
348
|
+
Ok(out)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
pub fn init(ruby: &Ruby, cmp20: &magnus::RModule) -> Result<(), Error> {
|
|
352
|
+
let mta = cmp20.define_module("Mta")?;
|
|
353
|
+
let error = cmp20.define_error("MtaError", ruby.exception_standard_error())?;
|
|
354
|
+
cmp20.const_set("MTA_ERROR", error)?;
|
|
355
|
+
let order = p256_order().to_str_radix(16);
|
|
356
|
+
mta.const_set("P256_ORDER", ruby.str_from_slice(order.as_bytes()))?;
|
|
357
|
+
|
|
358
|
+
mta.define_module_function("generate_keypair", magnus::function!(mta_generate_keypair, 1))?;
|
|
359
|
+
mta.define_module_function(
|
|
360
|
+
"generate_commitment_key",
|
|
361
|
+
magnus::function!(mta_generate_commitment_key, 1),
|
|
362
|
+
)?;
|
|
363
|
+
mta.define_module_function("party_i_init", magnus::function!(mta_party_i_init, 4))?;
|
|
364
|
+
mta.define_module_function("party_j_respond", magnus::function!(mta_party_j_respond, 7))?;
|
|
365
|
+
mta.define_module_function("party_i_finish", magnus::function!(mta_party_i_finish, 6))?;
|
|
366
|
+
mta.define_module_function("full", magnus::function!(mta_full, 7))?;
|
|
367
|
+
Ok(())
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
fn mta_error_class(ruby: &Ruby) -> magnus::ExceptionClass {
|
|
371
|
+
ruby
|
|
372
|
+
.class_object()
|
|
373
|
+
.const_get::<_, magnus::RModule>("Confium")
|
|
374
|
+
.and_then(|m| m.const_get::<_, magnus::RModule>("TC"))
|
|
375
|
+
.and_then(|m| m.const_get::<_, magnus::RModule>("Cmp20"))
|
|
376
|
+
.and_then(|m| m.const_get::<_, magnus::ExceptionClass>("MTA_ERROR"))
|
|
377
|
+
.unwrap_or_else(|_| ruby.exception_runtime_error())
|
|
378
|
+
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Confium::Transparency::OTS — OpenTimestamps client
|
|
3
|
+
# Confium::Transparency::OTS — OpenTimestamps calendar client.
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# (TODO.completion/011-ots-ers-exposure.md).
|
|
5
|
+
# Real wire-protocol implementation over the native extension
|
|
6
|
+
# (confium-transparency 0.8.5+): stamps POST the 32-byte digest to a
|
|
7
|
+
# calendar server and parse the returned partial proof; verification
|
|
8
|
+
# replays the op tree from the digest and classifies attestations.
|
|
9
|
+
# Network failures raise (Confium::Transparency::OTS is backed by a
|
|
10
|
+
# real HTTP client) — there is no silent nil.
|
|
12
11
|
|
|
13
12
|
module Confium
|
|
14
13
|
module Transparency
|
|
@@ -20,44 +19,48 @@ module Confium
|
|
|
20
19
|
https://a.pool.eternitywall.com
|
|
21
20
|
].freeze
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
class << self
|
|
23
|
+
# Stamp a 32-byte digest via the default calendar pool.
|
|
24
|
+
# Returns a Proof (pending attestation — Bitcoin confirmation
|
|
25
|
+
# arrives later; poll with #upgrade).
|
|
26
|
+
#
|
|
27
|
+
# @param hash [String] 32-byte SHA-256 digest to anchor
|
|
28
|
+
# @return [Confium::Transparency::OTS::Proof]
|
|
29
|
+
# @raise [IOError, ArgumentError]
|
|
30
|
+
def stamp(hash)
|
|
31
|
+
default_client.stamp(hash)
|
|
32
|
+
end
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
# Verify a proof: replay its op tree from the digest and
|
|
35
|
+
# classify the attestations.
|
|
36
|
+
#
|
|
37
|
+
# @param proof [Proof, String] the proof (or raw .ots bytes —
|
|
38
|
+
# pass the digest alongside for the String form)
|
|
39
|
+
# @param digest [String, nil] required when proof is bytes
|
|
40
|
+
# @return [Hash] { pending: [uri], bitcoin: [height],
|
|
41
|
+
# litecoin: [height], anchored: bool }
|
|
42
|
+
def verify(proof, digest = nil)
|
|
43
|
+
proof = Proof.new(digest, proof) if digest && proof.is_a?(String)
|
|
44
|
+
proof.verify
|
|
31
45
|
end
|
|
32
46
|
|
|
33
|
-
|
|
34
|
-
|
|
47
|
+
# Upgrade a pending proof (fetch a more complete one).
|
|
48
|
+
#
|
|
49
|
+
# @return [Proof]
|
|
50
|
+
def upgrade(proof)
|
|
51
|
+
default_client.upgrade(proof)
|
|
35
52
|
end
|
|
36
|
-
end
|
|
37
53
|
|
|
38
|
-
|
|
39
|
-
# Returns a Receipt (stub: returns nil — requires network).
|
|
40
|
-
#
|
|
41
|
-
# @param hash [String] 32-byte SHA-256 hash to anchor
|
|
42
|
-
# @return [Receipt, nil]
|
|
43
|
-
def self.stamp(_hash)
|
|
44
|
-
# Real implementation: calls the Rust OTS client which
|
|
45
|
-
# submits the hash to calendar servers and returns a
|
|
46
|
-
# merged proof. Requires network access.
|
|
47
|
-
nil
|
|
48
|
-
end
|
|
54
|
+
private
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# @param receipt [Receipt, String] the OTS proof
|
|
54
|
-
# @param hash [String] the 32-byte hash
|
|
55
|
-
# @return [Boolean]
|
|
56
|
-
def self.verify(_receipt, _hash)
|
|
57
|
-
# Real implementation: calls the Rust OTS verifier which
|
|
58
|
-
# walks the Bitcoin blockchain proof.
|
|
59
|
-
false
|
|
56
|
+
def default_client
|
|
57
|
+
@default_client ||= Client.new(DEFAULT_CALENDARS)
|
|
58
|
+
end
|
|
60
59
|
end
|
|
60
|
+
|
|
61
|
+
# The native Client is defined by the extension
|
|
62
|
+
# (Confium::Transparency::OTS::Client) with #stamp / #upgrade;
|
|
63
|
+
# the Proof class carries #digest / #to_bytes / #verify.
|
|
61
64
|
end
|
|
62
65
|
end
|
|
63
66
|
end
|
data/lib/confium/transparency.rb
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
# The Transparency module itself is defined by the native Rust
|
|
6
6
|
# extension via magnus at require time. This file registers
|
|
7
7
|
# pure-Ruby autoloads for the Transparency submodules.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
# `require`, not `autoload`: the native extension defines the OTS
|
|
9
|
+
# constant at load time, which would shadow the autoload entry and
|
|
10
|
+
# keep this file from ever running — the Ruby-level convenience
|
|
11
|
+
# methods would silently vanish.
|
|
12
|
+
require 'confium/transparency/ots'
|
data/lib/confium/version.rb
CHANGED
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.7.
|
|
4
|
+
version: 0.7.2
|
|
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-09-
|
|
11
|
+
date: 2026-09-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|
|
@@ -120,13 +120,16 @@ files:
|
|
|
120
120
|
- ext/confium_native/src/composite.rs
|
|
121
121
|
- ext/confium_native/src/deployment.rs
|
|
122
122
|
- ext/confium_native/src/ers.rs
|
|
123
|
+
- ext/confium_native/src/gvl.rs
|
|
123
124
|
- ext/confium_native/src/lib.rs
|
|
124
125
|
- ext/confium_native/src/net.rs
|
|
125
126
|
- ext/confium_native/src/openpgp_verify.rs
|
|
127
|
+
- ext/confium_native/src/ots.rs
|
|
126
128
|
- ext/confium_native/src/path.rs
|
|
127
129
|
- ext/confium_native/src/pki.rs
|
|
128
130
|
- ext/confium_native/src/store.rs
|
|
129
131
|
- ext/confium_native/src/tc.rs
|
|
132
|
+
- ext/confium_native/src/tc_mta.rs
|
|
130
133
|
- ext/confium_native/src/tc_session.rs
|
|
131
134
|
- ext/confium_native/src/transparency.rs
|
|
132
135
|
- ext/confium_native/src/util.rs
|