diff --git a/database/integrity/0013-test.ts.keep b/database/integrity/0013-test.ts.keep new file mode 100644 index 000000000..b020c0657 --- /dev/null +++ b/database/integrity/0013-test.ts.keep @@ -0,0 +1,86 @@ +/* MIGRATION TO CLEAN PRODUCTION DATA + * + * the way the passphrases are stored in login_user_backups is inconsistent. + * we need to try to detect which word list was used and transform it accordingly + */ + +import fs from 'fs' + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const sodium = require('sodium-native') + +const PHRASE_WORD_COUNT = 24 +const WORDS = fs + .readFileSync('src/config/mnemonic.uncompressed_buffer13116.txt') + .toString() + .split(',') + +const KeyPairEd25519Create = (passphrase: string[]): Buffer[] => { + if (!passphrase.length || passphrase.length < PHRASE_WORD_COUNT) { + throw new Error('passphrase empty or to short') + } + + const state = Buffer.alloc(sodium.crypto_hash_sha512_STATEBYTES) + sodium.crypto_hash_sha512_init(state) + + for (let i = 0; i < PHRASE_WORD_COUNT; i++) { + const value = Buffer.alloc(8) + const wordIndex = WORDS.indexOf(passphrase[i]) + value.writeBigInt64LE(BigInt(wordIndex)) + sodium.crypto_hash_sha512_update(state, value) + } + // trailing space is part of the login_server implementation + const clearPassphrase = passphrase.slice(0, PHRASE_WORD_COUNT).join(' ') + ' ' + sodium.crypto_hash_sha512_update(state, Buffer.from(clearPassphrase)) + const outputHashBuffer = Buffer.alloc(sodium.crypto_hash_sha512_BYTES) + sodium.crypto_hash_sha512_final(state, outputHashBuffer) + + const pubKey = Buffer.alloc(sodium.crypto_sign_PUBLICKEYBYTES) + const privKey = Buffer.alloc(sodium.crypto_sign_SECRETKEYBYTES) + + sodium.crypto_sign_seed_keypair( + pubKey, + privKey, + outputHashBuffer.slice(0, sodium.crypto_sign_SEEDBYTES), + ) + + return [pubKey, privKey] +} + +export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + // Delete data with no reference in login_users table + // eslint-disable-next-line no-console + // 663 affected rows + const userBackups = await queryFn( + `SELECT passphrase, LOWER(HEX(pubkey)) as pubkey, user_id + FROM login_user_backups + LEFT JOIN login_users ON login_user_backups.user_id = login_users.id + WHERE user_id=1503`, + // WHERE pubkey is not null`, // todo fix this condition and regenerate + ) + let i = 0 + // eslint-disable-next-line no-console + userBackups.forEach(async (userBackup) => { + const passphrase = userBackup.passphrase.split(' ') + const keyPair = KeyPairEd25519Create(passphrase) + if (keyPair[0].toString('hex') !== userBackup.pubkey) { + i++ + // eslint-disable-next-line no-console + console.log( + 'Missmatch Pubkey', + i, + userBackup.user_id, + `"${userBackup.passphrase}"`, + `"${keyPair[0].toString('hex')}`, + `"${userBackup.pubkey}"`, + ) + } else { + // eslint-disable-next-line no-console + // console.log('SUCCESS: ', `"${keyPair[0].toString('hex')}`, `"${userBackup.pubkey}"`) + } + }) +} + +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + return [] // cannot transform things back +} \ No newline at end of file diff --git a/database/integrity/README.md b/database/integrity/README.md new file mode 100644 index 000000000..c960a270e --- /dev/null +++ b/database/integrity/README.md @@ -0,0 +1,5 @@ +This is a test to find if all passphrases evaluate to the saved public key. + +You need `yarn add sodium-native` in order to make it work. + +This could be the start of database integrity tests in oder to evaluate the correctness of the database \ No newline at end of file