tapyrus 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|