|
| 1 | +# ERC-1271: Standard Signature Validation Method for Contracts |
| 2 | + |
| 3 | +**Автор:** [Павел Найданов](https://github.com/PavelNaydanov) 🕵️♂️ |
| 4 | + |
| 5 | +Стандарт ERC-1271 предлагает универсальный интерфейс для проверки подписи. Смарт-контракт, который реализует подобный интерфейс, будет считаться сайнером (Signer) и подтверждать валидность подписи. |
| 6 | + |
| 7 | +Это ключевая идея стандарта — позволить контрактам выступать в роли `signer'ов`, несмотря на отсутствие у них приватных ключей для генерации подписей. Вместо этого смарт-контракт предоставляет метод `isValidSignature()`, через который внешние протоколы и приложения могут запросить проверку валидности подписи. |
| 8 | + |
| 9 | + |
| 10 | + |
| 11 | +На схеме видно, что EOA, который является владельцем смарт-контракта **Wallet**, подписывает транзакцию и приложение передает вместе с вызовом его подпись на смарт-контракт **Target**, который может представлять биржу, лендинг протокол и другие DApps. **Target** валидирует подпись EOA на смарт-контракте **Wallet**, который выступает `Signer'ом` в рамках стандарта ERC-1271. |
| 12 | + |
| 13 | +_Важно!_ Стоит отметить, что в нашем случае подпись сгенерирована приватным ключом EOA, но могут быть использованы и другие типы проверок. |
| 14 | + |
| 15 | +Смарт-контракт Wallet должен реализовать функцию: |
| 16 | +```solidity |
| 17 | +function isValidSignature(bytes32 _hash, bytes calldata _signature) external view returns (bytes4) |
| 18 | +``` |
| 19 | + |
| 20 | +Это `view` функция, которая не должна изменять состояние. Также стоит обратить внимание на возвращаемое значение: в случае успеха возвращается `bytes4` - это селектор функции `isValidSignature()` (селектор самой себя). Это классический подход, когда возвращается не булевое значение. Возвращаемые `bytes4` вносят больше определенности, так как смарт-контракт не поддерживающий EIP-1271, при вызове у него функции `isValidSignature()`, может провалиться в `fallback()` функцию и вернуть **true** непреднамеренно. |
| 21 | + |
| 22 | +Пример кода ниже взят из спецификации [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271). |
| 23 | + |
| 24 | +```solidity |
| 25 | +
|
| 26 | + // bytes4(keccak256("isValidSignature(bytes32,bytes)") |
| 27 | + bytes4 constant internal MAGICVALUE = 0x1626ba7e; |
| 28 | +
|
| 29 | + /** |
| 30 | + * @dev Should return whether the signature provided is valid for the provided hash |
| 31 | + * @param _hash Hash of the data to be signed |
| 32 | + * @param _signature Signature byte array associated with _hash |
| 33 | + * |
| 34 | + * MUST return the bytes4 magic value 0x1626ba7e when function passes. |
| 35 | + * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) |
| 36 | + * MUST allow external calls |
| 37 | + */ |
| 38 | + function isValidSignature( |
| 39 | + bytes32 _hash, |
| 40 | + bytes memory _signature) |
| 41 | + public |
| 42 | + view |
| 43 | + returns (bytes4 magicValue); |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +_Важно!_ Реализация функции `isValidSignature()` не регламентируется строго и может содержать сложную реализацию проверки подписи под капотом, которая зависит: |
| 48 | + - от контекста (например, от времени или состояния) |
| 49 | + - от EOA (например, от уровня авторизации подписывающего в смарт-кошельке) |
| 50 | + - от схемы подписи (например, ECDSA, multisig, BLS) и т.д. |
| 51 | + |
| 52 | +Поэтому некоторые реализации могут потреблять больше газа, чем ожидалось. Важно не задавать количество газа, отправляемого при вызове функции с Target контракта, так как это может привести к провалу транзакции. |
| 53 | + |
| 54 | +По сути, это все что необходимо знать про этот стандарт. |
| 55 | + |
| 56 | +## Проблемы с ERC-1271 |
| 57 | + |
| 58 | +Множество протоколов которые решают проблемы абстракции аккаунтов поддерживают ERC-1271 под капотом своих смарт-контрактов, реализующих абстрактные аккаунты. |
| 59 | + |
| 60 | +В 2023 года компания Alchemy обнаружила уязвимость в своем смарт-контракте `LightAccount`. Проблема заключалась в возможности воспроизведения подписи ERC-1271 повторно. EOA, которые завели себе несколько `LightAccount`, давая подпись для одного из них, автоматически разрешали применить подпись и ко второму `LightAccount`. |
| 61 | + |
| 62 | + |
| 63 | + |
| 64 | +На схеме видно, что с одной и той же подписью `Recipient` может получить два транша с разных аккаунтов пользователя. |
| 65 | + |
| 66 | +Подобная проблема оказалась еще у целого ряда АА протоколов: Zerodev, Biconomy, Soul Wallet, EIP4337Fallback для Gnosis Safes от eth-infinitism, AmbireAccount, SmartAccount от OKX, BaseWallet от Argent и Fuse Wallet. |
| 67 | + |
| 68 | +Проблема касалась случаев, когда подпись разрешала смарт-контрактам Permit2 перевести токены. Также сюда можно отнести другие подобные смарт-контракты, например, Cowswap, Lens протокол и другие. |
| 69 | + |
| 70 | +Ребята из Alchemy забили тревогу и совместно с другими специалистами по АА выработали два решение. |
| 71 | + |
| 72 | +**Решение 1. Использовать domain из EIP-712** |
| 73 | + |
| 74 | +При помощи структуры domain, как в [EIP-712](https://eips.ethereum.org/EIPS/eip-712), определить на каком смарт-контракте можно проверить подпись. |
| 75 | + |
| 76 | +```solidity |
| 77 | +function isValidSignature(bytes32 digest, bytes calldata sig) external view returns (bytes4) { |
| 78 | + bytes32 domainSeparator = |
| 79 | + keccak256( |
| 80 | + abi.encode( |
| 81 | + _DOMAIN_SEPARATOR_TYPEHASH, |
| 82 | + _NAME_HASH, |
| 83 | + _VERSION_HASH, |
| 84 | + block.chainid, |
| 85 | + address(walletA) // Определяет на каком смарт-контракте кошельке может быть валидирована подпись |
| 86 | + ) |
| 87 | + ); |
| 88 | +
|
| 89 | + bytes32 wrappedDigest = keccak256(abi.encode("\x19\x01", domainSeparator, digest)); |
| 90 | +
|
| 91 | + return ECDSA.recover(wrappedDigest, sig); |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +**Решение 2. Добавлять адрес смарт-контракта к хешу данных** |
| 96 | + |
| 97 | +Используется хеш подписи и к ней добавляется адрес смарт-контракта на котором можно валидировать подпись. |
| 98 | +Это решение более легковесно, но означает, что клиентам кошельков придётся отображать непрозрачный хеш для подписи пользователей. |
| 99 | + |
| 100 | +```solidity |
| 101 | +function isValidSignature(bytes32 digest, bytes calldata sig) external view returns (bytes4) { |
| 102 | + bytes32 wrappedDigest = keccak256(abi.encode(digest, address(SCA)); |
| 103 | + return ECDSA.recover(wrappedDigest, sig); |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +## Вывод |
| 108 | + |
| 109 | +ERC-1271 - это очень простой ERC, но он имеет решающее значение для установления стандарта проверки подписей с помощью смарт-контрактов. |
| 110 | + |
| 111 | +Многие думают, что стандарт - это возможность смарт-контрактам давать разрешения через подпись от своего имени. Но я бы посмотрел еще шире, на мой взгляд этот стандарт можно применить в любом случае, когда необходимо проверять подпись на смарт-контрактах. Поэтому, если ваши смарт-контракты реализуют любую безгазовость через подписи или проверяют комиссии установленные бекендом и так далее, то смело можно использовать этот интерфейс от ERC-1271. |
| 112 | + |
| 113 | +## Links |
| 114 | + |
| 115 | +1. [ERC-1271: Standard Signature Validation Method for Contracts](https://eips.ethereum.org/EIPS/eip-1271) |
| 116 | +2. [ERC-1271 Signature Replay Vulnerability](https://www.alchemy.com/blog/erc-1271-signature-replay-vulnerability) |
| 117 | +3. [Clarifying ERC-1271: Smart Contract Signature Verification](https://medium.com/taipei-ethereum-meetup/clarifications-on-erc-1271-smart-contract-signature-verification-and-signing-cd5c2fb7ac1b) |
0 commit comments