public function testNetworkSerializer() { $network = Bitcoin::getDefaultNetwork(); $parser = new NetworkMessageSerializer(Bitcoin::getDefaultNetwork()); $factory = new Factory($network, new Random()); $version = '1'; $relayUntil = '9999999'; $expiration = '9898989'; $id = '123'; $cancel = '0'; $minVer = '0'; $maxVer = '0'; $priority = '50'; $comment = new Buffer('comment'); $statusBar = new Buffer('statusBar'); $setCancel = [1, 2]; $setSubVer = [50, 99]; $detail = new AlertDetail($version, $relayUntil, $expiration, $id, $cancel, $minVer, $maxVer, $priority, $comment, $statusBar, $setCancel, $setSubVer); $adapter = EcAdapterFactory::getPhpEcc(new Math(), EccFactory::getSecgCurves()->generator256k1()); $sig = new Signature($adapter, '1', '1'); $alert = $factory->alert($detail, $sig); $serialized = $alert->getNetworkMessage()->getBuffer(); $parsed = $parser->parse($serialized)->getPayload(); /** @var \BitWasp\Bitcoin\Networking\Messages\Alert $parsed */ $this->assertEquals($alert->getDetail(), $parsed->getDetail()); $this->assertEquals($alert->getSignature()->getR(), $parsed->getSignature()->getR()); $this->assertEquals($alert->getSignature()->getS(), $parsed->getSignature()->getS()); }
/** * @param CertificateInfo $info * @return Sequence */ public function getCertInfoAsn(CertificateInfo $info) { $curve = EccFactory::getSecgCurves()->curve256k1(); if ($this->extension === null) { return new Sequence(new Integer($info->getSerialNo()), new Sequence(SigAlgorithmOidMapper::getSigAlgorithmOid($info->getSigAlgorithm())), $this->subjectSer->toAsn($info->getIssuerInfo()), new Sequence(new UTCTime($info->getValidityStart()->format(CertificateSerializer::UTCTIME_FORMAT)), new UTCTime($info->getValidityEnd()->format(CertificateSerializer::UTCTIME_FORMAT))), $this->subjectSer->toAsn($info->getSubjectInfo()), $this->getSubjectKeyASN($curve, $info->getPublicKey())); } return new Sequence(new Integer($info->getSerialNo()), new Sequence(SigAlgorithmOidMapper::getSigAlgorithmOid($info->getSigAlgorithm())), $this->subjectSer->toAsn($info->getIssuerInfo()), new Sequence(new UTCTime($info->getValidityStart()->format(CertificateSerializer::UTCTIME_FORMAT)), new UTCTime($info->getValidityEnd()->format(CertificateSerializer::UTCTIME_FORMAT))), $this->subjectSer->toAsn($info->getSubjectInfo()), $this->getSubjectKeyASN($curve, $info->getPublicKey()), $this->extension->apply($info)); }
/** * * @dataProvider getAdapters */ public function testSecp256r1EquivalenceToNistP192(MathAdapterInterface $adapter) { $secpFactory = EccFactory::getSecgCurves($adapter); $nistFactory = EccFactory::getNistCurves($adapter); $signer = new Signer($adapter); $secret = $adapter->hexDec('DC51D3866A15BACDE33D96F992FCA99DA7E6EF0934E7097559C27F1614C88A7F'); $secpKey = $secpFactory->generator256r1()->getPrivateKeyFrom($secret); $nistKey = $nistFactory->generator256()->getPrivateKeyFrom($secret); $randomK = RandomGeneratorFactory::getRandomGenerator()->generate($secpKey->getPoint()->getOrder()); $message = RandomGeneratorFactory::getRandomGenerator()->generate($secpKey->getPoint()->getOrder()); $sigSecp = $signer->sign($secpKey, $message, $randomK); $sigNist = $signer->sign($nistKey, $message, $randomK); $this->assertEquals($sigNist->getR(), $sigSecp->getR()); $this->assertEquals($sigNist->getS(), $sigSecp->getS()); }
protected function setUp() { // file containing a json array of {compressed=>'', decompressed=>''} values // of compressed and uncompressed ECDSA public keys (testing secp256k1 curve) $file_comp = TEST_DATA_DIR . '/compression.json'; if (!file_exists($file_comp)) { $this->fail('Key compression input data not found'); } $file_sqrt = TEST_DATA_DIR . '/square_root_mod_p.json'; if (!file_exists($file_sqrt)) { $this->fail('Square root input data not found'); } $this->generator = EccFactory::getSecgCurves()->generator256k1(); $this->compression_data = json_decode(file_get_contents($file_comp)); $this->sqrt_data = json_decode(file_get_contents($file_sqrt)); }
/** * Load the generator to be used throughout */ public static function getGenerator() { return EccFactory::getSecgCurves(self::getMath())->generator256k1(); }
/** * Public Key From MPK * * This function is used to generate a public key from the supplied * $mpk - the master public key, and an $iteration indicating which * address in the sequence should be generated. * * @param string $mpk * @param int $iteration * @return string */ public static function public_key_from_mpk($mpk, $iteration, $change = 0, $compressed = false) { $change = $change == 0 ? '0' : '1'; $math = EccFactory::getAdapter(); $gen = EccFactory::getSecgCurves($math)->generator256k1(); // Generate the curve, and the generator point. // Prepare the input values, by converting the MPK to X and Y coordinates $x = $math->hexDec(substr($mpk, 0, 64)); $y = $math->hexDec(substr($mpk, 64, 64)); // Generate a scalar from the $iteration and $mpk $z = $math->hexDec(hash('sha256', hash('sha256', "{$iteration}:{$change}:" . pack('H*', $mpk), true))); // Add the Point defined by $x and $y, to the result of EC multiplication of $z by $gen $pt = $gen->getCurve()->getPoint($x, $y, $gen->getOrder()); $pt = $pt->add($gen->mul($z)); // Generate the uncompressed public key. $keystr = '04' . str_pad($math->decHex($pt->getX()), 64, '0', STR_PAD_LEFT) . str_pad($math->decHex($pt->getY()), 64, '0', STR_PAD_LEFT); return $compressed == true ? BitcoinLib::compress_public_key($keystr) : $keystr; }
private function getCurve() { if (!is_null($this->curve)) { return $this->curve; } switch ($this->type) { case self::TYPE_SECP256R1: $curve = EccFactory::getSecgCurves()->curve256r1(); break; case self::TYPE_SECP384R1: $curve = EccFactory::getSecgCurves()->curve384r1(); break; default: return null; } $this->curve = $curve; return $this->curve; }
public function testSignAndIsCanonical() { $cnt = (getenv('BITCOINLIB_EXTENSIVE_TESTING') ?: 1) * 10; $math = EccFactory::getAdapter(); $G = EccFactory::getSecgCurves()->generator256k1(); $private = $G->createPrivateKey(); for ($i = 0; $i < $cnt; $i++) { $randomMsgHash = $math->hexDec((string) hash('sha256', 'random' . $i)); $randomK = $math->hexDec((string) bin2hex(mcrypt_create_iv(32, \MCRYPT_DEV_URANDOM))); $signer = new \Mdanter\Ecc\Crypto\Signature\Signer($math); $sign = $signer->sign($private, $randomMsgHash, $randomK); $this->assertInstanceOf('Mdanter\\Ecc\\Crypto\\Signature\\Signature', $sign); $sig = RawTransaction::encode_signature($sign); $this->assertTrue(RawTransaction::is_canonical_signature($sig)); } }
/** * @dataProvider getAdapters */ public function testSimpleDeterministicAlgorithm(MathAdapterInterface $math) { // Set $offset to '0' to confirm it matches with the first output of this program $G = EccFactory::getSecgCurves($math)->generator256k1(); $secret = '2'; $sharedOffsetDerivedFromMasterPubkey = '2'; $pubkey = $math->getEcMath($G, $secret)->getPoint(); // (P+o)%n -> Only has point $pubData = $math->getEcMath($G, $pubkey)->add($sharedOffsetDerivedFromMasterPubkey); $this->assertSame('point', $pubData->getType()); $this->assertSame('103388573995635080359749164254216598308788835304023601477803095234286494993683', $pubData->result()->getX()); $this->assertSame('37057141145242123013015316630864329550140216928701153669873286428255828810018', $pubData->result()->getY()); // (k+o)%n -> Result is int, for the same point. $prvData = $math->getEcMath($G, $secret)->add($sharedOffsetDerivedFromMasterPubkey); $this->assertSame('int', $prvData->getType()); $this->assertSame('4', $prvData->result()); $pub = $prvData->getPoint(); $this->assertSame('103388573995635080359749164254216598308788835304023601477803095234286494993683', $pub->getX()); $this->assertSame('37057141145242123013015316630864329550140216928701153669873286428255828810018', $pub->getY()); }
/** * Sign * * This function accepts the same parameters as signrawtransaction. * $raw_transaction is a hex encoded string for an unsigned/partially * signed transaction. $inputs is an array, containing the txid/vout/ * scriptPubKey/redeemscript. $priv_keys contains WIF keys. * * The function looks at each TxIn and tries to sign, if the hash160 * belongs to a key specified in the wallet. * * @param array $wallet * @param string $raw_transaction * @param string $inputs * @param string $magic_byte * @param string $magic_p2sh_byte * @return array */ public static function sign($wallet, $raw_transaction, $inputs, $magic_byte = null, $magic_p2sh_byte = null) { $math = EccFactory::getAdapter(); $generator = EccFactory::getSecgCurves($math)->generator256k1(); $magic_byte = BitcoinLib::magicByte($magic_byte); $magic_p2sh_byte = BitcoinLib::magicP2SHByte($magic_p2sh_byte); // Generate digests of inputs to sign. $message_hash = self::_create_txin_signature_hash($raw_transaction, $inputs); $inputs_arr = (array) json_decode($inputs); // Generate an association of expected hash160's and related information. $decode = self::decode($raw_transaction); $req_sigs = 0; $sign_count = 0; foreach ($decode['vin'] as $vin => $input) { $scriptPubKey = self::_decode_scriptPubKey($inputs_arr[$vin]->scriptPubKey); $tx_info = self::_get_transaction_type($scriptPubKey, $magic_byte, $magic_p2sh_byte); if (isset($wallet[$tx_info['hash160']])) { $key_info = $wallet[$tx_info['hash160']]; $message_hash_dec = $math->hexDec($message_hash[$vin]); if ($key_info['type'] == 'scripthash') { $signatures = self::extract_input_signatures_p2sh($input, $message_hash[$vin], $key_info); $sign_count += count($signatures); // Create Signature foreach ($key_info['keys'] as $key) { $key_dec = $math->hexDec($key['private_key']); $k = $math->hexDec((string) bin2hex(mcrypt_create_iv(32, \MCRYPT_DEV_URANDOM))); $signer = new Signer($math); $_private_key = $generator->getPrivateKeyFrom($key_dec); $sign = $signer->sign($_private_key, $message_hash_dec, $k); if ($sign !== false) { $sign_count++; $signatures[$key['public_key']] = self::encode_signature($sign); } } $decode['vin'][$vin]['scriptSig']['hex'] = self::_apply_sig_scripthash_multisig($signatures, $key_info); // Increase required # signature counter. $req_sigs += $key_info['required_signature_count']; } if ($key_info['type'] == 'pubkeyhash') { $key_dec = $math->hexDec($key_info['private_key']); $signer = new Signer($math); $_private_key = $generator->getPrivateKeyFrom($key_dec); $sign = $signer->sign($_private_key, $message_hash_dec, $math->hexDec((string) bin2hex(mcrypt_create_iv(32, \MCRYPT_DEV_URANDOM)))); if ($sign !== false) { $sign_count++; $decode['vin'][$vin]['scriptSig']['hex'] = self::_apply_sig_pubkeyhash(self::encode_signature($sign), $key_info['public_key']); } $req_sigs++; } } else { $req_sigs++; } } $new_raw = self::encode($decode); // If the transaction isn't fully signed, return false. // If it's fully signed, perform signature verification, return true if valid, or invalid if signatures are incorrect. $complete = $req_sigs - $sign_count <= 0 ? self::validate_signed_transaction($new_raw, $inputs, $magic_byte, $magic_p2sh_byte) == true ? 'true' : 'false' : 'false'; return array('hex' => $new_raw, 'complete' => $complete, 'sign_count' => $sign_count, 'req_sigs' => $req_sigs); }
/** * Encode Signature * * This function accepts a signature object, and information about * the txout being spent, and the relevant key for signing, and * encodes the signature in DER format. * * @param Signature $signature * @return string */ public static function encode_signature(Signature $signature) { $math = EccFactory::getAdapter(); $generator = EccFactory::getSecgCurves($math)->generator256k1(); // if S is > half then we substract from N if (self::check_signature_is_high_s($signature)) { $n = $generator->getOrder(); $signature = new Signature($signature->getR(), $math->sub($n, $signature->getS())); } $rBin = pack("H*", BitcoinLib::hex_encode($signature->getR())); $sBin = pack("H*", BitcoinLib::hex_encode($signature->getS())); // Pad R and S if their highest bit is flipped, ie, // they are negative. $rt = $rBin[0] & pack('H*', '80'); if (ord($rt) == 128) { $rBin = pack('H*', '00') . $rBin; } $st = $sBin[0] & pack('H*', '80'); if (ord($st) == 128) { $sBin = pack('H*', '00') . $sBin; } $r = bin2hex($rBin); $s = bin2hex($sBin); // Create the signature. $der_sig = '30' . self::_dec_to_bytes(4 + (strlen($r) + strlen($s)) / 2, 1) . '02' . self::pushdata($r) . '02' . self::pushdata($s) . '01'; return $der_sig; }
/** * */ public function setUp() { $this->math = EccFactory::getAdapter(); $this->G = EccFactory::getSecgCurves()->generator256k1(); }
/** * Check Valid HMAC Key * * This function checks that the generated private keys meet the standard * for private keys, as imposed by the secp256k1 curve. The key can't * be zero, nor can it >= $n, which is the order of the secp256k1 * curve. Returning false trigger an error, or cause the program to * increase the address number and rerun the CKD function. * * @param string $key * @return boolean */ public static function check_valid_hmac_key($key) { $math = EccFactory::getAdapter(); $g = EccFactory::getSecgCurves($math)->generator256k1(); $n = $g->getOrder(); // initialize the key as a base 16 number. $g_l = $math->hexDec($key); // compare it to zero $_equal_zero = $math->cmp($g_l, 0); // compare it to the order of the curve $_GE_n = $math->cmp($g_l, $n); // Check for invalid data if ($_equal_zero == 0 || $_GE_n == 1 || $_GE_n == 0) { return false; } return true; }
/** * verify a signed message * * @param $address * @param $signature * @param $message * @return bool * @throws \Exception */ public static function verifyMessage($address, $signature, $message) { $math = EccFactory::getAdapter(); $generator = EccFactory::getSecgCurves($math)->generator256k1(); // extract parameters $address = substr(hex2bin(self::base58_decode($address)), 0, -4); if (strlen($address) != 21 || $address[0] != hex2bin(self::magicByte())) { throw new \InvalidArgumentException('invalid Bitcoin address'); } $signature = base64_decode($signature, true); if ($signature === false) { throw new \InvalidArgumentException('invalid base64 signature'); } if (strlen($signature) != 65) { throw new \InvalidArgumentException('invalid signature length'); } $recoveryFlags = ord($signature[0]) - 27; if ($recoveryFlags < 0 || $recoveryFlags > 7) { throw new \InvalidArgumentException('invalid signature type'); } $isCompressed = ($recoveryFlags & 4) != 0; // message is <varInt><prefix><varInt><message> $messageHash = "Bitcoin Signed Message:\n" . hex2bin(RawTransaction::_encode_vint(strlen($message))) . $message; $messageHash = hash('sha256', hash('sha256', $messageHash, true), true); try { $pubkey = self::recoverPubKey($math->hexDec(bin2hex(substr($signature, 1, 32))), $math->hexDec(bin2hex(substr($signature, 33, 32))), $math->hexDec(bin2hex($messageHash)), $recoveryFlags, $generator); } catch (\Exception $e) { throw new \Exception("unable to recover key", 0, $e); } if ($pubkey === false) { throw new \Exception('unable to recover key'); } $point = $pubkey->getPoint(); // see that the key we recovered is for the address given if (!$isCompressed) { $pubBinStr = "" . str_pad(hex2bin(self::padHex($math->decHex($point->getX()))), 32, "", STR_PAD_LEFT) . str_pad(hex2bin(self::padHex($math->decHex($point->getY()))), 32, "", STR_PAD_LEFT); } else { $pubBinStr = ($math->mod($point->getY(), 2) == 0 ? "" : "") . str_pad(hex2bin(self::padHex($math->decHex($point->getX()))), 32, "", STR_PAD_LEFT); } $derivedAddress = hex2bin(self::magicByte()) . hash('ripemd160', hash('sha256', $pubBinStr, true), true); return $address === $derivedAddress; }