Skip to content

Commit 27f83fd

Browse files
authored
Merge pull request #2 from haqq-network/issue/HW-731/refactor-ui-trx-addresses-should-be-displayed-correctly
feat(HW-731): TRON private key and address generation
2 parents baaf0d2 + ef29e29 commit 27f83fd

File tree

9 files changed

+8217
-5554
lines changed

9 files changed

+8217
-5554
lines changed

.yarnrc.yml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodeLinker: node-modules

package.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"dist/**/*"
99
],
1010
"license": "MIT",
11-
"private": false,
1211
"repository": {
1312
"type": "git",
1413
"url": "https://github.com/haqq-network/haqq-rn-wallet-providers.git"
@@ -33,6 +32,7 @@
3332
"@ledgerhq/hw-transport": "6.30.3",
3433
"@ledgerhq/react-native-hw-transport-ble": "6.30.0",
3534
"bech32-converting": "^1.0.9",
35+
"bip32": "1.0.4",
3636
"bip39": "^3.1.0",
3737
"ethers": "^5.7.2",
3838
"events": "^3.3.0",
@@ -48,11 +48,11 @@
4848
"react-native-encrypted-storage": "*"
4949
},
5050
"devDependencies": {
51-
"@react-native-community/eslint-config": "3.2.0",
5251
"@haqq/encryption-react-native": "^0.0.3",
5352
"@haqq/provider-web3-utils": "^0.0.13",
5453
"@haqq/shared-react-native": "^0.0.8",
5554
"@ledgerhq/types-devices": "^6.23.0",
55+
"@react-native-community/eslint-config": "3.2.0",
5656
"@types/jest": "^29.5.1",
5757
"@types/node": "^20.1.2",
5858
"@types/react-native": "^0.71.12",
@@ -90,15 +90,15 @@
9090
"vm": "vm-browserify"
9191
},
9292
"browser": {
93+
"_stream_duplex": "readable-stream/duplex",
94+
"_stream_passthrough": "readable-stream/passthrough",
95+
"_stream_readable": "readable-stream/readable",
96+
"_stream_transform": "readable-stream/transform",
97+
"_stream_writable": "readable-stream/writable",
9398
"crypto": "react-native-crypto",
9499
"http": "@tradle/react-native-http",
95100
"https": "https-browserify",
96101
"os": "react-native-os",
97-
"_stream_transform": "readable-stream/transform",
98-
"_stream_readable": "readable-stream/readable",
99-
"_stream_writable": "readable-stream/writable",
100-
"_stream_duplex": "readable-stream/duplex",
101-
"_stream_passthrough": "readable-stream/passthrough",
102102
"stream": "stream-browserify",
103103
"vm": "vm-browserify"
104104
}

src/providers/mnemonic/provider.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ import EncryptedStorage from 'react-native-encrypted-storage';
77
import {ProviderMnemonicBaseOptions} from './types';
88

99
import {ITEM_KEYS, WalletType} from '../../constants';
10+
import {Multichain} from '../../services/multichain';
1011
import {compressPublicKey} from '../../utils';
1112
import {getMnemonic} from '../../utils/mnemonic/get-mnemonic';
1213
import {ProviderBase} from '../base-provider';
13-
import {ProviderBaseOptions, ProviderInterface} from '../types';
14+
import {NETWORK_TYPE, ProviderBaseOptions, ProviderInterface} from '../types';
1415

1516
export class ProviderMnemonicBase
1617
extends ProviderBase<ProviderMnemonicBaseOptions>
@@ -27,6 +28,7 @@ export class ProviderMnemonicBase
2728
mnemonic === null
2829
? (await generateEntropy(16)).toString('hex')
2930
: mnemonicToEntropy(mnemonic);
31+
3032
const seed = await mnemonicToSeed(entropyToMnemonic(entropy));
3133

3234
const privateData = await encryptShare(
@@ -117,7 +119,7 @@ export class ProviderMnemonicBase
117119
}
118120

119121
async getAccountInfo(hdPath: string) {
120-
let resp = {publicKey: '', address: ''};
122+
let resp = {publicKey: '', address: '', tronAddress: ''};
121123
try {
122124
const share = await getMnemonic(
123125
this._options.account,
@@ -130,17 +132,22 @@ export class ProviderMnemonicBase
130132

131133
const seed = await ProviderMnemonicBase.shareToSeed(share);
132134

133-
const privateKey = await derive(seed, hdPath);
135+
const ethPrivateKey = await derive(seed, hdPath);
134136

135-
if (!privateKey) {
137+
if (!ethPrivateKey) {
136138
throw new Error('private_key_not_found');
137139
}
138140

139-
const account = await accountInfo(privateKey);
141+
const account = await accountInfo(ethPrivateKey);
140142

141143
resp = {
142144
publicKey: compressPublicKey(account.publicKey),
143145
address: account.address,
146+
tronAddress: await Multichain.generateAddress(
147+
NETWORK_TYPE.TRON,
148+
hdPath,
149+
await this.getMnemonicPhrase(),
150+
),
144151
};
145152
this.emit('getPublicKeyForHDPath', true);
146153
} catch (e) {

src/providers/sss/provider.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ import EncryptedStorage from 'react-native-encrypted-storage';
1414
import {ProviderSSSBaseOptions, StorageInterface} from './types';
1515

1616
import {ITEM_KEYS, WalletType} from '../../constants';
17+
import {Multichain} from '../../services/multichain';
1718
import {
1819
Polynomial,
1920
compressPublicKey,
2021
getSeed,
2122
lagrangeInterpolation,
2223
} from '../../utils';
2324
import {ProviderBase} from '../base-provider';
24-
import {ProviderBaseOptions, ProviderInterface} from '../types';
25+
import {NETWORK_TYPE, ProviderBaseOptions, ProviderInterface} from '../types';
2526

2627
export class ProviderSSSBase
2728
extends ProviderBase<ProviderSSSBaseOptions>
@@ -244,7 +245,7 @@ export class ProviderSSSBase
244245
}
245246

246247
async getAccountInfo(hdPath: string) {
247-
let resp = {publicKey: '', address: ''};
248+
let resp = {publicKey: '', address: '', tronAddress: ''};
248249
try {
249250
const {seed} = await getSeed(
250251
this._options.account,
@@ -267,6 +268,11 @@ export class ProviderSSSBase
267268
resp = {
268269
publicKey: compressPublicKey(account.publicKey),
269270
address: account.address,
271+
tronAddress: await Multichain.generateAddress(
272+
NETWORK_TYPE.TRON,
273+
hdPath,
274+
await this.getMnemonicPhrase(),
275+
),
270276
};
271277
this.emit('getPublicKeyForHDPath', true);
272278
} catch (e) {

src/providers/types.ts

+6
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,9 @@ export type ProviderBaseError = {
111111
error: Error;
112112
source: string;
113113
};
114+
115+
export enum NETWORK_TYPE {
116+
HAQQ,
117+
ETH,
118+
TRON,
119+
}

src/services/multichain.ts

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import * as bip32 from 'bip32';
2+
import {mnemonicToSeed} from 'bip39';
3+
import * as CryptoJS from 'crypto-js';
4+
import {ec} from 'elliptic';
5+
6+
import {NETWORK_TYPE} from '../providers/types';
7+
import {encodeBase58} from '../utils';
8+
9+
export class Multichain {
10+
static async generatePrivateKey(
11+
type: NETWORK_TYPE,
12+
hdPath: string,
13+
mnemonic: string,
14+
): Promise<string> {
15+
switch (type) {
16+
case NETWORK_TYPE.TRON:
17+
return Multichain.generateTronPrivateKey(hdPath, mnemonic);
18+
default:
19+
return '';
20+
}
21+
}
22+
23+
private static async generateTronPrivateKey(
24+
hdPath: string,
25+
mnemonic: string,
26+
): Promise<string> {
27+
const tronSeed = await mnemonicToSeed(mnemonic);
28+
const root = bip32.fromSeed(tronSeed);
29+
const child = root.derivePath(hdPath);
30+
return child.privateKey.toString('hex');
31+
}
32+
33+
static async generateAddress(
34+
type: NETWORK_TYPE,
35+
hdPath: string,
36+
mnemonic: string,
37+
): Promise<string> {
38+
switch (type) {
39+
case NETWORK_TYPE.TRON:
40+
return Multichain.generateTronAddress(
41+
await this.generateTronPrivateKey(hdPath, mnemonic),
42+
);
43+
default:
44+
return '';
45+
}
46+
}
47+
48+
private static async generateTronAddress(
49+
privateKey: string,
50+
): Promise<string> {
51+
const EC = new ec('secp256k1');
52+
53+
// Create key pair from private key
54+
const keyPair = EC.keyFromPrivate(privateKey);
55+
56+
// Get public key and remove prefix 0x
57+
const publicKey = keyPair.getPublic('hex').slice(2);
58+
59+
// Hashing public key with Keccak256
60+
const publicKeyHash = CryptoJS.SHA3(publicKey, {
61+
outputLength: 256,
62+
}).toString();
63+
64+
// Get last 20 bites (40 hex symbols) from public key's hash
65+
// Also add TRON prefix 41
66+
const tronAddressHex = '41' + publicKeyHash.slice(-40);
67+
68+
// Convert hex-address to base58 which is TRON address format
69+
const addressBuffer = Buffer.from(tronAddressHex, 'hex');
70+
const addressHash = CryptoJS.SHA256(
71+
CryptoJS.SHA256(addressBuffer).toString(CryptoJS.enc.Hex),
72+
).toString(CryptoJS.enc.Hex);
73+
const checksum = addressHash.slice(0, 8);
74+
75+
return encodeBase58(
76+
Buffer.concat([addressBuffer, Buffer.from(checksum, 'hex')]),
77+
);
78+
}
79+
}

src/utils/utils.ts

+25
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,28 @@ export function hexBuffer(str: string | Buffer): Buffer {
3030
export function normalize0x(strWith0x: string): string {
3131
return strWith0x.replace(/^(0x)*/, '0x');
3232
}
33+
34+
export function encodeBase58(buffer: Buffer): string {
35+
const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
36+
const base = BigInt(58);
37+
38+
let result = '';
39+
let num = BigInt('0x' + buffer.toString('hex'));
40+
41+
while (num > 0) {
42+
const remainder = num % base;
43+
num = num / base;
44+
result = alphabet[Number(remainder)] + result;
45+
}
46+
47+
// Добавляем ведущие нули
48+
for (const byte of buffer) {
49+
if (byte === 0x00) {
50+
result = alphabet[0] + result;
51+
} else {
52+
break;
53+
}
54+
}
55+
56+
return result;
57+
}

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"compilerOptions": {
3-
"target": "es5",
3+
"target": "es2015",
44
"module": "commonjs",
55
"esModuleInterop": true,
66
"skipLibCheck": true,

0 commit comments

Comments
 (0)