#include <fc/crypto/elliptic.hpp>

#include <fc/crypto/base58.hpp>
#include <fc/crypto/hmac.hpp>
#include <fc/crypto/openssl.hpp>
#include <fc/crypto/sha512.hpp>

#include <fc/fwd_impl.hpp>
#include <fc/macros.hpp>
#include <fc/exception/exception.hpp>
#include <fc/log/logger.hpp>

#include <assert.h>
#include <secp256k1.h>
#include <secp256k1_recovery.h>
#include <secp256k1_rangeproof.h>

#if _WIN32
# include <malloc.h>
#else
# include <alloca.h>
#endif

#include "_elliptic_impl_priv.hpp"

namespace fc { namespace ecc {
    namespace detail
    {
        const secp256k1_context* _get_context() {
            static secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);
            return ctx;
        }

        void _init_lib() {
            static const secp256k1_context* ctx = _get_context();
            static int init_o = init_openssl();
            FC_UNUSED(ctx, init_o);
        }

        class public_key_impl
        {
            public:
                public_key_impl() BOOST_NOEXCEPT
                {
                    _init_lib();
                }

                public_key_impl( const public_key_impl& cpy ) BOOST_NOEXCEPT
                    : _key( cpy._key )
                {
                    _init_lib();
                }

                public_key_data _key;
        };

        typedef fc::array<char,37> chr37;
        chr37 _derive_message( const public_key_data& key, int i );
        fc::sha256 _left( const fc::sha512& v );
        fc::sha256 _right( const fc::sha512& v );
        const ec_group& get_curve();
        const private_key_secret& get_curve_order();
        const private_key_secret& get_half_curve_order();
    }

    static const public_key_data empty_pub;
    static const private_key_secret empty_priv;

    fc::sha512 private_key::get_shared_secret( const public_key& other )const
    {
      FC_ASSERT( my->_key != empty_priv );
      FC_ASSERT( other.my->_key != empty_pub );

      secp256k1_pubkey pubkey;
      FC_ASSERT(secp256k1_ec_pubkey_parse(detail::_get_context(), &pubkey, (const unsigned char*)other.my->_key.begin(), other.my->_key.size()));

      FC_ASSERT(secp256k1_ec_pubkey_tweak_mul(detail::_get_context(), &pubkey, (unsigned char*)my->_key.data()));

      public_key_data pub;
      size_t pk_len = pub.size();
      FC_ASSERT(secp256k1_ec_pubkey_serialize(detail::_get_context(), (unsigned char*)pub.begin(), &pk_len, &pubkey, SECP256K1_EC_COMPRESSED));

      return fc::sha512::hash(pub.begin() + 1, pub.size() - 1);
    }


    public_key::public_key() {}

    public_key::public_key( const public_key &pk ) : my( pk.my ) {}

    public_key::public_key( public_key &&pk ) : my( std::move( pk.my ) ) {}

    public_key::~public_key() {}

    public_key& public_key::operator=( const public_key& pk )
    {
        my = pk.my;
        return *this;
    }

    public_key& public_key::operator=( public_key&& pk )
    {
        my = pk.my;
        return *this;
    }

    bool public_key::valid()const
    {
      return my->_key != empty_pub;
    }

    public_key public_key::add( const fc::sha256& digest )const
    {
      FC_ASSERT(my->_key != empty_pub);

      secp256k1_pubkey pubkey;
      FC_ASSERT(secp256k1_ec_pubkey_parse(detail::_get_context(), &pubkey, (const unsigned char*)my->_key.begin(), my->_key.size()));

      FC_ASSERT(secp256k1_ec_pubkey_tweak_add(detail::_get_context(), &pubkey, (unsigned char*)digest.data()));

      public_key_data new_key;
      size_t pk_len = new_key.size();
      FC_ASSERT(secp256k1_ec_pubkey_serialize(detail::_get_context(), (unsigned char*)new_key.begin(), &pk_len, &pubkey, SECP256K1_EC_COMPRESSED));
      return public_key(new_key);
    }

    std::string public_key::to_base58() const
    {
        FC_ASSERT( my->_key != empty_pub );
        return to_base58( my->_key );
    }

    public_key_data public_key::serialize()const
    {
        FC_ASSERT( my->_key != empty_pub );
        return my->_key;
    }

    public_key_point_data public_key::serialize_ecc_point()const
    {
      FC_ASSERT(my->_key != empty_pub);

      secp256k1_pubkey pubkey;
      FC_ASSERT(secp256k1_ec_pubkey_parse(detail::_get_context(), &pubkey, (const unsigned char*)my->_key.begin(), my->_key.size()));

      public_key_point_data dat;
      size_t pk_len = dat.size();
      FC_ASSERT(secp256k1_ec_pubkey_serialize(detail::_get_context(), (unsigned char*)dat.begin(), &pk_len, &pubkey, SECP256K1_EC_UNCOMPRESSED));
      FC_ASSERT(pk_len == dat.size());
      return dat;
    }

    public_key::public_key( const public_key_point_data& dat )
    {
        const char* front = &dat.data[0];
        if( *front == 0 ){}
        else
        {
            EC_KEY *key = EC_KEY_new_by_curve_name( NID_secp256k1 );
            key = o2i_ECPublicKey( &key, (const unsigned char**)&front, sizeof(dat) );
            FC_ASSERT( key );
            EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED );
            unsigned char* buffer = (unsigned char*) my->_key.begin();
            i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling
            EC_KEY_free( key );
        }
    }

    public_key::public_key( const public_key_data& dat )
    {
        my->_key = dat;
    }

    public_key::public_key(const compact_signature& c, const fc::sha256& digest, canonical_signature_type canon_type)
    {
      int nV = c.data[0];
      if (nV < 27 || nV >= 35)
        FC_THROW_EXCEPTION(exception, "unable to reconstruct public key from signature");

      FC_ASSERT(is_canonical(c, canon_type), "signature is not canonical");

      secp256k1_ecdsa_recoverable_signature sig;
      FC_ASSERT(secp256k1_ecdsa_recoverable_signature_parse_compact(detail::_get_context(), &sig, (const unsigned char*)c.begin() + 1, (*c.begin() - 27) & 3));

      secp256k1_pubkey recovered_key;
      FC_ASSERT(secp256k1_ecdsa_recover(detail::_get_context(), &recovered_key, &sig, (unsigned char*)digest.data()));

      size_t pk_len = my->_key.size();
      FC_ASSERT(secp256k1_ec_pubkey_serialize(detail::_get_context(), (unsigned char*)my->_key.begin(), &pk_len, &recovered_key, SECP256K1_EC_COMPRESSED));
      FC_ASSERT(pk_len == my->_key.size());
    }

    extended_public_key::extended_public_key(const public_key& k, const fc::sha256& c, int child, int parent, uint8_t depth) :
      public_key(k), c(c), child_num(child), parent_fp(parent), depth(depth)
    {
    }

    extended_public_key extended_public_key::derive_normal_child(int i) const
    {
      public_key_data key = serialize();
      const detail::chr37 data = detail::_derive_message(key, i);
      fc::sha512 l = hmac_sha512().digest(c.data(), c.data_size(), data.begin(), data.size());
      fc::sha256 left = detail::_left(l);
      FC_ASSERT(left < detail::get_curve_order());

      secp256k1_pubkey pubkey;
      FC_ASSERT(secp256k1_ec_pubkey_parse(detail::_get_context(), &pubkey, (const unsigned char*)key.begin(), key.size()));

      FC_ASSERT(secp256k1_ec_pubkey_tweak_add(detail::_get_context(), &pubkey, (const unsigned char*)left.data()));

      size_t pk_len = key.size();
      FC_ASSERT(secp256k1_ec_pubkey_serialize(detail::_get_context(), (unsigned char*)key.begin(), &pk_len, &pubkey, SECP256K1_EC_COMPRESSED));

      // FIXME: check validity - if left + key == infinity then invalid
      extended_public_key result(key, detail::_right(l), i, fingerprint(), depth + 1);
      return result;
    }

    static void to_bignum(const unsigned char* in, ssl_bignum& out, unsigned int len)
    {
      if (*in & 0x80)
      {
        unsigned char* buffer = (unsigned char*)alloca(len + 1);
        *buffer = 0;
        memcpy(buffer + 1, in, len);
        BN_bin2bn(buffer, sizeof(buffer), out);
      }
      else
      {
        BN_bin2bn(in, len, out);
      }
    }

    static void to_bignum(const private_key_secret& in, ssl_bignum& out)
    {
      to_bignum((unsigned char*)in.data(), out, in.data_size());
    }

    static void from_bignum(const ssl_bignum& in, unsigned char* out, unsigned int len)
    {
      unsigned int l = BN_num_bytes(in);
      if (l > len)
      {
        unsigned char* buffer = (unsigned char*)alloca(l);
        BN_bn2bin(in, buffer);
        memcpy(out, buffer + l - len, len);
      }
      else
      {
        memset(out, 0, len - l);
        BN_bn2bin(in, out + len - l);
      }
    }

    static void from_bignum(const ssl_bignum& in, private_key_secret& out)
    { 
      from_bignum(in, (unsigned char*)out.data(), out.data_size());
    }

    static void invert( const private_key_secret& in, private_key_secret& out )
    {
        ssl_bignum bn_in;
        to_bignum( in, bn_in );
        ssl_bignum bn_n;
        to_bignum( detail::get_curve_order(), bn_n );
        ssl_bignum bn_inv;
        bn_ctx ctx( BN_CTX_new() );
        FC_ASSERT( BN_mod_inverse( bn_inv, bn_in, bn_n, ctx ) );
        from_bignum( bn_inv, out );
    }

    static void to_point( const public_key_data& in, ec_point& out )
    {
        bn_ctx ctx( BN_CTX_new() );
        const ec_group& curve = detail::get_curve();
        private_key_secret x;
        memcpy( x.data(), in.begin() + 1, x.data_size() );
        ssl_bignum bn_x;
        to_bignum( x, bn_x );
        FC_ASSERT( EC_POINT_set_compressed_coordinates_GFp( curve, out, bn_x, *in.begin() & 1, ctx ) > 0 );
    }

    static void from_point( const ec_point& in, public_key_data& out )
    {
        bn_ctx ctx( BN_CTX_new() );
        const ec_group& curve = detail::get_curve();
        ssl_bignum bn_x;
        ssl_bignum bn_y;
        FC_ASSERT( EC_POINT_get_affine_coordinates_GFp( curve, in, bn_x, bn_y, ctx ) > 0 );
        private_key_secret x;
        from_bignum( bn_x, x );
        memcpy( out.begin() + 1, x.data(), out.size() - 1 );
        *out.begin() = BN_is_bit_set( bn_y, 0 ) ? 3 : 2;
    }

//    static void print(const unsigned char* data) {
//        for (int i = 0; i < 32; i++) {
//            printf("%02x", *data++);
//        }
//    }
//
//    static void print(private_key_secret key) {
//        print((unsigned char*) key.data());
//    }
//
//    static void print(public_key_data key) {
//        print((unsigned char*) key.begin() + 1);
//    }
    static void canonicalize(unsigned char* int256)
    {
      fc::sha256 biggi((char*)int256, 32);
      if (detail::get_half_curve_order() >= biggi)
        return; // nothing to do
      ssl_bignum bn_k;
      to_bignum(int256, bn_k, 32);
      ssl_bignum bn_n;
      to_bignum(detail::get_curve_order(), bn_n);
      FC_ASSERT(BN_sub(bn_k, bn_n, bn_k));
      from_bignum(bn_k, int256, 32);
    }

    static public_key compute_k(const private_key_secret& a, const private_key_secret& c, const public_key& p)
    {
      public_key_data P = p.serialize();
      secp256k1_pubkey pubkey;
      FC_ASSERT(secp256k1_ec_pubkey_parse(detail::_get_context(), &pubkey, (const unsigned char*)P.begin(), P.size()));

      private_key_secret prod = a;
      FC_ASSERT(secp256k1_ec_privkey_tweak_mul(detail::_get_context(), (unsigned char*)prod.data(), (unsigned char*)c.data()));
      invert(prod, prod);

      FC_ASSERT(secp256k1_ec_pubkey_tweak_mul(detail::_get_context(), &pubkey, (const unsigned char*)prod.data()));

      size_t pk_len = P.size();
      FC_ASSERT(secp256k1_ec_pubkey_serialize(detail::_get_context(), (unsigned char*)P.begin(), &pk_len, &pubkey, SECP256K1_EC_COMPRESSED));
      return public_key(P);
    }

    static public_key compute_t(const private_key_secret& a, const private_key_secret& b, const private_key_secret& c, 
                                const private_key_secret& d, const public_key_data& p, const public_key_data& q)
    {
      private_key_secret prod;
      invert(c, prod); // prod == c^-1
      FC_ASSERT(secp256k1_ec_privkey_tweak_mul(detail::_get_context(), (unsigned char*)prod.data(), (unsigned char*)d.data()));
      // prod == c^-1 * d

      public_key_data accu = p;
      secp256k1_pubkey accu_pubkey;
      FC_ASSERT(secp256k1_ec_pubkey_parse(detail::_get_context(), &accu_pubkey, (const unsigned char*)accu.begin(), accu.size()));
      FC_ASSERT(secp256k1_ec_pubkey_tweak_mul(detail::_get_context(), &accu_pubkey, (unsigned char*)prod.data()));
      size_t accu_pk_len = accu.size();
      FC_ASSERT(secp256k1_ec_pubkey_serialize(detail::_get_context(), (unsigned char*)accu.begin(), &accu_pk_len, &accu_pubkey, SECP256K1_EC_COMPRESSED));
      // accu == prod * P == c^-1 * d * P

      ec_point point_accu(EC_POINT_new(detail::get_curve()));
      to_point(accu, point_accu);
      ec_point point_q(EC_POINT_new(detail::get_curve()));
      to_point(q, point_q);
      bn_ctx ctx(BN_CTX_new());
      FC_ASSERT(EC_POINT_add(detail::get_curve(), point_accu, point_accu, point_q, ctx) > 0);
      from_point(point_accu, accu);
      // accu == c^-1 * a * P + Q

      FC_ASSERT(secp256k1_ec_pubkey_parse(detail::_get_context(), &accu_pubkey, (const unsigned char*)accu.begin(), accu.size()));
      FC_ASSERT(secp256k1_ec_pubkey_tweak_add(detail::_get_context(), &accu_pubkey, (unsigned char*)b.data()));
      // accu == c^-1 * a * P + Q + b*G

      public_key_data k = compute_k(a, c, p).serialize();
      memcpy(prod.data(), k.begin() + 1, prod.data_size());
      // prod == Kx
      FC_ASSERT(secp256k1_ec_privkey_tweak_mul(detail::_get_context(), (unsigned char*)prod.data(), (unsigned char*)a.data()));
      // prod == Kx * a
      invert(prod, prod);
      // prod == (Kx * a)^-1

      FC_ASSERT(secp256k1_ec_pubkey_tweak_mul(detail::_get_context(), &accu_pubkey, (unsigned char*)prod.data()));
      accu_pk_len = accu.size();
      FC_ASSERT(secp256k1_ec_pubkey_serialize(detail::_get_context(), (unsigned char*)accu.begin(), &accu_pk_len, &accu_pubkey, SECP256K1_EC_COMPRESSED));
      // accu == (c^-1 * a * P + Q + b*G) * (Kx * a)^-1

      //        printf("T: "); print(accu); printf("\n");
      return public_key(accu);
    }

    extended_private_key::extended_private_key( const private_key& k, const sha256& c,
                                                int child, int parent, uint8_t depth ) : 
      private_key(k), c(c), child_num(child), parent_fp(parent), depth(depth) 
    {}

    extended_private_key extended_private_key::private_derive_rest(const fc::sha512& hash, int i) const
    {
      fc::sha256 left = detail::_left(hash);
      FC_ASSERT(left < detail::get_curve_order());
      FC_ASSERT(secp256k1_ec_privkey_tweak_add(detail::_get_context(), (unsigned char*)left.data(), (unsigned char*)get_secret().data()));
      extended_private_key result(private_key::regenerate(left), detail::_right(hash), i, fingerprint(), depth + 1);
      return result;
    }

    public_key extended_private_key::blind_public_key(const extended_public_key& bob, int i) const
    {
      private_key_secret a = generate_a(i).get_secret();
      private_key_secret b = generate_b(i).get_secret();
      private_key_secret c = generate_c(i).get_secret();
      private_key_secret d = generate_d(i).get_secret();
      public_key_data p = bob.generate_p(i).serialize();
      public_key_data q = bob.generate_q(i).serialize();
      //        printf("a: "); print(a); printf("\n");
      //        printf("b: "); print(b); printf("\n");
      //        printf("c: "); print(c); printf("\n");
      //        printf("d: "); print(d); printf("\n");
      //        printf("P: "); print(p); printf("\n");
      //        printf("Q: "); print(q); printf("\n");
      return compute_t(a, b, c, d, p, q);
    }

    blinded_hash extended_private_key::blind_hash(const fc::sha256& hash, int i) const
    {
      private_key_secret a = generate_a(i).get_secret();
      private_key_secret b = generate_b(i).get_secret();
      FC_ASSERT(secp256k1_ec_privkey_tweak_mul(detail::_get_context(), (unsigned char*)a.data(), (unsigned char*)hash.data()));
      FC_ASSERT(secp256k1_ec_privkey_tweak_add(detail::_get_context(), (unsigned char*)a.data(), (unsigned char*)b.data()));
      //        printf("hash: "); print(hash); printf("\n");
      //        printf("blinded: "); print(a); printf("\n");
      return a;
    }

    private_key_secret extended_private_key::compute_p(int i) const
    {
      private_key_secret p_inv = derive_normal_child(2 * i).get_secret();
      invert(p_inv, p_inv);
      //        printf("p: "); print(p_inv); printf("\n");
      return p_inv;
    }

    private_key_secret extended_private_key::compute_q(int i, const private_key_secret& p) const
    {
      private_key_secret q = derive_normal_child(2 * i + 1).get_secret();
      FC_ASSERT(secp256k1_ec_privkey_tweak_mul(detail::_get_context(), (unsigned char*)q.data(), (unsigned char*)p.data()));
      //        printf("q: "); print(q); printf("\n");
      return q;
    }

    blind_signature extended_private_key::blind_sign(const blinded_hash& hash, int i) const
    {
      private_key_secret p = compute_p(i);
      private_key_secret q = compute_q(i, p);
      FC_ASSERT(secp256k1_ec_privkey_tweak_mul(detail::_get_context(), (unsigned char*)p.data(), (unsigned char*)hash.data()));
      FC_ASSERT(secp256k1_ec_privkey_tweak_add(detail::_get_context(), (unsigned char*)p.data(), (unsigned char*)q.data()));
      //        printf("blind_sig: "); print(p); printf("\n");
      return p;
    }

    compact_signature extended_private_key::unblind_signature(const extended_public_key& bob, const blind_signature& sig, const fc::sha256& hash, int i) const
    {
      private_key_secret a = generate_a(i).get_secret();
      private_key_secret b = generate_b(i).get_secret();
      private_key_secret c = generate_c(i).get_secret();
      private_key_secret d = generate_d(i).get_secret();
      public_key p = bob.generate_p(i);
      public_key q = bob.generate_q(i);
      public_key_data k = compute_k(a, c, p);
      public_key_data t = compute_t(a, b, c, d, p, q).serialize();

      FC_ASSERT(secp256k1_ec_privkey_tweak_mul(detail::_get_context(), (unsigned char*)c.data(), (unsigned char*)sig.data()));
      FC_ASSERT(secp256k1_ec_privkey_tweak_add(detail::_get_context(), (unsigned char*)c.data(), (unsigned char*)d.data()));

      compact_signature result;
      memcpy(result.begin() + 1, k.begin() + 1, 32);
      memcpy(result.begin() + 33, c.data(), 32);
      canonicalize(result.begin() + 33);
      //        printf("unblinded: "); print(result.begin() + 33); printf("\n");
      for (int i = 0; i < 4; i++)
      {
        secp256k1_ecdsa_recoverable_signature sig;
        if (secp256k1_ecdsa_recoverable_signature_parse_compact(detail::_get_context(), &sig, (const unsigned char*)result.begin() + 1, i))
        {
          secp256k1_pubkey recovered_key;
          if (secp256k1_ecdsa_recover(detail::_get_context(), &recovered_key, &sig, (unsigned char*)hash.data()))
          {
            unsigned char pubkey[33];
            size_t pk_len = 33;
            FC_ASSERT(secp256k1_ec_pubkey_serialize(detail::_get_context(), (unsigned char*)pubkey, &pk_len, &recovered_key, SECP256K1_EC_COMPRESSED));

            if (!memcmp(t.begin(), pubkey, sizeof(pubkey)))
            {
              *result.begin() = 27 + 4 + i;
              return result;
              //                } else {
              //                    printf("Candidate: "); print( pubkey ); printf("\n");
            }
          }
        }
      }
      FC_ASSERT(0, "Failed to unblind - use different i");
    }

    commitment_type blind(const blind_factor_type& blind, uint64_t value)
    {
      secp256k1_pedersen_commitment commit;
      FC_ASSERT(secp256k1_pedersen_commit(detail::_get_context(), &commit, (unsigned char*)&blind, value, secp256k1_generator_h));

      commitment_type result;
      FC_ASSERT(secp256k1_pedersen_commitment_serialize(detail::_get_context(), (unsigned char*)result.begin(), &commit));
      return result;
    }

    blind_factor_type blind_sum(const std::vector<blind_factor_type>& blinds_in, uint32_t non_neg)
    {
      blind_factor_type result;
      std::vector<const unsigned char*> blinds(blinds_in.size());
      for (uint32_t i = 0; i < blinds_in.size(); ++i)
        blinds[i] = (const unsigned char*)&blinds_in[i];
      FC_ASSERT(secp256k1_pedersen_blind_sum(detail::_get_context(), (unsigned char*)&result, blinds.data(), blinds_in.size(), non_neg));
      return result;
    }

    /**  verifies that commnits + neg_commits == 0 */
    bool verify_sum(const std::vector<commitment_type>& commits_in, const std::vector<commitment_type>& neg_commits_in)
    {
      std::vector<secp256k1_pedersen_commitment> commits(commits_in.size());
      for (unsigned i = 0; i < commits_in.size(); ++i)
        FC_ASSERT(secp256k1_pedersen_commitment_parse(detail::_get_context(), &commits[i], (const unsigned char *)commits_in[i].begin()));

      std::vector<secp256k1_pedersen_commitment> neg_commits(neg_commits_in.size());
      for (unsigned i = 0; i < neg_commits_in.size(); ++i)
        FC_ASSERT(secp256k1_pedersen_commitment_parse(detail::_get_context(), &neg_commits[i], (const unsigned char *)neg_commits_in[i].begin()));

      std::vector<secp256k1_pedersen_commitment*> commits_ptrs(commits_in.size());
      for (unsigned i = 0; i < commits.size(); ++i)
        commits_ptrs[i] = &commits[i];

      std::vector<secp256k1_pedersen_commitment*> neg_commits_ptrs(neg_commits_in.size());
      for (unsigned i = 0; i < neg_commits.size(); ++i)
        neg_commits_ptrs[i] = &neg_commits[i];

      return secp256k1_pedersen_verify_tally(detail::_get_context(), commits_ptrs.data(), commits_ptrs.size(), neg_commits_ptrs.data(), neg_commits_ptrs.size());
    }

    bool verify_range(uint64_t& min_val, uint64_t& max_val, const commitment_type& commit, const std::vector<char>& proof)
    {
      secp256k1_pedersen_commitment commitment;
      FC_ASSERT(secp256k1_pedersen_commitment_parse(detail::_get_context(), &commitment, (const unsigned char *)commit.begin()));
      return secp256k1_rangeproof_verify(detail::_get_context(), &min_val, &max_val, &commitment, (const unsigned char*)proof.data(), proof.size(), 
                                         NULL, 0, secp256k1_generator_h);
    }

    std::vector<char> range_proof_sign(uint64_t min_value, const commitment_type& commit, const blind_factor_type& commit_blind, 
                                       const blind_factor_type& nonce, int8_t base10_exp, uint8_t min_bits, uint64_t actual_value)
    {
      size_t proof_len = 5134;
      std::vector<char> proof(proof_len);

      secp256k1_pedersen_commitment commitment;
      FC_ASSERT(secp256k1_pedersen_commitment_parse(detail::_get_context(), &commitment, (const unsigned char *)commit.begin()));
      FC_ASSERT(secp256k1_rangeproof_sign(detail::_get_context(), (unsigned char*)proof.data(), &proof_len, min_value, &commitment,
                                          (const unsigned char*)&commit_blind, (const unsigned char*)&nonce, base10_exp, min_bits, actual_value,
                                          NULL /*?*/, 0, NULL, 0, secp256k1_generator_h));
      proof.resize(proof_len);
      return proof;
    }

    bool verify_range_proof_rewind(blind_factor_type& blind_out, uint64_t& value_out, string& message_out, const blind_factor_type& nonce, 
                                   uint64_t& min_val, uint64_t& max_val, commitment_type commit, const std::vector<char>& proof)
    {
      char msg[4096];
      size_t mlen = 0;
      secp256k1_pedersen_commitment commitment;
      FC_ASSERT(secp256k1_pedersen_commitment_parse(detail::_get_context(), &commitment, (const unsigned char *)commit.begin()));
      FC_ASSERT(secp256k1_rangeproof_rewind(detail::_get_context(), (unsigned char*)&blind_out, &value_out, (unsigned char*)msg, &mlen, (const unsigned char*)&nonce, &min_val,
                                            &max_val, &commitment, (const unsigned char*)proof.data(), proof.size(),
                                            NULL /*?*/, 0, secp256k1_generator_h));

      message_out = std::string(msg, mlen);
      return true;
    }

    range_proof_info range_get_info(const std::vector<char>& proof)
    {
      range_proof_info result;
      FC_ASSERT(secp256k1_rangeproof_info(detail::_get_context(), (int*)&result.exp, (int*)&result.mantissa, (uint64_t*)&result.min_value, 
                                          (uint64_t*)&result.max_value, (const unsigned char*)proof.data(), (int)proof.size()));

      return result;
    }
} }
