Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Announcement type and ForEachAnnouncement #14

Merged
merged 3 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions chain/announcements.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package chain

import (
"bytes"

"go.sia.tech/core/types"
)

// AnnouncementSpecifier is the specifier used in the arbitrary data section
// (for v1 txns) and the attestation value (for v2 txns) when a host announces
// its net address.
const AnnouncementSpecifier = "HostAnnouncement"

type (
// Announcement represents a v1 or v2 host announcement after it has been
// validated.
Announcement struct {
NetAddress string
PublicKey types.PublicKey
}

// V1Announcement represents a host announcement in v1 as stored in the
// arbitrary data section of a transaction.
V1Announcement struct {
Specifier types.Specifier
NetAddress string
PublicKey types.UnlockKey
Signature types.Signature
}
)

// DecodeFrom decodes a signed announcement.
func (a *V1Announcement) DecodeFrom(d *types.Decoder) {
a.Specifier.DecodeFrom(d)
a.NetAddress = d.ReadString()
a.PublicKey.DecodeFrom(d)
a.Signature.DecodeFrom(d)
}

// EncodeTo encodes a signed announcement.
func (a V1Announcement) EncodeTo(e *types.Encoder) {
a.Specifier.EncodeTo(e)
e.WriteString(a.NetAddress)
a.PublicKey.EncodeTo(e)
a.Signature.EncodeTo(e)
}

// VerifySignature verifies the signature of the announcement using its public
// key. This can only succeed if the PublicKey is a valid ed25519 public key.
func (a V1Announcement) VerifySignature() bool {
sigHash := a.sigHash()
var pk types.PublicKey
copy(pk[:], a.PublicKey.Key)
return pk.VerifyHash(sigHash, a.Signature)
}

// Sign signs the announcement using the given private key.
func (a *V1Announcement) Sign(sk types.PrivateKey) {
sigHash := a.sigHash()
a.Signature = sk.SignHash(sigHash)
}

// sigHash returns the hash that is signed by the announcement's signature.
func (a V1Announcement) sigHash() types.Hash256 {
buf := new(bytes.Buffer)
e := types.NewEncoder(buf)
a.Specifier.EncodeTo(e)
e.WriteString(a.NetAddress)
a.PublicKey.EncodeTo(e)
e.Flush()
return types.HashBytes(buf.Bytes())
}

// ForEachAnnouncement calls fn on each host announcement in a block.
func ForEachAnnouncement(b types.Block, fn func(Announcement)) {
for _, txn := range b.Transactions {
for _, arb := range txn.ArbitraryData {
// decode announcement
var ha V1Announcement
dec := types.NewBufDecoder(arb)
ha.DecodeFrom(dec)
if err := dec.Err(); err != nil {
continue
} else if ha.Specifier != types.NewSpecifier(AnnouncementSpecifier) {
continue
} else if !ha.VerifySignature() {
continue
}
var pk types.PublicKey
copy(pk[:], ha.PublicKey.Key)
fn(Announcement{
NetAddress: ha.NetAddress,
PublicKey: pk,
})
}
}
for _, txn := range b.V2Transactions() {
for _, att := range txn.Attestations {
if att.Key != AnnouncementSpecifier {
ChrisSchinnerl marked this conversation as resolved.
Show resolved Hide resolved
continue
}
fn(Announcement{
NetAddress: string(att.Value),
PublicKey: att.PublicKey,
})
}
}
}
97 changes: 97 additions & 0 deletions chain/announcements_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package chain

import (
"bytes"
"testing"

"go.sia.tech/core/types"
)

func TestAnnouncementsSignature(t *testing.T) {
t.Parallel()

sk := types.GeneratePrivateKey()
pk := sk.PublicKey()

// create a v1 announcement
v1Ann := V1Announcement{
Specifier: types.Specifier{0x01, 0x02, 0x03},
NetAddress: "foo.bar:1234",
PublicKey: pk.UnlockKey(),
}

// sign it and verify signature
v1Ann.Sign(sk)
if v1Ann.Signature == (types.Signature{}) {
t.Fatal("signature not set")
} else if !v1Ann.VerifySignature() {
t.Fatal("signature verification failed")
}

// change a field and verify signature fails
v1Ann.NetAddress = "baz.qux:5678"
if v1Ann.VerifySignature() {
t.Fatal("signature verification succeeded")
}
}

func TestForEachAnnon(t *testing.T) {
t.Parallel()

// create a v1 announcement
sk := types.GeneratePrivateKey()
pk := sk.PublicKey()
v1Ann := V1Announcement{
Specifier: types.NewSpecifier(AnnouncementSpecifier),
NetAddress: "foo.bar:1234",
PublicKey: pk.UnlockKey(),
}
v1Ann.Sign(sk)

// encode it
buf := new(bytes.Buffer)
enc := types.NewEncoder(buf)
v1Ann.EncodeTo(enc)
enc.Flush()

// create a block
b := types.Block{
Transactions: []types.Transaction{
{
ArbitraryData: [][]byte{buf.Bytes()},
},
},
V2: &types.V2BlockData{
Transactions: []types.V2Transaction{
{
Attestations: []types.Attestation{
{
PublicKey: pk,
Key: AnnouncementSpecifier,
Value: []byte(v1Ann.NetAddress),
Signature: types.Signature{}, // not necessary
},
},
},
},
},
}

// extract them
var announcements []Announcement
ForEachAnnouncement(b, func(a Announcement) {
announcements = append(announcements, a)
})
if len(announcements) != 2 {
t.Fatal("expected 1 announcement", len(announcements))
}
for _, a := range announcements {
if a.NetAddress != v1Ann.NetAddress {
t.Fatal("unexpected net address:", a.NetAddress)
} else if uk := a.PublicKey.UnlockKey(); uk.Algorithm != v1Ann.PublicKey.Algorithm {
t.Fatal("unexpected public key specifier:", uk.Algorithm)
} else if !bytes.Equal(uk.Key, v1Ann.PublicKey.Key) {
t.Fatal("unexpected public key:", uk.Key)
}
}
}
Loading