10.7. Signing a message with PHP¶
What follows is a simple project to sign the messages with PHP. The following example shows you how to execute a simple signing of transaction with PHP.
10.7.1. Create files¶
{
"name": "cargox/php-signer",
"description": "Example of singing Ethereum messages with PHP",
"authors": [
{
"name": "CargoX"
}
],
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": ">=5.5",
"kornrunner/keccak": ">=1.0.0",
"kornrunner/secp256k1": ">=0.1.2",
"pear/console_commandline": ">=1.2.2"
}
}
<?
declare(strict_types=1);
error_reporting(E_ALL ^ E_WARNING);
require __DIR__ . '/../vendor/autoload.php';
use kornrunner\Keccak;
use kornrunner\Secp256k1;
// =============================================================================================
// Sanity check
// =============================================================================================
$emptyHashActual = Keccak::hash("", 256);
$emptyHashExpected = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470";
if ($emptyHashActual != $emptyHashExpected) {
throw new Exception('Expected: ' . $emptyHashExpected . '\nGot: ' . $emptyHashActual);
}
// =============================================================================================
// Actual methods that sign the message
// =============================================================================================
/**
* Sign the given message using secp256k1 ESCDA and given private key.
* (Key and message must be given in HEX format).
*/
function Sign($message, $privateKey, $verbose = False) {
$secp256k1 = new Secp256k1();
if ($verbose) {
echo "Sign(message): $message\r\n";
}
// return signature contains r, s and recovery param (v).
// message and privateKey are hex strings
$signature = $secp256k1->sign($message, $privateKey, [
"canonical" => true,
"n" => null,
]);
$result = "0x" . $signature->toHex();
// kornrunner library does not add the recovery param (recid) at the end, so we must do it ourselves
// This hack with 0 is ok, as recovery param is always between 0 and 3
$result = $result . "0" . dechex($signature->getRecoveryParam());
return $result;
}
/**
* Create an Ethereum message signature and sign it using given private key.
* (Key must be given in HEX format) and message must be in binary format.
*/
function SignHash($message, $privateKey, $verbose = False) {
if ($verbose) {
echo "SignHash(message): " . bin2hex($message) . "\r\n";
}
$msglen = strlen($message);
$msg = hex2bin("19") . "Ethereum Signed Message:" . hex2bin("0A") . $msglen . $message;
$hash = Keccak::hash($msg, 256);
return Sign($hash, $privateKey, $verbose);
}
/**
* Sign the challenge. Takes two parameters -- the challenge (usually a normal, text-based string),
* and the private key (private key must be in hex format, without the staring `0x`).
* Returns the message signature in hex-encoded string.
*
* Example call:
* SignChallenge("u3Z5LUDVp31tMtKtwCGr1FCs3kw", "a0f0e1232dae3ce8a0bf968c452602663975005537e37e79d28c23af52b3114e")
*
* Returns:
* "0x2a2785be5d38ba773765df2232d749b01c4ebc97721f9d033a696b7f893ba45d20bccf4341ae6b01a8f697e22e1e4b4697e02e04352363bee1eeaa9494e096cb"
*/
function SignChallenge($challenge, $privateKey, $verbose = False) {
if ($verbose) {
echo "SignChallenge(challenge): $challenge \r\n";
}
$hash = Keccak::hash($challenge, 256);
$hash = hex2bin($hash);
return SignHash($hash, $privateKey, $verbose);
}
?>
<?
declare(strict_types=1);
error_reporting(E_ALL ^ E_WARNING);
require_once 'sign.php';
// =============================================================================================
// Example usage
// =============================================================================================
$parser = new Console_CommandLine();
$parser->description = 'A simple tool to sign Ethereum challenges and hashes.';
$parser->version = '1.0.0';
// add an option to make the program verbose
$parser->addOption('verbose', array(
'short_name' => '-v',
'long_name' => '--verbose',
'action' => 'StoreTrue',
'description' => 'turn on verbose output'
));
$parser->addOption('privateKey', array(
'short_name' => '-p',
'long_name' => '--private-key',
'description' => 'Hex-encoded key for signature',
'help_name' => 'PRIVATE_KEY',
'action' => 'StoreString'
));
$parser->addOption('method', array(
'short_name' => '-x',
'long_name' => '--method',
'description' => 'Method to use',
'list' => array('challenge', 'hash', 'message'),
'help_name' => 'METHOD',
'action' => 'StoreString'
));
$parser->addOption('message', array(
'short_name' => '-m',
'long_name' => '--message',
'description' => 'Message to sign. Either a free text message (for challenge) or HEX encoded message',
'help_name' => 'MESSAGE',
'action' => 'StoreString'
));
try {
$result = $parser->parse();
$privateKey = $result->options['privateKey'];
$verbose = $result->options['verbose'];
$method = $result->options['method'];
$message = $result->options['message'];
if ($method) {
if ($method == 'challenge') {
echo SignChallenge($message, $privateKey, $verbose) . "\n";
} else if ($method == 'hash') {
if (strpos($message, '0x') === 0) {
// Cut the start off
$message = substr($message, 2, strlen($message));
}
$message = hex2bin($message);
echo SignHash($message, $privateKey, $verbose) . "\n";
} else if ($method == 'message') {
if (strpos($message, '0x') === 0) {
// Cut the start off
$message = substr($message, 2, strlen($message));
}
echo Sign($message, $privateKey, $verbose) . "\n";
}
}
} catch (Exception $exc) {
$parser->displayError($exc->getMessage());
}
?>
10.7.2. Install dependencies¶
Execute the following command:
compose require -v
Note
Depending on your OS, you'll also need to install sources for PHP's extract
and install PHP's gmp
extension.
If you're using the Docker composer image this can be done with the following
Dockerfile
:
FROM composer:latest
RUN mkdir -p /usr/src/php-signer/sign
COPY composer.json /usr/src/php-signer
RUN true && \
apk add --update gmp && \
apk add --virtual build-dependencies --update build-base gmp-dev && \
docker-php-source extract && \
docker-php-ext-install gmp && \
cd /usr/src/php-signer && composer require -v && \
docker-php-source delete && \
apk del build-dependencies && (rm -rf /var/cache/apk/* || true)
10.7.3. Test signing¶
If using Dockerfile
simply do:
docker build . -t cargox/php-signer
docker run -it --rm -w /usr/src/php-signer cargox/php-signer \
php sign/demo.php \
--verbose \
--private-key "a0f0e1232dae3ce8a0bf968c452602663975005537e37e79d28c23af52b3114e" \
--method challenge \
--message "u3Z5LUDVp31tMtKtwCGr1FCs3kw"
or, without docker:
php sign/demo.php \
--verbose \
--private-key "a0f0e1232dae3ce8a0bf968c452602663975005537e37e79d28c23af52b3114e" \
--method challenge \
--message "u3Z5LUDVp31tMtKtwCGr1FCs3kw"