tapyrus 0.1.0 → 0.2.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -14
  3. data/exe/tapyrusrbd +2 -2
  4. data/lib/openassets/util.rb +2 -4
  5. data/lib/schnorr.rb +83 -0
  6. data/lib/schnorr/signature.rb +38 -0
  7. data/lib/tapyrus.rb +9 -11
  8. data/lib/tapyrus/block.rb +1 -32
  9. data/lib/tapyrus/block_header.rb +7 -6
  10. data/lib/tapyrus/chain_params.rb +13 -26
  11. data/lib/tapyrus/chainparams/{testnet.yml → dev.yml} +7 -9
  12. data/lib/tapyrus/chainparams/{mainnet.yml → prod.yml} +7 -10
  13. data/lib/tapyrus/constants.rb +12 -34
  14. data/lib/tapyrus/ext.rb +5 -0
  15. data/lib/tapyrus/ext/json_parser.rb +47 -0
  16. data/lib/tapyrus/ext_key.rb +5 -10
  17. data/lib/tapyrus/key.rb +57 -29
  18. data/lib/tapyrus/message.rb +2 -2
  19. data/lib/tapyrus/message/base.rb +1 -0
  20. data/lib/tapyrus/message/block.rb +3 -3
  21. data/lib/tapyrus/message/cmpct_block.rb +3 -5
  22. data/lib/tapyrus/message/tx.rb +2 -2
  23. data/lib/tapyrus/network/peer.rb +1 -15
  24. data/lib/tapyrus/node/cli.rb +15 -11
  25. data/lib/tapyrus/node/configuration.rb +1 -1
  26. data/lib/tapyrus/node/spv.rb +1 -1
  27. data/lib/tapyrus/opcodes.rb +5 -0
  28. data/lib/tapyrus/out_point.rb +1 -1
  29. data/lib/tapyrus/rpc/request_handler.rb +3 -3
  30. data/lib/tapyrus/rpc/tapyrus_core_client.rb +17 -15
  31. data/lib/tapyrus/script/color.rb +79 -0
  32. data/lib/tapyrus/script/multisig.rb +0 -27
  33. data/lib/tapyrus/script/script.rb +74 -89
  34. data/lib/tapyrus/script/script_error.rb +8 -14
  35. data/lib/tapyrus/script/script_interpreter.rb +65 -86
  36. data/lib/tapyrus/script/tx_checker.rb +16 -4
  37. data/lib/tapyrus/secp256k1.rb +1 -0
  38. data/lib/tapyrus/secp256k1/rfc6979.rb +43 -0
  39. data/lib/tapyrus/secp256k1/ruby.rb +5 -31
  40. data/lib/tapyrus/store/chain_entry.rb +1 -0
  41. data/lib/tapyrus/tx.rb +18 -160
  42. data/lib/tapyrus/tx_in.rb +4 -11
  43. data/lib/tapyrus/tx_out.rb +2 -1
  44. data/lib/tapyrus/util.rb +8 -0
  45. data/lib/tapyrus/validation.rb +1 -6
  46. data/lib/tapyrus/version.rb +1 -1
  47. data/lib/tapyrus/wallet/account.rb +1 -0
  48. data/lib/tapyrus/wallet/master_key.rb +1 -0
  49. data/tapyrusrb.gemspec +3 -3
  50. metadata +42 -39
  51. data/lib/tapyrus/chainparams/regtest.yml +0 -38
  52. data/lib/tapyrus/descriptor.rb +0 -147
  53. data/lib/tapyrus/script_witness.rb +0 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73863e0b35030aee45baa96661f5bbd0322e78381be022d8372e620f8cd8668c
4
- data.tar.gz: 3f6378420bb9940a4829e378bb9975c67f5e5c5b61e4c5e077d85f83e0f59715
3
+ metadata.gz: 1625f752a1848cec88c87af856ade34417b9f8aa618ee31db66387e60d8e7b8b
4
+ data.tar.gz: b30ed36b3f4e74f96b57866261b0be40c74883dcfa6605d68a73da99316bb746
5
5
  SHA512:
6
- metadata.gz: 5ffa34b251d363ade9355430980d50d233c104071b15f46535fee2009b8826f4b163891e384cec997b72e2680387cd3d200b55a41c4ebda8c359fdf6f965f091
7
- data.tar.gz: c3860a9b13c2f43ef55b47cb985eb9467d5c4fdf9f1436b6c0e97b9a1b04a2ecdcd726e0103baa5ccd302753cc460f0240c0cd76c32cc08347e46e997e0ec7d6
6
+ metadata.gz: 743afb61fcd03ab089bf3124e007d23789b87eac5f35176b3430966814b4ada178f49960dba7240df5f02d7bdfd4b9ef9639c0ca7a9c26d89f9abd498c0ee5ce
7
+ data.tar.gz: 1300203703bd438cf1e5a54967f9dcc1ee04d7a698a1678ad24b4ef8f3c18b568993790c40de537655d8b20fb06dec35dfaaeef8fe7d1fcc2886366876e18501
data/README.md CHANGED
@@ -65,29 +65,21 @@ And then add to your .rb file:
65
65
 
66
66
  The parameters of the blockchain are managed by `Tapyrus::ChainParams`. Switch chain parameters as follows:
67
67
 
68
- * mainnet
68
+ * prod
69
69
 
70
70
  ```ruby
71
- Tapyrus.chain_params = :mainnet
71
+ Tapyrus.chain_params = :prod
72
72
  ```
73
73
 
74
- This parameter is described in https://github.com/chaintope/tapyrusrb/blob/master/lib/tapyrus/chainparams/mainnet.yml.
74
+ This parameter is described in https://github.com/chaintope/tapyrusrb/blob/master/lib/tapyrus/chainparams/prod.yml.
75
75
 
76
- * testnet
76
+ * dev
77
77
 
78
78
  ```ruby
79
- Tapyrus.chain_params = :testnet
79
+ Tapyrus.chain_params = :dev
80
80
  ```
81
81
 
82
- This parameter is described in https://github.com/chaintope/tapyrusrb/blob/master/lib/tapyrus/chainparams/testnet.yml.
83
-
84
- * regtest
85
-
86
- ```ruby
87
- Tapyrus.chain_params = :regtest
88
- ```
89
-
90
- This parameter is described in https://github.com/chaintope/tapyrusrb/blob/master/lib/tapyrus/chainparams/regtest.yml.
82
+ This parameter is described in https://github.com/chaintope/tapyrusrb/blob/master/lib/tapyrus/chainparams/dev.yml.
91
83
 
92
84
  ## Contributing
93
85
 
@@ -16,11 +16,11 @@ end
16
16
 
17
17
  class Tapyrusrbd < Thor
18
18
 
19
- class_option :network, aliases: '-n', default: :mainnet
19
+ class_option :network, aliases: '-n', default: :prod
20
20
 
21
21
  desc 'start', 'start tapyrusrb daemon.'
22
22
  def start
23
- network = options['network'] ? options['network'].to_sym : :mainnet
23
+ network = options['network'] ? options['network'].to_sym : :prod
24
24
  Tapyrus.chain_params = network
25
25
  FileUtils.mkdir_p(Tapyrus.base_dir)
26
26
  execute_daemon(['start', network: network])
@@ -18,10 +18,8 @@ module OpenAssets
18
18
  end
19
19
 
20
20
  def oa_version_byte
21
- case Tapyrus.chain_params.network
22
- when 'mainnet' then OA_VERSION_BYTE
23
- when 'testnet', 'regtest' then OA_VERSION_BYTE_TESTNET
24
- end
21
+ return OA_VERSION_BYTE if Tapyrus.chain_params.prod?
22
+ return OA_VERSION_BYTE_TESTNET if Tapyrus.chain_params.dev?
25
23
  end
26
24
  end
27
25
  end
@@ -0,0 +1,83 @@
1
+ module Schnorr
2
+ autoload :Signature, 'schnorr/signature'
3
+
4
+ module_function
5
+
6
+ GROUP = ECDSA::Group::Secp256k1
7
+ ALGO16 = 'SCHNORR + SHA256'
8
+
9
+ # Generate schnorr signature.
10
+ # @param message (String) A message to be signed with binary format.
11
+ # @param private_key (Integer) The private key.
12
+ # (The number of times to add the generator point to itself to get the public key.)
13
+ # @return (Schnorr::Signature)
14
+ def sign(message, private_key)
15
+ raise 'The message must be a 32-byte array.' unless message.bytesize == 32
16
+ raise 'private_key is zero or over the curve order.' if private_key == 0 || private_key >= GROUP.order
17
+
18
+ p = GROUP.new_point(private_key)
19
+ secret = ECDSA::Format::IntegerOctetString.encode(private_key, GROUP.byte_length)
20
+ secret = secret + message + ALGO16
21
+ nonce = Tapyrus::Secp256k1::RFC6979.generate_rfc6979_nonce(secret, '')
22
+
23
+ k0 = nonce % GROUP.order
24
+ raise 'Creation of signature failed. k is zero' if k0.zero?
25
+
26
+ r = GROUP.new_point(k0)
27
+ k = ECDSA::PrimeField.jacobi(r.y, GROUP.field.prime) == 1 ? k0 : GROUP.order - k0
28
+
29
+ e = create_challenge(r.x, p, message)
30
+
31
+ Schnorr::Signature.new(r.x, (k + e * private_key) % GROUP.order)
32
+ end
33
+
34
+ # Verifies the given {Signature} and returns true if it is valid.
35
+ # @param message (String) A message to be signed with binary format.
36
+ # @param public_key (String) The public key with binary format.
37
+ # @param signature (String) The signature with binary format.
38
+ # @return (Boolean) whether signature is valid.
39
+ def valid_sig?(message, signature, public_key)
40
+ check_sig!(message, signature, public_key)
41
+ rescue InvalidSignatureError, ECDSA::Format::DecodeError
42
+ false
43
+ end
44
+
45
+ # Verifies the given {Signature} and raises an {InvalidSignatureError} if it is invalid.
46
+ # @param message (String) A message to be signed with binary format.
47
+ # @param public_key (String) The public key with binary format.
48
+ # @param signature (String) The signature with binary format.
49
+ # @return (Boolean)
50
+ def check_sig!(message, signature, public_key)
51
+ sig = Schnorr::Signature.decode(signature)
52
+ pubkey = ECDSA::Format::PointOctetString.decode(public_key, GROUP)
53
+ field = GROUP.field
54
+
55
+ raise Schnorr::InvalidSignatureError, 'Invalid signature: r is not in the field.' unless field.include?(sig.r)
56
+ raise Schnorr::InvalidSignatureError, 'Invalid signature: s is not in the field.' unless field.include?(sig.s)
57
+ raise Schnorr::InvalidSignatureError, 'Invalid signature: r is zero.' if sig.r.zero?
58
+ raise Schnorr::InvalidSignatureError, 'Invalid signature: s is zero.' if sig.s.zero?
59
+ raise Schnorr::InvalidSignatureError, 'Invalid signature: r is larger than field size.' if sig.r >= field.prime
60
+ raise Schnorr::InvalidSignatureError, 'Invalid signature: s is larger than group order.' if sig.s >= GROUP.order
61
+
62
+ e = create_challenge(sig.r, pubkey, message)
63
+
64
+ r = GROUP.new_point(sig.s) + pubkey.multiply_by_scalar(e).negate
65
+
66
+ if r.infinity? || r.x != sig.r || ECDSA::PrimeField.jacobi(r.y, GROUP.field.prime) != 1
67
+ raise Schnorr::InvalidSignatureError, 'signature verification failed.'
68
+ end
69
+
70
+ true
71
+ end
72
+
73
+ # create signature digest.
74
+ # @param (Integer) x a x coordinate for R.
75
+ # @param (ECDSA::Point) p a public key.
76
+ # @return (Integer) digest e.
77
+ def create_challenge(x, p, message)
78
+ r_x = ECDSA::Format::IntegerOctetString.encode(x, GROUP.byte_length)
79
+ p_str= ECDSA::Format::PointOctetString.encode(p, compression:true)
80
+ (ECDSA.normalize_digest(Digest::SHA256.digest(r_x + p_str + message), GROUP.bit_length)) % GROUP.order
81
+ end
82
+
83
+ end
@@ -0,0 +1,38 @@
1
+ module Schnorr
2
+
3
+ class InvalidSignatureError < StandardError; end
4
+
5
+ # Instances of this class represents Schnorr signatures,
6
+ # which are simply a pair of integers named `r` and `s`.
7
+ class Signature
8
+
9
+ attr_reader :r
10
+ attr_reader :s
11
+
12
+ # @param r (Integer) the value of r.
13
+ # @param s (Integer) the value of s.
14
+ def initialize(r, s)
15
+ @r, @s = r, s
16
+ r.is_a?(Integer) or raise ArgumentError, 'r is not an integer.'
17
+ s.is_a?(Integer) or raise ArgumentError, 's is not an integer.'
18
+ end
19
+
20
+ # Parse a string to a {Signature}.
21
+ # @param string (String) signature string with binary format.
22
+ # @return (Signature) signature instance.
23
+ def self.decode(string)
24
+ raise InvalidSignatureError, 'Invalid schnorr signature length.' unless string.bytesize == 64
25
+ r = string[0...32].unpack('H*').first.to_i(16)
26
+ s = string[32..-1].unpack('H*').first.to_i(16)
27
+ new(r, s)
28
+ end
29
+
30
+ # Encode signature to string.
31
+ # @return (String) encoded signature.
32
+ def encode
33
+ ECDSA::Format::IntegerOctetString.encode(r, 32) + ECDSA::Format::IntegerOctetString.encode(s, 32)
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -6,14 +6,15 @@ require 'eventmachine'
6
6
  require 'ecdsa'
7
7
  require 'securerandom'
8
8
  require 'json'
9
- require 'bech32'
10
9
  require 'ffi'
11
10
  require 'observer'
12
11
  require 'tmpdir'
13
12
  require_relative 'openassets'
13
+ require_relative 'schnorr'
14
14
 
15
15
  module Tapyrus
16
16
 
17
+ autoload :Ext, 'tapyrus/ext'
17
18
  autoload :Util, 'tapyrus/util'
18
19
  autoload :ChainParams, 'tapyrus/chain_params'
19
20
  autoload :Message, 'tapyrus/message'
@@ -29,7 +30,6 @@ module Tapyrus
29
30
  autoload :TxIn, 'tapyrus/tx_in'
30
31
  autoload :TxOut, 'tapyrus/tx_out'
31
32
  autoload :OutPoint, 'tapyrus/out_point'
32
- autoload :ScriptWitness, 'tapyrus/script_witness'
33
33
  autoload :MerkleTree, 'tapyrus/merkle_tree'
34
34
  autoload :Key, 'tapyrus/key'
35
35
  autoload :ExtKey, 'tapyrus/ext_key'
@@ -46,18 +46,18 @@ module Tapyrus
46
46
  autoload :Wallet, 'tapyrus/wallet'
47
47
  autoload :BloomFilter, 'tapyrus/bloom_filter'
48
48
  autoload :KeyPath, 'tapyrus/key_path'
49
- autoload :Descriptor, 'tapyrus/descriptor'
50
49
  autoload :SLIP39, 'tapyrus/slip39'
50
+ autoload :Color, 'tapyrus/script/color'
51
51
 
52
52
  require_relative 'tapyrus/constants'
53
53
 
54
54
  extend Util
55
55
 
56
- @chain_param = :mainnet
56
+ @chain_param = :prod
57
57
 
58
58
  # set tapyrus network chain params
59
59
  def self.chain_params=(name)
60
- raise "chain params for #{name} is not defined." unless %i(mainnet testnet regtest).include?(name.to_sym)
60
+ raise "chain params for #{name} is not defined." unless %i(prod dev).include?(name.to_sym)
61
61
  @current_chain = nil
62
62
  @chain_param = name.to_sym
63
63
  end
@@ -66,12 +66,10 @@ module Tapyrus
66
66
  def self.chain_params
67
67
  return @current_chain if @current_chain
68
68
  case @chain_param
69
- when :mainnet
70
- @current_chain = Tapyrus::ChainParams.mainnet
71
- when :testnet
72
- @current_chain = Tapyrus::ChainParams.testnet
73
- when :regtest
74
- @current_chain = Tapyrus::ChainParams.regtest
69
+ when :prod
70
+ @current_chain = Tapyrus::ChainParams.prod
71
+ when :dev
72
+ @current_chain = Tapyrus::ChainParams.dev
75
73
  end
76
74
  @current_chain
77
75
  end
@@ -21,23 +21,6 @@ module Tapyrus
21
21
  header.block_hash
22
22
  end
23
23
 
24
- # calculate block weight
25
- def weight
26
- stripped_size * (WITNESS_SCALE_FACTOR - 1) + size
27
- end
28
-
29
- # calculate total size (include witness data.)
30
- def size
31
- 80 + Tapyrus.pack_var_int(transactions.size).bytesize +
32
- transactions.inject(0){|sum, tx| sum + (tx.witness? ? tx.serialize_witness_format.bytesize : tx.serialize_old_format.bytesize)}
33
- end
34
-
35
- # calculate base size (not include witness data.)
36
- def stripped_size
37
- 80 + Tapyrus.pack_var_int(transactions.size).bytesize +
38
- transactions.inject(0){|sum, tx| sum + tx.serialize_old_format.bytesize}
39
- end
40
-
41
24
  # check the merkle root in the block header matches merkle root calculated from tx list.
42
25
  def valid_merkle_root?
43
26
  calculate_merkle_root == header.merkle_root
@@ -48,24 +31,10 @@ module Tapyrus
48
31
  Tapyrus::MerkleTree.build_from_leaf(transactions.map(&:tx_hash)).merkle_root
49
32
  end
50
33
 
51
- # check the witness commitment in coinbase tx matches witness commitment calculated from tx list.
52
- def valid_witness_commitment?
53
- transactions[0].witness_commitment == calculate_witness_commitment
54
- end
55
-
56
- # calculate witness commitment from tx list.
57
- def calculate_witness_commitment
58
- witness_hashes = [COINBASE_WTXID]
59
- witness_hashes += (transactions[1..-1].map(&:witness_hash))
60
- reserved_value = transactions[0].inputs[0].script_witness.stack.map(&:bth).join
61
- root_hash = Tapyrus::MerkleTree.build_from_leaf(witness_hashes).merkle_root
62
- Tapyrus.double_sha256([root_hash + reserved_value].pack('H*')).bth
63
- end
64
-
65
34
  # return this block height. block height is included in coinbase.
66
35
  # if block version under 1, height does not include in coinbase, so return nil.
67
36
  def height
68
- return nil if header.version < 2
37
+ return nil if header.features < 2
69
38
  coinbase_tx = transactions[0]
70
39
  return nil unless coinbase_tx.coinbase_tx?
71
40
  buf = StringIO.new(coinbase_tx.inputs[0].script_sig.to_payload)
@@ -2,16 +2,17 @@ module Tapyrus
2
2
 
3
3
  # Block Header
4
4
  class BlockHeader
5
+ include Tapyrus::HexConverter
5
6
 
6
- attr_accessor :version
7
+ attr_accessor :features
7
8
  attr_accessor :prev_hash
8
9
  attr_accessor :merkle_root
9
10
  attr_accessor :time # unix timestamp
10
11
  attr_accessor :bits
11
12
  attr_accessor :nonce
12
13
 
13
- def initialize(version, prev_hash, merkle_root, time, bits, nonce)
14
- @version = version
14
+ def initialize(features, prev_hash, merkle_root, time, bits, nonce)
15
+ @features = features
15
16
  @prev_hash = prev_hash
16
17
  @merkle_root = merkle_root
17
18
  @time = time
@@ -20,12 +21,12 @@ module Tapyrus
20
21
  end
21
22
 
22
23
  def self.parse_from_payload(payload)
23
- version, prev_hash, merkle_root, time, bits, nonce = payload.unpack('Va32a32VVV')
24
- new(version, prev_hash.bth, merkle_root.bth, time, bits, nonce)
24
+ features, prev_hash, merkle_root, time, bits, nonce = payload.unpack('Va32a32VVV')
25
+ new(features, prev_hash.bth, merkle_root.bth, time, bits, nonce)
25
26
  end
26
27
 
27
28
  def to_payload
28
- [version, prev_hash.htb, merkle_root.htb, time, bits, nonce].pack('Va32a32VVV')
29
+ [features, prev_hash.htb, merkle_root.htb, time, bits, nonce].pack('Va32a32VVV')
29
30
  end
30
31
 
31
32
  # compute difficulty target from bits.
@@ -10,7 +10,8 @@ module Tapyrus
10
10
  attr_reader :message_magic
11
11
  attr_reader :address_version
12
12
  attr_reader :p2sh_version
13
- attr_reader :bech32_hrp
13
+ attr_reader :cp2pkh_version
14
+ attr_reader :cp2sh_version
14
15
  attr_reader :privkey_version
15
16
  attr_reader :extended_privkey_version
16
17
  attr_reader :extended_pubkey_version
@@ -23,6 +24,7 @@ module Tapyrus
23
24
  attr_reader :bip84_pubkey_p2wsh_version
24
25
  attr_reader :bip84_privkey_p2wsh_version
25
26
  attr_reader :default_port
27
+ attr_reader :rpc_port
26
28
  attr_reader :protocol_version
27
29
  attr_reader :retarget_interval
28
30
  attr_reader :retarget_time
@@ -36,34 +38,23 @@ module Tapyrus
36
38
 
37
39
  attr_accessor :dust_relay_fee
38
40
 
39
- # fork coin id.
40
- attr_accessor :fork_id
41
41
 
42
- # mainnet genesis
43
- def self.mainnet
44
- init('mainnet')
42
+ # production genesis
43
+ def self.prod
44
+ init('prod')
45
45
  end
46
46
 
47
- # testnet genesis
48
- def self.testnet
49
- init('testnet')
47
+ # development genesis
48
+ def self.dev
49
+ init('dev')
50
50
  end
51
51
 
52
- # regtest genesis
53
- def self.regtest
54
- init('regtest')
52
+ def prod?
53
+ network == 'prod'
55
54
  end
56
55
 
57
- def mainnet?
58
- network == 'mainnet'
59
- end
60
-
61
- def testnet?
62
- network == 'testnet'
63
- end
64
-
65
- def regtest?
66
- network == 'regtest'
56
+ def dev?
57
+ network == 'dev'
67
58
  end
68
59
 
69
60
  def genesis_block
@@ -73,10 +64,6 @@ module Tapyrus
73
64
  Tapyrus::Block.new(header)
74
65
  end
75
66
 
76
- # whether fork coin.
77
- def fork_chain?
78
- !fork_id.nil?
79
- end
80
67
 
81
68
  def self.init(name)
82
69
  i = YAML.load(File.open("#{__dir__}/chainparams/#{name}.yml"))
@@ -1,10 +1,11 @@
1
1
  --- !ruby/object:Tapyrus::ChainParams
2
- network: "testnet"
2
+ network: "dev"
3
3
  magic_head: "0b110907"
4
- message_magic: "Bitcoin Signed Message:\n"
4
+ message_magic: "Tapyrus Signed Message:\n"
5
5
  address_version: "6f"
6
6
  p2sh_version: "c4"
7
- bech32_hrp: 'tb'
7
+ cp2pkh_version: "70"
8
+ cp2sh_version: "c5"
8
9
  privkey_version: "ef"
9
10
  extended_privkey_version: "04358394"
10
11
  extended_pubkey_version: "043587cf"
@@ -16,8 +17,9 @@ bip84_pubkey_p2wpkh_version: "045f1cf6"
16
17
  bip84_pubkey_p2wsh_version: "02575483"
17
18
  bip84_privkey_p2wpkh_version: "045f18bc"
18
19
  bip84_privkey_p2wsh_version: "02575048"
19
- default_port: 18333
20
- protocol_version: 70013
20
+ default_port: 12383
21
+ rpc_port: 12381
22
+ protocol_version: 10000
21
23
  retarget_interval: 2016
22
24
  retarget_time: 1209600 # 2 weeks
23
25
  target_spacing: 600 # block interval
@@ -26,10 +28,6 @@ bip34_height: 227931
26
28
  genesis_hash: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
27
29
  proof_of_work_limit: 0x1d00ffff
28
30
  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
31
  genesis:
34
32
  hash: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
35
33
  merkle_root: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"