Skip to content

Commit 2ef2c6e

Browse files
committed
fix: eigen kms mod
1 parent fe84f4e commit 2ef2c6e

File tree

4 files changed

+136
-154
lines changed

4 files changed

+136
-154
lines changed

packages/adapters/signers/src/eigen.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@ impl eigenda::Sign for Signer {
2121
match self {
2222
Signer::Private(signer) => {
2323
// private_key.sign_digest cannot fail
24-
let Ok(sig) = signer.sign_digest(message).await else {
25-
return Err(
26-
anyhow::anyhow!("Failed to sign digest with private key signer").into(),
27-
);
28-
};
24+
let sig = signer
25+
.sign_digest(message)
26+
.await
27+
.expect("Private key signing should never fail");
2928
Ok(sig)
3029
}
3130
Signer::Kms(signer) => signer.sign_digest(message).await,
Lines changed: 126 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,216 +1,195 @@
1-
use anyhow::Context;
21
use async_trait::async_trait;
3-
use aws_sdk_kms::Client as InnerClient;
4-
use aws_sdk_kms::primitives::Blob;
2+
use aws_sdk_kms::{Client as AwsKmsClient, primitives::Blob};
53
use k256::{
64
ecdsa::{RecoveryId, Signature, VerifyingKey},
75
pkcs8::DecodePublicKey,
86
};
97
use rust_eigenda_signers::{Message, RecoverableSignature};
108
use thiserror::Error;
119

10+
#[derive(Debug, Error)]
11+
pub enum Error {
12+
#[error("KMS GetPublicKey operation failed")]
13+
KmsGetPublicKey(#[from] aws_sdk_kms::operation::get_public_key::GetPublicKeyError),
14+
15+
#[error("KMS Sign operation failed")]
16+
KmsSign(#[from] aws_sdk_kms::operation::sign::SignError),
17+
18+
#[error("Failed to get public key from KMS")]
19+
MissingPublicKey,
20+
21+
#[error("Failed to get signature from KMS")]
22+
MissingSignature,
23+
24+
#[error("Failed to parse DER-encoded public key")]
25+
InvalidPublicKeyDer(#[source] k256::pkcs8::spki::Error),
26+
27+
#[error("Failed to parse DER-encoded signature")]
28+
InvalidSignatureDer(#[source] k256::ecdsa::Error),
29+
30+
#[error("Failed to convert public key format")]
31+
PublicKeyConversion(#[source] rust_eigenda_signers::PublicKeyError),
32+
33+
#[error("Invalid recoverable signature")]
34+
InvalidRecoverableSignature(#[source] rust_eigenda_signers::InvalidRecoveryId),
35+
36+
#[error("Failed to parse SEC1-encoded public key")]
37+
InvalidSec1PublicKey(#[source] k256::elliptic_curve::Error),
38+
39+
#[error("Could not determine recovery ID for signature")]
40+
RecoveryIdNotFound,
41+
42+
#[error("Invalid recovery ID: {0}")]
43+
InvalidRecoveryId(u8),
44+
}
45+
46+
type Result<T> = std::result::Result<T, Error>;
47+
1248
#[derive(Debug, Clone)]
1349
pub struct Signer {
1450
key_id: String,
15-
client: InnerClient,
51+
client: AwsKmsClient,
1652
public_key: rust_eigenda_signers::PublicKey,
1753
k256_verifying_key: VerifyingKey,
1854
}
1955

2056
impl Signer {
21-
pub async fn new(client: InnerClient, key_id: String) -> anyhow::Result<Self> {
22-
let public_key_der = client
57+
pub async fn new(client: AwsKmsClient, key_id: String) -> Result<Self> {
58+
let response = client
2359
.get_public_key()
2460
.key_id(&key_id)
2561
.send()
2662
.await
27-
.context("Failed to get public key")?
63+
.map_err(|e| e.into_service_error())?;
64+
65+
let public_key_der = response
2866
.public_key
29-
.context("Public key missing from response")?
67+
.ok_or(Error::MissingPublicKey)?
3068
.into_inner();
3169

3270
// Parse the DER-encoded public key using k256
33-
let k256_pub_key = VerifyingKey::from_public_key_der(&public_key_der)
34-
.context("Failed to parse public key DER from KMS")?;
35-
36-
// Convert k256 public key to secp256k1 public key
37-
let mut pub_key: [u8; 65] = [0; 65];
38-
pub_key.copy_from_slice(k256_pub_key.to_encoded_point(false).as_bytes());
39-
let secp_pub_key = rust_eigenda_signers::PublicKey::new(pub_key)
40-
.context("Failed to convert k256 pubkey to secp256k1 pubkey")?;
71+
let k256_verifying_key = VerifyingKey::from_public_key_der(&public_key_der)
72+
.map_err(Error::InvalidPublicKeyDer)?;
73+
74+
// Convert k256 public key to rust_eigenda_signers public key
75+
// Use uncompressed format directly
76+
let uncompressed_bytes = k256_verifying_key.to_encoded_point(false);
77+
let public_key = rust_eigenda_signers::PublicKey::new(
78+
uncompressed_bytes
79+
.as_bytes()
80+
.try_into()
81+
.expect("uncompressed point is always 65 bytes"),
82+
)
83+
.map_err(Error::PublicKeyConversion)?;
4184

4285
Ok(Self {
4386
key_id,
4487
client,
45-
public_key: secp_pub_key,
46-
k256_verifying_key: k256_pub_key,
88+
public_key,
89+
k256_verifying_key,
4790
})
4891
}
4992

50-
pub fn inner(&self) -> &InnerClient {
93+
pub fn inner(&self) -> &AwsKmsClient {
5194
&self.client
5295
}
5396

54-
async fn get_raw_signature(&self, key_id: &str, data: &[u8]) -> anyhow::Result<Signature> {
97+
pub async fn sign_prehash(&self, key_id: &str, prehash: &[u8]) -> Result<Vec<u8>> {
98+
let signature = self.sign_with_kms(key_id, prehash).await?;
99+
let recovery_id = self.compute_recovery_id(prehash, &signature)?;
100+
101+
// Combine signature and recovery ID
102+
let mut signature_bytes = Vec::with_capacity(65);
103+
signature_bytes.extend_from_slice(&signature.to_bytes());
104+
signature_bytes.push(recovery_id);
105+
106+
Ok(signature_bytes)
107+
}
108+
109+
pub async fn get_public_key(&self, key_id: &str) -> Result<Vec<u8>> {
110+
let response = self
111+
.client
112+
.get_public_key()
113+
.key_id(key_id)
114+
.send()
115+
.await
116+
.map_err(|e| e.into_service_error())?;
117+
118+
let der_bytes = response
119+
.public_key
120+
.ok_or(Error::MissingPublicKey)?
121+
.into_inner();
122+
123+
// Convert to uncompressed SEC1 format
124+
let verifying_key =
125+
VerifyingKey::from_public_key_der(&der_bytes).map_err(Error::InvalidPublicKeyDer)?;
126+
127+
Ok(verifying_key.to_encoded_point(false).as_bytes().to_vec())
128+
}
129+
130+
async fn sign_with_kms(&self, key_id: &str, digest: &[u8]) -> Result<Signature> {
55131
let response = self
56132
.client
57133
.sign()
58134
.key_id(key_id)
59-
.message(data.into())
135+
.message(Blob::new(digest))
60136
.message_type(aws_sdk_kms::types::MessageType::Digest)
61137
.signing_algorithm(aws_sdk_kms::types::SigningAlgorithmSpec::EcdsaSha256)
62138
.send()
63-
.await?;
139+
.await
140+
.map_err(|e| e.into_service_error())?;
64141

65-
let der_signature = response
66-
.signature
67-
.ok_or_else(|| anyhow::anyhow!("kms signature missing"))?;
142+
let signature_der = response.signature.ok_or(Error::MissingSignature)?;
68143

69-
let signature = Signature::from_der(der_signature.as_ref())?;
144+
let signature =
145+
Signature::from_der(signature_der.as_ref()).map_err(Error::InvalidSignatureDer)?;
146+
147+
// Normalize S value to ensure it's in the lower half of the order
70148
Ok(signature.normalize_s().unwrap_or(signature))
71149
}
72150

73-
fn determine_recovery_id(
74-
&self,
75-
prehash: &[u8],
76-
signature: &Signature,
77-
public_key: &[u8],
78-
) -> anyhow::Result<u8> {
79-
let expected_key = VerifyingKey::from_sec1_bytes(public_key)?;
80-
81-
// try both possible recovery IDs
151+
#[allow(clippy::result_large_err)]
152+
fn compute_recovery_id(&self, prehash: &[u8], signature: &Signature) -> Result<u8> {
153+
// For secp256k1, only recovery IDs 0 and 1 are valid for uncompressed keys
82154
for recovery_id in 0..2 {
83-
if let Ok(recovered_key) = VerifyingKey::recover_from_prehash(
84-
prehash,
85-
signature,
86-
RecoveryId::from_byte(recovery_id).expect("valid recovery id"),
87-
) {
88-
if recovered_key == expected_key {
155+
let rec_id =
156+
RecoveryId::from_byte(recovery_id).ok_or(Error::InvalidRecoveryId(recovery_id))?;
157+
158+
if let Ok(recovered_key) =
159+
VerifyingKey::recover_from_prehash(prehash, signature, rec_id)
160+
{
161+
if recovered_key == self.k256_verifying_key {
89162
return Ok(recovery_id);
90163
}
91164
}
92165
}
93166

94-
Err(anyhow::anyhow!("Could not determine recovery ID"))
95-
}
96-
97-
pub async fn sign_prehash(&self, key_id: &str, prehash: &[u8]) -> anyhow::Result<Vec<u8>> {
98-
let public_key = self.get_public_key(key_id).await?;
99-
let signature = self.get_raw_signature(key_id, prehash).await?;
100-
101-
let recovery_id = self.determine_recovery_id(prehash, &signature, &public_key)?;
102-
103-
// combine into final signature
104-
let mut signature_bytes = signature.to_bytes().to_vec();
105-
signature_bytes.push(recovery_id);
106-
107-
Ok(signature_bytes)
108-
}
109-
110-
pub async fn get_public_key(&self, key_id: &str) -> anyhow::Result<Vec<u8>> {
111-
let key_info = self.client.get_public_key().key_id(key_id).send().await?;
112-
113-
let der_bytes: Vec<u8> = key_info
114-
.public_key
115-
.ok_or_else(|| anyhow::anyhow!("kms public key missing"))?
116-
.into();
117-
118-
// convert to uncompressed form
119-
let verifying_key = VerifyingKey::from_public_key_der(&der_bytes)?;
120-
let encoded_point = verifying_key.to_encoded_point(false);
121-
122-
Ok(encoded_point.as_bytes().to_vec())
123-
}
124-
125-
fn k256_recovery_id(
126-
&self,
127-
signature: &k256::ecdsa::Signature,
128-
message_hash: &[u8; 32],
129-
) -> anyhow::Result<u8> {
130-
signature
131-
.get_recovery_id(message_hash, &self.k256_verifying_key)
132-
.context("Failed to determine recovery ID")
167+
Err(Error::RecoveryIdNotFound)
133168
}
134169
}
135170

136-
#[derive(Error, Debug)]
137-
#[error(transparent)]
138-
pub struct Error(#[from] anyhow::Error);
139-
140171
#[async_trait]
141-
impl eigenda::Sign for crate::eigen::kms::Signer {
172+
impl eigenda::Sign for Signer {
142173
type Error = Error;
143174

144-
async fn sign_digest(&self, message: &Message) -> Result<RecoverableSignature, Self::Error> {
175+
async fn sign_digest(&self, message: &Message) -> Result<RecoverableSignature> {
145176
let digest_bytes: &[u8; 32] = message;
146177

147-
let sign_response = self
148-
.client
149-
.sign()
150-
.key_id(&self.key_id)
151-
.message(Blob::new(digest_bytes))
152-
.message_type(aws_sdk_kms::types::MessageType::Digest)
153-
.signing_algorithm(aws_sdk_kms::types::SigningAlgorithmSpec::EcdsaSha256)
154-
.send()
155-
.await
156-
.context("while requesting KMS to sign the digest")?;
157-
158-
let signature_der = sign_response
159-
.signature
160-
.ok_or_else(|| anyhow::anyhow!("Signature missing from KMS response"))?
161-
.into_inner();
178+
// Sign the digest with KMS
179+
let signature = self.sign_with_kms(&self.key_id, digest_bytes).await?;
162180

163-
let k256_sig = k256::ecdsa::Signature::from_der(&signature_der)
164-
.context("Failed to parse DER signature")?;
181+
// Compute recovery ID
182+
let recovery_id = self.compute_recovery_id(digest_bytes, &signature)?;
165183

166-
let k256_sig_normalized = k256_sig.normalize_s().unwrap_or(k256_sig);
184+
// Build recoverable signature
185+
let mut sig_bytes = [0u8; 65];
186+
sig_bytes[..64].copy_from_slice(&signature.to_bytes());
187+
sig_bytes[64] = recovery_id;
167188

168-
let k256_recid = self
169-
.k256_recovery_id(&k256_sig_normalized, digest_bytes)
170-
.context("Failed to determine recovery ID")?;
171-
172-
let mut sig: [u8; 65] = [0; 65];
173-
sig[64] = k256_recid;
174-
sig[..64].copy_from_slice(k256_sig_normalized.to_bytes().as_ref());
175-
176-
let standard_recoverable_sig = rust_eigenda_signers::RecoverableSignature::from_bytes(&sig)
177-
.context("Failed to create recoverable signature")?;
178-
179-
Ok(standard_recoverable_sig)
189+
RecoverableSignature::from_bytes(&sig_bytes).map_err(Error::InvalidRecoverableSignature)
180190
}
181191

182192
fn public_key(&self) -> rust_eigenda_signers::PublicKey {
183193
self.public_key
184194
}
185195
}
186-
187-
trait RecIdExt {
188-
fn get_recovery_id(
189-
&self,
190-
message_hash: &[u8; 32],
191-
expected_pubkey: &VerifyingKey,
192-
) -> anyhow::Result<u8>;
193-
}
194-
195-
impl RecIdExt for k256::ecdsa::Signature {
196-
fn get_recovery_id(
197-
&self,
198-
message_hash: &[u8; 32],
199-
expected_pubkey: &VerifyingKey,
200-
) -> anyhow::Result<u8> {
201-
(0..2)
202-
.find_map(|id| {
203-
let recovery_id = k256::ecdsa::RecoveryId::from_byte(id)
204-
.with_context(|| format!("Bad RecoveryId byte {}", id))
205-
.ok()?;
206-
207-
let recovered_key =
208-
VerifyingKey::recover_from_prehash(message_hash, self, recovery_id).ok()?;
209-
210-
(&recovered_key == expected_pubkey).then_some(id)
211-
})
212-
.ok_or_else(|| {
213-
anyhow::anyhow!("Could not recover correct public key from k256 signature")
214-
})
215-
}
216-
}

packages/adapters/signers/src/eth.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ use eth::Address;
88

99
pub mod kms {
1010
pub use alloy::signers::aws::AwsSigner as Signer;
11+
#[cfg(feature = "test-helpers")]
1112
use alloy::{
1213
consensus::SignableTransaction,
1314
network::TxSigner,
1415
primitives::{B256, ChainId},
1516
signers::Signature,
1617
};
18+
#[cfg(feature = "test-helpers")]
1719
use eth::Address;
1820

1921
#[cfg(feature = "test-helpers")]
@@ -24,6 +26,7 @@ pub mod kms {
2426
pub signer: Signer,
2527
}
2628

29+
#[cfg(feature = "test-helpers")]
2730
#[async_trait::async_trait]
2831
impl TxSigner<Signature> for TestEthKmsSigner {
2932
fn address(&self) -> Address {
@@ -38,6 +41,7 @@ pub mod kms {
3841
}
3942
}
4043

44+
#[cfg(feature = "test-helpers")]
4145
#[async_trait::async_trait]
4246
impl alloy::signers::Signer<Signature> for TestEthKmsSigner {
4347
async fn sign_hash(&self, hash: &B256) -> alloy::signers::Result<Signature> {

0 commit comments

Comments
 (0)