Skip to content

Commit

Permalink
Merge pull request #4488 from cfromknecht/enforce-min-cltv-18
Browse files Browse the repository at this point in the history
multi: enforce conservative minimum for user-chosen CLTV deltas
  • Loading branch information
Roasbeef committed Jul 27, 2020
2 parents e64e71d + 53ab223 commit da16a19
Show file tree
Hide file tree
Showing 15 changed files with 2,133 additions and 837 deletions.
2 changes: 1 addition & 1 deletion channeldb/migration_01_to_11/migration_11_invoices.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
bitcoinCfg "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb/kvdb"
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11/zpay32"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/zpay32"
litecoinCfg "github.com/ltcsuite/ltcd/chaincfg"
)

Expand Down
158 changes: 158 additions & 0 deletions channeldb/migration_01_to_11/zpay32/amountunits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package zpay32

import (
"fmt"
"strconv"

"github.com/lightningnetwork/lnd/lnwire"
)

var (
// toMSat is a map from a unit to a function that converts an amount
// of that unit to millisatoshis.
toMSat = map[byte]func(uint64) (lnwire.MilliSatoshi, error){
'm': mBtcToMSat,
'u': uBtcToMSat,
'n': nBtcToMSat,
'p': pBtcToMSat,
}

// fromMSat is a map from a unit to a function that converts an amount
// in millisatoshis to an amount of that unit.
fromMSat = map[byte]func(lnwire.MilliSatoshi) (uint64, error){
'm': mSatToMBtc,
'u': mSatToUBtc,
'n': mSatToNBtc,
'p': mSatToPBtc,
}
)

// mBtcToMSat converts the given amount in milliBTC to millisatoshis.
func mBtcToMSat(m uint64) (lnwire.MilliSatoshi, error) {
return lnwire.MilliSatoshi(m) * 100000000, nil
}

// uBtcToMSat converts the given amount in microBTC to millisatoshis.
func uBtcToMSat(u uint64) (lnwire.MilliSatoshi, error) {
return lnwire.MilliSatoshi(u * 100000), nil
}

// nBtcToMSat converts the given amount in nanoBTC to millisatoshis.
func nBtcToMSat(n uint64) (lnwire.MilliSatoshi, error) {
return lnwire.MilliSatoshi(n * 100), nil
}

// pBtcToMSat converts the given amount in picoBTC to millisatoshis.
func pBtcToMSat(p uint64) (lnwire.MilliSatoshi, error) {
if p < 10 {
return 0, fmt.Errorf("minimum amount is 10p")
}
if p%10 != 0 {
return 0, fmt.Errorf("amount %d pBTC not expressible in msat",
p)
}
return lnwire.MilliSatoshi(p / 10), nil
}

// mSatToMBtc converts the given amount in millisatoshis to milliBTC.
func mSatToMBtc(msat lnwire.MilliSatoshi) (uint64, error) {
if msat%100000000 != 0 {
return 0, fmt.Errorf("%d msat not expressible "+
"in mBTC", msat)
}
return uint64(msat / 100000000), nil
}

// mSatToUBtc converts the given amount in millisatoshis to microBTC.
func mSatToUBtc(msat lnwire.MilliSatoshi) (uint64, error) {
if msat%100000 != 0 {
return 0, fmt.Errorf("%d msat not expressible "+
"in uBTC", msat)
}
return uint64(msat / 100000), nil
}

// mSatToNBtc converts the given amount in millisatoshis to nanoBTC.
func mSatToNBtc(msat lnwire.MilliSatoshi) (uint64, error) {
if msat%100 != 0 {
return 0, fmt.Errorf("%d msat not expressible in nBTC", msat)
}
return uint64(msat / 100), nil
}

// mSatToPBtc converts the given amount in millisatoshis to picoBTC.
func mSatToPBtc(msat lnwire.MilliSatoshi) (uint64, error) {
return uint64(msat * 10), nil
}

// decodeAmount returns the amount encoded by the provided string in
// millisatoshi.
func decodeAmount(amount string) (lnwire.MilliSatoshi, error) {
if len(amount) < 1 {
return 0, fmt.Errorf("amount must be non-empty")
}

// If last character is a digit, then the amount can just be
// interpreted as BTC.
char := amount[len(amount)-1]
digit := char - '0'
if digit >= 0 && digit <= 9 {
btc, err := strconv.ParseUint(amount, 10, 64)
if err != nil {
return 0, err
}
return lnwire.MilliSatoshi(btc) * mSatPerBtc, nil
}

// If not a digit, it must be part of the known units.
conv, ok := toMSat[char]
if !ok {
return 0, fmt.Errorf("unknown multiplier %c", char)
}

// Known unit.
num := amount[:len(amount)-1]
if len(num) < 1 {
return 0, fmt.Errorf("number must be non-empty")
}

am, err := strconv.ParseUint(num, 10, 64)
if err != nil {
return 0, err
}

return conv(am)
}

// encodeAmount encodes the provided millisatoshi amount using as few characters
// as possible.
func encodeAmount(msat lnwire.MilliSatoshi) (string, error) {
// If possible to express in BTC, that will always be the shortest
// representation.
if msat%mSatPerBtc == 0 {
return strconv.FormatInt(int64(msat/mSatPerBtc), 10), nil
}

// Should always be expressible in pico BTC.
pico, err := fromMSat['p'](msat)
if err != nil {
return "", fmt.Errorf("unable to express %d msat as pBTC: %v",
msat, err)
}
shortened := strconv.FormatUint(pico, 10) + "p"
for unit, conv := range fromMSat {
am, err := conv(msat)
if err != nil {
// Not expressible using this unit.
continue
}

// Save the shortest found representation.
str := strconv.FormatUint(am, 10) + string(unit)
if len(str) < len(shortened) {
shortened = str
}
}

return shortened, nil
}
168 changes: 168 additions & 0 deletions channeldb/migration_01_to_11/zpay32/bech32.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package zpay32

import (
"fmt"
"strings"
)

const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"

var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}

// NOTE: This method it a slight modification of the method bech32.Decode found
// btcutil, allowing strings to be more than 90 characters.

// decodeBech32 decodes a bech32 encoded string, returning the human-readable
// part and the data part excluding the checksum.
// Note: the data will be base32 encoded, that is each element of the returned
// byte array will encode 5 bits of data. Use the ConvertBits method to convert
// this to 8-bit representation.
func decodeBech32(bech string) (string, []byte, error) {
// The maximum allowed length for a bech32 string is 90. It must also
// be at least 8 characters, since it needs a non-empty HRP, a
// separator, and a 6 character checksum.
// NB: The 90 character check specified in BIP173 is skipped here, to
// allow strings longer than 90 characters.
if len(bech) < 8 {
return "", nil, fmt.Errorf("invalid bech32 string length %d",
len(bech))
}
// Only ASCII characters between 33 and 126 are allowed.
for i := 0; i < len(bech); i++ {
if bech[i] < 33 || bech[i] > 126 {
return "", nil, fmt.Errorf("invalid character in "+
"string: '%c'", bech[i])
}
}

// The characters must be either all lowercase or all uppercase.
lower := strings.ToLower(bech)
upper := strings.ToUpper(bech)
if bech != lower && bech != upper {
return "", nil, fmt.Errorf("string not all lowercase or all " +
"uppercase")
}

// We'll work with the lowercase string from now on.
bech = lower

// The string is invalid if the last '1' is non-existent, it is the
// first character of the string (no human-readable part) or one of the
// last 6 characters of the string (since checksum cannot contain '1'),
// or if the string is more than 90 characters in total.
one := strings.LastIndexByte(bech, '1')
if one < 1 || one+7 > len(bech) {
return "", nil, fmt.Errorf("invalid index of 1")
}

// The human-readable part is everything before the last '1'.
hrp := bech[:one]
data := bech[one+1:]

// Each character corresponds to the byte with value of the index in
// 'charset'.
decoded, err := toBytes(data)
if err != nil {
return "", nil, fmt.Errorf("failed converting data to bytes: "+
"%v", err)
}

if !bech32VerifyChecksum(hrp, decoded) {
moreInfo := ""
checksum := bech[len(bech)-6:]
expected, err := toChars(bech32Checksum(hrp,
decoded[:len(decoded)-6]))
if err == nil {
moreInfo = fmt.Sprintf("Expected %v, got %v.",
expected, checksum)
}
return "", nil, fmt.Errorf("checksum failed. " + moreInfo)
}

// We exclude the last 6 bytes, which is the checksum.
return hrp, decoded[:len(decoded)-6], nil
}

// toBytes converts each character in the string 'chars' to the value of the
// index of the corresponding character in 'charset'.
func toBytes(chars string) ([]byte, error) {
decoded := make([]byte, 0, len(chars))
for i := 0; i < len(chars); i++ {
index := strings.IndexByte(charset, chars[i])
if index < 0 {
return nil, fmt.Errorf("invalid character not part of "+
"charset: %v", chars[i])
}
decoded = append(decoded, byte(index))
}
return decoded, nil
}

// toChars converts the byte slice 'data' to a string where each byte in 'data'
// encodes the index of a character in 'charset'.
func toChars(data []byte) (string, error) {
result := make([]byte, 0, len(data))
for _, b := range data {
if int(b) >= len(charset) {
return "", fmt.Errorf("invalid data byte: %v", b)
}
result = append(result, charset[b])
}
return string(result), nil
}

// For more details on the checksum calculation, please refer to BIP 173.
func bech32Checksum(hrp string, data []byte) []byte {
// Convert the bytes to list of integers, as this is needed for the
// checksum calculation.
integers := make([]int, len(data))
for i, b := range data {
integers[i] = int(b)
}
values := append(bech32HrpExpand(hrp), integers...)
values = append(values, []int{0, 0, 0, 0, 0, 0}...)
polymod := bech32Polymod(values) ^ 1
var res []byte
for i := 0; i < 6; i++ {
res = append(res, byte((polymod>>uint(5*(5-i)))&31))
}
return res
}

// For more details on the polymod calculation, please refer to BIP 173.
func bech32Polymod(values []int) int {
chk := 1
for _, v := range values {
b := chk >> 25
chk = (chk&0x1ffffff)<<5 ^ v
for i := 0; i < 5; i++ {
if (b>>uint(i))&1 == 1 {
chk ^= gen[i]
}
}
}
return chk
}

// For more details on HRP expansion, please refer to BIP 173.
func bech32HrpExpand(hrp string) []int {
v := make([]int, 0, len(hrp)*2+1)
for i := 0; i < len(hrp); i++ {
v = append(v, int(hrp[i]>>5))
}
v = append(v, 0)
for i := 0; i < len(hrp); i++ {
v = append(v, int(hrp[i]&31))
}
return v
}

// For more details on the checksum verification, please refer to BIP 173.
func bech32VerifyChecksum(hrp string, data []byte) bool {
integers := make([]int, len(data))
for i, b := range data {
integers[i] = int(b)
}
concat := append(bech32HrpExpand(hrp), integers...)
return bech32Polymod(concat) == 1
}

0 comments on commit da16a19

Please sign in to comment.