From d013bb7e724e79308a9d08d1b6d5ddf648a01ea3 Mon Sep 17 00:00:00 2001 From: Brannon King Date: Tue, 27 Jul 2021 07:56:51 -0400 Subject: [PATCH] [lbry] reject invalid claim names at mempool --- blockchain/validate.go | 19 ++++++---------- mempool/mempool.go | 2 +- txscript/nameclaim.go | 45 +++++++++++++++++++++++++++----------- txscript/nameclaim_test.go | 14 ++++++++++++ 4 files changed, 54 insertions(+), 26 deletions(-) diff --git a/blockchain/validate.go b/blockchain/validate.go index 5a31b3d3..4a2e45e1 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -228,8 +228,8 @@ func withinLevelBounds(reduction int64, lv int64) bool { } // CheckTransactionSanity performs some preliminary checks on a transaction to -// ensure it is sane. These checks are context free. -func CheckTransactionSanity(tx *btcutil.Tx) error { +// ensure it is sane. +func CheckTransactionSanity(tx *btcutil.Tx, enforceSoftFork bool) error { // A transaction must have at least one input. msgTx := tx.MsgTx() if len(msgTx.TxIn) == 0 { @@ -288,15 +288,10 @@ func CheckTransactionSanity(tx *btcutil.Tx) error { btcutil.MaxSatoshi) return ruleError(ErrBadTxOutValue, str) } - if txscript.ClaimScriptSize(txOut.PkScript) > txscript.MaxClaimScriptSize { - str := fmt.Sprintf("claimscript exceeds max size of %v", - txscript.MaxClaimScriptSize) - return ruleError(ErrBadTxOutValue, str) - } - if txscript.ClaimNameSize(txOut.PkScript) > txscript.MaxClaimNameSize { - str := fmt.Sprintf("claim name exceeds max size of %v", - txscript.MaxClaimNameSize) - return ruleError(ErrBadTxOutValue, str) + + err := txscript.AllClaimsAreSane(txOut.PkScript, enforceSoftFork) + if err != nil { + return ruleError(ErrBadTxOutValue, err.Error()) } } @@ -552,7 +547,7 @@ func checkBlockSanity(block *btcutil.Block, powLimit *big.Int, timeSource Median // Do some preliminary checks on each transaction to ensure they are // sane before continuing. for _, tx := range transactions { - err := CheckTransactionSanity(tx) + err := CheckTransactionSanity(tx, false) if err != nil { return err } diff --git a/mempool/mempool.go b/mempool/mempool.go index 1203731d..65d8e8cf 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -963,7 +963,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *btcutil.Tx, isNew, rateLimit, rejec // Perform preliminary sanity checks on the transaction. This makes // use of blockchain which contains the invariant rules for what // transactions are allowed into blocks. - err := blockchain.CheckTransactionSanity(tx) + err := blockchain.CheckTransactionSanity(tx, true) if err != nil { if cerr, ok := err.(blockchain.RuleError); ok { return nil, nil, chainRuleError(cerr) diff --git a/txscript/nameclaim.go b/txscript/nameclaim.go index 936dac91..0c473848 100644 --- a/txscript/nameclaim.go +++ b/txscript/nameclaim.go @@ -1,7 +1,9 @@ package txscript import ( + "bytes" "fmt" + "unicode/utf8" "github.com/btcsuite/btcd/wire" ) @@ -134,17 +136,8 @@ func StripClaimScriptPrefix(script []byte) []byte { return script[cs.Size():] } -// ClaimScriptSize returns size of the claim script minus the script pubkey part. -func ClaimScriptSize(script []byte) int { - cs, err := DecodeClaimScript(script) - if err != nil { - return len(script) - } - return cs.Size() -} - -// ClaimNameSize returns size of the name in a claim script or 0 if script is not a claimtrie transaction. -func ClaimNameSize(script []byte) int { +// claimNameSize returns size of the name in a claim script or 0 if script is not a claimtrie transaction. +func claimNameSize(script []byte) int { cs, err := DecodeClaimScript(script) if err != nil { return 0 @@ -156,8 +149,8 @@ func ClaimNameSize(script []byte) int { func CalcMinClaimTrieFee(tx *wire.MsgTx, minFeePerNameClaimChar int64) int64 { var minFee int64 for _, txOut := range tx.TxOut { - // TODO: lbrycrd ignored transactions that weren't OP_CLAIMNAME - minFee += int64(ClaimNameSize(txOut.PkScript)) + // TODO maybe: lbrycrd ignored transactions that weren't OP_CLAIMNAME + minFee += int64(claimNameSize(txOut.PkScript)) } return minFee * minFeePerNameClaimChar } @@ -200,3 +193,29 @@ func isUpdateClaim(pops []parsedOpcode) bool { pops[4].opcode.value == OP_2DROP && pops[5].opcode.value == OP_2DROP } + +const illegalChars = "=&#:*$@%?/\x00" + +func AllClaimsAreSane(script []byte, enforceSoftFork bool) error { + cs, err := DecodeClaimScript(script) + if err != ErrNotClaimScript { + if err != nil { + return fmt.Errorf("invalid claim script: %s", err.Error()) + } + if cs.Size() > MaxClaimScriptSize { + return fmt.Errorf("claimscript exceeds max size of %v", MaxClaimScriptSize) + } + if len(cs.Name()) > MaxClaimNameSize { + return fmt.Errorf("claim name exceeds max size of %v", MaxClaimNameSize) + } + if enforceSoftFork { + if !utf8.Valid(cs.Name()) { + return fmt.Errorf("claim name is not valid UTF-8") + } + if bytes.ContainsAny(cs.Name(), illegalChars) { + return fmt.Errorf("claim name has illegal chars; it should not contain any of these: %s", illegalChars) + } + } + } + return nil +} diff --git a/txscript/nameclaim_test.go b/txscript/nameclaim_test.go index 89715534..ed1a07ce 100644 --- a/txscript/nameclaim_test.go +++ b/txscript/nameclaim_test.go @@ -71,3 +71,17 @@ func TestCreationParseLoopSupport(t *testing.T) { r.Equal(claimID, script.ClaimID()) r.Nil(script.Value()) } + +func TestInvalidChars(t *testing.T) { + r := require.New(t) + + script, err := ClaimNameScript("tester", "value") + r.NoError(err) + r.NoError(AllClaimsAreSane(script, true)) + + for i := range []byte(illegalChars) { + script, err := ClaimNameScript("a"+illegalChars[i:i+1], "value") + r.NoError(err) + r.Error(AllClaimsAreSane(script, true)) + } +}