| Server IP : 54.37.205.81 / Your IP : 216.73.216.76 Web Server : nginx/1.22.1 System : Linux vps-249481fa 6.1.0-50-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.176-1 (2026-07-02) x86_64 User : debian ( 1000) PHP Version : 8.2.32 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /proc/self/root/tmp/ |
Upload File : |
(() => {
"use strict";
const translations = {
zh: {
topline:"欧洲数字证据,连接中国版权服务",statusOnline:"公开验证已连接",
navHome:"首页",navCreate:"创建证据",navVerify:"验证证书",navDossier:"中欧档案",navPartner:"合作伙伴",
heroTitle:"别让你的创意,<em>成为别人的生意。</em>",
heroLead:"设计、文件和数字内容在发送给工厂、客户或合作伙伴之前,先建立一套可验证的跨境证据。",
heroProtect:"立即保护",heroVerify:"验证证书",trustSha:"SHA-256 + SM3 本地指纹",trustEu:"欧洲时间戳",trustChina:"中国服务接口已预留",
verified:"已验证",confirmed:"已确认",demoTitle:"跨境设计系列",levelEu:"欧洲证据",levelChina:"中国证据",connectorReady:"接口已准备",
available:"可用",apiIntegration:"API 接入",pilot:"试点",
proofEuTitle:"欧洲数字证据",proofEuText:"文件指纹、可验证证书、二维码、公开验证和完整技术记录。",
proofBtcTitle:"比特币时间证明",proofBtcText:"通过 OpenTimestamps 将文件指纹锚定到比特币网络。",
proofChinaTitle:"中国可信时间戳",proofChinaText:"为中国 TSA/API 预留独立连接层,仅传输哈希。",
proofDossierTitle:"中欧跨境档案",proofDossierText:"在同一页面连接证据、版本、接收方与交付记录。",
pratoKicker:"PRATO · MODA · TESSILE",pratoTitle:"设计发给工厂之前,先把证据锁定。",
pratoText:"CopyrightBridge 为纺织、时尚、皮具和产品设计提供一个明确的操作流程:先锁定文件和版本,再发送给生产商、实验室、客户或合作伙伴。",
startDossier:"开始创建档案",case1:"面料图案与花型",case1Text:"印花、纹理、图案、色彩方案",
case2:"服装设计与系列",case2Text:"草图、款式、样衣、系列文件",case3:"产品图片与目录",case3Text:"摄影、产品页、电商素材、目录",
case4:"技术文件与包装",case4Text:"规格书、版型、标签、包装设计",
createKicker:"先保护,再发送",createTitle:"创建一份跨境证据草案",
createIntro:"文件始终留在你的浏览器中。CopyrightBridge 本地计算 SHA-256 和 SM3,生成可交给 CopyrightChain 完成正式认证的操作草案。",
chooseFile:"选择文件",filePrivate:"文件不会上传到本页面",workType:"作品类型",typeTextile:"面料图案 / 花型",typeFashion:"服装设计 / 系列",
typePhoto:"摄影 / 产品图片",typeCatalog:"目录 / 电商内容",typePackaging:"包装 / 品牌设计",typeSoftware:"软件 / 技术文件",typeOther:"其他创作",
workTitle:"作品标题",rightsHolder:"权利人 / 企业",recipient:"接收方",destination:"目的地 / 市场",projectRef:"项目 / 订单编号",
sendReason:"发送目的",reasonProduction:"生产 / 打样",reasonProposal:"商业提案",reasonReview:"客户审核",reasonLicense:"许可 / 授权",reasonOther:"其他",
notes:"备注",localOnlyTitle:"本地处理",localOnlyText:"哈希在浏览器中计算,原始文件不会发送到 CopyrightBridge。",
generateDraft:"生成证据草案",clear:"清除",draftAwaiting:"等待创建档案",draftAwaitingText:"选择文件并填写项目数据,即可生成本地跨境证据草案。",
localDraft:"本地草案 · 尚未认证",certifyNow:"在 CopyrightChain 完成认证",downloadDraft:"下载草案 JSON",
verifyKicker:"公开验证",verifyTitle:"验证 CopyrightChain 证书",verifyIntro:"输入证书 ID、SHA-256、验证参数或完整验证链接。",verifyButton:"立即验证",
dossierTitle:"一份作品,一套跨境证据。",dossierText:"一个二维码连接欧洲数字证据、比特币时间证明、中国服务状态、版本历史与交付记录。",
feature1:"双语作品信息",feature2:"公开验证",feature3:"版本与交付记录",feature4:"中国服务连接层",shareWechat:"复制链接到微信",scanVerify:"扫码打开验证入口",
partnerKicker:"中国技术合作伙伴",partnerTitle:"把中国可信版权服务接入欧洲市场。",
partnerText:"CopyrightBridge 正在对接中国时间戳、版权登记、电子证据和侵权监测服务,建设中欧数字版权证据网关。",
contactPartner:"联系合作团队",partnerApi:"中国 TSA / API 时间戳",partnerRegistration:"版权登记与证书验证",partnerMarket:"欧洲客户入口与双语档案",
footerText:"连接欧洲数字证据与中国版权服务的独立跨境平台。",
verificationState:"证书状态",identity:"作品信息",title:"作品标题",owner:"权利人",date:"认证日期",type:"证书类型",layers:"证据层",
ccEvidence:"CopyrightChain 证据",bitcoin:"比特币 / OpenTimestamps",timestamp:"欧洲时间戳",chinaConnector:"中国证据连接器",
absent:"此证书未包含",block:"区块",provider:"服务商",readyPartner:"已准备接入合作伙伴 API",invalid:"未找到可验证证书",loading:"正在验证…",
fileRequired:"请先选择文件。",titleRequired:"请输入作品标题。",hashing:"正在本地计算 SHA-256 和 SM3…",copied:"链接已复制,可粘贴到微信。",copyFailed:"无法自动复制,请复制浏览器地址。",
draftType:"作品类型",draftDestination:"目的地",draftCreated:"创建时间"
},
en: {
topline:"European digital evidence connected to Chinese copyright services",statusOnline:"Public verification connected",
navHome:"Home",navCreate:"Create evidence",navVerify:"Verify certificate",navDossier:"China–Europe dossier",navPartner:"Partners",
heroTitle:"Do not let your creativity <em>become someone else’s business.</em>",
heroLead:"Before sending designs, files or digital content to a factory, client or partner, create a verifiable cross-border evidence dossier.",
heroProtect:"Protect now",heroVerify:"Verify certificate",trustSha:"Local SHA-256 + SM3 fingerprints",trustEu:"European timestamp",trustChina:"China service layer prepared",
verified:"Verified",confirmed:"Confirmed",demoTitle:"Cross-Border Design Collection",levelEu:"European Evidence",levelChina:"China Evidence",connectorReady:"Connector ready",
available:"AVAILABLE",apiIntegration:"API INTEGRATION",pilot:"PILOT",
proofEuTitle:"European digital evidence",proofEuText:"File fingerprint, verifiable certificate, QR, public verification and technical record.",
proofBtcTitle:"Bitcoin time proof",proofBtcText:"OpenTimestamps anchors the file fingerprint to the Bitcoin network.",
proofChinaTitle:"China trusted timestamp",proofChinaText:"A separate connector for Chinese TSA/API services, transmitting only the hash.",
proofDossierTitle:"China–Europe dossier",proofDossierText:"Connect evidence, versions, recipients and delivery records on one page.",
pratoKicker:"PRATO · FASHION · TEXTILE",pratoTitle:"Lock the evidence before the design reaches the factory.",
pratoText:"CopyrightBridge gives textile, fashion, leather and product-design businesses a clear workflow: lock the file and version first, then send it to producers, laboratories, clients or partners.",
startDossier:"Start the dossier",case1:"Textile patterns and prints",case1Text:"Prints, textures, patterns and colourways",
case2:"Fashion designs and collections",case2Text:"Sketches, styles, samples and collection files",case3:"Product images and catalogues",case3Text:"Photography, product pages, e-commerce assets and catalogues",
case4:"Technical files and packaging",case4Text:"Specifications, patterns, labels and packaging design",
createKicker:"PROTECT FIRST. SEND SECOND.",createTitle:"Create a cross-border evidence draft",
createIntro:"The file stays in your browser. CopyrightBridge calculates SHA-256 and SM3 locally and creates an operational draft ready to be certified through CopyrightChain.",
chooseFile:"Choose file",filePrivate:"The file is not uploaded to this page",workType:"Work type",typeTextile:"Textile pattern / print",typeFashion:"Fashion design / collection",
typePhoto:"Photography / product image",typeCatalog:"Catalogue / e-commerce content",typePackaging:"Packaging / brand design",typeSoftware:"Software / technical file",typeOther:"Other creative work",
workTitle:"Work title",rightsHolder:"Rights holder / company",recipient:"Recipient",destination:"Destination / market",projectRef:"Project / order reference",
sendReason:"Purpose of sending",reasonProduction:"Production / sampling",reasonProposal:"Commercial proposal",reasonReview:"Client review",reasonLicense:"Licence / authorisation",reasonOther:"Other",
notes:"Notes",localOnlyTitle:"LOCAL PROCESSING",localOnlyText:"Hashes are calculated in the browser. The original file is not sent to CopyrightBridge.",
generateDraft:"Generate evidence draft",clear:"Clear",draftAwaiting:"Waiting for a dossier",draftAwaitingText:"Choose a file and enter the project data to create a local cross-border evidence draft.",
localDraft:"LOCAL DRAFT · NOT YET CERTIFIED",certifyNow:"Complete certification on CopyrightChain",downloadDraft:"Download draft JSON",
verifyKicker:"PUBLIC VERIFICATION",verifyTitle:"Verify a CopyrightChain certificate",verifyIntro:"Enter a certificate ID, SHA-256, verification parameters or a full verification URL.",verifyButton:"Verify now",
dossierTitle:"One work. One cross-border evidence dossier.",dossierText:"One QR connects European digital evidence, Bitcoin time proof, China service status, version history and delivery records.",
feature1:"Bilingual work data",feature2:"Public verification",feature3:"Version and delivery records",feature4:"China service connector",shareWechat:"Copy link for WeChat",scanVerify:"Scan to open verification",
partnerKicker:"CHINESE TECHNOLOGY PARTNERS",partnerTitle:"Bring trusted Chinese copyright services into the European market.",
partnerText:"CopyrightBridge is connecting Chinese timestamp, copyright registration, electronic evidence and infringement-monitoring services to build a China–Europe digital copyright gateway.",
contactPartner:"Contact partnership team",partnerApi:"Chinese TSA / API timestamp",partnerRegistration:"Copyright registration and certificate verification",partnerMarket:"European client gateway and bilingual dossier",
footerText:"An independent cross-border platform connecting European digital evidence and Chinese copyright services.",
verificationState:"CERTIFICATE STATUS",identity:"Work identity",title:"Title",owner:"Owner",date:"Certification date",type:"Certificate type",layers:"Evidence layers",
ccEvidence:"CopyrightChain Evidence",bitcoin:"Bitcoin / OpenTimestamps",timestamp:"European Timestamp",chinaConnector:"China Evidence Connector",
absent:"Not included in this certificate",block:"Block",provider:"Provider",readyPartner:"Ready for partner API",invalid:"No verifiable certificate found",loading:"Verifying…",
fileRequired:"Choose a file first.",titleRequired:"Enter a work title.",hashing:"Calculating SHA-256 and SM3 locally…",copied:"Link copied. Paste it into WeChat.",copyFailed:"Automatic copy failed. Copy the browser address.",
draftType:"Work type",draftDestination:"Destination",draftCreated:"Created"
},
it: {
topline:"Prova digitale europea collegata ai servizi cinesi sul copyright",statusOnline:"Verifica pubblica collegata",
navHome:"Home",navCreate:"Crea una prova",navVerify:"Verifica",navDossier:"Dossier Cina–Europa",navPartner:"Partner",
heroTitle:"Non lasciare che la tua creatività <em>diventi il business di qualcun altro.</em>",
heroLead:"Prima di inviare design, file o contenuti digitali a un produttore, cliente o partner, crea un dossier transfrontaliero verificabile.",
heroProtect:"Proteggi ora",heroVerify:"Verifica certificato",trustSha:"Impronte locali SHA-256 + SM3",trustEu:"Marca temporale europea",trustChina:"Livello servizi Cina predisposto",
verified:"Verificato",confirmed:"Confermato",demoTitle:"Collezione di design transfrontaliera",levelEu:"Prova europea",levelChina:"Prova cinese",connectorReady:"Connettore pronto",
available:"DISPONIBILE",apiIntegration:"INTEGRAZIONE API",pilot:"PILOTA",
proofEuTitle:"Prova digitale europea",proofEuText:"Impronta del file, certificato verificabile, QR, verifica pubblica e registrazione tecnica.",
proofBtcTitle:"Prova temporale Bitcoin",proofBtcText:"OpenTimestamps ancora l’impronta del file alla rete Bitcoin.",
proofChinaTitle:"Marca temporale cinese",proofChinaText:"Connettore separato per TSA/API cinesi, trasmettendo soltanto l’hash.",
proofDossierTitle:"Dossier Cina–Europa",proofDossierText:"Collega prove, versioni, destinatari e registri di consegna in una sola pagina.",
pratoKicker:"PRATO · MODA · TESSILE",pratoTitle:"Blocca la prova prima che il design arrivi al produttore.",
pratoText:"CopyrightBridge offre a tessile, moda, pelletteria e design di prodotto un flusso chiaro: prima fissa file e versione, poi invia a produttori, laboratori, clienti o partner.",
startDossier:"Inizia il dossier",case1:"Pattern e disegni tessili",case1Text:"Stampe, texture, fantasie e varianti colore",
case2:"Design moda e collezioni",case2Text:"Bozzetti, modelli, campioni e file di collezione",case3:"Immagini prodotto e cataloghi",case3Text:"Fotografia, schede prodotto, e-commerce e cataloghi",
case4:"File tecnici e packaging",case4Text:"Specifiche, cartamodelli, etichette e confezioni",
createKicker:"PRIMA PROTEGGI. POI INVIA.",createTitle:"Crea una bozza di prova transfrontaliera",
createIntro:"Il file resta nel browser. CopyrightBridge calcola localmente SHA-256 e SM3 e genera una bozza operativa pronta per la certificazione su CopyrightChain.",
chooseFile:"Seleziona il file",filePrivate:"Il file non viene caricato in questa pagina",workType:"Tipo di opera",typeTextile:"Pattern / disegno tessile",typeFashion:"Design moda / collezione",
typePhoto:"Fotografia / immagine prodotto",typeCatalog:"Catalogo / contenuto e-commerce",typePackaging:"Packaging / identità di marca",typeSoftware:"Software / file tecnico",typeOther:"Altra opera creativa",
workTitle:"Titolo dell’opera",rightsHolder:"Titolare / impresa",recipient:"Destinatario",destination:"Destinazione / mercato",projectRef:"Progetto / ordine",
sendReason:"Motivo dell’invio",reasonProduction:"Produzione / campionatura",reasonProposal:"Proposta commerciale",reasonReview:"Revisione cliente",reasonLicense:"Licenza / autorizzazione",reasonOther:"Altro",
notes:"Note",localOnlyTitle:"ELABORAZIONE LOCALE",localOnlyText:"Gli hash vengono calcolati nel browser. Il file originale non viene inviato a CopyrightBridge.",
generateDraft:"Genera bozza di prova",clear:"Pulisci",draftAwaiting:"In attesa del dossier",draftAwaitingText:"Seleziona un file e inserisci i dati del progetto per creare una bozza locale di prova transfrontaliera.",
localDraft:"BOZZA LOCALE · NON ANCORA CERTIFICATA",certifyNow:"Completa la certificazione su CopyrightChain",downloadDraft:"Scarica bozza JSON",
verifyKicker:"VERIFICA PUBBLICA",verifyTitle:"Verifica un certificato CopyrightChain",verifyIntro:"Inserisci ID, SHA-256, parametri o URL completo di verifica.",verifyButton:"Verifica ora",
dossierTitle:"Un’opera. Un dossier di prova transfrontaliero.",dossierText:"Un QR collega prova digitale europea, prova temporale Bitcoin, stato dei servizi cinesi, versioni e registri di consegna.",
feature1:"Dati bilingui dell’opera",feature2:"Verifica pubblica",feature3:"Versioni e consegne",feature4:"Connettore servizi Cina",shareWechat:"Copia link per WeChat",scanVerify:"Scansiona per aprire la verifica",
partnerKicker:"PARTNER TECNOLOGICI CINESI",partnerTitle:"Porta i servizi fiduciari cinesi nel mercato europeo.",
partnerText:"CopyrightBridge collega timestamp, registrazione del copyright, prova elettronica e monitoraggio cinesi per costruire il gateway digitale Cina–Europa.",
contactPartner:"Contatta il team partnership",partnerApi:"Timestamp cinese TSA / API",partnerRegistration:"Registrazione e verifica del copyright",partnerMarket:"Ingresso clienti europei e dossier bilingue",
footerText:"Piattaforma transfrontaliera indipendente che collega prove digitali europee e servizi cinesi sul copyright.",
verificationState:"STATO CERTIFICATO",identity:"Identità dell’opera",title:"Titolo",owner:"Titolare",date:"Data di certificazione",type:"Tipo certificato",layers:"Livelli di prova",
ccEvidence:"Prova CopyrightChain",bitcoin:"Bitcoin / OpenTimestamps",timestamp:"Marca temporale europea",chinaConnector:"Connettore prova cinese",
absent:"Non presente in questo certificato",block:"Blocco",provider:"Provider",readyPartner:"Pronto per API partner",invalid:"Certificato verificabile non trovato",loading:"Verifica in corso…",
fileRequired:"Seleziona prima un file.",titleRequired:"Inserisci il titolo dell’opera.",hashing:"Calcolo locale SHA-256 e SM3 in corso…",copied:"Link copiato. Puoi incollarlo su WeChat.",copyFailed:"Copia automatica non riuscita. Copia l’indirizzo del browser.",
draftType:"Tipo opera",draftDestination:"Destinazione",draftCreated:"Creato"
}
};
let currentLang = localStorage.getItem("cb-lang") || "zh";
let currentDraft = null;
const q = (s, root=document) => root.querySelector(s);
const qa = (s, root=document) => [...root.querySelectorAll(s)];
const t = key => translations[currentLang]?.[key] ?? translations.en[key] ?? key;
function applyLanguage(lang){
currentLang = translations[lang] ? lang : "zh";
localStorage.setItem("cb-lang", currentLang);
document.documentElement.lang = currentLang === "zh" ? "zh-CN" : currentLang;
qa("[data-lang]").forEach(b => b.classList.toggle("active", b.dataset.lang === currentLang));
qa("[data-i18n]").forEach(el => {
const value = t(el.dataset.i18n);
if (value !== undefined) el.innerHTML = value;
});
if (currentDraft) renderDraft(currentDraft);
}
qa("[data-lang]").forEach(btn => btn.addEventListener("click", () => applyLanguage(btn.dataset.lang)));
q("#mobileMenu").addEventListener("click", () => {
const nav = q("#mainNav");
nav.classList.toggle("open");
q("#mobileMenu").setAttribute("aria-expanded", nav.classList.contains("open"));
});
qa("#mainNav a").forEach(a => a.addEventListener("click", () => q("#mainNav").classList.remove("open")));
const fileInput = q("#evidenceFile");
const fileDrop = q("#fileDrop");
fileInput.addEventListener("change", () => updateFileName());
["dragenter","dragover"].forEach(ev => fileDrop.addEventListener(ev, e => {e.preventDefault();fileDrop.classList.add("drag")}));
["dragleave","drop"].forEach(ev => fileDrop.addEventListener(ev, e => {e.preventDefault();fileDrop.classList.remove("drag")}));
fileDrop.addEventListener("drop", e => {
if (e.dataTransfer.files?.length) {
fileInput.files = e.dataTransfer.files;
updateFileName();
}
});
function updateFileName(){
const f = fileInput.files?.[0];
q("#fileName").textContent = f ? `${f.name} · ${formatBytes(f.size)}` : t("filePrivate");
}
function formatBytes(n){
if (!Number.isFinite(n)) return "—";
const u=["B","KB","MB","GB"]; let i=0,v=n;
while(v>=1024&&i<u.length-1){v/=1024;i++}
return `${v.toFixed(i?1:0)} ${u[i]}`;
}
function rotr(x,n){ return (x>>>n)|(x<<(32-n)); }
function rotl(x,n){ return (x<<n)|(x>>>(32-n)); }
function p0(x){ return x ^ rotl(x,9) ^ rotl(x,17); }
function p1(x){ return x ^ rotl(x,15) ^ rotl(x,23); }
function ff(x,y,z,j){ return j<16 ? x^y^z : (x&y)|(x&z)|(y&z); }
function gg(x,y,z,j){ return j<16 ? x^y^z : (x&y)|((~x)&z); }
function sm3Hex(input){
const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
const bitLen = bytes.length * 8;
const padLen = (56 - ((bytes.length + 1) % 64) + 64) % 64;
const padded = new Uint8Array(bytes.length + 1 + padLen + 8);
padded.set(bytes); padded[bytes.length] = 0x80;
const view = new DataView(padded.buffer);
const hi = Math.floor(bitLen / 0x100000000);
const lo = bitLen >>> 0;
view.setUint32(padded.length-8, hi, false);
view.setUint32(padded.length-4, lo, false);
let V = [0x7380166f,0x4914b2b9,0x172442d7,0xda8a0600,0xa96f30bc,0x163138aa,0xe38dee4d,0xb0fb0e4e];
const W = new Uint32Array(68), W1 = new Uint32Array(64);
for(let off=0; off<padded.length; off+=64){
for(let j=0;j<16;j++) W[j]=view.getUint32(off+j*4,false);
for(let j=16;j<68;j++) W[j]=(p1(W[j-16]^W[j-9]^rotl(W[j-3],15))^rotl(W[j-13],7)^W[j-6])>>>0;
for(let j=0;j<64;j++) W1[j]=(W[j]^W[j+4])>>>0;
let [A,B,C,D,E,F,G,H]=V;
for(let j=0;j<64;j++){
const T = j<16 ? 0x79cc4519 : 0x7a879d8a;
const SS1 = rotl(((rotl(A,12) + E + rotl(T,j%32))>>>0),7)>>>0;
const SS2 = (SS1 ^ rotl(A,12))>>>0;
const TT1 = (ff(A,B,C,j) + D + SS2 + W1[j])>>>0;
const TT2 = (gg(E,F,G,j) + H + SS1 + W[j])>>>0;
D=C; C=rotl(B,9)>>>0; B=A; A=TT1;
H=G; G=rotl(F,19)>>>0; F=E; E=p0(TT2)>>>0;
}
V=[A^V[0],B^V[1],C^V[2],D^V[3],E^V[4],F^V[5],G^V[6],H^V[7]].map(x=>x>>>0);
}
return V.map(x=>x.toString(16).padStart(8,"0")).join("");
}
function shaRor(x,n){ return (x>>>n)|(x<<(32-n)); }
function sha256Fallback(buffer){
const K = new Uint32Array([
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
]);
const bytes = new Uint8Array(buffer);
const bitLength = bytes.length * 8;
const totalLength = Math.ceil((bytes.length + 9) / 64) * 64;
const padded = new Uint8Array(totalLength);
padded.set(bytes);
padded[bytes.length] = 0x80;
const view = new DataView(padded.buffer);
view.setUint32(totalLength - 8, Math.floor(bitLength / 0x100000000), false);
view.setUint32(totalLength - 4, bitLength >>> 0, false);
const H = new Uint32Array([
0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,
0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19
]);
const w = new Uint32Array(64);
for(let off=0; off<totalLength; off+=64){
for(let i=0;i<16;i++) w[i]=view.getUint32(off+i*4,false);
for(let i=16;i<64;i++){
const s0=shaRor(w[i-15],7)^shaRor(w[i-15],18)^(w[i-15]>>>3);
const s1=shaRor(w[i-2],17)^shaRor(w[i-2],19)^(w[i-2]>>>10);
w[i]=(w[i-16]+s0+w[i-7]+s1)>>>0;
}
let [a,b,c,d,e,f,g,h]=H;
for(let i=0;i<64;i++){
const S1=shaRor(e,6)^shaRor(e,11)^shaRor(e,25);
const ch=(e&f)^((~e)&g);
const t1=(h+S1+ch+K[i]+w[i])>>>0;
const S0=shaRor(a,2)^shaRor(a,13)^shaRor(a,22);
const maj=(a&b)^(a&c)^(b&c);
const t2=(S0+maj)>>>0;
h=g; g=f; f=e; e=(d+t1)>>>0;
d=c; c=b; b=a; a=(t1+t2)>>>0;
}
H[0]=(H[0]+a)>>>0; H[1]=(H[1]+b)>>>0;
H[2]=(H[2]+c)>>>0; H[3]=(H[3]+d)>>>0;
H[4]=(H[4]+e)>>>0; H[5]=(H[5]+f)>>>0;
H[6]=(H[6]+g)>>>0; H[7]=(H[7]+h)>>>0;
}
return [...H].map(x=>x.toString(16).padStart(8,"0")).join("");
}
async function sha256Hex(buffer){
if(globalThis.crypto?.subtle){
try{
const digest = await globalThis.crypto.subtle.digest("SHA-256", buffer);
return [...new Uint8Array(digest)]
.map(b=>b.toString(16).padStart(2,"0"))
.join("");
}catch{}
}
return sha256Fallback(buffer);
}
q("#evidenceForm").addEventListener("submit", async e => {
e.preventDefault();
const file = fileInput.files?.[0];
const title = q("#workTitle").value.trim();
if (!file) return showDraftError(t("fileRequired"));
if (!title) return showDraftError(t("titleRequired"));
q("#generateDraft").disabled = true;
q("#generateDraft").textContent = t("hashing");
try {
const buffer = await file.arrayBuffer();
const [sha, sm3] = await Promise.all([sha256Hex(buffer), Promise.resolve(sm3Hex(buffer))]);
currentDraft = {
draft_id: `CB-DRAFT-${Date.now()}`,
created_at: new Date().toISOString(),
file_name: file.name,
file_size: file.size,
work_type: q("#workType").value,
title,
rights_holder: q("#rightsHolder").value.trim(),
recipient: q("#recipient").value.trim(),
destination: q("#destination").value,
project_reference: q("#projectRef").value.trim(),
send_reason: q("#sendReason").value,
notes: q("#notes").value.trim(),
sha256: sha,
sm3
};
localStorage.setItem("cb-last-draft", JSON.stringify(currentDraft));
renderDraft(currentDraft);
} catch (err) {
showDraftError(String(err?.message || err));
} finally {
q("#generateDraft").disabled = false;
q("#generateDraft").innerHTML = t("generateDraft");
}
});
function renderDraft(d){
q("#draftEmpty").hidden = true;
q("#draftContent").hidden = false;
q("#draftTitle").textContent = d.title || "—";
const typeLabel = q(`#workType option[value="${CSS.escape(d.work_type)}"]`)?.textContent || d.work_type;
q("#draftMeta").textContent = `${typeLabel} · ${d.file_name} · ${formatBytes(d.file_size)}`;
q("#draftSha").textContent = d.sha256;
q("#draftSm3").textContent = d.sm3;
q("#draftRecipient").textContent = d.recipient || "—";
q("#draftProject").textContent = d.project_reference || "—";
}
function showDraftError(msg){ alert(msg); }
q("#clearDraft").addEventListener("click", () => {
q("#evidenceForm").reset(); updateFileName(); currentDraft=null;
q("#draftContent").hidden=true; q("#draftEmpty").hidden=false;
});
q("#downloadDraft").addEventListener("click", () => {
if(!currentDraft) return;
const blob = new Blob([JSON.stringify(currentDraft,null,2)],{type:"application/json"});
const a=document.createElement("a"); a.href=URL.createObjectURL(blob);
a.download=`${currentDraft.draft_id}.json`; a.click(); URL.revokeObjectURL(a.href);
});
function parseVerifyInput(raw){
const input = raw.trim(), params = {};
if(!input) return params;
try{
if(/^https?:\/\//i.test(input)){
const u=new URL(input);
["id","hash","txid","code"].forEach(k=>{const v=u.searchParams.get(k);if(v)params[k]=v});
}else if(input.includes("=")||input.includes("&")){
let parts=input.replace(/^\?/,"").split("&");
if(parts[0]&&!parts[0].includes("=")){
const first=parts.shift(); params.id=/^\d{6}$/.test(first)?`CD-${first}`:first;
}
const sp=new URLSearchParams(parts.join("&"));
["id","hash","txid","code"].forEach(k=>{const v=sp.get(k);if(v)params[k]=v});
}else if(/^[a-f0-9]{64}$/i.test(input)) params.hash=input;
else params.id=/^\d{6}$/.test(input)?`CD-${input}`:input;
}catch{params.id=input}
return params;
}
function normalizeCertificate(data){
const x=data?.certificate??data?.data??data??{};
if(x.ok===false||x.verified===false) throw new Error(x.error||"VERIFY_NOT_FOUND");
const id=x.certificate_id??x.cert_id??x.id??x.uid??"—";
let title=x.title??x.filename??x.work_title??`CopyrightChain ${id}`;
if(/^Copyright_Cert_CD-\d+$/i.test(title)&&!title.includes(id)) title=`CopyrightChain ${id}`;
const bitcoinPresent=!!(x.btc_txid||x.btc_block_height||x.blockchain_status||x.ots_status||x.ots_proof_url||x.confirmed_at||x.anchored_at);
const timestampPresent=!!(x.timestamp_used||x.timestamp_available||x.timestamp_status||x.timestamp_provider||x.timestamp_issued_at);
return {
id,title,owner:x.owner??x.holder??x.author??x.full_name??"—",
date:x.created_at??x.certified_at??x.deposit_date??x.timestamp??"—",
hash:x.sha256??x.hash??x.file_hash??"—",type:x.cert_type??"—",
bitcoinPresent,bitcoinStatus:x.bitcoin_status??x.blockchain_status??x.ots_status??x.anchor_status??"",
block:x.btc_block_height??x.block_height??x.ots_block_height??"",
timestampPresent,timestampStatus:x.timestamp_status??"",timestampProvider:x.timestamp_provider??""
};
}
async function verifyCertificate(raw){
if(raw.trim().toUpperCase()==="CC-CB-DEMO-2026") return {
id:"CC-CB-DEMO-2026",title:t("demoTitle"),owner:"CopyrightBridge Demo",date:new Date().toISOString(),
hash:"8e4e2b9d0f3bd8f0baf03f4522ca2f0612da23e43f3d4a284b0b9a9a9d7a41",type:"demo",
bitcoinPresent:true,bitcoinStatus:"confirmed",block:"949491",timestampPresent:true,timestampStatus:"issued",timestampProvider:"Openapi / InfoCert"
};
const params=new URLSearchParams(parseVerifyInput(raw));
if(![...params].length) throw new Error("MISSING_QUERY");
const res=await fetch(`/cc-api/api/ai/verify/public?${params.toString()}`,{headers:{Accept:"application/json"}});
if(!res.ok) throw new Error(`VERIFY_HTTP_${res.status}`);
return normalizeCertificate(await res.json());
}
function fmtDate(v){
if(!v||v==="—")return "—";
const d=new Date(v); if(Number.isNaN(d.getTime())) return v;
return d.toLocaleString(currentLang==="zh"?"zh-CN":currentLang==="it"?"it-IT":"en-GB");
}
function dataRow(label,value,mono=false){
return `<div class="data-row"><span>${escapeHtml(label)}</span>${mono?`<code>${escapeHtml(String(value))}</code>`:`<b>${escapeHtml(String(value))}</b>`}</div>`;
}
function evidenceRow(label,status,detail,pending=false){
return `<div class="evidence-row ${pending?"pending":""}"><span class="evidence-symbol">${pending?"↗":"✓"}</span><div><b>${escapeHtml(label)}</b><small>${escapeHtml(status)}${detail?` · ${escapeHtml(detail)}`:""}</small></div></div>`;
}
function renderVerification(c){
const btcStatus=c.bitcoinPresent?(c.bitcoinStatus||t("verified")):t("absent");
const btcDetail=c.bitcoinPresent&&c.block?`${t("block")} ${c.block}`:"";
const tsStatus=c.timestampPresent?(c.timestampStatus||t("verified")):t("absent");
const tsDetail=c.timestampPresent&&c.timestampProvider?`${t("provider")}: ${c.timestampProvider}`:"";
q("#verifyResult").innerHTML=`
<div class="verified-banner">
<span class="verified-icon">✓</span>
<div><small>${escapeHtml(t("verificationState"))}</small><h3>${escapeHtml(t("verified"))}</h3></div>
<code>${escapeHtml(c.id)}</code>
</div>
<div class="verification-grid">
<div class="result-panel"><h3>${escapeHtml(t("identity"))}</h3>
${dataRow(t("title"),c.title)}
${dataRow(t("owner"),c.owner)}
${dataRow(t("date"),fmtDate(c.date))}
${dataRow(t("type"),c.type)}
${dataRow("SHA-256",c.hash,true)}
</div>
<div class="result-panel"><h3>${escapeHtml(t("layers"))}</h3>
${evidenceRow(t("ccEvidence"),t("verified"),"")}
${evidenceRow(t("bitcoin"),btcStatus,btcDetail,!c.bitcoinPresent)}
${evidenceRow(t("timestamp"),tsStatus,tsDetail,!c.timestampPresent)}
${evidenceRow(t("chinaConnector"),t("readyPartner"),"",true)}
</div>
</div>`;
q("#verifyResult").hidden=false;
}
async function runVerify(){
const raw=q("#verifyQuery").value;
const msg=q("#verifyMessage");
msg.hidden=false; msg.className="verify-message loading"; msg.textContent=t("loading");
q("#verifyResult").hidden=true;
try{
const result=await verifyCertificate(raw);
msg.hidden=true; renderVerification(result);
}catch(err){
msg.className="verify-message";msg.textContent=`${t("invalid")} (${err?.message||err})`;
}
}
q("#verifyButton").addEventListener("click",runVerify);
q("#verifyQuery").addEventListener("keydown",e=>{if(e.key==="Enter")runVerify()});
q("#demoVerify").addEventListener("click",()=>{q("#verifyQuery").value="CC-CB-DEMO-2026";runVerify()});
q("#shareWechat").addEventListener("click",async()=>{
const value=`${location.origin}${location.pathname}#verify`;
try{await navigator.clipboard.writeText(value);q("#shareStatus").textContent=t("copied")}
catch{q("#shareStatus").textContent=t("copyFailed")}
});
function escapeHtml(v){
return String(v??"").replace(/[&<>"']/g,m=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[m]));
}
const saved=localStorage.getItem("cb-last-draft");
if(saved){try{currentDraft=JSON.parse(saved);renderDraft(currentDraft)}catch{}}
applyLanguage(currentLang);
// SM3 known test vector: "abc" must equal 66c7f0f462eeedd9...
const test=sm3Hex(new TextEncoder().encode("abc"));
if(!test.startsWith("66c7f0f462eeedd9")) console.warn("SM3 self-test failed",test);
})();
/* CopyrightBridge v0.4.1 — reposition active section after language change */
(() => {
document.querySelectorAll("[data-lang]").forEach(button => {
button.addEventListener("click", () => {
window.setTimeout(() => {
const target = document.querySelector(window.location.hash);
if (target) {
target.scrollIntoView({
behavior: "instant",
block: "start"
});
}
}, 80);
});
});
})();