733ecd2681
Add root Cargo workspace with 9 crates: salvium-crypto (extended), salvium-types, salvium-consensus, salvium-wallet, salvium-tx, salvium-rpc, salvium-miner (extended), salvium-cli, salvium-multisig. New modules: chain_state, block_weight, alt_chain, validation, offline signing, stake lifecycle, wallet sync/query/encryption/utxo, randomx utilities, and full multisig crate with CARROT support. Delete 188 JS test/helper/debug files; archive integration test scripts to test/legacy-js/ for live testnet use. Testnet integration tests (transfer, stake, burn, convert, sweep) remain as #[ignore]- gated Rust tests runnable with --ignored against a live daemon.
154 lines
5.3 KiB
JavaScript
154 lines
5.3 KiB
JavaScript
#!/usr/bin/env bun
|
|
/**
|
|
* Debug full CARROT scan flow
|
|
*
|
|
* Usage:
|
|
* SPEND_SECRET="64-char-hex" TX_HASH="tx-hash" bun test/debug-full-carrot-scan.js
|
|
*
|
|
* Environment variables:
|
|
* SPEND_SECRET - 64-character hex spend secret key (required)
|
|
* TX_HASH - Transaction hash to scan (required)
|
|
* DAEMON_URL - Daemon RPC URL (default: http://seed01.salvium.io:19081)
|
|
*/
|
|
|
|
import { DaemonRPC } from '../src/rpc/daemon.js';
|
|
import { deriveKeys, deriveCarrotKeys } from '../src/carrot.js';
|
|
import { generateCarrotSubaddressMap } from '../src/subaddress.js';
|
|
import { hexToBytes, bytesToHex } from '../src/address.js';
|
|
import {
|
|
carrotEcdhKeyExchange,
|
|
computeCarrotViewTag,
|
|
makeInputContext,
|
|
makeCarrotSenderReceiverSecret,
|
|
recoverAddressSpendPubkey
|
|
} from '../src/carrot-scanning.js';
|
|
|
|
const SPEND_SECRET = process.env.SPEND_SECRET;
|
|
const TX_HASH = process.env.TX_HASH;
|
|
const DAEMON_URL = process.env.DAEMON_URL || 'http://seed01.salvium.io:19081';
|
|
|
|
if (!SPEND_SECRET || !TX_HASH) {
|
|
console.error('ERROR: SPEND_SECRET and TX_HASH environment variables required.\n');
|
|
console.log('Usage:');
|
|
console.log(' SPEND_SECRET="64-char-hex" TX_HASH="tx-hash" bun test/debug-full-carrot-scan.js');
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('=== Full CARROT Scan Debug ===\n');
|
|
|
|
// 1. Derive keys
|
|
const spendSecret = hexToBytes(SPEND_SECRET);
|
|
const cnKeys = deriveKeys(spendSecret);
|
|
const carrotKeys = deriveCarrotKeys(spendSecret);
|
|
|
|
console.log('1. Wallet Keys:');
|
|
console.log(' k_vi:', carrotKeys.viewIncomingKey);
|
|
console.log(' K_s:', carrotKeys.accountSpendPubkey);
|
|
|
|
// 2. Generate subaddress map
|
|
console.log('\n2. Generating subaddress map...');
|
|
const subaddressMap = generateCarrotSubaddressMap(
|
|
hexToBytes(carrotKeys.accountSpendPubkey),
|
|
hexToBytes(carrotKeys.accountViewPubkey),
|
|
hexToBytes(carrotKeys.generateAddressSecret),
|
|
50, 200
|
|
);
|
|
console.log(' Map size:', subaddressMap.size);
|
|
|
|
// Check if subaddress (0,1) is in the map
|
|
let found01 = null;
|
|
for (const [key, idx] of subaddressMap.entries()) {
|
|
if (idx.major === 0 && idx.minor === 1) {
|
|
found01 = key;
|
|
break;
|
|
}
|
|
}
|
|
console.log(' Subaddress (0,1) K_s:', found01 || 'NOT FOUND');
|
|
|
|
// 3. Fetch transaction
|
|
console.log('\n3. Fetching transaction...');
|
|
const daemon = new DaemonRPC({ url: DAEMON_URL });
|
|
const txResult = await daemon.getTransactions([TX_HASH], { decode_as_json: true });
|
|
const tx = JSON.parse(txResult.result.txs[0].as_json);
|
|
|
|
// Extract D_e from tx_extra
|
|
const extra = new Uint8Array(tx.extra);
|
|
let D_e = null;
|
|
if (extra[0] === 0x01) {
|
|
D_e = extra.slice(1, 33);
|
|
}
|
|
console.log(' D_e:', bytesToHex(D_e));
|
|
|
|
// Extract first key image
|
|
const firstKeyImage = tx.vin[0].key.k_image;
|
|
console.log(' First key image:', firstKeyImage);
|
|
|
|
// Build input context
|
|
const inputContext = makeInputContext(firstKeyImage);
|
|
console.log(' Input context:', bytesToHex(inputContext));
|
|
|
|
// 4. Process output 1 (the 10 SAL output)
|
|
console.log('\n4. Processing Output 1:');
|
|
const output = tx.vout[1];
|
|
const carrot = output.target.carrot_v1;
|
|
|
|
const Ko = hexToBytes(carrot.key);
|
|
const viewTagActual = hexToBytes(carrot.view_tag);
|
|
const commitment = tx.rct_signatures.outPk ? hexToBytes(tx.rct_signatures.outPk[1]) : null;
|
|
|
|
console.log(' Ko:', carrot.key);
|
|
console.log(' View tag:', carrot.view_tag);
|
|
console.log(' Commitment:', commitment ? bytesToHex(commitment) : 'NULL');
|
|
|
|
// 5. Compute shared secret
|
|
console.log('\n5. Computing shared secret:');
|
|
const k_vi = hexToBytes(carrotKeys.viewIncomingKey);
|
|
const s_sr_unctx = carrotEcdhKeyExchange(k_vi, D_e);
|
|
console.log(' s_sr_unctx:', bytesToHex(s_sr_unctx));
|
|
|
|
// 6. Compute view tag
|
|
console.log('\n6. Verifying view tag:');
|
|
const viewTagExpected = computeCarrotViewTag(s_sr_unctx, inputContext, Ko);
|
|
console.log(' Expected:', bytesToHex(viewTagExpected));
|
|
console.log(' Actual:', carrot.view_tag);
|
|
console.log(' Match:', bytesToHex(viewTagExpected) === carrot.view_tag ? 'YES ✓' : 'NO ✗');
|
|
|
|
if (bytesToHex(viewTagExpected) !== carrot.view_tag) {
|
|
console.log('\n VIEW TAG MISMATCH - stopping here');
|
|
process.exit(1);
|
|
}
|
|
|
|
// 7. Compute contextualized shared secret
|
|
console.log('\n7. Computing contextualized shared secret:');
|
|
const s_sr_ctx = makeCarrotSenderReceiverSecret(s_sr_unctx, D_e, inputContext);
|
|
console.log(' s_sr_ctx:', bytesToHex(s_sr_ctx));
|
|
|
|
// 8. Recover address spend pubkey
|
|
console.log('\n8. Recovering address spend pubkey:');
|
|
const K_s_recovered = recoverAddressSpendPubkey(Ko, s_sr_ctx, commitment || new Uint8Array(32));
|
|
const K_s_recovered_hex = bytesToHex(K_s_recovered);
|
|
console.log(' Recovered K_s:', K_s_recovered_hex);
|
|
console.log(' Main address K_s:', carrotKeys.accountSpendPubkey);
|
|
console.log(' Match main:', K_s_recovered_hex === carrotKeys.accountSpendPubkey ? 'YES' : 'NO');
|
|
|
|
// 9. Check subaddress map
|
|
console.log('\n9. Checking subaddress map:');
|
|
const subaddrIdx = subaddressMap.get(K_s_recovered_hex);
|
|
if (subaddrIdx) {
|
|
console.log(' FOUND! Subaddress index:', subaddrIdx);
|
|
} else {
|
|
console.log(' NOT FOUND in subaddress map');
|
|
console.log(' Looking for similar keys...');
|
|
|
|
// Check first few entries
|
|
let count = 0;
|
|
for (const [key, idx] of subaddressMap.entries()) {
|
|
if (count < 5 || (idx.major === 0 && idx.minor <= 5)) {
|
|
console.log(` (${idx.major},${idx.minor}): ${key.slice(0, 32)}...`);
|
|
}
|
|
count++;
|
|
}
|
|
}
|
|
|
|
console.log('\n=== End Debug ===');
|