diff --git a/local-tests/build.mjs b/local-tests/build.mjs index 9ab37f131..aeb15f8aa 100644 --- a/local-tests/build.mjs +++ b/local-tests/build.mjs @@ -36,6 +36,7 @@ const createBuildConfig = (entry, outfile, globalName) => ({ inject: [getPath('./shim.mjs')], mainFields: ['module', 'main'], ...(globalName ? { globalName } : {}), + sourcemap: true, }); /** diff --git a/local-tests/setup/shiva-client.d.ts b/local-tests/setup/shiva-client.d.ts index 7bb27c17a..fd401da69 100644 --- a/local-tests/setup/shiva-client.d.ts +++ b/local-tests/setup/shiva-client.d.ts @@ -10,6 +10,7 @@ type ContractAbis = { pubkeyRouter: string; pkpPermissions: string; pkpHelper: string; + priceFeed: string; contractResolver: string; paymentDelegation: string; }; diff --git a/local-tests/setup/shiva-client.ts b/local-tests/setup/shiva-client.ts index a09aba4cc..16fb75cd7 100644 --- a/local-tests/setup/shiva-client.ts +++ b/local-tests/setup/shiva-client.ts @@ -116,6 +116,791 @@ export class TestnetClient { PKPHelper: { abi: JSON.parse(testNetConfig.contractAbis.pkpHelper), }, + PriceFeed: { + abi: [ + { + inputs: [ + { + internalType: 'bytes4', + name: '_selector', + type: 'bytes4', + }, + ], + name: 'CannotAddFunctionToDiamondThatAlreadyExists', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes4[]', + name: '_selectors', + type: 'bytes4[]', + }, + ], + name: 'CannotAddSelectorsToZeroAddress', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_selector', + type: 'bytes4', + }, + ], + name: 'CannotRemoveFunctionThatDoesNotExist', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_selector', + type: 'bytes4', + }, + ], + name: 'CannotRemoveImmutableFunction', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_selector', + type: 'bytes4', + }, + ], + name: 'CannotReplaceFunctionThatDoesNotExists', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_selector', + type: 'bytes4', + }, + ], + name: 'CannotReplaceFunctionWithTheSameFunctionFromTheSameFacet', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes4[]', + name: '_selectors', + type: 'bytes4[]', + }, + ], + name: 'CannotReplaceFunctionsFromFacetWithZeroAddress', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_selector', + type: 'bytes4', + }, + ], + name: 'CannotReplaceImmutableFunction', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint8', + name: '_action', + type: 'uint8', + }, + ], + name: 'IncorrectFacetCutAction', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: '_initializationContractAddress', + type: 'address', + }, + { + internalType: 'bytes', + name: '_calldata', + type: 'bytes', + }, + ], + name: 'InitializationFunctionReverted', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: '_contractAddress', + type: 'address', + }, + { + internalType: 'string', + name: '_message', + type: 'string', + }, + ], + name: 'NoBytecodeAtAddress', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: '_facetAddress', + type: 'address', + }, + ], + name: 'NoSelectorsProvidedForFacetForCut', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: '_user', + type: 'address', + }, + { + internalType: 'address', + name: '_contractOwner', + type: 'address', + }, + ], + name: 'NotContractOwner', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: '_facetAddress', + type: 'address', + }, + ], + name: 'RemoveFacetAddressMustBeZeroAddress', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'facetAddress', + type: 'address', + }, + { + internalType: 'enum IDiamond.FacetCutAction', + name: 'action', + type: 'uint8', + }, + { + internalType: 'bytes4[]', + name: 'functionSelectors', + type: 'bytes4[]', + }, + ], + indexed: false, + internalType: 'struct IDiamond.FacetCut[]', + name: '_diamondCut', + type: 'tuple[]', + }, + { + indexed: false, + internalType: 'address', + name: '_init', + type: 'address', + }, + { + indexed: false, + internalType: 'bytes', + name: '_calldata', + type: 'bytes', + }, + ], + name: 'DiamondCut', + type: 'event', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'facetAddress', + type: 'address', + }, + { + internalType: 'enum IDiamond.FacetCutAction', + name: 'action', + type: 'uint8', + }, + { + internalType: 'bytes4[]', + name: 'functionSelectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct IDiamond.FacetCut[]', + name: '_diamondCut', + type: 'tuple[]', + }, + { + internalType: 'address', + name: '_init', + type: 'address', + }, + { + internalType: 'bytes', + name: '_calldata', + type: 'bytes', + }, + ], + name: 'diamondCut', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_functionSelector', + type: 'bytes4', + }, + ], + name: 'facetAddress', + outputs: [ + { + internalType: 'address', + name: 'facetAddress_', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'facetAddresses', + outputs: [ + { + internalType: 'address[]', + name: 'facetAddresses_', + type: 'address[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_facet', + type: 'address', + }, + ], + name: 'facetFunctionSelectors', + outputs: [ + { + internalType: 'bytes4[]', + name: '_facetFunctionSelectors', + type: 'bytes4[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'facets', + outputs: [ + { + components: [ + { + internalType: 'address', + name: 'facetAddress', + type: 'address', + }, + { + internalType: 'bytes4[]', + name: 'functionSelectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct IDiamondLoupe.Facet[]', + name: 'facets_', + type: 'tuple[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_interfaceId', + type: 'bytes4', + }, + ], + name: 'supportsInterface', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: 'owner_', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_newOwner', + type: 'address', + }, + ], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'CallerNotOwner', + type: 'error', + }, + { + inputs: [], + name: 'MustBeLessThan100', + type: 'error', + }, + { + inputs: [], + name: 'MustBeNonzero', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'newPrice', + type: 'uint256', + }, + ], + name: 'BaseNetworkPriceSet', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'newPrice', + type: 'uint256', + }, + ], + name: 'MaxNetworkPriceSet', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'stakingAddress', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'usagePercent', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256[]', + name: 'newPrices', + type: 'uint256[]', + }, + ], + name: 'UsageSet', + type: 'event', + }, + { + inputs: [ + { + internalType: 'uint256[]', + name: 'productIds', + type: 'uint256[]', + }, + ], + name: 'baseNetworkPrices', + outputs: [ + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256[]', + name: 'productIds', + type: 'uint256[]', + }, + ], + name: 'getNodesForRequest', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + components: [ + { + components: [ + { + internalType: 'uint32', + name: 'ip', + type: 'uint32', + }, + { + internalType: 'uint128', + name: 'ipv6', + type: 'uint128', + }, + { + internalType: 'uint32', + name: 'port', + type: 'uint32', + }, + { + internalType: 'address', + name: 'nodeAddress', + type: 'address', + }, + { + internalType: 'uint256', + name: 'reward', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'senderPubKey', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'receiverPubKey', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'lastActiveEpoch', + type: 'uint256', + }, + ], + internalType: 'struct LibStakingStorage.Validator', + name: 'validator', + type: 'tuple', + }, + { + internalType: 'uint256[]', + name: 'prices', + type: 'uint256[]', + }, + ], + internalType: + 'struct LibPriceFeedStorage.NodeInfoAndPrices[]', + name: '', + type: 'tuple[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getStakingAddress', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256[]', + name: 'productIds', + type: 'uint256[]', + }, + ], + name: 'maxNetworkPrices', + outputs: [ + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'node', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'productIds', + type: 'uint256[]', + }, + ], + name: 'price', + outputs: [ + { + components: [ + { + internalType: 'address', + name: 'stakerAddress', + type: 'address', + }, + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'productId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + ], + internalType: 'struct LibPriceFeedStorage.NodePriceData[]', + name: '', + type: 'tuple[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'productId', + type: 'uint256', + }, + ], + name: 'prices', + outputs: [ + { + components: [ + { + internalType: 'address', + name: 'stakerAddress', + type: 'address', + }, + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'productId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + ], + internalType: 'struct LibPriceFeedStorage.NodePriceData[]', + name: '', + type: 'tuple[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'newPrice', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'productIds', + type: 'uint256[]', + }, + ], + name: 'setBaseNetworkPrices', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'newPrice', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'productIds', + type: 'uint256[]', + }, + ], + name: 'setMaxNetworkPrices', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'usagePercent', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'productIds', + type: 'uint256[]', + }, + ], + name: 'setUsage', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'usagePercent', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'productId', + type: 'uint256', + }, + ], + name: 'usagePercentToPrice', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'usagePercent', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'productIds', + type: 'uint256[]', + }, + ], + name: 'usagePercentToPrices', + outputs: [ + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + ], + }, LITToken: { abi: JSON.parse(testNetConfig.contractAbis.litToken), }, diff --git a/local-tests/test.ts b/local-tests/test.ts index 6862ca9bf..72cfdb26c 100644 --- a/local-tests/test.ts +++ b/local-tests/test.ts @@ -3,7 +3,9 @@ import { runInBand, runTestsParallel } from './setup/tinny-operations'; // import { testBundleSpeed } from './tests/test-bundle-speed'; // import { testExample } from './tests/test-example'; import { testUseEoaSessionSigsToExecuteJsSigning } from './tests/testUseEoaSessionSigsToExecuteJsSigning'; -import { testUseEoaSessionSigsToPkpSign } from './tests/testUseEoaSessionSigsToPkpSign'; +import { testUseEoaSessionSigsToPkpSignK256 } from 'local-tests/tests/testUseEoaSessionSigsToPkpSignK256'; +import { testUseEoaSessionSigsToPkpSignP256 } from 'local-tests/tests/testUseEoaSessionSigsToPkpSignP256'; +import { testUseEoaSessionSigsToPkpSignP384 } from 'local-tests/tests/testUseEoaSessionSigsToPkpSignP384'; import { testUsePkpSessionSigsToExecuteJsSigning } from './tests/testUsePkpSessionSigsToExecuteJsSigning'; import { testUsePkpSessionSigsToPkpSign } from './tests/testUsePkpSessionSigsToPkpSign'; import { testUseValidLitActionCodeGeneratedSessionSigsToPkpSign } from './tests/testUseValidLitActionCodeGeneratedSessionSigsToPkpSign'; @@ -163,7 +165,9 @@ setLitActionsCodeToLocal(); const eoaSessionSigsTests = { testUseEoaSessionSigsToExecuteJsSigning, - testUseEoaSessionSigsToPkpSign, + testUseEoaSessionSigsToPkpSignK256, + testUseEoaSessionSigsToPkpSignP256, + testUseEoaSessionSigsToPkpSignP384, testUseEoaSessionSigsToExecuteJsSigningInParallel, testUseEoaSessionSigsToExecuteJsClaimKeys, testUseEoaSessionSigsToExecuteJsClaimMultipleKeys, diff --git a/local-tests/tests.ts b/local-tests/tests.ts index 8d42ceebb..531f8d089 100644 --- a/local-tests/tests.ts +++ b/local-tests/tests.ts @@ -1,5 +1,7 @@ import { testUseEoaSessionSigsToExecuteJsSigning } from './tests/testUseEoaSessionSigsToExecuteJsSigning'; -import { testUseEoaSessionSigsToPkpSign } from './tests/testUseEoaSessionSigsToPkpSign'; +import { testUseEoaSessionSigsToPkpSignK256 } from 'local-tests/tests/testUseEoaSessionSigsToPkpSignK256'; +import { testUseEoaSessionSigsToPkpSignP256 } from 'local-tests/tests/testUseEoaSessionSigsToPkpSignP256'; +import { testUseEoaSessionSigsToPkpSignP384 } from 'local-tests/tests/testUseEoaSessionSigsToPkpSignP384'; import { testUsePkpSessionSigsToExecuteJsSigning } from './tests/testUsePkpSessionSigsToExecuteJsSigning'; import { testUsePkpSessionSigsToPkpSign } from './tests/testUsePkpSessionSigsToPkpSign'; import { testUseValidLitActionCodeGeneratedSessionSigsToPkpSign } from './tests/testUseValidLitActionCodeGeneratedSessionSigsToPkpSign'; @@ -103,7 +105,9 @@ import { testFailBatchGeneratePrivateKeysAtomic } from './tests/wrapped-keys/tes import { testUseEoaSessionSigsToRequestSingleResponse } from './tests/testUseEoaSessionSigsToRequestSingleResponse'; export { testUseEoaSessionSigsToExecuteJsSigning } from './tests/testUseEoaSessionSigsToExecuteJsSigning'; -export { testUseEoaSessionSigsToPkpSign } from './tests/testUseEoaSessionSigsToPkpSign'; +export { testUseEoaSessionSigsToPkpSignK256 } from 'local-tests/tests/testUseEoaSessionSigsToPkpSignK256'; +export { testUseEoaSessionSigsToPkpSignP256 } from 'local-tests/tests/testUseEoaSessionSigsToPkpSignP256'; +export { testUseEoaSessionSigsToPkpSignP384 } from 'local-tests/tests/testUseEoaSessionSigsToPkpSignP384'; export { testUsePkpSessionSigsToExecuteJsSigning } from './tests/testUsePkpSessionSigsToExecuteJsSigning'; export { testUsePkpSessionSigsToPkpSign } from './tests/testUsePkpSessionSigsToPkpSign'; export { testUseValidLitActionCodeGeneratedSessionSigsToPkpSign } from './tests/testUseValidLitActionCodeGeneratedSessionSigsToPkpSign'; @@ -251,7 +255,9 @@ const wrappedKeysTests = { const eoaSessionSigsTests = { testUseEoaSessionSigsToExecuteJsSigning, - testUseEoaSessionSigsToPkpSign, + testUseEoaSessionSigsToPkpSignK256, + testUseEoaSessionSigsToPkpSignP256, + testUseEoaSessionSigsToPkpSignP384, testUseEoaSessionSigsToExecuteJsSigningInParallel, testUseEoaSessionSigsToExecuteJsClaimKeys, testUseEoaSessionSigsToExecuteJsClaimMultipleKeys, diff --git a/local-tests/tests/testUseEoaSessionSigsToPkpSignK256.ts b/local-tests/tests/testUseEoaSessionSigsToPkpSignK256.ts new file mode 100644 index 000000000..9a97ffef5 --- /dev/null +++ b/local-tests/tests/testUseEoaSessionSigsToPkpSignK256.ts @@ -0,0 +1,86 @@ +import EC from 'elliptic'; +import { createHash } from 'crypto'; + +import { log } from '@lit-protocol/misc'; +import { getEoaSessionSigs } from 'local-tests/setup/session-sigs/get-eoa-session-sigs'; +import { TinnyEnvironment } from 'local-tests/setup/tinny-environment'; + +/** + * Test Commands: + * ✅ NETWORK=datil-dev yarn test:local --filter=testUseEoaSessionSigsToPkpSignK256 + * ✅ NETWORK=datil-test yarn test:local --filter=testUseEoaSessionSigsToPkpSignK256 + * ✅ NETWORK=custom yarn test:local --filter=testUseEoaSessionSigsToPkpSignK256 + */ +export const testUseEoaSessionSigsToPkpSignK256 = async ( + devEnv: TinnyEnvironment +) => { + const alice = await devEnv.createRandomPerson(); + const messageToSign = [1, 2, 3, 4, 5]; + const messageHash = createHash('sha256') + .update(Buffer.from(messageToSign)) + .digest(); + + const eoaSessionSigs = await getEoaSessionSigs(devEnv, alice); + const runWithSessionSigs = await devEnv.litNodeClient.pkpSign({ + pubKey: alice.pkp.publicKey, + sessionSigs: eoaSessionSigs, + toSign: messageHash, + signingScheme: 'EcdsaK256Sha256', + }); + + devEnv.releasePrivateKeyFromUser(alice); + + // -- assertions + // r, s, dataSigned, and public key should be present + if (!runWithSessionSigs.r) { + throw new Error(`Expected "r" in runWithSessionSigs`); + } + if (!runWithSessionSigs.s) { + throw new Error(`Expected "s" in runWithSessionSigs`); + } + if (!runWithSessionSigs.dataSigned) { + throw new Error(`Expected "dataSigned" in runWithSessionSigs`); + } + if (!runWithSessionSigs.publicKey) { + throw new Error(`Expected "publicKey" in runWithSessionSigs`); + } + + // signature must start with 0x + if (!runWithSessionSigs.signature.startsWith('0x')) { + throw new Error(`Expected "signature" to start with 0x`); + } + + // recid must be parseable as a number + if (isNaN(runWithSessionSigs.recid)) { + throw new Error(`Expected "recid" to be parseable as a number`); + } + + const ec = new EC.ec('secp256k1'); + + // Public key derived from message and signature + const recoveredPubKey = ec.recoverPubKey( + messageHash, + runWithSessionSigs, + runWithSessionSigs.recid + ); + // Public key returned from nodes + const runWithSessionSigsUncompressedPublicKey = ec + .keyFromPublic(runWithSessionSigs.publicKey, 'hex') + .getPublic(false, 'hex'); + + if ( + runWithSessionSigsUncompressedPublicKey !== + recoveredPubKey.encode('hex', false) + ) { + throw new Error( + `Expected recovered public key to match runWithSessionSigsUncompressedPublicKey and recoveredPubKey.encode('hex', false)` + ); + } + if (recoveredPubKey.encode('hex', false) !== alice.pkp.publicKey) { + throw new Error( + `Expected recovered public key to match alice.pkp.publicKey` + ); + } + + log('✅ testUseEoaSessionSigsToPkpSignK256'); +}; diff --git a/local-tests/tests/testUseEoaSessionSigsToPkpSign.ts b/local-tests/tests/testUseEoaSessionSigsToPkpSignP256.ts similarity index 52% rename from local-tests/tests/testUseEoaSessionSigsToPkpSign.ts rename to local-tests/tests/testUseEoaSessionSigsToPkpSignP256.ts index 9ad4340c3..80cbc57c1 100644 --- a/local-tests/tests/testUseEoaSessionSigsToPkpSign.ts +++ b/local-tests/tests/testUseEoaSessionSigsToPkpSignP256.ts @@ -1,4 +1,5 @@ -import { ethers } from 'ethers'; +import EC from 'elliptic'; +import { createHash } from 'crypto'; import { log } from '@lit-protocol/misc'; import { getEoaSessionSigs } from 'local-tests/setup/session-sigs/get-eoa-session-sigs'; @@ -6,34 +7,29 @@ import { TinnyEnvironment } from 'local-tests/setup/tinny-environment'; /** * Test Commands: - * ✅ NETWORK=datil-dev yarn test:local --filter=testUseEoaSessionSigsToPkpSign - * ✅ NETWORK=datil-test yarn test:local --filter=testUseEoaSessionSigsToPkpSign - * ✅ NETWORK=custom yarn test:local --filter=testUseEoaSessionSigsToPkpSign + * ✅ NETWORK=datil-dev yarn test:local --filter=testUseEoaSessionSigsToPkpSignP256 + * ✅ NETWORK=datil-test yarn test:local --filter=testUseEoaSessionSigsToPkpSignP256 + * ✅ NETWORK=custom yarn test:local --filter=testUseEoaSessionSigsToPkpSignP256 */ -export const testUseEoaSessionSigsToPkpSign = async ( +export const testUseEoaSessionSigsToPkpSignP256 = async ( devEnv: TinnyEnvironment ) => { const alice = await devEnv.createRandomPerson(); + const messageToSign = [1, 2, 3, 4, 5]; + const messageHash = createHash('sha256') + .update(Buffer.from(messageToSign)) + .digest(); const eoaSessionSigs = await getEoaSessionSigs(devEnv, alice); const runWithSessionSigs = await devEnv.litNodeClient.pkpSign({ - toSign: alice.loveLetter, pubKey: alice.pkp.publicKey, sessionSigs: eoaSessionSigs, + toSign: messageHash, + signingScheme: 'EcdsaP256Sha256', }); devEnv.releasePrivateKeyFromUser(alice); - // Expected output: - // { - // r: "25fc0d2fecde8ed801e9fee5ad26f2cf61d82e6f45c8ad1ad1e4798d3b747fd9", - // s: "549fe745b4a09536e6e7108d814cf7e44b93f1d73c41931b8d57d1b101833214", - // recid: 1, - // signature: "0x25fc0d2fecde8ed801e9fee5ad26f2cf61d82e6f45c8ad1ad1e4798d3b747fd9549fe745b4a09536e6e7108d814cf7e44b93f1d73c41931b8d57d1b1018332141c", - // publicKey: "04A3CD53CCF63597D3FFCD1DF1E8236F642C7DF8196F532C8104625635DC55A1EE59ABD2959077432FF635DF2CED36CC153050902B71291C4D4867E7DAAF964049", - // dataSigned: "7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4", - // } - // -- assertions // r, s, dataSigned, and public key should be present if (!runWithSessionSigs.r) { @@ -59,29 +55,33 @@ export const testUseEoaSessionSigsToPkpSign = async ( throw new Error(`Expected "recid" to be parseable as a number`); } - const signature = ethers.utils.joinSignature({ - r: '0x' + runWithSessionSigs.r, - s: '0x' + runWithSessionSigs.s, - recoveryParam: runWithSessionSigs.recid, - }); - const recoveredPubKey = ethers.utils.recoverPublicKey( - alice.loveLetter, - signature - ); + const ec = new EC.ec('p256'); - console.log("recoveredPubKey:", recoveredPubKey); + // Public key derived from message and signature + const recoveredPubKey = ec.recoverPubKey( + messageHash, + runWithSessionSigs, + runWithSessionSigs.recid + ); + // Public key returned from nodes + const runWithSessionSigsUncompressedPublicKey = ec + .keyFromPublic(runWithSessionSigs.publicKey, 'hex') + .getPublic(false, 'hex'); - // FIXME: Consider adding these assertions back after the v flipping PR is merged - // if (recoveredPubKey !== `0x${runWithSessionSigs.publicKey.toLowerCase()}`) { - // throw new Error( - // `Expected recovered public key to match runWithSessionSigs.publicKey` - // ); - // } - // if (recoveredPubKey !== `0x${alice.pkp.publicKey.toLowerCase()}`) { + if ( + runWithSessionSigsUncompressedPublicKey !== + recoveredPubKey.encode('hex', false) + ) { + throw new Error( + `Expected recovered public key to match runWithSessionSigsUncompressedPublicKey and recoveredPubKey.encode('hex', false)` + ); + } + // PKP public key lives in k256, it cannot be directly compared + // if (recoveredPubKey.encode('hex', false) !== alice.pkp.publicKey) { // throw new Error( // `Expected recovered public key to match alice.pkp.publicKey` // ); // } - log('✅ testUseEoaSessionSigsToPkpSign'); + log('✅ testUseEoaSessionSigsToPkpSignP256'); }; diff --git a/local-tests/tests/testUseEoaSessionSigsToPkpSignP384.ts b/local-tests/tests/testUseEoaSessionSigsToPkpSignP384.ts new file mode 100644 index 000000000..9519e57fb --- /dev/null +++ b/local-tests/tests/testUseEoaSessionSigsToPkpSignP384.ts @@ -0,0 +1,87 @@ +import EC from 'elliptic'; +import { createHash } from 'crypto'; + +import { log } from '@lit-protocol/misc'; +import { getEoaSessionSigs } from 'local-tests/setup/session-sigs/get-eoa-session-sigs'; +import { TinnyEnvironment } from 'local-tests/setup/tinny-environment'; + +/** + * Test Commands: + * ✅ NETWORK=datil-dev yarn test:local --filter=testUseEoaSessionSigsToPkpSignP384 + * ✅ NETWORK=datil-test yarn test:local --filter=testUseEoaSessionSigsToPkpSignP384 + * ✅ NETWORK=custom yarn test:local --filter=testUseEoaSessionSigsToPkpSignP384 + */ +export const testUseEoaSessionSigsToPkpSignP384 = async ( + devEnv: TinnyEnvironment +) => { + const alice = await devEnv.createRandomPerson(); + const messageToSign = [1, 2, 3, 4, 5]; + const messageHash = createHash('sha384') + .update(Buffer.from(messageToSign)) + .digest(); + + const eoaSessionSigs = await getEoaSessionSigs(devEnv, alice); + const runWithSessionSigs = await devEnv.litNodeClient.pkpSign({ + pubKey: alice.pkp.publicKey, + sessionSigs: eoaSessionSigs, + toSign: messageHash, + signingScheme: 'EcdsaP384Sha384', + }); + + devEnv.releasePrivateKeyFromUser(alice); + + // -- assertions + // r, s, dataSigned, and public key should be present + if (!runWithSessionSigs.r) { + throw new Error(`Expected "r" in runWithSessionSigs`); + } + if (!runWithSessionSigs.s) { + throw new Error(`Expected "s" in runWithSessionSigs`); + } + if (!runWithSessionSigs.dataSigned) { + throw new Error(`Expected "dataSigned" in runWithSessionSigs`); + } + if (!runWithSessionSigs.publicKey) { + throw new Error(`Expected "publicKey" in runWithSessionSigs`); + } + + // signature must start with 0x + if (!runWithSessionSigs.signature.startsWith('0x')) { + throw new Error(`Expected "signature" to start with 0x`); + } + + // recid must be parseable as a number + if (isNaN(runWithSessionSigs.recid)) { + throw new Error(`Expected "recid" to be parseable as a number`); + } + + const ec = new EC.ec('p384'); + + // Public key derived from message and signature + const recoveredPubKey = ec.recoverPubKey( + messageHash, + runWithSessionSigs, + runWithSessionSigs.recid + ); // Error: The recovery param is more than two bits + // Public key returned from nodes + const runWithSessionSigsUncompressedPublicKey = ec + .keyFromPublic(runWithSessionSigs.publicKey, 'hex') + .getPublic(false, 'hex'); + + if ( + runWithSessionSigsUncompressedPublicKey !== + recoveredPubKey.encode('hex', false) + ) { + throw new Error( + `Expected recovered public key to match runWithSessionSigsUncompressedPublicKey and recoveredPubKey.encode('hex', false)` + ); + } + // PKP public key lives in k256, it cannot be directly compared + // if (recoveredPubKey.encode('hex', false) !== alice.pkp.publicKey) { + // throw new Error( + // `Expected recovered public key to match alice.pkp.publicKey` + // ); + // } + + log('✅ testUseEoaSessionSigsToPkpSignP384'); +}; diff --git a/package.json b/package.json index c25670178..c87a0f4e8 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "cross-fetch": "3.1.8", "date-and-time": "^2.4.1", "depd": "^2.0.0", + "elliptic": "^6.6.1", "ethers": "^5.7.1", "jose": "^4.14.4", "micromodal": "^0.4.10", @@ -87,6 +88,7 @@ "@nx/web": "17.3.0", "@solana/web3.js": "1.95.3", "@types/depd": "^1.1.36", + "@types/elliptic": "^6.4.18", "@types/events": "^3.0.3", "@types/jest": "27.4.1", "@types/node": "18.19.18", diff --git a/packages/constants/src/lib/constants/constants.ts b/packages/constants/src/lib/constants/constants.ts index 2cde80b50..80a108a2b 100644 --- a/packages/constants/src/lib/constants/constants.ts +++ b/packages/constants/src/lib/constants/constants.ts @@ -1285,6 +1285,8 @@ export const LIT_CURVE = { EcdsaCaitSith: 'ECDSA_CAIT_SITH', // Legacy alias of K256 EcdsaCAITSITHP256: 'EcdsaCaitSithP256', EcdsaK256Sha256: 'EcdsaK256Sha256', // same as caitsith + EcdsaP256Sha256: 'EcdsaP256Sha256', + EcdsaP384Sha384: 'EcdsaP384Sha384', } as const; export type LIT_CURVE_TYPE = keyof typeof LIT_CURVE; diff --git a/packages/constants/src/lib/constants/mappers.ts b/packages/constants/src/lib/constants/mappers.ts index c0071a6d3..ebc9eaade 100644 --- a/packages/constants/src/lib/constants/mappers.ts +++ b/packages/constants/src/lib/constants/mappers.ts @@ -11,9 +11,9 @@ const deprecated = depd('lit-js-sdk:constants:mappers'); */ export const NETWORK_CONTEXT_BY_NETWORK: { [key in LIT_NETWORK_VALUES]: - | typeof datilDev - | typeof datilTest - | typeof datil; + | typeof datilDev + | typeof datilTest + | typeof datil; } = { 'datil-dev': datilDev, 'datil-test': datilTest, @@ -34,13 +34,13 @@ export const GLOBAL_OVERWRITE_IPFS_CODE_BY_NETWORK: { /** * Product IDs used for price feed and node selection - * + * * - DECRYPTION (0): Used for decryption operations - * - SIGN (1): Used for signing operations + * - SIGN (1): Used for signing operations * - LA (2): Used for Lit Actions execution */ export const PRODUCT_IDS = { DECRYPTION: 0, // For decryption operations SIGN: 1, // For signing operations LA: 2, // For Lit Actions execution -} as const; \ No newline at end of file +} as const; diff --git a/packages/contracts-sdk/src/lib/contracts-sdk.ts b/packages/contracts-sdk/src/lib/contracts-sdk.ts index a34953489..8882a563a 100644 --- a/packages/contracts-sdk/src/lib/contracts-sdk.ts +++ b/packages/contracts-sdk/src/lib/contracts-sdk.ts @@ -908,6 +908,12 @@ export class LitContracts { environment ); break; + case 'PriceFeed': + address = await resolverContract['getContract']( + await resolverContract['PRICE_FEED_CONTRACT'](), + environment + ); + break; case 'PubkeyRouter': address = await resolverContract['getContract']( await resolverContract['PUB_KEY_ROUTER_CONTRACT'](), @@ -932,6 +938,8 @@ export class LitContracts { environment ); break; + default: + throw new Error('Wrong contract name'); // TODO improve this error } return address; @@ -1244,14 +1252,14 @@ export class LitContracts { /** * Gets price feed information for nodes in the network. - * + * * @param {Object} params - The parameters object * @param {LIT_NETWORKS_KEYS} params.litNetwork - The Lit network to get price feed info for * @param {LitContractContext | LitContractResolverContext} [params.networkContext] - Optional network context * @param {string} [params.rpcUrl] - Optional RPC URL to use * @param {number[]} [params.productIds] - Optional array of product IDs to get prices for. Defaults to [DECRYPTION, LA, SIGN] * @param {typeof HTTP | typeof HTTPS | null} [params.nodeProtocol] - Optional node protocol to use - * + * * @returns {Promise<{ * epochId: number, * minNodeCount: number, @@ -1259,7 +1267,7 @@ export class LitContracts { * arr: Array<{network: string, price: number}>, * mapByAddress: Record * } - * }>} + * }>} */ public static getPriceFeedInfo = async ({ litNetwork, @@ -1273,7 +1281,6 @@ export class LitContracts { nodeProtocol?: typeof HTTP | typeof HTTPS | null; productIds?: (typeof PRODUCT_IDS)[keyof typeof PRODUCT_IDS][]; }): Promise => { - if (!productIds || productIds.length === 0) { log('No product IDs provided. Defaulting to 0'); productIds = [PRODUCT_IDS.DECRYPTION, PRODUCT_IDS.LA, PRODUCT_IDS.SIGN]; @@ -1282,7 +1289,12 @@ export class LitContracts { // check if productIds is any numbers in the PRODUCT_IDS object productIds.forEach((productId) => { if (!Object.values(PRODUCT_IDS).includes(productId)) { - throw new Error(`❌ Invalid product ID: ${productId}. We only accept ${Object.values(PRODUCT_IDS).join(', ')}`); + // TODO improve this error + throw new Error( + `❌ Invalid product ID: ${productId}. We only accept ${Object.values( + PRODUCT_IDS + ).join(', ')}` + ); } }); diff --git a/packages/crypto/src/lib/crypto.ts b/packages/crypto/src/lib/crypto.ts index 131cda6f7..beb4d9d75 100644 --- a/packages/crypto/src/lib/crypto.ts +++ b/packages/crypto/src/lib/crypto.ts @@ -1,4 +1,4 @@ -import { splitSignature } from 'ethers/lib/utils'; +import { joinSignature, splitSignature } from 'ethers/lib/utils'; import { InvalidParamType, @@ -8,13 +8,12 @@ import { NoValidShares, UnknownError, } from '@lit-protocol/constants'; -import { checkType, log } from '@lit-protocol/misc'; +import { log } from '@lit-protocol/misc'; import { nacl } from '@lit-protocol/nacl'; import { CombinedECDSASignature, NodeAttestation, SessionKeyPair, - SigningAccessControlConditionJWTPayload, SigShare, } from '@lit-protocol/types'; import { @@ -34,6 +33,49 @@ import { sevSnpVerify, } from '@lit-protocol/wasm'; +export function joinEcdsaSignature(signature: { + r: string; + s: string; + recid: number; +}): string { + if (signature.r.length === 98) { + // For P384 signatures (48 bytes/98 hex each for r and s) + const rBuf = Buffer.from(signature.r.replace('0x', ''), 'hex'); + const sBuf = Buffer.from(signature.s.replace('0x', ''), 'hex'); + const recIdBuf = Buffer.from([signature.recid]); + const joinedSignature = + '0x' + Buffer.concat([rBuf, sBuf, recIdBuf]).toString('hex'); + return joinedSignature; + } else { + // For K256/P256 signatures + const joinedSignature = joinSignature({ + r: signature.r, + s: signature.s, + recoveryParam: signature.recid, + }); + return joinedSignature; + } +} + +export function splitEcdsaSignature(signature: Buffer): CombinedECDSASignature { + if (signature.length === 97) { + // For P384 signatures (97 bytes) + return { + r: '0x' + signature.slice(0, 48).toString('hex'), + s: '0x' + signature.slice(48, 96).toString('hex'), + recid: signature[96], + }; + } else { + // For K256/P256 signatures (64/65 bytes) + const ethersSignature = splitSignature(signature); + return { + r: ethersSignature.r, + s: ethersSignature.s, + recid: ethersSignature.recoveryParam, + }; + } +} + /** ---------- Exports ---------- */ const LIT_CORS_PROXY = `https://cors.litgateway.com`; @@ -167,6 +209,8 @@ const ecdsaSigntureTypeMap: Partial> = { [LIT_CURVE.EcdsaK256]: 'K256', [LIT_CURVE.EcdsaCAITSITHP256]: 'P256', [LIT_CURVE.EcdsaK256Sha256]: 'K256', + [LIT_CURVE.EcdsaP256Sha256]: 'P256', + [LIT_CURVE.EcdsaP384Sha384]: 'P384', }; /** @@ -214,14 +258,14 @@ export const combineEcdsaShares = async ( await ecdsaVerify(variant!, messageHash, publicKey, [r, s, recId]); - const signature = splitSignature( - Buffer.concat([r, s, Buffer.from([recId + 27])]) + const signature = splitEcdsaSignature( + Buffer.concat([r, s, Buffer.from([recId])]) ); return { r: signature.r.slice('0x'.length), s: signature.s.slice('0x'.length), - recid: signature.recoveryParam, + recid: signature.recid, }; }; diff --git a/packages/event-listener/package.json b/packages/event-listener/package.json index d502769ad..10ecae875 100644 --- a/packages/event-listener/package.json +++ b/packages/event-listener/package.json @@ -26,7 +26,7 @@ "scripts": { "generate-lit-actions": "yarn node ./esbuild.config.js" }, - "version": "7.0.3", + "version": "8.0.0-alpha.0", "main": "./dist/src/index.js", "typings": "./dist/src/index.d.ts" } diff --git a/packages/lit-node-client-nodejs/src/lib/helpers/get-signatures.test.ts b/packages/lit-node-client-nodejs/src/lib/helpers/get-signatures.test.ts index 766943f25..0c00ffcfb 100644 --- a/packages/lit-node-client-nodejs/src/lib/helpers/get-signatures.test.ts +++ b/packages/lit-node-client-nodejs/src/lib/helpers/get-signatures.test.ts @@ -1,4 +1,4 @@ -import { SigResponse } from '@lit-protocol/types'; +import { ECDSASigResponse } from '@lit-protocol/types'; import { getSignatures } from './get-signatures'; @@ -49,7 +49,7 @@ describe('getSignatures', () => { ]; const requestId = ''; - const signatures = await getSignatures<{ sig: SigResponse }>({ + const signatures = await getSignatures<{ sig: ECDSASigResponse }>({ networkPubKeySet, minNodeCount, signedData, diff --git a/packages/lit-node-client-nodejs/src/lib/helpers/get-signatures.ts b/packages/lit-node-client-nodejs/src/lib/helpers/get-signatures.ts index b96a41900..cbd818feb 100644 --- a/packages/lit-node-client-nodejs/src/lib/helpers/get-signatures.ts +++ b/packages/lit-node-client-nodejs/src/lib/helpers/get-signatures.ts @@ -1,5 +1,3 @@ -import { joinSignature } from 'ethers/lib/utils'; - import { LIT_CURVE, NoValidShares, @@ -7,13 +5,13 @@ import { UnknownSignatureError, UnknownSignatureType, } from '@lit-protocol/constants'; -import { combineEcdsaShares } from '@lit-protocol/crypto'; +import { combineEcdsaShares, joinEcdsaSignature } from '@lit-protocol/crypto'; import { logErrorWithRequestId, logWithRequestId, mostCommonString, } from '@lit-protocol/misc'; -import { SigResponse, SigShare } from '@lit-protocol/types'; +import { ECDSASigResponse, SigShare } from '@lit-protocol/types'; export const getFlattenShare = (share: any): SigShare => { // flatten the signature object so that the properties of the signature are top level @@ -90,7 +88,7 @@ export const getFlattenShare = (share: any): SigShare => { * @param {number} params.minNodeCount - The threshold number of nodes * @param {any[]} params.signedData - The array of signature shares from each node. * @param {string} [params.requestId=''] - The optional request ID for logging purposes. - * @returns {T | { signature: SigResponse; sig: SigResponse }} - The final signatures or an object containing the final signatures. + * @returns {T | { signature: ECDSASigResponse; sig: ECDSASigResponse }} - The final signatures or an object containing the final signatures. * * @example * @@ -102,7 +100,7 @@ export const getSignatures = async (params: { minNodeCount: number; signedData: any[]; requestId: string; -}): Promise => { +}): Promise => { const { networkPubKeySet, minNodeCount, signedData, requestId } = params; const initialKeys = [...new Set(signedData.flatMap((i) => Object.keys(i)))]; @@ -168,7 +166,7 @@ export const getSignatures = async (params: { shares.sort((a, b) => a.shareIndex - b.shareIndex); - let sigName = shares[0].sigName; + const sigName = shares[0].sigName; logWithRequestId( requestId, @@ -231,10 +229,14 @@ export const getSignatures = async (params: { // -- validate if signature type is ECDSA if ( - sigType !== LIT_CURVE.EcdsaCaitSith && - sigType !== LIT_CURVE.EcdsaK256 && - sigType !== LIT_CURVE.EcdsaCAITSITHP256 && - sigType! == LIT_CURVE.EcdsaK256Sha256 + ![ + LIT_CURVE.EcdsaCaitSith, + LIT_CURVE.EcdsaK256, + LIT_CURVE.EcdsaCAITSITHP256, + LIT_CURVE.EcdsaK256Sha256, + LIT_CURVE.EcdsaP256Sha256, + LIT_CURVE.EcdsaP384Sha384, + ].includes(sigType) ) { throw new UnknownSignatureType( { @@ -261,10 +263,10 @@ export const getSignatures = async (params: { ); } - const encodedSig = joinSignature({ + const encodedSig = joinEcdsaSignature({ r: '0x' + signature.r, s: '0x' + signature.s, - recoveryParam: signature.recid, + recid: signature.recid, }); signatures[key] = { diff --git a/packages/lit-node-client-nodejs/src/lib/helpers/parse-pkp-sign-response.ts b/packages/lit-node-client-nodejs/src/lib/helpers/parse-pkp-sign-response.ts index 02619392b..2cbb5a003 100644 --- a/packages/lit-node-client-nodejs/src/lib/helpers/parse-pkp-sign-response.ts +++ b/packages/lit-node-client-nodejs/src/lib/helpers/parse-pkp-sign-response.ts @@ -17,7 +17,9 @@ export const snakeToCamel = (s: string): string => * @param obj - The object whose keys need to be converted. * @returns The object with keys converted to camelCase. */ -export const convertKeysToCamelCase = (obj: { [key: string]: any }): any => +export const convertKeysToCamelCase = >( + obj: T +): Record => Object.keys(obj).reduce( (acc, key) => ({ ...acc, @@ -31,7 +33,9 @@ export const convertKeysToCamelCase = (obj: { [key: string]: any }): any => * @param obj - The object to clean string values from. * @returns A new object with string values cleaned. */ -export const cleanStringValues = (obj: { [key: string]: any }): any => +export const cleanStringValues = >( + obj: T +): Record => Object.keys(obj).reduce( (acc, key) => ({ ...acc, @@ -54,7 +58,7 @@ export const parsePkpSignResponse = ( delete signatureShare.result; const camelCaseShare = convertKeysToCamelCase(signatureShare); - const cleanedShare = cleanStringValues(camelCaseShare); + const cleanedShare = cleanStringValues(camelCaseShare) as any; // TODO fix the types in this file // Change 'dataSigned' from 'digest' if (cleanedShare.digest) { diff --git a/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts b/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts index b0cfc219e..e811ad3c3 100644 --- a/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts +++ b/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts @@ -127,7 +127,7 @@ import type { SessionKeyPair, SessionSigningTemplate, SessionSigsMap, - SigResponse, + ECDSASigResponse, SignSessionKeyProp, SignSessionKeyResponse, Signature, @@ -1099,7 +1099,7 @@ export class LitNodeClientNodeJs * @param params.sessionSigs - The session signatures to use * @param params.authMethods - (optional) The auth methods to use */ - pkpSign = async (params: JsonPkpSignSdkParams): Promise => { + pkpSign = async (params: JsonPkpSignSdkParams): Promise => { // -- validate required params const requiredParamKeys = ['toSign', 'pubKey']; @@ -1159,6 +1159,7 @@ export class LitNodeClientNodeJs const reqBody: JsonPkpSignRequest = { toSign: normalizeArray(params.toSign), + signingScheme: params.signingScheme, pubkey: hexPrefixed(params.pubKey), authSig: sessionSig, @@ -1181,7 +1182,7 @@ export class LitNodeClientNodeJs return this.generatePromise(urlWithPath, reqBody, requestId); }); - const res = await this.handleNodePromises( + const res = await this.handleNodePromises( nodePromises, requestId, this.connectedNodes.size @@ -1190,11 +1191,11 @@ export class LitNodeClientNodeJs // ========== Handle Response ========== // -- case: promises rejected if (!res.success) { - this._throwNodeError(res, requestId); + return this._throwNodeError(res, requestId); } - // -- case: promises success (TODO: check the keys of "values") - const responseData = (res as SuccessNodePromises).values; + // -- case: promises success + const responseData = res.values; logWithRequestId( requestId, @@ -1207,7 +1208,7 @@ export class LitNodeClientNodeJs const signedDataList = parsePkpSignResponse(responseData); try { - const signatures = await getSignatures<{ signature: SigResponse }>({ + const signatures = await getSignatures<{ signature: ECDSASigResponse }>({ requestId, networkPubKeySet: this.networkPubKeySet, minNodeCount: this.config.minNodeCount, diff --git a/packages/pkp-base/src/lib/pkp-base.ts b/packages/pkp-base/src/lib/pkp-base.ts index 2770a4223..5ef6de0eb 100644 --- a/packages/pkp-base/src/lib/pkp-base.ts +++ b/packages/pkp-base/src/lib/pkp-base.ts @@ -21,7 +21,7 @@ import { PKPBaseProp, AuthSig, PKPBaseDefaultParams, - SigResponse, + ECDSASigResponse, RPCUrls, AuthMethod, SessionSigsMap, @@ -399,7 +399,7 @@ export class PKPBase { * * @throws {Error} - Throws an error if `pkpPubKey` is not provided, if `controllerAuthSig` or `controllerSessionSigs` is not provided, if `controllerSessionSigs` is not an object, or if an error occurs during the signing process. */ - async runSign(toSign: Uint8Array): Promise { + async runSign(toSign: Uint8Array): Promise { await this.ensureLitNodeClientReady(); // If no PKP public key is provided, throw error @@ -419,6 +419,7 @@ export class PKPBase { toSign, pubKey: this.uncompressedPubKey, sessionSigs: controllerSessionSigs, + signingScheme: 'EcdsaK256Sha256', }); if (!sig) { diff --git a/packages/pkp-cosmos/src/lib/pkp-cosmos.ts b/packages/pkp-cosmos/src/lib/pkp-cosmos.ts index 3438ce657..6aac53013 100644 --- a/packages/pkp-cosmos/src/lib/pkp-cosmos.ts +++ b/packages/pkp-cosmos/src/lib/pkp-cosmos.ts @@ -42,7 +42,7 @@ import { PKPClientHelpers, PKPCosmosWalletProp, PKPWallet, - SigResponse, + ECDSASigResponse, } from '@lit-protocol/types'; export { Long } from 'cosmjs-types/helpers'; @@ -338,7 +338,7 @@ export class PKPCosmosWallet * * @throws {Error} - Throws an error if `pkpPubKey` is not provided, if `controllerAuthSig` or `controllerSessionSigs` is not provided, if `controllerSessionSigs` is not an object, or if an error occurs during the signing process. */ - async runSign(toSign: Uint8Array): Promise { + async runSign(toSign: Uint8Array): Promise { return this.pkpBase.runSign(toSign); } } diff --git a/packages/pkp-ethers/src/lib/pkp-ethers.ts b/packages/pkp-ethers/src/lib/pkp-ethers.ts index 179ec04e6..cb259595c 100644 --- a/packages/pkp-ethers/src/lib/pkp-ethers.ts +++ b/packages/pkp-ethers/src/lib/pkp-ethers.ts @@ -49,7 +49,7 @@ import { PKPClientHelpers, PKPEthersWalletProp, PKPWallet, - SigResponse, + ECDSASigResponse, } from '@lit-protocol/types'; import { ethRequestHandler } from './handler'; @@ -607,7 +607,7 @@ export class PKPEthersWallet * * @throws {Error} - Throws an error if `pkpPubKey` is not provided, if `controllerAuthSig` or `controllerSessionSigs` is not provided, if `controllerSessionSigs` is not an object, or if an error occurs during the signing process. */ - async runSign(toSign: Uint8Array): Promise { + async runSign(toSign: Uint8Array): Promise { return this.pkpBase.runSign(toSign); } } diff --git a/packages/pkp-sui/src/lib/pkp-sui.ts b/packages/pkp-sui/src/lib/pkp-sui.ts index 776187b29..f024ae1d5 100644 --- a/packages/pkp-sui/src/lib/pkp-sui.ts +++ b/packages/pkp-sui/src/lib/pkp-sui.ts @@ -31,7 +31,7 @@ import { blake2b } from '@noble/hashes/blake2b'; import { sha256 } from '@noble/hashes/sha256'; import { PKPBase } from '@lit-protocol/pkp-base'; -import { PKPBaseProp, PKPWallet, SigResponse } from '@lit-protocol/types'; +import { PKPBaseProp, PKPWallet, ECDSASigResponse } from '@lit-protocol/types'; import { InvalidArgumentException, UnknownError, @@ -331,7 +331,7 @@ export class PKPSuiWallet implements PKPWallet, Signer { * * @throws {Error} - Throws an error if `pkpPubKey` is not provided, if `controllerAuthSig` or `controllerSessionSigs` is not provided, if `controllerSessionSigs` is not an object, or if an error occurs during the signing process. */ - async runSign(toSign: Uint8Array): Promise { + async runSign(toSign: Uint8Array): Promise { return this.pkpBase.runSign(toSign); } } diff --git a/packages/types/src/lib/interfaces.ts b/packages/types/src/lib/interfaces.ts index 3dbaa3cc6..8b4817e9a 100644 --- a/packages/types/src/lib/interfaces.ts +++ b/packages/types/src/lib/interfaces.ts @@ -232,9 +232,32 @@ pub struct JsonExecutionRequest { } */ +export type SigningScheme = + | 'Bls12381' + | 'EcdsaK256Sha256' + | 'EcdsaP256Sha256' + | 'EcdsaP384Sha384' + | 'SchnorrEd25519Sha512' + | 'SchnorrK256Sha256' + | 'SchnorrP256Sha256' + | 'SchnorrP384Sha384' + | 'SchnorrRistretto25519Sha512' + | 'SchnorrEd448Shake256' + | 'SchnorrRedJubjubBlake2b512' + | 'SchnorrK256Taproot' + | 'SchnorrRedDecaf377Blake2b512' + | 'SchnorrkelSubstrate' + | 'Bls12381G1'; + +export interface Node { + socketAddress: string; + value: number; +} + export interface BaseJsonPkpSignRequest { authMethods?: AuthMethod[]; toSign: ArrayLike; + signingScheme: SigningScheme; } /** @@ -615,7 +638,14 @@ export interface GetSigningShareForDecryptionRequest extends JsonAccsRequest { dataToEncryptHash: string; } -export interface SigResponse { +export interface FrostSigResponse { + // TODO Add Schnorr signature version of r, s and v/recid + signature: string; + publicKey: string; + dataSigned: string; +} + +export interface ECDSASigResponse { r: string; s: string; recid: number; @@ -627,7 +657,7 @@ export interface SigResponse { export interface ExecuteJsResponseBase { signatures: | { - sig: SigResponse; + sig: ECDSASigResponse; } | any; } @@ -667,12 +697,15 @@ export interface SendNodeCommand { requestId: string; } export interface SigShare { + // TODO check with SigningScheme sigType: | 'BLS' | 'K256' | 'ECDSA_CAIT_SITH' // Legacy alias of K256 | 'EcdsaCaitSithP256' - | 'EcdsaK256Sha256'; + | 'EcdsaK256Sha256' + | 'EcdsaP256Sha256' + | 'EcdsaP384Sha384'; signatureShare: string; shareIndex?: number; @@ -1267,7 +1300,7 @@ export interface PKPWallet { getAddress: () => Promise; init: () => Promise; runLitAction: (toSign: Uint8Array, sigName: string) => Promise; - runSign: (toSign: Uint8Array) => Promise; + runSign: (toSign: Uint8Array) => Promise; } export type PKPEthersWalletProp = Omit< diff --git a/packages/types/src/lib/types.ts b/packages/types/src/lib/types.ts index 24927dc67..53153b476 100644 --- a/packages/types/src/lib/types.ts +++ b/packages/types/src/lib/types.ts @@ -217,11 +217,11 @@ export type ContractName = keyof ExclusiveLitContractContext; */ export interface LitContractResolverContext { [index: string]: - | string - | LitContractContext - | ethers.providers.JsonRpcProvider - | undefined - | number; + | string + | LitContractContext + | ethers.providers.JsonRpcProvider + | undefined + | number; resolverAddress: string; abi: any; environment: number; @@ -270,11 +270,11 @@ export type EpochInfo = { timeout: number; }; -export type PriceFeedInfo = { +export interface PriceFeedInfo { epochId: number; minNodeCount: number; networkPrices: { - arr: Array<{ network: string, price: number }>, - mapByAddress: Record - } -} \ No newline at end of file + arr: { network: string; price: number }[]; + mapByAddress: Record; + }; +} diff --git a/packages/wasm/rust/Cargo.toml b/packages/wasm/rust/Cargo.toml index 033624672..6195ece48 100644 --- a/packages/wasm/rust/Cargo.toml +++ b/packages/wasm/rust/Cargo.toml @@ -18,7 +18,7 @@ blsful = { version = "2.5.7", default-features = false, features = ["rust"] } base64_light = "0.1" getrandom = { version = "0.2", features = ["js"] } hex = "0.4" -hd-keys-curves-wasm = { version = "1.0.1", default-features = false, features = ["k256", "p256"] } +hd-keys-curves-wasm = { version = "1.0.1", default-features = false, features = ["k256", "p256", "p384"] } serde = "1.0" serde_json = "1.0" serde_bare = "0.5" @@ -27,6 +27,7 @@ serde-wasm-bindgen = "0.6" elliptic-curve = "0.13" k256 = { version = "0.13", features = ["arithmetic"] } p256 = { version = "0.13", features = ["arithmetic"] } +p384 = { version = "0.13", features = ["arithmetic"] } sha2 = "0.10" wee_alloc = { version = "0.4.5", optional = true } diff --git a/packages/wasm/rust/src/ecdsa.rs b/packages/wasm/rust/src/ecdsa.rs index d89df1b5f..217da38d9 100644 --- a/packages/wasm/rust/src/ecdsa.rs +++ b/packages/wasm/rust/src/ecdsa.rs @@ -12,6 +12,7 @@ use hd_keys_curves_wasm::{HDDerivable, HDDeriver}; use js_sys::Uint8Array; use k256::Secp256k1; use p256::NistP256; +use p384::NistP384; use serde::Deserialize; use serde_bytes::Bytes; use tsify::Tsify; @@ -24,6 +25,7 @@ use crate::abi::{from_js, into_js, into_uint8array, JsResult}; pub enum EcdsaVariant { K256, P256, + P384, } struct Ecdsa(C); @@ -40,6 +42,10 @@ impl HdCtx for NistP256 { const CTX: &'static [u8] = b"LIT_HD_KEY_ID_P256_XMD:SHA-256_SSWU_RO_NUL_"; } +impl HdCtx for NistP384 { + const CTX: &'static [u8] = b"LIT_HD_KEY_ID_P384_XMD:SHA-384_SSWU_RO_NUL_"; +} + #[wasm_bindgen] extern "C" { #[wasm_bindgen(typescript_type = "[Uint8Array, Uint8Array, number]")] @@ -279,6 +285,13 @@ pub fn ecdsa_combine_and_verify_with_derived_key( id, public_keys, ), + EcdsaVariant::P384 => Ecdsa::::combine_and_verify_with_derived_key( + pre_signature, + signature_shares, + message_hash, + id, + public_keys, + ), } } @@ -304,6 +317,12 @@ pub fn ecdsa_combine_and_verify( message_hash, public_key, ), + EcdsaVariant::P384 => Ecdsa::::combine_and_verify_with_specified_key( + pre_signature, + signature_shares, + message_hash, + public_key, + ), } } @@ -317,6 +336,7 @@ pub fn ecdsa_combine( match variant { EcdsaVariant::K256 => Ecdsa::::combine(presignature, signature_shares), EcdsaVariant::P256 => Ecdsa::::combine(presignature, signature_shares), + EcdsaVariant::P384 => Ecdsa::::combine(presignature, signature_shares), } } @@ -330,6 +350,7 @@ pub fn ecdsa_verify( match variant { EcdsaVariant::K256 => Ecdsa::::verify(message_hash, public_key, signature), EcdsaVariant::P256 => Ecdsa::::verify(message_hash, public_key, signature), + EcdsaVariant::P384 => Ecdsa::::verify(message_hash, public_key, signature), } } @@ -342,5 +363,6 @@ pub fn ecdsa_derive_key( match variant { EcdsaVariant::K256 => Ecdsa::::derive_key(id, public_keys), EcdsaVariant::P256 => Ecdsa::::derive_key(id, public_keys), + EcdsaVariant::P384 => Ecdsa::::derive_key(id, public_keys), } } diff --git a/yarn.lock b/yarn.lock index a50832294..ff3d3f83b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5111,6 +5111,13 @@ dependencies: "@babel/types" "^7.20.7" +"@types/bn.js@*", "@types/bn.js@^5.1.0", "@types/bn.js@^5.1.1": + version "5.1.6" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.6.tgz#9ba818eec0c85e4d3c679518428afdf611d03203" + integrity sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w== + dependencies: + "@types/node" "*" + "@types/bn.js@^4.11.3": version "4.11.6" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" @@ -5118,13 +5125,6 @@ dependencies: "@types/node" "*" -"@types/bn.js@^5.1.0", "@types/bn.js@^5.1.1": - version "5.1.6" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.6.tgz#9ba818eec0c85e4d3c679518428afdf611d03203" - integrity sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w== - dependencies: - "@types/node" "*" - "@types/cacheable-request@^6.0.1", "@types/cacheable-request@^6.0.2": version "6.0.3" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" @@ -5149,6 +5149,13 @@ dependencies: "@types/node" "*" +"@types/elliptic@^6.4.18": + version "6.4.18" + resolved "https://registry.yarnpkg.com/@types/elliptic/-/elliptic-6.4.18.tgz#bc96e26e1ccccbabe8b6f0e409c85898635482e1" + integrity sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw== + dependencies: + "@types/bn.js" "*" + "@types/events@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.3.tgz#a8ef894305af28d1fc6d2dfdfc98e899591ea529" @@ -10452,7 +10459,7 @@ elliptic@6.6.0: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4, elliptic@^6.5.5, elliptic@^6.5.7: +elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4, elliptic@^6.5.5, elliptic@^6.5.7, elliptic@^6.6.1: version "6.6.1" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.1.tgz#3b8ffb02670bf69e382c7f65bf524c97c5405c06" integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==