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

Conversation

guggero
Copy link
Member

@guggero guggero commented Jun 6, 2025

This PR introduces a new V2 address that:

  • Allows grouped assets to be sent.
  • Allows the amount to be defined as zero by the recipient, allowing the sender to choose the amount.
  • Can be re-used indefinitely without any privacy downsides.
  • Use a new proof courier type to transfer an encrypted send fragment from the sender to the receiver using a mailbox system (but a more robust mailbox than the previous hashmailbox implementation).

proof/send.go Outdated
// ScriptKeyDerivationUniquePedersen means the script key is derived
// using the address's recipient ID key and a single leaf that contains
// an un-spendable Pedersen commitment key
// (OP_CHECKSIG <NUMS_key + asset_id * G>). This can be used to
Copy link
Member

Choose a reason for hiding this comment

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

Ah ok, so this is just to ensure unique script keys for each unique asset ID in a send fragment, but we still plan on inserting that new asset TLV containing the shared secret hash to make all asset commitments for a given addr unique?

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct. Will add the odd TLV value to make sure identical sends (e.g. same amounts and same asset IDs) will still produce a different on-chain output for better privacy.

// TxProof is the proof of the transaction that contains the asset
// outputs that are being sent. This is used as proof-of work to show
// 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.

proof/courier.go Outdated
Proof: &mboxrpc.SendMessageRequest_TxProof{
TxProof: rpcProof,
},
// TODO(guggero): what value?
Copy link
Member

Choose a reason for hiding this comment

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

Good question...I think will depend on what we end up using as the max block height/expiry value for the server.

Copy link
Member Author

Choose a reason for hiding this comment

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

Went with 210k blocks for now, can perhaps make it configurable later on. And currently we don't delete messages yet. Not sure if we want to add message cleanup (both after user ACK and expiry) in this PR or whether I should create an issue for it.

rpcserver.go Outdated
if err != nil {
return nil, fmt.Errorf("unable to decode "+
"addr: %w", err)
}

// TODO(guggero): Need to add send fragment envelopes to
Copy link
Member

Choose a reason for hiding this comment

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

Can spin out into a sub-issue.

Do the vPSBTs actually need to be updated though? IIUC, we just need to specify the new proof courier semantics.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, I think we should be able to support the vPSBT flow by just adding the TAP address as a new field to the vOutput. Otherwise we'd have to add safety checks to make sure users don't use the vPSBT flow with V2 addresses and then fail at the proof transfer. So adding vPSBT support is probably the easier way.
Added it as a TODO that I'm going to address before re-requesting review.

"service handle: %w", err)
}

err = courier.DeliverFragment(ctx, envelope)
Copy link
Member

Choose a reason for hiding this comment

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

Any reason to not fold this into the loop below where we'll attempt to send out the proofs/fragments in parallel?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. The cardinality is different for fragments vs. proofs. But I made it so that we can always try again to send the same message, so we just try in the same loop. Also makes the whole backoff and re-try logic work seamlessly.

// required for V2 addresses that don't specify an amount (amount of zero).
// If an address does specify an amount, then it cannot be overridden in
// this map.
map<string, uint64> address_amounts = 5;
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, for parsing, what if we allowed tap_addrs to be blank if this is specified, then we only read out address_amounts. This way a user doesn't need to specify an addr twice if it's a v2 addr.

Another alternative would be to upgrade tap_addrs to be a new message type, that can specify the addr, then also a custom amount.

Mainly thinking about how we can streamline the process of a new user potentially unknowingly using a re-useable addr.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point here. I updated the RPC to have two fields, the old tap_addrs and new addresses_with_amounts list, both being mutually exclusive. So we can deprecate the older one eventually.

@guggero guggero force-pushed the group-key-addr-part-2 branch from a5c2fd0 to b482ea3 Compare June 19, 2025 11:48
@guggero guggero changed the title [group key addrs 3/?]: send and receive support for V2 addresses [group key addrs 4/4]: send and receive support for V2 addresses Jun 19, 2025
@guggero guggero changed the title [group key addrs 4/4]: send and receive support for V2 addresses [group key addrs 5/5]: send and receive support for V2 addresses Jun 23, 2025
@guggero guggero force-pushed the group-key-addr-part-2 branch from b482ea3 to 8f8e164 Compare June 23, 2025 14:55
@guggero guggero changed the base branch from group-key-addr to add-mailbox-server June 23, 2025 14:55
@guggero guggero force-pushed the add-mailbox-server branch from 094b31e to 3d1a126 Compare June 24, 2025 08:27
@guggero guggero force-pushed the group-key-addr-part-2 branch from 8f8e164 to 060159b Compare June 24, 2025 08:27
@guggero guggero force-pushed the add-mailbox-server branch 3 times, most recently from dc426c6 to 74953ff Compare June 25, 2025 08:49
@guggero guggero force-pushed the group-key-addr-part-2 branch 2 times, most recently from db7f36c to a3dbdd3 Compare June 26, 2025 09:14
@guggero guggero changed the title [group key addrs 5/5]: send and receive support for V2 addresses [group key addrs 6/6]: send and receive support for V2 addresses Jun 26, 2025
@guggero guggero changed the base branch from add-mailbox-server to pedersen-keys-refactor June 26, 2025 09:17
@guggero guggero force-pushed the pedersen-keys-refactor branch from 5687720 to 46d9159 Compare June 26, 2025 10:18
@guggero guggero force-pushed the group-key-addr-part-2 branch from a3dbdd3 to ad2c782 Compare June 26, 2025 10:19
@guggero guggero force-pushed the pedersen-keys-refactor branch 2 times, most recently from e5fdf7e to eea552e Compare June 27, 2025 10:56
@guggero guggero force-pushed the group-key-addr-part-2 branch 2 times, most recently from ae1aefb to cd9b477 Compare June 30, 2025 15:33
@guggero guggero force-pushed the pedersen-keys-refactor branch from eea552e to 555c711 Compare June 30, 2025 15:34
@guggero guggero changed the base branch from more-group-key-refactors to main July 22, 2025 11:47
@guggero guggero force-pushed the group-key-addr-part-2 branch from feee5ca to a74f887 Compare July 22, 2025 11:47
@ZZiigguurraatt
Copy link

Not sure if you want to address everything in this PR, but do we want to make a "meta" issue like #1446 to track changes to bip-tap.mediawiki that need to be addressed later? Like I feel like there is probably a lot of new stuff related to this PR that is not documented anywhere clearly?

@guggero guggero force-pushed the group-key-addr-part-2 branch from a74f887 to 805d197 Compare July 23, 2025 14:15
@guggero
Copy link
Member Author

guggero commented Jul 23, 2025

Not sure if you want to address everything in this PR, but do we want to make a "meta" issue like #1446 to track changes to bip-tap.mediawiki that need to be addressed later? Like I feel like there is probably a lot of new stuff related to this PR that is not documented anywhere clearly?

Added that here: #293

@guggero guggero force-pushed the group-key-addr-part-2 branch from 805d197 to b8ae454 Compare July 23, 2025 16:07
@guggero
Copy link
Member Author

guggero commented Jul 23, 2025

I've addressed all comments and also added a commit that addresses a user experience issue: For a v2 address with a group key, we don't really want to show an asset ID. So it's zeroed out.

I think that introduced a problem in one of the itests, which I'll fix tomorrow. I'll also add more unit test coverage for the receive flow to the asset custodian.

But even with the above two TODOs, I still think this is ready for another round of review.

guggero added 15 commits July 24, 2025 11:22
To make sure we support future recovery cases, we don't want to delete
any messages for outpoints that haven't been spent.
So we rename and clarify the meaning of the expiry block delta.
No cleanup mechanism is currently implemented anyway, so this just
serves as documentation for when we do add that feature in the future.
This commit adds a new proof courier protocol that's based on the
universe RPC protocol but also adds the auth mailbox capability.

We assume that all our official universes will be upgraded to run an
auth mailbox compatible universe, so we change the protocol for all our
default instances.
This is a preparatory commit that deals with all the database changes
required to allow a single address receive event to have multiple asset
outputs, and with that multiple proofs associated with it.
As a preparation for creating events from wire transactions that aren't
notified by the lnd wallet (and therefore don't come through the
lndclient calls), we make the arguments to GetOrCreateEvents a bit more
explicit.
We refactor the proof handling to allow multiple outputs to be processed
and proofs for them to be fetched.
Adds the new --addrs_with_amounts flag that allows to specify the amount
to send to an address that allows sending user-defined amounts (address
V2).
For v2 addresses we want the address to only show the group key if a
group key is used. The value of the asset ID becomes meaningless for an
address that supports group keys. Unfortunately we can't really _remove_
the value from the address (it's always encoded). But we can set it to
all zeroes so it's more apparent to the user that it's not used.
@guggero guggero force-pushed the group-key-addr-part-2 branch from b8ae454 to 601bb7e Compare July 24, 2025 09:23
@guggero
Copy link
Member Author

guggero commented Jul 24, 2025

Okay, I've addressed all TODOs. And with the litd itest fixed, this should now also be testable on the tapd-main-branch of litd, cc @ZZiigguurraatt.

Copy link
Contributor

@ffranr ffranr left a comment

Choose a reason for hiding this comment

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

I've reviewed until commit multi: don't use lndclient transaction for event

All looks good so far!

Comment on lines +110 to +114
// 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.
uint32 expiry_block_delta = 4;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this value user-configurable? I would have expected it to be a constant defined by the authmailbox service operator.

If this is indeed intended to be set by the client, should we enforce an upper bound on the field? Even if we’re not performing cleanup yet, it still might makes sense to cap the value.

@@ -155,11 +155,14 @@ func TestAddressInsertion(t *testing.T) {

// Make a series of new addrs, then insert them into the DB.
const numAddrs = 5
proofCourierAddr := address.RandProofCourierAddr(t)
addrVersion := test.RandFlip(address.V0, address.V1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we include V2 in this random selection? Other similar statements below.

Could potentially gain V2 address test coverage.

Comment on lines +1155 to +1156
var assetID asset.ID
copy(assetID[:], dbOutput.AssetID)
Copy link
Contributor

Choose a reason for hiding this comment

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

could use asset.NewIDFromBytes here

Comment on lines +441 to +442
// This code path is only for V0 and V1
// addresses, which only have a single output.
Copy link
Contributor

Choose a reason for hiding this comment

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

sanity check the addr version here and below in mapToTapAddr?

Comment on lines +3582 to +3601
// Ensure all addrs are of the same asset specifier, as the
// wallet only supports funding for one asset or group at a
// time.
//
// TODO(guggero): Support creating multiple virtual packets, one
// for each asset ID when the user wants to send multiple asset
// IDs at the same time without going through the PSBT flow.
firstSpec := asset.NewSpecifierOptionalGroupPubKey(
tapAddrs[0].AssetID, tapAddrs[0].GroupKey,
)
if idx > 0 {
spec := asset.NewSpecifierOptionalGroupPubKey(
tapAddrs[idx].AssetID, tapAddrs[idx].GroupKey,
)
if spec != firstSpec {
return nil, fmt.Errorf("all addrs must be of "+
"the same asset ID or group key %v",
firstSpec)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

hmm the equality check may not work correctly if the user specifies an asset group for one of the addresses and asset IDs from that group for the others. But probably not a big deal.

Comment on lines +32 to +37
const (
// DefaultSendFragmentExpiryDelta is the default number of blocks that
// we expect a send fragment to be valid for after its claimed outpoint
// has been spent. This is roughly equivalent to 90 days.
DefaultSendFragmentExpiryDelta = 12_960
)
Copy link
Contributor

Choose a reason for hiding this comment

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

👍, why let the user specify, we can just use this const everywhere perhaps

}

return fundedVPkt, nil
}

// createSendFragments creates the send fragments for the given funded virtual
Copy link
Contributor

Choose a reason for hiding this comment

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

this creates the send manifests? so maybe we should rename this function and modify the doc comment?

"for output %d: %w", outIdx, err)
}

if addr.Version < address.V2 {
Copy link
Contributor

Choose a reason for hiding this comment

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

if addr.Version != address.V2 { here? so we can be explicit about V3, but V3 shouldn't get to this point anyway, maybe we'll forget to update this part of the code or something

Maybe we should add a method to address.Tap. Something like: SupportsSendManifest() bool? So we leak the version switch less.

@@ -252,6 +252,12 @@ func DescribeAddrs(addrs []*address.Tap) (*FundingDescriptor, error) {
firstAddr.AssetID, firstAddr.GroupKey,
)

if firstAddr.Version == address.V2 && firstAddr.GroupKey != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of if firstAddr.Version == address.V2 here maybe a method like firstAddr.SupportsGroupKey

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

[feature]: add support for re-useable group key based addresses [feature]: support of passing arbitary amount to a taproot address
6 participants