From 0dafaf6c60d5d49b54fa6ab383bd0885c5660e43 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Wed, 22 Oct 2025 18:44:43 -0400 Subject: [PATCH 01/11] Modify `nucypher-core` dependency to use local dependency so that the latest changes from `nucypher-core` can be used. --- package.json | 4 +-- packages/shared/package.json | 2 +- packages/taco/package.json | 2 +- pnpm-lock.yaml | 65 +++++++++++++----------------------- 4 files changed, 27 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index 30a9c3d36..149b2f410 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ }, "dependencies": { "@changesets/cli": "^2.28.1", - "@nucypher/nucypher-core": "^0.14.5", + "@nucypher/nucypher-core": "link:../nucypher-core/nucypher-core-wasm-bundler", "ethers": "^5.8.0" }, "devDependencies": { @@ -65,7 +65,7 @@ ] }, "overrides": { - "@nucypher/nucypher-core": "^0.14.5", + "@nucypher/nucypher-core": "link:../nucypher-core/nucypher-core-wasm-bundler", "glob-parent@<5.1.2": ">=5.1.2", "node-forge@<1.0.0": ">=1.0.0", "node-forge@<1.3.0": ">=1.3.0", diff --git a/packages/shared/package.json b/packages/shared/package.json index 22464a787..41ac2f966 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -43,7 +43,7 @@ "@ethersproject/abi": "^5.8.0", "@ethersproject/providers": "^5.8.0", "@nucypher/nucypher-contracts": "0.26.0-alpha.1", - "@nucypher/nucypher-core": "^0.14.5", + "@nucypher/nucypher-core": "link:../../../nucypher-core/nucypher-core-wasm-bundler", "axios": "^1.8.4", "deep-equal": "^2.2.3", "ethers": "^5.8.0", diff --git a/packages/taco/package.json b/packages/taco/package.json index 32222200f..53d5efdf9 100644 --- a/packages/taco/package.json +++ b/packages/taco/package.json @@ -42,7 +42,7 @@ }, "dependencies": { "@astronautlabs/jsonpath": "^1.1.2", - "@nucypher/nucypher-core": "^0.14.5", + "@nucypher/nucypher-core": "link:../../../nucypher-core/nucypher-core-wasm-bundler", "@nucypher/shared": "workspace:*", "@nucypher/taco-auth": "workspace:*", "ethers": "^5.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0450d806e..a296ebea0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: - '@nucypher/nucypher-core': ^0.14.5 + '@nucypher/nucypher-core': link:../nucypher-core/nucypher-core-wasm-bundler glob-parent@<5.1.2: '>=5.1.2' node-forge@<1.0.0: '>=1.0.0' node-forge@<1.3.0: '>=1.3.0' @@ -19,8 +19,8 @@ importers: specifier: ^2.28.1 version: 2.29.5 '@nucypher/nucypher-core': - specifier: ^0.14.5 - version: 0.14.5 + specifier: link:../nucypher-core/nucypher-core-wasm-bundler + version: link:../nucypher-core/nucypher-core-wasm-bundler ethers: specifier: ^5.8.0 version: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -491,8 +491,8 @@ importers: packages/pre: dependencies: '@nucypher/nucypher-core': - specifier: ^0.14.5 - version: 0.14.5 + specifier: link:../../../nucypher-core/nucypher-core-wasm-bundler + version: link:../../../nucypher-core/nucypher-core-wasm-bundler '@nucypher/shared': specifier: workspace:* version: link:../shared @@ -516,8 +516,8 @@ importers: specifier: 0.26.0-alpha.1 version: 0.26.0-alpha.1 '@nucypher/nucypher-core': - specifier: ^0.14.5 - version: 0.14.5 + specifier: link:../../../nucypher-core/nucypher-core-wasm-bundler + version: link:../../../nucypher-core/nucypher-core-wasm-bundler axios: specifier: ^1.8.4 version: 1.10.0 @@ -565,8 +565,8 @@ importers: specifier: ^1.1.2 version: 1.1.2 '@nucypher/nucypher-core': - specifier: ^0.14.5 - version: 0.14.5 + specifier: link:../../../nucypher-core/nucypher-core-wasm-bundler + version: link:../../../nucypher-core/nucypher-core-wasm-bundler '@nucypher/shared': specifier: workspace:* version: link:../shared @@ -621,11 +621,13 @@ importers: specifier: workspace:* version: link:../test-utils + packages/taco-auth/dist/es: {} + packages/test-utils: dependencies: '@nucypher/nucypher-core': - specifier: ^0.14.5 - version: 0.14.5 + specifier: link:../../../nucypher-core/nucypher-core-wasm-bundler + version: link:../../../nucypher-core/nucypher-core-wasm-bundler '@nucypher/shared': specifier: workspace:* version: link:../shared @@ -2548,9 +2550,6 @@ packages: '@nucypher/nucypher-contracts@0.26.0-alpha.1': resolution: {integrity: sha512-ISS66SyKbDLXFF88Z6k8Tnc/L6Shx6tq22CjPpovbSD2PGS/UzE3uDxvFTKZfgcIJYsi7GV/ho2Brsx4sHYTLg==} - '@nucypher/nucypher-core@0.14.5': - resolution: {integrity: sha512-Q3kBuJL0qtTtnxrM5DEQauQUvDlXmwubm9u1h7gbyLhs+aZNC9WDyjEUbE43+uahlHu4k1hKEMxD1gjV165ChA==} - '@nucypher/shared@0.5.0': resolution: {integrity: sha512-Y+0oEgVtoud4BP/pvj2elPm5igrQ8AAUwYYmUiLpXNqQMRF4zFnYaGbl+xhKk45CYG8S9cDKJEN8nopzVADTNw==} engines: {node: '>=18', pnpm: '>=8.0.0'} @@ -12105,14 +12104,12 @@ snapshots: '@nucypher/nucypher-contracts@0.26.0-alpha.1': {} - '@nucypher/nucypher-core@0.14.5': {} - '@nucypher/shared@0.5.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@ethersproject/abi': 5.8.0 '@ethersproject/providers': 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@nucypher/nucypher-contracts': 0.25.0 - '@nucypher/nucypher-core': 0.14.5 + '@nucypher/nucypher-core': link:../nucypher-core/nucypher-core-wasm-bundler axios: 1.10.0 deep-equal: 2.2.3 ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -12138,7 +12135,7 @@ snapshots: '@nucypher/taco@0.6.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@astronautlabs/jsonpath': 1.1.2 - '@nucypher/nucypher-core': 0.14.5 + '@nucypher/nucypher-core': link:../nucypher-core/nucypher-core-wasm-bundler '@nucypher/shared': 0.5.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@nucypher/taco-auth': 0.3.0(bufferutil@4.0.9)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -15214,8 +15211,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.8.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) eslint-plugin-react: 7.37.5(eslint@8.57.0) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.0) @@ -15270,21 +15267,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0): - dependencies: - '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.1 - eslint: 8.57.0 - get-tsconfig: 4.10.1 - is-bun-module: 2.0.0 - stable-hash: 0.0.5 - tinyglobby: 0.2.14 - unrs-resolver: 1.9.2 - optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) - transitivePeerDependencies: - - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 @@ -15296,7 +15278,7 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.9.2 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -15321,14 +15303,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.8.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -15414,7 +15395,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -15425,7 +15406,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -15437,7 +15418,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.8.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack From 4470fadca3be4ce0097461c204db752053d4db30 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 23 Oct 2025 16:14:04 -0400 Subject: [PATCH 02/11] Update Porter module to use `nucypher-core` signature request and response objects. --- packages/shared/src/porter.ts | 79 ++++----- packages/shared/test/porter.test.ts | 266 ++++++++++++---------------- 2 files changed, 148 insertions(+), 197 deletions(-) diff --git a/packages/shared/src/porter.ts b/packages/shared/src/porter.ts index e658c19a2..796829d34 100644 --- a/packages/shared/src/porter.ts +++ b/packages/shared/src/porter.ts @@ -4,7 +4,9 @@ import { EncryptedThresholdDecryptionResponse, PublicKey, RetrievalKit, + SignatureResponse, TreasureMap, + UserOperationSignatureRequest, } from '@nucypher/nucypher-core'; import axios, { AxiosRequestConfig, @@ -148,11 +150,20 @@ type PostTacoDecryptResponse = { }; export type TacoDecryptResult = { - encryptedResponses: Record; - errors: Record; + encryptedResponses: Record< + ChecksumAddress, + EncryptedThresholdDecryptionResponse + >; + errors: Record; }; // Signing types + +type PostTacoSignRequest = { + readonly signing_requests: Record; + readonly threshold: number; +}; + type TacoSignResponse = { readonly result: { readonly signing_results: { @@ -172,32 +183,10 @@ export type TacoSignature = { }; export type TacoSignResult = { - signingResults: { [ursulaAddress: string]: TacoSignature }; - errors: Record; + signingResults: Record; + errors: Record; }; -function decodeSignature( - signerAddress: string, - signatureB64: string, -): { result?: TacoSignature; error?: string } { - try { - const decodedData = JSON.parse( - new TextDecoder().decode(fromBase64(signatureB64)), - ); - return { - result: { - messageHash: decodedData.message_hash, - signature: decodedData.signature, - signerAddress, - }, - }; - } catch (error) { - return { - error: `Failed to decode signature: ${error}`, - }; - } -} - export class PorterClient { readonly porterUrls: URL[]; @@ -345,11 +334,16 @@ export class PorterClient { } public async signUserOp( - signingRequests: Record, + signingRequests: Record, threshold: number, ): Promise { - const data: Record = { - signing_requests: signingRequests, + const data: PostTacoSignRequest = { + signing_requests: Object.fromEntries( + Object.entries(signingRequests).map(([ursula, signingRequest]) => [ + ursula, + toBase64(signingRequest.toBytes()), + ]), + ), threshold: threshold, }; @@ -358,27 +352,26 @@ export class PorterClient { method: 'post', data, }); - const { signatures, errors } = resp.data.result.signing_results; - const allErrors: Record = { ...errors }; const signingResults: { [ursulaAddress: string]: TacoSignature } = {}; - for (const [ursulaAddress, [signerAddress, signatureB64]] of Object.entries( - signatures || {}, - )) { - const decoded = decodeSignature(signerAddress, signatureB64); - if (decoded.error) { - // issue with decoding signature, add to errors - allErrors[ursulaAddress] = decoded.error; - continue; - } - // Always include all decoded signatures in signingResults - signingResults[ursulaAddress] = decoded.result!; + for (const [ + ursulaAddress, + [signerAddress, signatureResponseBase64], + ] of Object.entries(signatures || {})) { + const signatureResponse = SignatureResponse.fromBytes( + fromBase64(signatureResponseBase64), + ); + signingResults[ursulaAddress] = { + messageHash: `0x${toHexString(signatureResponse.hash)}`, + signature: `0x${toHexString(signatureResponse.signature)}`, + signerAddress, + }; } return { signingResults, - errors: allErrors, + errors, }; } } diff --git a/packages/shared/test/porter.test.ts b/packages/shared/test/porter.test.ts index ccda73b13..6fa6d7b4f 100644 --- a/packages/shared/test/porter.test.ts +++ b/packages/shared/test/porter.test.ts @@ -2,6 +2,12 @@ import { fakeUrsulas } from '@nucypher/test-utils'; import axios, { HttpStatusCode } from 'axios'; import { beforeAll, describe, expect, it, MockInstance, vi } from 'vitest'; +import { + SignatureResponse, + UserOperation, + UserOperationSignatureRequest, +} from '@nucypher/nucypher-core'; + import { domains, getPorterUris, @@ -13,6 +19,7 @@ import { toHexString, Ursula, } from '../src'; +import { fromHexString } from '../src/utils'; const fakePorterUris = [ 'https://_this_should_crash.com/', @@ -57,14 +64,13 @@ const createMockSignResponse = (errorCase?: boolean) => ({ signatures: errorCase ? { '0xabcd': [ - '0xefgh', + '0xefff', toBase64( - new TextEncoder().encode( - JSON.stringify({ - message_hash: '0x1234', - signature: '0xbeef', - }), - ), + new SignatureResponse( + fromHexString('0x1234'), + fromHexString('0xbeef'), + 0, + ).toBytes(), ), ], } @@ -72,23 +78,21 @@ const createMockSignResponse = (errorCase?: boolean) => ({ '0x1234': [ '0x5678', toBase64( - new TextEncoder().encode( - JSON.stringify({ - message_hash: '0x1234', - signature: '0xdead', - }), - ), + new SignatureResponse( + fromHexString('0x1234'), + fromHexString('0xdead'), + 0, + ).toBytes(), ), ], '0xabcd': [ - '0xefgh', + '0xefff', toBase64( - new TextEncoder().encode( - JSON.stringify({ - message_hash: '0x1234', - signature: '0xbeef', - }), - ), + new SignatureResponse( + fromHexString('0x1234'), + fromHexString('0xbeef'), + 0, + ).toBytes(), ), ], }, @@ -195,31 +199,34 @@ describe('PorterClient Signing', () => { }); describe('signUserOp', () => { - const mockPackedUserOp = { - sender: '0x1234', - nonce: '0x1', - factory: '0x0', - factoryData: '0x', - callData: '0xabc', - callGasLimit: '0x20000', - verificationGasLimit: '0x15000', - preVerificationGas: '0x1000', - maxFeePerGas: '0xabc', - maxPriorityFeePerGas: '0x123', - paymaster: '0x0', - paymasterVerificationGasLimit: '0x0', - paymasterPostOpGasLimit: '0x0', - paymasterData: '0x', - signature: '0x', - }; + // since this uses wasm it must be called after initialize (beforeAll()) so we use a factory function + const createUserSignatureRequest = () => + new UserOperationSignatureRequest( + new UserOperation( + '0x000000000000000000000000000000000000000a', + BigInt(0), // nonce + fromHexString('0xabc'), // callData + BigInt(0), // callGasLimit + BigInt(0), // verificationGasLimit + BigInt(0), // preVerificationGasLimit + BigInt(0), // maxFeePerGas + BigInt(0), // maxPriorityFeePerGas + ), + 1, // cohort ID + BigInt(1), // chain ID + '0.8.0', + null, + ); it('should successfully sign a UserOperation', async () => { + const userOpSignatureRequest = createUserSignatureRequest(); + mockSignUserOp(true); const porterClient = new PorterClient(fakePorterUris[2]); const result = await porterClient.signUserOp( { - '0x1234': JSON.stringify(mockPackedUserOp), - '0xabcd': JSON.stringify(mockPackedUserOp), + '0x1234': userOpSignatureRequest, + '0xabcd': userOpSignatureRequest, }, 2, ); @@ -234,7 +241,7 @@ describe('PorterClient Signing', () => { '0xabcd': { messageHash: '0x1234', signature: '0xbeef', - signerAddress: '0xefgh', + signerAddress: '0xefff', }, }, errors: {}, @@ -242,14 +249,16 @@ describe('PorterClient Signing', () => { }); it('should handle UserOperation signing failures', async () => { + const userOpSignatureRequest = createUserSignatureRequest(); + mockSignUserOp(false); const porterClient = new PorterClient(fakePorterUris[2]); await expect( porterClient.signUserOp( { - '0x1234': JSON.stringify(mockPackedUserOp), - '0xabcd': JSON.stringify(mockPackedUserOp), + '0x1234': userOpSignatureRequest, + '0xabcd': userOpSignatureRequest, }, 2, ), @@ -257,13 +266,15 @@ describe('PorterClient Signing', () => { }); it('should handle errors from Porter response in UserOperation signing', async () => { + const userOpSignatureRequest = createUserSignatureRequest(); + // Mock a response with errors from Porter mockSignUserOp(true, true); const porterClient = new PorterClient(fakePorterUris[2]); const result = await porterClient.signUserOp( { - '0x1234': JSON.stringify(mockPackedUserOp), - '0xabcd': JSON.stringify(mockPackedUserOp), + '0x1234': userOpSignatureRequest, + '0xabcd': userOpSignatureRequest, }, 2, ); @@ -273,7 +284,7 @@ describe('PorterClient Signing', () => { '0xabcd': { messageHash: '0x1234', signature: '0xbeef', - signerAddress: '0xefgh', + signerAddress: '0xefff', }, }, errors: { @@ -283,6 +294,8 @@ describe('PorterClient Signing', () => { }); it('should include error when message hashes do not match', async () => { + const userOpSignatureRequest = createUserSignatureRequest(); + const createMismatchedResponse = () => ({ result: { signing_results: { @@ -290,23 +303,21 @@ describe('PorterClient Signing', () => { '0x1234': [ '0x5678', toBase64( - new TextEncoder().encode( - JSON.stringify({ - message_hash: '0x1234', - signature: '0xdead', - }), - ), + new SignatureResponse( + fromHexString('0x1234'), + fromHexString('0xdead'), + 0, + ).toBytes(), ), ], '0xabcd': [ - '0xefgh', + '0xefff', toBase64( - new TextEncoder().encode( - JSON.stringify({ - message_hash: '0xdifferent', // Different message hash - signature: '0xbeef', - }), - ), + new SignatureResponse( + fromHexString('0xdddd'), // Different message hash + fromHexString('0xbeef'), + 0, + ).toBytes(), ), ], }, @@ -327,8 +338,8 @@ describe('PorterClient Signing', () => { const porterClient = new PorterClient(fakePorterUris[2]); const result = await porterClient.signUserOp( { - '0x1234': JSON.stringify(mockPackedUserOp), - '0xabcd': JSON.stringify(mockPackedUserOp), + '0x1234': userOpSignatureRequest, + '0xabcd': userOpSignatureRequest, }, 2, ); @@ -341,9 +352,9 @@ describe('PorterClient Signing', () => { signerAddress: '0x5678', }, '0xabcd': { - messageHash: '0xdifferent', // Different hash + messageHash: '0xdddd', // Different hash signature: '0xbeef', - signerAddress: '0xefgh', + signerAddress: '0xefff', }, }, errors: {}, // No errors - mismatched hashes don't generate errors, just prevent aggregation @@ -351,6 +362,8 @@ describe('PorterClient Signing', () => { }); it('should not return aggregated signature when threshold not met', async () => { + const userOpSignatureRequest = createUserSignatureRequest(); + const createInsufficientResponse = () => ({ result: { signing_results: { @@ -358,12 +371,11 @@ describe('PorterClient Signing', () => { '0x1234': [ '0x5678', toBase64( - new TextEncoder().encode( - JSON.stringify({ - message_hash: '0x1234', - signature: '0xdead', - }), - ), + new SignatureResponse( + fromHexString('0x1234'), + fromHexString('0xdead'), + 0, + ).toBytes(), ), ], // Only 1 signature, but threshold is 2 @@ -385,8 +397,8 @@ describe('PorterClient Signing', () => { const porterClient = new PorterClient(fakePorterUris[2]); const result = await porterClient.signUserOp( { - '0x1234': JSON.stringify(mockPackedUserOp), - '0xabcd': JSON.stringify(mockPackedUserOp), + '0x1234': userOpSignatureRequest, + '0xabcd': userOpSignatureRequest, }, 2, // threshold of 2, but only 1 signature ); @@ -404,12 +416,14 @@ describe('PorterClient Signing', () => { }); it('should successfully sign', async () => { + const userOpSignatureRequest = createUserSignatureRequest(); + mockSignUserOp(true); const porterClient = new PorterClient(fakePorterUris[2]); const result = await porterClient.signUserOp( { - '0x1234': JSON.stringify(mockPackedUserOp), - '0xabcd': JSON.stringify(mockPackedUserOp), + '0x1234': userOpSignatureRequest, + '0xabcd': userOpSignatureRequest, }, 2, ); @@ -424,69 +438,16 @@ describe('PorterClient Signing', () => { '0xabcd': { messageHash: '0x1234', signature: '0xbeef', - signerAddress: '0xefgh', + signerAddress: '0xefff', }, }, errors: {}, }); }); - it('should handle decode errors', async () => { - const createOptimisticErrorResponse = () => ({ - result: { - signing_results: { - signatures: { - '0x1234': [ - '0x5678', - toBase64( - new TextEncoder().encode( - JSON.stringify({ - message_hash: '0x1234', - signature: '0xdead', - }), - ), - ), - ], - '0xabcd': ['0xefgh', 'invalid-base64-data!!!'], // Invalid signature data - }, - errors: {}, - }, - }, - }); - - vi.spyOn(axios, 'request').mockImplementation(async (config) => { - if (config.url === '/sign' && config.baseURL === fakePorterUris[2]) { - return Promise.resolve({ - status: HttpStatusCode.Ok, - data: createOptimisticErrorResponse(), - }); - } - }); - - const porterClient = new PorterClient(fakePorterUris[2]); - const result = await porterClient.signUserOp( - { - '0x1234': JSON.stringify(mockPackedUserOp), - '0xabcd': JSON.stringify(mockPackedUserOp), - }, - 2, - ); - - expect(result).toEqual({ - signingResults: { - '0x1234': { - messageHash: '0x1234', - signature: '0xdead', - signerAddress: '0x5678', - }, - }, - errors: { - '0xabcd': expect.stringContaining('Failed to decode signature'), // Decode error - }, - }); - }); - it('should aggregate only threshold-meeting hash', async () => { + const userOpSignatureRequest = createUserSignatureRequest(); + const createMixedHashResponse = () => ({ result: { signing_results: { @@ -494,34 +455,31 @@ describe('PorterClient Signing', () => { '0x1234': [ '0x5678', toBase64( - new TextEncoder().encode( - JSON.stringify({ - message_hash: '0xhash1', - signature: '0xdead', - }), - ), + new SignatureResponse( + fromHexString('0x0001'), + fromHexString('0xdead'), + 0, + ).toBytes(), ), ], '0xabcd': [ - '0xefgh', + '0xefff', toBase64( - new TextEncoder().encode( - JSON.stringify({ - message_hash: '0xhash1', // Same hash, meets threshold - signature: '0xbeef', - }), - ), + new SignatureResponse( + fromHexString('0x0001'), // Same hash, meets threshold + fromHexString('0xbeef'), + 0, + ).toBytes(), ), ], '0xdef0': [ '0xabc1', toBase64( - new TextEncoder().encode( - JSON.stringify({ - message_hash: '0xhash2', // Different hash, doesn't meet threshold - signature: '0xcafe', - }), - ), + new SignatureResponse( + fromHexString('0x0002'), // Different hash, doesn't meet threshold + fromHexString('0xcafe'), + 0, + ).toBytes(), ), ], }, @@ -542,9 +500,9 @@ describe('PorterClient Signing', () => { const porterClient = new PorterClient(fakePorterUris[2]); const result = await porterClient.signUserOp( { - '0x1234': JSON.stringify(mockPackedUserOp), - '0xabcd': JSON.stringify(mockPackedUserOp), - '0xdef0': JSON.stringify(mockPackedUserOp), + '0x1234': userOpSignatureRequest, + '0xabcd': userOpSignatureRequest, + '0xdef0': userOpSignatureRequest, }, 2, // threshold of 2 ); @@ -553,17 +511,17 @@ describe('PorterClient Signing', () => { // different hashes returned separately signingResults: { '0x1234': { - messageHash: '0xhash1', + messageHash: '0x0001', signature: '0xdead', signerAddress: '0x5678', }, '0xabcd': { - messageHash: '0xhash1', + messageHash: '0x0001', signature: '0xbeef', - signerAddress: '0xefgh', + signerAddress: '0xefff', }, '0xdef0': { - messageHash: '0xhash2', + messageHash: '0x0002', signature: '0xcafe', signerAddress: '0xabc1', }, From a1b474011273b9af923b1cf39bf146a8fb0a8951 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 23 Oct 2025 16:16:54 -0400 Subject: [PATCH 03/11] Update tests to accommodate changes to `nucypher-core` objects from 0.15.0 release. --- packages/taco/test/taco.test.ts | 2 +- packages/taco/test/test-utils.ts | 7 ++++--- packages/test-utils/src/utils.ts | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/taco/test/taco.test.ts b/packages/taco/test/taco.test.ts index e035f211f..23ce52803 100644 --- a/packages/taco/test/taco.test.ts +++ b/packages/taco/test/taco.test.ts @@ -63,7 +63,7 @@ describe('taco', () => { const { decryptionShares } = fakeTDecFlow({ ...mockedDkg, message: toBytes(message), - dkgPublicKey: mockedDkg.dkg.publicKey(), + dkgPublicKey: mockedDkg.serverAggregate.publicKey, thresholdMessageKit: messageKit, }); const { participantSecrets, participants } = await mockDkgParticipants( diff --git a/packages/taco/test/test-utils.ts b/packages/taco/test/test-utils.ts index a6dfb8f7f..faae09d01 100644 --- a/packages/taco/test/test-utils.ts +++ b/packages/taco/test/test-utils.ts @@ -133,7 +133,7 @@ export const fakeDkgTDecFlowE2E: ( threshold = 4, ) => { const ritual = fakeDkgFlow(variant, ritualId, sharesNum, threshold); - const dkgPublicKey = ritual.dkg.publicKey(); + const dkgPublicKey = ritual.serverAggregate.publicKey; const provider = fakeProvider(); const thresholdMessageKit = await encryptMessage( message, @@ -159,7 +159,7 @@ export const fakeDkgTDecFlowE2E: ( export const fakeCoordinatorRitual = async (): Promise => { const ritual = await fakeDkgTDecFlowE2E(); - const dkgPkBytes = ritual.dkg.publicKey().toBytes(); + const dkgPkBytes = ritual.serverAggregate.publicKey.toBytes(); return { initiator: ritual.validators[0].address.toString(), dkgSize: ritual.sharesNum, @@ -220,10 +220,11 @@ export const fakeDkgRitual = (ritual: { dkg: Dkg; sharesNum: number; threshold: number; + serverAggregate: AggregatedTranscript; }) => { return new DkgRitual( fakeRitualId, - ritual.dkg.publicKey(), + ritual.serverAggregate.publicKey, ritual.sharesNum, ritual.threshold, DkgRitualState.ACTIVE, diff --git a/packages/test-utils/src/utils.ts b/packages/test-utils/src/utils.ts index dc328f23c..77395dd87 100644 --- a/packages/test-utils/src/utils.ts +++ b/packages/test-utils/src/utils.ts @@ -239,7 +239,7 @@ export const fakeDkgFlow = ( for (let i = 0; i < sharesNum; i++) { const keypair = Keypair.random(); validatorKeypairs.push(keypair); - const validator = new Validator(genEthAddr(i), keypair.publicKey); + const validator = new Validator(genEthAddr(i), keypair.publicKey, i); validators.push(validator); } From bca94cacc445705c05e7c668a5b637438afdae76 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 23 Oct 2025 16:21:23 -0400 Subject: [PATCH 04/11] Define `UserOperationToSign` as a common type that any library can use to specify their corresponding UserOperation. Convert `UserOperationToSign` to `nucypher-core`:UserOperation when crafting signature request. Use latest types from Porter for sending signature requests and processing signature response. Add/update tests. --- packages/shared/src/types.ts | 142 ++++++------ packages/taco/integration-test/sign.test.ts | 4 +- packages/taco/src/sign.ts | 34 ++- packages/taco/src/types.ts | 4 +- packages/taco/test/taco-sign.test.ts | 242 +++++++++++++++----- 5 files changed, 276 insertions(+), 150 deletions(-) diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index 9148cef91..23ad354fb 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -1,82 +1,90 @@ -import { ethers } from 'ethers'; +import { UserOperation } from '@nucypher/nucypher-core'; + +import { fromHexString } from './utils'; export type ChecksumAddress = `0x${string}`; export type HexEncodedBytes = string; export type Base64EncodedBytes = string; -export type UserOperation = { - sender: string; - nonce: number; - factory: string; - factoryData: string; - callData: string; - callGasLimit: number; - verificationGasLimit: number; - preVerificationGas: number; - maxFeePerGas: number; - maxPriorityFeePerGas: number; - paymaster: string; - paymasterVerificationGasLimit: number; - paymasterPostOpGasLimit: number; - paymasterData: string; - signature: string; +export type UserOperationToSign = { + sender: `0x${string}`; + nonce: bigint | number; + callData: `0x${string}` | Uint8Array; + callGasLimit: bigint | number; + verificationGasLimit: bigint | number; + preVerificationGas: bigint | number; + maxFeePerGas: bigint | number; + maxPriorityFeePerGas: bigint | number; + // optional fields + factory?: `0x${string}` | undefined; + factoryData?: `0x${string}` | Uint8Array | undefined; + paymaster?: `0x${string}` | undefined; + paymasterVerificationGasLimit?: bigint | number | undefined; + paymasterPostOpGasLimit?: bigint | number | undefined; + paymasterData?: `0x${string}` | Uint8Array | undefined; + signature?: `0x${string}` | Uint8Array | undefined; }; -export class UserOperationSignatureRequest { - constructor( - private userOp: ReturnType, - private aaVersion: string, - private cohortId: number, - private chainId: number, - private context: unknown = {}, - private signatureType: string = 'userop', - ) {} +export function toCoreUserOperation( + userOperation: UserOperationToSign, +): UserOperation { + const userOp = new UserOperation( + userOperation.sender, + typeof userOperation.nonce === 'bigint' + ? userOperation.nonce + : BigInt(userOperation.nonce), + userOperation.callData instanceof Uint8Array + ? userOperation.callData + : fromHexString(userOperation.callData), + typeof userOperation.callGasLimit === 'bigint' + ? userOperation.callGasLimit + : BigInt(userOperation.callGasLimit), + typeof userOperation.verificationGasLimit === 'bigint' + ? userOperation.verificationGasLimit + : BigInt(userOperation.verificationGasLimit), + typeof userOperation.preVerificationGas === 'bigint' + ? userOperation.preVerificationGas + : BigInt(userOperation.preVerificationGas || 0), + typeof userOperation.maxFeePerGas === 'bigint' + ? userOperation.maxFeePerGas + : BigInt(userOperation.maxFeePerGas), + typeof userOperation.maxPriorityFeePerGas === 'bigint' + ? userOperation.maxPriorityFeePerGas + : BigInt(userOperation.maxPriorityFeePerGas), + ); - toBytes(): Uint8Array { - const data = { - user_op: JSON.stringify(this.userOp), - aa_version: this.aaVersion, - cohort_id: this.cohortId, - chain_id: this.chainId, - context: this.context, - signature_type: this.signatureType, - }; - return new TextEncoder().encode(JSON.stringify(data)); - } -} + // optional factory data + if (userOperation.factory) { + const factory_data = + userOperation.factoryData instanceof Uint8Array + ? userOperation.factoryData + : fromHexString(userOperation.factoryData || '0x'); -function normalizeAddress(address: string): string | null { - if (!address || address === '0x') { - return null; + userOp.setFactoryData(userOperation.factory, factory_data); } - try { - // Use ethers to get the checksummed address - this will throw on invalid addresses - return ethers.utils.getAddress(address); - } catch (error) { - // Re-throw the error to fail fast on invalid addresses - throw new Error( - `Invalid address: ${address}. ${error instanceof Error ? error.message : 'Unknown error'}`, + // optional paymaster data + if (userOperation.paymaster) { + const paymaster_data = + userOperation.paymasterData instanceof Uint8Array + ? userOperation.paymasterData + : fromHexString(userOperation.paymasterData || '0x'); + const paymaster_verification_gas_limit = + typeof userOperation.paymasterVerificationGasLimit === 'bigint' + ? userOperation.paymasterVerificationGasLimit + : BigInt(userOperation.paymasterVerificationGasLimit || 0); + const paymaster_post_op_gas_limit = + typeof userOperation.paymasterPostOpGasLimit === 'bigint' + ? userOperation.paymasterPostOpGasLimit + : BigInt(userOperation.paymasterPostOpGasLimit || 0); + + userOp.setPaymasterData( + userOperation.paymaster, + paymaster_verification_gas_limit, + paymaster_post_op_gas_limit, + paymaster_data, ); } -} -export function convertUserOperationToPython(userOp: UserOperation) { - return { - sender: normalizeAddress(userOp.sender), - nonce: userOp.nonce, - factory: normalizeAddress(userOp.factory), - factory_data: userOp.factoryData || '0x', - call_data: userOp.callData || '0x', - call_gas_limit: userOp.callGasLimit, - verification_gas_limit: userOp.verificationGasLimit, - pre_verification_gas: userOp.preVerificationGas, - max_fee_per_gas: userOp.maxFeePerGas, - max_priority_fee_per_gas: userOp.maxPriorityFeePerGas, - paymaster: normalizeAddress(userOp.paymaster), - paymaster_verification_gas_limit: userOp.paymasterVerificationGasLimit, - paymaster_post_op_gas_limit: userOp.paymasterPostOpGasLimit, - paymaster_data: userOp.paymasterData || '0x', - signature: userOp.signature || '0x', - }; + return userOp; } diff --git a/packages/taco/integration-test/sign.test.ts b/packages/taco/integration-test/sign.test.ts index 8a70e4c2d..9f65c92e2 100644 --- a/packages/taco/integration-test/sign.test.ts +++ b/packages/taco/integration-test/sign.test.ts @@ -3,7 +3,7 @@ import { fromHexString, getPorterUris, SigningCoordinatorAgent, - UserOperation, + UserOperationToSign, } from '@nucypher/shared'; import { ethers } from 'ethers'; import { beforeAll, describe, expect, test } from 'vitest'; @@ -43,7 +43,7 @@ describe.skipIf(!process.env.RUNNING_IN_CI)( test('should sign a user operation', async () => { // Create a sample user operation with all required fields - const userOp: UserOperation = { + const userOp: UserOperationToSign = { sender: DUMMY_ADDRESS, nonce: 0, factory: '0x', diff --git a/packages/taco/src/sign.ts b/packages/taco/src/sign.ts index b22c03c0b..7df6bd2cb 100644 --- a/packages/taco/src/sign.ts +++ b/packages/taco/src/sign.ts @@ -1,5 +1,5 @@ +import { UserOperationSignatureRequest } from '@nucypher/nucypher-core'; import { - convertUserOperationToPython, Domain, fromHexString, getPorterUris, @@ -7,10 +7,9 @@ import { SigningCoordinatorAgent, TacoSignature, TacoSignResult, - toBase64, + toCoreUserOperation, toHexString, - UserOperation, - UserOperationSignatureRequest, + UserOperationToSign, } from '@nucypher/shared'; import { ethers } from 'ethers'; @@ -71,7 +70,7 @@ export async function signUserOp( domain: Domain, cohortId: number, chainId: number, - userOp: UserOperation, + userOp: UserOperationToSign, aaVersion: 'mdt' | '0.8.0' | string, context?: ConditionContext, porterUris?: string[], @@ -93,23 +92,20 @@ export async function signUserOp( cohortId, ); - const pythonUserOp = convertUserOperationToPython(userOp); - + const coreContext = context ? await context.toCoreContext() : null; + const coreUserOp = toCoreUserOperation(userOp); const signingRequest = new UserOperationSignatureRequest( - pythonUserOp, - aaVersion, + coreUserOp, cohortId, - chainId, - context || {}, - 'userop', + BigInt(chainId), + aaVersion, + coreContext, ); - const signingRequests: Record = Object.fromEntries( - signers.map((signer) => [ - signer.provider, - toBase64(signingRequest.toBytes()), - ]), - ); + const signingRequests: Record = + Object.fromEntries( + signers.map((signer) => [signer.provider, signingRequest]), + ); // Build signing request for the user operation const porterSignResult: TacoSignResult = await porter.signUserOp( @@ -119,7 +115,7 @@ export async function signUserOp( const hashToSignatures: Map< string, - { [ursulaAddress: string]: TacoSignature } + Record > = new Map(); // Single pass: decode signatures and populate signingResults diff --git a/packages/taco/src/types.ts b/packages/taco/src/types.ts index 8871cc860..d41b148c2 100644 --- a/packages/taco/src/types.ts +++ b/packages/taco/src/types.ts @@ -4,5 +4,5 @@ export { Context as CoreContext, } from '@nucypher/nucypher-core'; -// Re-export UserOperation from shared package to avoid duplication -export type { UserOperation } from '@nucypher/shared'; +// Re-export UserOperationToSign from shared package to avoid duplication +export { UserOperationToSign } from '@nucypher/shared'; diff --git a/packages/taco/test/taco-sign.test.ts b/packages/taco/test/taco-sign.test.ts index 824ef1cad..aa481fc13 100644 --- a/packages/taco/test/taco-sign.test.ts +++ b/packages/taco/test/taco-sign.test.ts @@ -1,11 +1,18 @@ import { - convertUserOperationToPython, + UserOperation, + UserOperationSignatureRequest, +} from '@nucypher/nucypher-core'; +import { + fromHexString, + initialize, PorterClient, SigningCoordinatorAgent, + toCoreUserOperation, + UserOperationToSign, } from '@nucypher/shared'; import { fakePorterUri } from '@nucypher/test-utils'; import { ethers } from 'ethers'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { ContractCondition } from '../src/conditions/base/contract'; import { RpcCondition } from '../src/conditions/base/rpc'; @@ -13,10 +20,70 @@ import { CompoundCondition } from '../src/conditions/compound-condition'; import { ConditionExpression } from '../src/conditions/condition-expr'; import { setSigningCohortConditions, signUserOp } from '../src/sign'; +function getNumberValue(value: bigint | number): bigint { + return typeof value === 'bigint' ? value : BigInt(value); +} + +function checkUserOpEquality(op1: UserOperationToSign, op2: UserOperation) { + expect(op1.sender).toEqual(op2.sender); + + expect(getNumberValue(op1.nonce)).toEqual(getNumberValue(op2.nonce)); + + const callData = + op1.callData instanceof Uint8Array + ? op1.callData + : fromHexString(op1.callData); + expect(callData).toEqual(callData); + + expect(getNumberValue(op1.callGasLimit)).toEqual(op2.callGasLimit); + expect(getNumberValue(op1.verificationGasLimit)).toEqual( + op2.verificationGasLimit, + ); + expect(getNumberValue(op1.preVerificationGas)).toEqual( + op2.preVerificationGas, + ); + expect(getNumberValue(op1.maxFeePerGas)).toEqual(op2.maxFeePerGas); + expect(getNumberValue(op1.maxPriorityFeePerGas)).toEqual( + op2.maxPriorityFeePerGas, + ); + + if (op1.factory === undefined) { + expect(op2.factory).toBeUndefined(); + } else { + expect(op1.factory).toEqual(op2.factory); + const factoryData = + op1.factoryData instanceof Uint8Array + ? op1.factoryData + : fromHexString(op1.factoryData || '0x'); + expect(factoryData).toEqual(op2.factoryData); + } + + if (op1.paymaster === undefined) { + expect(op2.paymaster).toBeUndefined(); + } else { + expect(op1.paymaster).toEqual(op2.paymaster); + expect( + getNumberValue(op1.paymasterVerificationGasLimit || BigInt(0)), + ).toEqual(op2.paymasterVerificationGasLimit); + expect(getNumberValue(op1.paymasterPostOpGasLimit || BigInt(0))).toEqual( + op2.paymasterPostOpGasLimit, + ); + const paymasterData = + op1.paymasterData instanceof Uint8Array + ? op1.paymasterData + : fromHexString(op1.paymasterData || '0x'); + expect(paymasterData).toEqual(op2.paymasterData); + } +} + describe('TACo Signing', () => { let porterSignUserOpMock: ReturnType; let mockProvider: ethers.providers.Provider; + beforeAll(async () => { + await initialize(); + }); + beforeEach(() => { porterSignUserOpMock = vi.fn(); mockProvider = {} as ethers.providers.Provider; @@ -31,23 +98,94 @@ describe('TACo Signing', () => { vi.spyOn(SigningCoordinatorAgent, 'getThreshold').mockResolvedValue(2); }); + describe('toCoreUserOperation', () => { + const userOpToSign: UserOperationToSign = { + sender: '0x742D35Cc6634C0532925A3b8D33c9c0E7B66C8E8', + nonce: BigInt(1), + callData: fromHexString('0xabc'), + callGasLimit: BigInt(131072), + verificationGasLimit: BigInt(86016), + preVerificationGas: BigInt(4096), + maxFeePerGas: BigInt(2748), + maxPriorityFeePerGas: BigInt(291), + }; + + it('should convert base fields', () => { + const coreUserOp = toCoreUserOperation(userOpToSign); + checkUserOpEquality(userOpToSign, coreUserOp); + }); + it('should convert factory optional fields', () => { + const updatedUserOp: UserOperationToSign = { + ...userOpToSign, + factory: '0x000000000000000000000000000000000000000A', + factoryData: fromHexString('0xdef'), + }; + const coreUserOp = toCoreUserOperation(updatedUserOp); + checkUserOpEquality(updatedUserOp, coreUserOp); + }); + it('should convert factory data to default when not specified but factory is', () => { + const updatedUserOp: UserOperationToSign = { + ...userOpToSign, + factory: '0x000000000000000000000000000000000000000A', + }; + const coreUserOp = toCoreUserOperation(updatedUserOp); + checkUserOpEquality(updatedUserOp, coreUserOp); + }); + it('should convert paymaster optional fields', () => { + const updatedUserOp: UserOperationToSign = { + ...userOpToSign, + paymaster: '0x000000000000000000000000000000000000000C', + paymasterVerificationGasLimit: BigInt(50000), + paymasterPostOpGasLimit: BigInt(30000), + paymasterData: fromHexString('0xdef'), + }; + const coreUserOp = toCoreUserOperation(updatedUserOp); + checkUserOpEquality(updatedUserOp, coreUserOp); + }); + it('should convert paymaster optional fields to defaults when not specified but paymaster is', () => { + const updatedUserOp: UserOperationToSign = { + ...userOpToSign, + paymaster: '0x000000000000000000000000000000000000000C', + }; + const coreUserOp = toCoreUserOperation(updatedUserOp); + checkUserOpEquality(updatedUserOp, coreUserOp); + }); + it('should handle alternative types: number and byte fields', () => { + const updatedUserOp: UserOperationToSign = { + ...userOpToSign, + // number instead of bigint + nonce: 1, + callData: '0xabc', // hex instead of byte array + callGasLimit: 131072, + verificationGasLimit: 86016, + preVerificationGas: 4096, + maxFeePerGas: 2748, + maxPriorityFeePerGas: 291, + // include optional fields + factory: '0x000000000000000000000000000000000000000A', + factoryData: '0xabc', // hex instead of byte array + + paymaster: '0x000000000000000000000000000000000000000C', + // number instead of big int + paymasterVerificationGasLimit: 50000, + paymasterPostOpGasLimit: 30000, + paymasterData: '0xdef', // hex instead of byte array + }; + const coreUserOp = toCoreUserOperation(updatedUserOp); + checkUserOpEquality(updatedUserOp, coreUserOp); + }); + }); + describe('signUserOp', () => { - const userOp = { + const userOp: UserOperationToSign = { sender: '0x742D35Cc6634C0532925A3b8D33c9c0E7B66C8E8', - nonce: 1, - factory: '0x0000000000000000000000000000000000000000', - factoryData: '0x', - callData: '0xabc', - callGasLimit: 131072, - verificationGasLimit: 86016, - preVerificationGas: 4096, - maxFeePerGas: 2748, - maxPriorityFeePerGas: 291, - paymaster: '0x0000000000000000000000000000000000000000', - paymasterVerificationGasLimit: 0, - paymasterPostOpGasLimit: 0, - paymasterData: '0x', - signature: '0x', + nonce: BigInt(1), + callData: fromHexString('0xabc'), + callGasLimit: BigInt(131072), + verificationGasLimit: BigInt(86016), + preVerificationGas: BigInt(4096), + maxFeePerGas: BigInt(2748), + maxPriorityFeePerGas: BigInt(291), }; const chainId = 1; const cohortId = 5; @@ -88,34 +226,26 @@ describe('TACo Signing', () => { porterUris, ); - const pythonUserOp = convertUserOperationToPython(userOp); - expect(porterSignUserOpMock).toHaveBeenCalledWith( { - '0xnode1': btoa( - JSON.stringify({ - user_op: JSON.stringify(pythonUserOp), - aa_version: validAAVersion, - cohort_id: cohortId, - chain_id: chainId, - context: {}, - signature_type: 'userop', - }), - ), - '0xnode2': btoa( - JSON.stringify({ - user_op: JSON.stringify(pythonUserOp), - aa_version: validAAVersion, - cohort_id: cohortId, - chain_id: chainId, - context: {}, - signature_type: 'userop', - }), - ), + '0xnode1': expect.any(UserOperationSignatureRequest), + '0xnode2': expect.any(UserOperationSignatureRequest), }, threshold, ); + const call = porterSignUserOpMock.mock.calls.at(-1)!; + const [op] = call; + + const requests = [op['0xnode1'], op['0xnode2']]; + requests.forEach((element) => { + checkUserOpEquality(userOp, element.userOp); + expect(element.aaVersion).toEqual(validAAVersion); + expect(element.cohortId).toEqual(cohortId); + expect(element.chainId).toEqual(BigInt(chainId)); + expect(element.context).toBeUndefined(); + }); + expect(result).toEqual({ messageHash: '0xhash1', aggregatedSignature: '0xdeadbeef', @@ -152,32 +282,24 @@ describe('TACo Signing', () => { `Threshold of signatures not met; TACo signing failed with errors: ${JSON.stringify(errors)}`, ); - const pythonUserOp = convertUserOperationToPython(userOp); expect(porterSignUserOpMock).toHaveBeenCalledWith( { - '0xnode1': btoa( - JSON.stringify({ - user_op: JSON.stringify(pythonUserOp), - aa_version: aaVersion, - cohort_id: cohortId, - chain_id: chainId, - context: {}, - signature_type: 'userop', - }), - ), - '0xnode2': btoa( - JSON.stringify({ - user_op: JSON.stringify(pythonUserOp), - aa_version: aaVersion, - cohort_id: cohortId, - chain_id: chainId, - context: {}, - signature_type: 'userop', - }), - ), + '0xnode1': expect.any(UserOperationSignatureRequest), + '0xnode2': expect.any(UserOperationSignatureRequest), }, threshold, ); + const call = porterSignUserOpMock.mock.calls.at(-1)!; + const [op] = call; + + const requests = [op['0xnode1'], op['0xnode2']]; + requests.forEach((element) => { + checkUserOpEquality(userOp, element.userOp); + expect(element.aaVersion).toEqual(aaVersion); + expect(element.cohortId).toEqual(cohortId); + expect(element.chainId).toEqual(BigInt(chainId)); + expect(element.context).toBeUndefined(); + }); }); it('should handle insufficient signatures in Porter response', async () => { const signingResults = { From 1cd124e9817f2b3a9a74d8d0519bfe00eee57b7b Mon Sep 17 00:00:00 2001 From: derekpierre Date: Fri, 24 Oct 2025 20:06:03 -0400 Subject: [PATCH 05/11] Use latest update for `SignatureResponse` from `nucypher-core` which now includes signer address. Modify Porter response to handle SignatureResponse and not a tuple of signer address and SignatureResponse. Update tests. --- packages/shared/src/porter.ts | 14 +-- packages/shared/test/porter.test.ts | 136 +++++++++++++--------------- 2 files changed, 67 insertions(+), 83 deletions(-) diff --git a/packages/shared/src/porter.ts b/packages/shared/src/porter.ts index 796829d34..1e466c6cf 100644 --- a/packages/shared/src/porter.ts +++ b/packages/shared/src/porter.ts @@ -167,10 +167,7 @@ type PostTacoSignRequest = { type TacoSignResponse = { readonly result: { readonly signing_results: { - readonly signatures: Record< - ChecksumAddress, - [ChecksumAddress, Base64EncodedBytes] - >; + readonly signatures: Record; readonly errors: Record; }; }; @@ -355,17 +352,16 @@ export class PorterClient { const { signatures, errors } = resp.data.result.signing_results; const signingResults: { [ursulaAddress: string]: TacoSignature } = {}; - for (const [ - ursulaAddress, - [signerAddress, signatureResponseBase64], - ] of Object.entries(signatures || {})) { + for (const [ursulaAddress, signatureResponseBase64] of Object.entries( + signatures || {}, + )) { const signatureResponse = SignatureResponse.fromBytes( fromBase64(signatureResponseBase64), ); signingResults[ursulaAddress] = { messageHash: `0x${toHexString(signatureResponse.hash)}`, signature: `0x${toHexString(signatureResponse.signature)}`, - signerAddress, + signerAddress: signatureResponse.signer, }; } diff --git a/packages/shared/test/porter.test.ts b/packages/shared/test/porter.test.ts index 6fa6d7b4f..b34c3e238 100644 --- a/packages/shared/test/porter.test.ts +++ b/packages/shared/test/porter.test.ts @@ -64,9 +64,9 @@ const createMockSignResponse = (errorCase?: boolean) => ({ signatures: errorCase ? { '0xabcd': [ - '0xefff', toBase64( new SignatureResponse( + '0x0000000000000000000000000000000000000001', fromHexString('0x1234'), fromHexString('0xbeef'), 0, @@ -76,9 +76,9 @@ const createMockSignResponse = (errorCase?: boolean) => ({ } : { '0x1234': [ - '0x5678', toBase64( new SignatureResponse( + '0x0000000000000000000000000000000000000002', fromHexString('0x1234'), fromHexString('0xdead'), 0, @@ -86,9 +86,9 @@ const createMockSignResponse = (errorCase?: boolean) => ({ ), ], '0xabcd': [ - '0xefff', toBase64( new SignatureResponse( + '0x0000000000000000000000000000000000000001', fromHexString('0x1234'), fromHexString('0xbeef'), 0, @@ -236,12 +236,12 @@ describe('PorterClient Signing', () => { '0x1234': { messageHash: '0x1234', signature: '0xdead', - signerAddress: '0x5678', + signerAddress: '0x0000000000000000000000000000000000000002', }, '0xabcd': { messageHash: '0x1234', signature: '0xbeef', - signerAddress: '0xefff', + signerAddress: '0x0000000000000000000000000000000000000001', }, }, errors: {}, @@ -284,7 +284,7 @@ describe('PorterClient Signing', () => { '0xabcd': { messageHash: '0x1234', signature: '0xbeef', - signerAddress: '0xefff', + signerAddress: '0x0000000000000000000000000000000000000001', }, }, errors: { @@ -300,26 +300,22 @@ describe('PorterClient Signing', () => { result: { signing_results: { signatures: { - '0x1234': [ - '0x5678', - toBase64( - new SignatureResponse( - fromHexString('0x1234'), - fromHexString('0xdead'), - 0, - ).toBytes(), - ), - ], - '0xabcd': [ - '0xefff', - toBase64( - new SignatureResponse( - fromHexString('0xdddd'), // Different message hash - fromHexString('0xbeef'), - 0, - ).toBytes(), - ), - ], + '0x1234': toBase64( + new SignatureResponse( + '0x0000000000000000000000000000000000000002', + fromHexString('0x1234'), + fromHexString('0xdead'), + 0, + ).toBytes(), + ), + '0xabcd': toBase64( + new SignatureResponse( + '0x0000000000000000000000000000000000000001', + fromHexString('0xdddd'), // Different message hash + fromHexString('0xbeef'), + 0, + ).toBytes(), + ), }, errors: {}, }, @@ -349,12 +345,12 @@ describe('PorterClient Signing', () => { '0x1234': { messageHash: '0x1234', signature: '0xdead', - signerAddress: '0x5678', + signerAddress: '0x0000000000000000000000000000000000000002', }, '0xabcd': { messageHash: '0xdddd', // Different hash signature: '0xbeef', - signerAddress: '0xefff', + signerAddress: '0x0000000000000000000000000000000000000001', }, }, errors: {}, // No errors - mismatched hashes don't generate errors, just prevent aggregation @@ -368,16 +364,14 @@ describe('PorterClient Signing', () => { result: { signing_results: { signatures: { - '0x1234': [ - '0x5678', - toBase64( - new SignatureResponse( - fromHexString('0x1234'), - fromHexString('0xdead'), - 0, - ).toBytes(), - ), - ], + '0x1234': toBase64( + new SignatureResponse( + '0x0000000000000000000000000000000000000002', + fromHexString('0x1234'), + fromHexString('0xdead'), + 0, + ).toBytes(), + ), // Only 1 signature, but threshold is 2 }, errors: {}, @@ -408,7 +402,7 @@ describe('PorterClient Signing', () => { '0x1234': { messageHash: '0x1234', signature: '0xdead', - signerAddress: '0x5678', + signerAddress: '0x0000000000000000000000000000000000000002', }, }, errors: {}, @@ -433,12 +427,12 @@ describe('PorterClient Signing', () => { '0x1234': { messageHash: '0x1234', signature: '0xdead', - signerAddress: '0x5678', + signerAddress: '0x0000000000000000000000000000000000000002', }, '0xabcd': { messageHash: '0x1234', signature: '0xbeef', - signerAddress: '0xefff', + signerAddress: '0x0000000000000000000000000000000000000001', }, }, errors: {}, @@ -452,36 +446,30 @@ describe('PorterClient Signing', () => { result: { signing_results: { signatures: { - '0x1234': [ - '0x5678', - toBase64( - new SignatureResponse( - fromHexString('0x0001'), - fromHexString('0xdead'), - 0, - ).toBytes(), - ), - ], - '0xabcd': [ - '0xefff', - toBase64( - new SignatureResponse( - fromHexString('0x0001'), // Same hash, meets threshold - fromHexString('0xbeef'), - 0, - ).toBytes(), - ), - ], - '0xdef0': [ - '0xabc1', - toBase64( - new SignatureResponse( - fromHexString('0x0002'), // Different hash, doesn't meet threshold - fromHexString('0xcafe'), - 0, - ).toBytes(), - ), - ], + '0x1234': toBase64( + new SignatureResponse( + '0x0000000000000000000000000000000000000002', + fromHexString('0x0001'), + fromHexString('0xdead'), + 0, + ).toBytes(), + ), + '0xabcd': toBase64( + new SignatureResponse( + '0x0000000000000000000000000000000000000001', + fromHexString('0x0001'), // Same hash, meets threshold + fromHexString('0xbeef'), + 0, + ).toBytes(), + ), + '0xdef0': toBase64( + new SignatureResponse( + '0x0000000000000000000000000000000000000003', + fromHexString('0x0002'), // Different hash, doesn't meet threshold + fromHexString('0xcafe'), + 0, + ).toBytes(), + ), }, errors: {}, }, @@ -513,17 +501,17 @@ describe('PorterClient Signing', () => { '0x1234': { messageHash: '0x0001', signature: '0xdead', - signerAddress: '0x5678', + signerAddress: '0x0000000000000000000000000000000000000002', }, '0xabcd': { messageHash: '0x0001', signature: '0xbeef', - signerAddress: '0xefff', + signerAddress: '0x0000000000000000000000000000000000000001', }, '0xdef0': { messageHash: '0x0002', signature: '0xcafe', - signerAddress: '0xabc1', + signerAddress: '0x0000000000000000000000000000000000000003', }, }, errors: {}, From 76f6f79ee5fb0837b542440ad6120591f055f2a7 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Mon, 27 Oct 2025 10:46:12 -0400 Subject: [PATCH 06/11] Add conversion of PackedUserOperationToSign -> PackedUserOperation (core object). Add type guard for PackedUserOperationToSign. Add helper methods for converting number values to bigint, and data values to Uint8Array. --- packages/shared/src/types.ts | 99 ++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 39 deletions(-) diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index 23ad354fb..2f4a2c301 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -1,4 +1,4 @@ -import { UserOperation } from '@nucypher/nucypher-core'; +import { PackedUserOperation, UserOperation } from '@nucypher/nucypher-core'; import { fromHexString } from './utils'; @@ -25,59 +25,57 @@ export type UserOperationToSign = { signature?: `0x${string}` | Uint8Array | undefined; }; +export type PackedUserOperationToSign = { + sender: `0x${string}`; + nonce: bigint | number; + initCode: `0x${string}` | Uint8Array; + callData: `0x${string}` | Uint8Array; + accountGasLimit: `0x${string}` | Uint8Array; + preVerificationGas: bigint | number; + gasFees: `0x${string}` | Uint8Array; + paymasterAndData: `0x${string}` | Uint8Array; +}; + +function getBigIntValue(value: bigint | number): bigint { + return typeof value === 'bigint' ? value : BigInt(value); +} + +function getUint8ArrayValue(value: `0x${string}` | Uint8Array): Uint8Array { + return value instanceof Uint8Array ? value : fromHexString(value); +} + export function toCoreUserOperation( userOperation: UserOperationToSign, ): UserOperation { const userOp = new UserOperation( userOperation.sender, - typeof userOperation.nonce === 'bigint' - ? userOperation.nonce - : BigInt(userOperation.nonce), - userOperation.callData instanceof Uint8Array - ? userOperation.callData - : fromHexString(userOperation.callData), - typeof userOperation.callGasLimit === 'bigint' - ? userOperation.callGasLimit - : BigInt(userOperation.callGasLimit), - typeof userOperation.verificationGasLimit === 'bigint' - ? userOperation.verificationGasLimit - : BigInt(userOperation.verificationGasLimit), - typeof userOperation.preVerificationGas === 'bigint' - ? userOperation.preVerificationGas - : BigInt(userOperation.preVerificationGas || 0), - typeof userOperation.maxFeePerGas === 'bigint' - ? userOperation.maxFeePerGas - : BigInt(userOperation.maxFeePerGas), - typeof userOperation.maxPriorityFeePerGas === 'bigint' - ? userOperation.maxPriorityFeePerGas - : BigInt(userOperation.maxPriorityFeePerGas), + getBigIntValue(userOperation.nonce), + getUint8ArrayValue(userOperation.callData), + getBigIntValue(userOperation.callGasLimit), + getBigIntValue(userOperation.verificationGasLimit), + getBigIntValue(userOperation.preVerificationGas), + getBigIntValue(userOperation.maxFeePerGas), + getBigIntValue(userOperation.maxPriorityFeePerGas), ); // optional factory data if (userOperation.factory) { - const factory_data = - userOperation.factoryData instanceof Uint8Array - ? userOperation.factoryData - : fromHexString(userOperation.factoryData || '0x'); + const factory_data = getUint8ArrayValue(userOperation.factoryData || '0x'); userOp.setFactoryData(userOperation.factory, factory_data); } // optional paymaster data if (userOperation.paymaster) { - const paymaster_data = - userOperation.paymasterData instanceof Uint8Array - ? userOperation.paymasterData - : fromHexString(userOperation.paymasterData || '0x'); - const paymaster_verification_gas_limit = - typeof userOperation.paymasterVerificationGasLimit === 'bigint' - ? userOperation.paymasterVerificationGasLimit - : BigInt(userOperation.paymasterVerificationGasLimit || 0); - const paymaster_post_op_gas_limit = - typeof userOperation.paymasterPostOpGasLimit === 'bigint' - ? userOperation.paymasterPostOpGasLimit - : BigInt(userOperation.paymasterPostOpGasLimit || 0); - + const paymaster_verification_gas_limit = getBigIntValue( + userOperation.paymasterVerificationGasLimit || 0, + ); + const paymaster_post_op_gas_limit = getBigIntValue( + userOperation.paymasterPostOpGasLimit || 0, + ); + const paymaster_data = getUint8ArrayValue( + userOperation.paymasterData || '0x', + ); userOp.setPaymasterData( userOperation.paymaster, paymaster_verification_gas_limit, @@ -88,3 +86,26 @@ export function toCoreUserOperation( return userOp; } + +export function toCorePackedUserOperation( + packedUserOperation: PackedUserOperationToSign, +): PackedUserOperation { + const packedUserOp = new PackedUserOperation( + packedUserOperation.sender, + getBigIntValue(packedUserOperation.nonce), + getUint8ArrayValue(packedUserOperation.initCode), + getUint8ArrayValue(packedUserOperation.callData), + getUint8ArrayValue(packedUserOperation.accountGasLimit), + getBigIntValue(packedUserOperation.preVerificationGas), + getUint8ArrayValue(packedUserOperation.gasFees), + getUint8ArrayValue(packedUserOperation.paymasterAndData), + ); + + return packedUserOp; +} + +export function isPackedUserOperation( + op: UserOperationToSign | PackedUserOperationToSign, +): op is PackedUserOperationToSign { + return 'initCode' in op && 'gasFees' in op; +} From f96e55b502b78cf59e97d9f07b26698b4efdaf62 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Mon, 27 Oct 2025 10:49:48 -0400 Subject: [PATCH 07/11] Add support allowing both UserOperations and PackedUserOperations to be signed. --- .../contracts/agents/signing-coordinator.ts | 2 +- packages/shared/src/porter.ts | 8 +- packages/taco/src/sign.ts | 74 +++++++++++++++---- 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/packages/shared/src/contracts/agents/signing-coordinator.ts b/packages/shared/src/contracts/agents/signing-coordinator.ts index 251647576..c2fe8b507 100644 --- a/packages/shared/src/contracts/agents/signing-coordinator.ts +++ b/packages/shared/src/contracts/agents/signing-coordinator.ts @@ -5,7 +5,7 @@ import { Domain } from '../../porter'; import { SigningCoordinator__factory } from '../ethers-typechain'; import { SigningCoordinator } from '../ethers-typechain/SigningCoordinator'; -type SignerInfo = { +export type SignerInfo = { operator: string; provider: string; signature: string; diff --git a/packages/shared/src/porter.ts b/packages/shared/src/porter.ts index 1e466c6cf..c79479db7 100644 --- a/packages/shared/src/porter.ts +++ b/packages/shared/src/porter.ts @@ -2,6 +2,7 @@ import { CapsuleFrag, EncryptedThresholdDecryptionRequest, EncryptedThresholdDecryptionResponse, + PackedUserOperationSignatureRequest, PublicKey, RetrievalKit, SignatureResponse, @@ -331,7 +332,10 @@ export class PorterClient { } public async signUserOp( - signingRequests: Record, + signingRequests: Record< + string, + UserOperationSignatureRequest | PackedUserOperationSignatureRequest + >, threshold: number, ): Promise { const data: PostTacoSignRequest = { @@ -356,7 +360,7 @@ export class PorterClient { signatures || {}, )) { const signatureResponse = SignatureResponse.fromBytes( - fromBase64(signatureResponseBase64), + fromBase64(signatureResponseBase64 as string), ); signingResults[ursulaAddress] = { messageHash: `0x${toHexString(signatureResponse.hash)}`, diff --git a/packages/taco/src/sign.ts b/packages/taco/src/sign.ts index 7df6bd2cb..e60fd94e0 100644 --- a/packages/taco/src/sign.ts +++ b/packages/taco/src/sign.ts @@ -1,12 +1,19 @@ -import { UserOperationSignatureRequest } from '@nucypher/nucypher-core'; +import { + PackedUserOperationSignatureRequest, + UserOperationSignatureRequest, +} from '@nucypher/nucypher-core'; import { Domain, fromHexString, getPorterUris, + isPackedUserOperation, + PackedUserOperationToSign, PorterClient, + SignerInfo, SigningCoordinatorAgent, TacoSignature, TacoSignResult, + toCorePackedUserOperation, toCoreUserOperation, toHexString, UserOperationToSign, @@ -52,6 +59,53 @@ function aggregateSignatures( return `0x${toHexString(new Uint8Array(allBytes))}`; } +async function makeSigningRequests( + signers: SignerInfo[], + cohortId: number, + chainId: number, + userOp: UserOperationToSign | PackedUserOperationToSign, + aaVersion: string, + context?: ConditionContext, +): Promise< + Record< + string, + PackedUserOperationSignatureRequest | UserOperationSignatureRequest + > +> { + const coreContext = context ? await context.toCoreContext() : null; + + let signingRequest: + | PackedUserOperationSignatureRequest + | UserOperationSignatureRequest; + if (isPackedUserOperation(userOp)) { + const corePackedUserOp = toCorePackedUserOperation(userOp); + signingRequest = new PackedUserOperationSignatureRequest( + corePackedUserOp, + cohortId, + BigInt(chainId), + aaVersion, + coreContext, + ); + } else { + const coreUserOp = toCoreUserOperation(userOp); + signingRequest = new UserOperationSignatureRequest( + coreUserOp, + cohortId, + BigInt(chainId), + aaVersion, + coreContext, + ); + } + + const signingRequests: Record< + string, + PackedUserOperationSignatureRequest | UserOperationSignatureRequest + > = Object.fromEntries( + signers.map((signer) => [signer.provider, signingRequest]), + ); + return signingRequests; +} + /** * Signs a UserOperation. * @param provider - The Ethereum provider to use for signing. @@ -70,7 +124,7 @@ export async function signUserOp( domain: Domain, cohortId: number, chainId: number, - userOp: UserOperationToSign, + userOp: UserOperationToSign | PackedUserOperationToSign, aaVersion: 'mdt' | '0.8.0' | string, context?: ConditionContext, porterUris?: string[], @@ -92,21 +146,15 @@ export async function signUserOp( cohortId, ); - const coreContext = context ? await context.toCoreContext() : null; - const coreUserOp = toCoreUserOperation(userOp); - const signingRequest = new UserOperationSignatureRequest( - coreUserOp, + const signingRequests = await makeSigningRequests( + signers, cohortId, - BigInt(chainId), + chainId, + userOp, aaVersion, - coreContext, + context, ); - const signingRequests: Record = - Object.fromEntries( - signers.map((signer) => [signer.provider, signingRequest]), - ); - // Build signing request for the user operation const porterSignResult: TacoSignResult = await porter.signUserOp( signingRequests, From e104bdf39072ae519cffe03bc575ee278baf2d4d Mon Sep 17 00:00:00 2001 From: derekpierre Date: Mon, 27 Oct 2025 14:47:03 -0400 Subject: [PATCH 08/11] Add tests for signing of packed user operation objects. --- packages/shared/test/porter.test.ts | 60 +++++++++-- packages/taco/test/taco-sign.test.ts | 144 ++++++++++++++++++++++++--- 2 files changed, 182 insertions(+), 22 deletions(-) diff --git a/packages/shared/test/porter.test.ts b/packages/shared/test/porter.test.ts index b34c3e238..c6d84178e 100644 --- a/packages/shared/test/porter.test.ts +++ b/packages/shared/test/porter.test.ts @@ -3,6 +3,8 @@ import axios, { HttpStatusCode } from 'axios'; import { beforeAll, describe, expect, it, MockInstance, vi } from 'vitest'; import { + PackedUserOperation, + PackedUserOperationSignatureRequest, SignatureResponse, UserOperation, UserOperationSignatureRequest, @@ -204,13 +206,13 @@ describe('PorterClient Signing', () => { new UserOperationSignatureRequest( new UserOperation( '0x000000000000000000000000000000000000000a', - BigInt(0), // nonce + BigInt(123), // nonce fromHexString('0xabc'), // callData - BigInt(0), // callGasLimit - BigInt(0), // verificationGasLimit - BigInt(0), // preVerificationGasLimit - BigInt(0), // maxFeePerGas - BigInt(0), // maxPriorityFeePerGas + BigInt(456), // callGasLimit + BigInt(789), // verificationGasLimit + BigInt(101112), // preVerificationGasLimit + BigInt(131415), // maxFeePerGas + BigInt(161718), // maxPriorityFeePerGas ), 1, // cohort ID BigInt(1), // chain ID @@ -248,6 +250,52 @@ describe('PorterClient Signing', () => { }); }); + it('should successfully sign a PackedUserOperation', async () => { + const packedUserOperationSignatureRequest = + new PackedUserOperationSignatureRequest( + new PackedUserOperation( + '0x000000000000000000000000000000000000000a', + BigInt(123), // nonce + fromHexString('0xabc'), // initCode + fromHexString('0xdef'), // callData + fromHexString('0x01020304'), // accountGasLimit + BigInt(101112), // preVerificationGas + fromHexString('0x05060708'), // gasFees + fromHexString('0x090a0b0c'), // paymasterAndData + ), + 1, // cohort ID + BigInt(1), // chain ID + 'mdt', + null, + ); + + mockSignUserOp(true); + const porterClient = new PorterClient(fakePorterUris[2]); + const result = await porterClient.signUserOp( + { + '0x1234': packedUserOperationSignatureRequest, + '0xabcd': packedUserOperationSignatureRequest, + }, + 2, + ); + + expect(result).toEqual({ + signingResults: { + '0x1234': { + messageHash: '0x1234', + signature: '0xdead', + signerAddress: '0x0000000000000000000000000000000000000002', + }, + '0xabcd': { + messageHash: '0x1234', + signature: '0xbeef', + signerAddress: '0x0000000000000000000000000000000000000001', + }, + }, + errors: {}, + }); + }); + it('should handle UserOperation signing failures', async () => { const userOpSignatureRequest = createUserSignatureRequest(); diff --git a/packages/taco/test/taco-sign.test.ts b/packages/taco/test/taco-sign.test.ts index aa481fc13..93bf040fa 100644 --- a/packages/taco/test/taco-sign.test.ts +++ b/packages/taco/test/taco-sign.test.ts @@ -1,12 +1,17 @@ import { + PackedUserOperation, + PackedUserOperationSignatureRequest, UserOperation, UserOperationSignatureRequest, } from '@nucypher/nucypher-core'; import { fromHexString, initialize, + isPackedUserOperation, + PackedUserOperationToSign, PorterClient, SigningCoordinatorAgent, + toCorePackedUserOperation, toCoreUserOperation, UserOperationToSign, } from '@nucypher/shared'; @@ -24,10 +29,52 @@ function getNumberValue(value: bigint | number): bigint { return typeof value === 'bigint' ? value : BigInt(value); } +function checkPackedUserOpEquality( + op1: PackedUserOperationToSign, + op2: PackedUserOperation, +) { + expect(op1.sender).toEqual(op2.sender); + expect(getNumberValue(op1.nonce)).toEqual(op2.nonce); + + const initCode = + op1.initCode instanceof Uint8Array + ? op1.initCode + : fromHexString(op1.initCode); + expect(initCode).toEqual(op2.initCode); + + const callData = + op1.callData instanceof Uint8Array + ? op1.callData + : fromHexString(op1.callData); + expect(callData).toEqual(op2.callData); + + const accountGasLimit = + op1.accountGasLimit instanceof Uint8Array + ? op1.accountGasLimit + : fromHexString(op1.accountGasLimit); + expect(accountGasLimit).toEqual(op2.accountGasLimits); + + expect(getNumberValue(op1.preVerificationGas)).toEqual( + op2.preVerificationGas, + ); + + const gasFees = + op1.gasFees instanceof Uint8Array + ? op1.gasFees + : fromHexString(op1.gasFees); + expect(gasFees).toEqual(op2.gasFees); + + const paymasterAndData = + op1.paymasterAndData instanceof Uint8Array + ? op1.paymasterAndData + : fromHexString(op1.paymasterAndData); + expect(paymasterAndData).toEqual(op2.paymasterAndData); +} + function checkUserOpEquality(op1: UserOperationToSign, op2: UserOperation) { expect(op1.sender).toEqual(op2.sender); - expect(getNumberValue(op1.nonce)).toEqual(getNumberValue(op2.nonce)); + expect(getNumberValue(op1.nonce)).toEqual(op2.nonce); const callData = op1.callData instanceof Uint8Array @@ -153,9 +200,9 @@ describe('TACo Signing', () => { it('should handle alternative types: number and byte fields', () => { const updatedUserOp: UserOperationToSign = { ...userOpToSign, - // number instead of bigint + // number instead of bigint, hex instead of byte array nonce: 1, - callData: '0xabc', // hex instead of byte array + callData: '0xabc', callGasLimit: 131072, verificationGasLimit: 86016, preVerificationGas: 4096, @@ -163,18 +210,50 @@ describe('TACo Signing', () => { maxPriorityFeePerGas: 291, // include optional fields factory: '0x000000000000000000000000000000000000000A', - factoryData: '0xabc', // hex instead of byte array + factoryData: '0xabc', paymaster: '0x000000000000000000000000000000000000000C', // number instead of big int paymasterVerificationGasLimit: 50000, paymasterPostOpGasLimit: 30000, - paymasterData: '0xdef', // hex instead of byte array + paymasterData: '0xdef', }; const coreUserOp = toCoreUserOperation(updatedUserOp); checkUserOpEquality(updatedUserOp, coreUserOp); }); }); + describe('toCorePackedUserOperation', () => { + const packedUserOpToSign: PackedUserOperationToSign = { + sender: '0x742D35Cc6634C0532925A3b8D33c9c0E7B66C8E8', + nonce: BigInt(123), + initCode: fromHexString('0xabc'), + callData: fromHexString('0xdef'), + accountGasLimit: fromHexString('0x01020304'), + preVerificationGas: BigInt(101112), + gasFees: fromHexString('0x05060708'), + paymasterAndData: fromHexString('0x090a0b0c'), + }; + + it('should convert base fields', () => { + const corePackedUserOp = toCorePackedUserOperation(packedUserOpToSign); + checkPackedUserOpEquality(packedUserOpToSign, corePackedUserOp); + }); + it('should handle alternative types: number and byte fields', () => { + const updatedPackedUserOp: PackedUserOperationToSign = { + // number instead of bigint, hex instead of byte array + ...packedUserOpToSign, + nonce: 1, + initCode: '0xabc', + callData: '0xdef', + accountGasLimit: '0x01020304', + preVerificationGas: 4096, + gasFees: '0x05060708', + paymasterAndData: '0x090a0b0c', + }; + const corePackedUserOp = toCorePackedUserOperation(updatedPackedUserOp); + checkPackedUserOpEquality(updatedPackedUserOp, corePackedUserOp); + }); + }); describe('signUserOp', () => { const userOp: UserOperationToSign = { @@ -187,15 +266,34 @@ describe('TACo Signing', () => { maxFeePerGas: BigInt(2748), maxPriorityFeePerGas: BigInt(291), }; + const packedUserOp: PackedUserOperationToSign = { + sender: '0x742D35Cc6634C0532925A3b8D33c9c0E7B66C8E8', + nonce: BigInt(1), + initCode: fromHexString('0xabc'), + callData: fromHexString('0xdef'), + accountGasLimit: fromHexString('0x01020304'), + preVerificationGas: BigInt(101112), + gasFees: fromHexString('0x05060708'), + paymasterAndData: fromHexString('0x090a0b0c'), + }; + const chainId = 1; const cohortId = 5; const porterUris = [fakePorterUri]; const aaVersion = '0.8.0'; const threshold = 2; - it.each(['0.8.0', 'mdt'])( - 'should sign a user operation with a valid aa versions', - async (validAAVersion) => { + it.each([ + ['0.8.0', userOp], + ['0.8.0', packedUserOp], + ['mdt', userOp], + ['mdt', packedUserOp], + ])( + 'should sign user operation and packed user operations for valid aa versions', + async ( + validAAVersion: string, + userOp: UserOperationToSign | PackedUserOperationToSign, + ) => { const signingResults = { '0xnode1': { messageHash: '0xhash1', @@ -226,20 +324,34 @@ describe('TACo Signing', () => { porterUris, ); - expect(porterSignUserOpMock).toHaveBeenCalledWith( - { - '0xnode1': expect.any(UserOperationSignatureRequest), - '0xnode2': expect.any(UserOperationSignatureRequest), - }, - threshold, - ); + if (isPackedUserOperation(userOp)) { + expect(porterSignUserOpMock).toHaveBeenCalledWith( + { + '0xnode1': expect.any(PackedUserOperationSignatureRequest), + '0xnode2': expect.any(PackedUserOperationSignatureRequest), + }, + threshold, + ); + } else { + expect(porterSignUserOpMock).toHaveBeenCalledWith( + { + '0xnode1': expect.any(UserOperationSignatureRequest), + '0xnode2': expect.any(UserOperationSignatureRequest), + }, + threshold, + ); + } const call = porterSignUserOpMock.mock.calls.at(-1)!; const [op] = call; const requests = [op['0xnode1'], op['0xnode2']]; requests.forEach((element) => { - checkUserOpEquality(userOp, element.userOp); + if (isPackedUserOperation(userOp)) { + checkPackedUserOpEquality(userOp, element.packedUserOp); + } else { + checkUserOpEquality(userOp, element.userOp); + } expect(element.aaVersion).toEqual(validAAVersion); expect(element.cohortId).toEqual(cohortId); expect(element.chainId).toEqual(BigInt(chainId)); From f05097d6007d253fd601a2a9ba43d1f03cffea65 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Wed, 29 Oct 2025 20:55:17 -0400 Subject: [PATCH 09/11] Properly convert optional fields for UserOperation. --- packages/shared/src/types.ts | 32 ++++++++------ packages/taco/test/taco-sign.test.ts | 63 +++++++++++++++++++++++----- 2 files changed, 71 insertions(+), 24 deletions(-) diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index 2f4a2c301..8d4b96cdb 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -60,27 +60,33 @@ export function toCoreUserOperation( // optional factory data if (userOperation.factory) { - const factory_data = getUint8ArrayValue(userOperation.factoryData || '0x'); + let factory_data = undefined; + if (userOperation.factoryData) { + factory_data = getUint8ArrayValue(userOperation.factoryData); + } userOp.setFactoryData(userOperation.factory, factory_data); } // optional paymaster data if (userOperation.paymaster) { - const paymaster_verification_gas_limit = getBigIntValue( - userOperation.paymasterVerificationGasLimit || 0, - ); - const paymaster_post_op_gas_limit = getBigIntValue( - userOperation.paymasterPostOpGasLimit || 0, - ); - const paymaster_data = getUint8ArrayValue( - userOperation.paymasterData || '0x', - ); + if (!userOperation.paymasterVerificationGasLimit) { + throw new Error( + 'paymasterVerificationGasLimit is required when paymaster is set', + ); + } else if (!userOperation.paymasterPostOpGasLimit) { + throw new Error( + 'paymasterPostOpGasLimit is required when paymaster is set', + ); + } + userOp.setPaymasterData( userOperation.paymaster, - paymaster_verification_gas_limit, - paymaster_post_op_gas_limit, - paymaster_data, + getBigIntValue(userOperation.paymasterVerificationGasLimit), + getBigIntValue(userOperation.paymasterPostOpGasLimit), + userOperation.paymasterData + ? getUint8ArrayValue(userOperation.paymasterData) + : undefined, ); } diff --git a/packages/taco/test/taco-sign.test.ts b/packages/taco/test/taco-sign.test.ts index 93bf040fa..a3552fa06 100644 --- a/packages/taco/test/taco-sign.test.ts +++ b/packages/taco/test/taco-sign.test.ts @@ -80,7 +80,7 @@ function checkUserOpEquality(op1: UserOperationToSign, op2: UserOperation) { op1.callData instanceof Uint8Array ? op1.callData : fromHexString(op1.callData); - expect(callData).toEqual(callData); + expect(callData).toEqual(op2.callData); expect(getNumberValue(op1.callGasLimit)).toEqual(op2.callGasLimit); expect(getNumberValue(op1.verificationGasLimit)).toEqual( @@ -98,10 +98,15 @@ function checkUserOpEquality(op1: UserOperationToSign, op2: UserOperation) { expect(op2.factory).toBeUndefined(); } else { expect(op1.factory).toEqual(op2.factory); + } + + if (op1.factoryData === undefined) { + expect(op2.factoryData).toBeUndefined(); + } else { const factoryData = op1.factoryData instanceof Uint8Array ? op1.factoryData - : fromHexString(op1.factoryData || '0x'); + : fromHexString(op1.factoryData); expect(factoryData).toEqual(op2.factoryData); } @@ -109,16 +114,31 @@ function checkUserOpEquality(op1: UserOperationToSign, op2: UserOperation) { expect(op2.paymaster).toBeUndefined(); } else { expect(op1.paymaster).toEqual(op2.paymaster); - expect( - getNumberValue(op1.paymasterVerificationGasLimit || BigInt(0)), - ).toEqual(op2.paymasterVerificationGasLimit); - expect(getNumberValue(op1.paymasterPostOpGasLimit || BigInt(0))).toEqual( + } + + if (op1.paymasterVerificationGasLimit === undefined) { + expect(op2.paymasterVerificationGasLimit).toBeUndefined(); + } else { + expect(getNumberValue(op1.paymasterVerificationGasLimit)).toEqual( + op2.paymasterVerificationGasLimit, + ); + } + + if (op1.paymasterPostOpGasLimit === undefined) { + expect(op2.paymasterPostOpGasLimit).toBeUndefined(); + } else { + expect(getNumberValue(op1.paymasterPostOpGasLimit)).toEqual( op2.paymasterPostOpGasLimit, ); + } + + if (op1.paymasterData === undefined) { + expect(op2.paymasterData).toBeUndefined(); + } else { const paymasterData = op1.paymasterData instanceof Uint8Array ? op1.paymasterData - : fromHexString(op1.paymasterData || '0x'); + : fromHexString(op1.paymasterData); expect(paymasterData).toEqual(op2.paymasterData); } } @@ -161,7 +181,7 @@ describe('TACo Signing', () => { const coreUserOp = toCoreUserOperation(userOpToSign); checkUserOpEquality(userOpToSign, coreUserOp); }); - it('should convert factory optional fields', () => { + it('should allow factory optional fields', () => { const updatedUserOp: UserOperationToSign = { ...userOpToSign, factory: '0x000000000000000000000000000000000000000A', @@ -170,7 +190,7 @@ describe('TACo Signing', () => { const coreUserOp = toCoreUserOperation(updatedUserOp); checkUserOpEquality(updatedUserOp, coreUserOp); }); - it('should convert factory data to default when not specified but factory is', () => { + it('should allow factory and no factory data', () => { const updatedUserOp: UserOperationToSign = { ...userOpToSign, factory: '0x000000000000000000000000000000000000000A', @@ -178,7 +198,7 @@ describe('TACo Signing', () => { const coreUserOp = toCoreUserOperation(updatedUserOp); checkUserOpEquality(updatedUserOp, coreUserOp); }); - it('should convert paymaster optional fields', () => { + it('should allow paymaster optional fields with paymasterData', () => { const updatedUserOp: UserOperationToSign = { ...userOpToSign, paymaster: '0x000000000000000000000000000000000000000C', @@ -189,14 +209,35 @@ describe('TACo Signing', () => { const coreUserOp = toCoreUserOperation(updatedUserOp); checkUserOpEquality(updatedUserOp, coreUserOp); }); - it('should convert paymaster optional fields to defaults when not specified but paymaster is', () => { + it('should allow paymaster optional fields with no paymasterData', () => { const updatedUserOp: UserOperationToSign = { ...userOpToSign, paymaster: '0x000000000000000000000000000000000000000C', + paymasterVerificationGasLimit: BigInt(50000), + paymasterPostOpGasLimit: BigInt(30000), }; const coreUserOp = toCoreUserOperation(updatedUserOp); checkUserOpEquality(updatedUserOp, coreUserOp); }); + it('should raise when paymaster specified but other required fields are not present', () => { + let updatedUserOp: UserOperationToSign = { + ...userOpToSign, + paymaster: '0x000000000000000000000000000000000000000C', + // missing required paymasterVerificationGasLimit and paymasterPostOpGasLimit + }; + expect(() => toCoreUserOperation(updatedUserOp)).toThrow( + 'paymasterVerificationGasLimit is required when paymaster is set', + ); + + updatedUserOp = { + ...updatedUserOp, + paymasterVerificationGasLimit: BigInt(50000), + // missing required paymasterPostOpGasLimit + }; + expect(() => toCoreUserOperation(updatedUserOp)).toThrow( + 'paymasterPostOpGasLimit is required when paymaster is set', + ); + }); it('should handle alternative types: number and byte fields', () => { const updatedUserOp: UserOperationToSign = { ...userOpToSign, From 0f755d75ae0d51acddcd1729e99c31f00efba8cf Mon Sep 17 00:00:00 2001 From: derekpierre Date: Wed, 29 Oct 2025 20:55:51 -0400 Subject: [PATCH 10/11] Code cleanup. --- packages/shared/src/porter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/porter.ts b/packages/shared/src/porter.ts index c79479db7..c3b782f39 100644 --- a/packages/shared/src/porter.ts +++ b/packages/shared/src/porter.ts @@ -345,7 +345,7 @@ export class PorterClient { toBase64(signingRequest.toBytes()), ]), ), - threshold: threshold, + threshold, }; const resp: AxiosResponse = await this.tryAndCall({ From fa1dc2d513d7234e147d517aed11cd960d704d0f Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 30 Oct 2025 08:48:55 -0400 Subject: [PATCH 11/11] Use experimental nucypher-core 0.15.1-dev.1 package. --- package.json | 4 ++-- packages/pre/package.json | 2 +- packages/shared/package.json | 2 +- packages/taco/package.json | 2 +- packages/test-utils/package.json | 2 +- pnpm-lock.yaml | 31 ++++++++++++++++++------------- 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 149b2f410..11b056915 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ }, "dependencies": { "@changesets/cli": "^2.28.1", - "@nucypher/nucypher-core": "link:../nucypher-core/nucypher-core-wasm-bundler", + "@nucypher/nucypher-core": "^0.15.1-dev.1", "ethers": "^5.8.0" }, "devDependencies": { @@ -65,7 +65,7 @@ ] }, "overrides": { - "@nucypher/nucypher-core": "link:../nucypher-core/nucypher-core-wasm-bundler", + "@nucypher/nucypher-core": "^0.15.1-dev.1", "glob-parent@<5.1.2": ">=5.1.2", "node-forge@<1.0.0": ">=1.0.0", "node-forge@<1.3.0": ">=1.3.0", diff --git a/packages/pre/package.json b/packages/pre/package.json index 6aa3ccf7f..71437f31e 100644 --- a/packages/pre/package.json +++ b/packages/pre/package.json @@ -38,7 +38,7 @@ "typedoc": "typedoc" }, "dependencies": { - "@nucypher/nucypher-core": "^0.14.5", + "@nucypher/nucypher-core": "^0.15.1-dev.1", "@nucypher/shared": "workspace:*", "ethers": "^5.8.0" }, diff --git a/packages/shared/package.json b/packages/shared/package.json index 41ac2f966..59f1c73a0 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -43,7 +43,7 @@ "@ethersproject/abi": "^5.8.0", "@ethersproject/providers": "^5.8.0", "@nucypher/nucypher-contracts": "0.26.0-alpha.1", - "@nucypher/nucypher-core": "link:../../../nucypher-core/nucypher-core-wasm-bundler", + "@nucypher/nucypher-core": "^0.15.1-dev.1", "axios": "^1.8.4", "deep-equal": "^2.2.3", "ethers": "^5.8.0", diff --git a/packages/taco/package.json b/packages/taco/package.json index 53d5efdf9..44744313b 100644 --- a/packages/taco/package.json +++ b/packages/taco/package.json @@ -42,7 +42,7 @@ }, "dependencies": { "@astronautlabs/jsonpath": "^1.1.2", - "@nucypher/nucypher-core": "link:../../../nucypher-core/nucypher-core-wasm-bundler", + "@nucypher/nucypher-core": "^0.15.1-dev.1", "@nucypher/shared": "workspace:*", "@nucypher/taco-auth": "workspace:*", "ethers": "^5.8.0", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index cea78cb70..4f9f4de61 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -29,7 +29,7 @@ "lint:fix": "pnpm lint --fix" }, "dependencies": { - "@nucypher/nucypher-core": "^0.14.5", + "@nucypher/nucypher-core": "^0.15.1-dev.1", "@nucypher/shared": "workspace:*", "@nucypher/taco-auth": "workspace:*", "axios": "^1.8.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a296ebea0..09e835fd0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: - '@nucypher/nucypher-core': link:../nucypher-core/nucypher-core-wasm-bundler + '@nucypher/nucypher-core': ^0.15.1-dev.1 glob-parent@<5.1.2: '>=5.1.2' node-forge@<1.0.0: '>=1.0.0' node-forge@<1.3.0: '>=1.3.0' @@ -19,8 +19,8 @@ importers: specifier: ^2.28.1 version: 2.29.5 '@nucypher/nucypher-core': - specifier: link:../nucypher-core/nucypher-core-wasm-bundler - version: link:../nucypher-core/nucypher-core-wasm-bundler + specifier: ^0.15.1-dev.1 + version: 0.15.1-dev.1 ethers: specifier: ^5.8.0 version: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -491,8 +491,8 @@ importers: packages/pre: dependencies: '@nucypher/nucypher-core': - specifier: link:../../../nucypher-core/nucypher-core-wasm-bundler - version: link:../../../nucypher-core/nucypher-core-wasm-bundler + specifier: ^0.15.1-dev.1 + version: 0.15.1-dev.1 '@nucypher/shared': specifier: workspace:* version: link:../shared @@ -516,8 +516,8 @@ importers: specifier: 0.26.0-alpha.1 version: 0.26.0-alpha.1 '@nucypher/nucypher-core': - specifier: link:../../../nucypher-core/nucypher-core-wasm-bundler - version: link:../../../nucypher-core/nucypher-core-wasm-bundler + specifier: ^0.15.1-dev.1 + version: 0.15.1-dev.1 axios: specifier: ^1.8.4 version: 1.10.0 @@ -565,8 +565,8 @@ importers: specifier: ^1.1.2 version: 1.1.2 '@nucypher/nucypher-core': - specifier: link:../../../nucypher-core/nucypher-core-wasm-bundler - version: link:../../../nucypher-core/nucypher-core-wasm-bundler + specifier: ^0.15.1-dev.1 + version: 0.15.1-dev.1 '@nucypher/shared': specifier: workspace:* version: link:../shared @@ -626,8 +626,8 @@ importers: packages/test-utils: dependencies: '@nucypher/nucypher-core': - specifier: link:../../../nucypher-core/nucypher-core-wasm-bundler - version: link:../../../nucypher-core/nucypher-core-wasm-bundler + specifier: ^0.15.1-dev.1 + version: 0.15.1-dev.1 '@nucypher/shared': specifier: workspace:* version: link:../shared @@ -2550,6 +2550,9 @@ packages: '@nucypher/nucypher-contracts@0.26.0-alpha.1': resolution: {integrity: sha512-ISS66SyKbDLXFF88Z6k8Tnc/L6Shx6tq22CjPpovbSD2PGS/UzE3uDxvFTKZfgcIJYsi7GV/ho2Brsx4sHYTLg==} + '@nucypher/nucypher-core@0.15.1-dev.1': + resolution: {integrity: sha512-h4sRBk6j+qC8mfevQHMHTbpVB6k0em8NKAeVAO3xH7eloDA8nzoBiKHe9NMeavklUyBwPVQKbDuO4aWQm/SLKg==} + '@nucypher/shared@0.5.0': resolution: {integrity: sha512-Y+0oEgVtoud4BP/pvj2elPm5igrQ8AAUwYYmUiLpXNqQMRF4zFnYaGbl+xhKk45CYG8S9cDKJEN8nopzVADTNw==} engines: {node: '>=18', pnpm: '>=8.0.0'} @@ -12104,12 +12107,14 @@ snapshots: '@nucypher/nucypher-contracts@0.26.0-alpha.1': {} + '@nucypher/nucypher-core@0.15.1-dev.1': {} + '@nucypher/shared@0.5.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@ethersproject/abi': 5.8.0 '@ethersproject/providers': 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@nucypher/nucypher-contracts': 0.25.0 - '@nucypher/nucypher-core': link:../nucypher-core/nucypher-core-wasm-bundler + '@nucypher/nucypher-core': 0.15.1-dev.1 axios: 1.10.0 deep-equal: 2.2.3 ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -12135,7 +12140,7 @@ snapshots: '@nucypher/taco@0.6.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@astronautlabs/jsonpath': 1.1.2 - '@nucypher/nucypher-core': link:../nucypher-core/nucypher-core-wasm-bundler + '@nucypher/nucypher-core': 0.15.1-dev.1 '@nucypher/shared': 0.5.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@nucypher/taco-auth': 0.3.0(bufferutil@4.0.9)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)