Skip to content

Commit 33a37af

Browse files
committed
tappsbt+tapsend: add address to vOutput
For V2 addresses we need to know certain information from the address when creating the final send fragment. Because we might be transporting a vPSBT over RPC boundaries between funding and finalizing, we need to also transport the address between those steps. The easiest way to do that is to encode the address into the virtual output directly.
1 parent 92b41b6 commit 33a37af

File tree

8 files changed

+329
-195
lines changed

8 files changed

+329
-195
lines changed

tappsbt/create.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ func FromAddresses(receiverAddrs []*address.Tap,
8080
AnchorOutputInternalKey: &addr.InternalKey,
8181
AnchorOutputTapscriptSibling: addr.TapscriptSibling,
8282
ProofDeliveryAddress: &addr.ProofCourierAddr,
83+
Address: addr,
8384
})
8485
}
8586

tappsbt/decode.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ func NewFromPsbt(packet *psbt.Packet) (*VPacket, error) {
116116
vOut := &VOutput{}
117117
err = vOut.decode(
118118
packet.Outputs[idx], packet.UnsignedTx.TxOut[idx],
119+
chainParams,
119120
)
120121
if err != nil {
121122
return nil, fmt.Errorf("error decoding virtual output "+
@@ -212,7 +213,9 @@ func (i *VInput) decode(pIn psbt.PInput) error {
212213
}
213214

214215
// decode decodes the given POutput and wire.TxOut into the current VOutput.
215-
func (o *VOutput) decode(pOut psbt.POutput, txOut *wire.TxOut) error {
216+
func (o *VOutput) decode(pOut psbt.POutput, txOut *wire.TxOut,
217+
chainParams *address.ChainParams) error {
218+
216219
o.Amount = uint64(txOut.Value)
217220

218221
if len(txOut.PkScript) != schnorr.PubKeyBytesLen+2 {
@@ -305,6 +308,9 @@ func (o *VOutput) decode(pOut psbt.POutput, txOut *wire.TxOut) error {
305308
}, {
306309
key: PsbtKeyTypeOutputTapAltLeaves,
307310
decoder: altLeavesDecoder(&o.AltLeaves),
311+
}, {
312+
key: PsbtKeyTypeOutputTapAddress,
313+
decoder: addressDecoder(&o.Address, chainParams),
308314
}}
309315

310316
for idx := range mapping {
@@ -524,3 +530,28 @@ func urlDecoder(u **url.URL) decoderFunc {
524530
return tlvDecoder(*u, asset.UrlDecoder)(key, byteVal)
525531
}
526532
}
533+
534+
// addressDecoder returns a decoder function that can handle nil Taproot
535+
// addresses.
536+
func addressDecoder(a **address.Tap,
537+
chainParams *address.ChainParams) decoderFunc {
538+
539+
return func(key, byteVal []byte) error {
540+
if len(byteVal) == 0 {
541+
return nil
542+
}
543+
544+
addr := address.Tap{
545+
ChainParams: chainParams,
546+
}
547+
err := addr.Decode(bytes.NewReader(byteVal))
548+
if err != nil {
549+
return fmt.Errorf("error decoding Taproot address: %w",
550+
err)
551+
}
552+
553+
*a = &addr
554+
555+
return nil
556+
}
557+
}

tappsbt/decode_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,14 +193,20 @@ func TestEncodingDecoding(t *testing.T) {
193193
assertEqualPackets(t, pkg, decoded)
194194
}
195195

196+
proofCourierAddr := address.RandProofCourierAddr(t)
196197
testCases := []testCase{{
197198
name: "minimal packet",
198199
pkg: func(t *testing.T) *VPacket {
199-
proofCourierAddr := address.RandProofCourierAddr(t)
200200
addr, _, _ := address.RandAddr(
201201
t, testParams, proofCourierAddr,
202202
)
203203

204+
// The address' genesis information isn't encoded, so it
205+
// will be the empty genesis after decoding. To make
206+
// sure we can directly compare before and after, we
207+
// also attach an empty genesis to the address.
208+
addr.AttachGenesis(asset.Genesis{})
209+
204210
pkg, err := FromAddresses([]*address.Tap{addr.Tap}, 1)
205211
require.NoError(t, err)
206212
pkg.Outputs = append(pkg.Outputs, &VOutput{

tappsbt/encode.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/btcsuite/btcd/btcutil/psbt"
1313
"github.com/btcsuite/btcd/txscript"
1414
"github.com/btcsuite/btcd/wire"
15+
"github.com/lightninglabs/taproot-assets/address"
1516
"github.com/lightninglabs/taproot-assets/asset"
1617
"github.com/lightninglabs/taproot-assets/commitment"
1718
"github.com/lightninglabs/taproot-assets/fn"
@@ -301,6 +302,9 @@ func (o *VOutput) encode(coinType uint32) (psbt.POutput, *wire.TxOut, error) {
301302
}, {
302303
key: PsbtKeyTypeOutputTapAltLeaves,
303304
encoder: altLeavesEncoder(o.AltLeaves),
305+
}, {
306+
key: PsbtKeyTypeOutputTapAddress,
307+
encoder: addressEncoder(o.Address),
304308
},
305309
}
306310

@@ -527,3 +531,26 @@ func urlEncoder(val *url.URL) encoderFunc {
527531
}, nil
528532
}
529533
}
534+
535+
// addressEncoder returns a function that encodes the given Taproot Asset
536+
// address as a custom PSBT field. If the address is nil, it returns nil.
537+
func addressEncoder(val *address.Tap) encoderFunc {
538+
return func(key []byte) ([]*customPsbtField, error) {
539+
if val == nil {
540+
return nil, nil
541+
}
542+
543+
var b bytes.Buffer
544+
if err := val.Encode(&b); err != nil {
545+
return nil, fmt.Errorf("error encoding address: %w",
546+
err)
547+
}
548+
549+
return []*customPsbtField{
550+
{
551+
Key: fn.CopySlice(key),
552+
Value: b.Bytes(),
553+
},
554+
}, nil
555+
}
556+
}

tappsbt/interface.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ var (
5959
PsbtKeyTypeOutputTapAssetLockTime = []byte{0x7c}
6060
PsbtKeyTypeOutputTapAssetRelativeLockTime = []byte{0x7d}
6161
PsbtKeyTypeOutputTapAltLeaves = []byte{0x7e}
62+
PsbtKeyTypeOutputTapAddress = []byte{0x7f}
6263
)
6364

6465
// The following keys are used as custom fields on the BTC level anchor
@@ -597,6 +598,11 @@ type VOutput struct {
597598
// data-carrying leaves are used for a purpose distinct from
598599
// representing individual Taproot Assets.
599600
AltLeaves []asset.AltLeaf[asset.Asset]
601+
602+
// Address is the Taproot Asset address that was used to create this
603+
// output. This is only present for non-interactive outputs that were
604+
// created from a Taproot Asset address.
605+
Address *address.Tap
600606
}
601607

602608
// Copy creates a deep copy of the VOutput.
@@ -625,6 +631,7 @@ func (o *VOutput) Copy() *VOutput {
625631
ProofDeliveryAddress: o.ProofDeliveryAddress,
626632
ProofSuffix: o.ProofSuffix,
627633
AltLeaves: asset.CopyAltLeaves(o.AltLeaves),
634+
Address: o.Address,
628635
}
629636
}
630637

tappsbt/mock.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,17 @@ func RandPacket(t testing.TB, setVersion, altLeaves bool) *VPacket {
116116
courierAddress, err := url.Parse("https://example.com")
117117
require.NoError(t, err)
118118

119+
courierURL := address.RandProofCourierAddr(t)
120+
addr1, _, _ := address.RandAddr(t, testParams, courierURL)
121+
addr2, _, _ := address.RandAddr(t, testParams, courierURL)
122+
123+
// The address' genesis information isn't encoded, so it will be the
124+
// empty genesis after decoding. To make sure we can directly compare
125+
// before and after, we also attach an empty genesis to the two
126+
// addresses.
127+
addr1.AttachGenesis(asset.Genesis{})
128+
addr2.AttachGenesis(asset.Genesis{})
129+
119130
randVInput := VInput{
120131
PrevID: asset.PrevID{
121132
OutPoint: op,
@@ -154,6 +165,7 @@ func RandPacket(t testing.TB, setVersion, altLeaves bool) *VPacket {
154165
ProofSuffix: &inputProof,
155166
RelativeLockTime: 345,
156167
LockTime: 456,
168+
Address: addr1.Tap,
157169
}
158170

159171
randVOutput2 := VOutput{
@@ -170,6 +182,7 @@ func RandPacket(t testing.TB, setVersion, altLeaves bool) *VPacket {
170182
Asset: testOutputAsset,
171183
ScriptKey: testOutputAsset.ScriptKey,
172184
AnchorOutputTapscriptSibling: &testPreimage2,
185+
Address: addr2.Tap,
173186
}
174187

175188
if altLeaves {
@@ -614,6 +627,11 @@ func NewTestFromVOutput(t testing.TB, v *VOutput,
614627
)
615628
}
616629
}
630+
631+
if v.Address != nil {
632+
vo.Address = address.NewTestFromAddress(t, v.Address)
633+
}
634+
617635
return vo
618636
}
619637

@@ -640,6 +658,7 @@ type TestVOutput struct {
640658
RelativeLockTime uint64 `json:"relative_lock_time"`
641659
LockTime uint64 `json:"lock_time"`
642660
AltLeaves []*asset.TestAsset `json:"alt_leaves"`
661+
Address *address.TestAddress `json:"address"`
643662
}
644663

645664
func (to *TestVOutput) ToVOutput(t testing.TB) *VOutput {
@@ -741,5 +760,9 @@ func (to *TestVOutput) ToVOutput(t testing.TB) *VOutput {
741760
}
742761
}
743762

763+
if to.Address != nil {
764+
v.Address = to.Address.ToAddress(t)
765+
}
766+
744767
return v
745768
}

0 commit comments

Comments
 (0)