Merge pull request #2122 from cfromknecht/blob-variable-length-addr

[watchtower/blob] variable length sweep addr
This commit is contained in:
Olaoluwa Osuntokun 2018-10-31 17:31:29 -07:00 committed by GitHub
commit fb4d3909a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 183 additions and 90 deletions

@ -33,14 +33,19 @@ const (
CiphertextExpansion = 16 CiphertextExpansion = 16
// V0PlaintextSize is the plaintext size of a version 0 encoded blob. // V0PlaintextSize is the plaintext size of a version 0 encoded blob.
// sweep address: 42 bytes // sweep address length: 1 byte
// padded sweep address: 42 bytes
// revocation pubkey: 33 bytes // revocation pubkey: 33 bytes
// local delay pubkey: 33 bytes // local delay pubkey: 33 bytes
// csv delay: 4 bytes // csv delay: 4 bytes
// commit to-local revocation sig: 64 bytes // commit to-local revocation sig: 64 bytes
// commit to-remote pubkey: 33 bytes, maybe blank // commit to-remote pubkey: 33 bytes, maybe blank
// commit to-remote sig: 64 bytes, maybe blank // commit to-remote sig: 64 bytes, maybe blank
V0PlaintextSize = 273 V0PlaintextSize = 274
// MaxSweepAddrSize defines the maximum sweep address size that can be
// encoded in a blob.
MaxSweepAddrSize = 42
) )
// Size returns the size of the encoded-and-encrypted blob in bytes. // Size returns the size of the encoded-and-encrypted blob in bytes.
@ -89,6 +94,14 @@ var (
ErrNoCommitToRemoteOutput = errors.New( ErrNoCommitToRemoteOutput = errors.New(
"cannot obtain commit to-remote p2wkh output script from blob", "cannot obtain commit to-remote p2wkh output script from blob",
) )
// ErrSweepAddressToLong is returned when trying to encode or decode a
// sweep address with length greater than the maximum length of 42
// bytes, which supports p2wkh and p2sh addresses.
ErrSweepAddressToLong = fmt.Errorf(
"sweep address must be less than or equal to %d bytes long",
MaxSweepAddrSize,
)
) )
// PubKey is a 33-byte, serialized compressed public key. // PubKey is a 33-byte, serialized compressed public key.
@ -108,7 +121,7 @@ type JusticeKit struct {
// //
// NOTE: This is chosen to be the length of a maximally sized witness // NOTE: This is chosen to be the length of a maximally sized witness
// program. // program.
SweepAddress [42]byte SweepAddress []byte
// RevocationPubKey is the compressed pubkey that guards the revocation // RevocationPubKey is the compressed pubkey that guards the revocation
// clause of the remote party's to-local output. // clause of the remote party's to-local output.
@ -318,10 +331,11 @@ func (b *JusticeKit) decode(r io.Reader, ver uint16) error {
// encodeV0 encodes the JusticeKit using the version 0 encoding scheme to the // encodeV0 encodes the JusticeKit using the version 0 encoding scheme to the
// provided io.Writer. The encoding supports sweeping of the commit to-local // provided io.Writer. The encoding supports sweeping of the commit to-local
// output, and optionally the commit to-remote output. The encoding produces a // output, and optionally the commit to-remote output. The encoding produces a
// constant-size plaintext size of 273 bytes. // constant-size plaintext size of 274 bytes.
// //
// blob version 0 plaintext encoding: // blob version 0 plaintext encoding:
// sweep address: 42 bytes // sweep address length: 1 byte
// padded sweep address: 42 bytes
// revocation pubkey: 33 bytes // revocation pubkey: 33 bytes
// local delay pubkey: 33 bytes // local delay pubkey: 33 bytes
// csv delay: 4 bytes // csv delay: 4 bytes
@ -329,8 +343,23 @@ func (b *JusticeKit) decode(r io.Reader, ver uint16) error {
// commit to-remote pubkey: 33 bytes, maybe blank // commit to-remote pubkey: 33 bytes, maybe blank
// commit to-remote sig: 64 bytes, maybe blank // commit to-remote sig: 64 bytes, maybe blank
func (b *JusticeKit) encodeV0(w io.Writer) error { func (b *JusticeKit) encodeV0(w io.Writer) error {
// Write 42-byte sweep address where client funds will be deposited. // Assert the sweep address length is sane.
_, err := w.Write(b.SweepAddress[:]) if len(b.SweepAddress) > MaxSweepAddrSize {
return ErrSweepAddressToLong
}
// Write the actual length of the sweep address as a single byte.
err := binary.Write(w, byteOrder, uint8(len(b.SweepAddress)))
if err != nil {
return err
}
// Pad the sweep address to our maximum length of 42 bytes.
var sweepAddressBuf [MaxSweepAddrSize]byte
copy(sweepAddressBuf[:], b.SweepAddress)
// Write padded 42-byte sweep address.
_, err = w.Write(sweepAddressBuf[:])
if err != nil { if err != nil {
return err return err
} }
@ -371,12 +400,13 @@ func (b *JusticeKit) encodeV0(w io.Writer) error {
} }
// decodeV0 reconstructs a JusticeKit from the io.Reader, using version 0 // decodeV0 reconstructs a JusticeKit from the io.Reader, using version 0
// encoding scheme. This will parse a constant size input stream of 273 bytes to // encoding scheme. This will parse a constant size input stream of 274 bytes to
// recover information for the commit to-local output, and possibly the commit // recover information for the commit to-local output, and possibly the commit
// to-remote output. // to-remote output.
// //
// blob version 0 plaintext encoding: // blob version 0 plaintext encoding:
// sweep address: 42 bytes // sweep address length: 1 byte
// padded sweep address: 42 bytes
// revocation pubkey: 33 bytes // revocation pubkey: 33 bytes
// local delay pubkey: 33 bytes // local delay pubkey: 33 bytes
// csv delay: 4 bytes // csv delay: 4 bytes
@ -384,12 +414,29 @@ func (b *JusticeKit) encodeV0(w io.Writer) error {
// commit to-remote pubkey: 33 bytes, maybe blank // commit to-remote pubkey: 33 bytes, maybe blank
// commit to-remote sig: 64 bytes, maybe blank // commit to-remote sig: 64 bytes, maybe blank
func (b *JusticeKit) decodeV0(r io.Reader) error { func (b *JusticeKit) decodeV0(r io.Reader) error {
// Read 42-byte sweep address where client funds will be deposited. // Read the sweep address length as a single byte.
_, err := io.ReadFull(r, b.SweepAddress[:]) var sweepAddrLen uint8
err := binary.Read(r, byteOrder, &sweepAddrLen)
if err != nil { if err != nil {
return err return err
} }
// Assert the sweep address length is sane.
if sweepAddrLen > MaxSweepAddrSize {
return ErrSweepAddressToLong
}
// Read padded 42-byte sweep address.
var sweepAddressBuf [MaxSweepAddrSize]byte
_, err = io.ReadFull(r, sweepAddressBuf[:])
if err != nil {
return err
}
// Parse sweep address from padded buffer.
b.SweepAddress = make([]byte, sweepAddrLen)
copy(b.SweepAddress, sweepAddressBuf[:])
// Read 33-byte revocation public key. // Read 33-byte revocation public key.
_, err = io.ReadFull(r, b.RevocationPubKey[:]) _, err = io.ReadFull(r, b.RevocationPubKey[:])
if err != nil { if err != nil {

@ -27,10 +27,20 @@ func makeSig(i int) lnwire.Sig {
return sig return sig
} }
var descriptorTests = []struct { func makeAddr(size int) []byte {
addr := make([]byte, size)
if _, err := io.ReadFull(rand.Reader, addr); err != nil {
panic("unable to create addr")
}
return addr
}
type descriptorTest struct {
name string name string
encVersion uint16 encVersion uint16
decVersion uint16 decVersion uint16
sweepAddr []byte
revPubKey blob.PubKey revPubKey blob.PubKey
delayPubKey blob.PubKey delayPubKey blob.PubKey
csvDelay uint32 csvDelay uint32
@ -40,11 +50,14 @@ var descriptorTests = []struct {
commitToRemoteSig lnwire.Sig commitToRemoteSig lnwire.Sig
encErr error encErr error
decErr error decErr error
}{ }
var descriptorTests = []descriptorTest{
{ {
name: "to-local only", name: "to-local only",
encVersion: 0, encVersion: 0,
decVersion: 0, decVersion: 0,
sweepAddr: makeAddr(22),
revPubKey: makePubKey(0), revPubKey: makePubKey(0),
delayPubKey: makePubKey(1), delayPubKey: makePubKey(1),
csvDelay: 144, csvDelay: 144,
@ -54,6 +67,7 @@ var descriptorTests = []struct {
name: "to-local and p2wkh", name: "to-local and p2wkh",
encVersion: 0, encVersion: 0,
decVersion: 0, decVersion: 0,
sweepAddr: makeAddr(22),
revPubKey: makePubKey(0), revPubKey: makePubKey(0),
delayPubKey: makePubKey(1), delayPubKey: makePubKey(1),
csvDelay: 144, csvDelay: 144,
@ -66,6 +80,7 @@ var descriptorTests = []struct {
name: "unknown encrypt version", name: "unknown encrypt version",
encVersion: 1, encVersion: 1,
decVersion: 0, decVersion: 0,
sweepAddr: makeAddr(34),
revPubKey: makePubKey(0), revPubKey: makePubKey(0),
delayPubKey: makePubKey(1), delayPubKey: makePubKey(1),
csvDelay: 144, csvDelay: 144,
@ -76,12 +91,44 @@ var descriptorTests = []struct {
name: "unknown decrypt version", name: "unknown decrypt version",
encVersion: 0, encVersion: 0,
decVersion: 1, decVersion: 1,
sweepAddr: makeAddr(34),
revPubKey: makePubKey(0), revPubKey: makePubKey(0),
delayPubKey: makePubKey(1), delayPubKey: makePubKey(1),
csvDelay: 144, csvDelay: 144,
commitToLocalSig: makeSig(1), commitToLocalSig: makeSig(1),
decErr: blob.ErrUnknownBlobVersion, decErr: blob.ErrUnknownBlobVersion,
}, },
{
name: "sweep addr length zero",
encVersion: 0,
decVersion: 0,
sweepAddr: makeAddr(0),
revPubKey: makePubKey(0),
delayPubKey: makePubKey(1),
csvDelay: 144,
commitToLocalSig: makeSig(1),
},
{
name: "sweep addr max size",
encVersion: 0,
decVersion: 0,
sweepAddr: makeAddr(blob.MaxSweepAddrSize),
revPubKey: makePubKey(0),
delayPubKey: makePubKey(1),
csvDelay: 144,
commitToLocalSig: makeSig(1),
},
{
name: "sweep addr too long",
encVersion: 0,
decVersion: 0,
sweepAddr: makeAddr(blob.MaxSweepAddrSize + 1),
revPubKey: makePubKey(0),
delayPubKey: makePubKey(1),
csvDelay: 144,
commitToLocalSig: makeSig(1),
encErr: blob.ErrSweepAddressToLong,
},
} }
// TestBlobJusticeKitEncryptDecrypt asserts that encrypting and decrypting a // TestBlobJusticeKitEncryptDecrypt asserts that encrypting and decrypting a
@ -89,82 +136,81 @@ var descriptorTests = []struct {
// when passed invalid combinations, and that all successfully encrypted blobs // when passed invalid combinations, and that all successfully encrypted blobs
// are of constant size. // are of constant size.
func TestBlobJusticeKitEncryptDecrypt(t *testing.T) { func TestBlobJusticeKitEncryptDecrypt(t *testing.T) {
for i, test := range descriptorTests { for _, test := range descriptorTests {
boj := &blob.JusticeKit{ t.Run(test.name, func(t *testing.T) {
RevocationPubKey: test.revPubKey, testBlobJusticeKitEncryptDecrypt(t, test)
LocalDelayPubKey: test.delayPubKey, })
CSVDelay: test.csvDelay, }
CommitToLocalSig: test.commitToLocalSig, }
CommitToRemotePubKey: test.commitToRemotePubKey,
CommitToRemoteSig: test.commitToRemoteSig, func testBlobJusticeKitEncryptDecrypt(t *testing.T, test descriptorTest) {
} boj := &blob.JusticeKit{
SweepAddress: test.sweepAddr,
// Generate a random encryption key for the blob. The key is RevocationPubKey: test.revPubKey,
// sized at 32 byte, as in practice we will be using the remote LocalDelayPubKey: test.delayPubKey,
// party's commitment txid as the key. CSVDelay: test.csvDelay,
key := make([]byte, blob.KeySize) CommitToLocalSig: test.commitToLocalSig,
_, err := io.ReadFull(rand.Reader, key) CommitToRemotePubKey: test.commitToRemotePubKey,
if err != nil { CommitToRemoteSig: test.commitToRemoteSig,
t.Fatalf("test #%d %s -- unable to generate blob "+ }
"encryption key: %v", i, test.name, err)
} // Generate a random encryption key for the blob. The key is
// sized at 32 byte, as in practice we will be using the remote
nonce := make([]byte, blob.NonceSize) // party's commitment txid as the key.
_, err = io.ReadFull(rand.Reader, nonce) key := make([]byte, blob.KeySize)
if err != nil { _, err := io.ReadFull(rand.Reader, key)
t.Fatalf("test #%d %s -- unable to generate nonce "+ if err != nil {
"nonce: %v", i, test.name, err) t.Fatalf("unable to generate blob encryption key: %v", err)
} }
// Encrypt the blob plaintext using the generated key and nonce := make([]byte, blob.NonceSize)
// target version for this test. _, err = io.ReadFull(rand.Reader, nonce)
ctxt, err := boj.Encrypt(nonce, key, test.encVersion) if err != nil {
if err != test.encErr { t.Fatalf("unable to generate nonce nonce: %v", err)
t.Fatalf("test #%d %s -- unable to encrypt blob: %v", }
i, test.name, err)
} else if test.encErr != nil { // Encrypt the blob plaintext using the generated key and
// If the test expected an encryption failure, we can // target version for this test.
// continue to the next test. ctxt, err := boj.Encrypt(nonce, key, test.encVersion)
continue if err != test.encErr {
} t.Fatalf("unable to encrypt blob: %v", err)
} else if test.encErr != nil {
// Ensure that all encrypted blobs are padded out to the same // If the test expected an encryption failure, we can
// size: 282 bytes for version 0. // continue to the next test.
if len(ctxt) != blob.Size(test.encVersion) { return
t.Fatalf("test #%d %s -- expected blob to have "+ }
"size %d, got %d instead", i, test.name,
blob.Size(test.encVersion), len(ctxt)) // Ensure that all encrypted blobs are padded out to the same
// size: 282 bytes for version 0.
} if len(ctxt) != blob.Size(test.encVersion) {
t.Fatalf("expected blob to have size %d, got %d instead",
// Decrypt the encrypted blob, reconstructing the original blob.Size(test.encVersion), len(ctxt))
// blob plaintext from the decrypted contents. We use the target
// decryption version specified by this test case. }
boj2, err := blob.Decrypt(nonce, key, ctxt, test.decVersion)
if err != test.decErr { // Decrypt the encrypted blob, reconstructing the original
t.Fatalf("test #%d %s -- unable to decrypt blob: %v", // blob plaintext from the decrypted contents. We use the target
i, test.name, err) // decryption version specified by this test case.
} else if test.decErr != nil { boj2, err := blob.Decrypt(nonce, key, ctxt, test.decVersion)
// If the test expected an decryption failure, we can if err != test.decErr {
// continue to the next test. t.Fatalf("unable to decrypt blob: %v", err)
continue } else if test.decErr != nil {
} // If the test expected an decryption failure, we can
// continue to the next test.
// Check that the decrypted blob properly reports whether it has return
// a to-remote output or not. }
if boj2.HasCommitToRemoteOutput() != test.hasCommitToRemote {
t.Fatalf("test #%d %s -- expected blob has_to_remote "+ // Check that the decrypted blob properly reports whether it has
"to be %v, got %v", i, test.name, // a to-remote output or not.
test.hasCommitToRemote, if boj2.HasCommitToRemoteOutput() != test.hasCommitToRemote {
boj2.HasCommitToRemoteOutput()) t.Fatalf("expected blob has_to_remote to be %v, got %v",
} test.hasCommitToRemote, boj2.HasCommitToRemoteOutput())
}
// Check that the original blob plaintext matches the
// one reconstructed from the encrypted blob. // Check that the original blob plaintext matches the
if !reflect.DeepEqual(boj, boj2) { // one reconstructed from the encrypted blob.
t.Fatalf("test #%d %s -- decrypted plaintext does not "+ if !reflect.DeepEqual(boj, boj2) {
"match original, want: %v, got %v", t.Fatalf("decrypted plaintext does not match original, "+
i, test.name, boj, boj2) "want: %v, got %v", boj, boj2)
}
} }
} }