From 071ce34516f3924f1c54ebfbf1356f90b1c45c52 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Wed, 31 Jan 2024 23:36:41 +0100 Subject: [PATCH] itest: add vpsbt sighash coverage --- itest/psbt_test.go | 179 +++++++++++++++++++++++++++++++++++++ itest/test_list_on_test.go | 4 + 2 files changed, 183 insertions(+) diff --git a/itest/psbt_test.go b/itest/psbt_test.go index 0b0639ce7..76ea7cce5 100644 --- a/itest/psbt_test.go +++ b/itest/psbt_test.go @@ -4,10 +4,12 @@ import ( "bytes" "context" "encoding/base64" + "fmt" "testing" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcwallet/waddrmgr" tap "github.com/lightninglabs/taproot-assets" "github.com/lightninglabs/taproot-assets/address" @@ -18,6 +20,7 @@ import ( "github.com/lightninglabs/taproot-assets/taprpc" wrpc "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc" "github.com/lightninglabs/taproot-assets/taprpc/mintrpc" + "github.com/lightninglabs/taproot-assets/tapscript" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/stretchr/testify/require" @@ -1265,6 +1268,182 @@ func testMultiInputPsbtSingleAssetID(t *harnessTest) { require.Len(t.t, secondaryNodeAssets.Assets, 0) } +// testPsbtSighashNone tests that the SIGHASH_NONE flag of vPSBTs is properly +// accounted for in the generated signatures, +func testPsbtSighashNone(t *harnessTest) { + // First, we'll make a normal asset with enough units to allow us to + // send it around a few times. + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{issuableAssets[0]}, + ) + + mintedAsset := rpcAssets[0] + genInfo := rpcAssets[0].AssetGenesis + + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout) + defer cancel() + + // Now that we have the asset created, we'll make a new node that'll + // serve as the node which'll receive the assets. + secondTapd := setupTapdHarness( + t.t, t, t.lndHarness.Bob, t.universeServer, + ) + defer func() { + require.NoError(t.t, secondTapd.stop(!*noDelete)) + }() + + var ( + alice = t.tapd + bob = secondTapd + numUnits = uint64(500) + ) + + // We need to derive two keys, one for the new script key and one for + // the internal key. + bobScriptKey, bobInternalKey := deriveKeys(t.t, bob) + + // Now we create a script tree consisting of two simple scripts. + preImage := []byte("hash locks are cool") + leaf1 := test.ScriptHashLock(t.t, preImage) + leaf2 := test.ScriptSchnorrSig(t.t, bobScriptKey.RawKey.PubKey) + leaf1Hash := leaf1.TapHash() + leaf2Hash := leaf2.TapHash() + tapScript := input.TapscriptPartialReveal( + bobScriptKey.RawKey.PubKey, leaf2, leaf1Hash[:], + ) + rootHash := tapScript.ControlBlock.RootHash(leaf2.Script) + + sendToTapscriptAddr( + ctxt, t, alice, bob, numUnits, genInfo, mintedAsset, + bobScriptKey, bobInternalKey, tapScript, rootHash, + ) + + // Now try to send back those assets using the PSBT flow. + aliceAddr, err := alice.NewAddr(ctxb, &taprpc.NewAddrRequest{ + AssetId: genInfo.AssetId, + Amt: numUnits / 5, + AssetVersion: mintedAsset.Version, + }) + require.NoError(t.t, err) + AssertAddrCreated(t.t, alice, rpcAssets[0], aliceAddr) + + fundResp := fundAddressSendPacket(t, bob, aliceAddr) + t.Logf("Funded PSBT: %v", + base64.StdEncoding.EncodeToString(fundResp.FundedPsbt)) + + fundedPacket, err := tappsbt.NewFromRawBytes( + bytes.NewReader(fundResp.FundedPsbt), false, + ) + require.NoError(t.t, err) + + // We can now ask the wallet to sign the script path, since we only need + // a signature. + controlBlockBytes, err := tapScript.ControlBlock.ToBytes() + require.NoError(t.t, err) + fundedPacket.Inputs[0].TaprootMerkleRoot = rootHash[:] + fundedPacket.Inputs[0].TaprootLeafScript = []*psbt.TaprootTapLeafScript{ + { + ControlBlock: controlBlockBytes, + Script: leaf2.Script, + LeafVersion: leaf2.LeafVersion, + }, + } + fundedPacket.Inputs[0].TaprootBip32Derivation[0].LeafHashes = [][]byte{ + leaf2Hash[:], + } + + // Before signing, we set the sighash of the first input to SIGHASH_NONE + // which allows us to alter the outputs of the PSBT after the signature + // has been generated. + fundedPacket.Inputs[0].SighashType = txscript.SigHashNone + + var b bytes.Buffer + err = fundedPacket.Serialize(&b) + require.NoError(t.t, err) + + fmt.Println("SGHSH: bob signing") + signedResp, err := bob.SignVirtualPsbt( + ctxb, &wrpc.SignVirtualPsbtRequest{ + FundedPsbt: b.Bytes(), + }, + ) + require.NoError(t.t, err) + require.Contains(t.t, signedResp.SignedInputs, uint32(0)) + fmt.Println("SGHSH: bob signing done") + + // Now we deserialize the signed packet again in order to edit it + // and then anchor it. + signedPacket, err := tappsbt.NewFromRawBytes( + bytes.NewReader(signedResp.SignedPsbt), false, + ) + require.NoError(t.t, err) + + // Edit the already signed PSBT and change the output amounts. This + // should be ok as we used SIGHASH_NONE for the input's signature. + signedPacket.Outputs[0].Amount -= 1 + signedPacket.Outputs[1].Amount += 1 + + // Keep a backup of the PrevWitnesses as our input is already signed. + // When Bob re-creates the outputs for the vPSBT we will need to + // re-attach the witnesses to the new vPkt as the inputs are already + // signed. + witnessBackup := signedPacket.Outputs[0].Asset.PrevWitnesses + + // Bob now creates the output assets. + tapscript.PrepareOutputAssets(context.Background(), signedPacket) + + // We attach the backed-up Previous Witnesses to the newly created + // outputs by Bob. + signedPacket.Outputs[0].Asset.PrevWitnesses = witnessBackup + signedPacket.Outputs[1].Asset.PrevWitnesses[0].SplitCommitment.RootAsset. + PrevWitnesses = witnessBackup + + // Serialize the edited signed packet. + var buffer bytes.Buffer + err = signedPacket.Serialize(&buffer) + require.NoError(t.t, err) + signedBytes := buffer.Bytes() + + // Now we'll attempt to complete the transfer. + sendResp, err := bob.AnchorVirtualPsbts( + ctxb, &wrpc.AnchorVirtualPsbtsRequest{ + VirtualPsbts: [][]byte{signedBytes}, + }, + ) + require.NoError(t.t, err) + + fmt.Println("SGHSH: bob sending") + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, bob, sendResp, + genInfo.AssetId, + []uint64{(4*numUnits)/5 - 1, (numUnits / 5) + 1}, 0, 1, + ) + fmt.Println("SGHSH: bob sending done") + + // This is an interactive/PSBT based transfer, so we do need to manually + // send the proof from the sender to the receiver because the proof + // courier address gets lost in the address->PSBT conversion. + _ = sendProof(t, bob, alice, aliceAddr.ScriptKey, genInfo) + + // If Bob was successful in his attempt to edit the outputs, Alice + // should see an asset with an amount of 399. + aliceAssets, err := alice.ListAssets(ctxb, &taprpc.ListAssetRequest{ + WithWitness: true, + }) + require.NoError(t.t, err) + + found := false + for _, asset := range aliceAssets.Assets { + if asset.Amount == (numUnits/5)+1 { + found = true + } + } + + require.True(t.t, found) +} + func deriveKeys(t *testing.T, tapd *tapdHarness) (asset.ScriptKey, keychain.KeyDescriptor) { diff --git a/itest/test_list_on_test.go b/itest/test_list_on_test.go index 7ff7ca92f..ff341a896 100644 --- a/itest/test_list_on_test.go +++ b/itest/test_list_on_test.go @@ -171,6 +171,10 @@ var testCases = []*testCase{ name: "psbt multi send", test: testPsbtMultiSend, }, + { + name: "psbt_sighash_none", + test: testPsbtSighashNone, + }, { name: "multi input psbt single asset id", test: testMultiInputPsbtSingleAssetID,