* @copyright 2014 BITPAY, INC. * @license http://opensource.org/licenses/MIT The MIT License (MIT) * @version Release 0.1 * @link https://github.com/ionux/<> * @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 * @copyright 2014 BITPAY, INC. * @license http://opensource.org/licenses/MIT The MIT License (MIT) * @version Release 0.1 * @link https://github.com/ionux/<> * @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 "
";
			 * 	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 "
"; * } * * ************************************************************************** * **************** 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;$igetMessage(); } } 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 */ }