// File: contracts/TheWord.sol



//SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "./Verifier.sol";

/* Reference Poseidon hasher library contract using 6 inputs */
library Poseidon {
    function poseidon(uint256[6] memory) public pure returns (uint256) {}
}

contract TheWord {
    /// STRUCTS ///
    struct Round {
        uint256 commitment; // the hash of the secret phrase
        uint256 prize; // the value of locked up ether to burn when when round is over
        string shoutedBy; // the address of the user who shouted the secret phrase
        bool active; // true if round can be shouted, false if over or doesn't exist
    }

    // Standard Groth16 proof elements
    struct Groth16Proof {
        uint256[2] a;
        uint256[2][2] b;
        uint256[2] c;
    }

    /// VARIABLES ///

    // Circom R1CS Verifier contract
    Groth16Verifier verifier;

    // Address to burn ether to
    address payable public burnLocation = payable(0x00);

    // index of rounds
    uint256 public round;

    // map round index to round storage
    mapping(uint256 => Round) public rounds;

    /// EVENTS ///
    event NewRound(
        uint256 round,
        uint256 commitment,
        string username,
        uint256 prize
    );
    event Shouted(uint256 round, string shoutedBy);
    event PrizeAdded(uint256 round, uint256 added);

    /// MODIFIERS ///

    /**
     * Prevent function from being called if given index does not point to active round
     * @param _round - the round index to check for activity status
     */
    modifier active(uint256 _round) {
        require(rounds[_round].active, "Round is not active");
        _;
    }

    /**
     * Constructs new TheWord contract
     * @param _verifier - the address of the deployed circom r1cs verifier for the word proofs
     */
    constructor(address _verifier) {
        verifier = Groth16Verifier(_verifier);
    }

    /// EXTERNAL FUNCTIONS ///

    /**
     * Create a new round, optionally funding a prize for shouting
     * @dev cannot guarantee the secret phrase is within length requirement (could solve in circuit)
     *
     * @param _commitment - the hash of the secret phrase for which knowledge must be proven
     * @param _username - the username of the user proving their knowledge of the secret phrase
     * @param _proof - the groth16 proof that demonstrates the creator knows the secret phrase
     */
    function newRound(
        uint256 _commitment,
        string memory _username,
        Groth16Proof calldata _proof
    ) external payable {
        // convert username to uint256
        uint256 username = stringToUint(_username);

        // check that the proof is valid
        require(
            verifier.verifyProof(
                _proof.a,
                _proof.b,
                _proof.c,
                [_commitment, username]
            ),
            "Invalid proof"
        );

        // create a new round
        round++;
        rounds[round].commitment = _commitment;
        rounds[round].prize = msg.value;
        rounds[round].active = true;

        // emit event announcing new round
        emit NewRound(round, _commitment, _username, msg.value);
    }

    /**
     * End a round by shouting the secret phrase
     * @dev todo: maybe ecrecover a signature on secret for shoutedBy
     *
     * @param _round - the round index
     * @param _secret - the secret phrase that hashes to the round commitment
     * @param _username - the username of the user proving their knowledge of the secret phrase
     */
    function shout(
        uint256 _round,
        string calldata _secret,
        string memory _username
    ) external active(_round) {
        // convert the secret phrase to field elements
        uint256[6] memory felts = secretToFelts(_secret);

        // compute the hash of the phrase
        uint256 hash = hashPhrase(felts);

        // compare the hash to the commitment
        require(hash == rounds[_round].commitment, "Invalid secret phrase");

        // burn ether if prize exists for round and mark round as inactive
        endRound(_round, _username);

        // emit event announcing round end
        emit Shouted(_round, _username);
    }

    /**
     * Adds a prize to a round that has already been created
     * @param _round - the round to add a prize to
     */
    function fundPrize(uint256 _round) external payable active(_round) {
        rounds[_round].prize += msg.value;
        emit PrizeAdded(_round, msg.value);
    }

    /// INTERNAL FUNCTIONS ///

    /**
     * Send ether from the contract to the burn address, and mark as inactive
     * @dev this function is only ever called by shout which provides the `active` modifier check
     *
     * @param _round - the round to burn ether from
     * @param _shoutedBy - the username of the user who shouted the secret phrase
     */
    function endRound(uint256 _round, string memory _shoutedBy) internal {
        rounds[_round].active = false;
        rounds[_round].shoutedBy = _shoutedBy;
        if (rounds[_round].prize > 0) {
            burnLocation.transfer(rounds[_round].prize);
        }
    }

    /// VIEW/ PURE FUNCTIONS ///

    /**
     * Converts a secret phrase as utf8 bytes into 6 serialized bn254 field elements
     *
     * @param _secret - the secret phrase, utf8 encoded, to convert to field elements
     * @return felts - the 6 field elements representing the secret phrase
     */
    function secretToFelts(
        string calldata _secret
    ) public pure returns (uint256[6] memory felts) {
        bytes memory secret = bytes(_secret);
        require(
            secret.length <= 180,
            "secret too long: must be <= 200 characters"
        );

        for (uint256 i = 0; i < 6; i++) {
            uint256 start = i * 31;
            uint256 end = (i + 1) * 31;

            bytes memory chunk = new bytes(32);

            for (uint256 j = start; j < end && j < secret.length; j++) {
                chunk[j - start + 1] = secret[j];
            }

            bytes32 chunk32;
            assembly {
                chunk32 := mload(add(chunk, 32))
            }

            felts[i] = uint256(chunk32);
        }
    }

    /**
     * Hashes the 6 field elements representing secret phrase into the public commitment
     *
     * @param _felts - the preimage to hash
     * @return - the poseidon hash commiting to the secret phrase
     */
    function hashPhrase(
        uint256[6] memory _felts
    ) public pure returns (uint256) {
        return Poseidon.poseidon(_felts);
    }

    function stringToUint(string memory source) public pure returns (uint256 result) {
        bytes memory tempString = bytes(source);
        if (tempString.length == 0) {
            return 0x0;
        }
        
        // For each byte, shift result and insert the byte
        for (uint256 i = 0; i < tempString.length; i++) {
            result = (result << 8) | uint256(uint8(tempString[i]));
        }
    }
}

// File: contracts/Verifier.sol



// SPDX-License-Identifier: GPL-3.0
/*
    Copyright 2021 0KIMS association.

    This file is generated with [snarkJS](https://github.com/iden3/snarkjs).

    snarkJS is a free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    snarkJS is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
    License for more details.

    You should have received a copy of the GNU General Public License
    along with snarkJS. If not, see <https://www.gnu.org/licenses/>.
*/

pragma solidity >=0.7.0 <0.9.0;

contract Groth16Verifier {
    // Scalar field size
    uint256 constant r    = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
    // Base field size
    uint256 constant q   = 21888242871839275222246405745257275088696311157297823662689037894645226208583;

    // Verification Key data
    uint256 constant alphax  = 9526385790532871687215529217220873884227928931149809248336571282923313396106;
    uint256 constant alphay  = 21514950079872959705810978772756325328244854949685521730808737769175706105020;
    uint256 constant betax1  = 2716993543496060298796733064727635793292506509085152491379209538220117158382;
    uint256 constant betax2  = 20390368492886277093479380604774182574397340921248780260205596793295696674689;
    uint256 constant betay1  = 21764382463394824083591075546883615515682120102557591645420831661367465193367;
    uint256 constant betay2  = 12682167178280460625448986307109874161659067512125256426557601527559613505029;
    uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
    uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
    uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
    uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
    uint256 constant deltax1 = 4101997915818703989480745681395641243754430031549478442656633631473459377754;
    uint256 constant deltax2 = 10906997380511376953049043930596140484928975373929750469282752765580878567599;
    uint256 constant deltay1 = 21467614705199245308953952438252223903556487680113609709431051398494080793017;
    uint256 constant deltay2 = 14148267251354792026140554064990543520838005031627642915066139480082886598994;

    
    uint256 constant IC0x = 1481576207721992903324306469202784205263698883633483801846749476639749907908;
    uint256 constant IC0y = 14079879823088619349409019686751314160916086276213976861320608433134023444236;
    
    uint256 constant IC1x = 7383731686009945241890027660424753486806019510710608577959624978038477330556;
    uint256 constant IC1y = 15989552492341906507948596904664651598249821034965897193817841304269401419658;
    
    uint256 constant IC2x = 14560868027354574229864667972499776606845480756169309217438610379539610036620;
    uint256 constant IC2y = 2229063762005447500484823201700247299911184494037799604681263211101013646192;
    
 
    // Memory data
    uint16 constant pVk = 0;
    uint16 constant pPairing = 128;

    uint16 constant pLastMem = 896;

    function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[2] calldata _pubSignals) public view returns (bool) {
        assembly {
            function checkField(v) {
                if iszero(lt(v, q)) {
                    mstore(0, 0)
                    return(0, 0x20)
                }
            }
            
            // G1 function to multiply a G1 value(x,y) to value in an address
            function g1_mulAccC(pR, x, y, s) {
                let success
                let mIn := mload(0x40)
                mstore(mIn, x)
                mstore(add(mIn, 32), y)
                mstore(add(mIn, 64), s)

                success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)

                if iszero(success) {
                    mstore(0, 0)
                    return(0, 0x20)
                }

                mstore(add(mIn, 64), mload(pR))
                mstore(add(mIn, 96), mload(add(pR, 32)))

                success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)

                if iszero(success) {
                    mstore(0, 0)
                    return(0, 0x20)
                }
            }

            function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
                let _pPairing := add(pMem, pPairing)
                let _pVk := add(pMem, pVk)

                mstore(_pVk, IC0x)
                mstore(add(_pVk, 32), IC0y)

                // Compute the linear combination vk_x
                
                g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
                
                g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))
                

                // -A
                mstore(_pPairing, calldataload(pA))
                mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q))

                // B
                mstore(add(_pPairing, 64), calldataload(pB))
                mstore(add(_pPairing, 96), calldataload(add(pB, 32)))
                mstore(add(_pPairing, 128), calldataload(add(pB, 64)))
                mstore(add(_pPairing, 160), calldataload(add(pB, 96)))

                // alpha1
                mstore(add(_pPairing, 192), alphax)
                mstore(add(_pPairing, 224), alphay)

                // beta2
                mstore(add(_pPairing, 256), betax1)
                mstore(add(_pPairing, 288), betax2)
                mstore(add(_pPairing, 320), betay1)
                mstore(add(_pPairing, 352), betay2)

                // vk_x
                mstore(add(_pPairing, 384), mload(add(pMem, pVk)))
                mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32))))


                // gamma2
                mstore(add(_pPairing, 448), gammax1)
                mstore(add(_pPairing, 480), gammax2)
                mstore(add(_pPairing, 512), gammay1)
                mstore(add(_pPairing, 544), gammay2)

                // C
                mstore(add(_pPairing, 576), calldataload(pC))
                mstore(add(_pPairing, 608), calldataload(add(pC, 32)))

                // delta2
                mstore(add(_pPairing, 640), deltax1)
                mstore(add(_pPairing, 672), deltax2)
                mstore(add(_pPairing, 704), deltay1)
                mstore(add(_pPairing, 736), deltay2)


                let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20)

                isOk := and(success, mload(_pPairing))
            }

            let pMem := mload(0x40)
            mstore(0x40, add(pMem, pLastMem))

            // Validate that all evaluations ∈ F
            
            checkField(calldataload(add(_pubSignals, 0)))
            
            checkField(calldataload(add(_pubSignals, 32)))
            
            checkField(calldataload(add(_pubSignals, 64)))
            

            // Validate all evaluations
            let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem)

            mstore(0, isValid)
             return(0, 0x20)
         }
     }
 }