Skip to content

Commit

Permalink
feat(aggregator): Add support for multiple signers in input (#211)
Browse files Browse the repository at this point in the history
Fixes #205

Signed-off-by: Alexis Asseman <alexis@semiotic.ai>
  • Loading branch information
aasseman committed Feb 8, 2024
1 parent 2ed564b commit b16f23d
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 41 deletions.
1 change: 1 addition & 0 deletions tap_aggregator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ ruint = "1.10.1"

[dev-dependencies]
jsonrpsee = { version = "0.18.0", features = ["http-client", "jsonrpsee-core"] }
rand = "0.8.5"
rstest = "0.17.0"
48 changes: 33 additions & 15 deletions tap_aggregator/src/aggregator.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2023-, Semiotic AI, Inc.
// SPDX-License-Identifier: Apache-2.0

use std::collections::hash_set;
use std::collections::{hash_set, HashSet};

use alloy_primitives::Address;
use alloy_sol_types::Eip712Domain;
use anyhow::{Ok, Result};
use alloy_sol_types::{Eip712Domain, SolStruct};
use anyhow::{bail, Ok, Result};
use ethers_core::types::Signature;
use ethers_signers::{LocalWallet, Signer};
use ethers_signers::LocalWallet;

use tap_core::{
eip_712_signed_message::EIP712SignedMessage,
Expand All @@ -19,22 +19,26 @@ pub async fn check_and_aggregate_receipts(
receipts: &[EIP712SignedMessage<Receipt>],
previous_rav: Option<EIP712SignedMessage<ReceiptAggregateVoucher>>,
wallet: &LocalWallet,
accepted_addresses: &HashSet<Address>,
) -> Result<EIP712SignedMessage<ReceiptAggregateVoucher>> {
// Get the address of the wallet
let address: [u8; 20] = wallet.address().into();
let address: Address = address.into();

// Check that the receipts are unique
check_signatures_unique(receipts)?;

// Check that the receipts are signed by ourselves
receipts
.iter()
.try_for_each(|receipt| receipt.verify(domain_separator, address))?;
// Check that the receipts are signed by an accepted signer address
receipts.iter().try_for_each(|receipt| {
check_signature_is_from_one_of_addresses(
receipt.clone(),
domain_separator,
accepted_addresses,
)
})?;

// Check that the previous rav is signed by ourselves
// Check that the previous rav is signed by an accepted signer address
if let Some(previous_rav) = &previous_rav {
previous_rav.verify(domain_separator, address)?;
check_signature_is_from_one_of_addresses(
previous_rav.clone(),
domain_separator,
accepted_addresses,
)?;
}

// Check that the receipts timestamp is greater than the previous rav
Expand Down Expand Up @@ -68,6 +72,20 @@ pub async fn check_and_aggregate_receipts(
Ok(EIP712SignedMessage::new(domain_separator, rav, wallet).await?)
}

fn check_signature_is_from_one_of_addresses<M: SolStruct>(
message: EIP712SignedMessage<M>,
domain_separator: &Eip712Domain,
accepted_addresses: &HashSet<Address>,
) -> Result<()> {
let recovered_address = message.recover_signer(domain_separator)?;
if !accepted_addresses.contains(&recovered_address) {
bail!(tap_core::Error::InvalidRecoveredSigner {
address: recovered_address,
});
}
Ok(())
}

fn check_allocation_id(
receipts: &[EIP712SignedMessage<Receipt>],
allocation_id: Address,
Expand Down
19 changes: 18 additions & 1 deletion tap_aggregator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#![doc = include_str!("../README.md")]

use std::borrow::Cow;
use std::collections::HashSet;
use std::str::FromStr;

use alloy_primitives::{Address, FixedBytes, U256};
Expand All @@ -25,10 +26,18 @@ struct Args {
#[arg(long, default_value_t = 8080, env = "TAP_PORT")]
port: u16,

/// Sender private key for signing Receipt Aggregate Vouchers, as a hex string.
/// Signer private key for signing Receipt Aggregate Vouchers, as a hex string.
#[arg(long, env = "TAP_PRIVATE_KEY")]
private_key: String,

/// Signer public keys. Not the counterpart of the signer private key. Signers that are allowed
/// for the incoming receipts / RAV to aggregate. Useful when needing to accept receipts that
/// were signed with a different key (e.g. a recent key rotation, or receipts coming from a
/// different gateway / aggregator that use a different signing key).
/// Expects a comma-separated list of Ethereum addresses.
#[arg(long, env = "TAP_PUBLIC_KEYS")]
public_keys: Option<Vec<Address>>,

/// Maximum request body size in bytes.
/// Defaults to 10MB.
#[arg(long, default_value_t = 10 * 1024 * 1024, env = "TAP_MAX_REQUEST_BODY_SIZE")]
Expand Down Expand Up @@ -94,11 +103,19 @@ async fn main() -> Result<()> {
// Create the EIP-712 domain separator.
let domain_separator = create_eip712_domain(&args)?;

// Create HashSet of *all* allowed signers
let mut accepted_addresses: HashSet<Address> = std::collections::HashSet::new();
accepted_addresses.insert(wallet.address().0.into());
if let Some(public_keys) = &args.public_keys {
accepted_addresses.extend(public_keys.iter().cloned());
}

// Start the JSON-RPC server.
// This await is non-blocking
let (handle, _) = server::run_server(
args.port,
wallet,
accepted_addresses,
domain_separator,
args.max_request_body_size,
args.max_response_body_size,
Expand Down
Loading

0 comments on commit b16f23d

Please sign in to comment.