Tutorial 7 — Connect an App Proof to Cardano
Open this anchored audit log in App-Chain Studio
- Level: intermediate to advanced
- Time: about 20 minutes on local devnet
- Outcome: bootstrap a threshold-enforced script anchor, advance it after app blocks finalize, prove one record against the anchored root, and know which checks need your own Cardano source.
Before you start: finish Tutorial 1 and stop or
clean its cluster. You need the extracted Yano X JVM distribution, curl, and
jq. Every command runs from the distribution’s top-level directory. In
Studio, add the anchor:script capability; it asks for reviewed validator and
thread-policy references, while the local launcher uses the bundled artifacts.
An app ledger finality certificate proves that the configured member threshold approved a block. An L1 anchor makes a later application position durable and discoverable through Cardano. These are related but distinct proofs.
1. Start an anchored local cluster
Section titled “1. Start an anchored local cluster”export YANO_CLUSTER_DIR=/tmp/yano-tutorial-anchor
./yano.sh appchain cluster start 3 \ --anchor-mode script \ --anchor-every 2Script mode uses a thread NFT and a Plutus V3 validator. The local launcher has
a deterministic demo anchor seed and can fund it from the self-contained
devnet faucet. When the cluster is up, the launcher’s summary names the next
step, anchor-bootstrap <chain>.
The commands below use node 0’s HTTP port, HTTP_BASE. Set it to the first
port of the range the launcher printed. It is 7070 unless the launcher
printed a line such as HTTP range 7070-7072 unavailable; using 7073-7075:
HTTP_BASE=70702. Bootstrap one chain’s immutable anchor identity
Section titled “2. Bootstrap one chain’s immutable anchor identity”./yano.sh appchain cluster anchor-bootstrap orders-chainThe command funds the anchor wallet from the devnet faucet, then calls the
chain’s admin/anchor/bootstrap endpoint and prints its JSON answer.
Bootstrap consumes a seed UTxO, mints the one-shot thread NFT, and creates the
genesis datum. It establishes identity; it does not let the wallet alone claim
an arbitrary application tip.
Inspect the L1 Anchor card on node 0’s status page,
http://127.0.0.1:$HTTP_BASE/ui/app-chain/. It should show the thread policy,
script address, wallet, anchored height, transaction, and lag.
3. Produce application progress
Section titled “3. Produce application progress”Submit one event through the REST API so you keep its message id, and wait until it is final:
API=http://127.0.0.1:$HTTP_BASE/api/v1/app-chain/chains/orders-chain
MESSAGE_ID=$(curl -s -X POST "$API/messages" \ -H 'Content-Type: application/json' \ -d '{"topic":"audit","body":"{\"event\":\"certificate-issued\",\"id\":\"C-1\"}"}' \ | jq -r .messageId)
until HEIGHT=$(curl -sf "$API/messages/$MESSAGE_ID" | jq -er .height); do sleep 1; doneecho "final at height $HEIGHT"An anchor is due once --anchor-every 2 blocks have accumulated, and blocks
exist only when there are messages. Submit a second event, then wait until a
confirmed anchor covers your message’s height:
./yano.sh appchain cluster submit orders-chain audit \ '{"event":"certificate-published","id":"C-1"}'
until curl -sf "$API/anchor/commitment" \ | jq -e --argjson h "$HEIGHT" '.anchoredHeight >= $h' >/dev/null; do sleep 2done./yano.sh appchain cluster statuscurl -s "$API/status" | jq '.anchor | {bootstrapped, lastAnchoredHeight, lastAnchorTx, lagBlocks}'All members independently reconcile the authenticated script UTxO from their own L1 view. Node-local “confirmed since restart” counters may differ after a restart; the durable anchored height, transaction, and lag should converge.
4. Read the node’s anchor
Section titled “4. Read the node’s anchor”curl -s "$API/anchor/commitment" \ | jq '{mode, anchoredHeight, stateRoot, transactionHash, provenance}'Expected output, with your values:
{ "mode": "script", "anchoredHeight": <a>, "stateRoot": "<64 hex characters>", "transactionHash": "<64 hex characters>", "provenance": "L1-confirmed by this node"}The provenance says what this is: the node’s report of what it saw on Cardano. It is the starting point for verification, not the end of it.
5. Prove the record against the anchored root
Section titled “5. Prove the record against the anchored root”Ask for a typed proof of your message at the latest confirmed anchor, and check that its root is the anchored root:
ANCHOR_ROOT=$(curl -s "$API/anchor/commitment" | jq -r .stateRoot)
curl -s -X POST "$API/proof-subjects/finalized-message-v1/proof" \ -H 'Content-Type: application/json' \ -d "$(jq -nc --arg id "$MESSAGE_ID" ' {coordinates:{"message-id":$id}, view:"latest-confirmed-anchor", claim:{claimId:"recorded",operands:{}}, includeEvidence:false}')" \ | jq --arg root "$ANCHOR_ROOT" '{trust, height: .proof.version, presence: .proof.presence, recorded: .claimResult.satisfied, sameRootAsAnchor: (.proof.stateRoot == $root)}'Expected output:
{ "trust": "NODE_CONFIRMED_L1_REFERENCE", "height": <a>, "presence": "PRESENT", "recorded": true, "sameRootAsAnchor": true}If presence is ABSENT, the anchor you read predates your message: wait for
the next anchor and ask again. The proof response also carries the block’s
finality certificate, which you verify in step 7.
6. Export portable proof material
Section titled “6. Export portable proof material”Save the material a verifier needs, so it can be checked later without this cluster:
curl -s "$API/messages/$MESSAGE_ID/proof-package" > message-proof-package.jsoncurl -s "$API/evidence/$MESSAGE_ID" > evidence-bundle.jsonjq 'keys' message-proof-package.jsonThe proof package (appchain-message-proof-v1) holds the message’s inclusion
path, the block-message-root and state-record proofs, the evidence bundle, and
the anchor reference the node used. Its embedded verification results are
explanatory only. The evidence bundle holds the message’s block, every block up
to the anchored one, and their finality signatures; verify it with
EvidenceVerifier.verify(bundle, trustContext) from yano-core-api, with a
trust context you pinned yourself.
7. The independent verification chain
Section titled “7. The independent verification chain”Everything so far came from one node. Seven checks remove that trust. Step through them, then see how a forged root or an old anchor shows up:
What you're seeing: one orders-chain message, final at height 42 and covered by a script anchor. Each step names what it proves and what you must pin yourself.
- Recompute the record Ask a node for a typed proof of
finalized-message-v1with the viewlatest-confirmed-anchor. Recompute the state key from your message id, and decode the value: height, index, topic, and sender. The node labels the answerNODE_CONFIRMED_L1_REFERENCE, which is its own claim about Cardano.POST …/proof-subjects/finalized-message-v1/proof {"view": "latest-confirmed-anchor", …} - Check the path Verify the MPF path from that key and value to the returned state root, with
ProofVerifier.verifyInternalConsistency. This is proof math only,INTERNAL_CONSISTENCY_ONLY: a node could have invented the whole tree and its root.- passes: The path recomputes the returned root
- Check finality Verify the block header’s finality certificate with
ProofVerifier.verifyCertified. You pin the genesis id, state profile, member keys, threshold, and the consensus-context digest for that height, all from configuration you trust, never from the response.- passes: The path recomputes the returned root
- passes: A threshold of the pinned member keys signed the header that commits this root
- Follow the hash chain Needed when your record sits in an older block than the anchor, as a message proof against its block’s messages root does.
evidence/{messageId}returns every block up to the anchored one, andEvidenceVerifierchecks each link and signature. A typed proof at the anchored height skips this.GET …/evidence/{messageId} - Fetch from Cardano Read the anchor transaction and the thread UTxO from a Cardano source you choose: your own node, db-sync, Koios, or an explorer. A transaction hash in a Yano response is not verification.
- Check the anchor identity Require the validator’s script address and the thread token’s policy id that you recorded when the anchor was bootstrapped. Anyone can write a datum to some other address; only this thread is the ledger’s anchor.
- passes: The output sits at the expected script address
- passes: It holds the expected thread token
- Match the datum Decode the inline datum and match its fields: chain id, genesis id, application id, commitment profile, format fingerprint, height, block hash, state root, member keys, and threshold. Now the record is tied to Cardano without trusting any node:
INDEPENDENTLY_VERIFIED_L1_ANCHOR.- passes: Height and block hash match the certified block
- passes: State root matches the proof’s root
- passes: Identity, member keys, and threshold match what you pinned
What if: A node forges the root
Proof math alone cannot catch a made-up tree.
- Recompute the record Ask a node for a typed proof of
finalized-message-v1with the viewlatest-confirmed-anchor. Recompute the state key from your message id, and decode the value: height, index, topic, and sender. The node labels the answerNODE_CONFIRMED_L1_REFERENCE, which is its own claim about Cardano.POST …/proof-subjects/finalized-message-v1/proof {"view": "latest-confirmed-anchor", …} - Check the path The path checks out: the node built a tree around your record and computed its root,
91c4…. Consistency says nothing about who agreed to that root.- passes: The path recomputes the returned root
- Check finality No threshold of the member keys you pinned signed a header that commits
91c4…. The check fails and you stop here.- passes: The path recomputes the returned root
- fails: A threshold of the pinned member keys signed the header that commits this root
What if: The anchor is older than the record
Absent under an older anchor does not mean it never happened.
- Recompute the record Your message is final at height 42, but the latest confirmed anchor is at height 40. The proof is against the anchored root, where your record does not exist yet: its presence is
ABSENT.POST …/proof-subjects/finalized-message-v1/proof {"view": "latest-confirmed-anchor", …} - Wait for the next anchor Poll
anchor/commitmentuntilanchoredHeightis at least 42, then ask again. Anchors followevery-blocks, so a quiet ledger may need one more block, or themax-interval-minutestimer.GET …/anchor/commitment
- you
- a node you do not trust
- Cardano
- independently verified
- rejected
A transaction hash appearing in a Yano JSON response is not, by itself,
independent anchor verification. From Java, use ProofVerifier.verifyCertified
for steps 1–3 and EvidenceVerifier for the hash chain, rather than trusting
server booleans.
Metadata versus script anchoring
Section titled “Metadata versus script anchoring”| Property | Metadata | Script |
|---|---|---|
| Setup | Fund wallet | Fund wallet + bootstrap |
| L1 enforcement | Data commitment only | Monotonic thread + threshold member signatures |
| Main safety authority | Anchor wallet | On-chain validator and member threshold |
| Typical use | Low-cost timestamp/discovery | Strong consortium settlement boundary |
Both modes commit application data; only script mode enforces the threshold and monotonic successor rules on chain. Neither checks that a root is correct: in script mode, the members who co-sign do.
Public test networks
Section titled “Public test networks”For preview or preprod:
- generate a dedicated raw 32-byte anchor seed;
- provide it through the owner-only anchor-key file mechanism;
- fund the printed enterprise address with test ADA;
- use an explicit public-network confirmation before the demo spends; and
- increase cadence/stability settings to match real L1 timing and fees.
Do not reuse a wallet mnemonic, validator member seed, actor signing key, or API key as the anchor wallet.
8. Clean up
Section titled “8. Clean up”./yano.sh appchain cluster cleanunset YANO_CLUSTER_DIRLocal devnet state is disposable. A public script anchor is permanent Cardano history even after local files are deleted.
Go deeper
Section titled “Go deeper”- Follow L1 anchoring §5 for portable evidence bundles and exact trust-context construction.
- Read Cardano anchoring to step through how members and the validator check each advance.
- Test L1 rollback and anchor resubmission before a pilot.
- Review the pinned Aiken release artifacts and the Java/julc cross-implementation drift checks before depending on a released script identity.