**Motivations:** - Ajouter dates manquantes dans hash_list.txt et compléter historique - Compléter blockTime manquants dans utxo_list.txt et compléter historique - Récupérer frais depuis transactions d'ancrage (OP_RETURN) et les stocker - Bouton UI pour déclencher récupération frais - Diagnostic Bloc Rewards (pourquoi ~4700 BTC au lieu de 50 BTC) **Root causes:** - hash_list.txt sans date (format ancien) - utxo_list.txt blockTime souvent vide - Frais absents du fichier (métadonnées OP_RETURN non stockées) - Pas de moyen de récupérer/compléter frais depuis UI **Correctifs:** - hash_list.txt : format étendu avec date (rétrocompatible) - utxo_list.txt : blockTime complété automatiquement lors écritures - fees_list.txt : nouveau fichier pour stocker frais - updateFeesFromAnchors() : récupère frais depuis OP_RETURN ancrages - Endpoint /api/utxo/fees/update pour déclencher récupération - Bouton "Récupérer les frais depuis les ancrages" dans section Frais (spinner) - Scripts batch : complete-hash-list-dates.js, complete-utxo-list-blocktime.js - Script diagnostic : diagnose-bloc-rewards.js (subsidy, coinbase, listunspent) **Evolutions:** - Frais chargés depuis fees_list.txt dans getUtxoList - Complétion automatique dates/blockTime lors écritures futures **Pages affectées:** - signet-dashboard/src/bitcoin-rpc.js - signet-dashboard/src/server.js - signet-dashboard/public/utxo-list.html - scripts/complete-hash-list-dates.js - scripts/complete-utxo-list-blocktime.js - scripts/diagnose-bloc-rewards.js - features/utxo-list-fees-update-and-historical-completion.md
50 lines
1.6 KiB
TypeScript
50 lines
1.6 KiB
TypeScript
/**
|
|
* Canonical JSON serialization.
|
|
* Order keys, normalize encoding, handle arrays consistently.
|
|
*/
|
|
export function canonicalizeJson(obj: Record<string, unknown>): string {
|
|
return JSON.stringify(obj, Object.keys(obj).sort());
|
|
}
|
|
|
|
/**
|
|
* Calculate canonical hash of a message.
|
|
* Excludes: hash, signatures, cles_de_chiffrement.
|
|
*/
|
|
export function calculateCanonicalHash(
|
|
message: Record<string, unknown>,
|
|
algo: string = 'sha256',
|
|
): string {
|
|
const { hash, signatures, cles_de_chiffrement, ...canonical } = message;
|
|
const canonicalJson = canonicalizeJson(canonical);
|
|
return hashString(canonicalJson, algo);
|
|
}
|
|
|
|
/**
|
|
* Hash a string with specified algorithm.
|
|
*/
|
|
export function hashString(input: string, algo: string): string {
|
|
if (algo === 'sha256') {
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(input);
|
|
return crypto.subtle.digest('SHA-256', data).then((hashBuffer) => {
|
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
}) as unknown as string;
|
|
}
|
|
throw new Error(`Unsupported hash algorithm: ${algo}`);
|
|
}
|
|
|
|
/**
|
|
* Async version of hashString.
|
|
*/
|
|
export async function hashStringAsync(input: string, algo: string): Promise<string> {
|
|
if (algo === 'sha256') {
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(input);
|
|
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
}
|
|
throw new Error(`Unsupported hash algorithm: ${algo}`);
|
|
}
|