diff --git a/CHANGELOG.fr.md b/CHANGELOG.fr.md
index 7f234a21371d08c1f00b9dc1227467691837b346..241bf645e94eaeb87193624ceb54a063c77a76c9 100644
--- a/CHANGELOG.fr.md
+++ b/CHANGELOG.fr.md
@@ -14,6 +14,10 @@ et ce projet adhère au [versionnage sémantique](https://semver.org/spec/v2.0.0
 
 ## [Non-publié/Non-Stabilisé] (par [1000i100])
 
+## [Version 3.5.6] - 2023-01-01 (par [1000i100])
+### Corrections
+- dictionary-parser génère désormais correctement les variantes désaccentuées et majuscule/minuscule, sans couplage entre identifiant secret et mot de passe.
+
 ## [Version 3.5.5] - 2022-12-31 (par [1000i100])
 ### Corrections
 - Dans dictionary et dictionary-parser, les différents modes autour des majuscules sont désormais strictement progressif : 0 tel quel, 1 TOUT MAJUSCULE + tout minuscule + Première Lettre En Majuscule, 2 comme 1 mais en passant en minuscule chaque caractère individuellement.
@@ -148,8 +152,10 @@ et ce projet adhère au [versionnage sémantique](https://semver.org/spec/v2.0.0
 - intégration des librairies de crypto nécessaires
 - calcul de la clef publique correspondant à chaque combinaison de secrets saisie, et comparaison à la clef publique de référence.
 
-[Non-publié/Non-Stabilisé]: https://git.duniter.org/libs/g1lib.js/-/compare/v3.5.4...main
+[Non-publié/Non-Stabilisé]: https://git.duniter.org/libs/g1lib.js/-/compare/v3.5.6...main
 
+[Version 3.5.6]: https://git.duniter.org/libs/g1lib.js/-/compare/v3.5.5...v3.5.6
+[Version 3.5.5]: https://git.duniter.org/libs/g1lib.js/-/compare/v3.5.4...v3.5.5
 [Version 3.5.4]: https://git.duniter.org/libs/g1lib.js/-/compare/v3.5.3...v3.5.4
 [Version 3.5.3]: https://git.duniter.org/libs/g1lib.js/-/compare/v3.5.2...v3.5.3
 [Version 3.5.2]: https://git.duniter.org/libs/g1lib.js/-/compare/v3.5.1...v3.5.2
diff --git a/npm/package.json b/npm/package.json
index 90d303c14dd2e67684b63489a6790bcf5fcfb33d..d9c026445f9ca13be174472fa902820d189a7ee9 100644
--- a/npm/package.json
+++ b/npm/package.json
@@ -1,6 +1,6 @@
 {
   "name": "g1lib",
-  "version": "3.5.5",
+  "version": "3.5.6",
   "description": "An ubiquitous static javascript toolbox lib for Ǧ1 / Duniter ecosystem with reliability in mind.",
   "main": "nodejs/all.mjs",
 	"browser": "browser/all.mjs",
diff --git a/src/dictionary-escaper.mjs b/src/dictionary-escaper.mjs
index 11155e58229b9db7b5024e2b7d8a513d96e6796b..bd62c08e6c792babffb1e43fe65fffd4646cc8a0 100644
--- a/src/dictionary-escaper.mjs
+++ b/src/dictionary-escaper.mjs
@@ -1,19 +1,19 @@
 const specialMap = {
-	'(': String.fromCharCode(0x2001),
-	')': String.fromCharCode(0x2002),
-	'|': String.fromCharCode(0x2003),
-	'{': String.fromCharCode(0x2004),
-	'}': String.fromCharCode(0x2005),
-	',': String.fromCharCode(0x2006),
-	'[': String.fromCharCode(0x2007),
-	']': String.fromCharCode(0x2008),
-	'-': String.fromCharCode(0x2009),
-	'<': String.fromCharCode(0x200a),
-	'>': String.fromCharCode(0x200b),
-	':': String.fromCharCode(0x200c),
-	'=': String.fromCharCode(0x200d),
-	'@': String.fromCharCode(0x200e),
-	'\\': String.fromCharCode(0x200f),
+	'(': String.fromCharCode(0x2300),
+	')': String.fromCharCode(0x2301),
+	'|': String.fromCharCode(0x2302),
+	'{': String.fromCharCode(0x2303),
+	'}': String.fromCharCode(0x2304),
+	',': String.fromCharCode(0x2305),
+	'[': String.fromCharCode(0x2306),
+	']': String.fromCharCode(0x2307),
+	'-': String.fromCharCode(0x2308),
+	'<': String.fromCharCode(0x2309),
+	'>': String.fromCharCode(0x230a),
+	':': String.fromCharCode(0x230b),
+	'=': String.fromCharCode(0x230c),
+	'@': String.fromCharCode(0x230d),
+	'\\': String.fromCharCode(0x230e),
 };
 const revertSpecial = swapKeyValue(specialMap);
 function swapKeyValue(object) {
diff --git a/src/dictionary-parser.mjs b/src/dictionary-parser.mjs
index a3d2da8a58bb6a353052df150bdd40988dc12099..277f56d76e989513230ff0f3b5bae4d5526636ce 100644
--- a/src/dictionary-parser.mjs
+++ b/src/dictionary-parser.mjs
@@ -11,10 +11,13 @@ export function parse(dictionaryString,options={}) {
 	if(typeof options.leetSpeak === 'undefined') options.leetSpeak = 0;
 	const escapeAllString = options.escapeAll===1?escapeAll(dictionaryString):options.escapeAll===2?`${dictionaryString}\n${escapeAll(dictionaryString)}`:dictionaryString;
 	const escapedString = escape2utfSpecial(escapeAllString);
-	const unbracketed = bracketsHandler(escapedString);
-	const allLines = unbracketed.split('\n');
+	const refConverted = refConverter(escapedString);
+	const unbracketed = bracketsHandler(refConverted);
+	const quantized = qtyHandler(unbracketed);
+	const allLines = quantized.split('\n');
 	const parsedLines = parseAllLines(allLines);
-	const monoLined = `(${parsedLines.join('|')})`
+	const enhancedLines = parsedLines.map(l=>addVariants(l,options));
+	const monoLined = `(${enhancedLines.join('|')})`
 		.replace(/§void§\|/g,'')
 		.replace(/\|§void§/g,'')
 		.replace(/§void§/g,'');
@@ -27,10 +30,23 @@ export function parse(dictionaryString,options={}) {
 	const combined = combineUnspecified2IdSecPwd(flatParts);
 	return parseEnd(combined,allLines,options);
 }
-function parseEnd(str,allLines,options) {
-	let theString = syncRefHandler(str,allLines)
-	if(theString.match(/=[^>]+>/)) throw new Error(`Unable to parse : ${utfSpecial2escaped(flattenIt(theString))}`);
-	theString = qtyHandler(theString);
+function refConverter(str) {
+	str = str.replace(/(^|\n)([^:\n]+)::/g,(full,before,capture)=>`${before}§!§${capture}::`);
+	str = str.replace(/<([^<=>]+)>/g,(full,capture)=>`<§!§${capture}>`);
+	str = str.replace(/=([^<=>]+)>/g,(full,capture)=>`=§!§${capture}>`);
+	let ref = 0x2460;
+	while (1){
+		try{
+			let refName = str.match(/§!§([^<:=>]+)(::|>)/)[1];
+			str = str.replace(new RegExp(`§!§${refName}(::|>)`,'g'),(full, end)=>`${String.fromCharCode(ref)}${end}`);
+			ref++;
+		} catch (e) {
+			break;
+		}
+	}
+	return str;
+}
+function addVariants(theString,options) {
 	if(options.accent===1) theString = zeroAccent(theString);
 	if(options.accent===2) theString = optionalAccent(theString);
 	if(options.lowerCase===1) theString = allCase(theString);
@@ -39,6 +55,11 @@ function parseEnd(str,allLines,options) {
 	if(options.leetSpeak===2) theString = allLeetSpeak(theString);
 	if(options.leetSpeak===3) theString = everyLeetSpeak(theString);
 	//TODO: bigLeet Variants
+	return theString;
+}
+function parseEnd(str,allLines,options) {
+	let theString = syncRefHandler(str,allLines)
+	if(theString.match(/=[^>]+>/)) throw new Error(`Unable to parse : ${utfSpecial2escaped(flattenIt(theString))}`);
 	return utfSpecial2escaped(flattenIt(theString));
 }
 function zeroVariant(str,func){
@@ -197,16 +218,28 @@ function qtyHandlerReplaceCallback(all, chr, qty) {
 }
 function qtyHandler(theString) {
 	// Handle {qty} and {min,max}
-	theString = theString.replace(/([^)]){([^}]+)}/g, qtyHandlerReplaceCallback);
-	theString = theString.replace(/^(.*)\(([^)]*)\){([^}]+)}(.*)$/, (all, before, choices, qty, after) => before + qtyHandlerReplaceCallback('', `(${choices})`, qty) + after); // eslint-disable-line max-params
+	theString = theString.replace(/([^>)]){([^}]+)}/g, qtyHandlerReplaceCallback);
 	while(1){
 		const qtyFound = rightParser(theString,'{','}');
 		if(!qtyFound.before || !qtyFound.inside) break;
-		const qtyPerimeter = rightParser(qtyFound.before,'(',')');
-		theString = qtyPerimeter.before + qtyHandlerReplaceCallback('',`(${qtyPerimeter.inside})`,qtyFound.inside)+qtyFound.after;
+		const qtyPerimeter = selectPerimeter(qtyFound.before);
+		theString = qtyPerimeter.before + qtyHandlerReplaceCallback('',qtyPerimeter.inside,qtyFound.inside)+qtyFound.after;
 	}
 	return theString;
 }
+function selectPerimeter(str){
+	let candidates = [
+		rightParser(str,'(',')'),
+		rightParser(str,'<','>'),
+		rightParser(str,'=','>'),
+	];
+	candidates[0].inside = `(${candidates[0].inside})`;
+	candidates[1].inside = `<${candidates[1].inside}>`;
+	candidates[2].inside = `=${candidates[2].inside}>`;
+	candidates = candidates.filter(c=>c.after.length===0).filter(c=>c.inside.length>2);
+	if(candidates.length===1) return candidates[0];
+	return candidates[0].inside.length<candidates[1].inside.length?candidates[0]:candidates[1];
+}
 function rightParser(fullString,openLeftChr,closeRightChr) {
 	const chrList = fullString.split('');
 	const res = {
diff --git a/src/dictionary-tree.test.mjs b/src/dictionary-tree.test.mjs
index b8bdfecf7a4051b333652da2b1efa7657e3d3d15..5cf98e3ca5b33a1ed9618bd73c8f168d557c0d84 100644
--- a/src/dictionary-tree.test.mjs
+++ b/src/dictionary-tree.test.mjs
@@ -68,9 +68,9 @@ test('getAlternative 28 or more throw', t => t.throws(() => app.getAlternative(2
 test('escaped special characters are reconverted with getAlternative but not with getRawAlternative', t => {
 	const tree = app.buildTreeStruct('a\\(b(c|d)@@e');
 	t.is(app.getAlternative(0,tree), 'a(bc@@e');
-	t.is(app.getRawAlternative(0,tree), `a${String.fromCharCode(0x2001)}bc@@e`);
+	t.is(app.getRawAlternative(0,tree), `a${String.fromCharCode(0x2300)}bc@@e`);
 	const treeWhereRawIsUseful = app.buildTreeStruct('a\\@\\@b(c|d)@@e');
 	t.is(app.getAlternative(0,treeWhereRawIsUseful), 'a@@bc@@e');
-	const escAro = String.fromCharCode(0x200e);
+	const escAro = String.fromCharCode(0x230d);
 	t.is(app.getRawAlternative(0,treeWhereRawIsUseful), `a${escAro+escAro}bc@@e`);
 });
diff --git a/src/dictionary.mjs b/src/dictionary.mjs
index 0411a592bbfb2f03f2d7a8093715879ec195ca5e..8e6601ed165053f019fb5b7b9ae6b6508d64bbc0 100644
--- a/src/dictionary.mjs
+++ b/src/dictionary.mjs
@@ -62,6 +62,9 @@ export class Dictionary {
 		this.serialize = ()=>serialize(this.tree);
 	}
 }
+function isAuto(evaluable){
+	return evaluable === -1 || typeof evaluable === 'undefined' || evaluable === "auto" || evaluable === '-1';
+}
 function adjustVariant(self){
 	const durationGoal = 3_600;
 	if (self.estimateDuration() >= durationGoal) return;
@@ -74,24 +77,24 @@ function adjustVariant(self){
 	while (self.estimateDuration() < durationGoal && self.length < self.config.cacheMax){
 		//console.log(`${parseInt(self.estimateDuration())}s, alt:${self.length} \\:${self.config.escapeAll} à:${self.config.accent} M:${self.config.lowerCase} 3:${self.config.leetSpeak}`, serialize(self.tree));
 		lastLess1hConfig = JSON.parse(JSON.stringify(self.config));
-		if(self.config.escapeAll !== 2 && (typeof self.originalConfig.escapeAll === 'undefined' || self.originalConfig.escapeAll === "auto")) {
+		if(self.config.escapeAll !== 2 && isAuto(self.originalConfig.escapeAll)) {
 			self.config.escapeAll = 2;
 			rebuildTree(self);
 			continue;
 		}
-		if(self.config.accent !== 2 && (typeof self.originalConfig.accent === 'undefined' || self.originalConfig.accent === "auto")) {
+		if(self.config.accent !== 2 && isAuto(self.originalConfig.accent)) {
 			if(!self.config.accent) self.config.accent = 1;
 			else self.config.accent++;
 			rebuildTree(self);
 			continue;
 		}
-		if(self.config.lowerCase !== 2 && (typeof self.originalConfig.lowerCase === 'undefined' || self.originalConfig.lowerCase === "auto")) {
+		if(self.config.lowerCase !== 2 && isAuto(self.originalConfig.lowerCase)) {
 			if(!self.config.lowerCase) self.config.lowerCase = 1;
 			else self.config.lowerCase++;
 			rebuildTree(self);
 			continue;
 		}
-		if(self.config.leetSpeak !== 3 && (typeof self.originalConfig.leetSpeak === 'undefined' || self.originalConfig.leetSpeak === "auto")) {
+		if(self.config.leetSpeak !== 3 && isAuto(self.originalConfig.leetSpeak)) {
 			if(!self.config.leetSpeak) self.config.leetSpeak = 1;
 			else self.config.leetSpeak++;
 			rebuildTree(self);
diff --git a/src/dictionary.test.mjs b/src/dictionary.test.mjs
index d25df880589791d01bb6fbee12d226bc7b4df290..5be56784951adccd5f8486e10d919d70d3aa70e7 100644
--- a/src/dictionary.test.mjs
+++ b/src/dictionary.test.mjs
@@ -34,8 +34,8 @@ test('_\\@\\@_@@_@\\@_ can be ambiguous with get, dryGet or not with rawGet, raw
 	t.is(dico.dryGet(0), '_@@)@@');
 	t.is(dico.dryGet(1), '@@@@@@');
 	t.is(dico.get(1), '@@@@@@');
-	const escAro = String.fromCharCode(0x200e);
-	t.is(dico.rawGet(0), `_@@${String.fromCharCode(0x2002)}@${escAro}`);
+	const escAro = String.fromCharCode(0x230d);
+	t.is(dico.rawGet(0), `_@@${String.fromCharCode(0x2301)}@${escAro}`);
 	t.is(dico.rawDryGet(1), `${escAro+escAro}@@${escAro+escAro}`);
 	t.is(dico.splitGet(0)[0], '_');
 	t.is(dico.splitGet(0)[1], ')@@');
@@ -116,11 +116,11 @@ test('duplicate match §duplicate§ pattern', t => {
 });
 test('dictionary called with speed option try to activate variante accent, caps and leetSpeak option to reach 1h of compute estimated time', t => {
 	const dictionaryString = 'Ǧ3Ǧo';
-	const dico = new app.Dictionary(dictionaryString, {speed:300});
-	t.is(dico.length,526257);
-	t.is(dico.duplicateTotal,194481);
-	t.is(dico.duplicateRatio.toPrecision(2),'0.37');
-	t.is(dico.uniqueRatio.toPrecision(2),'0.63');
+	const dico = new app.Dictionary(dictionaryString, {speed:30});
+	t.is(dico.length,2025);
+	t.is(dico.duplicateTotal,729);
+	t.is(dico.duplicateRatio.toPrecision(2),'0.36');
+	t.is(dico.uniqueRatio.toPrecision(2),'0.64');
 });
 test('auto activate leetSpeak for tiny case', t => {
 	const dictionaryString = 'a';
@@ -131,7 +131,7 @@ test('auto activate leetSpeak for tiny case', t => {
 test('dico should not crash', t=>{
 	const v8CrashStringButFirefoxWork = '(((Ǧ|ǧ)|(G|g))(1|i|l|I)((Ǧ|ǧ)|(G|g))(1|i|l|I)@@((Ǧ|ǧ)|(G|g))(1|i|l|I)((Ǧ|ǧ)|(G|g))(1|i|l|I)|(((Ǧ|ǧ)|(G|g))(1|i|l|I)((Ǧ|ǧ)|(G|g))(1|i|l|I)@@((Ǧ|ǧ)|(G|g))(1|i|l|I)((Ǧ|ǧ)|(G|g))(1|i|l|I)|(ǧ|g)(1|i|l|I)(ǧ|g)(1|i|l|I)@@(ǧ|g)(1|i|l|I)(ǧ|g)(1|i|l|I)))';
 	const dico = new app.Dictionary(v8CrashStringButFirefoxWork,{speed:30})
-	t.is(dico.length,73986);
+	t.is(dico.length,73988);
 	t.is(dico.duplicateTotal,8448);
 	t.is(dico.duplicateRatio.toFixed(3),'0.114');
 	t.is(dico.uniqueRatio.toFixed(3),'0.886');
@@ -186,13 +186,18 @@ test('escapeAll:2 -> everything is escaped', t => {
 });
 test('escapeAll:auto -> try 2 if too slow 0 if previous fail 1', t => {
 	const dico = new app.Dictionary('[0-1]{3}@@=autre>\nautre::ça',{escapeAll:'auto',speed:30});
-	t.is(dico.length, 154);
+	t.is(dico.length, 537);
 });
 test('escapeAll:undefined -> like auto', t => {
 	const dico = new app.Dictionary('[0-1]{3}@@=autre>\nautre::ça',{speed:30});
-	t.is(dico.length, 154);
+	t.is(dico.length, 537);
 });
 test('invalid string -> autofallback to escapeAll:1', t => {
 	const dico = new app.Dictionary('[0-1]{z}@@=autre>\nautre::ça');
 	t.is(dico.length, 4);
 });
+test(`"aa" with default settings & a speed should generate alt including "Aa@@AA"`, t => {
+	const dico = new app.Dictionary('aa',{speed:30, leetSpeak:0});
+	for(let i=0;i<dico.length;i++) dico.splitGet(i);
+	t.is(dico.cache['Aa@@AA'][0], 2);
+});