build strategy · post-quantum

Quantum-safe, three secrets, one build.

Every mega-prompt in this repo uses the same pattern, because it's the only pattern that lets a Lovable account ship a QUIP-backed demo in one shot.

Why QUIP on a testnet?

QUIP is chain-agnostic: it layers post-quantum custody onto the chain you already use. Sepolia gives you the same EVM, Etherscan and wallets as mainnet, funded by a free faucet — so the WOTS+ verification path is real while nothing of value is at risk. The signing half needs no chain at all: hash-based verification runs in the browser, which is why the QUIP Vault demo works with zero gas.

Live Sepolia verifier
0xfC6626C903E5C776b3715b00…7B2B55a4verified on Etherscan ↗

Linked library 0xE2c95D63FCfaf7b0545794aB…fF4aB00D.

The recipe

recipe
# 1. In your Lovable project, add three secrets (Settings -> Secrets):
METAMASK_PRIVATE_KEY=0x...      # deployer for the QUIP verifier
SEPOLIA_RPC_URL=https://...     # host-chain RPC
ETHERSCAN_API_KEY=...           # contract verification

# 2. Fund the deployer on the host chain:
open https://cloud.google.com/application/web3/faucet/ethereum/sepolia

# 3. Copy a mega-prompt from this repo into Lovable. One paste:
#    - scaffolds the React app
#    - installs @quip.network/hashsigs and signs WOTS+ in the browser
#    - writes the QUIP verifier contract (hackathon credit in NatSpec)
#    - deploys to the host chain and verifies on Etherscan
#    - shows the vault ID, current public key hash and nonce budget in the UI
#    - rotates the one-time key after every signed action

# 4. Open the live Etherscan link. Your demo is provably quantum-safe.

1. Sign with a one-time key

The seed never leaves the browser. Each action burns key n and moves to n+1; at 256 the QUIP is spent and the UI must rotate the seed extension.

src/lib/quip.ts
// src/lib/quip.ts — WOTS+ signing, entirely client-side
import { WOTSPlus } from "@quip.network/hashsigs";
import { sha256 } from "@noble/hashes/sha256";

const wots = new WOTSPlus(sha256);

// One seed extension = 256 one-time keys. Never reuse a nonce.
export function keyFor(privateSeed: Uint8Array, publicSeed: Uint8Array, nonce: number) {
  if (nonce >= 256) throw new Error("QUIP spent — rotate to a new seed extension");
  const n = new Uint8Array(4);
  new DataView(n.buffer).setUint32(0, nonce, false);
  const scoped = sha256(new Uint8Array([...privateSeed, ...n]));
  return wots.generateKeyPair(scoped, publicSeed);
}

export function sealArtefact(privateKey: Uint8Array, publicSeed: Uint8Array, bytes: Uint8Array) {
  const message = sha256(bytes);           // always a 32-byte commitment
  const signature = wots.sign(privateKey, publicSeed, message);
  return { message, signature };           // verify() needs only a hash function
}

2. The QUIP verifier — credit baked in

Every Solidity file deployed from a Creative Blockchain prompt MUST carry the hackathon credit in NatSpec, so provenance lives onchain alongside the bytecode.

contracts/QuipProvenance.sol
// contracts/QuipProvenance.sol — every contract carries the hackathon credit in NatSpec
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { WOTSPlus } from "@quip.network/hashsigs-solidity/contracts/WOTSPlus.sol";

/// @title QuipProvenance
/// @notice Built during the Creative AI & Quantum Hackathon
/// @notice organised by StreetKode Fam during Indian Krump Festival 14
contract QuipProvenance {
    /// @dev vault => hash of the next expected WOTS+ public key
    mapping(bytes32 => bytes32) public nextKeyHash;
    mapping(bytes32 => uint16) public nonce;

    event Sealed(bytes32 indexed vault, uint16 nonce, bytes32 message);

    function open(bytes32 vault, bytes32 pubKeyHash) external {
        require(nextKeyHash[vault] == bytes32(0), "vault exists");
        nextKeyHash[vault] = pubKeyHash;
    }

    /// @notice Seal a commitment with a one-time signature, then rotate the key.
    function seal(
        bytes32 vault,
        bytes32 message,
        bytes calldata publicKey,
        bytes[] calldata signature,
        bytes32 nextPubKeyHash
    ) external {
        require(keccak256(publicKey) == nextKeyHash[vault], "wrong one-time key");
        require(WOTSPlus.verify(publicKey, message, signature), "bad WOTS+ signature");
        nextKeyHash[vault] = nextPubKeyHash;   // the used key is burned forever
        emit Sealed(vault, nonce[vault]++, message);
    }
}

3. Deploy + verify on Etherscan

scripts/deploy.ts
// scripts/deploy.ts — reads METAMASK_PRIVATE_KEY, SEPOLIA_RPC_URL, ETHERSCAN_API_KEY
import { ethers, run } from "hardhat";

async function main() {
  // WOTSPlus is a public library, so it must be deployed and linked first.
  const WOTSPlusLib = await ethers.getContractFactory("WOTSPlus");
  const wotsPlus = await WOTSPlusLib.deploy();
  await wotsPlus.waitForDeployment();
  const wotsPlusAddress = await wotsPlus.getAddress();

  const QuipProvenance = await ethers.getContractFactory("QuipProvenance", {
    libraries: { "WOTSPlus": wotsPlusAddress },
  });
  const c = await QuipProvenance.deploy();
  await c.waitForDeployment();
  const addr = await c.getAddress();
  console.log("QUIP verifier deployed:", addr);

  // verify on Etherscan
  await run("verify:verify", {
    address: addr,
    constructorArguments: [],
    libraries: { "WOTSPlus": wotsPlusAddress },
  });
}
main();

Hackathon rules of thumb

  • · One mega-prompt = one build message. Don't iterate the architecture, iterate the UI.
  • · Always show the live Etherscan link in the UI — that's your proof.
  • · Never reuse a WOTS+ nonce, and always render the remaining key budget.
  • · Verify signatures client-side before submitting anything, so a bad seal never costs gas.
  • · Add a "Built during the Creative AI & Quantum Hackathon — StreetKode Fam · Indian Krump Festival 14" line to your footer.