diff --git a/local-tests/build.mjs b/local-tests/build.mjs index 9ab37f131..85edfc1a5 100644 --- a/local-tests/build.mjs +++ b/local-tests/build.mjs @@ -35,6 +35,7 @@ const createBuildConfig = (entry, outfile, globalName) => ({ format: 'esm', inject: [getPath('./shim.mjs')], mainFields: ['module', 'main'], + sourcemap: true, ...(globalName ? { globalName } : {}), }); diff --git a/local-tests/package.json b/local-tests/package.json index 704077d5c..1a9b87853 100644 --- a/local-tests/package.json +++ b/local-tests/package.json @@ -1,6 +1,6 @@ { "name": "@lit-protocol/tinny", - "version": "0.0.15", + "version": "0.0.19", "description": "A package to run the test script for Lit Protocol with custom commands", "type": "module", "main": "./index.js", @@ -89,9 +89,9 @@ "@solana/web3.js": "^1.95.3", "bech32": "^2.0.0", "pako": "^2.1.0", - "@lit-protocol/misc": "^7.0.0", - "@lit-protocol/lit-node-client": "^7.0.0", - "@lit-protocol/lit-auth-client": "^7.0.0", - "@lit-protocol/contracts": "^0.0.71" + "@lit-protocol/misc": "^8.0.0-alpha.0", + "@lit-protocol/lit-node-client": "^8.0.0-alpha.0", + "@lit-protocol/lit-auth-client": "^8.0.0-alpha.0", + "@lit-protocol/contracts": "^0.1.10" } } diff --git a/local-tests/setup/networkContext.json b/local-tests/setup/networkContext.json index e2e4f67f5..47b471331 100644 --- a/local-tests/setup/networkContext.json +++ b/local-tests/setup/networkContext.json @@ -1,6 +1,6 @@ { "Allowlist": { - "address": "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F", + "address": "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9", "abi": [ { "inputs": [], @@ -231,7 +231,7 @@ "name": "Allowlist" }, "LITToken": { - "address": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "address": "0x59b670e9fA9D0A427751Af201D676719a970857b", "abi": [ { "inputs": [ @@ -1279,7 +1279,7 @@ "name": "LITToken" }, "Multisender": { - "address": "0x4826533B4897376654Bb4d4AD88B7faFD0C98528", + "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029", "abi": [ { "anonymous": false, @@ -1388,7 +1388,7 @@ "name": "Multisender" }, "PKPHelper": { - "address": "0xcbEAF3BDe82155F56486Fb5a1072cb8baAf547cc", + "address": "0x04C89607413713Ec9775E14b954286519d836FEf", "abi": [ { "inputs": [ @@ -2370,7 +2370,7 @@ "name": "PKPHelper" }, "PKPNFT": { - "address": "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9", + "address": "0x99bbA657f2BbC93c02D617f8bA121cB8Fc104Acf", "abi": [ { "inputs": [ @@ -2895,6 +2895,19 @@ "name": "Transfer", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newTrustedForwarder", + "type": "address" + } + ], + "name": "TrustedForwarderSet", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -3168,6 +3181,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getTrustedForwarder", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "initialize", @@ -3433,6 +3459,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "forwarder", + "type": "address" + } + ], + "name": "setTrustedForwarder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -3574,7 +3613,7 @@ "name": "PKPNFT" }, "PKPNFTMetadata": { - "address": "0x5eb3Bc0a489C5A8288765d2336659EbCA68FCd00", + "address": "0x82e01223d51Eb87e16A03E24687EDF0F294da6f1", "abi": [ { "inputs": [ @@ -3732,7 +3771,7 @@ "name": "PKPNFTMetadata" }, "PKPPermissions": { - "address": "0xFD471836031dc5108809D173A067e8486B9047A3", + "address": "0xdbC43Ba45381e02825b14322cDdd15eC4B3164E6", "abi": [ { "inputs": [ @@ -4286,6 +4325,19 @@ "name": "RootHashUpdated", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newTrustedForwarder", + "type": "address" + } + ], + "name": "TrustedForwarderSet", + "type": "event" + }, { "inputs": [ { @@ -4687,6 +4739,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getTrustedForwarder", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -4945,6 +5010,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "forwarder", + "type": "address" + } + ], + "name": "setTrustedForwarder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -5022,7 +5100,7 @@ "name": "PKPPermissions" }, "PubkeyRouter": { - "address": "0x70e0bA845a1A0F2DA3359C97E0285013525FFC49", + "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D", "abi": [ { "inputs": [ @@ -5545,6 +5623,19 @@ "name": "ToggleEvent", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newTrustedForwarder", + "type": "address" + } + ], + "name": "TrustedForwarderSet", + "type": "event" + }, { "inputs": [ { @@ -5814,6 +5905,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getTrustedForwarder", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -5948,6 +6052,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "forwarder", + "type": "address" + } + ], + "name": "setTrustedForwarder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -5982,7 +6099,7 @@ "name": "PubkeyRouter" }, "Staking": { - "address": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", + "address": "0x9E545E3C0baAB3E08CdfD552C960A1050f373042", "abi": [ { "inputs": [ @@ -6375,11 +6492,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [], - "name": "CallerNotOwner", - "type": "error" - }, { "inputs": [ { @@ -6404,6 +6516,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakerAddress", + "type": "address" + } + ], + "name": "getCurrentRealmIdForStakerAddress", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -6423,6 +6554,38 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakerAddress", + "type": "address" + } + ], + "name": "getShawdowRealmIdForStakerAddress", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getUnassignedStakerAddresses", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "getUnassignedValidators", @@ -6471,18 +6634,43 @@ }, { "internalType": "uint256", - "name": "commission", + "name": "commissionRate", "type": "uint256" }, { "internalType": "uint256", - "name": "commissionRate", + "name": "lastRewardEpoch", "type": "uint256" }, { "internalType": "uint256", - "name": "lastRewardEpoch", + "name": "lastRealmId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedFixedCostRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedCommission", "type": "uint256" + }, + { + "internalType": "address", + "name": "operatorAddress", + "type": "address" } ], "internalType": "struct LibStakingStorage.Validator[]", @@ -6588,6 +6776,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "numRealms", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -6642,18 +6843,43 @@ }, { "internalType": "uint256", - "name": "commission", + "name": "commissionRate", "type": "uint256" }, { "internalType": "uint256", - "name": "commissionRate", + "name": "lastRewardEpoch", "type": "uint256" }, { "internalType": "uint256", - "name": "lastRewardEpoch", + "name": "lastRealmId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedFixedCostRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedCommission", "type": "uint256" + }, + { + "internalType": "address", + "name": "operatorAddress", + "type": "address" } ], "internalType": "struct LibStakingStorage.Validator", @@ -6664,11 +6890,31 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "CallerNotOwner", + "type": "error" + }, { "inputs": [], "name": "CallerNotOwnerOrDevopsAdmin", "type": "error" }, + { + "inputs": [], + "name": "CannotStakeZero", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidNewSharePrice", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSlashPercentage", + "type": "error" + }, { "inputs": [ { @@ -6680,6 +6926,22 @@ "name": "MustBeInNextValidatorSetLockedOrReadyForNextEpochState", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "internalType": "address[]", + "name": "validatorsInNextEpoch", + "type": "address[]" + } + ], + "name": "ValidatorIsNotInNextEpoch", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -6975,13 +7237,13 @@ { "indexed": false, "internalType": "uint256", - "name": "newMaxTripleCount", + "name": "newMaxPresignCount", "type": "uint256" }, { "indexed": false, "internalType": "uint256", - "name": "newMinTripleCount", + "name": "newMinPresignCount", "type": "uint256" }, { @@ -6993,7 +7255,7 @@ { "indexed": false, "internalType": "uint256", - "name": "newMaxTripleConcurrency", + "name": "newMaxPresignConcurrency", "type": "uint256" }, { @@ -7012,11 +7274,43 @@ { "indexed": false, "internalType": "address", - "name": "newStakingTokenAddress", + "name": "newResolverContractAddress", "type": "address" } ], - "name": "StakingTokenSet", + "name": "ResolverContractAddressSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "staker", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Staked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newStakingTokenAddress", + "type": "address" + } + ], + "name": "StakingTokenSet", "type": "event" }, { @@ -7032,6 +7326,32 @@ "name": "StateChanged", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "staker", + "type": "address" + } + ], + "name": "ValidatorBanned", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "staker", + "type": "address" + } + ], + "name": "ValidatorKickedFromNextEpoch", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -7045,6 +7365,32 @@ "name": "ValidatorRejoinedNextEpoch", "type": "event" }, + { + "inputs": [ + { + "internalType": "address", + "name": "staker", + "type": "address" + } + ], + "name": "addPermittedStaker", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "addRealm", + "outputs": [ + { + "internalType": "uint256", + "name": "realmId", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -7110,14 +7456,37 @@ { "inputs": [ { - "internalType": "address", - "name": "validatorStakerAddress", - "type": "address" + "internalType": "uint256", + "name": "source_realmId", + "type": "uint256" }, { "internalType": "uint256", - "name": "amountToPenalize", + "name": "target_realmId", "type": "uint256" + }, + { + "internalType": "address[]", + "name": "target_validators", + "type": "address[]" + } + ], + "name": "adminSetupShadowSplicing", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "percentage", + "type": "uint256" + }, + { + "internalType": "address", + "name": "stakerAddress", + "type": "address" } ], "name": "adminSlashValidator", @@ -7148,6 +7517,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "realmId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "decreaseRewardPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -7174,6 +7561,50 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "realmId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "increaseRewardPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "staker", + "type": "address" + } + ], + "name": "removePermittedStaker", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "realmId", + "type": "uint256" + } + ], + "name": "removeRealm", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -7317,6 +7748,16 @@ "internalType": "uint256", "name": "minSelfStakeTimelock", "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minValidatorCountToClampMinimumThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minThresholdToClampAt", + "type": "uint256" } ], "internalType": "struct LibStakingStorage.GlobalConfig", @@ -7329,6 +7770,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "newResolverAddress", + "type": "address" + } + ], + "name": "setContractResolver", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -7458,6 +7912,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "permittedStakersOn", + "type": "bool" + } + ], + "name": "setPermittedStakersOn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -7474,12 +7941,12 @@ }, { "internalType": "uint256", - "name": "maxTripleCount", + "name": "maxPresignCount", "type": "uint256" }, { "internalType": "uint256", - "name": "minTripleCount", + "name": "minPresignCount", "type": "uint256" }, { @@ -7489,13 +7956,18 @@ }, { "internalType": "uint256", - "name": "maxTripleConcurrency", + "name": "maxPresignConcurrency", "type": "uint256" }, { "internalType": "bool", "name": "rpcHealthcheckEnabled", "type": "bool" + }, + { + "internalType": "uint256", + "name": "minEpochForRewards", + "type": "uint256" } ], "internalType": "struct LibStakingStorage.RealmConfig", @@ -7508,6 +7980,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newTotalSupply", + "type": "uint256" + } + ], + "name": "setTokenTotalSupplyStandIn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "CallerNotContract", @@ -7523,11 +8008,6 @@ "name": "CannotModifyUnfrozen", "type": "error" }, - { - "inputs": [], - "name": "CannotStakeZero", - "type": "error" - }, { "inputs": [], "name": "CannotWithdrawFrozen", @@ -7560,11 +8040,6 @@ "name": "InsufficientSelfStake", "type": "error" }, - { - "inputs": [], - "name": "InvalidNewSharePrice", - "type": "error" - }, { "inputs": [], "name": "InvalidRatio", @@ -7675,6 +8150,11 @@ "name": "TimeLockNotMet", "type": "error" }, + { + "inputs": [], + "name": "TooSoonToWithdraw", + "type": "error" + }, { "inputs": [ { @@ -7692,11 +8172,29 @@ { "indexed": false, "internalType": "address", - "name": "newResolverContractAddress", + "name": "stakerAddress", "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "rewards", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fromEpoch", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "toEpoch", + "type": "uint256" } ], - "name": "ResolverContractAddressSet", + "name": "FixedCostRewardsClaimed", "type": "event" }, { @@ -7809,19 +8307,44 @@ "anonymous": false, "inputs": [ { - "indexed": true, + "indexed": false, "internalType": "address", - "name": "staker", + "name": "newTrustedForwarder", + "type": "address" + } + ], + "name": "TrustedForwarderSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "stakerAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", - "name": "amount", + "name": "rewards", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fromEpoch", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "toEpoch", "type": "uint256" } ], - "name": "Staked", + "name": "ValidatorCommissionClaimed", "type": "event" }, { @@ -7856,37 +8379,6 @@ "name": "Withdrawn", "type": "event" }, - { - "inputs": [ - { - "internalType": "address", - "name": "staker", - "type": "address" - } - ], - "name": "addPermittedStaker", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "percentage", - "type": "uint256" - }, - { - "internalType": "address", - "name": "stakerAddress", - "type": "address" - } - ], - "name": "adminSlashValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -7925,6 +8417,24 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "realmId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxNumberOfEpochsToClaim", + "type": "uint256" + } + ], + "name": "claimFixedCostRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -7941,6 +8451,11 @@ "internalType": "uint256", "name": "stakeRecordId", "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxNumberOfEpochsToClaim", + "type": "uint256" } ], "name": "claimStakeRewards", @@ -7949,7 +8464,18 @@ "type": "function" }, { - "inputs": [], + "inputs": [ + { + "internalType": "uint256", + "name": "realmId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxNumberOfEpochsToClaim", + "type": "uint256" + } + ], "name": "claimValidatorCommission", "outputs": [], "stateMutability": "nonpayable", @@ -8023,7 +8549,17 @@ }, { "internalType": "uint256", - "name": "totalRewards", + "name": "totalStakeRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "validatorFixedCostRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "validatorCommission", "type": "uint256" }, { @@ -8074,46 +8610,89 @@ }, { "internalType": "uint256", - "name": "stakeRecordId", + "name": "rewardEpochNumber", "type": "uint256" - }, + } + ], + "name": "getRewardEpochView", + "outputs": [ { - "internalType": "uint256", - "name": "amount", - "type": "uint256" + "components": [ + { + "internalType": "uint256", + "name": "epochEnd", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalStakeWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalStakeRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "validatorFixedCostRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "validatorCommission", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "slope", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "slopeIncrease", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "validatorSharePrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stakeAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "validatorSharePriceAtLastUpdate", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "initial", + "type": "bool" + } + ], + "internalType": "struct LibStakingStorage.RewardEpoch", + "name": "", + "type": "tuple" } ], - "name": "increaseStakeRecordAmount", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { - "inputs": [ - { - "internalType": "uint256", - "name": "realmId", - "type": "uint256" - }, + "inputs": [], + "name": "getTrustedForwarder", + "outputs": [ { "internalType": "address", - "name": "stakerAddress", + "name": "", "type": "address" - }, - { - "internalType": "uint256", - "name": "stakeRecordId", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "newTimeLock", - "type": "uint256" } ], - "name": "increaseStakeRecordTimelock", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { @@ -8125,25 +8704,25 @@ }, { "internalType": "uint256", - "name": "rewardEpochNumber", + "name": "stakeRecordId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "additionalAmount", "type": "uint256" } ], - "name": "initializeRewardEpoch", + "name": "increaseStakeRecordAmount", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ - { - "internalType": "uint256", - "name": "realmId", - "type": "uint256" - }, { "internalType": "address", - "name": "userStakerAddress", + "name": "stakerAddress", "type": "address" }, { @@ -8152,12 +8731,12 @@ "type": "uint256" }, { - "internalType": "address", - "name": "newStakerAddress", - "type": "address" + "internalType": "uint256", + "name": "additionalTimeLock", + "type": "uint256" } ], - "name": "migrateStakeRecord", + "name": "increaseStakeRecordTimelock", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -8166,80 +8745,76 @@ "inputs": [ { "internalType": "address", - "name": "staker", + "name": "stakerAddress", "type": "address" - } - ], - "name": "removePermittedStaker", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ + }, { - "internalType": "address", - "name": "newResolverAddress", - "type": "address" - } - ], - "name": "setContractResolver", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ + "internalType": "uint256", + "name": "rewardEpochNumber", + "type": "uint256" + }, { "internalType": "bool", - "name": "permittedStakersOn", + "name": "isInitial", "type": "bool" } ], - "name": "setPermittedStakersOn", + "name": "initializeRewardEpoch", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ + { + "internalType": "uint256", + "name": "realmId", + "type": "uint256" + }, { "internalType": "address", - "name": "stakerAddress", + "name": "operatorAddressToMigrateFrom", "type": "address" }, { "internalType": "uint256", - "name": "rate", + "name": "stakeRecordId", "type": "uint256" + }, + { + "internalType": "address", + "name": "operatorAddressToMigrateTo", + "type": "address" } ], - "name": "setValidatorComissionRate", + "name": "migrateStakeRecord", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ - { - "internalType": "uint256", - "name": "percentage", - "type": "uint256" - }, { "internalType": "address", - "name": "stakerAddress", + "name": "forwarder", "type": "address" } ], - "name": "slashValidator", - "outputs": [ + "name": "setTrustedForwarder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "uint256", - "name": "", + "name": "rate", "type": "uint256" } ], + "name": "setValidatorCommissionRate", + "outputs": [], "stateMutability": "nonpayable", "type": "function" }, @@ -8307,25 +8882,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "realmId", - "type": "uint256" - } - ], - "name": "updateRewardEpoch", - "outputs": [ - { - "internalType": "uint256", - "name": "currentRewardEpoch", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -8419,6 +8975,11 @@ "name": "CouldNotMapNodeAddressToStakerAddress", "type": "error" }, + { + "inputs": [], + "name": "InvalidAttestedAddress", + "type": "error" + }, { "inputs": [ { @@ -8575,6 +9136,11 @@ "name": "SignaledReadyForWrongEpochNumber", "type": "error" }, + { + "inputs": [], + "name": "StakerAddressMismatch", + "type": "error" + }, { "inputs": [ { @@ -8602,6 +9168,17 @@ "name": "TryingToWithdrawMoreThanStaked", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "staker", + "type": "address" + } + ], + "name": "ValidatorAlreadyInNextValidatorSet", + "type": "error" + }, { "inputs": [ { @@ -8622,16 +9199,11 @@ "inputs": [ { "internalType": "address", - "name": "validator", + "name": "staker", "type": "address" - }, - { - "internalType": "address[]", - "name": "validatorsInNextEpoch", - "type": "address[]" } ], - "name": "ValidatorIsNotInNextEpoch", + "name": "ValidatorNotInNextEpoch", "type": "error" }, { @@ -8719,13 +9291,13 @@ { "indexed": false, "internalType": "uint256", - "name": "newMaxTripleCount", + "name": "newMaxPresignCount", "type": "uint256" }, { "indexed": false, "internalType": "uint256", - "name": "newMinTripleCount", + "name": "newMinPresignCount", "type": "uint256" }, { @@ -8737,7 +9309,7 @@ { "indexed": false, "internalType": "uint256", - "name": "newMaxTripleConcurrency", + "name": "newMaxPresignConcurrency", "type": "uint256" }, { @@ -8852,25 +9424,6 @@ "name": "RewardsDurationUpdated", "type": "event" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amountBurned", - "type": "uint256" - } - ], - "name": "ValidatorKickedFromNextEpoch", - "type": "event" - }, { "anonymous": false, "inputs": [ @@ -9000,39 +9553,27 @@ "internalType": "uint256", "name": "realmId", "type": "uint256" - }, - { - "internalType": "uint32", - "name": "ip", - "type": "uint32" - }, - { - "internalType": "uint128", - "name": "ipv6", - "type": "uint128" - }, - { - "internalType": "uint32", - "name": "port", - "type": "uint32" - }, - { - "internalType": "address", - "name": "nodeAddress", - "type": "address" - }, + } + ], + "name": "requestToJoin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "uint256", - "name": "senderPubKey", + "name": "realmId", "type": "uint256" }, { - "internalType": "uint256", - "name": "receiverPubKey", - "type": "uint256" + "internalType": "address", + "name": "stakerAddress", + "type": "address" } ], - "name": "requestToJoin", + "name": "requestToJoinAsAdmin", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -9045,29 +9586,27 @@ "type": "uint256" }, { - "internalType": "uint32", - "name": "ip", - "type": "uint32" - }, - { - "internalType": "uint128", - "name": "ipv6", - "type": "uint128" - }, - { - "internalType": "uint32", - "name": "port", - "type": "uint32" - }, + "internalType": "address", + "name": "stakerAddress", + "type": "address" + } + ], + "name": "requestToJoinAsForShadowSplicing", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "uint256", - "name": "senderPubKey", + "name": "realmId", "type": "uint256" }, { - "internalType": "uint256", - "name": "receiverPubKey", - "type": "uint256" + "internalType": "address", + "name": "stakerAddress", + "type": "address" } ], "name": "requestToJoinAsNode", @@ -9114,7 +9653,7 @@ }, { "internalType": "address", - "name": "nodeAddress", + "name": "operatorAddress", "type": "address" }, { @@ -9167,36 +9706,6 @@ "internalType": "uint256", "name": "amount", "type": "uint256" - }, - { - "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": "senderPubKey", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "receiverPubKey", - "type": "uint256" } ], "name": "stakeAndJoin", @@ -9477,11 +9986,6 @@ "name": "NodeAddressNotFoundForStaker", "type": "error" }, - { - "inputs": [], - "name": "StakeNotFound", - "type": "error" - }, { "inputs": [ { @@ -9497,14 +10001,14 @@ "type": "uint256" }, { - "internalType": "uint256", - "name": "rewards", - "type": "uint256" + "internalType": "address[]", + "name": "validatorsInCurrentEpoch", + "type": "address[]" }, { - "internalType": "bool", - "name": "isStarted", - "type": "bool" + "internalType": "uint256", + "name": "actualEpochLength", + "type": "uint256" } ], "internalType": "struct LibStakingStorage.RewardEpochGlobalStats", @@ -9512,7 +10016,7 @@ "type": "tuple" } ], - "name": "calculateRewardsPerEpoch", + "name": "calculateRewardsPerDay", "outputs": [ { "internalType": "uint256", @@ -9687,27 +10191,27 @@ }, { "internalType": "uint256", - "name": "endTime", + "name": "nextRewardEpochNumber", "type": "uint256" }, { "internalType": "uint256", - "name": "retries", + "name": "endTime", "type": "uint256" }, { "internalType": "uint256", - "name": "timeout", + "name": "retries", "type": "uint256" }, { "internalType": "uint256", - "name": "startTime", + "name": "timeout", "type": "uint256" }, { "internalType": "uint256", - "name": "lastEpochStart", + "name": "startTime", "type": "uint256" } ], @@ -9792,18 +10296,43 @@ }, { "internalType": "uint256", - "name": "commission", + "name": "commissionRate", "type": "uint256" }, { "internalType": "uint256", - "name": "commissionRate", + "name": "lastRewardEpoch", "type": "uint256" }, { "internalType": "uint256", - "name": "lastRewardEpoch", + "name": "lastRealmId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedFixedCostRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedCommission", "type": "uint256" + }, + { + "internalType": "address", + "name": "operatorAddress", + "type": "address" } ], "internalType": "struct LibStakingStorage.Validator[]", @@ -9843,27 +10372,27 @@ }, { "internalType": "uint256", - "name": "endTime", + "name": "nextRewardEpochNumber", "type": "uint256" }, { "internalType": "uint256", - "name": "retries", + "name": "endTime", "type": "uint256" }, { "internalType": "uint256", - "name": "timeout", + "name": "retries", "type": "uint256" }, { "internalType": "uint256", - "name": "startTime", + "name": "timeout", "type": "uint256" }, { "internalType": "uint256", - "name": "lastEpochStart", + "name": "startTime", "type": "uint256" } ], @@ -9900,38 +10429,63 @@ }, { "internalType": "uint256", - "name": "reward", + "name": "reward", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "senderPubKey", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "receiverPubKey", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastActiveEpoch", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "commissionRate", "type": "uint256" }, { "internalType": "uint256", - "name": "senderPubKey", + "name": "lastRewardEpoch", "type": "uint256" }, { "internalType": "uint256", - "name": "receiverPubKey", + "name": "lastRealmId", "type": "uint256" }, { "internalType": "uint256", - "name": "lastActiveEpoch", + "name": "delegatedStakeAmount", "type": "uint256" }, { "internalType": "uint256", - "name": "commission", + "name": "delegatedStakeWeight", "type": "uint256" }, { "internalType": "uint256", - "name": "commissionRate", + "name": "lastRewardEpochClaimedFixedCostRewards", "type": "uint256" }, { "internalType": "uint256", - "name": "lastRewardEpoch", + "name": "lastRewardEpochClaimedCommission", "type": "uint256" + }, + { + "internalType": "address", + "name": "operatorAddress", + "type": "address" } ], "internalType": "struct LibStakingStorage.Validator[]", @@ -9961,6 +10515,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getAllValidators", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "getKeyTypes", @@ -9993,6 +10560,82 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "address", + "name": "stakerAddress", + "type": "address" + } + ], + "name": "getLastStakeRecord", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "unfreezeStart", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timeLock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastUpdateTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "initialSharePrice", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "loaded", + "type": "bool" + }, + { + "internalType": "bool", + "name": "frozen", + "type": "bool" + }, + { + "internalType": "address", + "name": "attributionAddress", + "type": "address" + } + ], + "internalType": "struct LibStakingStorage.StakeRecord", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "getLitCirc", @@ -10003,7 +10646,25 @@ "type": "uint256" } ], - "stateMutability": "pure", + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLowestRewardEpochNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", "type": "function" }, { @@ -10087,6 +10748,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "realmId", + "type": "uint256" + } + ], + "name": "getNonShadowValidators", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -10110,14 +10790,14 @@ "type": "uint256" }, { - "internalType": "uint256", - "name": "rewards", - "type": "uint256" + "internalType": "address[]", + "name": "validatorsInCurrentEpoch", + "type": "address[]" }, { - "internalType": "bool", - "name": "isStarted", - "type": "bool" + "internalType": "uint256", + "name": "actualEpochLength", + "type": "uint256" } ], "internalType": "struct LibStakingStorage.RewardEpochGlobalStats", @@ -10147,6 +10827,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "realmId", + "type": "uint256" + } + ], + "name": "getShadowValidators", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -10196,7 +10895,7 @@ }, { "internalType": "uint256", - "name": "rewardEpochCheckpoint", + "name": "lastRewardEpochClaimed", "type": "uint256" }, { @@ -10213,6 +10912,11 @@ "internalType": "bool", "name": "frozen", "type": "bool" + }, + { + "internalType": "address", + "name": "attributionAddress", + "type": "address" } ], "internalType": "struct LibStakingStorage.StakeRecord", @@ -10291,7 +10995,7 @@ }, { "internalType": "uint256", - "name": "rewardEpochCheckpoint", + "name": "lastRewardEpochClaimed", "type": "uint256" }, { @@ -10308,6 +11012,11 @@ "internalType": "bool", "name": "frozen", "type": "bool" + }, + { + "internalType": "address", + "name": "attributionAddress", + "type": "address" } ], "internalType": "struct LibStakingStorage.StakeRecord[]", @@ -10326,56 +11035,14 @@ "type": "address" }, { - "components": [ - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "unfreezeStart", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "timeLock", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "lastUpdateTimestamp", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "rewardEpochCheckpoint", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "initialSharePrice", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "loaded", - "type": "bool" - }, - { - "internalType": "bool", - "name": "frozen", - "type": "bool" - } - ], - "internalType": "struct LibStakingStorage.StakeRecord", - "name": "stakeRecord", - "type": "tuple" + "internalType": "uint256", + "name": "recordId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "userStakerAddress", + "type": "address" }, { "internalType": "uint256", @@ -10430,7 +11097,7 @@ }, { "internalType": "uint256", - "name": "rewardEpochCheckpoint", + "name": "lastRewardEpochClaimed", "type": "uint256" }, { @@ -10447,6 +11114,11 @@ "internalType": "bool", "name": "frozen", "type": "bool" + }, + { + "internalType": "address", + "name": "attributionAddress", + "type": "address" } ], "internalType": "struct LibStakingStorage.StakeRecord", @@ -10532,7 +11204,7 @@ }, { "internalType": "uint256", - "name": "rewardEpochCheckpoint", + "name": "lastRewardEpochClaimed", "type": "uint256" }, { @@ -10549,6 +11221,11 @@ "internalType": "bool", "name": "frozen", "type": "bool" + }, + { + "internalType": "address", + "name": "attributionAddress", + "type": "address" } ], "internalType": "struct LibStakingStorage.StakeRecord", @@ -10755,18 +11432,43 @@ }, { "internalType": "uint256", - "name": "commission", + "name": "commissionRate", "type": "uint256" }, { "internalType": "uint256", - "name": "commissionRate", + "name": "lastRewardEpoch", "type": "uint256" }, { "internalType": "uint256", - "name": "lastRewardEpoch", + "name": "lastRealmId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedFixedCostRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedCommission", "type": "uint256" + }, + { + "internalType": "address", + "name": "operatorAddress", + "type": "address" } ], "internalType": "struct LibStakingStorage.Validator[]", @@ -10831,18 +11533,43 @@ }, { "internalType": "uint256", - "name": "commission", + "name": "commissionRate", "type": "uint256" }, { "internalType": "uint256", - "name": "commissionRate", + "name": "lastRewardEpoch", "type": "uint256" }, { "internalType": "uint256", - "name": "lastRewardEpoch", + "name": "lastRealmId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedFixedCostRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedCommission", "type": "uint256" + }, + { + "internalType": "address", + "name": "operatorAddress", + "type": "address" } ], "internalType": "struct LibStakingStorage.Validator[]", @@ -10907,18 +11634,43 @@ }, { "internalType": "uint256", - "name": "commission", + "name": "commissionRate", "type": "uint256" }, { "internalType": "uint256", - "name": "commissionRate", + "name": "lastRewardEpoch", "type": "uint256" }, { "internalType": "uint256", - "name": "lastRewardEpoch", + "name": "lastRealmId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedFixedCostRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedCommission", "type": "uint256" + }, + { + "internalType": "address", + "name": "operatorAddress", + "type": "address" } ], "internalType": "struct LibStakingStorage.Validator[]", @@ -11073,6 +11825,16 @@ "internalType": "uint256", "name": "minSelfStakeTimelock", "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minValidatorCountToClampMinimumThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minThresholdToClampAt", + "type": "uint256" } ], "internalType": "struct LibStakingStorage.GlobalConfig", @@ -11083,6 +11845,30 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "realmId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "stakerAddress", + "type": "address" + } + ], + "name": "isActiveShadowValidator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -11344,6 +12130,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "operatorAddress", + "type": "address" + } + ], + "name": "operatorAddressToStakerAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -11411,12 +12216,12 @@ }, { "internalType": "uint256", - "name": "maxTripleCount", + "name": "maxPresignCount", "type": "uint256" }, { "internalType": "uint256", - "name": "minTripleCount", + "name": "minPresignCount", "type": "uint256" }, { @@ -11426,13 +12231,18 @@ }, { "internalType": "uint256", - "name": "maxTripleConcurrency", + "name": "maxPresignConcurrency", "type": "uint256" }, { "internalType": "bool", "name": "rpcHealthcheckEnabled", "type": "bool" + }, + { + "internalType": "uint256", + "name": "minEpochForRewards", + "type": "uint256" } ], "internalType": "struct LibStakingStorage.RealmConfig", @@ -11488,10 +12298,20 @@ }, { "inputs": [ + { + "internalType": "uint256", + "name": "realmId", + "type": "uint256" + }, { "internalType": "address", "name": "stakerAddress", "type": "address" + }, + { + "internalType": "bool", + "name": "stakerInCurrentValidatorSet", + "type": "bool" } ], "name": "validatorSelfStakeWillExpire", @@ -11559,18 +12379,43 @@ }, { "internalType": "uint256", - "name": "commission", + "name": "commissionRate", "type": "uint256" }, { "internalType": "uint256", - "name": "commissionRate", + "name": "lastRewardEpoch", "type": "uint256" }, { "internalType": "uint256", - "name": "lastRewardEpoch", + "name": "lastRealmId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedFixedCostRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedCommission", "type": "uint256" + }, + { + "internalType": "address", + "name": "operatorAddress", + "type": "address" } ], "internalType": "struct LibStakingStorage.Validator", @@ -11794,6 +12639,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "FORWARDER_CONTRACT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "HD_KEY_DERIVER_CONTRACT", @@ -12258,7 +13116,7 @@ "name": "ContractResolver" }, "PriceFeed": { - "address": "0x1c85638e118b37167e9298c2268758e058DdfDA0", + "address": "0xf953b3A269d80e3eB0F2947630Da976B896A8C5b", "abi": [ { "inputs": [ @@ -12692,6 +13550,19 @@ "name": "MaxNetworkPriceSet", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newTrustedForwarder", + "type": "address" + } + ], + "name": "TrustedForwarderSet", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -12736,6 +13607,41 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getNodeCapacityConfig", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "pkpSignMaxConcurrency", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "encSignMaxConcurrency", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "litActionMaxConcurrency", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "globalMaxCapacity", + "type": "uint256" + } + ], + "internalType": "struct LibPriceFeedStorage.NodeCapacityConfig", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -12807,18 +13713,43 @@ }, { "internalType": "uint256", - "name": "commission", + "name": "commissionRate", "type": "uint256" }, { "internalType": "uint256", - "name": "commissionRate", + "name": "lastRewardEpoch", "type": "uint256" }, { "internalType": "uint256", - "name": "lastRewardEpoch", + "name": "lastRealmId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatedStakeWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedFixedCostRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastRewardEpochClaimedCommission", "type": "uint256" + }, + { + "internalType": "address", + "name": "operatorAddress", + "type": "address" } ], "internalType": "struct LibStakingStorage.Validator", @@ -12852,6 +13783,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getTrustedForwarder", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -12971,7 +13915,60 @@ "type": "uint256[]" } ], - "name": "setBaseNetworkPrices", + "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": [ + { + "components": [ + { + "internalType": "uint256", + "name": "pkpSignMaxConcurrency", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "encSignMaxConcurrency", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "litActionMaxConcurrency", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "globalMaxCapacity", + "type": "uint256" + } + ], + "internalType": "struct LibPriceFeedStorage.NodeCapacityConfig", + "name": "config", + "type": "tuple" + } + ], + "name": "setNodeCapacityConfig", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -12979,17 +13976,12 @@ { "inputs": [ { - "internalType": "uint256", - "name": "newPrice", - "type": "uint256" - }, - { - "internalType": "uint256[]", - "name": "productIds", - "type": "uint256[]" + "internalType": "address", + "name": "forwarder", + "type": "address" } ], - "name": "setMaxNetworkPrices", + "name": "setTrustedForwarder", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -13064,7 +14056,7 @@ "name": "PriceFeed" }, "Ledger": { - "address": "0xBEc49fA140aCaA83533fB00A2BB19bDdd0290f25", + "address": "0x4C2F7092C2aE51D986bEFEe378e50BD4dB99C901", "abi": [ { "inputs": [ @@ -13652,6 +14644,19 @@ "name": "RewardWithdrawRequest", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newTrustedForwarder", + "type": "address" + } + ], + "name": "TrustedForwarderSet", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -13815,6 +14820,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getTrustedForwarder", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -13987,6 +15005,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "forwarder", + "type": "address" + } + ], + "name": "setTrustedForwarder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -14054,5 +15085,227 @@ } ], "name": "Ledger" + }, + "Forwarder": { + "address": "0xAA292E8611aDF267e563f334Ee42320aC96D0463", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "InvalidShortString", + "type": "error" + }, + { + "inputs": [], + "name": "SignatureDoesNotMatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "str", + "type": "string" + } + ], + "name": "StringTooLong", + "type": "error" + }, + { + "inputs": [], + "name": "TransactionRevertedSilently", + "type": "error" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Forwarder.ForwardRequest", + "name": "req", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "execute", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + } + ], + "name": "getNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Forwarder.ForwardRequest", + "name": "req", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "verify", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "name": "Forwarder" } -} +} \ No newline at end of file diff --git a/local-tests/setup/tinny-environment.ts b/local-tests/setup/tinny-environment.ts index 089166466..6f6a58cb2 100644 --- a/local-tests/setup/tinny-environment.ts +++ b/local-tests/setup/tinny-environment.ts @@ -1,14 +1,5 @@ -import { LitContracts } from '@lit-protocol/contracts-sdk'; -import { LitNodeClient } from '@lit-protocol/lit-node-client'; -import { - AuthSig, - LitContractContext, - LitContractResolverContext, -} from '@lit-protocol/types'; -import { ProcessEnvs, TinnyEnvConfig } from './tinny-config'; -import { TinnyPerson } from './tinny-person'; +import { ethers, Signer } from 'ethers'; -import { createSiweMessage, generateAuthSig } from '@lit-protocol/auth-helpers'; import { CENTRALISATION_BY_NETWORK, LIT_NETWORK, @@ -16,8 +7,17 @@ import { PRODUCT_IDS, RPC_URL_BY_NETWORK, } from '@lit-protocol/constants'; -import { ethers, Signer } from 'ethers'; +import { LitContracts } from '@lit-protocol/contracts-sdk'; +import { LitNodeClient } from '@lit-protocol/lit-node-client'; +import { + AuthSig, + LitContractContext, + LitContractResolverContext, +} from '@lit-protocol/types'; + import { ShivaClient, TestnetClient } from './shiva-client'; +import { ProcessEnvs, TinnyEnvConfig } from './tinny-config'; +import { TinnyPerson } from './tinny-person'; import { toErrorWithMessage } from './tinny-utils'; console.log('checking env', process.env['DEBUG']); @@ -49,7 +49,7 @@ export class TinnyEnvironment { DEBUG: process.env['DEBUG'] === 'true', REQUEST_PER_KILOSECOND: parseInt(process.env['REQUEST_PER_KILOSECOND']) || - (process.env['NETWORK'] as LIT_NETWORK_VALUES) === 'datil-dev' + (process.env['NETWORK'] as LIT_NETWORK_VALUES) === LIT_NETWORK.NagaDev ? 1 : 200, LIT_RPC_URL: process.env['LIT_RPC_URL'], diff --git a/local-tests/setup/tinny-person.ts b/local-tests/setup/tinny-person.ts index 9d1fad5cc..39e21f0be 100644 --- a/local-tests/setup/tinny-person.ts +++ b/local-tests/setup/tinny-person.ts @@ -27,8 +27,9 @@ export class TinnyPerson { public authMethodOwnedPkp: PKPInfo; // Pass this to data to sign - public loveLetter: Uint8Array = ethers.utils.arrayify( - ethers.utils.keccak256([1, 2, 3, 4, 5]) + public loveLetter: Uint8Array = new Uint8Array([1, 2, 3, 4, 5]); + public hashedLoveLetter = ethers.utils.arrayify( + ethers.utils.keccak256(this.loveLetter) ); public provider: ethers.providers.StaticJsonRpcProvider; @@ -152,13 +153,12 @@ export class TinnyPerson { console.log('[𐬺🧪 Tinny Person𐬺] Minting a PKP...'); const walletMintRes = await this.contractsClient.pkpNftContractUtils.write.mint(); - this.pkp = walletMintRes.pkp; /** - * ==================================== - * Mint a PKP wiuth eth wallet auth method - * ==================================== + * ====================================== + * Mint a PKP with eth wallet auth method + * ====================================== */ console.log( '[𐬺🧪 Tinny Person𐬺] Minting a PKP with eth wallet auth method...' diff --git a/local-tests/tests/testUseEoaSessionSigsToExecuteJsSigning.ts b/local-tests/tests/testUseEoaSessionSigsToExecuteJsSigning.ts index 415572de8..451e4b6ca 100644 --- a/local-tests/tests/testUseEoaSessionSigsToExecuteJsSigning.ts +++ b/local-tests/tests/testUseEoaSessionSigsToExecuteJsSigning.ts @@ -25,11 +25,13 @@ export const testUseEoaSessionSigsToExecuteJsSigning = async ( }); })();`, jsParams: { - dataToSign: alice.loveLetter, + dataToSign: alice.hashedLoveLetter, publicKey: alice.pkp.publicKey, }, }); + console.log('res:', res); + devEnv.releasePrivateKeyFromUser(alice); // -- Expected output: @@ -47,20 +49,20 @@ export const testUseEoaSessionSigsToExecuteJsSigning = async ( // } // -- assertions - if (!res.signatures.sig.r) { - throw new Error(`Expected "r" in res.signatures.sig`); - } - if (!res.signatures.sig.s) { - throw new Error(`Expected "s" in res.signatures.sig`); - } + // if (!res.signatures.sig.r) { + // throw new Error(`Expected "r" in res.signatures.sig`); + // } + // if (!res.signatures.sig.s) { + // throw new Error(`Expected "s" in res.signatures.sig`); + // } - if (!res.signatures.sig.dataSigned) { - throw new Error(`Expected "dataSigned" in res.signatures.sig`); - } + // if (!res.signatures.sig.dataSigned) { + // throw new Error(`Expected "dataSigned" in res.signatures.sig`); + // } - if (!res.signatures.sig.publicKey) { - throw new Error(`Expected "publicKey" in res.signatures.sig`); - } + // if (!res.signatures.sig.publicKey) { + // throw new Error(`Expected "publicKey" in res.signatures.sig`); + // } log('✅ testUseEoaSessionSigsToExecuteJsSigning'); }; diff --git a/local-tests/tests/testUseEoaSessionSigsToPkpSign.ts b/local-tests/tests/testUseEoaSessionSigsToPkpSign.ts index 0431da214..f709437ee 100644 --- a/local-tests/tests/testUseEoaSessionSigsToPkpSign.ts +++ b/local-tests/tests/testUseEoaSessionSigsToPkpSign.ts @@ -1,86 +1,210 @@ -import { ethers } from 'ethers'; +import { p256 } from '@noble/curves/p256'; +import { p384 } from '@noble/curves/p384'; +import { secp256k1 } from '@noble/curves/secp256k1'; +import { hexToBytes } from '@noble/hashes/utils'; +import { + UnknownSignatureError, + EcdsaSigType, + SigType, +} from '@lit-protocol/constants'; +import { hashLitMessage } from '@lit-protocol/crypto'; import { log } from '@lit-protocol/misc'; + import { getEoaAuthContext } from 'local-tests/setup/session-sigs/get-eoa-session-sigs'; import { TinnyEnvironment } from 'local-tests/setup/tinny-environment'; +interface SigningSchemeConfig { + hasRecoveryId?: boolean; + hashesMessage: boolean; + recoversPublicKey?: boolean; + signingScheme: SigType; +} + +// Map the right curve function per signing scheme +export const ecdsaCurveFunctions: Record = { + EcdsaK256Sha256: secp256k1, + EcdsaP256Sha256: p256, + EcdsaP384Sha384: p384, +} as const; + /** * Test Commands: - * ✅ NETWORK=datil-dev yarn test:local --filter=testUseEoaSessionSigsToPkpSign - * ✅ NETWORK=datil-test yarn test:local --filter=testUseEoaSessionSigsToPkpSign + * ✅ NETWORK=naga-dev yarn test:local --filter=testUseEoaSessionSigsToPkpSign + * ✅ NETWORK=naga-test yarn test:local --filter=testUseEoaSessionSigsToPkpSign * ✅ NETWORK=custom yarn test:local --filter=testUseEoaSessionSigsToPkpSign */ export const testUseEoaSessionSigsToPkpSign = async ( devEnv: TinnyEnvironment ) => { const alice = await devEnv.createRandomPerson(); + const signingSchemeConfigs: SigningSchemeConfig[] = [ + // BLS + // { + // signingScheme: 'Bls12381', // TODO nodes accept this signing scheme but they throw an unexpected error + // hashesMessage: false, + // }, + // { + // signingScheme: 'Bls12381G1ProofOfPossession', + // hashesMessage: false, + // }, + // ECDSA + { + hasRecoveryId: true, + hashesMessage: true, + recoversPublicKey: true, + signingScheme: 'EcdsaK256Sha256', + }, + { + hasRecoveryId: true, + hashesMessage: true, + recoversPublicKey: true, + signingScheme: 'EcdsaP256Sha256', + }, + { + hasRecoveryId: true, + hashesMessage: true, + recoversPublicKey: true, + signingScheme: 'EcdsaP384Sha384', + }, + // FROST + { + signingScheme: 'SchnorrEd25519Sha512', + hashesMessage: false, + }, + { + signingScheme: 'SchnorrK256Sha256', + hashesMessage: false, + }, + { + signingScheme: 'SchnorrP256Sha256', + hashesMessage: false, + }, + { + signingScheme: 'SchnorrP384Sha384', + hashesMessage: false, + }, + { + signingScheme: 'SchnorrRistretto25519Sha512', + hashesMessage: false, + }, + { + signingScheme: 'SchnorrEd448Shake256', + hashesMessage: false, + }, + { + signingScheme: 'SchnorrRedJubjubBlake2b512', + hashesMessage: false, + }, + { + signingScheme: 'SchnorrK256Taproot', + hashesMessage: false, + }, + { + signingScheme: 'SchnorrRedDecaf377Blake2b512', + hashesMessage: false, + }, + { + signingScheme: 'SchnorrkelSubstrate', + hashesMessage: false, + }, + ]; - // const eoaSessionSigs = await getEoaSessionSigs(devEnv, alice); - const runWithSessionSigs = await devEnv.litNodeClient.pkpSign({ - toSign: alice.loveLetter, - pubKey: alice.pkp.publicKey, - authContext: getEoaAuthContext(devEnv, alice), - }); + for (const signingSchemeConfig of signingSchemeConfigs) { + try { + const signingScheme = signingSchemeConfig.signingScheme; + log(`Checking testUseEoaSessionSigsToPkpSign for ${signingSchemeConfig}`); - devEnv.releasePrivateKeyFromUser(alice); + const pkpSignature = await devEnv.litNodeClient.pkpSign({ + pubKey: alice.pkp.publicKey, + authContext: getEoaAuthContext(devEnv, alice), + messageToSign: alice.loveLetter, + signingScheme, + }); - // Expected output: - // { - // r: "25fc0d2fecde8ed801e9fee5ad26f2cf61d82e6f45c8ad1ad1e4798d3b747fd9", - // s: "549fe745b4a09536e6e7108d814cf7e44b93f1d73c41931b8d57d1b101833214", - // recid: 1, - // signature: "0x25fc0d2fecde8ed801e9fee5ad26f2cf61d82e6f45c8ad1ad1e4798d3b747fd9549fe745b4a09536e6e7108d814cf7e44b93f1d73c41931b8d57d1b1018332141c", - // publicKey: "04A3CD53CCF63597D3FFCD1DF1E8236F642C7DF8196F532C8104625635DC55A1EE59ABD2959077432FF635DF2CED36CC153050902B71291C4D4867E7DAAF964049", - // dataSigned: "7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4", - // } + 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`); - } + // -- Combined signature format assertions + for (const hexString of [ + 'signature', + 'verifyingKey', + 'signedData', + 'publicKey', + ]) { + if ( + !pkpSignature[hexString] || + !pkpSignature[hexString].startsWith('0x') + ) { + throw new Error( + `Expected "${hexString}" hex string in pkpSignature. Signing Scheme: ${signingScheme}` + ); + } + } + // Verify correct recoveryId + if ( + signingSchemeConfig.hasRecoveryId + ? ![0, 1].includes(pkpSignature.recoveryId) + : pkpSignature.recoveryId !== null + ) { + throw new Error( + `Expected "recoveryId" to be 0/1 for ECDSA and "null" for the rest of curves. Signing Scheme: ${signingScheme}` + ); + } - // signature must start with 0x - if (!runWithSessionSigs.signature.startsWith('0x')) { - throw new Error(`Expected "signature" to start with 0x`); - } + if (signingSchemeConfig.recoversPublicKey) { + const curve = ecdsaCurveFunctions[signingScheme]; + const signatureBytes = hexToBytes( + pkpSignature.signature.replace(/^0x/, '') + ); + const signature = curve.Signature.fromCompact( + signatureBytes + ).addRecoveryBit(pkpSignature.recoveryId); - // recid must be parseable as a number - if (isNaN(runWithSessionSigs.recid)) { - throw new Error(`Expected "recid" to be parseable as a number`); - } + const msgHash = hexToBytes(pkpSignature.signedData.replace(/^0x/, '')); + const recoveredPubKeyBytes = signature.recoverPublicKey(msgHash); + const recoveredPubKey = recoveredPubKeyBytes.toHex(false); - const signature = ethers.utils.joinSignature({ - r: '0x' + runWithSessionSigs.r, - s: '0x' + runWithSessionSigs.s, - recoveryParam: runWithSessionSigs.recid, - }); - const recoveredPubKey = ethers.utils.recoverPublicKey( - alice.loveLetter, - signature - ); + if (pkpSignature.publicKey.replace('0x', '') !== recoveredPubKey) { + throw new Error( + `Expected recovered public key to match nodesPublicKey` + ); + } + // PKP public key lives in k256, it cannot be directly compared in any other curve + if ( + signingScheme === 'EcdsaK256Sha256' && + alice.pkp.publicKey !== recoveredPubKey + ) { + throw new Error( + `Expected recovered public key to match alice.pkp.publicKey. Signing Scheme: ${signingSchemeConfig}` + ); + } + } - console.log('recoveredPubKey:', recoveredPubKey); + const messageHash = signingSchemeConfig.hashesMessage + ? hashLitMessage(signingScheme as EcdsaSigType, alice.loveLetter) + : alice.loveLetter; + const messageHashHex = Buffer.from(messageHash).toString('hex'); + if (pkpSignature.signedData.replace('0x', '') !== messageHashHex) { + throw new Error( + `Expected signed data to match hashLitMessage(signingScheme, alice.loveLetter). Signing Scheme: ${signingScheme}` + ); + } - if (recoveredPubKey !== `0x${runWithSessionSigs.publicKey.toLowerCase()}`) { - throw new Error( - `Expected recovered public key to match runWithSessionSigs.publicKey` - ); - } - if (recoveredPubKey !== `0x${alice.pkp.publicKey.toLowerCase()}`) { - throw new Error( - `Expected recovered public key to match alice.pkp.publicKey` - ); + log(`✅ testUseEoaSessionSigsToPkpSign - ${signingScheme}`); + } catch (e) { + throw new UnknownSignatureError( + { + info: { + signingSchemeConfig, + message: alice.loveLetter, + pkp: alice.pkp, + }, + cause: e, + }, + `Signature failed with signing scheme ${signingSchemeConfig.signingScheme}` + ); + } } - log('✅ testUseEoaSessionSigsToPkpSign'); + log('✅ testUseEoaSessionSigsToPkpSign all signing schemes'); }; diff --git a/package.json b/package.json index aa616feb6..e37e9c1d5 100644 --- a/package.json +++ b/package.json @@ -41,9 +41,10 @@ "@cosmjs/stargate": "0.30.1", "@dotenvx/dotenvx": "^1.6.4", "@lit-protocol/accs-schemas": "^0.0.22", - "@lit-protocol/contracts": "^0.0.86", + "@lit-protocol/contracts": "^0.1.10", "@metamask/eth-sig-util": "5.0.2", "@mysten/sui.js": "^0.37.1", + "@noble/curves": "^1.8.1", "@openagenda/verror": "^3.1.4", "@simplewebauthn/browser": "^7.2.0", "@simplewebauthn/typescript-types": "^7.0.0", @@ -60,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", @@ -70,7 +72,8 @@ "tslib": "^2.7.0", "tweetnacl": "^1.0.3", "tweetnacl-util": "^0.15.1", - "uint8arrays": "^4.0.3" + "uint8arrays": "^4.0.3", + "zod": "^3.24.1" }, "devDependencies": { "@nx/devkit": "17.3.0", @@ -86,6 +89,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 5b858793e..4af81c611 100644 --- a/packages/constants/src/lib/constants/constants.ts +++ b/packages/constants/src/lib/constants/constants.ts @@ -1,9 +1,5 @@ -import depd from 'depd'; - import { LITChain, LITCosmosChain, LITEVMChain, LITSVMChain } from './types'; -const deprecated = depd('lit-js-sdk:constants:constants'); - /** * Lit Protocol Network Public Key */ diff --git a/packages/constants/src/lib/constants/curves.ts b/packages/constants/src/lib/constants/curves.ts index 4950961dc..bfe280ac8 100644 --- a/packages/constants/src/lib/constants/curves.ts +++ b/packages/constants/src/lib/constants/curves.ts @@ -1,52 +1,94 @@ -// pub enum SigningScheme { - -// -- BLS -// Bls12381, - -// -- ECDSA -// EcdsaK256Sha256, -// EcdsaP256Sha256, -// EcdsaP384Sha384, - -// -- Frost -// SchnorrEd25519Sha512, -// SchnorrK256Sha256, -// SchnorrP256Sha256, -// SchnorrP384Sha384, -// SchnorrRistretto25519Sha512, -// SchnorrEd448Shake256, -// SchnorrRedJubjubBlake2b512, -// SchnorrK256Taproot, -// SchnorrRedDecaf377Blake2b512, -// SchnorrkelSubstrate, -// } -export const LIT_CURVE = { - BLS: 'BLS', - EcdsaK256: 'K256', - EcdsaCaitSith: 'ECDSA_CAIT_SITH', // Legacy alias of K256 - EcdsaCAITSITHP256: 'EcdsaCaitSithP256', - EcdsaK256Sha256: 'EcdsaK256Sha256', // same as caitsith -} as const; +import { z } from 'zod'; + +import { ObjectMapFromArray } from './utils'; + +// export type BlsSigType = 'Bls12381G1ProofOfPossession'; + +export type EcdsaSigType = + | 'EcdsaK256Sha256' + | 'EcdsaP256Sha256' + | 'EcdsaP384Sha384'; + +export type FrostSigType = + | 'SchnorrEd25519Sha512' + | 'SchnorrK256Sha256' + | 'SchnorrP256Sha256' + | 'SchnorrP384Sha384' + | 'SchnorrRistretto25519Sha512' + | 'SchnorrEd448Shake256' + | 'SchnorrRedJubjubBlake2b512' + | 'SchnorrK256Taproot' + | 'SchnorrRedDecaf377Blake2b512' + | 'SchnorrkelSubstrate'; + +export type SigType = /* BlsSigType | */ EcdsaSigType | FrostSigType; -export type LIT_CURVE_TYPE = keyof typeof LIT_CURVE; +// ----- Frost Variant +export const LIT_FROST_VARIANT_VALUES = [ + 'SchnorrEd25519Sha512', + 'SchnorrK256Sha256', + 'SchnorrP256Sha256', + 'SchnorrP384Sha384', + 'SchnorrRistretto25519Sha512', + 'SchnorrEd448Shake256', + 'SchnorrRedJubjubBlake2b512', + 'SchnorrK256Taproot', + 'SchnorrRedDecaf377Blake2b512', + 'SchnorrkelSubstrate', +] as const satisfies readonly FrostSigType[]; +export const LIT_FROST_VARIANT = ObjectMapFromArray(LIT_FROST_VARIANT_VALUES); +export const LIT_FROST_VARIANT_SCHEMA = z.enum(LIT_FROST_VARIANT_VALUES); +export type LitFrostVariantType = z.infer; -// This should replicate SigShare.sigType in types package +// ----- BLS Variant +// export const LIT_BLS_VARIANT_VALUES = [ +// 'Bls12381G1ProofOfPossession', +// ] as const satisfies readonly BlsSigType[]; +// export const LIT_BLS_VARIANT = ObjectMapFromArray(LIT_BLS_VARIANT_VALUES); +// export const LIT_BLS_VARIANT_SCHEMA = z.enum(LIT_BLS_VARIANT_VALUES); +// export type LitBlsVariantType = z.infer; + +// ----- ECDSA Variant +export const LIT_ECDSA_VARIANT_VALUES = [ + 'EcdsaK256Sha256', + 'EcdsaP256Sha256', + 'EcdsaP384Sha384', +] as const satisfies readonly EcdsaSigType[]; +export const LIT_ECDSA_VARIANT = ObjectMapFromArray(LIT_ECDSA_VARIANT_VALUES); +export const LIT_ECDSA_VARIANT_SCHEMA = z.enum(LIT_ECDSA_VARIANT_VALUES); +export type LitEcdsaVariantType = z.infer; + +// ----- All Curve Types +export const LIT_CURVE = { + // ...LIT_BLS_VARIANT, + ...LIT_FROST_VARIANT, + ...LIT_ECDSA_VARIANT, +}; + +export type LIT_CURVE_TYPE = keyof typeof LIT_CURVE; // Identical to SigType = BlsSigType | EcdsaSigType | FrostSigType; export type LIT_CURVE_VALUES = (typeof LIT_CURVE)[keyof typeof LIT_CURVE]; -/** - * CHANGE: This is not needed when the combiner is integrated - */ -export const CURVE_GROUPS = ['ECDSA', 'BLS'] as const; - -/** - * CHANGE: This is not needed when the combiner is integrated - */ + +export const CURVE_GROUPS = ['BLS', 'ECDSA', 'FROST'] as const; + export const CURVE_GROUP_BY_CURVE_TYPE: Record< LIT_CURVE_VALUES, (typeof CURVE_GROUPS)[number] > = { - [LIT_CURVE.EcdsaK256]: CURVE_GROUPS[0], - [LIT_CURVE.EcdsaK256Sha256]: CURVE_GROUPS[0], - [LIT_CURVE.EcdsaCAITSITHP256]: CURVE_GROUPS[0], - [LIT_CURVE.EcdsaCaitSith]: CURVE_GROUPS[0], - [LIT_CURVE.BLS]: CURVE_GROUPS[1], + // BLS + // [LIT_CURVE.Bls12381G1ProofOfPossession]: CURVE_GROUPS[0], + // ECDSA + [LIT_CURVE.EcdsaK256Sha256]: CURVE_GROUPS[1], + [LIT_CURVE.EcdsaP256Sha256]: CURVE_GROUPS[1], + [LIT_CURVE.EcdsaP384Sha384]: CURVE_GROUPS[1], + // FROST + [LIT_CURVE.SchnorrEd25519Sha512]: CURVE_GROUPS[2], + [LIT_CURVE.SchnorrK256Sha256]: CURVE_GROUPS[2], + [LIT_CURVE.SchnorrP256Sha256]: CURVE_GROUPS[2], + [LIT_CURVE.SchnorrP384Sha384]: CURVE_GROUPS[2], + [LIT_CURVE.SchnorrRistretto25519Sha512]: CURVE_GROUPS[2], + [LIT_CURVE.SchnorrEd448Shake256]: CURVE_GROUPS[2], + [LIT_CURVE.SchnorrRedJubjubBlake2b512]: CURVE_GROUPS[2], + [LIT_CURVE.SchnorrK256Taproot]: CURVE_GROUPS[2], + [LIT_CURVE.SchnorrRedDecaf377Blake2b512]: CURVE_GROUPS[2], + [LIT_CURVE.SchnorrkelSubstrate]: CURVE_GROUPS[2], } as const; diff --git a/packages/constants/src/lib/constants/mappers.ts b/packages/constants/src/lib/constants/mappers.ts index ac93bb0eb..d80101d33 100644 --- a/packages/constants/src/lib/constants/mappers.ts +++ b/packages/constants/src/lib/constants/mappers.ts @@ -1,4 +1,4 @@ -import { _nagaDev } from '@lit-protocol/contracts'; +import { nagaDev } from '@lit-protocol/contracts'; import { LIT_NETWORK_VALUES } from './constants'; @@ -6,9 +6,9 @@ import { LIT_NETWORK_VALUES } from './constants'; * Mapping of network context by network value. */ export const NETWORK_CONTEXT_BY_NETWORK: { - [key in LIT_NETWORK_VALUES]: typeof _nagaDev | undefined; + [key in LIT_NETWORK_VALUES]: typeof nagaDev | undefined; } = { - 'naga-dev': _nagaDev, + 'naga-dev': nagaDev, custom: undefined, } as const; @@ -36,3 +36,5 @@ export const PRODUCT_IDS = { SIGN: 1, // For signing operations LIT_ACTION: 2, // For Lit Actions execution } as const; +export type PRODUCT_IDS_TYPE = keyof typeof PRODUCT_IDS; +export type PRODUCT_IDS_VALUES = (typeof PRODUCT_IDS)[keyof typeof PRODUCT_IDS]; diff --git a/packages/constants/src/lib/constants/utils.ts b/packages/constants/src/lib/constants/utils.ts new file mode 100644 index 000000000..450da3106 --- /dev/null +++ b/packages/constants/src/lib/constants/utils.ts @@ -0,0 +1,11 @@ +/** + * @example + * const obj = ['a', 'b', 'c'] + * ObjectMapFromArray(obj) // { a: 'a', b: 'b', c: 'c' } + */ +export const ObjectMapFromArray = (arr: T) => { + return arr.reduce( + (acc, scope) => ({ ...acc, [scope]: scope }), + {} as { [K in T[number]]: K } + ); +}; diff --git a/packages/contracts-sdk/src/lib/contracts-sdk.ts b/packages/contracts-sdk/src/lib/contracts-sdk.ts index f0fb8e067..f73c0fd5c 100644 --- a/packages/contracts-sdk/src/lib/contracts-sdk.ts +++ b/packages/contracts-sdk/src/lib/contracts-sdk.ts @@ -51,9 +51,6 @@ import { decToHex, hexToDec, intToIP } from './hex2dec'; import { getPriceFeedInfo } from './price-feed-info-manager'; import { ValidatorStruct } from './types'; -// CHANGE: this should be dynamically set, but we only have 1 net atm. -const REALM_ID = 1; - declare global { interface Window { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -394,11 +391,35 @@ export class LitContracts { network: LIT_NETWORKS_KEYS, litContractName: ContractName ) { + let contractContext: LitContractContext | undefined; + + if (!contractContext && network === 'custom') { + contractContext = this.customContext as unknown as LitContractContext; + } else { + const networkContext = NETWORK_CONTEXT_BY_NETWORK[ + network + ] as unknown as LitContractContext; + + // Find the contract data from the network context + const contractData = networkContext['data'].find((data: any) => { + return data.name === litContractName; + }); + + // If found, transform it to the expected format + if (contractData) { + contractContext = {} as LitContractContext; + contractContext[litContractName] = { + address: contractData.contracts[0].address_hash, + abi: contractData.contracts[0].ABI, + }; + } + } + return LitContracts.getLitContract( network, litContractName, ...(this.rpc ? [this.rpc] : []), - ...(this.customContext ? [this.customContext] : []), + ...(contractContext ? [contractContext] : []), ...(this.signer ? [this.signer] : []) ); } @@ -638,6 +659,17 @@ export class LitContracts { environment ); break; + default: + throw new InvalidArgumentException( + { + info: { + contract, + environment, + contractNames, + }, + }, + 'Contract not found' + ); } return address; @@ -834,11 +866,13 @@ export class LitContracts { networkContext, rpcUrl, nodeProtocol, + realmId, }: { litNetwork: LIT_NETWORKS_KEYS; networkContext?: LitContractContext | LitContractResolverContext; rpcUrl?: string; nodeProtocol?: typeof HTTP | typeof HTTPS | null; + realmId: number; }): Promise<{ stakingContract: ethers.Contract; epochInfo: EpochInfo; @@ -854,7 +888,7 @@ export class LitContracts { const [epochInfo, minNodeCount, activeUnkickedValidatorStructs] = await stakingContract['getActiveUnkickedValidatorStructsAndCounts']( - REALM_ID + realmId ); const typedEpochInfo: EpochInfo = { @@ -888,6 +922,23 @@ export class LitContracts { reward: item[4], seconderPubkey: item[5], receiverPubkey: item[6], + lastActiveEpoch: item[7], + commission: item[8], + lastRewardEpoch: item[9], + + // -- new params for naga + // lastRealmId: BigNumber { _hex: '0x01', _isBigNumber: true }, + // delegatedStakeAmount: BigNumber { _hex: '0x00', _isBigNumber: true }, + // delegatedStakeWeight: BigNumber { _hex: '0x00', _isBigNumber: true }, + // lastRewardEpochClaimedFixedCostRewards: BigNumber { _hex: '0x00', _isBigNumber: true }, + // lastRewardEpochClaimedCommission: BigNumber { _hex: '0x00', _isBigNumber: true }, + // operatorAddress: '0x4542d87b0ceC8C9EFEC642452e82059Fc8346581' + lastRealmId: item[10], + delegatedStakeAmount: item[11], + delegatedStakeWeight: item[12], + lastRewardEpochClaimedFixedCostRewards: item[13], + lastRewardEpochClaimedCommission: item[14], + operatorAddress: item[15], }; }); @@ -899,7 +950,7 @@ export class LitContracts { // networks are all the nodes we know from the `getActiveUnkickedValidatorStructsAndCounts` function, but we also want to sort it by price feed // which we need to call the price feed contract const priceFeedInfo = await LitContracts.getPriceFeedInfo({ - realmId: REALM_ID, + realmId, litNetwork, networkContext, rpcUrl, @@ -1013,6 +1064,7 @@ export class LitContracts { this.network, 'PKPNFT' ); + if (!pkpNftContract) { throw new InitError( { diff --git a/packages/core/src/lib/lit-core.ts b/packages/core/src/lib/lit-core.ts index 7ee0479e7..ca9485416 100644 --- a/packages/core/src/lib/lit-core.ts +++ b/packages/core/src/lib/lit-core.ts @@ -9,8 +9,6 @@ import { InvalidEthBlockhash, InvalidNodeAttestation, InvalidParamType, - LIT_CURVE, - LIT_CURVE_VALUES, LIT_ENDPOINT, LIT_ERROR_CODE, LIT_NETWORK, @@ -118,6 +116,7 @@ export type LitNodeClientConfigWithDefaults = Required< }; export class LitCore { + private _realmId: number = 1; config: LitNodeClientConfigWithDefaults = { alertWhenUnauthorized: false, debug: true, @@ -139,6 +138,7 @@ export class LitCore { lastBlockHashRetrieved: number | null = null; private _stakingContract: ethers.Contract | null = null; private _stakingContractListener: null | Listener = null; + private _advancedEpochListener: null | Listener = null; private _connectingPromise: null | Promise = null; private _epochCache: EpochCache = { currentNumber: null, @@ -147,6 +147,14 @@ export class LitCore { private _blockHashUrl = 'https://block-indexer.litgateway.com/get_most_recent_valid_block'; + /** + * Get the current realm ID. + * @returns The current realm ID + */ + get realmId(): number { + return this._realmId; + } + // ========== Constructor ========== constructor(config: LitNodeClientConfig | CustomNetwork) { if (!(config.litNetwork in LIT_NETWORKS)) { @@ -242,6 +250,7 @@ export class LitCore { networkContext: this.config.contractContext, rpcUrl: this.config.rpcUrl, nodeProtocol: this.config.nodeProtocol, + realmId: this._realmId, }); // Validate minNodeCount @@ -344,6 +353,42 @@ export class LitCore { } } + private async _handleAdvancedEpoch(realmId: number, epochNumber: number) { + log( + `AdvancedEpoch detected: realmId=${realmId}, epochNumber=${epochNumber}` + ); + + // Update the realmId + this._realmId = realmId; + + // Get updated validator data for the new realm + const validatorData = await this._getValidatorData(); + + // Update epoch state + this._epochState = await this._fetchCurrentEpochState( + validatorData.epochInfo + ); + + // Reconnect if not on a centralized network + if (CENTRALISATION_BY_NETWORK[this.config.litNetwork] !== 'centralised') { + try { + log( + 'Realm ID changed, reconnecting to get correct validator set for new realm' + ); + + // We always reconnect on realm change + log('Reconnecting for new realm ID:', realmId); + await this.connect(); + } catch (err: unknown) { + const { message = '' } = err as Error; + logError( + 'Error while attempting to reconnect to nodes after realm change:', + message + ); + } + } + } + /** * Sets up a listener to detect state changes (new epochs) in the staking contract. * When a new epoch is detected, it triggers the `setNewConfig` function to update @@ -373,6 +418,28 @@ export class LitCore { } } + private _listenForAdvancedEpoch() { + // Check if we've already set up the listener to avoid duplicates + if (this._advancedEpochListener) { + return; + } + + if (this._stakingContract) { + log( + 'listening for AdvancedEpoch event on staking contract: ', + this._stakingContract.address + ); + + // Create a function instance for the listener + this._advancedEpochListener = (realmId: number, epochNumber: number) => { + // Handle the AdvancedEpoch event + this._handleAdvancedEpoch(realmId, epochNumber); + }; + + this._stakingContract.on('AdvancedEpoch', this._advancedEpochListener); + } + } + /** * Gets the set of nodes from validator data, transforming bootstrap URLs into NodeSet objects. * @@ -409,6 +476,11 @@ export class LitCore { this._stakingContract.off('StateChanged', this._stakingContractListener); this._stakingContractListener = null; } + + if (this._stakingContract && this._advancedEpochListener) { + this._stakingContract.off('AdvancedEpoch', this._advancedEpochListener); + this._advancedEpochListener = null; + } } /** @@ -514,6 +586,7 @@ export class LitCore { // this._scheduleNetworkSync(); this._listenForNewEpoch(); + this._listenForAdvancedEpoch(); this.ready = true; @@ -1230,16 +1303,12 @@ export class LitCore { }; /** - * Calculates an HD public key from a given keyId - * The curve type or signature type is assumed to be k256 unless provided + * Calculates an K256 HD public key from a given keyId + * * @param keyId - * @param {LIT_CURVE_VALUES} sigType * @returns {string} public key */ - computeHDPubKey = async ( - keyId: string, - sigType: LIT_CURVE_VALUES = LIT_CURVE.EcdsaCaitSith - ): Promise => { + computeHDPubKey = async (keyId: string): Promise => { if (!this.hdRootPubkeys) { logError('root public keys not found, have you connected to the nodes?'); throw new LitNodeClientNotReadyError( @@ -1247,11 +1316,7 @@ export class LitCore { 'root public keys not found, have you connected to the nodes?' ); } - return await computeHDPubKey( - this.hdRootPubkeys as string[], - keyId, - sigType - ); + return await computeHDPubKey(this.hdRootPubkeys as string[], keyId); }; /** diff --git a/packages/crypto/src/lib/crypto.spec.ts b/packages/crypto/src/lib/crypto.spec.ts index 8dd8980e7..1e7c1a6c2 100644 --- a/packages/crypto/src/lib/crypto.spec.ts +++ b/packages/crypto/src/lib/crypto.spec.ts @@ -1,69 +1,634 @@ -import * as ethers from 'ethers'; -import { joinSignature } from 'ethers/lib/utils'; +import { NoValidShares } from '@lit-protocol/constants'; +import { + CleanLitNodeSignature, + LitActionSignedData, + PKPSignEndpointResponse, +} from '@lit-protocol/types'; +import { nacl } from '@lit-protocol/nacl'; -import { SigShare } from '@lit-protocol/types'; +import { + combineExecuteJsNodeShares, + combinePKPSignNodeShares, + walletEncrypt, + walletDecrypt, +} from './crypto'; -import { combineEcdsaShares } from './crypto'; +const MOCK_SESSION_SIGS = { + 'http://127.0.0.1:7470': { + sig: 'fefcd74c2bb2794356a10e62722c2ca4ef47386475ca72865d8dd7cc096fd1715d8b076b29349328e0b13d09f3296768e6b1cbb81e02d2b697b7641984260b01', + derivedVia: 'litSessionSignViaNacl', + signedMessage: `{"sessionKey":"6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d","resourceAbilityRequests":[{"resource":{"resource":"*","resourcePrefix":"lit-pkp"},"ability":"pkp-signing"},{"resource":{"resource":"*","resourcePrefix":"lit-litaction"},"ability":"lit-action-execution"}],"capabilities":[{"sig":"0x64192b2156f6d60f2c9aed887f5a0c6003afb6cd7a25b94683c74311fe895e8849a178d4edbe9f38cb0d8a3d7d0342b67b521ada92b68f73f0a1e5fa4f33ae751c","derivedVia":"web3.eth.personal.sign","signedMessage":"localhost wants you to sign in with your Ethereum account:\\n0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC\\n\\nThis is a test statement. You can put anything you want here. I further authorize the stated URI to perform the following actions on my behalf: (1) 'Threshold': 'Execution' for 'lit-litaction://*'. (2) 'Threshold': 'Signing' for 'lit-pkp://*'.\\n\\nURI: lit:session:6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d\\nVersion: 1\\nChain ID: 1\\nNonce: 0x4ee32274a45ab4622d49ea0f9bc3984f2a251e7b88320e23e231673efa564280\\nIssued At: 2025-04-09T14:37:09.339Z\\nExpiration Time: 2025-04-10T14:37:09.337Z\\nResources:\\n- urn:recap:eyJhdHQiOnsibGl0LWxpdGFjdGlvbjovLyoiOnsiVGhyZXNob2xkL0V4ZWN1dGlvbiI6W3t9XX0sImxpdC1wa3A6Ly8qIjp7IlRocmVzaG9sZC9TaWduaW5nIjpbe31dfX0sInByZiI6W119","address":"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"}],"issuedAt":"2025-04-09T14:37:09.373Z","expiration":"2025-04-10T14:37:09.337Z","nodeAddress":"http://127.0.0.1:7470","maxPrice":"113427455640312821154458202477256070485"}`, + address: '6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d', + algo: 'ed25519', + }, + 'http://127.0.0.1:7471': { + sig: 'ec550ae2addd6fbc399ef26158b9cf8b2a1c52240ee60b62c49c8a782c020d0aae5602090029f5a0024f936f0a747d7c007dfecee44bbe99a5cc12dcef7d3309', + derivedVia: 'litSessionSignViaNacl', + signedMessage: `{"sessionKey":"6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d","resourceAbilityRequests":[{"resource":{"resource":"*","resourcePrefix":"lit-pkp"},"ability":"pkp-signing"},{"resource":{"resource":"*","resourcePrefix":"lit-litaction"},"ability":"lit-action-execution"}],"capabilities":[{"sig":"0x64192b2156f6d60f2c9aed887f5a0c6003afb6cd7a25b94683c74311fe895e8849a178d4edbe9f38cb0d8a3d7d0342b67b521ada92b68f73f0a1e5fa4f33ae751c","derivedVia":"web3.eth.personal.sign","signedMessage":"localhost wants you to sign in with your Ethereum account:\\n0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC\\n\\nThis is a test statement. You can put anything you want here. I further authorize the stated URI to perform the following actions on my behalf: (1) 'Threshold': 'Execution' for 'lit-litaction://*'. (2) 'Threshold': 'Signing' for 'lit-pkp://*'.\\n\\nURI: lit:session:6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d\\nVersion: 1\\nChain ID: 1\\nNonce: 0x4ee32274a45ab4622d49ea0f9bc3984f2a251e7b88320e23e231673efa564280\\nIssued At: 2025-04-09T14:37:09.339Z\\nExpiration Time: 2025-04-10T14:37:09.337Z\\nResources:\\n- urn:recap:eyJhdHQiOnsibGl0LWxpdGFjdGlvbjovLyoiOnsiVGhyZXNob2xkL0V4ZWN1dGlvbiI6W3t9XX0sImxpdC1wa3A6Ly8qIjp7IlRocmVzaG9sZC9TaWduaW5nIjpbe31dfX0sInByZiI6W119","address":"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"}],"issuedAt":"2025-04-09T14:37:09.373Z","expiration":"2025-04-10T14:37:09.337Z","nodeAddress":"http://127.0.0.1:7471","maxPrice":"113427455640312821154458202477256070485"}`, + address: '6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d', + algo: 'ed25519', + }, + 'http://127.0.0.1:7472': { + sig: '5f4d83f7fc425ebfca85e45fed0799b93ac215c5ae1c7d279217ca70c434eb43db6e282f365d4f4dc7d76494548e33e4cb6e5f8901b5f142e447fc9718589f07', + derivedVia: 'litSessionSignViaNacl', + signedMessage: `{"sessionKey":"6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d","resourceAbilityRequests":[{"resource":{"resource":"*","resourcePrefix":"lit-pkp"},"ability":"pkp-signing"},{"resource":{"resource":"*","resourcePrefix":"lit-litaction"},"ability":"lit-action-execution"}],"capabilities":[{"sig":"0x64192b2156f6d60f2c9aed887f5a0c6003afb6cd7a25b94683c74311fe895e8849a178d4edbe9f38cb0d8a3d7d0342b67b521ada92b68f73f0a1e5fa4f33ae751c","derivedVia":"web3.eth.personal.sign","signedMessage":"localhost wants you to sign in with your Ethereum account:\\n0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC\\n\\nThis is a test statement. You can put anything you want here. I further authorize the stated URI to perform the following actions on my behalf: (1) 'Threshold': 'Execution' for 'lit-litaction://*'. (2) 'Threshold': 'Signing' for 'lit-pkp://*'.\\n\\nURI: lit:session:6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d\\nVersion: 1\\nChain ID: 1\\nNonce: 0x4ee32274a45ab4622d49ea0f9bc3984f2a251e7b88320e23e231673efa564280\\nIssued At: 2025-04-09T14:37:09.339Z\\nExpiration Time: 2025-04-10T14:37:09.337Z\\nResources:\\n- urn:recap:eyJhdHQiOnsibGl0LWxpdGFjdGlvbjovLyoiOnsiVGhyZXNob2xkL0V4ZWN1dGlvbiI6W3t9XX0sImxpdC1wa3A6Ly8qIjp7IlRocmVzaG9sZC9TaWduaW5nIjpbe31dfX0sInByZiI6W119","address":"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"}],"issuedAt":"2025-04-09T14:37:09.373Z","expiration":"2025-04-10T14:37:09.337Z","nodeAddress":"http://127.0.0.1:7472","maxPrice":"113427455640312821154458202477256070485"}`, + address: '6db14e40ab381cf208b39aec31aebb025e7b12f0ceec60519e2b0c191f4fcb3d', + algo: 'ed25519', + }, +}; -describe('combine ECDSA Shares', () => { - it('Should recombine ECDSA signature shares', async () => { - const sigShares: SigShare[] = [ +describe('combineExecuteJsNodeShares', () => { + it('should throw when there are no shares to combine', async () => { + const shares: LitActionSignedData[] = []; + + await expect(combineExecuteJsNodeShares(shares)).rejects.toThrow( + NoValidShares + ); + }); + + it('should throw when signature combination cannot be verified', async () => { + // Insufficient shares for threshold + const shares: LitActionSignedData[] = [ + // { + // sigType: 'EcdsaK256Sha256', + // signatureShare: + // '{"EcdsaSignedMessageShare":{"digest":"04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62","result":"success","share_id":"\\"989DD924B8821903330AC0801F99EB27E3E5235EE299B2A06A611780EC0C7AE1\\"","peer_id":"5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a","signature_share":"\\"BD865CD2460CF80EC3384849CA91F2FFE99440771C18E3DC95CF35403CA1C78F\\"","big_r":"\\"033C83EFBCC67B3EC667DFCE53AD76DF7AA7D5F09834E32BF92B5DA42450AE173E\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + // publicKey: + // '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + // sigName: 'ethPersonalSignMessageEcdsa', + // }, + { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62","result":"success","share_id":"\\"816E54B265612C92CD87FC32892C024C9E2DD5FA67AA20FED4816A49A560FC03\\"","peer_id":"b104a1b35585fec58cbb632d17fbe60b10297d7853261ea9054d5e372952936a","signature_share":"\\"5F47F3F8439787E71F01C05B7AFF4E09B5E0D960110EEB91A3EC56A0E1DD06B1\\"","big_r":"\\"033C83EFBCC67B3EC667DFCE53AD76DF7AA7D5F09834E32BF92B5DA42450AE173E\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'ethPersonalSignMessageEcdsa', + }, + { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62","result":"success","share_id":"\\"B8EF7C21FAF54664646B64869D1B12861F6D905F1F1F095E60F7238806252AE3\\"","peer_id":"6555c8c26671f5e21611adba0a3c31b28128443f2d76c2818db169efcff38151","signature_share":"\\"D4C0F937C7088D32D9A027E4F774764BC80C189066936636A133CF0E98064C47\\"","big_r":"\\"033C83EFBCC67B3EC667DFCE53AD76DF7AA7D5F09834E32BF92B5DA42450AE173E\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'ethPersonalSignMessageEcdsa', + }, + ]; + + await expect(combineExecuteJsNodeShares(shares)).rejects.toThrow( + NoValidShares + ); + }); + + it('should combine shares from lit action signed data', async () => { + const shares: LitActionSignedData[] = [ { - sigType: 'ECDSA_CAIT_SITH' as const, + sigType: 'EcdsaK256Sha256', signatureShare: - 'BC8108AD9CAE8358942BB4B27632B87FFA705CCB675F85A59847CC1B84845A38', - bigR: '03E6D15C805443F57F57E180C730C2FCA5297F7671E8148A669410808AB4D70122', + '{"EcdsaSignedMessageShare":{"digest":"04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62","result":"success","share_id":"\\"989DD924B8821903330AC0801F99EB27E3E5235EE299B2A06A611780EC0C7AE1\\"","peer_id":"5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a","signature_share":"\\"BD865CD2460CF80EC3384849CA91F2FFE99440771C18E3DC95CF35403CA1C78F\\"","big_r":"\\"033C83EFBCC67B3EC667DFCE53AD76DF7AA7D5F09834E32BF92B5DA42450AE173E\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', publicKey: - '03AECABDF2EDC1194BED6FE9650F08D109C77D2526236EA3F6C20F88E0675643BC', - dataSigned: - '90AB86E6389AA65B56D701E36EEECD786242405C792ED863C395FA7C55E517A4', - sigName: 'sig', + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'ethPersonalSignMessageEcdsa', }, { - sigType: 'K256' as const, + sigType: 'EcdsaK256Sha256', signatureShare: - 'BA77EB500884A60583DEA49578D4BB64BB55EF497F37C88DF935D739CE8E0A9F', - bigR: '03E6D15C805443F57F57E180C730C2FCA5297F7671E8148A669410808AB4D70122', + '{"EcdsaSignedMessageShare":{"digest":"04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62","result":"success","share_id":"\\"816E54B265612C92CD87FC32892C024C9E2DD5FA67AA20FED4816A49A560FC03\\"","peer_id":"b104a1b35585fec58cbb632d17fbe60b10297d7853261ea9054d5e372952936a","signature_share":"\\"5F47F3F8439787E71F01C05B7AFF4E09B5E0D960110EEB91A3EC56A0E1DD06B1\\"","big_r":"\\"033C83EFBCC67B3EC667DFCE53AD76DF7AA7D5F09834E32BF92B5DA42450AE173E\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', publicKey: - '03AECABDF2EDC1194BED6FE9650F08D109C77D2526236EA3F6C20F88E0675643BC', - dataSigned: - '90AB86E6389AA65B56D701E36EEECD786242405C792ED863C395FA7C55E517A4', - sigName: 'sig', + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'ethPersonalSignMessageEcdsa', }, { - sigType: 'ECDSA_CAIT_SITH' as const, + sigType: 'EcdsaK256Sha256', signatureShare: - 'EF850AE61B6D658976B2560B880BF03ABC1A070BACDEAE2311781F65A524F245', - bigR: '03E6D15C805443F57F57E180C730C2FCA5297F7671E8148A669410808AB4D70122', + '{"EcdsaSignedMessageShare":{"digest":"04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62","result":"success","share_id":"\\"B8EF7C21FAF54664646B64869D1B12861F6D905F1F1F095E60F7238806252AE3\\"","peer_id":"6555c8c26671f5e21611adba0a3c31b28128443f2d76c2818db169efcff38151","signature_share":"\\"D4C0F937C7088D32D9A027E4F774764BC80C189066936636A133CF0E98064C47\\"","big_r":"\\"033C83EFBCC67B3EC667DFCE53AD76DF7AA7D5F09834E32BF92B5DA42450AE173E\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', publicKey: - '03AECABDF2EDC1194BED6FE9650F08D109C77D2526236EA3F6C20F88E0675643BC', - dataSigned: - '90AB86E6389AA65B56D701E36EEECD786242405C792ED863C395FA7C55E517A4', - sigName: 'sig', + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'ethPersonalSignMessageEcdsa', + }, + ]; + const combinedSignature: CleanLitNodeSignature = { + signature: + '0x3C83EFBCC67B3EC667DFCE53AD76DF7AA7D5F09834E32BF92B5DA42450AE173E0E70B5FDAF52F2D74425CF75C2FA48A80DDC8765CAD60AD2A4B56229E9E767FB', + verifyingKey: + '0x3056301006072A8648CE3D020106052B8104000A0342000450A6083580384CBCDDD0D809165BA8EE53B5E768724C7B6080AE55790DA9508125810F89BAB7D56077E37BE1681463A6262108E50FBA439D94808F3CB1CC704E', + signedData: + '0x04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62', + recoveryId: 0, + }; + + await expect(combineExecuteJsNodeShares(shares)).resolves.toEqual( + combinedSignature + ); + }); +}); + +describe('combinePKPSignNodeShares', () => { + it('should throw when there are no shares to combine', async () => { + const shares: PKPSignEndpointResponse[] = []; + + await expect(combinePKPSignNodeShares(shares)).rejects.toThrow( + NoValidShares + ); + }); + + it('should throw when signature combination cannot be verified', async () => { + // Insufficient shares for threshold + const shares: PKPSignEndpointResponse[] = [ + // { + // success: true, + // signedData: [ + // 116, 248, 31, 225, 103, 217, 155, 76, 180, 29, 109, 12, 205, 168, 34, + // 120, 202, 238, 159, 62, 47, 37, 213, 229, 163, 147, 111, 243, 220, + // 236, 96, 208, + // ], + // signatureShare: { + // EcdsaSignedMessageShare: { + // digest: + // '74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + // result: 'success', + // share_id: + // '"989DD924B8821903330AC0801F99EB27E3E5235EE299B2A06A611780EC0C7AE1"', + // peer_id: + // '5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a', + // signature_share: + // '"89936DDC19D3A6A16C65CE5B40E41AB19258D04986B78FE6C3EEB2F37C996099"', + // big_r: + // '"03BFEA95308F38CBA7C8AF5831EA8CC35D418C4E574ADE5B77B38A1E565AF7E8E1"', + // compressed_public_key: + // '"028f9710cb7eeea2cfa126d9103e471c776a2d1b5263d29176587d1a86b12dc039"', + // public_key: + // '"048f9710cb7eeea2cfa126d9103e471c776a2d1b5263d29176587d1a86b12dc039e88803650af44072ecd9c85837701916bb48259ccb39720808402f1e5e008a32"', + // sig_type: 'EcdsaK256Sha256', + // }, + // }, + // }, + { + success: true, + signedData: [ + 116, 248, 31, 225, 103, 217, 155, 76, 180, 29, 109, 12, 205, 168, 34, + 120, 202, 238, 159, 62, 47, 37, 213, 229, 163, 147, 111, 243, 220, + 236, 96, 208, + ], + signatureShare: { + EcdsaSignedMessageShare: { + digest: + '74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + result: 'success', + share_id: + '"816E54B265612C92CD87FC32892C024C9E2DD5FA67AA20FED4816A49A560FC03"', + peer_id: + 'b104a1b35585fec58cbb632d17fbe60b10297d7853261ea9054d5e372952936a', + signature_share: + '"3817ED8D97A7DA25DB20911CD57E0B90A83FA4A22DCE411A5C5B3409C92B338C"', + big_r: + '"03BFEA95308F38CBA7C8AF5831EA8CC35D418C4E574ADE5B77B38A1E565AF7E8E1"', + compressed_public_key: + '"028f9710cb7eeea2cfa126d9103e471c776a2d1b5263d29176587d1a86b12dc039"', + public_key: + '"048f9710cb7eeea2cfa126d9103e471c776a2d1b5263d29176587d1a86b12dc039e88803650af44072ecd9c85837701916bb48259ccb39720808402f1e5e008a32"', + sig_type: 'EcdsaK256Sha256', + }, + }, + }, + { + success: true, + signedData: [ + 116, 248, 31, 225, 103, 217, 155, 76, 180, 29, 109, 12, 205, 168, 34, + 120, 202, 238, 159, 62, 47, 37, 213, 229, 163, 147, 111, 243, 220, + 236, 96, 208, + ], + signatureShare: { + EcdsaSignedMessageShare: { + digest: + '74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + result: 'success', + share_id: + '"B8EF7C21FAF54664646B64869D1B12861F6D905F1F1F095E60F7238806252AE3"', + peer_id: + '6555c8c26671f5e21611adba0a3c31b28128443f2d76c2818db169efcff38151', + signature_share: + '"7E06B7D903A515F366AC93EBA5A5598130B9A85984BC9A644999AE793B544547"', + big_r: + '"03BFEA95308F38CBA7C8AF5831EA8CC35D418C4E574ADE5B77B38A1E565AF7E8E1"', + compressed_public_key: + '"028f9710cb7eeea2cfa126d9103e471c776a2d1b5263d29176587d1a86b12dc039"', + public_key: + '"048f9710cb7eeea2cfa126d9103e471c776a2d1b5263d29176587d1a86b12dc039e88803650af44072ecd9c85837701916bb48259ccb39720808402f1e5e008a32"', + sig_type: 'EcdsaK256Sha256', + }, + }, }, ]; - const sig = await combineEcdsaShares(sigShares); - expect(sig.r).toBeDefined(); - expect(sig.s).toBeDefined(); - expect(sig.recid).toBeDefined(); + await expect(combinePKPSignNodeShares(shares)).rejects.toThrow( + NoValidShares + ); + }); + + it('should combine shares from lit action signed data using ECDSA', async () => { + const shares: PKPSignEndpointResponse[] = [ + { + success: true, + signedData: [ + 116, 248, 31, 225, 103, 217, 155, 76, 180, 29, 109, 12, 205, 168, 34, + 120, 202, 238, 159, 62, 47, 37, 213, 229, 163, 147, 111, 243, 220, + 236, 96, 208, + ], + signatureShare: { + EcdsaSignedMessageShare: { + digest: + '74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + result: 'success', + share_id: + '"989DD924B8821903330AC0801F99EB27E3E5235EE299B2A06A611780EC0C7AE1"', + peer_id: + '5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a', + signature_share: + '"89936DDC19D3A6A16C65CE5B40E41AB19258D04986B78FE6C3EEB2F37C996099"', + big_r: + '"03BFEA95308F38CBA7C8AF5831EA8CC35D418C4E574ADE5B77B38A1E565AF7E8E1"', + compressed_public_key: + '"028f9710cb7eeea2cfa126d9103e471c776a2d1b5263d29176587d1a86b12dc039"', + public_key: + '"048f9710cb7eeea2cfa126d9103e471c776a2d1b5263d29176587d1a86b12dc039e88803650af44072ecd9c85837701916bb48259ccb39720808402f1e5e008a32"', + sig_type: 'EcdsaK256Sha256', + }, + }, + }, + { + success: true, + signedData: [ + 116, 248, 31, 225, 103, 217, 155, 76, 180, 29, 109, 12, 205, 168, 34, + 120, 202, 238, 159, 62, 47, 37, 213, 229, 163, 147, 111, 243, 220, + 236, 96, 208, + ], + signatureShare: { + EcdsaSignedMessageShare: { + digest: + '74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + result: 'success', + share_id: + '"816E54B265612C92CD87FC32892C024C9E2DD5FA67AA20FED4816A49A560FC03"', + peer_id: + 'b104a1b35585fec58cbb632d17fbe60b10297d7853261ea9054d5e372952936a', + signature_share: + '"3817ED8D97A7DA25DB20911CD57E0B90A83FA4A22DCE411A5C5B3409C92B338C"', + big_r: + '"03BFEA95308F38CBA7C8AF5831EA8CC35D418C4E574ADE5B77B38A1E565AF7E8E1"', + compressed_public_key: + '"028f9710cb7eeea2cfa126d9103e471c776a2d1b5263d29176587d1a86b12dc039"', + public_key: + '"048f9710cb7eeea2cfa126d9103e471c776a2d1b5263d29176587d1a86b12dc039e88803650af44072ecd9c85837701916bb48259ccb39720808402f1e5e008a32"', + sig_type: 'EcdsaK256Sha256', + }, + }, + }, + { + success: true, + signedData: [ + 116, 248, 31, 225, 103, 217, 155, 76, 180, 29, 109, 12, 205, 168, 34, + 120, 202, 238, 159, 62, 47, 37, 213, 229, 163, 147, 111, 243, 220, + 236, 96, 208, + ], + signatureShare: { + EcdsaSignedMessageShare: { + digest: + '74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + result: 'success', + share_id: + '"B8EF7C21FAF54664646B64869D1B12861F6D905F1F1F095E60F7238806252AE3"', + peer_id: + '6555c8c26671f5e21611adba0a3c31b28128443f2d76c2818db169efcff38151', + signature_share: + '"7E06B7D903A515F366AC93EBA5A5598130B9A85984BC9A644999AE793B544547"', + big_r: + '"03BFEA95308F38CBA7C8AF5831EA8CC35D418C4E574ADE5B77B38A1E565AF7E8E1"', + compressed_public_key: + '"028f9710cb7eeea2cfa126d9103e471c776a2d1b5263d29176587d1a86b12dc039"', + public_key: + '"048f9710cb7eeea2cfa126d9103e471c776a2d1b5263d29176587d1a86b12dc039e88803650af44072ecd9c85837701916bb48259ccb39720808402f1e5e008a32"', + sig_type: 'EcdsaK256Sha256', + }, + }, + }, + ]; + const combinedSignature: CleanLitNodeSignature = { + signature: + '0xBFEA95308F38CBA7C8AF5831EA8CC35D418C4E574ADE5B77B38A1E565AF7E8E13FB21342B52096BAAE32F363BC077FC4B0A3405E89F9CB29AA1136E9B0E2982B', + verifyingKey: + '0x3056301006072A8648CE3D020106052B8104000A034200048F9710CB7EEEA2CFA126D9103E471C776A2D1B5263D29176587D1A86B12DC039E88803650AF44072ECD9C85837701916BB48259CCB39720808402F1E5E008A32', + signedData: + '0x74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + recoveryId: 1, + }; - const sigRes = joinSignature({ - r: '0x' + sig.r, - s: '0x' + sig.s, - v: sig.recid, - }); + await expect(combinePKPSignNodeShares(shares)).resolves.toEqual( + combinedSignature + ); + }); - const msg = ethers.utils.arrayify('0x' + sigShares[0].dataSigned); - const recoveredPk = ethers.utils.recoverPublicKey(msg, sigRes); + it('should combine shares from lit action signed data using FROST', async () => { + const shares: PKPSignEndpointResponse[] = [ + { + success: true, + signedData: [1, 2, 3, 4, 5], + signatureShare: { + FrostSignedMessageShare: { + message: '0102030405', + result: 'success', + share_id: + '["Ed25519Sha512",[229,242,127,190,79,231,53,41,103,129,79,210,21,111,246,106,35,29,126,255,3,70,87,183,242,91,230,44,3,61,116,3]]', + peer_id: + 'b104a1b35585fec58cbb632d17fbe60b10297d7853261ea9054d5e372952936a', + signature_share: + '["Ed25519Sha512","cd29ec508f37df75da5617add256cc3ed6c9f1176804ed6fd0c07cd912d2d60f"]', + signing_commitments: + '["Ed25519Sha512","00b169f0da371b4893357499464f33f9697082948d307403ee5066c06ffa42488cae2a9a8c1fa727d7f47b70cd0e8f3b7b01f23c4481fca18a54c9974a58072dd29986e4c8"]', + verifying_share: + '["Ed25519Sha512","a51fdfe8f71bbdff7fe6de23950aaa3aed215d42e6efc877d604eac1526340bb"]', + public_key: + '["Ed25519Sha512","365fd6758f9001e6460abf3af94b975afb25f362cf5d918438e9df41c5c18d51"]', + sig_type: 'SchnorrEd25519Sha512', + }, + }, + }, + { + success: true, + signedData: [1, 2, 3, 4, 5], + signatureShare: { + FrostSignedMessageShare: { + message: '0102030405', + result: 'success', + share_id: + '["Ed25519Sha512",[133,105,1,43,107,189,19,79,100,113,137,109,5,182,163,167,182,34,140,214,3,95,44,102,178,192,50,247,195,232,33,2]]', + peer_id: + '5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a', + signature_share: + '["Ed25519Sha512","b67a3a373935f3ba1c936130d4a218e2b4f0938432eaa1e69c224fcbd500de09"]', + signing_commitments: + '["Ed25519Sha512","00b169f0da5a30c8476e01ad57a1efe5eabc75a1986e6cfe28f7fec2db304701492e561daa5bcf16b6ab41148b7355ac88eb0f6107a70863699c7f58f8a50114fe5a1536e7"]', + verifying_share: + '["Ed25519Sha512","1dd439c815ceaf7157cf7352a3b7cf2244e6509ea5e88a1c686a50779e05559b"]', + public_key: + '["Ed25519Sha512","365fd6758f9001e6460abf3af94b975afb25f362cf5d918438e9df41c5c18d51"]', + sig_type: 'SchnorrEd25519Sha512', + }, + }, + }, + { + success: true, + signedData: [1, 2, 3, 4, 5], + signatureShare: { + FrostSignedMessageShare: { + message: '0102030405', + result: 'success', + share_id: + '["Ed25519Sha512",[120,2,187,138,101,21,192,99,172,206,182,184,45,83,216,198,184,93,183,55,83,34,185,90,150,221,88,228,91,232,123,2]]', + peer_id: + '6555c8c26671f5e21611adba0a3c31b28128443f2d76c2818db169efcff38151', + signature_share: + '["Ed25519Sha512","8ec47092d3019deab5b338ee651cc1abb08b834e7ec4c501daddd38881109903"]', + signing_commitments: + '["Ed25519Sha512","00b169f0da8086dadbbea30cd0aaae8d1ed233b34fd97b43801726a3de8db45631f5c32e46ba759f778caff9f059bb1cccb3d82deddf51ea2b141bde38ca7b04790b398658"]', + verifying_share: + '["Ed25519Sha512","43d71074f58e0548f14e1aaf7a1f65d49a0a07fc657a4757ba00720739628541"]', + public_key: + '["Ed25519Sha512","365fd6758f9001e6460abf3af94b975afb25f362cf5d918438e9df41c5c18d51"]', + sig_type: 'SchnorrEd25519Sha512', + }, + }, + }, + ]; + const combinedSignature: CleanLitNodeSignature = { + signature: + '0x2aa5af31b860e5fdf8c2aeaa25db347a0220e834d25d7eabb8be14828b2e83c92495a1bd810b5dc3d600ba282e1cc7b73b4609eb18b3545847c19f2d6ae34d0d', + verifyingKey: + '0x365fd6758f9001e6460abf3af94b975afb25f362cf5d918438e9df41c5c18d51', + signedData: '0x0102030405', + recoveryId: null, + }; - // normalize the public keys to addresses and compare - const addr = ethers.utils.computeAddress( - ethers.utils.arrayify('0x' + sigShares[0].publicKey) + await expect(combinePKPSignNodeShares(shares)).resolves.toEqual( + combinedSignature ); - const recoveredAddr = ethers.utils.computeAddress( - ethers.utils.arrayify(recoveredPk) + }); +}); + +describe('walletEncrypt and walletDecrypt', () => { + it('should encrypt and decrypt a message successfully', async () => { + // Generate key pairs using the box functionality + const aliceKeyPair = nacl.box.keyPair(); + const bobKeyPair = nacl.box.keyPair(); + + console.log('aliceKeyPair', aliceKeyPair); + console.log('bobKeyPair', bobKeyPair); + + // Message to encrypt + const message = new TextEncoder().encode('This is a secret message'); + + // Alice encrypts a message for Bob + const encryptedPayload = await walletEncrypt( + aliceKeyPair.secretKey, + bobKeyPair.publicKey, + MOCK_SESSION_SIGS['http://127.0.0.1:7470'], + message ); - expect(recoveredAddr).toEqual(addr); + + console.log('encryptedPayload', encryptedPayload); + + // Verify payload structure + expect(encryptedPayload).toHaveProperty('V1'); + expect(encryptedPayload.V1).toHaveProperty('verification_key'); + expect(encryptedPayload.V1).toHaveProperty('ciphertext_and_tag'); + expect(encryptedPayload.V1).toHaveProperty('session_signature'); + expect(encryptedPayload.V1).toHaveProperty('random'); + expect(encryptedPayload.V1).toHaveProperty('created_at'); + + // Bob decrypts the message from Alice + const decryptedMessage = await walletDecrypt( + bobKeyPair.secretKey, + encryptedPayload + ); + + // Verify decryption was successful + expect(decryptedMessage).not.toBeNull(); + expect(new TextDecoder().decode(decryptedMessage as Uint8Array)).toBe( + 'This is a secret message' + ); + }); + + it('should return null when decryption fails', async () => { + // Generate key pairs + const aliceKeyPair = nacl.box.keyPair(); + const bobKeyPair = nacl.box.keyPair(); + const eveKeyPair = nacl.box.keyPair(); // Eve is an eavesdropper + + // Message to encrypt + const message = new TextEncoder().encode('This is a secret message'); + + // Alice encrypts a message for Bob + const encryptedPayload = await walletEncrypt( + aliceKeyPair.secretKey, + bobKeyPair.publicKey, + MOCK_SESSION_SIGS['http://127.0.0.1:7470'], + message + ); + + // Eve tries to decrypt the message with her key (should fail) + const decryptedByEve = await walletDecrypt( + eveKeyPair.secretKey, + encryptedPayload + ); + + // Verify decryption failed + expect(decryptedByEve).toBeNull(); + }); + + it('should handle tampering with the encrypted payload', async () => { + // Generate key pairs + const aliceKeyPair = nacl.box.keyPair(); + const bobKeyPair = nacl.box.keyPair(); + + // Message to encrypt + const message = new TextEncoder().encode('This is a secret message'); + + // Alice encrypts a message for Bob + const encryptedPayload = await walletEncrypt( + aliceKeyPair.secretKey, + bobKeyPair.publicKey, + MOCK_SESSION_SIGS['http://127.0.0.1:7470'], + message + ); + + // Tamper with the ciphertext + const tamperedPayload = { + ...encryptedPayload, + V1: { + ...encryptedPayload.V1, + ciphertext_and_tag: + encryptedPayload.V1.ciphertext_and_tag.substring(0, 10) + + 'ff' + + encryptedPayload.V1.ciphertext_and_tag.substring(12), + }, + }; + + // Bob tries to decrypt the tampered message + const decryptedTamperedMessage = await walletDecrypt( + bobKeyPair.secretKey, + tamperedPayload + ); + + // Verify decryption failed due to tampering + expect(decryptedTamperedMessage).toBeNull(); + }); +}); + +describe('walletEncrypt and walletDecrypt', () => { + it('should encrypt and decrypt a message successfully', async () => { + // Generate key pairs using the box functionality + const aliceKeyPair = nacl.box.keyPair(); + const bobKeyPair = nacl.box.keyPair(); + + console.log('aliceKeyPair', aliceKeyPair); + console.log('bobKeyPair', bobKeyPair); + + // Message to encrypt + const message = new TextEncoder().encode('This is a secret message'); + + // Alice encrypts a message for Bob + const encryptedPayload = await walletEncrypt( + aliceKeyPair.secretKey, + bobKeyPair.publicKey, + MOCK_SESSION_SIGS['http://127.0.0.1:7470'], + message + ); + + console.log('encryptedPayload', encryptedPayload); + + // Verify payload structure + expect(encryptedPayload).toHaveProperty('V1'); + expect(encryptedPayload.V1).toHaveProperty('verification_key'); + expect(encryptedPayload.V1).toHaveProperty('ciphertext_and_tag'); + expect(encryptedPayload.V1).toHaveProperty('session_signature'); + expect(encryptedPayload.V1).toHaveProperty('random'); + expect(encryptedPayload.V1).toHaveProperty('created_at'); + + // Bob decrypts the message from Alice + const decryptedMessage = await walletDecrypt( + bobKeyPair.secretKey, + encryptedPayload + ); + + // Verify decryption was successful + expect(decryptedMessage).not.toBeNull(); + expect(new TextDecoder().decode(decryptedMessage as Uint8Array)).toBe( + 'This is a secret message' + ); + }); + + it('should return null when decryption fails', async () => { + // Generate key pairs + const aliceKeyPair = nacl.box.keyPair(); + const bobKeyPair = nacl.box.keyPair(); + const eveKeyPair = nacl.box.keyPair(); // Eve is an eavesdropper + + // Message to encrypt + const message = new TextEncoder().encode('This is a secret message'); + + // Alice encrypts a message for Bob + const encryptedPayload = await walletEncrypt( + aliceKeyPair.secretKey, + bobKeyPair.publicKey, + MOCK_SESSION_SIGS['http://127.0.0.1:7470'], + message + ); + + // Eve tries to decrypt the message with her key (should fail) + const decryptedByEve = await walletDecrypt( + eveKeyPair.secretKey, + encryptedPayload + ); + + // Verify decryption failed + expect(decryptedByEve).toBeNull(); + }); + + it('should handle tampering with the encrypted payload', async () => { + // Generate key pairs + const aliceKeyPair = nacl.box.keyPair(); + const bobKeyPair = nacl.box.keyPair(); + + // Message to encrypt + const message = new TextEncoder().encode('This is a secret message'); + + // Alice encrypts a message for Bob + const encryptedPayload = await walletEncrypt( + aliceKeyPair.secretKey, + bobKeyPair.publicKey, + MOCK_SESSION_SIGS['http://127.0.0.1:7470'], + message + ); + + // Tamper with the ciphertext + const tamperedPayload = { + ...encryptedPayload, + V1: { + ...encryptedPayload.V1, + ciphertext_and_tag: + encryptedPayload.V1.ciphertext_and_tag.substring(0, 10) + + 'ff' + + encryptedPayload.V1.ciphertext_and_tag.substring(12), + }, + }; + + // Bob tries to decrypt the tampered message + const decryptedTamperedMessage = await walletDecrypt( + bobKeyPair.secretKey, + tamperedPayload + ); + + // Verify decryption failed due to tampering + expect(decryptedTamperedMessage).toBeNull(); }); }); diff --git a/packages/crypto/src/lib/crypto.ts b/packages/crypto/src/lib/crypto.ts index 54c46e5f9..1659f1af8 100644 --- a/packages/crypto/src/lib/crypto.ts +++ b/packages/crypto/src/lib/crypto.ts @@ -1,17 +1,36 @@ -import { joinSignature, splitSignature } from 'ethers/lib/utils'; +import { sha256 } from '@noble/hashes/sha256'; +import { sha384 } from '@noble/hashes/sha512'; +import { x25519 } from '@noble/curves/ed25519'; +import { sha512 } from '@noble/hashes/sha512'; +import { nacl } from '@lit-protocol/nacl'; import { + CurveTypeNotFoundError, + EcdsaSigType, InvalidParamType, - LIT_CURVE, - LIT_CURVE_VALUES, NetworkError, NoValidShares, UnknownError, - UnknownSignatureError, } from '@lit-protocol/constants'; -import { log } from '@lit-protocol/misc'; -import { nacl } from '@lit-protocol/nacl'; -import { NodeAttestation, SessionKeyPair, SigShare } from '@lit-protocol/types'; +import { + applyTransformations, + cleanArrayValues, + cleanStringValues, + convertKeysToCamelCase, + convertNumberArraysToUint8Arrays, + hexifyStringValues, + log, +} from '@lit-protocol/misc'; +import { + AuthSig, + CleanLitNodeSignature, + CombinedLitNodeSignature, + LitActionSignedData, + NodeAttestation, + PKPSignEndpointResponse, + SessionKeyPair, + WalletEncryptedPayload, +} from '@lit-protocol/types'; import { uint8arrayFromString, uint8arrayToString, @@ -22,12 +41,10 @@ import { blsEncrypt, BlsSignatureShareJsonString, blsVerify, - ecdsaCombine, ecdsaDeriveKey, - EcdsaVariant, - ecdsaVerify, sevSnpGetVcekUrl, sevSnpVerify, + unifiedCombineAndVerify, } from '@lit-protocol/wasm'; /** ---------- Exports ---------- */ @@ -82,21 +99,6 @@ export const encrypt = async ( ); }; -/** - * Decrypt ciphertext using BLS signature shares. - * - * @param ciphertextBase64 base64-encoded string of the ciphertext to decrypt - * @param shares hex-encoded array of the BLS signature shares - * @returns Uint8Array of the decrypted data - */ -export const decryptWithSignatureShares = async ( - ciphertextBase64: string, - shares: BlsSignatureShare[] -): Promise => { - const sigShares = toJSONShares(shares); - return doDecrypt(ciphertextBase64, sigShares); -}; - /** * Verify and decrypt ciphertext using BLS signature shares. * @@ -169,128 +171,108 @@ export const verifySignature = async ( await blsVerify(publicKey, message, signature); }; -const ecdsaSigntureTypeMap: Partial> = { - [LIT_CURVE.EcdsaCaitSith]: 'K256', - [LIT_CURVE.EcdsaK256]: 'K256', - [LIT_CURVE.EcdsaCAITSITHP256]: 'P256', - [LIT_CURVE.EcdsaK256Sha256]: 'K256', +const parseCombinedSignature = ( + combinedSignature: CombinedLitNodeSignature +): CleanLitNodeSignature => { + const transformations = [ + convertKeysToCamelCase, + cleanArrayValues, + convertNumberArraysToUint8Arrays, + cleanStringValues, + hexifyStringValues, + ]; + return applyTransformations( + combinedSignature as unknown as Record, + transformations + ) as unknown as CleanLitNodeSignature; }; /** + * Combine and verify Lit execute js node shares * - * Combine ECDSA Shares + * @param { LitActionSignedData[] } litActionResponseData * - * @param { Array } sigShares + * @returns { CleanLitNodeSignature } signature * - * @returns { any } + * @throws { NoValidShares } * */ -export const combineEcdsaShares = async ( - sigShares: SigShare[] -): Promise<{ - r: string; - s: string; - recid: number; - signature: `0x${string}`; -}> => { - const validShares = sigShares.filter((share) => share.signatureShare); - - const anyValidShare = validShares[0]; - - if (!anyValidShare) { +export const combineExecuteJsNodeShares = async ( + litActionResponseData: LitActionSignedData[] +): Promise => { + try { + const combinerShares = litActionResponseData.map((s) => s.signatureShare); + const unifiedSignature = await unifiedCombineAndVerify(combinerShares); + const combinedSignature = JSON.parse( + unifiedSignature + ) as CombinedLitNodeSignature; + + return parseCombinedSignature(combinedSignature); + } catch (e) { throw new NoValidShares( { info: { - shares: sigShares, + shares: litActionResponseData, }, + cause: e, }, - 'No valid shares to combine' + 'No valid lit action shares to combine' ); } +}; - const variant = - ecdsaSigntureTypeMap[anyValidShare.sigType as LIT_CURVE_VALUES]; - const presignature = Buffer.from(anyValidShare.bigR!, 'hex'); - const signatureShares = validShares.map((share) => - Buffer.from(share.signatureShare, 'hex') - ); - - const [r, s, recId] = await ecdsaCombine( - variant!, - presignature, - signatureShares - ); - - const publicKey = Buffer.from(anyValidShare.publicKey, 'hex'); - const messageHash = Buffer.from(anyValidShare.dataSigned!, 'hex'); - - await ecdsaVerify(variant!, messageHash, publicKey, [r, s, recId]); +/** + * Combine and verify Lit pkp sign node shares + * + * @param { PKPSignEndpointResponse[] } nodesSignResponseData + * + * @returns { CleanLitNodeSignature } signature + * + * @throws { NoValidShares } + * + */ +export const combinePKPSignNodeShares = async ( + nodesSignResponseData: PKPSignEndpointResponse[] +): Promise => { + try { + const validShares = nodesSignResponseData.filter((share) => share.success); - const signature = splitSignature( - Buffer.concat([r, s, Buffer.from([recId + 27])]) - ); + const combinerShares = validShares.map((s) => + JSON.stringify(s.signatureShare) + ); + const unifiedSignature = await unifiedCombineAndVerify(combinerShares); + const combinedSignature = JSON.parse( + unifiedSignature + ) as CombinedLitNodeSignature; - // validate r before returning - if (!signature.r) { - throw new UnknownSignatureError( + return parseCombinedSignature(combinedSignature); + } catch (e) { + throw new NoValidShares( { info: { - signature, + shares: nodesSignResponseData, }, + cause: e, }, - 'signature could not be combined' + 'No valid pkp sign shares to combine' ); } - - const _r = signature.r.slice('0x'.length); - const _s = signature.s.slice('0x'.length); - const _recid = signature.recoveryParam; - - const encodedSig = joinSignature({ - r: '0x' + _r, - s: '0x' + _s, - recoveryParam: _recid, - }) as `0x${string}`; - - return { - r: _r, - s: _s, - recid: _recid, - signature: encodedSig, - }; }; export const computeHDPubKey = async ( pubkeys: string[], - keyId: string, - sigType: LIT_CURVE_VALUES + keyId: string ): Promise => { - const variant = ecdsaSigntureTypeMap[sigType]; - - switch (sigType) { - case LIT_CURVE.EcdsaCaitSith: - case LIT_CURVE.EcdsaK256: - // a bit of pre processing to remove characters which will cause our wasm module to reject the values. - pubkeys = pubkeys.map((value: string) => { - return value.replace('0x', ''); - }); - keyId = keyId.replace('0x', ''); - const preComputedPubkey = await ecdsaDeriveKey( - variant!, - Buffer.from(keyId, 'hex'), - pubkeys.map((hex: string) => Buffer.from(hex, 'hex')) - ); - return Buffer.from(preComputedPubkey).toString('hex'); - default: - throw new InvalidParamType( - { - info: { - sigType, - }, - }, - `Non supported signature type` - ); - } + // a bit of preprocessing to remove characters which will cause our wasm module to reject the values. + pubkeys = pubkeys.map((value: string) => { + return value.replace('0x', ''); + }); + keyId = keyId.replace('0x', ''); + const preComputedPubkey = await ecdsaDeriveKey( + Buffer.from(keyId, 'hex'), + pubkeys.map((hex: string) => Buffer.from(hex, 'hex')) + ); + return Buffer.from(preComputedPubkey).toString('hex'); }; /** @@ -371,6 +353,121 @@ async function getAmdCert(url: string): Promise { } } +export const walletEncrypt = async ( + myWalletSecretKey: Uint8Array, + theirWalletPublicKey: Uint8Array, + sessionSig: AuthSig, + message: Uint8Array +): Promise => { + const uint8SessionSig = Buffer.from(JSON.stringify(sessionSig)); + + const random = new Uint8Array(16); + crypto.getRandomValues(random); + const dateNow = Date.now(); + const createdAt = Math.floor(dateNow / 1000); + const timestamp = Buffer.alloc(8); + timestamp.writeBigUInt64BE(BigInt(createdAt), 0); + + const myWalletPublicKey = new Uint8Array(32); + nacl.lowlevel.crypto_scalarmult_base(myWalletPublicKey, myWalletSecretKey); + + // Construct AAD (Additional Authenticated Data) - data that is authenticated but not encrypted + const sessionSignature = uint8SessionSig; // Replace with actual session signature + const theirPublicKey = Buffer.from(theirWalletPublicKey); // Replace with their public key + const myPublicKey = Buffer.from(myWalletPublicKey); // Replace with your wallet public key + + const aad = Buffer.concat([ + sessionSignature, + random, + timestamp, + theirPublicKey, + myPublicKey, + ]); + + const hash = new Uint8Array(64); + nacl.lowlevel.crypto_hash(hash, aad); + + const nonce = hash.slice(0, 24); + const ciphertext = nacl.box( + message, + nonce, + theirPublicKey, + myWalletSecretKey + ); + return { + V1: { + verification_key: uint8ArrayToHex(myWalletPublicKey), + ciphertext_and_tag: uint8ArrayToHex(ciphertext), + session_signature: uint8ArrayToHex(sessionSignature), + random: uint8ArrayToHex(random), + created_at: new Date(dateNow).toISOString(), + }, + }; +}; + +export const walletDecrypt = async ( + myWalletSecretKey: Uint8Array, + payload: WalletEncryptedPayload +): Promise => { + const dateSent = new Date(payload.V1.created_at); + const createdAt = Math.floor(dateSent.getTime() / 1000); + const timestamp = Buffer.alloc(8); + timestamp.writeBigUInt64BE(BigInt(createdAt), 0); + + const myWalletPublicKey = new Uint8Array(32); + nacl.lowlevel.crypto_scalarmult_base(myWalletPublicKey, myWalletSecretKey); + + // Construct AAD + const random = Buffer.from(hexToUint8Array(payload.V1.random)); + const sessionSignature = Buffer.from( + hexToUint8Array(payload.V1.session_signature) + ); // Replace with actual session signature + const theirPublicKey = hexToUint8Array(payload.V1.verification_key); + const theirPublicKeyBuffer = Buffer.from(theirPublicKey); // Replace with their public key + const myPublicKey = Buffer.from(myWalletPublicKey); // Replace with your wallet public key + + const aad = Buffer.concat([ + sessionSignature, + random, + timestamp, + theirPublicKeyBuffer, + myPublicKey, + ]); + + const hash = new Uint8Array(64); + nacl.lowlevel.crypto_hash(hash, aad); + + const nonce = hash.slice(0, 24); + + // Convert hex ciphertext back to Uint8Array + const ciphertext = hexToUint8Array(payload.V1.ciphertext_and_tag); + + const message = nacl.box.open( + ciphertext, + nonce, + theirPublicKey, + myWalletSecretKey + ); + return message; +}; + +function uint8ArrayToHex(array: Uint8Array) { + return Array.from(array) + .map((byte) => byte.toString(16).padStart(2, '0')) + .join(''); +} + +function hexToUint8Array(hexString: string): Uint8Array { + if (hexString.length % 2 !== 0) { + throw new Error('Hex string must have an even length'); + } + const bytes = new Uint8Array(hexString.length / 2); + for (let i = 0; i < bytes.length; i++) { + bytes[i] = parseInt(hexString.slice(i * 2, i * 2 + 2), 16); + } + return bytes; +} + /** * * Check the attestation against AMD certs @@ -379,13 +476,13 @@ async function getAmdCert(url: string): Promise { * @param { string } challengeHex The challenge we sent * @param { string } url The URL we talked to * - * @returns { Promise } A promise that throws if the attestation is invalid + * @returns { Promise } A promise that throws if the attestation is invalid */ export const checkSevSnpAttestation = async ( attestation: NodeAttestation, challengeHex: string, url: string -) => { +): Promise => { const noonce = Buffer.from(attestation.noonce, 'base64'); const challenge = Buffer.from(challengeHex, 'hex'); const data = Object.fromEntries( @@ -501,7 +598,32 @@ export const checkSevSnpAttestation = async ( return sevSnpVerify(report, data, signatures, challenge, vcekCert); }; -declare global { - // eslint-disable-next-line no-var, @typescript-eslint/no-explicit-any - var LitNodeClient: any; +// Map the right hash function per signing scheme +export const ecdsaHashFunctions: Record< + EcdsaSigType, + (arg0: Uint8Array) => Uint8Array +> = { + EcdsaK256Sha256: sha256, + EcdsaP256Sha256: sha256, + EcdsaP384Sha384: sha384, +} as const; + +export function hashLitMessage( + signingScheme: EcdsaSigType, + message: Uint8Array +): Uint8Array { + const hashFn = ecdsaHashFunctions[signingScheme]; + + if (!hashFn) { + throw new CurveTypeNotFoundError( + { + info: { + signingScheme, + }, + }, + `No known hash function for specified signing scheme ${signingScheme}` + ); + } + + return hashFn(message); } diff --git a/packages/event-listener/src/lib/types.ts b/packages/event-listener/src/lib/types.ts index 1ec982857..a5d055f02 100644 --- a/packages/event-listener/src/lib/types.ts +++ b/packages/event-listener/src/lib/types.ts @@ -2,10 +2,11 @@ import { ethers } from 'ethers'; import { LitContracts } from '@lit-protocol/contracts-sdk'; import { LitNodeClient } from '@lit-protocol/lit-node-client'; +import { Hex } from '@lit-protocol/types'; import { BaseTransitionParams } from './transitions'; -export type Address = `0x${string}`; +export type Address = Hex; export type voidAsyncFunction = () => Promise; export type onError = (error: unknown) => void; diff --git a/packages/lit-node-client-nodejs/src/lib/helpers/get-claims-list.test.ts b/packages/lit-node-client-nodejs/src/lib/helpers/get-claims-list.test.ts index 499034e6a..8151e8885 100644 --- a/packages/lit-node-client-nodejs/src/lib/helpers/get-claims-list.test.ts +++ b/packages/lit-node-client-nodejs/src/lib/helpers/get-claims-list.test.ts @@ -1,87 +1,142 @@ -import { NodeShare } from '@lit-protocol/types'; +import { ExecuteJsValueResponse } from '@lit-protocol/types'; + import { getClaimsList } from './get-claims-list'; describe('getClaimsList', () => { it('should return an empty array if responseData is empty', () => { - const responseData: NodeShare[] = []; + const responseData: ExecuteJsValueResponse[] = []; const result = getClaimsList(responseData); expect(result).toEqual([]); }); it('should parse the real data correctly', () => { - const responseData = [ + const responseData: ExecuteJsValueResponse[] = [ { success: true, - signedData: {}, + signedData: { + signEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"7d87c5ea75f7378bb701e404c50639161af3eff66293e9f375b5f17eb50476f4","result":"success","share_id":"\\"816E54B265612C92CD87FC32892C024C9E2DD5FA67AA20FED4816A49A560FC03\\"","peer_id":"b104a1b35585fec58cbb632d17fbe60b10297d7853261ea9054d5e372952936a","signature_share":"\\"B4C05B5662A5FA4316E818DEDDA26FE49BC255FD18CEAAE737918B76860E0603\\"","big_r":"\\"025C4C4E1C1450CF2BB52A6D9B47E351FCB6AD61721F6EB1D1B9D07885FEEF17BF\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'signEcdsa', + }, + ethPersonalSignMessageEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62","result":"success","share_id":"\\"816E54B265612C92CD87FC32892C024C9E2DD5FA67AA20FED4816A49A560FC03\\"","peer_id":"b104a1b35585fec58cbb632d17fbe60b10297d7853261ea9054d5e372952936a","signature_share":"\\"F8C932F4D1CD0590EE602E3BD38DF8E654D60CD92D118FCCBF5E0D1F6F5DC9C6\\"","big_r":"\\"031B4BFE05DA0235CEEED499049FA415FE9C1D03BE6DD149EB5740972655628F42\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'ethPersonalSignMessageEcdsa', + }, + }, decryptedData: {}, claimData: { - foo: { + claimKey: { signature: - '36ffccaec30f52730dcc6fa411383dd23233be55da5bce7e9e0161dc88cfd0541a7f18f9dbb37677f660bc812ff6d29c1c3f92cb7245c0e20f97787ff3324ad31c', + '09c8030422c182ff583005e1f28649f37543d6c7486a0b750a95e31b9b374ebe7d5636c962c493ef5caa2bbb0795f86668e4ffd1de3bea8606aefbc0d8e88bd31b', derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + 'cd27cca0fcb92698b199970c75a560437d32f39849acea8f0cb0adea745dd5ea', }, }, - response: '', - logs: '', + response: + '{"signAndCombineEcdsa":"{\\"r\\":\\"944a5b7290ea8d887156644d0a080561c87e1487459ffe8b31330e4e18a92588\\",\\"s\\":\\"66bf328efe592b25a3e134f66a8e7e6364c9f6787082fe34dcefaed3facee7dc\\",\\"v\\":1}","signEcdsa":"success","ethPersonalSignMessageEcdsa":"success","foo":"bar"}', + logs: '===== starting\n===== responding\n', }, { success: true, - signedData: {}, + signedData: { + signEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"7d87c5ea75f7378bb701e404c50639161af3eff66293e9f375b5f17eb50476f4","result":"success","share_id":"\\"989DD924B8821903330AC0801F99EB27E3E5235EE299B2A06A611780EC0C7AE1\\"","peer_id":"5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a","signature_share":"\\"193837FC7153CBDFEC9A358FAD6EB2919DD10C66CA1B81F65245DBE6C352EA0E\\"","big_r":"\\"025C4C4E1C1450CF2BB52A6D9B47E351FCB6AD61721F6EB1D1B9D07885FEEF17BF\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'signEcdsa', + }, + ethPersonalSignMessageEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62","result":"success","share_id":"\\"989DD924B8821903330AC0801F99EB27E3E5235EE299B2A06A611780EC0C7AE1\\"","peer_id":"5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a","signature_share":"\\"6D088286CEED1699BB8B4B49E2343EE7A34B29030B8A10E03C50C19C25C9D164\\"","big_r":"\\"031B4BFE05DA0235CEEED499049FA415FE9C1D03BE6DD149EB5740972655628F42\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'ethPersonalSignMessageEcdsa', + }, + }, decryptedData: {}, claimData: { - foo: { + claimKey: { signature: - 'ac4e1b37a969af3a03331dabb9418d137cec9e8b366ff7cafcf6688ff07b15d070c42c8c16b0f945ea03653a0d286f2f59fdef529db38e7c33b65aae4b287ce71b', + 'c8395ddc0b3fd9de46e4342656eddf13750f34d8b062d32a31dfa15430d6c4d901245be4aaa17a316124057740f3166c249fae93a1fa826b7f60aea4783fc9ca1b', derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + 'cd27cca0fcb92698b199970c75a560437d32f39849acea8f0cb0adea745dd5ea', }, }, - response: '', - logs: '', + response: + '{"signAndCombineEcdsa":"{\\"r\\":\\"944a5b7290ea8d887156644d0a080561c87e1487459ffe8b31330e4e18a92588\\",\\"s\\":\\"66bf328efe592b25a3e134f66a8e7e6364c9f6787082fe34dcefaed3facee7dc\\",\\"v\\":1}","signEcdsa":"success","ethPersonalSignMessageEcdsa":"success","foo":"bar"}', + logs: '===== starting\n===== responding\n', }, { success: true, - signedData: {}, + signedData: { + signEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"7d87c5ea75f7378bb701e404c50639161af3eff66293e9f375b5f17eb50476f4","result":"success","share_id":"\\"B8EF7C21FAF54664646B64869D1B12861F6D905F1F1F095E60F7238806252AE3\\"","peer_id":"6555c8c26671f5e21611adba0a3c31b28128443f2d76c2818db169efcff38151","signature_share":"\\"AC369404EAAC0E47153FFCF2D79769C1EEFC4709053E985B69ADA4076693BE56\\"","big_r":"\\"025C4C4E1C1450CF2BB52A6D9B47E351FCB6AD61721F6EB1D1B9D07885FEEF17BF\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'signEcdsa', + }, + ethPersonalSignMessageEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62","result":"success","share_id":"\\"B8EF7C21FAF54664646B64869D1B12861F6D905F1F1F095E60F7238806252AE3\\"","peer_id":"6555c8c26671f5e21611adba0a3c31b28128443f2d76c2818db169efcff38151","signature_share":"\\"0ECCE198FEBD337740DB617ADEB558CF8E00EC18A6783D88AAD8F73A31A0F4B1\\"","big_r":"\\"031B4BFE05DA0235CEEED499049FA415FE9C1D03BE6DD149EB5740972655628F42\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'ethPersonalSignMessageEcdsa', + }, + }, decryptedData: {}, claimData: { - foo: { + claimKey: { signature: - 'fd5bad778bd70ece43616c0531b13a70bf9b0a853d38aa7b92560a0070e59e7b619979bc79b1ac2dc6886b44a2bdb402e5804a00d010f415d8cf5c6673540d131c', + '96972387c7322f29172936baebd516461fff7e0dee7f378bb258d88bd0109e582425e0211db85fce47da34729bb70b8aed07b79d6be6b1553907e21069e4186e1b', derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + 'cd27cca0fcb92698b199970c75a560437d32f39849acea8f0cb0adea745dd5ea', }, }, - response: '', - logs: '', + response: + '{"signAndCombineEcdsa":"{\\"r\\":\\"944a5b7290ea8d887156644d0a080561c87e1487459ffe8b31330e4e18a92588\\",\\"s\\":\\"66bf328efe592b25a3e134f66a8e7e6364c9f6787082fe34dcefaed3facee7dc\\",\\"v\\":1}","signEcdsa":"success","ethPersonalSignMessageEcdsa":"success","foo":"bar"}', + logs: '===== starting\n===== responding\n', }, - ] as NodeShare[]; + ]; const result = getClaimsList(responseData); expect(result).toEqual([ { - foo: { + claimKey: { signature: - '36ffccaec30f52730dcc6fa411383dd23233be55da5bce7e9e0161dc88cfd0541a7f18f9dbb37677f660bc812ff6d29c1c3f92cb7245c0e20f97787ff3324ad31c', + '09c8030422c182ff583005e1f28649f37543d6c7486a0b750a95e31b9b374ebe7d5636c962c493ef5caa2bbb0795f86668e4ffd1de3bea8606aefbc0d8e88bd31b', derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + 'cd27cca0fcb92698b199970c75a560437d32f39849acea8f0cb0adea745dd5ea', }, }, { - foo: { + claimKey: { signature: - 'ac4e1b37a969af3a03331dabb9418d137cec9e8b366ff7cafcf6688ff07b15d070c42c8c16b0f945ea03653a0d286f2f59fdef529db38e7c33b65aae4b287ce71b', + 'c8395ddc0b3fd9de46e4342656eddf13750f34d8b062d32a31dfa15430d6c4d901245be4aaa17a316124057740f3166c249fae93a1fa826b7f60aea4783fc9ca1b', derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + 'cd27cca0fcb92698b199970c75a560437d32f39849acea8f0cb0adea745dd5ea', }, }, { - foo: { + claimKey: { signature: - 'fd5bad778bd70ece43616c0531b13a70bf9b0a853d38aa7b92560a0070e59e7b619979bc79b1ac2dc6886b44a2bdb402e5804a00d010f415d8cf5c6673540d131c', + '96972387c7322f29172936baebd516461fff7e0dee7f378bb258d88bd0109e582425e0211db85fce47da34729bb70b8aed07b79d6be6b1553907e21069e4186e1b', derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + 'cd27cca0fcb92698b199970c75a560437d32f39849acea8f0cb0adea745dd5ea', }, }, ]); diff --git a/packages/lit-node-client-nodejs/src/lib/helpers/get-claims-list.ts b/packages/lit-node-client-nodejs/src/lib/helpers/get-claims-list.ts index 64d08fcc8..317efec36 100644 --- a/packages/lit-node-client-nodejs/src/lib/helpers/get-claims-list.ts +++ b/packages/lit-node-client-nodejs/src/lib/helpers/get-claims-list.ts @@ -1,29 +1,16 @@ -import { ClaimsList, NodeShare } from '@lit-protocol/types'; +import { applyTransformations, cleanStringValues } from '@lit-protocol/misc'; +import { ClaimsList, ExecuteJsValueResponse } from '@lit-protocol/types'; /** * Retrieves a list of claims from the provided response data. * @param responseData The response data containing the claims. * @returns An array of claims. */ -export const getClaimsList = (responseData: NodeShare[]): ClaimsList => { +export const getClaimsList = ( + responseData: ExecuteJsValueResponse[] +): ClaimsList => { const claimsList = responseData - .map((r) => { - const { claimData } = r; - if (claimData) { - for (const key of Object.keys(claimData)) { - for (const subkey of Object.keys(claimData[key])) { - if (typeof claimData[key][subkey] == 'string') { - claimData[key][subkey] = claimData[key][subkey].replaceAll( - '"', - '' - ); - } - } - } - return claimData; - } - return null; - }) + .map((rd) => applyTransformations(rd.claimData, [cleanStringValues])) .filter((item) => item !== null); return claimsList as unknown as ClaimsList; diff --git a/packages/lit-node-client-nodejs/src/lib/helpers/get-claims.test.ts b/packages/lit-node-client-nodejs/src/lib/helpers/get-claims.test.ts index 8db4df559..507b929b1 100644 --- a/packages/lit-node-client-nodejs/src/lib/helpers/get-claims.test.ts +++ b/packages/lit-node-client-nodejs/src/lib/helpers/get-claims.test.ts @@ -1,55 +1,60 @@ +import { ClaimsList, Signature } from '@lit-protocol/types'; + import { getClaims } from './get-claims'; describe('getClaims', () => { it('should return the correct claims object', () => { - const claims = [ + const claims: ClaimsList = [ { - foo: { + claimKey: { signature: - '36ffccaec30f52730dcc6fa411383dd23233be55da5bce7e9e0161dc88cfd0541a7f18f9dbb37677f660bc812ff6d29c1c3f92cb7245c0e20f97787ff3324ad31c', + '09c8030422c182ff583005e1f28649f37543d6c7486a0b750a95e31b9b374ebe7d5636c962c493ef5caa2bbb0795f86668e4ffd1de3bea8606aefbc0d8e88bd31b', derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + 'cd27cca0fcb92698b199970c75a560437d32f39849acea8f0cb0adea745dd5ea', }, }, { - foo: { + claimKey: { signature: - 'ac4e1b37a969af3a03331dabb9418d137cec9e8b366ff7cafcf6688ff07b15d070c42c8c16b0f945ea03653a0d286f2f59fdef529db38e7c33b65aae4b287ce71b', + 'c8395ddc0b3fd9de46e4342656eddf13750f34d8b062d32a31dfa15430d6c4d901245be4aaa17a316124057740f3166c249fae93a1fa826b7f60aea4783fc9ca1b', derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + 'cd27cca0fcb92698b199970c75a560437d32f39849acea8f0cb0adea745dd5ea', }, }, { - foo: { + claimKey: { signature: - 'fd5bad778bd70ece43616c0531b13a70bf9b0a853d38aa7b92560a0070e59e7b619979bc79b1ac2dc6886b44a2bdb402e5804a00d010f415d8cf5c6673540d131c', + '96972387c7322f29172936baebd516461fff7e0dee7f378bb258d88bd0109e582425e0211db85fce47da34729bb70b8aed07b79d6be6b1553907e21069e4186e1b', derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + 'cd27cca0fcb92698b199970c75a560437d32f39849acea8f0cb0adea745dd5ea', }, }, ]; - const expectedClaims = { - foo: { + const expectedClaims: Record< + string, + { signatures: Signature[]; derivedKeyId: string } + > = { + claimKey: { signatures: [ { - r: '0x36ffccaec30f52730dcc6fa411383dd23233be55da5bce7e9e0161dc88cfd054', - s: '0x1a7f18f9dbb37677f660bc812ff6d29c1c3f92cb7245c0e20f97787ff3324ad3', - v: 28, + r: '0x09c8030422c182ff583005e1f28649f37543d6c7486a0b750a95e31b9b374ebe', + s: '0x7d5636c962c493ef5caa2bbb0795f86668e4ffd1de3bea8606aefbc0d8e88bd3', + v: 27, }, { - r: '0xac4e1b37a969af3a03331dabb9418d137cec9e8b366ff7cafcf6688ff07b15d0', - s: '0x70c42c8c16b0f945ea03653a0d286f2f59fdef529db38e7c33b65aae4b287ce7', + r: '0xc8395ddc0b3fd9de46e4342656eddf13750f34d8b062d32a31dfa15430d6c4d9', + s: '0x01245be4aaa17a316124057740f3166c249fae93a1fa826b7f60aea4783fc9ca', v: 27, }, { - r: '0xfd5bad778bd70ece43616c0531b13a70bf9b0a853d38aa7b92560a0070e59e7b', - s: '0x619979bc79b1ac2dc6886b44a2bdb402e5804a00d010f415d8cf5c6673540d13', - v: 28, + r: '0x96972387c7322f29172936baebd516461fff7e0dee7f378bb258d88bd0109e58', + s: '0x2425e0211db85fce47da34729bb70b8aed07b79d6be6b1553907e21069e4186e', + v: 27, }, ], derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + 'cd27cca0fcb92698b199970c75a560437d32f39849acea8f0cb0adea745dd5ea', }, }; @@ -59,70 +64,96 @@ describe('getClaims', () => { }); it('should return the correct claims object with different claims', () => { - ``; - const claims = [ + const claims: ClaimsList = [ { - foo: { + bar: { signature: - '36ffccaec30f52730dcc6fa411383dd23233be55da5bce7e9e0161dc88cfd0541a7f18f9dbb37677f660bc812ff6d29c1c3f92cb7245c0e20f97787ff3324ad31c', + '2e0ac9ccbb7bb72e626f6732c6b56cbc3382c2f091285c4a5ac68c22f7de08840e80e92924caa18e95fcd3bdfd6741f1b7e470b2d9300a0516100af5642f3df51b', derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + '2bb0c99d5f677bb564a0a2f9228e2d69ec823df5c7b2b019507e3ac1c02c5ea2', }, - bar: { + foo: { signature: - 'ac4e1b37a969af3a03331dabb9418d137cec9e8b366ff7cafcf6688ff07b15d070c42c8c16b0f945ea03653a0d286f2f59fdef529db38e7c33b65aae4b287ce71b', + '952714bafa338ab816152c26b4da10b4cbf28a8594ee061f4ba0f614269ffb233b5055723fe4f11e86e9cd3a09ad9d5ac827d5c19b29834e5ed871748f90462f1c', derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + 'fb8559bcf5e49b3d581c7108f7885922531bb82fafd319c988c00ca12399ac88', }, }, { + bar: { + signature: + 'be5f34b1e3ae79b93ae6c5f62e443b2fa29a3b8f9582bae6156badf7bb374b02113b0b053f2abf84d17b4beeefd4945b13636bd338df827487e2fad3bfd346021b', + derivedKeyId: + '2bb0c99d5f677bb564a0a2f9228e2d69ec823df5c7b2b019507e3ac1c02c5ea2', + }, foo: { signature: - 'fd5bad778bd70ece43616c0531b13a70bf9b0a853d38aa7b92560a0070e59e7b619979bc79b1ac2dc6886b44a2bdb402e5804a00d010f415d8cf5c6673540d131c', + 'b96f2ce3854705c6e24e76714303fb9b122ebe40dcc4007d30dd2bac8e3e8acb2172d1eb191c27566a3123c6038c0cb3b0c87326ec5942ef9c724e3a01f77f7d1b', derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + 'fb8559bcf5e49b3d581c7108f7885922531bb82fafd319c988c00ca12399ac88', }, + }, + { bar: { signature: - 'ac4e1b37a969af3a03331dabb9418d137cec9e8b366ff7cafcf6688ff07b15d070c42c8c16b0f945ea03653a0d286f2f59fdef529db38e7c33b65aae4b287ce71b', + '6de2630fadf9ba2f43c53be82a52a7a8d7209d68dfbbc5d4edd6670423093c937488e14cc792f7db7d99f143bff6e358aa2c5a4294f7d14a39ee6afe521e86891c', derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + '2bb0c99d5f677bb564a0a2f9228e2d69ec823df5c7b2b019507e3ac1c02c5ea2', + }, + foo: { + signature: + '55526b8437e45e31c462b31bccc57f30aeb393bd2500621bdc019aee99ad0f864830e21068a02b2b537697103eaa7d313344703b55b2f82bc927f54bd7be9d751c', + derivedKeyId: + 'fb8559bcf5e49b3d581c7108f7885922531bb82fafd319c988c00ca12399ac88', }, }, ]; - const expectedClaims = { - foo: { + const expectedClaims: Record< + string, + { signatures: Signature[]; derivedKeyId: string } + > = { + bar: { signatures: [ { - r: '0x36ffccaec30f52730dcc6fa411383dd23233be55da5bce7e9e0161dc88cfd054', - s: '0x1a7f18f9dbb37677f660bc812ff6d29c1c3f92cb7245c0e20f97787ff3324ad3', - v: 28, + r: '0x2e0ac9ccbb7bb72e626f6732c6b56cbc3382c2f091285c4a5ac68c22f7de0884', + s: '0x0e80e92924caa18e95fcd3bdfd6741f1b7e470b2d9300a0516100af5642f3df5', + v: 27, + }, + { + r: '0xbe5f34b1e3ae79b93ae6c5f62e443b2fa29a3b8f9582bae6156badf7bb374b02', + s: '0x113b0b053f2abf84d17b4beeefd4945b13636bd338df827487e2fad3bfd34602', + v: 27, }, { - r: '0xfd5bad778bd70ece43616c0531b13a70bf9b0a853d38aa7b92560a0070e59e7b', - s: '0x619979bc79b1ac2dc6886b44a2bdb402e5804a00d010f415d8cf5c6673540d13', + r: '0x6de2630fadf9ba2f43c53be82a52a7a8d7209d68dfbbc5d4edd6670423093c93', + s: '0x7488e14cc792f7db7d99f143bff6e358aa2c5a4294f7d14a39ee6afe521e8689', v: 28, }, ], derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + '2bb0c99d5f677bb564a0a2f9228e2d69ec823df5c7b2b019507e3ac1c02c5ea2', }, - bar: { + foo: { signatures: [ { - r: '0xac4e1b37a969af3a03331dabb9418d137cec9e8b366ff7cafcf6688ff07b15d0', - s: '0x70c42c8c16b0f945ea03653a0d286f2f59fdef529db38e7c33b65aae4b287ce7', - v: 27, + r: '0x952714bafa338ab816152c26b4da10b4cbf28a8594ee061f4ba0f614269ffb23', + s: '0x3b5055723fe4f11e86e9cd3a09ad9d5ac827d5c19b29834e5ed871748f90462f', + v: 28, }, { - r: '0xac4e1b37a969af3a03331dabb9418d137cec9e8b366ff7cafcf6688ff07b15d0', - s: '0x70c42c8c16b0f945ea03653a0d286f2f59fdef529db38e7c33b65aae4b287ce7', + r: '0xb96f2ce3854705c6e24e76714303fb9b122ebe40dcc4007d30dd2bac8e3e8acb', + s: '0x2172d1eb191c27566a3123c6038c0cb3b0c87326ec5942ef9c724e3a01f77f7d', v: 27, }, + { + r: '0x55526b8437e45e31c462b31bccc57f30aeb393bd2500621bdc019aee99ad0f86', + s: '0x4830e21068a02b2b537697103eaa7d313344703b55b2f82bc927f54bd7be9d75', + v: 28, + }, ], derivedKeyId: - '22c14f271322473459c456056ffc6e1c0dc1efcb2d15e5be538ad081b224b3d0', + 'fb8559bcf5e49b3d581c7108f7885922531bb82fafd319c988c00ca12399ac88', }, }; diff --git a/packages/lit-node-client-nodejs/src/lib/helpers/get-claims.ts b/packages/lit-node-client-nodejs/src/lib/helpers/get-claims.ts index 8bc984efe..5d2355e61 100644 --- a/packages/lit-node-client-nodejs/src/lib/helpers/get-claims.ts +++ b/packages/lit-node-client-nodejs/src/lib/helpers/get-claims.ts @@ -1,6 +1,7 @@ -import { Signature } from '@lit-protocol/types'; import { ethers } from 'ethers'; +import { ClaimsList, Signature } from '@lit-protocol/types'; + /** * Retrieves the claims from an array of objects and organizes them into a record. * Each claim is associated with its corresponding signatures and derived key ID. @@ -9,7 +10,7 @@ import { ethers } from 'ethers'; * @returns A record where each key represents a claim, and the value is an object containing the claim's signatures and derived key ID. */ export const getClaims = ( - claims: any[] + claims: ClaimsList ): Record => { const keys: string[] = Object.keys(claims[0]); const signatures: Record = {}; @@ -17,11 +18,12 @@ export const getClaims = ( string, { signatures: Signature[]; derivedKeyId: string } > = {}; - for (let i = 0; i < keys.length; i++) { + + for (const key of keys) { const claimSet: { signature: string; derivedKeyId: string }[] = claims.map( - (c) => c[keys[i]] + (c) => c[key] ); - signatures[keys[i]] = []; + signatures[key] = []; claimSet.map((c) => { const sig = ethers.utils.splitSignature(`0x${c.signature}`); const convertedSig = { @@ -29,13 +31,14 @@ export const getClaims = ( s: sig.s, v: sig.v, }; - signatures[keys[i]].push(convertedSig); + signatures[key].push(convertedSig); }); - claimRes[keys[i]] = { - signatures: signatures[keys[i]], + claimRes[key] = { + signatures: signatures[key], derivedKeyId: claimSet[0].derivedKeyId, }; } + return claimRes; }; 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 a0177e322..3671f6d65 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,78 +1,438 @@ +import { NoValidShares } from '@lit-protocol/constants'; import { - EcdsaSignedMessageShareParsed, - SigResponse, + ExecuteJsValueResponse, + LitNodeSignature, + PKPSignEndpointResponse, } from '@lit-protocol/types'; -import { getSignatures } from './get-signatures'; +import { + combineExecuteJSSignatures, + combinePKPSignSignatures, +} from './get-signatures'; + +const requestId = 'REQUEST_ID'; +const threshold = 3; -describe('getSignatures', () => { - it('should return signatures object', async () => { - const networkPubKeySet = 'testing'; - const minNodeCount = 1; - const signedData = [ +describe('combineExecuteJSSignatures', () => { + it('should throw when threshold is not met', async () => { + const shares: ExecuteJsValueResponse[] = [ { - sigType: 'K256', - dataSigned: 'fail', - signatureShare: '', - bigR: '', - publicKey: '', - sigName: 'sig', + success: true, + signedData: { + ethPersonalSignMessageEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62","result":"success","share_id":"\\"989DD924B8821903330AC0801F99EB27E3E5235EE299B2A06A611780EC0C7AE1\\"","peer_id":"5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a","signature_share":"\\"3CD32659E13395EA472B5803F441D6336610AE837A64C618FB88A7D52E40F3E6\\"","big_r":"\\"0364400D1F87B954C788AD6FD25C835E3FA46EBFE23B96763204B8FC8D3265A2AA\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'ethPersonalSignMessageEcdsa', + }, + signEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"7d87c5ea75f7378bb701e404c50639161af3eff66293e9f375b5f17eb50476f4","result":"success","share_id":"\\"989DD924B8821903330AC0801F99EB27E3E5235EE299B2A06A611780EC0C7AE1\\"","peer_id":"5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a","signature_share":"\\"F462406990EEFC365A04970594A8D28FBAB0C241089A35EDDDE18BA22B5F3E10\\"","big_r":"\\"035AE42C8D841E45EE956C749038027B12B378BDD5BD9ADEB47DA31C99B8A0076A\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'signEcdsa', + }, + }, + decryptedData: {}, + claimData: { + claimKey: { + signature: + 'fd50548ca7e4264e834f384d2d53e2194f1c88f56b3d2d72c3d3f6b248f368f47b583a543ee84d1996311e42f2d6ef7d96b2b49be21dbe1af1eb998f3d10a60e1c', + derivedKeyId: + '8ef1510b2225c2d881221ba953e9ac586c432f75f8052ef56998bd17b4a1f1cb', + }, + }, + response: + '{"signAndCombineEcdsa":"{\\"r\\":\\"b0e2b90cde3357919f4582c4556e437daf94ab48357e7d52c76a4120c8702988\\",\\"s\\":\\"45c37dd91e8eaa16e2d2d2b7d08316f8d8c6186349680b10cf4d699439e75652\\",\\"v\\":0}","signEcdsa":"success","ethPersonalSignMessageEcdsa":"success","foo":"bar"}', + logs: '===== starting\n===== responding\n', }, { - sigType: 'K256', - dataSigned: - '7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4', - signatureShare: - '1301BE04CF3A269709C2BDC29F7EFD1FBB3FC037C00AD2B5BDA8726B74CB5AF4', - bigR: '0290947D801A421D4A347FFFD386703C97BEF8E8AC83C3AB256ACE09255C37C521', - publicKey: - '04423427A87DEE9420BAC5C38355FE4A8C30EA796D87950C0143B49422D88C8FC70C381CB45300D8AD8A95139FFEEA5F265EFE00B65481BBB97B311C6833B69AE3', - sigName: 'sig', + success: true, + signedData: { + signEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"7d87c5ea75f7378bb701e404c50639161af3eff66293e9f375b5f17eb50476f4","result":"success","share_id":"\\"816E54B265612C92CD87FC32892C024C9E2DD5FA67AA20FED4816A49A560FC03\\"","peer_id":"b104a1b35585fec58cbb632d17fbe60b10297d7853261ea9054d5e372952936a","signature_share":"\\"6CA25D6E1FC17CCEABDB39AE60559AC8DD89E088D84589C60F1864EE2327EC25\\"","big_r":"\\"035AE42C8D841E45EE956C749038027B12B378BDD5BD9ADEB47DA31C99B8A0076A\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'signEcdsa', + }, + ethPersonalSignMessageEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62","result":"success","share_id":"\\"816E54B265612C92CD87FC32892C024C9E2DD5FA67AA20FED4816A49A560FC03\\"","peer_id":"b104a1b35585fec58cbb632d17fbe60b10297d7853261ea9054d5e372952936a","signature_share":"\\"F858C082562DBE3D8F3722B046E0648390A7666E795F602194F1D39E73D9A803\\"","big_r":"\\"0364400D1F87B954C788AD6FD25C835E3FA46EBFE23B96763204B8FC8D3265A2AA\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'ethPersonalSignMessageEcdsa', + }, + }, + decryptedData: {}, + claimData: { + claimKey: { + signature: + '74d1a74a947b86efac665113323210149e91ef66669951ead4abf056c44553b3596abdcfa5a7a9ee69f12d316bc90033243fd253bb78d59e8ed40122de0a3b2d1b', + derivedKeyId: + '8ef1510b2225c2d881221ba953e9ac586c432f75f8052ef56998bd17b4a1f1cb', + }, + }, + response: + '{"signAndCombineEcdsa":"{\\"r\\":\\"b0e2b90cde3357919f4582c4556e437daf94ab48357e7d52c76a4120c8702988\\",\\"s\\":\\"45c37dd91e8eaa16e2d2d2b7d08316f8d8c6186349680b10cf4d699439e75652\\",\\"v\\":0}","signEcdsa":"success","ethPersonalSignMessageEcdsa":"success","foo":"bar"}', + logs: '===== starting\n===== responding\n', + }, + { + success: false, + signedData: {}, + decryptedData: {}, + claimData: {}, + response: '', + logs: '', + }, + ]; + + await expect( + combineExecuteJSSignatures({ + nodesLitActionSignedData: shares, + threshold, + requestId, + }) + ).rejects.toThrow(NoValidShares); + }); + + it('should return the combined signature', async () => { + const shares: ExecuteJsValueResponse[] = [ + { + success: true, + signedData: { + ethPersonalSignMessageEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62","result":"success","share_id":"\\"989DD924B8821903330AC0801F99EB27E3E5235EE299B2A06A611780EC0C7AE1\\"","peer_id":"5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a","signature_share":"\\"3CD32659E13395EA472B5803F441D6336610AE837A64C618FB88A7D52E40F3E6\\"","big_r":"\\"0364400D1F87B954C788AD6FD25C835E3FA46EBFE23B96763204B8FC8D3265A2AA\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'ethPersonalSignMessageEcdsa', + }, + signEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"7d87c5ea75f7378bb701e404c50639161af3eff66293e9f375b5f17eb50476f4","result":"success","share_id":"\\"989DD924B8821903330AC0801F99EB27E3E5235EE299B2A06A611780EC0C7AE1\\"","peer_id":"5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a","signature_share":"\\"F462406990EEFC365A04970594A8D28FBAB0C241089A35EDDDE18BA22B5F3E10\\"","big_r":"\\"035AE42C8D841E45EE956C749038027B12B378BDD5BD9ADEB47DA31C99B8A0076A\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'signEcdsa', + }, + }, + decryptedData: {}, + claimData: { + claimKey: { + signature: + 'fd50548ca7e4264e834f384d2d53e2194f1c88f56b3d2d72c3d3f6b248f368f47b583a543ee84d1996311e42f2d6ef7d96b2b49be21dbe1af1eb998f3d10a60e1c', + derivedKeyId: + '8ef1510b2225c2d881221ba953e9ac586c432f75f8052ef56998bd17b4a1f1cb', + }, + }, + response: + '{"signAndCombineEcdsa":"{\\"r\\":\\"b0e2b90cde3357919f4582c4556e437daf94ab48357e7d52c76a4120c8702988\\",\\"s\\":\\"45c37dd91e8eaa16e2d2d2b7d08316f8d8c6186349680b10cf4d699439e75652\\",\\"v\\":0}","signEcdsa":"success","ethPersonalSignMessageEcdsa":"success","foo":"bar"}', + logs: '===== starting\n===== responding\n', }, { - sigType: 'K256', - dataSigned: - '7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4', - signatureShare: - 'F649B4CEAEE015877161AC8F062270200F65EC166C9BD7BF6F877EBB345F2F8F', - bigR: '0290947D801A421D4A347FFFD386703C97BEF8E8AC83C3AB256ACE09255C37C521', + success: true, + signedData: { + signEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"7d87c5ea75f7378bb701e404c50639161af3eff66293e9f375b5f17eb50476f4","result":"success","share_id":"\\"816E54B265612C92CD87FC32892C024C9E2DD5FA67AA20FED4816A49A560FC03\\"","peer_id":"b104a1b35585fec58cbb632d17fbe60b10297d7853261ea9054d5e372952936a","signature_share":"\\"6CA25D6E1FC17CCEABDB39AE60559AC8DD89E088D84589C60F1864EE2327EC25\\"","big_r":"\\"035AE42C8D841E45EE956C749038027B12B378BDD5BD9ADEB47DA31C99B8A0076A\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'signEcdsa', + }, + ethPersonalSignMessageEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62","result":"success","share_id":"\\"816E54B265612C92CD87FC32892C024C9E2DD5FA67AA20FED4816A49A560FC03\\"","peer_id":"b104a1b35585fec58cbb632d17fbe60b10297d7853261ea9054d5e372952936a","signature_share":"\\"F858C082562DBE3D8F3722B046E0648390A7666E795F602194F1D39E73D9A803\\"","big_r":"\\"0364400D1F87B954C788AD6FD25C835E3FA46EBFE23B96763204B8FC8D3265A2AA\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'ethPersonalSignMessageEcdsa', + }, + }, + decryptedData: {}, + claimData: { + claimKey: { + signature: + '74d1a74a947b86efac665113323210149e91ef66669951ead4abf056c44553b3596abdcfa5a7a9ee69f12d316bc90033243fd253bb78d59e8ed40122de0a3b2d1b', + derivedKeyId: + '8ef1510b2225c2d881221ba953e9ac586c432f75f8052ef56998bd17b4a1f1cb', + }, + }, + response: + '{"signAndCombineEcdsa":"{\\"r\\":\\"b0e2b90cde3357919f4582c4556e437daf94ab48357e7d52c76a4120c8702988\\",\\"s\\":\\"45c37dd91e8eaa16e2d2d2b7d08316f8d8c6186349680b10cf4d699439e75652\\",\\"v\\":0}","signEcdsa":"success","ethPersonalSignMessageEcdsa":"success","foo":"bar"}', + logs: '===== starting\n===== responding\n', + }, + { + success: true, + signedData: { + ethPersonalSignMessageEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62","result":"success","share_id":"\\"B8EF7C21FAF54664646B64869D1B12861F6D905F1F1F095E60F7238806252AE3\\"","peer_id":"6555c8c26671f5e21611adba0a3c31b28128443f2d76c2818db169efcff38151","signature_share":"\\"B7AF0DBCE67A07EFDEB38D44491673EF23CC9FF9CBC81399A2C3A3948ED2B1BC\\"","big_r":"\\"0364400D1F87B954C788AD6FD25C835E3FA46EBFE23B96763204B8FC8D3265A2AA\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'ethPersonalSignMessageEcdsa', + }, + signEcdsa: { + sigType: 'EcdsaK256Sha256', + signatureShare: + '{"EcdsaSignedMessageShare":{"digest":"7d87c5ea75f7378bb701e404c50639161af3eff66293e9f375b5f17eb50476f4","result":"success","share_id":"\\"B8EF7C21FAF54664646B64869D1B12861F6D905F1F1F095E60F7238806252AE3\\"","peer_id":"6555c8c26671f5e21611adba0a3c31b28128443f2d76c2818db169efcff38151","signature_share":"\\"2F4376CF77A51A7EFBC604FAFFC56F31A7370B359C559EF56C51EA236C8C3F70\\"","big_r":"\\"035AE42C8D841E45EE956C749038027B12B378BDD5BD9ADEB47DA31C99B8A0076A\\"","compressed_public_key":"\\"0250a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da95081\\"","public_key":"\\"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e\\"","sig_type":"EcdsaK256Sha256"}}', + publicKey: + '"0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e"', + sigName: 'signEcdsa', + }, + }, + decryptedData: {}, + claimData: { + claimKey: { + signature: + '266e0aae5b98f78a82c7e11072b0a9cb1284dd1b326c34aa144d44f6c31f48f520fcc05ac9e243aa2596cb8c65ce1e015bc3d690243b9f462cccd8f5600bcc341c', + derivedKeyId: + '8ef1510b2225c2d881221ba953e9ac586c432f75f8052ef56998bd17b4a1f1cb', + }, + }, + response: + '{"signAndCombineEcdsa":"{\\"r\\":\\"b0e2b90cde3357919f4582c4556e437daf94ab48357e7d52c76a4120c8702988\\",\\"s\\":\\"45c37dd91e8eaa16e2d2d2b7d08316f8d8c6186349680b10cf4d699439e75652\\",\\"v\\":0}","signEcdsa":"success","ethPersonalSignMessageEcdsa":"success","foo":"bar"}', + logs: '===== starting\n===== responding\n', + }, + ]; + const combinedSignatures: Record = { + ethPersonalSignMessageEcdsa: { + signature: + '0x64400D1F87B954C788AD6FD25C835E3FA46EBFE23B96763204B8FC8D3265A2AA13250B66E224A3E84AE9F8077BC751575AD904E19F0506A34C669E116F7F34DD', + verifyingKey: + '0x3056301006072A8648CE3D020106052B8104000A0342000450A6083580384CBCDDD0D809165BA8EE53B5E768724C7B6080AE55790DA9508125810F89BAB7D56077E37BE1681463A6262108E50FBA439D94808F3CB1CC704E', + signedData: + '0x04f09ca42a7f2d7268e756c590c5e29de79dcb28b55b01f8c1211a31438a3e62', + recoveryId: 0, + publicKey: + '0x0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e', + sigType: 'EcdsaK256Sha256', + }, + signEcdsa: { + signature: + '0x5AE42C8D841E45EE956C749038027B12B378BDD5BD9ADEB47DA31C99B8A0076A6FB7EB58D7AA6C7BFE5A2A510B3C237335EC0BCDE15BE1CE2658E265E55918DD', + verifyingKey: + '0x3056301006072A8648CE3D020106052B8104000A0342000450A6083580384CBCDDD0D809165BA8EE53B5E768724C7B6080AE55790DA9508125810F89BAB7D56077E37BE1681463A6262108E50FBA439D94808F3CB1CC704E', + signedData: + '0x7d87c5ea75f7378bb701e404c50639161af3eff66293e9f375b5f17eb50476f4', + recoveryId: 0, publicKey: - '04423427A87DEE9420BAC5C38355FE4A8C30EA796D87950C0143B49422D88C8FC70C381CB45300D8AD8A95139FFEEA5F265EFE00B65481BBB97B311C6833B69AE3', - sigName: 'sig', + '0x0450a6083580384cbcddd0d809165ba8ee53b5e768724c7b6080ae55790da9508125810f89bab7d56077e37be1681463a6262108e50fba439d94808f3cb1cc704e', + sigType: 'EcdsaK256Sha256', + }, + }; + + await expect( + combineExecuteJSSignatures({ + nodesLitActionSignedData: shares, + threshold, + requestId, + }) + ).resolves.toEqual(combinedSignatures); + }); +}); + +describe('combinePKPSignSignatures', () => { + it('should throw when threshold is not met', async () => { + const shares: PKPSignEndpointResponse[] = [ + { + success: true, + signedData: [ + 116, 248, 31, 225, 103, 217, 155, 76, 180, 29, 109, 12, 205, 168, 34, + 120, 202, 238, 159, 62, 47, 37, 213, 229, 163, 147, 111, 243, 220, + 236, 96, 208, + ], + signatureShare: { + EcdsaSignedMessageShare: { + digest: + '74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + result: 'success', + share_id: + '"B8EF7C21FAF54664646B64869D1B12861F6D905F1F1F095E60F7238806252AE3"', + peer_id: + '6555c8c26671f5e21611adba0a3c31b28128443f2d76c2818db169efcff38151', + signature_share: + '"3D53B4698F798F98F65D2CB90BFD170278E7014E3DA87E217BCAAEBBB8D5DDF9"', + big_r: + '"039EF446668DDE56A9F803F07B371756EEAB3AAF797A8E4EBD7A273CB86874C143"', + compressed_public_key: + '"0366069291e81515949b7659dd00bef10403cbce747404ced4ad72e97690fd1e29"', + public_key: + '"0466069291e81515949b7659dd00bef10403cbce747404ced4ad72e97690fd1e29bf1741d91941fa1f2407c59445a3c9af78b7c7e94c0782cfd11353c1ee163993"', + sig_type: 'EcdsaK256Sha256', + }, + }, + }, + { + success: true, + signedData: [ + 116, 248, 31, 225, 103, 217, 155, 76, 180, 29, 109, 12, 205, 168, 34, + 120, 202, 238, 159, 62, 47, 37, 213, 229, 163, 147, 111, 243, 220, + 236, 96, 208, + ], + signatureShare: { + EcdsaSignedMessageShare: { + digest: + '74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + result: 'success', + share_id: + '"816E54B265612C92CD87FC32892C024C9E2DD5FA67AA20FED4816A49A560FC03"', + peer_id: + 'b104a1b35585fec58cbb632d17fbe60b10297d7853261ea9054d5e372952936a', + signature_share: + '"A0DD6D5EEC9EADACF86E14C8B252344EAEB505B722F5A7C18ECB4F3FFA44A3AE"', + big_r: + '"039EF446668DDE56A9F803F07B371756EEAB3AAF797A8E4EBD7A273CB86874C143"', + compressed_public_key: + '"0366069291e81515949b7659dd00bef10403cbce747404ced4ad72e97690fd1e29"', + public_key: + '"0466069291e81515949b7659dd00bef10403cbce747404ced4ad72e97690fd1e29bf1741d91941fa1f2407c59445a3c9af78b7c7e94c0782cfd11353c1ee163993"', + sig_type: 'EcdsaK256Sha256', + }, + }, + }, + { + success: false, + signedData: [], + signatureShare: { + EcdsaSignedMessageShare: { + digest: '', + result: 'fail', + share_id: '', + peer_id: '', + signature_share: '', + big_r: '', + compressed_public_key: '', + public_key: '', + sig_type: 'EcdsaK256Sha256', + }, + }, }, ]; - const requestId = ''; - const signatures = await getSignatures({ - networkPubKeySet, - threshold: minNodeCount, - signedMessageShares: - signedData as unknown as EcdsaSignedMessageShareParsed[], - requestId, - }); + await expect( + combinePKPSignSignatures({ + nodesPkpSignResponseData: shares, + threshold, + requestId, + }) + ).rejects.toThrow(NoValidShares); + }); - console.log('signatures:', signatures); + it('should return the combined signature', async () => { + const shares: PKPSignEndpointResponse[] = [ + { + success: true, + signedData: [ + 116, 248, 31, 225, 103, 217, 155, 76, 180, 29, 109, 12, 205, 168, 34, + 120, 202, 238, 159, 62, 47, 37, 213, 229, 163, 147, 111, 243, 220, + 236, 96, 208, + ], + signatureShare: { + EcdsaSignedMessageShare: { + digest: + '74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + result: 'success', + share_id: + '"989DD924B8821903330AC0801F99EB27E3E5235EE299B2A06A611780EC0C7AE1"', + peer_id: + '5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a', + signature_share: + '"159E02F1F0B5B875FE65A8A534109E0A35DAE0F900FC3CDE2400491289A975FD"', + big_r: + '"03265381E6E2879FF4AA1C0B9991123A3B9E6759A66C3432C60D6F7D8DB7ABAC24"', + compressed_public_key: + '"032e0cfe8e42758449da56ef09669ec4a31c3d8b55f8b28d390c830264d1426dc7"', + public_key: + '"042e0cfe8e42758449da56ef09669ec4a31c3d8b55f8b28d390c830264d1426dc73bbe2171d83f52483a66922746bfda297bd1dc69c4d5ed5163a523b0b10d0db3"', + sig_type: 'EcdsaK256Sha256', + }, + }, + }, + { + success: true, + signedData: [ + 116, 248, 31, 225, 103, 217, 155, 76, 180, 29, 109, 12, 205, 168, 34, + 120, 202, 238, 159, 62, 47, 37, 213, 229, 163, 147, 111, 243, 220, + 236, 96, 208, + ], + signatureShare: { + EcdsaSignedMessageShare: { + digest: + '74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + result: 'success', + share_id: + '"816E54B265612C92CD87FC32892C024C9E2DD5FA67AA20FED4816A49A560FC03"', + peer_id: + 'b104a1b35585fec58cbb632d17fbe60b10297d7853261ea9054d5e372952936a', + signature_share: + '"F3B8CAAAC28A09D5F9125D6FD0A122E41451CDDCE8E9001C07D8D91F5DBE0F23"', + big_r: + '"03265381E6E2879FF4AA1C0B9991123A3B9E6759A66C3432C60D6F7D8DB7ABAC24"', + compressed_public_key: + '"032e0cfe8e42758449da56ef09669ec4a31c3d8b55f8b28d390c830264d1426dc7"', + public_key: + '"042e0cfe8e42758449da56ef09669ec4a31c3d8b55f8b28d390c830264d1426dc73bbe2171d83f52483a66922746bfda297bd1dc69c4d5ed5163a523b0b10d0db3"', + sig_type: 'EcdsaK256Sha256', + }, + }, + }, + { + success: true, + signedData: [ + 116, 248, 31, 225, 103, 217, 155, 76, 180, 29, 109, 12, 205, 168, 34, + 120, 202, 238, 159, 62, 47, 37, 213, 229, 163, 147, 111, 243, 220, + 236, 96, 208, + ], + signatureShare: { + EcdsaSignedMessageShare: { + digest: + '74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + result: 'success', + share_id: + '"B8EF7C21FAF54664646B64869D1B12861F6D905F1F1F095E60F7238806252AE3"', + peer_id: + '6555c8c26671f5e21611adba0a3c31b28128443f2d76c2818db169efcff38151', + signature_share: + '"42D2DA7F7E05CBCF927EA934797919D2857AA9D7EB35F3BECAE0C59BC62B81E0"', + big_r: + '"03265381E6E2879FF4AA1C0B9991123A3B9E6759A66C3432C60D6F7D8DB7ABAC24"', + compressed_public_key: + '"032e0cfe8e42758449da56ef09669ec4a31c3d8b55f8b28d390c830264d1426dc7"', + public_key: + '"042e0cfe8e42758449da56ef09669ec4a31c3d8b55f8b28d390c830264d1426dc73bbe2171d83f52483a66922746bfda297bd1dc69c4d5ed5163a523b0b10d0db3"', + sig_type: 'EcdsaK256Sha256', + }, + }, + }, + ]; + const combinedSignature: LitNodeSignature = { + signature: + '0x265381E6E2879FF4AA1C0B9991123A3B9E6759A66C3432C60D6F7D8DB7ABAC244C29A81C31458E1B89F6AF497E2ADAC214F87BC725D2907D36E78940DD5CC5BF', + verifyingKey: + '0x3056301006072A8648CE3D020106052B8104000A034200042E0CFE8E42758449DA56EF09669EC4A31C3D8B55F8B28D390C830264D1426DC73BBE2171D83F52483A66922746BFDA297BD1DC69C4D5ED5163A523B0B10D0DB3', + signedData: + '0x74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + recoveryId: 1, + publicKey: + '0x042e0cfe8e42758449da56ef09669ec4a31c3d8b55f8b28d390c830264d1426dc73bbe2171d83f52483a66922746bfda297bd1dc69c4d5ed5163a523b0b10d0db3', + sigType: 'EcdsaK256Sha256', + }; - expect(signatures).toHaveProperty('publicKey'); - expect(signatures).toHaveProperty('r'); - expect(signatures).toHaveProperty('recid'); - expect(signatures).toHaveProperty('s'); - expect(signatures).toHaveProperty('signature'); - expect(signatures.dataSigned).toBe( - '7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4' - ); - expect(signatures.publicKey).toBe( - '04423427A87DEE9420BAC5C38355FE4A8C30EA796D87950C0143B49422D88C8FC70C381CB45300D8AD8A95139FFEEA5F265EFE00B65481BBB97B311C6833B69AE3' - ); - expect(signatures.r).toBe( - '90947d801a421d4a347fffd386703c97bef8e8ac83c3ab256ace09255c37c521' - ); - expect(signatures.recid).toBe(0); - expect(signatures.s).toBe( - '094b72d37e1a3c1e7b246a51a5a16d410ff6cf677d5e0a396d5d9299d8f44942' - ); - expect(signatures.signature).toBe( - '0x90947d801a421d4a347fffd386703c97bef8e8ac83c3ab256ace09255c37c521094b72d37e1a3c1e7b246a51a5a16d410ff6cf677d5e0a396d5d9299d8f449421b' - ); + await expect( + combinePKPSignSignatures({ + nodesPkpSignResponseData: shares, + threshold, + requestId, + }) + ).resolves.toEqual(combinedSignature); }); }); 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 77941e63b..7ec027c8a 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,125 +1,175 @@ -import { joinSignature } from 'ethers/lib/utils'; - +import { NoValidShares } from '@lit-protocol/constants'; import { - CURVE_GROUP_BY_CURVE_TYPE, - LIT_CURVE_VALUES, - NoValidShares, - ParamNullError, - UnknownSignatureError, - UnknownSignatureType, - CurveTypeNotFoundError, -} from '@lit-protocol/constants'; -import { combineEcdsaShares } from '@lit-protocol/crypto'; + combineExecuteJsNodeShares, + combinePKPSignNodeShares, +} from '@lit-protocol/crypto'; import { + applyTransformations, + cleanStringValues, + hexifyStringValues, logErrorWithRequestId, - logWithRequestId, mostCommonString, } from '@lit-protocol/misc'; import { - EcdsaSignedMessageShareParsed, - SigResponse, - SigShare, + ExecuteJsValueResponse, + LitNodeSignature, + LitActionSignedData, + PKPSignEndpointResponse, } from '@lit-protocol/types'; -/** - * Retrieves and combines signature shares from multiple nodes to generate the final signatures. - * - * @template T - The type of the final signatures. For `executeJs` endpoint, it returns as `signature`, and for `pkpSign` endpoint, it returns as `sig`. - * @param {any} params.networkPubKeySet - The public key set of the network. - * @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. - * - * @example - * - * executeJs: getSignatures<{ signature: SigResponse }> - * pkpSign: getSignatures<{ sig: SigResponse }> - */ -export const getSignatures = async (params: { - networkPubKeySet: any; - threshold: number; - signedMessageShares: EcdsaSignedMessageShareParsed[]; - requestId: string; -}): Promise => { - let { networkPubKeySet, threshold, signedMessageShares, requestId } = params; +import { parsePkpSignResponse } from './parse-pkp-sign-response'; - if (networkPubKeySet === null) { - throw new ParamNullError( - { - info: { - requestId, - }, - }, - 'networkPubKeySet cannot be null' - ); - } +function assertThresholdShares( + requestId: string, + threshold: number, + shares: { success: boolean }[] +) { + const successfulShares = shares.filter((response) => response.success); - if (signedMessageShares.length < threshold) { + if (successfulShares.length < threshold) { logErrorWithRequestId( requestId, - `not enough nodes to get the signatures. Expected ${threshold}, got ${signedMessageShares.length}` + `Not enough nodes to get the lit action signatures. Expected ${threshold}, got ${successfulShares.length}` ); throw new NoValidShares( { info: { requestId, - shares: signedMessageShares.length, + shares, threshold, }, }, - `The total number of valid signatures shares "${signedMessageShares.length}" does not meet the threshold of "${threshold}"` + `The total number of valid lit action signatures shares "${successfulShares.length}" does not meet the threshold of "${threshold}"` ); } +} - const curveType = signedMessageShares[0].sigType; +/** + * Combines signature shares from multiple nodes running a lit action to generate the final signatures. + * + * @param {number} params.threshold - The threshold number of nodes + * @param {PKPSignEndpointResponse[]} params.nodesLitActionSignedData - The array of signature shares from each node. + * @param {string} params.requestId - The request ID, for logging purposes. + * @returns {LitNodeSignature} - The final signatures or an object containing the final signatures. + */ +export const combineExecuteJSSignatures = async (params: { + nodesLitActionSignedData: ExecuteJsValueResponse[]; + requestId: string; + threshold: number; +}): Promise> => { + const { threshold, requestId, nodesLitActionSignedData } = params; - if (!curveType) { - throw new CurveTypeNotFoundError( - { - info: { - requestId, + assertThresholdShares(requestId, threshold, nodesLitActionSignedData); + + const sigResponses = {} as Record; + + const keyedSignedData = nodesLitActionSignedData.reduce< + Record + >((acc, nodeLitActionSignedData) => { + Object.keys(nodeLitActionSignedData.signedData).forEach((signedDataKey) => { + if (!acc[signedDataKey]) { + acc[signedDataKey] = []; + } + + acc[signedDataKey].push( + nodeLitActionSignedData.signedData[signedDataKey] + ); + }); + + return acc; + }, {} as Record); + + const signatureKeys = Object.keys(keyedSignedData); + await Promise.all( + signatureKeys.map(async (signatureKey) => { + const signatureShares = keyedSignedData[signatureKey]; + const publicKey = mostCommonString( + signatureShares.map((s) => s.publicKey) + ); + const sigType = mostCommonString(signatureShares.map((s) => s.sigType)); + + if (!publicKey || !sigType) { + throw new NoValidShares( + { + info: { + requestId, + publicKey, + shares: nodesLitActionSignedData, + sigType, + }, + }, + 'Could not get public key or sig type from lit action shares' + ); + } + + // -- combine signature shares + const combinedSignature = await combineExecuteJsNodeShares( + signatureShares + ); + + const sigResponse = applyTransformations( + { + ...combinedSignature, + publicKey, + sigType, }, - }, - 'No curve type "%s" found', - curveType - ); - } + [cleanStringValues, hexifyStringValues] + ) as unknown as LitNodeSignature; + + sigResponses[signatureKey] = sigResponse; + }) + ); - const curveGroup = CURVE_GROUP_BY_CURVE_TYPE[curveType as LIT_CURVE_VALUES]; + return sigResponses; +}; - if (curveGroup !== 'ECDSA') { - throw new UnknownSignatureType( +/** + * Combines signature shares from multiple nodes running pkp sign to generate the final signature. + * + * @param {number} params.threshold - The threshold number of nodes + * @param {PKPSignEndpointResponse[]} params.nodesLitActionSignedData - The array of signature shares from each node. + * @param {string} params.requestId - The request ID, for logging purposes. + * @returns {LitNodeSignature} - The final signatures or an object containing the final signatures. + */ +export const combinePKPSignSignatures = async (params: { + nodesPkpSignResponseData: PKPSignEndpointResponse[]; + requestId: string; + threshold: number; +}): Promise => { + const { threshold, requestId, nodesPkpSignResponseData } = params; + + assertThresholdShares(requestId, threshold, nodesPkpSignResponseData); + + const parsedPkpSignResponse = parsePkpSignResponse(nodesPkpSignResponseData); + const publicKey = mostCommonString( + parsedPkpSignResponse.map((s) => s.publicKey) + ); + const sigType = mostCommonString(parsedPkpSignResponse.map((s) => s.sigType)); + + if (!publicKey || !sigType) { + throw new NoValidShares( { info: { requestId, - signatureType: curveType, + publicKey, + shares: nodesPkpSignResponseData, + sigType, }, }, - 'signature type is %s which is invalid', - curveType + 'Could not get public key or sig type from pkp sign shares' ); } - // -- combine - const combinedSignature = await combineEcdsaShares(signedMessageShares); - - const _publicKey = mostCommonString( - signedMessageShares.map((s) => s.publicKey) + // -- combine signature shares + const combinedSignature = await combinePKPSignNodeShares( + nodesPkpSignResponseData ); - const _dataSigned = mostCommonString( - signedMessageShares.map((s) => s.dataSigned) - ); - - if (!_publicKey || !_dataSigned) { - throw new Error('No valid publicKey or dataSigned found'); - } - const sigResponse: SigResponse = { + const sigResponse = { ...combinedSignature, - publicKey: _publicKey, - dataSigned: _dataSigned, + publicKey, + sigType, }; return sigResponse; diff --git a/packages/lit-node-client-nodejs/src/lib/helpers/parse-pkp-sign-response.test.ts b/packages/lit-node-client-nodejs/src/lib/helpers/parse-pkp-sign-response.test.ts index 40ffdf61d..f64fe89ad 100644 --- a/packages/lit-node-client-nodejs/src/lib/helpers/parse-pkp-sign-response.test.ts +++ b/packages/lit-node-client-nodejs/src/lib/helpers/parse-pkp-sign-response.test.ts @@ -1,27 +1,34 @@ import { cleanStringValues, - parsePkpSignResponse, convertKeysToCamelCase, snakeToCamel, -} from './parse-pkp-sign-response'; +} from '@lit-protocol/misc'; +import { PKPSignEndpointResponse } from '@lit-protocol/types'; + +import { parsePkpSignResponse } from './parse-pkp-sign-response'; describe('parsePkpSignResponse', () => { - it('should parse PKP sign response correctly', () => { - const responseData = [ + it('should parse ECDSA PKP sign response correctly', () => { + const responseData: PKPSignEndpointResponse[] = [ { - success: true, + success: false, signedData: [ 125, 135, 197, 234, 117, 247, 55, 139, 183, 1, 228, 4, 197, 6, 57, 22, 26, 243, 239, 246, 98, 147, 233, 243, 117, 181, 241, 126, 181, 4, 118, 244, ], signatureShare: { - digest: 'fail', - result: 'fail', - signature_share: '', - big_r: '', - public_key: '', - sig_type: '', + EcdsaSignedMessageShare: { + digest: 'fail', + result: 'fail', + share_id: '', + peer_id: '', + signature_share: '', + big_r: '', + compressed_public_key: '', + public_key: '', + sig_type: '', + }, }, }, { @@ -32,16 +39,24 @@ describe('parsePkpSignResponse', () => { 244, ], signatureShare: { - digest: - '"7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4"', - result: 'success', - signature_share: - '"3ED0A844FAE40DF6210A6B2EACB9426E52E8339E243E697E33CF14E0CDE2B827"', - big_r: - '"0332188F0918B7DEBB0CC846B00B0AAD9300308260C2DAD25A85FDECA671C36B1B"', - public_key: - '"04156D7E068BF5ED014057B8B6365BF89053D567D38EC24030C699B94065F2D39B4D45F463464F1A138D7149D1C0EF41ACF9B8826050B9E3DCC847DE2127BDB726"', - sig_type: 'K256', + EcdsaSignedMessageShare: { + digest: + '"7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4"', + result: 'success', + share_id: + '"1A0369823607C6EF403D86BA41534DDB1420730C696060EAD7931DE5DB603937"', + peer_id: + '"800ca9780644bb7e1908efa6bd1a0686f1095158c3ba6f1649ef9d2d67bfaf34"', + signature_share: + '"3ED0A844FAE40DF6210A6B2EACB9426E52E8339E243E697E33CF14E0CDE2B827"', + big_r: + '"0332188F0918B7DEBB0CC846B00B0AAD9300308260C2DAD25A85FDECA671C36B1B"', + compressed_public_key: + '"0381ff5b9f673837eacd4dca7e9377084250dccfc13ebf13913e662182027d1482"', + public_key: + '"04156D7E068BF5ED014057B8B6365BF89053D567D38EC24030C699B94065F2D39B4D45F463464F1A138D7149D1C0EF41ACF9B8826050B9E3DCC847DE2127BDB726"', + sig_type: 'EcdsaK256Sha256', + }, }, }, { @@ -52,64 +67,222 @@ describe('parsePkpSignResponse', () => { 244, ], signatureShare: { - digest: - '"7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4"', - result: 'success', - signature_share: - '"B1AA643E88F8937B71CE2D43DCB73E0180AC96D1E39ECC579F0EC9635F37D4CB"', - big_r: - '"0332188F0918B7DEBB0CC846B00B0AAD9300308260C2DAD25A85FDECA671C36B1B"', - public_key: - '"04156D7E068BF5ED014057B8B6365BF89053D567D38EC24030C699B94065F2D39B4D45F463464F1A138D7149D1C0EF41ACF9B8826050B9E3DCC847DE2127BDB726"', - sig_type: 'K256', + EcdsaSignedMessageShare: { + digest: + '"7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4"', + result: 'success', + share_id: + '"1A0369823607C6EF403D86BA41534DDB1420730C696060EAD7931DE5DB603937"', + peer_id: + '"800ca9780644bb7e1908efa6bd1a0686f1095158c3ba6f1649ef9d2d67bfaf34"', + signature_share: + '"B1AA643E88F8937B71CE2D43DCB73E0180AC96D1E39ECC579F0EC9635F37D4CB"', + big_r: + '"0332188F0918B7DEBB0CC846B00B0AAD9300308260C2DAD25A85FDECA671C36B1B"', + compressed_public_key: + '"0381ff5b9f673837eacd4dca7e9377084250dccfc13ebf13913e662182027d1482"', + public_key: + '"04156D7E068BF5ED014057B8B6365BF89053D567D38EC24030C699B94065F2D39B4D45F463464F1A138D7149D1C0EF41ACF9B8826050B9E3DCC847DE2127BDB726"', + sig_type: 'EcdsaK256Sha256', + }, }, }, ]; const expectedOutput = [ { - // signature: { digest: 'fail', + shareId: '', + peerId: '', signatureShare: '', bigR: '', + compressedPublicKey: '', publicKey: '', sigType: '', dataSigned: 'fail', - // }, }, { - // signature: { digest: - '7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4', + '0x7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4', + shareId: + '0x1A0369823607C6EF403D86BA41534DDB1420730C696060EAD7931DE5DB603937', + peerId: + '0x800ca9780644bb7e1908efa6bd1a0686f1095158c3ba6f1649ef9d2d67bfaf34', signatureShare: - '3ED0A844FAE40DF6210A6B2EACB9426E52E8339E243E697E33CF14E0CDE2B827', - bigR: '0332188F0918B7DEBB0CC846B00B0AAD9300308260C2DAD25A85FDECA671C36B1B', + '0x3ED0A844FAE40DF6210A6B2EACB9426E52E8339E243E697E33CF14E0CDE2B827', + bigR: '0x0332188F0918B7DEBB0CC846B00B0AAD9300308260C2DAD25A85FDECA671C36B1B', + compressedPublicKey: + '0x0381ff5b9f673837eacd4dca7e9377084250dccfc13ebf13913e662182027d1482', publicKey: - '04156D7E068BF5ED014057B8B6365BF89053D567D38EC24030C699B94065F2D39B4D45F463464F1A138D7149D1C0EF41ACF9B8826050B9E3DCC847DE2127BDB726', - sigType: 'K256', + '0x04156D7E068BF5ED014057B8B6365BF89053D567D38EC24030C699B94065F2D39B4D45F463464F1A138D7149D1C0EF41ACF9B8826050B9E3DCC847DE2127BDB726', + sigType: 'EcdsaK256Sha256', dataSigned: - '7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4', - - // }, + '0x7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4', }, { - // signature: { digest: - '7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4', + '0x7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4', + shareId: + '0x1A0369823607C6EF403D86BA41534DDB1420730C696060EAD7931DE5DB603937', + peerId: + '0x800ca9780644bb7e1908efa6bd1a0686f1095158c3ba6f1649ef9d2d67bfaf34', signatureShare: - 'B1AA643E88F8937B71CE2D43DCB73E0180AC96D1E39ECC579F0EC9635F37D4CB', - bigR: '0332188F0918B7DEBB0CC846B00B0AAD9300308260C2DAD25A85FDECA671C36B1B', + '0xB1AA643E88F8937B71CE2D43DCB73E0180AC96D1E39ECC579F0EC9635F37D4CB', + bigR: '0x0332188F0918B7DEBB0CC846B00B0AAD9300308260C2DAD25A85FDECA671C36B1B', + compressedPublicKey: + '0x0381ff5b9f673837eacd4dca7e9377084250dccfc13ebf13913e662182027d1482', publicKey: - '04156D7E068BF5ED014057B8B6365BF89053D567D38EC24030C699B94065F2D39B4D45F463464F1A138D7149D1C0EF41ACF9B8826050B9E3DCC847DE2127BDB726', - sigType: 'K256', + '0x04156D7E068BF5ED014057B8B6365BF89053D567D38EC24030C699B94065F2D39B4D45F463464F1A138D7149D1C0EF41ACF9B8826050B9E3DCC847DE2127BDB726', + sigType: 'EcdsaK256Sha256', dataSigned: - '7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4', + '0x7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4', + }, + ]; + + const output = parsePkpSignResponse(responseData); - // }, + expect(output).toEqual(expectedOutput); + }); + + it('should parse FROST PKP sign response correctly', () => { + const responseData: PKPSignEndpointResponse[] = [ + { + success: false, + signedData: [ + 116, 248, 31, 225, 103, 217, 155, 76, 180, 29, 109, 12, 205, 168, 34, + 120, 202, 238, 159, 62, 47, 37, 213, 229, 163, 147, 111, 243, 220, + 236, 96, 208, + ], + signatureShare: { + FrostSignedMessageShare: { + message: 'fail', + result: 'fail', + share_id: '', + peer_id: '', + signature_share: '', + signing_commitments: '', + verifying_share: '', + public_key: '', + sig_type: '', + }, + }, + }, + { + success: true, + signedData: [ + 116, 248, 31, 225, 103, 217, 155, 76, 180, 29, 109, 12, 205, 168, 34, + 120, 202, 238, 159, 62, 47, 37, 213, 229, 163, 147, 111, 243, 220, + 236, 96, 208, + ], + signatureShare: { + FrostSignedMessageShare: { + message: + '74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + result: 'success', + share_id: + '["K256Sha256",[21,126,1,81,188,147,173,138,16,169,115,205,1,224,43,54,73,148,113,48,206,233,7,6,217,224,119,81,249,220,48,41]]', + peer_id: + '77b2a2f061adf273b52307fb1c9960137c854382a9ae8d54d60c149e006a0d7c', + signature_share: + '["K256Sha256","12f86c0d816e98076bdf9cfc39812f7d242f7ac73aefa3638fb7cd1cf63ef7ed"]', + signing_commitments: + '["K256Sha256","00eed6b1b10396989fe69ba9f582ec87e14a01dcad420ad6fd1ec0ce1a63165f30947dc86fef029f61149ebcbd87868105bebd3582577a3c4b6c6a092c621a8a842940b04d8629"]', + verifying_share: + '["K256Sha256","022ba83de8961efba1490d9d3603a51b9d1c0eb17245ce0cbd8295d62ccd7e886c"]', + public_key: + '["K256Sha256","02c5f80a840bc7d00f26dfb8c2a0075aeffc620df39d2188f3e0237ec42dbe920a"]', + sig_type: 'SchnorrK256Sha256', + }, + }, + }, + { + success: true, + signedData: [ + 116, 248, 31, 225, 103, 217, 155, 76, 180, 29, 109, 12, 205, 168, 34, + 120, 202, 238, 159, 62, 47, 37, 213, 229, 163, 147, 111, 243, 220, + 236, 96, 208, + ], + signatureShare: { + FrostSignedMessageShare: { + message: + '74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + result: 'success', + share_id: + '["K256Sha256",[226,183,233,101,218,75,198,127,202,46,107,100,150,15,170,176,229,25,121,33,202,13,26,6,192,49,160,84,130,11,169,174]]', + peer_id: + '8b714aa4b2b8cda1502834522c146d648b1d1eb71910b7064fa3adcb6269a575', + signature_share: + '["K256Sha256","5e6350c02c361f1ed97865ed57bd395190991c673ea55044c3c291155cc14c9b"]', + signing_commitments: + '["K256Sha256","00eed6b1b102f72cf9291f23e939db9758123f155da75cf1150526b847c3c788de7e9b2f6e1f035a3db8a5664117f161e9eca110bbf515395a1c44625202aad1311d71b1b5df7a"]', + verifying_share: + '["K256Sha256","02b680446e13263aea72c7da159393e64228110d4a4a6db36481bc55c92c616c46"]', + public_key: + '["K256Sha256","02c5f80a840bc7d00f26dfb8c2a0075aeffc620df39d2188f3e0237ec42dbe920a"]', + sig_type: 'SchnorrK256Sha256', + }, + }, + }, + ]; + + const expectedOutput = [ + { + message: 'fail', + shareId: '', + peerId: '', + signatureShare: '', + signingCommitments: '', + verifyingShare: '', + publicKey: '', + sigType: '', + dataSigned: 'fail', + }, + { + message: + '0x74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + shareId: new Uint8Array([ + 21, 126, 1, 81, 188, 147, 173, 138, 16, 169, 115, 205, 1, 224, 43, 54, + 73, 148, 113, 48, 206, 233, 7, 6, 217, 224, 119, 81, 249, 220, 48, 41, + ]), + peerId: + '0x77b2a2f061adf273b52307fb1c9960137c854382a9ae8d54d60c149e006a0d7c', + signatureShare: + '0x12f86c0d816e98076bdf9cfc39812f7d242f7ac73aefa3638fb7cd1cf63ef7ed', + signingCommitments: + '0x00eed6b1b10396989fe69ba9f582ec87e14a01dcad420ad6fd1ec0ce1a63165f30947dc86fef029f61149ebcbd87868105bebd3582577a3c4b6c6a092c621a8a842940b04d8629', + verifyingShare: + '0x022ba83de8961efba1490d9d3603a51b9d1c0eb17245ce0cbd8295d62ccd7e886c', + publicKey: + '0x02c5f80a840bc7d00f26dfb8c2a0075aeffc620df39d2188f3e0237ec42dbe920a', + sigType: 'SchnorrK256Sha256', + dataSigned: + '0x74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + }, + { + message: + '0x74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', + shareId: new Uint8Array([ + 226, 183, 233, 101, 218, 75, 198, 127, 202, 46, 107, 100, 150, 15, + 170, 176, 229, 25, 121, 33, 202, 13, 26, 6, 192, 49, 160, 84, 130, 11, + 169, 174, + ]), + peerId: + '0x8b714aa4b2b8cda1502834522c146d648b1d1eb71910b7064fa3adcb6269a575', + signatureShare: + '0x5e6350c02c361f1ed97865ed57bd395190991c673ea55044c3c291155cc14c9b', + signingCommitments: + '0x00eed6b1b102f72cf9291f23e939db9758123f155da75cf1150526b847c3c788de7e9b2f6e1f035a3db8a5664117f161e9eca110bbf515395a1c44625202aad1311d71b1b5df7a', + verifyingShare: + '0x02b680446e13263aea72c7da159393e64228110d4a4a6db36481bc55c92c616c46', + publicKey: + '0x02c5f80a840bc7d00f26dfb8c2a0075aeffc620df39d2188f3e0237ec42dbe920a', + sigType: 'SchnorrK256Sha256', + dataSigned: + '0x74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0', }, ]; - const output = parsePkpSignResponse(responseData as any); + const output = parsePkpSignResponse(responseData); expect(output).toEqual(expectedOutput); }); 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 126184270..313bed473 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 @@ -1,49 +1,16 @@ import { - EcdsaSignedMessageShareParsed, + applyTransformations, + cleanArrayValues, + cleanStringValues, + convertKeysToCamelCase, + convertNumberArraysToUint8Arrays, + hexifyStringValues, +} from '@lit-protocol/misc'; +import { PKPSignEndpointResponse, + PKPSignEndpointSharesParsed, } from '@lit-protocol/types'; -/** - * Converts a snake_case string to camelCase. - * @param s The snake_case string to convert. - * @returns The camelCase version of the input string. - * - * @example - * snakeToCamel('hello_world') // 'helloWorld' - */ -export const snakeToCamel = (s: string): string => - s.replace(/(_\w)/g, (m) => m[1].toUpperCase()); - -/** - * Converts the keys of an object from snake_case to camelCase. - * - * @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 => - Object.keys(obj).reduce( - (acc, key) => ({ - ...acc, - [snakeToCamel(key)]: obj[key], - }), - {} - ); - -/** - * Removes double quotes from string values in an object. - * @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 => - Object.keys(obj).reduce( - (acc, key) => ({ - ...acc, - [key]: - typeof obj[key] === 'string' ? obj[key].replace(/"/g, '') : obj[key], - }), - {} - ); - /** * Parses the PKP sign response data and transforms it into a standardised format because the raw response contains snake cases and double quotes. * @param responseData - The response data containing PKP sign shares. @@ -51,46 +18,55 @@ export const cleanStringValues = (obj: { [key: string]: any }): any => */ export const parsePkpSignResponse = ( responseData: PKPSignEndpointResponse[] -): EcdsaSignedMessageShareParsed[] => { - const ecdsaSignedMessageShares = responseData.map(({ signatureShare }) => { - // Determine if the object is lifted or contains a nested structure - // Example scenarios this logic handles: - // 1. If `signatureShare` is nested (e.g., { EcdsaSignedMessageShare: { ... } }), - // it will extract the nested object (i.e., the value of `EcdsaSignedMessageShare`). - // NOTE: against `f8047310fd4ac97ac01ff07a7cd1213808a3396e` in this case - // 2. If `signatureShare` is directly lifted (e.g., { digest: "...", result: "...", share_id: "..." }), - // it will treat `signatureShare` itself as the resolved object. - // NOTE: against `60318791258d273df8209b912b386680d25d0df3` in this case - // 3. If `signatureShare` is null, not an object, or does not match expected patterns, - // it will throw an error later for invalid structure. - const resolvedShare = - typeof signatureShare === 'object' && - !Array.isArray(signatureShare) && - Object.keys(signatureShare).length === 1 && - typeof signatureShare[ - Object.keys(signatureShare)[0] as keyof typeof signatureShare - ] === 'object' - ? signatureShare[ - Object.keys(signatureShare)[0] as keyof typeof signatureShare - ] - : signatureShare; +): PKPSignEndpointSharesParsed[] => { + const parsedSignatureShares = responseData.map( + ({ signatureShare }) => { + // Determine if the object is lifted or contains a nested structure + // Example scenarios this logic handles: + // 1. If `signatureShare` is nested (e.g., { EcdsaSignedMessageShare: { ... } }), + // it will extract the nested object (i.e., the value of `EcdsaSignedMessageShare`). + // NOTE: against `f8047310fd4ac97ac01ff07a7cd1213808a3396e` in this case + // 2. If `signatureShare` is directly lifted (e.g., { digest: "...", result: "...", share_id: "..." }), + // it will treat `signatureShare` itself as the resolved object. + // NOTE: against `60318791258d273df8209b912b386680d25d0df3` in this case + // 3. If `signatureShare` is null, not an object, or does not match expected patterns, + // it will throw an error later for invalid structure. + const resolvedShare = + typeof signatureShare === 'object' && + !Array.isArray(signatureShare) && + Object.keys(signatureShare).length === 1 && + typeof signatureShare[ + Object.keys(signatureShare)[0] as keyof typeof signatureShare + ] === 'object' + ? signatureShare[ + Object.keys(signatureShare)[0] as keyof typeof signatureShare + ] + : signatureShare; - if (!resolvedShare || typeof resolvedShare !== 'object') { - throw new Error('Invalid signatureShare structure.'); - } + if (!resolvedShare || typeof resolvedShare !== 'object') { + throw new Error('Invalid signatureShare structure.'); + } - const camelCaseShare = convertKeysToCamelCase(resolvedShare); - const parsedShareMessage = cleanStringValues(camelCaseShare); + const transformations = [ + convertKeysToCamelCase, + cleanArrayValues, + convertNumberArraysToUint8Arrays, + cleanStringValues, + hexifyStringValues, + ]; + const parsedShare = applyTransformations(resolvedShare, transformations); - // Rename `digest` to `dataSigned` - if (parsedShareMessage.digest) { - parsedShareMessage.dataSigned = parsedShareMessage.digest; - } + // Frost has `message`, Ecdsa has `digest`. Copy both to `dataSigned` + if (parsedShare['digest'] || parsedShare['message']) { + parsedShare['dataSigned'] = + parsedShare['digest'] || parsedShare['message']; + } - delete parsedShareMessage.result; + delete parsedShare['result']; - return parsedShareMessage; - }); + return parsedShare as unknown as PKPSignEndpointSharesParsed; + } + ); - return ecdsaSignedMessageShares; + return parsedSignatureShares; }; diff --git a/packages/lit-node-client-nodejs/src/lib/helpers/pocess-lit-action-response-strategy.spec.ts b/packages/lit-node-client-nodejs/src/lib/helpers/pocess-lit-action-response-strategy.spec.ts index d1549a995..9870eeac8 100644 --- a/packages/lit-node-client-nodejs/src/lib/helpers/pocess-lit-action-response-strategy.spec.ts +++ b/packages/lit-node-client-nodejs/src/lib/helpers/pocess-lit-action-response-strategy.spec.ts @@ -1,19 +1,15 @@ -import { assert } from 'console'; - -import { NodeShare } from '@lit-protocol/types'; +import { ExecuteJsValueResponse } from '@lit-protocol/types'; import { processLitActionResponseStrategy } from './process-lit-action-response-strategy'; describe('processLitActionResponseStrategy', () => { - const litActionResponses: any[] = [ + const litActionResponses: ExecuteJsValueResponse[] = [ { success: true, signedData: { sig: { - sigType: 'K256', - dataSigned: 'fail', + sigType: 'EcdsaK256Sha256', signatureShare: '', - bigR: '', publicKey: '', sigName: 'sig', }, @@ -27,10 +23,8 @@ describe('processLitActionResponseStrategy', () => { success: true, signedData: { sig: { - sigType: 'K256', - dataSigned: 'fail', + sigType: 'EcdsaK256Sha256', signatureShare: '', - bigR: '', publicKey: '', sigName: 'sig', }, @@ -44,12 +38,9 @@ describe('processLitActionResponseStrategy', () => { success: true, signedData: { sig: { - sigType: 'K256', - dataSigned: - '"7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4"', + sigType: 'EcdsaK256Sha256', signatureShare: '"E90BAE64AFA7C571CE41BEF25FF771CA2F1BC20FC09A7762200552B30ACC0CDC"', - bigR: '"02330092EBF809B05EA0A032A42AD2FE32579D997A739D7BB4CF40EBA83B4355D3"', publicKey: '"047E3AC46588256338E62D8763592B8AA9BD13C31C9326D51CE82254A1839759A4FE7C1281AA1A9F8E810DA52B72046731CB3EE4D213799F7CE26C55A63783DB78"', sigName: 'sig', @@ -64,12 +55,9 @@ describe('processLitActionResponseStrategy', () => { success: true, signedData: { sig: { - sigType: 'K256', - dataSigned: - '"7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4"', + sigType: 'EcdsaK256Sha256', signatureShare: '"31977D4BE7F49C0CD97CC0756CCA3244A949EA7D591F79B64F324846507448CD"', - bigR: '"02330092EBF809B05EA0A032A42AD2FE32579D997A739D7BB4CF40EBA83B4355D3"', publicKey: '"047E3AC46588256338E62D8763592B8AA9BD13C31C9326D51CE82254A1839759A4FE7C1281AA1A9F8E810DA52B72046731CB3EE4D213799F7CE26C55A63783DB78"', sigName: 'sig', @@ -84,12 +72,9 @@ describe('processLitActionResponseStrategy', () => { success: true, signedData: { sig: { - sigType: 'K256', - dataSigned: - '"7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4"', + sigType: 'EcdsaK256Sha256', signatureShare: '"F21798A1A37CC86566EA0D751F37CC144774A1A8A4FCD5E6E64287690FB60119"', - bigR: '"02330092EBF809B05EA0A032A42AD2FE32579D997A739D7BB4CF40EBA83B4355D3"', publicKey: '"047E3AC46588256338E62D8763592B8AA9BD13C31C9326D51CE82254A1839759A4FE7C1281AA1A9F8E810DA52B72046731CB3EE4D213799F7CE26C55A63783DB78"', sigName: 'sig', @@ -104,12 +89,9 @@ describe('processLitActionResponseStrategy', () => { success: true, signedData: { sig: { - sigType: 'K256', - dataSigned: - '"7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4"', + sigType: 'EcdsaK256Sha256', signatureShare: '"7ECB0E020BED801905D3FE941751E4313086603BBBF21F1756832F02A6FBE567"', - bigR: '"02330092EBF809B05EA0A032A42AD2FE32579D997A739D7BB4CF40EBA83B4355D3"', publicKey: '"047E3AC46588256338E62D8763592B8AA9BD13C31C9326D51CE82254A1839759A4FE7C1281AA1A9F8E810DA52B72046731CB3EE4D213799F7CE26C55A63783DB78"', sigName: 'sig', @@ -122,15 +104,13 @@ describe('processLitActionResponseStrategy', () => { }, ]; - const litActionResponsesNonJson: any[] = [ + const litActionResponsesNonJson: ExecuteJsValueResponse[] = [ { success: true, signedData: { sig: { - sigType: 'K256', - dataSigned: 'fail', + sigType: 'EcdsaK256Sha256', signatureShare: '', - bigR: '', publicKey: '', sigName: 'sig', }, @@ -144,10 +124,8 @@ describe('processLitActionResponseStrategy', () => { success: true, signedData: { sig: { - sigType: 'K256', - dataSigned: 'fail', + sigType: 'EcdsaK256Sha256', signatureShare: '', - bigR: '', publicKey: '', sigName: 'sig', }, @@ -161,12 +139,9 @@ describe('processLitActionResponseStrategy', () => { success: true, signedData: { sig: { - sigType: 'K256', - dataSigned: - '"7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4"', + sigType: 'EcdsaK256Sha256', signatureShare: '"E90BAE64AFA7C571CE41BEF25FF771CA2F1BC20FC09A7762200552B30ACC0CDC"', - bigR: '"02330092EBF809B05EA0A032A42AD2FE32579D997A739D7BB4CF40EBA83B4355D3"', publicKey: '"047E3AC46588256338E62D8763592B8AA9BD13C31C9326D51CE82254A1839759A4FE7C1281AA1A9F8E810DA52B72046731CB3EE4D213799F7CE26C55A63783DB78"', sigName: 'sig', @@ -181,12 +156,9 @@ describe('processLitActionResponseStrategy', () => { success: true, signedData: { sig: { - sigType: 'K256', - dataSigned: - '"7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4"', + sigType: 'EcdsaK256Sha256', signatureShare: '"31977D4BE7F49C0CD97CC0756CCA3244A949EA7D591F79B64F324846507448CD"', - bigR: '"02330092EBF809B05EA0A032A42AD2FE32579D997A739D7BB4CF40EBA83B4355D3"', publicKey: '"047E3AC46588256338E62D8763592B8AA9BD13C31C9326D51CE82254A1839759A4FE7C1281AA1A9F8E810DA52B72046731CB3EE4D213799F7CE26C55A63783DB78"', sigName: 'sig', @@ -201,12 +173,9 @@ describe('processLitActionResponseStrategy', () => { success: true, signedData: { sig: { - sigType: 'K256', - dataSigned: - '"7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4"', + sigType: 'EcdsaK256Sha256', signatureShare: '"F21798A1A37CC86566EA0D751F37CC144774A1A8A4FCD5E6E64287690FB60119"', - bigR: '"02330092EBF809B05EA0A032A42AD2FE32579D997A739D7BB4CF40EBA83B4355D3"', publicKey: '"047E3AC46588256338E62D8763592B8AA9BD13C31C9326D51CE82254A1839759A4FE7C1281AA1A9F8E810DA52B72046731CB3EE4D213799F7CE26C55A63783DB78"', sigName: 'sig', @@ -221,12 +190,9 @@ describe('processLitActionResponseStrategy', () => { success: true, signedData: { sig: { - sigType: 'K256', - dataSigned: - '"7D87C5EA75F7378BB701E404C50639161AF3EFF66293E9F375B5F17EB50476F4"', + sigType: 'EcdsaK256Sha256', signatureShare: '"7ECB0E020BED801905D3FE941751E4313086603BBBF21F1756832F02A6FBE567"', - bigR: '"02330092EBF809B05EA0A032A42AD2FE32579D997A739D7BB4CF40EBA83B4355D3"', publicKey: '"047E3AC46588256338E62D8763592B8AA9BD13C31C9326D51CE82254A1839759A4FE7C1281AA1A9F8E810DA52B72046731CB3EE4D213799F7CE26C55A63783DB78"', sigName: 'sig', diff --git a/packages/lit-node-client-nodejs/src/lib/helpers/process-lit-action-response-strategy.ts b/packages/lit-node-client-nodejs/src/lib/helpers/process-lit-action-response-strategy.ts index 4d725ffed..a203b0772 100644 --- a/packages/lit-node-client-nodejs/src/lib/helpers/process-lit-action-response-strategy.ts +++ b/packages/lit-node-client-nodejs/src/lib/helpers/process-lit-action-response-strategy.ts @@ -1,29 +1,28 @@ +import { log, logError } from '@lit-protocol/misc'; import { LitActionResponseStrategy, - ResponseStrategy, - NodeShare, + ExecuteJsValueResponse, } from '@lit-protocol/types'; -import { log, logError } from '@lit-protocol/misc'; /** * Finds the most and least common object within an of objects array * @param responses any[] - * @returns an object which contains both the least and most occuring item in the array + * @returns an object which contains both the least and most occurring item in the array */ -const _findFrequency = (responses: string[]): { min: any; max: any } => { +const _findFrequency = (responses: T[]): { min: T; max: T } => { const sorted = responses.sort( - (a: any, b: any) => - responses.filter((v: any) => v === a).length - - responses.filter((v: any) => v === b).length + (a, b) => + responses.filter((v) => v === a).length - + responses.filter((v) => v === b).length ); return { min: sorted[0], max: sorted[sorted?.length - 1] }; }; export const processLitActionResponseStrategy = ( - responses: NodeShare[], + responses: ExecuteJsValueResponse[], strategy: LitActionResponseStrategy -): any => { +) => { const executionResponses = responses.map((nodeResp) => { return nodeResp.response; }); @@ -50,7 +49,7 @@ export const processLitActionResponseStrategy = ( } } - let respFrequency = _findFrequency(copiedExecutionResponses); + const respFrequency = _findFrequency(copiedExecutionResponses); if (strategy?.strategy === 'leastCommon') { log( 'strategy found to be most common, taking most common response from execution results' @@ -65,6 +64,6 @@ export const processLitActionResponseStrategy = ( log( 'no strategy found, using least common response object from execution results' ); - respFrequency.min; + return respFrequency.min; } }; 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 8270c1b1f..9a13b3934 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 @@ -19,6 +19,8 @@ import { } from '@lit-protocol/auth-helpers'; import { AUTH_METHOD_TYPE, + CURVE_GROUP_BY_CURVE_TYPE, + EcdsaSigType, EITHER_TYPE, FALLBACK_IPFS_GATEWAYS, GLOBAL_OVERWRITE_IPFS_CODE_BY_NETWORK, @@ -26,7 +28,6 @@ import { InvalidParamType, InvalidSessionSigs, InvalidSignatureError, - LIT_CURVE, LIT_CURVE_TYPE, LIT_ENDPOINT, LitNodeClientNotReadyError, @@ -34,8 +35,10 @@ import { ParamNullError, ParamsMissingError, PRODUCT_IDS, + PRODUCT_IDS_TYPE, SIWE_URI_PREFIX, UnknownError, + UnknownSignatureError, UnsupportedMethodError, WalletSignatureNotFoundError, } from '@lit-protocol/constants'; @@ -45,6 +48,7 @@ import { combineSignatureShares, encrypt, generateSessionKeyPair, + hashLitMessage, verifyAndDecryptWithSignatureShares, verifySignature, } from '@lit-protocol/crypto'; @@ -69,9 +73,35 @@ import { } from '@lit-protocol/misc-browser'; import { nacl } from '@lit-protocol/nacl'; import { + AuthMethod, + ExecuteJsValueResponse, + LitNodeSignature, +} from '@lit-protocol/types'; +import { + uint8arrayFromString, + uint8arrayToString, +} from '@lit-protocol/uint8arrays'; + +import { encodeCode } from './helpers/encode-code'; +import { getBlsSignatures } from './helpers/get-bls-signatures'; +import { getClaims } from './helpers/get-claims'; +import { getClaimsList } from './helpers/get-claims-list'; +import { getExpiration } from './helpers/get-expiration'; +import { getMaxPricesForNodeProduct } from './helpers/get-max-prices-for-node-product'; +import { + combineExecuteJSSignatures, + combinePKPSignSignatures, +} from './helpers/get-signatures'; +import { normalizeArray } from './helpers/normalize-array'; +import { normalizeJsParams } from './helpers/normalize-params'; +import { parseAsJsonOrString } from './helpers/parse-as-json-or-string'; +import { processLitActionResponseStrategy } from './helpers/process-lit-action-response-strategy'; +import { blsSessionSigVerify } from './helpers/validate-bls-session-sig'; + +import type { AuthCallback, AuthCallbackParams, - type AuthenticationContext, + AuthenticationContext, AuthSig, BlsResponseData, CapacityCreditsReq, @@ -101,8 +131,6 @@ import { NodeBlsSigningShare, NodeCommandResponse, NodeSet, - NodeShare, - PKPSignEndpointResponse, RejectedNodePromises, SessionKeyPair, SessionSigningTemplate, @@ -110,29 +138,8 @@ import { Signature, SignSessionKeyProp, SignSessionKeyResponse, - SigResponse, SuccessNodePromises, } from '@lit-protocol/types'; -import { AuthMethod } from '@lit-protocol/types'; -import { - uint8arrayFromString, - uint8arrayToString, -} from '@lit-protocol/uint8arrays'; - -import { encodeCode } from './helpers/encode-code'; -import { getBlsSignatures } from './helpers/get-bls-signatures'; -import { getClaims } from './helpers/get-claims'; -import { getClaimsList } from './helpers/get-claims-list'; -import { getExpiration } from './helpers/get-expiration'; -import { getMaxPricesForNodeProduct } from './helpers/get-max-prices-for-node-product'; -import { getSignatures } from './helpers/get-signatures'; -import { normalizeArray } from './helpers/normalize-array'; -import { normalizeJsParams } from './helpers/normalize-params'; -import { parseAsJsonOrString } from './helpers/parse-as-json-or-string'; -import { parsePkpSignResponse } from './helpers/parse-pkp-sign-response'; -import { processLitActionResponseStrategy } from './helpers/process-lit-action-response-strategy'; -import { removeDoubleQuotes } from './helpers/remove-double-quotes'; -import { blsSessionSigVerify } from './helpers/validate-bls-session-sig'; export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { /** Tracks the total max price a user is willing to pay for each supported product type @@ -140,7 +147,7 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { * * If the user never sets a max price, it means 'unlimited' */ - defaultMaxPriceByProduct: Record = { + defaultMaxPriceByProduct: Record = { DECRYPTION: BigInt(-1), SIGN: BigInt(-1), LIT_ACTION: BigInt(-1), @@ -725,7 +732,8 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { } // -- case: promises success (TODO: check the keys of "values") - const responseData = (res as SuccessNodePromises).values; + const responseData = (res as SuccessNodePromises) + .values; logWithRequestId( requestId, @@ -734,9 +742,7 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { ); // -- find the responseData that has the most common response - const mostCommonResponse = findMostCommonResponse( - responseData - ) as NodeShare; + const mostCommonResponse = findMostCommonResponse(responseData); const responseFromStrategy = processLitActionResponseStrategy( responseData, @@ -744,20 +750,15 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { ); mostCommonResponse.response = responseFromStrategy; - const isSuccess = mostCommonResponse.success; const hasSignedData = Object.keys(mostCommonResponse.signedData).length > 0; const hasClaimData = Object.keys(mostCommonResponse.claimData).length > 0; - // -- we must also check for claim responses as a user may have submitted for a claim and signatures must be aggregated before returning - if (isSuccess && !hasSignedData && !hasClaimData) { - return mostCommonResponse as unknown as ExecuteJsResponse; - } - // -- in the case where we are not signing anything on Lit action and using it as purely serverless function if (!hasSignedData && !hasClaimData) { return { + success: mostCommonResponse.success, claims: {}, - signatures: null, + signatures: {}, decryptions: [], response: mostCommonResponse.response, logs: mostCommonResponse.logs, @@ -766,10 +767,8 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { // ========== Extract shares from response data ========== - // -- 1. combine signed data as a list, and get the signatures from it - const signedDataList = responseData.map((r) => { - return removeDoubleQuotes(r.signedData); - }); + // -- 1. combine signed data and get the signatures from it + const signedDataList = responseData.map((r) => r.signedData); logWithRequestId( requestId, @@ -777,12 +776,10 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { signedDataList ); - // Flatten the signedDataList by moving the data within the `sig` (or any other key user may choose) object to the top level. - // The specific key name (`sig`) is irrelevant, as the contents of the object are always lifted directly. - const key = Object.keys(signedDataList[0])[0]; // Get the first key of the object - - const flattenedSignedMessageShares = signedDataList.map((item) => { - return item[key]; // Return the value corresponding to that key + const signatures = await combineExecuteJSSignatures({ + nodesLitActionSignedData: responseData, + requestId, + threshold: this._getThreshold(), }); // -- 2. combine responses as a string, and parse it as JSON if possible @@ -805,17 +802,7 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { // ========== Result ========== const returnVal: ExecuteJsResponse = { claims, - signatures: hasSignedData - ? { - [key]: await getSignatures({ - requestId, - networkPubKeySet: this.networkPubKeySet, - threshold: params.useSingleNode ? 1 : this._getThreshold(), - signedMessageShares: flattenedSignedMessageShares, - }), - } - : {}, - // decryptions: [], + signatures, response: parsedResponse, logs: mostCommonLogs, }; @@ -850,14 +837,15 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { * Use PKP to sign * * @param { JsonPkpSignSdkParams } params - * @param params.toSign - The data to sign + * @param params.messageToSign - The data to sign, not hashed + * @param params.signingScheme - The signing scheme to use when signing the message. If hashing is needed, it will be derived from this * @param params.pubKey - The public key to sign with * @param params.sessionSigs - The session signatures to use - * @param params.authMethods - (optional) The auth methods to use + * @param [params.authMethods] - The auth methods to use */ - pkpSign = async (params: JsonPkpSignSdkParams): Promise => { + pkpSign = async (params: JsonPkpSignSdkParams): Promise => { // -- validate required params - const requiredParamKeys = ['toSign', 'pubKey', 'authContext']; + const requiredParamKeys = ['messageToSign', 'pubKey', 'authContext']; // TODO migrate to zod (requiredParamKeys as (keyof JsonPkpSignSdkParams)[]).forEach((key) => { if (!params[key]) { @@ -890,7 +878,7 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { // validate session sigs const checkedSessionSigs = validateSessionSigs(sessionSigs); - if (checkedSessionSigs.isValid === false) { + if (!checkedSessionSigs.isValid) { throw new InvalidSessionSigs( {}, `Invalid sessionSigs. Errors: ${checkedSessionSigs.errors}` @@ -910,8 +898,17 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { url, }); - const reqBody: JsonPkpSignRequest = { - toSign: normalizeArray(params.toSign), + const toSign = + CURVE_GROUP_BY_CURVE_TYPE[params.signingScheme] !== 'ECDSA' + ? params.messageToSign! + : hashLitMessage( + params.signingScheme as EcdsaSigType, + params.messageToSign! + ); + + const reqBody: JsonPkpSignRequest = { + toSign: normalizeArray(toSign), + signingScheme: params.signingScheme, pubkey: hexPrefixed(params.pubKey), authSig: sessionSig, @@ -923,7 +920,6 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { // nodeSet: thresholdNodeSet, nodeSet: this._getNodeSet(targetNodeUrls), - signingScheme: 'EcdsaK256Sha256', }; logWithRequestId(requestId, 'reqBody:', reqBody); @@ -945,11 +941,10 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { // ========== Handle Response ========== if (!res.success) { - this._throwNodeError(res, requestId); + return this._throwNodeError(res, requestId); } - const responseData = (res as SuccessNodePromises) - .values; + const responseData = res.values; logWithRequestId( requestId, @@ -957,23 +952,28 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { JSON.stringify(responseData) ); - // clean up the response data (as there are double quotes & snake cases in the response) - const signedMessageShares = parsePkpSignResponse(responseData); - try { - const signatures = await getSignatures({ + const signatures = await combinePKPSignSignatures({ + nodesPkpSignResponseData: responseData, requestId, - networkPubKeySet: this.networkPubKeySet, threshold: this._getThreshold(), - signedMessageShares: signedMessageShares, }); logWithRequestId(requestId, `signature combination`, signatures); return signatures; } catch (e) { - console.error('Error getting signature', e); - throw e; + throw new UnknownSignatureError( + { + info: { + responseData, + requestId, + threshold: this._getThreshold(), + }, + cause: e, + }, + 'Could not combine pkp signature shares' + ); } }; @@ -1365,7 +1365,7 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { authMethods: params.authMethods, ...(params?.pkpPublicKey && { pkpPublicKey: params.pkpPublicKey }), siweMessage: siweMessage, - curveType: LIT_CURVE.BLS, + curveType: 'BLS', // -- custom auths ...(params?.litActionIpfsId && { @@ -1374,7 +1374,6 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { ...(params?.litActionCode && { code: params.litActionCode }), ...(params?.jsParams && { jsParams: params.jsParams }), ...(this.currentEpochNumber && { epoch: this.currentEpochNumber }), - signingScheme: LIT_CURVE.BLS, }; log(`[signSessionKey] body:`, body); @@ -1551,7 +1550,7 @@ export class LitNodeClientNodeJs extends LitCore implements ILitNodeClient { product, }: { userMaxPrice?: bigint; - product: keyof typeof PRODUCT_IDS; + product: PRODUCT_IDS_TYPE; }) => { log('getMaxPricesForNodeProduct()', { product }); const getUserMaxPrice = () => { diff --git a/packages/misc/src/lib/misc.spec.ts b/packages/misc/src/lib/misc.spec.ts index f20dc6050..413c3d9aa 100644 --- a/packages/misc/src/lib/misc.spec.ts +++ b/packages/misc/src/lib/misc.spec.ts @@ -28,20 +28,85 @@ describe('utils', () => { expect((console.log as any).mock.calls[2][0]).toBe('Error Message'); }); - it('should get the most common string in an array', () => { - const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 8]; + describe('mostCommonString', () => { + it('should get the most common string in an array', () => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 8]; - const mostOccured = utilsModule.mostCommonString(arr); + const mostOccured = utilsModule.mostCommonString(arr); - expect(mostOccured).toBe(8); - }); + expect(mostOccured).toBe(8); + }); + + it('should get the last element of the array if every element only appears once', () => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; + + const mostOccured = utilsModule.mostCommonString(arr); + + expect(mostOccured).toBe(0); + }); - it('should get the last element of the array if every element only appears once', () => { - const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; + it('returns the most common element in an array of strings', () => { + const arr = ['apple', 'banana', 'apple', 'orange']; + expect(utilsModule.mostCommonString(arr)).toBe('apple'); + }); - const mostOccured = utilsModule.mostCommonString(arr); + it('returns the most common element in an array of numbers', () => { + const arr = [1, 2, 2, 3, 2]; + expect(utilsModule.mostCommonString(arr)).toBe(2); + }); + + it('returns undefined for an empty array', () => { + expect(utilsModule.mostCommonString([])).toBeUndefined(); + }); + + it('handles tie scenarios by returning one of the tied elements', () => { + const result = utilsModule.mostCommonString(['x', 'y']); + expect(['x', 'y']).toContain(result); + }); + }); - expect(mostOccured).toBe(0); + describe('findMostCommonResponse', () => { + it('returns the most common responses for flat objects', () => { + const responses = [ + { a: 'x', b: 'y' }, + { a: 'x', b: 'z' }, + { a: 'w', b: 'y' }, + ]; + const expected = { a: 'x', b: 'y' }; + expect(utilsModule.findMostCommonResponse(responses)).toEqual(expected); + }); + + it('throws when given an empty array, there is no most common response', () => { + expect(() => utilsModule.findMostCommonResponse([])).toThrow( + 'findMostCommonResponse requires at least one response object' + ); + }); + + it('handles nested objects recursively', () => { + const responses = [ + { a: { x: 1, y: 2 } }, + { a: { x: 1, y: 3 } }, + { a: { x: 2, y: 2 } }, + ]; + const expected = { a: { x: 1, y: 2 } }; + expect(utilsModule.findMostCommonResponse(responses)).toEqual(expected); + }); + + it('filters out undefined and empty string values', () => { + const responses = [ + { a: '', b: 10 }, + { a: 5, b: 10 }, + { a: 5, b: undefined }, + ]; + const expected = { a: 5, b: 10 }; + expect(utilsModule.findMostCommonResponse(responses)).toEqual(expected); + }); + + it('assigns undefined when all values for a key are filtered out', () => { + const responses = [{ a: '' }, { a: undefined }]; + const expected = { a: undefined }; + expect(utilsModule.findMostCommonResponse(responses)).toEqual(expected); + }); }); it('should get value type by a given value', () => { diff --git a/packages/misc/src/lib/misc.ts b/packages/misc/src/lib/misc.ts index 6f2152e97..e8e529261 100644 --- a/packages/misc/src/lib/misc.ts +++ b/packages/misc/src/lib/misc.ts @@ -1,4 +1,3 @@ -import { LitNodeClientConfig } from '@lit-protocol/types'; import { Contract } from '@ethersproject/contracts'; import { JsonRpcProvider } from '@ethersproject/providers'; import Ajv, { JSONSchemaType } from 'ajv'; @@ -25,6 +24,8 @@ import { AuthSig, NodeErrorV3, ClaimResult, + Hex, + LitNodeClientConfig, MintCallback, RelayClaimProcessor, } from '@lit-protocol/types'; @@ -69,57 +70,61 @@ export const mostCommonString = (arr: T[]): T | undefined => { .pop(); }; -export const findMostCommonResponse = (responses: object[]): object => { +/** + * Recursively finds the most common value for each key across an array of response objects. + * + * For each key found in any response object, the function aggregates all non-empty values (ignoring + * `undefined` and empty strings) and determines the most frequently occurring value. If the value is an object + * (and not an array), the function recurses into that object. + * + * @template T - The shape of the input objects in the array. + * @param {T[]} responses - An array of response objects. + * @returns {T} An object with each key set to its most common value across all responses. + */ +export const findMostCommonResponse = >( + responses: T[] +): T => { + if (responses.length === 0) { + throw new Error( + 'findMostCommonResponse requires at least one response object' + ); + } + const result: Record = {}; // Aggregate all values for each key across all responses const keys = new Set(responses.flatMap(Object.keys)); for (const key of keys) { - const values = responses.map( - (response: Record) => response[key] - ); + const values = responses.map((response) => response[key]); - // Filter out undefined values before processing + // Filter out undefined and empty string values before processing const filteredValues = values.filter( (value) => value !== undefined && value !== '' ); if (filteredValues.length === 0) { - result[key] = undefined; // or set a default value if needed + result[key] = undefined; } else if ( typeof filteredValues[0] === 'object' && !Array.isArray(filteredValues[0]) ) { - // Recursive case for objects + // Recursive case for nested objects result[key] = findMostCommonResponse(filteredValues); } else { - // Most common element from filtered values + // Determine the most common element from filtered values result[key] = mostCommonString(filteredValues); } } - return result; + return result as T; }; declare global { - var wasmExport: any; - var wasmECDSA: any; var logger: any; var logManager: any; } -export const throwRemovedFunctionError = (functionName: string) => { - throw new RemovedFunctionError( - { - info: { - functionName, - }, - }, - `This function "${functionName}" has been removed. Please use the old SDK.` - ); -}; - export const bootstrapLogManager = ( id: string, level: LOG_LEVEL_VALUES = LOG_LEVEL.DEBUG @@ -161,7 +166,7 @@ export const log = (...args: any): void => { return; } - // if there are there are logs in buffer, print them first and empty the buffer. + // if there are logs in buffer, print them first and empty the buffer. while (logBuffer.length > 0) { const log = logBuffer.shift() ?? ''; globalThis?.logger && globalThis?.logger.debug(...log); @@ -619,17 +624,21 @@ export const defaultMintClaimCallback: MintCallback< return body.requestId; }; +export const isHexableString = (str: string): boolean => { + return /^(0x|0X)?[0-9a-fA-F]+$/.test(str); +}; + /** * Adds a '0x' prefix to a string if it doesn't already have one. * @param str - The input string. * @returns The input string with a '0x' prefix. */ -export const hexPrefixed = (str: string): `0x${string}` => { +export const hexPrefixed = (str: string): Hex => { if (str.startsWith('0x')) { - return str as `0x${string}`; + return str as Hex; } - return ('0x' + str) as `0x${string}`; + return ('0x' + str) as Hex; }; /** @@ -720,6 +729,123 @@ export function sendRequest( }); } +/** + * Converts a snake_case string to camelCase. + * @param s The snake_case string to convert. + * @returns The camelCase version of the input string. + * + * @example + * snakeToCamel('hello_world') // 'helloWorld' + */ +export const snakeToCamel = (s: string): string => + s.replace(/(_\w)/g, (m) => m[1].toUpperCase()); + +export type Transformation = ( + target: Record +) => Record; + +/** + * Converts the keys of an object from snake_case to camelCase. + * + * @param obj - The object whose keys need to be converted. + * @returns The object with keys converted to camelCase. + */ +export const convertKeysToCamelCase: Transformation = ( + obj: Record +): Record => + Object.keys(obj).reduce( + (acc, key) => ({ + ...acc, + [snakeToCamel(key)]: obj[key], + }), + {} + ); + +/** + * Removes values that are received as a two element array (tuple) by just leaving the second one + * + * @param obj - The object that can have tupled elements + * @returns The object with tupled elements removed, keeping only the second element + */ +export const cleanArrayValues: Transformation = ( + obj: Record +): Record => + Object.keys(obj).reduce( + (acc, key) => ({ + ...acc, + [key]: + typeof obj[key] === 'string' && obj[key].charAt(0) === '[' + ? JSON.parse(obj[key])[1] + : obj[key], + }), + {} + ); + +/** + * Converts number arrays to Uint8Arrays in an object. + * + * @param obj - The object that can have number arrays. + * @returns A new object with number arrays converted to Uint8Arrays. + */ +export const convertNumberArraysToUint8Arrays: Transformation = ( + obj: Record +): Record => + Object.keys(obj).reduce( + (acc, key) => ({ + ...acc, + [key]: + Array.isArray(obj[key]) && typeof obj[key][0] === 'number' + ? new Uint8Array(obj[key]) + : obj[key], + }), + {} + ); + +/** + * Removes double quotes from string values in an object. + * + * @param obj - The object to clean string values from. + * @returns A new object with string values cleaned. + */ +export const cleanStringValues: Transformation = ( + obj: Record +): Record => + Object.keys(obj).reduce( + (acc, key) => ({ + ...acc, + [key]: + typeof obj[key] === 'string' ? obj[key].replace(/"/g, '') : obj[key], + }), + {} + ); + +/** + * Asserts hex values have a prefix of 0x. + * + * @param obj - The object to hex string values from. + * @returns A new object with string values hexed. + */ +export const hexifyStringValues: Transformation = ( + obj: Record +): Record => + Object.keys(obj).reduce( + (acc, key) => ({ + ...acc, + [key]: + typeof obj[key] === 'string' && isHexableString(obj[key]) + ? hexPrefixed(obj[key]) + : obj[key], + }), + {} + ); + +export const applyTransformations = ( + target: Record, + transformations: Transformation[] +): Record => { + return transformations.reduce((acc, transform) => transform(acc), target); +}; + /** * Attempts to normalize a string by unescaping it until it can be parsed as a JSON object, * then stringifies it exactly once. If the input is a regular string that does not represent diff --git a/packages/pkp-base/src/lib/pkp-base.ts b/packages/pkp-base/src/lib/pkp-base.ts index 3f7ff3feb..63db57e7d 100644 --- a/packages/pkp-base/src/lib/pkp-base.ts +++ b/packages/pkp-base/src/lib/pkp-base.ts @@ -9,7 +9,9 @@ */ import { InitError, + InvalidSignatureError, LitNodeClientNotReadyError, + SigType, UnknownError, } from '@lit-protocol/constants'; import { LitNodeClient } from '@lit-protocol/lit-node-client'; @@ -18,8 +20,8 @@ import { AuthenticationContext, JsonExecutionSdkParams, PKPBaseProp, + LitNodeSignature, PKPBaseDefaultParams, - SigResponse, RPCUrls, } from '@lit-protocol/types'; @@ -250,12 +252,14 @@ export class PKPBase { * @param {Uint8Array} toSign - The data to be signed by the Lit action. * @param {string} sigName - The name of the signature to be returned by the Lit action. * - * @returns {Promise} - A Promise that resolves with the signature returned by the Lit action. + * @returns {Promise} - A Promise that resolves with the signature returned by the Lit action. * * @throws {Error} - Throws an error if `pkpPubKey` is not provided, if `controllerAuthSig` or `controllerSessionSigs` is not provided, if `controllerSessionSigs` is not an object, if `executeJsArgs` does not have either `code` or `ipfsId`, or if an error occurs during the execution of the Lit action. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - async runLitAction(toSign: Uint8Array, sigName: string): Promise { + async runLitAction( + toSign: Uint8Array, + sigName: string + ): Promise { // -- validate executeJsArgs if (this.litActionCode && this.litActionIPFS) { throw new InitError( @@ -319,25 +323,23 @@ export class PKPBase { this.log('res:', res); this.log('res.signatures[sigName]:', sig); - if (sig.r && sig.s) { - // pad sigs with 0 if length is odd - sig.r = sig.r.length % 2 === 0 ? sig.r : '0' + sig.r; - sig.s = sig.s.length % 2 === 0 ? sig.s : '0' + sig.s; - } - return sig; } /** * Sign the provided data with the PKP private key. * - * @param {Uint8Array} toSign - The data to be signed. + * @param {Uint8Array} messageToSign - The message to be signed (will be hashed using SHA256). + * @param {SigType} signingScheme - The signing scheme to use for the signature. Defaults to 'EcdsaK256Sha256'. * * @returns {Promise} - A Promise that resolves with the signature of the provided data. * * @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( + messageToSign: Uint8Array, + signingScheme: SigType = 'EcdsaK256Sha256' + ): Promise { await this.ensureLitNodeClientReady(); // If no PKP public key is provided, throw error @@ -350,26 +352,28 @@ export class PKPBase { this.validateAuthContext(); - try { - const sig = await this.litNodeClient.pkpSign({ - toSign, - pubKey: this.uncompressedPubKey, - authContext: this.authContext, - }); - - if (!sig) { - throw new UnknownError({}, 'No signature returned'); - } - - // pad sigs with 0 if length is odd - sig.r = sig.r.length % 2 === 0 ? sig.r : '0' + sig.r; - sig.s = sig.s.length % 2 === 0 ? sig.s : '0' + sig.s; + const sig = await this.litNodeClient.pkpSign({ + messageToSign, + pubKey: this.uncompressedPubKey, + authContext: this.authContext, + signingScheme, + }); - return sig; - } catch (e) { - console.log('err: ', e); - throw e; + if (!sig) { + throw new InvalidSignatureError( + { + info: { + messageToSign, + signingScheme, + compressedPubKey: this.compressedPubKey, + uncompressedPubKey: this.uncompressedPubKey, + }, + }, + 'No signature returned' + ); } + + return sig; } /** @@ -387,12 +391,11 @@ export class PKPBase { /** * Logs the provided arguments to the console, but only if debugging is enabled. * - * @param {...any[]} args - The values to be logged to the console. + * @param {unknown[]} args - The values to be logged to the console. * * @returns {void} - This function does not return a value. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - log(...args: any[]): void { + log(...args: unknown[]): void { if (this.debug) { console.log(this.orange + this.PREFIX + this.reset, ...args); } diff --git a/packages/pkp-cosmos/src/lib/pkp-cosmos.ts b/packages/pkp-cosmos/src/lib/pkp-cosmos.ts index 3438ce657..735423613 100644 --- a/packages/pkp-cosmos/src/lib/pkp-cosmos.ts +++ b/packages/pkp-cosmos/src/lib/pkp-cosmos.ts @@ -14,8 +14,8 @@ import { encodeSecp256k1Signature, rawSecp256k1PubkeyToRawAddress, } from '@cosmjs/amino'; -import { Secp256k1, sha256, ExtendedSecp256k1Signature } from '@cosmjs/crypto'; -import { toBech32, fromHex } from '@cosmjs/encoding'; +import { Secp256k1, sha256 } from '@cosmjs/crypto'; +import { toBech32 } from '@cosmjs/encoding'; import { makeSignBytes, AccountData, @@ -36,13 +36,14 @@ import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx'; import { InvalidArgumentException, RemovedFunctionError, + SigType, } from '@lit-protocol/constants'; import { PKPBase } from '@lit-protocol/pkp-base'; import { + LitNodeSignature, PKPClientHelpers, PKPCosmosWalletProp, PKPWallet, - SigResponse, } from '@lit-protocol/types'; export { Long } from 'cosmjs-types/helpers'; @@ -177,30 +178,20 @@ export class PKPCosmosWallet const hashedMessage = sha256(signBytes); // Run the LIT action to obtain the signature. - let signature; + let litSignature; if (this.pkpBase.useAction) { - signature = await this.runLitAction(hashedMessage, this.defaultSigName); + litSignature = await this.runLitAction( + hashedMessage, + this.defaultSigName + ); } else { - signature = await this.runSign(hashedMessage); + litSignature = await this.runSign(hashedMessage); } - // Create an ExtendedSecp256k1Signature from the signature components. - const extendedSig = new ExtendedSecp256k1Signature( - fromHex(signature.r), - fromHex(signature.s), - signature.recid - ); - - // Combine the R and S components of the signature into a Uint8Array. - const signatureBytes = new Uint8Array([ - ...extendedSig.r(32), - ...extendedSig.s(32), - ]); - // Encode the signature in the Cosmos-compatible format. const stdSignature = encodeSecp256k1Signature( this.pkpBase.compressedPubKeyBuffer, - signatureBytes + Buffer.from(litSignature.signature.replace('0x', ''), 'hex') ); // Log the encoded signature. @@ -321,11 +312,14 @@ export class PKPCosmosWallet * @param {Uint8Array} toSign - The data to be signed by the Lit action. * @param {string} sigName - The name of the signature to be returned by the Lit action. * - * @returns {Promise} - A Promise that resolves with the signature returned by the Lit action. + * @returns {Promise} - A Promise that resolves with the signature returned by the Lit action. * * @throws {Error} - Throws an error if `pkpPubKey` is not provided, if `controllerAuthSig` or `controllerSessionSigs` is not provided, if `controllerSessionSigs` is not an object, if `executeJsArgs` does not have either `code` or `ipfsId`, or if an error occurs during the execution of the Lit action. */ - async runLitAction(toSign: Uint8Array, sigName: string): Promise { + async runLitAction( + toSign: Uint8Array, + sigName: string + ): Promise { return this.pkpBase.runLitAction(toSign, sigName); } @@ -333,12 +327,16 @@ export class PKPCosmosWallet * Sign the provided data with the PKP private key. * * @param {Uint8Array} toSign - The data to be signed. + * @param {SigType} signingScheme - The signing scheme to use for the signature. Defaults to 'EcdsaK256Sha256'. * - * @returns {Promise} - A Promise that resolves with the signature of the provided data. + * @returns {Promise} - A Promise that resolves with the lit signature of the provided data. * * @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 { - return this.pkpBase.runSign(toSign); + async runSign( + toSign: Uint8Array, + signingScheme: SigType = 'EcdsaK256Sha256' + ): Promise { + return this.pkpBase.runSign(toSign, signingScheme); } } diff --git a/packages/pkp-ethers/src/lib/handler.ts b/packages/pkp-ethers/src/lib/handler.ts index 6a065fc4e..57191c465 100644 --- a/packages/pkp-ethers/src/lib/handler.ts +++ b/packages/pkp-ethers/src/lib/handler.ts @@ -2,8 +2,9 @@ // Integrated from https://github.com/LIT-Protocol/lit-pkp-sdk/blob/main/examples/signTypedData.mjs // -import { joinSignature } from '@ethersproject/bytes'; import { typedSignatureHash } from '@metamask/eth-sig-util'; +import { ethers } from 'ethers'; + import { InvalidArgumentException, InvalidParamType, @@ -13,6 +14,7 @@ import { UnsupportedMethodError, } from '@lit-protocol/constants'; +import { convertHexToUtf8, getTransactionToSign } from './helper'; import { PKPEthersWallet } from './pkp-ethers'; import { EIP712TypedData, @@ -22,11 +24,8 @@ import { ETHSignature, LitTypeDataSigner, UnknownETHMethod, - ETHRequestSigningPayload, ETHTxRes, } from './pkp-ethers-types'; -import { ethers } from 'ethers'; -import { convertHexToUtf8, getTransactionToSign } from './helper'; /** * Signs an EIP-712 typed data object or a JSON string representation of the typed data object. @@ -50,7 +49,7 @@ export const signTypedData = async ( msgParams = JSON.parse(msgParams); } - const { types, domain, primaryType, message } = msgParams as T; + const { types, domain, message } = msgParams as T; if (types['EIP712Domain']) { delete types['EIP712Domain']; @@ -95,13 +94,7 @@ export const signTypedDataLegacy = async ( // sig = await _signer.signMessage(messageHash); } - const encodedSig = joinSignature({ - r: '0x' + sig.r, - s: '0x' + sig.s, - v: sig.recid, - }); - - return encodedSig; + return sig.signature; }; /** @@ -116,7 +109,7 @@ export const signTypedDataLegacy = async ( export const validateAddressesMatch = ( signerAddress: string, requestAddress: string -) => { +): void => { if (signerAddress.toLowerCase() !== requestAddress.toLowerCase()) { throw new UnauthorizedException( { @@ -188,9 +181,9 @@ export function getTypedDataVersionInfo({ signer, payload }: ETHHandlerReq) { }; } - let addressRequested: string = payload.params[info.addressIndex]; + const addressRequested: string = payload.params[info.addressIndex]; validateAddressesMatch((signer as PKPEthersWallet).address, addressRequested); - let msgParams = payload.params[info.msgParamsIndex]; + const msgParams = payload.params[info.msgParamsIndex]; return { addressRequested, msgParams, info }; } @@ -354,7 +347,6 @@ export const signHandler = async ({ export const personalSignHandler = async ({ signer, payload, - capability, }: ETHHandlerReq): Promise => { const addressRequested = payload.params[1]; @@ -362,10 +354,6 @@ export const personalSignHandler = async ({ const msg = convertHexToUtf8(payload.params[0]); - // -- we will add capability to for resource - if (capability) { - } - const signature = await (signer as PKPEthersWallet).signMessage(msg); validateSignature(signature); @@ -445,7 +433,7 @@ export const ethRequestHandler = async ({ } return data; - } catch (e: any) { + } catch (e: unknown) { throw new UnknownError( { info: { diff --git a/packages/pkp-ethers/src/lib/helper.ts b/packages/pkp-ethers/src/lib/helper.ts index 3be517e65..14a7ed36b 100644 --- a/packages/pkp-ethers/src/lib/helper.ts +++ b/packages/pkp-ethers/src/lib/helper.ts @@ -1,4 +1,4 @@ -import { ethers } from 'ethers'; +import { ethers, BytesLike } from 'ethers'; /** * Convert a hexadecimal value to its UTF-8 string representation. @@ -40,7 +40,7 @@ export const getTransactionToSign = ( return formattedTx; }; -export function isSignedTransaction(tx: any): boolean { +export function isSignedTransaction(tx: BytesLike): boolean { try { const parsedTx = ethers.utils.parseTransaction(tx); return !!parsedTx.v && !!parsedTx.r && !!parsedTx.s; diff --git a/packages/pkp-ethers/src/lib/pkp-ethers.ts b/packages/pkp-ethers/src/lib/pkp-ethers.ts index 179ec04e6..c6e8963f0 100644 --- a/packages/pkp-ethers/src/lib/pkp-ethers.ts +++ b/packages/pkp-ethers/src/lib/pkp-ethers.ts @@ -7,13 +7,7 @@ import { TypedDataSigner, } from '@ethersproject/abstract-signer'; import { getAddress } from '@ethersproject/address'; -import { - arrayify, - Bytes, - concat, - hexDataSlice, - joinSignature, -} from '@ethersproject/bytes'; +import { arrayify, Bytes, concat, hexDataSlice } from '@ethersproject/bytes'; import { hashMessage, _TypedDataEncoder } from '@ethersproject/hash'; import { defaultPath, HDNode, entropyToMnemonic } from '@ethersproject/hdnode'; import { @@ -40,16 +34,17 @@ import { RPC_URL_BY_NETWORK, InvalidParamType, UnknownError, + SigType, UnsupportedMethodError, UnsupportedChainException, LIT_CHAINS, } from '@lit-protocol/constants'; import { PKPBase } from '@lit-protocol/pkp-base'; import { + LitNodeSignature, PKPClientHelpers, PKPEthersWalletProp, PKPWallet, - SigResponse, } from '@lit-protocol/types'; import { ethRequestHandler } from './handler'; @@ -293,26 +288,23 @@ export class PKPEthersWallet await this.pkpBase.ensureLitNodeClientReady(); const toSign = arrayify(hashMessage(message)); - let signature; + + let litSignature; if (this.pkpBase.useAction) { this.pkpBase.log('running lit action => sigName: pkp-eth-sign-message'); - signature = await this.runLitAction(toSign, 'pkp-eth-sign-message'); + litSignature = await this.runLitAction(toSign, 'pkp-eth-sign-message'); } else { this.pkpBase.log('requesting signature from nodes'); - signature = await this.runSign(toSign); + litSignature = await this.runSign(toSign); } - return joinSignature({ - r: '0x' + signature.r, - s: '0x' + signature.s, - v: signature.recid, - }); + return litSignature.signature; } async _signTypedData( domain: TypedDataDomain, types: Record, - value: Record + value: Record ): Promise { // Check if the LIT node client is connected, and connect if it's not. await this.pkpBase.ensureLitNodeClientReady(); @@ -349,21 +341,20 @@ export class PKPEthersWallet populated.value ); const toSignBuffer = arrayify(toSign); - let signature; + let litSignature; if (this.pkpBase.useAction) { this.pkpBase.log('running lit action => sigName: pkp-eth-sign-message'); - signature = await this.runLitAction(toSignBuffer, 'pkp-eth-sign-message'); + litSignature = await this.runLitAction( + toSignBuffer, + 'pkp-eth-sign-message' + ); } else { this.pkpBase.log('requesting signature from nodes'); - signature = await this.runSign(toSignBuffer); + litSignature = await this.runSign(toSignBuffer); } - return joinSignature({ - r: '0x' + signature.r, - s: '0x' + signature.s, - v: signature.recid, - }); + return litSignature.signature; } encrypt( @@ -590,11 +581,14 @@ export class PKPEthersWallet * @param {Uint8Array} toSign - The data to be signed by the Lit action. * @param {string} sigName - The name of the signature to be returned by the Lit action. * - * @returns {Promise} - A Promise that resolves with the signature returned by the Lit action. + * @returns {Promise} - A Promise that resolves with the signature returned by the Lit action. * * @throws {Error} - Throws an error if `pkpPubKey` is not provided, if `controllerAuthSig` or `controllerSessionSigs` is not provided, if `controllerSessionSigs` is not an object, if `executeJsArgs` does not have either `code` or `ipfsId`, or if an error occurs during the execution of the Lit action. */ - async runLitAction(toSign: Uint8Array, sigName: string): Promise { + async runLitAction( + toSign: Uint8Array, + sigName: string + ): Promise { return this.pkpBase.runLitAction(toSign, sigName); } @@ -602,12 +596,16 @@ export class PKPEthersWallet * Sign the provided data with the PKP private key. * * @param {Uint8Array} toSign - The data to be signed. + * @param {SigType} signingScheme - The signing scheme to use for the signature. Defaults to 'EcdsaK256Sha256'. * - * @returns {Promise} - A Promise that resolves with the signature of the provided data. + * @returns {Promise} - A Promise that resolves with the lit signature of the provided data. * * @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 { - return this.pkpBase.runSign(toSign); + async runSign( + toSign: Uint8Array, + signingScheme: SigType = 'EcdsaK256Sha256' + ): Promise { + return this.pkpBase.runSign(toSign, signingScheme); } } diff --git a/packages/pkp-sui/src/lib/pkp-sui.ts b/packages/pkp-sui/src/lib/pkp-sui.ts index 776187b29..9f38db828 100644 --- a/packages/pkp-sui/src/lib/pkp-sui.ts +++ b/packages/pkp-sui/src/lib/pkp-sui.ts @@ -18,24 +18,18 @@ import { fromB64, messageWithIntent, toB64, - toSerializedSignature, getTotalGasUsedUpperBound, } from '@mysten/sui.js'; -import { - hexToBytes, - numberToBytesBE, - bytesToHex, -} from '@noble/curves/abstract/utils'; -import { secp256k1 } from '@noble/curves/secp256k1'; 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 { InvalidArgumentException, + SigType, UnknownError, } from '@lit-protocol/constants'; +import { PKPBase } from '@lit-protocol/pkp-base'; +import { LitNodeSignature, PKPBaseProp, PKPWallet } from '@lit-protocol/types'; import { getDigestFromBytes } from './TransactionBlockData'; @@ -46,9 +40,6 @@ export class PKPSuiWallet implements PKPWallet, Signer { readonly prop: PKPBaseProp; readonly publicKey: Secp256k1PublicKey; - // Default Lit Action signature name - defaultSigName: string = 'pkp-sui-sign-tx'; - constructor(prop: PKPBaseProp, provider: JsonRpcProvider) { this.pkpBase = PKPBase.createInstance(prop); @@ -77,25 +68,7 @@ export class PKPSuiWallet implements PKPWallet, Signer { const digest = blake2b(data, { dkLen: 32 }); const msgHash = sha256(digest); const signature = await this.runSign(msgHash); - const numToNByteStr = (num: number | bigint): string => - bytesToHex(numberToBytesBE(num, secp256k1.CURVE.nByteLength)); - - // TODO response from PKPBase.runSign has this values defined as strings - const compactHex = - (typeof signature.r === 'string' - ? signature.r - : numToNByteStr(signature.r)) + - (typeof signature.s === 'string' - ? signature.s - : numToNByteStr(signature.s)); - const compactRawBytes = hexToBytes(compactHex); - - const result = toSerializedSignature({ - signature: compactRawBytes, - signatureScheme: 'Secp256k1', - pubKey: this.publicKey, - }); - return result; + return signature.signature; } connect(provider: JsonRpcProvider): PKPSuiWallet { @@ -314,11 +287,14 @@ export class PKPSuiWallet implements PKPWallet, Signer { * @param {Uint8Array} toSign - The data to be signed by the Lit action. * @param {string} sigName - The name of the signature to be returned by the Lit action. * - * @returns {Promise} - A Promise that resolves with the signature returned by the Lit action. + * @returns {Promise} - A Promise that resolves with the signature returned by the Lit action. * * @throws {Error} - Throws an error if `pkpPubKey` is not provided, if `controllerAuthSig` or `controllerSessionSigs` is not provided, if `controllerSessionSigs` is not an object, if `executeJsArgs` does not have either `code` or `ipfsId`, or if an error occurs during the execution of the Lit action. */ - async runLitAction(toSign: Uint8Array, sigName: string): Promise { + async runLitAction( + toSign: Uint8Array, + sigName: string + ): Promise { return this.pkpBase.runLitAction(toSign, sigName); } @@ -326,12 +302,16 @@ export class PKPSuiWallet implements PKPWallet, Signer { * Sign the provided data with the PKP private key. * * @param {Uint8Array} toSign - The data to be signed. + * @param {SigType} signingScheme - The signing scheme to use for the signature. Defaults to 'EcdsaK256Sha256'. * - * @returns {Promise} - A Promise that resolves with the signature of the provided data. + * @returns {Promise} - A Promise that resolves with the lit signature of the provided data. * * @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 { - return this.pkpBase.runSign(toSign); + async runSign( + toSign: Uint8Array, + signingScheme: SigType = 'EcdsaK256Sha256' + ): Promise { + return this.pkpBase.runSign(toSign, signingScheme); } } diff --git a/packages/types/src/lib/EndpointResponses.ts b/packages/types/src/lib/EndpointResponses.ts index 5d0d83a08..eb021da7a 100644 --- a/packages/types/src/lib/EndpointResponses.ts +++ b/packages/types/src/lib/EndpointResponses.ts @@ -1,67 +1,214 @@ -export type SigType = - | 'BLS' - | 'K256' - | 'ECDSA_CAIT_SITH' // Legacy alias of K256 - | 'EcdsaCaitSithP256' - | 'EcdsaK256Sha256'; +import { + // BlsSigType + EcdsaSigType, + FrostSigType, +} from '@lit-protocol/constants'; + +import { Hex } from './types'; + +// See src/p2p_comms/web/models.rs > BlsSignedMessageShare +// Example output: +// "BlsSignedMessageShare": { +// "message": "0102030405", +// "result": "success", +// "peer_id": "5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a", +// "share_id": "\"19a7c43a2b7bbedcea0a40ab17fe0f4a1acf31bdecb9ebeb96c1d3a62e4885f0\"", +// "signature_share": "{\"ProofOfPossession\":{\"identifier\":\"f085482ea6d3c196ebebb9ecbd31cf1a4a0ffe17ab400aeadcbe7b2b3ac4a719\",\"value\":\"8a56ee7b1f7c1eb93e1ccfa2ec02c0f344dcbb66d3cb0742ceaad2aa655da431575b70635db1aa6208061ebdc64442e108c6ae49eb996d72f590ac99d4edda180cb4ef4610bf58b00f75910fda6670bd58eb9b4397f38c8ea5886d9914cb2d24\"}}", +// "verifying_share": "{\"identifier\":\"f085482ea6d3c196ebebb9ecbd31cf1a4a0ffe17ab400aeadcbe7b2b3ac4a719\",\"value\":\"911725a46083ac660d283be18965f2fc3c3f817272b8499c4b46477e868a2d515d670f4fb89cb837bc1cd0dc7c00655b\"}", +// "public_key": "\"8fb7104e7fcfae43b77646d6ade34b116c7a69aa53cba75167e267fff36150727dd1064ca477b6cd763f8382c737a35d\"", +// "sig_type": "Bls12381G1ProofOfPossession" +// } +// Notice how some values are double quoted, and some are not. We need to clean this up. +// export interface BlsSignedMessageShareRaw { +// message: string; +// peer_id: string; +// public_key: string; +// result: string; +// share_id: string; +// sig_type: string; +// signature_share: string; +// verifying_share: string; +// } // See src/p2p_comms/web/models.rs > EcdsaSignedMessageShare // Example output: // "EcdsaSignedMessageShare": { -// "digest": "7d87c5ea75f7378bb701e404c50639161af3eff66293e9f375b5f17eb50476f4", +// "digest": "74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0", // "result": "success", -// "share_id": "\"1A0369823607C6EF403D86BA41534DDB1420730C696060EAD7931DE5DB603937\"", -// "signature_share": "\"034ABD450B174E9627E904651F172EDEC02C09409C40394D9234334F5630110B\"", -// "big_r": "\"02C9E772791F423556F9D3E8852EB66D522C8161C71CC0771B3DD4A3F0F120851E\"", -// "compressed_public_key": "\"0381ff5b9f673837eacd4dca7e9377084250dccfc13ebf13913e662182027d1482\"", -// "public_key": "\"0481ff5b9f673837eacd4dca7e9377084250dccfc13ebf13913e662182027d148243a12fd2835de355660b1b21abdf42efe47cd2871ed9df15a055e67ac2adae43\"", +// "share_id": "\"989DD924B8821903330AC0801F99EB27E3E5235EE299B2A06A611780EC0C7AE1\"", +// "peer_id": "5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a", +// "signature_share": "\"4A862E429D8D45F693261D7A7A2003D628C172A3562F8546CD6A4E77B1C3471B\"", +// "big_r": "\"037AAA9E30F118821CE3831B8218BF6DD0B61AB9B12D64980201E8B396C8168356\"", +// "compressed_public_key": "\"021b11247309045cfa640cae5f75acae6b5b4e79f4990719569fcddbd026918fce\"", +// "public_key": "\"041b11247309045cfa640cae5f75acae6b5b4e79f4990719569fcddbd026918fce13c041bb974c7ba06c78a4454babde69ddf30972ceccde8c9707f997be24cc80\"", // "sig_type": "EcdsaK256Sha256" // } // Notice how some values are double quoted, and some are not. We need to clean this up. export interface EcdsaSignedMessageShareRaw { + big_r: string; + compressed_public_key: string; digest: string; + peer_id: string; + public_key: string; result: string; share_id: string; + sig_type: string; signature_share: string; - big_r: string; - compressed_public_key: string; +} + +// See src/p2p_comms/web/models.rs > FrostSignedMessageShare +// Example output: +// "FrostSignedMessageShare": { +// "message": "0102030405", +// "result": "success", +// "share_id": "[\"Ed25519Sha512\",[120,2,187,138,101,21,192,99,172,206,182,184,45,83,216,198,184,93,183,55,83,34,185,90,150,221,88,228,91,232,123,2]]", +// "peer_id": "6555c8c26671f5e21611adba0a3c31b28128443f2d76c2818db169efcff38151", +// "signature_share": "[\"Ed25519Sha512\",\"c5903ed6a791874dbb17dc5971a8fc1ce46aee0c1f0fa1decf496eae1a87d10b\"]", +// "signing_commitments": "[\"Ed25519Sha512\",\"00b169f0da1b70b3886efed232a7965609b5bf485d8d2bc5c2aa529f63f9493ce6de97543247f4730e1fef2e4991d2579ab4b100b72a476c3e77ac6c7808df3e975eba913f\"]", +// "verifying_share": "[\"Ed25519Sha512\",\"8f548f118988ef7b27789b60b627df91a50b9c8c522d9a628d89417fc8219842\"]", +// "public_key": "[\"Ed25519Sha512\",\"87a64cc4fd848d173619bf5c4af16fd14920e8e3d04b3af03091a707e16f85d4\"]", +// "sig_type": "SchnorrEd25519Sha512" +// } +// Notice how some values are double quoted, and some are not. We need to clean this up. +export interface FrostSignedMessageShareRaw { + message: string; + peer_id: string; public_key: string; + result: string; + share_id: string; sig_type: string; + signature_share: string; + signing_commitments: string; + verifying_share: string; } +type SignatureShare = + // | { + // BlsSignedMessageShare: BlsSignedMessageShareRaw; + // } + | { + EcdsaSignedMessageShare: EcdsaSignedMessageShareRaw; + } + | { + FrostSignedMessageShare: FrostSignedMessageShareRaw; + }; + /** * This is what the /web/pkp/sign endpoint returns */ export interface PKPSignEndpointResponse { success: boolean; - signedData: Uint8Array; - signatureShare: { - EcdsaSignedMessageShare: EcdsaSignedMessageShareRaw; - }; + signedData: number[]; // Convertible to Uint8Array + signatureShare: SignatureShare; +} + +export interface LitActionClaimData { + signature: string; + derivedKeyId: string; } +export interface LitActionSignedData { + publicKey: string; + signatureShare: string; // JSON.stringify(SignatureShare) + sigName: string; + sigType: EcdsaSigType; +} + +/** + * This is what the /web/execute/v2 endpoint returns + */ +export interface ExecuteJsValueResponse { + claimData: Record; + decryptedData: any; // TODO check + logs: string; + response: string; + signedData: Record; + success: boolean; +} + +/** + * This is the cleaned up version of the BlsSignedMessageShareRaw + * + * @example + * { + * "message": "0x0102030405", + * "peerId": "0x5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a", + * "shareId": Uint8Array(...), + * "signatureShare": "{ProofOfPossession:{identifier:f085482ea6d3c196ebebb9ecbd31cf1a4a0ffe17ab400aeadcbe7b2b3ac4a719,value:8a56ee7b1f7c1eb93e1ccfa2ec02c0f344dcbb66d3cb0742ceaad2aa655da431575b70635db1aa6208061ebdc64442e108c6ae49eb996d72f590ac99d4edda180cb4ef4610bf58b00f75910fda6670bd58eb9b4397f38c8ea5886d9914cb2d24}}", + * "verifyingShare": "{identifier:f085482ea6d3c196ebebb9ecbd31cf1a4a0ffe17ab400aeadcbe7b2b3ac4a719,value:911725a46083ac660d283be18965f2fc3c3f817272b8499c4b46477e868a2d515d670f4fb89cb837bc1cd0dc7c00655b}", + * "publicKey": "0x8fb7104e7fcfae43b77646d6ade34b116c7a69aa53cba75167e267fff36150727dd1064ca477b6cd763f8382c737a35d", + * "sigType": "Bls12381G1ProofOfPossession", + * "dataSigned": "0x0102030405" + * } + */ +// export interface BlsSignedMessageShareParsed { +// dataSigned: Hex; +// message: Hex; +// peerId: Hex; +// publicKey: Hex; +// shareId: Uint8Array; +// signatureShare: Hex; +// signingCommitments: Hex; +// sigType: BlsSigType; +// verifyingShare: Hex; +// } + /** * This is the cleaned up version of the EcdsaSignedMessageShareRaw * * @example - * { - * "digest": "7d87c5ea75f7378bb701e404c50639161af3eff66293e9f375b5f17eb50476f4", - * "shareId": "1A0369823607C6EF403D86BA41534DDB1420730C696060EAD7931DE5DB603937", - * "signatureShare": "6F103C0E9632E39CE4BEB3CEE162E2E1E5514CC6D8B5F5700E9B88DDE91A7AB0", - * "bigR": "0295635836AED7FDE834F5B835B2D3500070FDF22174A717C91D5375C6EFDDE167", - * "compressedPublicKey": "021b922522df1c30b64f0bc53554fd2be50fe75287574f273fd944122c54518c85", - * "publicKey": "041b922522df1c30b64f0bc53554fd2be50fe75287574f273fd944122c54518c850768f5eb6e7c9aeef54e07c89df578ace291f58a34bbe32187d60cb12882343a", - * "sigType": "EcdsaK256Sha256", - * "dataSigned": "7d87c5ea75f7378bb701e404c50639161af3eff66293e9f375b5f17eb50476f4" + * { + * "digest": "0x74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0", + * "shareId": "0x989DD924B8821903330AC0801F99EB27E3E5235EE299B2A06A611780EC0C7AE1", + * "peerId": "0x5d6549a90c835b672953dec25b20f278de72b5a47019c74a2e4e8207e01b684a", + * "signatureShare": "0x4A862E429D8D45F693261D7A7A2003D628C172A3562F8546CD6A4E77B1C3471B", + * "bigR": "0x037AAA9E30F118821CE3831B8218BF6DD0B61AB9B12D64980201E8B396C8168356", + * "compressedPublicKey": "0x021b11247309045cfa640cae5f75acae6b5b4e79f4990719569fcddbd026918fce", + * "publicKey": "0x041b11247309045cfa640cae5f75acae6b5b4e79f4990719569fcddbd026918fce13c041bb974c7ba06c78a4454babde69ddf30972ceccde8c9707f997be24cc80", + * "sigType": "EcdsaK256Sha256", + * "dataSigned": "0x74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0" * } */ export interface EcdsaSignedMessageShareParsed { - digest?: string; - shareId?: string; - signatureShare: string; - bigR: string; - compressedPublicKey?: string; - publicKey: string; - sigType: SigType; - dataSigned: string; + bigR: Hex; + compressedPublicKey: Hex; + dataSigned: Hex; + digest: Hex; + peerId: Hex; + publicKey: Hex; + shareId: Hex; + signatureShare: Hex; + sigType: EcdsaSigType; +} + +/** + * This is the cleaned up version of the FrostSignedMessageShareRaw + * + * @example + * { + * "message": "0x0102030405", + * "shareId": Uint8Array(...), + * "peerId": "0x6555c8c26671f5e21611adba0a3c31b28128443f2d76c2818db169efcff38151", + * "signatureShare": "0xc5903ed6a791874dbb17dc5971a8fc1ce46aee0c1f0fa1decf496eae1a87d10b", + * "signingCommitments": "0x00b169f0da1b70b3886efed232a7965609b5bf485d8d2bc5c2aa529f63f9493ce6de97543247f4730e1fef2e4991d2579ab4b100b72a476c3e77ac6c7808df3e975eba913f", + * "verifyingShare": "0x8f548f118988ef7b27789b60b627df91a50b9c8c522d9a628d89417fc8219842", + * "publicKey": "0x87a64cc4fd848d173619bf5c4af16fd14920e8e3d04b3af03091a707e16f85d4", + * "sigType": "SchnorrEd25519Sha512", + * "dataSigned": "0x0102030405" + * } + */ +export interface FrostSignedMessageShareParsed { + dataSigned: Hex; + message: Hex; + peerId: Hex; + publicKey: Hex; + shareId: Uint8Array; + signatureShare: Hex; + signingCommitments: Hex; + sigType: FrostSigType; + verifyingShare: Hex; } + +export type PKPSignEndpointSharesParsed = + // | BlsSignedMessageShareParsed + EcdsaSignedMessageShareParsed | FrostSignedMessageShareParsed; diff --git a/packages/types/src/lib/ILitNodeClient.ts b/packages/types/src/lib/ILitNodeClient.ts index bb92650d2..28f5ed681 100644 --- a/packages/types/src/lib/ILitNodeClient.ts +++ b/packages/types/src/lib/ILitNodeClient.ts @@ -1,5 +1,3 @@ -import { PRODUCT_IDS } from '@lit-protocol/constants'; - import { AuthenticationContext, CapacityCreditsReq, @@ -14,10 +12,14 @@ import { JsonHandshakeResponse, JsonPkpSignSdkParams, LitNodeClientConfig, - SigResponse, + LitNodeSignature, } from './interfaces'; import { ClaimProcessor, ClaimRequest } from './types'; +// keyof typeof @lit-protocol/constants -> PRODUCT_IDS. Importing creates a circular reference +export type PRODUCT_IDS_TYPE = 'DECRYPTION' | 'SIGN' | 'LIT_ACTION'; +export type PRODUCT_IDS_VALUES = 0 | 1 | 2; + export interface ILitNodeClient { config: LitNodeClientConfig; connectedNodes: Set; @@ -42,7 +44,7 @@ export interface ILitNodeClient { * @param product - The product type to set the max price for * @param price - The max price to set */ - setDefaultMaxPrice(product: keyof typeof PRODUCT_IDS, price: bigint): void; + setDefaultMaxPrice(product: PRODUCT_IDS_TYPE, price: bigint): void; /** * Get PKP authentication context @@ -57,7 +59,7 @@ export interface ILitNodeClient { */ getMaxPricesForNodeProduct(params: { userMaxPrice?: bigint; - product: keyof typeof PRODUCT_IDS; + product: PRODUCT_IDS_TYPE; }): Promise<{ url: string; price: bigint }[]>; /** @@ -81,11 +83,11 @@ export interface ILitNodeClient { * Sign using PKP * @param params - PKP signing parameters */ - pkpSign(params: JsonPkpSignSdkParams): Promise; + pkpSign(params: JsonPkpSignSdkParams): Promise; /** * Execute JS on the nodes and combine and return any resulting signatures - * @param { ExecuteJsRequest } params + * @param { JsonExecutionSdkParams } params * @returns { ExecuteJsResponse } */ executeJs( diff --git a/packages/types/src/lib/interfaces.ts b/packages/types/src/lib/interfaces.ts index 19978bd31..f3363b157 100644 --- a/packages/types/src/lib/interfaces.ts +++ b/packages/types/src/lib/interfaces.ts @@ -1,7 +1,7 @@ import { Provider } from '@ethersproject/abstract-provider'; -import depd from 'depd'; -import { SigType } from './EndpointResponses'; +import { SigType } from '@lit-protocol/constants'; + import { ILitNodeClient } from './ILitNodeClient'; import { ISessionCapabilityObject, LitResourceAbilityRequest } from './models'; import { @@ -9,6 +9,7 @@ import { AccessControlConditions, Chain, EvmContractConditions, + Hex, IRelayAuthStatus, JsonRequest, LIT_NETWORKS_KEYS, @@ -18,7 +19,6 @@ import { SolRpcConditions, UnifiedAccessControlConditions, } from './types'; -const deprecated = depd('lit-js-sdk:types:interfaces'); export interface AccsOperatorParams { operator: string; @@ -162,28 +162,22 @@ export interface ClaimKeyResponse { mintTx: string; } -/** - * Struct in rust - * ----- -pub struct JsonExecutionRequest { - pub auth_sig: AuthSigItem, - #[serde(default = "default_epoch")] - pub epoch: u64, +export interface Node { + socketAddress: string; + value: number; +} - pub ipfs_id: Option, - pub code: Option, - pub js_params: Option, - pub auth_methods: Option>, +export interface BaseJsonPkpSignRequest { + signingScheme: SigType; } - */ /** * The 'pkpSign' function param. Please note that the structure - * is different than the payload sent to the node. + * is different from the payload sent to the node. */ -export interface JsonPkpSignSdkParams { +export interface JsonPkpSignSdkParams extends BaseJsonPkpSignRequest { pubKey: string; - toSign: ArrayLike; + messageToSign: Uint8Array; authContext: AuthenticationContext; userMaxPrice?: bigint; } @@ -191,17 +185,17 @@ export interface JsonPkpSignSdkParams { /** * The actual payload structure sent to the node /pkp/sign endpoint. */ -export interface JsonPkpSignRequest extends NodeSetRequired { - toSign: ArrayLike; - authMethods?: AuthMethod[]; +export interface JsonPkpSignRequest + extends BaseJsonPkpSignRequest, + NodeSetRequired { authSig: AuthSig; + authMethods?: AuthMethod[]; + toSign: ArrayLike; /** * note that 'key' is in lower case, because this is what the node expects */ pubkey: string; - - signingScheme: T; } /** @@ -388,27 +382,6 @@ export interface JsonSigningRetrieveRequest extends JsonAccsRequest { sessionSigs?: SessionSigsMap; } -/** - * Struct in rust - * ----- -pub struct JsonSigningStoreRequest { - pub key: String, - pub val: String, - pub chain: Option, - pub permanant: Option, - pub auth_sig: AuthSigItem, -} - */ -export interface JsonSigningStoreRequest { - key: string; - val: string; - chain?: string; - permanant?: 0 | 1; - permanent?: 0 | 1; - authSig?: AuthSig; - sessionSigs?: SessionSigsMap; -} - /** * Struct in rust * ----- @@ -429,9 +402,7 @@ export interface JsonEncryptionRetrieveRequest extends JsonAccsRequest { export interface LitActionResponseStrategy { strategy: ResponseStrategy; - customFilter?: ( - responses: Record[] - ) => Record; + customFilter?: (responses: T[]) => T; } export interface IpfsOptions { @@ -545,26 +516,8 @@ export interface DecryptResponse { decryptedData: Uint8Array; } -export interface GetSigningShareForDecryptionRequest extends JsonAccsRequest { - dataToEncryptHash: string; -} - -export interface SigResponse { - r: string; - s: string; - recid: number; - signature: `0x${string}`; - publicKey: string; // pkp public key (no 0x prefix) - dataSigned: string; -} - export interface ExecuteJsResponseBase { - signatures: - | { - sig: SigResponse; - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - | any; + signatures: Record; } /** @@ -600,33 +553,6 @@ export interface ExecuteJsNoSigningResponse extends ExecuteJsResponseBase { logs: string; } -export interface SigShare { - sigType: SigType; - signatureShare: string; - bigR?: string; - publicKey: string; - dataSigned?: string | 'fail'; - siweMessage?: string; - sigName?: string; -} - -export interface NodeShare { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - claimData: any; - - // I think this is deprecated - // eslint-disable-next-line @typescript-eslint/no-explicit-any - unsignedJwt: any; - signedData: SigShare; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - decryptedData: any; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - response: any; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - logs: any; - success?: boolean | ''; -} - export interface NodeBlsSigningShare { // eslint-disable-next-line @typescript-eslint/no-explicit-any unsignedJwt?: any; @@ -704,6 +630,68 @@ export interface FormattedMultipleAccs { formattedUnifiedAccessControlConditions: any; } +// BLS +// ================== unifiedSignature: { +// "signature":"{\"ProofOfPossession\":\"abff2ed7e23d0e7c01880e76fd66c84b23427842447ec5474a842ca729d5f21b675342aac3fe94386ffd02c6726b14740c4571efcf45fb86342fd806b6557bcfb32c225f006d7870eea0980810c26606779667786c1a875d563b5731c65da21f\"}", +// "verifying_key":"\"af93eb21b135d6a95d46f03662168f34e0ea8a749d0579ac46aa7aff721c1259991997c0598aad6838678a2f17dd85e2\"", +// "signed_data":"0102030405", +// "recovery_id":null} +// ECDSA +// ================== unifiedSignature: { +// "signature":"\"F0E7A72197D93FFF7A5A22B2A865511C6A117DE75E08519A2298061485169A2B6EE44425E45AD3A7455720C5D786EFFFBB24D94DD4E8F2143CDBF777BBCA6FE1\"", +// "verifying_key":"\"3056301006072A8648CE3D020106052B8104000A0342000438629EBC85B2C8E06C90D8BFAAC45BAA6706E6C6F7AF318DD569FA273DBDC2D5935590F819D6B8EB23DE5B2799F4F3106F4D55F1EA873292FC282ADCC5FA7F4F\"", +// "signed_data":"74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0", +// "recovery_id":1} +// FROST +// ================== unifiedSignature: { +// "signature":"[\"K256Sha256\",\"036e6bf0d688202166c8086b40b72306514566d314ec6209a09ddf5be62c4ff1003ce0fbc2afd715261b0d3cbba216ca2e0ccb46447f63015345194fe48101a3df\"]", +// "verifying_key":"[\"K256Sha256\",\"0374f4c61cf60ba3a3dc31cac8d2cc502803ff83f8fe7e24d7081881e938e44ff3\"]", +// "signed_data":"74f81fe167d99b4cb41d6d0ccda82278caee9f3e2f25d5e5a3936ff3dcec60d0", +// "recovery_id":null} +export interface CombinedLitNodeSignature { + signature: string; + verifying_key: string; + signed_data: string; + recovery_id: number | null; +} + +export interface CleanLitNodeSignature { + signature: Hex; + verifyingKey: Hex; + signedData: Hex; + recoveryId: 0 | 1 | null; +} + +export interface LitNodeSignature extends CleanLitNodeSignature { + publicKey: Hex; + sigType: SigType; +} + +export interface WalletEncryptedPayload { + V1: { + verification_key: string; + ciphertext_and_tag: string; + session_signature: string; + random: string; + created_at: string; + }; +} + +export interface WalletEncryptedPayload { + V1: { + verification_key: string; + ciphertext_and_tag: string; + session_signature: string; + random: string; + created_at: string; + } +} + +export interface HandshakeWithNode { + url: string; + challenge: string; +} + export interface NodeAttestation { type: string; noonce: string; @@ -979,9 +967,11 @@ export interface RPCUrls { export interface PKPWallet { getAddress: () => Promise; init: () => Promise; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - runLitAction: (toSign: Uint8Array, sigName: string) => Promise; - runSign: (toSign: Uint8Array) => Promise; + runLitAction: ( + toSign: Uint8Array, + sigName: string + ) => 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 f72cf3393..68a42fbd8 100644 --- a/packages/types/src/lib/types.ts +++ b/packages/types/src/lib/types.ts @@ -18,6 +18,8 @@ import { JsonSigningRetrieveRequest, } from './interfaces'; +export type Hex = `0x${string}`; + export type ConditionType = 'solRpc' | 'evmBasic' | 'evmContract' | 'cosmos'; export type AccsDefaultParams = LPACC_EVM_BASIC; diff --git a/packages/wasm/rust/Cargo.toml b/packages/wasm/rust/Cargo.toml index e08242239..41fd74edc 100644 --- a/packages/wasm/rust/Cargo.toml +++ b/packages/wasm/rust/Cargo.toml @@ -11,29 +11,32 @@ repository = "https://github.com/LIT-Protocol/js-sdk" crate-type = ["cdylib", "rlib"] [features] +default = ["verify-only"] +verify-only = ["lit-frost/verify_only"] +test-shares = ["lit-frost/default", "vsss-rs"] [dependencies] wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } -blsful = { version = "3.0.0-pre8", default-features = false, features = ["rust"] } -base64_light = "0.1" -getrandom = { version = "0.2", features = ["js"] } +blsful = { version = "3.0.0-pre8", default-features = false, features = ["rust"] } +base64_light = "0.1.5" +ecdsa = "0.16.9" +generic-array = "1.1.1" +lit-frost = { git = "https://github.com/LIT-Protocol/lit-frost.git" } hex = "0.4" hd-keys-curves-wasm = { version = "1.0.1", default-features = false, features = ["k256", "p256"] } lit-bls-wasm = { git = "https://github.com/LIT-Protocol/lit-bls-wasm" } serde = "1.0" serde_json = "1.0" -serde_bare = "0.5" serde-wasm-bindgen = "0.6" elliptic-curve = "0.13" -k256 = { version = "0.13", features = ["arithmetic"] } -p256 = { version = "0.13", features = ["arithmetic"] } +elliptic-curve-tools = "0.1.2" +k256 = { version = "0.13", features = ["arithmetic", "serde"] } +p256 = { version = "0.13", features = ["arithmetic", "serde"] } +p384 = { version = "0.13", features = ["arithmetic", "serde"] } sha2 = "0.10" +vsss-rs = { version = "5.1.0", optional = true } -wee_alloc = { version = "0.4.5", optional = true } - -console_error_panic_hook = { version = "0.1.7", optional = true } -wasm-bindgen-futures = "0.4.40" js-sys = "0.3.67" sev = { version = "2.0.2", default-features = false, features = [ @@ -43,17 +46,11 @@ sev = { version = "2.0.2", default-features = false, features = [ rand = "0.8" serde_bytes = "0.11.14" tsify = { version = "0.4.5", default-features = false, features = ["js"] } -jubjub-plus = { version = "0.10.4" } -web-sys = { version = "0.3", features = ["console"] } [dev-dependencies] -wasm-bindgen-test = "0.3.34" -ciborium = "0.2" k256 = "0.13" rand = "0.8" -rand_chacha = "0.3" -digest = "0.10" [profile.release] opt-level = "z" diff --git a/packages/wasm/rust/src/bls.rs b/packages/wasm/rust/src/bls.rs index 238c68c88..b248c981a 100644 --- a/packages/wasm/rust/src/bls.rs +++ b/packages/wasm/rust/src/bls.rs @@ -1,14 +1,11 @@ -use wasm_bindgen::prelude::*; +use base64_light::{base64_decode, base64_encode_bytes}; use js_sys::Uint8Array; -use serde::{ Deserialize }; -use base64_light::{ base64_decode, base64_encode_bytes }; -use tsify::Tsify; use lit_bls_wasm::{ - encrypt, - decrypt_with_signature_shares, - combine_signature_shares, - verify_signature, + combine_signature_shares, decrypt_with_signature_shares, encrypt, verify_signature, }; +use serde::Deserialize; +use tsify::Tsify; +use wasm_bindgen::prelude::*; type JsResult = Result; @@ -17,15 +14,14 @@ type JsResult = Result; // ----------------------------------------------------------------------- #[wasm_bindgen(js_name = "blsCombine")] pub fn bls_combine(signature_shares: JsValue) -> Result { - let shares: Vec = serde_wasm_bindgen - ::from_value(signature_shares) - .map_err(|e| format!("Failed to parse shares: {}", e))?; + let shares: Vec = serde_wasm_bindgen::from_value(signature_shares) + .map_err(|e| format!("Failed to parse shares: {}", e))?; - let combined_signature = combine_signature_shares( - serde_wasm_bindgen::to_value(&shares).unwrap() - ).map_err(|e| format!("Failed to combine signature shares: {}", e))?; + let combined_signature = + combine_signature_shares(serde_wasm_bindgen::to_value(&shares).unwrap()) + .map_err(|e| format!("Failed to combine signature shares: {}", e))?; - Ok(combined_signature) + Ok(combined_signature) } // ----------------------------------------------------------------------- @@ -33,40 +29,36 @@ pub fn bls_combine(signature_shares: JsValue) -> Result { // ----------------------------------------------------------------------- #[wasm_bindgen(js_name = "blsVerify")] pub fn bls_verify( - public_key: Uint8Array, // buffer, but will be converted to hex string - message: Uint8Array, // buffer, but will be converted to hex string - signature: String // this is the result from bls_combine. It's a hex string + public_key: Uint8Array, // buffer, but will be converted to hex string + message: Uint8Array, // buffer, but will be converted to hex string + signature: String, // this is the result from bls_combine. It's a hex string ) -> JsResult<()> { - // check if signature is a valid hex string - if !signature.chars().all(|c| c.is_ascii_hexdigit()) { - return Err(JsValue::from_str("Signature must be a hex string")); - } - // convert public_key to hex string - let public_key_hex = hex::encode(public_key.to_vec()); - - // convert message to base64 string - let message_base64 = base64_encode_bytes(&message.to_vec()); - - // Validate all inputs are hex - if !public_key_hex.chars().all(|c| c.is_ascii_hexdigit()) { - return Err(JsValue::from_str("Public key must be a hex string")); - } - - if !signature.chars().all(|c| c.is_ascii_hexdigit()) { - return Err(JsValue::from_str("Signature must be a hex string")); - } - - let signature_bytes = hex - ::decode(&signature) - .map_err(|e| - JsValue::from_str(&format!("Failed to decode signature hex: {}", e)) - )?; - - let signature_base64 = base64_encode_bytes(&signature_bytes); - - verify_signature(&public_key_hex, &message_base64, &signature_base64).map_err( - |e| JsValue::from_str(&format!("Verification failed: {}", e)) - ) + // check if signature is a valid hex string + if !signature.chars().all(|c| c.is_ascii_hexdigit()) { + return Err(JsValue::from_str("Signature must be a hex string")); + } + // convert public_key to hex string + let public_key_hex = hex::encode(public_key.to_vec()); + + // convert message to base64 string + let message_base64 = base64_encode_bytes(&message.to_vec()); + + // Validate all inputs are hex + if !public_key_hex.chars().all(|c| c.is_ascii_hexdigit()) { + return Err(JsValue::from_str("Public key must be a hex string")); + } + + if !signature.chars().all(|c| c.is_ascii_hexdigit()) { + return Err(JsValue::from_str("Signature must be a hex string")); + } + + let signature_bytes = hex::decode(&signature) + .map_err(|e| JsValue::from_str(&format!("Failed to decode signature hex: {}", e)))?; + + let signature_base64 = base64_encode_bytes(&signature_bytes); + + verify_signature(&public_key_hex, &message_base64, &signature_base64) + .map_err(|e| JsValue::from_str(&format!("Verification failed: {}", e))) } // ----------------------------------------------------------------------- @@ -74,23 +66,20 @@ pub fn bls_verify( // ----------------------------------------------------------------------- #[wasm_bindgen(js_name = "blsEncrypt")] pub fn bls_encrypt( - encryption_key: Uint8Array, - message: Uint8Array, - identity: Uint8Array + encryption_key: Uint8Array, + message: Uint8Array, + identity: Uint8Array, ) -> JsResult { - let encryption_key_hex = hex::encode(encryption_key.to_vec()); - let message_base64 = base64_encode_bytes(&message.to_vec()); - let identity_base64 = base64_encode_bytes(&identity.to_vec()); + let encryption_key_hex = hex::encode(encryption_key.to_vec()); + let message_base64 = base64_encode_bytes(&message.to_vec()); + let identity_base64 = base64_encode_bytes(&identity.to_vec()); - let ciphertext = encrypt( - &encryption_key_hex, - &message_base64, - &identity_base64 - ).map_err(|e| JsValue::from_str(&format!("Encryption failed: {}", e)))?; + let ciphertext = encrypt(&encryption_key_hex, &message_base64, &identity_base64) + .map_err(|e| JsValue::from_str(&format!("Encryption failed: {}", e)))?; - let decoded_ciphertext = base64_decode(&ciphertext); + let decoded_ciphertext = base64_decode(&ciphertext); - Ok(Uint8Array::from(decoded_ciphertext.as_slice())) + Ok(Uint8Array::from(decoded_ciphertext.as_slice())) } // ----------------------------------------------------------------------- @@ -98,21 +87,21 @@ pub fn bls_encrypt( // ----------------------------------------------------------------------- #[wasm_bindgen(js_name = "blsDecrypt")] pub fn bls_decrypt( - ciphertext: Uint8Array, - signature_shares: JsValue // this is the result from bls_combine. It's a hex string + ciphertext: Uint8Array, + signature_shares: JsValue, // this is the result from bls_combine. It's a hex string ) -> JsResult { - let ciphertext_base64 = base64_encode_bytes(&ciphertext.to_vec()); + let ciphertext_base64 = base64_encode_bytes(&ciphertext.to_vec()); - let shares: Vec = serde_wasm_bindgen - ::from_value(signature_shares) - .map_err(|e| format!("[blsDecrypt] Failed to parse shares: {}", e))?; + let shares: Vec = serde_wasm_bindgen::from_value(signature_shares) + .map_err(|e| format!("[blsDecrypt] Failed to parse shares: {}", e))?; - let plaintext = decrypt_with_signature_shares( - &ciphertext_base64, - serde_wasm_bindgen::to_value(&shares).unwrap() - ).map_err(|e| JsValue::from_str(&format!("Decryption failed: {}", e)))?; + let plaintext = decrypt_with_signature_shares( + &ciphertext_base64, + serde_wasm_bindgen::to_value(&shares).unwrap(), + ) + .map_err(|e| JsValue::from_str(&format!("Decryption failed: {}", e)))?; - let decoded_plaintext = base64_decode(&plaintext); + let decoded_plaintext = base64_decode(&plaintext); - Ok(Uint8Array::from(decoded_plaintext.as_slice())) + Ok(Uint8Array::from(decoded_plaintext.as_slice())) } diff --git a/packages/wasm/rust/src/combine.rs b/packages/wasm/rust/src/combine.rs new file mode 100644 index 000000000..69531e0fd --- /dev/null +++ b/packages/wasm/rust/src/combine.rs @@ -0,0 +1,517 @@ +use ecdsa::{ + hazmat::{DigestPrimitive, VerifyPrimitive}, + signature::hazmat::PrehashVerifier, + RecoveryId, Signature, +}; +use std::ops::Add; + +use crate::abi::JsResult; +use elliptic_curve::{ + generic_array::ArrayLength, + group::{Curve as _, GroupEncoding}, + ops::Reduce, + pkcs8::AssociatedOid, + point::{AffineCoordinates, DecompressPoint, PointCompression}, + sec1::{EncodedPoint, FromEncodedPoint, ModulusSize, ToEncodedPoint}, + Curve, CurveArithmetic, Field, FieldBytesSize, PrimeCurve, ScalarPrimitive, +}; +use elliptic_curve_tools::{group, prime_field}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsError; + +/// Attempts to combine the signature shares. +/// If the resulting combined signature is valid, returns the combined signature. +/// Otherwise, returns an error. +/// +/// It does not distinguish between different types of signatures (e.g., ECDSA, BLS, etc.) +/// and will return the first valid signature it finds in the following order +/// +/// 1. Frost +/// 2. BLS +/// 3. ECDSA +#[wasm_bindgen(js_name = "combineAndVerify")] +pub fn combine_and_verify(signature_shares: Vec) -> JsResult { + let mut de_shares = Vec::with_capacity(signature_shares.len()); + for str_share in &signature_shares { + let share = serde_json::from_str(str_share) + .map_err(|_| JsError::new("invalid signature share format"))?; + de_shares.push(share); + } + let signature = combine_and_verify_signature_shares(&de_shares)?; + serde_json::to_string(&signature).map_err(|_| JsError::new("signature")) +} + +#[derive(Serialize)] +pub struct SignedDataOutput { + signature: String, + verifying_key: String, + signed_data: String, + recovery_id: Option, +} + +#[derive(Deserialize)] +#[cfg_attr(feature = "test-shares", derive(Serialize))] +pub(crate) enum SignableOutput { + EcdsaSignedMessageShare(EcdsaSignedMessageShare), + FrostSignedMessageShare(FrostSignedMessageShare), + BlsSignedMessageShare(BlsSignedMessageShare), +} + +#[derive(Clone, Deserialize)] +#[cfg_attr(feature = "test-shares", derive(Serialize))] +pub(crate) struct EcdsaSignedMessageShare { + pub(crate) digest: String, + pub(crate) result: String, + pub(crate) share_id: String, + pub(crate) peer_id: String, + pub(crate) signature_share: String, + pub(crate) big_r: String, + pub(crate) compressed_public_key: String, + pub(crate) public_key: String, + pub(crate) sig_type: String, +} + +#[derive(Deserialize)] +#[cfg_attr(feature = "test-shares", derive(Serialize))] +pub(crate) struct FrostSignedMessageShare { + pub(crate) message: String, + pub(crate) result: String, + pub(crate) share_id: String, + pub(crate) peer_id: String, + pub(crate) signature_share: String, + pub(crate) signing_commitments: String, + pub(crate) verifying_share: String, + pub(crate) public_key: String, + pub(crate) sig_type: String, +} + +#[derive(Deserialize)] +#[cfg_attr(feature = "test-shares", derive(Serialize))] +pub(crate) struct BlsSignedMessageShare { + pub(crate) message: String, + pub(crate) result: String, + pub(crate) peer_id: String, + pub(crate) share_id: String, + pub(crate) signature_share: String, + pub(crate) verifying_share: String, + pub(crate) public_key: String, + pub(crate) sig_type: String, +} + +/// A signature share +#[derive(Deserialize)] +struct SignatureShare +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive, + C::ProjectivePoint: GroupEncoding, + as Add>::Output: ArrayLength, +{ + /// The signature `r` component + #[serde(with = "group")] + r: C::ProjectivePoint, + /// The signature `s` component + #[serde(with = "prime_field")] + s: C::Scalar, +} + +impl SignatureShare +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive, + C::ProjectivePoint: GroupEncoding, + as Add>::Output: ArrayLength, +{ + /// Combine the signature shares into a signature + /// Verify should be called after wards to check everything + pub fn combine_into_signature(shares: &[SignatureShare]) -> JsResult> { + // Ensure non-empty shares + if shares.is_empty() { + return Err(JsError::new("insufficient signature shares")); + } + // Check that all signature shares have the same r + if shares[1..].iter().any(|s| s.r != shares[0].r) { + return Err(JsError::new("invalid share found")); + } + let sig_s = shares.iter().fold(C::Scalar::ZERO, |acc, s| acc + s.s); + + Ok(FullSignature { + r: shares[0].r, + s: sig_s, + }) + } +} + +#[derive(Serialize)] +struct FullSignature +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive, + C::ProjectivePoint: GroupEncoding, + as Add>::Output: ArrayLength, +{ + /// The signature `r` component + #[serde(with = "group")] + r: C::ProjectivePoint, + /// The signature `s` component + #[serde(with = "prime_field")] + s: C::Scalar, +} + +impl TryFrom> for Signature +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive, + C::ProjectivePoint: GroupEncoding, + as Add>::Output: ArrayLength, +{ + type Error = JsError; + + fn try_from(value: FullSignature) -> JsResult { + let r = x_coordinate::(&value.r); + let r = >>::into(r); + let s = >>::into(value.s); + // from_scalars checks that both r and s are not zero + let signature = Signature::::from_scalars(r.to_bytes(), s.to_bytes()) + .map_err(|_| JsError::new("invalid signature result"))?; + match signature.normalize_s() { + Some(normalized) => Ok(normalized), + None => Ok(signature), + } + } +} + +pub(crate) fn x_coordinate(point: &C::ProjectivePoint) -> C::Scalar +where + C: PrimeCurve + CurveArithmetic, +{ + let pt = point.to_affine(); + ::Uint>>::reduce_bytes(&pt.x()) +} + +/// Attempts to combine the signature shares. +/// If the resulting combined signature is valid, returns the combined signature. +/// Otherwise, returns an error. +/// +/// It does not distinguish between different types of signatures (e.g., ECDSA, BLS, etc.) +/// and will return the first valid signature it finds in the following order +/// +/// 1. Frost +/// 2. BLS +/// 3. ECDSA +fn combine_and_verify_signature_shares( + signature_shares: &[SignableOutput], +) -> JsResult { + let mut bls_signing_package = Vec::with_capacity(signature_shares.len()); + let mut frost_signing_package = Vec::with_capacity(signature_shares.len()); + let mut ecdsa_signing_package = + Vec::::with_capacity(signature_shares.len()); + + for signature_share in signature_shares { + match signature_share { + SignableOutput::EcdsaSignedMessageShare(ecdsa_msg_share) => { + if ecdsa_msg_share.result == "success" { + ecdsa_signing_package.push(ecdsa_msg_share.clone()); + } + } + SignableOutput::BlsSignedMessageShare(bls_msg_share) => { + if bls_msg_share.result == "success" { + let identifier: blsful::inner_types::Scalar = + serde_json::from_str(&bls_msg_share.share_id) + .map_err(|_| JsError::new("bls share id"))?; + let signature_share: blsful::SignatureShare = + serde_json::from_str(&bls_msg_share.signature_share) + .map_err(|_| JsError::new("bls signature share"))?; + let verifying_share: blsful::PublicKeyShare = + serde_json::from_str(&bls_msg_share.verifying_share) + .map_err(|_| JsError::new("bls verifying share"))?; + let public_key: blsful::PublicKey = + serde_json::from_str(&bls_msg_share.public_key) + .map_err(|_| JsError::new("bls public key"))?; + let message = hex::decode(&bls_msg_share.message) + .map_err(|_| JsError::new("bls message"))?; + bls_signing_package.push(( + identifier, + signature_share, + verifying_share, + public_key, + message, + bls_msg_share.peer_id.clone(), + )); + } + } + SignableOutput::FrostSignedMessageShare(frost_msg_share) => { + if frost_msg_share.result == "success" { + let identifier: lit_frost::Identifier = + serde_json::from_str(&frost_msg_share.share_id) + .map_err(|_| JsError::new("frost identifier"))?; + let signature_share: lit_frost::SignatureShare = + serde_json::from_str(&frost_msg_share.signature_share) + .map_err(|_| JsError::new("frost signature share"))?; + let verifying_share: lit_frost::VerifyingShare = + serde_json::from_str(&frost_msg_share.verifying_share) + .map_err(|_| JsError::new("frost verifying share"))?; + let public_key: lit_frost::VerifyingKey = + serde_json::from_str(&frost_msg_share.public_key) + .map_err(|_| JsError::new("frost public key"))?; + let signing_commitments: lit_frost::SigningCommitments = + serde_json::from_str(&frost_msg_share.signing_commitments) + .map_err(|_| JsError::new("frost signing commitments"))?; + let scheme = match frost_msg_share.sig_type.as_str() { + "SchnorrEd25519Sha512" => lit_frost::Scheme::Ed25519Sha512, + "SchnorrK256Sha256" => lit_frost::Scheme::K256Sha256, + "SchnorrP256Sha256" => lit_frost::Scheme::P256Sha256, + "SchnorrP384Sha384" => lit_frost::Scheme::P384Sha384, + "SchnorrRistretto25519Sha512" => lit_frost::Scheme::Ristretto25519Sha512, + "SchnorrEd448Shake256" => lit_frost::Scheme::Ed448Shake256, + "SchnorrRedJubjubBlake2b512" => lit_frost::Scheme::RedJubjubBlake2b512, + "SchnorrK256Taproot" => lit_frost::Scheme::K256Taproot, + "SchnorrRedDecaf377Blake2b512" => lit_frost::Scheme::RedDecaf377Blake2b512, + "SchnorrkelSubstrate" => lit_frost::Scheme::SchnorrkelSubstrate, + _ => return Err(JsError::new("frost signing scheme")), + }; + let message = hex::decode(&frost_msg_share.message) + .map_err(|_| JsError::new("frost message"))?; + frost_signing_package.push(( + identifier, + signature_share, + verifying_share, + public_key, + signing_commitments, + scheme, + message, + frost_msg_share.peer_id.clone(), + )); + } + } + } + } + + if frost_signing_package.len() > 1 { + let first_entry = &frost_signing_package[0]; + let mut signature_shares = Vec::with_capacity(frost_signing_package.len()); + let mut verifying_shares = Vec::with_capacity(frost_signing_package.len()); + let mut signing_commitments = Vec::with_capacity(frost_signing_package.len()); + + signature_shares.push((first_entry.0.clone(), first_entry.1.clone())); + verifying_shares.push((first_entry.0.clone(), first_entry.2.clone())); + signing_commitments.push((first_entry.0.clone(), first_entry.4.clone())); + + for entry in &frost_signing_package[1..] { + debug_assert_eq!( + first_entry.3, entry.3, + "frost public keys do not match: {}, {}", + first_entry.2, entry.2 + ); + debug_assert_eq!( + first_entry.5, entry.5, + "frost signing schemes do not match: {}, {}", + first_entry.4, entry.4 + ); + debug_assert_eq!( + first_entry.6, + entry.6, + "frost messages do not match: {}, {}", + hex::encode(&first_entry.6), + hex::encode(&entry.6) + ); + signature_shares.push((entry.0.clone(), entry.1.clone())); + verifying_shares.push((entry.0.clone(), entry.2.clone())); + signing_commitments.push((entry.0.clone(), entry.4.clone())); + } + let res = first_entry.5.aggregate( + &first_entry.6, + &signing_commitments, + &signature_shares, + &verifying_shares, + &first_entry.3, + ); + if res.is_err() { + let e = res.expect_err("frost signature from shares is invalid"); + match e { + lit_frost::Error::Cheaters(cheaters) => { + let mut cheater_peer_ids = Vec::with_capacity(cheaters.len()); + for cheater in cheaters { + let found = frost_signing_package + .iter() + .find(|p| p.0 == cheater) + .map(|cheater| cheater.7.clone()); + if let Some(peer_id) = found { + cheater_peer_ids.push(peer_id); + } + } + return Err(JsError::new(&format!( + "frost signature from shares is invalid. Invalid share peer ids: {}", + cheater_peer_ids.join(", ") + ))); + } + _ => { + return Err(JsError::new("frost signature from shares is invalid")); + } + } + } else { + return Ok(SignedDataOutput { + signature: serde_json::to_string( + &res.expect("frost signature from shares is valid"), + ) + .map_err(|_| JsError::new("frost signature"))?, + verifying_key: serde_json::to_string(&first_entry.3) + .map_err(|_| JsError::new("frost verifying key"))?, + signed_data: hex::encode(&first_entry.6), + recovery_id: None, + }); + } + } + if bls_signing_package.len() > 1 { + let first_entry = &bls_signing_package[0]; + let mut signature_shares = Vec::with_capacity(bls_signing_package.len()); + let mut verifying_shares = Vec::with_capacity(bls_signing_package.len()); + + signature_shares.push(first_entry.1); + verifying_shares.push((first_entry.0, first_entry.5.clone(), first_entry.2)); + for entry in &bls_signing_package[1..] { + debug_assert_eq!( + first_entry.3, entry.3, + "bls public keys do not match: {}, {}", + first_entry.2, entry.2 + ); + debug_assert_eq!( + first_entry.4, + entry.4, + "bls messages do not match: {}, {}", + hex::encode(&first_entry.4), + hex::encode(&entry.4) + ); + signature_shares.push(entry.1); + verifying_shares.push((entry.0, entry.5.clone(), entry.2)); + } + let public_key = first_entry.3; + let signature = blsful::Signature::::from_shares(&signature_shares) + .expect("bls signature from shares"); + if signature.verify(&public_key, &first_entry.4).is_err() { + // Identify which shares are invalid + let mut invalid_shares = Vec::with_capacity(signature_shares.len()); + for (share, (_identifier, peer_id, verifier)) in + signature_shares.iter().zip(verifying_shares.iter()) + { + if share.verify(verifier, &first_entry.4).is_err() { + invalid_shares.push(peer_id.clone()); + } + } + return Err(JsError::new(&format!( + "bls signature from shares is invalid. Invalid share peer ids: {}", + invalid_shares.join(", ") + ))); + } + return Ok(SignedDataOutput { + signature: serde_json::to_string(&signature) + .map_err(|_| JsError::new("bls signature"))?, + verifying_key: serde_json::to_string(&public_key) + .map_err(|_| JsError::new("bls verifying key"))?, + signed_data: hex::encode(&first_entry.4), + recovery_id: None, + }); + } + if ecdsa_signing_package.len() > 1 { + match ecdsa_signing_package[0].sig_type.as_str() { + "EcdsaK256Sha256" => { + return verify_ecdsa_signing_package::(&ecdsa_signing_package); + } + "EcdsaP256Sha256" => { + return verify_ecdsa_signing_package::(&ecdsa_signing_package); + } + "EcdsaP384Sha384" => { + return verify_ecdsa_signing_package::(&ecdsa_signing_package); + } + _ => {} + } + } + + Err(JsError::new("no valid signature shares found")) +} + +fn verify_ecdsa_signing_package(shares: &[EcdsaSignedMessageShare]) -> JsResult +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive + AssociatedOid + PointCompression, + C::ProjectivePoint: GroupEncoding + CompressedHex, + C::AffinePoint: DeserializeOwned + + FromEncodedPoint + + ToEncodedPoint + + VerifyPrimitive + + DecompressPoint, + C::Scalar: DeserializeOwned, + as Add>::Output: ArrayLength, + ::FieldBytesSize: ModulusSize, +{ + let mut sig_shares = Vec::>::with_capacity(shares.len()); + let first_share = &shares[0]; + sig_shares.push(SignatureShare { + r: C::ProjectivePoint::from( + serde_json::from_str::(&first_share.big_r).expect("r"), + ), + s: serde_json::from_str(&first_share.signature_share).expect("s"), + }); + for share in &shares[1..] { + debug_assert_eq!(first_share.public_key, share.public_key); + debug_assert_eq!(first_share.digest, share.digest); + debug_assert_eq!(first_share.big_r, share.big_r); + debug_assert_eq!(first_share.sig_type, share.sig_type); + + sig_shares.push(SignatureShare { + r: C::ProjectivePoint::from( + serde_json::from_str::(&share.big_r).expect("r"), + ), + s: serde_json::from_str(&share.signature_share).expect("s"), + }); + } + let public_key: String = serde_json::from_str(&first_share.public_key).expect("public key"); + let public_key = ::from_uncompressed_hex(&public_key) + .expect("public key"); + let signature = SignatureShare::::combine_into_signature(&sig_shares).expect("signature"); + + let message = hex::decode(&first_share.digest).expect("message"); + let vk = ecdsa::VerifyingKey::::from_affine(public_key.to_affine()).expect("verifying key"); + let signature: Signature = signature.try_into().expect("signature"); + as PrehashVerifier>>::verify_prehash( + &vk, &message, &signature, + ) + .map_err(|_| JsError::new("ecdsa signature verification failed"))?; + + let digest_bytes = hex::decode(&shares[0].digest).map_err(|_| JsError::new("digest"))?; + let rid = RecoveryId::trial_recovery_from_prehash(&vk, &digest_bytes, &signature) + .map_err(|_| JsError::new("recovery id"))?; + + Ok(SignedDataOutput { + signature: serde_json::to_string(&signature) + .map_err(|_| JsError::new("ecdsa signature"))?, + verifying_key: serde_json::to_string(&vk) + .map_err(|_| JsError::new("ecdsa verifying key"))?, + signed_data: shares[0].digest.clone(), + recovery_id: Some(rid.to_byte()), + }) +} + +trait CompressedHex: Sized { + fn from_uncompressed_hex(hex: &str) -> Option; +} + +impl CompressedHex for k256::ProjectivePoint { + fn from_uncompressed_hex(hex: &str) -> Option { + let bytes = hex::decode(hex).ok()?; + let pt = EncodedPoint::::from_bytes(bytes).ok()?; + Option::from(Self::from_encoded_point(&pt)) + } +} + +impl CompressedHex for p256::ProjectivePoint { + fn from_uncompressed_hex(hex: &str) -> Option { + let bytes = hex::decode(hex).ok()?; + let pt = EncodedPoint::::from_bytes(bytes).ok()?; + Option::from(Self::from_encoded_point(&pt)) + } +} + +impl CompressedHex for p384::ProjectivePoint { + fn from_uncompressed_hex(hex: &str) -> Option { + let bytes = hex::decode(hex).ok()?; + let pt = EncodedPoint::::from_bytes(bytes).ok()?; + Option::from(Self::from_encoded_point(&pt)) + } +} diff --git a/packages/wasm/rust/src/ecdsa.rs b/packages/wasm/rust/src/ecdsa.rs index d89df1b5f..76605f587 100644 --- a/packages/wasm/rust/src/ecdsa.rs +++ b/packages/wasm/rust/src/ecdsa.rs @@ -11,21 +11,12 @@ use elliptic_curve::{ use hd_keys_curves_wasm::{HDDerivable, HDDeriver}; use js_sys::Uint8Array; use k256::Secp256k1; -use p256::NistP256; -use serde::Deserialize; use serde_bytes::Bytes; use tsify::Tsify; use wasm_bindgen::{prelude::*, JsError}; use crate::abi::{from_js, into_js, into_uint8array, JsResult}; -#[derive(Tsify, Deserialize)] -#[tsify(from_wasm_abi)] -pub enum EcdsaVariant { - K256, - P256, -} - struct Ecdsa(C); trait HdCtx { @@ -36,10 +27,6 @@ impl HdCtx for Secp256k1 { const CTX: &'static [u8] = b"LIT_HD_KEY_ID_K256_XMD:SHA-256_SSWU_RO_NUL_"; } -impl HdCtx for NistP256 { - const CTX: &'static [u8] = b"LIT_HD_KEY_ID_P256_XMD:SHA-256_SSWU_RO_NUL_"; -} - #[wasm_bindgen] extern "C" { #[wasm_bindgen(typescript_type = "[Uint8Array, Uint8Array, number]")] @@ -254,93 +241,10 @@ where } } -/// Perform all three functions at once -#[wasm_bindgen(js_name = "ecdsaCombineAndVerifyWithDerivedKey")] -pub fn ecdsa_combine_and_verify_with_derived_key( - variant: EcdsaVariant, - pre_signature: Uint8Array, - signature_shares: Vec, - message_hash: Uint8Array, - id: Uint8Array, - public_keys: Vec, -) -> JsResult { - match variant { - EcdsaVariant::K256 => Ecdsa::::combine_and_verify_with_derived_key( - pre_signature, - signature_shares, - message_hash, - id, - public_keys, - ), - EcdsaVariant::P256 => Ecdsa::::combine_and_verify_with_derived_key( - pre_signature, - signature_shares, - message_hash, - id, - public_keys, - ), - } -} - -/// Perform combine and verify with a specified public key -#[wasm_bindgen(js_name = "ecdsaCombineAndVerify")] -pub fn ecdsa_combine_and_verify( - variant: EcdsaVariant, - pre_signature: Uint8Array, - signature_shares: Vec, - message_hash: Uint8Array, - public_key: Uint8Array, -) -> JsResult { - match variant { - EcdsaVariant::K256 => Ecdsa::::combine_and_verify_with_specified_key( - pre_signature, - signature_shares, - message_hash, - public_key, - ), - EcdsaVariant::P256 => Ecdsa::::combine_and_verify_with_specified_key( - pre_signature, - signature_shares, - message_hash, - public_key, - ), - } -} - -/// Combine ECDSA signatures shares -#[wasm_bindgen(js_name = "ecdsaCombine")] -pub fn ecdsa_combine( - variant: EcdsaVariant, - presignature: Uint8Array, - signature_shares: Vec, -) -> JsResult { - match variant { - EcdsaVariant::K256 => Ecdsa::::combine(presignature, signature_shares), - EcdsaVariant::P256 => Ecdsa::::combine(presignature, signature_shares), - } -} - -#[wasm_bindgen(js_name = "ecdsaVerify")] -pub fn ecdsa_verify( - variant: EcdsaVariant, - message_hash: Uint8Array, - public_key: Uint8Array, - signature: EcdsaSignature, -) -> JsResult<()> { - match variant { - EcdsaVariant::K256 => Ecdsa::::verify(message_hash, public_key, signature), - EcdsaVariant::P256 => Ecdsa::::verify(message_hash, public_key, signature), - } -} - #[wasm_bindgen(js_name = "ecdsaDeriveKey")] pub fn ecdsa_derive_key( - variant: EcdsaVariant, id: Uint8Array, public_keys: Vec, ) -> JsResult { - match variant { - EcdsaVariant::K256 => Ecdsa::::derive_key(id, public_keys), - EcdsaVariant::P256 => Ecdsa::::derive_key(id, public_keys), - } + Ecdsa::::derive_key(id, public_keys) } diff --git a/packages/wasm/rust/src/lib.rs b/packages/wasm/rust/src/lib.rs index 25b63abac..327771ad9 100644 --- a/packages/wasm/rust/src/lib.rs +++ b/packages/wasm/rust/src/lib.rs @@ -1,7 +1,10 @@ pub mod abi; pub mod bls; +mod combine; pub mod ecdsa; pub mod sev_snp; +#[cfg(feature = "test-shares")] +pub mod test; use wasm_bindgen::prelude::*; diff --git a/packages/wasm/rust/src/sev_snp.rs b/packages/wasm/rust/src/sev_snp.rs index b3186b994..71588d86f 100644 --- a/packages/wasm/rust/src/sev_snp.rs +++ b/packages/wasm/rust/src/sev_snp.rs @@ -138,7 +138,7 @@ fn get_expected_report_data( hasher.update(value); } - if signatures.len() > 0 { + if !signatures.is_empty() { hasher.update("signatures"); for idx in 0..((signatures.len() - 1) as usize) { diff --git a/packages/wasm/rust/src/test.rs b/packages/wasm/rust/src/test.rs new file mode 100644 index 000000000..dd255c4bb --- /dev/null +++ b/packages/wasm/rust/src/test.rs @@ -0,0 +1,327 @@ +use crate::abi::JsResult; +use crate::combine::{ + x_coordinate, BlsSignedMessageShare, EcdsaSignedMessageShare, FrostSignedMessageShare, + SignableOutput, +}; +use blsful::SignatureSchemes; +use ecdsa::hazmat::DigestPrimitive; +use elliptic_curve::group::Curve as _; +use elliptic_curve::ops::{Invert, Reduce}; +use elliptic_curve::sec1::{ModulusSize, ToEncodedPoint}; +use elliptic_curve::{Curve, CurveArithmetic, Field, Group, PrimeCurve}; +use sha2::digest::{Digest, FixedOutput}; +use std::collections::BTreeMap; +use std::num::NonZeroU16; +use vsss_rs::{ + shamir, DefaultShare, IdentifierPrimeField, ParticipantIdGeneratorType, ValuePrimeField, +}; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsError; + +/// This will create shares for a given message using the specified signing scheme. +/// The keys will be generated on demand and returned +#[wasm_bindgen(js_name = "createShares")] +pub fn create_shares( + message: String, + signing_scheme: String, + threshold: usize, +) -> JsResult> { + match signing_scheme.as_str() { + "Bls12381" => create_bls_shares(message, signing_scheme, threshold), + "EcdsaK256Sha256" => { + create_ecdsa_shares::(message, signing_scheme, threshold) + } + "EcdsaP256Sha256" => { + create_ecdsa_shares::(message, signing_scheme, threshold) + } + "EcdsaP384Sha384" => { + create_ecdsa_shares::(message, signing_scheme, threshold) + } + "SchnorrEd25519Sha512" => create_frost_shares( + message, + signing_scheme, + threshold, + lit_frost::Scheme::Ed25519Sha512, + ), + "SchnorrK256Sha256" => create_frost_shares( + message, + signing_scheme, + threshold, + lit_frost::Scheme::K256Sha256, + ), + "SchnorrP256Sha256" => create_frost_shares( + message, + signing_scheme, + threshold, + lit_frost::Scheme::P256Sha256, + ), + "SchnorrP384Sha384" => create_frost_shares( + message, + signing_scheme, + threshold, + lit_frost::Scheme::P384Sha384, + ), + "SchnorrRistretto25519Sha512" => create_frost_shares( + message, + signing_scheme, + threshold, + lit_frost::Scheme::Ristretto25519Sha512, + ), + "SchnorrEd448Shake256" => create_frost_shares( + message, + signing_scheme, + threshold, + lit_frost::Scheme::Ed448Shake256, + ), + "SchnorrRedJubjubBlake2b512" => create_frost_shares( + message, + signing_scheme, + threshold, + lit_frost::Scheme::RedJubjubBlake2b512, + ), + "SchnorrK256Taproot" => create_frost_shares( + message, + signing_scheme, + threshold, + lit_frost::Scheme::K256Taproot, + ), + "SchnorrRedDecaf377Blake2b512" => create_frost_shares( + message, + signing_scheme, + threshold, + lit_frost::Scheme::RedDecaf377Blake2b512, + ), + "SchnorrkelSubstrate" => create_frost_shares( + message, + signing_scheme, + threshold, + lit_frost::Scheme::SchnorrkelSubstrate, + ), + "Bls12381G1ProofOfPossession" => create_bls_shares(message, signing_scheme, threshold), + _ => Err(JsError::new("Invalid signing scheme")), + } +} + +fn create_bls_shares( + message: String, + signing_scheme: String, + threshold: usize, +) -> JsResult> { + let sk = blsful::Bls12381G2::new_secret_key(); + let pk = sk.public_key(); + let public_key = serde_json::to_string(&pk)?; + + let shares = sk.split(threshold, threshold)?; + let mut signature_shares = Vec::with_capacity(threshold); + let hex_msg = hex::encode(message.as_bytes()); + for (i, share) in shares.iter().enumerate() { + let verifying_share = share.public_key()?; + let signature = share + .sign(SignatureSchemes::ProofOfPossession, message.as_bytes()) + .map_err(|e| JsError::new(&format!("Error signing message: {:?}", e)))?; + signature_shares.push(serde_json::to_string( + &SignableOutput::BlsSignedMessageShare(BlsSignedMessageShare { + message: hex_msg.clone(), + result: "success".to_string(), + peer_id: (i + 1).to_string(), + share_id: serde_json::to_string(&share.0.identifier).unwrap(), + signature_share: serde_json::to_string(&signature).unwrap(), + verifying_share: serde_json::to_string(&verifying_share).unwrap(), + public_key: public_key.clone(), + sig_type: signing_scheme.clone(), + }), + )?); + } + Ok(signature_shares) +} + +fn create_frost_shares( + message: String, + signing_scheme: String, + threshold: usize, + scheme: lit_frost::Scheme, +) -> JsResult> { + let signers = threshold as u16; + let mut rng = rand::rngs::OsRng; + let (secret_shares, vk) = scheme + .generate_with_trusted_dealer(signers, signers, &mut rng) + .map_err(|e| JsError::new(&format!("Error generating scheme: {:?}", e)))?; + + let verifying_key = serde_json::to_string(&vk)?; + + let mut signing_package = BTreeMap::new(); + let mut signing_commitments = Vec::with_capacity(threshold); + + for (id, secret_share) in &secret_shares { + let res = scheme.signing_round1(&secret_share, &mut rng); + let (nonces, commitments) = res.unwrap(); + signing_package.insert(id.clone(), (nonces, secret_share)); + signing_commitments.push((id.clone(), commitments)); + } + + let mut signature_shares = Vec::with_capacity(threshold); + let hex_msg = hex::encode(message.as_bytes()); + for (i, (id, (nonces, secret_share))) in signing_package.iter().enumerate() { + let res = scheme.signing_round2( + message.as_bytes(), + &signing_commitments, + &nonces, + &lit_frost::KeyPackage { + identifier: id.clone(), + secret_share: (*secret_share).clone(), + verifying_key: vk.clone(), + threshold: NonZeroU16::new(signers).unwrap(), + }, + ); + let signature = res?; + let vks = scheme.verifying_share(&secret_share)?; + signature_shares.push(serde_json::to_string( + &SignableOutput::FrostSignedMessageShare(FrostSignedMessageShare { + message: hex_msg.clone(), + result: "success".to_string(), + share_id: serde_json::to_string(id).unwrap(), + peer_id: (i + 1).to_string(), + signature_share: serde_json::to_string(&signature).unwrap(), + signing_commitments: serde_json::to_string(&signing_commitments[i].1).unwrap(), + verifying_share: serde_json::to_string(&vks).unwrap(), + public_key: verifying_key.clone(), + sig_type: signing_scheme.clone(), + }), + )?); + } + Ok(signature_shares) +} + +fn create_ecdsa_shares( + message: String, + signing_scheme: String, + threshold: usize, +) -> JsResult> +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive, + C::ProjectivePoint: ToEncodedPoint, + C::AffinePoint: serde::Serialize, + C::Scalar: serde::Serialize, + ::FieldBytesSize: ModulusSize, +{ + let secret = C::Scalar::random(rand::rngs::OsRng); + let public: C::ProjectivePoint = C::ProjectivePoint::generator() * secret; + let public_key = + serde_json::to_string(&hex::encode(public.to_encoded_point(false).to_bytes()))?; + let compressed_public_key = + serde_json::to_string(&hex::encode(public.to_encoded_point(true).to_bytes()))?; + + let wrapped_secret = IdentifierPrimeField(secret); + let ids = (0..threshold) + .map(|_| IdentifierPrimeField(C::Scalar::random(rand::rngs::OsRng))) + .collect::>(); + let list_ids = ParticipantIdGeneratorType::list(&ids); + + let shares = shamir::split_secret_with_participant_generator::< + DefaultShare, ValuePrimeField>, + >( + threshold, + threshold, + &wrapped_secret, + rand::rngs::OsRng, + &[list_ids], + ) + .map_err(|e| JsError::new(&format!("Error splitting secret: {:?}", e)))?; + + let k = C::Scalar::random(rand::rngs::OsRng); + let big_r: C::ProjectivePoint = C::ProjectivePoint::generator() * k; + let big_r_str = serde_json::to_string(&big_r.to_affine())?; + let k_inv = elliptic_curve::ops::Invert::invert(&k).unwrap(); + let r = x_coordinate::(&big_r); + + let digest = ::Digest::new_with_prefix(message.as_bytes()); + let m_bytes = digest.finalize_fixed(); + let z = ::Uint>>::reduce_bytes(&m_bytes); + + let digest = hex::encode(m_bytes); + let mut signature_shares = Vec::with_capacity(threshold); + let lagrange_ids = ids.iter().map(|i| i.0).collect::>(); + for (i, share) in shares.iter().enumerate() { + let l = lagrange::(&share.identifier.0, &lagrange_ids); + let sig_share = l * k_inv * (z + share.value.0 * r); + + signature_shares.push(serde_json::to_string( + &SignableOutput::EcdsaSignedMessageShare(EcdsaSignedMessageShare { + digest: digest.clone(), + result: "success".to_string(), + share_id: serde_json::to_string(&share.identifier).unwrap(), + peer_id: (i + 1).to_string(), + signature_share: serde_json::to_string(&sig_share).unwrap(), + big_r: big_r_str.clone(), + compressed_public_key: compressed_public_key.clone(), + public_key: public_key.clone(), + sig_type: signing_scheme.clone(), + }), + )?); + } + + Ok(signature_shares) +} + +pub(crate) fn lagrange(xi: &C::Scalar, participants: &[C::Scalar]) -> C::Scalar +where + C: CurveArithmetic, +{ + let xi = *(xi.as_ref()); + let mut num = C::Scalar::ONE; + let mut den = C::Scalar::ONE; + for xj in participants { + let xj = *(xj.as_ref()); + if xi == xj { + continue; + } + num *= xj; + den *= xj - xi; + } + num * Field::invert(&den).expect("Denominator should not be zero") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::combine::combine_and_verify; + + // run with `cargo test --no-default-features --features=test-shares -- --nocapture` + // to get a print out of all signature share types and the expected signature output + // + // To just run the test without printing, use + // `cargo test --no-default-features --features=test-shares` + #[test] + fn create_shares_test() { + const THRESHOLD: usize = 3; + let message = "Hello, world!".to_string(); + let signing_schemes = [ + "Bls12381", + "EcdsaK256Sha256", + "EcdsaP256Sha256", + "EcdsaP384Sha384", + "SchnorrEd25519Sha512", + "SchnorrK256Sha256", + "SchnorrP256Sha256", + "SchnorrP384Sha384", + "SchnorrRistretto25519Sha512", + "SchnorrEd448Shake256", + "SchnorrRedJubjubBlake2b512", + "SchnorrK256Taproot", + "SchnorrRedDecaf377Blake2b512", + "SchnorrkelSubstrate", + "Bls12381G1ProofOfPossession", + ]; + + for signing_scheme in signing_schemes { + let signing_scheme = signing_scheme.to_string(); + let shares = create_shares(message.clone(), signing_scheme.to_string(), THRESHOLD); + assert!(shares.is_ok()); + let shares = shares.unwrap(); + println!("{:?}", shares); + let sig = combine_and_verify(shares); + assert!(sig.is_ok()); + println!("{:?}", sig.unwrap()); + } + } +} diff --git a/packages/wasm/src/index.ts b/packages/wasm/src/index.ts index be265f9a0..e111d048a 100644 --- a/packages/wasm/src/index.ts +++ b/packages/wasm/src/index.ts @@ -1,12 +1,10 @@ //@ts-ignore source map not found import { - EcdsaVariant, InitOutput, //@ts-ignore source map not found getModule, initSync, } from './pkg/wasm-internal'; -export type { EcdsaVariant } from './pkg/wasm-internal'; import * as wasmInternal from './pkg/wasm-internal'; @@ -84,7 +82,7 @@ export async function blsCombine( * - 12381G2 * - 12381G1 * @param {Uint8Array} ciphertext - * @param {Uint8Array} decryption_key + * @param {BlsSignatureShareJsonString[]} signature_shares * @returns {Uint8Array} */ export async function blsDecrypt( @@ -135,96 +133,37 @@ export async function blsVerify( } /** - * Combine ECDSA signatures shares + * K256 HD key derivation * - * Supports: - * - K256 - * - P256 - * @param {EcdsaVariant} variant - * @param {Uint8Array} presignature - * @param {(Uint8Array)[]} signature_shares - * @returns {[Uint8Array, Uint8Array, number]} - */ -export async function ecdsaCombine( - variant: EcdsaVariant, - presignature: Uint8Array, - signature_shares: Uint8Array[] -): Promise<[Uint8Array, Uint8Array, number]> { - await loadModules(); - return wasmInternal.ecdsaCombine(variant, presignature, signature_shares); -} - -/** - * HD key derivation - * - * Supports: - * - k256 - * - p256 - * @param {EcdsaVariant} variant ecdsa scheme * @param {Uint8Array} id keyid which will be used for the key derivation * @param {(Uint8Array)[]} public_keys ecdsa root keys * @returns {Uint8Array} */ export async function ecdsaDeriveKey( - variant: EcdsaVariant, id: Uint8Array, public_keys: Uint8Array[] ): Promise { await loadModules(); - return wasmInternal.ecdsaDeriveKey(variant, id, public_keys); + return wasmInternal.ecdsaDeriveKey(id, public_keys); } /** - * Verifier for ECDSA signatures + * Unified combiner and verifier for Lit supported signatures + * It will check for successful signature in the following order * * Supports: - * - k256 - * - p256 - ** Note ** Not currently supported through the lit network. Please use other ECSDSA signature verification - * @param {EcdsaVariant} variant - * @param {Uint8Array} message_hash - * @param {Uint8Array} public_key - * @param {[Uint8Array, Uint8Array, number]} signature - */ -export async function ecdsaVerify( - variant: EcdsaVariant, - message_hash: Uint8Array, - public_key: Uint8Array, - signature: [Uint8Array, Uint8Array, number] -): Promise { - await loadModules(); - return wasmInternal.ecdsaVerify(variant, message_hash, public_key, signature); -} - -/** - * Combiner and verifier for ECDSA signatures + * - Frost + * - BLS + * - ECDSA * - * Supports: - * - k256 - * - p256 - * ** Note ** Not currently supported through the lit network. Please use other ECSDSA signature verification - * @param {EcdsaVariant} variant - * @param {Uint8Array} pre_signature - * @param {Uint8Array[]} signature_shares - * @param {Uint8Array} message_hash - * @param {Uint8Array} public_key - * @param {[Uint8Array, Uint8Array, number]} signature + * @param {Uint8Array[]} shares + * @return {Uint8Array[]} signature */ -export async function ecdsaCombineAndVerify( - variant: EcdsaVariant, - pre_signature: Uint8Array, - signature_shares: Uint8Array[], - message_hash: Uint8Array, - public_key: Uint8Array -): Promise<[Uint8Array, Uint8Array, number]> { +export async function unifiedCombineAndVerify( + shares: string[] +): Promise { await loadModules(); - return wasmInternal.ecdsaCombineAndVerify( - variant, - pre_signature, - signature_shares, - message_hash, - public_key - ); + return wasmInternal.combineAndVerify(shares); } /** diff --git a/packages/wasm/src/lib/ecdsa.spec.ts b/packages/wasm/src/lib/ecdsa.spec.ts index 64e6620dc..be831615c 100644 --- a/packages/wasm/src/lib/ecdsa.spec.ts +++ b/packages/wasm/src/lib/ecdsa.spec.ts @@ -1,19 +1,11 @@ /// import { ethers } from 'ethers'; -import { - messageHex, - presignatureHex, - publicKeyHex, - signatureHex, - signatureSharesHex, -} from './ecdsa-data.spec.json'; -import { ecdsaCombine, ecdsaVerify, ecdsaDeriveKey } from '..'; +import { messageHex, publicKeyHex, signatureHex } from './ecdsa-data.spec.json'; +import { ecdsaDeriveKey } from '..'; const publicKey = Buffer.from(publicKeyHex, 'hex'); const uncompressedPublicKey = ethers.utils.computePublicKey(publicKey); -const presignature = Buffer.from(presignatureHex, 'hex'); -const signatureShares = signatureSharesHex.map((s) => Buffer.from(s, 'hex')); const message = Buffer.from(messageHex, 'hex'); const signature = { @@ -23,17 +15,6 @@ const signature = { }; describe('ECDSA', () => { - it('should combine signatures', async () => { - const [r, s, v] = await ecdsaCombine('K256', presignature, signatureShares); - expect(r).toBeInstanceOf(Uint8Array); - expect(s).toBeInstanceOf(Uint8Array); - expect(v === 0 || v === 1).toBeTruthy(); - - expect(Buffer.from(r)).toEqual(signature.r); - expect(Buffer.from(s)).toEqual(signature.s); - expect(v).toEqual(signature.v); - }); - it('should generate valid signatures for ethers', () => { expect( ethers.utils.recoverPublicKey( @@ -43,20 +24,9 @@ describe('ECDSA', () => { ).toEqual(uncompressedPublicKey); }); - it('should verify signature', async () => { - await ecdsaVerify('K256', message, publicKey, [ - signature.r, - signature.s, - signature.v, - ]); - }); - it('should derive keys', async () => { const identity = Buffer.from('test', 'ascii'); - const derivedKey = await ecdsaDeriveKey('K256', identity, [ - publicKey, - publicKey, - ]); + const derivedKey = await ecdsaDeriveKey(identity, [publicKey, publicKey]); expect(derivedKey).toBeInstanceOf(Uint8Array); expect(Buffer.from(derivedKey)).toEqual( diff --git a/tools/scripts/pub.mjs b/tools/scripts/pub.mjs index 4f3c30879..8f6c1c1ea 100644 --- a/tools/scripts/pub.mjs +++ b/tools/scripts/pub.mjs @@ -49,15 +49,15 @@ if (OPTION) { const lerna = await readJsonFile('lerna.json'); const lernaVersion = lerna.version; -let dirs = await listDirsRecursive('dist/packages', false) - .filter((item) => { - if (item.includes('wrapped-keys')) { - greenLog(`Skipping ${item}`); - return false; - } +let dirs = (await listDirsRecursive('dist/packages', false)) || []; +dirs = dirs.filter((item) => { + if (item.includes('wrapped-keys')) { + greenLog(`Skipping ${item}`); + return false; + } - return true; - }); + return true; +}); console.log('Ready to publish the following packages:'); diff --git a/yarn.lock b/yarn.lock index 2868d5d1f..0316a7caf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,16 @@ # yarn lockfile v1 +"@adraffy/ens-normalize@1.10.1": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" + integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== + +"@adraffy/ens-normalize@^1.10.1": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" + integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== + "@ampproject/remapping@^2.2.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" @@ -3002,10 +3012,16 @@ dependencies: ajv "^8.12.0" -"@lit-protocol/contracts@^0.0.86": - version "0.0.86" - resolved "https://registry.yarnpkg.com/@lit-protocol/contracts/-/contracts-0.0.86.tgz#adec861d0b775995523483b2fa5f4baf83d735a9" - integrity sha512-JtSjXwClG9wietQMERhSN1NqYas8JjQbso0FA9BAyv4svS3ejeKVwWcXUUvHPK9gDWPVhBzmvMNaB7ooR5UpBw== +"@lit-protocol/contracts@^0.1.10": + version "0.1.10" + resolved "https://registry.yarnpkg.com/@lit-protocol/contracts/-/contracts-0.1.10.tgz#0b8f5d039a9d19b4212046a8daa9d70f4a811f06" + integrity sha512-jNy9uZ2E2BH7EXUUUtxmjEnWJi/lud/qM/3o7DvTcd8mIKBaVIzR8142IJaRBqd7qCkbSmpts/FK+D1nYaUmLA== + dependencies: + "@t3-oss/env-core" "^0.12.0" + "@typechain/ethers-v6" "^0.5.1" + ethers "^6.13.5" + viem "^2.23.3" + zod "^3.24.2" "@ljharb/resumer@~0.0.1": version "0.0.1" @@ -3086,6 +3102,13 @@ resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.2.1.tgz#3812b72c057a28b44ff0ad4aff5ca846e5b9cdc9" integrity sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA== +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + "@noble/curves@1.4.2", "@noble/curves@~1.4.0": version "1.4.2" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9" @@ -3100,7 +3123,7 @@ dependencies: "@noble/hashes" "1.7.0" -"@noble/curves@^1.0.0", "@noble/curves@^1.4.2", "@noble/curves@^1.6.0", "@noble/curves@~1.8.1": +"@noble/curves@1.8.1", "@noble/curves@^1.0.0", "@noble/curves@^1.4.2", "@noble/curves@^1.6.0", "@noble/curves@^1.8.1", "@noble/curves@~1.8.1": version "1.8.1" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.8.1.tgz#19bc3970e205c99e4bdb1c64a4785706bce497ff" integrity sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ== @@ -3112,6 +3135,11 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@noble/hashes@1.4.0", "@noble/hashes@~1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" @@ -4268,7 +4296,7 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" -"@scure/bip32@^1.3.0": +"@scure/bip32@1.6.2", "@scure/bip32@^1.3.0", "@scure/bip32@^1.5.0": version "1.6.2" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.6.2.tgz#093caa94961619927659ed0e711a6e4bf35bffd0" integrity sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw== @@ -4293,7 +4321,7 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" -"@scure/bip39@^1.2.0": +"@scure/bip39@1.5.4", "@scure/bip39@^1.2.0", "@scure/bip39@^1.4.0": version "1.5.4" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.5.4.tgz#07fd920423aa671be4540d59bdd344cc1461db51" integrity sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA== @@ -4830,6 +4858,11 @@ dependencies: defer-to-connect "^2.0.1" +"@t3-oss/env-core@^0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@t3-oss/env-core/-/env-core-0.12.0.tgz#d5b6d92bf07d2f3ccdf59cc428f1faf114350d35" + integrity sha512-lOPj8d9nJJTt81mMuN9GMk8x5veOt7q9m11OSnCBJhwp1QrL/qR+M8Y467ULBSm9SunosryWNbmQQbgoiMgcdw== + "@testing-library/cypress@^8.0.2": version "8.0.7" resolved "https://registry.yarnpkg.com/@testing-library/cypress/-/cypress-8.0.7.tgz#18315eba3cf8852808afadf122e4858406384015" @@ -4993,6 +5026,14 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@typechain/ethers-v6@^0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v6/-/ethers-v6-0.5.1.tgz#42fe214a19a8b687086c93189b301e2b878797ea" + integrity sha512-F+GklO8jBWlsaVV+9oHaPh5NJdd6rAKN4tklGfInX1Q7h0xPgVLP39Jl3eCulPB5qexI71ZFHwbljx4ZXNfouA== + dependencies: + lodash "^4.17.15" + ts-essentials "^7.0.1" + "@types/aria-query@^5.0.1": version "5.0.4" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" @@ -5031,6 +5072,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" @@ -5038,13 +5086,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" @@ -5069,6 +5110,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" @@ -5173,6 +5221,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@22.7.5": + version "22.7.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b" + integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== + dependencies: + undici-types "~6.19.2" + "@types/node@^12.12.54", "@types/node@^12.12.6": version "12.20.55" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" @@ -6034,7 +6089,7 @@ abi-decoder@^2.3.0: web3-eth-abi "^1.2.1" web3-utils "^1.2.1" -abitype@^1.0.8: +abitype@1.0.8, abitype@^1.0.6, abitype@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.8.tgz#3554f28b2e9d6e9f35eb59878193eabd1b9f46ba" integrity sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg== @@ -6116,6 +6171,11 @@ aes-js@3.0.0: resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -10376,7 +10436,7 @@ elliptic@6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -elliptic@6.6.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.6.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.6.1: version "6.6.1" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.1.tgz#3b8ffb02670bf69e382c7f65bf524c97c5405c06" integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g== @@ -11788,6 +11848,19 @@ ethers@^5.0.13, ethers@^5.1.4, ethers@^5.7.1: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" +ethers@^6.13.5: + version "6.13.5" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.13.5.tgz#8c1d6ac988ac08abc3c1d8fabbd4b8b602851ac4" + integrity sha512-+knKNieu5EKRThQJWwqaJ10a6HE9sSehGeqWN65//wE7j47ZpFhKAnHB/JJFibwwg61I/koxaPsXbXpD/skNOQ== + dependencies: + "@adraffy/ens-normalize" "1.10.1" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@types/node" "22.7.5" + aes-js "4.0.0-beta.5" + tslib "2.7.0" + ws "8.17.1" + ethjs-unit@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" @@ -11845,7 +11918,7 @@ eventemitter3@4.0.7, eventemitter3@^4.0.0, eventemitter3@^4.0.4: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -eventemitter3@^5.0.1: +eventemitter3@5.0.1, eventemitter3@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== @@ -14646,6 +14719,11 @@ isomorphic-ws@^5.0.0: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== +isows@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" + integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw== + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -18379,6 +18457,19 @@ own-keys@^1.0.1: object-keys "^1.1.1" safe-push-apply "^1.0.0" +ox@0.6.9: + version "0.6.9" + resolved "https://registry.yarnpkg.com/ox/-/ox-0.6.9.tgz#da1ee04fa10de30c8d04c15bfb80fe58b1f554bd" + integrity sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug== + dependencies: + "@adraffy/ens-normalize" "^1.10.1" + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" + "@scure/bip32" "^1.5.0" + "@scure/bip39" "^1.4.0" + abitype "^1.0.6" + eventemitter3 "5.0.1" + p-cancelable@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" @@ -21341,7 +21432,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -21359,15 +21450,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^2.0.0, string-width@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -21494,7 +21576,7 @@ stringify-package@^1.0.1: resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg== -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -21522,13 +21604,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -22183,6 +22258,11 @@ ts-api-utils@^1.0.1: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== +ts-essentials@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38" + integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ== + ts-jest@29.2.5: version "29.2.5" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" @@ -22241,6 +22321,11 @@ tslib@1.14.1, tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.7.0, tslib@^2.8.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" @@ -22538,6 +22623,11 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + undici-types@~6.20.0: version "6.20.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" @@ -23046,6 +23136,20 @@ vfile@^6.0.0: "@types/unist" "^3.0.0" vfile-message "^4.0.0" +viem@^2.23.3: + version "2.26.2" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.26.2.tgz#0095676262ffe44bf5a6c7f8a221a4b81bc766d7" + integrity sha512-+yXSl1n+jV/Kn/zpETiNq0WOcINXti29nxdPIONvvNh+Es0VfeusW8bb2okVfa55pmuc8kOCOpB5wq3ZIUCTSA== + dependencies: + "@noble/curves" "1.8.1" + "@noble/hashes" "1.7.1" + "@scure/bip32" "1.6.2" + "@scure/bip39" "1.5.4" + abitype "1.0.8" + isows "1.0.6" + ox "0.6.9" + ws "8.18.1" + vm-browserify@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" @@ -23638,7 +23742,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -23673,15 +23777,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -23774,6 +23869,16 @@ ws@7.5.3: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== +ws@8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + +ws@8.18.1: + version "8.18.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb" + integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w== + ws@^3.0.0: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" @@ -24033,6 +24138,16 @@ yoctocolors-cjs@^2.1.2: resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242" integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA== +zod@^3.24.1: + version "3.24.1" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.1.tgz#27445c912738c8ad1e9de1bea0359fa44d9d35ee" + integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A== + +zod@^3.24.2: + version "3.24.2" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.2.tgz#8efa74126287c675e92f46871cfc8d15c34372b3" + integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ== + zwitch@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"