commitment.go raw

   1  package gnarlring
   2  
   3  import (
   4  	"crypto/rand"
   5  	"io"
   6  	"math"
   7  )
   8  
   9  // ChildCommitment holds one child's per-epoch short-vector commitment.
  10  // The child produces w = H_i * z (z freshly sampled Gaussian), and the
  11  // coordinator collects all 27 w_i to hash into the epoch target.
  12  type ChildCommitment struct {
  13  	Index  uint8   // child position 0..26
  14  	PubKey *Poly27 // child's NTRU public key H_i (31 bytes serialized)
  15  	W      *Poly27 // commitment w_i = H_i * z_i (31 bytes serialized)
  16  }
  17  
  18  // NewChildCommitment creates a commitment: samples fresh z, computes w = pk * z.
  19  func NewChildCommitment(index int, pk *NTRUPublicKey, rng io.Reader) *ChildCommitment {
  20  	if rng == nil {
  21  		rng = rand.Reader
  22  	}
  23  	gs := NewGaussSamplerFrom(DefaultSigma(), rng)
  24  	z := gs.SamplePoly()
  25  	w := Mul(pk.H, z)
  26  	return &ChildCommitment{
  27  		Index:  uint8(index),
  28  		PubKey: pk.H,
  29  		W:      w,
  30  	}
  31  }
  32  
  33  // AggregatedCommitment collects up to 27 child commitments by index.
  34  type AggregatedCommitment struct {
  35  	Children [N]*ChildCommitment // nil for unused slots
  36  	Count    int
  37  }
  38  
  39  // NewAggregatedCommitment returns an empty aggregation.
  40  func NewAggregatedCommitment() *AggregatedCommitment {
  41  	return &AggregatedCommitment{}
  42  }
  43  
  44  // Add inserts a child commitment at its index. Returns false on conflict.
  45  func (ac *AggregatedCommitment) Add(cc *ChildCommitment) bool {
  46  	if cc.Index >= N {
  47  		return false
  48  	}
  49  	if ac.Children[cc.Index] != nil {
  50  		return false
  51  	}
  52  	ac.Children[cc.Index] = cc
  53  	ac.Count++
  54  	return true
  55  }
  56  
  57  // Remove clears a slot.
  58  func (ac *AggregatedCommitment) Remove(index uint8) {
  59  	if index < N && ac.Children[index] != nil {
  60  		ac.Children[index] = nil
  61  		ac.Count--
  62  	}
  63  }
  64  
  65  // IsComplete reports whether all N slots are filled.
  66  func (ac *AggregatedCommitment) IsComplete() bool {
  67  	return ac.Count == N
  68  }
  69  
  70  // Target produces the sparse challenge polynomial for the coordinator to sign.
  71  // Hashes epoch counter + all w_i (zero for empty slots) + message. Returns a
  72  // dense Poly27 suitable as ffSampling target.
  73  func (ac *AggregatedCommitment) Target(msg []byte, epoch uint64) *Poly27 {
  74  	input := make([]byte, 0, 16+N*PolyBytes+len(msg))
  75  	input = append(input, []byte("gnarl-epoch-v1")...)
  76  	input = appendUint64LE(input, epoch)
  77  	for i := 0; i < N; i++ {
  78  		if ac.Children[i] != nil {
  79  			input = append(input, ac.Children[i].W.MarshalBinary()...)
  80  		} else {
  81  			input = append(input, make([]byte, PolyBytes)...)
  82  		}
  83  	}
  84  	input = append(input, msg...)
  85  
  86  	return hashBytesToPoly(input)
  87  }
  88  
  89  // WCompressed returns the GMid-style hash of all w_i for compact verification.
  90  func (ac *AggregatedCommitment) WCompressed(epoch uint64) []byte {
  91  	input := make([]byte, 0, 16+N*PolyBytes)
  92  	input = append(input, []byte("gnarl-wcomp-v1")...)
  93  	input = appendUint64LE(input, epoch)
  94  	for i := 0; i < N; i++ {
  95  		if ac.Children[i] != nil {
  96  			input = append(input, ac.Children[i].W.MarshalBinary()...)
  97  		} else {
  98  			input = append(input, make([]byte, PolyBytes)...)
  99  		}
 100  	}
 101  	return hashBytes(input)
 102  }
 103  
 104  func appendUint64LE(buf []byte, v uint64) []byte {
 105  	var tmp [8]byte
 106  	tmp[0] = byte(v)
 107  	tmp[1] = byte(v >> 8)
 108  	tmp[2] = byte(v >> 16)
 109  	tmp[3] = byte(v >> 24)
 110  	tmp[4] = byte(v >> 32)
 111  	tmp[5] = byte(v >> 40)
 112  	tmp[6] = byte(v >> 48)
 113  	tmp[7] = byte(v >> 56)
 114  	return append(buf, tmp[:]...)
 115  }
 116  
 117  // DefaultSigma returns the standard Gaussian sigma for the gnarl ring.
 118  func DefaultSigma() float64 {
 119  	return math.Sqrt(float64(N)) * 2.0
 120  }
 121  
 122  // hashBytes returns a 27-byte hash of input using FNV-style state chaining.
 123  func hashBytes(input []byte) []byte {
 124  	var state uint64 = 14695981039346656037
 125  	out := make([]byte, 27)
 126  	for i := 0; i < 27; i++ {
 127  		for _, b := range input {
 128  			state ^= uint64(b)
 129  			state *= 1099511628211
 130  		}
 131  		state ^= uint64(i)
 132  		state *= 1099511628211
 133  		out[i] = byte(state >> 32)
 134  	}
 135  	return out
 136  }
 137  
 138  // hashBytesToPoly returns a dense Poly27 derived from input.
 139  func hashBytesToPoly(input []byte) *Poly27 {
 140  	var state uint64 = 14695981039346656037
 141  	c := NewPoly27()
 142  	for i := 0; i < N; i++ {
 143  		for _, b := range input {
 144  			state ^= uint64(b)
 145  			state *= 1099511628211
 146  		}
 147  		state ^= uint64(i)
 148  		state *= 1099511628211
 149  		c.Coeffs[i] = uint16(state % uint64(Q))
 150  	}
 151  	return c
 152  }
 153  
 154  // salt generates a random 16-byte salt.
 155  func salt16() ([16]byte, error) {
 156  	var s [16]byte
 157  	_, err := rand.Read(s[:])
 158  	return s, err
 159  }
 160