bitcoinrb 0.8.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: af836e45167490d93d1d9b5d8f29f4e473a78dc314b98cd789bb47e347a42037
4
- data.tar.gz: 020fe745057f3b987c8bd8828be9f7f657d3085a7d8ee37cf0218b0761202325
3
+ metadata.gz: b6b5d0d0d17295086ec1cf989eb1df1be2d776bff6c5a38e85c34e21e258f56b
4
+ data.tar.gz: c93206160d6709f0050cbe8a001df40f712a6e8a779a61117c8c0cc911d4fd2f
5
5
  SHA512:
6
- metadata.gz: 0db2c744f372a2e71c337cb8970bb8cad81f37aa81824504d5fe6ee46819516ca6869c3cab0af1474aa0906905496af3e8f97eacc6cdaf9b7eced0a6cd247ff2
7
- data.tar.gz: dab1256db8085c43ec5ab5d784d465cbdb3d74e663d4f052fd76161decacd9fb5fe22b3c2f99cc20381dda8ea203d63866db45e6e8c8ed6d8b4a2d4c27acfe89
6
+ metadata.gz: fa5759ab05a69bfdaadda07d5cbf74c700bba2586f2544eaf823199c357b88405814dcaed7c6466a1a94e50027d5289a51f057d86815699e92862cb447b2478f
7
+ data.tar.gz: 8629b46d88f6674482bfaff89dffcf28e90edc857649aeaff56d579c12d0e4df99f9cc7cad63a31ecc1742afc64cfa089a7f86754be5614e27c145e0e693e1cb
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-3.0.0
1
+ ruby-3.0.2
data/README.md CHANGED
@@ -15,7 +15,7 @@ Bitcoinrb supports following feature:
15
15
  * Key generation and verification for ECDSA, including [BIP-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) and [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) supports.
16
16
  * ECDSA signature(RFC6979 -Deterministic ECDSA, LOW-S, LOW-R support)
17
17
  * Segwit support (parsing segwit payload, Bech32 address, sign for segwit tx, [BIP-141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki), [BIP-143](https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki), [BIP-144](https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki))
18
- * [BIP-173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) Bech32 address support
18
+ * bech32([BIP-173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)) and bech32m([BIP-350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki)) address support
19
19
  * [BIP-174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki) PSBT(Partially Signed Bitcoin Transaction) support
20
20
  * [BIP-85](https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki) Deterministic Entropy From BIP32 Keychains support by `Bitcoin::BIP85Entropy` class.
21
21
  * Schnorr signature([BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki))
@@ -3,6 +3,15 @@ module Bitcoin
3
3
  COIN = 100_000_000
4
4
  MAX_MONEY = 21_000_000 * COIN
5
5
 
6
+ # Byte size of the ripemd160 hash
7
+ RIPEMD160_SIZE = 20
8
+ # Byte size of the SHA256 hash
9
+ SHA256_SIZE = 32
10
+ # Byte size of the HASH160 hash
11
+ HASH160_SIZE = 20
12
+ # Byte size of the HASH256 hash
13
+ HASH256_SIZE = 32
14
+
6
15
  # The maximum allowed size for a serialized block, in bytes (only for buffer size limits)
7
16
  MAX_BLOCK_SERIALIZED_SIZE = 4_000_000
8
17
  # The maximum allowed weight for a block, see BIP 141 (network rule)
@@ -53,22 +62,28 @@ module Bitcoin
53
62
  MANDATORY_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH
54
63
 
55
64
  # Standard script verification flags that standard transactions will comply with.
56
- STANDARD_SCRIPT_VERIFY_FLAGS = [MANDATORY_SCRIPT_VERIFY_FLAGS,
57
- SCRIPT_VERIFY_DERSIG,
58
- SCRIPT_VERIFY_STRICTENC,
59
- SCRIPT_VERIFY_MINIMALDATA,
60
- SCRIPT_VERIFY_NULLDUMMY,
61
- SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS,
62
- SCRIPT_VERIFY_CLEANSTACK,
63
- SCRIPT_VERIFY_MINIMALIF,
64
- SCRIPT_VERIFY_NULLFAIL,
65
- SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY,
66
- SCRIPT_VERIFY_CHECKSEQUENCEVERIFY,
67
- SCRIPT_VERIFY_LOW_S,
68
- SCRIPT_VERIFY_WITNESS,
69
- SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM,
70
- SCRIPT_VERIFY_WITNESS_PUBKEYTYPE,
71
- SCRIPT_VERIFY_CONST_SCRIPTCODE].inject(SCRIPT_VERIFY_NONE){|flags, f| flags |= f}
65
+ STANDARD_SCRIPT_VERIFY_FLAGS = [
66
+ MANDATORY_SCRIPT_VERIFY_FLAGS,
67
+ SCRIPT_VERIFY_DERSIG,
68
+ SCRIPT_VERIFY_STRICTENC,
69
+ SCRIPT_VERIFY_MINIMALDATA,
70
+ SCRIPT_VERIFY_NULLDUMMY,
71
+ SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS,
72
+ SCRIPT_VERIFY_CLEANSTACK,
73
+ SCRIPT_VERIFY_MINIMALIF,
74
+ SCRIPT_VERIFY_NULLFAIL,
75
+ SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY,
76
+ SCRIPT_VERIFY_CHECKSEQUENCEVERIFY,
77
+ SCRIPT_VERIFY_LOW_S,
78
+ SCRIPT_VERIFY_WITNESS,
79
+ SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM,
80
+ SCRIPT_VERIFY_WITNESS_PUBKEYTYPE,
81
+ SCRIPT_VERIFY_CONST_SCRIPTCODE,
82
+ SCRIPT_VERIFY_TAPROOT,
83
+ SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION,
84
+ SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS,
85
+ SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE
86
+ ].inject(SCRIPT_VERIFY_NONE){|flags, f| flags |= f}
72
87
 
73
88
  # for script
74
89
 
@@ -0,0 +1,29 @@
1
+ module Bitcoin
2
+ module Ext
3
+ module ObjectExt
4
+ refine Object do
5
+ def build_json
6
+ if self.is_a?(Array)
7
+ "[#{self.map{|o|o.to_h.to_json}.join(',')}]"
8
+ else
9
+ to_h.to_json
10
+ end
11
+ end
12
+
13
+ def to_h
14
+ return self if self.is_a?(String)
15
+ instance_variables.inject({}) do |result, var|
16
+ key = var.to_s
17
+ key.slice!(0) if key.start_with?('@')
18
+ value = instance_variable_get(var)
19
+ if value.is_a?(Array)
20
+ result.update(key => value.map{|v|v.to_h})
21
+ else
22
+ result.update(key => value.class.to_s.start_with?("Bitcoin::") ? value.to_h : value)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
data/lib/bitcoin/ext.rb CHANGED
@@ -2,5 +2,6 @@ module Bitcoin
2
2
  module Ext
3
3
  autoload :JsonParser, 'bitcoin/ext/json_parser'
4
4
  autoload :ArrayExt, 'bitcoin/ext/array_ext'
5
+ autoload :ObjectExt, 'bitcoin/ext/object_ext'
5
6
  end
6
7
  end
data/lib/bitcoin/key.rb CHANGED
@@ -30,7 +30,6 @@ module Bitcoin
30
30
  # @param [Boolean] compressed [Deprecated] whether public key is compressed.
31
31
  # @return [Bitcoin::Key] a key object.
32
32
  def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false)
33
- puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
34
33
  if key_type
35
34
  @key_type = key_type
36
35
  compressed = @key_type != TYPES[:uncompressed]
@@ -213,7 +212,6 @@ module Bitcoin
213
212
  # get xonly public key (32 bytes).
214
213
  # @return [String] xonly public key with hex format
215
214
  def xonly_pubkey
216
- puts "Derive a public key whose y-coordinate is different from this public key." if compressed? && pubkey[0...2] != '02'
217
215
  pubkey[2..65]
218
216
  end
219
217
 
@@ -4,6 +4,8 @@ module Bitcoin
4
4
  # P2P message handler used by peer connection class.
5
5
  module MessageHandler
6
6
 
7
+ using Bitcoin::Ext::ObjectExt
8
+
7
9
  # handle p2p message.
8
10
  def handle(message)
9
11
  peer.last_recv = Time.now.to_i
@@ -14,6 +14,11 @@ module Bitcoin
14
14
  attr_accessor :hd_key_paths
15
15
  attr_accessor :partial_sigs
16
16
  attr_accessor :sighash_type
17
+ attr_accessor :ripemd160_preimages
18
+ attr_accessor :sha256_preimages
19
+ attr_accessor :hash160_preimages
20
+ attr_accessor :hash256_preimages
21
+ attr_accessor :proprietaries
17
22
  attr_accessor :unknowns
18
23
 
19
24
  def initialize(non_witness_utxo: nil, witness_utxo: nil)
@@ -21,6 +26,11 @@ module Bitcoin
21
26
  @witness_utxo = witness_utxo
22
27
  @partial_sigs = {}
23
28
  @hd_key_paths = {}
29
+ @ripemd160_preimages = {}
30
+ @sha256_preimages = {}
31
+ @hash160_preimages = {}
32
+ @hash256_preimages = {}
33
+ @proprietaries = []
24
34
  @unknowns = {}
25
35
  end
26
36
 
@@ -59,7 +69,7 @@ module Bitcoin
59
69
  input.partial_sigs[pubkey.pubkey] = value
60
70
  when PSBT_IN_TYPES[:sighash]
61
71
  raise ArgumentError, 'Invalid input sighash type typed key.' unless key_len == 1
62
- raise ArgumentError 'Duplicate Key, input sighash type already provided.' if input.sighash_type
72
+ raise ArgumentError, 'Duplicate Key, input sighash type already provided.' if input.sighash_type
63
73
  input.sighash_type = value.unpack1('I')
64
74
  when PSBT_IN_TYPES[:redeem_script]
65
75
  raise ArgumentError, 'Invalid redeemscript typed key.' unless key_len == 1
@@ -81,6 +91,25 @@ module Bitcoin
81
91
  raise ArgumentError, 'Invalid final script witness typed key.' unless key_len == 1
82
92
  raise ArgumentError, 'Duplicate Key, input final scriptWitness already provided.' if input.final_script_witness
83
93
  input.final_script_witness = Bitcoin::ScriptWitness.parse_from_payload(value)
94
+ when PSBT_IN_TYPES[:ripemd160]
95
+ raise ArgumentError, 'Size of key was not the expected size for the type ripemd160 preimage' unless key.bytesize == RIPEMD160_SIZE
96
+ raise ArgumentError, 'Duplicate Key, input ripemd160 preimage already provided' if input.ripemd160_preimages[key.bth]
97
+ input.ripemd160_preimages[key.bth] = value.bth
98
+ when PSBT_IN_TYPES[:sha256]
99
+ raise ArgumentError, 'Size of key was not the expected size for the type sha256 preimage' unless key.bytesize == SHA256_SIZE
100
+ raise ArgumentError, 'Duplicate Key, input sha256 preimage already provided' if input.sha256_preimages[key.bth]
101
+ input.sha256_preimages[key.bth] = value.bth
102
+ when PSBT_IN_TYPES[:hash160]
103
+ raise ArgumentError, 'Size of key was not the expected size for the type hash160 preimage' unless key.bytesize == HASH160_SIZE
104
+ raise ArgumentError, 'Duplicate Key, input hash160 preimage already provided' if input.hash160_preimages[key.bth]
105
+ input.hash160_preimages[key.bth] = value.bth
106
+ when PSBT_IN_TYPES[:hash256]
107
+ raise ArgumentError, 'Size of key was not the expected size for the type hash256 preimage' unless key.bytesize == HASH256_SIZE
108
+ raise ArgumentError, 'Duplicate Key, input hash256 preimage already provided' if input.hash256_preimages[key.bth]
109
+ input.hash256_preimages[key.bth] = value.bth
110
+ when PSBT_IN_TYPES[:proprietary]
111
+ raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if input.proprietaries.any?{|p| p.key == key}
112
+ input.proprietaries << Proprietary.new(key, value)
84
113
  else
85
114
  unknown_key = ([key_type].pack('C') + key).bth
86
115
  raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if input.unknowns[unknown_key]
@@ -102,9 +131,14 @@ module Bitcoin
102
131
  payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:redeem_script], value: redeem_script.to_payload) if redeem_script
103
132
  payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:witness_script], value: witness_script.to_payload) if witness_script
104
133
  payload << hd_key_paths.values.map(&:to_payload).join
134
+ payload << ripemd160_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:ripemd160], key: k.htb, value: v.htb)}.join
135
+ payload << sha256_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:sha256], key: k.htb, value: v.htb)}.join
136
+ payload << hash160_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:hash160], key: k.htb, value: v.htb)}.join
137
+ payload << hash256_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:hash256], key: k.htb, value: v.htb)}.join
105
138
  end
106
139
  payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_sig], value: final_script_sig.to_payload) if final_script_sig
107
140
  payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_witness], value: final_script_witness.to_payload) if final_script_witness
141
+ payload << proprietaries.map(&:to_payload).join
108
142
  payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join
109
143
  payload << PSBT_SEPARATOR.itb
110
144
  payload
@@ -7,10 +7,12 @@ module Bitcoin
7
7
  attr_accessor :redeem_script
8
8
  attr_accessor :witness_script
9
9
  attr_accessor :hd_key_paths
10
+ attr_accessor :proprietaries
10
11
  attr_accessor :unknowns
11
12
 
12
13
  def initialize
13
14
  @hd_key_paths = {}
15
+ @proprietaries = []
14
16
  @unknowns = {}
15
17
  end
16
18
 
@@ -41,6 +43,9 @@ module Bitcoin
41
43
  when PSBT_OUT_TYPES[:bip32_derivation]
42
44
  raise ArgumentError, 'Duplicate Key, pubkey derivation path already provided' if output.hd_key_paths[key.bth]
43
45
  output.hd_key_paths[key.bth] = Bitcoin::PSBT::HDKeyPath.new(key, Bitcoin::PSBT::KeyOriginInfo.parse_from_payload(value))
46
+ when PSBT_OUT_TYPES[:proprietary]
47
+ raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if output.proprietaries.any?{|p| p.key == key}
48
+ output.proprietaries << Proprietary.new(key, value)
44
49
  else
45
50
  unknown_key = ([key_type].pack('C') + key).bth
46
51
  raise ArgumentError, 'Duplicate Key, key for unknown value already provided' if output.unknowns[unknown_key]
@@ -56,6 +61,7 @@ module Bitcoin
56
61
  payload << PSBT.serialize_to_vector(PSBT_OUT_TYPES[:redeem_script], value: redeem_script) if redeem_script
57
62
  payload << PSBT.serialize_to_vector(PSBT_OUT_TYPES[:witness_script], value: witness_script) if witness_script
58
63
  payload << hd_key_paths.values.map{|v|v.to_payload(PSBT_OUT_TYPES[:bip32_derivation])}.join
64
+ payload << proprietaries.map(&:to_payload).join
59
65
  payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join
60
66
  payload << PSBT_SEPARATOR.itb
61
67
  payload
@@ -0,0 +1,44 @@
1
+ module Bitcoin
2
+ module PSBT
3
+ # Proprietary element of PSBT
4
+ # https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#Proprietary_Use_Type
5
+ class Proprietary
6
+ attr_accessor :identifier # binary format
7
+ attr_accessor :sub_type # integer
8
+ attr_accessor :value # binary format
9
+
10
+ # @param [String] key key with binary format without key type(0xfc).
11
+ # @param [String] value value with binary format.
12
+ def initialize(key, value)
13
+ buf = StringIO.new(key)
14
+ id_len = Bitcoin.unpack_var_int_from_io(buf)
15
+ @identifier = buf.read(id_len)
16
+ @sub_type = Bitcoin.unpack_var_int_from_io(buf)
17
+ @value = value
18
+ end
19
+
20
+ # Show contents
21
+ # @return [String]
22
+ def to_s
23
+ "identifier: #{identifier&.bth}, sub type: #{sub_type}, value: #{value&.bth}"
24
+ end
25
+
26
+ # Get key data with key type(0xfc).
27
+ # @return [String] key data with binary format.
28
+ def key
29
+ k = [PSBT_GLOBAL_TYPES[:proprietary]].pack('C')
30
+ k << Bitcoin.pack_var_int(identifier ? identifier.bytesize : 0)
31
+ k << identifier if identifier
32
+ k << Bitcoin.pack_var_int(sub_type)
33
+ k
34
+ end
35
+
36
+ # Convert to payload
37
+ # @return [String] payload with binary format.
38
+ def to_payload
39
+ k = key
40
+ Bitcoin.pack_var_int(k.bytesize) + k + Bitcoin.pack_var_int(value.bytesize) + value
41
+ end
42
+ end
43
+ end
44
+ end
@@ -32,6 +32,7 @@ module Bitcoin
32
32
  attr_accessor :xpubs
33
33
  attr_reader :inputs
34
34
  attr_reader :outputs
35
+ attr_accessor :proprietaries
35
36
  attr_accessor :unknowns
36
37
  attr_accessor :version_number
37
38
 
@@ -40,6 +41,7 @@ module Bitcoin
40
41
  @xpubs = []
41
42
  @inputs = tx ? tx.in.map{Input.new}: []
42
43
  @outputs = tx ? tx.out.map{Output.new}: []
44
+ @proprietaries = []
43
45
  @unknowns = {}
44
46
  end
45
47
 
@@ -66,7 +68,7 @@ module Bitcoin
66
68
  found_sep = true
67
69
  break
68
70
  end
69
- key_type = buf.read(1).unpack1('C')
71
+ key_type = Bitcoin.unpack_var_int_from_io(buf)
70
72
  key = buf.read(key_len - 1)
71
73
  value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
72
74
 
@@ -89,6 +91,9 @@ module Bitcoin
89
91
  when PSBT_GLOBAL_TYPES[:ver]
90
92
  partial_tx.version_number = value.unpack1('V')
91
93
  raise ArgumentError, "An unsupported version was detected." if SUPPORT_VERSION < partial_tx.version_number
94
+ when PSBT_GLOBAL_TYPES[:proprietary]
95
+ raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if partial_tx.proprietaries.any?{|p| p.key == key}
96
+ partial_tx.proprietaries << Proprietary.new(key, value)
92
97
  else
93
98
  raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if partial_tx.unknowns[key]
94
99
  partial_tx.unknowns[([key_type].pack('C') + key).bth] = value
@@ -148,6 +153,7 @@ module Bitcoin
148
153
  payload << PSBT.serialize_to_vector(PSBT_GLOBAL_TYPES[:unsigned_tx], value: tx.to_payload)
149
154
  payload << xpubs.map(&:to_payload).join
150
155
  payload << PSBT.serialize_to_vector(PSBT_GLOBAL_TYPES[:ver], value: [version_number].pack('V')) if version_number
156
+ payload << proprietaries.map(&:to_payload).join
151
157
  payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join
152
158
 
153
159
  payload << PSBT_SEPARATOR.itb
@@ -162,6 +168,15 @@ module Bitcoin
162
168
  Base64.strict_encode64(to_payload)
163
169
  end
164
170
 
171
+ # Store the PSBT to a file.
172
+ # @param [String] path File path to store.
173
+ def to_file(path)
174
+ raise ArgumentError, 'The file already exists' if File.exist?(path)
175
+ File.open(path, 'w') do |f|
176
+ f.write(to_payload)
177
+ end
178
+ end
179
+
165
180
  # update input key-value maps.
166
181
  # @param [Bitcoin::Tx] prev_tx previous tx reference by input.
167
182
  # @param [Bitcoin::Script] redeem_script redeem script to set input.
data/lib/bitcoin/psbt.rb CHANGED
@@ -9,14 +9,38 @@ module Bitcoin
9
9
  autoload :Output, 'bitcoin/psbt/output'
10
10
  autoload :KeyOriginInfo, 'bitcoin/psbt/key_origin_info'
11
11
  autoload :HDKeyPath, 'bitcoin/psbt/hd_key_path'
12
+ autoload :Proprietary, 'bitcoin/psbt/proprietary'
12
13
 
13
14
  # constants for PSBT
14
15
  PSBT_MAGIC_BYTES = 0x70736274
15
- PSBT_GLOBAL_TYPES = {unsigned_tx: 0x00, xpub: 0x01, ver: 0xfb}
16
- PSBT_IN_TYPES = {non_witness_utxo: 0x00, witness_utxo: 0x01, partial_sig: 0x02,
17
- sighash: 0x03, redeem_script: 0x04, witness_script: 0x05,
18
- bip32_derivation: 0x06, script_sig: 0x07, script_witness: 0x08}
19
- PSBT_OUT_TYPES = {redeem_script: 0x00, witness_script: 0x01, bip32_derivation: 0x02}
16
+ PSBT_GLOBAL_TYPES = {
17
+ unsigned_tx: 0x00,
18
+ xpub: 0x01,
19
+ ver: 0xfb,
20
+ proprietary: 0xfc
21
+ }
22
+ PSBT_IN_TYPES = {
23
+ non_witness_utxo: 0x00,
24
+ witness_utxo: 0x01,
25
+ partial_sig: 0x02,
26
+ sighash: 0x03,
27
+ redeem_script: 0x04,
28
+ witness_script: 0x05,
29
+ bip32_derivation: 0x06,
30
+ script_sig: 0x07,
31
+ script_witness: 0x08,
32
+ ripemd160: 0x0a,
33
+ sha256: 0x0b,
34
+ hash160: 0x0c,
35
+ hash256: 0x0d,
36
+ proprietary: 0xfc
37
+ }
38
+ PSBT_OUT_TYPES = {
39
+ redeem_script: 0x00,
40
+ witness_script: 0x01,
41
+ bip32_derivation: 0x02,
42
+ proprietary: 0xfc
43
+ }
20
44
  PSBT_SEPARATOR = 0x00
21
45
 
22
46
  SUPPORT_VERSION = 0
@@ -31,6 +55,14 @@ module Bitcoin
31
55
  s << Bitcoin.pack_var_int(value.bytesize) << value
32
56
  s
33
57
  end
58
+
59
+ # Load PSBT from file.
60
+ # @param [String] path File path of PSBT.
61
+ # @return [Bitcoin::PSBT::Tx] PSBT object.
62
+ def load_from_file(path)
63
+ raise ArgumentError, 'File not found' unless File.exist?(path)
64
+ Bitcoin::PSBT::Tx.parse_from_payload(File.read(path))
65
+ end
34
66
  end
35
67
 
36
68
  end
@@ -551,7 +551,7 @@ module Bitcoin
551
551
  def p2pkh_addr
552
552
  return nil unless p2pkh?
553
553
  hash160 = chunks[2].pushed_data.bth
554
- return nil unless hash160.htb.bytesize == 20
554
+ return nil unless hash160.htb.bytesize == RIPEMD160_SIZE
555
555
  Bitcoin.encode_base58_address(hash160, Bitcoin.chain_params.address_version)
556
556
  end
557
557
 
@@ -564,7 +564,7 @@ module Bitcoin
564
564
  def p2sh_addr
565
565
  return nil unless p2sh?
566
566
  hash160 = chunks[1].pushed_data.bth
567
- return nil unless hash160.htb.bytesize == 20
567
+ return nil unless hash160.htb.bytesize == RIPEMD160_SIZE
568
568
  Bitcoin.encode_base58_address(hash160, Bitcoin.chain_params.p2sh_version)
569
569
  end
570
570
 
@@ -163,9 +163,9 @@ module Bitcoin
163
163
  end
164
164
  return set_error(SCRIPT_ERR_STACK_SIZE) if stack.size > MAX_STACK_SIZE
165
165
  need_evaluate = true
166
+ elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION)
167
+ return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION)
166
168
  end
167
-
168
- return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION)
169
169
  return true unless need_evaluate
170
170
  end
171
171
  elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
@@ -62,6 +62,7 @@ module Bitcoin
62
62
 
63
63
  def generate(tx, input_index, hash_type, opts)
64
64
  amount = opts[:amount]
65
+ raise ArgumentError, 'segwit sighash requires amount.' unless amount
65
66
  output_script = opts[:script_code]
66
67
  skip_separator_index = opts[:skip_separator_index]
67
68
  hash_prevouts = Bitcoin.double_sha256(tx.inputs.map{|i|i.out_point.to_payload}.join)
@@ -70,7 +71,9 @@ module Bitcoin
70
71
  amount = [amount].pack('Q')
71
72
  nsequence = [tx.inputs[input_index].sequence].pack('V')
72
73
  hash_outputs = Bitcoin.double_sha256(tx.outputs.map{|o|o.to_payload}.join)
73
-
74
+ if output_script.p2wsh?
75
+ warn('The output_script must be a witness script, not the P2WSH itself.')
76
+ end
74
77
  script_code = output_script.to_script_code(skip_separator_index)
75
78
 
76
79
  case (hash_type & 0x1f)
@@ -7,7 +7,7 @@ module Bitcoin
7
7
  # Initialize
8
8
  # @param [Bitcoin::Script] script Locking script
9
9
  # @param [Integer] leaf_ver The leaf version of this script.
10
- def initialize(script, leaf_ver)
10
+ def initialize(script, leaf_ver = Bitcoin::TAPROOT_LEAF_TAPSCRIPT)
11
11
  raise Taproot::Error, 'script must be Bitcoin::Script object' unless script.is_a?(Bitcoin::Script)
12
12
  @script = script
13
13
  @leaf_ver = leaf_ver
@@ -1,34 +1,50 @@
1
1
  module Bitcoin
2
2
  module Taproot
3
3
 
4
- # Utility class to construct Taproot outputs from internal key and script tree.
4
+ # Utility class to construct Taproot outputs from internal key and script tree.keyPathSpending
5
5
  # SimpleBuilder builds a script tree that places all lock scripts, in the order they are added, as leaf nodes.
6
6
  # It is not possible to specify the depth of the locking script or to insert any intermediate nodes.
7
7
  class SimpleBuilder
8
8
  include Bitcoin::Opcodes
9
9
 
10
10
  attr_reader :internal_key # String with hex format
11
- attr_reader :leaves # Array[LeafNode]
11
+ attr_reader :branches # List of branch that has two child leaves
12
12
 
13
13
  # Initialize builder.
14
14
  # @param [String] internal_key Internal public key with hex format.
15
- # @param [Array[Bitcoin::Script]] scripts Scripts for each lock condition.
16
- # @param [Integer] leaf_ver (Optional) The leaf version of tapscript.
17
- # @raise [Bitcoin::Taproot::Builder] +internal_pubkey+ dose not xonly public key or script in +scripts+ does not instance of Bitcoin::Script.
15
+ # @param [Array[Bitcoin::Taproot::LeafNode]] leaves (Optional) Array of leaf nodes for each lock condition.
16
+ # @raise [Bitcoin::Taproot::Builder] +internal_pubkey+ dose not xonly public key or leaf in +leaves+ does not instance of Bitcoin::Taproot::LeafNode.
18
17
  # @return [Bitcoin::Taproot::SimpleBuilder]
19
- def initialize(internal_key, *scripts, leaf_ver: Bitcoin::TAPROOT_LEAF_TAPSCRIPT)
18
+ def initialize(internal_key, leaves = [])
20
19
  raise Error, 'Internal public key must be 32 bytes' unless internal_key.htb.bytesize == 32
21
- @leaves = scripts.map { |script| LeafNode.new(script, leaf_ver) }
20
+ raise Error, 'leaf must be Bitcoin::Taproot::LeafNode object' if leaves.find{ |leaf| !leaf.is_a?(Bitcoin::Taproot::LeafNode)}
21
+
22
+ @leaves = leaves
23
+ @branches = leaves.each_slice(2).map.to_a
22
24
  @internal_key = internal_key
23
25
  end
24
26
 
25
- # Add lock script to leaf node.
26
- # @param [Bitcoin::Script] script lock script.
27
- # @param [Integer] leaf_ver (Optional) The leaf version of tapscript.
28
- # @raise [Bitcoin::Taproot::Builder] If +script+ does not instance of Bitcoin::Script.
29
- # @return [Bitcoin::Taproot::SimpleBuilder] self
30
- def <<(script, leaf_ver: Bitcoin::TAPROOT_LEAF_TAPSCRIPT)
31
- leaves << LeafNode.new(script, leaf_ver)
27
+ # Add a leaf node to the end of the current branch.
28
+ # @param [Bitcoin::Taproot::LeafNode] leaf Leaf node to be added.
29
+ def add_leaf(leaf)
30
+ raise Error, 'leaf must be Bitcoin::Taproot::LeafNode object' unless leaf.is_a?(Bitcoin::Taproot::LeafNode)
31
+
32
+ if branches.last&.size == 1
33
+ branches.last << leaf
34
+ else
35
+ branches << [leaf]
36
+ end
37
+ self
38
+ end
39
+
40
+ # Add a pair of leaf nodes as a branch. If there is only one, add a branch with only one child.
41
+ # @param [Bitcoin::Taproot::LeafNode] leaf1 Leaf node to be added.
42
+ # @param [Bitcoin::Taproot::LeafNode] leaf2 Leaf node to be added.
43
+ def add_branch(leaf1, leaf2 = nil)
44
+ raise Error, 'leaf1 must be Bitcoin::Taproot::LeafNode object' unless leaf1.is_a?(Bitcoin::Taproot::LeafNode)
45
+ raise Error, 'leaf2 must be Bitcoin::Taproot::LeafNode object' if leaf2 && !leaf2.is_a?(Bitcoin::Taproot::LeafNode)
46
+
47
+ branches << (leaf2.nil? ? [leaf1] : [leaf1, leaf2])
32
48
  self
33
49
  end
34
50
 
@@ -42,8 +58,7 @@ module Bitcoin
42
58
  # Compute the tweaked public key.
43
59
  # @return [Bitcoin::Key] the tweaked public key
44
60
  def tweak_public_key
45
- key = Bitcoin::Key.new(priv_key: tweak.bth, key_type: Key::TYPES[:compressed])
46
- Bitcoin::Key.from_point(key.to_point + Bitcoin::Key.from_xonly_pubkey(internal_key).to_point)
61
+ Taproot.tweak_public_key(Bitcoin::Key.from_xonly_pubkey(internal_key), merkle_root)
47
62
  end
48
63
 
49
64
  # Compute the secret key for a tweaked public key.
@@ -51,29 +66,33 @@ module Bitcoin
51
66
  # @return [Bitcoin::Key] secret key for a tweaked public key
52
67
  def tweak_private_key(key)
53
68
  raise Error, 'Requires private key' unless key.priv_key
54
- p = key.to_point
55
- private_key = p.has_even_y? ? key.priv_key.to_i(16) : ECDSA::Group::Secp256k1.order - key.priv_key.to_i(16)
56
- Bitcoin::Key.new(priv_key: ((tweak.bti + private_key) % ECDSA::Group::Secp256k1.order).to_even_length_hex)
69
+
70
+ Taproot.tweak_private_key(key, merkle_root)
57
71
  end
58
72
 
59
73
  # Generate control block needed to unlock with script-path.
60
- # @param [Bitcoin::Script] script Script to use for unlocking.
61
- # @param [Integer] leaf_ver leaf version of script.
74
+ # @param [Bitcoin::Taproot::LeafNode] leaf Leaf to use for unlocking.
62
75
  # @return [String] control block with binary format.
63
- def control_block(script, leaf_ver: Bitcoin::TAPROOT_LEAF_TAPSCRIPT)
64
- path = inclusion_proof(script, leaf_ver: leaf_ver)
76
+ def control_block(leaf)
77
+ path = inclusion_proof(leaf)
65
78
  parity = tweak_public_key.to_point.has_even_y? ? 0 : 1
66
- [parity + leaf_ver].pack("C") + internal_key.htb + path.join
79
+ [parity + leaf.leaf_ver].pack("C") + internal_key.htb + path.join
67
80
  end
68
81
 
69
- # Generate inclusion proof for +script+.
70
- # @param [Bitcoin::Script] script The script in script tree.
71
- # @param [Integer] leaf_ver (Optional) The leaf version of tapscript.
82
+ # Generate inclusion proof for +leaf+.
83
+ # @param [Bitcoin::Taproot::LeafNode] leaf The leaf node in script tree.
72
84
  # @return [Array[String]] Inclusion proof.
73
- def inclusion_proof(script, leaf_ver: Bitcoin::TAPROOT_LEAF_TAPSCRIPT)
74
- parents = leaves
75
- parent_hash = leaf_hash(script, leaf_ver: leaf_ver)
85
+ # @raise [Bitcoin::Taproot::Error] If the specified +leaf+ does not exist
86
+ def inclusion_proof(leaf)
76
87
  proofs = []
88
+ target_branch = branches.find{|b| b.include?(leaf)}
89
+ raise Error 'Specified leaf does not exist' unless target_branch
90
+
91
+ # flatten each branch
92
+ proofs << hash_value(target_branch.find{|b| b != leaf}) if target_branch.size == 2
93
+ parent_hash = combine_hash(target_branch)
94
+ parents = branches.map {|pair| combine_hash(pair)}
95
+
77
96
  until parents.size == 1
78
97
  parents = parents.each_slice(2).map do |pair|
79
98
  combined = combine_hash(pair)
@@ -92,29 +111,26 @@ module Bitcoin
92
111
  proofs
93
112
  end
94
113
 
95
- # Computes leaf hash
96
- # @param [Bitcoin::Script] script
97
- # @param [Integer] leaf_ver leaf version
98
- # @@return [String] leaf hash with binary format.
99
- def leaf_hash(script, leaf_ver: Bitcoin::TAPROOT_LEAF_TAPSCRIPT)
100
- raise Error, 'script does not exist' unless leaves.find{ |leaf| leaf.script == script}
101
- LeafNode.new(script, leaf_ver).leaf_hash
102
- end
103
-
104
114
  private
105
115
 
106
116
  # Compute tweak from script tree.
107
117
  # @return [String] tweak with binary format.
108
118
  def tweak
109
- parents = leaves
119
+ Taproot.tweak(Bitcoin::Key.from_xonly_pubkey(internal_key), merkle_root)
120
+ end
121
+
122
+ # Calculate merkle root from branches.
123
+ # @return [String] merkle root with hex format.
124
+ def merkle_root
125
+ parents = branches.map {|pair| combine_hash(pair)}
110
126
  if parents.empty?
111
127
  parents = ['']
128
+ elsif parents.size == 1
129
+ parents = [combine_hash(parents)]
112
130
  else
113
131
  parents = parents.each_slice(2).map { |pair| combine_hash(pair) } until parents.size == 1
114
132
  end
115
- t = Bitcoin.tagged_hash('TapTweak', internal_key.htb + parents.first)
116
- raise Error, 'tweak value exceeds the curve order' if t.bti >= ECDSA::Group::Secp256k1.order
117
- t
133
+ parents.first.bth
118
134
  end
119
135
 
120
136
  def combine_hash(pair)
@@ -5,5 +5,41 @@ module Bitcoin
5
5
 
6
6
  autoload :LeafNode, 'bitcoin/taproot/leaf_node'
7
7
  autoload :SimpleBuilder, 'bitcoin/taproot/simple_builder'
8
+
9
+ module_function
10
+
11
+ # Calculate tweak value from +internal_pubkey+ and +merkle_root+.
12
+ # @param [Bitcoin::Key] internal_key Internal key with hex format(x-only public key).
13
+ # @param [String] merkle_root Merkle root value of script tree with hex format.
14
+ # @return [String] teak value with binary format.
15
+ def tweak(internal_key, merkle_root)
16
+ raise Error, 'internal_key must be Bitcoin::Key object.' unless internal_key.is_a?(Bitcoin::Key)
17
+
18
+ merkle_root ||= ''
19
+ t = Bitcoin.tagged_hash('TapTweak', internal_key.xonly_pubkey.htb + merkle_root.htb)
20
+ raise Error, 'tweak value exceeds the curve order' if t.bti >= ECDSA::Group::Secp256k1.order
21
+
22
+ t
23
+ end
24
+
25
+ # Generate tweak public key form +internal_pubkey+ and +merkle_root+.
26
+ # @param [Bitcoin::Key] internal_key Internal key with hex format(x-only public key).
27
+ # @param [String] merkle_root Merkle root value of script tree with hex format.
28
+ # @return [Bitcoin::Key] Tweaked public key.
29
+ def tweak_public_key(internal_key, merkle_root)
30
+ t = tweak(internal_key, merkle_root)
31
+ key = Bitcoin::Key.new(priv_key: t.bth, key_type: Key::TYPES[:compressed])
32
+ Bitcoin::Key.from_point(key.to_point + internal_key.to_point)
33
+ end
34
+
35
+ # Generate tweak private key
36
+ #
37
+ def tweak_private_key(internal_private_key, merkle_root)
38
+ p = internal_private_key.to_point
39
+ private_key = p.has_even_y? ? internal_private_key.priv_key.to_i(16) :
40
+ ECDSA::Group::Secp256k1.order - internal_private_key.priv_key.to_i(16)
41
+ t = tweak(internal_private_key, merkle_root)
42
+ Bitcoin::Key.new(priv_key: ((t.bti + private_key) % ECDSA::Group::Secp256k1.order).to_even_length_hex)
43
+ end
8
44
  end
9
45
  end
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.8.0"
2
+ VERSION = "1.1.0"
3
3
  end
data/lib/bitcoin.rb CHANGED
@@ -194,32 +194,6 @@ module Bitcoin
194
194
 
195
195
  end
196
196
 
197
- class ::Object
198
-
199
- def build_json
200
- if self.is_a?(Array)
201
- "[#{self.map{|o|o.to_h.to_json}.join(',')}]"
202
- else
203
- to_h.to_json
204
- end
205
- end
206
-
207
- def to_h
208
- return self if self.is_a?(String)
209
- instance_variables.inject({}) do |result, var|
210
- key = var.to_s
211
- key.slice!(0) if key.start_with?('@')
212
- value = instance_variable_get(var)
213
- if value.is_a?(Array)
214
- result.update(key => value.map{|v|v.to_h})
215
- else
216
- result.update(key => value.class.to_s.start_with?("Bitcoin::") ? value.to_h : value)
217
- end
218
- end
219
- end
220
-
221
- end
222
-
223
197
  class ::Integer
224
198
  def to_even_length_hex
225
199
  hex = to_s(16)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitcoinrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-06-29 00:00:00.000000000 Z
11
+ date: 2022-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ecdsa
@@ -364,6 +364,7 @@ files:
364
364
  - lib/bitcoin/ext/array_ext.rb
365
365
  - lib/bitcoin/ext/ecdsa.rb
366
366
  - lib/bitcoin/ext/json_parser.rb
367
+ - lib/bitcoin/ext/object_ext.rb
367
368
  - lib/bitcoin/ext_key.rb
368
369
  - lib/bitcoin/gcs_filter.rb
369
370
  - lib/bitcoin/key.rb
@@ -450,6 +451,7 @@ files:
450
451
  - lib/bitcoin/psbt/input.rb
451
452
  - lib/bitcoin/psbt/key_origin_info.rb
452
453
  - lib/bitcoin/psbt/output.rb
454
+ - lib/bitcoin/psbt/proprietary.rb
453
455
  - lib/bitcoin/psbt/tx.rb
454
456
  - lib/bitcoin/rpc.rb
455
457
  - lib/bitcoin/rpc/bitcoin_core_client.rb
@@ -514,7 +516,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
514
516
  - !ruby/object:Gem::Version
515
517
  version: '0'
516
518
  requirements: []
517
- rubygems_version: 3.2.3
519
+ rubygems_version: 3.1.4
518
520
  signing_key:
519
521
  specification_version: 4
520
522
  summary: The implementation of Bitcoin Protocol for Ruby.