diff --git a/packages/core/package.json b/packages/core/package.json index 03b7a9d25..fd7094cdb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -68,6 +68,7 @@ "dayjs": "^1.11.7", "graphql": "^16.6.0", "graphql-ws": "^5.11.3", + "secp256k1": "^5.0.0", "text-encoding": "^0.7.0", "ws": "^8.12.1", "zen-observable-ts": "^1.1.0" @@ -76,6 +77,7 @@ "@lightsparkdev/eslint-config": "*", "@lightsparkdev/tsconfig": "0.0.0", "@types/crypto-js": "^4.1.1", + "@types/secp256k1": "^4.0.3", "@types/ws": "^8.5.4", "eslint": "^8.3.0", "eslint-watch": "^8.0.0", diff --git a/packages/core/src/crypto/NodeKeyCache.ts b/packages/core/src/crypto/NodeKeyCache.ts index bda70be20..f0836048c 100644 --- a/packages/core/src/crypto/NodeKeyCache.ts +++ b/packages/core/src/crypto/NodeKeyCache.ts @@ -4,11 +4,18 @@ import autoBind from "auto-bind"; import { b64decode } from "../utils/base64.js"; import type { CryptoInterface } from "./crypto.js"; -import { DefaultCrypto } from "./crypto.js"; +import { DefaultCrypto, LightsparkSigningException } from "./crypto.js"; import type { KeyOrAliasType } from "./KeyOrAlias.js"; +import { + RSASigningKey, + Secp256k1SigningKey, + type SigningKey, +} from "./SigningKey.js"; +import { SigningKeyType } from "./types.js"; class NodeKeyCache { - private idToKey: Map; + private idToKey: Map; + constructor(private readonly cryptoImpl: CryptoInterface = DefaultCrypto) { this.idToKey = new Map(); autoBind(this); @@ -17,23 +24,51 @@ class NodeKeyCache { public async loadKey( id: string, keyOrAlias: KeyOrAliasType, - ): Promise { + signingKeyType: SigningKeyType, + ): Promise { + let signingKey: SigningKey; + if (keyOrAlias.alias !== undefined) { - this.idToKey.set(id, keyOrAlias.alias); - return keyOrAlias.alias; + switch (signingKeyType) { + case SigningKeyType.RSASigningKey: + signingKey = new RSASigningKey( + { alias: keyOrAlias.alias }, + this.cryptoImpl, + ); + break; + default: + throw new LightsparkSigningException( + `Aliases are not supported for signing key type ${signingKeyType}`, + ); + } + + this.idToKey.set(id, signingKey); + return signingKey; } - const decoded = b64decode(this.stripPemTags(keyOrAlias.key)); + try { - const key = await this.cryptoImpl.importPrivateSigningKey(decoded); - this.idToKey.set(id, key); - return key; + if (signingKeyType === SigningKeyType.Secp256k1SigningKey) { + signingKey = new Secp256k1SigningKey(keyOrAlias.key); + } else { + const decoded = b64decode(this.stripPemTags(keyOrAlias.key)); + const cryptoKeyOrAlias = + await this.cryptoImpl.importPrivateSigningKey(decoded); + const key = + typeof cryptoKeyOrAlias === "string" + ? { alias: cryptoKeyOrAlias } + : cryptoKeyOrAlias; + signingKey = new RSASigningKey(key, this.cryptoImpl); + } + + this.idToKey.set(id, signingKey); + return signingKey; } catch (e) { console.log("Error importing key: ", e); } return null; } - public getKey(id: string): CryptoKey | string | undefined { + public getKey(id: string): SigningKey | undefined { return this.idToKey.get(id); } diff --git a/packages/core/src/crypto/SigningKey.ts b/packages/core/src/crypto/SigningKey.ts new file mode 100644 index 000000000..3378ce303 --- /dev/null +++ b/packages/core/src/crypto/SigningKey.ts @@ -0,0 +1,50 @@ +import { createHash } from "crypto"; +import secp256k1 from "secp256k1"; +import { hexToBytes, SigningKeyType, type CryptoInterface } from "../index.js"; + +interface Alias { + alias: string; +} + +function isAlias(key: CryptoKey | Alias): key is Alias { + return "alias" in key; +} + +export abstract class SigningKey { + readonly type: SigningKeyType; + + constructor(type: SigningKeyType) { + this.type = type; + } + + abstract sign(data: Uint8Array): Promise; +} + +export class RSASigningKey extends SigningKey { + constructor( + private readonly privateKey: CryptoKey | Alias, + private readonly cryptoImpl: CryptoInterface, + ) { + super(SigningKeyType.RSASigningKey); + } + + async sign(data: Uint8Array) { + const key = isAlias(this.privateKey) + ? this.privateKey.alias + : this.privateKey; + return this.cryptoImpl.sign(key, data); + } +} + +export class Secp256k1SigningKey extends SigningKey { + constructor(private readonly privateKey: string) { + super(SigningKeyType.Secp256k1SigningKey); + } + + async sign(data: Uint8Array) { + const keyBytes = new Uint8Array(hexToBytes(this.privateKey)); + const hash = createHash("sha256").update(data).digest(); + const signResult = secp256k1.ecdsaSign(hash, keyBytes); + return signResult.signature; + } +} diff --git a/packages/core/src/crypto/index.ts b/packages/core/src/crypto/index.ts index a2223cdb9..efcd95e12 100644 --- a/packages/core/src/crypto/index.ts +++ b/packages/core/src/crypto/index.ts @@ -4,3 +4,5 @@ export * from "./crypto.js"; export * from "./KeyOrAlias.js"; export { default as LightsparkSigningException } from "./LightsparkSigningException.js"; export { default as NodeKeyCache } from "./NodeKeyCache.js"; +export * from "./SigningKey.js"; +export * from "./types.js"; diff --git a/packages/core/src/crypto/types.ts b/packages/core/src/crypto/types.ts new file mode 100644 index 000000000..92050040c --- /dev/null +++ b/packages/core/src/crypto/types.ts @@ -0,0 +1,4 @@ +export enum SigningKeyType { + RSASigningKey = "RSASigningKey", + Secp256k1SigningKey = "Secp256k1SigningKey", +} diff --git a/packages/core/src/requester/Requester.ts b/packages/core/src/requester/Requester.ts index d7e08cda3..4083799cb 100644 --- a/packages/core/src/requester/Requester.ts +++ b/packages/core/src/requester/Requester.ts @@ -228,9 +228,10 @@ class Requester { const encodedPayload = new TextEncoderImpl().encode( JSON.stringify(payload), ); - const signedPayload = await this.cryptoImpl.sign(key, encodedPayload); - const encodedSignedPayload = b64encode(signedPayload); + const signedPayload = await key.sign(encodedPayload); + + const encodedSignedPayload = b64encode(signedPayload); headers["X-Lightspark-Signing"] = JSON.stringify({ v: "1", signature: encodedSignedPayload, diff --git a/packages/core/src/utils/hex.ts b/packages/core/src/utils/hex.ts new file mode 100644 index 000000000..01856ffc6 --- /dev/null +++ b/packages/core/src/utils/hex.ts @@ -0,0 +1,15 @@ +export const bytesToHex = (bytes: Uint8Array): string => { + return bytes.reduce((acc: string, byte: number) => { + return (acc += ("0" + byte.toString(16)).slice(-2)); + }, ""); +}; + +export const hexToBytes = (hex: string): Uint8Array => { + const bytes: number[] = []; + + for (let c = 0; c < hex.length; c += 2) { + bytes.push(parseInt(hex.substr(c, 2), 16)); + } + + return Uint8Array.from(bytes); +}; diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index c7ef930ca..62f6e9007 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -3,4 +3,5 @@ export * from "./base64.js"; export * from "./currency.js"; export * from "./environment.js"; +export * from "./hex.js"; export * from "./types.js"; diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 541ea73bb..c381e522e 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "@lightsparkdev/tsconfig/base.json", - "include": ["src"], + "include": ["src", "src/crypto/types.ts"], "exclude": ["test", "node_modules", "dist"] } diff --git a/packages/lightspark-cli/src/index.ts b/packages/lightspark-cli/src/index.ts index 23b8afaab..aec69015d 100644 --- a/packages/lightspark-cli/src/index.ts +++ b/packages/lightspark-cli/src/index.ts @@ -88,15 +88,15 @@ const initEnv = async (options: OptionValues) => { let content = `export ${RequiredCredentials.ClientId}="${clientId}"\n`; content += `export ${RequiredCredentials.ClientSecret}="${clientSecret}"\n`; - let baseUrl: string | undefined; + let baseApiUrl: string | undefined; if (options.env === "dev") { - baseUrl = "api.dev.dev.sparkinfra.net"; - content += `export LIGHTSPARK_BASE_URL="${baseUrl}"\n`; + baseApiUrl = "api.dev.dev.sparkinfra.net"; + content += `export LIGHTSPARK_BASE_URL="${baseApiUrl}" # API url for dev deployment environment\n`; } const client = new LightsparkClient( new AccountTokenAuthProvider(clientId, clientSecret), - baseUrl, + baseApiUrl, ); let tokenBitcoinNetwork; @@ -360,7 +360,7 @@ const payInvoice = async ( "\n", ); - await client.unlockNode(nodeId, nodePassword); + await client.loadNodeSigningKey(nodeId, { password: nodePassword }); const payment = await client.payInvoice( nodeId, encodedInvoice, @@ -392,7 +392,7 @@ const createTestModePayment = async ( throw new Error("Node password not found in environment."); } - await client.unlockNode(nodeId, nodePassword); + await client.loadNodeSigningKey(nodeId, { password: nodePassword }); const payment = await client.createTestModePayment( nodeId, encodedInvoice, diff --git a/packages/lightspark-sdk/.eslintrc.cjs b/packages/lightspark-sdk/.eslintrc.cjs index 7741ddd5a..ebceead51 100644 --- a/packages/lightspark-sdk/.eslintrc.cjs +++ b/packages/lightspark-sdk/.eslintrc.cjs @@ -1,6 +1,6 @@ module.exports = { extends: ["@lightsparkdev/eslint-config/base"], - ignorePatterns: ["jest.config.ts"], + ignorePatterns: ["jest.config.ts", "lightspark_crypto.js"], overrides: [ { files: ["./src/objects/**/*.ts?(x)"], diff --git a/packages/lightspark-sdk/README.md b/packages/lightspark-sdk/README.md index 464bb5694..400e8bc59 100644 --- a/packages/lightspark-sdk/README.md +++ b/packages/lightspark-sdk/README.md @@ -49,7 +49,7 @@ const nodeID = ; const nodePassword = ; try { - await lightsparkClient.unlockNode(nodeID, nodePassword); + await lightsparkClient.loadNodeSigningKey(nodeID, { password: nodePassword }); } catch (e) { console.error("Failed to unlock node", e); } diff --git a/packages/lightspark-sdk/examples/node-scripts/internal_example.ts b/packages/lightspark-sdk/examples/node-scripts/internal_example.ts index 87c257685..6263465ec 100644 --- a/packages/lightspark-sdk/examples/node-scripts/internal_example.ts +++ b/packages/lightspark-sdk/examples/node-scripts/internal_example.ts @@ -251,7 +251,7 @@ console.log(""); // Let's send the payment. // First, we need to recover the signing key. -await client.unlockNode(node2Id, credentials.node2Password!); +await client.loadNodeSigningKey(node2Id, { password: credentials.node2Password! }); console.log(`${credentials.node2Name}'s signing key has been loaded.`); // Then we can send the payment diff --git a/packages/lightspark-sdk/src/NodeKeyLoaderCache.ts b/packages/lightspark-sdk/src/NodeKeyLoaderCache.ts new file mode 100644 index 000000000..5648a51fd --- /dev/null +++ b/packages/lightspark-sdk/src/NodeKeyLoaderCache.ts @@ -0,0 +1,87 @@ +import { + LightsparkSigningException, + type CryptoInterface, + type NodeKeyCache, + type Requester, + type SigningKey, +} from "@lightsparkdev/core"; +import { + isMasterSeedSigningKeyLoaderArgs, + isNodeIdAndPasswordSigningKeyLoaderArgs, + MasterSeedSigningKeyLoader, + NodeIdAndPasswordSigningKeyLoader, + type SigningKeyLoader, + type SigningKeyLoaderArgs, +} from "./SigningKeyLoader.js"; + +/** + * A cache for SigningKeyLoaders associated with nodes. + */ +export default class NodeKeyLoaderCache { + private idToLoader: Map; + + constructor( + private readonly nodeKeyCache: NodeKeyCache, + private readonly cryptoImpl: CryptoInterface = DefaultCrypto, + ) { + this.idToLoader = new Map(); + } + + /** + * Sets the signing key loader for a node. + * Instantiates a signing key loader based on the type of args passed in by the user. + * + * @param nodeId The ID of the node to get the key for + * @param loaderArgs Loader arguments for loading the key + * @param requester Requester used for loading the key + */ + setLoader( + nodeId: string, + loaderArgs: SigningKeyLoaderArgs, + requester: Requester, + ) { + let loader: SigningKeyLoader; + if (isNodeIdAndPasswordSigningKeyLoaderArgs(loaderArgs)) { + loader = new NodeIdAndPasswordSigningKeyLoader( + { nodeId, ...loaderArgs }, + requester, + this.cryptoImpl, + ); + } else if (isMasterSeedSigningKeyLoaderArgs(loaderArgs)) { + loader = new MasterSeedSigningKeyLoader({ ...loaderArgs }); + } else { + throw new LightsparkSigningException("Invalid signing key loader args"); + } + + this.idToLoader.set(nodeId, loader); + } + + /** + * Gets the key for a node using the loader set by [setLoader] + * + * @param id The ID of the node to get the key for + * @returns The loaded key + */ + async getKeyWithLoader(id: string): Promise { + if (this.nodeKeyCache.hasKey(id)) { + return this.nodeKeyCache.getKey(id); + } + + const loader = this.idToLoader.get(id); + if (!loader) { + throw new LightsparkSigningException( + "No signing key loader found for node " + id, + ); + } + const loaderResult = await loader.loadSigningKey(); + if (!loaderResult) { + return; + } + + return this.nodeKeyCache.loadKey( + id, + { key: loaderResult.key }, + loaderResult.type, + ); + } +} diff --git a/packages/lightspark-sdk/src/SigningKeyLoader.ts b/packages/lightspark-sdk/src/SigningKeyLoader.ts new file mode 100644 index 000000000..d290e3319 --- /dev/null +++ b/packages/lightspark-sdk/src/SigningKeyLoader.ts @@ -0,0 +1,177 @@ +import { + b64encode, + LightsparkSigningException, + SigningKeyType, + type CryptoInterface, + type Maybe, + type Requester, +} from "@lightsparkdev/core"; +import { RecoverNodeSigningKey } from "./graphql/RecoverNodeSigningKey.js"; +import { BitcoinNetwork } from "./index.js"; +import { + LightsparkSigner, + Network, +} from "./lightspark_crypto/lightspark_crypto.js"; + +const SIGNING_KEY_PATH = "m/5"; + +const getCryptoLibNetwork = (bitcoinNetwork: BitcoinNetwork): Network => { + switch (bitcoinNetwork) { + case BitcoinNetwork.MAINNET: + return Network.Bitcoin; + case BitcoinNetwork.TESTNET: + return Network.Testnet; + case BitcoinNetwork.REGTEST: + return Network.Regtest; + default: + throw new Error( + `Unsupported lightspark_crypto network ${bitcoinNetwork}.`, + ); + } +}; + +/** + * Args for creating a new SigningKeyLoader. Must be one of the sub types. + */ +export type SigningKeyLoaderArgs = + | NodeIdAndPasswordSigningKeyLoaderArgs + | MasterSeedSigningKeyLoaderArgs; + +/** + * Args for creating a new SigningKeyLoader from a node ID and password. + * This cannot be used if you are using remote signing. It is used to recover an RSA operation signing key using + * the password you chose when setting up your node. For REGTEST nodes, the password is "1234!@#$". + */ +export interface NodeIdAndPasswordSigningKeyLoaderArgs { + password: string; +} + +/** + * Internal version of NodeIdAndPasswordSigningKeyLoaderArgs that includes the node ID. + */ +interface NodeIdAndPasswordSigningKeyLoaderArgsInternal + extends NodeIdAndPasswordSigningKeyLoaderArgs { + nodeId: string; +} + +/** + * Args for creating a new SigningKeyLoader from a master seed and network. + * This should be used if you are using remote signing, rather than an RSA operation signing key. + */ +export interface MasterSeedSigningKeyLoaderArgs { + masterSeed: Uint8Array; + network: BitcoinNetwork; +} + +/** + * The result of loading a signing key. + */ +export interface SigningKeyLoaderResult { + key: string; + type: SigningKeyType; +} + +export interface SigningKeyLoader { + loadSigningKey: () => Promise; +} + +export function isNodeIdAndPasswordSigningKeyLoaderArgs( + args: SigningKeyLoaderArgs, +): args is NodeIdAndPasswordSigningKeyLoaderArgs { + return (args as NodeIdAndPasswordSigningKeyLoaderArgs).password !== undefined; +} + +export function isMasterSeedSigningKeyLoaderArgs( + args: SigningKeyLoaderArgs, +): args is MasterSeedSigningKeyLoaderArgs { + return (args as MasterSeedSigningKeyLoaderArgs).masterSeed !== undefined; +} + +/** + * Key loader that loads an RSA signing key by providing a node ID and password to recover the key from Lightspark. + */ +export class NodeIdAndPasswordSigningKeyLoader implements SigningKeyLoader { + private readonly nodeId: string; + private readonly password: string; + private readonly requester: Requester; + private readonly cryptoImpl: CryptoInterface; + + constructor( + args: NodeIdAndPasswordSigningKeyLoaderArgsInternal, + requester: Requester, + cryptoImpl: CryptoInterface, + ) { + this.nodeId = args.nodeId; + this.password = args.password; + this.requester = requester; + this.cryptoImpl = cryptoImpl; + } + + async loadSigningKey() { + const encryptedKey = await this.recoverNodeSigningKey(); + if (!encryptedKey) { + console.warn("No encrypted key found for node " + this.nodeId); + return; + } + + const signingPrivateKey = + await this.cryptoImpl.decryptSecretWithNodePassword( + encryptedKey.cipher, + encryptedKey.encrypted_value, + this.password, + ); + + if (!signingPrivateKey) { + throw new LightsparkSigningException( + "Unable to decrypt signing key with provided password. Please try again.", + ); + } + + let signingPrivateKeyPEM = ""; + if (new Uint8Array(signingPrivateKey)[0] === 48) { + // Support DER format - https://github.com/lightsparkdev/webdev/pull/1982 + signingPrivateKeyPEM = b64encode(signingPrivateKey); + } else { + const dec = new TextDecoder(); + signingPrivateKeyPEM = dec.decode(signingPrivateKey); + } + + return { key: signingPrivateKeyPEM, type: SigningKeyType.RSASigningKey }; + } + + private async recoverNodeSigningKey(): Promise< + Maybe<{ encrypted_value: string; cipher: string }> + > { + const response = await this.requester.makeRawRequest( + RecoverNodeSigningKey, + { nodeId: this.nodeId }, + ); + const nodeEntity = response.entity; + if (nodeEntity?.__typename === "LightsparkNodeWithOSK") { + return nodeEntity.encrypted_signing_private_key; + } + return null; + } +} + +/** + * Key loader that loads a Secp256k1 signing key from a master seed. + */ +export class MasterSeedSigningKeyLoader implements SigningKeyLoader { + private readonly masterSeed: Uint8Array; + private readonly network: BitcoinNetwork; + + constructor(args: MasterSeedSigningKeyLoaderArgs) { + this.masterSeed = args.masterSeed; + this.network = args.network; + } + + async loadSigningKey() { + const lightsparkSigner = LightsparkSigner.from_bytes( + this.masterSeed, + getCryptoLibNetwork(this.network), + ); + const privateKey = lightsparkSigner.derive_private_key(SIGNING_KEY_PATH); + return { key: privateKey, type: SigningKeyType.Secp256k1SigningKey }; + } +} diff --git a/packages/lightspark-sdk/src/client.ts b/packages/lightspark-sdk/src/client.ts index ece433cf9..7304b7cb8 100644 --- a/packages/lightspark-sdk/src/client.ts +++ b/packages/lightspark-sdk/src/client.ts @@ -9,16 +9,16 @@ import type { KeyOrAliasType, Maybe, Query, + SigningKey, } from "@lightsparkdev/core"; import { - b64encode, DefaultCrypto, - KeyOrAlias, LightsparkAuthException, LightsparkException, LightsparkSigningException, NodeKeyCache, Requester, + SigningKeyType, StubAuthProvider, } from "@lightsparkdev/core"; import { createHash } from "crypto"; @@ -40,12 +40,12 @@ import type { AccountDashboard } from "./graphql/MultiNodeDashboard.js"; import { MultiNodeDashboard } from "./graphql/MultiNodeDashboard.js"; import { PayInvoice } from "./graphql/PayInvoice.js"; import { PayUmaInvoice } from "./graphql/PayUmaInvoice.js"; -import { RecoverNodeSigningKey } from "./graphql/RecoverNodeSigningKey.js"; import { RequestWithdrawal } from "./graphql/RequestWithdrawal.js"; import { SendPayment } from "./graphql/SendPayment.js"; import { SingleNodeDashboard as SingleNodeDashboardQuery } from "./graphql/SingleNodeDashboard.js"; import { TransactionsForNode } from "./graphql/TransactionsForNode.js"; import { TransactionSubscription } from "./graphql/TransactionSubscription.js"; +import NodeKeyLoaderCache from "./NodeKeyLoaderCache.js"; import Account from "./objects/Account.js"; import { ApiTokenFromJson } from "./objects/ApiToken.js"; import BitcoinNetwork from "./objects/BitcoinNetwork.js"; @@ -70,6 +70,7 @@ import { TransactionUpdateFromJson } from "./objects/TransactionUpdate.js"; import type WithdrawalMode from "./objects/WithdrawalMode.js"; import type WithdrawalRequest from "./objects/WithdrawalRequest.js"; import { WithdrawalRequestFromJson } from "./objects/WithdrawalRequest.js"; +import { type SigningKeyLoaderArgs } from "./SigningKeyLoader.js"; const sdkVersion = packageJson.version; @@ -99,6 +100,9 @@ const sdkVersion = packageJson.version; class LightsparkClient { private requester: Requester; private readonly nodeKeyCache: NodeKeyCache; + private readonly nodeKeyLoaderCache: NodeKeyLoaderCache; + private readonly LIGHTSPARK_SDK_ENDPOINT = + process.env.LIGHTSPARK_SDK_ENDPOINT || "graphql/server/2023-09-13"; /** * Constructs a new LightsparkClient. @@ -115,9 +119,13 @@ class LightsparkClient { private readonly cryptoImpl: CryptoInterface = DefaultCrypto, ) { this.nodeKeyCache = new NodeKeyCache(this.cryptoImpl); + this.nodeKeyLoaderCache = new NodeKeyLoaderCache( + this.nodeKeyCache, + this.cryptoImpl, + ); this.requester = new Requester( this.nodeKeyCache, - LIGHTSPARK_SDK_ENDPOINT, + this.LIGHTSPARK_SDK_ENDPOINT, `js-lightspark-sdk/${sdkVersion}`, authProvider, serverUrl, @@ -127,6 +135,35 @@ class LightsparkClient { autoBind(this); } + /** + * Sets the key loader for a node. This unlocks client operations that require a private key. + * Passing in [NodeIdAndPasswordSigningKeyLoaderArgs] loads the RSA key for an OSK node. + * Passing in [MasterSeedSigningKeyLoaderArgs] loads the Secp256k1 key for a remote signing node. + * + * @param nodeId The ID of the node the key is for + * @param loader The loader for the key + */ + public async loadNodeSigningKey( + nodeId: string, + loaderArgs: SigningKeyLoaderArgs, + ) { + this.nodeKeyLoaderCache.setLoader(nodeId, loaderArgs, this.requester); + const key = await this.getNodeSigningKey(nodeId); + return !!key; + } + + /** + * Gets the signing key for a node. Must have previously called [loadNodeSigningKey]. + * + * @param nodeId The ID of the node the key is for + * @returns The signing key for the node + */ + public async getNodeSigningKey( + nodeId: string, + ): Promise { + return await this.nodeKeyLoaderCache.getKeyWithLoader(nodeId); + } + /** * Sets the auth provider for the client. This is useful for switching between auth providers if you are using * multiple accounts or waiting for the user to log in. @@ -136,7 +173,7 @@ class LightsparkClient { public async setAuthProvider(authProvider: AuthProvider) { this.requester = new Requester( this.nodeKeyCache, - LIGHTSPARK_SDK_ENDPOINT, + this.LIGHTSPARK_SDK_ENDPOINT, `js-lightspark-sdk/${sdkVersion}`, authProvider, this.serverUrl, @@ -584,63 +621,6 @@ class LightsparkClient { ); } - /** - * Unlock the given node for sensitive operations like sending payments. - * - * @param nodeId The ID of the node to unlock. - * @param password The node password assigned at node creation. - * @returns True if the node was unlocked successfully, false otherwise. - */ - public async unlockNode(nodeId: string, password: string): Promise { - const encryptedKey = await this.recoverNodeSigningKey(nodeId); - if (!encryptedKey) { - console.warn("No encrypted key found for node " + nodeId); - return false; - } - - const signingPrivateKey = - await this.cryptoImpl.decryptSecretWithNodePassword( - encryptedKey.cipher, - encryptedKey.encrypted_value, - password, - ); - - if (!signingPrivateKey) { - throw new LightsparkSigningException( - "Unable to decrypt signing key with provided password. Please try again.", - ); - } - - let signingPrivateKeyPEM = ""; - if (new Uint8Array(signingPrivateKey)[0] === 48) { - // Support DER format - https://github.com/lightsparkdev/webdev/pull/1982 - signingPrivateKeyPEM = b64encode(signingPrivateKey); - } else { - const dec = new TextDecoder(); - signingPrivateKeyPEM = dec.decode(signingPrivateKey); - } - - await this.nodeKeyCache.loadKey( - nodeId, - KeyOrAlias.key(signingPrivateKeyPEM), - ); - return true; - } - - private async recoverNodeSigningKey( - nodeId: string, - ): Promise> { - const response = await this.requester.makeRawRequest( - RecoverNodeSigningKey, - { nodeId }, - ); - const nodeEntity = response.entity; - if (nodeEntity?.__typename === "LightsparkNodeWithOSK") { - return nodeEntity.encrypted_signing_private_key; - } - return null; - } - /** * Directly unlocks a node with a signing private key or alias. * @@ -651,7 +631,11 @@ class LightsparkClient { nodeId: string, signingPrivateKeyOrAlias: KeyOrAliasType, ) { - await this.nodeKeyCache.loadKey(nodeId, signingPrivateKeyOrAlias); + await this.nodeKeyCache.loadKey( + nodeId, + signingPrivateKeyOrAlias, + SigningKeyType.RSASigningKey, + ); } /** @@ -1004,6 +988,4 @@ class LightsparkClient { } } -const LIGHTSPARK_SDK_ENDPOINT = "graphql/server/2023-09-13"; - export default LightsparkClient; diff --git a/packages/lightspark-sdk/src/lightspark_crypto/lightspark_crypto.d.ts b/packages/lightspark-sdk/src/lightspark_crypto/lightspark_crypto.d.ts new file mode 100644 index 000000000..c3e9221fe --- /dev/null +++ b/packages/lightspark-sdk/src/lightspark_crypto/lightspark_crypto.d.ts @@ -0,0 +1,157 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + */ +export enum Network { + Bitcoin = 0, + Testnet = 1, + Regtest = 2, +} +/** + */ +export class InvoiceSignature { + free(): void; + /** + * @returns {Uint8Array} + */ + get_signature(): Uint8Array; + /** + * @returns {number} + */ + get_recovery_id(): number; +} +/** + */ +export class LightsparkSigner { + free(): void; + /** + * @param {Seed} seed + * @param {number} network + * @returns {LightsparkSigner} + */ + static new(seed: Seed, network: number): LightsparkSigner; + /** + * @param {Uint8Array} seed + * @param {number} network + * @returns {LightsparkSigner} + */ + static from_bytes(seed: Uint8Array, network: number): LightsparkSigner; + /** + * @returns {string} + */ + get_master_public_key(): string; + /** + * @param {string} derivation_path + * @returns {string} + */ + derive_public_key(derivation_path: string): string; + /** + * @param {Uint8Array} message + * @param {string} derivation_path + * @param {boolean} is_raw + * @param {Uint8Array | undefined} add_tweak + * @param {Uint8Array | undefined} mul_tweak + * @returns {Uint8Array} + */ + derive_key_and_sign( + message: Uint8Array, + derivation_path: string, + is_raw: boolean, + add_tweak?: Uint8Array, + mul_tweak?: Uint8Array, + ): Uint8Array; + /** + * @param {Uint8Array} public_key + * @returns {Uint8Array} + */ + ecdh(public_key: Uint8Array): Uint8Array; + /** + * @param {string} derivation_path + * @param {bigint} per_commitment_point_idx + * @returns {Uint8Array} + */ + get_per_commitment_point( + derivation_path: string, + per_commitment_point_idx: bigint, + ): Uint8Array; + /** + * @param {string} derivation_path + * @param {bigint} per_commitment_point_idx + * @returns {Uint8Array} + */ + release_per_commitment_secret( + derivation_path: string, + per_commitment_point_idx: bigint, + ): Uint8Array; + /** + * @returns {Uint8Array} + */ + generate_preimage_nonce(): Uint8Array; + /** + * @param {Uint8Array} nonce + * @returns {Uint8Array} + */ + generate_preimage(nonce: Uint8Array): Uint8Array; + /** + * @param {Uint8Array} nonce + * @returns {Uint8Array} + */ + generate_preimage_hash(nonce: Uint8Array): Uint8Array; + /** + * @param {string} derivation_path + * @returns {string} + */ + derive_private_key(derivation_path: string): string; + /** + * @param {string} unsigned_invoice + * @returns {InvoiceSignature} + */ + sign_invoice_wasm(unsigned_invoice: string): InvoiceSignature; + /** + * @param {Uint8Array} invoice_hash + * @returns {InvoiceSignature} + */ + sign_invoice_hash_wasm(invoice_hash: Uint8Array): InvoiceSignature; +} +/** + */ +export class Mnemonic { + free(): void; + /** + * @returns {Mnemonic} + */ + static random(): Mnemonic; + /** + * @param {Uint8Array} entropy + * @returns {Mnemonic} + */ + static from_entropy(entropy: Uint8Array): Mnemonic; + /** + * @param {string} phrase + * @returns {Mnemonic} + */ + static from_phrase(phrase: string): Mnemonic; + /** + * @returns {string} + */ + as_string(): string; +} +/** + */ +export class Seed { + free(): void; + /** + * @param {Mnemonic} mnemonic + * @returns {Seed} + */ + static from_mnemonic(mnemonic: Mnemonic): Seed; + /** + * @param {Uint8Array} seed + * @returns {Seed} + */ + static new(seed: Uint8Array): Seed; + /** + * @returns {Uint8Array} + */ + as_bytes(): Uint8Array; +} diff --git a/packages/lightspark-sdk/src/lightspark_crypto/lightspark_crypto.js b/packages/lightspark-sdk/src/lightspark_crypto/lightspark_crypto.js new file mode 100644 index 000000000..e36ef86bb --- /dev/null +++ b/packages/lightspark-sdk/src/lightspark_crypto/lightspark_crypto.js @@ -0,0 +1,1010 @@ +let imports = {}; +imports["__wbindgen_placeholder__"] = module.exports; +let wasm; +const { TextDecoder, TextEncoder } = require(`util`); + +let cachedTextDecoder = new TextDecoder("utf-8", { + ignoreBOM: true, + fatal: true, +}); + +cachedTextDecoder.decode(); + +let cachedUint8Memory0 = null; + +function getUint8Memory0() { + if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +const heap = new Array(128).fill(undefined); + +heap.push(undefined, null, true, false); + +let heap_next = heap.length; + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function getObject(idx) { + return heap[idx]; +} + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +let cachedInt32Memory0 = null; + +function getInt32Memory0() { + if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachedInt32Memory0; +} + +let WASM_VECTOR_LEN = 0; + +function passArray8ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 1, 1) >>> 0; + getUint8Memory0().set(arg, ptr / 1); + WASM_VECTOR_LEN = arg.length; + return ptr; +} + +let cachedTextEncoder = new TextEncoder("utf-8"); + +const encodeString = + typeof cachedTextEncoder.encodeInto === "function" + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); + } + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length, + }; + }; + +function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8Memory0() + .subarray(ptr, ptr + buf.length) + .set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8Memory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7f) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0; + const view = getUint8Memory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function _assertClass(instance, klass) { + if (!(instance instanceof klass)) { + throw new Error(`expected instance of ${klass.name}`); + } + return instance.ptr; +} + +function getArrayU8FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); +} + +function isLikeNone(x) { + return x === undefined || x === null; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } +} +/** + */ +module.exports.Network = Object.freeze({ + Bitcoin: 0, + 0: "Bitcoin", + Testnet: 1, + 1: "Testnet", + Regtest: 2, + 2: "Regtest", +}); +/** + */ +class InvoiceSignature { + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(InvoiceSignature.prototype); + obj.__wbg_ptr = ptr; + + return obj; + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_invoicesignature_free(ptr); + } + /** + * @returns {Uint8Array} + */ + get_signature() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.invoicesignature_get_signature(retptr, this.__wbg_ptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var v1 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1); + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {number} + */ + get_recovery_id() { + const ret = wasm.invoicesignature_get_recovery_id(this.__wbg_ptr); + return ret; + } +} +module.exports.InvoiceSignature = InvoiceSignature; +/** + */ +class LightsparkSigner { + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(LightsparkSigner.prototype); + obj.__wbg_ptr = ptr; + + return obj; + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_lightsparksigner_free(ptr); + } + /** + * @param {Seed} seed + * @param {number} network + * @returns {LightsparkSigner} + */ + static new(seed, network) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + _assertClass(seed, Seed); + wasm.lightsparksigner_new(retptr, seed.__wbg_ptr, network); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return LightsparkSigner.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {Uint8Array} seed + * @param {number} network + * @returns {LightsparkSigner} + */ + static from_bytes(seed, network) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray8ToWasm0(seed, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.lightsparksigner_from_bytes(retptr, ptr0, len0, network); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return LightsparkSigner.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {string} + */ + get_master_public_key() { + let deferred2_0; + let deferred2_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.lightsparksigner_get_master_public_key(retptr, this.__wbg_ptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + var ptr1 = r0; + var len1 = r1; + if (r3) { + ptr1 = 0; + len1 = 0; + throw takeObject(r2); + } + deferred2_0 = ptr1; + deferred2_1 = len1; + return getStringFromWasm0(ptr1, len1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); + } + } + /** + * @param {string} derivation_path + * @returns {string} + */ + derive_public_key(derivation_path) { + let deferred3_0; + let deferred3_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0( + derivation_path, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + const len0 = WASM_VECTOR_LEN; + wasm.lightsparksigner_derive_public_key( + retptr, + this.__wbg_ptr, + ptr0, + len0, + ); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + var ptr2 = r0; + var len2 = r1; + if (r3) { + ptr2 = 0; + len2 = 0; + throw takeObject(r2); + } + deferred3_0 = ptr2; + deferred3_1 = len2; + return getStringFromWasm0(ptr2, len2); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_free(deferred3_0, deferred3_1, 1); + } + } + /** + * @param {Uint8Array} message + * @param {string} derivation_path + * @param {boolean} is_raw + * @param {Uint8Array | undefined} add_tweak + * @param {Uint8Array | undefined} mul_tweak + * @returns {Uint8Array} + */ + derive_key_and_sign(message, derivation_path, is_raw, add_tweak, mul_tweak) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray8ToWasm0(message, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0( + derivation_path, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + const len1 = WASM_VECTOR_LEN; + var ptr2 = isLikeNone(add_tweak) + ? 0 + : passArray8ToWasm0(add_tweak, wasm.__wbindgen_malloc); + var len2 = WASM_VECTOR_LEN; + var ptr3 = isLikeNone(mul_tweak) + ? 0 + : passArray8ToWasm0(mul_tweak, wasm.__wbindgen_malloc); + var len3 = WASM_VECTOR_LEN; + wasm.lightsparksigner_derive_key_and_sign( + retptr, + this.__wbg_ptr, + ptr0, + len0, + ptr1, + len1, + is_raw, + ptr2, + len2, + ptr3, + len3, + ); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + if (r3) { + throw takeObject(r2); + } + var v5 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1); + return v5; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {Uint8Array} public_key + * @returns {Uint8Array} + */ + ecdh(public_key) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray8ToWasm0(public_key, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.lightsparksigner_ecdh(retptr, this.__wbg_ptr, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + if (r3) { + throw takeObject(r2); + } + var v2 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1); + return v2; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {string} derivation_path + * @param {bigint} per_commitment_point_idx + * @returns {Uint8Array} + */ + get_per_commitment_point(derivation_path, per_commitment_point_idx) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0( + derivation_path, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + const len0 = WASM_VECTOR_LEN; + wasm.lightsparksigner_get_per_commitment_point( + retptr, + this.__wbg_ptr, + ptr0, + len0, + per_commitment_point_idx, + ); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + if (r3) { + throw takeObject(r2); + } + var v2 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1); + return v2; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {string} derivation_path + * @param {bigint} per_commitment_point_idx + * @returns {Uint8Array} + */ + release_per_commitment_secret(derivation_path, per_commitment_point_idx) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0( + derivation_path, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + const len0 = WASM_VECTOR_LEN; + wasm.lightsparksigner_release_per_commitment_secret( + retptr, + this.__wbg_ptr, + ptr0, + len0, + per_commitment_point_idx, + ); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + if (r3) { + throw takeObject(r2); + } + var v2 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1); + return v2; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {Uint8Array} + */ + generate_preimage_nonce() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.lightsparksigner_generate_preimage_nonce(retptr, this.__wbg_ptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var v1 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1); + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {Uint8Array} nonce + * @returns {Uint8Array} + */ + generate_preimage(nonce) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray8ToWasm0(nonce, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.lightsparksigner_generate_preimage( + retptr, + this.__wbg_ptr, + ptr0, + len0, + ); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + if (r3) { + throw takeObject(r2); + } + var v2 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1); + return v2; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {Uint8Array} nonce + * @returns {Uint8Array} + */ + generate_preimage_hash(nonce) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray8ToWasm0(nonce, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.lightsparksigner_generate_preimage_hash( + retptr, + this.__wbg_ptr, + ptr0, + len0, + ); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + if (r3) { + throw takeObject(r2); + } + var v2 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1); + return v2; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {string} derivation_path + * @returns {string} + */ + derive_private_key(derivation_path) { + let deferred3_0; + let deferred3_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0( + derivation_path, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + const len0 = WASM_VECTOR_LEN; + wasm.lightsparksigner_derive_private_key( + retptr, + this.__wbg_ptr, + ptr0, + len0, + ); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + var ptr2 = r0; + var len2 = r1; + if (r3) { + ptr2 = 0; + len2 = 0; + throw takeObject(r2); + } + deferred3_0 = ptr2; + deferred3_1 = len2; + return getStringFromWasm0(ptr2, len2); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_free(deferred3_0, deferred3_1, 1); + } + } + /** + * @param {string} unsigned_invoice + * @returns {InvoiceSignature} + */ + sign_invoice_wasm(unsigned_invoice) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0( + unsigned_invoice, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + const len0 = WASM_VECTOR_LEN; + wasm.lightsparksigner_sign_invoice_wasm( + retptr, + this.__wbg_ptr, + ptr0, + len0, + ); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return InvoiceSignature.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {Uint8Array} invoice_hash + * @returns {InvoiceSignature} + */ + sign_invoice_hash_wasm(invoice_hash) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray8ToWasm0(invoice_hash, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.lightsparksigner_sign_invoice_hash_wasm( + retptr, + this.__wbg_ptr, + ptr0, + len0, + ); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return InvoiceSignature.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } +} +module.exports.LightsparkSigner = LightsparkSigner; +/** + */ +class Mnemonic { + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(Mnemonic.prototype); + obj.__wbg_ptr = ptr; + + return obj; + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_mnemonic_free(ptr); + } + /** + * @returns {Mnemonic} + */ + static random() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.mnemonic_random(retptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Mnemonic.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {Uint8Array} entropy + * @returns {Mnemonic} + */ + static from_entropy(entropy) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray8ToWasm0(entropy, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.mnemonic_from_entropy(retptr, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Mnemonic.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {string} phrase + * @returns {Mnemonic} + */ + static from_phrase(phrase) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0( + phrase, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + const len0 = WASM_VECTOR_LEN; + wasm.mnemonic_from_phrase(retptr, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Mnemonic.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {string} + */ + as_string() { + let deferred1_0; + let deferred1_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.mnemonic_as_string(retptr, this.__wbg_ptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + deferred1_0 = r0; + deferred1_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + } + } +} +module.exports.Mnemonic = Mnemonic; +/** + */ +class Seed { + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(Seed.prototype); + obj.__wbg_ptr = ptr; + + return obj; + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_seed_free(ptr); + } + /** + * @param {Mnemonic} mnemonic + * @returns {Seed} + */ + static from_mnemonic(mnemonic) { + _assertClass(mnemonic, Mnemonic); + const ret = wasm.seed_from_mnemonic(mnemonic.__wbg_ptr); + return Seed.__wrap(ret); + } + /** + * @param {Uint8Array} seed + * @returns {Seed} + */ + static new(seed) { + const ptr0 = passArray8ToWasm0(seed, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.seed_new(ptr0, len0); + return Seed.__wrap(ret); + } + /** + * @returns {Uint8Array} + */ + as_bytes() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.invoicesignature_get_signature(retptr, this.__wbg_ptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var v1 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1); + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } +} +module.exports.Seed = Seed; + +module.exports.__wbindgen_error_new = function (arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); +}; + +module.exports.__wbg_getRandomValues_37fa2ca9e4e07fab = function () { + return handleError(function (arg0, arg1) { + getObject(arg0).getRandomValues(getObject(arg1)); + }, arguments); +}; + +module.exports.__wbindgen_object_drop_ref = function (arg0) { + takeObject(arg0); +}; + +module.exports.__wbg_randomFillSync_dc1e9a60c158336d = function () { + return handleError(function (arg0, arg1) { + getObject(arg0).randomFillSync(takeObject(arg1)); + }, arguments); +}; + +module.exports.__wbg_crypto_c48a774b022d20ac = function (arg0) { + const ret = getObject(arg0).crypto; + return addHeapObject(ret); +}; + +module.exports.__wbindgen_is_object = function (arg0) { + const val = getObject(arg0); + const ret = typeof val === "object" && val !== null; + return ret; +}; + +module.exports.__wbg_process_298734cf255a885d = function (arg0) { + const ret = getObject(arg0).process; + return addHeapObject(ret); +}; + +module.exports.__wbg_versions_e2e78e134e3e5d01 = function (arg0) { + const ret = getObject(arg0).versions; + return addHeapObject(ret); +}; + +module.exports.__wbg_node_1cd7a5d853dbea79 = function (arg0) { + const ret = getObject(arg0).node; + return addHeapObject(ret); +}; + +module.exports.__wbindgen_is_string = function (arg0) { + const ret = typeof getObject(arg0) === "string"; + return ret; +}; + +module.exports.__wbg_msCrypto_bcb970640f50a1e8 = function (arg0) { + const ret = getObject(arg0).msCrypto; + return addHeapObject(ret); +}; + +module.exports.__wbg_require_8f08ceecec0f4fee = function () { + return handleError(function () { + const ret = module.require; + return addHeapObject(ret); + }, arguments); +}; + +module.exports.__wbindgen_is_function = function (arg0) { + const ret = typeof getObject(arg0) === "function"; + return ret; +}; + +module.exports.__wbindgen_string_new = function (arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); +}; + +module.exports.__wbg_newnoargs_581967eacc0e2604 = function (arg0, arg1) { + const ret = new Function(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); +}; + +module.exports.__wbg_call_cb65541d95d71282 = function () { + return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments); +}; + +module.exports.__wbindgen_object_clone_ref = function (arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); +}; + +module.exports.__wbg_self_1ff1d729e9aae938 = function () { + return handleError(function () { + const ret = self.self; + return addHeapObject(ret); + }, arguments); +}; + +module.exports.__wbg_window_5f4faef6c12b79ec = function () { + return handleError(function () { + const ret = window.window; + return addHeapObject(ret); + }, arguments); +}; + +module.exports.__wbg_globalThis_1d39714405582d3c = function () { + return handleError(function () { + const ret = globalThis.globalThis; + return addHeapObject(ret); + }, arguments); +}; + +module.exports.__wbg_global_651f05c6a0944d1c = function () { + return handleError(function () { + const ret = global.global; + return addHeapObject(ret); + }, arguments); +}; + +module.exports.__wbindgen_is_undefined = function (arg0) { + const ret = getObject(arg0) === undefined; + return ret; +}; + +module.exports.__wbg_call_01734de55d61e11d = function () { + return handleError(function (arg0, arg1, arg2) { + const ret = getObject(arg0).call(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }, arguments); +}; + +module.exports.__wbg_buffer_085ec1f694018c4f = function (arg0) { + const ret = getObject(arg0).buffer; + return addHeapObject(ret); +}; + +module.exports.__wbg_newwithbyteoffsetandlength_6da8e527659b86aa = function ( + arg0, + arg1, + arg2, +) { + const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); +}; + +module.exports.__wbg_new_8125e318e6245eed = function (arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); +}; + +module.exports.__wbg_set_5cf90238115182c3 = function (arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); +}; + +module.exports.__wbg_newwithlength_e5d69174d6984cd7 = function (arg0) { + const ret = new Uint8Array(arg0 >>> 0); + return addHeapObject(ret); +}; + +module.exports.__wbg_subarray_13db269f57aa838d = function (arg0, arg1, arg2) { + const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); +}; + +module.exports.__wbindgen_throw = function (arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +}; + +module.exports.__wbindgen_memory = function () { + const ret = wasm.memory; + return addHeapObject(ret); +}; + +const path = require("path").join(__dirname, "lightspark_crypto_bg.wasm"); +const bytes = require("fs").readFileSync(path); + +const wasmModule = new WebAssembly.Module(bytes); +const wasmInstance = new WebAssembly.Instance(wasmModule, imports); +wasm = wasmInstance.exports; +module.exports.__wasm = wasm; diff --git a/packages/lightspark-sdk/src/lightspark_crypto/lightspark_crypto_bg.wasm b/packages/lightspark-sdk/src/lightspark_crypto/lightspark_crypto_bg.wasm new file mode 100644 index 000000000..b1288f85c Binary files /dev/null and b/packages/lightspark-sdk/src/lightspark_crypto/lightspark_crypto_bg.wasm differ diff --git a/packages/lightspark-sdk/src/lightspark_crypto/lightspark_crypto_bg.wasm.d.ts b/packages/lightspark-sdk/src/lightspark_crypto/lightspark_crypto_bg.wasm.d.ts new file mode 100644 index 000000000..523315ae6 --- /dev/null +++ b/packages/lightspark-sdk/src/lightspark_crypto/lightspark_crypto_bg.wasm.d.ts @@ -0,0 +1,120 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export function __wbg_mnemonic_free(a: number): void; +export function mnemonic_random(a: number): void; +export function mnemonic_from_entropy(a: number, b: number, c: number): void; +export function mnemonic_from_phrase(a: number, b: number, c: number): void; +export function mnemonic_as_string(a: number, b: number): void; +export function __wbg_seed_free(a: number): void; +export function seed_from_mnemonic(a: number): number; +export function seed_new(a: number, b: number): number; +export function __wbg_invoicesignature_free(a: number): void; +export function invoicesignature_get_signature(a: number, b: number): void; +export function invoicesignature_get_recovery_id(a: number): number; +export function __wbg_lightsparksigner_free(a: number): void; +export function lightsparksigner_new(a: number, b: number, c: number): void; +export function lightsparksigner_from_bytes( + a: number, + b: number, + c: number, + d: number, +): void; +export function lightsparksigner_get_master_public_key( + a: number, + b: number, +): void; +export function lightsparksigner_derive_public_key( + a: number, + b: number, + c: number, + d: number, +): void; +export function lightsparksigner_derive_key_and_sign( + a: number, + b: number, + c: number, + d: number, + e: number, + f: number, + g: number, + h: number, + i: number, + j: number, + k: number, +): void; +export function lightsparksigner_ecdh( + a: number, + b: number, + c: number, + d: number, +): void; +export function lightsparksigner_get_per_commitment_point( + a: number, + b: number, + c: number, + d: number, + e: number, +): void; +export function lightsparksigner_release_per_commitment_secret( + a: number, + b: number, + c: number, + d: number, + e: number, +): void; +export function lightsparksigner_generate_preimage_nonce( + a: number, + b: number, +): void; +export function lightsparksigner_generate_preimage( + a: number, + b: number, + c: number, + d: number, +): void; +export function lightsparksigner_generate_preimage_hash( + a: number, + b: number, + c: number, + d: number, +): void; +export function lightsparksigner_derive_private_key( + a: number, + b: number, + c: number, + d: number, +): void; +export function lightsparksigner_sign_invoice_wasm( + a: number, + b: number, + c: number, + d: number, +): void; +export function lightsparksigner_sign_invoice_hash_wasm( + a: number, + b: number, + c: number, + d: number, +): void; +export function seed_as_bytes(a: number, b: number): void; +export function rustsecp256k1_v0_8_1_context_create(a: number): number; +export function rustsecp256k1_v0_8_1_context_destroy(a: number): void; +export function rustsecp256k1_v0_8_1_default_illegal_callback_fn( + a: number, + b: number, +): void; +export function rustsecp256k1_v0_8_1_default_error_callback_fn( + a: number, + b: number, +): void; +export function __wbindgen_add_to_stack_pointer(a: number): number; +export function __wbindgen_malloc(a: number, b: number): number; +export function __wbindgen_realloc( + a: number, + b: number, + c: number, + d: number, +): number; +export function __wbindgen_free(a: number, b: number, c: number): void; +export function __wbindgen_exn_store(a: number): void; diff --git a/packages/lightspark-sdk/src/lightspark_crypto/package.json b/packages/lightspark-sdk/src/lightspark_crypto/package.json new file mode 100644 index 000000000..c574ed8c2 --- /dev/null +++ b/packages/lightspark-sdk/src/lightspark_crypto/package.json @@ -0,0 +1,11 @@ +{ + "name": "lightspark-crypto", + "version": "0.1.0", + "files": [ + "lightspark_crypto_bg.wasm", + "lightspark_crypto.js", + "lightspark_crypto.d.ts" + ], + "main": "lightspark_crypto.js", + "types": "lightspark_crypto.d.ts" +} diff --git a/packages/vite/index.cjs b/packages/vite/index.cjs index f30004b74..c5fb50f8a 100644 --- a/packages/vite/index.cjs +++ b/packages/vite/index.cjs @@ -59,6 +59,8 @@ module.exports.buildConfig = ({ port = 3000, base = "/", dirname }) => resolve: { alias: { src: path.resolve(dirname, "./src"), + // We need createHash in core which is used by browser projects (see: https://github.com/huggingface/huggingface.js/issues/137) + crypto: 'crypto-browserify', }, }, }); diff --git a/packages/wallet-sdk/src/client.ts b/packages/wallet-sdk/src/client.ts index 4e7401060..8370d626a 100644 --- a/packages/wallet-sdk/src/client.ts +++ b/packages/wallet-sdk/src/client.ts @@ -12,6 +12,7 @@ import { LightsparkException, NodeKeyCache, Requester, + SigningKeyType, StubAuthProvider, } from "@lightsparkdev/core"; import autoBind from "auto-bind"; @@ -99,11 +100,13 @@ class LightsparkClient { * @param serverUrl The base URL of the server to connect to. Defaults to lightspark production. * @param cryptoImpl The crypto implementation to use. Defaults to web and node compatible crypto. * For React Native, you should use the `ReactNativeCrypto` implementation from `@lightsparkdev/react-native`. + * @param signingKeyType The type of signing key used in the LightsparkClient. Different signing operations are used depending on the key type. */ constructor( private authProvider: AuthProvider = new StubAuthProvider(), private readonly serverUrl: string = "api.lightspark.com", private readonly cryptoImpl: CryptoInterface = DefaultCrypto, + private readonly signingKeyType: SigningKeyType = SigningKeyType.RSASigningKey, ) { this.nodeKeyCache = new NodeKeyCache(this.cryptoImpl); this.requester = new Requester( @@ -687,11 +690,15 @@ class LightsparkClient { * application outside of the SDK. It is the responsibility of the application to ensure that the key is valid and * that it is the correct key for the wallet. Otherwise signed requests will fail. * - * @param sigingKeyBytesOrAlias An object holding either the PEM encoded bytes of the wallet's private signing key or, + * @param signingKeyBytesOrAlias An object holding either the PEM encoded bytes of the wallet's private signing key or, * in the case of ReactNative, the alias of the key in the mobile keychain. */ - public loadWalletSigningKey(sigingKeyBytesOrAlias: KeyOrAliasType) { - return this.nodeKeyCache.loadKey(WALLET_NODE_ID_KEY, sigingKeyBytesOrAlias); + public loadWalletSigningKey(signingKeyBytesOrAlias: KeyOrAliasType) { + return this.nodeKeyCache.loadKey( + WALLET_NODE_ID_KEY, + signingKeyBytesOrAlias, + SigningKeyType.RSASigningKey, + ); } /**