ephemeral_wire_test.go raw

   1  package crypto
   2  
   3  import (
   4  	"testing"
   5  )
   6  
   7  func TestEphemeralMarshalRoundTrip(t *testing.T) {
   8  	params := DefaultParams(Security128)
   9  	tags := []string{"word", "punct"}
  10  
  11  	kpAlice, err := Generate(params, tags, testFactory)
  12  	if err != nil {
  13  		t.Fatalf("Generate(Alice): %v", err)
  14  	}
  15  	kpBob, err := Generate(params, tags, testFactory)
  16  	if err != nil {
  17  		t.Fatalf("Generate(Bob): %v", err)
  18  	}
  19  
  20  	msg, err := PrepareExchange(
  21  		&kpAlice.Private, kpAlice.Public.Spore, kpBob.Public.Spore, params,
  22  	)
  23  	if err != nil {
  24  		t.Fatalf("PrepareExchange: %v", err)
  25  	}
  26  
  27  	// Marshal.
  28  	data, err := msg.MarshalEphemeral(params.N)
  29  	if err != nil {
  30  		t.Fatalf("MarshalEphemeral: %v", err)
  31  	}
  32  
  33  	if len(data) == 0 {
  34  		t.Fatal("marshalled data should not be empty")
  35  	}
  36  
  37  	// Unmarshal.
  38  	recovered, err := UnmarshalEphemeralMessage(data)
  39  	if err != nil {
  40  		t.Fatalf("UnmarshalEphemeralMessage: %v", err)
  41  	}
  42  
  43  	// Verify signature preserved.
  44  	if recovered.Signature == nil {
  45  		t.Fatal("recovered signature should not be nil")
  46  	}
  47  	// V2 stores only the first 16 bytes of the challenge (truncated binding).
  48  	for i := range 16 {
  49  		if recovered.Signature.Challenge[i] != msg.Signature.Challenge[i] {
  50  			t.Errorf("challenge byte %d: got %02x, want %02x",
  51  				i, recovered.Signature.Challenge[i], msg.Signature.Challenge[i])
  52  		}
  53  	}
  54  	// Commitment is not preserved in V2 (recomputed by verifier).
  55  
  56  	// Verify pattern length preserved.
  57  	if len(recovered.Pattern) != len(msg.Pattern) {
  58  		t.Errorf("pattern length = %d, want %d", len(recovered.Pattern), len(msg.Pattern))
  59  	}
  60  
  61  	// Count occupied sites in both.
  62  	origOcc := 0
  63  	for _, s := range msg.Pattern {
  64  		if s.Occupied {
  65  			origOcc++
  66  		}
  67  	}
  68  	recOcc := 0
  69  	for _, s := range recovered.Pattern {
  70  		if s.Occupied {
  71  			recOcc++
  72  		}
  73  	}
  74  	if recOcc != origOcc {
  75  		t.Errorf("occupied count = %d, want %d", recOcc, origOcc)
  76  	}
  77  
  78  	// Verify occupied sites have correct tags and projections.
  79  	for i, s := range msg.Pattern {
  80  		if !s.Occupied {
  81  			continue
  82  		}
  83  		r := recovered.Pattern[i]
  84  		if !r.Occupied {
  85  			t.Errorf("site %d: should be occupied", i)
  86  			continue
  87  		}
  88  		if r.TypeTag != s.TypeTag {
  89  			t.Errorf("site %d: tag = %q, want %q", i, r.TypeTag, s.TypeTag)
  90  		}
  91  		if r.Projection != s.Projection {
  92  			t.Errorf("site %d: projection = %d, want %d", i, r.Projection, s.Projection)
  93  		}
  94  		if r.Perm != s.Perm {
  95  			t.Errorf("site %d: perm = %d, want %d", i, r.Perm, s.Perm)
  96  		}
  97  	}
  98  
  99  	// Verify sender fingerprint PermDist preserved.
 100  	for i := range 6 {
 101  		if recovered.SenderFingerprint.PermDist[i] != msg.SenderFingerprint.PermDist[i] {
 102  			t.Errorf("PermDist[%d] = %d, want %d",
 103  				i, recovered.SenderFingerprint.PermDist[i], msg.SenderFingerprint.PermDist[i])
 104  		}
 105  	}
 106  }
 107  
 108  func TestEphemeralMarshalNil(t *testing.T) {
 109  	var msg *EphemeralMessage
 110  	_, err := msg.MarshalEphemeral(256)
 111  	if err == nil {
 112  		t.Error("expected error for nil message")
 113  	}
 114  }
 115  
 116  func TestEphemeralUnmarshalTruncated(t *testing.T) {
 117  	_, err := UnmarshalEphemeralMessage([]byte{0x01})
 118  	if err == nil {
 119  		t.Error("expected error for truncated data")
 120  	}
 121  }
 122  
 123  func TestEphemeralUnmarshalBadVersion(t *testing.T) {
 124  	data := make([]byte, 100)
 125  	data[0] = 0xFF // bad version
 126  	data[1] = 0xFF
 127  	_, err := UnmarshalEphemeralMessage(data)
 128  	if err == nil {
 129  		t.Error("expected error for bad version")
 130  	}
 131  }
 132  
 133  func TestEphemeralMarshalDeterministic(t *testing.T) {
 134  	params := DefaultParams(Security128)
 135  	tags := []string{"word", "punct"}
 136  
 137  	kpAlice, _ := Generate(params, tags, testFactory)
 138  	kpBob, _ := Generate(params, tags, testFactory)
 139  
 140  	msg, _ := PrepareExchange(
 141  		&kpAlice.Private, kpAlice.Public.Spore, kpBob.Public.Spore, params,
 142  	)
 143  
 144  	data1, _ := msg.MarshalEphemeral(params.N)
 145  	data2, _ := msg.MarshalEphemeral(params.N)
 146  
 147  	if len(data1) != len(data2) {
 148  		t.Errorf("marshal lengths differ: %d vs %d", len(data1), len(data2))
 149  	}
 150  	for i := range data1 {
 151  		if data1[i] != data2[i] {
 152  			t.Errorf("marshal differs at byte %d: %02x vs %02x", i, data1[i], data2[i])
 153  			break
 154  		}
 155  	}
 156  }
 157  
 158  func TestEphemeralMarshalPreservesSignatureIntegrity(t *testing.T) {
 159  	// Verify that a marshalled+unmarshalled message preserves the
 160  	// signature's internal consistency (challenge, commitment, response).
 161  	// CompleteExchange uses the peer's known SporeFingerprint from their
 162  	// spore (not from the wire message), so full exchange round-trip
 163  	// verification is a protocol-level test, not a wire format test.
 164  	params := DefaultParams(Security128)
 165  	tags := []string{"word", "punct"}
 166  
 167  	kpAlice, _ := Generate(params, tags, testFactory)
 168  	kpBob, _ := Generate(params, tags, testFactory)
 169  
 170  	msg, _ := PrepareExchange(
 171  		&kpAlice.Private, kpAlice.Public.Spore, kpBob.Public.Spore, params,
 172  	)
 173  
 174  	data, err := msg.MarshalEphemeral(params.N)
 175  	if err != nil {
 176  		t.Fatalf("marshal: %v", err)
 177  	}
 178  
 179  	recovered, err := UnmarshalEphemeralMessage(data)
 180  	if err != nil {
 181  		t.Fatalf("unmarshal: %v", err)
 182  	}
 183  
 184  	// V2: truncated challenge (first 16 bytes) must be preserved.
 185  	for i := range 16 {
 186  		if recovered.Signature.Challenge[i] != msg.Signature.Challenge[i] {
 187  			t.Errorf("challenge byte %d not preserved", i)
 188  		}
 189  	}
 190  	// Commitment is not preserved in V2 format.
 191  
 192  	// Response occupied count must match.
 193  	origOcc := 0
 194  	for _, s := range msg.Signature.Response {
 195  		if s.Occupied {
 196  			origOcc++
 197  		}
 198  	}
 199  	recOcc := len(recovered.Signature.Response)
 200  	if recOcc != origOcc {
 201  		t.Errorf("signature response occupied = %d, want %d", recOcc, origOcc)
 202  	}
 203  
 204  	// V2 proof lengths must match response count.
 205  	if len(recovered.Signature.Proof.LockIns) != recOcc {
 206  		t.Errorf("proof lock-ins length = %d, want %d",
 207  			len(recovered.Signature.Proof.LockIns), recOcc)
 208  	}
 209  }
 210