Merge pull request #2122 from cfromknecht/blob-variable-length-addr
[watchtower/blob] variable length sweep addr
This commit is contained in:
commit
fb4d3909a1
@ -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)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user