"""
*******************************************************************************
*   Ledger Blue
*   (c) 2016 Ledger
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
********************************************************************************
"""

import hashlib
try:
	import secp256k1
	USE_SECP = secp256k1.HAS_ECDH
except (ImportError, AttributeError):
	USE_SECP = False

if not USE_SECP:
	import ecpy
	from builtins import int
	from ecpy.curves import Curve, Point
	from ecpy.keys import ECPublicKey, ECPrivateKey
	from ecpy.ecdsa import ECDSA
	CURVE_SECP256K1 = Curve.get_curve('secp256k1')
	SIGNER = ECDSA()

class PublicKey(object):
	def __init__(self, pubkey=None, raw=False):
		if USE_SECP:
			self.obj = secp256k1.PublicKey(pubkey, raw)
		else:
			if not raw:
				raise Exception("Non raw init unsupported")
			pubkey = pubkey[1:]
			x = int.from_bytes(pubkey[0:32], 'big')
			y = int.from_bytes(pubkey[32:], 'big')
			self.obj = ECPublicKey(Point(x, y, CURVE_SECP256K1))

	def ecdsa_deserialize(self, ser_sig):
		if USE_SECP:
			return self.obj.ecdsa_deserialize(ser_sig)
		else:
			return ser_sig

	def serialize(self, compressed=True):
		if USE_SECP:
			return self.obj.serialize(compressed)
		else:
			if not compressed:
				out = b"\x04"
				out += self.obj.W.x.to_bytes(32, 'big')
				out += self.obj.W.y.to_bytes(32, 'big')
			else:
				out = b"\x03" if ((self.obj.W.y & 1) != 0) else "\x02"
				out += self.obj.W.x.to_bytes(32, 'big')
			return out

	def ecdh(self, scalar, scpv3=False):
		if USE_SECP:
			return self.obj.ecdh(scalar)
		else:
			scalar = int.from_bytes(scalar, 'big')
			point = self.obj.W * scalar
			if not scpv3:
				# libsecp256k1 style secret
				out = b"\x03" if ((point.y & 1) != 0) else b"\x02"
				out += point.x.to_bytes(32, 'big')
			else:
				out = point.x.to_bytes(32, 'big')
				out += b"\x00\x00\x00\x01"
			hash = hashlib.sha256()
			hash.update(out)
			return hash.digest()

	def tweak_add(self, scalar):
		if USE_SECP:
			self.obj = self.obj.tweak_add(scalar)
		else:
			scalar = int.from_bytes(scalar, 'big')
			privKey = ECPrivateKey(scalar, CURVE_SECP256K1)
			self.obj = ECPublicKey(self.obj.W + privKey.get_public_key().W)

	def ecdsa_verify(self, msg, raw_sig, raw=False, digest=hashlib.sha256):
		if USE_SECP:
			return self.obj.ecdsa_verify(msg, raw_sig, raw, digest)
		else:
			if not raw:
				h = digest()
				h.update(msg)
				msg = h.digest()
			raw_sig = bytearray(raw_sig)
			return SIGNER.verify(msg, raw_sig, self.obj)

class PrivateKey(object):

	def __init__(self, privkey=None, raw=True):
		if USE_SECP:
			self.obj = secp256k1.PrivateKey(privkey, raw)
			self.pubkey = self.obj.pubkey
		else:
			if not raw:
				raise Exception("Non raw init unsupported")
			if privkey == None:
				privkey = ecpy.ecrand.rnd(CURVE_SECP256K1.order)
			else:
				privkey = int.from_bytes(privkey,'big')
			self.obj = ECPrivateKey(privkey, CURVE_SECP256K1)
			pubkey = self.obj.get_public_key().W
			out = b"\x04"
			out += pubkey.x.to_bytes(32, 'big')
			out += pubkey.y.to_bytes(32, 'big')
			self.pubkey = PublicKey(out, raw=True)

	def serialize(self):
		if USE_SECP:
			return self.obj.serialize()
		else:
			return "%.64x"%self.obj.d

	def ecdsa_serialize(self, raw_sig):
		if USE_SECP:
			return self.obj.ecdsa_serialize(raw_sig)
		else:
			return raw_sig

	def ecdsa_sign(self, msg, raw=False, digest=hashlib.sha256, rfc6979=False):
		if USE_SECP:
			return self.obj.ecdsa_sign(msg, raw, digest)
		else:
			if not raw:
				h = digest()
				h.update(msg)
				msg = h.digest()
			if rfc6979:
				signature = SIGNER.sign_rfc6979(msg, self.obj, digest, True)
			else:
				signature = SIGNER.sign(msg, self.obj, True)
			return signature
