﻿using System;

namespace Snet
{
	public class DH64
	{
		private const ulong p = 0xffffffffffffffc5;
		private const ulong g = 5;

		private static ulong mul_mod_p(ulong a, ulong b) {
			ulong m = 0;
			while (b > 0) {
				if ((b&1) > 0) {
					var t = p - a;
					if (m >= t) {
						m -= t;
					} else {
						m += a;
					}
				}
				if (a >= p-a) {
					a = a*2 - p;
				} else {
					a = a * 2;
				}
				b >>= 1;
			}
			return m;
		}

		private static ulong pow_mod_p(ulong a, ulong b) {
			if (b == 1) {
				return a;
			}
			var t = pow_mod_p(a, b>>1);
			t = mul_mod_p(t, t);
			if ((b%2) > 0) {
				t = mul_mod_p(t, a);
			}
			return t;
		}

		private static ulong powmodp(ulong a , ulong b) {
			if (a == 0) {
				throw new Exception("DH64 zero public key");
			}
			if (b == 0) {
				throw new Exception("DH64 zero private key");
			}
			if (a > p) {
				a %= p;
			}
			return pow_mod_p(a, b);
		}

		private Random rand;

		public DH64() {
			rand = new Random();
		}

		public void KeyPair(out ulong privateKey, out ulong publicKey) {
			var a = (ulong)rand.Next();
			var b = (ulong)rand.Next() + 1;
			privateKey = (a<<32) | b;
			publicKey = PublicKey(privateKey);
		}

		public ulong PublicKey(ulong privateKey) {
			return powmodp(g, privateKey);
		}

		public ulong Secret(ulong privateKey, ulong anotherPublicKey) {
			return powmodp(anotherPublicKey, privateKey);
		}
	}
}