Одноразовые адреса
Stealth-адреса: как отправить деньги на адрес, не записывая его в блокчейн
Отправитель берёт случайный r, публикует R = r·G и отправляет деньги на одноразовый ключ P = Hs(8·r·A ‖ i)·G + B. Получатель находит его, вычисляя то же самое через a: Hs(8·a·R ‖ i)·G + B. Тратит его приватным ключом x = Hs(8·a·R ‖ i) + b.
Задача
Мы хотим, чтобы:
- в блокчейне не было ни адреса получателя, ни чего-либо, что повторяется между платежами одному человеку;
- получатель мог сам найти свои выходы, не общаясь с отправителем;
- потратить выход мог только получатель.
Решение — обмен ключами Диффи–Хеллмана, встроенный в каждую транзакцию.
Шаг за шагом
1. Отправитель выбирает случайный r
Для каждой транзакции — новый случайный r (transaction private key). Его публичная версия R = r·G (transaction public key) записывается в поле tx_extra транзакции.
2. Общий секрет
Отправитель вычисляет 8·r·A (A — публичный ключ просмотра из адреса). Получатель позже вычислит 8·a·R. Это одна и та же точка, потому что a·R = a·r·G = r·a·G = r·A. В коде Monero она называется key derivation.
Множитель 8 (кофактор) нужен, чтобы «очистить» точку от возможной компоненты малого порядка: даже если кто-то подсунет злонамеренную R, результат окажется в правильной подгруппе.
3. Одноразовый ключ
Это и есть одноразовый адрес (output public key). Он выглядит как случайная точка: без a или r его не с чем сравнить.
4. Получатель сканирует
Для каждой новой транзакции и каждого выхода i кошелёк вычисляет Hs(8·a·R ‖ i)·G + B и сравнивает с P. Совпало — выход наш. Для этого нужен только ключ просмотра a и публичный B — поэтому view-only кошелёк может находить входящие.
5. Получатель тратит
Приватный ключ одноразового выхода:
Для этого нужен ключ траты b. Ключом x подписывается трата, и из него же вычисляется key image.
Сумма тоже шифруется общим секретом
Из того же общего секрета выводится маска для суммы. Сумма выхода (8 байт) хранится как amount ⊕ Keccak("amount" ‖ Hs(8rA ‖ i))[0..8], а «ослепляющий множитель» обязательства — как Hs("commitment_mask" ‖ Hs(8rA ‖ i)). Получатель вычисляет то же самое и расшифровывает сумму. Подробнее — в обязательствах Педерсена.
Слабые места
- Сканирование дорогое: для каждого выхода каждой транзакции — умножение точки на скаляр. Отчасти лечится view tags.
- Janus-атака: отправитель, подозревающий, что два подадреса принадлежат одному человеку, может собрать выход из частей разных подадресов и посмотреть, «заметит» ли получатель платёж. Защита в кошельках есть частично; полностью закрывает её Carrot.
- Отправитель знает r и может доказать третьей стороне, что заплатил на определённый адрес (это иногда полезно — «payment proof»).