Skip to content

Commit 96c2adf

Browse files
authored
Merge pull request #5784 from BitGo/WIN-4880
feat(sdk-coin-icp): implement signing functionality and improve transaction validation
2 parents 9f3813b + 9ec7714 commit 96c2adf

File tree

5 files changed

+60
-20
lines changed

5 files changed

+60
-20
lines changed

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
TransactionRecipient,
55
TransactionType,
66
InvalidTransactionError,
7-
MethodNotImplementedError,
87
TransactionType as BitGoTransactionType,
98
} from '@bitgo/sdk-core';
109
import { BaseCoin as CoinConfig } from '@bitgo/statics';
@@ -200,11 +199,6 @@ export class Transaction extends BaseTransaction {
200199
return JSON.stringify(transaction);
201200
}
202201

203-
/** @inheritdoc */
204-
canSign(key: BaseKey): boolean {
205-
throw new MethodNotImplementedError();
206-
}
207-
208202
async parseUnsignedTransaction(rawTransaction: string): Promise<ParsedTransaction> {
209203
const unsignedTransaction = this._utils.cborDecode(
210204
this._utils.blobFromHex(rawTransaction)
@@ -284,4 +278,9 @@ export class Transaction extends BaseTransaction {
284278
const httpCanisterUpdate = updates[0].content as HttpCanisterUpdate;
285279
return await this.getParsedTransactionFromUpdate(httpCanisterUpdate, true);
286280
}
281+
282+
/** @inheritdoc */
283+
canSign(key: BaseKey): boolean {
284+
return true;
285+
}
287286
}

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
import { BaseCoin as CoinConfig } from '@bitgo/statics';
22
import BigNumber from 'bignumber.js';
3-
import {
4-
BaseTransactionBuilder,
5-
BuildTransactionError,
6-
BaseAddress,
7-
MethodNotImplementedError,
8-
BaseKey,
9-
} from '@bitgo/sdk-core';
3+
import { BaseTransactionBuilder, BuildTransactionError, BaseAddress, SigningError, BaseKey } from '@bitgo/sdk-core';
104
import { Transaction } from './transaction';
115
import utils from './utils';
126
import { IcpTransactionData } from './iface';
@@ -24,10 +18,6 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
2418
this._transaction = new Transaction(_coinConfig, utils);
2519
}
2620

27-
validateKey(key: BaseKey): void {
28-
throw new MethodNotImplementedError();
29-
}
30-
3121
/**
3222
* Sets the public key and the address of the sender of this transaction.
3323
*
@@ -144,4 +134,14 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
144134
validateRawTransaction(rawTransaction: IcpTransactionData): void {
145135
utils.validateRawTransaction(rawTransaction);
146136
}
137+
138+
/** @inheritdoc */
139+
validateKey(key: BaseKey): void {
140+
if (!key || !key.key) {
141+
throw new SigningError('Key is required');
142+
}
143+
if (!utils.isValidPrivateKey(key.key)) {
144+
throw new SigningError('Invalid private key');
145+
}
146+
}
147147
}

modules/sdk-coin-icp/src/lib/transferBuilder.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { BaseCoin as CoinConfig } from '@bitgo/statics';
22
import { TransactionBuilder } from './transactionBuilder';
3-
import { BaseTransaction, BuildTransactionError, MethodNotImplementedError } from '@bitgo/sdk-core';
3+
import { BaseTransaction, BuildTransactionError, BaseKey } from '@bitgo/sdk-core';
44
import { Utils } from './utils';
55
import { Transaction } from './transaction';
66
import { UnsignedTransactionBuilder } from './unsignedTransactionBuilder';
@@ -123,7 +123,9 @@ export class TransferBuilder extends TransactionBuilder {
123123
}
124124

125125
/** @inheritdoc */
126-
protected signImplementation(): BaseTransaction {
127-
throw new MethodNotImplementedError();
126+
protected signImplementation(key: BaseKey): BaseTransaction {
127+
const signatures = this._utils.getSignatures(this._transaction.payloadsData, this._publicKey, key.key);
128+
this._transaction.addSignature(signatures);
129+
return this._transaction;
128130
}
129131
}

modules/sdk-coin-icp/src/lib/utils.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
Signatures,
1919
IcpMetadata,
2020
SendArgs,
21+
PayloadsData,
22+
CurveType,
2123
} from './iface';
2224
import { KeyPair as IcpKeyPair } from './keyPair';
2325
import { decode, encode } from 'cbor-x'; // The "cbor-x" library is used here because it supports modern features like BigInt. do not replace it with "cbor as "cbor" is not compatible with Rust's serde_cbor when handling big numbers.
@@ -676,6 +678,27 @@ export class Utils implements BaseUtils {
676678
isValidTransactionId(txId: string): boolean {
677679
return this.isValidHash(txId);
678680
}
681+
682+
getSignatures(payloadsData: PayloadsData, senderPublicKey: string, senderPrivateKey: string): Signatures[] {
683+
return payloadsData.payloads.map((payload) => ({
684+
signing_payload: payload,
685+
signature_type: payload.signature_type,
686+
public_key: {
687+
hex_bytes: senderPublicKey,
688+
curve_type: CurveType.SECP256K1,
689+
},
690+
hex_bytes: this.signPayload(senderPrivateKey, payload.hex_bytes),
691+
}));
692+
}
693+
694+
signPayload = (privateKey: string, payloadHex: string): string => {
695+
const privateKeyBytes = Buffer.from(privateKey, 'hex');
696+
const payloadHash = crypto.createHash('sha256').update(Buffer.from(payloadHex, 'hex')).digest('hex');
697+
const signature = secp256k1.sign(payloadHash, privateKeyBytes);
698+
const r = Buffer.from(signature.r.toString(16).padStart(64, '0'), 'hex');
699+
const s = Buffer.from(signature.s.toString(16).padStart(64, '0'), 'hex');
700+
return Buffer.concat([r, s]).toString('hex');
701+
};
679702
}
680703

681704
const utils = new Utils();

modules/sdk-coin-icp/test/unit/transactionBuilder/transactionBuilder.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import should from 'should';
22
import { getBuilderFactory } from '../getBuilderFactory';
3+
import { BaseKey } from '@bitgo/sdk-core';
34
import * as testData from '../../resources/icp';
45
import sinon from 'sinon';
56

@@ -85,4 +86,19 @@ describe('ICP Transaction Builder', async () => {
8586
should.equal(broadcastTxnObj.signed_transaction, signedTxn);
8687
should.equal(broadcastTxnObj.network_identifier.network, '00000000000000020101');
8788
});
89+
90+
it('should sign a txn and then give txn in broadcast format', async () => {
91+
const baseKey: BaseKey = { key: testData.accounts.account1.secretKey };
92+
txBuilder.sign(baseKey);
93+
should.deepEqual(txn.signaturePayload, testData.signatures);
94+
txBuilder.combine();
95+
const signedTxn = txBuilder.transaction.signedTransaction;
96+
signedTxn.should.be.a.String();
97+
should.equal(signedTxn, testData.signedTransaction);
98+
const broadcastTxn = txBuilder.transaction.toBroadcastFormat();
99+
broadcastTxn.should.be.a.String();
100+
const broadcastTxnObj = JSON.parse(broadcastTxn);
101+
should.equal(broadcastTxnObj.signed_transaction, signedTxn);
102+
should.equal(broadcastTxnObj.network_identifier.network, '00000000000000020101');
103+
});
88104
});

0 commit comments

Comments
 (0)