"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const kyber_1 = require("@dedis/kyber");
const crypto_browserify_1 = require("crypto-browserify");
const keccak_1 = tslib_1.__importDefault(require("keccak"));
const light_1 = require("protobufjs/light");
const byzcoin_1 = require("../byzcoin");
const coin_instance_1 = tslib_1.__importDefault(require("../byzcoin/contracts/coin-instance"));
const instance_1 = tslib_1.__importDefault(require("../byzcoin/instance"));
const protobuf_1 = require("../protobuf");
const curve25519 = kyber_1.curve.newCurve("edwards25519");
class OnChainSecretInstance extends instance_1.default {
    constructor(rpc, inst) {
        super(inst);
        this.rpc = rpc;
        if (inst.contractID.toString() !== OnChainSecretInstance.contractID) {
            throw new Error(`mismatch contract name: ${inst.contractID} vs ${OnChainSecretInstance.contractID}`);
        }
        this.write = Write.decode(inst.data);
    }
    /**
     * Spawn a longTermSecret instance
     *
     * @param bc        The RPC to use
     * @param darcID    The darc instance ID
     * @param signers   The list of signers for the transaction
     * @param write The write structure containing the encrypted secret
     * @returns a promise that resolves with the new instance
     */
    static spawn(bc, darcID, signers) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const inst = byzcoin_1.Instruction.createSpawn(darcID, OnChainSecretInstance.contractID, []);
            yield inst.updateCounters(bc, signers);
            const ctx = byzcoin_1.ClientTransaction.make(bc.getProtocolVersion(), inst);
            ctx.signWith([signers]);
            yield bc.sendTransactionAndWait(ctx, 10);
            return OnChainSecretInstance.fromByzcoin(bc, ctx.instructions[0].deriveId(), 1);
        });
    }
    /**
     * Initializes using an existing coinInstance from ByzCoin
     * @param bc    The RPC to use
     * @param iid   The instance ID
     * @param waitMatch how many times to wait for a match - useful if its called just after an addTransactionAndWait.
     * @param interval how long to wait between two attempts in waitMatch.
     * @returns a promise that resolves with the OnChainSecret instance
     */
    static fromByzcoin(bc, iid, waitMatch = 0, interval = 1000) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new OnChainSecretInstance(bc, yield instance_1.default.fromByzcoin(bc, iid, waitMatch, interval));
        });
    }
}
exports.OnChainSecretInstance = OnChainSecretInstance;
OnChainSecretInstance.contractID = "longTermSecret";
class CalypsoWriteInstance extends instance_1.default {
    constructor(rpc, inst) {
        super(inst);
        this.rpc = rpc;
        if (inst.contractID.toString() !== CalypsoWriteInstance.contractID) {
            throw new Error(`mismatch contract name: ${inst.contractID} vs ${CalypsoWriteInstance.contractID}`);
        }
        this.write = Write.decode(inst.data);
    }
    /**
     * Spawn a calypsoWrite instance
     *
     * @param bc        The RPC to use
     * @param darcID    The darc instance ID
     * @param write The write structure containing the encrypted secret
     * @param signers   The list of signers for the transaction
     * @param preID used to calculate the ID of the write instance
     * @returns a promise that resolves with the new instance
     */
    static spawn(bc, darcID, write, signers, preID) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const ctx = byzcoin_1.ClientTransaction.make(bc.getProtocolVersion(), byzcoin_1.Instruction.createSpawn(darcID, CalypsoWriteInstance.contractID, [new byzcoin_1.Argument({
                    name: CalypsoWriteInstance.argumentWrite,
                    value: Buffer.from(Write.encode(write).finish()),
                })]));
            if (preID) {
                ctx.instructions[0].spawn.args.push(new byzcoin_1.Argument({ name: this.argumentPreID, value: preID }));
            }
            yield ctx.updateCountersAndSign(bc, [signers]);
            yield bc.sendTransactionAndWait(ctx, 10);
            return CalypsoWriteInstance.fromByzcoin(bc, ctx.instructions[0].deriveIdArg(), 1);
        });
    }
    /**
     * Initializes using an existing coinInstance from ByzCoin
     * @param bc    The RPC to use
     * @param iid   The instance ID
     * @returns a promise that resolves with the coin instance
     */
    static fromByzcoin(bc, iid, waitMatch = 0, interval = 1000) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new CalypsoWriteInstance(bc, yield instance_1.default.fromByzcoin(bc, iid, waitMatch, interval));
        });
    }
    /**
     * Returns the instanceID given a preID.
     * @param preID
     */
    static preToInstID(preID) {
        return instance_1.default.calcInstID(this.contractID, preID);
    }
    spawnRead(pub, signers, coin, coinSigners, preID) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (this.write.cost && (!coin || !coinSigners)) {
                throw new Error("spawning a read instance costs coins");
            }
            let pay;
            if (coin && coinSigners) {
                pay = byzcoin_1.Instruction.createInvoke(coin.id, coin_instance_1.default.contractID, coin_instance_1.default.commandFetch, [
                    new byzcoin_1.Argument({ name: coin_instance_1.default.argumentCoins, value: Buffer.from(this.write.cost.value.toBytesLE()) }),
                ]);
            }
            return CalypsoReadInstance.spawn(this.rpc, this.id, pub, signers, pay, preID);
        });
    }
    /**
     * Update sends an update command to an existing CalypsoWriteInstance and changes the data and/or the extraData
     * field of the Write structure. If neither data, nor extraData is given, it returns without sending a
     * transaction to byzcoin.
     * @param u a structure holding the data and/or extraData to be updated.
     */
    update(u) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const args = [];
            if (u.data) {
                args.push(new byzcoin_1.Argument({ name: CalypsoWriteInstance.argumentData, value: u.data }));
            }
            if (u.extraData) {
                args.push(new byzcoin_1.Argument({ name: CalypsoWriteInstance.argumentExtraData, value: u.extraData }));
            }
            if (args.length === 0) {
                return;
            }
            const ctx = byzcoin_1.ClientTransaction.make(this.rpc.getProtocolVersion(), byzcoin_1.Instruction.createInvoke(this.id, this.contractID, CalypsoWriteInstance.commandUpdate, args));
            return this.rpc.sendTransactionAndWait(ctx, 10);
        });
    }
}
exports.CalypsoWriteInstance = CalypsoWriteInstance;
CalypsoWriteInstance.contractID = "calypsoWrite";
CalypsoWriteInstance.commandUpdate = "update";
CalypsoWriteInstance.argumentWrite = "write";
CalypsoWriteInstance.argumentDarcID = "darcID";
CalypsoWriteInstance.argumentPreID = "preID";
CalypsoWriteInstance.argumentData = "data";
CalypsoWriteInstance.argumentExtraData = "extraData";
class CalypsoReadInstance extends instance_1.default {
    constructor(rpc, inst) {
        super(inst);
        this.rpc = rpc;
        if (inst.contractID.toString() !== CalypsoReadInstance.contractID) {
            throw new Error(`mismatch contract name: ${inst.contractID} vs ${CalypsoReadInstance.contractID}`);
        }
        this.read = Read.decode(inst.data);
    }
    static spawn(bc, writeId, pub, signers, pay, preID) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const read = new Read({ write: writeId, xc: pub.marshalBinary() });
            const instrs = [byzcoin_1.Instruction.createSpawn(writeId, CalypsoReadInstance.contractID, [
                    new byzcoin_1.Argument({
                        name: CalypsoReadInstance.argumentRead,
                        value: Buffer.from(Read.encode(read).finish()),
                    }),
                ])];
            if (preID) {
                instrs[0].spawn.args.push(new byzcoin_1.Argument({ name: this.argumentPreID, value: preID }));
            }
            const ctxSigners = [signers];
            if (pay) {
                instrs.unshift(pay);
                ctxSigners.unshift(signers);
            }
            const ctx = byzcoin_1.ClientTransaction.make(bc.getProtocolVersion(), ...instrs);
            yield ctx.updateCountersAndSign(bc, ctxSigners);
            yield bc.sendTransactionAndWait(ctx);
            return CalypsoReadInstance.fromByzcoin(bc, ctx.instructions[ctx.instructions.length - 1].deriveIdArg(), 1);
        });
    }
    /**
     * Initializes using an existing CalypsoReadInstance from ByzCoin
     * @param bc    The RPC to use
     * @param iid   The instance ID
     * @returns a promise that resolves with the coin instance
     */
    static fromByzcoin(bc, iid, waitMatch = 0, interval = 1000) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new CalypsoReadInstance(bc, yield instance_1.default.fromByzcoin(bc, iid, waitMatch, interval));
        });
    }
    /**
     * Returns the instanceID given a preID.
     * @param preID
     */
    static preToInstID(preID) {
        return instance_1.default.calcInstID(this.contractID, preID);
    }
    decrypt(ocs, priv) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            // Note that we send the full proof in that case to insure the conodes
            // can easily verify the proof.
            const xhatenc = yield ocs.reencryptKey(yield this.rpc.getProof(this.read.write), yield this.rpc.getProof(this.id));
            return xhatenc.decrypt(priv);
        });
    }
}
exports.CalypsoReadInstance = CalypsoReadInstance;
CalypsoReadInstance.contractID = "calypsoRead";
CalypsoReadInstance.argumentRead = "read";
CalypsoReadInstance.argumentPreID = "preID";
class Write extends light_1.Message {
    constructor(props) {
        super(props);
    }
    /**
     * @see README#Message classes
     */
    static register() {
        protobuf_1.registerMessage("calypso.Write", Write);
    }
    /**
     * createWrite returns a new write structure that contains a proof for the read-request
     * with regard to the LTS-ID and the write-Darc.
     *
     * @param ltsid the long-term-secret ID that can re-encrypt the key
     * @param writeDarc allowed to ask for a re-encryption
     * @param X the aggregate public key under which the symmetric key will be encrypted
     * @param key the symmetric key to be encrypted
     */
    static createWrite(ltsid, writeDarc, X, key, rand) {
        // wr := &Write{LTSID: ltsid}
        const wr = new Write();
        // r := suite.Scalar().Pick(suite.RandomStream())
        const r = curve25519.scalar().pick(rand);
        // C := suite.Point().Mul(r, X)
        const C = curve25519.point().mul(r, X);
        // wr.U = suite.Point().Mul(r, nil)
        wr.u = curve25519.point().mul(r).marshalBinary();
        // Create proof
        // if len(key) > suite.Point().EmbedLen() {
        // 	return nil
        // }
        if (key.length > curve25519.point().embedLen()) {
            throw new Error("key is too long");
        }
        // kp := suite.Point().Embed(key, suite.RandomStream())
        const kp = curve25519.point().embed(key, rand);
        // wr.C = suite.Point().Add(C, kp)
        wr.c = curve25519.point().add(C, kp).marshalBinary();
        // gBar := suite.Point().Embed(ltsid.Slice(), keccak.New(ltsid.Slice()))
        const k = new keccak_1.default("shake256");
        k.update(ltsid);
        const gBar = curve25519.point().embed(Buffer.from(ltsid.subarray(0, curve25519.point().embedLen())), (l) => k.squeeze(l));
        // wr.Ubar = suite.Point().Mul(r, gBar)
        wr.ubar = curve25519.point().mul(r, gBar).marshalBinary();
        // s := suite.Scalar().Pick(suite.RandomStream())
        const s = curve25519.scalar().pick(rand);
        // w := suite.Point().Mul(s, nil)
        const w = curve25519.point().mul(s);
        // wBar := suite.Point().Mul(s, gBar)
        const wBar = curve25519.point().mul(s, gBar);
        // hash := sha256.New()
        const hash = crypto_browserify_1.createHash("sha256");
        // wr.C.MarshalTo(hash)
        hash.update(wr.c);
        // wr.U.MarshalTo(hash)
        hash.update(wr.u);
        // wr.Ubar.MarshalTo(hash)
        hash.update(wr.ubar);
        // w.MarshalTo(hash)
        hash.update(w.marshalBinary());
        // wBar.MarshalTo(hash)
        hash.update(wBar.marshalBinary());
        // hash.Write(writeDarc)
        hash.update(writeDarc);
        // wr.E = suite.Scalar().SetBytes(hash.Sum(nil))
        const E = curve25519.scalar().setBytes(hash.digest());
        wr.e = E.marshalBinary();
        // wr.F = suite.Scalar().Add(s, suite.Scalar().Mul(wr.E, r))
        wr.f = curve25519.scalar().add(s, curve25519.scalar().mul(E, r)).marshalBinary();
        wr.ltsid = ltsid;
        return wr;
    }
    toBytes() {
        return Buffer.from(Write.encode(this).finish());
    }
}
exports.Write = Write;
class Read extends light_1.Message {
    constructor(props) {
        super(props);
    }
    /**
     * @see README#Message classes
     */
    static register() {
        protobuf_1.registerMessage("calypso.Read", Read);
    }
    toBytes() {
        return Buffer.from(Read.encode(this).finish());
    }
}
exports.Read = Read;
// DecodeKey can be used by the reader of ByzCoin to convert the
// re-encrypted secret back to a symmetric key that can be used later to decode
// the document.
//
// Input:
//   - suite - the cryptographic suite to use
//   - X - the aggregate public key of the DKG
//   - C - the encrypted key
//   - XhatEnc - the re-encrypted schnorr-commit
//   - xc - the private key of the reader
//
// Output:
//   - key - the re-assembled key
//   - err - an eventual error when trying to recover the data from the points
// func DecodeKey(suite kyber.Group, X kyber.Point, C kyber.Point, XhatEnc kyber.Point,
// 	xc kyber.Scalar) (key []byte, err error) {
/* tslint:disable: variable-name */
function DecodeKey(X, C, XhatEnc, priv) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        // 	xcInv := suite.Scalar().Neg(xc)
        const xcInv = curve25519.scalar().neg(priv);
        // 	XhatDec := suite.Point().Mul(xcInv, X)
        const XhatDec = curve25519.point().mul(xcInv, X);
        // 	Xhat := suite.Point().Add(XhatEnc, XhatDec)
        const Xhat = curve25519.point().add(XhatEnc, XhatDec);
        // 	XhatInv := suite.Point().Neg(Xhat)
        const XhatInv = curve25519.point().neg(Xhat);
        // Decrypt C to keyPointHat
        // 	keyPointHat := suite.Point().Add(C, XhatInv)
        const keyPointHat = curve25519.point().add(C, XhatInv);
        // 	key, err = keyPointHat.Data()
        return Buffer.from(keyPointHat.data());
    });
}
exports.DecodeKey = DecodeKey;
Write.register();
Read.register();
