88
99 "github.com/btcsuite/btcd/btcec/v2/schnorr"
1010 "github.com/btcsuite/btcd/btcutil/psbt"
11+ "github.com/btcsuite/btcd/txscript"
1112 "github.com/btcsuite/btcwallet/waddrmgr"
1213 tap "github.com/lightninglabs/taproot-assets"
1314 "github.com/lightninglabs/taproot-assets/address"
@@ -18,8 +19,11 @@ import (
1819 "github.com/lightninglabs/taproot-assets/taprpc"
1920 wrpc "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc"
2021 "github.com/lightninglabs/taproot-assets/taprpc/mintrpc"
22+ "github.com/lightninglabs/taproot-assets/taprpc/tapdevrpc"
23+ "github.com/lightninglabs/taproot-assets/tapscript"
2124 "github.com/lightningnetwork/lnd/input"
2225 "github.com/lightningnetwork/lnd/keychain"
26+ "github.com/lightningnetwork/lnd/lntest/wait"
2327 "github.com/stretchr/testify/require"
2428)
2529
@@ -1265,6 +1269,356 @@ func testMultiInputPsbtSingleAssetID(t *harnessTest) {
12651269 require .Len (t .t , secondaryNodeAssets .Assets , 0 )
12661270}
12671271
1272+ // testPsbtSighashNone tests that the SIGHASH_NONE flag of vPSBTs is properly
1273+ // accounted for in the generated signatures,
1274+ func testPsbtSighashNone (t * harnessTest ) {
1275+ // First, we'll make a normal asset with enough units to allow us to
1276+ // send it around a few times.
1277+ rpcAssets := MintAssetsConfirmBatch (
1278+ t .t , t .lndHarness .Miner .Client , t .tapd ,
1279+ []* mintrpc.MintAssetRequest {issuableAssets [0 ]},
1280+ )
1281+
1282+ mintedAsset := rpcAssets [0 ]
1283+ genInfo := rpcAssets [0 ].AssetGenesis
1284+
1285+ ctxb := context .Background ()
1286+ ctxt , cancel := context .WithTimeout (ctxb , defaultWaitTimeout )
1287+ defer cancel ()
1288+
1289+ // Now that we have the asset created, we'll make a new node that'll
1290+ // serve as the node which'll receive the assets.
1291+ secondTapd := setupTapdHarness (
1292+ t .t , t , t .lndHarness .Bob , t .universeServer ,
1293+ )
1294+ defer func () {
1295+ require .NoError (t .t , secondTapd .stop (! * noDelete ))
1296+ }()
1297+
1298+ var (
1299+ alice = t .tapd
1300+ bob = secondTapd
1301+ numUnits = uint64 (500 )
1302+ )
1303+
1304+ // We need to derive two keys, one for the new script key and one for
1305+ // the internal key.
1306+ bobScriptKey , bobInternalKey := deriveKeys (t .t , bob )
1307+
1308+ // Now we create a script tree consisting of two simple scripts.
1309+ preImage := []byte ("hash locks are cool" )
1310+ leaf1 := test .ScriptHashLock (t .t , preImage )
1311+ leaf2 := test .ScriptSchnorrSig (t .t , bobScriptKey .RawKey .PubKey )
1312+ leaf1Hash := leaf1 .TapHash ()
1313+ leaf2Hash := leaf2 .TapHash ()
1314+ tapScript := input .TapscriptPartialReveal (
1315+ bobScriptKey .RawKey .PubKey , leaf2 , leaf1Hash [:],
1316+ )
1317+ rootHash := tapScript .ControlBlock .RootHash (leaf2 .Script )
1318+
1319+ sendToTapscriptAddr (
1320+ ctxt , t , alice , bob , numUnits , genInfo , mintedAsset ,
1321+ bobScriptKey , bobInternalKey , tapScript , rootHash ,
1322+ )
1323+
1324+ // Now try to send back those assets using the PSBT flow.
1325+ aliceAddr , err := alice .NewAddr (ctxb , & taprpc.NewAddrRequest {
1326+ AssetId : genInfo .AssetId ,
1327+ Amt : numUnits / 5 ,
1328+ AssetVersion : mintedAsset .Version ,
1329+ })
1330+ require .NoError (t .t , err )
1331+ AssertAddrCreated (t .t , alice , rpcAssets [0 ], aliceAddr )
1332+
1333+ fundResp := fundAddressSendPacket (t , bob , aliceAddr )
1334+ t .Logf ("Funded PSBT: %v" ,
1335+ base64 .StdEncoding .EncodeToString (fundResp .FundedPsbt ))
1336+
1337+ fundedPacket , err := tappsbt .NewFromRawBytes (
1338+ bytes .NewReader (fundResp .FundedPsbt ), false ,
1339+ )
1340+ require .NoError (t .t , err )
1341+
1342+ // We can now ask the wallet to sign the script path, since we only need
1343+ // a signature.
1344+ controlBlockBytes , err := tapScript .ControlBlock .ToBytes ()
1345+ require .NoError (t .t , err )
1346+ fundedPacket .Inputs [0 ].TaprootMerkleRoot = rootHash [:]
1347+ fundedPacket .Inputs [0 ].TaprootLeafScript = []* psbt.TaprootTapLeafScript {
1348+ {
1349+ ControlBlock : controlBlockBytes ,
1350+ Script : leaf2 .Script ,
1351+ LeafVersion : leaf2 .LeafVersion ,
1352+ },
1353+ }
1354+ fundedPacket .Inputs [0 ].TaprootBip32Derivation [0 ].LeafHashes = [][]byte {
1355+ leaf2Hash [:],
1356+ }
1357+
1358+ // Before signing, we set the sighash of the first input to SIGHASH_NONE
1359+ // which allows us to alter the outputs of the PSBT after the signature
1360+ // has been generated.
1361+ fundedPacket .Inputs [0 ].SighashType = txscript .SigHashNone
1362+
1363+ var b bytes.Buffer
1364+ err = fundedPacket .Serialize (& b )
1365+ require .NoError (t .t , err )
1366+
1367+ signedResp , err := bob .SignVirtualPsbt (
1368+ ctxb , & wrpc.SignVirtualPsbtRequest {
1369+ FundedPsbt : b .Bytes (),
1370+ },
1371+ )
1372+ require .NoError (t .t , err )
1373+ require .Contains (t .t , signedResp .SignedInputs , uint32 (0 ))
1374+
1375+ // Now we deserialize the signed packet again in order to edit it
1376+ // and then anchor it.
1377+ signedPacket , err := tappsbt .NewFromRawBytes (
1378+ bytes .NewReader (signedResp .SignedPsbt ), false ,
1379+ )
1380+ require .NoError (t .t , err )
1381+
1382+ // Edit the already signed PSBT and change the output amounts. This
1383+ // should be ok as we used SIGHASH_NONE for the input's signature.
1384+ signedPacket .Outputs [0 ].Amount -= 1
1385+ signedPacket .Outputs [1 ].Amount += 1
1386+
1387+ // Keep a backup of the PrevWitnesses as our input is already signed.
1388+ // When Bob re-creates the outputs for the vPSBT we will need to
1389+ // re-attach the witnesses to the new vPkt as the inputs are already
1390+ // signed.
1391+ witnessBackup := signedPacket .Outputs [0 ].Asset .PrevWitnesses
1392+
1393+ // Bob now creates the output assets.
1394+ err = tapscript .PrepareOutputAssets (context .Background (), signedPacket )
1395+ require .NoError (t .t , err )
1396+
1397+ // We attach the backed-up Previous Witnesses to the newly created
1398+ // outputs by Bob.
1399+ signedPacket .Outputs [0 ].Asset .PrevWitnesses = witnessBackup
1400+ signedPacket .Outputs [1 ].Asset .PrevWitnesses [0 ].SplitCommitment .RootAsset .
1401+ PrevWitnesses = witnessBackup
1402+
1403+ // Serialize the edited signed packet.
1404+ var buffer bytes.Buffer
1405+ err = signedPacket .Serialize (& buffer )
1406+ require .NoError (t .t , err )
1407+ signedBytes := buffer .Bytes ()
1408+
1409+ // Now we'll attempt to complete the transfer.
1410+ sendResp , err := bob .AnchorVirtualPsbts (
1411+ ctxb , & wrpc.AnchorVirtualPsbtsRequest {
1412+ VirtualPsbts : [][]byte {signedBytes },
1413+ },
1414+ )
1415+ require .NoError (t .t , err )
1416+
1417+ ConfirmAndAssertOutboundTransfer (
1418+ t .t , t .lndHarness .Miner .Client , bob , sendResp ,
1419+ genInfo .AssetId ,
1420+ []uint64 {(4 * numUnits )/ 5 - 1 , (numUnits / 5 ) + 1 }, 0 , 1 ,
1421+ )
1422+
1423+ // This is an interactive/PSBT based transfer, so we do need to manually
1424+ // send the proof from the sender to the receiver because the proof
1425+ // courier address gets lost in the address->PSBT conversion.
1426+ _ = sendProof (t , bob , alice , sendResp , aliceAddr .ScriptKey , genInfo )
1427+
1428+ // If Bob was successful in his attempt to edit the outputs, Alice
1429+ // should see an asset with an amount of 399.
1430+ aliceAssets , err := alice .ListAssets (ctxb , & taprpc.ListAssetRequest {
1431+ WithWitness : true ,
1432+ })
1433+ require .NoError (t .t , err )
1434+
1435+ found := false
1436+ for _ , asset := range aliceAssets .Assets {
1437+ if asset .Amount == (numUnits / 5 )+ 1 {
1438+ found = true
1439+ }
1440+ }
1441+
1442+ require .True (t .t , found )
1443+ }
1444+
1445+ // testPsbtSighashNoneInvalid tests that the SIGHASH_NONE flag of vPSBTs is
1446+ // properly accounted for in the generated signatures. This case tests that the
1447+ // transfer is invalidated when the flag is not used.
1448+ func testPsbtSighashNoneInvalid (t * harnessTest ) {
1449+ // First, we'll make a normal asset with enough units to allow us to
1450+ // send it around a few times.
1451+ rpcAssets := MintAssetsConfirmBatch (
1452+ t .t , t .lndHarness .Miner .Client , t .tapd ,
1453+ []* mintrpc.MintAssetRequest {issuableAssets [0 ]},
1454+ )
1455+
1456+ mintedAsset := rpcAssets [0 ]
1457+ genInfo := rpcAssets [0 ].AssetGenesis
1458+
1459+ ctxb := context .Background ()
1460+ ctxt , cancel := context .WithTimeout (ctxb , defaultWaitTimeout )
1461+ defer cancel ()
1462+
1463+ // Now that we have the asset created, we'll make a new node that'll
1464+ // serve as the node which'll receive the assets.
1465+ secondTapd := setupTapdHarness (
1466+ t .t , t , t .lndHarness .Bob , t .universeServer ,
1467+ )
1468+ defer func () {
1469+ require .NoError (t .t , secondTapd .stop (! * noDelete ))
1470+ }()
1471+
1472+ var (
1473+ alice = t .tapd
1474+ bob = secondTapd
1475+ numUnits = uint64 (500 )
1476+ )
1477+
1478+ // We need to derive two keys, one for the new script key and one for
1479+ // the internal key.
1480+ bobScriptKey , bobInternalKey := deriveKeys (t .t , bob )
1481+
1482+ // Now we create a script tree consisting of two simple scripts.
1483+ preImage := []byte ("hash locks are cool" )
1484+ leaf1 := test .ScriptHashLock (t .t , preImage )
1485+ leaf2 := test .ScriptSchnorrSig (t .t , bobScriptKey .RawKey .PubKey )
1486+ leaf1Hash := leaf1 .TapHash ()
1487+ leaf2Hash := leaf2 .TapHash ()
1488+ tapScript := input .TapscriptPartialReveal (
1489+ bobScriptKey .RawKey .PubKey , leaf2 , leaf1Hash [:],
1490+ )
1491+ rootHash := tapScript .ControlBlock .RootHash (leaf2 .Script )
1492+
1493+ sendToTapscriptAddr (
1494+ ctxt , t , alice , bob , numUnits , genInfo , mintedAsset ,
1495+ bobScriptKey , bobInternalKey , tapScript , rootHash ,
1496+ )
1497+
1498+ // Now try to send back those assets using the PSBT flow.
1499+ aliceAddr , err := alice .NewAddr (ctxb , & taprpc.NewAddrRequest {
1500+ AssetId : genInfo .AssetId ,
1501+ Amt : numUnits / 5 ,
1502+ AssetVersion : mintedAsset .Version ,
1503+ })
1504+ require .NoError (t .t , err )
1505+ AssertAddrCreated (t .t , alice , rpcAssets [0 ], aliceAddr )
1506+
1507+ fundResp := fundAddressSendPacket (t , bob , aliceAddr )
1508+ t .Logf ("Funded PSBT: %v" ,
1509+ base64 .StdEncoding .EncodeToString (fundResp .FundedPsbt ))
1510+
1511+ fundedPacket , err := tappsbt .NewFromRawBytes (
1512+ bytes .NewReader (fundResp .FundedPsbt ), false ,
1513+ )
1514+ require .NoError (t .t , err )
1515+
1516+ // We can now ask the wallet to sign the script path, since we only need
1517+ // a signature.
1518+ controlBlockBytes , err := tapScript .ControlBlock .ToBytes ()
1519+ require .NoError (t .t , err )
1520+ fundedPacket .Inputs [0 ].TaprootMerkleRoot = rootHash [:]
1521+ fundedPacket .Inputs [0 ].TaprootLeafScript = []* psbt.TaprootTapLeafScript {
1522+ {
1523+ ControlBlock : controlBlockBytes ,
1524+ Script : leaf2 .Script ,
1525+ LeafVersion : leaf2 .LeafVersion ,
1526+ },
1527+ }
1528+ fundedPacket .Inputs [0 ].TaprootBip32Derivation [0 ].LeafHashes = [][]byte {
1529+ leaf2Hash [:],
1530+ }
1531+
1532+ // This is where we would normally set the sighash flag to SIGHASH_NONE,
1533+ // but instead we skip that step to verify that the VM will invalidate
1534+ // the transfer when any inputs or outputs are mutated.
1535+
1536+ var b bytes.Buffer
1537+ err = fundedPacket .Serialize (& b )
1538+ require .NoError (t .t , err )
1539+
1540+ signedResp , err := bob .SignVirtualPsbt (
1541+ ctxb , & wrpc.SignVirtualPsbtRequest {
1542+ FundedPsbt : b .Bytes (),
1543+ },
1544+ )
1545+ require .NoError (t .t , err )
1546+ require .Contains (t .t , signedResp .SignedInputs , uint32 (0 ))
1547+
1548+ // Now we deserialize the signed packet again in order to edit it
1549+ // and then anchor it.
1550+ signedPacket , err := tappsbt .NewFromRawBytes (
1551+ bytes .NewReader (signedResp .SignedPsbt ), false ,
1552+ )
1553+ require .NoError (t .t , err )
1554+
1555+ // Edit the already signed PSBT and change the output amounts. This
1556+ // should be ok as we used SIGHASH_NONE for the input's signature.
1557+ signedPacket .Outputs [0 ].Amount -= 1
1558+ signedPacket .Outputs [1 ].Amount += 1
1559+
1560+ // Keep a backup of the PrevWitnesses as our input is already signed.
1561+ // When Bob re-creates the outputs for the vPSBT we will need to
1562+ // re-attach the witnesses to the new vPkt as the inputs are already
1563+ // signed.
1564+ witnessBackup := signedPacket .Outputs [0 ].Asset .PrevWitnesses
1565+
1566+ // Bob now creates the output assets.
1567+ err = tapscript .PrepareOutputAssets (context .Background (), signedPacket )
1568+ require .NoError (t .t , err )
1569+
1570+ // We attach the backed-up Previous Witnesses to the newly created
1571+ // outputs by Bob.
1572+ signedPacket .Outputs [0 ].Asset .PrevWitnesses = witnessBackup
1573+ signedPacket .Outputs [1 ].Asset .PrevWitnesses [0 ].SplitCommitment .RootAsset .
1574+ PrevWitnesses = witnessBackup
1575+
1576+ // Serialize the edited signed packet.
1577+ var buffer bytes.Buffer
1578+ err = signedPacket .Serialize (& buffer )
1579+ require .NoError (t .t , err )
1580+ signedBytes := buffer .Bytes ()
1581+
1582+ // Now we'll attempt to complete the transfer.
1583+ sendResp , err := bob .AnchorVirtualPsbts (
1584+ ctxb , & wrpc.AnchorVirtualPsbtsRequest {
1585+ VirtualPsbts : [][]byte {signedBytes },
1586+ },
1587+ )
1588+ require .NoError (t .t , err )
1589+
1590+ ConfirmAndAssertOutboundTransfer (
1591+ t .t , t .lndHarness .Miner .Client , bob , sendResp ,
1592+ genInfo .AssetId ,
1593+ []uint64 {(4 * numUnits )/ 5 - 1 , (numUnits / 5 ) + 1 }, 0 , 1 ,
1594+ )
1595+
1596+ // Export Bob's faulty proof for this transfer.
1597+ var proofResp * taprpc.ProofFile
1598+ waitErr := wait .NoError (func () error {
1599+ resp , err := bob .ExportProof (ctxb , & taprpc.ExportProofRequest {
1600+ AssetId : genInfo .AssetId ,
1601+ ScriptKey : aliceAddr .ScriptKey ,
1602+ })
1603+ if err != nil {
1604+ return err
1605+ }
1606+
1607+ proofResp = resp
1608+ return nil
1609+ }, defaultWaitTimeout )
1610+ require .NoError (t .t , waitErr )
1611+
1612+ // Alice now attempts to import the proof. This will also trigger a
1613+ // transfer validation. This is where we expect the VM to invalidate
1614+ // the proof.
1615+ _ , err = alice .ImportProof (ctxb , & tapdevrpc.ImportProofRequest {
1616+ ProofFile : proofResp .RawProofFile ,
1617+ GenesisPoint : genInfo .GenesisPoint ,
1618+ })
1619+ require .ErrorContains (t .t , err , "unable to verify proof" )
1620+ }
1621+
12681622func deriveKeys (t * testing.T , tapd * tapdHarness ) (asset.ScriptKey ,
12691623 keychain.KeyDescriptor ) {
12701624
0 commit comments