/*
 * Code taken from https://github.com/InstantWebP2P/tweetnacl-java under MIT license
 */
// Copyright (c) 2014 Tom Zhou<iwebpp@gmail.com>


package com.iwebpp.crypto;

import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.concurrent.atomic.AtomicLong;


/*
 * @description
 *   TweetNacl.c Java porting
 * */
public final class TweetNaclFast {

    private final static String TAG = "TweetNaclFast";

    /*
     * @description
     *   Box algorithm, Public-key authenticated encryption
     * */
    public static final class Box {

        private final static String TAG = "Box";

        private AtomicLong nonce;

        private byte [] theirPublicKey;
        private byte [] mySecretKey;
        private byte [] sharedKey;

        public Box(byte [] theirPublicKey, byte [] mySecretKey) {
            this(theirPublicKey, mySecretKey, 68);
        }

        public Box(byte [] theirPublicKey, byte [] mySecretKey, long nonce) {
            this.theirPublicKey = theirPublicKey;
            this.mySecretKey = mySecretKey;

            this.nonce = new AtomicLong(nonce);

            // generate pre-computed shared key
            before();
        }

        public void setNonce(long nonce) {
            this.nonce.set(nonce);
        }
        public long getNonce() {
            return this.nonce.get();
        }
        public long incrNonce() {
            return this.nonce.incrementAndGet();
        }
        private byte[] generateNonce() {
            // generate nonce
            long nonce = this.nonce.get();

            byte [] n = new byte[nonceLength];
            for (int i = 0; i < nonceLength; i += 8) {
                n[i+0] = (byte) (nonce>>> 0);
                n[i+1] = (byte) (nonce>>> 8);
                n[i+2] = (byte) (nonce>>>16);
                n[i+3] = (byte) (nonce>>>24);
                n[i+4] = (byte) (nonce>>>32);
                n[i+5] = (byte) (nonce>>>40);
                n[i+6] = (byte) (nonce>>>48);
                n[i+7] = (byte) (nonce>>>56);
            }

            return n;
        }

        /*
         * @description
         *   Encrypt and authenticates message using peer's public key,
         *   our secret key, and the given nonce, which must be unique
         *   for each distinct message for a key pair.
         *
         *   Returns an encrypted and authenticated message,
         *   which is nacl.box.overheadLength longer than the original message.
         * */
        public byte [] box(byte [] message) {
            if (message==null) return null;
            return box(message, 0, message.length);
        }

        public byte [] box(byte [] message, final int moff) {
            if (!(message!=null && message.length>moff)) return null;
            return box(message, moff, message.length-moff);
        }

        public byte [] box(byte [] message, final int moff, final int mlen) {
            if (!(message!=null && message.length>=(moff+mlen))) return null;

            // prepare shared key
            if (this.sharedKey == null) before();

            return after(message, moff, mlen);
        }

        /*
         * @description
         *   Encrypt and authenticates message using peer's public key,
         *   our secret key, and the given nonce, which must be unique
         *   for each distinct message for a key pair.
         *
         *   Explicitly pass the nonce
         *
         *   Returns an encrypted and authenticated message,
         *   which is nacl.box.overheadLength longer than the original message.
         * */
        public byte [] box(byte [] message, byte [] theNonce) {
            if (message==null) return null;
            return box(message, 0, message.length, theNonce);
        }

        public byte [] box(byte [] message, final int moff, byte [] theNonce) {
            if (!(message!=null && message.length>moff)) return null;
            return box(message, moff, message.length-moff, theNonce);
        }

        public byte [] box(byte [] message, final int moff, final int mlen, byte [] theNonce) {
            if (!(message!=null && message.length>=(moff+mlen) &&
                    theNonce!=null && theNonce.length==nonceLength))
                return null;

            // prepare shared key
            if (this.sharedKey == null) before();

            return after(message, moff, mlen, theNonce);
        }

        /*
         * @description
         *   Authenticates and decrypts the given box with peer's public key,
         *   our secret key, and the given nonce.
         *
         *   Returns the original message, or null if authentication fails.
         * */
        public byte [] open(byte [] box) {
            if (box==null) return null;

            // prepare shared key
            if (this.sharedKey == null) before();

            return open_after(box, 0, box.length);
        }
        public byte [] open(byte [] box, final int boxoff) {
            if (!(box!=null && box.length>boxoff)) return null;

            // prepare shared key
            if (this.sharedKey == null) before();

            return open_after(box, boxoff, box.length-boxoff);
        }
        public byte [] open(byte [] box, final int boxoff, final int boxlen) {
            if (!(box!=null && box.length>=(boxoff+boxlen))) return null;

            // prepare shared key
            if (this.sharedKey == null) before();

            return open_after(box, boxoff, boxlen);
        }


        /*
         * @description
         *   Authenticates and decrypts the given box with peer's public key,
         *   our secret key, and the given nonce.
         *   Explicit passing of nonce
         *   Returns the original message, or null if authentication fails.
         * */
        public byte [] open(byte [] box, byte [] theNonce) {
            if (!(box!=null &&
                    theNonce!=null && theNonce.length==nonceLength))
                return null;

            // prepare shared key
            if (this.sharedKey == null) before();

            return open_after(box, 0, box.length, theNonce);
        }
        public byte [] open(byte [] box, final int boxoff, byte [] theNonce) {
            if (!(box!=null && box.length>boxoff &&
                    theNonce!=null && theNonce.length==nonceLength))
                return null;

            // prepare shared key
            if (this.sharedKey == null) before();

            return open_after(box, boxoff, box.length-boxoff, theNonce);
        }
        public byte [] open(byte [] box, final int boxoff, final int boxlen, byte [] theNonce) {
            if (!(box!=null && box.length>=(boxoff+boxlen) &&
                    theNonce!=null && theNonce.length==nonceLength))
                return null;

            // prepare shared key
            if (this.sharedKey == null) before();

            return open_after(box, boxoff, boxlen, theNonce);
        }


        /*
         * @description
         *   Returns a precomputed shared key
         *   which can be used in nacl.box.after and nacl.box.open.after.
         * */
        public byte [] before() {
            if (this.sharedKey == null) {
                this.sharedKey = new byte[sharedKeyLength];
                crypto_box_beforenm(this.sharedKey, this.theirPublicKey, this.mySecretKey);
            }

            return this.sharedKey;
        }

        /*
         * @description
         *   Same as nacl.box, but uses a shared key precomputed with nacl.box.before.
         * */
        public byte [] after(byte [] message, final int moff, final int mlen) {
            return after(message, moff, mlen, generateNonce());
        }

        /*
         * @description
         *   Same as nacl.box, but uses a shared key precomputed with nacl.box.before,
         *   and passes a nonce explicitly.
         * */
        public byte [] after(byte [] message, final int moff, final int mlen, byte [] theNonce) {
            // check message
            if (!(message!=null && message.length>=(moff+mlen) &&
                    theNonce!=null && theNonce.length==nonceLength))
                return null;

            // message buffer
            byte [] m = new byte[mlen + zerobytesLength];

            // cipher buffer
            byte [] c = new byte[m.length];

            for (int i = 0; i < mlen; i ++)
                m[i+zerobytesLength] = message[i+moff];

            if (0 != crypto_box_afternm(c, m, m.length, theNonce, sharedKey))
                return null;

            // wrap byte_buf_t on c offset@boxzerobytesLength
            ///return new byte_buf_t(c, boxzerobytesLength, c.length-boxzerobytesLength);
            byte [] ret = new byte[c.length-boxzerobytesLength];

            for (int i = 0; i < ret.length; i ++)
                ret[i] = c[i+boxzerobytesLength];

            return ret;
        }

        /*
         * @description
         *   Same as nacl.box.open,
         *   but uses a shared key pre-computed with nacl.box.before.
         * */
        public byte [] open_after(byte [] box, final int boxoff, final int boxlen) {
            return open_after(box, boxoff, boxlen, generateNonce());
        }

        public byte [] open_after(byte [] box, final int boxoff, final int boxlen, byte [] theNonce) {
            // check message
            if (!(box!=null && box.length>=(boxoff+boxlen) && boxlen>=boxzerobytesLength))
                return null;

            // cipher buffer
            byte [] c = new byte[boxlen + boxzerobytesLength];

            // message buffer
            byte [] m = new byte[c.length];

            for (int i = 0; i < boxlen; i++)
                c[i+boxzerobytesLength] = box[i+boxoff];

            if (crypto_box_open_afternm(m, c, c.length, theNonce, sharedKey) != 0)
                return null;

            // wrap byte_buf_t on m offset@zerobytesLength
            ///return new byte_buf_t(m, zerobytesLength, m.length-zerobytesLength);
            byte [] ret = new byte[m.length-zerobytesLength];

            for (int i = 0; i < ret.length; i ++)
                ret[i] = m[i+zerobytesLength];

            return ret;
        }

        /*
         * @description
         *   Length of public key in bytes.
         * */
        public static final int publicKeyLength = 32;

        /*
         * @description
         *   Length of secret key in bytes.
         * */
        public static final int secretKeyLength = 32;

        /*
         * @description
         *   Length of precomputed shared key in bytes.
         * */
        public static final int sharedKeyLength = 32;

        /*
         * @description
         *   Length of nonce in bytes.
         * */
        public static final int nonceLength     = 24;

        /*
         * @description
         *   zero bytes in case box
         * */
        public static final int zerobytesLength    = 32;
        /*
         * @description
         *   zero bytes in case open box
         * */
        public static final int boxzerobytesLength = 16;

        /*
         * @description
         *   Length of overhead added to box compared to original message.
         * */
        public static final int overheadLength  = 16;

        public static class KeyPair {
            private byte [] publicKey;
            private byte [] secretKey;

            public KeyPair() {
                publicKey = new byte[publicKeyLength];
                secretKey = new byte[secretKeyLength];
            }

            public byte [] getPublicKey() {
                return publicKey;
            }

            public byte [] getSecretKey() {
                return secretKey;
            }
        }

        /*
         * @description
         *   Generates a new random key pair for box and
         *   returns it as an object with publicKey and secretKey members:
         * */
        public static KeyPair keyPair() {
            KeyPair kp = new KeyPair();

            crypto_box_keypair(kp.getPublicKey(), kp.getSecretKey());
            return kp;
        }

        public static KeyPair keyPair_fromSecretKey(byte [] secretKey) {
            KeyPair kp = new KeyPair();
            byte [] sk = kp.getSecretKey();
            byte [] pk = kp.getPublicKey();

            // copy sk
            for (int i = 0; i < sk.length; i ++)
                sk[i] = secretKey[i];

            crypto_scalarmult_base(pk, sk);
            return kp;
        }

    }

    /*
     * @description
     *   Secret Box algorithm, secret key
     * */
    public static final class SecretBox {

        private final static String TAG = "SecretBox";

        private AtomicLong nonce;

        private byte [] key;

        public SecretBox(byte [] key) {
            this(key, 68);
        }

        public SecretBox(byte [] key, long nonce) {
            this.key = key;

            this.nonce = new AtomicLong(nonce);
        }

        public void setNonce(long nonce) {
            this.nonce.set(nonce);
        }
        public long getNonce() {
            return this.nonce.get();
        }
        public long incrNonce() {
            return this.nonce.incrementAndGet();
        }
        private byte[] generateNonce() {
            // generate nonce
            long nonce = this.nonce.get();

            byte [] n = new byte[nonceLength];
            for (int i = 0; i < nonceLength; i += 8) {
                n[i+0] = (byte) (nonce>>> 0);
                n[i+1] = (byte) (nonce>>> 8);
                n[i+2] = (byte) (nonce>>>16);
                n[i+3] = (byte) (nonce>>>24);
                n[i+4] = (byte) (nonce>>>32);
                n[i+5] = (byte) (nonce>>>40);
                n[i+6] = (byte) (nonce>>>48);
                n[i+7] = (byte) (nonce>>>56);
            }

            return n;
        }

        /*
         * @description
         *   Encrypt and authenticates message using the key and the nonce.
         *   The nonce must be unique for each distinct message for this key.
         *
         *   Returns an encrypted and authenticated message,
         *   which is nacl.secretbox.overheadLength longer than the original message.
         * */
        public byte [] box(byte [] message) {
            if (message==null) return null;
            return box(message, 0, message.length);
        }

        public byte [] box(byte [] message, final int moff) {
            if (!(message!=null && message.length>moff)) return null;
            return box(message, moff, message.length-moff);
        }

        public byte [] box(byte [] message, final int moff, final int mlen) {
            // check message
            if (!(message!=null && message.length>=(moff+mlen)))
                return null;
            return box(message, moff, message.length-moff, generateNonce());
        }

        public byte [] box(byte [] message, byte [] theNonce) {
            if (message==null) return null;
            return box(message, 0, message.length, theNonce);
        }

        public byte [] box(byte [] message, final int moff, byte [] theNonce) {
            if (!(message!=null && message.length>moff)) return null;
            return box(message, moff, message.length-moff, theNonce);
        }

        public byte [] box(byte [] message, final int moff, final int mlen, byte [] theNonce) {
            // check message
            if (!(message!=null && message.length>=(moff+mlen) &&
                    theNonce!=null && theNonce.length==nonceLength))
                return null;

            // message buffer
            byte [] m = new byte[mlen + zerobytesLength];

            // cipher buffer
            byte [] c = new byte[m.length];

            for (int i = 0; i < mlen; i ++)
                m[i+zerobytesLength] = message[i+moff];

            if (0 != crypto_secretbox(c, m, m.length, theNonce, key))
                return null;

            // TBD optimizing ...
            // wrap byte_buf_t on c offset@boxzerobytesLength
            ///return new byte_buf_t(c, boxzerobytesLength, c.length-boxzerobytesLength);
            byte [] ret = new byte[c.length-boxzerobytesLength];

            for (int i = 0; i < ret.length; i ++)
                ret[i] = c[i+boxzerobytesLength];

            return ret;
        }

        /*
         * @description
         *   Authenticates and decrypts the given secret box
         *   using the key and the nonce.
         *
         *   Returns the original message, or null if authentication fails.
         * */
        public byte [] open(byte [] box) {
            if (box==null) return null;
            return open(box, 0, box.length);
        }

        public byte [] open(byte [] box, final int boxoff) {
            if (!(box!=null && box.length>boxoff)) return null;
            return open(box, boxoff, box.length-boxoff);
        }

        public byte [] open(byte [] box, final int boxoff, final int boxlen) {
            // check message
            if (!(box!=null && box.length>=(boxoff+boxlen) && boxlen>=boxzerobytesLength))
                return null;
            return open(box, boxoff, box.length-boxoff, generateNonce());
        }

        public byte [] open(byte [] box, byte [] theNonce) {
            if (box==null) return null;
            return open(box, 0, box.length, theNonce);
        }

        public byte [] open(byte [] box, final int boxoff, byte [] theNonce) {
            if (!(box!=null && box.length>boxoff)) return null;
            return open(box, boxoff, box.length-boxoff, theNonce);
        }

        public byte [] open(byte [] box, final int boxoff, final int boxlen, byte [] theNonce) {
            // check message
            if (!(box!=null && box.length>=(boxoff+boxlen) && boxlen>=boxzerobytesLength &&
                    theNonce!=null && theNonce.length==nonceLength))
                return null;

            // cipher buffer
            byte [] c = new byte[boxlen + boxzerobytesLength];

            // message buffer
            byte [] m = new byte[c.length];

            for (int i = 0; i < boxlen; i++)
                c[i+boxzerobytesLength] = box[i+boxoff];

            if (0 != crypto_secretbox_open(m, c, c.length, theNonce, key))
                return null;

            // wrap byte_buf_t on m offset@zerobytesLength
            ///return new byte_buf_t(m, zerobytesLength, m.length-zerobytesLength);
            byte [] ret = new byte[m.length-zerobytesLength];

            for (int i = 0; i < ret.length; i ++)
                ret[i] = m[i+zerobytesLength];

            return ret;
        }

        /*
         * @description
         *   Length of key in bytes.
         * */
        public static final int keyLength      = 32;

        /*
         * @description
         *   Length of nonce in bytes.
         * */
        public static final int nonceLength    = 24;

        /*
         * @description
         *   Length of overhead added to secret box compared to original message.
         * */
        public static final int overheadLength = 16;

        /*
         * @description
         *   zero bytes in case box
         * */
        public static final int zerobytesLength    = 32;
        /*
         * @description
         *   zero bytes in case open box
         * */
        public static final int boxzerobytesLength = 16;

    }

    /*
     * @description
     *   Scalar multiplication, Implements curve25519.
     * */
    public static final class ScalarMult {

        private final static String TAG = "ScalarMult";

        /*
         * @description
         *   Multiplies an integer n by a group element p and
         *   returns the resulting group element.
         * */
        public static byte [] scalseMult(byte [] n, byte [] p) {
            if (!(n.length==scalarLength && p.length==groupElementLength))
                return null;

            byte [] q = new byte [scalarLength];

            crypto_scalarmult(q, n, p);

            return q;
        }

        /*
         * @description
         *   Multiplies an integer n by a standard group element and
         *   returns the resulting group element.
         * */
        public static byte [] scalseMult_base(byte [] n) {
            if (!(n.length==scalarLength))
                return null;

            byte [] q = new byte [scalarLength];

            crypto_scalarmult_base(q, n);

            return q;
        }

        /*
         * @description
         *   Length of scalar in bytes.
         * */
        public static final int scalarLength        = 32;

        /*
         * @description
         *   Length of group element in bytes.
         * */
        public static final int groupElementLength  = 32;

    }


    /*
     * @description
     *   Hash algorithm, Implements SHA-512.
     * */
    public static final class Hash {

        private final static String TAG = "Hash";

        /*
         * @description
         *   Returns SHA-512 hash of the message.
         * */
        public static byte[] sha512(byte [] message) {
            if (!(message!=null && message.length>0))
                return null;

            byte [] out = new byte[hashLength];

            crypto_hash(out, message);

            return out;
        }
        public static byte[] sha512(String message) throws UnsupportedEncodingException {
            return sha512(message.getBytes("utf-8"));
        }

        /*
         * @description
         *   Length of hash in bytes.
         * */
        public static final int hashLength       = 64;

    }


    /*
     * @description
     *   Signature algorithm, Implements ed25519.
     * */
    public static final class Signature {

        private final static String TAG = "Signature";

        private byte [] theirPublicKey;
        private byte [] mySecretKey;

        public Signature(byte [] theirPublicKey, byte [] mySecretKey) {
            this.theirPublicKey = theirPublicKey;
            this.mySecretKey = mySecretKey;
        }

        /*
         * @description
         *   Signs the message using the secret key and returns a signed message.
         * */
        public byte [] sign(byte [] message) {
            if (message==null) return null;

            return sign(message, 0, message.length);
        }
        public byte [] sign(byte [] message, final int moff) {
            if (!(message!=null && message.length>moff)) return null;

            return sign(message, moff, message.length-moff);
        }
        public byte [] sign(byte [] message, final int moff, final int mlen) {
            // check message
            if (!(message!=null && message.length>=(moff+mlen)))
                return null;

            // signed message
            byte [] sm = new byte[mlen + signatureLength];

            crypto_sign(sm, -1, message, moff, mlen, mySecretKey);

            return sm;
        }

        /*
         * @description
         *   Verifies the signed message and returns the message without signature.
         *   Returns null if verification failed.
         * */
        public byte [] open(byte [] signedMessage) {
            if (signedMessage==null) return null;

            return open(signedMessage, 0, signedMessage.length);
        }
        public byte [] open(byte [] signedMessage, final int smoff) {
            if (!(signedMessage!=null && signedMessage.length>smoff)) return null;

            return open(signedMessage, smoff, signedMessage.length-smoff);
        }
        public byte [] open(byte [] signedMessage, final int smoff, final int smlen) {
            // check sm length
            if (!(signedMessage!=null && signedMessage.length>=(smoff+smlen) && smlen>=signatureLength))
                return null;

            // temp buffer
            byte [] tmp = new byte[smlen];

            if (0 != crypto_sign_open(tmp, -1, signedMessage, smoff, smlen, theirPublicKey))
                return null;

            // message
            byte [] msg = new byte[smlen-signatureLength];
            for (int i = 0; i < msg.length; i ++)
                msg[i] = signedMessage[smoff+ i+signatureLength];

            return msg;
        }

        /*
         * @description
         *   Signs the message using the secret key and returns a signature.
         * */
        public byte [] detached(byte [] message) {
            byte[] signedMsg = this.sign(message);
            byte[] sig = new byte[signatureLength];
            for (int i = 0; i < sig.length; i++)
                sig[i] = signedMsg[i];
            return sig;
        }

        /*
         * @description
         *   Verifies the signature for the message and
         *   returns true if verification succeeded or false if it failed.
         * */
        public boolean detached_verify(byte [] message, byte [] signature) {
            if (signature.length != signatureLength)
                return false;
            if (theirPublicKey.length != publicKeyLength)
                return false;
            byte [] sm = new byte[signatureLength + message.length];
            byte [] m = new byte[signatureLength + message.length];
            for (int i = 0; i < signatureLength; i++)
                sm[i] = signature[i];
            for (int i = 0; i < message.length; i++)
                sm[i + signatureLength] = message[i];
            return (crypto_sign_open(m, -1, sm, 0, sm.length, theirPublicKey) >= 0);
        }

        /*
         * @description
         *   Generates new random key pair for signing and
         *   returns it as an object with publicKey and secretKey members
         * */
        public static class KeyPair {
            private byte [] publicKey;
            private byte [] secretKey;

            public KeyPair() {
                publicKey = new byte[publicKeyLength];
                secretKey = new byte[secretKeyLength];
            }

            public byte [] getPublicKey() {
                return publicKey;
            }

            public byte [] getSecretKey() {
                return secretKey;
            }
        }

        /*
         * @description
         *   Signs the message using the secret key and returns a signed message.
         * */
        public static KeyPair keyPair() {
            KeyPair kp = new KeyPair();

            crypto_sign_keypair(kp.getPublicKey(), kp.getSecretKey(), false);
            return kp;
        }
        public static KeyPair keyPair_fromSecretKey(byte [] secretKey) {
            KeyPair kp = new KeyPair();
            byte [] pk = kp.getPublicKey();
            byte [] sk = kp.getSecretKey();

            // copy sk
            for (int i = 0; i < kp.getSecretKey().length; i ++)
                sk[i] = secretKey[i];

            // copy pk from sk
            for (int i = 0; i < kp.getPublicKey().length; i ++)
                pk[i] = secretKey[32+i]; // hard-copy

            return kp;
        }

        public static KeyPair keyPair_fromSeed(byte [] seed) {
            KeyPair kp = new KeyPair();
            byte [] pk = kp.getPublicKey();
            byte [] sk = kp.getSecretKey();

            // copy sk
            for (int i = 0; i < seedLength; i ++)
                sk[i] = seed[i];

            // generate pk from sk
            crypto_sign_keypair(pk, sk, true);

            return kp;
        }

        /*
         * @description
         *   Length of signing public key in bytes.
         * */
        public static final int publicKeyLength = 32;

        /*
         * @description
         *   Length of signing secret key in bytes.
         * */
        public static final int secretKeyLength = 64;

        /*
         * @description
         *   Length of seed for nacl.sign.keyPair.fromSeed in bytes.
         * */
        public static final int seedLength      = 32;

        /*
         * @description
         *   Length of signature in bytes.
         * */
        public static final int signatureLength = 64;
    }


    ////////////////////////////////////////////////////////////////////////////////////
    /*
     * @description
     *   Codes below are ported tweetnacl-fast.js from TweetNacl.c/TweetNacl.h
     * */

    private static final byte [] _0 = new byte[16];
    private static final byte [] _9 = new byte[32];
    static {
        ///for (int i = 0; i < _0.length; i ++) _0[i] = 0;

        ///for (int i = 0; i < _9.length; i ++) _9[i] = 0;
        _9[0] = 9;
    }

    private static final long []     gf0 = new long[16];
    private static final long []     gf1 = new long[16];
    private static final long [] _121665 = new long[16];
    static {
        ///for (int i = 0; i < gf0.length; i ++) gf0[i] = 0;

        ///for (int i = 0; i < gf1.length; i ++)  gf1[i] = 0;
        gf1[0] = 1;

        ///for (int i = 0; i < _121665.length; i ++) _121665[i] = 0;
        _121665[0] = 0xDB41; _121665[1] = 1;
    }

    private static final long []  D = new long [] {
            0x78a3, 0x1359, 0x4dca, 0x75eb,
            0xd8ab, 0x4141, 0x0a4d, 0x0070,
            0xe898, 0x7779, 0x4079, 0x8cc7,
            0xfe73, 0x2b6f, 0x6cee, 0x5203
    };
    private static final long [] D2 = new long [] {
            0xf159, 0x26b2, 0x9b94, 0xebd6,
            0xb156, 0x8283, 0x149a, 0x00e0,
            0xd130, 0xeef3, 0x80f2, 0x198e,
            0xfce7, 0x56df, 0xd9dc, 0x2406
    };
    private static final long []  X = new long [] {
            0xd51a, 0x8f25, 0x2d60, 0xc956,
            0xa7b2, 0x9525, 0xc760, 0x692c,
            0xdc5c, 0xfdd6, 0xe231, 0xc0a4,
            0x53fe, 0xcd6e, 0x36d3, 0x2169
    };
    private static final long []  Y = new long [] {
            0x6658, 0x6666, 0x6666, 0x6666,
            0x6666, 0x6666, 0x6666, 0x6666,
            0x6666, 0x6666, 0x6666, 0x6666,
            0x6666, 0x6666, 0x6666, 0x6666
    };
    private static final long []  I = new long [] {
            0xa0b0, 0x4a0e, 0x1b27, 0xc4ee,
            0xe478, 0xad2f, 0x1806, 0x2f43,
            0xd7a7, 0x3dfb, 0x0099, 0x2b4d,
            0xdf0b, 0x4fc1, 0x2480, 0x2b83
    };

    private static void ts64(byte [] x, final int xoff, long u)
    {
        ///int i;
        ///for (i = 7;i >= 0;--i) { x[i+xoff] = (byte)(u&0xff); u >>>= 8; }

        x[7+xoff] = (byte)(u&0xff); u >>>= 8;
        x[6+xoff] = (byte)(u&0xff); u >>>= 8;
        x[5+xoff] = (byte)(u&0xff); u >>>= 8;
        x[4+xoff] = (byte)(u&0xff); u >>>= 8;
        x[3+xoff] = (byte)(u&0xff); u >>>= 8;
        x[2+xoff] = (byte)(u&0xff); u >>>= 8;
        x[1+xoff] = (byte)(u&0xff); u >>>= 8;
        x[0+xoff] = (byte)(u&0xff); ///u >>>= 8;
    }

    private static int vn(
            byte [] x, final int xoff,
            byte [] y, final int yoff,
            int n)
    {
        int i,d = 0;
        for (i = 0; i < n; i ++) d |= (x[i+xoff]^y[i+yoff]) & 0xff;
        return (1 & ((d - 1) >>> 8)) - 1;
    }

    private static int crypto_verify_16(
            byte [] x, final int xoff,
            byte [] y, final int yoff)
    {
        return vn(x,xoff,y,yoff,16);
    }
    public static int crypto_verify_16(byte [] x, byte [] y)
    {
        return crypto_verify_16(x,0, y,0);
    }

    private static int crypto_verify_32(
            byte [] x, final int xoff,
            byte [] y, final int yoff)
    {
        return vn(x,xoff,y,yoff,32);
    }
    public static int crypto_verify_32(byte [] x, byte [] y)
    {
        return crypto_verify_32(x,0, y,0);
    }

    private static void core_salsa20(byte [] o, byte [] p, byte [] k, byte [] c) {
        int     j0  = c[ 0] & 0xff | (c[ 1] & 0xff)<<8 | (c[ 2] & 0xff)<<16 | (c[ 3] & 0xff)<<24,
                j1  = k[ 0] & 0xff | (k[ 1] & 0xff)<<8 | (k[ 2] & 0xff)<<16 | (k[ 3] & 0xff)<<24,
                j2  = k[ 4] & 0xff | (k[ 5] & 0xff)<<8 | (k[ 6] & 0xff)<<16 | (k[ 7] & 0xff)<<24,
                j3  = k[ 8] & 0xff | (k[ 9] & 0xff)<<8 | (k[10] & 0xff)<<16 | (k[11] & 0xff)<<24,
                j4  = k[12] & 0xff | (k[13] & 0xff)<<8 | (k[14] & 0xff)<<16 | (k[15] & 0xff)<<24,
                j5  = c[ 4] & 0xff | (c[ 5] & 0xff)<<8 | (c[ 6] & 0xff)<<16 | (c[ 7] & 0xff)<<24,
                j6  = p[ 0] & 0xff | (p[ 1] & 0xff)<<8 | (p[ 2] & 0xff)<<16 | (p[ 3] & 0xff)<<24,
                j7  = p[ 4] & 0xff | (p[ 5] & 0xff)<<8 | (p[ 6] & 0xff)<<16 | (p[ 7] & 0xff)<<24,
                j8  = p[ 8] & 0xff | (p[ 9] & 0xff)<<8 | (p[10] & 0xff)<<16 | (p[11] & 0xff)<<24,
                j9  = p[12] & 0xff | (p[13] & 0xff)<<8 | (p[14] & 0xff)<<16 | (p[15] & 0xff)<<24,
                j10 = c[ 8] & 0xff | (c[ 9] & 0xff)<<8 | (c[10] & 0xff)<<16 | (c[11] & 0xff)<<24,
                j11 = k[16] & 0xff | (k[17] & 0xff)<<8 | (k[18] & 0xff)<<16 | (k[19] & 0xff)<<24,
                j12 = k[20] & 0xff | (k[21] & 0xff)<<8 | (k[22] & 0xff)<<16 | (k[23] & 0xff)<<24,
                j13 = k[24] & 0xff | (k[25] & 0xff)<<8 | (k[26] & 0xff)<<16 | (k[27] & 0xff)<<24,
                j14 = k[28] & 0xff | (k[29] & 0xff)<<8 | (k[30] & 0xff)<<16 | (k[31] & 0xff)<<24,
                j15 = c[12] & 0xff | (c[13] & 0xff)<<8 | (c[14] & 0xff)<<16 | (c[15] & 0xff)<<24;

        int     x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7,
                x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14,
                x15 = j15, u;

        for (int i = 0; i < 20; i += 2) {
            u = x0 + x12 | 0;
            x4 ^= u<<7 | u>>>(32-7);
            u = x4 + x0 | 0;
            x8 ^= u<<9 | u>>>(32-9);
            u = x8 + x4 | 0;
            x12 ^= u<<13 | u>>>(32-13);
            u = x12 + x8 | 0;
            x0 ^= u<<18 | u>>>(32-18);

            u = x5 + x1 | 0;
            x9 ^= u<<7 | u>>>(32-7);
            u = x9 + x5 | 0;
            x13 ^= u<<9 | u>>>(32-9);
            u = x13 + x9 | 0;
            x1 ^= u<<13 | u>>>(32-13);
            u = x1 + x13 | 0;
            x5 ^= u<<18 | u>>>(32-18);

            u = x10 + x6 | 0;
            x14 ^= u<<7 | u>>>(32-7);
            u = x14 + x10 | 0;
            x2 ^= u<<9 | u>>>(32-9);
            u = x2 + x14 | 0;
            x6 ^= u<<13 | u>>>(32-13);
            u = x6 + x2 | 0;
            x10 ^= u<<18 | u>>>(32-18);

            u = x15 + x11 | 0;
            x3 ^= u<<7 | u>>>(32-7);
            u = x3 + x15 | 0;
            x7 ^= u<<9 | u>>>(32-9);
            u = x7 + x3 | 0;
            x11 ^= u<<13 | u>>>(32-13);
            u = x11 + x7 | 0;
            x15 ^= u<<18 | u>>>(32-18);

            u = x0 + x3 | 0;
            x1 ^= u<<7 | u>>>(32-7);
            u = x1 + x0 | 0;
            x2 ^= u<<9 | u>>>(32-9);
            u = x2 + x1 | 0;
            x3 ^= u<<13 | u>>>(32-13);
            u = x3 + x2 | 0;
            x0 ^= u<<18 | u>>>(32-18);

            u = x5 + x4 | 0;
            x6 ^= u<<7 | u>>>(32-7);
            u = x6 + x5 | 0;
            x7 ^= u<<9 | u>>>(32-9);
            u = x7 + x6 | 0;
            x4 ^= u<<13 | u>>>(32-13);
            u = x4 + x7 | 0;
            x5 ^= u<<18 | u>>>(32-18);

            u = x10 + x9 | 0;
            x11 ^= u<<7 | u>>>(32-7);
            u = x11 + x10 | 0;
            x8 ^= u<<9 | u>>>(32-9);
            u = x8 + x11 | 0;
            x9 ^= u<<13 | u>>>(32-13);
            u = x9 + x8 | 0;
            x10 ^= u<<18 | u>>>(32-18);

            u = x15 + x14 | 0;
            x12 ^= u<<7 | u>>>(32-7);
            u = x12 + x15 | 0;
            x13 ^= u<<9 | u>>>(32-9);
            u = x13 + x12 | 0;
            x14 ^= u<<13 | u>>>(32-13);
            u = x14 + x13 | 0;
            x15 ^= u<<18 | u>>>(32-18);
        }
        x0 =  x0 +  j0 | 0;
        x1 =  x1 +  j1 | 0;
        x2 =  x2 +  j2 | 0;
        x3 =  x3 +  j3 | 0;
        x4 =  x4 +  j4 | 0;
        x5 =  x5 +  j5 | 0;
        x6 =  x6 +  j6 | 0;
        x7 =  x7 +  j7 | 0;
        x8 =  x8 +  j8 | 0;
        x9 =  x9 +  j9 | 0;
        x10 = x10 + j10 | 0;
        x11 = x11 + j11 | 0;
        x12 = x12 + j12 | 0;
        x13 = x13 + j13 | 0;
        x14 = x14 + j14 | 0;
        x15 = x15 + j15 | 0;

        o[ 0] = (byte) (x0 >>>  0 & 0xff);
        o[ 1] = (byte) (x0 >>>  8 & 0xff);
        o[ 2] = (byte) (x0 >>> 16 & 0xff);
        o[ 3] = (byte) (x0 >>> 24 & 0xff);

        o[ 4] = (byte) (x1 >>>  0 & 0xff);
        o[ 5] = (byte) (x1 >>>  8 & 0xff);
        o[ 6] = (byte) (x1 >>> 16 & 0xff);
        o[ 7] = (byte) (x1 >>> 24 & 0xff);

        o[ 8] = (byte) (x2 >>>  0 & 0xff);
        o[ 9] = (byte) (x2 >>>  8 & 0xff);
        o[10] = (byte) (x2 >>> 16 & 0xff);
        o[11] = (byte) (x2 >>> 24 & 0xff);

        o[12] = (byte) (x3 >>>  0 & 0xff);
        o[13] = (byte) (x3 >>>  8 & 0xff);
        o[14] = (byte) (x3 >>> 16 & 0xff);
        o[15] = (byte) (x3 >>> 24 & 0xff);

        o[16] = (byte) (x4 >>>  0 & 0xff);
        o[17] = (byte) (x4 >>>  8 & 0xff);
        o[18] = (byte) (x4 >>> 16 & 0xff);
        o[19] = (byte) (x4 >>> 24 & 0xff);

        o[20] = (byte) (x5 >>>  0 & 0xff);
        o[21] = (byte) (x5 >>>  8 & 0xff);
        o[22] = (byte) (x5 >>> 16 & 0xff);
        o[23] = (byte) (x5 >>> 24 & 0xff);

        o[24] = (byte) (x6 >>>  0 & 0xff);
        o[25] = (byte) (x6 >>>  8 & 0xff);
        o[26] = (byte) (x6 >>> 16 & 0xff);
        o[27] = (byte) (x6 >>> 24 & 0xff);

        o[28] = (byte) (x7 >>>  0 & 0xff);
        o[29] = (byte) (x7 >>>  8 & 0xff);
        o[30] = (byte) (x7 >>> 16 & 0xff);
        o[31] = (byte) (x7 >>> 24 & 0xff);

        o[32] = (byte) (x8 >>>  0 & 0xff);
        o[33] = (byte) (x8 >>>  8 & 0xff);
        o[34] = (byte) (x8 >>> 16 & 0xff);
        o[35] = (byte) (x8 >>> 24 & 0xff);

        o[36] = (byte) (x9 >>>  0 & 0xff);
        o[37] = (byte) (x9 >>>  8 & 0xff);
        o[38] = (byte) (x9 >>> 16 & 0xff);
        o[39] = (byte) (x9 >>> 24 & 0xff);

        o[40] = (byte) (x10 >>>  0 & 0xff);
        o[41] = (byte) (x10 >>>  8 & 0xff);
        o[42] = (byte) (x10 >>> 16 & 0xff);
        o[43] = (byte) (x10 >>> 24 & 0xff);

        o[44] = (byte) (x11 >>>  0 & 0xff);
        o[45] = (byte) (x11 >>>  8 & 0xff);
        o[46] = (byte) (x11 >>> 16 & 0xff);
        o[47] = (byte) (x11 >>> 24 & 0xff);

        o[48] = (byte) (x12 >>>  0 & 0xff);
        o[49] = (byte) (x12 >>>  8 & 0xff);
        o[50] = (byte) (x12 >>> 16 & 0xff);
        o[51] = (byte) (x12 >>> 24 & 0xff);

        o[52] = (byte) (x13 >>>  0 & 0xff);
        o[53] = (byte) (x13 >>>  8 & 0xff);
        o[54] = (byte) (x13 >>> 16 & 0xff);
        o[55] = (byte) (x13 >>> 24 & 0xff);

        o[56] = (byte) (x14 >>>  0 & 0xff);
        o[57] = (byte) (x14 >>>  8 & 0xff);
        o[58] = (byte) (x14 >>> 16 & 0xff);
        o[59] = (byte) (x14 >>> 24 & 0xff);

        o[60] = (byte) (x15 >>>  0 & 0xff);
        o[61] = (byte) (x15 >>>  8 & 0xff);
        o[62] = (byte) (x15 >>> 16 & 0xff);
        o[63] = (byte) (x15 >>> 24 & 0xff);
		
		/*String dbgt = "";
		for (int dbg = 0; dbg < o.length; dbg ++) dbgt += " "+o[dbg];
		Log.d(TAG, "core_salsa20 -> "+dbgt);
*/
    }

    private static void core_hsalsa20(byte [] o, byte [] p, byte [] k, byte [] c) {
        int     j0  = c[ 0] & 0xff | (c[ 1] & 0xff)<<8 | (c[ 2] & 0xff)<<16 | (c[ 3] & 0xff)<<24,
                j1  = k[ 0] & 0xff | (k[ 1] & 0xff)<<8 | (k[ 2] & 0xff)<<16 | (k[ 3] & 0xff)<<24,
                j2  = k[ 4] & 0xff | (k[ 5] & 0xff)<<8 | (k[ 6] & 0xff)<<16 | (k[ 7] & 0xff)<<24,
                j3  = k[ 8] & 0xff | (k[ 9] & 0xff)<<8 | (k[10] & 0xff)<<16 | (k[11] & 0xff)<<24,
                j4  = k[12] & 0xff | (k[13] & 0xff)<<8 | (k[14] & 0xff)<<16 | (k[15] & 0xff)<<24,
                j5  = c[ 4] & 0xff | (c[ 5] & 0xff)<<8 | (c[ 6] & 0xff)<<16 | (c[ 7] & 0xff)<<24,
                j6  = p[ 0] & 0xff | (p[ 1] & 0xff)<<8 | (p[ 2] & 0xff)<<16 | (p[ 3] & 0xff)<<24,
                j7  = p[ 4] & 0xff | (p[ 5] & 0xff)<<8 | (p[ 6] & 0xff)<<16 | (p[ 7] & 0xff)<<24,
                j8  = p[ 8] & 0xff | (p[ 9] & 0xff)<<8 | (p[10] & 0xff)<<16 | (p[11] & 0xff)<<24,
                j9  = p[12] & 0xff | (p[13] & 0xff)<<8 | (p[14] & 0xff)<<16 | (p[15] & 0xff)<<24,
                j10 = c[ 8] & 0xff | (c[ 9] & 0xff)<<8 | (c[10] & 0xff)<<16 | (c[11] & 0xff)<<24,
                j11 = k[16] & 0xff | (k[17] & 0xff)<<8 | (k[18] & 0xff)<<16 | (k[19] & 0xff)<<24,
                j12 = k[20] & 0xff | (k[21] & 0xff)<<8 | (k[22] & 0xff)<<16 | (k[23] & 0xff)<<24,
                j13 = k[24] & 0xff | (k[25] & 0xff)<<8 | (k[26] & 0xff)<<16 | (k[27] & 0xff)<<24,
                j14 = k[28] & 0xff | (k[29] & 0xff)<<8 | (k[30] & 0xff)<<16 | (k[31] & 0xff)<<24,
                j15 = c[12] & 0xff | (c[13] & 0xff)<<8 | (c[14] & 0xff)<<16 | (c[15] & 0xff)<<24;

        int     x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7,
                x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14,
                x15 = j15, u;

        for (int i = 0; i < 20; i += 2) {
            u = x0 + x12 | 0;
            x4 ^= u<<7 | u>>>(32-7);
            u = x4 + x0 | 0;
            x8 ^= u<<9 | u>>>(32-9);
            u = x8 + x4 | 0;
            x12 ^= u<<13 | u>>>(32-13);
            u = x12 + x8 | 0;
            x0 ^= u<<18 | u>>>(32-18);

            u = x5 + x1 | 0;
            x9 ^= u<<7 | u>>>(32-7);
            u = x9 + x5 | 0;
            x13 ^= u<<9 | u>>>(32-9);
            u = x13 + x9 | 0;
            x1 ^= u<<13 | u>>>(32-13);
            u = x1 + x13 | 0;
            x5 ^= u<<18 | u>>>(32-18);

            u = x10 + x6 | 0;
            x14 ^= u<<7 | u>>>(32-7);
            u = x14 + x10 | 0;
            x2 ^= u<<9 | u>>>(32-9);
            u = x2 + x14 | 0;
            x6 ^= u<<13 | u>>>(32-13);
            u = x6 + x2 | 0;
            x10 ^= u<<18 | u>>>(32-18);

            u = x15 + x11 | 0;
            x3 ^= u<<7 | u>>>(32-7);
            u = x3 + x15 | 0;
            x7 ^= u<<9 | u>>>(32-9);
            u = x7 + x3 | 0;
            x11 ^= u<<13 | u>>>(32-13);
            u = x11 + x7 | 0;
            x15 ^= u<<18 | u>>>(32-18);

            u = x0 + x3 | 0;
            x1 ^= u<<7 | u>>>(32-7);
            u = x1 + x0 | 0;
            x2 ^= u<<9 | u>>>(32-9);
            u = x2 + x1 | 0;
            x3 ^= u<<13 | u>>>(32-13);
            u = x3 + x2 | 0;
            x0 ^= u<<18 | u>>>(32-18);

            u = x5 + x4 | 0;
            x6 ^= u<<7 | u>>>(32-7);
            u = x6 + x5 | 0;
            x7 ^= u<<9 | u>>>(32-9);
            u = x7 + x6 | 0;
            x4 ^= u<<13 | u>>>(32-13);
            u = x4 + x7 | 0;
            x5 ^= u<<18 | u>>>(32-18);

            u = x10 + x9 | 0;
            x11 ^= u<<7 | u>>>(32-7);
            u = x11 + x10 | 0;
            x8 ^= u<<9 | u>>>(32-9);
            u = x8 + x11 | 0;
            x9 ^= u<<13 | u>>>(32-13);
            u = x9 + x8 | 0;
            x10 ^= u<<18 | u>>>(32-18);

            u = x15 + x14 | 0;
            x12 ^= u<<7 | u>>>(32-7);
            u = x12 + x15 | 0;
            x13 ^= u<<9 | u>>>(32-9);
            u = x13 + x12 | 0;
            x14 ^= u<<13 | u>>>(32-13);
            u = x14 + x13 | 0;
            x15 ^= u<<18 | u>>>(32-18);
        }

        o[ 0] = (byte) (x0 >>>  0 & 0xff);
        o[ 1] = (byte) (x0 >>>  8 & 0xff);
        o[ 2] = (byte) (x0 >>> 16 & 0xff);
        o[ 3] = (byte) (x0 >>> 24 & 0xff);

        o[ 4] = (byte) (x5 >>>  0 & 0xff);
        o[ 5] = (byte) (x5 >>>  8 & 0xff);
        o[ 6] = (byte) (x5 >>> 16 & 0xff);
        o[ 7] = (byte) (x5 >>> 24 & 0xff);

        o[ 8] = (byte) (x10 >>>  0 & 0xff);
        o[ 9] = (byte) (x10 >>>  8 & 0xff);
        o[10] = (byte) (x10 >>> 16 & 0xff);
        o[11] = (byte) (x10 >>> 24 & 0xff);

        o[12] = (byte) (x15 >>>  0 & 0xff);
        o[13] = (byte) (x15 >>>  8 & 0xff);
        o[14] = (byte) (x15 >>> 16 & 0xff);
        o[15] = (byte) (x15 >>> 24 & 0xff);

        o[16] = (byte) (x6 >>>  0 & 0xff);
        o[17] = (byte) (x6 >>>  8 & 0xff);
        o[18] = (byte) (x6 >>> 16 & 0xff);
        o[19] = (byte) (x6 >>> 24 & 0xff);

        o[20] = (byte) (x7 >>>  0 & 0xff);
        o[21] = (byte) (x7 >>>  8 & 0xff);
        o[22] = (byte) (x7 >>> 16 & 0xff);
        o[23] = (byte) (x7 >>> 24 & 0xff);

        o[24] = (byte) (x8 >>>  0 & 0xff);
        o[25] = (byte) (x8 >>>  8 & 0xff);
        o[26] = (byte) (x8 >>> 16 & 0xff);
        o[27] = (byte) (x8 >>> 24 & 0xff);

        o[28] = (byte) (x9 >>>  0 & 0xff);
        o[29] = (byte) (x9 >>>  8 & 0xff);
        o[30] = (byte) (x9 >>> 16 & 0xff);
        o[31] = (byte) (x9 >>> 24 & 0xff);


		/*String dbgt = "";
		for (int dbg = 0; dbg < o.length; dbg ++) dbgt += " "+o[dbg];
		Log.d(TAG, "core_hsalsa20 -> "+dbgt);
*/
    }

    public static int crypto_core_salsa20(byte [] out, byte [] in, byte [] k, byte [] c)
    {
        ///core(out,in,k,c,0);
        core_salsa20(out,in,k,c);

        ///String dbgt = "";
        ///for (int dbg = 0; dbg < out.length; dbg ++) dbgt += " "+out[dbg];
        ///L/og.d(TAG, "crypto_core_salsa20 -> "+dbgt);

        return 0;
    }

    public static int crypto_core_hsalsa20(byte [] out, byte [] in, byte [] k, byte [] c)
    {
        ///core(out,in,k,c,1);
        core_hsalsa20(out,in,k,c);

        ///String dbgt = "";
        ///for (int dbg = 0; dbg < out.length; dbg ++) dbgt += " "+out[dbg];
        ///L/og.d(TAG, "crypto_core_hsalsa20 -> "+dbgt);

        return 0;
    }

    // "expand 32-byte k"
    private static final byte[] sigma = {101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107};
	
	/*static {
		try {
			sigma = "expand 32-byte k".getBytes("utf-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}*/

    private static int crypto_stream_salsa20_xor(byte [] c,int cpos, byte [] m,int mpos, long b, byte [] n, byte [] k)
    {
        byte [] z = new byte[16], x = new byte[64];
        int u, i;
        for (i = 0; i < 16; i++) z[i] = 0;
        for (i = 0; i < 8; i++) z[i] = n[i];
        while (b >= 64) {
            crypto_core_salsa20(x,z,k,sigma);
            for (i = 0; i < 64; i++) c[cpos+i] = (byte) ((m[mpos+i] ^ x[i]) & 0xff);
            u = 1;
            for (i = 8; i < 16; i++) {
                u = u + (z[i] & 0xff) | 0;
                z[i] = (byte) (u & 0xff);
                u >>>= 8;
            }
            b -= 64;
            cpos += 64;
            mpos += 64;
        }
        if (b > 0) {
            crypto_core_salsa20(x,z,k,sigma);
            for (i = 0; i < b; i++) c[cpos+i] = (byte) ((m[mpos+i] ^ x[i]) & 0xff);
        }

        ///String dbgt = "";
        ///for (int dbg = 0; dbg < c.length-cpos; dbg ++) dbgt += " "+c[dbg +cpos];
        ///Log.d(TAG, "crypto_stream_salsa20_xor, c -> "+dbgt);

        return 0;
    }

    public static int crypto_stream_salsa20(byte [] c,int cpos, long b, byte [] n, byte [] k) {
        byte [] z = new byte[16], x = new byte[64];
        int u, i;
        for (i = 0; i < 16; i++) z[i] = 0;
        for (i = 0; i < 8; i++) z[i] = n[i];
        while (b >= 64) {
            crypto_core_salsa20(x,z,k,sigma);
            for (i = 0; i < 64; i++) c[cpos+i] = x[i];
            u = 1;
            for (i = 8; i < 16; i++) {
                u = u + (z[i] & 0xff) | 0;
                z[i] = (byte) (u & 0xff);
                u >>>= 8;
            }
            b -= 64;
            cpos += 64;
        }
        if (b > 0) {
            crypto_core_salsa20(x,z,k,sigma);
            for (i = 0; i < b; i++) c[cpos+i] = x[i];
        }

        ///String dbgt = "";
        ///for (int dbg = 0; dbg < c.length-cpos; dbg ++) dbgt += " "+c[dbg +cpos];
        ///Log.d(TAG, "crypto_stream_salsa20, c -> "+dbgt);

        return 0;
    }

    public static int  crypto_stream(byte [] c,int cpos, long d, byte [] n, byte [] k) {
        byte [] s = new byte[32];
        crypto_core_hsalsa20(s,n,k,sigma);
        byte [] sn = new byte[8];
        for (int i = 0; i < 8; i++) sn[i] = n[i+16];
        return crypto_stream_salsa20(c,cpos,d,sn,s);
    }

    public static int crypto_stream_xor(byte [] c,int cpos, byte [] m,int mpos, long d, byte [] n, byte [] k) {
        byte [] s = new byte[32];
		
		/*String dbgt = "";
		for (int dbg = 0; dbg < n.length; dbg ++) dbgt += " "+n[dbg];
		Log.d(TAG, "crypto_stream_xor, nonce -> "+dbgt);
		
		dbgt = "";
		for (int dbg = 0; dbg < k.length; dbg ++) dbgt += " "+k[dbg];
		Log.d(TAG, "crypto_stream_xor, shk -> "+dbgt);
		*/

        crypto_core_hsalsa20(s,n,k,sigma);
        byte [] sn = new byte[8];
        for (int i = 0; i < 8; i++) sn[i] = n[i+16];
        return crypto_stream_salsa20_xor(c,cpos,m,mpos,d,sn,s);
    }

    /*
     * Port of Andrew Moon's Poly1305-donna-16. Public domain.
     * https://github.com/floodyberry/poly1305-donna
     */
    public static final class poly1305 {

        private byte[] buffer;
        private int[] r;
        private int[] h;
        private int[] pad;
        private int leftover;
        private int fin;

        public poly1305(byte [] key) {
            this.buffer = new byte[16];
            this.r = new int[10];
            this.h = new int[10];
            this.pad = new int[8];
            this.leftover = 0;
            this.fin = 0;

            int t0, t1, t2, t3, t4, t5, t6, t7;

            t0 = key[ 0] & 0xff | (key[ 1] & 0xff) << 8; this.r[0] = ( t0                     ) & 0x1fff;
            t1 = key[ 2] & 0xff | (key[ 3] & 0xff) << 8; this.r[1] = ((t0 >>> 13) | (t1 <<  3)) & 0x1fff;
            t2 = key[ 4] & 0xff | (key[ 5] & 0xff) << 8; this.r[2] = ((t1 >>> 10) | (t2 <<  6)) & 0x1f03;
            t3 = key[ 6] & 0xff | (key[ 7] & 0xff) << 8; this.r[3] = ((t2 >>>  7) | (t3 <<  9)) & 0x1fff;
            t4 = key[ 8] & 0xff | (key[ 9] & 0xff) << 8; this.r[4] = ((t3 >>>  4) | (t4 << 12)) & 0x00ff;
            this.r[5] = ((t4 >>>  1)) & 0x1ffe;
            t5 = key[10] & 0xff | (key[11] & 0xff) << 8; this.r[6] = ((t4 >>> 14) | (t5 <<  2)) & 0x1fff;
            t6 = key[12] & 0xff | (key[13] & 0xff) << 8; this.r[7] = ((t5 >>> 11) | (t6 <<  5)) & 0x1f81;
            t7 = key[14] & 0xff | (key[15] & 0xff) << 8; this.r[8] = ((t6 >>>  8) | (t7 <<  8)) & 0x1fff;
            this.r[9] = ((t7 >>>  5)) & 0x007f;

            this.pad[0] = key[16] & 0xff | (key[17] & 0xff) << 8;
            this.pad[1] = key[18] & 0xff | (key[19] & 0xff) << 8;
            this.pad[2] = key[20] & 0xff | (key[21] & 0xff) << 8;
            this.pad[3] = key[22] & 0xff | (key[23] & 0xff) << 8;
            this.pad[4] = key[24] & 0xff | (key[25] & 0xff) << 8;
            this.pad[5] = key[26] & 0xff | (key[27] & 0xff) << 8;
            this.pad[6] = key[28] & 0xff | (key[29] & 0xff) << 8;
            this.pad[7] = key[30] & 0xff | (key[31] & 0xff) << 8;
        }

        public poly1305 blocks(byte [] m, int mpos, int bytes) {
            int hibit = this.fin!=0 ? 0 : (1 << 11);
            int t0, t1, t2, t3, t4, t5, t6, t7, c;
            int d0, d1, d2, d3, d4, d5, d6, d7, d8, d9;

            int     h0 = this.h[0],
                    h1 = this.h[1],
                    h2 = this.h[2],
                    h3 = this.h[3],
                    h4 = this.h[4],
                    h5 = this.h[5],
                    h6 = this.h[6],
                    h7 = this.h[7],
                    h8 = this.h[8],
                    h9 = this.h[9];

            int     r0 = this.r[0],
                    r1 = this.r[1],
                    r2 = this.r[2],
                    r3 = this.r[3],
                    r4 = this.r[4],
                    r5 = this.r[5],
                    r6 = this.r[6],
                    r7 = this.r[7],
                    r8 = this.r[8],
                    r9 = this.r[9];

            while (bytes >= 16) {
                t0 = m[mpos+ 0] & 0xff | (m[mpos+ 1] & 0xff) << 8; h0 += ( t0                     ) & 0x1fff;
                t1 = m[mpos+ 2] & 0xff | (m[mpos+ 3] & 0xff) << 8; h1 += ((t0 >>> 13) | (t1 <<  3)) & 0x1fff;
                t2 = m[mpos+ 4] & 0xff | (m[mpos+ 5] & 0xff) << 8; h2 += ((t1 >>> 10) | (t2 <<  6)) & 0x1fff;
                t3 = m[mpos+ 6] & 0xff | (m[mpos+ 7] & 0xff) << 8; h3 += ((t2 >>>  7) | (t3 <<  9)) & 0x1fff;
                t4 = m[mpos+ 8] & 0xff | (m[mpos+ 9] & 0xff) << 8; h4 += ((t3 >>>  4) | (t4 << 12)) & 0x1fff;
                h5 += ((t4 >>>  1)) & 0x1fff;
                t5 = m[mpos+10] & 0xff | (m[mpos+11] & 0xff) << 8; h6 += ((t4 >>> 14) | (t5 <<  2)) & 0x1fff;
                t6 = m[mpos+12] & 0xff | (m[mpos+13] & 0xff) << 8; h7 += ((t5 >>> 11) | (t6 <<  5)) & 0x1fff;
                t7 = m[mpos+14] & 0xff | (m[mpos+15] & 0xff) << 8; h8 += ((t6 >>>  8) | (t7 <<  8)) & 0x1fff;
                h9 += ((t7 >>> 5)) | hibit;

                c = 0;

                d0 = c;
                d0 += h0 * r0;
                d0 += h1 * (5 * r9);
                d0 += h2 * (5 * r8);
                d0 += h3 * (5 * r7);
                d0 += h4 * (5 * r6);
                c = (d0 >>> 13); d0 &= 0x1fff;
                d0 += h5 * (5 * r5);
                d0 += h6 * (5 * r4);
                d0 += h7 * (5 * r3);
                d0 += h8 * (5 * r2);
                d0 += h9 * (5 * r1);
                c += (d0 >>> 13); d0 &= 0x1fff;

                d1 = c;
                d1 += h0 * r1;
                d1 += h1 * r0;
                d1 += h2 * (5 * r9);
                d1 += h3 * (5 * r8);
                d1 += h4 * (5 * r7);
                c = (d1 >>> 13); d1 &= 0x1fff;
                d1 += h5 * (5 * r6);
                d1 += h6 * (5 * r5);
                d1 += h7 * (5 * r4);
                d1 += h8 * (5 * r3);
                d1 += h9 * (5 * r2);
                c += (d1 >>> 13); d1 &= 0x1fff;

                d2 = c;
                d2 += h0 * r2;
                d2 += h1 * r1;
                d2 += h2 * r0;
                d2 += h3 * (5 * r9);
                d2 += h4 * (5 * r8);
                c = (d2 >>> 13); d2 &= 0x1fff;
                d2 += h5 * (5 * r7);
                d2 += h6 * (5 * r6);
                d2 += h7 * (5 * r5);
                d2 += h8 * (5 * r4);
                d2 += h9 * (5 * r3);
                c += (d2 >>> 13); d2 &= 0x1fff;

                d3 = c;
                d3 += h0 * r3;
                d3 += h1 * r2;
                d3 += h2 * r1;
                d3 += h3 * r0;
                d3 += h4 * (5 * r9);
                c = (d3 >>> 13); d3 &= 0x1fff;
                d3 += h5 * (5 * r8);
                d3 += h6 * (5 * r7);
                d3 += h7 * (5 * r6);
                d3 += h8 * (5 * r5);
                d3 += h9 * (5 * r4);
                c += (d3 >>> 13); d3 &= 0x1fff;

                d4 = c;
                d4 += h0 * r4;
                d4 += h1 * r3;
                d4 += h2 * r2;
                d4 += h3 * r1;
                d4 += h4 * r0;
                c = (d4 >>> 13); d4 &= 0x1fff;
                d4 += h5 * (5 * r9);
                d4 += h6 * (5 * r8);
                d4 += h7 * (5 * r7);
                d4 += h8 * (5 * r6);
                d4 += h9 * (5 * r5);
                c += (d4 >>> 13); d4 &= 0x1fff;

                d5 = c;
                d5 += h0 * r5;
                d5 += h1 * r4;
                d5 += h2 * r3;
                d5 += h3 * r2;
                d5 += h4 * r1;
                c = (d5 >>> 13); d5 &= 0x1fff;
                d5 += h5 * r0;
                d5 += h6 * (5 * r9);
                d5 += h7 * (5 * r8);
                d5 += h8 * (5 * r7);
                d5 += h9 * (5 * r6);
                c += (d5 >>> 13); d5 &= 0x1fff;

                d6 = c;
                d6 += h0 * r6;
                d6 += h1 * r5;
                d6 += h2 * r4;
                d6 += h3 * r3;
                d6 += h4 * r2;
                c = (d6 >>> 13); d6 &= 0x1fff;
                d6 += h5 * r1;
                d6 += h6 * r0;
                d6 += h7 * (5 * r9);
                d6 += h8 * (5 * r8);
                d6 += h9 * (5 * r7);
                c += (d6 >>> 13); d6 &= 0x1fff;

                d7 = c;
                d7 += h0 * r7;
                d7 += h1 * r6;
                d7 += h2 * r5;
                d7 += h3 * r4;
                d7 += h4 * r3;
                c = (d7 >>> 13); d7 &= 0x1fff;
                d7 += h5 * r2;
                d7 += h6 * r1;
                d7 += h7 * r0;
                d7 += h8 * (5 * r9);
                d7 += h9 * (5 * r8);
                c += (d7 >>> 13); d7 &= 0x1fff;

                d8 = c;
                d8 += h0 * r8;
                d8 += h1 * r7;
                d8 += h2 * r6;
                d8 += h3 * r5;
                d8 += h4 * r4;
                c = (d8 >>> 13); d8 &= 0x1fff;
                d8 += h5 * r3;
                d8 += h6 * r2;
                d8 += h7 * r1;
                d8 += h8 * r0;
                d8 += h9 * (5 * r9);
                c += (d8 >>> 13); d8 &= 0x1fff;

                d9 = c;
                d9 += h0 * r9;
                d9 += h1 * r8;
                d9 += h2 * r7;
                d9 += h3 * r6;
                d9 += h4 * r5;
                c = (d9 >>> 13); d9 &= 0x1fff;
                d9 += h5 * r4;
                d9 += h6 * r3;
                d9 += h7 * r2;
                d9 += h8 * r1;
                d9 += h9 * r0;
                c += (d9 >>> 13); d9 &= 0x1fff;

                c = (((c << 2) + c)) | 0;
                c = (c + d0) | 0;
                d0 = c & 0x1fff;
                c = (c >>> 13);
                d1 += c;

                h0 = d0;
                h1 = d1;
                h2 = d2;
                h3 = d3;
                h4 = d4;
                h5 = d5;
                h6 = d6;
                h7 = d7;
                h8 = d8;
                h9 = d9;

                mpos += 16;
                bytes -= 16;
            }
            this.h[0] = h0;
            this.h[1] = h1;
            this.h[2] = h2;
            this.h[3] = h3;
            this.h[4] = h4;
            this.h[5] = h5;
            this.h[6] = h6;
            this.h[7] = h7;
            this.h[8] = h8;
            this.h[9] = h9;

            return this;
        }

        public poly1305 finish(byte [] mac, int macpos) {
            int [] g = new int[10];
            int c, mask, f, i;

            if (this.leftover != 0) {
                i = this.leftover;
                this.buffer[i++] = 1;
                for (; i < 16; i++) this.buffer[i] = 0;
                this.fin = 1;
                this.blocks(this.buffer, 0, 16);
            }

            c = this.h[1] >>> 13;
            this.h[1] &= 0x1fff;
            for (i = 2; i < 10; i++) {
                this.h[i] += c;
                c = this.h[i] >>> 13;
                this.h[i] &= 0x1fff;
            }
            this.h[0] += (c * 5);
            c = this.h[0] >>> 13;
            this.h[0] &= 0x1fff;
            this.h[1] += c;
            c = this.h[1] >>> 13;
            this.h[1] &= 0x1fff;
            this.h[2] += c;

            g[0] = this.h[0] + 5;
            c = g[0] >>> 13;
            g[0] &= 0x1fff;
            for (i = 1; i < 10; i++) {
                g[i] = this.h[i] + c;
                c = g[i] >>> 13;
                g[i] &= 0x1fff;
            }
            g[9] -= (1 << 13); g[9] &= 0xffff;

                        /*
                        backport from tweetnacl-fast.js https://github.com/dchest/tweetnacl-js/releases/tag/v0.14.3
                        <<<
                        "The issue was not properly detecting if st->h was >= 2^130 - 5, 
                        coupled with [testing mistake] not catching the failure.
                        The chance of the bug affecting anything in the real world is essentially zero luckily, 
                        but it's good to have it fixed."
                        >>>
                        */
            ///change mask = (g[9] >>> ((2 * 8) - 1)) - 1; to as
            mask = (c ^ 1) - 1;
            mask &= 0xffff;
            ///////////////////////////////////////

            for (i = 0; i < 10; i++) g[i] &= mask;
            mask = ~mask;
            for (i = 0; i < 10; i++) this.h[i] = (this.h[i] & mask) | g[i];

            this.h[0] = ((this.h[0]       ) | (this.h[1] << 13)                    ) & 0xffff;
            this.h[1] = ((this.h[1] >>>  3) | (this.h[2] << 10)                    ) & 0xffff;
            this.h[2] = ((this.h[2] >>>  6) | (this.h[3] <<  7)                    ) & 0xffff;
            this.h[3] = ((this.h[3] >>>  9) | (this.h[4] <<  4)                    ) & 0xffff;
            this.h[4] = ((this.h[4] >>> 12) | (this.h[5] <<  1) | (this.h[6] << 14)) & 0xffff;
            this.h[5] = ((this.h[6] >>>  2) | (this.h[7] << 11)                    ) & 0xffff;
            this.h[6] = ((this.h[7] >>>  5) | (this.h[8] <<  8)                    ) & 0xffff;
            this.h[7] = ((this.h[8] >>>  8) | (this.h[9] <<  5)                    ) & 0xffff;

            f = this.h[0] + this.pad[0];
            this.h[0] = f & 0xffff;
            for (i = 1; i < 8; i++) {
                f = (((this.h[i] + this.pad[i]) | 0) + (f >>> 16)) | 0;
                this.h[i] = f & 0xffff;
            }

            mac[macpos+ 0] = (byte) ((this.h[0] >>> 0) & 0xff);
            mac[macpos+ 1] = (byte) ((this.h[0] >>> 8) & 0xff);
            mac[macpos+ 2] = (byte) ((this.h[1] >>> 0) & 0xff);
            mac[macpos+ 3] = (byte) ((this.h[1] >>> 8) & 0xff);
            mac[macpos+ 4] = (byte) ((this.h[2] >>> 0) & 0xff);
            mac[macpos+ 5] = (byte) ((this.h[2] >>> 8) & 0xff);
            mac[macpos+ 6] = (byte) ((this.h[3] >>> 0) & 0xff);
            mac[macpos+ 7] = (byte) ((this.h[3] >>> 8) & 0xff);
            mac[macpos+ 8] = (byte) ((this.h[4] >>> 0) & 0xff);
            mac[macpos+ 9] = (byte) ((this.h[4] >>> 8) & 0xff);
            mac[macpos+10] = (byte) ((this.h[5] >>> 0) & 0xff);
            mac[macpos+11] = (byte) ((this.h[5] >>> 8) & 0xff);
            mac[macpos+12] = (byte) ((this.h[6] >>> 0) & 0xff);
            mac[macpos+13] = (byte) ((this.h[6] >>> 8) & 0xff);
            mac[macpos+14] = (byte) ((this.h[7] >>> 0) & 0xff);
            mac[macpos+15] = (byte) ((this.h[7] >>> 8) & 0xff);

            return this;
        }

        public poly1305 update(byte [] m, int mpos, int bytes) {
            int i, want;

            if (this.leftover != 0) {
                want = (16 - this.leftover);
                if (want > bytes)
                    want = bytes;
                for (i = 0; i < want; i++)
                    this.buffer[this.leftover + i] = m[mpos+i];
                bytes -= want;
                mpos += want;
                this.leftover += want;
                if (this.leftover < 16)
                    return this;
                this.blocks(buffer, 0, 16);
                this.leftover = 0;
            }

            if (bytes >= 16) {
                want = bytes - (bytes % 16);
                this.blocks(m, mpos, want);
                mpos += want;
                bytes -= want;
            }

            if (bytes != 0) {
                for (i = 0; i < bytes; i++)
                    this.buffer[this.leftover + i] = m[mpos+i];
                this.leftover += bytes;
            }

            return this;
        }

    }

    private static int crypto_onetimeauth(
            byte[] out,final int outpos,
            byte[] m,final int mpos,
            int n,
            byte [] k)
    {
        poly1305 s = new poly1305(k);
        s.update(m, mpos, n);
        s.finish(out, outpos);
		
		/*String dbgt = "";
		for (int dbg = 0; dbg < out.length-outpos; dbg ++) dbgt += " "+out[dbg+outpos];
		Log.d(TAG, "crypto_onetimeauth -> "+dbgt);
		*/

        return 0;
    }
    public static int crypto_onetimeauth(byte [] out, byte [] m, int /*long*/ n , byte [] k) {
        return crypto_onetimeauth(out,0, m,0, n, k);
    }

    private static int crypto_onetimeauth_verify(
            byte[] h,final int hoff,
            byte[] m,final int moff,
            int /*long*/ n,
            byte [] k)
    {
        byte [] x = new byte[16];
        crypto_onetimeauth(x,0,m,moff,n,k);
        return crypto_verify_16(h,hoff,x,0);
    }
    public static int crypto_onetimeauth_verify(byte [] h, byte [] m, int /*long*/ n, byte [] k) {
        return crypto_onetimeauth_verify(h,0, m,0, n, k);
    }
    public static int crypto_onetimeauth_verify(byte [] h, byte [] m, byte [] k) {
        return crypto_onetimeauth_verify(h, m, m!=null? m.length:0, k);
    }

    public static int crypto_secretbox(byte [] c, byte [] m, int /*long*/ d, byte [] n, byte [] k)
    {
        int i;
        if (d < 32) return -1;
        crypto_stream_xor(c,0,m,0,d,n,k);
        crypto_onetimeauth(c,16, c,32, d-32, c);
        ///for (i = 0; i < 16; i++) c[i] = 0;
        return 0;
    }

    public static int crypto_secretbox_open(byte []m,byte []c,int /*long*/ d,byte []n,byte []k)
    {
        int i;
        byte[] x = new byte[32];
        if (d < 32) return -1;
        crypto_stream(x,0,32,n,k);
        if (crypto_onetimeauth_verify(c,16, c,32, d-32, x) != 0) return -1;
        crypto_stream_xor(m,0,c,0,d,n,k);
        ///for (i = 0; i < 32; i++) m[i] = 0;
        return 0;
    }

    private static void set25519(long [] r, long [] a)
    {
        int i;
        for (i = 0; i < 16; i ++) r[i]=a[i];
    }

    private static void car25519(long [] o)
    {
        int i;
        long v, c = 1;
        for (i = 0; i < 16; i++) {
            v = o[i] + c + 65535;
            c = v>>16;
            o[i] = v - c * 65536;
        }
        o[0] += c-1 + 37 * (c-1);
    }

    private static void sel25519(
            long[] p,
            long[] q,
            int b)
    {
        sel25519(p,0, q,0, b);
    }
    private static void sel25519(
            long[] p,final int poff,
            long[] q,final int qoff,
            int b)
    {
        long t, c = ~(b-1);
        for (int i = 0; i < 16; i++) {
            t = c & (p[i+poff] ^ q[i+qoff]);
            p[i+poff] ^= t;
            q[i+qoff] ^= t;
        }
    }

    private static void pack25519(byte [] o, long [] n,final int noff)
    {
        int i, j, b;
        long [] m = new long[16], t = new long[16];
        for (i = 0; i < 16; i++) t[i] = n[i+noff];
        car25519(t);
        car25519(t);
        car25519(t);
        for (j = 0; j < 2; j++) {
            m[0] = t[0] - 0xffed;
            for (i = 1; i < 15; i++) {
                m[i] = t[i] - 0xffff - ((m[i-1]>>16) & 1);
                m[i-1] &= 0xffff;
            }
            m[15] = t[15] - 0x7fff - ((m[14]>>16) & 1);
            b = (int) ((m[15]>>16) & 1);
            m[14] &= 0xffff;
            sel25519(t,0, m,0, 1-b);
        }
        for (i = 0; i < 16; i++) {
            o[2*i] = (byte) (t[i] & 0xff);
            o[2*i+1] = (byte) (t[i]>>8);
        }
    }

    private static int neq25519(long [] a, long [] b) {
        return neq25519(a,0, b,0);
    }
    private static int neq25519(long [] a,final int aoff, long [] b,final int boff)
    {
        byte [] c = new byte[32], d = new byte[32];
        pack25519(c, a,aoff);
        pack25519(d, b,boff);
        return crypto_verify_32(c, 0, d, 0);
    }

    private static byte par25519(long [] a)
    {
        return par25519(a,0);
    }
    private static byte par25519(long [] a,final int aoff)
    {
        byte [] d = new byte[32];
        pack25519(d, a,aoff);
        return (byte) (d[0] & 1);
    }

    private static void unpack25519(long [] o, byte [] n)
    {
        int i;
        for (i = 0; i < 16; i ++) o[i]=(n[2*i]&0xff)+((long)((n[2*i+1]<<8)&0xffff));
        o[15] &= 0x7fff;
    }

    private static void A(
            long [] o,
            long [] a,
            long [] b)
    {
        A(o,0, a,0, b,0);
    }
    private static void A(
            long [] o,final int ooff,
            long [] a,final int aoff,
            long [] b,final int boff)
    {
        int i;
        for (i = 0; i < 16; i ++) o[i+ooff] = a[i+aoff] + b[i+boff];
    }

    private static void Z(
            long [] o,
            long [] a,
            long [] b)
    {
        Z(o,0, a,0, b,0);
    }
    private static void Z(
            long [] o,final int ooff,
            long [] a,final int aoff,
            long [] b,final int boff)
    {
        int i;
        for (i = 0; i < 16; i ++) o[i+ooff] = a[i+aoff] - b[i+boff];
    }

    private static void M(
            long [] o,
            long [] a,
            long [] b)
    {
        M(o,0, a,0, b,0);
    }
    private static void M(
            long [] o,final int ooff,
            long [] a,final int aoff,
            long [] b,final int boff)
    {
        long v, c,
                t0 = 0,  t1 = 0,  t2 = 0,  t3 = 0,  t4 = 0,  t5 = 0,  t6 = 0,  t7 = 0,
                t8 = 0,  t9 = 0, t10 = 0, t11 = 0, t12 = 0, t13 = 0, t14 = 0, t15 = 0,
                t16 = 0, t17 = 0, t18 = 0, t19 = 0, t20 = 0, t21 = 0, t22 = 0, t23 = 0,
                t24 = 0, t25 = 0, t26 = 0, t27 = 0, t28 = 0, t29 = 0, t30 = 0,
                b0 = b[0 +boff],
                b1 = b[1 +boff],
                b2 = b[2 +boff],
                b3 = b[3 +boff],
                b4 = b[4 +boff],
                b5 = b[5 +boff],
                b6 = b[6 +boff],
                b7 = b[7 +boff],
                b8 = b[8 +boff],
                b9 = b[9 +boff],
                b10 = b[10 +boff],
                b11 = b[11 +boff],
                b12 = b[12 +boff],
                b13 = b[13 +boff],
                b14 = b[14 +boff],
                b15 = b[15 +boff];

        v = a[0 +aoff];
        t0 += v * b0;
        t1 += v * b1;
        t2 += v * b2;
        t3 += v * b3;
        t4 += v * b4;
        t5 += v * b5;
        t6 += v * b6;
        t7 += v * b7;
        t8 += v * b8;
        t9 += v * b9;
        t10 += v * b10;
        t11 += v * b11;
        t12 += v * b12;
        t13 += v * b13;
        t14 += v * b14;
        t15 += v * b15;
        v = a[1 +aoff];
        t1 += v * b0;
        t2 += v * b1;
        t3 += v * b2;
        t4 += v * b3;
        t5 += v * b4;
        t6 += v * b5;
        t7 += v * b6;
        t8 += v * b7;
        t9 += v * b8;
        t10 += v * b9;
        t11 += v * b10;
        t12 += v * b11;
        t13 += v * b12;
        t14 += v * b13;
        t15 += v * b14;
        t16 += v * b15;
        v = a[2 +aoff];
        t2 += v * b0;
        t3 += v * b1;
        t4 += v * b2;
        t5 += v * b3;
        t6 += v * b4;
        t7 += v * b5;
        t8 += v * b6;
        t9 += v * b7;
        t10 += v * b8;
        t11 += v * b9;
        t12 += v * b10;
        t13 += v * b11;
        t14 += v * b12;
        t15 += v * b13;
        t16 += v * b14;
        t17 += v * b15;
        v = a[3 +aoff];
        t3 += v * b0;
        t4 += v * b1;
        t5 += v * b2;
        t6 += v * b3;
        t7 += v * b4;
        t8 += v * b5;
        t9 += v * b6;
        t10 += v * b7;
        t11 += v * b8;
        t12 += v * b9;
        t13 += v * b10;
        t14 += v * b11;
        t15 += v * b12;
        t16 += v * b13;
        t17 += v * b14;
        t18 += v * b15;
        v = a[4 +aoff];
        t4 += v * b0;
        t5 += v * b1;
        t6 += v * b2;
        t7 += v * b3;
        t8 += v * b4;
        t9 += v * b5;
        t10 += v * b6;
        t11 += v * b7;
        t12 += v * b8;
        t13 += v * b9;
        t14 += v * b10;
        t15 += v * b11;
        t16 += v * b12;
        t17 += v * b13;
        t18 += v * b14;
        t19 += v * b15;
        v = a[5 +aoff];
        t5 += v * b0;
        t6 += v * b1;
        t7 += v * b2;
        t8 += v * b3;
        t9 += v * b4;
        t10 += v * b5;
        t11 += v * b6;
        t12 += v * b7;
        t13 += v * b8;
        t14 += v * b9;
        t15 += v * b10;
        t16 += v * b11;
        t17 += v * b12;
        t18 += v * b13;
        t19 += v * b14;
        t20 += v * b15;
        v = a[6 +aoff];
        t6 += v * b0;
        t7 += v * b1;
        t8 += v * b2;
        t9 += v * b3;
        t10 += v * b4;
        t11 += v * b5;
        t12 += v * b6;
        t13 += v * b7;
        t14 += v * b8;
        t15 += v * b9;
        t16 += v * b10;
        t17 += v * b11;
        t18 += v * b12;
        t19 += v * b13;
        t20 += v * b14;
        t21 += v * b15;
        v = a[7 +aoff];
        t7 += v * b0;
        t8 += v * b1;
        t9 += v * b2;
        t10 += v * b3;
        t11 += v * b4;
        t12 += v * b5;
        t13 += v * b6;
        t14 += v * b7;
        t15 += v * b8;
        t16 += v * b9;
        t17 += v * b10;
        t18 += v * b11;
        t19 += v * b12;
        t20 += v * b13;
        t21 += v * b14;
        t22 += v * b15;
        v = a[8 +aoff];
        t8 += v * b0;
        t9 += v * b1;
        t10 += v * b2;
        t11 += v * b3;
        t12 += v * b4;
        t13 += v * b5;
        t14 += v * b6;
        t15 += v * b7;
        t16 += v * b8;
        t17 += v * b9;
        t18 += v * b10;
        t19 += v * b11;
        t20 += v * b12;
        t21 += v * b13;
        t22 += v * b14;
        t23 += v * b15;
        v = a[9 +aoff];
        t9 += v * b0;
        t10 += v * b1;
        t11 += v * b2;
        t12 += v * b3;
        t13 += v * b4;
        t14 += v * b5;
        t15 += v * b6;
        t16 += v * b7;
        t17 += v * b8;
        t18 += v * b9;
        t19 += v * b10;
        t20 += v * b11;
        t21 += v * b12;
        t22 += v * b13;
        t23 += v * b14;
        t24 += v * b15;
        v = a[10 +aoff];
        t10 += v * b0;
        t11 += v * b1;
        t12 += v * b2;
        t13 += v * b3;
        t14 += v * b4;
        t15 += v * b5;
        t16 += v * b6;
        t17 += v * b7;
        t18 += v * b8;
        t19 += v * b9;
        t20 += v * b10;
        t21 += v * b11;
        t22 += v * b12;
        t23 += v * b13;
        t24 += v * b14;
        t25 += v * b15;
        v = a[11 +aoff];
        t11 += v * b0;
        t12 += v * b1;
        t13 += v * b2;
        t14 += v * b3;
        t15 += v * b4;
        t16 += v * b5;
        t17 += v * b6;
        t18 += v * b7;
        t19 += v * b8;
        t20 += v * b9;
        t21 += v * b10;
        t22 += v * b11;
        t23 += v * b12;
        t24 += v * b13;
        t25 += v * b14;
        t26 += v * b15;
        v = a[12 +aoff];
        t12 += v * b0;
        t13 += v * b1;
        t14 += v * b2;
        t15 += v * b3;
        t16 += v * b4;
        t17 += v * b5;
        t18 += v * b6;
        t19 += v * b7;
        t20 += v * b8;
        t21 += v * b9;
        t22 += v * b10;
        t23 += v * b11;
        t24 += v * b12;
        t25 += v * b13;
        t26 += v * b14;
        t27 += v * b15;
        v = a[13 +aoff];
        t13 += v * b0;
        t14 += v * b1;
        t15 += v * b2;
        t16 += v * b3;
        t17 += v * b4;
        t18 += v * b5;
        t19 += v * b6;
        t20 += v * b7;
        t21 += v * b8;
        t22 += v * b9;
        t23 += v * b10;
        t24 += v * b11;
        t25 += v * b12;
        t26 += v * b13;
        t27 += v * b14;
        t28 += v * b15;
        v = a[14 +aoff];
        t14 += v * b0;
        t15 += v * b1;
        t16 += v * b2;
        t17 += v * b3;
        t18 += v * b4;
        t19 += v * b5;
        t20 += v * b6;
        t21 += v * b7;
        t22 += v * b8;
        t23 += v * b9;
        t24 += v * b10;
        t25 += v * b11;
        t26 += v * b12;
        t27 += v * b13;
        t28 += v * b14;
        t29 += v * b15;
        v = a[15 +aoff];
        t15 += v * b0;
        t16 += v * b1;
        t17 += v * b2;
        t18 += v * b3;
        t19 += v * b4;
        t20 += v * b5;
        t21 += v * b6;
        t22 += v * b7;
        t23 += v * b8;
        t24 += v * b9;
        t25 += v * b10;
        t26 += v * b11;
        t27 += v * b12;
        t28 += v * b13;
        t29 += v * b14;
        t30 += v * b15;

        t0  += 38 * t16;
        t1  += 38 * t17;
        t2  += 38 * t18;
        t3  += 38 * t19;
        t4  += 38 * t20;
        t5  += 38 * t21;
        t6  += 38 * t22;
        t7  += 38 * t23;
        t8  += 38 * t24;
        t9  += 38 * t25;
        t10 += 38 * t26;
        t11 += 38 * t27;
        t12 += 38 * t28;
        t13 += 38 * t29;
        t14 += 38 * t30;
        // t15 left as is

        // first car
        c = 1;
        v =  t0 + c + 65535; c = v >> 16;  t0 = v - c * 65536;
        v =  t1 + c + 65535; c = v >> 16;  t1 = v - c * 65536;
        v =  t2 + c + 65535; c = v >> 16;  t2 = v - c * 65536;
        v =  t3 + c + 65535; c = v >> 16;  t3 = v - c * 65536;
        v =  t4 + c + 65535; c = v >> 16;  t4 = v - c * 65536;
        v =  t5 + c + 65535; c = v >> 16;  t5 = v - c * 65536;
        v =  t6 + c + 65535; c = v >> 16;  t6 = v - c * 65536;
        v =  t7 + c + 65535; c = v >> 16;  t7 = v - c * 65536;
        v =  t8 + c + 65535; c = v >> 16;  t8 = v - c * 65536;
        v =  t9 + c + 65535; c = v >> 16;  t9 = v - c * 65536;
        v = t10 + c + 65535; c = v >> 16; t10 = v - c * 65536;
        v = t11 + c + 65535; c = v >> 16; t11 = v - c * 65536;
        v = t12 + c + 65535; c = v >> 16; t12 = v - c * 65536;
        v = t13 + c + 65535; c = v >> 16; t13 = v - c * 65536;
        v = t14 + c + 65535; c = v >> 16; t14 = v - c * 65536;
        v = t15 + c + 65535; c = v >> 16; t15 = v - c * 65536;
        t0 += c-1 + 37 * (c-1);

        // second car
        c = 1;
        v =  t0 + c + 65535; c = v >> 16;  t0 = v - c * 65536;
        v =  t1 + c + 65535; c = v >> 16;  t1 = v - c * 65536;
        v =  t2 + c + 65535; c = v >> 16;  t2 = v - c * 65536;
        v =  t3 + c + 65535; c = v >> 16;  t3 = v - c * 65536;
        v =  t4 + c + 65535; c = v >> 16;  t4 = v - c * 65536;
        v =  t5 + c + 65535; c = v >> 16;  t5 = v - c * 65536;
        v =  t6 + c + 65535; c = v >> 16;  t6 = v - c * 65536;
        v =  t7 + c + 65535; c = v >> 16;  t7 = v - c * 65536;
        v =  t8 + c + 65535; c = v >> 16;  t8 = v - c * 65536;
        v =  t9 + c + 65535; c = v >> 16;  t9 = v - c * 65536;
        v = t10 + c + 65535; c = v >> 16; t10 = v - c * 65536;
        v = t11 + c + 65535; c = v >> 16; t11 = v - c * 65536;
        v = t12 + c + 65535; c = v >> 16; t12 = v - c * 65536;
        v = t13 + c + 65535; c = v >> 16; t13 = v - c * 65536;
        v = t14 + c + 65535; c = v >> 16; t14 = v - c * 65536;
        v = t15 + c + 65535; c = v >> 16; t15 = v - c * 65536;
        t0 += c-1 + 37 * (c-1);

        o[ 0 +ooff] = t0;
        o[ 1 +ooff] = t1;
        o[ 2 +ooff] = t2;
        o[ 3 +ooff] = t3;
        o[ 4 +ooff] = t4;
        o[ 5 +ooff] = t5;
        o[ 6 +ooff] = t6;
        o[ 7 +ooff] = t7;
        o[ 8 +ooff] = t8;
        o[ 9 +ooff] = t9;
        o[10 +ooff] = t10;
        o[11 +ooff] = t11;
        o[12 +ooff] = t12;
        o[13 +ooff] = t13;
        o[14 +ooff] = t14;
        o[15 +ooff] = t15;
    }

    private static void S(
            long [] o,
            long [] a)
    {
        S(o,0, a,0);
    }
    private static void S(
            long [] o,final int ooff,
            long [] a,final int aoff)
    {
        M(o,ooff, a,aoff, a,aoff);
    }

    private static void inv25519(
            long [] o,final int ooff,
            long [] i,final int ioff)
    {
        long [] c = new long[16];
        int a;
        for (a = 0; a < 16; a++) c[a] = i[a+ioff];
        for (a = 253; a >= 0; a--) {
            S(c,0, c,0);
            if(a != 2 && a != 4) M(c,0, c,0, i,ioff);
        }
        for (a = 0; a < 16; a++) o[a+ooff] = c[a];
    }

    private static void pow2523(long [] o,long [] i)
    {
        long [] c = new long[16];
        int a;

        for (a = 0; a < 16; a ++) c[a]=i[a];

        for(a=250;a>=0;a--) {
            S(c,0, c,0);
            if(a!=1) M(c,0, c,0, i,0);
        }

        for (a = 0; a < 16; a ++) o[a]=c[a];
    }

    public static int crypto_scalarmult(byte []q,byte []n,byte []p)
    {
        byte [] z = new byte[32];
        long [] x = new long[80];
        int r, i;
        long [] a = new long[16], b = new long[16], c = new long[16],
                d = new long[16], e = new long[16], f = new long[16];
        for (i = 0; i < 31; i++) z[i] = n[i];
        z[31]=(byte) (((n[31]&127)|64) & 0xff);
        z[0]&=248;
        unpack25519(x,p);
        for (i = 0; i < 16; i++) {
            b[i]=x[i];
            d[i]=a[i]=c[i]=0;
        }
        a[0]=d[0]=1;
        for (i=254;i>=0;--i) {
            r=(z[i>>>3]>>>(i&7))&1;
            sel25519(a,b,r);
            sel25519(c,d,r);
            A(e,a,c);
            Z(a,a,c);
            A(c,b,d);
            Z(b,b,d);
            S(d,e);
            S(f,a);
            M(a,c,a);
            M(c,b,e);
            A(e,a,c);
            Z(a,a,c);
            S(b,a);
            Z(c,d,f);
            M(a,c,_121665);
            A(a,a,d);
            M(c,c,a);
            M(a,d,f);
            M(d,b,x);
            S(b,e);
            sel25519(a,b,r);
            sel25519(c,d,r);
        }
        for (i = 0; i < 16; i++) {
            x[i+16]=a[i];
            x[i+32]=c[i];
            x[i+48]=b[i];
            x[i+64]=d[i];
        }
        inv25519(x,32, x,32);
        M(x,16, x,16, x,32);
        pack25519(q, x,16);

        return 0;
    }

    public static int crypto_scalarmult_base(byte []q,byte []n)
    {
        return crypto_scalarmult(q,n,_9);
    }

    public static int crypto_box_keypair(byte [] y, byte [] x)
    {
        randombytes(x,32);
        return crypto_scalarmult_base(y,x);
    }

    public static int crypto_box_beforenm(byte []k,byte []y,byte []x)
    {
        byte[] s = new byte[32];
        crypto_scalarmult(s,x,y);

		/*String dbgt = "";
		for (int dbg = 0; dbg < s.length; dbg ++) dbgt += " "+s[dbg];
		Log.d(TAG, "crypto_box_beforenm -> "+dbgt);
		
	    dbgt = "";
		for (int dbg = 0; dbg < x.length; dbg ++) dbgt += " "+x[dbg];
		Log.d(TAG, "crypto_box_beforenm, x -> "+dbgt);

	    dbgt = "";
		for (int dbg = 0; dbg < y.length; dbg ++) dbgt += " "+y[dbg];
		Log.d(TAG, "crypto_box_beforenm, y -> "+dbgt);
		*/

        return crypto_core_hsalsa20(k, _0, s, sigma);
    }

    public static int crypto_box_afternm(byte []c,byte []m,int /*long*/ d,byte []n,byte []k)
    {
        return crypto_secretbox(c,m,d,n,k);
    }

    public static int crypto_box_open_afternm(byte []m,byte []c,int /*long*/ d,byte []n,byte []k)
    {
        return crypto_secretbox_open(m,c,d,n,k);
    }

    public static int crypto_box(byte []c,byte []m,int /*long*/ d,byte []n,byte []y,byte []x)
    {
        byte[] k = new byte[32];

        ///L/og.d(TAG, "crypto_box start ...");

        crypto_box_beforenm(k,y,x);
        return crypto_box_afternm(c,m,d,n,k);
    }

    public static int crypto_box_open(byte []m,byte []c,int /*long*/ d,byte []n,byte []y,byte []x)
    {
        byte[] k = new byte[32];
        crypto_box_beforenm(k,y,x);
        return crypto_box_open_afternm(m,c,d,n,k);
    }

    private static final long K[] = {
            0x428a2f98d728ae22L, 0x7137449123ef65cdL, 0xb5c0fbcfec4d3b2fL, 0xe9b5dba58189dbbcL,
            0x3956c25bf348b538L, 0x59f111f1b605d019L, 0x923f82a4af194f9bL, 0xab1c5ed5da6d8118L,
            0xd807aa98a3030242L, 0x12835b0145706fbeL, 0x243185be4ee4b28cL, 0x550c7dc3d5ffb4e2L,
            0x72be5d74f27b896fL, 0x80deb1fe3b1696b1L, 0x9bdc06a725c71235L, 0xc19bf174cf692694L,
            0xe49b69c19ef14ad2L, 0xefbe4786384f25e3L, 0x0fc19dc68b8cd5b5L, 0x240ca1cc77ac9c65L,
            0x2de92c6f592b0275L, 0x4a7484aa6ea6e483L, 0x5cb0a9dcbd41fbd4L, 0x76f988da831153b5L,
            0x983e5152ee66dfabL, 0xa831c66d2db43210L, 0xb00327c898fb213fL, 0xbf597fc7beef0ee4L,
            0xc6e00bf33da88fc2L, 0xd5a79147930aa725L, 0x06ca6351e003826fL, 0x142929670a0e6e70L,
            0x27b70a8546d22ffcL, 0x2e1b21385c26c926L, 0x4d2c6dfc5ac42aedL, 0x53380d139d95b3dfL,
            0x650a73548baf63deL, 0x766a0abb3c77b2a8L, 0x81c2c92e47edaee6L, 0x92722c851482353bL,
            0xa2bfe8a14cf10364L, 0xa81a664bbc423001L, 0xc24b8b70d0f89791L, 0xc76c51a30654be30L,
            0xd192e819d6ef5218L, 0xd69906245565a910L, 0xf40e35855771202aL, 0x106aa07032bbd1b8L,
            0x19a4c116b8d2d0c8L, 0x1e376c085141ab53L, 0x2748774cdf8eeb99L, 0x34b0bcb5e19b48a8L,
            0x391c0cb3c5c95a63L, 0x4ed8aa4ae3418acbL, 0x5b9cca4f7763e373L, 0x682e6ff3d6b2b8a3L,
            0x748f82ee5defb2fcL, 0x78a5636f43172f60L, 0x84c87814a1f0ab72L, 0x8cc702081a6439ecL,
            0x90befffa23631e28L, 0xa4506cebde82bde9L, 0xbef9a3f7b2c67915L, 0xc67178f2e372532bL,
            0xca273eceea26619cL, 0xd186b8c721c0c207L, 0xeada7dd6cde0eb1eL, 0xf57d4f7fee6ed178L,
            0x06f067aa72176fbaL, 0x0a637dc5a2c898a6L, 0x113f9804bef90daeL, 0x1b710b35131c471bL,
            0x28db77f523047d84L, 0x32caab7b40c72493L, 0x3c9ebe0a15c9bebcL, 0x431d67c49c100d4cL,
            0x4cc5d4becb3e42b6L, 0x597f299cfc657e2aL, 0x5fcb6fab3ad6faecL, 0x6c44198c4a475817L
    };

    private static int crypto_hashblocks_hl(int [] hh,int [] hl, byte [] m,final int moff, int n) {

        ///String dbgt = "";
        ///for (int dbg = 0; dbg < n; dbg ++) dbgt += " "+m[dbg+moff];
        ///Log.d(TAG, "crypto_hashblocks_hl m/"+n + "-> "+dbgt);

        int []  wh = new int[16], wl = new int[16];
        int     bh0, bh1, bh2, bh3, bh4, bh5, bh6, bh7,
                bl0, bl1, bl2, bl3, bl4, bl5, bl6, bl7,
                th, tl, h, l, i, j, a, b, c, d;

        int     ah0 = hh[0],
                ah1 = hh[1],
                ah2 = hh[2],
                ah3 = hh[3],
                ah4 = hh[4],
                ah5 = hh[5],
                ah6 = hh[6],
                ah7 = hh[7],

                al0 = hl[0],
                al1 = hl[1],
                al2 = hl[2],
                al3 = hl[3],
                al4 = hl[4],
                al5 = hl[5],
                al6 = hl[6],
                al7 = hl[7];

        int pos = 0;
        while (n >= 128) {
            for (i = 0; i < 16; i++) {
                j = 8 * i + pos;
                wh[i] = ((m[j+0+moff]&0xff) << 24) | ((m[j+1+moff]&0xff) << 16) | ((m[j+2+moff]&0xff) << 8) | ((m[j+3+moff]&0xff) << 0);
                wl[i] = ((m[j+4+moff]&0xff) << 24) | ((m[j+5+moff]&0xff) << 16) | ((m[j+6+moff]&0xff) << 8) | ((m[j+7+moff]&0xff) << 0);
            }
            for (i = 0; i < 80; i++) {
                bh0 = ah0;
                bh1 = ah1;
                bh2 = ah2;
                bh3 = ah3;
                bh4 = ah4;
                bh5 = ah5;
                bh6 = ah6;
                bh7 = ah7;

                bl0 = al0;
                bl1 = al1;
                bl2 = al2;
                bl3 = al3;
                bl4 = al4;
                bl5 = al5;
                bl6 = al6;
                bl7 = al7;

                // add
                h = ah7;
                l = al7;

                a = l & 0xffff; b = l >>> 16;
                c = h & 0xffff; d = h >>> 16;

                // Sigma1
                h = ((ah4 >>> 14) | (al4 << (32-14))) ^ ((ah4 >>> 18) | (al4 << (32-18))) ^ ((al4 >>> (41-32)) | (ah4 << (32-(41-32))));
                l = ((al4 >>> 14) | (ah4 << (32-14))) ^ ((al4 >>> 18) | (ah4 << (32-18))) ^ ((ah4 >>> (41-32)) | (al4 << (32-(41-32))));

                a += l & 0xffff; b += l >>> 16;
                c += h & 0xffff; d += h >>> 16;

                // Ch
                h = (ah4 & ah5) ^ (~ah4 & ah6);
                l = (al4 & al5) ^ (~al4 & al6);

                a += l & 0xffff; b += l >>> 16;
                c += h & 0xffff; d += h >>> 16;

                // K
                ///h = K[i*2];
                ///l = K[i*2+1];
                h = (int) ((K[i]>>>32) & 0xffffffff);
                l = (int) ((K[i]>>> 0) & 0xffffffff);

                ///Log.d(TAG, "i"+i + ",h:0x"+Integer.toHexString(h) + ",l:0x"+Integer.toHexString(l));

                a += l & 0xffff; b += l >>> 16;
                c += h & 0xffff; d += h >>> 16;

                // w
                h = wh[i%16];
                l = wl[i%16];

                a += l & 0xffff; b += l >>> 16;
                c += h & 0xffff; d += h >>> 16;

                b += a >>> 16;
                c += b >>> 16;
                d += c >>> 16;

                th = c & 0xffff | d << 16;
                tl = a & 0xffff | b << 16;

                // add
                h = th;
                l = tl;

                a = l & 0xffff; b = l >>> 16;
                c = h & 0xffff; d = h >>> 16;

                // Sigma0
                h = ((ah0 >>> 28) | (al0 << (32-28))) ^ ((al0 >>> (34-32)) | (ah0 << (32-(34-32)))) ^ ((al0 >>> (39-32)) | (ah0 << (32-(39-32))));
                l = ((al0 >>> 28) | (ah0 << (32-28))) ^ ((ah0 >>> (34-32)) | (al0 << (32-(34-32)))) ^ ((ah0 >>> (39-32)) | (al0 << (32-(39-32))));

                a += l & 0xffff; b += l >>> 16;
                c += h & 0xffff; d += h >>> 16;

                // Maj
                h = (ah0 & ah1) ^ (ah0 & ah2) ^ (ah1 & ah2);
                l = (al0 & al1) ^ (al0 & al2) ^ (al1 & al2);

                a += l & 0xffff; b += l >>> 16;
                c += h & 0xffff; d += h >>> 16;

                b += a >>> 16;
                c += b >>> 16;
                d += c >>> 16;

                bh7 = (c & 0xffff) | (d << 16);
                bl7 = (a & 0xffff) | (b << 16);

                // add
                h = bh3;
                l = bl3;

                a = l & 0xffff; b = l >>> 16;
                c = h & 0xffff; d = h >>> 16;

                h = th;
                l = tl;

                a += l & 0xffff; b += l >>> 16;
                c += h & 0xffff; d += h >>> 16;

                b += a >>> 16;
                c += b >>> 16;
                d += c >>> 16;

                bh3 = (c & 0xffff) | (d << 16);
                bl3 = (a & 0xffff) | (b << 16);

                ah1 = bh0;
                ah2 = bh1;
                ah3 = bh2;
                ah4 = bh3;
                ah5 = bh4;
                ah6 = bh5;
                ah7 = bh6;
                ah0 = bh7;

                al1 = bl0;
                al2 = bl1;
                al3 = bl2;
                al4 = bl3;
                al5 = bl4;
                al6 = bl5;
                al7 = bl6;
                al0 = bl7;

                if (i%16 == 15) {
                    for (j = 0; j < 16; j++) {
                        // add
                        h = wh[j];
                        l = wl[j];

                        a = l & 0xffff; b = l >>> 16;
                        c = h & 0xffff; d = h >>> 16;

                        h = wh[(j+9)%16];
                        l = wl[(j+9)%16];

                        a += l & 0xffff; b += l >>> 16;
                        c += h & 0xffff; d += h >>> 16;

                        // sigma0
                        th = wh[(j+1)%16];
                        tl = wl[(j+1)%16];
                        h = ((th >>> 1) | (tl << (32-1))) ^ ((th >>> 8) | (tl << (32-8))) ^ (th >>> 7);
                        l = ((tl >>> 1) | (th << (32-1))) ^ ((tl >>> 8) | (th << (32-8))) ^ ((tl >>> 7) | (th << (32-7)));

                        a += l & 0xffff; b += l >>> 16;
                        c += h & 0xffff; d += h >>> 16;

                        // sigma1
                        th = wh[(j+14)%16];
                        tl = wl[(j+14)%16];
                        h = ((th >>> 19) | (tl << (32-19))) ^ ((tl >>> (61-32)) | (th << (32-(61-32)))) ^ (th >>> 6);
                        l = ((tl >>> 19) | (th << (32-19))) ^ ((th >>> (61-32)) | (tl << (32-(61-32)))) ^ ((tl >>> 6) | (th << (32-6)));

                        a += l & 0xffff; b += l >>> 16;
                        c += h & 0xffff; d += h >>> 16;

                        b += a >>> 16;
                        c += b >>> 16;
                        d += c >>> 16;

                        wh[j] = (c & 0xffff) | (d << 16);
                        wl[j] = (a & 0xffff) | (b << 16);
                    }
                }
            }

            // add
            h = ah0;
            l = al0;

            a = l & 0xffff; b = l >>> 16;
            c = h & 0xffff; d = h >>> 16;

            h = hh[0];
            l = hl[0];

            a += l & 0xffff; b += l >>> 16;
            c += h & 0xffff; d += h >>> 16;

            b += a >>> 16;
            c += b >>> 16;
            d += c >>> 16;

            hh[0] = ah0 = (c & 0xffff) | (d << 16);
            hl[0] = al0 = (a & 0xffff) | (b << 16);

            h = ah1;
            l = al1;

            a = l & 0xffff; b = l >>> 16;
            c = h & 0xffff; d = h >>> 16;

            h = hh[1];
            l = hl[1];

            a += l & 0xffff; b += l >>> 16;
            c += h & 0xffff; d += h >>> 16;

            b += a >>> 16;
            c += b >>> 16;
            d += c >>> 16;

            hh[1] = ah1 = (c & 0xffff) | (d << 16);
            hl[1] = al1 = (a & 0xffff) | (b << 16);

            h = ah2;
            l = al2;

            a = l & 0xffff; b = l >>> 16;
            c = h & 0xffff; d = h >>> 16;

            h = hh[2];
            l = hl[2];

            a += l & 0xffff; b += l >>> 16;
            c += h & 0xffff; d += h >>> 16;

            b += a >>> 16;
            c += b >>> 16;
            d += c >>> 16;

            hh[2] = ah2 = (c & 0xffff) | (d << 16);
            hl[2] = al2 = (a & 0xffff) | (b << 16);

            h = ah3;
            l = al3;

            a = l & 0xffff; b = l >>> 16;
            c = h & 0xffff; d = h >>> 16;

            h = hh[3];
            l = hl[3];

            a += l & 0xffff; b += l >>> 16;
            c += h & 0xffff; d += h >>> 16;

            b += a >>> 16;
            c += b >>> 16;
            d += c >>> 16;

            hh[3] = ah3 = (c & 0xffff) | (d << 16);
            hl[3] = al3 = (a & 0xffff) | (b << 16);

            h = ah4;
            l = al4;

            a = l & 0xffff; b = l >>> 16;
            c = h & 0xffff; d = h >>> 16;

            h = hh[4];
            l = hl[4];

            a += l & 0xffff; b += l >>> 16;
            c += h & 0xffff; d += h >>> 16;

            b += a >>> 16;
            c += b >>> 16;
            d += c >>> 16;

            hh[4] = ah4 = (c & 0xffff) | (d << 16);
            hl[4] = al4 = (a & 0xffff) | (b << 16);

            h = ah5;
            l = al5;

            a = l & 0xffff; b = l >>> 16;
            c = h & 0xffff; d = h >>> 16;

            h = hh[5];
            l = hl[5];

            a += l & 0xffff; b += l >>> 16;
            c += h & 0xffff; d += h >>> 16;

            b += a >>> 16;
            c += b >>> 16;
            d += c >>> 16;

            hh[5] = ah5 = (c & 0xffff) | (d << 16);
            hl[5] = al5 = (a & 0xffff) | (b << 16);

            h = ah6;
            l = al6;

            a = l & 0xffff; b = l >>> 16;
            c = h & 0xffff; d = h >>> 16;

            h = hh[6];
            l = hl[6];

            a += l & 0xffff; b += l >>> 16;
            c += h & 0xffff; d += h >>> 16;

            b += a >>> 16;
            c += b >>> 16;
            d += c >>> 16;

            hh[6] = ah6 = (c & 0xffff) | (d << 16);
            hl[6] = al6 = (a & 0xffff) | (b << 16);

            h = ah7;
            l = al7;

            a = l & 0xffff; b = l >>> 16;
            c = h & 0xffff; d = h >>> 16;

            h = hh[7];
            l = hl[7];

            a += l & 0xffff; b += l >>> 16;
            c += h & 0xffff; d += h >>> 16;

            b += a >>> 16;
            c += b >>> 16;
            d += c >>> 16;

            hh[7] = ah7 = (c & 0xffff) | (d << 16);
            hl[7] = al7 = (a & 0xffff) | (b << 16);

            pos += 128;
            n -= 128;
				
				/*dbgt = "";
				for (int dbg = 0; dbg < hh.length; dbg ++) dbgt += " "+hh[dbg];
				Log.d(TAG, "\ncrypto_hashblocks_hl hh -> "+dbgt);
				
				dbgt = "";
				for (int dbg = 0; dbg < hl.length; dbg ++) dbgt += " "+hl[dbg];
				Log.d(TAG, "\ncrypto_hashblocks_hl hl -> "+dbgt);*/
        }

        return n;
    }

    // TBD 64bits of n
    ///int crypto_hash(byte [] out, byte [] m, long n)
    public static int crypto_hash(byte [] out, byte [] m,final int moff, int n)
    {
        int []  hh = new int[8],
                hl = new int[8];
        byte [] x = new byte[256];
        int     i, b = n;
        long    u;

        hh[0] = 0x6a09e667;
        hh[1] = 0xbb67ae85;
        hh[2] = 0x3c6ef372;
        hh[3] = 0xa54ff53a;
        hh[4] = 0x510e527f;
        hh[5] = 0x9b05688c;
        hh[6] = 0x1f83d9ab;
        hh[7] = 0x5be0cd19;

        hl[0] = 0xf3bcc908;
        hl[1] = 0x84caa73b;
        hl[2] = 0xfe94f82b;
        hl[3] = 0x5f1d36f1;
        hl[4] = 0xade682d1;
        hl[5] = 0x2b3e6c1f;
        hl[6] = 0xfb41bd6b;
        hl[7] = 0x137e2179;

        if (n >= 128) {
            crypto_hashblocks_hl(hh, hl, m,moff, n);
            n %= 128;
        }

        for (i = 0; i < n; i++) x[i] = m[b-n+i +moff];
        x[n] = (byte) 128;

        n = 256-128*(n<112?1:0);
        x[n-9] = 0;

        ts64(x, n-8,  b<<3/*(b / 0x20000000) | 0, b << 3*/);

        crypto_hashblocks_hl(hh, hl, x,0, n);

        for (i = 0; i < 8; i++) {
            u = hh[i]; u <<= 32; u |= hl[i]&0xffffffffL;
            ts64(out, 8*i, u);
        }

        return 0;
    }
    public static int crypto_hash(byte [] out, byte [] m) {
        return crypto_hash(out, m,0, m!=null? m.length : 0);
    }

    // gf: long[16]
    ///private static void add(gf p[4],gf q[4])
    private static void add(long [] p[], long [] q[])
    {
        long [] a = new long[16];
        long [] b = new long[16];
        long [] c = new long[16];
        long [] d = new long[16];
        long [] t = new long[16];
        long [] e = new long[16];
        long [] f = new long[16];
        long [] g = new long[16];
        long [] h = new long[16];


        long [] p0 = p[0];
        long [] p1 = p[1];
        long [] p2 = p[2];
        long [] p3 = p[3];

        long [] q0 = q[0];
        long [] q1 = q[1];
        long [] q2 = q[2];
        long [] q3 = q[3];

        Z(a,0, p1,0, p0,0);
        Z(t,0, q1,0, q0,0);
        M(a,0, a,0,   t,0);
        A(b,0, p0,0, p1,0);
        A(t,0, q0,0, q1,0);
        M(b,0, b,0,   t,0);
        M(c,0, p3,0, q3,0);
        M(c,0, c,0,  D2,0);
        M(d,0, p2,0, q2,0);

        A(d,0, d,0, d,0);
        Z(e,0, b,0, a,0);
        Z(f,0, d,0, c,0);
        A(g,0, d,0, c,0);
        A(h,0, b,0, a,0);

        M(p0,0, e,0, f,0);
        M(p1,0, h,0, g,0);
        M(p2,0, g,0, f,0);
        M(p3,0, e,0, h,0);
    }

    private static void cswap(long [] p[], long [] q[], byte b)
    {
        int i;

        for (i = 0; i < 4; i ++)
            sel25519(p[i],0, q[i],0, b);
    }

    private static void pack(byte [] r, long [] p[])
    {
        long [] tx = new long[16];
        long [] ty = new long[16];
        long [] zi = new long[16];

        inv25519(zi,0, p[2],0);

        M(tx,0, p[0],0, zi,0);
        M(ty,0, p[1],0, zi,0);

        pack25519(r, ty,0);

        r[31] ^= par25519(tx,0) << 7;
    }

    private static void scalarmult(long [] p[], long [] q[], byte[] s,final int soff)
    {
        int i;

        set25519(p[0],gf0);
        set25519(p[1],gf1);
        set25519(p[2],gf1);
        set25519(p[3],gf0);

        for (i = 255;i >= 0;--i) {
            byte b = (byte) ((s[i/8+soff] >>> (i&7))&1);

            cswap(p,q,b);
            add(q,p);
            add(p,p);
            cswap(p,q,b);
        }

        ///String dbgt = "";
        ///for (int dbg = 0; dbg < p.length; dbg ++) for (int dd = 0; dd < p[dbg].length; dd ++) dbgt += " "+p[dbg][dd];
        ///L/og.d(TAG, "scalarmult -> "+dbgt);
    }

    private static void scalarbase(long [] p[], byte[] s,final int soff)
    {
        long [] [] q = new long [4] [];

        q[0] = new long [16];
        q[1] = new long [16];
        q[2] = new long [16];
        q[3] = new long [16];

        set25519(q[0],X);
        set25519(q[1],Y);
        set25519(q[2],gf1);
        M(q[3],0, X,0, Y,0);
        scalarmult(p,q, s,soff);
    }

    public static int  crypto_sign_keypair(byte [] pk, byte [] sk, boolean seeded) {
        byte [] d = new byte[64];
        long [] [] p = new long [4] [];

        p[0] = new long [16];
        p[1] = new long [16];
        p[2] = new long [16];
        p[3] = new long [16];

        int i;

        if (!seeded) randombytes(sk, 32);
        crypto_hash(d, sk,0, 32);
        d[0]  &= 248;
        d[31] &= 127;
        d[31] |= 64;

        scalarbase(p, d,0);
        pack(pk, p);

        for (i = 0; i < 32; i++) sk[i+32] = pk[i];
        return 0;
    }

    private static final long L[] = {
            0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,
            0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
            0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0x10
    };

    private static void modL(byte[] r,final int roff, long x[])
    {
        long carry;
        int i, j;

        for (i = 63;i >= 32;--i) {
            carry = 0;
            for (j = i - 32;j < i - 12;++j) {
                x[j] += carry - 16 * x[i] * L[j - (i - 32)];
                carry = (x[j] + 128) >> 8;
                x[j] -= carry << 8;
            }
            x[j] += carry;
            x[i] = 0;
        }
        carry = 0;

        for (j = 0; j < 32; j ++) {
            x[j] += carry - (x[31] >> 4) * L[j];
            carry = x[j] >> 8;
            x[j] &= 255;
        }

        for (j = 0; j < 32; j ++) x[j] -= carry * L[j];

        for (i = 0; i < 32; i ++) {
            x[i+1] += x[i] >> 8;
            r[i+roff] = (byte) (x[i] & 255);
        }
    }

    private static void reduce(byte [] r)
    {
        long[] x = new long [64];
        int i;

        for (i = 0; i < 64; i ++) x[i] = (long) (r[i]&0xff);

        for (i = 0; i < 64; i ++) r[i] = 0;

        modL(r,0, x);
    }

    // TBD... 64bits of n
    ///int crypto_sign(byte [] sm, long * smlen, byte [] m, long n, byte [] sk)
    public static int crypto_sign(byte [] sm, long dummy /* *smlen not used*/, byte [] m,final int moff, int/*long*/ n, byte [] sk)
    {
        byte[] d = new byte[64], h = new byte[64], r = new byte[64];

        int i, j;
        long [] x = new long[64];

        long [] [] p = new long [4] [];
        p[0] = new long [16];
        p[1] = new long [16];
        p[2] = new long [16];
        p[3] = new long [16];

        crypto_hash(d, sk,0, 32);
        d[0] &= 248;
        d[31] &= 127;
        d[31] |= 64;

        ///*smlen = n+64;

        for (i = 0; i < n; i ++) sm[64 + i] = m[i+moff];

        for (i = 0; i < 32; i ++) sm[32 + i] = d[32 + i];

        crypto_hash(r, sm,32, n+32);
        reduce(r);
        scalarbase(p, r,0);
        pack(sm,p);

        for (i = 0; i < 32; i ++) sm[i+32] = sk[i+32];
        crypto_hash(h, sm,0, n + 64);
        reduce(h);

        for (i = 0; i < 64; i ++) x[i] = 0;

        for (i = 0; i < 32; i ++) x[i] = (long) (r[i]&0xff);

        for (i = 0; i < 32; i ++) for (j = 0; j < 32; j ++) x[i+j] += (h[i]&0xff) * (long) (d[j]&0xff);

        modL(sm,32, x);

        return 0;
    }

    private static int unpackneg(long [] r[], byte p[])
    {
        long []    t = new long [16];
        long []  chk = new long [16];
        long []  num = new long [16];
        long []  den = new long [16];
        long [] den2 = new long [16];
        long [] den4 = new long [16];
        long [] den6 = new long [16];

        set25519(r[2], gf1);
        unpack25519(r[1], p);
        S(num, r[1]);
        M(den, num, D);
        Z(num, num, r[2]);
        A(den, r[2], den);

        S(den2, den);
        S(den4, den2);
        M(den6, den4, den2);
        M(t, den6, num);
        M(t, t, den);

        pow2523(t, t);
        M(t, t, num);
        M(t, t, den);
        M(t, t, den);
        M(r[0], t, den);

        S(chk, r[0]);
        M(chk, chk, den);
        if (neq25519(chk, num)!=0) M(r[0], r[0], I);

        S(chk, r[0]);
        M(chk, chk, den);
        if (neq25519(chk, num)!=0) return -1;

        if (par25519(r[0]) == ((p[31]&0xFF)>>>7)) Z(r[0], gf0, r[0]);

        M(r[3], r[0], r[1]);

        return 0;
    }

    /// TBD 64bits of mlen
    ///int crypto_sign_open(byte []m,long *mlen,byte []sm,long n,byte []pk)
    public static int crypto_sign_open(byte [] m, long dummy /* *mlen not used*/, byte [] sm,final int smoff, int/*long*/ n, byte []pk)
    {
        int i;
        byte[] t = new byte[32], h = new byte[64];

        long [] [] p = new long [4] [];
        p[0] = new long [16];
        p[1] = new long [16];
        p[2] = new long [16];
        p[3] = new long [16];

        long [] [] q = new long [4] [];
        q[0] = new long [16];
        q[1] = new long [16];
        q[2] = new long [16];
        q[3] = new long [16];

        ///*mlen = -1;

        if (n < 64) return -1;

        if (unpackneg(q,pk)!=0) return -1;

        for (i = 0; i < n; i ++) m[i] = sm[i+smoff];

        for (i = 0; i < 32; i ++) m[i+32] = pk[i];

        crypto_hash(h, m,0, n);

        reduce(h);
        scalarmult(p,q, h,0);

        scalarbase(q, sm,32+smoff);
        add(p,q);
        pack(t,p);

        n -= 64;
        if (crypto_verify_32(sm,smoff, t,0)!=0) {
            // optimizing it
            ///for (i = 0; i < n; i ++) m[i] = 0;
            return -1;
        }

        // TBD optimizing ...
        ///for (i = 0; i < n; i ++) m[i] = sm[i + 64 + smoff];
        ///*mlen = n;

        return 0;
    }

    /*
     * @description
     *   Java SecureRandom generator
     * */
    private static final SecureRandom jrandom = new SecureRandom();

    public static byte[] randombytes(byte [] x) {
        jrandom.nextBytes(x);
        return x;
    }

    public static byte[] randombytes(int len) {
        return randombytes(new byte[len]);
    }

    public static byte[] randombytes(byte [] x, int len) {
        byte [] b = randombytes(len);
        System.arraycopy(b, 0, x, 0, len);
        return x;
    }
	
/*
  public static byte[] randombytes(byte [] x, int len) {
		int ret = len % 8;
		long rnd;

		for (int i = 0; i < len-ret; i += 8) {
			rnd = jrandom.nextLong();

			x[i+0] = (byte) (rnd >>>  0);
			x[i+1] = (byte) (rnd >>>  8);
			x[i+2] = (byte) (rnd >>> 16);
			x[i+3] = (byte) (rnd >>> 24);
			x[i+4] = (byte) (rnd >>> 32);
			x[i+5] = (byte) (rnd >>> 40);
			x[i+6] = (byte) (rnd >>> 48);
			x[i+7] = (byte) (rnd >>> 56);
		}

		if (ret > 0) {         
			rnd = jrandom.nextLong();
			for (int i = len-ret; i < len; i ++)
				x[i] = (byte) (rnd >>> 8*i);
		}
		return x;
	}
*/

    public static byte[] makeBoxNonce() {
        return randombytes(Box.nonceLength);
    }

    public static byte[] makeSecretBoxNonce() {
        return randombytes(SecretBox.nonceLength);
    }

    public static String hexEncodeToString( byte [] raw ) {
        String HEXES = "0123456789ABCDEF";
        final StringBuilder hex = new StringBuilder( 2 * raw.length );
        for ( final byte b : raw ) {
            hex.append(HEXES.charAt((b & 0xF0) >> 4))
                    .append(HEXES.charAt((b & 0x0F)));
        }
        return hex.toString();
    }

    public static byte[] hexDecode(String s) {
        byte[] b = new byte[s.length() / 2];
        for (int i = 0; i < s.length(); i += 2) {
            b[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i+1), 16));
        }
        return b;
    }

    // public static boolean java.util.Arrays.equals(array1, array2);

}