Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/shared/src/contracts/agents/signing-coordinator.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { getContract } from '@nucypher/nucypher-contracts';
import { SessionStaticKey } from '@nucypher/nucypher-core';
import { ethers } from 'ethers';

import { Domain } from '../../porter';
import { fromHexString } from '../../utils';
import { SigningCoordinator__factory } from '../ethers-typechain';
import { SigningCoordinator } from '../ethers-typechain/SigningCoordinator';

type SignerInfo = {
operator: string;
provider: string;
signature: string;
signingRequestStaticKey: SessionStaticKey;
};

export class SigningCoordinatorAgent {
Expand All @@ -24,10 +27,16 @@ export class SigningCoordinatorAgent {
(
participant: SigningCoordinator.SigningCohortParticipantStructOutput,
) => {
// Extract signingRequestStaticKey from contract
const signingRequestStaticKey = SessionStaticKey.fromBytes(
fromHexString(participant.signingRequestStaticKey),
);

return {
operator: participant.operator,
provider: participant.provider,
signature: participant.signature,
signingRequestStaticKey,
};
},
);
Expand Down
107 changes: 50 additions & 57 deletions packages/shared/src/porter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {
CapsuleFrag,
EncryptedThresholdDecryptionRequest,
EncryptedThresholdDecryptionResponse,
EncryptedThresholdSigningRequest,
EncryptedThresholdSigningResponse,
PublicKey,
RetrievalKit,
TreasureMap,
Expand Down Expand Up @@ -153,12 +155,12 @@ export type TacoDecryptResult = {
};

// Signing types
type TacoSignResponse = {
type TacoSignResponseEncrypted = {
readonly result: {
readonly signing_results: {
readonly signatures: Record<
readonly encrypted_signing_responses: Record<
ChecksumAddress,
[ChecksumAddress, Base64EncodedBytes]
Base64EncodedBytes
>;
readonly errors: Record<ChecksumAddress, string>;
};
Expand All @@ -171,33 +173,11 @@ export type TacoSignature = {
signerAddress: string;
};

export type TacoSignResult = {
signingResults: { [ursulaAddress: string]: TacoSignature };
export type TacoSignResultEncrypted = {
encryptedResponses: Record<string, EncryptedThresholdSigningResponse>;
errors: Record<string, string>;
};

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[];

Expand Down Expand Up @@ -344,41 +324,54 @@ export class PorterClient {
return { encryptedResponses, errors };
}

/**
* Signs a UserOperation using encrypted signing requests.
* This method mirrors the pattern used by tacoDecrypt for encrypted decryption requests.
*
* @param encryptedRequests - Encrypted signing requests for each signer
* @param threshold - Minimum number of signatures required
* @returns Encrypted signing responses and errors
*/
public async signUserOp(
signingRequests: Record<string, string>,
encryptedRequests: Record<string, EncryptedThresholdSigningRequest>,
threshold: number,
): Promise<TacoSignResult> {
const data: Record<string, unknown> = {
signing_requests: signingRequests,
threshold: threshold,
): Promise<TacoSignResultEncrypted> {
const data = {
encrypted_signing_requests: Object.fromEntries(
Object.entries(encryptedRequests).map(
([provider, encryptedRequest]) => [
provider,
toBase64(encryptedRequest.toBytes()),
],
),
),
threshold,
};

const resp: AxiosResponse<TacoSignResponse> = await this.tryAndCall({
url: '/sign',
method: 'post',
data,
});
const resp: AxiosResponse<TacoSignResponseEncrypted> =
await this.tryAndCall({
url: '/sign',
method: 'post',
data,
});

const { signatures, errors } = resp.data.result.signing_results;
const allErrors: Record<string, string> = { ...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!;
}
const { encrypted_signing_responses, errors } =
resp.data.result.signing_results;

return {
signingResults,
errors: allErrors,
};
const signingResponses = Object.entries(encrypted_signing_responses).map(
([address, encryptedResponseBase64]) => {
const encryptedResponse = EncryptedThresholdSigningResponse.fromBytes(
fromBase64(encryptedResponseBase64),
);
return [address, encryptedResponse];
},
);

const encryptedResponses: Record<
string,
EncryptedThresholdSigningResponse
> = Object.fromEntries(signingResponses);

return { encryptedResponses, errors };
}
}
Loading
Loading