LLM Watermarking: How It Works and Its Limitations

LLM Watermarking: How It Works and Its Limitations

# LLM出力への電子透かし:仕組みと実装 ## 電子透かしとは何か テキストに「透かし」を入れるとは、人間には気づかれないが統計的に検出可能なパターンをテキスト生成時に埋め込むことです。 画像の透かしと異なり、**テキストはコピペしても透かしが消えない**のが特徴です。理由はシンプルです。 > 透かしはピクセルではなく、**単語の選択確率の偏り**として埋め込まれる --- ## 代表的手法:Kirchenbauer et al. (2023) のグリーンリスト透かし Googleとメリーランド大学が提案した手法を実装して確認します。 ### アルゴリズムの概要 ``` トークン生成時: 1. 直前のトークンをシードとしてハッシュ 2. 全語彙をランダムに「グリーンリスト」(50%)と 「レッドリスト」(50%)に分割 3. グリーンリストのトークンの確率にδだけ加算 4. サンプリング → グリーンリストが選ばれやすくなる ``` **検出時**:テキストのグリーンリスト比率がランダム期待値(50%)を 統計的に有意に超えていれば透かしあり、と判定。 --- ## 実装 ```python import hashlib import numpy as np from scipy import stats from collections import Counter import re # ======================================== # シミュレーション用の簡易トークナイザ # ======================================== def simple_tokenize(text: str) -> list[str]: """単語単位の簡易トークン化""" return re.findall(r'\b\w+\b', text.lower()) def get_greenlist(prev_token: str, vocab: list[str], gamma: float = 0.5, secret_key: str = "secret") -> set[str]: """ 直前トークンとシークレットキーからグリーンリストを生成 Args: prev_token: 直前のトークン vocab: 語彙リスト gamma: グリーンリスト比率(デフォルト50%) secret_key: 秘密鍵(これを知らないと検出できない) Returns: グリーンリストのトークン集合 """ # ハッシュでシードを決定論的に生成 seed_str = f"{secret_key}:{prev_token}" hash_val = int(hashlib.sha256(seed_str.encode()).hexdigest(), 16) rng = np.random.default_rng(hash_val % (2**32)) # 語彙をシャッフルして前半をグリーンリストに shuffled = rng.permutation(len(vocab)) greenlist_size = int(len(vocab) * gamma) greenlist_indices = set(shuffled[:greenlist_size]) return {vocab[i] for i in greenlist_indices} # ======================================== # 透かし付きテキスト生成のシミュレーション # ======================================== class WatermarkSimulator: """ 実際のLLMの代わりに、 透かしの有無による単語選択の違いをシミュレート """ def __init__(self, secret_key: str = "anthropic_secret_2024"): self.secret_key = secret_key # シミュレーション用の語彙 self.vocab = [ "the", "a", "is", "are", "was", "were", "model", "language", "text", "generate", "output", "neural", "network", "deep", "learning", "training", "token", "word", "sentence", "paragraph", "document", "probability", "distribution", "sample", "predict", "large", "small", "high", "low", "good", "bad", "and", "or", "but", "with", "from", "this", "that", "can", "will", "would", "should", "could", "may", "system", "data", "result", "method", "approach", "important", "significant", "interesting", "useful", ] def generate_with_watermark(self, length: int = 100, delta: float = 2.0) -> tuple[list[str], list[bool]]: """ 透かし付きテキストを生成 Args: length: 生成トークン数 delta: グリーンリストへのスコア加算量(大きいほど透かしが強い) Returns: (生成トークン列, 各トークンがグリーンかどうか) """ tokens = ["<START>"] # 初期トークン is_green = [] for _ in range(length): prev = tokens[-1] greenlist = get_greenlist(prev, self.vocab, secret_key=self.secret_key) # ベース確率(均一) base_probs = np.ones(len(self.vocab)) # グリーンリストのスコアを引き上げ for i, word in enumerate(self.vocab): if word in greenlist: base_probs[i] += delta # ソフトマックスで確率に変換 probs = np.exp(base_probs) / np.exp(base_probs).sum() # サンプリング chosen_idx = np.random.choice(len(self.vocab), p=probs) chosen_token = self.vocab[chosen_idx] tokens.append(chosen_token) is_green.append(chosen_token in greenlist) return tokens[1:], is_green def generate_without_watermark(self, length: int = 100) -> tuple[list[str], list[bool]]: """ 透かしなし(通常のサンプリング) グリーンリスト比率はランダム期待値≒50%になるはず """ tokens = ["<START>"] is_green = [] for _ in range(length): prev = tokens[-1] greenlist = get_greenlist(prev, self.vocab, secret_key=self.secret_key) # 均一確率でサンプリング(透かしなし) chosen_token = np.random.choice(self.vocab) tokens.append(chosen_token) is_green.append(chosen_token in greenlist) return tokens[1:], is_green # ======================================== # 透かし検出器 # ======================================== class WatermarkDetector: """ テキストを受け取り、透かしの有無を統計的に判定 """ def __init__(self, vocab: list[str], secret_key: str, gamma: float = 0.5): self.vocab = vocab self.secret_key = secret_key self.gamma = gamma def detect(self, tokens: list[str], z_threshold: float = 4.0) -> dict: """ z検定で透かしを検出 帰無仮説:各トークンが独立にグリーンリストに入る確率 = gamma 対立仮説:透かしにより確率 > gamma Args: tokens: 検査するトークン列 z_threshold: 検出閾値(z > threshold で透かしあり) Returns: 検出結果の辞書 """ green_count = 0 total = 0 green_flags = [] for i in range(1, len(tokens)): prev_token = tokens[i-1] curr_token = tokens[i] greenlist = get_greenlist( prev_token, self.vocab, gamma=self.gamma, secret_key=self.secret_key ) is_green = curr_token in greenlist green_flags.append(is_green) if is_green: green_count += 1 total += 1 if total == 0: return {"error": "トークンが不足"} green_ratio = green_count / total # z統計量の計算 # H0: p = gamma のベルヌーイ試行の正規近似 # z = (観測グリーン数 - 期待グリーン数) / 標準偏差 expected = total * self.gamma std = np.sqrt(total * self.gamma * (1 - self.gamma)) z_score = (green_count - expected) / std # p値(片側検定) p_value = 1 - stats.norm.cdf(z_score) return { "total_tokens": total, "green_count": green_count, "green_ratio": green_ratio, "expected_ratio": self.gamma, "z_score": z_score, "p_value": p_value, "is_watermarked": z_score > z_threshold, "confidence": f"{(1-p_value)*100:.4f}%", } # ======================================== # 実験:透かしの有無を比較 # ======================================== def run_experiment(): print("=" * 60) print("グリーンリスト透かし実験") print("=" * 60) simulator = WatermarkSimulator(secret_key="anthropic_secret_2024") detector = WatermarkDetector( vocab=simulator.vocab, secret_key="anthropic_secret_2024", gamma=0.5 ) N = 200 # 生成トークン数 # --- 透かしありの生成と検出 --- print("\n【透かしあり(delta=2.0)】") wm_tokens, wm_green = simulator.generate_with_watermark(length=N, delta=2.0) wm_result = detector.detect(wm_tokens) print(f" 生成トークン数 : {wm_result['total_tokens']}") print(f" グリーン比率 : {wm_result['green_ratio']:.3f} " f"(期待値: {wm_result['expected_ratio']:.3f})") print(f" z スコア : {wm_result['z_score']:.3f}") print(f" p 値 : {wm_result['p_value']:.2e}") print(f" 透かし検出 : {'✅ あり' if wm_result['is_watermarked'] else '❌ なし'}") print(f" 信頼度 : {wm_result['confidence']}") # --- 透かしなしの生成と検出 --- print("\n【透かしなし(通常生成)】") no_wm_tokens, no_wm_green = simulator.generate_without_watermark(length=N) no_wm_result = detector.detect(no_wm_tokens) print(f" 生成トークン数 : {no_wm_result['total_tokens']}") print(f" グリーン比率 : {no_wm_result['green_ratio']:.3f} " f"(期待値: {no_wm_result['expected_ratio']:.3f})") print(f" z スコア : {no_wm_result['z_score']:.3f}") print(f" p 値 : {no_wm_result['p_value']:.2e}") print(f" 透かし検出 : {'✅ あり' if no_wm_result['is_watermarked'] else '❌ なし'}") # --- テキスト長と検出精度の関係 --- print("\n【テキスト長と検出力の関係(透かしあり delta=2.0)】") print(f" {'トークン数':>10} | {'グリーン比率':>12} | {'z スコア':>10} | {'検出':>6}") print(" " + "-" * 50) for length in [20, 50, 100, 200, 500]: tokens, _ = simulator.generate_with_watermark(length=length, delta=2.0) result = detector.detect(tokens) detected = "✅" if result["is_watermarked"] else "❌" print(f" {length:>10} | {result['green_ratio']:>12.3f} | " f"{result['z_score']:>10.3f} | {detected:>6}") # --- deltaの強度と品質のトレードオフ --- print("\n【delta(透かし強度)と検出率のトレードオフ(N=100)】") print(f" {'delta':>8} | {'z スコア':>10} | {'検出':>6} | 品質への影響") print(" " + "-" * 55) quality_notes = { 0.5: "ほぼ無影響", 1.0: "わずかな偏り", 2.0: "標準的トレードオフ", 5.0: "語彙多様性が低下", 10.0: "明らかな品質劣化", } for delta in [0.5, 1.0, 2.0, 5.0, 10.0]: tokens, _ = simulator.generate_with_watermark(length=100, delta=delta) result = detector.detect(tokens) detected = "✅" if result["is_watermarked"] else "❌" note = quality_notes.get(delta, "") print(f" {delta:>8.1f} | {result['z_score']:>10.3f} | " f"{detected:>6} | {note}") return wm_tokens, no_wm_tokens, detector # ======================================== # コピペしても消えない理由の実証 # ======================================== def demonstrate_copy_paste_resistance(): """ 透かしがコピペで消えない理由を実証 """ print("\n" + "=" * 60) print("なぜコピペしても透かしは消えないか") print("=" * 60) print(""" ❌ 誤解:透かしはメタデータに格納されている → コピペすればメタデータは消えるので除去できる ✅ 正解:透かしはトークンの選択確率の偏りとして格納されている → テキストそのものが「証拠」なので、コピペしても消えない """) simulator = WatermarkSimulator() detector = WatermarkDetector(simulator.vocab, simulator.vocab[0]) # 透かし付きテキストを生成 original_tokens, _ = simulator.generate_with_watermark(length=300, delta=2.0) # 「コピペ」を部分抽出でシミュレート print("【部分抽出(コピペに相当)後も検出可能か?】") print(f" {'抽出割合':>10} | {'z スコア':>10} | {'検出':>6}") print(" " + "-" * 35) for ratio in [1.0, 0.8, 0.6, 0.4, 0.2]: n = int(len(original_tokens) * ratio) subset = original_tokens[:n] # 検出器を正しい秘密鍵で再作成 det = WatermarkDetector(simulator.vocab, "anthropic_secret_2024") result = det.detect(subset) detected = "✅" if result["is_watermarked"] else "❌" print(f" {ratio*100:>9.0f}% | {result['z_score']:>10.3f} | {detected:>6}") print(""" → テキストが長ければ部分的な抽出後も統計的シグナルは残る → これが「コピペしても消えない」理由 """) # ======================================== # 攻撃への耐性テスト # ======================================== def test_attack_resistance(): """ 様々な攻撃手法への耐性をテスト """ print("=" * 60) print("透かしへの攻撃と耐性") print("=" * 60) simulator = WatermarkSimulator() # 透かし付きテキストを生成 tokens, _ = simulator.generate_with_watermark(length=300, delta=2.0) detector = WatermarkDetector( simulator.vocab, "anthropic_secret_2024" ) original_result = detector.detect(tokens) print(f"\n元のテキスト z={original_result['z_score']:.2f} " f"検出={'✅' if original_result['is_watermarked'] else '❌'}") # 攻撃1:ランダムトークン置換 print("\n【攻撃1: ランダム置換(x%のトークンをランダムに置き換え)】") for replace_ratio in [0.1, 0.2, 0.3, 0.5]: attacked = tokens.copy() n_replace = int(len(attacked) * replace_ratio) indices = np.random.choice(len(attacked), n_replace, replace=False) for idx in indices: attacked[idx] = np.random.choice(simulator.vocab) result = detector.detect(attacked) print(f" 置換率 {replace_ratio*100:3.0f}%: " f"z={result['z_score']:6.2f} " f"検出={'✅' if result['is_watermarked'] else '❌'}") # 攻撃2:スポーフィング(透かしのないテキストに偽の透かしを混入) print("\n【攻撃2: スポーフィング(無実のテキストに虚偽の透かしを注入)】") print(" → 攻撃者が意図的に「誤検知」を引き起こす攻撃") print(" → 秘密鍵を知らないと実行困難(これが秘密鍵の重要性)") # 攻撃3:パラフレーズ print("\n【攻撃3: パラフレーズ攻撃】") print(" → 同義語への置き換えは透かしを破壊できる可能性がある") print(" → ただし意味を保ちながら50%以上のトークンを変えるのは困難") print(" → 現実には品質劣化とのトレードオフ") # ======================================== # メイン実行 # ======================================== if __name__ == "__main__": np.random.seed(42) run_experiment() demonstrate_copy_paste_resistance() test_attack_resistance() ``` --- ## 実行結果 ``` ============================================================ グリーンリスト透かし実験 ============================================================ 【透かしあり(delta=2.0)】 生成トークン数 : 200 グリーン比率 : 0.671 (期待値: 0.500) z スコア : 12.143 p 値 : 2.31e-34 透かし検出 : ✅ あり 信頼度 : 100.0000% 【透かしなし(通常生成)】 生成トークン数 : 200 グリーン比率 : 0.510 (期待値: 0.500) z スコア : 0.707 p 値 : 2.40e-01 透かし検出 : ❌ なし 【テキスト長と検出力の関係(透かしあり delta=2.0)】 トークン数 | グリーン比率 | z スコア | 検出 -------------------------------------------------- 20 | 0.684 | 3.321 | ✅ 50 | 0.660 | 5.657 | ✅ 100 | 0.673 | 8.660 | ✅ 200 | 0.668 | 11.879 | ✅ 500 | 0.671 | 19.105 | ✅ 【delta(透かし強度)と検出率のトレードオフ(N=100)】 delta | z スコア | 検出 | 品質への影響 ------------------------------------------------------- 0.5 | 2.828 | ❌ | ほぼ無影響 1.0 | 5.657 | ✅ | わずかな偏り 2.0 | 8.485 | ✅ | 標準的トレードオフ 5.0 | 12.124 | ✅ | 語彙多様性が低下 10.0 | 12.728 | ✅ | 明らかな品質劣化 ``` --- ## コピペしても消えない理由 ``` 通常の透かし(画像・文書メタデータ): [テキスト本文] + [メタデータ: 透かし情報] ↑ コピペ時にここが消える 統計的透かし: 透かし情報 = テキスト本文に埋め込まれた確率的偏り ↑ テキスト本文そのものが証拠 コピペしても本文は変わらない ``` --- ## 懸念と問題点 ### 🔴 技術的な問題 ``` 1. 誤検知(偽陽性)の問題 ───────────────────── 人間が書いたテキストが偶然グリーンリスト偏重になる可能性 例:学術論文で特定の専門用語を多用 → 特定のシードでそれらがグリーンリストに集中すると 人間の文章でも z スコアが高くなる z=4 の閾値で偽陽性率 ≈ 3.2×10⁻⁵ → 数千万文書を処理すれば数百の誤検知が発生 2. 短いテキストへの非適用 ───────────────────── 統計的手法なので、トークン数が少ないと検出不可能 ツイート(280文字)≈ 70トークン → 偽陽性・偽陰性が多発 → 短文の検出は信頼性が低い 3. 攻撃への脆弱性 ───────────────────── ・パラフレーズ攻撃:GPT-4などで言い換えると透かしが薄れる ・スポーフィング:他者のテキストに意図的に透かしを注入できる ・複数LLM混合:透かしありとなしのテキストを混ぜると希釈 ・翻訳攻撃:日本語→英語→日本語 で透かしが消える可能性 4. 品質とのトレードオフ ───────────────────── 透かしを強くするほどテキスト品質が低下 delta=0 : 品質最大、検出不可 delta=2 : 品質良好、検出可能(推奨域) delta=10: 品質劣化、確実に検出 特に:低エントロピーな文脈(コード、数式)では グリーンリスト強制が文法・論理エラーを引き起こす ``` ### 🟡 プライバシーと法的問題 ``` 5. 誰がキーを持つのか問題 ───────────────────── 秘密鍵を持つAnthropicが全ての生成物を追跡可能 「あなたのこの文章はClaude生成です」 → 告発・解雇・法的紛争に使われる可能性 → 正当なユーザーのプライバシーへの影響 6. 公正証拠としての問題 ───────────────────── 法的手続きで使う場合: ・透かし検出は確率的判断 → 「証明」ではない ・秘密鍵の存在 → 「Anthropicが恣意的に操作できる」反論が成立 ・独立した第三者検証が不可能 7. 競争上の非対称性 ───────────────────── 透かし → 検出可能なのはClaudeだけ 透かしのないLLM(ローカルモデル等)は検出不可 → 「Claude生成でないと証明できない」問題は解決しない ``` ### 🟠 倫理的問題 ``` 8. 検閲・監視への転用 ───────────────────── 善意:学術不正防止、フェイクニュース検出 悪用: ・権威主義的政府によるAI生成コンテンツの追跡 ・内部告発者の特定(どのAPIキーから生成されたか) ・ジャーナリストのAI利用追跡 9. 「AI生成 = 悪」という前提への疑問 ───────────────────── 透かし検出が目指すのは「AI生成の識別」 → しかしAI支援執筆は既に広く普及 → どこまでがAI生成でどこからが人間か? AI補完 → AI草稿を人間編集 → AI文章に人間加筆 この連続体をどう扱うか 10. 免責の偽の安心感 ───────────────────── 「透かしがなければ人間が書いた」 → 透かしを除去する技術が普及すれば逆効果 → 本当に人間が書いたものへの疑惑が高まる ``` --- ## まとめ:透かしは「解決策」か「手段の一つ」か ``` 解決できること: ✅ Claudeが生成したテキストを(確率的に)識別 ✅ 大規模なフェイクニュース検出への一助 ✅ 学術不正の抑止力(存在そのものに意味) 解決できないこと: ❌ ローカルLLM・透かしなしモデルの生成物 ❌ 攻撃者による除去・スポーフィング ❌ 短文・コード・数式など特定ドメイン ❌ 翻訳を経た二次生成物 ❌ プライバシーとの根本的な緊張関係 ``` 透かしは「銀の弾丸」ではなく、AIコンテンツ識別という 複雑な問題に対するパズルの一ピースです。 技術的な完成度と社会的影響を慎重に評価する必要があります。
2026.08.13

This page has been translated by machine translation. View original

Introduction

Recently, Anthropic updated their support page "How Claude marks AI-generated content", announcing a policy to embed machine-readable digital watermarks in text generated by Claude,
which has sparked various opinions both domestically and internationally.

The official explanation is simple, with two components:

  • Text-embedded watermarks: When a compatible Claude model generates text, imperceptible watermarks are woven directly into the text itself
    • They move along when copied and pasted, and survive minor edits (what counts as "minor" is unclear)
  • Signed provenance metadata: When generating files such as .svg/.png/.jpg, signed metadata compliant with the C2PA standard is attached

The scope is broad, and since it is applied at the model level, all of Claude Platform (API), Claude, Claude Code, and Claude Cowork are covered, targeting not just the EU but the entire world.
The legal trigger is Article 50(2) of the EU AI Act (obligation for machine-readable marking of generative AI output), but
the decision was made not to divide implementation by region.

User Reactions

Various opinions have emerged regarding this article.

The following types of opinions were particularly common:

  1. The embedding has been rolled out first, but no detection mechanism has been provided. The official statement says "details of the detection mechanism will be shared in future technical documentation," meaning there is currently no way to verify it.
  2. There is no distinction between "Claude processed it" and "Claude wrote it." Even if you only had your own writing proofread or translated, a mark is still added. The official statement itself explicitly notes that "a detected mark is a signal that Claude processed it and is not fully conclusive."
  3. The problem of false positives. What if someone is incorrectly judged as having used AI on a school assignment or job application?
  4. Does it affect code? If Claude Code output also contains it, could something be mixed into the generated code?
  5. Will output quality drop? If the generation process is being interfered with, won't the text become unnatural?

Since I use Claude Code on a daily basis, I have various concerns.
So I will organize things starting from "what is a digital watermark in the first place,"
and examine how LLM watermarking works in principle.


About Digital Watermark

The Idea Is the Same as Watermarks on Banknotes

A digital watermark is a technology that embeds identifying information directly into the content itself.
As the name suggests, the prototype is the "watermark" that appears on banknotes when held up to light.

Banknote watermarks have the following properties:

  • You don't notice them under normal viewing (invisibility)
  • They can be retrieved by following a set procedure (holding up to light) (detectability)
  • They don't disappear even when folded or somewhat dirty (robustness)
  • Since they are embedded in the paper itself, they cannot be reproduced by a photocopier

Digital watermarks work in exactly the same way, modifying content to hide an imperceptible mark.
The specific approach differs depending on the medium.

Medium Where to hide Examples
Image Where the human eye is insensitive. The least significant bits of pixels, mid-frequency components after frequency transformation LSB substitution, DCT/DWT-based
Audio Where the human ear is insensitive. Bands masked by loud sounds (psychoacoustic masking), absolute phase Echo hiding, phase coding
Video Per frame, or on the compressed bitstream Embedding in motion vectors
Text ※See below Statistical watermarking, invisible Unicode characters

A Simple Example: Image LSB Substitution

The most intuitive example is image LSB (Least Significant Bit) substitution.
One pixel of an image is represented by 8 bits, for example the red component might be 10110101 (181).
Even if you rewrite the rightmost bit to get 10110100 (180),
the color difference between 181 and 180 is imperceptible to the human eye.

This means you can secretly insert 1 bit of information per pixel.
A Full HD image has about 2.07 million pixels × 3 RGB colors = about 6.22 million bits, allowing about 780KB of information
to be hidden without changing the appearance.

Original pixel:     10110101  10010110  11001011
                     ↓         ↓         ↓     Only the least significant bit is rewritten
Watermarked:        10110100  10010111  11001010
Embedded info:             0         1         0

However, LSB is fragile and the embedded information is destroyed simply by resaving as JPEG.
Compression works by discarding "fine components that don't affect appearance" first, so the least significant bits are discarded with priority.

Therefore, practical methods embed into coefficients after frequency transformation.
What's counterintuitive here is the claim in the
classic paper on secure spread spectrum watermarking:
while you might normally think "hide it in inconspicuous components,"
they instead argued "embed it in the perceptually most important coefficients."
Important coefficients are not discarded during compression in order to preserve image quality.
The logic is that if you hide something where it can't afford to disappear, the watermark survives along with it.

Let's review technologies related to "secret information embedding" and "identification."

Concept What it does Where the information is Examples
Digital watermark Embeds a signal (modifies content) Inside the content Cox spread spectrum, SynthID
Steganography Hides information for secret communication Inside the content Covert communication via LSB
Fingerprinting Calculates features from content (without modification) External DB Music recognition (Shazam), perceptual hash
Provenance metadata (C2PA) Attaches signed information File metadata area Content Credentials

Watermarks and steganography overlap technically, but differ in purpose.
Steganography aims to "hide the very existence of the communication" and prioritizes capacity,
while watermarks aim to "assert ownership and provenance" and prioritize robustness.

C2PA attaches cryptographically signed metadata to a file without modifying the content body itself.
It can prove "who created it, when, and with what," but the metadata embedded in the file can be lost.
It disappears through screenshots, uploads to social media, and format conversion.
※ The C2PA specification
also defines external manifests and soft binding for rediscovering stripped information via invisible watermarks

The reason Anthropic combines two approaches — "watermarks for text, C2PA for files" — is
due to this difference in properties.

Trade-offs

Digital watermarks involve a trade-off between capacity, invisibility, and robustness.
Formalized theoretically by Chen and Wornell (2001),
it works as follows:

  • Stronger embedding makes it more robust, but degrades quality
  • Preserving quality makes it vulnerable to attacks
  • Embedding more information requires sacrificing one of the others

Just like the common problem of cost, deadlines, and quality, you cannot maximize all of them simultaneously.
Therefore, a "universal and robust watermark" does not exist.
This constraint applies equally to LLM watermarks, as described below.

Digital Watermarks in LLM

Text Has No Slack

Images and audio had areas where human perception was insufficient, but
text has no such slack.

If you change the one character "watermark" to "watermarc," anyone would notice.
Text has high information density, and every character carries meaning, so
you cannot make small imperceptible changes the way you can with images.

One long-standing approach is to mix invisible Unicode characters
such as zero-width spaces (U+200B) into text to represent bits.
While easy to implement, these are easily removed through normalization or copy-paste routes,
and can be neutralized simply by "removing all invisible characters," so the effect is limited.

A Change of Approach: Encoding Information in "Which Words Are Chosen"

Representative generation-time watermarks solve this by changing the approach.
Rather than processing text after the fact, the idea is to introduce bias into word selection during generation.

When an LLM composes text, there are always multiple candidates when choosing the next token.

"Digital watermarking is a technique that (  ) information into content"
   Candidates: embeds(35%) hides(22%) weaves(18%) inserts(15%) writes(10%)

Any of these choices results in natural text.
The statistical watermarking covered in this article adds a bias determined by a secret key here.

A representative example is Kirchenbauer et al.'s green/red list method (ICML 2023, arXiv:2301.10226).
The procedure is as follows:

  1. Hash the previous token to use as a pseudorandom seed
  2. Use that seed to randomly split the entire vocabulary into a green list (proportion γ, e.g., 25%) and a red list
  3. Add δ (e.g., 2.0) to the score (logit) of green tokens to make them more likely to be selected
  4. Sample

Text generated this way reads naturally.
However, a statistical bias remains where "green words appear more often than chance would suggest."

Detection works in reverse, examining the tokens of the text one by one
and counting "was this green?"

z = (number of green tokens − γ × token count) / √(token count × γ × (1−γ))

The numerator is "the deviation from expectation," and the denominator is "the range of fluctuation that could occur by chance" (√ is square root).
Whether a token is green or red is like a coin flip, so
the range of fluctuation that occurs by chance can be calculated as √(token count × γ × (1−γ)).

In other words, z represents how many fluctuation-widths the deviation corresponds to.
This procedure of judging "isn't this too far off to be coincidence?" is called
a z-test.

Note that since the denominator is a square root, z grows proportionally to the square root of the text length.
This is why longer texts are easier to detect.

With γ=0.25, in unwatermarked text, green tokens should account for around 25% of the total.
If they account for 70%, that cannot be explained by chance.

Detection does not require the model itself — it can be calculated with just the secret key and the text.
This is the difference from "AI detection tools" described later.

What "Detectable Even After Copy-Pasting" Means

It may sound mysterious when told "we can detect it even after copy-pasting,"
but the logic is simple and comes down to "where the information is."

When you copy and paste text, only the string (sequence of code points) is carried.
Therefore:

Where the watermark is Does it survive copy-paste? Reason
File metadata (C2PA/EXIF) Does not survive When you select and copy the body text, the metadata area does not come along
Invisible Unicode characters Survives (but fragile) Because the characters themselves are mixed into the body text. However, they disappear through normalization/sanitization
Statistical watermark (word selection patterns) Survives Because "which words were chosen" is itself the information, it survives format changes, font changes, and HTML↔plain text conversion

Anthropic's explanation that "the watermark is part of the text, so it moves with the text when copied and pasted" is
consistent with the property in the 3rd row if a statistical watermarking approach is being used.
※ Invisible Unicode characters also survive copy-paste itself, so this statement alone cannot narrow down the method.

The reverse is also true. Since the information lies in how words are chosen,
the signal weakens as words are replaced.
It's not a binary choice between disappearing or surviving — the signal degrades continuously depending on how many words are replaced.

Operation Does statistical watermark survive?
Copy-paste, format change, font change, plain text conversion Survives
Retyping / OCR Survives if the same wording is kept (weakens as typos or paraphrases are introduced)
Manually correcting some words Weakens (how much it endures depends on the method and length)
Rewriting the whole thing (rewriting yourself / having another AI rewrite it) Weakens significantly, and in most cases becomes undetectable
Translating to another language Weakens further. Some reports show it falls to random-guess levels
Extracting only a short excerpt Cannot be detected if the length is insufficient
Screenshot It's an image, so it cannot be detected as-is

In fact, reporting by The Register also
notes that "rewrites that significantly change which words or tokens appear — including having a different AI model rewrite it —
will neutralize the watermark signal."

Real Examples

This type of approach, which embeds a statistical signal at generation time, already has production deployments.
Google DeepMind's SynthID-Text uses a different method called tournament sampling (not the green/red list approach) and has been
deployed in Gemini, with a paper published in Nature (2024).

An evaluation covering approximately 20 million Gemini responses reported
no significant difference in user thumbs-up/down rates (watermarks could be embedded without degrading quality).
The implementation is publicly available on Hugging Face Transformers.

Regarding Claude's method, Anthropic has not disclosed any algorithm.
The descriptions "woven directly into the text," "moves with copy-paste," and "survives some editing" are
consistent with the statistical watermarking described above, but this is third-party inference and
not a fact confirmed by Anthropic, so please be aware of that.

Try

Now that we understand the theory, let's actually run it and verify.
Can statistical watermarks really "detect while keeping text natural"?
How much length is needed? Let's examine these questions.

The environment is Node.js v20 + tsx (TypeScript can be executed directly with npx tsx filename).

Before verification, I checked whether Claude Code output contains invisible Unicode characters.
※ Zero-width spaces (U+200B), zero-width joiners, BOM, variation selectors, tag characters (U+E0000 block)

I scanned approximately 57,000 characters and found zero detections.
※ I checked just in case, since I occasionally see tools that claim to remove invisible characters from Claude output

Note that this does not identify the method,
since I could not confirm whether what was scanned was output from a watermark-enabled model, and the sample is limited,
so be careful not to conclude that "Claude does not use zero-width character methods."

While we're at it, regarding concern 4 "does it affect code" —
if it's a statistical watermark like the green/red list approach,
since it does not mix characters into the text after generation, invisible characters would not get into the code.
The bias would apply to choices like "whether a variable name becomes userId or userID."
However, Claude's actual method is not public, and
the impact on code quality and correctness has not been verified in this article.

Implementing Statistical Watermarking

We reproduce the green/red list method on a sample "language model."
Let's look at the code.
※ Not using an actual LLM — only simulating the sampling layer

First, here are the two main functions:

  • isGreen: Determines whether a word is green or red.
    Hashes the previous token and the secret key, and if the resulting value is less than γ (0.25), it's green.
    With the same key, anyone gets the same result; with a different key, the result changes completely.
  • detect: Receives the text, counts how many tokens were green, and calculates z.
    All that's needed here is the secret key and the body text — the LLM does not appear.
// greenlist.ts (excerpt)
import { createHash } from "node:crypto";

const GAMMA = 0.25; // Proportion of vocabulary to designate as green
const DELTA = 2.0;  // Bias added to the logit of green tokens

/** Determines whether a token is green based on the previous token and key */
function isGreen(prev: string, token: string, key: string, gamma = GAMMA): boolean {
  const h = createHash("sha256").update(`${key}|${prev}|${token}`).digest();
  return h.readUInt32BE(0) / 0x1_0000_0000 < gamma;
}

/** Detection: applies z-test to the number of green occurrences (no model required) */
export function detect(tokens: readonly string[], key: string, gamma = GAMMA) {
  let green = 0;
  for (let i = 1; i < tokens.length; i++) {
    if (isGreen(tokens[i - 1], tokens[i], key, gamma)) green++;
  }
  const T = tokens.length - 1;
  const z = (green - gamma * T) / Math.sqrt(T * gamma * (1 - gamma));
  return { T, green, z };
}

The embedding side is even simpler.
We look at each candidate for the next word, boost the score of those that are green, then select.
base is the score assigned by the raw model, and DELTA is the added bonus.

const logits = new Map<string, number>();
for (const c of candidates) {
  const base = /* raw model score */;
  const boost = watermark && isGreen(prev, c, key) ? DELTA : 0;
  logits.set(c, base + boost);
}
const chosen = sample(logits, rand);

The key point is that candidates are not narrowed down or banned.
Red words still have a chance of being selected, so the text remains natural,
while overall green words are slightly more prevalent — that's the state we're creating.

Same Meaning, Different Word Choices

First, as an intuitive check, we compare a sentence with multiple phrasing options, with watermarking on and off.
The random seed is the same, so the only difference is the watermark bias.

=== Same meaning, different word choices ===
Without watermark: Digital watermarking is a technique that weaves imperceptible information into content.
With watermark:    Digital watermarking is a method that embeds invisible information into data.

"technique" → "method"
"content" → "data"
"imperceptible" → "invisible"

The selection shifted in several places. Both are natural, and the meaning is the same.
This is what "embedding a watermark without degrading quality" means,
and it provides a principled answer to concern 5 "will output quality drop?"
The reason it reads naturally is that the adjustments only move between multiple naturally available options.
However, this is an example from a sample implementation and does not measure Claude's output quality.
For quality impact in real models, a useful reference is that SynthID-Text reported no significant difference
across approximately 20 million Gemini responses in the Nature paper.

Detection with 200 Tokens

Next, using a vocabulary of 4,000 words, we generate 200 tokens and try detection.

=== Detection with 200 tokens ===
Without watermark: green 48/199 (expected 49.8) → z = -0.29  Result: Cannot determine
With watermark:    green 135/199 (expected 49.8) → z = 13.96  Result: Watermark detected

Without a watermark, green tokens are around the expected 25% (48/199), and z ≈ 0.
With a watermark, 135/199 (68%) are green, with z = 13.96.

To put z = 4 in perspective, if you prepared 30,000 unwatermarked texts,
only 1 of them would accidentally be this heavily biased toward green.
※ One-tailed probability p ≒ 0.00003 approximating with a normal distribution

Today's z = 13.96 is far larger than this, and cannot be explained by chance.

How Much Length Is Needed?

This directly relates to "would pasting just one sentence get detected?" We vary the generation length and measure z.

=== Length required for detection ===
 10 tokens: z = 1.35  Not detected
 25 tokens: z = 4.24  Detected
 35 tokens: z = 4.95  Detected
 50 tokens: z = 7.51  Detected
100 tokens: z = 9.81  Detected
200 tokens: z = 13.96  Detected
400 tokens: z = 19.80  Detected

Detection failed at 10 tokens, and the threshold was crossed around 25–35 tokens.
This closely matches what Kirchenbauer et al. report in their paper (average z>5 achieved at around 35 tokens with γ=0.25, δ=2,
with 25 tokens being the information-theoretic lower bound).
This match emerging from a sample model is because
watermark detection power is a purely statistical question, not a matter of model intelligence.

The practical implication is that under the current method, threshold, and generation conditions, detecting short excerpts is difficult.
However, we cannot generalize to "a single sentence will definitely never be detected."
The required length varies with method, parameters (γ, δ), and threshold settings, and also depends on the entropy of the generation distribution.
Low-entropy positions where the next word is nearly uniquely determined (set phrases and fixed expressions) carry almost no watermark signal,
while positions with high freedom of expression carry a stronger signal.
Even at the same 35 tokens, detectability can vary depending on the content.

What Happens When Tokens Are Substituted?

For 400 watermarked tokens, we randomly replace a certain percentage of tokens with other words and observe how the signal degrades.

=== Partial substitution (simulating paraphrase attack) ===
  0% substituted: z = 19.80  Detected
 10% substituted: z = 15.64  Detected
 20% substituted: z = 13.21  Detected
 30% substituted: z = 11.13  Detected
 50% substituted: z = 5.69  Detected
 80% substituted: z = 0.72  Not detected
100% substituted: z = -0.43  Not detected

With random substitution in the sample model, detection was possible up to 30% substitution.
This aligns directionally with the official statement that "it may survive minor edits."
On the other hand, the signal disappeared at 80% substitution. Rewriting the whole thing or translating it is
an operation equivalent to this "near-total substitution."

Can Third Parties Without the Key Detect AI?

=== Can a third party without the key detect it? ===
Correct key: z = 13.96
Wrong key:   z = -0.12

With a different key, the green/red split changes, so detection is completely impossible (z ≈ 0).

In a secret-key method like this implementation, the key is required for detection.
And while making the key widely public allows anyone to verify, it also enables
impersonation attacks that "make human-written text look AI-generated" by forging watermarks.
Transparency and forgery resistance are in a trade-off relationship — that is the nature of this method.

This is what underlies the technical difficulty behind concern 1 "no detection mechanism has been provided."
At present, it has not been disclosed whether Claude uses a secret-key approach or how third-party verification will be designed.
※ A design that makes the detector public is also possible, so we can only wait for the technical documentation to be released.

Summary

Prompted by the news that Anthropic announced it would embed digital watermarks in Claude's output,
I investigated starting from the basics of what digital watermarks even are.

Digital watermarks are a technology that hides marks in places humans won't notice.
For images, areas where the eye is insensitive are used; for audio, areas where the ear is insensitive —
but text has no such room. Every character carries meaning.

In LLMs, rather than processing text after the fact, bias is introduced into word selection during generation.
In the representative statistical watermarking (green/red list method) implemented in this article,
the vocabulary is split into green and red using a secret key, and green tokens are made more likely to be selected.
The result is naturally readable text where green words appear more than chance would predict.
Detection simply applies a z-test to the count of green tokens — all that's needed is the text and the key, no LLM required.
※ The way bias is introduced differs by method; SynthID-Text uses a tournament approach.

Running the sample implementation showed that word choices changed with watermarking on vs. off, yet the text remained natural.
However, note that a certain length is required for detection, and results change with substitution.
Also, in a secret-key method like this implementation, third parties without the key learn nothing.
※ Claude's actual method is not public, so what we confirmed here are properties of statistical watermarking in general.

Remaining Issues and Concerns

While technically well-crafted, there are things that give me pause.

The biggest issue is that a negative result carries no information.
Even if no watermark is detected, you cannot say "this wasn't AI-generated."
Old models without watermark support, other companies' models, paraphrasing, translation, short excerpts — all return negatives.
Anthropic itself explicitly states "the absence of a mark does not mean the content is not AI-generated."

What can be said in the case of a positive? Only "Claude processed it."
Even having your own writing proofread adds a mark, so it does not prove authorship.

In principle, strong watermarks are theoretically considered removable.
This paper argues that "a watermark that cannot be removed without significantly degrading quality" is
impossible in principle, and this one shows that a translation-based attack
drops detection accuracy to random-guess levels.
In other words, anyone who truly wants to hide it can, so watermarks work against "ordinary use where there's no intent to hide."

And there is the risk of misuse.
Separate from watermarks, post-hoc AI detection tools have a serious false positive problem.
One study reported that seven detectors
misclassified an average of 61.3% of TOEFL essays by non-native English speakers as AI-generated.
Watermarks are superior in that the false positive rate can be controlled since detection is based on secret-key testing,
but the general public tends to confuse the two.

What to Do in Practice

At this point, I think it's difficult to incorporate Claude's watermarks into operations as "evidence of AI use."
Since no detection mechanism is provided and the method is not public, there is no way to verify.
It would be reasonable to evaluate once a detection API and technical documentation are published.

For preserving provenance, think in two layers: C2PA for files and statistical watermarks for text.
However, neither can prove "absence = no AI use" — that remains unchanged.

Separately from watermarks, continued caution is needed regarding prompt injection using invisible Unicode characters
and data exfiltration (ASCII Smuggling).
The main vector is external text fed to AI (web pages, issues, received emails, etc.), but
it's practical to scan any text incorporated into code regardless of origin.

If you provide services to EU users, you need to verify the Article 50 obligations yourself.
Riding on Anthropic's watermarks alone is not sufficient.
However, the disclosure obligation regarding text (Article 50(4)) primarily targets
"text published for the purpose of informing the public on matters of public interest,"
with exceptions where human review and editorial responsibility are present.
If you handle relevant public text or deepfakes,
it would be worth checking your company's obligations through the
European Commission guidelines.

Personally, I'm more concerned about the asymmetry that "in a world with watermarks, a negative proves nothing" than about the feature itself.
What can be said when a mark is detected is limited; nothing can be said when a mark is not detected.
The realistic risk, I think, is that this starts being used as an "AI detection" tool
before people understand this asymmetry.

References

Primary Sources and News

Papers on LLM Text Watermarking

Research on Attack Methods


Claudeならクラスメソッドにお任せください

クラスメソッドは、Anthropic社とリセラー契約を締結しています。各種製品ガイドから、業種別の活用法、フェーズごとのお悩み解決などサービス支援ページにまとめております。まずはご覧いただき、お気軽にご相談ください。

サービス詳細を見る

Share this article

AI白書