Find the watermark
A word model trained on about 300 of Aesop's fables writes with a hidden watermark. A detector with the key counts the green words.
Loading the fables (190 KB)...
VerdictNothing to check
Green tokens0 of 0 (chance: about 0)
Score (z)n/a
Chance without a watermarkn/a
Tokens you swapped0
Challenges
0 of 4 complete
- Make the detector flag a watermarked text only 20 tokens long. (not yet)
- Start from a text the detector flags, then tap words to swap them until it no longer does. (not yet)
- Check a text the model's key flags with a different key. (not yet)
- Type or paste at least 40 tokens of your own writing and check it. (not yet)
Try this
- Set the strength to 0 and press Write a new text a few times. The score stays near zero.
- Back at strength 2, shorten the text to 20 tokens, then 10. When does the detector start missing it?
- Tap words in a flagged text to swap them and watch the score in the chart fall.
- Switch the detector to a different key. Same text, no watermark in sight.
A green-list watermark in a few linesPython with the standard library
import hashlib, math, random
# A stand-in "model" that finds any of these words equally likely next.
vocab = ("the fox crow lion mouse hare tortoise dog wolf lamb ant bee "
"saw found ran slept caught freed jumped ate dropped asked "
"grapes cheese bone water field river tree nest hill road "
"and but then quickly slowly gladly at last again").split()
GAMMA = 0.25 # share of words on each green list
def green(prev, word, key):
# The key and the previous word decide whether `word` is green.
h = hashlib.sha256(f"{key}|{prev}|{word}".encode()).digest()
return int.from_bytes(h[:4], "big") / 2**32 < GAMMA
def write(n, delta, seed, key="model key"):
rng, prev, out = random.Random(seed), ".", []
for _ in range(n):
# Push green words up by a factor of e^delta, then sample.
weights = [math.exp(delta) if green(prev, w, key) else 1 for w in vocab]
prev = rng.choices(vocab, weights)[0]
out.append(prev)
return out
def z_score(tokens, key):
pairs = set(zip(["."] + tokens, tokens)) # score each word pair once
g = sum(green(p, w, key) for p, w in pairs)
t = len(pairs)
return (g - GAMMA * t) / math.sqrt(t * GAMMA * (1 - GAMMA))
plain = write(60, delta=0, seed=1)
marked = write(60, delta=2, seed=1)
print(" ".join(marked[:10]), "...")
print(f"no watermark: z = {z_score(plain, 'model key'):5.2f}")
print(f"watermark, model's key: z = {z_score(marked, 'model key'):5.2f}")
print(f"watermark, other key: z = {z_score(marked, 'other key'):5.2f}")mouse and and ant asked water nest but tortoise crow ...
no watermark: z = 0.08
watermark, model's key: z = 8.05
watermark, other key: z = 0.89