invoice: refactor parsing tagged fields

This commit refactors parsing each of the tagged fields of an invoice
into their own method. This makes the code easier to read and will allow
us to introduce unit tests for each parsing method.
This commit is contained in:
Wilmer Paulino 2018-01-09 00:46:29 -05:00
parent cb7699068b
commit 6f5d673679
No known key found for this signature in database
GPG Key ID: 6DF57B9F9514972F

@ -588,7 +588,7 @@ func parseTimestamp(data []byte) (uint64, error) {
func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) error { func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) error {
index := 0 index := 0
for { for {
// If less than 3 groups less, it cannot possibly contain more // If there are less than 3 groups to read, there cannot be more
// interesting information, as we need the type (1 group) and // interesting information, as we need the type (1 group) and
// length (2 groups). // length (2 groups).
if len(fields)-index < 3 { if len(fields)-index < 3 {
@ -596,7 +596,10 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
} }
typ := fields[index] typ := fields[index]
dataLength := uint16(fields[index+1]<<5) | uint16(fields[index+2]) dataLength, err := parseFieldDataLength(fields[index+1 : index+3])
if err != nil {
return err
}
// If we don't have enough field data left to read this length, // If we don't have enough field data left to read this length,
// return error. // return error.
@ -616,17 +619,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
continue continue
} }
if dataLength != hashBase32Len { invoice.PaymentHash, err = parsePaymentHash(base32Data)
// Skipping unknown field length.
continue
}
hash, err := bech32.ConvertBits(base32Data, 5, 8, false)
if err != nil {
return err
}
var pHash [32]byte
copy(pHash[:], hash[:])
invoice.PaymentHash = &pHash
case fieldTypeD: case fieldTypeD:
if invoice.Description != nil { if invoice.Description != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@ -634,13 +627,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
continue continue
} }
base256Data, err := bech32.ConvertBits(base32Data, 5, 8, invoice.Description, err = parseDescription(base32Data)
false)
if err != nil {
return err
}
desc := string(base256Data)
invoice.Description = &desc
case fieldTypeN: case fieldTypeN:
if invoice.Destination != nil { if invoice.Destination != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@ -648,21 +635,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
continue continue
} }
if len(base32Data) != pubKeyBase32Len { invoice.Destination, err = parseDestination(base32Data)
// Skip unknown length.
continue
}
base256Data, err := bech32.ConvertBits(base32Data, 5, 8,
false)
if err != nil {
return err
}
invoice.Destination, err = btcec.ParsePubKey(base256Data,
btcec.S256())
if err != nil {
return err
}
case fieldTypeH: case fieldTypeH:
if invoice.DescriptionHash != nil { if invoice.DescriptionHash != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@ -670,17 +643,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
continue continue
} }
if len(base32Data) != hashBase32Len { invoice.DescriptionHash, err = parseDescriptionHash(base32Data)
// Skip unknown length.
continue
}
hash, err := bech32.ConvertBits(base32Data, 5, 8, false)
if err != nil {
return err
}
var dHash [32]byte
copy(dHash[:], hash[:])
invoice.DescriptionHash = &dHash
case fieldTypeX: case fieldTypeX:
if invoice.expiry != nil { if invoice.expiry != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@ -688,12 +651,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
continue continue
} }
exp, err := base32ToUint64(base32Data) invoice.expiry, err = parseExpiry(base32Data)
if err != nil {
return err
}
dur := time.Duration(exp) * time.Second
invoice.expiry = &dur
case fieldTypeC: case fieldTypeC:
if invoice.minFinalCLTVExpiry != nil { if invoice.minFinalCLTVExpiry != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@ -701,11 +659,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
continue continue
} }
expiry, err := base32ToUint64(base32Data) invoice.minFinalCLTVExpiry, err = parseMinFinalCLTVExpiry(base32Data)
if err != nil {
return err
}
invoice.minFinalCLTVExpiry = &expiry
case fieldTypeF: case fieldTypeF:
if invoice.FallbackAddr != nil { if invoice.FallbackAddr != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@ -713,56 +667,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
continue continue
} }
var addr btcutil.Address invoice.FallbackAddr, err = parseFallbackAddr(base32Data, net)
version := base32Data[0]
switch version {
case 0:
witness, err := bech32.ConvertBits(
base32Data[1:], 5, 8, false)
if err != nil {
return err
}
switch len(witness) {
case 20:
addr, err = btcutil.NewAddressWitnessPubKeyHash(
witness, net)
case 32:
addr, err = btcutil.NewAddressWitnessScriptHash(
witness, net)
default:
return fmt.Errorf("unknown witness "+
"program length: %d", len(witness))
}
if err != nil {
return err
}
case 17:
pkHash, err := bech32.ConvertBits(base32Data[1:],
5, 8, false)
if err != nil {
return err
}
addr, err = btcutil.NewAddressPubKeyHash(pkHash,
net)
if err != nil {
return err
}
case 18:
scriptHash, err := bech32.ConvertBits(
base32Data[1:], 5, 8, false)
if err != nil {
return err
}
addr, err = btcutil.NewAddressScriptHashFromHash(
scriptHash, net)
if err != nil {
return err
}
default:
// Skipping unknown witness version.
continue
}
invoice.FallbackAddr = addr
case fieldTypeR: case fieldTypeR:
if invoice.RoutingInfo != nil { if invoice.RoutingInfo != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@ -770,39 +675,204 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
continue continue
} }
base256Data, err := bech32.ConvertBits(base32Data, 5, 8, invoice.RoutingInfo, err = parseRoutingInfo(base32Data)
false)
if err != nil {
return err
}
for len(base256Data) > 0 {
info := ExtraRoutingInfo{}
info.PubKey, err = btcec.ParsePubKey(
base256Data[:33], btcec.S256())
if err != nil {
return err
}
info.ShortChanID = binary.BigEndian.Uint64(
base256Data[33:41])
info.FeeBaseMsat = binary.BigEndian.Uint32(
base256Data[41:45])
info.FeeProportionalMillionths = binary.BigEndian.Uint32(
base256Data[45:49])
info.CltvExpDelta = binary.BigEndian.Uint16(
base256Data[49:51])
invoice.RoutingInfo = append(
invoice.RoutingInfo, info)
base256Data = base256Data[51:]
}
default: default:
// Ignore unknown type. // Ignore unknown type.
} }
// Check if there was an error from parsing any of the tagged
// fields and return it.
if err != nil {
return err
}
} }
return nil return nil
} }
// parseFieldDataLength converts the two byte slice into a uint16.
func parseFieldDataLength(data []byte) (uint16, error) {
if len(data) != 2 {
return 0, fmt.Errorf("data length must be 2 bytes, was %d",
len(data))
}
return uint16(data[0]<<5) | uint16(data[1]), nil
}
// parsePaymentHash converts a 256-bit payment hash (encoded in base32)
// to *[32]byte.
func parsePaymentHash(data []byte) (*[32]byte, error) {
var paymentHash [32]byte
// As BOLT-11 states, a reader must skip over the payment hash field if
// it does not have a length of 52, so avoid returning an error.
if len(data) != hashBase32Len {
return nil, nil
}
hash, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
copy(paymentHash[:], hash[:])
return &paymentHash, nil
}
// parseDescription converts the data (encoded in base32) into a string to use
// as the description.
func parseDescription(data []byte) (*string, error) {
base256Data, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
description := string(base256Data)
return &description, nil
}
// parseDestination converts the data (encoded in base32) into a 33-byte public
// key of the payee node.
func parseDestination(data []byte) (*btcec.PublicKey, error) {
// As BOLT-11 states, a reader must skip over the destination field
// if it does not have a length of 53, so avoid returning an error.
if len(data) != pubKeyBase32Len {
return nil, nil
}
base256Data, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
return btcec.ParsePubKey(base256Data, btcec.S256())
}
// parseDescriptionHash converts a 256-bit description hash (encoded in base32)
// to *[32]byte.
func parseDescriptionHash(data []byte) (*[32]byte, error) {
var descriptionHash [32]byte
// As BOLT-11 states, a reader must skip over the description hash field
// if it does not have a length of 52, so avoid returning an error.
if len(data) != hashBase32Len {
return nil, nil
}
hash, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
copy(descriptionHash[:], hash[:])
return &descriptionHash, nil
}
// parseExpiry converts the data (encoded in base32) into the expiry time.
func parseExpiry(data []byte) (*time.Duration, error) {
expiry, err := base32ToUint64(data)
if err != nil {
return nil, err
}
duration := time.Duration(expiry) * time.Second
return &duration, nil
}
// parseMinFinalCLTVExpiry converts the data (encoded in base32) into a uint64
// to use as the minFinalCLTVExpiry.
func parseMinFinalCLTVExpiry(data []byte) (*uint64, error) {
expiry, err := base32ToUint64(data)
if err != nil {
return nil, err
}
return &expiry, nil
}
// parseFallbackAddr converts the data (encoded in base32) into a fallback
// on-chain address.
func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, error) {
var addr btcutil.Address
version := data[0]
switch version {
case 0:
witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
if err != nil {
return nil, err
}
switch len(witness) {
case 20:
addr, err = btcutil.NewAddressWitnessPubKeyHash(witness, net)
case 32:
addr, err = btcutil.NewAddressWitnessScriptHash(witness, net)
default:
return nil, fmt.Errorf("unknown witness program length %d",
len(witness))
}
if err != nil {
return nil, err
}
case 17:
pubKeyHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
if err != nil {
return nil, err
}
addr, err = btcutil.NewAddressPubKeyHash(pubKeyHash, net)
if err != nil {
return nil, err
}
case 18:
scriptHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
if err != nil {
return nil, err
}
addr, err = btcutil.NewAddressScriptHashFromHash(scriptHash, net)
if err != nil {
return nil, err
}
default:
// Ignore unknown version.
}
return addr, nil
}
// parseRoutingInfo converts the data (encoded in base32) into an array
// containing one or more entries of extra routing info.
func parseRoutingInfo(data []byte) ([]ExtraRoutingInfo, error) {
base256Data, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
var routingInfo []ExtraRoutingInfo
info := ExtraRoutingInfo{}
for len(base256Data) > 0 {
info.PubKey, err = btcec.ParsePubKey(base256Data[:33], btcec.S256())
if err != nil {
return nil, err
}
info.ShortChanID = binary.BigEndian.Uint64(base256Data[33:41])
info.FeeBaseMsat = binary.BigEndian.Uint32(base256Data[41:45])
info.FeeProportionalMillionths = binary.BigEndian.Uint32(base256Data[45:49])
info.CltvExpDelta = binary.BigEndian.Uint16(base256Data[49:51])
routingInfo = append(routingInfo, info)
base256Data = base256Data[51:]
}
return routingInfo, nil
}
// writeTaggedFields writes the non-nil tagged fields of the Invoice to the // writeTaggedFields writes the non-nil tagged fields of the Invoice to the
// base32 buffer. // base32 buffer.
func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error { func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {