""" WaveSign β€” Invisible File Authentication UI matching wavesign_merged.html: cool blue, dot-grid, card layout, JetBrains Mono """ import streamlit as st from PIL import Image import json, io, zipfile from core import sign_image, embed_watermark, verify_image, detect_mode from pdf_utils import sign_pdf, verify_pdf, get_pdf_page_count st.set_page_config( page_title="WaveSign β€” Invisible File Authentication", page_icon="πŸ”", layout="wide", initial_sidebar_state="collapsed" ) CSS = """ """ st.markdown(CSS, unsafe_allow_html=True) # ── wrapper + header ────────────────────────────────────────────────── st.markdown("""
Beta
System online
Invisible File Signing
Protect your files with an invisible signature
Upload your file, set a secret key, and get a signed copy you can share. Anyone with the key can instantly verify the file is authentic and untampered.
πŸ” No accounts needed
πŸ”‘ Key never stored
⚑ Instant verification
πŸ–Ό Images & PDFs
""", unsafe_allow_html=True) tab1, tab2, tab3, tab4 = st.tabs(["πŸ” Sign a File", "πŸ” Verify a File", "❓ How It Works", "⚑ API"]) # ══════════════════════════════════════════════════════════════════════ # TAB 1 β€” SIGN # ══════════════════════════════════════════════════════════════════════ with tab1: L, R = st.columns([1, 0.56], gap="large") with L: # Step 1 st.markdown("""
1 Upload Your File
Supports images (PNG, JPG, WEBP) and documents (PDF).
""", unsafe_allow_html=True) uploaded = st.file_uploader("file", type=["png","jpg","jpeg","webp","pdf"], key="su", label_visibility="collapsed") _mode = None if uploaded: mb = uploaded.size / 1048576 if mb > 20: st.warning(f"Large file ({mb:.1f} MB) β€” may be slow on free hosting.") elif mb > 8: st.info(f"{mb:.1f} MB β€” will take a few extra seconds.") if uploaded.name.lower().endswith(".pdf"): n = get_pdf_page_count(uploaded.getvalue()) st.markdown(f'πŸ“‘ PDF β€” {n} page{"s" if n!=1 else ""}', unsafe_allow_html=True) _mode = "document" else: _m = detect_mode(Image.open(io.BytesIO(uploaded.getvalue()))) _mode = _m if _m == "color": st.markdown('πŸ–ΌοΈ Color image', unsafe_allow_html=True) else: st.markdown('πŸ“„ Document', unsafe_allow_html=True) st.markdown("""
PNGJPG WEBPPDF
""", unsafe_allow_html=True) # Step 2 st.markdown("""
2 Create Your Secret Key
Choose any passphrase. You will need the same key to verify this file later. Keep it safe.
Passphrase
""", unsafe_allow_html=True) secret = st.text_input("key", placeholder="e.g. my-company-2024", key="sk", type="password", label_visibility="collapsed") st.markdown("""
Your key is never sent to any server. It stays on your device only.
""", unsafe_allow_html=True) sign_btn = st.button("Sign File β†’", key="sb") with R: if sign_btn and uploaded and secret: is_pdf = uploaded.name.lower().endswith(".pdf") base = uploaded.name.rsplit(".", 1)[0] if is_pdf: with st.spinner("Signing all pages…"): spdf, sigs, np_ = sign_pdf(uploaded.getvalue(), secret, strength=0.03) st.markdown(f"""
Signed Document
πŸ“‘
{np_} Page{"s" if np_!=1 else ""} Signed
Invisible signature embedded
""", unsafe_allow_html=True) from pdf2image import convert_from_bytes as _cfb pv = _cfb(spdf, dpi=72, first_page=1, last_page=min(2, np_)) st.markdown('
Page Preview
', unsafe_allow_html=True) pc = st.columns(len(pv)) for i,(col,pg) in enumerate(zip(pc,pv)): with col: st.markdown(f'
Page {i+1}
', unsafe_allow_html=True) st.image(pg, use_container_width=True) st.markdown('
', unsafe_allow_html=True) cn,_ = st.columns([3,1]) with cn: fname = st.text_input("Name", value=f"wavesign_{base}", key="dn", label_visibility="visible") fname = (fname or f"wavesign_{base}").strip() zb = io.BytesIO() with zipfile.ZipFile(zb,"w",zipfile.ZIP_DEFLATED) as zf: zf.writestr(f"{fname}.pdf", spdf) zf.writestr(f"{fname}.json", json.dumps(sigs, indent=2)) zb.seek(0) st.download_button("⬇ Download Signed Package", zb.getvalue(), file_name=f"{fname}.zip", mime="application/zip", use_container_width=True) st.markdown(f'
Contains: {fname}.pdf + verification file
', unsafe_allow_html=True) else: with st.spinner("Signing your file…"): img = Image.open(uploaded) m = detect_mode(img) wm = embed_watermark(img, secret, strength=0.015 if m=="color" else 0.03, mode=m) sig = sign_image(wm, secret, mode=m) st.markdown('
Your Signed File
', unsafe_allow_html=True) c1,c2 = st.columns(2) with c1: st.markdown('
Original
', unsafe_allow_html=True) st.image(img, use_container_width=True) with c2: st.markdown('
Signed
', unsafe_allow_html=True) st.image(wm, use_container_width=True) st.markdown(f'
{sig["sig_hash"][:48]}…
', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) cn,_ = st.columns([3,1]) with cn: fname = st.text_input("Name", value=f"wavesign_{base}", key="dn", label_visibility="visible", placeholder="e.g. contract_signed") fname = (fname or f"wavesign_{base}").strip() zb = io.BytesIO() with zipfile.ZipFile(zb,"w",zipfile.ZIP_DEFLATED) as zf: ib = io.BytesIO(); wm.save(ib, format="PNG") zf.writestr(f"{fname}.png", ib.getvalue()) zf.writestr(f"{fname}.json", json.dumps(sig, indent=2)) zb.seek(0) st.download_button("⬇ Download Signed Package", zb.getvalue(), file_name=f"{fname}.zip", mime="application/zip", use_container_width=True) st.markdown(f'
Contains: {fname}.png + verification file
', unsafe_allow_html=True) elif sign_btn: st.warning("Please upload a file and enter a secret key.") else: st.markdown("""
Your Signed File
✦
Ready to sign
""", unsafe_allow_html=True) # ══════════════════════════════════════════════════════════════════════ # TAB 2 β€” VERIFY # ══════════════════════════════════════════════════════════════════════ with tab2: L, R = st.columns([1, 0.56], gap="large") with L: st.markdown("""
1 Upload Signed File
Upload the signed image or PDF from your WaveSign package β€” not the original.
""", unsafe_allow_html=True) vf = st.file_uploader("Signed file", type=["png","jpg","jpeg","webp","pdf"], key="vu", label_visibility="collapsed") st.markdown('
', unsafe_allow_html=True) st.markdown("""
2 Upload Verification File
Upload the .json verification file from your WaveSign package. Keep it safe β€” it cannot be recovered if lost.
""", unsafe_allow_html=True) sf = st.file_uploader("Verification file", type=["json"], key="sfu", label_visibility="collapsed") st.markdown('
', unsafe_allow_html=True) st.markdown("""
3 Enter Your Secret Key
Passphrase
""", unsafe_allow_html=True) vk = st.text_input("vkey", placeholder="the key used when signing…", key="vk", type="password", label_visibility="collapsed") st.markdown('
', unsafe_allow_html=True) vbtn = st.button("Check Authenticity β†’", key="vb") with R: if vbtn and vf and sf and vk: try: sd = json.loads(sf.getvalue().decode("utf-8")) is_pdf = vf.name.lower().endswith(".pdf") is_pdf_s = isinstance(sd, list) if is_pdf and is_pdf_s: with st.spinner("Checking all pages…"): res = verify_pdf(vf.getvalue(), vk, sd) ok = all(r["is_valid"] for r in res) tot = len(res) fail = [str(r["page_index"]+1) for r in res if not r["is_valid"]] if ok: st.markdown(f"""
βœ…
Document Authentic
All {tot} pages verified β€” no changes detected
""", unsafe_allow_html=True) else: st.markdown(f"""
❌
Document Modified
Page{"s" if len(fail)>1 else ""} {", ".join(fail)} failed
""", unsafe_allow_html=True) if tot > 1: st.markdown('
Page Results
', unsafe_allow_html=True) for r in res: ic = "βœ…" if r["is_valid"] else "❌" cls = "mv-ok" if r["is_valid"] else "mv-err" lbl = "Authentic" if r["is_valid"] else "Modified" st.markdown(f'
Page {r["page_index"]+1}{ic} {lbl}
', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) elif not is_pdf and not is_pdf_s: image = Image.open(vf) with st.spinner("Checking your file…"): r = verify_image(image, vk, sd) if r["is_valid"]: st.markdown("""
βœ…
File is Authentic
Signature verified β€” no changes detected
""", unsafe_allow_html=True) else: reason = "File modified after signing" if not r.get("spatial_hash_match", True) else "Wrong key or different signing account" st.markdown(f"""
❌
Verification Failed
{reason}
""", unsafe_allow_html=True) st.image(image, use_container_width=True) else: st.error("Verification file doesn't match the uploaded file type.") except Exception as e: st.error(f"Error: {e}") elif vbtn: st.warning("Please complete all three steps.") else: st.markdown("""
Verification Result
πŸ”
Awaiting verification
""", unsafe_allow_html=True) # ══════════════════════════════════════════════════════════════════════ # TAB 3 β€” HOW IT WORKS # ══════════════════════════════════════════════════════════════════════ with tab3: C1, C2 = st.columns([3, 2], gap="large") with C1: st.markdown("""
✍ Signing a File
""", unsafe_allow_html=True) for n, t, d in [ ("1", "Upload your file", "Choose any image or PDF to protect. Format is detected automatically."), ("2", "Set a secret key", "Your passphrase locks the signature. Only someone with the same key can verify it. It never leaves your device."), ("3", "Download your package", "A ZIP with two files: the signed file (visually identical to the original) and a small verification file. Keep both together."), ]: st.markdown(f'
{n}
{t}
{d}
', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) st.markdown("""
πŸ” Verifying a File
""", unsafe_allow_html=True) for n, t, d in [ ("1", "Upload the signed file", "Use the signed copy from your package β€” not the original."), ("2", "Upload the verification file", "The small .json file from your package. Without it, verification isn't possible."), ("3", "Enter your key", "WaveSign instantly tells you whether the file is authentic or has been changed since signing."), ]: st.markdown(f'
{n}
{t}
{d}
', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) with C2: st.markdown('
Use Cases
', unsafe_allow_html=True) for ico, t, d in [ ("πŸ“„", "Contracts & Documents", "Share a signed PDF β€” any edit is instantly caught on verification"), ("πŸ–ΌοΈ", "Images & Photos", "Prove your image hasn't been cropped, filtered, or altered"), ("🎨", "Creative Work", "Sign before publishing β€” verify origin and integrity later"), ("πŸ”", "Sensitive Files", "Any modification after signing invalidates the signature"), ]: st.markdown(f'
{ico}
{t}
{d}
', unsafe_allow_html=True) st.markdown('
Important to Know
', unsafe_allow_html=True) for tip in [ "Always share the signed file, not the original", "Keep your verification file β€” it cannot be recovered", "Use the same key to sign and verify", "Signed files look identical to the original", ]: st.markdown(f'
β†’{tip}
', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) # ══════════════════════════════════════════════════════════════════════ # TAB 4 β€” API ACCESS # ══════════════════════════════════════════════════════════════════════ with tab4: AC, BC = st.columns([3, 2], gap="large") with AC: # Hero intro card st.markdown("""
⚑ REST API β€” Workflow Integration
The WaveSign REST API lets you embed invisible file signing and verification directly into your pipelines, scripts, and applications β€” no browser required. Every request is authenticated with your personal API token. Files are processed by the same proprietary signing engine that powers this app.
πŸ” Token-authenticated
πŸ“‚ multipart/form-data
⚑ JSON responses
πŸ–Ό Images & PDFs
""", unsafe_allow_html=True) # POST /sign card st.markdown("""
1 POST /sign
Endpoint
POST  https://roseluo-wavesign-api.hf.space/sign
Request β€” multipart/form-data
file      image/PDF to sign (PNG Β· JPG Β· WEBP Β· PDF)
key       your secret passphrase
Authorization  Bearer <your-api-token>
Response β€” application/json
signed_file   base64-encoded signed file
sig_file      base64-encoded signature file (JSON)
file_format   detected format (png Β· pdf Β· …)
status        "ok"
curl example
curl -X POST https://roseluo-wavesign-api.hf.space/sign \\ -H "Authorization: Bearer <your-api-token>" \\ -F "file=@document.pdf" \\ -F "key=my-secret-passphrase" \\ -o response.json
""", unsafe_allow_html=True) # POST /verify card st.markdown("""
2 POST /verify
Endpoint
POST  https://roseluo-wavesign-api.hf.space/verify
Request β€” multipart/form-data
file          the signed image or PDF
sig_file      the .json signature file from signing
key          your secret passphrase
Authorization  Bearer <your-api-token>
Response β€” application/json
is_valid    true / false
status      "authentic" or "modified"
detail      human-readable verification message
curl example
curl -X POST https://roseluo-wavesign-api.hf.space/verify \\ -H "Authorization: Bearer <your-api-token>" \\ -F "file=@document_signed.pdf" \\ -F "sig_file=@document_signed.json" \\ -F "key=my-secret-passphrase"
""", unsafe_allow_html=True) with BC: # Python example card st.markdown("""
Python Example
import requests, base64 API_TOKEN = "<your-api-token>" BASE_URL = "https://roseluo-wavesign-api.hf.space" HEADERS = {"Authorization": f"Bearer {API_TOKEN}"} # ── sign ───────────────────────────── with open("document.pdf", "rb") as f: r = requests.post( f"{BASE_URL}/sign", headers=HEADERS, files={"file": f}, data={"key": "my-secret"}, ) resp = r.json() signed = base64.b64decode(resp["signed_file"]) sig = base64.b64decode(resp["sig_file"]) with open("document_signed.pdf", "wb") as f: f.write(signed) with open("document_signed.json", "wb") as f: f.write(sig) # ── verify ─────────────────────────── with (open("document_signed.pdf","rb") as pf, open("document_signed.json","rb") as sf): r = requests.post( f"{BASE_URL}/verify", headers=HEADERS, files={"file": pf, "sig_file": sf}, data={"key": "my-secret"}, ) print(r.json()) # {"is_valid": true, "status": "authentic", ...}
""", unsafe_allow_html=True) # Request access card st.markdown("""
Request API Access
The WaveSign API is currently available on request. Reach out with a brief description of your use case and we'll send you an authentication token along with full integration documentation.
Contact
api@your-domain.com
Please include your intended request volume and a short description of your integration so we can set the right rate limits for your token.
""", unsafe_allow_html=True) # Key facts card st.markdown('
Key Facts
', unsafe_allow_html=True) for lbl, val, cls in [ ("Auth method", "Bearer token", "mv-neu"), ("File transfer", "multipart/form-data", "mv-neu"), ("Response", "JSON", "mv-neu"), ("Max file size", "20 MB", "mv-neu"), ("Formats", "PNG Β· JPG Β· PDF", "mv-neu"), ]: st.markdown(f'
{lbl}{val}
', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) # ── footer + close wrapper ──────────────────────────────────────────── st.markdown("""
WaveSign Β· Invisible File Authentication Β· Β© 2026 roseluo
""", unsafe_allow_html=True)