Skip to Content
Scalus Club is now open! Join us to get an early access to new features 🎉
DocumentationDesign PatternsParameter Validation

Parameter Validation Pattern

Verify that script instances are legitimate instantiations of parameterized scripts with specific parameter values.

The Challenge

When a minting policy needs to ensure tokens go only to a spending script parameterized with a specific value (e.g., a royalty address), it cannot directly inspect the target script’s parameters. Each different parameter creates a different script hash, making it impossible to distinguish between legitimate and arbitrary script destinations.

Example problem: An NFT creator wants to ensure their NFTs are always minted directly to a marketplace that enforces their royalty settings. Without parameter validation, a minting policy cannot verify the destination script has the correct creator address baked in.

How It Works

Script hashing in Cardano follows this formula:

script_hash = blake2b_224(language_tag ++ cbor_encoded_program) Language tags: - PlutusV1: 0x01 - PlutusV2: 0x02 - PlutusV3: 0x03 When parameters are applied: parameterized_program = base_program $ param1 $ param2 ... parameterized_hash = blake2b_224(language_tag ++ cbor(parameterized_program))

The pattern:

  1. Off-chain: Compile base script, apply parameters, compute expected hash
  2. Off-chain: Pass expected hash to the dependent script (e.g., minting policy)
  3. On-chain: Dependent script verifies outputs go to addresses matching expected hash

When to Use This Pattern

Best for:

  • Minting policies that need to verify tokens go to specific parameterized scripts
  • Multi-script coordination where scripts must verify each other’s parameters
  • Factory patterns where minted tokens reference scripts with verified parameters
  • Royalty enforcement across marketplaces

Not ideal for:

  • Simple single-script validators
  • Cases where parameter verification isn’t security-critical

API Reference

Off-chain Functions (ParameterValidation)

FunctionDescription
computeScriptHashV3Compute hash for PlutusV3 script with applied parameters
computeScriptHashV2Compute hash for PlutusV2 script with applied parameters
computeScriptHashV1Compute hash for PlutusV1 script with applied parameters

On-chain Functions (ParameterValidationOnChain)

FunctionDescription
verifyScriptCredentialVerify credential matches expected script hash (fails if not)
verifyAddressScriptVerify address has expected script credential (fails if not)
findOutputsToScriptFind outputs sent to a specific script hash
isExpectedScriptCheck if credential matches expected hash (returns Boolean)

Implementation Guide

Off-chain: Computing Expected Hash

import scalus.patterns.ParameterValidation import scalus.uplc.builtin.Data.toData // Compute the expected script hash for a parameterized script def computeMarketplaceHash(creatorPkh: PubKeyHash): ValidatorHash = { ParameterValidation.computeScriptHashV3( MarketplaceBaseProgram.program.deBruijnedProgram, creatorPkh.toData ) } // Create parameterized scripts val marketplaceHash = computeMarketplaceHash(creatorPkh) val marketplace = MarketplaceBaseProgram.program.deBruijnedProgram $ creatorPkh.toData val mintingPolicy = NFTMintingBaseProgram.program.deBruijnedProgram $ NFTMintParams(marketplaceHash, tokenName).toData

On-chain: Verifying Script Destination

import scalus.patterns.ParameterValidationOnChain @Compile object NFTMintingPolicy { inline def validate(params: NFTMintParams)(scData: Data): Unit = { val sc = scData.to[ScriptContext] sc.scriptInfo match case ScriptInfo.MintingScript(policyId) => // Mint exactly 1 of our token and nothing else under this policy require( sc.txInfo.mint.hasOnly(policyId, params.tokenName, 1), "Must mint exactly 1 NFT and no other token under this policy" ) // Find the single output holding units of this token val nftOutput = sc.txInfo.outputs.findUniqueOrFail( _.value.quantityOf(policyId, params.tokenName) > 0, "NFT must go to exactly one output" ) // Verify output goes to the expected marketplace script ParameterValidationOnChain.verifyAddressScript( nftOutput.address, params.expectedMarketplaceHash ) case _ => fail("Unsupported script purpose") } }

Key insight: The expected hash is computed off-chain and passed as a parameter to the minting policy. The on-chain code only needs to compare hashes, not recompute them.

Two details of the snippet carry security weight. mint.hasOnly(policyId, name, 1) rejects any other token name under the same policy in the same transaction, which a bare quantityOf(...) === 1 does not. outputs.findUniqueOrFail(p, msg) fails when the token lands in zero or several outputs, where find(p).getOrFail(msg) would silently pick the first.

Example: NFT with Verified Marketplace

The complete example shows an NFT minting policy that ensures NFTs can only be sent to a marketplace parameterized with the correct creator royalty address.

Marketplace validator (parameterized by creator):

@Compile object MarketplaceValidator { inline def validate(creatorPkh: PubKeyHash)(scData: Data): Unit = { val sc = scData.to[ScriptContext] sc.scriptInfo match case ScriptInfo.SpendingScript(_, datum) => val listing = datum.getOrFail("Datum required").to[Listing] val action = sc.redeemer.to[MarketplaceRedeemer] action match case MarketplaceRedeemer.Buy => // Verify royalty payment to creator (10%) val royaltyAmount = listing.price * BigInt(10) / BigInt(100) val creatorCred = Credential.PubKeyCredential(creatorPkh) // ... verify payments to creator and seller case MarketplaceRedeemer.Cancel => require(sc.txInfo.signatories.contains(listing.seller)) case _ => fail("Unsupported script purpose") } }

Complete workflow:

object ParameterValidationUsage { // 1. Compute marketplace hash for a given creator def computeMarketplaceHash(creatorPkh: PubKeyHash): ValidatorHash = { ParameterValidation.computeScriptHashV3( MarketplaceBaseProgram.program.deBruijnedProgram, creatorPkh.toData ) } // 2. Create parameterized marketplace script def createMarketplaceScript(creatorPkh: PubKeyHash) = { MarketplaceBaseProgram.program.deBruijnedProgram $ creatorPkh.toData } // 3. Create parameterized NFT minting policy def createNFTMintingPolicy(expectedMarketplaceHash: ValidatorHash, tokenName: TokenName) = { val params = NFTMintParams(expectedMarketplaceHash, tokenName) NFTMintingBaseProgram.program.deBruijnedProgram $ params.toData } // 4. Complete setup def setupNFTWithMarketplace(creatorPkh: PubKeyHash, tokenName: TokenName) = { val marketplaceHash = computeMarketplaceHash(creatorPkh) val marketplace = createMarketplaceScript(creatorPkh) val mintingPolicy = createNFTMintingPolicy(marketplaceHash, tokenName) (marketplace, mintingPolicy, marketplaceHash) } }

Security consideration: The expected hash must be computed correctly off-chain. If an attacker can influence the hash computation, they could redirect tokens to malicious scripts.

Alternative Approaches

ApproachProsCons
Parameter ValidationDirect verification, no runtime overheadRequires off-chain hash computation
Beacon tokensFlexible, can verify at runtimeAdditional minting policy needed
Oracle/reference inputsDynamic parameter updatesCentralization risk

Resources

Last updated on