Page MenuHomePhabricator

[Cashtab] use ecash-lib instead of external dep for hashing NFT image uploads
ClosedPublic

Authored by bytesofman on Aug 28 2025, 11:41.

Details

Summary

One unique characteristic of NFTs on XEC is that the NFT itself can store unique identifying information linking it to an associated file.

For now, this is only supported in Cashtab and for images.

This feature was added before ecash-lib, which has its own sha256 method, was implemented in Cashtab. Replace external lib with ecash-lib method.

Test Plan

npm test

Note that we do not directly test this behavior in the app. Here is an AI-generated script to confirm that the new behavior implemented here matches the old behavior.

#!/usr/bin/env node

// Verification script for SHA256 migration from js-sha256 to ecash-lib
// Run with: node verify-sha256-migration.js
//
// This script uses the actual libraries to verify they produce identical results

const { sha256: jsSha256 } = require('js-sha256');
const { sha256: ecashLibSha256, toHex } = require('ecash-lib');

console.log('πŸ” SHA256 Migration Verification Script');
console.log('=====================================\n');

console.log('πŸ“¦ Using actual libraries:');
console.log('- js-sha256 version:', require('js-sha256/package.json').version);
console.log('- ecash-lib version:', require('ecash-lib/package.json').version);
console.log('');

// Test cases that simulate real usage scenarios
const testCases = [
    {
        name: 'Empty file',
        input: '',
        description: 'Empty string (like empty file upload)'
    },
    {
        name: 'Small text file',
        input: 'Hello World',
        description: 'Simple text content'
    },
    {
        name: 'Image file content',
        input: 'This is a test image file content for token creation',
        description: 'Simulates actual file content from CreateTokenForm'
    },
    {
        name: 'Binary data (PNG header)',
        input: Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]),
        description: 'PNG file header bytes'
    },
    {
        name: 'Large content',
        input: 'A'.repeat(1000),
        description: 'Large file content (1000 characters)'
    },
    {
        name: 'Special characters',
        input: 'πŸŽ¨πŸ“ΈπŸ–ΌοΈ Token Icon with emojis!',
        description: 'Unicode content with emojis'
    }
];

let allTestsPassed = true;
let testCount = 0;

console.log('Running verification tests...\n');

testCases.forEach((testCase, index) => {
    testCount++;
    console.log(`Test ${index + 1}: ${testCase.name}`);
    console.log(`Description: ${testCase.description}`);
    
    const arrayBuffer = Buffer.from(testCase.input);
    
    // Test old js-sha256 approach (exactly as used in CreateTokenForm)
    const jsResult = jsSha256(arrayBuffer);
    
    // Test new ecash-lib approach (exactly as implemented in CreateTokenForm)
    const uint8Array = new Uint8Array(arrayBuffer);
    const hashBytes = ecashLibSha256(uint8Array);
    const hashHex = toHex(hashBytes);
    
    // Compare results
    const resultsMatch = jsResult === hashHex;
    
    if (resultsMatch) {
        console.log(`βœ… PASSED: Results match`);
        console.log(`   Hash: ${jsResult}`);
    } else {
        console.log(`❌ FAILED: Results differ`);
        console.log(`   js-sha256: ${jsResult}`);
        console.log(`   ecash-lib:  ${hashHex}`);
        allTestsPassed = false;
    }
    
    console.log('');
});

// Test the exact CreateTokenForm scenario
console.log('πŸ”§ Testing CreateTokenForm exact scenario...\n');

const fileContent = 'This is a test image file content for token creation';
const arrayBuffer = Buffer.from(fileContent);

console.log('File content:', fileContent);
console.log('ArrayBuffer length:', arrayBuffer.length);

// Old approach (js-sha256) - exactly as it was in CreateTokenForm
const jsResult = jsSha256(arrayBuffer);
console.log('\nOLD APPROACH (js-sha256):');
console.log(`  Result: ${jsResult}`);
console.log(`  Type: ${typeof jsResult}`);
console.log(`  Length: ${jsResult.length}`);

// New approach (ecash-lib) - exactly as implemented in CreateTokenForm
const uint8Array = new Uint8Array(arrayBuffer);
const hashBytes = ecashLibSha256(uint8Array);
const hashHex = toHex(hashBytes);

console.log('\nNEW APPROACH (ecash-lib):');
console.log(`  Uint8Array length: ${uint8Array.length}`);
console.log(`  sha256 result (Uint8Array): ${hashBytes}`);
console.log(`  toHex result: ${hashHex}`);
console.log(`  Type: ${typeof hashHex}`);
console.log(`  Length: ${hashHex.length}`);

const exactScenarioMatch = jsResult === hashHex;
console.log(`\nExact scenario match: ${exactScenarioMatch ? 'βœ… YES' : '❌ NO'}`);

// Additional verification: test with different input types
console.log('\nπŸ” Additional verification tests...\n');

// Test with string input directly
const stringInput = 'Direct string input';
const jsStringResult = jsSha256(stringInput);
const ecashStringResult = toHex(ecashLibSha256(Buffer.from(stringInput)));
console.log(`String input test: ${jsStringResult === ecashStringResult ? 'βœ… PASSED' : '❌ FAILED'}`);

// Test with Uint8Array input
const uint8Input = new Uint8Array([72, 101, 108, 108, 111]); // "Hello"
const jsUint8Result = jsSha256(uint8Input);
const ecashUint8Result = toHex(ecashLibSha256(uint8Input));
console.log(`Uint8Array input test: ${jsUint8Result === ecashUint8Result ? 'βœ… PASSED' : '❌ FAILED'}`);

console.log('\n=====================================');
console.log('FINAL VERIFICATION RESULTS:');
console.log(`Tests run: ${testCount}`);
console.log(`All tests passed: ${allTestsPassed ? 'βœ… YES' : '❌ NO'}`);
console.log(`Exact scenario match: ${exactScenarioMatch ? 'βœ… YES' : '❌ NO'}`);

if (allTestsPassed && exactScenarioMatch) {
    console.log('\nπŸŽ‰ VERIFICATION SUCCESSFUL!');
    console.log('The ecash-lib implementation produces identical results to js-sha256.');
    console.log('The migration is safe and behavior is preserved.');
} else {
    console.log('\n⚠️  VERIFICATION FAILED!');
    console.log('The implementations produce different results.');
    console.log('Further investigation is needed before proceeding with migration.');
}

console.log('\nπŸ“ Migration Summary:');
console.log('- Removed: js-sha256 dependency');
console.log('- Added: sha256 and toHex imports from ecash-lib');
console.log('- Updated: CreateTokenForm to use ecash-lib sha256');
console.log('- Verified: Identical behavior and results');

console.log('\nπŸ”§ Code Changes Made:');
console.log('1. Removed: import { sha256, Message } from "js-sha256";');
console.log('2. Added: import { toHex, sha256 } from "ecash-lib";');
console.log('3. Updated usage:');
console.log('   const uint8Array = new Uint8Array(hashreader.result as ArrayBuffer);');
console.log('   const hashBytes = sha256(uint8Array);');
console.log('   const hashHex = toHex(hashBytes);');
console.log('4. Removed: js-sha256 from package.json dependencies');

console.log('\nπŸ“‹ Library Information:');
console.log('- js-sha256: Returns hex string directly');
console.log('- ecash-lib sha256: Returns Uint8Array, requires toHex() conversion');
console.log('- Both produce identical SHA256 hashes for the same input');

Output

πŸ” SHA256 Migration Verification Script
=====================================

πŸ“¦ Using actual libraries:
- js-sha256 version: 0.11.1
- ecash-lib version: 4.3.1

Running verification tests...

Test 1: Empty file
Description: Empty string (like empty file upload)
βœ… PASSED: Results match
   Hash: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Test 2: Small text file
Description: Simple text content
βœ… PASSED: Results match
   Hash: a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e

Test 3: Image file content
Description: Simulates actual file content from CreateTokenForm
βœ… PASSED: Results match
   Hash: feee2fb25fc262c36462b82327cd3fe117f37a1273fa7d72e24fbdf76a70f73d

Test 4: Binary data (PNG header)
Description: PNG file header bytes
βœ… PASSED: Results match
   Hash: 4c4b6a3be1314ab86138bef4314dde022e600960d8689a2c8f8631802d20dab6

Test 5: Large content
Description: Large file content (1000 characters)
βœ… PASSED: Results match
   Hash: c2e686823489ced2017f6059b8b239318b6364f6dcd835d0a519105a1eadd6e4

Test 6: Special characters
Description: Unicode content with emojis
βœ… PASSED: Results match
   Hash: 3d45251179242573b9fdcce4ce8afceb0d8f1677d227f4bf3e7b638e4e03cc77

πŸ”§ Testing CreateTokenForm exact scenario...

File content: This is a test image file content for token creation
ArrayBuffer length: 52

OLD APPROACH (js-sha256):
  Result: feee2fb25fc262c36462b82327cd3fe117f37a1273fa7d72e24fbdf76a70f73d
  Type: string
  Length: 64

NEW APPROACH (ecash-lib):
  Uint8Array length: 52
  sha256 result (Uint8Array): 254,238,47,178,95,194,98,195,100,98,184,35,39,205,63,225,23,243,122,18,115,250,125,114,226,79,189,247,106,112,247,61
  toHex result: feee2fb25fc262c36462b82327cd3fe117f37a1273fa7d72e24fbdf76a70f73d
  Type: string
  Length: 64

Exact scenario match: βœ… YES

πŸ” Additional verification tests...

String input test: βœ… PASSED
Uint8Array input test: βœ… PASSED

=====================================
FINAL VERIFICATION RESULTS:
Tests run: 6
All tests passed: βœ… YES
Exact scenario match: βœ… YES

πŸŽ‰ VERIFICATION SUCCESSFUL!
The ecash-lib implementation produces identical results to js-sha256.
The migration is safe and behavior is preserved.

πŸ“ Migration Summary:
- Removed: js-sha256 dependency
- Added: sha256 and toHex imports from ecash-lib
- Updated: CreateTokenForm to use ecash-lib sha256
- Verified: Identical behavior and results

πŸ”§ Code Changes Made:
1. Removed: import { sha256, Message } from "js-sha256";
2. Added: import { toHex, sha256 } from "ecash-lib";
3. Updated usage:
   const uint8Array = new Uint8Array(hashreader.result as ArrayBuffer);
   const hashBytes = sha256(uint8Array);
   const hashHex = toHex(hashBytes);
4. Removed: js-sha256 from package.json dependencies

πŸ“‹ Library Information:
- js-sha256: Returns hex string directly
- ecash-lib sha256: Returns Uint8Array, requires toHex() conversion
- Both produce identical SHA256 hashes for the same input

Diff Detail

Repository
rABC Bitcoin ABC
Lint
Lint Not Applicable
Unit
Tests Not Applicable