confium 0.3.2 → 0.3.4

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.
@@ -10,7 +10,7 @@
10
10
  //! *building* + signing lands in a follow-up once the Rust builder API
11
11
  //! stabilizes (currently `confium_pki::cert::builder` is private).
12
12
 
13
- use crate::util::{bytes_from_value, bytes_to_rstring, enforce_size};
13
+ use crate::util::{bytes_from_value, bytes_to_rstring, enforce_size, parse_error};
14
14
  use chrono::{DateTime, Utc};
15
15
  use confium_pki::{
16
16
  cert::{Certificate as RustCert, CertificateSigningRequest as RustCsr},
@@ -20,7 +20,7 @@ use confium_pki::{
20
20
  xmldsig::{canonicalize, canonicalize_exclusive},
21
21
  };
22
22
  use magnus::{
23
- exception, function, method, prelude::*, typed_data::Obj, DataTypeFunctions, Error, Module,
23
+ function, method, prelude::*, typed_data::Obj, DataTypeFunctions, Error, Module,
24
24
  Object, RHash, RString, Ruby, TryConvert, TypedData, Value,
25
25
  };
26
26
 
@@ -34,21 +34,21 @@ impl Certificate {
34
34
  fn from_der(bytes: Value) -> Result<Obj<Self>, Error> {
35
35
  let der = bytes_from_value(bytes)?;
36
36
  let cert = RustCert::from_der(&der)
37
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
38
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
37
+ .map_err(|e| parse_error(e.to_string(), "Certificate.from_der", Some("der"), None))?;
38
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
39
39
  Ok(ruby.obj_wrap(Self { inner: cert }))
40
40
  }
41
41
 
42
42
  fn from_pem(pem: String) -> Result<Obj<Self>, Error> {
43
43
  enforce_size(pem.len())?;
44
44
  let cert = RustCert::from_pem(&pem)
45
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
46
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
45
+ .map_err(|e| parse_error(e.to_string(), "Certificate.from_pem", Some("pem"), None))?;
46
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
47
47
  Ok(ruby.obj_wrap(Self { inner: cert }))
48
48
  }
49
49
 
50
50
  fn to_der(&self) -> Result<RString, Error> {
51
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
51
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
52
52
  Ok(bytes_to_rstring(&ruby, &self.inner.to_der()))
53
53
  }
54
54
 
@@ -81,13 +81,13 @@ impl Certificate {
81
81
 
82
82
  fn valid_at(&self, iso8601: String) -> Result<bool, Error> {
83
83
  let now = DateTime::parse_from_rfc3339(&iso8601)
84
- .map_err(|e| Error::new(exception::arg_error(), format!("invalid ISO8601 time: {e}")))?
84
+ .map_err(|e| crate::util::arg_error(format!("invalid ISO8601 time: {e}")))?
85
85
  .with_timezone(&Utc);
86
86
  Ok(self.inner.is_within_validity(now))
87
87
  }
88
88
 
89
89
  fn public_key_bytes(&self) -> Result<RString, Error> {
90
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
90
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
91
91
  Ok(bytes_to_rstring(&ruby, self.inner.public_key_bytes()))
92
92
  }
93
93
  }
@@ -102,21 +102,21 @@ impl Csr {
102
102
  fn from_der(bytes: Value) -> Result<Obj<Self>, Error> {
103
103
  let der = bytes_from_value(bytes)?;
104
104
  let csr = RustCsr::from_der(&der)
105
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
106
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
105
+ .map_err(|e| parse_error(e.to_string(), "Csr.from_der", Some("der"), None))?;
106
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
107
107
  Ok(ruby.obj_wrap(Self { inner: csr }))
108
108
  }
109
109
 
110
110
  fn from_pem(pem: String) -> Result<Obj<Self>, Error> {
111
111
  enforce_size(pem.len())?;
112
112
  let csr = RustCsr::from_pem(&pem)
113
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
114
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
113
+ .map_err(|e| parse_error(e.to_string(), "Csr.from_pem", Some("pem"), None))?;
114
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
115
115
  Ok(ruby.obj_wrap(Self { inner: csr }))
116
116
  }
117
117
 
118
118
  fn to_der(&self) -> Result<RString, Error> {
119
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
119
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
120
120
  Ok(bytes_to_rstring(&ruby, &self.inner.to_der()))
121
121
  }
122
122
 
@@ -135,8 +135,8 @@ impl SignedData {
135
135
  fn from_json(json: String) -> Result<Obj<Self>, Error> {
136
136
  enforce_size(json.len())?;
137
137
  let sd: RustSignedData = serde_json::from_str(&json)
138
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
139
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
138
+ .map_err(|e| parse_error(e.to_string(), "SignedData.from_json", Some("json"), None))?;
139
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
140
140
  Ok(ruby.obj_wrap(Self {
141
141
  inner: std::cell::RefCell::new(sd),
142
142
  }))
@@ -162,15 +162,11 @@ impl SignedData {
162
162
  certificates: Value,
163
163
  ) -> Result<Obj<Self>, Error> {
164
164
  if signature.is_nil() {
165
- return Err(Error::new(
166
- exception::arg_error(),
167
- "signature is required",
165
+ return Err(crate::util::arg_error("signature is required",
168
166
  ));
169
167
  }
170
168
  if certificates.is_nil() {
171
- return Err(Error::new(
172
- exception::arg_error(),
173
- "certificates is required",
169
+ return Err(crate::util::arg_error("certificates is required",
174
170
  ));
175
171
  }
176
172
 
@@ -179,16 +175,15 @@ impl SignedData {
179
175
 
180
176
  let certs_array: magnus::RArray = magnus::RArray::try_convert(certificates)?;
181
177
  let mut cert_ders = Vec::with_capacity(certs_array.len());
182
- for item in certs_array.each() {
183
- let v = item?;
178
+ for v in certs_array.into_iter() {
184
179
  let der = bytes_from_value(v)?;
185
180
  enforce_size(der.len())?;
186
181
  cert_ders.push(der);
187
182
  }
188
183
 
189
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
184
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
190
185
  let sd = build_detached_signature(Vec::new(), algorithm, sig_bytes, cert_ders)
191
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
186
+ .map_err(|e| crate::util::runtime(e.to_string()))?;
192
187
  Ok(ruby.obj_wrap(Self {
193
188
  inner: std::cell::RefCell::new(sd),
194
189
  }))
@@ -196,7 +191,7 @@ impl SignedData {
196
191
 
197
192
  fn to_json(&self) -> Result<String, Error> {
198
193
  serde_json::to_string(&*self.inner.borrow())
199
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))
194
+ .map_err(|e| crate::util::runtime(e.to_string()))
200
195
  }
201
196
 
202
197
  /// Encode this SignedData as DER bytes (RFC 5652 ContentInfo).
@@ -204,9 +199,9 @@ impl SignedData {
204
199
  /// The output is parseable by `openssl cms` / `openssl pkcs7` and
205
200
  /// any standards-compliant CMS consumer.
206
201
  fn to_der(&self) -> Result<RString, Error> {
207
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
202
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
208
203
  let der = encode_signed_data_der(&self.inner.borrow())
209
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
204
+ .map_err(|e| crate::util::runtime(e.to_string()))?;
210
205
  Ok(bytes_to_rstring(&ruby, &der))
211
206
  }
212
207
 
@@ -225,7 +220,7 @@ impl SignedData {
225
220
  fn content(&self) -> Result<Option<Obj<CertWrapper>>, Error> {
226
221
  // Wrap the optional content bytes in a small value object so Ruby
227
222
  // can ask `.present?` / `.bytes` without juggling nil-vs-string.
228
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
223
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
229
224
  match &self.inner.borrow().encap_content_info.content {
230
225
  Some(bytes) => {
231
226
  let wrapper = CertWrapper {
@@ -242,16 +237,17 @@ impl SignedData {
242
237
  }
243
238
 
244
239
  fn certificate_at(&self, index: usize) -> Result<RString, Error> {
245
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
240
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
246
241
  self.inner
247
242
  .borrow()
248
243
  .certificates
249
244
  .get(index)
250
245
  .map(|c| bytes_to_rstring(&ruby, c))
251
246
  .ok_or_else(|| {
252
- Error::new(
253
- exception::index_error(),
247
+ crate::util::index_error(
254
248
  format!("certificate index {index} out of range"),
249
+ "SignedData.certificate_at",
250
+ Some(index as u64),
255
251
  )
256
252
  })
257
253
  }
@@ -294,8 +290,8 @@ impl SignedData {
294
290
  } else {
295
291
  Err(format!("unsupported signature algorithm OID: {oid}"))
296
292
  }
297
- }).map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
298
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
293
+ }).map_err(|e| crate::util::runtime(e.to_string()))?;
294
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
299
295
  Ok(ruby.obj_wrap(CmsVerificationResult { inner: result }))
300
296
  }
301
297
  }
@@ -349,7 +345,7 @@ pub struct CertWrapper {
349
345
 
350
346
  impl CertWrapper {
351
347
  fn bytes(&self) -> Result<RString, Error> {
352
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
348
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
353
349
  Ok(bytes_to_rstring(&ruby, &self.bytes))
354
350
  }
355
351
 
@@ -362,12 +358,12 @@ impl CertWrapper {
362
358
 
363
359
  fn xmldsig_canonicalize(xml: String) -> Result<String, Error> {
364
360
  enforce_size(xml.len())?;
365
- canonicalize(&xml).map_err(|e| Error::new(exception::runtime_error(), e.to_string()))
361
+ canonicalize(&xml).map_err(|e| parse_error(e.to_string(), "XMLDSig.canonicalize", Some("xml"), None))
366
362
  }
367
363
 
368
364
  fn xmldsig_canonicalize_exclusive(xml: String) -> Result<String, Error> {
369
365
  enforce_size(xml.len())?;
370
- canonicalize_exclusive(&xml).map_err(|e| Error::new(exception::runtime_error(), e.to_string()))
366
+ canonicalize_exclusive(&xml).map_err(|e| parse_error(e.to_string(), "XMLDSig.canonicalize_exclusive", Some("xml"), None))
371
367
  }
372
368
 
373
369
  pub fn init(ruby: &Ruby, parent: magnus::RModule) -> Result<(), Error> {
@@ -19,10 +19,10 @@ use confium_tc_frost_p256::{
19
19
  };
20
20
  use confium_tc_cmp20::inprocess as cmp20_inprocess;
21
21
  use confium_tc_gg18::inprocess as gg18_inprocess;
22
- use magnus::{exception, function, method, prelude::*, DataTypeFunctions, Error, Module, Object, RHash, Ruby, TryConvert, TypedData, Value};
22
+ use magnus::{function, method, DataTypeFunctions, Error, Module, RHash, Ruby, TryConvert, TypedData, Value};
23
23
  use p256::Scalar;
24
24
 
25
- use crate::util::threshold_error;
25
+ use crate::util::{bytes_from_value, bytes_to_rstring, threshold_error};
26
26
 
27
27
  #[derive(TypedData, DataTypeFunctions)]
28
28
  #[magnus(class = "Confium::TC::FrostP256::Share", size)]
@@ -37,7 +37,7 @@ impl ShareWrap {
37
37
  }
38
38
 
39
39
  fn y_bytes(&self) -> Result<magnus::RString, Error> {
40
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
40
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
41
41
  Ok(bytes_to_rstring(&ruby, &self.y_bytes))
42
42
  }
43
43
  }
@@ -49,20 +49,16 @@ fn split_secret_into_shares(
49
49
  ) -> Result<magnus::RArray, Error> {
50
50
  let bytes = bytes_from_value(secret_bytes)?;
51
51
  if bytes.len() != 32 {
52
- return Err(Error::new(
53
- exception::arg_error(),
54
- format!("secret must be exactly 32 bytes, got {}", bytes.len()),
52
+ return Err(crate::util::arg_error(format!("secret must be exactly 32 bytes, got {}", bytes.len()),
55
53
  ));
56
54
  }
57
55
  let secret_arr: [u8; 32] = bytes.as_slice().try_into().unwrap();
58
56
  let secret = scalar_from_bytes(&secret_arr).ok_or_else(|| {
59
- Error::new(
60
- exception::arg_error(),
61
- "secret is not a valid P-256 scalar (reduce mod n failed)",
57
+ crate::util::arg_error("secret is not a valid P-256 scalar (reduce mod n failed)",
62
58
  )
63
59
  })?;
64
60
  let shares: Vec<Share> = split_secret(&secret, threshold, party_count);
65
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
61
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
66
62
  let result = ruby.ary_new_capa(shares.len());
67
63
  for s in shares {
68
64
  result.push(ruby.obj_wrap(ShareWrap {
@@ -76,27 +72,25 @@ fn split_secret_into_shares(
76
72
  fn recover(shares_value: Value) -> Result<magnus::RString, Error> {
77
73
  let arr = magnus::RArray::try_convert(shares_value)?;
78
74
  let mut shares: Vec<Share> = Vec::with_capacity(arr.len());
79
- for v in arr.each() {
80
- let h: RHash = RHash::try_convert(v?)?;
75
+ for v in arr.into_iter() {
76
+ let h: RHash = RHash::try_convert(v)?;
81
77
  let x: u32 = h.fetch::<_, u32>("x")?;
82
78
  let y_value: Value = h.fetch::<_, Value>("y")?;
83
79
  let y_bytes = bytes_from_value(y_value)?;
84
80
  if y_bytes.len() != 32 {
85
- return Err(Error::new(
86
- exception::arg_error(),
87
- format!("share y must be 32 bytes, got {}", y_bytes.len()),
81
+ return Err(crate::util::arg_error(format!("share y must be 32 bytes, got {}", y_bytes.len()),
88
82
  ));
89
83
  }
90
84
  let y_arr: [u8; 32] = y_bytes.as_slice().try_into().unwrap();
91
85
  let y = scalar_from_bytes(&y_arr).ok_or_else(|| {
92
- Error::new(exception::arg_error(), "share y is not a valid P-256 scalar")
86
+ crate::util::arg_error("share y is not a valid P-256 scalar")
93
87
  })?;
94
88
  shares.push(Share { x, y });
95
89
  }
96
90
  let refs: Vec<&Share> = shares.iter().collect();
97
91
  let secret = recover_secret(&refs)
98
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
99
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
92
+ .map_err(|e| crate::util::runtime(e.to_string()))?;
93
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
100
94
  Ok(bytes_to_rstring(&ruby, scalar_to_bytes(&secret).as_ref()))
101
95
  }
102
96
 
@@ -114,14 +108,12 @@ fn sign(private_key_bytes: Value, message: Value) -> Result<RHash, Error> {
114
108
  let pk_bytes = bytes_from_value(private_key_bytes)?;
115
109
  let msg = bytes_from_value(message)?;
116
110
  if pk_bytes.len() != 32 {
117
- return Err(Error::new(
118
- exception::arg_error(),
119
- format!("private key must be 32 bytes, got {}", pk_bytes.len()),
111
+ return Err(crate::util::arg_error(format!("private key must be 32 bytes, got {}", pk_bytes.len()),
120
112
  ));
121
113
  }
122
114
  let pk_arr: [u8; 32] = pk_bytes.as_slice().try_into().unwrap();
123
115
  let secret = scalar_from_bytes(&pk_arr).ok_or_else(|| {
124
- Error::new(exception::arg_error(), "private key not a valid P-256 scalar")
116
+ crate::util::arg_error("private key not a valid P-256 scalar")
125
117
  })?;
126
118
  let kp = Keypair {
127
119
  secret_scalar: secret,
@@ -137,7 +129,7 @@ fn sign(private_key_bytes: Value, message: Value) -> Result<RHash, Error> {
137
129
  Some(&msg),
138
130
  Some(&e.to_string()),
139
131
  );
140
- return Err(Error::new(exception::runtime_error(), e.to_string()));
132
+ return Err(crate::util::crypto_error(e.to_string(), "FrostP256.sign", "ecdsa-p256"));
141
133
  }
142
134
  };
143
135
  crate::audit::fire_event(
@@ -147,38 +139,14 @@ fn sign(private_key_bytes: Value, message: Value) -> Result<RHash, Error> {
147
139
  Some(&msg),
148
140
  None,
149
141
  );
150
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
142
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
151
143
  let result = ruby.hash_new();
152
144
  result.aset("der", bytes_to_rstring(&ruby, &signed.der_bytes))?;
153
145
  result.aset("fixed", bytes_to_rstring(&ruby, &signed.fixed_bytes))?;
154
146
  Ok(result)
155
147
  }
156
148
 
157
- fn bytes_from_value(v: Value) -> Result<Vec<u8>, Error> {
158
- use magnus::RString;
159
- if let Ok(s) = RString::try_convert(v) {
160
- return Ok(unsafe { s.as_slice() }.to_vec());
161
- }
162
- let arr: Vec<i64> = Vec::<i64>::try_convert(v)?;
163
- arr.into_iter()
164
- .map(|i| {
165
- if !(0..=255).contains(&i) {
166
- Err(Error::new(
167
- exception::arg_error(),
168
- format!("byte out of range 0..255: {i}"),
169
- ))
170
- } else {
171
- Ok(i as u8)
172
- }
173
- })
174
- .collect()
175
- }
176
149
 
177
- fn bytes_to_rstring(_ruby: &Ruby, bytes: &[u8]) -> magnus::RString {
178
- let s = magnus::RString::buf_new(0);
179
- s.cat(bytes);
180
- s
181
- }
182
150
 
183
151
  // ===== ElGamal-P256 threshold encryption (KEM-style) =====
184
152
 
@@ -186,7 +154,7 @@ fn elgamal_encapsulate(ruby: &Ruby, public_key_bytes: Value) -> Result<RHash, Er
186
154
  let bytes = bytes_from_value(public_key_bytes)?;
187
155
  let pk = ElGamalPublicKey { bytes };
188
156
  let (ciphertext, shared_secret) = encapsulate(&pk)
189
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
157
+ .map_err(|e| crate::util::crypto_error(e.to_string(), "ElGamalP256.encapsulate", "elgamal-p256"))?;
190
158
  let result = ruby.hash_new();
191
159
  let ct_hash = ruby.hash_new();
192
160
  ct_hash.aset("c1", bytes_to_rstring(ruby, &ciphertext.c1))?;
@@ -210,8 +178,8 @@ fn elgamal_partial_decrypt(party_index: u32, share_bytes: Value, ciphertext_valu
210
178
  bytes: share_b,
211
179
  };
212
180
  let partial = partial_decrypt(&share, &ciphertext)
213
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
214
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
181
+ .map_err(|e| crate::util::crypto_error(e.to_string(), "ElGamalP256.partial_decrypt", "elgamal-p256"))?;
182
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
215
183
  let result = ruby.hash_new();
216
184
  result.aset("party_index", partial.party_index)?;
217
185
  result.aset("bytes", bytes_to_rstring(&ruby, &partial.bytes))?;
@@ -221,8 +189,8 @@ fn elgamal_partial_decrypt(party_index: u32, share_bytes: Value, ciphertext_valu
221
189
  fn elgamal_aggregate(partials_value: Value, threshold: u32, ciphertext_value: Value) -> Result<magnus::RString, Error> {
222
190
  let arr = magnus::RArray::try_convert(partials_value)?;
223
191
  let mut partials: Vec<PartialDecryption> = Vec::with_capacity(arr.len());
224
- for v in arr.each() {
225
- let h: RHash = RHash::try_convert(v?)?;
192
+ for v in arr.into_iter() {
193
+ let h: RHash = RHash::try_convert(v)?;
226
194
  let party_index: u32 = h.fetch::<_, u32>("party_index")?;
227
195
  let bytes_value: Value = h.fetch::<_, Value>("bytes")?;
228
196
  partials.push(PartialDecryption {
@@ -238,8 +206,8 @@ fn elgamal_aggregate(partials_value: Value, threshold: u32, ciphertext_value: Va
238
206
  c2: bytes_from_value(c2_value)?,
239
207
  };
240
208
  let shared = aggregate_partials(&partials, threshold, &ciphertext)
241
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
242
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
209
+ .map_err(|e| crate::util::crypto_error(e.to_string(), "ElGamalP256.aggregate_partials", "elgamal-p256"))?;
210
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
243
211
  Ok(bytes_to_rstring(&ruby, &shared))
244
212
  }
245
213
 
@@ -317,8 +285,8 @@ fn cmp20_sign(
317
285
  ) -> Result<magnus::RString, Error> {
318
286
  let arr = magnus::RArray::try_convert(shares_value)?;
319
287
  let mut share_bytes: Vec<Vec<u8>> = Vec::with_capacity(arr.len());
320
- for v in arr.each() {
321
- let s = bytes_from_value(v?)?;
288
+ for v in arr.into_iter() {
289
+ let s = bytes_from_value(v)?;
322
290
  share_bytes.push(s);
323
291
  }
324
292
  let supplied = share_bytes.len();
@@ -389,8 +357,8 @@ fn gg18_sign(
389
357
  ) -> Result<magnus::RString, Error> {
390
358
  let arr = magnus::RArray::try_convert(shares_value)?;
391
359
  let mut share_bytes: Vec<Vec<u8>> = Vec::with_capacity(arr.len());
392
- for v in arr.each() {
393
- let s = bytes_from_value(v?)?;
360
+ for v in arr.into_iter() {
361
+ let s = bytes_from_value(v)?;
394
362
  share_bytes.push(s);
395
363
  }
396
364
  let supplied = share_bytes.len();
@@ -9,9 +9,10 @@ use confium_transparency::{
9
9
  entry::{ArtifactType, MerkleEntry},
10
10
  merkle::{Hash, InclusionProof as RustInclusionProof, MerkleTree as RustMerkleTree, Side},
11
11
  };
12
+ use crate::util::{bytes_from_value, bytes_to_rstring};
12
13
  use magnus::{
13
- exception, function, method, prelude::*, typed_data::Obj, DataTypeFunctions, Error, IntoValue,
14
- Module, Object, RString, Ruby, TryConvert, TypedData, Value,
14
+ function, method, prelude::*, typed_data::Obj, DataTypeFunctions, Error, IntoValue,
15
+ Module, Object, Ruby, TypedData, Value,
15
16
  };
16
17
 
17
18
  #[derive(TypedData, DataTypeFunctions)]
@@ -30,9 +31,7 @@ impl MerkleTree {
30
31
  fn append(&self, artifact_hash: Value) -> Result<u64, Error> {
31
32
  let bytes = bytes_from_value(artifact_hash)?;
32
33
  if bytes.len() != 32 {
33
- return Err(Error::new(
34
- exception::arg_error(),
35
- format!("artifact_hash must be exactly 32 bytes, got {}", bytes.len()),
34
+ return Err(crate::util::arg_error(format!("artifact_hash must be exactly 32 bytes, got {}", bytes.len()),
36
35
  ));
37
36
  }
38
37
  let mut artifact_hash = [0u8; 32];
@@ -54,7 +53,7 @@ impl MerkleTree {
54
53
  /// to iterate: `tree.to_a.map(&:sequence)` etc.
55
54
  fn entries(&self) -> Result<magnus::RArray, Error> {
56
55
  let len = self.inner.borrow().len();
57
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
56
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
58
57
  let arr = ruby.ary_new_capa(len);
59
58
  for i in 0..(len as u64) {
60
59
  let proof = self.inclusion_proof(i)?;
@@ -72,9 +71,9 @@ impl MerkleTree {
72
71
  .inner
73
72
  .borrow()
74
73
  .consistency_proof(old_size)
75
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
74
+ .map_err(|e| crate::util::index_error(e.to_string(), "MerkleTree.consistency_proof", Some(old_size as u64)))?;
76
75
  let ruby = Ruby::get()
77
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
76
+ .map_err(|e| crate::util::runtime(e.to_string()))?;
78
77
  let arr = ruby.ary_new_capa(proof.len());
79
78
  for h in &proof {
80
79
  arr.push(bytes_to_rstring(&ruby, h))?;
@@ -107,16 +106,12 @@ impl MerkleTree {
107
106
  ) -> Result<bool, Error> {
108
107
  let old_bytes = bytes_from_value(old_root)?;
109
108
  if old_bytes.len() != 32 {
110
- return Err(Error::new(
111
- exception::arg_error(),
112
- format!("old_root must be 32 bytes, got {}", old_bytes.len()),
109
+ return Err(crate::util::arg_error(format!("old_root must be 32 bytes, got {}", old_bytes.len()),
113
110
  ));
114
111
  }
115
112
  let new_bytes = bytes_from_value(new_root)?;
116
113
  if new_bytes.len() != 32 {
117
- return Err(Error::new(
118
- exception::arg_error(),
119
- format!("new_root must be 32 bytes, got {}", new_bytes.len()),
114
+ return Err(crate::util::arg_error(format!("new_root must be 32 bytes, got {}", new_bytes.len()),
120
115
  ));
121
116
  }
122
117
  let mut old_root_hash: Hash = [0u8; 32];
@@ -125,12 +120,10 @@ impl MerkleTree {
125
120
  new_root_hash.copy_from_slice(&new_bytes);
126
121
 
127
122
  let mut proof_hashes: Vec<Hash> = Vec::with_capacity(proof.len());
128
- for item in proof.each() {
129
- let bytes = bytes_from_value(item?)?;
123
+ for item in proof.into_iter() {
124
+ let bytes = bytes_from_value(item)?;
130
125
  if bytes.len() != 32 {
131
- return Err(Error::new(
132
- exception::arg_error(),
133
- format!("proof entries must be 32 bytes, got {}", bytes.len()),
126
+ return Err(crate::util::arg_error(format!("proof entries must be 32 bytes, got {}", bytes.len()),
134
127
  ));
135
128
  }
136
129
  let mut h: Hash = [0u8; 32];
@@ -142,7 +135,7 @@ impl MerkleTree {
142
135
  .borrow()
143
136
  .verify_consistency(old_root_hash, new_root_hash, old_size, new_size, &proof_hashes)
144
137
  .map(|_| true)
145
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))
138
+ .map_err(|e| crate::util::verification_error(e.to_string(), "MerkleTree.verify_consistency", None, None))
146
139
  }
147
140
 
148
141
  fn root(&self) -> Value {
@@ -155,14 +148,14 @@ impl MerkleTree {
155
148
  let tree = self.inner.borrow();
156
149
  let proof = tree
157
150
  .inclusion_proof(seq)
158
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
151
+ .map_err(|e| crate::util::index_error(e.to_string(), "MerkleTree.inclusion_proof", Some(seq)))?;
159
152
  let entry = tree
160
153
  .entry(seq)
161
- .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
154
+ .map_err(|e| crate::util::index_error(e.to_string(), "MerkleTree.inclusion_proof", Some(seq)))?;
162
155
  // Re-derive the leaf hash with the same algorithm the tree uses
163
156
  // internally (H(0x01 | entry_hash)).
164
157
  let leaf_hash = hash_leaf(entry.entry_hash());
165
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
158
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
166
159
  Ok(ruby.obj_wrap(InclusionProofWrap {
167
160
  inner: proof,
168
161
  leaf_hash,
@@ -187,7 +180,7 @@ impl InclusionProofWrap {
187
180
  }
188
181
 
189
182
  fn steps(&self) -> Result<Value, Error> {
190
- let ruby = Ruby::get().map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?;
183
+ let ruby = Ruby::get().map_err(|e| crate::util::runtime(e.to_string()))?;
191
184
  let result = ruby.hash_new();
192
185
  for (i, step) in self.inner.steps.iter().enumerate() {
193
186
  let step_hash = ruby.hash_new();
@@ -207,9 +200,7 @@ impl InclusionProofWrap {
207
200
  fn verify(&self, root_bytes: Value) -> Result<bool, Error> {
208
201
  let bytes = bytes_from_value(root_bytes)?;
209
202
  if bytes.len() != 32 {
210
- return Err(Error::new(
211
- exception::arg_error(),
212
- format!("root must be exactly 32 bytes, got {}", bytes.len()),
203
+ return Err(crate::util::arg_error(format!("root must be exactly 32 bytes, got {}", bytes.len()),
213
204
  ));
214
205
  }
215
206
  let mut root: Hash = [0u8; 32];
@@ -234,16 +225,12 @@ impl InclusionProofWrap {
234
225
  fn verify_with_leaf(&self, leaf_bytes: Value, root_bytes: Value) -> Result<bool, Error> {
235
226
  let leaf = bytes_from_value(leaf_bytes)?;
236
227
  if leaf.len() != 32 {
237
- return Err(Error::new(
238
- exception::arg_error(),
239
- format!("leaf_hash must be exactly 32 bytes, got {}", leaf.len()),
228
+ return Err(crate::util::arg_error(format!("leaf_hash must be exactly 32 bytes, got {}", leaf.len()),
240
229
  ));
241
230
  }
242
231
  let root_raw = bytes_from_value(root_bytes)?;
243
232
  if root_raw.len() != 32 {
244
- return Err(Error::new(
245
- exception::arg_error(),
246
- format!("root must be exactly 32 bytes, got {}", root_raw.len()),
233
+ return Err(crate::util::arg_error(format!("root must be exactly 32 bytes, got {}", root_raw.len()),
247
234
  ));
248
235
  }
249
236
  let mut current: Hash = [0u8; 32];
@@ -261,35 +248,7 @@ impl InclusionProofWrap {
261
248
  }
262
249
  }
263
250
 
264
- fn bytes_from_value(v: Value) -> Result<Vec<u8>, Error> {
265
- // Accept either a binary String (preferred for byte data) or an Array
266
- // of small integers (also commonly used in Ruby crypto code).
267
- if let Ok(s) = RString::try_convert(v) {
268
- // SAFETY: we treat the string's raw bytes as opaque cryptographic
269
- // input — we never interpret them as a UTF-8 string. Encoding is
270
- // irrelevant for hash input.
271
- return Ok(unsafe { s.as_slice() }.to_vec());
272
- }
273
- let arr: Vec<i64> = Vec::<i64>::try_convert(v)?;
274
- arr.into_iter()
275
- .map(|i| {
276
- if !(0..=255).contains(&i) {
277
- Err(Error::new(
278
- exception::arg_error(),
279
- format!("byte out of range 0..255: {i}"),
280
- ))
281
- } else {
282
- Ok(i as u8)
283
- }
284
- })
285
- .collect()
286
- }
287
251
 
288
- fn bytes_to_rstring(_ruby: &Ruby, bytes: &[u8]) -> RString {
289
- let s = RString::buf_new(0);
290
- s.cat(bytes);
291
- s
292
- }
293
252
 
294
253
  fn hash_leaf(entry_hash: Hash) -> Hash {
295
254
  use sha2::{Digest, Sha256};