Skip to content

Commit

Permalink
Merge pull request #2 from GridPlus/feat/move-reusable-parts-to-core
Browse files Browse the repository at this point in the history
feat(core): move reusable logic from btc signer to core
  • Loading branch information
netbonus authored Jul 3, 2024
2 parents 2025414 + ecd165a commit ad7f6e8
Show file tree
Hide file tree
Showing 11 changed files with 452 additions and 110 deletions.
6 changes: 1 addition & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
{
"name": "@xdefi-tech/chains",
"version": "1.0.0",
"workspaces": [
"utility-packages/*",
"packages/*",
"examples/*"
],
"workspaces": ["utility-packages/*", "packages/*", "examples/*"],
"scripts": {
"prepare": "husky install",
"build": "turbo run build",
Expand Down
4 changes: 2 additions & 2 deletions packages/bitcoin/src/signers/lattice.signer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Msg } from '@xdefi-tech/chains-core';
import type { Msg } from '@xdefi-tech/chains-core';
import fetch from 'cross-fetch';

import { BitcoinProvider } from '../chain.provider';
import { IndexerDataSource } from '../datasource';
import { BITCOIN_MANIFEST } from '../manifests';
import { ChainMsg, MsgBody } from '../msg';
import type { ChainMsg, MsgBody } from '../msg';

import LatticeSigner from './lattice.signer';
globalThis.fetch = fetch;
Expand Down
87 changes: 11 additions & 76 deletions packages/bitcoin/src/signers/lattice.signer.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,11 @@
import { Signer, SignerDecorator } from '@xdefi-tech/chains-core';
import {
fetchAddresses,
fetchBtcLegacyAddresses,
setup,
sign,
signBtcLegacyTx,
} from 'gridplus-sdk';
import { fetchAddresses, signBtcLegacyTx } from 'gridplus-sdk';
import * as Bitcoin from 'bitcoinjs-lib';

import { ChainMsg } from '../msg';
import type { ChainMsg } from '../msg';

@SignerDecorator(Signer.SignerType.LATTICE)
export class LatticeSigner extends Signer.Provider {
private client!: string;
public isPaired: boolean;

constructor(client: string, isPaired: boolean) {
super();
this.client = client;
this.isPaired = isPaired;
}

static async create({
deviceId,
password,
name,
}: {
deviceId: string;
password: string;
name: string;
}): Promise<LatticeSigner> {
let clientData = '';

const getStoredClient = () => clientData;
const setStoredClient = (newClientData: string | null) => {
clientData = newClientData ?? '';
};

const isPaired = await setup({
deviceId,
password,
name,
getStoredClient,
setStoredClient,
});

return new LatticeSigner(clientData, isPaired);
}

export class LatticeSigner extends Signer.LatticeProvider {
verifyAddress(address: string): boolean {
try {
Bitcoin.address.toOutputScript(address);
Expand All @@ -59,7 +17,8 @@ export class LatticeSigner extends Signer.Provider {

async getAddress(derivation: string): Promise<string> {
const addresses = await fetchAddresses({
startPath: convertDerivationPathToArray(derivation),
startPath:
Signer.LatticeProvider.convertDerivationPathToArray(derivation),
n: 1,
});
return addresses[0];
Expand All @@ -73,33 +32,8 @@ export class LatticeSigner extends Signer.Provider {
}
}

export function convertDerivationPathToArray(path: string): number[] {
// Validate input path
if (!path.startsWith('m')) {
throw new Error('Invalid derivation path. Must start with "m".');
}

// Split the path into components, skipping the first element ('m')
const components = path.split('/').slice(1);

// Convert each component to the corresponding number
const result = components.map((component) => {
// Check if the component is hardened (ends with a single quote)
const isHardened = component.endsWith("'");

// Parse the number, removing the single quote if it is hardened
const number = parseInt(isHardened ? component.slice(0, -1) : component);

// Add the hardened offset if necessary
return isHardened ? HARDENED_OFFSET + number : number;
});

return result;
}

const HARDENED_OFFSET = 0x80000000; // Hardened offset
const BTC_PURPOSE_P2SH_P2WPKH = 49 + HARDENED_OFFSET; // Example constant for a specific purpose
const BTC_TESTNET_COIN = 1 + HARDENED_OFFSET; // Example constant for testnet
const BTC_PURPOSE_P2SH_P2WPKH = 49 + Signer.LatticeConst.HARDENED_OFFSET; // Example constant for a specific purpose
const BTC_TESTNET_COIN = 1 + Signer.LatticeConst.HARDENED_OFFSET; // Example constant for testnet

// Type definitions for transaction structures
interface Input {
Expand Down Expand Up @@ -137,7 +71,8 @@ function convertTransactionToBtcTxData(
transaction: Transaction,
derivation: string
): BtcTxData {
const signerPath = convertDerivationPathToArray(derivation);
const signerPath =
Signer.LatticeProvider.convertDerivationPathToArray(derivation);
const prevOuts = transaction.inputs.map((input, index) => ({
txHash: input.hash,
value: input.value,
Expand All @@ -156,7 +91,7 @@ function convertTransactionToBtcTxData(
);
const recipient = recipientOutput?.address || '';
const value = recipientOutput?.value || 0;
const feeSatoshis = parseFloat(transaction.fee) * 100000000; // Convert BTC to satoshis
const feeSatoshis = Number.parseFloat(transaction.fee) * 100000000; // Convert BTC to satoshis

const changeOutput = transaction.outputs.find((output) => !output.address);
const changePathIndex = transaction.outputs.indexOf(
Expand All @@ -172,7 +107,7 @@ function convertTransactionToBtcTxData(
changePath: [
BTC_PURPOSE_P2SH_P2WPKH,
BTC_TESTNET_COIN,
HARDENED_OFFSET,
Signer.LatticeConst.HARDENED_OFFSET,
1,
changePathIndex,
],
Expand Down
9 changes: 8 additions & 1 deletion packages/bitcoin/src/signers/web.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { PrivateKeySigner } from './private-key.signer';
import LedgerSigner from './ledger.signer';
import TrezorSigner from './trezor.signer';
import LatticeSigner from './lattice.signer';
import { SeedPhraseSigner } from './seed-phrase.signer';

export { PrivateKeySigner, LedgerSigner, TrezorSigner, SeedPhraseSigner };
export {
PrivateKeySigner,
LedgerSigner,
TrezorSigner,
LatticeSigner,
SeedPhraseSigner,
};
26 changes: 6 additions & 20 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@
"import": "./dist/index.js"
}
},
"files": [
"dist",
"CHANGELOG.md",
"README.md"
],
"files": ["dist", "CHANGELOG.md", "README.md"],
"devDependencies": {
"@types/lodash": "4.14.182",
"@types/uuid": "9.0.1",
Expand All @@ -36,6 +32,7 @@
"@next/bundle-analyzer": "12.3.0",
"@trezor/connect-web": "9.1.4",
"bignumber.js": "9.1.2",
"gridplus-sdk": "^2.5.2",
"graphql": "16.6.0",
"graphql-ws": "5.11.1",
"inversify": "6.0.1",
Expand All @@ -62,18 +59,12 @@
},
"type": "commonjs",
"tsup": {
"entry": [
"src/index.ts",
"!src/**/*.spec.*",
"!src/custom.d.ts"
],
"entry": ["src/index.ts", "!src/**/*.spec.*", "!src/custom.d.ts"],
"outDir": "dist",
"format": "cjs",
"splitting": false,
"dts": true,
"types": [
"./dist/index.d.ts"
],
"types": ["./dist/index.d.ts"],
"platform": "browser",
"target": "ES6"
},
Expand All @@ -83,14 +74,9 @@
"transform": {
".+\\.(t|j)s$": "ts-jest"
},
"modulePathIgnorePatterns": [
"<rootDir>/dist/"
],
"modulePathIgnorePatterns": ["<rootDir>/dist/"],
"testEnvironment": "jsdom",
"watchPlugins": [
"jest-watch-typeahead/filename",
"jest-watch-typeahead/testname"
],
"watchPlugins": ["jest-watch-typeahead/filename", "jest-watch-typeahead/testname"],
"testTimeout": 15000
}
}
1 change: 1 addition & 0 deletions packages/core/src/core/signer/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './signer.provider';
export * from './trezor.provider';
export * from './lattice.provider';
export * from './interfaces';
10 changes: 10 additions & 0 deletions packages/core/src/core/signer/lattice.provider.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { LatticeProvider } from './lattice.provider';

const testDerivationPath = "m/84'/0'/0'/0/0";

describe('lattice.provider', () => {
it('converts derivation string to int array', () => {
const result = LatticeProvider.convertDerivationPathToArray(testDerivationPath);
expect(result).toEqual([2147483732, 2147483648, 2147483648, 0, 0]);
});
});
90 changes: 90 additions & 0 deletions packages/core/src/core/signer/lattice.provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { setup } from 'gridplus-sdk';

import { Injectable } from '../../common';
import type { MsgData } from '../msg';

import { Provider } from './signer.provider';

const HARDENED_OFFSET = 0x80000000; // Hardened offset

@Injectable()
export class LatticeProvider extends Provider {
private static instance: LatticeProvider;
public initialized = false;
private client!: string;
public isPaired: boolean;

constructor(client: string, isPaired: boolean) {
super();
this.client = client;
this.isPaired = isPaired;
this.initialized = true;
}

static async create({
deviceId,
password,
name,
}: {
deviceId: string;
password: string;
name: string;
}): Promise<LatticeProvider> {
let clientData = '';

const getStoredClient = () => clientData;
const setStoredClient = (newClientData: string | null) => {
clientData = newClientData ?? '';
};

const isPaired = await setup({
deviceId,
password,
name,
getStoredClient,
setStoredClient,
});

return new LatticeProvider(clientData, isPaired);
}

static convertDerivationPathToArray(path: string): number[] {
// Validate input path
if (!path.startsWith('m')) {
throw new Error('Invalid derivation path. Must start with "m".');
}

// Split the path into components, skipping the first element ('m')
const components = path.split('/').slice(1);

// Convert each component to the corresponding number
const result = components.map((component) => {
// Check if the component is hardened (ends with a single quote)
const isHardened = component.endsWith("'");

// Parse the number, removing the single quote if it is hardened
const number = Number.parseInt(isHardened ? component.slice(0, -1) : component);

// Add the hardened offset if necessary
return isHardened ? HARDENED_OFFSET + number : number;
});

return result;
}

async getAddress(derivation: string): Promise<string> {
throw new Error('Should be implemented on specific signer level.');
}

async sign(msg: MsgData, derivation: string) {
throw new Error('Should be implemented on specific signer level.');
}

verifyAddress(address: string): boolean {
throw new Error('Should be implemented on specific signer level.');
}
}

export const LatticeConst = {
HARDENED_OFFSET,
};
Loading

0 comments on commit ad7f6e8

Please sign in to comment.