Skip to content

Commit

Permalink
Reject invalid authorities [#3458]
Browse files Browse the repository at this point in the history
  • Loading branch information
firelizzard18 committed Oct 18, 2023
1 parent be8efdc commit f3810e0
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 30 deletions.
99 changes: 78 additions & 21 deletions internal/core/execute/v2/block/msg_signature_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (x SignatureRequest) Process(batch *database.Batch, ctx *MessageContext) (_
// Process the message
req, err := x.check(batch, ctx)
if err == nil {
err = x.record(batch, ctx, req)
err = x.process(batch, ctx, req)
}

// Record the message and its status
Expand All @@ -80,26 +80,41 @@ func (x SignatureRequest) Process(batch *database.Batch, ctx *MessageContext) (_
return status, nil
}

func (SignatureRequest) record(batch *database.Batch, ctx *MessageContext, req *messaging.SignatureRequest) error {
// Check if the transaction has already been recorded
pending := batch.Account(req.Authority).Pending()
_, err := pending.Index(req.TxID)
switch {
case err == nil:
// Already recorded as pending
return nil
func (x SignatureRequest) process(batch *database.Batch, ctx *MessageContext, req *messaging.SignatureRequest) error {
// If the 'authority' is not the principal, verify it exists and is an
// authority
var invalid bool
if ctx.GetActiveGlobals().ExecutorVersion.V2BaikonurEnabled() && !req.Authority.Equal(req.TxID.Account()) {
ok, err := x.authorityIsValid(batch, ctx, req)
if err != nil {
return errors.UnknownError.Wrap(err)
}
if !ok {
invalid = true
}
}

case errors.Is(err, errors.NotFound):
// Ok
if !invalid {
// Check if the transaction has already been recorded
pending := batch.Account(req.Authority).Pending()
_, err := pending.Index(req.TxID)
switch {
case err == nil:
// Already recorded as pending
return nil

default:
return errors.UnknownError.WithFormat("load pending: %w", err)
}
case errors.Is(err, errors.NotFound):
// Ok

// Record the transaction as pending
err = batch.Account(req.Authority).Pending().Add(req.TxID)
if err != nil {
return errors.UnknownError.WithFormat("add pending: %w", err)
default:
return errors.UnknownError.WithFormat("load pending: %w", err)
}

// Record the transaction as pending
err = batch.Account(req.Authority).Pending().Add(req.TxID)
if err != nil {
return errors.UnknownError.WithFormat("add pending: %w", err)
}
}

// Get the transaction from the message bundle (or the database) and store
Expand All @@ -126,10 +141,52 @@ func (SignatureRequest) record(batch *database.Batch, ctx *MessageContext, req *
}

// If the 'authority' is the principal, send a signature request to each authority
if !req.Authority.Equal(req.TxID.Account()) {
return nil
if req.Authority.Equal(req.TxID.Account()) {
return x.requestSignaturesFromAuthorities(batch, ctx, req)
}
return nil
}

func (SignatureRequest) authorityIsValid(batch *database.Batch, ctx *MessageContext, req *messaging.SignatureRequest) (bool, error) {
var message string
account, err := batch.Account(req.Authority).Main().Get()
switch {
case err == nil:
if _, ok := account.(protocol.Authority); ok {
return true, nil
}
message = req.Authority.String() + " is not an authority"

case errors.Is(err, errors.NotFound):
message = req.Authority.String() + " does not exist"

default:
// Unknown error
return true, errors.UnknownError.WithFormat("load authority: %w", err)
}

// The invalid authority abstains from the transaction
authSig := &protocol.AuthoritySignature{
Authority: req.Authority,
Origin: req.Authority,
TxID: req.TxID,
Cause: req.ID(),
Vote: protocol.VoteTypeAbstain,
Memo: message,
}

err = ctx.didProduce(
batch,
authSig.RoutingLocation(),
&messaging.SignatureMessage{
Signature: authSig,
TxID: req.TxID,
},
)
return false, errors.UnknownError.Wrap(err)
}

func (SignatureRequest) requestSignaturesFromAuthorities(batch *database.Batch, ctx *MessageContext, req *messaging.SignatureRequest) error {
principal, err := batch.Account(req.TxID.Account()).Main().Get()
switch {
case err == nil:
Expand All @@ -154,7 +211,7 @@ func (SignatureRequest) record(batch *database.Batch, ctx *MessageContext, req *

msg := new(messaging.SignatureRequest)
msg.Authority = auth.Url
msg.Cause = ctx.message.ID()
msg.Cause = req.ID()
msg.TxID = req.TxID
err = ctx.didProduce(batch, msg.Authority, msg)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions protocol/signatures.yml
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,6 @@ AuthoritySignature:
type: url
pointer: true
repeatable: true
- name: Memo
type: string
optional: true
19 changes: 19 additions & 0 deletions protocol/types_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ type AuthoritySignature struct {
// Cause is the ID of the signature that produced this.
Cause *url.TxID `json:"cause,omitempty" form:"cause" query:"cause" validate:"required"`
Delegator []*url.URL `json:"delegator,omitempty" form:"delegator" query:"delegator" validate:"required"`
Memo string `json:"memo,omitempty" form:"memo" query:"memo"`
extraData []byte
}

Expand Down Expand Up @@ -1495,6 +1496,7 @@ func (v *AuthoritySignature) Copy() *AuthoritySignature {
u.Delegator[i] = v
}
}
u.Memo = v.Memo
if len(v.extraData) > 0 {
u.extraData = make([]byte, len(v.extraData))
copy(u.extraData, v.extraData)
Expand Down Expand Up @@ -3767,6 +3769,9 @@ func (v *AuthoritySignature) Equal(u *AuthoritySignature) bool {
return false
}
}
if !(v.Memo == u.Memo) {
return false
}

return true
}
Expand Down Expand Up @@ -6558,6 +6563,7 @@ var fieldNames_AuthoritySignature = []string{
5: "TxID",
6: "Cause",
7: "Delegator",
8: "Memo",
}

func (v *AuthoritySignature) MarshalBinary() ([]byte, error) {
Expand Down Expand Up @@ -6589,6 +6595,9 @@ func (v *AuthoritySignature) MarshalBinary() ([]byte, error) {
writer.WriteUrl(7, v)
}
}
if !(len(v.Memo) == 0) {
writer.WriteString(8, v.Memo)
}

_, _, err := writer.Reset(fieldNames_AuthoritySignature)
if err != nil {
Expand Down Expand Up @@ -13376,6 +13385,9 @@ func (v *AuthoritySignature) UnmarshalFieldsFrom(reader *encoding.Reader) error
break
}
}
if x, ok := reader.ReadString(8); ok {
v.Memo = x
}

seen, err := reader.Reset(fieldNames_AuthoritySignature)
if err != nil {
Expand Down Expand Up @@ -17493,6 +17505,7 @@ func (v *AuthoritySignature) MarshalJSON() ([]byte, error) {
TxID *url.TxID `json:"txID,omitempty"`
Cause *url.TxID `json:"cause,omitempty"`
Delegator encoding.JsonList[*url.URL] `json:"delegator,omitempty"`
Memo string `json:"memo,omitempty"`
}{}
u.Type = v.Type()
if !(v.Origin == nil) {
Expand All @@ -17513,6 +17526,9 @@ func (v *AuthoritySignature) MarshalJSON() ([]byte, error) {
if !(len(v.Delegator) == 0) {
u.Delegator = v.Delegator
}
if !(len(v.Memo) == 0) {
u.Memo = v.Memo
}
return json.Marshal(&u)
}

Expand Down Expand Up @@ -19679,6 +19695,7 @@ func (v *AuthoritySignature) UnmarshalJSON(data []byte) error {
TxID *url.TxID `json:"txID,omitempty"`
Cause *url.TxID `json:"cause,omitempty"`
Delegator encoding.JsonList[*url.URL] `json:"delegator,omitempty"`
Memo string `json:"memo,omitempty"`
}{}
u.Type = v.Type()
u.Origin = v.Origin
Expand All @@ -19687,6 +19704,7 @@ func (v *AuthoritySignature) UnmarshalJSON(data []byte) error {
u.TxID = v.TxID
u.Cause = v.Cause
u.Delegator = v.Delegator
u.Memo = v.Memo
if err := json.Unmarshal(data, &u); err != nil {
return err
}
Expand All @@ -19699,6 +19717,7 @@ func (v *AuthoritySignature) UnmarshalJSON(data []byte) error {
v.TxID = u.TxID
v.Cause = u.Cause
v.Delegator = u.Delegator
v.Memo = u.Memo
return nil
}

Expand Down
102 changes: 102 additions & 0 deletions test/e2e/sig_additional_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (

"github.com/stretchr/testify/require"
"gitlab.com/accumulatenetwork/accumulate/pkg/build"
"gitlab.com/accumulatenetwork/accumulate/pkg/errors"
"gitlab.com/accumulatenetwork/accumulate/pkg/types/messaging"
"gitlab.com/accumulatenetwork/accumulate/protocol"
. "gitlab.com/accumulatenetwork/accumulate/protocol"
. "gitlab.com/accumulatenetwork/accumulate/test/harness"
. "gitlab.com/accumulatenetwork/accumulate/test/helpers"
Expand Down Expand Up @@ -111,3 +114,102 @@ func TestUpdateKeyAdditionalAuthority(t *testing.T) {
_, _, ok := page.EntryByKey(otherKey[32:])
require.True(t, ok)
}

func TestMissingAdditionalAuthority(t *testing.T) {
var timestamp uint64
alice := AccountUrl("alice")
bob := AccountUrl("bob")
aliceKey := acctesting.GenerateKey(alice)

// Initialize
sim := NewSim(t,
simulator.SimpleNetwork(t.Name(), 3, 1),
simulator.Genesis(GenesisTime),
)

MakeIdentity(t, sim.DatabaseFor(alice), alice, aliceKey[32:])
CreditCredits(t, sim.DatabaseFor(alice), alice.JoinPath("book", "1"), 1e12)
// DO NOT CREATE BOB

// Initiate, requiring a signature from bob
st := sim.BuildAndSubmitSuccessfully(
build.Transaction().For(alice, "book", "1").
AdditionalAuthority(bob, "book").
BurnCredits(1).
SignWith(alice, "book", "1").Version(1).Timestamp(&timestamp).PrivateKey(aliceKey))

// The transaction is rejected due to the missing authority
var bobSigSt *protocol.TransactionStatus
sim.StepUntil(
Txn(st[0].TxID).Fails().WithError(errors.Rejected),
Sig(st[1].TxID).SignatureRequestTo(bob, "book").AuthoritySignature().Capture(&bobSigSt).Completes())

bobSig := sim.QueryMessage(bobSigSt.TxID, nil).Message.(*messaging.SignatureMessage).Signature.(*AuthoritySignature)
require.Equal(t, bobSig.Memo, "acc://bob.acme/book does not exist")
}

func TestMissingCreateAuthority(t *testing.T) {
var timestamp uint64
alice := AccountUrl("alice")
bob := AccountUrl("bob")
aliceKey := acctesting.GenerateKey(alice)

// Initialize
sim := NewSim(t,
simulator.SimpleNetwork(t.Name(), 3, 1),
simulator.Genesis(GenesisTime),
)

MakeIdentity(t, sim.DatabaseFor(alice), alice, aliceKey[32:])
CreditCredits(t, sim.DatabaseFor(alice), alice.JoinPath("book", "1"), 1e12)
// DO NOT CREATE BOB

// Initiate, requiring a signature from bob
st := sim.BuildAndSubmitSuccessfully(
build.Transaction().For(alice).
CreateTokenAccount(alice, "tokens").ForToken(ACME).
WithAuthority(bob, "book").
SignWith(alice, "book", "1").Version(1).Timestamp(&timestamp).PrivateKey(aliceKey))

// The transaction is rejected due to the missing authority
var bobSigSt *protocol.TransactionStatus
sim.StepUntil(
Txn(st[0].TxID).Fails().WithError(errors.Rejected),
Sig(st[1].TxID).SignatureRequestTo(bob, "book").AuthoritySignature().Capture(&bobSigSt).Completes())

bobSig := sim.QueryMessage(bobSigSt.TxID, nil).Message.(*messaging.SignatureMessage).Signature.(*AuthoritySignature)
require.Equal(t, bobSig.Memo, "acc://bob.acme/book does not exist")
}

func TestInvalidCreateAuthority(t *testing.T) {
var timestamp uint64
alice := AccountUrl("alice")
bob := AccountUrl("bob")
aliceKey := acctesting.GenerateKey(alice)

// Initialize
sim := NewSim(t,
simulator.SimpleNetwork(t.Name(), 3, 1),
simulator.Genesis(GenesisTime),
)

MakeIdentity(t, sim.DatabaseFor(alice), alice, aliceKey[32:])
CreditCredits(t, sim.DatabaseFor(alice), alice.JoinPath("book", "1"), 1e12)
MakeIdentity(t, sim.DatabaseFor(bob), bob)

// Initiate, requiring a signature from an account that is not a key book
st := sim.BuildAndSubmitSuccessfully(
build.Transaction().For(alice).
CreateTokenAccount(alice, "tokens").ForToken(ACME).
WithAuthority(bob).
SignWith(alice, "book", "1").Version(1).Timestamp(&timestamp).PrivateKey(aliceKey))

// The transaction is rejected due to the missing authority
var bobSigSt *protocol.TransactionStatus
sim.StepUntil(
Txn(st[0].TxID).Fails().WithError(errors.Rejected),
Sig(st[1].TxID).SignatureRequestTo(bob).AuthoritySignature().Capture(&bobSigSt).Completes())

bobSig := sim.QueryMessage(bobSigSt.TxID, nil).Message.(*messaging.SignatureMessage).Signature.(*AuthoritySignature)
require.Equal(t, bobSig.Memo, "acc://bob.acme is not an authority")
}
24 changes: 15 additions & 9 deletions test/harness/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,29 +156,35 @@ func (c sigCond) CreditPayment() msgCond {

// SignatureRequest defines a condition on signature requests produced by a
// signature.
func (c sigCond) SignatureRequest() sigCond2 {
return sigCond2{c.with("signature request", deliveredThen, producedFiltered(func(r *msgResult) bool {
func (c sigCond) SignatureRequest() sigCond {
return sigCond{c.with("signature request", deliveredThen, producedFiltered(func(r *msgResult) bool {
_, ok := messaging.UnwrapAs[*messaging.SignatureRequest](r.Message)
return ok
}))}
}

// SignatureRequestTo defines a condition on signature requests produced by a
// signature.
func (c sigCond) SignatureRequestTo(auth *url.URL, path ...string) sigCond {
auth = auth.JoinPath(path...)
return sigCond{c.with("signature request", deliveredThen, producedFiltered(func(r *msgResult) bool {
msg, ok := messaging.UnwrapAs[*messaging.SignatureRequest](r.Message)
return ok && msg.Authority.Equal(auth)
}))}
}

// AuthoritySignature defines a condition on the authority signature produced by
// a signature.
func (c sigCond) AuthoritySignature() sigCond2 {
return sigCond2{c.with("authority signature", deliveredThen, producedFiltered(func(r *msgResult) bool {
func (c sigCond) AuthoritySignature() sigCond {
return sigCond{c.with("authority signature", deliveredThen, producedFiltered(func(r *msgResult) bool {
msg, ok := messaging.UnwrapAs[*messaging.SignatureMessage](r.Message)
return ok && msg.Signature.Type() == protocol.SignatureTypeAuthority
}))}
}

// sigCond2 provides methods to define conditions on a signature request or
// authority signature.
type sigCond2 struct{ msgCond }

// Produced defines a condition on messages produced by a signature request or
// authority signature.
func (c sigCond2) Produced() sigCond2 { return sigCond2{c.with("produced", deliveredThen, produced)} }
func (c sigCond) Produced() sigCond { return sigCond{c.with("produced", deliveredThen, produced)} }

// msgCond provides methods to define conditions on a message.
type msgCond struct {
Expand Down

0 comments on commit f3810e0

Please sign in to comment.