gaussian.go raw

   1  package crypto
   2  
   3  import (
   4  	"git.mleku.dev/mleku/dendrite/pkg/axiom"
   5  	"git.mleku.dev/mleku/dendrite/pkg/dissolve"
   6  	"git.mleku.dev/mleku/dendrite/pkg/lattice"
   7  	"git.mleku.dev/mleku/dendrite/pkg/ratio"
   8  )
   9  
  10  // SiteMark records one lattice site's state in a ciphertext or sample.
  11  type SiteMark struct {
  12  	Index      uint64      // position in the lattice
  13  	Occupied   bool        // element bonded here?
  14  	TypeTag    string      // element type tag (if occupied)
  15  	ValueHash  Hamadryad   // Hamadryad hash of element value (if occupied)
  16  	Projection uint8       // 8-bit: [age(2)|key(3)|vertex(3)]
  17  	ProjPath   uint16      // rendering path index (token stream position)
  18  	Age        uint8       // 2-bit ADSR phase (0=Attack, 1=Decay, 2=Sustain, 3=Release)
  19  	Perm       uint8       // S_3 permutation index
  20  	LockIn     ratio.Ratio // bond strength
  21  }
  22  
  23  // NoiseSample records a dissolution event during encryption.
  24  type NoiseSample struct {
  25  	Index  uint64      // which site dissolved
  26  	TypeTag string     // what was there
  27  	LockIn ratio.Ratio // how strong the bond was before dissolution
  28  }
  29  
  30  // GaussianSampler produces discrete Gaussian samples using the
  31  // lattice's own dissolution dynamics. Elements that survive
  32  // dissolution are samples: their probability of survival at
  33  // a site is governed by ContextualLockIn relative to the threshold.
  34  type GaussianSampler struct {
  35  	Lattice   *lattice.Lattice
  36  	Width     ratio.Ratio // sigma parameter (controls spread)
  37  	Threshold ratio.Ratio // dissolution cutoff = sampling boundary
  38  }
  39  
  40  // NewSampler creates a sampler from an existing lattice.
  41  // The threshold determines the dissolution cutoff: elements with
  42  // ContextualLockIn below threshold are dissolved (become noise).
  43  func NewSampler(l *lattice.Lattice, threshold ratio.Ratio) *GaussianSampler {
  44  	return &GaussianSampler{
  45  		Lattice:   l,
  46  		Width:     threshold,
  47  		Threshold: threshold,
  48  	}
  49  }
  50  
  51  // Sample runs one dissolution scan and returns the surviving
  52  // bonding pattern as a sample from the discrete Gaussian.
  53  func (gs *GaussianSampler) Sample() []SiteMark {
  54  	// Run dissolution — elements below threshold are removed.
  55  	dissolved := make(chan axiom.Element, gs.Lattice.Size())
  56  	events := make(chan dissolve.Event, gs.Lattice.Size())
  57  
  58  	cfg := dissolve.Config{
  59  		Threshold: gs.Threshold,
  60  	}
  61  	dissolve.ScanOnce(gs.Lattice, cfg, dissolved, events)
  62  
  63  	// Drain channels.
  64  	close(dissolved)
  65  	close(events)
  66  	for range dissolved {
  67  	}
  68  	for range events {
  69  	}
  70  
  71  	// Collect surviving pattern.
  72  	return snapshot(gs.Lattice)
  73  }
  74  
  75  // NoiseVector produces a noise vector for LWE encryption by
  76  // running dissolution and recording which sites dissolved.
  77  // Length = lattice dimension. Values are lock-in depths of dissolved sites.
  78  func (gs *GaussianSampler) NoiseVector() ([]ratio.Ratio, []NoiseSample) {
  79  	dissolved := make(chan axiom.Element, gs.Lattice.Size())
  80  	events := make(chan dissolve.Event, gs.Lattice.Size())
  81  
  82  	cfg := dissolve.Config{
  83  		Threshold: gs.Threshold,
  84  	}
  85  	dissolve.ScanOnce(gs.Lattice, cfg, dissolved, events)
  86  
  87  	close(dissolved)
  88  	close(events)
  89  
  90  	// Drain dissolved elements.
  91  	for range dissolved {
  92  	}
  93  
  94  	// Collect noise samples from dissolution events.
  95  	var noise []NoiseSample
  96  	noiseVec := make([]ratio.Ratio, gs.Lattice.Size())
  97  	for ev := range events {
  98  		idx := uint64(ev.NodeID)
  99  		tag := ""
 100  		if ev.Element != nil {
 101  			tag = ev.Element.Type()
 102  		}
 103  		noise = append(noise, NoiseSample{
 104  			Index:  idx,
 105  			TypeTag: tag,
 106  			LockIn: ev.LockIn,
 107  		})
 108  		if int(idx) < len(noiseVec) {
 109  			noiseVec[idx] = ev.LockIn
 110  		}
 111  	}
 112  
 113  	return noiseVec, noise
 114  }
 115  
 116  // snapshot captures the current bonding pattern of the lattice.
 117  func snapshot(l *lattice.Lattice) []SiteMark {
 118  	nodes := l.Nodes()
 119  	marks := make([]SiteMark, len(nodes))
 120  	for i, n := range nodes {
 121  		marks[i] = SiteMark{
 122  			Index:      uint64(n.ID()),
 123  			Occupied:   n.Occupied(),
 124  			Projection: n.ProjectionByte(), // [age(2)|key(3)|vertex(3)]
 125  			ProjPath:   n.ProjectionPath(),
 126  			Age:        n.Age(),
 127  			Perm:       n.Permutation(),
 128  			LockIn:     n.LockIn(),
 129  		}
 130  		if n.Occupied() {
 131  			occ := n.Occupant()
 132  			marks[i].TypeTag = occ.Type()
 133  			marks[i].ValueHash = hashValue(occ.Value())
 134  		}
 135  	}
 136  	return marks
 137  }
 138