Skip to content

Commit b16f23d

Browse files
authored
feat(aggregator): Add support for multiple signers in input (#211)
Fixes #205 Signed-off-by: Alexis Asseman <[email protected]>
1 parent 2ed564b commit b16f23d

File tree

6 files changed

+141
-41
lines changed

6 files changed

+141
-41
lines changed

tap_aggregator/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@ ruint = "1.10.1"
3434

3535
[dev-dependencies]
3636
jsonrpsee = { version = "0.18.0", features = ["http-client", "jsonrpsee-core"] }
37+
rand = "0.8.5"
3738
rstest = "0.17.0"

tap_aggregator/src/aggregator.rs

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
// Copyright 2023-, Semiotic AI, Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use std::collections::hash_set;
4+
use std::collections::{hash_set, HashSet};
55

66
use alloy_primitives::Address;
7-
use alloy_sol_types::Eip712Domain;
8-
use anyhow::{Ok, Result};
7+
use alloy_sol_types::{Eip712Domain, SolStruct};
8+
use anyhow::{bail, Ok, Result};
99
use ethers_core::types::Signature;
10-
use ethers_signers::{LocalWallet, Signer};
10+
use ethers_signers::LocalWallet;
1111

1212
use tap_core::{
1313
eip_712_signed_message::EIP712SignedMessage,
@@ -19,22 +19,26 @@ pub async fn check_and_aggregate_receipts(
1919
receipts: &[EIP712SignedMessage<Receipt>],
2020
previous_rav: Option<EIP712SignedMessage<ReceiptAggregateVoucher>>,
2121
wallet: &LocalWallet,
22+
accepted_addresses: &HashSet<Address>,
2223
) -> Result<EIP712SignedMessage<ReceiptAggregateVoucher>> {
23-
// Get the address of the wallet
24-
let address: [u8; 20] = wallet.address().into();
25-
let address: Address = address.into();
26-
27-
// Check that the receipts are unique
2824
check_signatures_unique(receipts)?;
2925

30-
// Check that the receipts are signed by ourselves
31-
receipts
32-
.iter()
33-
.try_for_each(|receipt| receipt.verify(domain_separator, address))?;
26+
// Check that the receipts are signed by an accepted signer address
27+
receipts.iter().try_for_each(|receipt| {
28+
check_signature_is_from_one_of_addresses(
29+
receipt.clone(),
30+
domain_separator,
31+
accepted_addresses,
32+
)
33+
})?;
3434

35-
// Check that the previous rav is signed by ourselves
35+
// Check that the previous rav is signed by an accepted signer address
3636
if let Some(previous_rav) = &previous_rav {
37-
previous_rav.verify(domain_separator, address)?;
37+
check_signature_is_from_one_of_addresses(
38+
previous_rav.clone(),
39+
domain_separator,
40+
accepted_addresses,
41+
)?;
3842
}
3943

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

75+
fn check_signature_is_from_one_of_addresses<M: SolStruct>(
76+
message: EIP712SignedMessage<M>,
77+
domain_separator: &Eip712Domain,
78+
accepted_addresses: &HashSet<Address>,
79+
) -> Result<()> {
80+
let recovered_address = message.recover_signer(domain_separator)?;
81+
if !accepted_addresses.contains(&recovered_address) {
82+
bail!(tap_core::Error::InvalidRecoveredSigner {
83+
address: recovered_address,
84+
});
85+
}
86+
Ok(())
87+
}
88+
7189
fn check_allocation_id(
7290
receipts: &[EIP712SignedMessage<Receipt>],
7391
allocation_id: Address,

tap_aggregator/src/main.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#![doc = include_str!("../README.md")]
55

66
use std::borrow::Cow;
7+
use std::collections::HashSet;
78
use std::str::FromStr;
89

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

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

33+
/// Signer public keys. Not the counterpart of the signer private key. Signers that are allowed
34+
/// for the incoming receipts / RAV to aggregate. Useful when needing to accept receipts that
35+
/// were signed with a different key (e.g. a recent key rotation, or receipts coming from a
36+
/// different gateway / aggregator that use a different signing key).
37+
/// Expects a comma-separated list of Ethereum addresses.
38+
#[arg(long, env = "TAP_PUBLIC_KEYS")]
39+
public_keys: Option<Vec<Address>>,
40+
3241
/// Maximum request body size in bytes.
3342
/// Defaults to 10MB.
3443
#[arg(long, default_value_t = 10 * 1024 * 1024, env = "TAP_MAX_REQUEST_BODY_SIZE")]
@@ -94,11 +103,19 @@ async fn main() -> Result<()> {
94103
// Create the EIP-712 domain separator.
95104
let domain_separator = create_eip712_domain(&args)?;
96105

106+
// Create HashSet of *all* allowed signers
107+
let mut accepted_addresses: HashSet<Address> = std::collections::HashSet::new();
108+
accepted_addresses.insert(wallet.address().0.into());
109+
if let Some(public_keys) = &args.public_keys {
110+
accepted_addresses.extend(public_keys.iter().cloned());
111+
}
112+
97113
// Start the JSON-RPC server.
98114
// This await is non-blocking
99115
let (handle, _) = server::run_server(
100116
args.port,
101117
wallet,
118+
accepted_addresses,
102119
domain_separator,
103120
args.max_request_body_size,
104121
args.max_response_body_size,

0 commit comments

Comments
 (0)