monacoin-openassets-ruby 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE +23 -0
- data/README.md +420 -0
- data/Rakefile +5 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/monacoin-openassets +66 -0
- data/lib/monacoin-openassets.rb +23 -0
- data/lib/openassets/api.rb +423 -0
- data/lib/openassets/cache.rb +8 -0
- data/lib/openassets/cache/output_cache.rb +43 -0
- data/lib/openassets/cache/sqlite_base.rb +26 -0
- data/lib/openassets/cache/ssl_certificate_cache.rb +44 -0
- data/lib/openassets/cache/transaction_cache.rb +33 -0
- data/lib/openassets/error.rb +5 -0
- data/lib/openassets/medhod_filter.rb +55 -0
- data/lib/openassets/protocol.rb +10 -0
- data/lib/openassets/protocol/asset_definition.rb +118 -0
- data/lib/openassets/protocol/asset_definition_loader.rb +43 -0
- data/lib/openassets/protocol/http_asset_definition_loader.rb +27 -0
- data/lib/openassets/protocol/marker_output.rb +128 -0
- data/lib/openassets/protocol/output_type.rb +27 -0
- data/lib/openassets/protocol/transaction_output.rb +157 -0
- data/lib/openassets/provider.rb +7 -0
- data/lib/openassets/provider/api_error.rb +9 -0
- data/lib/openassets/provider/bitcoin_core_provider.rb +123 -0
- data/lib/openassets/provider/block_chain_provider_base.rb +22 -0
- data/lib/openassets/send_asset_param.rb +17 -0
- data/lib/openassets/send_bitcoin_param.rb +13 -0
- data/lib/openassets/transaction.rb +12 -0
- data/lib/openassets/transaction/dust_output_error.rb +10 -0
- data/lib/openassets/transaction/insufficient_asset_quantity_error.rb +7 -0
- data/lib/openassets/transaction/insufficient_funds_error.rb +10 -0
- data/lib/openassets/transaction/out_point.rb +22 -0
- data/lib/openassets/transaction/spendable_output.rb +38 -0
- data/lib/openassets/transaction/transaction_build_error.rb +9 -0
- data/lib/openassets/transaction/transaction_builder.rb +319 -0
- data/lib/openassets/transaction/transfer_parameters.rb +41 -0
- data/lib/openassets/util.rb +173 -0
- data/lib/openassets/version.rb +3 -0
- data/openassets-ruby.gemspec +38 -0
- 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,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
|