3034 lines
85 KiB
PHP
3034 lines
85 KiB
PHP
<?php
|
|
|
|
namespace BitPay;
|
|
|
|
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
|
|
|
|
|
/**
|
|
* This is a self-contained PHP implementation of BitPay's new cryptographically
|
|
* secure API: https://test.bitpay.com/api.
|
|
*
|
|
* NOTE: This API is currently only available in our test environment and is not
|
|
* yet ready for production. Some or all of the API calls may change before the
|
|
* official release.
|
|
*
|
|
* PHP version 5
|
|
*
|
|
* LICENSE: This file is subject to the MIT License (MIT) which is available
|
|
* at the following URI: http://opensource.org/licenses/MIT. Permission is hereby
|
|
* granted, free of charge, to any person obtaining a copy of this software and
|
|
* associated documentation files ( the "Software" ), to deal in the Software
|
|
* without restriction, including without limitation the rights to use, copy,
|
|
* modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
|
* subject to the following conditions: The above copyright notice and this
|
|
* permission notice shall be included in all copies or substantial portions of
|
|
* the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* @category Bitcoin
|
|
* @package Bitcoin\BitPay\PHP-APIv2-Client
|
|
* @author Rich Morgan <rich@bitpay.com>
|
|
* @copyright 2014 BITPAY, INC.
|
|
* @license http://opensource.org/licenses/MIT The MIT License (MIT)
|
|
* @version Release 0.1
|
|
* @link https://github.com/ionux/<<TODO: Name>>
|
|
* @since File available since Release 0.1
|
|
*/
|
|
|
|
|
|
/*
|
|
* Date functions require the timezone be set. This call
|
|
* will set the timezone used by this script and not
|
|
* cause any spurious warning messages in the web server
|
|
* log file.
|
|
*/
|
|
date_default_timezone_set( @date_default_timezone_get() );
|
|
|
|
|
|
/*
|
|
* For debugging purposes, this is set to a high value.
|
|
*/
|
|
set_time_limit( 7200 );
|
|
|
|
|
|
/*
|
|
* Clears the file status cache to prevent cached
|
|
* information from being used in the event the file
|
|
* being referenced has been changed.
|
|
*/
|
|
clearstatcache();
|
|
|
|
|
|
/*
|
|
* The following three lines control error message
|
|
* display during develpment. In production environments
|
|
* all error reporting should be via the system logger
|
|
* and not displayed to the end-user for security
|
|
* reasons.
|
|
*/
|
|
error_reporting( E_ALL );
|
|
ini_set( 'display_errors' , TRUE );
|
|
ini_set( 'display_startup_errors', TRUE );
|
|
|
|
|
|
/*
|
|
* Constants used by Bitcoin address and SIN (key) creation
|
|
*/
|
|
define( 'ADDRESSVERSION', '00' );
|
|
define( 'SINTYPE' , '02' );
|
|
define( 'SINVERSION' , '0F' );
|
|
|
|
|
|
/*
|
|
* The GMP PHP extension is required for the elliptic curve math functions,
|
|
* see: http://www.php.net/manual/en/book.gmp.php. Future versions of this
|
|
* library may add support for the BC Math extension.
|
|
*/
|
|
if ( function_exists( 'gmp_cmp' ) ) {
|
|
define( 'MATH_TYPE', 'GMP' );
|
|
} else {
|
|
die ( 'FATAL: This class requires the GMP math extension to be installed. Please contact your web hosting support.' );
|
|
}
|
|
|
|
|
|
/*
|
|
* This function is used to generate the cryptographically-strong
|
|
* random number value. see: http://us3.php.net/openssl_random_pseudo_bytes
|
|
*/
|
|
if ( !function_exists( 'openssl_random_pseudo_bytes' ) ) {
|
|
die ( 'FATAL: OpenSSL PHP extension missing. Please install this extension to use the ECCkeygen class.' );
|
|
}
|
|
|
|
|
|
/**
|
|
* This is a self-contained PHP implementation of BitPay's new cryptographically
|
|
* secure API.
|
|
*
|
|
* @category Bitcoin
|
|
* @package Bitcoin\BitPay\PHP-APIv2-Client
|
|
* @author Rich Morgan <rich@bitpay.com>
|
|
* @copyright 2014 BITPAY, INC.
|
|
* @license http://opensource.org/licenses/MIT The MIT License (MIT)
|
|
* @version Release 0.1
|
|
* @link https://github.com/ionux/<<TODO: Name>>
|
|
* @since Class available since Release 0.1
|
|
*/
|
|
class BitPay {
|
|
|
|
/*
|
|
* Elliptic curve parameters for secp256k1, for more information see:
|
|
* http://www.secg.org/collateral/sec2_final.pdf
|
|
*
|
|
* The elliptic curve domain parameters over Fp associated with a Koblitz
|
|
* curve secp256k1are specified by the sextuple T = ( p; a; b; G; n; h )
|
|
* where the finite field F p is defined by:
|
|
*/
|
|
|
|
/**
|
|
* @var string Special point on the curve
|
|
*/
|
|
private $Inf = 'infinity';
|
|
|
|
/**
|
|
* @var string Base point in uncompressed, hexadecimal format
|
|
*/
|
|
private $G = '0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8';
|
|
|
|
/**
|
|
* @var string Field prime in hexadecimal format
|
|
*/
|
|
private $p = '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F';
|
|
|
|
/**
|
|
* @var string Order of G in hexadecimal format
|
|
*/
|
|
private $n = '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141';
|
|
|
|
/**
|
|
* @var string Coefficient of x in hexadecimal format
|
|
*/
|
|
private $a = '0x0';
|
|
|
|
/**
|
|
* @var string Intercept in hexadecimal format
|
|
*/
|
|
private $b = '0x7';
|
|
|
|
/**
|
|
* @var string Cofactor of G in hexadecimal format
|
|
*/
|
|
private $h = '0x1';
|
|
|
|
/**
|
|
* @var boolean Used to store the tokens returned from the BitPay network
|
|
*/
|
|
private $getTokens = false;
|
|
|
|
/**
|
|
* @var boolean Determines whether or not to crytpographically sign requests
|
|
*/
|
|
private $signRequests = false;
|
|
|
|
/**
|
|
* @var boolean Flag to hold OpenSSL extension check
|
|
*/
|
|
private $opensslAvailable = false;
|
|
|
|
/**
|
|
* @var boolean Flag to hold CURL extension check
|
|
*/
|
|
private $curlAvailable = false;
|
|
|
|
/**
|
|
* @var boolean Flag to hold https check
|
|
*/
|
|
private $secureComms = false;
|
|
|
|
/**
|
|
* @var boolean Is this being used for testing?
|
|
*/
|
|
private $_testing = true;
|
|
|
|
/**
|
|
* @var string Private elliptic curve key in hexadecimal format
|
|
*/
|
|
private $privECKey = '';
|
|
|
|
/**
|
|
* @var string Public elliptic curve key in hexadecimal format
|
|
*/
|
|
private $pubECKey = '';
|
|
|
|
/**
|
|
* @var string BitPay API Host parameter
|
|
*/
|
|
private $apiHost = '';
|
|
|
|
/**
|
|
* @var string BitPay API Post parameter
|
|
*/
|
|
private $apiPort = '';
|
|
|
|
/**
|
|
* @var string Holds the GUID value used for idempotence in POST requests
|
|
*/
|
|
private $GUID = '';
|
|
|
|
/**
|
|
* @var string An ever-incrementing value used for API requests
|
|
*/
|
|
private $nonce = '';
|
|
|
|
/**
|
|
* @var string Service Identification Number, also referred to as a "client" or "key"
|
|
*/
|
|
private $SIN = '';
|
|
|
|
/**
|
|
* @var string ECDSA signature value in a DER-encoded format
|
|
*/
|
|
private $signature = '';
|
|
|
|
/**
|
|
* @var string Filename holding the EC private key, do not make this web-accessible
|
|
*/
|
|
private $privKeyFilename = '';
|
|
|
|
/**
|
|
* @var string Filename holding the EC public key, do not make this web-accessible
|
|
*/
|
|
private $pubKeyFilename = '';
|
|
|
|
/**
|
|
* @var string Directory holding the EC keyfiles, do not make this web-accessible
|
|
*/
|
|
private $keyfile_dir = '';
|
|
|
|
/**
|
|
* @var string Version of this library class
|
|
*/
|
|
private $_version = '0.1';
|
|
|
|
/**
|
|
* @var string Variable holding the facade used for certain resouces in API requests
|
|
*/
|
|
private $facade = 'public';
|
|
|
|
/**
|
|
* @var string Save file name for generated EC keypair resources - do not make this web-accessible
|
|
*/
|
|
private $defaultSaveFile = 'bparc.rm';
|
|
|
|
/**
|
|
* @var string Save file name for generated SIN (client) resource - do not make this web-accessible
|
|
*/
|
|
private $defaultSINFile = 'bpsrc.rm';
|
|
|
|
/**
|
|
* @var string URI for the BitPay payment API resource
|
|
*/
|
|
private $bitpayURL = 'https://test.bitpay.com';
|
|
|
|
/**
|
|
* @var array Container for the tokens retrieved from the gateway
|
|
*/
|
|
private $tokens = array();
|
|
|
|
/**
|
|
* @var array Holds error messages for application reference
|
|
*/
|
|
private $errors = array();
|
|
|
|
/**
|
|
* @var array Holds notice messages for application reference
|
|
*/
|
|
private $notices = array();
|
|
|
|
/**
|
|
* @var array Container of supported OpenSSL digest methods
|
|
*/
|
|
private $opensslDigestMethods = array();
|
|
|
|
/**
|
|
* @var array Container of supported OpenSSL hash algorithms
|
|
*/
|
|
private $hashAlgos = array();
|
|
|
|
|
|
/**
|
|
* Class constructor - initializes the environment and prepares
|
|
* the BitPay payment gateway class to be used.
|
|
*
|
|
* @param string $keyfiledir Directory holding the EC keyfiles - do not make this web-accessible
|
|
* @param string $pubfilename Filename holding the EC public key - do not make this web-accessible
|
|
* @param string $privfilename Filename holding the EC private key - do not make this web-accessible
|
|
* @param string $privECKey Private elliptic curve key in hexadecimal format
|
|
* @param string $pubECKey Public elliptic curve key in hexadecimal format
|
|
* @param string $prevSIN Service Identification Number, also referred to as a "client" or "key"
|
|
* @param string $SINfilename Filename for a previously generated SIN
|
|
* @param array $options Array of option class overrides
|
|
* @param boolean $testing Testing flag used for development
|
|
* @return void
|
|
* @access public
|
|
* @since Method available since Release 0.1
|
|
*/
|
|
public function __construct ( $keyfiledir = '', $pubfilename = '',
|
|
$privfilename = '', $privECKey = '',
|
|
$pubECKey = '', $prevSIN = '',
|
|
$SINfilename = '', $options = array(),
|
|
$testing = false ) {
|
|
|
|
/*
|
|
* TODO: do we need an autoloader? this class is self-contained
|
|
*/
|
|
if ( !spl_autoload_register( array( $this, 'autoloader' ) ) ) {
|
|
die ( 'FATAL: Cannot register class autoloader.' );
|
|
}
|
|
|
|
|
|
/*
|
|
* private & public keys are mandatory. you can pass in your own
|
|
* pre-generated keypair or the class will attempt to read them from
|
|
* disk if the keyfile_dir and filenames are valid. lastly, it will
|
|
* generate a pair if that fails.
|
|
*/
|
|
if ( !isset( $privECKey ) || trim( $privECKey ) == '' || is_null( $privECKey ) ||
|
|
!isset( $pubECKey ) || trim( $pubECKey ) == '' || is_null( $pubECKey ) ) {
|
|
|
|
/*
|
|
* key data not directly provided, check for directory and filename
|
|
* values are provided.
|
|
*/
|
|
clearstatcache();
|
|
|
|
if ( substr( $keyfiledir,-1 ) != '/' ) {
|
|
$keyfiledir .= '/';
|
|
}
|
|
|
|
if ( substr( $privfilename,0,1 ) == '/' ) {
|
|
$privfilename = substr( $privfilename, 1 );
|
|
}
|
|
|
|
if ( substr( $pubfilename,0,1 ) == '/' ) {
|
|
$pubfilename = substr( $pubfilename, 1 );
|
|
}
|
|
|
|
if ( is_dir( $keyfiledir ) &&
|
|
is_file( $keyfiledir . $privfilename ) &&
|
|
is_file( $keyfiledir . $pubfilename ) ) {
|
|
|
|
/*
|
|
* directory and both filenames provided.
|
|
* attempt to read them
|
|
*/
|
|
if ( !is_readable( $keyfiledir . $pubfilename ) ) {
|
|
die ( 'FATAL: The public key file "' . $keyfiledir . $pubfilename . '" is not readable. Check the file permissions.' );
|
|
} else {
|
|
$pubfile_contents = file_get_contents( $keyfiledir . $pubfilename );
|
|
}
|
|
|
|
if ( !is_readable( $keyfiledir . $privfilename ) ) {
|
|
die ( 'FATAL: The private key file "' . $keyfiledir . $privfilename . '" is not readable. Check the file permissions.' );
|
|
} else {
|
|
$privfile_contents = file_get_contents( $keyfiledir . $privfilename );
|
|
}
|
|
|
|
/*
|
|
* read them successfully, now check that they are actually
|
|
* valid keys
|
|
*/
|
|
if ( $this->checkKeyfile( $pubfile_contents ) &&
|
|
$this->checkKeyfile( $privfile_contents ) ) {
|
|
|
|
if ( $pubfile_contents !== false &&
|
|
$privfile_contents !== false ) {
|
|
|
|
$this->set( 'privECkey', $this->parseKeyfile( $privfile_contents ) );
|
|
$this->set( 'pubECkey' , $this->parseKeyfile( $pubfile_contents ) );
|
|
|
|
if ( $this->get( 'privECkey' ) == false ||
|
|
$this->get( 'pubECkey' ) == false ) {
|
|
|
|
die ( 'FATAL: The format of the supplied keyfiles is unknown. Only PEM, DER and plain hex formatted keys are supported.' );
|
|
|
|
}
|
|
|
|
$this->set( 'keyfile_dir', $keyfiledir );
|
|
$this->set( 'privKeyFilename', $privfilename );
|
|
$this->set( 'privKeyFilename', $privfilename );
|
|
|
|
$this->addNotice( 'Loaded keyfile data from "' . $privfilename . '" and "' . $pubfilename . '" successfully.' );
|
|
|
|
} else {
|
|
|
|
die ( 'FATAL: The format of the supplied keyfiles is unknown. Only PEM, DER and plain hex formatted keys are supported.' );
|
|
}
|
|
|
|
} else {
|
|
|
|
die ( 'FATAL: The format of the supplied keyfiles is unknown. Only PEM, DER and plain hex formatted keys are supported.' );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/*
|
|
* no keys or keyfiles specified. check for previously saved keyfile
|
|
*/
|
|
clearstatcache();
|
|
|
|
if ( substr( $keyfiledir,-1 ) != '/' ) {
|
|
$keyfiledir .= '/';
|
|
}
|
|
|
|
if ( !is_dir( $keyfiledir ) ) {
|
|
die ( 'FATAL: No keyfiles, keyfile directory, or key values specified. Refer to the README.md file for usage instructions.' );
|
|
}
|
|
|
|
if ( !is_readable( $keyfiledir ) ) {
|
|
die ( 'FATAL: Keyfile directory "' . $keyfiledir . '" specified but it is not readable. Check the directory permissions.' );
|
|
|
|
}
|
|
|
|
$savefiledata = $this->readKeypair( $keyfiledir );
|
|
|
|
if ( $savefiledata == false ) {
|
|
|
|
/*
|
|
* no keyfound from previous class instantiations. creating
|
|
* a new pair. but first check to see we have a valid keyfile
|
|
* directory specified so we can save these keys for future use.
|
|
*/
|
|
$this->addNotice( 'No EC keypair data or key files specified. Generating my own keypair and attempting to save to "' .
|
|
$keyfiledir . $this->get( 'defaultSaveFile' ) .
|
|
'".' );
|
|
|
|
$keys = $this->generateKeypair();
|
|
|
|
$this->set( 'keyfile_dir', $keyfiledir );
|
|
$this->set( 'privECKey' , $keys['private_key_hex'] );
|
|
$this->set( 'pubECKey' , $keys['public_key'] );
|
|
|
|
if ( $this->saveKeypair( $keys ) ) {
|
|
|
|
$this->addNotice( 'Successfully saved newly generated keypair to "' .
|
|
$this->get( 'keyfile_dir' ) . $this->get( 'defaultSaveFile' ) .
|
|
'". For security reasons, ensure this file is NOT web accessible.' );
|
|
|
|
} else {
|
|
|
|
die ( 'FATAL: Could not save new keypair. Ensure the directory "' . $keyfiledir . '" is writable by the webserver.' );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/*
|
|
* keyfile found. using those values.
|
|
*/
|
|
$this->set( 'keyfile_dir', $keyfiledir );
|
|
$this->set( 'privECKey' , $savefiledata['private_key_hex'] );
|
|
$this->set( 'pubECKey' , $savefiledata['public_key'] );
|
|
|
|
$this->addNotice( 'No key data was provided but I found an existing keyfile in "' . $keyfiledir . '" and loaded the keypair data successfully.' );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/*
|
|
* keypair data provided directly. testing before assigning them
|
|
*/
|
|
if ( $this->parseKeyfile( $privECKey ) &&
|
|
$this->parseKeyfile( $pubECKey ) ) {
|
|
|
|
$this->set( 'privECKey', $privECKey );
|
|
$this->set( 'pubECKey' , $pubECKey );
|
|
|
|
$this->addNotice( 'Key test passed. Using provided EC keypair.' );
|
|
|
|
} else {
|
|
|
|
die ( 'FATAL: The format of the supplied keyfiles is unknown. Only PEM, DER and plain hex formatted keys are supported.' );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* if you've generated a SIN somewhere else or want to reuse one
|
|
* previously generated by this class, pass it as the second
|
|
* parameter. otherwise we will generate a new one here.
|
|
*
|
|
* TODO: Fix to use saveSIN() & readSIN() functions.
|
|
*/
|
|
if ( isset( $prevSIN ) && trim( $prevSIN ) != '' ) {
|
|
|
|
$this->set( 'SIN', $prevSIN );
|
|
$this->addNotice( 'Loaded SIN successfully.' );
|
|
|
|
} else {
|
|
|
|
$this->set( 'SIN', $this->generateSIN( $pubECKey ) );
|
|
$this->addNotice( 'No SIN specified. Generated my own.' );
|
|
|
|
if ( !$this->saveSIN($this->SIN) ) {
|
|
die( 'FATAL: Could not write the new SIN data to a file.' );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* by default, we want to sign all requests and self-initialize the client
|
|
* by retrieving access token from the server. these should be set to false
|
|
* in `options` passed if you don't have an account or don't have a sin
|
|
* associated with your account.
|
|
*
|
|
* TODO: This is done inside the sendRequest() function, I believe...
|
|
*/
|
|
$this->set( 'getTokens' , true );
|
|
$this->set( 'signRequests', true );
|
|
|
|
|
|
/*
|
|
* override the default options if needed
|
|
*/
|
|
foreach ( $options as $k => $v ) {
|
|
if ( isset( $this->$k ) ) {
|
|
if ( !$this->set( $this->$k, $v ) ) {
|
|
die ( 'FATAL: The option ' . $k .
|
|
' is not an option or cannot be set.' );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* initialize nonce
|
|
* TODO: This is also done inside the sendRequest() function,
|
|
* so I don't think I need this here...
|
|
*/
|
|
$this->set( 'nonce', $this->getNonce() );
|
|
|
|
|
|
/*
|
|
* setup container for access tokens
|
|
*/
|
|
if ( $this->get( 'getTokens' ) )
|
|
$this->set( 'tokens', $this->getAccessTokens() );
|
|
|
|
|
|
/*
|
|
* determine if the openssl extension is installed
|
|
*/
|
|
if ( function_exists( 'openssl_error_string' ) ) {
|
|
|
|
$this->set( 'opensslAvailable', true );
|
|
|
|
/*
|
|
* populate the list of openssl digest methods
|
|
*/
|
|
$this->set( 'opensslDigestMethods', $this->getOpensslDigestMethods() );
|
|
$this->addNotice( 'OpenSSL extension loaded and digest methods populated.' );
|
|
|
|
} else {
|
|
|
|
die ( 'FATAL: The PHP OpenSSL extension is not installed on this server. ' .
|
|
'Please contact your web hosting support.' );
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* determine if we can perform the required hash functions
|
|
* natively or have to use the fallback
|
|
*/
|
|
if ( function_exists( 'hash_algos' ) ) {
|
|
|
|
$this->set( 'hashAlgos', hash_algos() );
|
|
$this->addNotice( 'Using the native PHP hash functions.' );
|
|
|
|
} else {
|
|
|
|
$this->addNotice( 'The native PHP hash functions are not available. ' .
|
|
'Using slower fallback hash functions.' );
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* determine if the curl extension is installed
|
|
*/
|
|
if ( function_exists( 'curl_init' ) ) {
|
|
|
|
$this->set( 'curlAvailable', true );
|
|
|
|
/*
|
|
* check if we can talk to BitPay securely which is required per the official documentation
|
|
*/
|
|
$curl = curl_init();
|
|
|
|
$header = array(
|
|
'X-BitPay-Plugin-Info: newapiphplib' . $this->get( '_version' ),
|
|
);
|
|
|
|
curl_setopt( $curl, CURLOPT_URL, $this->get( 'bitpayURL' ) );
|
|
curl_setopt( $curl, CURLOPT_PORT, 443 );
|
|
curl_setopt( $curl, CURLOPT_HTTPHEADER, $header );
|
|
curl_setopt( $curl, CURLOPT_TIMEOUT, 10 );
|
|
curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, 1 );
|
|
curl_setopt( $curl, CURLOPT_SSL_VERIFYHOST, 2 );
|
|
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
|
|
curl_setopt( $curl, CURLOPT_FORBID_REUSE, 1 );
|
|
curl_setopt( $curl, CURLOPT_FRESH_CONNECT, 1 );
|
|
|
|
/*
|
|
* Returns TRUE on success or FALSE on failure. However, if the CURLOPT_RETURNTRANSFER
|
|
* option is set, it will return the result on success, FALSE on failure.
|
|
*/
|
|
if ( !curl_exec( $curl ) ) {
|
|
|
|
$this->addError( 'Problem with the curl check: ' . curl_error( $curl ) );
|
|
|
|
} else {
|
|
|
|
$this->addNotice( 'Curl check passed successfully.' );
|
|
$this->set( 'secureComms', true );
|
|
|
|
}
|
|
|
|
curl_close( $curl );
|
|
|
|
} else {
|
|
|
|
die ( 'FATAL: The PHP cURL extension is not installed on this server. ' .
|
|
'Please contact your web hosting support.' );
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* end of __construct() function
|
|
*/
|
|
}
|
|
|
|
|
|
/**
|
|
* BitPay class property get magic method override
|
|
*
|
|
* @param string $name Name of the variable to retrieve
|
|
* @return string|boolean $name Returns variable, if exists, or false on failure
|
|
* @access public
|
|
* @since Method available since Release 0.1
|
|
*/
|
|
public function __get( $name ) {
|
|
|
|
if ( is_null( $name ) || trim( $name ) == '' )
|
|
return false;
|
|
|
|
if ( isset( $name ) ) {
|
|
return $this->$name;
|
|
} else {
|
|
$this->addError( 'Error in get(): Property ' . $name .
|
|
' does not exist. Cannot get.' );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* BitPay class property set magic method override
|
|
*
|
|
* @param string $name Name of the variable to retrieve
|
|
* @param mixed $val Name of the variable to retrieve
|
|
* @return boolean $name Returns true on success or false on failure
|
|
* @access public
|
|
* @since Method available since Release 0.1
|
|
*/
|
|
public function __set( $name, $val ) {
|
|
|
|
if ( is_null( $name ) || trim( $name ) == '' )
|
|
return false;
|
|
|
|
if ( isset( $name ) ) {
|
|
$this->$name = $val;
|
|
return true;
|
|
} else {
|
|
$this->addError( 'Error in set(): Property ' . $name .
|
|
' does not exist. Cannot set.' );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* BitPay class property setter
|
|
*
|
|
* @param string $name Name of the variable to retrieve
|
|
* @param mixed $val Name of the variable to retrieve
|
|
* @return boolean $name Returns true on success or false on failure
|
|
* @access public
|
|
* @since Method available since Release 0.1
|
|
*/
|
|
public function __unset( $name ) {
|
|
|
|
/*
|
|
* we don't want anything important accidentally unset
|
|
*/
|
|
$this->addError( 'Error in unset(): Cannot unset properties of this class.' );
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Generates an elliptic curve public/private keypair
|
|
*
|
|
* @param boolean $verbose Flag to show extra debugging information about the keys
|
|
* @return array|boolean $name Returns array of keypair data on success or false on failure
|
|
* @access public
|
|
* @final
|
|
* @since Method available since Release 0.1
|
|
*/
|
|
final public function generateKeypair( $verbose = false ) {
|
|
|
|
/*
|
|
* generate a new EC keypair and return values. only set the
|
|
* verbose and testing variables to 'true' for development
|
|
* purposes ONLY otherwise your private key will be exposed!
|
|
*/
|
|
try {
|
|
|
|
$time_start = microtime( true );
|
|
$n_order = $this->n;
|
|
|
|
/*
|
|
* the ( x,y ) components from the uncompressed public key are the first
|
|
* 32-bytes and second 32-bytes after the leading 0x04 byte
|
|
*/
|
|
$Gx = '0x' . substr( $this->G, 2, 64 );
|
|
$Gy = '0x' . substr( $this->G, 66, 64 );
|
|
|
|
/*
|
|
* generate a crypto-strong 256-bit random number in the set [1, n-1]
|
|
* http://www.php.net/manual/en/function.openssl-random-pseudo-bytes.php
|
|
*/
|
|
do {
|
|
|
|
$private_key = openssl_random_pseudo_bytes( 32, $cstrong );
|
|
|
|
if ( !$cstrong ) {
|
|
die ( 'FATAL: Could not generate cryptographically strong random number. ' .
|
|
'Your OpenSSL implementation may be broken or old.' );
|
|
}
|
|
|
|
/*
|
|
* convert the supplied binary data to hex for use in our crypto math functions
|
|
*/
|
|
$private_key_hex = self::add0x( strtoupper( bin2hex( $private_key ) ) );
|
|
|
|
} while ( gmp_cmp( $private_key_hex, 1 ) <= 0 ||
|
|
gmp_cmp( $private_key_hex, $n_order ) >= 0 );
|
|
|
|
/*
|
|
* initialize our secp256k1 curve point
|
|
*/
|
|
$P = array( 'x' => $Gx, 'y' => $Gy );
|
|
|
|
/*
|
|
* R = private_key * P
|
|
*/
|
|
$R = $this->doubleAndAdd( $private_key_hex, $P );
|
|
$Rx_hex = self::encodeHex( $R['x'] );
|
|
$Ry_hex = self::encodeHex( $R['y'] );
|
|
|
|
/*
|
|
* ensure our new ( x,y ) point values are padded to 32-bytes
|
|
*/
|
|
while ( strlen( $Rx_hex ) < 64 )
|
|
$Rx_hex = '0' . $Rx_hex;
|
|
|
|
while ( strlen( $Ry_hex ) < 64 )
|
|
$Ry_hex = '0' . $Ry_hex;
|
|
|
|
$point_quality = $this->pointTest(
|
|
$R,
|
|
self::decodeHex( $this->a ),
|
|
self::decodeHex( $this->b ),
|
|
self::decodeHex( $this->p )
|
|
);
|
|
|
|
/*
|
|
* capture our generation time for informational purposes only.
|
|
* it is safe to comment/remove the time generation code if needed.
|
|
*/
|
|
$time_end = microtime( true );
|
|
$time = $time_end - $time_start;
|
|
|
|
|
|
/****************************************************************************
|
|
* You do not want to expose your private key to anyone so only uncomment
|
|
* this next code block if you need it for testing/debugging purposes.
|
|
* It is provided here as a convenience and is safe to delete entirely.
|
|
*
|
|
* **************************************************************************
|
|
* **************** DO NOT USE IN PRODUCTION ENVIRONMENTS!!! ****************
|
|
* ******************** ONLY FOR USE DURING DEVELOPMENT! ********************
|
|
*
|
|
* if ( $verbose && $testing ) {
|
|
* echo "<pre>";
|
|
* echo "Private key ( hex ) : " . substr( $private_key_hex,2 ) . "\r\n";
|
|
* echo "Private key ( dec ) : " . self::decodeHex( $private_key_hex ) . "\r\n";
|
|
* echo "Public key ( hex ) : 04" . $Rx_hex . "" . $Ry_hex . "\r\n";
|
|
* echo "Public key ( dec ) : 04" . $R['x'] . "" . $R['y'] . "\r\n\r\n";
|
|
*
|
|
* if ( $point_quality )
|
|
* echo "Good point!";
|
|
* else
|
|
* echo "Bad point!";
|
|
*
|
|
* echo "</pre>";
|
|
* }
|
|
*
|
|
* **************************************************************************
|
|
* **************** DO NOT USE IN PRODUCTION ENVIRONMENTS!!! ****************
|
|
* ******************** ONLY FOR USE DURING DEVELOPMENT! ********************
|
|
****************************************************************************/
|
|
|
|
|
|
/*
|
|
* the pointTest() method returns true/false on success/failure
|
|
*/
|
|
if ( $point_quality ) {
|
|
|
|
return array(
|
|
'private_key_hex' => substr( $private_key_hex,2 ),
|
|
'private_key_dec' => self::decodeHex( $private_key_hex ),
|
|
'public_key' => '04' . $Rx_hex . $Ry_hex,
|
|
'public_key_compressed' => '02' . $Rx_hex,
|
|
'public_key_x' => $Rx_hex,
|
|
'public_key_y' => $Ry_hex,
|
|
'generation_time' => $time
|
|
);
|
|
|
|
} else {
|
|
|
|
$this->addError( 'Error in generateKeypair(): The pointTest function failed - Do not use this EC point!' );
|
|
return false;
|
|
|
|
}
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in generateKeypair(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* end of generateKeypair() function
|
|
*/
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Public wrapper function for signature generation.
|
|
*
|
|
* @param boolean $verbose Name of the variable to retrieve
|
|
* @return boolean $name Returns true on success or false on failure
|
|
* @access public
|
|
* @final
|
|
* @since Method available since Release 0.1
|
|
*/
|
|
final public function generateSignature( $data, $private_key ) {
|
|
|
|
|
|
$sig = $this->gmp_hash( $data, $private_key );
|
|
|
|
if ( $sig != false )
|
|
return $sig['sig_hex'];
|
|
else
|
|
return false;
|
|
}
|
|
|
|
|
|
final public function generateOpenSSLKeypair( $keybits = 512, $digest_alg = 'sha512' ) {
|
|
/* function to generate a new RSA keypair. this is not used for point derivation
|
|
* or for generating signatures. only used for assymetric data encryption, as needed
|
|
*/
|
|
|
|
try {
|
|
|
|
/* see: http://www.php.net/manual/en/function.openssl-pkey-new.php */
|
|
if ( function_exists( 'openssl_pkey_new' ) ) {
|
|
$keypair = array();
|
|
|
|
/* openssl keysize can't be smaller than 384 bits */
|
|
if ( ( int )$keybits < 384 ) {
|
|
$this->addNotice( 'generateOpenSSLKeypair: Keybits param of "' .
|
|
$keybits . '" is invalid. Setting to the minimum value of 384.' );
|
|
|
|
$keybits = 384;
|
|
}
|
|
|
|
if ( !isset( $digest_alg ) || trim( $digest_alg ) == '' ) {
|
|
$this->addNotice( 'generateOpenSSLKeypair: Digest algorithm missing. Using sha512.' );
|
|
$digest_alg = 'sha512';
|
|
}
|
|
|
|
/* RSA is the only supported key time at this time
|
|
* http://www.php.net/manual/en/function.openssl-csr-new.php
|
|
*/
|
|
$config = array(
|
|
'digest_alg' => $digest_alg,
|
|
'private_key_bits' => ( int )$keybits,
|
|
'private_key_type' => OPENSSL_KEYTYPE_RSA
|
|
);
|
|
|
|
$resource = openssl_pkey_new( $config );
|
|
|
|
if ( !$resource ) {
|
|
$this->addError( 'Error in generateOpenSSLKeypair: Could not create new OpenSSL resource.' );
|
|
|
|
/* with the openssl extension, you also have it's own errors returned */
|
|
while ( $msg = openssl_error_string() )
|
|
$this->addError( 'Error in generateOpenSSLKeypair: OpenSSL reported error: ' . $msg );
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( openssl_pkey_export( $resource, $keypair['pri'] ) ) {
|
|
$publickey = openssl_pkey_get_details( $resource );
|
|
$keypair['pub'] = $publickey['key'];
|
|
} else {
|
|
$this->addError( 'Error in generateOpenSSLKeypair: Private key could not be ' .
|
|
'determined from OpenSSL key resource.' );
|
|
|
|
while ( $msg = openssl_error_string() )
|
|
$this->addError( 'Error in generateOpenSSLKeypair: OpenSSL reported error: ' . $msg );
|
|
|
|
return false;
|
|
}
|
|
|
|
openssl_pkey_free( $resource );
|
|
|
|
return $keypair;
|
|
|
|
} else {
|
|
$this->addError( 'Error in generateOpenSSLKeypair: OpenSSL PHP extension missing. Cannot continue.' );
|
|
return false;
|
|
}
|
|
|
|
} catch ( Exception $e ) {
|
|
while ( $msg = openssl_error_string() )
|
|
$this->addError( 'Error in generateOpenSSLKeypair: OpenSSL reported error: ' . $msg );
|
|
|
|
$this->addError( 'Error in generateOpenSSLKeypair(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public function generateBitcoinAddresspair( $testnet = false, $public_key = '', $private_key = '' ) {
|
|
/* generated address keypairs will not be saved to disk, only returned.
|
|
* also, be careful with your private key. if it is compromised, any BTC
|
|
* associated with that address could be stolen. use at own risk!
|
|
*/
|
|
|
|
try {
|
|
|
|
if ( $public_key == '' || $private_key == '' ) {
|
|
$this->addError( 'Error in generateBitcoinAddresspair(): You must specify a EC keypair to ' .
|
|
'generate a Bitcoin address pair.' );
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( $testnet )
|
|
return self::BTCAddress_testnet( $public_key, $private_key );
|
|
else
|
|
return self::BTCAddress( $public_key, $private_key );
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in generateBitcoinAddresspair(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public function asFacade( $newFacade ) {
|
|
/* the facade determines the type of resource accessible
|
|
* either public, user, merchant or payroll. in the API
|
|
* document, this is referred to as 'scope'.
|
|
*/
|
|
|
|
try {
|
|
|
|
/* check the newly requested facade against the list of
|
|
* available facades for this user. the public facade is
|
|
* usable by anyone and doesn't count.
|
|
*/
|
|
if ( isset( $newFacade ) &&
|
|
$newFacade != 'public' &&
|
|
in_array( $newFacade, $this->get( 'tokens[\'facades\']' ) ) &&
|
|
trim( $newFacade ) != '' ) {
|
|
|
|
$this->set( 'facade', strtolower( trim( $newFacade ) ) );
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
if ( $newFacade == 'public' ) {
|
|
/* every user can request the public facade */
|
|
$this->resetFacade();
|
|
} else {
|
|
$this->addError( 'Error in asFacade(): You do not have access to the ' .
|
|
$newFacade . ' facade.' );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
/* failsafe if we get to this point. throw an error. */
|
|
$this->addError( 'Error in asFacade(): Unknown facade: "' . $newFacade .
|
|
'". Cannot send transactions with this value.' );
|
|
|
|
return false;
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in asFacade(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public function get( $name = false, $args = false ) {
|
|
/* TODO: debatable getter method... */
|
|
if ( !$args )
|
|
return $this->__get( $name );
|
|
else
|
|
return $this->sendRequest( 'GET', $args );
|
|
}
|
|
|
|
|
|
final public function set( $name = false, $val = false, $args = false ) {
|
|
/* TODO: debatable setter method */
|
|
if ( !$args )
|
|
return $this->__set( $name, $val );
|
|
else
|
|
return $this->sendRequest( 'PUT', $args );
|
|
}
|
|
|
|
|
|
final public function showMethods() {
|
|
|
|
try {
|
|
|
|
$class = new ReflectionClass( 'BitPay' );
|
|
return $class->getMethods();
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in showMethods(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public function showProperties() {
|
|
|
|
try {
|
|
|
|
$class = new ReflectionClass( 'BitPay' );
|
|
return $class->getProperties();
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in showProperties(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final public static function encodeBase58( $hex ) {
|
|
|
|
try {
|
|
|
|
/* TODO: give reference for BASE-58 encoding here... */
|
|
if ( strlen( $hex ) %2 != 0 ) {
|
|
$return = 'Error in encodeBase58(): Uneven number of hex characters passed. ' .
|
|
'Cannot encode the string: ' . $hex;
|
|
} else {
|
|
$orighex = $hex;
|
|
$chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
$hex = self::decodeHex( $hex );
|
|
$return = '';
|
|
|
|
/* TODO: why?? */
|
|
$hex = self::add0x( $hex );
|
|
|
|
switch ( MATH_TYPE ) {
|
|
case 'GMP':
|
|
while ( gmp_cmp( $hex, 0 ) == 1 ) {
|
|
$dv = gmp_div_q( $hex, '58' );
|
|
$rem = gmp_strval( gmp_div_r( $hex, '58' ) );
|
|
$hex = $dv;
|
|
$return = $return . $chars[$rem];
|
|
}
|
|
break;
|
|
|
|
default:
|
|
$this->addError( 'Error in encodeBase58(): Unknown MATH_TYPE' );
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
$return = strrev( $return );
|
|
|
|
for ( $i=0; $i < strlen( $orighex ) && substr( $orighex, $i, 2 ) == '00'; $i += 2 )
|
|
$return = '1' . $return;
|
|
}
|
|
|
|
return $return;
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in encodeBase58(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public static function decodeBase58( $base58 ) {
|
|
|
|
try {
|
|
|
|
$origbase58 = $base58;
|
|
$chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
$return = '0';
|
|
|
|
for ( $i = 0; $i < strlen( $base58 ); $i++ ) {
|
|
$current = strpos( $chars, $base58[$i] );
|
|
|
|
switch ( MATH_TYPE ) {
|
|
case 'GMP':
|
|
$return = gmp_mul( $return, '58' );
|
|
$return = gmp_strval( gmp_add( $return, $current ) );
|
|
break;
|
|
|
|
default:
|
|
$this->addError( 'Error in decodeBase58(): Unknown MATH_TYPE' );
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$return = self::encodeHex( $return );
|
|
|
|
for ( $i = 0; $i < strlen( $origbase58 ) && $origbase58[$i] == '1'; $i++ )
|
|
$return = '00' . $return;
|
|
|
|
if ( strlen( $return ) %2 != 0 )
|
|
$return = '0' . $return;
|
|
|
|
return $return;
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in decodeBase58(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public static function hash160ToAddress( $hash160, $addressversion = ADDRESSVERSION ) {
|
|
|
|
try {
|
|
|
|
$hash160 = $addressversion . $hash160;
|
|
$check = pack( 'H*', $hash160 );
|
|
$check = hash( 'sha256', hash( 'sha256', $check, true ) );
|
|
$check = substr( $check, 0, 8 );
|
|
$hash160 = strtoupper( $hash160 . $check );
|
|
|
|
return self::encodeBase58( $hash160 );
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in hash160ToAddress(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public static function addressToHash160( $addr ) {
|
|
|
|
try {
|
|
|
|
$addr = self::decodeBase58( $addr );
|
|
$addr = substr( $addr, 2, strlen( $addr ) - 10 );
|
|
|
|
return $addr;
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in addressToHash160(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public static function checkAddress( $addr, $addressversion = ADDRESSVERSION ) {
|
|
|
|
try {
|
|
|
|
$addr = self::decodeBase58( $addr );
|
|
|
|
if ( strlen( $addr ) != 50 ) {
|
|
$this->addError( 'Error in checkAddress(): Invalid address length.' );
|
|
return false;
|
|
}
|
|
|
|
$version = substr( $addr, 0, 2 );
|
|
|
|
if ( hexdec( $version ) > hexdec( $addressversion ) ) {
|
|
$this->addError( 'Error in checkAddress(): Unknown address version byte.' );
|
|
return false;
|
|
}
|
|
|
|
$check = substr( $addr, 0, strlen( $addr ) - 8 );
|
|
$check = pack( 'H*', $check );
|
|
$check = strtoupper( hash( 'sha256', hash( 'sha256', $check, true ) ) );
|
|
$check = substr( $check, 0, 8 );
|
|
|
|
return $check == substr( $addr, strlen( $addr ) - 8 );
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in checkAddress(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public static function hash160( $data ) {
|
|
|
|
try {
|
|
|
|
$data = pack( 'H*', $data );
|
|
return strtoupper( hash( 'ripemd160', hash( 'sha256', $data, true ) ) );
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in hash160(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public static function pubKeyToAddress( $pubkey ) {
|
|
|
|
try {
|
|
|
|
return self::hash160ToAddress( self::hash160( $pubkey ) );
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in pubKeyToAddress(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public static function remove0x( $string ) {
|
|
|
|
try {
|
|
|
|
if ( trim( $string ) == '' || !is_string( $string ) ) {
|
|
$this->addError( 'Error in remove0x(): Value passed was not a string.' );
|
|
return false;
|
|
}
|
|
|
|
if ( strtolower( substr( $string, 0, 2 ) ) == '0x' )
|
|
$string = substr( $string, 2 );
|
|
|
|
return $string;
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in remove0x(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public static function add0x( $string ) {
|
|
|
|
try {
|
|
|
|
if ( !is_string( $string ) ) {
|
|
$this->addError( 'Error in add0x(): Value passed was not a string.' );
|
|
return false;
|
|
}
|
|
|
|
if ( strtolower( substr( $string, 0, 2 ) ) != '0x' )
|
|
$string = '0x' . strtoupper( $string );
|
|
|
|
return $string;
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in remove0x(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public static function decodeHex( $hex ) {
|
|
|
|
try {
|
|
|
|
if ( !is_string( $hex ) ) {
|
|
$this->addError( 'Error in decodeHex(): Value passed was not a string.' );
|
|
return false;
|
|
}
|
|
|
|
$hex = self::add0x( $hex );
|
|
$chars = '0123456789ABCDEF';
|
|
$return = '0';
|
|
|
|
for ( $i=0;$i<strlen( $hex );$i++ ) {
|
|
$current = strpos( $chars, $hex[$i] );
|
|
|
|
switch ( MATH_TYPE ) {
|
|
case 'GMP':
|
|
$return = gmp_mul( $return, '16' );
|
|
$return = gmp_strval( gmp_add( $return, $current ) );
|
|
break;
|
|
|
|
default:
|
|
return 'Error in decodeHex(): Unknown MATH_TYPE';
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $return;
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in decodeHex(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public static function encodeHex( $dec ) {
|
|
|
|
try {
|
|
|
|
if ( !is_string( $dec ) ) {
|
|
$this->addError( 'Error in encodeHex(): Value passed was not a string.' );
|
|
return false;
|
|
}
|
|
|
|
$chars = '0123456789ABCDEF';
|
|
$return = '';
|
|
|
|
switch ( MATH_TYPE ) {
|
|
case 'GMP':
|
|
while ( gmp_cmp( $dec,0 ) == 1 ) {
|
|
$dv = gmp_div_q( $dec, '16' );
|
|
$rem = gmp_strval( gmp_div_r( $dec, '16' ) );
|
|
$dec = $dv;
|
|
$return = $return . $chars[$rem];
|
|
}
|
|
break;
|
|
|
|
default:
|
|
$this->addError( 'Error in encodeHex(): ' . $e->getMessage() );
|
|
return 'Error in encodeHex(): Unknown MATH_TYPE';
|
|
break;
|
|
}
|
|
|
|
return strrev( $return );
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in encodeHex(): ' . $e->getMessage() );
|
|
return 'Error in encodeHex(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final public function showErrors() {
|
|
return $this->get( 'errors' );
|
|
}
|
|
|
|
|
|
final public function clearErrors() {
|
|
return $this->set( 'errors', $blank = array() );
|
|
}
|
|
|
|
|
|
final public function showNotices() {
|
|
return $this->get( 'notices' );
|
|
}
|
|
|
|
|
|
final public function clearNotices() {
|
|
return $this->set( 'notices', $blank = array() );
|
|
}
|
|
|
|
|
|
final public function resetFacade() {
|
|
$this->set( 'facade', 'public' );
|
|
}
|
|
|
|
|
|
final public function getResourceArgs() {
|
|
// TODO: what was I doing here??
|
|
}
|
|
|
|
|
|
final public function autoloader( $f ) {
|
|
|
|
// TODO: Not sure I need this...
|
|
$interfaceFile = 'classes/interface/' . $f . 'Interface.php';
|
|
|
|
if ( file_exists( $interfaceFile ) )
|
|
require_once $interfaceFile;
|
|
|
|
$classFile = 'classes/' . $f . '.php';
|
|
|
|
if ( file_exists( $classFile ) )
|
|
require_once $classFile;
|
|
|
|
$utilFile = 'classes/util/' . $f . '.php';
|
|
|
|
if ( file_exists( $utilFile ) )
|
|
require_once $utilFile;
|
|
|
|
}
|
|
|
|
|
|
final public function REST( $verb = "GET", $args = array() ) {
|
|
/*
|
|
* Public method to send REST request to the BitPay gateway.
|
|
* The default request will be GET.
|
|
*/
|
|
switch( strtoupper( trim( $verb ) ) ) {
|
|
case 'GET':
|
|
break;
|
|
case 'PUT':
|
|
break;
|
|
case 'POST':
|
|
break;
|
|
default:
|
|
/* Unknown verb. */
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
final private function getOpensslDigestMethods() {
|
|
|
|
try {
|
|
|
|
/* http://www.php.net/manual/en/function.openssl-get-md-methods.php */
|
|
if ( $this->get( 'opensslAvailable' ) ) {
|
|
$digests = openssl_get_md_methods();
|
|
$digests_and_aliases = openssl_get_md_methods( true );
|
|
|
|
return array_diff( $digests_and_aliases, $digests );
|
|
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in getOpensslDigestMethods(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function saveKeypair( $keypair = array() ) {
|
|
/* write newly generated keypair to disk for re-use.
|
|
* you may change the name of the keyfile as needed
|
|
* but it is extremely important that this file is NOT
|
|
* in a web-accessible location!
|
|
*/
|
|
|
|
try {
|
|
|
|
clearstatcache();
|
|
|
|
$filename = $this->get( 'defaultSaveFile' );
|
|
$directory = trim( $this->get( 'keyfile_dir' ) );
|
|
|
|
if ( trim( $filename ) == '' || is_null( $filename ) ||
|
|
trim( $directory ) == '' || is_null( $directory ) ) {
|
|
|
|
/* we have no directory or filename information */
|
|
$this->addError( 'Error in saveKeypair(): No filename or directory name provided. ' .
|
|
'Cannot save key data.' );
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( substr( $directory, -1 ) != '/' )
|
|
$directory .= '/';
|
|
|
|
if ( !is_writable( $directory ) ) {
|
|
$this->addError( 'Error in saveKeypair(): Directory "' .
|
|
$directory . '" is not writable. Cannot save key data.' );
|
|
|
|
return false;
|
|
}
|
|
|
|
$savefile = $directory . $filename;
|
|
|
|
$data = '524d524d524d3002' .
|
|
$keypair['private_key_hex'] . '02' .
|
|
$keypair['public_key'] . '02';
|
|
|
|
if ( file_put_contents( $savefile,
|
|
base64_encode(
|
|
$this->gmp_binconv( $data . hash( 'sha256', $data ) )
|
|
),
|
|
LOCK_EX ) === false ) {
|
|
|
|
/* could not save our keypair data */
|
|
$this->addError( 'Error in saveKeypair(): Error writing keyfile data to disk. Check file permissions and disk space.' );
|
|
return false;
|
|
|
|
} else {
|
|
$this->addNotice( 'Successfully wrote keypair data to "' . $savefile . '".' );
|
|
return true;
|
|
}
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in saveKeypair(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
final private function readKeypair( $location = '' ) {
|
|
/* read previously saved keypair for re-use. */
|
|
|
|
try {
|
|
|
|
clearstatcache();
|
|
|
|
if ( trim( $location ) == '' || is_null( $location ) ) {
|
|
/* we have no directory information */
|
|
$this->addError( 'Error in readKeypair(): No directory name provided. Cannot read key data.' );
|
|
return false;
|
|
}
|
|
|
|
$filename = $this->get( 'defaultSaveFile' );
|
|
$directory = $location;
|
|
$private_key_hex = '';
|
|
$public_key_hex = '';
|
|
$data = '';
|
|
|
|
if ( trim( $filename ) == '' || is_null( $filename ) ||
|
|
trim( $directory ) == '' || is_null( $directory ) ) {
|
|
|
|
/* we have no directory or filename information */
|
|
$this->addError( 'Error in readKeypair(): No filename or directory name provided. Cannot read key data.' );
|
|
return false;
|
|
}
|
|
|
|
if ( substr( $directory, -1 ) != '/' )
|
|
$directory .= '/';
|
|
|
|
$savefile = $directory . $filename;
|
|
|
|
if ( !file_exists( $savefile ) && !is_readable( $savefile ) ) {
|
|
$this->addError( 'Error in readKeypair(): Keypair data filename "' .
|
|
$savefile . '" does not exist or is not readable. Cannot read key data.' );
|
|
|
|
return false;
|
|
}
|
|
|
|
$data = file_get_contents( $savefile );
|
|
|
|
if ( $data === false ) {
|
|
$this->addError( 'Error in readKeypair(): Keypair data filename "' .
|
|
$savefile . '" does not contain any data or is not readable.' );
|
|
|
|
return false;
|
|
} else {
|
|
$this->addNotice( 'Keypair data successfully read from filename "' . $savefile . '".' );
|
|
$data = bin2hex( base64_decode( $data ) );
|
|
}
|
|
|
|
if ( strtoupper( substr( $data, 0, 12 ) ) == '524d524d524d' &&
|
|
hash( 'sha256', substr( $data, 0, -64 ) ) == substr( $data, 148 ) &&
|
|
strlen( $data ) == 212 ) {
|
|
|
|
/* check to ensure our data is good and hasn't been tampered with */
|
|
$this->addNotice( 'Keypair data passed security checks. Appears to be valid.' );
|
|
|
|
return array(
|
|
'private_key_hex' => substr( $data, 15, 64 ),
|
|
'public_key' => substr( substr( $data, 81, 64 ) )
|
|
);
|
|
|
|
} else {
|
|
/* our keydata is corrupt/invalid */
|
|
$this->addError( 'Error in readKeypair(): Keypair data is invalid. The file is corrupt and the data cannot be used.' );
|
|
|
|
return false;
|
|
}
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in readKeypair(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function gmpPointAdd( $P, $Q ) {
|
|
/* elliptic curve point addition:
|
|
* P + Q = R where
|
|
* s = ( yP - yQ ) / ( xP - xQ ) mod p
|
|
* xR = s2 - xP - xQ mod p
|
|
* yR = -yP + s( xP - xR ) mod p
|
|
*/
|
|
|
|
try {
|
|
if ( is_null( $P ) || is_null( $Q ) )
|
|
return 'infinity';
|
|
|
|
if ( $P == 'infinity' )
|
|
return $Q;
|
|
|
|
if ( $Q =='infinity' )
|
|
return $P;
|
|
|
|
if ( $P == $Q )
|
|
return $this->gmpPointDouble( $P );
|
|
|
|
$s = 0;
|
|
$R = array( 'x' => 0, 'y' => 0, 's' => 0 );
|
|
|
|
$m = gmp_sub( $P['y'], $Q['y'] );
|
|
$n = gmp_sub( $P['x'], $Q['x'] );
|
|
$o = gmp_invert( $n, $this->p );
|
|
$st = gmp_mul( $m, $o );
|
|
$s = gmp_mod( $st, $this->p );
|
|
|
|
$R['x'] = gmp_mod( gmp_sub( gmp_sub( gmp_mul( $s, $s ), $P['x'] ), $Q['x'] ), $this->p );
|
|
$R['y'] = gmp_mod( gmp_add( gmp_sub( 0, $P['y'] ), gmp_mul( $s, gmp_sub( $P['x'], $R['x'] ) ) ), $this->p );
|
|
$R['s'] = gmp_strval( $s );
|
|
$R['x'] = gmp_strval( $R['x'] );
|
|
$R['y'] = gmp_strval( $R['y'] );
|
|
|
|
return $R;
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in gmpPointAdd(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function gmpPointDouble( $P ) {
|
|
/* elliptic curve point doubling:
|
|
* 2P = R where
|
|
* s = ( 3xP2 + a ) / ( 2yP ) mod p
|
|
* xR = s2 - 2xP mod p
|
|
* yR = -yP + s( xP - xR ) mod p
|
|
*/
|
|
|
|
try {
|
|
if ( is_null( $P ) )
|
|
return 'infinity';
|
|
|
|
if ( $P == 'infinity' )
|
|
return $P;
|
|
|
|
$s = 0;
|
|
$R = array( 'x' => 0, 'y' => 0, 's' => 0, 'p' => $this->p, 'a' => $this->a );
|
|
|
|
$m = gmp_add( gmp_mul( 3, gmp_mul( $P['x'], $P['x'] ) ), $this->a );
|
|
$o = gmp_mul( 2, $P['y'] );
|
|
$n = gmp_invert( $o, $this->p );
|
|
$n2 = gmp_mod( $o, $this->p );
|
|
$st = gmp_mul( $m, $n );
|
|
$st2 = gmp_mul( $m, $n2 );
|
|
$s = gmp_mod( $st, $this->p );
|
|
$s2 = gmp_mod( $st2, $this->p );
|
|
$xmul = gmp_mul( 2, $P['x'] );
|
|
$smul = gmp_mul( $s, $s );
|
|
$xsub = gmp_sub( $smul, $xmul );
|
|
$xmod = gmp_mod( $xsub, $this->p );
|
|
$R['x'] = $xmod;
|
|
$ysub = gmp_sub( $P['x'], $R['x'] );
|
|
$ymul = gmp_mul( $s, $ysub );
|
|
$ysub2 = gmp_sub( 0, $P['y'] );
|
|
$yadd = gmp_add( $ysub2, $ymul );
|
|
|
|
$R['x'] = gmp_strval( $R['x'] );
|
|
$R['y'] = gmp_strval( gmp_mod( $yadd, $this->p ) );
|
|
$R['s'] = gmp_strval( $s );
|
|
|
|
return $R;
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in gmpPointDouble(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function gmpD2B( $num ) {
|
|
/* convert a decimal number to its binary representation */
|
|
|
|
try {
|
|
|
|
if ( is_null( $num ) ||
|
|
!is_string( $num ) ||
|
|
!isset( $num ) ||
|
|
trim( $num ) == '' ) {
|
|
|
|
/* we have missing/invalid number data */
|
|
$this->addError( 'Error in gmpD2B(): Number missing or NULL. Cannot continue.' );
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( substr( strtolower( $num ),0,2 ) == '0x' )
|
|
$num = self::decodeHex( substr( $num, 2 ) );
|
|
|
|
$tmp = $num;
|
|
$bin = '';
|
|
|
|
while ( gmp_cmp( $tmp,'0' ) > 0 ) {
|
|
if ( gmp_mod( $tmp, 2 ) == '1' )
|
|
$bin .= '1';
|
|
else
|
|
$bin .= '0';
|
|
|
|
$tmp = gmp_div( $tmp, 2 );
|
|
}
|
|
|
|
return $bin;
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in gmpD2B(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function doubleAndAdd( $x, $P ) {
|
|
/* one efficient method for handling scalar
|
|
* point multiplication. others exist that
|
|
* offer various benefits over this method.
|
|
*/
|
|
|
|
try {
|
|
|
|
/* convert our number to binary */
|
|
$tmp = $this->gmpD2B( $x );
|
|
|
|
/* obtain the number of bits */
|
|
$n = strlen( $tmp ) - 1;
|
|
|
|
/* our initial starting value */
|
|
$S = 'infinity';
|
|
|
|
/* loop through and double the point each time
|
|
* but where the bits are set, perform an add
|
|
*/
|
|
while ( $n >= 0 ) {
|
|
$S = $this->gmpPointDouble( $S );
|
|
|
|
if ( $tmp[$n] == '1' )
|
|
$S = $this->gmpPointAdd( $S, $P );
|
|
|
|
$n--;
|
|
}
|
|
|
|
return $S;
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in doubleAndAdd(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function pointTest( $P, $a, $b, $p ) {
|
|
/* general form of an elliptic curve:
|
|
* y^2 mod p = x^3 + ax + b mod p
|
|
*
|
|
* we will use that function to test if
|
|
* a point P is on the defined curve.
|
|
*/
|
|
|
|
try {
|
|
|
|
/* calculate y^2 */
|
|
$y2 = gmp_mul( $P['y'], $P['y'] );
|
|
|
|
/* calculate x^3 */
|
|
$x3 = gmp_mul( gmp_mul( $P['x'], $P['x'] ), $P['x'] );
|
|
|
|
/* calculate x^3 + ax + b */
|
|
$ax = gmp_mul( $a, $P['x'] );
|
|
$left = gmp_strval( gmp_mod( $y2, $p ) );
|
|
$right = gmp_strval( gmp_mod( gmp_add( gmp_add( $x3, $ax ), $b ), $p ) );
|
|
|
|
if($this->_testing)
|
|
$this->addNotice("DEBUG - In pointTest: left = $left and right = $right");
|
|
|
|
/* if our left term is equal to our right term
|
|
* our point is good.
|
|
*/
|
|
if ( $left == $right )
|
|
return true;
|
|
else
|
|
return false;
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in pointTest(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function gmp_hash( $message, $private_key ) {
|
|
/* calculate the ECDSA signature for a given message.
|
|
* for more info, see the FIPS DSS standard
|
|
*/
|
|
|
|
try {
|
|
|
|
/* convert the hex message to decimal */
|
|
$e = self::decodeHex( $message );
|
|
|
|
/* while r AND s are both not zero, do the following: */
|
|
do {
|
|
|
|
/* supplied private key, 'd' */
|
|
$d = self::add0x( $private_key );
|
|
|
|
/* get another random number 'k' */
|
|
$k = openssl_random_pseudo_bytes( 32, $cstrong );
|
|
|
|
if ( !$cstrong ) {
|
|
die ( 'FATAL: Could not generate cryptographically strong random number. ' .
|
|
'Your OpenSSL implementation may be broken or old.' );
|
|
}
|
|
|
|
$k_hex = self::add0x( strtoupper( bin2hex( $k ) ) );
|
|
|
|
/* get the G point parameters ( x, y ) */
|
|
$Gx = self::add0x( substr( $this->G, 2, 64 ) );
|
|
$Gy = self::add0x( substr( $this->G, 66, 64 ) );
|
|
|
|
/* calculate a new curve point from Q = k * G ( x1, y1 ) */
|
|
$P = array( 'x' => $Gx, 'y' => $Gy );
|
|
$R = $this->doubleAndAdd( $k_hex, $P );
|
|
$Rx_hex = self::encodeHex( $R['x'] );
|
|
$Ry_hex = self::encodeHex( $R['y'] );
|
|
|
|
/* ensure our coordinates are padded to 32 bytes */
|
|
while ( strlen( $Rx_hex ) < 64 )
|
|
$Rx_hex = '0' . $Rx_hex;
|
|
|
|
while ( strlen( $Ry_hex ) < 64 )
|
|
$Ry_hex = '0' . $Ry_hex;
|
|
|
|
/* ok, now that we have a new curve point we can
|
|
* calculate the actual signature values. first is 'r'
|
|
* where r = x1 mod n
|
|
*/
|
|
$r = gmp_strval( gmp_mod( self::add0x( $Rx_hex ), $this->n ) );
|
|
|
|
/* now calculate 's' where s = k^-1 * ( e+d*r ) mod n */
|
|
$edr = gmp_add( $e, gmp_mul( $d, $r ) );
|
|
$invk = gmp_invert( $k_hex, $this->n );
|
|
$kedr = gmp_mul( $invk, $edr );
|
|
$s = gmp_strval( gmp_mod( $kedr, $this->n ) );
|
|
|
|
/* the signature is the coordinate pair ( r,s ) */
|
|
$signature = array(
|
|
'r' => self::encodeHex( $r ),
|
|
's' => self::encodeHex( $s )
|
|
);
|
|
|
|
/* ensure our coordinates are padded to 32 bytes */
|
|
while ( strlen( $signature['r'] ) < 64 )
|
|
$signature['r'] = '0' . $signature['r'];
|
|
|
|
while ( strlen( $signature['s'] ) < 64 )
|
|
$signature['s'] = '0' . $signature['s'];
|
|
|
|
/* according to the spec, if either 'r' or 's' is 0
|
|
* we have to re-calculate the entire signature
|
|
*/
|
|
} while ( gmp_cmp( $r,'0' ) <= 0 || gmp_cmp( $s, '0' ) <= 0 );
|
|
|
|
$this->addNotice( 'Signature generation successful.' );
|
|
|
|
/* the 'sig_hex' value is the actual value used in the x-signature header */
|
|
return array(
|
|
'sig_rs' => $signature,
|
|
'sig_hex' => $this->serializeSig( $signature['r'],
|
|
$signature['s'] )
|
|
);
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in gmp_hash(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function gmp_verifysig( $r, $s, $msg, $Q ) {
|
|
/* calculate the ECDSA signature for a given message.
|
|
* for more info, see the FIPS DSS standard
|
|
*/
|
|
|
|
try {
|
|
|
|
if ( !isset( $r ) || trim( $r ) == '' || is_null( $r ) ||
|
|
!isset( $s ) || trim( $s ) == '' || is_null( $s ) ||
|
|
!isset( $msg ) || trim( $msg ) == '' || is_null( $msg ) ||
|
|
!isset( $Q ) || !is_array( $Q ) || is_null( $Q ) ) {
|
|
|
|
/* we have invalid data to verify */
|
|
$this->addError( 'Error in gmp_verifysig(): Invalid or missing parameters passed to function.' );
|
|
return false;
|
|
}
|
|
|
|
/* get the G, P, Q point parameters ( x, y ) */
|
|
$Gx = self::add0x( substr( $this->G, 2, 64 ) );
|
|
$Gy = self::add0x( substr( $this->G, 66, 64 ) );
|
|
|
|
$P = array( 'x' => $Gx, 'y' => $Gy );
|
|
|
|
$Qx = self::add0x( substr( $Q, 2, 64 ) );
|
|
$Qy = self::add0x( substr( $Q, 66, 64 ) );
|
|
|
|
$Q = array( 'x' => $Qx, 'y' => $Qy );
|
|
|
|
$r = self::add0x( $r );
|
|
$s = self::add0x( $s );
|
|
|
|
/* check to see if r,s are in [1, n-1] */
|
|
if ( gmp_cmp( $r, 1 ) <= 0 && gmp_cmp( $r, $this->n ) > 0 ) {
|
|
$this->addError( 'Error in gmp_verifysig(): r is out of range!' );
|
|
return false;
|
|
}
|
|
|
|
if ( gmp_cmp( $s, 1 ) <= 0 && gmp_cmp( $s, $this->n ) > 0 ) {
|
|
$this->addError( 'Error in gmp_verifysig(): s is out of range!' );
|
|
return false;
|
|
}
|
|
|
|
/* calculate the hash 'e' of $msg
|
|
* first, convert 'e' to decimal
|
|
*/
|
|
$e = self::decodeHex( $msg );
|
|
|
|
/* calculate 'w' where w = s^-1 mod n */
|
|
$w = gmp_invert( $s, $this->n );
|
|
|
|
/* calculate 'u1' where u1 = e*w mod n */
|
|
$u1 = gmp_mod( gmp_mul( $e, $w ), $this->n );
|
|
|
|
/* calculate 'u2' where u2 = r*w mod n */
|
|
$u2 = gmp_mod( gmp_mul( $r, $w ), $this->n );
|
|
|
|
/* Get new point 'Z' from two scalar multiplies and one add:
|
|
* Z( x1,y1 ) = ( u1 * G ) + ( u2 * Q )
|
|
*/
|
|
$Za = $this->doubleAndAdd( $u1, $P );
|
|
$Zb = $this->doubleAndAdd( $u2, $Q );
|
|
$Z = $this->gmpPointAdd( $Za, $Zb );
|
|
|
|
$Zx_hex = self::encodeHex( $Z['x'] );
|
|
$Zy_hex = self::encodeHex( $Z['y'] );
|
|
|
|
/* ensure our coordinates are padded to 32 bytes */
|
|
while ( strlen( $Zx_hex ) < 64 )
|
|
$Zx_hex = '0' . $Zx_hex;
|
|
|
|
while ( strlen( $Zy_hex ) < 64 )
|
|
$Zy_hex = '0' . $Zy_hex;
|
|
|
|
/* the signature is valid if r is congruent to x1 ( mod n )
|
|
* or, in other words, if r - x1 is an integer multiple of n
|
|
*/
|
|
$rsubx = gmp_sub( $r, self::add0x( $Zx_hex ) );
|
|
$rsubx_rem = gmp_div_r( $rsubx, $this->n );
|
|
|
|
if ( gmp_cmp( $rsubx_rem, '0' ) == 0 )
|
|
return true;
|
|
else
|
|
return false;
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in gmp_verifysig(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function serializeSig( $r,$s ) {
|
|
/* ASN.1 encodes the DER signature:
|
|
* 0x30 + size( all ) + 0x02 + size( r ) + r + 0x02 + size( s ) + s
|
|
*/
|
|
|
|
try {
|
|
|
|
if ( !isset( $r ) || trim( $r ) == '' || is_null( $r ) ||
|
|
!isset( $s ) || trim( $s ) == '' || is_null( $s ) ) {
|
|
|
|
/* we have missing or null data passed */
|
|
$this->addError( 'Error in serializeSig(): The r and s values cannot be missing or NULL.' );
|
|
return false;
|
|
}
|
|
|
|
|
|
for ( $x=0;$x<256;$x++ )
|
|
$digits[$x] = chr( $x );
|
|
|
|
$dec = self::decodeHex( $r );
|
|
$byte = '';
|
|
$seq = '';
|
|
$retval = array();
|
|
|
|
while ( gmp_cmp( $dec,'0' ) > 0 ) {
|
|
$dv = gmp_div( $dec, '256' );
|
|
$rem = gmp_strval( gmp_mod( $dec, '256' ) );
|
|
$dec = $dv;
|
|
$byte = $byte . $digits[$rem];
|
|
}
|
|
|
|
$byte = strrev( $byte );
|
|
|
|
/* if the msb is set add 0x00 */
|
|
if ( gmp_cmp( self::add0x( bin2hex( $byte[0] ) ), '0x80' ) >= 0 )
|
|
$byte = chr( 0x00 ) . $byte;
|
|
|
|
$retval['bin_r'] = bin2hex( $byte );
|
|
$seq = chr( 0x02 ) . chr( strlen( $byte ) ) . $byte;
|
|
$dec = self::decodeHex( $s );
|
|
$byte = '';
|
|
|
|
while ( gmp_cmp( $dec,'0' ) > 0 ) {
|
|
$dv = gmp_div( $dec, '256' );
|
|
$rem = gmp_strval( gmp_mod( $dec, '256' ) );
|
|
$dec = $dv;
|
|
$byte = $byte . $digits[$rem];
|
|
}
|
|
|
|
$byte = strrev( $byte );
|
|
|
|
/* if the msb is set add 0x00 */
|
|
if ( gmp_cmp( self::add0x( bin2hex( $byte[0] ) ), '0x80' ) >= 0 )
|
|
$byte = chr( 0x00 ) . $byte;
|
|
|
|
$retval['bin_s'] = bin2hex( $byte );
|
|
$seq = $seq . chr( 0x02 ) . chr( strlen( $byte ) ) . $byte;
|
|
$seq = chr( 0x30 ) . chr( strlen( $seq ) ) . $seq;
|
|
$retval['seq'] = bin2hex( $seq );
|
|
|
|
return $retval;
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in serializeSig(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function generateSIN( $publicKey ) {
|
|
/* the SIN is defined as the base58 representation of
|
|
* SINVERSION + SINTYPE + RIPEMD-160( SHA-256( public key ) )
|
|
* see: https://en.bitcoin.it/wiki/Identity_protocol_v1
|
|
*/
|
|
|
|
try {
|
|
|
|
if ( is_null( $publicKey ) ||
|
|
!isset( $publicKey ) ||
|
|
trim( $publicKey ) == '' ||
|
|
strlen( $publicKey ) < 64 ||
|
|
!$this->checkKeyfile( $publicKey ) ) {
|
|
|
|
$this->addError( 'Error in generateSIN: Invalid public key passed to function.' );
|
|
return false;
|
|
}
|
|
|
|
/* take the sha256 hash of the public key in binary form and returning binary */
|
|
$step1 = hash( 'sha256', gmp_binconv( $pub2 ), true );
|
|
|
|
/* take the ripemd160 hash of the sha256 hash in binary form returning hex */
|
|
$step2 = hash( 'ripemd160', $step1 );
|
|
|
|
/* prepend the hex version and hex SINtype to the hex form of the ripemd160 hash */
|
|
$step3 = SINVERSION . SINTYPE . $step2;
|
|
|
|
/* convert the appended hex string back to binary and double sha256 hash it leaving it in binary both times */
|
|
$step4 = hash( 'sha256', hash( 'sha256', gmp_binconv( $step3 ), true ), true );
|
|
|
|
/* convert it back to hex and take the first 4 hex bytes for a checksum */
|
|
$step5 = substr( bin2hex( $step4 ), 0, 8 );
|
|
|
|
/* append the first 4 bytes to the fully appended string in step 3 */
|
|
$step6 = $step3 . $step5;
|
|
|
|
/* finally base58 encode it and you're done! */
|
|
$encoded = trim( encodeBase58( $step6 ) );
|
|
|
|
if ( substr( $encoded, 0, 1 ) == 'T' ) {
|
|
/* the SIN should start with a 'T' so if it doesn't something went wrong in the hashing */
|
|
return $encoded;
|
|
} else {
|
|
$this->addError( 'Error in generateSIN(): Invalid SIN generated. It should start with the "T" character but I got "' .
|
|
$encoded . '" instead. Cannot use this value.' );
|
|
return false;
|
|
}
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in generateSIN(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function saveSIN( $sin = '' ) {
|
|
/* write newly generated SIN to disk for re-use.
|
|
* you may change the name of this file as needed
|
|
* but it is extremely important that this file is NOT
|
|
* in a web-accessible location!
|
|
*/
|
|
|
|
try {
|
|
|
|
if ( !isset( $sin ) ||
|
|
trim( $sin ) == '' ||
|
|
is_null( $sin ) ) {
|
|
|
|
/* must have SIN data to store! */
|
|
$this->addError( 'Error in saveSIN(): Missing or NULL SIN data passed to this function.' );
|
|
return false;
|
|
}
|
|
|
|
$filename = $this->get( 'defaultSINFile' );
|
|
$directory = trim( $this->get( 'keyfile_dir' ) );
|
|
|
|
if ( trim( $filename ) == '' ||
|
|
is_null( $filename ) ||
|
|
$trim( $directory ) == '' ||
|
|
is_null( $directory ) ) {
|
|
|
|
/* we have no filename or directory information to work with */
|
|
$this->addError( 'Error in saveSIN(): Missing directory or filename information. Cannot save SIN data to disk.' );
|
|
return false;
|
|
}
|
|
|
|
if ( substr( $directory,-1 ) != '/' )
|
|
$directory .= '/';
|
|
|
|
if ( !is_writable( $directory ) ) {
|
|
$this->addError( 'Error in saveSIN(): The directory "' . $directory . '" is not writable. Cannot save SIN data to disk.' );
|
|
return false;
|
|
}
|
|
|
|
$savefile = $directory . $filename;
|
|
$data = '524d524d524d3002' . $sin . '02';
|
|
|
|
if ( file_put_contents( $savefile,
|
|
base64_encode(
|
|
$this->gmp_binconv( $data . hash( 'sha256', $data ) )
|
|
),
|
|
LOCK_EX ) === false ) {
|
|
|
|
$this->addError( 'Error in saveSIN(): Could not write SIN data to file.' );
|
|
return false;
|
|
|
|
} else {
|
|
$this->addNotice( 'Successfully wrote SIN data to "' . $savefile . '".' );
|
|
return true;
|
|
}
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in saveSIN(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function readSIN( $location ) {
|
|
/* read previously saved keypair for re-use. */
|
|
|
|
try {
|
|
|
|
clearstatcache();
|
|
|
|
if ( !isset( $location ) ||
|
|
trim( $location ) == '' ||
|
|
is_null( $location ) ) {
|
|
|
|
/* must have file location to locate file */
|
|
$this->addError( 'Error in readSIN(): Missing or NULL location data passed to this function.' );
|
|
return false;
|
|
}
|
|
|
|
$filename = $this->get( 'defaultSINFile' );
|
|
$directory = $location;
|
|
$private_key_hex = '';
|
|
$public_key_hex = '';
|
|
$data = '';
|
|
|
|
if ( trim( $filename ) == '' ||
|
|
is_null( $filename ) ||
|
|
$trim( $directory ) == '' ||
|
|
is_null( $directory ) ) {
|
|
|
|
/* we have no filename or directory information to work with */
|
|
$this->addError( 'Error in readSIN(): Invalid fileame or directory data. Cannot read file from disk.' );
|
|
return false;
|
|
}
|
|
|
|
if ( substr( $directory, -1 ) != '/' )
|
|
$directory .= '/';
|
|
|
|
$savefile = $directory . $filename;
|
|
|
|
if ( !file_exists( $savefile ) && !is_readable( $savefile ) ) {
|
|
$this->addError( 'Error in readSIN(): The filename "' .
|
|
$savefile . '" does not exist or cannot be read. Check the file permissions.' );
|
|
|
|
return false;
|
|
}
|
|
|
|
$data = file_get_contents( $savefile );
|
|
|
|
if ( $data === false ) {
|
|
$this->addError( 'Error in readSIN(): The filename "' .
|
|
$savefile . '" is empty or could not be read. Check the file permissions.' );
|
|
|
|
return false;
|
|
} else {
|
|
/* good, we could read the file and it contained data */
|
|
$data = bin2hex( base64_decode( $data ) );
|
|
}
|
|
|
|
/* check to see if our data has been tampered with is has been corrupted */
|
|
if ( strtoupper( substr( $data, 0, 12 ) ) == '524d524d524d' &&
|
|
hash( 'sha256', substr( $data, 0, -64 ) ) == substr( $data, -64 ) ) {
|
|
|
|
$this->addError( 'Error in readSIN(): The filename "' .
|
|
$savefile . '" contained invalid or corrupted data. Cannot use this SIN file.' );
|
|
|
|
return $SIN = substr( $data, 15, -66 );
|
|
} else {
|
|
$this->addNotice( 'Error in readSIN(): The filename "' .
|
|
$savefile . '" passed the security checks. Successfully loaded data.' );
|
|
|
|
return false;
|
|
}
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in readKeypair(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
final private function getAccessTokens() {
|
|
/* TODO: Flesh this function out more... */
|
|
|
|
// Send a GET request to /tokens?nonce=( some higher nonce )
|
|
|
|
// Example response:
|
|
// {"data":[{"merchant":"B7jGBcyMUc5GqGFvdMu4JytSbuU3Y2MnjYWoMeJtWf7p"},{"user/sin":"GXzw3Hyr8baajPfbkmJm7DsEtEDdjg3AKABVBz8hn5t2"}]}
|
|
if( !isset( $this->tokens['merchant'] ) || !isset( $this->tokens['user/sin'] ) || !isset( $this->tokens['payroll'] ) ) {
|
|
// We haven't retrieved our tokens/facades yet.
|
|
$request = $this->sendRequest( 'GET', array( 'resource' => 'tokens', 'params' => '' ) );
|
|
}
|
|
|
|
return $this->get( 'tokens' );
|
|
}
|
|
|
|
|
|
final private function getNonce() {
|
|
|
|
try {
|
|
|
|
/* microtime() is preferred because it returns
|
|
* a higher resolution value
|
|
*/
|
|
if ( function_exists( 'microtime' ) )
|
|
return str_replace( '.', '', microtime( true ) );
|
|
else
|
|
return time();
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in getNonce(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function checkKeyfile( $data ) {
|
|
/* TODO: attempt to determine if the format valid
|
|
* PEM or DER and return the result
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
final private function parseKeyfile( $data ) {
|
|
/* TODO: convert the keyfile contents from either
|
|
* PEM or DER into hex format used here
|
|
*/
|
|
}
|
|
|
|
|
|
final private function gmp_binconv( $hex ) {
|
|
|
|
try {
|
|
|
|
/* converts hex value into byte array */
|
|
for ( $x=0; $x<256; $x++ )
|
|
$digits[$x] = chr( $x );
|
|
|
|
$dec = self::add0x( $hex );
|
|
|
|
$byte = '';
|
|
$seq = '';
|
|
|
|
while ( gmp_cmp( $dec, '0' ) > 0 ) {
|
|
$dv = gmp_div( $dec, '256' );
|
|
$rem = gmp_strval( gmp_mod( $dec, '256' ) );
|
|
$dec = $dv;
|
|
$byte = $byte . $digits[$rem];
|
|
}
|
|
|
|
$byte = strrev( $byte );
|
|
|
|
return $byte;
|
|
|
|
} catch ( Exception $e ) {
|
|
return 'Error in gmp_binconv(): ' . $e->getMessage();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function sendRequest( $verb = 'GET', $arguments ) {
|
|
|
|
/* note: the api is currently only on the bitpay test server and
|
|
* not released for production. some of the methods may change.
|
|
* for more information on the new API requests, see:
|
|
* https://test.bitpay.com/api
|
|
*
|
|
* Two special headers must be present for all non-public facades:
|
|
* x-pubkey and x-signature
|
|
*/
|
|
try {
|
|
|
|
if( $arguments == '' ||
|
|
is_null( $arguments ) ||
|
|
!isset( $arguments['resource'] ) ||
|
|
!isset( $arguments['params'] ) ) {
|
|
|
|
$this->addError( 'Error in sendRequest(): Missing request arguments. Cannot process request.' );
|
|
return false;
|
|
}
|
|
|
|
array( 'resource' => 'tokens', 'params' => '' );
|
|
|
|
$userFacade = $this->get( 'facade' );
|
|
|
|
switch ( $this->get( 'facade' ) ) {
|
|
|
|
/*
|
|
* These are the three non-public facade types
|
|
* which requre the special x-headers.
|
|
*/
|
|
case 'user':
|
|
case 'merchant':
|
|
case 'payroll':
|
|
if ( $this->get( 'pubECKey' ) != '' ) {
|
|
$xpubkey = 'x-pubkey: ' . $this->get( 'pubECKey' );
|
|
} else {
|
|
$this->addError( 'Error in sendRequest(): Public key missing or invalid. Cannot process request.' );
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* public specific headers
|
|
*/
|
|
case 'public':
|
|
break;
|
|
|
|
/*
|
|
* unknown facade
|
|
*/
|
|
default:
|
|
$this->addError( 'Error in sendRequest(): Unknown facade. Cannot process request.' );
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* The nonce is used to prevent transaction replay and therefore each
|
|
* nonce has to be higher than the one before. So briefly loop until
|
|
* getNonce() is higher than $currentNonce. Nonces are required for
|
|
* every API request regardless of verb.
|
|
*/
|
|
$currentNonce = $this->getNonce();
|
|
|
|
/*
|
|
* Sanity check value to prevent infinite loops
|
|
*/
|
|
$sanity = 0;
|
|
|
|
while ( ( $currentNonce == $this->get( 'nonce' ) ) && $sanity < 50 ) {
|
|
$this->set( 'nonce', $this->getNonce() );
|
|
$sanity++;
|
|
}
|
|
|
|
if ( $sanity >= 50 ) {
|
|
$this->addError( 'Error in sendRequest: The plugin tried to generate a higher
|
|
nonce but could not. Was your webserver\'s time changed?' );
|
|
return false;
|
|
}
|
|
|
|
$requestString = '';
|
|
$signature = '';
|
|
|
|
/*
|
|
* This master switch/case block is based on the REST verb and
|
|
* contains the verb-specific resource logic inside each case.
|
|
* Supported verbs are GET, PUT and POST.
|
|
*
|
|
* For public resources, the $signature variable will never be
|
|
* set to any value since the x-signature HTTP header is not
|
|
* required for that facade. Otherwise, the signature will
|
|
* be created after the request string is built.
|
|
*
|
|
* TODO: When building the signature, if it fails add a message
|
|
* to the error array.
|
|
*/
|
|
switch ( strtoupper( $verb ) ) {
|
|
|
|
/*
|
|
* GET is the default verb when one is not specified
|
|
* and retrieves information about a particular resouce.
|
|
*/
|
|
case 'GET':
|
|
|
|
/*
|
|
* >>> bills - merchant
|
|
* Retrieves all of the caller's bills.
|
|
*/
|
|
if ( $userFacade == 'merchant' &&
|
|
$arguments['resource'] == 'bills' &&
|
|
$arguments['params'] == '' ) {
|
|
|
|
$requestString = $this->bitpayURL . '/bills?nonce=' .
|
|
$this->getNonce();
|
|
|
|
$signature = $this->generateSignature( $requestString, $this->privECKey );
|
|
|
|
if ( $signature )
|
|
'x-signature: ' . $signature;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* >>> bills - merchant
|
|
* Retrieves all of the caller's bills with a status of X
|
|
*
|
|
* string params: status
|
|
*/
|
|
if ( $userFacade == 'merchant' &&
|
|
$arguments['resource'] == 'bills' &&
|
|
(
|
|
$arguments['params'] != '' ||
|
|
stristr( $arguments['params'],'status=' ) !== false
|
|
) ) {
|
|
|
|
$requestString = $this->bitpayURL . '/bills/{"status":"' .
|
|
substr( $arguments['params'], 6, strlen( $arguments['params'] ) ) .
|
|
'","nonce":"' . $this->getNonce().'"}';
|
|
|
|
$signature = $this->generateSignature( $requestString, $this->privECKey );
|
|
|
|
if ( $signature )
|
|
'x-signature: ' . $signature;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* >>> bills/:billId - merchant
|
|
* Retrieves the specified bill.
|
|
*/
|
|
if ( $userFacade == 'merchant' &&
|
|
$arguments['resource'] == 'bills' &&
|
|
(
|
|
$arguments['params'] != '' ||
|
|
stristr( $arguments['params'],'billId=' ) !== false
|
|
)
|
|
) {
|
|
|
|
$requestString = $this->bitpayURL . '/bills/{"billId":"' .
|
|
substr( $arguments['params'], 6, strlen( $arguments['params'] ) ) .
|
|
'","nonce":"' . $this->getNonce().'"}';
|
|
|
|
$signature = $this->generateSignature( $requestString, $this->privECKey );
|
|
|
|
if ( $signature )
|
|
'x-signature: ' . $signature;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* >>> clients - user
|
|
* Retrieves the registered client access keys for the caller.
|
|
*/
|
|
if ( $userFacade == 'user' &&
|
|
$arguments['resource'] == 'clients' &&
|
|
$arguments['params'] == '' ) {
|
|
|
|
$requestString = $this->bitpayURL . '/clients?nonce=' . $this->getNonce();
|
|
$signature = $this->generateSignature( $requestString, $this->privECKey );
|
|
|
|
if ( $signature )
|
|
'x-signature: ' . $signature;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* >>> currencies - public
|
|
* Retrieves the list of supported currencies.
|
|
*/
|
|
if ( $userFacade == 'public' &&
|
|
$arguments['resource'] == 'currencies' &&
|
|
$arguments['params'] == '' ) {
|
|
|
|
$requestString = $this->bitpayURL . '/currencies?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* >>> currencies/CODE - public
|
|
* Retrieves the specified currency.
|
|
*/
|
|
if ( $userFacade == 'public' &&
|
|
$arguments['resource'] == 'currencies' &&
|
|
( $arguments['params'] != '' ||
|
|
stristr( $arguments['params'],'code=' ) !== false ) ) {
|
|
|
|
$requestString = $this->bitpayURL . '/currencies/' .
|
|
substr( $arguments['params'], 4, strlen( $arguments['params'] ) ) .
|
|
'?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* >>> invoices - merchant
|
|
* Retrieves invoices for the calling merchant filtered by query.
|
|
*
|
|
* string params: status, orderId, itemCode, dateStart (required), dateEnd
|
|
* number params: limit, skip
|
|
*/
|
|
if( $userFacade == 'merchant' &&
|
|
$arguments['resource'] == 'invoices' &&
|
|
$arguments['params'] == '' ) {
|
|
|
|
$this->addError( 'Error in sendRequest: No parameters specified for the invoices resource.' );
|
|
return false;
|
|
|
|
} else {
|
|
|
|
$invoicesParams = explode( ',', $arguments['params'] );
|
|
|
|
array_push();
|
|
|
|
if( !isset( $invoicesParams['dateStart'] ) )
|
|
return false;
|
|
|
|
$invoicesParams = json_encode( $invoicesParams );
|
|
|
|
|
|
}
|
|
|
|
/*
|
|
* invoices/:invoiceId
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$requestString = $this->bitpayURL . '/currencies?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* invoices/:invoiceId/refunds
|
|
*
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$requestString = $this->bitpayURL . '/currencies?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/* invoices/:invoiceId/refunds/:requestId
|
|
*
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$requestString = $this->bitpayURL . '/currencies?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
|
|
/*
|
|
* >>> ledgers - merchant
|
|
* Retrieves the caller's ledgers for each currency with summary.
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$requestString = $this->bitpayURL . '/currencies?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* ledgers/:currency
|
|
*
|
|
* string params: startDate, endDate
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$requestString = $this->bitpayURL . '/currencies?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* >>> orgs - user
|
|
* Retrieves caller's organizations
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$requestString = $this->bitpayURL . '/currencies?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* >>> payouts - payroll
|
|
* Retrieves caller's payroll payouts
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$requestString = $this->bitpayURL . '/currencies?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* payouts/:payoutId
|
|
* string params: status
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$requestString = $this->bitpayURL . '/currencies?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* >>> rates - public
|
|
* Retrieves the exchange rate for all currencies.
|
|
*/
|
|
if ( $userFacade == 'merchant' &&
|
|
$arguments['resource'] == 'bills' &&
|
|
$arguments['params'] == '' ) {
|
|
|
|
$requestString = $this->bitpayURL . '/rates?nonce=' . $this->getNonce();
|
|
|
|
}
|
|
|
|
/*
|
|
* >>> rates/:currency - public
|
|
* Retrieves the exchange rate for the given currency.
|
|
*
|
|
* string params: format
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$requestString = $this->bitpayURL . '/rates?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* >>> tokens - user
|
|
* Retrieves the callers facade access tokens.
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$requestString = $this->bitpayURL . '/rates?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* >>> user - user
|
|
* Retrieves caller's user information.
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$requestString = $this->bitpayURL . '/rates?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
|
|
/*
|
|
* End of GET verb logic.
|
|
*/
|
|
break;
|
|
|
|
|
|
/*
|
|
* The POST verb creates a resource and takes a special GUID parameter
|
|
* to enforce idempotence (i.e. subsequent calls to the same POST payload
|
|
* should not create an additional resource of the same type.)
|
|
*/
|
|
case 'POST':
|
|
|
|
/*
|
|
* >>> applications - public
|
|
* Creates an application for a new merchant account.
|
|
*
|
|
* array params: users => email ( string ), firstname ( string ), lastname ( string ), phone ( string ), agreedToTOSandPP ( boolean )
|
|
* orgs => name ( string ), address1 ( string ), address2 ( string ), city ( string ), zip ( string ), country ( string ),
|
|
* isNonProfit ( boolean ), usTaxId ( string ), industry ( string ), website ( string ), cartPos ( string ),
|
|
* affiliate0id ( string )
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$this->set( 'GUID', $this->generateGUID() );
|
|
$requestString = $this->bitpayURL . '/rates?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* >>> bills - merchant
|
|
* Creates a bill for the calling merchant.
|
|
* [ array params: items => description ( string ), price ( number ), quantity ( number ) ]
|
|
* [ string params: currency, showRate, archived, name, address1, address2, city, state, zip, country, email, phone ]
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$this->set( 'GUID', $this->generateGUID() );
|
|
$requestString = $this->bitpayURL . '/rates?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* >>> invoices - merchant
|
|
* Creates an invoice for the calling merchant.
|
|
* [ number params: price ]
|
|
* [ string params: currency, orderID, itemDesc, itemCode, notificationEmail, notificationURL, redirectURL, posData ]
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$this->set( 'GUID', $this->generateGUID() );
|
|
$requestString = $this->bitpayURL . '/rates?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* >>> invoices/:invoiceId/refunds - merchant
|
|
* Creates a refund request for the given invoice.
|
|
* [ number params: amount ]
|
|
* [ string params: bitcoinAddress, currency ]
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$this->set( 'GUID', $this->generateGUID() );
|
|
$requestString = $this->bitpayURL . '/rates?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* >>> invoices/:invoiceId/notifications - merchant
|
|
* Resends the IPN for the specified invoice
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$this->set( 'GUID', $this->generateGUID() );
|
|
$requestString = $this->bitpayURL . '/rates?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* >>> keys - public
|
|
* Adds the given key ( SIN ) to the given account.
|
|
* [ string params: sin, email, label ]
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$this->set( 'GUID', $this->generateGUID() );
|
|
$requestString = $this->bitpayURL . '/rates?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* >>> payouts - payroll
|
|
* Creates a payout batch request.
|
|
* [ array params: instructions=> amount ( number ), address ( string ), label ( string ) ]
|
|
* [ number params: amount, effectiveDate ]
|
|
* [ string params: currency, reference, pricingMethod, notificationEmail, notificationURL ]
|
|
*/
|
|
if ( $userFacade == 'merchant' && $arguments['resource'] == 'bills' && $arguments['params'] == '' ) {
|
|
$this->set( 'GUID', $this->generateGUID() );
|
|
$requestString = $this->bitpayURL . '/rates?nonce=' . $this->getNonce();
|
|
}
|
|
|
|
/*
|
|
* End of POST verb logic.
|
|
*/
|
|
break;
|
|
|
|
|
|
/*
|
|
* The PUT verb is used for updating an existing resource.
|
|
*/
|
|
case 'PUT':
|
|
|
|
/* >>> bills/:billId
|
|
* Updates the specified bill.
|
|
* [ array params: items => description ( string ), price ( number ), quantity ( number ) ]
|
|
* [ string params: currency, showRate, archived, name, address1, address2, city, state, zip, country, email, phone ]
|
|
*
|
|
*
|
|
* >>> keys/:keyId
|
|
* Modifies the given key ( approving or disabled ).
|
|
* [ bool params: disabled, approved ]
|
|
* [ string params: verificationCode ]
|
|
*
|
|
*
|
|
* >>> orgs/:orgId
|
|
* Updates organizations's merchant plan.
|
|
* [ bool params: notifyOnPaid, notifyOnComplete ]
|
|
* [ string params: smsNumber, name, address1, address2, city, state, region, zip, country, industry, contactName, phone ]
|
|
* [ website, description, cartPOSsoftware, pricingCurrency, payoutCurrency, orderEmail, transactionSpeed ]
|
|
* [ number params: payoutPercentage ]
|
|
*
|
|
*
|
|
* >>> payouts/:payoutId
|
|
* Updates the given payout request.
|
|
* [ string params: status ]
|
|
*
|
|
*
|
|
* >>> user
|
|
* Updates caller's user information.
|
|
* [ string params: phone, name ]
|
|
*
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* End of POST verb logic.
|
|
*/
|
|
break;
|
|
|
|
default:
|
|
$this->addError( 'The verb ' . var_export( $verb,true ) . ' is not supported.' );
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* TODO: Include CURL code to make the call
|
|
*/
|
|
|
|
/*
|
|
* TODO: handle response here from API call.
|
|
*/
|
|
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in sendRequest(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function addError( $msg ) {
|
|
|
|
try {
|
|
|
|
$errs = $this->get( 'errors' );
|
|
|
|
if ( count( $errs ) < 64 ) {
|
|
array_push( $errs, date( 'Y-m-d H:i:s' ) . ' :: ' . $msg );
|
|
$this->set( 'errors', $errs );
|
|
} else {
|
|
$this->writeToLog( $errs );
|
|
$this->clearErrors();
|
|
array_push( $errs, date( 'Y-m-d H:i:s' ) . ' :: ' . $msg );
|
|
$this->set( 'errors', $errs );
|
|
}
|
|
|
|
return true;
|
|
|
|
} catch ( Exception $e ) {
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function addNotice( $msg ) {
|
|
|
|
try {
|
|
|
|
$msgs = $this->get( 'notices' );
|
|
|
|
if ( count( $msgs ) < 64 ) {
|
|
array_push( $msgs, date( 'Y-m-d H:i:s' ) . ' :: ' . $msg );
|
|
$this->set( 'notices', $msgs );
|
|
} else {
|
|
$this->clearNotices();
|
|
array_push( $msgs, date( 'Y-m-d H:i:s' ) . ' :: ' . $msg );
|
|
$this->set( 'notices', $msgs );
|
|
}
|
|
|
|
return true;
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( 'Error in sendRequest(): ' . $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function processResponse( $resp = array() ) {
|
|
|
|
try {
|
|
|
|
$processed_response = '';
|
|
|
|
// TODO: handle response from sendRequest
|
|
|
|
if ( isset( $resp ) ) {
|
|
// TODO: do something
|
|
|
|
return $processed_response;
|
|
|
|
} else {
|
|
$this->addError( 'Attempted to process a non-existent response.' );
|
|
return false;
|
|
}
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function writeToLog( $filename = '', $data ) {
|
|
|
|
try {
|
|
|
|
$msg = '';
|
|
|
|
foreach ( $data as $key => $value )
|
|
$msg .= $value . "\r\n";
|
|
|
|
//file_put_contents( $filename, $msg, FILE_APPEND );
|
|
|
|
// TODO: flesh this out...
|
|
//if()
|
|
error_log($data);
|
|
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final private function generateGUID() {
|
|
|
|
try {
|
|
|
|
$guid = '';
|
|
|
|
if ( function_exists( 'com_create_guid' ) ) {
|
|
$this->addNotice( 'Using Microsoft GUID creation method.' );
|
|
return trim( com_create_guid(), '{}' );
|
|
} elseif ( function_exists( 'openssl_random_pseudo_bytes' ) ) {
|
|
$this->addNotice( 'Using secure openssl random GUID creation method.' );
|
|
$guid = bin2hex( openssl_random_pseudo_bytes( 4 ) ) . '-' .
|
|
bin2hex( openssl_random_pseudo_bytes( 2 ) ) . '-' .
|
|
bin2hex( openssl_random_pseudo_bytes( 2 ) ) . '-' .
|
|
bin2hex( openssl_random_pseudo_bytes( 2 ) ) . '-' .
|
|
bin2hex( openssl_random_pseudo_bytes( 6 ) );
|
|
} else {
|
|
$this->addNotice( 'Using fallback mt_rand GUID creation method.' );
|
|
$guid = substr( md5( mt_rand() ),0,8 ) . '-' .
|
|
substr( md5( mt_rand() ),0,4 ) . '-' .
|
|
substr( md5( mt_rand() ),0,4 ) . '-' .
|
|
substr( md5( mt_rand() ),0,4 ) . '-' .
|
|
substr( md5( mt_rand() ),0,12 );
|
|
}
|
|
|
|
return strtoupper( $guid );
|
|
|
|
} catch ( Exception $e ) {
|
|
$this->addError( $e->getMessage() );
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
/* End BitPay Class */
|
|
}
|