counterparty_ruby 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.swp
2
+ *.swo
3
+ *.gem
4
+ doc
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,46 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ counterparty_ruby (0.9.0)
5
+ bitcoin-ruby
6
+ ffi
7
+ rest_client
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ bitcoin-ruby (0.0.6)
13
+ diff-lcs (1.2.5)
14
+ ffi (1.9.6)
15
+ json (1.8.1)
16
+ netrc (0.7.9)
17
+ rake (10.3.2)
18
+ rdoc (4.2.0)
19
+ json (~> 1.4)
20
+ rest_client (1.8.2)
21
+ netrc (~> 0.7.7)
22
+ rspec (3.1.0)
23
+ rspec-core (~> 3.1.0)
24
+ rspec-expectations (~> 3.1.0)
25
+ rspec-mocks (~> 3.1.0)
26
+ rspec-core (3.1.7)
27
+ rspec-support (~> 3.1.0)
28
+ rspec-expectations (3.1.2)
29
+ diff-lcs (>= 1.2.0, < 2.0)
30
+ rspec-support (~> 3.1.0)
31
+ rspec-its (1.1.0)
32
+ rspec-core (>= 3.0.0)
33
+ rspec-expectations (>= 3.0.0)
34
+ rspec-mocks (3.1.3)
35
+ rspec-support (~> 3.1.0)
36
+ rspec-support (3.1.2)
37
+
38
+ PLATFORMS
39
+ ruby
40
+
41
+ DEPENDENCIES
42
+ counterparty_ruby!
43
+ rake
44
+ rdoc
45
+ rspec
46
+ rspec-its
data/README.md ADDED
@@ -0,0 +1,197 @@
1
+ counterparty_ruby
2
+ =================
3
+ A ruby gem for communicating with a Counterparty (Bitcoin / XCP) API server.
4
+
5
+ What's up party people! [Chris DeRose](https://www.chrisderose.com) here,
6
+ community director of the [Counterparty Foundation](http://counterpartyfoundation.org/),
7
+ and today we're going to discuss.... Ruby! Or more specifically, how to start using
8
+ counterparty in your ruby and/or ruby on rails app.
9
+
10
+ This gem is designed to abstract communications with the counterpartyd api
11
+ server in an ActiveRecord-esque object model. But note that we also support
12
+ calling the api methods directly via the documented api calls in the offical docs.
13
+
14
+ Below you'll see some examples to get you started, but feel free to peruse the
15
+ specs for yet more examples. These examples all assume that you're running a
16
+ working counterpartyd on the same system that the code is running on, but it's
17
+ easy to specify another server connection if you'd like. (Just set the
18
+ Counterparty.connection to a connection object that references the correct API
19
+ server).
20
+
21
+ We hope you find this useful everyone, let us know if there are any issues, and
22
+ keep us updated on the apps you're building! We're working to make this
23
+ counterparty spool-up process as simple as possible, so bear with us while we're
24
+ setting everything up.
25
+
26
+ ![Party On Wayne](http://data.whicdn.com/images/24796384/tumblr_m0ng6rBeWT1qhd0xso1_500_large.jpg)
27
+
28
+ ## Examples
29
+ Documentation on the objects is available via:
30
+ * [counterparty_ruby's rubydoc](http://www.rubydoc.info/github/brighton36/counterparty_ruby/master),
31
+ * [The Counterparty official API guide](https://github.com/CounterpartyXCP/counterpartyd/blob/master/docs/API.rst#read-api-function-reference).
32
+
33
+ #### Find the first burn
34
+ Here we retrieve burns from the blockchain using ActiveRecord style method calls.
35
+
36
+ ```ruby
37
+ require 'counterparty_ruby'
38
+
39
+ Counterparty.production!
40
+
41
+ burns = Counterparty::Burn.find order_by: 'tx_hash', order_dir: 'asc',
42
+ start_block: 280537, end_block: 280539
43
+
44
+ puts "First burned via: %s" % burns.first.source
45
+ # => 1ADpYypUcnbezuuYpCyRCY7G4KD6a9YXiF
46
+ ```
47
+
48
+ #### Find the first burn (Alternative API-like syntax)
49
+ This example achieves the same outcome as the above example, but uses a more
50
+ json-esque call syntax.
51
+
52
+ ```ruby
53
+ require 'counterparty_ruby'
54
+
55
+ # This connects to production mode on localhost:
56
+ production = Counterparty.connection.new 4000
57
+
58
+ # Note that we follow the api reference for calls here:
59
+ burns = production.get_burns order_by: 'tx_hash', order_dir: 'asc',
60
+ start_block: 280537, end_block: 280539
61
+
62
+ puts "First burned via: %s" % burns.first.source
63
+ # => 1ADpYypUcnbezuuYpCyRCY7G4KD6a9YXiF
64
+ ```
65
+
66
+ #### Create an Issuance
67
+ Here we create an asset and persist that asset intothe blockchain using ActiveRecord style method calls.
68
+
69
+ ```ruby
70
+ require 'counterparty_ruby'
71
+
72
+ # Note that we default to test mode. Too, this example requires that your
73
+ # private key for this address exists in the counterparty server's bitcoind
74
+ first_asset = Counterparty::Issuance.new(
75
+ source: 'msCXwsPVbv1Q1pc5AjXd5TdVwy3a1fSYB2',
76
+ asset: 'MYFIRSTASSET',
77
+ description: "Its party time!",
78
+ divisible: false,
79
+ quantity: 100 )
80
+
81
+ transaction_id = first_asset.save!
82
+
83
+ puts "Transaction %s has been entered into the mempool" % transaction_id
84
+ ```
85
+
86
+ #### Create an Issuance (Alternative API-like syntax)
87
+ This example achieves the same outcome as the above example, but uses a more
88
+ json-esque call syntax.
89
+
90
+ ```ruby
91
+ require 'counterparty_ruby'
92
+
93
+ # Note that we default to test mode. Too, this example requires that your
94
+ # private key for this address exists in the counterparty server's bitcoind
95
+ transaction_id = Counterparty.connection.do_issuance(
96
+ source: 'msCXwsPVbv1Q1pc5AjXd5TdVwy3a1fSYB2',
97
+ asset: 'MYFIRSTASSET',
98
+ description: "Its party time!",
99
+ divisible: false,
100
+ quantity: 100 )
101
+
102
+ puts "Transaction %s has been entered into the mempool" % transaction_id
103
+ ```
104
+
105
+ #### Broadcast the outcome of an event
106
+ If you're the oracle, tasked with resolving a bet, here's how you would announce
107
+ an outcome to the network.
108
+
109
+ ```ruby
110
+ require 'counterparty_ruby'
111
+
112
+ gold_down = Counterparty::Broadcast.new(
113
+ source: 'msCXwsPVbv1Q1pc5AjXd5TdVwy3a1fSYB2',
114
+ fee_fraction: 0.05,
115
+ text: "Price of gold, 12AM UTC March1. 1=inc 2=dec/const",
116
+ timestamp: 1418926641,
117
+ value: 2 )
118
+
119
+ # Note that in this example, we're passing the private key that corresponds
120
+ # to the public key. This allows us to sign transactions without having to
121
+ # import the key onto the server's bitcoind. Note that this (currently) does
122
+ # pass the key to the counterpartyd server. This behavior may change in later
123
+ # versions.
124
+ tx_id = gold_down.save! 'cP7ufwcbZujaa1qkKthLbVZUaP88RS5r9awyXerJE5rAEMTRVmzc'
125
+
126
+ puts "Gold was broadcast down in transaction %s" % tx_id
127
+ ```
128
+
129
+ #### Compile, Publish and Execute a Serpent Contract
130
+ This is still beta behavior, and only supported on testnet, but here's a quick
131
+ example of how Smart Contracts are published and executed. Note that we require
132
+ the serpent CLI executable is installed on the running system
133
+
134
+ ```ruby
135
+ require 'open3'
136
+ require 'counterparty_ruby'
137
+
138
+ class Serpent
139
+ # Be sure to use the version from : https://github.com/ethereum/pyethereum
140
+ SERPENT='/usr/local/bin/serpent'
141
+
142
+ def compile(contract)
143
+ serpent 'compile', '-s', :stdin_data => contract
144
+ end
145
+
146
+ def encode_datalist(data)
147
+ serpent 'encode_datalist', data
148
+ end
149
+
150
+ private
151
+
152
+ def serpent(*args)
153
+ options = (args.last.kind_of? Hash) ? args.pop : {}
154
+ stdout, status = Open3.capture2( ([SERPENT]+args).join(' '), options)
155
+
156
+ raise StandardError, "Compile Failed: %s" % status.exitstatus unless status.success?
157
+
158
+ stdout.chomp
159
+ end
160
+ end
161
+
162
+ SOURCE_ADDRESS="msCXwsPVbv1Q1pc5AjXd5TdVwy3a1fSYB2"
163
+
164
+ Counterparty.test!
165
+
166
+ serpent = Serpent.new
167
+
168
+ compiled_script = serpent.compile <<-eos
169
+ return(msg.data[0]*2)
170
+ eos
171
+
172
+ contract_id = Counterparty::Publish.new( source: SOURCE_ADDRESS,
173
+ code_hex: compiled_script, gasprice: 1, startgas: 1000000, endowment: 0,
174
+ allow_unconfirmed_inputs: true ).save!
175
+
176
+ datalist = serpent.encode_datalist '53'
177
+
178
+ execute_id = Counterparty::Execute.new( source: SOURCE_ADDRESS,
179
+ contract_id: contract_id, payload_hex: datalist, gasprice: 5,
180
+ startgas: 160000, value: 10, allow_unconfirmed_inputs: true ).save!
181
+
182
+ puts "Executed Transaction ID: %s" % execute_id
183
+ ```
184
+
185
+ ## Have questions?
186
+ The _best_ place to start is the [Counterparty API reference](https://github.com/CounterpartyXCP/counterpartyd/blob/master/docs/API.rst#read-api-function-reference).
187
+ You'll soon find that this gem is merely a wrapper around the official
188
+ counterpartyd json API.
189
+
190
+ But, if that doesn't help tweet [@derosetech](https://twitter.com/derosetech)
191
+ and/or check the [Counterparty Forums](https://forums.counterparty.io/) for more
192
+ help from the community.
193
+
194
+ We appreciate your patience if you're having problems, please bear in mind that
195
+ we're in active development mode. And thank-you for using Counterparty!
196
+
197
+ ![Best Party Ever](http://www.quickmeme.com/img/c8/c8cc224c5b1e8b1baafeba4287d9534add53273bc79572a5fcc8ab8ab2cc19ab.jpg)
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ # File: Rakefile
2
+
3
+ require 'rake'
4
+ require 'rake/packagetask'
5
+ require 'rdoc/task'
6
+ require 'rspec/core/rake_task'
7
+
8
+ spec = Gem::Specification.load('counterparty_ruby.gemspec')
9
+
10
+ RDOC_FILES = FileList["README.md", "lib/*.rb", "lib/counterparty/*.rb"]
11
+
12
+ Rake::RDocTask.new do |rd|
13
+ rd.main = "README.rdoc"
14
+ rd.rdoc_dir = "doc/site/api"
15
+ rd.rdoc_files.include(RDOC_FILES)
16
+ end
17
+
18
+ Rake::RDocTask.new(:ri) do |rd|
19
+ rd.main = "README.md"
20
+ rd.rdoc_dir = "doc/ri"
21
+ rd.options << "--ri-system"
22
+ rd.rdoc_files.include(RDOC_FILES)
23
+ end
24
+
25
+ RSpec::Core::RakeTask.new('spec')
26
+
27
+ task :spec => :compile
28
+
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/counterparty/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Chris DeRose"]
6
+ gem.email = ["chris@chrisderose.com"]
7
+ gem.description = %q{This gem is designed to abstract communications with the counterpartyd api server in an ActiveRecord-esque object model}
8
+ gem.summary = %q{An ActiveRecord-esque abstraction of the Counterparty API}
9
+ gem.homepage = "https://github.com/brighton36/counterparty_ruby"
10
+ gem.files = `git ls-files`.split($\)
11
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
12
+ gem.test_files = gem.files.grep(%r{^(spec)/})
13
+ gem.name = "counterparty_ruby"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = Counterparty::VERSION
16
+ gem.required_ruby_version = '>= 1.9'
17
+ gem.license = 'LGPL'
18
+
19
+ ['rest_client', 'bitcoin-ruby', 'ffi'].each do |dependency|
20
+ gem.add_runtime_dependency dependency
21
+ end
22
+ ['rspec', 'rspec-its', 'rake', 'rdoc'].each do |dependency|
23
+ gem.add_development_dependency dependency
24
+ end
25
+ end
26
+
@@ -0,0 +1,200 @@
1
+ module Counterparty
2
+ # This class connects to the Counterparty api. Mostly it's not intended for
3
+ # use by library consumers, but there are some helper methods in here for
4
+ # those that prefer the Connection.get_burns syntax instead of the
5
+ # Counterparty::Burn.find syntax
6
+ class Connection
7
+ # The default connection timeout, nil is "no timeout"
8
+ DEFAULT_TIMEOUT = nil
9
+
10
+ attr_accessor :host, :port, :username, :password
11
+
12
+ # A response timeout threshold By default, this initializes to -1,
13
+ # which means the library will wait indefinitely before timing out
14
+ attr_writer :timeout
15
+
16
+ def initialize(port=14000, username='rpc', password='1234', host='localhost')
17
+ @host,@port,@username,@password=host.to_s,port.to_i,username.to_s,password.to_s
18
+ @timeout = DEFAULT_TIMEOUT
19
+ end
20
+
21
+ # The url being connected to for the purpose of an api call
22
+ def api_url
23
+ 'http://%s:%s@%s:%s/api/' % [@username,@password,@host,@port.to_s]
24
+ end
25
+
26
+ # Returns a signed raw transaction, given a private key
27
+ def sign_tx(raw_tx, private_key)
28
+ request 'sign_tx', unsigned_tx_hex: raw_tx, privkey: private_key
29
+ end
30
+
31
+ # Broadcasts a signed transaction onto the bitcoin blockchain
32
+ def broadcast_tx(signed_tx)
33
+ request 'broadcast_tx', signed_tx_hex: signed_tx
34
+ end
35
+
36
+ # Issue a request to the counterpartyd server for the given method, with the
37
+ # given params.
38
+ def request(method, params)
39
+ client = RestClient::Resource.new api_url, :timeout => @timeout
40
+ response = JSON.parse client.post({ method: method,
41
+ params: params, jsonrpc: '2.0', id: '0' }.to_json,
42
+ user: @username, password: @password, accept: 'json',
43
+ content_type: 'json' )
44
+
45
+ raise JsonResponseError.new response if response.has_key? 'code'
46
+ raise ResponseError.new response['error'] if response.has_key? 'error'
47
+
48
+ response['result']
49
+ end
50
+
51
+ # This creates the legacy api-format request methods on the connection
52
+ # argument. It lets us be relatively introspective about how we do this so
53
+ # as to be future-proof here going forward
54
+ def self.resource_request(klass) # :nodoc:
55
+ define_method(klass.to_get_request){ |params| klass.find params }
56
+ define_method(klass.to_do_request){ |params| klass.create(params).save! }
57
+ define_method(klass.to_create_request){ |params| klass.create(params).to_raw_tx }
58
+ end
59
+
60
+ # :method: do_balance
61
+ # # Sends a do_balance call to the counterpartyd server, given the provided params
62
+
63
+ # :method: create_balance
64
+ # # Sends a create_balance call to the counterpartyd server, given the provided params
65
+
66
+ # :method: do_bet
67
+ # # Sends a do_bet call to the counterpartyd server, given the provided params
68
+
69
+ # :method: create_bet
70
+ # # Sends a create_bet call to the counterpartyd server, given the provided params
71
+
72
+ # :method: do_betmatch
73
+ # # Sends a do_betmatch call to the counterpartyd server, given the provided params
74
+
75
+ # :method: create_betmatch
76
+ # # Sends a create_betmatch call to the counterpartyd server, given the provided params
77
+
78
+ # :method: do_broadcast
79
+ # # Sends a do_broadcast call to the counterpartyd server, given the provided params
80
+
81
+ # :method: create_broadcast
82
+ # # Sends a create_broadcast call to the counterpartyd server, given the provided params
83
+
84
+ # :method: do_btcpays
85
+ # # Sends a do_btcpays call to the counterpartyd server, given the provided params
86
+
87
+ # :method: create_btcpay
88
+ # # Sends a create_btcpay call to the counterpartyd server, given the provided params
89
+
90
+ # :method: do_burn
91
+ # # Sends a do_burn call to the counterpartyd server, given the provided params
92
+
93
+ # :method: create_burn
94
+ # # Sends a create_burn call to the counterpartyd server, given the provided params
95
+
96
+ # :method: do_callback
97
+ # # Sends a do_callback call to the counterpartyd server, given the provided params
98
+
99
+ # :method: create_callback
100
+ # # Sends a create_callback call to the counterpartyd server, given the provided params
101
+
102
+ # :method: do_cancel
103
+ # # Sends a do_cancel call to the counterpartyd server, given the provided params
104
+
105
+ # :method: create_cancel
106
+ # # Sends a create_cancel call to the counterpartyd server, given the provided params
107
+
108
+ # :method: do_credit
109
+ # # Sends a do_credit call to the counterpartyd server, given the provided params
110
+
111
+ # :method: create_credit
112
+ # # Sends a create_credit call to the counterpartyd server, given the provided params
113
+
114
+ # :method: do_debit
115
+ # # Sends a do_debit call to the counterpartyd server, given the provided params
116
+
117
+ # :method: create_debit
118
+ # # Sends a create_debit call to the counterpartyd server, given the provided params
119
+
120
+ # :method: do_dividend
121
+ # # Sends a do_dividend call to the counterpartyd server, given the provided params
122
+
123
+ # :method: create_dividend
124
+ # # Sends a create_dividend call to the counterpartyd server, given the provided params
125
+
126
+ # :method: do_issuance
127
+ # # Sends a do_issuance call to the counterpartyd server, given the provided params
128
+
129
+ # :method: create_issuance
130
+ # # Sends a create_issuance call to the counterpartyd server, given the provided params
131
+
132
+ # :method: do_order
133
+ # # Sends a do_order call to the counterpartyd server, given the provided params
134
+
135
+ # :method: create_order
136
+ # # Sends a create_order call to the counterpartyd server, given the provided params
137
+
138
+ # :method: do_ordermatch
139
+ # # Sends a do_ordermatch call to the counterpartyd server, given the provided params
140
+
141
+ # :method: create_ordermatch
142
+ # # Sends a create_ordermatch call to the counterpartyd server, given the provided params
143
+
144
+ # :method: do_send
145
+ # # Sends a do_send call to the counterpartyd server, given the provided params
146
+
147
+ # :method: create_send
148
+ # # Sends a create_send call to the counterpartyd server, given the provided params
149
+
150
+ # :method: do_message
151
+ # # Sends a do_message call to the counterpartyd server, given the provided params
152
+
153
+ # :method: create_message
154
+ # # Sends a create_message call to the counterpartyd server, given the provided params
155
+
156
+ # :method: do_callback
157
+ # # Sends a do_callback call to the counterpartyd server, given the provided params
158
+
159
+ # :method: create_callback
160
+ # # Sends a create_callback call to the counterpartyd server, given the provided params
161
+
162
+ # :method: do_betexpiration
163
+ # # Sends a do_betexpiration call to the counterpartyd server, given the provided params
164
+
165
+ # :method: create_betexpiration
166
+ # # Sends a create_betexpiration call to the counterpartyd server, given the provided params
167
+
168
+ # :method: do_orderexpiration
169
+ # # Sends a do_orderexpiration call to the counterpartyd server, given the provided params
170
+
171
+ # :method: create_orderexpiration
172
+ # # Sends a create_orderexpiration call to the counterpartyd server, given the provided params
173
+
174
+ # :method: do_betmatchexpiration
175
+ # # Sends a do_betmatchexpiration call to the counterpartyd server, given the provided params
176
+
177
+ # :method: create_betmatchexpiration
178
+ # # Sends a create_betmatchexpiration call to the counterpartyd server, given the provided params
179
+
180
+ # :method: do_ordermatchexpiration
181
+ # # Sends a do_ordermatchexpiration call to the counterpartyd server, given the provided params
182
+
183
+ # :method: create_ordermatchexpiration
184
+ # # Sends a create_ordermatchexpiration call to the counterpartyd server, given the provided params
185
+
186
+ # Go ahead and setup the defined resources, and throw them into the native-style
187
+ # api methods:
188
+ Counterparty.constants.each do |c|
189
+ begin
190
+ klass = Counterparty.const_get(c)
191
+ if klass.respond_to?(:api_name) && klass != Counterparty::CounterResource
192
+ self.resource_request klass
193
+ end
194
+ rescue NameError
195
+ next
196
+ end
197
+ end
198
+
199
+ end
200
+ end
@@ -0,0 +1,130 @@
1
+ # This class is mostly used to decode json transactions from raw transactions
2
+ # much of this implementation was inspired from:
3
+ # https://gist.github.com/shesek/5835695
4
+ class RawTx
5
+ # This is a map of offset to characters used for decoding base64 strings:
6
+ BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
7
+
8
+ MAX_32BIT_INTEGER=2**32-1
9
+
10
+ # Creates a raw transaction from a hexstring
11
+ def initialize(as_hexstring)
12
+ @hexstring = as_hexstring.downcase
13
+ raise ArgumentError, "Unsupported hex" unless /\A[0-9a-f]+\Z/.match @hexstring
14
+ end
15
+
16
+ # Returns this transaction in a standard json format
17
+ def to_hash(options = {})
18
+ bytes = @hexstring.scan(/../).collect(&:hex)
19
+ size = bytes.length
20
+
21
+ # Now we start shift elements off the byte stack:
22
+ version = shift_u32(bytes)
23
+
24
+ raise ArgumentError, "Unsupported Version/Tx" unless version == 0x01
25
+
26
+ # Parse the inputs:
27
+ ins = (0...shift_varint(bytes)).collect do
28
+ hash = bytes.slice!(0,32).reverse.collect{|n| '%02x' % n}.join
29
+ index,script,seq = shift_u32(bytes),shift_varchar(bytes),shift_u32(bytes)
30
+
31
+ # NOTE: We may want to base64 encode the hash, or support this via an
32
+ # option : self.class.bytes_to_base64_s(hash).reverse),
33
+
34
+ # NOTE: The :seq field isnt actually used right now, so some rawtx decoders
35
+ # return the varint (like decoder), and some return UINT_MAX (4294967295)
36
+ { 'txid' => hash, 'vout' => index, 'sequence' => MAX_32BIT_INTEGER,
37
+ 'scriptSig' => {'hex' => hex_stringify(script),
38
+ 'asm' => disassemble_script(script) } }
39
+ end
40
+
41
+ # Parse outputs:
42
+ outs = (0...shift_varint(bytes)).collect do |i|
43
+ value, script = shift_u64(bytes), shift_varchar(bytes)
44
+
45
+ { 'value' => self.class.bytes_to_ui(value).to_f/1e8, 'n' => i,
46
+ 'scriptPubKey' => {'hex' => hex_stringify(script),
47
+ 'asm' => disassemble_script(script) } }
48
+ end
49
+
50
+ lock_time = shift_u32 bytes
51
+
52
+ {'vin' => ins, 'vout' => outs, 'lock_time' => lock_time, 'ver' => version,
53
+ 'vin_sz' => ins.length, 'vout_sz' => outs.length, 'size' => size}
54
+ end
55
+
56
+ # Convert an array of 4 bit numbers into an unsigned int,
57
+ # works for numbers up to 32-bit only
58
+ def self.nibbles_to_ui(nibbles)
59
+ raise ArgumentError if nibbles.length > 4
60
+
61
+ nibbles.each_with_index.inject(0){ |sum, (b,i)|
62
+ sum += (b.to_i & 0xff) << (4 * i) }
63
+ end
64
+
65
+ # Convert an array of 8 bit numbers into an unsigned int,
66
+ # Remember that for each input byte, the most significant nibble comes last.
67
+ def self.bytes_to_ui(bytes)
68
+ nibbles = bytes.collect{|b| [b & 0x0f, b >> 4]}.flatten
69
+ nibbles.each_with_index.inject(0){|sum,(b,i)| sum += b * 16**i}
70
+ end
71
+
72
+ # Convert an array of bytes to a base64 string.
73
+ def self.bytes_to_base64_s(input_bytes)
74
+ input_bytes.each_slice(3).collect.with_index{ |bytes,i|
75
+ # This is the base offset of this 3-byte slice in the sequence:
76
+ i_triplet = i*3
77
+
78
+ # Here we compose a triplet by or'ing the shifted bytes:
79
+ triplet = bytes.collect.with_index{|b,j| b << 8*(2-j) }.reduce(:|)
80
+
81
+ # And here we convert to chars, unless with equals as nil-padding:
82
+ 0.upto(3).collect do |j|
83
+ (i_triplet * 8 + j * 6 <= input_bytes.length * 8) ?
84
+ BASE64_CHARS[((triplet >> 6 * (3 - j)) & 0x3F)] : '='
85
+ end
86
+ }.join
87
+ end
88
+
89
+ private
90
+
91
+ def disassemble_script(bytes)
92
+ # We don't actually need to reference a hash argument to achieve disassembly:
93
+ btc_script = Bitcoin::Script.new String.new
94
+ chunks = btc_script.parse bytes.pack('C*')
95
+ btc_script.to_string chunks
96
+ end
97
+
98
+ def hex_stringify(nibbles)
99
+ nibbles.collect{|c| '%02x' % c}.join
100
+ end
101
+
102
+ # https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_string
103
+ def shift_varchar(bytes)
104
+ bytes.slice! 0, shift_varint(bytes)
105
+ end
106
+
107
+ # https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer
108
+ def shift_varint(bytes)
109
+ n = shift_u8 bytes
110
+ case n
111
+ when 0xfd then shift_u16 bytes
112
+ when 0xfe then shift_u32 bytes
113
+ when 0xff then shift_u64 bytes
114
+ else n
115
+ end
116
+ end
117
+
118
+ # These are numeric byte-shift short-cuts that make for more readable code
119
+ # above:
120
+ [1,2,4].each do |n|
121
+ define_method('shift_u%s' % [n*8]) do |bytes|
122
+ self.class.nibbles_to_ui bytes.slice!(0,n)
123
+ end
124
+ end
125
+
126
+ # 64 bit requests are an exception, and kept as 8-byte arrays:
127
+ def shift_u64(bytes)
128
+ bytes.slice!(0,8)
129
+ end
130
+ end