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
11 changes: 9 additions & 2 deletions src/model/transaction/AggregateTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,12 +289,19 @@ export class AggregateTransaction extends Transaction {
* @internal
* @returns {Uint8Array}
*/
protected generateBytes(): Uint8Array {
protected generateBytes(signer?: PublicAccount): Uint8Array {
const signerBuffer = new Uint8Array(32);
const signatureBuffer = new Uint8Array(64);

let transactions = Uint8Array.from([]);
this.innerTransactions.forEach((transaction) => {
if (!transaction.signer) {
if (this.type === TransactionType.AGGREGATE_COMPLETE) {
transaction = Object.assign({__proto__: Object.getPrototypeOf(transaction)}, transaction, {signer});
} else {
throw new Error(
'InnerTransaction signer must be provide. Only AggregateComplete transaction can use delegated signer.');
}
}
const transactionByte = transaction.toAggregateTransactionBytes();
transactions = GeneratorUtils.concatTypedArrays(transactions, transactionByte);
});
Expand Down
2 changes: 1 addition & 1 deletion src/model/transaction/InnerTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ import {Transaction} from './Transaction';
/**
* Transaction with signer included, used when adding signer to transactions included in an aggregate transaction.
*/
export type InnerTransaction = Transaction & {signer: PublicAccount};
export type InnerTransaction = Transaction & {signer?: PublicAccount};
11 changes: 7 additions & 4 deletions src/model/transaction/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ export abstract class Transaction {

/**
* @internal
* @param singer Optional singer for delegated aggregate complete transaction only.
*/
protected abstract generateBytes(): Uint8Array;
protected abstract generateBytes(signer?: PublicAccount): Uint8Array;

/**
* @internal
Expand All @@ -123,7 +124,7 @@ export abstract class Transaction {
public signWith(account: Account, generationHash: string): SignedTransaction {
const generationHashBytes = Array.from(Convert.hexToUint8(generationHash));
const signSchema = SHA3Hasher.resolveSignSchema(account.networkType);
const byteBuffer = Array.from(this.generateBytes());
const byteBuffer = Array.from(this.generateBytes(account.publicAccount));
const signingBytes = generationHashBytes.concat(byteBuffer.slice(4 + 64 + 32));
const keyPairEncoded = KeyPair.createKeyPairFromPrivateKeyString(account.privateKey, signSchema);
const signature = Array.from(KeyPair.sign(account, new Uint8Array(signingBytes), signSchema));
Expand Down Expand Up @@ -162,10 +163,12 @@ export abstract class Transaction {

/**
* Convert an aggregate transaction to an inner transaction including transaction signer.
* @param signer - Transaction signer.
* Signer is optional for `AggregateComplete` transaction `ONLY`.
* If no signer provided, aggregate transaction signer will be delegated on signing
* @param signer - Innre transaction signer. (Optional for `AggregateComplete` transaction `ONLY`)
* @returns InnerTransaction
*/
public toAggregate(signer: PublicAccount): InnerTransaction {
public toAggregate(signer?: PublicAccount): InnerTransaction {
if (this.type === TransactionType.AGGREGATE_BONDED || this.type === TransactionType.AGGREGATE_COMPLETE) {
throw new Error('Inner transaction cannot be an aggregated transaction.');
}
Expand Down
2 changes: 1 addition & 1 deletion src/service/AggregateTransactionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class AggregateTransactionService {
signers.push(signedTransaction.signerPublicKey);
}
return observableFrom(aggregateTransaction.innerTransactions).pipe(
mergeMap((innerTransaction) => this.accountHttp.getMultisigAccountInfo(innerTransaction.signer.address)
mergeMap((innerTransaction) => this.accountHttp.getMultisigAccountInfo(innerTransaction.signer!.address)
.pipe(
/**
* For multisig account, we need to get the graph info in case it has multiple levels
Expand Down
50 changes: 50 additions & 0 deletions test/model/transaction/AggregateTransaction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {expect} from 'chai';
import {ChronoUnit} from 'js-joda';
import { TransactionMapping } from '../../../src/core/utils/TransactionMapping';
import {CreateTransactionFromDTO} from '../../../src/infrastructure/transaction/CreateTransactionFromDTO';
import { CreateTransactionFromPayload } from '../../../src/infrastructure/transaction/CreateTransactionFromPayload';
import {Account} from '../../../src/model/account/Account';
import {Address} from '../../../src/model/account/Address';
import {PublicAccount} from '../../../src/model/account/PublicAccount';
Expand Down Expand Up @@ -116,6 +117,55 @@ describe('AggregateTransaction', () => {
)).to.be.equal('019054419050B9837EFAB4BBE8A4B9BB32D812F9885C00D8FC1650E1420D000000746573742D6D657373616765');
});

it('should createComplete an AggregateTransaction object with delegated signer', () => {
const transferTransaction = TransferTransaction.create(
Deadline.create(1, ChronoUnit.HOURS),
Address.createFromRawAddress('SBILTA367K2LX2FEXG5TFWAS7GEFYAGY7QLFBYKC'),
[],
PlainMessage.create('test-message'),
NetworkType.MIJIN_TEST,
);

const aggregateTransaction = AggregateTransaction.createComplete(
Deadline.create(),
[transferTransaction.toAggregate()],
NetworkType.MIJIN_TEST,
[]);
expect(aggregateTransaction.innerTransactions[0].signer).to.be.undefined;

const signedTransaction = aggregateTransaction.signWith(account, generationHash);

expect(signedTransaction.payload.substring(0, 8)).to.be.equal('CD000000');
expect(signedTransaction.payload.substring(240, 256)).to.be.equal('5100000051000000');
expect(signedTransaction.payload.substring(
320,
signedTransaction.payload.length,
)).to.be.equal('019054419050B9837EFAB4BBE8A4B9BB32D812F9885C00D8FC1650E1420D000000746573742D6D657373616765');

const createdFromPayload = CreateTransactionFromPayload(signedTransaction.payload);
expect((createdFromPayload as AggregateTransaction).innerTransactions[0].signer!.publicKey)
.to.be.equal(account.publicKey);
});

it('should throw exception with delegated signer', () => {
expect(() => {
const transferTransaction = TransferTransaction.create(
Deadline.create(1, ChronoUnit.HOURS),
Address.createFromRawAddress('SBILTA367K2LX2FEXG5TFWAS7GEFYAGY7QLFBYKC'),
[],
PlainMessage.create('test-message'),
NetworkType.MIJIN_TEST,
);
const aggregateTransaction = AggregateTransaction.createBonded(
Deadline.create(),
[transferTransaction.toAggregate()],
NetworkType.MIJIN_TEST,
[]);

aggregateTransaction.signWith(account, generationHash);
}).to.throw(Error, 'InnerTransaction signer must be provide. Only AggregateComplete transaction can use delegated signer.');
});

it('should createComplete an AggregateTransaction object with NamespaceRegistrationTransaction', () => {
const registerNamespaceTransaction = NamespaceRegistrationTransaction.createRootNamespace(
Deadline.create(),
Expand Down