Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sign multi-sig transaction with hw wallet #1604

Merged
merged 2 commits into from
Mar 3, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/Cardano/util/index.ts
Original file line number Diff line number Diff line change
@@ -5,3 +5,4 @@ export * from './resolveInputValue';
export * from './phase2Validation';
export * from './addressesShareAnyKey';
export * from './plutusDataUtils';
export * from './isScriptAddress';
8 changes: 8 additions & 0 deletions packages/core/src/Cardano/util/isScriptAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Address, CredentialType, PaymentAddress } from '../Address';

export const isScriptAddress = (address: PaymentAddress): boolean => {
const baseAddress = Address.fromBech32(address).asBase();
const paymentCredential = baseAddress?.getPaymentCredential();
const stakeCredential = baseAddress?.getStakeCredential();
return paymentCredential?.type === CredentialType.ScriptHash && stakeCredential?.type === CredentialType.ScriptHash;
};
18 changes: 18 additions & 0 deletions packages/core/test/Cardano/util/isScriptAddress.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { PaymentAddress } from '../../../src/Cardano';
import { isScriptAddress } from '../../../src/Cardano/util';

describe('isScriptAddress', () => {
it('returns false when it receives a non-script address', () => {
const nonScriptAddress = PaymentAddress(
'addr_test1qpfhhfy2qgls50r9u4yh0l7z67xpg0a5rrhkmvzcuqrd0znuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475q9gw0lz'
);
expect(isScriptAddress(nonScriptAddress)).toBe(false);
});

it('returns true when it receives a script address', () => {
const scriptAddress = PaymentAddress(
'addr_test1xr806j8xcq6cw6jjkzfxyewyue33zwnu4ajnu28hakp5fmc6gddlgeqee97vwdeafwrdgrtzp2rw8rlchjf25ld7r2ssptq3m9'
);
expect(isScriptAddress(scriptAddress)).toBe(true);
});
});
9 changes: 8 additions & 1 deletion packages/hardware-ledger/src/LedgerKeyAgent.ts
Original file line number Diff line number Diff line change
@@ -322,6 +322,10 @@ const getDerivationPath = (
};
};

const multiSigWitnessPaths: BIP32Path[] = [
util.accountKeyDerivationPathToBip32Path(0, { index: 0, role: KeyRole.External }, KeyPurpose.MULTI_SIG)
];

export class LedgerKeyAgent extends KeyAgentBase {
readonly deviceConnection?: LedgerConnection;
readonly #communicationType: CommunicationType;
@@ -733,7 +737,10 @@ export class LedgerKeyAgent extends KeyAgentBase {
tagCborSets: txBody.hasTaggedSets()
},
signingMode,
tx: ledgerTxData
tx: ledgerTxData,
...(signingMode === TransactionSigningMode.MULTISIG_TRANSACTION && {
additionalWitnessPaths: multiSigWitnessPaths
})
});

if (!areStringsEqualInConstantTime(result.txHashHex, hash)) {
3 changes: 2 additions & 1 deletion packages/hardware-ledger/src/transformers/txOut.ts
Original file line number Diff line number Diff line change
@@ -20,8 +20,9 @@ const toDestination: Transform<Cardano.TxOut, Ledger.TxOutputDestination, Ledger
context
) => {
const knownAddress = context?.knownAddresses.find((address) => address.address === txOut.address);
const isScriptAddress = Cardano.util.isScriptAddress(txOut.address);

if (knownAddress) {
if (knownAddress && !isScriptAddress) {
const paymentKeyPath = util.paymentKeyPathFromGroupedAddress(knownAddress);
const stakeKeyPath = util.stakeKeyPathFromGroupedAddress(knownAddress);

33 changes: 30 additions & 3 deletions packages/hardware-trezor/src/TrezorKeyAgent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as Crypto from '@cardano-sdk/crypto';
import * as Trezor from '@trezor/connect';
import { BIP32Path } from '@cardano-sdk/crypto';
import { Cardano, NotImplementedError, Serialization } from '@cardano-sdk/core';
import {
CardanoKeyConst,
@@ -9,6 +10,7 @@ import {
KeyAgentDependencies,
KeyAgentType,
KeyPurpose,
KeyRole,
SerializableTrezorKeyAgentData,
SignBlobResult,
SignTransactionContext,
@@ -68,6 +70,10 @@ const containsOnlyScriptHashCredentials = (tx: Omit<Trezor.CardanoSignTransactio
return !tx.withdrawals?.some((withdrawal) => !withdrawal.scriptHash);
};

const multiSigWitnessPaths: BIP32Path[] = [
util.accountKeyDerivationPathToBip32Path(0, { index: 0, role: KeyRole.External }, KeyPurpose.MULTI_SIG)
];

const isMultiSig = (tx: Omit<Trezor.CardanoSignTransaction, 'signingMode'>): boolean => {
const allThirdPartyInputs = !tx.inputs.some((input) => input.path);
// Trezor doesn't allow change outputs to address controlled by your keys and instead you have to use script address for change out
@@ -100,7 +106,8 @@ export class TrezorKeyAgent extends KeyAgentBase {
manifest,
communicationType,
silentMode = false,
lazyLoad = false
lazyLoad = false,
shouldHandlePassphrase = false
}: TrezorConfig): Promise<boolean> {
const trezorConnect = getTrezorConnect(communicationType);
try {
@@ -116,6 +123,23 @@ export class TrezorKeyAgent extends KeyAgentBase {
// Show Trezor Suite popup. Disabled for node based apps
popup: communicationType !== CommunicationType.Node && !silentMode
});

if (shouldHandlePassphrase) {
trezorConnect.on(Trezor.UI_EVENT, (event) => {
// React on ui-request_passphrase event
if (event.type === Trezor.UI.REQUEST_PASSPHRASE && event.payload.device) {
trezorConnect.uiResponse({
payload: {
passphraseOnDevice: true,
save: true,
value: ''
},
type: Trezor.UI.RECEIVE_PASSPHRASE
});
}
});
}

return true;
} catch (error: any) {
if (error.code === 'Init_AlreadyInitialized') return true;
@@ -215,7 +239,7 @@ export class TrezorKeyAgent extends KeyAgentBase {

async signTransaction(
txBody: Serialization.TransactionBody,
{ knownAddresses, txInKeyPathMap }: SignTransactionContext
{ knownAddresses, txInKeyPathMap, scripts }: SignTransactionContext
): Promise<Cardano.Signatures> {
try {
await this.isTrezorInitialized;
@@ -235,12 +259,15 @@ export class TrezorKeyAgent extends KeyAgentBase {
const trezorConnect = getTrezorConnect(this.#communicationType);
const result = await trezorConnect.cardanoSignTransaction({
...trezorTxData,
...(signingMode === Trezor.PROTO.CardanoTxSigningMode.MULTISIG_TRANSACTION && {
additionalWitnessRequests: multiSigWitnessPaths
}),
signingMode
});

const expectedPublicKeys = await Promise.all(
util
.ownSignatureKeyPaths(body, knownAddresses, txInKeyPathMap)
.ownSignatureKeyPaths(body, knownAddresses, txInKeyPathMap, undefined, scripts)
.map((derivationPath) => this.derivePublicKey(derivationPath))
);

3 changes: 2 additions & 1 deletion packages/hardware-trezor/src/transformers/txOut.ts
Original file line number Diff line number Diff line change
@@ -10,8 +10,9 @@ const toDestination: Transform<Cardano.TxOut, TrezorTxOutputDestination, TrezorT
context
) => {
const knownAddress = context?.knownAddresses.find((address: GroupedAddress) => address.address === txOut.address);
const isScriptAddress = Cardano.util.isScriptAddress(txOut.address);

if (!knownAddress) {
if (!knownAddress || isScriptAddress) {
return {
address: txOut.address
};
2 changes: 2 additions & 0 deletions packages/key-management/src/types.ts
Original file line number Diff line number Diff line change
@@ -92,6 +92,8 @@ export interface TrezorConfig {
email: string;
appUrl: string;
};
/** When set to true, Trezor automatically handle passphrase entry by forcing it to occur on the device */
shouldHandlePassphrase?: boolean;
}

export interface SerializableKeyAgentDataBase {
25 changes: 17 additions & 8 deletions packages/key-management/src/util/ownSignatureKeyPaths.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as Crypto from '@cardano-sdk/crypto';
import { AccountKeyDerivationPath, GroupedAddress, TxInId, TxInKeyPathMap } from '../types';
import { AccountKeyDerivationPath, GroupedAddress, KeyRole, TxInId, TxInKeyPathMap } from '../types';
import { Cardano } from '@cardano-sdk/core';
import { DREP_KEY_DERIVATION_PATH } from './key';
import { Ed25519KeyHashHex } from '@cardano-sdk/crypto';
@@ -300,15 +300,24 @@ const checkStakeCredential = (address: GroupedAddress, keyHash: Crypto.Ed25519Ke
? { derivationPaths: [address.stakeKeyDerivationPath], requiresForeignSignatures: false }
: { derivationPaths: [], requiresForeignSignatures: true };

const checkPaymentCredential = (address: GroupedAddress, keyHash: Crypto.Ed25519KeyHashHex): SignatureCheck => {
const checkPaymentCredential = (address: GroupedAddress, keyHash: Crypto.Ed25519KeyHashHex) => {
const paymentCredential = Cardano.Address.fromBech32(address.address)?.asBase()?.getPaymentCredential();
return paymentCredential?.type === Cardano.CredentialType.KeyHash &&
if (
paymentCredential?.type === Cardano.CredentialType.ScriptHash &&
paymentCredential.hash === Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(keyHash)
? {
derivationPaths: [{ index: address.index, role: Number(address.type) }],
requiresForeignSignatures: false
}
: { derivationPaths: [], requiresForeignSignatures: true };
)
return {
derivationPaths: [{ index: address.index, role: Number(address.type) }],
requiresForeignSignatures: false
};

if (paymentCredential?.type === Cardano.CredentialType.ScriptHash) {
return {
derivationPaths: [{ index: address.index, role: KeyRole.External }],
requiresForeignSignatures: false
};
}
return { derivationPaths: [], requiresForeignSignatures: true };
};

const combineSignatureChecks = (a: SignatureCheck, b: SignatureCheck): SignatureCheck => ({
42 changes: 41 additions & 1 deletion packages/key-management/test/util/ownSignaturePaths.test.ts
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ const createGroupedAddress = (
rewardAccount: Cardano.RewardAccount,
type: AddressType,
index: number,
stakeKeyDerivationPath: AccountKeyDerivationPath
stakeKeyDerivationPath?: AccountKeyDerivationPath
) =>
({
...createBaseGroupedAddress(address, rewardAccount, type, index),
@@ -633,5 +633,45 @@ describe('KeyManagement.util.ownSignaturePaths', () => {

expect(util.ownSignatureKeyPaths(txBody, [knownAddress1], {}, undefined, scripts)).toEqual([]);
});
it('includes derivation paths for multi-signature native scripts', async () => {
const scriptAddress = Cardano.PaymentAddress(
'addr_test1xr806j8xcq6cw6jjkzfxyewyue33zwnu4ajnu28hakp5fmc6gddlgeqee97vwdeafwrdgrtzp2rw8rlchjf25ld7r2ssptq3m9'
);
const scriptRewardAccount = Cardano.RewardAccount(
'stake_test17qdyxkl5vsvujlx8xu75hpk5p43q4phr3lutey420klp4gg7zmhrn'
);
const txBody: Cardano.TxBody = {
fee: BigInt(0),
inputs: [{}, {}, {}] as Cardano.TxIn[],
outputs: []
};

const scripts: Cardano.Script[] = [
{
__type: Cardano.ScriptType.Native,
kind: Cardano.NativeScriptKind.RequireAnyOf,
scripts: [
{
__type: Cardano.ScriptType.Native,
keyHash: Ed25519KeyHashHex('b498c0eaceb9a8c7c829d36fc84e892113c9d2636b53b0636d7518b4'),
kind: Cardano.NativeScriptKind.RequireSignature
},
{
__type: Cardano.ScriptType.Native,
keyHash: Ed25519KeyHashHex(otherStakeKeyHash),
kind: Cardano.NativeScriptKind.RequireSignature
}
]
}
];

const knownAddress = createGroupedAddress(scriptAddress, scriptRewardAccount, AddressType.External, 0);
expect(util.ownSignatureKeyPaths(txBody, [knownAddress], {}, undefined, scripts)).toEqual([
{
index: 0,
role: KeyRole.External
}
]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import * as Crypto from '@cardano-sdk/crypto';
import { BaseWallet, createSharedWallet } from '../../../src';
import { Cardano } from '@cardano-sdk/core';
import { CommunicationType, KeyPurpose, KeyRole, util } from '@cardano-sdk/key-management';
import { InitializeTxProps, InitializeTxResult } from '@cardano-sdk/tx-construction';
import { LedgerKeyAgent } from '@cardano-sdk/hardware-ledger';
import { dummyLogger as logger } from 'ts-log';
import { mockProviders as mocks } from '@cardano-sdk/util-dev';

describe('LedgerSharedWalletKeyAgent', () => {
let ledgerKeyAgent: LedgerKeyAgent;
let wallet: BaseWallet;

beforeAll(async () => {
ledgerKeyAgent = await LedgerKeyAgent.createWithDevice(
{
chainId: Cardano.ChainIds.Preprod,
communicationType: CommunicationType.Node,
purpose: KeyPurpose.MULTI_SIG
},
{ bip32Ed25519: await Crypto.SodiumBip32Ed25519.create(), logger }
);
});

afterAll(async () => {
await ledgerKeyAgent.deviceConnection?.transport.close();
});

describe('signTransaction', () => {
let txInternals: InitializeTxResult;

beforeAll(async () => {
const walletPubKey = await ledgerKeyAgent.derivePublicKey({ index: 0, role: KeyRole.External });
const walletKeyHash = ledgerKeyAgent.bip32Ed25519.getPubKeyHash(walletPubKey);

const walletStakePubKey = await ledgerKeyAgent.derivePublicKey({ index: 0, role: KeyRole.Stake });
const walletStakeKeyHash = ledgerKeyAgent.bip32Ed25519.getPubKeyHash(walletStakePubKey);

const paymentScript: Cardano.NativeScript = {
__type: Cardano.ScriptType.Native,
kind: Cardano.NativeScriptKind.RequireAnyOf,
scripts: [
{
__type: Cardano.ScriptType.Native,
keyHash: walletKeyHash,
kind: Cardano.NativeScriptKind.RequireSignature
},
{
__type: Cardano.ScriptType.Native,
keyHash: Crypto.Ed25519KeyHashHex('b275b08c999097247f7c17e77007c7010cd19f20cc086ad99d398539'),
kind: Cardano.NativeScriptKind.RequireSignature
}
]
};

const stakingScript: Cardano.NativeScript = {
__type: Cardano.ScriptType.Native,
kind: Cardano.NativeScriptKind.RequireAnyOf,
scripts: [
{
__type: Cardano.ScriptType.Native,
keyHash: walletStakeKeyHash,
kind: Cardano.NativeScriptKind.RequireSignature
},
{
__type: Cardano.ScriptType.Native,
keyHash: Crypto.Ed25519KeyHashHex('b275b08c999097247f7c17e77007c7010cd19f20cc086ad99d398539'),
kind: Cardano.NativeScriptKind.RequireSignature
}
]
};

const outputs: Cardano.TxOut[] = [
{
address: Cardano.PaymentAddress(
'addr_test1qpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5ewvxwdrt70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qum8x5w'
),
scriptReference: paymentScript,
value: { coins: 11_111_111n }
}
];
const props: InitializeTxProps = {
outputs: new Set<Cardano.TxOut>(outputs)
};

wallet = createSharedWallet(
{ name: 'Shared HW Wallet' },
{
assetProvider: mocks.mockAssetProvider(),
chainHistoryProvider: mocks.mockChainHistoryProvider(),
logger,
networkInfoProvider: mocks.mockNetworkInfoProvider(),
paymentScript,
rewardAccountInfoProvider: mocks.mockRewardAccountInfoProvider(),
rewardsProvider: mocks.mockRewardsProvider(),
stakingScript,
txSubmitProvider: mocks.mockTxSubmitProvider(),
utxoProvider: mocks.mockUtxoProvider(),
witnesser: util.createBip32Ed25519Witnesser(util.createAsyncKeyAgent(ledgerKeyAgent))
}
);
txInternals = await wallet.initializeTx(props);
});

afterAll(() => wallet.shutdown());

it('successfully signs a transaction', async () => {
const tx = await wallet.finalizeTx({ tx: txInternals });
expect(tx.witness.signatures.size).toBe(1);
});
});
});
6 changes: 4 additions & 2 deletions packages/wallet/test/hardware/trezor/TrezorKeyAgent.test.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import {
Bip32Account,
CommunicationType,
SerializableTrezorKeyAgentData,
TrezorConfig,
util
} from '@cardano-sdk/key-management';
import { AssetId, mockProviders as mocks } from '@cardano-sdk/util-dev';
@@ -31,12 +32,13 @@ describe('TrezorKeyAgent', () => {
let txSubmitProvider: mocks.TxSubmitProviderStub;
let address: Cardano.PaymentAddress;

const trezorConfig = {
const trezorConfig: TrezorConfig = {
communicationType: CommunicationType.Node,
manifest: {
appUrl: 'https://your.application.com',
email: 'email@developer.com'
}
},
shouldHandlePassphrase: true
};

beforeAll(async () => {
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import * as Crypto from '@cardano-sdk/crypto';
import { BaseWallet, createSharedWallet } from '../../../src';
import { Cardano } from '@cardano-sdk/core';
import { CommunicationType, KeyPurpose, KeyRole, TrezorConfig, util } from '@cardano-sdk/key-management';
import { InitializeTxProps, InitializeTxResult } from '@cardano-sdk/tx-construction';
import { TrezorKeyAgent } from '@cardano-sdk/hardware-trezor';
import { dummyLogger as logger } from 'ts-log';
import { mockProviders as mocks } from '@cardano-sdk/util-dev';

describe('TrezorSharedWalletKeyAgent', () => {
let wallet: BaseWallet;
let trezorKeyAgent: TrezorKeyAgent;
let paymentScript: Cardano.NativeScript;

const trezorConfig: TrezorConfig = {
communicationType: CommunicationType.Node,
manifest: {
appUrl: 'https://your.application.com',
email: 'email@developer.com'
},
shouldHandlePassphrase: true
};

beforeAll(async () => {
trezorKeyAgent = await TrezorKeyAgent.createWithDevice(
{
chainId: Cardano.ChainIds.Preprod,
purpose: KeyPurpose.MULTI_SIG,
trezorConfig
},
{
bip32Ed25519: await Crypto.SodiumBip32Ed25519.create(),
logger
}
);

const paymentKeyHash = trezorKeyAgent.bip32Ed25519.getPubKeyHash(
await trezorKeyAgent.derivePublicKey({ index: 0, role: KeyRole.External })
);
const stakeKeyHash = trezorKeyAgent.bip32Ed25519.getPubKeyHash(
await trezorKeyAgent.derivePublicKey({ index: 0, role: KeyRole.Stake })
);

paymentScript = {
__type: Cardano.ScriptType.Native,
kind: Cardano.NativeScriptKind.RequireAnyOf,
scripts: [
{
__type: Cardano.ScriptType.Native,
keyHash: paymentKeyHash,
kind: Cardano.NativeScriptKind.RequireSignature
},
{
__type: Cardano.ScriptType.Native,
keyHash: Crypto.Ed25519KeyHashHex('b275b08c999097247f7c17e77007c7010cd19f20cc086ad99d398539'),
kind: Cardano.NativeScriptKind.RequireSignature
}
]
};

const stakingScript: Cardano.NativeScript = {
__type: Cardano.ScriptType.Native,
kind: Cardano.NativeScriptKind.RequireAnyOf,
scripts: [
{
__type: Cardano.ScriptType.Native,
keyHash: stakeKeyHash,
kind: Cardano.NativeScriptKind.RequireSignature
},
{
__type: Cardano.ScriptType.Native,
keyHash: Crypto.Ed25519KeyHashHex('b275b08c999097247f7c17e77007c7010cd19f20cc086ad99d398539'),
kind: Cardano.NativeScriptKind.RequireSignature
}
]
};

wallet = createSharedWallet(
{ name: 'Shared HW Wallet' },
{
assetProvider: mocks.mockAssetProvider(),
chainHistoryProvider: mocks.mockChainHistoryProvider(),
logger,
networkInfoProvider: mocks.mockNetworkInfoProvider(),
paymentScript,
rewardAccountInfoProvider: mocks.mockRewardAccountInfoProvider(),
rewardsProvider: mocks.mockRewardsProvider(),
stakingScript,
txSubmitProvider: mocks.mockTxSubmitProvider(),
utxoProvider: mocks.mockUtxoProvider(),
witnesser: util.createBip32Ed25519Witnesser(util.createAsyncKeyAgent(trezorKeyAgent))
}
);
});

afterAll(() => wallet.shutdown());

describe('Sign Transaction', () => {
let props: InitializeTxProps;
let txInternals: InitializeTxResult;
const simpleOutput = {
address: Cardano.PaymentAddress(
'addr_test1qpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5ewvxwdrt70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qum8x5w'
),
value: { coins: 11_111_111n }
};

it('should sign simple multi-sig transaction', async () => {
props = {
outputs: new Set<Cardano.TxOut>([simpleOutput])
};
txInternals = await wallet.initializeTx(props);
const witnessedTx = await wallet.finalizeTx({
signingContext: { scripts: [paymentScript] },
tx: txInternals
});
expect(witnessedTx.witness.signatures.size).toBe(1);
});
});
});
4 changes: 3 additions & 1 deletion yarn-project.nix
Original file line number Diff line number Diff line change
@@ -441,7 +441,7 @@ cacheEntries = {
"@emurgo/cip14-js@npm:3.0.1" = { filename = "@emurgo-cip14-js-npm-3.0.1-6011030ea2-9eaf312410.zip"; sha512 = "9eaf3124108e8c252a745de9ef1f334ab26a32271077b00fe0ea2a06e40838dd435165dac523ebd4d851ae7a94d8c56766dabc372aabffedd36551c798c607c5"; };
"@endemolshinegroup/cosmiconfig-typescript-loader@npm:3.0.2" = { filename = "@endemolshinegroup-cosmiconfig-typescript-loader-npm-3.0.2-97436e68fc-7fe0198622.zip"; sha512 = "7fe0198622b1063c40572034df7e8ba867865a1b4815afe230795929abcf785758b34d7806a8e2100ba8ab4e92c5a1c3e11a980c466c4406df6e7ec6e50df8b6"; };
"@es-joy/jsdoccomment@npm:0.10.8" = { filename = "@es-joy-jsdoccomment-npm-0.10.8-d03c65b162-3e144ef393.zip"; sha512 = "3e144ef393459a541b64f6c9c8e62fb6d9b47e1a2c626410487ede12c472064f6ce6e0911df60b42ccf126d5a66102707eef59ca14767cb7aeb5e608b227558d"; };
"@esbuild/linux-x64@npm:0.21.5" = { filename = "@esbuild-linux-x64-npm-0.21.5-88079726c4-8.zip"; sha512 = "91c202dca064909b2c56522f98e3a3b24bc5d43405506b4e67923ecb5d0cc2b78dcee8d815f705d71395402f8532670a391777a3cf6a08894049e453becf07a0"; };
"@esbuild/darwin-arm64@npm:0.21.5" = { filename = "@esbuild-darwin-arm64-npm-0.21.5-62349c1520-8.zip"; sha512 = "50d5d633be3d0fe0fce54c4740171ae6d2e8f5220280a6f6996f234c718de25535e50a31cee1745b5b80f2cc9e336c42c7fc2b49f3ea38b5f3ff5d8c97ef4123"; };
"@eslint/eslintrc@npm:0.4.3" = { filename = "@eslint-eslintrc-npm-0.4.3-ee1bbcab87-03a7704150.zip"; sha512 = "03a7704150b868c318aab6a94d87a33d30dc2ec579d27374575014f06237ba1370ae11178db772f985ef680d469dc237e7b16a1c5d8edaaeb8c3733e7a95a6d3"; };
"@ethereumjs/common@npm:4.4.0" = { filename = "@ethereumjs-common-npm-4.4.0-ee991f5124-6b8cbfcfb5.zip"; sha512 = "6b8cbfcfb5bdde839545c89dce3665706733260e26455d0eb3bcbc3c09e371ae629d51032b95d86f2aeeb15325244a6622171f9005165266fefd923eaa99f1c5"; };
"@ethereumjs/rlp@npm:5.0.2" = { filename = "@ethereumjs-rlp-npm-5.0.2-72fb389b37-b569061ddb.zip"; sha512 = "b569061ddb1f4cf56a82f7a677c735ba37f9e94e2bbaf567404beb9e2da7aa1f595e72fc12a17c61f7aec67fd5448443efe542967c685a2fe0ffc435793dcbab"; };
@@ -1461,6 +1461,8 @@ cacheEntries = {
"fs.realpath@npm:1.0.0" = { filename = "fs.realpath-npm-1.0.0-c8f05d8126-99ddea01a7.zip"; sha512 = "99ddea01a7e75aa276c250a04eedeffe5662bce66c65c07164ad6264f9de18fb21be9433ead460e54cff20e31721c811f4fb5d70591799df5f85dce6d6746fd0"; };
"fsevents@npm:2.3.2" = { filename = "fsevents-npm-2.3.2-a881d6ac9f-97ade64e75.zip"; sha512 = "97ade64e75091afee5265e6956cb72ba34db7819b4c3e94c431d4be2b19b8bb7a2d4116da417950c3425f17c8fe693d25e20212cac583ac1521ad066b77ae31f"; };
"fsevents@npm:2.3.3" = { filename = "fsevents-npm-2.3.3-ce9fb0ffae-11e6ea6fea.zip"; sha512 = "11e6ea6fea15e42461fc55b4b0e4a0a3c654faa567f1877dbd353f39156f69def97a69936d1746619d656c4b93de2238bf731f6085a03a50cabf287c9d024317"; };
"fsevents@patch:fsevents@npm%3A2.3.2#~builtin<compat/fsevents>::version=2.3.2&hash=18f3a7" = { filename = "fsevents-patch-3340e2eb10-8.zip"; sha512 = "edbd0fd80be379c14409605f77e52fdc78a119e17f875e8b90a220c3e5b29e54a1477c21d91fd30b957ea4866406dc3ff87b61432d2840ff8866b309e5866140"; };
"fsevents@patch:fsevents@npm%3A2.3.3#~builtin<compat/fsevents>::version=2.3.3&hash=18f3a7" = { filename = "fsevents-patch-7934e3c202-8.zip"; sha512 = "4639e24e2774cbd3669bd08521e0eeeb9d05bbabffdfdee418cc75a237660bc2fb30520a266ad5379199e2d657f430dd4236ad3642674ef32f20cc7258506725"; };
"ftp@npm:0.3.10" = { filename = "ftp-npm-0.3.10-348fb9ac23-ddd313c1d4.zip"; sha512 = "ddd313c1d44eb7429f3a7d77a0155dc8fe86a4c64dca58f395632333ce4b4e74c61413c6e0ef66ea3f3d32d905952fbb6d028c7117d522f793eb1fa282e17357"; };
"function-bind@npm:1.1.1" = { filename = "function-bind-npm-1.1.1-b56b322ae9-b32fbaebb3.zip"; sha512 = "b32fbaebb3f8ec4969f033073b43f5c8befbb58f1a79e12f1d7490358150359ebd92f49e72ff0144f65f2c48ea2a605bff2d07965f548f6474fd8efd95bf361a"; };
"function.prototype.name@npm:1.1.5" = { filename = "function.prototype.name-npm-1.1.5-e776a642bb-acd21d733a.zip"; sha512 = "acd21d733a9b649c2c442f067567743214af5fa248dbeee69d8278ce7df3329ea5abac572be9f7470b4ec1cd4d8f1040e3c5caccf98ebf2bf861a0deab735c27"; };