/** * Given a sequence, get the human readable node. Ie, 0 -> 0, 0x80000000 -> 0h * @param $sequence * @return string */ public function getNode($sequence) { if ($this->isHardened($sequence)) { $sequence = $this->math->sub($sequence, self::START_HARDENED) . 'h'; } return $sequence; }
/** * Convert a lock time to the timestamp it's locked to. * Throws an exception when: * - Lock time appears to be in the block locktime range ( < Locktime::BLOCK_MAX ) * - When the lock time exceeds the max possible lock time ( > Locktime::INT_MAX ) * * @param int|string $lockTime * @return int|string * @throws \Exception */ public function toTimestamp($lockTime) { if ($this->math->cmp($lockTime, self::BLOCK_MAX) <= 0) { throw new \Exception('Lock time out of range for timestamp'); } if ($this->math->cmp($lockTime, self::INT_MAX) > 0) { throw new \Exception('Lock time too large'); } $timestamp = $this->math->sub($lockTime, self::BLOCK_MAX); return $timestamp; }
/** * @param UtxoView $view * @param TransactionInterface $tx * @param int $spendHeight * @return $this */ public function checkContextualInputs(UtxoView $view, TransactionInterface $tx, $spendHeight) { $valueIn = gmp_init(0); for ($i = 0, $nInputs = count($tx->getInputs()); $i < $nInputs; $i++) { /*if ($out->isCoinbase()) { // todo: cb / height if ($spendHeight - $out->getHeight() < $this->params->coinbaseMaturityAge()) { return false; } }*/ $value = gmp_init($view->fetchByInput($tx->getInput($i))->getOutput()->getValue(), 10); $valueIn = $this->math->add($valueIn, $value); $this->consensus->checkAmount($valueIn); } $valueOut = gmp_init(0); foreach ($tx->getOutputs() as $output) { $valueOut = $this->math->add($valueOut, gmp_init($output->getValue(), 10)); $this->consensus->checkAmount($valueOut); } if ($this->math->cmp($valueIn, $valueOut) < 0) { throw new \RuntimeException('Value-in is less than value-out'); } $fee = $this->math->sub($valueIn, $valueOut); $this->consensus->checkAmount($fee); return $this; }
/** * @return BufferInterface */ private function serialize() { if ($this->math->cmp($this->number, '0') === 0) { return new Buffer('', 0); } // Using array of integers instead of bytes $result = []; $negative = $this->math->cmp($this->number, 0) < 0; $abs = $negative ? $this->math->sub(0, $this->number) : $this->number; while ($this->math->cmp($abs, 0) > 0) { //array_unshift($result, (int)$this->math->bitwiseAnd($abs, 0xff)); $result[] = (int) $this->math->bitwiseAnd($abs, 0xff); $abs = $this->math->rightShift($abs, 8); } if ($result[count($result) - 1] & 0x80) { //array_unshift($result, $negative ? 0x80 : 0); $result[] = $negative ? 0x80 : 0; } else { if ($negative) { //$result[0] |= 0x80; $result[count($result) - 1] |= 0x80; } } $s = ''; foreach ($result as $i) { $s .= chr($i); } return new Buffer($s, null, $this->math); }
/** * @param Parser $parser * @return CompactSignature * @throws ParserOutOfRange */ public function fromParser(Parser &$parser) { try { list($byte, $r, $s) = $this->getTemplate()->parse($parser); $recoveryFlags = $this->math->sub($byte, 27); if ($recoveryFlags < 0 || $recoveryFlags > 7) { throw new \InvalidArgumentException('invalid signature type'); } $isCompressed = $this->math->bitwiseAnd($recoveryFlags, 4) != 0; $recoveryId = $recoveryFlags - ($isCompressed ? 4 : 0); } catch (ParserOutOfRange $e) { throw new ParserOutOfRange('Failed to extract full signature from parser'); } $signature = new CompactSignature($r, $s, $recoveryId, $isCompressed); return $signature; }
/** * @param Peer $sender * @param TransactionSignatureInterface $txSig * @return TransactionSignatureInterface */ public function fixSig(Peer $sender, TransactionSignatureInterface $txSig, &$wasMalleated = false) { $sig = $txSig->getSignature(); if (!$this->adapter->validateSignatureElement($sig->getS(), true)) { $ip = $sender->getRemoteAddr()->getIp(); if (!isset($this->violators[$ip])) { $this->violators[$sender->getRemoteAddr()->getIp()] = 1; } else { $this->violators[$sender->getRemoteAddr()->getIp()]++; } $wasMalleated = true; $this->counter++; $txSig = new TransactionSignature($this->adapter, new Signature($this->adapter, $sig->getR(), $this->math->sub($this->order, $sig->getS())), $txSig->getHashType()); if (!$this->adapter->validateSignatureElement($txSig->getSignature()->getS(), true)) { die('failed to produce a low-s signature'); } } return $txSig; }
/** * @param ChainState $state * @param BlockInterface $block * @param Headers $headers * @param UtxoIdx $utxoIdx * @return BlockIndex */ public function accept(ChainState $state, BlockInterface $block, Headers $headers, UtxoIdx $utxoIdx) { $bestBlock = $state->getLastBlock(); if ($bestBlock->getHash() !== $block->getHeader()->getPrevBlock()) { throw new \RuntimeException('Blocks:accept() Block does not extend this chain!'); } $index = $headers->accept($state, $block->getHeader()); $this->blockCheck->check($block)->checkContextual($block, $bestBlock); //$view = $utxoIdx->fetchView($state, $block); $view = $this->db->fetchUtxoView($block); $flagP2sh = $this->consensus->scriptVerifyPayToScriptHash($bestBlock->getHeader()->getTimestamp()); $flags = new Flags($flagP2sh ? InterpreterInterface::VERIFY_P2SH : InterpreterInterface::VERIFY_NONE); $nInputs = 0; $nFees = 0; $nSigOps = 0; $txs = $block->getTransactions(); foreach ($block->getTransactions() as $tx) { $nInputs += count($tx->getInputs()); $nSigOps += $this->blockCheck->getLegacySigOps($tx); if ($nSigOps > $this->consensus->getParams()->getMaxBlockSigOps()) { throw new \RuntimeException('Blocks::accept() - too many sigops'); } if (!$tx->isCoinbase()) { if ($flagP2sh) { $nSigOps = $this->blockCheck->getP2shSigOps($view, $tx); if ($nSigOps > $this->consensus->getParams()->getMaxBlockSigOps()) { throw new \RuntimeException('Blocks::accept() - too many sigops'); } } $fee = $this->math->sub($view->getValueIn($this->math, $tx), $tx->getValueOut()); $nFees = $this->math->add($nFees, $fee); $this->blockCheck->checkInputs($view, $tx, $index->getHeight(), $flags); } } $this->blockCheck->checkCoinbaseSubsidy($txs[0], $nFees, $index->getHeight()); $this->db->insertBlock($block); $state->updateLastBlock($index); return $index; }
/** * @param UtxoView $view * @param TransactionInterface $tx * @param $spendHeight * @return bool */ public function checkContextualInputs(UtxoView $view, TransactionInterface $tx, $spendHeight) { $valueIn = 0; for ($i = 0; $i < count($tx->getInputs()); $i++) { $utxo = $view->fetchByInput($tx->getInput($i)); /*if ($out->isCoinbase()) { // todo: cb / height if ($spendHeight - $out->getHeight() < $this->params->coinbaseMaturityAge()) { return false; } }*/ $value = $utxo->getOutput()->getValue(); $valueIn = $this->math->add($value, $valueIn); if (!$this->consensus->checkAmount($valueIn) || !$this->consensus->checkAmount($value)) { throw new \RuntimeException('CheckAmount failed for inputs value'); } } $valueOut = 0; foreach ($tx->getOutputs() as $output) { $valueOut = $this->math->add($output->getValue(), $valueOut); if (!$this->consensus->checkAmount($valueOut) || !$this->consensus->checkAmount($output->getValue())) { throw new \RuntimeException('CheckAmount failed for outputs value'); } } if ($this->math->cmp($valueIn, $valueOut) < 0) { throw new \RuntimeException('Value-in is less than value out'); } $fee = $this->math->sub($valueIn, $valueOut); if ($this->math->cmp($fee, 0) < 0) { throw new \RuntimeException('Fee is less than zero'); } if (!$this->consensus->checkAmount($fee)) { throw new \RuntimeException('CheckAmount failed for fee'); } return true; }
/** * @return Buffer */ private function serialize() { if ($this->math->cmp($this->number, '0') === 0) { return new Buffer('', 4); } // Using array of integers instead of bytes $result = []; $negative = $this->math->cmp($this->number, 0) < 0; $abs = $negative ? $this->math->sub(0, $this->number) : $this->number; while ($this->math->cmp($abs, 0) > 0) { array_unshift($result, (int) $this->math->bitwiseAnd($abs, 0xff)); $abs = $this->math->rightShift($abs, 8); } if ($result[0] & 0x80) { array_unshift($result, $negative ? 0x80 : 0x0); } else { if ($negative) { array_unshift($result, 0x80); } } return new Buffer(array_reduce($result, function ($agg, $current) { return $agg . chr($current); }), 4, $this->math); }
/** * @param $opCode * @param ScriptStack $mainStack * @param ScriptStack $altStack * @throws \BitWasp\Bitcoin\Exceptions\ScriptStackException * @throws \Exception */ public function op($opCode, ScriptStack $mainStack, ScriptStack $altStack) { $opCodes = $this->opCodes; $opName = $this->opCodes->getOp($opCode); $castToBool = $this->castToBool; if ($opName == 'OP_TOALTSTACK') { if ($mainStack->size() < 1) { throw new \Exception('Invalid stack operation OP_TOALTSTACK'); } $altStack->push($mainStack->pop()); return; } else { if ($opName == 'OP_FROMALTSTACK') { if ($altStack->size() < 1) { throw new \Exception('Invalid alt-stack operation OP_FROMALTSTACK'); } $mainStack->push($altStack->pop()); return; } else { if ($opName == 'OP_IFDUP') { // If top value not zero, duplicate it. if ($mainStack->size() < 1) { throw new \Exception('Invalid stack operation OP_IFDUP'); } $vch = $mainStack->top(-1); if ($castToBool($vch)) { $mainStack->push($vch); } return; } else { if ($opName == 'OP_DEPTH') { $num = $mainStack->size(); $bin = Buffer::hex($this->math->decHex($num)); $mainStack->push($bin); return; } else { if ($opName == 'OP_DROP') { if ($mainStack->size() < 1) { throw new \Exception('Invalid stack operation OP_DROP'); } $mainStack->pop(); return; } else { if ($opName == 'OP_DUP') { if ($mainStack->size() < 1) { throw new \Exception('Invalid stack operation OP_DUP'); } $vch = $mainStack->top(-1); $mainStack->push($vch); return; } else { if ($opName == 'OP_NIP') { if ($mainStack->size() < 2) { throw new \Exception('Invalid stack operation OP_NIP'); } $mainStack->erase(-2); return; } else { if ($opName == 'OP_OVER') { if ($mainStack->size() < 2) { throw new \Exception('Invalid stack operation OP_OVER'); } $vch = $mainStack->top(-2); $mainStack->push($vch); return; } else { if (in_array($opName, ['OP_PICK', 'OP_ROLL'])) { // cscriptnum if ($mainStack->size() < 2) { throw new \Exception('Invalid stack operation OP_PICK'); } $n = $mainStack->top(-1)->getInt(); $mainStack->pop(); if ($this->math->cmp($n, 0) < 0 || $this->math->cmp($n, $mainStack->size()) >= 0) { throw new \Exception('Invalid stack operation OP_PICK'); } $pos = $this->math->sub($this->math->sub(0, $n), 1); $vch = $mainStack->top($pos); if ($opCodes->isOp($opCode, 'OP_ROLL')) { $mainStack->erase($pos); } $mainStack->push($vch); return; } else { if ($opName == 'OP_ROT') { if ($mainStack->size() < 3) { throw new \Exception('Invalid stack operation OP_ROT'); } $mainStack->swap(-3, -2); $mainStack->swap(-2, -1); return; } else { if ($opName == 'OP_SWAP') { if ($mainStack->size() < 2) { throw new \Exception('Invalid stack operation OP_SWAP'); } $mainStack->swap(-2, -1); return; } else { if ($opName == 'OP_TUCK') { if ($mainStack->size() < 2) { throw new \Exception('Invalid stack operation OP_TUCK'); } $vch = $mainStack->top(-1); $mainStack->insert($mainStack->end() - 2, $vch); return; } else { if ($opName == 'OP_2DROP') { if ($mainStack->size() < 2) { throw new \Exception('Invalid stack operation OP_2DROP'); } $mainStack->pop(); $mainStack->pop(); return; } else { if ($opName == 'OP_2DUP') { if ($mainStack->size() < 2) { throw new \Exception('Invalid stack operation OP_2DUP'); } $string1 = $mainStack->top(-2); $string2 = $mainStack->top(-1); $mainStack->push($string1); $mainStack->push($string2); return; } else { if ($opName == 'OP_3DUP') { if ($mainStack->size() < 3) { throw new \Exception('Invalid stack operation OP_3DUP'); } $string1 = $mainStack->top(-3); $string2 = $mainStack->top(-2); $string3 = $mainStack->top(-1); $mainStack->push($string1); $mainStack->push($string2); $mainStack->push($string3); return; } else { if ($opName == 'OP_2OVER') { if ($mainStack->size() < 4) { throw new \Exception('Invalid stack operation OP_2OVER'); } $string1 = $mainStack->top(-4); $string2 = $mainStack->top(-3); $mainStack->push($string1); $mainStack->push($string2); return; } else { if ($opName == 'OP_2ROT') { if ($mainStack->size() < 6) { throw new \Exception('Invalid stack operation OP_2ROT'); } $string1 = $mainStack->top(-6); $string2 = $mainStack->top(-5); $mainStack->erase(-6); $mainStack->erase(-5); $mainStack->push($string1); $mainStack->push($string2); return; } else { if ($opName == 'OP_2SWAP') { if ($mainStack->size() < 4) { throw new \Exception('Invalid stack operation OP_2SWAP'); } $mainStack->swap(-3, -1); $mainStack->swap(-4, -2); return; } } } } } } } } } } } } } } } } } } throw new \Exception('Opcode not found'); }
/** * @param Math $math * @param TransactionInterface $tx * @return \GMP */ public function getFeePaid(Math $math, TransactionInterface $tx) { return $math->sub($this->getValueIn($math, $tx), gmp_init($tx->getValueOut())); }