counterparty_ruby 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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