tapyrus 0.1.0
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 +10 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +12 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +100 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/tapyrusrb-cli +5 -0
- data/exe/tapyrusrbd +41 -0
- data/lib/openassets/marker_output.rb +20 -0
- data/lib/openassets/payload.rb +54 -0
- data/lib/openassets/util.rb +28 -0
- data/lib/openassets.rb +9 -0
- data/lib/tapyrus/base58.rb +38 -0
- data/lib/tapyrus/block.rb +77 -0
- data/lib/tapyrus/block_header.rb +88 -0
- data/lib/tapyrus/bloom_filter.rb +78 -0
- data/lib/tapyrus/chain_params.rb +90 -0
- data/lib/tapyrus/chainparams/mainnet.yml +41 -0
- data/lib/tapyrus/chainparams/regtest.yml +38 -0
- data/lib/tapyrus/chainparams/testnet.yml +41 -0
- data/lib/tapyrus/constants.rb +195 -0
- data/lib/tapyrus/descriptor.rb +147 -0
- data/lib/tapyrus/ext_key.rb +337 -0
- data/lib/tapyrus/key.rb +296 -0
- data/lib/tapyrus/key_path.rb +26 -0
- data/lib/tapyrus/logger.rb +42 -0
- data/lib/tapyrus/merkle_tree.rb +149 -0
- data/lib/tapyrus/message/addr.rb +35 -0
- data/lib/tapyrus/message/base.rb +28 -0
- data/lib/tapyrus/message/block.rb +46 -0
- data/lib/tapyrus/message/block_transaction_request.rb +45 -0
- data/lib/tapyrus/message/block_transactions.rb +31 -0
- data/lib/tapyrus/message/block_txn.rb +27 -0
- data/lib/tapyrus/message/cmpct_block.rb +42 -0
- data/lib/tapyrus/message/error.rb +10 -0
- data/lib/tapyrus/message/fee_filter.rb +27 -0
- data/lib/tapyrus/message/filter_add.rb +28 -0
- data/lib/tapyrus/message/filter_clear.rb +17 -0
- data/lib/tapyrus/message/filter_load.rb +39 -0
- data/lib/tapyrus/message/get_addr.rb +17 -0
- data/lib/tapyrus/message/get_block_txn.rb +27 -0
- data/lib/tapyrus/message/get_blocks.rb +29 -0
- data/lib/tapyrus/message/get_data.rb +21 -0
- data/lib/tapyrus/message/get_headers.rb +28 -0
- data/lib/tapyrus/message/header_and_short_ids.rb +57 -0
- data/lib/tapyrus/message/headers.rb +35 -0
- data/lib/tapyrus/message/headers_parser.rb +24 -0
- data/lib/tapyrus/message/inv.rb +21 -0
- data/lib/tapyrus/message/inventories_parser.rb +23 -0
- data/lib/tapyrus/message/inventory.rb +51 -0
- data/lib/tapyrus/message/mem_pool.rb +17 -0
- data/lib/tapyrus/message/merkle_block.rb +42 -0
- data/lib/tapyrus/message/network_addr.rb +63 -0
- data/lib/tapyrus/message/not_found.rb +21 -0
- data/lib/tapyrus/message/ping.rb +30 -0
- data/lib/tapyrus/message/pong.rb +26 -0
- data/lib/tapyrus/message/prefilled_tx.rb +29 -0
- data/lib/tapyrus/message/reject.rb +46 -0
- data/lib/tapyrus/message/send_cmpct.rb +43 -0
- data/lib/tapyrus/message/send_headers.rb +16 -0
- data/lib/tapyrus/message/tx.rb +30 -0
- data/lib/tapyrus/message/ver_ack.rb +17 -0
- data/lib/tapyrus/message/version.rb +69 -0
- data/lib/tapyrus/message.rb +70 -0
- data/lib/tapyrus/mnemonic/wordlist/chinese_simplified.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/chinese_traditional.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/english.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/french.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/italian.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/japanese.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/spanish.txt +2048 -0
- data/lib/tapyrus/mnemonic.rb +77 -0
- data/lib/tapyrus/network/connection.rb +73 -0
- data/lib/tapyrus/network/message_handler.rb +241 -0
- data/lib/tapyrus/network/peer.rb +223 -0
- data/lib/tapyrus/network/peer_discovery.rb +42 -0
- data/lib/tapyrus/network/pool.rb +135 -0
- data/lib/tapyrus/network.rb +13 -0
- data/lib/tapyrus/node/cli.rb +112 -0
- data/lib/tapyrus/node/configuration.rb +38 -0
- data/lib/tapyrus/node/spv.rb +79 -0
- data/lib/tapyrus/node.rb +7 -0
- data/lib/tapyrus/opcodes.rb +178 -0
- data/lib/tapyrus/out_point.rb +44 -0
- data/lib/tapyrus/rpc/http_server.rb +65 -0
- data/lib/tapyrus/rpc/request_handler.rb +150 -0
- data/lib/tapyrus/rpc/tapyrus_core_client.rb +72 -0
- data/lib/tapyrus/rpc.rb +7 -0
- data/lib/tapyrus/script/multisig.rb +92 -0
- data/lib/tapyrus/script/script.rb +551 -0
- data/lib/tapyrus/script/script_error.rb +111 -0
- data/lib/tapyrus/script/script_interpreter.rb +668 -0
- data/lib/tapyrus/script/tx_checker.rb +81 -0
- data/lib/tapyrus/script_witness.rb +38 -0
- data/lib/tapyrus/secp256k1/native.rb +174 -0
- data/lib/tapyrus/secp256k1/ruby.rb +123 -0
- data/lib/tapyrus/secp256k1.rb +12 -0
- data/lib/tapyrus/slip39/share.rb +122 -0
- data/lib/tapyrus/slip39/sss.rb +245 -0
- data/lib/tapyrus/slip39/wordlist/english.txt +1024 -0
- data/lib/tapyrus/slip39.rb +93 -0
- data/lib/tapyrus/store/chain_entry.rb +67 -0
- data/lib/tapyrus/store/db/level_db.rb +98 -0
- data/lib/tapyrus/store/db.rb +9 -0
- data/lib/tapyrus/store/spv_chain.rb +101 -0
- data/lib/tapyrus/store.rb +9 -0
- data/lib/tapyrus/tx.rb +347 -0
- data/lib/tapyrus/tx_in.rb +89 -0
- data/lib/tapyrus/tx_out.rb +74 -0
- data/lib/tapyrus/util.rb +133 -0
- data/lib/tapyrus/validation.rb +115 -0
- data/lib/tapyrus/version.rb +3 -0
- data/lib/tapyrus/wallet/account.rb +151 -0
- data/lib/tapyrus/wallet/base.rb +162 -0
- data/lib/tapyrus/wallet/db.rb +81 -0
- data/lib/tapyrus/wallet/master_key.rb +110 -0
- data/lib/tapyrus/wallet.rb +8 -0
- data/lib/tapyrus.rb +219 -0
- data/tapyrusrb.conf.sample +0 -0
- data/tapyrusrb.gemspec +47 -0
- metadata +451 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require "murmurhash3"
|
|
2
|
+
module Tapyrus
|
|
3
|
+
class BloomFilter
|
|
4
|
+
LN2_SQUARED = 0.4804530139182014246671025263266649717305529515945455 # log(2) ** 2
|
|
5
|
+
LN2 = 0.6931471805599453094172321214581765680755001343602552 # log(2)
|
|
6
|
+
|
|
7
|
+
MAX_BLOOM_FILTER_SIZE = 36_000 # bytes
|
|
8
|
+
MAX_HASH_FUNCS = 50
|
|
9
|
+
|
|
10
|
+
attr_reader :filter, :hash_funcs, :tweak
|
|
11
|
+
|
|
12
|
+
def initialize(filter, hash_funcs, tweak)
|
|
13
|
+
@filter = filter
|
|
14
|
+
@hash_funcs = hash_funcs
|
|
15
|
+
@tweak = tweak
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Create a new bloom filter.
|
|
19
|
+
# @param [Integer] elements_length the number of elements
|
|
20
|
+
# @param [Float] fp_rate the false positive rate chosen by the client
|
|
21
|
+
# @param [Integer] tweak A random value to add to the seed value in the hash function used by the bloom filter
|
|
22
|
+
def self.create_filter(elements_length, fp_rate, tweak = 0)
|
|
23
|
+
# The size S of the filter in bytes is given by (-1 / pow(log(2), 2) * N * log(P)) / 8
|
|
24
|
+
len = [[(-elements_length * Math.log(fp_rate) / (LN2_SQUARED * 8)).to_i, MAX_BLOOM_FILTER_SIZE].min, 1].max
|
|
25
|
+
filter = Array.new(len, 0)
|
|
26
|
+
# The number of hash functions required is given by S * 8 / N * log(2)
|
|
27
|
+
hash_funcs = [[(filter.size * 8 * LN2 / elements_length).to_i, MAX_HASH_FUNCS].min, 1].max
|
|
28
|
+
BloomFilter.new(filter, hash_funcs, tweak)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param [String] data The data element to add to the current filter.
|
|
32
|
+
def add(data)
|
|
33
|
+
return if full?
|
|
34
|
+
hash_funcs.times do |i|
|
|
35
|
+
hash = to_hash(data, i)
|
|
36
|
+
set_bit(hash)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns true if the given data matches the filter
|
|
41
|
+
# @param [String] data The data to check the current filter
|
|
42
|
+
# @return [Boolean] true if the given data matches the filter
|
|
43
|
+
def contains?(data)
|
|
44
|
+
return true if full?
|
|
45
|
+
hash_funcs.times do |i|
|
|
46
|
+
hash = to_hash(data, i)
|
|
47
|
+
return false unless check_bit(hash)
|
|
48
|
+
end
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def clear
|
|
53
|
+
filter.fill(0)
|
|
54
|
+
@full = false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_a
|
|
58
|
+
filter
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
def to_hash(data, i)
|
|
63
|
+
MurmurHash3::V32.str_hash(data, (i * 0xfba4c795 + tweak) & 0xffffffff) % (filter.length * 8)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def set_bit(data)
|
|
67
|
+
filter[data >> 3] |= (1 << (7 & data))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def check_bit(data)
|
|
71
|
+
filter[data >> 3] & (1 << (7 & data)) != 0
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def full?
|
|
75
|
+
@full |= filter.all? {|byte| byte == 0xff}
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module Tapyrus
|
|
4
|
+
|
|
5
|
+
# Network parameter class
|
|
6
|
+
class ChainParams
|
|
7
|
+
|
|
8
|
+
attr_reader :network
|
|
9
|
+
attr_reader :magic_head
|
|
10
|
+
attr_reader :message_magic
|
|
11
|
+
attr_reader :address_version
|
|
12
|
+
attr_reader :p2sh_version
|
|
13
|
+
attr_reader :bech32_hrp
|
|
14
|
+
attr_reader :privkey_version
|
|
15
|
+
attr_reader :extended_privkey_version
|
|
16
|
+
attr_reader :extended_pubkey_version
|
|
17
|
+
attr_reader :bip49_pubkey_p2wpkh_p2sh_version
|
|
18
|
+
attr_reader :bip49_privkey_p2wpkh_p2sh_version
|
|
19
|
+
attr_reader :bip49_pubkey_p2wsh_p2sh_version
|
|
20
|
+
attr_reader :bip49_privkey_p2wsh_p2sh_version
|
|
21
|
+
attr_reader :bip84_pubkey_p2wpkh_version
|
|
22
|
+
attr_reader :bip84_privkey_p2wpkh_version
|
|
23
|
+
attr_reader :bip84_pubkey_p2wsh_version
|
|
24
|
+
attr_reader :bip84_privkey_p2wsh_version
|
|
25
|
+
attr_reader :default_port
|
|
26
|
+
attr_reader :protocol_version
|
|
27
|
+
attr_reader :retarget_interval
|
|
28
|
+
attr_reader :retarget_time
|
|
29
|
+
attr_reader :target_spacing
|
|
30
|
+
attr_reader :max_money
|
|
31
|
+
attr_reader :bip34_height
|
|
32
|
+
attr_reader :proof_of_work_limit
|
|
33
|
+
attr_reader :dns_seeds
|
|
34
|
+
attr_reader :genesis
|
|
35
|
+
attr_reader :bip44_coin_type
|
|
36
|
+
|
|
37
|
+
attr_accessor :dust_relay_fee
|
|
38
|
+
|
|
39
|
+
# fork coin id.
|
|
40
|
+
attr_accessor :fork_id
|
|
41
|
+
|
|
42
|
+
# mainnet genesis
|
|
43
|
+
def self.mainnet
|
|
44
|
+
init('mainnet')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# testnet genesis
|
|
48
|
+
def self.testnet
|
|
49
|
+
init('testnet')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# regtest genesis
|
|
53
|
+
def self.regtest
|
|
54
|
+
init('regtest')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def mainnet?
|
|
58
|
+
network == 'mainnet'
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def testnet?
|
|
62
|
+
network == 'testnet'
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def regtest?
|
|
66
|
+
network == 'regtest'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def genesis_block
|
|
70
|
+
header = Tapyrus::BlockHeader.new(
|
|
71
|
+
genesis['version'], genesis['prev_hash'].rhex, genesis['merkle_root'].rhex,
|
|
72
|
+
genesis['time'], genesis['bits'], genesis['nonce'])
|
|
73
|
+
Tapyrus::Block.new(header)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# whether fork coin.
|
|
77
|
+
def fork_chain?
|
|
78
|
+
!fork_id.nil?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.init(name)
|
|
82
|
+
i = YAML.load(File.open("#{__dir__}/chainparams/#{name}.yml"))
|
|
83
|
+
i.dust_relay_fee ||= Tapyrus::DUST_RELAY_TX_FEE
|
|
84
|
+
i
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private_class_method :init
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
--- !ruby/object:Tapyrus::ChainParams
|
|
2
|
+
network: "mainnet"
|
|
3
|
+
magic_head: "f9beb4d9"
|
|
4
|
+
message_magic: "Bitcoin Signed Message:\n"
|
|
5
|
+
address_version: "00"
|
|
6
|
+
p2sh_version: "05"
|
|
7
|
+
bech32_hrp: 'bc'
|
|
8
|
+
privkey_version: "80"
|
|
9
|
+
extended_privkey_version: "0488ade4"
|
|
10
|
+
extended_pubkey_version: "0488b21e"
|
|
11
|
+
bip49_pubkey_p2wpkh_p2sh_version: "049d7cb2"
|
|
12
|
+
bip49_pubkey_p2wsh_p2sh_version: "0295b43f"
|
|
13
|
+
bip49_privkey_p2wpkh_p2sh_version: "049d7878"
|
|
14
|
+
bip49_privkey_p2wsh_p2sh_version: "0295b005"
|
|
15
|
+
bip84_pubkey_p2wpkh_version: "04b24746"
|
|
16
|
+
bip84_pubkey_p2wsh_version: "02aa7ed3"
|
|
17
|
+
bip84_privkey_p2wpkh_version: "04b2430c"
|
|
18
|
+
bip84_privkey_p2wsh_version: "02aa7a99"
|
|
19
|
+
default_port: 8333
|
|
20
|
+
protocol_version: 70013
|
|
21
|
+
retarget_interval: 2016
|
|
22
|
+
retarget_time: 1209600 # 2 weeks
|
|
23
|
+
target_spacing: 600 # block interval
|
|
24
|
+
max_money: 21000000
|
|
25
|
+
bip34_height: 227931
|
|
26
|
+
proof_of_work_limit: 0x1d00ffff
|
|
27
|
+
dns_seeds:
|
|
28
|
+
- "seed.bitcoin.sipa.be"
|
|
29
|
+
- "dnsseed.bluematt.me"
|
|
30
|
+
- "dnsseed.bitcoin.dashjr.org"
|
|
31
|
+
- "seed.bitcoinstats.com"
|
|
32
|
+
- "seed.bitcoin.jonasschnelli.ch"
|
|
33
|
+
genesis:
|
|
34
|
+
hash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
|
35
|
+
merkle_root: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
|
|
36
|
+
time: 1231006505
|
|
37
|
+
nonce: 2083236893
|
|
38
|
+
bits: 0x1d00ffff
|
|
39
|
+
version: 1
|
|
40
|
+
prev_hash: "0000000000000000000000000000000000000000000000000000000000000000"
|
|
41
|
+
bip44_coin_type: 0
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
--- !ruby/object:Tapyrus::ChainParams
|
|
2
|
+
network: "regtest"
|
|
3
|
+
magic_head: "fabfb5da"
|
|
4
|
+
message_magic: "Bitcoin Signed Message:\n"
|
|
5
|
+
address_version: "6f"
|
|
6
|
+
p2sh_version: "c4"
|
|
7
|
+
bech32_hrp: 'bcrt'
|
|
8
|
+
privkey_version: "ef"
|
|
9
|
+
extended_privkey_version: "04358394"
|
|
10
|
+
extended_pubkey_version: "043587cf"
|
|
11
|
+
bip49_pubkey_p2wpkh_p2sh_version: "044a5262"
|
|
12
|
+
bip49_pubkey_p2wsh_p2sh_version: "024285ef"
|
|
13
|
+
bip49_privkey_p2wpkh_p2sh_version: "044a4e28"
|
|
14
|
+
bip49_privkey_p2wsh_p2sh_version: "024285b5"
|
|
15
|
+
bip84_pubkey_p2wpkh_version: "045f1cf6"
|
|
16
|
+
bip84_pubkey_p2wsh_version: "02575483"
|
|
17
|
+
bip84_privkey_p2wpkh_version: "045f18bc"
|
|
18
|
+
bip84_privkey_p2wsh_version: "02575048"
|
|
19
|
+
default_port: 18444
|
|
20
|
+
protocol_version: 70013
|
|
21
|
+
retarget_interval: 2016
|
|
22
|
+
retarget_time: 1209600 # 2 weeks
|
|
23
|
+
target_spacing: 600 # block interval
|
|
24
|
+
max_money: 21000000
|
|
25
|
+
bip34_height: 0
|
|
26
|
+
genesis_hash: "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
|
|
27
|
+
proof_of_work_limit: 0x207fffff
|
|
28
|
+
dns_seeds:
|
|
29
|
+
genesis:
|
|
30
|
+
hash: "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
|
|
31
|
+
merkle_root: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
|
|
32
|
+
time: 1296688602
|
|
33
|
+
nonce: 2
|
|
34
|
+
bits: 0x207fffff
|
|
35
|
+
version: 1
|
|
36
|
+
prev_hash: "0000000000000000000000000000000000000000000000000000000000000000"
|
|
37
|
+
bip44_coin_type: 1
|
|
38
|
+
dust_relay_fee: 3600
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
--- !ruby/object:Tapyrus::ChainParams
|
|
2
|
+
network: "testnet"
|
|
3
|
+
magic_head: "0b110907"
|
|
4
|
+
message_magic: "Bitcoin Signed Message:\n"
|
|
5
|
+
address_version: "6f"
|
|
6
|
+
p2sh_version: "c4"
|
|
7
|
+
bech32_hrp: 'tb'
|
|
8
|
+
privkey_version: "ef"
|
|
9
|
+
extended_privkey_version: "04358394"
|
|
10
|
+
extended_pubkey_version: "043587cf"
|
|
11
|
+
bip49_pubkey_p2wpkh_p2sh_version: "044a5262"
|
|
12
|
+
bip49_pubkey_p2wsh_p2sh_version: "024285ef"
|
|
13
|
+
bip49_privkey_p2wpkh_p2sh_version: "044a4e28"
|
|
14
|
+
bip49_privkey_p2wsh_p2sh_version: "024285b5"
|
|
15
|
+
bip84_pubkey_p2wpkh_version: "045f1cf6"
|
|
16
|
+
bip84_pubkey_p2wsh_version: "02575483"
|
|
17
|
+
bip84_privkey_p2wpkh_version: "045f18bc"
|
|
18
|
+
bip84_privkey_p2wsh_version: "02575048"
|
|
19
|
+
default_port: 18333
|
|
20
|
+
protocol_version: 70013
|
|
21
|
+
retarget_interval: 2016
|
|
22
|
+
retarget_time: 1209600 # 2 weeks
|
|
23
|
+
target_spacing: 600 # block interval
|
|
24
|
+
max_money: 21000000
|
|
25
|
+
bip34_height: 227931
|
|
26
|
+
genesis_hash: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
|
|
27
|
+
proof_of_work_limit: 0x1d00ffff
|
|
28
|
+
dns_seeds:
|
|
29
|
+
- "testnet-seed.bitcoin.jonasschnelli.ch"
|
|
30
|
+
- "seed.tbtc.petertodd.org"
|
|
31
|
+
- "testnet-seed.bluematt.me"
|
|
32
|
+
- "testnet-seed.bitcoin.schildbach.de"
|
|
33
|
+
genesis:
|
|
34
|
+
hash: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
|
|
35
|
+
merkle_root: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
|
|
36
|
+
time: 1296688602
|
|
37
|
+
nonce: 414098458
|
|
38
|
+
bits: 0x1d00ffff
|
|
39
|
+
version: 1
|
|
40
|
+
prev_hash: "0000000000000000000000000000000000000000000000000000000000000000"
|
|
41
|
+
bip44_coin_type: 1
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
module Tapyrus
|
|
2
|
+
|
|
3
|
+
COIN = 100_000_000
|
|
4
|
+
MAX_MONEY = 21_000_000 * COIN
|
|
5
|
+
|
|
6
|
+
# The maximum allowed size for a serialized block, in bytes (only for buffer size limits)
|
|
7
|
+
MAX_BLOCK_SERIALIZED_SIZE = 4_000_000
|
|
8
|
+
# The maximum allowed weight for a block, see BIP 141 (network rule)
|
|
9
|
+
MAX_BLOCK_WEIGHT = 4_000_000
|
|
10
|
+
# The maximum allowed number of signature check operations in a block (network rule)
|
|
11
|
+
MAX_BLOCK_SIGOPS_COST = 80_000
|
|
12
|
+
# Coinbase transaction outputs can only be spent after this number of new blocks (network rule)
|
|
13
|
+
COINBASE_MATURITY = 100
|
|
14
|
+
WITNESS_SCALE_FACTOR = 4
|
|
15
|
+
|
|
16
|
+
# 60 is the lower bound for the size of a valid serialized Tx
|
|
17
|
+
MIN_TRANSACTION_WEIGHT = WITNESS_SCALE_FACTOR * 60
|
|
18
|
+
# 10 is the lower bound for the size of a serialized Tx
|
|
19
|
+
MIN_SERIALIZABLE_TRANSACTION_WEIGHT = WITNESS_SCALE_FACTOR * 10
|
|
20
|
+
|
|
21
|
+
# Flags for nSequence and nLockTime locks
|
|
22
|
+
LOCKTIME_VERIFY_SEQUENCE = (1 << 0)
|
|
23
|
+
LOCKTIME_MEDIAN_TIME_PAST = (1 << 1)
|
|
24
|
+
|
|
25
|
+
# Min feerate for defining dust.
|
|
26
|
+
DUST_RELAY_TX_FEE = 3000
|
|
27
|
+
|
|
28
|
+
# script verify flags
|
|
29
|
+
SCRIPT_VERIFY_NONE = 0
|
|
30
|
+
SCRIPT_VERIFY_P2SH = (1 << 0)
|
|
31
|
+
SCRIPT_VERIFY_STRICTENC = (1 << 1)
|
|
32
|
+
SCRIPT_VERIFY_DERSIG = (1 << 2)
|
|
33
|
+
SCRIPT_VERIFY_LOW_S = (1 << 3)
|
|
34
|
+
SCRIPT_VERIFY_NULLDUMMY = (1 << 4)
|
|
35
|
+
SCRIPT_VERIFY_SIGPUSHONLY = (1 << 5)
|
|
36
|
+
SCRIPT_VERIFY_MINIMALDATA = (1 << 6)
|
|
37
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 7)
|
|
38
|
+
SCRIPT_VERIFY_CLEANSTACK = (1 << 8)
|
|
39
|
+
SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = (1 << 9) # Verify CHECKLOCKTIMEVERIFY (BIP-65)
|
|
40
|
+
SCRIPT_VERIFY_CHECKSEQUENCEVERIFY = (1 << 10) # support CHECKSEQUENCEVERIFY opcode (BIP-112)
|
|
41
|
+
SCRIPT_VERIFY_WITNESS = (1 << 11) # Support segregated witness
|
|
42
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM = (1 << 12) # Making v1-v16 witness program non-standard
|
|
43
|
+
SCRIPT_VERIFY_MINIMALIF = (1 << 13) # Segwit script only: Require the argument of OP_IF/NOTIF to be exactly 0x01 or empty vector
|
|
44
|
+
SCRIPT_VERIFY_NULLFAIL = (1 << 14) # Signature(s) must be empty vector if an CHECK(MULTI)SIG operation failed
|
|
45
|
+
SCRIPT_VERIFY_WITNESS_PUBKEYTYPE = (1 << 15) # Public keys in segregated witness scripts must be compressed
|
|
46
|
+
SCRIPT_VERIFY_CONST_SCRIPTCODE = (1 << 16) # Making OP_CODESEPARATOR and FindAndDelete fail any non-segwit scripts
|
|
47
|
+
|
|
48
|
+
MANDATORY_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH
|
|
49
|
+
|
|
50
|
+
# Standard script verification flags that standard transactions will comply with.
|
|
51
|
+
STANDARD_SCRIPT_VERIFY_FLAGS = [MANDATORY_SCRIPT_VERIFY_FLAGS,
|
|
52
|
+
SCRIPT_VERIFY_DERSIG,
|
|
53
|
+
SCRIPT_VERIFY_STRICTENC,
|
|
54
|
+
SCRIPT_VERIFY_MINIMALDATA,
|
|
55
|
+
SCRIPT_VERIFY_NULLDUMMY,
|
|
56
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS,
|
|
57
|
+
SCRIPT_VERIFY_CLEANSTACK,
|
|
58
|
+
SCRIPT_VERIFY_MINIMALIF,
|
|
59
|
+
SCRIPT_VERIFY_NULLFAIL,
|
|
60
|
+
SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY,
|
|
61
|
+
SCRIPT_VERIFY_CHECKSEQUENCEVERIFY,
|
|
62
|
+
SCRIPT_VERIFY_LOW_S,
|
|
63
|
+
SCRIPT_VERIFY_WITNESS,
|
|
64
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM,
|
|
65
|
+
SCRIPT_VERIFY_WITNESS_PUBKEYTYPE,
|
|
66
|
+
SCRIPT_VERIFY_CONST_SCRIPTCODE].inject(SCRIPT_VERIFY_NONE){|flags, f| flags |= f}
|
|
67
|
+
|
|
68
|
+
# for script
|
|
69
|
+
|
|
70
|
+
# witness version
|
|
71
|
+
WITNESS_VERSION = 0x00
|
|
72
|
+
|
|
73
|
+
# Maximum script length in bytes
|
|
74
|
+
MAX_SCRIPT_SIZE = 10000
|
|
75
|
+
|
|
76
|
+
# Maximum number of public keys per multisig
|
|
77
|
+
MAX_PUBKEYS_PER_MULTISIG = 20
|
|
78
|
+
|
|
79
|
+
# Maximum number of non-push operations per script
|
|
80
|
+
MAX_OPS_PER_SCRIPT = 201
|
|
81
|
+
|
|
82
|
+
# Maximum number of bytes pushable to the stack
|
|
83
|
+
MAX_SCRIPT_ELEMENT_SIZE = 520
|
|
84
|
+
|
|
85
|
+
# Maximum number of size in the stack
|
|
86
|
+
MAX_STACK_SIZE = 1000
|
|
87
|
+
|
|
88
|
+
# Threshold for nLockTime: below this value it is interpreted as block number, otherwise as UNIX timestamp.
|
|
89
|
+
LOCKTIME_THRESHOLD = 500000000
|
|
90
|
+
|
|
91
|
+
# Signature hash types/flags
|
|
92
|
+
SIGHASH_TYPE = { all: 1, none: 2, single: 3, anyonecanpay: 128 }
|
|
93
|
+
|
|
94
|
+
# SIGHASH_FORK_ID for replay protection of the fork coin
|
|
95
|
+
SIGHASH_FORK_ID = 0x40
|
|
96
|
+
|
|
97
|
+
# fork coin id.
|
|
98
|
+
FORK_ID_CASH = 0
|
|
99
|
+
FORK_ID_GOLD = 79
|
|
100
|
+
|
|
101
|
+
# Maximum number length in bytes
|
|
102
|
+
DEFAULT_MAX_NUM_SIZE = 4
|
|
103
|
+
|
|
104
|
+
# 80 bytes of data, +1 for OP_RETURN, +2 for the pushdata opcodes.
|
|
105
|
+
MAX_OP_RETURN_RELAY = 83
|
|
106
|
+
|
|
107
|
+
SIG_VERSION = [:base, :witness_v0]
|
|
108
|
+
|
|
109
|
+
# for script error
|
|
110
|
+
SCRIPT_ERR_OK = 0
|
|
111
|
+
SCRIPT_ERR_UNKNOWN_ERROR = 1
|
|
112
|
+
SCRIPT_ERR_EVAL_FALSE = 2
|
|
113
|
+
SCRIPT_ERR_OP_RETURN = 3
|
|
114
|
+
|
|
115
|
+
# Max sizes
|
|
116
|
+
SCRIPT_ERR_SCRIPT_SIZE = 10
|
|
117
|
+
SCRIPT_ERR_PUSH_SIZE = 11
|
|
118
|
+
SCRIPT_ERR_OP_COUNT = 12
|
|
119
|
+
SCRIPT_ERR_STACK_SIZE = 13
|
|
120
|
+
SCRIPT_ERR_SIG_COUNT = 14
|
|
121
|
+
SCRIPT_ERR_PUBKEY_COUNT = 15
|
|
122
|
+
|
|
123
|
+
# Failed verify operations
|
|
124
|
+
SCRIPT_ERR_VERIFY = 20
|
|
125
|
+
SCRIPT_ERR_EQUALVERIFY = 21
|
|
126
|
+
SCRIPT_ERR_CHECKMULTISIGVERIFY = 22
|
|
127
|
+
SCRIPT_ERR_CHECKSIGVERIFY = 23
|
|
128
|
+
SCRIPT_ERR_NUMEQUALVERIFY = 24
|
|
129
|
+
|
|
130
|
+
# Logical/Format/Canonical errors
|
|
131
|
+
SCRIPT_ERR_BAD_OPCODE = 30
|
|
132
|
+
SCRIPT_ERR_DISABLED_OPCODE = 31
|
|
133
|
+
SCRIPT_ERR_INVALID_STACK_OPERATION = 32
|
|
134
|
+
SCRIPT_ERR_INVALID_ALTSTACK_OPERATION = 33
|
|
135
|
+
SCRIPT_ERR_UNBALANCED_CONDITIONAL = 34
|
|
136
|
+
|
|
137
|
+
# CHECKLOCKTIMEVERIFY and CHECKSEQUENCEVERIFY
|
|
138
|
+
SCRIPT_ERR_NEGATIVE_LOCKTIME = 40
|
|
139
|
+
SCRIPT_ERR_UNSATISFIED_LOCKTIME = 41
|
|
140
|
+
|
|
141
|
+
# Malleability
|
|
142
|
+
SCRIPT_ERR_SIG_HASHTYPE = 50
|
|
143
|
+
SCRIPT_ERR_SIG_DER = 51
|
|
144
|
+
SCRIPT_ERR_MINIMALDATA = 52
|
|
145
|
+
SCRIPT_ERR_SIG_PUSHONLY = 53
|
|
146
|
+
SCRIPT_ERR_SIG_HIGH_S = 54
|
|
147
|
+
SCRIPT_ERR_SIG_NULLDUMMY = 55
|
|
148
|
+
SCRIPT_ERR_PUBKEYTYPE = 56
|
|
149
|
+
SCRIPT_ERR_CLEANSTACK = 56
|
|
150
|
+
SCRIPT_ERR_MINIMALIF = 57
|
|
151
|
+
SCRIPT_ERR_SIG_NULLFAIL = 58
|
|
152
|
+
|
|
153
|
+
# softfork safeness
|
|
154
|
+
SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS = 60
|
|
155
|
+
SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM = 61
|
|
156
|
+
|
|
157
|
+
# segregated witness
|
|
158
|
+
SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH = 70
|
|
159
|
+
SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY = 71
|
|
160
|
+
SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH = 72
|
|
161
|
+
SCRIPT_ERR_WITNESS_MALLEATED = 73
|
|
162
|
+
SCRIPT_ERR_WITNESS_MALLEATED_P2SH = 74
|
|
163
|
+
SCRIPT_ERR_WITNESS_UNEXPECTED = 75
|
|
164
|
+
SCRIPT_ERR_WITNESS_PUBKEYTYPE = 76
|
|
165
|
+
|
|
166
|
+
# Constant scriptCode
|
|
167
|
+
SCRIPT_ERR_OP_CODESEPARATOR = 77
|
|
168
|
+
SCRIPT_ERR_SIG_FINDANDDELETE = 78
|
|
169
|
+
|
|
170
|
+
SCRIPT_ERR_ERROR_COUNT = 80
|
|
171
|
+
|
|
172
|
+
ERRCODES_MAP = Hash[*constants.grep(/^SCRIPT_ERR_/).map { |c| [const_get(c), c.to_s] }.flatten]
|
|
173
|
+
NAME_MAP = Hash[*constants.grep(/^SCRIPT_ERR_/).map { |c| [c.to_s, const_get(c)] }.flatten]
|
|
174
|
+
|
|
175
|
+
# witness commitment
|
|
176
|
+
WITNESS_COMMITMENT_HEADER = 'aa21a9ed'
|
|
177
|
+
|
|
178
|
+
COINBASE_WTXID = '00'* 32
|
|
179
|
+
|
|
180
|
+
# for message
|
|
181
|
+
MESSAGE_HEADER_SIZE = 24
|
|
182
|
+
|
|
183
|
+
# for peer
|
|
184
|
+
PARALLEL_THREAD = 3
|
|
185
|
+
|
|
186
|
+
# Maximum amount of time that a block timestamp is allowed to exceed the current network-adjusted time before the block will be accepted.
|
|
187
|
+
MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60
|
|
188
|
+
|
|
189
|
+
# Size of set to pick median time from.
|
|
190
|
+
MEDIAN_TIME_SPAN = 11
|
|
191
|
+
|
|
192
|
+
BIP32_EXTKEY_WITH_VERSION_SIZE = 78
|
|
193
|
+
|
|
194
|
+
HARDENED_THRESHOLD = 2147483648 # 2**31
|
|
195
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
module Tapyrus
|
|
2
|
+
|
|
3
|
+
module Descriptor
|
|
4
|
+
|
|
5
|
+
include Tapyrus::Opcodes
|
|
6
|
+
|
|
7
|
+
# generate P2PK output for the given public key.
|
|
8
|
+
# @param [String] key private key or public key with hex format
|
|
9
|
+
# @return [Tapyrus::Script] P2PK script.
|
|
10
|
+
def pk(key)
|
|
11
|
+
Tapyrus::Script.new << extract_pubkey(key) << OP_CHECKSIG
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# generate P2PKH output for the given public key.
|
|
15
|
+
# @param [String] key private key or public key with hex format.
|
|
16
|
+
# @return [Tapyrus::Script] P2PKH script.
|
|
17
|
+
def pkh(key)
|
|
18
|
+
Tapyrus::Script.to_p2pkh(Tapyrus.hash160(extract_pubkey(key)))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# generate P2PKH output for the given public key.
|
|
22
|
+
# @param [String] key private key or public key with hex format.
|
|
23
|
+
# @return [Tapyrus::Script] P2WPKH script.
|
|
24
|
+
def wpkh(key)
|
|
25
|
+
pubkey = extract_pubkey(key)
|
|
26
|
+
raise ArgumentError, "Uncompressed key are not allowed." unless compressed_key?(pubkey)
|
|
27
|
+
Tapyrus::Script.to_p2wpkh(Tapyrus.hash160(pubkey))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# generate P2SH embed the argument.
|
|
31
|
+
# @param [String or Script] script script to be embed.
|
|
32
|
+
# @return [Tapyrus::Script] P2SH script.
|
|
33
|
+
def sh(script)
|
|
34
|
+
script = script.to_hex if script.is_a?(Tapyrus::Script)
|
|
35
|
+
raise ArgumentError, "P2SH script is too large, 547 bytes is larger than #{Tapyrus::MAX_SCRIPT_ELEMENT_SIZE} bytes." if script.htb.bytesize > Tapyrus::MAX_SCRIPT_ELEMENT_SIZE
|
|
36
|
+
Tapyrus::Script.to_p2sh(Tapyrus.hash160(script))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# generate P2WSH embed the argument.
|
|
40
|
+
# @param [String or Script] script script to be embed.
|
|
41
|
+
# @return [Tapyrus::Script] P2WSH script.
|
|
42
|
+
def wsh(script)
|
|
43
|
+
script = Tapyrus::Script(script.htb) if script.is_a?(String)
|
|
44
|
+
raise ArgumentError, "P2SH script is too large, 547 bytes is larger than #{Tapyrus::MAX_SCRIPT_ELEMENT_SIZE} bytes." if script.to_payload.bytesize > Tapyrus::MAX_SCRIPT_ELEMENT_SIZE
|
|
45
|
+
raise ArgumentError, "Uncompressed key are not allowed." if script.get_pubkeys.any?{|p|!compressed_key?(p)}
|
|
46
|
+
Tapyrus::Script.to_p2wsh(script)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# an alias for the collection of `pk(KEY)` and `pkh(KEY)`.
|
|
50
|
+
# If the key is compressed, it also includes `wpkh(KEY)` and `sh(wpkh(KEY))`.
|
|
51
|
+
# @param [String] key private key or public key with hex format.
|
|
52
|
+
# @return [Array[Tapyrus::Script]]
|
|
53
|
+
def combo(key)
|
|
54
|
+
result = [pk(key), pkh(key)]
|
|
55
|
+
pubkey = extract_pubkey(key)
|
|
56
|
+
if compressed_key?(pubkey)
|
|
57
|
+
result << wpkh(key)
|
|
58
|
+
result << sh(result.last)
|
|
59
|
+
end
|
|
60
|
+
result
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# generate multisig output for given keys.
|
|
64
|
+
# @param [Integer] threshold the threshold of multisig.
|
|
65
|
+
# @param [Array[String]] keys an array of keys.
|
|
66
|
+
# @return [Tapyrus::Script] multisig script.
|
|
67
|
+
def multi(threshold, *keys, sort: false)
|
|
68
|
+
raise ArgumentError, 'Multisig threshold is not valid.' unless threshold.is_a?(Integer)
|
|
69
|
+
raise ArgumentError, 'Multisig threshold cannot be 0, must be at least 1.' unless threshold > 0
|
|
70
|
+
raise ArgumentError, 'Multisig threshold cannot be larger than the number of keys.' if threshold > keys.size
|
|
71
|
+
raise ArgumentError, 'Multisig must have between 1 and 16 keys, inclusive.' if keys.size > 16
|
|
72
|
+
pubkeys = keys.map{|key| extract_pubkey(key) }
|
|
73
|
+
Tapyrus::Script.to_multisig_script(threshold, pubkeys, sort: sort)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# generate sorted multisig output for given keys.
|
|
77
|
+
# @param [Integer] threshold the threshold of multisig.
|
|
78
|
+
# @param [Array[String]] keys an array of keys.
|
|
79
|
+
# @return [Tapyrus::Script] multisig script.
|
|
80
|
+
def sortedmulti(threshold, *keys)
|
|
81
|
+
multi(threshold, *keys, sort: true)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
# extract public key from KEY format.
|
|
87
|
+
# @param [String] key KEY string.
|
|
88
|
+
# @return [String] public key.
|
|
89
|
+
def extract_pubkey(key)
|
|
90
|
+
if key.start_with?('[') # BIP32 fingerprint
|
|
91
|
+
raise ArgumentError, 'Invalid key origin.' if key.count('[') > 1 || key.count(']') > 1
|
|
92
|
+
info = key[1...key.index(']')] # TODO
|
|
93
|
+
fingerprint, *paths = info.split('/')
|
|
94
|
+
raise ArgumentError, 'Fingerprint is not hex.' unless fingerprint.valid_hex?
|
|
95
|
+
raise ArgumentError, 'Fingerprint is not 4 bytes.' unless fingerprint.size == 8
|
|
96
|
+
key = key[(key.index(']') + 1)..-1]
|
|
97
|
+
else
|
|
98
|
+
raise ArgumentError, 'Invalid key origin.' if key.include?(']')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# check BIP32 derivation path
|
|
102
|
+
key, *paths = key.split('/')
|
|
103
|
+
|
|
104
|
+
if key.start_with?('xprv')
|
|
105
|
+
key = Tapyrus::ExtKey.from_base58(key)
|
|
106
|
+
key = derive_path(key, paths, true) if paths
|
|
107
|
+
elsif key.start_with?('xpub')
|
|
108
|
+
key = Tapyrus::ExtPubkey.from_base58(key)
|
|
109
|
+
key = derive_path(key, paths, false) if paths
|
|
110
|
+
else
|
|
111
|
+
begin
|
|
112
|
+
key = Tapyrus::Key.from_wif(key)
|
|
113
|
+
rescue ArgumentError
|
|
114
|
+
key_type = compressed_key?(key) ? Tapyrus::Key::TYPES[:compressed] : Tapyrus::Key::TYPES[:uncompressed]
|
|
115
|
+
key = Tapyrus::Key.new(pubkey: key, key_type: key_type)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
key = key.is_a?(Tapyrus::Key) ? key : key.key
|
|
119
|
+
raise ArgumentError, 'Invalid pubkey.' unless key.fully_valid_pubkey?
|
|
120
|
+
key.pubkey
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def compressed_key?(key)
|
|
124
|
+
%w(02 03).include?(key[0..1]) && [key].pack("H*").bytesize == 33
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def derive_path(key, paths, is_private)
|
|
128
|
+
paths.each do |path|
|
|
129
|
+
raise ArgumentError, 'xpub can not derive hardened key.' if !is_private && path.end_with?("'")
|
|
130
|
+
if is_private
|
|
131
|
+
hardened = path.end_with?("'")
|
|
132
|
+
path = hardened ? path[0..-2] : path
|
|
133
|
+
raise ArgumentError, 'Key path value is not a valid value.' unless path =~ /^[0-9]+$/
|
|
134
|
+
raise ArgumentError, 'Key path value is out of range.' if !hardened && path.to_i >= Tapyrus::HARDENED_THRESHOLD
|
|
135
|
+
key = key.derive(path.to_i, hardened)
|
|
136
|
+
else
|
|
137
|
+
raise ArgumentError, 'Key path value is not a valid value.' unless path =~ /^[0-9]+$/
|
|
138
|
+
raise ArgumentError, 'Key path value is out of range.' if path.to_i >= Tapyrus::HARDENED_THRESHOLD
|
|
139
|
+
key = key.derive(path.to_i)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
key
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
end
|