diff --git a/dist/services/AeternityService/AeternityService.d.ts b/dist/services/AeternityService/AeternityService.d.ts index 2128f37fb96187fa3c8f21fb541e004ff9cfadb6..642f960800126960a888c168ee308115c298dcd3 100644 --- a/dist/services/AeternityService/AeternityService.d.ts +++ b/dist/services/AeternityService/AeternityService.d.ts @@ -1,4 +1,4 @@ -import { TxData, TxMerkleTreeData } from "../../types"; +import { TxData, DecodedCallData } from "../../types"; import { Request } from "../../utils/requestAdapter"; declare class AeternityService { readonly _nodeUrls: any[]; @@ -14,7 +14,7 @@ declare class AeternityService { */ requestApi: (apiUrls: string[]) => Request; getTxDataByHash(hash: string): Promise<TxData>; - decodeContractCallData(callData: string): Promise<TxMerkleTreeData>; + decodeContractCallData(callData: string): Promise<DecodedCallData>; getBlock(height: number): Promise<{ height: number; time: number; diff --git a/dist/services/AeternityService/AeternityService.js b/dist/services/AeternityService/AeternityService.js index ba1339a50d49aa076de55b664dc19bf866f42bdf..0e4e895a82c0a573ec467b4152a7b9926d7b2e3f 100644 --- a/dist/services/AeternityService/AeternityService.js +++ b/dist/services/AeternityService/AeternityService.js @@ -101,8 +101,8 @@ class AeternityService { }, })); return { - merkleeTreeFileName: (_c = (_b = (_a = data.arguments[0]) === null || _a === void 0 ? void 0 : _a.value[0]) === null || _b === void 0 ? void 0 : _b.key) === null || _c === void 0 ? void 0 : _c.value, - rootNodeHash: (_f = (_e = (_d = data.arguments[0]) === null || _d === void 0 ? void 0 : _d.value[0]) === null || _e === void 0 ? void 0 : _e.val) === null || _f === void 0 ? void 0 : _f.value, + key: (_c = (_b = (_a = data.arguments[0]) === null || _a === void 0 ? void 0 : _a.value[0]) === null || _b === void 0 ? void 0 : _b.key) === null || _c === void 0 ? void 0 : _c.value, + value: (_f = (_e = (_d = data.arguments[0]) === null || _d === void 0 ? void 0 : _d.value[0]) === null || _e === void 0 ? void 0 : _e.val) === null || _f === void 0 ? void 0 : _f.value, }; }); } diff --git a/dist/services/VerificationService/VerificationService.d.ts b/dist/services/VerificationService/VerificationService.d.ts index 3f23a6507bf02cb08774757b560599fb5c22b293..d5085487b28ba060b0f08fa7ec4181ea2da59b71 100644 --- a/dist/services/VerificationService/VerificationService.d.ts +++ b/dist/services/VerificationService/VerificationService.d.ts @@ -1,8 +1,9 @@ declare const EventEmitter: any; -import { BlockData, MerkleTree, StatusData, TxData, VerificationData } from "../../types"; +import { BlockData, StatusesBatchData, StatusData, TxData, VerificationData } from "../../types"; import { AxiosError } from "axios"; export declare const TRANSACTION_RETRIEVED = "TRANSACTION_RETRIEVED"; export declare const MERKLE_TREE_VERIFIED = "MERKLE_TREE_VERIFIED"; +export declare const STATUS_BATCH_VERIFIED = "STATUS_BATCH_VERIFIED"; export declare const BLOCK_DATA_RETRIEVED = "BLOCK_DATA_RETRIEVED"; declare class VerificationService extends EventEmitter { private _aeternityService; @@ -11,8 +12,15 @@ declare class VerificationService extends EventEmitter { getTransaction(hash: string): Promise<TxData>; getBlockData(blockHeight: number): Promise<BlockData>; verifyStatusData(statusData: StatusData): Promise<VerificationData>; - getMerkleTreeForTransaction(txData: TxData): Promise<MerkleTree>; - verifyMerkleTreeNode(nodeToVerify: string, merkleTree: MerkleTree): Promise<boolean>; + getStatusBatchDataForTx(txData: TxData): Promise<StatusesBatchData>; + verifyStatusSHA256Batch(status: string, statusesBatchData: StatusesBatchData): Promise<{ + verified: boolean; + statusPosition: number; + }>; + verifyStatusesMerkleTree(nodeToVerify: string, merkleTree: StatusesBatchData): Promise<{ + verified: boolean; + statusPosition: number; + }>; catchVerificationError(error: AxiosError, verificationFailureMessage: string): void; } export default VerificationService; diff --git a/dist/services/VerificationService/VerificationService.js b/dist/services/VerificationService/VerificationService.js index 4d743c67d0d1d9317f4dd44924592c9bf5477fa1..6dd84ab88bc588c150731a378ea4c57b466c507a 100644 --- a/dist/services/VerificationService/VerificationService.js +++ b/dist/services/VerificationService/VerificationService.js @@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.BLOCK_DATA_RETRIEVED = exports.MERKLE_TREE_VERIFIED = exports.TRANSACTION_RETRIEVED = void 0; +exports.BLOCK_DATA_RETRIEVED = exports.STATUS_BATCH_VERIFIED = exports.MERKLE_TREE_VERIFIED = exports.TRANSACTION_RETRIEVED = void 0; const index_1 = require("../../index"); const EventEmitter = require("eventemitter2"); const AeternityService_1 = __importDefault(require("../AeternityService/AeternityService")); @@ -20,7 +20,15 @@ const CloudflareService_1 = __importDefault(require("../CloudflareService")); const VerificationError_1 = __importDefault(require("./VerificationError")); exports.TRANSACTION_RETRIEVED = "TRANSACTION_RETRIEVED"; exports.MERKLE_TREE_VERIFIED = "MERKLE_TREE_VERIFIED"; +exports.STATUS_BATCH_VERIFIED = "STATUS_BATCH_VERIFIED"; exports.BLOCK_DATA_RETRIEVED = "BLOCK_DATA_RETRIEVED"; +/** + * Legacy verification method. Support for older statuses + */ +const VERIFICATION_METHOD_MERKLE_TREE = "VERIFICATION_METHOD_MERKLE_TREE"; +const VERIFICATION_METHOD_SHA256_BATCH = "VERIFICATION_METHOD_SHA256_BATCH"; +const STATUSES_BATCH_NAME_PREFIX = "batch"; +const STATUSES_MERKLE_TREE_NAME_PREFIX = "bmt"; class VerificationService extends EventEmitter { constructor(cdnUrl, cdnBucket, aeternityNodeUrls, aeternityCompilerUrls, aeternityContractBytecode) { super(); @@ -78,20 +86,34 @@ class VerificationService extends EventEmitter { return __awaiter(this, void 0, void 0, function* () { let verificationError; let blockData; - let merkleTreeDetails; + let batchVerificationDetails; let txData; try { txData = yield this.getTransaction(statusData.transactionHash); this.emit(exports.TRANSACTION_RETRIEVED, txData); - const merkleTree = yield this.getMerkleTreeForTransaction(txData); - const statusVerified = yield this.verifyMerkleTreeNode(statusData.statusRaw, merkleTree); - merkleTreeDetails = { + const statusBatchData = yield this.getStatusBatchDataForTx(txData); + const { verificationMethod, items: batchItems, hash: batchHash, } = statusBatchData; + let statusVerified; + let statusPosition = -1; + if (verificationMethod === VERIFICATION_METHOD_MERKLE_TREE) { + ({ + verified: statusVerified, + statusPosition, + } = yield this.verifyStatusesMerkleTree(statusData.statusRaw, statusBatchData)); + } + else if (verificationMethod === VERIFICATION_METHOD_SHA256_BATCH) { + ({ + verified: statusVerified, + statusPosition, + } = yield this.verifyStatusSHA256Batch(statusData.statusRaw, statusBatchData)); + } + batchVerificationDetails = { verified: statusVerified, - treeSize: merkleTree.nodes.length, - statusPosition: merkleTree.nodes.indexOf(statusData.statusRaw) + 1, - rootHash: merkleTree.rootHash, + batchSize: batchItems.length, + statusPosition, + batchHash, }; - this.emit(exports.MERKLE_TREE_VERIFIED, merkleTreeDetails); + this.emit(exports.STATUS_BATCH_VERIFIED, batchVerificationDetails); blockData = yield this.getBlockData(txData.blockHeight); this.emit(exports.BLOCK_DATA_RETRIEVED, blockData); } @@ -105,38 +127,94 @@ class VerificationService extends EventEmitter { } return { statusData, - merkleTreeDetails, + batchVerificationDetails, blockData, verificationError, txData, }; }); } - getMerkleTreeForTransaction(txData) { + getStatusBatchDataForTx(txData) { var _a; return __awaiter(this, void 0, void 0, function* () { + let verificationMethod; + let batchItems; + let decodedCallData; try { - const txMerkleTreeData = yield this._aeternityService.decodeContractCallData((_a = txData.tx) === null || _a === void 0 ? void 0 : _a.callData); - const merkleTreeNodes = yield this._cloudflareService.getMerkleTree(txMerkleTreeData.merkleeTreeFileName); - return { - nodes: merkleTreeNodes, - rootHash: txMerkleTreeData.rootNodeHash, - }; + decodedCallData = yield this._aeternityService.decodeContractCallData((_a = txData.tx) === null || _a === void 0 ? void 0 : _a.callData); } catch (e) { - this.catchVerificationError(e, `Error extracting Merkle tree from transaction.`); + this.catchVerificationError(e, `Error decoding contract call data`); + } + const { key: batchFilename, value } = decodedCallData; + const batchHash = value; + const lowerCaseFilename = batchFilename.toLowerCase(); + if (lowerCaseFilename.startsWith(STATUSES_MERKLE_TREE_NAME_PREFIX)) { + verificationMethod = VERIFICATION_METHOD_MERKLE_TREE; + } + else if (lowerCaseFilename.startsWith(STATUSES_BATCH_NAME_PREFIX)) { + verificationMethod = VERIFICATION_METHOD_SHA256_BATCH; + } + else { + // Unrecognized verification method + throw new VerificationError_1.default(`No corresponding verification method found for batch "${batchFilename}"`); } + try { + if (verificationMethod === VERIFICATION_METHOD_MERKLE_TREE) { + batchItems = yield this._cloudflareService.getMerkleTree(batchFilename); + } + else if (verificationMethod === VERIFICATION_METHOD_SHA256_BATCH) { + batchItems = (yield this._cloudflareService.fetchFile(batchFilename)); + } + } + catch (e) { + this.catchVerificationError(e, `Error obtaining batch of statuses for verification.`); + } + return { + verificationMethod, + hash: batchHash, + items: batchItems, + }; + }); + } + verifyStatusSHA256Batch(status, statusesBatchData) { + return __awaiter(this, void 0, void 0, function* () { + const statusSha256 = index_1.arrayBufferToBase64(yield index_1.CryptoService.SHA256(status, "base64")); + let verified = false; + const statusPosition = statusesBatchData.items.indexOf(statusSha256) + 1; + if (statusPosition > 0) { + const bytesToHash = []; + let hashesTotalByteLength = 0; + for (const statusHash of statusesBatchData.items) { + const statusHashBytes = index_1.base64ToArrayBuffer(statusHash); + hashesTotalByteLength += statusHashBytes.byteLength; + bytesToHash.push(new Uint8Array(statusHashBytes)); + } + const batchBytes = new Uint8Array(hashesTotalByteLength); + let bytesOffset = 0; + bytesToHash.forEach((statusHashBytes) => { + batchBytes.set(statusHashBytes, bytesOffset); + bytesOffset += statusHashBytes.byteLength; + }); + const batchHash = index_1.arrayBufferToBase64(yield index_1.CryptoService.SHA256(batchBytes)); + verified = batchHash === statusesBatchData.hash; + } + return { + verified, + statusPosition, + }; }); } - verifyMerkleTreeNode(nodeToVerify, merkleTree) { + verifyStatusesMerkleTree(nodeToVerify, merkleTree) { return __awaiter(this, void 0, void 0, function* () { - const leaves = yield Promise.all(merkleTree.nodes.map((x) => __awaiter(this, void 0, void 0, function* () { return Buffer.from(yield index_1.CryptoService.SHA256(x)); }))); + const leaves = yield Promise.all(merkleTree.items.map((x) => __awaiter(this, void 0, void 0, function* () { return Buffer.from(yield index_1.CryptoService.SHA256(x)); }))); const rootHash = (yield index_1.getMerkleTreeRootHash(leaves)).toString("base64"); - const verified = rootHash === merkleTree.rootHash; + const verified = rootHash === merkleTree.hash; if (!verified) { throw new VerificationError_1.default(`Merkle tree not verified.`); } - return verified; + const statusPosition = merkleTree.items.indexOf(nodeToVerify) + 1; + return { verified, statusPosition }; }); } catchVerificationError(error, verificationFailureMessage) { diff --git a/dist/types.d.ts b/dist/types.d.ts index c7f71067b6cdea1bba585513e3ce23d697463f28..c186ed8b7e0a83e61ad178764b3cd6b43e74f660 100644 --- a/dist/types.d.ts +++ b/dist/types.d.ts @@ -29,21 +29,22 @@ export interface StatusData { merkleTreeId: string; transactionHash: string; } -export interface MerkleTreeVerificationDetails { +export interface BatchVerificationDetails { statusPosition: number; - treeSize: number; + batchSize: number; verified: boolean; - rootHash: string; + batchHash: string; } -export interface MerkleTree { - rootHash: string; - nodes: Array<string>; +export interface StatusesBatchData { + verificationMethod: string; + hash: string; + items: Array<string>; } export interface VerificationData { statusData?: StatusData; blockData?: BlockData; txData?: TxData; - merkleTreeDetails?: MerkleTreeVerificationDetails; + batchVerificationDetails?: BatchVerificationDetails; verificationError?: VerificationError; } export interface BlockData { @@ -58,9 +59,9 @@ export interface TxData { callData: string; }; } -export interface TxMerkleTreeData { - merkleeTreeFileName: string; - rootNodeHash: string; +export interface DecodedCallData { + key: string; + value: string; } export interface AttachmentSignature { value: string;