Skip to content

Commit

Permalink
feat(refactor): Use merkelise-metadata for Ledger signing, disconti…
Browse files Browse the repository at this point in the history
…nue metadata service (#2195)
  • Loading branch information
rossbulat authored Jul 29, 2024
1 parent e2a6829 commit 907ea60
Show file tree
Hide file tree
Showing 15 changed files with 849 additions and 736 deletions.
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@
"@fortawesome/free-regular-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@ledgerhq/hw-transport-webhid": "^6.29.0",
"@polkadot/api": "^12.0.2",
"@polkadot/keyring": "^12.6.2",
"@ledgerhq/hw-transport-webhid": "^6.29.2",
"@polkadot-api/merkleize-metadata": "^1.1.2",
"@polkadot/api": "^12.2.2",
"@polkadot/keyring": "^13.0.2",
"@polkadot/rpc-provider": "10.11.2",
"@polkadot/util": "^12.6.2",
"@polkadot/util-crypto": "^12.6.2",
"@polkadot/util": "^13.0.2",
"@polkadot/util-crypto": "^13.0.2",
"@polkawatch/ddp-client": "^2.0.16",
"@substrate/connect": "0.7.35",
"@w3ux/extension-assets": "0.3.1",
Expand All @@ -36,7 +37,7 @@
"@w3ux/react-polkicon": "1.2.0",
"@w3ux/utils": "^0.4.0",
"@w3ux/validator-assets": "^0.2.0",
"@zondax/ledger-substrate": "^0.44.5",
"@zondax/ledger-substrate": "^0.44.7",
"bignumber.js": "^9.1.2",
"bn.js": "^5.2.1",
"buffer": "^6.0.3",
Expand Down
2 changes: 1 addition & 1 deletion src/contexts/LedgerHardware/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const defaultLedgerHardwareContext: LedgerHardwareContextInterface = {
handleErrors: (err) => {},
handleGetAddress: (txMetadataChainId, accountIndex, ss58Prefix) =>
new Promise((resolve) => resolve()),
handleSignTx: (txMetadataChainId, uid, index, payload) =>
handleSignTx: (txMetadataChainId, uid, index, payload, txMetadata) =>
new Promise((resolve) => resolve()),
handleResetLedgerTask: () => {},
runtimesInconsistent: false,
Expand Down
5 changes: 3 additions & 2 deletions src/contexts/LedgerHardware/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,15 @@ export const LedgerHardwareProvider = ({
txMetadataChainId: string,
uid: number,
index: number,
payload: AnyJson
payload: AnyJson,
txMetadata: AnyJson
) => {
try {
setIsExecuting(true);
const { app, productName } = await Ledger.initialise(txMetadataChainId);
setFeedback(t('approveTransactionLedger'));

const result = await Ledger.signPayload(app, index, payload);
const result = await Ledger.signPayload(app, index, payload, txMetadata);

setIsExecuting(false);
setFeedback(t('signedTransactionSuccessfully'));
Expand Down
18 changes: 11 additions & 7 deletions src/contexts/LedgerHardware/static/ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@ export class Ledger {
// Initialise ledger transport, initialise app, and return with device info.
static initialise = async (txMetadataChainId: string) => {
this.transport = await TransportWebHID.create();
const app = new PolkadotGenericApp(
Ledger.transport,
txMetadataChainId,
'https://api.zondax.ch/polkadot/transaction/metadata'
);
const app = new PolkadotGenericApp(Ledger.transport, txMetadataChainId);
const { productName } = this.transport.device;
return { app, productName };
};
Expand Down Expand Up @@ -74,12 +70,20 @@ export class Ledger {
static signPayload = async (
app: PolkadotGenericApp,
index: number,
payload: AnyJson
payload: AnyJson,
txMetadata: AnyJson
) => {
await this.ensureOpen();

const bip42Path = `m/44'/354'/${index}'/${0}'/${0}'`;
const result = await app.sign(bip42Path, Buffer.from(payload.toU8a(true)));
const buff = Buffer.from(txMetadata);

const result = await app.signWithMetadata(
bip42Path,
payload.toU8a(true),
buff
);

await this.ensureClosed();
return result;
};
Expand Down
3 changes: 2 additions & 1 deletion src/contexts/LedgerHardware/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export interface LedgerHardwareContextInterface {
txMetadataChainId: string,
uid: number,
index: number,
payload: AnyJson
payload: AnyJson,
txMetadata: AnyJson
) => Promise<void>;
handleResetLedgerTask: () => void;
}
Expand Down
2 changes: 1 addition & 1 deletion src/contexts/TxMeta/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export const defaultTxMeta: TxMetaContextInterface = {
resetTxFees: () => {},
notEnoughFunds: false,
getPayloadUid: () => 0,
getTxMetadata: () => {},
getTxPayload: () => {},
getTxPayloadValue: () => {},
setTxPayload: (payload, payloadValue, uid) => {},
incrementPayloadUid: () => 0,
resetTxPayload: () => {},
Expand Down
12 changes: 6 additions & 6 deletions src/contexts/TxMeta/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ export const TxMetaProvider = ({ children }: { children: ReactNode }) => {
// reason we give every payload a uid, and check whether this uid matches the active extrinsic
// before submitting it.
const [txPayload, setTxPayloadState] = useState<{
payload: AnyJson;
txMetadata: AnyJson;
payloadValue: AnyJson;
uid: number;
} | null>(null);
const txPayloadRef = useRef(txPayload);
const getPayloadUid = () => txPayloadRef.current?.uid || 1;
const getTxPayload = () => txPayloadRef.current?.payload || null;
const getTxPayloadValue = () => txPayloadRef.current?.payloadValue || null;
const getTxMetadata = () => txPayloadRef.current?.txMetadata || null;
const getTxPayload = () => txPayloadRef.current?.payloadValue || null;

// Store an optional signed transaction if extrinsics require manual signing (e.g. Ledger).
const [txSignature, setTxSignatureState] = useState<AnyJson>(null);
Expand All @@ -77,13 +77,13 @@ export const TxMetaProvider = ({ children }: { children: ReactNode }) => {

// Set the transaction payload and uid. Overwrites any existing payload.
const setTxPayload = (
payload: AnyJson,
txMetadata: AnyJson,
payloadValue: AnyJson,
uid: number
) => {
setStateWithRef(
{
payload,
txMetadata,
payloadValue,
uid,
},
Expand Down Expand Up @@ -171,8 +171,8 @@ export const TxMetaProvider = ({ children }: { children: ReactNode }) => {
resetTxFees,
notEnoughFunds,
getPayloadUid,
getTxMetadata,
getTxPayload,
getTxPayloadValue,
setTxPayload,
incrementPayloadUid,
resetTxPayload,
Expand Down
2 changes: 1 addition & 1 deletion src/contexts/TxMeta/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export interface TxMetaContextInterface {
resetTxFees: () => void;
notEnoughFunds: boolean;
getPayloadUid: () => number;
getTxMetadata: () => AnyJson;
getTxPayload: () => AnyJson;
getTxPayloadValue: () => AnyJson;
setTxPayload: (payload: AnyJson, payloadValue: AnyJson, uid: number) => void;
incrementPayloadUid: () => number;
resetTxPayload: () => void;
Expand Down
76 changes: 44 additions & 32 deletions src/hooks/useBuildPayload/index.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,46 @@
// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors
// SPDX-License-Identifier: GPL-3.0-only

import { merkleizeMetadata } from '@polkadot-api/merkleize-metadata';
import type { ApiPromise } from '@polkadot/api';
import { objectSpread, u8aToHex } from '@polkadot/util';
import type { AnyJson } from '@w3ux/types';
import { ZondaxMetadataHashApiUrl } from 'consts';
import { useApi } from 'contexts/Api';
import { useBalances } from 'contexts/Balances';
import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts';
import { useNetwork } from 'contexts/Network';
import { useTxMeta } from 'contexts/TxMeta';
import type { AnyApi } from 'types';

export const useBuildPayload = () => {
const { api } = useApi();
const { getNonce } = useBalances();
const {
networkData: { unit },
} = useNetwork();
const { getAccount } = useImportedAccounts();

const { setTxPayload } = useTxMeta();
const { getAccount } = useImportedAccounts();

// Request a metadata hash from Zondax API service.
const fetchMetadataHash = async () => {
const requestMetadataHash = await fetch(ZondaxMetadataHashApiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ id: unit.toLowerCase() }),
});
const fetchMetadataHash = async (a: ApiPromise, p: AnyJson) => {
const metadata = await a.call.metadata.metadataAtVersion(15);
const { specName, specVersion } = a.runtimeVersion;

const opts = {
base58Prefix: (a.consts.system.ss58Prefix as AnyApi).toNumber(),
decimals: a.registry.chainDecimals[0],
specName: specName.toString(),
specVersion: specVersion.toNumber(),
tokenSymbol: a.registry.chainTokens[0],
};

const response = await requestMetadataHash.json();
const merkleizedMetadata = merkleizeMetadata(metadata.toHex(), opts);
const metadataHash = u8aToHex(merkleizedMetadata.digest());
const payload = objectSpread({}, p, { metadataHash, mode: 1 });
const newPayload = a.registry.createType('ExtrinsicPayload', payload);

return `0x${response.metadataHash}`;
return {
newPayload,
newTxMetadata: merkleizedMetadata.getProofForExtrinsicPayload(
u8aToHex(newPayload.toU8a(true))
),
};
};

// Build and set payload of the transaction and store it in TxMetaContext.
Expand All @@ -56,9 +64,11 @@ export const useBuildPayload = () => {
const nonce = api.registry.createType('Compact<Index>', accountNonce);

// Construct the payload value.
const payload: AnyJson = {
const payloadJson: AnyJson = {
specVersion: api.runtimeVersion.specVersion.toHex(),
transactionVersion: api.runtimeVersion.transactionVersion.toHex(),
runtimeVersion: api.runtimeVersion,
version: api.extrinsicVersion,
address: from,
blockHash: lastHeader.hash.toHex(),
blockNumber: blockNumber.toHex(),
Expand All @@ -68,28 +78,30 @@ export const useBuildPayload = () => {
nonce: nonce.toHex(),
signedExtensions: api.registry.signedExtensions,
tip: api.registry.createType('Compact<Balance>', 0).toHex(),
version: tx.version,
withSignedTransaction: true,
};

let payload;
let txMetadata = null;

// If the source is `ledger`, add the metadata hash to the payload.
if (source === 'ledger') {
const metadataHash = await fetchMetadataHash();
payload.mode = 1;
payload.metadataHash = metadataHash;
const { newPayload, newTxMetadata } = await fetchMetadataHash(
api,
payloadJson
);
payload = newPayload;
txMetadata = newTxMetadata;
} else {
// Create the payload raw.
payload = api.registry.createType('ExtrinsicPayload', payload, {
version: payloadJson.version,
});
txMetadata = null;
}

// Create the payload bytes.
const payloadBytes = api.registry.createType(
'ExtrinsicPayload',
payload,
{
version: payload.version,
}
);

// Persist both the payload and the payload bytes in state, indexed by its uid.
setTxPayload(payloadBytes, payload, uid);
setTxPayload(txMetadata, payload, uid);
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useNominationStatus/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const useNominationStatus = () => {
const nominations =
type === 'nominator'
? getNominations(who)
: activePoolNominations?.targets ?? [];
: (activePoolNominations?.targets ?? []);

return getNominationsStatusFromTargets(who, nominations);
};
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useSubmitExtrinsic/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const useSubmitExtrinsic = ({
txFees,
setTxFees,
setSender,
getTxPayloadValue,
getTxPayload,
getTxSignature,
setTxSignature,
resetTxPayload,
Expand Down Expand Up @@ -229,7 +229,7 @@ export const useSubmitExtrinsic = ({
// pre-submission state update
setSubmitting(true);

const txPayloadValue = getTxPayloadValue();
const txPayloadValue = getTxPayload();
const txSignature = getTxSignature();

// handle signed transaction.
Expand All @@ -238,7 +238,7 @@ export const useSubmitExtrinsic = ({
txRef.current.addSignature(
fromRef.current,
txSignature,
txPayloadValue
txPayloadValue.toHex()
);

const unsub = await txRef.current.send(
Expand Down
4 changes: 3 additions & 1 deletion src/library/Account/PoolAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ const PoolAccount = ({ label, pool, syncing }: PoolAccountProps) => {
// Default display text value.
const defaultDisplay = ellipsisFn(pool.addresses.stash);

let text = syncing ? t('syncing') : poolsMetaData[pool.id] ?? defaultDisplay;
let text = syncing
? t('syncing')
: (poolsMetaData[pool.id] ?? defaultDisplay);

// Check if super identity has been byte encoded.
const displayAsBytes = u8aToString(u8aUnwrapBytes(text));
Expand Down
12 changes: 10 additions & 2 deletions src/library/SubmitTx/ManualSign/Ledger/Submit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const Submit = ({
const { getTxSignature } = useTxMeta();
const { getAccount } = useImportedAccounts();
const { activeAccount } = useActiveAccounts();
const { getTxPayload, getPayloadUid } = useTxMeta();
const { getTxMetadata, getTxPayload, getPayloadUid } = useTxMeta();
const { txMetadataChainId } = getLedgerApp(network);

const getAddressIndex = () =>
Expand All @@ -43,8 +43,16 @@ export const Submit = ({
const handleTxSubmit = async () => {
const uid = getPayloadUid();
const accountIndex = getAddressIndex();
const txMetadata = await getTxMetadata();
const payload = await getTxPayload();
await handleSignTx(txMetadataChainId, uid, accountIndex, payload);

await handleSignTx(
txMetadataChainId,
uid,
accountIndex,
payload,
txMetadata
);
};

// Check device runtime version.
Expand Down
1 change: 1 addition & 0 deletions src/library/SubmitTx/ManualSign/Vault/SignPrompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ButtonSecondary } from 'kits/Buttons/ButtonSecondary';
export const SignPrompt = ({ submitAddress }: SignerPromptProps) => {
const { t } = useTranslation('library');
const { getTxPayload, setTxSignature } = useTxMeta();

const payload = getTxPayload();
const payloadU8a = payload?.toU8a();
const { closePrompt } = usePrompt();
Expand Down
Loading

0 comments on commit 907ea60

Please sign in to comment.