diff --git a/package.json b/package.json index fe618d1759..bc29b00c0a 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "type": "git", "url": "https://github.com/LedgerHQ/ledger-live-common" }, - "version": "21.32.3", + "version": "21.32.4", "main": "lib/index.js", "types": "lib/index.d.ts", "license": "Apache-2.0", diff --git a/src/__tests__/__snapshots__/all.libcore.ts.snap b/src/__tests__/__snapshots__/all.libcore.ts.snap index a4796f9406..a676be54a9 100644 --- a/src/__tests__/__snapshots__/all.libcore.ts.snap +++ b/src/__tests__/__snapshots__/all.libcore.ts.snap @@ -28707,6 +28707,115 @@ Array [ ] `; +exports[`solana currency bridge scanAccounts solana seed 1 1`] = ` +Array [ + Object { + "balance": "83389840", + "currencyId": "solana", + "derivationMode": "solanaMain", + "freshAddress": "AQbkEagmPgmsdAfS4X8V8UyJnXXjVPMvjeD15etqQ3Jh", + "freshAddressPath": "44'/501'", + "freshAddresses": Array [ + Object { + "address": "AQbkEagmPgmsdAfS4X8V8UyJnXXjVPMvjeD15etqQ3Jh", + "derivationPath": "44'/501'", + }, + ], + "id": "js:2:solana:AQbkEagmPgmsdAfS4X8V8UyJnXXjVPMvjeD15etqQ3Jh:solanaMain", + "index": 0, + "name": "Solana 1", + "nfts": undefined, + "operationsCount": 2, + "pendingOperations": Array [], + "seedIdentifier": "AQbkEagmPgmsdAfS4X8V8UyJnXXjVPMvjeD15etqQ3Jh", + "spendableBalance": "83389840", + "starred": false, + "swapHistory": Array [], + "syncHash": undefined, + "unitMagnitude": 9, + "used": true, + }, + Object { + "balance": "0", + "currencyId": "solana", + "derivationMode": "solanaSub", + "freshAddress": "6rEgdtB3sgjKJnRE172YEr9z6qUyr4nFW28vJokuD36A", + "freshAddressPath": "44'/501'/0'", + "freshAddresses": Array [ + Object { + "address": "6rEgdtB3sgjKJnRE172YEr9z6qUyr4nFW28vJokuD36A", + "derivationPath": "44'/501'/0'", + }, + ], + "id": "js:2:solana:6rEgdtB3sgjKJnRE172YEr9z6qUyr4nFW28vJokuD36A:solanaSub", + "index": 0, + "name": "Solana 1", + "nfts": undefined, + "operationsCount": 0, + "pendingOperations": Array [], + "seedIdentifier": "AQbkEagmPgmsdAfS4X8V8UyJnXXjVPMvjeD15etqQ3Jh", + "spendableBalance": "0", + "starred": false, + "swapHistory": Array [], + "syncHash": undefined, + "unitMagnitude": 9, + "used": false, + }, +] +`; + +exports[`solana currency bridge scanAccounts solana seed 1 2`] = ` +Array [ + Array [ + Object { + "accountId": "js:2:solana:AQbkEagmPgmsdAfS4X8V8UyJnXXjVPMvjeD15etqQ3Jh:solanaMain", + "blockHash": "4NSL4VrfWd2eUccMD95dLQsdy5UGz8yhokpfH1et1R2c", + "blockHeight": 108520722, + "contract": undefined, + "extra": Object {}, + "fee": "0", + "hasFailed": false, + "hash": "25KWBvKtVgKR3yoRmozTY6wmiW8atwrnzAnTXdsms8jqg5aR8GnCDxdJzWXtzMZPvbsE6SUuBkGFXudy2mrcTYna", + "id": "js:2:solana:AQbkEagmPgmsdAfS4X8V8UyJnXXjVPMvjeD15etqQ3Jh:solanaMain-25KWBvKtVgKR3yoRmozTY6wmiW8atwrnzAnTXdsms8jqg5aR8GnCDxdJzWXtzMZPvbsE6SUuBkGFXudy2mrcTYna-IN", + "operator": undefined, + "recipients": Array [ + "AQbkEagmPgmsdAfS4X8V8UyJnXXjVPMvjeD15etqQ3Jh", + ], + "senders": Array [ + "7CZgkK494jMdoY8xpXY3ViLjpDGMbNikCzMtAT5cAjKk", + ], + "standard": undefined, + "tokenId": undefined, + "type": "IN", + "value": "93394840", + }, + Object { + "accountId": "js:2:solana:AQbkEagmPgmsdAfS4X8V8UyJnXXjVPMvjeD15etqQ3Jh:solanaMain", + "blockHash": "9tPbgLaETEenufCt5SzXMuWijgFJj549W9j5cJLbaogn", + "blockHeight": 108521109, + "contract": undefined, + "extra": Object {}, + "fee": "5000", + "hasFailed": false, + "hash": "A29zPnK1jPr2tGziTnaAvSnadYR2kLCv9sPywj9FJsaEFjtpwmUonspN3WJgz4u6XWmjtVpoFsDrygEnvW51cgk", + "id": "js:2:solana:AQbkEagmPgmsdAfS4X8V8UyJnXXjVPMvjeD15etqQ3Jh:solanaMain-A29zPnK1jPr2tGziTnaAvSnadYR2kLCv9sPywj9FJsaEFjtpwmUonspN3WJgz4u6XWmjtVpoFsDrygEnvW51cgk-OUT", + "operator": undefined, + "recipients": Array [ + "8RtwWeqdFz4EFuZU3MAadfYMWSdRMamjFrfq6BXkHuNN", + ], + "senders": Array [ + "AQbkEagmPgmsdAfS4X8V8UyJnXXjVPMvjeD15etqQ3Jh", + ], + "standard": undefined, + "tokenId": undefined, + "type": "OUT", + "value": "10005000", + }, + ], + Array [], +] +`; + exports[`stellar currency bridge scanAccounts stellar seed 1 1`] = ` Array [ Object { diff --git a/src/__tests__/families/bitcoin/wallet-btc/wallet.integration.test.ts b/src/__tests__/families/bitcoin/wallet-btc/wallet.integration.test.ts index e59eb0b7e5..602ec11ea8 100644 --- a/src/__tests__/families/bitcoin/wallet-btc/wallet.integration.test.ts +++ b/src/__tests__/families/bitcoin/wallet-btc/wallet.integration.test.ts @@ -5,6 +5,8 @@ import { Account } from "../../../../families/bitcoin/wallet-btc/account"; import { Merge } from "../../../../families/bitcoin/wallet-btc/pickingstrategies/Merge"; import MockBtc from "../../../../mock/Btc"; +jest.setTimeout(180000); + describe("testing wallet", () => { const wallet = new BitcoinLikeWallet(); let account: Account; diff --git a/src/__tests__/families/bitcoin/wallet-btc/xpub.getAddress.ts b/src/__tests__/families/bitcoin/wallet-btc/xpub.getAddress.ts index eba69fe2c6..a4aea43ff0 100644 --- a/src/__tests__/families/bitcoin/wallet-btc/xpub.getAddress.ts +++ b/src/__tests__/families/bitcoin/wallet-btc/xpub.getAddress.ts @@ -5,6 +5,8 @@ import Xpub from "../../../../families/bitcoin/wallet-btc/xpub"; import coininfo from "coininfo"; import BCHCrypto from "../../../../families/bitcoin/wallet-btc/crypto/bitcoincash"; import BTCCrypto from "../../../../families/bitcoin/wallet-btc/crypto/bitcoin"; +import ZECCrypto from "../../../../families/bitcoin/wallet-btc/crypto/zec"; +import ZENCrypto from "../../../../families/bitcoin/wallet-btc/crypto/zen"; describe("Unit tests for getAddress", () => { it("Test getAddress for bch and btc", async () => { @@ -44,4 +46,33 @@ describe("Unit tests for getAddress", () => { addresses = await btcxpub.getXpubAddresses(); expect(addresses[0].address).toEqual("1L3fqoWstvLqEA6TgXkuLoXX8xG1xhirG3"); }, 60000); + + it("Test getoutputScriptFromAddress for btc, zcash and zen", async () => { + const btcCrypto = new BTCCrypto({ + network: coininfo.bitcoin.main.toBitcoinJS(), + }); + const zecCrypto = new ZECCrypto({ + network: coininfo.zcash.main.toBitcoinJS(), + }); + const zenCrypto = new ZENCrypto({ + network: coininfo.zcash.main.toBitcoinJS(), + }); + expect( + btcCrypto + .toOutputScript("1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX") + .toString("hex") + ).toEqual("76a91499bc78ba577a95a11f1a344d4d2ae55f2f857b9888ac"); + expect( + zecCrypto + .toOutputScript("t1T5XJvzQhh2gTsi3c5Vn9x5SMhpSWLSnVy") + .toString("hex") + ).toEqual("76a91464fa33fb6f8d72455af2a4e73ae30412af2c97ba88ac"); + expect( + zenCrypto + .toOutputScript("znjbHth4PxBJM8FmHgvXYHkuq99nKFkWvMg") + .toString("hex") + ).toEqual( + "76a914cb009bf12fc17d28e61527951101fdabfeaa187288ac209ec9845acb02fab24e1c0368b3b517c1a4488fba97f0e3459ac053ea0100000003c01f02b4" + ); + }, 30000); }); diff --git a/src/families/bitcoin/wallet-btc/crypto/bip32.ts b/src/families/bitcoin/wallet-btc/crypto/bip32.ts index 626d4d4db8..9931eacd9f 100644 --- a/src/families/bitcoin/wallet-btc/crypto/bip32.ts +++ b/src/families/bitcoin/wallet-btc/crypto/bip32.ts @@ -1,4 +1,4 @@ -import ecc from "tiny-secp256k1"; +import { publicKeyTweakAdd } from "secp256k1"; import createHmac from "create-hmac"; // the BIP32 class is inspired from https://github.com/bitcoinjs/bip32/blob/master/src/bip32.js @@ -28,7 +28,7 @@ class BIP32 { const I = createHmac("sha512", this.chainCode).update(data).digest(); const IL = I.slice(0, 32); const IR = I.slice(32); - const Ki = ecc.pointAddScalar(this.publicKey, IL, true); + const Ki = Buffer.from(publicKeyTweakAdd(this.publicKey, IL)); return new BIP32(Ki, IR, this.network, this.depth + 1, index); } } diff --git a/src/families/bitcoin/wallet-btc/crypto/zec.ts b/src/families/bitcoin/wallet-btc/crypto/zec.ts index fd38a8978b..afbb3fad4a 100644 --- a/src/families/bitcoin/wallet-btc/crypto/zec.ts +++ b/src/families/bitcoin/wallet-btc/crypto/zec.ts @@ -1,11 +1,8 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore -import { toOutputScript } from "bitcoinjs-lib/src/address"; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore import zec from "zcash-bitcore-lib"; import bs58check from "bs58check"; -import coininfo from "coininfo"; +import * as bjs from "bitcoinjs-lib"; import { InvalidAddress } from "@ledgerhq/errors"; import { DerivationModes } from "../types"; import Base from "./base"; @@ -32,19 +29,13 @@ class ZCash extends Base { return bs58check.encode(Buffer.from(taddr)); } - private static toBitcoinAddr(taddr: string) { - // refer to https://runkitcdn.com/gojomo/baddr2taddr/1.0.2 - const baddr = new Uint8Array(21); - baddr.set(bs58check.decode(taddr).slice(2), 1); - return bs58check.encode(Buffer.from(baddr)); - } - // eslint-disable-next-line getLegacyAddress(xpub: string, account: number, index: number): string { - const pubkey = new zec.HDPublicKey(xpub); - const child = pubkey.derive(account).derive(index); - const address = new zec.Address(child.publicKey, zec.Networks.livenet); - return address.toString(); + const pk = bjs.crypto.hash160(this.getPubkeyAt(xpub, account, index)); + const payload = Buffer.allocUnsafe(22); + payload.writeUInt16BE(this.network.pubKeyHash, 0); + pk.copy(payload, 2); + return bs58check.encode(payload); } customGetAddress( @@ -65,11 +56,20 @@ class ZCash extends Base { if (!this.validateAddress(address)) { throw new InvalidAddress(); } - // TODO find a better way to calculate the script from zec address instead of converting to bitcoin address - return toOutputScript( - ZCash.toBitcoinAddr(address), - coininfo.bitcoin.main.toBitcoinJS() + const version = Number( + "0x" + bs58check.decode(address).slice(0, 2).toString("hex") ); + if (version === this.network.pubKeyHash) { + //Pay-to-PubkeyHash + return bjs.payments.p2pkh({ hash: bs58check.decode(address).slice(2) }) + .output as Buffer; + } + if (version === this.network.scriptHash) { + //Pay-to-Script-Hash + return bjs.payments.p2sh({ hash: bs58check.decode(address).slice(2) }) + .output as Buffer; + } + throw new InvalidAddress(); } // eslint-disable-next-line class-methods-use-this diff --git a/src/families/bitcoin/wallet-btc/crypto/zen.ts b/src/families/bitcoin/wallet-btc/crypto/zen.ts index 39f09f6a1a..7b6d19d7af 100644 --- a/src/families/bitcoin/wallet-btc/crypto/zen.ts +++ b/src/families/bitcoin/wallet-btc/crypto/zen.ts @@ -1,14 +1,8 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import { toOutputScript } from "bitcoinjs-lib/src/address"; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import zec from "zcash-bitcore-lib"; import bs58check from "bs58check"; -import coininfo from "coininfo"; import { InvalidAddress } from "@ledgerhq/errors"; import { DerivationModes } from "../types"; import Base from "./base"; +import * as bjs from "bitcoinjs-lib"; class Zen extends Base { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -26,15 +20,15 @@ class Zen extends Base { }, bip44: 121, private: 0x80, - public: 0x2096, - scripthash: 0x2089, + public: 0x2089, + scripthash: 0x2096, }; this.network.name = "Zencash"; this.network.unit = "ZEN"; this.network.messagePrefix = "Zencash Signed Message:\n"; this.network.wif = 0x80; - this.network.pubKeyHash = 0x2096; - this.network.scriptHash = 0x2089; + this.network.pubKeyHash = 0x2089; + this.network.scriptHash = 0x2096; this.network.dustThreshold = 10000; this.network.dustPolicy = "FIXED"; this.network.usesTimestampedTransaction = false; @@ -50,21 +44,13 @@ class Zen extends Base { return bs58check.encode(Buffer.from(taddr)); } - private static toBitcoinAddr(taddr: string) { - // refer to https://runkitcdn.com/gojomo/baddr2taddr/1.0.2 - const baddr = new Uint8Array(21); - baddr.set(bs58check.decode(taddr).slice(2), 1); - return bs58check.encode(Buffer.from(baddr)); - } - // eslint-disable-next-line getLegacyAddress(xpub: string, account: number, index: number): string { - const pubkey = new zec.HDPublicKey(xpub); - const child = pubkey.derive(account).derive(index); - const address = new zec.Address(child.publicKey, zec.Networks.livenet); - const baddr = new Uint8Array(21); - baddr.set(bs58check.decode(address.toString()).slice(2), 1); - return this.baddrToTaddr(bs58check.encode(Buffer.from(baddr))); + const pk = bjs.crypto.hash160(this.getPubkeyAt(xpub, account, index)); + const payload = Buffer.allocUnsafe(22); + payload.writeUInt16BE(this.network.pubKeyHash, 0); + pk.copy(payload, 2); + return bs58check.encode(payload); } customGetAddress( @@ -85,10 +71,23 @@ class Zen extends Base { if (!this.validateAddress(address)) { throw new InvalidAddress(); } - const outputScript = toOutputScript( - Zen.toBitcoinAddr(address), - coininfo.bitcoin.main.toBitcoinJS() + let outputScript: Buffer; + const version = Number( + "0x" + bs58check.decode(address).slice(0, 2).toString("hex") ); + if (version === this.network.pubKeyHash) { + //Pay-to-PubkeyHash + outputScript = bjs.payments.p2pkh({ + hash: bs58check.decode(address).slice(2), + }).output as Buffer; + } else if (version === this.network.scriptHash) { + //Pay-to-Script-Hash + outputScript = bjs.payments.p2sh({ + hash: bs58check.decode(address).slice(2), + }).output as Buffer; + } else { + throw new InvalidAddress(); + } // refer to https://github.com/LedgerHQ/lib-ledger-core/blob/fc9d762b83fc2b269d072b662065747a64ab2816/core/src/wallet/bitcoin/scripts/BitcoinLikeScript.cpp#L139 and https://github.com/LedgerHQ/lib-ledger-core/blob/fc9d762b83fc2b269d072b662065747a64ab2816/core/src/wallet/bitcoin/networks.cpp#L39 for bip115 Script and its network parameters const bip115Script = Buffer.from( "209ec9845acb02fab24e1c0368b3b517c1a4488fba97f0e3459ac053ea0100000003c01f02b4", diff --git a/src/families/ethereum/speculos-deviceActions.ts b/src/families/ethereum/speculos-deviceActions.ts index 154ec776ff..3e429d144a 100644 --- a/src/families/ethereum/speculos-deviceActions.ts +++ b/src/families/ethereum/speculos-deviceActions.ts @@ -101,6 +101,10 @@ const acceptTransaction: DeviceAction = deviceActionFlow({ title: "Contract", button: "Rr", }, + { + title: "Network", + button: "Rr", + }, { title: "Max fees", button: "Rr", diff --git a/src/families/solana/test-dataset.ts b/src/families/solana/test-dataset.ts index 2601a34288..0623d5f570 100644 --- a/src/families/solana/test-dataset.ts +++ b/src/families/solana/test-dataset.ts @@ -1,5 +1,5 @@ import BigNumber from "bignumber.js"; -import { DatasetTest, encodeAccountId } from "../../types"; +import { CurrenciesData, DatasetTest, encodeAccountId } from "../../types"; import { Transaction, TransactionModel } from "./types"; @@ -75,8 +75,7 @@ const fees = (signatureCount: number) => const zero = new BigNumber(0); -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const solana = { +const solana: CurrenciesData = { scanAccounts: [scanAccounts1], accounts: [ { @@ -438,7 +437,7 @@ const solana = { const dataset: DatasetTest = { implementations: [getEnv("MOCK") ? "mock" : "js"], // FIXME we should actually put both mock and js like other dataset do currencies: { - // solana, // TODO re enable when it's ready + solana, }, }; diff --git a/src/families/tezos/synchronisation.ts b/src/families/tezos/synchronisation.ts index d065637b04..fedd53e40f 100644 --- a/src/families/tezos/synchronisation.ts +++ b/src/families/tezos/synchronisation.ts @@ -74,6 +74,7 @@ export const getAccountShape: GetAccountShape = async (infoInput) => { if (apiAccount.type === "empty") { return { id: accountId, + xpub: address, blockHeight, lastSyncDate: new Date(), tezosResources: { @@ -110,6 +111,7 @@ export const getAccountShape: GetAccountShape = async (infoInput) => { const accountShape = { id: accountId, + xpub: address, operations, balance, subAccounts, diff --git a/src/featureFlags/FeatureToggle.tsx b/src/featureFlags/FeatureToggle.tsx index b109de3e27..0fbe8ca6ac 100644 --- a/src/featureFlags/FeatureToggle.tsx +++ b/src/featureFlags/FeatureToggle.tsx @@ -17,8 +17,8 @@ export const FeatureToggle = ({ const feature = useFeature(featureId); if (!feature || !feature.enabled) { - return fallback; + return fallback ?? null; } - return children; + return children ?? null; }; diff --git a/src/featureFlags/defaultFeatures.ts b/src/featureFlags/defaultFeatures.ts index 5ade0284e0..7a37eb83d3 100644 --- a/src/featureFlags/defaultFeatures.ts +++ b/src/featureFlags/defaultFeatures.ts @@ -1,7 +1,7 @@ import { DefaultFeatures } from "./types"; export const defaultFeatures: DefaultFeatures = { - market: { + learn: { enabled: false, }, }; diff --git a/src/featureFlags/types.ts b/src/featureFlags/types.ts index 3b57938c83..4b35dace20 100644 --- a/src/featureFlags/types.ts +++ b/src/featureFlags/types.ts @@ -1,4 +1,4 @@ -export type FeatureId = "market"; // Add others with union +export type FeatureId = "learn"; // Add others with union (e.g. "learn" | "market" | "foo") // We use objects instead of direct booleans for potential future improvements // like feature versioning etc