Skip to content

Commit

Permalink
chore: 🔧 add ln-auth (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
apotdevin authored Sep 25, 2020
1 parent 78efc34 commit 56b6907
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 30 deletions.
27 changes: 27 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
"balanceofsatoshis": "^5.47.0",
"bcryptjs": "^2.4.3",
"bech32": "^1.1.4",
"bip32": "^2.0.5",
"bip39": "^3.0.2",
"cookie": "^0.4.1",
"crypto-js": "^4.0.0",
"date-fns": "^2.16.1",
Expand Down Expand Up @@ -72,6 +74,7 @@
"react-table": "^7.5.1",
"react-toastify": "^6.0.8",
"react-tooltip": "^4.2.10",
"secp256k1": "^4.0.2",
"styled-components": "^5.2.0",
"styled-react-modal": "^2.0.1",
"styled-theming": "^2.2.0",
Expand Down Expand Up @@ -112,6 +115,7 @@
"@types/qrcode.react": "^1.0.1",
"@types/react-copy-to-clipboard": "^4.3.0",
"@types/react-table": "^7.0.23",
"@types/secp256k1": "^4.0.1",
"@types/styled-components": "^5.1.3",
"@types/styled-react-modal": "^1.2.0",
"@types/styled-theming": "^2.2.5",
Expand Down
114 changes: 96 additions & 18 deletions server/schema/lnurl/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,27 @@ import { to } from 'server/helpers/async';
import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { ContextType } from 'server/types/apiTypes';
import { createInvoice, decodePaymentRequest, pay } from 'ln-service';
import {
createInvoice,
decodePaymentRequest,
pay,
getWalletInfo,
diffieHellmanComputeSecret,
} from 'ln-service';
import {
CreateInvoiceType,
DecodedType,
DiffieHellmanComputeSecretType,
GetWalletInfoType,
PayInvoiceType,
} from 'server/types/ln-service.types';
// import { GetPublicKeyType } from 'server/types/ln-service.types';
// import hmacSHA256 from 'crypto-js/hmac-sha256';

import hmacSHA256 from 'crypto-js/hmac-sha256';
import { enc } from 'crypto-js';
import * as bip39 from 'bip39';
import * as bip32 from 'bip32';
import * as secp256k1 from 'secp256k1';
import { BIP32Interface } from 'bip32';

type LnUrlPayResponseType = {
pr?: string;
Expand Down Expand Up @@ -57,34 +70,99 @@ type WithdrawRequestType = {
type RequestType = PayRequestType | WithdrawRequestType;
type RequestWithType = { isTypeOf: string } & RequestType;

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'), '');

export const lnUrlResolvers = {
Mutation: {
lnUrl: async (
lnUrlAuth: async (
_: undefined,
{ type, url }: LnUrlParams,
{ url }: LnUrlParams,
context: ContextType
): Promise<string> => {
): Promise<{ status: string; message: string }> => {
await requestLimiter(context.ip, 'lnUrl');
const { lnd } = context;

// const fullUrl = new URL(url);
const domainUrl = new URL(url);
const host = domainUrl.host;

// const { lnd } = context;
const k1 = domainUrl.searchParams.get('k1');

// if (type === 'login') {
// logger.debug({ type, url });
if (!host || !k1) {
logger.error('Missing host or k1 in url: %o', url);
throw new Error('WrongUrlFormat');
}

// const info = await to<GetPublicKeyType>(
// getPublicKey({ lnd, family: 138, index: 0 })
// );
const wallet = await to<GetWalletInfoType>(getWalletInfo({ lnd }));

// const hashed = hmacSHA256(fullUrl.host, info.public_key);
// Generate entropy
const secret = await to<DiffieHellmanComputeSecretType>(
diffieHellmanComputeSecret({
lnd,
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);

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

logger.debug({ type, url });
// 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) {
logger.error('Error deriving private or public key: %o', url);
throw new Error('ErrorDerivingPrivateKey');
}

return 'confirmed';
// 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 final url with signature and public key
const finalUrl = `${url}&sig=${encodedSignature}&key=${linkingKey}`;

try {
const response = await fetch(finalUrl);
const json = await response.json();

logger.debug('LnUrlAuth response: %o', json);

if (json.status === 'ERROR') {
return { ...json, message: json.reason || 'LnServiceError' };
}

return { ...json, message: json.event || 'LnServiceSuccess' };
} catch (error) {
logger.error('Error authenticating with LnUrl service: %o', error);
throw new Error('ProblemAuthenticatingWithLnUrlService');
}
},
fetchLnUrl: async (
_: undefined,
Expand Down
5 changes: 5 additions & 0 deletions server/schema/lnurl/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export const lnUrlTypes = gql`
union LnUrlRequest = WithdrawRequest | PayRequest
type AuthResponse {
status: String!
message: String!
}
type PaySuccess {
tag: String
description: String
Expand Down
2 changes: 1 addition & 1 deletion server/schema/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export const queryTypes = gql`

export const mutationTypes = gql`
type Mutation {
lnUrlAuth(url: String!): AuthResponse!
lnUrlPay(callback: String!, amount: Int!, comment: String): PaySuccess!
lnUrlWithdraw(
callback: String!
Expand All @@ -99,7 +100,6 @@ export const mutationTypes = gql`
description: String
): String!
fetchLnUrl(url: String!): LnUrlRequest
lnUrl(type: String!, url: String!): String!
createBaseInvoice(amount: Int!): baseInvoiceType
createThunderPoints(
id: String!
Expand Down
4 changes: 4 additions & 0 deletions server/types/ln-service.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ export type GetWalletInfoType = {
public_key: string;
};

export type DiffieHellmanComputeSecretType = {
secret: string;
};

export type GetNodeType = { alias: string; color: string };

export type UtxoType = {};
Expand Down
46 changes: 46 additions & 0 deletions src/graphql/mutations/__generated__/lnUrl.generated.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/graphql/mutations/lnUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ export const FETCH_LN_URL = gql`
}
`;

export const AUTH_LN_URL = gql`
mutation AuthLnUrl($url: String!) {
lnUrlAuth(url: $url) {
status
message
}
}
`;

export const PAY_LN_URL = gql`
mutation PayLnUrl($callback: String!, $amount: Int!, $comment: String) {
lnUrlPay(callback: $callback, amount: $amount, comment: $comment) {
Expand Down
19 changes: 12 additions & 7 deletions src/graphql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,10 @@ export type QueryGetSessionTokenArgs = {

export type Mutation = {
__typename?: 'Mutation';
lnUrlAuth: AuthResponse;
lnUrlPay: PaySuccess;
lnUrlWithdraw: Scalars['String'];
fetchLnUrl?: Maybe<LnUrlRequest>;
lnUrl: Scalars['String'];
createBaseInvoice?: Maybe<BaseInvoiceType>;
createThunderPoints: Scalars['Boolean'];
closeChannel?: Maybe<CloseChannelType>;
Expand All @@ -234,6 +234,11 @@ export type Mutation = {
};


export type MutationLnUrlAuthArgs = {
url: Scalars['String'];
};


export type MutationLnUrlPayArgs = {
callback: Scalars['String'];
amount: Scalars['Int'];
Expand All @@ -254,12 +259,6 @@ export type MutationFetchLnUrlArgs = {
};


export type MutationLnUrlArgs = {
type: Scalars['String'];
url: Scalars['String'];
};


export type MutationCreateBaseInvoiceArgs = {
amount: Scalars['Int'];
};
Expand Down Expand Up @@ -1041,6 +1040,12 @@ export type PayRequest = {

export type LnUrlRequest = WithdrawRequest | PayRequest;

export type AuthResponse = {
__typename?: 'AuthResponse';
status: Scalars['String'];
message: Scalars['String'];
};

export type PaySuccess = {
__typename?: 'PaySuccess';
tag?: Maybe<Scalars['String']>;
Expand Down
Loading

0 comments on commit 56b6907

Please sign in to comment.