forked from gavinandresen/paymentrequest
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PHP command-line util for creating payment requests
- Loading branch information
1 parent
6ce98a0
commit 5f4a9b7
Showing
2 changed files
with
240 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<?php | ||
|
||
// | ||
// Decode a "base58check" address into its parts: one-byte version, 20-byte hash, 4-byte checksum. | ||
// Based on code from Jeff Garziks picocoin project. | ||
// | ||
// Returns either false or an array with (version, hash, checksum) | ||
// Relies on bcmath to do the heavy lifting. | ||
// | ||
function decode_base58($btcaddress) | ||
{ | ||
// Compute big base58 number: | ||
$chars="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; | ||
$n = "0"; | ||
for ($i = 0; $i < strlen($btcaddress); $i++) { | ||
$p1 = strpos($chars, $btcaddress[$i]); | ||
if ($p1 === false) return false; | ||
$n = bcmul($n, "58"); | ||
$n = bcadd($n, (string)$p1); | ||
} | ||
// Peel off bytes to get checksum / hash / version: | ||
$checksum = ""; | ||
for ($i = 0; $i < 4; $i++) { | ||
$byte = bcmod($n, "256"); | ||
$checksum = chr((int)$byte) . $checksum; | ||
$n = bcdiv($n, "256"); | ||
} | ||
$hash = ""; | ||
for ($i = 0; $i < 20; $i++) { | ||
$byte = bcmod($n, "256"); | ||
$hash = chr((int)$byte) . $hash; | ||
$n = bcdiv($n, "256"); | ||
} | ||
$version = (int)$n; | ||
|
||
// Make sure checksum is correct: | ||
$check = hash('sha256', hash('sha256', chr($version).$hash, true), true); | ||
if (substr($check,0,4) != $checksum) return false; | ||
|
||
return array($version, $hash, $checksum); | ||
} | ||
|
||
// | ||
// Convert a Bitcoin address to a raw-bytes Script | ||
// | ||
// Returns false if passed an invalid Bitcoin address, | ||
// otherwise returns an array | ||
// containing (boolean fTestnet, string Script) | ||
// | ||
function address_to_script($btcaddress) | ||
{ | ||
$vhc = decode_base58($btcaddress); | ||
if ($vhc === False) return False; | ||
|
||
$version = $vhc[0]; | ||
$hash = $vhc[1]; | ||
|
||
$testnet = false; | ||
$script = ""; | ||
switch ($version) { | ||
case 111: | ||
$testnet = true; // ... fall through | ||
case 0: | ||
// Pay to public key: | ||
// DUP HASH160 push-0x14-bytes ...hash... EQUALVERIFY CHECKSIG | ||
$script = "\x76\xa9\x14".$hash."\x88\xac"; | ||
break; | ||
case 196: | ||
$testnet = true; // ... fall through | ||
case 5: | ||
// Pay to script hash: | ||
// HASH160 push-0x14-bytes ... hash ... EQUAL | ||
$script = "\xa9\x14".$hash."\x87"; | ||
break; | ||
default: | ||
return false; | ||
} | ||
return array($testnet, $script); | ||
} | ||
|
||
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
#!/usr/bin/env php | ||
<?php | ||
|
||
// Command-line utility to create a payment request and | ||
// send it to standard output, to demonstrate how you'd use | ||
// the code in paymentrequest.php / certificates.php in web server | ||
// code | ||
// | ||
|
||
$usage = <<<USAGE | ||
Required arguments: | ||
paytoaddress= : one of your bitcoin addresses (ideally, a unique-per-customer address) | ||
certificate= : PEM-encoded file containing certificate | ||
privatekey= : PEM file containing private key for certificate | ||
Optional: | ||
amount= : amount (in BTC) that needs to be paid | ||
memo= : message to user | ||
expires= : unix timestamp (integer) when this Request expires | ||
receipt_url= : URL where a Payment message should be sent | ||
out= : file to write to (default: standard output) | ||
single_use= : 1 is single-use, 0 is multi-use | ||
USAGE; | ||
|
||
// Protocol buffer stuff: | ||
require_once 'DrSlump/Protobuf.php'; | ||
\DrSlump\Protobuf::autoload(); | ||
require_once 'paymentrequest.php'; | ||
|
||
// Certificate handling stuff: | ||
require_once 'certificates.php'; | ||
|
||
// Bitcoin address stuff: | ||
require_once 'base58.php'; | ||
|
||
$details = new \payments\PaymentDetails(); | ||
$details->setTime(time()); | ||
|
||
$paymentRequest = new \payments\PaymentRequest(); | ||
|
||
$payto = NULL; | ||
$amount = NULL; | ||
$certificate = NULL; | ||
$privatekey = NULL; | ||
$outfile = NULL; | ||
|
||
for ($i = 1; $i < $argc; $i++) { | ||
$keyval = explode("=", $argv[$i]); | ||
if (count($keyval) != 2) { | ||
echo "Unrecognized argument: ".$argv[$i]."\n"; | ||
echo $usage; | ||
exit(1); | ||
} | ||
$key = trim($keyval[0], "-"); | ||
$val = $keyval[1]; | ||
switch ($key) { | ||
case "paytoaddress": | ||
$payto = $val; | ||
break; | ||
case "certificate": | ||
$certificate = $val; | ||
break; | ||
case "privatekey": | ||
$privatekey = $val; | ||
break; | ||
case "amount": | ||
$amount = $val; | ||
break; | ||
case "memo": | ||
$details->setMemo($val); | ||
break; | ||
case "expires": | ||
$details->setExpires($val); | ||
break; | ||
case "receipt_url": | ||
$details->setReceiptUrl($val); | ||
break; | ||
case "out": | ||
$outfile = $val; | ||
break; | ||
case "single_use": | ||
$details->setSingleUse($val ? true : false); | ||
break; | ||
default: | ||
echo "Unrecognized argument: ".$argv[$i]."\n"; | ||
echo $usage; | ||
exit(1); | ||
} | ||
} | ||
|
||
if ($payto === NULL || $certificate === NULL || $privatekey === NULL) { | ||
echo "You must specify paytoaddress= certificate= and privatekey=\n"; | ||
exit(1); | ||
} | ||
|
||
$paymentRequest->setSerializedPaymentDetails($details->serialize()); | ||
|
||
$certChain = new \payments\X509Certificates(); | ||
$leafCert = file_get_contents($certificate); | ||
|
||
// http-fetch parent certificates. In a real web application, you should avoid | ||
// constantly re-fetching the certificate chain, and should, instead, fetch it once | ||
// and then store it in your database or in a file on disk and only re-fetch it when | ||
// your certificate expires or one of the intermediate certificates is revoked/replaced. | ||
$certs = fetch_chain($leafCert); | ||
foreach ($certs as $cert) { | ||
$certChain->addCertificate($cert); | ||
} | ||
|
||
// | ||
// Create signature | ||
// | ||
$paymentRequest->setPkiType("x509+sha1"); | ||
$paymentRequest->setPkiData($certChain->serialize()); | ||
|
||
$priv_key = file_get_contents($privatekey); | ||
$pkeyid = openssl_get_privatekey($priv_key); | ||
|
||
$paymentRequest->setSignature(""); | ||
$dataToSign = $paymentRequest->serialize(); | ||
|
||
$signature = ""; | ||
$result = openssl_sign($dataToSign, $signature, $pkeyid, OPENSSL_ALGO_SHA1); | ||
if ($signature === FALSE) { | ||
echo "ERROR: signing failed.\n"; | ||
exit(1); | ||
} | ||
$paymentRequest->setSignature($signature); | ||
|
||
$data = $paymentRequest->serialize(); | ||
|
||
// | ||
// Done; output: | ||
// | ||
|
||
if ($outfile) { | ||
file_put_contents($outfile, $data); | ||
} | ||
else { | ||
echo $data; | ||
} | ||
|
||
exit(0); | ||
|
||
// A web application serving up a payment request would do something like this: | ||
|
||
header('Content-Type: application/x-bitcoin-payment-request'); | ||
$filename = "r".(string)time().".bitcoinpaymentrequest"; // ... or any unique filename | ||
header('Content-Disposition: inline; filename='.$filename); | ||
header('Content-Transfer-Encoding: binary'); | ||
header('Expires: 0'); | ||
header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); | ||
header('Content-Length: ' . (string)strlen($data)); | ||
echo $data; | ||
|
||
exit(0); | ||
|
||
?> |