比特币脚本系统详解 - 从理论到实践
· 阅读需 2 分钟
比特币脚本系统是比特币网络的核心组件之一,它定义了如何验证交易的有效性。本文将深入探讨比特币脚本的工作原理,并通过实际代码示例来展示如何操作脚本。
以下代码参考 bitcoinjs-lib 的实现, 并进行了一些修改. 请参考 bitcoinjs-lib 了解更多信息.
什么是比特币脚本?
比特币脚本是一种基于栈的编程语言,用于定义比特币交易的解锁和锁定条件。每个比特币交易都包含输入脚本(scriptSig)和输出脚本(scriptPubKey),通过执行这些脚本来验证交易的有效性。
核心功能模块
1. 脚本编译 (compile)
脚本编译是将脚本块(chunks)转换为字节码的过程:
export function compile(chunks: Uint8Array[]): Buffer {
if (Buffer.isBuffer(chunks)) return chunks;
typeforce(typeforce.Array, chunks);
const bufferSize = chunks.reduce((accum, chunk) => {
// data chunk
if (Buffer.isBuffer(chunk)) {
// adhere to BIP62.3, minimal push policy
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) {
return accum + 1;
}
return accum + pushdata.encodingLength(chunk.length) + chunk.length;
}
// opcode
return accum + 1;
}, 0.0);
const buffer = Buffer.allocUnsafe(bufferSize);
let offset = 0;
chunks.forEach((chunk) => {
// data chunk
if (Buffer.isBuffer(chunk)) {
// adhere to BIP62.3, minimal push policy
const opcode = asMinimalOP(chunk);
if (opcode !== undefined) {
buffer.writeUInt8(opcode, offset);
offset += 1;
return;
}
offset += pushdata.encode(buffer, chunk.length, offset);
chunk.copy(buffer, offset);
offset += chunk.length;
// opcode
} else {
buffer.writeUInt8(chunk, offset);
offset += 1;
}
});
if (offset !== buffer.length) throw new Error('Could not decode chunks');
return buffer;
}
2. 脚本反编译 (decompile)
反编译是将字节码转换回脚本块的过程:
export function decompile(buffer: Buffer) {
if (typeforce.Array(buffer)) return buffer;
typeforce(typeforce.Buffer, buffer);
const chunks = [];
let i = 0;
while (i < buffer.length) {
const opcode = buffer[i];
// data chunk
if (opcode > OPS.OP_0 && opcode <= OPS.OP_PUSHDATA4) {
const d = pushdata.decode(buffer, i);
if (d === null) return null;
i += d.size;
if (i + d.number > buffer.length) return null;
const data = buffer.slice(i, i + d.number);
i += d.number;
const op = asMinimalOP(data);
if (op !== undefined) {
chunks.push(op);
} else {
chunks.push(data);
}
} else {
chunks.push(opcode);
i += 1;
}
}
return chunks;
}
3. ASM 格式转换
比特币脚本支持人类可读的 ASM(Assembly)格式:
export function fromASM(asm: string) {
const separator = ' ';
typeforce(typeforce.String, asm);
const chunks = asm.split(separator).map((str) => {
// opcode
if (OPS[str] !== undefined) return OPS[str];
typeforce(typeforce.Hex, str);
return Buffer.from(str, 'hex');
});
return compile(chunks);
}
export function toASM(chunks: Uint8Array[]) {
const separator = ' ';
if (Buffer.isBuffer(chunks)) {
chunks = decompile(chunks);
}
return chunks
.map((chunk) => {
if (Buffer.isBuffer(chunk)) {
const op = asMinimalOP(chunk);
if (op === undefined) return chunk.toString('hex');
chunk = op;
}
return REVERSE_OPS[chunk];
})
.join(separator);
}
实际应用示例
创建 P2PKH 脚本
// 创建一个标准的 P2PKH 脚本
const pubkeyHash = Buffer.from('1234567890abcdef1234567890abcdef12345678', 'hex');
const p2pkhScript = [
OPS.OP_DUP,
OPS.OP_HASH160,
pubkeyHash,
OPS.OP_EQUALVERIFY,
OPS.OP_CHECKSIG
];
const compiledScript = compile(p2pkhScript);
console.log('Compiled script:', compiledScript.toString('hex'));
验证公钥格式
const pubkey = Buffer.from('02...', 'hex'); // 你的公钥
if (isCanonicalPubKey(pubkey)) {
console.log('Valid canonical public key');
} else {
console.log('Invalid public key format');
}
最小推送策略 (BIP62.3)
比特币脚本遵循最小推送策略,确保数据以最紧凑的方式表示:
export function asMinimalOP(buffer: Uint8Array) {
if (buffer.length === 0) return OPS.OP_0;
if (buffer.length !== 1) return;
if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0];
if (buffer[0] === 0x81) return OPS.OP_1NEGATE;
}
总结
比特币脚本系统提供了强大的灵活性来定义各种交易条件。通过理解脚本的编译、反编译和 ASM 格式转换,开发者可以:
- 创建自定义的交易类型
- 验证交易脚本的有效性
- 调试和优化脚本性能
- 实现复杂的智能合约逻辑
在下一篇文章中,我们将探讨比特币地址的生成和验证机制。