shogun-core
Version:
SHOGUN SDK - Core library for Shogun SDK
416 lines (415 loc) • 17.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NostrConnectorPlugin = void 0;
const base_1 = require("../base");
const nostrConnector_1 = require("./nostrConnector");
const nostrSigner_1 = require("./nostrSigner");
const logger_1 = require("../../utils/logger");
const errorHandler_1 = require("../../utils/errorHandler");
/**
* Plugin for managing Bitcoin wallet functionality in ShogunCore
* Supports Alby, Nostr extensions, or direct key management
*/
class NostrConnectorPlugin extends base_1.BasePlugin {
name = "nostr";
version = "1.0.0";
description = "Provides Bitcoin wallet connection and authentication for ShogunCore";
bitcoinConnector = null;
signer = null;
/**
* @inheritdoc
*/
initialize(core) {
super.initialize(core);
// Initialize the Bitcoin wallet module
this.bitcoinConnector = new nostrConnector_1.NostrConnector();
this.signer = new nostrSigner_1.NostrSigner(this.bitcoinConnector);
(0, logger_1.log)("Bitcoin wallet plugin initialized with signer support");
}
/**
* @inheritdoc
*/
destroy() {
if (this.bitcoinConnector) {
this.bitcoinConnector.cleanup();
}
this.bitcoinConnector = null;
this.signer = null;
super.destroy();
(0, logger_1.log)("Bitcoin wallet plugin destroyed");
}
/**
* Ensure that the Bitcoin wallet module is initialized
* @private
*/
assertBitcoinConnector() {
this.assertInitialized();
if (!this.bitcoinConnector) {
throw new Error("Bitcoin wallet module not initialized");
}
return this.bitcoinConnector;
}
/**
* Assicura che il signer sia inizializzato
* @private
*/
assertSigner() {
this.assertInitialized();
if (!this.signer) {
throw new Error("Nostr signer not initialized");
}
return this.signer;
}
/**
* @inheritdoc
*/
isAvailable() {
return this.assertBitcoinConnector().isAvailable();
}
/**
* Check if Alby extension is available
* Note: Alby is deprecated in favor of Nostr
*/
isAlbyAvailable() {
(0, logger_1.log)("Alby is deprecated, using Nostr instead");
return this.isNostrExtensionAvailable();
}
/**
* Check if Nostr extension is available
*/
isNostrExtensionAvailable() {
return this.assertBitcoinConnector().isNostrExtensionAvailable();
}
/**
* @inheritdoc
*/
async connectBitcoinWallet(type = "nostr") {
// Prioritize nostr over alby (since they are functionally identical)
// If type is alby, try to use nostr instead
if (type === "alby") {
(0, logger_1.log)("Alby is deprecated, using Nostr instead");
type = "nostr";
}
return this.assertBitcoinConnector().connectWallet(type);
}
/**
* @inheritdoc
*/
async generateCredentials(address) {
(0, logger_1.log)("Calling credential generation for Bitcoin wallet");
return this.assertBitcoinConnector().generateCredentials(address);
}
/**
* @inheritdoc
*/
cleanup() {
this.assertBitcoinConnector().cleanup();
}
/**
* Clear signature cache for better user recovery
* @param address - Optional specific address to clear, or clear all if not provided
*/
clearSignatureCache(address) {
this.assertBitcoinConnector().clearSignatureCache(address);
}
/**
* @inheritdoc
*/
async verifySignature(message, signature, address) {
return this.assertBitcoinConnector().verifySignature(message, signature, address);
}
/**
* @inheritdoc
*/
async generatePassword(signature) {
return this.assertBitcoinConnector().generatePassword(signature);
}
// === NOSTR SIGNER METHODS ===
/**
* Creates a new Nostr signing credential
* CONSISTENT with normal Nostr approach
*/
async createSigningCredential(address) {
try {
(0, logger_1.log)(`Creating Nostr signing credential for address: ${address}`);
return await this.assertSigner().createSigningCredential(address);
}
catch (error) {
(0, logger_1.logError)(`Error creating Nostr signing credential: ${error.message}`);
throw error;
}
}
/**
* Creates an authenticator function for Nostr signing
*/
createAuthenticator(address) {
try {
(0, logger_1.log)(`Creating Nostr authenticator for address: ${address}`);
return this.assertSigner().createAuthenticator(address);
}
catch (error) {
(0, logger_1.logError)(`Error creating Nostr authenticator: ${error.message}`);
throw error;
}
}
/**
* Creates a derived key pair from Nostr credential
*/
async createDerivedKeyPair(address, extra) {
try {
(0, logger_1.log)(`Creating derived key pair for address: ${address}`);
return await this.assertSigner().createDerivedKeyPair(address, extra);
}
catch (error) {
(0, logger_1.logError)(`Error creating derived key pair: ${error.message}`);
throw error;
}
}
/**
* Signs data with derived keys after Nostr verification
*/
async signWithDerivedKeys(data, address, extra) {
try {
(0, logger_1.log)(`Signing data with derived keys for address: ${address}`);
return await this.assertSigner().signWithDerivedKeys(data, address, extra);
}
catch (error) {
(0, logger_1.logError)(`Error signing with derived keys: ${error.message}`);
throw error;
}
}
/**
* Get signing credential by address
*/
getSigningCredential(address) {
return this.assertSigner().getCredential(address);
}
/**
* List all signing credentials
*/
listSigningCredentials() {
return this.assertSigner().listCredentials();
}
/**
* Remove a signing credential
*/
removeSigningCredential(address) {
return this.assertSigner().removeCredential(address);
}
// === CONSISTENCY METHODS ===
/**
* Creates a Gun user from Nostr signing credential
* This ensures the SAME user is created as with normal approach
*/
async createGunUserFromSigningCredential(address) {
try {
const core = this.assertInitialized();
(0, logger_1.log)(`Creating Gun user from Nostr signing credential: ${address}`);
return await this.assertSigner().createGunUser(address, core.gun);
}
catch (error) {
(0, logger_1.logError)(`Error creating Gun user from Nostr signing credential: ${error.message}`);
throw error;
}
}
/**
* Get the Gun user public key for a signing credential
*/
getGunUserPubFromSigningCredential(address) {
return this.assertSigner().getGunUserPub(address);
}
/**
* Get the password (for consistency checking)
*/
getPassword(address) {
return this.assertSigner().getPassword(address);
}
/**
* Verify consistency between oneshot and normal approaches
* This ensures both approaches create the same Gun user
*/
async verifyConsistency(address, expectedUserPub) {
try {
(0, logger_1.log)(`Verifying Nostr consistency for address: ${address}`);
return await this.assertSigner().verifyConsistency(address, expectedUserPub);
}
catch (error) {
(0, logger_1.logError)(`Error verifying Nostr consistency: ${error.message}`);
return { consistent: false };
}
}
/**
* Complete oneshot workflow that creates the SAME Gun user as normal approach
* This is the recommended method for oneshot signing with full consistency
*/
async setupConsistentOneshotSigning(address) {
try {
(0, logger_1.log)(`Setting up consistent Nostr oneshot signing for: ${address}`);
// 1. Create signing credential (with consistent password generation)
const credential = await this.createSigningCredential(address);
// 2. Create authenticator
const authenticator = this.createAuthenticator(address);
// 3. Create Gun user (same as normal approach)
const gunUser = await this.createGunUserFromSigningCredential(address);
return {
credential,
authenticator,
gunUser,
username: credential.username,
password: credential.password,
};
}
catch (error) {
(0, logger_1.logError)(`Error setting up consistent Nostr oneshot signing: ${error.message}`);
throw error;
}
}
// === EXISTING METHODS ===
/**
* Login with Bitcoin wallet
* @param address - Bitcoin address
* @returns {Promise<AuthResult>} Authentication result
* @description Authenticates the user using Bitcoin wallet credentials after signature verification
*/
async login(address) {
(0, logger_1.log)("Login with Bitcoin wallet");
try {
const core = this.assertInitialized();
(0, logger_1.log)(`Bitcoin wallet login attempt for address: ${address}`);
if (!address) {
throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.VALIDATION, "ADDRESS_REQUIRED", "Bitcoin address required for login");
}
if (!this.isAvailable()) {
throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.ENVIRONMENT, "BITCOIN_WALLET_UNAVAILABLE", "No Bitcoin wallet available in the browser");
}
(0, logger_1.log)("Generating credentials for Bitcoin wallet login...");
const credentials = await this.generateCredentials(address);
if (!credentials?.username ||
!credentials?.password ||
!credentials.signature ||
!credentials.message) {
throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.AUTHENTICATION, "CREDENTIAL_GENERATION_FAILED", "Bitcoin wallet credentials not generated correctly or signature missing");
}
(0, logger_1.log)(`Credentials generated successfully. Username: ${credentials.username}`);
(0, logger_1.log)("Verifying Bitcoin wallet signature...");
const isValid = await this.verifySignature(credentials.message, credentials.signature, address);
if (!isValid) {
(0, logger_1.logError)(`Signature verification failed for address: ${address}`);
throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.SECURITY, "SIGNATURE_VERIFICATION_FAILED", "Bitcoin wallet signature verification failed");
}
(0, logger_1.log)("Bitcoin wallet signature verified successfully.");
// Set authentication method to nostr before login
core.setAuthMethod("nostr");
// Use core's login method with direct GunDB authentication
(0, logger_1.log)("Logging in using core login method...");
const loginResult = await core.login(credentials.username, credentials.password);
if (!loginResult.success) {
throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.AUTHENTICATION, "BITCOIN_LOGIN_FAILED", loginResult.error || "Failed to log in with Bitcoin credentials");
}
// Emit login event
core.emit("auth:login", {
userPub: loginResult.userPub,
username: credentials.username,
method: "bitcoin",
});
return loginResult;
}
catch (error) {
// Handle both ShogunError and generic errors
const errorType = error?.type || errorHandler_1.ErrorType.AUTHENTICATION;
const errorCode = error?.code || "BITCOIN_LOGIN_ERROR";
const errorMessage = error?.message || "Unknown error during Bitcoin wallet login";
const handledError = errorHandler_1.ErrorHandler.handle(errorType, errorCode, errorMessage, error);
return {
success: false,
error: handledError.message,
};
}
}
/**
* Register a new user with Bitcoin wallet
* @param address - Bitcoin address
* @returns {Promise<AuthResult>} Registration result
* @description Creates a new user account with Bitcoin wallet credentials
*/
async signUp(address) {
(0, logger_1.log)("Sign up with Bitcoin wallet");
try {
const core = this.assertInitialized();
(0, logger_1.log)(`Bitcoin wallet signup attempt for address: ${address}`);
if (!address) {
throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.VALIDATION, "ADDRESS_REQUIRED", "Bitcoin address required for signup");
}
if (!this.isAvailable()) {
throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.ENVIRONMENT, "BITCOIN_WALLET_UNAVAILABLE", "No Bitcoin wallet available in the browser");
}
(0, logger_1.log)("Generating credentials for Bitcoin wallet signup...");
const credentials = await this.generateCredentials(address);
if (!credentials?.username ||
!credentials?.password ||
!credentials.signature ||
!credentials.message) {
throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.AUTHENTICATION, "CREDENTIAL_GENERATION_FAILED", "Bitcoin wallet credentials not generated correctly or signature missing");
}
(0, logger_1.log)(`Credentials generated successfully. Username: ${credentials.username}`);
// Verify signature
(0, logger_1.log)("Verifying Bitcoin wallet signature...");
const isValid = await this.verifySignature(credentials.message, credentials.signature, address);
if (!isValid) {
(0, logger_1.logError)(`Signature verification failed for address: ${address}`);
throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.SECURITY, "SIGNATURE_VERIFICATION_FAILED", "Bitcoin wallet signature verification failed");
}
(0, logger_1.log)("Bitcoin wallet signature verified successfully.");
// Set authentication method to nostr before signup
core.setAuthMethod("nostr");
// Use core's signUp method with direct GunDB authentication
(0, logger_1.log)("Signing up using core signUp method...");
const signUpResult = await core.signUp(credentials.username, credentials.password);
if (!signUpResult.success) {
// Check if the error is "User already created"
if (signUpResult.error &&
(signUpResult.error.includes("User already created") ||
signUpResult.error.includes("already created") ||
signUpResult.error.includes("già creato"))) {
// User already exists, suggest login instead
return {
success: false,
error: "User already exists. Please try logging in instead. If login fails, try clearing the signature cache and registering again.",
};
}
throw (0, errorHandler_1.createError)(errorHandler_1.ErrorType.AUTHENTICATION, "BITCOIN_SIGNUP_FAILED", signUpResult.error || "Failed to sign up with Bitcoin credentials");
}
// Emit signup event
core.emit("auth:signup", {
userPub: signUpResult.userPub,
username: credentials.username,
method: "bitcoin",
});
return signUpResult;
}
catch (error) {
// Handle both ShogunError and generic errors
const errorType = error?.type || errorHandler_1.ErrorType.AUTHENTICATION;
const errorCode = error?.code || "BITCOIN_SIGNUP_ERROR";
const errorMessage = error?.message || "Unknown error during Bitcoin wallet signup";
const handledError = errorHandler_1.ErrorHandler.handle(errorType, errorCode, errorMessage, error);
return {
success: false,
error: handledError.message,
};
}
}
/**
* Convenience method that matches the interface pattern
*/
async loginWithBitcoinWallet(address) {
return this.login(address);
}
/**
* Convenience method that matches the interface pattern
*/
async signUpWithBitcoinWallet(address) {
return this.signUp(address);
}
}
exports.NostrConnectorPlugin = NostrConnectorPlugin;