Compare commits

..

No commits in common. "master" and "saizai-patch-1" have entirely different histories.

23 changed files with 341 additions and 114 deletions

2
.gitignore vendored
View File

@ -9,5 +9,3 @@ pkg
*.swp
bitpaykey.pem
constants.txt
coverage/
.pem.data

View File

@ -1,5 +1,2 @@
sudo: false
rvm:
- 2.1.10
- 2.2.5
- 2.3.1
- 2.1.0

View File

@ -2,14 +2,6 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [2.4.4] - 2015-04-14
### Changed
- Separated key utilities into its own Gem
## [2.4.3] - 2015-04-13
### Changed
- Loosened production gem requirements from patch level to major level
## [2.4.2] - 2015-03-11
### Fixed
- GitHub issue 39: handling post paths that include a ? and require a token. A workaround exists for this issue.

View File

@ -29,26 +29,7 @@ Most calls to the BitPay REST API require that your client is paired with the bi
Your client can be paired via the `pos` (point-of-sale) or `merchant` facade (or both). The `pos` facade allows for invoices to be created. The `merchant` facade has broader privileges to view all invoices, bills, and ledger entries, as well as to issue refunds. Consider the level of access required when you pair your client.
### A quick note on keys
The BitPay client gem includes the BitPay KeyUtilities gem, which can be used to generate new public private key pairs which it returns in PEM format. However, there are no methods which save the keys anywhere, so it is your responsibility to store the PEM file somewhere secure.
### BitPay authentication
BitPay authentication depends on four parts:
1. An account on our servers.
1. A token shared between the client and the server.
1. A public key, shared between the client and the server.
1. A private key, held exclusively by the client.
In order to complete authentication, you have to associate your private key with a token, and associate that token with an account. Once this authentication is complete, as long as you have the private key, you never have to authenticate again. The token you created will always be associated with that private key, so any time you create a new bitpay client object with that key, it is authenticated with BitPay. This is true whether you use the ruby-client, python client, or no client at all, the key is the important thing.
There are two ways to authenticate, from the client side or the server side. The Ruby Client supports both.
To pair from the server side, you log in to the BitPay server, navigate to dashboard/merchant/api-tokens, and create a new token. This creates a new token, which is associated with your account. It is not associated with a key, so we provide a pairing code that you can use as a one time secret to associate the token with a key. From the client side, you can use the client.pair_pos_client(<pairing_code>) method to associate that method with a key held by the client.
To pair from the client side, you use the client to call the /tokens endpoint on the server with no parameters. This creates a token on the server and associates that token with a public key. What it doesn't do is associate that token to an account (because we don't know what account to associate with). This call returns a pairing code, which is a one time secret that allows you to find the token you just created. In order to associate the token with an account, you log in to the BitPay server, and use the dashboard/merchant/api-tokens interface to associate the token with a specific account. And example of client side pairing is shown below.
_For development or quick deployment purposes, consider the [BitPay Ruby Command-Line Interface](https://github.com/bitpay/ruby-cli) to simplify the deployment process_
### Pairing Programatically
@ -57,31 +38,30 @@ If you are developing a client with built-in pairing capability, you can pair pr
* `pair_client()` will perform a client-initiated pairing, and will provide a pairing code that can be entered at https://bitpay.com/dashboard/merchant/api-tokens to assign either `merchant` or `pos` facade.
* `pair_client('pairing_code')` will complete a server-initiated pairing, when provided a pre-generated pairing code from https://bitpay.com/dashboard/merchant/api-tokens. In this case, the `pos` facade will be automatically assigned.
This is an example of creating a paired client with the BitPay toolset.
The example below demonstrates this using a locally generated PEM file using OpenSSL and the irb tool.
```bash
$ gem install bitpay-sdk
Successfully installed bitpay-sdk-2.2.0
1 gem installed
$ openssl ecparam -genkey -name secp256k1 -noout -out bitpaykey.pem
$ irb
2.1.1 :001 > require 'bitpay_sdk'
=> true
2.1.2 :002 > pem = BitPay::KeyUtils.generate_pem
=> "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIH8oSTRm8lVMTVOsDZleIB8AmkiuHnp+ctEknqeUmZahoAcGBSuBBAAK\noUQDQgAEbjhdKA+X8NEKgcbHhyJaBMvePV7Sj6AQuOMQzuZYdskdkPY1/jlfQwNG\n4GVd/zSw4uhfukw/SDBOEKlQGVAmxQ==\n-----END EC PRIVATE KEY-----\n"
2.1.1 :002 > client = BitPay::SDK::Client.new(api_uri: 'https://test.bitpay.com', pem: pem)
2.1.1 :002 > client = BitPay::SDK::Client.new(api_uri: 'https://test.bitpay.com', pem: File.read('bitpaykey.pem'), insecure: true)
=> #<BitPay::SDK::Client:0x000000019c6d40 @pem="---... @tokens={}>
2.1.1 :003 > client.pair_client()
=> {"data"=>[{"policies"=>[{"policy"=>"id", "method"=>"inactive", "params"=>["Tf49SFeiUAtytFEW2EUqZgWj32nP51PK73M"]}], "token"=>"BKQyVdaGQZAArdkkSuvtZN5gcN2355c8vXLj5eFPkfuK", "dateCreated"=>1422474475162, "pairingExpiration"=>1422560875162, "pairingCode"=>"Vy76yTh"}]}
```
As described above, using the value from the `pairingCode` element, visit https://test.bitpay.com/api-tokens and search to register for the appropriate facade. That client is now paired. As previously mentioned, you must save the pem string you generated in order to use the client again.
As described above, using the value from the `pairingCode` element, visit https://test.bitpay.com/api-tokens and search to register for the appropriate facade
## General Usage
### Initialize the client
```ruby
client = BitPay::SDK::Client.new(pem: File.read('bitpaykey.pem'))
client = BitPay::SDK::Client.new(pem: File.read('bitpaykey.pem')
```
Optional parameters:
@ -94,7 +74,7 @@ Optional parameters:
### Create a new bitcoin invoice
```ruby
invoice = client.create_invoice(price: <price>, currency: <currency>)
invoice = client.create_invoice (price: <price>, currency: <currency>)
```
With invoice creation, `price` and `currency` are the only required fields. If you are sending a customer from your website to make a purchase, setting `redirectURL` will redirect the customer to your website when the invoice is paid.
@ -184,20 +164,12 @@ client.cancel_refund(id: 'JB49z2MsDH7FunczeyDS8j', request_id: '4evCrXq4EDXk4oqD
### Make a HTTP request directly against the REST API
For API tasks which lack a dedicated library method, BitPay provides methods that will automatically apply the proper cryptographic parameters to a request.
For API tasks which lack a dedicated library method, BitPay provides a method that will automatically apply the proper cryptographic parameters to a request.
```ruby
client.send_request("GET", "invoices/JB49z2MsDH7FunczeyDS8j", facade: 'merchant')
## This request is identical to:
token = client.get_token("merchant")
client.get(path: "invoices/JB49z2MsDH7FunczeyDS8j", token: token)
## post requests are also possible
token = client.get_token("merchant")
client.post(path: "tokens", token: token, params: {facade: "pos"}) #returns a new token with pairing code
## equivalent to
client.send_request("POST", "tokens", facade: 'merchant', params: {facade: 'pos'})
client.send_request("GET", "/invoices/JB49z2MsDH7FunczeyDS8j", facade: 'merchant')
```
Usage:
* Specify HTTP verb and REST endpoint
* Specifying a `facade` will fetch and apply the corresponding `token`

View File

@ -1,2 +1,6 @@
source 'https://rubygems.org'
gemspec
platform :jruby do
gem 'jruby-openssl'
end

View File

@ -1,16 +1,9 @@
# BitPay Library for Ruby
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/bitpay/ruby-client/master/LICENSE.md)
[![Travis](https://img.shields.io/travis/bitpay/ruby-client.svg?style=flat-square)](https://travis-ci.org/bitpay/ruby-client)
[![Gem](https://img.shields.io/gem/v/bitpay-sdk.svg?style=flat-square)](https://rubygems.org/gems/bitpay-sdk)
[![Code Coverage](https://img.shields.io/coveralls/bitpay/ruby-client.svg?style=flat-square)](https://coveralls.io/r/bitpay/ruby-client?branch=master)
[![Code Climate](https://img.shields.io/codeclimate/github/bitpay/ruby-client.svg?style=flat-square)](https://codeclimate.com/github/bitpay/ruby-client)
# BitPay Library for Ruby [![](https://secure.travis-ci.org/bitpay/ruby-client.png)](http://travis-ci.org/bitpay/ruby-client) [![Gem Version](https://badge.fury.io/rb/bitpay-sdk.svg)](http://badge.fury.io/rb/bitpay-sdk)
Powerful, flexible, lightweight interface to the BitPay Bitcoin Payment Gateway API.
The `bitpay-sdk` gem provides all the programattic tools required to implement a ruby client application for the BitPay REST API.
The `bitpay-sdk` gem provides all the programattic tools required to implement a ruby client application for the BitPay REST API. For developers who prefer the ease of command-line pairing during the development or deployment process, BitPay provides a complementary [Ruby CLI gem](https://github.com/bitpay/ruby-cli) which can be used in conjunction with this gem.
## [Getting Started &raquo;](https://github.com/bitpay/ruby-client/blob/master/GUIDE.md)
## [Getting Started &raquo;](http://dev.bitpay.com/guides/ruby.html)
## Found a bug?
Let us know! Send a pull request or a patch. Questions? Ask! We're here to help. We will respond to all filed issues.

View File

@ -1,9 +1,12 @@
require "bundler/gem_tasks"
require 'rspec/core/rake_task'
require 'capybara'
require 'capybara/poltergeist'
require 'mongo'
require 'cucumber'
require 'cucumber/rake/task'
require_relative 'config/constants.rb'
require_relative 'config/capybara.rb'
RSpec::Core::RakeTask.new(:spec)

View File

@ -17,17 +17,19 @@ Gem::Specification.new do |s|
s.bindir = 'bin'
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.add_dependency 'bitpay-key-utils', '~>2.0.0'
s.add_dependency 'json', '~>1.8'
s.add_dependency 'rack', '~>1.5'
s.add_dependency 'ecdsa', '~>1.2'
s.add_development_dependency 'rack', '~> 2.0'
s.add_development_dependency 'rake', '12.0'
s.add_development_dependency 'rake', '10.3.2'
s.add_development_dependency 'webmock', '1.18.0'
s.add_development_dependency 'pry', '0.10.1'
s.add_development_dependency 'pry-byebug', '2.0.0'
s.add_development_dependency 'pry-rescue', '1.4.1'
s.add_development_dependency 'cucumber', '~> 1.3.17'
s.add_development_dependency 'capybara', '2.4.3'
s.add_development_dependency 'cucumber', '1.3.17'
s.add_development_dependency 'poltergeist', '1.5.1'
s.add_development_dependency 'airborne', '0.0.20'
s.add_development_dependency 'rspec', '3.1.0'
s.add_development_dependency 'mongo', '1.11.1'
s.add_development_dependency 'coveralls'
end

6
config/capybara.rb Normal file
View File

@ -0,0 +1,6 @@
Capybara.javascript_driver = :poltergeist
Capybara.default_driver = :poltergeist
Capybara.default_wait_time = 10
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, timeout: 60, js_errors: false, phantomjs_options: ['--ignore-ssl-errors=yes', '--ssl-protocol=TLSv1', '--web-security=false'])
end

View File

@ -5,8 +5,19 @@
# source ./spec/set_constants.sh https://test.bitpay.com testuser@gmail.com mypassword
#
APIURI = ENV['BITPAYAPI']
ROOT_ADDRESS = ENV['RCROOTADDRESS']
TEST_USER = ENV['RCTESTUSER']
TEST_PASS = ENV['RCTESTPASSWORD']
DASHBOARD_URL = "#{ROOT_ADDRESS}/dashboard/merchant/home"
# Specify a bitpay txid which has 6+ confirmations. Default belongs to 'bitpayrubyclient@gmail.com' test account
REFUND_TRANSACTION = ENV['REFUND_TRANSACTION']
REFUND_ADDRESS = ENV['REFUND_ADDRESS']
unless
ROOT_ADDRESS &&
TEST_USER &&
TEST_PASS
then
raise "Missing configuration options - see constants.rb"
end

View File

@ -5,11 +5,11 @@ Feature: pairing with bitpay
Scenario: the client has a correct pairing code
Given the user pairs with BitPay with a valid pairing code
Then the user receives a require token from bitpay
Then the user is paired with BitPay
Scenario: the client initiates pairing
Given the user performs a client-side pairing
Then the user receives an inactive token from bitpay
Then the user has a merchant token
Scenario Outline: the client has a bad pairing code
Given the user fails to pair with a semantically <valid> code <code>
@ -17,3 +17,8 @@ Feature: pairing with bitpay
Examples:
| valid | code | error | message |
| invalid | "a1b2c3d4" | BitPay::ArgumentError | "pairing code is not legal" |
Scenario: the client has a bad port configuration to a closed port
When the fails to pair with BitPay because of an incorrect port
Then they will receive a BitPay::ConnectionError matching "Connection refused"

View File

@ -3,9 +3,6 @@ Feature: issuing a refund
The merchant wants to issue a refund
So that they can serve their customers
Background:
Given the user is authenticated with BitPay
Scenario: creating a refund
Given the user creates a refund
Then they will receive a refund id

View File

@ -2,33 +2,42 @@
@error = nil
When(/^the user pairs with BitPay(?: with a valid pairing code|)$/) do
@client = new_client_from_stored_values
claim_code = get_claim_code_from_server @client
claim_code = get_claim_code_from_server
pem = BitPay::KeyUtils.generate_pem
@client = BitPay::SDK::Client.new(api_uri: ROOT_ADDRESS, pem: pem, insecure: true)
sleep 1 # rate limit compliance
@token = @client.pair_pos_client(claim_code)
end
When(/^the fails to pair with BitPay because of an incorrect port$/) do
pem = BitPay::KeyUtils.generate_pem
address = ROOT_ADDRESS.split(':').slice(0,2).join(':') + ":999"
client = BitPay::SDK::Client.new(api_uri: address, pem: pem, insecure: true)
begin
sleep 1 # rate limit compliance
client.pair_pos_client("1ab2c34")
raise "pairing unexpectedly worked"
rescue => error
@error = error
true
end
end
Given(/^the user is authenticated with BitPay$/) do
@client = new_client_from_stored_values
raise "client not authenticated" unless client_has_tokens(@client)
end
Given(/^the user is paired with BitPay$/) do
raise "Client is not paired" unless @client.verify_tokens
end
Then(/^the user receives an? ([A-z]+) token from bitpay$/) do |expected|
actual = @token[0]["policies"][0]["method"]
raise "Token not correct, #{actual} != #{expected}" unless actual == expected
end
Given(/^the user has a bad pairing_code "(.*?)"$/) do |arg1|
# This is a no-op, pairing codes are transient and never actually saved
end
Then(/^the user fails to pair with a semantically (?:in|)valid code "(.*?)"$/) do |code|
pem = BitPay::KeyUtils.generate_pem
client = BitPay::SDK::Client.new(api_uri: APIURI, pem: pem, insecure: true)
client = BitPay::SDK::Client.new(api_uri: ROOT_ADDRESS, pem: pem, insecure: true)
begin
sleep 1 # rate limit compliance
client.pair_pos_client(code)
@ -46,8 +55,10 @@ end
Given(/^the user performs a client\-side pairing$/) do
sleep 1
pem = BitPay::KeyUtils.generate_pem
@client = BitPay::SDK::Client.new(api_uri: APIURI, pem: pem, insecure: true)
@token = @client.pair_client({facade: 'merchant'})
@client = BitPay::SDK::Client.new(api_uri: ROOT_ADDRESS, pem: pem, insecure: true)
response = @client.pair_client({facade: 'merchant'})
@token = response.first["token"]
approve_token_on_server(response.first["pairingCode"])
end
Then(/^the user has a merchant token$/) do

View File

@ -1,6 +1,7 @@
Given(/^the user creates a refund$/) do
sleep(1)
@response = @client.refund_invoice(id: REFUND_TRANSACTION, params: {amount: 1, currency: 'USD', bitcoinAddress: REFUND_ADDRESS})
client = new_client_from_stored_values
@response = client.refund_invoice(id: REFUND_TRANSACTION, params: {amount: 1, currency: 'USD', bitcoinAddress: REFUND_ADDRESS})
end
Then(/^they will receive a refund id$/) do
@ -9,7 +10,8 @@ Then(/^they will receive a refund id$/) do
end
Given(/^the user requests a specific refund$/) do
@response = @client.get_refund(invoice_id: REFUND_TRANSACTION, request_id: @refund_id)
client = new_client_from_stored_values
@response = client.get_refund(invoice_id: REFUND_TRANSACTION, request_id: @refund_id)
end
Then(/^they will receive the refund$/) do
@ -26,7 +28,6 @@ Then(/^they will receive an array of refunds$/) do
end
Given(/^a properly formatted cancellation request$/) do
sleep(1)
client = new_client_from_stored_values
@refund_id = client.get_all_refunds_for_invoice(id: REFUND_TRANSACTION).first["id"]
@response = client.cancel_refund(invoice_id: REFUND_TRANSACTION, request_id: @refund_id)

View File

@ -1,8 +1,10 @@
require 'capybara/poltergeist'
require 'pry'
require 'fileutils'
require File.join File.dirname(__FILE__), '..', '..', 'lib', 'bitpay_sdk.rb'
require_relative '../../config/constants.rb'
require_relative '../../config/capybara.rb'
module BitPay
@ -14,18 +16,75 @@ module BitPay
TOKEN_FILE_PATH = File.join(BITPAY_CREDENTIALS_DIR, TOKEN_FILE)
end
# Lots of sleeps in here to deal with finicky transitions and PhantomJS
def get_claim_code_from_server
Capybara::visit ROOT_ADDRESS
log_in unless logged_in
Capybara::visit DASHBOARD_URL
raise "Bad Login" unless Capybara.current_session.current_url == DASHBOARD_URL
Capybara::visit "#{ROOT_ADDRESS}/api-tokens"
Capybara::find(".token-access-new-button").find(".btn").find(".icon-plus", match: :first).trigger("click")
sleep 0.50
Capybara::find(".token-access-new-button-wrapper").find_by_id("token-new-form", visible: true).find(".btn").trigger("click")
Capybara::find(".token-claimcode", match: :first).text
end
def approve_token_on_server(pairing_code)
Capybara::visit ROOT_ADDRESS
log_in unless logged_in
Capybara::visit DASHBOARD_URL
raise "Bad Login" unless Capybara.current_session.current_url == DASHBOARD_URL
Capybara::visit "#{ROOT_ADDRESS}/api-tokens"
Capybara::fill_in 'pairingCode', :with => pairing_code
Capybara::click_button "Find"
Capybara::click_button "Approve"
end
def log_in
Capybara::visit "#{ROOT_ADDRESS}/dashboard/login/"
Capybara::fill_in 'email', :with => TEST_USER
Capybara::fill_in 'password', :with => TEST_PASS
Capybara::click_on('Login')
Capybara::find(".ion-gear-a", match: :first)
end
def new_paired_client
claim_code = get_claim_code_from_server
pem = BitPay::KeyUtils.generate_pem
client = BitPay::SDK::Client.new(api_uri: ROOT_ADDRESS, pem: pem, insecure: true)
client.pair_pos_client(claim_code)
client
end
def new_client_from_stored_values
pem = ENV['BITPAYPEM'].gsub("\\n", "\n")
BitPay::SDK::Client.new(api_uri: APIURI, pem: pem, insecure: true)
if File.file?(BitPay::PRIVATE_KEY_PATH) && File.file?(BitPay::TOKEN_FILE_PATH)
token = get_token_from_file
pem = File.read(BitPay::PRIVATE_KEY_PATH)
client = BitPay::SDK::Client.new(pem: pem, tokens: token, insecure: true, api_uri: ROOT_ADDRESS )
unless client.verify_tokens then
raise "Locally stored tokens are invalid, please remove #{BitPay::TOKEN_FILE_PATH}" end
else
pem = BitPay::KeyUtils.generate_pem
client = BitPay::SDK::Client.new(api_uri: ROOT_ADDRESS, pem: pem, insecure: true)
sleep 1 # rate limit compliance
response = client.pair_client({facade: 'merchant'})
pairing_code = response.first["pairingCode"]
token = response #.first["token"]
approve_token_on_server(pairing_code)
FileUtils.mkdir_p(BitPay::BITPAY_CREDENTIALS_DIR)
File.write(BitPay::PRIVATE_KEY_PATH, pem)
File.write(BitPay::TOKEN_FILE_PATH, JSON.generate(token))
end
client
end
def get_claim_code_from_server client
token = client.get(path: "tokens")["data"].select{|tuple| tuple["merchant"]}.first.values.first
client.post(path: "tokens", token: token, params: {facade: "pos"})["data"][0]["pairingCode"]
def get_token_from_file
token = JSON.parse(File.read(BitPay::TOKEN_FILE_PATH))[0]
{token['facade'] => token['token']}
end
def client_has_tokens client
data = client.get(path: "tokens")["data"]
data.select{|tuple| tuple["pos"]}.any? && data.select{|tuple| tuple["merchant"]}.any?
def logged_in
Capybara::has_link?('Dashboard')
end

View File

@ -6,7 +6,7 @@ require 'uri'
require 'net/https'
require 'json'
require 'bitpay_key_utils'
require_relative 'key_utils'
require_relative 'rest_connector'
module BitPay
@ -66,7 +66,7 @@ module BitPay
raise BitPay::ArgumentError, "Illegal Argument: Price must be formatted as a float" unless
price.is_a?(Numeric) ||
/^[[:digit:]]+(\.[[:digit:]]{2})?$/.match(price) ||
currency == 'BTC' && /^[[:digit:]]+(\.[[:digit:]]{1,8})?$/.match(price)
currency == 'BTC' && /^[[:digit:]]+(\.[[:digit:]]{1,6})?$/.match(price)
raise BitPay::ArgumentError, "Illegal Argument: Currency is invalid." unless /^[[:upper:]]{3}$/.match(currency)
params.merge!({price: price, currency: currency})
token = get_token(facade)
@ -83,15 +83,10 @@ module BitPay
invoice["data"]
end
def get_invoices(params = {})
token = get_token('merchant')
invoice = get(path: "invoices", token: token, params: params)
invoice["data"]
end
## Gets the public version of the invoice
#
def get_public_invoice(id:)
invoice = get(path: "invoices/#{id}", public_request: true)
invoice = get(path: "invoices/#{id}", public: true)
invoice["data"]
end

124
lib/bitpay/key_utils.rb Normal file
View File

@ -0,0 +1,124 @@
# license Copyright 2011-2014 BitPay, Inc., MIT License
# see http://opensource.org/licenses/MIT
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
require 'uri'
require 'net/https'
require 'json'
require 'openssl'
require 'ecdsa'
require 'securerandom'
require 'digest/sha2'
require 'cgi'
module BitPay
class KeyUtils
class << self
def nonce
Time.now.utc.strftime('%Y%m%d%H%M%S%L')
end
## Generates a new private key
#
def generate_pem
key = OpenSSL::PKey::EC.new("secp256k1")
key.generate_key
key.to_pem
end
def create_key pem
OpenSSL::PKey::EC.new(pem)
end
def create_new_key
key = OpenSSL::PKey::EC.new("secp256k1")
key.generate_key
key
end
def get_private_key key
key.private_key.to_int.to_s(16)
end
def get_public_key key
key.public_key.group.point_conversion_form = :compressed
key.public_key.to_bn.to_s(16).downcase
end
def get_private_key_from_pem pem
raise BitPayError, MISSING_PEM unless pem
key = OpenSSL::PKey::EC.new(pem)
get_private_key key
end
def get_public_key_from_pem pem
raise BitPayError, MISSING_PEM unless pem
key = OpenSSL::PKey::EC.new(pem)
get_public_key key
end
def generate_sin_from_pem pem
#http://blog.bitpay.com/2014/07/01/bitauth-for-decentralized-authentication.html
#https://en.bitcoin.it/wiki/Identity_protocol_v1
# NOTE: All Digests are calculated against the binary representation,
# hence the requirement to use [].pack("H*") to convert to binary for each step
#Generate Private Key
key = OpenSSL::PKey::EC.new pem
key.public_key.group.point_conversion_form = :compressed
public_key = key.public_key.to_bn.to_s(2)
step_one = Digest::SHA256.hexdigest(public_key)
step_two = Digest::RMD160.hexdigest([step_one].pack("H*"))
step_three = "0F02" + step_two
step_four_a = Digest::SHA256.hexdigest([step_three].pack("H*"))
step_four = Digest::SHA256.hexdigest([step_four_a].pack("H*"))
step_five = step_four[0..7]
step_six = step_three + step_five
encode_base58(step_six)
end
## Generate ECDSA signature
# This is the last method that requires the ecdsa gem, which we would like to replace
def sign(message, privkey)
group = ECDSA::Group::Secp256k1
digest = Digest::SHA256.digest(message)
signature = nil
while signature.nil?
temp_key = 1 + SecureRandom.random_number(group.order - 1)
signature = ECDSA.sign(group, privkey.to_i(16), digest, temp_key)
return ECDSA::Format::SignatureDerString.encode(signature).unpack("H*").first
end
end
########## Private Class Methods ################
## Base58 Encoding Method
#
private
def encode_base58 (data)
code_string = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
base = 58
x = data.hex
output_string = ""
while x > 0 do
remainder = x % base
x = x / base
output_string << code_string[remainder]
end
pos = 0
while data[pos,2] == "00" do
output_string << code_string[0]
pos += 2
end
output_string.reverse()
end
end
end
end

View File

@ -8,7 +8,7 @@ module BitPay
token ||= get_token(facade)
case verb.upcase
when "GET"
return get(path: path, token: token, params: params)
return get(path: path, token: token)
when "POST"
return post(path: path, token: token, params: params)
else
@ -16,12 +16,12 @@ module BitPay
end
end
def get(path:, token: nil, public_request: false, params: {})
urlpath = '/' + path + '?'
urlpath = urlpath + 'token=' + token if token
urlpath = urlpath + '&' + params.to_param if params.present?
def get(path:, token: nil, public: false)
urlpath = '/' + path
token_prefix = if urlpath.include? '?' then '&token=' else '?token=' end
urlpath = urlpath + token_prefix + token if token
request = Net::HTTP::Get.new urlpath
unless public_request
unless public
request['X-Signature'] = KeyUtils.sign(@uri.to_s + urlpath, @priv_key)
request['X-Identity'] = @pub_key
end

View File

@ -3,5 +3,5 @@
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
module BitPay
VERSION = '2.4.6'
VERSION = '2.4.2'
end

View File

@ -22,7 +22,10 @@ module BitPay
# User agent reported to API
USER_AGENT = 'ruby-bitpay-sdk '+VERSION
MISSING_PEM = 'No pem file specified. Pass pem string'
class BitPayError < StandardError; end
class ArgumentError < ArgumentError; end
class ConnectionError < Errno::ECONNREFUSED; end
end

41
spec/key_utils_spec.rb Normal file
View File

@ -0,0 +1,41 @@
require 'spec_helper'
describe BitPay::KeyUtils do
let(:key_utils) {BitPay::KeyUtils}
describe '.generate_pem' do
it 'should generate a pem string' do
regex = /BEGIN\ EC\ PRIVATE\ KEY/
expect(regex.match(key_utils.generate_pem)).to be_truthy
end
end
describe '.get_public_key_from_pem' do
it 'should generate the right public key' do
expect(key_utils.get_public_key_from_pem(PEM)).to eq(PUB_KEY)
end
it 'should get pem from the env if none is passed' do
expect(key_utils.get_public_key_from_pem(PEM)).to eq(PUB_KEY)
end
end
describe '.generate_sin_from_pem' do
let(:pem){PEM}
let(:sin){CLIENT_ID}
it 'will return the right sin for the right pem' do
expect(key_utils.generate_sin_from_pem(pem)).to eq sin
end
end
context "errors when priv_key is not provided" do
it 'will not retrieve public key' do
expect{key_utils.get_public_key_from_pem(nil)}.to raise_error(BitPay::BitPayError)
end
end
end

12
spec/set_constants.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
export RCROOTADDRESS=$1
echo $RCROOTADDRESS
export RCTESTUSER=$2
echo $RCTESTUSER
export RCTESTPASSWORD=$3
echo $RCTESTPASSWORD
export REFUND_TRANSACTION=$4
echo $REFUND_TRANSACTION
export REFUND_ADDRESS=$5
echo $REFUND_ADDRESS

View File

@ -1,10 +1,11 @@
require 'webmock/rspec'
require 'pry'
require 'coveralls'
Coveralls.wear!
require 'capybara/rspec'
require 'capybara/poltergeist'
require File.join File.dirname(__FILE__), '..', 'lib', 'bitpay_sdk.rb'
require_relative '../config/constants.rb'
require_relative '../config/capybara.rb'
#
## Test Variables