Issues (130)

Transaction/SignatureHash/TxSigHashSerializer.php (1 issue)

Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Transaction\SignatureHash;
6
7
use BitWasp\Bitcoin\Script\Opcodes;
8
use BitWasp\Bitcoin\Script\ScriptInterface;
9
use BitWasp\Bitcoin\Serializer\Types;
10
use BitWasp\Bitcoin\Transaction\TransactionInterface;
11
12
class TxSigHashSerializer
13
{
14
    /**
15
     * @var TransactionInterface
16
     */
17
    private $tx;
18
19
    /**
20
     * @var ScriptInterface
21
     */
22
    private $scriptCode;
23
24
    /**
25
     * @var int
26
     */
27
    private $nIn;
28
29
    /**
30
     * @var \BitWasp\Buffertools\Types\VarInt
31
     */
32
    private $varint;
33
34
    /**
35
     * @var \BitWasp\Buffertools\Types\ByteString
36
     */
37
    private $bs32le;
38
39
    /**
40
     * @var bool
41
     */
42
    private $anyoneCanPay = false;
43
44
    /**
45
     * @var bool
46
     */
47
    private $hashSingle = false;
48
49
    /**
50
     * @var bool
51
     */
52
    private $hashNone = false;
53
54
    /**
55
     * @param TransactionInterface $tx
56
     * @param ScriptInterface $scriptCode
57
     * @param int $nIn
58
     * @param int $nHashTypeIn
59
     */
60 252
    public function __construct(TransactionInterface $tx, ScriptInterface $scriptCode, int $nIn, int $nHashTypeIn)
61
    {
62 252
        $this->tx = $tx;
63 252
        $this->scriptCode = $scriptCode;
64 252
        $this->nIn = $nIn;
65 252
        $this->anyoneCanPay = !!($nHashTypeIn & SigHash::ANYONECANPAY);
66
67 252
        $bits = $nHashTypeIn & 0x1f;
68 252
        $this->hashSingle = $bits === SigHash::SINGLE;
69 252
        $this->hashNone = $bits === SigHash::NONE;
70
71 252
        $this->varint = Types::varint();
72 252
        $this->bs32le = Types::bytestringle(32);
73 252
    }
74
75
    /**
76
     * @return string
77
     */
78 252
    private function serializeScript(): string
79
    {
80 252
        $script = $this->scriptCode;
81 252
        $parser = $script->getScriptParser();
82
83 252
        $nSize = $script->getBuffer()->getSize();
84 252
        $nSeparators = 0;
85 252
        foreach ($parser as $operation) {
86 252
            if ($operation->getOp() === Opcodes::OP_CODESEPARATOR) {
87
                $nSeparators++;
88
            }
89
        }
90
91 252
        $newSize = $nSize - $nSeparators;
92 252
        $out = $this->varint->write($newSize);
93 252
        $begin = $position = 0;
0 ignored issues
show
The assignment to $position is dead and can be removed.
Loading history...
94
95 252
        foreach ($parser as $operation) {
96 252
            if ($operation->getOp() === Opcodes::OP_CODESEPARATOR) {
97
                $position = $parser->getPosition();
98
                $out .= $parser->slice($position)->getBinary();
99
                $begin = $position;
100
            }
101
        }
102
103 252
        if ($begin !== $newSize) {
104 252
            $out .= $parser->slice($begin, $newSize - $begin)->getBinary();
105
        }
106
107 252
        return $out;
108
    }
109
110
    /**
111
     * @param int $nInput
112
     * @return string
113
     */
114 252
    public function serializeInput(int $nInput): string
115
    {
116 252
        if ($this->anyoneCanPay) {
117 10
            $nInput = $this->nIn;
118
        }
119
120 252
        $txIn = $this->tx->getInput($nInput);
121 252
        $outpoint = $txIn->getOutPoint();
122 252
        $out = $this->bs32le->write($outpoint->getTxId()) . pack('V', $outpoint->getVout());
123
124 252
        if ($nInput !== $this->nIn) {
125
            // script length is zero
126 10
            $out .= "\x00";
127
        } else {
128 252
            $out .= $this->serializeScript();
129
        }
130
131 252
        if ($nInput !== $this->nIn && ($this->hashSingle || $this->hashNone)) {
132 6
            $out .= pack('V', 0);
133
        } else {
134 252
            $out .= pack('V', $txIn->getSequence());
135
        }
136
137 252
        return $out;
138
    }
139
140
    /**
141
     * @param int $nOutput
142
     * @return string
143
     */
144 245
    public function serializeOutput(int $nOutput): string
145
    {
146 245
        if ($this->hashSingle && $nOutput != $this->nIn) {
147 6
            $out = pack('P', -1) . "\x00";
148
        } else {
149 245
            $txOut = $this->tx->getOutput($nOutput);
150 245
            $scriptBuf = $txOut->getScript()->getBuffer();
151 245
            $out = pack('P', $txOut->getValue()) . $this->varint->write($scriptBuf->getSize()) . $scriptBuf->getBinary();
152
        }
153
154 245
        return $out;
155
    }
156
157
    /**
158
     * @return string
159
     */
160 252
    public function serializeTransaction(): string
161
    {
162 252
        $data = Types::int32le()->write($this->tx->getVersion());
163
164 252
        $nInputs = $this->anyoneCanPay ? 1 : count($this->tx->getInputs());
165 252
        $data .= $this->varint->write($nInputs);
166 252
        for ($i = 0; $i < $nInputs; $i++) {
167 252
            $data .= $this->serializeInput($i);
168
        }
169
170 252
        $nOutputs = $this->hashNone ? 0 : ($this->hashSingle ? $this->nIn + 1 : count($this->tx->getOutputs()));
171 252
        $data .= $this->varint->write($nOutputs);
172 252
        for ($i = 0; $i < $nOutputs; $i++) {
173 245
            $data .= $this->serializeOutput($i);
174
        }
175
176 252
        $data .= pack('V', $this->tx->getLockTime());
177 252
        return $data;
178
    }
179
}
180