#!/usr/bin/env python3 """Second implementation of extensions/opus-dred.md (`wai.audio.opus.dred.int`, the integer profile `dred-int/1` of draft-ietf-mlcodec-opus-dred-07), over the corpus `live-conformance/dred/`. Written from the extension's text and the draft's. It shares no code with wai-rs: it reads an Opus packet's framing (RFC 6716 section 3.2) and its padding's extensions, decodes the DRED payload with RFC 6716's range decoder (section 4.1), transcribes the draft's tables for itself and checks them against the digest the extension states, reads the weights file, turns every weight into an integer from its float32 bits, computes the two activation tables, runs the decoder network in integer arithmetic as section 4.6 states it, rebuilds each stream's reconstruction under its loss pattern, and verifies each player's log with WAI's strict Ed25519 rule (claim-conformance/_ed25519.py) and BLAKE3 written from its specification (claim-conformance/_blake3.py). It reads section 8's codes and the digests of sections 4.1, 4.2, 4.5 and 4.6 from the extension. python3 -I dred_verify.py The weights file is the draft's ([DRED-Weights]); `dred/fetch.sh` fetches it. The verifier gives every vector of the corpus the outcome it states, checks that every reason of section 8 refuses a vector, and then mutates copies of the corpus and requires every copy to fail. Exit status 0 only if all of that holds. Python 3.8 or later; standard library only. """ import copy import hashlib import json import os import re import struct import sys from operator import mul HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance")) import _ed25519 as ED # noqa: E402 from _blake3 import blake3 # noqa: E402 EXTENSION = os.path.join(HERE, "..", "extensions", "opus-dred.md") CAPABILITY = "wai.audio.opus.dred.int" LOWERING = "dred-int/1" class Refused(Exception): def __init__(self, code, at=None): super().__init__(code) self.code = code self.at = at def sha(b): return hashlib.sha256(b).digest() def le32(v): return struct.pack("> 1) self.norm() def byte(self): if self.offs < len(self.buf): self.offs += 1 return self.buf[self.offs - 1] return 0 def norm(self): while self.rng <= 1 << 23: self.nbits += 8 self.rng <<= 8 sym = self.rem self.rem = self.byte() sym = ((sym << 8) | self.rem) >> 1 self.val = ((self.val << 8) + (255 & ~sym)) & ((1 << 31) - 1) def decode(self, ft): self.ext = self.rng // ft return ft - min(self.val // self.ext + 1, ft) def update(self, fl, fh, ft): s = self.ext * (ft - fh) self.val -= s self.rng = self.ext * (fh - fl) if fl > 0 else self.rng - s self.norm() def uint(self, ft): s = self.decode(ft) self.update(s, s + 1, ft) return s def icdf16(self, icdf): r = self.rng >> 15 s, k = self.rng, 0 while True: t, s = s, r * icdf[k] if self.val < s: k += 1 else: break self.val -= s self.rng = t - s self.norm() return k def tell(self): return self.nbits - self.rng.bit_length() # ---- section 3.3: the payload ---- SLOPE = [0, 2, 3, 4, 6, 8, 12, 16] CAP = 1 << 16 def coefficient(d, p0, r): if r == 0 or p0 == 255: return 0 s0 = 32768 - 128 * p0 sign = d.icdf16([s0, s0 // 2, 0]) if sign == 0: return 0 icdf = [max(7, 128 * r)] for i in range(1, 7): icdf.append(max(7 - i, (icdf[i - 1] * 128 * r) >> 15)) icdf.append(0) mag = 1 while True: v = d.icdf16(icdf) mag += v if mag > CAP: raise Refused("dred_latent_range") if v != 7: break return -mag if sign == 2 else mag def vector(d, q, decay, p0, dim): return [coefficient(d, p0[k][q], decay[k][q]) for k in range(dim)] def parse_payload(b): d = Dec(b) q0, dq = d.uint(16), d.uint(8) x = d.uint(2) e = d.uint(256) if x == 1 else 0 i = d.uint(32) qmax = 15 if q0 < 14 and dq != 0: n = 14 - q0 s = d.decode(2 * n) if s >= n: qmax = q0 + (s - n) + 1 d.update(s, s + 1, 2 * n) else: d.update(0, n, 2 * n) state = vector(d, q0, S_DECAY, S_P0, 50) lats = [] while 8 * len(b) - d.tell() > 7: q = min(qmax, q0 + (SLOPE[dq] * len(lats) + 8) // 16) lats.append((vector(d, q, L_DECAY, L_P0, 25), q)) return {"q0": q0, "dq": dq, "x": x, "e": e, "i": i, "qmax": qmax, "encoded": 32 * e + i, "state": state, "latents": lats} def symbol_bytes(p): b = bytes([p["q0"], p["dq"], p["x"], p["e"], p["i"], p["qmax"]]) + struct.pack("= len(p): raise Refused("packet_malformed") if p[i] < 252: return p[i], i + 1 if i + 1 >= len(p): raise Refused("packet_malformed") return 4 * p[i + 1] + p[i], i + 2 def framing(p): """(frames, frame duration, padding) of a packet under RFC 6716 section 3.4's rules.""" bad = Refused("packet_malformed") if not p: raise bad config, code = p[0] >> 3, p[0] & 3 ticks = frame_ticks(config) pad = b"" if code == 0: lens = [len(p) - 1] elif code == 1: if (len(p) - 1) % 2: raise bad lens = [(len(p) - 1) // 2] * 2 elif code == 2: n1, i = frame_len(p, 1) if n1 > len(p) - i: raise bad lens = [n1, len(p) - i - n1] else: if len(p) < 2: raise bad vbr, padded, m = p[1] & 0x80, p[1] & 0x40, p[1] & 0x3F if m == 0 or m * ticks > 48: raise bad i, plen = 2, 0 if padded: while True: if i >= len(p): raise bad b = p[i] i += 1 if b == 255: plen += 254 else: plen += b break if i + plen > len(p): raise bad end = len(p) - plen pad = p[end:] if vbr: lens = [] for _ in range(m - 1): n, i = frame_len(p, i) if i > end: raise bad lens.append(n) if i > end or sum(lens) > end - i: raise bad lens.append(end - i - sum(lens)) else: if i > end or (end - i) % m: raise bad lens = [(end - i) // m] * m if any(n > 1275 for n in lens): raise bad return len(lens), ticks, pad def skip(p, at, idb, trailing): """Where one extension's payload ends and its length bytes, or None past the padding.""" i, L = idb >> 1, idb & 1 if (i == 0 and L == 1) or i == 2: return at, 0 if 0 < i < 32: return (at + L, 0) if len(p) - at >= L else None if L == 0: return (len(p) - trailing, 0) if len(p) - at >= trailing else None j, n, hdr = at, 0, 0 while True: if j >= len(p): return None lace = p[j] j += 1 hdr += 1 n += lace if lace != 255: break if n > len(p) - j: return None return j + n, hdr def extensions(p, frames): """Every extension instance (id, frame, data) of the padding, in bitstream order.""" out = [] at = frame = 0 since, last_long, trailing = 0, None, 0 while at < len(p): start, idb = at, p[at] i, L = idb >> 1, idb & 1 r = skip(p, at + 1, idb, 0) if r is None: return out at, hdr = r if i == 1: inc = 1 if L == 0 else p[start + 1] if inc == 0: continue frame += inc if frame >= frames: return out since, last_long, trailing = at, None, 0 elif i == 2: src, j = [], since while j < start: end_j, _ = skip(p, j + 1, p[j], 0) if p[j] > 3: src.append((end_j, p[j])) j = end_j for f in range(frame + 1, frames): for end_j, b in src: if L == 0 and f + 1 >= frames and end_j == last_long: b &= ~1 r = skip(p, at, b, trailing) if r is None: return out out.append((b >> 1, f, p[at + r[1]:r[0]])) at = r[0] since, last_long = at, None if L == 0: frame += 1 if frame >= frames: return out elif i > 2: if i >= 32: last_long, trailing = at, 0 else: trailing += L out.append((i, frame, p[start + 1 + hdr:at])) return out def find_dred(p): frames, ticks, pad = framing(p) for i, f, data in extensions(pad, frames): if i == 32 and len(data) > 0: return data, f * ticks if i == 126 and len(data) > 2 and data[:2] == b"D\x0c": return data[2:], f * ticks return None # ---- section 4.1 and 4.4: the weights, as integers ---- WEIGHTS_SHA256 = "394f0ecf19926cebd678019dc94a54c88ae078340c16325069707f286cdf28a6" LAYERS = [("dense1", 26, 96, False), ("hidden_init", 50, 128, False), ("gru_init", 128, 320, True)] for _k, _n in zip(range(1, 6), [96, 192, 288, 384, 480]): LAYERS += [("gru%d_input" % _k, _n, 192, True), ("gru%d_recurrent" % _k, 64, 192, False)] LAYERS += [("glu%d" % _k, 64, 64, False) for _k in range(1, 6)] LAYERS += [("conv_dense%d" % _k, _n, 32, True) for _k, _n in zip(range(1, 6), [160, 256, 352, 448, 544])] LAYERS += [("conv%d" % _k, 64, 32, False) for _k in range(1, 6)] LAYERS += [("output", 576, 80, True)] def quantize(bits, f): """R_half_even of the float32's value times 2^f, from its bits (section 4.4).""" e, m = (bits >> 23) & 0xFF, bits & 0x7FFFFF if e == 255: raise Refused("model_mismatch") mant, exp = (m, -149) if e == 0 else (m | 0x800000, e - 150) k = exp + f if k >= 0: mag = mant << k elif -k >= 63: mag = 0 else: s = -k q, r, h = mant >> s, mant & ((1 << s) - 1), 1 << (s - 1) mag = q + 1 if (r > h or (r == h and q & 1)) else q return -mag if bits >> 31 else mag def records(blob): out, at = {}, 0 while at < len(blob): if len(blob) - at < 64 or blob[at:at + 4] != b"DNNw": raise Refused("model_mismatch") version, kind, size, block = struct.unpack_from("= 64 or name in out: raise Refused("model_mismatch") data = blob[at + 64:at + 64 + size] if kind in (0, 1): out[name] = list(struct.unpack("<%d%s" % (size // 4, "I" if kind == 0 else "i"), data)) else: out[name] = data at += 64 + block return out def layers(blob): """Each layer's float32 bit patterns: weights [out][in] and bias.""" arr = records(blob) out = {} for name, nin, nout, sparse in LAYERS: raw, bias = arr["dec_%s_weights_float" % name], arr["dec_%s_bias" % name] w = [[0] * nin for _ in range(nout)] if sparse: idx, p, wp = arr["dec_%s_weights_idx" % name], 0, 0 for og in range(0, nout, 8): n = idx[p] p += 1 for _ in range(n): pos = idx[p] p += 1 if pos % 4 or pos >= nin: raise Refused("model_mismatch") for i in range(4): for o in range(8): w[og + o][pos + i] = raw[wp + 8 * i + o] wp += 32 else: for i in range(nin): for o in range(nout): w[o][i] = raw[i * nout + o] out[name] = (w, bias) return out # ---- section 4.5: the activation tables ---- C0 = 4609434768280048980 def exp_q62(u): c, x = C0, 1 << 62 for k in range(15): if (u >> k) & 1: x = (x * c) >> 62 c = (c * c) >> 62 return x def tables(): tanh, sig = [0] * 65536, [0] * 65536 for v in range(-32767, 32768): x = exp_q62(abs(v)) b = (1 << 62) + x a = (1 << 15) * ((1 << 62) - x) t = min(32767, (2 * a + b) // (2 * b)) tanh[v + 32768] = -t if v < 0 else t a = 1 << 77 if v >= 0 else (1 << 15) * x sig[v + 32768] = min(32767, (2 * a + b) // (2 * b)) return tanh, sig def table_digest(t): return hashlib.sha256(b"".join(struct.pack("> s def clamp(v, lo=-32767, hi=32767): return lo if v < lo else hi if v > hi else v class Net: def __init__(self, blob): L = layers(blob) self.tanh, self.sig = tables() def q(name, bias_frac): w, b = L[name] return [[quantize(x, 24) for x in row] for row in w], [quantize(x, bias_frac) for x in b] self.l = {} for name, _, _, _ in LAYERS: if name.startswith("gru") and name != "gru_init": continue self.l[name] = q(name, 40 if name in ("dense1", "hidden_init") else 39) for k in range(1, 6): wi, bi = q("gru%d_input" % k, 39) wr, br = q("gru%d_recurrent" % k, 39) self.l["gates%d" % k] = ([wi[o] + wr[o] for o in range(128)], [bi[o] + br[o] for o in range(128)]) self.l["hin%d" % k] = (wi[128:], bi[128:]) self.l["hrec%d" % k] = (wr[128:], br[128:]) for name, (w, b) in self.l.items(): if any(not -(1 << 31) <= x < 1 << 31 for row in w for x in row): raise Refused("model_mismatch") def dense(self, x, name, shift, act=None): w, b = self.l[name] y = [rhu(sum(map(mul, row, x)) + bo, shift) for row, bo in zip(w, b)] if act is not None: y = [act[clamp(v) + 32768] for v in y] return y def init(self, state): a = self.dense(state, "hidden_init", 28, self.tanh) s = self.dense(a, "gru_init", 27, self.tanh) return [s[64 * k:64 * k + 64] for k in range(5)] def step(self, z, hs, cs): t = self.dense(z, "dense1", 28, self.tanh) nh, nc = [], [] for k in range(1, 6): h = hs[k - 1] g = self.dense(t + h, "gates%d" % k, 28, self.sig) zg, r = g[:64], g[64:] hin = self.dense(t, "hin%d" % k, 19) hrec = self.dense(h, "hrec%d" % k, 19) hc = [self.tanh[clamp(rhu(a + rhu(rr * b, 15), 8)) + 32768] for a, rr, b in zip(hin, r, hrec)] h2 = [clamp(c + rhu(zz * (hh - c), 15)) for c, zz, hh in zip(hc, zg, h)] gl = self.dense(h2, "glu%d" % k, 28, self.sig) t = t + [rhu(a * b, 15) for a, b in zip(h2, gl)] c = self.dense(t, "conv_dense%d" % k, 27, self.tanh) v = self.dense(cs[k - 1] + c, "conv%d" % k, 27, self.tanh) t = t + v nh.append(h2) nc.append(c) x = [clamp(v, -(1 << 31), (1 << 31) - 1) for v in self.dense(t, "output", 23)] return x, nh, nc def decode(self, p, n): """Rows 0 .. 4n-1 of a payload, newest first (section 4.7).""" def deq(x, s): s = max(1, s) return clamp((2 * x * (1 << 24) + s) // (2 * s), -(1 << 31) + 1, (1 << 31) - 1) q0 = p["q0"] hs = self.init([deq(x, S_SCALE[k][q0]) for k, x in enumerate(p["state"])]) cs = [[0] * 32 for _ in range(5)] out = [] for v, q in p["latents"][:n]: z = [deq(x, L_SCALE[k][q]) for k, x in enumerate(v)] + [(q - 8) << 13] x, hs, cs = self.step(z, hs, cs) out += [x[20 * j:20 * j + 20] for j in range(4)] return out def row_bytes(rs): return b"".join(le32(v) for r in rs for v in r) # ---- section 5: a stream under loss ---- def lost(seed, enter, stay, n): out, prev = [], False for i in range(n): u = int.from_bytes(sha(b"wai:dred-loss\x01" + seed + i.to_bytes(4, "big"))[:2], "big") prev = u < (stay if prev else enter) out.append(prev) return out def dred_of(p): try: found = find_dred(p) if found is None: return None pl = parse_payload(found[0]) except Refused: return None return pl, 16 - pl["encoded"] + found[1] def slots(packets, d): out, m = [], 0 while m < len(packets): if packets[m] is not None: m += 1 continue a = m while m < len(packets) and packets[m] is None: m += 1 rec = None if m < len(packets): got = dred_of(packets[m]) if got is not None: rec = (m, len(got[0]["latents"]), m * d + got[1]) for f in range(a, m): for h in (0, 1): e = f * d + 4 * (h + 1) src = None if rec is not None and rec[2] - e >= 0 and (rec[2] - e) // 4 < 4 * rec[1]: src = (rec[0], (rec[2] - e) // 4) out.append((f, h, src)) return out class Rebuild: """Rows of each recovering packet, decoded through as many latents as asked (cached).""" def __init__(self, net): self.net, self.cache = net, {} def rows(self, packet, n): key = (bytes(packet), n) if key not in self.cache: self.cache[key] = self.net.decode(dred_of(packet)[0], n) return self.cache[key] def buffer(ss, packets, rb): need = {} for _, _, src in ss: if src: need[src[0]] = max(need.get(src[0], 0), src[1] // 4 + 1) b = b"" for f, h, src in ss: b += struct.pack("= prev[0]["at"]) if not ok: raise Refused("player_chain_broken", ["player", i]) prev = (v, link) e = v["entry"] if e["type"] != "present": continue good = e["decoder"] == CAPABILITY and e["variant"] == LOWERING and e["tier"] == "decode-equivalence" good = good and any( n < len(objects) and objects[n] is not None and content_hash(objects[n]).hex() == e["subject"] and e["output"] == sha(buffer(ss[a:b], packets, rb)).hex() for n, a, b in rs ) if not good: raise Refused("present_output_irreproducible", ["player", i]) def check_payload(v, net): p = bytes.fromhex(v["packet"]) try: found = find_dred(p) if found is None: return "none", None pl = parse_payload(found[0]) except Refused as r: return {"refuse": r.code}, None rs = net.decode(pl, len(pl["latents"])) got = { "frame_offset": found[1], "q0": pl["q0"], "dq": pl["dq"], "extended": pl["e"] if pl["x"] == 1 else None, "indicator": pl["i"], "qmax": pl["qmax"], "dred_offset": 16 - pl["encoded"] + found[1], "latents": len(pl["latents"]), "symbols_sha256": sha(symbol_bytes(pl)).hex(), "rows": len(rs), "features_sha256": sha(row_bytes(rs)).hex(), "newest": rs[0], } return "dred", got def outcome_of(doc_case, stream, net, rb): try: check_case(doc_case, stream, net, rb) return "accept" except Refused as r: return {"refuse": r.code, "at": r.at} # ---- the extension's text ---- def section(text, n): m = re.search(r"\n## %d\. [^\n]*\n(.*?)(?=\n## |\Z)" % n, text, re.S) if not m: raise SystemExit("extensions/opus-dred.md has no section %d" % n) return m.group(1) def listed_codes(text): return re.findall(r"^\| `([a-z_]+)` \|", section(text, 8), re.M) def verify(doc, net, rb, text, tanh, sig): """Every statement of the corpus, or the first that fails, as a string.""" s4 = section(text, 4) for want in (WEIGHTS_SHA256, hashlib.sha256(TABLES).hexdigest(), table_digest(tanh), table_digest(sig), str(C0)): if want not in s4: return "section 4 does not state %s" % want if doc["weights"]["sha256"] != WEIGHTS_SHA256 or doc["tables_sha256"] != hashlib.sha256(TABLES).hexdigest(): return "the corpus names other weights or tables" lw = doc["lowering"] if lw["exp_c0"] != str(C0) or lw["tanh_table_sha256"] != table_digest(tanh) or lw["sigmoid_table_sha256"] != table_digest(sig): return "the corpus states other tables" for k in ("init_sha256", "step_sha256"): if lw[k] not in s4: return "section 4.6 does not state the corpus's %s" % k given = set() for v in doc["payloads"]: kind, got = check_payload(v, net) if kind != v["outcome"]: return "payload %s: %r, not %r" % (v["name"], kind, v["outcome"]) if isinstance(kind, dict): given.add(kind["refuse"]) if got is not None and got != v["dred"]: return "payload %s: states %r, decodes to %r" % (v["name"], v["dred"], got) for c in doc["cases"]: got = outcome_of(c, doc["streams"][c["stream"]], net, rb) if got != c["outcome"]: return "case %s: %r, not %r" % (c["name"], got, c["outcome"]) if isinstance(got, dict): given.add(got["refuse"]) missing = [c for c in listed_codes(text) if c not in given] if missing: return "no vector refuses %s" % ", ".join(missing) return None def mutations(doc): """Copies of the corpus with one defect each, every one of which must fail.""" def flip(h, at): return h[:at] + ("0" if h[at] != "0" else "1") + h[at + 1:] out = [] pv = [i for i, v in enumerate(doc["payloads"]) if v["outcome"] == "dred"] def m(label, f): d = copy.deepcopy(doc) f(d) out.append((label, d)) m("a payload's packet byte", lambda d: d["payloads"][pv[0]].__setitem__("packet", flip(d["payloads"][pv[0]]["packet"], 60))) m("a payload's row digest", lambda d: d["payloads"][pv[1]]["dred"].__setitem__("features_sha256", flip(d["payloads"][pv[1]]["dred"]["features_sha256"], 0))) m("a payload's newest row", lambda d: d["payloads"][pv[2]]["dred"]["newest"].__setitem__(3, d["payloads"][pv[2]]["dred"]["newest"][3] + 1)) m("a payload's symbol digest", lambda d: d["payloads"][pv[0]]["dred"].__setitem__("symbols_sha256", flip(d["payloads"][pv[0]]["dred"]["symbols_sha256"], 5))) first_refused = next(i for i, v in enumerate(doc["payloads"]) if isinstance(v["outcome"], dict)) m("a refused payload stated as none", lambda d: d["payloads"][first_refused].__setitem__("outcome", "none")) m("the tables' digest", lambda d: d.__setitem__("tables_sha256", flip(d["tables_sha256"], 0))) m("the tanh table's digest", lambda d: d["lowering"].__setitem__("tanh_table_sha256", flip(d["lowering"]["tanh_table_sha256"], 9))) m("a step graph's identity", lambda d: d["lowering"].__setitem__("step_sha256", flip(d["lowering"]["step_sha256"], 9))) acc = [i for i, c in enumerate(doc["cases"]) if c["outcome"] == "accept"] m("an accepted record's output", lambda d: d["cases"][acc[0]]["record"].__setitem__("output", flip(d["cases"][acc[0]]["record"]["output"], 3))) m("an accepted record's lost list", lambda d: d["cases"][acc[1]]["record"]["lost"].pop()) m("an accepted record's slot", lambda d: d["cases"][acc[0]]["record"]["slots"].__setitem__(0, [d["cases"][acc[0]]["record"]["slots"][0][0], 1 - d["cases"][acc[0]]["record"]["slots"][0][1]] + d["cases"][acc[0]]["record"]["slots"][0][2:])) m("a stream's loss seed", lambda d: d["streams"][doc["cases"][acc[0]]["stream"]]["loss"].__setitem__("seed", flip(d["streams"][doc["cases"][acc[0]]["stream"]]["loss"]["seed"], 2))) def recovering_object(d): objs = d["streams"][doc["cases"][acc[0]]["stream"]]["objects"] subject = json.loads(doc["cases"][acc[0]]["player"][0])["entry"]["subject"] i = next(i for i, o in enumerate(objs) if o is not None and content_hash(bytes.fromhex(o)).hex() == subject) objs[i] = flip(objs[i], len(objs[i]) - 9) m("a recovering packet's last bytes", recovering_object) def player_output(d): e = json.loads(d["cases"][acc[0]]["player"][0]) e["entry"]["output"] = flip(e["entry"]["output"], 0) d["cases"][acc[0]]["player"][0] = json.dumps(e, separators=(",", ":")) m("an accepted log's output", player_output) m("a refusal's code", lambda d: d["cases"][-2]["outcome"].__setitem__("refuse", "reconstruction_mismatch")) first_record = next(i for i, c in enumerate(doc["cases"]) if c["outcome"] != "accept" and c["outcome"]["at"] == ["record"]) m("a refusal's place", lambda d: d["cases"][first_record]["outcome"].__setitem__("at", ["player", 0])) return out def main(): if len(sys.argv) != 3: raise SystemExit("usage: python3 -I dred_verify.py ") blob = open(sys.argv[2], "rb").read() if hashlib.sha256(blob).hexdigest() != WEIGHTS_SHA256: raise SystemExit("weights_digest_mismatch: %s is not the draft's weights file" % sys.argv[2]) doc = json.load(open(os.path.join(sys.argv[1], "vectors.json"))) text = open(EXTENSION).read() if listed_codes(text) != ["packet_malformed", "dred_latent_range", "weights_digest_mismatch", "loss_pattern_mismatch", "reconstruction_mismatch"]: raise SystemExit("section 8 lists %r" % listed_codes(text)) net = Net(blob) rb = Rebuild(net) err = verify(doc, net, rb, text, net.tanh, net.sig) if err: print("FAIL: " + err) return 1 print("dred: %d payload vectors and %d cases reach their outcomes; every reason of section 8 refuses one" % (len(doc["payloads"]), len(doc["cases"]))) caught = 0 for label, d in mutations(doc): if verify(d, net, rb, text, net.tanh, net.sig) is None: print("FAIL: the mutation '%s' was accepted" % label) return 1 caught += 1 print("dred: every mutated copy (%d) fails" % caught) return 0 if __name__ == "__main__": sys.exit(main())