Skip to content

Commit 3231af0

Browse files
committed
feat(sdk-coin-vet): add vechain keyPair
also adds transaction builder skeleton Ticket: COIN-4374
1 parent 097e425 commit 3231af0

File tree

11 files changed

+421
-5
lines changed

11 files changed

+421
-5
lines changed

modules/sdk-coin-vet/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@
4343
"@bitgo/sdk-core": "^35.1.0",
4444
"@bitgo/statics": "^54.1.0",
4545
"@bitgo/abstract-eth": "^24.4.0",
46-
"@bitgo/secp256k1": "^1.3.3"
46+
"@bitgo/secp256k1": "^1.3.3",
47+
"ethereumjs-util": "7.1.5",
48+
"bignumber.js": "^9.1.1",
49+
"tweetnacl": "^1.0.3"
4750
},
4851
"devDependencies": {
4952
"@bitgo/sdk-api": "^1.63.3",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export const DEFAULT_GAS_UNIT_PRICE = 100;
2+
export const VET_TRANSACTION_ID_LENGTH = 64;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* The transaction data returned from the toJson() function of a transaction
3+
*/
4+
export interface TxData {
5+
id: string;
6+
}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
import * as Constants from './constants';
22
import * as Utils from './utils';
3+
import * as Interface from './iface';
34

4-
export { Constants, Utils };
5+
export { KeyPair } from './keyPair';
6+
export { Transaction } from './transaction';
7+
export { TransactionBuilder } from './transactionBuilder';
8+
export { TransferBuilder } from './transferBuilder';
9+
export { TransactionBuilderFactory } from './transactionBuilderFactory';
10+
export { Constants, Utils, Interface };
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { addHexPrefix, pubToAddress } from 'ethereumjs-util';
2+
import {
3+
DefaultKeys,
4+
isPrivateKey,
5+
isPublicKey,
6+
isSeed,
7+
KeyPairOptions,
8+
Secp256k1ExtendedKeyPair,
9+
} from '@bitgo/sdk-core';
10+
import { randomBytes } from 'crypto';
11+
import { bip32 } from '@bitgo/secp256k1';
12+
13+
const DEFAULT_SEED_SIZE_BYTES = 64;
14+
15+
/**
16+
* Ethereum keys and address management.
17+
*/
18+
export class KeyPair extends Secp256k1ExtendedKeyPair {
19+
/**
20+
* Public constructor. By default, creates a key pair with a random master seed.
21+
*
22+
* @param { KeyPairOptions } source Either a master seed, a private key (extended or raw), or a public key
23+
* (extended, compressed, or uncompressed)
24+
*/
25+
constructor(source?: KeyPairOptions) {
26+
super(source);
27+
if (!source) {
28+
const seed = randomBytes(DEFAULT_SEED_SIZE_BYTES);
29+
this.hdNode = bip32.fromSeed(seed);
30+
} else if (isSeed(source)) {
31+
this.hdNode = bip32.fromSeed(source.seed);
32+
} else if (isPrivateKey(source)) {
33+
this.recordKeysFromPrivateKey(source.prv);
34+
} else if (isPublicKey(source)) {
35+
this.recordKeysFromPublicKey(source.pub);
36+
} else {
37+
throw new Error('Invalid key pair options');
38+
}
39+
40+
if (this.hdNode) {
41+
this.keyPair = Secp256k1ExtendedKeyPair.toKeyPair(this.hdNode);
42+
}
43+
}
44+
45+
/**
46+
* Ethereum default keys format is raw private and uncompressed public key
47+
*
48+
* @returns { DefaultKeys } The keys in the protocol default key format
49+
*/
50+
getKeys(): DefaultKeys {
51+
return {
52+
pub: this.getPublicKey({ compressed: this.hdNode !== undefined })
53+
.toString('hex')
54+
.toUpperCase(),
55+
prv: this.getPrivateKey()?.toString('hex').toUpperCase(),
56+
};
57+
}
58+
59+
/**
60+
* Get an Ethereum public address
61+
*
62+
* @returns {string} The address derived from the public key
63+
*/
64+
getAddress(): string {
65+
const publicKey = Buffer.from(this.getKeys().pub, 'hex'); // first two characters identify a public key
66+
return addHexPrefix(pubToAddress(publicKey, true).toString('hex'));
67+
}
68+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { BaseKey, BaseTransaction } from '@bitgo/sdk-core';
2+
import { TxData } from './iface';
3+
4+
export class Transaction extends BaseTransaction {
5+
canSign(key: BaseKey): boolean {
6+
return false;
7+
}
8+
9+
toBroadcastFormat(): string {
10+
throw new Error('Method not implemented.');
11+
}
12+
13+
toJson(): TxData {
14+
throw new Error('Method not implemented.');
15+
}
16+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import {
2+
BaseAddress,
3+
BaseKey,
4+
BaseTransactionBuilder,
5+
BuildTransactionError,
6+
FeeOptions,
7+
PublicKey as BasePublicKey,
8+
Signature,
9+
TransactionType,
10+
} from '@bitgo/sdk-core';
11+
import { Transaction } from './transaction';
12+
import utils from './utils';
13+
import BigNumber from 'bignumber.js';
14+
15+
export abstract class TransactionBuilder extends BaseTransactionBuilder {
16+
protected _transaction: Transaction;
17+
private _signatures: Signature[] = [];
18+
19+
// get and set region
20+
/**
21+
* The transaction type.
22+
*/
23+
protected abstract get transactionType(): TransactionType;
24+
25+
/** @inheritdoc */
26+
protected get transaction(): Transaction {
27+
return this._transaction;
28+
}
29+
30+
/** @inheritdoc */
31+
protected set transaction(transaction: Transaction) {
32+
this._transaction = transaction;
33+
}
34+
35+
/** @inheritdoc */
36+
protected signImplementation(key: BaseKey): Transaction {
37+
throw new Error('Method not implemented.');
38+
}
39+
40+
/** @inheritDoc */
41+
addSignature(publicKey: BasePublicKey, signature: Buffer): void {
42+
this._signatures.push({ publicKey, signature });
43+
}
44+
45+
/**
46+
* Sets the sender of this transaction.
47+
* This account will be responsible for paying transaction fees.
48+
*
49+
* @param {string} senderAddress the account that is sending this transaction
50+
* @returns {TransactionBuilder} This transaction builder
51+
*/
52+
sender(senderAddress: string): this {
53+
throw new Error('Method not implemented.');
54+
}
55+
56+
fee(feeOptions: FeeOptions): this {
57+
throw new Error('Method not implemented.');
58+
}
59+
60+
/** @inheritdoc */
61+
protected fromImplementation(rawTransaction: string): Transaction {
62+
throw new Error('Method not implemented.');
63+
}
64+
65+
/** @inheritdoc */
66+
protected async buildImplementation(): Promise<Transaction> {
67+
throw new Error('Method not implemented.');
68+
}
69+
70+
// region Validators
71+
/** @inheritdoc */
72+
validateAddress(address: BaseAddress, addressFormat?: string): void {
73+
if (!utils.isValidAddress(address.address)) {
74+
throw new BuildTransactionError('Invalid address ' + address.address);
75+
}
76+
}
77+
78+
/** @inheritdoc */
79+
validateKey(key: BaseKey): void {
80+
throw new Error('Method not implemented.');
81+
}
82+
83+
/** @inheritdoc */
84+
validateRawTransaction(rawTransaction: string): void {
85+
throw new Error('Method not implemented.');
86+
}
87+
88+
/** @inheritdoc */
89+
validateTransaction(transaction?: Transaction): void {
90+
throw new Error('Method not implemented.');
91+
}
92+
93+
/** @inheritdoc */
94+
validateValue(value: BigNumber): void {
95+
if (value.isLessThan(0)) {
96+
throw new BuildTransactionError('Value cannot be less than zero');
97+
}
98+
}
99+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { BaseTransactionBuilderFactory } from '@bitgo/sdk-core';
2+
import { TransactionBuilder } from './transactionBuilder';
3+
import { TransferBuilder } from './transferBuilder';
4+
5+
export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
6+
/** @inheritdoc */
7+
from(raw: string): TransactionBuilder {
8+
throw new Error('Method not implemented.');
9+
}
10+
11+
/** @inheritdoc */
12+
getTransferBuilder(): TransferBuilder {
13+
throw new Error('Method not implemented.');
14+
}
15+
16+
/** @inheritdoc */
17+
getWalletInitializationBuilder(): void {
18+
throw new Error('Method not implemented.');
19+
}
20+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { TransactionBuilder } from './transactionBuilder';
2+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
3+
import { TransactionType } from '@bitgo/sdk-core';
4+
5+
export class TransferBuilder extends TransactionBuilder {
6+
constructor(_coinConfig: Readonly<CoinConfig>) {
7+
super(_coinConfig);
8+
}
9+
10+
protected get transactionType(): TransactionType {
11+
return TransactionType.Send;
12+
}
13+
}

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { BaseUtils } from '@bitgo/sdk-core';
2+
import { VET_TRANSACTION_ID_LENGTH } from './constants';
3+
import { KeyPair } from './keyPair';
24

35
export class Utils implements BaseUtils {
46
isValidAddress(address: string): boolean {
@@ -10,19 +12,34 @@ export class Utils implements BaseUtils {
1012
}
1113

1214
isValidPrivateKey(key: string): boolean {
13-
throw new Error('Method not implemented');
15+
try {
16+
new KeyPair({ prv: key });
17+
return true;
18+
} catch (e) {
19+
return false;
20+
}
1421
}
1522

1623
isValidPublicKey(key: string): boolean {
17-
throw new Error('Method not implemented');
24+
try {
25+
new KeyPair({ pub: key });
26+
return true;
27+
} catch (e) {
28+
return false;
29+
}
1830
}
1931

2032
isValidSignature(signature: string): boolean {
2133
throw new Error('Method not implemented');
2234
}
2335

2436
isValidTransactionId(txId: string): boolean {
25-
throw new Error('Method not implemented');
37+
return this.isValidHex(txId, VET_TRANSACTION_ID_LENGTH);
38+
}
39+
40+
isValidHex(value: string, length: number): boolean {
41+
const regex = new RegExp(`^(0x|0X)[a-fA-F0-9]{${length}}$`);
42+
return regex.test(value);
2643
}
2744
}
2845

0 commit comments

Comments
 (0)