diff --git a/CHANGELOG.fr.md b/CHANGELOG.fr.md index 6190c5ae2336e773f922ba569dd8a47b8719907b..49cb6007aa0836a072cfeb6a7db611b6ab4c8ddd 100644 --- a/CHANGELOG.fr.md +++ b/CHANGELOG.fr.md @@ -13,6 +13,10 @@ et ce projet adhère au [versionnage sémantique](https://semver.org/spec/v2.0.0 ## [Non-publié/Non-Stabilisé] (par [1000i100]) +### Ajouté +- crypto.checkKey(pubKeyWithChecksum) +- crypto.pubKey2checksum(b58pubKey, optionalBool:b58viewDependant, optionalBool:checksumWithoutLeadingZero) + ## [Version 3.1.0] - 2021-04-01 (par [1000i100] & [Hugo]) ### Ajouté - génération du [format court d'affichage de pubKey](https://forum.duniter.org/t/format-de-checksum/7616) diff --git a/CI/vanityLike.mjs b/CI/vanityLike.mjs new file mode 100644 index 0000000000000000000000000000000000000000..783aea8c28b4d24a8a81c61946678655faa1916d --- /dev/null +++ b/CI/vanityLike.mjs @@ -0,0 +1,34 @@ +import * as app from "../src/crypto.mjs"; + +function main() { + for (let x = 1; true; x++) { + const strX = ('' + x).replace(/0/g, ''); + if ( + app.pubKey2checksum('pubKey' + strX) !== app.pubKey2checksum('pubKey' + strX, false, true) + && + app.pubKey2checksum('pubKey' + strX, true) !== app.pubKey2checksum('pubKey' + strX, true, true) + /*&& + app.pubKey2checksum('pubKey'+strX).includes('11') + && + app.pubKey2checksum('pubKey'+strX,true).includes('11') + */ + ) { + console.log('pubKey' + strX, + app.pubKey2checksum('pubKey' + strX), + app.pubKey2checksum('pubKey' + strX, false, true), + app.pubKey2checksum('pubKey' + strX, true), + app.pubKey2checksum('pubKey' + strX, true, true) + ); + console.log('11111111111111111111111pubKey49311', + app.pubKey2checksum('11111111111111111111111pubKey49311'), + app.pubKey2checksum('11111111111111111111111pubKey49311', false, true), + app.pubKey2checksum('11111111111111111111111pubKey49311', true), + app.pubKey2checksum('11111111111111111111111pubKey49311', true, true) + ); + return 'pubKey' + strX; + } + if (strX.includes('99999')) console.log('pubKey' + strX); + } +} + +main(); diff --git a/src/crypto.mjs b/src/crypto.mjs index 5c2173f9115a2a408be0507046e8789ff2f73297..e02e9d05f196fe74b545246f535da831c1357e70 100644 --- a/src/crypto.mjs +++ b/src/crypto.mjs @@ -1,15 +1,17 @@ -export {b58, saltPass2seed, seed2keyPair, idSecPass2rawAll, raw2b58, idSecPass2cleanKeys, pubKey2shortKey}; // Alt deps : import {generate_keypair} from "ecma-nacl/build/lib/signing/sign.js"; // Alt deps : import scrypt from "ecma-nacl/build/lib/scrypt/scrypt.js"; import nacl from '../generated/vendors/nacl.mjs'; import {b58} from './basex.mjs'; + +export {b58}; import sha from '../node_modules/js-sha256/src/sha256.mjs'; + const sha256 = sha(); const generateKeypair = nacl.sign.keyPair.fromSeed; import scrypt from '../generated/vendors/scrypt.mjs'; -async function idSecPass2rawAll(idSec, pass) { +export async function idSecPass2rawAll(idSec, pass) { const rawSeed = await saltPass2seed(idSec, pass); const keyPair = seed2keyPair(rawSeed); return { @@ -25,16 +27,16 @@ function raw2b58(raws) { return result; } -async function idSecPass2cleanKeys(idSec, pass) { +export async function idSecPass2cleanKeys(idSec, pass) { const raw = await idSecPass2rawAll(idSec, pass); return Object.assign(raw2b58(raw), {idSec, password: pass}); } -function seed2keyPair(seed) { +export function seed2keyPair(seed) { return generateKeypair(seed); } -async function saltPass2seed(idSec, pass) { +export async function saltPass2seed(idSec, pass) { const options = { logN: 12, r: 16, @@ -45,10 +47,41 @@ async function saltPass2seed(idSec, pass) { return scrypt(pass.normalize('NFKC'), idSec.normalize('NFKC'), options); } -function pubKey2shortKey(pubkey) { - const checksum = b58.encode(sha256.digest(sha256.digest(b58.decode(pubkey)))); - const pubKeyBegin = pubkey.substr(0, 4); - const pubKeyEnd = pubkey.substr(-4, 4); - const hashBegin = checksum.substr(0, 3); - return `${pubKeyBegin}…${pubKeyEnd}:${hashBegin}`; +export function pubKey2shortKey(pubKey) { + const pubKeyBegin = pubKey.substr(0, 4); + const pubKeyEnd = pubKey.substr(-4, 4); + const checksum = pubKey2checksum(pubKey); + return `${pubKeyBegin}…${pubKeyEnd}:${checksum}`; +} + +export function pubKey2checksum(b58pubKey, b58viewDependant = false, checksumWithoutLeadingZero = false) { + let binPubKey; + if (b58viewDependant) binPubKey = b58.decode(b58pubKey); + else binPubKey = b58pubKey2bin(b58pubKey); + const hash = sha256.digest(sha256.digest(binPubKey)); + if (checksumWithoutLeadingZero) { + let zero = 0 + while (hash[zero] === 0) zero++; + const shorterHash = hash.slice(zero); + return b58.encode(shorterHash).substr(0, 3); + } + return b58.encode(hash).substr(0, 3); +} + +function b58pubKey2bin(b58pubKey) { + const binPubKey = new Uint8Array(32); + const decoded = b58.decode(b58pubKey); + binPubKey.set(decoded, 32 - decoded.length); + return binPubKey; +} + +export function checkKey(pubKeyWithChecksum) { + const part = pubKeyWithChecksum.split(':'); + const b58pubKey = part[0]; + const checkSum = part[1]; + if (pubKey2checksum(b58pubKey) === checkSum) return true; + if (pubKey2checksum(b58pubKey, true) === checkSum) return true; + if (pubKey2checksum(b58pubKey, false, true) === checkSum) return true; + if (pubKey2checksum(b58pubKey, true, true) === checkSum) return true; + throw new Error('Bad checksum'); } diff --git a/src/crypto.test.mjs b/src/crypto.test.mjs index 439c6c10957f58864b20ee70567c23c758599726..6aa4f7ff646de71878261f350b883b09ea6e59da 100644 --- a/src/crypto.test.mjs +++ b/src/crypto.test.mjs @@ -1,5 +1,6 @@ import test from 'ava'; import * as app from './crypto.mjs'; +import {b58} from "./crypto.mjs"; const idSec = 'a'; const mdp = 'b'; @@ -8,9 +9,10 @@ const pubKey = 'AoxVA41dGL2s4ogMNdbCw3FFYjFo5FPK36LuiW1tjGbG'; const secretKey = '3ZsmZhnRv137dS1s7Q3jFGKLTDyhkwguPHfnWBxzDCTTHKWGnYw9zBk3gcCUJCc72TEUuyzM7cqpo7c5LYhs1Qtv'; const seed = '9eADqX8V6VcPdJCHCVYiE1Vnift9nFNrvr9aTaXA5RJc'; -test('b58 should decode/encode well', t => { - t.is(app.b58.encode(app.b58.decode(pubKey)), pubKey); -}); +test('b58 should decode/encode well', t => t.is(app.b58.encode(app.b58.decode(pubKey)), pubKey)); +test('b58 on pubKey with leading 1', t => t.is(app.b58.encode(app.b58.decode('12BjyvjoAf5qik7R8TKDJAHJugsX23YgJGi2LmBUv2nx')), '12BjyvjoAf5qik7R8TKDJAHJugsX23YgJGi2LmBUv2nx')); +test('b58 on pubKey without leading 1', t => t.is(app.b58.encode(app.b58.decode('2BjyvjoAf5qik7R8TKDJAHJugsX23YgJGi2LmBUv2nx')), '2BjyvjoAf5qik7R8TKDJAHJugsX23YgJGi2LmBUv2nx')); + test('saltPass2seed should convert salt & password to seed with scrypt', async t => { t.is(app.b58.encode(await app.saltPass2seed(idSec, mdp)), seed); }); @@ -38,3 +40,34 @@ test('pubKey2shortKey match RML1…zvSY:3k4', t => { const shortKey = 'RML1…zvSY:3k4'; t.is(app.pubKey2shortKey(pubKey), shortKey); }); +test('pubKey2checksum RML12butz : 3k4', t => t.is(app.pubKey2checksum('RML12butzV3xZmkWnNAmRwuepKPYvzQ4euHwhHhzvSY'), '3k4')); +test('pubKey2checksum 12Bj : 8pQ', t => t.is(app.pubKey2checksum('12BjyvjoAf5qik7R8TKDJAHJugsX23YgJGi2LmBUv2nx'), '8pQ')); +test('pubKey2checksum 2Bjy : 8pQ', t => t.is(app.pubKey2checksum('2BjyvjoAf5qik7R8TKDJAHJugsX23YgJGi2LmBUv2nx'), '8pQ')); +test('pubKey2checksum ascii 2Bjy : 5vi', t => t.is(app.pubKey2checksum('2BjyvjoAf5qik7R8TKDJAHJugsX23YgJGi2LmBUv2nx', true), '5vi')); +test('pubKey2checksum 1111 : 3ud', t => t.is(app.pubKey2checksum('11111111111111111111111111111111'), '3ud')); +test('pubKey2checksum "" : 3ud', t => t.is(app.pubKey2checksum(''), '3ud')); +test('pubKey2checksum 1pubKey542 : 1ML', t => t.is(app.pubKey2checksum('1pubKey542'), '1ML')); +test('pubKey2checksum pubKey542 : 1ML', t => t.is(app.pubKey2checksum('pubKey542'), '1ML')); +test('pubKey2checksum ascii 1111111111111111111111111pubKey542 : 1ML', t => t.is(app.pubKey2checksum('1111111111111111111111111pubKey542', true), '1ML')); +test('pubKey2checksum ascii 1pubKey542 : DSs', t => t.is(app.pubKey2checksum('1pubKey542', true), 'DSs')); +test('pubKey2checksum ascii pubKey542 : DEE', t => t.is(app.pubKey2checksum('pubKey542', true), 'DEE')); +test('pubKey2checksum checksumWithoutLeadingZero 1pubKey542 : MLT', t => t.is(app.pubKey2checksum('pubKey542', false, true), 'MLT')); + +test('checkKey pubKey542:1ML', t => t.true(app.checkKey('pubKey542:1ML'))); +test('checkKey pubKey542:MLT', t => t.true(app.checkKey('pubKey542:MLT'))); +test('checkKey pubKey542:DEE', t => t.true(app.checkKey('pubKey542:DEE'))); + +test('checkKey 11111111111111111111111pubKey49311:14R', t => t.true(app.checkKey('11111111111111111111111pubKey49311:14R'))); +test('checkKey 11111111111111111111111pubKey49311:4Ru', t => t.true(app.checkKey('11111111111111111111111pubKey49311:4Ru'))); +test('checkKey 111pubKey49311:14R', t => t.true(app.checkKey('111pubKey49311:14R'))); +test('checkKey 11pubKey49311:14R', t => t.true(app.checkKey('11pubKey49311:14R'))); +test('checkKey 1pubKey49311:14R', t => t.true(app.checkKey('1pubKey49311:14R'))); +test('checkKey pubKey49311:14R', t => t.true(app.checkKey('pubKey49311:14R'))); +test('checkKey pubKey49311:4Ru', t => t.true(app.checkKey('pubKey49311:4Ru'))); +test('checkKey pubKey49311:12p', t => t.true(app.checkKey('pubKey49311:12p'))); +test('checkKey pubKey49311:2p7', t => t.true(app.checkKey('pubKey49311:2p7'))); +test('checkKey false 11111111111111111111111pubKey49311:12p', t => t.throws(() => app.checkKey('11111111111111111111111pubKey49311:12p'))); +test('checkKey false 11111111111111111111111pubKey49311:2p7', t => t.throws(() => app.checkKey('11111111111111111111111pubKey49311:2p7'))); +test('checkKey false pubKey49311:111', t => t.throws(() => app.checkKey('pubKey49311:111'))); + +test('checkKey false 0pubKey49311:any', t => t.throws(() => app.checkKey('0pubKey49311:any')));