Skip to content

Instantly share code, notes, and snippets.

@crypt0miester
Last active November 1, 2024 13:25
Show Gist options
  • Save crypt0miester/54a799c1a704b262b63f7ab40e9d535d to your computer and use it in GitHub Desktop.
Save crypt0miester/54a799c1a704b262b63f7ab40e9d535d to your computer and use it in GitHub Desktop.
import { Keypair, PublicKey } from '@solana/web3.js';
import * as argon2 from 'argon2';
import crypto from 'crypto';
import { sign } from 'tweetnacl';
interface KeygenParams {
passCode: string;
appName: string;
userId?: string; // Optional unique identifier
}
export async function createDeterministicKeypair({
passCode,
appName = 'dappWebsite.io',
userId
}: KeygenParams): Promise<Keypair> {
// Validate passCode
const validation = validatePassCode(passCode);
if (!validation.isValid) {
throw new Error(`Invalid passCode: ${validation.error}`);
}
try {
// Normalize inputs
const sanitizedAppName = appName.replace(/[^a-zA-Z0-9.-]/g, '');
// Create a deterministic but unique salt per user/app
// This prevents rainbow table attacks while remaining deterministic
const saltBase = `${sanitizedAppName}:${userId || ''}:v1`; // v1 for version control
const deterministicSalt = crypto
.createHash('sha256')
.update(saltBase)
.digest();
// Use Argon2 with deterministic parameters
const derivedKey = await argon2.hash(passCode, {
salt: deterministicSalt,
type: argon2.argon2id,
memoryCost: 131072, // 128MB
timeCost: 4, // Number of iterations
parallelism: 1, // Single thread for determinism
hashLength: 32, // Length needed for Solana keypair
raw: true // Get raw buffer instead of encoded hash
});
// Create keypair from derived key
const newKey = Keypair.fromSeed(new Uint8Array(derivedKey));
// Only log in development
// if (process.env.NODE_ENV === 'development') {
console.log('Public Key:', newKey.publicKey.toString());
// }
return newKey;
} catch (error) {
throw new Error('Failed to create keypair');
}
}
// Optional: Add key verification function
export async function verifyDeterministicKey(
passCode: string,
publicKey: string,
appName: string,
userId?: string
): Promise<boolean> {
return createDeterministicKeypair({ passCode, appName, userId })
.then(keypair => keypair.publicKey.toString() === publicKey)
.catch(() => false);
}
// Helper function to create a recovery phrase from the passCode
export function generateRecoveryPhrase(passCode: string): string {
// Use a cryptographic hash to create a base for the recovery code
const hash = crypto.createHash('sha256').update(passCode).digest('hex');
// Split into groups of 4 for easier reading
return hash.match(/.{1,4}/g)?.join('-') || '';
}
export function verifySignature(
message: string,
signature: Uint8Array,
publicKey: PublicKey
): boolean {
try {
const encodedMessage = new TextEncoder().encode(message);
const publicKeyBytes = publicKey.toBytes();
const isValid = sign.detached.verify(
encodedMessage,
signature,
publicKeyBytes
);
return isValid;
} catch (error) {
console.error('Error verifying signature:', error);
return false;
}
}
interface ValidationResult {
isValid: boolean;
error?: string;
}
export function validatePassCode(passCode: string): ValidationResult {
// check for minimum length
if (passCode.length < 12) {
return {
isValid: false,
error: 'PassCode must be at least 12 characters long'
};
}
// check for maximum length to prevent potential issues
if (passCode.length > 64) {
return {
isValid: false,
error: 'PassCode must not exceed 64 characters'
};
}
// check for spaces (this check applies to all lengths)
if (/\s/.test(passCode)) {
return {
isValid: false,
error: 'PassCode must not contain spaces'
};
}
// passCode is 16 characters or longer, skip complexity checks
if (passCode.length >= 16) {
return { isValid: true };
}
// complex validation only for passCodes less than 16 characters
// check for uppercase letters
if (!/[A-Z]/.test(passCode)) {
return {
isValid: false,
error: 'PassCode must contain at least one uppercase letter'
};
}
// check for lowercase letters
if (!/[a-z]/.test(passCode)) {
return {
isValid: false,
error: 'PassCode must contain at least one lowercase letter'
};
}
// check for numbers
if (!/\d/.test(passCode)) {
return {
isValid: false,
error: 'PassCode must contain at least one number'
};
}
// check for special characters
if (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>/?]/.test(passCode)) {
return {
isValid: false,
error: 'PassCode must contain at least one special character'
};
}
// check for consecutive repeating characters
if (/(.)\1{2,}/.test(passCode)) {
return {
isValid: false,
error: 'PassCode must not contain three or more consecutive repeating characters'
};
}
return { isValid: true };
}
createDeterministicKeypair({
passCode: '12345678910111213',
appName: 'dappWebsite.io',
userId: "[email protected]"
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment