btcruby 0.0.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +18 -0
  3. data/.travis.yml +7 -0
  4. data/FAQ.md +7 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +18 -0
  7. data/HOWTO.md +17 -0
  8. data/LICENSE +19 -0
  9. data/README.md +59 -0
  10. data/Rakefile +6 -0
  11. data/TODO.txt +40 -0
  12. data/bin/console +19 -0
  13. data/btcruby.gemspec +20 -0
  14. data/documentation/address.md +73 -0
  15. data/documentation/base58.md +52 -0
  16. data/documentation/block.md +127 -0
  17. data/documentation/block_header.md +120 -0
  18. data/documentation/constants.md +88 -0
  19. data/documentation/data.md +54 -0
  20. data/documentation/diagnostics.md +90 -0
  21. data/documentation/extensions.md +76 -0
  22. data/documentation/hash_functions.md +58 -0
  23. data/documentation/hash_id.md +22 -0
  24. data/documentation/index.md +230 -0
  25. data/documentation/key.md +177 -0
  26. data/documentation/keychain.md +180 -0
  27. data/documentation/network.md +75 -0
  28. data/documentation/opcode.md +220 -0
  29. data/documentation/openssl.md +7 -0
  30. data/documentation/p2pkh.md +71 -0
  31. data/documentation/p2sh.md +64 -0
  32. data/documentation/proof_of_work.md +84 -0
  33. data/documentation/script.md +280 -0
  34. data/documentation/signature.md +71 -0
  35. data/documentation/transaction.md +213 -0
  36. data/documentation/transaction_builder.md +188 -0
  37. data/documentation/transaction_input.md +133 -0
  38. data/documentation/transaction_output.md +130 -0
  39. data/documentation/wif.md +72 -0
  40. data/documentation/wire_format.md +70 -0
  41. data/lib/btcruby/address.rb +296 -0
  42. data/lib/btcruby/base58.rb +108 -0
  43. data/lib/btcruby/big_number.rb +47 -0
  44. data/lib/btcruby/block.rb +170 -0
  45. data/lib/btcruby/block_header.rb +231 -0
  46. data/lib/btcruby/constants.rb +59 -0
  47. data/lib/btcruby/currency_formatter.rb +64 -0
  48. data/lib/btcruby/data.rb +98 -0
  49. data/lib/btcruby/diagnostics.rb +92 -0
  50. data/lib/btcruby/errors.rb +8 -0
  51. data/lib/btcruby/extensions.rb +65 -0
  52. data/lib/btcruby/hash_functions.rb +54 -0
  53. data/lib/btcruby/hash_id.rb +18 -0
  54. data/lib/btcruby/key.rb +517 -0
  55. data/lib/btcruby/keychain.rb +464 -0
  56. data/lib/btcruby/network.rb +73 -0
  57. data/lib/btcruby/opcode.rb +197 -0
  58. data/lib/btcruby/open_assets/asset.rb +35 -0
  59. data/lib/btcruby/open_assets/asset_address.rb +49 -0
  60. data/lib/btcruby/open_assets/asset_definition.rb +75 -0
  61. data/lib/btcruby/open_assets/asset_id.rb +24 -0
  62. data/lib/btcruby/open_assets/asset_marker.rb +94 -0
  63. data/lib/btcruby/open_assets/asset_processor.rb +377 -0
  64. data/lib/btcruby/open_assets/asset_transaction.rb +184 -0
  65. data/lib/btcruby/open_assets/asset_transaction_builder/errors.rb +15 -0
  66. data/lib/btcruby/open_assets/asset_transaction_builder/provider.rb +32 -0
  67. data/lib/btcruby/open_assets/asset_transaction_builder/result.rb +47 -0
  68. data/lib/btcruby/open_assets/asset_transaction_builder.rb +418 -0
  69. data/lib/btcruby/open_assets/asset_transaction_input.rb +64 -0
  70. data/lib/btcruby/open_assets/asset_transaction_output.rb +140 -0
  71. data/lib/btcruby/open_assets.rb +26 -0
  72. data/lib/btcruby/openssl.rb +536 -0
  73. data/lib/btcruby/proof_of_work.rb +110 -0
  74. data/lib/btcruby/safety.rb +26 -0
  75. data/lib/btcruby/script.rb +733 -0
  76. data/lib/btcruby/signature_hashtype.rb +37 -0
  77. data/lib/btcruby/transaction.rb +511 -0
  78. data/lib/btcruby/transaction_builder/errors.rb +15 -0
  79. data/lib/btcruby/transaction_builder/provider.rb +54 -0
  80. data/lib/btcruby/transaction_builder/result.rb +73 -0
  81. data/lib/btcruby/transaction_builder/signer.rb +28 -0
  82. data/lib/btcruby/transaction_builder.rb +520 -0
  83. data/lib/btcruby/transaction_input.rb +298 -0
  84. data/lib/btcruby/transaction_outpoint.rb +30 -0
  85. data/lib/btcruby/transaction_output.rb +315 -0
  86. data/lib/btcruby/version.rb +3 -0
  87. data/lib/btcruby/wif.rb +118 -0
  88. data/lib/btcruby/wire_format.rb +362 -0
  89. data/lib/btcruby.rb +44 -2
  90. data/sample_code/creating_a_p2sh_multisig_address.rb +21 -0
  91. data/sample_code/creating_a_transaction_manually.rb +44 -0
  92. data/sample_code/generating_an_address.rb +20 -0
  93. data/sample_code/using_transaction_builder.rb +49 -0
  94. data/spec/address_spec.rb +206 -0
  95. data/spec/all.rb +6 -0
  96. data/spec/base58_spec.rb +83 -0
  97. data/spec/block_header_spec.rb +18 -0
  98. data/spec/block_spec.rb +18 -0
  99. data/spec/currency_formatter_spec.rb +46 -0
  100. data/spec/data_spec.rb +50 -0
  101. data/spec/diagnostics_spec.rb +41 -0
  102. data/spec/key_spec.rb +205 -0
  103. data/spec/keychain_spec.rb +261 -0
  104. data/spec/network_spec.rb +48 -0
  105. data/spec/open_assets/asset_address_spec.rb +33 -0
  106. data/spec/open_assets/asset_id_spec.rb +15 -0
  107. data/spec/open_assets/asset_marker_spec.rb +47 -0
  108. data/spec/open_assets/asset_processor_spec.rb +567 -0
  109. data/spec/open_assets/asset_transaction_builder_spec.rb +273 -0
  110. data/spec/open_assets/asset_transaction_spec.rb +70 -0
  111. data/spec/proof_of_work_spec.rb +53 -0
  112. data/spec/script_spec.rb +66 -0
  113. data/spec/spec_helper.rb +8 -0
  114. data/spec/transaction_builder_spec.rb +338 -0
  115. data/spec/transaction_spec.rb +162 -0
  116. data/spec/wire_format_spec.rb +283 -0
  117. metadata +141 -7
@@ -0,0 +1,72 @@
1
+ [Index](index.md)
2
+
3
+ BTC::WIF
4
+ ========
5
+
6
+ A subclass of [BTC::Address](address.md) that represents a serialized private [key](key.md) in a Wallet Import Format (WIF), also known as "sipa format".
7
+
8
+ Initializers
9
+ ------------
10
+
11
+ #### new(string: *String*)
12
+
13
+ Returns a new WIF by parsing a [Base58Check](base58.md)-encoded string (must be a valid WIF string).
14
+
15
+ #### new(private\_key: *String*, network: *BTC::Network*, public\_key\_compressed: *false|true*)
16
+
17
+ Returns a new WIF with the 32-byte binary string `private_key`.
18
+
19
+ If `network` is not specified, [BTC::Network.default](network.md#default) is used.
20
+
21
+ If `public_key_compressed` is not specified, `false` is used.
22
+
23
+ #### new(key: *BTC::Key*, network: *BTC::Network*, public\_key\_compressed: *false|true*)
24
+
25
+ Returns a new WIF with [key.private_key](key.md).
26
+
27
+ Raises `ArgumentError` if `key` does not have a `private_key` component.
28
+
29
+ If `network` is not specified, `key.network` is used (instance of [BTC::Network](network.md)).
30
+
31
+ If `public_key_compressed` is not specified, `key.public_key_compressed` is used.
32
+
33
+
34
+ Instance Methods
35
+ ----------------
36
+
37
+ #### key
38
+
39
+ Returns a [BTC::Key](key.md) instance represented by the WIF. The returned object inherits `network` and `public_key_compressed` attributes.
40
+
41
+ #### private_key
42
+
43
+ Returns raw binary 32-byte private key stored in WIF.
44
+
45
+ #### to_s
46
+
47
+ Returns string representation of the WIF in [Base58Check](base58.md) encoding.
48
+
49
+ #### public_address
50
+
51
+ Returns [BTC::PublicKeyAddress](p2pkh.md) instance corresponding to this key's public key.
52
+
53
+ #### public\_key\_compressed
54
+
55
+ Returns `true` or `false` depending on whether the corresponding [public key](key.md) should be compressed (33 bytes) or not (65 bytes).
56
+
57
+ #### network
58
+
59
+ Returns a [BTC::Network](network.md) instance based on the version prefix of the address (mainnet or testnet).
60
+
61
+ #### version
62
+
63
+ Returns an integer value of the one-byte prefix of the WIF encoding. 128 for mainnet, 239 for testnet.
64
+
65
+ #### p2pkh?
66
+
67
+ Returns `false`.
68
+
69
+ #### p2sh?
70
+
71
+ Returns `false`.
72
+
@@ -0,0 +1,70 @@
1
+ [Index](index.md)
2
+
3
+ BTC::WireFormat
4
+ ===============
5
+
6
+ WireFormat module implements routines dealing with parsing and writing protocol messages.
7
+ All functions are also available as methods of `BTC::WireFormat` object.
8
+
9
+ Various structures have a variable-length data prepended by a length prefix which is itself of variable length.
10
+ This length prefix is a variable-length integer, *varint* (aka *CompactSize*).
11
+
12
+ NB. *varint* refers to what Satoshi called [CompactSize](https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer).
13
+ BitcoinQT has later added even more compact format called *CVarInt* to use in its local block storage. *CVarInt* is not implemented here.
14
+
15
+ Value Encoded | Storage Size (bytes) | Format
16
+ :---------------|:-----------------------|:----------------------------
17
+ < 0xfd | 1 | uint8_t
18
+ ≤ 0xffff | 3 | 0xfd followed by the value as little endian uint16_t
19
+ ≤ 0xffffffff | 5 | 0xfe followed by the value as little endian uint32_t
20
+ > 0xffffffff | 9 | 0xff followed by the value as little endian uint64_t
21
+
22
+
23
+ Module Functions
24
+ ----------------
25
+
26
+ #### read_varint(data: *String*, offset: *Integer*)
27
+ #### read_varint(stream: *IO*, offset: *Integer*)
28
+
29
+ Returns `[value, length]` by reading from binary string `data` or `stream`.
30
+ Value is a decoded integer value. Length is number of bytes read (including offset bytes).
31
+
32
+ In case of failure, returns `[nil, length]` where `length` is a number of bytes read before the error was encountered.
33
+
34
+ Default `offset` is 0.
35
+
36
+
37
+ #### encode_varint(*integer*)
38
+
39
+ Returns binary varint representation of `integer`.
40
+
41
+
42
+ #### write_varint(integer, data: *String*)
43
+ #### write_varint(integer, stream: *IO*)
44
+
45
+ Encodes the `integer` and appends it to binary string `data` or writes to `stream`.
46
+
47
+ Returns binary varint representation of `integer`.
48
+
49
+
50
+ #### read_string(data: *String*, offset: *Integer*)
51
+ #### read_string(stream: *IO*, offset: *Integer*)
52
+
53
+ Returns `[string, length]` where `length` is number of bytes read (includes length prefix and offset bytes).
54
+
55
+ In case of failure, returns `[nil, length]` where `length` is a number of bytes read before the error was encountered.
56
+
57
+ Default `offset` is 0.
58
+
59
+
60
+ #### encode_string(*string*)
61
+
62
+ Returns binary representation of `string` (equals the string itself prepended with its byte length in varint format).
63
+
64
+
65
+ #### write_string(string, data: *String*)
66
+ #### write_string(string, stream: *IO*)
67
+
68
+ Encodes the `string` and appends it to binary string `data` or writes to `stream`.
69
+
70
+ Returns binary representation of `string` (that is, with varint length prefix).
@@ -0,0 +1,296 @@
1
+ # Addresses are Base58-encoded pieces of data representing various objects:
2
+ #
3
+ # 1. Public key hash address. Example: 19FGfswVqxNubJbh1NW8A4t51T9x9RDVWQ.
4
+ # 2. Private key for uncompressed public key (WIF).
5
+ # Example: 5KQntKuhYWSRXNqp2yhdXzjekYAR7US3MT1715Mbv5CyUKV6hVe.
6
+ # 3. Private key for compressed public key (WIF).
7
+ # Example: L3p8oAcQTtuokSCRHQ7i4MhjWc9zornvpJLfmg62sYpLRJF9woSu.
8
+ # 4. Script hash address (P2SH). Example: 3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8.
9
+ #
10
+ # To differentiate between testnet and mainnet, use `network` accessor or `mainnet?`/`testnet?` methods.
11
+ #
12
+ # To check if the instance of the class is available for
13
+ # mainnet or testnet, use mainnet? and testnet? methods respectively.
14
+ #
15
+ # Usage:
16
+ # 1. When receiving an address in Base58 format, convert it to a proper type by doing:
17
+ #
18
+ # address = BTC::Address.parse("19FGfswVqxNubJbh1NW8A4t51T9x9RDVWQ")
19
+ #
20
+ # 2. To create an address, use appropriate type and call with_data(binary_data):
21
+ #
22
+ # address = BTC::PublicKeyAddress.new(hash: hash)
23
+ #
24
+ # 3. To convert address to its Base68Check format call to_s:
25
+ #
26
+ # string = address.to_s
27
+ #
28
+ module BTC
29
+ class Address
30
+
31
+ # Converts base58-encoded string to a BTC::Address instance.
32
+ # If BTC::Address is already provided, returns it.
33
+ def self.with_address(string_or_address)
34
+ raise ArgumentError, "Use Address.parse(...) instead"
35
+ end
36
+
37
+ def self.with_string(base58check_string)
38
+ raise ArgumentError, "Use #{self}.parse(...) instead"
39
+ end
40
+
41
+ # Decodes address from a Base58Check-encoded string
42
+ def self.parse(string_or_address)
43
+ raise ArgumentError, "Argument is missing" if !string_or_address
44
+ if string_or_address.is_a?(self)
45
+ return string_or_address
46
+ elsif string_or_address.is_a?(Address)
47
+ raise ArgumentError, "Argument must be an instance of #{self}, not #{string_or_address.class}."
48
+ end
49
+ string = string_or_address
50
+ raise ArgumentError, "String is expected" if !string.is_a?(String)
51
+ raw_data = Base58.data_from_base58check(string)
52
+ result = parse_raw_data(raw_data, string)
53
+ if !result.is_a?(self)
54
+ raise ArgumentError, "Argument must be an instance of #{self}, not #{result.class}."
55
+ end
56
+ result
57
+ end
58
+
59
+ # Internal method to parse address from raw binary data.
60
+ def self.parse_raw_data(raw_data, _string = nil)
61
+ raise ArgumentError, "Raw data is missing" if !raw_data
62
+ if raw_data.bytesize < 2 # should contain at least a version byte and some content
63
+ raise FormatError, "Failed to decode BTC::Address: raw data is too short"
64
+ end
65
+ version = raw_data.bytes.first
66
+ address_class = version_to_class_dictionary[version]
67
+ if !address_class
68
+ raise FormatError, "Failed to decode BTC::Address: unknown version #{version}"
69
+ end
70
+ return address_class.new(string: _string, _raw_data: raw_data)
71
+ end
72
+
73
+ def network
74
+ @network ||= if !@version
75
+ BTC::Network.default
76
+ elsif @version == self.class.mainnet_version
77
+ BTC::Network.mainnet
78
+ else
79
+ BTC::Network.testnet
80
+ end
81
+ end
82
+
83
+ def version
84
+ @version ||= if self.network.mainnet?
85
+ self.class.mainnet_version
86
+ else
87
+ self.class.testnet_version
88
+ end
89
+ end
90
+
91
+ # Returns binary contents of the address (without version byte and checksum).
92
+ def data
93
+ @data
94
+ end
95
+
96
+ # Returns a public version of the address. For public addresses (P2PKH and P2SH) returns self.
97
+ def public_address
98
+ self
99
+ end
100
+
101
+ # Two instances are equal when they have the same contents and versions.
102
+ def ==(other)
103
+ return false if !other
104
+ self.data == other.data && self.version == other.version
105
+ end
106
+ alias_method :eql?, :==
107
+
108
+ # Returns Base58Check representation of an address.
109
+ def to_s
110
+ @base58check_string ||= Base58.base58check_from_data(self.data_for_base58check_encoding)
111
+ end
112
+
113
+ # Whether this address is usable on mainnet.
114
+ def mainnet?
115
+ self.network.mainnet?
116
+ end
117
+
118
+ # Whether this address is usable on testnet.
119
+ def testnet?
120
+ self.network.testnet?
121
+ end
122
+
123
+ # Whether this address is pay-to-public-key-hash (classic address which is a hash of a single public key).
124
+ def p2pkh?
125
+ false
126
+ end
127
+
128
+ # Whether this address is pay-to-script-hash.
129
+ def p2sh?
130
+ false
131
+ end
132
+
133
+ def inspect
134
+ %{#<#{self.class}:#{to_s}>}
135
+ end
136
+
137
+ protected
138
+
139
+ # Overriden in subclasses to provide concrete version
140
+ def self.mainnet_version
141
+ raise Exception, "Override mainnet_version in your subclass"
142
+ end
143
+
144
+ def self.testnet_version
145
+ raise Exception, "Override testnet_version in your subclass"
146
+ end
147
+
148
+ # To override in subclasses
149
+ def data_for_base58check_encoding
150
+ raise Exception, "Override data_for_base58check_encoding in #{self.class} to return complete data to be base58-encoded."
151
+ end
152
+
153
+ private
154
+
155
+ def self.version_to_class_dictionary
156
+ @version_to_class_dictionary ||= [
157
+ PublicKeyAddress,
158
+ ScriptHashAddress,
159
+ WIF,
160
+ AssetID,
161
+ AssetAddress
162
+ ].inject({}) do |dict, cls|
163
+ dict[cls.mainnet_version] = cls
164
+ dict[cls.testnet_version] = cls
165
+ dict
166
+ end
167
+ end
168
+ end
169
+
170
+ class BitcoinPaymentAddress < Address
171
+ end
172
+
173
+ # Base class for P2SH and P2PKH addresses
174
+ class Hash160Address < BitcoinPaymentAddress
175
+
176
+ HASH160_LENGTH = 20
177
+
178
+ # Instantiates addess with the 20-byte hash
179
+ def self.with_data(hash, network: nil)
180
+ raise ArgumentError, "Use #{self}.new(hash: ...) instead"
181
+ end
182
+
183
+ def initialize(string: nil, hash: nil, network: nil, _raw_data: nil)
184
+ if string || _raw_data
185
+ _raw_data ||= Base58.data_from_base58check(string)
186
+ if _raw_data.bytesize != (1 + HASH160_LENGTH)
187
+ raise FormatError, "Raw data should have length #{1 + HASH160_LENGTH}, but it is #{_raw_data.bytesize} instead"
188
+ end
189
+ @base58check_string = string
190
+ @data = _raw_data[1, HASH160_LENGTH]
191
+ @version = _raw_data.bytes.first
192
+ @network = nil
193
+ elsif hash
194
+ if hash.bytesize != HASH160_LENGTH
195
+ raise FormatError, "Data should have length #{HASH160_LENGTH}, but it is #{hash.bytesize} instead"
196
+ end
197
+ @base58check_string = nil
198
+ @data = hash
199
+ @version = nil
200
+ @network = network
201
+ else
202
+ raise ArgumentError, "Either data or string must be provided"
203
+ end
204
+ end
205
+
206
+ def hash
207
+ @data
208
+ end
209
+
210
+ def data_for_base58check_encoding
211
+ BTC::Data.data_from_bytes([self.version]) + @data
212
+ end
213
+ end
214
+
215
+
216
+
217
+ # Standard pulic key (P2PKH) address (e.g. 19FGfswVqxNubJbh1NW8A4t51T9x9RDVWQ)
218
+ class PublicKeyAddress < Hash160Address
219
+
220
+ def self.mainnet_version
221
+ 0
222
+ end
223
+
224
+ def self.testnet_version
225
+ 111
226
+ end
227
+
228
+ def p2pkh?
229
+ true
230
+ end
231
+
232
+ # Instantiates address with a BTC::Key or a binary public key.
233
+ def initialize(string: nil, hash: nil, network: nil, _raw_data: nil, public_key: nil, key: nil)
234
+ if key
235
+ super(hash: BTC.hash160(key.public_key), network: key.network || network)
236
+ elsif public_key
237
+ super(hash: BTC.hash160(public_key), network: network)
238
+ else
239
+ super(string: string, hash: hash, network: network, _raw_data: _raw_data)
240
+ end
241
+ end
242
+
243
+ # Instantiates an address with a public key binary data
244
+ def self.with_public_key(public_key, network: nil)
245
+ raise ArgumentError, "Use #{self}.new(public_key: ...) instead"
246
+ end
247
+
248
+ # Returns BTC::Script with data 'OP_DUP OP_HASH160 <hash> OP_EQUALVERIFY OP_CHECKSIG'
249
+ def script
250
+ raise ArgumentError, "BTC::PublicKeyAddress: invalid data length (must be 20 bytes)" if self.data.bytesize != 20
251
+ BTC::Script.new << OP_DUP << OP_HASH160 << self.data << OP_EQUALVERIFY << OP_CHECKSIG
252
+ end
253
+ end
254
+
255
+
256
+ # P2SH address (e.g. 3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8)
257
+ class ScriptHashAddress < Hash160Address
258
+
259
+ def self.mainnet_version
260
+ 5
261
+ end
262
+
263
+ def self.testnet_version
264
+ 196
265
+ end
266
+
267
+ def p2sh?
268
+ true
269
+ end
270
+
271
+ # Instantiates address with a given redeem script.
272
+ def initialize(string: nil, hash: nil, network: nil, _raw_data: nil, redeem_script: nil)
273
+ if redeem_script
274
+ super(hash: BTC.hash160(redeem_script.data), network: network)
275
+ else
276
+ super(string: string, hash: hash, network: network, _raw_data: _raw_data)
277
+ end
278
+ end
279
+
280
+ # Instantiates address with a given redeem script.
281
+ def self.with_redeem_script(redeem_script, network: nil)
282
+ raise ArgumentError, "Use #{self}.new(redeem_script: ...) instead"
283
+ end
284
+
285
+ # Instantiates address with a given redeem script binary data.
286
+ def self.with_redeem_script_data(redeem_script_data, network: nil)
287
+ raise ArgumentError, "Use #{self}.new(hash: BTC.hash160(redeem_script.data)) instead"
288
+ end
289
+
290
+ # Returns BTC::Script with data 'OP_HASH160 <hash> OP_EQUAL'
291
+ def script
292
+ raise ArgumentError, "BTC::ScriptHashAddress: invalid data length (must be 20 bytes)" if self.data.bytesize != 20
293
+ BTC::Script.new << OP_HASH160 << self.data << OP_EQUAL
294
+ end
295
+ end
296
+ end
@@ -0,0 +1,108 @@
1
+ module BTC
2
+ # Base58 is used for compact human-friendly representation of Bitcoin addresses and private keys.
3
+ # Typically Base58-encoded text also contains a checksum (so-called "Base58Check").
4
+ # Addresses look like 19FGfswVqxNubJbh1NW8A4t51T9x9RDVWQ.
5
+ # Private keys look like 5KQntKuhYWSRXNqp2yhdXzjekYAR7US3MT1715Mbv5CyUKV6hVe.
6
+ #
7
+ # Here is what Satoshi said about Base58:
8
+ # Why base-58 instead of standard base-64 encoding?
9
+ # - Don't want 0OIl characters that look the same in some fonts and
10
+ # could be used to create visually identical looking account numbers.
11
+ # - A string with non-alphanumeric characters is not as easily accepted as an account number.
12
+ # - E-mail usually won't line-break if there's no punctuation to break at.
13
+ # - Double-clicking selects the whole number as one word if it's all alphanumeric.
14
+ #
15
+ module Base58
16
+ extend self
17
+
18
+ ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".freeze
19
+
20
+ # Converts binary string into its Base58 representation.
21
+ # If string is empty returns an empty string.
22
+ # If string is nil raises ArgumentError
23
+ def base58_from_data(data)
24
+ raise ArgumentError, "Data is missing" if !data
25
+ leading_zeroes = 0
26
+ int = 0
27
+ base = 1
28
+ data.bytes.reverse_each do |byte|
29
+ if byte == 0
30
+ leading_zeroes += 1
31
+ else
32
+ leading_zeroes = 0
33
+ int += base*byte
34
+ end
35
+ base *= 256
36
+ end
37
+ return ("1"*leading_zeroes) + base58_from_int(int)
38
+ end
39
+
40
+ # Converts binary string into its Base58 representation.
41
+ # If string is empty returns an empty string.
42
+ # If string is nil raises ArgumentError.
43
+ def data_from_base58(string)
44
+ raise ArgumentError, "String is missing" if !string
45
+ int = int_from_base58(string)
46
+ bytes = []
47
+ while int > 0
48
+ remainder = int % 256
49
+ int = int / 256
50
+ bytes.unshift(remainder)
51
+ end
52
+ data = BTC::Data.data_from_bytes(bytes)
53
+ byte_for_1 = "1".bytes.first
54
+ BTC::Data.ensure_ascii_compatible_encoding(string).bytes.each do |byte|
55
+ break if byte != byte_for_1
56
+ data = "\x00" + data
57
+ end
58
+ data
59
+ end
60
+
61
+ def base58check_from_data(data)
62
+ raise ArgumentError, "Data is missing" if !data
63
+ return base58_from_data(data + BTC.hash256(data)[0,4])
64
+ end
65
+
66
+ def data_from_base58check(string)
67
+ data = data_from_base58(string)
68
+ if data.bytesize < 4
69
+ raise FormatError, "Invalid Base58Check string: too short string #{string.inspect}"
70
+ end
71
+ payload_size = data.bytesize - 4
72
+ payload = data[0, payload_size]
73
+ checksum = data[payload_size, 4]
74
+ if checksum != BTC.hash256(payload)[0,4]
75
+ raise FormatError, "Invalid Base58Check string: checksum invalid in #{string.inspect}"
76
+ end
77
+ payload
78
+ end
79
+
80
+ private
81
+
82
+ def base58_from_int(int)
83
+ raise ArgumentError, "Integer is missing" if !int
84
+ string = ''
85
+ base = ALPHABET.size
86
+ while int > 0
87
+ int, remainder = int.divmod(base)
88
+ string = ALPHABET[remainder] + string
89
+ end
90
+ return string
91
+ end
92
+
93
+ def int_from_base58(string)
94
+ raise ArgumentError, "String is missing" if !string
95
+ int = 0
96
+ base = ALPHABET.size
97
+ string.reverse.each_char.with_index do |char,index|
98
+ char_index = ALPHABET.index(char)
99
+ if !char_index
100
+ raise FormatError, "Invalid Base58 character: #{char.inspect} at index #{index} (full string: #{string.inspect})"
101
+ end
102
+ int += char_index*(base**index)
103
+ end
104
+ int
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,47 @@
1
+ # OpenSSL-compatible BigNumber API.
2
+ module BTC
3
+ class BigNumber
4
+
5
+ # Ruby Integer representation (Fixnum or Bignum)
6
+ attr_reader :integer
7
+
8
+ # Raw little-endian signed integer data
9
+ attr_reader :signed_little_endian
10
+
11
+ # OpenSSL-compatible big-endian signed integer data
12
+ attr_reader :unsigned_big_endian
13
+
14
+ # Initializes with one of the formats:
15
+ # 1) Raw little-endian data extracted from MPI,
16
+ # 2) Native OpenSSL BIGNUM big-endian unsigned big integer,
17
+ # 3) Ruby Integer (Fixnum or Bignum)
18
+ def initialize(signed_little_endian: nil, unsigned_big_endian: nil, integer: nil)
19
+ if signed_little_endian
20
+
21
+ raise "Not Implemented"
22
+
23
+ elsif unsigned_big_endian
24
+
25
+ raise "Not Implemented"
26
+
27
+ elsif integer
28
+ @integer = integer
29
+ else
30
+ raise ArgumentError, "One of the arguments must not be nil"
31
+ end
32
+ end
33
+
34
+ def integer
35
+ @integer
36
+ end
37
+
38
+ def signed_little_endian
39
+ raise "Not Implemented" # reversed mpi with stripped 4-byte length prefix
40
+ end
41
+
42
+ def unsigned_big_endian
43
+ raise "Not Implemented" # bin2bn
44
+ end
45
+
46
+ end # BigNumber
47
+ end # BTC