Skip to content

Commit

Permalink
feat(sdk-coin-apt): added logic for fee payer signing
Browse files Browse the repository at this point in the history
TICKET: COIN-2838
  • Loading branch information
at31416 committed Jan 22, 2025
1 parent 9d1ff23 commit 444b849
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 41 deletions.
1 change: 1 addition & 0 deletions modules/sdk-coin-apt/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ export interface TransferTxData {
gasUnitPrice: number;
gasUsed: number;
expirationTime: number;
feePayer: string;
}
88 changes: 68 additions & 20 deletions modules/sdk-coin-apt/src/lib/transaction/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
AccountAddress,
AccountAuthenticatorEd25519,
Aptos,
APTOS_COIN,
AptosConfig,
DEFAULT_MAX_GAS_AMOUNT,
Ed25519PublicKey,
Expand All @@ -23,22 +22,24 @@ import {
RawTransaction,
SignedTransaction,
SimpleTransaction,
TransactionAuthenticatorEd25519,
TransactionAuthenticatorFeePayer,
} from '@aptos-labs/ts-sdk';
import { DEFAULT_GAS_UNIT_PRICE, SECONDS_PER_WEEK, UNAVAILABLE_TEXT } from '../constants';
import utils from '../utils';
import BigNumber from 'bignumber.js';

export abstract class Transaction extends BaseTransaction {
protected _rawTransaction: RawTransaction;
protected _signature: Signature;
protected _senderSignature: Signature;
protected _feePayerSignature: Signature;
protected _sender: string;
protected _recipient: TransactionRecipient;
protected _sequenceNumber: number;
protected _maxGasAmount: number;
protected _gasUnitPrice: number;
protected _gasUsed: number;
protected _expirationTime: number;
protected _feePayerAddress: string;

static EMPTY_PUBLIC_KEY = Buffer.alloc(32);
static EMPTY_SIGNATURE = Buffer.alloc(64);
Expand All @@ -50,7 +51,13 @@ export abstract class Transaction extends BaseTransaction {
this._gasUsed = 0;
this._expirationTime = Math.floor(Date.now() / 1e3) + SECONDS_PER_WEEK;
this._sequenceNumber = 0;
this._signature = {
this._senderSignature = {
publicKey: {
pub: Hex.fromHexInput(Transaction.EMPTY_PUBLIC_KEY).toString(),
},
signature: Transaction.EMPTY_SIGNATURE,
};
this._feePayerSignature = {
publicKey: {
pub: Hex.fromHexInput(Transaction.EMPTY_PUBLIC_KEY).toString(),
},
Expand Down Expand Up @@ -120,6 +127,14 @@ export abstract class Transaction extends BaseTransaction {
this._expirationTime = value;
}

get feePayerAddress(): string {
return this._feePayerAddress;
}

set feePayerAddress(value: string) {
this._feePayerAddress = value;
}

set transactionType(transactionType: TransactionType) {
this._type = transactionType;
}
Expand All @@ -136,19 +151,38 @@ export abstract class Transaction extends BaseTransaction {
}

serialize(): string {
const publicKeyBuffer = utils.getBufferFromHexString(this._signature.publicKey.pub);
const publicKey = new Ed25519PublicKey(publicKeyBuffer);
const senderPublicKeyBuffer = utils.getBufferFromHexString(this._senderSignature.publicKey.pub);
const senderPublicKey = new Ed25519PublicKey(senderPublicKeyBuffer);

const signature = new Ed25519Signature(this._signature.signature);
const senderSignature = new Ed25519Signature(this._senderSignature.signature);
const senderAuthenticator = new AccountAuthenticatorEd25519(senderPublicKey, senderSignature);

const feePayerPublicKeyBuffer = utils.getBufferFromHexString(this._feePayerSignature.publicKey.pub);
const feePayerPublicKey = new Ed25519PublicKey(feePayerPublicKeyBuffer);

const feePayerSignature = new Ed25519Signature(this._feePayerSignature.signature);
const feePayerAuthenticator = new AccountAuthenticatorEd25519(feePayerPublicKey, feePayerSignature);

const txnAuthenticator = new TransactionAuthenticatorFeePayer(senderAuthenticator, [], [], {
address: AccountAddress.fromString(this._feePayerAddress),
authenticator: feePayerAuthenticator,
});

const txnAuthenticator = new TransactionAuthenticatorEd25519(publicKey, signature);
const signedTxn = new SignedTransaction(this._rawTransaction, txnAuthenticator);
return signedTxn.toString();
}

addSignature(publicKey: PublicKey, signature: Buffer): void {
addSenderSignature(publicKey: PublicKey, signature: Buffer): void {
this._signatures = [signature.toString('hex')];
this._signature = { publicKey, signature };
this._senderSignature = { publicKey, signature };
}

addFeePayerSignature(publicKey: PublicKey, signature: Buffer): void {
this._feePayerSignature = { publicKey, signature };
}

addFeePayerAddress(address: string): void {
this._feePayerAddress = address;
}

async build(): Promise<void> {
Expand Down Expand Up @@ -197,9 +231,15 @@ export abstract class Transaction extends BaseTransaction {
this._rawTransaction = rawTxn;

this.loadInputsAndOutputs();
const authenticator = signedTxn.authenticator as TransactionAuthenticatorEd25519;
const signature = Buffer.from(authenticator.signature.toUint8Array());
this.addSignature({ pub: authenticator.public_key.toString() }, signature);
const authenticator = signedTxn.authenticator as any;
this._feePayerAddress = authenticator.fee_payer.address.toString();
const senderSignature = Buffer.from(authenticator.sender.signature.toUint8Array());
this.addSenderSignature({ pub: authenticator.sender.public_key.toString() }, senderSignature);
const feePayerSignature = Buffer.from(authenticator.fee_payer.authenticator.signature.toUint8Array());
this.addFeePayerSignature(
{ pub: authenticator.fee_payer.authenticator.public_key.toString() },
feePayerSignature
);
} catch (e) {
console.error('invalid signed transaction', e);
throw new Error('invalid signed transaction');
Expand Down Expand Up @@ -229,8 +269,7 @@ export abstract class Transaction extends BaseTransaction {
const simpleTxn = await aptos.transaction.build.simple({
sender: senderAddress,
data: {
function: '0x1::coin::transfer',
typeArguments: [APTOS_COIN],
function: '0x1::aptos_account::transfer',
functionArguments: [recipientAddress, this.recipient.amount],
},
options: {
Expand All @@ -248,13 +287,22 @@ export abstract class Transaction extends BaseTransaction {
}

private generateTxnId() {
if (!this._signature || !this._signature.publicKey || !this._signature.signature) {
if (
!this._senderSignature ||
!this._senderSignature.publicKey ||
!this._senderSignature.signature ||
!this._feePayerSignature.publicKey ||
!this._feePayerSignature.signature
) {
return;
}
const transaction = new SimpleTransaction(this._rawTransaction);
const publicKey = new Ed25519PublicKey(utils.getBufferFromHexString(this._signature.publicKey.pub));
const signature = new Ed25519Signature(this._signature.signature);
const senderAuthenticator = new AccountAuthenticatorEd25519(publicKey, signature);
this._id = generateUserTransactionHash({ transaction, senderAuthenticator });
const senderPublicKey = new Ed25519PublicKey(utils.getBufferFromHexString(this._senderSignature.publicKey.pub));
const senderSignature = new Ed25519Signature(this._senderSignature.signature);
const senderAuthenticator = new AccountAuthenticatorEd25519(senderPublicKey, senderSignature);
const feePayerPublicKey = new Ed25519PublicKey(utils.getBufferFromHexString(this._feePayerSignature.publicKey.pub));
const feePayerSignature = new Ed25519Signature(this._feePayerSignature.signature);
const feePayerAuthenticator = new AccountAuthenticatorEd25519(feePayerPublicKey, feePayerSignature);
this._id = generateUserTransactionHash({ transaction, senderAuthenticator, feePayerAuthenticator });
}
}
15 changes: 13 additions & 2 deletions modules/sdk-coin-apt/src/lib/transaction/transferTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { Transaction } from './transaction';
import { AptTransactionExplanation, TransferTxData } from '../iface';
import { TransactionRecipient, TransactionType } from '@bitgo/sdk-core';
import { generateSigningMessage, RAW_TRANSACTION_SALT } from '@aptos-labs/ts-sdk';
import {
AccountAddress,
FeePayerRawTransaction,
generateSigningMessage,
RAW_TRANSACTION_WITH_DATA_SALT,
} from '@aptos-labs/ts-sdk';

export class TransferTransaction extends Transaction {
constructor(coinConfig) {
Expand All @@ -10,7 +15,12 @@ export class TransferTransaction extends Transaction {
}

public get signablePayload(): Buffer {
return Buffer.from(generateSigningMessage(this._rawTransaction.bcsToBytes(), RAW_TRANSACTION_SALT));
const feePayerRawTxn = new FeePayerRawTransaction(
this._rawTransaction,
[],
AccountAddress.fromString(this._feePayerAddress)
);
return Buffer.from(generateSigningMessage(feePayerRawTxn.bcsToBytes(), RAW_TRANSACTION_WITH_DATA_SALT));
}

/** @inheritDoc */
Expand Down Expand Up @@ -52,6 +62,7 @@ export class TransferTransaction extends Transaction {
gasUnitPrice: this.gasUnitPrice,
gasUsed: this.gasUsed,
expirationTime: this.expirationTime,
feePayer: this.feePayerAddress,
};
}
}
12 changes: 10 additions & 2 deletions modules/sdk-coin-apt/src/lib/transactionBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,12 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
}

/** @inheritDoc */
addSignature(publicKey: BasePublicKey, signature: Buffer): void {
this.transaction.addSignature(publicKey, signature);
addSenderSignature(publicKey: BasePublicKey, signature: Buffer): void {
this.transaction.addSenderSignature(publicKey, signature);
}

addFeePayerSignature(publicKey: BasePublicKey, signature: Buffer): void {
this.transaction.addFeePayerSignature(publicKey, signature);
}

/** @inheritdoc */
Expand Down Expand Up @@ -151,4 +155,8 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
this.validateValue(new BigNumber(gasData.maxGasAmount));
this.validateValue(new BigNumber(gasData.gasUnitPrice));
}

addFeePayerAddress(address: string): void {
this.transaction.addFeePayerAddress(address);
}
}
13 changes: 12 additions & 1 deletion modules/sdk-coin-apt/test/resources/apt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ export const sender2 = {
publicKey: '0x2121dcd098069ae535697dd019cfd8677ca7aba0adac1d1959cbce6dc54b1259',
};

export const sender3 = {
address: '0x1aed808916ab9b1b30b07abb53561afd46847285ce28651221d406173a372449',
publicKey: '0xf73836f42257240e43d439552471fc9dbcc3f1af5bd0b4ed83f44b5f66146442',
};

export const feePayer = {
address: '0xdbc87a1c816d9bcd06b683c37e80c7162e4d48da7812198b830e4d5d8e0629f2',
privateKey: '0x51a9507d9127841a1465189188d93065cb26bad3bec05d3c8f28e9c1fc35bda0',
publicKey: '0x5223396c531f13e031a9f0cb26d459d799a52e51be9a1cb9e871afb4c31f91ff',
};

export const recipients: Recipient[] = [
{
address: addresses.validAddresses[0],
Expand All @@ -48,7 +59,7 @@ export const invalidRecipients: Recipient[] = [
];

export const TRANSFER =
'0xc8f02d25aa698b3e9fbd8a08e8da4c8ee261832a25a4cde8731b5ec356537d09170000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e000220f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad908e803000000000000400d0300000000006400000000000000207c7667000000000200202121dcd098069ae535697dd019cfd8677ca7aba0adac1d1959cbce6dc54b12594010f340ec153b724c4dc1c9a435d0fafed1775d851c1e8d965925a7879550c69a4677925d9198334a72ae7ce8998226ff0a83743c7ba8a2831136c072bf21c404';
'0x1aed808916ab9b1b30b07abb53561afd46847285ce28651221d406173a37244992000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e73666572000220f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad908e803000000000000400d0300000000006400000000000000979390670000000002030020f73836f42257240e43d439552471fc9dbcc3f1af5bd0b4ed83f44b5f6614644240caeb90efd4b7ecd922c97bb3163e6a9de1fbb2ee0fc0d56af484f4af9b0015c5831341550af29b3686713b6657c821d894635fe13c7933f06ee043728f040b090000dbc87a1c816d9bcd06b683c37e80c7162e4d48da7812198b830e4d5d8e0629f200205223396c531f13e031a9f0cb26d459d799a52e51be9a1cb9e871afb4c31f91ff4013e7e8a1325ee5f656c93baa3d0206a1d9bd6da5abdc6f5d9b8bbbb0926ddac68f3e57a915dd217d2d43e776a6cc01af72f895ea712acc836d30349f29a3c606';

export const INVALID_TRANSFER =
'AAAAAAAAAAAAA6e7361637469bc4a58e500b9e64cb6547ee9b403000000000000002064ba1fb2f2fbd2938a350015d601f4db89cd7e8e2370d0dd9ae3ac4f635c1581111b8a49f67370bc4a58e500b9e64cb6462e39b802000000000000002064ba1fb2f2fbd2938a350015d601f4db89cd7e8e2370d0dd9ae3ac47aa1ff81f01c4173a804406a365e69dfb297d4eaaf002546ebd016400000000000000cba4a48bb0f8b586c167e5dcefaa1c5e96ab3f0836d6ca08f2081732944d1e5b6b406a4a462e39b8030000000000000020b9490ede63215262c434e03f606d9799f3ba704523ceda184b386d47aa1ff81f01000000000000006400000000000000';
6 changes: 3 additions & 3 deletions modules/sdk-coin-apt/test/unit/apt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ describe('APT:', function () {
describe('Parse and Explain Transactions: ', () => {
const transferInputsResponse = [
{
address: testData.sender2.address,
address: testData.sender3.address,
amount: testData.AMOUNT.toString(),
},
];
Expand Down Expand Up @@ -145,7 +145,7 @@ describe('APT:', function () {
'sender',
'type',
],
id: '0x43ea7697550d5effb68c47488fd32a7756ee418e8d2be7d6b7f634f3ac0d7766',
id: '0x9ec764992194c4b4095289a61073e91cf5404d5bedb5a42ab8bf16d07353332b',
outputs: [
{
address: '0xf7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad9',
Expand All @@ -156,7 +156,7 @@ describe('APT:', function () {
changeOutputs: [],
changeAmount: '0',
fee: { fee: '0' },
sender: '0xc8f02d25aa698b3e9fbd8a08e8da4c8ee261832a25a4cde8731b5ec356537d09',
sender: '0x1aed808916ab9b1b30b07abb53561afd46847285ce28651221d406173a372449',
type: 0,
});
});
Expand Down
24 changes: 14 additions & 10 deletions modules/sdk-coin-apt/test/unit/transferBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('Apt Transfer Transaction', () => {
});
txBuilder.sequenceNumber(14);
txBuilder.expirationTime(1736246155);
txBuilder.addFeePayerAddress(testData.feePayer.address);
const tx = (await txBuilder.build()) as TransferTransaction;
should.equal(tx.sender, testData.sender2.address);
should.equal(tx.recipient.address, testData.recipients[0].address);
Expand All @@ -44,7 +45,7 @@ describe('Apt Transfer Transaction', () => {
const rawTx = tx.toBroadcastFormat();
should.equal(utils.isValidRawTransaction(rawTx), true);
rawTx.should.equal(
'0xc8f02d25aa698b3e9fbd8a08e8da4c8ee261832a25a4cde8731b5ec356537d090e0000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e000220f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad908e803000000000000400d03000000000064000000000000008b037d670000000002002000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
'0xc8f02d25aa698b3e9fbd8a08e8da4c8ee261832a25a4cde8731b5ec356537d090e000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e73666572000220f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad908e803000000000000400d03000000000064000000000000008b037d670000000002030020000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dbc87a1c816d9bcd06b683c37e80c7162e4d48da7812198b830e4d5d8e0629f2002000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
);
});

Expand All @@ -54,7 +55,7 @@ describe('Apt Transfer Transaction', () => {
should.equal(tx.type, TransactionType.Send);
tx.inputs.length.should.equal(1);
tx.inputs[0].should.deepEqual({
address: testData.sender2.address,
address: testData.sender3.address,
value: testData.recipients[0].amount,
coin: 'tapt',
});
Expand All @@ -64,11 +65,11 @@ describe('Apt Transfer Transaction', () => {
value: testData.recipients[0].amount,
coin: 'tapt',
});
should.equal(tx.id, '0x43ea7697550d5effb68c47488fd32a7756ee418e8d2be7d6b7f634f3ac0d7766');
should.equal(tx.id, '0x9ec764992194c4b4095289a61073e91cf5404d5bedb5a42ab8bf16d07353332b');
should.equal(tx.maxGasAmount, 200000);
should.equal(tx.gasUnitPrice, 100);
should.equal(tx.sequenceNumber, 23);
should.equal(tx.expirationTime, 1735818272);
should.equal(tx.sequenceNumber, 146);
should.equal(tx.expirationTime, 1737528215);
should.equal(tx.type, TransactionType.Send);
const rawTx = tx.toBroadcastFormat();
should.equal(utils.isValidRawTransaction(rawTx), true);
Expand All @@ -86,11 +87,12 @@ describe('Apt Transfer Transaction', () => {
});
txBuilder.sequenceNumber(14);
txBuilder.expirationTime(1736246155);
txBuilder.addFeePayerAddress(testData.feePayer.address);
const tx = (await txBuilder.build()) as TransferTransaction;
const signablePayload = tx.signablePayload;
should.equal(
signablePayload.toString('hex'),
'b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b193c8f02d25aa698b3e9fbd8a08e8da4c8ee261832a25a4cde8731b5ec356537d090e0000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e000220f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad908e803000000000000400d03000000000064000000000000008b037d670000000002'
'5efa3c4f02f83a0f4b2d69fc95c607cc02825cc4e7be536ef0992df050d9e67c01c8f02d25aa698b3e9fbd8a08e8da4c8ee261832a25a4cde8731b5ec356537d090e000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e73666572000220f7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad908e803000000000000400d03000000000064000000000000008b037d67000000000200dbc87a1c816d9bcd06b683c37e80c7162e4d48da7812198b830e4d5d8e0629f2'
);
});

Expand All @@ -105,6 +107,7 @@ describe('Apt Transfer Transaction', () => {
});
txBuilder.sequenceNumber(14);
txBuilder.expirationTime(1736246155);
txBuilder.addFeePayerAddress(testData.feePayer.address);
const tx = (await txBuilder.build()) as TransferTransaction;
const toJson = tx.toJson();
should.equal(toJson.sender, testData.sender2.address);
Expand All @@ -116,22 +119,23 @@ describe('Apt Transfer Transaction', () => {
should.equal(toJson.maxGasAmount, 200000);
should.equal(toJson.gasUnitPrice, 100);
should.equal(toJson.expirationTime, 1736246155);
should.equal(toJson.feePayer, testData.feePayer.address);
});

it('should build a signed tx and validate its toJson', async function () {
const txBuilder = factory.from(testData.TRANSFER);
const tx = (await txBuilder.build()) as TransferTransaction;
const toJson = tx.toJson();
should.equal(toJson.id, '0x43ea7697550d5effb68c47488fd32a7756ee418e8d2be7d6b7f634f3ac0d7766');
should.equal(toJson.sender, '0xc8f02d25aa698b3e9fbd8a08e8da4c8ee261832a25a4cde8731b5ec356537d09');
should.equal(toJson.id, '0x9ec764992194c4b4095289a61073e91cf5404d5bedb5a42ab8bf16d07353332b');
should.equal(toJson.sender, '0x1aed808916ab9b1b30b07abb53561afd46847285ce28651221d406173a372449');
should.deepEqual(toJson.recipient, {
address: '0xf7405c28a02cf5bab4ea4498240bb3579db45951794eb1c843bef0534c093ad9',
amount: '1000',
});
should.equal(toJson.sequenceNumber, 23);
should.equal(toJson.sequenceNumber, 146);
should.equal(toJson.maxGasAmount, 200000);
should.equal(toJson.gasUnitPrice, 100);
should.equal(toJson.expirationTime, 1735818272);
should.equal(toJson.expirationTime, 1737528215);
});
});

Expand Down
Loading

0 comments on commit 444b849

Please sign in to comment.