Skip to content

Commit 661580b

Browse files
authored
Merge pull request #3801 from IntersectMBO/3745-display-authors-for-live-voting-governance-actions: support ed25519 author signature validation on gov actions
(feat#3745): support ed25519 author signature validation on gov actions
2 parents 199568c + 25584c4 commit 661580b

File tree

13 files changed

+353
-24
lines changed

13 files changed

+353
-24
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+
proposalResponseJson = proposalJson,
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.3-beta",
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: 97 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import { useMemo, useState, Fragment } from "react";
1+
import { useMemo, useState, useEffect } from "react";
22
import { Box, Tabs, Tab, styled, Skeleton } from "@mui/material";
33
import { useLocation } from "react-router-dom";
4+
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
5+
import CancelOutlinedIcon from "@mui/icons-material/CancelOutlined";
6+
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
47

58
import { CopyButton, ExternalModalButton, Tooltip, Typography } from "@atoms";
69
import {
@@ -23,8 +26,10 @@ import {
2326
getFullGovActionId,
2427
mapArrayToObjectByKeys,
2528
encodeCIP129Identifier,
29+
validateSignature,
2630
} from "@utils";
2731
import { MetadataValidationStatus, ProposalData } from "@models";
32+
import { errorRed, successGreen } from "@/consts";
2833
import { GovernanceActionType } from "@/types/governanceAction";
2934
import { useAppContext } from "@/context";
3035

@@ -77,6 +82,7 @@ export const GovernanceActionDetailsCardData = ({
7782
proposal: {
7883
abstract,
7984
authors,
85+
json: jsonContent,
8086
createdDate,
8187
createdEpochNo,
8288
details,
@@ -369,28 +375,39 @@ export const GovernanceActionDetailsCardData = ({
369375
<GovernanceActionCardElement
370376
label={t("govActions.authors.title")}
371377
text={
372-
(authors ?? []).length <= 0
373-
? t("govActions.authors.noDataAvailable")
374-
: (authors ?? []).map((author, idx, arr) => (
375-
<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
378+
<Box sx={{ display: "flex", gap: 2, flexWrap: "wrap" }}>
379+
{(authors ?? []).length <= 0
380+
? t("govActions.authors.noDataAvailable")
381+
: (authors ?? []).map((author) => (
382+
<Box
383+
key={author.publicKey}
384+
sx={{ display: "flex", gap: 0.5, alignItems: "center" }}
388385
>
386+
<AuthorSignatureStatus
387+
signature={author.signature}
388+
publicKey={author.publicKey}
389+
algorithm={author.witnessAlgorithm}
390+
jsonContent={jsonContent}
391+
/>
389392
<span>{author.name}</span>
390-
</Tooltip>
391-
{idx < arr.length - 1 && <span>,&nbsp;</span>}
392-
</Fragment>
393-
))
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+
<InfoOutlinedIcon fontSize="small" />
407+
</Tooltip>
408+
</Box>
409+
))}
410+
</Box>
394411
}
395412
textVariant="longText"
396413
dataTestId="authors"
@@ -498,3 +515,62 @@ const HardforkDetailsTabContent = ({
498515
</Box>
499516
);
500517
};
518+
519+
const AuthorSignatureStatus = ({
520+
algorithm,
521+
publicKey,
522+
signature,
523+
jsonContent,
524+
}: {
525+
algorithm?: string;
526+
publicKey?: string;
527+
signature?: string;
528+
jsonContent?: Record<string, unknown>;
529+
}) => {
530+
const { t } = useTranslation();
531+
const [isSignatureValid, setIsSignatureValid] = useState<boolean | null>(
532+
null,
533+
);
534+
535+
useEffect(() => {
536+
let cancelled = false;
537+
async function checkSignature() {
538+
const args = {
539+
jsonContent,
540+
algorithm,
541+
publicKey,
542+
signature,
543+
};
544+
const result = await validateSignature(args);
545+
if (!cancelled) setIsSignatureValid(result);
546+
}
547+
checkSignature();
548+
return () => {
549+
cancelled = true;
550+
};
551+
}, [algorithm, jsonContent, publicKey, signature]);
552+
553+
if (isSignatureValid === null) {
554+
return <Skeleton variant="text" width={16} />;
555+
}
556+
return (
557+
<Tooltip
558+
heading={
559+
isSignatureValid
560+
? t("govActions.authors.singatureVerified")
561+
: t("govActions.authors.signatureNotVerified")
562+
}
563+
placement="bottom-end"
564+
arrow
565+
>
566+
{isSignatureValid ? (
567+
<CheckCircleOutlineIcon
568+
sx={{ color: successGreen.c500 }}
569+
fontSize="small"
570+
/>
571+
) : (
572+
<CancelOutlinedIcon sx={{ color: errorRed.c500 }} fontSize="small" />
573+
)}
574+
</Tooltip>
575+
);
576+
};

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -423,10 +423,12 @@
423423
"anchorHash": "Metadata anchor hash",
424424
"authors": {
425425
"noDataAvailable": "No data available",
426-
"title": "Authors",
426+
"title": "Author(s)",
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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ export type ProposalData = {
240240
publicKey?: string;
241241
signature?: string;
242242
}[];
243+
json?: Record<string, unknown>;
243244
} & SubmittedVotesData;
244245

245246
export type NewConstitutionAnchor = {

0 commit comments

Comments
 (0)