Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/crypto/src/Bip32Ed25519.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ export interface Bip32Ed25519 {
*/
derivePublicKey(parentKey: Bip32PublicKeyHex, derivationIndices: BIP32Path): Bip32PublicKeyHex;

/**
* Given a parent extended key and a set of indices, this function computes the corresponding child extended key.
*
* @param parentKey The parent extended key.
* @param derivationIndices The list of derivation indices.
* @returns A promise returning the child extended public key.
*/
derivePublicKeyAsync(parentKey: Bip32PublicKeyHex, derivationIndices: BIP32Path): Promise<Bip32PublicKeyHex>;

/**
* Generates an Ed25519 signature using an extended private key.
*
Expand Down
13 changes: 13 additions & 0 deletions packages/crypto/src/blake2b-hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,23 @@ export interface Blake2b {
* @param outputLengthBytes digest size, e.g. 28 for blake2b-224 or 32 for blake2b-256
*/
hash<T extends HexBlob>(message: HexBlob, outputLengthBytes: number): T;

/**
* @param message payload to hash
* @param outputLengthBytes digest size, e.g. 28 for blake2b-224 or 32 for blake2b-256
*/
hashAsync<T extends HexBlob>(message: HexBlob, outputLengthBytes: number): Promise<T>;
}

export const blake2b: Blake2b = {
hash<T extends HexBlob>(message: HexBlob, outputLengthBytes: number) {
return hash(outputLengthBytes).update(hexStringToBuffer(message)).digest('hex') as T;
},
async hashAsync<T extends HexBlob>(message: HexBlob, outputLengthBytes: number): Promise<T> {
return new Promise((resolve) => {
setImmediate(() => {
resolve(blake2b.hash<T>(message, outputLengthBytes));
});
});
}
};
11 changes: 11 additions & 0 deletions packages/crypto/src/strategies/CmlBip32Ed25519.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ export class CmlBip32Ed25519 implements Bip32Ed25519 {
});
}

public async derivePublicKeyAsync(
parentKey: Bip32PublicKeyHex,
derivationIndices: BIP32Path
): Promise<Bip32PublicKeyHex> {
return new Promise((resolve) => {
setImmediate(() => {
resolve(this.derivePublicKey(parentKey, derivationIndices));
});
});
}

public sign(
privateKey: Ed25519PrivateExtendedKeyHex | Ed25519PrivateNormalKeyHex,
message: HexBlob
Expand Down
11 changes: 11 additions & 0 deletions packages/crypto/src/strategies/SodiumBip32Ed25519.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ export class SodiumBip32Ed25519 implements Bip32Ed25519 {
return pubKey.derive(derivationIndices).hex();
}

public async derivePublicKeyAsync(
parentKey: Bip32PublicKeyHex,
derivationIndices: BIP32Path
): Promise<Bip32PublicKeyHex> {
return new Promise((resolve) => {
setImmediate(() => {
resolve(this.derivePublicKey(parentKey, derivationIndices));
});
});
}

public sign(
privateKey: Ed25519PrivateExtendedKeyHex | Ed25519PrivateNormalKeyHex,
message: HexBlob
Expand Down
3 changes: 2 additions & 1 deletion packages/e2e/test/handle/KoraLabsHandleProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ const checkHandleResolution = (source: string, result: unknown) => {
expect(['string', 'undefined']).toContain(typeof profilePic);
};

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

beforeAll(() => {
Expand Down
32 changes: 18 additions & 14 deletions packages/e2e/test/local-network/register-pool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,21 @@ describe('local-network/register-pool', () => {

await unDelegateWallet(wallet);

const poolPubKey = wallet1.bip32Account.derivePublicKey({
const poolPubKey = await wallet1.bip32Account.derivePublicKey({
index: 0,
role: KeyRole.External
});

const poolKeyHash = bip32Ed25519.getPubKeyHash(poolPubKey);
const poolId = Cardano.PoolId.fromKeyHash(poolKeyHash);
const poolRewardAccount = wallet1.bip32Account.deriveAddress(
{
index: 0,
type: AddressType.External
},
0
const poolRewardAccount = (
await wallet1.bip32Account.deriveAddress(
{
index: 0,
type: AddressType.External
},
0
)
).rewardAccount;

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

const poolPubKey = wallet2.bip32Account.derivePublicKey({
const poolPubKey = await wallet2.bip32Account.derivePublicKey({
index: 0,
role: KeyRole.External
});

const poolKeyHash = bip32Ed25519.getPubKeyHash(poolPubKey);
const poolId = Cardano.PoolId.fromKeyHash(poolKeyHash);
const poolRewardAccount = wallet2.bip32Account.deriveAddress(
{
index: 0,
type: AddressType.External
},
0
const poolRewardAccount = (
await wallet2.bip32Account.deriveAddress(
{
index: 0,
type: AddressType.External
},
0
)
).rewardAccount;

const registrationCert: Cardano.PoolRegistrationCertificate = {
Expand Down
16 changes: 9 additions & 7 deletions packages/e2e/test/long-running/cache-invalidation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,19 +107,21 @@ describe('cache invalidation', () => {

await walletReady(wallet);

const poolPubKey = wallet1.bip32Account.derivePublicKey({
const poolPubKey = await wallet1.bip32Account.derivePublicKey({
index: 0,
role: KeyRole.External
});

const poolKeyHash = bip32Ed25519.getPubKeyHash(poolPubKey);
const poolId = Cardano.PoolId.fromKeyHash(poolKeyHash);
const poolRewardAccount = wallet1.bip32Account.deriveAddress(
{
index: 0,
type: AddressType.External
},
0
const poolRewardAccount = (
await wallet1.bip32Account.deriveAddress(
{
index: 0,
type: AddressType.External
},
0
)
).rewardAccount;

const registrationCert: Cardano.PoolRegistrationCertificate = {
Expand Down
29 changes: 18 additions & 11 deletions packages/key-management/src/Bip32Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ type Bip32AccountProps = {
};

export type Bip32AccountDependencies = {
bip32Ed25519: Pick<Bip32Ed25519, 'derivePublicKey'>;
blake2b: Blake2b;
bip32Ed25519: Pick<Bip32Ed25519, 'derivePublicKeyAsync'>;
blake2b: Pick<Blake2b, 'hashAsync'>;
};

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

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

derivePublicKey(derivationPath: AccountKeyDerivationPath) {
const extendedKey = this.#bip32Ed25519.derivePublicKey(this.extendedAccountPublicKeyHex, [
async derivePublicKey(derivationPath: AccountKeyDerivationPath): Promise<Crypto.Ed25519PublicKeyHex> {
const extendedKey = await this.#bip32Ed25519.derivePublicKeyAsync(this.extendedAccountPublicKeyHex, [
derivationPath.role,
derivationPath.index
]);
return Ed25519PublicKeyHex.fromBip32PublicKey(extendedKey);
}

deriveAddress(
async deriveAddress(
paymentKeyDerivationPath: AccountAddressDerivationPath,
stakeKeyDerivationIndex: number
): GroupedAddress {
): Promise<GroupedAddress> {
const stakeKeyDerivationPath = {
index: stakeKeyDerivationIndex,
role: KeyRole.Stake
};

const derivedPublicPaymentKey = this.derivePublicKey({
const derivedPublicPaymentKey = await this.derivePublicKey({
index: paymentKeyDerivationPath.index,
role: Number(paymentKeyDerivationPath.type)
});

const derivedPublicPaymentKeyHash = this.#blake2b.hash(derivedPublicPaymentKey, BIP32_PUBLIC_KEY_HASH_LENGTH);
const derivedPublicPaymentKeyHash = (await this.#blake2b.hashAsync(
derivedPublicPaymentKey,
BIP32_PUBLIC_KEY_HASH_LENGTH
)) as Crypto.Hash28ByteBase16;

const publicStakeKey = this.derivePublicKey(stakeKeyDerivationPath);
const publicStakeKeyHash = this.#blake2b.hash(publicStakeKey, BIP32_PUBLIC_KEY_HASH_LENGTH);
const publicStakeKey = await this.derivePublicKey(stakeKeyDerivationPath);
const publicStakeKeyHash = (await this.#blake2b.hashAsync(
publicStakeKey,
BIP32_PUBLIC_KEY_HASH_LENGTH
)) as Crypto.Hash28ByteBase16;

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

Expand Down Expand Up @@ -105,6 +111,7 @@ export class Bip32Account {
* Creates a new instance of the Bip32Ed25519AddressManager class.
*
* @param keyAgent The key agent that will be used to derive addresses.
* @param dependencies Optional dependencies for the Bip32Account. If not provided, default dependencies will be created.
*/
static async fromAsyncKeyAgent(
keyAgent: AsyncKeyAgent,
Expand Down
2 changes: 1 addition & 1 deletion packages/key-management/test/Bip32Account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe('Bip32Account', () => {
[4, '4444444444444444444444444444444444444444444444444444444444444444']
]);

testnetAccount.derivePublicKey = jest.fn((x: AccountKeyDerivationPath) =>
testnetAccount.derivePublicKey = jest.fn(async (x: AccountKeyDerivationPath) =>
Crypto.Ed25519PublicKeyHex(keyMap.get(x.index)!)
);

Expand Down
4 changes: 3 additions & 1 deletion packages/tx-construction/src/tx-builder/initializeTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import { hasCorrectVoteDelegation } from './hasCorrectVoteDelegation';

const dRepPublicKeyHash = async (addressManager?: Bip32Account): Promise<Ed25519KeyHashHex | undefined> =>
addressManager &&
Ed25519PublicKey.fromHex(addressManager.derivePublicKey(util.DREP_KEY_DERIVATION_PATH)).hash().hex();
Ed25519PublicKey.fromHex(await addressManager.derivePublicKey(util.DREP_KEY_DERIVATION_PATH))
.hash()
.hex();

const DREP_REG_REQUIRED_PROTOCOL_VERSION = 10;

Expand Down
15 changes: 9 additions & 6 deletions packages/wallet/src/services/PublicStakeKeysTracker.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AccountKeyDerivationPath, Bip32Account, GroupedAddress } from '@cardano-sdk/key-management';
import { Cardano } from '@cardano-sdk/core';
import { Ed25519PublicKeyHex } from '@cardano-sdk/crypto';
import { Observable, distinctUntilChanged, map, switchMap } from 'rxjs';
import { Observable, defaultIfEmpty, distinctUntilChanged, forkJoin, from, map, mergeMap, switchMap } from 'rxjs';
import { TrackerSubject } from '@cardano-sdk/util-rxjs';
import { deepEquals } from '@cardano-sdk/util';

Expand Down Expand Up @@ -48,11 +48,14 @@ export const createPublicStakeKeysTracker = ({
new TrackerSubject(
rewardAccounts$.pipe(
withStakeKeyDerivationPaths(addresses$),
map((derivationPathsAndStatus) =>
derivationPathsAndStatus.map(({ stakeKeyDerivationPath, credentialStatus }) => ({
credentialStatus,
publicStakeKey: addressManager.derivePublicKey(stakeKeyDerivationPath)
}))
mergeMap((derivationPathsAndStatus) =>
forkJoin(
derivationPathsAndStatus.map(({ stakeKeyDerivationPath, credentialStatus }) =>
from(addressManager.derivePublicKey(stakeKeyDerivationPath)).pipe(
map((publicStakeKey) => ({ credentialStatus, publicStakeKey }))
)
)
).pipe(defaultIfEmpty([]))
),
distinctUntilChanged(deepEquals)
)
Expand Down
11 changes: 3 additions & 8 deletions packages/wallet/test/PersonalWallet/methods.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe('BaseWallet methods', () => {

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

it('will retry deriving pubDrepKey if one does not exist', async () => {
wallet.shutdown();
bip32Account.derivePublicKey = jest
.fn()
.mockImplementationOnce(() => {
throw new Error('error');
})
.mockReturnValue('string');
bip32Account.derivePublicKey = jest.fn().mockRejectedValueOnce('error').mockResolvedValue('string');
wallet = createPersonalWallet(
{ name: 'Test Wallet' },
{
Expand Down Expand Up @@ -819,7 +814,7 @@ describe('BaseWallet methods', () => {
beforeEach(() => {
wallet.shutdown();

bip32Account.deriveAddress = jest.fn((args) => {
bip32Account.deriveAddress = jest.fn(async (args) => {
if (args.index === 0) {
return groupedAddress;
}
Expand Down
13 changes: 7 additions & 6 deletions packages/wallet/test/services/PublicStakeKeysTracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AccountKeyDerivationPath, Bip32Account, GroupedAddress, KeyRole } from
import { Cardano } from '@cardano-sdk/core';
import { ObservableWallet } from '../../src';
import { PubStakeKeyAndStatus, createPublicStakeKeysTracker } from '../../src/services/PublicStakeKeysTracker';
import { delay, firstValueFrom, from, lastValueFrom, of, shareReplay, toArray } from 'rxjs';
import { firstValueFrom, from, lastValueFrom, of, shareReplay, toArray } from 'rxjs';
import { mockProviders as mocks } from '@cardano-sdk/util-dev';

describe('PublicStakeKeysTracker', () => {
Expand Down Expand Up @@ -64,7 +64,9 @@ describe('PublicStakeKeysTracker', () => {
}
];

derivePublicKey = jest.fn().mockImplementation((path: AccountKeyDerivationPath) => `abc-${path.index}`);
derivePublicKey = jest
.fn()
.mockImplementation((path: AccountKeyDerivationPath) => Promise.resolve(`abc-${path.index}`));
bip32Account = {
accountIndex: 0,
chainId: Cardano.ChainIds.Preview,
Expand Down Expand Up @@ -135,7 +137,7 @@ describe('PublicStakeKeysTracker', () => {

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

const stakePubKeys$ = createPublicStakeKeysTracker({
addresses$,
Expand All @@ -155,7 +157,7 @@ describe('PublicStakeKeysTracker', () => {
});

it('emits when addresses change', async () => {
const addresses$ = from([[addresses[0]], addresses]).pipe(delay(1));
const addresses$ = from([[addresses[0]], addresses]);
const rewardAccounts$ = of(rewardAccounts);

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

it('does not emit duplicates', async () => {
const rewardAccounts$ = from([rewardAccounts, rewardAccounts]).pipe(delay(1));
const rewardAccounts$ = from([rewardAccounts, rewardAccounts]);
const addresses$ = from([[addresses[0]], addresses, addresses]).pipe(
delay(1),
shareReplay({ bufferSize: 1, refCount: true })
);

Expand Down
Loading