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,197 @@
1
+ # Bitcoin script consists of opcodes ("operation codes") and raw binary data.
2
+ # Opcodes define how the data is added, removed and modified on the stack.
3
+ module BTC
4
+ module Opcode
5
+ extend self
6
+
7
+ # Returns a human-readable name for a given opcode byte value.
8
+ def name_for_opcode(opcode)
9
+ OPCODE_VALUE_TO_NAME[opcode] || "OP_UNKNOWN"
10
+ end
11
+
12
+ # Returns an opcode byte value for a given name ("OP_...").
13
+ def opcode_for_name(name)
14
+ OPCODE_NAME_TO_VALUE[name] || OP_INVALIDOPCODE
15
+ end
16
+
17
+ # Returns OP_1NEGATE, OP_0 .. OP_16 for ints from -1 to 16.
18
+ # Returns OP_INVALIDOPCODE for other ints.
19
+ def opcode_for_small_integer(small_int)
20
+ raise ArgumentError, "small_int must not be nil" if !small_int
21
+ return OP_0 if small_int == 0
22
+ return OP_1NEGATE if small_int == -1
23
+ if small_int >= 1 && small_int <= 16
24
+ return OP_1 + (small_int - 1)
25
+ end
26
+ return OP_INVALIDOPCODE
27
+ end
28
+
29
+ # Converts opcode OP_<N> or OP_1NEGATE to an integer value.
30
+ # If incorrect opcode is given, nil is returned.
31
+ def small_integer_from_opcode(opcode)
32
+ return 0 if opcode == "".b # OP_O
33
+ return 0 if opcode == OP_0
34
+ return -1 if opcode == OP_1NEGATE
35
+ if opcode >= OP_1 && opcode <= OP_16
36
+ return opcode - (OP_1 - 1)
37
+ end
38
+ return nil
39
+ end
40
+ end
41
+
42
+ # 1. Operators pushing data on stack.
43
+
44
+ # Push 1 byte 0x00 on the stack
45
+ OP_FALSE = 0x00
46
+ OP_0 = 0x00
47
+
48
+ # Any opcode with value < PUSHDATA1 is a length of the string to be pushed on the stack.
49
+ # So opcode 0x01 is followed by 1 byte of data, 0x09 by 9 bytes and so on up to 0x4b (75 bytes)
50
+
51
+ # PUSHDATA<N> opcode is followed by N-byte length of the string that follows.
52
+ OP_PUSHDATA1 = 0x4c # followed by a 1-byte length of the string to push (allows pushing 0..255 bytes).
53
+ OP_PUSHDATA2 = 0x4d # followed by a 2-byte length of the string to push (allows pushing 0..65535 bytes).
54
+ OP_PUSHDATA4 = 0x4e # followed by a 4-byte length of the string to push (allows pushing 0..4294967295 bytes).
55
+ OP_1NEGATE = 0x4f # pushes -1 number on the stack
56
+ OP_RESERVED = 0x50 # Not assigned. If executed, transaction is invalid.
57
+
58
+ # OP_<N> pushes number <N> on the stack
59
+ OP_TRUE = 0x51
60
+ OP_1 = 0x51
61
+ OP_2 = 0x52
62
+ OP_3 = 0x53
63
+ OP_4 = 0x54
64
+ OP_5 = 0x55
65
+ OP_6 = 0x56
66
+ OP_7 = 0x57
67
+ OP_8 = 0x58
68
+ OP_9 = 0x59
69
+ OP_10 = 0x5a
70
+ OP_11 = 0x5b
71
+ OP_12 = 0x5c
72
+ OP_13 = 0x5d
73
+ OP_14 = 0x5e
74
+ OP_15 = 0x5f
75
+ OP_16 = 0x60
76
+
77
+ # 2. Control flow operators
78
+
79
+ OP_NOP = 0x61 # Does nothing
80
+ OP_VER = 0x62 # Not assigned. If executed, transaction is invalid.
81
+
82
+ # BitcoinQT executes all operators from OP_IF to OP_ENDIF even inside "non-executed" branch (to keep track of nesting).
83
+ # Since OP_VERIF and OP_VERNOTIF are not assigned, even inside a non-executed branch they will fall in "default:" switch case
84
+ # and cause the script to fail. Some other ops like OP_VER can be present inside non-executed branch because they'll be skipped.
85
+ OP_IF = 0x63 # If the top stack value is not 0, the statements are executed. The top stack value is removed.
86
+ OP_NOTIF = 0x64 # If the top stack value is 0, the statements are executed. The top stack value is removed.
87
+ OP_VERIF = 0x65 # Not assigned. Script is invalid with that opcode (even if inside non-executed branch).
88
+ OP_VERNOTIF = 0x66 # Not assigned. Script is invalid with that opcode (even if inside non-executed branch).
89
+ OP_ELSE = 0x67 # Executes code if the previous OP_IF or OP_NOTIF was not executed.
90
+ OP_ENDIF = 0x68 # Finishes if/else block
91
+
92
+ OP_VERIFY = 0x69 # Removes item from the stack if it's not 0x00 or 0x80 (negative zero). Otherwise, marks script as invalid.
93
+ OP_RETURN = 0x6a # Marks transaction as invalid.
94
+
95
+ # Stack ops
96
+ OP_TOALTSTACK = 0x6b # Moves item from the stack to altstack
97
+ OP_FROMALTSTACK = 0x6c # Moves item from the altstack to stack
98
+ OP_2DROP = 0x6d
99
+ OP_2DUP = 0x6e
100
+ OP_3DUP = 0x6f
101
+ OP_2OVER = 0x70
102
+ OP_2ROT = 0x71
103
+ OP_2SWAP = 0x72
104
+ OP_IFDUP = 0x73
105
+ OP_DEPTH = 0x74
106
+ OP_DROP = 0x75
107
+ OP_DUP = 0x76
108
+ OP_NIP = 0x77
109
+ OP_OVER = 0x78
110
+ OP_PICK = 0x79
111
+ OP_ROLL = 0x7a
112
+ OP_ROT = 0x7b
113
+ OP_SWAP = 0x7c
114
+ OP_TUCK = 0x7d
115
+
116
+ # Splice ops
117
+ OP_CAT = 0x7e # Disabled opcode. If executed, transaction is invalid.
118
+ OP_SUBSTR = 0x7f # Disabled opcode. If executed, transaction is invalid.
119
+ OP_LEFT = 0x80 # Disabled opcode. If executed, transaction is invalid.
120
+ OP_RIGHT = 0x81 # Disabled opcode. If executed, transaction is invalid.
121
+ OP_SIZE = 0x82
122
+
123
+ # Bit logic
124
+ OP_INVERT = 0x83 # Disabled opcode. If executed, transaction is invalid.
125
+ OP_AND = 0x84 # Disabled opcode. If executed, transaction is invalid.
126
+ OP_OR = 0x85 # Disabled opcode. If executed, transaction is invalid.
127
+ OP_XOR = 0x86 # Disabled opcode. If executed, transaction is invalid.
128
+
129
+ OP_EQUAL = 0x87 # Last two items are removed from the stack and compared. Result (true or false) is pushed to the stack.
130
+ OP_EQUALVERIFY = 0x88 # Same as OP_EQUAL, but removes the result from the stack if it's true or marks script as invalid.
131
+
132
+ OP_RESERVED1 = 0x89 # Disabled opcode. If executed, transaction is invalid.
133
+ OP_RESERVED2 = 0x8a # Disabled opcode. If executed, transaction is invalid.
134
+
135
+ # Numeric
136
+ OP_1ADD = 0x8b # adds 1 to last item, pops it from stack and pushes result.
137
+ OP_1SUB = 0x8c # substracts 1 to last item, pops it from stack and pushes result.
138
+ OP_2MUL = 0x8d # Disabled opcode. If executed, transaction is invalid.
139
+ OP_2DIV = 0x8e # Disabled opcode. If executed, transaction is invalid.
140
+ OP_NEGATE = 0x8f # negates the number, pops it from stack and pushes result.
141
+ OP_ABS = 0x90 # replaces number with its absolute value
142
+ OP_NOT = 0x91 # replaces number with True if it's zero, False otherwise.
143
+ OP_0NOTEQUAL = 0x92 # replaces number with True if it's not zero, False otherwise.
144
+
145
+ OP_ADD = 0x93 # (x y -- x+y)
146
+ OP_SUB = 0x94 # (x y -- x-y)
147
+ OP_MUL = 0x95 # Disabled opcode. If executed, transaction is invalid.
148
+ OP_DIV = 0x96 # Disabled opcode. If executed, transaction is invalid.
149
+ OP_MOD = 0x97 # Disabled opcode. If executed, transaction is invalid.
150
+ OP_LSHIFT = 0x98 # Disabled opcode. If executed, transaction is invalid.
151
+ OP_RSHIFT = 0x99 # Disabled opcode. If executed, transaction is invalid.
152
+
153
+ OP_BOOLAND = 0x9a
154
+ OP_BOOLOR = 0x9b
155
+ OP_NUMEQUAL = 0x9c
156
+ OP_NUMEQUALVERIFY = 0x9d
157
+ OP_NUMNOTEQUAL = 0x9e
158
+ OP_LESSTHAN = 0x9f
159
+ OP_GREATERTHAN = 0xa0
160
+ OP_LESSTHANOREQUAL = 0xa1
161
+ OP_GREATERTHANOREQUAL = 0xa2
162
+ OP_MIN = 0xa3
163
+ OP_MAX = 0xa4
164
+
165
+ OP_WITHIN = 0xa5
166
+
167
+ # Crypto
168
+ OP_RIPEMD160 = 0xa6
169
+ OP_SHA1 = 0xa7
170
+ OP_SHA256 = 0xa8
171
+ OP_HASH160 = 0xa9
172
+ OP_HASH256 = 0xaa
173
+ OP_CODESEPARATOR = 0xab # This opcode is rarely used because it's useless, but we need to support it anyway.
174
+ OP_CHECKSIG = 0xac
175
+ OP_CHECKSIGVERIFY = 0xad
176
+ OP_CHECKMULTISIG = 0xae
177
+ OP_CHECKMULTISIGVERIFY = 0xaf
178
+
179
+ # Expansion
180
+ OP_NOP1 = 0xb0
181
+ OP_NOP2 = 0xb1
182
+ OP_NOP3 = 0xb2
183
+ OP_NOP4 = 0xb3
184
+ OP_NOP5 = 0xb4
185
+ OP_NOP6 = 0xb5
186
+ OP_NOP7 = 0xb6
187
+ OP_NOP8 = 0xb7
188
+ OP_NOP9 = 0xb8
189
+ OP_NOP10 = 0xb9
190
+
191
+ OP_INVALIDOPCODE = 0xff
192
+
193
+ OPCODE_NAME_TO_VALUE = Hash[*constants.grep(/^OP_/).map{|c| [c.to_s, const_get(c)] }.flatten]
194
+ OPCODE_VALUE_TO_NAME = Hash[*constants.grep(/^OP_/).map{|c| [const_get(c), c.to_s] }.flatten]
195
+
196
+ end
197
+
@@ -0,0 +1,35 @@
1
+ module BTC
2
+ # Implementation of OpenAssets protocol.
3
+ # https://github.com/OpenAssets/open-assets-protocol/blob/master/specification.mediawiki
4
+ class Asset
5
+
6
+ # BTC::AssetID instance identifying this asset.
7
+ attr_accessor :asset_id
8
+
9
+ # Binary 160-bit identifier of the asset (same as `asset_id.hash`)
10
+ attr_accessor :identifier_binary
11
+
12
+ # Optional attributes, set either by initializer when possible, or externally.
13
+ attr_accessor :script
14
+ attr_accessor :output
15
+
16
+ # Initializes assets with one of the following:
17
+ # * script - a BTC::Script instance.
18
+ # * output - a BTC::TransactionOutput instance that issues the asset.
19
+ # * asset_id - a Base58 identifier of the asset or BTC::AssetID instance.
20
+ def initialize(asset_id: nil, script: nil, output: nil, network: nil)
21
+ if script || output
22
+ script ||= output.script
23
+ @output = output
24
+ @script = script
25
+ @identifier_binary = BTC::Data.hash160(script.data)
26
+ @asset_id = AssetID.new(hash: @identifier_binary, network: network)
27
+ elsif asset_id
28
+ @asset_id = Address.parse(asset_id)
29
+ @identifier_binary = @asset_id.hash
30
+ else
31
+ raise ArgumentError, "Either asset_id, script or output must be specified."
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,49 @@
1
+ module BTC
2
+ # Represents an Asset Address, where the assets can be sent.
3
+ class AssetAddress < BTC::Address
4
+ NAMESPACE = 0x13
5
+
6
+ def self.mainnet_version
7
+ NAMESPACE
8
+ end
9
+
10
+ def self.testnet_version
11
+ NAMESPACE
12
+ end
13
+
14
+ attr_reader :bitcoin_address
15
+
16
+ def initialize(string: nil, bitcoin_address: nil, _raw_data: nil)
17
+ if string
18
+ _raw_data ||= Base58.data_from_base58check(string)
19
+ raise FormatError, "Too short AssetAddress" if _raw_data.bytesize < 2
20
+ raise FormatError, "Invalid namespace for AssetAddress" if _raw_data.bytes[0] != NAMESPACE
21
+ @bitcoin_address = Address.parse_raw_data(_raw_data[1..-1])
22
+ @base58check_string = string
23
+ elsif bitcoin_address
24
+ @base58check_string = nil
25
+ @bitcoin_address = BTC::Address.parse(bitcoin_address)
26
+ else
27
+ raise ArgumentError, "Either data or string must be provided"
28
+ end
29
+ # If someone accidentally supplied AssetAddress as a bitcoin address,
30
+ # simply unwrap the bitcoin address from it.
31
+ while @bitcoin_address.is_a?(self.class)
32
+ @bitcoin_address = @bitcoin_address.bitcoin_address
33
+ @base58check_string = nil
34
+ end
35
+ end
36
+
37
+ def network
38
+ @bitcoin_address.network
39
+ end
40
+
41
+ def script
42
+ @bitcoin_address.script
43
+ end
44
+
45
+ def data_for_base58check_encoding
46
+ BTC::Data.data_from_bytes([NAMESPACE]) + @bitcoin_address.data_for_base58check_encoding
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,75 @@
1
+ # Implementation of OpenAssets Asset Definition Format
2
+ # https://github.com/OpenAssets/open-assets-protocol/blob/master/asset-definition-protocol.mediawiki
3
+ require 'json' # in Ruby 2 stdlib
4
+ module BTC
5
+ class AssetDefinition
6
+
7
+ DEFAULT_VERSION = "1.0".freeze
8
+
9
+ attr_accessor :asset_ids
10
+ attr_accessor :name_short
11
+ attr_accessor :name
12
+ attr_accessor :contract_url
13
+ attr_accessor :issuer
14
+ attr_accessor :description
15
+ attr_accessor :description_mime
16
+ attr_accessor :type
17
+ attr_accessor :divisibility
18
+ attr_accessor :link_to_website
19
+ attr_accessor :icon_url
20
+ attr_accessor :image_url
21
+ attr_accessor :version
22
+
23
+ def initialize(dictionary: nil,
24
+ json: nil,
25
+ asset_ids: [],
26
+ name: nil,
27
+ name_short: nil,
28
+ issuer: nil,
29
+ type: nil,
30
+ divisibility: 0)
31
+
32
+ @json = json
33
+ dictionary ||= (json ? JSON.parse(json) : nil)
34
+ if dictionary
35
+ @asset_ids = dictionary["asset_ids"].map{|aid| AssetID.parse(aid) }
36
+ else
37
+ @asset_ids = asset_ids
38
+ @name = name
39
+ @name_short = name_short
40
+ @issuer = issuer
41
+ @type = type
42
+ @divisibility = divisibility
43
+ end
44
+ @version ||= DEFAULT_VERSION
45
+ @asset_ids ||= []
46
+ @divisibility ||= 0
47
+ end
48
+
49
+ def dictionary
50
+ dict = {}
51
+ dict["asset_ids"] = self.asset_ids.map{|aid| aid.to_s}
52
+ dict["name_short"] = self.name_short || ""
53
+ dict["name"] = self.name || ""
54
+ dict["contract_url"] = self.contract_url if self.contract_url
55
+ dict["issuer"] = self.issuer if self.issuer
56
+ dict["description"] = self.description if self.description
57
+ dict["description_mime"] = self.description_mime if self.description_mime
58
+ dict["type"] = self.type if self.type
59
+ dict["divisibility"] = self.divisibility if self.divisibility
60
+ dict["link_to_website"] = self.link_to_website if self.link_to_website
61
+ dict["icon_url"] = self.icon_url if self.icon_url
62
+ dict["image_url"] = self.image_url if self.image_url
63
+ dict["version"] = self.version if self.version
64
+ dict
65
+ end
66
+
67
+ def json
68
+ @json ||= JSON.generate(dictionary)
69
+ end
70
+
71
+ def sha256
72
+ BTC::Data.hex_from_data(BTC.sha256(json))
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,24 @@
1
+ module BTC
2
+ # Represents an Asset ID.
3
+ class AssetID < BTC::Hash160Address
4
+
5
+ def self.mainnet_version
6
+ 23 # "A" prefix
7
+ end
8
+
9
+ def self.testnet_version
10
+ 115
11
+ end
12
+
13
+ # Instantiates AssetID with output, output script or raw hash.
14
+ # To compute an Asset ID for the Asset Definition file, use `trim_script_prefix: true`.
15
+ def initialize(string: nil, hash: nil, network: nil, _raw_data: nil, script: nil, trim_script_prefix: false)
16
+ if script
17
+ script = script.without_dropped_prefix_data if trim_script_prefix
18
+ super(hash: BTC.hash160(script.data), network: network)
19
+ else
20
+ super(string: string, hash: hash, network: network, _raw_data: _raw_data)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,94 @@
1
+ module BTC
2
+ class AssetMarker
3
+
4
+ MAGIC = "\x4f\x41".freeze
5
+ VERSION1 = "\x01\x00".freeze
6
+ PREFIX_V1 = MAGIC + VERSION1
7
+
8
+ attr_accessor :quantities
9
+ attr_accessor :metadata
10
+
11
+ attr_reader :data
12
+ attr_reader :output
13
+ attr_reader :script
14
+
15
+ def initialize(output: nil, script: nil, data: nil, quantities: nil, metadata: nil)
16
+ if output || script
17
+ script ||= output.script
18
+ raise ArgumentError, "Script is not an OP_RETURN script" if !script.op_return_script?
19
+ data = script.op_return_data
20
+ raise ArgumentError, "No pushdata found in script" if !data || data.bytesize == 0
21
+ @output = output
22
+ @script = script
23
+ end
24
+ if data
25
+ data = BTC::Data.ensure_binary_encoding(data)
26
+
27
+ raise ArgumentError, "Data must be at least 6 bytes long (4 bytes prefix and 2 bytes for varints)" if data.bytesize < 6
28
+ raise ArgumentError, "Prefix is invalid. Expected #{BTC::Data.hex_from_data(PREFIX_V1)}" if data[0, PREFIX_V1.bytesize] != PREFIX_V1
29
+
30
+ offset = PREFIX_V1.bytesize
31
+ count, bytesread = WireFormat.read_varint(data: data, offset: offset)
32
+ raise ArgumentError, "Cannot read Asset Quantity Count varint" if !count
33
+ offset = bytesread
34
+ @quantities = []
35
+ count.times do
36
+ qty, bytesread = WireFormat.read_uleb128(data: data, offset: offset)
37
+ raise ArgumentError, "Cannot read Asset Quantity LEB128 unsigned integer" if !qty
38
+ raise ArgumentError, "Open Assets limit LEB128 encoding for quantities to 9 bytes" if (bytesread - offset) > 9
39
+ @quantities << qty
40
+ offset = bytesread
41
+ end
42
+ metadata, bytesread = WireFormat.read_string(data: data, offset: offset)
43
+ raise ArgumentError, "Cannot read Asset Metadata" if !metadata
44
+ @metadata = metadata
45
+ @data = data[0, bytesread]
46
+ else
47
+ # Initialize with optional attributes
48
+ @quantities = quantities
49
+ @metadata = metadata
50
+ end
51
+ end
52
+
53
+ def quantities
54
+ @quantities ||= []
55
+ end
56
+
57
+ def quantities=(qs)
58
+ @quantities = qs
59
+ @data = nil
60
+ @script = nil
61
+ @output = nil
62
+ end
63
+
64
+ def metadata
65
+ @metadata ||= "".b
66
+ end
67
+
68
+ def metadata=(md)
69
+ @metadata = md
70
+ @data = nil
71
+ @script = nil
72
+ @output = nil
73
+ end
74
+
75
+ def data
76
+ @data ||= begin
77
+ PREFIX_V1 +
78
+ WireFormat.encode_varint(self.quantities.size) +
79
+ self.quantities.inject("".b){|buf, qty| buf << WireFormat.encode_uleb128(qty) } +
80
+ WireFormat.encode_string(self.metadata)
81
+ end
82
+ end
83
+
84
+ def script
85
+ @script ||= begin
86
+ Script.new << OP_RETURN << self.data
87
+ end
88
+ end
89
+
90
+ def output
91
+ @output ||= TransactionOutput.new(value: 0, script: self.script)
92
+ end
93
+ end
94
+ end