Skip to content

Commit

Permalink
fix: lnurl utility func
Browse files Browse the repository at this point in the history
  • Loading branch information
apotdevin committed Jan 30, 2022
1 parent af89bf4 commit 3035b1a
Show file tree
Hide file tree
Showing 3 changed files with 8 additions and 82 deletions.
3 changes: 2 additions & 1 deletion src/server/modules/api/lnmarkets/lnmarkets.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Module } from '@nestjs/common';
import { FetchModule } from '../../fetch/fetch.module';
import { NodeModule } from '../../node/node.module';
import { LnUrlModule } from '../lnurl/lnurl.module';
import { LnMarketsResolver } from './lnmarkets.resolver';
import { LnMarketsService } from './lnmarkets.service';

@Module({
imports: [NodeModule, FetchModule],
imports: [LnUrlModule, NodeModule, FetchModule],
providers: [LnMarketsService, LnMarketsResolver],
})
export class LnMarketsModule {}
81 changes: 3 additions & 78 deletions src/server/modules/api/lnmarkets/lnmarkets.service.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { NodeService } from '../../node/node.service';
import hmacSHA256 from 'crypto-js/hmac-sha256';
import bip39 from 'bip39';
import BIP32Factory, { BIP32Interface } from 'bip32';
import secp256k1 from 'secp256k1';
import * as ecc from 'tiny-secp256k1';
import { enc } from 'crypto-js';
import { FetchService } from '../../fetch/fetch.service';
import { bech32 } from 'bech32';
import { ConfigService } from '@nestjs/config';

const bip32 = BIP32Factory(ecc);

const fromHexString = (hexString: string) =>
new Uint8Array(
hexString.match(/.{1,2}/g)?.map(byte => parseInt(byte, 16)) || []
);

const toHexString = (bytes: Uint8Array) =>
bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
import { LnUrlService } from '../lnurl/lnurl.service';

const decodeLnUrl = (url: string): string => {
const cleanUrl = url.toLowerCase().replace('lightning:', '');
Expand All @@ -34,7 +18,7 @@ export class LnMarketsService {
constructor(
private configService: ConfigService,
private fetchService: FetchService,
private nodeService: NodeService,
private lnUrlService: LnUrlService,
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger
) {}

Expand Down Expand Up @@ -105,65 +89,6 @@ export class LnMarketsService {
}
}

async lnAuthUrlGenerator(url: string, id: string) {
const domainUrl = new URL(url);
const host = domainUrl.host;

const k1 = domainUrl.searchParams.get('k1');

if (!host || !k1) {
this.logger.error('Missing host or k1 in url', { url });
throw new Error('WrongUrlFormat');
}

const wallet = await this.nodeService.getWalletInfo(id);

// Generate entropy
const secret = await this.nodeService.diffieHellmanComputeSecret(id, {
key_family: 138,
key_index: 0,
partner_public_key: wallet?.public_key,
});

// Generate hash from host and entropy
const hashed = hmacSHA256(host, secret.secret).toString(enc.Hex);

const indexes =
hashed.match(/.{1,4}/g)?.map(index => parseInt(index, 16)) || [];

// Generate private seed from entropy
const secretKey = bip39.entropyToMnemonic(hashed);
const base58 = bip39.mnemonicToSeedSync(secretKey);

// Derive private seed from previous private seed and path
const node: BIP32Interface = bip32.fromSeed(base58);
const derived = node.derivePath(
`m/138/${indexes[0]}/${indexes[1]}/${indexes[2]}/${indexes[3]}`
);

// Get private and public key from derived private seed
const privateKey = derived.privateKey?.toString('hex');
const linkingKey = derived.publicKey.toString('hex');

if (!privateKey || !linkingKey) {
this.logger.error('Error deriving private or public key', { url });
throw new Error('ErrorDerivingPrivateKey');
}

// Sign k1 with derived private seed
const sigObj = secp256k1.ecdsaSign(
fromHexString(k1),
fromHexString(privateKey)
);

// Get signature
const signature = secp256k1.signatureExport(sigObj.signature);
const encodedSignature = toHexString(signature);

// Build and return final url with signature and public key
return `${url}&sig=${encodedSignature}&key=${linkingKey}`;
}

async getLnMarketsAuth(
id: string,
cookie?: string | null
Expand Down Expand Up @@ -212,7 +137,7 @@ export class LnMarketsService {

// Decode the LnUrl and authenticate with it
const decoded = decodeLnUrl(lnUrl);
const finalUrl = await this.lnAuthUrlGenerator(decoded, id);
const finalUrl = await this.lnUrlService.lnAuthUrlGenerator(id, decoded);

// Try to authenticate with lnMarkets
try {
Expand Down
6 changes: 3 additions & 3 deletions src/server/modules/api/lnurl/lnurl.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { enc } from 'crypto-js';
import hmacSHA256 from 'crypto-js/hmac-sha256';
import bip39 from 'bip39';
import { entropyToMnemonic, mnemonicToSeedSync } from 'bip39';
import BIP32Factory, { BIP32Interface } from 'bip32';
import secp256k1 from 'secp256k1';
import * as ecc from 'tiny-secp256k1';
Expand Down Expand Up @@ -54,8 +54,8 @@ export class LnUrlService {
hashed.match(/.{1,4}/g)?.map(index => parseInt(index, 16)) || [];

// Generate private seed from entropy
const secretKey = bip39.entropyToMnemonic(hashed);
const base58 = bip39.mnemonicToSeedSync(secretKey);
const secretKey = entropyToMnemonic(hashed);
const base58 = mnemonicToSeedSync(secretKey);

// Derive private seed from previous private seed and path
const node: BIP32Interface = bip32.fromSeed(base58);
Expand Down

0 comments on commit 3035b1a

Please sign in to comment.