<?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 */
}
