Skip to content

Commit d2c7b31

Browse files
Merge pull request #1634 from input-output-hk/feat/make-bip32account-async
2 parents 8b3da5e + 97c3adc commit d2c7b31

File tree

13 files changed

+114
-55
lines changed

13 files changed

+114
-55
lines changed

packages/crypto/src/Bip32Ed25519.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,15 @@ export interface Bip32Ed25519 {
8282
*/
8383
derivePublicKey(parentKey: Bip32PublicKeyHex, derivationIndices: BIP32Path): Bip32PublicKeyHex;
8484

85+
/**
86+
* Given a parent extended key and a set of indices, this function computes the corresponding child extended key.
87+
*
88+
* @param parentKey The parent extended key.
89+
* @param derivationIndices The list of derivation indices.
90+
* @returns A promise returning the child extended public key.
91+
*/
92+
derivePublicKeyAsync(parentKey: Bip32PublicKeyHex, derivationIndices: BIP32Path): Promise<Bip32PublicKeyHex>;
93+
8594
/**
8695
* Generates an Ed25519 signature using an extended private key.
8796
*

packages/crypto/src/blake2b-hash.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,23 @@ export interface Blake2b {
2222
* @param outputLengthBytes digest size, e.g. 28 for blake2b-224 or 32 for blake2b-256
2323
*/
2424
hash<T extends HexBlob>(message: HexBlob, outputLengthBytes: number): T;
25+
26+
/**
27+
* @param message payload to hash
28+
* @param outputLengthBytes digest size, e.g. 28 for blake2b-224 or 32 for blake2b-256
29+
*/
30+
hashAsync<T extends HexBlob>(message: HexBlob, outputLengthBytes: number): Promise<T>;
2531
}
2632

2733
export const blake2b: Blake2b = {
2834
hash<T extends HexBlob>(message: HexBlob, outputLengthBytes: number) {
2935
return hash(outputLengthBytes).update(hexStringToBuffer(message)).digest('hex') as T;
36+
},
37+
async hashAsync<T extends HexBlob>(message: HexBlob, outputLengthBytes: number): Promise<T> {
38+
return new Promise((resolve) => {
39+
setImmediate(() => {
40+
resolve(blake2b.hash<T>(message, outputLengthBytes));
41+
});
42+
});
3043
}
3144
};

packages/crypto/src/strategies/CmlBip32Ed25519.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@ export class CmlBip32Ed25519 implements Bip32Ed25519 {
100100
});
101101
}
102102

103+
public async derivePublicKeyAsync(
104+
parentKey: Bip32PublicKeyHex,
105+
derivationIndices: BIP32Path
106+
): Promise<Bip32PublicKeyHex> {
107+
return new Promise((resolve) => {
108+
setImmediate(() => {
109+
resolve(this.derivePublicKey(parentKey, derivationIndices));
110+
});
111+
});
112+
}
113+
103114
public sign(
104115
privateKey: Ed25519PrivateExtendedKeyHex | Ed25519PrivateNormalKeyHex,
105116
message: HexBlob

packages/crypto/src/strategies/SodiumBip32Ed25519.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,17 @@ export class SodiumBip32Ed25519 implements Bip32Ed25519 {
7070
return pubKey.derive(derivationIndices).hex();
7171
}
7272

73+
public async derivePublicKeyAsync(
74+
parentKey: Bip32PublicKeyHex,
75+
derivationIndices: BIP32Path
76+
): Promise<Bip32PublicKeyHex> {
77+
return new Promise((resolve) => {
78+
setImmediate(() => {
79+
resolve(this.derivePublicKey(parentKey, derivationIndices));
80+
});
81+
});
82+
}
83+
7384
public sign(
7485
privateKey: Ed25519PrivateExtendedKeyHex | Ed25519PrivateNormalKeyHex,
7586
message: HexBlob

packages/e2e/test/handle/KoraLabsHandleProvider.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ const checkHandleResolution = (source: string, result: unknown) => {
2626
expect(['string', 'undefined']).toContain(typeof profilePic);
2727
};
2828

29-
describe('KoraLabsHandleProvider', () => {
29+
// Fix flaky tests LW-13058
30+
describe.skip('KoraLabsHandleProvider', () => {
3031
let provider: KoraLabsHandleProvider;
3132

3233
beforeAll(() => {

packages/e2e/test/local-network/register-pool.test.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,21 @@ describe('local-network/register-pool', () => {
8282

8383
await unDelegateWallet(wallet);
8484

85-
const poolPubKey = wallet1.bip32Account.derivePublicKey({
85+
const poolPubKey = await wallet1.bip32Account.derivePublicKey({
8686
index: 0,
8787
role: KeyRole.External
8888
});
8989

9090
const poolKeyHash = bip32Ed25519.getPubKeyHash(poolPubKey);
9191
const poolId = Cardano.PoolId.fromKeyHash(poolKeyHash);
92-
const poolRewardAccount = wallet1.bip32Account.deriveAddress(
93-
{
94-
index: 0,
95-
type: AddressType.External
96-
},
97-
0
92+
const poolRewardAccount = (
93+
await wallet1.bip32Account.deriveAddress(
94+
{
95+
index: 0,
96+
type: AddressType.External
97+
},
98+
0
99+
)
98100
).rewardAccount;
99101

100102
const registrationCert: Cardano.PoolRegistrationCertificate = {
@@ -167,19 +169,21 @@ describe('local-network/register-pool', () => {
167169
await walletReady(wallet);
168170
await unDelegateWallet(wallet);
169171

170-
const poolPubKey = wallet2.bip32Account.derivePublicKey({
172+
const poolPubKey = await wallet2.bip32Account.derivePublicKey({
171173
index: 0,
172174
role: KeyRole.External
173175
});
174176

175177
const poolKeyHash = bip32Ed25519.getPubKeyHash(poolPubKey);
176178
const poolId = Cardano.PoolId.fromKeyHash(poolKeyHash);
177-
const poolRewardAccount = wallet2.bip32Account.deriveAddress(
178-
{
179-
index: 0,
180-
type: AddressType.External
181-
},
182-
0
179+
const poolRewardAccount = (
180+
await wallet2.bip32Account.deriveAddress(
181+
{
182+
index: 0,
183+
type: AddressType.External
184+
},
185+
0
186+
)
183187
).rewardAccount;
184188

185189
const registrationCert: Cardano.PoolRegistrationCertificate = {

packages/e2e/test/long-running/cache-invalidation.test.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,19 +107,21 @@ describe('cache invalidation', () => {
107107

108108
await walletReady(wallet);
109109

110-
const poolPubKey = wallet1.bip32Account.derivePublicKey({
110+
const poolPubKey = await wallet1.bip32Account.derivePublicKey({
111111
index: 0,
112112
role: KeyRole.External
113113
});
114114

115115
const poolKeyHash = bip32Ed25519.getPubKeyHash(poolPubKey);
116116
const poolId = Cardano.PoolId.fromKeyHash(poolKeyHash);
117-
const poolRewardAccount = wallet1.bip32Account.deriveAddress(
118-
{
119-
index: 0,
120-
type: AddressType.External
121-
},
122-
0
117+
const poolRewardAccount = (
118+
await wallet1.bip32Account.deriveAddress(
119+
{
120+
index: 0,
121+
type: AddressType.External
122+
},
123+
0
124+
)
123125
).rewardAccount;
124126

125127
const registrationCert: Cardano.PoolRegistrationCertificate = {

packages/key-management/src/Bip32Account.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ type Bip32AccountProps = {
2323
};
2424

2525
export type Bip32AccountDependencies = {
26-
bip32Ed25519: Pick<Bip32Ed25519, 'derivePublicKey'>;
27-
blake2b: Blake2b;
26+
bip32Ed25519: Pick<Bip32Ed25519, 'derivePublicKeyAsync'>;
27+
blake2b: Pick<Blake2b, 'hashAsync'>;
2828
};
2929

3030
/** Derives public keys and addresses from a BIP32-ED25519 public key */
@@ -33,7 +33,7 @@ export class Bip32Account {
3333
readonly chainId: Cardano.ChainId;
3434
readonly accountIndex: number;
3535
readonly #bip32Ed25519: Bip32AccountDependencies['bip32Ed25519'];
36-
readonly #blake2b: Blake2b;
36+
readonly #blake2b: Bip32AccountDependencies['blake2b'];
3737

3838
/** Initializes a new instance of the Bip32Ed25519AddressManager class. */
3939
constructor(
@@ -47,32 +47,38 @@ export class Bip32Account {
4747
this.accountIndex = accountIndex;
4848
}
4949

50-
derivePublicKey(derivationPath: AccountKeyDerivationPath) {
51-
const extendedKey = this.#bip32Ed25519.derivePublicKey(this.extendedAccountPublicKeyHex, [
50+
async derivePublicKey(derivationPath: AccountKeyDerivationPath): Promise<Crypto.Ed25519PublicKeyHex> {
51+
const extendedKey = await this.#bip32Ed25519.derivePublicKeyAsync(this.extendedAccountPublicKeyHex, [
5252
derivationPath.role,
5353
derivationPath.index
5454
]);
5555
return Ed25519PublicKeyHex.fromBip32PublicKey(extendedKey);
5656
}
5757

58-
deriveAddress(
58+
async deriveAddress(
5959
paymentKeyDerivationPath: AccountAddressDerivationPath,
6060
stakeKeyDerivationIndex: number
61-
): GroupedAddress {
61+
): Promise<GroupedAddress> {
6262
const stakeKeyDerivationPath = {
6363
index: stakeKeyDerivationIndex,
6464
role: KeyRole.Stake
6565
};
6666

67-
const derivedPublicPaymentKey = this.derivePublicKey({
67+
const derivedPublicPaymentKey = await this.derivePublicKey({
6868
index: paymentKeyDerivationPath.index,
6969
role: Number(paymentKeyDerivationPath.type)
7070
});
7171

72-
const derivedPublicPaymentKeyHash = this.#blake2b.hash(derivedPublicPaymentKey, BIP32_PUBLIC_KEY_HASH_LENGTH);
72+
const derivedPublicPaymentKeyHash = (await this.#blake2b.hashAsync(
73+
derivedPublicPaymentKey,
74+
BIP32_PUBLIC_KEY_HASH_LENGTH
75+
)) as Crypto.Hash28ByteBase16;
7376

74-
const publicStakeKey = this.derivePublicKey(stakeKeyDerivationPath);
75-
const publicStakeKeyHash = this.#blake2b.hash(publicStakeKey, BIP32_PUBLIC_KEY_HASH_LENGTH);
77+
const publicStakeKey = await this.derivePublicKey(stakeKeyDerivationPath);
78+
const publicStakeKeyHash = (await this.#blake2b.hashAsync(
79+
publicStakeKey,
80+
BIP32_PUBLIC_KEY_HASH_LENGTH
81+
)) as Crypto.Hash28ByteBase16;
7682

7783
const stakeCredential = { hash: publicStakeKeyHash, type: Cardano.CredentialType.KeyHash };
7884

@@ -105,6 +111,7 @@ export class Bip32Account {
105111
* Creates a new instance of the Bip32Ed25519AddressManager class.
106112
*
107113
* @param keyAgent The key agent that will be used to derive addresses.
114+
* @param dependencies Optional dependencies for the Bip32Account. If not provided, default dependencies will be created.
108115
*/
109116
static async fromAsyncKeyAgent(
110117
keyAgent: AsyncKeyAgent,

packages/key-management/test/Bip32Account.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ describe('Bip32Account', () => {
8383
[4, '4444444444444444444444444444444444444444444444444444444444444444']
8484
]);
8585

86-
testnetAccount.derivePublicKey = jest.fn((x: AccountKeyDerivationPath) =>
86+
testnetAccount.derivePublicKey = jest.fn(async (x: AccountKeyDerivationPath) =>
8787
Crypto.Ed25519PublicKeyHex(keyMap.get(x.index)!)
8888
);
8989

packages/tx-construction/src/tx-builder/initializeTx.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import { hasCorrectVoteDelegation } from './hasCorrectVoteDelegation';
1212

1313
const dRepPublicKeyHash = async (addressManager?: Bip32Account): Promise<Ed25519KeyHashHex | undefined> =>
1414
addressManager &&
15-
Ed25519PublicKey.fromHex(addressManager.derivePublicKey(util.DREP_KEY_DERIVATION_PATH)).hash().hex();
15+
Ed25519PublicKey.fromHex(await addressManager.derivePublicKey(util.DREP_KEY_DERIVATION_PATH))
16+
.hash()
17+
.hex();
1618

1719
const DREP_REG_REQUIRED_PROTOCOL_VERSION = 10;
1820

packages/wallet/src/services/PublicStakeKeysTracker.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { AccountKeyDerivationPath, Bip32Account, GroupedAddress } from '@cardano-sdk/key-management';
22
import { Cardano } from '@cardano-sdk/core';
33
import { Ed25519PublicKeyHex } from '@cardano-sdk/crypto';
4-
import { Observable, distinctUntilChanged, map, switchMap } from 'rxjs';
4+
import { Observable, defaultIfEmpty, distinctUntilChanged, forkJoin, from, map, mergeMap, switchMap } from 'rxjs';
55
import { TrackerSubject } from '@cardano-sdk/util-rxjs';
66
import { deepEquals } from '@cardano-sdk/util';
77

@@ -48,11 +48,14 @@ export const createPublicStakeKeysTracker = ({
4848
new TrackerSubject(
4949
rewardAccounts$.pipe(
5050
withStakeKeyDerivationPaths(addresses$),
51-
map((derivationPathsAndStatus) =>
52-
derivationPathsAndStatus.map(({ stakeKeyDerivationPath, credentialStatus }) => ({
53-
credentialStatus,
54-
publicStakeKey: addressManager.derivePublicKey(stakeKeyDerivationPath)
55-
}))
51+
mergeMap((derivationPathsAndStatus) =>
52+
forkJoin(
53+
derivationPathsAndStatus.map(({ stakeKeyDerivationPath, credentialStatus }) =>
54+
from(addressManager.derivePublicKey(stakeKeyDerivationPath)).pipe(
55+
map((publicStakeKey) => ({ credentialStatus, publicStakeKey }))
56+
)
57+
)
58+
).pipe(defaultIfEmpty([]))
5659
),
5760
distinctUntilChanged(deepEquals)
5861
)

packages/wallet/test/PersonalWallet/methods.test.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ describe('BaseWallet methods', () => {
107107

108108
const asyncKeyAgent = await testAsyncKeyAgent();
109109
bip32Account = await Bip32Account.fromAsyncKeyAgent(asyncKeyAgent);
110-
bip32Account.deriveAddress = jest.fn().mockReturnValue(groupedAddress);
110+
bip32Account.deriveAddress = jest.fn().mockResolvedValue(groupedAddress);
111111
witnesser = util.createBip32Ed25519Witnesser(asyncKeyAgent);
112112
wallet = createPersonalWallet(
113113
{ name: 'Test Wallet' },
@@ -671,12 +671,7 @@ describe('BaseWallet methods', () => {
671671

672672
it('will retry deriving pubDrepKey if one does not exist', async () => {
673673
wallet.shutdown();
674-
bip32Account.derivePublicKey = jest
675-
.fn()
676-
.mockImplementationOnce(() => {
677-
throw new Error('error');
678-
})
679-
.mockReturnValue('string');
674+
bip32Account.derivePublicKey = jest.fn().mockRejectedValueOnce('error').mockResolvedValue('string');
680675
wallet = createPersonalWallet(
681676
{ name: 'Test Wallet' },
682677
{
@@ -819,7 +814,7 @@ describe('BaseWallet methods', () => {
819814
beforeEach(() => {
820815
wallet.shutdown();
821816

822-
bip32Account.deriveAddress = jest.fn((args) => {
817+
bip32Account.deriveAddress = jest.fn(async (args) => {
823818
if (args.index === 0) {
824819
return groupedAddress;
825820
}

packages/wallet/test/services/PublicStakeKeysTracker.test.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { AccountKeyDerivationPath, Bip32Account, GroupedAddress, KeyRole } from
22
import { Cardano } from '@cardano-sdk/core';
33
import { ObservableWallet } from '../../src';
44
import { PubStakeKeyAndStatus, createPublicStakeKeysTracker } from '../../src/services/PublicStakeKeysTracker';
5-
import { delay, firstValueFrom, from, lastValueFrom, of, shareReplay, toArray } from 'rxjs';
5+
import { firstValueFrom, from, lastValueFrom, of, shareReplay, toArray } from 'rxjs';
66
import { mockProviders as mocks } from '@cardano-sdk/util-dev';
77

88
describe('PublicStakeKeysTracker', () => {
@@ -64,7 +64,9 @@ describe('PublicStakeKeysTracker', () => {
6464
}
6565
];
6666

67-
derivePublicKey = jest.fn().mockImplementation((path: AccountKeyDerivationPath) => `abc-${path.index}`);
67+
derivePublicKey = jest
68+
.fn()
69+
.mockImplementation((path: AccountKeyDerivationPath) => Promise.resolve(`abc-${path.index}`));
6870
bip32Account = {
6971
accountIndex: 0,
7072
chainId: Cardano.ChainIds.Preview,
@@ -135,7 +137,7 @@ describe('PublicStakeKeysTracker', () => {
135137

136138
it('emits when reward accounts change', async () => {
137139
const addresses$ = of(addresses);
138-
const rewardAccounts$ = from([[rewardAccounts[0]], rewardAccounts]).pipe(delay(1));
140+
const rewardAccounts$ = from([[rewardAccounts[0]], rewardAccounts]);
139141

140142
const stakePubKeys$ = createPublicStakeKeysTracker({
141143
addresses$,
@@ -155,7 +157,7 @@ describe('PublicStakeKeysTracker', () => {
155157
});
156158

157159
it('emits when addresses change', async () => {
158-
const addresses$ = from([[addresses[0]], addresses]).pipe(delay(1));
160+
const addresses$ = from([[addresses[0]], addresses]);
159161
const rewardAccounts$ = of(rewardAccounts);
160162

161163
const stakePubKeys$ = createPublicStakeKeysTracker({
@@ -176,9 +178,8 @@ describe('PublicStakeKeysTracker', () => {
176178
});
177179

178180
it('does not emit duplicates', async () => {
179-
const rewardAccounts$ = from([rewardAccounts, rewardAccounts]).pipe(delay(1));
181+
const rewardAccounts$ = from([rewardAccounts, rewardAccounts]);
180182
const addresses$ = from([[addresses[0]], addresses, addresses]).pipe(
181-
delay(1),
182183
shareReplay({ bufferSize: 1, refCount: true })
183184
);
184185

0 commit comments

Comments
 (0)