Cardano on-chain validators written in Aiken (Plutus v3, stdlib v3.0.0).
LICENSE # Apache-2.0 – source code
LICENSE-BRAND # All Rights Reserved – logos and brand assets
EC.svg / ED.svg / EF.svg / EU.svg # Brand assets (All Rights Reserved)
credential/
├── aiken.toml # Project manifest
├── aiken.lock # Dependency lock file
├── plutus.json # Compiled Plutus blueprint
├── LICENSE # Apache-2.0
├── validators/
│ └── documint.ak # DocuMint – Document/certificate anchor minting policy (production EchoCert logic)
├── lib/
│ └── benchmarks/
│ ├── clausify/benchmark.ak # Clausify benchmark
│ └── knights/ # Knight's tour benchmark suite
│ ├── benchmark.ak
│ ├── chess_set.ak
│ ├── heuristic.ak
│ ├── queue.ak
│ ├── sort.ak
│ └── types.ak
└── legacy/ # Archived, never-deployed echocert.ak + audit trail
├── README.md # Timeline + explanation of the documint/echocert discrepancy
├── echocert.ak # Original EchoCert source (not compiled by aiken build)
└── evidence/
├── policy-derivation.md # Reproducible proof of the deployed policy's derivation
├── mainnet-assets.json
├── mainnet-deployed-script.json
├── plutus-frontend-deployed.json
├── plutus-source-correct.json
└── IMG_5085.jpg
⚠️ Important: The on-chain EchoCert minting policy deployed on mainnet and preprod runsdocumint's validator logic, not the separateechocert.ak. Seecredential/legacy/README.mdfor the historical background and reproducible proof. For more context, see echoforgellc.tech/blog.
The production minting policy for issuing and revoking on-chain credential certificates as Cardano native tokens.
Note: The production implementation uses documint.ak (see below). The original echocert.ak was never deployed; its source code is archived in credential/legacy/ for historical reference.
EchoCert turns a credential (name + date + issuer) into a unique, non-fungible token anchored on Cardano. Each token name equals SHA-256(name | date | issuer), making the token name itself a cryptographic commitment to the certificate content.
| Parameter | Type | Description |
|---|---|---|
treasury |
ByteArray |
Payment credential hash that receives the 1 ADA issuance fee |
issuer |
ByteArray |
Public key hash of the certificate issuer's own wallet. Under this permissionless design, anyone can be an issuer for their own certificates (see Trust Tier model below); this parameter identifies who issued a given certificate, not whether they were authorised to. |
pub type CertAction {
IssueCert { cert_hash: ByteArray }
RevokeCert
}IssueCert { cert_hash }
- Transaction must be signed by
issuer(extra_signatoriescontainsissuer). - Exactly one token is minted under this policy; its name must equal
cert_hash. - The minted quantity must be exactly 1 (no batch minting).
- At least one transaction output must pay ≥ 1 ADA (1,000,000 lovelace) to the
treasuryaddress (supports bothVerificationKeyandScripttreasury credentials).
RevokeCert
- Transaction must be signed by
issuer. - Exactly one token is burned under this policy (quantity = −1).
| Threat | Mitigation |
|---|---|
| Impersonating another issuer | Every mint requires an extra_signatories check against the issuer key hash — no one can mint under a policy parameterised to someone else's key. (This does not restrict who may become an issuer — see Trust Tier model.) |
| Token name forgery | Token name is enforced to equal the cert_hash supplied in the redeemer; mismatching names cause immediate script failure. |
| Batch inflation | The validator pattern-matches [Pair(token_name, quantity)] — any attempt to mint more than one token name or more than 1 unit under the policy is rejected at the pattern-match. |
| Free minting (no fee) | IssueCert requires at least one output ≥ 1 ADA to the treasury; a transaction that omits this output fails. |
| Burn-as-mint attack | RevokeCert enforces quantity = −1; passing a positive quantity causes the equality check to fail. |
| Non-issuer revocation | RevokeCert also requires issuer signature — holders cannot burn certificates they did not issue. |
else clause |
The else(_) { fail } catch-all ensures the policy rejects any purpose other than minting (e.g. accidental spend). |
| Test | Expected |
|---|---|
issue_cert_succeeds |
✅ Pass |
issue_cert_succeeds_with_excess_ada |
✅ Pass |
issue_cert_succeeds_script_treasury |
✅ Pass |
issue_cert_fails_wrong_signer |
❌ Fail (expected) |
issue_cert_fails_wrong_token_name |
❌ Fail (expected) |
issue_cert_fails_quantity_not_one |
❌ Fail (expected) |
issue_cert_fails_insufficient_fee |
❌ Fail (expected) |
issue_cert_fails_no_treasury_output |
❌ Fail (expected) |
revoke_cert_succeeds |
✅ Pass |
revoke_cert_fails_wrong_signer |
❌ Fail (expected) |
revoke_cert_fails_positive_quantity |
❌ Fail (expected) |
This is the production-deployed minting validator for EchoCert. Although named documint, it enforces the same security guarantees as the separate echocert.ak design. All EchoCert credentials minted on mainnet and preprod are governed by this validator.
A minting policy for anchoring document hashes on Cardano as native tokens — a tamper-evident notarisation primitive.
DocuMint creates a unique on-chain anchor for any document. The token name equals SHA-256(document_bytes), so the token itself proves that a specific byte sequence existed at block time. Burning the token formally revokes the anchor.
When used as EchoCert's production validator, it anchors credential credentials (name + date + issuer) as immutable on-chain records.
| Parameter | Type | Description |
|---|---|---|
treasury |
ByteArray |
Payment credential hash that receives the 5 ADA notarisation fee |
issuer |
ByteArray |
Public key hash of the certificate issuer's own wallet. Under this permissionless design, anyone can be an issuer for their own certificates (see Trust Tier model below); this parameter identifies who issued a given certificate, not whether they were authorised to. |
pub type Action {
MintAnchor { doc_hash: ByteArray }
BurnAnchor
}MintAnchor { doc_hash }
- Transaction must be signed by
issuer. - Exactly one token is minted under this policy; its name must equal
doc_hash. - The minted quantity must be exactly 1.
- At least one transaction output must pay ≥ 5 ADA (5,000,000 lovelace) to the
treasuryaddress (supports bothVerificationKeyandScripttreasury credentials).
BurnAnchor
- Transaction must be signed by
issuer. - Exactly one token is burned under this policy (quantity = −1).
| Threat | Mitigation |
|---|---|
| Impersonating another issuer | Every mint requires an extra_signatories check against the issuer key hash — no one can anchor under a policy parameterised to someone else's key. (This does not restrict who may become an issuer — see Trust Tier model.) |
| Hash substitution | Token name is enforced to equal doc_hash from the redeemer; a forged or different hash fails the equality check. |
| Duplicate anchors | One token per transaction prevents bulk minting; the pattern-match [Pair(...)] rejects any multi-name or multi-quantity mint. |
| Fee evasion | MintAnchor requires an output ≥ 5 ADA to the treasury — no payment, no anchor. |
| Positive-quantity burn attempt | BurnAnchor enforces quantity = −1; passing +1 (or any positive value) fails the final equality check. |
| Unauthorised revocation | BurnAnchor requires issuer signature — third parties cannot destroy anchors. |
else clause |
else(_) { fail } rejects all non-mint purposes (spend, certify, etc.). |
| Test | Expected |
|---|---|
mint_anchor_succeeds |
✅ Pass |
mint_anchor_succeeds_with_extra_lovelace |
✅ Pass |
mint_anchor_succeeds_treasury_is_script |
✅ Pass |
mint_anchor_succeeds_with_multiple_outputs |
✅ Pass |
mint_anchor_fails_without_issuer_signature |
❌ Fail (expected) |
mint_anchor_fails_with_wrong_token_name |
❌ Fail (expected) |
mint_anchor_fails_with_quantity_not_one |
❌ Fail (expected) |
mint_anchor_fails_insufficient_treasury_payment |
❌ Fail (expected) |
mint_anchor_fails_no_treasury_output |
❌ Fail (expected) |
mint_anchor_fails_no_signature_at_all |
❌ Fail (expected) |
burn_anchor_succeeds |
✅ Pass |
burn_anchor_fails_without_issuer_signature |
❌ Fail (expected) |
burn_anchor_fails_with_positive_quantity |
❌ Fail (expected) |
The production EchoCert system runs documint.ak, not a separate echocert.ak deployment. This decision was made in July 2026 after an on-chain audit confirmed that documint and echocert enforce equivalent trust and security properties (see credential/legacy/evidence/policy-derivation.md for reproducible verification).
The original echocert.ak validator was never deployed to mainnet or preprod. Its source code is preserved in credential/legacy/echocert.ak along with a full audit trail proving the discrepancy and explaining the timeline (see credential/legacy/README.md).
Both validators follow the same defence-in-depth pattern:
Issuer key check → Token name binding → Quantity check → Fee / output check
-
Issuer-gated minting — All actions require an explicit signature from the configured
issuerkey hash, checked vialist.has(self.extra_signatories, issuer). This is a hard on-chain constraint; no off-chain bypass is possible. -
Hash-bound token names — Token names equal the document/certificate hash supplied at call time, creating a cryptographic link between the token and its real-world content.
-
Singleton enforcement — The pattern match
expect [Pair(token_name, quantity)]ensures exactly one token entry exists under the policy per transaction. Any attempt to mint multiple names or batch-mint multiple units fails at deserialization. -
Fail-fast with
expect— The validators use Aiken'sexpectkeyword, which immediately aborts with a script error on any violated constraint rather than silently continuing. -
Exhaustive
elsebranch —else(_) { fail }prevents the policy from being used as a spend or other validator kind, eliminating unexpected attack surfaces. -
Treasury as payment commitment — Requiring a minimum lovelace payment to a fixed treasury address prevents free-of-cost minting and creates a financial stake in every issuance.
This platform records certificate content and issuance time. Unverified certificates do not represent the issuer's true identity. Domain verification only proves the issuer controls the domain, not institutional qualifications or content authenticity. Certificate authenticity is the responsibility of the issuer.
treasury and issuer are both compile-time parameters baked into the validator script (see validator documint(treasury, issuer) in documint.ak). Applying a different issuer key produces a different compiled script, and therefore a different script hash — a different Policy ID. Since this is a permissionless system (Tier 1), any wallet can become an issuer, so there is no one Policy ID that "is" EchoCert the way a single NFT collection has one canonical Policy ID. Checking "is this Policy ID the official one?" is not a well-formed question here.
Genuineness is instead established as a three-layer model, which separates "is this certificate real" from "is this issuer trustworthy":
-
Structural verification (protocol conformance). Independent of which Policy ID is involved, anyone can confirm the minting transaction satisfies the validator's hard rules: exactly one token name is minted under the policy, in a quantity of exactly one (no batch minting), that name equals the SHA-256 hash of the certificate/document content, and the script's compiled bytes match the published
documintvalidator inplutus.json(a Plutus script hash is deterministic from its bytes plus its applied parameters — seecredential/legacy/evidence/policy-derivation.mdfor a worked, reproducible example of this check). This proves the token was minted by a genuine copy of the validator — not that it came from the official platform. -
Treasury anchoring (platform anchor). Structural conformance alone isn't sufficient — anyone can compile their own copy of the same validator with their own
issuerkey, satisfying layer 1 while having nothing to do with EchoForge. The differentiator is thetreasuryparameter: every certificate issued through the official platform pays its fee to the published EchoForge treasury address, regardless of whichissuerkey signed it. 1 A transaction that is structurally correct but pays a treasury address outside that set did not come from the official platform, no matter how correct its logic looks. This is the one constant across every legitimate issuer, in place of a single Policy ID. -
Issuer trust tier (real-world identity). Passing layers 1–2 only proves the certificate is authentic to the protocol — it says nothing about who the specific issuer is or whether they can be trusted. That question is answered separately by the three tiers below (Unverified / Domain-Linked / On-Chain Identity).
In short: the Policy ID identifies which issuer minted a token, not whether it's genuine. Genuineness comes from structure + treasury anchor (layers 1–2); trustworthiness of the issuer is a separate judgment made via the tier system (layer 3).
- Anyone can issue certificates without any identity verification.
- Certificates carry an "Unverified Issuer" watermark.
- Suitable for personal use, small events, and informal certificates.
⚠️ This platform does not verify any issuer information.
- Issuers prove domain control via DNS TXT records to unlock the green-label professional style.
- Certificate displays "Issued by [Name] ✓"; the lookup page shows a green checkmark next to the wallet address.
- Trust comes from the issuer's own domain reputation; the platform provides the verification tool.
⚠️ This platform only verifies the issuer's control over the stated domain — not institutional qualifications or certificate content.
The system cannot distinguish lookalike domains (e.g. stonepark.edu vs st0nepark.edu). Always verify the domain spelling against the institution's official website. The recipient bears responsibility for this check; the platform provides the domain display as a verification aid.
- Only wallets holding specific Governance Tokens or verified via Cardano DID may issue certificates with the official color scheme.
- Trust is derived from on-chain data — transactions originating from a known DAO multisig wallet are verifiably authentic.
- Certificate authenticity is the responsibility of the issuer, not the platform.
The compiled validator schemas and their CBOR-encoded scripts are published in credential/plutus.json (Plutus v3, generated by Aiken v1.1.21).
| Validator | Entry Point |
|---|---|
documint.documint |
documint.documint.mint |
echocert.echocert |
echocert.echocert.mint |
- Aiken CLI ≥ v1.1.21
cd credential
aiken buildThis produces (or updates) plutus.json with the compiled scripts.
cd credential
aiken checkAll tests (success and expected-failure cases) should pass.
cd credential
aiken bench| Field | Value |
|---|---|
| Compiler | Aiken v1.1.21 |
| Plutus version | v3 |
| Standard library | aiken-lang/stdlib v3.0.0 |
| License | Apache-2.0 (code) / All Rights Reserved (brand assets) |
| Network | Cardano (mainnet / preprod) |
- Code: Apache-2.0
- Brand assets (
EC.svg,ED.svg,EF.svg,EU.svg): All Rights Reserved
Footnotes
-
For developers reconciling this against the authenticity-check implementation: "the published treasury address" is not always a single unchanging constant — it may be rotated for security (e.g. a cold-wallet migration), in which case it resolves to a small set of historical addresses, each valid for the time window it was in effect. Verification matches a certificate's mint time to the official address in effect during that window; older, superseded addresses remain recognized as valid for certificates minted while they were current. This detail is omitted from the main text above for readability; general readers can treat "the treasury address" as effectively a single constant. ↩