diff --git a/src/__tests__/families/bitcoin/wallet-btc/xpub.getAddress.ts b/src/__tests__/families/bitcoin/wallet-btc/xpub.getAddress.ts new file mode 100644 index 0000000000..a4aea43ff0 --- /dev/null +++ b/src/__tests__/families/bitcoin/wallet-btc/xpub.getAddress.ts @@ -0,0 +1,78 @@ +import { DerivationModes } from "../../../../families/bitcoin/wallet-btc"; +import BitcoinLikeStorage from "../../../../families/bitcoin/wallet-btc/storage"; +import BitcoinLikeExplorer from "../../../../families/bitcoin/wallet-btc/explorer"; +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 () => { + const bchCrypto = new BCHCrypto({ + network: coininfo.bitcoincash.main.toBitcoinJS(), + }); + const btcCrypto = new BTCCrypto({ + network: coininfo.bitcoin.main.toBitcoinJS(), + }); + const bchxpub = new Xpub({ + storage: new BitcoinLikeStorage(), + explorer: new BitcoinLikeExplorer({ + explorerURI: "https://explorers.api.vault.ledger.com/blockchain/v3/bch", + explorerVersion: "v3", + }), + crypto: bchCrypto, + xpub: "xpub6BtWBf3Pu6hYwJBKvEwG7JtrTxxDrSGy39HaTgZz6GTSaFWFdoCtuEXSQtoKGaYdz1emg8xTXKYwjhu3xXRPzFnYS1z4yjKj7hLDQyNeDZr", + derivationMode: DerivationModes.LEGACY, + }); + await bchxpub.syncAddress(0, 0); + let addresses = await bchxpub.getXpubAddresses(); + expect(addresses[0].address).toEqual( + "bitcoincash:qrgwhfg7tn4xs9pg2vu5rhkud490j9yfnqd63uk64m" + ); + + const btcxpub = new Xpub({ + storage: new BitcoinLikeStorage(), + explorer: new BitcoinLikeExplorer({ + explorerURI: "https://explorers.api.vault.ledger.com/blockchain/v3/btc", + explorerVersion: "v3", + }), + crypto: btcCrypto, + xpub: "xpub6BtWBf3Pu6hYwJBKvEwG7JtrTxxDrSGy39HaTgZz6GTSaFWFdoCtuEXSQtoKGaYdz1emg8xTXKYwjhu3xXRPzFnYS1z4yjKj7hLDQyNeDZr", + derivationMode: DerivationModes.LEGACY, + }); + await btcxpub.syncAddress(0, 0); + 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/__tests__/families/bitcoin/wallet-btc/xpub.syncing.integration.test.ts b/src/__tests__/families/bitcoin/wallet-btc/xpub.syncing.integration.test.ts index d98d7cb6d4..768c61018b 100644 --- a/src/__tests__/families/bitcoin/wallet-btc/xpub.syncing.integration.test.ts +++ b/src/__tests__/families/bitcoin/wallet-btc/xpub.syncing.integration.test.ts @@ -62,6 +62,7 @@ describe("xpub integration sync", () => { coin: "dgb", explorerVersion: "v3", }, + /* { xpub: "xpub6CUGRUonZSQ4TWtTMmzXdrXDtypWKiKrhko4egpiMZbpiaQL2jkwSB1icqYh2cfDfVxdx4df189oLKnC5fSwqPfgyP3hooxujYzAu3fDVmz", // 3000ms derivationMode: DerivationModes.LEGACY, @@ -71,6 +72,7 @@ describe("xpub integration sync", () => { coin: "btc", explorerVersion: "v3", }, + */ { xpub: "xpub6D4waFVPfPCpRvPkQd9A6n65z3hTp6TvkjnBHG5j2MCKytMuadKgfTUHqwRH77GQqCKTTsUXSZzGYxMGpWpJBdYAYVH75x7yMnwJvra1BUJ", // 5400ms derivationMode: DerivationModes.LEGACY, 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 b9f17e8ff9..36882eca04 100644 --- a/src/families/bitcoin/wallet-btc/crypto/zec.ts +++ b/src/families/bitcoin/wallet-btc/crypto/zec.ts @@ -1,22 +1,19 @@ // 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 { ICrypto } from "./types"; import Base from "./base"; -class ZCash implements ICrypto { +class ZCash extends Base { // eslint-disable-next-line @typescript-eslint/no-explicit-any network: any; // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor({ network }: { network: any }) { + super({ network }); this.network = network; this.network.dustThreshold = 10000; this.network.dustPolicy = "FIXED"; @@ -32,19 +29,13 @@ class ZCash implements ICrypto { 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); } getAddress( @@ -78,11 +69,20 @@ class ZCash implements ICrypto { 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 876e97cb73..1b674c011d 100644 --- a/src/families/bitcoin/wallet-btc/crypto/zen.ts +++ b/src/families/bitcoin/wallet-btc/crypto/zen.ts @@ -1,22 +1,16 @@ -// 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 { ICrypto } from "./types"; import Base from "./base"; +import * as bjs from "bitcoinjs-lib"; -class Zen implements ICrypto { +class Zen extends Base { // eslint-disable-next-line @typescript-eslint/no-explicit-any network: any; // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor({ network }: { network: any }) { + super({ network }); // refer to https://github.com/HorizenOfficial/zen/blob/master/src/chainparams.cpp for the blockchain params this.network = network; this.network.versions = { @@ -26,15 +20,15 @@ class Zen implements ICrypto { }, 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 implements ICrypto { 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); } getAddress( @@ -98,10 +84,23 @@ class Zen implements ICrypto { 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",