Last active
November 1, 2024 13:25
-
-
Save crypt0miester/54a799c1a704b262b63f7ab40e9d535d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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