Skip to content

Commit 9a1aabb

Browse files
feat(sdk-coin-ada): implement key pair and utils for ada sdk
Ticket: origin/BG-50795
1 parent 64538b7 commit 9a1aabb

File tree

12 files changed

+353
-23
lines changed

12 files changed

+353
-23
lines changed

modules/bitgo/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
"@bitgo/account-lib": "^2.19.0",
5151
"@bitgo/blockapis": "^1.0.1",
5252
"@bitgo/sdk-api": "^1.0.1",
53-
"@bitgo/sdk-coin-ada": "^1.0.0",
5453
"@bitgo/sdk-coin-algo": "^1.0.0",
5554
"@bitgo/sdk-coin-avaxc": "^1.0.0",
5655
"@bitgo/sdk-coin-avaxp": "^1.0.0",

modules/bitgo/src/v2/coinFactory.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { CoinFactory } from '@bitgo/sdk-core';
55
import { AlgoToken } from '@bitgo/sdk-coin-algo';
66
import { Bcha, Tbcha } from '@bitgo/sdk-coin-bcha';
77
import {
8-
Ada,
98
Algo,
109
AvaxC,
1110
AvaxCToken,
@@ -39,7 +38,6 @@ import {
3938
StellarToken,
4039
Stx,
4140
Susd,
42-
Tada,
4341
Talgo,
4442
TavaxC,
4543
TavaxP,
@@ -81,7 +79,6 @@ import { SolToken } from '@bitgo/sdk-coin-sol';
8179
import { HbarToken } from './coins/hbarToken';
8280

8381
function registerCoinConstructors(globalCoinFactory: CoinFactory): void {
84-
globalCoinFactory.register('ada', Ada.createInstance);
8582
globalCoinFactory.register('algo', Algo.createInstance);
8683
globalCoinFactory.register('avaxc', AvaxC.createInstance);
8784
globalCoinFactory.register('avaxp', AvaxP.createInstance);
@@ -111,7 +108,6 @@ function registerCoinConstructors(globalCoinFactory: CoinFactory): void {
111108
globalCoinFactory.register('sol', Sol.createInstance);
112109
globalCoinFactory.register('stx', Stx.createInstance);
113110
globalCoinFactory.register('susd', Susd.createInstance);
114-
globalCoinFactory.register('tada', Tada.createInstance);
115111
globalCoinFactory.register('talgo', Talgo.createInstance);
116112
globalCoinFactory.register('tavaxc', TavaxC.createInstance);
117113
globalCoinFactory.register('tavaxp', TavaxP.createInstance);

modules/bitgo/src/v2/coins/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import { AbstractUtxoCoin } from '@bitgo/abstract-utxo';
22
export { AbstractUtxoCoin };
33
import { Algo, AlgoToken, Talgo } from '@bitgo/sdk-coin-algo';
44
export { Algo, AlgoToken, Talgo };
5-
import { Ada, Tada } from '@bitgo/sdk-coin-ada';
6-
export { Ada, Tada };
75
import { AvaxC, TavaxC, AvaxCToken } from '@bitgo/sdk-coin-avaxc';
86
export { AvaxC, TavaxC, AvaxCToken };
97
import { AvaxP, TavaxP } from '@bitgo/sdk-coin-avaxp';

modules/sdk-coin-ada/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
"dependencies": {
4444
"@bitgo/sdk-core": "^1.0.1",
4545
"@bitgo/statics": "^6.17.0",
46+
"@emurgo/cardano-serialization-lib-browser": "^10.2.0",
47+
"@emurgo/cardano-serialization-lib-nodejs": "^10.2.0",
4648
"bignumber.js": "^9.0.2"
4749
},
4850
"devDependencies": {
Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { DefaultKeys, KeyPairOptions, Ed25519KeyPair, AddressFormat } from '@bitgo/sdk-core';
1+
import { DefaultKeys, KeyPairOptions, Ed25519KeyPair, toHex, AddressFormat } from '@bitgo/sdk-core';
2+
import {
3+
PublicKey,
4+
PrivateKey,
5+
EnterpriseAddress,
6+
NetworkInfo,
7+
StakeCredential,
8+
} from '@emurgo/cardano-serialization-lib-nodejs';
29

310
export class KeyPair extends Ed25519KeyPair {
411
/**
@@ -11,18 +18,42 @@ export class KeyPair extends Ed25519KeyPair {
1118
super(source);
1219
}
1320

14-
getAddress(format?: AddressFormat): string {
15-
throw new Error('Method not implemented.');
21+
/**
22+
* @returns { Address }
23+
*/
24+
getAddress(format): string {
25+
const bytesFromHex = new Uint8Array(Buffer.from(this.keyPair.pub, 'hex'));
26+
const pubKey = PublicKey.from_bytes(bytesFromHex);
27+
let enterpriseAddress;
28+
if (format === AddressFormat.testnet) {
29+
enterpriseAddress = EnterpriseAddress.new(
30+
NetworkInfo.testnet().network_id(),
31+
StakeCredential.from_keyhash(pubKey.hash())
32+
);
33+
} else if (format === AddressFormat.mainnet) {
34+
enterpriseAddress = EnterpriseAddress.new(
35+
NetworkInfo.mainnet().network_id(),
36+
StakeCredential.from_keyhash(pubKey.hash())
37+
);
38+
}
39+
return enterpriseAddress.to_address().to_bech32();
1640
}
1741

1842
getKeys(): DefaultKeys {
19-
throw new Error('Method not implemented.');
43+
const result: DefaultKeys = { pub: this.keyPair.pub };
44+
if (this.keyPair.prv) {
45+
result.prv = this.keyPair.prv;
46+
}
47+
return result;
2048
}
2149

2250
recordKeysFromPrivateKeyInProtocolFormat(prv: string): DefaultKeys {
23-
throw new Error('Method not implemented.');
51+
const rawPrv = PrivateKey.from_bech32(prv).as_bytes();
52+
return new KeyPair({ prv: toHex(rawPrv) }).keyPair;
2453
}
25-
recordKeysFromPublicKeyInProtocolFormat(prv: string): DefaultKeys {
26-
throw new Error('Method not implemented.');
54+
55+
recordKeysFromPublicKeyInProtocolFormat(pub: string): DefaultKeys {
56+
const rawPub = PublicKey.from_bech32(pub).as_bytes();
57+
return { pub: toHex(rawPub) };
2758
}
2859
}

modules/sdk-coin-ada/src/utils.ts

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,68 @@
1-
import { BaseUtils, NotImplementedError } from '@bitgo/sdk-core';
1+
import { BaseUtils } from '@bitgo/sdk-core';
2+
import { Ed25519Signature, Address } from '@emurgo/cardano-serialization-lib-nodejs';
3+
import { KeyPair } from './keyPair';
24

35
export class Utils implements BaseUtils {
4-
/** @inheritdoc */
6+
validateBlake2b(hash: string): boolean {
7+
if (hash.length !== 64) {
8+
return false;
9+
}
10+
return hash.match(/^[a-zA-Z0-9]+$/) !== null;
11+
}
512

13+
/** @inheritdoc */
614
isValidAddress(address: string): boolean {
7-
throw new NotImplementedError('method not implemented');
15+
try {
16+
Address.from_bech32(address);
17+
return true;
18+
} catch (err) {
19+
return false;
20+
}
821
}
922

1023
/** @inheritdoc */
1124
isValidBlockId(hash: string): boolean {
12-
throw new NotImplementedError('method not implemented');
25+
return this.validateBlake2b(hash);
1326
}
1427

1528
/** @inheritdoc */
1629
isValidPrivateKey(key: string): boolean {
17-
throw new NotImplementedError('method not implemented');
30+
// this will return true for both extended and non-extended ED25519 keys
31+
return this.isValidKey(key);
32+
}
33+
34+
isValidKey(key: string): boolean {
35+
try {
36+
new KeyPair({ prv: key });
37+
return true;
38+
} catch {
39+
return false;
40+
}
1841
}
1942

2043
/** @inheritdoc */
21-
isValidPublicKey(key: string): boolean {
22-
throw new NotImplementedError('method not implemented');
44+
isValidPublicKey(pubKey: string): boolean {
45+
try {
46+
new KeyPair({ pub: pubKey });
47+
return true;
48+
} catch {
49+
return false;
50+
}
2351
}
2452

2553
/** @inheritdoc */
2654
isValidSignature(signature: string): boolean {
27-
throw new NotImplementedError('method not implemented');
55+
try {
56+
Ed25519Signature.from_hex(signature);
57+
return true;
58+
} catch (err) {
59+
return false;
60+
}
2861
}
2962

3063
/** @inheritdoc */
3164
isValidTransactionId(txId: string): boolean {
32-
throw new NotImplementedError('method not implemented');
65+
return this.validateBlake2b(txId);
3366
}
3467
}
3568

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

Whitespace-only changes.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
export const address = {
2+
address1: 'stake1vpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5egfu2p0u',
3+
address2:
4+
'addr_test1qzpu9dmeztx7sfrl4qqtw3rvcewt822wn8mnatv8uc5yzdmvvz7nw9gmznn65g4ksrrfvyzhz52knc3mqxdyya47gz2q83wks3',
5+
address3:
6+
'37btjrVyb4KDXBNC4haBVPCrro8AQPHwvCMp3RFhhSVWwfFmZ6wwzSK6JK1hY6wHNmtrpTf1kdbva8TCneM2YsiXT7mrzT21EacHnPpz5YyUdj64na',
7+
address4: 'addr_test1vr8rakm66rcfv4fcxqykg5lf0yv7lsyk9mvapx369jpvtcgfcuk7f',
8+
};
9+
10+
export const blockHash = {
11+
hash1: '24bf2cf83bc3b1819a4395ef4d9d3b60d687bfa53a9bf4c6c55acbf11e85ea03',
12+
hash2: '59548738f2096318ab745e8252b62ac24fd9ab38379777513977a2315af21580',
13+
};
14+
15+
export const signatures = {
16+
signature1:
17+
'ed25519_sig14swpdlus75gyq6nsgagyq2dwwawzm463mddq2epmw98s4evq6cqc87sgzlfe2ks979kfuktwvqyveh38gl5f6mvxsl98tfpgksd6xzsrucym0',
18+
signature2:
19+
'ed25519_sig1cqhp5j32e8tt8w7jqgpdcppuhqlwntuwk8duyj4lvjj4t687ws2t5ckzhfegfez6zls3kd4we3f48kwvwx03v8uktjuceuyg98crvqcy7jd5x',
20+
};
21+
22+
export const txIds = {
23+
hash1: 'e2d6ef426b378626599c5723f685a8ecf28940a1d54aa4afd63d7a31fb8c7d31',
24+
hash2: '5f325bb8c4225db5faa8de6297f427f489c9d39572ded0451b89f565a7cbd347',
25+
hash3: 'a19e177ba871af621a5c8bb429e756c2aa982ade9d4ae169c66f718284dd16cf',
26+
};
27+
28+
export const privateKeys = {
29+
prvKeyExtended:
30+
'ed25519e_sk13rv6afkern90kljwzqauj9p8s0qu4t29yjvm0u7q7exfq7yrp3xqyermevx7jg0xfjkg0sc5mdel0j46y8ym3fr6skygxxz7dm9zxygh5n244',
31+
prvKey2: 'ed25519_sk1u79r9v3a4dccx9l9rn8d0wrrd49umyf5x5ft6txfc8lpc96syzaqjccnu6',
32+
prvKey3WrongFormat:
33+
'ed25519e_sk1qr4lgrte8t43m6ky2cjdcnle583y54ex9j7z85fshenq86g92d8u6a40tgkpl702gqrqdgcdlxa7wzkgm74pqyspck7wclawgeu3yjslnu7nz3sdlszl1ad',
34+
prvKey4: '00cebbe7db664726835a451e2cb8c1eba596369318b3e158656f54f76d747b7b',
35+
prvKey5WrongFormat: '3560s-vl.1axfsjfjt6sa-q23-dssddddagag',
36+
};
37+
38+
export const publicKeys = {
39+
pubKey1: '9937fd481a9887463a67c39bad141ead730bffe7ed2cea31d7a02759bc197d00',
40+
pubKey2: 'ba0fe12d67c2e111aaabc809f8a3ee3168522371ffca360a459e4043dd91eb08',
41+
pubKey3: 'ed25519_pk1eame4ge0x5yrwpuqs5eyw89kfmjpgfkfh02xzdx6c2k9k2swcr5sjq4kzj',
42+
pubKey4: 'sdfghjkiu87y6t5r4e3w2sdxcfghjui654',
43+
};
44+
45+
export const enterpriseAccounts = {
46+
account1: {
47+
secretKey: 'ed25519_sk1amwk5s3l4fh6l86u8v2yxftldlg6mh9efdce5xy4wusl2kftr4aqlcy535',
48+
publicKey: 'ed25519_pk1zy8mkv3k0d2d04qqv0xpme54x8y6yywqpjwhgddw5huvlh6m8gusds3l67',
49+
hexPublic: '110fbb32367b54d7d40063cc1de69531c9a211c00c9d7435aea5f8cfdf5b3a39',
50+
baseAddress: 'addr_test1vztvlmj9ht3phzcfml02zejan534rwpsm23z8rmakt9wkxcc82u67',
51+
},
52+
53+
account2: {
54+
secretKey: 'ed25519_sk1t5yluzh50txlj3xq4rvg0sfaksy0qyavne57k6zae2575qz438nstqw5uq',
55+
publicKey: 'ed25519_pk138a2jey48a9a7ejrjxsq3td8vg7jz4urtnqmjcnqvcyet8d7jtjsey474x',
56+
hexPublic: '89faa964953f4bdf664391a008ada7623d2157835cc1b962606609959dbe92e5',
57+
baseAddress: 'addr_test1vpunqlch9k25zzzc76h7k8lra7wqx48qkmq0wtvd055um3sm4rler',
58+
},
59+
60+
account3: {
61+
secretKey: 'ed25519_sk1f44nvnxr8d37th07v44eumdtqkhlp29ddhtqv08n3alkhv2al2mqp2ak73',
62+
publicKey: 'ed25519_pk1gczdpplaq4ams4t9zt6t508pkygc364ltgkz9gnp4vxjz4m52maqdqpas9',
63+
baseAddress: 'addr_test1vq9arfq9pugs57apr3535z470ma2tvg8pnjy54q6s60muzscgmuj4',
64+
},
65+
66+
account4: {
67+
publicKeyHex: '9937fd481a9887463a67c39bad141ead730bffe7ed2cea31d7a02759bc197d00',
68+
baseAddress: 'addr_test1vpyzy3pkh0qwgyhnzz93v65t92p2gzwpswcysh94ffx30qqxke78c',
69+
},
70+
71+
seedAccount: {
72+
seed: '',
73+
secretKey:
74+
'xprv14zqmq4drmkvs76gxvqhg8dfdzcnf0hqer2ux0n2y8z09ulu5h4wegja87sygl77txhx7avkuefeq53ucq4m7e7wzx8n9wa603kk4hex3tvx0m2y3d2sjasnd7xh8tyy64mdksrv2v9xm8naf48xshtzlscwhqwcz',
75+
publicKey:
76+
'xpub196u3a0yqdke428lwmmq9k0wfuxw0npq45fr7yy99umykds0dcv3azkcvlk5fz64p9mpxmudwwkgf4tkmdqxc5c2dk086n2wdpwk9lpswvqje8',
77+
enterpriseAddress: 'addr_test1vr9exkzjnh6898pjg632qv7tnqs6h073dhjg3qq9jp9tcsg8d6n35',
78+
},
79+
};
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import should from 'should';
2+
import { KeyPair } from '../../src/keyPair';
3+
import { enterpriseAccounts } from '../resources/';
4+
import { PrivateKey, PublicKey } from '@emurgo/cardano-serialization-lib-nodejs';
5+
import { AddressFormat, toHex } from '@bitgo/sdk-core';
6+
7+
describe('Ada Keypair', () => {
8+
const defaultSeed = { seed: Buffer.alloc(32) };
9+
10+
describe('Keypair creation', () => {
11+
it('initial state', () => {
12+
const keyPair = new KeyPair();
13+
should.exists(keyPair.getKeys().prv);
14+
should.exists(keyPair.getKeys().prv);
15+
});
16+
17+
it('initialization from private key', () => {
18+
let keyPair = new KeyPair({
19+
prv: enterpriseAccounts.account1.secretKey,
20+
});
21+
22+
should.equal(
23+
keyPair.getKeys().prv,
24+
toHex(PrivateKey.from_bech32(enterpriseAccounts.account1.secretKey).as_bytes())
25+
);
26+
27+
should.equal(keyPair.getKeys().pub, enterpriseAccounts.account1.hexPublic);
28+
29+
keyPair = new KeyPair({
30+
prv: enterpriseAccounts.account2.secretKey,
31+
});
32+
33+
should.equal(
34+
keyPair.getKeys().prv,
35+
toHex(PrivateKey.from_bech32(enterpriseAccounts.account2.secretKey).as_bytes())
36+
);
37+
should.equal(keyPair.getKeys().pub, enterpriseAccounts.account2.hexPublic);
38+
});
39+
40+
it('initialization from public key', () => {
41+
let keyPair = new KeyPair({ pub: enterpriseAccounts.account1.publicKey });
42+
43+
should.equal(
44+
keyPair.getKeys().pub,
45+
toHex(PublicKey.from_bech32(enterpriseAccounts.account1.publicKey).as_bytes())
46+
);
47+
48+
keyPair = new KeyPair({ pub: enterpriseAccounts.account2.publicKey });
49+
should.equal(keyPair.getAddress(AddressFormat.testnet), enterpriseAccounts.account2.baseAddress);
50+
51+
keyPair = new KeyPair({ pub: enterpriseAccounts.account4.publicKeyHex });
52+
should.equal(keyPair.getKeys().pub, enterpriseAccounts.account4.publicKeyHex);
53+
});
54+
});
55+
56+
describe('KeyPair validation', () => {
57+
it('should fail to create from an invalid seed', () => {
58+
const seed = { seed: Buffer.alloc(8) }; // Seed should be 512 bits (64 bytes)
59+
should.throws(() => new KeyPair(seed), 'bad seed size');
60+
});
61+
62+
it('should fail to create from an invalid public key', () => {
63+
const source = {
64+
pub: '01D63D',
65+
};
66+
should.throws(() => new KeyPair(source), 'address seems to be malformed');
67+
});
68+
69+
it('should fail to create from an invalid private key', () => {
70+
const source = {
71+
prv: '82A34',
72+
};
73+
should.throws(() => new KeyPair(source), 'Invalid base32 characters');
74+
});
75+
});
76+
77+
describe('getAddress', () => {
78+
it('should get an address', () => {
79+
let keyPair = new KeyPair({ prv: enterpriseAccounts.account1.secretKey });
80+
let address = keyPair.getAddress(AddressFormat.testnet);
81+
address.should.equal(enterpriseAccounts.account1.baseAddress);
82+
83+
keyPair = new KeyPair({ prv: enterpriseAccounts.account3.secretKey });
84+
address = keyPair.getAddress(AddressFormat.testnet);
85+
address.should.equal(enterpriseAccounts.account3.baseAddress);
86+
87+
keyPair = new KeyPair({ pub: enterpriseAccounts.account4.publicKeyHex });
88+
address = keyPair.getAddress(AddressFormat.testnet);
89+
address.should.equal(enterpriseAccounts.account4.baseAddress);
90+
});
91+
});
92+
93+
describe('getKeys', () => {
94+
it('should get private and public keys in the protocol default format', () => {
95+
const keyPair = new KeyPair(defaultSeed);
96+
const { prv, pub } = keyPair.getKeys();
97+
98+
prv!.should.equal(prv);
99+
pub.should.equal(pub);
100+
const address = keyPair.getAddress(AddressFormat.testnet);
101+
address.should.equal(enterpriseAccounts.seedAccount.enterpriseAddress);
102+
});
103+
104+
it('should get private and public keys for a random seed', () => {
105+
const keyPair = new KeyPair();
106+
const { prv, pub } = keyPair.getKeys();
107+
should.exist(prv);
108+
should.exist(pub);
109+
});
110+
});
111+
});

0 commit comments

Comments
 (0)