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

Dust account replay security #169

Open
gavofyork opened this Issue Nov 1, 2016 · 3 comments

Comments

Projects
None yet
6 participants
@gavofyork

gavofyork commented Nov 1, 2016

Brief

EIP #168 allows for unused accounts to be reclaimed, freeing space in the state trie. However, these reclaimed accounts are now vulnerable to replay attacks. This is a proposal to ensure such accounts remain secure for reuse, against replay attacks, even after being reaped.

Description

Rather than having the account nonce begin at zero and increment, which is open to replay attacks if the nonce is reset during the lifetime of the account, instead it begins equal to the block number in which it is created.

We only increment the nonce at most once per block. This ensures that the range of nonce between resets is never repeated and thus avoids replay attacks, particularly on accounts which send more transactions than their lifetime measured in blocks.

Specification

Variant A

a. Newly created account entries get a nonce equal to the current block number.
b. Transactions do not result in the increment of the sender account by one.
c. At the end of the block, each account that sent at least one transaction has its nonce incremented by one.
d. A transaction of the same hash may not be included in the same block.

Variant B:

Exactly as Variant A with additional rules:

e. Transactions include an additional pre-signature field, ordering.
f. A transaction of ordering = O is only valid for inclusion in a block if either:

  • O = 0; or
  • there exists, in this block, at least one transaction of ordering = O - 1.

Variant C

a. Newly created account entries get a nonce equal to the current block number times MAX_TXS_PER_ACCT_PER_BLOCK.
b. Transactions can only be sent from a nonempty account.
c. If an account has nonzero nonce, then a transaction sent from it may be included in the current block iff:

  • the transaction has the same nonce as the account nonce; and
  • the nonce divided by MAX_TXS_PER_ACCT_PER_BLOCK is no greater than the current block number.
    The account nonce is incremented as normal if the transaction is included.

Example MAX_TXS_PER_ACCT_PER_BLOCK could be 64.

Critique

In general this mechanism leads to a fairly modest state trie increase of at most 3 bytes per account (which may be reducible through contextual compression) and around 4 bytes per transaction.

This mechanism, in the worst case, delivers a little more faff for transactors who make many transactions in a batch since any that are not included in the same block will necessarily need re-signing and re-submitting. This is mitigated by the fact that large batches are automated and relatively uncommon. Furthermore, large batch payments are better managed through a simple suiciding contract-creation transaction which guarantees they are all processed and largely moots the problem that they could be split with many transactions needing to be re-submitted.

Variant 2 exists to allow multiple transactions, possibly of equivalent content, to be included in the same block with a particular ordering. It would cost around an extra byte per transaction. If variant 2 were not adopted, similar functionality could alternatively be provided through a suiciding contract-creation transaction executing a batch.

@vbuterin

This comment has been minimized.

Show comment
Hide comment
@vbuterin

vbuterin Nov 4, 2016

Collaborator

I think there may be a simpler way to do this:

  • If an account is empty, and it is touched, its nonce is set to block.number * MAX_TXS_PER_ACCT_PER_BLOCK.
  • Transactions can only be sent from a nonempty account.
  • If an account has nonzero nonce, then the transaction must have the same nonce as the account nonce. The account nonce is incremented.
  • If an account has nonce == (block.number + 1) * MAX_TXS_PER_ACCT_PER_BLOCK, then transactions from that account are invalid until the block number is incremented.

We could set MAX_TXS_PER_ACCT_PER_BLOCK to something like 64.

Collaborator

vbuterin commented Nov 4, 2016

I think there may be a simpler way to do this:

  • If an account is empty, and it is touched, its nonce is set to block.number * MAX_TXS_PER_ACCT_PER_BLOCK.
  • Transactions can only be sent from a nonempty account.
  • If an account has nonzero nonce, then the transaction must have the same nonce as the account nonce. The account nonce is incremented.
  • If an account has nonce == (block.number + 1) * MAX_TXS_PER_ACCT_PER_BLOCK, then transactions from that account are invalid until the block number is incremented.

We could set MAX_TXS_PER_ACCT_PER_BLOCK to something like 64.

@janx

This comment has been minimized.

Show comment
Hide comment
@janx

janx Dec 2, 2016

Member

There may be another two ways:

Way 1:

  • record in local db a global MAX_NONCE which is the largest of all account nonces
  • newly created account get a nonce equal to MAX_NONCE

Way 2:

  • after block X, use RLP type (int, int) for new nonce, which is a pair of integers:
    • nonce[0] represents account generation
    • nonce[1] represents txs number sent from account as usual
  • if an account is reaped, only nonce[1] is reset to zero, nonce[0] is left unmodified
  • if an account is touched, it's nonce[0] is incremented

The first way add nothing new to account/transaction, while the second adds ~2 bytes to account data.

Member

janx commented Dec 2, 2016

There may be another two ways:

Way 1:

  • record in local db a global MAX_NONCE which is the largest of all account nonces
  • newly created account get a nonce equal to MAX_NONCE

Way 2:

  • after block X, use RLP type (int, int) for new nonce, which is a pair of integers:
    • nonce[0] represents account generation
    • nonce[1] represents txs number sent from account as usual
  • if an account is reaped, only nonce[1] is reset to zero, nonce[0] is left unmodified
  • if an account is touched, it's nonce[0] is incremented

The first way add nothing new to account/transaction, while the second adds ~2 bytes to account data.

@gumb0

This comment has been minimized.

Show comment
Hide comment
@gumb0

gumb0 Feb 2, 2018

Member

Way 1:

  • record in local db a global MAX_NONCE which is the largest of all account nonces
  • newly created account get a nonce equal to MAX_NONCE

Possible downside of this I see: fast sync after downloading the state trie would need to go over all accounts to find the current MAX_NONCE

Member

gumb0 commented Feb 2, 2018

Way 1:

  • record in local db a global MAX_NONCE which is the largest of all account nonces
  • newly created account get a nonce equal to MAX_NONCE

Possible downside of this I see: fast sync after downloading the state trie would need to go over all accounts to find the current MAX_NONCE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment