SCH Course의 Replay Attak 파트들을 풀어나가면서 배웠던 내용들을 정리해보고자 한다.
2명의 지정된 signer가 서명한 off-chain 서명으로만 인출할 수 있는 Multisig Wallet에서 정상적인 방식으로 attacker가 1 ETH를 출금한다.
이 signature를 다시 제출하여 Wallet을 drain한다.
mapping(bytes32 => bool) used
이에 대한 Prevention으로 해설에서는 이렇게 동일한 signature의 사용을 막도록 하고 있다.
하지만 만일 owner가 동일한 금액을 인출하고자 할 경우에는 정당하게 서명을 다시 받더라도 인출을 할 수 없는 문제가 생긴다.
따라서 내생각에는 owner의 nonce를 추적해서 동일한 nonce를 사용할 수 없도록 막는 것이 더 맞는 방법인 것 같다.
이에 대해서는 EIP-2612에서도 나와있는 내용이다.
(이전 글 ERC-2612: Permit Extension for EIP-20 Signed Approvals 을 참조)
ecrecover에서 정상적으로 verify가 되지 않을 경우, revert시키는 것이 아니라 address(0)을 return시키는 점을 이용하여 Replay를 하게되는 문제이다.
ecrecover의 결과가 address(0)일 경우 revert시켜버리는 로직을 추가하여야 한다.
가급적이면 마음 편하게 그냥 OpenZeppelin의 ECDSA library를 사용하는 것이 낫다.
이에 대해서도 분석한 글을 작성해 두었다
Contract에 등록된 signer의 서명을 받아야만 NFT를 mint할 수 있는 contract에서 남은 NFT를 모두 민팅해버려야 한다.
mapping (bytes => mapping (address => bool)) used;
문제는 이렇게 signater => minter 구조로,
특정 signature로 특정 minter에게 mint를 하였는지를 체크하고 있다.
하지만 다른 account로 mint하게 되면, 별 소용이 없다.
mapping (bytes => bool) used
이에 대해서 해설에서는 Replay Attack 1에서와 동일하게 동일한 Signature를 사용하지 못하도록 하고 있다.
하지만 이렇게 하면 아까와 같이 동일한 account로 정당하게 서명을 받더라도 다시 mint를 할 수가 없게 된다.
nonce를 check하는 방식이 제일 맞는 것 같다.
ERC-191, EIP-712, EIP-2612 등 Off-Chain 서명을 위한 많은 표준이 있다.
특히 Replay에 대한 방어가 가장 중요한데 큰 범위부터 다음과 같다.
다른 chain에 재사용
-> DOMAIN_SEPERATOR의 chainId
다른 contract에 동일한 selector를 가진 함수에 재사용
-> DOMAIN_SEPERATOR의 verifyingContract
다른 contract에 동일한 parameter 구조를 가진 함수에 재사용
-> typeHash
동일한 chain의 동일한 contract에 재사용 (이 부분은 EIP-712에 나와있지 않음)
-> 동일 message가 1번만 사용되어야 한다면 mapping(bytes => bool) used을,
아니라면 mapping(address => uint256) nonce를 사용