Understanding Bitcoin’s UTXO Model and Building It with Node.js
This article explains Bitcoin’s innovative UTXO (unspent transaction output) model, why traditional account‑based systems are unsuitable, and provides a complete Node.js implementation covering transaction inputs, outputs, coinbase and regular transfers, complete with code examples and execution details.
UTXO Model in Bitcoin
Bitcoin replaces the traditional account‑based transaction model with an unspent transaction output (UTXO) model to avoid storage pressure, privacy leaks, and the inability to create relational tables on a blockchain. Each transaction references previous unspent outputs (UTXOs) via inputs and creates new outputs that lock BTC amounts with scripts.
In the UTXO model, a transaction input points to a previous transaction output that can be spent; it contains the previous transaction ID, the output index, and an unlocking script that proves ownership. A transaction output stores the BTC amount and a locking script (typically a public‑key hash) that can only be unlocked with the corresponding private key.
UTXO Implementation in Node.js
Transaction Input
<code>export class Input {
private txId: string;
private outputIndex: number;
private unlockScript: string;
public get $txId(): string { return this.txId; }
public set $txId(value: string) { this.txId = value; }
public get $outputIndex(): number { return this.outputIndex; }
public set $outputIndex(value: number) { this.outputIndex = value; }
public get $unlockScript(): string { return this.unlockScript; }
public set $unlockScript(value: string) { this.unlockScript = value; }
constructor(txId: string, index: number, unlockScript: string) {
this.txId = txId;
this.outputIndex = index;
this.unlockScript = unlockScript;
}
public static createInputsFromUnserialize(objs: Array<Input>) {
let ins = [];
objs.forEach(obj => {
ins.push(new Input(obj.txId, obj.outputIndex, obj.unlockScript));
});
return ins;
}
canUnlock(privateKey: string): boolean {
if (privateKey == this.unlockScript) {
return true;
} else {
return false;
}
}
}
</code>The
txIdidentifies the transaction containing the UTXO,
outputIndexselects the specific output, and
unlockScriptis a simplified script that checks the provided private key.
Transaction Output
<code>import * as rsaConfig from '../../rsa.json';
export class Output {
private value: number;
private lockScript: string;
private txId: string;
private index: number;
public get $index(): number { return this.index; }
public set $index(value: number) { this.index = value; }
public get $txId(): string { return this.txId; }
public set $txId(value: string) { this.txId = value; }
public get $value(): number { return this.value; }
public set $value(value: number) { this.value = value; }
constructor(value: number, publicKey: string) {
this.value = value;
this.lockScript = publicKey;
}
public static createOnputsFromUnserialize(objs: Array<Output>) {
let outs = [];
objs.forEach(obj => {
outs.push(new Output(obj.value, obj.lockScript));
});
return outs;
}
canUnlock(privateKey: string): boolean {
if (privateKey == rsaConfig[this.lockScript]) {
return true;
} else {
return false;
}
}
}
</code>The
valuefield holds the amount of BTC, while
lockScript(here simplified to a public key) locks the output until the matching private key unlocks it.
A Transaction
A transaction consists of multiple inputs and outputs and is identified by a unique
txId. The implementation serializes inputs and outputs, concatenates them with a timestamp, and hashes the result with SHA‑256 to produce the transaction ID.
<code>export class Transaction {
private txId: string;
private inputTxs: Array<Input>;
private outputTxs: Array<Output>;
constructor(txId: string, inputs: Array<Input>, outputs: Array<Output>) {
this.txId = txId;
this.inputTxs = inputs;
this.outputTxs = outputs;
}
public get $txId(): string { return this.txId; }
public set $txId(value: string) { this.txId = value; }
public get $inputTxs(): Array<Input> { return this.inputTxs; }
public set $inputTxs(value: Array<Input>) { this.inputTxs = value; }
public get $outputTxs(): Array<Output> { return this.outputTxs; }
public set $outputTxs(value: Array<Output>) { this.outputTxs = value; }
public setTxId() {
let sha256 = crypto.createHash('sha256');
sha256.update(JSON.stringify(this.inputTxs) + JSON.stringify(this.outputTxs) + Date.now(), 'utf8');
this.txId = sha256.digest('hex');
}
}
</code>Coinbase Transaction
A coinbase transaction has no inputs (input index = -1 and empty txId) and rewards the miner with newly created BTC plus transaction fees.
<code>public static createCoinbaseTx(pubKey: string, info: string) {
let input = new Input('', -1, info);
let output = new Output(AWARD, pubKey);
let tx = new Transaction('', [input], [output]);
tx.setTxId();
return tx;
}
public static isCoinbaseTx(tx: Transaction) {
if (tx.$inputTxs.length == 1 && tx.$inputTxs[0].$outputIndex == -1 && tx.$inputTxs[0].$txId == '') {
return true;
} else {
return false;
}
}
</code>Transfer Transaction
To transfer BTC, the sender’s UTXOs are gathered, inputs are created, outputs are generated for the recipient and (if needed) change back to the sender, and the transaction ID is computed.
<code>public static createTransaction(from: string, fromPubkey: string, fromKey: string, to: string, toPubkey: string, coin: number) {
let outputs = this.findUTXOToTransfer(fromKey, coin);
console.log(`UTXOToTransfer: ${JSON.stringify(outputs)}, from: ${from} to ${to} transfer ${coin}`);
let inputTx = [], sum = 0, outputTx = [];
outputs.forEach(o => {
sum += o.$value;
inputTx.push(new Input(o.$txId, o.$index, fromKey));
});
if (sum < coin) {
throw Error(`Insufficient balance, transfer failed! from ${from} to ${to} ${coin}btc, but only have ${sum}btc`);
}
outputTx.push(new Output(coin, toPubkey));
if (sum > coin) {
outputTx.push(new Output(sum - coin, fromPubkey));
}
let tx = new Transaction('', inputTx, outputTx);
tx.setTxId();
return tx;
}
</code>The method
findUTXOToTransfersearches all unspent outputs belonging to the sender, verifies them with
output.canUnlock(secreteKey), and selects enough UTXOs to cover the requested amount.
Conclusion
The presented UTXO implementation is a simplified educational version based on Bitcoin’s design. The source code is open‑source at the author’s GitHub repository (feature/utxo branch) for further exploration and improvement.
Weidian Tech Team
The Weidian Technology Platform is an open hub for consolidating technical knowledge. Guided by a spirit of sharing, we publish diverse tech insights and experiences to grow and look ahead together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.