/** * @param $hex * @return PublicKey * @throws \Exception */ public function parse($hex) { $hex = Buffer::hex($hex); if (!in_array($hex->getSize(), [PublicKey::LENGTH_COMPRESSED, PublicKey::LENGTH_UNCOMPRESSED])) { throw new \Exception('Invalid hex string, must match size of compressed or uncompressed public key'); } return $this->ecAdapter->publicKeyFromBuffer($hex); }
/** * @param $mnemonic * @return Buffer */ public function mnemonicToEntropy($mnemonic) { $math = $this->ecAdapter->getMath(); $words = explode(" ", $mnemonic); if ($math->mod(count($words), 3) != 0) { throw new \InvalidArgumentException('Invalid mnemonic'); } $bits = array(); foreach ($words as $word) { $idx = $this->wordList->getIndex($word); $bits[] = str_pad($math->baseConvert($idx, 10, 2), 11, '0', STR_PAD_LEFT); } $bits = implode("", $bits); $CS = strlen($bits) / 33; $ENT = strlen($bits) - $CS; $csBits = substr($bits, -1 * $CS); $entBits = substr($bits, 0, -1 * $CS); $binary = ''; $bitsInChar = 8; for ($i = 0; $i < $ENT; $i += $bitsInChar) { // Extract 8 bits at a time, convert to hex, pad, and convert to binary. $eBits = substr($entBits, $i, $bitsInChar); $binary .= hex2bin(str_pad($math->baseConvert($eBits, 2, 16), 2, '0', STR_PAD_LEFT)); } $entropy = new Buffer($binary); if ($csBits !== $this->calculateChecksum($entropy, $CS)) { throw new \InvalidArgumentException('Checksum does not match'); } return $entropy; }
/** * @param NetworkInterface $network * @return string */ public function toWif(NetworkInterface $network = null) { $network = $network ?: Bitcoin::getNetwork(); $wifSerializer = new WifPrivateKeySerializer($this->ecAdapter->getMath(), new HexPrivateKeySerializer($this->ecAdapter)); $wif = $wifSerializer->serialize($network, $this); return $wif; }
/** * @param string $mnemonic * @return Buffer */ public function mnemonicToEntropy($mnemonic) { $math = $this->ecAdapter->getMath(); $wordList = $this->wordList; $words = explode(" ", $mnemonic); $n = count($wordList); $out = ''; $thirdWordCount = count($words) / 3; for ($i = 0; $i < $thirdWordCount; $i++) { list($word1, $word2, $word3) = array_slice($words, $math->mul(3, $i), 3); $index1 = $wordList->getIndex($word1); $index2 = $wordList->getIndex($word2); $index3 = $wordList->getIndex($word3); $x = $math->add($index1, $math->add($math->mul($n, $math->mod($index2 - $index1, $n)), $math->mul($n, $math->mul($n, $math->mod($index3 - $index2, $n))))); $out .= str_pad($math->decHex($x), 8, '0', STR_PAD_LEFT); } return Buffer::hex($out); }
/** * Decodes a BIP32 path into it's actual 32bit sequence numbers: ie, m/0/1'/2/3' -> m/0/2147483649/2/2147483651 * * @param string $path * @return string */ public function decodePath($path) { $pathPieces = explode("/", $path); if (strlen($path) == 0 || count($pathPieces) == 0) { throw new \InvalidArgumentException('Invalid path passed to decodePath()'); } $newPath = array(); $helper = new HierarchicalKeySequence($this->ecAdapter->getMath()); foreach ($pathPieces as $c => $sequence) { $newPath[] = $helper->fromNode($sequence); } $path = implode("/", $newPath); return $path; }
/** * @param TransactionInterface $tx * @param integer $inputToExtract * @throws \Exception */ public function extractSigs(TransactionInterface $tx, $inputToExtract) { $parsed = $tx->getInputs()->getInput($inputToExtract)->getScript()->getScriptParser()->parse(); $size = count($parsed); switch ($this->getScriptType()) { case OutputClassifier::PAYTOPUBKEYHASH: // Supply signature and public key in scriptSig if ($size == 2) { $this->setSignatures([TransactionSignatureFactory::fromHex($parsed[0]->getHex(), $this->ecAdapter->getMath())]); $this->setPublicKeys([PublicKeyFactory::fromHex($parsed[1]->getHex(), $this->ecAdapter)]); } break; case OutputClassifier::PAYTOPUBKEY: // Only has a signature in the scriptSig if ($size == 1) { $this->setSignatures([TransactionSignatureFactory::fromHex($parsed[0]->getHex(), $this->ecAdapter->getMath())]); } break; case OutputClassifier::MULTISIG: $keys = $this->getRedeemScript()->getKeys(); foreach ($keys as $idx => $key) { $this->setSignature($idx, null); } if ($size > 2 && $size <= $this->getRedeemScript()->getKeyCount() + 2) { $sigs = []; foreach ($keys as $key) { $sigs[$key->getPubKeyHash()->getHex()] = []; } // Extract Signatures (as buffers), then compile arrays of [pubkeyHash => signature] $sigHash = new SignatureHash($tx); foreach (array_slice($parsed, 1, -1) as $item) { if ($item instanceof Buffer) { $txSig = TransactionSignatureFactory::fromHex($item, $this->ecAdapter->getMath()); $linked = $this->ecAdapter->associateSigs([$txSig->getSignature()], $sigHash->calculate($this->getRedeemScript(), $inputToExtract, $txSig->getHashType()), $this->getRedeemScript()->getKeys()); if (count($linked)) { $key = array_keys($linked)[0]; $sigs[$key] = array_merge($sigs[$key], [$txSig]); } } } // We have all the signatures from the tx now. array_shift the sigs for a public key, as it's encountered. foreach ($keys as $idx => $key) { $hash = $key->getPubKeyHash()->getHex(); $this->setSignature($idx, isset($sigs[$hash]) ? array_shift($sigs[$hash]) : null); } } break; } }
/** * @param string $message * @param PrivateKeyInterface $privateKey * @return SignedMessage */ public function sign($message, PrivateKeyInterface $privateKey) { $hash = $this->calculateMessageHash($message); return new SignedMessage($message, $this->ecAdapter->signCompact($hash, $privateKey, new Rfc6979($this->ecAdapter, $privateKey, $hash, 'sha256'))); }
/** * @param int $bytes * @return Buffer */ public function bytes($bytes) { $integer = $this->hmac->generate($this->ecAdapter->getGenerator()->getOrder()); return Buffer::hex($this->ecAdapter->getMath()->decHex($integer)); }
/** * @param PrivateKeyInterface $privKey * @param Buffer $hash * @param $sigHashType * @return TransactionSignature */ public function sign(PrivateKeyInterface $privKey, Buffer $hash, $sigHashType) { return new TransactionSignature($this->ecAdapter->sign($hash, $privKey, $this->deterministicSignatures ? new Rfc6979($this->ecAdapter, $privKey, $hash, 'sha256') : new Random()), $sigHashType); }
/** * @return Buffer */ public function getMPK() { $math = $this->ecAdapter->getMath(); $point = $this->getMasterPublicKey()->getPoint(); return Buffertools::concat(Buffer::hex($math->decHex($point->getX()), 32), Buffer::hex($math->decHex($point->getY()), 32)); }
/** * @param PrivateKeyInterface $privateKey * @return Buffer */ public function serialize(PrivateKeyInterface $privateKey) { $multiplier = $privateKey->getSecretMultiplier(); return Buffer::hex($this->ecAdapter->getMath()->decHex($multiplier), 32); }
/** * @return bool */ public function run() { $math = $this->ecAdapter->getMath(); $opcodes = $this->script->getOpCodes(); $flags = $this->flags; $mainStack = $this->state->getMainStack(); $altStack = $this->state->getAltStack(); $vfStack = $this->state->getVfStack(); $this->hashStartPos = 0; $this->opCount = 0; $parser = $this->script->getScriptParser(); $_bn0 = Buffer::hex('00'); $_bn1 = Buffer::hex('01'); if ($this->script->getBuffer()->getSize() > 10000) { return false; } $checkFExec = function () use(&$vfStack) { $c = 0; $len = $vfStack->end(); for ($i = 0; $i < $len; $i++) { if ($vfStack->top(0 - $len - $i) == true) { $c++; } } return (bool) $c; }; try { while ($parser->next($opCode, $pushData) === true) { $fExec = !$checkFExec(); // If pushdata was written to, if ($pushData instanceof Buffer && $pushData->getSize() > InterpreterInterface::MAX_SCRIPT_ELEMENT_SIZE) { throw new \Exception('Error - push size'); } // OP_RESERVED should not count towards opCount if ($this->script->getOpcodes()->cmp($opCode, 'OP_16') > 0 && ++$this->opCount) { $this->checkOpcodeCount(); } if ($this->checkDisabledOpcodes) { if ($this->isDisabledOp($opCode)) { throw new \Exception('Disabled Opcode'); } } if ($fExec && $opCode >= 0 && $opcodes->cmp($opCode, 'OP_PUSHDATA4') <= 0) { // In range of a pushdata opcode if ($flags->checkFlags(InterpreterInterface::VERIFY_MINIMALDATA) && !$this->checkMinimalPush($opCode, $pushData)) { throw new ScriptRuntimeException(InterpreterInterface::VERIFY_MINIMALDATA, 'Minimal pushdata required'); } $mainStack->push($pushData); //echo " - [pushed '" . $pushData->getHex() . "']\n"; } elseif ($fExec || $opcodes->isOp($opCode, 'OP_IF') <= 0 && $opcodes->isOp($opCode, 'OP_ENDIF')) { //echo " - [". $opcodes->getOp($opCode) . "]\n"; switch ($opCode) { case $opcodes->getOpByName('OP_1NEGATE'): case $opcodes->cmp($opCode, 'OP_1') >= 0 && $opcodes->cmp($opCode, 'OP_16') <= 0: $pushInt = new PushIntOperation($opcodes); $pushInt->op($opCode, $mainStack); break; case $opcodes->cmp($opCode, 'OP_NOP1') >= 0 && $opcodes->cmp($opCode, 'OP_NOP10') <= 0: if ($flags->checkFlags(InterpreterInterface::VERIFY_DISCOURAGE_UPGRADABLE_NOPS)) { throw new ScriptRuntimeException(InterpreterInterface::VERIFY_DISCOURAGE_UPGRADABLE_NOPS, 'Upgradable NOPS found - this is discouraged'); } break; case $opcodes->getOpByName('OP_NOP'): case $opcodes->isOp($opCode, 'OP_IF') || $opcodes->isOp($opCode, 'OP_NOTIF'): case $opcodes->isOp($opCode, 'OP_ELSE') || $opcodes->isOp($opCode, 'OP_ENDIF'): case $opcodes->getOpByName('OP_VERIFY'): case $opcodes->getOpByName('OP_RETURN'): $flowControl = new FlowControlOperation($opcodes, function (Buffer $buffer) { return $this->castToBool($buffer); }); $flowControl->op($opCode, $mainStack, $vfStack, $fExec); break; case $opcodes->getOpByName('OP_RESERVED'): // todo break; case $opcodes->getOpByName('OP_TOALTSTACK'): case $opcodes->getOpByName('OP_FROMALTSTACK'): case $opcodes->cmp($opCode, 'OP_IFDUP') >= 0 && $opcodes->cmp($opCode, 'OP_TUCK') <= 0: case $opcodes->cmp($opCode, 'OP_2DROP') >= 0 && $opcodes->cmp($opCode, 'OP_2SWAP') <= 0: $stackOper = new StackOperation($opcodes, $this->ecAdapter->getMath(), function (Buffer $buffer) { return $this->castToBool($buffer); }); $stackOper->op($opCode, $mainStack, $altStack); break; case $opcodes->getOpByName('OP_SIZE'): if ($mainStack->size() < 1) { throw new \Exception('Invalid stack operation OP_SIZE'); } // todo: Int sizes? $vch = $mainStack->top(-1); $size = Buffer::hex($math->decHex($vch->getSize())); $mainStack->push($size); break; case $opcodes->getOpByName('OP_EQUAL'): // cscriptnum // cscriptnum case $opcodes->getOpByName('OP_EQUALVERIFY'): //case $this->isOp($opCode, 'OP_NOTEQUAL'): // use OP_NUMNOTEQUAL if ($mainStack->size() < 2) { throw new \Exception('Invalid stack operation OP_EQUAL'); } $vch1 = $mainStack->top(-2); $vch2 = $mainStack->top(-1); $equal = $vch1->getBinary() === $vch2->getBinary(); // OP_NOTEQUAL is disabled //if ($this->isOp($opCode, 'OP_NOTEQUAL')) { // $equal = !$equal; //} $mainStack->pop(); $mainStack->pop(); $mainStack->push($equal ? $_bn1 : $_bn0); if ($opcodes->isOp($opCode, 'OP_EQUALVERIFY')) { if ($equal) { $mainStack->pop(); } else { throw new \Exception('Error EQUALVERIFY'); } } break; // Arithmetic operations // Arithmetic operations case $opcodes->cmp($opCode, 'OP_1ADD') >= 0 && $opcodes->cmp($opCode, 'OP_WITHIN') <= 0: $arithmetic = new ArithmeticOperation($opcodes, $this->ecAdapter->getMath(), function (Buffer $buffer) { return $this->castToBool($buffer); }, $_bn0, $_bn1); $arithmetic->op($opCode, $mainStack); break; // Hash operations // Hash operations case $opcodes->cmp($opCode, 'OP_RIPEMD160') >= 0 && $opcodes->cmp($opCode, 'OP_HASH256') <= 0: $hash = new HashOperation($opcodes); $hash->op($opCode, $mainStack); break; case $opcodes->getOpByName('OP_CODESEPARATOR'): $this->hashStartPos = $parser->getPosition(); break; case $opcodes->getOpByName('OP_CHECKSIG'): case $opcodes->getOpByName('OP_CHECKSIGVERIFY'): if ($mainStack->size() < 2) { throw new \Exception('Invalid stack operation'); } $vchPubKey = $mainStack->top(-1); $vchSig = $mainStack->top(-2); $scriptCode = new Script($this->script->getBuffer()->slice($this->hashStartPos)); $success = $this->checkSig($scriptCode, $vchSig, $vchPubKey); $mainStack->pop(); $mainStack->pop(); $mainStack->push($success ? $_bn1 : $_bn0); if ($opcodes->isOp($opCode, 'OP_CHECKSIGVERIFY')) { if ($success) { $mainStack->pop(); } else { throw new \Exception('Checksig verify'); } } break; case $opcodes->getOpByName('OP_CHECKMULTISIG'): case $opcodes->getOpByName('OP_CHECKMULTISIGVERIFY'): $i = 1; if ($mainStack->size() < $i) { throw new \Exception('Invalid stack operation'); } $math = $this->ecAdapter->getMath(); $keyCount = $mainStack->top(-$i)->getInt(); if ($math->cmp($keyCount, 0) < 0 || $math->cmp($keyCount, 20) > 0) { throw new \Exception('OP_CHECKMULTISIG: Public key count exceeds 20'); } $this->opCount += $keyCount; $this->checkOpcodeCount(); // Extract positions of the keys, and signatures, from the stack. $ikey = ++$i; $i += $keyCount; if ($mainStack->size() < $i) { throw new \Exception('Invalid stack operation'); } $sigCount = $mainStack->top(-$i)->getInt(); // cscriptnum if ($math->cmp($sigCount, 0) < 0 || $math->cmp($sigCount, $keyCount) > 0) { throw new \Exception('Invalid Signature count'); } $isig = ++$i; $i += $sigCount; // Extract the script since the last OP_CODESEPARATOR $scriptCode = new Script($this->script->getBuffer()->slice($this->hashStartPos)); $fSuccess = true; while ($fSuccess && $sigCount > 0) { // Fetch the signature and public key $sig = $mainStack->top(-$isig); $pubkey = $mainStack->top(-$ikey); // Erase the signature and public key. $mainStack->erase(-$isig); $mainStack->erase(-$ikey); // Decrement $i, since we are consuming stack values. $i -= 2; if ($this->checksig($scriptCode, $sig, $pubkey)) { $isig++; $sigCount--; } $ikey++; $keyCount--; // If there are more signatures left than keys left, // then too many signatures have failed. Exit early, // without checking any further signatures. if ($sigCount > $keyCount) { $fSuccess = false; } } while ($i-- > 1) { $mainStack->pop(); } // A bug causes CHECKMULTISIG to consume one extra argument // whose contents were not checked in any way. // // Unfortunately this is a potential source of mutability, // so optionally verify it is exactly equal to zero prior // to removing it from the stack. if ($mainStack->size() < 1) { throw new \Exception('Invalid stack operation'); } if ($flags->checkFlags(InterpreterInterface::VERIFY_NULL_DUMMY) && $mainStack->top(-1)->getSize()) { throw new ScriptRuntimeException(InterpreterInterface::VERIFY_NULL_DUMMY, 'Extra P2SH stack value should be OP_0'); } $mainStack->pop(); $mainStack->push($fSuccess ? $_bn1 : $_bn0); if ($opcodes->isOp($opCode, 'OP_CHECKMULTISIGVERIFY')) { if ($fSuccess) { $mainStack->pop(); } else { throw new \Exception('OP_CHECKMULTISIG verify'); } } break; default: throw new \Exception('Opcode not found'); } if ($mainStack->size() + $altStack->size() > 1000) { throw new \Exception('Invalid stack size, exceeds 1000'); } } } if (!$vfStack->end() == 0) { throw new \Exception('Unbalanced conditional at script end'); } return true; } catch (ScriptRuntimeException $e) { //echo "\n Runtime: " . $e->getMessage() . "\n"; // Failure due to script tags, can access flag: $e->getFailureFlag() return false; } catch (\Exception $e) { //echo "\n General: " . $e->getMessage() ; return false; } }
/** * @param int $tweak * @return PublicKeyInterface */ public function tweakMul($tweak) { return $this->ecAdapter->publicKeyMul($this, $tweak); }