Add canonical signed operation envelopes

This commit is contained in:
Eric Wendland 2026-05-16 14:37:16 +02:00
commit 373ff53d5c
12 changed files with 255 additions and 11 deletions

View file

@ -8,6 +8,7 @@ license.workspace = true
[dependencies]
serde.workspace = true
thiserror.workspace = true
geth-codec = { path = "../geth-codec" }
geth-types = { path = "../geth-types" }
[dev-dependencies]

View file

@ -3,12 +3,21 @@ use serde::{Deserialize, Serialize};
pub const KEYCHAIN_SIGNATURE_NAMESPACE: &str = "geth.keychain.v1@geth.local";
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SignedKeychainOp {
pub op: KeychainOp,
pub signer: KeyId,
pub signature_namespace: String,
pub signature: Vec<u8>,
pub type SignedKeychainOp = geth_codec::SignedEnvelope<KeychainOp, KeyId>;
pub fn keychain_signing_payload(op: &KeychainOp) -> Result<Vec<u8>, geth_codec::CodecError> {
geth_codec::signing_payload(KEYCHAIN_SIGNATURE_NAMESPACE, op)
}
pub fn keychain_signing_payload_hash(
op: &KeychainOp,
) -> Result<geth_types::BlobHash, geth_codec::CodecError> {
geth_codec::signing_payload_hash(KEYCHAIN_SIGNATURE_NAMESPACE, op)
}
#[must_use]
pub fn signed_keychain_op(op: KeychainOp, signer: KeyId, signature: Vec<u8>) -> SignedKeychainOp {
geth_codec::SignedEnvelope::new(KEYCHAIN_SIGNATURE_NAMESPACE, op, signer, signature)
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@ -134,4 +143,27 @@ mod tests {
let decoded: KeychainOp = serde_json::from_str(&json).expect("decode");
assert_eq!(decoded, op);
}
#[test]
fn keychain_signing_payload_is_canonical_and_namespaced() {
let op = KeychainOp {
id: "op:1".into(),
created_at: UnixMillis(1),
kind: KeychainOpKind::AdminKeyAdd {
key: "key:admin".into(),
},
};
assert_eq!(
keychain_signing_payload(&op).expect("payload"),
keychain_signing_payload(&op).expect("payload again")
);
assert_ne!(
keychain_signing_payload_hash(&op).expect("hash"),
geth_codec::hash_canonical(&op).expect("raw op hash")
);
let signed = signed_keychain_op(op.clone(), "key:admin".into(), vec![1, 2, 3]);
assert_eq!(signed.namespace(), KEYCHAIN_SIGNATURE_NAMESPACE);
assert_eq!(signed.payload(), &op);
}
}