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
44 changes: 35 additions & 9 deletions src/model/transaction/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,16 +214,12 @@ export abstract class Transaction {
public signWith(account: Account, generationHash: string): SignedTransaction {
const generationHashBytes = Array.from(Convert.hexToUint8(generationHash));
const byteBuffer = Array.from(this.generateBytes());
// 1. prepare the raw transaction to be signed
const signingBytes = this.getSigningBytes(byteBuffer, generationHashBytes);
const keyPairEncoded = KeyPair.createKeyPairFromPrivateKeyString(account.privateKey);
const signature = Array.from(KeyPair.sign(keyPairEncoded, new Uint8Array(signingBytes)));
const signedTransactionBuffer = byteBuffer
.splice(0, 8)
.concat(signature)
.concat(Array.from(keyPairEncoded.publicKey))
.concat(Array.from(new Uint8Array(4)))
.concat(byteBuffer.splice(64 + 32 + 4, byteBuffer.length));
const payload = Convert.uint8ToHex(signedTransactionBuffer);
// 2. sign the raw transaction
const signature = Transaction.signRawTransaction(account.privateKey, Uint8Array.from(signingBytes));
// 3. prepare the (signed) payload
const payload = Transaction.preparePayload(Uint8Array.from(byteBuffer), signature, account.publicKey);
return new SignedTransaction(
payload,
Transaction.createTransactionHash(payload, generationHashBytes),
Expand All @@ -233,6 +229,36 @@ export abstract class Transaction {
);
}

/**
* Signs raw transaction with the given private key
* @param {string} privateKey - Private key of the signer account
* @param {Uint8Array} rawTransactionSigningBytes - Raw transaction siging bytes
* @returns {Uint8Array} Signature byte array
*/
public static signRawTransaction(privateKey: string, rawTransactionSigningBytes: Uint8Array): Uint8Array {
const keyPairEncoded = KeyPair.createKeyPairFromPrivateKeyString(privateKey);
return KeyPair.sign(keyPairEncoded, new Uint8Array(rawTransactionSigningBytes));
}

/**
* Prepares and return signed payload
* @param {Uint8Array} serializedTransaction Serialized transaction
* @param {Uint8Array} signature Signature of the transaction
* @param {string} publicKey Public key of the signing account
* @returns {string} Payload (ready to be announced)
*/
public static preparePayload(serializedTransaction: Uint8Array, signature: Uint8Array, publicKey: string): string {
const transactionBytes = Array.from(serializedTransaction);
const signatureBytes = Array.from(signature);
const signedTransactionBuffer = transactionBytes
.splice(0, 8)
.concat(signatureBytes)
.concat(Array.from(Convert.hexToUint8(publicKey)))
.concat(Array.from(new Uint8Array(4)))
.concat(transactionBytes.splice(64 + 32 + 4, transactionBytes.length));
return Convert.uint8ToHex(signedTransactionBuffer);
}

/**
* Generate signing bytes
* @param payloadBytes Payload buffer
Expand Down
25 changes: 25 additions & 0 deletions test/model/transaction/Transaction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,4 +391,29 @@ describe('Transaction', () => {
expect((tx as Transaction).isSigned(account.address)).to.be.true;
expect((tx as Transaction).isSigned(Address.createFromRawAddress('VATNE7Q5BITMUTRRN6IB4I7FLSDRDWZA35C4KNQ'))).to.be.false;
});

it('should prepare valid transaction payload', () => {
const tx = TransferTransaction.create(
Deadline.createFromDTO('1'),
Address.createFromRawAddress('VATNE7Q5BITMUTRRN6IB4I7FLSDRDWZA35C4KNQ'),
[],
PlainMessage.create('test-message'),
NetworkType.PRIVATE_TEST,
) as Transaction;
const expectedPayload =
'AD00000000000000F5CEC2900A317ED93F5BC6621AE11FF960E07BD9D780CF625EA49FD8F073973EB4DF2598B089CC78A6C48519138EB1CC0A0927467D1925838DDA074C6C81170F9801508C58666C746F471538E43002B85B1CD542F9874B2861183919BA8787B60000000001A8544100000000000000000100000000000000A826D27E1D0A26CA4E316F901E23E55C8711DB20DF45C5360D0000000000000000746573742D6D657373616765';
const signature =
'F5CEC2900A317ED93F5BC6621AE11FF960E07BD9D780CF625EA49FD8F073973EB4DF2598B089CC78A6C48519138EB1CC0A0927467D1925838DDA074C6C81170F';
const payload = Transaction.preparePayload(Convert.hexToUint8(tx.serialize()), Convert.hexToUint8(signature), account.publicKey);
expect(payload).to.be.eq(expectedPayload);
});

it('should sign raw transaction and produce a valid signature', () => {
const txSigningBytes =
'57F7DA205008026C776CB6AED843393F04CD458E0AA2D9F1D5F31A402072B2D601A8544100000000000000000100000000000000A826D27E1D0A26CA4E316F901E23E55C8711DB20DF45C5360D0000000000000000746573742D6D657373616765';
const expectedSignature =
'F5CEC2900A317ED93F5BC6621AE11FF960E07BD9D780CF625EA49FD8F073973EB4DF2598B089CC78A6C48519138EB1CC0A0927467D1925838DDA074C6C81170F';
const signature = Transaction.signRawTransaction(account.privateKey, Convert.hexToUint8(txSigningBytes));
expect(Convert.uint8ToHex(signature)).to.be.eq(expectedSignature);
});
});