wire.go raw

   1  package gnarlring
   2  
   3  // CommitmentFrame is the wire format for a single child commitment (63 bytes).
   4  //
   5  //	Offset  Size  Field
   6  //	0       1     Index (uint8, 0..26)
   7  //	1       31    PubKey (Poly27, 9-bit unsigned)
   8  //	32      31    W (commitment, Poly27, 9-bit unsigned)
   9  const CommitmentFrameSize = 1 + PolyBytes + PolyBytes // 63
  10  
  11  func MarshalCommitmentFrame(cc *ChildCommitment) []byte {
  12  	buf := make([]byte, CommitmentFrameSize)
  13  	buf[0] = cc.Index
  14  	copy(buf[1:1+PolyBytes], cc.PubKey.MarshalBinary())
  15  	copy(buf[1+PolyBytes:], cc.W.MarshalBinary())
  16  	return buf
  17  }
  18  
  19  func UnmarshalCommitmentFrame(data []byte) *ChildCommitment {
  20  	if len(data) < CommitmentFrameSize {
  21  		return nil
  22  	}
  23  	cc := &ChildCommitment{Index: data[0]}
  24  	var err error
  25  	cc.PubKey, err = UnmarshalBinary(data[1 : 1+PolyBytes])
  26  	if err != nil {
  27  		return nil
  28  	}
  29  	cc.W, err = UnmarshalBinary(data[1+PolyBytes : CommitmentFrameSize])
  30  	if err != nil {
  31  		return nil
  32  	}
  33  	return cc
  34  }
  35  
  36  // EpochFrame is the full epoch broadcast (≈1783 bytes).
  37  //
  38  //	Offset  Size    Field
  39  //	0       8       Counter (uint64 LE)
  40  //	8       31      RootPK (Poly27, 9-bit unsigned)
  41  //	39      63×27   Frames [N]CommitmentFrame
  42  //	1740    47      RootSig (16B salt + 31B s2 at 9-bit signed)
  43  const EpochFrameHeaderSize = 8 + PolyBytes                    // 39
  44  const EpochFrameSize = EpochFrameHeaderSize + N*CommitmentFrameSize + sigBytes // 39 + 1701 + 47 = 1787
  45  
  46  func MarshalEpochFrame(es *EpochState) []byte {
  47  	buf := make([]byte, EpochFrameSize)
  48  	putUint64LE(buf[0:8], es.Counter)
  49  	copy(buf[8:8+PolyBytes], es.Coordinator.MarshalBinary())
  50  
  51  	off := EpochFrameHeaderSize
  52  	for i := 0; i < N; i++ {
  53  		if es.Agg.Children[i] != nil {
  54  			copy(buf[off:off+CommitmentFrameSize], MarshalCommitmentFrame(es.Agg.Children[i]))
  55  		}
  56  		off += CommitmentFrameSize
  57  	}
  58  
  59  	if es.RootSig != nil {
  60  		copy(buf[off:], es.RootSig.MarshalBinary())
  61  	}
  62  	return buf
  63  }
  64  
  65  func UnmarshalEpochFrame(data []byte) *EpochState {
  66  	if len(data) < EpochFrameSize {
  67  		return nil
  68  	}
  69  	c := getUint64LE(data[0:8])
  70  	pk, err := UnmarshalBinary(data[8 : 8+PolyBytes])
  71  	if err != nil {
  72  		return nil
  73  	}
  74  
  75  	es := StartEpoch(c, &NTRUPublicKey{H: pk})
  76  	off := EpochFrameHeaderSize
  77  	for i := 0; i < N; i++ {
  78  		cc := UnmarshalCommitmentFrame(data[off : off+CommitmentFrameSize])
  79  		if cc != nil {
  80  			es.Agg.Add(cc)
  81  		}
  82  		off += CommitmentFrameSize
  83  	}
  84  
  85  	sig, err := UnmarshalNTRUSig(data[off : off+sigBytes])
  86  	if err == nil {
  87  		es.RootSig = sig
  88  		es.finalized = true
  89  	}
  90  	return es
  91  }
  92  
  93  // EpochCheckFrame is the compact relay-path proof (113 bytes).
  94  //
  95  //	Offset  Size  Field
  96  //	0       8     Counter (uint64 LE)
  97  //	8       31    RootPK (Poly27, 9-bit unsigned)
  98  //	39      27    WCompressed (hash of all w_i + epoch, 27 bytes)
  99  //	66      47    RootSig (16B salt + 31B s2)
 100  //
 101  // Does NOT carry the full w_i list. The verifier must have cached the
 102  // epoch frame or trust the WCompressed binding.
 103  const EpochCheckFrameSize = 8 + PolyBytes + 27 + sigBytes // 113
 104  
 105  func MarshalEpochCheckFrame(es *EpochState) []byte {
 106  	buf := make([]byte, EpochCheckFrameSize)
 107  	putUint64LE(buf[0:8], es.Counter)
 108  	copy(buf[8:8+PolyBytes], es.Coordinator.MarshalBinary())
 109  	copy(buf[8+PolyBytes:8+PolyBytes+27], es.Agg.WCompressed(es.Counter))
 110  	if es.RootSig != nil {
 111  		copy(buf[8+PolyBytes+27:], es.RootSig.MarshalBinary())
 112  	}
 113  	return buf
 114  }
 115  
 116  func UnmarshalEpochCheckFrame(data []byte) (*EpochCheckFrame, error) {
 117  	if len(data) < EpochCheckFrameSize {
 118  		return nil, errShortData
 119  	}
 120  	cf := &EpochCheckFrame{Counter: getUint64LE(data[0:8])}
 121  	var err error
 122  	cf.PK, err = UnmarshalBinary(data[8 : 8+PolyBytes])
 123  	if err != nil {
 124  		return nil, err
 125  	}
 126  	copy(cf.WCompressed[:], data[8+PolyBytes:8+PolyBytes+27])
 127  	cf.Sig, err = UnmarshalNTRUSig(data[8+PolyBytes+27 : EpochCheckFrameSize])
 128  	if err != nil {
 129  		return nil, err
 130  	}
 131  	return cf, nil
 132  }
 133  
 134  // EpochCheckFrame is a deserialized compact frame.
 135  type EpochCheckFrame struct {
 136  	Counter     uint64
 137  	PK          *Poly27
 138  	WCompressed [27]byte
 139  	Sig         *NTRUSignature
 140  }
 141  
 142  func putUint64LE(buf []byte, v uint64) {
 143  	buf[0] = byte(v)
 144  	buf[1] = byte(v >> 8)
 145  	buf[2] = byte(v >> 16)
 146  	buf[3] = byte(v >> 24)
 147  	buf[4] = byte(v >> 32)
 148  	buf[5] = byte(v >> 40)
 149  	buf[6] = byte(v >> 48)
 150  	buf[7] = byte(v >> 56)
 151  }
 152  
 153  func getUint64LE(buf []byte) uint64 {
 154  	return uint64(buf[0]) | uint64(buf[1])<<8 | uint64(buf[2])<<16 |
 155  		uint64(buf[3])<<24 | uint64(buf[4])<<32 | uint64(buf[5])<<40 |
 156  		uint64(buf[6])<<48 | uint64(buf[7])<<56
 157  }
 158