Add canonical signed operation envelopes
This commit is contained in:
parent
bfc5df698c
commit
373ff53d5c
12 changed files with 255 additions and 11 deletions
|
|
@ -12,6 +12,7 @@ hex.workspace = true
|
|||
rand_core.workspace = true
|
||||
serde.workspace = true
|
||||
thiserror.workspace = true
|
||||
geth-codec = { path = "../geth-codec" }
|
||||
geth-types = { path = "../geth-types" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
|
||||
use rand_core::OsRng;
|
||||
use serde::Serialize;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
|
@ -12,6 +13,8 @@ pub enum CryptoError {
|
|||
InvalidKey,
|
||||
#[error("signature verification failed")]
|
||||
Verify,
|
||||
#[error("canonical encoding failed: {0}")]
|
||||
Codec(#[from] geth_codec::CodecError),
|
||||
}
|
||||
|
||||
pub struct AgentKey {
|
||||
|
|
@ -73,6 +76,14 @@ impl AgentKey {
|
|||
pub fn sign(&self, bytes: &[u8]) -> Vec<u8> {
|
||||
self.signing_key.sign(bytes).to_bytes().to_vec()
|
||||
}
|
||||
|
||||
pub fn sign_canonical<T: Serialize + ?Sized>(
|
||||
&self,
|
||||
namespace: &str,
|
||||
payload: &T,
|
||||
) -> Result<Vec<u8>, CryptoError> {
|
||||
Ok(self.sign(&geth_codec::signing_payload(namespace, payload)?))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<(), CryptoError> {
|
||||
|
|
@ -85,6 +96,19 @@ pub fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<(),
|
|||
.map_err(|_| CryptoError::Verify)
|
||||
}
|
||||
|
||||
pub fn verify_canonical<T: Serialize + ?Sized>(
|
||||
public_key: &[u8],
|
||||
namespace: &str,
|
||||
payload: &T,
|
||||
signature: &[u8],
|
||||
) -> Result<(), CryptoError> {
|
||||
verify(
|
||||
public_key,
|
||||
&geth_codec::signing_payload(namespace, payload)?,
|
||||
signature,
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn blake3_hex(bytes: &[u8]) -> String {
|
||||
blake3::hash(bytes).to_hex().to_string()
|
||||
|
|
@ -115,4 +139,39 @@ mod tests {
|
|||
"3a4aa805ade0d4694a1bb69ad5b9a2f1dffcd4de2136df9a465023722d26e325"
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
struct CanonicalSample {
|
||||
version: u8,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonical_signature_verifies_only_for_matching_namespace() {
|
||||
let key = AgentKey::generate();
|
||||
let sample = CanonicalSample {
|
||||
version: 1,
|
||||
name: "geth".to_owned(),
|
||||
};
|
||||
let signature = key
|
||||
.sign_canonical("geth.keychain.v1@geth.local", &sample)
|
||||
.expect("sign");
|
||||
|
||||
verify_canonical(
|
||||
&key.verifying_key().to_bytes(),
|
||||
"geth.keychain.v1@geth.local",
|
||||
&sample,
|
||||
&signature,
|
||||
)
|
||||
.expect("verify matching namespace");
|
||||
assert!(matches!(
|
||||
verify_canonical(
|
||||
&key.verifying_key().to_bytes(),
|
||||
"geth.auth-op.v1@geth.local",
|
||||
&sample,
|
||||
&signature,
|
||||
),
|
||||
Err(CryptoError::Verify)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue