zpay32: adjust uint64 encoding to account for math.MaxUnit64

In this commit, we fix a logic error in our routine for converting a
uint64 to/from base32. Before this commit, we assumed that the max
number of groups was 12. However, the math.MaxUint64 (1<<64 - 1) can
actually consume more than 12 groups with an extra set of bits. Before
this commit, we would panic when attempting to parse an invoice
generated like so:
  * addinvoice --amt 1337000 --expiry 99999999999999999

To fix this issue, we modify our logic to expect at most 13 groups.
Additionally, we've added a new test that would panic before applying
this commit.

Fixes #972.
This commit is contained in:
Olaoluwa Osuntokun 2018-03-29 16:23:47 -07:00
parent 117441b7d0
commit 10847170ee
No known key found for this signature in database
GPG Key ID: 964EA263DD637C21
2 changed files with 43 additions and 9 deletions

View File

@ -1056,8 +1056,8 @@ func writeTaggedField(bufferBase32 *bytes.Buffer, dataType byte, data []byte) er
// base32ToUint64 converts a base32 encoded number to uint64. // base32ToUint64 converts a base32 encoded number to uint64.
func base32ToUint64(data []byte) (uint64, error) { func base32ToUint64(data []byte) (uint64, error) {
// Maximum that fits in uint64 is 64 / 5 = 12 groups. // Maximum that fits in uint64 is ceil(64 / 5) = 12 groups.
if len(data) > 12 { if len(data) > 13 {
return 0, fmt.Errorf("cannot parse data of length %d as uint64", return 0, fmt.Errorf("cannot parse data of length %d as uint64",
len(data)) len(data))
} }
@ -1077,9 +1077,9 @@ func uint64ToBase32(num uint64) []byte {
return []byte{0} return []byte{0}
} }
// To fit an uint64, we need at most is 64 / 5 = 12 groups. // To fit an uint64, we need at most is ceil(64 / 5) = 13 groups.
arr := make([]byte, 12) arr := make([]byte, 13)
i := 12 i := 13
for num > 0 { for num > 0 {
i-- i--
arr[i] = byte(num & uint64(31)) // 0b11111 in binary arr[i] = byte(num & uint64(31)) // 0b11111 in binary

View File

@ -2,6 +2,7 @@ package zpay32
import ( import (
"encoding/binary" "encoding/binary"
"math"
"reflect" "reflect"
"testing" "testing"
"time" "time"
@ -527,7 +528,11 @@ func TestParseExpiry(t *testing.T) {
result: &testExpiry60, result: &testExpiry60,
}, },
{ {
data: []byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc}, data: []byte{
0x0, 0x1, 0x2, 0x3, 0x4, 0x5,
0x6, 0x7, 0x8, 0x9, 0xa, 0xb,
0xc, 0x3,
},
valid: false, // data too long valid: false, // data too long
}, },
} }
@ -547,7 +552,8 @@ func TestParseExpiry(t *testing.T) {
} }
} }
// TestParseMinFinalCLTVExpiry checks that the minFinalCLTVExpiry is properly parsed. // TestParseMinFinalCLTVExpiry checks that the minFinalCLTVExpiry is properly
// parsed.
func TestParseMinFinalCLTVExpiry(t *testing.T) { func TestParseMinFinalCLTVExpiry(t *testing.T) {
t.Parallel() t.Parallel()
@ -567,12 +573,20 @@ func TestParseMinFinalCLTVExpiry(t *testing.T) {
result: 60, result: 60,
}, },
{ {
data: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc}, data: []byte{
0x1, 0x2, 0x3, 0x4, 0x5,
0x6, 0x7, 0x8, 0x9, 0xa,
0xb, 0xc,
},
valid: true, valid: true,
result: 38390726480144748, result: 38390726480144748,
}, },
{ {
data: []byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc}, data: []byte{
0x0, 0x1, 0x2, 0x3, 0x4, 0x5,
0x6, 0x7, 0x8, 0x9, 0xa, 0xb,
0xc, 0x94,
},
valid: false, // data too long valid: false, // data too long
}, },
} }
@ -592,6 +606,26 @@ func TestParseMinFinalCLTVExpiry(t *testing.T) {
} }
} }
// TestParseMinFinalCLTVExpiry tests that were able to properly encode/decode
// the math.MaxUint64 integer without panicking.
func TestParseMaxUint64Expiry(t *testing.T) {
t.Parallel()
expiry := uint64(math.MaxUint64)
expiryBytes := uint64ToBase32(expiry)
expiryReParse, err := base32ToUint64(expiryBytes)
if err != nil {
t.Fatalf("unable to parse uint64: %v", err)
}
if expiryReParse != expiry {
t.Fatalf("wrong expiry: expected %v got %v", expiry,
expiryReParse)
}
}
// TestParseFallbackAddr checks that the fallback address is properly parsed. // TestParseFallbackAddr checks that the fallback address is properly parsed.
func TestParseFallbackAddr(t *testing.T) { func TestParseFallbackAddr(t *testing.T) {
t.Parallel() t.Parallel()