const secret = 0x801d06ddc9ed804bc3af6e90d493ba5b776383a468ee241a43257fa9a9ff13c09ab0a96d82d3072c113e3920e080e5deaa4c13fa84825281e5b9653461014931bc35aa9a29d1a624cf773851e81f4b0dd723aec85d96bb1dfee651478af664d930b0b3d53836d95dbc880815fff1b02143de18e18e7d0fcdab8240e38dee128b276n const hint = 64 /** The next byte contains the number of bytes to be pushed onto the stack. */ const OP_PUSHDATA1 = 0x4c /** The next 2 bytes contains the number of bytes to be pushed onto the stack. */ const OP_PUSHDATA2 = 0x4d /** The next 4 bytes contains the number of bytes to be pushed onto the stack. */ const OP_PUSHDATA4 = 0x4e /** The top two items on the stack are swapped. */ const OP_SWAP = 0x7c /** Boolean and between each bit in the inputs. */ const OP_AND = 0x84 /** Boolean OR between each bit in the inputs. */ const OP_OR = 0x85 /** The input is hashed two times with SHA-256. */ const OP_HASH256 = 0xaa /** Returns 1 if the inputs are exactly equal, 0 otherwise; runs OP_VERIFY afterward. */ const OP_EQUALVERIFY = 0x88 // Generate the OP Codes and print the ASM in hex const opCodes = await buildOpCodes(secret, hint) console.log(opCodesToHex(opCodes)) //////////////////// // Helper Methods // //////////////////// /** Creates OP Codes to obfuscate the secret using the given hint. */ async function buildOpCodes(secret: bigint, hint: number) { const secretBytes = toBytes(secret) if (hint > secretBytes.byteLength * 8) throw new Error('Secret is too short to be hidden sufficiently.') // hint = 8 const hidden = 2n ** BigInt(hint) - 1n // 0x000000ff let secretMask = 2n ** BigInt(secretBytes.byteLength * 8) - 1n // 0xffffffff secretMask -= hidden // 0xffffff00 const visible = secret & secretMask // 0x12312300 const hiddenMask = toBytes(hidden, secretBytes.byteLength) const visibleMask = toBytes(visible, secretBytes.byteLength) const solutionHash = await hash256(secretBytes) // sha256(sha256((<input> & `hidden`) | `visible`)) == `solution` return [ solutionHash, OP_SWAP, hiddenMask, OP_AND, visibleMask, OP_OR, OP_HASH256, OP_EQUALVERIFY, ] } /** Mini BTC script compiler. */ function opCodesToHex(opCodes: Array<number | ArrayBuffer>) { let ret = [] for (const data of opCodes) { if (typeof data === 'number') ret.push(data) else if (data instanceof ArrayBuffer) { if (data.byteLength < 0x4c) { } // push buffer directly on the stack else if (data.byteLength < 0xff) ret.push(OP_PUSHDATA1) else if (data.byteLength < 0xff_ff) ret.push(OP_PUSHDATA2) else if (data.byteLength < 0xff_ff_ff_ff) ret.push(OP_PUSHDATA4) else throw new Error('Too many bytes to add to stack') ret.push(data.byteLength, ...new Uint8Array(data)) } } return ret.map(compactHex).join('') } /** Create little-endian (same as bitcoin) buffer from bigint. */ function toBytes(s: bigint, size = Math.ceil(s.toString(16).length / 2)) { const result = new Uint8Array(size) let i = size while (s) { result[--i] = Number(s & 0xffn) s >>= 8n } return result.buffer } /** JS version of `OP_HASH256`, same operation used in BTC miners. */ async function hash256(data: ArrayBuffer) { const round1 = await crypto.subtle.digest('SHA-256', data) return await crypto.subtle.digest('SHA-256', round1) } function compactHex(num: number) { const hex = num.toString(16) return hex.padStart(hex.length + (hex.length % 2), '0') }