/** * @param callable|null $hashFunction * @return string * @throws MerkleTreeEmpty */ public function calculateHash(callable $hashFunction = null) { $hashFxn = $hashFunction ?: function ($value) { return hash('sha256', hash('sha256', $value, true), true); }; $txCount = count($this->transactions); if ($txCount == 0) { // TODO: Probably necessary. Should always have a coinbase at least. throw new MerkleTreeEmpty('Cannot compute Merkle root of an empty tree'); } if ($txCount == 1) { $buffer = $hashFxn($this->transactions->getTransaction(0)->getBinary()); } else { // Create a fixed size Merkle Tree $tree = new FixedSizeTree($txCount + $txCount % 2, $hashFxn); // Compute hash of each transaction $last = ''; foreach ($this->transactions->getTransactions() as $i => $transaction) { $last = $transaction->getBinary(); $tree->set($i, $last); } // Check if we need to repeat the last hash (odd number of transactions) if (!$this->math->isEven($txCount)) { $tree->set($txCount, $last); } $buffer = $tree->hash(); } $hash = new Buffer(Buffertools::flipBytes($buffer)); $hash = $hash->getHex(); $this->setLastHash($hash); return $this->getLastHash(); }
public function testHashIsSerializedInReverseOrder() { $buffer = Buffer::hex('0001020300010203000102030001020300010203000102030001020300010203'); $inv = Inventory::block($buffer); $results = unpack("Vtype/H64hash", $inv->getBinary()); $parsedBuffer = Buffer::hex($results['hash']); $this->assertEquals($buffer->getHex(), Buffertools::flipBytes($parsedBuffer)->getHex()); }
/** * @dataProvider getVectors */ public function testByteStringLe($math, $size, $string) { $buffer = Buffer::hex($string, $size); $t = new ByteString($math, $size, ByteOrder::LE); $out = $t->write($buffer); $eFlipped = Buffertools::flipBytes(pack("H*", $string)); $this->assertEquals($eFlipped, $out); $parser = new Parser(new Buffer($out)); $this->assertEquals($string, $t->read($parser)->getHex()); }
/** * @param $bitSize * @return array */ private function generateSizeBasedTests($bitSize, $byteOrder) { $math = EccFactory::getAdapter(); $halfPos = $math->baseConvert(str_pad('7', $bitSize / 4, 'f', STR_PAD_RIGHT), 16, 10); $maxPos = $math->baseConvert(str_pad('', $bitSize / 4, 'f', STR_PAD_RIGHT), 16, 10); $test = function ($integer) use($bitSize, $math, $byteOrder) { $hex = str_pad($math->baseConvert($integer, 10, 16), $bitSize / 4, '0', STR_PAD_LEFT); if ($byteOrder == ByteOrder::LE) { $hex = Buffertools::flipBytes(Buffer::hex($hex))->getHex(); } return [$integer, $hex, null]; }; return [$test(0), $test(1), $test($halfPos), $test($maxPos)]; }
public function testFlipBytes() { $buffer = Buffer::hex('41'); $string = $buffer->getBinary(); $flip = Buffertools::flipBytes($string); $this->assertSame($flip, $string); $buffer = Buffer::hex('4141'); $string = $buffer->getBinary(); $flip = Buffertools::flipBytes($string); $this->assertSame($flip, $string); $buffer = Buffer::hex('4142'); $string = $buffer->getBinary(); $flip = Buffertools::flipBytes($string); $this->assertSame($flip, chr(0x42) . chr(0x41)); $buffer = Buffer::hex('0102030405060708'); $string = $buffer->getBinary(); $flip = Buffertools::flipBytes($string); $this->assertSame($flip, chr(0x8) . chr(0x7) . chr(0x6) . chr(0x5) . chr(0x4) . chr(0x3) . chr(0x2) . chr(0x1)); }
/** * @return Buffer */ public function flip() { return Buffertools::flipBytes($this); }
/** * Calculate the hash of the current transaction, when you are looking to * spend $txOut, and are signing $inputToSign. The SigHashType defaults to * SIGHASH_ALL, though SIGHASH_SINGLE, SIGHASH_NONE, SIGHASH_ANYONECANPAY * can be used. * * @param ScriptInterface $txOutScript * @param int $inputToSign * @param int $sighashType * @return BufferInterface * @throws \Exception */ public function calculate(ScriptInterface $txOutScript, $inputToSign, $sighashType = SigHash::ALL) { $math = Bitcoin::getMath(); $tx = new TxMutator($this->transaction); $inputs = $tx->inputsMutator(); $outputs = $tx->outputsMutator(); // Default SIGHASH_ALL procedure: null all input scripts foreach ($inputs as $input) { $input->script(new Script()); } $inputs[$inputToSign]->script($txOutScript); if ($math->cmp($math->bitwiseAnd($sighashType, 31), SigHash::NONE) === 0) { // Set outputs to empty vector, and set sequence number of inputs to 0. $outputs->null(); // Let the others update at will. Set sequence of inputs we're not signing to 0. foreach ($inputs as $i => $input) { if ($i !== $inputToSign) { $input->sequence(0); } } } elseif ($math->cmp($math->bitwiseAnd($sighashType, 31), SigHash::SINGLE) === 0) { // Resize output array to $inputToSign + 1, set remaining scripts to null, // and set sequence's to zero. $nOutput = $inputToSign; if ($nOutput >= $this->nOutputs) { return Buffer::hex('0100000000000000000000000000000000000000000000000000000000000000', 32, $math); } // Resize, set to null $outputs->slice(0, $nOutput + 1); for ($i = 0; $i < $nOutput; $i++) { $outputs[$i]->null(); } // Let the others update at will. Set sequence of inputs we're not signing to 0 foreach ($inputs as $i => $input) { if ($i !== $inputToSign) { $input->sequence(0); } } } // This can happen regardless of whether it's ALL, NONE, or SINGLE if ($math->cmp($math->bitwiseAnd($sighashType, SigHash::ANYONECANPAY), 0) > 0) { $input = $inputs[$inputToSign]->done(); $inputs->null()->add($input); } return Hash::sha256d(Buffertools::concat($tx->done()->getBuffer(), Buffertools::flipBytes(Buffer::int($sighashType, 4, $math)))); }
/** * Write $data as $bytes bytes. Can be flipped if needed. * * @param integer $bytes * @param $data * @param bool $flipBytes * @return $this */ public function writeBytes($bytes, $data, $flipBytes = false) { // Treat $data to ensure it's a buffer, with the correct size if ($data instanceof SerializableInterface) { $data = $data->getBuffer(); } if ($data instanceof Buffer) { // only create a new buffer if the size does not match if ($data->getSize() != $bytes) { $data = new Buffer($data->getBinary(), $bytes, $this->math); } } else { // Convert to a buffer $data = Buffer::hex($data, $bytes, $this->math); } // At this point $data will be a Buffer $binary = $data->getBinary(); if ($flipBytes) { $binary = Buffertools::flipBytes($binary); } $this->string .= $binary; return $this; }
/** * @return string */ public function getTransactionId() { $hash = bin2hex(Buffertools::flipBytes(Hash::sha256d($this->getBuffer()))); return $hash; }