Skip to content

Commit f5ea89e

Browse files
committed
(feat#3745): support ed25519 author signature validation on gov actions
1 parent b7deafb commit f5ea89e

File tree

13 files changed

+345
-17
lines changed

13 files changed

+345
-17
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ changes.
1414

1515
- Preserve maintenance ending banner state on the wallet connection change [Issue 3681](https://github.com/IntersectMBO/govtool/issues/3681)
1616
- Add authors for Live Voting Governance Actions [Issue 3745](https://github.com/IntersectMBO/govtool/issues/3745)
17+
- Add support for ed25519 author signature validation on gov actions [Issue 3745](https://github.com/IntersectMBO/govtool/issues/3745)
1718

1819
### Fixed
1920

govtool/backend/sql/list-proposals.sql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ SELECT
304304
COALESCE(cv.ccAbstainVotes, 0) cc_abstain_votes,
305305
prev_gov_action.index as prev_gov_action_index,
306306
encode(prev_gov_action_tx.hash, 'hex') as prev_gov_action_tx_hash,
307+
off_chain_vote_data.json as json_content,
307308
COALESCE(
308309
json_agg(
309310
json_build_object(
@@ -367,4 +368,5 @@ GROUP BY
367368
off_chain_vote_gov_action_data.title,
368369
off_chain_vote_gov_action_data.abstract,
369370
off_chain_vote_gov_action_data.motivation,
370-
off_chain_vote_gov_action_data.rationale;
371+
off_chain_vote_gov_action_data.rationale,
372+
off_chain_vote_data.json;

govtool/backend/src/VVA/API.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ proposalToResponse timeZone Types.Proposal {..} =
245245
proposalResponseCcAbstainVotes = proposalCcAbstainVotes,
246246
proposalResponsePrevGovActionIndex = proposalPrevGovActionIndex,
247247
proposalResponsePrevGovActionTxHash = HexText <$> proposalPrevGovActionTxHash,
248+
proposalResponseBody = proposalBody,
248249
proposalResponseAuthors = ProposalAuthors <$> proposalAuthors
249250
}
250251

govtool/backend/src/VVA/API/Types.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ data ProposalResponse
401401
, proposalResponseCcAbstainVotes :: Integer
402402
, proposalResponsePrevGovActionIndex :: Maybe Integer
403403
, proposalResponsePrevGovActionTxHash :: Maybe HexText
404+
, proposalResponseJson :: Maybe Value
404405
, proposalResponseAuthors :: Maybe ProposalAuthors
405406
}
406407
deriving (Generic, Show)

govtool/backend/src/VVA/Types.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ data Proposal
208208
, proposalCcAbstainVotes :: Integer
209209
, proposalPrevGovActionIndex :: Maybe Integer
210210
, proposalPrevGovActionTxHash :: Maybe Text
211+
, proposalJson :: Maybe Value
211212
, proposalAuthors :: Maybe Value
212213
}
213214
deriving (Show)
@@ -242,6 +243,7 @@ instance FromRow Proposal where
242243
<*> (floor @Scientific <$> field) -- proposalCcAbstainVotes
243244
<*> field -- prevGovActionIndex
244245
<*> field -- prevGovActionTxHash
246+
<*> field -- proposalJson
245247
<*> field -- proposalAuthors
246248

247249
data TransactionStatus = TransactionStatus

govtool/frontend/package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

govtool/frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@intersect.mbo/pdf-ui": "1.0.1-alfa",
3333
"@mui/icons-material": "^5.14.3",
3434
"@mui/material": "^5.14.4",
35+
"@noble/ed25519": "^2.3.0",
3536
"@rollup/plugin-babel": "^6.0.4",
3637
"@rollup/pluginutils": "^5.1.0",
3738
"@sentry/react": "^7.77.0",

govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx

Lines changed: 89 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { useMemo, useState, Fragment } from "react";
1+
import { useMemo, useState, Fragment, useEffect } from "react";
22
import { Box, Tabs, Tab, styled, Skeleton } from "@mui/material";
33
import { useLocation } from "react-router-dom";
4+
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
5+
import CancelIcon from "@mui/icons-material/Cancel";
46

57
import { CopyButton, ExternalModalButton, Tooltip, Typography } from "@atoms";
68
import {
@@ -23,6 +25,7 @@ import {
2325
getFullGovActionId,
2426
mapArrayToObjectByKeys,
2527
encodeCIP129Identifier,
28+
validateSignature,
2629
} from "@utils";
2730
import { MetadataValidationStatus, ProposalData } from "@models";
2831
import { GovernanceActionType } from "@/types/governanceAction";
@@ -77,6 +80,7 @@ export const GovernanceActionDetailsCardData = ({
7780
proposal: {
7881
abstract,
7982
authors,
83+
json: jsonContent,
8084
createdDate,
8185
createdEpochNo,
8286
details,
@@ -373,21 +377,35 @@ export const GovernanceActionDetailsCardData = ({
373377
? t("govActions.authors.noDataAvailable")
374378
: (authors ?? []).map((author, idx, arr) => (
375379
<Fragment key={author.publicKey}>
376-
<Tooltip
377-
heading={`${t("govActions.authors.witnessAlgorithm")}: ${
378-
author.witnessAlgorithm
379-
}`}
380-
paragraphOne={`${t("govActions.authors.publicKey")}: ${
381-
author.publicKey
382-
}`}
383-
paragraphTwo={`${t("govActions.authors.signature")}: ${
384-
author.signature
385-
}`}
386-
placement="bottom-end"
387-
arrow
380+
<span
381+
style={{
382+
display: "inline-flex",
383+
alignItems: "center",
384+
gap: 2,
385+
}}
388386
>
389-
<span>{author.name}</span>
390-
</Tooltip>
387+
<AuthorSignatureStatus
388+
signature={author.signature}
389+
publicKey={author.publicKey}
390+
algorithm={author.witnessAlgorithm}
391+
jsonContent={jsonContent}
392+
/>
393+
<Tooltip
394+
heading={`${t("govActions.authors.witnessAlgorithm")}: ${
395+
author.witnessAlgorithm
396+
}`}
397+
paragraphOne={`${t("govActions.authors.publicKey")}: ${
398+
author.publicKey
399+
}`}
400+
paragraphTwo={`${t("govActions.authors.signature")}: ${
401+
author.signature
402+
}`}
403+
placement="bottom-end"
404+
arrow
405+
>
406+
<span>{author.name}</span>
407+
</Tooltip>
408+
</span>
391409
{idx < arr.length - 1 && <span>,&nbsp;</span>}
392410
</Fragment>
393411
))
@@ -498,3 +516,59 @@ const HardforkDetailsTabContent = ({
498516
</Box>
499517
);
500518
};
519+
520+
const AuthorSignatureStatus = ({
521+
algorithm,
522+
publicKey,
523+
signature,
524+
jsonContent,
525+
}: {
526+
algorithm?: string;
527+
publicKey?: string;
528+
signature?: string;
529+
jsonContent?: Record<string, unknown>;
530+
}) => {
531+
const { t } = useTranslation();
532+
const [isSignatureValid, setIsSignatureValid] = useState<boolean | null>(
533+
null,
534+
);
535+
536+
useEffect(() => {
537+
let cancelled = false;
538+
async function checkSignature() {
539+
const args = {
540+
jsonContent,
541+
algorithm,
542+
publicKey,
543+
signature,
544+
};
545+
const result = await validateSignature(args);
546+
if (!cancelled) setIsSignatureValid(result);
547+
}
548+
checkSignature();
549+
return () => {
550+
cancelled = true;
551+
};
552+
}, [algorithm, jsonContent, publicKey, signature]);
553+
554+
if (isSignatureValid === null) {
555+
return <Skeleton variant="text" width={16} />;
556+
}
557+
return (
558+
<Tooltip
559+
heading={
560+
isSignatureValid
561+
? t("govActions.authors.singatureVerified")
562+
: t("govActions.authors.signatureNotVerified")
563+
}
564+
placement="bottom-end"
565+
arrow
566+
>
567+
{isSignatureValid ? (
568+
<CheckCircleIcon sx={{ color: "success.main", fontSize: 16 }} />
569+
) : (
570+
<CancelIcon sx={{ color: "error.main", fontSize: 16 }} />
571+
)}
572+
</Tooltip>
573+
);
574+
};

govtool/frontend/src/i18n/locales/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,9 @@
426426
"title": "Authors",
427427
"publicKey": "Public Key",
428428
"signature": "Signature",
429-
"witnessAlgorithm": "Witness Algorithm"
429+
"witnessAlgorithm": "Witness Algorithm",
430+
"singatureVerified": "Author signature is verified",
431+
"signatureNotVerified": "Author signature is not verified"
430432
},
431433
"backToGovActions": "Back to Governance Actions",
432434
"castVote": "<0>You voted {{vote}} on this proposal</0>\non {{date}} (Epoch {{epoch}})",

govtool/frontend/src/models/api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,9 @@ export type ProposalData = {
239239
witnessAlgorithm?: string;
240240
publicKey?: string;
241241
signature?: string;
242+
isSignatureValid?: boolean;
242243
}[];
244+
json?: Record<string, unknown>;
243245
} & SubmittedVotesData;
244246

245247
export type NewConstitutionAnchor = {

0 commit comments

Comments
 (0)