Skip to content

[group key addrs 7/7]: send and receive support for V2 addresses #1587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 139 additions & 7 deletions proof/courier.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import (
"github.com/lightninglabs/lightning-node-connect/hashmailrpc"
"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/internal/ecies"
"github.com/lightninglabs/taproot-assets/rpcutils"
mboxrpc "github.com/lightninglabs/taproot-assets/taprpc/authmailboxrpc"
unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
Expand Down Expand Up @@ -67,7 +69,8 @@ type CourierHarness interface {
type Courier interface {
// DeliverProof attempts to delivery a proof to the receiver, using the
// information in the Addr type.
DeliverProof(context.Context, Recipient, *AnnotatedProof) error
DeliverProof(context.Context, Recipient, *AnnotatedProof,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should create a distinct interface? The addr fragments are proofs, but it may be the case that the same Courier that can handle proofs, can't also handle the new addr fragments.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I think I initially misunderstood your comment here #1587 (comment) and interpreted it as also using the same method. But can take things apart again, no problem.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, actually, this would make things a bit more complicated to implement. With the send fragment being delivered in the same method as the actual proof, we can re-use the same backoff and re-try logic for the fragment as we do for the proof itself. This also makes it sort of an atomic operation for the caller, so they know whether the status for an output can be completed.

*SendManifest) error

// ReceiveProof attempts to obtain a proof as identified by the passed
// locator from the source encapsulated within the specified address.
Expand Down Expand Up @@ -890,8 +893,15 @@ func (h *HashMailCourier) ensureConnect(ctx context.Context) error {
// information in the Addr type.
//
// TODO(roasbeef): other delivery context as type param?
func (h *HashMailCourier) DeliverProof(ctx context.Context,
recipient Recipient, proof *AnnotatedProof) error {
func (h *HashMailCourier) DeliverProof(ctx context.Context, recipient Recipient,
proof *AnnotatedProof, sendFragment *SendManifest) error {

// Send fragment manifests are only supported by the auth mailbox/
// universe RPC courier.
if sendFragment != nil {
return fmt.Errorf("send fragment not supported by hashmail " +
"courier")
}

// Compute the stream IDs for the sender and receiver. Note that these
// stream IDs are derived from the recipient's script key only. Which
Expand Down Expand Up @@ -1207,6 +1217,10 @@ type UniverseRpcCourier struct {
// the universe RPC server.
client unirpc.UniverseClient

// mboxClient is the RPC client that the courier will use to interact
// with the auth mailbox RPC server.
mboxClient mboxrpc.MailboxClient

// backoffHandle is a handle to the backoff procedure used in proof
// delivery.
backoffHandle *BackoffHandler
Expand Down Expand Up @@ -1291,19 +1305,123 @@ func (c *UniverseRpcCourier) ensureConnect(ctx context.Context) error {
}

c.client = unirpc.NewUniverseClient(conn)
c.mboxClient = mboxrpc.NewMailboxClient(conn)
c.rawConn = conn

// Make sure we initiate the connection. The GetInfo RPC method is in
// the base macaroon white list, so it doesn't require any
// authentication, independent of the universe's configuration.
_, err = c.client.Info(ctx, &unirpc.InfoRequest{})
if err != nil {
// If we fail to connect, we'll close the connection and return
// the error.
if closeErr := c.rawConn.Close(); closeErr != nil {
log.Errorf("Unable to close RPC courier connection: %v",
closeErr)
}

return err
return fmt.Errorf("unable to connect to universe RPC courier "+
"service: %w", err)
}

// If this is the "old" universe RPC courier type, then we'll skip the
// additional auth mailbox RPC check, as it is not required for the
// old courier type.
if c.addr.Scheme != AuthMailboxUniRpcCourierType {
return nil
}

// If this is an auth mailbox type connection (which is a normal
// universe RPC connection plus an auth mailbox RPC for transmitting the
// send fragments), we'll also want to make sure the server supports
// that additional auth mailbox RPC.
_, err = c.mboxClient.MailboxInfo(ctx, &mboxrpc.MailboxInfoRequest{})
if err != nil {
// If we fail to connect, we'll close the connection and return
// the error.
if closeErr := c.rawConn.Close(); closeErr != nil {
log.Errorf("Unable to close RPC courier connection: %v",
closeErr)
}

return fmt.Errorf("unable to connect to auth mailbox RPC "+
"courier service: %w", err)
}

return nil
}

// deliverFragment attempts to deliver a send fragment to the recipient
// using the auth mailbox protocol. If the underlying courier doesn't
// support send fragments, then this method will return an error.
func (c *UniverseRpcCourier) deliverFragment(ctx context.Context,
fragment *SendManifest) error {

if c.addr.Scheme != AuthMailboxUniRpcCourierType {
return fmt.Errorf("send fragment not supported by standalone "+
"universe RPC courier, must use '%s'",
AuthMailboxUniRpcCourierType)
}

// We generate an ephemeral key pair that we'll use to create the shared
// secret that to encrypt the send fragment with, before sending it to
// the receiver. The public key of the pair will be part of the
// encrypted payload, so we can throw away the private key after doing
// the ECDH operation below to arrive at the shared secret.
privKey, err := btcec.NewPrivateKey()
if err != nil {
return fmt.Errorf("unable to generate ephemeral key: %w", err)
}
senderEphemeralKey := privKey.PubKey()
senderKeyBytes := senderEphemeralKey.SerializeCompressed()

sharedSecret, err := ecies.ECDH(privKey, &fragment.Receiver)
if err != nil {
return fmt.Errorf("unable to derive shared key: %w", err)
}

log.Infof("Transferring send fragment to receiver with claimed "+
"outpoint %v", fragment.Fragment.OutPoint)

msg, err := fn.Encode(&fragment.Fragment)
if err != nil {
return fmt.Errorf("unable to encode send fragment: %w", err)
}

encryptedPayload, err := ecies.EncryptSha256ChaCha20Poly1305(
sharedSecret, msg, senderKeyBytes,
)
if err != nil {
return fmt.Errorf("unable to encrypt send fragment: %w", err)
}

rpcProof, err := MarshalTxProof(fragment.TxProof)
if err != nil {
return fmt.Errorf("unable to marshal tx proof: %w", err)
}

resp, err := c.mboxClient.SendMessage(ctx, &mboxrpc.SendMessageRequest{
ReceiverId: fragment.Receiver.SerializeCompressed(),
EncryptedPayload: encryptedPayload,
Proof: &mboxrpc.SendMessageRequest_TxProof{
TxProof: rpcProof,
},
ExpiryBlockDelta: fragment.ExpiryBlockDelta,
})
if err != nil {
return fmt.Errorf("unable to send message: %w", err)
}

log.Infof("Successfully sent send fragment to server, ID=%d",
resp.MessageId)

return nil
}

// DeliverProof attempts to delivery a proof file to the receiver.
func (c *UniverseRpcCourier) DeliverProof(ctx context.Context,
recipient Recipient, annotatedProof *AnnotatedProof) error {
recipient Recipient, annotatedProof *AnnotatedProof,
sendFragment *SendManifest) error {

// Decode annotated proof into proof file.
proofFile := &File{}
Expand Down Expand Up @@ -1400,6 +1518,18 @@ func (c *UniverseRpcCourier) DeliverProof(ctx context.Context,
)
defer subCtxCancel()

// Sending a message to the courier is idempotent, so we
// can safely retry it every time before we insert the
// proofs.
if sendFragment != nil {
err = c.deliverFragment(subCtx, sendFragment)
if err != nil {
return fmt.Errorf("error delivering "+
"send fragment to courier "+
"service: %w", err)
}
}

assetProof := unirpc.AssetProof{
Key: &universeKey,
AssetLeaf: &assetLeaf,
Expand Down Expand Up @@ -1475,6 +1605,9 @@ func (c *UniverseRpcCourier) ReceiveProof(ctx context.Context,
)
defer subCtxCancel()

log.Tracef("Querying for proof with script key %x",
loc.ScriptKey.SerializeCompressed())

resp, err := c.client.QueryProof(subCtx, &universeKey)
if err != nil {
return fmt.Errorf("error retrieving proof "+
Expand Down Expand Up @@ -1729,8 +1862,7 @@ func CheckUniverseRpcCourierConnection(ctx context.Context,
ctxt, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
courier, err := NewUniverseRpcCourier(
ctxt, &UniverseRpcCourierCfg{}, nil, nil, courierURL,
false,
ctxt, &UniverseRpcCourierCfg{}, nil, nil, courierURL, false,
)
if err != nil {
return fmt.Errorf("unable to test connection proof courier "+
Expand Down
2 changes: 1 addition & 1 deletion proof/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ func (m *MockProofCourier) Stop() error {
// DeliverProof attempts to delivery a proof to the receiver, using the
// information in the Addr type.
func (m *MockProofCourier) DeliverProof(_ context.Context,
_ Recipient, proof *AnnotatedProof) error {
_ Recipient, proof *AnnotatedProof, _ *SendManifest) error {

m.Lock()
defer m.Unlock()
Expand Down
7 changes: 7 additions & 0 deletions proof/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ type SendManifest struct {
// to the auth mailbox server.
TxProof TxProof
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we're duplicating the block header + height here across TxProof and SendFragment. Or is the idea that the encoding is smart enough to only encode once and copy the fields over as needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The envelope itself isn't encoded, only the SendFragment within. So the TxProof here is is what's required to send the fragment to the server.
Perhaps "envelope" isn't the best analogy here, as that's also sent along with a letter/message. Going to rename this to SendManifest instead, perhaps that makes more sense.


// ExpiryBlockDelta is a user-defined expiry block height delta for the
// message. Once the outpoint claimed by the proof provided by this
// message has been spent, then after this number of blocks after the
// spending transaction height the message can be considered expired and
// may be deleted.
ExpiryBlockDelta uint32

// Receiver is the receiver's public key of the asset outputs, used
// to decrypt the send fragment. This is the internal key of the address
// that was used to send the assets.
Expand Down
Loading