-
Notifications
You must be signed in to change notification settings - Fork 100
Open
Labels
bugSomething isn't workingSomething isn't working
Description
Summary
Summary
MeshJS produces an incorrect script hash for PlutusV3 scripts, causing a permanent lock of funds in UTxOs created with MeshJS on Cardano Conway era (Preview/Mainnet).
The Problem
When using resolveScriptHash() or building transactions with PlutusV3 scripts, MeshJS:
- Unwraps the CBOR ByteString before hashing.
- Hashes the raw UPLC bytes instead of the CBOR-wrapped bytes.
- Produces a different hash than what Aiken/Cardano-CLI produces.
- When spending, puts raw bytes (not CBOR) into the witness set.
Root Cause Analysis
Correct Cardano Specification (CIP-0057, Conway Era):
script_hash = blake2b_224(0x03 || cbor_wrapped_script_bytes)
(Where cbor_wrapped_script_bytes includes the 59 01 b0 header).
MeshJS Behavior:
It appears MeshJS unwraps the CBOR (extracts inner bytes) and hashes 0x03 || unwrapped, which is incorrect for V3.
Suggested Fix
Option 1: Fix Hash Calculation
In resolveScriptHash for V3:
function resolveScriptHash(scriptCbor: string, version: "V3"): string {
const scriptBytes = Buffer.from(scriptCbor, "hex");
// DO NOT unwrap! Hash the CBOR as-is
return blake2b_224(Buffer.concat([Buffer.from([0x03]), scriptBytes]));
}
### Steps to reproduce the bug
1. **Create Aiken PlutusV3 Script**
Run `aiken build`. This produces a `plutus.json` with a specific hash (e.g., `47be01b4...`).
2. **Use MeshJS to Create Escrow**
```typescript
import { resolveScriptHash, MeshTxBuilder } from "@meshsdk/core";
import contract from "./plutus.json";
const scriptCbor = contract.validators[0].compiledCode;
// Aiken Hash: 47be01b4...
// Bug: MeshJS calculates wrong hash
const meshHash = resolveScriptHash(scriptCbor, "V3");
console.log(meshHash); // Output: d80cc51a... (DIFFERENT!)
// Transaction locks funds at the WRONG address (derived from d80cc51a...)
const tx = new MeshTxBuilder({...});
await tx
.txOut(scriptAddress, [{unit: "lovelace", quantity: "20000000"}])
.txOutInlineDatumValue(datum)
.complete();
### Actual Result
tx.spendingPlutusScriptV3()
.txIn(utxo.txHash, utxo.outputIndex, ...)
.txInScript(scriptCbor)
.txInRedeemerValue(redeemer)
.txInInlineDatumPresent();
await tx.complete(); // Fails on submission
The transaction fails with a `MalformedScriptWitnesses` error because the node expects the hash derived from the Raw Bytes (since that's what created the address), but the witness provided is likely malformed or rejected due to header mismatch.
**Hash Comparison:**
- **Aiken/CLI Hash (Correct):** `47be01b4e6535eff63f75202295fc81afb1b80635e0b3c55d110ec61`
- **MeshJS Hash (Incorrect):** `d80cc51a64e0157b1663fd1008fe3d06237e270924d223b57feb6a12`
**Error Log:**
```json
{
"error": "ConwayUtxowFailure (MalformedScriptWitnesses (fromList [ScriptHash \"d80cc51a64e0157b1663fd1008fe3d06237e270924d223b57feb6a12\"]))"
}
### Expected Result
1. `resolveScriptHash(scriptCbor, "V3")` should return the same hash as Aiken's `plutus.json` and Cardano-CLI.
2. Transaction witnesses should contain **CBOR-wrapped** script bytes.
3. Users should be able to spend UTxOs locked with PlutusV3 scripts without `MalformedScriptWitnesses` errors.
### SDK version
@meshsdk/core (latest / Nov 2025)
### Environment type
- [x] Node.js
- [x] Browser
- [x] Browser Extension
- [ ] Other
### Environment details
- **Network**: Cardano Preview Testnet (Conway Era)
- **Smart Contract**: Aiken Plutus V3 validator
- **Wallet**: Eternl (Browser Extension)
- **OS**: Windows/Linuxjosef0xb
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working