Skip to content

Commit

Permalink
Merge pull request #217 from guggero/tlv-ticket
Browse files Browse the repository at this point in the history
sidecar: add ticket with TLV encoders
  • Loading branch information
guggero authored Mar 1, 2021
2 parents 00ac0e5 + 7c3bf31 commit c0a59f2
Show file tree
Hide file tree
Showing 5 changed files with 912 additions and 0 deletions.
96 changes: 96 additions & 0 deletions sidecar/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package sidecar

import (
"bytes"
"crypto/sha256"
"encoding/base64"
"fmt"
)

const (
// checksumLen is the number of bytes we add as a checksum. We'll take
// this many bytes out of the full SHA256 hash and add it to the string
// encoded version of the ticket as an additional integrity check.
checksumLen = 4
)

var (
// defaultStringEncoding is the default encoding algorithm we use to
// encode the gzipped binary data as a string.
defaultStringEncoding = base64.URLEncoding

// sidecarPrefix is a specially prepared prefix that will spell out
// "sidecar" when encoded as base64. It will cause all our sidecar
// tickets to be easily recognizable with a human readable part.
sidecarPrefix = []byte{0xb2, 0x27, 0x5e, 0x71, 0xaa, 0xc0}
)

// EncodeToString serializes and encodes the ticket as an URL safe string that
// contains a human readable prefix and a checksum.
func EncodeToString(t *Ticket) (string, error) {
var buf bytes.Buffer

// Add a prefix to make the ticket easily recognizable by human eyes.
if _, err := buf.Write(sidecarPrefix); err != nil {
return "", err
}

// Serialize the raw ticket itself.
if err := SerializeTicket(&buf, t); err != nil {
return "", err
}

// Create a checksum (SHA256) of all the bytes so far (prefix + raw
// ticket) and add 4 bytes of that to the string encoded version. This
// will prevent the ticket from appearing valid even if parts of it were
// not copy/pasted correctly.
hash := sha256.New()
_, _ = hash.Write(buf.Bytes())
checksum := hash.Sum(nil)[0:4]
if _, err := buf.Write(checksum); err != nil {
return "", err
}

return defaultStringEncoding.EncodeToString(buf.Bytes()), nil
}

// DecodeString decodes and then deserializes the given sidecar ticket string.
func DecodeString(s string) (*Ticket, error) {
rawBytes, err := defaultStringEncoding.DecodeString(s)
if err != nil {
return nil, err
}

if len(rawBytes) < len(sidecarPrefix)+checksumLen {
return nil, fmt.Errorf("not a sidecar ticket, invalid length")
}

totalLength := len(rawBytes)
prefixLength := len(sidecarPrefix)
payloadLength := totalLength - prefixLength - checksumLen

prefix := rawBytes[0:prefixLength]
payload := rawBytes[prefixLength : prefixLength+payloadLength]
checksum := rawBytes[totalLength-checksumLen : totalLength]

// Make sure the prefix matches our static prefix.
if !bytes.Equal(prefix, sidecarPrefix) {
return nil, fmt.Errorf("not a sicecar ticket, invalid prefix "+
"%x", prefix)
}

// Calculate the SHA256 sum of the prefix and payload and compare it to
// the checksum we also expect to be in the full string serialized
// ticket.
hash := sha256.New()
_, _ = hash.Write(prefix)
_, _ = hash.Write(payload)
calculatedChecksum := hash.Sum(nil)[0:4]
if !bytes.Equal(checksum, calculatedChecksum) {
return nil, fmt.Errorf("invalid sidecar ticket, checksum " +
"mismatch")
}

// Everything's fine, let's decode the actual payload.
return DeserializeTicket(bytes.NewReader(payload))
}
68 changes: 68 additions & 0 deletions sidecar/codec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package sidecar

import (
"math/big"
"testing"

"github.com/btcsuite/btcd/btcec"
"github.com/stretchr/testify/require"
)

var (
hardcodedTicket = "sidecarAAQgHBgUEAwIBAAIBYwMBAgp5CwgAAAAAAAADCQwIAA" +
"AAAAAAA3gNIQLVLm5gAB7Vh7eEvz8Y3CkF2DsvNuSWJj8oOxp1iSh5xw5AAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAFhRGFSEC1S5uYAAe1Ye3hL8_GNwpBdg7Lzbkli" +
"Y_KDsadYkoeccWIQMZ1hb2o3OyT-XUX7KWS-pAVBloIPQe8aCTqcjiNOV89x" +
"5kHyALFiEsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGMAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAISgiKSBjWE0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAA3VE-c="
)

// TestEncodeDecode tests that a ticket can be encoded and decoded from/to a
// string and back.
func TestEncodeDecode(t *testing.T) {
// Test with all struct members set.
ticketMaximal := &Ticket{
ID: [8]byte{7, 6, 5, 4, 3, 2, 1, 0},
Version: Version(99),
State: StateRegistered,
Offer: Offer{
Capacity: 777,
PushAmt: 888,
SignPubKey: testPubKey,
SigOfferDigest: &btcec.Signature{
R: new(big.Int).SetInt64(44),
S: new(big.Int).SetInt64(22),
},
},
Recipient: &Recipient{
NodePubKey: testPubKey,
MultiSigPubKey: testPubKey2,
},
Order: &Order{
BidNonce: [32]byte{11, 22, 33, 44},
SigOrderDigest: &btcec.Signature{
R: new(big.Int).SetInt64(99),
S: new(big.Int).SetInt64(33),
},
},
Execution: &Execution{
PendingChannelID: [32]byte{99, 88, 77},
},
}

serialized, err := EncodeToString(ticketMaximal)
require.NoError(t, err)

deserializedTicket, err := DecodeString(serialized)
require.NoError(t, err)
require.Equal(t, ticketMaximal, deserializedTicket)

// Make sure nothing changed in the encoding without us noticing by
// comparing it to a hard coded version.
deserializedTicket, err = DecodeString(hardcodedTicket)
require.NoError(t, err)
require.Equal(t, ticketMaximal, deserializedTicket)
}
Loading

0 comments on commit c0a59f2

Please sign in to comment.