a; $b = $secp256k1->b; $G = $secp256k1->G; $n = $secp256k1->n; $p = $secp256k1->p; if(empty($k)) { throw new \Exception('No Private Key was defined'); } if(null == $nonce) { $random = openssl_random_pseudo_bytes(256, $cStrong); $random = $random . microtime(true).rand(100000000000, 1000000000000); $nonce = gmp_strval(gmp_mod(gmp_init(hash('sha256',$random), 16), $n), 16); } //first part of the signature (R). $rPt = PointMathGMP::mulPoint($nonce, $G, $a, $b, $p); $R = gmp_strval($rPt ['x'], 16); while(strlen($R) < 64) { $R = '0' . $R; } //second part of the signature (S). //S = nonce^-1 (hash + privKey * R) mod p $S = gmp_strval( gmp_mod( gmp_mul( gmp_invert( gmp_init($nonce, 16), $n ), gmp_add( gmp_init($hash, 16), gmp_mul( gmp_init($k, 16), gmp_init($R, 16) ) ) ), $n ), 16 ); if(strlen($S)%2) { $S = '0' . $S; } if(strlen($R)%2) { $R = '0' . $R; } return array('R' => $R, 'S' => $S); } /*** * Sign a hash with the private key that was set and returns a DER encoded signature * * @param $hash * @param null $nonce * @return string */ public static function signHash($hash, $k, $nonce = null) { $points = self::getSignatureHashPoints($hash, $k, $nonce); $signature = '02' . dechex(strlen(hex2bin($points['R']))) . $points['R'] . '02' . dechex(strlen(hex2bin($points['S']))) . $points['S']; $signature = '30' . dechex(strlen(hex2bin($signature))) . $signature; return $signature; } /*** * extract the public key from the signature and using the recovery flag. * see http://crypto.stackexchange.com/a/18106/10927 * based on https://github.com/brainwallet/brainwallet.github.io/blob/master/js/bitcoinsig.js * possible public keys are r−1(sR−zG) and r−1(sR′−zG) * Recovery flag rules are : * binary number between 28 and 35 inclusive * if the flag is > 30 then the address is compressed. * * @param $flag (INT) * @param $R (HEX String) * @param $S (HEX String) * @param $hash (HEX String) * @return array */ public static function getPubKeyWithRS($flag, $R, $S, $hash) { $secp256k1 = new SECp256k1(); $a = $secp256k1->a; $b = $secp256k1->b; $G = $secp256k1->G; $n = $secp256k1->n; $p = $secp256k1->p; $isCompressed = false; if ($flag < 27 || $flag >= 35) { return false; } if($flag >= 31) //if address is compressed { $isCompressed = true; $flag -= 4; } $recid = $flag - 27; //step 1.1 $x = null; $x = gmp_add( gmp_init($R, 16), gmp_mul( $n, gmp_div_q( //check if j is equal to 0 or to 1. gmp_init($recid, 10), gmp_init(2, 10) ) ) ); //step 1.3 $y = null; if(1 == $flag % 2) //check if y is even. { $gmpY = PointMathGMP::calculateYWithX(gmp_strval($x, 16), $a, $b, $p, '02'); if(null != $gmpY) $y = gmp_init($gmpY, 16); } else { $gmpY = PointMathGMP::calculateYWithX(gmp_strval($x, 16), $a, $b, $p, '03'); if(null != $gmpY) $y = gmp_init($gmpY, 16); } if(null == $y) return null; $Rpt = array('x' => $x, 'y' => $y); //step 1.6.1 //calculate r^-1 (S*Rpt - eG) $eG = PointMathGMP::mulPoint($hash, $G, $a, $b, $p); $Rinv = gmp_strval(gmp_invert(gmp_init($R, 16), $n), 16); // Possible issue $eG['y'] = gmp_mod(gmp_neg($eG['y']), $p); // Possible Fix //$eG['y'] = gmp_neg($eG['y']); $SR = PointMathGMP::mulPoint($S, $Rpt, $a, $b, $p); $sR_plus_eGNeg = PointMathGMP::addPoints($SR, $eG, $a, $p); $pubKey = PointMathGMP::mulPoint( $Rinv, $sR_plus_eGNeg, $a, $b, $p ); $pubKey['x'] = gmp_strval($pubKey['x'], 16); $pubKey['y'] = gmp_strval($pubKey['y'], 16); while(strlen($pubKey['x']) < 64) $pubKey['x'] = '0' . $pubKey['x']; while(strlen($pubKey['y']) < 64) $pubKey['y'] = '0' . $pubKey['y']; if($isCompressed){ $derPubKey = AddressCodec::Compress($pubKey); } else{ $derPubKey = AddressCodec::Hex($pubKey); } if(self::checkSignaturePoints($derPubKey, $R, $S, $hash)){ return $derPubKey; } else{ return false; } } // Same as Below but accepts HEX strings public static function recoverPublicKey_HEX($flag, $R, $S, $hash){ return self::recoverPublicKey(gmp_init($R,16), gmp_init($S,16), gmp_init($hash,16), $flag); } // $R, $S, and $hash are GMP // $recoveryFlags is INT public static function recoverPublicKey($R, $S, $hash, $recoveryFlags){ $secp256k1 = new SECp256k1(); $a = $secp256k1->a; $b = $secp256k1->b; $G = $secp256k1->G; $n = $secp256k1->n; $p = $secp256k1->p; $isYEven = ($recoveryFlags & 1) != 0; $isSecondKey = ($recoveryFlags & 2) != 0; // PointMathGMP::mulPoint wants HEX String $e = gmp_strval($hash, 16); $s = gmp_strval($S, 16); // Precalculate (p + 1) / 4 where p is the field order // $p_over_four is GMP static $p_over_four; // XXX just assuming only one curve/prime will be used if (!$p_over_four) { $p_over_four = gmp_div(gmp_add($p, 1), 4); } // 1.1 Compute x // $x is GMP if (!$isSecondKey) { $x = $R; } else { $x = gmp_add($R, $n); } // 1.3 Convert x to point // $alpha is GMP $alpha = gmp_mod(gmp_add(gmp_add(gmp_pow($x, 3), gmp_mul($a, $x)), $b), $p); // $beta is DEC String (INT) $beta = gmp_strval(gmp_powm($alpha, $p_over_four, $p)); // If beta is even, but y isn't or vice versa, then convert it, // otherwise we're done and y == beta. if (PointMathGMP::isEvenNumber($beta) == $isYEven) { // gmp_sub function will convert the DEC String "$beta" into a GMP // $y is a GMP $y = gmp_sub($p, $beta); } else { // $y is a GMP $y = gmp_init($beta); } // 1.4 Check that nR is at infinity (implicitly done in construtor) -- Not reallly // $Rpt is Array(GMP, GMP) $Rpt = array('x' => $x, 'y' => $y); // 1.6.1 Compute a candidate public key Q = r^-1 (sR - eG) // $rInv is a HEX String $rInv = gmp_strval(gmp_invert($R, $n), 16); // $eGNeg is Array (GMP, GMP) $eGNeg = PointMathGMP::negatePoint(PointMathGMP::mulPoint($e, $G, $a, $b, $p)); $sR = PointMathGMP::mulPoint($s, $Rpt, $a, $b, $p); $sR_plus_eGNeg = PointMathGMP::addPoints($sR, $eGNeg, $a, $p); // $Q is Array (GMP, GMP) $Q = PointMathGMP::mulPoint($rInv, $sR_plus_eGNeg, $a, $b, $p); // Q is the derrived public key // $pubkey is Array (HEX String, HEX String) // Ensure it's always 64 HEX Charaters $pubKey['x'] = str_pad(gmp_strval($Q['x'], 16), 64, 0, STR_PAD_LEFT); $pubKey['y'] = str_pad(gmp_strval($Q['y'], 16), 64, 0, STR_PAD_LEFT); return $pubKey; } /*** * Check signature with public key R & S values of the signature and the message hash. * * @param $pubKey * @param $R * @param $S * @param $hash * @return bool */ public static function checkSignaturePoints($pubKey, $R, $S, $hash) { $secp256k1 = new SECp256k1(); $a = $secp256k1->a; $b = $secp256k1->b; $G = $secp256k1->G; $n = $secp256k1->n; $p = $secp256k1->p; $pubKeyPts = AddressCodec::Decompress($pubKey); // S^-1* hash * G + S^-1 * R * Qa // S^-1* hash $exp1 = gmp_strval( gmp_mul( gmp_invert( gmp_init($S, 16), $n ), gmp_init($hash, 16) ), 16 ); // S^-1* hash * G $exp1Pt = PointMathGMP::mulPoint($exp1, $G, $a, $b, $p); // S^-1 * R $exp2 = gmp_strval( gmp_mul( gmp_invert( gmp_init($S, 16), $n ), gmp_init($R, 16) ), 16 ); // S^-1 * R * Qa $pubKeyPts['x'] = gmp_init($pubKeyPts['x'], 16); $pubKeyPts['y'] = gmp_init($pubKeyPts['y'], 16); $exp2Pt = PointMathGMP::mulPoint($exp2, $pubKeyPts, $a, $b, $p); $resultingPt = PointMathGMP::addPoints($exp1Pt, $exp2Pt, $a, $p); $xRes = gmp_strval($resultingPt['x'], 16); while(strlen($xRes) < 64) $xRes = '0' . $xRes; if($xRes == $R) return true; else return false; } /*** * checkSignaturePoints wrapper for DER signatures * * @param $pubKey * @param $signature * @param $hash * @return bool */ public static function checkDerSignature($pubKey, $signature, $hash) { $signature = hex2bin($signature); if('30' != bin2hex(substr($signature, 0, 1))) return false; $RLength = hexdec(bin2hex(substr($signature, 3, 1))); $R = bin2hex(substr($signature, 4, $RLength)); $SLength = hexdec(bin2hex(substr($signature, $RLength + 5, 1))); $S = bin2hex(substr($signature, $RLength + 6, $SLength)); //echo "\n\nsignature:\n"; //print_r(bin2hex($signature)); //echo "\n\nR:\n"; //print_r($R); //echo "\n\nS:\n"; //print_r($S); return self::checkSignaturePoints($pubKey, $R, $S, $hash); } } ?>