From 5663ca4a6059a2e8088c0d8b1e0309b8aa8e9cc0 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Wed, 19 Nov 2025 19:47:46 -0500 Subject: [PATCH 1/5] Properly sort signatures based on signer addresses when aggregating signatures returned from nodes. --- packages/taco/src/sign.ts | 35 ++++++++++++++++++---------- packages/taco/test/taco-sign.test.ts | 17 +++++++------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/packages/taco/src/sign.ts b/packages/taco/src/sign.ts index ef2df329b..9942776ca 100644 --- a/packages/taco/src/sign.ts +++ b/packages/taco/src/sign.ts @@ -51,20 +51,31 @@ export type SignResult = { }; function aggregateSignatures( - signaturesByAddress: { - [checksumAddress: string]: TacoSignature; - }, + signatures: TacoSignature[], threshold: number, ): string { - // Aggregate hex signatures by concatenating them; being careful to remove the '0x' prefix from each signature except the first one. - const signatures = Object.values(signaturesByAddress) - .map((sig) => sig.signature) - .slice(0, threshold); - if (signatures.length === 1) { - return signatures[0]; + // Aggregate hex signatures by concatenating them; being careful to sort + // and remove the '0x' prefix from each signature except the first one. + + // sort by signer address + const sortedSignatures = signatures + .slice() + .sort((a, b) =>{ + const addrA = BigInt(a.signerAddress); + const addrB = BigInt(b.signerAddress); + return addrA < addrB ? -1 : addrA > addrB ? 1 : 0; + }) + .map((sig) => sig.signature); + + const thresholdSignatures = sortedSignatures.slice(0, threshold); + if (thresholdSignatures.length === 1) { + return thresholdSignatures[0]; } + // Concatenate signatures - const allBytes = signatures.flatMap((hex) => Array.from(fromHexString(hex))); + const allBytes = thresholdSignatures.flatMap((sig) => + Array.from(fromHexString(sig)), + ); return `0x${toHexString(new Uint8Array(allBytes))}`; } @@ -197,7 +208,7 @@ export async function signUserOp( ); const aggregatedSignature = aggregateSignatures( - signaturesToAggregate, + Object.values(signaturesToAggregate), threshold, ); @@ -300,7 +311,7 @@ function collectSignatures( // Insufficient signatures for a message hash to meet the threshold if (!signaturesToAggregate) { - //we have multiple hashes, which means we have mismatched hashes from different nodes + // we have multiple hashes, which means we have mismatched hashes from different nodes // we don't really expect this to happen (other than some malicious nodes) console.error( 'Porter returned mismatched message hashes:', diff --git a/packages/taco/test/taco-sign.test.ts b/packages/taco/test/taco-sign.test.ts index ed73e3c03..360cdb768 100644 --- a/packages/taco/test/taco-sign.test.ts +++ b/packages/taco/test/taco-sign.test.ts @@ -357,24 +357,25 @@ describe('TACo Signing', () => { userOp: UserOperationToSign | PackedUserOperationToSign, ) => { const encryptedResponses = { - '0xnode1': new SignatureResponse( - signersInfo['0xnode1'].signerAddress, + // return out of order to ensure sorting works correctly + '0xnode2': new SignatureResponse( + signersInfo['0xnode2'].signerAddress, fromHexString('0xa1'), - fromHexString('0xdead'), + fromHexString('0xbeef'), 0, ).encrypt( requesterSk.deriveSharedSecret( - signersInfo['0xnode1'].signingRequestStaticKey, + signersInfo['0xnode2'].signingRequestStaticKey, ), ), - '0xnode2': new SignatureResponse( - signersInfo['0xnode2'].signerAddress, + '0xnode1': new SignatureResponse( + signersInfo['0xnode1'].signerAddress, fromHexString('0xa1'), - fromHexString('0xbeef'), + fromHexString('0xdead'), 0, ).encrypt( requesterSk.deriveSharedSecret( - signersInfo['0xnode2'].signingRequestStaticKey, + signersInfo['0xnode1'].signingRequestStaticKey, ), ), }; From 7a5dd56285802bcce8626bb2ebaed5e5175812f3 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Wed, 19 Nov 2025 19:49:32 -0500 Subject: [PATCH 2/5] Use `accountGasLimits` and not `accountGasLimit` as property on PackedUserOperationToSign. --- packages/shared/src/types.ts | 4 ++-- packages/shared/test/porter.test.ts | 2 +- packages/taco/test/taco-sign.test.ts | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index 8d4b96cdb..c0fd94aec 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -30,7 +30,7 @@ export type PackedUserOperationToSign = { nonce: bigint | number; initCode: `0x${string}` | Uint8Array; callData: `0x${string}` | Uint8Array; - accountGasLimit: `0x${string}` | Uint8Array; + accountGasLimits: `0x${string}` | Uint8Array; preVerificationGas: bigint | number; gasFees: `0x${string}` | Uint8Array; paymasterAndData: `0x${string}` | Uint8Array; @@ -101,7 +101,7 @@ export function toCorePackedUserOperation( getBigIntValue(packedUserOperation.nonce), getUint8ArrayValue(packedUserOperation.initCode), getUint8ArrayValue(packedUserOperation.callData), - getUint8ArrayValue(packedUserOperation.accountGasLimit), + getUint8ArrayValue(packedUserOperation.accountGasLimits), getBigIntValue(packedUserOperation.preVerificationGas), getUint8ArrayValue(packedUserOperation.gasFees), getUint8ArrayValue(packedUserOperation.paymasterAndData), diff --git a/packages/shared/test/porter.test.ts b/packages/shared/test/porter.test.ts index ae057ba54..046894664 100644 --- a/packages/shared/test/porter.test.ts +++ b/packages/shared/test/porter.test.ts @@ -289,7 +289,7 @@ describe('PorterClient Signing', () => { BigInt(123), // nonce fromHexString('0xabc'), // initCode fromHexString('0xdef'), // callData - fromHexString('0x01020304'), // accountGasLimit + fromHexString('0x01020304'), // accountGasLimits BigInt(101112), // preVerificationGas fromHexString('0x05060708'), // gasFees fromHexString('0x090a0b0c'), // paymasterAndData diff --git a/packages/taco/test/taco-sign.test.ts b/packages/taco/test/taco-sign.test.ts index 360cdb768..fcce85295 100644 --- a/packages/taco/test/taco-sign.test.ts +++ b/packages/taco/test/taco-sign.test.ts @@ -53,11 +53,11 @@ function checkPackedUserOpEquality( : fromHexString(op1.callData); expect(callData).toEqual(op2.callData); - const accountGasLimit = - op1.accountGasLimit instanceof Uint8Array - ? op1.accountGasLimit - : fromHexString(op1.accountGasLimit); - expect(accountGasLimit).toEqual(op2.accountGasLimits); + const accountGasLimits = + op1.accountGasLimits instanceof Uint8Array + ? op1.accountGasLimits + : fromHexString(op1.accountGasLimits); + expect(accountGasLimits).toEqual(op2.accountGasLimits); expect(toBigInt(op1.preVerificationGas)).toEqual(op2.preVerificationGas); @@ -290,7 +290,7 @@ describe('TACo Signing', () => { nonce: BigInt(123), initCode: fromHexString('0xabc'), callData: fromHexString('0xdef'), - accountGasLimit: fromHexString('0x01020304'), + accountGasLimits: fromHexString('0x01020304'), preVerificationGas: BigInt(101112), gasFees: fromHexString('0x05060708'), paymasterAndData: fromHexString('0x090a0b0c'), @@ -307,7 +307,7 @@ describe('TACo Signing', () => { nonce: 1, initCode: '0xabc', callData: '0xdef', - accountGasLimit: '0x01020304', + accountGasLimits: '0x01020304', preVerificationGas: 4096, gasFees: '0x05060708', paymasterAndData: '0x090a0b0c', @@ -333,7 +333,7 @@ describe('TACo Signing', () => { nonce: BigInt(1), initCode: fromHexString('0xabc'), callData: fromHexString('0xdef'), - accountGasLimit: fromHexString('0x01020304'), + accountGasLimits: fromHexString('0x01020304'), preVerificationGas: BigInt(101112), gasFees: fromHexString('0x05060708'), paymasterAndData: fromHexString('0x090a0b0c'), From fffefe2db5b00e60ab7c923bd9effcd7421f6be5 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Wed, 19 Nov 2025 19:51:00 -0500 Subject: [PATCH 3/5] Allow optional signature property on PackedUserOperationToSign. --- packages/shared/src/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index c0fd94aec..8d5ea9f0c 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -34,6 +34,7 @@ export type PackedUserOperationToSign = { preVerificationGas: bigint | number; gasFees: `0x${string}` | Uint8Array; paymasterAndData: `0x${string}` | Uint8Array; + signature?: `0x${string}` | Uint8Array | undefined; }; function getBigIntValue(value: bigint | number): bigint { From d09fad787bdaf5478f758f42d305f8e9b6afbef3 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Wed, 19 Nov 2025 19:51:18 -0500 Subject: [PATCH 4/5] Expose `PackedUserOperationToSign` and `UserOperationToSign` from within `nucypher/taco` package. --- packages/taco/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/taco/src/index.ts b/packages/taco/src/index.ts index 34e6bdf0f..866ce1c24 100644 --- a/packages/taco/src/index.ts +++ b/packages/taco/src/index.ts @@ -1,6 +1,8 @@ export { DkgPublicKey, ThresholdMessageKit } from '@nucypher/nucypher-core'; export { Domain, + PackedUserOperationToSign, + UserOperationToSign, domains, fromBytes, getPorterUris, From 838cf34e60a538a392c075e3a29ccb192ab1e3f3 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 20 Nov 2025 10:59:14 -0500 Subject: [PATCH 5/5] Fix sorting of signatures when aggregating signatures. --- packages/taco/src/sign.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/taco/src/sign.ts b/packages/taco/src/sign.ts index 9942776ca..31b5c62bf 100644 --- a/packages/taco/src/sign.ts +++ b/packages/taco/src/sign.ts @@ -58,13 +58,12 @@ function aggregateSignatures( // and remove the '0x' prefix from each signature except the first one. // sort by signer address - const sortedSignatures = signatures - .slice() - .sort((a, b) =>{ - const addrA = BigInt(a.signerAddress); - const addrB = BigInt(b.signerAddress); - return addrA < addrB ? -1 : addrA > addrB ? 1 : 0; - }) + const sortedSignatures = [...signatures] + .sort((a, b) => + a.signerAddress + .toLowerCase() + .localeCompare(b.signerAddress.toLowerCase()), + ) .map((sig) => sig.signature); const thresholdSignatures = sortedSignatures.slice(0, threshold);