tapyrus 0.2.7 → 0.2.12
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 +4 -4
- data/.github/workflows/ruby.yml +37 -0
- data/.prettierignore +3 -0
- data/.prettierrc.yaml +3 -0
- data/.ruby-version +1 -1
- data/CODE_OF_CONDUCT.md +7 -7
- data/README.md +14 -17
- data/Rakefile +3 -3
- data/lib/openassets/marker_output.rb +0 -4
- data/lib/openassets/payload.rb +4 -10
- data/lib/openassets.rb +0 -2
- data/lib/schnorr/sign_to_contract.rb +51 -0
- data/lib/schnorr/signature.rb +3 -6
- data/lib/schnorr.rb +14 -9
- data/lib/tapyrus/base58.rb +7 -6
- data/lib/tapyrus/bip175.rb +78 -0
- data/lib/tapyrus/block.rb +1 -2
- data/lib/tapyrus/block_header.rb +15 -9
- data/lib/tapyrus/bloom_filter.rb +5 -3
- data/lib/tapyrus/chain_params.rb +1 -4
- data/lib/tapyrus/chainparams/dev.yml +3 -2
- data/lib/tapyrus/chainparams/prod.yml +3 -2
- data/lib/tapyrus/constants.rb +29 -23
- data/lib/tapyrus/errors.rb +1 -3
- data/lib/tapyrus/ext/ecdsa.rb +4 -4
- data/lib/tapyrus/ext/json_parser.rb +1 -4
- data/lib/tapyrus/ext.rb +1 -1
- data/lib/tapyrus/ext_key.rb +44 -32
- data/lib/tapyrus/key.rb +31 -35
- data/lib/tapyrus/key_path.rb +15 -12
- data/lib/tapyrus/logger.rb +20 -16
- data/lib/tapyrus/merkle_tree.rb +22 -20
- data/lib/tapyrus/message/addr.rb +1 -7
- data/lib/tapyrus/message/base.rb +0 -3
- data/lib/tapyrus/message/block.rb +2 -9
- data/lib/tapyrus/message/block_transaction_request.rb +3 -6
- data/lib/tapyrus/message/block_transactions.rb +2 -6
- data/lib/tapyrus/message/block_txn.rb +0 -4
- data/lib/tapyrus/message/cmpct_block.rb +1 -7
- data/lib/tapyrus/message/error.rb +1 -4
- data/lib/tapyrus/message/fee_filter.rb +1 -4
- data/lib/tapyrus/message/filter_add.rb +0 -4
- data/lib/tapyrus/message/filter_clear.rb +0 -4
- data/lib/tapyrus/message/filter_load.rb +2 -5
- data/lib/tapyrus/message/get_addr.rb +0 -4
- data/lib/tapyrus/message/get_block_txn.rb +0 -4
- data/lib/tapyrus/message/get_blocks.rb +0 -3
- data/lib/tapyrus/message/get_data.rb +1 -4
- data/lib/tapyrus/message/get_headers.rb +1 -3
- data/lib/tapyrus/message/header_and_short_ids.rb +3 -9
- data/lib/tapyrus/message/headers.rb +0 -4
- data/lib/tapyrus/message/headers_parser.rb +3 -8
- data/lib/tapyrus/message/inv.rb +1 -4
- data/lib/tapyrus/message/inventories_parser.rb +2 -7
- data/lib/tapyrus/message/inventory.rb +12 -5
- data/lib/tapyrus/message/mem_pool.rb +0 -4
- data/lib/tapyrus/message/merkle_block.rb +4 -9
- data/lib/tapyrus/message/network_addr.rb +7 -6
- data/lib/tapyrus/message/not_found.rb +0 -3
- data/lib/tapyrus/message/ping.rb +0 -3
- data/lib/tapyrus/message/pong.rb +0 -3
- data/lib/tapyrus/message/prefilled_tx.rb +0 -4
- data/lib/tapyrus/message/reject.rb +0 -3
- data/lib/tapyrus/message/send_cmpct.rb +1 -3
- data/lib/tapyrus/message/send_headers.rb +0 -3
- data/lib/tapyrus/message/tx.rb +0 -4
- data/lib/tapyrus/message/ver_ack.rb +1 -5
- data/lib/tapyrus/message/version.rb +2 -5
- data/lib/tapyrus/message.rb +14 -16
- data/lib/tapyrus/mnemonic.rb +17 -15
- data/lib/tapyrus/network/connection.rb +0 -3
- data/lib/tapyrus/network/message_handler.rb +61 -60
- data/lib/tapyrus/network/peer.rb +13 -12
- data/lib/tapyrus/network/peer_discovery.rb +10 -9
- data/lib/tapyrus/network/pool.rb +12 -12
- data/lib/tapyrus/network.rb +0 -2
- data/lib/tapyrus/node/cli.rb +12 -14
- data/lib/tapyrus/node/configuration.rb +1 -3
- data/lib/tapyrus/node/spv.rb +2 -3
- data/lib/tapyrus/node.rb +1 -1
- data/lib/tapyrus/opcodes.rb +9 -7
- data/lib/tapyrus/out_point.rb +5 -5
- data/lib/tapyrus/rpc/http_server.rb +21 -22
- data/lib/tapyrus/rpc/request_handler.rb +16 -21
- data/lib/tapyrus/rpc/tapyrus_core_client.rb +67 -25
- data/lib/tapyrus/rpc.rb +1 -0
- data/lib/tapyrus/script/color.rb +10 -0
- data/lib/tapyrus/script/multisig.rb +13 -12
- data/lib/tapyrus/script/script.rb +93 -88
- data/lib/tapyrus/script/script_error.rb +1 -4
- data/lib/tapyrus/script/script_interpreter.rb +439 -399
- data/lib/tapyrus/script/tx_checker.rb +20 -10
- data/lib/tapyrus/secp256k1/native.rb +14 -15
- data/lib/tapyrus/secp256k1/rfc6979.rb +7 -4
- data/lib/tapyrus/secp256k1/ruby.rb +10 -12
- data/lib/tapyrus/secp256k1.rb +0 -4
- data/lib/tapyrus/slip39/share.rb +41 -29
- data/lib/tapyrus/slip39/sss.rb +92 -49
- data/lib/tapyrus/slip39.rb +20 -5
- data/lib/tapyrus/store/chain_entry.rb +0 -4
- data/lib/tapyrus/store/db/level_db.rb +5 -9
- data/lib/tapyrus/store/db.rb +0 -2
- data/lib/tapyrus/store/spv_chain.rb +11 -17
- data/lib/tapyrus/store.rb +1 -3
- data/lib/tapyrus/tx.rb +45 -37
- data/lib/tapyrus/tx_builder.rb +160 -0
- data/lib/tapyrus/tx_in.rb +1 -6
- data/lib/tapyrus/tx_out.rb +2 -7
- data/lib/tapyrus/util.rb +7 -9
- data/lib/tapyrus/validation.rb +12 -11
- data/lib/tapyrus/version.rb +1 -1
- data/lib/tapyrus/wallet/account.rb +22 -18
- data/lib/tapyrus/wallet/base.rb +12 -9
- data/lib/tapyrus/wallet/db.rb +6 -9
- data/lib/tapyrus/wallet/master_key.rb +2 -4
- data/lib/tapyrus.rb +7 -22
- data/tapyrusrb.gemspec +13 -14
- metadata +26 -7
- data/.travis.yml +0 -14
data/lib/tapyrus/slip39.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module Tapyrus
|
|
2
2
|
module SLIP39
|
|
3
|
-
|
|
4
3
|
WORDS = File.readlines("#{__dir__}/slip39/wordlist/english.txt").map(&:strip)
|
|
5
4
|
|
|
6
5
|
module_function
|
|
@@ -15,37 +14,53 @@ module Tapyrus
|
|
|
15
14
|
|
|
16
15
|
# The length of the radix in bits.
|
|
17
16
|
RADIX_BITS = 10
|
|
17
|
+
|
|
18
18
|
# The number of words in the wordlist.
|
|
19
|
-
RADIX = 2
|
|
19
|
+
RADIX = 2**RADIX_BITS
|
|
20
|
+
|
|
20
21
|
# The length of the random identifier in bits.
|
|
21
22
|
ID_LENGTH_BITS = 15
|
|
23
|
+
|
|
22
24
|
# The length of the iteration exponent in bits.
|
|
23
25
|
ITERATION_EXP_LENGTH_BITS = 5
|
|
26
|
+
|
|
24
27
|
# The length of the random identifier and iteration exponent in words.
|
|
25
28
|
ID_EXP_LENGTH_WORDS = bits_to_words(ID_LENGTH_BITS + ITERATION_EXP_LENGTH_BITS)
|
|
29
|
+
|
|
26
30
|
# The maximum number of shares that can be created.
|
|
27
31
|
MAX_SHARE_COUNT = 16
|
|
32
|
+
|
|
28
33
|
# The length of the RS1024 checksum in words.
|
|
29
34
|
CHECKSUM_LENGTH_WORDS = 3
|
|
35
|
+
|
|
30
36
|
# The length of the digest of the shared secret in bytes.
|
|
31
37
|
DIGEST_LENGTH_BYTES = 4
|
|
38
|
+
|
|
32
39
|
# The customization string used in the RS1024 checksum and in the PBKDF2 salt.
|
|
33
40
|
CUSTOMIZATION_STRING = 'shamir'.bytes
|
|
41
|
+
|
|
34
42
|
# The length of the mnemonic in words without the share value.
|
|
35
43
|
METADATA_LENGTH_WORDS = ID_EXP_LENGTH_WORDS + 2 + CHECKSUM_LENGTH_WORDS
|
|
44
|
+
|
|
36
45
|
# The minimum allowed entropy of the master secret.
|
|
37
46
|
MIN_STRENGTH_BITS = 128
|
|
47
|
+
|
|
38
48
|
# The minimum allowed length of the mnemonic in words.
|
|
39
49
|
MIN_MNEMONIC_LENGTH_WORDS = METADATA_LENGTH_WORDS + bits_to_words(MIN_STRENGTH_BITS)
|
|
50
|
+
|
|
40
51
|
# The minimum number of iterations to use in PBKDF2.
|
|
41
|
-
BASE_ITERATION_COUNT =
|
|
52
|
+
BASE_ITERATION_COUNT = 10_000
|
|
53
|
+
|
|
42
54
|
# The number of rounds to use in the Feistel cipher.
|
|
43
55
|
ROUND_COUNT = 4
|
|
56
|
+
|
|
44
57
|
# The index of the share containing the shared secret.
|
|
45
58
|
SECRET_INDEX = 255
|
|
59
|
+
|
|
46
60
|
# The index of the share containing the digest of the shared secret.
|
|
47
61
|
DIGEST_INDEX = 254
|
|
48
62
|
|
|
63
|
+
# prettier-ignore
|
|
49
64
|
EXP_TABLE = [
|
|
50
65
|
1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19,
|
|
51
66
|
53, 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34,
|
|
@@ -66,6 +81,7 @@ module Tapyrus
|
|
|
66
81
|
57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246
|
|
67
82
|
]
|
|
68
83
|
|
|
84
|
+
# prettier-ignore
|
|
69
85
|
LOG_TABLE = [
|
|
70
86
|
0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3,
|
|
71
87
|
100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28,
|
|
@@ -88,6 +104,5 @@ module Tapyrus
|
|
|
88
104
|
|
|
89
105
|
autoload :SSS, 'tapyrus/slip39/sss'
|
|
90
106
|
autoload :Share, 'tapyrus/slip39/share'
|
|
91
|
-
|
|
92
107
|
end
|
|
93
|
-
end
|
|
108
|
+
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module Tapyrus
|
|
2
2
|
module Store
|
|
3
|
-
|
|
4
3
|
# wrap a block header object with extra data.
|
|
5
4
|
class ChainEntry
|
|
6
5
|
include Tapyrus::HexConverter
|
|
@@ -60,9 +59,6 @@ module Tapyrus
|
|
|
60
59
|
height_value = height_value.htb.reverse
|
|
61
60
|
Tapyrus.pack_var_int(height_value.bytesize) + height_value + header.to_payload
|
|
62
61
|
end
|
|
63
|
-
|
|
64
62
|
end
|
|
65
|
-
|
|
66
63
|
end
|
|
67
|
-
|
|
68
64
|
end
|
|
@@ -3,9 +3,7 @@ require 'leveldb-native'
|
|
|
3
3
|
module Tapyrus
|
|
4
4
|
module Store
|
|
5
5
|
module DB
|
|
6
|
-
|
|
7
6
|
class LevelDB
|
|
8
|
-
|
|
9
7
|
attr_reader :db
|
|
10
8
|
attr_reader :logger
|
|
11
9
|
|
|
@@ -62,9 +60,11 @@ module Tapyrus
|
|
|
62
60
|
# @param [Tapyrus::Store::ChainEntry]
|
|
63
61
|
def save_entry(entry)
|
|
64
62
|
db.batch do
|
|
65
|
-
db.put(entry.key
|
|
63
|
+
db.put(entry.key, entry.to_payload)
|
|
66
64
|
db.put(height_key(entry.height), entry.block_hash)
|
|
67
|
-
|
|
65
|
+
if entry.header.upgrade_agg_pubkey?
|
|
66
|
+
add_agg_pubkey(entry.height == 0 ? 0 : entry.height + 1, entry.header.x_field)
|
|
67
|
+
end
|
|
68
68
|
connect_entry(entry)
|
|
69
69
|
end
|
|
70
70
|
end
|
|
@@ -134,9 +134,7 @@ module Tapyrus
|
|
|
134
134
|
unless tip_block.block_hash == entry.prev_hash
|
|
135
135
|
raise "entry(#{entry.block_hash}) does not reference current best block hash(#{tip_block.block_hash})"
|
|
136
136
|
end
|
|
137
|
-
unless tip_block.height + 1 == entry.height
|
|
138
|
-
raise "block height is small than current best block."
|
|
139
|
-
end
|
|
137
|
+
raise 'block height is small than current best block.' unless tip_block.height + 1 == entry.height
|
|
140
138
|
end
|
|
141
139
|
db.put(KEY_PREFIX[:best], entry.block_hash)
|
|
142
140
|
db.put(KEY_PREFIX[:next] + entry.prev_hash, entry.block_hash)
|
|
@@ -148,9 +146,7 @@ module Tapyrus
|
|
|
148
146
|
index = db.get(KEY_PREFIX[:latest_agg_pubkey])
|
|
149
147
|
index&.to_i(16)
|
|
150
148
|
end
|
|
151
|
-
|
|
152
149
|
end
|
|
153
|
-
|
|
154
150
|
end
|
|
155
151
|
end
|
|
156
152
|
end
|
data/lib/tapyrus/store/db.rb
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
module Tapyrus
|
|
2
|
-
|
|
3
2
|
module Store
|
|
4
|
-
|
|
5
3
|
KEY_PREFIX = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
entry: 'e', # key: block hash, value: Tapyrus::Store::ChainEntry payload
|
|
5
|
+
height: 'h', # key: block height, value: block hash.
|
|
6
|
+
best: 'B', # value: best block hash.
|
|
7
|
+
next: 'n', # key: block hash, value: A hash of the next block of the specified hash
|
|
8
|
+
agg_pubkey: 'a', # key: index, value: Activated block height | aggregated public key.
|
|
9
|
+
latest_agg_pubkey: 'g' # value: latest agg pubkey index.
|
|
12
10
|
}
|
|
13
11
|
|
|
14
12
|
class SPVChain
|
|
15
|
-
|
|
16
13
|
attr_reader :db
|
|
17
14
|
attr_reader :logger
|
|
18
15
|
|
|
@@ -53,7 +50,9 @@ module Tapyrus
|
|
|
53
50
|
logger.info("append header #{header.block_id}")
|
|
54
51
|
best_block = latest_block
|
|
55
52
|
current_height = best_block.height
|
|
56
|
-
|
|
53
|
+
unless header.valid?(db.agg_pubkey_with_height(current_height + 1))
|
|
54
|
+
raise "this header is invalid. #{header.block_hash}"
|
|
55
|
+
end
|
|
57
56
|
if best_block.block_hash == header.prev_hash
|
|
58
57
|
entry = Tapyrus::Store::ChainEntry.new(header, current_height + 1)
|
|
59
58
|
db.save_entry(entry)
|
|
@@ -107,13 +106,8 @@ module Tapyrus
|
|
|
107
106
|
# if database is empty, put genesis block.
|
|
108
107
|
# @param [Tapyrus::Block] genesis genesis block
|
|
109
108
|
def initialize_block(genesis)
|
|
110
|
-
unless latest_block
|
|
111
|
-
db.save_entry(ChainEntry.new(genesis.header, 0))
|
|
112
|
-
end
|
|
109
|
+
db.save_entry(ChainEntry.new(genesis.header, 0)) unless latest_block
|
|
113
110
|
end
|
|
114
|
-
|
|
115
111
|
end
|
|
116
|
-
|
|
117
112
|
end
|
|
118
|
-
|
|
119
|
-
end
|
|
113
|
+
end
|
data/lib/tapyrus/store.rb
CHANGED
data/lib/tapyrus/tx.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
# https://github.com/lian/bitcoin-ruby/blob/master/COPYING
|
|
3
3
|
|
|
4
4
|
module Tapyrus
|
|
5
|
-
|
|
6
5
|
# Transaction class
|
|
7
6
|
class Tx
|
|
8
7
|
include Tapyrus::HexConverter
|
|
@@ -10,7 +9,7 @@ module Tapyrus
|
|
|
10
9
|
MAX_STANDARD_VERSION = 2
|
|
11
10
|
|
|
12
11
|
# The maximum weight for transactions we're willing to relay/mine
|
|
13
|
-
MAX_STANDARD_TX_WEIGHT =
|
|
12
|
+
MAX_STANDARD_TX_WEIGHT = 400_000
|
|
14
13
|
|
|
15
14
|
attr_accessor :features
|
|
16
15
|
attr_reader :inputs
|
|
@@ -34,14 +33,10 @@ module Tapyrus
|
|
|
34
33
|
|
|
35
34
|
in_count = Tapyrus.unpack_var_int_from_io(buf)
|
|
36
35
|
|
|
37
|
-
in_count.times
|
|
38
|
-
tx.inputs << TxIn.parse_from_payload(buf)
|
|
39
|
-
end
|
|
36
|
+
in_count.times { tx.inputs << TxIn.parse_from_payload(buf) }
|
|
40
37
|
|
|
41
38
|
out_count = Tapyrus.unpack_var_int_from_io(buf)
|
|
42
|
-
out_count.times
|
|
43
|
-
tx.outputs << TxOut.parse_from_payload(buf)
|
|
44
|
-
end
|
|
39
|
+
out_count.times { tx.outputs << TxOut.parse_from_payload(buf) }
|
|
45
40
|
|
|
46
41
|
tx.lock_time = buf.read(4).unpack('V').first
|
|
47
42
|
|
|
@@ -58,7 +53,7 @@ module Tapyrus
|
|
|
58
53
|
|
|
59
54
|
def txid
|
|
60
55
|
buf = [features].pack('V')
|
|
61
|
-
buf << Tapyrus.pack_var_int(inputs.length) << inputs.map{|i|i.to_payload(use_malfix: true)}.join
|
|
56
|
+
buf << Tapyrus.pack_var_int(inputs.length) << inputs.map { |i| i.to_payload(use_malfix: true) }.join
|
|
62
57
|
buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
|
|
63
58
|
buf << [lock_time].pack('V')
|
|
64
59
|
Tapyrus.double_sha256(buf).reverse.bth
|
|
@@ -95,6 +90,7 @@ module Tapyrus
|
|
|
95
90
|
outputs.each do |o|
|
|
96
91
|
return false unless o.script_pubkey.standard?
|
|
97
92
|
data_count += 1 if o.script_pubkey.op_return?
|
|
93
|
+
|
|
98
94
|
# TODO add non P2SH multisig relay(permitbaremultisig)
|
|
99
95
|
return false if o.dust?
|
|
100
96
|
end
|
|
@@ -114,8 +110,14 @@ module Tapyrus
|
|
|
114
110
|
# @param [Integer] amount tapyrus amount locked in input. required for witness input only.
|
|
115
111
|
# @param [Integer] skip_separator_index If output_script is P2WSH and output_script contains any OP_CODESEPARATOR,
|
|
116
112
|
# the script code needs is the witnessScript but removing everything up to and including the last executed OP_CODESEPARATOR before the signature checking opcode being executed.
|
|
117
|
-
def sighash_for_input(
|
|
118
|
-
|
|
113
|
+
def sighash_for_input(
|
|
114
|
+
input_index,
|
|
115
|
+
output_script,
|
|
116
|
+
hash_type: SIGHASH_TYPE[:all],
|
|
117
|
+
sig_version: :base,
|
|
118
|
+
amount: nil,
|
|
119
|
+
skip_separator_index: 0
|
|
120
|
+
)
|
|
119
121
|
raise ArgumentError, 'input_index must be specified.' unless input_index
|
|
120
122
|
raise ArgumentError, 'does not exist input corresponding to input_index.' if input_index >= inputs.size
|
|
121
123
|
raise ArgumentError, 'script_pubkey must be specified.' unless output_script
|
|
@@ -129,16 +131,19 @@ module Tapyrus
|
|
|
129
131
|
# @param [Integer] amount the amount of tapyrus, require for witness program only.
|
|
130
132
|
# @param [Array] flags the flags used when execute script interpreter.
|
|
131
133
|
def verify_input_sig(input_index, script_pubkey, amount: nil, flags: STANDARD_SCRIPT_VERIFY_FLAGS)
|
|
132
|
-
if script_pubkey.p2sh?
|
|
133
|
-
flags << SCRIPT_VERIFY_P2SH
|
|
134
|
-
end
|
|
134
|
+
flags << SCRIPT_VERIFY_P2SH if script_pubkey.p2sh?
|
|
135
135
|
verify_input_sig_for_legacy(input_index, script_pubkey, flags)
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
def to_h
|
|
139
139
|
{
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
txid: txid,
|
|
141
|
+
hash: tx_hash,
|
|
142
|
+
features: features,
|
|
143
|
+
size: size,
|
|
144
|
+
locktime: lock_time,
|
|
145
|
+
vin: inputs.map(&:to_h),
|
|
146
|
+
vout: outputs.map.with_index { |tx_out, index| tx_out.to_h.merge({ n: index }) }
|
|
142
147
|
}
|
|
143
148
|
end
|
|
144
149
|
|
|
@@ -154,43 +159,48 @@ module Tapyrus
|
|
|
154
159
|
|
|
155
160
|
# generate sighash with legacy format
|
|
156
161
|
def sighash_for_legacy(index, script_code, hash_type)
|
|
157
|
-
ins =
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
ins =
|
|
163
|
+
inputs.map.with_index do |i, idx|
|
|
164
|
+
if idx == index
|
|
165
|
+
i.to_payload(script_code.delete_opcode(Tapyrus::Opcodes::OP_CODESEPARATOR))
|
|
166
|
+
else
|
|
167
|
+
case hash_type & 0x1f
|
|
162
168
|
when SIGHASH_TYPE[:none], SIGHASH_TYPE[:single]
|
|
163
169
|
i.to_payload(Tapyrus::Script.new, 0)
|
|
164
170
|
else
|
|
165
171
|
i.to_payload(Tapyrus::Script.new)
|
|
172
|
+
end
|
|
166
173
|
end
|
|
167
174
|
end
|
|
168
|
-
end
|
|
169
175
|
|
|
170
176
|
outs = outputs.map(&:to_payload)
|
|
171
177
|
out_size = Tapyrus.pack_var_int(outputs.size)
|
|
172
178
|
|
|
173
179
|
case hash_type & 0x1f
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
180
|
+
when SIGHASH_TYPE[:none]
|
|
181
|
+
outs = ''
|
|
182
|
+
out_size = Tapyrus.pack_var_int(0)
|
|
183
|
+
when SIGHASH_TYPE[:single]
|
|
184
|
+
return "\x01".ljust(32, "\x00") if index >= outputs.size
|
|
185
|
+
outs =
|
|
186
|
+
outputs[0...(index + 1)].map.with_index { |o, idx| (idx == index) ? o.to_payload : o.to_empty_payload }.join
|
|
187
|
+
out_size = Tapyrus.pack_var_int(index + 1)
|
|
181
188
|
end
|
|
182
189
|
|
|
183
|
-
if hash_type & SIGHASH_TYPE[:anyonecanpay] != 0
|
|
184
|
-
ins = [ins[index]]
|
|
185
|
-
end
|
|
190
|
+
ins = [ins[index]] if hash_type & SIGHASH_TYPE[:anyonecanpay] != 0
|
|
186
191
|
|
|
187
|
-
buf = [
|
|
188
|
-
|
|
192
|
+
buf = [
|
|
193
|
+
[features].pack('V'),
|
|
194
|
+
Tapyrus.pack_var_int(ins.size),
|
|
195
|
+
ins,
|
|
196
|
+
out_size,
|
|
197
|
+
outs,
|
|
198
|
+
[lock_time, hash_type].pack('VV')
|
|
199
|
+
].join
|
|
189
200
|
|
|
190
201
|
Tapyrus.double_sha256(buf)
|
|
191
202
|
end
|
|
192
203
|
|
|
193
|
-
|
|
194
204
|
# verify input signature for legacy tx.
|
|
195
205
|
def verify_input_sig_for_legacy(input_index, script_pubkey, flags)
|
|
196
206
|
script_sig = inputs[input_index].script_sig
|
|
@@ -199,7 +209,5 @@ module Tapyrus
|
|
|
199
209
|
|
|
200
210
|
interpreter.verify_script(script_sig, script_pubkey)
|
|
201
211
|
end
|
|
202
|
-
|
|
203
212
|
end
|
|
204
|
-
|
|
205
213
|
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
module Tapyrus
|
|
2
|
+
#
|
|
3
|
+
# Transaction Builder class.
|
|
4
|
+
#
|
|
5
|
+
# TxBuilder makes it easy to build transactions without having to deal with TxOut/TxIn/Script directly.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
#
|
|
9
|
+
# txb = Tapyrus::TxBuilder.new
|
|
10
|
+
# utxo1 = {
|
|
11
|
+
# script_pubkey: Tapyrus::Script.parse_from_addr('mgCuyNQ1pUbKqL57tJQZX3hhUCaZcuX3RQ'),
|
|
12
|
+
# txid: 'e1fb3255ead43dccd3ae0ac2c4f81b32260ca52749936a739669918bbb895411',
|
|
13
|
+
# index: 0,
|
|
14
|
+
# value: 3_000
|
|
15
|
+
# }
|
|
16
|
+
# color_id = Tapyrus::Color::ColorIdentifier.nft(...)
|
|
17
|
+
# utxo2 = {
|
|
18
|
+
# script_pubkey: Tapyrus::Script.parse_from_addr('mu9QMUcB9UCHbQjZJLAuyysQhM9tmFQbPx'),
|
|
19
|
+
# color_id: color_id,
|
|
20
|
+
# txid: 'e1fb3255ead43dccd3ae0ac2c4f81b32260ca52749936a739669918bbb895411',
|
|
21
|
+
# index: 1,
|
|
22
|
+
# value: 3_000
|
|
23
|
+
# }
|
|
24
|
+
#
|
|
25
|
+
# tx = txb
|
|
26
|
+
# .add_utxo(utxo1)
|
|
27
|
+
# .add_utxo(utxo2)
|
|
28
|
+
# .data("0102030405060a0b0c")
|
|
29
|
+
# .reissuable(utxo1[:script_pubkey],'n4jKJN5UMLsAejL1M5CTzQ8npeWoLBLCAH', 10_000)
|
|
30
|
+
# .pay('n4jKJN5UMLsAejL1M5CTzQ8npeWoLBLCAH', 1_000)
|
|
31
|
+
# .build
|
|
32
|
+
#
|
|
33
|
+
class TxBuilder
|
|
34
|
+
attr_reader :outputs, :utxos
|
|
35
|
+
|
|
36
|
+
def initialize
|
|
37
|
+
@utxos = []
|
|
38
|
+
@incomings = {}
|
|
39
|
+
@outgoings = {}
|
|
40
|
+
@outputs = []
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Add utxo for transaction input
|
|
44
|
+
# @param utxo [Hash] a hash whose fields are `txid`, `index`, `script_pubkey`, `value`, and `color_id` (color_id is optional)
|
|
45
|
+
def add_utxo(utxo)
|
|
46
|
+
@utxos << utxo
|
|
47
|
+
color_id = utxo[:color_id] || Tapyrus::Color::ColorIdentifier.default
|
|
48
|
+
@incomings[color_id] ||= 0
|
|
49
|
+
@incomings[color_id] += utxo[:value]
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Issue reissuable token
|
|
54
|
+
# @param script_pubkey [Tapyrus::Script] the script pubkey in the issue input.
|
|
55
|
+
# @param address [String] p2pkh or p2sh address.
|
|
56
|
+
# @param value [Integer] issued amount.
|
|
57
|
+
def reissuable(script_pubkey, address, value)
|
|
58
|
+
color_id = Tapyrus::Color::ColorIdentifier.reissuable(script_pubkey)
|
|
59
|
+
pay(address, value, color_id)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Issue non reissuable token
|
|
63
|
+
# @param out_point [Tapyrus::OutPoint] the out point at issue input.
|
|
64
|
+
# @param address [String] p2pkh or p2sh address.
|
|
65
|
+
# @param value [Integer] issued amount.
|
|
66
|
+
def non_reissuable(out_point, address, value)
|
|
67
|
+
color_id = Tapyrus::Color::ColorIdentifier.non_reissuable(out_point)
|
|
68
|
+
pay(address, value, color_id)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Issue NFT
|
|
72
|
+
# @param out_point [Tapyrus::OutPoint] the out point at issue input.
|
|
73
|
+
# @param address [String] p2pkh or p2sh address.
|
|
74
|
+
# @param value [Integer] issued amount.
|
|
75
|
+
def nft(out_point, address)
|
|
76
|
+
color_id = Tapyrus::Color::ColorIdentifier.nft(out_point)
|
|
77
|
+
pay(address, 1, color_id)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Create payment output.
|
|
81
|
+
# @param address [String] tapyrus address with Base58 format
|
|
82
|
+
# @param value [Integer] issued or transferred amount
|
|
83
|
+
# @param color_id [Tapyrus::Color::ColorIdentifier] color id
|
|
84
|
+
def pay(address, value, color_id = Tapyrus::Color::ColorIdentifier.default)
|
|
85
|
+
script_pubkey = Tapyrus::Script.parse_from_addr(address)
|
|
86
|
+
|
|
87
|
+
unless color_id.default?
|
|
88
|
+
raise ArgumentError, 'invalid address' if !script_pubkey.p2pkh? && !script_pubkey.p2sh?
|
|
89
|
+
script_pubkey = script_pubkey.add_color(color_id)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
@outgoings[color_id] ||= 0
|
|
93
|
+
@outgoings[color_id] += value
|
|
94
|
+
@outputs << Tapyrus::TxOut.new(script_pubkey: script_pubkey, value: value)
|
|
95
|
+
self
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Create data output
|
|
99
|
+
# @param contents [[String]] array of hex string
|
|
100
|
+
def data(*contents)
|
|
101
|
+
payload = contents.join
|
|
102
|
+
script = Tapyrus::Script.new << Tapyrus::Script::OP_RETURN << payload
|
|
103
|
+
@outputs << Tapyrus::TxOut.new(script_pubkey: script)
|
|
104
|
+
self
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Set transaction fee.
|
|
108
|
+
# @param fee [Integer] transaction fee
|
|
109
|
+
def fee(fee)
|
|
110
|
+
@fee = fee
|
|
111
|
+
self
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Set address for change.
|
|
115
|
+
# If set, #build method add output for change which has the specified address
|
|
116
|
+
# If not set, transaction built by #build method has no output for change.
|
|
117
|
+
# @param address [String] p2pkh or p2sh address.
|
|
118
|
+
def change_address(address)
|
|
119
|
+
script_pubkey = Tapyrus::Script.parse_from_addr(address)
|
|
120
|
+
raise ArgumentError, 'invalid address' if !script_pubkey.p2pkh? && !script_pubkey.p2sh?
|
|
121
|
+
@change_script_pubkey = script_pubkey
|
|
122
|
+
self
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Build transaction
|
|
126
|
+
def build
|
|
127
|
+
tx = Tapyrus::Tx.new
|
|
128
|
+
expand_input(tx)
|
|
129
|
+
@outputs.each { |output| tx.outputs << output }
|
|
130
|
+
add_change(tx) if @change_script_pubkey
|
|
131
|
+
tx
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def add_change(tx)
|
|
137
|
+
@incomings.each do |color_id, in_amount|
|
|
138
|
+
out_amount = @outgoings[color_id] || 0
|
|
139
|
+
change, script_pubkey =
|
|
140
|
+
if color_id.default?
|
|
141
|
+
[in_amount - out_amount - estimated_fee, @change_script_pubkey]
|
|
142
|
+
else
|
|
143
|
+
[in_amount - out_amount, @change_script_pubkey.add_color(color_id)]
|
|
144
|
+
end
|
|
145
|
+
tx.outputs << Tapyrus::TxOut.new(script_pubkey: script_pubkey, value: change) if change > 0
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def expand_input(tx)
|
|
150
|
+
@utxos.each do |utxo|
|
|
151
|
+
tx.inputs << Tapyrus::TxIn.new(out_point: Tapyrus::OutPoint.from_txid(utxo[:txid], utxo[:index]))
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Return transaction fee
|
|
156
|
+
def estimated_fee
|
|
157
|
+
@fee
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
data/lib/tapyrus/tx_in.rb
CHANGED
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
# https://github.com/lian/bitcoin-ruby/blob/master/COPYING
|
|
3
3
|
|
|
4
4
|
module Tapyrus
|
|
5
|
-
|
|
6
5
|
# transaction input
|
|
7
6
|
class TxIn
|
|
8
|
-
|
|
9
7
|
attr_accessor :out_point
|
|
10
8
|
attr_accessor :script_sig
|
|
11
9
|
attr_accessor :sequence
|
|
@@ -58,11 +56,10 @@ module Tapyrus
|
|
|
58
56
|
p
|
|
59
57
|
end
|
|
60
58
|
|
|
61
|
-
|
|
62
59
|
def to_h
|
|
63
60
|
sig = script_sig.to_h
|
|
64
61
|
sig.delete(:type)
|
|
65
|
-
h = {txid: out_point.txid, vout: out_point.index,
|
|
62
|
+
h = { txid: out_point.txid, vout: out_point.index, script_sig: sig }
|
|
66
63
|
h[:sequence] = sequence
|
|
67
64
|
h
|
|
68
65
|
end
|
|
@@ -76,7 +73,5 @@ module Tapyrus
|
|
|
76
73
|
return nil unless out_point
|
|
77
74
|
out_point.tx_hash
|
|
78
75
|
end
|
|
79
|
-
|
|
80
76
|
end
|
|
81
|
-
|
|
82
77
|
end
|
data/lib/tapyrus/tx_out.rb
CHANGED
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
# https://github.com/lian/bitcoin-ruby/blob/master/COPYING
|
|
3
3
|
|
|
4
4
|
module Tapyrus
|
|
5
|
-
|
|
6
5
|
# transaction output
|
|
7
6
|
class TxOut
|
|
8
|
-
|
|
9
7
|
include OpenAssets::MarkerOutput
|
|
10
8
|
include Tapyrus::Color::ColoredOutput
|
|
11
9
|
|
|
@@ -39,7 +37,7 @@ module Tapyrus
|
|
|
39
37
|
end
|
|
40
38
|
|
|
41
39
|
def to_h
|
|
42
|
-
{value: value_to_btc, script_pubkey: script_pubkey.to_h}
|
|
40
|
+
{ value: value_to_btc, script_pubkey: script_pubkey.to_h }
|
|
43
41
|
end
|
|
44
42
|
|
|
45
43
|
def ==(other)
|
|
@@ -65,11 +63,8 @@ module Tapyrus
|
|
|
65
63
|
n_size = size
|
|
66
64
|
n_size += (32 + 4 + 1 + 107 + 4)
|
|
67
65
|
fee = n_size * Tapyrus.chain_params.dust_relay_fee / 1000
|
|
68
|
-
if fee == 0 && n_size != 0
|
|
69
|
-
fee = Tapyrus.chain_params.dust_relay_fee > 0 ? 1 : -1
|
|
70
|
-
end
|
|
66
|
+
fee = Tapyrus.chain_params.dust_relay_fee > 0 ? 1 : -1 if fee == 0 && n_size != 0
|
|
71
67
|
fee
|
|
72
68
|
end
|
|
73
69
|
end
|
|
74
|
-
|
|
75
70
|
end
|
data/lib/tapyrus/util.rb
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
# https://github.com/lian/bitcoin-ruby/blob/master/COPYING
|
|
3
3
|
|
|
4
4
|
module Tapyrus
|
|
5
|
-
|
|
6
5
|
# tapyrus utility.
|
|
7
6
|
# following methods can be used as follows.
|
|
8
7
|
# Tapyrus.pack_var_int(5)
|
|
9
8
|
module Util
|
|
10
|
-
|
|
11
9
|
def pack_var_string(payload)
|
|
12
10
|
pack_var_int(payload.bytesize) + payload
|
|
13
11
|
end
|
|
@@ -18,7 +16,7 @@ module Tapyrus
|
|
|
18
16
|
end
|
|
19
17
|
|
|
20
18
|
def pack_var_int(i)
|
|
21
|
-
if i <
|
|
19
|
+
if i < 0xfd
|
|
22
20
|
[i].pack('C')
|
|
23
21
|
elsif i <= 0xffff
|
|
24
22
|
[0xfd, i].pack('Cv')
|
|
@@ -111,10 +109,14 @@ module Tapyrus
|
|
|
111
109
|
def decode_base58_address(addr)
|
|
112
110
|
hex = Base58.decode(addr)
|
|
113
111
|
if hex.size == 50 && calc_checksum(hex[0...-8]) == hex[-8..-1]
|
|
114
|
-
|
|
112
|
+
unless [Tapyrus.chain_params.address_version, Tapyrus.chain_params.p2sh_version].include?(hex[0..1])
|
|
113
|
+
raise 'Invalid version bytes.'
|
|
114
|
+
end
|
|
115
115
|
[hex[2...-8], hex[0..1]]
|
|
116
116
|
elsif hex.size == 116 && calc_checksum(hex[0...-8]) == hex[-8..-1]
|
|
117
|
-
|
|
117
|
+
unless [Tapyrus.chain_params.cp2pkh_version, Tapyrus.chain_params.cp2sh_version].include?(hex[0..1])
|
|
118
|
+
raise 'Invalid version bytes.'
|
|
119
|
+
end
|
|
118
120
|
[hex[2...-8], hex[0..1]]
|
|
119
121
|
else
|
|
120
122
|
raise 'Invalid address.'
|
|
@@ -142,15 +144,11 @@ module Tapyrus
|
|
|
142
144
|
false
|
|
143
145
|
end
|
|
144
146
|
end
|
|
145
|
-
|
|
146
147
|
end
|
|
147
148
|
|
|
148
149
|
module HexConverter
|
|
149
|
-
|
|
150
150
|
def to_hex
|
|
151
151
|
to_payload.bth
|
|
152
152
|
end
|
|
153
|
-
|
|
154
153
|
end
|
|
155
|
-
|
|
156
154
|
end
|