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

refactor: deobfuscate dragonberry #383

Merged
merged 15 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
64 changes: 41 additions & 23 deletions go/ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,56 @@ import (
_ "golang.org/x/crypto/ripemd160" //nolint:staticcheck
)

// validate the IAVL Ops
func validateIavlOps(op opType, b int) error {
// validateIavlOps validates the prefix to ensure it begins with
// the height, size, and version of the IAVL tree. Each varint must be a bounded value.
// In addition, the remaining bytes are validated to ensure they correspond to the correct
// length. The layerNum is the tree depth.
func validateIavlOps(op opType, layerNum int) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b -> layerNum open to other naming suggestions

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

layerNum is good to me, maybe we can indicate in godoc that it corresponds to tree depth

r := bytes.NewReader(op.GetPrefix())

values := []int64{}
for i := 0; i < 3; i++ {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to remove the for loop for readability, so each height, size, and version could have a var name rather than an index

varInt, err := binary.ReadVarint(r)
if err != nil {
return err
}
values = append(values, varInt)
height, err := binary.ReadVarint(r)
if err != nil {
return fmt.Errorf("failed to read IAVL height varint: %w", err)
}
if int(height) < 0 || int(height) < layerNum {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previous check: if int(values[0]) < b { moved here in int(height) < layerNum

return fmt.Errorf("IAVL height (%d) must be non-negative and greater than or equal to the layer number (%d)", height, layerNum)
}

// values must be bounded
if int(varInt) < 0 {
return fmt.Errorf("wrong value in IAVL leaf op")
}
size, err := binary.ReadVarint(r)
if err != nil {
return fmt.Errorf("failed to read IAVL size varint: %w", err)
}

if int(size) < 0 {
return fmt.Errorf("IAVL size must be non-negative")
}
if int(values[0]) < b {
return fmt.Errorf("wrong value in IAVL leaf op")

version, err := binary.ReadVarint(r)
if err != nil {
return fmt.Errorf("failed to read IAVL version varint: %w", err)
}

if int(version) < 0 {
return fmt.Errorf("IAVL version must be non-negative")
}

r2 := r.Len()
if b == 0 {
if r2 != 0 {
return fmt.Errorf("invalid op")
// grab the length of the remainder of the prefix
remLen := r.Len()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r2 -> remLen open to other naming suggestions!

if layerNum == 0 {
if remLen != 0 {
return fmt.Errorf("expected remaining prefix length to be 0, got: %d", remLen)
}
} else {
if !(r2^(0xff&0x01) == 0 || r2 == (0xde+int('v'))/10) {
return fmt.Errorf("invalid op")
// when the child comes from the left, the suffix if filled in
// prefix: height | size | version | length byte (1 remainder)
//
// when the child comes from the right, the suffix is empty
// prefix: height | size | version | length byte | 32 byte hash | next length byte (34 remainder)
Comment on lines +63 to +67
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @damiannolan for this!

if remLen != 1 && remLen != 34 {
return fmt.Errorf("remainder of prefix must be of length 1 or 34, got: %d", remLen)
}
if op.GetHash()^1 != 0 {
return fmt.Errorf("invalid op")
if op.GetHash() != HashOp_SHA256 {
return fmt.Errorf("IAVL hash op must be %v", HashOp_SHA256)
}
}
return nil
Expand Down
120 changes: 119 additions & 1 deletion go/ops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,124 @@ import (
"testing"
)

func TestValidateIavlOps(t *testing.T) {
var (
op opType
layerNum int
)
cases := []struct {
name string
malleate func()
expError error
}{
{
"success",
func() {},
nil,
},
{
"failure: reading varint",
func() {
op.(*InnerOp).Prefix = []byte{}
},
errors.New("failed to read IAVL height varint: EOF"),
},
{
"failure: invalid height value",
func() {
op.(*InnerOp).Prefix = []byte{1}
},
errors.New("IAVL height (-1) must be non-negative and greater than or equal to the layer number (1)"),
},
{
"failure: invalid size varint",
func() {
var varintBuf [binary.MaxVarintLen64]byte
prefix := convertVarIntToBytes(int64(5), varintBuf)
op.(*InnerOp).Prefix = prefix
},
errors.New("failed to read IAVL size varint: EOF"),
},
{
"failure: invalid size value",
func() {
var varintBuf [binary.MaxVarintLen64]byte
prefix := convertVarIntToBytes(int64(5), varintBuf)
prefix = append(prefix, convertVarIntToBytes(int64(-1), varintBuf)...) // size
op.(*InnerOp).Prefix = prefix
},
errors.New("IAVL size must be non-negative"),
},
{
"failure: invalid version varint",
func() {
var varintBuf [binary.MaxVarintLen64]byte
prefix := convertVarIntToBytes(int64(5), varintBuf)
prefix = append(prefix, convertVarIntToBytes(int64(10), varintBuf)...)
op.(*InnerOp).Prefix = prefix
},
errors.New("failed to read IAVL version varint: EOF"),
},
{
"failure: invalid version value",
func() {
var varintBuf [binary.MaxVarintLen64]byte
prefix := convertVarIntToBytes(int64(5), varintBuf)
prefix = append(prefix, convertVarIntToBytes(int64(10), varintBuf)...)
prefix = append(prefix, convertVarIntToBytes(int64(-1), varintBuf)...) // version
op.(*InnerOp).Prefix = prefix
},
errors.New("IAVL version must be non-negative"),
},
{
"failure: invalid remaining length with layer number is 0",
func() {
layerNum = 0
},
fmt.Errorf("expected remaining prefix length to be 0, got: 1"),
},
{
"failure: invalid reminaing length with non-zero layer number",
func() {
layerNum = 1
op.(*InnerOp).Prefix = append(op.(*InnerOp).Prefix, []byte{1}...)
},
fmt.Errorf("remainder of prefix must be of length 1 or 34, got: 2"),
},
{
"failure: invalid hash",
func() {
op.(*InnerOp).Hash = HashOp_NO_HASH
},
fmt.Errorf("IAVL hash op must be %v", HashOp_SHA256),
},
}
for _, tc := range cases {
tc := tc

t.Run(tc.name, func(t *testing.T) {
op = &InnerOp{
Hash: HashOp_SHA256,
Prefix: generateInnerOpPrefix(),
Suffix: []byte(""),
}
layerNum = 1

tc.malleate()

err := validateIavlOps(op, layerNum)
if tc.expError == nil && err != nil {
t.Fatal(err)
}

if tc.expError != nil && err.Error() != tc.expError.Error() {
t.Fatalf("expected: %v, got: %v", tc.expError, err)
}
})

}
}

func TestLeafOp(t *testing.T) {
cases := LeafOpTestData(t)

Expand Down Expand Up @@ -114,7 +232,7 @@ func TestInnerOpCheckAgainstSpec(t *testing.T) {
func() {
innerOp.Prefix = []byte{0x01}
},
fmt.Errorf("wrong value in IAVL leaf op"),
fmt.Errorf("IAVL height (-1) must be non-negative and greater than or equal to the layer number (1)"),
},
{
"failure: inner prefix starts with leaf prefix",
Expand Down
2 changes: 1 addition & 1 deletion go/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestCheckAgainstSpec(t *testing.T) {
if tc.Err == "" && err != nil {
t.Fatalf("Unexpected error: %v", err)
} else if tc.Err != "" && tc.Err != err.Error() {
t.Fatalf("Expected error: %s, got %s", tc.Err, err.Error())
t.Fatalf("Expected error: %s, got: %s", tc.Err, err.Error())
}
})
}
Expand Down
2 changes: 1 addition & 1 deletion testdata/TestCheckAgainstSpecData.json
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,7 @@
"hash": 1
}
},
"Err": "inner, unexpected EOF"
"Err": "inner, IAVL height (0) must be non-negative and greater than or equal to the layer number (3)"
},
"rejects only inner proof (hash mismatch)": {
"Proof": {
Expand Down
Loading