cfg_if::cfg_if! {
if #[cfg(feature = "signer")] {
pub mod signer;
pub use signer::*;
}
}
pub use crate::solidity_types::Envelope;
use crate::{
ServiceId, ServiceManagerEnvelope, ServiceManagerSignatureData, SignatureData, SignatureKind,
SubmitAction, TriggerAction, TriggerData, WasmResponse, WorkflowId,
};
use alloy_primitives::{eip191_hash_message, keccak256, FixedBytes, SignatureError};
use alloy_sol_types::SolValue;
use async_trait::async_trait;
use ripemd::Ripemd160;
use serde::{Deserialize, Serialize};
use sha2::Digest;
use thiserror::Error;
use utoipa::ToSchema;
#[derive(Serialize, Deserialize, Clone, Debug, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct AggregatorInput {
pub trigger_action: TriggerAction,
pub operator_response: WasmResponse,
}
impl AggregatorInput {
pub fn event_id(&self) -> Result<EventId, bincode::error::EncodeError> {
let salt = match &self.operator_response.event_id_salt {
Some(bytes) => EventIdSalt::WasmResponse(bytes),
None => EventIdSalt::Trigger(&self.trigger_action.data),
};
EventId::new(
&self.trigger_action.config.service_id,
&self.trigger_action.config.workflow_id,
salt,
)
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait WavsSignable {
fn encode_data(&self) -> anyhow::Result<Vec<u8>>;
fn prefix_eip191_hash(&self) -> anyhow::Result<FixedBytes<32>> {
let envelope_bytes = self.encode_data()?;
Ok(eip191_hash_message(keccak256(&envelope_bytes)))
}
fn unprefixed_hash(&self) -> anyhow::Result<FixedBytes<32>> {
let envelope_bytes = self.encode_data()?;
Ok(keccak256(&envelope_bytes))
}
}
impl WavsSignable for Envelope {
fn encode_data(&self) -> anyhow::Result<Vec<u8>> {
Ok(self.abi_encode())
}
}
impl WavsSignable for &[SubmitAction] {
fn encode_data(&self) -> anyhow::Result<Vec<u8>> {
Ok(bincode::serde::encode_to_vec(
self,
bincode::config::standard(),
)?)
}
}
impl From<Envelope> for ServiceManagerEnvelope {
fn from(envelope: Envelope) -> Self {
ServiceManagerEnvelope {
eventId: envelope.eventId,
ordering: envelope.ordering,
payload: envelope.payload,
}
}
}
impl From<SignatureData> for ServiceManagerSignatureData {
fn from(signature_data: SignatureData) -> Self {
ServiceManagerSignatureData {
signers: signature_data.signers,
signatures: signature_data.signatures,
referenceBlock: signature_data.referenceBlock,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, ToSchema, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub struct WavsSignature {
pub data: Vec<u8>,
pub kind: SignatureKind,
}
#[derive(
Serialize,
Deserialize,
Clone,
Eq,
PartialEq,
Debug,
Hash,
bincode::Decode,
bincode::Encode,
Ord,
PartialOrd,
Default,
)]
#[serde(transparent)]
pub struct EventId(#[serde(with = "const_hex")] [u8; 20]);
impl EventId {
pub fn new(
service_id: &ServiceId,
workflow_id: &WorkflowId,
salt: EventIdSalt<'_>,
) -> Result<Self, bincode::error::EncodeError> {
let bincode_config = bincode::config::standard();
let mut hasher = Ripemd160::new();
hasher.update(service_id.inner());
hasher.update(workflow_id.as_bytes());
match salt {
EventIdSalt::WasmResponse(bytes) => hasher.update(bytes),
EventIdSalt::Trigger(trigger_data) => match trigger_data {
TriggerData::AtProtoEvent {
repo,
collection,
rkey,
action,
cid,
record,
rev,
op_index,
..
} => {
let encoded = bincode::serde::encode_to_vec(
(repo, collection, rkey, action, cid, record, rev, op_index),
bincode_config,
)?;
hasher.update(encoded);
}
_ => hasher.update(bincode::serde::encode_to_vec(trigger_data, bincode_config)?),
},
}
let result = hasher.finalize();
Ok(Self(result.into()))
}
pub fn as_bytes(&self) -> &[u8; 20] {
&self.0
}
}
pub enum EventIdSalt<'a> {
WasmResponse(&'a [u8]),
Trigger(&'a TriggerData),
}
impl From<[u8; 20]> for EventId {
fn from(value: [u8; 20]) -> Self {
Self(value)
}
}
impl From<FixedBytes<20>> for EventId {
fn from(value: FixedBytes<20>) -> Self {
Self(value.0)
}
}
impl From<EventId> for FixedBytes<20> {
fn from(value: EventId) -> Self {
FixedBytes(value.0)
}
}
impl std::fmt::Display for EventId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", const_hex::encode(self.0))
}
}
impl AsRef<[u8]> for EventId {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(transparent)]
pub struct EventOrder([u8; 12]);
impl EventOrder {
pub fn new_u64(value: u64) -> Self {
let mut bytes = [0; 12];
bytes[0..8].copy_from_slice(&value.to_be_bytes());
Self(bytes)
}
}
impl From<FixedBytes<12>> for EventOrder {
fn from(value: FixedBytes<12>) -> Self {
Self(value.0)
}
}
impl From<EventOrder> for FixedBytes<12> {
fn from(value: EventOrder) -> Self {
FixedBytes(value.0)
}
}
impl std::fmt::Display for EventOrder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", const_hex::encode(self.0))
}
}
impl AsRef<[u8]> for EventOrder {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Debug, Error)]
pub enum SigningError {
#[error("Unable to recover signer address: {0:?}")]
RecoverSignerAddress(SignatureError),
#[error("Unable to get data hash: {0:?}")]
DataHash(anyhow::Error),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::AtProtoAction;
#[test]
fn atproto_event_id_ignores_seq_and_timestamp() {
let service_id = ServiceId::hash(b"service");
let workflow_id = WorkflowId::new("flow1").unwrap();
let base = TriggerData::AtProtoEvent {
sequence: 1,
timestamp: 1000,
repo: "did:example:123".to_string(),
collection: "app.bsky.feed.post".to_string(),
rkey: "abc".to_string(),
action: AtProtoAction::Create,
cid: Some("cid123".to_string()),
record: None,
rev: Some("rev1".to_string()),
op_index: Some(0),
};
let different = match &base {
TriggerData::AtProtoEvent {
repo,
collection,
rkey,
action,
cid,
record,
rev,
op_index,
..
} => TriggerData::AtProtoEvent {
sequence: 9999,
timestamp: 999999,
repo: repo.clone(),
collection: collection.clone(),
rkey: rkey.clone(),
action: action.clone(),
cid: cid.clone(),
record: record.clone(),
rev: rev.clone(),
op_index: *op_index,
},
_ => unreachable!(),
};
let id1 = EventId::new(&service_id, &workflow_id, EventIdSalt::Trigger(&base)).unwrap();
let id2 =
EventId::new(&service_id, &workflow_id, EventIdSalt::Trigger(&different)).unwrap();
assert_eq!(id1, id2);
}
}