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 +4 -4
- data/.ruby-version +1 -1
- data/README.md +1 -1
- data/lib/bitcoin/constants.rb +31 -16
- data/lib/bitcoin/ext/object_ext.rb +29 -0
- data/lib/bitcoin/ext.rb +1 -0
- data/lib/bitcoin/key.rb +0 -2
- data/lib/bitcoin/network/message_handler.rb +2 -0
- data/lib/bitcoin/psbt/input.rb +35 -1
- data/lib/bitcoin/psbt/output.rb +6 -0
- data/lib/bitcoin/psbt/proprietary.rb +44 -0
- data/lib/bitcoin/psbt/tx.rb +16 -1
- data/lib/bitcoin/psbt.rb +37 -5
- data/lib/bitcoin/script/script.rb +2 -2
- data/lib/bitcoin/script/script_interpreter.rb +2 -2
- data/lib/bitcoin/sighash_generator.rb +4 -1
- data/lib/bitcoin/taproot/leaf_node.rb +1 -1
- data/lib/bitcoin/taproot/simple_builder.rb +59 -43
- data/lib/bitcoin/taproot.rb +36 -0
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin.rb +0 -26
- metadata +5 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: b6b5d0d0d17295086ec1cf989eb1df1be2d776bff6c5a38e85c34e21e258f56b
         | 
| 4 | 
            +
              data.tar.gz: c93206160d6709f0050cbe8a001df40f712a6e8a779a61117c8c0cc911d4fd2f
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: fa5759ab05a69bfdaadda07d5cbf74c700bba2586f2544eaf823199c357b88405814dcaed7c6466a1a94e50027d5289a51f057d86815699e92862cb447b2478f
         | 
| 7 | 
            +
              data.tar.gz: 8629b46d88f6674482bfaff89dffcf28e90edc857649aeaff56d579c12d0e4df99f9cc7cad63a31ecc1742afc64cfa089a7f86754be5614e27c145e0e693e1cb
         | 
    
        data/.ruby-version
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            ruby-3.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)  | 
| 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))  
         | 
    
        data/lib/bitcoin/constants.rb
    CHANGED
    
    | @@ -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 = [ | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
             | 
| 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
    
    
    
        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 |  | 
    
        data/lib/bitcoin/psbt/input.rb
    CHANGED
    
    | @@ -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
         | 
    
        data/lib/bitcoin/psbt/output.rb
    CHANGED
    
    | @@ -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
         | 
    
        data/lib/bitcoin/psbt/tx.rb
    CHANGED
    
    | @@ -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 =  | 
| 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 = { | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 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 ==  | 
| 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 ==  | 
| 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 : | 
| 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:: | 
| 16 | 
            -
                  # @ | 
| 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,  | 
| 18 | 
            +
                  def initialize(internal_key, leaves = [])
         | 
| 20 19 | 
             
                    raise Error, 'Internal public key must be 32 bytes' unless internal_key.htb.bytesize == 32
         | 
| 21 | 
            -
                     | 
| 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  | 
| 26 | 
            -
                  # @param [Bitcoin:: | 
| 27 | 
            -
                   | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 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 | 
            -
                     | 
| 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 | 
            -
             | 
| 55 | 
            -
                     | 
| 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:: | 
| 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( | 
| 64 | 
            -
                    path = inclusion_proof( | 
| 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 + | 
| 70 | 
            -
                  # @param [Bitcoin:: | 
| 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 | 
            -
                   | 
| 74 | 
            -
             | 
| 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 | 
            -
                     | 
| 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 | 
            -
                     | 
| 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)
         | 
    
        data/lib/bitcoin/taproot.rb
    CHANGED
    
    | @@ -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
         | 
    
        data/lib/bitcoin/version.rb
    CHANGED
    
    
    
        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:  | 
| 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:  | 
| 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. | 
| 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.
         |