charms-js
Version:
TypeScript SDK for decoding Bitcoin transactions containing Charms data
162 lines (161 loc) • 6.75 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractAndVerifyCharms = extractAndVerifyCharms;
const bitcoin = __importStar(require("bitcoinjs-lib"));
const bitcoin_tx_1 = require("./bitcoin_tx");
function extractAndVerifyCharms(txHex) {
try {
// Use the real BitcoinTx verification logic
const bitcoinTx = bitcoin_tx_1.BitcoinTx.fromHex(txHex);
// Use a default spell VK for now - in production this would be provided
const defaultSpellVk = "0x0034872b5af38c95fe82fada696b09a448f7ab0928273b7ac8c58ba29db774b9";
// Extract spell and proof
const spellTxIn = bitcoinTx.tx.ins[bitcoinTx.tx.ins.length - 1];
const { spell, proof } = bitcoinTx.parseSpellAndProof(spellTxIn);
// Verify the spell
const verifiedSpell = bitcoinTx.extractAndVerifySpell(defaultSpellVk);
// Convert verified spell to VerifiedCharm format
const verifiedCharms = [];
const tx = bitcoin.Transaction.fromHex(txHex);
verifiedSpell.tx.outs.forEach((out, index) => {
Object.entries(out).forEach(([appIdIndex, charmData]) => {
const appIdNum = parseInt(appIdIndex);
const appIds = Object.keys(verifiedSpell.app_public_inputs);
const rawAppId = appIds[appIdNum];
// Convert app ID to hex if it's an array of bytes, otherwise use as-is
let appId;
if (rawAppId && typeof rawAppId === 'string' && rawAppId.includes(',')) {
// Convert comma-separated byte array to hex
const bytes = rawAppId.split(',').map(s => parseInt(s.trim()));
appId = Buffer.from(bytes).toString('hex');
}
else {
appId = rawAppId || `$${appIdIndex.padStart(4, '0')}`;
}
const outputAddress = getOutputAddress(tx, index);
const verifiedCharm = {
utxo: {
tx: tx.getId(),
index: index
},
address: outputAddress,
appId: appId,
app: null,
verified: true, // Already verified by extractAndVerifySpell
proof: Array.isArray(proof) ? Buffer.from(proof).toString('hex') : JSON.stringify(proof)
};
// Add charm-specific data
if (typeof charmData === 'number') {
verifiedCharm.value = charmData;
}
else if (typeof charmData === 'object' && charmData !== null) {
Object.assign(verifiedCharm, charmData);
}
verifiedCharms.push(verifiedCharm);
});
});
return verifiedCharms;
}
catch (error) {
console.error('Error extracting and verifying charms:', error);
return [];
}
}
function getOutputAddress(tx, index) {
try {
const output = tx.outs[index];
if (!output)
return 'unknown';
// Try to decode the address from the script
try {
return bitcoin.address.fromOutputScript(output.script, bitcoin.networks.testnet);
}
catch {
try {
return bitcoin.address.fromOutputScript(output.script, bitcoin.networks.bitcoin);
}
catch {
return output.script.toString('hex');
}
}
}
catch (error) {
return 'unknown';
}
}
// Validates spell structure against transaction
function validateSpellStructure(spell, tx) {
// Spell must inherit inputs from the enchanted tx
if (spell.tx.ins !== undefined && spell.tx.ins !== null) {
return { valid: false, error: 'Spell must inherit inputs from the enchanted tx' };
}
// Spell tx outs must not exceed transaction outputs
if (spell.tx.outs.length > tx.outs.length) {
return { valid: false, error: 'Spell tx outs mismatch' };
}
return { valid: true };
}
// Adds inputs to spell from transaction inputs (excluding spell commitment input)
function addInputsToSpell(spell, txIns) {
const txInsUtxoIds = txIns.map(input => {
const txid = Buffer.from(input.hash).reverse().toString('hex');
const vout = input.index;
return `${txid}:${vout}`;
});
return {
...spell,
tx: {
...spell.tx,
ins: txInsUtxoIds
}
};
}
// Placeholder for actual Groth16 verification
// In a real implementation, this would use a WebAssembly module or similar
// to perform the actual cryptographic verification
function performGroth16Verification(spell, proof, spellVk) {
// This is a placeholder implementation
// In production, this would:
// 1. Convert spell to SP1 public values format
// 2. Use Groth16 verifier with the proof, public values, and verification keys
// 3. Return the actual verification result
console.log('Performing Groth16 verification (placeholder)');
console.log('Spell version:', spell.version);
console.log('Proof present:', !!proof);
console.log('Verification key present:', !!spellVk);
// For now, return true if all required components are present
return !!(spell && proof && spellVk);
}