function getVerifiedAddress160Bin($signature, $message) { global $secp256k1_G; $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; // hash message, recover key $messageHash = hash('sha256', hash('sha256', "Bitcoin Signed Message:\n" . numToVarIntString(strlen($message)) . $message, true), true); $pubkey = recoverPubKey(bin2gmp(substr($signature, 1, 32)), bin2gmp(substr($signature, 33, 32)), bin2gmp($messageHash), $recoveryFlags, $secp256k1_G); if ($pubkey === false) { throw new InvalidArgumentException('Unable to recover key.'); } $point = $pubkey->getPoint(); // see that the key we recovered is for the address given if (!$isCompressed) { $pubBinStr = "" . str_pad(gmp2bin($point->getX()), 32, "", STR_PAD_LEFT) . str_pad(gmp2bin($point->getY()), 32, "", STR_PAD_LEFT); } else { $pubBinStr = (isBignumEven($point->getY()) ? "" : "") . str_pad(gmp2bin($point->getX()), 32, "", STR_PAD_LEFT); } $derivedAddress = hash('ripemd160', hash('sha256', $pubBinStr, true), true); return $derivedAddress; }
function isMessageSignatureValid($address, $signature, $message) { // curve definition // http://www.secg.org/download/aid-784/sec2-v2.pdf static $secp256k1 = null; static $secp256k1_G = null; $secp256k1 == null && ($secp256k1 = new CurveFp('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F', '0', '7')); $secp256k1_G == null && ($secp256k1_G = new Point($secp256k1, '0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798', '0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8', '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141')); // extract parameters $address = base58check_decode($address); if (strlen($address) != 21 || $address[0] != "") { 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; // hash message, recover key $messageHash = hash('sha256', hash('sha256', "Bitcoin Signed Message:\n" . numToVarIntString(strlen($message)) . $message, true), true); $pubkey = recoverPubKey(bin2gmp(substr($signature, 1, 32)), bin2gmp(substr($signature, 33, 32)), bin2gmp($messageHash), $recoveryFlags, $secp256k1_G); if ($pubkey === false) { throw new InvalidArgumentException('unable to recover key'); } $point = $pubkey->getPoint(); // see that the key we recovered is for the address given if (!$isCompressed) { $pubBinStr = "" . str_pad(gmp2bin($point->getX()), 32, "", STR_PAD_LEFT) . str_pad(gmp2bin($point->getY()), 32, "", STR_PAD_LEFT); } else { $pubBinStr = (isBignumEven($point->getY()) ? "" : "") . str_pad(gmp2bin($point->getX()), 32, "", STR_PAD_LEFT); } $derivedAddress = "" . hash('ripemd160', hash('sha256', $pubBinStr, true), true); return $address === $derivedAddress; }