Let's say we have a 32 byte-output hash function.
There is a lovely one-time-signature (OTS) scheme by Leslie Lamport. The way
it works is that you choose 2N*32
random bytes and use that as a private key.
Then, the public key is the hashes of every 32 byte section.
To sign a message, we hash the message and use the bits of the hash to decide which of our secret 32 byte values which we've committed to via a hash in the public key. If the bit is 0, we reveal the even one at 2 times that bit-index, and if the bit is 1, we reveal the odd one at that 2 times bit-index + 1.
To make any one-time-signature scheme into a many-time one, we can use Merkle trees. We take M one-time private keys and create a Merkle tree based on their respective public keys. Then, our public key is the Merkle root of that tree. A signature consists of a Merkle authentication path, the public key we used, and a signature using the private key corresponding to that public key.
I've implemented this idea in Rust: blake3-lamport-signatures.