Ethereum Ledger signing (#1542)
* [TESTING] Ecdsa signing * remove hardcoding * Remove consoles * Fix import of exdsa accounts from ledger * Add signing * Add correct keypair type to hardware meta * lint * Update ui libs to 3.14.1 * Most recent changes * Detect chain type and hash message accordingly * testing logs * Only use pubkey * Make Ethereum signing work * lint * Remove rogue console.logs * Change to isHex() --------- Co-authored-by: Valentin Fernandez <tinchofernandez8@gmail.com>
This commit is contained in:
parent
592b63f60f
commit
a154f9bd54
@ -64,8 +64,8 @@ export default class Extension {
|
||||
return true;
|
||||
}
|
||||
|
||||
private accountsCreateHardware ({ accountIndex, address, addressOffset, genesisHash, hardwareType, name }: RequestAccountCreateHardware): boolean {
|
||||
keyring.addHardware(address, hardwareType, { accountIndex, addressOffset, genesisHash, name });
|
||||
private accountsCreateHardware ({ accountIndex, address, addressOffset, genesisHash, hardwareType, name, type }: RequestAccountCreateHardware): boolean {
|
||||
keyring.addHardware(address, hardwareType, { accountIndex, addressOffset, genesisHash, name, type });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -204,6 +204,7 @@ export interface RequestAccountCreateHardware {
|
||||
genesisHash: HexString;
|
||||
hardwareType: string;
|
||||
name: string;
|
||||
type: KeypairType;
|
||||
}
|
||||
|
||||
export interface RequestAccountChangePassword {
|
||||
|
||||
@ -9,6 +9,7 @@ export interface Chain {
|
||||
genesisHash?: string;
|
||||
hasMetadata: boolean;
|
||||
icon: string;
|
||||
isEthereum?: boolean;
|
||||
isUnknown?: boolean;
|
||||
name: string;
|
||||
registry: Registry;
|
||||
|
||||
@ -9,7 +9,7 @@ import React, { useCallback, useContext, useEffect, useRef, useState } from 'rea
|
||||
|
||||
import { settings } from '@polkadot/ui-settings';
|
||||
|
||||
import { ActionContext, Address, Button, ButtonArea, Dropdown, VerticalSpace, Warning } from '../components/index.js';
|
||||
import { ActionContext, Address, Button, ButtonArea, Dropdown, Switch, VerticalSpace, Warning } from '../components/index.js';
|
||||
import { useLedger, useTranslation } from '../hooks/index.js';
|
||||
import { createAccountHardware } from '../messaging.js';
|
||||
import { Header, Name } from '../partials/index.js';
|
||||
@ -39,9 +39,10 @@ function ImportLedger ({ className }: Props): React.ReactElement {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isBusy, setIsBusy] = useState(false);
|
||||
const [genesis, setGenesis] = useState<HexString | null>(null);
|
||||
const [isEthereum, setIsEthereum] = useState(false);
|
||||
const onAction = useContext(ActionContext);
|
||||
const [name, setName] = useState<string | null>(null);
|
||||
const { address, error: ledgerError, isLoading: ledgerLoading, isLocked: ledgerLocked, refresh, warning: ledgerWarning } = useLedger(genesis, accountIndex, addressOffset);
|
||||
const { address, error: ledgerError, isLoading: ledgerLoading, isLocked: ledgerLocked, refresh, type, warning: ledgerWarning } = useLedger(genesis, accountIndex, addressOffset, isEthereum);
|
||||
|
||||
useEffect(() => {
|
||||
if (address) {
|
||||
@ -72,10 +73,10 @@ function ImportLedger ({ className }: Props): React.ReactElement {
|
||||
|
||||
const _onSave = useCallback(
|
||||
() => {
|
||||
if (address && genesis && name) {
|
||||
if (address && genesis && name && type) {
|
||||
setIsBusy(true);
|
||||
|
||||
createAccountHardware(address, 'ledger', accountIndex, addressOffset, name, genesis)
|
||||
createAccountHardware(address, 'ledger', accountIndex, addressOffset, name, genesis, type)
|
||||
.then(() => onAction('/'))
|
||||
.catch((error: Error) => {
|
||||
console.error(error);
|
||||
@ -85,7 +86,7 @@ function ImportLedger ({ className }: Props): React.ReactElement {
|
||||
});
|
||||
}
|
||||
},
|
||||
[accountIndex, address, addressOffset, genesis, name, onAction]
|
||||
[accountIndex, address, addressOffset, genesis, name, onAction, type]
|
||||
);
|
||||
|
||||
// select element is returning a string
|
||||
@ -105,7 +106,16 @@ function ImportLedger ({ className }: Props): React.ReactElement {
|
||||
isExternal
|
||||
isHardware
|
||||
name={name}
|
||||
type={type ?? undefined}
|
||||
/>
|
||||
<div className='ethereum-toggle'>
|
||||
<Switch
|
||||
checked={isEthereum}
|
||||
checkedLabel={t('Ethereum Account')}
|
||||
onChange={setIsEthereum}
|
||||
uncheckedLabel={t('ED25519 Account')}
|
||||
/>
|
||||
</div>
|
||||
<Dropdown
|
||||
className='network'
|
||||
label={t('Network')}
|
||||
@ -137,6 +147,7 @@ function ImportLedger ({ className }: Props): React.ReactElement {
|
||||
options={addOps.current}
|
||||
value={addressOffset}
|
||||
/>
|
||||
|
||||
</>
|
||||
)}
|
||||
{!!ledgerWarning && (
|
||||
@ -183,4 +194,8 @@ export default styled(ImportLedger)<Props>`
|
||||
.refreshIcon {
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
|
||||
.ethereum-toggle {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
`;
|
||||
|
||||
@ -27,6 +27,7 @@ interface Props {
|
||||
className?: string;
|
||||
error: string | null;
|
||||
genesisHash?: string;
|
||||
isEthereum?: boolean;
|
||||
onSignature?: ({ signature }: { signature: HexString }, signedTransaction?: HexString) => void;
|
||||
payloadJson?: SignerPayloadJSON;
|
||||
payloadExt?: ExtrinsicPayload
|
||||
@ -55,11 +56,11 @@ function getMetadataProof (chain: Chain, payload: SignerPayloadJSON) {
|
||||
};
|
||||
}
|
||||
|
||||
function LedgerSign ({ accountIndex, addressOffset, className, error, genesisHash, onSignature, payloadExt, payloadJson, setError }: Props): React.ReactElement<Props> {
|
||||
function LedgerSign ({ accountIndex, addressOffset, className, error, genesisHash, isEthereum = false, onSignature, payloadExt, payloadJson, setError }: Props): React.ReactElement<Props> {
|
||||
const [isBusy, setIsBusy] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const chain = useMetadata(genesisHash);
|
||||
const { error: ledgerError, isLoading: ledgerLoading, isLocked: ledgerLocked, ledger, refresh, warning: ledgerWarning } = useLedger(genesisHash, accountIndex, addressOffset);
|
||||
const { error: ledgerError, isLoading: ledgerLoading, isLocked: ledgerLocked, ledger, refresh, type: _ledgerType, warning: ledgerWarning } = useLedger(genesisHash, accountIndex, addressOffset, isEthereum);
|
||||
|
||||
useEffect(() => {
|
||||
if (ledgerError) {
|
||||
@ -94,30 +95,53 @@ function LedgerSign ({ accountIndex, addressOffset, className, error, genesisHas
|
||||
}
|
||||
|
||||
const { raw, txMetadata } = getMetadataProof(chain, payloadJson);
|
||||
|
||||
const metaBuff = Buffer.from(txMetadata);
|
||||
|
||||
(ledger as LedgerGeneric).signWithMetadata(raw.toU8a(true), accountIndex, addressOffset, { metadata: metaBuff })
|
||||
.then((signature) => {
|
||||
const extrinsic = chain.registry.createType(
|
||||
'Extrinsic',
|
||||
{ method: raw.method },
|
||||
{ version: 4 }
|
||||
);
|
||||
if (isEthereum) {
|
||||
(ledger as LedgerGeneric).signWithMetadataEcdsa(raw.toU8a(true), accountIndex, addressOffset, { metadata: metaBuff })
|
||||
.then(({ signature }) => {
|
||||
const extrinsic = chain.registry.createType(
|
||||
'Extrinsic',
|
||||
{ method: raw.method },
|
||||
{ version: 4 }
|
||||
);
|
||||
|
||||
(ledger as LedgerGeneric).getAddress(chain.ss58Format, false, accountIndex, addressOffset)
|
||||
.then(({ address }) => {
|
||||
extrinsic.addSignature(address, signature.signature, raw.toHex());
|
||||
onSignature(signature, extrinsic.toHex());
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
setError(e.message);
|
||||
setIsBusy(false);
|
||||
});
|
||||
}).catch((e: Error) => {
|
||||
setError(e.message);
|
||||
setIsBusy(false);
|
||||
});
|
||||
(ledger as LedgerGeneric).getAddressEcdsa(false, accountIndex, addressOffset)
|
||||
.then(({ address }) => {
|
||||
extrinsic.addSignature(`0x${address}`, signature, raw.toHex());
|
||||
onSignature({ signature }, extrinsic.toHex());
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
setError(e.message);
|
||||
setIsBusy(false);
|
||||
});
|
||||
}).catch((e: Error) => {
|
||||
setError(e.message);
|
||||
setIsBusy(false);
|
||||
});
|
||||
} else {
|
||||
(ledger as LedgerGeneric).signWithMetadata(raw.toU8a(true), accountIndex, addressOffset, { metadata: metaBuff })
|
||||
.then((signature) => {
|
||||
const extrinsic = chain.registry.createType(
|
||||
'Extrinsic',
|
||||
{ method: raw.method },
|
||||
{ version: 4 }
|
||||
);
|
||||
|
||||
(ledger as LedgerGeneric).getAddress(chain.ss58Format, false, accountIndex, addressOffset)
|
||||
.then(({ address }) => {
|
||||
extrinsic.addSignature(address, signature.signature, raw.toHex());
|
||||
onSignature(signature, extrinsic.toHex());
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
setError(e.message);
|
||||
setIsBusy(false);
|
||||
});
|
||||
}).catch((e: Error) => {
|
||||
setError(e.message);
|
||||
setIsBusy(false);
|
||||
});
|
||||
}
|
||||
} else if (currApp === 'chainSpecific') {
|
||||
(ledger as Ledger).sign(payloadExt.toU8a(true), accountIndex, addressOffset)
|
||||
.then((signature) => {
|
||||
@ -128,7 +152,7 @@ function LedgerSign ({ accountIndex, addressOffset, className, error, genesisHas
|
||||
});
|
||||
}
|
||||
},
|
||||
[accountIndex, addressOffset, chain, ledger, onSignature, payloadJson, payloadExt, setError]
|
||||
[accountIndex, addressOffset, chain, ledger, onSignature, payloadJson, payloadExt, setError, isEthereum]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -43,7 +43,7 @@ function isRawPayload (payload: SignerPayloadJSON | SignerPayloadRaw): payload i
|
||||
return !!(payload as SignerPayloadRaw).data;
|
||||
}
|
||||
|
||||
export default function Request ({ account: { accountIndex, addressOffset, genesisHash, isExternal, isHardware }, buttonText, isFirst, request, signId, url }: Props): React.ReactElement<Props> | null {
|
||||
export default function Request ({ account: { accountIndex, addressOffset, genesisHash, isExternal, isHardware, type }, buttonText, isFirst, request, signId, url }: Props): React.ReactElement<Props> | null {
|
||||
const onAction = useContext(ActionContext);
|
||||
const [{ hexBytes, payload }, setData] = useState<Data>({ hexBytes: null, payload: null });
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@ -125,6 +125,7 @@ export default function Request ({ account: { accountIndex, addressOffset, genes
|
||||
addressOffset={addressOffset || 0}
|
||||
error={error}
|
||||
genesisHash={json.genesisHash}
|
||||
isEthereum={type === 'ethereum'}
|
||||
onSignature={_onSignature}
|
||||
payloadExt={payload}
|
||||
payloadJson={json}
|
||||
|
||||
@ -14,6 +14,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
|
||||
import { hexToU8a, isHex } from '@polkadot/util';
|
||||
import { decodeAddress, encodeAddress } from '@polkadot/util-crypto';
|
||||
|
||||
import details from '../assets/details.svg';
|
||||
@ -71,8 +72,7 @@ function findAccountByAddress (accounts: AccountJson[], _address: string): Accou
|
||||
// recodes an supplied address using the prefix/genesisHash, include the actual saved account & chain
|
||||
function recodeAddress (address: string, accounts: AccountWithChildren[], chain: Chain | null, settings: SettingsStruct): Recoded {
|
||||
// decode and create a shortcut for the encoded address
|
||||
const publicKey = decodeAddress(address);
|
||||
|
||||
const publicKey = isHex(address) ? hexToU8a(address) : decodeAddress(address);
|
||||
// find our account using the actual publicKey, and then find the associated chain
|
||||
const account = findSubstrateAccount(accounts, publicKey);
|
||||
const prefix = chain ? chain.ss58Format : (settings.prefix === -1 ? 42 : settings.prefix);
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
import type { Network } from '@polkadot/networks/types';
|
||||
import type { HexString } from '@polkadot/util/types';
|
||||
import type { KeypairType } from '@polkadot/util-crypto/types';
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
@ -31,6 +32,7 @@ interface State extends StateBase {
|
||||
isLocked: boolean;
|
||||
ledger: LedgerGeneric | Ledger | null;
|
||||
refresh: () => void;
|
||||
type: KeypairType | null;
|
||||
warning: string | null;
|
||||
}
|
||||
|
||||
@ -82,13 +84,14 @@ function retrieveLedger (genesis: string): LedgerGeneric | Ledger {
|
||||
return ledger;
|
||||
}
|
||||
|
||||
export default function useLedger (genesis?: string | null, accountIndex = 0, addressOffset = 0): State {
|
||||
export default function useLedger (genesis?: string | null, accountIndex = 0, addressOffset = 0, isEthereum = false): State {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isLocked, setIsLocked] = useState(false);
|
||||
const [refreshLock, setRefreshLock] = useState(false);
|
||||
const [warning, setWarning] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [address, setAddress] = useState<string | null>(null);
|
||||
const [type, setType] = useState<KeypairType | null>(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleGetAddressError = (e: Error, genesis: string) => {
|
||||
@ -154,17 +157,28 @@ export default function useLedger (genesis?: string | null, accountIndex = 0, ad
|
||||
|
||||
// Just in case, but this shouldn't be triggered
|
||||
assert(chosenNetwork, t('This network is not available, please report an issue to update the known chains'));
|
||||
|
||||
const currApp = settings.get().ledgerApp;
|
||||
|
||||
if (currApp === 'generic' || currApp === 'migration') {
|
||||
(ledger as LedgerGeneric).getAddress(chosenNetwork.ss58Format, false, accountIndex, addressOffset)
|
||||
.then((res) => {
|
||||
setIsLoading(false);
|
||||
setAddress(res.address);
|
||||
}).catch((e: Error) => {
|
||||
handleGetAddressError(e, genesis);
|
||||
});
|
||||
if (isEthereum) {
|
||||
(ledger as LedgerGeneric).getAddressEcdsa(false, accountIndex, addressOffset)
|
||||
.then((res) => {
|
||||
setIsLoading(false);
|
||||
setAddress(`0x${res.address}`);
|
||||
setType('ethereum');
|
||||
}).catch((e: Error) => {
|
||||
handleGetAddressError(e, genesis);
|
||||
});
|
||||
} else {
|
||||
(ledger as LedgerGeneric).getAddress(chosenNetwork.ss58Format, false, accountIndex, addressOffset)
|
||||
.then((res) => {
|
||||
setIsLoading(false);
|
||||
setAddress(res.address);
|
||||
setType('ed25519');
|
||||
}).catch((e: Error) => {
|
||||
handleGetAddressError(e, genesis);
|
||||
});
|
||||
}
|
||||
} else if (currApp === 'chainSpecific') {
|
||||
(ledger as Ledger).getAddress(false, accountIndex, addressOffset)
|
||||
.then((res) => {
|
||||
@ -177,7 +191,7 @@ export default function useLedger (genesis?: string | null, accountIndex = 0, ad
|
||||
// If the dependency array is exhaustive, with t, the translation function, it
|
||||
// triggers a useless re-render when ledger device is connected.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [accountIndex, addressOffset, genesis, ledger]);
|
||||
}, [accountIndex, addressOffset, genesis, ledger, isEthereum]);
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
setRefreshLock(true);
|
||||
@ -185,5 +199,5 @@ export default function useLedger (genesis?: string | null, accountIndex = 0, ad
|
||||
setWarning(null);
|
||||
}, []);
|
||||
|
||||
return ({ ...getState(), address, error, isLoading, isLocked, ledger, refresh, warning });
|
||||
return ({ ...getState(), address, error, isLoading, isLocked, ledger, refresh, type, warning });
|
||||
}
|
||||
|
||||
@ -143,8 +143,8 @@ export async function createAccountExternal (name: string, address: string, gene
|
||||
return sendMessage('pri(accounts.create.external)', { address, genesisHash, name });
|
||||
}
|
||||
|
||||
export async function createAccountHardware (address: string, hardwareType: string, accountIndex: number, addressOffset: number, name: string, genesisHash: HexString): Promise<boolean> {
|
||||
return sendMessage('pri(accounts.create.hardware)', { accountIndex, address, addressOffset, genesisHash, hardwareType, name });
|
||||
export async function createAccountHardware (address: string, hardwareType: string, accountIndex: number, addressOffset: number, name: string, genesisHash: HexString, type: KeypairType): Promise<boolean> {
|
||||
return sendMessage('pri(accounts.create.hardware)', { accountIndex, address, addressOffset, genesisHash, hardwareType, name, type });
|
||||
}
|
||||
|
||||
export async function createAccountSuri (name: string, password: string, suri: string, type?: KeypairType, genesisHash?: HexString | null): Promise<boolean> {
|
||||
|
||||
@ -182,6 +182,8 @@
|
||||
"Ledger App": "",
|
||||
"Don't ask again": "",
|
||||
"Previous": "",
|
||||
"ED25519 Account": "",
|
||||
"Ethereum Account": "",
|
||||
"It looks like this request is coming from an suspicious origin. Please verify the source carefully.": "",
|
||||
"Password is not strong enough": "",
|
||||
"Password must be at least {{length}} characters long": ""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user