Skip to content

Commit

Permalink
Merge pull request #593 from iotaledger/feat/bitmask-check-trailing-z…
Browse files Browse the repository at this point in the history
…ero-bytes

Add checks for trailing zero bytes in capabilities
  • Loading branch information
muXxer authored Oct 31, 2023
2 parents 5d7d593 + 4ee6269 commit a6d98f3
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 4 deletions.
2 changes: 2 additions & 0 deletions address.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ var (
ErrUnknownAddrType = ierrors.New("unknown address type")
// ErrInvalidAddressType gets returned when an address type is invalid.
ErrInvalidAddressType = ierrors.New("invalid address type")
// ErrInvalidRestrictedAddress gets returned when a RestrictedAddress is invalid.
ErrInvalidRestrictedAddress = ierrors.New("invalid restricted address")
// ErrInvalidNestedAddressType gets returned when a nested address inside a MultiAddress or RestrictedAddress is invalid.
ErrInvalidNestedAddressType = ierrors.New("invalid nested address type")
// ErrImplicitAccountCreationAddressInInvalidUnlockCondition gets returned when a Implicit Account Creation Address
Expand Down
59 changes: 58 additions & 1 deletion address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestAddressDeSerialize(t *testing.T) {
},
{
name: "ok - RestrictedEd25519Address without capabilities",
source: tpkg.RandRestrictedEd25519Address(iotago.AddressCapabilitiesBitMask{0x0}),
source: tpkg.RandRestrictedEd25519Address(iotago.AddressCapabilitiesBitMask{}),
target: &iotago.RestrictedAddress{},
},
{
Expand Down Expand Up @@ -433,6 +433,63 @@ func TestRestrictedAddressCapabilities(t *testing.T) {
assertRestrictedAddresses(t, addresses)
}

//nolint:dupl // we have a lot of similar tests
func TestRestrictedAddressCapabilitiesBitMask(t *testing.T) {

type test struct {
name string
addr *iotago.RestrictedAddress
wantErr error
}

tests := []*test{
{
name: "ok - no trailing zero bytes",
addr: &iotago.RestrictedAddress{
Address: tpkg.RandEd25519Address(),
AllowedCapabilities: iotago.AddressCapabilitiesBitMask{0x01, 0x02},
},
wantErr: nil,
},
{
name: "ok - empty capabilities",
addr: &iotago.RestrictedAddress{
Address: tpkg.RandEd25519Address(),
AllowedCapabilities: iotago.AddressCapabilitiesBitMask{},
},
wantErr: nil,
},
{
name: "fail - trailing zero bytes",
addr: &iotago.RestrictedAddress{
Address: tpkg.RandEd25519Address(),
AllowedCapabilities: iotago.AddressCapabilitiesBitMask{0x01, 0x00},
},
wantErr: iotago.ErrBitmaskTrailingZeroBytes,
},
{
name: "fail - single zero bytes",
addr: &iotago.RestrictedAddress{
Address: tpkg.RandEd25519Address(),
AllowedCapabilities: iotago.AddressCapabilitiesBitMask{0x00},
},
wantErr: iotago.ErrBitmaskTrailingZeroBytes,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, err := tpkg.TestAPI.Encode(test.addr, serix.WithValidation())
if test.wantErr != nil {
require.ErrorIs(t, err, test.wantErr)

return
}
require.NoError(t, err)
})
}
}

type outputsSyntacticalValidationTest struct {
// the name of the testcase
name string
Expand Down
5 changes: 5 additions & 0 deletions api_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ var (
// restrictedAddressValidatorFunc is a validator which checks that:
// 1. ImplicitAccountCreationAddress are not nested inside the RestrictedAddress.
// 2. RestrictedAddresses are not nested inside the RestrictedAddress.
// 3. The bitmask does not contain trailing zero bytes.
restrictedAddressValidatorFunc = func(ctx context.Context, addr RestrictedAddress) error {
if err := BitMaskNonTrailingZeroBytesValidatorFunc(addr.AllowedCapabilities); err != nil {
return ierrors.Wrapf(ErrInvalidRestrictedAddress, "invalid allowed capabilities bitmask: %w", err)
}

switch addr.Address.(type) {
case *Ed25519Address, *AccountAddress, *NFTAddress, *AnchorAddress, *MultiAddress:
// allowed address types
Expand Down
19 changes: 19 additions & 0 deletions bitmask.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
package iotago

import (
"github.com/iotaledger/hive.go/ierrors"
"github.com/iotaledger/iota.go/v4/hexutil"
)

var (
// ErrBitmaskTrailingZeroBytes gets returned when the trailing bytes of a bitmask are zero.
ErrBitmaskTrailingZeroBytes = ierrors.New("bitmask trailing bytes are zero")
)

func BitMaskHasBit(bm []byte, bit uint) bool {
byteIndex := bit / 8
if uint(len(bm)) <= byteIndex {
Expand All @@ -21,3 +31,12 @@ func BitMaskSetBit(bm []byte, bit uint) []byte {

return newBitmask
}

// BitMaskNonTrailingZeroBytesValidatorFunc checks that the trailing bytes of the bitmask are not zero.
func BitMaskNonTrailingZeroBytesValidatorFunc(bm []byte) error {
if len(bm) == 0 || bm[len(bm)-1] != 0 {
return nil
}

return ierrors.Wrapf(ErrBitmaskTrailingZeroBytes, "bitmask: %s", hexutil.EncodeHex(bm))
}
4 changes: 3 additions & 1 deletion transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const (
var (
// ErrTxEssenceNetworkIDInvalid gets returned when a network ID within a Transaction is invalid.
ErrTxEssenceNetworkIDInvalid = ierrors.New("invalid network ID")
// ErrTxEssenceCapabilitiesInvalid gets returned when the capabilities within a Transaction are invalid.
ErrTxEssenceCapabilitiesInvalid = ierrors.New("invalid capabilities")
// ErrInputUTXORefsNotUnique gets returned if multiple inputs reference the same UTXO.
ErrInputUTXORefsNotUnique = ierrors.New("inputs must each reference a unique UTXO")
// ErrInputBICNotUnique gets returned if multiple inputs reference the same BIC.
Expand Down Expand Up @@ -252,7 +254,7 @@ func (t *Transaction) Size() int {
func (t *Transaction) syntacticallyValidate(api API) error {
protoParams := api.ProtocolParameters()

if err := t.TransactionEssence.syntacticallyValidate(api); err != nil {
if err := t.TransactionEssence.SyntacticallyValidate(api); err != nil {
return err
}

Expand Down
8 changes: 6 additions & 2 deletions transaction_essence.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,13 @@ func (u *TransactionEssence) WorkScore(workScoreParameters *WorkScoreParameters)
return workScoreContextInputs.Add(workScoreInputs, workScoreAllotments)
}

// syntacticallyValidate checks whether the transaction essence is syntactically valid.
// SyntacticallyValidate checks whether the transaction essence is syntactically valid.
// The function does not syntactically validate the input or outputs themselves.
func (u *TransactionEssence) syntacticallyValidate(api API) error {
func (u *TransactionEssence) SyntacticallyValidate(api API) error {
if err := BitMaskNonTrailingZeroBytesValidatorFunc(u.Capabilities); err != nil {
return ierrors.Wrapf(ErrTxEssenceCapabilitiesInvalid, "invalid capabilities bitmask: %w", err)
}

protoParams := api.ProtocolParameters()
expectedNetworkID := protoParams.NetworkID()
if u.NetworkID != expectedNetworkID {
Expand Down
48 changes: 48 additions & 0 deletions transaction_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
//nolint:scopelint
package iotago_test

import (
"math/big"
"testing"

"github.com/stretchr/testify/require"

"github.com/iotaledger/hive.go/serializer/v2"
iotago "github.com/iotaledger/iota.go/v4"
"github.com/iotaledger/iota.go/v4/tpkg"
Expand Down Expand Up @@ -251,3 +254,48 @@ func TestAllotmentUniqueness(t *testing.T) {
t.Run(tt.name, tt.deSerialize)
}
}

func TestTransactionEssenceCapabilitiesBitMask(t *testing.T) {

type test struct {
name string
tx *iotago.Transaction
wantErr error
}

randTransactionWithCapabilities := func(capabilities iotago.TransactionCapabilitiesBitMask) *iotago.Transaction {
tx := tpkg.RandTransaction(tpkg.TestAPI)
tx.Capabilities = capabilities
return tx
}

tests := []*test{
{
name: "ok - no trailing zero bytes",
tx: randTransactionWithCapabilities(iotago.TransactionCapabilitiesBitMask{0x01}),
wantErr: nil,
},
{
name: "ok - empty capabilities",
tx: randTransactionWithCapabilities(iotago.TransactionCapabilitiesBitMask{}),
wantErr: nil,
},
{
name: "fail - single zero byte",
tx: randTransactionWithCapabilities(iotago.TransactionCapabilitiesBitMask{0x00}),
wantErr: iotago.ErrBitmaskTrailingZeroBytes,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.tx.SyntacticallyValidate(tpkg.TestAPI)
if test.wantErr != nil {
require.ErrorIs(t, err, test.wantErr)

return
}
require.NoError(t, err)
})
}
}

0 comments on commit a6d98f3

Please sign in to comment.