Цифровые подписи

До сих пор мы не вдавались в подробности о цифровых подписях. В этом разделе мы рассмотрим, как работают цифровые подписи и как их можно использовать для представления доказательства владения закрытым ключом без раскрытия этого закрытого ключа.

Алгоритм эллиптической кривой цифровой подписи

Алгоритм цифровой подписи, используемый в Ethereum, - это алгоритм цифровой подписи на основе эллиптической кривой (ECDSA). Он основан на парах закрытых и открытых ключей с эллиптической кривой, как описано в [elliptic_curve].

Цифровая подпись служит трем целям в Ethereum.

  1. Во-первых, подпись доказывает, что владелец закрытого ключа, который по косвенным признакам является владельцем счета в Ethereum, санкционировал расходование ether или выполнение контракта.
  2. Во-вторых, она гарантирует неотказуемость: доказательство авторизации является неоспоримым.
  3. В-третьих, подпись доказывает, что данные транзакции не были и не могут быть изменены кем-либо после подписания транзакции.

Определение цифровой подписи в Википедии: Цифровая подпись - это математическая схема для подтверждения подлинности цифровых сообщений или документов. Действительная цифровая подпись дает получателю основания полагать, что сообщение было создано известным отправителем (аутентификация), что отправитель не может отрицать факт отправки сообщения (неотрицание) и что сообщение не было изменено при передаче (целостность).

Как работают цифровые подписи

Цифровая подпись - это математическая схема, состоящая из двух частей.

  1. Алгоритм создания подписи с помощью закрытого ключа (ключа подписи) из сообщения (в нашем случае это транзакция).
  2. Алгоритм, который позволяет любому человеку проверить подпись, используя только сообщение и открытый ключ.

Создание цифровой подписи

В реализации ECDSA в Ethereum транзакция это "сообщение", которое подписывается. А точнее, хэш Keccak-256 данных транзакции, закодированных в RLP. Ключом подписи закрытый ключ EOA. Результатом является подпись:

Sig = Fsig(Fkeccak256 (m), k)

где:

  • k - закрытый ключ подписи
  • m - транзакция, закодированная в RLP
  • Fkeccak256 - хэш-функция Keccak-256
  • Fsig - алгоритм подписи
  • Sig - результирующая подпись

Функция Fsig создает подпись Sig, состоящую из двух значений, обычно называемых r и s: Sig = (r, s)

Проверка подписи

Чтобы проверить подпись, необходимо иметь подпись (r и s), сериализованную транзакцию и открытый ключ, соответствующий закрытому ключу, использованному для создания подписи. По сути, проверка подписи означает, что "только владелец закрытого ключа, создавшего этот открытый ключ, мог создать эту подпись на данной транзакции".

Алгоритм проверки подписи принимает сообщение (т.е. хэш транзакции для нашего использования), открытый ключ подписанта и подпись (значения r и s) и возвращает true, если подпись действительна для данного сообщения и открытого ключа.

Математика ECDSA

Как упоминалось ранее, подписи создаются с помощью математической функции Fsig, которая производит подпись, состоящую из двух значений, r и s. В этом разделе мы рассмотрим функцию Fsig более подробно.

Алгоритм подписи сначала генерирует эфемерный (временный) закрытый ключ криптографически безопасным способом. Этот временный ключ используется при вычислении значений r и s, чтобы гарантировать, что реальный личный ключ отправителя не может быть вычислен злоумышленниками, наблюдающими за подписанными транзакциями в сети Ethereum.

Как мы знаем из [pubkey], эфемерный закрытый ключ используется для получения соответствующего (эфемерного) открытого ключа, поэтому мы имеем:

  • криптографически безопасное случайное число q, которое используется в качестве эфемерного закрытого ключа
  • Соответствующий эфемерный открытый ключ Q, сгенерированный из q и точки генератора эллиптической кривой G

В этом случае значение r цифровой подписи является координатой x эфемерного открытого ключа Q.

Далее алгоритм вычисляет значение s подписи, такое, что:

s ≡ q^-1(Keccak256(m) + r * k) (mod p)

где:

  • q - эфемерный закрытый ключ
  • r - координата x эфемерного открытого ключа
  • k - закрытый ключ подписывающего (владельца EOA)
  • m - данные транзакции
  • p - простой порядок эллиптической кривой.

Проверка - это обратная функция генерации подписи, использующая значения r и s и открытый ключ отправителя для вычисления значения Q, которое является точкой на эллиптической кривой (эфемерный открытый ключ, использованный при создании подписи).

Этапы работы следующие:

  1. Проверьте правильность формирования всех входов
  2. Вычислите w = s^-1 mod p
  3. Рассчитайте u1 = Keccak256(m) * w mod p
  4. Рассчитайте u2 = r * w mod p
  5. Наконец, вычислите точку на эллиптической кривой Q ≡ u1 * G + u2 * K (mod p).

где:

  • r и s - значения подписи
  • K - открытый ключ подписанта (владельца EOA)
  • m - данные транзакции, которая была подписана
  • G - точка генератора эллиптической кривой
  • p - простой порядок эллиптической кривой.

Если координата x вычисленной точки Q равна __r, то проверяющий может сделать вывод, что подпись действительна.

Обратите внимание, что при проверке подписи закрытый ключ не известен и не раскрыт.

Совет: ECDSA - это довольно сложная математика; полное объяснение выходит за рамки этой книги. В Интернете можно найти множество замечательных руководств, в которых все объясняется шаг за шагом: наберите в поисковике "ECDSA explained" или попробуйте вот это: Understanding How ECDSA Protects Your Data .

Подписание транзакций на практике

Чтобы транзакция была действительной, отправитель должен подписать сообщение цифровой подписью, используя алгоритм цифровой подписи с эллиптической кривой. Когда мы говорим "подписать транзакцию", мы на самом деле имеем в виду "подписать хэш Keccak-256 данных RLP-сериализованной транзакции". Подпись применяется к хэшу данных транзакции, а не к самой транзакции.

Чтобы подписать транзакцию в Ethereum, инициатор должен:

  1. Создайте структуру данных транзакции, содержащую девять полей: nonce, gasPrice, gasLimit, to, value, data, chainID, 0, 0.
  2. Создать RLP-кодированное сериализованное сообщение структуры данных транзакции.
  3. Вычислите хэш Keccak-256 этого сериализованного сообщения.
  4. Вычислите подпись ECDSA, подписав хэш закрытым ключом отправителя EOA.
  5. Добавьте вычисленные значения v, r и s подписи ECDSA к транзакции.

Специальная переменная подписи v указывает на две вещи: идентификатор цепочки и идентификатор восстановления, чтобы помочь функции ECDSArecover проверить подпись. Она вычисляется как одно из 27 или 28, либо как удвоенный идентификатор цепочки плюс 35 или 36. Дополнительную информацию об идентификаторе цепочки см. в разделе Создание необработанной транзакции с помощью EIP-155. Идентификатор восстановления (27 или 28 в подписях "старого стиля" или 35 или 36 в полных транзакциях в стиле Spurious Dragon) используется для указания четности y-компонента открытого ключа (подробнее см. раздел Значение префикса подписи (v) и восстановление открытого ключа).

Примечание: В блоке № 2,675,000 Ethereum реализовал хард форк "Spurious Dragon", который, среди прочих изменений, ввел новую схему подписания, включающую защиту от воспроизведения транзакций (предотвращение воспроизведения транзакций, предназначенных для одной сети, в других). Эта новая схема подписания указана в EIP-155. Это изменение влияет на форму транзакции и ее подпись, поэтому необходимо обратить внимание на первую из трех переменных подписи (т.е. v), которая принимает одну из двух форм и указывает на поля данных, включенные в хэшируемое сообщение транзакции.

Создание и подписание необработанных транзакций

В этом разделе мы создадим необработанную транзакцию и подпишем ее, используя библиотеку ethereumjs-tx, которую можно установить с помощью npm. Это демонстрирует функции, которые обычно используются в кошельке или приложении, подписывающем транзакции от имени пользователя.

Исходный код этого примера находится в файле raw_tx_demo.js в репозитории GitHub книги: link:code/web3js/raw_tx/raw_tx_demo.js[]

Выполнение кода примера дает следующие результаты:

$ node raw_tx_demo.js
RLP-Encoded Tx: 0xe6808609184e72a0008303000094b0920c523d582040f2bcb1bd7fb1c7c1...
Tx Hash: 0xaa7f03f9f4e52fcf69f836a6d2bbc7706580adce0a068ff6525ba337218e6992
Signed Raw Transaction: 0xf866808609184e72a0008303000094b0920c523d582040f2bcb1...

Создание необработанных транзакций с помощью EIP-155

Стандарт EIP-155 "Простая защита от атак повторного воспроизведения" определяет кодирование транзакций с защитой от атак повторного воспроизведения, которое включает идентификатор цепочки внутри данных транзакции перед подписанием. Это гарантирует, что транзакции, созданные для одного блокчейна (например, основной сети Ethereum), будут недействительны на другом блокчейне (например, Ethereum Classic или тестовой сети Ropsten). Таким образом, транзакции, транслируемые в одной сети, не могут быть воспроизведены в другой, отсюда и название стандарта.

EIP-155 добавляет три поля к основным шести полям структуры данных транзакции, а именно: идентификатор цепочки, 0 и 0. Эти три поля добавляются к данным транзакции до их кодирования и хэширования. Таким образом, они изменяют хэш транзакции, к которому впоследствии применяется подпись. Включая идентификатор цепочки в подписываемые данные, подпись транзакции предотвращает любые изменения, поскольку при изменении идентификатора цепочки подпись становится недействительной. Таким образом, EIP-155 делает невозможным воспроизведение транзакции на другой цепи, поскольку действительность подписи зависит от идентификатора цепи.

Поле идентификатора цепи принимает значение в соответствии с сетью, для которой предназначена транзакция, как описано в разделе Идентификаторы цепи.

Таблица 1. Идентификаторы цепей

Цепь Идентификатор цепи
Ethereum mainnet 1
Morden (устаревшее), Expanse 2
Ropsten 3
Rinkeby 4
Основная сеть Rootstock 30
Испытательная сеть Rootstock 31
Kovan 42
Ethereum Classic mainnet 61
Ethereum Classic testnet 62
Geth частные сети 1337

Полученная структура транзакции кодируется RLP, хэшируется и подписывается. Алгоритм подписи немного модифицирован, чтобы кодировать идентификатор цепочки в префиксе v.

Более подробную информацию см. в спецификации EIP-155.