Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: H-02 Votes Can Be Blocked #593

Merged
merged 7 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions starknet/src/authenticators/eth_tx.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,13 @@ mod EthTxAuthenticator {
use starknet::{ContractAddress, EthAddress, Felt252TryIntoEthAddress, EthAddressIntoFelt252,};
use sx::interfaces::{ISpaceDispatcher, ISpaceDispatcherTrait};
use sx::types::{UserAddress, Strategy, IndexedStrategy, Choice};
use sx::utils::LegacyHashFelt252EthAddress;
use sx::utils::constants::{PROPOSE_SELECTOR, VOTE_SELECTOR, UPDATE_PROPOSAL_SELECTOR};

#[storage]
struct Storage {
_starknet_commit_address: EthAddress,
_commits: LegacyMap::<felt252, EthAddress>
_commits: LegacyMap::<(felt252, EthAddress), bool>
}

#[external(v0)]
Expand Down Expand Up @@ -176,19 +177,17 @@ mod EthTxAuthenticator {
assert(
from_address == self._starknet_commit_address.read().into(), 'Invalid commit address'
);
// Prevents hash being overwritten by a different sender.
assert(self._commits.read(hash).into() == 0, 'Commit already exists');
self._commits.write(hash, sender_address.try_into().unwrap());
let sender_address = sender_address.try_into().unwrap();
assert(self._commits.read((hash, sender_address)) == false, 'Commit already exists');
self._commits.write((hash, sender_address), true);
}

#[generate_trait]
impl InternalImpl of InternalTrait {
fn consume_commit(ref self: ContractState, hash: felt252, sender_address: EthAddress) {
let committer_address = self._commits.read(hash);
assert(committer_address.is_non_zero(), 'Commit not found');
assert(committer_address == sender_address, 'Invalid sender address');
assert(self._commits.read((hash, sender_address)), 'Commit not found');
// Delete the commit to prevent replay attacks.
self._commits.write(hash, Zeroable::zero());
self._commits.write((hash, sender_address), false);
}
}
}
4 changes: 2 additions & 2 deletions starknet/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ mod utils {

mod legacy_hash;
use legacy_hash::{
LegacyHashEthAddress, LegacyHashUsedSalts, LegacyHashChoice, LegacyHashUserAddress,
LegacyHashVotePower, LegacyHashVoteRegistry, LegacyHashSpanFelt252
LegacyHashEthAddress, LegacyHashFelt252EthAddress, LegacyHashUsedSalts, LegacyHashChoice,
LegacyHashUserAddress, LegacyHashVotePower, LegacyHashVoteRegistry, LegacyHashSpanFelt252
};

mod math;
Expand Down
8 changes: 8 additions & 0 deletions starknet/src/utils/legacy_hash.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ impl LegacyHashEthAddress of LegacyHash<EthAddress> {
}
}

impl LegacyHashFelt252EthAddress of LegacyHash<(felt252, EthAddress)> {
fn hash(state: felt252, value: (felt252, EthAddress)) -> felt252 {
let (_felt252, _eth_address) = value;
let state = LegacyHash::hash(state, _felt252);
LegacyHash::hash(state, _eth_address)
}
}

impl LegacyHashSpanFelt252 of LegacyHash<Span<felt252>> {
fn hash(state: felt252, mut value: Span<felt252>) -> felt252 {
let len = value.len();
Expand Down
60 changes: 59 additions & 1 deletion tests/eth-tx-auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,10 +473,68 @@ describe('Ethereum Transaction Authenticator', function () {
);
expect.fail('Should have failed');
} catch (err: any) {
expect(err.message).to.contain(shortString.encodeShortString('Invalid sender address'));
expect(err.message).to.contain(shortString.encodeShortString('Commit not found'));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we add a test that tests the "attack" vector that OZ mentioned? Just to add it as a regression test for later work

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah sorry added but forgot to push

}, 1000000);

it('should not revert if the same commit was made twice', async () => {
await starknet.devnet.restart();
await starknet.devnet.load('./dump.pkl');
await starknet.devnet.increaseTime(10);
await starknet.devnet.loadL1MessagingContract(eth_network, mockStarknetMessaging.address);

const proposal = {
author: signer.address,
metadataUri: ['0x1', '0x2', '0x3', '0x4'],
executionStrategy: {
address: '0x0000000000000000000000000000000000005678',
params: ['0x0'],
},
userProposalValidationParams: [
'0xffffffffffffffffffffffffffffffffffffffffff',
'0x1234',
'0x5678',
'0x9abc',
],
};

const proposeCommitPreImage = CallData.compile({
target: space.address,
selector: selector.getSelectorFromName('propose'),
...proposal,
});

// Commit hash of payload to the Starknet Commit L1 contract
const commit = `0x${poseidonHashMany(proposeCommitPreImage.map((v) => BigInt(v))).toString(
16,
)}`;

// Committing payload from a different address to the author
await starknetCommit
.connect(invalidSigner)
.commit(ethTxAuthenticator.address, commit, { value: 18485000000000 });

expect((await starknet.devnet.flush()).consumed_messages.from_l1).to.have.a.lengthOf(1);

// Committing the same payload from the author
await starknetCommit.commit(ethTxAuthenticator.address, commit, { value: 18485000000000 });

expect((await starknet.devnet.flush()).consumed_messages.from_l1).to.have.a.lengthOf(1);

await account.invoke(
ethTxAuthenticator,
'authenticate_propose',
CallData.compile({
target: space.address,
author: proposal.author,
metadataURI: proposal.metadataUri,
executionStrategy: proposal.executionStrategy,
userProposalValidationParams: proposal.userProposalValidationParams,
}),
{ rawInput: true },
);
}, 1000000);

it('a commit cannot be consumed twice', async () => {
await starknet.devnet.restart();
await starknet.devnet.load('./dump.pkl');
Expand Down