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

@ -5,6 +5,23 @@ pub const AUTH_SIGNATURE_NAMESPACE: &str = "geth.auth-op.v1@geth.local";
pub const RESOURCE_GRANT_SIGNATURE_NAMESPACE: &str = "geth.resource-grant.v1@geth.local";
pub const REVOCATION_SIGNATURE_NAMESPACE: &str = "geth.revocation.v1@geth.local";
pub type SignedAuthOp = geth_codec::SignedEnvelope<AuthOp, PrincipalId>;
pub fn auth_signing_payload(op: &AuthOp) -> Result<Vec<u8>, geth_codec::CodecError> {
geth_codec::signing_payload(AUTH_SIGNATURE_NAMESPACE, op)
}
pub fn auth_signing_payload_hash(
op: &AuthOp,
) -> Result<geth_types::BlobHash, geth_codec::CodecError> {
geth_codec::signing_payload_hash(AUTH_SIGNATURE_NAMESPACE, op)
}
#[must_use]
pub fn signed_auth_op(op: AuthOp, signer: PrincipalId, signature: Vec<u8>) -> SignedAuthOp {
geth_codec::SignedEnvelope::new(AUTH_SIGNATURE_NAMESPACE, op, signer, signature)
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthOp {
pub id: AuthOpId,
@ -105,4 +122,30 @@ mod tests {
let decoded: AuthOp = serde_json::from_str(&json).expect("decode");
assert_eq!(decoded, op);
}
#[test]
fn auth_signing_payload_is_canonical_and_namespaced() {
let op = AuthOp {
id: "op:auth:1".into(),
resource: "resource:notes".into(),
created_at: UnixMillis(10),
kind: AuthOpKind::GrantCreate {
grant_id: "grant:1".to_owned(),
principal: "node:laptop".into(),
capabilities: vec!["kv.read".into(), "kv.write_prefix:apps/foo/".into()],
},
};
assert_eq!(
auth_signing_payload(&op).expect("payload"),
auth_signing_payload(&op).expect("payload again")
);
assert_ne!(
auth_signing_payload_hash(&op).expect("hash"),
geth_codec::hash_canonical(&op).expect("raw op hash")
);
let signed = signed_auth_op(op.clone(), "node:laptop".into(), vec![1, 2, 3]);
assert_eq!(signed.namespace(), AUTH_SIGNATURE_NAMESPACE);
assert_eq!(signed.payload(), &op);
}
}