def derived_keychain(index_or_path, hardened: nil)

      if index_or_path.is_a?(String)
        if hardened != nil
          raise ArgumentError, "Ambiguous use of `hardened` flag when deriving keychain with a string path"
        end
        return derived_keychain_with_path(index_or_path)
      end

      index = index_or_path

      raise ArgumentError, "Index must not be nil" if !index

      # As we use explicit "hardened" argument, do not allow higher bit set.
      if index < 0 || index > 0x7fffffff || (0x80000000 & index) != 0
        raise ArgumentError, "Index >= 0x80000000 is not valid. Use `hardened: true` argument instead."
      end

      if hardened && !@private_key
        # Not possible to derive hardened keychain without a private key.
        raise BTCError, "Not possible to derive a hardened keychain without a private key (index: #{index})."
      end

      private_key = nil
      public_key = nil
      chain_code = nil

      data = "".b

      if hardened
        data << "\x00" << @private_key
      else
        data << @public_key
      end

      data << BTC::WireFormat.encode_uint32be(hardened ? (0x80000000 | index) : index)

      digest = BTC.hmac_sha512(data: data, key: @chain_code)

      chain_code = digest[32,32]

      lib = BTC::OpenSSL
      lib.autorelease do |pool|

        factor = pool.new_bn(digest[0,32])
        n = lib.group_order

        if lib.BN_cmp(factor, n) >= 0
          raise BTCError, "Factor for index #{index} is greater than curve order."
        end

        if @private_key
          pk = pool.new_bn(@private_key)
          lib.BN_mod_add_quick(pk, pk, factor, n) # pk = (pk + factor) % n

          # Check for invalid derivation.

          if lib.BN_cmp(pk, pool.new_bn("\x00")) == 0
            raise BTCError, "Private key is zero for index #{index}."
          end

          private_key = lib.data_from_bn(pk, min_length: 32)
        else

          # Convert pubkey to a EC point
          pubkey_x = pool.new_bn(@public_key)
          pubkey_point = pool.new_ec_point
          lib.EC_POINT_bn2point(lib.group, pubkey_x, pubkey_point, pool.bn_ctx)

          # Compute point = pubkey + factor*G
          point = pool.new_ec_point
          # /** Computes r = generator * n + q * m
          #  *  \param  group  underlying EC_GROUP object
          #  *  \param  r      EC_POINT object for the result
          #  *  \param  n      BIGNUM with the multiplier for the group generator (optional)
          #  *  \param  q      EC_POINT object with the first factor of the second summand
          #  *  \param  m      BIGNUM with the second factor of the second summand
          #  *  \param  ctx    BN_CTX object (optional)
          #  *  \return 1 on success and 0 if an error occured
          #  */
          # int EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx);
          lib.EC_POINT_mul(lib.group, point, factor, pubkey_point, pool.new_bn("\x01"), pool.bn_ctx)

          # Check for invalid derivation.
          if 1 == lib.EC_POINT_is_at_infinity(lib.group, point)
            raise BTCError, "Resulting point is at infinity for index #{index}."
          end

          lib.EC_POINT_point2bn(lib.group, point, BTC::OpenSSL::POINT_CONVERSION_COMPRESSED, pubkey_x, pool.bn_ctx)

          public_key = lib.data_from_bn(pubkey_x, required_length: 33)
        end
      end

      self.class.new(_components: [
                     private_key,
                     public_key,
                     chain_code,
                     nil,
                     self.fingerprint,
                     index,
                     @depth + 1,
                     !!hardened,
                     @network])
    end
def derived_keychain(index_or_path, hardened: nil)

      if index_or_path.is_a?(String)
        if hardened != nil
          raise ArgumentError, "Ambiguous use of `hardened` flag when deriving keychain with a string path"
        end
        return derived_keychain_with_path(index_or_path)
      end

      index = index_or_path

      raise ArgumentError, "Index must not be nil" if !index

      # As we use explicit "hardened" argument, do not allow higher bit set.
      if index < 0 || index > 0x7fffffff || (0x80000000 & index) != 0
        raise ArgumentError, "Index >= 0x80000000 is not valid. Use `hardened: true` argument instead."
      end

      if hardened && !@private_key
        # Not possible to derive hardened keychain without a private key.
        raise BTCError, "Not possible to derive a hardened keychain without a private key (index: #{index})."
      end

      private_key = nil
      public_key = nil
      chain_code = nil

      data = "".b

      if hardened
        data << "\x00" << @private_key
      else
        data << @public_key
      end

      data << BTC::WireFormat.encode_uint32be(hardened ? (0x80000000 | index) : index)

      digest = BTC.hmac_sha512(data: data, key: @chain_code)

      chain_code = digest[32,32]

      lib = BTC::OpenSSL
      lib.autorelease do |pool|

        factor = pool.new_bn(digest[0,32])
        n = lib.group_order

        if lib.BN_cmp(factor, n) >= 0
          raise BTCError, "Factor for index #{index} is greater than curve order."
        end

        if @private_key
          pk = pool.new_bn(@private_key)
          lib.BN_mod_add_quick(pk, pk, factor, n) # pk = (pk + factor) % n

          # Check for invalid derivation.

          if lib.BN_cmp(pk, pool.new_bn("\x00")) == 0
            raise BTCError, "Private key is zero for index #{index}."
          end

          private_key = lib.data_from_bn(pk, min_length: 32)
        else

          # Convert pubkey to a EC point
          pubkey_x = pool.new_bn(@public_key)
          pubkey_point = pool.new_ec_point
          lib.EC_POINT_bn2point(lib.group, pubkey_x, pubkey_point, pool.bn_ctx)

          # Compute point = pubkey + factor*G
          point = pool.new_ec_point
          # /** Computes r = generator * n + q * m
          #  *  \param  group  underlying EC_GROUP object
          #  *  \param  r      EC_POINT object for the result
          #  *  \param  n      BIGNUM with the multiplier for the group generator (optional)
          #  *  \param  q      EC_POINT object with the first factor of the second summand
          #  *  \param  m      BIGNUM with the second factor of the second summand
          #  *  \param  ctx    BN_CTX object (optional)
          #  *  \return 1 on success and 0 if an error occured
          #  */
          # int EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx);
          lib.EC_POINT_mul(lib.group, point, factor, pubkey_point, pool.new_bn("\x01"), pool.bn_ctx)

          # Check for invalid derivation.
          if 1 == lib.EC_POINT_is_at_infinity(lib.group, point)
            raise BTCError, "Resulting point is at infinity for index #{index}."
          end

          lib.EC_POINT_point2bn(lib.group, point, BTC::OpenSSL::POINT_CONVERSION_COMPRESSED, pubkey_x, pool.bn_ctx)

          public_key = lib.data_from_bn(pubkey_x, required_length: 33)
        end
      end

      self.class.new(_components: [
                     private_key,
                     public_key,
                     chain_code,
                     nil,
                     self.fingerprint,
                     index,
                     @depth + 1,
                     !!hardened,
                     @network])
    end
Returns a derived keychain at a given index.
 If hardened = true, uses hardened derivation (possible only when private
 key is present; otherwise returns nil).
 Index must be less of equal BTC::Keychain::MAX_INDEX, otherwise raises ArgumentError.
 Raises BTCError for some indexes (when hashing leads to invalid EC points)
 which is very rare (chance is below 2^-127), but must be expected.
 In such case, simply use next index.
 By default, a normal (non-hardened) derivation is used.