#pragma once
/**
	@file
	@brief somewhat homomorphic encryption with one-time multiplication, based on prime-order pairings
	@author MITSUNARI Shigeo(@herumi)
	see https://github.com/herumi/mcl/blob/master/misc/she/she.pdf
	@license modified new BSD license
	http://opensource.org/licenses/BSD-3-Clause
*/
#include <cmath>
#include <vector>
#include <iosfwd>
#ifndef MCLBN_FP_UNIT_SIZE
	#define MCLBN_FP_UNIT_SIZE 4
#endif
#if MCLBN_FP_UNIT_SIZE == 4
#include <mcl/bn256.hpp>
#elif MCLBN_FP_UNIT_SIZE == 6
#include <mcl/bn384.hpp>
#elif MCLBN_FP_UNIT_SIZE == 8
#include <mcl/bn512.hpp>
#else
	#error "MCLBN_FP_UNIT_SIZE must be 4, 6, or 8"
#endif

#include <mcl/window_method.hpp>
#include <cybozu/endian.hpp>
#include <cybozu/serializer.hpp>

namespace mcl { namespace she {

using namespace mcl::bn;

namespace local {

#ifndef MCLSHE_WIN_SIZE
	#define MCLSHE_WIN_SIZE 10
#endif
static const size_t winSize = MCLSHE_WIN_SIZE;
static const size_t defaultTryNum = 2048;

struct KeyCount {
	uint32_t key;
	int32_t count; // power
	bool operator<(const KeyCount& rhs) const
	{
		return key < rhs.key;
	}
	bool isSame(const KeyCount& rhs) const
	{
		return key == rhs.key && count == rhs.count;
	}
};

template<class G, bool = true>
struct InterfaceForHashTable : G {
	static G& castG(InterfaceForHashTable& x) { return static_cast<G&>(x); }
	static const G& castG(const InterfaceForHashTable& x) { return static_cast<const G&>(x); }
	void clear() { clear(castG(*this)); }
	void normalize() { normalize(castG(*this)); }
	static bool isOdd(const G& P) { return P.y.isOdd(); }
	static bool isZero(const G& P) { return P.isZero(); }
	static bool isSameX(const G& P, const G& Q) { return P.x == Q.x; }
	static uint32_t getHash(const G& P) { return uint32_t(*P.x.getUnit()); }
	static void clear(G& P) { P.clear(); }
	static void normalize(G& P) { P.normalize(); }
	static void dbl(G& Q, const G& P) { G::dbl(Q, P); }
	static void neg(G& Q, const G& P) { G::neg(Q, P); }
	static void add(G& R, const G& P, const G& Q) { G::add(R, P, Q); }
	template<class INT>
	static void mul(G& Q, const G& P, const INT& x) { G::mul(Q, P, x); }
};

/*
	treat Fp12 as EC
	unitary inverse of (a, b) = (a, -b)
	then b.a.a or -b.a.a is odd
*/
template<class G>
struct InterfaceForHashTable<G, false> : G {
	static G& castG(InterfaceForHashTable& x) { return static_cast<G&>(x); }
	static const G& castG(const InterfaceForHashTable& x) { return static_cast<const G&>(x); }
	void clear() { clear(castG(*this)); }
	void normalize() { normalize(castG(*this)); }
	static bool isOdd(const G& x) { return x.b.a.a.isOdd(); }
	static bool isZero(const G& x) { return x.isOne(); }
	static bool isSameX(const G& x, const G& Q) { return x.a == Q.a; }
	static uint32_t getHash(const G& x) { return uint32_t(*x.getFp0()->getUnit()); }
	static void clear(G& x) { x = 1; }
	static void normalize(G&) { }
	static void dbl(G& y, const G& x) { G::sqr(y, x); }
	static void neg(G& Q, const G& P) { G::unitaryInv(Q, P); }
	static void add(G& z, const G& x, const G& y) { G::mul(z, x, y); }
	template<class INT>
	static void mul(G& z, const G& x, const INT& y) { G::pow(z, x, y); }
};

template<class G>
char GtoChar();
template<>char GtoChar<bn::G1>() { return '1'; }
template<>char GtoChar<bn::G2>() { return '2'; }
template<>char GtoChar<bn::GT>() { return 'T'; }

/*
	HashTable<EC, true> or HashTable<Fp12, false>
*/
template<class G, bool isEC = true>
class HashTable {
	typedef InterfaceForHashTable<G, isEC> I;
	typedef std::vector<KeyCount> KeyCountVec;
	KeyCountVec kcv_;
	G P_;
	mcl::fp::WindowMethod<I> wm_;
	G nextP_;
	G nextNegP_;
	size_t tryNum_;
	void setWindowMethod()
	{
		const size_t bitSize = G::BaseFp::BaseFp::getBitSize();
		wm_.init(static_cast<const I&>(P_), bitSize, local::winSize);
	}
public:
	HashTable() : tryNum_(local::defaultTryNum) {}
	bool operator==(const HashTable& rhs) const
	{
		if (kcv_.size() != rhs.kcv_.size()) return false;
		for (size_t i = 0; i < kcv_.size(); i++) {
			if (!kcv_[i].isSame(rhs.kcv_[i])) return false;
		}
		return P_ == rhs.P_ && nextP_ == rhs.nextP_;
	}
	bool operator!=(const HashTable& rhs) const { return !operator==(rhs); }
	/*
		compute log_P(xP) for |x| <= hashSize * tryNum
	*/
	void init(const G& P, size_t hashSize, size_t tryNum = local::defaultTryNum)
	{
		if (hashSize == 0) {
			kcv_.clear();
			return;
		}
		if (hashSize >= 0x80000000u) throw cybozu::Exception("HashTable:init:hashSize is too large");
		P_ = P;
		tryNum_ = tryNum;
		kcv_.resize(hashSize);
		G xP;
		I::clear(xP);
		for (int i = 1; i <= (int)kcv_.size(); i++) {
			I::add(xP, xP, P_);
			I::normalize(xP);
			kcv_[i - 1].key = I::getHash(xP);
			kcv_[i - 1].count = I::isOdd(xP) ? i : -i;
		}
		nextP_ = xP;
		I::dbl(nextP_, nextP_);
		I::add(nextP_, nextP_, P_); // nextP = (hasSize * 2 + 1)P
		I::neg(nextNegP_, nextP_); // nextNegP = -nextP
		/*
			ascending order of abs(count) for same key
		*/
		std::stable_sort(kcv_.begin(), kcv_.end());
		setWindowMethod();
	}
	void setTryNum(size_t tryNum)
	{
		this->tryNum_ = tryNum;
	}
	/*
		log_P(xP)
		find range which has same hash of xP in kcv_,
		and detect it
	*/
	int basicLog(G xP, bool *ok = 0) const
	{
		if (ok) *ok = true;
		if (I::isZero(xP)) return 0;
		typedef KeyCountVec::const_iterator Iter;
		KeyCount kc;
		I::normalize(xP);
		kc.key = I::getHash(xP);
		kc.count = 0;
		std::pair<Iter, Iter> p = std::equal_range(kcv_.begin(), kcv_.end(), kc);
		G Q;
		I::clear(Q);
		int prev = 0;
		/*
			check range which has same hash
		*/
		while (p.first != p.second) {
			int count = p.first->count;
			int abs_c = std::abs(count);
			assert(abs_c >= prev); // assume ascending order
			bool neg = count < 0;
			G T;
//			I::mul(T, P, abs_c - prev);
			mulByWindowMethod(T, abs_c - prev);
			I::add(Q, Q, T);
			I::normalize(Q);
			if (I::isSameX(Q, xP)) {
				bool QisOdd = I::isOdd(Q);
				bool xPisOdd = I::isOdd(xP);
				if (QisOdd ^ xPisOdd ^ neg) return -count;
				return count;
			}
			prev = abs_c;
			++p.first;
		}
		if (ok) {
			*ok = false;
			return 0;
		}
		throw cybozu::Exception("HashTable:basicLog:not found");
	}
	/*
		compute log_P(xP)
		call basicLog at most 2 * tryNum
	*/
	int64_t log(const G& xP) const
	{
		bool ok;
		int c = basicLog(xP, &ok);
		if (ok) {
			return c;
		}
		G posP = xP, negP = xP;
		int64_t posCenter = 0;
		int64_t negCenter = 0;
		int64_t next = (int64_t)kcv_.size() * 2 + 1;
		for (size_t i = 1; i < tryNum_; i++) {
			I::add(posP, posP, nextNegP_);
			posCenter += next;
			c = basicLog(posP, &ok);
			if (ok) {
				return posCenter + c;
			}
			I::add(negP, negP, nextP_);
			negCenter -= next;
			c = basicLog(negP, &ok);
			if (ok) {
				return negCenter + c;
			}
		}
		throw cybozu::Exception("HashTable:log:not found");
	}
	/*
		remark
		tryNum is not saved.
	*/
	template<class OutputStream>
	void save(OutputStream& os) const
	{
		cybozu::save(os, BN::param.cp.curveType);
		cybozu::writeChar(os, GtoChar<G>());
		cybozu::save(os, kcv_.size());
		cybozu::write(os, &kcv_[0], sizeof(kcv_[0]) * kcv_.size());
		P_.save(os);
	}
	size_t save(void *buf, size_t maxBufSize) const
	{
		cybozu::MemoryOutputStream os(buf, maxBufSize);
		save(os);
		return os.getPos();
	}
	/*
		remark
		tryNum is not set
	*/
	template<class InputStream>
	void load(InputStream& is)
	{
		int curveType;
		cybozu::load(curveType, is);
		if (curveType != BN::param.cp.curveType) throw cybozu::Exception("HashTable:bad curveType") << curveType;
		char c = 0;
		if (!cybozu::readChar(&c, is) || c != GtoChar<G>()) throw cybozu::Exception("HashTable:bad c") << (int)c;
		size_t kcvSize;
		cybozu::load(kcvSize, is);
		kcv_.resize(kcvSize);
		cybozu::read(&kcv_[0], sizeof(kcv_[0]) * kcvSize, is);
		P_.load(is);
		I::mul(nextP_, P_, (kcvSize * 2) + 1);
		I::neg(nextNegP_, nextP_);
		setWindowMethod();
	}
	size_t load(const void *buf, size_t bufSize)
	{
		cybozu::MemoryInputStream is(buf, bufSize);
		load(is);
		return is.getPos();
	}
	const mcl::fp::WindowMethod<I>& getWM() const { return wm_; }
	/*
		mul(x, P, y);
	*/
	template<class T>
	void mulByWindowMethod(G& x, const T& y) const
	{
		wm_.mul(static_cast<I&>(x), y);
	}
};

template<class G>
int log(const G& P, const G& xP)
{
	if (xP.isZero()) return 0;
	if (xP == P) return 1;
	G negT;
	G::neg(negT, P);
	if (xP == negT) return -1;
	G T = P;
	for (int i = 2; i < 100; i++) {
		T += P;
		if (xP == T) return i;
		G::neg(negT, T);
		if (xP == negT) return -i;
	}
	throw cybozu::Exception("she:log:not found");
}

} // mcl::she::local

template<size_t dummyInpl = 0>
struct SHET {
	class SecretKey;
	class PublicKey;
	class PrecomputedPublicKey;
	// additive HE
	class CipherTextA; // = CipherTextG1 + CipherTextG2
	class CipherTextGT; // multiplicative HE
	class CipherText; // CipherTextA + CipherTextGT

	static G1 P_;
	static G2 Q_;
	static GT ePQ_; // e(P, Q)
	static std::vector<Fp6> Qcoeff_;
	static local::HashTable<G1> PhashTbl_;
	static local::HashTable<G2> QhashTbl_;
	static mcl::fp::WindowMethod<G2> Qwm_;
	typedef local::InterfaceForHashTable<GT, false> GTasEC;
	static local::HashTable<GT, false> ePQhashTbl_;
	static bool useDecG1ViaGT_;
	static bool useDecG2ViaGT_;
	static bool isG1only_;
private:
	template<class G>
	class CipherTextAT : public fp::Serializable<CipherTextAT<G> > {
		G S_, T_;
		friend class SecretKey;
		friend class PublicKey;
		friend class PrecomputedPublicKey;
		friend class CipherTextA;
		friend class CipherTextGT;
		bool isZero(const Fr& x) const
		{
			G xT;
			G::mul(xT, T_, x);
			return S_ == xT;
		}
	public:
		const G& getS() const { return S_; }
		const G& getT() const { return T_; }
		void clear()
		{
			S_.clear();
			T_.clear();
		}
		static void add(CipherTextAT& z, const CipherTextAT& x, const CipherTextAT& y)
		{
			/*
				(S, T) + (S', T') = (S + S', T + T')
			*/
			G::add(z.S_, x.S_, y.S_);
			G::add(z.T_, x.T_, y.T_);
		}
		static void sub(CipherTextAT& z, const CipherTextAT& x, const CipherTextAT& y)
		{
			/*
				(S, T) - (S', T') = (S - S', T - T')
			*/
			G::sub(z.S_, x.S_, y.S_);
			G::sub(z.T_, x.T_, y.T_);
		}
		// INT = int64_t or Fr
		template<class INT>
		static void mul(CipherTextAT& z, const CipherTextAT& x, const INT& y)
		{
			G::mul(z.S_, x.S_, y);
			G::mul(z.T_, x.T_, y);
		}
		static void neg(CipherTextAT& y, const CipherTextAT& x)
		{
			G::neg(y.S_, x.S_);
			G::neg(y.T_, x.T_);
		}
		void add(const CipherTextAT& c) { add(*this, *this, c); }
		void sub(const CipherTextAT& c) { sub(*this, *this, c); }
		template<class InputStream>
		void load(bool *pb, InputStream& is, int ioMode = IoSerialize)
		{
			S_.load(pb, is, ioMode); if (!*pb) return;
			T_.load(pb, is, ioMode);
		}
		template<class OutputStream>
		void save(bool *pb, OutputStream& os, int ioMode = IoSerialize) const
		{
			const char sep = *fp::getIoSeparator(ioMode);
			S_.save(pb, os, ioMode); if (!*pb) return;
			if (sep) {
				cybozu::writeChar(pb, os, sep);
				if (!*pb) return;
			}
			T_.save(pb, os, ioMode);
		}
		template<class InputStream>
		void load(InputStream& is, int ioMode = IoSerialize)
		{
			bool b;
			load(&b, is, ioMode);
			if (!b) throw cybozu::Exception("she:CipherTextA:load");
		}
		template<class OutputStream>
		void save(OutputStream& os, int ioMode = IoSerialize) const
		{
			bool b;
			save(&b, os, ioMode);
			if (!b) throw cybozu::Exception("she:CipherTextA:save");
		}
		friend std::istream& operator>>(std::istream& is, CipherTextAT& self)
		{
			self.load(is, fp::detectIoMode(G::getIoMode(), is));
			return is;
		}
		friend std::ostream& operator<<(std::ostream& os, const CipherTextAT& self)
		{
			self.save(os, fp::detectIoMode(G::getIoMode(), os));
			return os;
		}
		bool operator==(const CipherTextAT& rhs) const
		{
			return S_ == rhs.S_ && T_ == rhs.T_;
		}
		bool operator!=(const CipherTextAT& rhs) const { return !operator==(rhs); }
	};
	/*
		g1 = millerLoop(P1, Q)
		g2 = millerLoop(P2, Q)
	*/
	static void doubleMillerLoop(GT& g1, GT& g2, const G1& P1, const G1& P2, const G2& Q)
	{
#if 1
		std::vector<Fp6> Qcoeff;
		precomputeG2(Qcoeff, Q);
		precomputedMillerLoop(g1, P1, Qcoeff);
		precomputedMillerLoop(g2, P2, Qcoeff);
#else
		millerLoop(g1, P1, Q);
		millerLoop(g2, P2, Q);
#endif
	}
	static void finalExp4(GT out[4], const GT in[4])
	{
		for (int i =  0; i < 4; i++) {
			finalExp(out[i], in[i]);
		}
	}
	static void tensorProductML(GT g[4], const G1& S1, const G1& T1, const G2& S2, const G2& T2)
	{
		/*
			(S1, T1) x (S2, T2) = (ML(S1, S2), ML(S1, T2), ML(T1, S2), ML(T1, T2))
		*/
		doubleMillerLoop(g[0], g[2], S1, T1, S2);
		doubleMillerLoop(g[1], g[3], S1, T1, T2);
	}
	static void tensorProduct(GT g[4], const G1& S1, const G1& T1, const G2& S2, const G2& T2)
	{
		/*
			(S1, T1) x (S2, T2) = (e(S1, S2), e(S1, T2), e(T1, S2), e(T1, T2))
		*/
		tensorProductML(g,S1, T1, S2,T2);
		finalExp4(g, g);
	}
	template<class Tag, size_t n>
	struct ZkpT : public fp::Serializable<ZkpT<Tag, n> > {
		Fr d_[n];
		template<class InputStream>
		void load(bool *pb, InputStream& is, int ioMode = IoSerialize)
		{
			for (size_t i = 0; i < n; i++) {
				d_[i].load(pb, is, ioMode); if (!*pb) return;
			}
		}
		template<class OutputStream>
		void save(bool *pb, OutputStream& os, int ioMode = IoSerialize) const
		{
			const char sep = *fp::getIoSeparator(ioMode);
			d_[0].save(pb, os, ioMode); if (!*pb) return;
			for (size_t i = 1; i < n; i++) {
				if (sep) {
					cybozu::writeChar(pb, os, sep);
					if (!*pb) return;
				}
				d_[i].save(pb, os, ioMode);
			}
		}
		template<class InputStream>
		void load(InputStream& is, int ioMode = IoSerialize)
		{
			bool b;
			load(&b, is, ioMode);
			if (!b) throw cybozu::Exception("she:ZkpT:load");
		}
		template<class OutputStream>
		void save(OutputStream& os, int ioMode = IoSerialize) const
		{
			bool b;
			save(&b, os, ioMode);
			if (!b) throw cybozu::Exception("she:ZkpT:save");
		}
		friend std::istream& operator>>(std::istream& is, ZkpT& self)
		{
			self.load(is, fp::detectIoMode(Fr::getIoMode(), is));
			return is;
		}
		friend std::ostream& operator<<(std::ostream& os, const ZkpT& self)
		{
			self.save(os, fp::detectIoMode(Fr::getIoMode(), os));
			return os;
		}
	};
	struct ZkpBinTag;
	struct ZkpEqTag; // d_[] = { c, sp, ss, sm }
	struct ZkpBinEqTag; // d_[] = { d0, d1, sp0, sp1, ss, sp, sm }
public:
	/*
		Zkp for m = 0 or 1
	*/
	typedef ZkpT<ZkpBinTag, 4> ZkpBin;
	/*
		Zkp for decG1(c1) == decG2(c2)
	*/
	typedef ZkpT<ZkpEqTag, 4> ZkpEq;
	/*
		Zkp for (m = 0 or 1) and decG1(c1) == decG2(c2)
	*/
	typedef ZkpT<ZkpBinEqTag, 7> ZkpBinEq;

	typedef CipherTextAT<G1> CipherTextG1;
	typedef CipherTextAT<G2> CipherTextG2;

	static void init(const mcl::CurveParam& cp = mcl::BN254, size_t hashSize = 1024, size_t tryNum = local::defaultTryNum)
	{
		initPairing(cp);
		hashAndMapToG1(P_, "0");
		hashAndMapToG2(Q_, "0");
		pairing(ePQ_, P_, Q_);
		precomputeG2(Qcoeff_, Q_);
		setRangeForDLP(hashSize);
		useDecG1ViaGT_ = false;
		useDecG2ViaGT_ = false;
		isG1only_ = false;
		setTryNum(tryNum);
	}
	static void init(size_t hashSize, size_t tryNum = local::defaultTryNum)
	{
		init(mcl::BN254, hashSize, tryNum);
	}
	/*
		standard lifted ElGamal encryption
	*/
	static void initG1only(const mcl::EcParam& para, size_t hashSize = 1024, size_t tryNum = local::defaultTryNum)
	{
		Fp::init(para.p);
		Fr::init(para.n);
		G1::init(para.a, para.b);
		const Fp x0(para.gx);
		const Fp y0(para.gy);
		P_.set(x0, y0);

		setRangeForG1DLP(hashSize);
		useDecG1ViaGT_ = false;
		useDecG2ViaGT_ = false;
		isG1only_ = true;
		setTryNum(tryNum);
	}
	/*
		set range for G1-DLP
	*/
	static void setRangeForG1DLP(size_t hashSize)
	{
		PhashTbl_.init(P_, hashSize);
	}
	/*
		set range for G2-DLP
	*/
	static void setRangeForG2DLP(size_t hashSize)
	{
		QhashTbl_.init(Q_, hashSize);
	}
	/*
		set range for GT-DLP
	*/
	static void setRangeForGTDLP(size_t hashSize)
	{
		ePQhashTbl_.init(ePQ_, hashSize);
	}
	/*
		set range for G1/G2/GT DLP
		decode message m for |m| <= hasSize * tryNum
		decode time = O(log(hasSize) * tryNum)
	*/
	static void setRangeForDLP(size_t hashSize)
	{
		setRangeForG1DLP(hashSize);
		setRangeForG2DLP(hashSize);
		setRangeForGTDLP(hashSize);
	}
	static void setTryNum(size_t tryNum)
	{
		PhashTbl_.setTryNum(tryNum);
		QhashTbl_.setTryNum(tryNum);
		ePQhashTbl_.setTryNum(tryNum);
	}
	static void useDecG1ViaGT(bool use = true)
	{
		useDecG1ViaGT_ = use;
	}
	static void useDecG2ViaGT(bool use = true)
	{
		useDecG2ViaGT_ = use;
	}
	/*
		only one element is necessary for each G1 and G2.
		this is better than David Mandell Freeman's algorithm
	*/
	class SecretKey : public fp::Serializable<SecretKey> {
		Fr x_, y_;
		void getPowOfePQ(GT& v, const CipherTextGT& c) const
		{
			/*
				(s, t, u, v) := (e(S, S'), e(S, T'), e(T, S'), e(T, T'))
				s v^(xy) / (t^y u^x) = s (v^x / t) ^ y / u^x
				= e(P, Q)^(mm')
			*/
			GT t, u;
			GT::unitaryInv(t, c.g_[1]);
			GT::unitaryInv(u, c.g_[2]);
			GT::pow(v, c.g_[3], x_);
			v *= t;
			GT::pow(v, v, y_);
			GT::pow(u, u, x_);
			v *= u;
			v *= c.g_[0];
		}
	public:
		void setByCSPRNG()
		{
			x_.setRand();
			if (!isG1only_) y_.setRand();
		}
		/*
			set xP and yQ
		*/
		void getPublicKey(PublicKey& pub) const
		{
			pub.set(x_, y_);
		}
#if 0
		// log_x(y)
		int log(const GT& x, const GT& y) const
		{
			if (y == 1) return 0;
			if (y == x) return 1;
			GT inv;
			GT::unitaryInv(inv, x);
			if (y == inv) return -1;
			GT t = x;
			for (int i = 2; i < 100; i++) {
				t *= x;
				if (y == t) return i;
				GT::unitaryInv(inv, t);
				if (y == inv) return -i;
			}
			throw cybozu::Exception("she:dec:log:not found");
		}
#endif
		int64_t dec(const CipherTextG1& c) const
		{
			if (useDecG1ViaGT_) return decViaGT(c);
			/*
				S = mP + rxP
				T = rP
				R = S - xT = mP
			*/
			G1 R;
			G1::mul(R, c.T_, x_);
			G1::sub(R, c.S_, R);
			return PhashTbl_.log(R);
		}
		int64_t dec(const CipherTextG2& c) const
		{
			if (useDecG2ViaGT_) return decViaGT(c);
			G2 R;
			G2::mul(R, c.T_, y_);
			G2::sub(R, c.S_, R);
			return QhashTbl_.log(R);
		}
		int64_t dec(const CipherTextA& c) const
		{
			return dec(c.c1_);
		}
		int64_t dec(const CipherTextGT& c) const
		{
			GT v;
			getPowOfePQ(v, c);
			return ePQhashTbl_.log(v);
//			return log(g, v);
		}
		int64_t decViaGT(const CipherTextG1& c) const
		{
			G1 R;
			G1::mul(R, c.T_, x_);
			G1::sub(R, c.S_, R);
			GT v;
			pairing(v, R, Q_);
			return ePQhashTbl_.log(v);
		}
		int64_t decViaGT(const CipherTextG2& c) const
		{
			G2 R;
			G2::mul(R, c.T_, y_);
			G2::sub(R, c.S_, R);
			GT v;
			pairing(v, P_, R);
			return ePQhashTbl_.log(v);
		}
		int64_t dec(const CipherText& c) const
		{
			if (c.isMultiplied()) {
				return dec(c.m_);
			} else {
				return dec(c.a_);
			}
		}
		bool isZero(const CipherTextG1& c) const
		{
			return c.isZero(x_);
		}
		bool isZero(const CipherTextG2& c) const
		{
			return c.isZero(y_);
		}
		bool isZero(const CipherTextA& c) const
		{
			return c.c1_.isZero(x_);
		}
		bool isZero(const CipherTextGT& c) const
		{
			GT v;
			getPowOfePQ(v, c);
			return v.isOne();
		}
		bool isZero(const CipherText& c) const
		{
			if (c.isMultiplied()) {
				return isZero(c.m_);
			} else {
				return isZero(c.a_);
			}
		}
		template<class InputStream>
		void load(bool *pb, InputStream& is, int ioMode = IoSerialize)
		{
			x_.load(pb, is, ioMode); if (!*pb) return;
			if (!isG1only_) y_.load(pb, is, ioMode);
		}
		template<class OutputStream>
		void save(bool *pb, OutputStream& os, int ioMode = IoSerialize) const
		{
			const char sep = *fp::getIoSeparator(ioMode);
			x_.save(pb, os, ioMode); if (!*pb) return;
			if (isG1only_) return;
			if (sep) {
				cybozu::writeChar(pb, os, sep);
				if (!*pb) return;
			}
			y_.save(os, ioMode);
		}
		template<class InputStream>
		void load(InputStream& is, int ioMode = IoSerialize)
		{
			bool b;
			load(&b, is, ioMode);
			if (!b) throw cybozu::Exception("she:SecretKey:load");
		}
		template<class OutputStream>
		void save(OutputStream& os, int ioMode = IoSerialize) const
		{
			bool b;
			save(&b, os, ioMode);
			if (!b) throw cybozu::Exception("she:SecretKey:save");
		}
		friend std::istream& operator>>(std::istream& is, SecretKey& self)
		{
			self.load(is, fp::detectIoMode(Fr::getIoMode(), is));
			return is;
		}
		friend std::ostream& operator<<(std::ostream& os, const SecretKey& self)
		{
			self.save(os, fp::detectIoMode(Fr::getIoMode(), os));
			return os;
		}
		bool operator==(const SecretKey& rhs) const
		{
			return x_ == rhs.x_ && (isG1only_ || y_ == rhs.y_);
		}
		bool operator!=(const SecretKey& rhs) const { return !operator==(rhs); }
	};
private:
	/*
		simple ElGamal encryptionfor G1 and G2
		(S, T) = (m P + r xP, rP)
		Pmul.mul(X, a) // X = a P
		xPmul.mul(X, a) // X = a xP
		use *encRand if encRand is not null
	*/
	template<class G, class INT, class MulG, class I>
	static void ElGamalEnc(G& S, G& T, const INT& m, const mcl::fp::WindowMethod<I>& Pmul, const MulG& xPmul, const Fr *encRand = 0)
	{
		Fr r;
		if (encRand) {
			r = *encRand;
		} else {
			r.setRand();
		}
		Pmul.mul(static_cast<I&>(T), r);
		xPmul.mul(S, r); // S = r xP
		if (m == 0) return;
		G C;
		Pmul.mul(static_cast<I&>(C), m);
		S += C;
	}
	/*
		https://github.com/herumi/mcl/blob/master/misc/she/nizkp.pdf

		encRand is a random value used for ElGamalEnc()
		d[1-m] ; rand
		s[1-m] ; rand
		R[0][1-m] = s[1-m] P - d[1-m] T
		R[1][1-m] = s[1-m] xP - d[1-m] (S - (1-m) P)
		r ; rand
		R[0][m] = r P
		R[1][m] = r xP
		c = H(S, T, R[0][0], R[0][1], R[1][0], R[1][1])
		d[m] = c - d[1-m]
		s[m] = r + d[m] encRand
	*/
	template<class G, class I, class MulG>
	static void makeZkpBin(ZkpBin& zkp, const G& S, const G& T, const Fr& encRand, const G& P, int m, const mcl::fp::WindowMethod<I>& Pmul, const MulG& xPmul)
	{
		if (m != 0 && m != 1) throw cybozu::Exception("makeZkpBin:bad m") << m;
		Fr *s = &zkp.d_[0];
		Fr *d = &zkp.d_[2];
		G R[2][2];
		d[1-m].setRand();
		s[1-m].setRand();
		G T1, T2;
		Pmul.mul(static_cast<I&>(T1),  s[1-m]); // T1 = s[1-m] P
		G::mul(T2, T, d[1-m]);
		G::sub(R[0][1-m], T1, T2); // s[1-m] P - d[1-m]T
		xPmul.mul(T1, s[1-m]); // T1 = s[1-m] xP
		if (m == 0) {
			G::sub(T2, S, P);
			G::mul(T2, T2, d[1-m]);
		} else {
			G::mul(T2, S, d[1-m]);
		}
		G::sub(R[1][1-m], T1, T2); // s[1-m] xP - d[1-m](S - (1-m) P)
		Fr r;
		r.setRand();
		Pmul.mul(static_cast<I&>(R[0][m]), r); // R[0][m] = r P
		xPmul.mul(R[1][m], r); // R[1][m] = r xP
		char buf[sizeof(G) * 2];
		cybozu::MemoryOutputStream os(buf, sizeof(buf));
		S.save(os);
		T.save(os);
		R[0][0].save(os);
		R[0][1].save(os);
		R[1][0].save(os);
		R[1][1].save(os);
		Fr c;
		c.setHashOf(buf, os.getPos());
		d[m] = c - d[1-m];
		s[m] = r + d[m] * encRand;
	}
	/*
		R[0][i] = s[i] P - d[i] T ; i = 0,1
		R[1][0] = s[0] xP - d[0] S
		R[1][1] = s[1] xP - d[1](S - P)
		c = H(S, T, R[0][0], R[0][1], R[1][0], R[1][1])
		c == d[0] + d[1]
	*/
	template<class G, class I, class MulG>
	static bool verifyZkpBin(const G& S, const G& T, const G& P, const ZkpBin& zkp, const mcl::fp::WindowMethod<I>& Pmul, const MulG& xPmul)
	{
		const Fr *s = &zkp.d_[0];
		const Fr *d = &zkp.d_[2];
		G R[2][2];
		G T1, T2;
		for (int i = 0; i < 2; i++) {
			Pmul.mul(static_cast<I&>(T1), s[i]); // T1 = s[i] P
			G::mul(T2, T, d[i]);
			G::sub(R[0][i], T1, T2);
		}
		xPmul.mul(T1, s[0]); // T1 = s[0] xP
		G::mul(T2, S, d[0]);
		G::sub(R[1][0], T1, T2);
		xPmul.mul(T1, s[1]); // T1 = x[1] xP
		G::sub(T2, S, P);
		G::mul(T2, T2, d[1]);
		G::sub(R[1][1], T1, T2);
		char buf[sizeof(G) * 2];
		cybozu::MemoryOutputStream os(buf, sizeof(buf));
		S.save(os);
		T.save(os);
		R[0][0].save(os);
		R[0][1].save(os);
		R[1][0].save(os);
		R[1][1].save(os);
		Fr c;
		c.setHashOf(buf, os.getPos());
		return c == d[0] + d[1];
	}
	/*
		encRand1, encRand2 are random values use for ElGamalEnc()
	*/
	template<class G1, class G2, class INT, class I1, class I2, class MulG1, class MulG2>
	static void makeZkpEq(ZkpEq& zkp, G1& S1, G1& T1, G2& S2, G2& T2, const INT& m, const mcl::fp::WindowMethod<I1>& Pmul, const MulG1& xPmul, const mcl::fp::WindowMethod<I2>& Qmul, const MulG2& yQmul)
	{
		Fr p, s;
		p.setRand();
		s.setRand();
		ElGamalEnc(S1, T1, m, Pmul, xPmul, &p);
		ElGamalEnc(S2, T2, m, Qmul, yQmul, &s);
		Fr rp, rs, rm;
		rp.setRand();
		rs.setRand();
		rm.setRand();
		G1 R1, R2;
		G2 R3, R4;
		ElGamalEnc(R1, R2, rm, Pmul, xPmul, &rp);
		ElGamalEnc(R3, R4, rm, Qmul, yQmul, &rs);
		char buf[sizeof(G1) * 4 + sizeof(G2) * 4];
		cybozu::MemoryOutputStream os(buf, sizeof(buf));
		S1.save(os);
		T1.save(os);
		S2.save(os);
		T2.save(os);
		R1.save(os);
		R2.save(os);
		R3.save(os);
		R4.save(os);
		Fr& c = zkp.d_[0];
		Fr& sp = zkp.d_[1];
		Fr& ss = zkp.d_[2];
		Fr& sm = zkp.d_[3];
		c.setHashOf(buf, os.getPos());
		Fr::mul(sp, c, p);
		sp += rp;
		Fr::mul(ss, c, s);
		ss += rs;
		Fr::mul(sm, c, m);
		sm += rm;
	}
	template<class G1, class G2, class I1, class I2, class MulG1, class MulG2>
	static bool verifyZkpEq(const ZkpEq& zkp, const G1& S1, const G1& T1, const G2& S2, const G2& T2, const mcl::fp::WindowMethod<I1>& Pmul, const MulG1& xPmul, const mcl::fp::WindowMethod<I2>& Qmul, const MulG2& yQmul)
	{
		const Fr& c = zkp.d_[0];
		const Fr& sp = zkp.d_[1];
		const Fr& ss = zkp.d_[2];
		const Fr& sm = zkp.d_[3];
		G1 R1, R2, X1;
		G2 R3, R4, X2;
		ElGamalEnc(R1, R2, sm, Pmul, xPmul, &sp);
		G1::mul(X1, S1, c);
		R1 -= X1;
		G1::mul(X1, T1, c);
		R2 -= X1;
		ElGamalEnc(R3, R4, sm, Qmul, yQmul, &ss);
		G2::mul(X2, S2, c);
		R3 -= X2;
		G2::mul(X2, T2, c);
		R4 -= X2;
		char buf[sizeof(G1) * 4 + sizeof(G2) * 4];
		cybozu::MemoryOutputStream os(buf, sizeof(buf));
		S1.save(os);
		T1.save(os);
		S2.save(os);
		T2.save(os);
		R1.save(os);
		R2.save(os);
		R3.save(os);
		R4.save(os);
		Fr c2;
		c2.setHashOf(buf, os.getPos());
		return c == c2;
	}
	/*
		encRand1, encRand2 are random values use for ElGamalEnc()
	*/
	template<class G1, class G2, class I1, class I2, class MulG1, class MulG2>
	static void makeZkpBinEq(ZkpBinEq& zkp, G1& S1, G1& T1, G2& S2, G2& T2, int m, const mcl::fp::WindowMethod<I1>& Pmul, const MulG1& xPmul, const mcl::fp::WindowMethod<I2>& Qmul, const MulG2& yQmul)
	{
		if (m != 0 && m != 1) throw cybozu::Exception("makeZkpBinEq:bad m") << m;
		Fr *d = &zkp.d_[0];
		Fr *spm = &zkp.d_[2];
		Fr& ss = zkp.d_[4];
		Fr& sp = zkp.d_[5];
		Fr& sm = zkp.d_[6];
		Fr p, s;
		p.setRand();
		s.setRand();
		ElGamalEnc(S1, T1, m, Pmul, xPmul, &p);
		ElGamalEnc(S2, T2, m, Qmul, yQmul, &s);
		d[1-m].setRand();
		spm[1-m].setRand();
		G1 R1[2], R2[2], X1;
		Pmul.mul(static_cast<I1&>(R1[1-m]), spm[1-m]);
		G1::mul(X1, T1, d[1-m]);
		R1[1-m] -= X1;
		if (m == 0) {
			G1::sub(X1, S1, P_);
			G1::mul(X1, X1, d[1-m]);
		} else {
			G1::mul(X1, S1, d[1-m]);
		}
		xPmul.mul(R2[1-m], spm[1-m]);
		R2[1-m] -= X1;
		Fr rpm, rp, rs, rm;
		rpm.setRand();
		rp.setRand();
		rs.setRand();
		rm.setRand();
		ElGamalEnc(R2[m], R1[m], 0, Pmul, xPmul, &rpm);
		G1 R3, R4;
		G2 R5, R6;
		ElGamalEnc(R4, R3, rm, Pmul, xPmul, &rp);
		ElGamalEnc(R6, R5, rm, Qmul, yQmul, &rs);
		char buf[sizeof(Fr) * 12];
		cybozu::MemoryOutputStream os(buf, sizeof(buf));
		S1.save(os);
		T1.save(os);
		R1[0].save(os);
		R1[1].save(os);
		R2[0].save(os);
		R2[1].save(os);
		R3.save(os);
		R4.save(os);
		R5.save(os);
		R6.save(os);
		Fr c;
		c.setHashOf(buf, os.getPos());
		Fr::sub(d[m], c, d[1-m]);
		Fr::mul(spm[m], d[m], p);
		spm[m] += rpm;
		Fr::mul(sp, c, p);
		sp += rp;
		Fr::mul(ss, c, s);
		ss += rs;
		Fr::mul(sm, c, m);
		sm += rm;
	}
	template<class G1, class G2, class I1, class I2, class MulG1, class MulG2>
	static bool verifyZkpBinEq(const ZkpBinEq& zkp, const G1& S1, const G1& T1, const G2& S2, const G2& T2, const mcl::fp::WindowMethod<I1>& Pmul, const MulG1& xPmul, const mcl::fp::WindowMethod<I2>& Qmul, const MulG2& yQmul)
	{
		const Fr *d = &zkp.d_[0];
		const Fr *spm = &zkp.d_[2];
		const Fr& ss = zkp.d_[4];
		const Fr& sp = zkp.d_[5];
		const Fr& sm = zkp.d_[6];
		G1 R1[2], R2[2], X1;
		for (int i = 0; i < 2; i++) {
			Pmul.mul(static_cast<I1&>(R1[i]), spm[i]);
			G1::mul(X1, T1, d[i]);
			R1[i] -= X1;
		}
		xPmul.mul(R2[0], spm[0]);
		G1::mul(X1, S1, d[0]);
		R2[0] -= X1;
		xPmul.mul(R2[1], spm[1]);
		G1::sub(X1, S1, P_);
		G1::mul(X1, X1, d[1]);
		R2[1] -= X1;
		Fr c;
		Fr::add(c, d[0], d[1]);
		G1 R3, R4;
		G2 R5, R6;
		ElGamalEnc(R4, R3, sm, Pmul, xPmul, &sp);
		G1::mul(X1, T1, c);
		R3 -= X1;
		G1::mul(X1, S1, c);
		R4 -= X1;
		ElGamalEnc(R6, R5, sm, Qmul, yQmul, &ss);
		G2 X2;
		G2::mul(X2, T2, c);
		R5 -= X2;
		G2::mul(X2, S2, c);
		R6 -= X2;
		char buf[sizeof(Fr) * 12];
		cybozu::MemoryOutputStream os(buf, sizeof(buf));
		S1.save(os);
		T1.save(os);
		R1[0].save(os);
		R1[1].save(os);
		R2[0].save(os);
		R2[1].save(os);
		R3.save(os);
		R4.save(os);
		R5.save(os);
		R6.save(os);
		Fr c2;
		c2.setHashOf(buf, os.getPos());
		return c == c2;
	}
	/*
		common method for PublicKey and PrecomputedPublicKey
	*/
	template<class T>
	struct PublicKeyMethod {
		/*
			you can use INT as int64_t and Fr,
			but the return type of dec() is int64_t.
		*/
		template<class INT>
		void enc(CipherTextG1& c, const INT& m) const
		{
			static_cast<const T&>(*this).encG1(c, m);
		}
		template<class INT>
		void enc(CipherTextG2& c, const INT& m) const
		{
			static_cast<const T&>(*this).encG2(c, m);
		}
		template<class INT>
		void enc(CipherTextA& c, const INT& m) const
		{
			enc(c.c1_, m);
			enc(c.c2_, m);
		}
		template<class INT>
		void enc(CipherTextGT& c, const INT& m) const
		{
			static_cast<const T&>(*this).encGT(c, m);
		}
		template<class INT>
		void enc(CipherText& c, const INT& m, bool multiplied = false) const
		{
			c.isMultiplied_ = multiplied;
			if (multiplied) {
				enc(c.m_, m);
			} else {
				enc(c.a_, m);
			}
		}
		/*
			reRand method is for circuit privacy
		*/
		template<class CT>
		void reRandT(CT& c) const
		{
			CT c0;
			static_cast<const T&>(*this).enc(c0, 0);
			CT::add(c, c, c0);
		}
		void reRand(CipherTextG1& c) const { reRandT(c); }
		void reRand(CipherTextG2& c) const { reRandT(c); }
		void reRand(CipherTextGT& c) const { reRandT(c); }
		void reRand(CipherText& c) const
		{
			if (c.isMultiplied()) {
				reRandT(c.m_);
			} else {
				reRandT(c.a_);
			}
		}
		/*
			convert from CipherTextG1 to CipherTextGT
		*/
		void convert(CipherTextGT& cm, const CipherTextG1& c1) const
		{
			/*
				Enc(1) = (S, T) = (Q + r yQ, rQ) = (Q, 0) if r = 0
				cm = c1 * (Q, 0) = (S, T) * (Q, 0) = (e(S, Q), 1, e(T, Q), 1)
			*/
			precomputedMillerLoop(cm.g_[0], c1.getS(), Qcoeff_);
			finalExp(cm.g_[0], cm.g_[0]);
			precomputedMillerLoop(cm.g_[2], c1.getT(), Qcoeff_);
			finalExp(cm.g_[2], cm.g_[2]);

			cm.g_[1] = cm.g_[3] = 1;
		}
		/*
			convert from CipherTextG2 to CipherTextGT
		*/
		void convert(CipherTextGT& cm, const CipherTextG2& c2) const
		{
			/*
				Enc(1) = (S, T) = (P + r xP, rP) = (P, 0) if r = 0
				cm = (P, 0) * c2 = (e(P, S), e(P, T), 1, 1)
			*/
			pairing(cm.g_[0], P_, c2.getS());
			pairing(cm.g_[1], P_, c2.getT());
			cm.g_[2] = cm.g_[3] = 1;
		}
		void convert(CipherTextGT& cm, const CipherTextA& ca) const
		{
			convert(cm, ca.c1_);
		}
		void convert(CipherText& cm, const CipherText& ca) const
		{
			if (ca.isMultiplied()) throw cybozu::Exception("she:PublicKey:convertCipherText:already isMultiplied");
			cm.isMultiplied_ = true;
			convert(cm.m_, ca.a_);
		}
	};
public:
	class PublicKey : public fp::Serializable<PublicKey,
		PublicKeyMethod<PublicKey> > {
		G1 xP_;
		G2 yQ_;
		friend class SecretKey;
		friend class PrecomputedPublicKey;
		template<class T>
		friend struct PublicKeyMethod;
		template<class G>
		struct MulG {
			const G& base;
			MulG(const G& base) : base(base) {}
			template<class INT>
			void mul(G& out, const INT& m) const
			{
				G::mul(out, base, m);
			}
		};
		void set(const Fr& x, const Fr& y)
		{
			G1::mul(xP_, P_, x);
			if (!isG1only_) G2::mul(yQ_, Q_, y);
		}
		template<class INT>
		void encG1(CipherTextG1& c, const INT& m) const
		{
			const MulG<G1> xPmul(xP_);
			ElGamalEnc(c.S_, c.T_, m, PhashTbl_.getWM(), xPmul);
		}
		template<class INT>
		void encG2(CipherTextG2& c, const INT& m) const
		{
			const MulG<G2> yQmul(yQ_);
			ElGamalEnc(c.S_, c.T_, m, QhashTbl_.getWM(), yQmul);
		}
public:
		void encWithZkpBin(CipherTextG1& c, ZkpBin& zkp, int m) const
		{
			Fr encRand;
			encRand.setRand();
			const MulG<G1> xPmul(xP_);
			ElGamalEnc(c.S_, c.T_, m, PhashTbl_.getWM(), xPmul, &encRand);
			makeZkpBin(zkp, c.S_, c.T_, encRand, P_, m,  PhashTbl_.getWM(), xPmul);
		}
		void encWithZkpBin(CipherTextG2& c, ZkpBin& zkp, int m) const
		{
			Fr encRand;
			encRand.setRand();
			const MulG<G2> yQmul(yQ_);
			ElGamalEnc(c.S_, c.T_, m, QhashTbl_.getWM(), yQmul, &encRand);
			makeZkpBin(zkp, c.S_, c.T_, encRand, Q_, m,  QhashTbl_.getWM(), yQmul);
		}
		bool verify(const CipherTextG1& c, const ZkpBin& zkp) const
		{
			const MulG<G1> xPmul(xP_);
			return verifyZkpBin(c.S_, c.T_, P_, zkp, PhashTbl_.getWM(), xPmul);
		}
		bool verify(const CipherTextG2& c, const ZkpBin& zkp) const
		{
			const MulG<G2> yQmul(yQ_);
			return verifyZkpBin(c.S_, c.T_, Q_, zkp, QhashTbl_.getWM(), yQmul);
		}
		template<class INT>
		void encWithZkpEq(CipherTextG1& c1, CipherTextG2& c2, ZkpEq& zkp, const INT& m) const
		{
			const MulG<G1> xPmul(xP_);
			const MulG<G2> yQmul(yQ_);
			makeZkpEq(zkp, c1.S_, c1.T_, c2.S_, c2.T_, m, PhashTbl_.getWM(), xPmul, QhashTbl_.getWM(), yQmul);
		}
		bool verify(const CipherTextG1& c1, const CipherTextG2& c2, const ZkpEq& zkp) const
		{
			const MulG<G1> xPmul(xP_);
			const MulG<G2> yQmul(yQ_);
			return verifyZkpEq(zkp, c1.S_, c1.T_, c2.S_, c2.T_, PhashTbl_.getWM(), xPmul, QhashTbl_.getWM(), yQmul);
		}
		void encWithZkpBinEq(CipherTextG1& c1, CipherTextG2& c2, ZkpBinEq& zkp, int m) const
		{
			const MulG<G1> xPmul(xP_);
			const MulG<G2> yQmul(yQ_);
			makeZkpBinEq(zkp, c1.S_, c1.T_, c2.S_, c2.T_, m, PhashTbl_.getWM(), xPmul, QhashTbl_.getWM(), yQmul);
		}
		bool verify(const CipherTextG1& c1, const CipherTextG2& c2, const ZkpBinEq& zkp) const
		{
			const MulG<G1> xPmul(xP_);
			const MulG<G2> yQmul(yQ_);
			return verifyZkpBinEq(zkp, c1.S_, c1.T_, c2.S_, c2.T_, PhashTbl_.getWM(), xPmul, QhashTbl_.getWM(), yQmul);
		}
		template<class INT>
		void encGT(CipherTextGT& c, const INT& m) const
		{
			/*
				(s, t, u, v) = ((e^x)^a (e^y)^b (e^-xy)^c e^m, e^b, e^a, e^c)
				s = e(a xP + m P, Q)e(b P - c xP, yQ)
			*/
			Fr ra, rb, rc;
			ra.setRand();
			rb.setRand();
			rc.setRand();
			GT e;

			G1 P1, P2;
			G1::mul(P1, xP_, ra);
			if (m) {
//				G1::mul(P2, P, m);
				PhashTbl_.mulByWindowMethod(P2, m);
				P1 += P2;
			}
//			millerLoop(c.g[0], P1, Q);
			precomputedMillerLoop(c.g_[0], P1, Qcoeff_);
//			G1::mul(P1, P, rb);
			PhashTbl_.mulByWindowMethod(P1, rb);
			G1::mul(P2, xP_, rc);
			P1 -= P2;
			millerLoop(e, P1, yQ_);
			c.g_[0] *= e;
			finalExp(c.g_[0], c.g_[0]);
#if 1
			ePQhashTbl_.mulByWindowMethod(c.g_[1], rb);
			ePQhashTbl_.mulByWindowMethod(c.g_[2], ra);
			ePQhashTbl_.mulByWindowMethod(c.g_[3], rc);
#else
			GT::pow(c.g_[1], ePQ_, rb);
			GT::pow(c.g_[2], ePQ_, ra);
			GT::pow(c.g_[3], ePQ_, rc);
#endif
		}
	public:
		template<class InputStream>
		void load(bool *pb, InputStream& is, int ioMode = IoSerialize)
		{
			xP_.load(pb, is, ioMode); if (!*pb) return;
			if (!isG1only_) yQ_.load(pb, is, ioMode);
		}
		template<class OutputStream>
		void save(bool *pb, OutputStream& os, int ioMode = IoSerialize) const
		{
			const char sep = *fp::getIoSeparator(ioMode);
			xP_.save(pb, os, ioMode); if (!*pb) return;
			if (isG1only_) return;
			if (sep) {
				cybozu::writeChar(pb, os, sep);
				if (!*pb) return;
			}
			yQ_.save(pb, os, ioMode);
		}
		template<class InputStream>
		void load(InputStream& is, int ioMode = IoSerialize)
		{
			bool b;
			load(&b, is, ioMode);
			if (!b) throw cybozu::Exception("she:PublicKey:load");
		}
		template<class OutputStream>
		void save(OutputStream& os, int ioMode = IoSerialize) const
		{
			bool b;
			save(&b, os, ioMode);
			if (!b) throw cybozu::Exception("she:PublicKey:save");
		}
		friend std::istream& operator>>(std::istream& is, PublicKey& self)
		{
			self.load(is, fp::detectIoMode(G1::getIoMode(), is));
			return is;
		}
		friend std::ostream& operator<<(std::ostream& os, const PublicKey& self)
		{
			self.save(os, fp::detectIoMode(G1::getIoMode(), os));
			return os;
		}
		bool operator==(const PublicKey& rhs) const
		{
			return xP_ == rhs.xP_ && (isG1only_ || yQ_ == rhs.yQ_);
		}
		bool operator!=(const PublicKey& rhs) const { return !operator==(rhs); }
	};

	class PrecomputedPublicKey : public fp::Serializable<PrecomputedPublicKey,
		PublicKeyMethod<PrecomputedPublicKey> > {
		typedef local::InterfaceForHashTable<GT, false> GTasEC;
		typedef mcl::fp::WindowMethod<GTasEC> GTwin;
		template<class T>
		friend struct PublicKeyMethod;
		GT exPQ_;
		GT eyPQ_;
		GT exyPQ_;
		GTwin exPQwm_;
		GTwin eyPQwm_;
		GTwin exyPQwm_;
		mcl::fp::WindowMethod<G1> xPwm_;
		mcl::fp::WindowMethod<G2> yQwm_;
		template<class T>
		void mulByWindowMethod(GT& x, const GTwin& wm, const T& y) const
		{
			wm.mul(static_cast<GTasEC&>(x), y);
		}
		template<class INT>
		void encG1(CipherTextG1& c, const INT& m) const
		{
			ElGamalEnc(c.S_, c.T_, m, PhashTbl_.getWM(), xPwm_);
		}
		template<class INT>
		void encG2(CipherTextG2& c, const INT& m) const
		{
			ElGamalEnc(c.S_, c.T_, m, QhashTbl_.getWM(), yQwm_);
		}
		template<class INT>
		void encGT(CipherTextGT& c, const INT& m) const
		{
			/*
				(s, t, u, v) = (e^m e^(xya), (e^x)^b, (e^y)^c, e^(b + c - a))
			*/
			Fr ra, rb, rc;
			ra.setRand();
			rb.setRand();
			rc.setRand();
			GT t;
			ePQhashTbl_.mulByWindowMethod(c.g_[0], m); // e^m
			mulByWindowMethod(t, exyPQwm_, ra); // (e^xy)^a
			c.g_[0] *= t;
			mulByWindowMethod(c.g_[1], exPQwm_, rb); // (e^x)^b
			mulByWindowMethod(c.g_[2], eyPQwm_, rc); // (e^y)^c
			rb += rc;
			rb -= ra;
			ePQhashTbl_.mulByWindowMethod(c.g_[3], rb);
		}
	public:
		void init(const PublicKey& pub)
		{
			const size_t bitSize = Fr::getBitSize();
			xPwm_.init(pub.xP_, bitSize, local::winSize);
			if (isG1only_) return;
			yQwm_.init(pub.yQ_, bitSize, local::winSize);
			pairing(exPQ_, pub.xP_, Q_);
			pairing(eyPQ_, P_, pub.yQ_);
			pairing(exyPQ_, pub.xP_, pub.yQ_);
			exPQwm_.init(static_cast<const GTasEC&>(exPQ_), bitSize, local::winSize);
			eyPQwm_.init(static_cast<const GTasEC&>(eyPQ_), bitSize, local::winSize);
			exyPQwm_.init(static_cast<const GTasEC&>(exyPQ_), bitSize, local::winSize);
		}
		void encWithZkpBin(CipherTextG1& c, ZkpBin& zkp, int m) const
		{
			Fr encRand;
			encRand.setRand();
			ElGamalEnc(c.S_, c.T_, m, PhashTbl_.getWM(), xPwm_, &encRand);
			makeZkpBin(zkp, c.S_, c.T_, encRand, P_, m,  PhashTbl_.getWM(), xPwm_);
		}
		void encWithZkpBin(CipherTextG2& c, ZkpBin& zkp, int m) const
		{
			Fr encRand;
			encRand.setRand();
			ElGamalEnc(c.S_, c.T_, m, QhashTbl_.getWM(), yQwm_, &encRand);
			makeZkpBin(zkp, c.S_, c.T_, encRand, Q_, m,  QhashTbl_.getWM(), yQwm_);
		}
		bool verify(const CipherTextG1& c, const ZkpBin& zkp) const
		{
			return verifyZkpBin(c.S_, c.T_, P_, zkp, PhashTbl_.getWM(), xPwm_);
		}
		bool verify(const CipherTextG2& c, const ZkpBin& zkp) const
		{
			return verifyZkpBin(c.S_, c.T_, Q_, zkp, QhashTbl_.getWM(), yQwm_);
		}
		template<class INT>
		void encWithZkpEq(CipherTextG1& c1, CipherTextG2& c2, ZkpEq& zkp, const INT& m) const
		{
			makeZkpEq(zkp, c1.S_, c1.T_, c2.S_, c2.T_, m, PhashTbl_.getWM(), xPwm_, QhashTbl_.getWM(), yQwm_);
		}
		bool verify(const CipherTextG1& c1, const CipherTextG2& c2, const ZkpEq& zkp) const
		{
			return verifyZkpEq(zkp, c1.S_, c1.T_, c2.S_, c2.T_, PhashTbl_.getWM(), xPwm_, QhashTbl_.getWM(), yQwm_);
		}
		void encWithZkpBinEq(CipherTextG1& c1, CipherTextG2& c2, ZkpBinEq& zkp, int m) const
		{
			makeZkpBinEq(zkp, c1.S_, c1.T_, c2.S_, c2.T_, m, PhashTbl_.getWM(), xPwm_, QhashTbl_.getWM(), yQwm_);
		}
		bool verify(const CipherTextG1& c1, const CipherTextG2& c2, const ZkpBinEq& zkp) const
		{
			return verifyZkpBinEq(zkp, c1.S_, c1.T_, c2.S_, c2.T_, PhashTbl_.getWM(), xPwm_, QhashTbl_.getWM(), yQwm_);
		}
	};
	class CipherTextA {
		CipherTextG1 c1_;
		CipherTextG2 c2_;
		friend class SecretKey;
		friend class PublicKey;
		friend class CipherTextGT;
		template<class T>
		friend struct PublicKeyMethod;
	public:
		void clear()
		{
			c1_.clear();
			c2_.clear();
		}
		static void add(CipherTextA& z, const CipherTextA& x, const CipherTextA& y)
		{
			CipherTextG1::add(z.c1_, x.c1_, y.c1_);
			CipherTextG2::add(z.c2_, x.c2_, y.c2_);
		}
		static void sub(CipherTextA& z, const CipherTextA& x, const CipherTextA& y)
		{
			CipherTextG1::sub(z.c1_, x.c1_, y.c1_);
			CipherTextG2::sub(z.c2_, x.c2_, y.c2_);
		}
		static void mul(CipherTextA& z, const CipherTextA& x, int64_t y)
		{
			CipherTextG1::mul(z.c1_, x.c1_, y);
			CipherTextG2::mul(z.c2_, x.c2_, y);
		}
		static void neg(CipherTextA& y, const CipherTextA& x)
		{
			CipherTextG1::neg(y.c1_, x.c1_);
			CipherTextG2::neg(y.c2_, x.c2_);
		}
		void add(const CipherTextA& c) { add(*this, *this, c); }
		void sub(const CipherTextA& c) { sub(*this, *this, c); }
		template<class InputStream>
		void load(bool *pb, InputStream& is, int ioMode = IoSerialize)
		{
			c1_.load(pb, is, ioMode); if (!*pb) return;
			c2_.load(pb, is, ioMode);
		}
		template<class OutputStream>
		void save(bool *pb, OutputStream& os, int ioMode = IoSerialize) const
		{
			const char sep = *fp::getIoSeparator(ioMode);
			c1_.save(pb, os, ioMode); if (!*pb) return;
			if (sep) {
				cybozu::writeChar(pb, os, sep);
				if (!*pb) return;
			}
			c2_.save(pb, os, ioMode);
		}
		template<class InputStream>
		void load(InputStream& is, int ioMode = IoSerialize)
		{
			bool b;
			load(&b, is, ioMode);
			if (!b) throw cybozu::Exception("she:CipherTextA:load");
		}
		template<class OutputStream>
		void save(OutputStream& os, int ioMode = IoSerialize) const
		{
			bool b;
			save(&b, os, ioMode);
			if (!b) throw cybozu::Exception("she:CipherTextA:save");
		}
		friend std::istream& operator>>(std::istream& is, CipherTextA& self)
		{
			self.load(is, fp::detectIoMode(G1::getIoMode(), is));
			return is;
		}
		friend std::ostream& operator<<(std::ostream& os, const CipherTextA& self)
		{
			self.save(os, fp::detectIoMode(G1::getIoMode(), os));
			return os;
		}
		bool operator==(const CipherTextA& rhs) const
		{
			return c1_ == rhs.c1_ && c2_ == rhs.c2_;
		}
		bool operator!=(const CipherTextA& rhs) const { return !operator==(rhs); }
	};

	class CipherTextGT : public fp::Serializable<CipherTextGT> {
		GT g_[4];
		friend class SecretKey;
		friend class PublicKey;
		friend class PrecomputedPublicKey;
		friend class CipherTextA;
		template<class T>
		friend struct PublicKeyMethod;
	public:
		void clear()
		{
			for (int i = 0; i < 4; i++) {
				g_[i].setOne();
			}
		}
		static void neg(CipherTextGT& y, const CipherTextGT& x)
		{
			for (int i = 0; i < 4; i++) {
				GT::unitaryInv(y.g_[i], x.g_[i]);
			}
		}
		static void add(CipherTextGT& z, const CipherTextGT& x, const CipherTextGT& y)
		{
			/*
				(g[i]) + (g'[i]) = (g[i] * g'[i])
			*/
			for (int i = 0; i < 4; i++) {
				GT::mul(z.g_[i], x.g_[i], y.g_[i]);
			}
		}
		static void sub(CipherTextGT& z, const CipherTextGT& x, const CipherTextGT& y)
		{
			/*
				(g[i]) - (g'[i]) = (g[i] / g'[i])
			*/
			GT t;
			for (size_t i = 0; i < 4; i++) {
				GT::unitaryInv(t, y.g_[i]);
				GT::mul(z.g_[i], x.g_[i], t);
			}
		}
		static void mulML(CipherTextGT& z, const CipherTextG1& x, const CipherTextG2& y)
		{
			/*
				(S1, T1) * (S2, T2) = (ML(S1, S2), ML(S1, T2), ML(T1, S2), ML(T1, T2))
			*/
			tensorProductML(z.g_, x.S_, x.T_, y.S_, y.T_);
		}
		static void finalExp(CipherTextGT& y, const CipherTextGT& x)
		{
			finalExp4(y.g_, x.g_);
		}
		/*
			mul(x, y) = mulML(x, y) + finalExp
			mul(c11, c12) + mul(c21, c22)
			= finalExp(mulML(c11, c12) + mulML(c21, c22)),
			then one finalExp can be reduced
		*/
		static void mul(CipherTextGT& z, const CipherTextG1& x, const CipherTextG2& y)
		{
			/*
				(S1, T1) * (S2, T2) = (e(S1, S2), e(S1, T2), e(T1, S2), e(T1, T2))
			*/
			mulML(z, x, y);
			finalExp(z, z);
		}
		static void mul(CipherTextGT& z, const CipherTextA& x, const CipherTextA& y)
		{
			mul(z, x.c1_, y.c2_);
		}
		static void mul(CipherTextGT& z, const CipherTextGT& x, int64_t y)
		{
			for (int i = 0; i < 4; i++) {
				GT::pow(z.g_[i], x.g_[i], y);
			}
		}
		void add(const CipherTextGT& c) { add(*this, *this, c); }
		void sub(const CipherTextGT& c) { sub(*this, *this, c); }
		template<class InputStream>
		void load(bool *pb, InputStream& is, int ioMode = IoSerialize)
		{
			for (int i = 0; i < 4; i++) {
				g_[i].load(pb, is, ioMode); if (!*pb) return;
			}
		}
		template<class OutputStream>
		void save(bool *pb, OutputStream& os, int ioMode = IoSerialize) const
		{
			const char sep = *fp::getIoSeparator(ioMode);
			g_[0].save(pb, os, ioMode); if (!*pb) return;
			for (int i = 1; i < 4; i++) {
				if (sep) {
					cybozu::writeChar(pb, os, sep);
					if (!*pb) return;
				}
				g_[i].save(pb, os, ioMode); if (!*pb) return;
			}
		}
		template<class InputStream>
		void load(InputStream& is, int ioMode = IoSerialize)
		{
			bool b;
			load(&b, is, ioMode);
			if (!b) throw cybozu::Exception("she:CipherTextGT:load");
		}
		template<class OutputStream>
		void save(OutputStream& os, int ioMode = IoSerialize) const
		{
			bool b;
			save(&b, os, ioMode);
			if (!b) throw cybozu::Exception("she:CipherTextGT:save");
		}
		friend std::istream& operator>>(std::istream& is, CipherTextGT& self)
		{
			self.load(is, fp::detectIoMode(G1::getIoMode(), is));
			return is;
		}
		friend std::ostream& operator<<(std::ostream& os, const CipherTextGT& self)
		{
			self.save(os, fp::detectIoMode(G1::getIoMode(), os));
			return os;
		}
		bool operator==(const CipherTextGT& rhs) const
		{
			for (int i = 0; i < 4; i++) {
				if (g_[i] != rhs.g_[i]) return false;
			}
			return true;
		}
		bool operator!=(const CipherTextGT& rhs) const { return !operator==(rhs); }
	};

	class CipherText : public fp::Serializable<CipherText> {
		bool isMultiplied_;
		CipherTextA a_;
		CipherTextGT m_;
		friend class SecretKey;
		friend class PublicKey;
		template<class T>
		friend struct PublicKeyMethod;
	public:
		CipherText() : isMultiplied_(false) {}
		void clearAsAdded()
		{
			isMultiplied_ = false;
			a_.clear();
		}
		void clearAsMultiplied()
		{
			isMultiplied_ = true;
			m_.clear();
		}
		bool isMultiplied() const { return isMultiplied_; }
		static void add(CipherText& z, const CipherText& x, const CipherText& y)
		{
			if (x.isMultiplied() && y.isMultiplied()) {
				z.isMultiplied_ = true;
				CipherTextGT::add(z.m_, x.m_, y.m_);
				return;
			}
			if (!x.isMultiplied() && !y.isMultiplied()) {
				z.isMultiplied_ = false;
				CipherTextA::add(z.a_, x.a_, y.a_);
				return;
			}
			throw cybozu::Exception("she:CipherText:add:mixed CipherText");
		}
		static void sub(CipherText& z, const CipherText& x, const CipherText& y)
		{
			if (x.isMultiplied() && y.isMultiplied()) {
				z.isMultiplied_ = true;
				CipherTextGT::sub(z.m_, x.m_, y.m_);
				return;
			}
			if (!x.isMultiplied() && !y.isMultiplied()) {
				z.isMultiplied_ = false;
				CipherTextA::sub(z.a_, x.a_, y.a_);
				return;
			}
			throw cybozu::Exception("she:CipherText:sub:mixed CipherText");
		}
		static void neg(CipherText& y, const CipherText& x)
		{
			if (x.isMultiplied()) {
				y.isMultiplied_ = true;
				CipherTextGT::neg(y.m_, x.m_);
				return;
			} else {
				y.isMultiplied_ = false;
				CipherTextA::neg(y.a_, x.a_);
				return;
			}
		}
		static void mul(CipherText& z, const CipherText& x, const CipherText& y)
		{
			if (x.isMultiplied() || y.isMultiplied()) {
				throw cybozu::Exception("she:CipherText:mul:mixed CipherText");
			}
			z.isMultiplied_ = true;
			CipherTextGT::mul(z.m_, x.a_, y.a_);
		}
		static void mul(CipherText& z, const CipherText& x, int64_t y)
		{
			if (x.isMultiplied()) {
				CipherTextGT::mul(z.m_, x.m_, y);
			} else {
				CipherTextA::mul(z.a_, x.a_, y);
			}
		}
		void add(const CipherText& c) { add(*this, *this, c); }
		void sub(const CipherText& c) { sub(*this, *this, c); }
		void mul(const CipherText& c) { mul(*this, *this, c); }
		template<class InputStream>
		void load(bool *pb, InputStream& is, int ioMode = IoSerialize)
		{
			cybozu::writeChar(pb, isMultiplied_ ? '0' : '1', is); if (!*pb) return;
			if (isMultiplied()) {
				m_.load(pb, is, ioMode);
			} else {
				a_.load(pb, is, ioMode);
			}
		}
		template<class OutputStream>
		void save(bool *pb, OutputStream& os, int ioMode = IoSerialize) const
		{
			char c;
			if (!cybozu::readChar(&c, os)) return;
			if (c == '0' || c == '1') {
				isMultiplied_ = c == '0';
			} else {
				*pb = false;
				return;
			}
			if (isMultiplied()) {
				m_.save(pb, os, ioMode);
			} else {
				a_.save(pb, os, ioMode);
			}
		}
		template<class InputStream>
		void load(InputStream& is, int ioMode = IoSerialize)
		{
			bool b;
			load(&b, is, ioMode);
			if (!b) throw cybozu::Exception("she:CipherText:load");
		}
		template<class OutputStream>
		void save(OutputStream& os, int ioMode = IoSerialize) const
		{
			bool b;
			save(&b, os, ioMode);
			if (!b) throw cybozu::Exception("she:CipherText:save");
		}
		friend std::istream& operator>>(std::istream& is, CipherText& self)
		{
			self.load(is, fp::detectIoMode(G1::getIoMode(), is));
			return is;
		}
		friend std::ostream& operator<<(std::ostream& os, const CipherText& self)
		{
			self.save(os, fp::detectIoMode(G1::getIoMode(), os));
			return os;
		}
		bool operator==(const CipherTextGT& rhs) const
		{
			if (isMultiplied() != rhs.isMultiplied()) return false;
			if (isMultiplied()) {
				return m_ == rhs.m_;
			}
			return a_ == rhs.a_;
		}
		bool operator!=(const CipherTextGT& rhs) const { return !operator==(rhs); }
	};
};
typedef local::HashTable<G1> HashTableG1;
typedef local::HashTable<G2> HashTableG2;
typedef local::HashTable<Fp12, false> HashTableGT;

template<size_t dummyInpl> G1 SHET<dummyInpl>::P_;
template<size_t dummyInpl> G2 SHET<dummyInpl>::Q_;
template<size_t dummyInpl> Fp12 SHET<dummyInpl>::ePQ_;
template<size_t dummyInpl> std::vector<Fp6> SHET<dummyInpl>::Qcoeff_;
template<size_t dummyInpl> HashTableG1 SHET<dummyInpl>::PhashTbl_;
template<size_t dummyInpl> HashTableG2 SHET<dummyInpl>::QhashTbl_;
template<size_t dummyInpl> HashTableGT SHET<dummyInpl>::ePQhashTbl_;
template<size_t dummyInpl> bool SHET<dummyInpl>::useDecG1ViaGT_;
template<size_t dummyInpl> bool SHET<dummyInpl>::useDecG2ViaGT_;
template<size_t dummyInpl> bool SHET<dummyInpl>::isG1only_;
typedef mcl::she::SHET<> SHE;
typedef SHE::SecretKey SecretKey;
typedef SHE::PublicKey PublicKey;
typedef SHE::PrecomputedPublicKey PrecomputedPublicKey;
typedef SHE::CipherTextG1 CipherTextG1;
typedef SHE::CipherTextG2 CipherTextG2;
typedef SHE::CipherTextGT CipherTextGT;
typedef SHE::CipherTextA CipherTextA;
typedef CipherTextGT CipherTextGM; // old class
typedef SHE::CipherText CipherText;
typedef SHE::ZkpBin ZkpBin;
typedef SHE::ZkpEq ZkpEq;
typedef SHE::ZkpBinEq ZkpBinEq;

inline void init(const mcl::CurveParam& cp = mcl::BN254, size_t hashSize = 1024, size_t tryNum = local::defaultTryNum)
{
	SHE::init(cp, hashSize, tryNum);
}
inline void initG1only(const mcl::EcParam& para, size_t hashSize = 1024, size_t tryNum = local::defaultTryNum)
{
	SHE::initG1only(para, hashSize, tryNum);
}
inline void init(size_t hashSize, size_t tryNum = local::defaultTryNum) { SHE::init(hashSize, tryNum); }
inline void setRangeForG1DLP(size_t hashSize) { SHE::setRangeForG1DLP(hashSize); }
inline void setRangeForG2DLP(size_t hashSize) { SHE::setRangeForG2DLP(hashSize); }
inline void setRangeForGTDLP(size_t hashSize) { SHE::setRangeForGTDLP(hashSize); }
inline void setRangeForDLP(size_t hashSize) { SHE::setRangeForDLP(hashSize); }
inline void setTryNum(size_t tryNum) { SHE::setTryNum(tryNum); }
inline void useDecG1ViaGT(bool use = true) { SHE::useDecG1ViaGT(use); }
inline void useDecG2ViaGT(bool use = true) { SHE::useDecG2ViaGT(use); }
inline HashTableG1& getHashTableG1() { return SHE::PhashTbl_; }
inline HashTableG2& getHashTableG2() { return SHE::QhashTbl_; }
inline HashTableGT& getHashTableGT() { return SHE::ePQhashTbl_; }

inline void add(CipherTextG1& z, const CipherTextG1& x, const CipherTextG1& y) { CipherTextG1::add(z, x, y); }
inline void add(CipherTextG2& z, const CipherTextG2& x, const CipherTextG2& y) { CipherTextG2::add(z, x, y); }
inline void add(CipherTextGT& z, const CipherTextGT& x, const CipherTextGT& y) { CipherTextGT::add(z, x, y); }
inline void add(CipherText& z, const CipherText& x, const CipherText& y) { CipherText::add(z, x, y); }

inline void sub(CipherTextG1& z, const CipherTextG1& x, const CipherTextG1& y) { CipherTextG1::sub(z, x, y); }
inline void sub(CipherTextG2& z, const CipherTextG2& x, const CipherTextG2& y) { CipherTextG2::sub(z, x, y); }
inline void sub(CipherTextGT& z, const CipherTextGT& x, const CipherTextGT& y) { CipherTextGT::sub(z, x, y); }
inline void sub(CipherText& z, const CipherText& x, const CipherText& y) { CipherText::sub(z, x, y); }

inline void neg(CipherTextG1& y, const CipherTextG1& x) { CipherTextG1::neg(y, x); }
inline void neg(CipherTextG2& y, const CipherTextG2& x) { CipherTextG2::neg(y, x); }
inline void neg(CipherTextGT& y, const CipherTextGT& x) { CipherTextGT::neg(y, x); }
inline void neg(CipherText& y, const CipherText& x) { CipherText::neg(y, x); }

template<class INT>
inline void mul(CipherTextG1& z, const CipherTextG1& x, const INT& y) { CipherTextG1::mul(z, x, y); }
template<class INT>
inline void mul(CipherTextG2& z, const CipherTextG2& x, const INT& y) { CipherTextG2::mul(z, x, y); }
template<class INT>
inline void mul(CipherTextGT& z, const CipherTextGT& x, const INT& y) { CipherTextGT::mul(z, x, y); }
template<class INT>
inline void mul(CipherText& z, const CipherText& x, const INT& y) { CipherText::mul(z, x, y); }

inline void mul(CipherTextGT& z, const CipherTextG1& x, const CipherTextG2& y) { CipherTextGT::mul(z, x, y); }
inline void mul(CipherText& z, const CipherText& x, const CipherText& y) { CipherText::mul(z, x, y); }

} } // mcl::she