Skip to content
Open
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
302 changes: 152 additions & 150 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,16 @@
"devDependencies": {
"@babel/preset-env": "7.29.0",
"@babel/preset-typescript": "7.28.5",
"@eslint/js": "9.39.2",
"@eslint/js": "9.39.3",
"@types/jest": "30.0.0",
"babel-jest": "30.2.0",
"eslint": "9.39.2",
"eslint": "9.39.3",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-prettier": "5.5.5",
"globals": "17.3.0",
"globals": "17.4.0",
"jest": "30.2.0",
"typescript": "5.9.3",
"typescript-eslint": "8.54.0"
"typescript-eslint": "8.56.1"
}
}
2 changes: 1 addition & 1 deletion src/StateTransitionClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ export class StateTransitionClient {
}

public submitCertificationRequest(certificationData: CertificationData): Promise<CertificationResponse> {
return this.client.submitCertificationRequest(certificationData, false);
return this.client.submitCertificationRequest(certificationData);
}
}
7 changes: 2 additions & 5 deletions src/api/AggregatorClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,8 @@ export class AggregatorClient implements IAggregatorClient {
/**
* @inheritDoc
*/
public async submitCertificationRequest(
certificationData: CertificationData,
receipt: boolean = false,
): Promise<CertificationResponse> {
const request = await CertificationRequest.create(certificationData, receipt);
public async submitCertificationRequest(certificationData: CertificationData): Promise<CertificationResponse> {
const request = await CertificationRequest.create(certificationData);

const response = await this.transport.request(
'certification_request',
Expand Down
8 changes: 2 additions & 6 deletions src/api/CertificationRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,20 @@ export class CertificationRequest {
* Constructs a CertificationRequest instance.
* @param {StateId} stateId Unique state identifier.
* @param {CertificationData} certificationData Certification data.
* @param {boolean} receipt Optional flag to request a receipt.
*/
private constructor(
public readonly stateId: StateId,
public readonly certificationData: CertificationData,
public readonly receipt: boolean = false,
) {}

/**
* Create a new CertificationRequest instance.
* @param {CertificationData} certificationData Certification data.
* @param {boolean} receipt Optional flag to request a receipt.
*
* @returns {Promise<CertificationRequest>} A promise that resolves to a CertificationRequest instance.
*/
public static async create(certificationData: CertificationData, receipt?: boolean): Promise<CertificationRequest> {
return new CertificationRequest(await StateId.fromCertificationData(certificationData), certificationData, receipt);
public static async create(certificationData: CertificationData): Promise<CertificationRequest> {
return new CertificationRequest(await StateId.fromCertificationData(certificationData), certificationData);
}

/**
Expand All @@ -38,7 +35,6 @@ export class CertificationRequest {
return CborSerializer.encodeArray(
this.stateId.toCBOR(),
this.certificationData.toCBOR(),
CborSerializer.encodeBoolean(this.receipt),
CborSerializer.encodeUnsignedInteger(0),
);
}
Expand Down
139 changes: 3 additions & 136 deletions src/api/CertificationResponse.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
import { CertificationData } from './CertificationData.js';
import { DataHasher } from '../crypto/hash/DataHasher.js';
import { HashAlgorithm } from '../crypto/hash/HashAlgorithm.js';
import { ISigningService } from '../crypto/ISigningService.js';
import { Signature } from '../crypto/secp256k1/Signature.js';
import { SigningService } from '../crypto/secp256k1/SigningService.js';
import { InvalidJsonStructureError } from '../InvalidJsonStructureError.js';
import { CborDeserializer } from '../serialization/cbor/CborDeserializer.js';
import { CborSerializer } from '../serialization/cbor/CborSerializer.js';
import { HexConverter } from '../serialization/HexConverter.js';

/**
* Possible results from the aggregator when submitting a certification request.
Expand Down Expand Up @@ -36,99 +27,14 @@ export enum CertificationStatus {
}

export interface ICertificationResponseJson {
readonly receipt?: IReceiptJson;
readonly status: CertificationStatus;
}

/**
* Receipt information for a successful certification request.
*/
export interface IReceiptJson {
readonly publicKey: string;
readonly signature: string;
}

/**
* Receipt object returned by the aggregator on certification request.
*/
class Receipt {
public constructor(
private readonly _publicKey: Uint8Array,
public readonly signature: Signature,
) {}

public get publicKey(): Uint8Array {
return new Uint8Array(this._publicKey);
}

/**
* Parse a receipt object from CBOR bytes.
* @param {Uint8Array} bytes CBOR-encoded receipt
* @returns {Receipt} Parsed receipt
*/
public static fromCBOR(bytes: Uint8Array): Receipt {
const data = CborDeserializer.decodeArray(bytes);
return new Receipt(CborDeserializer.decodeByteString(data[0]), Signature.fromCBOR(data[1]));
}

/**
* Parse a receipt object from JSON.
* @param {unknown} data Raw receipt
* @returns {Receipt} Parsed receipt
* @throws {InvalidJsonStructureError} InvalidJsonStructureError if the data does not match the expected shape
*/
public static fromJSON(data: unknown): Receipt {
if (!Receipt.isJSON(data)) {
throw new InvalidJsonStructureError();
}

return new Receipt(HexConverter.decode(data.publicKey), Signature.fromJSON(data.signature));
}

/**
* Check if the given data is a valid JSON receipt object.
* @param {unknown} data Raw receipt
* @returns {boolean} True if the data is a valid JSON receipt object
*/
public static isJSON(data: unknown): data is IReceiptJson {
return (
typeof data === 'object' &&
data !== null &&
'publicKey' in data &&
typeof data.publicKey === 'string' &&
'signature' in data &&
typeof data.signature === 'string'
);
}

/**
* Convert the receipt to CBOR bytes.
* @returns {Uint8Array} CBOR-encoded receipt
*/
public toCBOR(): Uint8Array {
return CborSerializer.encodeArray(CborSerializer.encodeByteString(this._publicKey), this.signature.toCBOR());
}

/**
* Convert the receipt to a JSON object.
* @returns {IReceiptJson} JSON representation of the receipt
*/
public toJSON(): IReceiptJson {
return {
publicKey: HexConverter.encode(this._publicKey),
signature: this.signature.toJSON(),
};
}
}

/**
* Response object returned by the aggregator on certification request.
*/
export class CertificationResponse {
public constructor(
public readonly status: CertificationStatus,
public receipt: Receipt | null,
) {}
public constructor(public readonly status: CertificationStatus) {}

/**
* Create a new certification response.
Expand All @@ -137,27 +43,7 @@ export class CertificationResponse {
* @returns {CertificationResponse} Created certification response
*/
public static create(status: CertificationStatus): CertificationResponse {
return new CertificationResponse(status, null);
}

/**
* Create a new certification response.
* @param {ISigningService} signingService Aggregator signing service
* @param {CertificationResponse} certificationData Certification data
* @param {CertificationStatus} status Certification response status
*
* @returns {Promise<CertificationResponse>} Created certification response
*/
public static async createWithReceipt(
signingService: ISigningService<Signature>,
certificationData: CertificationData,
status: CertificationStatus,
): Promise<CertificationResponse> {
const signature = await signingService.sign(
await new DataHasher(HashAlgorithm.SHA256).update(certificationData.toCBOR()).digest(),
);

return new CertificationResponse(status, new Receipt(signingService.publicKey, signature));
return new CertificationResponse(status);
}

/**
Expand All @@ -172,7 +58,7 @@ export class CertificationResponse {
throw new InvalidJsonStructureError();
}

return new CertificationResponse(data.status, data.receipt ? Receipt.fromJSON(data.receipt) : null);
return new CertificationResponse(data.status);
}

/**
Expand All @@ -198,26 +84,7 @@ export class CertificationResponse {
*/
public toJSON(): ICertificationResponseJson {
return {
receipt: this.receipt?.toJSON(),
status: this.status,
};
}

/**
* Verify the receipt of the commitment.
*
* @returns {boolean} True if the receipt is valid, false otherwise
*/
public async verifyReceipt(certificationData: CertificationData): Promise<boolean> {
if (!this.receipt) {
throw new Error('Receipt is not part of the response.');
}

return SigningService.verifyWithPublicKey(
// TODO: Implement whenever this is implemented in aggregator
await certificationData.calculateLeafValue(),
this.receipt.signature.bytes,
this.receipt.publicKey,
);
}
}
3 changes: 1 addition & 2 deletions src/api/IAggregatorClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ export interface IAggregatorClient {
* Submit a transaction commitment for inclusion in the ledger.
*
* @param {CertificationData} certificationData The certification data to submit
* @param {boolean} receipt Require a signed receipt of the commitment, default is false
* @returns Result status from the aggregator
*/
submitCertificationRequest(certificationData: CertificationData, receipt: boolean): Promise<CertificationResponse>;
submitCertificationRequest(certificationData: CertificationData): Promise<CertificationResponse>;
}
26 changes: 3 additions & 23 deletions tests/unit/api/CertificationRequestTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { CborSerializer } from '../../../src/serialization/cbor/CborSerializer.j
import { HexConverter } from '../../../src/serialization/HexConverter.js';

describe('CertificationRequest', () => {
it('should encode and decode CBOR to exactly same object', async () => {
let request = await CertificationRequest.create(
it('should encode object to expected CBOR bytes', async () => {
const request = await CertificationRequest.create(
CertificationData.fromCBOR(
CborSerializer.encodeArray(
HexConverter.decode('8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),
Expand All @@ -21,27 +21,7 @@ describe('CertificationRequest', () => {
);

expect(HexConverter.encode(request.toCBOR())).toEqual(
'8458207191bb9f044715f712ca5e77e91b585cf892eb5755ae4d77231ad429c53cf661848301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798582000000000000000000000000000000000000000000000000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058418c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01f400',
);

request = await CertificationRequest.create(
CertificationData.fromCBOR(
CborSerializer.encodeArray(
HexConverter.decode('8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),
CborSerializer.encodeByteString(new Uint8Array(32)),
CborSerializer.encodeByteString(new Uint8Array(32)),
CborSerializer.encodeByteString(
HexConverter.decode(
'8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01',
),
),
),
),
true,
);

expect(HexConverter.encode(request.toCBOR())).toEqual(
'8458207191bb9f044715f712ca5e77e91b585cf892eb5755ae4d77231ad429c53cf661848301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798582000000000000000000000000000000000000000000000000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058418c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01f500',
'8358207191bb9f044715f712ca5e77e91b585cf892eb5755ae4d77231ad429c53cf661848301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798582000000000000000000000000000000000000000000000000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058418c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a0100',
);
});
});
Loading