2016-09-20 21:28:03 +02:00
|
|
|
/*
|
|
|
|
|
* This file is part of the Bitcoin project
|
2024-01-09 22:06:45 +01:00
|
|
|
* Copyright (C) 2016-2024 Tom Zander <tomz@freedommail.ch>
|
2016-09-20 21:28:03 +02:00
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
#include "Transaction.h"
|
|
|
|
|
#include "StreamMethods.h"
|
|
|
|
|
|
|
|
|
|
#include <QFile>
|
|
|
|
|
#include <QDebug>
|
2024-10-06 19:42:04 +02:00
|
|
|
#include <QCryptographicHash>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
void printHex(const char *data, int index, QTextStream &out)
|
|
|
|
|
{
|
|
|
|
|
const unsigned char k = (unsigned char)(data[index]);
|
|
|
|
|
if (k < 16)
|
|
|
|
|
out << '0';
|
|
|
|
|
out << QString::number(k, 16);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-20 21:28:03 +02:00
|
|
|
|
|
|
|
|
Transaction::Transaction()
|
|
|
|
|
: m_version(-1),
|
|
|
|
|
m_nLockTime(0)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-03 15:26:54 +01:00
|
|
|
bool Transaction::read(const QString &filename)
|
2016-09-20 21:28:03 +02:00
|
|
|
{
|
2024-10-06 19:31:08 +02:00
|
|
|
qDebug() << "reading" << filename;
|
2016-09-20 21:28:03 +02:00
|
|
|
QFile in(filename);
|
|
|
|
|
if (!in.open(QIODevice::ReadOnly)) {
|
|
|
|
|
qWarning() << "Failed to open input" << filename;
|
2016-11-02 21:05:09 +01:00
|
|
|
return false;
|
2016-09-20 21:28:03 +02:00
|
|
|
}
|
|
|
|
|
QByteArray bytes = in.readAll();
|
|
|
|
|
in.close();
|
|
|
|
|
if (bytes.isEmpty()) {
|
|
|
|
|
qWarning() << "Empty input file";
|
2016-11-02 21:05:09 +01:00
|
|
|
return false;
|
2016-09-20 21:28:03 +02:00
|
|
|
}
|
2019-03-03 15:26:54 +01:00
|
|
|
return read(bytes);
|
2016-11-01 20:23:53 +01:00
|
|
|
}
|
2016-09-20 21:28:03 +02:00
|
|
|
|
2019-03-03 15:26:54 +01:00
|
|
|
bool Transaction::read(const QByteArray &bytes)
|
2016-11-01 20:23:53 +01:00
|
|
|
{
|
2024-10-06 19:31:08 +02:00
|
|
|
return parseTransaction(bytes);
|
2016-09-20 21:28:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Transaction::debug() const
|
|
|
|
|
{
|
|
|
|
|
QTextStream out(stdout);
|
2024-10-08 15:45:10 +02:00
|
|
|
out << "{\ninputs: [\n";
|
2016-09-20 21:28:03 +02:00
|
|
|
foreach (const TxIn &tx, m_inputs) {
|
2024-01-09 21:14:16 +01:00
|
|
|
out << " {\n txid: " << tx.transaction.toHex() << Qt::endl;
|
|
|
|
|
out << " vout: " << tx.prevIndex << Qt::endl;
|
2016-11-10 21:59:36 +01:00
|
|
|
if (tx.sequence & (1 << 31)) {
|
2024-01-09 21:14:16 +01:00
|
|
|
out << " sequence: " << QString::number(tx.sequence, 16) << Qt::endl;
|
2016-11-10 21:59:36 +01:00
|
|
|
} else {
|
|
|
|
|
if (tx.sequence & (1 << 22)) {
|
|
|
|
|
quint32 time = tx.sequence & 0xFFFF;
|
|
|
|
|
quint32 timeout = time * 512;
|
|
|
|
|
const int SECSPERHOUR = 3600;
|
|
|
|
|
const int SECSPERDAY = SECSPERHOUR * 24;
|
|
|
|
|
const int SECSPERWEEK = SECSPERDAY * 7;
|
|
|
|
|
out << " time-based-relative-locktime: " << QString::number(time, 16)
|
|
|
|
|
<< " (" << timeout << "sec / ";
|
|
|
|
|
|
|
|
|
|
if (timeout > SECSPERWEEK) {
|
|
|
|
|
out << (timeout / SECSPERWEEK) << " weeks ";
|
|
|
|
|
timeout -= (timeout / SECSPERWEEK) * SECSPERWEEK;
|
|
|
|
|
}
|
|
|
|
|
if (timeout > SECSPERDAY) {
|
|
|
|
|
out << (timeout / SECSPERDAY) << " days ";
|
|
|
|
|
timeout -= (timeout / SECSPERDAY) * SECSPERDAY;
|
|
|
|
|
}
|
|
|
|
|
if (timeout > SECSPERHOUR) {
|
|
|
|
|
out << (timeout / SECSPERHOUR) << " hours ";
|
|
|
|
|
timeout -= (timeout / SECSPERHOUR) * SECSPERHOUR;
|
|
|
|
|
}
|
|
|
|
|
if (timeout > 60) {
|
|
|
|
|
out << (timeout / 60) << " minutes ";
|
|
|
|
|
timeout -= (timeout / 60) * 60;
|
|
|
|
|
}
|
|
|
|
|
if (timeout > 0) {
|
|
|
|
|
out << timeout << " seconds";
|
|
|
|
|
}
|
|
|
|
|
out << ")\n";
|
|
|
|
|
} else if (tx.sequence != 0) {
|
2024-01-09 21:14:16 +01:00
|
|
|
out << " block-based-relative-locktime: " << QString::number(tx.sequence & 0xFFFF) << Qt::endl;
|
2016-11-10 21:59:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2016-09-20 21:28:03 +02:00
|
|
|
out << " script: ";
|
2019-03-03 15:26:54 +01:00
|
|
|
debugScript(tx.inputScript, 12, out);
|
2019-03-03 19:47:20 +01:00
|
|
|
checkInScript(tx.inputScript, 12, out);
|
2016-09-20 21:28:03 +02:00
|
|
|
out << " }\n";
|
|
|
|
|
}
|
|
|
|
|
out << "]\n";
|
|
|
|
|
out << "outputs: [\n";
|
|
|
|
|
foreach (const TxOut &tx, m_outputs) {
|
2024-01-09 21:14:16 +01:00
|
|
|
out << " {\n amount: " << tx.value << Qt::endl;
|
2016-09-20 21:28:03 +02:00
|
|
|
out << " script: ";
|
|
|
|
|
debugScript(tx.script, 12, out);
|
|
|
|
|
out << " }\n";
|
|
|
|
|
}
|
2024-10-06 19:42:04 +02:00
|
|
|
out << "]\nversion: " << m_version << "\nnLockTime: " << m_nLockTime << "\n";
|
|
|
|
|
out << "txid: ";
|
|
|
|
|
for (int i = 31; i >= 0; --i) {
|
|
|
|
|
assert(m_txid.size() == 32);
|
|
|
|
|
printHex(m_txid.constData(), i, out);
|
2016-09-20 21:28:03 +02:00
|
|
|
}
|
2024-10-06 19:42:04 +02:00
|
|
|
out << "\n}\n";
|
2016-09-20 21:28:03 +02:00
|
|
|
}
|
|
|
|
|
|
2016-11-10 21:59:36 +01:00
|
|
|
void Transaction::debugScript(const QByteArray &script, int textIndent, QTextStream &out)
|
2016-09-20 21:28:03 +02:00
|
|
|
{
|
|
|
|
|
const int length = script.length();
|
2016-11-10 21:59:36 +01:00
|
|
|
if (length == 0) {
|
2024-01-09 21:14:16 +01:00
|
|
|
out << "\"\"" << Qt::endl;
|
2016-11-10 21:59:36 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2016-09-20 21:28:03 +02:00
|
|
|
const char *data = script.constData();
|
|
|
|
|
int pos = 0;
|
|
|
|
|
QString indent;
|
|
|
|
|
for (int i = 0; i < textIndent; ++i) indent += ' ';
|
|
|
|
|
bool linefeed = false;
|
|
|
|
|
|
2024-01-09 22:06:45 +01:00
|
|
|
constexpr int MinTokenSize = /* prefix */ 1 + /* category */ + 32 + /* flags */ 1;
|
|
|
|
|
if (script.size() > MinTokenSize
|
|
|
|
|
&& static_cast<uint8_t>(script.begin()[0]) == 0xef) {
|
|
|
|
|
/*
|
|
|
|
|
* This output holds a cash-token.
|
|
|
|
|
* We eat the bytes that hold the cashtoken data and continue
|
|
|
|
|
* identifying the script.
|
|
|
|
|
*/
|
|
|
|
|
const uint8_t* tokenData = reinterpret_cast<const uint8_t*>(script.constData());
|
|
|
|
|
QByteArray category;
|
|
|
|
|
category.resize(32);
|
|
|
|
|
for (int i = 0; i < 32; ++i) { // but WHY!!!
|
|
|
|
|
category[i] = data[pos + 32 - i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out << "TokenId: " << category.toHex() << Qt::endl;
|
|
|
|
|
int8_t flags = data[33];
|
|
|
|
|
int skipAmount = MinTokenSize;
|
|
|
|
|
if ((flags & 0x40) == 0x40) { // has-commitment-length bit is set.
|
|
|
|
|
int commitmentLength = tokenData[skipAmount];
|
|
|
|
|
skipAmount += 1 + commitmentLength;
|
|
|
|
|
out << indent << " +- " << commitmentLength << " byte committment" << Qt::endl;
|
|
|
|
|
}
|
|
|
|
|
if ((flags & 0x10) == 0x10) { // has-amount bit is set.
|
|
|
|
|
int x = skipAmount;
|
|
|
|
|
out << indent << " +- Amount: "
|
|
|
|
|
<< Streaming::fetchBitcoinCompact(data, skipAmount);
|
|
|
|
|
const uint8_t amount = tokenData[x++];
|
|
|
|
|
if (amount == 253) // compact-size design.
|
|
|
|
|
x += 2;
|
|
|
|
|
else if (amount == 254)
|
|
|
|
|
x += 4;
|
|
|
|
|
else if (amount == 255)
|
|
|
|
|
x += 8;
|
|
|
|
|
assert(x == skipAmount);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (script.size() <= skipAmount) {
|
|
|
|
|
qWarning() << "Invalid token data";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
pos = skipAmount;
|
|
|
|
|
|
|
|
|
|
out << Qt::endl << indent;
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-20 21:28:03 +02:00
|
|
|
while (pos < length) {
|
2019-03-03 15:26:54 +01:00
|
|
|
const unsigned char k = (unsigned char)(data[pos]);
|
2024-09-05 13:23:43 +02:00
|
|
|
if (linefeed) {
|
|
|
|
|
if (k == 103) // OP_ELSE
|
|
|
|
|
out << indent.left(indent.size() -2);
|
|
|
|
|
else
|
|
|
|
|
out << indent;
|
|
|
|
|
}
|
2016-09-20 21:28:03 +02:00
|
|
|
if (k > 0 && k < 75) {
|
2024-09-05 10:43:59 +02:00
|
|
|
out << "OP_DATA_" << k << " ";
|
2019-03-03 19:47:20 +01:00
|
|
|
if (k > length - pos - 1) {
|
2024-09-17 10:29:13 +02:00
|
|
|
out << " ERROR push too large for data: " << k << " is too large. We only have "
|
2024-01-09 21:29:49 +01:00
|
|
|
<< (length - pos -1) << " bytes in output\n";
|
2019-03-03 19:47:20 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2016-09-20 21:28:03 +02:00
|
|
|
for (int i = 0; i < k; ++i) {
|
|
|
|
|
pos++;
|
|
|
|
|
printHex(data, pos, out);
|
|
|
|
|
}
|
|
|
|
|
linefeed = true;
|
|
|
|
|
} else {
|
|
|
|
|
linefeed = true;
|
|
|
|
|
switch (k) {
|
|
|
|
|
case 0:
|
2024-09-05 10:43:59 +02:00
|
|
|
out << "OP_0 (push empty item to stack)"; break;
|
2016-09-20 21:28:03 +02:00
|
|
|
case 76:
|
|
|
|
|
case 77:
|
|
|
|
|
case 78: {
|
|
|
|
|
const int width = k - 75;
|
|
|
|
|
out << "OP_PUSHDATA" << width << " ";
|
2016-11-10 21:59:36 +01:00
|
|
|
quint32 bytes = (quint8) data[++pos];
|
2016-09-20 21:28:03 +02:00
|
|
|
if (width > 1) {
|
|
|
|
|
bytes = bytes << 8;
|
|
|
|
|
bytes += data[++pos];
|
|
|
|
|
if (width > 2) {
|
|
|
|
|
bytes = bytes << 8;
|
|
|
|
|
bytes += data[++pos];
|
|
|
|
|
bytes = bytes << 8;
|
|
|
|
|
bytes += data[++pos];
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-11-10 21:59:36 +01:00
|
|
|
if (bytes >= (unsigned int) length || (int) bytes + pos >= length) { // double check to avoid overflows
|
|
|
|
|
out << "\nFAILED; the OP_PUSHDATA says its " << bytes << " bytes, thats more than we have\n";
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-09-05 10:43:59 +02:00
|
|
|
out << bytes << " ";
|
2016-09-20 21:28:03 +02:00
|
|
|
for (unsigned int i = 0; i < bytes; ++i) {
|
|
|
|
|
pos++;
|
|
|
|
|
printHex(data, pos, out);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2016-11-10 21:59:36 +01:00
|
|
|
case 79:
|
|
|
|
|
out << "OP_1NEGATE"; break;
|
2024-09-05 10:43:59 +02:00
|
|
|
case 80:
|
|
|
|
|
out << "OP_RESERVED"; break;
|
|
|
|
|
case 81:
|
|
|
|
|
out << "OP_TRUE"; break;
|
2016-09-20 21:28:03 +02:00
|
|
|
case 82: case 83: case 84: case 85: case 86: case 87: case 88: case 89: case 90:
|
|
|
|
|
case 91: case 92: case 93: case 94: case 95: case 96: {
|
|
|
|
|
int count = k - 80;
|
2024-09-05 13:23:43 +02:00
|
|
|
out << "OP_" << count << " (pushes " << count << " on stack)";
|
2016-09-20 21:28:03 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 97:
|
|
|
|
|
out << "OP_NOP"; break;
|
|
|
|
|
case 99:
|
2024-09-05 13:23:43 +02:00
|
|
|
out << "OP_IF";
|
|
|
|
|
indent += " ";
|
|
|
|
|
break;
|
2016-09-20 21:28:03 +02:00
|
|
|
case 100:
|
|
|
|
|
out << "OP_NOTIF"; break;
|
|
|
|
|
case 103:
|
|
|
|
|
out << "OP_ELSE"; break;
|
|
|
|
|
case 104:
|
2024-09-05 13:23:43 +02:00
|
|
|
out << "OP_ENDIF";
|
|
|
|
|
indent = indent.left(indent.size() - 2);
|
|
|
|
|
break;
|
2016-09-20 21:28:03 +02:00
|
|
|
case 105:
|
|
|
|
|
out << "OP_VERIFY"; break;
|
|
|
|
|
case 106:
|
2019-03-03 15:26:54 +01:00
|
|
|
out << "OP_RETURN ";
|
2024-01-09 22:22:30 +01:00
|
|
|
++pos;
|
|
|
|
|
if (script.size() >= pos + 4) {
|
|
|
|
|
bool isASCII = true;
|
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
|
|
|
if (!QChar(script.at(pos + i + 1)).isLetter())
|
|
|
|
|
isASCII = false;
|
|
|
|
|
}
|
|
|
|
|
if (isASCII) {
|
|
|
|
|
out << '[' << script.mid(pos + 1, pos + 3) << ']' << Qt::endl;
|
|
|
|
|
out << indent << ' ' << script.mid(pos + 5).toHex() << Qt::endl;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
out << script.mid(pos + 1).toHex() << Qt::endl;
|
2019-03-03 15:26:54 +01:00
|
|
|
return;
|
2016-09-20 21:28:03 +02:00
|
|
|
case 107:
|
|
|
|
|
out << "OP_TOALTSTACK"; break;
|
|
|
|
|
case 108:
|
|
|
|
|
out << "OP_FROMALTSTACK"; break;
|
|
|
|
|
case 115:
|
|
|
|
|
out << "OP_IFDUP"; break;
|
|
|
|
|
case 116:
|
|
|
|
|
out << "OP_DEPTH"; break;
|
|
|
|
|
case 117:
|
|
|
|
|
out << "OP_DROP"; break;
|
|
|
|
|
case 118:
|
|
|
|
|
out << "OP_DUP"; break;
|
|
|
|
|
case 119:
|
|
|
|
|
out << "OP_NIP"; break;
|
|
|
|
|
case 120:
|
|
|
|
|
out << "OP_OVER"; break;
|
|
|
|
|
case 121:
|
|
|
|
|
out << "OP_PICK"; break;
|
|
|
|
|
case 122:
|
|
|
|
|
out << "OP_ROLL"; break;
|
|
|
|
|
case 123:
|
|
|
|
|
out << "OP_ROT"; break;
|
|
|
|
|
case 124:
|
|
|
|
|
out << "OP_SWAP"; break;
|
|
|
|
|
case 125:
|
|
|
|
|
out << "OP_TUCK"; break;
|
|
|
|
|
case 109:
|
|
|
|
|
out << "OP_2DROP"; break;
|
|
|
|
|
case 110:
|
|
|
|
|
out << "OP_2DUP"; break;
|
|
|
|
|
case 111:
|
|
|
|
|
out << "OP_3DUP"; break;
|
|
|
|
|
case 112:
|
|
|
|
|
out << "OP_2OVER"; break;
|
|
|
|
|
case 113:
|
|
|
|
|
out << "OP_2ROT"; break;
|
|
|
|
|
case 114:
|
|
|
|
|
out << "OP_2SWAP"; break;
|
|
|
|
|
case 126:
|
2024-09-05 13:23:43 +02:00
|
|
|
out << "OP_CAT"; break;
|
2016-09-20 21:28:03 +02:00
|
|
|
case 127:
|
2024-09-05 13:23:43 +02:00
|
|
|
out << "OP_SPLIT"; break;
|
2016-09-20 21:28:03 +02:00
|
|
|
case 128:
|
2024-09-05 13:23:43 +02:00
|
|
|
out << "OP_NUM2BIN"; break;
|
2016-09-20 21:28:03 +02:00
|
|
|
case 129:
|
2024-09-05 13:23:43 +02:00
|
|
|
out << "OP_BIN2NUM"; break;
|
2016-09-20 21:28:03 +02:00
|
|
|
case 130:
|
|
|
|
|
out << "OP_SIZE"; break;
|
|
|
|
|
case 131:
|
|
|
|
|
out << "OP_INVERT [ILLEGAL!]"; break;
|
|
|
|
|
case 132:
|
|
|
|
|
out << "OP_AND [ILLEGAL!]"; break;
|
|
|
|
|
case 133:
|
|
|
|
|
out << "OP_OR [ILLEGAL!]"; break;
|
|
|
|
|
case 134:
|
|
|
|
|
out << "OP_XOR [ILLEGAL!]"; break;
|
|
|
|
|
case 135:
|
|
|
|
|
out << "OP_EQUAL"; break;
|
|
|
|
|
case 136:
|
|
|
|
|
out << "OP_EQUALVERIFY"; break;
|
|
|
|
|
case 139:
|
|
|
|
|
out << "OP_1ADD"; break;
|
|
|
|
|
case 140:
|
|
|
|
|
out << "OP_1SUB"; break;
|
|
|
|
|
case 141:
|
|
|
|
|
out << "OP_2MUL [ILLEGAL]"; break;
|
|
|
|
|
case 142:
|
|
|
|
|
out << "OP_2DIV [ILLEGAL]"; break;
|
|
|
|
|
case 143:
|
|
|
|
|
out << "OP_NEGATE"; break;
|
|
|
|
|
case 144:
|
|
|
|
|
out << "OP_ABS"; break;
|
|
|
|
|
case 145:
|
|
|
|
|
out << "OP_NOT"; break;
|
|
|
|
|
case 146:
|
|
|
|
|
out << "OP_0NOTEQUAL"; break;
|
|
|
|
|
case 147:
|
|
|
|
|
out << "OP_ADD"; break;
|
|
|
|
|
case 148:
|
|
|
|
|
out << "OP_SUB"; break;
|
|
|
|
|
case 149:
|
|
|
|
|
out << "OP_MUL [ILLEGAL]"; break;
|
|
|
|
|
case 150:
|
|
|
|
|
out << "OP_DIV [ILLEGAL]"; break;
|
|
|
|
|
case 151:
|
|
|
|
|
out << "OP_MOD [ILLEGAL]"; break;
|
|
|
|
|
case 152:
|
|
|
|
|
out << "OP_LSHIFT [ILLEGAL]"; break;
|
|
|
|
|
case 153:
|
|
|
|
|
out << "OP_RSHIFT [ILLEGAL]"; break;
|
|
|
|
|
case 154:
|
|
|
|
|
out << "OP_BOOLAND"; break;
|
|
|
|
|
case 155:
|
|
|
|
|
out << "OP_BOOLOR"; break;
|
|
|
|
|
case 156:
|
|
|
|
|
out << "OP_NUMEQUAL"; break;
|
|
|
|
|
case 157:
|
|
|
|
|
out << "OP_NUMEQUALVERIFY"; break;
|
|
|
|
|
case 158:
|
|
|
|
|
out << "OP_NUMNOTEQUAL"; break;
|
|
|
|
|
case 159:
|
|
|
|
|
out << "OP_LESSTHAN"; break;
|
|
|
|
|
case 160:
|
|
|
|
|
out << "OP_GREATERTHAN"; break;
|
|
|
|
|
case 161:
|
|
|
|
|
out << "OP_LESSTHANOREQUAL"; break;
|
|
|
|
|
case 162:
|
|
|
|
|
out << "OP_GREATERTHANOREQUAL"; break;
|
|
|
|
|
case 163:
|
|
|
|
|
out << "OP_MIN"; break;
|
|
|
|
|
case 164:
|
|
|
|
|
out << "OP_MAX"; break;
|
|
|
|
|
case 165:
|
|
|
|
|
out << "OP_WITHIN"; break;
|
|
|
|
|
|
|
|
|
|
case 171:
|
|
|
|
|
out << "OP_CODESEPARATOR"; break;
|
|
|
|
|
case 172:
|
|
|
|
|
out << "OP_CHECKSIG"; break;
|
|
|
|
|
case 173:
|
|
|
|
|
out << "OP_CHECKSIGVERIFY"; break;
|
|
|
|
|
case 174:
|
|
|
|
|
out << "OP_CHECKMULTISIG"; break;
|
|
|
|
|
case 175:
|
|
|
|
|
out << "OP_CHECKMULTISIGVERIFY"; break;
|
|
|
|
|
case 177:
|
|
|
|
|
out << "OP_CHECKLOCKTIMEVERIFY"; break;
|
|
|
|
|
case 178:
|
|
|
|
|
out << "OP_CHECKSEQUENCEVERIFY"; break;
|
|
|
|
|
case 253:
|
|
|
|
|
out << "OP_PUBKEYHASH [ILLEGAL]"; break;
|
|
|
|
|
case 254:
|
|
|
|
|
out << "OP_PUBKEY [ILLEGAL]"; break;
|
|
|
|
|
case 255:
|
|
|
|
|
out << "OP_INVALIDOPCODE [ILLEGAL]"; break;
|
|
|
|
|
case 98:
|
|
|
|
|
out << "OP_VER"; break;
|
|
|
|
|
case 101:
|
|
|
|
|
out << "OP_VERIF"; break;
|
|
|
|
|
case 102:
|
|
|
|
|
out << "OP_VERNOTIF"; break;
|
|
|
|
|
case 137:
|
|
|
|
|
out << "OP_RESERVED1"; break;
|
|
|
|
|
case 138:
|
|
|
|
|
out << "OP_RESERVED2"; break;
|
|
|
|
|
case 176: case 179: case 180: case 181: case 182: case 183: case 184: case 185:
|
|
|
|
|
out << "OP_NOP{}"; break;
|
|
|
|
|
case 0xA9:
|
|
|
|
|
out << "OP_HASH160 ";
|
|
|
|
|
break;
|
|
|
|
|
case 166:
|
2024-09-05 10:35:15 +02:00
|
|
|
out << "OP_RIPEMD160 ";
|
2016-09-20 21:28:03 +02:00
|
|
|
break;
|
|
|
|
|
case 167:
|
2024-09-05 10:35:15 +02:00
|
|
|
out << "OP_SHA1 ";
|
2016-09-20 21:28:03 +02:00
|
|
|
break;
|
|
|
|
|
case 168:
|
2024-09-05 10:35:15 +02:00
|
|
|
out << "OP_SHA256 ";
|
2016-09-20 21:28:03 +02:00
|
|
|
break;
|
|
|
|
|
case 170:
|
2024-09-05 10:35:15 +02:00
|
|
|
out << "OP_HASH256 ";
|
2016-09-20 21:28:03 +02:00
|
|
|
break;
|
2024-09-05 13:51:56 +02:00
|
|
|
case 187:
|
|
|
|
|
out << "OP_CHECKDATASIGVERIFY"; break;
|
2024-09-05 13:23:43 +02:00
|
|
|
case 188:
|
|
|
|
|
out << "OP_REVERSEBYTES"; break;
|
|
|
|
|
|
|
|
|
|
// introspection;
|
|
|
|
|
case 192:
|
|
|
|
|
out << "OP_INPUTINDEX"; break;
|
|
|
|
|
case 193:
|
|
|
|
|
out << "OP_ACTIVEBYTECODE"; break;
|
|
|
|
|
case 194:
|
|
|
|
|
out << "OP_TXVERSION"; break;
|
|
|
|
|
case 195:
|
|
|
|
|
out << "OP_TXINPUTCOUNT"; break;
|
|
|
|
|
case 196:
|
|
|
|
|
out << "OP_TXOUTPUTCOUNT"; break;
|
|
|
|
|
case 197:
|
|
|
|
|
out << "OP_TXLOCKTIME"; break;
|
|
|
|
|
case 198:
|
|
|
|
|
out << "OP_UTXOVALUE"; break;
|
|
|
|
|
case 199:
|
|
|
|
|
out << "OP_UTXOBYTECODE"; break;
|
|
|
|
|
case 200:
|
|
|
|
|
out << "OP_OUTPOINTTXHASH"; break;
|
|
|
|
|
case 201:
|
|
|
|
|
out << "OP_OUTPOINTINDEX"; break;
|
|
|
|
|
case 202:
|
|
|
|
|
out << "OP_INPUTBYTECODE"; break;
|
|
|
|
|
case 203:
|
|
|
|
|
out << "OP_INPUTSEQUENCENUMBER"; break;
|
|
|
|
|
case 204:
|
|
|
|
|
out << "OP_OUTPUTVALUE"; break;
|
|
|
|
|
case 205:
|
|
|
|
|
out << "OP_OUTPUTBYTECODE"; break;
|
|
|
|
|
|
|
|
|
|
// cash tokens.
|
|
|
|
|
case 206:
|
|
|
|
|
out << "OP_INPUTINDEX"; break;
|
|
|
|
|
case 207:
|
|
|
|
|
out << "OP_UTXOTOKENCOMMITMENT"; break;
|
|
|
|
|
case 208:
|
|
|
|
|
out << "OP_UTXOTOKENAMOUNT"; break;
|
|
|
|
|
case 209:
|
|
|
|
|
out << "OP_OUTPUTTOKENCATEGORY"; break;
|
|
|
|
|
case 210:
|
|
|
|
|
out << "OP_OUTPUTTOKENCOMMITMENT"; break;
|
|
|
|
|
case 211:
|
|
|
|
|
out << "OP_OUTPUTTOKENAMOUNT"; break;
|
|
|
|
|
|
2016-09-20 21:28:03 +02:00
|
|
|
default:
|
|
|
|
|
out << "UNKNOWN: " << QString::number(k, 16);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (linefeed)
|
2024-01-09 21:14:16 +01:00
|
|
|
out << Qt::endl;
|
2016-09-20 21:28:03 +02:00
|
|
|
++pos;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-03 19:47:20 +01:00
|
|
|
void Transaction::checkInScript(const QByteArray &script, int textIndent, QTextStream &out)
|
|
|
|
|
{
|
|
|
|
|
const int length = script.length();
|
|
|
|
|
const char *data = script.constData();
|
|
|
|
|
int pos = 0;
|
|
|
|
|
QString indent;
|
|
|
|
|
for (int i = 0; i < textIndent; ++i) indent += ' ';
|
|
|
|
|
|
|
|
|
|
QList<QByteArray> items;
|
|
|
|
|
while (pos < length) {
|
|
|
|
|
const unsigned char k = (unsigned char)(data[pos]);
|
2024-09-05 10:43:59 +02:00
|
|
|
if (k <= 75) { // op-data
|
2019-03-03 19:47:20 +01:00
|
|
|
items.append(script.mid(++pos, k));
|
|
|
|
|
pos += k;
|
2024-09-05 10:43:59 +02:00
|
|
|
} else if (k <= 78) {
|
|
|
|
|
// op push-data-x
|
|
|
|
|
const int pushType = k - 75;
|
|
|
|
|
int bytes = (quint8) data[++pos];
|
|
|
|
|
if (pushType > 1) {
|
|
|
|
|
bytes = bytes << 8;
|
|
|
|
|
bytes += data[++pos];
|
|
|
|
|
if (pushType > 2) {
|
|
|
|
|
bytes = bytes << 8;
|
|
|
|
|
bytes += data[++pos];
|
|
|
|
|
bytes = bytes << 8;
|
|
|
|
|
bytes += data[++pos];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
++pos;
|
|
|
|
|
assert(length - pos >= bytes);
|
|
|
|
|
items.append(script.mid(pos, bytes));
|
|
|
|
|
pos += bytes;
|
2019-03-03 19:47:20 +01:00
|
|
|
} else {
|
2024-09-05 12:49:29 +02:00
|
|
|
out << " lint: ERROR: non-push item in input!" << Qt::endl;
|
2019-03-03 19:47:20 +01:00
|
|
|
items.clear();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (items.size() == 2) {
|
|
|
|
|
auto sig = items.at(0);
|
|
|
|
|
auto pk = items.at(1);
|
2024-09-05 12:45:40 +02:00
|
|
|
if (sig.size() > 7 && sig.size() <= 73
|
|
|
|
|
&& (sig.at(0) == 0x30 || sig.size() == 65)
|
2019-03-03 19:47:20 +01:00
|
|
|
&& pk.size() > 10) {
|
|
|
|
|
// lets conclude this is the most common pair. A P2PKH input
|
2024-09-05 12:49:29 +02:00
|
|
|
out << " lint: P2PKH (";
|
2019-03-03 19:47:20 +01:00
|
|
|
uint8_t hashType = sig.at(sig.size() -1);
|
2024-09-05 12:45:40 +02:00
|
|
|
switch (hashType & 0x1F) {
|
2019-03-03 19:47:20 +01:00
|
|
|
case 1: out << "SIGHASH_ALL "; break;
|
|
|
|
|
case 2: out << "SIGHASH_NONE "; break;
|
|
|
|
|
case 3: out << "SIGHASH_SINGLE "; break;
|
2024-09-05 12:45:40 +02:00
|
|
|
default: out << "SIGHASH unknown: " << (hashType & 0x1F); break;
|
2019-03-03 19:47:20 +01:00
|
|
|
}
|
2024-09-05 12:45:40 +02:00
|
|
|
if (hashType & 0x20)
|
|
|
|
|
out << "SIGHASH_UTXO ";
|
2019-03-03 19:47:20 +01:00
|
|
|
if (hashType & 0x40 )
|
|
|
|
|
out << "SIGHASH_FORKID ";
|
|
|
|
|
if (hashType & 0x80)
|
|
|
|
|
out << "SIGHASH_ANYONECANPAY ";
|
2024-09-05 12:49:29 +02:00
|
|
|
out << ")" << Qt::endl;
|
2019-03-03 19:47:20 +01:00
|
|
|
}
|
|
|
|
|
}
|
2024-09-05 13:23:43 +02:00
|
|
|
if (items.size() > 3 && items.last().size() > 20) {
|
|
|
|
|
out << indent << "\\_" << Qt::endl;
|
|
|
|
|
out << indent << " P2SH likely. Trying to parse. Ignore errors if this isn't p2sh" << Qt::endl;
|
|
|
|
|
out << indent << " ";
|
|
|
|
|
debugScript(items.last(), textIndent + 4, out);
|
|
|
|
|
}
|
2019-03-03 19:47:20 +01:00
|
|
|
}
|
|
|
|
|
|
2024-10-06 19:31:08 +02:00
|
|
|
bool Transaction::parseTransaction(const QByteArray &bytes)
|
2016-09-20 21:28:03 +02:00
|
|
|
{
|
|
|
|
|
const int length = bytes.length();
|
|
|
|
|
const char *data = bytes.constData();
|
2016-11-10 21:59:36 +01:00
|
|
|
m_version = Streaming::fetch32bitValue(data, 0);
|
2016-09-20 21:28:03 +02:00
|
|
|
Q_ASSERT(data[0] <= 2);
|
|
|
|
|
Q_ASSERT(data[1] == 0);
|
|
|
|
|
Q_ASSERT(data[2] == 0);
|
|
|
|
|
Q_ASSERT(data[3] == 0);
|
|
|
|
|
|
|
|
|
|
int pos = 4;
|
2017-02-16 23:04:44 +01:00
|
|
|
quint64 count = Streaming::fetchBitcoinCompact(data, pos);
|
2016-09-20 21:28:03 +02:00
|
|
|
QList<TxIn> inputs;
|
|
|
|
|
for (unsigned int i = 0; i < count; ++i) {
|
|
|
|
|
TxIn tx;
|
2024-01-09 22:06:45 +01:00
|
|
|
tx.transaction.resize(32);
|
2019-03-03 15:26:54 +01:00
|
|
|
for (int i = 0; i < 32; ++i) { // but WHY!!!
|
2016-09-20 21:28:03 +02:00
|
|
|
tx.transaction[i] = data[pos + 31 - i];
|
|
|
|
|
}
|
|
|
|
|
pos += 32;
|
|
|
|
|
tx.prevIndex = Streaming::fetch32bitValue(data, pos);
|
|
|
|
|
pos += 4;
|
|
|
|
|
|
|
|
|
|
quint32 scriptLength = Streaming::fetchBitcoinCompact(data, pos);
|
2016-11-10 21:59:36 +01:00
|
|
|
if (scriptLength >= (unsigned int) (length - pos)) {
|
|
|
|
|
qWarning() << "ScriptLength (in/" << i << ") out of bounds";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2016-09-20 21:28:03 +02:00
|
|
|
|
2019-03-03 15:26:54 +01:00
|
|
|
tx.inputScript = QByteArray(data + pos, scriptLength);
|
2016-09-20 21:28:03 +02:00
|
|
|
pos += scriptLength;
|
|
|
|
|
tx.sequence = Streaming::fetch32bitValue(data, pos);
|
|
|
|
|
pos += 4;
|
|
|
|
|
inputs.append(tx);
|
2016-11-10 21:59:36 +01:00
|
|
|
if (pos >= length) {
|
|
|
|
|
qWarning() << "Tx truncated while reading inputs";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2016-09-20 21:28:03 +02:00
|
|
|
}
|
|
|
|
|
|
2017-02-16 23:04:44 +01:00
|
|
|
count = Streaming::fetchBitcoinCompact(data, pos);
|
2016-11-10 21:59:36 +01:00
|
|
|
if (pos >= length) {
|
|
|
|
|
qWarning() << "Tx truncated, can't find outputs";
|
|
|
|
|
return false;
|
2016-09-20 21:28:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<TxOut> outputs;
|
|
|
|
|
for (unsigned int i = 0; i < count; ++i) {
|
|
|
|
|
TxOut tx;
|
|
|
|
|
tx.value = Streaming::fetch64bitValue(data, pos);
|
|
|
|
|
// qDebug() << "value" << tx.value;
|
|
|
|
|
pos += 8;
|
|
|
|
|
|
|
|
|
|
quint32 scriptLength = Streaming::fetchBitcoinCompact(data, pos);
|
2016-11-10 21:59:36 +01:00
|
|
|
if (scriptLength >= (unsigned int) (length - pos)) {
|
|
|
|
|
qWarning() << "ScriptLength (output/" << i << ") out of bounds";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2016-09-20 21:28:03 +02:00
|
|
|
|
|
|
|
|
tx.script = QByteArray(data + pos, scriptLength);
|
|
|
|
|
pos += scriptLength;
|
|
|
|
|
outputs.append(tx);
|
2016-11-10 21:59:36 +01:00
|
|
|
if (pos >= length) {
|
|
|
|
|
qWarning() << "Tx truncated while reading outputs";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2016-09-20 21:28:03 +02:00
|
|
|
}
|
|
|
|
|
|
2016-11-10 21:59:36 +01:00
|
|
|
if (pos + 4 != length) {
|
|
|
|
|
qWarning() << "length of tx incorrect (" << length << ", expected" << pos + 4 << ")";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2016-09-20 21:28:03 +02:00
|
|
|
m_nLockTime = Streaming::fetch32bitValue(data, pos);
|
|
|
|
|
|
|
|
|
|
m_inputs= inputs;
|
|
|
|
|
m_outputs = outputs;
|
2024-10-06 19:42:04 +02:00
|
|
|
|
|
|
|
|
// the TXID calc in Bitcoin is weird.
|
|
|
|
|
// - the data is hashed
|
|
|
|
|
// - the hash is then hashed again
|
|
|
|
|
// - the result is then printed in reverse. (back to front)
|
|
|
|
|
QCryptographicHash hasher(QCryptographicHash::Sha256);
|
|
|
|
|
hasher.addData(bytes);
|
|
|
|
|
auto intermediateHash = hasher.result();
|
|
|
|
|
hasher.reset();
|
|
|
|
|
hasher.addData(intermediateHash);
|
|
|
|
|
m_txid = hasher.resultView().toByteArray();
|
|
|
|
|
assert(m_txid.length() == 32);
|
|
|
|
|
|
2016-11-10 21:59:36 +01:00
|
|
|
return true;
|
2016-09-20 21:28:03 +02:00
|
|
|
}
|