confium 0.7.1 → 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.
@@ -42,27 +42,24 @@ confium-attributes = "0.3.1"
42
42
  confium-pki = "0.5.6"
43
43
  confium-store = "0.5.8"
44
44
  confium-deployment = "0.3"
45
- confium-tc = "0.3.1"
45
+ confium-tc = "0.9"
46
46
  confium-tc-frost-p256 = "0.4"
47
47
  confium-tc-elgamal-p256 = "0.4"
48
- confium-tc-cmp20 = "0.4"
49
- confium-tc-gg18 = "0.4"
48
+ confium-tc-cmp20 = "0.9"
49
+ confium-tc-gg18 = "0.9"
50
50
 
51
51
  # Per-party session protocol: the tc-core 0.4 registry line. The 0.3-era
52
52
  # in-process drivers above keep their own registry harmlessly.
53
- confium-tc-session = { package = "confium-tc-core", version = "0.4.7" }
54
- confium-tc-frost-ed25519 = "0.4.7"
55
-
56
- # Newly published shared crypto crates (confium product restructuring).
57
- confium-tc-core = "0.3"
58
- confium-crypto-vss = "0.3"
59
- confium-crypto-zk = "0.3"
60
- confium-privacy = "0.3"
61
- confium-observability = "0.3"
53
+ confium-tc-session = { package = "confium-tc-core", version = "0.9" }
54
+ confium-tc-frost-ed25519 = "0.9"
62
55
 
63
56
  # P-256 scalar/point types used by the TC surface.
64
57
  p256 = { version = "0.13", features = ["ecdsa"] }
65
58
 
59
+ # Hex integer codecs for the MtA surface.
60
+ num-bigint = "0.4"
61
+ num-traits = "0.2"
62
+
66
63
  ed25519-dalek = { version = "2", features = ["rand_core"] }
67
64
  rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
68
65
 
@@ -15,6 +15,7 @@ mod path;
15
15
  mod pki;
16
16
  mod store;
17
17
  mod tc;
18
+ mod tc_mta;
18
19
  mod net;
19
20
  mod ots;
20
21
  mod tc_session;
@@ -232,6 +232,7 @@ pub fn init(ruby: &Ruby, parent: magnus::RModule) -> Result<(), Error> {
232
232
  let cmp20 = tc.define_module("Cmp20")?;
233
233
  cmp20.define_module_function("keygen", function!(cmp20_keygen, 2))?;
234
234
  cmp20.define_module_function("sign", function!(cmp20_sign, 3))?;
235
+ crate::tc_mta::init(ruby, &cmp20)?;
235
236
 
236
237
  let gg18 = tc.define_module("Gg18")?;
237
238
  gg18.define_module_function("keygen", function!(gg18_keygen, 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,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Confium
4
- VERSION = '0.7.1'
4
+ VERSION = '0.7.2'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: confium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Open
@@ -129,6 +129,7 @@ files:
129
129
  - ext/confium_native/src/pki.rs
130
130
  - ext/confium_native/src/store.rs
131
131
  - ext/confium_native/src/tc.rs
132
+ - ext/confium_native/src/tc_mta.rs
132
133
  - ext/confium_native/src/tc_session.rs
133
134
  - ext/confium_native/src/transparency.rs
134
135
  - ext/confium_native/src/util.rs