use k256::ecdsa::{RecoveryId, Signature, VerifyingKey};
use k256::EncodedPoint;
use tiny_keccak::{Hasher as _, Keccak};
use super::eth_local::eip55_checksum;
use super::SignerError;
pub fn parse_spki_secp256k1(spki_der: &[u8]) -> Result<VerifyingKey, SignerError> {
if spki_der.len() < 65 {
return Err(SignerError::Kms(format!(
"kms: SPKI DER too short ({} bytes; need >= 65)",
spki_der.len()
)));
}
for start in (0..=spki_der.len() - 65).rev() {
if spki_der[start] != 0x04 {
continue;
}
let candidate = &spki_der[start..start + 65];
if let Ok(point) = EncodedPoint::from_bytes(candidate) {
if let Ok(vk) = VerifyingKey::from_encoded_point(&point) {
return Ok(vk);
}
}
}
Err(SignerError::Kms(
"kms: SPKI DER did not contain a parseable secp256k1 point".to_string(),
))
}
pub fn address_from_verifying_key(vk: &VerifyingKey) -> String {
let encoded = vk.to_encoded_point(false);
let pk_bytes = encoded.as_bytes();
debug_assert_eq!(pk_bytes.len(), 65);
debug_assert_eq!(pk_bytes[0], 0x04);
let mut h = Keccak::v256();
h.update(&pk_bytes[1..]);
let mut hash = [0u8; 32];
h.finalize(&mut hash);
let mut addr = [0u8; 20];
addr.copy_from_slice(&hash[12..]);
eip55_checksum(&addr)
}
pub fn der_to_rs(der: &[u8]) -> Result<[u8; 64], SignerError> {
let mut p = 0usize;
if der.is_empty() || der[p] != 0x30 {
return Err(SignerError::Kms(format!(
"der_to_rs: not a SEQUENCE (got 0x{:02x})",
der.first().copied().unwrap_or(0)
)));
}
p += 1;
let (seq_len, ll) = read_der_len(&der[p..])?;
p += ll;
if seq_len + p > der.len() {
return Err(SignerError::Kms(format!(
"der_to_rs: truncated SEQUENCE (need {} more bytes)",
seq_len + p - der.len()
)));
}
let r = read_der_int(der, &mut p)?;
let s = read_der_int(der, &mut p)?;
if r.len() > 32 || s.len() > 32 {
return Err(SignerError::Kms(format!(
"der_to_rs: r/s longer than 32 bytes (r={}, s={})",
r.len(),
s.len()
)));
}
let mut out = [0u8; 64];
out[32 - r.len()..32].copy_from_slice(r);
out[64 - s.len()..64].copy_from_slice(s);
Ok(out)
}
fn read_der_len(buf: &[u8]) -> Result<(usize, usize), SignerError> {
if buf.is_empty() {
return Err(SignerError::Kms("der_to_rs: empty length".to_string()));
}
let first = buf[0];
if first < 0x80 {
return Ok((first as usize, 1));
}
let n = (first & 0x7f) as usize;
if n == 0 || n > 4 || buf.len() < 1 + n {
return Err(SignerError::Kms(format!(
"der_to_rs: bad length prefix 0x{first:02x}"
)));
}
let mut len = 0usize;
for &b in &buf[1..=n] {
len = (len << 8) | b as usize;
}
Ok((len, 1 + n))
}
fn read_der_int<'a>(buf: &'a [u8], p: &mut usize) -> Result<&'a [u8], SignerError> {
if *p >= buf.len() || buf[*p] != 0x02 {
return Err(SignerError::Kms(
"der_to_rs: expected INTEGER tag".to_string(),
));
}
*p += 1;
let (len, ll) = read_der_len(&buf[*p..])?;
*p += ll;
if *p + len > buf.len() {
return Err(SignerError::Kms("der_to_rs: truncated INTEGER".to_string()));
}
let mut bytes = &buf[*p..*p + len];
if bytes.len() > 1 && bytes[0] == 0x00 && bytes[1] & 0x80 != 0 {
bytes = &bytes[1..];
}
*p += len;
Ok(bytes)
}
pub fn der_to_rsv(
der: &[u8],
digest: &[u8; 32],
expected_pubkey: &VerifyingKey,
) -> Result<[u8; 65], SignerError> {
let rs = der_to_rs(der)?;
let mut sig = Signature::from_slice(&rs)
.map_err(|e| SignerError::Kms(format!("der_to_rsv: bad r||s: {e}")))?;
if let Some(normalized) = sig.normalize_s() {
sig = normalized;
}
for v in 0u8..=1 {
let recid = RecoveryId::try_from(v)
.map_err(|e| SignerError::Kms(format!("der_to_rsv: bad recid {v}: {e}")))?;
if let Ok(recovered) = VerifyingKey::recover_from_prehash(digest, &sig, recid) {
if &recovered == expected_pubkey {
let mut out = [0u8; 65];
out[..64].copy_from_slice(&sig.to_bytes());
out[64] = v;
return Ok(out);
}
}
}
Err(SignerError::Kms(
"der_to_rsv: neither recovery id produced the cached pubkey".to_string(),
))
}
#[cfg(test)]
mod tests {
use super::*;
use k256::ecdsa::signature::hazmat::PrehashSigner;
use k256::ecdsa::SigningKey;
#[test]
fn der_to_rs_strips_leading_zero_for_high_bit_scalars() {
let mut der = Vec::new();
der.push(0x30);
der.push(70);
der.extend_from_slice(&[0x02, 33, 0x00, 0x80]);
der.extend_from_slice(&[0u8; 31]);
der.extend_from_slice(&[0x02, 33, 0x00, 0x90]);
der.extend_from_slice(&[0u8; 31]);
let rs = der_to_rs(&der).unwrap();
assert_eq!(rs[0], 0x80);
assert_eq!(rs[32], 0x90);
assert!(rs[1..32].iter().all(|&b| b == 0));
assert!(rs[33..64].iter().all(|&b| b == 0));
}
#[test]
fn der_to_rs_pads_short_integers_to_32_bytes() {
let der = vec![0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02];
let rs = der_to_rs(&der).unwrap();
assert!(rs[..31].iter().all(|&b| b == 0));
assert_eq!(rs[31], 0x01);
assert!(rs[32..63].iter().all(|&b| b == 0));
assert_eq!(rs[63], 0x02);
}
#[test]
fn der_to_rs_rejects_non_sequence() {
let bad = vec![0x31, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02];
let err = der_to_rs(&bad).expect_err("must reject non-SEQUENCE");
match err {
SignerError::Kms(m) => assert!(m.contains("SEQUENCE"), "got: {m}"),
other => panic!("expected Kms, got {other:?}"),
}
}
#[test]
fn der_to_rs_rejects_truncated_sequence() {
let bad = vec![0x30, 70, 0x02, 0x01];
let err = der_to_rs(&bad).expect_err("must reject truncated SEQUENCE");
match err {
SignerError::Kms(m) => {
assert!(m.contains("truncated") || m.contains("INTEGER"), "got: {m}")
}
other => panic!("expected Kms, got {other:?}"),
}
}
#[test]
fn der_to_rs_rejects_wrong_inner_tag() {
let bad = vec![0x30, 0x04, 0x04, 0x02, 0x00, 0x01];
let err = der_to_rs(&bad).expect_err("must reject non-INTEGER inner");
match err {
SignerError::Kms(m) => assert!(m.contains("INTEGER"), "got: {m}"),
other => panic!("expected Kms, got {other:?}"),
}
}
#[test]
fn der_to_rs_rejects_oversized_integer() {
let mut der = Vec::new();
der.push(0x30);
der.push(70);
der.extend_from_slice(&[0x02, 33]);
der.extend_from_slice(&[0xFF; 33]);
der.extend_from_slice(&[0x02, 33]);
der.extend_from_slice(&[0xFF; 33]);
let err = der_to_rs(&der).expect_err("must reject oversized integer");
match err {
SignerError::Kms(m) => assert!(m.contains("32 bytes"), "got: {m}"),
other => panic!("expected Kms, got {other:?}"),
}
}
fn spki_from(signing: &SigningKey) -> Vec<u8> {
let pk = signing.verifying_key().to_encoded_point(false);
let pk_bytes = pk.as_bytes();
let mut spki = Vec::with_capacity(88);
spki.extend_from_slice(&[
0x30, 0x56, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06,
0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a, 0x03, 0x42, 0x00,
]);
spki.extend_from_slice(pk_bytes);
spki
}
#[test]
fn parse_spki_returns_correct_pubkey() {
let signing = SigningKey::from_bytes((&[0x11u8; 32]).into()).unwrap();
let spki = spki_from(&signing);
let vk = parse_spki_secp256k1(&spki).unwrap();
assert_eq!(&vk, signing.verifying_key());
}
#[test]
fn parse_spki_rejects_too_short_input() {
let err = parse_spki_secp256k1(&[0x30, 0x00]).expect_err("must reject");
match err {
SignerError::Kms(m) => assert!(m.contains("too short"), "got: {m}"),
other => panic!("expected Kms, got {other:?}"),
}
}
#[test]
fn parse_spki_rejects_garbage() {
let err = parse_spki_secp256k1(&[0x42; 100]).expect_err("must reject");
match err {
SignerError::Kms(m) => assert!(m.contains("not contain"), "got: {m}"),
other => panic!("expected Kms, got {other:?}"),
}
}
#[test]
fn parse_spki_handles_88_byte_canonical_secp256k1_form() {
let signing = SigningKey::from_bytes((&[0xAAu8; 32]).into()).unwrap();
let spki = spki_from(&signing);
assert_eq!(spki.len(), 88);
let vk = parse_spki_secp256k1(&spki).unwrap();
assert_eq!(&vk, signing.verifying_key());
}
#[test]
fn der_to_rsv_normalizes_high_s() {
let signing = SigningKey::from_bytes((&[0x44u8; 32]).into()).unwrap();
let digest = [0x55u8; 32];
let (sig, _): (Signature, RecoveryId) = signing.sign_prehash(&digest).unwrap();
let s_field = sig.s();
let high_s = -*s_field;
let mut high_s_bytes = [0u8; 32];
high_s_bytes.copy_from_slice(&high_s.to_bytes());
let mut hi_sig_bytes = [0u8; 64];
hi_sig_bytes[..32].copy_from_slice(&sig.r().to_bytes());
hi_sig_bytes[32..].copy_from_slice(&high_s_bytes);
let hi_sig = Signature::from_slice(&hi_sig_bytes).unwrap();
let der = hi_sig.to_der();
let rsv = der_to_rsv(der.as_bytes(), &digest, signing.verifying_key()).unwrap();
let recovered_sig = Signature::from_slice(&rsv[..64]).unwrap();
let s_bytes: [u8; 32] = recovered_sig.s().to_bytes().into();
let half_n: [u8; 32] = [
0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x5D, 0x57, 0x6E, 0x73, 0x57, 0xA4, 0x50, 0x1D, 0xDF, 0xE9, 0x2F, 0x46,
0x68, 0x1B, 0x20, 0xA0,
];
assert!(
s_bytes <= half_n,
"der_to_rsv must normalize to low-S; got s={}",
hex::encode(s_bytes)
);
}
#[test]
fn der_to_rsv_errors_when_pubkey_doesnt_match() {
let signing_a = SigningKey::from_bytes((&[0x77u8; 32]).into()).unwrap();
let signing_b = SigningKey::from_bytes((&[0x88u8; 32]).into()).unwrap();
let digest = [0x66u8; 32];
let (sig, _): (Signature, RecoveryId) = signing_a.sign_prehash(&digest).unwrap();
let der = sig.to_der().as_bytes().to_vec();
let err =
der_to_rsv(&der, &digest, signing_b.verifying_key()).expect_err("must not recover");
match err {
SignerError::Kms(m) => assert!(m.contains("recovery id"), "got: {m}"),
other => panic!("expected Kms, got {other:?}"),
}
}
#[test]
fn der_to_rsv_round_trips_normal_signature() {
let signing = SigningKey::from_bytes((&[0x33u8; 32]).into()).unwrap();
let digest = [0x99u8; 32];
let (sig, _): (Signature, RecoveryId) = signing.sign_prehash(&digest).unwrap();
let der = sig.to_der().as_bytes().to_vec();
let rsv = der_to_rsv(&der, &digest, signing.verifying_key()).unwrap();
let recovered_sig = Signature::from_slice(&rsv[..64]).unwrap();
let recid = RecoveryId::try_from(rsv[64]).unwrap();
let recovered = VerifyingKey::recover_from_prehash(&digest, &recovered_sig, recid).unwrap();
assert_eq!(&recovered, signing.verifying_key());
}
#[test]
fn address_from_verifying_key_matches_eip55() {
let signing = SigningKey::from_bytes((&[0x55u8; 32]).into()).unwrap();
let addr = address_from_verifying_key(signing.verifying_key());
assert!(addr.starts_with("0x"));
assert_eq!(addr.len(), 42);
}
}