CVE-2020-11071: Incorrect Comparison (CWE-697)
value: SLPJS (npm package slpjs) before version 0.27.2, has a vulnerability where users could experience false-negative validation outcomes for MINT transaction operations. A poorly implemented SLP wallet could allow spending of the affected tokens which would result in the destruction of a user's minting baton. This is fixed in version 0.27.2.
CWE description: The product compares two entities in a security-relevant context, but the comparison is incorrect, which may lead to resultant weaknesses.
CVSS v3 base score: 8.6
Published: 2020-05-12T01:15Z
Repository: https://github.com/simpleledger/slpjs
Language: TypeScript
Files changed: lib/localvalidator.ts
Commit date: 2020-04-29 12:48:21 -0400
Commit message: fix false negative case for MINT transactions

See the newly added unit test case for this issue.
Currently there are 151 unit tests.

Diff: diff --git a/lib/localvalidator.ts b/lib/localvalidator.ts
index 39514c8..aa9d135 100644
--- a/lib/localvalidator.ts
+++ b/lib/localvalidator.ts
@@ -280,14 +280,14 @@ export class LocalValidator implements SlpValidator {
                             }
                         }
                     }
                 } catch (_) {}
             }
-            if (this.cachedValidations[txid].parents.length !== 1) {
+            if (this.cachedValidations[txid].parents.length < 1) {
                 this.cachedValidations[txid].validity = false;
                 this.cachedValidations[txid].waiting = false;
-                this.cachedValidations[txid].invalidReason = "MINT transaction must have 1 valid baton parent.";
+                this.cachedValidations[txid].invalidReason = "MINT transaction must have at least 1 candidate baton parent input.";
                 return this.cachedValidations[txid].validity!;
             }
         }
         else if (slpmsg.transactionType === SlpTransactionType.SEND) {
             const tokenOutQty = slpmsg.sendOutputs!.reduce((t, v) => { return t.plus(v); }, new BigNumber(0));
@@ -340,14 +340,18 @@ export class LocalValidator implements SlpValidator {
         }
 
         // Set validity validation-cache for parents, and handle MINT condition with no valid input
         // we don't need to check proper token id since we only added parents with same ID in above steps.
         const parentTxids = [...new Set(this.cachedValidations[txid].parents.map(p => p.txid))];
-        for (let i = 0; i < parentTxids.length; i++) {
-            const valid = await this.isValidSlpTxid(parentTxids[i]);
-            this.cachedValidations[txid].parents.filter(p => p.txid === parentTxids[i]).map(p => p.valid = valid);
-            if (this.cachedValidations[txid].details!.transactionType === SlpTransactionType.MINT && !valid) {
+        for (const id of parentTxids) {
+            const valid = await this.isValidSlpTxid(id);
+            this.cachedValidations[txid].parents.filter(p => p.txid === id).map(p => p.valid = valid);
+        }
+
+        // Check MINT for exactly 1 valid MINT baton
+        if (this.cachedValidations[txid].details!.transactionType === SlpTransactionType.MINT) {
+            if (this.cachedValidations[txid].parents.filter(p => p.valid && p.inputQty === null).length !== 1) {
                 this.cachedValidations[txid].validity = false;
                 this.cachedValidations[txid].waiting = false;
                 this.cachedValidations[txid].invalidReason = "MINT transaction with invalid baton parent.";
                 return this.cachedValidations[txid].validity!;
             }

Vulnerable code:             if (this.cachedValidations[txid].parents.length !== 1) {
                this.cachedValidations[txid].invalidReason = "MINT transaction must have 1 valid baton parent.";
        for (let i = 0; i < parentTxids.length; i++) {
            const valid = await this.isValidSlpTxid(parentTxids[i]);
            this.cachedValidations[txid].parents.filter(p => p.txid === parentTxids[i]).map(p => p.valid = valid);
            if (this.cachedValidations[txid].details!.transactionType === SlpTransactionType.MINT && !valid) {
Fixed code:             if (this.cachedValidations[txid].parents.length < 1) {
                this.cachedValidations[txid].invalidReason = "MINT transaction must have at least 1 candidate baton parent input.";
        for (const id of parentTxids) {
            const valid = await this.isValidSlpTxid(id);
            this.cachedValidations[txid].parents.filter(p => p.txid === id).map(p => p.valid = valid);
        }

        // Check MINT for exactly 1 valid MINT baton
        if (this.cachedValidations[txid].details!.transactionType === SlpTransactionType.MINT) {
            if (this.cachedValidations[txid].parents.filter(p => p.valid && p.inputQty === null).length !== 1) {