Отложенные транзакции для Solidity
9.02.2018, ethereum
В этой заметке я хочу поделиться одной интересной техникой при разработке умных контрактов сети Ethereum.
Цифровая подпись
Ethereum поддерживает цифровые подписи на эллиптических кривых, функция ecrecover. Такая функция позволяет восстановить публичный ключ, а именно адрес Ethereum, из подписи произвольных данных.
contract Checker is owned {
function byOwner(bytes32 _message, uint8 _v, bytes32 _r, bytes32 _s) view returns (bool) {
return ecrecover(_message, _v, _r, _s) == owner;
}
}
В примере, метод byOwner
возвращает true тогда и только тогда, когда сообщение подписано приватным ключом владельца контракта. Для подписи сообщения популярные клиенты сети Ethereum предоставляют метод eth.sign(account, message)
.
Интересно отметить, что, например, клиент parity подставляет к подписываемому сообщению префикс, который важно учитывать при восстановлении адреса из цифровой подписи.
bytes constant MSGPREFIX = "\x19Ethereum Signed Message:\n32";
address signer = ecrecover(keccak256(MSGPREFIX, _message), _v, _r, _s);
Здесь 32
это длина сообщения в байтах.
Отложенное исполнение
Иногда бывает очень удобно не поселять транзакцию непосредственно в момент создания, а дождаться какого-нибудь события, либо чтобы вовсе поселение транзакции выполнил кто-то другой. Например, Алиса хочет перевести Бобу 2 ether. Здесь возможно несколько вариантов событий:
- Алиса знает аккаунт Боба:
- Алиса просто отправляет транзакцию о переводе средств на аккаунт Боба
- Алиса не знает аккаунт Боба:
- Алиса создает новый адрес, переводит на него ether и отдает приватный ключ Бобу (так называемый бумажный кошелек)
- Алиса подписывает отложенную транзакцию о переводе средств
Последний вариант наиболее интересен, Алисе не нужно знать ни адрес Боба ни даже отправлять транзакцию в сеть. Боб сможет сам указать адрес для получения и обеспечить исполнение транзакции.
Как это работает
Попробуем представить себе умный контракт, который обеспечил бы для Алисы отложенный перевод средств:
contract DelayedTransfer is owned {
function DelayedTransfer() payable {}
function transfer(address _to, uint256 _value, uint256 _nonce, uint8 _v, bytes32 _r, bytes32 _s) {
Это умный контракт, который может принимать эфир на баланс от Алисы. Алиса пополняет его один раз при создании и может раздавать с него эфир пока баланс не обнулится. И один метод для Боба с аргументами:
- адрес назначения
- величина перевода
- соль
- цифровая подпись
Боб получает от Алисы величину платежа, соль и подпись, и подставляет свой адрес в соответствующее поле транзакции.
mapping(uint256 => bool) public isUsed;
function transfer(address _to, uint256 _value, uint256 _nonce, uint8 _v, bytes32 _r, bytes32 _s) {
bytes32 message = keccak256(_value, _nonce);
require(ecrecover(message, _v, _r, _s) == owner && !isUsed[_nonce]);
isUsed[_nonce] = true;
_to.transfer(_value);
}
Здесь обязательным условием исполнения транзакции является то, что подписать значение и соль должен владелец контракта, т.е. Алиса. Также проверяется повторное исполнение перевода, переменная isUsed
. При этом адрес получателя не фиксируется. Это значит, что цифровая подпись Алисы становится для Боба аналогом купона на перевод и должна хранится в секрете.
Заключение
Что интересного демонстрирует этот пример. Цифровая подпись помогает решать вопросы доверия вне блокчейн, а поддержка со стороны умных контрактов открывает возможности отложенного управления цифровыми ценностями или процессами.