Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LLD - Fix LS QR Code integration test #8212

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
import React from "react";
import { render, screen } from "tests/testUtils";
import { render, screen, act } from "tests/testUtils";
import { createQRCodeHostInstance } from "@ledgerhq/ledger-key-ring-protocol/qrcode/index";
import { WalletSyncTestApp, simpleTrustChain, walletSyncActivatedState } from "./shared";

jest.mock("../hooks/useQRCode", () => ({
useQRCode: () => ({
startQRCodeProcessing: () => jest.fn(),
url: "https://ledger.com",
error: null,
isLoading: false,
}),
}));

jest.mock("../hooks/useGetMembers", () => ({
useGetMembers: () => ({
isLoading: false,
Expand All @@ -20,61 +12,59 @@ jest.mock("../hooks/useGetMembers", () => ({
}),
}));

const openDrawer = async () => {
const { user } = render(<WalletSyncTestApp />, {
initialState: {
walletSync: walletSyncActivatedState,
trustchain: {
trustchain: simpleTrustChain,
memberCredentials: {
pubkey: "pubkey",
privatekey: "privatekey",
},
},
},
});
const button = screen.getByRole("button", { name: "Manage" });
jest.mock("@ledgerhq/ledger-key-ring-protocol/qrcode/index", () => ({
createQRCodeHostInstance: jest.fn(),
}));

return {
button,
user,
};
};
jest.useFakeTimers({ advanceTimers: true });

describe("Synchronize flow", () => {
it("should open drawer and should do Synchronize flow with QRCode", async () => {
const { button, user } = await openDrawer();
await user.click(button);
let resolveQRCodeFlowPromise: unknown = null;
let requestDisplayDigits: unknown = null;
const mockPromiseQRCodeCandidate = new Promise(resolve => {
resolveQRCodeFlowPromise = resolve;
});
(createQRCodeHostInstance as jest.Mock).mockImplementation(({ onDisplayDigits }) => {
requestDisplayDigits = onDisplayDigits;
return mockPromiseQRCodeCandidate;
});

const { user } = render(<WalletSyncTestApp />, {
initialState: {
walletSync: walletSyncActivatedState,
trustchain: {
trustchain: simpleTrustChain,
memberCredentials: {
pubkey: "pubkey",
privatekey: "privatekey",
},
},
},
});

await user.click(screen.getByRole("button", { name: "Manage" }));

await user.click(await screen.findByTestId("walletSync-synchronize"));

await screen.findByText(/sync with the ledger live app on another phone/i);

act(() => {
if (typeof requestDisplayDigits === "function") requestDisplayDigits("321");
});

const row = await screen.findByTestId("walletSync-synchronize");
expect(await screen.findByTestId(/pin-code-digit-0/i)).toHaveTextContent("3");
expect(await screen.findByTestId(/pin-code-digit-1/i)).toHaveTextContent("2");
expect(await screen.findByTestId(/pin-code-digit-2/i)).toHaveTextContent("1");

await user.click(row);
if (typeof resolveQRCodeFlowPromise === "function") resolveQRCodeFlowPromise();

// QRCode Page
expect(
await screen.findByText(/Sync with the Ledger Live app on another phone/i),
).toBeDefined();
expect(await screen.findByText(/Hang tight.../i)).toBeDefined();

//TODO: Fix this test
//PinCode Page after scanning QRCode
// Need to wait 3 seconds to simulate the time taken to scan the QR code
// setTimeout(async () => {
// await waitFor(() => {
// screen.debug();
// expect(screen.getByText("Your Ledger Sync code")).toBeDefined();
// });
// }, 3000);
await act(async () => {
jest.advanceTimersByTime(3 * 1000);
});

// //Succes Page after PinCode
// setTimeout(async () => {
// await waitFor(() => {
// screen.debug();
// expect(
// screen.getByText(
// "Changes in your crypto accounts will now automatically appear across Ledger Live apps on synched phones and computers.",
// ),
// ).toBeDefined();
// });
// }, 3000);
expect(await screen.findByText(/sync successful!/i)).toBeDefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default function PinCodeStep() {
backgroundColor={colors.opacityDefault.c05}
alignItems="center"
justifyContent="center"
data-testid={`pin-code-digit-${index}`}
>
<Text fontSize={14} variant="body" fontWeight="medium" color="neutral.c100">
{digit}
Expand Down
2 changes: 2 additions & 0 deletions apps/ledger-live-desktop/tests/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ export default [...NFTsHandlers];
export const ALLOWED_UNHANDLED_REQUESTS = [
"https://nft.api.live.ledger.com/v1/marketdata/ethereum/1/contract/0xe3BE0054Da2F8da5002E8bdD8AA4c7fDf851E86D/floor-price",
"https://nft.api.live.ledger.com/v1/ethereum/1/contracts/infos",
"https://cloud-sync-backend.api.aws.stg.ldg-tech.com/_info",
"https://trustchain-backend.api.aws.stg.ldg-tech.com/_info",
];
24 changes: 10 additions & 14 deletions libs/hw-ledger-key-ring-protocol/src/ApduDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,20 +389,16 @@ export class APDU {
}
}

async function injectTrustedProperties(
function injectTrustedProperties(
command: Command,
properties: CommandResponse,
secret: Uint8Array,
): Promise<Command> {
): Command {
switch (command.getType()) {
case CommandType.Seed: {
const seedCommand = command as Seed;
const seedProperties = properties as SeedCommandResponse;
seedCommand.encryptedXpriv = await crypto.decrypt(
secret,
seedProperties.iv,
seedProperties.xpriv,
);
seedCommand.encryptedXpriv = crypto.decrypt(secret, seedProperties.iv, seedProperties.xpriv);
seedCommand.ephemeralPublicKey = seedProperties.ephemeralPublicKey;
seedCommand.initializationVector = seedProperties.commandIv;
seedCommand.groupKey = seedProperties.groupKey;
Expand All @@ -411,7 +407,7 @@ async function injectTrustedProperties(
case CommandType.Derive: {
const deriveCommand = command as Derive;
const deriveProperties = properties as SeedCommandResponse;
deriveCommand.encryptedXpriv = await crypto.decrypt(
deriveCommand.encryptedXpriv = crypto.decrypt(
secret,
deriveProperties.iv,
deriveProperties.xpriv,
Expand All @@ -428,7 +424,7 @@ async function injectTrustedProperties(
const publishKeyProperties = properties as PublishKeyCommandResponse;
publishKeyCommand.ephemeralPublicKey = publishKeyProperties.ephemeralPublicKey;
publishKeyCommand.initializationVector = publishKeyProperties.commandIv;
publishKeyCommand.encryptedXpriv = await crypto.decrypt(
publishKeyCommand.encryptedXpriv = crypto.decrypt(
secret,
publishKeyProperties.iv,
publishKeyProperties.xpriv,
Expand All @@ -449,7 +445,7 @@ findTrustedMember;

export class ApduDevice implements Device {
private transport: Transport;
private sessionKeyPair: Promise<KeyPair>;
private sessionKeyPair: KeyPair;

constructor(transport: Transport) {
this.transport = transport;
Expand All @@ -465,7 +461,7 @@ export class ApduDevice implements Device {
return new PublicKey(publicKey);
}

async getSeedId(data: Uint8Array): Promise<SeedIdResult> {
getSeedId(data: Uint8Array): Promise<SeedIdResult> {
return APDU.getSeedId(this.transport, data);
}

Expand Down Expand Up @@ -614,7 +610,7 @@ export class ApduDevice implements Device {
}

async sign(stream: CommandBlock[]): Promise<CommandBlock> {
const sessionKey = await this.sessionKeyPair;
const sessionKey = this.sessionKeyPair;
const trustedProperties: CommandResponse[] = [];

// We expect the stream to have a single block to sign (the last one)
Expand Down Expand Up @@ -649,8 +645,8 @@ export class ApduDevice implements Device {
const signature = await APDU.finalizeSignature(this.transport);

// Decrypt and inject trusted issuer
const secret = await crypto.ecdh(sessionKey, signature.sessionKey);
const issuer = await crypto.decrypt(secret, trustedIssuer.iv, trustedIssuer.issuer);
const secret = crypto.ecdh(sessionKey, signature.sessionKey);
const issuer = crypto.decrypt(secret, trustedIssuer.iv, trustedIssuer.issuer);

// Inject trusted properties for commands
for (let commandIndex = 0; commandIndex < blockToSign.commands.length; commandIndex++) {
Expand Down
25 changes: 11 additions & 14 deletions libs/hw-ledger-key-ring-protocol/src/CommandBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,14 +202,14 @@ export interface CommandBlock {
* @param parent The parent command block hash (if null, the block is the first block and a parent will be generated)
* @returns
*/
export async function createCommandBlock(
export function createCommandBlock(
issuer: Uint8Array,
commands: Command[],
signature: Uint8Array = new Uint8Array(),
parent: Uint8Array | null = null,
): Promise<CommandBlock> {
): CommandBlock {
if (parent === null) {
parent = parent = await crypto.randomBytes(32);
parent = parent = crypto.randomBytes(32);
}
return {
version: 1,
Expand All @@ -220,28 +220,25 @@ export async function createCommandBlock(
};
}

export async function signCommandBlock(
export function signCommandBlock(
block: CommandBlock,
issuer: Uint8Array,
secretKey: Uint8Array,
): Promise<CommandBlock> {
const signature = await crypto.sign(
await hashCommandBlock(block),
await crypto.keypairFromSecretKey(secretKey),
);
): CommandBlock {
const signature = crypto.sign(hashCommandBlock(block), crypto.keypairFromSecretKey(secretKey));
return {
...block,
signature,
};
}

export async function hashCommandBlock(block: CommandBlock): Promise<Uint8Array> {
return await crypto.hash(CommandStreamEncoder.encode([block]));
export function hashCommandBlock(block: CommandBlock): Uint8Array {
return crypto.hash(CommandStreamEncoder.encode([block]));
}

export async function verifyCommandBlock(block: CommandBlock): Promise<boolean> {
export function verifyCommandBlock(block: CommandBlock): boolean {
const unsignedBlock = { ...block };
unsignedBlock.signature = new Uint8Array();
const hash = await hashCommandBlock(unsignedBlock);
return await crypto.verify(hash, block.signature, block.issuer);
const hash = hashCommandBlock(unsignedBlock);
return crypto.verify(hash, block.signature, block.issuer);
}
14 changes: 6 additions & 8 deletions libs/hw-ledger-key-ring-protocol/src/CommandStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,9 @@ export class CommandStreamIssuer {
): Promise<CommandStream> {
const lastBlockHash =
this._stream.blocks.length > 0
? await hashCommandBlock(this._stream.blocks[this._stream.blocks.length - 1])
? hashCommandBlock(this._stream.blocks[this._stream.blocks.length - 1])
: null;
const block = await createCommandBlock(
const block = createCommandBlock(
ISSUER_PLACEHOLDER,
[],
new Uint8Array(),
Expand Down Expand Up @@ -167,7 +167,7 @@ export default class CommandStream {
return CommandStreamResolver.resolve(this._blocks);
}

public getRootHash(): Promise<Uint8Array> {
public getRootHash(): Uint8Array {
return hashCommandBlock(this._blocks[0]);
}

Expand Down Expand Up @@ -211,7 +211,7 @@ export default class CommandStream {
if (block.commands[0].getType() === CommandType.Derive) {
// Set the parent hash of the block to the root hash
const b = { ...block };
b.parent = await hashCommandBlock(stream[0]);
b.parent = hashCommandBlock(stream[0]);
stream = stream.concat([b]);
} else {
stream = stream.concat([block]);
Expand All @@ -227,10 +227,8 @@ export default class CommandStream {
parentHash: Uint8Array | null = null,
): Promise<CommandStream> {
const lastBlockHash =
this._blocks.length > 0
? await hashCommandBlock(this._blocks[this._blocks.length - 1])
: null;
const block = await createCommandBlock(
this._blocks.length > 0 ? hashCommandBlock(this._blocks[this._blocks.length - 1]) : null;
const block = createCommandBlock(
ISSUER_PLACEHOLDER,
commands,
new Uint8Array(),
Expand Down
22 changes: 11 additions & 11 deletions libs/hw-ledger-key-ring-protocol/src/Crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ export interface KeyPairWithChainCode extends KeyPair {
*
*/
export interface Crypto {
randomKeypair(): Promise<KeyPair>;
keypairFromSecretKey(secretKey: Uint8Array): Promise<KeyPair>;
sign(message: Uint8Array, keyPair: KeyPair): Promise<Uint8Array>;
verify(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): Promise<boolean>;
encrypt(secret: Uint8Array, nonce: Uint8Array, message: Uint8Array): Promise<Uint8Array>;
decrypt(secret: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array>;
randomBytes(size: number): Promise<Uint8Array>;
ecdh(keyPair: KeyPair, publicKey: Uint8Array): Promise<Uint8Array>;
hash(message: Uint8Array): Promise<Uint8Array>;
computeSymmetricKey(privateKey: Uint8Array, extra: Uint8Array): Promise<Uint8Array>;
randomKeypair(): KeyPair;
keypairFromSecretKey(secretKey: Uint8Array): KeyPair;
sign(message: Uint8Array, keyPair: KeyPair): Uint8Array;
verify(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean;
encrypt(secret: Uint8Array, nonce: Uint8Array, message: Uint8Array): Uint8Array;
decrypt(secret: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array): Uint8Array;
randomBytes(size: number): Uint8Array;
ecdh(keyPair: KeyPair, publicKey: Uint8Array): Uint8Array;
hash(message: Uint8Array): Uint8Array;
computeSymmetricKey(privateKey: Uint8Array, extra: Uint8Array): Uint8Array;
from_hex(hex: string): Uint8Array;
to_hex(bytes?: Uint8Array): string;
derivePrivate(xpriv: Uint8Array, path: number[]): Promise<KeyPairWithChainCode>;
derivePrivate(xpriv: Uint8Array, path: number[]): KeyPairWithChainCode;
}

export class DerivationPath {
Expand Down
Loading
Loading