#!/usr/bin/env python

"""
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you 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.
"""


"""
wcc

This module use cffi to access the c functions in the WCC library.

There is also an example usage program in this file.

"""
import cffi
import platform

# WCC Group Size
PGS = 32
# WCC Field Size
PFS = 32
G1 = 2 * PFS + 1
G2 = 4 * PFS
# Length of hash
HASH_BYTES = 32
# AES-GCM IV length
IVL = 12
# AES Key length
PAS = 16

# Hash function choice
SHA256 = 32
SHA384 = 48
SHA512 = 64
HASH_TYPE_WCC = SHA256

ffi = cffi.FFI()
ffi.cdef("""
typedef struct {
unsigned int ira[21];  /* random number...   */
int rndptr;   /* ...array & pointer */
unsigned int borrow;
int pool_ptr;
char pool[32];    /* random pool */
} csprng;

typedef struct
{
  int len;
  int max;
  char *val;
} octet;

extern void CREATE_CSPRNG(csprng *R,octet *S);
extern void KILL_CSPRNG(csprng *R);

extern int WCC_RANDOM_GENERATE(csprng *RNG,octet* S);
extern void WCC_Hq(int sha, octet *A,octet *B,octet *C,octet *D,octet *h);
extern int WCC_GET_G2_MULTIPLE(int sha, int hashDone,octet *S,octet *ID,octet *VG2);
extern int WCC_GET_G1_MULTIPLE(int sha, int hashDone,octet *S,octet *ID,octet *VG1);
extern int WCC_GET_G1_TPMULT(int sha, int date, octet *S,octet *ID,octet *VG1);
extern int WCC_GET_G2_TPMULT(int sha, int date, octet *S,octet *ID,octet *VG2);
extern int WCC_GET_G1_PERMIT(int sha, int date,octet *S,octet *HID,octet *G1TP);
extern int WCC_GET_G2_PERMIT(int sha, int date,octet *S,octet *HID,octet *G2TP);
extern int WCC_SENDER_KEY(int sha, int date, octet *xOct, octet *piaOct, octet *pibOct, octet *PbG2Oct, octet *PgG1Oct, octet *AKeyG1Oct, octet *ATPG1Oct, octet *IdBOct, octet *AESKeyOct);
extern int WCC_RECEIVER_KEY(int sha, int date, octet *yOct, octet *wOct,  octet *piaOct, octet *pibOct,  octet *PaG1Oct, octet *PgG1Oct, octet *BKeyG2Oct,octet *BTPG2Oct,  octet *IdAOct, octet *AESKeyOct);
extern void WCC_AES_GCM_ENCRYPT(octet *K,octet *IV,octet *H,octet *P,octet *C,octet *T);
extern void WCC_AES_GCM_DECRYPT(octet *K,octet *IV,octet *H,octet *C,octet *P,octet *T);
extern void WCC_HASH_ID(int sha, octet *,octet *);
extern int WCC_RECOMBINE_G1(octet *,octet *,octet *);
extern int WCC_RECOMBINE_G2(octet *,octet *,octet *);
extern unsigned int WCC_today(void);

""")

if (platform.system() == 'Windows'):
    libamcl_wcc = ffi.dlopen("libamcl_wcc.dll")
    libamcl_core = ffi.dlopen("libamcl_core.dll")
elif (platform.system() == 'Darwin'):
    libamcl_wcc = ffi.dlopen("libamcl_wcc.dylib")
    libamcl_core = ffi.dlopen("libamcl_core.dylib")
else:
    libamcl_wcc = ffi.dlopen("libamcl_wcc.so")
    libamcl_core = ffi.dlopen("libamcl_core.so")


def toHex(octetValue):
    """Converts an octet type into a string

    Add all the values in an octet into an array. This arrays is then
    converted to a string and hex encoded.

    Args::

        octetValue. An octet type

    Returns::

        String

    Raises:
        Exception
    """
    i = 0
    val = []
    while i < octetValue[0].len:
        val.append(octetValue[0].val[i])
        i = i + 1
    return ''.join(val).encode("hex")


if __name__ == "__main__":
    # Print hex values
    DEBUG = False

    # Seed
    seedHex = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"
    seed = seedHex.decode("hex")

    # Master Secret Shares
    MS1 = ffi.new("octet*")
    MS1val = ffi.new("char []", PGS)
    MS1[0].val = MS1val
    MS1[0].max = PGS
    MS1[0].len = PGS

    MS2 = ffi.new("octet*")
    MS2val = ffi.new("char []", PGS)
    MS2[0].val = MS2val
    MS2[0].max = PGS
    MS2[0].len = PGS

    # Alice Identity
    alice_id = raw_input("Please enter Alice's identity:")
    IdA = ffi.new("octet*")
    IdAval = ffi.new("char [%s]" % len(alice_id), alice_id)
    IdA[0].val = IdAval
    IdA[0].max = len(alice_id)
    IdA[0].len = len(alice_id)

    # Hash value of IdA
    AHV = ffi.new("octet*")
    AHVval = ffi.new("char []", HASH_BYTES)
    AHV[0].val = AHVval
    AHV[0].max = HASH_BYTES
    AHV[0].len = HASH_BYTES

    # Bob Identity
    bob_id = raw_input("Please enter Bob's identity:")
    IdB = ffi.new("octet*")
    IdBval = ffi.new("char [%s]" % len(bob_id), bob_id)
    IdB[0].val = IdBval
    IdB[0].max = len(bob_id)
    IdB[0].len = len(bob_id)

    # Hash value of IdB
    BHV = ffi.new("octet*")
    BHVval = ffi.new("char []", HASH_BYTES)
    BHV[0].val = BHVval
    BHV[0].max = HASH_BYTES
    BHV[0].len = HASH_BYTES

    # Sender keys
    A1KeyG1 = ffi.new("octet*")
    A1KeyG1val = ffi.new("char []", G1)
    A1KeyG1[0].val = A1KeyG1val
    A1KeyG1[0].max = G1
    A1KeyG1[0].len = G1

    A2KeyG1 = ffi.new("octet*")
    A2KeyG1val = ffi.new("char []", G1)
    A2KeyG1[0].val = A2KeyG1val
    A2KeyG1[0].max = G1
    A2KeyG1[0].len = G1

    AKeyG1 = ffi.new("octet*")
    AKeyG1val = ffi.new("char []", G1)
    AKeyG1[0].val = AKeyG1val
    AKeyG1[0].max = G1
    AKeyG1[0].len = G1

    # Receiver keys
    B1KeyG2 = ffi.new("octet*")
    B1KeyG2val = ffi.new("char []", G2)
    B1KeyG2[0].val = B1KeyG2val
    B1KeyG2[0].max = G2
    B1KeyG2[0].len = G2

    B2KeyG2 = ffi.new("octet*")
    B2KeyG2val = ffi.new("char []", G2)
    B2KeyG2[0].val = B2KeyG2val
    B2KeyG2[0].max = G2
    B2KeyG2[0].len = G2

    BKeyG2 = ffi.new("octet*")
    BKeyG2val = ffi.new("char []", G2)
    BKeyG2[0].val = BKeyG2val
    BKeyG2[0].max = G2
    BKeyG2[0].len = G2

    # Sender time permits
    A1TPG1 = ffi.new("octet*")
    A1TPG1val = ffi.new("char []", G1)
    A1TPG1[0].val = A1TPG1val
    A1TPG1[0].max = G1
    A1TPG1[0].len = G1

    A2TPG1 = ffi.new("octet*")
    A2TPG1val = ffi.new("char []", G1)
    A2TPG1[0].val = A2TPG1val
    A2TPG1[0].max = G1
    A2TPG1[0].len = G1

    ATPG1 = ffi.new("octet*")
    ATPG1val = ffi.new("char []", G1)
    ATPG1[0].val = ATPG1val
    ATPG1[0].max = G1
    ATPG1[0].len = G1

    # Receiver time permits
    B1TPG2 = ffi.new("octet*")
    B1TPG2val = ffi.new("char []", G2)
    B1TPG2[0].val = B1TPG2val
    B1TPG2[0].max = G2
    B1TPG2[0].len = G2

    B2TPG2 = ffi.new("octet*")
    B2TPG2val = ffi.new("char []", G2)
    B2TPG2[0].val = B2TPG2val
    B2TPG2[0].max = G2
    B2TPG2[0].len = G2

    BTPG2 = ffi.new("octet*")
    BTPG2val = ffi.new("char []", G2)
    BTPG2[0].val = BTPG2val
    BTPG2[0].max = G2
    BTPG2[0].len = G2

    # AES Keys
    KEY1 = ffi.new("octet*")
    KEY1val = ffi.new("char []", PAS)
    KEY1[0].val = KEY1val
    KEY1[0].max = PAS
    KEY1[0].len = PAS

    KEY2 = ffi.new("octet*")
    KEY2val = ffi.new("char []", PAS)
    KEY2[0].val = KEY2val
    KEY2[0].max = PAS
    KEY2[0].len = PAS

    X = ffi.new("octet*")
    Xval = ffi.new("char []", PGS)
    X[0].val = Xval
    X[0].max = PGS
    X[0].len = PGS

    Y = ffi.new("octet*")
    Yval = ffi.new("char []", PGS)
    Y[0].val = Yval
    Y[0].max = PGS
    Y[0].len = PGS

    W = ffi.new("octet*")
    Wval = ffi.new("char []", PGS)
    W[0].val = Wval
    W[0].max = PGS
    W[0].len = PGS

    PIA = ffi.new("octet*")
    PIAval = ffi.new("char []", PGS)
    PIA[0].val = PIAval
    PIA[0].max = PGS
    PIA[0].len = PGS

    PIB = ffi.new("octet*")
    PIBval = ffi.new("char []", PGS)
    PIB[0].val = PIBval
    PIB[0].max = PGS
    PIB[0].len = PGS

    PaG1 = ffi.new("octet*")
    PaG1val = ffi.new("char []", G1)
    PaG1[0].val = PaG1val
    PaG1[0].max = G1
    PaG1[0].len = G1

    PgG1 = ffi.new("octet*")
    PgG1val = ffi.new("char []", G1)
    PgG1[0].val = PgG1val
    PgG1[0].max = G1
    PgG1[0].len = G1

    PbG2 = ffi.new("octet*")
    PbG2val = ffi.new("char []", G2)
    PbG2[0].val = PbG2val
    PbG2[0].max = G2
    PbG2[0].len = G2

    # Assign a seed value
    RAW = ffi.new("octet*")
    RAWval = ffi.new("char [%s]" % len(seed), seed)
    RAW[0].val = RAWval
    RAW[0].len = len(seed)
    RAW[0].max = len(seed)
    if DEBUG:
        print "RAW: %s" % toHex(RAW)

    # random number generator
    RNG = ffi.new("csprng*")
    libamcl_core.CREATE_CSPRNG(RNG, RAW)

    # Today's date in epoch days
    date = libamcl_wcc.WCC_today()
    if DEBUG:
        print "Date %s" % date

    # Hash IdA
    libamcl_wcc.WCC_HASH_ID(HASH_TYPE_WCC, IdA, AHV)
    if DEBUG:
        print "IdA: %s" % toHex(IdA)
        print "AHV: %s" % toHex(AHV)

    # Hash IdB
    libamcl_wcc.WCC_HASH_ID(HASH_TYPE_WCC, IdB, BHV)
    if DEBUG:
        print "IdB: %s" % toHex(IdB)
        print "BHV: %s" % toHex(BHV)

    # Generate master secret for MIRACL and Customer
    rtn = libamcl_wcc.WCC_RANDOM_GENERATE(RNG, MS1)
    if rtn != 0:
        print "libamcl_wcc.WCC_RANDOM_GENERATE(RNG,MS1) Error %s", rtn
    rtn = libamcl_wcc.WCC_RANDOM_GENERATE(RNG, MS2)
    if rtn != 0:
        print "libamcl_wcc.WCC_RANDOM_GENERATE(RNG,MS2) Error %s" % rtn
    if DEBUG:
        print "MS1: %s" % toHex(MS1)
        print "MS2: %s" % toHex(MS2)

    # Generate Alice's sender key shares
    rtn = libamcl_wcc.WCC_GET_G1_MULTIPLE(HASH_TYPE_WCC, 1, MS1, AHV, A1KeyG1)
    if rtn != 0:
        print "libamcl_wcc.WCC_GET_G1_MULTIPLE(HASH_TYPE_WCC, 1, MS1,AHV,A1KeyG1) Error %s" % rtn
    rtn = libamcl_wcc.WCC_GET_G1_MULTIPLE(HASH_TYPE_WCC, 1, MS2, AHV, A2KeyG1)
    if rtn != 0:
        print "libamcl_wcc.WCC_GET_G1_MULTIPLE(HASH_TYPE_WCC, 1, MS2,AHV,A2KeyG1) Error %s" % rtn
    if DEBUG:
        print "A1KeyG1: %s" % toHex(A1KeyG1)
        print "A2KeyG1: %s" % toHex(A2KeyG1)

    # Combine Alices's sender key shares
    rtn = libamcl_wcc.WCC_RECOMBINE_G1(A1KeyG1, A2KeyG1, AKeyG1)
    if rtn != 0:
        print "libamcl_wcc.WCC_RECOMBINE_G1(A1KeyG1, A2KeyG1, AKeyG1) Error %s" % rtn
    print "AKeyG1: %s" % toHex(AKeyG1)

    # Generate Alice's sender time permit shares
    rtn = libamcl_wcc.WCC_GET_G1_PERMIT(HASH_TYPE_WCC, date, MS1, AHV, A1TPG1)
    if rtn != 0:
        print "libamcl_wcc.WCC_GET_G1_PERMIT(HASH_TYPE_WCC, date,MS1,AHV,A1TPG1) Error %s" % rtn
    rtn = libamcl_wcc.WCC_GET_G1_PERMIT(HASH_TYPE_WCC, date, MS2, AHV, A2TPG1)
    if rtn != 0:
        print "libamcl_wcc.WCC_GET_G1_PERMIT(HASH_TYPE_WCC, date,MS2,AHV,A2TPG1) Error %s" % rtn
    if DEBUG:
        print "A1TPG1: %s" % toHex(A1TPG1)
        print "A2TPG1: %s" % toHex(A2TPG1)

    # Combine Alice's sender Time Permit shares
    rtn = libamcl_wcc.WCC_RECOMBINE_G1(A1TPG1, A2TPG1, ATPG1)
    if rtn != 0:
        print "libamcl_wcc.WCC_RECOMBINE_G1(A1TPG1, A2TPG1, ATPG1) Error %s" % rtn
    print "ATPG1: %s" % toHex(ATPG1)

    # Generate Bob's receiver secret key shares
    rtn = libamcl_wcc.WCC_GET_G2_MULTIPLE(HASH_TYPE_WCC, 1, MS1, BHV, B1KeyG2)
    if rtn != 0:
        print "libamcl_wcc.WCC_GET_G2_MULTIPLE(HASH_TYPE_WCC,1,MS1,BHV,B1KeyG2) Error %s" % rtn
    rtn = libamcl_wcc.WCC_GET_G2_MULTIPLE(HASH_TYPE_WCC, 1, MS2, BHV, B2KeyG2)
    if rtn != 0:
        print "libamcl_wcc.WCC_GET_G2_MULTIPLE(HASH_TYPE_WCC,1,MS2,BHV,B2KeyG2) Error %s" % rtn
    if DEBUG:
        print "B1KeyG2: %s" % toHex(B1KeyG2)
        print "B2KeyG2: %s" % toHex(B2KeyG2)

    # Combine Bobs's receiver secret key shares
    rtn = libamcl_wcc.WCC_RECOMBINE_G2(B1KeyG2, B2KeyG2, BKeyG2)
    if rtn != 0:
        print "libamcl_wcc.WCC_RECOMBINE_G2(B1KeyG2, B2KeyG2, BKeyG2) Error %s" % rtn
    print "BKeyG2: %s" % toHex(BKeyG2)

    # Generate Bob's receiver time permit shares
    rtn = libamcl_wcc.WCC_GET_G2_PERMIT(HASH_TYPE_WCC, date, MS1, BHV, B1TPG2)
    if rtn != 0:
        print "libamcl_wcc.WCC_GET_G2_PERMIT(date,MS1,BHV,B1TPG2) Error %s" % rtn
    rtn = libamcl_wcc.WCC_GET_G2_PERMIT(HASH_TYPE_WCC, date, MS2, BHV, B2TPG2)
    if rtn != 0:
        print "libamcl_wcc.WCC_GET_G2_PERMIT(date,MS2,BHV,B2TPG2) Error %s" % rtn
    if DEBUG:
        print "B1TPG2: %s" % toHex(B1TPG2)
        print "B2TPG2: %s" % toHex(B2TPG2)

    # Combine Bob's receiver time permit shares
    rtn = libamcl_wcc.WCC_RECOMBINE_G2(B1TPG2, B2TPG2, BTPG2)
    if rtn != 0:
        print "libamcl_wcc.WCC_RECOMBINE_G2(B1TPG2, B2TPG2, BTPG2) Error %s" % rtn
    print "BTPG2: %s" % toHex(BTPG2)

    rtn = libamcl_wcc.WCC_RANDOM_GENERATE(RNG, X)
    if rtn != 0:
        print "libamcl_wcc.WCC_RANDOM_GENERATE(RNG,X) Error %s", rtn
    if DEBUG:
        print "X: %s" % toHex(X)

    rtn = libamcl_wcc.WCC_GET_G1_TPMULT(HASH_TYPE_WCC, date, X, IdA, PaG1)
    if rtn != 0:
        print "libamcl_wcc.WCC_GET_G1_TPMULT(HASH_TYPE_WCC,date,X,IdA,PaG1) Error %s", rtn
    if DEBUG:
        print "PaG1: %s" % toHex(PaG1)

    rtn = libamcl_wcc.WCC_RANDOM_GENERATE(RNG, W)
    if rtn != 0:
        print "libamcl_wcc.WCC_RANDOM_GENERATE(RNG,W) Error %s", rtn
    if DEBUG:
        print "W: %s" % toHex(W)

    rtn = libamcl_wcc.WCC_GET_G1_TPMULT(HASH_TYPE_WCC, date, W, IdA, PgG1)
    if rtn != 0:
        print "libamcl_wcc.WCC_GET_G1_TPMULT(HASH_TYPE_WCC,date,W,IdA,PgG1) Error %s", rtn
    if DEBUG:
        print "PgG1: %s" % toHex(PgG1)

    rtn = libamcl_wcc.WCC_RANDOM_GENERATE(RNG, Y)
    if rtn != 0:
        print "libamcl_wcc.WCC_RANDOM_GENERATE(RNG,Y) Error %s", rtn
    if DEBUG:
        print "Y: %s" % toHex(Y)

    rtn = libamcl_wcc.WCC_GET_G2_TPMULT(HASH_TYPE_WCC, date, Y, IdB, PbG2)
    if rtn != 0:
        print "libamcl_wcc.WCC_GET_G1_TPMULT(HASH_TYPE_WCC,date,Y,IdB,PbG2) Error %s", rtn
    if DEBUG:
        print "PbG2: %s" % toHex(PbG2)

    # PIA = Hq(PaG1,PbG2,PgG1,IdB)
    libamcl_wcc.WCC_Hq(HASH_TYPE_WCC, PaG1, PbG2, PgG1, IdB, PIA)
    if DEBUG:
        print "PIA: %s" % toHex(PIA)

    # PIB = Hq(PbG2,PaG1,PgG1,IdA)
    libamcl_wcc.WCC_Hq(HASH_TYPE_WCC, PbG2, PaG1, PgG1, IdA, PIB)
    if DEBUG:
        print "PIB: %s" % toHex(PIB)

    # Alice calculates AES Key
    rtn = libamcl_wcc.WCC_SENDER_KEY(
        HASH_TYPE_WCC,
        date,
        X,
        PIA,
        PIB,
        PbG2,
        PgG1,
        AKeyG1,
        ATPG1,
        IdB,
        KEY1)
    if rtn != 0:
        print "libamcl_wcc.WCC_SENDER_KEY(HASH_TYPE_WCC,date, X, PIA, PIB, PbG2, PgG1, AKeyG1, ATPG1, IdB, KEY1) Error %s" % rtn
    print "{0}'s AES Key: {1}".format(alice_id, toHex(KEY1))

    # Bob calculates AES Key
    rtn = libamcl_wcc.WCC_RECEIVER_KEY(
        HASH_TYPE_WCC,
        date,
        Y,
        W,
        PIA,
        PIB,
        PaG1,
        PgG1,
        BKeyG2,
        BTPG2,
        IdA,
        KEY2)
    if rtn != 0:
        print "libamcl_wcc.WCC_RECEIVER_KEY(HASH_TYPE_WCC,date, Y, W, PIA, PIB, PaG1, PgG1, BKeyG2, BTPG2, IdA, KEY2) Error %s" % rtn
    print "{0}'s AES Key: {1}".format(bob_id, toHex(KEY2))

    libamcl_core.KILL_CSPRNG(RNG)
