update code
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e66e791
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,25 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.war
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+# Ignore gradle build
+build/
+.gradle
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..f51ea63
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,10 @@
+language: java
+
+jdk:
+  - oraclejdk8
+
+script:
+  - ./gradlew clean build test --stacktrace --info
+
+after_success:
+  - if [ "$TRAVIS_JDK_VERSION" = "oraclejdk8" ]; then ./gradlew jacocoTestReport coveralls; fi;
diff --git a/AMCL.pdf b/AMCL.pdf
new file mode 100644
index 0000000..e4fa685
--- /dev/null
+++ b/AMCL.pdf
Binary files differ
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f9a77cd
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2018 MIRACL UK Ltd
+
+   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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..803fab8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,74 @@
+# MCJL - *Milagro Crypto Java Library*
+
+[![Master Branch](https://img.shields.io/badge/-master:-gray.svg)](https://github.com/milagro-crypto/milagro-crypto-java/tree/master)
+[![Master Build Status](https://secure.travis-ci.org/milagro-crypto/milagro-crypto-java.png?branch=master)](https://travis-ci.org/milagro-crypto/milagro-crypto-java?branch=master)
+[![Master Coverage Status](https://coveralls.io/repos/github/milagro-crypto/milagro-crypto-java/badge.svg?branch=master)](https://coveralls.io/github/milagro-crypto/milagro-crypto-java?branch=master)
+
+* **category**:    Library
+* **copyright**:   2018 The Apache Software Foundation
+* **license**:     ASL 2.0 ([Apache License Version 2.0, January 2004](http://www.apache.org/licenses/LICENSE-2.0))
+* **link**:        https://github.com/milagro-crypto/milagro-crypto-java
+* **introduction**: [AMCL.pdf](AMCL.pdf)
+
+
+## Description
+
+*MCJL - Milagro Crypto Java Library*
+
+* MCJL is a standards compliant JavaScript cryptographic library with no external dependencies except for the random seed source.
+
+* MCJL is a refactor of the *Java* code of [AMCL](https://github.com/milagro-crypto/amcl). For a detailed explanation about this library please read: [AMCL.pdf](AMCL.pdf). 
+
+* MCJL supports the standards for RSA, ECDH, ECIES, ECDSA and M-PIN, AES-GCM encryption/decryption, SHA256, SHA384, SHA512 and SHA3 hash functions and a cryptographically secure random number generator. Furthermore we recently added New Hope, a post-quantum key exchange.
+
+This library is created from the Java code in this directory 
+[ACML](https://github.com/milagro-crypto/amcl/tree/master/version3/java) 
+project. The config64.py script has been run in this AMCL directory and all 
+the curves and RSA security level were selected for a 64-bit build; the output
+Java files from this process are used in this project. If you require a 
+smaller JAR file please follow the instructions in the AMCL project.
+
+## Software Dependencies
+
+In order to build this library, the following packages are required:
+
+* [gradle](https://gradle.org/)
+
+## Setup
+This library is avaiable on Maven Central.
+
+Replace `VERSION` below with required version.
+
+To use `MCJL` with Maven project, use:
+```
+<dependency>
+  <groupId>org.miracl.milagro.amcl</groupId>
+  <artifactId>milagro-crypto-java</artifactId>
+  <version>VERSION</version>
+</dependency>
+```
+
+For Gradle project:
+```
+dependencies {
+   compile 'org.miracl.milagro.amcl:milagro-crypto-java:VERSION'
+}
+```
+
+Fill the `gradle.properties` file if you want to upload on Maven Central.
+
+`MCJL` needs Java 8.
+
+## Local Installation
+
+Use this command to compile library and install it as artifact to local Maven 
+repository.
+
+    ./gradlew clean build publishToMavenLocal --stacktrace --info
+
+## Contributions
+
+Contributions are very welcome. Please make pull requests to the develop 
+branch. You can run this command to build and test the code.
+
+    ./gradlew build
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..1d0ba9e
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.4.0
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..31bf75b
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,104 @@
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.bmuschko:gradle-nexus-plugin:2.3.1'
+    }
+}
+
+plugins {
+    id 'java-library'
+    id 'jacoco'
+    id 'com.github.kt3k.coveralls' version '2.6.3'
+    id 'io.codearte.nexus-staging' version '0.11.0'
+}
+
+apply plugin: 'java'
+apply plugin: 'maven-publish'
+apply plugin: 'com.bmuschko.nexus'
+
+nexusStaging {
+    packageGroup = "org.miracl"
+}
+
+publishing {
+    publications {
+        mavenJava(MavenPublication) {
+            artifactId 'milagro-crypto-java'
+            groupId 'org.miracl.milagro.amcl'
+            version '0.4.0'
+            from components.java
+        }
+    }
+
+    repositories {
+        maven {
+            // change to point to your repo, e.g. http://my.org/repo
+            url "$buildDir/repo"
+        }
+    }
+}
+
+
+dependencies {
+    // This dependency is exported to consumers, that is to say found on their compile classpath.
+    api 'org.apache.commons:commons-math3:3.6.1'
+
+    // This dependency is used internally, and not exposed to consumers on their own compile classpath.
+    implementation 'com.google.guava:guava:23.0'
+
+    // Use JUnit test framework
+    testImplementation 'junit:junit:4.12'
+}
+
+// In this section you declare where to find the dependencies of your project
+repositories {
+    jcenter()
+}
+
+jacocoTestReport {
+    reports {
+        xml.enabled = true
+        html.enabled = true
+    }
+}
+
+archivesBaseName = 'milagro-crypto-java'
+group = "org.miracl.milagro.amcl"
+version = "0.4.0"
+modifyPom {
+    project {
+        name 'milagro-crypto-java'
+        description 'MCJL - Milagro Crypto Java Library'
+        url 'https://github.com/milagro-crypto/milagro-crypto-java'
+        inceptionYear '2018'
+        scm {
+            url 'https://bitbucket.org/objdict/objjson'
+            connection 'scm:https://github.com/milagro-crypto/milagro-crypto-java.git'
+            developerConnection 'scm:git://github.com/milagro-crypto/milagro-crypto-java.git'
+        }
+        licenses {
+            license {
+                name 'The Apache Software License, Version 2.0'
+                url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                distribution 'repo'
+            }
+        }
+        developers {
+            developer {
+                email 'support@miracl.com'
+            }
+        }
+    }
+}
+extraArchive {
+    sources = true
+    tests = true
+    javadoc = true
+}
+nexus {
+    sign = true
+    repositoryUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
+    snapshotRepositoryUrl = 'https://oss.sonatype.org/content/repositories/snapshots/'
+}
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..a666450
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,11 @@
+# Examples
+
+These are two examples programs that require the library to be built before 
+they can be run. These are adapted from the tests for the BN254CX curve.
+Replace `VERSION` below with required version.
+
+    javac -classpath .:../build/libs/milagro-crypto-java-VERSION.jar  TestMPIN.java
+    java -classpath .:../build/libs/milagro-crypto-java-VERSION.jar  TestMPIN
+
+    javac -classpath .:../build/libs/milagro-crypto-java-VERSION.jar  TestECC.java
+    java -classpath .:../build/libs/milagro-crypto-java-VERSION.jar  TestECC
diff --git a/examples/TestECC.java b/examples/TestECC.java
new file mode 100644
index 0000000..100c086
--- /dev/null
+++ b/examples/TestECC.java
@@ -0,0 +1,174 @@
+/*
+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.
+*/
+
+/* ECDH/ECIES/ECDSA example for BN254CX curve */
+
+import org.apache.milagro.amcl.BN254CX.*;
+import org.apache.milagro.amcl.RAND;
+import org.apache.milagro.amcl.AES;
+
+public class TestECC {
+    private static void printBinary(byte[] array) {
+        int i;
+        for (i = 0; i < array.length; i++) {
+            System.out.printf("%02x", array[i]);
+        }
+        System.out.println();
+    }
+
+    public static void main(String[] args) {
+        byte[] RAW = new byte[100];
+        RAND rng = new RAND();
+        int i, j = 0, res;
+        int result;
+        String pp = new String("M0ng00se");
+
+        rng.clean();
+        for (i = 0; i < 100; i++) RAW[i] = (byte)(i);
+        rng.seed(100, RAW);
+
+        int EGS = ECDH.EGS;
+        int EFS = ECDH.EFS;
+        int EAS = AES.KS;
+        int sha = ECDH.HASH_TYPE;
+
+        byte[] S1 = new byte[EGS];
+        byte[] W0 = new byte[2 * EFS + 1];
+        byte[] W1 = new byte[2 * EFS + 1];
+        byte[] Z0 = new byte[EFS];
+        byte[] Z1 = new byte[EFS];
+
+        byte[] SALT = new byte[8];
+        byte[] P1 = new byte[3];
+        byte[] P2 = new byte[4];
+        byte[] V = new byte[2 * EFS + 1];
+        byte[] M = new byte[17];
+        byte[] T = new byte[12];
+        byte[] CS = new byte[EGS];
+        byte[] DS = new byte[EGS];
+
+        for (i = 0; i < 8; i++) SALT[i] = (byte)(i + 1); // set Salt
+
+        System.out.println("Testing ECDH code");
+        System.out.println("Alice's Passphrase= " + pp);
+        byte[] PW = pp.getBytes();
+
+        /* private key S0 of size EGS bytes derived from Password and Salt */
+
+        byte[] S0 = ECDH.PBKDF2(sha, PW, SALT, 1000, EGS);
+
+        System.out.print("Alice's private key= 0x");
+        printBinary(S0);
+
+        /* Generate Key pair S/W */
+        ECDH.KEY_PAIR_GENERATE(null, S0, W0);
+
+        System.out.print("Alice's public key= 0x");
+        printBinary(W0);
+
+        res = ECDH.PUBLIC_KEY_VALIDATE(W0);
+        if (res != 0) {
+            System.out.println("ECP Public Key is invalid!");
+        }
+        /* Random private key for other party */
+        ECDH.KEY_PAIR_GENERATE(rng, S1, W1);
+
+        System.out.print("Servers private key= 0x");
+        printBinary(S1);
+
+        System.out.print("Servers public key= 0x");
+        printBinary(W1);
+
+
+        res = ECDH.PUBLIC_KEY_VALIDATE(W1);
+        if (res != 0) {
+            System.out.println("ECP Public Key is invalid!");
+        }
+
+        /* Calculate common key using DH - IEEE 1363 method */
+
+        ECDH.SVDP_DH(S0, W1, Z0);
+        ECDH.SVDP_DH(S1, W0, Z1);
+
+        boolean same = true;
+        for (i = 0; i < EFS; i++)
+            if (Z0[i] != Z1[i]) same = false;
+
+        if (!same) {
+            System.out.println("*** ECPSVDP-DH Failed");
+        }
+
+        byte[] KEY = ECDH.KDF2(sha, Z0, null, EAS);
+
+        System.out.print("Alice's DH Key=  0x");
+        printBinary(KEY);
+        System.out.print("Servers DH Key=  0x");
+        printBinary(KEY);
+
+        if (ECP.CURVETYPE != ECP.MONTGOMERY) {
+            System.out.println("Testing ECIES");
+
+            P1[0] = 0x0;
+            P1[1] = 0x1;
+            P1[2] = 0x2;
+            P2[0] = 0x0;
+            P2[1] = 0x1;
+            P2[2] = 0x2;
+            P2[3] = 0x3;
+
+            for (i = 0; i <= 16; i++) M[i] = (byte) i;
+
+            byte[] C = ECDH.ECIES_ENCRYPT(sha, P1, P2, rng, W1, M, V, T);
+
+            System.out.println("Ciphertext= ");
+            System.out.print("V= 0x");
+            printBinary(V);
+            System.out.print("C= 0x");
+            printBinary(C);
+            System.out.print("T= 0x");
+            printBinary(T);
+
+
+            M = ECDH.ECIES_DECRYPT(sha, P1, P2, V, C, T, S1);
+            if (M.length == 0) {
+                System.out.println("*** ECIES Decryption Failed");
+            } else System.out.println("Decryption succeeded");
+
+            System.out.print("Message is 0x");
+            printBinary(M);
+
+            System.out.println("Testing ECDSA");
+
+            if (ECDH.SP_DSA(sha, rng, S0, M, CS, DS) != 0) {
+                System.out.println("***ECDSA Signature Failed");
+            }
+            System.out.println("Signature= ");
+            System.out.print("C= 0x");
+            printBinary(CS);
+            System.out.print("D= 0x");
+            printBinary(DS);
+
+            if (ECDH.VP_DSA(sha, W0, M, CS, DS) != 0) {
+                System.out.println("***ECDSA Verification Failed");
+            } else System.out.println("ECDSA Signature/Verification succeeded " + j);
+            System.out.println("");
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/examples/TestMPIN.java b/examples/TestMPIN.java
new file mode 100644
index 0000000..e5d7afc
--- /dev/null
+++ b/examples/TestMPIN.java
@@ -0,0 +1,267 @@
+/*
+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.
+*/
+
+/* MPIN example for BN254CX curve */
+import org.apache.milagro.amcl.BN254CX.*;
+import org.apache.milagro.amcl.RAND;
+
+public class TestMPIN {
+
+    static boolean PERMITS = true;
+    static boolean PINERROR = true;
+    static boolean FULL = true;
+    static boolean SINGLE_PASS = false;
+
+    static void printBinary(byte[] array) {
+        int i;
+        for (i = 0; i < array.length; i++) {
+            System.out.printf("%02x", array[i]);
+        }
+        System.out.println();
+    }
+
+
+    public static void main(String[] args) {
+        RAND rng = new RAND();
+        int EGS = MPIN.EGS;
+        int EFS = MPIN.EFS;
+        int G1S = 2 * EFS + 1; /* Group 1 Size */
+        int G2S = 4 * EFS; /* Group 2 Size */
+        int EAS = 16;
+
+        int sha = MPIN.HASH_TYPE;
+
+        byte[] S = new byte[EGS];
+        byte[] SST = new byte[G2S];
+        byte[] TOKEN = new byte[G1S];
+        byte[] PERMIT = new byte[G1S];
+        byte[] SEC = new byte[G1S];
+        byte[] xID = new byte[G1S];
+        byte[] xCID = new byte[G1S];
+        byte[] X = new byte[EGS];
+        byte[] Y = new byte[EGS];
+        byte[] E = new byte[12 * EFS];
+        byte[] F = new byte[12 * EFS];
+        byte[] HID = new byte[G1S];
+        byte[] HTID = new byte[G1S];
+
+        byte[] G1 = new byte[12 * EFS];
+        byte[] G2 = new byte[12 * EFS];
+        byte[] R = new byte[EGS];
+        byte[] Z = new byte[G1S];
+        byte[] W = new byte[EGS];
+        byte[] T = new byte[G1S];
+        byte[] CK = new byte[EAS];
+        byte[] SK = new byte[EAS];
+
+        byte[] HSID = null;
+        byte[] RAW = new byte[100];
+
+        rng.clean();
+        for (int i = 0; i < 100; i++) RAW[i] = (byte)(i);
+        rng.seed(100, RAW);
+
+        System.out.println("Testing MPIN code");
+
+        /* Trusted Authority set-up */
+
+        MPIN.RANDOM_GENERATE(rng, S);
+        System.out.print("Master Secret s: 0x");
+        printBinary(S);
+
+        /* Create Client Identity */
+        String IDstr = "testUser@miracl.com";
+        byte[] CLIENT_ID = IDstr.getBytes();
+
+        byte[] HCID = MPIN.HASH_ID(sha, CLIENT_ID, EFS); /* Either Client or TA calculates Hash(ID) - you decide! */
+
+        System.out.print("Client ID Hash= ");
+        printBinary(HCID);
+        System.out.print("Client ID= ");
+        printBinary(CLIENT_ID);
+
+        /* Client and Server are issued secrets by DTA */
+
+        MPIN.GET_CLIENT_SECRET(S, HCID, TOKEN);
+        System.out.print("Client Secret CS: 0x");
+        printBinary(TOKEN);
+
+        MPIN.GET_SERVER_SECRET(S, SST);
+        System.out.print("Server Secret SS: 0x");
+        printBinary(SST);
+
+
+        /* Client extracts PIN from secret to create Token */
+        int pin = 1234;
+        System.out.println("Client extracts PIN= " + pin);
+        int rtn = MPIN.EXTRACT_PIN(sha, CLIENT_ID, pin, TOKEN);
+        if (rtn != 0)
+            System.out.println("FAILURE: EXTRACT_PIN rtn: " + rtn);
+
+        System.out.print("Client Token TK: 0x");
+        printBinary(TOKEN);
+
+        if (FULL) {
+            MPIN.PRECOMPUTE(TOKEN, HCID, G1, G2);
+        }
+        int date;
+        if (PERMITS) {
+            date = MPIN.today();
+            /* Client gets "Time Token" permit from DTA */
+            MPIN.GET_CLIENT_PERMIT(sha, date, S, HCID, PERMIT);
+            System.out.print("Time Permit TP: 0x");
+            printBinary(PERMIT);
+
+            /* This encoding makes Time permit look random - Elligator squared */
+            MPIN.ENCODING(rng, PERMIT);
+            System.out.print("Encoded Time Permit TP: 0x");
+            printBinary(PERMIT);
+            MPIN.DECODING(PERMIT);
+            System.out.print("Decoded Time Permit TP: 0x");
+            printBinary(PERMIT);
+        } else date = 0;
+
+        //		System.out.print("\nPIN= ");
+        //		Scanner scan=new Scanner(System.in);
+        //		pin=scan.nextInt();
+
+        pin = 1234;
+
+        /* Set date=0 and PERMIT=null if time permits not in use
+
+        Client First pass: Inputs CLIENT_ID, optional RNG, pin, TOKEN and PERMIT. Output xID =x .H(CLIENT_ID) and re-combined secret SEC
+        If PERMITS are is use, then date!=0 and PERMIT is added to secret and xCID = x.(H(CLIENT_ID)+H(date|H(CLIENT_ID)))
+        Random value x is supplied externally if RNG=null, otherwise generated and passed out by RNG
+
+        IMPORTANT: To save space and time..
+        If Time Permits OFF set xCID = null, HTID=null and use xID and HID only
+        If Time permits are ON, AND pin error detection is required then all of xID, xCID, HID and HTID are required
+        If Time permits are ON, AND pin error detection is NOT required, set xID=null, HID=null and use xCID and HTID only.
+
+
+        */
+
+        byte[] pxID = xID;
+        byte[] pxCID = xCID;
+        byte[] pHID = HID;
+        byte[] pHTID = HTID;
+        byte[] pE = E;
+        byte[] pF = F;
+        byte[] pPERMIT = PERMIT;
+        byte[] prHID;
+
+        if (date != 0) {
+
+            prHID = pHTID;
+            if (!PINERROR) {
+                pxID = null;
+                //		pHID=null;  // new
+            }
+        } else {
+            prHID = pHID;
+            pPERMIT = null;
+            pxCID = null;
+            pHTID = null;
+        }
+        if (!PINERROR) {
+            pE = null;
+            pF = null;
+        }
+
+        if (SINGLE_PASS) {
+            System.out.println("MPIN Single Pass");
+            int timeValue = MPIN.GET_TIME();
+            rtn = MPIN.CLIENT(sha, date, CLIENT_ID, rng, X, pin, TOKEN, SEC, pxID, pxCID, pPERMIT, timeValue, Y);
+            if (rtn != 0)
+                System.out.println("FAILURE: CLIENT rtn: " + rtn);
+
+            if (FULL) {
+                HCID = MPIN.HASH_ID(sha, CLIENT_ID, EFS);
+                MPIN.GET_G1_MULTIPLE(rng, 1, R, HCID, Z); /* Also Send Z=r.ID to Server, remember random r */
+            }
+
+            rtn = MPIN.SERVER(sha, date, pHID, pHTID, Y, SST, pxID, pxCID, SEC, pE, pF, CLIENT_ID, timeValue);
+            if (rtn != 0)
+                System.out.println("FAILURE: SERVER rtn: " + rtn);
+
+            if (FULL) {
+                HSID = MPIN.HASH_ID(sha, CLIENT_ID, EFS);
+                MPIN.GET_G1_MULTIPLE(rng, 0, W, prHID, T); /* Also send T=w.ID to client, remember random w  */
+            }
+        } else {
+            System.out.println("MPIN Multi Pass");
+            /* Send U=x.ID to server, and recreate secret from token and pin */
+            rtn = MPIN.CLIENT_1(sha, date, CLIENT_ID, rng, X, pin, TOKEN, SEC, pxID, pxCID, pPERMIT);
+            if (rtn != 0)
+                System.out.println("FAILURE: CLIENT_1 rtn: " + rtn);
+
+            if (FULL) {
+                HCID = MPIN.HASH_ID(sha, CLIENT_ID, EFS);
+                MPIN.GET_G1_MULTIPLE(rng, 1, R, HCID, Z); /* Also Send Z=r.ID to Server, remember random r */
+            }
+
+            /* Server calculates H(ID) and H(T|H(ID)) (if time permits enabled), and maps them to points on the curve HID and HTID resp. */
+            MPIN.SERVER_1(sha, date, CLIENT_ID, pHID, pHTID);
+
+            /* Server generates Random number Y and sends it to Client */
+            MPIN.RANDOM_GENERATE(rng, Y);
+
+            if (FULL) {
+                HSID = MPIN.HASH_ID(sha, CLIENT_ID, EFS);
+                MPIN.GET_G1_MULTIPLE(rng, 0, W, prHID, T); /* Also send T=w.ID to client, remember random w  */
+            }
+
+            /* Client Second Pass: Inputs Client secret SEC, x and y. Outputs -(x+y)*SEC */
+            rtn = MPIN.CLIENT_2(X, Y, SEC);
+            if (rtn != 0)
+                System.out.println("FAILURE: CLIENT_2 rtn: " + rtn);
+
+            /* Server Second pass. Inputs hashed client id, random Y, -(x+y)*SEC, xID and xCID and Server secret SST. E and F help kangaroos to find error. */
+            /* If PIN error not required, set E and F = null */
+
+            rtn = MPIN.SERVER_2(date, pHID, pHTID, Y, SST, pxID, pxCID, SEC, pE, pF);
+
+            if (rtn != 0)
+                System.out.println("FAILURE: SERVER_2 rtn: " + rtn);
+        }
+
+        if (rtn == MPIN.BAD_PIN) {
+            if (PINERROR) {
+                int err = MPIN.KANGAROO(E, F);
+                if (err != 0) System.out.println("Client PIN is out by " + err);
+                else System.out.println("Server says - Bad Pin. I don't know you");
+            } else System.out.println("Server says - Bad Pin. I don't know you");
+
+        } else System.out.println("Server says - PIN is good! You really are " + IDstr);
+
+
+        if (FULL) {
+            byte[] H = MPIN.HASH_ALL(sha, HCID, pxID, pxCID, SEC, Y, Z, T, EFS);
+            MPIN.CLIENT_KEY(sha, G1, G2, pin, R, X, H, T, CK);
+            System.out.print("Client Key =  0x");
+            printBinary(CK);
+
+            H = MPIN.HASH_ALL(sha, HSID, pxID, pxCID, SEC, Y, Z, T, EFS);
+            MPIN.SERVER_KEY(sha, Z, SST, W, H, pHID, pxID, pxCID, SK);
+            System.out.print("Server Key =  0x");
+            printBinary(SK);
+        }
+        System.out.println("");
+    }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..60aa7da
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,6 @@
+#remove '#' and fill following fields:
+#nexusUsername=YOUR_SONATYPE_USER_NAME
+#nexusPassword=YOUR_SONATYPE_USER_PASSWORD
+#signing.keyId=KEY_ID
+#signing.password=KEY_PASSWORD
+#signing.secretKeyRingFile=/PATH/TO/SECRET/RING/FILE
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..a5fe1cb
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..be280be
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-bin.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..f84f2cf
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,18 @@
+/*
+ * This settings file was generated by the Gradle 'init' task.
+ *
+ * The settings file is used to specify which projects to include in your build.
+ * In a single project build this file can be empty or even removed.
+ *
+ * Detailed information about configuring a multi-project build in Gradle can be found
+ * in the user guide at https://docs.gradle.org/3.3/userguide/multi_project_builds.html
+ */
+
+/*
+// To declare projects as part of a multi-project build use the 'include' method
+include 'shared'
+include 'api'
+include 'services:webservice'
+*/
+
+rootProject.name = 'milagro-crypto-java'
diff --git a/src/main/java/org/apache/milagro/amcl/AES.java b/src/main/java/org/apache/milagro/amcl/AES.java
new file mode 100644
index 0000000..35d04dc
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/AES.java
@@ -0,0 +1,695 @@
+/*
+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.
+*/
+
+
+/* AES Encryption */ 
+package org.apache.milagro.amcl;
+
+public class AES {
+	int Nk,Nr;
+	int mode;
+	private int[] fkey=new int[60];
+	private int[] rkey=new int[60];
+	public byte[] f=new byte[16];
+
+
+	public static final int ECB=0;
+	public static final int CBC=1;
+	public static final int CFB1=2;
+	public static final int CFB2=3;
+	public static final int CFB4=5;
+	public static final int OFB1=14;
+	public static final int OFB2=15;
+	public static final int OFB4=17;
+	public static final int OFB8=21;
+	public static final int OFB16=29;
+	public static final int CTR1=30;
+	public static final int CTR2=31;
+	public static final int CTR4=33; 
+	public static final int CTR8=37; 
+	public static final int CTR16=45; 
+
+	private static final byte[] InCo={(byte)0xB,(byte)0xD,(byte)0x9,(byte)0xE};  /* Inverse Coefficients */
+
+	public static final int KS=16; /* Key Size in bytes */
+	public static final int BS=16; /* Block Size */
+
+	private static final byte[] ptab=
+	{(byte)1,(byte)3,(byte)5,(byte)15,(byte)17,(byte)51,(byte)85,(byte)255,(byte)26,(byte)46,(byte)114,(byte)150,(byte)161,(byte)248,(byte)19,(byte)53,
+	(byte)95,(byte)225,(byte)56,(byte)72,(byte)216,(byte)115,(byte)149,(byte)164,(byte)247,(byte)2,(byte)6,(byte)10,(byte)30,(byte)34,(byte)102,(byte)170,
+	(byte)229,(byte)52,(byte)92,(byte)228,(byte)55,(byte)89,(byte)235,(byte)38,(byte)106,(byte)190,(byte)217,(byte)112,(byte)144,(byte)171,(byte)230,(byte)49,
+	(byte)83,(byte)245,(byte)4,(byte)12,(byte)20,(byte)60,(byte)68,(byte)204,(byte)79,(byte)209,(byte)104,(byte)184,(byte)211,(byte)110,(byte)178,(byte)205,
+	(byte)76,(byte)212,(byte)103,(byte)169,(byte)224,(byte)59,(byte)77,(byte)215,(byte)98,(byte)166,(byte)241,(byte)8,(byte)24,(byte)40,(byte)120,(byte)136,
+	(byte)131,(byte)158,(byte)185,(byte)208,(byte)107,(byte)189,(byte)220,(byte)127,(byte)129,(byte)152,(byte)179,(byte)206,(byte)73,(byte)219,(byte)118,(byte)154,
+	(byte)181,(byte)196,(byte)87,(byte)249,(byte)16,(byte)48,(byte)80,(byte)240,(byte)11,(byte)29,(byte)39,(byte)105,(byte)187,(byte)214,(byte)97,(byte)163,
+	(byte)254,(byte)25,(byte)43,(byte)125,(byte)135,(byte)146,(byte)173,(byte)236,(byte)47,(byte)113,(byte)147,(byte)174,(byte)233,(byte)32,(byte)96,(byte)160,
+	(byte)251,(byte)22,(byte)58,(byte)78,(byte)210,(byte)109,(byte)183,(byte)194,(byte)93,(byte)231,(byte)50,(byte)86,(byte)250,(byte)21,(byte)63,(byte)65,
+	(byte)195,(byte)94,(byte)226,(byte)61,(byte)71,(byte)201,(byte)64,(byte)192,(byte)91,(byte)237,(byte)44,(byte)116,(byte)156,(byte)191,(byte)218,(byte)117,
+	(byte)159,(byte)186,(byte)213,(byte)100,(byte)172,(byte)239,(byte)42,(byte)126,(byte)130,(byte)157,(byte)188,(byte)223,(byte)122,(byte)142,(byte)137,(byte)128,
+	(byte)155,(byte)182,(byte)193,(byte)88,(byte)232,(byte)35,(byte)101,(byte)175,(byte)234,(byte)37,(byte)111,(byte)177,(byte)200,(byte)67,(byte)197,(byte)84,
+	(byte)252,(byte)31,(byte)33,(byte)99,(byte)165,(byte)244,(byte)7,(byte)9,(byte)27,(byte)45,(byte)119,(byte)153,(byte)176,(byte)203,(byte)70,(byte)202,
+	(byte)69,(byte)207,(byte)74,(byte)222,(byte)121,(byte)139,(byte)134,(byte)145,(byte)168,(byte)227,(byte)62,(byte)66,(byte)198,(byte)81,(byte)243,(byte)14,
+	(byte)18,(byte)54,(byte)90,(byte)238,(byte)41,(byte)123,(byte)141,(byte)140,(byte)143,(byte)138,(byte)133,(byte)148,(byte)167,(byte)242,(byte)13,(byte)23,
+	(byte)57,(byte)75,(byte)221,(byte)124,(byte)132,(byte)151,(byte)162,(byte)253,(byte)28,(byte)36,(byte)108,(byte)180,(byte)199,(byte)82,(byte)246,(byte)1};
+
+	private static final byte[] ltab=
+	{(byte)0,(byte)255,(byte)25,(byte)1,(byte)50,(byte)2,(byte)26,(byte)198,(byte)75,(byte)199,(byte)27,(byte)104,(byte)51,(byte)238,(byte)223,(byte)3,
+	(byte)100,(byte)4,(byte)224,(byte)14,(byte)52,(byte)141,(byte)129,(byte)239,(byte)76,(byte)113,(byte)8,(byte)200,(byte)248,(byte)105,(byte)28,(byte)193,
+	(byte)125,(byte)194,(byte)29,(byte)181,(byte)249,(byte)185,(byte)39,(byte)106,(byte)77,(byte)228,(byte)166,(byte)114,(byte)154,(byte)201,(byte)9,(byte)120,
+	(byte)101,(byte)47,(byte)138,(byte)5,(byte)33,(byte)15,(byte)225,(byte)36,(byte)18,(byte)240,(byte)130,(byte)69,(byte)53,(byte)147,(byte)218,(byte)142,
+	(byte)150,(byte)143,(byte)219,(byte)189,(byte)54,(byte)208,(byte)206,(byte)148,(byte)19,(byte)92,(byte)210,(byte)241,(byte)64,(byte)70,(byte)131,(byte)56,
+	(byte)102,(byte)221,(byte)253,(byte)48,(byte)191,(byte)6,(byte)139,(byte)98,(byte)179,(byte)37,(byte)226,(byte)152,(byte)34,(byte)136,(byte)145,(byte)16,
+	(byte)126,(byte)110,(byte)72,(byte)195,(byte)163,(byte)182,(byte)30,(byte)66,(byte)58,(byte)107,(byte)40,(byte)84,(byte)250,(byte)133,(byte)61,(byte)186,
+	(byte)43,(byte)121,(byte)10,(byte)21,(byte)155,(byte)159,(byte)94,(byte)202,(byte)78,(byte)212,(byte)172,(byte)229,(byte)243,(byte)115,(byte)167,(byte)87,
+	(byte)175,(byte)88,(byte)168,(byte)80,(byte)244,(byte)234,(byte)214,(byte)116,(byte)79,(byte)174,(byte)233,(byte)213,(byte)231,(byte)230,(byte)173,(byte)232,
+	(byte)44,(byte)215,(byte)117,(byte)122,(byte)235,(byte)22,(byte)11,(byte)245,(byte)89,(byte)203,(byte)95,(byte)176,(byte)156,(byte)169,(byte)81,(byte)160,
+	(byte)127,(byte)12,(byte)246,(byte)111,(byte)23,(byte)196,(byte)73,(byte)236,(byte)216,(byte)67,(byte)31,(byte)45,(byte)164,(byte)118,(byte)123,(byte)183,
+	(byte)204,(byte)187,(byte)62,(byte)90,(byte)251,(byte)96,(byte)177,(byte)134,(byte)59,(byte)82,(byte)161,(byte)108,(byte)170,(byte)85,(byte)41,(byte)157,
+	(byte)151,(byte)178,(byte)135,(byte)144,(byte)97,(byte)190,(byte)220,(byte)252,(byte)188,(byte)149,(byte)207,(byte)205,(byte)55,(byte)63,(byte)91,(byte)209,
+	(byte)83,(byte)57,(byte)132,(byte)60,(byte)65,(byte)162,(byte)109,(byte)71,(byte)20,(byte)42,(byte)158,(byte)93,(byte)86,(byte)242,(byte)211,(byte)171,
+	(byte)68,(byte)17,(byte)146,(byte)217,(byte)35,(byte)32,(byte)46,(byte)137,(byte)180,(byte)124,(byte)184,(byte)38,(byte)119,(byte)153,(byte)227,(byte)165,
+	(byte)103,(byte)74,(byte)237,(byte)222,(byte)197,(byte)49,(byte)254,(byte)24,(byte)13,(byte)99,(byte)140,(byte)128,(byte)192,(byte)247,(byte)112,(byte)7};
+
+	private static final byte[] fbsub=
+	{(byte)99,(byte)124,(byte)119,(byte)123,(byte)242,(byte)107,(byte)111,(byte)197,(byte)48,(byte)1,(byte)103,(byte)43,(byte)254,(byte)215,(byte)171,(byte)118,
+	(byte)202,(byte)130,(byte)201,(byte)125,(byte)250,(byte)89,(byte)71,(byte)240,(byte)173,(byte)212,(byte)162,(byte)175,(byte)156,(byte)164,(byte)114,(byte)192,
+	(byte)183,(byte)253,(byte)147,(byte)38,(byte)54,(byte)63,(byte)247,(byte)204,(byte)52,(byte)165,(byte)229,(byte)241,(byte)113,(byte)216,(byte)49,(byte)21,
+	(byte)4,(byte)199,(byte)35,(byte)195,(byte)24,(byte)150,(byte)5,(byte)154,(byte)7,(byte)18,(byte)128,(byte)226,(byte)235,(byte)39,(byte)178,(byte)117,
+	(byte)9,(byte)131,(byte)44,(byte)26,(byte)27,(byte)110,(byte)90,(byte)160,(byte)82,(byte)59,(byte)214,(byte)179,(byte)41,(byte)227,(byte)47,(byte)132,
+	(byte)83,(byte)209,(byte)0,(byte)237,(byte)32,(byte)252,(byte)177,(byte)91,(byte)106,(byte)203,(byte)190,(byte)57,(byte)74,(byte)76,(byte)88,(byte)207,
+	(byte)208,(byte)239,(byte)170,(byte)251,(byte)67,(byte)77,(byte)51,(byte)133,(byte)69,(byte)249,(byte)2,(byte)127,(byte)80,(byte)60,(byte)159,(byte)168,
+	(byte)81,(byte)163,(byte)64,(byte)143,(byte)146,(byte)157,(byte)56,(byte)245,(byte)188,(byte)182,(byte)218,(byte)33,(byte)16,(byte)255,(byte)243,(byte)210,
+	(byte)205,(byte)12,(byte)19,(byte)236,(byte)95,(byte)151,(byte)68,(byte)23,(byte)196,(byte)167,(byte)126,(byte)61,(byte)100,(byte)93,(byte)25,(byte)115,
+	(byte)96,(byte)129,(byte)79,(byte)220,(byte)34,(byte)42,(byte)144,(byte)136,(byte)70,(byte)238,(byte)184,(byte)20,(byte)222,(byte)94,(byte)11,(byte)219,
+	(byte)224,(byte)50,(byte)58,(byte)10,(byte)73,(byte)6,(byte)36,(byte)92,(byte)194,(byte)211,(byte)172,(byte)98,(byte)145,(byte)149,(byte)228,(byte)121,
+	(byte)231,(byte)200,(byte)55,(byte)109,(byte)141,(byte)213,(byte)78,(byte)169,(byte)108,(byte)86,(byte)244,(byte)234,(byte)101,(byte)122,(byte)174,(byte)8,
+	(byte)186,(byte)120,(byte)37,(byte)46,(byte)28,(byte)166,(byte)180,(byte)198,(byte)232,(byte)221,(byte)116,(byte)31,(byte)75,(byte)189,(byte)139,(byte)138,
+	(byte)112,(byte)62,(byte)181,(byte)102,(byte)72,(byte)3,(byte)246,(byte)14,(byte)97,(byte)53,(byte)87,(byte)185,(byte)134,(byte)193,(byte)29,(byte)158,
+	(byte)225,(byte)248,(byte)152,(byte)17,(byte)105,(byte)217,(byte)142,(byte)148,(byte)155,(byte)30,(byte)135,(byte)233,(byte)206,(byte)85,(byte)40,(byte)223,
+	(byte)140,(byte)161,(byte)137,(byte)13,(byte)191,(byte)230,(byte)66,(byte)104,(byte)65,(byte)153,(byte)45,(byte)15,(byte)176,(byte)84,(byte)187,(byte)22};
+
+	private static final byte[] rbsub=
+	{(byte)82,(byte)9,(byte)106,(byte)213,(byte)48,(byte)54,(byte)165,(byte)56,(byte)191,(byte)64,(byte)163,(byte)158,(byte)129,(byte)243,(byte)215,(byte)251,
+	(byte)124,(byte)227,(byte)57,(byte)130,(byte)155,(byte)47,(byte)255,(byte)135,(byte)52,(byte)142,(byte)67,(byte)68,(byte)196,(byte)222,(byte)233,(byte)203,
+	(byte)84,(byte)123,(byte)148,(byte)50,(byte)166,(byte)194,(byte)35,(byte)61,(byte)238,(byte)76,(byte)149,(byte)11,(byte)66,(byte)250,(byte)195,(byte)78,
+	(byte)8,(byte)46,(byte)161,(byte)102,(byte)40,(byte)217,(byte)36,(byte)178,(byte)118,(byte)91,(byte)162,(byte)73,(byte)109,(byte)139,(byte)209,(byte)37,
+	(byte)114,(byte)248,(byte)246,(byte)100,(byte)134,(byte)104,(byte)152,(byte)22,(byte)212,(byte)164,(byte)92,(byte)204,(byte)93,(byte)101,(byte)182,(byte)146,
+	(byte)108,(byte)112,(byte)72,(byte)80,(byte)253,(byte)237,(byte)185,(byte)218,(byte)94,(byte)21,(byte)70,(byte)87,(byte)167,(byte)141,(byte)157,(byte)132,
+	(byte)144,(byte)216,(byte)171,(byte)0,(byte)140,(byte)188,(byte)211,(byte)10,(byte)247,(byte)228,(byte)88,(byte)5,(byte)184,(byte)179,(byte)69,(byte)6,
+	(byte)208,(byte)44,(byte)30,(byte)143,(byte)202,(byte)63,(byte)15,(byte)2,(byte)193,(byte)175,(byte)189,(byte)3,(byte)1,(byte)19,(byte)138,(byte)107,
+	(byte)58,(byte)145,(byte)17,(byte)65,(byte)79,(byte)103,(byte)220,(byte)234,(byte)151,(byte)242,(byte)207,(byte)206,(byte)240,(byte)180,(byte)230,(byte)115,
+	(byte)150,(byte)172,(byte)116,(byte)34,(byte)231,(byte)173,(byte)53,(byte)133,(byte)226,(byte)249,(byte)55,(byte)232,(byte)28,(byte)117,(byte)223,(byte)110,
+	(byte)71,(byte)241,(byte)26,(byte)113,(byte)29,(byte)41,(byte)197,(byte)137,(byte)111,(byte)183,(byte)98,(byte)14,(byte)170,(byte)24,(byte)190,(byte)27,
+	(byte)252,(byte)86,(byte)62,(byte)75,(byte)198,(byte)210,(byte)121,(byte)32,(byte)154,(byte)219,(byte)192,(byte)254,(byte)120,(byte)205,(byte)90,(byte)244,
+	(byte)31,(byte)221,(byte)168,(byte)51,(byte)136,(byte)7,(byte)199,(byte)49,(byte)177,(byte)18,(byte)16,(byte)89,(byte)39,(byte)128,(byte)236,(byte)95,
+	(byte)96,(byte)81,(byte)127,(byte)169,(byte)25,(byte)181,(byte)74,(byte)13,(byte)45,(byte)229,(byte)122,(byte)159,(byte)147,(byte)201,(byte)156,(byte)239,
+	(byte)160,(byte)224,(byte)59,(byte)77,(byte)174,(byte)42,(byte)245,(byte)176,(byte)200,(byte)235,(byte)187,(byte)60,(byte)131,(byte)83,(byte)153,(byte)97,
+	(byte)23,(byte)43,(byte)4,(byte)126,(byte)186,(byte)119,(byte)214,(byte)38,(byte)225,(byte)105,(byte)20,(byte)99,(byte)85,(byte)33,(byte)12,(byte)125};
+
+	private static final byte[] rco=
+	{(byte)1,(byte)2,(byte)4,(byte)8,(byte)16,(byte)32,(byte)64,(byte)128,(byte)27,(byte)54,(byte)108,(byte)216,(byte)171,(byte)77,(byte)154,(byte)47};
+
+	private static final int[] ftable=
+	{0xa56363c6,0x847c7cf8,0x997777ee,0x8d7b7bf6,0xdf2f2ff,0xbd6b6bd6,
+	0xb16f6fde,0x54c5c591,0x50303060,0x3010102,0xa96767ce,0x7d2b2b56,
+	0x19fefee7,0x62d7d7b5,0xe6abab4d,0x9a7676ec,0x45caca8f,0x9d82821f,
+	0x40c9c989,0x877d7dfa,0x15fafaef,0xeb5959b2,0xc947478e,0xbf0f0fb,
+	0xecadad41,0x67d4d4b3,0xfda2a25f,0xeaafaf45,0xbf9c9c23,0xf7a4a453,
+	0x967272e4,0x5bc0c09b,0xc2b7b775,0x1cfdfde1,0xae93933d,0x6a26264c,
+	0x5a36366c,0x413f3f7e,0x2f7f7f5,0x4fcccc83,0x5c343468,0xf4a5a551,
+	0x34e5e5d1,0x8f1f1f9,0x937171e2,0x73d8d8ab,0x53313162,0x3f15152a,
+	0xc040408,0x52c7c795,0x65232346,0x5ec3c39d,0x28181830,0xa1969637,
+	0xf05050a,0xb59a9a2f,0x907070e,0x36121224,0x9b80801b,0x3de2e2df,
+	0x26ebebcd,0x6927274e,0xcdb2b27f,0x9f7575ea,0x1b090912,0x9e83831d,
+	0x742c2c58,0x2e1a1a34,0x2d1b1b36,0xb26e6edc,0xee5a5ab4,0xfba0a05b,
+	0xf65252a4,0x4d3b3b76,0x61d6d6b7,0xceb3b37d,0x7b292952,0x3ee3e3dd,
+	0x712f2f5e,0x97848413,0xf55353a6,0x68d1d1b9,0x0,0x2cededc1,
+	0x60202040,0x1ffcfce3,0xc8b1b179,0xed5b5bb6,0xbe6a6ad4,0x46cbcb8d,
+	0xd9bebe67,0x4b393972,0xde4a4a94,0xd44c4c98,0xe85858b0,0x4acfcf85,
+	0x6bd0d0bb,0x2aefefc5,0xe5aaaa4f,0x16fbfbed,0xc5434386,0xd74d4d9a,
+	0x55333366,0x94858511,0xcf45458a,0x10f9f9e9,0x6020204,0x817f7ffe,
+	0xf05050a0,0x443c3c78,0xba9f9f25,0xe3a8a84b,0xf35151a2,0xfea3a35d,
+	0xc0404080,0x8a8f8f05,0xad92923f,0xbc9d9d21,0x48383870,0x4f5f5f1,
+	0xdfbcbc63,0xc1b6b677,0x75dadaaf,0x63212142,0x30101020,0x1affffe5,
+	0xef3f3fd,0x6dd2d2bf,0x4ccdcd81,0x140c0c18,0x35131326,0x2fececc3,
+	0xe15f5fbe,0xa2979735,0xcc444488,0x3917172e,0x57c4c493,0xf2a7a755,
+	0x827e7efc,0x473d3d7a,0xac6464c8,0xe75d5dba,0x2b191932,0x957373e6,
+	0xa06060c0,0x98818119,0xd14f4f9e,0x7fdcdca3,0x66222244,0x7e2a2a54,
+	0xab90903b,0x8388880b,0xca46468c,0x29eeeec7,0xd3b8b86b,0x3c141428,
+	0x79dedea7,0xe25e5ebc,0x1d0b0b16,0x76dbdbad,0x3be0e0db,0x56323264,
+	0x4e3a3a74,0x1e0a0a14,0xdb494992,0xa06060c,0x6c242448,0xe45c5cb8,
+	0x5dc2c29f,0x6ed3d3bd,0xefacac43,0xa66262c4,0xa8919139,0xa4959531,
+	0x37e4e4d3,0x8b7979f2,0x32e7e7d5,0x43c8c88b,0x5937376e,0xb76d6dda,
+	0x8c8d8d01,0x64d5d5b1,0xd24e4e9c,0xe0a9a949,0xb46c6cd8,0xfa5656ac,
+	0x7f4f4f3,0x25eaeacf,0xaf6565ca,0x8e7a7af4,0xe9aeae47,0x18080810,
+	0xd5baba6f,0x887878f0,0x6f25254a,0x722e2e5c,0x241c1c38,0xf1a6a657,
+	0xc7b4b473,0x51c6c697,0x23e8e8cb,0x7cdddda1,0x9c7474e8,0x211f1f3e,
+	0xdd4b4b96,0xdcbdbd61,0x868b8b0d,0x858a8a0f,0x907070e0,0x423e3e7c,
+	0xc4b5b571,0xaa6666cc,0xd8484890,0x5030306,0x1f6f6f7,0x120e0e1c,
+	0xa36161c2,0x5f35356a,0xf95757ae,0xd0b9b969,0x91868617,0x58c1c199,
+	0x271d1d3a,0xb99e9e27,0x38e1e1d9,0x13f8f8eb,0xb398982b,0x33111122,
+	0xbb6969d2,0x70d9d9a9,0x898e8e07,0xa7949433,0xb69b9b2d,0x221e1e3c,
+	0x92878715,0x20e9e9c9,0x49cece87,0xff5555aa,0x78282850,0x7adfdfa5,
+	0x8f8c8c03,0xf8a1a159,0x80898909,0x170d0d1a,0xdabfbf65,0x31e6e6d7,
+	0xc6424284,0xb86868d0,0xc3414182,0xb0999929,0x772d2d5a,0x110f0f1e,
+	0xcbb0b07b,0xfc5454a8,0xd6bbbb6d,0x3a16162c};
+
+	private static final int[] rtable=
+	{0x50a7f451,0x5365417e,0xc3a4171a,0x965e273a,0xcb6bab3b,0xf1459d1f,
+	0xab58faac,0x9303e34b,0x55fa3020,0xf66d76ad,0x9176cc88,0x254c02f5,
+	0xfcd7e54f,0xd7cb2ac5,0x80443526,0x8fa362b5,0x495ab1de,0x671bba25,
+	0x980eea45,0xe1c0fe5d,0x2752fc3,0x12f04c81,0xa397468d,0xc6f9d36b,
+	0xe75f8f03,0x959c9215,0xeb7a6dbf,0xda595295,0x2d83bed4,0xd3217458,
+	0x2969e049,0x44c8c98e,0x6a89c275,0x78798ef4,0x6b3e5899,0xdd71b927,
+	0xb64fe1be,0x17ad88f0,0x66ac20c9,0xb43ace7d,0x184adf63,0x82311ae5,
+	0x60335197,0x457f5362,0xe07764b1,0x84ae6bbb,0x1ca081fe,0x942b08f9,
+	0x58684870,0x19fd458f,0x876cde94,0xb7f87b52,0x23d373ab,0xe2024b72,
+	0x578f1fe3,0x2aab5566,0x728ebb2,0x3c2b52f,0x9a7bc586,0xa50837d3,
+	0xf2872830,0xb2a5bf23,0xba6a0302,0x5c8216ed,0x2b1ccf8a,0x92b479a7,
+	0xf0f207f3,0xa1e2694e,0xcdf4da65,0xd5be0506,0x1f6234d1,0x8afea6c4,
+	0x9d532e34,0xa055f3a2,0x32e18a05,0x75ebf6a4,0x39ec830b,0xaaef6040,
+	0x69f715e,0x51106ebd,0xf98a213e,0x3d06dd96,0xae053edd,0x46bde64d,
+	0xb58d5491,0x55dc471,0x6fd40604,0xff155060,0x24fb9819,0x97e9bdd6,
+	0xcc434089,0x779ed967,0xbd42e8b0,0x888b8907,0x385b19e7,0xdbeec879,
+	0x470a7ca1,0xe90f427c,0xc91e84f8,0x0,0x83868009,0x48ed2b32,
+	0xac70111e,0x4e725a6c,0xfbff0efd,0x5638850f,0x1ed5ae3d,0x27392d36,
+	0x64d90f0a,0x21a65c68,0xd1545b9b,0x3a2e3624,0xb1670a0c,0xfe75793,
+	0xd296eeb4,0x9e919b1b,0x4fc5c080,0xa220dc61,0x694b775a,0x161a121c,
+	0xaba93e2,0xe52aa0c0,0x43e0223c,0x1d171b12,0xb0d090e,0xadc78bf2,
+	0xb9a8b62d,0xc8a91e14,0x8519f157,0x4c0775af,0xbbdd99ee,0xfd607fa3,
+	0x9f2601f7,0xbcf5725c,0xc53b6644,0x347efb5b,0x7629438b,0xdcc623cb,
+	0x68fcedb6,0x63f1e4b8,0xcadc31d7,0x10856342,0x40229713,0x2011c684,
+	0x7d244a85,0xf83dbbd2,0x1132f9ae,0x6da129c7,0x4b2f9e1d,0xf330b2dc,
+	0xec52860d,0xd0e3c177,0x6c16b32b,0x99b970a9,0xfa489411,0x2264e947,
+	0xc48cfca8,0x1a3ff0a0,0xd82c7d56,0xef903322,0xc74e4987,0xc1d138d9,
+	0xfea2ca8c,0x360bd498,0xcf81f5a6,0x28de7aa5,0x268eb7da,0xa4bfad3f,
+	0xe49d3a2c,0xd927850,0x9bcc5f6a,0x62467e54,0xc2138df6,0xe8b8d890,
+	0x5ef7392e,0xf5afc382,0xbe805d9f,0x7c93d069,0xa92dd56f,0xb31225cf,
+	0x3b99acc8,0xa77d1810,0x6e639ce8,0x7bbb3bdb,0x97826cd,0xf418596e,
+	0x1b79aec,0xa89a4f83,0x656e95e6,0x7ee6ffaa,0x8cfbc21,0xe6e815ef,
+	0xd99be7ba,0xce366f4a,0xd4099fea,0xd67cb029,0xafb2a431,0x31233f2a,
+	0x3094a5c6,0xc066a235,0x37bc4e74,0xa6ca82fc,0xb0d090e0,0x15d8a733,
+	0x4a9804f1,0xf7daec41,0xe50cd7f,0x2ff69117,0x8dd64d76,0x4db0ef43,
+	0x544daacc,0xdf0496e4,0xe3b5d19e,0x1b886a4c,0xb81f2cc1,0x7f516546,
+	0x4ea5e9d,0x5d358c01,0x737487fa,0x2e410bfb,0x5a1d67b3,0x52d2db92,
+	0x335610e9,0x1347d66d,0x8c61d79a,0x7a0ca137,0x8e14f859,0x893c13eb,
+	0xee27a9ce,0x35c961b7,0xede51ce1,0x3cb1477a,0x59dfd29c,0x3f73f255,
+	0x79ce1418,0xbf37c773,0xeacdf753,0x5baafd5f,0x146f3ddf,0x86db4478,
+	0x81f3afca,0x3ec468b9,0x2c342438,0x5f40a3c2,0x72c31d16,0xc25e2bc,
+	0x8b493c28,0x41950dff,0x7101a839,0xdeb30c08,0x9ce4b4d8,0x90c15664,
+	0x6184cb7b,0x70b632d5,0x745c6c48,0x4257b8d0};
+
+
+/* Rotates 32-bit word left by 1, 2 or 3 byte  */
+
+	private static int ROTL8(int x)
+	{
+		return (((x)<<8)|((x)>>>24));
+	}
+
+	private static int ROTL16(int x)
+	{
+		return (((x)<<16)|((x)>>>16));
+	}
+
+	private static int ROTL24(int x)
+	{
+		return (((x)<<24)|((x)>>>8));
+	}
+
+	private static int pack(byte[] b)
+	{ /* pack bytes into a 32-bit Word */
+		return ((((int)b[3])&0xff)<<24)|(((int)b[2]&0xff)<<16)|(((int)b[1]&0xff)<<8)|((int)b[0]&0xff);
+	}
+
+	private static byte[] unpack(int a)
+	{ /* unpack bytes from a word */
+		byte [] b=new byte[4];
+		b[0]=(byte)(a);
+		b[1]=(byte)(a>>>8);
+		b[2]=(byte)(a>>>16);
+		b[3]=(byte)(a>>>24);
+		return b;
+	}
+
+	private static byte bmul(byte x,byte y)
+	{ /* x.y= AntiLog(Log(x) + Log(y)) */
+
+		int ix=((int)x)&0xff;
+		int iy=((int)y)&0xff;
+		int lx=((int)ltab[ix])&0xff;
+		int ly=((int)ltab[iy])&0xff;
+		if (x!=0 && y!=0) return ptab[(lx+ly)%255];
+		else return (byte)0;
+	}
+
+  //  if (x && y) 
+
+	private static int SubByte(int a)
+	{
+		byte [] b=unpack(a);
+		b[0]=fbsub[(int)b[0]&0xff];
+		b[1]=fbsub[(int)b[1]&0xff];
+		b[2]=fbsub[(int)b[2]&0xff];
+		b[3]=fbsub[(int)b[3]&0xff];
+		return pack(b);    
+	}
+
+	private static byte product(int x,int y)
+	{ /* dot product of two 4-byte arrays */
+		byte [] xb;//=new byte[4];
+		byte [] yb;//=new byte[4];
+		xb=unpack(x);
+		yb=unpack(y); 
+
+		return (byte)(bmul(xb[0],yb[0])^bmul(xb[1],yb[1])^bmul(xb[2],yb[2])^bmul(xb[3],yb[3]));
+	}
+
+	private static int InvMixCol(int x)
+	{ /* matrix Multiplication */
+		int y,m;
+		byte [] b=new byte[4];
+
+		m=pack(InCo);
+		b[3]=product(m,x);
+		m=ROTL24(m);
+		b[2]=product(m,x);
+		m=ROTL24(m);
+		b[1]=product(m,x);
+		m=ROTL24(m);
+		b[0]=product(m,x);
+		y=pack(b);
+		return y;
+	}
+
+	private static void increment(byte [] f)
+	{
+		int i;
+		for (i=0;i<16;i++)
+		{
+			f[i]++;
+			if (f[i]!=0) break;
+		}
+	}
+
+/* reset cipher */
+	public void reset(int m,byte[] iv)
+	{ /* reset mode, or reset iv */
+		mode=m;
+		for (int i=0;i<16;i++)
+			f[i]=0;
+		if (mode!=ECB && iv!=null)
+			for (int i=0;i<16;i++)
+				f[i]=iv[i];
+	}
+
+	public byte[] getreg()
+	{
+		byte [] ir=new byte[16];
+		for (int i=0;i<16;i++) ir[i]=f[i];
+		return ir;
+	}
+
+/* Initialise cipher */
+	public boolean init(int m,int nk,byte[] key,byte[] iv)
+	{	/* Key=16 bytes */
+		/* Key Scheduler. Create expanded encryption key */
+		int i,j,k,N,nr;
+		int [] CipherKey=new int[8];
+		byte [] b=new byte[4];
+		nk/=4;
+
+		if (nk!=4 && nk!=6 && nk!=8) return false;
+
+		nr=6+nk;
+
+		Nk=nk; Nr=nr;
+
+		reset(m,iv);
+		N=4*(nr+1);
+    
+		for (i=j=0;i<nk;i++,j+=4)
+		{
+			for (k=0;k<4;k++) b[k]=key[j+k];
+			CipherKey[i]=pack(b);
+		}
+		for (i=0;i<nk;i++) fkey[i]=CipherKey[i];
+		for (j=nk,k=0;j<N;j+=nk,k++)
+		{
+			fkey[j]=fkey[j-nk]^SubByte(ROTL24(fkey[j-1]))^((int)rco[k])&0xff;
+			for (i=1;i<nk && (i+j)<N;i++)
+				fkey[i+j]=fkey[i+j-nk]^fkey[i+j-1];
+		}
+
+ /* now for the expanded decrypt key in reverse order */
+
+		for (j=0;j<4;j++) rkey[j+N-4]=fkey[j]; 
+		for (i=4;i<N-4;i+=4)
+		{
+			k=N-4-i;
+			for (j=0;j<4;j++) rkey[k+j]=InvMixCol(fkey[i+j]);
+		}
+		for (j=N-4;j<N;j++) rkey[j-N+4]=fkey[j];
+		return true;
+	}
+
+/* Encrypt a single block */
+	public void ecb_encrypt(byte[] buff)
+	{
+		int i,j,k;
+		int t;
+    	byte [] b=new byte[4];
+    	int [] p=new int[4];
+    	int [] q=new int[4];
+
+		for (i=j=0;i<4;i++,j+=4)
+		{
+			for (k=0;k<4;k++) b[k]=buff[j+k];
+			p[i]=pack(b);
+			p[i]^=fkey[i];
+		}
+
+		k=4;
+
+/* State alternates between p and q */
+		for (i=1;i<Nr;i++)
+		{ 
+			q[0]=fkey[k]^ftable[p[0]&0xff]^
+				ROTL8(ftable[(p[1]>>>8)&0xff])^
+				ROTL16(ftable[(p[2]>>>16)&0xff])^
+				ROTL24(ftable[(p[3]>>>24)&0xff]);
+			q[1]=fkey[k+1]^ftable[p[1]&0xff]^
+				ROTL8(ftable[(p[2]>>>8)&0xff])^
+				ROTL16(ftable[(p[3]>>>16)&0xff])^
+				ROTL24(ftable[(p[0]>>>24)&0xff]);
+			q[2]=fkey[k+2]^ftable[p[2]&0xff]^
+				ROTL8(ftable[(p[3]>>>8)&0xff])^
+				ROTL16(ftable[(p[0]>>>16)&0xff])^
+				ROTL24(ftable[(p[1]>>>24)&0xff]);
+			q[3]=fkey[k+3]^ftable[p[3]&0xff]^
+				ROTL8(ftable[(p[0]>>>8)&0xff])^
+				ROTL16(ftable[(p[1]>>>16)&0xff])^
+				ROTL24(ftable[(p[2]>>>24)&0xff]);
+
+			k+=4;
+			for (j=0;j<4;j++)
+			{
+				t=p[j]; p[j]=q[j]; q[j]=t;
+			}
+		}
+
+/* Last Round */ 
+    
+		q[0]=fkey[k]^((int)fbsub[p[0]&0xff]&0xff)^
+			ROTL8((int)fbsub[(p[1]>>>8)&0xff]&0xff)^
+			ROTL16((int)fbsub[(p[2]>>>16)&0xff]&0xff)^
+			ROTL24((int)fbsub[(p[3]>>>24)&0xff]&0xff);
+
+		q[1]=fkey[k+1]^((int)fbsub[p[1]&0xff]&0xff)^
+			ROTL8((int)fbsub[(p[2]>>>8)&0xff]&0xff)^
+			ROTL16((int)fbsub[(p[3]>>>16)&0xff]&0xff)^
+			ROTL24((int)fbsub[(p[0]>>>24)&0xff]&0xff);
+
+		q[2]=fkey[k+2]^((int)fbsub[p[2]&0xff]&0xff)^
+			ROTL8((int)fbsub[(p[3]>>>8)&0xff]&0xff)^
+			ROTL16((int)fbsub[(p[0]>>>16)&0xff]&0xff)^
+			ROTL24((int)fbsub[(p[1]>>>24)&0xff]&0xff);
+
+		q[3]=fkey[k+3]^((int)fbsub[(p[3])&0xff]&0xff)^
+			ROTL8((int)fbsub[(p[0]>>>8)&0xff]&0xff)^
+			ROTL16((int)fbsub[(p[1]>>>16)&0xff]&0xff)^
+			ROTL24((int)fbsub[(p[2]>>>24)&0xff]&0xff);
+
+		for (i=j=0;i<4;i++,j+=4)
+		{
+			b=unpack(q[i]);
+			for (k=0;k<4;k++) buff[j+k]=b[k];
+		}
+	}
+
+/* Decrypt a single block */
+	public void ecb_decrypt(byte[] buff)
+	{
+		int i,j,k;
+		int t;
+    	byte [] b=new byte[4];
+    	int [] p=new int[4];
+    	int [] q=new int[4];
+
+		for (i=j=0;i<4;i++,j+=4)
+		{
+			for (k=0;k<4;k++) b[k]=buff[j+k];
+			p[i]=pack(b);
+			p[i]^=rkey[i];
+		}
+
+		k=4;
+
+/* State alternates between p and q */
+		for (i=1;i<Nr;i++)
+		{ 
+			q[0]=rkey[k]^rtable[p[0]&0xff]^
+				ROTL8(rtable[(p[3]>>>8)&0xff])^
+				ROTL16(rtable[(p[2]>>>16)&0xff])^
+				ROTL24(rtable[(p[1]>>>24)&0xff]);
+			q[1]=rkey[k+1]^rtable[p[1]&0xff]^
+				ROTL8(rtable[(p[0]>>>8)&0xff])^
+				ROTL16(rtable[(p[3]>>>16)&0xff])^
+				ROTL24(rtable[(p[2]>>>24)&0xff]);
+			q[2]=rkey[k+2]^rtable[p[2]&0xff]^
+				ROTL8(rtable[(p[1]>>>8)&0xff])^
+				ROTL16(rtable[(p[0]>>>16)&0xff])^
+				ROTL24(rtable[(p[3]>>>24)&0xff]);
+			q[3]=rkey[k+3]^rtable[p[3]&0xff]^
+				ROTL8(rtable[(p[2]>>>8)&0xff])^
+				ROTL16(rtable[(p[1]>>>16)&0xff])^
+				ROTL24(rtable[(p[0]>>>24)&0xff]);
+
+			k+=4;
+			for (j=0;j<4;j++)
+			{
+				t=p[j]; p[j]=q[j]; q[j]=t;
+			}
+		}
+
+/* Last Round */ 
+
+		q[0]=rkey[k]^((int)rbsub[p[0]&0xff]&0xff)^
+			ROTL8((int)rbsub[(p[3]>>>8)&0xff]&0xff)^
+			ROTL16((int)rbsub[(p[2]>>>16)&0xff]&0xff)^
+			ROTL24((int)rbsub[(p[1]>>>24)&0xff]&0xff);
+		q[1]=rkey[k+1]^((int)rbsub[p[1]&0xff]&0xff)^
+			ROTL8((int)rbsub[(p[0]>>>8)&0xff]&0xff)^
+			ROTL16((int)rbsub[(p[3]>>>16)&0xff]&0xff)^
+			ROTL24((int)rbsub[(p[2]>>>24)&0xff]&0xff);
+		q[2]=rkey[k+2]^((int)rbsub[p[2]&0xff]&0xff)^
+			ROTL8((int)rbsub[(p[1]>>>8)&0xff]&0xff)^
+			ROTL16((int)rbsub[(p[0]>>>16)&0xff]&0xff)^
+			ROTL24((int)rbsub[(p[3]>>>24)&0xff]&0xff);
+		q[3]=rkey[k+3]^((int)rbsub[p[3]&0xff]&0xff)^
+			ROTL8((int)rbsub[(p[2]>>>8)&0xff]&0xff)^
+			ROTL16((int)rbsub[(p[1]>>>16)&0xff]&0xff)^
+			ROTL24((int)rbsub[(p[0]>>>24)&0xff]&0xff);
+
+		for (i=j=0;i<4;i++,j+=4)
+		{
+			b=unpack(q[i]);
+			for (k=0;k<4;k++) buff[j+k]=b[k];
+		}
+
+	}
+
+/* Encrypt using selected mode of operation */
+	public int encrypt(byte[] buff)
+	{
+		int j,bytes;
+		byte[] st=new byte[16];
+		int fell_off;
+
+// Supported Modes of Operation 
+
+		fell_off=0;
+		switch (mode)
+		{
+		case ECB: 
+			ecb_encrypt(buff);
+			return 0;
+		case CBC:
+			for (j=0;j<16;j++) buff[j]^=f[j];
+			ecb_encrypt(buff);
+			for (j=0;j<16;j++) f[j]=buff[j];
+			return 0;
+
+		case CFB1:
+		case CFB2:
+		case CFB4:
+			bytes=mode-CFB1+1;
+			for (j=0;j<bytes;j++) fell_off=(fell_off<<8)|f[j];
+			for (j=0;j<16;j++) st[j]=f[j];
+			for (j=bytes;j<16;j++) f[j-bytes]=f[j];
+			ecb_encrypt(st);
+			for (j=0;j<bytes;j++) 
+			{
+				buff[j]^=st[j];
+				f[16-bytes+j]=buff[j];
+			}
+			return fell_off;
+
+		case OFB1:
+		case OFB2:
+		case OFB4:
+		case OFB8:
+		case OFB16:
+
+			bytes=mode-OFB1+1;
+			ecb_encrypt(f);
+			for (j=0;j<bytes;j++) buff[j]^=f[j];
+			return 0;
+
+		case CTR1:
+		case CTR2:
+		case CTR4:
+		case CTR8:
+		case CTR16:
+
+			bytes=mode-CTR1+1;
+			for (j=0;j<16;j++) st[j]=f[j];
+			ecb_encrypt(st);
+			for (j=0;j<bytes;j++) buff[j]^=st[j];
+			increment(f);
+
+    default:
+			return 0;
+		}
+	}
+
+/* Decrypt using selected mode of operation */
+	public int decrypt(byte[] buff)
+	{
+		int j,bytes;
+		byte[] st=new byte[16];
+		int fell_off;
+
+   // Supported modes of operation 
+		fell_off=0;
+		switch (mode)
+		{
+		case ECB:
+			ecb_decrypt(buff);
+			return 0;
+		case CBC:
+			for (j=0;j<16;j++) 
+			{
+				st[j]=f[j];
+				f[j]=buff[j];
+			}
+			ecb_decrypt(buff);
+			for (j=0;j<16;j++)
+			{	 
+				buff[j]^=st[j];
+				st[j]=0;
+			}
+			return 0;
+		case CFB1:
+		case CFB2:
+		case CFB4:
+			bytes=mode-CFB1+1;
+			for (j=0;j<bytes;j++) fell_off=(fell_off<<8)|f[j];
+			for (j=0;j<16;j++) st[j]=f[j];
+			for (j=bytes;j<16;j++) f[j-bytes]=f[j];
+			ecb_encrypt(st);
+			for (j=0;j<bytes;j++)
+			{
+				f[16-bytes+j]=buff[j];
+				buff[j]^=st[j];
+			}
+			return fell_off;
+		case OFB1:
+		case OFB2:
+		case OFB4:
+		case OFB8:
+		case OFB16:
+			bytes=mode-OFB1+1;
+			ecb_encrypt(f);
+			for (j=0;j<bytes;j++) buff[j]^=f[j];
+			return 0;
+
+		case CTR1:
+		case CTR2:
+		case CTR4:
+		case CTR8:
+		case CTR16:
+
+			bytes=mode-CTR1+1;
+			for (j=0;j<16;j++) st[j]=f[j];
+			ecb_encrypt(st);
+			for (j=0;j<bytes;j++) buff[j]^=st[j];
+			increment(f);
+ 
+		default:
+			return 0;
+		}
+	}
+
+/* Clean up and delete left-overs */
+	public void end()
+	{ // clean up 
+		int i;
+		for (i=0;i<4*(Nr+1);i++)
+			fkey[i]=rkey[i]=0;
+		for (i=0;i<16;i++)
+			f[i]=0;
+	}
+
+	public static void main(String[] args) {
+		int i;
+
+		byte[] key=new byte[32];
+		byte[] block=new byte[16];
+		byte[] iv=new byte[16];
+
+		for (i=0;i<32;i++) key[i]=0;
+		key[0]=1;
+		for (i=0;i<16;i++) iv[i]=(byte)i;
+		for (i=0;i<16;i++) block[i]=(byte)i;
+
+		AES a=new AES();
+
+		a.init(CTR16,32,key,iv);
+		System.out.println("Plain= "); 
+		for (i=0;i<16;i++)  System.out.format("%02X ", block[i]&0xff);
+		System.out.println(""); 
+
+		a.encrypt(block);
+
+		System.out.println("Encrypt= "); 
+		for (i=0;i<16;i++)  System.out.format("%02X ", block[i]&0xff);
+		System.out.println(""); 
+
+		a.reset(CTR16,iv);
+		a.decrypt(block);
+
+		System.out.println("Decrypt= "); 
+		for (i=0;i<16;i++)  System.out.format("%02X ", block[i]&0xff);
+		System.out.println(""); 
+
+		a.end();
+
+	} 
+}
diff --git a/src/main/java/org/apache/milagro/amcl/ANSSI/BIG.java b/src/main/java/org/apache/milagro/amcl/ANSSI/BIG.java
new file mode 100644
index 0000000..ef3f9fa
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/ANSSI/BIG.java
@@ -0,0 +1,917 @@
+/*
+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.
+*/
+
+/* AMCL BIG number class */ 
+
+package org.apache.milagro.amcl.ANSSI;
+import org.apache.milagro.amcl.RAND;
+
+public class BIG {
+
+	public static final int CHUNK=64; /* Set word size */
+
+	public static final int MODBYTES=32; //(1+(MODBITS-1)/8);
+	public static final int BASEBITS=56; 
+
+	public static final int NLEN=(1+((8*MODBYTES-1)/BASEBITS));
+	public static final int DNLEN=2*NLEN;
+	public static final long BMASK=(((long)1<<BASEBITS)-1);
+
+	public static final int HBITS=BASEBITS/2;
+	public static final long HMASK=(((long)1<<HBITS)-1);
+	public static final int NEXCESS = ((int)1<<(CHUNK-BASEBITS-1));
+	public static final int BIGBITS=(MODBYTES*8);
+
+
+
+	protected long[] w=new long[NLEN];
+/* Constructors */
+	public BIG()
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=0;
+	}
+
+	public BIG(int x)
+	{
+		w[0]=x;
+		for (int i=1;i<NLEN;i++)
+			w[i]=0;
+	}
+
+	public BIG(BIG x)
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i];
+	}
+
+	public BIG(DBIG x)
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i];
+	}
+
+	public BIG(long[] x)
+	{
+			for (int i=0;i<NLEN;i++)
+				w[i]=x[i];
+	}
+
+	public long get(int i)
+	{
+		return w[i];
+	}
+
+	public void set(int i,long x)
+	{
+		w[i]=x;
+	} 
+
+
+/* Conditional swap of two bigs depending on d using XOR - no branches */
+	public void cswap(BIG b,int d)
+	{
+		int i;
+		long t,c=(long)d;
+		c=~(c-1);
+
+		for (i=0;i<NLEN;i++)
+		{
+			t=c&(w[i]^b.w[i]);
+			w[i]^=t;
+			b.w[i]^=t;
+		}
+	}
+
+	public void cmove(BIG g,int d)
+	{
+		int i;
+		long t,b=-d;
+
+		for (i=0;i<NLEN;i++)
+		{
+			w[i]^=(w[i]^g.w[i])&b;
+		}
+	}
+
+    public static long cast_to_chunk(int x)
+	{
+		return (long)x;
+	}
+
+/* normalise BIG - force all digits < 2^BASEBITS */
+	public long norm() {
+		long d,carry=0;
+		for (int i=0;i<NLEN-1;i++)
+		{
+			d=w[i]+carry;
+			w[i]=d&BMASK;
+			carry=(d>>BASEBITS);
+		}
+		w[NLEN-1]=(w[NLEN-1]+carry);
+		return (long)(w[NLEN-1]>>((8*MODBYTES)%BASEBITS));  
+	}
+
+/* return number of bits */
+	public int nbits() {
+		BIG t=new BIG(this);
+		int bts,k=NLEN-1;
+		long c;
+		t.norm();
+		while (k>=0 && t.w[k]==0) k--;
+		if (k<0) return 0;
+		bts=BASEBITS*k;
+		c=t.w[k];
+		while (c!=0) {c/=2; bts++;}
+		return bts;
+	}
+
+	public String toRawString()
+	{
+		BIG b=new BIG(this);
+		String s="(";
+		for (int i=0;i<NLEN-1;i++)
+		{
+			s+=Long.toHexString(b.w[i]); s+=",";
+		}
+		s+=Long.toHexString(b.w[NLEN-1]); s+=")";
+		return s;
+	}
+
+/* Convert to Hex String */
+	public String toString() {
+		BIG b;
+		String s="";
+		int len=nbits();
+
+		if (len%4==0) len/=4;
+		else {len/=4; len++;}
+		if (len<MODBYTES*2) len=MODBYTES*2;
+
+		for (int i=len-1;i>=0;i--)
+		{
+			b=new BIG(this);
+			b.shr(i*4);
+			s+=Long.toHexString(b.w[0]&15);
+		}
+		return s;
+	}
+
+/* set this[i]+=x*y+c, and return high part */
+
+	public static long[] muladd(long a,long b,long c,long r)
+	{
+		long x0,x1,y0,y1;
+		long[] tb=new long[2];
+		x0=a&HMASK;
+		x1=(a>>HBITS);
+		y0=b&HMASK;
+		y1=(b>>HBITS);
+		long bot=x0*y0;
+		long top=x1*y1;
+		long mid=x0*y1+x1*y0;
+		x0=mid&HMASK;
+		x1=(mid>>HBITS);
+		bot+=x0<<HBITS; bot+=c; bot+=r;
+		top+=x1;
+		long carry=bot>>BASEBITS;
+		bot&=BMASK;
+		top+=carry;
+		tb[0]=top;
+		tb[1]=bot;
+		return tb;
+	}
+
+/* this*=x, where x is >NEXCESS */
+	public long pmul(int c)
+	{
+		long ak,carry=0;
+		long[] cr=new long[2];
+
+		for (int i=0;i<NLEN;i++)
+		{
+			ak=w[i];
+			w[i]=0;
+
+			cr=muladd(ak,(long)c,carry,w[i]);
+			carry=cr[0];
+			w[i]=cr[1];
+
+		}
+		return carry;
+	}
+
+/* return this*c and catch overflow in DBIG */
+	public DBIG pxmul(int c)
+	{
+		DBIG m=new DBIG(0);	
+		long[] cr=new long[2];
+		long carry=0;
+		for (int j=0;j<NLEN;j++)
+		{
+			cr=muladd(w[j],(long)c,carry,m.w[j]);
+			carry=cr[0];
+			m.w[j]=cr[1];
+		}
+		m.w[NLEN]=carry;		
+		return m;
+	}
+
+/* divide by 3 */
+	public int div3()
+	{	
+		long ak,base,carry=0;
+		norm();
+		base=((long)1<<BASEBITS);
+		for (int i=NLEN-1;i>=0;i--)
+		{
+			ak=(carry*base+w[i]);
+			w[i]=ak/3;
+			carry=ak%3;
+		}
+		return (int)carry;
+	}
+
+/* return a*b where result fits in a BIG */
+	public static BIG smul(BIG a,BIG b)
+	{
+		long carry;
+		long[] cr=new long[2];
+		BIG c=new BIG(0);
+		for (int i=0;i<NLEN;i++)
+		{
+			carry=0;
+			for (int j=0;j<NLEN;j++)
+				if (i+j<NLEN)
+				{
+					cr=muladd(a.w[i],b.w[j],carry,c.w[i+j]);
+					carry=cr[0];
+					c.w[i+j]=cr[1];
+				}
+		}
+		return c;
+	}
+
+/* return a*b as DBIG */
+/* Inputs must be normed */
+	public static DBIG mul(BIG a,BIG b)
+	{
+		DBIG c=new DBIG(0);
+		long carry;
+		long[] cr=new long[2];
+
+		for (int i=0;i<NLEN;i++)
+		{
+			carry=0;
+			for (int j=0;j<NLEN;j++)
+			{
+				cr=muladd(a.w[i],b.w[j],carry,c.w[i+j]);
+				carry=cr[0];
+				c.w[i+j]=cr[1];
+			}
+			c.w[NLEN+i]=carry;
+		}
+
+		return c;
+	}
+
+/* return a^2 as DBIG */
+/* Input must be normed */
+	public static DBIG sqr(BIG a)
+	{
+		DBIG c=new DBIG(0);
+		long carry;
+		long[] cr=new long[2];
+
+		for (int i=0;i<NLEN;i++)
+		{
+			carry=0;
+			for (int j=i+1;j<NLEN;j++)
+			{
+				cr=muladd(2*a.w[i],a.w[j],carry,c.w[i+j]);
+				carry=cr[0];
+				c.w[i+j]=cr[1];
+			}
+			c.w[NLEN+i]=carry;
+		}
+
+		for (int i=0;i<NLEN;i++)
+		{
+			cr=muladd(a.w[i],a.w[i],0,c.w[2*i]);
+			c.w[2*i+1]+=cr[0];
+			c.w[2*i]=cr[1];
+		}
+		c.norm(); 
+		return c;
+	}
+
+	static BIG monty(BIG md,long MC,DBIG d)
+	{
+		BIG b;
+		long m,carry;
+		long[] cr=new long[2];
+		for (int i=0;i<NLEN;i++) 
+		{
+			if (MC==-1) m=(-d.w[i])&BMASK;
+			else
+			{
+				if (MC==1) m=d.w[i];
+				else m=(MC*d.w[i])&BMASK;
+			}
+
+			carry=0;
+			for (int j=0;j<NLEN;j++)
+			{
+				cr=muladd(m,md.w[j],carry,d.w[i+j]);
+				carry=cr[0];
+				d.w[i+j]=cr[1];
+			}
+			d.w[NLEN+i]+=carry;
+		}
+
+		b=new BIG(0);
+		for (int i=0;i<NLEN;i++ )
+			b.w[i]=d.w[NLEN+i];
+		b.norm();
+		return b;		
+	}
+
+
+
+/****************************************************************************/
+
+	public void xortop(long x)
+	{
+		w[NLEN-1]^=x;
+	}
+
+/* set x = x mod 2^m */
+	public void mod2m(int m)
+	{
+		int i,wd,bt;
+		wd=m/BASEBITS;
+		bt=m%BASEBITS;
+		w[wd]&=((cast_to_chunk(1)<<bt)-1);
+		for (i=wd+1;i<NLEN;i++) w[i]=0;
+	}
+
+/* return n-th bit */
+	public int bit(int n)
+	{
+		if ((w[n/BASEBITS]&(cast_to_chunk(1)<<(n%BASEBITS)))>0) return 1;
+		else return 0;
+	}
+
+/* Shift right by less than a word */
+	public int fshr(int k) {
+		int r=(int)(w[0]&((cast_to_chunk(1)<<k)-1)); /* shifted out part */
+		for (int i=0;i<NLEN-1;i++)
+			w[i]=(w[i]>>k)|((w[i+1]<<(BASEBITS-k))&BMASK);
+		w[NLEN-1]=w[NLEN-1]>>k;
+		return r;
+	}
+
+/* Shift right by less than a word */
+	public int fshl(int k) {
+		w[NLEN-1]=((w[NLEN-1]<<k))|(w[NLEN-2]>>(BASEBITS-k));
+		for (int i=NLEN-2;i>0;i--)
+			w[i]=((w[i]<<k)&BMASK)|(w[i-1]>>(BASEBITS-k));
+		w[0]=(w[0]<<k)&BMASK; 
+		return (int)(w[NLEN-1]>>((8*MODBYTES)%BASEBITS)); /* return excess - only used in FF.java */
+	}
+
+/* test for zero */
+	public boolean iszilch() {
+		for (int i=0;i<NLEN;i++)
+			if (w[i]!=0) return false;
+		return true; 
+	}
+
+/* set to zero */
+	public void zero()
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=0;
+	}
+
+/* set to one */
+	public void one()
+	{
+		w[0]=1;
+		for (int i=1;i<NLEN;i++)
+			w[i]=0;
+	}
+
+/* Test for equal to one */
+	public boolean isunity()
+	{
+		for (int i=1;i<NLEN;i++)
+			if (w[i]!=0) return false;
+		if (w[0]!=1) return false;
+		return true;
+	}
+
+/* Copy from another BIG */
+	public void copy(BIG x)
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i];
+	}
+
+	public void copy(DBIG x)
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i];
+	}
+
+/* general shift right */
+	public void shr(int k) {
+		int n=k%BASEBITS;
+		int m=k/BASEBITS;	
+		for (int i=0;i<NLEN-m-1;i++)
+			w[i]=(w[m+i]>>n)|((w[m+i+1]<<(BASEBITS-n))&BMASK);
+		if (NLEN>m) w[NLEN-m-1]=w[NLEN-1]>>n;
+		for (int i=NLEN-m;i<NLEN;i++) w[i]=0;
+	}
+
+/* general shift left */
+	public void shl(int k) {
+		int n=k%BASEBITS;
+		int m=k/BASEBITS;
+
+		w[NLEN-1]=((w[NLEN-1-m]<<n));
+		if (NLEN>=m+2) w[NLEN-1]|=(w[NLEN-m-2]>>(BASEBITS-n));
+
+		for (int i=NLEN-2;i>m;i--)
+			w[i]=((w[i-m]<<n)&BMASK)|(w[i-m-1]>>(BASEBITS-n));
+		w[m]=(w[0]<<n)&BMASK;
+		for (int i=0;i<m;i++) w[i]=0;
+	}
+
+/* return this+x */
+	public BIG plus(BIG x) {
+		BIG s=new BIG(0);
+		for (int i=0;i<NLEN;i++)
+			s.w[i]=w[i]+x.w[i];
+		return s;
+	}
+
+/* this+=x */
+	public void add(BIG x) {
+		for (int i=0;i<NLEN;i++)
+			w[i]+=x.w[i];
+	}
+
+/* this|=x */
+	public void or(BIG x) {
+		for (int i=0;i<NLEN;i++)
+			w[i]|=x.w[i];
+	}
+
+
+/* this+=x, where x is int */
+	public void inc(int x) {
+		norm();
+		w[0]+=x;
+	}
+
+/* this+=x, where x is long */
+	public void incl(long x) {
+		norm();
+		w[0]+=x;
+	}	
+
+/* return this.x */
+	public BIG minus(BIG x) {
+		BIG d=new BIG(0);
+		for (int i=0;i<NLEN;i++)
+			d.w[i]=w[i]-x.w[i];
+		return d;
+	}
+
+/* this-=x */
+	public void sub(BIG x) {
+		for (int i=0;i<NLEN;i++)
+			w[i]-=x.w[i];
+	}
+
+/* reverse subtract this=x-this */
+	public void rsub(BIG x) {
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i]-w[i];
+	}
+
+/* this-=x where x is int */
+	public void dec(int x) {
+		norm();
+		w[0]-=x;
+	}
+
+/* this*=x, where x is small int<NEXCESS */
+	public void imul(int c)
+	{
+		for (int i=0;i<NLEN;i++) w[i]*=c;
+	}
+
+/* convert this BIG to byte array */
+	public void tobytearray(byte[] b,int n)
+	{
+		
+		BIG c=new BIG(this);
+		c.norm();
+
+		for (int i=MODBYTES-1;i>=0;i--)
+		{
+			b[i+n]=(byte)c.w[0];
+			c.fshr(8);
+		}
+	}
+
+/* convert from byte array to BIG */
+	public static BIG frombytearray(byte[] b,int n)
+	{
+		BIG m=new BIG(0);
+
+		for (int i=0;i<MODBYTES;i++)
+		{
+			m.fshl(8); m.w[0]+=(int)b[i+n]&0xff;
+			//m.inc((int)b[i]&0xff);
+		}
+		return m; 
+	}
+
+	public void toBytes(byte[] b)
+	{
+		tobytearray(b,0);
+	}
+
+	public static BIG fromBytes(byte[] b)
+	{
+		return frombytearray(b,0);
+	}
+
+/* Compare a and b, return 0 if a==b, -1 if a<b, +1 if a>b. Inputs must be normalised */
+	public static int comp(BIG a,BIG b)
+	{
+		for (int i=NLEN-1;i>=0;i--)
+		{
+			if (a.w[i]==b.w[i]) continue;
+			if (a.w[i]>b.w[i]) return 1;
+			else  return -1;
+		}
+		return 0;
+	}
+
+/* Arazi and Qi inversion mod 256 */
+	public static int invmod256(int a)
+	{
+		int U,t1,t2,b,c;
+		t1=0;
+		c=(a>>1)&1;  
+		t1+=c;
+		t1&=1;
+		t1=2-t1;
+		t1<<=1;
+		U=t1+1;
+
+// i=2
+		b=a&3;
+		t1=U*b; t1>>=2;
+		c=(a>>2)&3;
+		t2=(U*c)&3;
+		t1+=t2;
+		t1*=U; t1&=3;
+		t1=4-t1;
+		t1<<=2;
+		U+=t1;
+
+// i=4
+		b=a&15;
+		t1=U*b; t1>>=4;
+		c=(a>>4)&15;
+		t2=(U*c)&15;
+		t1+=t2;
+		t1*=U; t1&=15;
+		t1=16-t1;
+		t1<<=4;
+		U+=t1;
+
+		return U;
+	}
+
+/* a=1/a mod 2^256. This is very fast! */
+	public void invmod2m()
+	{
+		int i;
+		BIG U=new BIG(0);
+		BIG b=new BIG(0);
+		BIG c=new BIG(0);
+
+		U.inc(invmod256(lastbits(8)));
+
+		for (i=8;i<BIGBITS;i<<=1)
+		{
+			U.norm();
+			b.copy(this); b.mod2m(i);
+			BIG t1=BIG.smul(U,b); 
+			t1.shr(i);
+
+			c.copy(this); c.shr(i); c.mod2m(i);
+			BIG t2=BIG.smul(U,c); t2.mod2m(i);
+
+			t1.add(t2);
+			t1.norm();
+			b=BIG.smul(t1,U); t1.copy(b);
+			t1.mod2m(i);
+
+			t2.one(); t2.shl(i); t1.rsub(t2); t1.norm();
+
+			t1.shl(i);
+			U.add(t1);
+		}
+		U.mod2m(BIGBITS);
+		copy(U);
+		norm();
+	}
+
+/* reduce this mod m */
+	public void mod(BIG m1)
+	{
+		int k=0;  
+		BIG r=new BIG(0);
+		BIG m=new BIG(m1);
+
+		norm();
+		if (comp(this,m)<0) return;
+		do
+		{
+			m.fshl(1);
+			k++;
+		} while (comp(this,m)>=0);
+
+		while (k>0)
+		{
+			m.fshr(1);
+
+			r.copy(this);
+			r.sub(m);
+			r.norm();
+			cmove(r,(int)(1-((r.w[NLEN-1]>>(CHUNK-1))&1)));
+			k--;
+		}
+	}
+
+/* divide this by m */
+	public void div(BIG m1)
+	{
+		int d,k=0;
+		norm();
+		BIG e=new BIG(1);
+		BIG m=new BIG(m1);
+		BIG b=new BIG(this);
+		BIG r=new BIG(0);
+		zero();
+
+		while (comp(b,m)>=0)
+		{
+			e.fshl(1);
+			m.fshl(1);
+			k++;
+		}
+
+		while (k>0)
+		{
+			m.fshr(1);
+			e.fshr(1);
+
+			r.copy(b);
+			r.sub(m);
+			r.norm();
+			d=(int)(1-((r.w[NLEN-1]>>(CHUNK-1))&1));
+			b.cmove(r,d);
+			r.copy(this);
+			r.add(e);
+			r.norm();
+			cmove(r,d);
+			k--;
+		}
+	}
+
+/* return parity */
+	public int parity()
+	{
+		return (int)(w[0]%2);
+	}
+
+/* return n last bits */
+	public int lastbits(int n)
+	{
+		int msk=(1<<n)-1;
+		norm();
+		return ((int)w[0])&msk;
+	}
+
+/* get 8*MODBYTES size random number */
+	public static BIG random(RAND rng)
+	{
+		BIG m=new BIG(0);
+		int i,b,j=0,r=0;
+
+/* generate random BIG */ 
+		for (i=0;i<8*MODBYTES;i++)   
+		{
+			if (j==0) r=rng.getByte();
+			else r>>=1;
+
+			b=r&1;
+			m.shl(1); m.w[0]+=b;// m.inc(b);
+			j++; j&=7; 
+		}
+		return m;
+	}
+
+/* Create random BIG in portable way, one bit at a time */
+	public static BIG randomnum(BIG q,RAND rng) 
+	{
+		DBIG d=new DBIG(0);
+		int i,b,j=0,r=0;
+		for (i=0;i<2*q.nbits();i++)
+		{
+			if (j==0) r=rng.getByte();
+			else r>>=1;
+
+			b=r&1;
+			d.shl(1); d.w[0]+=b;// m.inc(b);
+			j++; j&=7; 
+		}
+		BIG m=d.mod(q);
+		return m;
+	}
+
+/* return a*b mod m */
+	public static BIG modmul(BIG a1,BIG b1,BIG m)
+	{
+		BIG a=new BIG(a1);
+		BIG b=new BIG(b1);
+		a.mod(m);
+		b.mod(m);
+		DBIG d=mul(a,b);
+		return d.mod(m);
+	}
+
+/* return a^2 mod m */
+	public static BIG modsqr(BIG a1,BIG m)
+	{
+		BIG a=new BIG(a1);
+		a.mod(m);
+		DBIG d=sqr(a);
+		return d.mod(m);
+	}
+
+/* return -a mod m */
+	public static BIG modneg(BIG a1,BIG m)
+	{
+		BIG a=new BIG(a1);
+		a.mod(m);
+		return m.minus(a);
+	}
+
+/* return this^e mod m */
+	public BIG powmod(BIG e1,BIG m)
+	{
+		BIG e=new BIG(e1);
+		int bt;
+		norm();
+		e.norm();
+		BIG a=new BIG(1);
+		BIG z=new BIG(e);
+		BIG s=new BIG(this);
+		while (true)
+		{
+			bt=z.parity();
+			z.fshr(1);
+			if (bt==1) a=modmul(a,s,m);
+			if (z.iszilch()) break;
+			s=modsqr(s,m);
+		}
+		return a;
+	}
+
+/* Jacobi Symbol (this/p). Returns 0, 1 or -1 */
+	public int jacobi(BIG p)
+	{
+		int n8,k,m=0;
+		BIG t=new BIG(0);
+		BIG x=new BIG(0);
+		BIG n=new BIG(0);
+		BIG zilch=new BIG(0);
+		BIG one=new BIG(1);
+		if (p.parity()==0 || comp(this,zilch)==0 || comp(p,one)<=0) return 0;
+		norm();
+		x.copy(this);
+		n.copy(p);
+		x.mod(p);
+
+		while (comp(n,one)>0)
+		{
+			if (comp(x,zilch)==0) return 0;
+			n8=n.lastbits(3);
+			k=0;
+			while (x.parity()==0)
+			{
+				k++;
+				x.shr(1);
+			}
+			if (k%2==1) m+=(n8*n8-1)/8;
+			m+=(n8-1)*(x.lastbits(2)-1)/4;
+			t.copy(n);
+			t.mod(x);
+			n.copy(x);
+			x.copy(t);
+			m%=2;
+
+		}
+		if (m==0) return 1;
+		else return -1;
+	}
+
+/* this=1/this mod p. Binary method */
+	public void invmodp(BIG p)
+	{
+		mod(p);
+		BIG u=new BIG(this);
+		BIG v=new BIG(p);
+		BIG x1=new BIG(1);
+		BIG x2=new BIG(0);
+		BIG t=new BIG(0);
+		BIG one=new BIG(1);
+
+		while (comp(u,one)!=0 && comp(v,one)!=0)
+		{
+			while (u.parity()==0)
+			{
+				u.fshr(1);
+				if (x1.parity()!=0)
+				{
+					x1.add(p);
+					x1.norm();
+				}
+				x1.fshr(1);
+			}
+			while (v.parity()==0)
+			{
+				v.fshr(1);
+				if (x2.parity()!=0)
+				{
+					x2.add(p);
+					x2.norm();
+				}
+				x2.fshr(1);
+			}
+			if (comp(u,v)>=0)
+			{
+				u.sub(v);
+				u.norm();
+				if (comp(x1,x2)>=0) x1.sub(x2);
+				else
+				{
+					t.copy(p);
+					t.sub(x2);
+					x1.add(t);
+				}
+				x1.norm();
+			}
+			else
+			{
+				v.sub(u);
+				v.norm();
+				if (comp(x2,x1)>=0) x2.sub(x1);
+				else
+				{
+					t.copy(p);
+					t.sub(x1);
+					x2.add(t);
+				}
+				x2.norm();
+			}
+		}
+		if (comp(u,one)==0) copy(x1);
+		else copy(x2);
+	}
+}
diff --git a/src/main/java/org/apache/milagro/amcl/ANSSI/DBIG.java b/src/main/java/org/apache/milagro/amcl/ANSSI/DBIG.java
new file mode 100644
index 0000000..4ca545a
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/ANSSI/DBIG.java
@@ -0,0 +1,279 @@
+/*
+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.
+*/
+
+/* AMCL double length DBIG number class */ 
+
+package org.apache.milagro.amcl.ANSSI;
+
+public class DBIG {
+	protected long[] w=new long[BIG.DNLEN];
+
+/* normalise this */
+	public void norm() {
+		long d,carry=0;
+		for (int i=0;i<BIG.DNLEN-1;i++)
+		{
+			d=w[i]+carry;
+			carry=d>>BIG.BASEBITS;
+			w[i]=d&BIG.BMASK;
+		}
+		w[BIG.DNLEN-1]=(w[BIG.DNLEN-1]+carry);
+	}
+
+
+/*
+	public String toRawString()
+	{
+		DBIG b=new DBIG(this);
+		String s="(";
+		for (int i=0;i<BIG.DNLEN-1;i++)
+		{
+			s+=Long.toHexString(b.w[i]); s+=",";
+		}
+		s+=Long.toHexString(b.w[BIG.DNLEN-1]); s+=")";
+		return s;
+	}
+*/
+
+/* split DBIG at position n, return higher half, keep lower half */
+	public BIG split(int n)
+	{
+		BIG t=new BIG(0);
+		int m=n%BIG.BASEBITS;
+		long nw,carry=w[BIG.DNLEN-1]<<(BIG.BASEBITS-m);
+
+		for (int i=BIG.DNLEN-2;i>=BIG.NLEN-1;i--)
+		{
+			nw=(w[i]>>m)|carry;
+			carry=(w[i]<<(BIG.BASEBITS-m))&BIG.BMASK;
+			t.w[i-BIG.NLEN+1]=nw;
+			//t.set(i-BIG.NLEN+1,nw);
+		}
+		w[BIG.NLEN-1]&=(((long)1<<m)-1);
+		return t;
+	}
+
+/****************************************************************************/
+
+/* return number of bits in this */
+	public int nbits() {
+		int bts,k=BIG.DNLEN-1;
+		long c;
+		norm();
+		while (w[k]==0 && k>=0) k--;
+		if (k<0) return 0;
+		bts=BIG.BASEBITS*k;
+		c=w[k];
+		while (c!=0) {c/=2; bts++;}
+		return bts;
+	}
+
+/* convert this to string */
+	public String toString() {
+		DBIG b;
+		String s="";
+		int len=nbits();
+		if (len%4==0) len>>=2; //len/=4;
+		else {len>>=2; len++;}
+
+		for (int i=len-1;i>=0;i--)
+		{
+			b=new DBIG(this);
+			b.shr(i*4);
+			s+=Integer.toHexString((int)(b.w[0]&15));
+		}
+		return s;
+	}
+
+	public void cmove(DBIG g,int d)
+	{
+		int i;
+		for (i=0;i<BIG.DNLEN;i++)
+		{
+			w[i]^=(w[i]^g.w[i])&BIG.cast_to_chunk(-d);
+		}
+	}
+
+/* Constructors */
+	public DBIG(int x)
+	{
+		w[0]=x;
+		for (int i=1;i<BIG.DNLEN;i++)
+			w[i]=0;
+	}
+
+	public DBIG(DBIG x)
+	{
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]=x.w[i];
+	}
+
+	public DBIG(BIG x)
+	{
+		for (int i=0;i<BIG.NLEN-1;i++)
+			w[i]=x.w[i]; //get(i);
+
+		w[BIG.NLEN-1]=x.w[(BIG.NLEN-1)]&BIG.BMASK; /* top word normalized */
+		w[BIG.NLEN]=(x.w[(BIG.NLEN-1)]>>BIG.BASEBITS);
+
+		for (int i=BIG.NLEN+1;i<BIG.DNLEN;i++) w[i]=0;
+	}
+
+/* Copy from another DBIG */
+	public void copy(DBIG x)
+	{
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]=x.w[i];
+	}
+
+/* Copy into upper part */
+	public void ucopy(BIG x)
+	{
+		for (int i=0;i<BIG.NLEN;i++)
+			w[i]=0;
+		for (int i=BIG.NLEN;i<BIG.DNLEN;i++)
+			w[i]=x.w[i-BIG.NLEN];
+	}
+
+/* test this=0? */
+	public boolean iszilch() {
+		for (int i=0;i<BIG.DNLEN;i++)
+			if (w[i]!=0) return false;
+		return true; 
+	}
+
+/* shift this right by k bits */
+	public void shr(int k) {
+		int n=k%BIG.BASEBITS;
+		int m=k/BIG.BASEBITS;	
+		for (int i=0;i<BIG.DNLEN-m-1;i++)
+			w[i]=(w[m+i]>>n)|((w[m+i+1]<<(BIG.BASEBITS-n))&BIG.BMASK);
+		w[BIG.DNLEN-m-1]=w[BIG.DNLEN-1]>>n;
+		for (int i=BIG.DNLEN-m;i<BIG.DNLEN;i++) w[i]=0;
+	}
+
+/* shift this left by k bits */
+	public void shl(int k) {
+		int n=k%BIG.BASEBITS;
+		int m=k/BIG.BASEBITS;
+
+		w[BIG.DNLEN-1]=((w[BIG.DNLEN-1-m]<<n))|(w[BIG.DNLEN-m-2]>>(BIG.BASEBITS-n));
+		for (int i=BIG.DNLEN-2;i>m;i--)
+			w[i]=((w[i-m]<<n)&BIG.BMASK)|(w[i-m-1]>>(BIG.BASEBITS-n));
+		w[m]=(w[0]<<n)&BIG.BMASK; 
+		for (int i=0;i<m;i++) w[i]=0;
+	}
+
+/* this+=x */
+	public void add(DBIG x) {
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]+=x.w[i];	
+	}
+
+/* this-=x */
+	public void sub(DBIG x) {
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]-=x.w[i];
+	}
+
+	public void rsub(DBIG x) {
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]=x.w[i]-w[i];
+	}
+
+/* Compare a and b, return 0 if a==b, -1 if a<b, +1 if a>b. Inputs must be normalised */
+	public static int comp(DBIG a,DBIG b)
+	{
+		for (int i=BIG.DNLEN-1;i>=0;i--)
+		{
+			if (a.w[i]==b.w[i]) continue;
+			if (a.w[i]>b.w[i]) return 1;
+			else  return -1;
+		}
+		return 0;
+	}
+
+/* reduces this DBIG mod a BIG, and returns the BIG */
+	public BIG mod(BIG c)
+	{
+		int k=0;  
+		norm();
+		DBIG m=new DBIG(c);
+		DBIG r=new DBIG(0);
+
+		if (comp(this,m)<0) return new BIG(this);
+		
+		do
+		{
+			m.shl(1);
+			k++;
+		}
+		while (comp(this,m)>=0);
+
+		while (k>0)
+		{
+			m.shr(1);
+
+			r.copy(this);
+			r.sub(m);
+			r.norm();
+			cmove(r,(int)(1-((r.w[BIG.DNLEN-1]>>(BIG.CHUNK-1))&1)));
+
+			k--;
+		}
+		return new BIG(this);
+	}
+
+/* return this/c */
+	public BIG div(BIG c)
+	{
+		int d,k=0;
+		DBIG m=new DBIG(c);
+		DBIG dr=new DBIG(0);
+		BIG r=new BIG(0);
+		BIG a=new BIG(0);
+		BIG e=new BIG(1);
+		norm();
+
+		while (comp(this,m)>=0)
+		{
+			e.fshl(1);
+			m.shl(1);
+			k++;
+		}
+
+		while (k>0)
+		{
+			m.shr(1);
+			e.shr(1);
+
+			dr.copy(this);
+			dr.sub(m);
+			dr.norm();
+			d=(int)(1-((dr.w[BIG.DNLEN-1]>>(BIG.CHUNK-1))&1));
+			cmove(dr,d);
+			r.copy(a);
+			r.add(e);
+			r.norm();
+			a.cmove(r,d);
+			k--;
+		}
+		return a;
+	}
+}
diff --git a/src/main/java/org/apache/milagro/amcl/ANSSI/ECDH.java b/src/main/java/org/apache/milagro/amcl/ANSSI/ECDH.java
new file mode 100644
index 0000000..ec1311c
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/ANSSI/ECDH.java
@@ -0,0 +1,594 @@
+/*
+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.
+*/
+
+/* Elliptic Curve API high-level functions  */
+
+package org.apache.milagro.amcl.ANSSI;
+
+import org.apache.milagro.amcl.RAND;
+import org.apache.milagro.amcl.HASH256;
+import org.apache.milagro.amcl.HASH384;
+import org.apache.milagro.amcl.HASH512;
+import org.apache.milagro.amcl.AES;
+
+public final class ECDH {
+	public static final int INVALID_PUBLIC_KEY=-2;
+	public static final int ERROR=-3;
+	public static final int INVALID=-4;
+	public static final int EFS=BIG.MODBYTES;
+	public static final int EGS=BIG.MODBYTES;
+//	public static final int EAS=16;
+//	public static final int EBS=16;
+
+//	public static final int SHA256=32;
+//	public static final int SHA384=48;
+//	public static final int SHA512=64;
+
+
+//	public static final int HASH_TYPE=SHA512;
+
+
+/* Convert Integer to n-byte array */
+	public static byte[] inttoBytes(int n,int len)
+	{
+		int i;
+		byte[] b=new byte[len];
+
+		for (i=0;i<len;i++) b[i]=0;
+		i=len; 
+		while (n>0 && i>0)
+		{
+			i--;
+			b[i]=(byte)(n&0xff);
+			n/=256;
+		}	
+		return b;
+	}
+
+	public static byte[] hashit(int sha,byte[] A,int n,byte[] B,int pad)
+	{
+		byte[] R=null;
+
+		if (sha==ECP.SHA256)
+		{
+			HASH256 H=new HASH256();
+			H.process_array(A); if (n>0) H.process_num(n);
+			if (B!=null) H.process_array(B);
+			R=H.hash();
+		}
+		if (sha==ECP.SHA384)
+		{
+			HASH384 H=new HASH384();
+			H.process_array(A); if (n>0) H.process_num(n);
+			if (B!=null) H.process_array(B);
+			R=H.hash();
+		}
+		if (sha==ECP.SHA512)
+		{
+			HASH512 H=new HASH512();
+			H.process_array(A); if (n>0) H.process_num(n);
+			if (B!=null) H.process_array(B);
+			R=H.hash();
+		}
+		if (R==null) return null;
+
+		if (pad==0) return R;
+/* If pad>0 output is truncated or padded to pad bytes */
+		byte[] W=new byte[pad];
+		if (pad<=sha) 
+		{
+			for (int i=0;i<pad;i++) W[i]=R[i];
+		}
+		else
+		{
+			for (int i=0;i<sha;i++) W[i+pad-sha]=R[i];
+            for (int i=0;i<pad-sha;i++) W[i]=0;
+ 
+			//for (int i=0;i<sha;i++) W[i]=R[i];
+			//for (int i=sha;i<pad;i++) W[i]=0;
+		}
+		return W;
+	}
+
+/* Key Derivation Functions */
+/* Input octet Z */
+/* Output key of length olen */
+	public static byte[] KDF1(int sha,byte[] Z,int olen)
+	{
+/* NOTE: the parameter olen is the length of the output K in bytes */
+		int hlen=sha;
+		byte[] K=new byte[olen];
+		byte[] B;
+		int counter,cthreshold,k=0;
+    
+		for (int i=0;i<K.length;i++) K[i]=0;
+
+		cthreshold=olen/hlen; if (olen%hlen!=0) cthreshold++;
+
+		for (counter=0;counter<cthreshold;counter++)
+		{
+			B=hashit(sha,Z,counter,null,0);
+			if (k+hlen>olen) for (int i=0;i<olen%hlen;i++) K[k++]=B[i];
+			else for (int i=0;i<hlen;i++) K[k++]=B[i];
+		}
+		return K;
+	}
+
+	public static byte[] KDF2(int sha,byte[] Z,byte[] P,int olen)
+	{
+/* NOTE: the parameter olen is the length of the output k in bytes */
+		int hlen=sha;
+		byte[] K=new byte[olen];
+		byte[] B;
+		int counter,cthreshold,k=0;
+    
+		for (int i=0;i<K.length;i++) K[i]=0;
+
+		cthreshold=olen/hlen; if (olen%hlen!=0) cthreshold++;
+
+		for (counter=1;counter<=cthreshold;counter++)
+		{
+			B=hashit(sha,Z,counter,P,0);
+			if (k+hlen>olen) for (int i=0;i<olen%hlen;i++) K[k++]=B[i];
+			else for (int i=0;i<hlen;i++) K[k++]=B[i];
+		}
+
+		return K;
+	}
+
+/* Password based Key Derivation Function */
+/* Input password p, salt s, and repeat count */
+/* Output key of length olen */
+	public static byte[] PBKDF2(int sha,byte[] Pass,byte[] Salt,int rep,int olen)
+	{
+		int i,j,k,len,d,opt;
+		d=olen/sha; if (olen%sha!=0) d++;
+		byte[] F=new byte[sha];
+		byte[] U=new byte[sha];
+		byte[] S=new byte[Salt.length+4];
+
+		byte[] K=new byte[d*sha];
+		opt=0;
+
+		for (i=1;i<=d;i++)
+		{
+			for (j=0;j<Salt.length;j++) S[j]=Salt[j];
+			byte[] N=inttoBytes(i,4);
+			for (j=0;j<4;j++) S[Salt.length+j]=N[j];
+
+			HMAC(sha,S,Pass,F);
+
+			for (j=0;j<sha;j++) U[j]=F[j];
+			for (j=2;j<=rep;j++)
+			{
+				HMAC(sha,U,Pass,U);
+				for (k=0;k<sha;k++) F[k]^=U[k];
+			}
+			for (j=0;j<sha;j++) K[opt++]=F[j];
+		}
+		byte[] key=new byte[olen];
+		for (i=0;i<olen;i++) key[i]=K[i];
+		return key;
+	}
+
+/* Calculate HMAC of m using key k. HMAC is tag of length olen */
+	public static int HMAC(int sha,byte[] M,byte[] K,byte[] tag)
+	{
+	/* Input is from an octet m        *
+	* olen is requested output length in bytes. k is the key  *
+	* The output is the calculated tag */
+		int b=64;
+		if (sha>32) b=128;
+		byte[] B;
+		byte[] K0=new byte[b];
+		int olen=tag.length;
+
+		//b=K0.length;
+		if (olen<4 /*|| olen>sha*/) return 0;
+
+		for (int i=0;i<b;i++) K0[i]=0;
+
+		if (K.length > b) 
+		{
+			B=hashit(sha,K,0,null,0);
+			for (int i=0;i<sha;i++) K0[i]=B[i];
+		}
+		else
+			for (int i=0;i<K.length;i++ ) K0[i]=K[i];
+		
+		for (int i=0;i<b;i++) K0[i]^=0x36;
+		B=hashit(sha,K0,0,M,0);
+
+		for (int i=0;i<b;i++) K0[i]^=0x6a;
+		B=hashit(sha,K0,0,B,olen);
+
+		for (int i=0;i<olen;i++) tag[i]=B[i];
+
+		return 1;
+	}
+
+/* AES encryption/decryption. Encrypt byte array M using key K and returns ciphertext */
+	public static byte[] AES_CBC_IV0_ENCRYPT(byte[] K,byte[] M)
+	{ /* AES CBC encryption, with Null IV and key K */
+	/* Input is from an octet string M, output is to an octet string C */
+	/* Input is padded as necessary to make up a full final block */
+		AES a=new AES();
+		boolean fin;
+		int i,j,ipt,opt;
+		byte[] buff=new byte[16];
+		int clen=16+(M.length/16)*16;
+
+		byte[] C=new byte[clen];
+		int padlen;
+
+		a.init(AES.CBC,K.length,K,null);
+
+		ipt=opt=0;
+		fin=false;
+		for(;;)
+		{
+			for (i=0;i<16;i++)
+			{
+				if (ipt<M.length) buff[i]=M[ipt++];
+				else {fin=true; break;}
+			}
+			if (fin) break;
+			a.encrypt(buff);
+			for (i=0;i<16;i++)
+				C[opt++]=buff[i];
+		}    
+
+/* last block, filled up to i-th index */
+
+		padlen=16-i;
+		for (j=i;j<16;j++) buff[j]=(byte)padlen;
+
+		a.encrypt(buff);
+
+		for (i=0;i<16;i++)
+			C[opt++]=buff[i];
+		a.end();    
+		return C;
+	}
+
+/* returns plaintext if all consistent, else returns null string */
+	public static byte[] AES_CBC_IV0_DECRYPT(byte[] K,byte[] C)
+	{ /* padding is removed */
+		AES a=new AES();
+		int i,ipt,opt,ch;
+		byte[] buff=new byte[16];
+		byte[] MM=new byte[C.length];
+		boolean fin,bad;
+		int padlen;
+		ipt=opt=0;
+
+		a.init(AES.CBC,K.length,K,null);
+
+		if (C.length==0) return new byte[0];
+		ch=C[ipt++]; 
+  
+		fin=false;
+
+		for(;;)
+		{
+			for (i=0;i<16;i++)
+			{
+				buff[i]=(byte)ch;      
+				if (ipt>=C.length) {fin=true; break;}  
+				else ch=C[ipt++];  
+			}
+			a.decrypt(buff);
+			if (fin) break;
+			for (i=0;i<16;i++)
+				MM[opt++]=buff[i];
+		}    
+
+		a.end();
+		bad=false;
+		padlen=buff[15];
+		if (i!=15 || padlen<1 || padlen>16) bad=true;
+		if (padlen>=2 && padlen<=16)
+			for (i=16-padlen;i<16;i++) if (buff[i]!=padlen) bad=true;
+    
+		if (!bad) for (i=0;i<16-padlen;i++)
+					MM[opt++]=buff[i];
+
+		if (bad) return new byte[0];
+
+		byte[] M=new byte[opt];
+		for (i=0;i<opt;i++) M[i]=MM[i];
+
+		return M;
+	}
+
+/* Calculate a public/private EC GF(p) key pair W,S where W=S.G mod EC(p),
+ * where S is the secret key and W is the public key
+ * and G is fixed generator.
+ * If RNG is NULL then the private key is provided externally in S
+ * otherwise it is generated randomly internally */
+	public static int KEY_PAIR_GENERATE(RAND RNG,byte[] S,byte[] W)
+	{
+		BIG r,s;
+		ECP G,WP;
+		int res=0;
+	//	byte[] T=new byte[EFS];
+
+		G=ECP.generator();
+
+		r=new BIG(ROM.CURVE_Order);
+
+		if (RNG==null)
+		{
+			s=BIG.fromBytes(S);
+			s.mod(r);
+		}
+		else
+		{
+			s=BIG.randomnum(r,RNG);
+		}
+
+		//if (ROM.AES_S>0)
+		//{
+		//	s.mod2m(2*ROM.AES_S);
+		//}
+		s.toBytes(S);
+
+		WP=G.mul(s);
+		WP.toBytes(W,false);  // To use point compression on public keys, change to true 
+
+		return res;
+	}
+
+/* validate public key. */
+	public static int PUBLIC_KEY_VALIDATE(byte[] W)
+	{
+		BIG r,q,k;
+		ECP WP=ECP.fromBytes(W);
+		int nb,res=0;
+
+		r=new BIG(ROM.CURVE_Order);
+
+		if (WP.is_infinity()) res=INVALID_PUBLIC_KEY;
+
+		if (res==0)
+		{
+
+			q=new BIG(ROM.Modulus);
+			nb=q.nbits();
+			k=new BIG(1); k.shl((nb+4)/2);
+			k.add(q);
+			k.div(r);
+
+			while (k.parity()==0)
+			{
+				k.shr(1);
+				WP.dbl();
+			}
+
+			if (!k.isunity()) WP=WP.mul(k);
+			if (WP.is_infinity()) res=INVALID_PUBLIC_KEY; 
+		}
+		return res;
+	}
+
+/* IEEE-1363 Diffie-Hellman online calculation Z=S.WD */
+	public static int SVDP_DH(byte[] S,byte[] WD,byte[] Z)    
+	{
+		BIG r,s,wx,wy,z;
+		int valid;
+		ECP W;
+		int res=0;
+		byte[] T=new byte[EFS];
+
+		s=BIG.fromBytes(S);
+
+		W=ECP.fromBytes(WD);
+		if (W.is_infinity()) res=ERROR;
+
+		if (res==0)
+		{
+			r=new BIG(ROM.CURVE_Order);
+			s.mod(r);
+
+			W=W.mul(s);
+			if (W.is_infinity()) res=ERROR; 
+			else 
+			{
+				W.getX().toBytes(T);
+				for (int i=0;i<EFS;i++) Z[i]=T[i];
+			}
+		}
+		return res;
+	}
+
+/* IEEE ECDSA Signature, C and D are signature on F using private key S */
+	public static int SP_DSA(int sha,RAND RNG,byte[] S,byte[] F,byte[] C,byte[] D)
+	{
+		byte[] T=new byte[EFS];
+		BIG r,s,f,c,d,u,vx,w;
+		ECP G,V;
+		byte[] B=hashit(sha,F,0,null,BIG.MODBYTES);
+
+		G=ECP.generator();
+		r=new BIG(ROM.CURVE_Order);
+
+		s=BIG.fromBytes(S);
+		f=BIG.fromBytes(B);
+
+		c=new BIG(0);
+		d=new BIG(0);
+		V=new ECP();
+
+		do {
+			u=BIG.randomnum(r,RNG);
+			w=BIG.randomnum(r,RNG); /* side channel masking */
+			//if (ROM.AES_S>0)
+			//{
+			//	u.mod2m(2*ROM.AES_S);
+			//}			
+			V.copy(G);
+			V=V.mul(u);   		
+			vx=V.getX();
+			c.copy(vx);
+			c.mod(r);
+			if (c.iszilch()) continue;
+
+			u.copy(BIG.modmul(u,w,r));
+
+			u.invmodp(r);
+			d.copy(BIG.modmul(s,c,r));
+			d.add(f);
+
+			d.copy(BIG.modmul(d,w,r));
+
+			d.copy(BIG.modmul(u,d,r));
+		} while (d.iszilch());
+       
+		c.toBytes(T);
+		for (int i=0;i<EFS;i++) C[i]=T[i];
+		d.toBytes(T);
+		for (int i=0;i<EFS;i++) D[i]=T[i];
+		return 0;
+	}
+
+/* IEEE1363 ECDSA Signature Verification. Signature C and D on F is verified using public key W */
+	public static int VP_DSA(int sha,byte[] W,byte[] F, byte[] C,byte[] D)
+	{
+		BIG r,f,c,d,h2;
+		int res=0;
+		ECP G,WP,P;
+		int valid; 
+
+		byte[] B=hashit(sha,F,0,null,BIG.MODBYTES);
+
+		G=ECP.generator();
+		r=new BIG(ROM.CURVE_Order);
+
+		c=BIG.fromBytes(C);
+		d=BIG.fromBytes(D);
+		f=BIG.fromBytes(B);
+     
+		if (c.iszilch() || BIG.comp(c,r)>=0 || d.iszilch() || BIG.comp(d,r)>=0) 
+            res=INVALID;
+
+		if (res==0)
+		{
+			d.invmodp(r);
+			f.copy(BIG.modmul(f,d,r));
+			h2=BIG.modmul(c,d,r);
+
+			WP=ECP.fromBytes(W);
+			if (WP.is_infinity()) res=ERROR;
+			else
+			{
+				P=new ECP();
+				P.copy(WP);
+				P=P.mul2(h2,G,f);
+				if (P.is_infinity()) res=INVALID;
+				else
+				{
+					d=P.getX();
+					d.mod(r);
+					if (BIG.comp(d,c)!=0) res=INVALID;
+				}
+			}
+		}
+
+		return res;
+	}
+
+/* IEEE1363 ECIES encryption. Encryption of plaintext M uses public key W and produces ciphertext V,C,T */
+	public static byte[] ECIES_ENCRYPT(int sha,byte[] P1,byte[] P2,RAND RNG,byte[] W,byte[] M,byte[] V,byte[] T)
+	{ 
+		int i,len;
+
+		byte[] Z=new byte[EFS];
+		byte[] VZ=new byte[3*EFS+1];
+		byte[] K1=new byte[ECP.AESKEY];
+		byte[] K2=new byte[ECP.AESKEY];
+		byte[] U=new byte[EGS];
+
+		if (KEY_PAIR_GENERATE(RNG,U,V)!=0) return new byte[0];  
+		if (SVDP_DH(U,W,Z)!=0) return new byte[0];     
+
+		for (i=0;i<2*EFS+1;i++) VZ[i]=V[i];
+		for (i=0;i<EFS;i++) VZ[2*EFS+1+i]=Z[i];
+
+
+		byte[] K=KDF2(sha,VZ,P1,2*ECP.AESKEY);
+
+		for (i=0;i<ECP.AESKEY;i++) {K1[i]=K[i]; K2[i]=K[ECP.AESKEY+i];} 
+
+		byte[] C=AES_CBC_IV0_ENCRYPT(K1,M);
+
+		byte[] L2=inttoBytes(P2.length,8);	
+	
+		byte[] AC=new byte[C.length+P2.length+8];
+		for (i=0;i<C.length;i++) AC[i]=C[i];
+		for (i=0;i<P2.length;i++) AC[C.length+i]=P2[i];
+		for (i=0;i<8;i++) AC[C.length+P2.length+i]=L2[i];
+	
+		HMAC(sha,AC,K2,T);
+
+		return C;
+	}
+
+/* IEEE1363 ECIES decryption. Decryption of ciphertext V,C,T using private key U outputs plaintext M */
+	public static byte[] ECIES_DECRYPT(int sha,byte[] P1,byte[] P2,byte[] V,byte[] C,byte[] T,byte[] U)
+	{ 
+
+		int i,len;
+
+		byte[] Z=new byte[EFS];
+		byte[] VZ=new byte[3*EFS+1];
+		byte[] K1=new byte[ECP.AESKEY];
+		byte[] K2=new byte[ECP.AESKEY];
+		byte[] TAG=new byte[T.length];
+
+		if (SVDP_DH(U,V,Z)!=0) return new byte[0];  
+
+		for (i=0;i<2*EFS+1;i++) VZ[i]=V[i];
+		for (i=0;i<EFS;i++) VZ[2*EFS+1+i]=Z[i];
+
+		byte[] K=KDF2(sha,VZ,P1,2*ECP.AESKEY);
+
+		for (i=0;i<ECP.AESKEY;i++) {K1[i]=K[i]; K2[i]=K[ECP.AESKEY+i];} 
+
+		byte[] M=AES_CBC_IV0_DECRYPT(K1,C); 
+
+		if (M.length==0) return M;
+
+		byte[] L2=inttoBytes(P2.length,8);	
+	
+		byte[] AC=new byte[C.length+P2.length+8];
+
+		for (i=0;i<C.length;i++) AC[i]=C[i];
+		for (i=0;i<P2.length;i++) AC[C.length+i]=P2[i];
+		for (i=0;i<8;i++) AC[C.length+P2.length+i]=L2[i];
+	
+		HMAC(sha,AC,K2,TAG);
+
+		boolean same=true;
+		for (i=0;i<T.length;i++) if (T[i]!=TAG[i]) same=false;
+		if (!same) return new byte[0];
+	
+		return M;
+
+	}
+}
diff --git a/src/main/java/org/apache/milagro/amcl/ANSSI/ECP.java b/src/main/java/org/apache/milagro/amcl/ANSSI/ECP.java
new file mode 100644
index 0000000..694fbad
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/ANSSI/ECP.java
@@ -0,0 +1,1109 @@
+/*
+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.
+*/
+
+/* Elliptic Curve Point class */
+
+package org.apache.milagro.amcl.ANSSI;
+
+public final class ECP {
+
+	public static final int WEIERSTRASS=0;
+	public static final int EDWARDS=1;
+	public static final int MONTGOMERY=2;
+	public static final int NOT=0;
+	public static final int BN=1;
+	public static final int BLS=2;
+	public static final int D_TYPE=0;
+	public static final int M_TYPE=1;
+	public static final int POSITIVEX=0;
+	public static final int NEGATIVEX=1;
+
+	public static final int CURVETYPE=WEIERSTRASS;
+	public static final int CURVE_PAIRING_TYPE=NOT;
+	public static final int SEXTIC_TWIST=NOT;
+	public static final int SIGN_OF_X=NOT;
+
+	public static final int SHA256=32;
+	public static final int SHA384=48;
+	public static final int SHA512=64;
+
+	public static final int HASH_TYPE=32;
+	public static final int AESKEY=16;
+
+	private FP x;
+	private FP y;
+	private FP z;
+//	private boolean INF;
+
+/* Constructor - set to O */
+	public ECP() {
+		//INF=true;
+		x=new FP(0);
+		y=new FP(1);
+		if (CURVETYPE==EDWARDS)
+		{
+			z=new FP(1);
+		}
+		else
+		{
+			z=new FP(0);
+		}
+	}
+
+    public ECP(ECP e) {
+        this.x = new FP(e.x);
+        this.y = new FP(e.y);
+        this.z = new FP(e.z);
+    }
+
+/* test for O point-at-infinity */
+	public boolean is_infinity() {
+//		if (INF) return true;                            // Edits made
+		if (CURVETYPE==EDWARDS)
+		{
+			return (x.iszilch() && y.equals(z));
+		}
+		if (CURVETYPE==WEIERSTRASS)
+		{
+			return (x.iszilch() && z.iszilch());
+		}
+		if (CURVETYPE==MONTGOMERY)
+		{
+			return z.iszilch();
+		}
+		return true;
+	}
+/* Conditional swap of P and Q dependant on d */
+	private void cswap(ECP Q,int d)
+	{
+		x.cswap(Q.x,d);
+		if (CURVETYPE!=MONTGOMERY) y.cswap(Q.y,d);
+		z.cswap(Q.z,d);
+	//	if (CURVETYPE!=EDWARDS)
+	//	{
+	//		boolean bd;
+	//		if (d==0) bd=false;
+	//		else bd=true;
+	//		bd=bd&(INF^Q.INF);
+	//		INF^=bd;
+	//		Q.INF^=bd;
+	//	}
+	}
+
+/* Conditional move of Q to P dependant on d */
+	private void cmove(ECP Q,int d)
+	{
+		x.cmove(Q.x,d);
+		if (CURVETYPE!=MONTGOMERY) y.cmove(Q.y,d);
+		z.cmove(Q.z,d);
+	//	if (CURVETYPE!=EDWARDS)
+	//	{
+	//		boolean bd;
+	//		if (d==0) bd=false;
+	//		else bd=true;
+	//		INF^=(INF^Q.INF)&bd;
+	//	}
+	}
+
+/* return 1 if b==c, no branching */
+	private static int teq(int b,int c)
+	{
+		int x=b^c;
+		x-=1;  // if x=0, x now -1
+		return ((x>>31)&1);
+	}
+
+/* Constant time select from pre-computed table */
+	private void select(ECP W[],int b)
+	{
+		ECP MP=new ECP(); 
+		int m=b>>31;
+		int babs=(b^m)-m;
+
+		babs=(babs-1)/2;
+		cmove(W[0],teq(babs,0));  // conditional move
+		cmove(W[1],teq(babs,1));
+		cmove(W[2],teq(babs,2));
+		cmove(W[3],teq(babs,3));
+		cmove(W[4],teq(babs,4));
+		cmove(W[5],teq(babs,5));
+		cmove(W[6],teq(babs,6));
+		cmove(W[7],teq(babs,7));
+ 
+		MP.copy(this);
+		MP.neg();
+		cmove(MP,(int)(m&1));
+	}
+
+/* Test P == Q */
+	public boolean equals(ECP Q) {
+//		if (is_infinity() && Q.is_infinity()) return true;
+//		if (is_infinity() || Q.is_infinity()) return false;
+
+		FP a=new FP(0);                                        // Edits made
+		FP b=new FP(0);
+		a.copy(x); a.mul(Q.z); 
+		b.copy(Q.x); b.mul(z); 
+		if (!a.equals(b)) return false;
+		if (CURVETYPE!=MONTGOMERY)
+		{
+			a.copy(y); a.mul(Q.z); 
+			b.copy(Q.y); b.mul(z); 
+			if (!a.equals(b)) return false;
+		}
+		return true;
+	}
+
+/* this=P */
+	public void copy(ECP P)
+	{
+		x.copy(P.x);
+		if (CURVETYPE!=MONTGOMERY) y.copy(P.y);
+		z.copy(P.z);
+		//INF=P.INF;
+	}
+/* this=-this */
+	public void neg() {
+//		if (is_infinity()) return;
+		if (CURVETYPE==WEIERSTRASS)
+		{
+			y.neg(); y.norm();
+		}
+		if (CURVETYPE==EDWARDS)
+		{
+			x.neg(); x.norm();
+		}
+		return;
+	}
+/* set this=O */
+	public void inf() {
+//		INF=true;
+		x.zero();
+		if (CURVETYPE!=MONTGOMERY) y.one();
+		if (CURVETYPE!=EDWARDS) z.zero();
+		else z.one();
+	}
+
+/* Calculate RHS of curve equation */
+	public static FP RHS(FP x) {
+		x.norm();
+		FP r=new FP(x);
+		r.sqr();
+
+		if (CURVETYPE==WEIERSTRASS)
+		{ // x^3+Ax+B
+			FP b=new FP(new BIG(ROM.CURVE_B));
+			r.mul(x);
+			if (ROM.CURVE_A==-3)
+			{
+				FP cx=new FP(x);
+				cx.imul(3);
+				cx.neg(); cx.norm();
+				r.add(cx);
+			}
+			r.add(b);
+		}
+		if (CURVETYPE==EDWARDS)
+		{ // (Ax^2-1)/(Bx^2-1) 
+			FP b=new FP(new BIG(ROM.CURVE_B));
+
+			FP one=new FP(1);
+			b.mul(r);
+			b.sub(one);
+			b.norm();
+			if (ROM.CURVE_A==-1) r.neg();
+			r.sub(one); r.norm();
+			b.inverse();
+
+			r.mul(b);
+		}
+		if (CURVETYPE==MONTGOMERY)
+		{ // x^3+Ax^2+x
+			FP x3=new FP(0);
+			x3.copy(r);
+			x3.mul(x);
+			r.imul(ROM.CURVE_A);
+			r.add(x3);
+			r.add(x);
+		}
+		r.reduce();
+		return r;
+	}
+
+/* set (x,y) from two BIGs */
+	public ECP(BIG ix,BIG iy) {
+		x=new FP(ix);
+		y=new FP(iy);
+		z=new FP(1);
+		FP rhs=RHS(x);
+
+		if (CURVETYPE==MONTGOMERY)
+		{
+			if (rhs.jacobi()!=1) inf();
+			//if (rhs.jacobi()==1) INF=false;
+			//else inf();
+		}
+		else
+		{
+			FP y2=new FP(y);
+			y2.sqr();
+			if (!y2.equals(rhs)) inf();
+			//if (y2.equals(rhs)) INF=false;
+			//else inf();
+		}
+	}
+/* set (x,y) from BIG and a bit */
+	public ECP(BIG ix,int s) {
+		x=new FP(ix);
+		FP rhs=RHS(x);
+		y=new FP(0);
+		z=new FP(1);
+		if (rhs.jacobi()==1)
+		{
+			FP ny=rhs.sqrt();
+			if (ny.redc().parity()!=s) ny.neg();
+			y.copy(ny);
+			//INF=false;
+		}
+		else inf();
+	}
+
+/* set from x - calculate y from curve equation */
+	public ECP(BIG ix) {
+		x=new FP(ix);
+		FP rhs=RHS(x);
+		y=new FP(0);
+		z=new FP(1);
+		if (rhs.jacobi()==1)
+		{
+			if (CURVETYPE!=MONTGOMERY) y.copy(rhs.sqrt());
+			//INF=false;
+		}
+		else inf(); //INF=true;
+	}
+
+/* set to affine - from (x,y,z) to (x,y) */
+	public void affine() {
+		if (is_infinity()) return;	// 
+		FP one=new FP(1);
+		if (z.equals(one)) return;
+		z.inverse();
+		x.mul(z); x.reduce();
+		if (CURVETYPE!=MONTGOMERY)            // Edits made
+		{
+			y.mul(z); y.reduce();
+		}
+		z.copy(one);
+	}
+/* extract x as a BIG */
+	public BIG getX()
+	{
+		ECP W=new ECP(this);
+		W.affine();
+		return W.x.redc();
+	}
+/* extract y as a BIG */
+	public BIG getY()
+	{
+		ECP W=new ECP(this);
+		W.affine();
+		return W.y.redc();
+	}
+
+/* get sign of Y */
+	public int getS()
+	{
+		//affine();
+		BIG y=getY();
+		return y.parity();
+	}
+/* extract x as an FP */
+	public FP getx()
+	{
+		return x;
+	}
+/* extract y as an FP */
+	public FP gety()
+	{
+		return y;
+	}
+/* extract z as an FP */
+	public FP getz()
+	{
+		return z;
+	}
+/* convert to byte array */
+	public void toBytes(byte[] b,boolean compress)
+	{
+		byte[] t=new byte[BIG.MODBYTES];
+		ECP W=new ECP(this);
+		W.affine();
+
+		W.x.redc().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) b[i+1]=t[i];
+
+		if (CURVETYPE==MONTGOMERY)
+		{
+			b[0]=0x06;
+			return;
+		}
+
+		if (compress)
+		{
+			b[0]=0x02;
+			if (y.redc().parity()==1) b[0]=0x03;
+			return;
+		}
+
+		b[0]=0x04;
+
+		W.y.redc().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) b[i+BIG.MODBYTES+1]=t[i];
+	}
+/* convert from byte array to point */
+	public static ECP fromBytes(byte[] b)
+	{
+		byte[] t=new byte[BIG.MODBYTES];
+		BIG p=new BIG(ROM.Modulus);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=b[i+1];
+		BIG px=BIG.fromBytes(t);
+		if (BIG.comp(px,p)>=0) return new ECP();
+
+		if (CURVETYPE==MONTGOMERY)
+		{
+			return new ECP(px);
+		}
+
+		if (b[0]==0x04)
+		{
+			for (int i=0;i<BIG.MODBYTES;i++) t[i]=b[i+BIG.MODBYTES+1];
+			BIG py=BIG.fromBytes(t);
+			if (BIG.comp(py,p)>=0) return new ECP();
+			return new ECP(px,py);
+		}
+
+		if (b[0]==0x02 || b[0]==0x03)
+		{
+			return new ECP(px,(int)(b[0]&1));
+		}
+		return new ECP();
+	}
+/* convert to hex string */
+	public String toString() {
+		ECP W=new ECP(this);	
+		W.affine();
+		if (W.is_infinity()) return "infinity";
+		if (CURVETYPE==MONTGOMERY) return "("+W.x.redc().toString()+")";
+		else return "("+W.x.redc().toString()+","+W.y.redc().toString()+")";
+	}
+
+/* convert to hex string */
+	public String toRawString() {
+		//if (is_infinity()) return "infinity";
+		//affine();
+		ECP W=new ECP(this);	
+		if (CURVETYPE==MONTGOMERY) return "("+W.x.redc().toString()+","+W.z.redc().toString()+")";
+		else return "("+W.x.redc().toString()+","+W.y.redc().toString()+","+W.z.redc().toString()+")";
+	}
+
+/* this*=2 */
+	public void dbl() {
+//		if (INF) return;
+		
+		if (CURVETYPE==WEIERSTRASS)
+		{
+			if (ROM.CURVE_A==0)
+			{
+//System.out.println("Into dbl");
+				FP t0=new FP(y);                      /*** Change ***/    // Edits made
+				t0.sqr();
+				FP t1=new FP(y);
+				t1.mul(z);
+				FP t2=new FP(z);
+				t2.sqr();
+
+				z.copy(t0);
+				z.add(t0); z.norm(); 
+				z.add(z); z.add(z); z.norm();
+				t2.imul(3*ROM.CURVE_B_I);
+
+				FP x3=new FP(t2);
+				x3.mul(z);
+
+				FP y3=new FP(t0);
+				y3.add(t2); y3.norm();
+				z.mul(t1); 
+				t1.copy(t2); t1.add(t2); t2.add(t1);
+				t0.sub(t2); t0.norm(); y3.mul(t0); y3.add(x3);
+				t1.copy(x); t1.mul(y); 
+				x.copy(t0); x.norm(); x.mul(t1); x.add(x);
+				x.norm(); 
+				y.copy(y3); y.norm();
+//System.out.println("Out of dbl");
+			}
+			else
+			{
+				FP t0=new FP(x);
+				FP t1=new FP(y);
+				FP t2=new FP(z);
+				FP t3=new FP(x);
+				FP z3=new FP(z);
+				FP y3=new FP(0);
+				FP x3=new FP(0);
+				FP b=new FP(0);
+
+				if (ROM.CURVE_B_I==0)
+					b.copy(new FP(new BIG(ROM.CURVE_B)));
+
+				t0.sqr();  //1    x^2
+				t1.sqr();  //2    y^2
+				t2.sqr();  //3
+
+				t3.mul(y); //4
+				t3.add(t3); t3.norm();//5
+				z3.mul(x);   //6
+				z3.add(z3);  z3.norm();//7
+				y3.copy(t2); 
+				
+				if (ROM.CURVE_B_I==0)
+					y3.mul(b); //8
+				else
+					y3.imul(ROM.CURVE_B_I);
+				
+				y3.sub(z3); //y3.norm(); //9  ***
+				x3.copy(y3); x3.add(y3); x3.norm();//10
+
+				y3.add(x3); //y3.norm();//11
+				x3.copy(t1); x3.sub(y3); x3.norm();//12
+				y3.add(t1); y3.norm();//13
+				y3.mul(x3); //14
+				x3.mul(t3); //15
+				t3.copy(t2); t3.add(t2); //t3.norm(); //16
+				t2.add(t3); //t2.norm(); //17
+
+				if (ROM.CURVE_B_I==0)
+					z3.mul(b); //18
+				else
+					z3.imul(ROM.CURVE_B_I);
+
+				z3.sub(t2); //z3.norm();//19
+				z3.sub(t0); z3.norm();//20  ***
+				t3.copy(z3); t3.add(z3); //t3.norm();//21
+
+				z3.add(t3); z3.norm(); //22
+				t3.copy(t0); t3.add(t0); //t3.norm(); //23
+				t0.add(t3); //t0.norm();//24
+				t0.sub(t2); t0.norm();//25
+
+				t0.mul(z3);//26
+				y3.add(t0); //y3.norm();//27
+				t0.copy(y); t0.mul(z);//28
+				t0.add(t0); t0.norm(); //29
+				z3.mul(t0);//30
+				x3.sub(z3); //x3.norm();//31
+				t0.add(t0); t0.norm();//32
+				t1.add(t1); t1.norm();//33
+				z3.copy(t0); z3.mul(t1);//34
+
+				x.copy(x3); x.norm(); 
+				y.copy(y3); y.norm();
+				z.copy(z3); z.norm();
+			}
+		}
+		if (CURVETYPE==EDWARDS)
+		{
+//System.out.println("Into dbl");
+			FP C=new FP(x);
+			FP D=new FP(y);
+			FP H=new FP(z);
+			FP J=new FP(0);
+
+			x.mul(y); x.add(x); x.norm();
+			C.sqr();
+			D.sqr();
+
+			if (ROM.CURVE_A==-1) C.neg();	
+
+			y.copy(C); y.add(D); y.norm();
+			H.sqr(); H.add(H);
+
+			z.copy(y);
+			J.copy(y); 
+
+			J.sub(H); J.norm();
+			x.mul(J);
+
+			C.sub(D); C.norm();
+			y.mul(C);
+			z.mul(J);
+//System.out.println("Out of dbl");
+		}
+		if (CURVETYPE==MONTGOMERY)
+		{
+			FP A=new FP(x);
+			FP B=new FP(x);		
+			FP AA=new FP(0);
+			FP BB=new FP(0);
+			FP C=new FP(0);
+
+			A.add(z); A.norm();
+			AA.copy(A); AA.sqr();
+			B.sub(z); B.norm();
+			BB.copy(B); BB.sqr();
+			C.copy(AA); C.sub(BB); C.norm();
+			x.copy(AA); x.mul(BB);
+
+			A.copy(C); A.imul((ROM.CURVE_A+2)/4);
+
+			BB.add(A); BB.norm();
+			z.copy(BB); z.mul(C);
+		}
+		return;
+	}
+
+/* this+=Q */
+	public void add(ECP Q) {
+//		if (INF)
+//		{
+//			copy(Q);
+//			return;
+//		}
+//		if (Q.INF) return;
+
+		if (CURVETYPE==WEIERSTRASS)
+		{
+
+
+			if (ROM.CURVE_A==0)
+			{
+// Edits made
+//System.out.println("Into add");
+				int b=3*ROM.CURVE_B_I;
+				FP t0=new FP(x);
+				t0.mul(Q.x);
+				FP t1=new FP(y);
+				t1.mul(Q.y);
+				FP t2=new FP(z);
+				t2.mul(Q.z);
+				FP t3=new FP(x);
+				t3.add(y); t3.norm();
+				FP t4=new FP(Q.x);
+				t4.add(Q.y); t4.norm();
+				t3.mul(t4);
+				t4.copy(t0); t4.add(t1);
+
+				t3.sub(t4); t3.norm();
+				t4.copy(y);
+				t4.add(z); t4.norm();
+				FP x3=new FP(Q.y);
+				x3.add(Q.z); x3.norm();
+
+				t4.mul(x3);
+				x3.copy(t1);
+				x3.add(t2);
+	
+				t4.sub(x3); t4.norm();
+				x3.copy(x); x3.add(z); x3.norm();
+				FP y3=new FP(Q.x);
+				y3.add(Q.z); y3.norm();
+				x3.mul(y3);
+				y3.copy(t0);
+				y3.add(t2);
+				y3.rsub(x3); y3.norm();
+				x3.copy(t0); x3.add(t0); 
+				t0.add(x3); t0.norm();
+				t2.imul(b);
+
+				FP z3=new FP(t1); z3.add(t2); z3.norm();
+				t1.sub(t2); t1.norm(); 
+				y3.imul(b);
+	
+				x3.copy(y3); x3.mul(t4); t2.copy(t3); t2.mul(t1); x3.rsub(t2);
+				y3.mul(t0); t1.mul(z3); y3.add(t1);
+				t0.mul(t3); z3.mul(t4); z3.add(t0);
+
+				x.copy(x3); x.norm(); 
+				y.copy(y3); y.norm();
+				z.copy(z3); z.norm();
+//System.out.println("Out of add");
+			}
+			else
+			{
+				FP t0=new FP(x);
+				FP t1=new FP(y);
+				FP t2=new FP(z);
+				FP t3=new FP(x);
+				FP t4=new FP(Q.x);
+				FP z3=new FP(0);
+				FP y3=new FP(Q.x);
+				FP x3=new FP(Q.y);
+				FP b=new FP(0);
+
+				if (ROM.CURVE_B_I==0)
+					b.copy(new FP(new BIG(ROM.CURVE_B)));
+
+				t0.mul(Q.x); //1
+				t1.mul(Q.y); //2
+				t2.mul(Q.z); //3
+
+				t3.add(y); t3.norm(); //4
+				t4.add(Q.y); t4.norm();//5
+				t3.mul(t4);//6
+				t4.copy(t0); t4.add(t1); //t4.norm(); //7
+				t3.sub(t4); t3.norm(); //8
+				t4.copy(y); t4.add(z); t4.norm();//9
+				x3.add(Q.z); x3.norm();//10
+				t4.mul(x3); //11
+				x3.copy(t1); x3.add(t2); //x3.norm();//12
+
+				t4.sub(x3); t4.norm();//13
+				x3.copy(x); x3.add(z); x3.norm(); //14
+				y3.add(Q.z); y3.norm();//15
+
+				x3.mul(y3); //16
+				y3.copy(t0); y3.add(t2); //y3.norm();//17
+
+				y3.rsub(x3); y3.norm(); //18
+				z3.copy(t2); 
+				
+
+				if (ROM.CURVE_B_I==0)
+					z3.mul(b); //18
+				else
+					z3.imul(ROM.CURVE_B_I);
+				
+				x3.copy(y3); x3.sub(z3); x3.norm(); //20
+				z3.copy(x3); z3.add(x3); //z3.norm(); //21
+
+				x3.add(z3); //x3.norm(); //22
+				z3.copy(t1); z3.sub(x3); z3.norm(); //23
+				x3.add(t1); x3.norm(); //24
+
+				if (ROM.CURVE_B_I==0)
+					y3.mul(b); //18
+				else
+					y3.imul(ROM.CURVE_B_I);
+
+				t1.copy(t2); t1.add(t2); //t1.norm();//26
+				t2.add(t1); //t2.norm();//27
+
+				y3.sub(t2); //y3.norm(); //28
+
+				y3.sub(t0); y3.norm(); //29
+				t1.copy(y3); t1.add(y3); //t1.norm();//30
+				y3.add(t1); y3.norm(); //31
+
+				t1.copy(t0); t1.add(t0); //t1.norm(); //32
+				t0.add(t1); //t0.norm();//33
+				t0.sub(t2); t0.norm();//34
+				t1.copy(t4); t1.mul(y3);//35
+				t2.copy(t0); t2.mul(y3);//36
+				y3.copy(x3); y3.mul(z3);//37
+				y3.add(t2); //y3.norm();//38
+				x3.mul(t3);//39
+				x3.sub(t1);//40
+				z3.mul(t4);//41
+				t1.copy(t3); t1.mul(t0);//42
+				z3.add(t1); 
+				x.copy(x3); x.norm(); 
+				y.copy(y3); y.norm();
+				z.copy(z3); z.norm();
+			}
+		}
+		if (CURVETYPE==EDWARDS)
+		{
+//System.out.println("Into add");
+			FP A=new FP(z);
+			FP B=new FP(0);
+			FP C=new FP(x);
+			FP D=new FP(y);
+			FP E=new FP(0);
+			FP F=new FP(0);
+			FP G=new FP(0);
+
+			A.mul(Q.z);   
+			B.copy(A); B.sqr();    
+			C.mul(Q.x);      
+			D.mul(Q.y); 
+
+			E.copy(C); E.mul(D);  
+		
+			if (ROM.CURVE_B_I==0)
+			{
+				FP b=new FP(new BIG(ROM.CURVE_B));
+				E.mul(b);
+			}
+			else
+				E.imul(ROM.CURVE_B_I); 
+
+			F.copy(B); F.sub(E);      
+			G.copy(B); G.add(E);       
+
+			if (ROM.CURVE_A==1)
+			{
+				E.copy(D); E.sub(C);
+			}
+			C.add(D); 
+
+			B.copy(x); B.add(y);    
+			D.copy(Q.x); D.add(Q.y); B.norm(); D.norm(); 
+			B.mul(D);                   
+			B.sub(C); B.norm(); F.norm(); 
+			B.mul(F);                     
+			x.copy(A); x.mul(B); G.norm();  
+			if (ROM.CURVE_A==1)
+			{
+				E.norm(); C.copy(E); C.mul(G);  
+			}
+			if (ROM.CURVE_A==-1)
+			{
+				C.norm(); C.mul(G);
+			}
+			y.copy(A); y.mul(C);     
+
+			z.copy(F);	
+			z.mul(G);
+//System.out.println("Out of add");
+		}
+		return;
+	}
+
+/* Differential Add for Montgomery curves. this+=Q where W is this-Q and is affine. */
+	public void dadd(ECP Q,ECP W) {
+		FP A=new FP(x);
+		FP B=new FP(x);
+		FP C=new FP(Q.x);
+		FP D=new FP(Q.x);
+		FP DA=new FP(0);
+		FP CB=new FP(0);	
+			
+		A.add(z); 
+		B.sub(z); 
+
+		C.add(Q.z);
+		D.sub(Q.z);
+		A.norm();
+
+		D.norm();
+		DA.copy(D); DA.mul(A);
+
+		C.norm();
+		B.norm();
+		CB.copy(C); CB.mul(B);
+
+		A.copy(DA); A.add(CB); 
+		A.norm(); A.sqr();
+		B.copy(DA); B.sub(CB); 
+		B.norm(); B.sqr();
+
+		x.copy(A);
+		z.copy(W.x); z.mul(B);
+	}
+/* this-=Q */
+	public void sub(ECP Q) {
+		ECP NQ=new ECP(Q);
+		NQ.neg();
+		add(NQ);
+	}
+
+/* constant time multiply by small integer of length bts - use ladder */
+	public ECP pinmul(int e,int bts) {	
+		if (CURVETYPE==MONTGOMERY)
+			return this.mul(new BIG(e));
+		else
+		{
+			int nb,i,b;
+			ECP P=new ECP();
+			ECP R0=new ECP();
+			ECP R1=new ECP(); R1.copy(this);
+
+			for (i=bts-1;i>=0;i--)
+			{
+				b=(e>>i)&1;
+				P.copy(R1);
+				P.add(R0);
+				R0.cswap(R1,b);
+				R1.copy(P);
+				R0.dbl();
+				R0.cswap(R1,b);
+			}
+			P.copy(R0);
+			P.affine();
+			return P;
+		}
+	}
+
+/* return e.this */
+
+	public ECP mul(BIG e) {
+		if (e.iszilch() || is_infinity()) return new ECP();
+		ECP P=new ECP();
+		if (CURVETYPE==MONTGOMERY)
+		{
+/* use Ladder */
+			int nb,i,b;
+			ECP D=new ECP();
+			ECP R0=new ECP(); R0.copy(this);
+			ECP R1=new ECP(); R1.copy(this);
+			R1.dbl();
+
+			D.copy(this); D.affine();
+			nb=e.nbits();
+			for (i=nb-2;i>=0;i--)
+			{
+				b=e.bit(i);
+				P.copy(R1);
+
+				P.dadd(R0,D);
+				R0.cswap(R1,b);
+				R1.copy(P);
+				R0.dbl();
+				R0.cswap(R1,b);
+
+			}
+
+			P.copy(R0);
+		}
+		else
+		{
+// fixed size windows 
+			int i,b,nb,m,s,ns;
+			BIG mt=new BIG();
+			BIG t=new BIG();
+			ECP Q=new ECP();
+			ECP C=new ECP();
+			ECP[] W=new ECP[8];
+			byte[] w=new byte[1+(BIG.NLEN*BIG.BASEBITS+3)/4];
+
+			//affine();
+
+// precompute table 
+			Q.copy(this);
+
+			Q.dbl();
+			W[0]=new ECP();
+			W[0].copy(this);
+
+			for (i=1;i<8;i++)
+			{
+				W[i]=new ECP();
+				W[i].copy(W[i-1]);
+				W[i].add(Q);
+			}
+
+// make exponent odd - add 2P if even, P if odd 
+			t.copy(e);
+			s=t.parity();
+			t.inc(1); t.norm(); ns=t.parity(); mt.copy(t); mt.inc(1); mt.norm();
+			t.cmove(mt,s);
+			Q.cmove(this,ns);
+			C.copy(Q);
+
+			nb=1+(t.nbits()+3)/4;
+
+// convert exponent to signed 4-bit window 
+			for (i=0;i<nb;i++)
+			{
+				w[i]=(byte)(t.lastbits(5)-16);
+				t.dec(w[i]); t.norm();
+				t.fshr(4);	
+			}
+			w[nb]=(byte)t.lastbits(5);
+	
+			P.copy(W[(w[nb]-1)/2]);  
+			for (i=nb-1;i>=0;i--)
+			{
+				Q.select(W,w[i]);
+				P.dbl();
+				P.dbl();
+				P.dbl();
+				P.dbl();
+				P.add(Q);
+			}
+			P.sub(C); /* apply correction */
+		}
+		P.affine();
+		return P;
+	}
+
+/* Return e.this+f.Q */
+
+	public ECP mul2(BIG e,ECP Q,BIG f) {
+		BIG te=new BIG();
+		BIG tf=new BIG();
+		BIG mt=new BIG();
+		ECP S=new ECP();
+		ECP T=new ECP();
+		ECP C=new ECP();
+		ECP[] W=new ECP[8];
+		byte[] w=new byte[1+(BIG.NLEN*BIG.BASEBITS+1)/2];		
+		int i,s,ns,nb;
+		byte a,b;
+
+		//affine();
+		//Q.affine();
+
+		te.copy(e);
+		tf.copy(f);
+
+// precompute table 
+		W[1]=new ECP(); W[1].copy(this); W[1].sub(Q);
+		W[2]=new ECP(); W[2].copy(this); W[2].add(Q);
+		S.copy(Q); S.dbl();
+		W[0]=new ECP(); W[0].copy(W[1]); W[0].sub(S);
+		W[3]=new ECP(); W[3].copy(W[2]); W[3].add(S);
+		T.copy(this); T.dbl();
+		W[5]=new ECP(); W[5].copy(W[1]); W[5].add(T);
+		W[6]=new ECP(); W[6].copy(W[2]); W[6].add(T);
+		W[4]=new ECP(); W[4].copy(W[5]); W[4].sub(S);
+		W[7]=new ECP(); W[7].copy(W[6]); W[7].add(S);
+
+// if multiplier is odd, add 2, else add 1 to multiplier, and add 2P or P to correction 
+
+		s=te.parity();
+		te.inc(1); te.norm(); ns=te.parity(); mt.copy(te); mt.inc(1); mt.norm();
+		te.cmove(mt,s);
+		T.cmove(this,ns);
+		C.copy(T);
+
+		s=tf.parity();
+		tf.inc(1); tf.norm(); ns=tf.parity(); mt.copy(tf); mt.inc(1); mt.norm();
+		tf.cmove(mt,s);
+		S.cmove(Q,ns);
+		C.add(S);
+
+		mt.copy(te); mt.add(tf); mt.norm();
+		nb=1+(mt.nbits()+1)/2;
+
+// convert exponent to signed 2-bit window 
+		for (i=0;i<nb;i++)
+		{
+			a=(byte)(te.lastbits(3)-4);
+			te.dec(a); te.norm(); 
+			te.fshr(2);
+			b=(byte)(tf.lastbits(3)-4);
+			tf.dec(b); tf.norm(); 
+			tf.fshr(2);
+			w[i]=(byte)(4*a+b);
+		}
+		w[nb]=(byte)(4*te.lastbits(3)+tf.lastbits(3));
+		S.copy(W[(w[nb]-1)/2]);  
+
+		for (i=nb-1;i>=0;i--)
+		{
+			T.select(W,w[i]);
+			S.dbl();
+			S.dbl();
+			S.add(T);
+		}
+		S.sub(C); /* apply correction */
+		S.affine();
+		return S;
+	}
+
+// multiply a point by the curves cofactor
+	public void cfp()
+	{
+		int cf=ROM.CURVE_Cof_I;
+		if (cf==1) return;
+		if (cf==4)
+		{
+			dbl(); dbl();
+			//affine();
+			return;
+		} 
+		if (cf==8)
+		{
+			dbl(); dbl(); dbl();
+			//affine();
+			return;
+		}
+		BIG c=new BIG(ROM.CURVE_Cof);
+		copy(mul(c));
+	}
+
+/* Map byte string to curve point */
+	public static ECP mapit(byte[] h)
+	{
+		BIG q=new BIG(ROM.Modulus);
+		BIG x=BIG.fromBytes(h);
+		x.mod(q);
+		ECP P;
+
+		while (true)
+		{
+			while (true)
+			{
+				if (CURVETYPE!=MONTGOMERY)
+					P=new ECP(x,0);
+				else
+					P=new ECP(x);	
+				x.inc(1); x.norm();
+				if (!P.is_infinity()) break;
+			}
+			P.cfp();
+			if (!P.is_infinity()) break;
+		}
+		return P;
+	}
+
+	public static ECP generator()
+	{
+		ECP G;
+		BIG gx,gy;
+		gx=new BIG(ROM.CURVE_Gx);
+
+		if (ECP.CURVETYPE!=ECP.MONTGOMERY)
+		{
+			gy=new BIG(ROM.CURVE_Gy);
+			G=new ECP(gx,gy);
+		}
+		else
+			G=new ECP(gx);
+		return G;
+	}
+
+/*
+	public static void main(String[] args) {
+
+		BIG Gx=new BIG(ROM.CURVE_Gx);
+		BIG Gy;
+		ECP P;
+		if (CURVETYPE!=MONTGOMERY) Gy=new BIG(ROM.CURVE_Gy);
+		BIG r=new BIG(ROM.CURVE_Order);
+
+		//r.dec(7);
+	
+		System.out.println("Gx= "+Gx.toString());		
+		if (CURVETYPE!=MONTGOMERY) System.out.println("Gy= "+Gy.toString());	
+
+		if (CURVETYPE!=MONTGOMERY) P=new ECP(Gx,Gy);
+		else  P=new ECP(Gx);
+
+		System.out.println("P= "+P.toString());		
+
+		ECP R=P.mul(r);
+		//for (int i=0;i<10000;i++)
+		//	R=P.mul(r);
+	
+		System.out.println("R= "+R.toString());
+    } */
+}
+
diff --git a/src/main/java/org/apache/milagro/amcl/ANSSI/FP.java b/src/main/java/org/apache/milagro/amcl/ANSSI/FP.java
new file mode 100644
index 0000000..a28ab41
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/ANSSI/FP.java
@@ -0,0 +1,526 @@
+/*
+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.
+*/
+
+/* Finite Field arithmetic */
+/* AMCL mod p functions */
+
+package org.apache.milagro.amcl.ANSSI;
+
+public final class FP {
+
+	public static final int NOT_SPECIAL=0;
+	public static final int PSEUDO_MERSENNE=1;
+	public static final int MONTGOMERY_FRIENDLY=2;
+	public static final int GENERALISED_MERSENNE=3;
+
+	public static final int MODBITS=256; /* Number of bits in Modulus */
+	public static final int MOD8=7;  /* Modulus mod 8 */
+	public static final int MODTYPE=NOT_SPECIAL;
+
+	public static final int FEXCESS =((int)1<<24);  // BASEBITS*NLEN-MODBITS or 2^30 max!
+	public static final long OMASK=(long)(-1)<<(MODBITS%BIG.BASEBITS);
+	public static final int TBITS=MODBITS%BIG.BASEBITS; // Number of active bits in top word 
+	public static final long TMASK=((long)1<<TBITS)-1;
+
+
+	public final BIG x;
+	//public BIG p=new BIG(ROM.Modulus);
+	//public BIG r2modp=new BIG(ROM.R2modp);
+	public int XES;
+
+/**************** 64-bit specific ************************/
+
+/* reduce a DBIG to a BIG using the appropriate form of the modulus */
+	public static BIG mod(DBIG d)
+	{
+		if (MODTYPE==PSEUDO_MERSENNE)
+		{
+			BIG b;		
+			long v,tw;
+			BIG t=d.split(MODBITS);
+			b=new BIG(d);
+
+			v=t.pmul((int)ROM.MConst);
+
+			t.add(b);
+			t.norm();
+
+			tw=t.w[BIG.NLEN-1];
+			t.w[BIG.NLEN-1]&=FP.TMASK;
+			t.w[0]+=(ROM.MConst*((tw>>TBITS)+(v<<(BIG.BASEBITS-TBITS))));
+
+			t.norm();
+			return t;			
+		}
+		if (FP.MODTYPE==MONTGOMERY_FRIENDLY)
+		{
+			BIG b;		
+			long[] cr=new long[2];
+			for (int i=0;i<BIG.NLEN;i++)
+			{
+				cr=BIG.muladd(d.w[i],ROM.MConst-1,d.w[i],d.w[BIG.NLEN+i-1]);
+				d.w[BIG.NLEN+i]+=cr[0];
+				d.w[BIG.NLEN+i-1]=cr[1];
+			}
+			
+			b=new BIG(0);
+			for (int i=0;i<BIG.NLEN;i++ )
+				b.w[i]=d.w[BIG.NLEN+i];
+			b.norm();
+			return b;		
+		}
+		if (MODTYPE==GENERALISED_MERSENNE)
+		{ // GoldiLocks Only
+			BIG b;		
+			BIG t=d.split(MODBITS);
+			b=new BIG(d);
+			b.add(t);
+			DBIG dd=new DBIG(t);
+			dd.shl(MODBITS/2);
+
+			BIG tt=dd.split(MODBITS);
+			BIG lo=new BIG(dd);
+			b.add(tt);
+			b.add(lo);
+			b.norm();
+			tt.shl(MODBITS/2);
+			b.add(tt);
+
+			long carry=b.w[BIG.NLEN-1]>>TBITS;
+			b.w[BIG.NLEN-1]&=FP.TMASK;
+			b.w[0]+=carry;
+			
+			b.w[224/BIG.BASEBITS]+=carry<<(224%BIG.BASEBITS);
+			b.norm();
+			return b;		
+		}
+		if (MODTYPE==NOT_SPECIAL)
+		{
+			return BIG.monty(new BIG(ROM.Modulus),ROM.MConst,d);
+		}
+
+		return new BIG(0);
+	}
+
+
+
+/*********************************************************/
+
+
+/* Constructors */
+	public FP(int a)
+	{
+		x=new BIG(a);
+		nres();
+	}
+
+	public FP()
+	{
+		x=new BIG(0);
+		XES=1;
+	}
+
+	public FP(BIG a)
+	{
+		x=new BIG(a);
+		nres();
+	}
+	
+	public FP(FP a)
+	{
+		x=new BIG(a.x);
+		XES=a.XES;
+	}
+
+/* convert to string */
+	public String toString() 
+	{
+		String s=redc().toString();
+		return s;
+	}
+
+	public String toRawString() 
+	{
+		String s=x.toRawString();
+		return s;
+	}
+
+/* convert to Montgomery n-residue form */
+	public void nres()
+	{
+		if (MODTYPE!=PSEUDO_MERSENNE && MODTYPE!=GENERALISED_MERSENNE)
+		{
+			DBIG d=BIG.mul(x,new BIG(ROM.R2modp));  /*** Change ***/
+			x.copy(mod(d));
+			XES=2;
+		}
+		else XES=1;
+	}
+
+/* convert back to regular form */
+	public BIG redc()
+	{
+		if (MODTYPE!=PSEUDO_MERSENNE && MODTYPE!=GENERALISED_MERSENNE)
+		{
+			DBIG d=new DBIG(x);
+			return mod(d);
+		}
+		else 
+		{
+			BIG r=new BIG(x);
+			return r;
+		}
+	}
+
+/* test this=0? */
+	public boolean iszilch() {
+		FP z=new FP(this);
+		z.reduce();
+		return z.x.iszilch();
+
+	}
+
+/* copy from FP b */
+	public void copy(FP b)
+	{
+		x.copy(b.x);
+		XES=b.XES;
+	}
+
+/* set this=0 */
+	public void zero()
+	{
+		x.zero();
+		XES=1;
+	}
+	
+/* set this=1 */
+	public void one()
+	{
+		x.one(); nres();
+	}
+
+/* normalise this */
+	public void norm()
+	{
+		x.norm();
+	}
+
+/* swap FPs depending on d */
+	public void cswap(FP b,int d)
+	{
+		x.cswap(b.x,d);
+		int t,c=d;
+		c=~(c-1);
+		t=c&(XES^b.XES);
+		XES^=t;
+		b.XES^=t;
+	}
+
+/* copy FPs depending on d */
+	public void cmove(FP b,int d)
+	{
+		x.cmove(b.x,d);
+		XES^=(XES^b.XES)&(-d);
+
+	}
+
+/* this*=b mod Modulus */
+	public void mul(FP b)
+	{
+		if ((long)XES*b.XES>(long)FEXCESS) reduce();
+
+		DBIG d=BIG.mul(x,b.x);
+		x.copy(mod(d));
+		XES=2;
+	}
+
+/* this*=c mod Modulus, where c is a small int */
+	public void imul(int c)
+	{
+//		norm();
+		boolean s=false;
+		if (c<0)
+		{
+			c=-c;
+			s=true;
+		}
+
+		if (MODTYPE==PSEUDO_MERSENNE || MODTYPE==GENERALISED_MERSENNE)
+		{
+			DBIG d=x.pxmul(c);
+			x.copy(mod(d));
+			XES=2;
+		}
+		else
+		{
+			if (XES*c<=FEXCESS)
+			{
+				x.pmul(c);
+				XES*=c;
+			}
+			else
+			{  // this is not good
+				FP n=new FP(c);
+				mul(n);
+			}
+		}
+		
+/*
+		if (c<=BIG.NEXCESS && XES*c<=FEXCESS)
+		{
+			x.imul(c);
+			XES*=c;
+			x.norm();
+		}
+		else
+		{
+			DBIG d=x.pxmul(c);
+			x.copy(mod(d));
+			XES=2;
+		}
+*/
+		if (s) {neg(); norm();}
+
+	}
+
+/* this*=this mod Modulus */
+	public void sqr()
+	{
+		DBIG d;
+		if ((long)XES*XES>(long)FEXCESS) reduce();
+
+		d=BIG.sqr(x);	
+		x.copy(mod(d));
+		XES=2;
+	}
+
+/* this+=b */
+	public void add(FP b) {
+		x.add(b.x);
+		XES+=b.XES;
+		if (XES>FEXCESS) reduce();
+	}
+
+// https://graphics.stanford.edu/~seander/bithacks.html
+// constant time log to base 2 (or number of bits in)
+
+	private static int logb2(int v)
+	{
+		int r;
+		v |= v >>> 1;
+		v |= v >>> 2;
+		v |= v >>> 4;
+		v |= v >>> 8;
+		v |= v >>> 16;
+
+		v = v - ((v >>> 1) & 0x55555555);                  
+		v = (v & 0x33333333) + ((v >>> 2) & 0x33333333);  
+		r = ((v + (v >>> 4) & 0xF0F0F0F) * 0x1010101) >>> 24; 
+		return r;
+	}
+
+/* this = -this mod Modulus */
+	public void neg()
+	{
+		int sb;
+		BIG m=new BIG(ROM.Modulus);
+
+		sb=logb2(XES-1);
+		m.fshl(sb);
+		x.rsub(m);		
+
+		XES=(1<<sb);
+		if (XES>FEXCESS) reduce();
+	}
+
+/* this-=b */
+	public void sub(FP b)
+	{
+		FP n=new FP(b);
+		n.neg();
+		this.add(n);
+	}
+
+	public void rsub(FP b)
+	{
+		FP n=new FP(this);
+		n.neg();
+		this.copy(b);
+		this.add(n);
+	}
+
+/* this/=2 mod Modulus */
+	public void div2()
+	{
+		if (x.parity()==0)
+			x.fshr(1);
+		else
+		{
+			x.add(new BIG(ROM.Modulus));
+			x.norm();
+			x.fshr(1);
+		}
+	}
+
+/* this=1/this mod Modulus */
+	public void inverse()
+	{
+/*
+		BIG r=redc();
+		r.invmodp(p);
+		x.copy(r);
+		nres();
+*/
+		BIG m2=new BIG(ROM.Modulus);
+		m2.dec(2); m2.norm();
+		copy(pow(m2));
+
+	}
+
+/* return TRUE if this==a */
+	public boolean equals(FP a)
+	{
+		FP f=new FP(this);
+		FP s=new FP(a);
+		f.reduce();
+		s.reduce();
+		if (BIG.comp(f.x,s.x)==0) return true;
+		return false;
+	}
+
+/* reduce this mod Modulus */
+	public void reduce()
+	{
+		x.mod(new BIG(ROM.Modulus));
+		XES=1;
+	}
+
+	public FP pow(BIG e)
+	{
+		byte[] w=new byte[1+(BIG.NLEN*BIG.BASEBITS+3)/4];
+		FP [] tb=new FP[16];
+		BIG t=new BIG(e);
+		t.norm();
+		int nb=1+(t.nbits()+3)/4;
+
+		for (int i=0;i<nb;i++)
+		{
+			int lsbs=t.lastbits(4);
+			t.dec(lsbs);
+			t.norm();
+			w[i]=(byte)lsbs;
+			t.fshr(4);
+		}
+		tb[0]=new FP(1);
+		tb[1]=new FP(this);
+		for (int i=2;i<16;i++)
+		{
+			tb[i]=new FP(tb[i-1]);
+			tb[i].mul(this);
+		}
+		FP r=new FP(tb[w[nb-1]]);
+		for (int i=nb-2;i>=0;i--)
+		{
+			r.sqr();
+			r.sqr();
+			r.sqr();
+			r.sqr();
+			r.mul(tb[w[i]]);
+		}
+		r.reduce();
+		return r;
+	}
+
+/* return this^e mod Modulus 
+	public FP pow(BIG e)
+	{
+		int bt;
+		FP r=new FP(1);
+		e.norm();
+		x.norm();
+		FP m=new FP(this);
+		while (true)
+		{
+			bt=e.parity();
+			e.fshr(1);
+			if (bt==1) r.mul(m);
+			if (e.iszilch()) break;
+			m.sqr();
+		}
+		r.x.mod(p);
+		return r;
+	} */
+
+/* return sqrt(this) mod Modulus */
+	public FP sqrt()
+	{
+		reduce();
+		BIG b=new BIG(ROM.Modulus);
+		if (MOD8==5)
+		{
+			b.dec(5); b.norm(); b.shr(3);
+			FP i=new FP(this); i.x.shl(1);
+			FP v=i.pow(b);
+			i.mul(v); i.mul(v);
+			i.x.dec(1);
+			FP r=new FP(this);
+			r.mul(v); r.mul(i); 
+			r.reduce();
+			return r;
+		}
+		else
+		{
+			b.inc(1); b.norm(); b.shr(2);
+			return pow(b);
+		}
+	}
+
+/* return jacobi symbol (this/Modulus) */
+	public int jacobi()
+	{
+		BIG w=redc();
+		return w.jacobi(new BIG(ROM.Modulus));
+	}
+/*
+	public static void main(String[] args) {
+		BIG m=new BIG(ROM.Modulus);
+		BIG x=new BIG(3);
+		BIG e=new BIG(m);
+		e.dec(1);
+
+		System.out.println("m= "+m.nbits());	
+
+
+		BIG r=x.powmod(e,m);
+
+		System.out.println("m= "+m.toString());	
+		System.out.println("r= "+r.toString());	
+
+		BIG.cswap(m,r,0);
+
+		System.out.println("m= "+m.toString());	
+		System.out.println("r= "+r.toString());	
+
+//		FP y=new FP(3);
+//		FP s=y.pow(e);
+//		System.out.println("s= "+s.toString());	
+
+	} */
+}
diff --git a/src/main/java/org/apache/milagro/amcl/ANSSI/ROM.java b/src/main/java/org/apache/milagro/amcl/ANSSI/ROM.java
new file mode 100644
index 0000000..5f0510f
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/ANSSI/ROM.java
@@ -0,0 +1,43 @@
+/*
+	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.
+*/
+
+/* Fixed Data in ROM - Field and Curve parameters */
+
+
+package org.apache.milagro.amcl.ANSSI;
+
+public class ROM
+{
+
+// Base Bits= 56
+	public static final long[] Modulus= {0xFCF353D86E9C03L,0xADBCABC8CA6DE8L,0xE8CE42435B3961L,0xB3AD58F10126DL,0xF1FD178CL};
+	public static final long[] R2modp= {0x18D2374288CC9CL,0x4929E67646BD2BL,0x220E6C1D6F7F2DL,0x751B1FDABCE02EL,0xE7401B78L};
+	public static final long MConst= 0x97483A164E1155L;
+
+	public static final int CURVE_Cof_I= 1;
+	public static final long[] CURVE_Cof= {0x1L,0x0L,0x0L,0x0L,0x0L};
+	public static final int CURVE_A= -3;
+	public static final int CURVE_B_I= 0;
+	public static final long[] CURVE_B= {0x75ED967B7BB73FL,0xC9AE4B1A18030L,0x754A44C00FDFECL,0x5428A9300D4ABAL,0xEE353FCAL};
+	public static final long[] CURVE_Order= {0xFDD459C6D655E1L,0x67E140D2BF941FL,0xE8CE42435B53DCL,0xB3AD58F10126DL,0xF1FD178CL};
+	public static final long[] CURVE_Gx= {0xC97A2DD98F5CFFL,0xD2DCAF98B70164L,0x4749D423958C27L,0x56C139EB31183DL,0xB6B3D4C3L};
+	public static final long[] CURVE_Gy= {0x115A1554062CFBL,0xC307E8E4C9E183L,0xF0F3ECEF8C2701L,0xC8B204911F9271L,0x6142E0F7L};
+
+}
+
diff --git a/src/main/java/org/apache/milagro/amcl/BLS24/BIG.java b/src/main/java/org/apache/milagro/amcl/BLS24/BIG.java
new file mode 100644
index 0000000..df34ec3
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS24/BIG.java
@@ -0,0 +1,917 @@
+/*
+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.
+*/
+
+/* AMCL BIG number class */ 
+
+package org.apache.milagro.amcl.BLS24;
+import org.apache.milagro.amcl.RAND;
+
+public class BIG {
+
+	public static final int CHUNK=64; /* Set word size */
+
+	public static final int MODBYTES=60; //(1+(MODBITS-1)/8);
+	public static final int BASEBITS=56; 
+
+	public static final int NLEN=(1+((8*MODBYTES-1)/BASEBITS));
+	public static final int DNLEN=2*NLEN;
+	public static final long BMASK=(((long)1<<BASEBITS)-1);
+
+	public static final int HBITS=BASEBITS/2;
+	public static final long HMASK=(((long)1<<HBITS)-1);
+	public static final int NEXCESS = ((int)1<<(CHUNK-BASEBITS-1));
+	public static final int BIGBITS=(MODBYTES*8);
+
+
+
+	protected long[] w=new long[NLEN];
+/* Constructors */
+	public BIG()
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=0;
+	}
+
+	public BIG(int x)
+	{
+		w[0]=x;
+		for (int i=1;i<NLEN;i++)
+			w[i]=0;
+	}
+
+	public BIG(BIG x)
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i];
+	}
+
+	public BIG(DBIG x)
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i];
+	}
+
+	public BIG(long[] x)
+	{
+			for (int i=0;i<NLEN;i++)
+				w[i]=x[i];
+	}
+
+	public long get(int i)
+	{
+		return w[i];
+	}
+
+	public void set(int i,long x)
+	{
+		w[i]=x;
+	} 
+
+
+/* Conditional swap of two bigs depending on d using XOR - no branches */
+	public void cswap(BIG b,int d)
+	{
+		int i;
+		long t,c=(long)d;
+		c=~(c-1);
+
+		for (i=0;i<NLEN;i++)
+		{
+			t=c&(w[i]^b.w[i]);
+			w[i]^=t;
+			b.w[i]^=t;
+		}
+	}
+
+	public void cmove(BIG g,int d)
+	{
+		int i;
+		long t,b=-d;
+
+		for (i=0;i<NLEN;i++)
+		{
+			w[i]^=(w[i]^g.w[i])&b;
+		}
+	}
+
+    public static long cast_to_chunk(int x)
+	{
+		return (long)x;
+	}
+
+/* normalise BIG - force all digits < 2^BASEBITS */
+	public long norm() {
+		long d,carry=0;
+		for (int i=0;i<NLEN-1;i++)
+		{
+			d=w[i]+carry;
+			w[i]=d&BMASK;
+			carry=(d>>BASEBITS);
+		}
+		w[NLEN-1]=(w[NLEN-1]+carry);
+		return (long)(w[NLEN-1]>>((8*MODBYTES)%BASEBITS));  
+	}
+
+/* return number of bits */
+	public int nbits() {
+		BIG t=new BIG(this);
+		int bts,k=NLEN-1;
+		long c;
+		t.norm();
+		while (k>=0 && t.w[k]==0) k--;
+		if (k<0) return 0;
+		bts=BASEBITS*k;
+		c=t.w[k];
+		while (c!=0) {c/=2; bts++;}
+		return bts;
+	}
+
+	public String toRawString()
+	{
+		BIG b=new BIG(this);
+		String s="(";
+		for (int i=0;i<NLEN-1;i++)
+		{
+			s+=Long.toHexString(b.w[i]); s+=",";
+		}
+		s+=Long.toHexString(b.w[NLEN-1]); s+=")";
+		return s;
+	}
+
+/* Convert to Hex String */
+	public String toString() {
+		BIG b;
+		String s="";
+		int len=nbits();
+
+		if (len%4==0) len/=4;
+		else {len/=4; len++;}
+		if (len<MODBYTES*2) len=MODBYTES*2;
+
+		for (int i=len-1;i>=0;i--)
+		{
+			b=new BIG(this);
+			b.shr(i*4);
+			s+=Long.toHexString(b.w[0]&15);
+		}
+		return s;
+	}
+
+/* set this[i]+=x*y+c, and return high part */
+
+	public static long[] muladd(long a,long b,long c,long r)
+	{
+		long x0,x1,y0,y1;
+		long[] tb=new long[2];
+		x0=a&HMASK;
+		x1=(a>>HBITS);
+		y0=b&HMASK;
+		y1=(b>>HBITS);
+		long bot=x0*y0;
+		long top=x1*y1;
+		long mid=x0*y1+x1*y0;
+		x0=mid&HMASK;
+		x1=(mid>>HBITS);
+		bot+=x0<<HBITS; bot+=c; bot+=r;
+		top+=x1;
+		long carry=bot>>BASEBITS;
+		bot&=BMASK;
+		top+=carry;
+		tb[0]=top;
+		tb[1]=bot;
+		return tb;
+	}
+
+/* this*=x, where x is >NEXCESS */
+	public long pmul(int c)
+	{
+		long ak,carry=0;
+		long[] cr=new long[2];
+
+		for (int i=0;i<NLEN;i++)
+		{
+			ak=w[i];
+			w[i]=0;
+
+			cr=muladd(ak,(long)c,carry,w[i]);
+			carry=cr[0];
+			w[i]=cr[1];
+
+		}
+		return carry;
+	}
+
+/* return this*c and catch overflow in DBIG */
+	public DBIG pxmul(int c)
+	{
+		DBIG m=new DBIG(0);	
+		long[] cr=new long[2];
+		long carry=0;
+		for (int j=0;j<NLEN;j++)
+		{
+			cr=muladd(w[j],(long)c,carry,m.w[j]);
+			carry=cr[0];
+			m.w[j]=cr[1];
+		}
+		m.w[NLEN]=carry;		
+		return m;
+	}
+
+/* divide by 3 */
+	public int div3()
+	{	
+		long ak,base,carry=0;
+		norm();
+		base=((long)1<<BASEBITS);
+		for (int i=NLEN-1;i>=0;i--)
+		{
+			ak=(carry*base+w[i]);
+			w[i]=ak/3;
+			carry=ak%3;
+		}
+		return (int)carry;
+	}
+
+/* return a*b where result fits in a BIG */
+	public static BIG smul(BIG a,BIG b)
+	{
+		long carry;
+		long[] cr=new long[2];
+		BIG c=new BIG(0);
+		for (int i=0;i<NLEN;i++)
+		{
+			carry=0;
+			for (int j=0;j<NLEN;j++)
+				if (i+j<NLEN)
+				{
+					cr=muladd(a.w[i],b.w[j],carry,c.w[i+j]);
+					carry=cr[0];
+					c.w[i+j]=cr[1];
+				}
+		}
+		return c;
+	}
+
+/* return a*b as DBIG */
+/* Inputs must be normed */
+	public static DBIG mul(BIG a,BIG b)
+	{
+		DBIG c=new DBIG(0);
+		long carry;
+		long[] cr=new long[2];
+
+		for (int i=0;i<NLEN;i++)
+		{
+			carry=0;
+			for (int j=0;j<NLEN;j++)
+			{
+				cr=muladd(a.w[i],b.w[j],carry,c.w[i+j]);
+				carry=cr[0];
+				c.w[i+j]=cr[1];
+			}
+			c.w[NLEN+i]=carry;
+		}
+
+		return c;
+	}
+
+/* return a^2 as DBIG */
+/* Input must be normed */
+	public static DBIG sqr(BIG a)
+	{
+		DBIG c=new DBIG(0);
+		long carry;
+		long[] cr=new long[2];
+
+		for (int i=0;i<NLEN;i++)
+		{
+			carry=0;
+			for (int j=i+1;j<NLEN;j++)
+			{
+				cr=muladd(2*a.w[i],a.w[j],carry,c.w[i+j]);
+				carry=cr[0];
+				c.w[i+j]=cr[1];
+			}
+			c.w[NLEN+i]=carry;
+		}
+
+		for (int i=0;i<NLEN;i++)
+		{
+			cr=muladd(a.w[i],a.w[i],0,c.w[2*i]);
+			c.w[2*i+1]+=cr[0];
+			c.w[2*i]=cr[1];
+		}
+		c.norm(); 
+		return c;
+	}
+
+	static BIG monty(BIG md,long MC,DBIG d)
+	{
+		BIG b;
+		long m,carry;
+		long[] cr=new long[2];
+		for (int i=0;i<NLEN;i++) 
+		{
+			if (MC==-1) m=(-d.w[i])&BMASK;
+			else
+			{
+				if (MC==1) m=d.w[i];
+				else m=(MC*d.w[i])&BMASK;
+			}
+
+			carry=0;
+			for (int j=0;j<NLEN;j++)
+			{
+				cr=muladd(m,md.w[j],carry,d.w[i+j]);
+				carry=cr[0];
+				d.w[i+j]=cr[1];
+			}
+			d.w[NLEN+i]+=carry;
+		}
+
+		b=new BIG(0);
+		for (int i=0;i<NLEN;i++ )
+			b.w[i]=d.w[NLEN+i];
+		b.norm();
+		return b;		
+	}
+
+
+
+/****************************************************************************/
+
+	public void xortop(long x)
+	{
+		w[NLEN-1]^=x;
+	}
+
+/* set x = x mod 2^m */
+	public void mod2m(int m)
+	{
+		int i,wd,bt;
+		wd=m/BASEBITS;
+		bt=m%BASEBITS;
+		w[wd]&=((cast_to_chunk(1)<<bt)-1);
+		for (i=wd+1;i<NLEN;i++) w[i]=0;
+	}
+
+/* return n-th bit */
+	public int bit(int n)
+	{
+		if ((w[n/BASEBITS]&(cast_to_chunk(1)<<(n%BASEBITS)))>0) return 1;
+		else return 0;
+	}
+
+/* Shift right by less than a word */
+	public int fshr(int k) {
+		int r=(int)(w[0]&((cast_to_chunk(1)<<k)-1)); /* shifted out part */
+		for (int i=0;i<NLEN-1;i++)
+			w[i]=(w[i]>>k)|((w[i+1]<<(BASEBITS-k))&BMASK);
+		w[NLEN-1]=w[NLEN-1]>>k;
+		return r;
+	}
+
+/* Shift right by less than a word */
+	public int fshl(int k) {
+		w[NLEN-1]=((w[NLEN-1]<<k))|(w[NLEN-2]>>(BASEBITS-k));
+		for (int i=NLEN-2;i>0;i--)
+			w[i]=((w[i]<<k)&BMASK)|(w[i-1]>>(BASEBITS-k));
+		w[0]=(w[0]<<k)&BMASK; 
+		return (int)(w[NLEN-1]>>((8*MODBYTES)%BASEBITS)); /* return excess - only used in FF.java */
+	}
+
+/* test for zero */
+	public boolean iszilch() {
+		for (int i=0;i<NLEN;i++)
+			if (w[i]!=0) return false;
+		return true; 
+	}
+
+/* set to zero */
+	public void zero()
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=0;
+	}
+
+/* set to one */
+	public void one()
+	{
+		w[0]=1;
+		for (int i=1;i<NLEN;i++)
+			w[i]=0;
+	}
+
+/* Test for equal to one */
+	public boolean isunity()
+	{
+		for (int i=1;i<NLEN;i++)
+			if (w[i]!=0) return false;
+		if (w[0]!=1) return false;
+		return true;
+	}
+
+/* Copy from another BIG */
+	public void copy(BIG x)
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i];
+	}
+
+	public void copy(DBIG x)
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i];
+	}
+
+/* general shift right */
+	public void shr(int k) {
+		int n=k%BASEBITS;
+		int m=k/BASEBITS;	
+		for (int i=0;i<NLEN-m-1;i++)
+			w[i]=(w[m+i]>>n)|((w[m+i+1]<<(BASEBITS-n))&BMASK);
+		if (NLEN>m) w[NLEN-m-1]=w[NLEN-1]>>n;
+		for (int i=NLEN-m;i<NLEN;i++) w[i]=0;
+	}
+
+/* general shift left */
+	public void shl(int k) {
+		int n=k%BASEBITS;
+		int m=k/BASEBITS;
+
+		w[NLEN-1]=((w[NLEN-1-m]<<n));
+		if (NLEN>=m+2) w[NLEN-1]|=(w[NLEN-m-2]>>(BASEBITS-n));
+
+		for (int i=NLEN-2;i>m;i--)
+			w[i]=((w[i-m]<<n)&BMASK)|(w[i-m-1]>>(BASEBITS-n));
+		w[m]=(w[0]<<n)&BMASK;
+		for (int i=0;i<m;i++) w[i]=0;
+	}
+
+/* return this+x */
+	public BIG plus(BIG x) {
+		BIG s=new BIG(0);
+		for (int i=0;i<NLEN;i++)
+			s.w[i]=w[i]+x.w[i];
+		return s;
+	}
+
+/* this+=x */
+	public void add(BIG x) {
+		for (int i=0;i<NLEN;i++)
+			w[i]+=x.w[i];
+	}
+
+/* this|=x */
+	public void or(BIG x) {
+		for (int i=0;i<NLEN;i++)
+			w[i]|=x.w[i];
+	}
+
+
+/* this+=x, where x is int */
+	public void inc(int x) {
+		norm();
+		w[0]+=x;
+	}
+
+/* this+=x, where x is long */
+	public void incl(long x) {
+		norm();
+		w[0]+=x;
+	}	
+
+/* return this.x */
+	public BIG minus(BIG x) {
+		BIG d=new BIG(0);
+		for (int i=0;i<NLEN;i++)
+			d.w[i]=w[i]-x.w[i];
+		return d;
+	}
+
+/* this-=x */
+	public void sub(BIG x) {
+		for (int i=0;i<NLEN;i++)
+			w[i]-=x.w[i];
+	}
+
+/* reverse subtract this=x-this */
+	public void rsub(BIG x) {
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i]-w[i];
+	}
+
+/* this-=x where x is int */
+	public void dec(int x) {
+		norm();
+		w[0]-=x;
+	}
+
+/* this*=x, where x is small int<NEXCESS */
+	public void imul(int c)
+	{
+		for (int i=0;i<NLEN;i++) w[i]*=c;
+	}
+
+/* convert this BIG to byte array */
+	public void tobytearray(byte[] b,int n)
+	{
+		
+		BIG c=new BIG(this);
+		c.norm();
+
+		for (int i=MODBYTES-1;i>=0;i--)
+		{
+			b[i+n]=(byte)c.w[0];
+			c.fshr(8);
+		}
+	}
+
+/* convert from byte array to BIG */
+	public static BIG frombytearray(byte[] b,int n)
+	{
+		BIG m=new BIG(0);
+
+		for (int i=0;i<MODBYTES;i++)
+		{
+			m.fshl(8); m.w[0]+=(int)b[i+n]&0xff;
+			//m.inc((int)b[i]&0xff);
+		}
+		return m; 
+	}
+
+	public void toBytes(byte[] b)
+	{
+		tobytearray(b,0);
+	}
+
+	public static BIG fromBytes(byte[] b)
+	{
+		return frombytearray(b,0);
+	}
+
+/* Compare a and b, return 0 if a==b, -1 if a<b, +1 if a>b. Inputs must be normalised */
+	public static int comp(BIG a,BIG b)
+	{
+		for (int i=NLEN-1;i>=0;i--)
+		{
+			if (a.w[i]==b.w[i]) continue;
+			if (a.w[i]>b.w[i]) return 1;
+			else  return -1;
+		}
+		return 0;
+	}
+
+/* Arazi and Qi inversion mod 256 */
+	public static int invmod256(int a)
+	{
+		int U,t1,t2,b,c;
+		t1=0;
+		c=(a>>1)&1;  
+		t1+=c;
+		t1&=1;
+		t1=2-t1;
+		t1<<=1;
+		U=t1+1;
+
+// i=2
+		b=a&3;
+		t1=U*b; t1>>=2;
+		c=(a>>2)&3;
+		t2=(U*c)&3;
+		t1+=t2;
+		t1*=U; t1&=3;
+		t1=4-t1;
+		t1<<=2;
+		U+=t1;
+
+// i=4
+		b=a&15;
+		t1=U*b; t1>>=4;
+		c=(a>>4)&15;
+		t2=(U*c)&15;
+		t1+=t2;
+		t1*=U; t1&=15;
+		t1=16-t1;
+		t1<<=4;
+		U+=t1;
+
+		return U;
+	}
+
+/* a=1/a mod 2^256. This is very fast! */
+	public void invmod2m()
+	{
+		int i;
+		BIG U=new BIG(0);
+		BIG b=new BIG(0);
+		BIG c=new BIG(0);
+
+		U.inc(invmod256(lastbits(8)));
+
+		for (i=8;i<BIGBITS;i<<=1)
+		{
+			U.norm();
+			b.copy(this); b.mod2m(i);
+			BIG t1=BIG.smul(U,b); 
+			t1.shr(i);
+
+			c.copy(this); c.shr(i); c.mod2m(i);
+			BIG t2=BIG.smul(U,c); t2.mod2m(i);
+
+			t1.add(t2);
+			t1.norm();
+			b=BIG.smul(t1,U); t1.copy(b);
+			t1.mod2m(i);
+
+			t2.one(); t2.shl(i); t1.rsub(t2); t1.norm();
+
+			t1.shl(i);
+			U.add(t1);
+		}
+		U.mod2m(BIGBITS);
+		copy(U);
+		norm();
+	}
+
+/* reduce this mod m */
+	public void mod(BIG m1)
+	{
+		int k=0;  
+		BIG r=new BIG(0);
+		BIG m=new BIG(m1);
+
+		norm();
+		if (comp(this,m)<0) return;
+		do
+		{
+			m.fshl(1);
+			k++;
+		} while (comp(this,m)>=0);
+
+		while (k>0)
+		{
+			m.fshr(1);
+
+			r.copy(this);
+			r.sub(m);
+			r.norm();
+			cmove(r,(int)(1-((r.w[NLEN-1]>>(CHUNK-1))&1)));
+			k--;
+		}
+	}
+
+/* divide this by m */
+	public void div(BIG m1)
+	{
+		int d,k=0;
+		norm();
+		BIG e=new BIG(1);
+		BIG m=new BIG(m1);
+		BIG b=new BIG(this);
+		BIG r=new BIG(0);
+		zero();
+
+		while (comp(b,m)>=0)
+		{
+			e.fshl(1);
+			m.fshl(1);
+			k++;
+		}
+
+		while (k>0)
+		{
+			m.fshr(1);
+			e.fshr(1);
+
+			r.copy(b);
+			r.sub(m);
+			r.norm();
+			d=(int)(1-((r.w[NLEN-1]>>(CHUNK-1))&1));
+			b.cmove(r,d);
+			r.copy(this);
+			r.add(e);
+			r.norm();
+			cmove(r,d);
+			k--;
+		}
+	}
+
+/* return parity */
+	public int parity()
+	{
+		return (int)(w[0]%2);
+	}
+
+/* return n last bits */
+	public int lastbits(int n)
+	{
+		int msk=(1<<n)-1;
+		norm();
+		return ((int)w[0])&msk;
+	}
+
+/* get 8*MODBYTES size random number */
+	public static BIG random(RAND rng)
+	{
+		BIG m=new BIG(0);
+		int i,b,j=0,r=0;
+
+/* generate random BIG */ 
+		for (i=0;i<8*MODBYTES;i++)   
+		{
+			if (j==0) r=rng.getByte();
+			else r>>=1;
+
+			b=r&1;
+			m.shl(1); m.w[0]+=b;// m.inc(b);
+			j++; j&=7; 
+		}
+		return m;
+	}
+
+/* Create random BIG in portable way, one bit at a time */
+	public static BIG randomnum(BIG q,RAND rng) 
+	{
+		DBIG d=new DBIG(0);
+		int i,b,j=0,r=0;
+		for (i=0;i<2*q.nbits();i++)
+		{
+			if (j==0) r=rng.getByte();
+			else r>>=1;
+
+			b=r&1;
+			d.shl(1); d.w[0]+=b;// m.inc(b);
+			j++; j&=7; 
+		}
+		BIG m=d.mod(q);
+		return m;
+	}
+
+/* return a*b mod m */
+	public static BIG modmul(BIG a1,BIG b1,BIG m)
+	{
+		BIG a=new BIG(a1);
+		BIG b=new BIG(b1);
+		a.mod(m);
+		b.mod(m);
+		DBIG d=mul(a,b);
+		return d.mod(m);
+	}
+
+/* return a^2 mod m */
+	public static BIG modsqr(BIG a1,BIG m)
+	{
+		BIG a=new BIG(a1);
+		a.mod(m);
+		DBIG d=sqr(a);
+		return d.mod(m);
+	}
+
+/* return -a mod m */
+	public static BIG modneg(BIG a1,BIG m)
+	{
+		BIG a=new BIG(a1);
+		a.mod(m);
+		return m.minus(a);
+	}
+
+/* return this^e mod m */
+	public BIG powmod(BIG e1,BIG m)
+	{
+		BIG e=new BIG(e1);
+		int bt;
+		norm();
+		e.norm();
+		BIG a=new BIG(1);
+		BIG z=new BIG(e);
+		BIG s=new BIG(this);
+		while (true)
+		{
+			bt=z.parity();
+			z.fshr(1);
+			if (bt==1) a=modmul(a,s,m);
+			if (z.iszilch()) break;
+			s=modsqr(s,m);
+		}
+		return a;
+	}
+
+/* Jacobi Symbol (this/p). Returns 0, 1 or -1 */
+	public int jacobi(BIG p)
+	{
+		int n8,k,m=0;
+		BIG t=new BIG(0);
+		BIG x=new BIG(0);
+		BIG n=new BIG(0);
+		BIG zilch=new BIG(0);
+		BIG one=new BIG(1);
+		if (p.parity()==0 || comp(this,zilch)==0 || comp(p,one)<=0) return 0;
+		norm();
+		x.copy(this);
+		n.copy(p);
+		x.mod(p);
+
+		while (comp(n,one)>0)
+		{
+			if (comp(x,zilch)==0) return 0;
+			n8=n.lastbits(3);
+			k=0;
+			while (x.parity()==0)
+			{
+				k++;
+				x.shr(1);
+			}
+			if (k%2==1) m+=(n8*n8-1)/8;
+			m+=(n8-1)*(x.lastbits(2)-1)/4;
+			t.copy(n);
+			t.mod(x);
+			n.copy(x);
+			x.copy(t);
+			m%=2;
+
+		}
+		if (m==0) return 1;
+		else return -1;
+	}
+
+/* this=1/this mod p. Binary method */
+	public void invmodp(BIG p)
+	{
+		mod(p);
+		BIG u=new BIG(this);
+		BIG v=new BIG(p);
+		BIG x1=new BIG(1);
+		BIG x2=new BIG(0);
+		BIG t=new BIG(0);
+		BIG one=new BIG(1);
+
+		while (comp(u,one)!=0 && comp(v,one)!=0)
+		{
+			while (u.parity()==0)
+			{
+				u.fshr(1);
+				if (x1.parity()!=0)
+				{
+					x1.add(p);
+					x1.norm();
+				}
+				x1.fshr(1);
+			}
+			while (v.parity()==0)
+			{
+				v.fshr(1);
+				if (x2.parity()!=0)
+				{
+					x2.add(p);
+					x2.norm();
+				}
+				x2.fshr(1);
+			}
+			if (comp(u,v)>=0)
+			{
+				u.sub(v);
+				u.norm();
+				if (comp(x1,x2)>=0) x1.sub(x2);
+				else
+				{
+					t.copy(p);
+					t.sub(x2);
+					x1.add(t);
+				}
+				x1.norm();
+			}
+			else
+			{
+				v.sub(u);
+				v.norm();
+				if (comp(x2,x1)>=0) x2.sub(x1);
+				else
+				{
+					t.copy(p);
+					t.sub(x1);
+					x2.add(t);
+				}
+				x2.norm();
+			}
+		}
+		if (comp(u,one)==0) copy(x1);
+		else copy(x2);
+	}
+}
diff --git a/src/main/java/org/apache/milagro/amcl/BLS24/DBIG.java b/src/main/java/org/apache/milagro/amcl/BLS24/DBIG.java
new file mode 100644
index 0000000..3e2c9f0
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS24/DBIG.java
@@ -0,0 +1,279 @@
+/*
+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.
+*/
+
+/* AMCL double length DBIG number class */ 
+
+package org.apache.milagro.amcl.BLS24;
+
+public class DBIG {
+	protected long[] w=new long[BIG.DNLEN];
+
+/* normalise this */
+	public void norm() {
+		long d,carry=0;
+		for (int i=0;i<BIG.DNLEN-1;i++)
+		{
+			d=w[i]+carry;
+			carry=d>>BIG.BASEBITS;
+			w[i]=d&BIG.BMASK;
+		}
+		w[BIG.DNLEN-1]=(w[BIG.DNLEN-1]+carry);
+	}
+
+
+/*
+	public String toRawString()
+	{
+		DBIG b=new DBIG(this);
+		String s="(";
+		for (int i=0;i<BIG.DNLEN-1;i++)
+		{
+			s+=Long.toHexString(b.w[i]); s+=",";
+		}
+		s+=Long.toHexString(b.w[BIG.DNLEN-1]); s+=")";
+		return s;
+	}
+*/
+
+/* split DBIG at position n, return higher half, keep lower half */
+	public BIG split(int n)
+	{
+		BIG t=new BIG(0);
+		int m=n%BIG.BASEBITS;
+		long nw,carry=w[BIG.DNLEN-1]<<(BIG.BASEBITS-m);
+
+		for (int i=BIG.DNLEN-2;i>=BIG.NLEN-1;i--)
+		{
+			nw=(w[i]>>m)|carry;
+			carry=(w[i]<<(BIG.BASEBITS-m))&BIG.BMASK;
+			t.w[i-BIG.NLEN+1]=nw;
+			//t.set(i-BIG.NLEN+1,nw);
+		}
+		w[BIG.NLEN-1]&=(((long)1<<m)-1);
+		return t;
+	}
+
+/****************************************************************************/
+
+/* return number of bits in this */
+	public int nbits() {
+		int bts,k=BIG.DNLEN-1;
+		long c;
+		norm();
+		while (w[k]==0 && k>=0) k--;
+		if (k<0) return 0;
+		bts=BIG.BASEBITS*k;
+		c=w[k];
+		while (c!=0) {c/=2; bts++;}
+		return bts;
+	}
+
+/* convert this to string */
+	public String toString() {
+		DBIG b;
+		String s="";
+		int len=nbits();
+		if (len%4==0) len>>=2; //len/=4;
+		else {len>>=2; len++;}
+
+		for (int i=len-1;i>=0;i--)
+		{
+			b=new DBIG(this);
+			b.shr(i*4);
+			s+=Integer.toHexString((int)(b.w[0]&15));
+		}
+		return s;
+	}
+
+	public void cmove(DBIG g,int d)
+	{
+		int i;
+		for (i=0;i<BIG.DNLEN;i++)
+		{
+			w[i]^=(w[i]^g.w[i])&BIG.cast_to_chunk(-d);
+		}
+	}
+
+/* Constructors */
+	public DBIG(int x)
+	{
+		w[0]=x;
+		for (int i=1;i<BIG.DNLEN;i++)
+			w[i]=0;
+	}
+
+	public DBIG(DBIG x)
+	{
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]=x.w[i];
+	}
+
+	public DBIG(BIG x)
+	{
+		for (int i=0;i<BIG.NLEN-1;i++)
+			w[i]=x.w[i]; //get(i);
+
+		w[BIG.NLEN-1]=x.w[(BIG.NLEN-1)]&BIG.BMASK; /* top word normalized */
+		w[BIG.NLEN]=(x.w[(BIG.NLEN-1)]>>BIG.BASEBITS);
+
+		for (int i=BIG.NLEN+1;i<BIG.DNLEN;i++) w[i]=0;
+	}
+
+/* Copy from another DBIG */
+	public void copy(DBIG x)
+	{
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]=x.w[i];
+	}
+
+/* Copy into upper part */
+	public void ucopy(BIG x)
+	{
+		for (int i=0;i<BIG.NLEN;i++)
+			w[i]=0;
+		for (int i=BIG.NLEN;i<BIG.DNLEN;i++)
+			w[i]=x.w[i-BIG.NLEN];
+	}
+
+/* test this=0? */
+	public boolean iszilch() {
+		for (int i=0;i<BIG.DNLEN;i++)
+			if (w[i]!=0) return false;
+		return true; 
+	}
+
+/* shift this right by k bits */
+	public void shr(int k) {
+		int n=k%BIG.BASEBITS;
+		int m=k/BIG.BASEBITS;	
+		for (int i=0;i<BIG.DNLEN-m-1;i++)
+			w[i]=(w[m+i]>>n)|((w[m+i+1]<<(BIG.BASEBITS-n))&BIG.BMASK);
+		w[BIG.DNLEN-m-1]=w[BIG.DNLEN-1]>>n;
+		for (int i=BIG.DNLEN-m;i<BIG.DNLEN;i++) w[i]=0;
+	}
+
+/* shift this left by k bits */
+	public void shl(int k) {
+		int n=k%BIG.BASEBITS;
+		int m=k/BIG.BASEBITS;
+
+		w[BIG.DNLEN-1]=((w[BIG.DNLEN-1-m]<<n))|(w[BIG.DNLEN-m-2]>>(BIG.BASEBITS-n));
+		for (int i=BIG.DNLEN-2;i>m;i--)
+			w[i]=((w[i-m]<<n)&BIG.BMASK)|(w[i-m-1]>>(BIG.BASEBITS-n));
+		w[m]=(w[0]<<n)&BIG.BMASK; 
+		for (int i=0;i<m;i++) w[i]=0;
+	}
+
+/* this+=x */
+	public void add(DBIG x) {
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]+=x.w[i];	
+	}
+
+/* this-=x */
+	public void sub(DBIG x) {
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]-=x.w[i];
+	}
+
+	public void rsub(DBIG x) {
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]=x.w[i]-w[i];
+	}
+
+/* Compare a and b, return 0 if a==b, -1 if a<b, +1 if a>b. Inputs must be normalised */
+	public static int comp(DBIG a,DBIG b)
+	{
+		for (int i=BIG.DNLEN-1;i>=0;i--)
+		{
+			if (a.w[i]==b.w[i]) continue;
+			if (a.w[i]>b.w[i]) return 1;
+			else  return -1;
+		}
+		return 0;
+	}
+
+/* reduces this DBIG mod a BIG, and returns the BIG */
+	public BIG mod(BIG c)
+	{
+		int k=0;  
+		norm();
+		DBIG m=new DBIG(c);
+		DBIG r=new DBIG(0);
+
+		if (comp(this,m)<0) return new BIG(this);
+		
+		do
+		{
+			m.shl(1);
+			k++;
+		}
+		while (comp(this,m)>=0);
+
+		while (k>0)
+		{
+			m.shr(1);
+
+			r.copy(this);
+			r.sub(m);
+			r.norm();
+			cmove(r,(int)(1-((r.w[BIG.DNLEN-1]>>(BIG.CHUNK-1))&1)));
+
+			k--;
+		}
+		return new BIG(this);
+	}
+
+/* return this/c */
+	public BIG div(BIG c)
+	{
+		int d,k=0;
+		DBIG m=new DBIG(c);
+		DBIG dr=new DBIG(0);
+		BIG r=new BIG(0);
+		BIG a=new BIG(0);
+		BIG e=new BIG(1);
+		norm();
+
+		while (comp(this,m)>=0)
+		{
+			e.fshl(1);
+			m.shl(1);
+			k++;
+		}
+
+		while (k>0)
+		{
+			m.shr(1);
+			e.shr(1);
+
+			dr.copy(this);
+			dr.sub(m);
+			dr.norm();
+			d=(int)(1-((dr.w[BIG.DNLEN-1]>>(BIG.CHUNK-1))&1));
+			cmove(dr,d);
+			r.copy(a);
+			r.add(e);
+			r.norm();
+			a.cmove(r,d);
+			k--;
+		}
+		return a;
+	}
+}
diff --git a/src/main/java/org/apache/milagro/amcl/BLS24/ECDH.java b/src/main/java/org/apache/milagro/amcl/BLS24/ECDH.java
new file mode 100644
index 0000000..b0b19cd
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS24/ECDH.java
@@ -0,0 +1,594 @@
+/*
+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.
+*/
+
+/* Elliptic Curve API high-level functions  */
+
+package org.apache.milagro.amcl.BLS24;
+
+import org.apache.milagro.amcl.RAND;
+import org.apache.milagro.amcl.HASH256;
+import org.apache.milagro.amcl.HASH384;
+import org.apache.milagro.amcl.HASH512;
+import org.apache.milagro.amcl.AES;
+
+public final class ECDH {
+	public static final int INVALID_PUBLIC_KEY=-2;
+	public static final int ERROR=-3;
+	public static final int INVALID=-4;
+	public static final int EFS=BIG.MODBYTES;
+	public static final int EGS=BIG.MODBYTES;
+//	public static final int EAS=16;
+//	public static final int EBS=16;
+
+//	public static final int SHA256=32;
+//	public static final int SHA384=48;
+//	public static final int SHA512=64;
+
+
+//	public static final int HASH_TYPE=SHA512;
+
+
+/* Convert Integer to n-byte array */
+	public static byte[] inttoBytes(int n,int len)
+	{
+		int i;
+		byte[] b=new byte[len];
+
+		for (i=0;i<len;i++) b[i]=0;
+		i=len; 
+		while (n>0 && i>0)
+		{
+			i--;
+			b[i]=(byte)(n&0xff);
+			n/=256;
+		}	
+		return b;
+	}
+
+	public static byte[] hashit(int sha,byte[] A,int n,byte[] B,int pad)
+	{
+		byte[] R=null;
+
+		if (sha==ECP.SHA256)
+		{
+			HASH256 H=new HASH256();
+			H.process_array(A); if (n>0) H.process_num(n);
+			if (B!=null) H.process_array(B);
+			R=H.hash();
+		}
+		if (sha==ECP.SHA384)
+		{
+			HASH384 H=new HASH384();
+			H.process_array(A); if (n>0) H.process_num(n);
+			if (B!=null) H.process_array(B);
+			R=H.hash();
+		}
+		if (sha==ECP.SHA512)
+		{
+			HASH512 H=new HASH512();
+			H.process_array(A); if (n>0) H.process_num(n);
+			if (B!=null) H.process_array(B);
+			R=H.hash();
+		}
+		if (R==null) return null;
+
+		if (pad==0) return R;
+/* If pad>0 output is truncated or padded to pad bytes */
+		byte[] W=new byte[pad];
+		if (pad<=sha) 
+		{
+			for (int i=0;i<pad;i++) W[i]=R[i];
+		}
+		else
+		{
+			for (int i=0;i<sha;i++) W[i+pad-sha]=R[i];
+            for (int i=0;i<pad-sha;i++) W[i]=0;
+ 
+			//for (int i=0;i<sha;i++) W[i]=R[i];
+			//for (int i=sha;i<pad;i++) W[i]=0;
+		}
+		return W;
+	}
+
+/* Key Derivation Functions */
+/* Input octet Z */
+/* Output key of length olen */
+	public static byte[] KDF1(int sha,byte[] Z,int olen)
+	{
+/* NOTE: the parameter olen is the length of the output K in bytes */
+		int hlen=sha;
+		byte[] K=new byte[olen];
+		byte[] B;
+		int counter,cthreshold,k=0;
+    
+		for (int i=0;i<K.length;i++) K[i]=0;
+
+		cthreshold=olen/hlen; if (olen%hlen!=0) cthreshold++;
+
+		for (counter=0;counter<cthreshold;counter++)
+		{
+			B=hashit(sha,Z,counter,null,0);
+			if (k+hlen>olen) for (int i=0;i<olen%hlen;i++) K[k++]=B[i];
+			else for (int i=0;i<hlen;i++) K[k++]=B[i];
+		}
+		return K;
+	}
+
+	public static byte[] KDF2(int sha,byte[] Z,byte[] P,int olen)
+	{
+/* NOTE: the parameter olen is the length of the output k in bytes */
+		int hlen=sha;
+		byte[] K=new byte[olen];
+		byte[] B;
+		int counter,cthreshold,k=0;
+    
+		for (int i=0;i<K.length;i++) K[i]=0;
+
+		cthreshold=olen/hlen; if (olen%hlen!=0) cthreshold++;
+
+		for (counter=1;counter<=cthreshold;counter++)
+		{
+			B=hashit(sha,Z,counter,P,0);
+			if (k+hlen>olen) for (int i=0;i<olen%hlen;i++) K[k++]=B[i];
+			else for (int i=0;i<hlen;i++) K[k++]=B[i];
+		}
+
+		return K;
+	}
+
+/* Password based Key Derivation Function */
+/* Input password p, salt s, and repeat count */
+/* Output key of length olen */
+	public static byte[] PBKDF2(int sha,byte[] Pass,byte[] Salt,int rep,int olen)
+	{
+		int i,j,k,len,d,opt;
+		d=olen/sha; if (olen%sha!=0) d++;
+		byte[] F=new byte[sha];
+		byte[] U=new byte[sha];
+		byte[] S=new byte[Salt.length+4];
+
+		byte[] K=new byte[d*sha];
+		opt=0;
+
+		for (i=1;i<=d;i++)
+		{
+			for (j=0;j<Salt.length;j++) S[j]=Salt[j];
+			byte[] N=inttoBytes(i,4);
+			for (j=0;j<4;j++) S[Salt.length+j]=N[j];
+
+			HMAC(sha,S,Pass,F);
+
+			for (j=0;j<sha;j++) U[j]=F[j];
+			for (j=2;j<=rep;j++)
+			{
+				HMAC(sha,U,Pass,U);
+				for (k=0;k<sha;k++) F[k]^=U[k];
+			}
+			for (j=0;j<sha;j++) K[opt++]=F[j];
+		}
+		byte[] key=new byte[olen];
+		for (i=0;i<olen;i++) key[i]=K[i];
+		return key;
+	}
+
+/* Calculate HMAC of m using key k. HMAC is tag of length olen */
+	public static int HMAC(int sha,byte[] M,byte[] K,byte[] tag)
+	{
+	/* Input is from an octet m        *
+	* olen is requested output length in bytes. k is the key  *
+	* The output is the calculated tag */
+		int b=64;
+		if (sha>32) b=128;
+		byte[] B;
+		byte[] K0=new byte[b];
+		int olen=tag.length;
+
+		//b=K0.length;
+		if (olen<4 /*|| olen>sha*/) return 0;
+
+		for (int i=0;i<b;i++) K0[i]=0;
+
+		if (K.length > b) 
+		{
+			B=hashit(sha,K,0,null,0);
+			for (int i=0;i<sha;i++) K0[i]=B[i];
+		}
+		else
+			for (int i=0;i<K.length;i++ ) K0[i]=K[i];
+		
+		for (int i=0;i<b;i++) K0[i]^=0x36;
+		B=hashit(sha,K0,0,M,0);
+
+		for (int i=0;i<b;i++) K0[i]^=0x6a;
+		B=hashit(sha,K0,0,B,olen);
+
+		for (int i=0;i<olen;i++) tag[i]=B[i];
+
+		return 1;
+	}
+
+/* AES encryption/decryption. Encrypt byte array M using key K and returns ciphertext */
+	public static byte[] AES_CBC_IV0_ENCRYPT(byte[] K,byte[] M)
+	{ /* AES CBC encryption, with Null IV and key K */
+	/* Input is from an octet string M, output is to an octet string C */
+	/* Input is padded as necessary to make up a full final block */
+		AES a=new AES();
+		boolean fin;
+		int i,j,ipt,opt;
+		byte[] buff=new byte[16];
+		int clen=16+(M.length/16)*16;
+
+		byte[] C=new byte[clen];
+		int padlen;
+
+		a.init(AES.CBC,K.length,K,null);
+
+		ipt=opt=0;
+		fin=false;
+		for(;;)
+		{
+			for (i=0;i<16;i++)
+			{
+				if (ipt<M.length) buff[i]=M[ipt++];
+				else {fin=true; break;}
+			}
+			if (fin) break;
+			a.encrypt(buff);
+			for (i=0;i<16;i++)
+				C[opt++]=buff[i];
+		}    
+
+/* last block, filled up to i-th index */
+
+		padlen=16-i;
+		for (j=i;j<16;j++) buff[j]=(byte)padlen;
+
+		a.encrypt(buff);
+
+		for (i=0;i<16;i++)
+			C[opt++]=buff[i];
+		a.end();    
+		return C;
+	}
+
+/* returns plaintext if all consistent, else returns null string */
+	public static byte[] AES_CBC_IV0_DECRYPT(byte[] K,byte[] C)
+	{ /* padding is removed */
+		AES a=new AES();
+		int i,ipt,opt,ch;
+		byte[] buff=new byte[16];
+		byte[] MM=new byte[C.length];
+		boolean fin,bad;
+		int padlen;
+		ipt=opt=0;
+
+		a.init(AES.CBC,K.length,K,null);
+
+		if (C.length==0) return new byte[0];
+		ch=C[ipt++]; 
+  
+		fin=false;
+
+		for(;;)
+		{
+			for (i=0;i<16;i++)
+			{
+				buff[i]=(byte)ch;      
+				if (ipt>=C.length) {fin=true; break;}  
+				else ch=C[ipt++];  
+			}
+			a.decrypt(buff);
+			if (fin) break;
+			for (i=0;i<16;i++)
+				MM[opt++]=buff[i];
+		}    
+
+		a.end();
+		bad=false;
+		padlen=buff[15];
+		if (i!=15 || padlen<1 || padlen>16) bad=true;
+		if (padlen>=2 && padlen<=16)
+			for (i=16-padlen;i<16;i++) if (buff[i]!=padlen) bad=true;
+    
+		if (!bad) for (i=0;i<16-padlen;i++)
+					MM[opt++]=buff[i];
+
+		if (bad) return new byte[0];
+
+		byte[] M=new byte[opt];
+		for (i=0;i<opt;i++) M[i]=MM[i];
+
+		return M;
+	}
+
+/* Calculate a public/private EC GF(p) key pair W,S where W=S.G mod EC(p),
+ * where S is the secret key and W is the public key
+ * and G is fixed generator.
+ * If RNG is NULL then the private key is provided externally in S
+ * otherwise it is generated randomly internally */
+	public static int KEY_PAIR_GENERATE(RAND RNG,byte[] S,byte[] W)
+	{
+		BIG r,s;
+		ECP G,WP;
+		int res=0;
+	//	byte[] T=new byte[EFS];
+
+		G=ECP.generator();
+
+		r=new BIG(ROM.CURVE_Order);
+
+		if (RNG==null)
+		{
+			s=BIG.fromBytes(S);
+			s.mod(r);
+		}
+		else
+		{
+			s=BIG.randomnum(r,RNG);
+		}
+
+		//if (ROM.AES_S>0)
+		//{
+		//	s.mod2m(2*ROM.AES_S);
+		//}
+		s.toBytes(S);
+
+		WP=G.mul(s);
+		WP.toBytes(W,false);  // To use point compression on public keys, change to true 
+
+		return res;
+	}
+
+/* validate public key. */
+	public static int PUBLIC_KEY_VALIDATE(byte[] W)
+	{
+		BIG r,q,k;
+		ECP WP=ECP.fromBytes(W);
+		int nb,res=0;
+
+		r=new BIG(ROM.CURVE_Order);
+
+		if (WP.is_infinity()) res=INVALID_PUBLIC_KEY;
+
+		if (res==0)
+		{
+
+			q=new BIG(ROM.Modulus);
+			nb=q.nbits();
+			k=new BIG(1); k.shl((nb+4)/2);
+			k.add(q);
+			k.div(r);
+
+			while (k.parity()==0)
+			{
+				k.shr(1);
+				WP.dbl();
+			}
+
+			if (!k.isunity()) WP=WP.mul(k);
+			if (WP.is_infinity()) res=INVALID_PUBLIC_KEY; 
+		}
+		return res;
+	}
+
+/* IEEE-1363 Diffie-Hellman online calculation Z=S.WD */
+	public static int SVDP_DH(byte[] S,byte[] WD,byte[] Z)    
+	{
+		BIG r,s,wx,wy,z;
+		int valid;
+		ECP W;
+		int res=0;
+		byte[] T=new byte[EFS];
+
+		s=BIG.fromBytes(S);
+
+		W=ECP.fromBytes(WD);
+		if (W.is_infinity()) res=ERROR;
+
+		if (res==0)
+		{
+			r=new BIG(ROM.CURVE_Order);
+			s.mod(r);
+
+			W=W.mul(s);
+			if (W.is_infinity()) res=ERROR; 
+			else 
+			{
+				W.getX().toBytes(T);
+				for (int i=0;i<EFS;i++) Z[i]=T[i];
+			}
+		}
+		return res;
+	}
+
+/* IEEE ECDSA Signature, C and D are signature on F using private key S */
+	public static int SP_DSA(int sha,RAND RNG,byte[] S,byte[] F,byte[] C,byte[] D)
+	{
+		byte[] T=new byte[EFS];
+		BIG r,s,f,c,d,u,vx,w;
+		ECP G,V;
+		byte[] B=hashit(sha,F,0,null,BIG.MODBYTES);
+
+		G=ECP.generator();
+		r=new BIG(ROM.CURVE_Order);
+
+		s=BIG.fromBytes(S);
+		f=BIG.fromBytes(B);
+
+		c=new BIG(0);
+		d=new BIG(0);
+		V=new ECP();
+
+		do {
+			u=BIG.randomnum(r,RNG);
+			w=BIG.randomnum(r,RNG); /* side channel masking */
+			//if (ROM.AES_S>0)
+			//{
+			//	u.mod2m(2*ROM.AES_S);
+			//}			
+			V.copy(G);
+			V=V.mul(u);   		
+			vx=V.getX();
+			c.copy(vx);
+			c.mod(r);
+			if (c.iszilch()) continue;
+
+			u.copy(BIG.modmul(u,w,r));
+
+			u.invmodp(r);
+			d.copy(BIG.modmul(s,c,r));
+			d.add(f);
+
+			d.copy(BIG.modmul(d,w,r));
+
+			d.copy(BIG.modmul(u,d,r));
+		} while (d.iszilch());
+       
+		c.toBytes(T);
+		for (int i=0;i<EFS;i++) C[i]=T[i];
+		d.toBytes(T);
+		for (int i=0;i<EFS;i++) D[i]=T[i];
+		return 0;
+	}
+
+/* IEEE1363 ECDSA Signature Verification. Signature C and D on F is verified using public key W */
+	public static int VP_DSA(int sha,byte[] W,byte[] F, byte[] C,byte[] D)
+	{
+		BIG r,f,c,d,h2;
+		int res=0;
+		ECP G,WP,P;
+		int valid; 
+
+		byte[] B=hashit(sha,F,0,null,BIG.MODBYTES);
+
+		G=ECP.generator();
+		r=new BIG(ROM.CURVE_Order);
+
+		c=BIG.fromBytes(C);
+		d=BIG.fromBytes(D);
+		f=BIG.fromBytes(B);
+     
+		if (c.iszilch() || BIG.comp(c,r)>=0 || d.iszilch() || BIG.comp(d,r)>=0) 
+            res=INVALID;
+
+		if (res==0)
+		{
+			d.invmodp(r);
+			f.copy(BIG.modmul(f,d,r));
+			h2=BIG.modmul(c,d,r);
+
+			WP=ECP.fromBytes(W);
+			if (WP.is_infinity()) res=ERROR;
+			else
+			{
+				P=new ECP();
+				P.copy(WP);
+				P=P.mul2(h2,G,f);
+				if (P.is_infinity()) res=INVALID;
+				else
+				{
+					d=P.getX();
+					d.mod(r);
+					if (BIG.comp(d,c)!=0) res=INVALID;
+				}
+			}
+		}
+
+		return res;
+	}
+
+/* IEEE1363 ECIES encryption. Encryption of plaintext M uses public key W and produces ciphertext V,C,T */
+	public static byte[] ECIES_ENCRYPT(int sha,byte[] P1,byte[] P2,RAND RNG,byte[] W,byte[] M,byte[] V,byte[] T)
+	{ 
+		int i,len;
+
+		byte[] Z=new byte[EFS];
+		byte[] VZ=new byte[3*EFS+1];
+		byte[] K1=new byte[ECP.AESKEY];
+		byte[] K2=new byte[ECP.AESKEY];
+		byte[] U=new byte[EGS];
+
+		if (KEY_PAIR_GENERATE(RNG,U,V)!=0) return new byte[0];  
+		if (SVDP_DH(U,W,Z)!=0) return new byte[0];     
+
+		for (i=0;i<2*EFS+1;i++) VZ[i]=V[i];
+		for (i=0;i<EFS;i++) VZ[2*EFS+1+i]=Z[i];
+
+
+		byte[] K=KDF2(sha,VZ,P1,2*ECP.AESKEY);
+
+		for (i=0;i<ECP.AESKEY;i++) {K1[i]=K[i]; K2[i]=K[ECP.AESKEY+i];} 
+
+		byte[] C=AES_CBC_IV0_ENCRYPT(K1,M);
+
+		byte[] L2=inttoBytes(P2.length,8);	
+	
+		byte[] AC=new byte[C.length+P2.length+8];
+		for (i=0;i<C.length;i++) AC[i]=C[i];
+		for (i=0;i<P2.length;i++) AC[C.length+i]=P2[i];
+		for (i=0;i<8;i++) AC[C.length+P2.length+i]=L2[i];
+	
+		HMAC(sha,AC,K2,T);
+
+		return C;
+	}
+
+/* IEEE1363 ECIES decryption. Decryption of ciphertext V,C,T using private key U outputs plaintext M */
+	public static byte[] ECIES_DECRYPT(int sha,byte[] P1,byte[] P2,byte[] V,byte[] C,byte[] T,byte[] U)
+	{ 
+
+		int i,len;
+
+		byte[] Z=new byte[EFS];
+		byte[] VZ=new byte[3*EFS+1];
+		byte[] K1=new byte[ECP.AESKEY];
+		byte[] K2=new byte[ECP.AESKEY];
+		byte[] TAG=new byte[T.length];
+
+		if (SVDP_DH(U,V,Z)!=0) return new byte[0];  
+
+		for (i=0;i<2*EFS+1;i++) VZ[i]=V[i];
+		for (i=0;i<EFS;i++) VZ[2*EFS+1+i]=Z[i];
+
+		byte[] K=KDF2(sha,VZ,P1,2*ECP.AESKEY);
+
+		for (i=0;i<ECP.AESKEY;i++) {K1[i]=K[i]; K2[i]=K[ECP.AESKEY+i];} 
+
+		byte[] M=AES_CBC_IV0_DECRYPT(K1,C); 
+
+		if (M.length==0) return M;
+
+		byte[] L2=inttoBytes(P2.length,8);	
+	
+		byte[] AC=new byte[C.length+P2.length+8];
+
+		for (i=0;i<C.length;i++) AC[i]=C[i];
+		for (i=0;i<P2.length;i++) AC[C.length+i]=P2[i];
+		for (i=0;i<8;i++) AC[C.length+P2.length+i]=L2[i];
+	
+		HMAC(sha,AC,K2,TAG);
+
+		boolean same=true;
+		for (i=0;i<T.length;i++) if (T[i]!=TAG[i]) same=false;
+		if (!same) return new byte[0];
+	
+		return M;
+
+	}
+}
diff --git a/src/main/java/org/apache/milagro/amcl/BLS24/ECP.java b/src/main/java/org/apache/milagro/amcl/BLS24/ECP.java
new file mode 100644
index 0000000..b5b5839
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS24/ECP.java
@@ -0,0 +1,1109 @@
+/*
+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.
+*/
+
+/* Elliptic Curve Point class */
+
+package org.apache.milagro.amcl.BLS24;
+
+public final class ECP {
+
+	public static final int WEIERSTRASS=0;
+	public static final int EDWARDS=1;
+	public static final int MONTGOMERY=2;
+	public static final int NOT=0;
+	public static final int BN=1;
+	public static final int BLS=2;
+	public static final int D_TYPE=0;
+	public static final int M_TYPE=1;
+	public static final int POSITIVEX=0;
+	public static final int NEGATIVEX=1;
+
+	public static final int CURVETYPE=WEIERSTRASS;
+	public static final int CURVE_PAIRING_TYPE=BLS;
+	public static final int SEXTIC_TWIST=M_TYPE;
+	public static final int SIGN_OF_X=POSITIVEX;
+
+	public static final int SHA256=32;
+	public static final int SHA384=48;
+	public static final int SHA512=64;
+
+	public static final int HASH_TYPE=48;
+	public static final int AESKEY=24;
+
+	private FP x;
+	private FP y;
+	private FP z;
+//	private boolean INF;
+
+/* Constructor - set to O */
+	public ECP() {
+		//INF=true;
+		x=new FP(0);
+		y=new FP(1);
+		if (CURVETYPE==EDWARDS)
+		{
+			z=new FP(1);
+		}
+		else
+		{
+			z=new FP(0);
+		}
+	}
+
+    public ECP(ECP e) {
+        this.x = new FP(e.x);
+        this.y = new FP(e.y);
+        this.z = new FP(e.z);
+    }
+
+/* test for O point-at-infinity */
+	public boolean is_infinity() {
+//		if (INF) return true;                            // Edits made
+		if (CURVETYPE==EDWARDS)
+		{
+			return (x.iszilch() && y.equals(z));
+		}
+		if (CURVETYPE==WEIERSTRASS)
+		{
+			return (x.iszilch() && z.iszilch());
+		}
+		if (CURVETYPE==MONTGOMERY)
+		{
+			return z.iszilch();
+		}
+		return true;
+	}
+/* Conditional swap of P and Q dependant on d */
+	private void cswap(ECP Q,int d)
+	{
+		x.cswap(Q.x,d);
+		if (CURVETYPE!=MONTGOMERY) y.cswap(Q.y,d);
+		z.cswap(Q.z,d);
+	//	if (CURVETYPE!=EDWARDS)
+	//	{
+	//		boolean bd;
+	//		if (d==0) bd=false;
+	//		else bd=true;
+	//		bd=bd&(INF^Q.INF);
+	//		INF^=bd;
+	//		Q.INF^=bd;
+	//	}
+	}
+
+/* Conditional move of Q to P dependant on d */
+	private void cmove(ECP Q,int d)
+	{
+		x.cmove(Q.x,d);
+		if (CURVETYPE!=MONTGOMERY) y.cmove(Q.y,d);
+		z.cmove(Q.z,d);
+	//	if (CURVETYPE!=EDWARDS)
+	//	{
+	//		boolean bd;
+	//		if (d==0) bd=false;
+	//		else bd=true;
+	//		INF^=(INF^Q.INF)&bd;
+	//	}
+	}
+
+/* return 1 if b==c, no branching */
+	private static int teq(int b,int c)
+	{
+		int x=b^c;
+		x-=1;  // if x=0, x now -1
+		return ((x>>31)&1);
+	}
+
+/* Constant time select from pre-computed table */
+	private void select(ECP W[],int b)
+	{
+		ECP MP=new ECP(); 
+		int m=b>>31;
+		int babs=(b^m)-m;
+
+		babs=(babs-1)/2;
+		cmove(W[0],teq(babs,0));  // conditional move
+		cmove(W[1],teq(babs,1));
+		cmove(W[2],teq(babs,2));
+		cmove(W[3],teq(babs,3));
+		cmove(W[4],teq(babs,4));
+		cmove(W[5],teq(babs,5));
+		cmove(W[6],teq(babs,6));
+		cmove(W[7],teq(babs,7));
+ 
+		MP.copy(this);
+		MP.neg();
+		cmove(MP,(int)(m&1));
+	}
+
+/* Test P == Q */
+	public boolean equals(ECP Q) {
+//		if (is_infinity() && Q.is_infinity()) return true;
+//		if (is_infinity() || Q.is_infinity()) return false;
+
+		FP a=new FP(0);                                        // Edits made
+		FP b=new FP(0);
+		a.copy(x); a.mul(Q.z); 
+		b.copy(Q.x); b.mul(z); 
+		if (!a.equals(b)) return false;
+		if (CURVETYPE!=MONTGOMERY)
+		{
+			a.copy(y); a.mul(Q.z); 
+			b.copy(Q.y); b.mul(z); 
+			if (!a.equals(b)) return false;
+		}
+		return true;
+	}
+
+/* this=P */
+	public void copy(ECP P)
+	{
+		x.copy(P.x);
+		if (CURVETYPE!=MONTGOMERY) y.copy(P.y);
+		z.copy(P.z);
+		//INF=P.INF;
+	}
+/* this=-this */
+	public void neg() {
+//		if (is_infinity()) return;
+		if (CURVETYPE==WEIERSTRASS)
+		{
+			y.neg(); y.norm();
+		}
+		if (CURVETYPE==EDWARDS)
+		{
+			x.neg(); x.norm();
+		}
+		return;
+	}
+/* set this=O */
+	public void inf() {
+//		INF=true;
+		x.zero();
+		if (CURVETYPE!=MONTGOMERY) y.one();
+		if (CURVETYPE!=EDWARDS) z.zero();
+		else z.one();
+	}
+
+/* Calculate RHS of curve equation */
+	public static FP RHS(FP x) {
+		x.norm();
+		FP r=new FP(x);
+		r.sqr();
+
+		if (CURVETYPE==WEIERSTRASS)
+		{ // x^3+Ax+B
+			FP b=new FP(new BIG(ROM.CURVE_B));
+			r.mul(x);
+			if (ROM.CURVE_A==-3)
+			{
+				FP cx=new FP(x);
+				cx.imul(3);
+				cx.neg(); cx.norm();
+				r.add(cx);
+			}
+			r.add(b);
+		}
+		if (CURVETYPE==EDWARDS)
+		{ // (Ax^2-1)/(Bx^2-1) 
+			FP b=new FP(new BIG(ROM.CURVE_B));
+
+			FP one=new FP(1);
+			b.mul(r);
+			b.sub(one);
+			b.norm();
+			if (ROM.CURVE_A==-1) r.neg();
+			r.sub(one); r.norm();
+			b.inverse();
+
+			r.mul(b);
+		}
+		if (CURVETYPE==MONTGOMERY)
+		{ // x^3+Ax^2+x
+			FP x3=new FP(0);
+			x3.copy(r);
+			x3.mul(x);
+			r.imul(ROM.CURVE_A);
+			r.add(x3);
+			r.add(x);
+		}
+		r.reduce();
+		return r;
+	}
+
+/* set (x,y) from two BIGs */
+	public ECP(BIG ix,BIG iy) {
+		x=new FP(ix);
+		y=new FP(iy);
+		z=new FP(1);
+		FP rhs=RHS(x);
+
+		if (CURVETYPE==MONTGOMERY)
+		{
+			if (rhs.jacobi()!=1) inf();
+			//if (rhs.jacobi()==1) INF=false;
+			//else inf();
+		}
+		else
+		{
+			FP y2=new FP(y);
+			y2.sqr();
+			if (!y2.equals(rhs)) inf();
+			//if (y2.equals(rhs)) INF=false;
+			//else inf();
+		}
+	}
+/* set (x,y) from BIG and a bit */
+	public ECP(BIG ix,int s) {
+		x=new FP(ix);
+		FP rhs=RHS(x);
+		y=new FP(0);
+		z=new FP(1);
+		if (rhs.jacobi()==1)
+		{
+			FP ny=rhs.sqrt();
+			if (ny.redc().parity()!=s) ny.neg();
+			y.copy(ny);
+			//INF=false;
+		}
+		else inf();
+	}
+
+/* set from x - calculate y from curve equation */
+	public ECP(BIG ix) {
+		x=new FP(ix);
+		FP rhs=RHS(x);
+		y=new FP(0);
+		z=new FP(1);
+		if (rhs.jacobi()==1)
+		{
+			if (CURVETYPE!=MONTGOMERY) y.copy(rhs.sqrt());
+			//INF=false;
+		}
+		else inf(); //INF=true;
+	}
+
+/* set to affine - from (x,y,z) to (x,y) */
+	public void affine() {
+		if (is_infinity()) return;	// 
+		FP one=new FP(1);
+		if (z.equals(one)) return;
+		z.inverse();
+		x.mul(z); x.reduce();
+		if (CURVETYPE!=MONTGOMERY)            // Edits made
+		{
+			y.mul(z); y.reduce();
+		}
+		z.copy(one);
+	}
+/* extract x as a BIG */
+	public BIG getX()
+	{
+		ECP W=new ECP(this);
+		W.affine();
+		return W.x.redc();
+	}
+/* extract y as a BIG */
+	public BIG getY()
+	{
+		ECP W=new ECP(this);
+		W.affine();
+		return W.y.redc();
+	}
+
+/* get sign of Y */
+	public int getS()
+	{
+		//affine();
+		BIG y=getY();
+		return y.parity();
+	}
+/* extract x as an FP */
+	public FP getx()
+	{
+		return x;
+	}
+/* extract y as an FP */
+	public FP gety()
+	{
+		return y;
+	}
+/* extract z as an FP */
+	public FP getz()
+	{
+		return z;
+	}
+/* convert to byte array */
+	public void toBytes(byte[] b,boolean compress)
+	{
+		byte[] t=new byte[BIG.MODBYTES];
+		ECP W=new ECP(this);
+		W.affine();
+
+		W.x.redc().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) b[i+1]=t[i];
+
+		if (CURVETYPE==MONTGOMERY)
+		{
+			b[0]=0x06;
+			return;
+		}
+
+		if (compress)
+		{
+			b[0]=0x02;
+			if (y.redc().parity()==1) b[0]=0x03;
+			return;
+		}
+
+		b[0]=0x04;
+
+		W.y.redc().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) b[i+BIG.MODBYTES+1]=t[i];
+	}
+/* convert from byte array to point */
+	public static ECP fromBytes(byte[] b)
+	{
+		byte[] t=new byte[BIG.MODBYTES];
+		BIG p=new BIG(ROM.Modulus);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=b[i+1];
+		BIG px=BIG.fromBytes(t);
+		if (BIG.comp(px,p)>=0) return new ECP();
+
+		if (CURVETYPE==MONTGOMERY)
+		{
+			return new ECP(px);
+		}
+
+		if (b[0]==0x04)
+		{
+			for (int i=0;i<BIG.MODBYTES;i++) t[i]=b[i+BIG.MODBYTES+1];
+			BIG py=BIG.fromBytes(t);
+			if (BIG.comp(py,p)>=0) return new ECP();
+			return new ECP(px,py);
+		}
+
+		if (b[0]==0x02 || b[0]==0x03)
+		{
+			return new ECP(px,(int)(b[0]&1));
+		}
+		return new ECP();
+	}
+/* convert to hex string */
+	public String toString() {
+		ECP W=new ECP(this);	
+		W.affine();
+		if (W.is_infinity()) return "infinity";
+		if (CURVETYPE==MONTGOMERY) return "("+W.x.redc().toString()+")";
+		else return "("+W.x.redc().toString()+","+W.y.redc().toString()+")";
+	}
+
+/* convert to hex string */
+	public String toRawString() {
+		//if (is_infinity()) return "infinity";
+		//affine();
+		ECP W=new ECP(this);	
+		if (CURVETYPE==MONTGOMERY) return "("+W.x.redc().toString()+","+W.z.redc().toString()+")";
+		else return "("+W.x.redc().toString()+","+W.y.redc().toString()+","+W.z.redc().toString()+")";
+	}
+
+/* this*=2 */
+	public void dbl() {
+//		if (INF) return;
+		
+		if (CURVETYPE==WEIERSTRASS)
+		{
+			if (ROM.CURVE_A==0)
+			{
+//System.out.println("Into dbl");
+				FP t0=new FP(y);                      /*** Change ***/    // Edits made
+				t0.sqr();
+				FP t1=new FP(y);
+				t1.mul(z);
+				FP t2=new FP(z);
+				t2.sqr();
+
+				z.copy(t0);
+				z.add(t0); z.norm(); 
+				z.add(z); z.add(z); z.norm();
+				t2.imul(3*ROM.CURVE_B_I);
+
+				FP x3=new FP(t2);
+				x3.mul(z);
+
+				FP y3=new FP(t0);
+				y3.add(t2); y3.norm();
+				z.mul(t1); 
+				t1.copy(t2); t1.add(t2); t2.add(t1);
+				t0.sub(t2); t0.norm(); y3.mul(t0); y3.add(x3);
+				t1.copy(x); t1.mul(y); 
+				x.copy(t0); x.norm(); x.mul(t1); x.add(x);
+				x.norm(); 
+				y.copy(y3); y.norm();
+//System.out.println("Out of dbl");
+			}
+			else
+			{
+				FP t0=new FP(x);
+				FP t1=new FP(y);
+				FP t2=new FP(z);
+				FP t3=new FP(x);
+				FP z3=new FP(z);
+				FP y3=new FP(0);
+				FP x3=new FP(0);
+				FP b=new FP(0);
+
+				if (ROM.CURVE_B_I==0)
+					b.copy(new FP(new BIG(ROM.CURVE_B)));
+
+				t0.sqr();  //1    x^2
+				t1.sqr();  //2    y^2
+				t2.sqr();  //3
+
+				t3.mul(y); //4
+				t3.add(t3); t3.norm();//5
+				z3.mul(x);   //6
+				z3.add(z3);  z3.norm();//7
+				y3.copy(t2); 
+				
+				if (ROM.CURVE_B_I==0)
+					y3.mul(b); //8
+				else
+					y3.imul(ROM.CURVE_B_I);
+				
+				y3.sub(z3); //y3.norm(); //9  ***
+				x3.copy(y3); x3.add(y3); x3.norm();//10
+
+				y3.add(x3); //y3.norm();//11
+				x3.copy(t1); x3.sub(y3); x3.norm();//12
+				y3.add(t1); y3.norm();//13
+				y3.mul(x3); //14
+				x3.mul(t3); //15
+				t3.copy(t2); t3.add(t2); //t3.norm(); //16
+				t2.add(t3); //t2.norm(); //17
+
+				if (ROM.CURVE_B_I==0)
+					z3.mul(b); //18
+				else
+					z3.imul(ROM.CURVE_B_I);
+
+				z3.sub(t2); //z3.norm();//19
+				z3.sub(t0); z3.norm();//20  ***
+				t3.copy(z3); t3.add(z3); //t3.norm();//21
+
+				z3.add(t3); z3.norm(); //22
+				t3.copy(t0); t3.add(t0); //t3.norm(); //23
+				t0.add(t3); //t0.norm();//24
+				t0.sub(t2); t0.norm();//25
+
+				t0.mul(z3);//26
+				y3.add(t0); //y3.norm();//27
+				t0.copy(y); t0.mul(z);//28
+				t0.add(t0); t0.norm(); //29
+				z3.mul(t0);//30
+				x3.sub(z3); //x3.norm();//31
+				t0.add(t0); t0.norm();//32
+				t1.add(t1); t1.norm();//33
+				z3.copy(t0); z3.mul(t1);//34
+
+				x.copy(x3); x.norm(); 
+				y.copy(y3); y.norm();
+				z.copy(z3); z.norm();
+			}
+		}
+		if (CURVETYPE==EDWARDS)
+		{
+//System.out.println("Into dbl");
+			FP C=new FP(x);
+			FP D=new FP(y);
+			FP H=new FP(z);
+			FP J=new FP(0);
+
+			x.mul(y); x.add(x); x.norm();
+			C.sqr();
+			D.sqr();
+
+			if (ROM.CURVE_A==-1) C.neg();	
+
+			y.copy(C); y.add(D); y.norm();
+			H.sqr(); H.add(H);
+
+			z.copy(y);
+			J.copy(y); 
+
+			J.sub(H); J.norm();
+			x.mul(J);
+
+			C.sub(D); C.norm();
+			y.mul(C);
+			z.mul(J);
+//System.out.println("Out of dbl");
+		}
+		if (CURVETYPE==MONTGOMERY)
+		{
+			FP A=new FP(x);
+			FP B=new FP(x);		
+			FP AA=new FP(0);
+			FP BB=new FP(0);
+			FP C=new FP(0);
+
+			A.add(z); A.norm();
+			AA.copy(A); AA.sqr();
+			B.sub(z); B.norm();
+			BB.copy(B); BB.sqr();
+			C.copy(AA); C.sub(BB); C.norm();
+			x.copy(AA); x.mul(BB);
+
+			A.copy(C); A.imul((ROM.CURVE_A+2)/4);
+
+			BB.add(A); BB.norm();
+			z.copy(BB); z.mul(C);
+		}
+		return;
+	}
+
+/* this+=Q */
+	public void add(ECP Q) {
+//		if (INF)
+//		{
+//			copy(Q);
+//			return;
+//		}
+//		if (Q.INF) return;
+
+		if (CURVETYPE==WEIERSTRASS)
+		{
+
+
+			if (ROM.CURVE_A==0)
+			{
+// Edits made
+//System.out.println("Into add");
+				int b=3*ROM.CURVE_B_I;
+				FP t0=new FP(x);
+				t0.mul(Q.x);
+				FP t1=new FP(y);
+				t1.mul(Q.y);
+				FP t2=new FP(z);
+				t2.mul(Q.z);
+				FP t3=new FP(x);
+				t3.add(y); t3.norm();
+				FP t4=new FP(Q.x);
+				t4.add(Q.y); t4.norm();
+				t3.mul(t4);
+				t4.copy(t0); t4.add(t1);
+
+				t3.sub(t4); t3.norm();
+				t4.copy(y);
+				t4.add(z); t4.norm();
+				FP x3=new FP(Q.y);
+				x3.add(Q.z); x3.norm();
+
+				t4.mul(x3);
+				x3.copy(t1);
+				x3.add(t2);
+	
+				t4.sub(x3); t4.norm();
+				x3.copy(x); x3.add(z); x3.norm();
+				FP y3=new FP(Q.x);
+				y3.add(Q.z); y3.norm();
+				x3.mul(y3);
+				y3.copy(t0);
+				y3.add(t2);
+				y3.rsub(x3); y3.norm();
+				x3.copy(t0); x3.add(t0); 
+				t0.add(x3); t0.norm();
+				t2.imul(b);
+
+				FP z3=new FP(t1); z3.add(t2); z3.norm();
+				t1.sub(t2); t1.norm(); 
+				y3.imul(b);
+	
+				x3.copy(y3); x3.mul(t4); t2.copy(t3); t2.mul(t1); x3.rsub(t2);
+				y3.mul(t0); t1.mul(z3); y3.add(t1);
+				t0.mul(t3); z3.mul(t4); z3.add(t0);
+
+				x.copy(x3); x.norm(); 
+				y.copy(y3); y.norm();
+				z.copy(z3); z.norm();
+//System.out.println("Out of add");
+			}
+			else
+			{
+				FP t0=new FP(x);
+				FP t1=new FP(y);
+				FP t2=new FP(z);
+				FP t3=new FP(x);
+				FP t4=new FP(Q.x);
+				FP z3=new FP(0);
+				FP y3=new FP(Q.x);
+				FP x3=new FP(Q.y);
+				FP b=new FP(0);
+
+				if (ROM.CURVE_B_I==0)
+					b.copy(new FP(new BIG(ROM.CURVE_B)));
+
+				t0.mul(Q.x); //1
+				t1.mul(Q.y); //2
+				t2.mul(Q.z); //3
+
+				t3.add(y); t3.norm(); //4
+				t4.add(Q.y); t4.norm();//5
+				t3.mul(t4);//6
+				t4.copy(t0); t4.add(t1); //t4.norm(); //7
+				t3.sub(t4); t3.norm(); //8
+				t4.copy(y); t4.add(z); t4.norm();//9
+				x3.add(Q.z); x3.norm();//10
+				t4.mul(x3); //11
+				x3.copy(t1); x3.add(t2); //x3.norm();//12
+
+				t4.sub(x3); t4.norm();//13
+				x3.copy(x); x3.add(z); x3.norm(); //14
+				y3.add(Q.z); y3.norm();//15
+
+				x3.mul(y3); //16
+				y3.copy(t0); y3.add(t2); //y3.norm();//17
+
+				y3.rsub(x3); y3.norm(); //18
+				z3.copy(t2); 
+				
+
+				if (ROM.CURVE_B_I==0)
+					z3.mul(b); //18
+				else
+					z3.imul(ROM.CURVE_B_I);
+				
+				x3.copy(y3); x3.sub(z3); x3.norm(); //20
+				z3.copy(x3); z3.add(x3); //z3.norm(); //21
+
+				x3.add(z3); //x3.norm(); //22
+				z3.copy(t1); z3.sub(x3); z3.norm(); //23
+				x3.add(t1); x3.norm(); //24
+
+				if (ROM.CURVE_B_I==0)
+					y3.mul(b); //18
+				else
+					y3.imul(ROM.CURVE_B_I);
+
+				t1.copy(t2); t1.add(t2); //t1.norm();//26
+				t2.add(t1); //t2.norm();//27
+
+				y3.sub(t2); //y3.norm(); //28
+
+				y3.sub(t0); y3.norm(); //29
+				t1.copy(y3); t1.add(y3); //t1.norm();//30
+				y3.add(t1); y3.norm(); //31
+
+				t1.copy(t0); t1.add(t0); //t1.norm(); //32
+				t0.add(t1); //t0.norm();//33
+				t0.sub(t2); t0.norm();//34
+				t1.copy(t4); t1.mul(y3);//35
+				t2.copy(t0); t2.mul(y3);//36
+				y3.copy(x3); y3.mul(z3);//37
+				y3.add(t2); //y3.norm();//38
+				x3.mul(t3);//39
+				x3.sub(t1);//40
+				z3.mul(t4);//41
+				t1.copy(t3); t1.mul(t0);//42
+				z3.add(t1); 
+				x.copy(x3); x.norm(); 
+				y.copy(y3); y.norm();
+				z.copy(z3); z.norm();
+			}
+		}
+		if (CURVETYPE==EDWARDS)
+		{
+//System.out.println("Into add");
+			FP A=new FP(z);
+			FP B=new FP(0);
+			FP C=new FP(x);
+			FP D=new FP(y);
+			FP E=new FP(0);
+			FP F=new FP(0);
+			FP G=new FP(0);
+
+			A.mul(Q.z);   
+			B.copy(A); B.sqr();    
+			C.mul(Q.x);      
+			D.mul(Q.y); 
+
+			E.copy(C); E.mul(D);  
+		
+			if (ROM.CURVE_B_I==0)
+			{
+				FP b=new FP(new BIG(ROM.CURVE_B));
+				E.mul(b);
+			}
+			else
+				E.imul(ROM.CURVE_B_I); 
+
+			F.copy(B); F.sub(E);      
+			G.copy(B); G.add(E);       
+
+			if (ROM.CURVE_A==1)
+			{
+				E.copy(D); E.sub(C);
+			}
+			C.add(D); 
+
+			B.copy(x); B.add(y);    
+			D.copy(Q.x); D.add(Q.y); B.norm(); D.norm(); 
+			B.mul(D);                   
+			B.sub(C); B.norm(); F.norm(); 
+			B.mul(F);                     
+			x.copy(A); x.mul(B); G.norm();  
+			if (ROM.CURVE_A==1)
+			{
+				E.norm(); C.copy(E); C.mul(G);  
+			}
+			if (ROM.CURVE_A==-1)
+			{
+				C.norm(); C.mul(G);
+			}
+			y.copy(A); y.mul(C);     
+
+			z.copy(F);	
+			z.mul(G);
+//System.out.println("Out of add");
+		}
+		return;
+	}
+
+/* Differential Add for Montgomery curves. this+=Q where W is this-Q and is affine. */
+	public void dadd(ECP Q,ECP W) {
+		FP A=new FP(x);
+		FP B=new FP(x);
+		FP C=new FP(Q.x);
+		FP D=new FP(Q.x);
+		FP DA=new FP(0);
+		FP CB=new FP(0);	
+			
+		A.add(z); 
+		B.sub(z); 
+
+		C.add(Q.z);
+		D.sub(Q.z);
+		A.norm();
+
+		D.norm();
+		DA.copy(D); DA.mul(A);
+
+		C.norm();
+		B.norm();
+		CB.copy(C); CB.mul(B);
+
+		A.copy(DA); A.add(CB); 
+		A.norm(); A.sqr();
+		B.copy(DA); B.sub(CB); 
+		B.norm(); B.sqr();
+
+		x.copy(A);
+		z.copy(W.x); z.mul(B);
+	}
+/* this-=Q */
+	public void sub(ECP Q) {
+		ECP NQ=new ECP(Q);
+		NQ.neg();
+		add(NQ);
+	}
+
+/* constant time multiply by small integer of length bts - use ladder */
+	public ECP pinmul(int e,int bts) {	
+		if (CURVETYPE==MONTGOMERY)
+			return this.mul(new BIG(e));
+		else
+		{
+			int nb,i,b;
+			ECP P=new ECP();
+			ECP R0=new ECP();
+			ECP R1=new ECP(); R1.copy(this);
+
+			for (i=bts-1;i>=0;i--)
+			{
+				b=(e>>i)&1;
+				P.copy(R1);
+				P.add(R0);
+				R0.cswap(R1,b);
+				R1.copy(P);
+				R0.dbl();
+				R0.cswap(R1,b);
+			}
+			P.copy(R0);
+			P.affine();
+			return P;
+		}
+	}
+
+/* return e.this */
+
+	public ECP mul(BIG e) {
+		if (e.iszilch() || is_infinity()) return new ECP();
+		ECP P=new ECP();
+		if (CURVETYPE==MONTGOMERY)
+		{
+/* use Ladder */
+			int nb,i,b;
+			ECP D=new ECP();
+			ECP R0=new ECP(); R0.copy(this);
+			ECP R1=new ECP(); R1.copy(this);
+			R1.dbl();
+
+			D.copy(this); D.affine();
+			nb=e.nbits();
+			for (i=nb-2;i>=0;i--)
+			{
+				b=e.bit(i);
+				P.copy(R1);
+
+				P.dadd(R0,D);
+				R0.cswap(R1,b);
+				R1.copy(P);
+				R0.dbl();
+				R0.cswap(R1,b);
+
+			}
+
+			P.copy(R0);
+		}
+		else
+		{
+// fixed size windows 
+			int i,b,nb,m,s,ns;
+			BIG mt=new BIG();
+			BIG t=new BIG();
+			ECP Q=new ECP();
+			ECP C=new ECP();
+			ECP[] W=new ECP[8];
+			byte[] w=new byte[1+(BIG.NLEN*BIG.BASEBITS+3)/4];
+
+			//affine();
+
+// precompute table 
+			Q.copy(this);
+
+			Q.dbl();
+			W[0]=new ECP();
+			W[0].copy(this);
+
+			for (i=1;i<8;i++)
+			{
+				W[i]=new ECP();
+				W[i].copy(W[i-1]);
+				W[i].add(Q);
+			}
+
+// make exponent odd - add 2P if even, P if odd 
+			t.copy(e);
+			s=t.parity();
+			t.inc(1); t.norm(); ns=t.parity(); mt.copy(t); mt.inc(1); mt.norm();
+			t.cmove(mt,s);
+			Q.cmove(this,ns);
+			C.copy(Q);
+
+			nb=1+(t.nbits()+3)/4;
+
+// convert exponent to signed 4-bit window 
+			for (i=0;i<nb;i++)
+			{
+				w[i]=(byte)(t.lastbits(5)-16);
+				t.dec(w[i]); t.norm();
+				t.fshr(4);	
+			}
+			w[nb]=(byte)t.lastbits(5);
+	
+			P.copy(W[(w[nb]-1)/2]);  
+			for (i=nb-1;i>=0;i--)
+			{
+				Q.select(W,w[i]);
+				P.dbl();
+				P.dbl();
+				P.dbl();
+				P.dbl();
+				P.add(Q);
+			}
+			P.sub(C); /* apply correction */
+		}
+		P.affine();
+		return P;
+	}
+
+/* Return e.this+f.Q */
+
+	public ECP mul2(BIG e,ECP Q,BIG f) {
+		BIG te=new BIG();
+		BIG tf=new BIG();
+		BIG mt=new BIG();
+		ECP S=new ECP();
+		ECP T=new ECP();
+		ECP C=new ECP();
+		ECP[] W=new ECP[8];
+		byte[] w=new byte[1+(BIG.NLEN*BIG.BASEBITS+1)/2];		
+		int i,s,ns,nb;
+		byte a,b;
+
+		//affine();
+		//Q.affine();
+
+		te.copy(e);
+		tf.copy(f);
+
+// precompute table 
+		W[1]=new ECP(); W[1].copy(this); W[1].sub(Q);
+		W[2]=new ECP(); W[2].copy(this); W[2].add(Q);
+		S.copy(Q); S.dbl();
+		W[0]=new ECP(); W[0].copy(W[1]); W[0].sub(S);
+		W[3]=new ECP(); W[3].copy(W[2]); W[3].add(S);
+		T.copy(this); T.dbl();
+		W[5]=new ECP(); W[5].copy(W[1]); W[5].add(T);
+		W[6]=new ECP(); W[6].copy(W[2]); W[6].add(T);
+		W[4]=new ECP(); W[4].copy(W[5]); W[4].sub(S);
+		W[7]=new ECP(); W[7].copy(W[6]); W[7].add(S);
+
+// if multiplier is odd, add 2, else add 1 to multiplier, and add 2P or P to correction 
+
+		s=te.parity();
+		te.inc(1); te.norm(); ns=te.parity(); mt.copy(te); mt.inc(1); mt.norm();
+		te.cmove(mt,s);
+		T.cmove(this,ns);
+		C.copy(T);
+
+		s=tf.parity();
+		tf.inc(1); tf.norm(); ns=tf.parity(); mt.copy(tf); mt.inc(1); mt.norm();
+		tf.cmove(mt,s);
+		S.cmove(Q,ns);
+		C.add(S);
+
+		mt.copy(te); mt.add(tf); mt.norm();
+		nb=1+(mt.nbits()+1)/2;
+
+// convert exponent to signed 2-bit window 
+		for (i=0;i<nb;i++)
+		{
+			a=(byte)(te.lastbits(3)-4);
+			te.dec(a); te.norm(); 
+			te.fshr(2);
+			b=(byte)(tf.lastbits(3)-4);
+			tf.dec(b); tf.norm(); 
+			tf.fshr(2);
+			w[i]=(byte)(4*a+b);
+		}
+		w[nb]=(byte)(4*te.lastbits(3)+tf.lastbits(3));
+		S.copy(W[(w[nb]-1)/2]);  
+
+		for (i=nb-1;i>=0;i--)
+		{
+			T.select(W,w[i]);
+			S.dbl();
+			S.dbl();
+			S.add(T);
+		}
+		S.sub(C); /* apply correction */
+		S.affine();
+		return S;
+	}
+
+// multiply a point by the curves cofactor
+	public void cfp()
+	{
+		int cf=ROM.CURVE_Cof_I;
+		if (cf==1) return;
+		if (cf==4)
+		{
+			dbl(); dbl();
+			//affine();
+			return;
+		} 
+		if (cf==8)
+		{
+			dbl(); dbl(); dbl();
+			//affine();
+			return;
+		}
+		BIG c=new BIG(ROM.CURVE_Cof);
+		copy(mul(c));
+	}
+
+/* Map byte string to curve point */
+	public static ECP mapit(byte[] h)
+	{
+		BIG q=new BIG(ROM.Modulus);
+		BIG x=BIG.fromBytes(h);
+		x.mod(q);
+		ECP P;
+
+		while (true)
+		{
+			while (true)
+			{
+				if (CURVETYPE!=MONTGOMERY)
+					P=new ECP(x,0);
+				else
+					P=new ECP(x);	
+				x.inc(1); x.norm();
+				if (!P.is_infinity()) break;
+			}
+			P.cfp();
+			if (!P.is_infinity()) break;
+		}
+		return P;
+	}
+
+	public static ECP generator()
+	{
+		ECP G;
+		BIG gx,gy;
+		gx=new BIG(ROM.CURVE_Gx);
+
+		if (ECP.CURVETYPE!=ECP.MONTGOMERY)
+		{
+			gy=new BIG(ROM.CURVE_Gy);
+			G=new ECP(gx,gy);
+		}
+		else
+			G=new ECP(gx);
+		return G;
+	}
+
+/*
+	public static void main(String[] args) {
+
+		BIG Gx=new BIG(ROM.CURVE_Gx);
+		BIG Gy;
+		ECP P;
+		if (CURVETYPE!=MONTGOMERY) Gy=new BIG(ROM.CURVE_Gy);
+		BIG r=new BIG(ROM.CURVE_Order);
+
+		//r.dec(7);
+	
+		System.out.println("Gx= "+Gx.toString());		
+		if (CURVETYPE!=MONTGOMERY) System.out.println("Gy= "+Gy.toString());	
+
+		if (CURVETYPE!=MONTGOMERY) P=new ECP(Gx,Gy);
+		else  P=new ECP(Gx);
+
+		System.out.println("P= "+P.toString());		
+
+		ECP R=P.mul(r);
+		//for (int i=0;i<10000;i++)
+		//	R=P.mul(r);
+	
+		System.out.println("R= "+R.toString());
+    } */
+}
+
diff --git a/src/main/java/org/apache/milagro/amcl/BLS24/ECP4.java b/src/main/java/org/apache/milagro/amcl/BLS24/ECP4.java
new file mode 100644
index 0000000..e0205cb
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS24/ECP4.java
@@ -0,0 +1,768 @@
+/*
+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.
+*/
+
+/* AMCL Weierstrass elliptic curve functions over FP4 */
+
+package org.apache.milagro.amcl.BLS24;
+
+public final class ECP4 {
+	private FP4 x;
+	private FP4 y;
+	private FP4 z;
+//	private boolean INF;
+
+/* Constructor - set this=O */
+	public ECP4() {
+//		INF=true;
+		x=new FP4(0);
+		y=new FP4(1);
+		z=new FP4(0);
+	}
+
+    public ECP4(ECP4 e) {
+        this.x = new FP4(e.x);
+        this.y = new FP4(e.y);
+        this.z = new FP4(e.z);
+    }
+
+/* Test this=O? */
+	public boolean is_infinity() {
+//		if (INF) return true;                    //******
+		return (x.iszilch() && z.iszilch());
+	}
+/* copy this=P */
+	public void copy(ECP4 P)
+	{
+		x.copy(P.x);
+		y.copy(P.y);
+		z.copy(P.z);
+//		INF=P.INF;
+	}
+/* set this=O */
+	public void inf() {
+//		INF=true;
+		x.zero();
+		y.one();
+		z.zero();
+	}
+
+/* Conditional move of Q to P dependant on d */
+	public void cmove(ECP4 Q,int d)
+	{
+		x.cmove(Q.x,d);
+		y.cmove(Q.y,d);
+		z.cmove(Q.z,d);
+
+//		boolean bd;
+//		if (d==0) bd=false;
+//		else bd=true;
+//		INF^=(INF^Q.INF)&bd;
+	}
+
+/* return 1 if b==c, no branching */
+	public static int teq(int b,int c)
+	{
+		int x=b^c;
+		x-=1;  // if x=0, x now -1
+		return ((x>>31)&1);
+	}
+
+/* Constant time select from pre-computed table */
+	public void select(ECP4 W[],int b)
+	{
+		ECP4 MP=new ECP4(); 
+		int m=b>>31;
+		int babs=(b^m)-m;
+
+		babs=(babs-1)/2;
+
+		cmove(W[0],teq(babs,0));  // conditional move
+		cmove(W[1],teq(babs,1));
+		cmove(W[2],teq(babs,2));
+		cmove(W[3],teq(babs,3));
+		cmove(W[4],teq(babs,4));
+		cmove(W[5],teq(babs,5));
+		cmove(W[6],teq(babs,6));
+		cmove(W[7],teq(babs,7));
+ 
+		MP.copy(this);
+		MP.neg();
+		cmove(MP,(int)(m&1));
+	}
+
+/* Test if P == Q */
+	public boolean equals(ECP4 Q) {
+//		if (is_infinity() && Q.is_infinity()) return true;
+//		if (is_infinity() || Q.is_infinity()) return false;
+
+
+		FP4 a=new FP4(x);                            // *****
+		FP4 b=new FP4(Q.x);
+		a.mul(Q.z); 
+		b.mul(z); 
+		if (!a.equals(b)) return false;
+
+		a.copy(y); a.mul(Q.z); 
+		b.copy(Q.y); b.mul(z); 
+		if (!a.equals(b)) return false;
+
+		return true;
+	}
+/* set this=-this */
+	public void neg() {
+//		if (is_infinity()) return;
+		y.norm();
+		y.neg(); y.norm();
+		return;
+	}
+/* set to Affine - (x,y,z) to (x,y) */
+	public void affine() {
+		if (is_infinity()) return;
+		FP4 one=new FP4(1);
+		if (z.equals(one))
+		{
+			x.reduce();
+			y.reduce();
+			return;
+		}
+		z.inverse();
+
+		x.mul(z); x.reduce();               // *****
+		y.mul(z); y.reduce();
+		z.copy(one);
+	}
+
+/* extract affine x as FP4 */
+	public FP4 getX()
+	{
+		ECP4 W= new ECP4(this);
+		W.affine();
+		return W.x;
+	}
+/* extract affine y as FP4 */
+	public FP4 getY()
+	{
+		ECP4 W= new ECP4(this);
+		W.affine();
+		return W.y;
+	}
+/* extract projective x */
+	public FP4 getx()
+	{
+		return x;
+	}
+/* extract projective y */
+	public FP4 gety()
+	{
+		return y;
+	}
+/* extract projective z */
+	public FP4 getz()
+	{
+		return z;
+	}
+
+/* convert to byte array */
+	public void toBytes(byte[] b)
+	{
+		byte[] t=new byte[BIG.MODBYTES];
+		ECP4 W=new ECP4(this);
+		//affine();
+		int MB=BIG.MODBYTES;
+
+		W.x.geta().getA().toBytes(t);
+		for (int i=0;i<MB;i++) 
+			b[i]=t[i];
+		W.x.geta().getB().toBytes(t);
+		for (int i=0;i<MB;i++) 
+			b[i+MB]=t[i];
+		W.x.getb().getA().toBytes(t);
+		for (int i=0;i<MB;i++) 
+			b[i+2*MB]=t[i];
+		W.x.getb().getB().toBytes(t);
+		for (int i=0;i<MB;i++) 
+			b[i+3*MB]=t[i];
+
+		W.y.geta().getA().toBytes(t);
+		for (int i=0;i<MB;i++) 
+			b[i+4*MB]=t[i];
+		W.y.geta().getB().toBytes(t);
+		for (int i=0;i<MB;i++) 
+			b[i+5*MB]=t[i];
+		W.y.getb().getA().toBytes(t);
+		for (int i=0;i<MB;i++) 
+			b[i+6*MB]=t[i];
+		W.y.getb().getB().toBytes(t);
+		for (int i=0;i<MB;i++) 
+			b[i+7*MB]=t[i];
+
+	
+	}
+
+/* convert from byte array to point */
+	public static ECP4 fromBytes(byte[] b)
+	{
+		byte[] t=new byte[BIG.MODBYTES];
+		BIG ra;
+		BIG rb;
+		int MB=BIG.MODBYTES;
+
+		for (int i=0;i<MB;i++) {t[i]=b[i];}
+		ra=BIG.fromBytes(t);
+		for (int i=0;i<MB;i++) {t[i]=b[i+MB];}
+		rb=BIG.fromBytes(t);
+
+		FP2 ra4=new FP2(ra,rb);
+
+		for (int i=0;i<MB;i++) {t[i]=b[i+2*MB];}
+		ra=BIG.fromBytes(t);
+		for (int i=0;i<MB;i++) {t[i]=b[i+3*MB];}
+		rb=BIG.fromBytes(t);
+
+		FP2 rb4=new FP2(ra,rb);
+
+		FP4 rx=new FP4(ra4,rb4);
+
+		for (int i=0;i<MB;i++) {t[i]=b[i+4*MB];}
+		ra=BIG.fromBytes(t);
+		for (int i=0;i<MB;i++) {t[i]=b[i+5*MB];}
+		rb=BIG.fromBytes(t);
+
+		ra4=new FP2(ra,rb);
+
+		for (int i=0;i<MB;i++) {t[i]=b[i+6*MB];}
+		ra=BIG.fromBytes(t);
+		for (int i=0;i<MB;i++) {t[i]=b[i+7*MB];}
+		rb=BIG.fromBytes(t);
+
+		rb4=new FP2(ra,rb);
+		FP4 ry=new FP4(ra4,rb4);
+
+
+		return new ECP4(rx,ry);
+	}
+
+/* convert this to hex string */
+	public String toString() {
+		ECP4 W=new ECP4(this);	
+		W.affine();
+		if (W.is_infinity()) return "infinity";
+		return "("+W.x.toString()+","+W.y.toString()+")";
+	}
+
+/* Calculate RHS of twisted curve equation x^3+B/i */
+	public static FP4 RHS(FP4 x) {
+		x.norm();
+		FP4 r=new FP4(x);
+		r.sqr();
+		FP4 b=new FP4(new FP2(new BIG(ROM.CURVE_B)));
+
+		if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+		{
+			b.div_i();
+		}
+		if (ECP.SEXTIC_TWIST==ECP.M_TYPE)
+		{
+			b.times_i();
+		}
+
+
+		r.mul(x);
+		r.add(b);
+
+		r.reduce();
+		return r;
+	}
+
+/* construct this from (x,y) - but set to O if not on curve */
+	public ECP4(FP4 ix,FP4 iy) {
+		x=new FP4(ix);
+		y=new FP4(iy);
+		z=new FP4(1);
+		FP4 rhs=RHS(x);
+		FP4 y2=new FP4(y);
+		y2.sqr();
+		if (!y2.equals(rhs)) inf();
+		//if (y2.equals(rhs)) INF=false;
+		//else {x.zero();INF=true;}
+	}
+
+/* construct this from x - but set to O if not on curve */
+	public ECP4(FP4 ix) {
+		x=new FP4(ix);
+		y=new FP4(1);
+		z=new FP4(1);
+		FP4 rhs=RHS(x);
+		if (rhs.sqrt()) 
+		{
+			y.copy(rhs);
+			//INF=false;
+		}
+		else {inf(); /*x.zero();INF=true;*/}
+	}
+
+/* this+=this */
+	public int dbl() {
+//		if (INF) return -1;      
+
+		FP4 iy=new FP4(y);
+		if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+		{
+			iy.times_i(); //iy.norm();
+		}
+		FP4 t0=new FP4(y);                  //***** Change 
+		t0.sqr();            
+		if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+		{		
+			t0.times_i();
+		}
+		FP4 t1=new FP4(iy);  
+		t1.mul(z);
+		FP4 t2=new FP4(z);
+		t2.sqr();
+
+		z.copy(t0);
+		z.add(t0); z.norm(); 
+		z.add(z); 
+		z.add(z); 
+		z.norm();  
+
+		t2.imul(3*ROM.CURVE_B_I); 
+		if (ECP.SEXTIC_TWIST==ECP.M_TYPE)
+		{
+			t2.times_i();
+			//t2.norm();
+		}
+
+		FP4 x3=new FP4(t2);
+		x3.mul(z); 
+
+		FP4 y3=new FP4(t0);   
+
+		y3.add(t2); y3.norm();
+		z.mul(t1);
+		t1.copy(t2); t1.add(t2); t2.add(t1); t2.norm();  
+		t0.sub(t2); t0.norm();                           //y^2-9bz^2
+		y3.mul(t0); y3.add(x3);                          //(y^2+3z*2)(y^2-9z^2)+3b.z^2.8y^2
+		t1.copy(x); t1.mul(iy);						//
+		x.copy(t0); x.norm(); x.mul(t1); x.add(x);       //(y^2-9bz^2)xy2
+
+		x.norm(); 
+		y.copy(y3); y.norm();
+
+		return 1;
+	}
+
+/* this+=Q - return 0 for add, 1 for double, -1 for O */
+	public int add(ECP4 Q) {
+//		if (INF)
+//		{
+//			copy(Q);
+//			return -1;
+//		}
+//		if (Q.INF) return -1;
+
+		int b=3*ROM.CURVE_B_I;
+		FP4 t0=new FP4(x);
+		t0.mul(Q.x);         // x.Q.x
+		FP4 t1=new FP4(y);
+		t1.mul(Q.y);		 // y.Q.y
+
+		FP4 t2=new FP4(z);
+		t2.mul(Q.z);
+		FP4 t3=new FP4(x);
+		t3.add(y); t3.norm();          //t3=X1+Y1
+		FP4 t4=new FP4(Q.x);            
+		t4.add(Q.y); t4.norm();			//t4=X2+Y2
+		t3.mul(t4);						//t3=(X1+Y1)(X2+Y2)
+		t4.copy(t0); t4.add(t1);		//t4=X1.X2+Y1.Y2
+
+		t3.sub(t4); t3.norm(); 
+		if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+		{		
+			t3.times_i();  //t3.norm();         //t3=(X1+Y1)(X2+Y2)-(X1.X2+Y1.Y2) = X1.Y2+X2.Y1
+		}
+		t4.copy(y);                    
+		t4.add(z); t4.norm();			//t4=Y1+Z1
+		FP4 x3=new FP4(Q.y);
+		x3.add(Q.z); x3.norm();			//x3=Y2+Z2
+
+		t4.mul(x3);						//t4=(Y1+Z1)(Y2+Z2)
+		x3.copy(t1);					//
+		x3.add(t2);						//X3=Y1.Y2+Z1.Z2
+	
+		t4.sub(x3); t4.norm(); 
+		if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+		{	
+			t4.times_i(); //t4.norm();          //t4=(Y1+Z1)(Y2+Z2) - (Y1.Y2+Z1.Z2) = Y1.Z2+Y2.Z1
+		}
+		x3.copy(x); x3.add(z); x3.norm();	// x3=X1+Z1
+		FP4 y3=new FP4(Q.x);				
+		y3.add(Q.z); y3.norm();				// y3=X2+Z2
+		x3.mul(y3);							// x3=(X1+Z1)(X2+Z2)
+		y3.copy(t0);
+		y3.add(t2);							// y3=X1.X2+Z1+Z2
+		y3.rsub(x3); y3.norm();				// y3=(X1+Z1)(X2+Z2) - (X1.X2+Z1.Z2) = X1.Z2+X2.Z1
+
+		if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+		{
+			t0.times_i(); //t0.norm(); // x.Q.x
+			t1.times_i(); //t1.norm(); // y.Q.y
+		}
+		x3.copy(t0); x3.add(t0); 
+		t0.add(x3); t0.norm();
+		t2.imul(b); 	
+		if (ECP.SEXTIC_TWIST==ECP.M_TYPE)
+		{
+			t2.times_i();
+		}
+		FP4 z3=new FP4(t1); z3.add(t2); z3.norm();
+		t1.sub(t2); t1.norm(); 
+		y3.imul(b); 
+		if (ECP.SEXTIC_TWIST==ECP.M_TYPE)
+		{
+			y3.times_i(); 
+			//y3.norm();
+		}
+		x3.copy(y3); x3.mul(t4); t2.copy(t3); t2.mul(t1); x3.rsub(t2);
+		y3.mul(t0); t1.mul(z3); y3.add(t1);
+		t0.mul(t3); z3.mul(t4); z3.add(t0);
+
+		x.copy(x3); x.norm(); 
+		y.copy(y3); y.norm();
+		z.copy(z3); z.norm();
+
+		return 0;
+	}
+
+/* set this-=Q */
+	public int sub(ECP4 Q) {
+		ECP4 NQ=new ECP4(Q);
+		NQ.neg();
+		int D=add(NQ);
+
+		//Q.neg();
+		//int D=add(Q);
+		//Q.neg();
+		return D;
+	}
+
+	public static FP2[] frob_constants() {
+			BIG Fra=new BIG(ROM.Fra);
+			BIG Frb=new BIG(ROM.Frb);
+			FP2 X=new FP2(Fra,Frb);
+
+			FP2 F0=new FP2(X); F0.sqr();
+			FP2 F2=new FP2(F0);
+			F2.mul_ip(); F2.norm();
+			FP2 F1=new FP2(F2); F1.sqr();
+			F2.mul(F1);
+			F1.copy(X);
+			if (ECP.SEXTIC_TWIST == ECP.M_TYPE)
+			{
+				F1.mul_ip();
+				F1.inverse();
+				F0.copy(F1); F0.sqr();
+			}
+			F0.mul_ip(); F0.norm();
+			F1.mul(F0);
+			FP2[] F={F0,F1,F2};
+			return F;
+	}
+
+
+/* set this*=q, where q is Modulus, using Frobenius */
+	public void frob(FP2 F[],int n)
+	{
+//		if (INF) return;
+		for (int i=0;i<n;i++) {
+			x.frob(F[2]);
+			x.pmul(F[0]);
+		
+			y.frob(F[2]);
+			y.pmul(F[1]);
+			y.times_i();
+
+			z.frob(F[2]);
+		}
+	}
+
+/* P*=e */
+	public ECP4 mul(BIG e)
+	{
+/* fixed size windows */
+		int i,b,nb,m,s,ns;
+		BIG mt=new BIG();
+		BIG t=new BIG();
+		ECP4 P=new ECP4();
+		ECP4 Q=new ECP4();
+		ECP4 C=new ECP4();
+		ECP4[] W=new ECP4[8];
+		byte[] w=new byte[1+(BIG.NLEN*BIG.BASEBITS+3)/4];
+
+		if (is_infinity()) return new ECP4();
+
+		//affine();
+
+/* precompute table */
+		Q.copy(this);
+		Q.dbl();
+		W[0]=new ECP4();
+		W[0].copy(this);
+
+		for (i=1;i<8;i++)
+		{
+			W[i]=new ECP4();
+			W[i].copy(W[i-1]);
+			W[i].add(Q);
+		}
+
+/* make exponent odd - add 2P if even, P if odd */
+		t.copy(e);
+		s=t.parity();
+		t.inc(1); t.norm(); ns=t.parity(); mt.copy(t); mt.inc(1); mt.norm();
+		t.cmove(mt,s);
+		Q.cmove(this,ns);
+		C.copy(Q);
+
+		nb=1+(t.nbits()+3)/4;
+/* convert exponent to signed 4-bit window */
+		for (i=0;i<nb;i++)
+		{
+			w[i]=(byte)(t.lastbits(5)-16);
+			t.dec(w[i]); t.norm();
+			t.fshr(4);	
+		}
+		w[nb]=(byte)t.lastbits(5);
+	
+		P.copy(W[(w[nb]-1)/2]);  
+		for (i=nb-1;i>=0;i--)
+		{
+			Q.select(W,w[i]);
+			P.dbl();
+			P.dbl();
+			P.dbl();
+			P.dbl();
+			P.add(Q);
+		}
+		P.sub(C);
+		P.affine();
+		return P;
+	}
+
+/* P=u0.Q0+u1*Q1+u2*Q2+u3*Q3... */
+// Bos & Costello https://eprint.iacr.org/2013/458.pdf
+// Faz-Hernandez & Longa & Sanchez  https://eprint.iacr.org/2013/158.pdf
+// Side channel attack secure 
+
+	public static ECP4 mul8(ECP4[] Q,BIG[] u)
+	{
+		int i,j,k,nb,pb1,pb2;
+		ECP4 W=new ECP4();
+		ECP4 P=new ECP4();
+		ECP4[] T1=new ECP4[8];
+		ECP4[] T2=new ECP4[8];
+
+
+		BIG mt=new BIG();
+		BIG[] t=new BIG[8];
+
+		byte[] w1=new byte[BIG.NLEN*BIG.BASEBITS+1];
+		byte[] s1=new byte[BIG.NLEN*BIG.BASEBITS+1];
+		byte[] w2=new byte[BIG.NLEN*BIG.BASEBITS+1];
+		byte[] s2=new byte[BIG.NLEN*BIG.BASEBITS+1];
+
+		for (i=0;i<8;i++)
+		{
+			t[i]=new BIG(u[i]);
+			//Q[i].affine();
+			t[i].norm();
+		}
+
+        T1[0] = new ECP4(); T1[0].copy(Q[0]);  // Q[0]
+        T1[1] = new ECP4(); T1[1].copy(T1[0]); T1[1].add(Q[1]);  // Q[0]+Q[1]
+        T1[2] = new ECP4(); T1[2].copy(T1[0]); T1[2].add(Q[2]);  // Q[0]+Q[2]
+        T1[3] = new ECP4(); T1[3].copy(T1[1]); T1[3].add(Q[2]);  // Q[0]+Q[1]+Q[2]
+        T1[4] = new ECP4(); T1[4].copy(T1[0]); T1[4].add(Q[3]);  // Q[0]+Q[3]
+        T1[5] = new ECP4(); T1[5].copy(T1[1]); T1[5].add(Q[3]);  // Q[0]+Q[1]+Q[3]
+        T1[6] = new ECP4(); T1[6].copy(T1[2]); T1[6].add(Q[3]);  // Q[0]+Q[2]+Q[3]
+        T1[7] = new ECP4(); T1[7].copy(T1[3]); T1[7].add(Q[3]);  // Q[0]+Q[1]+Q[2]+Q[3]
+
+//  Use Frobenius 
+		FP2[] F=ECP4.frob_constants();
+
+		for (i=0;i<8;i++) {
+			T2[i] = new ECP4(); T2[i].copy(T1[i]);
+			T2[i].frob(F,4);
+		}
+
+    // Make it odd
+        pb1=1-t[0].parity();
+        t[0].inc(pb1);
+        t[0].norm();
+
+        pb2=1-t[4].parity();
+        t[4].inc(pb2);
+        t[4].norm();
+
+
+    // Number of bits
+        mt.zero();
+        for (i=0;i<8;i++) {
+            mt.or(t[i]); 
+        }
+        nb=1+mt.nbits();
+
+    // Sign pivot 
+        s1[nb-1]=1;
+		s2[nb-1]=1;
+        for (i=0;i<nb-1;i++) {
+            t[0].fshr(1);
+            s1[i]=(byte)(2*t[0].parity()-1);
+            t[4].fshr(1);
+            s2[i]=(byte)(2*t[4].parity()-1);
+        }
+
+    // Recoded exponent
+        for (i=0; i<nb; i++) {
+            w1[i]=0;
+            k=1;
+            for (j=1; j<4; j++) {
+                byte bt=(byte)(s1[i]*t[j].parity());
+                t[j].fshr(1);
+                t[j].dec((int)(bt)>>1);
+                t[j].norm();
+                w1[i]+=bt*(byte)k;
+                k*=2;
+            }
+
+            w2[i]=0;
+            k=1;
+            for (j=5; j<8; j++) {
+                byte bt=(byte)(s2[i]*t[j].parity());
+                t[j].fshr(1);
+                t[j].dec((int)(bt)>>1);
+                t[j].norm();
+                w2[i]+=bt*(byte)k;
+                k*=2;
+            }
+        } 
+
+    // Main loop
+        P.select(T1,(int)(2*w1[nb-1]+1));  
+		W.select(T2,(int)(2*w2[nb-1]+1)); 
+		P.add(W);
+        for (i=nb-2;i>=0;i--) {
+            P.dbl();
+            W.select(T1,(int)(2*w1[i]+s1[i]));
+            P.add(W);
+            W.select(T2,(int)(2*w2[i]+s2[i]));
+            P.add(W);
+
+        }
+
+    // apply correction
+        W.copy(P);   
+        W.sub(Q[0]);
+        P.cmove(W,pb1);   
+
+        W.copy(P);   
+        W.sub(Q[4]);
+        P.cmove(W,pb2);  
+
+		P.affine();
+		return P;
+	}        
+
+/* needed for SOK */
+	public static ECP4 mapit(byte[] h)
+	{
+		BIG q=new BIG(ROM.Modulus);
+		BIG x=BIG.fromBytes(h);
+		BIG one=new BIG(1);
+		FP4 X;
+		FP2 X2;
+		ECP4 Q;
+		x.mod(q);
+		while (true)
+		{
+			X2=new FP2(one,x);
+			X=new FP4(X2);
+			Q=new ECP4(X);
+			if (!Q.is_infinity()) break;
+			x.inc(1); x.norm();
+		}
+
+		FP2[] F=ECP4.frob_constants();
+		x=new BIG(ROM.CURVE_Bnx);
+
+/* Efficient hash maps to G2 on BLS curves - Budroni, Pintore */
+
+		ECP4 xQ=Q.mul(x);
+		ECP4 x2Q=xQ.mul(x);
+		ECP4 x3Q=x2Q.mul(x);
+		ECP4 x4Q=x3Q.mul(x);
+
+		if (ECP.SIGN_OF_X==ECP.NEGATIVEX)
+		{
+			xQ.neg();
+			x3Q.neg();
+		}	
+
+		x4Q.sub(x3Q);
+		x4Q.sub(Q);
+
+		x3Q.sub(x2Q);
+		x3Q.frob(F,1);
+
+		x2Q.sub(xQ);
+		x2Q.frob(F,2);
+
+		xQ.sub(Q);
+		xQ.frob(F,3);
+
+		Q.dbl();
+		Q.frob(F,4);
+
+		Q.add(x4Q);
+		Q.add(x3Q);
+		Q.add(x2Q);
+		Q.add(xQ);
+
+		Q.affine();
+		return Q;
+	}
+
+	public static ECP4 generator()
+	{
+
+		return new ECP4(
+			new FP4(
+				new FP2(
+					new BIG(ROM.CURVE_Pxaa),new BIG(ROM.CURVE_Pxab)),
+				new FP2(
+					new BIG(ROM.CURVE_Pxba),new BIG(ROM.CURVE_Pxbb))),
+			new FP4(
+				new FP2(
+					new BIG(ROM.CURVE_Pyaa),new BIG(ROM.CURVE_Pyab)),
+				new FP2(
+					new BIG(ROM.CURVE_Pyba),new BIG(ROM.CURVE_Pybb))));
+	}
+
+}
diff --git a/src/main/java/org/apache/milagro/amcl/BLS24/FP.java b/src/main/java/org/apache/milagro/amcl/BLS24/FP.java
new file mode 100644
index 0000000..1a0258f
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS24/FP.java
@@ -0,0 +1,526 @@
+/*
+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.
+*/
+
+/* Finite Field arithmetic */
+/* AMCL mod p functions */
+
+package org.apache.milagro.amcl.BLS24;
+
+public final class FP {
+
+	public static final int NOT_SPECIAL=0;
+	public static final int PSEUDO_MERSENNE=1;
+	public static final int MONTGOMERY_FRIENDLY=2;
+	public static final int GENERALISED_MERSENNE=3;
+
+	public static final int MODBITS=479; /* Number of bits in Modulus */
+	public static final int MOD8=3;  /* Modulus mod 8 */
+	public static final int MODTYPE=NOT_SPECIAL;
+
+	public static final int FEXCESS =((int)1<<25);  // BASEBITS*NLEN-MODBITS or 2^30 max!
+	public static final long OMASK=(long)(-1)<<(MODBITS%BIG.BASEBITS);
+	public static final int TBITS=MODBITS%BIG.BASEBITS; // Number of active bits in top word 
+	public static final long TMASK=((long)1<<TBITS)-1;
+
+
+	public final BIG x;
+	//public BIG p=new BIG(ROM.Modulus);
+	//public BIG r2modp=new BIG(ROM.R2modp);
+	public int XES;
+
+/**************** 64-bit specific ************************/
+
+/* reduce a DBIG to a BIG using the appropriate form of the modulus */
+	public static BIG mod(DBIG d)
+	{
+		if (MODTYPE==PSEUDO_MERSENNE)
+		{
+			BIG b;		
+			long v,tw;
+			BIG t=d.split(MODBITS);
+			b=new BIG(d);
+
+			v=t.pmul((int)ROM.MConst);
+
+			t.add(b);
+			t.norm();
+
+			tw=t.w[BIG.NLEN-1];
+			t.w[BIG.NLEN-1]&=FP.TMASK;
+			t.w[0]+=(ROM.MConst*((tw>>TBITS)+(v<<(BIG.BASEBITS-TBITS))));
+
+			t.norm();
+			return t;			
+		}
+		if (FP.MODTYPE==MONTGOMERY_FRIENDLY)
+		{
+			BIG b;		
+			long[] cr=new long[2];
+			for (int i=0;i<BIG.NLEN;i++)
+			{
+				cr=BIG.muladd(d.w[i],ROM.MConst-1,d.w[i],d.w[BIG.NLEN+i-1]);
+				d.w[BIG.NLEN+i]+=cr[0];
+				d.w[BIG.NLEN+i-1]=cr[1];
+			}
+			
+			b=new BIG(0);
+			for (int i=0;i<BIG.NLEN;i++ )
+				b.w[i]=d.w[BIG.NLEN+i];
+			b.norm();
+			return b;		
+		}
+		if (MODTYPE==GENERALISED_MERSENNE)
+		{ // GoldiLocks Only
+			BIG b;		
+			BIG t=d.split(MODBITS);
+			b=new BIG(d);
+			b.add(t);
+			DBIG dd=new DBIG(t);
+			dd.shl(MODBITS/2);
+
+			BIG tt=dd.split(MODBITS);
+			BIG lo=new BIG(dd);
+			b.add(tt);
+			b.add(lo);
+			b.norm();
+			tt.shl(MODBITS/2);
+			b.add(tt);
+
+			long carry=b.w[BIG.NLEN-1]>>TBITS;
+			b.w[BIG.NLEN-1]&=FP.TMASK;
+			b.w[0]+=carry;
+			
+			b.w[224/BIG.BASEBITS]+=carry<<(224%BIG.BASEBITS);
+			b.norm();
+			return b;		
+		}
+		if (MODTYPE==NOT_SPECIAL)
+		{
+			return BIG.monty(new BIG(ROM.Modulus),ROM.MConst,d);
+		}
+
+		return new BIG(0);
+	}
+
+
+
+/*********************************************************/
+
+
+/* Constructors */
+	public FP(int a)
+	{
+		x=new BIG(a);
+		nres();
+	}
+
+	public FP()
+	{
+		x=new BIG(0);
+		XES=1;
+	}
+
+	public FP(BIG a)
+	{
+		x=new BIG(a);
+		nres();
+	}
+	
+	public FP(FP a)
+	{
+		x=new BIG(a.x);
+		XES=a.XES;
+	}
+
+/* convert to string */
+	public String toString() 
+	{
+		String s=redc().toString();
+		return s;
+	}
+
+	public String toRawString() 
+	{
+		String s=x.toRawString();
+		return s;
+	}
+
+/* convert to Montgomery n-residue form */
+	public void nres()
+	{
+		if (MODTYPE!=PSEUDO_MERSENNE && MODTYPE!=GENERALISED_MERSENNE)
+		{
+			DBIG d=BIG.mul(x,new BIG(ROM.R2modp));  /*** Change ***/
+			x.copy(mod(d));
+			XES=2;
+		}
+		else XES=1;
+	}
+
+/* convert back to regular form */
+	public BIG redc()
+	{
+		if (MODTYPE!=PSEUDO_MERSENNE && MODTYPE!=GENERALISED_MERSENNE)
+		{
+			DBIG d=new DBIG(x);
+			return mod(d);
+		}
+		else 
+		{
+			BIG r=new BIG(x);
+			return r;
+		}
+	}
+
+/* test this=0? */
+	public boolean iszilch() {
+		FP z=new FP(this);
+		z.reduce();
+		return z.x.iszilch();
+
+	}
+
+/* copy from FP b */
+	public void copy(FP b)
+	{
+		x.copy(b.x);
+		XES=b.XES;
+	}
+
+/* set this=0 */
+	public void zero()
+	{
+		x.zero();
+		XES=1;
+	}
+	
+/* set this=1 */
+	public void one()
+	{
+		x.one(); nres();
+	}
+
+/* normalise this */
+	public void norm()
+	{
+		x.norm();
+	}
+
+/* swap FPs depending on d */
+	public void cswap(FP b,int d)
+	{
+		x.cswap(b.x,d);
+		int t,c=d;
+		c=~(c-1);
+		t=c&(XES^b.XES);
+		XES^=t;
+		b.XES^=t;
+	}
+
+/* copy FPs depending on d */
+	public void cmove(FP b,int d)
+	{
+		x.cmove(b.x,d);
+		XES^=(XES^b.XES)&(-d);
+
+	}
+
+/* this*=b mod Modulus */
+	public void mul(FP b)
+	{
+		if ((long)XES*b.XES>(long)FEXCESS) reduce();
+
+		DBIG d=BIG.mul(x,b.x);
+		x.copy(mod(d));
+		XES=2;
+	}
+
+/* this*=c mod Modulus, where c is a small int */
+	public void imul(int c)
+	{
+//		norm();
+		boolean s=false;
+		if (c<0)
+		{
+			c=-c;
+			s=true;
+		}
+
+		if (MODTYPE==PSEUDO_MERSENNE || MODTYPE==GENERALISED_MERSENNE)
+		{
+			DBIG d=x.pxmul(c);
+			x.copy(mod(d));
+			XES=2;
+		}
+		else
+		{
+			if (XES*c<=FEXCESS)
+			{
+				x.pmul(c);
+				XES*=c;
+			}
+			else
+			{  // this is not good
+				FP n=new FP(c);
+				mul(n);
+			}
+		}
+		
+/*
+		if (c<=BIG.NEXCESS && XES*c<=FEXCESS)
+		{
+			x.imul(c);
+			XES*=c;
+			x.norm();
+		}
+		else
+		{
+			DBIG d=x.pxmul(c);
+			x.copy(mod(d));
+			XES=2;
+		}
+*/
+		if (s) {neg(); norm();}
+
+	}
+
+/* this*=this mod Modulus */
+	public void sqr()
+	{
+		DBIG d;
+		if ((long)XES*XES>(long)FEXCESS) reduce();
+
+		d=BIG.sqr(x);	
+		x.copy(mod(d));
+		XES=2;
+	}
+
+/* this+=b */
+	public void add(FP b) {
+		x.add(b.x);
+		XES+=b.XES;
+		if (XES>FEXCESS) reduce();
+	}
+
+// https://graphics.stanford.edu/~seander/bithacks.html
+// constant time log to base 2 (or number of bits in)
+
+	private static int logb2(int v)
+	{
+		int r;
+		v |= v >>> 1;
+		v |= v >>> 2;
+		v |= v >>> 4;
+		v |= v >>> 8;
+		v |= v >>> 16;
+
+		v = v - ((v >>> 1) & 0x55555555);                  
+		v = (v & 0x33333333) + ((v >>> 2) & 0x33333333);  
+		r = ((v + (v >>> 4) & 0xF0F0F0F) * 0x1010101) >>> 24; 
+		return r;
+	}
+
+/* this = -this mod Modulus */
+	public void neg()
+	{
+		int sb;
+		BIG m=new BIG(ROM.Modulus);
+
+		sb=logb2(XES-1);
+		m.fshl(sb);
+		x.rsub(m);		
+
+		XES=(1<<sb);
+		if (XES>FEXCESS) reduce();
+	}
+
+/* this-=b */
+	public void sub(FP b)
+	{
+		FP n=new FP(b);
+		n.neg();
+		this.add(n);
+	}
+
+	public void rsub(FP b)
+	{
+		FP n=new FP(this);
+		n.neg();
+		this.copy(b);
+		this.add(n);
+	}
+
+/* this/=2 mod Modulus */
+	public void div2()
+	{
+		if (x.parity()==0)
+			x.fshr(1);
+		else
+		{
+			x.add(new BIG(ROM.Modulus));
+			x.norm();
+			x.fshr(1);
+		}
+	}
+
+/* this=1/this mod Modulus */
+	public void inverse()
+	{
+/*
+		BIG r=redc();
+		r.invmodp(p);
+		x.copy(r);
+		nres();
+*/
+		BIG m2=new BIG(ROM.Modulus);
+		m2.dec(2); m2.norm();
+		copy(pow(m2));
+
+	}
+
+/* return TRUE if this==a */
+	public boolean equals(FP a)
+	{
+		FP f=new FP(this);
+		FP s=new FP(a);
+		f.reduce();
+		s.reduce();
+		if (BIG.comp(f.x,s.x)==0) return true;
+		return false;
+	}
+
+/* reduce this mod Modulus */
+	public void reduce()
+	{
+		x.mod(new BIG(ROM.Modulus));
+		XES=1;
+	}
+
+	public FP pow(BIG e)
+	{
+		byte[] w=new byte[1+(BIG.NLEN*BIG.BASEBITS+3)/4];
+		FP [] tb=new FP[16];
+		BIG t=new BIG(e);
+		t.norm();
+		int nb=1+(t.nbits()+3)/4;
+
+		for (int i=0;i<nb;i++)
+		{
+			int lsbs=t.lastbits(4);
+			t.dec(lsbs);
+			t.norm();
+			w[i]=(byte)lsbs;
+			t.fshr(4);
+		}
+		tb[0]=new FP(1);
+		tb[1]=new FP(this);
+		for (int i=2;i<16;i++)
+		{
+			tb[i]=new FP(tb[i-1]);
+			tb[i].mul(this);
+		}
+		FP r=new FP(tb[w[nb-1]]);
+		for (int i=nb-2;i>=0;i--)
+		{
+			r.sqr();
+			r.sqr();
+			r.sqr();
+			r.sqr();
+			r.mul(tb[w[i]]);
+		}
+		r.reduce();
+		return r;
+	}
+
+/* return this^e mod Modulus 
+	public FP pow(BIG e)
+	{
+		int bt;
+		FP r=new FP(1);
+		e.norm();
+		x.norm();
+		FP m=new FP(this);
+		while (true)
+		{
+			bt=e.parity();
+			e.fshr(1);
+			if (bt==1) r.mul(m);
+			if (e.iszilch()) break;
+			m.sqr();
+		}
+		r.x.mod(p);
+		return r;
+	} */
+
+/* return sqrt(this) mod Modulus */
+	public FP sqrt()
+	{
+		reduce();
+		BIG b=new BIG(ROM.Modulus);
+		if (MOD8==5)
+		{
+			b.dec(5); b.norm(); b.shr(3);
+			FP i=new FP(this); i.x.shl(1);
+			FP v=i.pow(b);
+			i.mul(v); i.mul(v);
+			i.x.dec(1);
+			FP r=new FP(this);
+			r.mul(v); r.mul(i); 
+			r.reduce();
+			return r;
+		}
+		else
+		{
+			b.inc(1); b.norm(); b.shr(2);
+			return pow(b);
+		}
+	}
+
+/* return jacobi symbol (this/Modulus) */
+	public int jacobi()
+	{
+		BIG w=redc();
+		return w.jacobi(new BIG(ROM.Modulus));
+	}
+/*
+	public static void main(String[] args) {
+		BIG m=new BIG(ROM.Modulus);
+		BIG x=new BIG(3);
+		BIG e=new BIG(m);
+		e.dec(1);
+
+		System.out.println("m= "+m.nbits());	
+
+
+		BIG r=x.powmod(e,m);
+
+		System.out.println("m= "+m.toString());	
+		System.out.println("r= "+r.toString());	
+
+		BIG.cswap(m,r,0);
+
+		System.out.println("m= "+m.toString());	
+		System.out.println("r= "+r.toString());	
+
+//		FP y=new FP(3);
+//		FP s=y.pow(e);
+//		System.out.println("s= "+s.toString());	
+
+	} */
+}
diff --git a/src/main/java/org/apache/milagro/amcl/BLS24/FP2.java b/src/main/java/org/apache/milagro/amcl/BLS24/FP2.java
new file mode 100644
index 0000000..6704129
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS24/FP2.java
@@ -0,0 +1,425 @@
+/*
+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.
+*/
+
+/* Finite Field arithmetic  Fp^2 functions */
+
+/* FP2 elements are of the form a+ib, where i is sqrt(-1) */
+
+package org.apache.milagro.amcl.BLS24;
+
+public final class FP2 {
+	private final FP a;
+	private final FP b;
+
+/* reduce components mod Modulus */
+	public void reduce()
+	{
+		a.reduce();
+		b.reduce();
+	}
+
+/* normalise components of w */
+	public void norm()
+	{
+		a.norm();
+		b.norm();
+	}
+
+/* test this=0 ? */
+	public boolean iszilch() {
+		//reduce();
+		return (a.iszilch() && b.iszilch());
+	}
+
+	public void cmove(FP2 g,int d)
+	{
+		a.cmove(g.a,d);
+		b.cmove(g.b,d);
+	}
+
+/* test this=1 ? */
+	public boolean isunity() {
+		FP one=new FP(1);
+		return (a.equals(one) && b.iszilch());
+	}
+
+/* test this=x */
+	public boolean equals(FP2 x) {
+		return (a.equals(x.a) && b.equals(x.b));
+	}
+
+/* Constructors */
+	public FP2(int c)
+	{
+		a=new FP(c);
+		b=new FP(0);
+	}
+
+	public FP2(FP2 x)
+	{
+		a=new FP(x.a);
+		b=new FP(x.b);
+	}
+
+	public FP2(FP c,FP d)
+	{
+		a=new FP(c);
+		b=new FP(d);
+	}
+
+	public FP2(BIG c,BIG d)
+	{
+		a=new FP(c);
+		b=new FP(d);
+	}
+
+	public FP2(FP c)
+	{
+		a=new FP(c);
+		b=new FP(0);
+	}
+
+	public FP2(BIG c)
+	{
+		a=new FP(c);
+		b=new FP(0);
+	}
+/*
+	public BIG geta()
+	{
+		return a.tobig();
+	}
+*/
+/* extract a */
+	public BIG getA()
+	{ 
+		return a.redc();
+	}
+
+/* extract b */
+	public BIG getB()
+	{
+		return b.redc();
+	}
+
+/* copy this=x */
+	public void copy(FP2 x)
+	{
+		a.copy(x.a);
+		b.copy(x.b);
+	}
+
+/* set this=0 */
+	public void zero()
+	{
+		a.zero();
+		b.zero();
+	}
+
+/* set this=1 */
+	public void one()
+	{
+		a.one();
+		b.zero();
+	}
+
+/* negate this mod Modulus */
+	public void neg()
+	{
+		FP m=new FP(a);
+		FP t=new FP(0);
+
+		m.add(b);
+		m.neg();
+		t.copy(m); t.add(b);
+		b.copy(m);
+		b.add(a);
+		a.copy(t);
+	}
+
+/* set to a-ib */
+	public void conj()
+	{
+		b.neg();
+		b.norm();
+	}
+
+/* this+=a */
+	public void add(FP2 x)
+	{
+		a.add(x.a);
+		b.add(x.b);
+	}
+
+/* this-=a */
+	public void sub(FP2 x)
+	{
+		FP2 m=new FP2(x);
+		m.neg();
+		add(m);
+	}
+
+	public void rsub(FP2 x)       // *****
+	{
+		neg();
+		add(x);
+	}
+
+/* this*=s, where s is an FP */
+	public void pmul(FP s)
+	{
+		a.mul(s);
+		b.mul(s);
+	}
+
+/* this*=i, where i is an int */
+	public void imul(int c)
+	{
+		a.imul(c);
+		b.imul(c);
+	}
+
+/* this*=this */
+	public void sqr()
+	{
+		FP w1=new FP(a);
+		FP w3=new FP(a);
+		FP mb=new FP(b);
+
+		w1.add(b);
+		mb.neg();
+
+		w3.add(a);
+		w3.norm();
+		b.mul(w3);
+
+		a.add(mb);
+
+		w1.norm();
+		a.norm();
+
+		a.mul(w1);
+	}
+
+/* this*=y */
+/* Now uses Lazy reduction */
+	public void mul(FP2 y)
+	{
+		if ((long)(a.XES+b.XES)*(y.a.XES+y.b.XES)>(long)FP.FEXCESS)
+		{
+			if (a.XES>1) a.reduce();
+			if (b.XES>1) b.reduce();		
+		}
+
+		DBIG pR=new DBIG(0);
+		BIG C=new BIG(a.x);
+		BIG D=new BIG(y.a.x);
+
+		pR.ucopy(new BIG(ROM.Modulus));
+
+		DBIG A=BIG.mul(a.x,y.a.x);
+		DBIG B=BIG.mul(b.x,y.b.x);
+
+		C.add(b.x); C.norm();
+		D.add(y.b.x); D.norm();
+
+		DBIG E=BIG.mul(C,D);
+		DBIG F=new DBIG(A); F.add(B);
+		B.rsub(pR);
+
+		A.add(B); A.norm();
+		E.sub(F); E.norm();
+
+		a.x.copy(FP.mod(A)); a.XES=3;
+		b.x.copy(FP.mod(E)); b.XES=2;
+	}
+
+/* sqrt(a+ib) = sqrt(a+sqrt(a*a-n*b*b)/2)+ib/(2*sqrt(a+sqrt(a*a-n*b*b)/2)) */
+/* returns true if this is QR */
+	public boolean sqrt()
+	{
+		if (iszilch()) return true;
+		FP w1=new FP(b);
+		FP w2=new FP(a);
+		w1.sqr(); w2.sqr(); w1.add(w2);
+		if (w1.jacobi()!=1) { zero(); return false; }
+		w1=w1.sqrt();
+		w2.copy(a); w2.add(w1); 
+		w2.norm(); w2.div2();
+		if (w2.jacobi()!=1)
+		{
+			w2.copy(a); w2.sub(w1); 
+			w2.norm(); w2.div2();
+			if (w2.jacobi()!=1) { zero(); return false; }
+		}
+		w2=w2.sqrt();
+		a.copy(w2);
+		w2.add(w2);
+		w2.inverse();
+		b.mul(w2);
+		return true;
+	}
+
+/* output to hex string */
+	public String toString() 
+	{
+		return ("["+a.toString()+","+b.toString()+"]");
+	}
+
+	public String toRawString() 
+	{
+		return ("["+a.toRawString()+","+b.toRawString()+"]");
+	}
+
+/* this=1/this */
+	public void inverse()
+	{
+		norm();
+		FP w1=new FP(a);
+		FP w2=new FP(b);
+
+		w1.sqr();
+		w2.sqr();
+		w1.add(w2);
+		w1.inverse();
+		a.mul(w1);
+		w1.neg();
+		w1.norm();
+		b.mul(w1);
+	}
+
+/* this/=2 */
+	public void div2()
+	{
+		a.div2();
+		b.div2();
+	}
+
+/* this*=sqrt(-1) */
+	public void times_i()
+	{
+		FP z=new FP(a);
+		a.copy(b); a.neg();
+		b.copy(z);
+	}
+
+/* w*=(1+sqrt(-1)) */
+/* where X*2-(1+sqrt(-1)) is irreducible for FP4, assumes p=3 mod 8 */
+	public void mul_ip()
+	{
+		FP2 t=new FP2(this);
+		FP z=new FP(a);
+		a.copy(b);
+		a.neg();
+		b.copy(z);
+		add(t);
+	}
+
+	public void div_ip2()
+	{
+		FP2 t=new FP2(0);
+		norm();
+		t.a.copy(a); t.a.add(b);
+		t.b.copy(b); t.b.sub(a);
+		copy(t);
+		norm();
+	}
+
+/* w/=(1+sqrt(-1)) */
+	public void div_ip()
+	{
+		FP2 t=new FP2(0);
+		norm();
+		t.a.copy(a); t.a.add(b);
+		t.b.copy(b); t.b.sub(a);
+		copy(t);
+		norm();
+		div2();
+	}
+/*
+	public FP2 pow(BIG e)
+	{
+		int bt;
+		FP2 r=new FP2(1);
+		e.norm();
+		norm();
+		while (true)
+		{
+			bt=e.parity();
+			e.fshr(1);
+			if (bt==1) r.mul(this);
+			if (e.iszilch()) break;
+			sqr();
+		}
+
+		r.reduce();
+		return r;
+	}
+
+	public static void main(String[] args) {
+		BIG m=new BIG(ROM.Modulus);
+		BIG x=new BIG(3);
+		BIG e=new BIG(27);
+		BIG pp1=new BIG(m);
+		BIG pm1=new BIG(m);
+		BIG a=new BIG(1);
+		BIG b=new BIG(1);
+		FP2 w=new FP2(a,b);
+		FP2 z=new FP2(w);
+
+		byte[] RAW=new byte[100];
+
+		RAND rng=new RAND();
+		for (int i=0;i<100;i++) RAW[i]=(byte)(i);
+
+		rng.seed(100,RAW);
+
+	//	for (int i=0;i<100;i++)
+	//	{
+			a.randomnum(rng);
+			b.randomnum(rng);
+
+			w=new FP2(a,b);
+			System.out.println("w="+w.toString());
+
+			z=new FP2(w);
+			z.inverse();
+			System.out.println("z="+z.toString());
+
+			z.inverse();
+			if (!z.equals(w)) System.out.println("Error");
+	//	}
+
+//		System.out.println("m="+m.toString());
+//		w.sqr();
+//		w.mul(z);
+
+		System.out.println("w="+w.toString());
+
+
+		pp1.inc(1); pp1.norm();
+		pm1.dec(1); pm1.norm();
+		System.out.println("p+1="+pp1.toString());
+		System.out.println("p-1="+pm1.toString());
+		w=w.pow(pp1);
+		w=w.pow(pm1);
+		System.out.println("w="+w.toString());
+	}
+*/
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/milagro/amcl/BLS24/FP24.java b/src/main/java/org/apache/milagro/amcl/BLS24/FP24.java
new file mode 100644
index 0000000..d197f0d
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS24/FP24.java
@@ -0,0 +1,851 @@
+/*
+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.
+*/
+
+/* AMCL Fp^24 functions */
+/* FP24 elements are of the form a+i.b+i^2.c */
+
+package org.apache.milagro.amcl.BLS24;
+
+public final class FP24 {
+	private final FP8 a;
+	private final FP8 b;
+	private final FP8 c;
+/* reduce all components of this mod Modulus */
+	public void reduce()
+	{
+		a.reduce();
+		b.reduce();
+		c.reduce();
+	}
+
+/* normalise all components of this */
+	public void norm()
+	{
+		a.norm();
+		b.norm();
+		c.norm();
+	}
+/* test x==0 ? */
+	public boolean iszilch() {
+		//reduce();
+		return (a.iszilch() && b.iszilch() && c.iszilch());
+	}
+
+	public void cmove(FP24 g,int d)
+	{
+		a.cmove(g.a,d);
+		b.cmove(g.b,d);
+		c.cmove(g.c,d);		
+	}
+
+
+/* return 1 if b==c, no branching */
+	public static int teq(int b,int c)
+	{
+		int x=b^c;
+		x-=1;  // if x=0, x now -1
+		return ((x>>31)&1);
+	}
+
+/* Constant time select from pre-computed table */
+	public void select(FP24 g[],int b)
+	{
+		int m=b>>31;
+		int babs=(b^m)-m;
+
+		babs=(babs-1)/2;
+
+		cmove(g[0],teq(babs,0));  // conditional move
+		cmove(g[1],teq(babs,1));
+		cmove(g[2],teq(babs,2));
+		cmove(g[3],teq(babs,3));
+		cmove(g[4],teq(babs,4));
+		cmove(g[5],teq(babs,5));
+		cmove(g[6],teq(babs,6));
+		cmove(g[7],teq(babs,7));
+ 
+		FP24 invf=new FP24(this); 
+		invf.conj();
+		cmove(invf,(int)(m&1));
+	}
+
+	/* test x==1 ? */
+	public boolean isunity() {
+		FP8 one=new FP8(1);
+		return (a.equals(one) && b.iszilch() && c.iszilch());
+	}
+/* return 1 if x==y, else 0 */
+	public boolean equals(FP24 x)
+	{
+		return (a.equals(x.a) && b.equals(x.b) && c.equals(x.c));
+	}
+/* extract a from this */
+	public FP8 geta()
+	{
+		return a;
+	}
+/* extract b */
+	public FP8 getb()
+	{
+		return b;
+	}
+/* extract c */
+	public FP8 getc()
+	{
+		return c;
+	}
+/* copy this=x */
+	public void copy(FP24 x)
+	{
+		a.copy(x.a);
+		b.copy(x.b);
+		c.copy(x.c);
+	}
+/* set this=1 */
+	public void one()
+	{
+		a.one();
+		b.zero();
+		c.zero();
+	}
+/* this=conj(this) */
+	public void conj()
+	{
+		a.conj();
+		b.nconj();
+		c.conj();
+	}
+/* Constructors */
+	public FP24(FP8 d)
+	{
+		a=new FP8(d);
+		b=new FP8(0);
+		c=new FP8(0);
+	}
+
+	public FP24(int d)
+	{
+		a=new FP8(d);
+		b=new FP8(0);
+		c=new FP8(0);
+	}
+
+	public FP24(FP8 d,FP8 e,FP8 f)
+	{
+		a=new FP8(d);
+		b=new FP8(e);
+		c=new FP8(f);
+	}
+
+	public FP24(FP24 x)
+	{
+		a=new FP8(x.a);
+		b=new FP8(x.b);
+		c=new FP8(x.c);
+	}
+
+/* Granger-Scott Unitary Squaring */
+	public void usqr()
+	{
+//System.out.println("Into usqr");
+		FP8 A=new FP8(a);
+		FP8 B=new FP8(c);
+		FP8 C=new FP8(b);
+		FP8 D=new FP8(0);
+
+		a.sqr();
+		D.copy(a); D.add(a);
+		a.add(D);
+
+		a.norm();
+		A.nconj();
+
+		A.add(A);
+		a.add(A);
+		B.sqr();
+		B.times_i();
+
+		D.copy(B); D.add(B);
+		B.add(D);
+		B.norm();
+
+		C.sqr();
+		D.copy(C); D.add(C);
+		C.add(D);
+		C.norm();
+
+		b.conj();
+		b.add(b);
+		c.nconj();
+
+		c.add(c);
+		b.add(B);
+		c.add(C);
+		reduce();
+	}
+
+/* Chung-Hasan SQR2 method from http://cacr.uwaterloo.ca/techreports/2006/cacr2006-24.pdf */
+	public void sqr()
+	{
+		FP8 A=new FP8(a);
+		FP8 B=new FP8(b);
+		FP8 C=new FP8(c);
+		FP8 D=new FP8(a);
+
+		A.sqr();
+		B.mul(c);
+		B.add(B);
+	B.norm();
+		C.sqr();
+		D.mul(b);
+		D.add(D);
+
+		c.add(a);
+		c.add(b);
+	c.norm();
+		c.sqr();
+
+		a.copy(A);
+
+		A.add(B);
+		A.norm();
+		A.add(C);
+		A.add(D);
+		A.norm();
+
+		A.neg();
+		B.times_i();
+		C.times_i();
+
+		a.add(B);
+
+		b.copy(C); b.add(D);
+		c.add(A);
+
+		norm();
+	}
+
+/* FP12 full multiplication this=this*y */
+	public void mul(FP24 y)
+	{
+//System.out.println("Into mul");
+		FP8 z0=new FP8(a);
+		FP8 z1=new FP8(0);
+		FP8 z2=new FP8(b);
+		FP8 z3=new FP8(0);
+		FP8 t0=new FP8(a);
+		FP8 t1=new FP8(y.a);
+
+		z0.mul(y.a);
+		z2.mul(y.b);
+
+		t0.add(b);
+		t1.add(y.b);
+
+	t0.norm();
+	t1.norm();
+
+		z1.copy(t0); z1.mul(t1);
+		t0.copy(b); t0.add(c);
+
+		t1.copy(y.b); t1.add(y.c);
+
+	t0.norm();
+	t1.norm();
+
+		z3.copy(t0); z3.mul(t1);
+
+		t0.copy(z0); t0.neg();
+		t1.copy(z2); t1.neg();
+
+		z1.add(t0);
+		//z1.norm();
+		b.copy(z1); b.add(t1);
+
+		z3.add(t1);
+		z2.add(t0);
+
+		t0.copy(a); t0.add(c);
+		t1.copy(y.a); t1.add(y.c);
+
+t0.norm();
+t1.norm();
+	
+		t0.mul(t1);
+		z2.add(t0);
+
+		t0.copy(c); t0.mul(y.c);
+		t1.copy(t0); t1.neg();
+
+		c.copy(z2); c.add(t1);
+		z3.add(t1);
+		t0.times_i();
+		b.add(t0);
+	z3.norm();
+		z3.times_i();
+		a.copy(z0); a.add(z3);
+		norm();
+
+	}
+
+/* Special case of multiplication arises from special form of ATE pairing line function */
+	public void smul(FP24 y,int type)
+	{
+		if (type==ECP.D_TYPE)
+		{
+			FP8 z0=new FP8(a);
+			FP8 z2=new FP8(b);
+			FP8 z3=new FP8(b);
+			FP8 t0=new FP8(0);
+			FP8 t1=new FP8(y.a);
+			z0.mul(y.a);
+			z2.pmul(y.b.real());
+			b.add(a);
+			t1.real().add(y.b.real());
+
+			t1.norm();
+			b.norm();
+			b.mul(t1);
+			z3.add(c);
+			z3.norm();
+			z3.pmul(y.b.real());
+
+			t0.copy(z0); t0.neg();
+			t1.copy(z2); t1.neg();
+
+			b.add(t0);
+
+			b.add(t1);
+			z3.add(t1);
+			z2.add(t0);
+
+			t0.copy(a); t0.add(c);
+			t0.norm();
+			z3.norm();
+			t0.mul(y.a);
+			c.copy(z2); c.add(t0);
+
+			z3.times_i();
+			a.copy(z0); a.add(z3);
+		}
+		if (type==ECP.M_TYPE)
+		{
+			FP8 z0=new FP8(a);
+			FP8 z1=new FP8(0);
+			FP8 z2=new FP8(0);
+			FP8 z3=new FP8(0);
+			FP8 t0=new FP8(a);
+			FP8 t1=new FP8(0);
+		
+			z0.mul(y.a);
+			t0.add(b);
+			t0.norm();
+
+			z1.copy(t0); z1.mul(y.a);
+			t0.copy(b); t0.add(c);
+			t0.norm();
+
+			z3.copy(t0); //z3.mul(y.c);
+			z3.pmul(y.c.getb());
+			z3.times_i();
+
+			t0.copy(z0); t0.neg();
+
+			z1.add(t0);
+			b.copy(z1); 
+			z2.copy(t0);
+
+			t0.copy(a); t0.add(c);
+			t1.copy(y.a); t1.add(y.c);
+
+			t0.norm();
+			t1.norm();
+	
+			t0.mul(t1);
+			z2.add(t0);
+
+			t0.copy(c); 
+			
+			t0.pmul(y.c.getb());
+			t0.times_i();
+
+			t1.copy(t0); t1.neg();
+
+			c.copy(z2); c.add(t1);
+			z3.add(t1);
+			t0.times_i();
+			b.add(t0);
+			z3.norm();
+			z3.times_i();
+			a.copy(z0); a.add(z3);
+		}
+		norm();
+	}
+
+/* this=1/this */
+	public void inverse()
+	{
+		FP8 f0=new FP8(a);
+		FP8 f1=new FP8(b);
+		FP8 f2=new FP8(a);
+		FP8 f3=new FP8(0);
+
+		norm();
+		f0.sqr();
+		f1.mul(c);
+		f1.times_i();
+		f0.sub(f1);
+		f0.norm();
+
+		f1.copy(c); f1.sqr();
+		f1.times_i();
+		f2.mul(b);
+		f1.sub(f2);
+		f1.norm();
+
+		f2.copy(b); f2.sqr();
+		f3.copy(a); f3.mul(c);
+		f2.sub(f3);
+		f2.norm();
+
+		f3.copy(b); f3.mul(f2);
+		f3.times_i();
+		a.mul(f0);
+		f3.add(a);
+		c.mul(f1);
+		c.times_i();
+
+		f3.add(c);
+		f3.norm();
+		f3.inverse();
+		a.copy(f0); a.mul(f3);
+		b.copy(f1); b.mul(f3);
+		c.copy(f2); c.mul(f3);
+	}
+
+/* this=this^p using Frobenius */
+	public void frob(FP2 f,int n)
+	{
+		FP2 f2=new FP2(f);
+		FP2 f3=new FP2(f);
+
+		f2.sqr();
+		f3.mul(f2);
+
+		f3.mul_ip(); f3.norm();
+
+		for (int i=0;i<n;i++)
+		{
+			a.frob(f3);
+			b.frob(f3);
+			c.frob(f3);
+
+			b.qmul(f); b.times_i2();
+			c.qmul(f2); c.times_i2(); c.times_i2();
+		}
+	}
+
+/* trace function */
+	public FP8 trace()
+	{
+		FP8 t=new FP8(0);
+		t.copy(a);
+		t.imul(3);
+		t.reduce();
+		return t;
+	}
+
+/* convert from byte array to FP12 */
+	public static FP24 fromBytes(byte[] w)
+	{
+		BIG a,b;
+		FP2 c,d;
+		FP4 ea,eb;
+		FP8 e,f,g;
+		byte[] t=new byte[BIG.MODBYTES];
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		c=new FP2(a,b);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+2*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+3*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		d=new FP2(a,b);
+
+		ea=new FP4(c,d);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+4*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+5*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		c=new FP2(a,b);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+6*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+7*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		d=new FP2(a,b);
+
+		eb=new FP4(c,d);
+
+		e=new FP8(ea,eb);
+
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+8*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+9*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		c=new FP2(a,b);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+10*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+11*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		d=new FP2(a,b);
+
+		ea=new FP4(c,d);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+12*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+13*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		c=new FP2(a,b);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+14*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+15*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		d=new FP2(a,b);
+
+		eb=new FP4(c,d);
+
+		f=new FP8(ea,eb);
+
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+16*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+17*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		c=new FP2(a,b);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+18*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+19*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		d=new FP2(a,b);
+
+		ea=new FP4(c,d);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+20*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+21*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		c=new FP2(a,b);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+22*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+23*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		d=new FP2(a,b);
+
+		eb=new FP4(c,d);
+
+		g=new FP8(ea,eb);
+
+		return new FP24(e,f,g);
+	}
+
+/* convert this to byte array */
+	public void toBytes(byte[] w)
+	{
+		byte[] t=new byte[BIG.MODBYTES];
+
+		a.geta().geta().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i]=t[i];
+		a.geta().geta().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+BIG.MODBYTES]=t[i];
+		a.geta().getb().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+2*BIG.MODBYTES]=t[i];
+		a.geta().getb().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+3*BIG.MODBYTES]=t[i];
+
+		a.getb().geta().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+4*BIG.MODBYTES]=t[i];
+		a.getb().geta().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+5*BIG.MODBYTES]=t[i];
+		a.getb().getb().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+6*BIG.MODBYTES]=t[i];
+		a.getb().getb().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+7*BIG.MODBYTES]=t[i];
+
+
+		b.geta().geta().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+8*BIG.MODBYTES]=t[i];
+		b.geta().geta().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+9*BIG.MODBYTES]=t[i];
+		b.geta().getb().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+10*BIG.MODBYTES]=t[i];
+		b.geta().getb().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+11*BIG.MODBYTES]=t[i];
+
+		b.getb().geta().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+12*BIG.MODBYTES]=t[i];
+		b.getb().geta().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+13*BIG.MODBYTES]=t[i];
+		b.getb().getb().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+14*BIG.MODBYTES]=t[i];
+		b.getb().getb().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+15*BIG.MODBYTES]=t[i];
+
+		c.geta().geta().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+16*BIG.MODBYTES]=t[i];
+		c.geta().geta().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+17*BIG.MODBYTES]=t[i];
+		c.geta().getb().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+18*BIG.MODBYTES]=t[i];
+		c.geta().getb().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+19*BIG.MODBYTES]=t[i];
+
+		c.getb().geta().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+20*BIG.MODBYTES]=t[i];
+		c.getb().geta().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+21*BIG.MODBYTES]=t[i];
+		c.getb().getb().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+22*BIG.MODBYTES]=t[i];
+		c.getb().getb().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+23*BIG.MODBYTES]=t[i];
+
+	}
+
+/* convert to hex string */
+	public String toString() 
+	{
+		return ("["+a.toString()+","+b.toString()+","+c.toString()+"]");
+	}
+
+/* this=this^e */ 
+/* Note this is simple square and multiply, so not side-channel safe */
+	public FP24 pow(BIG e)
+	{
+		norm();
+		e.norm();
+		BIG e3=new BIG(e);
+		e3.pmul(3);
+		e3.norm();
+
+		FP24 w=new FP24(this);
+
+		int nb=e3.nbits();
+		for (int i=nb-2;i>=1;i--)
+		{
+			w.usqr();
+			int bt=e3.bit(i)-e.bit(i);
+			if (bt==1)
+				w.mul(this);
+			if (bt==-1)
+			{
+				conj(); w.mul(this); conj();
+			}
+		}
+		w.reduce();
+		return w;
+
+	}
+
+/* constant time powering by small integer of max length bts */
+	public void pinpow(int e,int bts)
+	{
+		int i,b;
+		FP24 [] R=new FP24[2];
+		R[0]=new FP24(1);
+		R[1]=new FP24(this);
+		for (i=bts-1;i>=0;i--)
+		{
+			b=(e>>i)&1;
+			R[1-b].mul(R[b]);
+			R[b].usqr();
+		}
+		this.copy(R[0]);
+	}
+
+	public FP8 compow(BIG e,BIG r)
+	{
+		FP24 g1=new FP24(0);
+		FP24 g2=new FP24(0);
+		FP2 f=new FP2(new BIG(ROM.Fra),new BIG(ROM.Frb));
+		BIG q=new BIG(ROM.Modulus);
+
+		BIG m=new BIG(q);
+		m.mod(r);
+
+		BIG a=new BIG(e);
+		a.mod(m);
+
+		BIG b=new BIG(e);
+		b.div(m);
+
+		g1.copy(this);
+		g2.copy(this);
+
+		FP8 c=g1.trace();
+
+		if (b.iszilch())
+		{
+			c=c.xtr_pow(e);
+			return c;
+		}
+
+		g2.frob(f,1);
+		FP8 cp=g2.trace();
+		g1.conj();
+		g2.mul(g1);
+		FP8 cpm1=g2.trace();
+		g2.mul(g1);
+		FP8 cpm2=g2.trace();
+
+		c=c.xtr_pow2(cp,cpm1,cpm2,a,b);
+
+		return c;
+	}
+
+/* p=q0^u0.q1^u1.q2^u2.q3^u3.... */
+// Bos & Costello https://eprint.iacr.org/2013/458.pdf
+// Faz-Hernandez & Longa & Sanchez  https://eprint.iacr.org/2013/158.pdf
+// Side channel attack secure 
+
+	public static FP24 pow8(FP24[] q,BIG[] u)
+	{
+		int i,j,k,nb,pb1,pb2;
+		FP24 [] g1=new FP24[8];
+		FP24 [] g2=new FP24[8];
+		FP24 r=new FP24(1);
+		FP24 p=new FP24(0);
+		BIG [] t=new BIG[8];
+		BIG mt=new BIG(0);
+		byte[] w1=new byte[BIG.NLEN*BIG.BASEBITS+1];
+		byte[] s1=new byte[BIG.NLEN*BIG.BASEBITS+1];
+		byte[] w2=new byte[BIG.NLEN*BIG.BASEBITS+1];
+		byte[] s2=new byte[BIG.NLEN*BIG.BASEBITS+1];
+
+		for (i=0;i<8;i++)
+		{
+			t[i]=new BIG(u[i]);
+			t[i].norm();
+		}
+
+		g1[0]=new FP24(q[0]);  // q[0]
+		g1[1]=new FP24(g1[0]); g1[1].mul(q[1]); // q[0].q[1]
+		g1[2]=new FP24(g1[0]); g1[2].mul(q[2]); // q[0].q[2]
+		g1[3]=new FP24(g1[1]); g1[3].mul(q[2]); // q[0].q[1].q[2]
+		g1[4]=new FP24(q[0]);  g1[4].mul(q[3]); // q[0].q[3]
+		g1[5]=new FP24(g1[1]); g1[5].mul(q[3]); // q[0].q[1].q[3]
+		g1[6]=new FP24(g1[2]); g1[6].mul(q[3]); // q[0].q[2].q[3]
+		g1[7]=new FP24(g1[3]); g1[7].mul(q[3]); // q[0].q[1].q[2].q[3]
+
+// Use Frobenius
+		FP2 f=new FP2(new BIG(ROM.Fra),new BIG(ROM.Frb));
+		for (i=0;i<8;i++)
+		{
+			g2[i]=new FP24(g1[i]);
+			g2[i].frob(f,4);
+		}
+
+    // Make it odd
+        pb1=1-t[0].parity();
+        t[0].inc(pb1);
+        t[0].norm();
+
+        pb2=1-t[4].parity();
+        t[4].inc(pb2);
+        t[4].norm();
+
+
+    // Number of bits
+        mt.zero();
+        for (i=0;i<8;i++) {
+            mt.or(t[i]); 
+        }
+        nb=1+mt.nbits();
+
+     // Sign pivot 
+        s1[nb-1]=1;
+		s2[nb-1]=1;
+        for (i=0;i<nb-1;i++) {
+            t[0].fshr(1);
+            s1[i]=(byte)(2*t[0].parity()-1);
+            t[4].fshr(1);
+            s2[i]=(byte)(2*t[4].parity()-1);
+        }
+
+    // Recoded exponent
+        for (i=0; i<nb; i++) {
+            w1[i]=0;
+            k=1;
+            for (j=1; j<4; j++) {
+                byte bt=(byte)(s1[i]*t[j].parity());
+                t[j].fshr(1);
+                t[j].dec((int)(bt)>>1);
+                t[j].norm();
+                w1[i]+=bt*(byte)k;
+                k*=2;
+            }
+
+            w2[i]=0;
+            k=1;
+            for (j=5; j<8; j++) {
+                byte bt=(byte)(s2[i]*t[j].parity());
+                t[j].fshr(1);
+                t[j].dec((int)(bt)>>1);
+                t[j].norm();
+                w2[i]+=bt*(byte)k;
+                k*=2;
+            }
+        } 
+
+     // Main loop
+        p.select(g1,(int)(2*w1[nb-1]+1)); 
+		r.select(g2,(int)(2*w2[nb-1]+1)); 
+		p.mul(r);
+        for (i=nb-2;i>=0;i--) {
+            p.usqr();
+            r.select(g1,(int)(2*w1[i]+s1[i]));
+            p.mul(r);
+            r.select(g2,(int)(2*w2[i]+s2[i]));
+            p.mul(r);
+
+        }
+
+    // apply correction
+        r.copy(q[0]); r.conj();   
+        r.mul(p);
+        p.cmove(r,pb1);
+
+        r.copy(q[4]); r.conj();   
+        r.mul(p);
+        p.cmove(r,pb2);
+
+ 		p.reduce();
+		return p;
+	}              
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/milagro/amcl/BLS24/FP4.java b/src/main/java/org/apache/milagro/amcl/BLS24/FP4.java
new file mode 100644
index 0000000..4580325
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS24/FP4.java
@@ -0,0 +1,721 @@
+/*
+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.
+*/
+
+/* Finite Field arithmetic  Fp^4 functions */
+
+/* FP4 elements are of the form a+ib, where i is sqrt(-1+sqrt(-1))  */
+
+package org.apache.milagro.amcl.BLS24;
+
+public final class FP4 {
+	private final FP2 a;
+	private final FP2 b;
+/* reduce all components of this mod Modulus */
+	public void reduce()
+	{
+		a.reduce();
+		b.reduce();
+	}
+/* normalise all components of this mod Modulus */
+	public void norm()
+	{
+		a.norm();
+		b.norm();
+	}
+/* test this==0 ? */
+	public boolean iszilch() {
+		//reduce();
+		return (a.iszilch() && b.iszilch());
+	}
+
+	public void cmove(FP4 g,int d)
+	{
+		a.cmove(g.a,d);
+		b.cmove(g.b,d);
+	}
+
+/* test this==1 ? */
+	public boolean isunity() {
+		FP2 one=new FP2(1);
+		return (a.equals(one) && b.iszilch());
+	}
+
+/* test is w real? That is in a+ib test b is zero */
+	public boolean isreal()
+	{
+		return b.iszilch();
+	}
+/* extract real part a */
+	public FP2 real()
+	{
+		return a;
+	}
+
+	public FP2 geta()
+	{
+		return a;
+	}
+/* extract imaginary part b */
+	public FP2 getb()
+	{
+		return b;
+	}
+/* test this=x? */
+	public boolean equals(FP4 x)
+	{
+		return (a.equals(x.a) && b.equals(x.b));
+	}
+/* constructors */
+	public FP4(int c)
+	{
+		a=new FP2(c);
+		b=new FP2(0);
+	}
+
+	public FP4(FP4 x)
+	{
+		a=new FP2(x.a);
+		b=new FP2(x.b);
+	}
+
+	public FP4(FP2 c,FP2 d)
+	{
+		a=new FP2(c);
+		b=new FP2(d);
+	}
+
+	public FP4(FP2 c)
+	{
+		a=new FP2(c);
+		b=new FP2(0);
+	}
+/* copy this=x */
+	public void copy(FP4 x)
+	{
+		a.copy(x.a);
+		b.copy(x.b);
+	}
+/* set this=0 */
+	public void zero()
+	{
+		a.zero();
+		b.zero();
+	}
+/* set this=1 */
+	public void one()
+	{
+		a.one();
+		b.zero();
+	}
+/* set this=-this */
+	public void neg()
+	{
+		norm();
+		FP2 m=new FP2(a);
+		FP2 t=new FP2(0);
+		m.add(b);
+//	m.norm();
+		m.neg();
+	//	m.norm();
+		t.copy(m); t.add(b);
+		b.copy(m);
+		b.add(a);
+		a.copy(t);
+	norm();
+	}
+/* this=conjugate(this) */
+	public void conj()
+	{
+		b.neg(); norm();
+	}
+/* this=-conjugate(this) */
+	public void nconj()
+	{
+		a.neg(); norm();
+	}
+/* this+=x */
+	public void add(FP4 x)
+	{
+		a.add(x.a);
+		b.add(x.b);
+	}
+/* this-=x */
+	public void sub(FP4 x)
+	{
+		FP4 m=new FP4(x);
+		m.neg();
+		add(m);
+	}
+
+/* this*=s where s is FP2 */
+	public void pmul(FP2 s)
+	{
+		a.mul(s);
+		b.mul(s);
+	}
+
+/* this=x-this */
+	public void rsub(FP4 x)
+	{
+		neg();
+		add(x);
+	}
+
+
+/* this*=c where c is int */
+	public void imul(int c)
+	{
+		a.imul(c);
+		b.imul(c);
+	}
+/* this*=this */	
+	public void sqr()
+	{
+//		norm();
+
+		FP2 t1=new FP2(a);
+		FP2 t2=new FP2(b);
+		FP2 t3=new FP2(a);
+
+		t3.mul(b);
+		t1.add(b);
+		t2.mul_ip();
+
+		t2.add(a);
+
+		t1.norm();
+		t2.norm();
+
+		a.copy(t1);
+
+		a.mul(t2);
+
+		t2.copy(t3);
+		t2.mul_ip();
+		t2.add(t3);
+		t2.norm();
+		t2.neg();
+		a.add(t2);
+
+		b.copy(t3);
+		b.add(t3);
+
+		norm();
+	}
+/* this*=y */
+	public void mul(FP4 y)
+	{
+//		norm();
+
+		FP2 t1=new FP2(a);
+		FP2 t2=new FP2(b);
+		FP2 t3=new FP2(0);
+		FP2 t4=new FP2(b);
+
+		t1.mul(y.a);
+		t2.mul(y.b);
+		t3.copy(y.b);
+		t3.add(y.a);
+		t4.add(a);
+
+	t3.norm();
+	t4.norm();
+
+		t4.mul(t3);
+
+	t3.copy(t1);
+	t3.neg();
+	t4.add(t3);
+	t4.norm();
+
+	//	t4.sub(t1);
+	//	t4.norm();
+
+	t3.copy(t2);
+	t3.neg();
+	b.copy(t4);
+	b.add(t3);
+
+	//	b.copy(t4);
+	//	b.sub(t2);
+
+		t2.mul_ip();
+		a.copy(t2);
+		a.add(t1);
+
+		norm();
+	}
+/* convert this to hex string */
+	public String toString() 
+	{
+		return ("["+a.toString()+","+b.toString()+"]");
+	}
+
+	public String toRawString() 
+	{
+		return ("["+a.toRawString()+","+b.toRawString()+"]");
+	}
+
+/* this=1/this */
+	public void inverse()
+	{
+//		norm();
+
+		FP2 t1=new FP2(a);
+		FP2 t2=new FP2(b);
+
+		t1.sqr();
+		t2.sqr();
+		t2.mul_ip();
+	t2.norm();
+		t1.sub(t2);
+		t1.inverse();
+		a.mul(t1);
+		t1.neg();
+	t1.norm();
+		b.mul(t1);
+	}
+
+
+/* this*=i where i = sqrt(-1+sqrt(-1)) */
+	public void times_i()
+	{
+//		norm();
+		FP2 s=new FP2(b);
+		FP2 t=new FP2(b);
+		s.times_i();
+		t.add(s);
+	//	t.norm();
+		b.copy(a);
+		a.copy(t);
+		norm();
+	}
+
+/* this=this^p using Frobenius */
+	public void frob(FP2 f)
+	{
+		a.conj();
+		b.conj();
+		b.mul(f);
+	}
+
+/* this=this^e */
+	public FP4 pow(BIG e)
+	{
+		norm();
+		e.norm();
+		FP4 w=new FP4(this);
+		BIG z=new BIG(e);
+		FP4 r=new FP4(1);
+		while (true)
+		{
+			int bt=z.parity();
+			z.fshr(1);
+			if (bt==1) r.mul(w);
+			if (z.iszilch()) break;
+			w.sqr();
+		}
+		r.reduce();
+		return r;
+	}
+/* XTR xtr_a function */
+	public void xtr_A(FP4 w,FP4 y,FP4 z) 
+	{
+		FP4 r=new FP4(w);
+		FP4 t=new FP4(w);
+	//y.norm();
+		r.sub(y);
+	r.norm();
+		r.pmul(a);
+		t.add(y);
+	t.norm();
+		t.pmul(b);
+		t.times_i();
+
+		copy(r);
+		add(t);
+		add(z);
+
+		norm();
+	}
+
+/* XTR xtr_d function */
+	public void xtr_D() {
+		FP4 w=new FP4(this);
+		sqr(); w.conj();
+		w.add(w);
+	w.norm();
+		sub(w);
+		reduce();
+	}
+
+/* r=x^n using XTR method on traces of FP12s */
+	public FP4 xtr_pow(BIG n) {
+		FP4 a=new FP4(3);
+		FP4 b=new FP4(this);
+		FP4 c=new FP4(b);
+		c.xtr_D();
+		FP4 t=new FP4(0);
+		FP4 r=new FP4(0);
+
+		n.norm();
+		int par=n.parity();
+		BIG v=new BIG(n); v.fshr(1);
+		if (par==0) {v.dec(1); v.norm();}
+
+		int nb=v.nbits();
+		for (int i=nb-1;i>=0;i--)
+		{
+			if (v.bit(i)!=1)
+			{
+				t.copy(b);
+				conj();
+				c.conj();
+				b.xtr_A(a,this,c);
+				conj();
+				c.copy(t);
+				c.xtr_D();
+				a.xtr_D();
+			}
+			else
+			{
+				t.copy(a); t.conj();
+				a.copy(b);
+				a.xtr_D();
+				b.xtr_A(c,this,t);
+				c.xtr_D();
+			}
+		}
+		if (par==0) r.copy(c);
+		else r.copy(b);
+		r.reduce();
+		return r;
+	}
+
+/* r=ck^a.cl^n using XTR double exponentiation method on traces of FP12s. See Stam thesis. */
+	public FP4 xtr_pow2(FP4 ck,FP4 ckml,FP4 ckm2l,BIG a,BIG b)
+	{
+		a.norm(); b.norm();
+		BIG e=new BIG(a);
+		BIG d=new BIG(b);
+		BIG w=new BIG(0);
+
+		FP4 cu=new FP4(ck);  // can probably be passed in w/o copying
+		FP4 cv=new FP4(this);
+		FP4 cumv=new FP4(ckml);
+		FP4 cum2v=new FP4(ckm2l);
+		FP4 r=new FP4(0);
+		FP4 t=new FP4(0);
+
+		int f2=0;
+		while (d.parity()==0 && e.parity()==0)
+		{
+			d.fshr(1);
+			e.fshr(1);
+			f2++;
+		}
+
+		while (BIG.comp(d,e)!=0)
+		{
+			if (BIG.comp(d,e)>0)
+			{
+				w.copy(e); w.imul(4); w.norm();
+				if (BIG.comp(d,w)<=0)
+				{
+					w.copy(d); d.copy(e);
+					e.rsub(w); e.norm();
+
+					t.copy(cv); 
+					t.xtr_A(cu,cumv,cum2v);
+					cum2v.copy(cumv); 
+					cum2v.conj();
+					cumv.copy(cv);
+					cv.copy(cu);
+					cu.copy(t);
+
+				}
+				else if (d.parity()==0)
+				{
+					d.fshr(1);
+					r.copy(cum2v); r.conj();
+					t.copy(cumv);
+					t.xtr_A(cu,cv,r);
+					cum2v.copy(cumv);
+					cum2v.xtr_D();
+					cumv.copy(t);
+					cu.xtr_D();
+				}
+				else if (e.parity()==1)
+				{
+					d.sub(e); d.norm();
+					d.fshr(1);
+					t.copy(cv);
+					t.xtr_A(cu,cumv,cum2v);
+					cu.xtr_D();
+					cum2v.copy(cv);
+					cum2v.xtr_D();
+					cum2v.conj();
+					cv.copy(t);
+				}
+				else
+				{
+					w.copy(d);
+					d.copy(e); d.fshr(1);
+					e.copy(w);
+					t.copy(cumv);
+					t.xtr_D();
+					cumv.copy(cum2v); cumv.conj();
+					cum2v.copy(t); cum2v.conj();
+					t.copy(cv);
+					t.xtr_D();
+					cv.copy(cu);
+					cu.copy(t);
+				}
+			}
+			if (BIG.comp(d,e)<0)
+			{
+				w.copy(d); w.imul(4); w.norm();
+				if (BIG.comp(e,w)<=0)
+				{
+					e.sub(d); e.norm();
+					t.copy(cv);
+					t.xtr_A(cu,cumv,cum2v);
+					cum2v.copy(cumv);
+					cumv.copy(cu);
+					cu.copy(t);
+				}
+				else if (e.parity()==0)
+				{
+					w.copy(d);
+					d.copy(e); d.fshr(1);
+					e.copy(w);
+					t.copy(cumv);
+					t.xtr_D();
+					cumv.copy(cum2v); cumv.conj();
+					cum2v.copy(t); cum2v.conj();
+					t.copy(cv);
+					t.xtr_D();
+					cv.copy(cu);
+					cu.copy(t);
+				}
+				else if (d.parity()==1)
+				{
+					w.copy(e);
+					e.copy(d);
+					w.sub(d); w.norm();
+					d.copy(w); d.fshr(1);
+					t.copy(cv);
+					t.xtr_A(cu,cumv,cum2v);
+					cumv.conj();
+					cum2v.copy(cu);
+					cum2v.xtr_D();
+					cum2v.conj();
+					cu.copy(cv);
+					cu.xtr_D();
+					cv.copy(t);
+				}
+				else
+				{
+					d.fshr(1);
+					r.copy(cum2v); r.conj();
+					t.copy(cumv);
+					t.xtr_A(cu,cv,r);
+					cum2v.copy(cumv);
+					cum2v.xtr_D();
+					cumv.copy(t);
+					cu.xtr_D();
+				}
+			}
+		}
+		r.copy(cv);
+		r.xtr_A(cu,cumv,cum2v);
+		for (int i=0;i<f2;i++)
+			r.xtr_D();
+		r=r.xtr_pow(d);
+		return r;
+	}
+
+/* this/=2 */
+	public void div2()
+	{
+		a.div2();
+		b.div2();
+	}
+
+	public void div_i()
+	{
+		FP2 u=new FP2(a);
+		FP2 v=new FP2(b);
+		u.div_ip();
+		a.copy(v);
+		b.copy(u);
+	}
+
+	public void div_2i() {
+		FP2 u=new FP2(a);
+		FP2 v=new FP2(b);
+		u.div_ip2();
+		v.add(v); v.norm();
+		a.copy(v);
+		b.copy(u);
+	}
+
+
+/* sqrt(a+ib) = sqrt(a+sqrt(a*a-n*b*b)/2)+ib/(2*sqrt(a+sqrt(a*a-n*b*b)/2)) */
+/* returns true if this is QR */
+	public boolean sqrt()
+	{
+		if (iszilch()) return true;
+		FP2 wa=new FP2(a);
+		FP2 ws=new FP2(b);
+		FP2 wt=new FP2(a);
+		
+		if (ws.iszilch())
+		{
+			if (wt.sqrt())
+			{
+				a.copy(wt);
+				b.zero();
+			} else {
+				wt.div_ip();
+				wt.sqrt();
+				b.copy(wt);
+				a.zero();
+			}
+			return true;
+		}
+
+		ws.sqr();
+		wa.sqr();
+		ws.mul_ip();
+		ws.norm();
+		wa.sub(ws);
+
+		ws.copy(wa);
+		if (!ws.sqrt()) {
+			return false;
+		}
+
+		wa.copy(wt); wa.add(ws); wa.norm(); wa.div2();
+
+		if (!wa.sqrt()) {
+			wa.copy(wt); wa.sub(ws); wa.norm(); wa.div2();
+			if (!wa.sqrt()) {
+				return false;
+			}
+		}
+		wt.copy(b);
+		ws.copy(wa); ws.add(wa);
+		ws.inverse();
+
+		wt.mul(ws);
+		a.copy(wa);
+		b.copy(wt);
+
+		return true;
+	}
+
+/* this*=s where s is FP */
+	public void qmul(FP s)
+	{
+		a.pmul(s);
+		b.pmul(s);
+	}
+
+
+
+/*
+	public static void main(String[] args) {
+		BIG m=new BIG(ROM.Modulus);
+		BIG e=new BIG(12);
+		BIG a=new BIG(0);
+		BIG b=new BIG(0);
+		
+		a.inc(27); b.inc(45);
+
+		FP2 w0=new FP2(a,b);
+
+		a.zero(); b.zero();
+		a.inc(33); b.inc(54);
+
+		FP2 w1=new FP2(a,b);
+
+
+		FP4 w=new FP4(w0,w1);
+		FP4 t=new FP4(w);
+
+		a=new BIG(ROM_ZZZ.CURVE_Fra);
+		b=new BIG(ROM_ZZZ.CURVE_Frb);
+
+		FP2 f=new FP2(a,b);
+
+		System.out.println("w= "+w.toString());
+
+		w=w.pow(m);
+
+		System.out.println("w^p= "+w.toString());
+
+		t.frob(f);
+
+
+		System.out.println("w^p= "+t.toString());
+
+		w=w.pow(m);
+		w=w.pow(m);
+		w=w.pow(m);
+		System.out.println("w^p4= "+w.toString());
+
+
+	System.out.println("Test Inversion");
+
+		w=new FP4(w0,w1);
+
+		w.inverse();
+
+		System.out.println("1/w mod p^4 = "+w.toString());
+
+		w.inverse();
+
+		System.out.println("1/(1/w) mod p^4 = "+w.toString());
+
+		FP4 ww=new FP4(w);
+
+		w=w.xtr_pow(e);
+		System.out.println("w^e= "+w.toString());
+
+
+		a.zero(); b.zero();
+		a.inc(37); b.inc(17);
+		w0=new FP2(a,b);
+		a.zero(); b.zero();
+		a.inc(49); b.inc(31);
+		w1=new FP2(a,b);
+
+		FP4 c1=new FP4(w0,w1);
+		FP4 c2=new FP4(w0,w1);
+		FP4 c3=new FP4(w0,w1);
+
+		BIG e1=new BIG(3331);
+		BIG e2=new BIG(3372);
+
+		FP4 cr=w.xtr_pow2(c1,c2,c3,e1,e2);
+
+		System.out.println("c^e= "+cr.toString()); 
+	} */
+}
diff --git a/src/main/java/org/apache/milagro/amcl/BLS24/FP8.java b/src/main/java/org/apache/milagro/amcl/BLS24/FP8.java
new file mode 100644
index 0000000..d164d19
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS24/FP8.java
@@ -0,0 +1,656 @@
+/*
+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.
+*/
+
+/* Finite Field arithmetic  Fp^8 functions */
+
+/* FP8 elements are of the form a+ib, where i is sqrt(sqrt(-1+sqrt(-1)))  */
+
+package org.apache.milagro.amcl.BLS24;
+
+public final class FP8 {
+	private final FP4 a;
+	private final FP4 b;
+/* reduce all components of this mod Modulus */
+	public void reduce()
+	{
+		a.reduce();
+		b.reduce();
+	}
+/* normalise all components of this mod Modulus */
+	public void norm()
+	{
+		a.norm();
+		b.norm();
+	}
+/* test this==0 ? */
+	public boolean iszilch() {
+		//reduce();
+		return (a.iszilch() && b.iszilch());
+	}
+
+	public void cmove(FP8 g,int d)
+	{
+		a.cmove(g.a,d);
+		b.cmove(g.b,d);
+	}
+
+/* test this==1 ? */
+	public boolean isunity() {
+		FP4 one=new FP4(1);
+		return (a.equals(one) && b.iszilch());
+	}
+
+/* test is w real? That is in a+ib test b is zero */
+	public boolean isreal()
+	{
+		return b.iszilch();
+	}
+/* extract real part a */
+	public FP4 real()
+	{
+		return a;
+	}
+
+	public FP4 geta()
+	{
+		return a;
+	}
+/* extract imaginary part b */
+	public FP4 getb()
+	{
+		return b;
+	}
+/* test this=x? */
+	public boolean equals(FP8 x)
+	{
+		return (a.equals(x.a) && b.equals(x.b));
+	}
+/* constructors */
+	public FP8(int c)
+	{
+		a=new FP4(c);
+		b=new FP4(0);
+	}
+
+	public FP8(FP8 x)
+	{
+		a=new FP4(x.a);
+		b=new FP4(x.b);
+	}
+
+	public FP8(FP4 c,FP4 d)
+	{
+		a=new FP4(c);
+		b=new FP4(d);
+	}
+
+	public FP8(FP4 c)
+	{
+		a=new FP4(c);
+		b=new FP4(0);
+	}
+/* copy this=x */
+	public void copy(FP8 x)
+	{
+		a.copy(x.a);
+		b.copy(x.b);
+	}
+/* set this=0 */
+	public void zero()
+	{
+		a.zero();
+		b.zero();
+	}
+/* set this=1 */
+	public void one()
+	{
+		a.one();
+		b.zero();
+	}
+/* set this=-this */
+	public void neg()
+	{
+		norm();
+		FP4 m=new FP4(a);
+		FP4 t=new FP4(0);
+		m.add(b);
+//	m.norm();
+		m.neg();
+	//	m.norm();
+		t.copy(m); t.add(b);
+		b.copy(m);
+		b.add(a);
+		a.copy(t);
+	norm();
+	}
+
+/* this=conjugate(this) */
+	public void conj()
+	{
+		b.neg(); norm();
+	}
+/* this=-conjugate(this) */
+	public void nconj()
+	{
+		a.neg(); norm();
+	}
+/* this+=x */
+	public void add(FP8 x)
+	{
+		a.add(x.a);
+		b.add(x.b);
+	}
+/* this-=x */
+	public void sub(FP8 x)
+	{
+		FP8 m=new FP8(x);
+		m.neg();
+		add(m);
+	}
+
+/* this=x-this */
+	public void rsub(FP8 x)
+	{
+		neg();
+		add(x);
+	}
+
+
+/* this*=s where s is FP4 */
+	public void pmul(FP4 s)
+	{
+		a.mul(s);
+		b.mul(s);
+	}
+/* this*=s where s is FP2 */
+	public void qmul(FP2 s)
+	{
+		a.pmul(s);
+		b.pmul(s);
+	}
+/* this*=s where s is FP */
+	public void tmul(FP s)
+	{
+		a.qmul(s);
+		b.qmul(s);
+	}
+/* this*=c where c is int */
+	public void imul(int c)
+	{
+		a.imul(c);
+		b.imul(c);
+	}
+
+/* this*=this */	
+	public void sqr()
+	{
+//		norm();
+
+		FP4 t1=new FP4(a);
+		FP4 t2=new FP4(b);
+		FP4 t3=new FP4(a);
+
+		t3.mul(b);
+		t1.add(b);
+		t2.times_i();
+
+		t2.add(a);
+
+		t1.norm();
+		t2.norm();
+
+		a.copy(t1);
+
+		a.mul(t2);
+
+		t2.copy(t3);
+		t2.times_i();
+		t2.add(t3);
+		t2.norm();
+		t2.neg();
+		a.add(t2);
+
+		b.copy(t3);
+		b.add(t3);
+
+		norm();
+	}
+
+/* this*=y */
+	public void mul(FP8 y)
+	{
+//		norm();
+
+		FP4 t1=new FP4(a);
+		FP4 t2=new FP4(b);
+		FP4 t3=new FP4(0);
+		FP4 t4=new FP4(b);
+
+		t1.mul(y.a);
+		t2.mul(y.b);
+		t3.copy(y.b);
+		t3.add(y.a);
+		t4.add(a);
+
+		t3.norm();
+		t4.norm();
+
+		t4.mul(t3);
+
+		t3.copy(t1);
+		t3.neg();
+		t4.add(t3);
+		t4.norm();
+
+	//	t4.sub(t1);
+	//	t4.norm();
+
+		t3.copy(t2);
+		t3.neg();
+		b.copy(t4);
+		b.add(t3);
+
+	//	b.copy(t4);
+	//	b.sub(t2);
+
+		t2.times_i();
+		a.copy(t2);
+		a.add(t1);
+
+		norm();
+	}
+
+/* convert this to hex string */
+	public String toString() 
+	{
+		return ("["+a.toString()+","+b.toString()+"]");
+	}
+
+/* this=1/this */
+	public void inverse()
+	{
+//		norm();
+
+		FP4 t1=new FP4(a);
+		FP4 t2=new FP4(b);
+
+		t1.sqr();
+		t2.sqr();
+		t2.times_i();
+		t2.norm();
+		t1.sub(t2); t1.norm();
+		t1.inverse();
+		a.mul(t1);
+		t1.neg();
+		t1.norm();
+		b.mul(t1);
+	}
+
+/* this*=i where i = sqrt(-1+sqrt(-1)) */
+	public void times_i()
+	{
+//		norm();
+		FP4 s=new FP4(b);
+		FP4 t=new FP4(a);
+		s.times_i();
+
+		b.copy(t);
+		a.copy(s);
+		norm();
+	}
+
+	public void times_i2()
+	{
+		a.times_i();
+		b.times_i();
+	}
+
+/* this=this^p using Frobenius */
+	public void frob(FP2 f)
+	{
+		FP2 ff=new FP2(f); ff.sqr(); ff.mul_ip(); ff.norm();
+
+		a.frob(ff);
+		b.frob(ff);
+		b.pmul(f);
+		b.times_i();
+
+	}
+
+/* this=this^e */
+	public FP8 pow(BIG e)
+	{
+		norm();
+		e.norm();
+		FP8 w=new FP8(this);
+		BIG z=new BIG(e);
+		FP8 r=new FP8(1);
+		while (true)
+		{
+			int bt=z.parity();
+			z.fshr(1);
+			if (bt==1) r.mul(w);
+			if (z.iszilch()) break;
+			w.sqr();
+		}
+		r.reduce();
+		return r;
+	}
+
+/* XTR xtr_a function */
+	public void xtr_A(FP8 w,FP8 y,FP8 z) 
+	{
+		FP8 r=new FP8(w);
+		FP8 t=new FP8(w);
+	
+		r.sub(y);
+		r.norm();
+		r.pmul(a);
+		t.add(y);
+		t.norm();
+		t.pmul(b);
+		t.times_i();
+
+		copy(r);
+		add(t);
+		add(z);
+
+		norm();
+	}
+
+/* XTR xtr_d function */
+	public void xtr_D() {
+		FP8 w=new FP8(this);
+		sqr(); w.conj();
+		w.add(w);
+		w.norm();
+		sub(w);
+		reduce();
+	}
+
+/* r=x^n using XTR method on traces of FP12s */
+	public FP8 xtr_pow(BIG n) {
+		FP8 a=new FP8(3);
+		FP8 b=new FP8(this);
+		FP8 c=new FP8(b);
+		c.xtr_D();
+		FP8 t=new FP8(0);
+		FP8 r=new FP8(0);
+
+		n.norm();
+		int par=n.parity();
+		BIG v=new BIG(n); v.fshr(1);
+		if (par==0) {v.dec(1); v.norm();}
+
+		int nb=v.nbits();
+		for (int i=nb-1;i>=0;i--)
+		{
+			if (v.bit(i)!=1)
+			{
+				t.copy(b);
+				conj();
+				c.conj();
+				b.xtr_A(a,this,c);
+				conj();
+				c.copy(t);
+				c.xtr_D();
+				a.xtr_D();
+			}
+			else
+			{
+				t.copy(a); t.conj();
+				a.copy(b);
+				a.xtr_D();
+				b.xtr_A(c,this,t);
+				c.xtr_D();
+			}
+		}
+		if (par==0) r.copy(c);
+		else r.copy(b);
+		r.reduce();
+		return r;
+	}
+
+/* r=ck^a.cl^n using XTR double exponentiation method on traces of FP12s. See Stam thesis. */
+	public FP8 xtr_pow2(FP8 ck,FP8 ckml,FP8 ckm2l,BIG a,BIG b)
+	{
+		a.norm(); b.norm();
+		BIG e=new BIG(a);
+		BIG d=new BIG(b);
+		BIG w=new BIG(0);
+
+		FP8 cu=new FP8(ck);  // can probably be passed in w/o copying
+		FP8 cv=new FP8(this);
+		FP8 cumv=new FP8(ckml);
+		FP8 cum2v=new FP8(ckm2l);
+		FP8 r=new FP8(0);
+		FP8 t=new FP8(0);
+
+		int f2=0;
+		while (d.parity()==0 && e.parity()==0)
+		{
+			d.fshr(1);
+			e.fshr(1);
+			f2++;
+		}
+
+		while (BIG.comp(d,e)!=0)
+		{
+			if (BIG.comp(d,e)>0)
+			{
+				w.copy(e); w.imul(4); w.norm();
+				if (BIG.comp(d,w)<=0)
+				{
+					w.copy(d); d.copy(e);
+					e.rsub(w); e.norm();
+
+					t.copy(cv); 
+					t.xtr_A(cu,cumv,cum2v);
+					cum2v.copy(cumv); 
+					cum2v.conj();
+					cumv.copy(cv);
+					cv.copy(cu);
+					cu.copy(t);
+
+				}
+				else if (d.parity()==0)
+				{
+					d.fshr(1);
+					r.copy(cum2v); r.conj();
+					t.copy(cumv);
+					t.xtr_A(cu,cv,r);
+					cum2v.copy(cumv);
+					cum2v.xtr_D();
+					cumv.copy(t);
+					cu.xtr_D();
+				}
+				else if (e.parity()==1)
+				{
+					d.sub(e); d.norm();
+					d.fshr(1);
+					t.copy(cv);
+					t.xtr_A(cu,cumv,cum2v);
+					cu.xtr_D();
+					cum2v.copy(cv);
+					cum2v.xtr_D();
+					cum2v.conj();
+					cv.copy(t);
+				}
+				else
+				{
+					w.copy(d);
+					d.copy(e); d.fshr(1);
+					e.copy(w);
+					t.copy(cumv);
+					t.xtr_D();
+					cumv.copy(cum2v); cumv.conj();
+					cum2v.copy(t); cum2v.conj();
+					t.copy(cv);
+					t.xtr_D();
+					cv.copy(cu);
+					cu.copy(t);
+				}
+			}
+			if (BIG.comp(d,e)<0)
+			{
+				w.copy(d); w.imul(4); w.norm();
+				if (BIG.comp(e,w)<=0)
+				{
+					e.sub(d); e.norm();
+					t.copy(cv);
+					t.xtr_A(cu,cumv,cum2v);
+					cum2v.copy(cumv);
+					cumv.copy(cu);
+					cu.copy(t);
+				}
+				else if (e.parity()==0)
+				{
+					w.copy(d);
+					d.copy(e); d.fshr(1);
+					e.copy(w);
+					t.copy(cumv);
+					t.xtr_D();
+					cumv.copy(cum2v); cumv.conj();
+					cum2v.copy(t); cum2v.conj();
+					t.copy(cv);
+					t.xtr_D();
+					cv.copy(cu);
+					cu.copy(t);
+				}
+				else if (d.parity()==1)
+				{
+					w.copy(e);
+					e.copy(d);
+					w.sub(d); w.norm();
+					d.copy(w); d.fshr(1);
+					t.copy(cv);
+					t.xtr_A(cu,cumv,cum2v);
+					cumv.conj();
+					cum2v.copy(cu);
+					cum2v.xtr_D();
+					cum2v.conj();
+					cu.copy(cv);
+					cu.xtr_D();
+					cv.copy(t);
+				}
+				else
+				{
+					d.fshr(1);
+					r.copy(cum2v); r.conj();
+					t.copy(cumv);
+					t.xtr_A(cu,cv,r);
+					cum2v.copy(cumv);
+					cum2v.xtr_D();
+					cumv.copy(t);
+					cu.xtr_D();
+				}
+			}
+		}
+		r.copy(cv);
+		r.xtr_A(cu,cumv,cum2v);
+		for (int i=0;i<f2;i++)
+			r.xtr_D();
+		r=r.xtr_pow(d);
+		return r;
+	}
+
+/* this/=2 */
+	public void div2()
+	{
+		a.div2();
+		b.div2();
+	}
+
+	public void div_i()
+	{
+		FP4 u=new FP4(a);
+		FP4 v=new FP4(b);
+		u.div_i();
+		a.copy(v);
+		b.copy(u);
+	}
+
+	public void div_i2() {
+		a.div_i();
+		b.div_i();
+	}
+
+	public void div_2i() {
+		FP4 u=new FP4(a);
+		FP4 v=new FP4(b);
+		u.div_2i();
+		v.add(v); v.norm();
+		a.copy(v);
+		b.copy(u);
+	}
+
+/* sqrt(a+ib) = sqrt(a+sqrt(a*a-n*b*b)/2)+ib/(2*sqrt(a+sqrt(a*a-n*b*b)/2)) */
+/* returns true if this is QR */
+	public boolean sqrt()
+	{
+		if (iszilch()) return true;
+		FP4 wa=new FP4(a);
+		FP4 ws=new FP4(b);
+		FP4 wt=new FP4(a);
+		
+		if (ws.iszilch())
+		{
+			if (wt.sqrt())
+			{
+				a.copy(wt);
+				b.zero();
+			} else {
+				wt.div_i();
+				wt.sqrt();
+				b.copy(wt);
+				a.zero();
+			}
+			return true;
+		}
+
+		ws.sqr();
+		wa.sqr();
+		ws.times_i();
+		ws.norm();
+		wa.sub(ws);
+
+		ws.copy(wa);
+		if (!ws.sqrt()) {
+			return false;
+		}
+
+		wa.copy(wt); wa.add(ws); wa.norm(); wa.div2();
+
+		if (!wa.sqrt()) {
+			wa.copy(wt); wa.sub(ws); wa.norm(); wa.div2();
+			if (!wa.sqrt()) {
+				return false;
+			}
+		}
+		wt.copy(b);
+		ws.copy(wa); ws.add(wa);
+		ws.inverse();
+
+		wt.mul(ws);
+		a.copy(wa);
+		b.copy(wt);
+
+		return true;
+	}
+}
diff --git a/src/main/java/org/apache/milagro/amcl/BLS24/MPIN192.java b/src/main/java/org/apache/milagro/amcl/BLS24/MPIN192.java
new file mode 100644
index 0000000..ee3f566
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS24/MPIN192.java
@@ -0,0 +1,806 @@
+/*
+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.
+*/
+
+/* MPIN API Functions */
+
+package org.apache.milagro.amcl.BLS24;
+
+import java.util.Date;
+
+import org.apache.milagro.amcl.RAND;
+import org.apache.milagro.amcl.HASH256;
+import org.apache.milagro.amcl.HASH384;
+import org.apache.milagro.amcl.HASH512;
+import org.apache.milagro.amcl.AES;
+
+public class MPIN192
+{
+
+//	public static final int SHA256=32;
+//	public static final int SHA384=48;
+//	public static final int SHA512=64;
+
+	public static final int EFS=BIG.MODBYTES;
+	public static final int EGS=BIG.MODBYTES;
+//	public static final int PAS=16;
+	public static final int INVALID_POINT=-14;
+	public static final int BAD_PARAMS=-11;
+	public static final int WRONG_ORDER=-18;
+	public static final int BAD_PIN=-19;
+
+/* Configure your PIN here */
+
+	public static final int MAXPIN=10000;  /* PIN less than this */
+	public static final int PBLEN=14;      /* Number of bits in PIN */
+	public static final int TS=10;         /* 10 for 4 digit PIN, 14 for 6-digit PIN - 2^TS/TS approx = sqrt(MAXPIN) */
+	public static final int TRAP=200;      /* 200 for 4 digit PIN, 2000 for 6-digit PIN  - approx 2*sqrt(MAXPIN) */
+
+//	public static final int HASH_TYPE=SHA256;
+
+
+/* Hash number (optional) and string to array size of Bigs */
+
+	public static byte[] hashit(int sha,int n,byte[] B,int len)
+	{
+		byte[] R=null;
+
+		if (sha==ECP.SHA256)
+		{
+			HASH256 H=new HASH256();
+			if (n>0) H.process_num(n);
+
+			H.process_array(B);
+			R=H.hash();
+		}
+		if (sha==ECP.SHA384)
+		{
+			HASH384 H=new HASH384();
+			if (n>0) H.process_num(n);
+			H.process_array(B);
+			R=H.hash();
+		}
+		if (sha==ECP.SHA512)
+		{
+			HASH512 H=new HASH512();
+			if (n>0) H.process_num(n);
+			H.process_array(B);
+			R=H.hash();
+		}
+		if (R==null) return null;
+		byte[] W=new byte[len];
+
+		if (sha>=len)
+			for (int i=0;i<len;i++) W[i]=R[i];
+		else
+		{
+			for (int i=0;i<sha;i++) W[i+len-sha]=R[i];
+            for (int i=0;i<len-sha;i++) W[i]=0;
+
+			//for (int i=0;i<sha;i++) W[i]=R[i];
+			//for (int i=sha;i<len;i++) W[i]=0;
+		}
+		return W;
+	}
+
+	/* return time in slots since epoch */
+	public static int today() {
+		Date date=new Date();
+		return (int) (date.getTime()/(1000*60*1440));
+	}
+
+	public static byte[] HASH_ID(int sha,byte[] ID,int len)
+	{
+		return hashit(sha,0,ID,len);
+	}
+
+/* Hash the M-Pin transcript - new */
+
+	public static byte[] HASH_ALL(int sha,byte[] HID,byte[] xID,byte[] xCID,byte[] SEC,byte[] Y,byte[] R,byte[] W,int len)
+	{
+		int i,ilen,tlen=0;
+
+		ilen=HID.length+SEC.length+Y.length+R.length+W.length;
+		if (xCID!=null) ilen+=xCID.length;
+		else ilen+=xID.length;
+
+		byte[] T = new byte[ilen];
+
+		for (i=0;i<HID.length;i++) T[i]=HID[i];
+		tlen+=HID.length;
+		if (xCID!=null)
+		{
+			for (i=0;i<xCID.length;i++) T[i+tlen]=xCID[i];
+			tlen+=xCID.length;
+		}	
+		else
+		{
+			for (i=0;i<xID.length;i++) T[i+tlen]=xID[i];
+			tlen+=xID.length;
+		}	
+		for (i=0;i<SEC.length;i++) T[i+tlen]=SEC[i];
+		tlen+=SEC.length;		
+		for (i=0;i<Y.length;i++) T[i+tlen]=Y[i];
+		tlen+=Y.length;	
+		for (i=0;i<R.length;i++) T[i+tlen]=R[i];
+		tlen+=R.length;		
+		for (i=0;i<W.length;i++) T[i+tlen]=W[i];
+		tlen+=W.length;		
+
+		return hashit(sha,0,T,len);
+	}
+
+/* return time since epoch */
+	public static int GET_TIME() {
+		Date date=new Date();
+		return (int) (date.getTime()/1000);
+	}
+
+	public static byte[] mpin_hash(int sha,FP8 c,ECP U)
+	{
+		byte[] w=new byte[EFS];
+		byte[] t=new byte[10*EFS];
+		byte[] h=null;
+		c.geta().geta().getA().toBytes(w); for (int i=0;i<EFS;i++) t[i]=w[i];
+		c.geta().geta().getB().toBytes(w); for (int i=EFS;i<2*EFS;i++) t[i]=w[i-EFS];
+		c.geta().getb().getA().toBytes(w); for (int i=2*EFS;i<3*EFS;i++) t[i]=w[i-2*EFS];
+		c.geta().getb().getB().toBytes(w); for (int i=3*EFS;i<4*EFS;i++) t[i]=w[i-3*EFS];
+	
+		c.getb().geta().getA().toBytes(w); for (int i=4*EFS;i<5*EFS;i++) t[i]=w[i-4*EFS];
+		c.getb().geta().getB().toBytes(w); for (int i=5*EFS;i<6*EFS;i++) t[i]=w[i-5*EFS];
+		c.getb().getb().getA().toBytes(w); for (int i=6*EFS;i<7*EFS;i++) t[i]=w[i-6*EFS];
+		c.getb().getb().getB().toBytes(w); for (int i=7*EFS;i<8*EFS;i++) t[i]=w[i-7*EFS];
+
+		
+		U.getX().toBytes(w); for (int i=8*EFS;i<9*EFS;i++) t[i]=w[i-8*EFS];
+		U.getY().toBytes(w); for (int i=9*EFS;i<10*EFS;i++) t[i]=w[i-9*EFS];
+		
+		if (sha==ECP.SHA256)
+		{
+			HASH256 H=new HASH256();
+			H.process_array(t);
+			h=H.hash();
+		}
+		if (sha==ECP.SHA384)
+		{
+			HASH384 H=new HASH384();
+			H.process_array(t);
+			h=H.hash();
+		}
+		if (sha==ECP.SHA512)
+		{
+			HASH512 H=new HASH512();
+			H.process_array(t);
+			h=H.hash();
+		}
+		if (h==null) return null;
+		byte[] R=new byte[ECP.AESKEY];
+		for (int i=0;i<ECP.AESKEY;i++) R[i]=h[i];
+		return R;
+	}
+
+/* these next two functions help to implement elligator squared - http://eprint.iacr.org/2014/043 */
+/* maps a random u to a point on the curve */
+	public static ECP map(BIG u,int cb)
+	{
+		ECP P;
+		BIG x=new BIG(u);
+		BIG p=new BIG(ROM.Modulus);
+		x.mod(p);
+		while (true)
+		{
+			P=new ECP(x,cb);
+			if (!P.is_infinity()) break;
+			x.inc(1);  x.norm();
+		}
+		return P;
+	}
+
+/* returns u derived from P. Random value in range 1 to return value should then be added to u */
+	public static int unmap(BIG u,ECP P)
+	{
+		int s=P.getS();
+		ECP R;
+		int r=0;
+		BIG x=P.getX();
+		u.copy(x);
+		while (true)
+		{
+			u.dec(1); u.norm();
+			r++;
+			R=new ECP(u,s);
+			if (!R.is_infinity()) break;
+		}
+		return r;
+	}
+
+
+
+/* these next two functions implement elligator squared - http://eprint.iacr.org/2014/043 */
+/* Elliptic curve point E in format (0x04,x,y} is converted to form {0x0-,u,v} */
+/* Note that u and v are indistinguisible from random strings */
+	public static int ENCODING(RAND rng,byte[] E)
+	{
+		int rn,m,su,sv;
+		byte[] T=new byte[EFS];
+
+		for (int i=0;i<EFS;i++) T[i]=E[i+1];
+		BIG u=BIG.fromBytes(T);
+		for (int i=0;i<EFS;i++) T[i]=E[i+EFS+1];
+		BIG v=BIG.fromBytes(T);
+		
+		ECP P=new ECP(u,v);
+		if (P.is_infinity()) return INVALID_POINT;
+
+		BIG p=new BIG(ROM.Modulus);
+		u=BIG.randomnum(p,rng);
+
+		su=rng.getByte(); /*if (su<0) su=-su;*/ su%=2;
+		
+		ECP W=map(u,su);
+		P.sub(W); //P.affine();
+		sv=P.getS();
+		rn=unmap(v,P);
+		m=rng.getByte(); /*if (m<0) m=-m;*/ m%=rn;
+		v.inc(m+1);
+		E[0]=(byte)(su+2*sv);
+		u.toBytes(T);
+		for (int i=0;i<EFS;i++) E[i+1]=T[i];
+		v.toBytes(T);
+		for (int i=0;i<EFS;i++) E[i+EFS+1]=T[i];		
+		
+		return 0;
+	}
+
+	public static int DECODING(byte[] D)
+	{
+		int su,sv;
+		byte[] T=new byte[EFS];
+
+		if ((D[0]&0x04)!=0) return INVALID_POINT;
+
+		for (int i=0;i<EFS;i++) T[i]=D[i+1];
+		BIG u=BIG.fromBytes(T);
+		for (int i=0;i<EFS;i++) T[i]=D[i+EFS+1];
+		BIG v=BIG.fromBytes(T);
+
+		su=D[0]&1;
+		sv=(D[0]>>1)&1;
+		ECP W=map(u,su);
+		ECP P=map(v,sv);
+		P.add(W); //P.affine();
+		u=P.getX();
+		v=P.getY();
+		D[0]=0x04;
+		u.toBytes(T);
+		for (int i=0;i<EFS;i++) D[i+1]=T[i];
+		v.toBytes(T);
+		for (int i=0;i<EFS;i++) D[i+EFS+1]=T[i];		
+		
+		return 0;
+	}
+
+/* R=R1+R2 in group G1 */
+	public static int RECOMBINE_G1(byte[] R1,byte[] R2,byte[] R)
+	{
+		ECP P=ECP.fromBytes(R1);
+		ECP Q=ECP.fromBytes(R2);
+
+		if (P.is_infinity() || Q.is_infinity()) return INVALID_POINT;
+
+		P.add(Q); //P.affine();
+
+		P.toBytes(R,false);
+		return 0;
+	}
+
+/* W=W1+W2 in group G2 */
+	public static int RECOMBINE_G2(byte[] W1,byte[] W2,byte[] W)
+	{
+		ECP4 P=ECP4.fromBytes(W1);
+		ECP4 Q=ECP4.fromBytes(W2);
+
+		if (P.is_infinity() || Q.is_infinity()) return INVALID_POINT;
+
+		P.add(Q); //P.affine();
+	
+		P.toBytes(W);
+		return 0;
+	}
+	
+/* create random secret S */
+	public static int RANDOM_GENERATE(RAND rng,byte[] S)
+	{
+		BIG s;
+		BIG r=new BIG(ROM.CURVE_Order);
+		s=BIG.randomnum(r,rng);
+		//if (ROM.AES_S>0)
+		//{
+		//	s.mod2m(2*ROM.AES_S);
+		//}
+		s.toBytes(S);
+		return 0;
+	}
+
+/* Extract PIN from TOKEN for identity CID */
+	public static int EXTRACT_PIN(int sha,byte[] CID,int pin,byte[] TOKEN)
+	{
+		ECP P=ECP.fromBytes(TOKEN);
+		if (P.is_infinity()) return INVALID_POINT;
+		byte[] h=hashit(sha,0,CID,EFS);
+		ECP R=ECP.mapit(h);
+
+
+		pin%=MAXPIN;
+
+		R=R.pinmul(pin,PBLEN);
+		P.sub(R); //P.affine();
+
+		P.toBytes(TOKEN,false);
+
+		return 0;
+	}
+
+/* Implement step 2 on client side of MPin protocol */
+	public static int CLIENT_2(byte[] X,byte[] Y,byte[] SEC)
+	{
+		BIG r=new BIG(ROM.CURVE_Order);
+		ECP P=ECP.fromBytes(SEC);
+		if (P.is_infinity()) return INVALID_POINT;
+
+		BIG px=BIG.fromBytes(X);
+		BIG py=BIG.fromBytes(Y);
+		px.add(py);
+		px.mod(r);
+	//	px.rsub(r);
+
+		P=PAIR192.G1mul(P,px);
+		P.neg();
+		P.toBytes(SEC,false);
+		return 0;
+	}
+
+/* Implement step 1 on client side of MPin protocol */
+	public static int CLIENT_1(int sha,int date,byte[] CLIENT_ID,RAND rng,byte[] X,int pin,byte[] TOKEN,byte[] SEC,byte[] xID,byte[] xCID,byte[] PERMIT)
+	{
+		BIG r=new BIG(ROM.CURVE_Order);
+		BIG x;
+		if (rng!=null)
+		{
+			x=BIG.randomnum(r,rng);
+			//if (ROM.AES_S>0)
+			//{
+			//	x.mod2m(2*ROM.AES_S);
+			//}
+			x.toBytes(X);
+		}
+		else
+		{
+			x=BIG.fromBytes(X);
+		}
+		ECP P,T,W;
+		BIG px;
+//		byte[] t=new byte[EFS];
+
+		byte[] h=hashit(sha,0,CLIENT_ID,EFS);
+		P=ECP.mapit(h);
+	
+		T=ECP.fromBytes(TOKEN);
+		if (T.is_infinity()) return INVALID_POINT;
+
+		pin%=MAXPIN;
+		W=P.pinmul(pin,PBLEN);
+		T.add(W);
+		if (date!=0)
+		{
+			W=ECP.fromBytes(PERMIT);
+			if (W.is_infinity()) return INVALID_POINT;
+			T.add(W);
+			h=hashit(sha,date,h,EFS);
+			W=ECP.mapit(h);
+			if (xID!=null)
+			{
+				P=PAIR192.G1mul(P,x);
+				P.toBytes(xID,false);
+				W=PAIR192.G1mul(W,x);
+				P.add(W); //P.affine();
+			}
+			else
+			{
+				P.add(W); //P.affine();
+				P=PAIR192.G1mul(P,x);
+			}
+			if (xCID!=null) P.toBytes(xCID,false);
+		}
+		else
+		{
+			if (xID!=null)
+			{
+				P=PAIR192.G1mul(P,x);
+				P.toBytes(xID,false);
+			}
+		}
+
+		//T.affine();
+		T.toBytes(SEC,false);
+		return 0;
+	}
+
+/* Extract Server Secret SST=S*Q where Q is fixed generator in G2 and S is master secret */
+	public static int GET_SERVER_SECRET(byte[] S,byte[] SST)
+	{
+		ECP4 Q=ECP4.generator();
+		BIG s=BIG.fromBytes(S);
+		Q=PAIR192.G2mul(Q,s);
+		Q.toBytes(SST);
+		return 0;
+	}
+
+/*
+ W=x*H(G);
+ if RNG == NULL then X is passed in 
+ if RNG != NULL the X is passed out 
+ if type=0 W=x*G where G is point on the curve, else W=x*M(G), where M(G) is mapping of octet G to point on the curve
+*/
+	public static int GET_G1_MULTIPLE(RAND rng, int type,byte[] X,byte[] G,byte[] W)
+	{
+		BIG x;
+		BIG r=new BIG(ROM.CURVE_Order);
+		if (rng!=null)
+		{
+			x=BIG.randomnum(r,rng);
+			//if (ROM.AES_S>0)
+			//{
+			//	x.mod2m(2*ROM.AES_S);
+			//}
+			x.toBytes(X);
+		}
+		else
+		{
+			x=BIG.fromBytes(X);
+		}
+		ECP P;
+		if (type==0)
+		{
+			P=ECP.fromBytes(G);
+			if (P.is_infinity()) return INVALID_POINT;
+		}
+		else
+			P=ECP.mapit(G);
+
+		PAIR192.G1mul(P,x).toBytes(W,false);
+		return 0;
+	}
+
+/* Client secret CST=S*H(CID) where CID is client ID and S is master secret */
+/* CID is hashed externally */
+	public static int GET_CLIENT_SECRET(byte[] S,byte[] CID,byte[] CST)
+	{
+		return GET_G1_MULTIPLE(null,1,S,CID,CST);
+	}
+
+/* Time Permit CTT=S*(date|H(CID)) where S is master secret */
+	public static int GET_CLIENT_PERMIT(int sha,int date,byte[] S,byte[] CID,byte[] CTT)
+	{
+		byte[] h=hashit(sha,date,CID,EFS);
+		ECP P=ECP.mapit(h);
+
+		BIG s=BIG.fromBytes(S);
+		ECP OP=PAIR192.G1mul(P,s);
+
+		OP.toBytes(CTT,false);
+		return 0;
+	}
+
+/* Outputs H(CID) and H(T|H(CID)) for time permits. If no time permits set HID=HTID */
+	public static void SERVER_1(int sha,int date,byte[] CID,byte[] HID,byte[] HTID)
+	{
+		byte[] h=hashit(sha,0,CID,EFS);
+		ECP R,P=ECP.mapit(h);
+
+		P.toBytes(HID,false);   // new
+		if (date!=0)
+		{
+	//		if (HID!=null) P.toBytes(HID,false);
+			h=hashit(sha,date,h,EFS);
+			R=ECP.mapit(h);
+			P.add(R); //P.affine();
+			P.toBytes(HTID,false);
+		}
+	//	else P.toBytes(HID,false);
+	}
+
+/* Implement step 2 of MPin protocol on server side */
+	public static int SERVER_2(int date,byte[] HID,byte[] HTID,byte[] Y,byte[] SST,byte[] xID,byte[] xCID,byte[] mSEC,byte[] E,byte[] F)
+	{
+		BIG q=new BIG(ROM.Modulus);
+		ECP4 Q=ECP4.generator();
+
+		ECP4 sQ=ECP4.fromBytes(SST);
+		if (sQ.is_infinity()) return INVALID_POINT;	
+
+		ECP R;
+		if (date!=0)
+			R=ECP.fromBytes(xCID);
+		else 
+		{
+			if (xID==null) return BAD_PARAMS;
+			R=ECP.fromBytes(xID);
+		}
+		if (R.is_infinity()) return INVALID_POINT;
+
+		BIG y=BIG.fromBytes(Y);
+		ECP P;
+		if (date!=0) P=ECP.fromBytes(HTID);
+		else 
+		{
+			if (HID==null) return BAD_PARAMS;
+			P=ECP.fromBytes(HID);
+		}
+	
+		if (P.is_infinity()) return INVALID_POINT;
+
+		P=PAIR192.G1mul(P,y);
+		P.add(R); //P.affine();
+		R=ECP.fromBytes(mSEC);
+		if (R.is_infinity()) return INVALID_POINT;
+
+		FP24 g;
+
+		g=PAIR192.ate2(Q,R,sQ,P);
+		g=PAIR192.fexp(g);
+
+		if (!g.isunity())
+		{
+			if (HID!=null && xID!=null && E!=null && F!=null)
+			{
+				g.toBytes(E);
+				if (date!=0)
+				{
+					P=ECP.fromBytes(HID);
+					if (P.is_infinity()) return INVALID_POINT;
+					R=ECP.fromBytes(xID);
+					if (R.is_infinity()) return INVALID_POINT;
+
+					P=PAIR192.G1mul(P,y);
+					P.add(R); //P.affine();
+				}
+				g=PAIR192.ate(Q,P);
+				g=PAIR192.fexp(g);
+				g.toBytes(F);
+			}
+			return BAD_PIN;
+		}
+
+		return 0;
+	}
+
+/* Pollards kangaroos used to return PIN error */
+	public static int KANGAROO(byte[] E,byte[] F)
+	{
+		FP24 ge=FP24.fromBytes(E);
+		FP24 gf=FP24.fromBytes(F);
+		int[] distance = new int[TS];
+		FP24 t=new FP24(gf);
+		FP24[] table=new FP24[TS];
+		int i,j,m,s,dn,dm,res,steps;
+
+		s=1;
+		for (m=0;m<TS;m++)
+		{
+			distance[m]=s;
+			table[m]=new FP24(t);
+			s*=2;
+			t.usqr();
+		}
+		t.one();
+		dn=0;
+		for (j=0;j<TRAP;j++)
+		{
+			i=t.geta().geta().geta().getA().lastbits(20)%TS;
+			t.mul(table[i]);
+			dn+=distance[i];
+		}
+		gf.copy(t); gf.conj();
+		steps=0; dm=0;
+		res=0;
+		while (dm-dn<MAXPIN)
+		{
+			steps++;
+			if (steps>4*TRAP) break;
+			i=ge.geta().geta().geta().getA().lastbits(20)%TS;
+			ge.mul(table[i]);
+			dm+=distance[i];
+			if (ge.equals(t))
+			{
+				res=dm-dn;
+				break;
+			}
+			if (ge.equals(gf))
+			{
+				res=dn-dm;
+				break;
+			}
+
+		}
+		if (steps>4*TRAP || dm-dn>=MAXPIN) {res=0; }    // Trap Failed  - probable invalid token
+		return res;
+	}
+
+/* Functions to support M-Pin Full */
+
+	public static int PRECOMPUTE(byte[] TOKEN,byte[] CID,byte[] G1,byte[] G2)
+	{
+		ECP P,T;
+		FP24 g;
+
+		T=ECP.fromBytes(TOKEN);
+		if (T.is_infinity()) return INVALID_POINT; 
+
+		P=ECP.mapit(CID);
+
+		ECP4 Q=ECP4.generator();
+
+		g=PAIR192.ate(Q,T);
+		g=PAIR192.fexp(g);
+		g.toBytes(G1);
+
+		g=PAIR192.ate(Q,P);
+		g=PAIR192.fexp(g);
+		g.toBytes(G2);
+
+		return 0;
+	}
+
+
+
+/* calculate common key on client side */
+/* wCID = w.(A+AT) */
+	public static int CLIENT_KEY(int sha,byte[] G1,byte[] G2,int pin,byte[] R,byte[] X,byte[] H,byte[] wCID,byte[] CK)
+	{
+		byte[] t;
+
+		FP24 g1=FP24.fromBytes(G1);
+		FP24 g2=FP24.fromBytes(G2);
+		BIG z=BIG.fromBytes(R);
+		BIG x=BIG.fromBytes(X);
+		BIG h=BIG.fromBytes(H);
+
+		ECP W=ECP.fromBytes(wCID);
+		if (W.is_infinity()) return INVALID_POINT; 
+
+		W=PAIR192.G1mul(W,x);
+
+//		FP2 f=new FP2(new BIG(ROM.Fra),new BIG(ROM.Frb));
+		BIG r=new BIG(ROM.CURVE_Order);
+//		BIG q=new BIG(ROM.Modulus);
+
+		z.add(h);	//new
+		z.mod(r);
+
+		g2.pinpow(pin,PBLEN);
+		g1.mul(g2);
+
+		FP8 c=g1.compow(z,r);
+
+		t=mpin_hash(sha,c,W);
+
+		for (int i=0;i<ECP.AESKEY;i++) CK[i]=t[i];
+
+		return 0;
+	}
+
+/* calculate common key on server side */
+/* Z=r.A - no time permits involved */
+
+	public static int SERVER_KEY(int sha,byte[] Z,byte[] SST,byte[] W,byte[] H,byte[] HID,byte[] xID,byte[] xCID,byte[] SK)
+	{
+		byte[] t;
+
+		ECP4 sQ=ECP4.fromBytes(SST);
+		if (sQ.is_infinity()) return INVALID_POINT; 
+		ECP R=ECP.fromBytes(Z);
+		if (R.is_infinity()) return INVALID_POINT; 
+		ECP A=ECP.fromBytes(HID);
+		if (A.is_infinity()) return INVALID_POINT; 
+
+		ECP U;
+		if (xCID!=null)
+			U=ECP.fromBytes(xCID);
+		else
+			U=ECP.fromBytes(xID);
+		if (U.is_infinity()) return INVALID_POINT; 
+
+		BIG w=BIG.fromBytes(W);
+		BIG h=BIG.fromBytes(H);
+		A=PAIR192.G1mul(A,h);	// new
+		R.add(A); //R.affine();
+
+		U=PAIR192.G1mul(U,w);
+		FP24 g=PAIR192.ate(sQ,R);
+		g=PAIR192.fexp(g);
+
+		FP8 c=g.trace();
+
+		t=mpin_hash(sha,c,U);
+
+		for (int i=0;i<ECP.AESKEY;i++) SK[i]=t[i];
+
+		return 0;
+	}
+
+/* Generate Y = H(epoch, xCID/xID) */
+	public static void GET_Y(int sha,int TimeValue,byte[] xCID,byte[] Y)
+	{
+		byte[] h = hashit(sha,TimeValue,xCID,EFS);
+		BIG y = BIG.fromBytes(h);
+		BIG q=new BIG(ROM.CURVE_Order);
+		y.mod(q);
+		//if (ROM.AES_S>0)
+		//{
+		//	y.mod2m(2*ROM.AES_S);
+		//}
+		y.toBytes(Y);
+	}
+        
+/* One pass MPIN Client */
+	public static int CLIENT(int sha,int date,byte[] CLIENT_ID,RAND RNG,byte[] X,int pin,byte[] TOKEN,byte[] SEC,byte[] xID,byte[] xCID,byte[] PERMIT, int TimeValue, byte[] Y)
+	{
+		int rtn=0;
+        
+		byte[] pID;
+		if (date == 0)
+			pID = xID;
+		else
+			pID = xCID;
+          
+		rtn = CLIENT_1(sha,date,CLIENT_ID,RNG,X,pin,TOKEN,SEC,xID,xCID,PERMIT);
+		if (rtn != 0)
+			return rtn;
+        
+		GET_Y(sha,TimeValue,pID,Y);
+        
+		rtn = CLIENT_2(X,Y,SEC);
+		if (rtn != 0)
+		return rtn;
+        
+		return 0;
+	}
+        
+/* One pass MPIN Server */
+	public static int SERVER(int sha,int date,byte[] HID,byte[] HTID,byte[] Y,byte[] SST,byte[] xID,byte[] xCID,byte[] SEC,byte[] E,byte[] F,byte[] CID, int TimeValue)
+	{
+		int rtn=0;
+        
+		byte[] pID;
+		if (date == 0)
+			pID = xID;
+		else
+			pID = xCID;
+          
+		SERVER_1(sha,date,CID,HID,HTID);
+        
+		GET_Y(sha,TimeValue,pID,Y);
+          
+		rtn = SERVER_2(date,HID,HTID,Y,SST,xID,xCID,SEC,E,F);
+		if (rtn != 0)
+			return rtn;
+        
+		return 0;
+	}
+}
diff --git a/src/main/java/org/apache/milagro/amcl/BLS24/PAIR192.java b/src/main/java/org/apache/milagro/amcl/BLS24/PAIR192.java
new file mode 100644
index 0000000..da3d100
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS24/PAIR192.java
@@ -0,0 +1,550 @@
+/*
+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.
+*/
+
+/* AMCL BN Curve Pairing functions */
+
+package org.apache.milagro.amcl.BLS24;
+
+public final class PAIR192 {
+
+	public static final boolean USE_GLV =true;
+	public static final boolean USE_GS_G2 =true;
+	public static final boolean USE_GS_GT =true;	
+	public static final boolean GT_STRONG=false;
+
+
+/* Line function */
+	public static FP24 line(ECP4 A,ECP4 B,FP Qx,FP Qy)
+	{
+//System.out.println("Into line");
+		FP8 a,b,c;                            // Edits here
+//		c=new FP8(0);
+		if (A==B)
+		{ // Doubling
+			FP4 XX=new FP4(A.getx());  //X
+			FP4 YY=new FP4(A.gety());  //Y
+			FP4 ZZ=new FP4(A.getz());  //Z
+			FP4 YZ=new FP4(YY);        //Y 
+			YZ.mul(ZZ);                //YZ
+			XX.sqr();	               //X^2
+			YY.sqr();	               //Y^2
+			ZZ.sqr();			       //Z^2
+			
+			YZ.imul(4);
+			YZ.neg(); YZ.norm();       //-2YZ
+			YZ.qmul(Qy);               //-2YZ.Ys
+
+			XX.imul(6);                //3X^2
+			XX.qmul(Qx);               //3X^2.Xs
+
+			int sb=3*ROM.CURVE_B_I;
+			ZZ.imul(sb); 	
+			
+			if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+			{
+				ZZ.div_2i();
+			}
+			if (ECP.SEXTIC_TWIST==ECP.M_TYPE)
+			{
+				ZZ.times_i();
+				ZZ.add(ZZ);
+				YZ.times_i();
+				YZ.norm();
+			}
+			
+			ZZ.norm(); // 3b.Z^2 
+
+			YY.add(YY);
+			ZZ.sub(YY); ZZ.norm();     // 3b.Z^2-Y^2
+
+			a=new FP8(YZ,ZZ);          // -2YZ.Ys | 3b.Z^2-Y^2 | 3X^2.Xs 
+			if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+			{			
+				b=new FP8(XX);             // L(0,1) | L(0,0) | L(1,0)
+				c=new FP8(0);
+			}
+			if (ECP.SEXTIC_TWIST==ECP.M_TYPE)
+			{
+				b=new FP8(0);
+				c=new FP8(XX); c.times_i();
+			}
+			A.dbl();
+		}
+		else
+		{ // Addition - assume B is affine
+
+			FP4 X1=new FP4(A.getx());    // X1
+			FP4 Y1=new FP4(A.gety());    // Y1
+			FP4 T1=new FP4(A.getz());    // Z1
+			FP4 T2=new FP4(A.getz());    // Z1
+			
+			T1.mul(B.gety());    // T1=Z1.Y2 
+			T2.mul(B.getx());    // T2=Z1.X2
+
+			X1.sub(T2); X1.norm();  // X1=X1-Z1.X2
+			Y1.sub(T1); Y1.norm();  // Y1=Y1-Z1.Y2
+
+			T1.copy(X1);            // T1=X1-Z1.X2
+			X1.qmul(Qy);            // X1=(X1-Z1.X2).Ys
+
+			if (ECP.SEXTIC_TWIST==ECP.M_TYPE)
+			{
+				X1.times_i();
+				X1.norm();
+			}
+
+			T1.mul(B.gety());       // T1=(X1-Z1.X2).Y2
+
+			T2.copy(Y1);            // T2=Y1-Z1.Y2
+			T2.mul(B.getx());       // T2=(Y1-Z1.Y2).X2
+			T2.sub(T1); T2.norm();          // T2=(Y1-Z1.Y2).X2 - (X1-Z1.X2).Y2
+			Y1.qmul(Qx);  Y1.neg(); Y1.norm(); // Y1=-(Y1-Z1.Y2).Xs
+
+			a=new FP8(X1,T2);       // (X1-Z1.X2).Ys  |  (Y1-Z1.Y2).X2 - (X1-Z1.X2).Y2  | - (Y1-Z1.Y2).Xs
+			if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+			{
+				b=new FP8(Y1);
+				c=new FP8(0);
+			}
+			if (ECP.SEXTIC_TWIST==ECP.M_TYPE)
+			{
+				b=new FP8(0);
+				c=new FP8(Y1); c.times_i();
+			}
+			A.add(B);
+		}
+//System.out.println("Out of line");
+		return new FP24(a,b,c);
+	}
+
+/* Optimal R-ate pairing */
+	public static FP24 ate(ECP4 P1,ECP Q1)
+	{
+		FP2 f;
+		BIG x=new BIG(ROM.CURVE_Bnx);
+		BIG n=new BIG(x);
+		FP24 lv;
+		int bt;
+		
+		ECP4 P=new ECP4(P1);
+		ECP Q=new ECP(Q1);
+
+		P.affine();
+		Q.affine();
+
+		BIG n3=new BIG(n);
+		n3.pmul(3);
+		n3.norm();
+
+		FP Qx=new FP(Q.getx());
+		FP Qy=new FP(Q.gety());
+
+		ECP4 A=new ECP4();
+		FP24 r=new FP24(1);
+		A.copy(P);
+
+		ECP4 MP=new ECP4();
+		MP.copy(P); MP.neg();
+
+		int nb=n3.nbits();
+
+		for (int i=nb-2;i>=1;i--)
+		{
+			r.sqr();
+			lv=line(A,A,Qx,Qy);
+			r.smul(lv,ECP.SEXTIC_TWIST);
+
+			bt=n3.bit(i)-n.bit(i); // bt=n.bit(i);
+			if (bt==1)
+			{
+				lv=line(A,P,Qx,Qy);
+				r.smul(lv,ECP.SEXTIC_TWIST);
+			}
+			if (bt==-1)
+			{
+				//P.neg();
+				lv=line(A,MP,Qx,Qy);
+				r.smul(lv,ECP.SEXTIC_TWIST);
+				//P.neg();
+			}
+		}
+
+		if (ECP.SIGN_OF_X==ECP.NEGATIVEX)
+		{
+			r.conj();
+		}
+
+		return r;
+	}
+
+/* Optimal R-ate double pairing e(P,Q).e(R,S) */
+	public static FP24 ate2(ECP4 P1,ECP Q1,ECP4 R1,ECP S1)
+	{
+		FP2 f;
+		BIG x=new BIG(ROM.CURVE_Bnx);
+		BIG n=new BIG(x);
+		FP24 lv;
+		int bt;
+
+		ECP4 P=new ECP4(P1);
+		ECP Q=new ECP(Q1);
+
+		P.affine();
+		Q.affine();
+
+		ECP4 R=new ECP4(R1);
+		ECP S=new ECP(S1);
+
+		R.affine();
+		S.affine();
+
+
+		BIG n3=new BIG(n);
+		n3.pmul(3);
+		n3.norm();
+
+		FP Qx=new FP(Q.getx());
+		FP Qy=new FP(Q.gety());
+		FP Sx=new FP(S.getx());
+		FP Sy=new FP(S.gety());
+
+		ECP4 A=new ECP4();
+		ECP4 B=new ECP4();
+		FP24 r=new FP24(1);
+
+		A.copy(P);
+		B.copy(R);
+
+		ECP4 MP=new ECP4();
+		MP.copy(P); MP.neg();
+		ECP4 MR=new ECP4();
+		MR.copy(R); MR.neg();
+
+
+		int nb=n3.nbits();
+
+		for (int i=nb-2;i>=1;i--)
+		{
+			r.sqr();
+			lv=line(A,A,Qx,Qy);
+			r.smul(lv,ECP.SEXTIC_TWIST);
+
+			lv=line(B,B,Sx,Sy);
+			r.smul(lv,ECP.SEXTIC_TWIST);
+
+			bt=n3.bit(i)-n.bit(i); // bt=n.bit(i);
+			if (bt==1)
+			{
+				lv=line(A,P,Qx,Qy);
+				r.smul(lv,ECP.SEXTIC_TWIST);
+				lv=line(B,R,Sx,Sy);
+				r.smul(lv,ECP.SEXTIC_TWIST);
+			}
+			if (bt==-1)
+			{
+				//P.neg(); 
+				lv=line(A,MP,Qx,Qy);
+				r.smul(lv,ECP.SEXTIC_TWIST);
+				//P.neg(); 
+				//R.neg();
+				lv=line(B,MR,Sx,Sy);
+				r.smul(lv,ECP.SEXTIC_TWIST);
+				//R.neg();
+			}
+		}
+
+		if (ECP.SIGN_OF_X==ECP.NEGATIVEX)
+		{
+			r.conj();
+		}
+
+		return r;
+	}
+
+/* final exponentiation - keep separate for multi-pairings and to avoid thrashing stack */
+	public static FP24 fexp(FP24 m)
+	{
+		FP2 f=new FP2(new BIG(ROM.Fra),new BIG(ROM.Frb));
+		BIG x=new BIG(ROM.CURVE_Bnx);
+		FP24 r=new FP24(m);
+
+/* Easy part of final exp */
+		FP24 lv=new FP24(r);
+		lv.inverse();
+		r.conj();
+
+		r.mul(lv);
+		lv.copy(r);
+		r.frob(f,4);
+		r.mul(lv);
+
+		FP24 t0,t1,t2,t3,t4,t5,t6,t7;
+/* Hard part of final exp */	
+// Ghamman & Fouotsa Method
+
+		t7=new FP24(r); t7.usqr();
+		t1=t7.pow(x);
+
+		x.fshr(1);
+		t2=t1.pow(x);
+		x.fshl(1);
+
+		if (ECP.SIGN_OF_X==ECP.NEGATIVEX) {
+			t1.conj();
+		}
+		t3=new FP24(t1); t3.conj();
+		t2.mul(t3);
+		t2.mul(r);
+
+		t3=t2.pow(x);
+		t4=t3.pow(x);
+		t5=t4.pow(x);
+
+		if (ECP.SIGN_OF_X==ECP.NEGATIVEX) {
+			t3.conj(); t5.conj();
+		}
+
+		t3.frob(f,6); t4.frob(f,5);
+		t3.mul(t4);
+
+		t6=t5.pow(x);
+		if (ECP.SIGN_OF_X==ECP.NEGATIVEX) {
+			t6.conj();
+		}
+
+		t5.frob(f,4);
+		t3.mul(t5);
+
+		t0=new FP24(t2); t0.conj();
+		t6.mul(t0);
+
+		t5.copy(t6);
+		t5.frob(f,3);
+
+		t3.mul(t5);
+		t5=t6.pow(x);
+		t6=t5.pow(x);
+
+		if (ECP.SIGN_OF_X==ECP.NEGATIVEX) {
+			t5.conj();
+		}
+
+		t0.copy(t5);
+		t0.frob(f,2);
+		t3.mul(t0);
+		t0.copy(t6);
+		t0.frob(f,1);
+
+		t3.mul(t0);
+		t5=t6.pow(x);
+
+		if (ECP.SIGN_OF_X==ECP.NEGATIVEX) {
+			t5.conj();
+		}
+		t2.frob(f,7);
+
+		t5.mul(t7);
+		t3.mul(t2);
+		t3.mul(t5);
+
+		r.mul(t3);
+
+		r.reduce();
+		return r;
+	}
+
+/* GLV method */
+	public static BIG[] glv(BIG e)
+	{
+		BIG[] u=new BIG[2];
+// -(x^4).P = (Beta.x,y)
+		BIG q=new BIG(ROM.CURVE_Order);
+		BIG x=new BIG(ROM.CURVE_Bnx);
+		BIG x2=BIG.smul(x,x);
+		x=BIG.smul(x2,x2);
+		u[0]=new BIG(e);
+		u[0].mod(x);
+		u[1]=new BIG(e);
+		u[1].div(x);
+		u[1].rsub(q);
+
+		return u;
+	}
+
+/* Galbraith & Scott Method */
+	public static BIG[] gs(BIG e)
+	{
+		BIG[] u=new BIG[8];
+
+		BIG q=new BIG(ROM.CURVE_Order);
+		BIG x=new BIG(ROM.CURVE_Bnx);
+		BIG w=new BIG(e);
+		for (int i=0;i<7;i++)
+		{
+			u[i]=new BIG(w);
+			u[i].mod(x);
+			w.div(x);
+		}
+		u[7]=new BIG(w);
+		if (ECP.SIGN_OF_X==ECP.NEGATIVEX)
+		{
+			u[1].copy(BIG.modneg(u[1],q));
+			u[3].copy(BIG.modneg(u[3],q));
+			u[5].copy(BIG.modneg(u[5],q));
+			u[7].copy(BIG.modneg(u[7],q));
+		}
+
+		return u;
+	}	
+
+/* Multiply P by e in group G1 */
+	public static ECP G1mul(ECP P,BIG e)
+	{
+		ECP R;
+		if (USE_GLV)
+		{
+			//P.affine();
+			R=new ECP();
+			R.copy(P);
+			int i,np,nn;
+			ECP Q=new ECP();
+			Q.copy(P); Q.affine();
+			BIG q=new BIG(ROM.CURVE_Order);
+			FP cru=new FP(new BIG(ROM.CURVE_Cru));
+			BIG t=new BIG(0);
+			BIG[] u=glv(e);
+			Q.getx().mul(cru);
+
+			np=u[0].nbits();
+			t.copy(BIG.modneg(u[0],q));
+			nn=t.nbits();
+			if (nn<np)
+			{
+				u[0].copy(t);
+				R.neg();
+			}
+
+			np=u[1].nbits();
+			t.copy(BIG.modneg(u[1],q));
+			nn=t.nbits();
+			if (nn<np)
+			{
+				u[1].copy(t);
+				Q.neg();
+			}
+			u[0].norm();
+			u[1].norm();
+			R=R.mul2(u[0],Q,u[1]);
+			
+		}
+		else
+		{
+			R=P.mul(e);
+		}
+		return R;
+	}
+
+/* Multiply P by e in group G2 */
+	public static ECP4 G2mul(ECP4 P,BIG e)
+	{
+		ECP4 R;
+		if (USE_GS_G2)
+		{
+			ECP4[] Q=new ECP4[8];
+			FP2[] F=ECP4.frob_constants();
+
+			BIG q=new BIG(ROM.CURVE_Order);
+			BIG[] u=gs(e);
+
+			BIG t=new BIG(0);
+			int i,np,nn;
+			//P.affine();
+
+			Q[0]=new ECP4(); Q[0].copy(P);
+			for (i=1;i<8;i++)
+			{
+				Q[i]=new ECP4(); Q[i].copy(Q[i-1]);
+				Q[i].frob(F,1);
+			}
+			for (i=0;i<8;i++)
+			{
+				np=u[i].nbits();
+				t.copy(BIG.modneg(u[i],q));
+				nn=t.nbits();
+				if (nn<np)
+				{
+					u[i].copy(t);
+					Q[i].neg();
+				}
+				u[i].norm();	
+				//Q[i].affine();
+			}
+
+			R=ECP4.mul8(Q,u);
+		}
+		else
+		{
+			R=P.mul(e);
+		}
+		return R;
+	}
+
+/* f=f^e */
+/* Note that this method requires a lot of RAM! Better to use compressed XTR method, see FP8.java */
+	public static FP24 GTpow(FP24 d,BIG e)
+	{
+		FP24 r;
+		if (USE_GS_GT)
+		{
+			FP24[] g=new FP24[8];
+			FP2 f=new FP2(new BIG(ROM.Fra),new BIG(ROM.Frb));
+			BIG q=new BIG(ROM.CURVE_Order);
+			BIG t=new BIG(0);
+			int i,np,nn;
+			BIG[] u=gs(e);
+
+			g[0]=new FP24(d);
+			for (i=1;i<8;i++)
+			{
+				g[i]=new FP24(0); g[i].copy(g[i-1]);
+				g[i].frob(f,1);
+			}
+			for (i=0;i<8;i++)
+			{
+				np=u[i].nbits();
+				t.copy(BIG.modneg(u[i],q));
+				nn=t.nbits();
+				if (nn<np)
+				{
+					u[i].copy(t);
+					g[i].conj();
+				}
+				u[i].norm();
+			}
+			r=FP24.pow8(g,u);
+		}
+		else
+		{
+			r=d.pow(e);
+		}
+		return r;
+	}
+
+}
+
diff --git a/src/main/java/org/apache/milagro/amcl/BLS24/ROM.java b/src/main/java/org/apache/milagro/amcl/BLS24/ROM.java
new file mode 100644
index 0000000..14658f9
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS24/ROM.java
@@ -0,0 +1,60 @@
+/*
+	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.
+*/
+
+/* Fixed Data in ROM - Field and Curve parameters */
+
+
+package org.apache.milagro.amcl.BLS24;
+
+public class ROM
+{
+
+// Base Bits= 56
+	public static final long[] Modulus= {0x44C1674A06152BL,0xFFE2E82D30DAF8L,0x6F1C5CBDB6A642L,0x3220DF068A328BL,0xE09E1F24406187L,0xBA825079733568L,0x6E803F2E77E4C1L,0x3CCC5BA839AECL,0x555C0078L};
+	public static final long[] R2modp= {0x6A4A1FE013DF5BL,0xE8E46D4D1BDE65L,0x1F841391F45C67L,0x9148A4516FB28L,0x4398524EDF4C88L,0x41C0E241B6DCE8L,0xE42C208C19411L,0xA7FE6FD73A7B1CL,0xFCCCA76L};
+	public static final long MConst= 0xBD5D7D8095FE7DL;
+	public static final long[] Fra= {0x5CA74ABBF96F1DL,0x1FF8BD0C6FFBADL,0x49E9E26237469CL,0x3CECA48407F8E5L,0x69D68FF59267B7L,0x5D199E33127CBDL,0xB97549184F313AL,0x4E77242DA52D8DL,0x4BBC87B9L};
+	public static final long[] Frb= {0xE81A1C8E0CA60EL,0xDFEA2B20C0DF4AL,0x25327A5B7F5FA6L,0xF5343A828239A6L,0x76C78F2EADF9CFL,0x5D68B24660B8ABL,0xB50AF61628B387L,0xB555A18CDE6D5EL,0x99F78BEL};
+
+	public static final int CURVE_A= 0;
+	public static final int CURVE_B_I= 19;
+	public static final int CURVE_Cof_I= 0;
+	public static final long[] CURVE_B= {0x13L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L};
+	public static final long[] CURVE_Order= {0x1A08FFF0000001L,0x1E7033FF551190L,0x6ADE7EE322DDAFL,0x848FC9D0CED13AL,0x50D81729CC224L,0x1F0F05B98BB44AL,0x10010010005A0L,0x0L,0x0L};
+	public static final long[] CURVE_Gx= {0x6760F5EBE3CCD4L,0xEFE2DAED9F4564L,0x783F08EBA1FCC1L,0xC6F8D95AF88134L,0xDCA8D1AE2D8477L,0x9077586CEFE4BFL,0x8B7FEA5D99BC1DL,0x17CAF9486DE9E1L,0x1AB2BE34L};
+	public static final long[] CURVE_Gy= {0xCBA5CAD21E5245L,0x6D6608C55DF6C4L,0xB3ED294F39746BL,0x145824920FF3C8L,0x63AA4FD63E5A64L,0x492A2BF79CE00FL,0x66A7A4529FF79AL,0x6C53E477B861CAL,0x47FCB70CL};
+
+	public static final long[] CURVE_Bnx= {0x100020011FF80L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L};
+	public static final long[] CURVE_Cof= {0xC1FFBFF9F415ABL,0x5556AAB7FFL,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L};
+	public static final long[] CURVE_Cru= {0xBC27146DD794A9L,0x3A30938AF33A43L,0xB112175223DDC6L,0x125CFBB4236DFBL,0x2358E379CE607L,0xD680C6EB20806EL,0x314C200860FF77L,0x3CBC5A88268E4L,0x555C0078L};
+	public static final long[] CURVE_Pxaa= {0xE2935374E24678L,0xC34342582408BL,0xF765CCDEFC69EL,0xC33AAD2888D7F9L,0x7FD2458967473AL,0x52908ED55CBAB3L,0x786671EB14AB88L,0xA3EC96077958C8L,0x959DE53L};
+	public static final long[] CURVE_Pxab= {0x7F9EBAFFB099B8L,0x3775A012A47038L,0x6B5D1B1FC23856L,0x7F0A26A730F9E3L,0x1C38F85DB2A5CAL,0x76A753E17E6926L,0x2D39D1BE5AD0F9L,0x31733DFC651E4CL,0x3B0DED08L};
+	public static final long[] CURVE_Pxba= {0xA1CDE711AD15D3L,0x853178DF6E16EDL,0x64BF43EA3E09A1L,0x2D8CD6DE566B2FL,0xF21C26C74FDB8BL,0x47BCC89E3F6B1EL,0x3FE2103F329F00L,0x4E507AF2AA28C3L,0x3EC27FADL};
+	public static final long[] CURVE_Pxbb= {0x7AB2875EE0F480L,0x4556E43D6C4B8CL,0xFB22DF80E1CB99L,0xF70FD0122F1FFDL,0xD5DB25698EF5EAL,0x4805CE1AF1BA3AL,0x1DA7CE2E465CB7L,0xCA0799F7E65855L,0xA5B38DBL};
+	public static final long[] CURVE_Pyaa= {0x86499314781AA0L,0x609DA303B70AB1L,0xA52A6145FC44BBL,0x462E04C42A3124L,0xC383AE19AE68BBL,0xA1B34F6BE4FCADL,0x198F901AD0BF4L,0x736C094362CED0L,0x5057F35DL};
+	public static final long[] CURVE_Pyab= {0xBBEC57EEAE08FAL,0x78774BAA5F96ADL,0x64CAF099A42CA0L,0xC89FBBCCF70478L,0x6B720FEF855245L,0x97F916376F7B3EL,0x60F5587B5DF7E1L,0x61EE89637816BDL,0x2CE2B496L};
+	public static final long[] CURVE_Pyba= {0x730276A5F0CC41L,0xF89325530AA1F5L,0xD9CD879AF8A147L,0xEE53E8A9FE2880L,0x420F07D3715390L,0x4C15D519B71F3AL,0x1A39DD3CB5B9B1L,0x3EE631A6BE39F8L,0x18070466L};
+	public static final long[] CURVE_Pybb= {0xF1B2E6515C1CAEL,0xD40D355B0988DCL,0xC243FDC38A7772L,0x5D338136B675CAL,0x164E8A1D72FCDFL,0xBBAE5CD0961ACL,0xD6D04691771EB1L,0xD9BDEC8B792840L,0x499D14EAL};
+	public static final long[][] CURVE_W= {{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L}};
+	public static final long[][][] CURVE_SB= {{{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L}},{{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L}}};
+	public static final long[][] CURVE_WB= {{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L}};
+	public static final long[][][] CURVE_BB= {{{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L}},{{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L}},{{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L}},{{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L},{0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L,0x0L}}};
+
+}
+
diff --git a/src/main/java/org/apache/milagro/amcl/BLS381/BIG.java b/src/main/java/org/apache/milagro/amcl/BLS381/BIG.java
new file mode 100644
index 0000000..5a5644f
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS381/BIG.java
@@ -0,0 +1,917 @@
+/*
+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.
+*/
+
+/* AMCL BIG number class */ 
+
+package org.apache.milagro.amcl.BLS381;
+import org.apache.milagro.amcl.RAND;
+
+public class BIG {
+
+	public static final int CHUNK=64; /* Set word size */
+
+	public static final int MODBYTES=48; //(1+(MODBITS-1)/8);
+	public static final int BASEBITS=58; 
+
+	public static final int NLEN=(1+((8*MODBYTES-1)/BASEBITS));
+	public static final int DNLEN=2*NLEN;
+	public static final long BMASK=(((long)1<<BASEBITS)-1);
+
+	public static final int HBITS=BASEBITS/2;
+	public static final long HMASK=(((long)1<<HBITS)-1);
+	public static final int NEXCESS = ((int)1<<(CHUNK-BASEBITS-1));
+	public static final int BIGBITS=(MODBYTES*8);
+
+
+
+	protected long[] w=new long[NLEN];
+/* Constructors */
+	public BIG()
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=0;
+	}
+
+	public BIG(int x)
+	{
+		w[0]=x;
+		for (int i=1;i<NLEN;i++)
+			w[i]=0;
+	}
+
+	public BIG(BIG x)
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i];
+	}
+
+	public BIG(DBIG x)
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i];
+	}
+
+	public BIG(long[] x)
+	{
+			for (int i=0;i<NLEN;i++)
+				w[i]=x[i];
+	}
+
+	public long get(int i)
+	{
+		return w[i];
+	}
+
+	public void set(int i,long x)
+	{
+		w[i]=x;
+	} 
+
+
+/* Conditional swap of two bigs depending on d using XOR - no branches */
+	public void cswap(BIG b,int d)
+	{
+		int i;
+		long t,c=(long)d;
+		c=~(c-1);
+
+		for (i=0;i<NLEN;i++)
+		{
+			t=c&(w[i]^b.w[i]);
+			w[i]^=t;
+			b.w[i]^=t;
+		}
+	}
+
+	public void cmove(BIG g,int d)
+	{
+		int i;
+		long t,b=-d;
+
+		for (i=0;i<NLEN;i++)
+		{
+			w[i]^=(w[i]^g.w[i])&b;
+		}
+	}
+
+    public static long cast_to_chunk(int x)
+	{
+		return (long)x;
+	}
+
+/* normalise BIG - force all digits < 2^BASEBITS */
+	public long norm() {
+		long d,carry=0;
+		for (int i=0;i<NLEN-1;i++)
+		{
+			d=w[i]+carry;
+			w[i]=d&BMASK;
+			carry=(d>>BASEBITS);
+		}
+		w[NLEN-1]=(w[NLEN-1]+carry);
+		return (long)(w[NLEN-1]>>((8*MODBYTES)%BASEBITS));  
+	}
+
+/* return number of bits */
+	public int nbits() {
+		BIG t=new BIG(this);
+		int bts,k=NLEN-1;
+		long c;
+		t.norm();
+		while (k>=0 && t.w[k]==0) k--;
+		if (k<0) return 0;
+		bts=BASEBITS*k;
+		c=t.w[k];
+		while (c!=0) {c/=2; bts++;}
+		return bts;
+	}
+
+	public String toRawString()
+	{
+		BIG b=new BIG(this);
+		String s="(";
+		for (int i=0;i<NLEN-1;i++)
+		{
+			s+=Long.toHexString(b.w[i]); s+=",";
+		}
+		s+=Long.toHexString(b.w[NLEN-1]); s+=")";
+		return s;
+	}
+
+/* Convert to Hex String */
+	public String toString() {
+		BIG b;
+		String s="";
+		int len=nbits();
+
+		if (len%4==0) len/=4;
+		else {len/=4; len++;}
+		if (len<MODBYTES*2) len=MODBYTES*2;
+
+		for (int i=len-1;i>=0;i--)
+		{
+			b=new BIG(this);
+			b.shr(i*4);
+			s+=Long.toHexString(b.w[0]&15);
+		}
+		return s;
+	}
+
+/* set this[i]+=x*y+c, and return high part */
+
+	public static long[] muladd(long a,long b,long c,long r)
+	{
+		long x0,x1,y0,y1;
+		long[] tb=new long[2];
+		x0=a&HMASK;
+		x1=(a>>HBITS);
+		y0=b&HMASK;
+		y1=(b>>HBITS);
+		long bot=x0*y0;
+		long top=x1*y1;
+		long mid=x0*y1+x1*y0;
+		x0=mid&HMASK;
+		x1=(mid>>HBITS);
+		bot+=x0<<HBITS; bot+=c; bot+=r;
+		top+=x1;
+		long carry=bot>>BASEBITS;
+		bot&=BMASK;
+		top+=carry;
+		tb[0]=top;
+		tb[1]=bot;
+		return tb;
+	}
+
+/* this*=x, where x is >NEXCESS */
+	public long pmul(int c)
+	{
+		long ak,carry=0;
+		long[] cr=new long[2];
+
+		for (int i=0;i<NLEN;i++)
+		{
+			ak=w[i];
+			w[i]=0;
+
+			cr=muladd(ak,(long)c,carry,w[i]);
+			carry=cr[0];
+			w[i]=cr[1];
+
+		}
+		return carry;
+	}
+
+/* return this*c and catch overflow in DBIG */
+	public DBIG pxmul(int c)
+	{
+		DBIG m=new DBIG(0);	
+		long[] cr=new long[2];
+		long carry=0;
+		for (int j=0;j<NLEN;j++)
+		{
+			cr=muladd(w[j],(long)c,carry,m.w[j]);
+			carry=cr[0];
+			m.w[j]=cr[1];
+		}
+		m.w[NLEN]=carry;		
+		return m;
+	}
+
+/* divide by 3 */
+	public int div3()
+	{	
+		long ak,base,carry=0;
+		norm();
+		base=((long)1<<BASEBITS);
+		for (int i=NLEN-1;i>=0;i--)
+		{
+			ak=(carry*base+w[i]);
+			w[i]=ak/3;
+			carry=ak%3;
+		}
+		return (int)carry;
+	}
+
+/* return a*b where result fits in a BIG */
+	public static BIG smul(BIG a,BIG b)
+	{
+		long carry;
+		long[] cr=new long[2];
+		BIG c=new BIG(0);
+		for (int i=0;i<NLEN;i++)
+		{
+			carry=0;
+			for (int j=0;j<NLEN;j++)
+				if (i+j<NLEN)
+				{
+					cr=muladd(a.w[i],b.w[j],carry,c.w[i+j]);
+					carry=cr[0];
+					c.w[i+j]=cr[1];
+				}
+		}
+		return c;
+	}
+
+/* return a*b as DBIG */
+/* Inputs must be normed */
+	public static DBIG mul(BIG a,BIG b)
+	{
+		DBIG c=new DBIG(0);
+		long carry;
+		long[] cr=new long[2];
+
+		for (int i=0;i<NLEN;i++)
+		{
+			carry=0;
+			for (int j=0;j<NLEN;j++)
+			{
+				cr=muladd(a.w[i],b.w[j],carry,c.w[i+j]);
+				carry=cr[0];
+				c.w[i+j]=cr[1];
+			}
+			c.w[NLEN+i]=carry;
+		}
+
+		return c;
+	}
+
+/* return a^2 as DBIG */
+/* Input must be normed */
+	public static DBIG sqr(BIG a)
+	{
+		DBIG c=new DBIG(0);
+		long carry;
+		long[] cr=new long[2];
+
+		for (int i=0;i<NLEN;i++)
+		{
+			carry=0;
+			for (int j=i+1;j<NLEN;j++)
+			{
+				cr=muladd(2*a.w[i],a.w[j],carry,c.w[i+j]);
+				carry=cr[0];
+				c.w[i+j]=cr[1];
+			}
+			c.w[NLEN+i]=carry;
+		}
+
+		for (int i=0;i<NLEN;i++)
+		{
+			cr=muladd(a.w[i],a.w[i],0,c.w[2*i]);
+			c.w[2*i+1]+=cr[0];
+			c.w[2*i]=cr[1];
+		}
+		c.norm(); 
+		return c;
+	}
+
+	static BIG monty(BIG md,long MC,DBIG d)
+	{
+		BIG b;
+		long m,carry;
+		long[] cr=new long[2];
+		for (int i=0;i<NLEN;i++) 
+		{
+			if (MC==-1) m=(-d.w[i])&BMASK;
+			else
+			{
+				if (MC==1) m=d.w[i];
+				else m=(MC*d.w[i])&BMASK;
+			}
+
+			carry=0;
+			for (int j=0;j<NLEN;j++)
+			{
+				cr=muladd(m,md.w[j],carry,d.w[i+j]);
+				carry=cr[0];
+				d.w[i+j]=cr[1];
+			}
+			d.w[NLEN+i]+=carry;
+		}
+
+		b=new BIG(0);
+		for (int i=0;i<NLEN;i++ )
+			b.w[i]=d.w[NLEN+i];
+		b.norm();
+		return b;		
+	}
+
+
+
+/****************************************************************************/
+
+	public void xortop(long x)
+	{
+		w[NLEN-1]^=x;
+	}
+
+/* set x = x mod 2^m */
+	public void mod2m(int m)
+	{
+		int i,wd,bt;
+		wd=m/BASEBITS;
+		bt=m%BASEBITS;
+		w[wd]&=((cast_to_chunk(1)<<bt)-1);
+		for (i=wd+1;i<NLEN;i++) w[i]=0;
+	}
+
+/* return n-th bit */
+	public int bit(int n)
+	{
+		if ((w[n/BASEBITS]&(cast_to_chunk(1)<<(n%BASEBITS)))>0) return 1;
+		else return 0;
+	}
+
+/* Shift right by less than a word */
+	public int fshr(int k) {
+		int r=(int)(w[0]&((cast_to_chunk(1)<<k)-1)); /* shifted out part */
+		for (int i=0;i<NLEN-1;i++)
+			w[i]=(w[i]>>k)|((w[i+1]<<(BASEBITS-k))&BMASK);
+		w[NLEN-1]=w[NLEN-1]>>k;
+		return r;
+	}
+
+/* Shift right by less than a word */
+	public int fshl(int k) {
+		w[NLEN-1]=((w[NLEN-1]<<k))|(w[NLEN-2]>>(BASEBITS-k));
+		for (int i=NLEN-2;i>0;i--)
+			w[i]=((w[i]<<k)&BMASK)|(w[i-1]>>(BASEBITS-k));
+		w[0]=(w[0]<<k)&BMASK; 
+		return (int)(w[NLEN-1]>>((8*MODBYTES)%BASEBITS)); /* return excess - only used in FF.java */
+	}
+
+/* test for zero */
+	public boolean iszilch() {
+		for (int i=0;i<NLEN;i++)
+			if (w[i]!=0) return false;
+		return true; 
+	}
+
+/* set to zero */
+	public void zero()
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=0;
+	}
+
+/* set to one */
+	public void one()
+	{
+		w[0]=1;
+		for (int i=1;i<NLEN;i++)
+			w[i]=0;
+	}
+
+/* Test for equal to one */
+	public boolean isunity()
+	{
+		for (int i=1;i<NLEN;i++)
+			if (w[i]!=0) return false;
+		if (w[0]!=1) return false;
+		return true;
+	}
+
+/* Copy from another BIG */
+	public void copy(BIG x)
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i];
+	}
+
+	public void copy(DBIG x)
+	{
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i];
+	}
+
+/* general shift right */
+	public void shr(int k) {
+		int n=k%BASEBITS;
+		int m=k/BASEBITS;	
+		for (int i=0;i<NLEN-m-1;i++)
+			w[i]=(w[m+i]>>n)|((w[m+i+1]<<(BASEBITS-n))&BMASK);
+		if (NLEN>m) w[NLEN-m-1]=w[NLEN-1]>>n;
+		for (int i=NLEN-m;i<NLEN;i++) w[i]=0;
+	}
+
+/* general shift left */
+	public void shl(int k) {
+		int n=k%BASEBITS;
+		int m=k/BASEBITS;
+
+		w[NLEN-1]=((w[NLEN-1-m]<<n));
+		if (NLEN>=m+2) w[NLEN-1]|=(w[NLEN-m-2]>>(BASEBITS-n));
+
+		for (int i=NLEN-2;i>m;i--)
+			w[i]=((w[i-m]<<n)&BMASK)|(w[i-m-1]>>(BASEBITS-n));
+		w[m]=(w[0]<<n)&BMASK;
+		for (int i=0;i<m;i++) w[i]=0;
+	}
+
+/* return this+x */
+	public BIG plus(BIG x) {
+		BIG s=new BIG(0);
+		for (int i=0;i<NLEN;i++)
+			s.w[i]=w[i]+x.w[i];
+		return s;
+	}
+
+/* this+=x */
+	public void add(BIG x) {
+		for (int i=0;i<NLEN;i++)
+			w[i]+=x.w[i];
+	}
+
+/* this|=x */
+	public void or(BIG x) {
+		for (int i=0;i<NLEN;i++)
+			w[i]|=x.w[i];
+	}
+
+
+/* this+=x, where x is int */
+	public void inc(int x) {
+		norm();
+		w[0]+=x;
+	}
+
+/* this+=x, where x is long */
+	public void incl(long x) {
+		norm();
+		w[0]+=x;
+	}	
+
+/* return this.x */
+	public BIG minus(BIG x) {
+		BIG d=new BIG(0);
+		for (int i=0;i<NLEN;i++)
+			d.w[i]=w[i]-x.w[i];
+		return d;
+	}
+
+/* this-=x */
+	public void sub(BIG x) {
+		for (int i=0;i<NLEN;i++)
+			w[i]-=x.w[i];
+	}
+
+/* reverse subtract this=x-this */
+	public void rsub(BIG x) {
+		for (int i=0;i<NLEN;i++)
+			w[i]=x.w[i]-w[i];
+	}
+
+/* this-=x where x is int */
+	public void dec(int x) {
+		norm();
+		w[0]-=x;
+	}
+
+/* this*=x, where x is small int<NEXCESS */
+	public void imul(int c)
+	{
+		for (int i=0;i<NLEN;i++) w[i]*=c;
+	}
+
+/* convert this BIG to byte array */
+	public void tobytearray(byte[] b,int n)
+	{
+		
+		BIG c=new BIG(this);
+		c.norm();
+
+		for (int i=MODBYTES-1;i>=0;i--)
+		{
+			b[i+n]=(byte)c.w[0];
+			c.fshr(8);
+		}
+	}
+
+/* convert from byte array to BIG */
+	public static BIG frombytearray(byte[] b,int n)
+	{
+		BIG m=new BIG(0);
+
+		for (int i=0;i<MODBYTES;i++)
+		{
+			m.fshl(8); m.w[0]+=(int)b[i+n]&0xff;
+			//m.inc((int)b[i]&0xff);
+		}
+		return m; 
+	}
+
+	public void toBytes(byte[] b)
+	{
+		tobytearray(b,0);
+	}
+
+	public static BIG fromBytes(byte[] b)
+	{
+		return frombytearray(b,0);
+	}
+
+/* Compare a and b, return 0 if a==b, -1 if a<b, +1 if a>b. Inputs must be normalised */
+	public static int comp(BIG a,BIG b)
+	{
+		for (int i=NLEN-1;i>=0;i--)
+		{
+			if (a.w[i]==b.w[i]) continue;
+			if (a.w[i]>b.w[i]) return 1;
+			else  return -1;
+		}
+		return 0;
+	}
+
+/* Arazi and Qi inversion mod 256 */
+	public static int invmod256(int a)
+	{
+		int U,t1,t2,b,c;
+		t1=0;
+		c=(a>>1)&1;  
+		t1+=c;
+		t1&=1;
+		t1=2-t1;
+		t1<<=1;
+		U=t1+1;
+
+// i=2
+		b=a&3;
+		t1=U*b; t1>>=2;
+		c=(a>>2)&3;
+		t2=(U*c)&3;
+		t1+=t2;
+		t1*=U; t1&=3;
+		t1=4-t1;
+		t1<<=2;
+		U+=t1;
+
+// i=4
+		b=a&15;
+		t1=U*b; t1>>=4;
+		c=(a>>4)&15;
+		t2=(U*c)&15;
+		t1+=t2;
+		t1*=U; t1&=15;
+		t1=16-t1;
+		t1<<=4;
+		U+=t1;
+
+		return U;
+	}
+
+/* a=1/a mod 2^256. This is very fast! */
+	public void invmod2m()
+	{
+		int i;
+		BIG U=new BIG(0);
+		BIG b=new BIG(0);
+		BIG c=new BIG(0);
+
+		U.inc(invmod256(lastbits(8)));
+
+		for (i=8;i<BIGBITS;i<<=1)
+		{
+			U.norm();
+			b.copy(this); b.mod2m(i);
+			BIG t1=BIG.smul(U,b); 
+			t1.shr(i);
+
+			c.copy(this); c.shr(i); c.mod2m(i);
+			BIG t2=BIG.smul(U,c); t2.mod2m(i);
+
+			t1.add(t2);
+			t1.norm();
+			b=BIG.smul(t1,U); t1.copy(b);
+			t1.mod2m(i);
+
+			t2.one(); t2.shl(i); t1.rsub(t2); t1.norm();
+
+			t1.shl(i);
+			U.add(t1);
+		}
+		U.mod2m(BIGBITS);
+		copy(U);
+		norm();
+	}
+
+/* reduce this mod m */
+	public void mod(BIG m1)
+	{
+		int k=0;  
+		BIG r=new BIG(0);
+		BIG m=new BIG(m1);
+
+		norm();
+		if (comp(this,m)<0) return;
+		do
+		{
+			m.fshl(1);
+			k++;
+		} while (comp(this,m)>=0);
+
+		while (k>0)
+		{
+			m.fshr(1);
+
+			r.copy(this);
+			r.sub(m);
+			r.norm();
+			cmove(r,(int)(1-((r.w[NLEN-1]>>(CHUNK-1))&1)));
+			k--;
+		}
+	}
+
+/* divide this by m */
+	public void div(BIG m1)
+	{
+		int d,k=0;
+		norm();
+		BIG e=new BIG(1);
+		BIG m=new BIG(m1);
+		BIG b=new BIG(this);
+		BIG r=new BIG(0);
+		zero();
+
+		while (comp(b,m)>=0)
+		{
+			e.fshl(1);
+			m.fshl(1);
+			k++;
+		}
+
+		while (k>0)
+		{
+			m.fshr(1);
+			e.fshr(1);
+
+			r.copy(b);
+			r.sub(m);
+			r.norm();
+			d=(int)(1-((r.w[NLEN-1]>>(CHUNK-1))&1));
+			b.cmove(r,d);
+			r.copy(this);
+			r.add(e);
+			r.norm();
+			cmove(r,d);
+			k--;
+		}
+	}
+
+/* return parity */
+	public int parity()
+	{
+		return (int)(w[0]%2);
+	}
+
+/* return n last bits */
+	public int lastbits(int n)
+	{
+		int msk=(1<<n)-1;
+		norm();
+		return ((int)w[0])&msk;
+	}
+
+/* get 8*MODBYTES size random number */
+	public static BIG random(RAND rng)
+	{
+		BIG m=new BIG(0);
+		int i,b,j=0,r=0;
+
+/* generate random BIG */ 
+		for (i=0;i<8*MODBYTES;i++)   
+		{
+			if (j==0) r=rng.getByte();
+			else r>>=1;
+
+			b=r&1;
+			m.shl(1); m.w[0]+=b;// m.inc(b);
+			j++; j&=7; 
+		}
+		return m;
+	}
+
+/* Create random BIG in portable way, one bit at a time */
+	public static BIG randomnum(BIG q,RAND rng) 
+	{
+		DBIG d=new DBIG(0);
+		int i,b,j=0,r=0;
+		for (i=0;i<2*q.nbits();i++)
+		{
+			if (j==0) r=rng.getByte();
+			else r>>=1;
+
+			b=r&1;
+			d.shl(1); d.w[0]+=b;// m.inc(b);
+			j++; j&=7; 
+		}
+		BIG m=d.mod(q);
+		return m;
+	}
+
+/* return a*b mod m */
+	public static BIG modmul(BIG a1,BIG b1,BIG m)
+	{
+		BIG a=new BIG(a1);
+		BIG b=new BIG(b1);
+		a.mod(m);
+		b.mod(m);
+		DBIG d=mul(a,b);
+		return d.mod(m);
+	}
+
+/* return a^2 mod m */
+	public static BIG modsqr(BIG a1,BIG m)
+	{
+		BIG a=new BIG(a1);
+		a.mod(m);
+		DBIG d=sqr(a);
+		return d.mod(m);
+	}
+
+/* return -a mod m */
+	public static BIG modneg(BIG a1,BIG m)
+	{
+		BIG a=new BIG(a1);
+		a.mod(m);
+		return m.minus(a);
+	}
+
+/* return this^e mod m */
+	public BIG powmod(BIG e1,BIG m)
+	{
+		BIG e=new BIG(e1);
+		int bt;
+		norm();
+		e.norm();
+		BIG a=new BIG(1);
+		BIG z=new BIG(e);
+		BIG s=new BIG(this);
+		while (true)
+		{
+			bt=z.parity();
+			z.fshr(1);
+			if (bt==1) a=modmul(a,s,m);
+			if (z.iszilch()) break;
+			s=modsqr(s,m);
+		}
+		return a;
+	}
+
+/* Jacobi Symbol (this/p). Returns 0, 1 or -1 */
+	public int jacobi(BIG p)
+	{
+		int n8,k,m=0;
+		BIG t=new BIG(0);
+		BIG x=new BIG(0);
+		BIG n=new BIG(0);
+		BIG zilch=new BIG(0);
+		BIG one=new BIG(1);
+		if (p.parity()==0 || comp(this,zilch)==0 || comp(p,one)<=0) return 0;
+		norm();
+		x.copy(this);
+		n.copy(p);
+		x.mod(p);
+
+		while (comp(n,one)>0)
+		{
+			if (comp(x,zilch)==0) return 0;
+			n8=n.lastbits(3);
+			k=0;
+			while (x.parity()==0)
+			{
+				k++;
+				x.shr(1);
+			}
+			if (k%2==1) m+=(n8*n8-1)/8;
+			m+=(n8-1)*(x.lastbits(2)-1)/4;
+			t.copy(n);
+			t.mod(x);
+			n.copy(x);
+			x.copy(t);
+			m%=2;
+
+		}
+		if (m==0) return 1;
+		else return -1;
+	}
+
+/* this=1/this mod p. Binary method */
+	public void invmodp(BIG p)
+	{
+		mod(p);
+		BIG u=new BIG(this);
+		BIG v=new BIG(p);
+		BIG x1=new BIG(1);
+		BIG x2=new BIG(0);
+		BIG t=new BIG(0);
+		BIG one=new BIG(1);
+
+		while (comp(u,one)!=0 && comp(v,one)!=0)
+		{
+			while (u.parity()==0)
+			{
+				u.fshr(1);
+				if (x1.parity()!=0)
+				{
+					x1.add(p);
+					x1.norm();
+				}
+				x1.fshr(1);
+			}
+			while (v.parity()==0)
+			{
+				v.fshr(1);
+				if (x2.parity()!=0)
+				{
+					x2.add(p);
+					x2.norm();
+				}
+				x2.fshr(1);
+			}
+			if (comp(u,v)>=0)
+			{
+				u.sub(v);
+				u.norm();
+				if (comp(x1,x2)>=0) x1.sub(x2);
+				else
+				{
+					t.copy(p);
+					t.sub(x2);
+					x1.add(t);
+				}
+				x1.norm();
+			}
+			else
+			{
+				v.sub(u);
+				v.norm();
+				if (comp(x2,x1)>=0) x2.sub(x1);
+				else
+				{
+					t.copy(p);
+					t.sub(x1);
+					x2.add(t);
+				}
+				x2.norm();
+			}
+		}
+		if (comp(u,one)==0) copy(x1);
+		else copy(x2);
+	}
+}
diff --git a/src/main/java/org/apache/milagro/amcl/BLS381/DBIG.java b/src/main/java/org/apache/milagro/amcl/BLS381/DBIG.java
new file mode 100644
index 0000000..fad920e
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS381/DBIG.java
@@ -0,0 +1,279 @@
+/*
+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.
+*/
+
+/* AMCL double length DBIG number class */ 
+
+package org.apache.milagro.amcl.BLS381;
+
+public class DBIG {
+	protected long[] w=new long[BIG.DNLEN];
+
+/* normalise this */
+	public void norm() {
+		long d,carry=0;
+		for (int i=0;i<BIG.DNLEN-1;i++)
+		{
+			d=w[i]+carry;
+			carry=d>>BIG.BASEBITS;
+			w[i]=d&BIG.BMASK;
+		}
+		w[BIG.DNLEN-1]=(w[BIG.DNLEN-1]+carry);
+	}
+
+
+/*
+	public String toRawString()
+	{
+		DBIG b=new DBIG(this);
+		String s="(";
+		for (int i=0;i<BIG.DNLEN-1;i++)
+		{
+			s+=Long.toHexString(b.w[i]); s+=",";
+		}
+		s+=Long.toHexString(b.w[BIG.DNLEN-1]); s+=")";
+		return s;
+	}
+*/
+
+/* split DBIG at position n, return higher half, keep lower half */
+	public BIG split(int n)
+	{
+		BIG t=new BIG(0);
+		int m=n%BIG.BASEBITS;
+		long nw,carry=w[BIG.DNLEN-1]<<(BIG.BASEBITS-m);
+
+		for (int i=BIG.DNLEN-2;i>=BIG.NLEN-1;i--)
+		{
+			nw=(w[i]>>m)|carry;
+			carry=(w[i]<<(BIG.BASEBITS-m))&BIG.BMASK;
+			t.w[i-BIG.NLEN+1]=nw;
+			//t.set(i-BIG.NLEN+1,nw);
+		}
+		w[BIG.NLEN-1]&=(((long)1<<m)-1);
+		return t;
+	}
+
+/****************************************************************************/
+
+/* return number of bits in this */
+	public int nbits() {
+		int bts,k=BIG.DNLEN-1;
+		long c;
+		norm();
+		while (w[k]==0 && k>=0) k--;
+		if (k<0) return 0;
+		bts=BIG.BASEBITS*k;
+		c=w[k];
+		while (c!=0) {c/=2; bts++;}
+		return bts;
+	}
+
+/* convert this to string */
+	public String toString() {
+		DBIG b;
+		String s="";
+		int len=nbits();
+		if (len%4==0) len>>=2; //len/=4;
+		else {len>>=2; len++;}
+
+		for (int i=len-1;i>=0;i--)
+		{
+			b=new DBIG(this);
+			b.shr(i*4);
+			s+=Integer.toHexString((int)(b.w[0]&15));
+		}
+		return s;
+	}
+
+	public void cmove(DBIG g,int d)
+	{
+		int i;
+		for (i=0;i<BIG.DNLEN;i++)
+		{
+			w[i]^=(w[i]^g.w[i])&BIG.cast_to_chunk(-d);
+		}
+	}
+
+/* Constructors */
+	public DBIG(int x)
+	{
+		w[0]=x;
+		for (int i=1;i<BIG.DNLEN;i++)
+			w[i]=0;
+	}
+
+	public DBIG(DBIG x)
+	{
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]=x.w[i];
+	}
+
+	public DBIG(BIG x)
+	{
+		for (int i=0;i<BIG.NLEN-1;i++)
+			w[i]=x.w[i]; //get(i);
+
+		w[BIG.NLEN-1]=x.w[(BIG.NLEN-1)]&BIG.BMASK; /* top word normalized */
+		w[BIG.NLEN]=(x.w[(BIG.NLEN-1)]>>BIG.BASEBITS);
+
+		for (int i=BIG.NLEN+1;i<BIG.DNLEN;i++) w[i]=0;
+	}
+
+/* Copy from another DBIG */
+	public void copy(DBIG x)
+	{
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]=x.w[i];
+	}
+
+/* Copy into upper part */
+	public void ucopy(BIG x)
+	{
+		for (int i=0;i<BIG.NLEN;i++)
+			w[i]=0;
+		for (int i=BIG.NLEN;i<BIG.DNLEN;i++)
+			w[i]=x.w[i-BIG.NLEN];
+	}
+
+/* test this=0? */
+	public boolean iszilch() {
+		for (int i=0;i<BIG.DNLEN;i++)
+			if (w[i]!=0) return false;
+		return true; 
+	}
+
+/* shift this right by k bits */
+	public void shr(int k) {
+		int n=k%BIG.BASEBITS;
+		int m=k/BIG.BASEBITS;	
+		for (int i=0;i<BIG.DNLEN-m-1;i++)
+			w[i]=(w[m+i]>>n)|((w[m+i+1]<<(BIG.BASEBITS-n))&BIG.BMASK);
+		w[BIG.DNLEN-m-1]=w[BIG.DNLEN-1]>>n;
+		for (int i=BIG.DNLEN-m;i<BIG.DNLEN;i++) w[i]=0;
+	}
+
+/* shift this left by k bits */
+	public void shl(int k) {
+		int n=k%BIG.BASEBITS;
+		int m=k/BIG.BASEBITS;
+
+		w[BIG.DNLEN-1]=((w[BIG.DNLEN-1-m]<<n))|(w[BIG.DNLEN-m-2]>>(BIG.BASEBITS-n));
+		for (int i=BIG.DNLEN-2;i>m;i--)
+			w[i]=((w[i-m]<<n)&BIG.BMASK)|(w[i-m-1]>>(BIG.BASEBITS-n));
+		w[m]=(w[0]<<n)&BIG.BMASK; 
+		for (int i=0;i<m;i++) w[i]=0;
+	}
+
+/* this+=x */
+	public void add(DBIG x) {
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]+=x.w[i];	
+	}
+
+/* this-=x */
+	public void sub(DBIG x) {
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]-=x.w[i];
+	}
+
+	public void rsub(DBIG x) {
+		for (int i=0;i<BIG.DNLEN;i++)
+			w[i]=x.w[i]-w[i];
+	}
+
+/* Compare a and b, return 0 if a==b, -1 if a<b, +1 if a>b. Inputs must be normalised */
+	public static int comp(DBIG a,DBIG b)
+	{
+		for (int i=BIG.DNLEN-1;i>=0;i--)
+		{
+			if (a.w[i]==b.w[i]) continue;
+			if (a.w[i]>b.w[i]) return 1;
+			else  return -1;
+		}
+		return 0;
+	}
+
+/* reduces this DBIG mod a BIG, and returns the BIG */
+	public BIG mod(BIG c)
+	{
+		int k=0;  
+		norm();
+		DBIG m=new DBIG(c);
+		DBIG r=new DBIG(0);
+
+		if (comp(this,m)<0) return new BIG(this);
+		
+		do
+		{
+			m.shl(1);
+			k++;
+		}
+		while (comp(this,m)>=0);
+
+		while (k>0)
+		{
+			m.shr(1);
+
+			r.copy(this);
+			r.sub(m);
+			r.norm();
+			cmove(r,(int)(1-((r.w[BIG.DNLEN-1]>>(BIG.CHUNK-1))&1)));
+
+			k--;
+		}
+		return new BIG(this);
+	}
+
+/* return this/c */
+	public BIG div(BIG c)
+	{
+		int d,k=0;
+		DBIG m=new DBIG(c);
+		DBIG dr=new DBIG(0);
+		BIG r=new BIG(0);
+		BIG a=new BIG(0);
+		BIG e=new BIG(1);
+		norm();
+
+		while (comp(this,m)>=0)
+		{
+			e.fshl(1);
+			m.shl(1);
+			k++;
+		}
+
+		while (k>0)
+		{
+			m.shr(1);
+			e.shr(1);
+
+			dr.copy(this);
+			dr.sub(m);
+			dr.norm();
+			d=(int)(1-((dr.w[BIG.DNLEN-1]>>(BIG.CHUNK-1))&1));
+			cmove(dr,d);
+			r.copy(a);
+			r.add(e);
+			r.norm();
+			a.cmove(r,d);
+			k--;
+		}
+		return a;
+	}
+}
diff --git a/src/main/java/org/apache/milagro/amcl/BLS381/ECDH.java b/src/main/java/org/apache/milagro/amcl/BLS381/ECDH.java
new file mode 100644
index 0000000..672ee85
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS381/ECDH.java
@@ -0,0 +1,594 @@
+/*
+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.
+*/
+
+/* Elliptic Curve API high-level functions  */
+
+package org.apache.milagro.amcl.BLS381;
+
+import org.apache.milagro.amcl.RAND;
+import org.apache.milagro.amcl.HASH256;
+import org.apache.milagro.amcl.HASH384;
+import org.apache.milagro.amcl.HASH512;
+import org.apache.milagro.amcl.AES;
+
+public final class ECDH {
+	public static final int INVALID_PUBLIC_KEY=-2;
+	public static final int ERROR=-3;
+	public static final int INVALID=-4;
+	public static final int EFS=BIG.MODBYTES;
+	public static final int EGS=BIG.MODBYTES;
+//	public static final int EAS=16;
+//	public static final int EBS=16;
+
+//	public static final int SHA256=32;
+//	public static final int SHA384=48;
+//	public static final int SHA512=64;
+
+
+//	public static final int HASH_TYPE=SHA512;
+
+
+/* Convert Integer to n-byte array */
+	public static byte[] inttoBytes(int n,int len)
+	{
+		int i;
+		byte[] b=new byte[len];
+
+		for (i=0;i<len;i++) b[i]=0;
+		i=len; 
+		while (n>0 && i>0)
+		{
+			i--;
+			b[i]=(byte)(n&0xff);
+			n/=256;
+		}	
+		return b;
+	}
+
+	public static byte[] hashit(int sha,byte[] A,int n,byte[] B,int pad)
+	{
+		byte[] R=null;
+
+		if (sha==ECP.SHA256)
+		{
+			HASH256 H=new HASH256();
+			H.process_array(A); if (n>0) H.process_num(n);
+			if (B!=null) H.process_array(B);
+			R=H.hash();
+		}
+		if (sha==ECP.SHA384)
+		{
+			HASH384 H=new HASH384();
+			H.process_array(A); if (n>0) H.process_num(n);
+			if (B!=null) H.process_array(B);
+			R=H.hash();
+		}
+		if (sha==ECP.SHA512)
+		{
+			HASH512 H=new HASH512();
+			H.process_array(A); if (n>0) H.process_num(n);
+			if (B!=null) H.process_array(B);
+			R=H.hash();
+		}
+		if (R==null) return null;
+
+		if (pad==0) return R;
+/* If pad>0 output is truncated or padded to pad bytes */
+		byte[] W=new byte[pad];
+		if (pad<=sha) 
+		{
+			for (int i=0;i<pad;i++) W[i]=R[i];
+		}
+		else
+		{
+			for (int i=0;i<sha;i++) W[i+pad-sha]=R[i];
+            for (int i=0;i<pad-sha;i++) W[i]=0;
+ 
+			//for (int i=0;i<sha;i++) W[i]=R[i];
+			//for (int i=sha;i<pad;i++) W[i]=0;
+		}
+		return W;
+	}
+
+/* Key Derivation Functions */
+/* Input octet Z */
+/* Output key of length olen */
+	public static byte[] KDF1(int sha,byte[] Z,int olen)
+	{
+/* NOTE: the parameter olen is the length of the output K in bytes */
+		int hlen=sha;
+		byte[] K=new byte[olen];
+		byte[] B;
+		int counter,cthreshold,k=0;
+    
+		for (int i=0;i<K.length;i++) K[i]=0;
+
+		cthreshold=olen/hlen; if (olen%hlen!=0) cthreshold++;
+
+		for (counter=0;counter<cthreshold;counter++)
+		{
+			B=hashit(sha,Z,counter,null,0);
+			if (k+hlen>olen) for (int i=0;i<olen%hlen;i++) K[k++]=B[i];
+			else for (int i=0;i<hlen;i++) K[k++]=B[i];
+		}
+		return K;
+	}
+
+	public static byte[] KDF2(int sha,byte[] Z,byte[] P,int olen)
+	{
+/* NOTE: the parameter olen is the length of the output k in bytes */
+		int hlen=sha;
+		byte[] K=new byte[olen];
+		byte[] B;
+		int counter,cthreshold,k=0;
+    
+		for (int i=0;i<K.length;i++) K[i]=0;
+
+		cthreshold=olen/hlen; if (olen%hlen!=0) cthreshold++;
+
+		for (counter=1;counter<=cthreshold;counter++)
+		{
+			B=hashit(sha,Z,counter,P,0);
+			if (k+hlen>olen) for (int i=0;i<olen%hlen;i++) K[k++]=B[i];
+			else for (int i=0;i<hlen;i++) K[k++]=B[i];
+		}
+
+		return K;
+	}
+
+/* Password based Key Derivation Function */
+/* Input password p, salt s, and repeat count */
+/* Output key of length olen */
+	public static byte[] PBKDF2(int sha,byte[] Pass,byte[] Salt,int rep,int olen)
+	{
+		int i,j,k,len,d,opt;
+		d=olen/sha; if (olen%sha!=0) d++;
+		byte[] F=new byte[sha];
+		byte[] U=new byte[sha];
+		byte[] S=new byte[Salt.length+4];
+
+		byte[] K=new byte[d*sha];
+		opt=0;
+
+		for (i=1;i<=d;i++)
+		{
+			for (j=0;j<Salt.length;j++) S[j]=Salt[j];
+			byte[] N=inttoBytes(i,4);
+			for (j=0;j<4;j++) S[Salt.length+j]=N[j];
+
+			HMAC(sha,S,Pass,F);
+
+			for (j=0;j<sha;j++) U[j]=F[j];
+			for (j=2;j<=rep;j++)
+			{
+				HMAC(sha,U,Pass,U);
+				for (k=0;k<sha;k++) F[k]^=U[k];
+			}
+			for (j=0;j<sha;j++) K[opt++]=F[j];
+		}
+		byte[] key=new byte[olen];
+		for (i=0;i<olen;i++) key[i]=K[i];
+		return key;
+	}
+
+/* Calculate HMAC of m using key k. HMAC is tag of length olen */
+	public static int HMAC(int sha,byte[] M,byte[] K,byte[] tag)
+	{
+	/* Input is from an octet m        *
+	* olen is requested output length in bytes. k is the key  *
+	* The output is the calculated tag */
+		int b=64;
+		if (sha>32) b=128;
+		byte[] B;
+		byte[] K0=new byte[b];
+		int olen=tag.length;
+
+		//b=K0.length;
+		if (olen<4 /*|| olen>sha*/) return 0;
+
+		for (int i=0;i<b;i++) K0[i]=0;
+
+		if (K.length > b) 
+		{
+			B=hashit(sha,K,0,null,0);
+			for (int i=0;i<sha;i++) K0[i]=B[i];
+		}
+		else
+			for (int i=0;i<K.length;i++ ) K0[i]=K[i];
+		
+		for (int i=0;i<b;i++) K0[i]^=0x36;
+		B=hashit(sha,K0,0,M,0);
+
+		for (int i=0;i<b;i++) K0[i]^=0x6a;
+		B=hashit(sha,K0,0,B,olen);
+
+		for (int i=0;i<olen;i++) tag[i]=B[i];
+
+		return 1;
+	}
+
+/* AES encryption/decryption. Encrypt byte array M using key K and returns ciphertext */
+	public static byte[] AES_CBC_IV0_ENCRYPT(byte[] K,byte[] M)
+	{ /* AES CBC encryption, with Null IV and key K */
+	/* Input is from an octet string M, output is to an octet string C */
+	/* Input is padded as necessary to make up a full final block */
+		AES a=new AES();
+		boolean fin;
+		int i,j,ipt,opt;
+		byte[] buff=new byte[16];
+		int clen=16+(M.length/16)*16;
+
+		byte[] C=new byte[clen];
+		int padlen;
+
+		a.init(AES.CBC,K.length,K,null);
+
+		ipt=opt=0;
+		fin=false;
+		for(;;)
+		{
+			for (i=0;i<16;i++)
+			{
+				if (ipt<M.length) buff[i]=M[ipt++];
+				else {fin=true; break;}
+			}
+			if (fin) break;
+			a.encrypt(buff);
+			for (i=0;i<16;i++)
+				C[opt++]=buff[i];
+		}    
+
+/* last block, filled up to i-th index */
+
+		padlen=16-i;
+		for (j=i;j<16;j++) buff[j]=(byte)padlen;
+
+		a.encrypt(buff);
+
+		for (i=0;i<16;i++)
+			C[opt++]=buff[i];
+		a.end();    
+		return C;
+	}
+
+/* returns plaintext if all consistent, else returns null string */
+	public static byte[] AES_CBC_IV0_DECRYPT(byte[] K,byte[] C)
+	{ /* padding is removed */
+		AES a=new AES();
+		int i,ipt,opt,ch;
+		byte[] buff=new byte[16];
+		byte[] MM=new byte[C.length];
+		boolean fin,bad;
+		int padlen;
+		ipt=opt=0;
+
+		a.init(AES.CBC,K.length,K,null);
+
+		if (C.length==0) return new byte[0];
+		ch=C[ipt++]; 
+  
+		fin=false;
+
+		for(;;)
+		{
+			for (i=0;i<16;i++)
+			{
+				buff[i]=(byte)ch;      
+				if (ipt>=C.length) {fin=true; break;}  
+				else ch=C[ipt++];  
+			}
+			a.decrypt(buff);
+			if (fin) break;
+			for (i=0;i<16;i++)
+				MM[opt++]=buff[i];
+		}    
+
+		a.end();
+		bad=false;
+		padlen=buff[15];
+		if (i!=15 || padlen<1 || padlen>16) bad=true;
+		if (padlen>=2 && padlen<=16)
+			for (i=16-padlen;i<16;i++) if (buff[i]!=padlen) bad=true;
+    
+		if (!bad) for (i=0;i<16-padlen;i++)
+					MM[opt++]=buff[i];
+
+		if (bad) return new byte[0];
+
+		byte[] M=new byte[opt];
+		for (i=0;i<opt;i++) M[i]=MM[i];
+
+		return M;
+	}
+
+/* Calculate a public/private EC GF(p) key pair W,S where W=S.G mod EC(p),
+ * where S is the secret key and W is the public key
+ * and G is fixed generator.
+ * If RNG is NULL then the private key is provided externally in S
+ * otherwise it is generated randomly internally */
+	public static int KEY_PAIR_GENERATE(RAND RNG,byte[] S,byte[] W)
+	{
+		BIG r,s;
+		ECP G,WP;
+		int res=0;
+	//	byte[] T=new byte[EFS];
+
+		G=ECP.generator();
+
+		r=new BIG(ROM.CURVE_Order);
+
+		if (RNG==null)
+		{
+			s=BIG.fromBytes(S);
+			s.mod(r);
+		}
+		else
+		{
+			s=BIG.randomnum(r,RNG);
+		}
+
+		//if (ROM.AES_S>0)
+		//{
+		//	s.mod2m(2*ROM.AES_S);
+		//}
+		s.toBytes(S);
+
+		WP=G.mul(s);
+		WP.toBytes(W,false);  // To use point compression on public keys, change to true 
+
+		return res;
+	}
+
+/* validate public key. */
+	public static int PUBLIC_KEY_VALIDATE(byte[] W)
+	{
+		BIG r,q,k;
+		ECP WP=ECP.fromBytes(W);
+		int nb,res=0;
+
+		r=new BIG(ROM.CURVE_Order);
+
+		if (WP.is_infinity()) res=INVALID_PUBLIC_KEY;
+
+		if (res==0)
+		{
+
+			q=new BIG(ROM.Modulus);
+			nb=q.nbits();
+			k=new BIG(1); k.shl((nb+4)/2);
+			k.add(q);
+			k.div(r);
+
+			while (k.parity()==0)
+			{
+				k.shr(1);
+				WP.dbl();
+			}
+
+			if (!k.isunity()) WP=WP.mul(k);
+			if (WP.is_infinity()) res=INVALID_PUBLIC_KEY; 
+		}
+		return res;
+	}
+
+/* IEEE-1363 Diffie-Hellman online calculation Z=S.WD */
+	public static int SVDP_DH(byte[] S,byte[] WD,byte[] Z)    
+	{
+		BIG r,s,wx,wy,z;
+		int valid;
+		ECP W;
+		int res=0;
+		byte[] T=new byte[EFS];
+
+		s=BIG.fromBytes(S);
+
+		W=ECP.fromBytes(WD);
+		if (W.is_infinity()) res=ERROR;
+
+		if (res==0)
+		{
+			r=new BIG(ROM.CURVE_Order);
+			s.mod(r);
+
+			W=W.mul(s);
+			if (W.is_infinity()) res=ERROR; 
+			else 
+			{
+				W.getX().toBytes(T);
+				for (int i=0;i<EFS;i++) Z[i]=T[i];
+			}
+		}
+		return res;
+	}
+
+/* IEEE ECDSA Signature, C and D are signature on F using private key S */
+	public static int SP_DSA(int sha,RAND RNG,byte[] S,byte[] F,byte[] C,byte[] D)
+	{
+		byte[] T=new byte[EFS];
+		BIG r,s,f,c,d,u,vx,w;
+		ECP G,V;
+		byte[] B=hashit(sha,F,0,null,BIG.MODBYTES);
+
+		G=ECP.generator();
+		r=new BIG(ROM.CURVE_Order);
+
+		s=BIG.fromBytes(S);
+		f=BIG.fromBytes(B);
+
+		c=new BIG(0);
+		d=new BIG(0);
+		V=new ECP();
+
+		do {
+			u=BIG.randomnum(r,RNG);
+			w=BIG.randomnum(r,RNG); /* side channel masking */
+			//if (ROM.AES_S>0)
+			//{
+			//	u.mod2m(2*ROM.AES_S);
+			//}			
+			V.copy(G);
+			V=V.mul(u);   		
+			vx=V.getX();
+			c.copy(vx);
+			c.mod(r);
+			if (c.iszilch()) continue;
+
+			u.copy(BIG.modmul(u,w,r));
+
+			u.invmodp(r);
+			d.copy(BIG.modmul(s,c,r));
+			d.add(f);
+
+			d.copy(BIG.modmul(d,w,r));
+
+			d.copy(BIG.modmul(u,d,r));
+		} while (d.iszilch());
+       
+		c.toBytes(T);
+		for (int i=0;i<EFS;i++) C[i]=T[i];
+		d.toBytes(T);
+		for (int i=0;i<EFS;i++) D[i]=T[i];
+		return 0;
+	}
+
+/* IEEE1363 ECDSA Signature Verification. Signature C and D on F is verified using public key W */
+	public static int VP_DSA(int sha,byte[] W,byte[] F, byte[] C,byte[] D)
+	{
+		BIG r,f,c,d,h2;
+		int res=0;
+		ECP G,WP,P;
+		int valid; 
+
+		byte[] B=hashit(sha,F,0,null,BIG.MODBYTES);
+
+		G=ECP.generator();
+		r=new BIG(ROM.CURVE_Order);
+
+		c=BIG.fromBytes(C);
+		d=BIG.fromBytes(D);
+		f=BIG.fromBytes(B);
+     
+		if (c.iszilch() || BIG.comp(c,r)>=0 || d.iszilch() || BIG.comp(d,r)>=0) 
+            res=INVALID;
+
+		if (res==0)
+		{
+			d.invmodp(r);
+			f.copy(BIG.modmul(f,d,r));
+			h2=BIG.modmul(c,d,r);
+
+			WP=ECP.fromBytes(W);
+			if (WP.is_infinity()) res=ERROR;
+			else
+			{
+				P=new ECP();
+				P.copy(WP);
+				P=P.mul2(h2,G,f);
+				if (P.is_infinity()) res=INVALID;
+				else
+				{
+					d=P.getX();
+					d.mod(r);
+					if (BIG.comp(d,c)!=0) res=INVALID;
+				}
+			}
+		}
+
+		return res;
+	}
+
+/* IEEE1363 ECIES encryption. Encryption of plaintext M uses public key W and produces ciphertext V,C,T */
+	public static byte[] ECIES_ENCRYPT(int sha,byte[] P1,byte[] P2,RAND RNG,byte[] W,byte[] M,byte[] V,byte[] T)
+	{ 
+		int i,len;
+
+		byte[] Z=new byte[EFS];
+		byte[] VZ=new byte[3*EFS+1];
+		byte[] K1=new byte[ECP.AESKEY];
+		byte[] K2=new byte[ECP.AESKEY];
+		byte[] U=new byte[EGS];
+
+		if (KEY_PAIR_GENERATE(RNG,U,V)!=0) return new byte[0];  
+		if (SVDP_DH(U,W,Z)!=0) return new byte[0];     
+
+		for (i=0;i<2*EFS+1;i++) VZ[i]=V[i];
+		for (i=0;i<EFS;i++) VZ[2*EFS+1+i]=Z[i];
+
+
+		byte[] K=KDF2(sha,VZ,P1,2*ECP.AESKEY);
+
+		for (i=0;i<ECP.AESKEY;i++) {K1[i]=K[i]; K2[i]=K[ECP.AESKEY+i];} 
+
+		byte[] C=AES_CBC_IV0_ENCRYPT(K1,M);
+
+		byte[] L2=inttoBytes(P2.length,8);	
+	
+		byte[] AC=new byte[C.length+P2.length+8];
+		for (i=0;i<C.length;i++) AC[i]=C[i];
+		for (i=0;i<P2.length;i++) AC[C.length+i]=P2[i];
+		for (i=0;i<8;i++) AC[C.length+P2.length+i]=L2[i];
+	
+		HMAC(sha,AC,K2,T);
+
+		return C;
+	}
+
+/* IEEE1363 ECIES decryption. Decryption of ciphertext V,C,T using private key U outputs plaintext M */
+	public static byte[] ECIES_DECRYPT(int sha,byte[] P1,byte[] P2,byte[] V,byte[] C,byte[] T,byte[] U)
+	{ 
+
+		int i,len;
+
+		byte[] Z=new byte[EFS];
+		byte[] VZ=new byte[3*EFS+1];
+		byte[] K1=new byte[ECP.AESKEY];
+		byte[] K2=new byte[ECP.AESKEY];
+		byte[] TAG=new byte[T.length];
+
+		if (SVDP_DH(U,V,Z)!=0) return new byte[0];  
+
+		for (i=0;i<2*EFS+1;i++) VZ[i]=V[i];
+		for (i=0;i<EFS;i++) VZ[2*EFS+1+i]=Z[i];
+
+		byte[] K=KDF2(sha,VZ,P1,2*ECP.AESKEY);
+
+		for (i=0;i<ECP.AESKEY;i++) {K1[i]=K[i]; K2[i]=K[ECP.AESKEY+i];} 
+
+		byte[] M=AES_CBC_IV0_DECRYPT(K1,C); 
+
+		if (M.length==0) return M;
+
+		byte[] L2=inttoBytes(P2.length,8);	
+	
+		byte[] AC=new byte[C.length+P2.length+8];
+
+		for (i=0;i<C.length;i++) AC[i]=C[i];
+		for (i=0;i<P2.length;i++) AC[C.length+i]=P2[i];
+		for (i=0;i<8;i++) AC[C.length+P2.length+i]=L2[i];
+	
+		HMAC(sha,AC,K2,TAG);
+
+		boolean same=true;
+		for (i=0;i<T.length;i++) if (T[i]!=TAG[i]) same=false;
+		if (!same) return new byte[0];
+	
+		return M;
+
+	}
+}
diff --git a/src/main/java/org/apache/milagro/amcl/BLS381/ECP.java b/src/main/java/org/apache/milagro/amcl/BLS381/ECP.java
new file mode 100644
index 0000000..a08bc11
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS381/ECP.java
@@ -0,0 +1,1109 @@
+/*
+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.
+*/
+
+/* Elliptic Curve Point class */
+
+package org.apache.milagro.amcl.BLS381;
+
+public final class ECP {
+
+	public static final int WEIERSTRASS=0;
+	public static final int EDWARDS=1;
+	public static final int MONTGOMERY=2;
+	public static final int NOT=0;
+	public static final int BN=1;
+	public static final int BLS=2;
+	public static final int D_TYPE=0;
+	public static final int M_TYPE=1;
+	public static final int POSITIVEX=0;
+	public static final int NEGATIVEX=1;
+
+	public static final int CURVETYPE=WEIERSTRASS;
+	public static final int CURVE_PAIRING_TYPE=BLS;
+	public static final int SEXTIC_TWIST=M_TYPE;
+	public static final int SIGN_OF_X=NEGATIVEX;
+
+	public static final int SHA256=32;
+	public static final int SHA384=48;
+	public static final int SHA512=64;
+
+	public static final int HASH_TYPE=32;
+	public static final int AESKEY=16;
+
+	private FP x;
+	private FP y;
+	private FP z;
+//	private boolean INF;
+
+/* Constructor - set to O */
+	public ECP() {
+		//INF=true;
+		x=new FP(0);
+		y=new FP(1);
+		if (CURVETYPE==EDWARDS)
+		{
+			z=new FP(1);
+		}
+		else
+		{
+			z=new FP(0);
+		}
+	}
+
+    public ECP(ECP e) {
+        this.x = new FP(e.x);
+        this.y = new FP(e.y);
+        this.z = new FP(e.z);
+    }
+
+/* test for O point-at-infinity */
+	public boolean is_infinity() {
+//		if (INF) return true;                            // Edits made
+		if (CURVETYPE==EDWARDS)
+		{
+			return (x.iszilch() && y.equals(z));
+		}
+		if (CURVETYPE==WEIERSTRASS)
+		{
+			return (x.iszilch() && z.iszilch());
+		}
+		if (CURVETYPE==MONTGOMERY)
+		{
+			return z.iszilch();
+		}
+		return true;
+	}
+/* Conditional swap of P and Q dependant on d */
+	private void cswap(ECP Q,int d)
+	{
+		x.cswap(Q.x,d);
+		if (CURVETYPE!=MONTGOMERY) y.cswap(Q.y,d);
+		z.cswap(Q.z,d);
+	//	if (CURVETYPE!=EDWARDS)
+	//	{
+	//		boolean bd;
+	//		if (d==0) bd=false;
+	//		else bd=true;
+	//		bd=bd&(INF^Q.INF);
+	//		INF^=bd;
+	//		Q.INF^=bd;
+	//	}
+	}
+
+/* Conditional move of Q to P dependant on d */
+	private void cmove(ECP Q,int d)
+	{
+		x.cmove(Q.x,d);
+		if (CURVETYPE!=MONTGOMERY) y.cmove(Q.y,d);
+		z.cmove(Q.z,d);
+	//	if (CURVETYPE!=EDWARDS)
+	//	{
+	//		boolean bd;
+	//		if (d==0) bd=false;
+	//		else bd=true;
+	//		INF^=(INF^Q.INF)&bd;
+	//	}
+	}
+
+/* return 1 if b==c, no branching */
+	private static int teq(int b,int c)
+	{
+		int x=b^c;
+		x-=1;  // if x=0, x now -1
+		return ((x>>31)&1);
+	}
+
+/* Constant time select from pre-computed table */
+	private void select(ECP W[],int b)
+	{
+		ECP MP=new ECP(); 
+		int m=b>>31;
+		int babs=(b^m)-m;
+
+		babs=(babs-1)/2;
+		cmove(W[0],teq(babs,0));  // conditional move
+		cmove(W[1],teq(babs,1));
+		cmove(W[2],teq(babs,2));
+		cmove(W[3],teq(babs,3));
+		cmove(W[4],teq(babs,4));
+		cmove(W[5],teq(babs,5));
+		cmove(W[6],teq(babs,6));
+		cmove(W[7],teq(babs,7));
+ 
+		MP.copy(this);
+		MP.neg();
+		cmove(MP,(int)(m&1));
+	}
+
+/* Test P == Q */
+	public boolean equals(ECP Q) {
+//		if (is_infinity() && Q.is_infinity()) return true;
+//		if (is_infinity() || Q.is_infinity()) return false;
+
+		FP a=new FP(0);                                        // Edits made
+		FP b=new FP(0);
+		a.copy(x); a.mul(Q.z); 
+		b.copy(Q.x); b.mul(z); 
+		if (!a.equals(b)) return false;
+		if (CURVETYPE!=MONTGOMERY)
+		{
+			a.copy(y); a.mul(Q.z); 
+			b.copy(Q.y); b.mul(z); 
+			if (!a.equals(b)) return false;
+		}
+		return true;
+	}
+
+/* this=P */
+	public void copy(ECP P)
+	{
+		x.copy(P.x);
+		if (CURVETYPE!=MONTGOMERY) y.copy(P.y);
+		z.copy(P.z);
+		//INF=P.INF;
+	}
+/* this=-this */
+	public void neg() {
+//		if (is_infinity()) return;
+		if (CURVETYPE==WEIERSTRASS)
+		{
+			y.neg(); y.norm();
+		}
+		if (CURVETYPE==EDWARDS)
+		{
+			x.neg(); x.norm();
+		}
+		return;
+	}
+/* set this=O */
+	public void inf() {
+//		INF=true;
+		x.zero();
+		if (CURVETYPE!=MONTGOMERY) y.one();
+		if (CURVETYPE!=EDWARDS) z.zero();
+		else z.one();
+	}
+
+/* Calculate RHS of curve equation */
+	public static FP RHS(FP x) {
+		x.norm();
+		FP r=new FP(x);
+		r.sqr();
+
+		if (CURVETYPE==WEIERSTRASS)
+		{ // x^3+Ax+B
+			FP b=new FP(new BIG(ROM.CURVE_B));
+			r.mul(x);
+			if (ROM.CURVE_A==-3)
+			{
+				FP cx=new FP(x);
+				cx.imul(3);
+				cx.neg(); cx.norm();
+				r.add(cx);
+			}
+			r.add(b);
+		}
+		if (CURVETYPE==EDWARDS)
+		{ // (Ax^2-1)/(Bx^2-1) 
+			FP b=new FP(new BIG(ROM.CURVE_B));
+
+			FP one=new FP(1);
+			b.mul(r);
+			b.sub(one);
+			b.norm();
+			if (ROM.CURVE_A==-1) r.neg();
+			r.sub(one); r.norm();
+			b.inverse();
+
+			r.mul(b);
+		}
+		if (CURVETYPE==MONTGOMERY)
+		{ // x^3+Ax^2+x
+			FP x3=new FP(0);
+			x3.copy(r);
+			x3.mul(x);
+			r.imul(ROM.CURVE_A);
+			r.add(x3);
+			r.add(x);
+		}
+		r.reduce();
+		return r;
+	}
+
+/* set (x,y) from two BIGs */
+	public ECP(BIG ix,BIG iy) {
+		x=new FP(ix);
+		y=new FP(iy);
+		z=new FP(1);
+		FP rhs=RHS(x);
+
+		if (CURVETYPE==MONTGOMERY)
+		{
+			if (rhs.jacobi()!=1) inf();
+			//if (rhs.jacobi()==1) INF=false;
+			//else inf();
+		}
+		else
+		{
+			FP y2=new FP(y);
+			y2.sqr();
+			if (!y2.equals(rhs)) inf();
+			//if (y2.equals(rhs)) INF=false;
+			//else inf();
+		}
+	}
+/* set (x,y) from BIG and a bit */
+	public ECP(BIG ix,int s) {
+		x=new FP(ix);
+		FP rhs=RHS(x);
+		y=new FP(0);
+		z=new FP(1);
+		if (rhs.jacobi()==1)
+		{
+			FP ny=rhs.sqrt();
+			if (ny.redc().parity()!=s) ny.neg();
+			y.copy(ny);
+			//INF=false;
+		}
+		else inf();
+	}
+
+/* set from x - calculate y from curve equation */
+	public ECP(BIG ix) {
+		x=new FP(ix);
+		FP rhs=RHS(x);
+		y=new FP(0);
+		z=new FP(1);
+		if (rhs.jacobi()==1)
+		{
+			if (CURVETYPE!=MONTGOMERY) y.copy(rhs.sqrt());
+			//INF=false;
+		}
+		else inf(); //INF=true;
+	}
+
+/* set to affine - from (x,y,z) to (x,y) */
+	public void affine() {
+		if (is_infinity()) return;	// 
+		FP one=new FP(1);
+		if (z.equals(one)) return;
+		z.inverse();
+		x.mul(z); x.reduce();
+		if (CURVETYPE!=MONTGOMERY)            // Edits made
+		{
+			y.mul(z); y.reduce();
+		}
+		z.copy(one);
+	}
+/* extract x as a BIG */
+	public BIG getX()
+	{
+		ECP W=new ECP(this);
+		W.affine();
+		return W.x.redc();
+	}
+/* extract y as a BIG */
+	public BIG getY()
+	{
+		ECP W=new ECP(this);
+		W.affine();
+		return W.y.redc();
+	}
+
+/* get sign of Y */
+	public int getS()
+	{
+		//affine();
+		BIG y=getY();
+		return y.parity();
+	}
+/* extract x as an FP */
+	public FP getx()
+	{
+		return x;
+	}
+/* extract y as an FP */
+	public FP gety()
+	{
+		return y;
+	}
+/* extract z as an FP */
+	public FP getz()
+	{
+		return z;
+	}
+/* convert to byte array */
+	public void toBytes(byte[] b,boolean compress)
+	{
+		byte[] t=new byte[BIG.MODBYTES];
+		ECP W=new ECP(this);
+		W.affine();
+
+		W.x.redc().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) b[i+1]=t[i];
+
+		if (CURVETYPE==MONTGOMERY)
+		{
+			b[0]=0x06;
+			return;
+		}
+
+		if (compress)
+		{
+			b[0]=0x02;
+			if (y.redc().parity()==1) b[0]=0x03;
+			return;
+		}
+
+		b[0]=0x04;
+
+		W.y.redc().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) b[i+BIG.MODBYTES+1]=t[i];
+	}
+/* convert from byte array to point */
+	public static ECP fromBytes(byte[] b)
+	{
+		byte[] t=new byte[BIG.MODBYTES];
+		BIG p=new BIG(ROM.Modulus);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=b[i+1];
+		BIG px=BIG.fromBytes(t);
+		if (BIG.comp(px,p)>=0) return new ECP();
+
+		if (CURVETYPE==MONTGOMERY)
+		{
+			return new ECP(px);
+		}
+
+		if (b[0]==0x04)
+		{
+			for (int i=0;i<BIG.MODBYTES;i++) t[i]=b[i+BIG.MODBYTES+1];
+			BIG py=BIG.fromBytes(t);
+			if (BIG.comp(py,p)>=0) return new ECP();
+			return new ECP(px,py);
+		}
+
+		if (b[0]==0x02 || b[0]==0x03)
+		{
+			return new ECP(px,(int)(b[0]&1));
+		}
+		return new ECP();
+	}
+/* convert to hex string */
+	public String toString() {
+		ECP W=new ECP(this);	
+		W.affine();
+		if (W.is_infinity()) return "infinity";
+		if (CURVETYPE==MONTGOMERY) return "("+W.x.redc().toString()+")";
+		else return "("+W.x.redc().toString()+","+W.y.redc().toString()+")";
+	}
+
+/* convert to hex string */
+	public String toRawString() {
+		//if (is_infinity()) return "infinity";
+		//affine();
+		ECP W=new ECP(this);	
+		if (CURVETYPE==MONTGOMERY) return "("+W.x.redc().toString()+","+W.z.redc().toString()+")";
+		else return "("+W.x.redc().toString()+","+W.y.redc().toString()+","+W.z.redc().toString()+")";
+	}
+
+/* this*=2 */
+	public void dbl() {
+//		if (INF) return;
+		
+		if (CURVETYPE==WEIERSTRASS)
+		{
+			if (ROM.CURVE_A==0)
+			{
+//System.out.println("Into dbl");
+				FP t0=new FP(y);                      /*** Change ***/    // Edits made
+				t0.sqr();
+				FP t1=new FP(y);
+				t1.mul(z);
+				FP t2=new FP(z);
+				t2.sqr();
+
+				z.copy(t0);
+				z.add(t0); z.norm(); 
+				z.add(z); z.add(z); z.norm();
+				t2.imul(3*ROM.CURVE_B_I);
+
+				FP x3=new FP(t2);
+				x3.mul(z);
+
+				FP y3=new FP(t0);
+				y3.add(t2); y3.norm();
+				z.mul(t1); 
+				t1.copy(t2); t1.add(t2); t2.add(t1);
+				t0.sub(t2); t0.norm(); y3.mul(t0); y3.add(x3);
+				t1.copy(x); t1.mul(y); 
+				x.copy(t0); x.norm(); x.mul(t1); x.add(x);
+				x.norm(); 
+				y.copy(y3); y.norm();
+//System.out.println("Out of dbl");
+			}
+			else
+			{
+				FP t0=new FP(x);
+				FP t1=new FP(y);
+				FP t2=new FP(z);
+				FP t3=new FP(x);
+				FP z3=new FP(z);
+				FP y3=new FP(0);
+				FP x3=new FP(0);
+				FP b=new FP(0);
+
+				if (ROM.CURVE_B_I==0)
+					b.copy(new FP(new BIG(ROM.CURVE_B)));
+
+				t0.sqr();  //1    x^2
+				t1.sqr();  //2    y^2
+				t2.sqr();  //3
+
+				t3.mul(y); //4
+				t3.add(t3); t3.norm();//5
+				z3.mul(x);   //6
+				z3.add(z3);  z3.norm();//7
+				y3.copy(t2); 
+				
+				if (ROM.CURVE_B_I==0)
+					y3.mul(b); //8
+				else
+					y3.imul(ROM.CURVE_B_I);
+				
+				y3.sub(z3); //y3.norm(); //9  ***
+				x3.copy(y3); x3.add(y3); x3.norm();//10
+
+				y3.add(x3); //y3.norm();//11
+				x3.copy(t1); x3.sub(y3); x3.norm();//12
+				y3.add(t1); y3.norm();//13
+				y3.mul(x3); //14
+				x3.mul(t3); //15
+				t3.copy(t2); t3.add(t2); //t3.norm(); //16
+				t2.add(t3); //t2.norm(); //17
+
+				if (ROM.CURVE_B_I==0)
+					z3.mul(b); //18
+				else
+					z3.imul(ROM.CURVE_B_I);
+
+				z3.sub(t2); //z3.norm();//19
+				z3.sub(t0); z3.norm();//20  ***
+				t3.copy(z3); t3.add(z3); //t3.norm();//21
+
+				z3.add(t3); z3.norm(); //22
+				t3.copy(t0); t3.add(t0); //t3.norm(); //23
+				t0.add(t3); //t0.norm();//24
+				t0.sub(t2); t0.norm();//25
+
+				t0.mul(z3);//26
+				y3.add(t0); //y3.norm();//27
+				t0.copy(y); t0.mul(z);//28
+				t0.add(t0); t0.norm(); //29
+				z3.mul(t0);//30
+				x3.sub(z3); //x3.norm();//31
+				t0.add(t0); t0.norm();//32
+				t1.add(t1); t1.norm();//33
+				z3.copy(t0); z3.mul(t1);//34
+
+				x.copy(x3); x.norm(); 
+				y.copy(y3); y.norm();
+				z.copy(z3); z.norm();
+			}
+		}
+		if (CURVETYPE==EDWARDS)
+		{
+//System.out.println("Into dbl");
+			FP C=new FP(x);
+			FP D=new FP(y);
+			FP H=new FP(z);
+			FP J=new FP(0);
+
+			x.mul(y); x.add(x); x.norm();
+			C.sqr();
+			D.sqr();
+
+			if (ROM.CURVE_A==-1) C.neg();	
+
+			y.copy(C); y.add(D); y.norm();
+			H.sqr(); H.add(H);
+
+			z.copy(y);
+			J.copy(y); 
+
+			J.sub(H); J.norm();
+			x.mul(J);
+
+			C.sub(D); C.norm();
+			y.mul(C);
+			z.mul(J);
+//System.out.println("Out of dbl");
+		}
+		if (CURVETYPE==MONTGOMERY)
+		{
+			FP A=new FP(x);
+			FP B=new FP(x);		
+			FP AA=new FP(0);
+			FP BB=new FP(0);
+			FP C=new FP(0);
+
+			A.add(z); A.norm();
+			AA.copy(A); AA.sqr();
+			B.sub(z); B.norm();
+			BB.copy(B); BB.sqr();
+			C.copy(AA); C.sub(BB); C.norm();
+			x.copy(AA); x.mul(BB);
+
+			A.copy(C); A.imul((ROM.CURVE_A+2)/4);
+
+			BB.add(A); BB.norm();
+			z.copy(BB); z.mul(C);
+		}
+		return;
+	}
+
+/* this+=Q */
+	public void add(ECP Q) {
+//		if (INF)
+//		{
+//			copy(Q);
+//			return;
+//		}
+//		if (Q.INF) return;
+
+		if (CURVETYPE==WEIERSTRASS)
+		{
+
+
+			if (ROM.CURVE_A==0)
+			{
+// Edits made
+//System.out.println("Into add");
+				int b=3*ROM.CURVE_B_I;
+				FP t0=new FP(x);
+				t0.mul(Q.x);
+				FP t1=new FP(y);
+				t1.mul(Q.y);
+				FP t2=new FP(z);
+				t2.mul(Q.z);
+				FP t3=new FP(x);
+				t3.add(y); t3.norm();
+				FP t4=new FP(Q.x);
+				t4.add(Q.y); t4.norm();
+				t3.mul(t4);
+				t4.copy(t0); t4.add(t1);
+
+				t3.sub(t4); t3.norm();
+				t4.copy(y);
+				t4.add(z); t4.norm();
+				FP x3=new FP(Q.y);
+				x3.add(Q.z); x3.norm();
+
+				t4.mul(x3);
+				x3.copy(t1);
+				x3.add(t2);
+	
+				t4.sub(x3); t4.norm();
+				x3.copy(x); x3.add(z); x3.norm();
+				FP y3=new FP(Q.x);
+				y3.add(Q.z); y3.norm();
+				x3.mul(y3);
+				y3.copy(t0);
+				y3.add(t2);
+				y3.rsub(x3); y3.norm();
+				x3.copy(t0); x3.add(t0); 
+				t0.add(x3); t0.norm();
+				t2.imul(b);
+
+				FP z3=new FP(t1); z3.add(t2); z3.norm();
+				t1.sub(t2); t1.norm(); 
+				y3.imul(b);
+	
+				x3.copy(y3); x3.mul(t4); t2.copy(t3); t2.mul(t1); x3.rsub(t2);
+				y3.mul(t0); t1.mul(z3); y3.add(t1);
+				t0.mul(t3); z3.mul(t4); z3.add(t0);
+
+				x.copy(x3); x.norm(); 
+				y.copy(y3); y.norm();
+				z.copy(z3); z.norm();
+//System.out.println("Out of add");
+			}
+			else
+			{
+				FP t0=new FP(x);
+				FP t1=new FP(y);
+				FP t2=new FP(z);
+				FP t3=new FP(x);
+				FP t4=new FP(Q.x);
+				FP z3=new FP(0);
+				FP y3=new FP(Q.x);
+				FP x3=new FP(Q.y);
+				FP b=new FP(0);
+
+				if (ROM.CURVE_B_I==0)
+					b.copy(new FP(new BIG(ROM.CURVE_B)));
+
+				t0.mul(Q.x); //1
+				t1.mul(Q.y); //2
+				t2.mul(Q.z); //3
+
+				t3.add(y); t3.norm(); //4
+				t4.add(Q.y); t4.norm();//5
+				t3.mul(t4);//6
+				t4.copy(t0); t4.add(t1); //t4.norm(); //7
+				t3.sub(t4); t3.norm(); //8
+				t4.copy(y); t4.add(z); t4.norm();//9
+				x3.add(Q.z); x3.norm();//10
+				t4.mul(x3); //11
+				x3.copy(t1); x3.add(t2); //x3.norm();//12
+
+				t4.sub(x3); t4.norm();//13
+				x3.copy(x); x3.add(z); x3.norm(); //14
+				y3.add(Q.z); y3.norm();//15
+
+				x3.mul(y3); //16
+				y3.copy(t0); y3.add(t2); //y3.norm();//17
+
+				y3.rsub(x3); y3.norm(); //18
+				z3.copy(t2); 
+				
+
+				if (ROM.CURVE_B_I==0)
+					z3.mul(b); //18
+				else
+					z3.imul(ROM.CURVE_B_I);
+				
+				x3.copy(y3); x3.sub(z3); x3.norm(); //20
+				z3.copy(x3); z3.add(x3); //z3.norm(); //21
+
+				x3.add(z3); //x3.norm(); //22
+				z3.copy(t1); z3.sub(x3); z3.norm(); //23
+				x3.add(t1); x3.norm(); //24
+
+				if (ROM.CURVE_B_I==0)
+					y3.mul(b); //18
+				else
+					y3.imul(ROM.CURVE_B_I);
+
+				t1.copy(t2); t1.add(t2); //t1.norm();//26
+				t2.add(t1); //t2.norm();//27
+
+				y3.sub(t2); //y3.norm(); //28
+
+				y3.sub(t0); y3.norm(); //29
+				t1.copy(y3); t1.add(y3); //t1.norm();//30
+				y3.add(t1); y3.norm(); //31
+
+				t1.copy(t0); t1.add(t0); //t1.norm(); //32
+				t0.add(t1); //t0.norm();//33
+				t0.sub(t2); t0.norm();//34
+				t1.copy(t4); t1.mul(y3);//35
+				t2.copy(t0); t2.mul(y3);//36
+				y3.copy(x3); y3.mul(z3);//37
+				y3.add(t2); //y3.norm();//38
+				x3.mul(t3);//39
+				x3.sub(t1);//40
+				z3.mul(t4);//41
+				t1.copy(t3); t1.mul(t0);//42
+				z3.add(t1); 
+				x.copy(x3); x.norm(); 
+				y.copy(y3); y.norm();
+				z.copy(z3); z.norm();
+			}
+		}
+		if (CURVETYPE==EDWARDS)
+		{
+//System.out.println("Into add");
+			FP A=new FP(z);
+			FP B=new FP(0);
+			FP C=new FP(x);
+			FP D=new FP(y);
+			FP E=new FP(0);
+			FP F=new FP(0);
+			FP G=new FP(0);
+
+			A.mul(Q.z);   
+			B.copy(A); B.sqr();    
+			C.mul(Q.x);      
+			D.mul(Q.y); 
+
+			E.copy(C); E.mul(D);  
+		
+			if (ROM.CURVE_B_I==0)
+			{
+				FP b=new FP(new BIG(ROM.CURVE_B));
+				E.mul(b);
+			}
+			else
+				E.imul(ROM.CURVE_B_I); 
+
+			F.copy(B); F.sub(E);      
+			G.copy(B); G.add(E);       
+
+			if (ROM.CURVE_A==1)
+			{
+				E.copy(D); E.sub(C);
+			}
+			C.add(D); 
+
+			B.copy(x); B.add(y);    
+			D.copy(Q.x); D.add(Q.y); B.norm(); D.norm(); 
+			B.mul(D);                   
+			B.sub(C); B.norm(); F.norm(); 
+			B.mul(F);                     
+			x.copy(A); x.mul(B); G.norm();  
+			if (ROM.CURVE_A==1)
+			{
+				E.norm(); C.copy(E); C.mul(G);  
+			}
+			if (ROM.CURVE_A==-1)
+			{
+				C.norm(); C.mul(G);
+			}
+			y.copy(A); y.mul(C);     
+
+			z.copy(F);	
+			z.mul(G);
+//System.out.println("Out of add");
+		}
+		return;
+	}
+
+/* Differential Add for Montgomery curves. this+=Q where W is this-Q and is affine. */
+	public void dadd(ECP Q,ECP W) {
+		FP A=new FP(x);
+		FP B=new FP(x);
+		FP C=new FP(Q.x);
+		FP D=new FP(Q.x);
+		FP DA=new FP(0);
+		FP CB=new FP(0);	
+			
+		A.add(z); 
+		B.sub(z); 
+
+		C.add(Q.z);
+		D.sub(Q.z);
+		A.norm();
+
+		D.norm();
+		DA.copy(D); DA.mul(A);
+
+		C.norm();
+		B.norm();
+		CB.copy(C); CB.mul(B);
+
+		A.copy(DA); A.add(CB); 
+		A.norm(); A.sqr();
+		B.copy(DA); B.sub(CB); 
+		B.norm(); B.sqr();
+
+		x.copy(A);
+		z.copy(W.x); z.mul(B);
+	}
+/* this-=Q */
+	public void sub(ECP Q) {
+		ECP NQ=new ECP(Q);
+		NQ.neg();
+		add(NQ);
+	}
+
+/* constant time multiply by small integer of length bts - use ladder */
+	public ECP pinmul(int e,int bts) {	
+		if (CURVETYPE==MONTGOMERY)
+			return this.mul(new BIG(e));
+		else
+		{
+			int nb,i,b;
+			ECP P=new ECP();
+			ECP R0=new ECP();
+			ECP R1=new ECP(); R1.copy(this);
+
+			for (i=bts-1;i>=0;i--)
+			{
+				b=(e>>i)&1;
+				P.copy(R1);
+				P.add(R0);
+				R0.cswap(R1,b);
+				R1.copy(P);
+				R0.dbl();
+				R0.cswap(R1,b);
+			}
+			P.copy(R0);
+			P.affine();
+			return P;
+		}
+	}
+
+/* return e.this */
+
+	public ECP mul(BIG e) {
+		if (e.iszilch() || is_infinity()) return new ECP();
+		ECP P=new ECP();
+		if (CURVETYPE==MONTGOMERY)
+		{
+/* use Ladder */
+			int nb,i,b;
+			ECP D=new ECP();
+			ECP R0=new ECP(); R0.copy(this);
+			ECP R1=new ECP(); R1.copy(this);
+			R1.dbl();
+
+			D.copy(this); D.affine();
+			nb=e.nbits();
+			for (i=nb-2;i>=0;i--)
+			{
+				b=e.bit(i);
+				P.copy(R1);
+
+				P.dadd(R0,D);
+				R0.cswap(R1,b);
+				R1.copy(P);
+				R0.dbl();
+				R0.cswap(R1,b);
+
+			}
+
+			P.copy(R0);
+		}
+		else
+		{
+// fixed size windows 
+			int i,b,nb,m,s,ns;
+			BIG mt=new BIG();
+			BIG t=new BIG();
+			ECP Q=new ECP();
+			ECP C=new ECP();
+			ECP[] W=new ECP[8];
+			byte[] w=new byte[1+(BIG.NLEN*BIG.BASEBITS+3)/4];
+
+			//affine();
+
+// precompute table 
+			Q.copy(this);
+
+			Q.dbl();
+			W[0]=new ECP();
+			W[0].copy(this);
+
+			for (i=1;i<8;i++)
+			{
+				W[i]=new ECP();
+				W[i].copy(W[i-1]);
+				W[i].add(Q);
+			}
+
+// make exponent odd - add 2P if even, P if odd 
+			t.copy(e);
+			s=t.parity();
+			t.inc(1); t.norm(); ns=t.parity(); mt.copy(t); mt.inc(1); mt.norm();
+			t.cmove(mt,s);
+			Q.cmove(this,ns);
+			C.copy(Q);
+
+			nb=1+(t.nbits()+3)/4;
+
+// convert exponent to signed 4-bit window 
+			for (i=0;i<nb;i++)
+			{
+				w[i]=(byte)(t.lastbits(5)-16);
+				t.dec(w[i]); t.norm();
+				t.fshr(4);	
+			}
+			w[nb]=(byte)t.lastbits(5);
+	
+			P.copy(W[(w[nb]-1)/2]);  
+			for (i=nb-1;i>=0;i--)
+			{
+				Q.select(W,w[i]);
+				P.dbl();
+				P.dbl();
+				P.dbl();
+				P.dbl();
+				P.add(Q);
+			}
+			P.sub(C); /* apply correction */
+		}
+		P.affine();
+		return P;
+	}
+
+/* Return e.this+f.Q */
+
+	public ECP mul2(BIG e,ECP Q,BIG f) {
+		BIG te=new BIG();
+		BIG tf=new BIG();
+		BIG mt=new BIG();
+		ECP S=new ECP();
+		ECP T=new ECP();
+		ECP C=new ECP();
+		ECP[] W=new ECP[8];
+		byte[] w=new byte[1+(BIG.NLEN*BIG.BASEBITS+1)/2];		
+		int i,s,ns,nb;
+		byte a,b;
+
+		//affine();
+		//Q.affine();
+
+		te.copy(e);
+		tf.copy(f);
+
+// precompute table 
+		W[1]=new ECP(); W[1].copy(this); W[1].sub(Q);
+		W[2]=new ECP(); W[2].copy(this); W[2].add(Q);
+		S.copy(Q); S.dbl();
+		W[0]=new ECP(); W[0].copy(W[1]); W[0].sub(S);
+		W[3]=new ECP(); W[3].copy(W[2]); W[3].add(S);
+		T.copy(this); T.dbl();
+		W[5]=new ECP(); W[5].copy(W[1]); W[5].add(T);
+		W[6]=new ECP(); W[6].copy(W[2]); W[6].add(T);
+		W[4]=new ECP(); W[4].copy(W[5]); W[4].sub(S);
+		W[7]=new ECP(); W[7].copy(W[6]); W[7].add(S);
+
+// if multiplier is odd, add 2, else add 1 to multiplier, and add 2P or P to correction 
+
+		s=te.parity();
+		te.inc(1); te.norm(); ns=te.parity(); mt.copy(te); mt.inc(1); mt.norm();
+		te.cmove(mt,s);
+		T.cmove(this,ns);
+		C.copy(T);
+
+		s=tf.parity();
+		tf.inc(1); tf.norm(); ns=tf.parity(); mt.copy(tf); mt.inc(1); mt.norm();
+		tf.cmove(mt,s);
+		S.cmove(Q,ns);
+		C.add(S);
+
+		mt.copy(te); mt.add(tf); mt.norm();
+		nb=1+(mt.nbits()+1)/2;
+
+// convert exponent to signed 2-bit window 
+		for (i=0;i<nb;i++)
+		{
+			a=(byte)(te.lastbits(3)-4);
+			te.dec(a); te.norm(); 
+			te.fshr(2);
+			b=(byte)(tf.lastbits(3)-4);
+			tf.dec(b); tf.norm(); 
+			tf.fshr(2);
+			w[i]=(byte)(4*a+b);
+		}
+		w[nb]=(byte)(4*te.lastbits(3)+tf.lastbits(3));
+		S.copy(W[(w[nb]-1)/2]);  
+
+		for (i=nb-1;i>=0;i--)
+		{
+			T.select(W,w[i]);
+			S.dbl();
+			S.dbl();
+			S.add(T);
+		}
+		S.sub(C); /* apply correction */
+		S.affine();
+		return S;
+	}
+
+// multiply a point by the curves cofactor
+	public void cfp()
+	{
+		int cf=ROM.CURVE_Cof_I;
+		if (cf==1) return;
+		if (cf==4)
+		{
+			dbl(); dbl();
+			//affine();
+			return;
+		} 
+		if (cf==8)
+		{
+			dbl(); dbl(); dbl();
+			//affine();
+			return;
+		}
+		BIG c=new BIG(ROM.CURVE_Cof);
+		copy(mul(c));
+	}
+
+/* Map byte string to curve point */
+	public static ECP mapit(byte[] h)
+	{
+		BIG q=new BIG(ROM.Modulus);
+		BIG x=BIG.fromBytes(h);
+		x.mod(q);
+		ECP P;
+
+		while (true)
+		{
+			while (true)
+			{
+				if (CURVETYPE!=MONTGOMERY)
+					P=new ECP(x,0);
+				else
+					P=new ECP(x);	
+				x.inc(1); x.norm();
+				if (!P.is_infinity()) break;
+			}
+			P.cfp();
+			if (!P.is_infinity()) break;
+		}
+		return P;
+	}
+
+	public static ECP generator()
+	{
+		ECP G;
+		BIG gx,gy;
+		gx=new BIG(ROM.CURVE_Gx);
+
+		if (ECP.CURVETYPE!=ECP.MONTGOMERY)
+		{
+			gy=new BIG(ROM.CURVE_Gy);
+			G=new ECP(gx,gy);
+		}
+		else
+			G=new ECP(gx);
+		return G;
+	}
+
+/*
+	public static void main(String[] args) {
+
+		BIG Gx=new BIG(ROM.CURVE_Gx);
+		BIG Gy;
+		ECP P;
+		if (CURVETYPE!=MONTGOMERY) Gy=new BIG(ROM.CURVE_Gy);
+		BIG r=new BIG(ROM.CURVE_Order);
+
+		//r.dec(7);
+	
+		System.out.println("Gx= "+Gx.toString());		
+		if (CURVETYPE!=MONTGOMERY) System.out.println("Gy= "+Gy.toString());	
+
+		if (CURVETYPE!=MONTGOMERY) P=new ECP(Gx,Gy);
+		else  P=new ECP(Gx);
+
+		System.out.println("P= "+P.toString());		
+
+		ECP R=P.mul(r);
+		//for (int i=0;i<10000;i++)
+		//	R=P.mul(r);
+	
+		System.out.println("R= "+R.toString());
+    } */
+}
+
diff --git a/src/main/java/org/apache/milagro/amcl/BLS381/ECP2.java b/src/main/java/org/apache/milagro/amcl/BLS381/ECP2.java
new file mode 100644
index 0000000..1870e3a
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS381/ECP2.java
@@ -0,0 +1,796 @@
+/*
+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.
+*/
+
+/* AMCL Weierstrass elliptic curve functions over FP2 */
+
+package org.apache.milagro.amcl.BLS381;
+
+public final class ECP2 {
+	private FP2 x;
+	private FP2 y;
+	private FP2 z;
+//	private boolean INF;
+
+/* Constructor - set this=O */
+	public ECP2() {
+//		INF=true;
+		x=new FP2(0);
+		y=new FP2(1);
+		z=new FP2(0);
+	}
+
+    public ECP2(ECP2 e) {
+        this.x = new FP2(e.x);
+        this.y = new FP2(e.y);
+        this.z = new FP2(e.z);
+    }
+
+/* Test this=O? */
+	public boolean is_infinity() {
+//		if (INF) return true;                    //******
+		return (x.iszilch() && z.iszilch());
+	}
+/* copy this=P */
+	public void copy(ECP2 P)
+	{
+		x.copy(P.x);
+		y.copy(P.y);
+		z.copy(P.z);
+//		INF=P.INF;
+	}
+/* set this=O */
+	public void inf() {
+//		INF=true;
+		x.zero();
+		y.one();
+		z.zero();
+	}
+
+/* Conditional move of Q to P dependant on d */
+	public void cmove(ECP2 Q,int d)
+	{
+		x.cmove(Q.x,d);
+		y.cmove(Q.y,d);
+		z.cmove(Q.z,d);
+
+	//	boolean bd;
+	//	if (d==0) bd=false;
+	//	else bd=true;
+	//	INF^=(INF^Q.INF)&bd;
+	}
+
+/* return 1 if b==c, no branching */
+	public static int teq(int b,int c)
+	{
+		int x=b^c;
+		x-=1;  // if x=0, x now -1
+		return ((x>>31)&1);
+	}
+
+/* Constant time select from pre-computed table */
+	public void select(ECP2 W[],int b)
+	{
+		ECP2 MP=new ECP2(); 
+		int m=b>>31;
+		int babs=(b^m)-m;
+
+		babs=(babs-1)/2;
+
+		cmove(W[0],teq(babs,0));  // conditional move
+		cmove(W[1],teq(babs,1));
+		cmove(W[2],teq(babs,2));
+		cmove(W[3],teq(babs,3));
+		cmove(W[4],teq(babs,4));
+		cmove(W[5],teq(babs,5));
+		cmove(W[6],teq(babs,6));
+		cmove(W[7],teq(babs,7));
+ 
+		MP.copy(this);
+		MP.neg();
+		cmove(MP,(int)(m&1));
+	}
+
+/* Test if P == Q */
+	public boolean equals(ECP2 Q) {
+//		if (is_infinity() && Q.is_infinity()) return true;
+//		if (is_infinity() || Q.is_infinity()) return false;
+
+
+		FP2 a=new FP2(x);                            // *****
+		FP2 b=new FP2(Q.x);
+		a.mul(Q.z); 
+		b.mul(z); 
+		if (!a.equals(b)) return false;
+
+		a.copy(y); a.mul(Q.z); 
+		b.copy(Q.y); b.mul(z); 
+		if (!a.equals(b)) return false;
+
+		return true;
+	}
+/* set this=-this */
+	public void neg() {
+//		if (is_infinity()) return;
+		y.norm();
+		y.neg(); y.norm();
+		return;
+	}
+/* set to Affine - (x,y,z) to (x,y) */
+	public void affine() {
+		if (is_infinity()) return;
+		FP2 one=new FP2(1);
+		if (z.equals(one))
+		{
+			x.reduce();
+			y.reduce();
+			return;
+		}
+		z.inverse();
+
+		x.mul(z); x.reduce();               // *****
+		y.mul(z); y.reduce();
+		z.copy(one);
+	}
+/* extract affine x as FP2 */
+	public FP2 getX()
+	{
+		ECP2 W=new ECP2(this);
+		W.affine();
+		return W.x;
+	}
+/* extract affine y as FP2 */
+	public FP2 getY()
+	{
+		ECP2 W=new ECP2(this);
+		W.affine();
+		return W.y;
+	}
+/* extract projective x */
+	public FP2 getx()
+	{
+		return x;
+	}
+/* extract projective y */
+	public FP2 gety()
+	{
+		return y;
+	}
+/* extract projective z */
+	public FP2 getz()
+	{
+		return z;
+	}
+/* convert to byte array */
+	public void toBytes(byte[] b)
+	{
+		byte[] t=new byte[BIG.MODBYTES];
+		ECP2 W=new ECP2(this);
+		W.affine();
+		W.x.getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++)
+			b[i]=t[i];
+		W.x.getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++)
+			b[i+BIG.MODBYTES]=t[i];
+
+		W.y.getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++)
+			b[i+2*BIG.MODBYTES]=t[i];
+		W.y.getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++)
+			b[i+3*BIG.MODBYTES]=t[i];
+	}
+/* convert from byte array to point */
+	public static ECP2 fromBytes(byte[] b)
+	{
+		byte[] t=new byte[BIG.MODBYTES];
+		BIG ra;
+		BIG rb;
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=b[i];
+		ra=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=b[i+BIG.MODBYTES];
+		rb=BIG.fromBytes(t);
+		FP2 rx=new FP2(ra,rb);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=b[i+2*BIG.MODBYTES];
+		ra=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=b[i+3*BIG.MODBYTES];
+		rb=BIG.fromBytes(t);
+		FP2 ry=new FP2(ra,rb);
+
+		return new ECP2(rx,ry);
+	}
+/* convert this to hex string */
+	public String toString() {
+		ECP2 W=new ECP2(this);	
+		W.affine();
+		if (W.is_infinity()) return "infinity";
+		return "("+W.x.toString()+","+W.y.toString()+")";
+	}
+
+/* Calculate RHS of twisted curve equation x^3+B/i */
+	public static FP2 RHS(FP2 x) {
+		x.norm();
+		FP2 r=new FP2(x);
+		r.sqr();
+		FP2 b=new FP2(new BIG(ROM.CURVE_B));
+
+		if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+		{
+			b.div_ip();
+		}
+		if (ECP.SEXTIC_TWIST==ECP.M_TYPE)
+		{
+			b.norm();
+			b.mul_ip();
+			b.norm();
+		}
+
+
+		r.mul(x);
+		r.add(b);
+
+		r.reduce();
+		return r;
+	}
+
+/* construct this from (x,y) - but set to O if not on curve */
+	public ECP2(FP2 ix,FP2 iy) {
+		x=new FP2(ix);
+		y=new FP2(iy);
+		z=new FP2(1);
+		FP2 rhs=RHS(x);
+		FP2 y2=new FP2(y);
+		y2.sqr();
+		if (!y2.equals(rhs)) inf();
+//		if (y2.equals(rhs)) INF=false;
+//		else {x.zero();INF=true;}
+	}
+
+/* construct this from x - but set to O if not on curve */
+	public ECP2(FP2 ix) {
+		x=new FP2(ix);
+		y=new FP2(1);
+		z=new FP2(1);
+		FP2 rhs=RHS(x);
+		if (rhs.sqrt()) 
+		{
+			y.copy(rhs);
+			//INF=false;
+		}
+		else {/*x.zero();INF=true;*/ inf();}
+	}
+
+/* this+=this */
+	public int dbl() {
+//		if (INF) return -1;      
+//System.out.println("Into dbl");
+		FP2 iy=new FP2(y);
+		if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+		{
+			iy.mul_ip(); iy.norm();
+		}
+		FP2 t0=new FP2(y);                  //***** Change 
+		t0.sqr();            
+		if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+		{		
+			t0.mul_ip();
+		}
+		FP2 t1=new FP2(iy);  
+		t1.mul(z);
+		FP2 t2=new FP2(z);
+		t2.sqr();
+
+		z.copy(t0);
+		z.add(t0); z.norm(); 
+		z.add(z); 
+		z.add(z); 
+		z.norm();  
+
+		t2.imul(3*ROM.CURVE_B_I); 
+		if (ECP.SEXTIC_TWIST==ECP.M_TYPE)
+		{
+			t2.mul_ip();
+			t2.norm();
+		}
+
+		FP2 x3=new FP2(t2);
+		x3.mul(z); 
+
+		FP2 y3=new FP2(t0);   
+
+		y3.add(t2); y3.norm();
+		z.mul(t1);
+		t1.copy(t2); t1.add(t2); t2.add(t1); t2.norm();  
+		t0.sub(t2); t0.norm();                           //y^2-9bz^2
+		y3.mul(t0); y3.add(x3);                          //(y^2+3z*2)(y^2-9z^2)+3b.z^2.8y^2
+		t1.copy(x); t1.mul(iy);						//
+		x.copy(t0); x.norm(); x.mul(t1); x.add(x);       //(y^2-9bz^2)xy2
+
+		x.norm(); 
+		y.copy(y3); y.norm();
+//System.out.println("Out of dbl");
+		return 1;
+	}
+
+/* this+=Q - return 0 for add, 1 for double, -1 for O */
+	public int add(ECP2 Q) {
+//		if (INF)
+//		{
+//			copy(Q);
+//			return -1;
+//		}
+//		if (Q.INF) return -1;
+//System.out.println("Into add");
+		int b=3*ROM.CURVE_B_I;
+		FP2 t0=new FP2(x);
+		t0.mul(Q.x);         // x.Q.x
+		FP2 t1=new FP2(y);
+		t1.mul(Q.y);		 // y.Q.y
+
+		FP2 t2=new FP2(z);
+		t2.mul(Q.z);
+		FP2 t3=new FP2(x);
+		t3.add(y); t3.norm();          //t3=X1+Y1
+		FP2 t4=new FP2(Q.x);            
+		t4.add(Q.y); t4.norm();			//t4=X2+Y2
+		t3.mul(t4);						//t3=(X1+Y1)(X2+Y2)
+		t4.copy(t0); t4.add(t1);		//t4=X1.X2+Y1.Y2
+
+		t3.sub(t4); t3.norm(); 
+		if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+		{		
+			t3.mul_ip();  t3.norm();         //t3=(X1+Y1)(X2+Y2)-(X1.X2+Y1.Y2) = X1.Y2+X2.Y1
+		}
+		t4.copy(y);                    
+		t4.add(z); t4.norm();			//t4=Y1+Z1
+		FP2 x3=new FP2(Q.y);
+		x3.add(Q.z); x3.norm();			//x3=Y2+Z2
+
+		t4.mul(x3);						//t4=(Y1+Z1)(Y2+Z2)
+		x3.copy(t1);					//
+		x3.add(t2);						//X3=Y1.Y2+Z1.Z2
+	
+		t4.sub(x3); t4.norm(); 
+		if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+		{	
+			t4.mul_ip(); t4.norm();          //t4=(Y1+Z1)(Y2+Z2) - (Y1.Y2+Z1.Z2) = Y1.Z2+Y2.Z1
+		}
+		x3.copy(x); x3.add(z); x3.norm();	// x3=X1+Z1
+		FP2 y3=new FP2(Q.x);				
+		y3.add(Q.z); y3.norm();				// y3=X2+Z2
+		x3.mul(y3);							// x3=(X1+Z1)(X2+Z2)
+		y3.copy(t0);
+		y3.add(t2);							// y3=X1.X2+Z1+Z2
+		y3.rsub(x3); y3.norm();				// y3=(X1+Z1)(X2+Z2) - (X1.X2+Z1.Z2) = X1.Z2+X2.Z1
+
+		if (ECP.SEXTIC_TWIST==ECP.D_TYPE)
+		{
+			t0.mul_ip(); t0.norm(); // x.Q.x
+			t1.mul_ip(); t1.norm(); // y.Q.y
+		}
+		x3.copy(t0); x3.add(t0); 
+		t0.add(x3); t0.norm();
+		t2.imul(b); 	
+		if (ECP.SEXTIC_TWIST==ECP.M_TYPE)
+		{
+			t2.mul_ip(); t2.norm();
+		}
+		FP2 z3=new FP2(t1); z3.add(t2); z3.norm();
+		t1.sub(t2); t1.norm(); 
+		y3.imul(b); 
+		if (ECP.SEXTIC_TWIST==ECP.M_TYPE)
+		{
+			y3.mul_ip(); 
+			y3.norm();
+		}
+		x3.copy(y3); x3.mul(t4); t2.copy(t3); t2.mul(t1); x3.rsub(t2);
+		y3.mul(t0); t1.mul(z3); y3.add(t1);
+		t0.mul(t3); z3.mul(t4); z3.add(t0);
+
+		x.copy(x3); x.norm(); 
+		y.copy(y3); y.norm();
+		z.copy(z3); z.norm();
+//System.out.println("Out of add");
+		return 0;
+	}
+
+/* set this-=Q */
+	public int sub(ECP2 Q) {
+		ECP2 NQ=new ECP2(Q);
+		NQ.neg();
+		int D=add(NQ);
+		//Q.neg();
+		//int D=add(Q);
+		//Q.neg();
+		return D;
+	}
+/* set this*=q, where q is Modulus, using Frobenius */
+	public void frob(FP2 X)
+	{
+//		if (INF) return;
+		FP2 X2=new FP2(X);
+
+		X2.sqr();
+		x.conj();
+		y.conj();
+		z.conj();
+		z.reduce();
+		x.mul(X2);
+
+		y.mul(X2);
+		y.mul(X);
+	}
+
+/* P*=e */
+	public ECP2 mul(BIG e)
+	{
+/* fixed size windows */
+		int i,b,nb,m,s,ns;
+		BIG mt=new BIG();
+		BIG t=new BIG();
+		ECP2 P=new ECP2();
+		ECP2 Q=new ECP2();
+		ECP2 C=new ECP2();
+		ECP2[] W=new ECP2[8];
+		byte[] w=new byte[1+(BIG.NLEN*BIG.BASEBITS+3)/4];
+
+		if (is_infinity()) return new ECP2();
+
+		//affine();
+
+/* precompute table */
+		Q.copy(this);
+		Q.dbl();
+		W[0]=new ECP2();
+		W[0].copy(this);
+
+		for (i=1;i<8;i++)
+		{
+			W[i]=new ECP2();
+			W[i].copy(W[i-1]);
+			W[i].add(Q);
+		}
+
+/* make exponent odd - add 2P if even, P if odd */
+		t.copy(e);
+		s=t.parity();
+		t.inc(1); t.norm(); ns=t.parity(); mt.copy(t); mt.inc(1); mt.norm();
+		t.cmove(mt,s);
+		Q.cmove(this,ns);
+		C.copy(Q);
+
+		nb=1+(t.nbits()+3)/4;
+/* convert exponent to signed 4-bit window */
+		for (i=0;i<nb;i++)
+		{
+			w[i]=(byte)(t.lastbits(5)-16);
+			t.dec(w[i]); t.norm();
+			t.fshr(4);	
+		}
+		w[nb]=(byte)t.lastbits(5);
+	
+		P.copy(W[(w[nb]-1)/2]);  
+		for (i=nb-1;i>=0;i--)
+		{
+			Q.select(W,w[i]);
+			P.dbl();
+			P.dbl();
+			P.dbl();
+			P.dbl();
+			P.add(Q);
+		}
+		P.sub(C);
+		P.affine();
+		return P;
+	}
+
+/* P=u0.Q0+u1*Q1+u2*Q2+u3*Q3 */
+// Bos & Costello https://eprint.iacr.org/2013/458.pdf
+// Faz-Hernandez & Longa & Sanchez  https://eprint.iacr.org/2013/158.pdf
+// Side channel attack secure 
+
+	public static ECP2 mul4(ECP2[] Q,BIG[] u)
+	{
+		int i,j,nb,pb;
+		ECP2 W=new ECP2();
+		ECP2 P=new ECP2();
+		ECP2[] T=new ECP2[8];
+
+		BIG mt=new BIG();
+		BIG[] t=new BIG[4];
+
+		byte[] w=new byte[BIG.NLEN*BIG.BASEBITS+1];
+		byte[] s=new byte[BIG.NLEN*BIG.BASEBITS+1];
+
+		for (i=0;i<4;i++)
+		{
+			t[i]=new BIG(u[i]);
+			t[i].norm();
+			//Q[i].affine();
+		}
+
+        T[0] = new ECP2(); T[0].copy(Q[0]);  // Q[0]
+        T[1] = new ECP2(); T[1].copy(T[0]); T[1].add(Q[1]);  // Q[0]+Q[1]
+        T[2] = new ECP2(); T[2].copy(T[0]); T[2].add(Q[2]);  // Q[0]+Q[2]
+        T[3] = new ECP2(); T[3].copy(T[1]); T[3].add(Q[2]);  // Q[0]+Q[1]+Q[2]
+        T[4] = new ECP2(); T[4].copy(T[0]); T[4].add(Q[3]);  // Q[0]+Q[3]
+        T[5] = new ECP2(); T[5].copy(T[1]); T[5].add(Q[3]);  // Q[0]+Q[1]+Q[3]
+        T[6] = new ECP2(); T[6].copy(T[2]); T[6].add(Q[3]);  // Q[0]+Q[2]+Q[3]
+        T[7] = new ECP2(); T[7].copy(T[3]); T[7].add(Q[3]);  // Q[0]+Q[1]+Q[2]+Q[3]
+
+    // Make it odd
+        pb=1-t[0].parity();
+        t[0].inc(pb);
+        t[0].norm();
+
+    // Number of bits
+        mt.zero();
+        for (i=0;i<4;i++) {
+            mt.or(t[i]); 
+        }
+        nb=1+mt.nbits();
+
+    // Sign pivot 
+        s[nb-1]=1;
+        for (i=0;i<nb-1;i++) {
+            t[0].fshr(1);
+            s[i]=(byte)(2*t[0].parity()-1);
+        }
+
+    // Recoded exponent
+        for (i=0; i<nb; i++) {
+            w[i]=0;
+            int k=1;
+            for (j=1; j<4; j++) {
+                byte bt=(byte)(s[i]*t[j].parity());
+                t[j].fshr(1);
+                t[j].dec((int)(bt)>>1);
+                t[j].norm();
+                w[i]+=bt*(byte)k;
+                k*=2;
+            }
+        } 
+
+    // Main loop
+        P.select(T,(int)(2*w[nb-1]+1));  
+        for (i=nb-2;i>=0;i--) {
+            P.dbl();
+            W.select(T,(int)(2*w[i]+s[i]));
+            P.add(W);
+        }
+
+    // apply correction
+        W.copy(P);   
+        W.sub(Q[0]);
+        P.cmove(W,pb);   
+		P.affine();
+		return P;
+	}        
+
+
+/* P=u0.Q0+u1*Q1+u2*Q2+u3*Q3 */
+/*
+	public static ECP2 mul4(ECP2[] Q,BIG[] u)
+	{
+		int i,j,nb;
+		int[] a=new int[4];
+		ECP2 T=new ECP2();
+		ECP2 C=new ECP2();
+		ECP2 P=new ECP2();
+		ECP2[] W=new ECP2[8];
+
+		BIG mt=new BIG();
+		BIG[] t=new BIG[4];
+
+		byte[] w=new byte[BIG.NLEN*BIG.BASEBITS+1];
+
+		for (i=0;i<4;i++)
+		{
+			t[i]=new BIG(u[i]);
+			Q[i].affine();
+		}
+
+// precompute table 
+
+		W[0]=new ECP2(); W[0].copy(Q[0]); W[0].sub(Q[1]);
+
+		W[1]=new ECP2(); W[1].copy(W[0]);
+		W[2]=new ECP2(); W[2].copy(W[0]);
+		W[3]=new ECP2(); W[3].copy(W[0]);
+		W[4]=new ECP2(); W[4].copy(Q[0]); W[4].add(Q[1]);
+		W[5]=new ECP2(); W[5].copy(W[4]);
+		W[6]=new ECP2(); W[6].copy(W[4]);
+		W[7]=new ECP2(); W[7].copy(W[4]);
+		T.copy(Q[2]); T.sub(Q[3]);
+		W[1].sub(T);
+		W[2].add(T);
+		W[5].sub(T);
+		W[6].add(T);
+		T.copy(Q[2]); T.add(Q[3]);
+		W[0].sub(T);
+		W[3].add(T);
+		W[4].sub(T);
+		W[7].add(T);
+
+// if multiplier is even add 1 to multiplier, and add P to correction 
+		mt.zero(); C.inf();
+		for (i=0;i<4;i++)
+		{
+			if (t[i].parity()==0)
+			{
+				t[i].inc(1); t[i].norm();
+				C.add(Q[i]);
+			}
+			mt.add(t[i]); mt.norm();
+		}
+
+		nb=1+mt.nbits();
+
+// convert exponent to signed 1-bit window 
+		for (j=0;j<nb;j++)
+		{
+			for (i=0;i<4;i++)
+			{
+				a[i]=(byte)(t[i].lastbits(2)-2);
+				t[i].dec(a[i]); t[i].norm(); 
+				t[i].fshr(1);
+			}
+			w[j]=(byte)(8*a[0]+4*a[1]+2*a[2]+a[3]);
+		}
+		w[nb]=(byte)(8*t[0].lastbits(2)+4*t[1].lastbits(2)+2*t[2].lastbits(2)+t[3].lastbits(2));
+
+		P.copy(W[(w[nb]-1)/2]);  
+		for (i=nb-1;i>=0;i--)
+		{
+			T.select(W,w[i]);
+			P.dbl();
+			P.add(T);
+		}
+		P.sub(C); // apply correction 
+
+		P.affine();
+		return P;
+	}
+*/
+
+/* needed for SOK */
+	public static ECP2 mapit(byte[] h)
+	{
+		BIG q=new BIG(ROM.Modulus);
+		BIG x=BIG.fromBytes(h);
+		BIG one=new BIG(1);
+		FP2 X;
+		ECP2 Q;
+		x.mod(q);
+		while (true)
+		{
+			X=new FP2(one,x);
+			Q=new ECP2(X);
+			if (!Q.is_infinity()) break;
+			x.inc(1); x.norm();
+		}
+
+		BIG Fra=new BIG(ROM.Fra);
+		BIG Frb=new BIG(ROM.Frb);
+		X=new FP2(Fra,Frb);
+
+		if (ECP.SEXTIC_TWIST==ECP.M_TYPE)
+		{
+			X.inverse();
+			X.norm();
+		}
+
+		x=new BIG(ROM.CURVE_Bnx);
+
+/* Fast Hashing to G2 - Fuentes-Castaneda, Knapp and Rodriguez-Henriquez */
+
+		if (ECP.CURVE_PAIRING_TYPE==ECP.BN)
+		{
+			ECP2 T,K;
+
+			T=new ECP2(); T.copy(Q);
+			T=T.mul(x); 
+			
+			if (ECP.SIGN_OF_X==ECP.NEGATIVEX)
+			{
+				T.neg();
+			}	
+			K=new ECP2(); K.copy(T);
+			K.dbl(); K.add(T); //K.affine();
+
+			K.frob(X);
+			Q.frob(X); Q.frob(X); Q.frob(X);
+			Q.add(T); Q.add(K);
+			T.frob(X); T.frob(X);
+			Q.add(T);
+
+		}
+
+/* Efficient hash maps to G2 on BLS curves - Budroni, Pintore */
+/* Q -> x2Q -xQ -Q +F(xQ -Q) +F(F(2Q)) */
+
+		if (ECP.CURVE_PAIRING_TYPE==ECP.BLS)
+		{
+		//	ECP2 xQ,x2Q;
+		//	xQ=new ECP2();
+		//	x2Q=new ECP2();
+
+			ECP2 xQ=Q.mul(x);
+			ECP2 x2Q=xQ.mul(x);
+
+			if (ECP.SIGN_OF_X==ECP.NEGATIVEX)
+			{
+				xQ.neg();
+			}	
+
+			x2Q.sub(xQ);
+			x2Q.sub(Q);
+
+			xQ.sub(Q);
+			xQ.frob(X);
+
+			Q.dbl();
+			Q.frob(X);
+			Q.frob(X);
+
+			Q.add(x2Q);
+			Q.add(xQ);
+		}
+		Q.affine();
+		return Q;
+	}
+
+	public static ECP2 generator()
+	{
+		return new ECP2(new FP2(new BIG(ROM.CURVE_Pxa),new BIG(ROM.CURVE_Pxb)),new FP2(new BIG(ROM.CURVE_Pya),new BIG(ROM.CURVE_Pyb)));
+	}
+
+/*
+	public static void main(String[] args) {
+		BIG r=new BIG(ROM.Modulus);
+
+		BIG Pxa=new BIG(ROM.CURVE_Pxa);
+		BIG Pxb=new BIG(ROM.CURVE_Pxb);
+		BIG Pya=new BIG(ROM.CURVE_Pya);
+		BIG Pyb=new BIG(ROM.CURVE_Pyb);
+
+		BIG Fra=new BIG(ROM.CURVE_Fra);
+		BIG Frb=new BIG(ROM.CURVE_Frb);
+
+		FP2 f=new FP2(Fra,Frb);
+
+		FP2 Px=new FP2(Pxa,Pxb);
+		FP2 Py=new FP2(Pya,Pyb);
+
+		ECP2 P=new ECP2(Px,Py);
+
+		System.out.println("P= "+P.toString());
+
+		P=P.mul(r);
+		System.out.println("P= "+P.toString());
+
+		ECP2 Q=new ECP2(Px,Py);
+		Q.frob(f);
+		System.out.println("Q= "+Q.toString());
+	} */
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/milagro/amcl/BLS381/FP.java b/src/main/java/org/apache/milagro/amcl/BLS381/FP.java
new file mode 100644
index 0000000..f3ae560
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS381/FP.java
@@ -0,0 +1,526 @@
+/*
+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.
+*/
+
+/* Finite Field arithmetic */
+/* AMCL mod p functions */
+
+package org.apache.milagro.amcl.BLS381;
+
+public final class FP {
+
+	public static final int NOT_SPECIAL=0;
+	public static final int PSEUDO_MERSENNE=1;
+	public static final int MONTGOMERY_FRIENDLY=2;
+	public static final int GENERALISED_MERSENNE=3;
+
+	public static final int MODBITS=381; /* Number of bits in Modulus */
+	public static final int MOD8=3;  /* Modulus mod 8 */
+	public static final int MODTYPE=NOT_SPECIAL;
+
+	public static final int FEXCESS =((int)1<<25);  // BASEBITS*NLEN-MODBITS or 2^30 max!
+	public static final long OMASK=(long)(-1)<<(MODBITS%BIG.BASEBITS);
+	public static final int TBITS=MODBITS%BIG.BASEBITS; // Number of active bits in top word 
+	public static final long TMASK=((long)1<<TBITS)-1;
+
+
+	public final BIG x;
+	//public BIG p=new BIG(ROM.Modulus);
+	//public BIG r2modp=new BIG(ROM.R2modp);
+	public int XES;
+
+/**************** 64-bit specific ************************/
+
+/* reduce a DBIG to a BIG using the appropriate form of the modulus */
+	public static BIG mod(DBIG d)
+	{
+		if (MODTYPE==PSEUDO_MERSENNE)
+		{
+			BIG b;		
+			long v,tw;
+			BIG t=d.split(MODBITS);
+			b=new BIG(d);
+
+			v=t.pmul((int)ROM.MConst);
+
+			t.add(b);
+			t.norm();
+
+			tw=t.w[BIG.NLEN-1];
+			t.w[BIG.NLEN-1]&=FP.TMASK;
+			t.w[0]+=(ROM.MConst*((tw>>TBITS)+(v<<(BIG.BASEBITS-TBITS))));
+
+			t.norm();
+			return t;			
+		}
+		if (FP.MODTYPE==MONTGOMERY_FRIENDLY)
+		{
+			BIG b;		
+			long[] cr=new long[2];
+			for (int i=0;i<BIG.NLEN;i++)
+			{
+				cr=BIG.muladd(d.w[i],ROM.MConst-1,d.w[i],d.w[BIG.NLEN+i-1]);
+				d.w[BIG.NLEN+i]+=cr[0];
+				d.w[BIG.NLEN+i-1]=cr[1];
+			}
+			
+			b=new BIG(0);
+			for (int i=0;i<BIG.NLEN;i++ )
+				b.w[i]=d.w[BIG.NLEN+i];
+			b.norm();
+			return b;		
+		}
+		if (MODTYPE==GENERALISED_MERSENNE)
+		{ // GoldiLocks Only
+			BIG b;		
+			BIG t=d.split(MODBITS);
+			b=new BIG(d);
+			b.add(t);
+			DBIG dd=new DBIG(t);
+			dd.shl(MODBITS/2);
+
+			BIG tt=dd.split(MODBITS);
+			BIG lo=new BIG(dd);
+			b.add(tt);
+			b.add(lo);
+			b.norm();
+			tt.shl(MODBITS/2);
+			b.add(tt);
+
+			long carry=b.w[BIG.NLEN-1]>>TBITS;
+			b.w[BIG.NLEN-1]&=FP.TMASK;
+			b.w[0]+=carry;
+			
+			b.w[224/BIG.BASEBITS]+=carry<<(224%BIG.BASEBITS);
+			b.norm();
+			return b;		
+		}
+		if (MODTYPE==NOT_SPECIAL)
+		{
+			return BIG.monty(new BIG(ROM.Modulus),ROM.MConst,d);
+		}
+
+		return new BIG(0);
+	}
+
+
+
+/*********************************************************/
+
+
+/* Constructors */
+	public FP(int a)
+	{
+		x=new BIG(a);
+		nres();
+	}
+
+	public FP()
+	{
+		x=new BIG(0);
+		XES=1;
+	}
+
+	public FP(BIG a)
+	{
+		x=new BIG(a);
+		nres();
+	}
+	
+	public FP(FP a)
+	{
+		x=new BIG(a.x);
+		XES=a.XES;
+	}
+
+/* convert to string */
+	public String toString() 
+	{
+		String s=redc().toString();
+		return s;
+	}
+
+	public String toRawString() 
+	{
+		String s=x.toRawString();
+		return s;
+	}
+
+/* convert to Montgomery n-residue form */
+	public void nres()
+	{
+		if (MODTYPE!=PSEUDO_MERSENNE && MODTYPE!=GENERALISED_MERSENNE)
+		{
+			DBIG d=BIG.mul(x,new BIG(ROM.R2modp));  /*** Change ***/
+			x.copy(mod(d));
+			XES=2;
+		}
+		else XES=1;
+	}
+
+/* convert back to regular form */
+	public BIG redc()
+	{
+		if (MODTYPE!=PSEUDO_MERSENNE && MODTYPE!=GENERALISED_MERSENNE)
+		{
+			DBIG d=new DBIG(x);
+			return mod(d);
+		}
+		else 
+		{
+			BIG r=new BIG(x);
+			return r;
+		}
+	}
+
+/* test this=0? */
+	public boolean iszilch() {
+		FP z=new FP(this);
+		z.reduce();
+		return z.x.iszilch();
+
+	}
+
+/* copy from FP b */
+	public void copy(FP b)
+	{
+		x.copy(b.x);
+		XES=b.XES;
+	}
+
+/* set this=0 */
+	public void zero()
+	{
+		x.zero();
+		XES=1;
+	}
+	
+/* set this=1 */
+	public void one()
+	{
+		x.one(); nres();
+	}
+
+/* normalise this */
+	public void norm()
+	{
+		x.norm();
+	}
+
+/* swap FPs depending on d */
+	public void cswap(FP b,int d)
+	{
+		x.cswap(b.x,d);
+		int t,c=d;
+		c=~(c-1);
+		t=c&(XES^b.XES);
+		XES^=t;
+		b.XES^=t;
+	}
+
+/* copy FPs depending on d */
+	public void cmove(FP b,int d)
+	{
+		x.cmove(b.x,d);
+		XES^=(XES^b.XES)&(-d);
+
+	}
+
+/* this*=b mod Modulus */
+	public void mul(FP b)
+	{
+		if ((long)XES*b.XES>(long)FEXCESS) reduce();
+
+		DBIG d=BIG.mul(x,b.x);
+		x.copy(mod(d));
+		XES=2;
+	}
+
+/* this*=c mod Modulus, where c is a small int */
+	public void imul(int c)
+	{
+//		norm();
+		boolean s=false;
+		if (c<0)
+		{
+			c=-c;
+			s=true;
+		}
+
+		if (MODTYPE==PSEUDO_MERSENNE || MODTYPE==GENERALISED_MERSENNE)
+		{
+			DBIG d=x.pxmul(c);
+			x.copy(mod(d));
+			XES=2;
+		}
+		else
+		{
+			if (XES*c<=FEXCESS)
+			{
+				x.pmul(c);
+				XES*=c;
+			}
+			else
+			{  // this is not good
+				FP n=new FP(c);
+				mul(n);
+			}
+		}
+		
+/*
+		if (c<=BIG.NEXCESS && XES*c<=FEXCESS)
+		{
+			x.imul(c);
+			XES*=c;
+			x.norm();
+		}
+		else
+		{
+			DBIG d=x.pxmul(c);
+			x.copy(mod(d));
+			XES=2;
+		}
+*/
+		if (s) {neg(); norm();}
+
+	}
+
+/* this*=this mod Modulus */
+	public void sqr()
+	{
+		DBIG d;
+		if ((long)XES*XES>(long)FEXCESS) reduce();
+
+		d=BIG.sqr(x);	
+		x.copy(mod(d));
+		XES=2;
+	}
+
+/* this+=b */
+	public void add(FP b) {
+		x.add(b.x);
+		XES+=b.XES;
+		if (XES>FEXCESS) reduce();
+	}
+
+// https://graphics.stanford.edu/~seander/bithacks.html
+// constant time log to base 2 (or number of bits in)
+
+	private static int logb2(int v)
+	{
+		int r;
+		v |= v >>> 1;
+		v |= v >>> 2;
+		v |= v >>> 4;
+		v |= v >>> 8;
+		v |= v >>> 16;
+
+		v = v - ((v >>> 1) & 0x55555555);                  
+		v = (v & 0x33333333) + ((v >>> 2) & 0x33333333);  
+		r = ((v + (v >>> 4) & 0xF0F0F0F) * 0x1010101) >>> 24; 
+		return r;
+	}
+
+/* this = -this mod Modulus */
+	public void neg()
+	{
+		int sb;
+		BIG m=new BIG(ROM.Modulus);
+
+		sb=logb2(XES-1);
+		m.fshl(sb);
+		x.rsub(m);		
+
+		XES=(1<<sb);
+		if (XES>FEXCESS) reduce();
+	}
+
+/* this-=b */
+	public void sub(FP b)
+	{
+		FP n=new FP(b);
+		n.neg();
+		this.add(n);
+	}
+
+	public void rsub(FP b)
+	{
+		FP n=new FP(this);
+		n.neg();
+		this.copy(b);
+		this.add(n);
+	}
+
+/* this/=2 mod Modulus */
+	public void div2()
+	{
+		if (x.parity()==0)
+			x.fshr(1);
+		else
+		{
+			x.add(new BIG(ROM.Modulus));
+			x.norm();
+			x.fshr(1);
+		}
+	}
+
+/* this=1/this mod Modulus */
+	public void inverse()
+	{
+/*
+		BIG r=redc();
+		r.invmodp(p);
+		x.copy(r);
+		nres();
+*/
+		BIG m2=new BIG(ROM.Modulus);
+		m2.dec(2); m2.norm();
+		copy(pow(m2));
+
+	}
+
+/* return TRUE if this==a */
+	public boolean equals(FP a)
+	{
+		FP f=new FP(this);
+		FP s=new FP(a);
+		f.reduce();
+		s.reduce();
+		if (BIG.comp(f.x,s.x)==0) return true;
+		return false;
+	}
+
+/* reduce this mod Modulus */
+	public void reduce()
+	{
+		x.mod(new BIG(ROM.Modulus));
+		XES=1;
+	}
+
+	public FP pow(BIG e)
+	{
+		byte[] w=new byte[1+(BIG.NLEN*BIG.BASEBITS+3)/4];
+		FP [] tb=new FP[16];
+		BIG t=new BIG(e);
+		t.norm();
+		int nb=1+(t.nbits()+3)/4;
+
+		for (int i=0;i<nb;i++)
+		{
+			int lsbs=t.lastbits(4);
+			t.dec(lsbs);
+			t.norm();
+			w[i]=(byte)lsbs;
+			t.fshr(4);
+		}
+		tb[0]=new FP(1);
+		tb[1]=new FP(this);
+		for (int i=2;i<16;i++)
+		{
+			tb[i]=new FP(tb[i-1]);
+			tb[i].mul(this);
+		}
+		FP r=new FP(tb[w[nb-1]]);
+		for (int i=nb-2;i>=0;i--)
+		{
+			r.sqr();
+			r.sqr();
+			r.sqr();
+			r.sqr();
+			r.mul(tb[w[i]]);
+		}
+		r.reduce();
+		return r;
+	}
+
+/* return this^e mod Modulus 
+	public FP pow(BIG e)
+	{
+		int bt;
+		FP r=new FP(1);
+		e.norm();
+		x.norm();
+		FP m=new FP(this);
+		while (true)
+		{
+			bt=e.parity();
+			e.fshr(1);
+			if (bt==1) r.mul(m);
+			if (e.iszilch()) break;
+			m.sqr();
+		}
+		r.x.mod(p);
+		return r;
+	} */
+
+/* return sqrt(this) mod Modulus */
+	public FP sqrt()
+	{
+		reduce();
+		BIG b=new BIG(ROM.Modulus);
+		if (MOD8==5)
+		{
+			b.dec(5); b.norm(); b.shr(3);
+			FP i=new FP(this); i.x.shl(1);
+			FP v=i.pow(b);
+			i.mul(v); i.mul(v);
+			i.x.dec(1);
+			FP r=new FP(this);
+			r.mul(v); r.mul(i); 
+			r.reduce();
+			return r;
+		}
+		else
+		{
+			b.inc(1); b.norm(); b.shr(2);
+			return pow(b);
+		}
+	}
+
+/* return jacobi symbol (this/Modulus) */
+	public int jacobi()
+	{
+		BIG w=redc();
+		return w.jacobi(new BIG(ROM.Modulus));
+	}
+/*
+	public static void main(String[] args) {
+		BIG m=new BIG(ROM.Modulus);
+		BIG x=new BIG(3);
+		BIG e=new BIG(m);
+		e.dec(1);
+
+		System.out.println("m= "+m.nbits());	
+
+
+		BIG r=x.powmod(e,m);
+
+		System.out.println("m= "+m.toString());	
+		System.out.println("r= "+r.toString());	
+
+		BIG.cswap(m,r,0);
+
+		System.out.println("m= "+m.toString());	
+		System.out.println("r= "+r.toString());	
+
+//		FP y=new FP(3);
+//		FP s=y.pow(e);
+//		System.out.println("s= "+s.toString());	
+
+	} */
+}
diff --git a/src/main/java/org/apache/milagro/amcl/BLS381/FP12.java b/src/main/java/org/apache/milagro/amcl/BLS381/FP12.java
new file mode 100644
index 0000000..f1ffef9
--- /dev/null
+++ b/src/main/java/org/apache/milagro/amcl/BLS381/FP12.java
@@ -0,0 +1,907 @@
+/*
+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.
+*/
+
+/* AMCL Fp^12 functions */
+/* FP12 elements are of the form a+i.b+i^2.c */
+
+package org.apache.milagro.amcl.BLS381;
+
+public final class FP12 {
+	private final FP4 a;
+	private final FP4 b;
+	private final FP4 c;
+/* reduce all components of this mod Modulus */
+	public void reduce()
+	{
+		a.reduce();
+		b.reduce();
+		c.reduce();
+	}
+/* normalise all components of this */
+	public void norm()
+	{
+		a.norm();
+		b.norm();
+		c.norm();
+	}
+/* test x==0 ? */
+	public boolean iszilch() {
+		//reduce();
+		return (a.iszilch() && b.iszilch() && c.iszilch());
+	}
+
+	public void cmove(FP12 g,int d)
+	{
+		a.cmove(g.a,d);
+		b.cmove(g.b,d);
+		c.cmove(g.c,d);		
+	}
+
+
+/* return 1 if b==c, no branching */
+	public static int teq(int b,int c)
+	{
+		int x=b^c;
+		x-=1;  // if x=0, x now -1
+		return ((x>>31)&1);
+	}
+
+/* Constant time select from pre-computed table */
+	public void select(FP12 g[],int b)
+	{
+		int m=b>>31;
+		int babs=(b^m)-m;
+
+		babs=(babs-1)/2;
+
+		cmove(g[0],teq(babs,0));  // conditional move
+		cmove(g[1],teq(babs,1));
+		cmove(g[2],teq(babs,2));
+		cmove(g[3],teq(babs,3));
+		cmove(g[4],teq(babs,4));
+		cmove(g[5],teq(babs,5));
+		cmove(g[6],teq(babs,6));
+		cmove(g[7],teq(babs,7));
+ 
+		FP12 invf=new FP12(this); 
+		invf.conj();
+		cmove(invf,(int)(m&1));
+	}
+
+
+/* test x==1 ? */
+	public boolean isunity() {
+		FP4 one=new FP4(1);
+		return (a.equals(one) && b.iszilch() && c.iszilch());
+	}
+/* return 1 if x==y, else 0 */
+	public boolean equals(FP12 x)
+	{
+		return (a.equals(x.a) && b.equals(x.b) && c.equals(x.c));
+	}
+/* extract a from this */
+	public FP4 geta()
+	{
+		return a;
+	}
+/* extract b */
+	public FP4 getb()
+	{
+		return b;
+	}
+/* extract c */
+	public FP4 getc()
+	{
+		return c;
+	}
+/* copy this=x */
+	public void copy(FP12 x)
+	{
+		a.copy(x.a);
+		b.copy(x.b);
+		c.copy(x.c);
+	}
+/* set this=1 */
+	public void one()
+	{
+		a.one();
+		b.zero();
+		c.zero();
+	}
+/* this=conj(this) */
+	public void conj()
+	{
+		a.conj();
+		b.nconj();
+		c.conj();
+	}
+/* Constructors */
+	public FP12(FP4 d)
+	{
+		a=new FP4(d);
+		b=new FP4(0);
+		c=new FP4(0);
+	}
+
+	public FP12(int d)
+	{
+		a=new FP4(d);
+		b=new FP4(0);
+		c=new FP4(0);
+	}
+
+	public FP12(FP4 d,FP4 e,FP4 f)
+	{
+		a=new FP4(d);
+		b=new FP4(e);
+		c=new FP4(f);
+	}
+
+	public FP12(FP12 x)
+	{
+		a=new FP4(x.a);
+		b=new FP4(x.b);
+		c=new FP4(x.c);
+	}
+
+/* Granger-Scott Unitary Squaring */
+	public void usqr()
+	{
+//System.out.println("Into usqr");
+		FP4 A=new FP4(a);
+		FP4 B=new FP4(c);
+		FP4 C=new FP4(b);
+		FP4 D=new FP4(0);
+
+		a.sqr();
+		D.copy(a); D.add(a);
+		a.add(D);
+
+		a.norm();
+		A.nconj();
+
+		A.add(A);
+		a.add(A);
+		B.sqr();
+		B.times_i();
+
+		D.copy(B); D.add(B);
+		B.add(D);
+		B.norm();
+
+		C.sqr();
+		D.copy(C); D.add(C);
+		C.add(D);
+		C.norm();
+
+		b.conj();
+		b.add(b);
+		c.nconj();
+
+		c.add(c);
+		b.add(B);
+		c.add(C);
+//System.out.println("Out of usqr 1");
+		reduce();
+//System.out.println("Out of usqr 2");
+	}
+
+/* Chung-Hasan SQR2 method from http://cacr.uwaterloo.ca/techreports/2006/cacr2006-24.pdf */
+	public void sqr()
+	{
+//System.out.println("Into sqr");
+		FP4 A=new FP4(a);
+		FP4 B=new FP4(b);
+		FP4 C=new FP4(c);
+		FP4 D=new FP4(a);
+
+		A.sqr();
+		B.mul(c);
+		B.add(B);
+	B.norm();
+		C.sqr();
+		D.mul(b);
+		D.add(D);
+
+		c.add(a);
+		c.add(b);
+	c.norm();
+		c.sqr();
+
+		a.copy(A);
+
+		A.add(B);
+		A.norm();
+		A.add(C);
+		A.add(D);
+		A.norm();
+
+		A.neg();
+		B.times_i();
+		C.times_i();
+
+		a.add(B);
+
+		b.copy(C); b.add(D);
+		c.add(A);
+//System.out.println("Out of sqr");
+		norm();
+	}
+
+/* FP12 full multiplication this=this*y */
+	public void mul(FP12 y)
+	{
+//System.out.println("Into mul");
+		FP4 z0=new FP4(a);
+		FP4 z1=new FP4(0);
+		FP4 z2=new FP4(b);
+		FP4 z3=new FP4(0);
+		FP4 t0=new FP4(a);
+		FP4 t1=new FP4(y.a);
+
+		z0.mul(y.a);
+		z2.mul(y.b);
+
+		t0.add(b);
+		t1.add(y.b);
+
+	t0.norm();
+	t1.norm();
+
+		z1.copy(t0); z1.mul(t1);
+		t0.copy(b); t0.add(c);
+
+		t1.copy(y.b); t1.add(y.c);
+
+	t0.norm();
+	t1.norm();
+
+		z3.copy(t0); z3.mul(t1);
+
+		t0.copy(z0); t0.neg();
+		t1.copy(z2); t1.neg();
+
+		z1.add(t0);
+		//z1.norm();
+		b.copy(z1); b.add(t1);
+
+		z3.add(t1);
+		z2.add(t0);
+
+		t0.copy(a); t0.add(c);
+		t1.copy(y.a); t1.add(y.c);
+
+t0.norm();
+t1.norm();
+	
+		t0.mul(t1);
+		z2.add(t0);
+
+		t0.copy(c); t0.mul(y.c);
+		t1.copy(t0); t1.neg();
+
+//		z2.norm();
+//		z3.norm();
+//		b.norm();
+
+		c.copy(z2); c.add(t1);
+		z3.add(t1);
+		t0.times_i();
+		b.add(t0);
+	z3.norm();
+		z3.times_i();
+		a.copy(z0); a.add(z3);
+		norm();
+//System.out.println("Out of mul");
+	}
+
+/* Special case of multiplication arises from special form of ATE pairing line function */
+	public void smul(FP12 y,int type)
+	{
+//System.out.println("Into smul");
+
+		if (type==ECP.D_TYPE)
+		{
+			FP4 z0=new FP4(a);
+			FP4 z2=new FP4(b);
+			FP4 z3=new FP4(b);
+			FP4 t0=new FP4(0);
+			FP4 t1=new FP4(y.a);
+			z0.mul(y.a);
+			z2.pmul(y.b.real());
+			b.add(a);
+			t1.real().add(y.b.real());
+
+			t1.norm();
+			b.norm();
+			b.mul(t1);
+			z3.add(c);
+			z3.norm();
+			z3.pmul(y.b.real());
+
+			t0.copy(z0); t0.neg();
+			t1.copy(z2); t1.neg();
+
+			b.add(t0);
+
+			b.add(t1);
+			z3.add(t1);
+			z2.add(t0);
+
+			t0.copy(a); t0.add(c);
+			t0.norm();
+			z3.norm();
+			t0.mul(y.a);
+			c.copy(z2); c.add(t0);
+
+			z3.times_i();
+			a.copy(z0); a.add(z3);
+		}
+		if (type==ECP.M_TYPE)
+		{
+			FP4 z0=new FP4(a);
+			FP4 z1=new FP4(0);
+			FP4 z2=new FP4(0);
+			FP4 z3=new FP4(0);
+			FP4 t0=new FP4(a);
+			FP4 t1=new FP4(0);
+		
+			z0.mul(y.a);
+			t0.add(b);
+			t0.norm();
+
+			z1.copy(t0); z1.mul(y.a);
+			t0.copy(b); t0.add(c);
+			t0.norm();
+
+			z3.copy(t0); //z3.mul(y.c);
+			z3.pmul(y.c.getb());
+			z3.times_i();
+
+			t0.copy(z0); t0.neg();
+
+			z1.add(t0);
+			b.copy(z1); 
+			z2.copy(t0);
+
+			t0.copy(a); t0.add(c);
+			t1.copy(y.a); t1.add(y.c);
+
+			t0.norm();
+			t1.norm();
+	
+			t0.mul(t1);
+			z2.add(t0);
+
+			t0.copy(c); 
+			
+			t0.pmul(y.c.getb());
+			t0.times_i();
+
+			t1.copy(t0); t1.neg();
+
+			c.copy(z2); c.add(t1);
+			z3.add(t1);
+			t0.times_i();
+			b.add(t0);
+			z3.norm();
+			z3.times_i();
+			a.copy(z0); a.add(z3);
+		}
+		norm();
+//System.out.println("Out of smul");
+	}
+
+/* this=1/this */
+	public void inverse()
+	{
+		FP4 f0=new FP4(a);
+		FP4 f1=new FP4(b);
+		FP4 f2=new FP4(a);
+		FP4 f3=new FP4(0);
+
+		norm();
+		f0.sqr();
+		f1.mul(c);
+		f1.times_i();
+		f0.sub(f1);
+	f0.norm();
+
+		f1.copy(c); f1.sqr();
+		f1.times_i();
+		f2.mul(b);
+		f1.sub(f2);
+	f1.norm();
+
+		f2.copy(b); f2.sqr();
+		f3.copy(a); f3.mul(c);
+		f2.sub(f3);
+	f2.norm();
+
+		f3.copy(b); f3.mul(f2);
+		f3.times_i();
+		a.mul(f0);
+		f3.add(a);
+		c.mul(f1);
+		c.times_i();
+
+		f3.add(c);
+	f3.norm();
+		f3.inverse();
+		a.copy(f0); a.mul(f3);
+		b.copy(f1); b.mul(f3);
+		c.copy(f2); c.mul(f3);
+	}
+
+/* this=this^p using Frobenius */
+	public void frob(FP2 f)
+	{
+		FP2 f2=new FP2(f);
+		FP2 f3=new FP2(f);
+
+		f2.sqr();
+		f3.mul(f2);
+
+		a.frob(f3);
+		b.frob(f3);
+		c.frob(f3);
+
+		b.pmul(f);
+		c.pmul(f2);
+	}
+
+/* trace function */
+	public FP4 trace()
+	{
+		FP4 t=new FP4(0);
+		t.copy(a);
+		t.imul(3);
+		t.reduce();
+		return t;
+	}
+
+/* convert from byte array to FP12 */
+	public static FP12 fromBytes(byte[] w)
+	{
+		BIG a,b;
+		FP2 c,d;
+		FP4 e,f,g;
+		byte[] t=new byte[BIG.MODBYTES];
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		c=new FP2(a,b);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+2*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+3*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		d=new FP2(a,b);
+
+		e=new FP4(c,d);
+
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+4*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+5*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		c=new FP2(a,b);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+6*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+7*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		d=new FP2(a,b);
+
+		f=new FP4(c,d);
+
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+8*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+9*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		c=new FP2(a,b);
+
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+10*BIG.MODBYTES];
+		a=BIG.fromBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) t[i]=w[i+11*BIG.MODBYTES];
+		b=BIG.fromBytes(t);
+		d=new FP2(a,b);
+
+		g=new FP4(c,d);
+
+		return new FP12(e,f,g);
+	}
+
+/* convert this to byte array */
+	public void toBytes(byte[] w)
+	{
+		byte[] t=new byte[BIG.MODBYTES];
+		a.geta().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i]=t[i];
+		a.geta().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+BIG.MODBYTES]=t[i];
+		a.getb().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+2*BIG.MODBYTES]=t[i];
+		a.getb().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+3*BIG.MODBYTES]=t[i];
+
+		b.geta().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+4*BIG.MODBYTES]=t[i];
+		b.geta().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+5*BIG.MODBYTES]=t[i];
+		b.getb().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+6*BIG.MODBYTES]=t[i];
+		b.getb().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+7*BIG.MODBYTES]=t[i];
+
+		c.geta().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+8*BIG.MODBYTES]=t[i];
+		c.geta().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+9*BIG.MODBYTES]=t[i];
+		c.getb().getA().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+10*BIG.MODBYTES]=t[i];
+		c.getb().getB().toBytes(t);
+		for (int i=0;i<BIG.MODBYTES;i++) w[i+11*BIG.MODBYTES]=t[i];
+	}
+
+/* convert to hex string */
+	public String toString() 
+	{
+		return ("["+a.toString()+","+b.toString()+","+c.toString()+"]");
+	}
+
+/* this=this^e */ 
+/* Note this is simple square and multiply, so not side-channel safe */
+	public FP12 pow(BIG e)
+	{
+		norm();
+		e.norm();
+		BIG e3=new BIG(e);
+		e3.pmul(3);
+		e3.norm();
+
+		FP12 w=new FP12(this);
+
+		int nb=e3.nbits();
+		for (int i=nb-2;i>=1;i--)
+		{
+			w.usqr();
+			int bt=e3.bit(i)-e.bit(i);
+			if (bt==1)
+				w.mul(this);
+			if (bt==-1)
+			{
+				conj(); w.mul(this); conj();
+			}
+		}
+		w.reduce();
+		return w;
+
+
+/*
+		BIG z=new BIG(e);
+		FP12 r=new FP12(1);
+
+		while (true)
+		{
+			int bt=z.parity();
+			z.fshr(1);
+			if (bt==1) r.mul(w);
+			if (z.iszilch()) break;
+			w.usqr();
+		}