Author

Topic: bittranshelperawk (Read 2237 times)

full member
Activity: 216
Merit: 100
February 13, 2014, 12:35:35 AM
#15
Написание скрипта несколько застопорилось, а вот генерация идей нет, поэтому выложу своё ви́дение дальнейшей разработки здесь, чтобы не пропало даром. Итак, sighash.

Beginners.
Что такое sighash? Это некий маркер, который позволяет устанавливать, что именно подписывает данная подпись, и в определённых случаях комбинировать выходы и входы вместе с подписями, собирая их из разных транзакций.
Принимает следующие значения:
SIGHASH_ALL (значение по умолчанию; в подавляющем большинстве случаев мы имеем дело именно с ним): подписываются все входы и все выходы.
SIGHASH_SINGLE: подписываются все входы и только один выход, чей номер равен номеру подписываемого входа. Означает, что «я готов потратить свои средства, если остальные потратят такие-то средства (на таких-то непотраченных выходах) и на выход, соответствующий моему входу, придёт столько-то монет; куда будут потрачены остальные монеты, мне безразлично».
SIGHASH_NONE: подписываются только входы (все). Означает, что «я готов потратить свои средства, если остальные потратят такие-то средства, и мне всё равно, куда пойдут монеты».
Все три вышеперечисленных значения можно комбинировать со значением SIGHASH_ANYONECANPAY, которое означает «из всех входов я подписываю только свой вход, остальные могут добавлять или изменять другие входы при желании». Естественно, на подпись выходов данный флаг не влияет.

Intermediate.
sighash — это маркер, относящийся именно к подписи, а не ко входу. Это означает, что несколько подписей, тратящих средства с мультисигнатурного адреса и относящихся к одному и тому же входу, в общем случае могут иметь разные sighash.
Для ручного формирования подписи с недефолтным sighash можно использовать команду «bitcoind signrawtransaction», 4-й параметр (sighashtype) отвечает именно за него.

Advanced.
Для подписывания каждого входа создаётся отдельная упрощённая транзакция, хэш которой, собственно, и подписывается. В эту упрощённую транзакцию попадают те или иные поля из реальной транзакции в зависимости от sighash. Остальные поля либо заменяются пустышками (или некоторыми условно-посторонними значениями), либо вообще выбрасываются.
Распишу покрытие подписи для каждого значения sighash. Допустим, у нас для каждой транзакции имеются две таблицы, одна для входов, другая для выходов. У таблицы выходов одно поле — «value/scriptPubKey» (в реальной таблице их следует разделять, но подписываются они всегда вместе, поэтому для упрощения объяснения буду считать их единым целым). Далее по тексту буду называть это поле просто «output». А вот у таблицы входов 3 поля: «pos», «txid/vout» (тоже единое; в дальнейшем «prevout») и «nsequence». «Pos» — это номер входа в транзакции; поле виртуальное, в самой транзакции в явном виде не присутствует, однако в некоторых случаях «подписывается» (влияет на подпись), а в некоторых нет. Для единообразия буду считать, что значения «pos» идут с нуля.
Есть ещё 2 подписываемых поля, общих для всей транзакции: «version» (версия транзакции, пока только 1) и «nLockTime». Они входят в покрытие при всех вариантах sighash (за одним исключением, являющимся багом протокола), дальше по тексту на этих полях заострять внимание не буду.
Теперь собственно варианты sighash.
ALL: в покрытие входят все «prevout» (вместе со всеми «pos»), все «nsequence» и все «output». Подписывание всех входов («prevout») автоматически означает, что подписываются и все «pos», т.е. порядок следования входов в такой транзакции менять нельзя. В противном случае изменится хэш упрощённой транзакции, и подпись станет некорректной.
У ALL самое строгое покрытие.
ALL|ANYONECANPAY: только свой «prevout», свой «nsequence» и все «output». «Pos» не входит в покрытие (т.е., если у вас все подписи с таким sighash, то входы — но не выходы — можно будет произвольно перетасовывать).
SINGLE: все «prevout» (+ все «pos»), только свой «nsequence» и только «свой» «output» (тот, чей номер равен номеру своего входа). Обратите внимание — «nsequence» подписывается тоже только один.
SINGLE|ANYONECANPAY: только свой «prevout», свой «nsequence», свой «output» и свой «pos». Последнее кажется странным, учитывая то, что более, казалось бы, строгое значение ALL|ANYONECANPAY не включает «pos» в покрытие, однако этот факт следует из алгоритма формирования упрощённой транзакции для SINGLE, в которую включаются pos+1 выходов, из них pos пустышек и только свой соответствует реальному. Т.е. скомбинировать две транзакции, каждая из которых состоит из одного входа и одного выхода и подписана SINGLE|ANYONECANPAY, в одну не получится — каждый вход должен сохранять свой номер. Зачем было выбрано такое решение — не знаю; если это была попытка защиты от Transaction Malleability, то, как видно, она не удалась (по другим причинам).
Ещё раз напомню про связанный с SINGLE баг в протоколе, когда у входа нет выхода с таким же номером (число выходов меньше числа входов).
NONE: в покрытие входят все «prevout» (+ все «pos») и только свой «nsequence» (аналогично SINGLE). Выходы не подписываются.
NONE|ANYONECANPAY: свой «prevout» и свой «nsequence». Самое либеральное покрытие, если не считать вышеупомянутого бага.

Теперь касательно реализации. Для скрипта список входов (строк, начинающихся с "i") и выходов ("o") будет считаться эталоном (заготовкой финальной транзакции); nLockTime из строки, начинающейся с "l", тоже будет считаться эталонным (но nSequence из той же строки — лишь рекомендательным). Далее, будут добавлены строки, начинающиеся, скажем, с "p", после которых идут транзакции-поставщики подписей. Примерно как "t", только для "t"-транзакций будет обязательным совпадение всех входов и выходов с эталоном, а для "p" нет.
Сам процесс: отбрасываю те транзакции, у которых nLockTime не соответствует эталонному. Из всех оставшихся транзакций (и "p", и "t") вырезаю подходящие подписи, проверяя для каждой из них соответствие их покрытия эталону. После этого шага для некоторых входов теоретически возможно появление нескольких конкурирующих подписей-кандидатов из разных транзакций (я сейчас про обычные, не только про мультисигнатурные адреса). Подбираю значения nSequence для всех входов (возможно, разные) так, чтобы корректным осталось максимальное количество подписей (конкурирующие подписи считая за одну), записываю эти подписи в транзакцию и тогда уже передаю получившийся результат команде «bitcoind signrawtransaction».

P.S. Добавил предупреждение в use cases в связи с текущей атакой.
full member
Activity: 216
Merit: 100
February 03, 2014, 08:14:58 PM
#14
Добавил ещё один use case (https://bitcointalksearch.org/topic/m.4825322) — множественные покупки с централизованным посредником.
full member
Activity: 216
Merit: 100
February 03, 2014, 11:01:06 AM
#13
Если инпутов N - то надо сериализовать данные из транзакции, подставляя на место всех scriptSig (пункты 5,6) либо scriptPubKey, либо "пустышку". Очевидно, данные для хеширования получатся разные.
Да, действительно. Расследование показало, что для каждого инпута создаётся отдельная «упрощённая» транзакция
script.cpp:
Code:
uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType)
{
    ...
    // Wrapper to serialize only the necessary parts of the transaction being signed
    CTransactionSignatureSerializer txTmp(txTo, scriptCode, nIn, nHashType);

    // Serialize and hash
    CHashWriter ss(SER_GETHASH, 0);
    ss << txTmp << nHashType;
    return ss.GetHash();
}
Здесь txTmp — упрощённая транзакция (без п.13), ss — полная упрощённая транзакция (с п.13), параметр scriptCode — это fromPubKey (как в п.6), параметр nIn — номер входа, для которого эта транзакция формируется. Помимо самих данных к упрощённой версии дописывается nHashType («ss << txTmp << nHashType», п.13); этот «hash code type» имеет самое прямое отношение к sighash, но то, что он один на всю транзакцию — не проблема, т.к. для каждого входа создаётся отдельный вариант.
Далее, тоже script.cpp:
Code:
class CTransactionSignatureSerializer {
    ...
    /** Serialize txTo */
    template
    void Serialize(S &s, int nType, int nVersion) const {
        // Serialize nVersion
        ::Serialize(s, txTo.nVersion, nType, nVersion);
        // Serialize vin
        unsigned int nInputs = fAnyoneCanPay ? 1 : txTo.vin.size();
        ::WriteCompactSize(s, nInputs);
        for (unsigned int nInput = 0; nInput < nInputs; nInput++)
             SerializeInput(s, nInput, nType, nVersion);
        // Serialize vout
        unsigned int nOutputs = fHashNone ? 0 : (fHashSingle ? nIn+1 : txTo.vout.size());
        ::WriteCompactSize(s, nOutputs);
        for (unsigned int nOutput = 0; nOutput < nOutputs; nOutput++)
             SerializeOutput(s, nOutput, nType, nVersion);
        // Serialie nLockTime
        ::Serialize(s, txTo.nLockTime, nType, nVersion);
    }
};
Для SIGHASH_ANYONECANPAY (fAnyoneCanPay) сериализуем (записываем в транзакцию) только один вход, в противном случае все входы. Ещё можно обратить внимание, сколько сериализуется выходов в зависимости от SIGHASH («unsigned int nOutputs = fHashNone ? 0 : (fHashSingle ? nIn+1 : txTo.vout.size());»).
Теперь входы (файл тот же):
Code:
class CTransactionSignatureSerializer {
    ...
    /** Serialize an input of txTo */
    template
    void SerializeInput(S &s, unsigned int nInput, int nType, int nVersion) const {
        // In case of SIGHASH_ANYONECANPAY, only the input being signed is serialized
        if (fAnyoneCanPay)
            nInput = nIn;
        // Serialize the prevout
        ::Serialize(s, txTo.vin[nInput].prevout, nType, nVersion);
        // Serialize the script
        if (nInput != nIn)
            // Blank out other inputs' signatures
            ::Serialize(s, CScript(), nType, nVersion);
        else
            SerializeScriptCode(s, nType, nVersion);
        // Serialize the nSequence
        if (nInput != nIn && (fHashSingle || fHashNone))
            // let the others update at will
            ::Serialize(s, (int)0, nType, nVersion);
        else
            ::Serialize(s, txTo.vin[nInput].nSequence, nType, nVersion);
    }
    ...
};
Здесь стоит обратить внимание на строку «if (nInput != nIn)» — для того выхода, для которого мы составляем упрощённую транзакцию, записываем scriptPubKey («SerializeScriptCode(s, nType, nVersion);»), для остальных — пустышку («::Serialize(s, CScript(), nType, nVersion);»).

P.S. В процессе расследования наткнулся на замечательное:
Code:
        fAnyoneCanPay(!!(nHashTypeIn & SIGHASH_ANYONECANPAY)),
Оно, конечно, понятно, что это для преобразования одного из вариантов {0, SIGHASH_ANYONECANPAY} в {0, 1} соответственно, но выглядит красиво Smiley
legendary
Activity: 1260
Merit: 1019
February 03, 2014, 12:14:39 AM
#12
В общем, я вроде победил эту проблему. Надо еще тестировать и причесать код, но в целом получилось вот что:
Если инпутов N - то надо сериализовать данные из транзакции, подставляя на место всех scriptSig (пункты 5,6) либо scriptPubKey, либо "пустышку". Очевидно, данные для хеширования получатся разные.
Я все время был "около правильного кода", просто в цикле перепутал i и j Smiley
full member
Activity: 216
Merit: 100
February 02, 2014, 04:45:59 PM
#11
A hash of the simplified transaction consisted of a SINGLE INPUT and ALL outputs is created, this is then signed by the private key for that input and stored in the "sig & pubkey" portion of that tx input. 
У меня есть подозрения, что это ни разу не так. Т.е. это вполне адекватный алгоритм для SIGHASH_ANYONECANPAY, но не для дефолтного SIGHASH_ALL, при котором, насколько я понял, должны подписываться не только все выходы, но и все входы.
Я доберусь-таки завтра до кода Smiley
Тогда какую цифру ставить в пункте 9? Допустим, на каждом инпуте по 1000 сатоши, а всего 10 инпутов. Ставим 10к или 1к?
Пункт 9 — это про выходы, во входах amount нигде не указывается. Так что 10к, если он у нас один. Впрочем, тут стоит иметь в виду, что если это вся транзакция, то для её принятия сетью потребуется указать комиссию, минимально допустимое значение которой в нашем случае будет как раз 10к Smiley В общем, стоит добавить инпут покрупнее.
legendary
Activity: 1260
Merit: 1019
February 02, 2014, 02:11:45 PM
#10
Что-то не получается.
Вот тут https://bitcointalksearch.org/topic/simple-transaction-signing-question-422021 написано, что надо формировать "simplified version" для каждого инпута, то есть хеши таки будут разными. В принципе логично, что инпуты подписываются независимо друг от друга и от порядка следования. Тогда какую цифру ставить в пункте 9? Допустим, на каждом инпуте по 1000 сатоши, а всего 10 инпутов. Ставим 10к или 1к?
В общем, буду благодарен за любую подсказку или совет.

full member
Activity: 216
Merit: 100
February 02, 2014, 10:05:23 AM
#9
Остался вопрос - как надо модифицировать алгоритм, чтобы подписывать несколько инпутов?
Вообще я не реализовывал самостоятельное подписывание транзакций, а лишь делегировал это команде «bitcoind signrawtransaction», но, судя по описанию, нужно просто повторить шаги 3-7 по количеству инпутов, а затем подписывать каждый инпут, повторяя шаги 15-18 (хэш из шага 14 не изменяется). Правда, что такое «hash code type» из шагов 13 и 19 (имеет ли он какое-нибудь отношение к sighash, и если имеет, то почему он один на всю транзакцию), я пока не понял. Завтра попробую код официального клиента почитать.
Да, на всякий случай могу порекомендовать почитать это, особенно разделы «Procedure for Hashtype SIGHASH_*», вдруг что прояснится.
legendary
Activity: 1260
Merit: 1019
February 02, 2014, 09:00:07 AM
#8
Quote
Скрипт ничего не отправляет в сеть — только формирует и подписывает транзакцию.

Слушай, а можно тебя вопросами помучать?
А то что-то ни на английском, ни тем более на русском не найду.
Даже спросить не у кого.
Я вот долго бился с подписыванием транзакций самостоятельно.
В конце концов набрел на вот это описание
http://bitcoin.stackexchange.com/questions/3374/how-to-redeem-a-basic-tx
Всё получилось, хотя пришлось еще разобраться с вариантом когда используется сокращенный формат публичного ключа. (вроде разобрался)
Остался вопрос - как надо модифицировать алгоритм, чтобы подписывать несколько инпутов?
Да, на всякий случай поясню - мне надо подписать обычную транзакцию с несколькими инпутами, а не мультисиг.
full member
Activity: 216
Merit: 100
February 02, 2014, 06:59:08 AM
#7
Неужели это тот самый ожидаемый инструментарий для работы с multisig транзакциями?... потестирую на досуге.
Я старался Smiley Правда, есть ещё что исправлять — sighash пока никак не учитывается, ну и checkonly работает только с разблокированным кошельком (пароль нужно ввести), хотя по смыслу этого требоваться не должно. Надо себя заставить Smiley
Осторожно, надежность контейнеров LXC не на много выше обычного chroot, т.е. обладающий root доступом в одной гостевой системе спокойно вылезает из песочницы в хост систему и соответственно в остальные гостевые.
Да, я в курсе, но root-доступ кошельку и не нужен. Впрочем, лично я LXC не использую — это так, умозаключения.

UPDATE. checkonly исправил, пароль больше не нужен.
legendary
Activity: 1120
Merit: 1069
February 02, 2014, 05:33:57 AM
#6
Неужели это тот самый ожидаемый инструментарий для работы с multisig транзакциями?... потестирую на досуге.
Впрочем, для решения проблемы из предыдущего правила, думаю, будет достаточно Linux Containers (LXC); скорее всего, эмуляции rdtsc там нет и качество haveged-энтропии в виртуалке не ухудшится.
Осторожно, надежность контейнеров LXC не на много выше обычного chroot, т.е. обладающий root доступом в одной гостевой системе спокойно вылезает из песочницы в хост систему и соответственно в остальные гостевые.
full member
Activity: 216
Merit: 100
January 30, 2014, 08:11:13 AM
#5
Публичным ключом транзакцию не подписать. Для подписи нужен именно приватный ключ
Само собой. Я имею в виду, чтобы клиент сам извлекал приватный ключ, соответствующий этому публичному ключу или адресу, если этот пубкей/адрес в кошельке есть. Примерно как createmultisig поступает:
Для создания мультисигнатурного адреса (bitcoind createmultisig) требуются публичные ключи (в справке официального клиента сказано, что можно указывать адреса или публичные ключи, на самом деле при указании адреса клиент извлекает соответствующий публичный ключ из кошелька, если он там был — иначе генерирует ошибку).
member
Activity: 229
Merit: 13
January 30, 2014, 04:46:58 AM
#4
Quote
Надо будет попробовать предложить разработчикам официального клиента патч, позволяющий указывать адреса или публичные ключи вместо приватных.
Публичным ключом транзакцию не подписать. Для подписи нужен именно приватный ключ
full member
Activity: 216
Merit: 100
January 29, 2014, 04:26:00 PM
#3
Use cases.
Навеяно этой статьёй.
В данном посте мультисигнатурные адреса буду обозначать так: {A1,...,An}m или просто {A1,...,An} если n == m.

1. Наследство/резервный вывод.
Создаём и подписываем транзакцию
Code:
TxR[0]: A1,...,An → R; TxR[0].nlocktime устанавливаем, допустим, на год вперёд
Транзакцию передаём получателю сразу. При приближении текущего времени к TxR0.nlocktime переводим средства на любой свой адрес (хоть на тот же самый):
Code:
TxT[1]: A1,...,An → A
и выпускаем новую резервную транзакцию
Code:
TxR[1]: (id(TxT[1]),0)A → R; TxR[1].nlocktime = currtime + delay
Для последующих переводов:
Code:
TxT[i]: (id(TxT[i-1]),0)A → A
TxR[i]: (id(TxT[i]),0)A → R; TxR[i].nlocktime = currtime + delay
Т.к. идентификатором непотраченных средств является (txid,vout), а не адрес, то текущая транзакция TxR[ i ] в момент очередного перевода средств становится недействительной, и после каждого перевода нужно выпускать новую резервную транзакцию.
Получатель не сможет задействовать резервную транзакцию до времени разблокирования; нам остаётся лишь следить за тем, чтобы не пропускать моменты TxR[ i ].nlocktime.

2. Хранение биткоинов на разных адресах (принадлежащих разным кошелькам) одновременно.
Code:
Tx1: S1,...,Sn → {A1,A2}
A1 и A2 находятся на разных кошельках и на разных компьютерах. Для кражи этих средств злоумышленнику потребуется взломать оба.
При необходимости можно выпустить резервную транзакцию к Tx1
Code:
TxR: (id(Tx1),0){A1,A2} → F; TxR.nlocktime = currtime + delay
Например, во избежание потери средств при поломке/краже одного из устройств и отсутствии бэкапов (что не отменяет необходимости бэкапить ключ адреса F). Или если вы опасаетесь, что не сможете при первом знакомстве с программой правильно перевести средства на мультисигнатурный адрес и боитесь их потерять — в этом случае не публикуйте Tx1 сразу, а сначала выпустите TxR и убедитесь в её корректности.

ВНИМАНИЕ: в связи со свежевсплывшей уязвимостью bitcoin-протокола не гарантируется стопроцентная безопасность для покупателя последующих схем (с транзакцией возврата).

3. Покупка/обмен по предварительной договорённости без ожидания подтверждений и без доступа в интернет.
As1,...,Asn (start), Ap (participant), Af (final) — адреса покупателя, в частном случае могут быть одинаковыми; Bp, Bf — то же самое для продавца. TxR — reserve transaction, TxP — payment transaction, Tx1 — транзакция пополнения баланса.
Покупатель создаёт и подписывает транзакцию
Code:
Tx1: As1 & ... & Asn → {Ap,Bp}
но пока не публикует её. Также покупатель создаёт резервную транзакцию возврата
Code:
TxR: (id(Tx1),0){Ap,Bp} → Af; TxR.nlocktime = currtime + delay
(delay может быть равным, допустим, неделе)
Подписывает своим ключом транзакцию TxR (она подписывается лишь наполовину, нужна ещё подпись продавца). Передаёт TxR_part продавцу. Продавец проверяет TxR_part, доподписывает её и возвращает полностью подписанную TxR покупателю. Покупатель проверяет TxR и в случае её корректности публикует Tx1.
На данный момент ситуация такова, что покупатель свои средства не теряет — даже если продавец исчезнет, покупатель сможет получить свои деньги обратно после TxR.nlocktime.
Одновременно с подписью TxR_part продавец формирует транзакцию оплаты
Code:
TxP: (id(Tx1),0){Ap,Bp} → Bf
подписывает свою часть и передаёт TxP_part вместе с TxR покупателю. Покупатель проверяет TxP_part, доподписывает её своим ключом и, не публикуя TxP, едет за покупкой/обменом. В момент совершения сделки покупатель просто передаёт TxP продавцу. Продавец проверяет TxP и публикует её (ему необязательно публиковать её сразу, это лишь нужно сделать до TxR.nlocktime).
Double spend с адреса {Ap,Bp} (точнее, с (id(Tx1),0)) невозможен, т.к. для него требуется подпись ключами и продавца, и покупателя. Поэтому, а также из-за того, что TxR временно заблокирована, публикование TxP можно отложить, а ожидание подтверждений вообще отбросить. Теоретически возможен лишь double spend с адресов As1...Asn вместо транзакции Tx1, но предполагается, что от момента предварительной договорённости (включения Tx1 в блокчейн) до момента покупки прошло достаточно много подтверждений.

4. Модификация предыдущей схемы для множественных покупок/обмена по частям без отправки промежуточных транзакций в блокчейн.
{Ap,Bp} теперь рассматриваем как некий счёт покупателя в магазине (у продавца, в обменнике).
Точно так же генерируем, подписываем, обмениваемся и публикуем Tx1 и TxR, как в предыдущем варианте (только, возможно, стоит отодвинуть TxR.nlocktime на более дальний срок, особенно если планируется длительное сотрудничество). Но теперь продавец запрашивает трату лишь части средств покупателя транзакцией
Code:
TxP[1]: (id(Tx1),0){Ap,Bp} → (payment[1])Bf & (change[1])Af; TxP[1].nlocktime = TxR.nlocktime - delta[1]
Можно использовать, например, delta == 3 часа.
Продавец отправляет TxP[1]_part, покупатель получает её, проверяет, доподписывает и отправляет TxP[1] продавцу.
Для новых покупок/траншей:
Code:
TxP[i]: (id(Tx1),0){Ap,Bp} → (cumulated_payment[i])Bf & (change[i])Af; TxP[i].nlocktime = TxP[i-1].nlocktime - delta[i]
Аналогично для последующих. Обратите внимание, что ни одна из вышеперечисленных транзакций кроме Tx1 в блокчейне не публикуется.
На данном этапе средства обоих участников «виртуальны» (их ещё нельзя потратить ни продавцу, ни покупателю). Однако, если сейчас сотрудничество прекратится, то продавец опубликует TxP[last] в момент времени TxP[last].nlocktime, и каждый участник получит свою долю в единоличное распоряжение.  Единственный момент — продавцу следует публиковать транзакцию TxP[last] сразу после наступления TxP[last].nlocktime, иначе через некоторое время у покупателя появится возможность опубликовать предыдущую транзакцию TxP[last-1] или даже TxP[last-k], которые отменят оплату k последних покупок.
В случае, если нужно снять средства, то очередную транзакцию оплаты TxP[ i ] выпускаем без nlocktime и публикуем её в блокчейне.
Для пополнения баланса можно просто перевести сумму пополнения на {Ap,Bp}
Code:
Tx2: An+1,...,An+m → {Ap,Bp}
Tx2 также стоит публиковать только после выпуска TxR2
Code:
TxR2: (id(Tx2),0){Ap,Bp} → Af; TxR2.nlocktime = TxR.nlocktime
Для последующих покупок:
Code:
TxP[i]: (id(Tx1),0){Ap,Bp} & (id(Tx2),0){Ap,Bp} → (cumulated_payment[i])Bf & (change[i])Af;
TxP[i].nlocktime = TxP[i-1].nlocktime - delta[i]

5. Модификация предыдущей схемы для случая, когда возможно движение средств в обе стороны.
- средства вносит один участник («покупатель»):
Действия те же, что и в предыдущей схеме, только подписывает TxP[ i ] первым тот, кто в данный момент собирается принимать биткоины. Ну и следить за приближением currtime к TxP[ i ].nlocktime теперь должны оба.
- средства вносят оба равноправных участника:
То же, что и в предыдущем варианте. Единственный нюанс — теперь возникает новая транзакция Tx2, переводящая средства от участника B на общий адрес. Для этой транзакции участником A (который на данной стадии играет роль продавца) подписывается транзакция возврата TxRB, TxRB.nlocktime = TxRA.nlocktime. Ну и теперь все TxP тратят средства с выходов как Tx1, так и Tx2 (аналогично варианту с пополнением баланса):
Code:
TxP[i]: (id(Tx1),0){Ap,Bp} & (id(Tx2),0){Ap,Bp} → (shareB[i])Bf & (shareA[i])Af;
TxP[i].nlocktime = TxP[i-1].nlocktime - delta[i]

Для гарантированного получения своих средств назад обоим участникам необходимо хранить лишь последнюю TxP, а также все TxR, выпущенные после последней TxP (после очередной TxP, включающей соответствующие входы, все TxR становятся неактуальными).

Предупреждение: для каждого совместного счёта следует использовать отдельный адрес-участник. Если используется один адрес (Bp), возможна такая ситуация:
- участник A говорит, что Ap принадлежит ему, генерирует и подписывает TxA, просит магазин подписать
Code:
TxRA_part: (id(TxA),0){Ap,Bp} → Af; TxRA.nlocktime = currtime + delay
и публикует TxA;
- участник E говорит, что Ep == Ap принадлежит ему, якобы генерирует и якобы подписывает TxE == TxA, просит магазин подписать
Code:
TxRE_part: (id(TxA),0){Ap,Bp} → Ef; TxRE.nlocktime произвольное
(желательно TxRE.nlocktime < TxRA.nlocktime, зависит от разрешения магазина выбирать delay самостоятельно), а затем получает средства после TxRE.nlocktime (если до этого момента в блокчейне не будет опубликована TxRA или TxP[ i ]).
Если же адреса BAp и BEp будут разными, то для кражи злоумышленнику придётся подбирать Eup такой, чтобы {Eup,BEp} == {Ap,BAp} (т.е. чтобы хэши их redeemScript'ов совпадали), что на данный момент практически нереально.

6. Модификация п.4 для множественных покупок/обмена с участием централизованного посредника.
C — адреса посредника.
Каждая из сторон (покупатели и продавцы) независимо друг от друга и в разное время заключают соглашения с посредником, перечисляют средства на совместные с ним адреса и генерируют транзакции возврата. Покупатель совершает покупку, продавец формирует транзакцию оплаты (перевод части средств с совместного с посредником счёта)
Code:
TxPB[i]: (id(Tx1B),0){Bp,CBp} → (cumulated_paymentB[i])Bf & (changeB[i])CBf;
TxPB[i].nlocktime = TxPB[i-1].nlocktime - deltaB[i]
подписывает её своим ключом и отправляет TxPB_part[ i ] посреднику, дополнительно указав, какому покупателю отправить счёт. Посредник принимает её, генерирует транзакцию оплаты для покупателя
Code:
TxPA[j]: (id(Tx1A),0){Ap,CAp} → (cumulated_paymentA[j])CAf & (changeA[j])Af;
TxPA[j].nlocktime = TxPA[j-1].nlocktime - deltaA[j]
подписывает её своим ключом и отправляет TxPA_part[j] покупателю. Вместе с этим посредник формирует документ, в котором указаны TxPB_part[ i ] и TxPA_part[j], подписывает его (например, с помощью PGP) и отправляет продавцу. Этот документ в дальнейшем будет являться доказательной базой, если посредник вздумает принять оплату от покупателя, но не вернуть эту оплату продавцу.
Покупатель проверяет TxPA_part[j] (в т.ч. её соответствие тому, что получил продавец в документе), доподписывает её и возвращает TxPA[j] посреднику. Посредник, проверив TxPA[j], доподписывает TxPB[ i ] и в свою очередь передаёт последнюю продавцу.
Риски, вносимые централизованным посредником:
- блокировка средств. Возможна лишь временная, т.к. у всех подписавших договор есть транзакции возврата.
- приём оплаты от покупателя (TxPA) и отсутствие передачи связанной TxPB продавцу. В этом случае покупатель передаёт полностью подписанную транзакцию TxPA продавцу, а тот выкладывает в общем доступе претензию (где указаны TxPA и подписанный документ с TxPB_part и TxPA_part) с требованием выпустить TxPB. В этой ситуации посредник либо уступает требованию, либо теряет репутацию, и клиенты переходят к другим посредникам. Если же сумма оплаты столь велика, что её потеря будет чувствительна для покупателя, то можно либо переводить её частями, либо перечислить напрямую от покупателя продавцу без посредников (необходимость ожидания подтверждений — меньшее зло в данном случае).

Пока всё.
full member
Activity: 216
Merit: 100
January 29, 2014, 04:18:30 PM
#2
Формат файла описания транзакции.
Формат строк для входов транзакции:
Quote
i             [- [(-|)     []]]
txid/vout (обязательные поля): «координаты» «монеты» — id транзакции и номер выхода в ней для соответствующего txout, который мы собираемся потратить);
- (необязательное поле): зарезервировано для sighash;
amount (необязательное поле): точное количество биткоинов в данном ещё не потраченном txout (можно опустить, указав «-»);
detailed address (необязательное поле): адрес, на котором лежит данный ещё не потраченный txout (формат см. далее).

Формат строк для выходов:
Quote
o       (-|) []
value (обязательное поле): сумма, переводимая на ; если указан «-», то на будет переведена сдача (если минусов несколько, сдача будет поделена поровну);
detailed address: адрес для перевода; отсутствующий адрес обозначает комиссию (т.е., комиссию в данном скрипте нужно указывать явно, по умолчанию она нулевая).

Формат строк для уточнения адресов:
Quote
a       

Формат строк для nlocktime:
Quote
l              
sequence number: номер варианта (присваивается каждому входу), обычно 0 — замена транзакций на данный момент сетью не поддерживается, заблокированные по времени транзакции просто не принимаются до наступления времени разблокировки;
locktime: время разблокирования транзакции в формате, воспринимаемом программой date (указывается без кавычек).

Формат строк для (частично) подписанных транзакций:
Quote
t       
Используется при передаче между отдельными подписантами (например, адрес входа является мультисигнатурным 2-из-2, один ключ лежит на одном компьютере, второй на другом).

Формат строк для действий:
Quote
checkonly
Только проверка транзакции без её подписания (проверяемая транзакция в этом случае указывается в строке «t      »).

Формат строк комментариев:
Quote
[#;-] Произвольный текст

Вывод скрипта (на stdout):
Quote
(false|)
false — в случае, если транзакция подписана не полностью; — для полностью подписанной транзакции.
Возможна также выдача предупреждений на stderr.

Формат поля :
Quote
(-       
[]|     ...)
Первый вариант — для простых адресов (pay-to-pubkey-hash) или для мультисигнатурных в явном виде, второй — для мультисигнатурных m-of-n (pay-to-script-hash). На данный момент n не может быть больше 3 (ограничение биткоин-сети).
Для создания мультисигнатурного адреса (bitcoind createmultisig) требуются публичные ключи (в справке официального клиента сказано, что можно указывать адреса или публичные ключи, на самом деле при указании адреса клиент извлекает соответствующий публичный ключ из кошелька, если он там был — иначе генерирует ошибку). Поэтому адрес, например, 2-из-3 стоит расписывать так:
Quote
...     2                 
a       -            
a       -            
a       -            
При описании мультисигнатурного адреса можно вместо адресов участников указывать их публичные ключи (а также смешивать ключи с адресами). Впрочем, на данный момент получение адреса из публичного ключа в скрипте не реализовано, поэтому каждый публичный ключ всё равно потребуется уточнять (как в 3 нижних строках последнего примера).
Предупреждение: не записывайте простые адреса в форме «1       
», данная запись будет воспринята как мультисигнатурный адрес 1-из-1.
Предупреждение: порядок следования адресов участников во множественном адресе имеет значение, например 2-of-{A,B} ≠ 2-of-{B,A}.

Уточнение адресов необходимо лишь для входов транзакции, для выходов оно необязательно. Т.е., разрешена такая запись транзакции:
Quote
i           -               -       3
o       -       -       3
a       2       1        1        1
a       -       1       
a       -       1       
a       -       1       
Обратите внимание, что участников мультисигнатурного адреса 3 мы не раскрываем (хотя, разумеется, это не запрещено).
Множественные адреса можно раскрывать непосредственно в строке входа/выхода транзакции (см. описание формата строк). Т.е., в вышеописанном примере первую и третью строки можно объединить в одну:
Quote
i           -               2       1        1        1

На момент генерации транзакции те транзакции, выходы которых мы собираемся тратить, не обязаны быть уже опубликованными (генерируемая транзакция становится «орфанной»). В этом случае требуется указание точной суммы и исходного адреса для каждого «орфанного» входа, в противном случае их можно опустить (соответствующая транзакция будет извлечена с помощью bitcoind getrawtransaction).

Для формирования списка непотраченных выходов («монет») можно воспользоваться командой
Quote
bitcoind-unspent.sh >> filename
после чего в filename удалить ненужные строки, соответствующие монетам, которые мы не собираемся тратить. Затем в каждой оставшейся строке убрать 2 последних поля (confirmations и account), например, командой
Quote
cat filename | sed 's/\t[[:digit:]]\+\t"[^"]*"$//' > new-filename
Внимание: непотраченные монеты на мультисигнатурных адресах в вашем клиенте, скорее всего, отображаться не будут (официальный клиент их в выводе listunspent на данный момент вроде не перечисляет, даже если вы явно добавили такой адрес командой addmultisigaddress). За этими монетами нужно следить самостоятельно.

to be continued...
full member
Activity: 216
Merit: 100
January 29, 2014, 04:14:01 PM
#1
Создал тут скрипт для упрощения переводов с/на мультисигнатурные адреса и для модификации nlocktime. Пока неполнофункциональный (планируются также вариации на тему sighash).
Просьба за парсинг транзакций сильно ногами не бить Smiley Он пока реализован через вызов bitcoind decoderawtransaction и парсинг JSON-ответа грубо-велосипедным способом, в дальнейшем планируется самостоятельный разбор hex-encoded транзакции.
Замена sequence number осуществляется простым текстовым поиском-заменой (а-ля sed 's/00ffffffff/00/'), поэтому теоретически возможны ложные срабатывания (с вероятностью в районе 1/2^40, около одной триллионной). Запилю самостоятельный разбор транзакции — исключу и эту вероятность.
Скрипт ничего не отправляет в сеть — только формирует и подписывает транзакцию. Перед её опубликованием (bitcoind sendrawtransaction) категорически рекомендуется её проверить (bitcoind decoderawtransaction).
Описания использования и use cases на английском пока нет (задача на ближайшее будущее), поэтому выложу здесь — см. следующие посты.

Инсталляция: Linux (*nix?) only. Просто скачайте скрипты *.awk, *.sh и установите их, например, в /usr/local/bin
Зависимости: bitcoind, awk, bc (+ sh, sed, date, имеющиеся практически в каждом Linux-дистрибутиве).
Использование:
Quote
bittransacthelperawk.awk filename
filename — файл с описанием генерируемой транзакции, формат файла см. ниже. Для упрощения формирования списка входов можно воспользоваться командой
Quote
bitcoind-unspent.sh >> filename

Правила безопасности:
1. Не оставляйте скрипты в домашнем каталоге после скачивания, перенесите их в /usr/local/bin (или, скажем, /opt/bin) и поменяйте владельца на root:root. В противном случае, если, допустим, в браузере обнаружится уязвимость, позволяющая злоумышленнику получить доступ к файловой системе (пусть и с правами простого пользователя, не root), то он сможет пропатчить скрипт, добавив туда перевод биткоинов с вашего кошелька на свой адрес.
2. Заведите отдельный пользовательский аккаунт только для работы кошелька (вместе с этим скриптом). Не запускайте браузер (и вообще любые другие программы) из этого аккаунта. Т.е., для торговли на бирже используйте другой аккаунт. Желательно тоже отдельный.
Следование этому правилу не отменяет необходимости предыдущего.
3. При подписи транзакции в скрипте используется (почти) полная форма команды signrawtransaction, т.е. приватные ключи передаются как параметры командной строки при вызове bitcoind. И, соответственно, при определённом везении могут быть перехвачены, например, командой «ps aux» (запущенной из-под любого пользователя — параметры вызова, как правило, в Linux не скрываются; впрочем, нужно точно подгадать момент — bitcoind signrawtransaction выполняется быстро). Обойти эту уязвимость можно выделением отдельной виртуальной системы только для кошелька со скриптом, однако см. следующее правило.
Надо будет попробовать предложить разработчикам официального клиента патч, позволяющий указывать адреса или публичные ключи вместо приватных.
4. Хотя при правильной настройке отдельная виртуальная система для кошелька будет безопаснее, чем просто отдельный пользовательский аккаунт, при неправильном подходе эта система будет более уязвима (вплоть до полной иллюзорности безопасности). Виртуальная система должна быть именно отдельной (т.е., ваша повседневная активность должна проходить в другой гостевой виртуальной системе, но не на самом хосте!), а хост-система должна иметь лишь самый минимум ПО (желательно ограничиться максимум роутингом, особые умельцы могут и его перенести в виртуалку с пробросом туда сетевого интерфейса, а хосту оставить только функциональность свитча). Также нужно тщательно изучить вопрос качества генерируемого рандома — например, haveged (генератор энтропии на базе нестабильности работы процессора) в некоторых виртуальных окружениях выдаёт предсказуемые данные, что может привести к тому, что ваши случайные приватные ключи будут не столь случайными.
Впрочем, для решения проблемы из предыдущего правила, думаю, будет достаточно Linux Containers (LXC); скорее всего, эмуляции rdtsc там нет и качество haveged-энтропии в виртуалке не ухудшится.

to be continued...
Jump to: