Transaction::setSender()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
1
<?php
2
3
/**
4
 * Copyright (c) 2020 UMI
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in all
14
 * copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 */
24
25
declare(strict_types=1);
26
27
namespace UmiTop\UmiCore\Transaction;
28
29
use Exception;
30
use UmiTop\UmiCore\Address\Address;
31
use UmiTop\UmiCore\Address\AddressInterface;
32
use UmiTop\UmiCore\Key\SecretKeyInterface;
33
use UmiTop\UmiCore\Util\ConverterTrait;
34
use UmiTop\UmiCore\Util\ValidatorTrait;
35
36
/**
37
 * Класс для работы с транзакциями.
38
 * @package UmiTop\UmiCore\Transaction
39
 */
40
class Transaction implements TransactionInterface
41
{
42
    use ValidatorTrait;
43
    use ConverterTrait;
44
45
    /** @var int Длина транзакции в байтах. */
46
    public const LENGTH = 150;
47
48
    /** @var string Транзакция в бинарном виде. */
49
    private $bytes;
50
51
    /**
52
     * Transaction constructor.
53
     */
54 19
    public function __construct()
55
    {
56 19
        $this->bytes = str_repeat("\x0", self::LENGTH);
57 19
        $this->setVersion(self::BASIC);
58 19
    }
59
60
    /**
61
     * Статический метод, создает объект из массива байтов.
62
     * @param string $bytes Транзакция в бинарном виде, длина 150 байт.
63
     * @return TransactionInterface
64
     * @throws Exception
65
     */
66 2
    public static function fromBytes(string $bytes): TransactionInterface
67
    {
68 2
        $trx = new Transaction();
69
70 2
        return $trx->setBytes($bytes);
71
    }
72
73
    /**
74
     * Транзакция в бинарном виде, длина 150 байт.
75
     * @return string
76
     */
77 1
    public function getBytes(): string
78
    {
79 1
        return $this->bytes;
80
    }
81
82
    /**
83
     * Устанавливает транзакцию из бинарной строки и возвращает $this.
84
     * @param string $bytes Транзакция в бинарном виде, длина 150 байт.
85
     * @return TransactionInterface
86
     * @throws Exception
87
     */
88 4
    public function setBytes(string $bytes): TransactionInterface
89
    {
90 4
        if (strlen($bytes) !== self::LENGTH) {
91 1
            throw new Exception('length must be 150 bytes');
92
        }
93 3
        $this->bytes = $bytes;
94
95 3
        return $this;
96
    }
97
98
    /**
99
     * Комиссия в сотых долях процента с шагом в 0.01%.
100
     * Принимает значения от 0 до 2000 (соответственно от 0% до 20%).
101
     * Доступно только для CreateStructure и UpdateStructure.
102
     * @return int
103
     * @throws Exception
104
     */
105 1
    public function getFeePercent(): int
106
    {
107 1
        $this->checkVersion([2, 3]);
108
109 1
        return $this->bytesToUint16(substr($this->bytes, 39, 2));
110
    }
111
112
    /**
113
     * Устанавливает размер комиссии и возвращает this.
114
     * Доступно только для CreateStructure и UpdateStructure.
115
     * @param int $percent Комиссия в сотых долях процента с шагом в 0.01%.
116
     * Принимает значения от 0 до 2000 (соответственно от 0% до 20%).
117
     * @return TransactionInterface
118
     * @throws Exception
119
     */
120 2
    public function setFeePercent(int $percent): TransactionInterface
121
    {
122 2
        $this->checkVersion([2, 3]);
123 2
        $this->validateInt($percent, 0, 2000);
124 1
        $this->bytes = substr_replace($this->bytes, $this->uint16ToBytes($percent), 39, 2);
125
126 1
        return $this;
127
    }
128
129
    /**
130
     * Хэш (txid) транзакции в бинарном виде.
131
     * @return string
132
     */
133 1
    public function getHash(): string
134
    {
135 1
        return hash('sha256', $this->bytes, true);
136
    }
137
138
    /**
139
     * Название структуры в кодировке UTF-8.
140
     * Доступно только для CreateStructure и UpdateStructure.
141
     * @return string
142
     * @throws Exception
143
     */
144 2
    public function getName(): string
145
    {
146 2
        $this->checkVersion([2, 3]);
147 2
        $this->validateInt(ord($this->bytes[41]), 0, 35);
148
149 1
        return substr($this->bytes, 42, ord($this->bytes[41]));
150
    }
151
152
    /**
153
     * Устанавливает название структуры и возвращает this.
154
     * Доступно только для CreateStructure и UpdateStructure.
155
     * @param string $name Название структуры в кодировке UTF-8.
156
     * @return TransactionInterface
157
     * @throws Exception
158
     */
159 2
    public function setName(string $name): TransactionInterface
160
    {
161 2
        $this->checkVersion([2, 3]);
162 2
        $this->validateInt(strlen($name), 0, 35);
163
164 1
        $this->bytes[41] = chr(strlen($name));
165 1
        $this->bytes = substr_replace($this->bytes, str_repeat("\x0", 35), 42, 35); // wipe
166 1
        $this->bytes = substr_replace($this->bytes, $name, 42, strlen($name));
167
168 1
        return $this;
169
    }
170
171
    /**
172
     * Nonce, целое число в промежутке от 0 до 18446744073709551615.
173
     * Генерируется автоматически при вызове sign().
174
     * @return int
175
     */
176 1
    public function getNonce(): int
177
    {
178 1
        return $this->bytesToInt64(substr($this->bytes, 77, 8));
179
    }
180
181
    /**
182
     * Устанавливает nonce и возвращает this.
183
     * @param int $nonce Целое число в промежутке от 0 до 18446744073709551615.
184
     * @return TransactionInterface
185
     */
186 2
    public function setNonce(int $nonce): TransactionInterface
187
    {
188 2
        $this->bytes = substr_replace($this->bytes, $this->int64ToBytes($nonce), 77, 8);
189
190 2
        return $this;
191
    }
192
193
    /**
194
     * Префикс адресов, принадлежащих структуре.
195
     * Доступно только для CreateStructure и UpdateStructure.
196
     * @return string
197
     * @throws Exception
198
     */
199 1
    public function getPrefix(): string
200
    {
201 1
        $this->checkVersion([2, 3]);
202
203 1
        return $this->versionToPrefix($this->bytesToUint16(substr($this->bytes, 35, 2)));
204
    }
205
206
    /**
207
     * Устанавливает префикс и возвращает $this.
208
     * Доступно только для CreateStructure и UpdateStructure.
209
     * @param string $prefix Три символа латиницы в нижнем регистре.
210
     * @return TransactionInterface
211
     * @throws Exception
212
     */
213 2
    public function setPrefix(string $prefix): TransactionInterface
214
    {
215 2
        $this->checkVersion([2, 3]);
216 1
        $this->bytes = substr_replace($this->bytes, $this->uint16ToBytes($this->prefixToVersion($prefix)), 35, 2);
217
218 1
        return $this;
219
    }
220
221
    /**
222
     * Профита в сотых долях процента с шагом в 0.01%.
223
     * Принимает значения от 100 до 500 (соответственно от 1% до 5%).
224
     * Доступно только для CreateStructure и UpdateStructure.
225
     * @return int
226
     * @throws Exception
227
     */
228 1
    public function getProfitPercent(): int
229
    {
230 1
        $this->checkVersion([2, 3]);
231
232 1
        return $this->bytesToUint16(substr($this->bytes, 37, 2));
233
    }
234
235
    /**
236
     * Устанавливает процент профита и возвращает $this.
237
     * Доступно только для CreateStructure и UpdateStructure.
238
     * @param int $percent Профит в сотых долях процента с шагом в 0.01%.
239
     * Принимает значения от 100 до 500 (соответственно от 1% до 5%).
240
     * @return TransactionInterface
241
     * @throws Exception
242
     */
243 2
    public function setProfitPercent(int $percent): TransactionInterface
244
    {
245 2
        $this->checkVersion([2, 3]);
246 2
        $this->validateInt($percent, 100, 500);
247 1
        $this->bytes = substr_replace($this->bytes, $this->uint16ToBytes($percent), 37, 2);
248
249 1
        return $this;
250
    }
251
252
    /**
253
     * Получатель.
254
     * Недоступно для транзакций CreateStructure и UpdateStructure.
255
     * @return AddressInterface
256
     * @throws Exception
257
     */
258 1
    public function getRecipient(): AddressInterface
259
    {
260 1
        $this->checkVersion([0, 1, 4, 5, 6, 7]);
261 1
        $adr = new Address();
262
263 1
        return $adr->setBytes(substr($this->bytes, 35, 34));
264
    }
265
266
    /**
267
     * Устанавливает получателя и возвращает $this.
268
     * Недоступно для транзакций CreateStructure и UpdateStructure.
269
     * @param AddressInterface $address Адрес получателя.
270
     * @return TransactionInterface
271
     * @throws Exception
272
     */
273 1
    public function setRecipient(AddressInterface $address): TransactionInterface
274
    {
275 1
        $this->checkVersion([0, 1, 4, 5, 6, 7]);
276 1
        $this->bytes = substr_replace($this->bytes, $address->getBytes(), 35, 34);
277
278 1
        return $this;
279
    }
280
281
    /**
282
     * Отправитель.
283
     * Доступно для всех типов транзакций.
284
     * @return AddressInterface
285
     * @throws Exception
286
     */
287 3
    public function getSender(): AddressInterface
288
    {
289 3
        $adr = new Address();
290
291 3
        return $adr->setBytes(substr($this->bytes, 1, 34));
292
    }
293
294
    /**
295
     * Устанавливает отправителя и возвращает $this.
296
     * @param AddressInterface $address Адрес отправителя.
297
     * @return TransactionInterface
298
     */
299 2
    public function setSender(AddressInterface $address): TransactionInterface
300
    {
301 2
        $this->bytes = substr_replace($this->bytes, $address->getBytes(), 1, 34);
302
303 2
        return $this;
304
    }
305
306
    /**
307
     * Цифровая подпись транзакции, длина 64 байта.
308
     * @return string
309
     */
310 2
    public function getSignature(): string
311
    {
312 2
        return substr($this->bytes, 85, 64);
313
    }
314
315
    /**
316
     * Устанавливает цифровую подпись и возвращает $this.
317
     * @param string $signature Подпись, длина 64 байта.
318
     * @return TransactionInterface
319
     */
320 2
    public function setSignature(string $signature): TransactionInterface
321
    {
322 2
        $this->bytes = substr_replace($this->bytes, $signature, 85, strlen($signature));
323
324 2
        return $this;
325
    }
326
327
    /**
328
     * Сумма перевода в UMI-центах, цело число в промежутке от 1 до 18446744073709551615.
329
     * Доступно только для Genesis и Basic транзакций.
330
     * @return int
331
     * @throws Exception
332
     */
333 1
    public function getValue(): int
334
    {
335 1
        $this->checkVersion([0, 1]);
336
337 1
        return $this->bytesToInt64(substr($this->bytes, 69, 8));
338
    }
339
340
    /**
341
     * Устанавливает сумму и возвращает $this.
342
     * Принимает значения в промежутке от 1 до 18446744073709551615.
343
     * Доступно только для Genesis и Basic транзакций.
344
     * @param int $value Целое число от 1 до 18446744073709551615.
345
     * @return TransactionInterface
346
     * @throws Exception
347
     */
348 1
    public function setValue(int $value): TransactionInterface
349
    {
350 1
        $this->checkVersion([0, 1]);
351 1
        $this->bytes = substr_replace($this->bytes, $this->int64ToBytes($value), 69, 8);
352
353 1
        return $this;
354
    }
355
356
    /**
357
     * Версия (тип) транзакции.
358
     * @return int
359
     */
360 12
    public function getVersion(): int
361
    {
362 12
        return ord($this->bytes[0]);
363
    }
364
365
    /**
366
     * Устанавливает версию и возвращает $this.
367
     * @param int $version Версия (тип) транзакции.
368
     * @return TransactionInterface
369
     * @throws Exception
370
     */
371 19
    public function setVersion(int $version): TransactionInterface
372
    {
373 19
        $this->validateInt($version, 0, 7);
374 19
        $this->bytes[0] = chr($version);
375
376 19
        return $this;
377
    }
378
379
    /**
380
     * Подписать транзакцию приватным ключом.
381
     * @param SecretKeyInterface $secretKey Приватный ключ.
382
     * @return TransactionInterface
383
     * @throws Exception
384
     */
385 1
    public function sign(SecretKeyInterface $secretKey): TransactionInterface
386
    {
387 1
        $this->setNonce((int)(microtime(true) * 100));
388 1
        $this->setSignature($secretKey->sign(substr($this->bytes, 0, 85)));
389
390 1
        return $this;
391
    }
392
393
    /**
394
     * Проверить транзакцию на соответствие формальным правилам.
395
     * @return bool
396
     * @throws Exception
397
     */
398 2
    public function verify(): bool
399
    {
400 2
        return $this->getSender()
401 2
            ->getPublicKey()
402 2
            ->verifySignature($this->getSignature(), substr($this->bytes, 0, 85));
403
    }
404
405
    /**
406
     * @param int[] $versions
407
     * @return void
408
     * @throws Exception
409
     */
410 11
    private function checkVersion(array $versions): void
411
    {
412 11
        if (!in_array($this->getVersion(), $versions, true)) {
413 1
            throw new Exception('invalid version');
414
        }
415 10
    }
416
}
417