Skip to content
Merged
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
20 changes: 16 additions & 4 deletions modules/abstract-substrate/src/lib/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,21 @@ export class Transaction extends BaseTransaction {
this._substrateTransaction = tx;
}

/**
* Returns the full raw encoded `ExtrinsicPayload` bytes (including the method) for this
* transaction, without applying the Substrate 256-byte blake2_256 signing rule.
*
* These are the bytes the HSM signs on the `polyx/signtx` path. They differ from
* {@link signablePayload} once the payload exceeds 256 bytes, where {@link signablePayload}
* is the blake2_256 hash instead of the raw bytes.
*/
get rawExtrinsicPayload(): Uint8Array {
const extrinsicPayload = this._registry.createType('ExtrinsicPayload', this._substrateTransaction, {
version: EXTRINSIC_VERSION,
});
return extrinsicPayload.toU8a({ method: true });
}

/**
* @inheritdoc
*
Expand All @@ -543,10 +558,7 @@ export class Transaction extends BaseTransaction {
* messages, causing TSS signature combination to fail.
*/
get signablePayload(): Buffer {
const extrinsicPayload = this._registry.createType('ExtrinsicPayload', this._substrateTransaction, {
version: EXTRINSIC_VERSION,
});
return utils.getSubstrateSigningBytes(extrinsicPayload.toU8a({ method: true }));
return utils.getSubstrateSigningBytes(this.rawExtrinsicPayload);
}

/**
Expand Down
7 changes: 7 additions & 0 deletions modules/sdk-coin-polyx/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ export interface TxData extends Interface.TxData {
toDID?: string;
instructionId?: string;
portfolioDID?: string;
/**
* Hex of the full raw encoded `ExtrinsicPayload` (see `Transaction.rawExtrinsicPayload`).
* Surfaced alongside the MPC/combine `signableHex` so consumers that need the raw payload
* (e.g. the HSM `polyx/signtx` path) can use it for extrinsics larger than 256 bytes, where
* the signing bytes are the blake2_256 hash rather than the raw payload.
*/
rawSignableHex?: string;
}

/**
Expand Down
5 changes: 4 additions & 1 deletion modules/sdk-coin-polyx/src/lib/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export class Transaction extends SubstrateTransaction {
eraPeriod: decodedTx.eraPeriod,
chainName: this._chainName,
tip: decodedTx.tip ? Number(decodedTx.tip) : 0,
rawSignableHex: Buffer.from(this.rawExtrinsicPayload).toString('hex'),
};

const txMethod = decodedTx.method.args;
Expand Down Expand Up @@ -99,7 +100,9 @@ export class Transaction extends SubstrateTransaction {
result.portfolioDID = rejectInstructionArgs.portfolio.did as string;
result.amount = '0'; // Reject instruction does not transfer any value
} else {
return super.toJson() as TxData;
const baseResult = super.toJson() as TxData;
baseResult.rawSignableHex = result.rawSignableHex;
return baseResult;
}

return result;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { decode } from '@substrate/txwrapper-polkadot';
import { coins } from '@bitgo/statics';
import should from 'should';
import { TransactionBuilderFactory, NominateBuilder, BatchBuilder } from '../../../src/lib';
import { TransactionBuilderFactory, NominateBuilder, BatchBuilder, PolyxTransaction } from '../../../src/lib';
import { TransactionType } from '@bitgo/sdk-core';
import utils from '../../../src/lib/utils';
import { accounts, nominateTx, nominateValidators, stakingTx } from '../../resources';
Expand Down Expand Up @@ -191,7 +191,7 @@ describe('Polyx Nominate Builder', function () {
.sequenceId({ name: 'Nonce', keyword: 'nonce', value: 15 })
.fee({ amount: 0, type: 'tip' })
.material(material);
return nominateBuilder.build();
return (await nominateBuilder.build()) as PolyxTransaction;
};

it('should return raw payload bytes when the extrinsic is at most 256 bytes', async () => {
Expand All @@ -200,14 +200,31 @@ describe('Polyx Nominate Builder', function () {
// small nominate extrinsic stays under the 256-byte threshold, so it is signed as-is
signablePayload.length.should.be.belowOrEqual(256);
signablePayload.length.should.not.equal(32);
// for a sub-256-byte payload, signablePayload is exactly the raw extrinsic payload
const rawExtrinsicPayload = Buffer.from(tx.rawExtrinsicPayload);
rawExtrinsicPayload.should.deepEqual(signablePayload);
// toJson surfaces the full raw payload as rawSignableHex even when it equals signablePayload
should.equal(tx.toJson().rawSignableHex, rawExtrinsicPayload.toString('hex'));
});

it('should return the 32-byte blake2_256 hash when the extrinsic exceeds 256 bytes', async () => {
// 6+ validators (~33 bytes each) pushes the nominate extrinsic over the 256-byte threshold
const manyValidators = Array(8).fill(validatorAddress);
// Each nominate target adds a 33-byte MultiAddress (1-byte variant + 32-byte account id) on
// top of 79 bytes of fixed signing-payload overhead (call index, era, nonce, tip,
// spec/transaction versions, genesis + block hash). So 5 targets stay raw at 244 bytes and
// 6 already cross to 277 bytes (hashed). 8 would also exceed the threshold; this uses 9 to
// mirror the multi-nomination scenario from SI-926 with margin (376 bytes).
const manyValidators = Array(9).fill(validatorAddress);
const tx = await buildNominateTx(manyValidators);
const signablePayload = tx.signablePayload;
should.equal(signablePayload.length, 32);
// the raw extrinsic payload stays full-length and diverges from the hashed signablePayload
const rawExtrinsicPayload = Buffer.from(tx.rawExtrinsicPayload);
rawExtrinsicPayload.length.should.be.greaterThan(256);
rawExtrinsicPayload.should.not.deepEqual(signablePayload);
// signablePayload is the blake2_256 hash of the raw extrinsic payload
utils.getSubstrateSigningBytes(tx.rawExtrinsicPayload).should.deepEqual(signablePayload);
// toJson surfaces the full raw payload as rawSignableHex for the HSM signing path
should.equal(tx.toJson().rawSignableHex, rawExtrinsicPayload.toString('hex'));
});

// The 256-byte boundary is impractical to hit with a real extrinsic, so exercise the
Expand Down
Loading