package crypto import ( "testing" "git.mleku.dev/mleku/dendrite/pkg/axiom" "git.mleku.dev/mleku/dendrite/pkg/lattice" "git.mleku.dev/mleku/dendrite/pkg/ratio" "git.mleku.dev/mleku/dendrite/pkg/spore" ) func buildSporeWithTags(tags []string, nodesPerTag int) *spore.Spore { l := lattice.New() for _, tag := range tags { var nodes []*lattice.Node for range nodesPerTag { n := l.AddNode([]axiom.Constraint{tagConstraint{tag}}) nodes = append(nodes, n) } for i := range nodes { l.Connect(nodes[i], nodes[(i+1)%len(nodes)]) } } return spore.Extract(l) } func TestExchangeSameStructure(t *testing.T) { tags := []string{"word", "punct", "space"} s1 := buildSporeWithTags(tags, 10) s2 := buildSporeWithTags(tags, 10) ss, err := Exchange(s1, s2) if err != nil { t.Fatalf("Exchange: %v", err) } if len(ss.CommonTags) != 3 { t.Errorf("common tags = %d, want 3", len(ss.CommonTags)) } if !ss.Confidence.Equal(ratio.One) { t.Errorf("confidence = %s, want 1/1", ss.Confidence) } if ss.Secret == (Hamadryad{}) { t.Error("secret should not be zero") } } func TestExchangePartialOverlap(t *testing.T) { s1 := buildSporeWithTags([]string{"word", "punct"}, 10) s2 := buildSporeWithTags([]string{"punct", "space"}, 10) ss, err := Exchange(s1, s2) if err != nil { t.Fatalf("Exchange: %v", err) } if len(ss.CommonTags) != 1 { t.Errorf("common tags = %d, want 1 (punct)", len(ss.CommonTags)) } if ss.CommonTags[0] != "punct" { t.Errorf("common tag = %q, want punct", ss.CommonTags[0]) } // Confidence: 1 common / max(2, 2) = 1/2 if !ss.Confidence.Equal(ratio.Half) { t.Errorf("confidence = %s, want 1/2", ss.Confidence) } } func TestExchangeNoOverlap(t *testing.T) { s1 := buildSporeWithTags([]string{"word"}, 10) s2 := buildSporeWithTags([]string{"punct"}, 10) ss, err := Exchange(s1, s2) if err != nil { t.Fatalf("Exchange: %v", err) } if len(ss.CommonTags) != 0 { t.Errorf("common tags = %d, want 0", len(ss.CommonTags)) } if !ss.Confidence.Equal(ratio.Zero) { t.Errorf("confidence = %s, want 0", ss.Confidence) } } func TestExchangeSymmetry(t *testing.T) { s1 := buildSporeWithTags([]string{"word", "punct"}, 8) s2 := buildSporeWithTags([]string{"word", "punct", "space"}, 6) ss1, err := Exchange(s1, s2) if err != nil { t.Fatalf("Exchange(1,2): %v", err) } ss2, err := Exchange(s2, s1) if err != nil { t.Fatalf("Exchange(2,1): %v", err) } if ss1.Secret != ss2.Secret { t.Error("exchange should be symmetric: both parties derive the same secret") } if !ss1.Confidence.Equal(ss2.Confidence) { t.Error("confidence should be symmetric") } } func TestExchangeNilSpore(t *testing.T) { s := buildSporeWithTags([]string{"word"}, 5) _, err := Exchange(nil, s) if err == nil { t.Error("expected error with nil spore") } _, err = Exchange(s, nil) if err == nil { t.Error("expected error with nil spore") } } func TestDeriveKey(t *testing.T) { ss := &SharedSecret{ CommonTags: []string{"word"}, Secret: Hamadryad{1, 2, 3}, Confidence: ratio.One, } key16 := DeriveKey(ss, "aes-128", 16) if len(key16) != 16 { t.Errorf("key length = %d, want 16", len(key16)) } key32 := DeriveKey(ss, "aes-256", 32) if len(key32) != 32 { t.Errorf("key length = %d, want 32", len(key32)) } key64 := DeriveKey(ss, "extended", 64) if len(key64) != 64 { t.Errorf("key length = %d, want 64", len(key64)) } // Different contexts should produce different keys. keyA := DeriveKey(ss, "context-a", 32) keyB := DeriveKey(ss, "context-b", 32) if string(keyA) == string(keyB) { t.Error("different contexts should produce different keys") } } func TestDeriveKeyDeterministic(t *testing.T) { ss := &SharedSecret{ CommonTags: []string{"word"}, Secret: Hamadryad{42}, Confidence: ratio.One, } key1 := DeriveKey(ss, "test", 32) key2 := DeriveKey(ss, "test", 32) if string(key1) != string(key2) { t.Error("same inputs should produce same key") } }