Skip to content

Commit 53db189

Browse files
authored
txn: additional LMsig tests (#6481)
1 parent f381e27 commit 53db189

File tree

1 file changed

+326
-7
lines changed

1 file changed

+326
-7
lines changed

data/transactions/verify/txn_test.go

Lines changed: 326 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -820,7 +820,35 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E=
820820
}
821821
_, err = TxnGroup(txnGroups[0], &blkHdr, nil, &dummyLedger)
822822
require.Error(t, err)
823-
require.Contains(t, err.Error(), "only have one of Sig, Msig, or LMsig")
823+
require.Contains(t, err.Error(), "should only have one of Sig, Msig, or LMsig")
824+
txnGroups[0][0].Lsig.Msig.Subsigs = nil
825+
826+
///// logic with sig and LMsig
827+
txnGroups[0][0].Lsig.LMsig.Subsigs = make([]crypto.MultisigSubsig, 1)
828+
txnGroups[0][0].Lsig.LMsig.Subsigs[0] = crypto.MultisigSubsig{
829+
Key: crypto.PublicKey{0x1},
830+
Sig: crypto.Signature{0x2},
831+
}
832+
_, err = TxnGroup(txnGroups[0], &blkHdr, nil, &dummyLedger)
833+
require.Error(t, err)
834+
require.Contains(t, err.Error(), "should only have one of Sig, Msig, or LMsig")
835+
txnGroups[0][0].Lsig.Sig = crypto.Signature{}
836+
txnGroups[0][0].Lsig.LMsig.Subsigs = nil
837+
838+
///// logic with Msig and LMsig
839+
txnGroups[0][0].Lsig.Msig.Subsigs = make([]crypto.MultisigSubsig, 1)
840+
txnGroups[0][0].Lsig.Msig.Subsigs[0] = crypto.MultisigSubsig{
841+
Key: crypto.PublicKey{0x1},
842+
Sig: crypto.Signature{0x2},
843+
}
844+
txnGroups[0][0].Lsig.LMsig.Subsigs = make([]crypto.MultisigSubsig, 1)
845+
txnGroups[0][0].Lsig.LMsig.Subsigs[0] = crypto.MultisigSubsig{
846+
Key: crypto.PublicKey{0x3},
847+
Sig: crypto.Signature{0x4},
848+
}
849+
_, err = TxnGroup(txnGroups[0], &blkHdr, nil, &dummyLedger)
850+
require.Error(t, err)
851+
require.Contains(t, err.Error(), "should only have one of Sig, Msig, or LMsig")
824852

825853
}
826854

@@ -1029,8 +1057,18 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E=
10291057
func TestTxnGroupCacheUpdateLogicWithMultiSig(t *testing.T) {
10301058
partitiontest.PartitionTest(t)
10311059

1060+
testVersions := []protocol.ConsensusVersion{protocol.ConsensusV40, protocol.ConsensusFuture}
1061+
for _, consensusVer := range testVersions {
1062+
t.Run(string(consensusVer), func(t *testing.T) {
1063+
useLMsig := config.Consensus[consensusVer].LogicSigLMsig
1064+
testTxnGroupCacheUpdateLogicWithMultiSig(t, consensusVer, useLMsig)
1065+
})
1066+
}
1067+
}
1068+
1069+
func testTxnGroupCacheUpdateLogicWithMultiSig(t *testing.T, consensusVer protocol.ConsensusVersion, useLMsig bool) {
10321070
secrets, _, pks, multiAddress := generateMultiSigAccounts(t, 30)
1033-
blkHdr := createDummyBlockHeader()
1071+
blkHdr := createDummyBlockHeader(consensusVer)
10341072

10351073
const numOfTxn = 20
10361074
signedTxn := make([]transactions.SignedTxn, numOfTxn)
@@ -1055,8 +1093,12 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E=
10551093
signedTxn[i].Txn.Sender = multiAddress[s]
10561094
signedTxn[i].Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849b")}
10571095
signedTxn[i].Lsig.Logic = op.Program
1058-
program := logic.MultisigProgram{Addr: crypto.Digest(multiAddress[s]), Program: op.Program}
1059-
1096+
var program crypto.Hashable
1097+
if useLMsig {
1098+
program = logic.MultisigProgram{Addr: crypto.Digest(multiAddress[s]), Program: op.Program}
1099+
} else {
1100+
program = logic.Program(op.Program)
1101+
}
10601102
// create multi sig that 2 out of 3 has signed the txn
10611103
var sigs [2]crypto.MultisigSig
10621104
for j := 0; j < 2; j++ {
@@ -1066,7 +1108,11 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E=
10661108
}
10671109
msig, err := crypto.MultisigAssemble(sigs[:])
10681110
require.NoError(t, err)
1069-
signedTxn[i].Lsig.LMsig = msig
1111+
if useLMsig {
1112+
signedTxn[i].Lsig.LMsig = msig
1113+
} else {
1114+
signedTxn[i].Lsig.Msig = msig
1115+
}
10701116
}
10711117

10721118
txnGroups := make([][]transactions.SignedTxn, len(signedTxn))
@@ -1076,10 +1122,18 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E=
10761122
}
10771123

10781124
breakSignatureFunc := func(txn *transactions.SignedTxn) {
1079-
txn.Lsig.LMsig.Subsigs[0].Sig[0]++
1125+
if useLMsig {
1126+
txn.Lsig.LMsig.Subsigs[0].Sig[0]++
1127+
} else {
1128+
txn.Lsig.Msig.Subsigs[0].Sig[0]++
1129+
}
10801130
}
10811131
restoreSignatureFunc := func(txn *transactions.SignedTxn) {
1082-
txn.Lsig.LMsig.Subsigs[0].Sig[0]--
1132+
if useLMsig {
1133+
txn.Lsig.LMsig.Subsigs[0].Sig[0]--
1134+
} else {
1135+
txn.Lsig.Msig.Subsigs[0].Sig[0]--
1136+
}
10831137
}
10841138

10851139
verifyGroup(t, txnGroups, &blkHdr, breakSignatureFunc, restoreSignatureFunc, crypto.ErrBatchHasFailedSigs.Error())
@@ -1212,3 +1266,268 @@ func BenchmarkTxn(b *testing.B) {
12121266
}
12131267
b.StopTimer()
12141268
}
1269+
1270+
// TestLogicSigMultisigValidation verifies that signatures are properly validated
1271+
// in different contexts (single-sig vs multisig, different multisig addresses).
1272+
func TestLogicSigMultisigValidation(t *testing.T) {
1273+
partitiontest.PartitionTest(t)
1274+
1275+
t.Run("v40", func(t *testing.T) { testLogicSigMultisigValidation(t, protocol.ConsensusV40, false) })
1276+
t.Run("v41", func(t *testing.T) { testLogicSigMultisigValidation(t, protocol.ConsensusV41, true) })
1277+
t.Run("future", func(t *testing.T) { testLogicSigMultisigValidation(t, protocol.ConsensusFuture, true) })
1278+
}
1279+
1280+
func testLogicSigMultisigValidation(t *testing.T, consensusVer protocol.ConsensusVersion, useLMsig bool) {
1281+
ops, err := logic.AssembleString("int 1")
1282+
require.NoError(t, err)
1283+
program := ops.Program
1284+
1285+
// Generate test keys
1286+
secrets := make([]*crypto.SignatureSecrets, 3)
1287+
for i := range secrets {
1288+
var seed crypto.Seed
1289+
crypto.RandBytes(seed[:])
1290+
secrets[i] = crypto.GenerateSignatureSecrets(seed)
1291+
}
1292+
1293+
// Helper to create a test transaction
1294+
makeTestTxn := func(sender basics.Address) transactions.SignedTxn {
1295+
return transactions.SignedTxn{
1296+
Txn: transactions.Transaction{
1297+
Type: protocol.PaymentTx,
1298+
Header: transactions.Header{
1299+
Sender: sender,
1300+
Fee: basics.MicroAlgos{Raw: 1000},
1301+
FirstValid: 1,
1302+
LastValid: 100,
1303+
GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}),
1304+
},
1305+
PaymentTxnFields: transactions.PaymentTxnFields{
1306+
Receiver: basics.Address{},
1307+
Amount: basics.MicroAlgos{Raw: 1000},
1308+
},
1309+
},
1310+
}
1311+
}
1312+
1313+
// Helper to verify a logic sig
1314+
verifyLogicSig := func(t *testing.T, stxn transactions.SignedTxn) error {
1315+
blkHdr := createDummyBlockHeader(consensusVer)
1316+
dummyLedger := DummyLedgerForSignature{}
1317+
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{stxn}, &blkHdr, &dummyLedger, nil)
1318+
require.NoError(t, err)
1319+
return logicSigVerify(0, groupCtx)
1320+
}
1321+
1322+
t.Run("MultisigToSingleSig", func(t *testing.T) {
1323+
pks := []crypto.PublicKey{secrets[0].SignatureVerifier}
1324+
msigAddr, err := crypto.MultisigAddrGen(1, 1, pks)
1325+
require.NoError(t, err)
1326+
1327+
// Sign in multisig context
1328+
var msig crypto.MultisigSig
1329+
if useLMsig { // >=v41: use MultisigProgram with address binding
1330+
msig, err = crypto.MultisigSign(logic.MultisigProgram{Addr: msigAddr, Program: program}, msigAddr, 1, 1, pks, *secrets[0])
1331+
} else { // v40: use Program directly
1332+
msig, err = crypto.MultisigSign(logic.Program(program), msigAddr, 1, 1, pks, *secrets[0])
1333+
}
1334+
require.NoError(t, err)
1335+
1336+
// Try to use multisig signature as single sig
1337+
stxn := makeTestTxn(basics.Address(secrets[0].SignatureVerifier))
1338+
stxn.Lsig = transactions.LogicSig{
1339+
Logic: program,
1340+
Sig: msig.Subsigs[0].Sig,
1341+
}
1342+
1343+
err = verifyLogicSig(t, stxn)
1344+
if useLMsig {
1345+
require.ErrorContains(t, err, "At least one signature didn't pass verification")
1346+
} else {
1347+
require.NoError(t, err)
1348+
}
1349+
})
1350+
1351+
t.Run("SingleSigToMultisig", func(t *testing.T) {
1352+
// Sign as single sig
1353+
singleSig := secrets[0].Sign(logic.Program(program))
1354+
1355+
// Create multisig with same key
1356+
pks := []crypto.PublicKey{secrets[0].SignatureVerifier}
1357+
msigAddr, err := crypto.MultisigAddrGen(1, 1, pks)
1358+
require.NoError(t, err)
1359+
1360+
// Try to use single sig in multisig
1361+
stxn := makeTestTxn(basics.Address(msigAddr))
1362+
msigWithSingleSig := crypto.MultisigSig{Version: 1, Threshold: 1,
1363+
Subsigs: []crypto.MultisigSubsig{{Key: secrets[0].SignatureVerifier, Sig: singleSig}},
1364+
}
1365+
1366+
if useLMsig { // >=v41: use LMsig field
1367+
stxn.Lsig = transactions.LogicSig{Logic: program, LMsig: msigWithSingleSig}
1368+
err = verifyLogicSig(t, stxn)
1369+
require.ErrorContains(t, err, "At least one signature didn't pass verification")
1370+
} else { // v40: use Msig field
1371+
stxn.Lsig = transactions.LogicSig{Logic: program, Msig: msigWithSingleSig}
1372+
err = verifyLogicSig(t, stxn)
1373+
require.NoError(t, err)
1374+
}
1375+
})
1376+
1377+
t.Run("CrossMultisigValidation", func(t *testing.T) {
1378+
// Create two different 1-of-2 multisigs
1379+
pks1 := []crypto.PublicKey{secrets[0].SignatureVerifier, secrets[1].SignatureVerifier, secrets[2].SignatureVerifier}
1380+
pks2 := []crypto.PublicKey{secrets[0].SignatureVerifier, secrets[1].SignatureVerifier}
1381+
1382+
msigAddr1, err := crypto.MultisigAddrGen(1, 2, pks1)
1383+
require.NoError(t, err)
1384+
msigAddr2, err := crypto.MultisigAddrGen(1, 2, pks2)
1385+
require.NoError(t, err)
1386+
1387+
// Sign for each multisig
1388+
var sig1, sig2 crypto.MultisigSig
1389+
if useLMsig { // >=v41: use MultisigProgram with address binding
1390+
sig1, err = crypto.MultisigSign(logic.MultisigProgram{Addr: msigAddr1, Program: program}, msigAddr1, 1, 2, pks1, *secrets[0])
1391+
require.NoError(t, err)
1392+
sig2, err = crypto.MultisigSign(logic.MultisigProgram{Addr: msigAddr2, Program: program}, msigAddr2, 1, 2, pks2, *secrets[1])
1393+
require.NoError(t, err)
1394+
} else { // v40: use Program directly
1395+
sig1, err = crypto.MultisigSign(logic.Program(program), msigAddr1, 1, 2, pks1, *secrets[0])
1396+
require.NoError(t, err)
1397+
sig2, err = crypto.MultisigSign(logic.Program(program), msigAddr2, 1, 2, pks2, *secrets[1])
1398+
require.NoError(t, err)
1399+
}
1400+
1401+
// Try to mix signatures from different multisigs
1402+
stxn := makeTestTxn(basics.Address(msigAddr2))
1403+
mixedMsig := crypto.MultisigSig{Version: 1, Threshold: 2,
1404+
Subsigs: []crypto.MultisigSubsig{
1405+
{Key: secrets[0].SignatureVerifier, Sig: sig1.Subsigs[0].Sig}, // from msigAddr1
1406+
{Key: secrets[1].SignatureVerifier, Sig: sig2.Subsigs[1].Sig}, // from msigAddr2
1407+
},
1408+
}
1409+
1410+
if useLMsig { // >=v41: use LMsig field
1411+
stxn.Lsig = transactions.LogicSig{Logic: program, LMsig: mixedMsig}
1412+
err = verifyLogicSig(t, stxn)
1413+
require.ErrorContains(t, err, "At least one signature didn't pass verification")
1414+
} else { // v40: use Msig field
1415+
stxn.Lsig = transactions.LogicSig{Logic: program, Msig: mixedMsig}
1416+
err = verifyLogicSig(t, stxn)
1417+
require.NoError(t, err)
1418+
}
1419+
})
1420+
1421+
t.Run("DisableMsig", func(t *testing.T) {
1422+
// Run on consensus when Msig is disabled, only LMsig allowed
1423+
if config.Consensus[consensusVer].LogicSigMsig || !config.Consensus[consensusVer].LogicSigLMsig {
1424+
t.Skip("requires LogicSigMsig=false and LogicSigLMsig=true")
1425+
}
1426+
1427+
pks := []crypto.PublicKey{secrets[0].SignatureVerifier, secrets[1].SignatureVerifier}
1428+
msigAddr, err := crypto.MultisigAddrGen(1, 2, pks)
1429+
require.NoError(t, err)
1430+
1431+
// Sign with address binding
1432+
sig1, err := crypto.MultisigSign(logic.MultisigProgram{Addr: msigAddr, Program: program}, msigAddr, 1, 2, pks, *secrets[0])
1433+
require.NoError(t, err)
1434+
sig2, err := crypto.MultisigSign(logic.MultisigProgram{Addr: msigAddr, Program: program}, msigAddr, 1, 2, pks, *secrets[1])
1435+
require.NoError(t, err)
1436+
1437+
msig, err := crypto.MultisigAssemble([]crypto.MultisigSig{sig1, sig2})
1438+
require.NoError(t, err)
1439+
1440+
// Create a transaction
1441+
stxn := makeTestTxn(basics.Address(msigAddr))
1442+
1443+
// Test with Msig field - should be rejected
1444+
stxn.Lsig = transactions.LogicSig{Logic: program, Msig: msig}
1445+
err = verifyLogicSig(t, stxn)
1446+
require.ErrorContains(t, err, "LogicSig Msig field not supported in this consensus version")
1447+
1448+
// Test with LMsig field - should work
1449+
stxn.Lsig = transactions.LogicSig{Logic: program, LMsig: msig}
1450+
err = verifyLogicSig(t, stxn)
1451+
require.NoError(t, err)
1452+
1453+
// Test with both fields - should fail
1454+
stxn.Lsig = transactions.LogicSig{Logic: program, Msig: msig, LMsig: msig}
1455+
err = verifyLogicSig(t, stxn)
1456+
require.ErrorContains(t, err, "LogicSig should only have one of Sig, Msig, or LMsig but has more than one")
1457+
})
1458+
}
1459+
1460+
func TestLogicSigMsigBothFlags(t *testing.T) {
1461+
partitiontest.PartitionTest(t)
1462+
1463+
// Create a test consensus version with both flags enabled
1464+
consensusVer := protocol.ConsensusCurrentVersion
1465+
testConsensus := config.Consensus[consensusVer]
1466+
testConsensus.LogicSigMsig = true
1467+
testConsensus.LogicSigLMsig = true
1468+
config.Consensus["test-lmsig-flags"] = testConsensus
1469+
defer delete(config.Consensus, "test-lmsig-flags")
1470+
1471+
// Simple test program that always approves
1472+
ops, err := logic.AssembleString("int 1")
1473+
require.NoError(t, err)
1474+
program := ops.Program
1475+
1476+
// Create test keys
1477+
var seed crypto.Seed
1478+
crypto.RandBytes(seed[:])
1479+
secret := crypto.GenerateSignatureSecrets(seed)
1480+
pks := []crypto.PublicKey{secret.SignatureVerifier}
1481+
1482+
msigAddr, err := crypto.MultisigAddrGen(1, 1, pks)
1483+
require.NoError(t, err)
1484+
1485+
// Sign with both methods
1486+
msig, err := crypto.MultisigSign(logic.Program(program), msigAddr, 1, 1, pks, *secret)
1487+
require.NoError(t, err)
1488+
1489+
lmsig, err := crypto.MultisigSign(logic.MultisigProgram{Addr: msigAddr, Program: program}, msigAddr, 1, 1, pks, *secret)
1490+
require.NoError(t, err)
1491+
1492+
// Create test transaction
1493+
stxn := transactions.SignedTxn{
1494+
Txn: transactions.Transaction{
1495+
Type: protocol.PaymentTx,
1496+
Header: transactions.Header{
1497+
Sender: basics.Address(msigAddr),
1498+
Fee: basics.MicroAlgos{Raw: 1000},
1499+
FirstValid: 1,
1500+
LastValid: 100,
1501+
GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}),
1502+
},
1503+
PaymentTxnFields: transactions.PaymentTxnFields{
1504+
Receiver: basics.Address{},
1505+
Amount: basics.MicroAlgos{Raw: 1000},
1506+
},
1507+
},
1508+
}
1509+
1510+
// Helper to verify a logic sig
1511+
verifyLogicSig := func() error {
1512+
blkHdr := createDummyBlockHeader("test-lmsig-flags")
1513+
dummyLedger := DummyLedgerForSignature{}
1514+
groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{stxn}, &blkHdr, &dummyLedger, nil)
1515+
require.NoError(t, err)
1516+
return logicSigVerify(0, groupCtx)
1517+
}
1518+
1519+
// Test with Msig field only - should work
1520+
stxn.Lsig = transactions.LogicSig{Logic: program, Msig: msig}
1521+
err = verifyLogicSig()
1522+
require.NoError(t, err)
1523+
1524+
// Test with LMsig field only - should work
1525+
stxn.Lsig = transactions.LogicSig{Logic: program, LMsig: lmsig}
1526+
err = verifyLogicSig()
1527+
require.NoError(t, err)
1528+
1529+
// Test with both fields - should fail
1530+
stxn.Lsig = transactions.LogicSig{Logic: program, Msig: msig, LMsig: lmsig}
1531+
err = verifyLogicSig()
1532+
require.ErrorContains(t, err, "LogicSig should only have one of Sig, Msig, or LMsig but has more than one")
1533+
}

0 commit comments

Comments
 (0)