"""Public offline lab: Pedersen share checks and one-use Shamir Beaver gates.
The tiny group and deterministic fixtures are NOT secure deployment parameters.
Full tables are held only by this single-process reference simulator. Real parties
see their own shares and the explicitly broadcast messages. Triple correctness,
fresh independent masks and private authenticated setup are model assumptions.
"""
from dataclasses import dataclass
from itertools import combinations
import json

def need(ok,message):
 if not ok:raise ValueError(message)
def scalar(x,q):return type(x)is int and 0<=x<q
def prime(n):
 if type(n)is not int or n<2:return False
 d=2
 while d*d<=n:
  if n%d==0:return False
  d+=1
 return True

def evaluate(coef,x,q):
 z=0
 for c in reversed(coef):z=(z*x+c)%q
 return z
class FieldShares:
 def __init__(self,q,t,coordinates):
  need(prime(q),'prime field');need(type(t)is int and t>=0,'degree')
  self.q=q;self.t=t;self.x=tuple(coordinates);self.n=len(self.x);self.k=t+1
  need(self.n>=2*t+1,'honest reconstruction availability');need(all(scalar(x,q) and x!=0 for x in self.x),'nonzero field coordinates');need(len(set(self.x))==self.n,'distinct coordinates');self.pos={x:i for i,x in enumerate(self.x)}
 def coefficients(self,cs):
  need(len(cs)==self.k and all(scalar(c,self.q) for c in cs),'canonical coefficients');return tuple(cs)
 def share(self,cs):
  self.coefficients(cs);return tuple(evaluate(cs,x,self.q) for x in self.x)
 def values(self,ys):need(len(ys)==self.n and all(scalar(y,self.q) for y in ys),'canonical shares')
 def interpolator(self,points):
  points=tuple(points);need(len(points)==self.k,'threshold points');xs=tuple(x for x,y in points);need(len(set(xs))==self.k,'distinct points');need(all(scalar(x,self.q) and x!=0 and x in self.pos and scalar(y,self.q) for x,y in points),'point encoding')
  inv=[]
  for j,x in enumerate(xs):
   den=1
   for l,z in enumerate(xs):
    if j!=l:den=den*(x-z)%self.q
   inv.append(pow(den,-1,self.q))
  def at(z):
   total=0
   for j,(x,y) in enumerate(points):
    num=1
    for l,w in enumerate(xs):
     if j!=l:num=num*(z-w)%self.q
    total=(total+y*num*inv[j])%self.q
   return total
  return at
 def open(self,ys):
  self.values(ys);at=self.interpolator(zip(self.x[:self.k],ys[:self.k]));need(all(at(x)==y for x,y in zip(self.x,ys)),'degree promise');return at(0)
@dataclass(frozen=True)
class Group:
 p:int=23
 q:int=11
 g:int=2
 h:int=13
 def __post_init__(self):
  need(prime(self.p) and prime(self.q) and (self.p-1)%self.q==0,'prime-order subgroup parameters')
  need(all(type(x)is int and 1<x<self.p and pow(x,self.q,self.p)==1 for x in (self.g,self.h)),'nonidentity generators')
 def member(self,x):return type(x)is int and 0<x<self.p and pow(x,self.q,self.p)==1
 def commit(self,s,r):
  need(scalar(s,self.q) and scalar(r,self.q),'canonical opening');return pow(self.g,s,self.p)*pow(self.h,r,self.p)%self.p
class VSS:
 def __init__(self,group,degree,coordinates):self.g=group;self.f=FieldShares(group.q,degree,coordinates)
 def public(self,C):need(len(C)==self.f.k and all(self.g.member(c) for c in C),'public commitments')
 def deal(self,secret_coefficients,blind_coefficients):
  a=self.f.coefficients(secret_coefficients);b=self.f.coefficients(blind_coefficients)
  return tuple(self.g.commit(x,y) for x,y in zip(a,b)),tuple(zip(self.f.share(a),self.f.share(b)))
 def valid(self,C,x,pair):
  self.public(C)
  if not scalar(x,self.g.q) or x==0 or x not in self.f.pos or pair is None or type(pair)not in (tuple,list) or len(pair)!=2 or not all(scalar(z,self.g.q) for z in pair):return False
  rhs=1;power=1
  for c in C:rhs=rhs*pow(c,power,self.g.p)%self.g.p;power=power*x%self.g.q
  return self.g.commit(*pair)==rhs
 def qualify(self,C,delivered,repairs,corrupt=(),false_complaints=(),silent_accept=()):
  # Honest parties complain on missing/invalid shares. The listed faulty parties
  # may complain despite a valid pair, or say OK despite missing/invalid data.
  # Their private acceptance does not determine honest acceptance.
  self.public(C);need(len(delivered)==self.f.n,'one delivery slot per identity')
  declared=tuple(tuple(v) for v in (corrupt,false_complaints,silent_accept,repairs.keys()))
  for ids in declared:need(all(scalar(x,self.g.q) and x!=0 and x in self.f.pos for x in ids),'canonical declared identities')
  corrupt,extra,silent=(set(v) for v in declared[:3]);need(len(corrupt)<=self.f.t and extra<=corrupt and silent<=corrupt and not(extra&silent),'bounded faulty declarations')
  complaints=tuple(x for x,p in zip(self.f.x,delivered) if (not self.valid(C,x,p) and x not in silent) or x in extra)
  if len(complaints)>self.f.t:return dict(status='ABORT',reason='too many complaints',complaints=complaints)
  fixed=list(delivered)
  for x in complaints:
   pair=repairs.get(x)
   if not self.valid(C,x,pair):return dict(status='ABORT',reason='missing or invalid public repair',complaints=complaints)
   fixed[self.f.pos[x]]=tuple(pair)
  return dict(status='ACCEPT',complaints=complaints,shares=tuple(fixed),public_repairs=tuple((x,tuple(repairs[x])) for x in complaints))
 def reconstruct(self,C,submissions):
  self.public(C);submissions=tuple(submissions);ids=tuple(x for x,p in submissions);need(all(scalar(x,self.g.q) and x!=0 and x in self.f.pos for x in ids),'canonical submitted identities');need(len(set(ids))==len(ids),'unique submitted identities')
  good=tuple((x,p)for x,p in submissions if self.valid(C,x,p));bad=tuple(x for x,p in submissions if not self.valid(C,x,p))
  if len(good)<self.f.k:return dict(status='INSUFFICIENT',accepted=len(good),rejected=bad)
  selected=good[:self.f.k];s=self.f.interpolator((x,p[0])for x,p in selected)(0);r=self.f.interpolator((x,p[1])for x,p in selected)(0)
  need(self.g.commit(s,r)==C[0],'interpolated opening');return dict(status='OK',secret=s,blind=r,selected=tuple(x for x,p in selected),rejected=bad)
 def extract_trapdoor(self,C,first,second):
  a=self.reconstruct(C,first);b=self.reconstruct(C,second);need(a['status']==b['status']=='OK' and a['secret']!=b['secret'],'conflicting accepted reconstructions')
  delta=(b['blind']-a['blind'])%self.g.q;need(delta!=0,'different blind openings');alpha=(a['secret']-b['secret'])*pow(delta,-1,self.g.q)%self.g.q;need(pow(self.g.g,alpha,self.g.p)==self.g.h,'DL recovery');return alpha

class TriplePool:
 def __init__(self,field):self.f=field;self.table={};self.used=set()
 def install(self,identifier,A,B,C):
  need(type(identifier)is str and identifier and identifier not in self.table,'fresh identifier')
  for v in (A,B,C):self.f.open(v)
  # Structural checks do NOT certify C(0)=A(0)B(0) or fresh independence.
  self.table[identifier]=(tuple(A),tuple(B),tuple(C))
 def take(self,identifier):
  need(identifier in self.table and identifier not in self.used,'unused triple');self.used.add(identifier);return self.table[identifier]
 def multiply(self,identifier,X,Y,stop_after_open=False):
  self.f.open(X);self.f.open(Y);A,B,C=self.take(identifier);q=self.f.q
  D=tuple((x-a)%q for x,a in zip(X,A));E=tuple((y-b)%q for y,b in zip(Y,B));d=self.f.open(D);e=self.f.open(E)
  if stop_after_open:raise ValueError('stopped after opening: triple remains consumed')
  Z=tuple((c+d*b+e*a+d*e)%q for a,b,c in zip(A,B,C));z=self.f.open(Z)
  return dict(D=D,E=E,d=d,e=e,Z=Z,diagnostic_product=z,identifier=identifier)
 def preprocess(self,identifier,a_coeff,b_coeff,c_tail):
  a=self.f.coefficients(a_coeff);b=self.f.coefficients(b_coeff);need(len(c_tail)==self.f.t and all(scalar(v,self.f.q)for v in c_tail),'C random tail')
  c=(a[0]*b[0]%self.f.q,*c_tail);self.install(identifier,self.f.share(a),self.f.share(b),self.f.share(c));return c

def simulate(field,coalition,Xc,Yc,Zc,Dcoef,Ecoef):
 """Terminal-view simulator: Dcoef/Ecoef must be independent uniform draws."""
 f=field;indices=tuple(coalition);need(len(set(indices))==len(indices) and len(indices)<=f.t and all(type(i)is int and 0<=i<f.n for i in indices),'static coalition')
 need(len(Xc)==len(Yc)==len(Zc)==len(indices),'local view lengths');need(all(scalar(v,f.q)for a in (Xc,Yc,Zc)for v in a),'local values');D=f.share(Dcoef);E=f.share(Ecoef);d=Dcoef[0];e=Ecoef[0]
 A=tuple((x-D[i])%f.q for i,x in zip(indices,Xc));B=tuple((y-E[i])%f.q for i,y in zip(indices,Yc));C=tuple((z-d*b-e*a-d*e)%f.q for z,a,b in zip(Zc,A,B))
 return dict(coalition=indices,X=tuple(Xc),Y=tuple(Yc),A=A,B=B,C=C,D=D,E=E,Z=tuple(Zc))
def view(field,coalition,X,Y,triple,result):
 get=lambda vs:tuple(vs[i]for i in coalition)
 A,B,C=triple;return dict(coalition=tuple(coalition),X=get(X),Y=get(Y),A=get(A),B=get(B),C=get(C),D=result['D'],E=result['E'],Z=get(result['Z']))
def main():
 rejected=[]
 def reject(label,fn):
  try:fn()
  except ValueError:rejected.append(label);return
  raise RuntimeError('expected rejection: '+label)
 v=VSS(Group(),1,(1,2,3));f=v.f;C,pairs=v.deal((4,3),(2,5));need(C==(13,9) and pairs==((7,7),(10,1),(2,6)),'VSS fixture')
 broken=(pairs[0],(0,1),pairs[2]);qualified=v.qualify(C,broken,{2:pairs[1]});need(qualified['status']=='ACCEPT' and qualified['complaints']==(2,),'repair');restored=[]
 for subset in combinations(zip(f.x,qualified['shares']),2):
  out=v.reconstruct(C,subset);need(out['secret']==4,'all qualified subsets');restored.append(out)
 abort=v.qualify(C,(None,None,pairs[2]),{});need(abort['status']=='ABORT','threshold complaint abort')
 badrepair=v.qualify(C,broken,{2:(0,1)});need(badrepair['status']=='ABORT','invalid repair abort')
 false=v.qualify(C,pairs,{3:pairs[2]},corrupt=(3,),false_complaints=(3,));need(false['status']=='ACCEPT','own-coordinate complaint')
 silent=v.qualify(C,(pairs[0],pairs[1],None),{},corrupt=(3,),silent_accept=(3,));need(silent['status']=='ACCEPT' and v.reconstruct(C,zip(f.x,silent['shares']))['secret']==4,'faulty OK cannot prevent honest reconstruction')
 filtered=v.reconstruct(C,zip(f.x,broken));need(filtered['secret']==4 and filtered['rejected']==(2,),'filter forged reconstruction')
 forged=(0,4);need(v.valid(C,2,forged),'known-trapdoor forgery');first=((1,pairs[0]),(2,forged));second=((1,pairs[0]),(3,pairs[2]));alpha=v.extract_trapdoor(C,first,second);need(alpha==7 and v.reconstruct(C,first)['secret']==3,'conditional binding boundary')
 X=f.share((4,3));Y=f.share((7,2));pool=TriplePool(f);pool.preprocess('gate-1',(3,4),(5,6),(7,));triple=pool.table['gate-1'];product=pool.multiply('gate-1',X,Y);need(product['D']==(0,10,9) and product['E']==(9,5,1) and product['Z']==(5,4,3) and product['diagnostic_product']==6,'multiplication fixture')
 for c in [(),(0,),(1,),(2,)]:
  real=view(f,c,X,Y,triple,product);fake=simulate(f,c,real['X'],real['Y'],real['Z'],(1,10),(2,7));need(real==fake,'specific terminal record')
 local=tuple(x*y%f.q for x,y in zip(X,Y));wrong=f.interpolator(zip(f.x[:2],local[:2]))(0);need(local==(8,0,4) and wrong==5,'degree growth');reject('pointwise product is not degree one',lambda:f.open(local));reject('triple reuse',lambda:pool.multiply('gate-1',X,Y))
 pool.preprocess('burn',(3,4),(5,6),(7,));reject('stopped gate consumes triple',lambda:pool.multiply('burn',X,Y,True));reject('stopped triple cannot retry',lambda:pool.multiply('burn',X,Y))
 # Deliberately violate ideal setup in two separate demonstrations.
 duplicate=TriplePool(f);duplicate.install('duplicate',*triple);changed=f.share((8,1));again=duplicate.multiply('duplicate',changed,Y);leak=(again['d']-product['d'])%f.q;need(leak==(8-4)%f.q,'reuse leaks secret difference')
 incorrect=TriplePool(f);incorrect.install('wrong-c',triple[0],triple[1],f.share((5,7)));biased=incorrect.multiply('wrong-c',X,Y);need(biased['diagnostic_product']==7,'bad triple shifts product')
 correlated=TriplePool(f);correlated.preprocess('same-mask',(3,4),(3,4),(7,));same=correlated.multiply('same-mask',X,Y);difference=(same['d']-same['e'])%f.q;need(difference==(4-7)%f.q,'correlated masks reveal input difference')
 CV,cpairs=v.deal((5,7),(1,3));need(all(v.valid(CV,x,p) for x,p in zip(f.x,cpairs)),'VSS does not certify multiplication correlation')
 reject('noncanonical corrupt identity',lambda:v.qualify(C,pairs,{},corrupt=(True,)))
 reject('noncanonical complaint identity',lambda:v.qualify(C,pairs,{1:pairs[0]},corrupt=(1,),false_complaints=(True,)))
 reject('noncanonical silent identity',lambda:v.qualify(C,pairs,{},corrupt=(1,),silent_accept=(True,)))
 reject('noncanonical repair identity',lambda:v.qualify(C,pairs,{True:pairs[0]}))
 need(v.qualify(C,pairs,{},corrupt=(3,3))['status']=='ACCEPT','canonical duplicate declarations retain set semantics')
 reject('noncanonical interpolation coordinate',lambda:f.interpolator(((True,7),(2,10))))
 reject('noncanonical identity',lambda:v.reconstruct(C,((True,pairs[0]),(2,pairs[1]))));need(not v.valid(C,True,pairs[0]),'Boolean is not a coordinate encoding')
 reject('duplicate reconstruction identity',lambda:v.reconstruct(C,((1,pairs[0]),(1,pairs[0]))));reject('out-of-range coefficient',lambda:v.deal((15,3),(2,5)));reject('unit h',lambda:Group(h=1));reject('nonmember commitment',lambda:v.public((5,9)));reject('duplicate coordinates',lambda:FieldShares(11,1,(1,1,2)));reject('insufficient honest remainder',lambda:FieldShares(11,2,(1,2,3)))
 insufficient=v.reconstruct(C,((1,pairs[0]),));need(insufficient['status']=='INSUFFICIENT','not enough valid shares')
 print(json.dumps(dict(status='PASS',vss=dict(commitments=C,pairs=pairs,qualified=qualified,reconstructions=restored,abort=abort,bad_repair=badrepair,false_complaint=false,faulty_ok=silent,filtered=filtered,trapdoor=dict(forged=forged,secret_first=v.reconstruct(C,first)['secret'],secret_second=4,recovered_alpha=alpha)),multiplication=dict(X=X,Y=Y,triple=triple,result=product,local_product=local,wrong_linear_reconstruction=wrong,reuse_difference=leak,bad_triple_product=biased['diagnostic_product'],bad_c_valid_vss_commitments=CV,correlated_masks_difference=difference),rejections=rejected),ensure_ascii=False,indent=2))
if __name__=='__main__':main()
