# duplicate-shielded-mint

> Identical canonical shielded mint inputs produce a duplicate coin commitment in one transaction.

Canonical: https://nightshade.dicksonwu.xyz/rules/duplicate-shielded-mint.html
Release: Nightshade 0.1.1 (v0.1.1)

| Field | Value |
| --- | --- |
| Rule ID | `duplicate-shielded-mint` |
| Severity | medium |
| Confidence | varies |
| Maturity | experimental |
| Selection | Audit default |
| Policy intent | not required |
| Default enabled | yes |
| Category | Audit-default ordinary rules |
| Engine facts used | resolved-calls, cfg, successful-predicates, canonical-mint-observations |
| Release | v0.1.1 |

## Selection and scope

Selected by the default audit profile, so an ordinary scan runs it.

```sh
./.nightshade/node_modules/.bin/nightshade ./contracts --detectors duplicate-shielded-mint
./.nightshade/node_modules/.bin/nightshade detector show duplicate-shielded-mint
```

The first command runs only this rule; `--detectors` takes a comma-separated list. The second prints the rule's metadata from your installed version.

Severity is review priority. It does not express exploitability or impact. Confidence is detector metadata, not a probability. All built-in rules are experimental.

Compiler scope: the engine supports the pinned profiles 0.28.0, 0.29.0, 0.30.0, 0.31.1, 0.33.0-rc.2, 0.34.0, 0.35.0-rc.1 and 0.35.0, with ZKIR2 by default. Support for this rule can be narrower; the rule documentation below states any narrower scope.

## Verified example

Synthetic example from the proof run of the released CLI. Two mint calls use identical token, amount, nonce and recipient inputs on the same path. Full example: https://nightshade.dicksonwu.xyz/examples.md#duplicate-shielded-mint

```compact
// Owned synthetic duplicate-mint regression.
import CompactStandardLibrary;

export circuit rewardTwice(nonce: Bytes<32>, recipient: ZswapCoinPublicKey): [] {
  const to = left<ZswapCoinPublicKey,ContractAddress>(disclose(recipient));
  mintShieldedToken(pad(32,"sdk:reward"),1,disclose(evolveNonce(0,nonce)),to);
  mintShieldedToken(pad(32,"sdk:reward"),1,disclose(evolveNonce(0,nonce)),to);
}
```

Output (exit code 1):

```text
$ nightshade vulnerable.compact --config config.json --compiler /path/to/compactc

MEDIUM — Duplicate shielded mint commitment [duplicate-shielded-mint]
vulnerable.compact:7:3
  7 |   mintShieldedToken(pad(32,"sdk:reward"),1,disclose(evolveNonce(0,nonce)),to);
    |   ^
rewardTwice invokes mintShieldedToken twice with provably identical token domain, amount, nonce and active recipient on one modeled successful history. The pinned library derives the same coin commitment in this contract context; duplicate commitments are rejected by the ledger. This is a transaction availability defect, not proof of token theft or a generic commitment-opening vulnerability.
Source: vulnerable.compact:6:3
Call path: rewardTwice → mintShieldedToken
Remediation: Use a distinct nonce for each minted output, for example evolveNonce with a distinct index. Review freshness across separate transactions independently.

Semantic profile: compact-0.34.0-zkir2.
Baseline analysis: complete; selected rules: complete.
Rule coverage: 1 selected rule(s); 0 with incomplete entries.
1 finding(s), 0 suppressed, 0 baseline; 1/1 entrypoint(s) completed; analysis complete. Compiler validation passed.
```

Corrected: Give the second mint a distinct nonce.

```compact
// Owned synthetic duplicate-mint regression.
import CompactStandardLibrary;

export circuit rewardTwice(nonce: Bytes<32>, recipient: ZswapCoinPublicKey): [] {
  const to = left<ZswapCoinPublicKey,ContractAddress>(disclose(recipient));
  mintShieldedToken(pad(32,"sdk:reward"),1,disclose(evolveNonce(0,nonce)),to);
  mintShieldedToken(pad(32,"sdk:reward"),1,disclose(evolveNonce(1,nonce)),to);
}
```

Output (exit code 0):

```text
$ nightshade corrected.compact --config config.json --compiler /path/to/compactc

Semantic profile: compact-0.34.0-zkir2.
Baseline analysis: complete; selected rules: complete.
Rule coverage: 1 selected rule(s); 0 with incomplete entries.
0 finding(s), 0 suppressed, 0 baseline; 1/1 entrypoint(s) completed; analysis complete. Compiler validation passed.
```

Paths are shortened to the example folder. A quiet result shows that this rule stopped firing, not that the contract is secure.

## Corpus checks

Synthetic corpus checks for this rule in the 0.1.1 proof run. A check counts only with compiler acceptance and complete analysis.

| Positive detected | Corrected quiet | Lookalike quiet | Other negative quiet | Incomplete |
| ---: | ---: | ---: | ---: | ---: |
| 1/1 | 1/1 | 1/1 | 0/0 | 0 |

No deviations were recorded for this rule.

These are behavior checks on synthetic fixtures, not a precision or recall estimate. See [evaluation](https://nightshade.dicksonwu.xyz/evaluation.md#synthetic-corpus).

## Rule documentation

From https://github.com/DicksonWu654/nightshade/blob/v0.1.1/docs/detectors/duplicate-shielded-mint.md at v0.1.1.

Availability: **default-on**, medium severity, high confidence for operand equality.

### Description, example and scenario

```compact
mintShieldedToken(domain, 1, nonce, recipient);
mintShieldedToken(domain, 1, nonce, recipient);
```

Two canonical library calls on the same modeled successful transaction history
have provably identical token domain, amount, nonce and active recipient. The
contract address is unchanged within that history. The library derives the same
coin commitment, which the ledger rejects as a duplicate. This is a transaction
availability defect; the rule does not establish theft or runtime feasibility.

### Recommendation

Use a different nonce for each minted output. `evolveNonce` derives nonces
from an index and a prior nonce; use distinct indices for different outputs.
Review freshness across separate transactions independently.

### Evidence and known false positives

Resolved canonical mint calls, immutable operand identities/constants and enforced
successful-path equalities establish equality. Re-evaluating `evolveNonce` with
identical inputs also establishes equality. A saved opaque computed nonce retains an observer-only immutable snapshot; separately computed unknown values do not become equal through shared dependencies. Inactive Either payloads are ignored;
active tags and addresses are retained. Separate histories, rejected paths,
independent unknown witnesses, changing ledger revisions, different amounts and
domains, and application namesakes do not create duplicates.

Intentionally rejected test transactions may be review matches. Generic repeated
`persistentCommit` openings are not diagnosed as a vulnerability. Cross-transaction
nonce freshness, custom mint/commitment implementations and symbolic reasoning
are outside scope. Unknown operands do not prove a duplicate; exhausted observation
or equality budgets are rule/entry-scoped incomplete, preserve baseline findings
and keep the overall scan incomplete.

## Suppress a match

If a match is intentional, suppress it on the line before with the exact rule ID and a reason of at least eight characters:

```compact
// nightshade-disable-next-line duplicate-shielded-mint -- Reason this match is intentional
```

Diagnostics cannot be suppressed, and an unmatched directive for a selected rule makes analysis incomplete. See [configuration](https://nightshade.dicksonwu.xyz/configuration.md#suppress-an-intentional-match) for fingerprint suppressions and baselines.

## References

- [Rule source document at v0.1.1](https://github.com/DicksonWu654/nightshade/blob/v0.1.1/docs/detectors/duplicate-shielded-mint.md)
- [Catalog entry in rules.json](https://nightshade.dicksonwu.xyz/rules.json)
- [Verified example](https://nightshade.dicksonwu.xyz/examples.md#duplicate-shielded-mint)
- [All rules](https://nightshade.dicksonwu.xyz/rules.md#audit-default-ordinary-rules)
- [Limitations](https://nightshade.dicksonwu.xyz/limitations.md)
