Skip to content

Commit cd03c5e

Browse files
authored
Merge pull request #897 from 0xMiden/mmagician-0.22.6
chore: prepare patch release `v0.22.6`
2 parents 869d52c + 90a3234 commit cd03c5e

File tree

6 files changed

+356
-10
lines changed

6 files changed

+356
-10
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.22.6 (2026-03-13)
2+
3+
- Added `Signature::from_der()` for ECDSA signatures over secp256k1 ([#842](https://github.com/0xMiden/crypto/pull/842)).
4+
- Added `PublicKey::from_der()` for ECDSA public keys over secp256k1 ([#855](https://github.com/0xMiden/crypto/pull/855)).
5+
16
## 0.22.5 (2026-03-11)
27

38
- Expose `StorageError` and `SubtreeUpdate` as prep. to externalize the `LargeSmt` RocksDB backend ([#850](https://github.com/0xMiden/crypto/pull/850)).

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ keywords = ["crypto", "hash", "merkle", "miden"]
1111
license = "MIT OR Apache-2.0"
1212
repository = "https://github.com/0xMiden/crypto"
1313
rust-version = "1.90"
14-
version = "0.22.5"
14+
version = "0.22.6"
1515

1616
[workspace.dependencies]
1717
miden-crypto-derive = { path = "miden-crypto-derive", version = "0.22" }

miden-crypto/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ ed25519-dalek = { features = ["zeroize"], version = "2" }
103103
flume = { version = "0.11.1" }
104104
hashbrown = { features = ["serde"], optional = true, version = "0.16" }
105105
hkdf = { default-features = false, version = "0.12" }
106-
k256 = { features = ["ecdh", "ecdsa"], version = "0.13" }
106+
k256 = { features = ["ecdh", "ecdsa", "pkcs8"], version = "0.13" }
107107
num = { default-features = false, features = ["alloc", "libm"], version = "0.4" }
108108
num-complex = { default-features = false, version = "0.4" }
109109
proptest = { default-features = false, features = ["alloc"], optional = true, version = "1.7" }

miden-crypto/src/dsa/ecdsa_k256_keccak/mod.rs

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use alloc::{string::ToString, vec::Vec};
66
use k256::{
77
ecdh::diffie_hellman,
88
ecdsa::{RecoveryId, SigningKey, VerifyingKey, signature::hazmat::PrehashVerifier},
9+
pkcs8::DecodePublicKey,
910
};
1011
use miden_crypto_derive::{SilentDebug, SilentDisplay};
1112
use rand::{CryptoRng, RngCore};
@@ -176,6 +177,16 @@ impl PublicKey {
176177

177178
Ok(Self { inner: verifying_key })
178179
}
180+
181+
/// Creates a public key from SPKI ASN.1 DER format bytes.
182+
///
183+
/// # Arguments
184+
/// * `bytes` - SPKI ASN.1 DER format bytes
185+
pub fn from_der(bytes: &[u8]) -> Result<Self, DeserializationError> {
186+
let verifying_key = VerifyingKey::from_public_key_der(bytes)
187+
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
188+
Ok(PublicKey { inner: verifying_key })
189+
}
179190
}
180191

181192
impl SequentialCommit for PublicKey {
@@ -255,18 +266,53 @@ impl Signature {
255266
/// * `recovery_id` - recovery ID (0-3)
256267
pub fn from_sec1_bytes_and_recovery_id(
257268
bytes: [u8; SIGNATURE_STANDARD_BYTES],
258-
v: u8,
269+
recovery_id: u8,
259270
) -> Result<Self, DeserializationError> {
260271
let mut r = [0u8; SCALARS_SIZE_BYTES];
261272
let mut s = [0u8; SCALARS_SIZE_BYTES];
262273
r.copy_from_slice(&bytes[0..SCALARS_SIZE_BYTES]);
263274
s.copy_from_slice(&bytes[SCALARS_SIZE_BYTES..2 * SCALARS_SIZE_BYTES]);
264275

265-
if v > 3 {
276+
if recovery_id > 3 {
266277
return Err(DeserializationError::InvalidValue(r#"Invalid recovery ID"#.to_string()));
267278
}
268279

269-
Ok(Signature { r, s, v })
280+
Ok(Signature { r, s, v: recovery_id })
281+
}
282+
283+
/// Creates a signature from ASN.1 DER format bytes with a given recovery id.
284+
///
285+
/// # Arguments
286+
/// * `bytes` - ASN.1 DER format bytes
287+
/// * `recovery_id` - recovery ID (0-3)
288+
pub fn from_der(bytes: &[u8], mut recovery_id: u8) -> Result<Self, DeserializationError> {
289+
if recovery_id > 3 {
290+
return Err(DeserializationError::InvalidValue(r#"Invalid recovery ID"#.to_string()));
291+
}
292+
293+
let sig = k256::ecdsa::Signature::from_der(bytes)
294+
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
295+
296+
// Normalize signature into "low s" form.
297+
// See https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki.
298+
let sig = if let Some(norm) = sig.normalize_s() {
299+
// Replacing s with (n - s) corresponds to negating the ephemeral point R
300+
// (i.e. R -> -R), which flips the y-parity of R. A recoverable signature's
301+
// `v` encodes that y-parity in its LSB, so we must toggle only that bit to
302+
// preserve recoverability.
303+
recovery_id ^= 1;
304+
norm
305+
} else {
306+
sig
307+
};
308+
309+
let (r, s) = sig.split_scalars();
310+
311+
Ok(Signature {
312+
r: r.to_bytes().into(),
313+
s: s.to_bytes().into(),
314+
v: recovery_id,
315+
})
270316
}
271317
}
272318

@@ -331,7 +377,11 @@ impl Deserializable for Signature {
331377
let s: [u8; SCALARS_SIZE_BYTES] = source.read_array()?;
332378
let v: u8 = source.read_u8()?;
333379

334-
Ok(Signature { r, s, v })
380+
if v > 3 {
381+
Err(DeserializationError::InvalidValue(r#"Invalid recovery ID"#.to_string()))
382+
} else {
383+
Ok(Signature { r, s, v })
384+
}
335385
}
336386
}
337387

0 commit comments

Comments
 (0)