"""
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("""
WaveSign
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("""
""", 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'
', 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'
', 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'
', 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)