diff --git a/src/Concordium.ts b/src/Concordium.ts index 8959e83..e5689ba 100644 --- a/src/Concordium.ts +++ b/src/Concordium.ts @@ -31,6 +31,12 @@ const P1_CONFIRM = 0x01; const P2_SIGNED_KEY = 0x01; // FOR SIGN TRANSACTION +const P1_FIRST_BATCH = 0x01; +const P1_AGGREGATION_KEY = 0x02; +const P1_URL_LENGTH = 0x03; +const P1_URL = 0x04; +const P1_COMMISSION_FEE = 0x05; + const P1_FIRST_CHUNK = 0x00; const P1_INITIAL_WITH_MEMO = 0x01; const P1_INITIAL_WITH_MEMO_SCHEDULE = 0x02; @@ -45,6 +51,7 @@ const P2_LAST = 0x00; const P1_INITIAL_PACKET = 0x00; const P1_SCHEDULED_TRANSFER_PAIRS = 0x01; + const INS = { // GET_VERSION: 0x03, VERIFY_ADDRESS: 0x00, @@ -353,30 +360,53 @@ export default class Concordium { }; } - async signConfigureBaker(txn, path: string): Promise<{ signature: string[]; transaction }> { + async signConfigureBaker(txn, path: string): Promise<{ signature: string[] }> { - const { payloads } = serializeConfigureBaker(txn, path); + const { payloadHeaderKindAndBitmap, payloadFirstBatch, payloadAggregationKeys, payloadUrlLength, payloadURL, payloadCommissionFee } = serializeConfigureBaker(txn, path); let response; - for (let i = 0; i < payloads.length; i++) { - const lastChunk = i === payloads.length - 1; - response = await this.sendToDevice( - INS.SIGN_CONFIGURE_BAKER, - P1_FIRST_CHUNK + i, - lastChunk ? P2_LAST : P2_MORE, - payloads[i] - ); - } + response = await this.sendToDevice( + INS.SIGN_CONFIGURE_BAKER, + P1_INITIAL_PACKET, + NONE, + payloadHeaderKindAndBitmap + ); + response = await this.sendToDevice( + INS.SIGN_CONFIGURE_BAKER, + P1_FIRST_BATCH, + NONE, + payloadFirstBatch + ); + response = await this.sendToDevice( + INS.SIGN_CONFIGURE_BAKER, + P1_AGGREGATION_KEY, + NONE, + payloadAggregationKeys + ); + response = await this.sendToDevice( + INS.SIGN_CONFIGURE_BAKER, + P1_URL_LENGTH, + NONE, + payloadUrlLength + ); + response = await this.sendToDevice( + INS.SIGN_CONFIGURE_BAKER, + P1_URL, + NONE, + payloadURL + ); + response = await this.sendToDevice( + INS.SIGN_CONFIGURE_BAKER, + P1_COMMISSION_FEE, + NONE, + payloadCommissionFee + ); if (response.length === 1) throw new Error("User has declined."); - const transaction = payloads.slice(1); - - return { signature: response.toString("hex"), - transaction: Buffer.concat(transaction).toString("hex"), }; } diff --git a/src/serialization.ts b/src/serialization.ts index e0e8c41..237e3ae 100644 --- a/src/serialization.ts +++ b/src/serialization.ts @@ -3,8 +3,22 @@ import { encodeDataBlob, encodeInt8, encodeWord16, encodeWord64, serializeAccoun import { DataBlob } from "@concordium/common-sdk/lib/types/DataBlob"; import { Buffer as NodeBuffer } from 'buffer/index'; import { AccountAddress } from "@concordium/web-sdk"; + const MAX_CHUNK_SIZE = 255; const MAX_SCHEDULE_CHUNK_SIZE = 15; +const HEADER_LENGTH = 60; +const TRANSACTION_KIND_LENGTH = 1; +const BITMAP_LENGTH = 2; +const STAKING_PAYLOAD_LENGTH = 8; +const RESTAKE_EARNINGS_PAYLOAD_LENGTH = 1; +const OPEN_FOR_DELEGATION_PAYLOAD_LENGTH = 1; +const KEYS_AGGREGATION_LENGTH = 160; +const KEYS_ELECTION_AND_SIGNATURE_LENGTH = 192; +const KEYS_PAYLOAD_LENGTH = KEYS_ELECTION_AND_SIGNATURE_LENGTH + KEYS_AGGREGATION_LENGTH; +const METADATA_URL_LENGTH = 2; +const TRANSACTION_FEE_COMMISSION_LENGTH = 4; +const BAKING_REWARD_COMMISSION_LENGTH = 4; +const FINALIZATION_REWARD_COMMISSION_LENGTH = 4; const serializePath = (path: number[]): Buffer => { const buf = Buffer.alloc(1 + path.length * 4); @@ -164,8 +178,64 @@ export const serializeConfigureDelegation = (txn: any, path: string): { payloads return serializeTransaction(txn, path); }; -export const serializeConfigureBaker = (txn: any, path: string): { payloads: Buffer[] } => { - return serializeTransaction(txn, path); +export const serializeConfigureBaker = (txn: any, path: string): { payloadHeaderKindAndBitmap: Buffer, payloadFirstBatch: Buffer, payloadAggregationKeys: Buffer, payloadUrlLength: Buffer, payloadURL: Buffer, payloadCommissionFee: Buffer } => { + let stake: Buffer = Buffer.alloc(0); + let restakeEarnings: Buffer = Buffer.alloc(0); + let openForDelegation: Buffer = Buffer.alloc(0); + let keys: Buffer = Buffer.alloc(0); + let metadataUrl: Buffer = Buffer.alloc(0); + let url: Buffer = Buffer.alloc(0); + let transactionFeeCommission: Buffer = Buffer.alloc(0); + let bakingRewardCommission: Buffer = Buffer.alloc(0); + let finalizationRewardCommission: Buffer = Buffer.alloc(0); + let offset: number = 0; + + const txSerialized = serializeAccountTransaction(txn); + const headerKindAndBitmap = txSerialized.subarray(0, HEADER_LENGTH + TRANSACTION_KIND_LENGTH + BITMAP_LENGTH); + offset += HEADER_LENGTH + TRANSACTION_KIND_LENGTH + BITMAP_LENGTH; + if (txn.payload.hasOwnProperty('stake')) { + stake = txSerialized.subarray(offset, offset + STAKING_PAYLOAD_LENGTH); + offset += STAKING_PAYLOAD_LENGTH; + } + if (txn.payload.hasOwnProperty('restakeEarnings')) { + restakeEarnings = txSerialized.subarray(offset, offset + RESTAKE_EARNINGS_PAYLOAD_LENGTH); + offset += RESTAKE_EARNINGS_PAYLOAD_LENGTH; + } + if (txn.payload.hasOwnProperty('openForDelegation')) { + openForDelegation = txSerialized.subarray(offset, offset + OPEN_FOR_DELEGATION_PAYLOAD_LENGTH); + offset += OPEN_FOR_DELEGATION_PAYLOAD_LENGTH; + } + if (txn.payload.hasOwnProperty('keys')) { + keys = txSerialized.subarray(offset, offset + KEYS_PAYLOAD_LENGTH); + offset += KEYS_PAYLOAD_LENGTH; + } + if (txn.payload.hasOwnProperty('metadataUrl')) { + metadataUrl = txSerialized.subarray(offset, offset + METADATA_URL_LENGTH); + offset += METADATA_URL_LENGTH; + url = txSerialized.subarray(offset, offset + metadataUrl.readUInt16BE(0)); + offset += metadataUrl.readUInt16BE(0); + } + if (txn.payload.hasOwnProperty('transactionFeeCommission')) { + transactionFeeCommission = txSerialized.subarray(offset, offset + TRANSACTION_FEE_COMMISSION_LENGTH); + offset += TRANSACTION_FEE_COMMISSION_LENGTH; + } + if (txn.payload.hasOwnProperty('bakingRewardCommission')) { + bakingRewardCommission = txSerialized.subarray(offset, offset + BAKING_REWARD_COMMISSION_LENGTH); + offset += BAKING_REWARD_COMMISSION_LENGTH; + } + if (txn.payload.hasOwnProperty('finalizationRewardCommission')) { + finalizationRewardCommission = txSerialized.subarray(offset, offset + FINALIZATION_REWARD_COMMISSION_LENGTH); + offset += FINALIZATION_REWARD_COMMISSION_LENGTH; + } + + const payloadHeaderKindAndBitmap = serializeTransactionPayloadsWithDerivationPath(path, headerKindAndBitmap); + const payloadFirstBatch = Buffer.concat([stake, restakeEarnings, openForDelegation, keys.subarray(0, KEYS_ELECTION_AND_SIGNATURE_LENGTH)]); + const payloadAggregationKeys = keys.subarray(KEYS_ELECTION_AND_SIGNATURE_LENGTH); + const payloadUrlLength = metadataUrl; + const payloadURL = url; + const payloadCommissionFee = Buffer.concat([transactionFeeCommission, bakingRewardCommission, finalizationRewardCommission]); + + return { payloadHeaderKindAndBitmap: payloadHeaderKindAndBitmap[0], payloadFirstBatch, payloadAggregationKeys, payloadUrlLength, payloadURL, payloadCommissionFee }; };