From 1d471170c1df5c9c2da71ccb405f2b99b8b9dae1 Mon Sep 17 00:00:00 2001 From: Jonas Hahn Date: Thu, 24 Oct 2024 23:58:19 +0200 Subject: [PATCH 1/6] Add verify command to verified build tab - Deriving the PDA from the upgrade authority and the program id, then getting the parameter from the PDA and composing it into a solana-verify comand --- app/components/account/VerifiedBuildCard.tsx | 41 +++++++++- .../common/VerifiedProgramBadge.tsx | 2 +- app/utils/verified-builds.tsx | 74 +++++++++++++++++-- 3 files changed, 106 insertions(+), 11 deletions(-) diff --git a/app/components/account/VerifiedBuildCard.tsx b/app/components/account/VerifiedBuildCard.tsx index 1928d518..43da87f4 100644 --- a/app/components/account/VerifiedBuildCard.tsx +++ b/app/components/account/VerifiedBuildCard.tsx @@ -6,11 +6,13 @@ import { ExternalLink } from 'react-feather'; import { OsecRegistryInfo, useVerifiedProgramRegistry } from '@/app/utils/verified-builds'; +import { Copyable } from '../common/Copyable'; import { LoadingCard } from '../common/LoadingCard'; export function VerifiedBuildCard({ data, pubkey }: { data: UpgradeableLoaderAccountData; pubkey: PublicKey }) { const { data: registryInfo, isLoading } = useVerifiedProgramRegistry({ options: { suspense: true }, + programAuthority: data.programData?.authority ? new PublicKey(data.programData.authority) : null, programId: pubkey, }); if (!data.programData) { @@ -50,6 +52,7 @@ enum DisplayType { String, URL, Date, + LongString, } type TableRow = { @@ -84,6 +87,11 @@ const ROWS: TableRow[] = [ key: 'last_verified_at', type: DisplayType.Date, }, + { + display: 'Verify Command', + key: 'verify_command', + type: DisplayType.LongString, + }, { display: 'Repository URL', key: 'repo_url', @@ -100,7 +108,32 @@ function RenderEntry({ value, type }: { value: OsecRegistryInfo[keyof OsecRegist ); case DisplayType.String: - return {value && (value as string).length > 1 ? value : '-'}; + return ( + + {value && (value as string).length > 1 ? value : '-'} + + ); + case DisplayType.LongString: + return ( + + {value && (value as string).length > 1 ? ( + <> + + {value} + + ) : ( + '-' + )} + + ); case DisplayType.URL: if (isValidLink(value as string)) { return ( @@ -120,7 +153,11 @@ function RenderEntry({ value, type }: { value: OsecRegistryInfo[keyof OsecRegist ); case DisplayType.Date: - return {value && (value as string).length > 1 ? new Date(value as string).toUTCString() : '-'}; + return ( + + {value && (value as string).length > 1 ? new Date(value as string).toUTCString() : '-'} + + ); default: break; } diff --git a/app/components/common/VerifiedProgramBadge.tsx b/app/components/common/VerifiedProgramBadge.tsx index f683bfae..89ab2799 100644 --- a/app/components/common/VerifiedProgramBadge.tsx +++ b/app/components/common/VerifiedProgramBadge.tsx @@ -12,7 +12,7 @@ export function VerifiedProgramBadge({ programData: ProgramDataAccountInfo; pubkey: PublicKey; }) { - const { isLoading, data: registryInfo } = useVerifiedProgramRegistry({ programId: pubkey }); + const { isLoading, data: registryInfo } = useVerifiedProgramRegistry({ programAuthority: programData.authority ? new PublicKey(programData.authority) : null, programId: pubkey }); const verifiedBuildTabPath = useClusterPath({ pathname: `/address/${pubkey.toBase58()}/verified-build` }); const hash = hashProgramData(programData); diff --git a/app/utils/verified-builds.tsx b/app/utils/verified-builds.tsx index 1eb84958..8781f3c5 100644 --- a/app/utils/verified-builds.tsx +++ b/app/utils/verified-builds.tsx @@ -1,10 +1,13 @@ import { sha256 } from '@noble/hashes/sha256'; -import { PublicKey } from '@solana/web3.js'; +import { Connection, PublicKey } from '@solana/web3.js'; import useSWRImmutable from 'swr/immutable'; +import { useAnchorProgram } from '../providers/anchor'; +import { useCluster } from '../providers/cluster'; import { ProgramDataAccountInfo } from '../validators/accounts/upgradeable-program'; const OSEC_REGISTRY_URL = 'https://verify.osec.io'; +const VERIFY_PROGRAM_ID = 'verifycLy8mB96wd9wqq3WDXQwM4oU6r42Th37Db9fC'; export type OsecRegistryInfo = { is_verified: boolean; @@ -13,29 +16,84 @@ export type OsecRegistryInfo = { executable_hash: string; last_verified_at: string | null; repo_url: string; -}; - -export type CheckedOsecRegistryInfo = { - explorer_hash: string; + verify_command: string; }; export function useVerifiedProgramRegistry({ programId, + programAuthority, options, }: { programId: PublicKey; + programAuthority: PublicKey | null; options?: { suspense: boolean }; }) { - const { data, error, isLoading } = useSWRImmutable( + const { url: clusterUrl } = useCluster(); + const connection = new Connection(clusterUrl); + + const { + data: registryData, + error: registryError, + isLoading: isRegistryLoading, + } = useSWRImmutable( `${programId.toBase58()}`, async (programId: string) => { - return fetch(`${OSEC_REGISTRY_URL}/status/${programId}`).then(response => response.json()); + const response = await fetch(`${OSEC_REGISTRY_URL}/status/${programId}`); + return response.json(); + }, + { suspense: options?.suspense } + ); + + const { program: accountAnchorProgram } = useAnchorProgram(VERIFY_PROGRAM_ID, connection.rpcEndpoint); + + // Fetch the PDA derived from the program upgrade authority + // TODO: Add getting verifier pubkey from the security.txt as second option once implemented + const { + data: pdaData, + error: pdaError, + isLoading: isPdaLoading, + } = useSWRImmutable( + programAuthority && accountAnchorProgram ? `pda-${programId.toBase58()}` : null, + async () => { + const [pda] = PublicKey.findProgramAddressSync( + [Buffer.from('otter_verify'), programAuthority!.toBuffer(), programId.toBuffer()], + new PublicKey(VERIFY_PROGRAM_ID) + ); + const pdaAccountInfo = await connection.getAccountInfo(pda); + if (!pdaAccountInfo || !pdaAccountInfo.data) { + throw new Error('PDA account info not found'); + } + return accountAnchorProgram.coder.accounts.decode('buildParams', pdaAccountInfo.data); }, { suspense: options?.suspense } ); - return { data: error ? null : (data as OsecRegistryInfo), isLoading }; + + const isLoading = isRegistryLoading || isPdaLoading; + + if (registryError || pdaError) { + return { data: null, error: registryError || pdaError, isLoading }; + } + + // Create command from the args of the verify PDA + if (registryData && pdaData && !isLoading) { + const verifiedData = registryData as OsecRegistryInfo; + verifiedData.verify_command = `solana-verify verify-from-repo -um --program-id ${programId.toBase58()} ${ + pdaData.gitUrl + } --commit-hash ${pdaData.commit}`; + + // Add additional args if available, for example mount-path and --library-name + if (pdaData.args && pdaData.args.length > 0) { + const argsString = pdaData.args.join(' '); + verifiedData.verify_command += ` ${argsString}`; + } + + return { data: verifiedData, isLoading }; + } + + return { data: null, isLoading }; } +// Helper function to hash program data export function hashProgramData(programData: ProgramDataAccountInfo): string { const buffer = Buffer.from(programData.data[0], 'base64'); // Truncate null bytes at the end of the buffer From b8dfb68f20363992c8946142ba6087fb63f1a2dc Mon Sep 17 00:00:00 2001 From: Jonas Hahn Date: Fri, 25 Oct 2024 00:12:55 +0200 Subject: [PATCH 2/6] Make sure the page still loads when there is no verify PDA --- app/utils/verified-builds.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/utils/verified-builds.tsx b/app/utils/verified-builds.tsx index 8781f3c5..67276c6e 100644 --- a/app/utils/verified-builds.tsx +++ b/app/utils/verified-builds.tsx @@ -61,7 +61,8 @@ export function useVerifiedProgramRegistry({ ); const pdaAccountInfo = await connection.getAccountInfo(pda); if (!pdaAccountInfo || !pdaAccountInfo.data) { - throw new Error('PDA account info not found'); + console.log('PDA account info not found'); + return null; } return accountAnchorProgram.coder.accounts.decode('buildParams', pdaAccountInfo.data); }, @@ -89,6 +90,11 @@ export function useVerifiedProgramRegistry({ return { data: verifiedData, isLoading }; } + if (registryData && pdaData == null && !isLoading) { + const verifiedData = registryData as OsecRegistryInfo; + verifiedData.verify_command = 'Program does not have a verify PDA uploaded.'; + return { data: verifiedData, isLoading }; + } return { data: null, isLoading }; } From 51b72b836ffd228c3a39db822fce3e6ceaafdf96 Mon Sep 17 00:00:00 2001 From: Jonas Hahn Date: Fri, 25 Oct 2024 13:43:05 +0200 Subject: [PATCH 3/6] Add mainnet check --- app/utils/verified-builds.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/utils/verified-builds.tsx b/app/utils/verified-builds.tsx index 67276c6e..192adf4b 100644 --- a/app/utils/verified-builds.tsx +++ b/app/utils/verified-builds.tsx @@ -5,6 +5,7 @@ import useSWRImmutable from 'swr/immutable'; import { useAnchorProgram } from '../providers/anchor'; import { useCluster } from '../providers/cluster'; import { ProgramDataAccountInfo } from '../validators/accounts/upgradeable-program'; +import { Cluster } from './cluster'; const OSEC_REGISTRY_URL = 'https://verify.osec.io'; const VERIFY_PROGRAM_ID = 'verifycLy8mB96wd9wqq3WDXQwM4oU6r42Th37Db9fC'; @@ -28,7 +29,7 @@ export function useVerifiedProgramRegistry({ programAuthority: PublicKey | null; options?: { suspense: boolean }; }) { - const { url: clusterUrl } = useCluster(); + const { url: clusterUrl, cluster: cluster } = useCluster(); const connection = new Connection(clusterUrl); const { @@ -92,13 +93,20 @@ export function useVerifiedProgramRegistry({ } if (registryData && pdaData == null && !isLoading) { const verifiedData = registryData as OsecRegistryInfo; - verifiedData.verify_command = 'Program does not have a verify PDA uploaded.'; + + verifiedData.verify_command = isMainnet(cluster) + ? 'Program does not have a verify PDA uploaded.' + : 'Verify command only available on mainnet.'; return { data: verifiedData, isLoading }; } return { data: null, isLoading }; } +function isMainnet(currentCluster: Cluster): boolean { + return currentCluster == Cluster.MainnetBeta; +} + // Helper function to hash program data export function hashProgramData(programData: ProgramDataAccountInfo): string { const buffer = Buffer.from(programData.data[0], 'base64'); From 7c6b3ecf27ae28f052b19e52019a444ef4927542 Mon Sep 17 00:00:00 2001 From: Jonas Hahn Date: Tue, 29 Oct 2024 15:29:29 +0100 Subject: [PATCH 4/6] Update verified-builds.tsx --- app/utils/verified-builds.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/utils/verified-builds.tsx b/app/utils/verified-builds.tsx index 192adf4b..d4d5540c 100644 --- a/app/utils/verified-builds.tsx +++ b/app/utils/verified-builds.tsx @@ -65,7 +65,7 @@ export function useVerifiedProgramRegistry({ console.log('PDA account info not found'); return null; } - return accountAnchorProgram.coder.accounts.decode('buildParams', pdaAccountInfo.data); + return accountAnchorProgram?.coder.accounts.decode('buildParams', pdaAccountInfo.data); }, { suspense: options?.suspense } ); From 9bac59bfee83eaeeb61e353fb1e4234269840668 Mon Sep 17 00:00:00 2001 From: Jonas Hahn Date: Tue, 29 Oct 2024 16:06:51 +0100 Subject: [PATCH 5/6] Remove -b flag and add link to docs --- app/components/account/VerifiedBuildCard.tsx | 20 ++++++++++++++++++++ app/utils/verified-builds.tsx | 18 ++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/app/components/account/VerifiedBuildCard.tsx b/app/components/account/VerifiedBuildCard.tsx index 43da87f4..4838205e 100644 --- a/app/components/account/VerifiedBuildCard.tsx +++ b/app/components/account/VerifiedBuildCard.tsx @@ -33,6 +33,26 @@ export function VerifiedBuildCard({ data, pubkey }: { data: UpgradeableLoaderAcc

Verified Build

Information provided by osec.io +
+ Note: Verified builds indicate that the onchain build was built from the source code + that is publicly available, but this does not imply a security audit. For more details, refer to the{' '} + + Verified Builds Docs + + . +
{ROWS.filter(x => x.key in registryInfo).map((x, idx) => { return ( diff --git a/app/utils/verified-builds.tsx b/app/utils/verified-builds.tsx index d4d5540c..b8ba4ada 100644 --- a/app/utils/verified-builds.tsx +++ b/app/utils/verified-builds.tsx @@ -85,8 +85,22 @@ export function useVerifiedProgramRegistry({ // Add additional args if available, for example mount-path and --library-name if (pdaData.args && pdaData.args.length > 0) { - const argsString = pdaData.args.join(' '); - verifiedData.verify_command += ` ${argsString}`; + const filteredArgs = []; + + for (let i = 0; i < pdaData.args.length; i++) { + const arg = pdaData.args[i]; + + if (arg === '-b' || arg === '--base-image') { + i++; // Also skip the parameter + continue; + } + filteredArgs.push(arg); + } + + if (filteredArgs.length > 0) { + const argsString = filteredArgs.join(' '); + verifiedData.verify_command += ` ${argsString}`; + } } return { data: verifiedData, isLoading }; From de66cf80c2d0550f2d2b7f28119dba3bfeb62076 Mon Sep 17 00:00:00 2001 From: Noah Gundotra Date: Wed, 30 Oct 2024 14:52:07 -0400 Subject: [PATCH 6/6] remove note styling --- app/components/account/VerifiedBuildCard.tsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/app/components/account/VerifiedBuildCard.tsx b/app/components/account/VerifiedBuildCard.tsx index 4838205e..a752c251 100644 --- a/app/components/account/VerifiedBuildCard.tsx +++ b/app/components/account/VerifiedBuildCard.tsx @@ -33,17 +33,9 @@ export function VerifiedBuildCard({ data, pubkey }: { data: UpgradeableLoaderAcc

Verified Build

Information provided by osec.io -
- Note: Verified builds indicate that the onchain build was built from the source code - that is publicly available, but this does not imply a security audit. For more details, refer to the{' '} +
+ Verified builds indicate that the onchain build was built from the source code that is publicly + available, but this does not imply a security audit. For more details, refer to the{' '}