ETH Price: $1,995.96 (+0.28%)

Contract Diff Checker

Contract Name:
QuipWallet

Contract Source Code:

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// Copyright (C) 2024 quip.network
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity^0.8.28;

// DEBUG: import {Vm} from "../lib/forge-std/src/Vm.sol";
// DEBUG: import {console} from "../lib/forge-std/src/console.sol";

library WOTSPlus {
    // DEBUG: Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));

    // SignatureSize: The size of the signature in bytes.
    uint16 public constant SignatureSize = uint16(NumSignatureChunks) * uint16(HashLen);
    // PublicKeySize: The size of the public key in bytes.
    uint8 public constant PublicKeySize = HashLen * 2;

    // Hash: The WOTS+ `F` hash function.
    function Hash(bytes memory data) internal pure returns (bytes32) {
        return keccak256(data);
    }

    // HashLen: The WOTS+ `n` security parameter which is the size 
    // of the hash function output in bytes.
    // This is 32 for keccak256 (256 / 8 = 32)
    uint8 public constant HashLen = 32;

    // MessageLen: The WOTS+ `m` parameter which is the size 
    // of the message to be signed in bytes 
    // (and also the size of our hash function)
    //
    // This is 32 for keccak256 (256 / 8 = 32)
    //
    // Note that this is not the message length itself as, like 
    // with most signatures, we hash the message and then compute
    // the signature on the hash of the message.
    uint8 public constant MessageLen = 32;

    // ChainLen: The WOTS+ `w`(internitz) parameter. 
    // This corresponds to the number of hash chains for each public
    // key segment and the base-w representation of the message
    // and checksum.
    // 
    // A larger value means a smaller signature size but a longer
    // computation time.
    // 
    // For XMSS (rfc8391) this value is limited to 4 or 16 because
    // they simplify the algorithm and offer the best trade-offs.
    uint8 public constant ChainLen = 16;
    // lg(ChainLen) so we don't calculate it
    uint8 public constant LgChainLen = 4;

    // NumMessageChunks: the `len_1` parameter which is the number of
    // message chunks. This is 
    // ceil(8n / lg(w)) -> ceil(8 * HashLen / lg(ChainLen))
    // or ceil(32*8 / lg(16)) -> 256 / 4 = 64
    // Python:  math.ceil(32*8 / math.log(16,2))
    uint8 public constant NumMessageChunks = 64;

    // NumChecksumChunks: the `len_2` parameter which is the number of
    // checksum chunks. This is
    // floor(lg(len_1 * (w - 1)) / lg(w)) + 1
    // -> floor(lg(NumMessageChunks * (ChainLen - 1)) / lg(ChainLen)) + 1
    // -> floor(lg(64 * 15) / lg(16)) + 1 = 3
    // Python: math.floor(math.log(64 * 15, 2) / math.log(16, 2)) + 1
    uint8 public constant NumChecksumChunks = 3;

    uint8 public constant NumSignatureChunks = NumMessageChunks + NumChecksumChunks;

    struct WinternitzAddress {
        bytes32 publicSeed;
        bytes32 publicKeyHash;
    }

    struct WinternitzElements {
        bytes32[NumSignatureChunks] elements;
    }

    struct WinternitzMessage {
        bytes32 messageHash;
    }

    // verify: Verify a WOTS+ signature. 
    // 1. The first part of the publicKey is a public seed used to regenerate the randomization elements. (`r` from the paper).
    // 2. The second part of the publicKey is the hash of the NumMessageChunks + NumChecksumChunks public key segments.
    // 3. Convert the Message to "base-w" representation (or base of ChainLen representation).
    // 4. Compute and add the checksum. 
    // 5. Run the chain function on each segment to reproduce each public key segment.
    // 6. Hash all public key segments together to recreate the original public key.
    function verify(
        WinternitzAddress calldata quipAddress, 
        WinternitzMessage calldata message, 
        WinternitzElements calldata signature
    ) public pure returns (bool) {
        // DEBUG: require(publicKey.length == PublicKeySize, 
        // DEBUG:     string.concat("public key length must be ", vm.toString(PublicKeySize), " bytes"));
        // DEBUG: require(message.length == MessageLen, 
        // DEBUG:     string.concat("message length must be ", vm.toString(MessageLen), " bytes"));
        // DEBUG: require(signature.length == NumSignatureChunks, 
        // DEBUG:     string.concat("signature length must be ", vm.toString(NumSignatureChunks), " bytes, not", vm.toString(signature.length)));
        
        WinternitzElements memory randomizationElements = generateRandomizationElements(quipAddress.publicSeed);

        // DEBUG: console.log("Public key seed:");
        // DEBUG: console.logBytes32(publicSeed);
        // DEBUG: console.log("Public key hash:");
        // DEBUG: console.logBytes32(publicKeyHash);
        // DEBUG: console.log("Message:");
        // DEBUG: console.logBytes(message);

        bytes memory publicKeySegments = new bytes(SignatureSize);

        // would it be clearer to compute these together in a subfunction, hiding the checksum details entirely?
        uint8[] memory chainSegments = ComputeMessageHashChainIndexes(message);

        // Compute each public key segment. These are done by taking the signature, which is prevChainOut at chainIdx - 1, 
        // and completing the hash chain via the chain function to recompute the public key segment.
        for (uint8 i = 0; i < chainSegments.length; i++ ) {
            uint8 chainIdx = chainSegments[i];
            uint8 numIterations = ChainLen - chainIdx - 1;
            bytes32 prevChainOut = signature.elements[i];

            bytes32 segment = chain(prevChainOut, randomizationElements, chainIdx, numIterations);

            // Copy bytes32 to the correct position in publicKeySegments
            uint16 offset = uint16(i) * uint16(HashLen);
            setSlice32(publicKeySegments, segment, offset);
        }

        // DEBUG: console.log("Computed Public key segments:");
        // DEBUG: console.logBytes(publicKeySegments);
        

        // Hash all public key segments together to recreate the original public key.
        bytes32 computedHash = Hash(publicKeySegments);

        // DEBUG: console.log("Computed public key hash:");
        // DEBUG: console.logBytes32(computedHash);

        // Compare computed hash with stored public key hash
        return computedHash == quipAddress.publicKeyHash;
    }

    // verify: Verify a WOTS+ signature. 
    // 1. The first part of the publicKey is a public seed used to regenerate the randomization elements. (`r` from the paper).
    // 2. The second part of the publicKey is the hash of the NumMessageChunks + NumChecksumChunks public key segments.
    // 3. Convert the Message to "base-w" representation (or base of ChainLen representation).
    // 4. Compute and add the checksum. 
    // 5. Run the chain function on each segment to reproduce each public key segment.
    // 6. Hash all public key segments together to recreate the original public key.
    function verifyWithRandomizationElements(
        WinternitzAddress calldata quipAddress, 
        WinternitzMessage calldata message, 
        WinternitzElements calldata signature,
        WinternitzElements memory randomizationElements
    ) public pure returns (bool) {
        // DEBUG: require(publicKey.length == PublicKeySize, 
        // DEBUG:     string.concat("public key length must be ", vm.toString(PublicKeySize), " bytes"));
        // DEBUG: require(message.length == MessageLen, 
        // DEBUG:     string.concat("message length must be ", vm.toString(MessageLen), " bytes"));
        // DEBUG: require(signature.length == NumSignatureChunks, 
        // DEBUG:     string.concat("signature length must be ", vm.toString(NumSignatureChunks), " bytes, not", vm.toString(signature.length)));
        

        // DEBUG: console.log("Public key hash:");
        // DEBUG: console.logBytes32(publicKeyHash);
        // DEBUG: console.log("Message:");
        // DEBUG: console.logBytes(message);

        bytes memory publicKeySegments = new bytes(SignatureSize);

        uint8[] memory chainSegments = ComputeMessageHashChainIndexes(message);


        // Compute each public key segment. These are done by taking the signature, which is prevChainOut at chainIdx - 1, 
        // and completing the hash chain via the chain function to recompute the public key segment.
        for (uint8 i = 0; i < chainSegments.length; i++ ) {
            uint8 chainIdx = chainSegments[i];
            uint8 numIterations = ChainLen - chainIdx - 1;
            bytes32 prevChainOut = signature.elements[i];

            bytes32 segment = chain(prevChainOut, randomizationElements, chainIdx, numIterations);

            // Copy bytes32 to the correct position in publicKeySegments
            uint16 offset = uint16(i) * uint16(HashLen);
            setSlice32(publicKeySegments, segment, offset);
        }

        // DEBUG: console.log("Computed Public key segments:");
        // DEBUG: console.logBytes(publicKeySegments);
        

        // Hash all public key segments together to recreate the original public key.
        bytes32 computedHash = Hash(publicKeySegments);

        // DEBUG: console.log("Computed public key hash:");
        // DEBUG: console.logBytes32(computedHash);

        // Compare computed hash with stored public key hash
        return computedHash == quipAddress.publicKeyHash;
    }    

    // sign: Sign a message with a WOTS+ private key. Do not use this, it is present as an example and
    // you should be using a typescript version of this function because it requires your private key.
    function sign(bytes32 privateKey, WinternitzMessage calldata message) public pure returns (bytes32[NumSignatureChunks] memory) {
        // DEBUG: require(privateKey.length == HashLen, 
        // DEBUG:     string.concat("private key length must be ", vm.toString(HashLen), " bytes"));
        // DEBUG: require(message.length == MessageLen, 
        // DEBUG:     string.concat("message length must be ", vm.toString(MessageLen), " bytes"));

        require(privateKey.length == HashLen, 
            string.concat("private key length must be 32 bytes"));

        bytes32 publicSeed = prf(privateKey, 0);
        WinternitzElements memory randomizationElements = generateRandomizationElements(publicSeed);
        bytes32 functionKey = randomizationElements.elements[0];
        bytes32[NumSignatureChunks] memory signature;

        uint8[] memory chainSegments = ComputeMessageHashChainIndexes(message);

        for (uint8 i = 0; i < chainSegments.length; i++ ) {
            uint16 chainIdx = chainSegments[i];
            bytes32 secretKeySegment = Hash(abi.encodePacked(functionKey, prf(privateKey, i + 1)));
            signature[i] = chain(secretKeySegment, randomizationElements, 0, chainIdx);
        }

        return signature;
    }

    // generateKeyPair: Generate a WOTS+ key pair. Do not use this, it is present as an example and
    // you should be using a typescript version of this function, presumably with better entropy source.
    function generateKeyPair(bytes32 privateSeed) public pure returns (WinternitzAddress memory, bytes32) {

        bytes32 privateKey = prf(privateSeed, 0);
        bytes32 publicSeed = prf(privateKey, 0);

        WinternitzElements memory randomizationElements = generateRandomizationElements(publicSeed);
        // functionKey is `k` from the paper, we define it as the index 0 from the prf, 
        // as the prf output is not used on the first element in the chain function.
        // This is hashed in on each chain iteration along with the randomization element.
        // It is part of the public key, so safe to define it with the public seed.
        // To set it, we hash it into the first segment with the secret key.
        // TODO: take a closer look at XMSS et al and see how they handle this, we should be
        // doing the same.
        bytes32 functionKey = randomizationElements.elements[0];

        bytes memory publicKeySegments = new bytes(SignatureSize);

        for (uint8 i = 0; i < NumSignatureChunks; i++) {
            bytes32 secretKeySegment = Hash(abi.encodePacked(functionKey, prf(privateKey, i + 1)));
            bytes32 segment = chain(secretKeySegment, randomizationElements, 0, ChainLen - 1);

            // Copy bytes32 to the correct position in publicKeySegments
            uint16 offset = uint16(i) * uint16(HashLen);
            setSlice32(publicKeySegments, segment, offset);
        }

        // DEBUG: console.log("Public key segments:");
        // DEBUG: console.logBytes(publicKeySegments);

        bytes32 publicKeyHash = Hash(publicKeySegments);

        // DEBUG: console.log("Public key hash:");
        // DEBUG: console.logBytes32(publicKeyHash);

        WinternitzAddress memory publicKey = WinternitzAddress({publicKeyHash: publicKeyHash, publicSeed: publicSeed});
        return (publicKey, privateKey);
    }

    function generateRandomizationElements(bytes32 publicSeed) public pure returns (WinternitzElements memory) {
        bytes32[NumSignatureChunks] memory elements;
        for (uint8 i = 0; i < NumSignatureChunks; i++) {
            elements[i] = prf(publicSeed, i);
        }
        return WinternitzElements({elements: elements});
    }

    // chain is the c_k^i function, 
    // the hash of (prevChainOut XOR randomization element at index).
    // As a practical matter, we generate the randomization elements
    // via a seed like in XMSS(rfc8391) with a defined PRF.
    function chain(
        bytes32 prevChainOut, 
        WinternitzElements memory randomizationElements, 
        uint16 index, 
        uint16 steps
    ) public pure returns (bytes32) {
        // DEBUG: require((index + steps) < ChainLen, 
        // DEBUG:     string.concat("steps + index must be less than ", vm.toString(ChainLen)));

        bytes32 chainOut = prevChainOut;
        for (uint8 i = 1; i <= steps; i++) {
            chainOut = Hash(abi.encodePacked(xor(chainOut, randomizationElements.elements[i + index])));
        }
        return chainOut;
    }

    // xor: XOR two bytes32 values
    function xor(bytes32 a, bytes32 b) internal pure returns (bytes32) {
        return bytes32(uint256(a) ^ uint256(b));
    }

    // There's no built-in x[a:b] semantic for bytes or bytes32 unless it's calldata, apparently...
    function setSlice32(bytes memory dst, bytes32 src, uint16 offset) internal pure {
        assembly {
            mstore(add(add(dst, 32), offset), src)
        }
    }

    // prf: Generate randomization elements from seed and index
    // Similar to XMSS RFC 8391 section 5.1
    // NOTE: while sha256 and ripemd160 are available in solidity,
    // they are implemented as precompiled contracts and are more expensive for gas. 
    function prf(bytes32 seed, uint16 index) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(
            bytes1(0x03),  // prefix to domain separate
            seed,          // the seed input
            uint16(index)  // the index/position
        ));
    }

    // ComputeMessageHashChainIndexes: Compute the chain indexes for a message. 
    // We convert the message to base-w representation (or base of ChainLen representation)
    // We attach the checksum, also in base-w representation, to the end of the hash chain index list. 
    function ComputeMessageHashChainIndexes(WinternitzMessage calldata message) internal pure returns (uint8[] memory) {
        uint8[] memory chainIndexes = new uint8[](NumMessageChunks + NumChecksumChunks);
        toBaseW(abi.encodePacked(message.messageHash), NumMessageChunks, chainIndexes, 0);
        checksum(chainIndexes);
        return chainIndexes;
    }

    // checksum: Calculate the checksum of the message and return it in basew
    function checksum(uint8[] memory basew) internal pure {
        uint16 csum = 0;
        for (uint8 i = 0; i < NumMessageChunks; i++ ) {
            csum = csum + ChainLen - 1 - basew[i];
        }

        // this is left-shifting the checksum to ensure proper alignment when 
        // converting to base-w representation.
        // This shift ensures that when we convert to base-w, the least significant
        // bits of the checksum will be properly aligned with the w-bit boundaries.
        // (8 - ((NumChecksumChunks * LgChainLen) % 8)) = 4
        csum = csum << 4;
        // Per XMSS (rfc8391) this is done in big endian...
        // It's 2 bytes because thats ceil( ( len_2 * lg(w) ) / 8 ), technically actually 
        // 12 bits, or 3 basew segments.
        bytes memory csumBytes = new bytes(2);
        csumBytes[0] = bytes1(uint8(csum >> 8));    // Most significant byte
        csumBytes[1] = bytes1(uint8(csum & 0xFF));  // Least significant byte

        // Convert checksum bytes to base-w and append to basew array
        toBaseW(csumBytes, NumChecksumChunks, basew, NumMessageChunks);
    }

    // toBaseW: Convert a message to base-w representation (or base of ChainLen representation)
    // These numbers are used to index into each hash chain which is rooted at a secret key segment and produces
    // a public key segment at the end of the chain. Verification of a signature means using these
    // index into each hash chain to recompute the corresponding public key segment.
    function toBaseW(bytes memory message, uint8 numChunks, uint8[] memory basew, uint8 offset) internal pure {
        // Input message index
        uint8 mIdx = 0;
        // Output basew index
        uint8 oIdx = 0 + offset;
        uint8 total = 0;
        uint8 bits = 0;

        for (uint8 consumed = 0; consumed < numChunks; consumed++) {
            // Consume more bits when we run out
            if (bits == 0) {
                total = uint8(message[mIdx]);
                mIdx++;
                bits += 8;
            }

            // Read lg(ChainLen) bits from the message (lg(w))
            bits -= LgChainLen;
            basew[oIdx] = (total >> bits) & (ChainLen - 1);
            oIdx++;
        }
    }
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// Copyright (C) 2025 quip.network
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.28;

import "@quip.network/hashsigs-solidity/contracts/WOTSPlus.sol";
import "./QuipWallet.sol";

contract QuipFactory {
    address payable public admin;
    address public immutable wotsLibrary;

    // Fees
    uint256 public creationFee = 0;
    uint256 public transferFee = 0;
    uint256 public executeFee = 0;

    // eth address -> "salt" vaultId -> QuipWallet address
    mapping(address => mapping(bytes32 => address)) public quips;

    // Track vaultIds for each owner
    mapping(address => bytes32[]) public vaultIds;

    event QuipCreated(
        uint256 amount,
        uint256 when,
        bytes32 vaultId,
        address creator,
        WOTSPlus.WinternitzAddress pqPubkey,
        address quip
    );

    receive() external payable {}

    fallback() external payable {}

    constructor(address payable initialOwner, address _wotsLibrary) payable {
        admin = initialOwner;
        wotsLibrary = _wotsLibrary;
    }

    /* NOTE: you can pregenerate the address as follows:
    bytes32 hash = keccak256(
        abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            keccak256(type(Lock).creationCode)
        )
    );
    address preAddr = address(uint160(uint(hash)));
    */
    function depositToWinternitz(
        bytes32 vaultId,
        address payable to,
        WOTSPlus.WinternitzAddress calldata pqTo
    ) public payable returns (address) {
        address contractAddr;

        bytes memory quipWalletCode = abi.encodePacked(
            type(QuipWallet).creationCode,
            // Encode params for the constructor
            abi.encode(address(this), to)
        );

        uint256 contractValue = msg.value - creationFee;

        assembly {
            // code starts after the first 32 bytes...
            // https://ethereum-blockchain-developer.com/110-upgrade-smart-contracts/12-metamorphosis-create2/
            let code := add(0x20, quipWalletCode)
            let codeSize := mload(quipWalletCode)
            contractAddr := create2(0, code, codeSize, vaultId)

            // revert on failure
            if iszero(extcodesize(contractAddr)) {
                revert(0, 0)
            }
        }

        assert(contractAddr != address(0));
        QuipWallet(payable(contractAddr)).initialize(pqTo);
        payable(contractAddr).transfer(contractValue);
        quips[to][vaultId] = contractAddr;
        vaultIds[to].push(vaultId);

        emit QuipCreated(
            msg.value,
            block.timestamp,
            vaultId,
            to,
            pqTo,
            contractAddr
        );

        return contractAddr;
    }

    function transferOwnership(address newOwner) public {
        require(msg.sender == admin, "You aren't the admin");
        admin = payable(newOwner);
    }

    function setCreationFee(uint256 newFee) public {
        require(msg.sender == admin, "You aren't the admin");
        creationFee = newFee;
    }

    function setTransferFee(uint256 newFee) public {
        require(msg.sender == admin, "You aren't the admin");
        transferFee = newFee;
    }

    function setExecuteFee(uint256 newFee) public {
        require(msg.sender == admin, "You aren't the admin");
        executeFee = newFee;
    }

    function withdraw(uint256 amount) public {
        require(msg.sender == admin, "You aren't the admin");
        require(address(this).balance >= amount, "Insufficient balance");
        admin.transfer(amount);
    }

    function owner() public view returns (address) {
        return admin;
    }
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// Copyright (C) 2025 quip.network
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.28;

import "@quip.network/hashsigs-solidity/contracts/WOTSPlus.sol";
import "./QuipFactory.sol";

// Uncomment this line to use console.log

contract QuipWallet {
    address payable public quipFactory;
    address payable public owner;
    WOTSPlus.WinternitzAddress public pqOwner;

    receive() external payable {}

    fallback() external payable {}

    event pqTransfer(
        uint256 amount,
        uint256 when,
        WOTSPlus.WinternitzAddress pqFrom,
        WOTSPlus.WinternitzAddress pqNext,
        address to
    );

    constructor(address payable creator, address payable newOwner) payable {
        quipFactory = creator;
        owner = payable(newOwner);
    }

    function initialize(WOTSPlus.WinternitzAddress calldata newPqOwner) public {
        require(
            msg.sender == owner || msg.sender == quipFactory,
            "You aren't the owner or creator"
        );
        require(
            pqOwner.publicSeed == bytes32(0) &&
                pqOwner.publicKeyHash == bytes32(0),
            "Already initialized"
        );
        pqOwner = newPqOwner;
    }

    function changePqOwner(
        WOTSPlus.WinternitzAddress calldata newPqOwner,
        WOTSPlus.WinternitzElements calldata pqSig
    ) public {
        require(msg.sender == owner, "You aren't the owner");

        bytes memory msgData = abi.encodePacked(
            pqOwner.publicSeed,
            pqOwner.publicKeyHash,
            newPqOwner.publicSeed,
            newPqOwner.publicKeyHash
        );

        WOTSPlus.WinternitzMessage memory message = WOTSPlus.WinternitzMessage({
            messageHash: keccak256(msgData)
        });

        require(WOTSPlus.verify(pqOwner, message, pqSig), "Invalid signature");
        pqOwner = newPqOwner;
    }

    function transferWithWinternitz(
        WOTSPlus.WinternitzAddress calldata nextPqOwner,
        WOTSPlus.WinternitzElements calldata pqSig,
        address payable to,
        uint256 value
    ) public payable {
        WOTSPlus.WinternitzAddress memory curPqOwner = pqOwner;

        uint256 fee = getTransferFee();

        require(msg.value >= fee, "Insufficient fee");
        require(msg.sender == owner, "You aren't the owner");
        require(address(this).balance >= value, "Insufficient balance");

        bytes memory msgData = abi.encodePacked(
            pqOwner.publicSeed,
            pqOwner.publicKeyHash,
            nextPqOwner.publicSeed,
            nextPqOwner.publicKeyHash,
            to,
            value
        );

        WOTSPlus.WinternitzMessage memory message = WOTSPlus.WinternitzMessage({
            messageHash: keccak256(msgData)
        });

        require(WOTSPlus.verify(pqOwner, message, pqSig), "Invalid signature");
        pqOwner = nextPqOwner;

        to.transfer(value);
        quipFactory.transfer(fee);

        emit pqTransfer(value, block.timestamp, curPqOwner, nextPqOwner, to);
    }

    function executeWithWinternitz(
        WOTSPlus.WinternitzAddress calldata nextPqOwner,
        WOTSPlus.WinternitzElements calldata pqSig,
        address payable target,
        bytes calldata opdata
    ) public payable returns (bool, bytes memory) {
        uint256 fee = getExecuteFee();
        require(msg.value >= fee, "Insufficient fee");

        uint256 forwardValue = msg.value - fee;

        require(msg.sender == owner, "You aren't the owner");

        WOTSPlus.WinternitzMessage memory message = WOTSPlus.WinternitzMessage({
            messageHash: keccak256(
                abi.encodePacked(
                    pqOwner.publicSeed,
                    pqOwner.publicKeyHash,
                    nextPqOwner.publicSeed,
                    nextPqOwner.publicKeyHash,
                    target,
                    opdata
                )
            )
        });

        require(WOTSPlus.verify(pqOwner, message, pqSig), "Invalid signature");
        pqOwner = nextPqOwner;
        quipFactory.transfer(fee);

        (bool success, bytes memory returnData) = target.call{
            value: forwardValue
        }(opdata);
        require(success, string(returnData));
        return (success, returnData);
    }

    function getTransferFee() public view returns (uint256) {
        return QuipFactory(quipFactory).transferFee();
    }

    function getExecuteFee() public view returns (uint256) {
        return QuipFactory(quipFactory).executeFee();
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):