Моя сырая транзакция уничтожена 0.0284377 BTC. Что я сделал не так?

Несколько лет назад я разработал .NET-модуль, который облегчает передачу BTC моим клиентам. Он создает двоичное представление желаемой транзакции на основе представленного материала здесь и здесь :

Затем двоичное представление преобразуется в шестнадцатеричный и вытесняется через различные бесплатные службы, такие как BlockExplorer, BlockCypher и т. д.

Система работает безупречно годами. До вчерашнего дня. Клиент запросил отправить 0.0284377 BTC на 38MRMGjMBMp4k7vZhKLHhcM9Pm8AMLy18v . Он никогда не получал его. Конечно, он был прав. Он никогда не отправлялся.

Я просмотрел все свои журналы и увидел, что мое программное обеспечение действительно отправляет транзакцию со следующими входами и выходами:

  • вход: 13P38hMYJXFdxDJJn8TtPUJZFXmcpf2J99
  • output # 1: 38MRMGjMBMp4k7vZhKLHhcM9Pm8AMLy18v (мой клиент)
  • output # 2: 1Ny3CV3rAsNMWpLfpxhXW3Fh71YmMEXXU7 (мой адрес изменения)

Все выглядело хорошо на моей стороне, но, конечно, когда я смотрел на блок-проводник, то, что я увидел, потрясло меня. Рассмотрите транзакцию 9a138b14dcc8ae740073c06933ae04e3b08fe6be6ada0dc175e6484250dfe269 и посмотрите на входы и выходы:

Как вы можете видеть, мой ввод правильный. Мой адрес изменения XU7 также верен. Но посмотрите на адрес моего клиента. Он говорит 17fQRjEudTVgexE8aDfhGyzDFEqSnnJJCA (который я буду называть JCA) вместо ожидаемого 38MRMGjMBMp4k7vZhKLHhcM9Pm8AMLy18v (который я буду называть 18v). Какого черта? Адрес JCA не известен ни мне, ни моему клиенту. Это совершенно неизвестный адрес.

Ясно, что проблема в моем коде где-то, поэтому я углубляюсь, чтобы узнать, что сделало эту конкретную транзакцию уникальной. Я определил, что его желаемый адрес (18v) начинается с «3», тогда как каждая другая исходящая транзакция, которую я завершил за всю историю моего приложения, имеет целевые адреса, начинающиеся с «1».

Моя секундомерная мера заключалась в том, чтобы заставить пользователей указывать только адреса, начинающиеся с 1. Но это временное решение. Мне нужно выяснить, что я делаю неправильно.

Я обратился к Google. Исследования показали, что адреса, начинающиеся с «3», обычно являются адресами SegWit, где те, которые начинаются с «1», являются традиционными адресами старой школы. Я помню все разговоры о SegWit несколько месяцев назад, но я полагал, что это не повлияло на меня, и мои предыдущие транзакции по-прежнему будут правильно устранены. Очевидно, что это был плохой вызов с моей стороны, поэтому теперь я должен выяснить, что я сделал неправильно, и откуда появился фиктивный адрес JCA.

Вот где я застрял. Я думаю, что моя проблема может быть связана с несжатыми и сжатыми открытыми ключами, основанными на обсуждениях здесь и здесь :

Вот что я знаю: когда приходит время создавать выходные данные для моей транзакции, мой код делает следующее (объяснение следует):

Func<String,byte[]> makeOutScript=(btcAddrHex)=>{
    byte[] addrBytes=BTCUtils.Base58Decode(btcAddrHex);
    byte[] pubKeyBytes=addrBytes.Take(addrBytes.Length-4).Skip(1).ToArray();

    using(MemoryStream ms=new MemoryStream()){
        using(BinaryWriter bw=new BinaryWriter(ms)){
            bw.Write((byte)0x76);   //op_dup
            bw.Write((byte)0xa9);   //op_hash160
            bw.Write((byte)20);     //size of public key
            bw.Write(pubKeyBytes);  //public key
            bw.Write((byte)0x88);   //op_equalverify
            bw.Write((byte)0xac);   //op_checksig

            bw.Flush();
            return ms.ToArray();
        }
    }
};

Что я делаю здесь: Преобразование выходного адреса в байты с помощью Base58, декодирующего его. Я снимаю последние четыре байта, которые являются контрольной суммой, и первый байт, который всегда равен 0x00 (видимо, индикатор сети какой-то). Это оставляет меня открытым открытым ключом. Фактически, глядя на мои внутренние заметки, на самом деле это не открытый ключ, а 0x04, добавленный к открытому ключу, а затем переданный через SHA256, а затем RIPEMD160. Но я буду ссылаться на этот blob как открытый ключ. После этого последовательность байтов равна 0x76, 0xa9, 20 (размер открытого ключа blob), сам открытый ключ blob, 0x88 и 0xac.

Я не претендую на то, чтобы понять, что означают все эти однобайтовые записи, но сообщения в блоге выше упомянутых включают их, так что это то, что я сделал, и до сих пор это работало нормально.

Возникает вопрос: как на самом деле адрес 18v трансформировался в адрес JCA, когда он былпредставленный в сеть? Может быть, мой «размер открытого ключа» (20) неверен? На уровне кишки кажется странным, что сжатый (18v) и несжатый (JCA) открытый ключ будут иметь постоянный размер 20. Но, возможно, я нахожусь на неправильном пути целиком, и сжатие не имеет к этому никакого отношения.

Мне не нужно рассказывать, насколько это было бы ужасно, если бы эта транзакция была 100 BTC вместо 0.0284377. Мне повезло, что моя ошибка была вызвана транзакцией с низким значением. Но я действительно хочу это решить. Можете ли вы указать мне правильный путь?

33 голоса | спросил Festus Martingale 1 FriEurope/Moscow2017-12-01T20:24:17+03:00Europe/Moscow12bEurope/MoscowFri, 01 Dec 2017 20:24:17 +0300 2017, 20:24:17

1 ответ


41

Адреса, начинающиеся с 3..., адреса P2SH , и они уже почти 6 лет. Исторически они видели ограниченное использование - использовались мультисигматические кошельки, такие как Copay и GreenAddress. Однако режим совместимости SegWit также использует P2SH, что делает их использование более распространенным в последнее время.

Основная проблема заключается в том, что ваш код для преобразования адреса в scriptPubKey не смотрит на байт версии. После того, как Base58 декодирует адрес, вы должны получить <1-байтовую версию> <20-байтовый хэш> <4-байтная контрольная сумма> состав. Если эта версия равна 0, что соответствует адресам 1..., этот хеш является хэшем открытого ключа, и вы правильно строите соответствующий scriptPubKey.

Однако, если этот номер версии равен 5, это адрес P2SH, который был указан в BIP13 , в котором используется соответствующий scriptPubKey, указанный в BIP16 .

В то время как SegWit изначально просто использовал старый формат P2SH, для SegWit также вводится новый формат адреса (который дает несколько более высокую производительность, гибкость и возможности обнаружения ошибок), описанный в BIP173 . (отказ от ответственности: я автор BIP173). Эти адреса начинаются с bc1....

Советую вам прекратить обслуживание и, по крайней мере, выполнить проверку того, что ожидается номер версии, вместе с набором тестов, который вы можете найти во многих реализациях. Если вам это нравится, вы также можете реализовать адреса BIP13 и BIP173, которые, скорее всего, станут более распространенными в ближайшее время.

ответил Pieter Wuille 1 FriEurope/Moscow2017-12-01T21:34:56+03:00Europe/Moscow12bEurope/MoscowFri, 01 Dec 2017 21:34:56 +0300 2017, 21:34:56

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132