Skip to content

Commit e52226b

Browse files
Merge pull request #6829 from BitGo/WIN-6869
feat(sdk-coin-iota): implement key creation and validation
2 parents 7dccb4c + ad13e3c commit e52226b

File tree

13 files changed

+492
-42
lines changed

13 files changed

+492
-42
lines changed

modules/sdk-coin-iota/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@
4242
"dependencies": {
4343
"@bitgo/sdk-core": "^36.5.0",
4444
"@bitgo/statics": "^57.5.0",
45+
"@iota/iota-sdk": "^1.6.0",
4546
"bignumber.js": "^9.1.2"
4647
},
4748
"devDependencies": {
4849
"@bitgo/sdk-api": "^1.67.0",
50+
"@bitgo/sdk-lib-mpc": "^10.7.0",
4951
"@bitgo/sdk-test": "^9.0.5"
5052
}
5153
}

modules/sdk-coin-iota/src/iota.ts

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@ import {
22
AuditDecryptedKeyParams,
33
BaseCoin,
44
BitGoBase,
5-
KeyPair as KeyPairInterface,
5+
KeyPair,
66
ParseTransactionOptions,
77
ParsedTransaction,
88
SignTransactionOptions,
99
SignedTransaction,
10-
VerifyAddressOptions,
1110
VerifyTransactionOptions,
11+
MultisigType,
12+
multisigTypes,
13+
MPCAlgorithm,
14+
InvalidAddressError,
15+
EDDSAMethods,
16+
TssVerifyAddressOptions,
17+
MPCType,
1218
} from '@bitgo/sdk-core';
1319
import { BaseCoin as StaticsBaseCoin, CoinFamily } from '@bitgo/statics';
1420
import utils from './lib/utils';
21+
import { KeyPair as IotaKeyPair } from './lib';
22+
import { auditEddsaPrivateKey } from '@bitgo/sdk-lib-mpc';
1523

1624
export class Iota extends BaseCoin {
1725
protected readonly _staticsCoin: Readonly<StaticsBaseCoin>;
@@ -46,6 +54,20 @@ export class Iota extends BaseCoin {
4654
return this._staticsCoin.fullName;
4755
}
4856

57+
/** @inheritDoc */
58+
supportsTss(): boolean {
59+
return true;
60+
}
61+
62+
/** inherited doc */
63+
getDefaultMultisigType(): MultisigType {
64+
return multisigTypes.tss;
65+
}
66+
67+
getMPCAlgorithm(): MPCAlgorithm {
68+
return MPCType.EDDSA;
69+
}
70+
4971
/**
5072
* Check if an address is valid
5173
* @param address the address to be validated
@@ -69,8 +91,30 @@ export class Iota extends BaseCoin {
6991
* Check if an address belongs to a wallet
7092
* @param params
7193
*/
72-
async isWalletAddress(params: VerifyAddressOptions): Promise<boolean> {
73-
return this.isValidAddress(params.address);
94+
async isWalletAddress(params: TssVerifyAddressOptions): Promise<boolean> {
95+
const { keychains, address, index } = params;
96+
97+
if (!this.isValidAddress(address)) {
98+
throw new InvalidAddressError(`invalid address: ${address}`);
99+
}
100+
101+
if (!keychains) {
102+
throw new Error('missing required param keychains');
103+
}
104+
105+
for (const keychain of keychains) {
106+
const MPC = await EDDSAMethods.getInitializedMpcInstance();
107+
const commonKeychain = keychain.commonKeychain as string;
108+
109+
const derivationPath = 'm/' + index;
110+
const derivedPublicKey = MPC.deriveUnhardened(commonKeychain, derivationPath).slice(0, 64);
111+
const expectedAddress = utils.getAddressFromPublicKey(derivedPublicKey);
112+
113+
if (address !== expectedAddress) {
114+
return false;
115+
}
116+
}
117+
return true;
74118
}
75119

76120
/**
@@ -86,12 +130,15 @@ export class Iota extends BaseCoin {
86130
* Generate a key pair
87131
* @param seed Optional seed to generate key pair from
88132
*/
89-
generateKeyPair(seed?: Uint8Array): KeyPairInterface {
90-
// For now we're just returning an empty implementation as the KeyPair class needs to be implemented
91-
// In a real implementation, we would use the KeyPair class properly
133+
generateKeyPair(seed?: Buffer): KeyPair {
134+
const keyPair = seed ? new IotaKeyPair({ seed }) : new IotaKeyPair();
135+
const keys = keyPair.getKeys();
136+
if (!keys.prv) {
137+
throw new Error('Missing prv in key generation.');
138+
}
92139
return {
93-
pub: '',
94-
prv: '',
140+
pub: keys.pub,
141+
prv: keys.prv,
95142
};
96143
}
97144

@@ -100,28 +147,25 @@ export class Iota extends BaseCoin {
100147
* @param pub Public key to check
101148
*/
102149
isValidPub(pub: string): boolean {
103-
// TODO: Implement proper IOTA public key validation
104-
return pub.length > 0;
150+
return utils.isValidPublicKey(pub);
105151
}
106152

107153
/**
108154
* Sign a transaction
109155
* @param params
110156
*/
111157
async signTransaction(params: SignTransactionOptions): Promise<SignedTransaction> {
112-
// TODO: Add IOTA-specific transaction signing logic
113-
return {
114-
halfSigned: {
115-
txHex: '',
116-
},
117-
};
158+
throw new Error('Method not implemented.');
118159
}
119160

120161
/**
121162
* Audit a decrypted private key to ensure it's valid
122163
* @param params
123164
*/
124-
auditDecryptedKey(params: AuditDecryptedKeyParams): void {
125-
// TODO: Implement IOTA-specific key validation logic
165+
auditDecryptedKey({ multiSigType, prv, publicKey }: AuditDecryptedKeyParams): void {
166+
if (multiSigType !== multisigTypes.tss) {
167+
throw new Error('Unsupported multiSigType');
168+
}
169+
auditEddsaPrivateKey(prv, publicKey ?? '');
126170
}
127171
}

modules/sdk-coin-iota/src/lib/constants.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,3 @@ export const IOTA_SIGNATURE_LENGTH = 128;
55
export const ADDRESS_BYTES_LENGTH = 32;
66
export const AMOUNT_BYTES_LENGTH = 8;
77
export const SECONDS_PER_WEEK = 7 * 24 * 60 * 60; // 1 week in seconds
8-
9-
// Define IOTA specific function paths
10-
export const IOTA_TRANSFER_FUNCTION = '0x0::coin::transfer';
11-
export const IOTA_BATCH_TRANSFER_FUNCTION = '0x1::coin_batch::transfer';
Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,41 @@
1-
import { DefaultKeys, Ed25519KeyPair } from '@bitgo/sdk-core';
1+
import { DefaultKeys, Ed25519KeyPair, KeyPairOptions } from '@bitgo/sdk-core';
2+
import utils from './utils';
23

34
export class KeyPair extends Ed25519KeyPair {
5+
/**
6+
* Public constructor. By default, creates a key pair with a random master seed.
7+
*
8+
* @param { KeyPairOptions } source Either a master seed, a private key, or a public key
9+
*/
10+
constructor(source?: KeyPairOptions) {
11+
super(source);
12+
}
13+
414
/** @inheritdoc */
515
getKeys(): DefaultKeys {
6-
throw new Error('Method not implemented.');
16+
const result: DefaultKeys = { pub: this.keyPair.pub };
17+
if (this.keyPair.prv) {
18+
result.prv = this.keyPair.prv;
19+
}
20+
return result;
721
}
822

923
/** @inheritdoc */
1024
recordKeysFromPrivateKeyInProtocolFormat(prv: string): DefaultKeys {
25+
// We don't use private keys for IOTA since it's implemented for TSS.
1126
throw new Error('Method not implemented.');
1227
}
1328

1429
/** @inheritdoc */
1530
recordKeysFromPublicKeyInProtocolFormat(pub: string): DefaultKeys {
16-
throw new Error('Method not implemented.');
31+
if (!utils.isValidPublicKey(pub)) {
32+
throw new Error(`Invalid Public Key ${pub}`);
33+
}
34+
return { pub };
1735
}
1836

1937
/** @inheritdoc */
2038
getAddress(): string {
21-
throw new Error('Method not implemented.');
39+
return utils.getAddressFromPublicKey(this.keyPair.pub);
2240
}
2341
}

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

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,51 @@
1-
import { BaseUtils } from '@bitgo/sdk-core';
1+
import { BaseUtils, isValidEd25519PublicKey, isValidEd25519SecretKey } from '@bitgo/sdk-core';
2+
import {
3+
IOTA_ADDRESS_LENGTH,
4+
IOTA_BLOCK_ID_LENGTH,
5+
IOTA_SIGNATURE_LENGTH,
6+
IOTA_TRANSACTION_ID_LENGTH,
7+
} from './constants';
8+
import { Ed25519PublicKey } from '@iota/iota-sdk/keypairs/ed25519';
29

310
export class Utils implements BaseUtils {
411
/** @inheritdoc */
512
isValidAddress(address: string): boolean {
6-
throw new Error('Method not implemented.');
13+
return this.isValidHex(address, IOTA_ADDRESS_LENGTH);
714
}
815

916
/** @inheritdoc */
1017
isValidBlockId(hash: string): boolean {
11-
throw new Error('Method not implemented.');
18+
return this.isValidHex(hash, IOTA_BLOCK_ID_LENGTH);
1219
}
1320

1421
/** @inheritdoc */
1522
isValidPrivateKey(key: string): boolean {
16-
throw new Error('Method not implemented.');
23+
return isValidEd25519SecretKey(key);
1724
}
1825

1926
/** @inheritdoc */
2027
isValidPublicKey(key: string): boolean {
21-
throw new Error('Method not implemented.');
28+
return isValidEd25519PublicKey(key);
2229
}
2330

2431
/** @inheritdoc */
2532
isValidSignature(signature: string): boolean {
26-
throw new Error('Method not implemented.');
33+
return this.isValidHex(signature, IOTA_SIGNATURE_LENGTH);
2734
}
2835

2936
/** @inheritdoc */
3037
isValidTransactionId(txId: string): boolean {
31-
throw new Error('Method not implemented.');
38+
return this.isValidHex(txId, IOTA_TRANSACTION_ID_LENGTH);
39+
}
40+
41+
isValidHex(value: string, length: number): boolean {
42+
const regex = new RegExp(`^(0x|0X)[a-fA-F0-9]{${length}}$`);
43+
return regex.test(value);
44+
}
45+
46+
getAddressFromPublicKey(publicKey: string): string {
47+
const iotaPublicKey = new Ed25519PublicKey(Buffer.from(publicKey, 'hex'));
48+
return iotaPublicKey.toIotaAddress();
3249
}
3350
}
3451

modules/sdk-coin-iota/test/integration/index.ts

Whitespace-only changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const addresses = {
2+
validAddresses: [
3+
'0xda97e166d40fa6a0c949b6aeb862e391c29139b563ae0430b2419c589a02a6e0',
4+
'0x0502b39ec0b82c10c64a07e969ee1140e67d8e0c0fc0c0f6319fe7e47dbb0ab5',
5+
],
6+
invalidAddresses: [
7+
'randomString',
8+
'0xc4173a804406a365e69dfb297ddfgsdcvf',
9+
'5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen',
10+
],
11+
};

0 commit comments

Comments
 (0)