diff --git a/packages/connect/e2e/__fixtures__/index.ts b/packages/connect/e2e/__fixtures__/index.ts index e86c1ccb5a7..f9df6ba282a 100644 --- a/packages/connect/e2e/__fixtures__/index.ts +++ b/packages/connect/e2e/__fixtures__/index.ts @@ -90,6 +90,5 @@ export { default as loadDevice } from './loadDevice'; // pushTransaction // recoveryDevice // requestLogin -// resetDevice // setProxy // wipeDevice diff --git a/packages/connect/e2e/__fixtures__/resetDevice.ts b/packages/connect/e2e/__fixtures__/resetDevice.ts deleted file mode 100644 index 65b19b39d8e..00000000000 --- a/packages/connect/e2e/__fixtures__/resetDevice.ts +++ /dev/null @@ -1,18 +0,0 @@ -export default { - method: 'resetDevice', - setup: { - wipe: true, - }, - // todo: can't run multiple resets, will get already initialized. maybe change beforeAll to beforeEach? - tests: [ - { - description: 'Reset device', - params: { - skipBackup: true, - }, - result: { - message: 'Initialized', - }, - }, - ], -}; diff --git a/packages/connect/e2e/tests/device/resetDevice.test.ts b/packages/connect/e2e/tests/device/resetDevice.test.ts new file mode 100644 index 00000000000..f04fbeb8778 --- /dev/null +++ b/packages/connect/e2e/tests/device/resetDevice.test.ts @@ -0,0 +1,69 @@ +import TrezorConnect from '../../../src'; +import { getController, setup, initTrezorConnect } from '../../common.setup'; + +const controller = getController(); + +describe('TrezorConnect.resetDevice', () => { + beforeAll(async () => { + await initTrezorConnect(controller); + }); + + beforeEach(async () => { + await setup(controller, { + wiped: true, + }); + }); + + afterAll(() => { + controller.dispose(); + TrezorConnect.dispose(); + }); + + it('resetDevice Bip39', async () => { + const response = await TrezorConnect.resetDevice({ + skip_backup: true, + backup_type: 0, + }); + expect(response.success).toEqual(true); + }); + + it('resetDevice Slip39_Basic', async () => { + const response = await TrezorConnect.resetDevice({ + skip_backup: true, + backup_type: 1, + }); + expect(response.success).toEqual(true); + }); + + it('resetDevice Slip39_Basic_Extendable', async () => { + const response = await TrezorConnect.resetDevice({ + skip_backup: true, + backup_type: 4, + }); + expect(response.success).toEqual(true); + }); + + it('resetDevice Slip39_Advanced', async () => { + const response = await TrezorConnect.resetDevice({ + skip_backup: true, + backup_type: 2, + }); + expect(response.success).toEqual(true); + }); + + it('resetDevice Slip39_Advanced_Extendable', async () => { + const response = await TrezorConnect.resetDevice({ + skip_backup: true, + backup_type: 5, + }); + expect(response.success).toEqual(true); + }); + + it('resetDevice Slip39_Single_Extendable', async () => { + const response = await TrezorConnect.resetDevice({ + skip_backup: true, + backup_type: 3, + }); + expect(response.success).toEqual(true); + }); +}); diff --git a/packages/connect/package.json b/packages/connect/package.json index 6c274c7cf79..c5bc0ffe11c 100644 --- a/packages/connect/package.json +++ b/packages/connect/package.json @@ -72,6 +72,7 @@ "@ethereumjs/common": "^4.4.0", "@ethereumjs/tx": "^5.4.0", "@fivebinaries/coin-selection": "3.0.0", + "@noble/hashes": "^1.6.1", "@trezor/blockchain-link": "workspace:*", "@trezor/blockchain-link-types": "workspace:*", "@trezor/connect-analytics": "workspace:*", @@ -82,6 +83,7 @@ "@trezor/transport": "workspace:*", "@trezor/utils": "workspace:*", "@trezor/utxo-lib": "workspace:*", + "bip39": "^3.1.0", "blakejs": "^1.2.1", "bs58": "^6.0.0", "bs58check": "^4.0.0", diff --git a/packages/connect/src/api/firmware/__tests__/verifyEntropy.test.ts b/packages/connect/src/api/firmware/__tests__/verifyEntropy.test.ts new file mode 100644 index 00000000000..c6509d2c8a2 --- /dev/null +++ b/packages/connect/src/api/firmware/__tests__/verifyEntropy.test.ts @@ -0,0 +1,36 @@ +import { verifyEntropy } from '../verifyEntropy'; + +describe('firmware/verifyEntropy', () => { + it('bip39 success', async () => { + const response = await verifyEntropy({ + strength: 256, + hostEntropy: 'e14806194511f95f2e6b7c5267fcb824469a478007d97339da08abb379244553', + commitment: '09c7dff5c85814852fb9cb12feecd11183f7af1c10296a5075f276c5eca9fb44', + trezorEntropy: 'ffa4581852ee93e789b9e83554f58dd1a3e09765a64d91d8cd08f6b5c813745d', + xpubs: { + "m/84'/0'/0'": + 'xpub6CCMQserNP7QkjspvUVWfCdjK1FcgFdmka3ZgzVgZKqkkCL5bfQoscxZ9UzTLLLedPGwkhQobEGE84gWvZY1tXaHJsVLMHA6cXNUmXnrj3s', + "m/44'/60'/0'": + 'xpub6Cdh8AtW8tSXTDYYGirGkmTCgYe4SCCAuqJLmqkhDFYVCkHLngQ7JmNSVuCQuQARqx5tDJJ9my1JCgaUHHizioZZoXRWw6LR95uQUkbKJi3', + }, + }); + expect(response.success).toEqual(true); + }); + + it('slip39 success', async () => { + const response = await verifyEntropy({ + type: 1, + strength: 256, + hostEntropy: '20e1524b5ea128b581c8882ddd5b030dd54e3bf49c8e7063768be13e0add4420', + commitment: '0ea37a3ae4e765ac59f6d721548920c92667bb9cc09b53ebf81404d3c07794a1', + trezorEntropy: '00610ab95fe09b3d32320662e96ba195344461b00322a09b86df68432db1e745', + xpubs: { + "m/84'/0'/0'": + 'xpub6CxDGHMZekeQtFmny74NHcf1cA8opN8yWHLdmXhwhin7WrjCWKgypDyG5SoCR7ae57JqPT8ZWd2st56yzgC8bzzpHRDurXxZxkaZfXeF1bW', + "m/44'/60'/0'": + 'xpub6CV17nmnkijMua6ZpyRU7MNnZjHoByRGoWf2nPxJmKF1EriH92awnhV7KS2X1mB6ke1fuRerGir3kvZr6uRcQqn2Pnv48gmhtsyaHcLALVG', + }, + }); + expect(response.success).toEqual(true); + }); +}); diff --git a/packages/connect/src/api/firmware/verifyEntropy.ts b/packages/connect/src/api/firmware/verifyEntropy.ts new file mode 100644 index 00000000000..355a77fea0e --- /dev/null +++ b/packages/connect/src/api/firmware/verifyEntropy.ts @@ -0,0 +1,167 @@ +import { entropyToMnemonic, mnemonicToSeed } from 'bip39'; +import { hmac } from '@noble/hashes/hmac'; +import { crypto } from '@noble/hashes/crypto'; +import { sha256 } from '@noble/hashes/sha256'; +import { randomBytes } from '@noble/hashes/utils'; + +import { bip32 } from '@trezor/utxo-lib'; + +import { PROTO } from '../../constants'; + +export const generateEntropy = (len: number) => { + try { + return Buffer.from(randomBytes(len)); + } catch { + throw new Error('generateEntropy: Environment does not support crypto random'); + } +}; + +// https://github.com/trezor/python-shamir-mnemonic/blob/master/shamir_mnemonic/cipher.py +const BASE_ITERATION_COUNT = 10000; +const ROUND_COUNT = 4; + +// https://github.com/trezor/python-shamir-mnemonic/blob/master/shamir_mnemonic/cipher.py +const roundFunction = async (i: number, passphrase: Buffer, e: number, salt: Buffer, r: Buffer) => { + const data = Buffer.concat([Buffer.from([i]), passphrase]); + const iterations = Math.floor((BASE_ITERATION_COUNT << e) / ROUND_COUNT); + + // '@noble/hashes/pbkdf2' takes ~ 8sec. in the web build + // const result = pbkdf2(sha256, data, Buffer.concat([salt, r]), { + // c: iterations, + // dkLen: r.length, + // }); + + // Nodejs only + // return crypto.pbkdf2Sync(data, Buffer.concat([salt, r]), iterations, r.length, 'sha256'); + + // Nodejs + WebCrypto equivalent + const { subtle } = crypto as Crypto; + const key = await subtle.importKey('raw', data, 'PBKDF2', false, ['deriveBits']); + const bits = await subtle.deriveBits( + { + name: 'PBKDF2', + hash: 'SHA-256', + salt: Buffer.concat([salt, r]), + iterations, + }, + key, + r.length * 8, + ); + + return Buffer.from(bits); +}; + +// https://github.com/trezor/python-shamir-mnemonic/blob/master/shamir_mnemonic/cipher.py +const xor = (a: Buffer, b: Buffer) => { + if (a.length !== b.length) { + throw new Error('Buffers must be of equal length to XOR.'); + } + const result = Buffer.alloc(a.length); + for (let i = 0; i < a.length; i++) { + result[i] = a[i] ^ b[i]; + } + + return result; +}; + +// https://github.com/trezor/python-shamir-mnemonic/blob/master/shamir_mnemonic/cipher.py +// simplified "decrypt" function +const entropyToSeedSlip39 = async (encryptedSecret: Buffer) => { + const iterationExponent = 1; + // const identifier = 0; + // const extendable = true, + const passphrase = Buffer.from('', 'utf-8'); // empty passphrase + const salt = Buffer.alloc(0); // extendable: True => no salt + + const half = Math.floor(encryptedSecret.length / 2); + let l = encryptedSecret.subarray(0, half); + let r = encryptedSecret.subarray(half); + for (let round = ROUND_COUNT - 1; round >= 0; round--) { + const f = await roundFunction(round, passphrase, iterationExponent, salt, r); + const rr = xor(l, f); + l = r; + r = rr; + } + + return Buffer.concat([r, l]); +}; + +const getEntropy = (trezorEntropy: string, hostEntropy: string, strength: number) => { + const data = Buffer.concat([ + Buffer.from(trezorEntropy, 'hex'), + Buffer.from(hostEntropy, 'hex'), + ]); + const entropy = sha256(data); + + return Buffer.from(entropy.subarray(0, Math.floor(strength / 8))); +}; + +const computeSeed = (type: VerifyEntropyOptions['type'], secret: Buffer) => { + const BackupType = PROTO.Enum_BackupType; + if ( + type && + [ + BackupType.Slip39_Basic, + BackupType.Slip39_Advanced, + BackupType.Slip39_Single_Extendable, + BackupType.Slip39_Basic_Extendable, + BackupType.Slip39_Advanced_Extendable, + ].includes(type) + ) { + // use slip39 + return entropyToSeedSlip39(secret); + } + + // use bip39 + return mnemonicToSeed(entropyToMnemonic(secret)); +}; + +const verifyCommitment = (entropy: string, commitment: string) => { + const hmacDigest = hmac(sha256, Buffer.from(entropy, 'hex'), Buffer.alloc(0)); + if (!Buffer.from(hmacDigest).equals(Buffer.from(commitment, 'hex'))) { + throw new Error('Invalid entropy commitment'); + } +}; + +type VerifyEntropyOptions = { + type?: PROTO.Enum_BackupType; // ResetDevice.backup_type + strength?: number; // ResetDevice.strength + commitment?: string; // entropy_commitment received from previous EntropyRequest + hostEntropy: string; // host_entropy used in previous EntropyAck + trezorEntropy?: string; // prev_entropy received from current EntropyRequest, after ResetDeviceContinue + xpubs: Record; // +}; + +export const verifyEntropy = async ({ + type, + strength, + trezorEntropy, + hostEntropy, + commitment, + xpubs, +}: VerifyEntropyOptions) => { + try { + if (!trezorEntropy || !commitment || !strength || Object.keys(xpubs).length < 1) { + throw new Error('Missing verifyEntropy data'); + } + + verifyCommitment(trezorEntropy, commitment); + // compute seed + const secret = getEntropy(trezorEntropy, hostEntropy, strength); + const seed = await computeSeed(type, secret); + + // derive xpubs and compare with FW results + const node = bip32.fromSeed(seed); + Object.keys(xpubs).forEach(path => { + const pubKey = node.derivePath(path); + const xpub = pubKey.neutered().toBase58(); + if (xpub !== xpubs[path]) { + throw new Error('verifyEntropy xpub mismatch'); + } + }); + + return { success: true as const }; + } catch (error) { + return { success: false as const, error: error.message }; + } +}; diff --git a/packages/connect/src/api/resetDevice.ts b/packages/connect/src/api/resetDevice.ts index d19f695c271..22938ed1be6 100644 --- a/packages/connect/src/api/resetDevice.ts +++ b/packages/connect/src/api/resetDevice.ts @@ -1,11 +1,15 @@ // origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/ResetDevice.js - import { Assert } from '@trezor/schema-utils'; +import { getRandomInt } from '@trezor/utils'; import { AbstractMethod } from '../core/AbstractMethod'; import { UI } from '../events'; import { getFirmwareRange } from './common/paramsValidator'; -import { PROTO } from '../constants'; +import { PROTO, ERRORS } from '../constants'; +import { validatePath } from '../utils/pathUtils'; +import { generateEntropy, verifyEntropy } from '../api/firmware/verifyEntropy'; + +type EntropyRequestData = PROTO.EntropyRequest & { host_entropy: string }; export default class ResetDevice extends AbstractMethod<'resetDevice', PROTO.ResetDevice> { init() { @@ -28,6 +32,8 @@ export default class ResetDevice extends AbstractMethod<'resetDevice', PROTO.Res skip_backup: payload.skip_backup, no_backup: payload.no_backup, backup_type: payload.backup_type, + entropy_check: + typeof payload.entropy_check === 'boolean' ? payload.entropy_check : true, }; } @@ -42,10 +48,75 @@ export default class ResetDevice extends AbstractMethod<'resetDevice', PROTO.Res }; } + // generate host_entropy and then call workflow: + // - ResetDevice > EntropyRequest > EntropyAck + // - EntropyCheckContinue > EntropyRequest > EntropyAck + private async getEntropyData( + type: 'ResetDevice' | 'EntropyCheckContinue', + ): Promise { + const cmd = this.device.getCommands(); + const entropy = generateEntropy(32).toString('hex'); + const params = type === 'ResetDevice' ? this.params : {}; + const entropyRequest = await cmd.typedCall(type, 'EntropyRequest', params); + // EntropyAck > Success if this.params.entropy_check === false + await cmd.typedCall('EntropyAck', ['EntropyCheckReady', 'Success'], { entropy }); + + return { + ...entropyRequest.message, + host_entropy: entropy, + }; + } + + private async entropyCheck(prevData: EntropyRequestData): Promise { + const cmd = this.device.getCommands(); + const paths = ["m/84'/0'/0'", "m/44'/60'/0'"]; + const xpubs: Record = {}; // + for (let i = 0; i < paths.length; i++) { + const p = paths[i]; + const pubKey = await cmd.getPublicKey({ address_n: validatePath(p) }); + xpubs[p] = pubKey.xpub; + } + + const currentData = await this.getEntropyData('EntropyCheckContinue'); + const res = await verifyEntropy({ + type: this.params.backup_type, + strength: this.params.strength, + commitment: prevData.entropy_commitment, + hostEntropy: prevData.host_entropy, + trezorEntropy: currentData.prev_entropy, + xpubs, + }); + if (res.error) { + throw ERRORS.TypedError('Failure_EntropyCheck', res.error); + } + + return currentData; + } + async run() { const cmd = this.device.getCommands(); - const response = await cmd.typedCall('ResetDevice', 'Success', this.params); - return response.message; + if (this.params.entropy_check && this.device.unavailableCapabilities['entropyCheck']) { + // entropy check requested but not supported by the firmware + this.params.entropy_check = false; + } + // Entropy check workflow: + // https://github.com/trezor/trezor-firmware/blob/57868ad48f4c462bb1f4fa57572067e89a039a60/docs/common/message-workflows.md#entropy-check-workflow + // steps: 1 - 4 + // ResetDevice > EntropyRequest > EntropyAck > EntropyCheckReady (new fw) || Success (old fw) + let entropyData = await this.getEntropyData('ResetDevice'); + + if (this.params.entropy_check) { + const tries = getRandomInt(1, 5); + for (let i = 0; i < tries; i++) { + // steps: 5 - 6 + // GetPublicKey > ResetDeviceContinue > EntropyRequest > EntropyAck > EntropyCheckReady + entropyData = await this.entropyCheck(entropyData); + } + // step 7 EntropyCheckContinue > Success + await cmd.typedCall('EntropyCheckContinue', 'Success', { finish: true }); + } + + return { message: 'Success' }; } } diff --git a/packages/connect/src/constants/errors.ts b/packages/connect/src/constants/errors.ts index a263a439e62..6c43740b6a3 100644 --- a/packages/connect/src/constants/errors.ts +++ b/packages/connect/src/constants/errors.ts @@ -52,6 +52,7 @@ export const ERROR_CODES = { Failure_PinInvalid: 'PIN invalid', Failure_PinMismatch: 'PIN mismatch', Failure_WipeCodeMismatch: 'Wipe code mismatch', + Failure_EntropyCheck: '', // message from verifyEntropy process Deeplink_VersionMismatch: 'Not compatible with current version of the app', } as const; diff --git a/packages/connect/src/data/config.ts b/packages/connect/src/data/config.ts index 93d17614379..96018139c27 100644 --- a/packages/connect/src/data/config.ts +++ b/packages/connect/src/data/config.ts @@ -259,5 +259,9 @@ export const config = { T2B1: '2.7.0', }, }, + { + capabilities: ['entropyCheck'], + min: { T1B1: '0', T2T1: '2.8.7', T2B1: '2.8.7' }, + }, ], }; diff --git a/packages/connect/src/device/DeviceCommands.ts b/packages/connect/src/device/DeviceCommands.ts index 9c714b706ce..e20d7cd5b4a 100644 --- a/packages/connect/src/device/DeviceCommands.ts +++ b/packages/connect/src/device/DeviceCommands.ts @@ -1,7 +1,5 @@ // original file https://github.com/trezor/connect/blob/develop/src/js/device/DeviceCommands.js -import { randomBytes } from 'crypto'; - import { Transport, Session } from '@trezor/transport'; import { MessagesSchema as Messages } from '@trezor/protobuf'; import { createTimeoutPromise, versionUtils } from '@trezor/utils'; @@ -43,17 +41,6 @@ const assertType = (res: DefaultPayloadMessage, resType: MessageKey | MessageKey } }; -const generateEntropy = (len: number) => { - try { - return randomBytes(len); - } catch { - throw ERRORS.TypedError( - 'Runtime', - 'generateEntropy: Environment does not support crypto random', - ); - } -}; - const filterForLog = (type: string, msg: any) => { const blacklist: { [key: string]: Record } = { // PassphraseAck: { @@ -431,12 +418,6 @@ export class DeviceCommands { return this._commonCall('ButtonAck', {}); } - if (res.type === 'EntropyRequest') { - return this._commonCall('EntropyAck', { - entropy: generateEntropy(32).toString('hex'), - }); - } - if (res.type === 'PinMatrixRequest') { return promptPin(this.device, res.message.type).then( pin => diff --git a/packages/connect/src/utils/__tests__/deviceFeaturesUtils.test.ts b/packages/connect/src/utils/__tests__/deviceFeaturesUtils.test.ts index 319a27cd1c6..8ca5d784b61 100644 --- a/packages/connect/src/utils/__tests__/deviceFeaturesUtils.test.ts +++ b/packages/connect/src/utils/__tests__/deviceFeaturesUtils.test.ts @@ -164,6 +164,7 @@ describe('utils/deviceFeaturesUtils', () => { coinjoin: 'update-required', signMessageNoScriptType: 'update-required', chunkify: 'no-support', + entropyCheck: 'no-support', }); }); @@ -193,6 +194,7 @@ describe('utils/deviceFeaturesUtils', () => { sol: 'no-capability', dsol: 'no-capability', chunkify: 'update-required', + entropyCheck: 'update-required', }); }); @@ -224,6 +226,7 @@ describe('utils/deviceFeaturesUtils', () => { vtc: 'no-support', xem: 'no-support', chunkify: 'update-required', + entropyCheck: 'update-required', }); }); @@ -307,6 +310,7 @@ describe('utils/deviceFeaturesUtils', () => { replaceTransaction: 'update-required', signMessageNoScriptType: 'update-required', taproot: 'update-required', + entropyCheck: 'update-required', }); }); @@ -338,6 +342,7 @@ describe('utils/deviceFeaturesUtils', () => { replaceTransaction: 'update-required', signMessageNoScriptType: 'update-required', taproot: 'update-required', + entropyCheck: 'no-support', }); }); @@ -370,6 +375,7 @@ describe('utils/deviceFeaturesUtils', () => { replaceTransaction: 'update-required', signMessageNoScriptType: 'update-required', taproot: 'update-required', + entropyCheck: 'no-support', }); }); }); diff --git a/packages/protobuf/messages.json b/packages/protobuf/messages.json index f5427b7c8d2..ecb398f5365 100644 --- a/packages/protobuf/messages.json +++ b/packages/protobuf/messages.json @@ -280,6 +280,12 @@ "SATOSHI": 3 } }, + "MultisigPubkeysOrder": { + "values": { + "PRESERVED": 0, + "LEXICOGRAPHIC": 1 + } + }, "MultisigRedeemScriptType": { "fields": { "pubkeys": { @@ -309,6 +315,13 @@ "options": { "packed": false } + }, + "pubkeys_order": { + "type": "MultisigPubkeysOrder", + "id": 6, + "options": { + "default": "PRESERVED" + } } }, "nested": { @@ -4860,6 +4873,10 @@ "options": { "default": "Bip39" } + }, + "entropy_check": { + "type": "bool", + "id": 11 } } }, @@ -4893,7 +4910,16 @@ } }, "EntropyRequest": { - "fields": {} + "fields": { + "entropy_commitment": { + "type": "bytes", + "id": 1 + }, + "prev_entropy": { + "type": "bytes", + "id": 2 + } + } }, "EntropyAck": { "fields": { @@ -4904,6 +4930,20 @@ } } }, + "EntropyCheckReady": { + "fields": {} + }, + "EntropyCheckContinue": { + "fields": { + "finish": { + "type": "bool", + "id": 1, + "options": { + "default": false + } + } + } + }, "RecoveryDevice": { "fields": { "word_count": { @@ -6713,6 +6753,8 @@ "BackupDevice": 34, "EntropyRequest": 35, "EntropyAck": 36, + "EntropyCheckReady": 994, + "EntropyCheckContinue": 995, "PassphraseRequest": 41, "PassphraseAck": 42, "RecoveryDevice": 45, diff --git a/packages/protobuf/src/messages-schema.ts b/packages/protobuf/src/messages-schema.ts index 84dd1d2d7dc..73622a0dc6d 100644 --- a/packages/protobuf/src/messages-schema.ts +++ b/packages/protobuf/src/messages-schema.ts @@ -206,6 +206,14 @@ export enum AmountUnit { export type EnumAmountUnit = Static; export const EnumAmountUnit = Type.Enum(AmountUnit); +export enum MultisigPubkeysOrder { + PRESERVED = 0, + LEXICOGRAPHIC = 1, +} + +export type EnumMultisigPubkeysOrder = Static; +export const EnumMultisigPubkeysOrder = Type.Enum(MultisigPubkeysOrder); + export type HDNodeType = Static; export const HDNodeType = Type.Object( { @@ -236,6 +244,7 @@ export const MultisigRedeemScriptType = Type.Object( m: Type.Number(), nodes: Type.Optional(Type.Array(HDNodeType)), address_n: Type.Optional(Type.Array(Type.Number())), + pubkeys_order: Type.Optional(EnumMultisigPubkeysOrder), }, { $id: 'MultisigRedeemScriptType' }, ); @@ -2572,6 +2581,7 @@ export const ResetDevice = Type.Object( skip_backup: Type.Optional(Type.Boolean()), no_backup: Type.Optional(Type.Boolean()), backup_type: Type.Optional(EnumEnum_BackupType), + entropy_check: Type.Optional(Type.Boolean()), }, { $id: 'ResetDevice' }, ); @@ -2595,7 +2605,13 @@ export const BackupDevice = Type.Object( ); export type EntropyRequest = Static; -export const EntropyRequest = Type.Object({}, { $id: 'EntropyRequest' }); +export const EntropyRequest = Type.Object( + { + entropy_commitment: Type.Optional(Type.String()), + prev_entropy: Type.Optional(Type.String()), + }, + { $id: 'EntropyRequest' }, +); export type EntropyAck = Static; export const EntropyAck = Type.Object( @@ -2605,6 +2621,17 @@ export const EntropyAck = Type.Object( { $id: 'EntropyAck' }, ); +export type EntropyCheckReady = Static; +export const EntropyCheckReady = Type.Object({}, { $id: 'EntropyCheckReady' }); + +export type EntropyCheckContinue = Static; +export const EntropyCheckContinue = Type.Object( + { + finish: Type.Optional(Type.Boolean()), + }, + { $id: 'EntropyCheckContinue' }, +); + export enum Enum_WordRequestType { WordRequestType_Plain = 0, WordRequestType_Matrix9 = 1, @@ -3707,6 +3734,8 @@ export const MessageType = Type.Object( BackupDevice, EntropyRequest, EntropyAck, + EntropyCheckReady, + EntropyCheckContinue, WordRequest, WordAck, SetU2FCounter, diff --git a/packages/protobuf/src/messages.ts b/packages/protobuf/src/messages.ts index 7159c1d683a..7bd86e1bcc3 100644 --- a/packages/protobuf/src/messages.ts +++ b/packages/protobuf/src/messages.ts @@ -141,6 +141,11 @@ export enum AmountUnit { SATOSHI = 3, } +export enum MultisigPubkeysOrder { + PRESERVED = 0, + LEXICOGRAPHIC = 1, +} + export type HDNodeType = { depth: number; fingerprint: number; @@ -161,6 +166,7 @@ export type MultisigRedeemScriptType = { m: number; nodes?: HDNodeType[]; address_n?: number[]; + pubkeys_order?: MultisigPubkeysOrder; }; export type GetPublicKey = { @@ -1687,6 +1693,7 @@ export type ResetDevice = { skip_backup?: boolean; no_backup?: boolean; backup_type?: Enum_BackupType; + entropy_check?: boolean; }; export type Slip39Group = { @@ -1699,12 +1706,21 @@ export type BackupDevice = { groups?: Slip39Group[]; }; -export type EntropyRequest = {}; +export type EntropyRequest = { + entropy_commitment?: string; + prev_entropy?: string; +}; export type EntropyAck = { entropy: string; }; +export type EntropyCheckReady = {}; + +export type EntropyCheckContinue = { + finish?: boolean; +}; + export enum Enum_WordRequestType { WordRequestType_Plain = 0, WordRequestType_Matrix9 = 1, @@ -2469,6 +2485,8 @@ export type MessageType = { BackupDevice: BackupDevice; EntropyRequest: EntropyRequest; EntropyAck: EntropyAck; + EntropyCheckReady: EntropyCheckReady; + EntropyCheckContinue: EntropyCheckContinue; WordRequest: WordRequest; WordAck: WordAck; SetU2FCounter: SetU2FCounter; diff --git a/scripts/ci/connect-test-matrix-generator.js b/scripts/ci/connect-test-matrix-generator.js index e74bd5cb6e1..f0373a0aa86 100644 --- a/scripts/ci/connect-test-matrix-generator.js +++ b/scripts/ci/connect-test-matrix-generator.js @@ -12,7 +12,7 @@ const groups = { api: { name: 'api', pattern: - 'authorizeCoinjoin cancelCoinjoinAuthorization passphrase unlockPath setBusy checkFirmwareAuthenticity keepSession cancel.test info.test', + 'authorizeCoinjoin cancelCoinjoinAuthorization passphrase unlockPath setBusy checkFirmwareAuthenticity keepSession cancel.test info.test resetDevice', includeFilter: '', }, // temporarily created group for flaky test - to spend less time on reruns and to make test result in CI more readable without investigating long logs diff --git a/scripts/list-outdated-dependencies/connect-dependencies.txt b/scripts/list-outdated-dependencies/connect-dependencies.txt index 2b642fb6543..da7527930c1 100644 --- a/scripts/list-outdated-dependencies/connect-dependencies.txt +++ b/scripts/list-outdated-dependencies/connect-dependencies.txt @@ -95,4 +95,6 @@ jest-extended jest-environment-node scroll-into-view-if-needed @types/bn.js -@types/events \ No newline at end of file +@types/events +bip39 +@noble/hashes \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 40bbdee6305..d08687718f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4881,13 +4881,20 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.4.0, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:~1.4.0": +"@noble/hashes@npm:1.4.0, @noble/hashes@npm:~1.4.0": version: 1.4.0 resolution: "@noble/hashes@npm:1.4.0" checksum: 10/e156e65794c473794c52fa9d06baf1eb20903d0d96719530f523cc4450f6c721a957c544796e6efd0197b2296e7cd70efeb312f861465e17940a3e3c7e0febc6 languageName: node linkType: hard +"@noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:^1.6.1": + version: 1.6.1 + resolution: "@noble/hashes@npm:1.6.1" + checksum: 10/74d9ad7b1437a22ba3b877584add3367587fbf818113152f293025d20d425aa74c191d18d434797312f2270458bc9ab3241c34d14ec6115fb16438b3248f631f + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -11798,6 +11805,7 @@ __metadata: "@ethereumjs/common": "npm:^4.4.0" "@ethereumjs/tx": "npm:^5.4.0" "@fivebinaries/coin-selection": "npm:3.0.0" + "@noble/hashes": "npm:^1.6.1" "@trezor/blockchain-link": "workspace:*" "@trezor/blockchain-link-types": "workspace:*" "@trezor/connect-analytics": "workspace:*" @@ -11812,6 +11820,7 @@ __metadata: "@trezor/utxo-lib": "workspace:*" "@types/karma": "npm:^6.3.8" babel-loader: "npm:^9.1.3" + bip39: "npm:^3.1.0" blakejs: "npm:^1.2.1" bs58: "npm:^6.0.0" bs58check: "npm:^4.0.0" @@ -16386,6 +16395,15 @@ __metadata: languageName: node linkType: hard +"bip39@npm:^3.1.0": + version: 3.1.0 + resolution: "bip39@npm:3.1.0" + dependencies: + "@noble/hashes": "npm:^1.2.0" + checksum: 10/406c0b5bdab0d43df2ff8916c5b57bb7f65cd36a115cbd7620a75b972638f8511840bfc27bfc68946027086fd33ed3050d8cd304e85fd527503b23d857a8486e + languageName: node + linkType: hard + "bip66@npm:^2.0.0": version: 2.0.0 resolution: "bip66@npm:2.0.0"