From 478e61571cf6ce216580d8c381a2f0b85ef92056 Mon Sep 17 00:00:00 2001 From: Elena Bardho Date: Mon, 10 Feb 2025 14:07:44 +0000 Subject: [PATCH 1/2] Add anchor metadata validation check --- src/app/components/transaction.tsx | 13 +++++++++++-- src/app/components/validationChecks.tsx | 6 ++++++ src/app/utils/txUtils.ts | 22 +++++++++++++++++++++- src/app/utils/txValidationUtils.ts | 17 +++++++++++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/app/components/transaction.tsx b/src/app/components/transaction.tsx index b6f9a02..5b21a83 100644 --- a/src/app/components/transaction.tsx +++ b/src/app/components/transaction.tsx @@ -8,7 +8,7 @@ import * as CLS from "@emurgo/cardano-serialization-lib-browser"; import ReactJsonPretty from 'react-json-pretty'; import * as txValidationUtils from "../utils/txValidationUtils"; import { TransactionChecks } from "./validationChecks"; -import { decodeHextoTx,convertGAToBech,getCardanoScanURL} from "../utils/txUtils"; +import { decodeHextoTx,convertGAToBech,getCardanoScanURL,getDataHashFromURI} from "../utils/txUtils"; import { VotingDetails } from "./votingDetails"; @@ -30,6 +30,7 @@ export const TransactionButton = () => { isSameNetwork: false, hasICCCredentials: false, isInOutputPlutusData: false, + isMetadataAnchorValid: false, }); const resetValidationState = () => { setValidationState((prev) => ({ @@ -40,6 +41,7 @@ export const TransactionButton = () => { isSameNetwork: false, hasICCCredentials: false, isInOutputPlutusData: false, + isMetadataAnchorValid: false, })); }; const checkTransaction = async () => { @@ -69,8 +71,12 @@ export const TransactionButton = () => { const voting_procedures= transactionBody.to_js_value().voting_procedures; if (!voting_procedures) throw new Error("Transaction has no voting procedures."); const votes=voting_procedures[0].votes; + if (!votes) throw new Error("Transaction has no votes."); const hasOneVote = txValidationUtils.hasOneVoteOnTransaction(transactionBody); const vote = voting_procedures[0].votes[0].voting_procedure.vote; + if(!votes[0].voting_procedure.anchor) throw new Error("Vote has no anchor."); + const voteMetadataURL = votes[0].voting_procedure.anchor.anchor_url; + const voteMetadataHash = votes[0].voting_procedure.anchor.anchor_data_hash; setValidationState({ isPartOfSigners: await txValidationUtils.isPartOfSigners(transactionBody, stakeCred), @@ -79,6 +85,7 @@ export const TransactionButton = () => { isSameNetwork: txValidationUtils.isSameNetwork(transactionBody, network), hasICCCredentials: txValidationUtils.hasValidICCCredentials(transactionBody, network), isInOutputPlutusData: txValidationUtils.isSignerInPlutusData(transactionBody, stakeCred), + isMetadataAnchorValid: await txValidationUtils.checkMetadataAnchor(voteMetadataURL,voteMetadataHash), }); //********************************************Voting Details *********************************************************************/ @@ -88,13 +95,15 @@ export const TransactionButton = () => { if (votes && hasOneVote) { const govActionID = convertGAToBech(votes[0].action_id.transaction_id, votes[0].action_id.index); + setvoteChoice(vote === 'Yes' ? 'Constitutional' : vote === 'No' ? 'Unconstitutional' : 'Abstain'); setgovActionID(govActionID); if(!votes[0].voting_procedure.anchor) throw new Error("Vote has no anchor."); setmetadataAnchorURL(votes[0].voting_procedure.anchor.anchor_url); setMetadataAnchorHash(votes[0].voting_procedure.anchor.anchor_data_hash); setCardanoscan(getCardanoScanURL(govActionID,transactionNetworkID)); - } + } + } diff --git a/src/app/components/validationChecks.tsx b/src/app/components/validationChecks.tsx index af52c70..4bb8bbc 100644 --- a/src/app/components/validationChecks.tsx +++ b/src/app/components/validationChecks.tsx @@ -7,6 +7,7 @@ interface TransactionChecksProps { isSameNetwork: boolean; hasICCCredentials: boolean; isInOutputPlutusData: boolean; + isMetadataAnchorValid: boolean; } export const TransactionChecks = ({ @@ -16,6 +17,7 @@ export const TransactionChecks = ({ isSameNetwork, hasICCCredentials, isInOutputPlutusData, + isMetadataAnchorValid, }: TransactionChecksProps) => { return ( @@ -43,6 +45,10 @@ export const TransactionChecks = ({ Is stake credential in plutus data?: {isInOutputPlutusData ? "✅" : "❌"} + + + Does the metadata match the provided hash? ?: {isMetadataAnchorValid ? "✅" : "❌"} + ); diff --git a/src/app/utils/txUtils.ts b/src/app/utils/txUtils.ts index f3d3ba5..4492a97 100644 --- a/src/app/utils/txUtils.ts +++ b/src/app/utils/txUtils.ts @@ -1,6 +1,7 @@ import * as CLS from "@emurgo/cardano-serialization-lib-browser"; import { deserializeAddress } from "@meshsdk/core"; import dotevn from "dotenv"; +import * as blake from 'blakejs'; dotevn.config(); const NEXT_PUBLIC_REST_IPFS_GATEWAY=process.env.NEXT_PUBLIC_REST_IPFS_GATEWAY; @@ -62,4 +63,23 @@ export const openInNewTab = (url: string) => { ? "https://" + NEXT_PUBLIC_REST_IPFS_GATEWAY + url?.slice(7) : "https://" + url; window.open(fullUrl, "_blank", "noopener,noreferrer"); -}; \ No newline at end of file +}; + +export const getDataHashFromURI = async (anchorURL: string) => { + console.log('Callllll fuuunctioooon'); + if (anchorURL !== "") { + console.log("Anchor data null") + } + if (anchorURL.startsWith("ipfs")) { + anchorURL = "https://" + NEXT_PUBLIC_REST_IPFS_GATEWAY + anchorURL.slice(7); + } + // anchorURL='https://ipfs.io/ipfs/bafkreidsmyjjfrsvj3czrsu5roy2undco2bhhcnqdgbievolgbyi7lptxy' + const data = await fetch(anchorURL); + const text = await data.text(); + console.log('THEEEEE TEXT',text); + const hash = blake.blake2bHex(text,undefined, 32); + console.log("Hash from text:", hash); + return hash +} + + diff --git a/src/app/utils/txValidationUtils.ts b/src/app/utils/txValidationUtils.ts index 2eb5c71..5b2f425 100644 --- a/src/app/utils/txValidationUtils.ts +++ b/src/app/utils/txValidationUtils.ts @@ -1,3 +1,5 @@ +import {getDataHashFromURI} from "../utils/txUtils"; + /** * Checks if the given stake credential is part of the required signers of the transaction. * @param transactionBody the body of the transaction to check. @@ -156,3 +158,18 @@ export const isSignerInPlutusData = (transactionBody: any, stakeCredential: stri return false; }; +/** + * Checks if the given anchor URL produces the given anchor data hash. + * @param anchorURL The URL of the anchor to check. + * @param anchor_data_hash The expected anchor data hash. + * @returns {Promise} True if the anchor URL produces the expected hash, false otherwise. + */ +export const checkMetadataAnchor = async (anchorURL: string, anchor_data_hash: string): Promise => { + try { + const producedHash = await getDataHashFromURI(anchorURL); + return producedHash === anchor_data_hash; + } catch (error) { + console.error("Error fetching metadata:", error); + return false; + } +}; From f6a2ccb634c6adac35c44c1a72d4b4e231983ad1 Mon Sep 17 00:00:00 2001 From: Elena Bardho Date: Mon, 10 Feb 2025 14:32:05 +0000 Subject: [PATCH 2/2] Small code fix --- src/app/components/transaction.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/components/transaction.tsx b/src/app/components/transaction.tsx index 5b21a83..0a6f791 100644 --- a/src/app/components/transaction.tsx +++ b/src/app/components/transaction.tsx @@ -99,8 +99,8 @@ export const TransactionButton = () => { setvoteChoice(vote === 'Yes' ? 'Constitutional' : vote === 'No' ? 'Unconstitutional' : 'Abstain'); setgovActionID(govActionID); if(!votes[0].voting_procedure.anchor) throw new Error("Vote has no anchor."); - setmetadataAnchorURL(votes[0].voting_procedure.anchor.anchor_url); - setMetadataAnchorHash(votes[0].voting_procedure.anchor.anchor_data_hash); + setmetadataAnchorURL(voteMetadataURL); + setMetadataAnchorHash(voteMetadataHash); setCardanoscan(getCardanoScanURL(govActionID,transactionNetworkID)); }