monacoin-openassets-ruby 0.1.3
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.
- 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
|