monacoin-openassets-ruby 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +11 -0
  7. data/CODE_OF_CONDUCT.md +13 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE +23 -0
  10. data/README.md +420 -0
  11. data/Rakefile +5 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +7 -0
  14. data/exe/monacoin-openassets +66 -0
  15. data/lib/monacoin-openassets.rb +23 -0
  16. data/lib/openassets/api.rb +423 -0
  17. data/lib/openassets/cache.rb +8 -0
  18. data/lib/openassets/cache/output_cache.rb +43 -0
  19. data/lib/openassets/cache/sqlite_base.rb +26 -0
  20. data/lib/openassets/cache/ssl_certificate_cache.rb +44 -0
  21. data/lib/openassets/cache/transaction_cache.rb +33 -0
  22. data/lib/openassets/error.rb +5 -0
  23. data/lib/openassets/medhod_filter.rb +55 -0
  24. data/lib/openassets/protocol.rb +10 -0
  25. data/lib/openassets/protocol/asset_definition.rb +118 -0
  26. data/lib/openassets/protocol/asset_definition_loader.rb +43 -0
  27. data/lib/openassets/protocol/http_asset_definition_loader.rb +27 -0
  28. data/lib/openassets/protocol/marker_output.rb +128 -0
  29. data/lib/openassets/protocol/output_type.rb +27 -0
  30. data/lib/openassets/protocol/transaction_output.rb +157 -0
  31. data/lib/openassets/provider.rb +7 -0
  32. data/lib/openassets/provider/api_error.rb +9 -0
  33. data/lib/openassets/provider/bitcoin_core_provider.rb +123 -0
  34. data/lib/openassets/provider/block_chain_provider_base.rb +22 -0
  35. data/lib/openassets/send_asset_param.rb +17 -0
  36. data/lib/openassets/send_bitcoin_param.rb +13 -0
  37. data/lib/openassets/transaction.rb +12 -0
  38. data/lib/openassets/transaction/dust_output_error.rb +10 -0
  39. data/lib/openassets/transaction/insufficient_asset_quantity_error.rb +7 -0
  40. data/lib/openassets/transaction/insufficient_funds_error.rb +10 -0
  41. data/lib/openassets/transaction/out_point.rb +22 -0
  42. data/lib/openassets/transaction/spendable_output.rb +38 -0
  43. data/lib/openassets/transaction/transaction_build_error.rb +9 -0
  44. data/lib/openassets/transaction/transaction_builder.rb +319 -0
  45. data/lib/openassets/transaction/transfer_parameters.rb +41 -0
  46. data/lib/openassets/util.rb +173 -0
  47. data/lib/openassets/version.rb +3 -0
  48. data/openassets-ruby.gemspec +38 -0
  49. metadata +246 -0
@@ -0,0 +1,8 @@
1
+ module OpenAssets
2
+ module Cache
3
+ autoload :SQLiteBase, 'openassets/cache/sqlite_base'
4
+ autoload :TransactionCache, 'openassets/cache/transaction_cache'
5
+ autoload :SSLCertificateCache, 'openassets/cache/ssl_certificate_cache'
6
+ autoload :OutputCache, 'openassets/cache/output_cache'
7
+ end
8
+ end
@@ -0,0 +1,43 @@
1
+ module OpenAssets
2
+ module Cache
3
+
4
+ # An object that can be used for caching coloring transaction output in a Sqlite database.
5
+ class OutputCache < SQLiteBase
6
+
7
+ def setup
8
+ db.execute <<-SQL
9
+ CREATE TABLE IF NOT EXISTS Output(
10
+ TransactionHash BLOB,
11
+ OutputIndex INT,
12
+ Value BigInt,
13
+ Script BLOB,
14
+ AssetId BLOB,
15
+ AssetQuantity INT,
16
+ OutputType INT,
17
+ Metadata BLOB,
18
+ PRIMARY KEY (TransactionHash, OutputIndex))
19
+ SQL
20
+ end
21
+
22
+ # Get a cached transaction output
23
+ # @param[String] txid The transaction id.
24
+ # @param[Integer] index The index of the output in the transaction.
25
+ # @return[OpenAssets::Protocol::TransactionOutput] The output for the txid and index provided if it is found in the cache, or nil otherwise.
26
+ def get(txid, index)
27
+ rows = db.execute('SELECT Value,Script,AssetId,AssetQuantity,OutputType,Metadata FROM Output WHERE TransactionHash = ? AND OutputIndex = ?', [txid, index])
28
+ return nil if rows.empty?
29
+ script = Bitcoin::Script.from_string(rows[0][1])
30
+ OpenAssets::Protocol::TransactionOutput.new(rows[0][0], script, rows[0][2], rows[0][3], rows[0][4], rows[0][5])
31
+ end
32
+
33
+ # Put a transaction output
34
+ # @param[String] txid The transaction id.
35
+ # @param[Integer] index The index of the output in the transaction.
36
+ # @param[OpenAssets::Protocol::TransactionOutput] output The output to save.
37
+ def put(txid, index, output)
38
+ db.execute('INSERT INTO Output (TransactionHash, OutputIndex, Value,Script,AssetId,AssetQuantity,OutputType,Metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
39
+ [txid, index, output.value, output.script.to_string, output.asset_id, output.asset_quantity, output.output_type, output.metadata])
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,26 @@
1
+ require 'sqlite3'
2
+
3
+ module OpenAssets
4
+ module Cache
5
+
6
+ # The base class of SQLite3 cache implementation.
7
+ class SQLiteBase
8
+
9
+ attr_reader :db
10
+
11
+ # Initializes the connection to the database, and creates the table if needed.
12
+ # @param[String] path The path to the database file. Use ':memory:' for an in-memory database.
13
+ def initialize(path)
14
+ @db = SQLite3::Database.new path
15
+ setup
16
+ end
17
+
18
+ # Setup table ddl, implements by subclass.
19
+ def setup
20
+ raise StandardError.new('need setup method implementation.')
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,44 @@
1
+ module OpenAssets
2
+ module Cache
3
+ class SSLCertificateCache < SQLiteBase
4
+
5
+ def initialize
6
+ path = OpenAssets.configuration ? OpenAssets.configuration[:cache] : ':memory:'
7
+ super(path)
8
+ end
9
+
10
+ def setup
11
+ db.execute <<-SQL
12
+ CREATE TABLE IF NOT EXISTS SslCertificate(
13
+ Url TEXT,
14
+ Subject TEXT,
15
+ ExpireDate TEXT,
16
+ PRIMARY KEY (Url))
17
+ SQL
18
+ end
19
+
20
+ # Return the subject value which defined by ssl certificate.
21
+ # @param[String] url The URL of asset definition file.
22
+ # @return[String] The subject value. If not found, return nil.
23
+ def get(url)
24
+ rows = db.execute('SELECT Subject,ExpireDate FROM SslCertificate WHERE Url = ?', [url])
25
+ return nil if rows.empty?
26
+ if rows[0][1].to_i < Time.now.to_i
27
+ db.execute('DELETE FROM SslCertificate where Url = ?', [url])
28
+ nil
29
+ else
30
+ rows[0][0]
31
+ end
32
+ end
33
+
34
+ # Saves a serialized transaction in cache.
35
+ # @param[String] url The URL of asset definition file.
36
+ # @param[String] subject The SSL Certificate subject value.
37
+ # @param[Time] expire_date The expire date of SSL Certificate.
38
+ def put(url, subject, expire_date)
39
+ db.execute('INSERT INTO SslCertificate (Url, Subject, ExpireDate) VALUES (?, ?, ?)', [url, subject, expire_date.to_i])
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,33 @@
1
+ module OpenAssets
2
+ module Cache
3
+
4
+ # An object that can be used for caching serialized transaction in a Sqlite database.
5
+ class TransactionCache < SQLiteBase
6
+
7
+ def setup
8
+ db.execute <<-SQL
9
+ CREATE TABLE IF NOT EXISTS Tx(
10
+ TransactionHash BLOB,
11
+ SerializedTx BLOB,
12
+ PRIMARY KEY (TransactionHash))
13
+ SQL
14
+ end
15
+
16
+ # Return the serialized transaction.
17
+ # @param[String] txid The transaction id.
18
+ # @return[String] The serialized transaction. If not found transaction, return nil.
19
+ def get(txid)
20
+ rows = db.execute('SELECT SerializedTx FROM Tx WHERE TransactionHash = ?', [txid])
21
+ rows.empty? ? nil : rows[0][0]
22
+ end
23
+
24
+ # Saves a serialized transaction in cache.
25
+ # @param[String] txid A transaction id.
26
+ # @param[String] serialized_tx A a hex-encoded serialized transaction.
27
+ def put(txid, serialized_tx)
28
+ db.execute('INSERT INTO Tx (TransactionHash, SerializedTx) VALUES (?, ?)', [txid, serialized_tx])
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ module OpenAssets
2
+ class Error < StandardError
3
+
4
+ end
5
+ end
@@ -0,0 +1,55 @@
1
+ module OpenAssets
2
+ module MethodFilter
3
+
4
+ module Filters
5
+
6
+ def before_filter(method, options = {})
7
+ @before_filters ||= {}
8
+ @before_filters[method] = options
9
+ end
10
+
11
+ def filtered_method?(method, options)
12
+ if options.has_key? :include
13
+ return true if options[:include].include? method.intern
14
+ false
15
+ elsif options.has_key? :exclude
16
+ return false if options[:exclude].include? method.intern
17
+ true
18
+ else
19
+ true
20
+ end
21
+ end
22
+
23
+ private
24
+ def method_added(name)
25
+ @before_filters ||= {}
26
+ @modified_methods ||= []
27
+ return if @modified_methods.include?(name)
28
+ return if @before_filters.include?(name)
29
+ return if /with_filters$/ =~ name.to_s
30
+ return if /without_filters$/ =~ name.to_s
31
+ @modified_methods << name
32
+
33
+ name = name.to_s
34
+ alias_method( "#{name}_without_filters", name)
35
+ before_filters = @before_filters
36
+
37
+ define_method("#{name}_with_filters") do |*args|
38
+ before_filters.each do |filter_name, options|
39
+ method(filter_name).call if self.class.filtered_method?(name, options)
40
+ end
41
+ result = method("#{name}_without_filters").call(*args)
42
+ result
43
+ end
44
+
45
+ alias_method(name, "#{name}_with_filters")
46
+ @modified_methods.delete_if { |x| name == x }
47
+ end
48
+ end
49
+
50
+ def self.included(receiver)
51
+ receiver.extend(Filters)
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,10 @@
1
+ module OpenAssets
2
+ module Protocol
3
+ autoload :MarkerOutput, 'openassets/protocol/marker_output'
4
+ autoload :TransactionOutput, 'openassets/protocol/transaction_output'
5
+ autoload :OutputType, 'openassets/protocol/output_type'
6
+ autoload :AssetDefinitionLoader, 'openassets/protocol/asset_definition_loader'
7
+ autoload :HttpAssetDefinitionLoader, 'openassets/protocol/http_asset_definition_loader'
8
+ autoload :AssetDefinition, 'openassets/protocol/asset_definition'
9
+ end
10
+ end
@@ -0,0 +1,118 @@
1
+ require 'rest-client'
2
+ require 'httpclient'
3
+ module OpenAssets
4
+ module Protocol
5
+
6
+ # The Definition of Open Asset
7
+ class AssetDefinition
8
+ include OpenAssets::MethodFilter
9
+
10
+ before_filter :clear_poa_cache, {:include => [:issuer=, :asset_definition_url=, :link_to_website=]}
11
+
12
+ attr_accessor :asset_definition_url
13
+
14
+ attr_accessor :asset_ids
15
+ attr_accessor :name_short
16
+ attr_accessor :name
17
+ attr_accessor :contract_url
18
+ attr_accessor :issuer
19
+ attr_accessor :description
20
+ attr_accessor :description_mime
21
+ attr_accessor :type
22
+ attr_accessor :divisibility
23
+ attr_accessor :link_to_website
24
+ attr_accessor :icon_url
25
+ attr_accessor :image_url
26
+ attr_accessor :version
27
+ attr_accessor :proof_of_authenticity
28
+
29
+ def initialize
30
+ @asset_ids = []
31
+ @version = '1.0'
32
+ @divisibility = 0
33
+ end
34
+
35
+ # Parse the JSON obtained from the json String, and create a AssetDefinition object.
36
+ # @param[String]
37
+ def self.parse_json(json)
38
+ parsed_json = JSON.parse(json)
39
+ definition = new
40
+ definition.asset_ids = parsed_json['asset_ids']
41
+ definition.name_short = parsed_json['name_short']
42
+ definition.name = parsed_json['name']
43
+ definition.contract_url = parsed_json['contract_url']
44
+ definition.issuer = parsed_json['issuer']
45
+ definition.description = parsed_json['description']
46
+ definition.description_mime = parsed_json['description_mime']
47
+ definition.type = parsed_json['type']
48
+ definition.divisibility = parsed_json['divisibility']
49
+ definition.link_to_website = parsed_json['link_to_website']
50
+ definition.icon_url = parsed_json['icon_url']
51
+ definition.image_url = parsed_json['image_url']
52
+ definition.version = parsed_json['version']
53
+ definition
54
+ end
55
+
56
+ def include_asset_id?(asset_id)
57
+ return false if asset_ids.nil? || asset_ids.empty?
58
+ asset_ids.include?(asset_id)
59
+ end
60
+
61
+ # Convert Asset Definition to json format.
62
+ def to_json
63
+ h = to_hash
64
+ h.delete('proof_of_authenticity')
65
+ h.to_json
66
+ end
67
+
68
+ def to_hash
69
+ instance_variables.inject({}) do |result, var|
70
+ key = var.to_s
71
+ key.slice!(0) if key.start_with?('@')
72
+ result.update(key => instance_variable_get(var))
73
+ end
74
+ end
75
+
76
+ # Check Proof of authenticity.
77
+ # SSL certificate subject matches issuer.
78
+ def proof_of_authenticity
79
+ @proof_of_authenticity ||= calc_proof_of_authenticity
80
+ end
81
+
82
+ private
83
+ def calc_proof_of_authenticity
84
+ result = false
85
+ if !asset_definition_url.nil? && link_to_website
86
+ subject = ssl_certificate_subject
87
+ return true if !subject.nil? && subject == issuer
88
+ end
89
+ result
90
+ end
91
+
92
+ def clear_poa_cache
93
+ @proof_of_authenticity = nil
94
+ end
95
+
96
+ def ssl_certificate_subject
97
+ cache = OpenAssets::Cache::SSLCertificateCache.new
98
+ subject = cache.get(asset_definition_url)
99
+ if subject.nil?
100
+ client = HTTPClient.new
101
+ client.redirect_uri_callback = ->(uri, res) {res.header['location'][0]}
102
+ response = client.get(asset_definition_url, :follow_redirect => true)
103
+ cert = response.peer_cert
104
+ unless cert.nil?
105
+ subjects = cert.subject.to_a
106
+ o = subjects.find{|x|x[0] == 'O'}
107
+ if !o.nil? && o.length > 2
108
+ subject = o[1]
109
+ cache.put(asset_definition_url, subject, cert.not_after)
110
+ end
111
+ end
112
+ end
113
+ subject
114
+ end
115
+ end
116
+
117
+ end
118
+ end
@@ -0,0 +1,43 @@
1
+ module OpenAssets
2
+ module Protocol
3
+
4
+ class AssetDefinitionLoader
5
+ extend Bitcoin::Util
6
+ extend OpenAssets::Util
7
+
8
+ attr_reader :loader
9
+
10
+ def initialize(url)
11
+ if url.start_with?('http://') || url.start_with?('https://')
12
+ @loader = HttpAssetDefinitionLoader.new(url)
13
+ end
14
+ end
15
+
16
+ # load Asset Definition File
17
+ # @return[OpenAssets::Protocol::AssetDefinition] loaded asset definition object
18
+ def load_definition
19
+ @loader.load if @loader
20
+ end
21
+
22
+ # create redeem script of asset definition file using p2sh
23
+ # @param[String] url The asset definition url.
24
+ # @param[String] to The open asset address to send the asset to.
25
+ # @return[Bitcoin::Script] redeem script.
26
+ def self.create_pointer_redeem_script(url, to)
27
+ asset_def = "u=#{url}".bytes.map{|b|b.to_s(16)}.join
28
+ btc_addr = oa_address_to_address(to)
29
+ script = Bitcoin::Script.from_string("#{asset_def}")
30
+ Bitcoin::Script.new(script.append_opcode(Bitcoin::Script::OP_DROP).to_payload + Bitcoin::Script.to_address_script(btc_addr))
31
+ end
32
+
33
+ # create ps2s script which specify asset definition pointer
34
+ # @param[String] url The asset definition url.
35
+ # @param[String] to The open asset address to send the asset to.
36
+ # @return[Bitcoin::Script] p2sh script.
37
+ def self.create_pointer_p2sh(url, to)
38
+ redeem_script = create_pointer_redeem_script(url, to)
39
+ Bitcoin::Script.new(Bitcoin::Script.to_p2sh_script(hash160(redeem_script.to_payload.bth)))
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ module OpenAssets
2
+ module Protocol
3
+
4
+ # Asset Definition loader for http or https uri scheme
5
+ class HttpAssetDefinitionLoader
6
+
7
+ attr_reader :url
8
+
9
+ def initialize(url)
10
+ @url = url
11
+ end
12
+
13
+ # load asset definition
14
+ def load
15
+ begin
16
+ definition = AssetDefinition.parse_json(RestClient::Request.execute(:method => :get, :url => url, :timeout => 10, :open_timeout => 10))
17
+ definition.asset_definition_url = url
18
+ definition
19
+ rescue => e
20
+ puts e
21
+ nil
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,128 @@
1
+ require 'rest-client'
2
+ module OpenAssets
3
+ module Protocol
4
+
5
+ class MarkerOutput
6
+ include OpenAssets::Util
7
+ extend OpenAssets::Util
8
+
9
+ MAX_ASSET_QUANTITY = 2 ** 63 -1
10
+
11
+ # A tag indicating thath this transaction is an Open Assets transaction.
12
+ OAP_MARKER = "4f41"
13
+ # The major revision number of the Open Assets Protocol.(1=0x0100)
14
+ VERSION = "0100"
15
+
16
+ attr_accessor :asset_quantities
17
+ attr_accessor :metadata
18
+
19
+ # @param [Array] asset_quantities The asset quantity array
20
+ # @param [String] metadata The metadata in the marker output.
21
+ def initialize(asset_quantities, metadata = '')
22
+ @asset_quantities = asset_quantities
23
+ @metadata = metadata
24
+ end
25
+
26
+ # Serialize the marker output into a Open Assets Payload buffer.
27
+ # @return [String] The serialized payload.
28
+ def to_payload
29
+ payload = [OAP_MARKER, VERSION]
30
+ asset_quantity_count = Bitcoin::Protocol.pack_var_int(@asset_quantities.length).unpack("H*")
31
+ payload << sort_count(asset_quantity_count[0])
32
+ @asset_quantities.map{|q|payload << encode_leb128(q)}
33
+ @metadata ||= ''
34
+ metadata_length = Bitcoin::Protocol.pack_var_int(@metadata.length).unpack("H*")
35
+ payload << sort_count(metadata_length[0])
36
+ payload << @metadata.bytes.map{|b| sprintf("%02x", b)}.join
37
+ payload.join
38
+ end
39
+
40
+ # Deserialize the marker output payload.
41
+ # @param [String] payload The Open Assets Payload.
42
+ # @return [OpenAssets::Protocol::MarkerOutput] The marker output object.
43
+ def self.deserialize_payload(payload)
44
+ return nil unless valid?(payload)
45
+ payload = payload[8..-1] # exclude OAP_MARKER,VERSION
46
+ asset_quantity, payload = parse_asset_qty(payload)
47
+ list = to_bytes(payload).map{|x|(x.to_i(16)>=128 ? x : x+"|")}.join.split('|')[0..(asset_quantity - 1)].join
48
+ asset_quantities = decode_leb128(list)
49
+ meta = to_bytes(payload[list.size..-1])
50
+ metadata = meta.empty? ? '' : meta[1..-1].map{|x|x.to_i(16).chr}.join
51
+ new(asset_quantities, metadata)
52
+ end
53
+
54
+ # Parses an output and returns the payload if the output matches the right pattern for a marker output,
55
+ # @param [Bitcoin::Script] output_script: The output script to be parsed.
56
+ # @return [String] The byte string of the marker output payload if the output fits the pattern, nil otherwise.
57
+ def self.parse_script(output_script)
58
+ data = Bitcoin::Script.new(output_script).get_op_return_data
59
+ return data if valid?(data)
60
+ end
61
+
62
+ # Creates an output script containing an OP_RETURN and a PUSHDATA from payload.
63
+ # @return [Bitcoin::Script] the output script.
64
+ def build_script
65
+ Bitcoin::Script.from_string("OP_RETURN #{to_payload}")
66
+ end
67
+
68
+ private
69
+ def self.parse_asset_qty(payload)
70
+ bytes = to_bytes(payload)
71
+ case bytes[0]
72
+ when "fd" then
73
+ [(bytes[1]+bytes[2]).to_i(16), payload[6..-1]]
74
+ when "fe" then
75
+ [(bytes[1]+bytes[2]+bytes[3]+bytes[4]).to_i(16),payload[10..-1]]
76
+ else
77
+ [bytes[0].to_i(16),payload[2..-1]]
78
+ end
79
+ end
80
+
81
+ def sort_count(count)
82
+ bytes = to_bytes(count)
83
+ case bytes[0]
84
+ when "fd" then
85
+ tmp = count[2..3]
86
+ count[2..3] = count[4..5]
87
+ count[4..5] = tmp
88
+ count
89
+ when "fe" then
90
+ tmp_1 = count[2..3]
91
+ tmp_2 = count[4..5]
92
+ count[2..3] = count[8..9]
93
+ count[8..9] = tmp_1
94
+ count[4..5] = count[6..7]
95
+ count[6..7] = tmp_2
96
+ count
97
+ else
98
+ count
99
+ end
100
+ end
101
+
102
+ # validate marker output format
103
+ # @param[String] data marker output data with start with 4f41
104
+ def self.valid?(data)
105
+ return false if data.nil?
106
+ # check open assets marker
107
+ return false unless data.start_with?(OAP_MARKER + VERSION)
108
+ # check asset quantity
109
+ offset = [OAP_MARKER + VERSION].pack('H*').length
110
+ count, offset = read_var_integer(data, offset)
111
+ return false unless count
112
+ # check metadata
113
+ count.times do
114
+ quantity, length = read_leb128(data, offset)
115
+ return false if quantity.nil? || (length - offset) > 9
116
+ offset = length
117
+ end
118
+ # check metadata
119
+ length, offset = read_var_integer(data, offset)
120
+ return false unless length
121
+ return false if [data].pack('H*').bytes.length < length + offset
122
+ true
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+ end