Skip to content

Commit 842d367

Browse files
authored
Merge pull request #1103 from lightninglabs/fix-split-locktime
commitment: splits shouldn't inherit locktime from inputs
2 parents 8eb6c53 + 881ecaf commit 842d367

File tree

7 files changed

+101
-16
lines changed

7 files changed

+101
-16
lines changed

asset/asset.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1831,6 +1831,26 @@ func (a *Asset) Copy() *Asset {
18311831
return &assetCopy
18321832
}
18331833

1834+
// CopySpendTemplate is similar to Copy, but should be used when wanting to
1835+
// spend an input asset in a new transaction. Compared to Copy, this method
1836+
// also blanks out some other fields that shouldn't always be carried along for
1837+
// a dependent spend.
1838+
func (a *Asset) CopySpendTemplate() *Asset {
1839+
assetCopy := a.Copy()
1840+
1841+
// We nil out the split commitment root, as we don't need to carry that
1842+
// into the next spend.
1843+
assetCopy.SplitCommitmentRoot = nil
1844+
1845+
// We'll also make sure to clear out the lock time and relative lock
1846+
// time from the input. The input at this point is already valid, so we
1847+
// don't need to inherit the time lock encumbrance.
1848+
assetCopy.RelativeLockTime = 0
1849+
assetCopy.LockTime = 0
1850+
1851+
return assetCopy
1852+
}
1853+
18341854
// DeepEqual returns true if this asset is equal with the given asset.
18351855
func (a *Asset) DeepEqual(o *Asset) bool {
18361856
return a.deepEqual(false, o)

asset/asset_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,3 +1194,25 @@ func TestNewAssetWithCustomVersion(t *testing.T) {
11941194

11951195
require.Equal(t, int(assetCustomVersion.Version), newVersion)
11961196
}
1197+
1198+
// TestCopySpendTemplate tests that the spend template is copied correctly.
1199+
func TestCopySpendTemplate(t *testing.T) {
1200+
newAsset := RandAsset(t, Normal)
1201+
newAsset.SplitCommitmentRoot = mssmt.NewComputedNode(hashBytes1, 1337)
1202+
newAsset.RelativeLockTime = 1
1203+
newAsset.LockTime = 2
1204+
1205+
// The template should have the relevant set of fields blanked.
1206+
spendTemplate := newAsset.CopySpendTemplate()
1207+
require.Zero(t, spendTemplate.SplitCommitmentRoot)
1208+
require.Zero(t, spendTemplate.RelativeLockTime)
1209+
require.Zero(t, spendTemplate.LockTime)
1210+
1211+
// If blank these fields of the OG asset, then things should be
1212+
// identical.
1213+
newAsset.SplitCommitmentRoot = nil
1214+
newAsset.RelativeLockTime = 0
1215+
newAsset.LockTime = 0
1216+
1217+
require.True(t, newAsset.DeepEqual(spendTemplate))
1218+
}

commitment/commitment_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,43 @@ func TestSplitCommitment(t *testing.T) {
656656
},
657657
err: nil,
658658
},
659+
{
660+
name: "single input split commitment lock time input",
661+
f: func() (*asset.Asset, *SplitLocator,
662+
[]*SplitLocator) {
663+
664+
input := randAsset(
665+
t, genesisNormal, groupKeyNormal,
666+
)
667+
input.Amount = 3
668+
input.RelativeLockTime = 1
669+
input.LockTime = 1
670+
671+
root := &SplitLocator{
672+
OutputIndex: 0,
673+
AssetID: genesisNormal.ID(),
674+
ScriptKey: asset.ToSerialized(
675+
input.ScriptKey.PubKey,
676+
),
677+
Amount: 1,
678+
}
679+
external := []*SplitLocator{{
680+
OutputIndex: 1,
681+
AssetID: genesisNormal.ID(),
682+
ScriptKey: asset.RandSerializedKey(t),
683+
Amount: 1,
684+
}, {
685+
686+
OutputIndex: 2,
687+
AssetID: genesisNormal.ID(),
688+
ScriptKey: asset.RandSerializedKey(t),
689+
Amount: 1,
690+
}}
691+
692+
return input, root, external
693+
},
694+
err: nil,
695+
},
659696
{
660697
name: "no external splits",
661698
f: func() (*asset.Asset, *SplitLocator, []*SplitLocator) {
@@ -871,6 +908,11 @@ func TestSplitCommitment(t *testing.T) {
871908
require.Contains(t, split.SplitAssets, *l)
872909
splitAsset := split.SplitAssets[*l]
873910

911+
// Make sure that the splits don't inherit lock
912+
// time information from the root asset.
913+
require.Zero(t, splitAsset.LockTime)
914+
require.Zero(t, splitAsset.RelativeLockTime)
915+
874916
// If this is a leaf split, then we need to
875917
// ensure that the prev ID is zero.
876918
if splitAsset.SplitCommitmentRoot == nil {

commitment/split.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ func NewSplitCommitment(ctx context.Context, inputs []SplitCommitmentInput,
194194
remainingAmount := totalInputAmount
195195
rootIdx := len(locators) - 1
196196
addAssetSplit := func(locator *SplitLocator) error {
197-
assetSplit := inputs[0].Asset.Copy()
197+
assetSplit := inputs[0].Asset.CopySpendTemplate()
198198
assetSplit.Amount = locator.Amount
199199
assetSplit.Version = locator.AssetVersion
200200

@@ -208,7 +208,6 @@ func NewSplitCommitment(ctx context.Context, inputs []SplitCommitmentInput,
208208
TxWitness: nil,
209209
SplitCommitment: nil,
210210
}}
211-
assetSplit.SplitCommitmentRoot = nil
212211

213212
splitAssets[*locator] = &SplitAsset{
214213
Asset: *assetSplit,

proof/courier.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1473,7 +1473,11 @@ func (c *UniverseRpcCourier) publishSubscriberEvent(event fn.Event) {
14731473

14741474
// Close closes the courier's connection to the remote gRPC service.
14751475
func (c *UniverseRpcCourier) Close() error {
1476-
return c.rawConn.Close()
1476+
if c.rawConn != nil {
1477+
return c.rawConn.Close()
1478+
}
1479+
1480+
return nil
14771481
}
14781482

14791483
// A compile-time assertion to ensure the UniverseRpcCourier meets the

proof/verifier.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -375,18 +375,20 @@ func CreateOwnershipProofAsset(ownedAsset *asset.Asset,
375375
),
376376
}
377377

378-
outputAsset := ownedAsset.Copy()
379-
outputAsset.ScriptKey = address.GenChallengeNUMS(challengeBytes)
380-
outputAsset.PrevWitnesses = []asset.Witness{{
381-
PrevID: &prevId,
382-
}}
383-
384378
// The ownership proof needs to be a 1-in-1-out transaction. So it will
385379
// definitely not have a split commitment. Keeping the split commitment
386380
// of the copied owned asset would lead to an issue with the
387381
// non-inflation check we have in the VM that takes the split commitment
388-
// root sum as the expected total output amount.
389-
outputAsset.SplitCommitmentRoot = nil
382+
// root sum as the expected total output amount. We also clear any time
383+
// locks, as they don't apply to the ownership proof.
384+
//
385+
// This is handled by CopySpendTemplate.
386+
outputAsset := ownedAsset.CopySpendTemplate()
387+
388+
outputAsset.ScriptKey = address.GenChallengeNUMS(challengeBytes)
389+
outputAsset.PrevWitnesses = []asset.Witness{{
390+
PrevID: &prevId,
391+
}}
390392

391393
return prevId, outputAsset
392394
}

tapfreighter/wallet.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -309,11 +309,7 @@ func createPassivePacket(params *address.ChainParams, passiveAsset *asset.Asset,
309309
}
310310

311311
// Specify virtual output.
312-
outputAsset := passiveAsset.Copy()
313-
314-
// Clear the split commitment root, as we'll be transferring the whole
315-
// asset.
316-
outputAsset.SplitCommitmentRoot = nil
312+
outputAsset := passiveAsset.CopySpendTemplate()
317313

318314
// Clear the output asset witness data. We'll be creating a new witness.
319315
outputAsset.PrevWitnesses = []asset.Witness{{

0 commit comments

Comments
 (0)