Skip to content

Commit 5bb1b54

Browse files
colin-axnerdamiannolanAdityaSripal
authored
refactor: deobfuscate dragonberry (#383)
* refactor: deobfuscate, many thanks to Damian for the help! * fix: test + build * Update go/ops.go * chore: update error message to be more informative * chore: error wording * fix: test * test: add unit tests * chore: update godocs as per review suggestion * Update go/ops.go Co-authored-by: Damian Nolan <[email protected]> * refactor: remove one line fn * Update go/ops_test.go --------- Co-authored-by: Damian Nolan <[email protected]> Co-authored-by: Aditya <[email protected]>
1 parent c0b9fcb commit 5bb1b54

File tree

4 files changed

+164
-32
lines changed

4 files changed

+164
-32
lines changed

go/ops.go

+43-29
Original file line numberDiff line numberDiff line change
@@ -20,38 +20,56 @@ import (
2020
_ "golang.org/x/crypto/ripemd160" //nolint:staticcheck
2121
)
2222

23-
// validate the IAVL Ops
24-
func validateIavlOps(op opType, b int) error {
23+
// validateIavlOps validates the prefix to ensure it begins with
24+
// the height, size, and version of the IAVL tree. Each varint must be a bounded value.
25+
// In addition, the remaining bytes are validated to ensure they correspond to the correct
26+
// length. The layerNum is the inverse of the tree depth, i.e. depth=0 means leaf, depth>=1 means inner node
27+
func validateIavlOps(op opType, layerNum int) error {
2528
r := bytes.NewReader(op.GetPrefix())
2629

27-
values := []int64{}
28-
for i := 0; i < 3; i++ {
29-
varInt, err := binary.ReadVarint(r)
30-
if err != nil {
31-
return err
32-
}
33-
values = append(values, varInt)
30+
height, err := binary.ReadVarint(r)
31+
if err != nil {
32+
return fmt.Errorf("failed to read IAVL height varint: %w", err)
33+
}
34+
if int(height) < 0 || int(height) < layerNum {
35+
return fmt.Errorf("IAVL height (%d) must be non-negative and greater than or equal to the layer number (%d)", height, layerNum)
36+
}
3437

35-
// values must be bounded
36-
if int(varInt) < 0 {
37-
return fmt.Errorf("wrong value in IAVL leaf op")
38-
}
38+
size, err := binary.ReadVarint(r)
39+
if err != nil {
40+
return fmt.Errorf("failed to read IAVL size varint: %w", err)
3941
}
40-
if int(values[0]) < b {
41-
return fmt.Errorf("wrong value in IAVL leaf op")
42+
43+
if int(size) < 0 {
44+
return fmt.Errorf("IAVL size must be non-negative")
45+
}
46+
47+
version, err := binary.ReadVarint(r)
48+
if err != nil {
49+
return fmt.Errorf("failed to read IAVL version varint: %w", err)
4250
}
4351

44-
r2 := r.Len()
45-
if b == 0 {
46-
if r2 != 0 {
47-
return fmt.Errorf("invalid op")
52+
if int(version) < 0 {
53+
return fmt.Errorf("IAVL version must be non-negative")
54+
}
55+
56+
// grab the length of the remainder of the prefix
57+
remLen := r.Len()
58+
if layerNum == 0 {
59+
if remLen != 0 {
60+
return fmt.Errorf("expected remaining prefix length to be 0, got: %d", remLen)
4861
}
4962
} else {
50-
if !(r2^(0xff&0x01) == 0 || r2 == (0xde+int('v'))/10) {
51-
return fmt.Errorf("invalid op")
63+
// when the child comes from the left, the suffix if filled in
64+
// prefix: height | size | version | length byte (1 remainder)
65+
//
66+
// when the child comes from the right, the suffix is empty
67+
// prefix: height | size | version | length byte | 32 byte hash | next length byte (34 remainder)
68+
if remLen != 1 && remLen != 34 {
69+
return fmt.Errorf("remainder of prefix must be of length 1 or 34, got: %d", remLen)
5270
}
53-
if op.GetHash()^1 != 0 {
54-
return fmt.Errorf("invalid op")
71+
if op.GetHash() != HashOp_SHA256 {
72+
return fmt.Errorf("IAVL hash op must be %v", HashOp_SHA256)
5573
}
5674
}
5775
return nil
@@ -102,7 +120,7 @@ func (op *LeafOp) CheckAgainstSpec(spec *ProofSpec) error {
102120
return errors.New("spec.LeafSpec must be non-nil")
103121
}
104122

105-
if validateSpec(spec) {
123+
if spec.SpecEquals(IavlSpec) {
106124
err := validateIavlOps(op, 0)
107125
if err != nil {
108126
return err
@@ -143,7 +161,7 @@ func (op *InnerOp) CheckAgainstSpec(spec *ProofSpec, b int) error {
143161
return fmt.Errorf("unexpected HashOp: %d", op.Hash)
144162
}
145163

146-
if validateSpec(spec) {
164+
if spec.SpecEquals(IavlSpec) {
147165
err := validateIavlOps(op, b)
148166
if err != nil {
149167
return err
@@ -228,10 +246,6 @@ func prepareLeafData(hashOp HashOp, lengthOp LengthOp, data []byte) ([]byte, err
228246
return doLengthOp(lengthOp, hdata)
229247
}
230248

231-
func validateSpec(spec *ProofSpec) bool {
232-
return spec.SpecEquals(IavlSpec)
233-
}
234-
235249
type opType interface {
236250
GetPrefix() []byte
237251
GetHash() HashOp

go/ops_test.go

+119-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,124 @@ import (
99
"testing"
1010
)
1111

12+
func TestValidateIavlOps(t *testing.T) {
13+
var (
14+
op opType
15+
layerNum int
16+
)
17+
cases := []struct {
18+
name string
19+
malleate func()
20+
expError error
21+
}{
22+
{
23+
"success",
24+
func() {},
25+
nil,
26+
},
27+
{
28+
"failure: reading varint",
29+
func() {
30+
op.(*InnerOp).Prefix = []byte{}
31+
},
32+
errors.New("failed to read IAVL height varint: EOF"),
33+
},
34+
{
35+
"failure: invalid height value",
36+
func() {
37+
op.(*InnerOp).Prefix = []byte{1}
38+
},
39+
errors.New("IAVL height (-1) must be non-negative and greater than or equal to the layer number (1)"),
40+
},
41+
{
42+
"failure: invalid size varint",
43+
func() {
44+
var varintBuf [binary.MaxVarintLen64]byte
45+
prefix := convertVarIntToBytes(int64(5), varintBuf)
46+
op.(*InnerOp).Prefix = prefix
47+
},
48+
errors.New("failed to read IAVL size varint: EOF"),
49+
},
50+
{
51+
"failure: invalid size value",
52+
func() {
53+
var varintBuf [binary.MaxVarintLen64]byte
54+
prefix := convertVarIntToBytes(int64(5), varintBuf)
55+
prefix = append(prefix, convertVarIntToBytes(int64(-1), varintBuf)...) // size
56+
op.(*InnerOp).Prefix = prefix
57+
},
58+
errors.New("IAVL size must be non-negative"),
59+
},
60+
{
61+
"failure: invalid version varint",
62+
func() {
63+
var varintBuf [binary.MaxVarintLen64]byte
64+
prefix := convertVarIntToBytes(int64(5), varintBuf)
65+
prefix = append(prefix, convertVarIntToBytes(int64(10), varintBuf)...)
66+
op.(*InnerOp).Prefix = prefix
67+
},
68+
errors.New("failed to read IAVL version varint: EOF"),
69+
},
70+
{
71+
"failure: invalid version value",
72+
func() {
73+
var varintBuf [binary.MaxVarintLen64]byte
74+
prefix := convertVarIntToBytes(int64(5), varintBuf)
75+
prefix = append(prefix, convertVarIntToBytes(int64(10), varintBuf)...)
76+
prefix = append(prefix, convertVarIntToBytes(int64(-1), varintBuf)...) // version
77+
op.(*InnerOp).Prefix = prefix
78+
},
79+
errors.New("IAVL version must be non-negative"),
80+
},
81+
{
82+
"failure: invalid remaining length with layer number is 0",
83+
func() {
84+
layerNum = 0
85+
},
86+
fmt.Errorf("expected remaining prefix length to be 0, got: 1"),
87+
},
88+
{
89+
"failure: invalid remaining length with non-zero layer number",
90+
func() {
91+
layerNum = 1
92+
op.(*InnerOp).Prefix = append(op.(*InnerOp).Prefix, []byte{1}...)
93+
},
94+
fmt.Errorf("remainder of prefix must be of length 1 or 34, got: 2"),
95+
},
96+
{
97+
"failure: invalid hash",
98+
func() {
99+
op.(*InnerOp).Hash = HashOp_NO_HASH
100+
},
101+
fmt.Errorf("IAVL hash op must be %v", HashOp_SHA256),
102+
},
103+
}
104+
for _, tc := range cases {
105+
tc := tc
106+
107+
t.Run(tc.name, func(t *testing.T) {
108+
op = &InnerOp{
109+
Hash: HashOp_SHA256,
110+
Prefix: generateInnerOpPrefix(),
111+
Suffix: []byte(""),
112+
}
113+
layerNum = 1
114+
115+
tc.malleate()
116+
117+
err := validateIavlOps(op, layerNum)
118+
if tc.expError == nil && err != nil {
119+
t.Fatal(err)
120+
}
121+
122+
if tc.expError != nil && err.Error() != tc.expError.Error() {
123+
t.Fatalf("expected: %v, got: %v", tc.expError, err)
124+
}
125+
})
126+
127+
}
128+
}
129+
12130
func TestLeafOp(t *testing.T) {
13131
cases := LeafOpTestData(t)
14132

@@ -114,7 +232,7 @@ func TestInnerOpCheckAgainstSpec(t *testing.T) {
114232
func() {
115233
innerOp.Prefix = []byte{0x01}
116234
},
117-
fmt.Errorf("wrong value in IAVL leaf op"),
235+
fmt.Errorf("IAVL height (-1) must be non-negative and greater than or equal to the layer number (1)"),
118236
},
119237
{
120238
"failure: inner prefix starts with leaf prefix",

go/proof_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func TestCheckAgainstSpec(t *testing.T) {
5252
if tc.Err == "" && err != nil {
5353
t.Fatalf("Unexpected error: %v", err)
5454
} else if tc.Err != "" && tc.Err != err.Error() {
55-
t.Fatalf("Expected error: %s, got %s", tc.Err, err.Error())
55+
t.Fatalf("Expected error: %s, got: %s", tc.Err, err.Error())
5656
}
5757
})
5858
}

testdata/TestCheckAgainstSpecData.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@
907907
"hash": 1
908908
}
909909
},
910-
"Err": "inner, unexpected EOF"
910+
"Err": "inner, IAVL height (0) must be non-negative and greater than or equal to the layer number (3)"
911911
},
912912
"rejects only inner proof (hash mismatch)": {
913913
"Proof": {

0 commit comments

Comments
 (0)