Skip to content

Commit 46af619

Browse files
authored
Merge pull request #6447 from BitGo/coin-4819
fix(sdk-coin-vet): fix deserialization in vet sdk
2 parents acf9413 + 5a7ce8e commit 46af619

File tree

5 files changed

+132
-89
lines changed

5 files changed

+132
-89
lines changed

modules/sdk-coin-vet/src/lib/transaction/transaction.ts

Lines changed: 73 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import BigNumber from 'bignumber.js';
2-
import {
3-
BaseTransaction,
4-
TransactionType,
5-
InvalidTransactionError,
6-
TransactionRecipient,
7-
PublicKey,
8-
} from '@bitgo/sdk-core';
2+
import { BaseTransaction, TransactionType, InvalidTransactionError, TransactionRecipient } from '@bitgo/sdk-core';
93
import { BaseCoin as CoinConfig } from '@bitgo/statics';
104
import {
115
TransactionBody,
@@ -35,13 +29,9 @@ export class Transaction extends BaseTransaction {
3529
private _dependsOn: string | null;
3630
private _nonce: number;
3731
private _sender: string;
38-
private _senderSignature: Buffer;
32+
private _senderSignature: Buffer | null;
3933
private _feePayerAddress: string;
40-
private _feePayerSignature: Buffer;
41-
private _feePayerPubKey: PublicKey;
42-
43-
static EMPTY_PUBLIC_KEY = Buffer.alloc(32);
44-
static EMPTY_SIGNATURE = Buffer.alloc(64);
34+
private _feePayerSignature: Buffer | null;
4535

4636
constructor(_coinConfig: Readonly<CoinConfig>) {
4737
super(_coinConfig);
@@ -55,15 +45,12 @@ export class Transaction extends BaseTransaction {
5545
this._dependsOn = null;
5646
this._nonce = 0x0;
5747
this._recipients = [];
58-
this._feePayerPubKey = {
59-
pub: HexUInt.of(Transaction.EMPTY_PUBLIC_KEY).toString(),
60-
};
61-
this._senderSignature = Transaction.EMPTY_SIGNATURE;
62-
this._feePayerSignature = Transaction.EMPTY_SIGNATURE;
48+
this._senderSignature = null;
49+
this._feePayerSignature = null;
6350
}
6451

6552
public get id(): string {
66-
this.generateTxnId();
53+
this.generateTxnIdAndSetSender();
6754
return this._id ?? 'UNAVAILABLE';
6855
}
6956

@@ -92,7 +79,11 @@ export class Transaction extends BaseTransaction {
9279
}
9380

9481
get senderSignature(): Uint8Array | undefined {
95-
return this._senderSignature;
82+
if (this._senderSignature) {
83+
return new Uint8Array(this._senderSignature);
84+
} else {
85+
return undefined;
86+
}
9687
}
9788

9889
set senderSignature(sig: Buffer) {
@@ -107,8 +98,12 @@ export class Transaction extends BaseTransaction {
10798
this._feePayerAddress = address;
10899
}
109100

110-
get feePayerSignature(): Uint8Array {
111-
return this._feePayerSignature;
101+
get feePayerSignature(): Uint8Array | undefined {
102+
if (this._feePayerSignature) {
103+
return new Uint8Array(this._feePayerSignature);
104+
} else {
105+
return undefined;
106+
}
112107
}
113108

114109
set feePayerSignature(sig: Buffer) {
@@ -187,14 +182,6 @@ export class Transaction extends BaseTransaction {
187182
this._nonce = n;
188183
}
189184

190-
get feePayerPubKey(): PublicKey {
191-
return this._feePayerPubKey;
192-
}
193-
194-
set feePayerPubKey(pubKey: PublicKey) {
195-
this._feePayerPubKey = pubKey;
196-
}
197-
198185
get contract(): string {
199186
return this._contract;
200187
}
@@ -300,7 +287,7 @@ export class Transaction extends BaseTransaction {
300287
this.loadInputsAndOutputs();
301288

302289
// Set sender address
303-
if (signedTx.origin) {
290+
if (signedTx.signature && signedTx.origin) {
304291
this.sender = signedTx.origin.toString().toLowerCase();
305292
}
306293

@@ -323,56 +310,52 @@ export class Transaction extends BaseTransaction {
323310
this.senderSignature = signature;
324311
}
325312

326-
getFeePayerPubKey(): string {
327-
return this.feePayerPubKey.pub;
328-
}
329-
330-
addFeePayerSignature(publicKey: PublicKey, signature: Buffer): void {
331-
this.feePayerPubKey = publicKey;
313+
addFeePayerSignature(signature: Buffer): void {
332314
this.feePayerSignature = signature;
333315
}
334316

335317
async build(): Promise<void> {
336318
this.buildClauses();
337319
await this.buildRawTransaction();
338-
this.generateTxnId();
320+
this.generateTxnIdAndSetSender();
339321
this.loadInputsAndOutputs();
340322
}
341323

342324
/**
343325
* Sets the transaction ID from the raw transaction if it is signed
344326
* @protected
345327
*/
346-
protected generateTxnId(): void {
328+
protected generateTxnIdAndSetSender(): void {
347329
// Check if we have a raw transaction
348330
if (!this.rawTransaction) {
349331
return;
350332
}
351-
352-
// Check if the transaction is signed by verifying signature exists
353-
if (!this.senderSignature || !this.feePayerSignature) {
354-
return;
355-
}
356-
357-
const halfSignedTransaction: VetTransaction = VetTransaction.of(this.rawTransaction.body, this.senderSignature);
358-
if (!halfSignedTransaction.signature) {
359-
return;
360-
}
361-
const fullSignedTransaction: VetTransaction = VetTransaction.of(
362-
halfSignedTransaction.body,
363-
nc_utils.concatBytes(
364-
// Drop any previous gas payer signature.
365-
halfSignedTransaction.signature.slice(0, Secp256k1.SIGNATURE_LENGTH),
366-
this.feePayerSignature
367-
)
368-
);
369-
if (!fullSignedTransaction.signature) {
370-
return;
371-
}
372-
try {
373-
this._id = fullSignedTransaction.id.toString();
374-
} catch (e) {
333+
if (!this.senderSignature) {
375334
return;
335+
} else {
336+
const halfSignedTransaction: VetTransaction = VetTransaction.of(this.rawTransaction.body, this.senderSignature);
337+
if (halfSignedTransaction.signature) {
338+
this._rawTransaction = halfSignedTransaction;
339+
this._sender = halfSignedTransaction.origin.toString().toLowerCase();
340+
} else {
341+
return;
342+
}
343+
if (this.feePayerSignature) {
344+
const fullSignedTransaction: VetTransaction = VetTransaction.of(
345+
halfSignedTransaction.body,
346+
nc_utils.concatBytes(
347+
// Drop any previous gas payer signature.
348+
halfSignedTransaction.signature.slice(0, Secp256k1.SIGNATURE_LENGTH),
349+
this.feePayerSignature
350+
)
351+
);
352+
if (fullSignedTransaction.signature) {
353+
this._rawTransaction = fullSignedTransaction;
354+
this._id = fullSignedTransaction.id.toString();
355+
} else {
356+
return;
357+
}
358+
}
376359
}
377360
}
378361

@@ -468,22 +451,34 @@ export class Transaction extends BaseTransaction {
468451
}
469452

470453
serialize(): string {
471-
const halfSignedTransaction: VetTransaction = VetTransaction.of(this.rawTransaction.body, this.senderSignature);
472-
if (!halfSignedTransaction.signature) {
473-
throw new InvalidTransactionError('Invalid sender signature in half-signed transaction');
474-
}
475-
if (!this.feePayerSignature) {
476-
throw new InvalidTransactionError('Missing fee payer signature');
454+
if (!this.senderSignature) {
455+
return HexUInt.of(this.rawTransaction.encoded).toString();
456+
} else {
457+
if (!this.feePayerSignature) {
458+
const senderSignedTransaction: VetTransaction = VetTransaction.of(
459+
this.rawTransaction.body,
460+
this.senderSignature
461+
);
462+
return HexUInt.of(senderSignedTransaction.encoded).toString();
463+
} else {
464+
const senderSignedTransaction: VetTransaction = VetTransaction.of(
465+
this.rawTransaction.body,
466+
this.senderSignature
467+
);
468+
if (senderSignedTransaction.signature) {
469+
const fullSignedTransaction: VetTransaction = VetTransaction.of(
470+
senderSignedTransaction.body,
471+
nc_utils.concatBytes(
472+
senderSignedTransaction.signature.slice(0, Secp256k1.SIGNATURE_LENGTH),
473+
this.feePayerSignature
474+
)
475+
);
476+
return HexUInt.of(fullSignedTransaction.encoded).toString();
477+
} else {
478+
throw new InvalidTransactionError('Transaction is not signed properly');
479+
}
480+
}
477481
}
478-
const fullSignedTransaction: VetTransaction = VetTransaction.of(
479-
halfSignedTransaction.body,
480-
nc_utils.concatBytes(
481-
// Drop any previous gas payer signature.
482-
halfSignedTransaction.signature.slice(0, Secp256k1.SIGNATURE_LENGTH),
483-
this.feePayerSignature
484-
)
485-
);
486-
return HexUInt.of(fullSignedTransaction.encoded).toString();
487482
}
488483

489484
static deserializeTransaction(rawTx: string): VetTransaction {

modules/sdk-coin-vet/src/lib/transactionBuilder/transactionBuilder.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
BuildTransactionError,
77
Recipient,
88
TransactionType,
9-
PublicKey,
109
ParseTransactionError,
1110
} from '@bitgo/sdk-core';
1211
import { BaseCoin as CoinConfig } from '@bitgo/statics';
@@ -41,6 +40,11 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
4140
this._transaction = tx;
4241
}
4342

43+
chainTag(tag: number): this {
44+
this._transaction.chainTag = tag;
45+
return this;
46+
}
47+
4448
/**
4549
* Sets the sender of this transaction.
4650
*
@@ -104,8 +108,8 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
104108
this.transaction.addSenderSignature(signature);
105109
}
106110

107-
addFeePayerSignature(publicKey: PublicKey, signature: Buffer): void {
108-
this.transaction.addFeePayerSignature(publicKey, signature);
111+
addFeePayerSignature(signature: Buffer): void {
112+
this.transaction.addFeePayerSignature(signature);
109113
}
110114

111115
/** @inheritdoc */
@@ -129,7 +133,6 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
129133
if (!transaction) {
130134
throw new Error('transaction not defined');
131135
}
132-
this.validateAddress({ address: transaction.sender });
133136
for (const recipient of transaction.recipients) {
134137
this.validateAddress({ address: recipient.address });
135138
this.validateValue(new BigNumber(recipient.amount));
@@ -167,10 +170,6 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
167170
this.transaction.feePayerAddress = address;
168171
}
169172

170-
getFeePayerPubKey(): string {
171-
return this.transaction.getFeePayerPubKey();
172-
}
173-
174173
/** @inheritdoc */
175174
protected signImplementation(key: BaseKey): Transaction {
176175
throw new Error('Method not implemented');

modules/sdk-coin-vet/test/resources/vet.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const SPONSORED_TRANSACTION =
77
'0xf8bc2788014e9cad44bade0940e0df94e59f1cea4e0fef511e3d0f4eec44adf19c4cbeec88016345785d8a0000808180825208808302d6b5c101b882ee76129c1259eb0c8a2b3b3e5f2b089cd11068da1c0db32a9e22228db83dd4be5c721858bc813514141fbd5cf641d0972ce47ceb9be61133fa2ebf0ea37c1f290011fdce201f56d639d827035a5ed8bcef42a42f6eb562bc76a6d95c7736cf8cf340122d1e2fb034668dc491d47b7d3bb10724ba2338a6e79df87bce9617fdce9c00';
88

99
export const UNSIGNED_TRANSACTION =
10-
'0xf8772788014eabfe2b8fcc1440dddc94e59f1cea4e0fef511e3d0f4eec44adf19c4cbeec85e8d4a51000808180825208808301bff7c0b8414f4b195e2dd666a01c1186df341ff3c0cbe2d6b4a58dca3c8c484f3eac05c4100a165a2730fe67b4c11b4a6c78ea8ab0757bf59735adef327a36737e1694536b00';
10+
'0xf4278801536ce9e9fb063840dddc94c52584d1c56e7bddcb6f65d50ff00f71e0ef897a85e8d4a510008081808252088083061c70c0';
1111

1212
export const UNSIGNED_TRANSACTION_2 =
1313
'0xf72788014ead140e77bbc140e0df94e59f1cea4e0fef511e3d0f4eec44adf19c4cbeec88016345785d8a00008081808252088082faf8c101';
@@ -45,14 +45,30 @@ export const TRANSFER_CLAUSE = [
4545
];
4646

4747
export const addresses = {
48-
validAddresses: ['0x7ca00e3bc8a836026c2917c6c7c6d049e52099dd', '0xe59f1cea4e0fef511e3d0f4eec44adf19c4cbeec'],
48+
validAddresses: [
49+
'0x7ca00e3bc8a836026c2917c6c7c6d049e52099dd',
50+
'0xe59f1cea4e0fef511e3d0f4eec44adf19c4cbeec',
51+
'0x7c87b9ffc6fd6c167c0e4fa9418720f3d659358e',
52+
],
4953
invalidAddresses: [
5054
'randomString',
5155
'0xc4173a804406a365e69dfb297ddfgsdcvf',
5256
'5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen',
5357
],
5458
};
5559

60+
export const senderSig =
61+
'062080cc77db9d5ce5db84f1cfb57c9906c9bfdce78e4d53757bbd3d536c731e1e5e406a4e783a4f63446aa28d21dc27796aae90fac79151c3d0b3ba68cde36801';
62+
63+
export const feePayerSig =
64+
'47ae79e23effd233206180ece3ab3d6d496310c03c55bd0566a54bb7d42095c97a90ca9ff4cfb94ec579a7ec7f79de4814dfc816d0a130189c07b746945482fb00';
65+
66+
export const senderSignedSerializedTxHex =
67+
'0xf72788014ead140e77bbc140e0df94e59f1cea4e0fef511e3d0f4eec44adf19c4cbeec88016345785d8a00008081808252088082faf8c101';
68+
69+
export const completeSignedSerializedHex =
70+
'0xf8bb2788014ead140e77bbc140e0df94e59f1cea4e0fef511e3d0f4eec44adf19c4cbeec88016345785d8a00008081808252088082faf8c101b882062080cc77db9d5ce5db84f1cfb57c9906c9bfdce78e4d53757bbd3d536c731e1e5e406a4e783a4f63446aa28d21dc27796aae90fac79151c3d0b3ba68cde3680147ae79e23effd233206180ece3ab3d6d496310c03c55bd0566a54bb7d42095c97a90ca9ff4cfb94ec579a7ec7f79de4814dfc816d0a130189c07b746945482fb00';
71+
5672
export const blockIds: { validBlockIds: string[]; invalidBlockIds: string[] } = {
5773
validBlockIds: [
5874
'0x014f12ed94c4b4770f7f9a73e2aa41a9dfbac02a49f36ec05acfdba8c7244ff0',

modules/sdk-coin-vet/test/transactionBuilder/transferBuilder.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,34 @@ describe('Vet Transfer Transaction', () => {
138138
should.equal(toJson.gasPriceCoef, 128);
139139
should.equal(toJson.expiration, 64);
140140
});
141+
142+
it('should build a unsigned tx then add sender sig and build again', async function () {
143+
const transaction = new Transaction(coins.get('tvet'));
144+
const txBuilder = factory.getTransferBuilder(transaction);
145+
txBuilder.sender(testData.addresses.validAddresses[2]);
146+
txBuilder.recipients(testData.recipients);
147+
txBuilder.gas(21000);
148+
txBuilder.nonce(64248);
149+
txBuilder.blockRef('0x014ead140e77bbc1');
150+
txBuilder.addFeePayerAddress(testData.feePayer.address);
151+
txBuilder.expiration(64);
152+
txBuilder.gasPriceCoef(128);
153+
const tx = (await txBuilder.build()) as Transaction;
154+
const unsignedSerializedTx = tx.toBroadcastFormat();
155+
156+
const builder1 = factory.from(unsignedSerializedTx);
157+
builder1.addSenderSignature(Buffer.from(testData.senderSig, 'hex'));
158+
const senderSignedTx = await builder1.build();
159+
const senderSignedSerializedTx = senderSignedTx.toBroadcastFormat();
160+
should.equal(senderSignedSerializedTx, testData.senderSignedSerializedTxHex);
161+
162+
const builder2 = factory.from(testData.senderSignedSerializedTxHex);
163+
builder2.addSenderSignature(Buffer.from(testData.senderSig, 'hex'));
164+
builder2.addFeePayerSignature(Buffer.from(testData.feePayerSig, 'hex'));
165+
const completelySignedTx = await builder2.build();
166+
should.equal(completelySignedTx.toBroadcastFormat(), testData.completeSignedSerializedHex);
167+
should.equal(completelySignedTx.id, '0xb988d614d3c24420cb2183239ab601b53e63ff4c81cea1e4888bc9d0aa6aad13');
168+
});
141169
});
142170

143171
describe('Fail', () => {

modules/sdk-coin-vet/test/unit/utils.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,16 @@ describe('Vechain util library', function () {
4646
describe('isValidDeserialize', function () {
4747
it('should succeed to correctly deserialize sponsored signed serialized transaction', function () {
4848
const signedTxn: VetTransaction = utils.deserializeTransaction(testData.SPONSORED_TRANSACTION);
49+
should.equal(signedTxn.origin.toString().toLowerCase(), '0x7ca00e3bc8a836026c2917c6c7c6d049e52099dd');
4950
should.equal(signedTxn.gasPayer.toString().toLowerCase(), '0xdc9fef0b84a0ccf3f1bd4b84e41743e3e051a083');
5051
});
5152
it('should succeed to correctly deserialize unsigned serialized transaction', function () {
5253
const signedTxn: VetTransaction = utils.deserializeTransaction(testData.UNSIGNED_TRANSACTION);
53-
should.equal(signedTxn.origin.toString().toLowerCase(), '0x7ca00e3bc8a836026c2917c6c7c6d049e52099dd');
54+
should.equal(signedTxn.isSigned, false);
55+
should.equal(signedTxn.body.gas, 21000);
56+
should.equal(signedTxn.body.nonce, 400496);
57+
should.equal(signedTxn.body.expiration, 64);
58+
should.equal(signedTxn.body.blockRef, '0x01536ce9e9fb0638');
5459
});
5560
it('should fail to deserialize an invalid serialized transaction', function () {
5661
should.throws(() => utils.deserializeTransaction(testData.INVALID_TRANSACTION));

0 commit comments

Comments
 (0)