Skip to content

Commit 9dc58d0

Browse files
committed
fix: disallow signers to be authorized for different payers (TRST-M10)
Signed-off-by: Tomás Migone <[email protected]>
1 parent c8efe89 commit 9dc58d0

File tree

6 files changed

+186
-24
lines changed

6 files changed

+186
-24
lines changed

packages/horizon/contracts/interfaces/ITAPCollector.sol

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@ interface ITAPCollector is IPaymentsCollector {
1818
address payer;
1919
// Timestamp at which thawing period ends (zero if not thawing)
2020
uint256 thawEndTimestamp;
21+
// Whether the signer authorization was revoked
22+
bool revoked;
2123
}
2224

2325
/// @notice The Receipt Aggregate Voucher (RAV) struct
2426
struct ReceiptAggregateVoucher {
27+
// The address of the payer the RAV was issued by
28+
address payer;
2529
// The address of the data service the RAV was issued to
2630
address dataService;
2731
// The address of the service provider the RAV was issued to
@@ -119,6 +123,12 @@ interface ITAPCollector is IPaymentsCollector {
119123
*/
120124
error TAPCollectorSignerNotAuthorizedByPayer(address payer, address signer);
121125

126+
/**
127+
* Thrown when the attempting to revoke a signer that was already revoked
128+
* @param signer The address of the signer
129+
*/
130+
error TAPCollectorAuthorizationAlreadyRevoked(address payer, address signer);
131+
122132
/**
123133
* Thrown when the signer is not thawing
124134
* @param signer The address of the signer
@@ -137,6 +147,13 @@ interface ITAPCollector is IPaymentsCollector {
137147
*/
138148
error TAPCollectorInvalidRAVSigner();
139149

150+
/**
151+
* Thrown when the RAV payer does not match the signers authorized payer
152+
* @param authorizedPayer The address of the authorized payer
153+
* @param ravPayer The address of the RAV payer
154+
*/
155+
error TAPCollectorInvalidRAVPayer(address authorizedPayer, address ravPayer);
156+
140157
/**
141158
* Thrown when the RAV is for a data service the service provider has no provision for
142159
* @param dataService The address of the data service
@@ -159,7 +176,8 @@ interface ITAPCollector is IPaymentsCollector {
159176
error TAPCollectorInconsistentRAVTokens(uint256 tokens, uint256 tokensCollected);
160177

161178
/**
162-
* @notice Authorize a signer to sign on behalf of the payer
179+
* @notice Authorize a signer to sign on behalf of the payer.
180+
* A signer can not be authorized for multiple payers even after revoking previous authorizations.
163181
* @dev Requirements:
164182
* - `signer` must not be already authorized
165183
* - `proofDeadline` must be greater than the current timestamp

packages/horizon/contracts/payments/collectors/TAPCollector.sol

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/Mes
1818
* @dev Note that the contract expects the RAV aggregate value to be monotonically increasing, each successive RAV for the same
1919
* (data service-payer-receiver) tuple should have a value greater than the previous one. The contract will keep track of the tokens
2020
* already collected and calculate the difference to collect.
21+
* @dev The contract also implements a mechanism to authorize signers to sign RAVs on behalf of a payer. Signers cannot be reused
22+
* for different payers.
2123
* @custom:security-contact Please email [email protected] if you find any
2224
* bugs. We may have an active bug bounty program.
2325
*/
@@ -27,7 +29,7 @@ contract TAPCollector is EIP712, GraphDirectory, ITAPCollector {
2729
/// @notice The EIP712 typehash for the ReceiptAggregateVoucher struct
2830
bytes32 private constant EIP712_RAV_TYPEHASH =
2931
keccak256(
30-
"ReceiptAggregateVoucher(address dataService,address serviceProvider,uint64 timestampNs,uint128 valueAggregate,bytes metadata)"
32+
"ReceiptAggregateVoucher(address payer,address dataService,address serviceProvider,uint64 timestampNs,uint128 valueAggregate,bytes metadata)"
3133
);
3234

3335
/// @notice Authorization details for payer-signer pairs
@@ -79,6 +81,7 @@ contract TAPCollector is EIP712, GraphDirectory, ITAPCollector {
7981
PayerAuthorization storage authorization = authorizedSigners[signer];
8082

8183
require(authorization.payer == msg.sender, TAPCollectorSignerNotAuthorizedByPayer(msg.sender, signer));
84+
require(!authorization.revoked, TAPCollectorAuthorizationAlreadyRevoked(msg.sender, signer));
8285

8386
authorization.thawEndTimestamp = block.timestamp + REVOKE_SIGNER_THAWING_PERIOD;
8487
emit SignerThawing(msg.sender, signer, authorization.thawEndTimestamp);
@@ -110,7 +113,7 @@ contract TAPCollector is EIP712, GraphDirectory, ITAPCollector {
110113
TAPCollectorSignerStillThawing(block.timestamp, authorization.thawEndTimestamp)
111114
);
112115

113-
delete authorizedSigners[signer];
116+
authorization.revoked = true;
114117
emit SignerRevoked(msg.sender, signer);
115118
}
116119

@@ -122,14 +125,26 @@ contract TAPCollector is EIP712, GraphDirectory, ITAPCollector {
122125
* @notice REVERT: This function may revert if ECDSA.recover fails, check ECDSA library for details.
123126
*/
124127
function collect(IGraphPayments.PaymentTypes paymentType, bytes memory data) external override returns (uint256) {
128+
// Ensure caller is the RAV data service
125129
(SignedRAV memory signedRAV, uint256 dataServiceCut) = abi.decode(data, (SignedRAV, uint256));
126130
require(
127131
signedRAV.rav.dataService == msg.sender,
128132
TAPCollectorCallerNotDataService(msg.sender, signedRAV.rav.dataService)
129133
);
130134

135+
// Ensure RAV signer is authorized for a payer
131136
address signer = _recoverRAVSigner(signedRAV);
132-
require(authorizedSigners[signer].payer != address(0), TAPCollectorInvalidRAVSigner());
137+
require(
138+
authorizedSigners[signer].payer != address(0) && !authorizedSigners[signer].revoked,
139+
TAPCollectorInvalidRAVSigner()
140+
);
141+
142+
// Ensure RAV payer matches the authorized payer
143+
address payer = signedRAV.rav.payer;
144+
require(
145+
authorizedSigners[signer].payer == payer,
146+
TAPCollectorInvalidRAVPayer(authorizedSigners[signer].payer, payer)
147+
);
133148

134149
// Check the service provider has an active provision with the data service
135150
// This prevents an attack where the payer can deny the service provider from collecting payments

packages/horizon/test/payments/tap-collector/TAPCollector.t.sol

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,10 @@ contract TAPCollectorTest is HorizonStakingSharedTest, PaymentsEscrowSharedTest
6666

6767
tapCollector.authorizeSigner(_signer, _proofDeadline, _proof);
6868

69-
(address _payer, uint256 thawEndTimestamp) = tapCollector.authorizedSigners(_signer);
69+
(address _payer, uint256 thawEndTimestamp, bool revoked) = tapCollector.authorizedSigners(_signer);
7070
assertEq(_payer, msgSender);
7171
assertEq(thawEndTimestamp, 0);
72+
assertEq(revoked, false);
7273
}
7374

7475
function _thawSigner(address _signer) internal {
@@ -80,9 +81,10 @@ contract TAPCollectorTest is HorizonStakingSharedTest, PaymentsEscrowSharedTest
8081

8182
tapCollector.thawSigner(_signer);
8283

83-
(address _payer, uint256 thawEndTimestamp) = tapCollector.authorizedSigners(_signer);
84+
(address _payer, uint256 thawEndTimestamp, bool revoked) = tapCollector.authorizedSigners(_signer);
8485
assertEq(_payer, msgSender);
8586
assertEq(thawEndTimestamp, expectedThawEndTimestamp);
87+
assertEq(revoked, false);
8688
}
8789

8890
function _cancelThawSigner(address _signer) internal {
@@ -93,29 +95,34 @@ contract TAPCollectorTest is HorizonStakingSharedTest, PaymentsEscrowSharedTest
9395

9496
tapCollector.cancelThawSigner(_signer);
9597

96-
(address _payer, uint256 thawEndTimestamp) = tapCollector.authorizedSigners(_signer);
98+
(address _payer, uint256 thawEndTimestamp, bool revoked) = tapCollector.authorizedSigners(_signer);
9799
assertEq(_payer, msgSender);
98100
assertEq(thawEndTimestamp, 0);
101+
assertEq(revoked, false);
99102
}
100103

101104
function _revokeAuthorizedSigner(address _signer) internal {
102105
(, address msgSender, ) = vm.readCallers();
103106

107+
(address beforePayer, uint256 beforeThawEndTimestamp, ) = tapCollector.authorizedSigners(_signer);
108+
104109
vm.expectEmit(address(tapCollector));
105110
emit ITAPCollector.SignerRevoked(msgSender, _signer);
106111

107112
tapCollector.revokeAuthorizedSigner(_signer);
108113

109-
(address _payer, uint256 thawEndTimestamp) = tapCollector.authorizedSigners(_signer);
110-
assertEq(_payer, address(0));
111-
assertEq(thawEndTimestamp, 0);
114+
(address afterPayer, uint256 afterThawEndTimestamp, bool afterRevoked) = tapCollector.authorizedSigners(_signer);
115+
116+
assertEq(beforePayer, afterPayer);
117+
assertEq(beforeThawEndTimestamp, afterThawEndTimestamp);
118+
assertEq(afterRevoked, true);
112119
}
113120

114121
function _collect(IGraphPayments.PaymentTypes _paymentType, bytes memory _data) internal {
115122
(ITAPCollector.SignedRAV memory signedRAV, uint256 dataServiceCut) = abi.decode(_data, (ITAPCollector.SignedRAV, uint256));
116123
bytes32 messageHash = tapCollector.encodeRAV(signedRAV.rav);
117124
address _signer = ECDSA.recover(messageHash, signedRAV.signature);
118-
(address _payer, ) = tapCollector.authorizedSigners(_signer);
125+
(address _payer, , ) = tapCollector.authorizedSigners(_signer);
119126
uint256 tokensAlreadyCollected = tapCollector.tokensCollected(signedRAV.rav.dataService, signedRAV.rav.serviceProvider, _payer);
120127
uint256 tokensToCollect = signedRAV.rav.valueAggregate - tokensAlreadyCollected;
121128
uint256 tokensDataService = tokensToCollect.mulPPM(dataServiceCut);

0 commit comments

Comments
 (0)