Skip to content

Commit

Permalink
fix: will fail when a coinbase tx is not confirmed (#422)
Browse files Browse the repository at this point in the history
* fix: will fail when a coinbase tx is not confirmed

* added script to generate dumm proofs

* mapped values to was-seg-tx-mined-compact parameters

* tests are now using Bitcoin transactions generated with the script

* fix codefactor

* removed commented error

* removed unneeded comments
  • Loading branch information
FriendsFerdinand committed Jul 24, 2023
1 parent 162ff32 commit 82f4000
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 51 deletions.
2 changes: 1 addition & 1 deletion sbtc-mini/contracts/clarity-bitcoin.clar
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@
;; because they converge at some point
)
(begin
(try! (was-tx-mined-compact burn-height ctx header { tx-index: u0, hashes: cproof, tree-depth: tree-depth }))
(asserts! (try! (was-tx-mined-compact burn-height ctx header { tx-index: u0, hashes: cproof, tree-depth: tree-depth })) (err u9999))
(let (
(witness-out (get-commitment-scriptPubKey
;; check if marker/in-counter is 0.
Expand Down
199 changes: 199 additions & 0 deletions sbtc-mini/ext/generate-proof.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import { concat } from "https://deno.land/[email protected]/bytes/mod.ts";
import { randomBytes } from "https://deno.land/[email protected]/node/crypto.ts";
import { sha256 } from "https://denopkg.com/chiefbiiko/[email protected]/mod.ts";
import { Buffer } from "https://deno.land/[email protected]/node/buffer.ts";
import { assert } from "https://deno.land/[email protected]/testing/asserts.ts";

export const LEAF_NODE_PREFIX: Uint8Array = new Uint8Array([0]);
export const INNER_NODE_PREFIX: Uint8Array = new Uint8Array([1]);

const numToUint8Array = (num) => {
const arr = new Uint8Array(8);

for(let i = 0; i < 8; i++)
arr.set([num/0x100**i], 7-i)

return arr;
}

const generateBitcoinMerkleTree = (data: Uint8Array[], merkleTree: Uint8Array[][]) => {
merkleTree.push(data);

if (data.length === 1) return merkleTree;

const newLevel: Uint8Array[] = [];

for (let i = 0; i < data.length; i += 2) {
// Left
const d1 = data[i]
// Right, or duplicate left if odd
const d2 = data[i + 1] || d1
newLevel.push(
sha256(sha256(concat(d1, d2))) as Uint8Array)
}

generateBitcoinMerkleTree(newLevel, merkleTree);
}

// length max value is 2^64 (8 byte)
const generateBlockIds = (length: number, randomIds = true): Uint8Array[] => {
const ids = Array.from(new Array(length), (_, idx): Uint8Array => {
if (randomIds)
// 32 bytes is enough randomness
return sha256(randomBytes(32)) as Uint8Array;
return sha256(numToUint8Array(idx)) as Uint8Array;
});

return ids;
}

// txId is reversed (not what you see in explorers)
// output: list of txids is reversed (not what you see in explorers)
const generateSegwitBlockIdsFromTxId = (length: number, txId: Uint8Array, pos: number, randomIds = true) => {
const ids = generateBlockIds(length, randomIds);
ids[0] = new Uint8Array(32);
ids[pos] = txId.slice(0, txId.length);

return ids;
}

// txId is reversed (not what you see in explorers)
// output: list of txids is reversed (not what you see in explorers)
const generateBlockIdsFromTxId = (length: number, txId: Uint8Array, pos: number, randomIds = true) => {
assert(length > pos, "list is not long eough for the position chosen");
assert(txId.byteLength === 32, "txID length has to be 32");

const ids = generateBlockIds(length, randomIds);
ids[pos] = txId.slice(0, txId.length);

return ids;
}

const compare = (left: Uint8Array, right: Uint8Array): boolean => {
if (left === right) return true;
if (left.byteLength != right.byteLength) return false;

for(let i = 0; i < left.byteLength; i++)
if(left[i] != right[i]) return false;

return true
}

const getTxIdProofPath = (leaf: Uint8Array, layers: Uint8Array[][]): Uint8Array[] | undefined => {
const leafs = layers[0];
const index = leafs.findIndex((val) => compare(leaf, val));

if(index >= 0)
return getProofPath(index, layers);

return undefined;
}

// txid is reversed (not what you see in explorers)
const getProofPath = (index: number, layers: Uint8Array[][]) => {
const proof = [];
for (let i = 0; i < layers.length; i++) {
const layer = layers[i].slice(0, layers[i].length);
const isRightNode = index % 2;
const pairIndex = (isRightNode ? index - 1 : true && index === layer.length - 1 && i < layers.length - 1
// Proof Generation for Bitcoin Trees
? index
// Proof Generation for Non-Bitcoin Trees
: index + 1)


if (pairIndex < layer.length) {
proof.push(layer[pairIndex].slice(0, layer[pairIndex].length))
}

// set index to parent index
index = Math.floor(index / 2)
}

return proof;
}

// The merkle root in LE relative to the way txids were hashed
const generateBlockHeader = (merkleRoot: Uint8Array): Uint8Array => {
const version = new Uint8Array(4);
const previousBlockHash = new Uint8Array(32);
const time = new Uint8Array(4);
const bits = new Uint8Array(4);
const nonce = new Uint8Array(4);

const header = new Uint8Array(80);

header.set(version, 0);
header.set(previousBlockHash, 4);
header.set(merkleRoot, 36);
header.set(time, 68);
header.set(bits, 72);
header.set(nonce, 76);

return header;
}

const getReversedTxId = (rawTx: Uint8Array): Uint8Array => {
return sha256(sha256(rawTx)) as Uint8Array;
}

const generateProofs = (segwitRawTx: Uint8Array, txAmount: number, txIndex: number, randomTxs = false) => {
// static coinbase address
const segwitBlockIds = generateSegwitBlockIdsFromTxId(txAmount, getReversedTxId(segwitRawTx), txIndex, randomTxs);
const reversedTxid = getReversedTxId(segwitRawTx);

const segwitLayers: Uint8Array[][] = [];

generateBitcoinMerkleTree(segwitBlockIds, segwitLayers);

const segwitProof = getTxIdProofPath(reversedTxid, segwitLayers)!;
const segwitMerkleRoot = segwitLayers[segwitLayers.length - 1][0];
const witnessReservedData = new Uint8Array(32);

const output = `6a24aa21a9ed${Buffer.from(sha256(sha256(concat(segwitMerkleRoot, witnessReservedData)))).toString("hex")}`;
const coinbaseRawTX = Buffer.from(`020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff23036f18250418b848644d65726d61696465722046545721010000686d20000000000000ffffffff02edfe250000000000160014c035e789d9efffa10aa92e93f48f29b8cfb224c2000000000000000026${output}0120000000000000000000000000000000000000000000000000000000000000000000000000`, "hex");
const coinbaseReversedTxid = getReversedTxId(coinbaseRawTX);

const blockIds = generateBlockIdsFromTxId(txAmount, coinbaseReversedTxid, 0, randomTxs);
const layers: Uint8Array[][] = [];

generateBitcoinMerkleTree(blockIds, layers);

const proof = getTxIdProofPath(coinbaseReversedTxid, layers)!;
const merkleRoot = layers[layers.length - 1][0];
const treeDepth = layers.length - 1;

const blockHeader = generateBlockHeader(merkleRoot.slice(0, merkleRoot.byteLength));

return {
coinbaseRawTX,
blockHeader,
txIndex,
treeDepth,
coinbaseProof: proof,
segwitRawTx,
segwitProof: segwitProof,
segwitMerkleRoot,
witnessReservedData,
};
}

generateProofs(Buffer.from("020000000001010052458c56fea00527237f73d6b7bb4cbaf1f5436c9d2673ae2e0164f4ad17d20000000000fdffffff010065cd1d00000000225120f855ca43402fb99cde0e3e634b175642561ff584fe76d1686630d8fd2ea93b360340000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f3c183c001a7321b74e2b6a7e949e6c4ad313035b1665095017007520f855ca43402fb99cde0e3e634b175642561ff584fe76d1686630d8fd2ea93b36ac41c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac074708f439116be919de13c6d3200d2305fcbdf5a9e7d2c079e85b427bb110e9000000000", "hex"), 2, 1);


// console.log("tx: ", Buffer.from(proof.segwitRawTx).toString("hex"));
// console.log("header: ", Buffer.from((sha256(sha256(proof.blockHeader)) as Uint8Array).reverse()).toString("hex"));
// console.log("tx-index: ", proof.txIndex);
// console.log("tree-depth: ", proof.treeDepth);
// console.log("block header: ", Buffer.from(proof.blockHeader).toString("hex"));

// console.log("\nwproof");
// console.log(proof.segwitProof.map((tx) => Buffer.from(tx).toString("hex")));
// console.log("witness-merkle-root: ", Buffer.from(proof.segwitMerkleRoot).toString("hex"));
// console.log("Witness Reversed Data: ", Buffer.from(proof.witnessReservedData).toString("hex"));

// console.log("ctx: ", Buffer.from(proof.coinbaseRawTX).toString("hex"));

// console.log("\ncproof");
// console.log(proof.coinbaseProof.map((tx) => Buffer.from(tx).toString("hex")));

43 changes: 1 addition & 42 deletions sbtc-mini/tests/clarity-bitcoin_test.clar
Original file line number Diff line number Diff line change
Expand Up @@ -24,42 +24,6 @@
)
)


;; ;; @name verify segwit transaction where OP_RETURN is in output[1]
;; ;; arbitrary segwit transaction
;; (define-public (test-was-wtx-mined-internal-1)
;; (let (
;; (burnchain-block-height u2431087)
;; ;; txid: 3b3a7a31c949048fabf759e670a55ffd5b9472a12e748b684db5d264b6852084
;; (raw-tx 0x0200000000010218f905443202116524547142bd55b69335dfc4e4c66ff3afaaaab6267b557c4b030000000000000000e0dbdf1039321ab7a2626ca5458e766c6107690b1a1923e075c4f691cc4928ac0000000000000000000220a10700000000002200208730dbfaa29c49f00312812aa12a62335113909711deb8da5ecedd14688188363c5f26010000000022512036f4ff452cb82e505436e73d0a8b630041b71e037e5997290ba1fe0ae7f4d8d50140a50417be5a056f63e052294cb20643f83038d5cd90e2f90c1ad3f80180026cb99d78cd4480fadbbc5b9cad5fb2248828fb21549e7cb3f7dbd7aefd2d541bd34f0140acde555b7689eae41d5ccf872bb32a270893bdaa1defc828b76c282f6c87fc387d7d4343c5f7288cfd9aa5da0765c7740ca97e44a0205a1abafa279b530d5fe36d182500)
;; ;; txid: f1bb118d6663640d6571d08ee36ffff78130c0a6a959e69929f952e745e54050
;; (raw-coinbase-tx 0x02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff23036f18250418b848644d65726d61696465722046545721010000686d20000000000000ffffffff02edfe250000000000160014c035e789d9efffa10aa92e93f48f29b8cfb224c20000000000000000266a24aa21a9ed260ac9521c6b0c1b09e438319b5cb3377911764f156e44da61b1ab820f75104c00000000)
;; ;; block id: 000000000000000606f86a5bc8fb6e38b16050fb4676dea26cba5222583c4d86
;; (raw-block-header 0x0000a02065bc9201b5b5a1d695a18e4d5efe5d52d8ccc4129a2499141d000000000000009160ba7ae5f29f9632dc0cd89f466ee64e2dddfde737a40808ddc147cd82406f18b8486488a127199842cec7)
;; (witness-merkle-root 0x15424423c2614c23aceec8d732b5330c21ff3a306f52243fbeef47a192c65c86)
;; (witness-reserved-data 0x0000000000000000000000000000000000000000000000000000000000000000)
;; (parsed-block-header (contract-call? .clarity-bitcoin parse-block-header raw-block-header))
;; (parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx))
;; (parsed-coinbase-tx (contract-call? .clarity-bitcoin parse-tx raw-tx))
;; )
;; ;; prepare
;; (unwrap-panic (add-burnchain-block-header-hash burnchain-block-height raw-block-header))

;; (contract-call? .clarity-bitcoin was-segwit-tx-mined-compact
;; burnchain-block-height
;; raw-tx
;; raw-block-header
;; u3
;; u2
;; (list 0xb2d7ec769ce60ebc0c8fb9cc37f0ad7481690fc176b82c8d17d3c05da80fea6b 0x122f3217765b6e8f3163f6725d4aa3d303e4ffe4b99a5e85fb4ff91a026c17a8)
;; witness-merkle-root
;; witness-reserved-data
;; raw-coinbase-tx
;; (list 0x5f4a8858a112953111d5f94605c4dd8d04690eeeb9ffe2f435475d95943f2f3f 0x3348bf81aae79941662a902206b3ed2d285713668ab134c9e113548daea596fc)
;; )
;; )
;; )

;; @name verify segwit transaction where OP_RETURN is in output[1]
;; arbitrary segwit transaction
(define-public (test-was-wtx-mined-internal-1)
Expand All @@ -77,8 +41,6 @@
(parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx))
(parsed-coinbase-tx (contract-call? .clarity-bitcoin parse-tx raw-tx))
)
;; prepare
;; (unwrap-panic (add-burnchain-block-header-hash burnchain-block-height raw-block-header))

(contract-call? .clarity-bitcoin was-segwit-tx-mined-compact
burnchain-block-height
Expand Down Expand Up @@ -112,7 +74,6 @@
(parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx))
(parsed-coinbase-tx (contract-call? .clarity-bitcoin parse-tx raw-tx))
)
;; prepare
(contract-call? .clarity-bitcoin was-segwit-tx-mined-compact
burnchain-block-height
raw-tx
Expand Down Expand Up @@ -162,7 +123,7 @@
)
)

;; @name verify segwit transaction where there is only the coinbase transaction. should fail
;; @name verify segwit transaction where the incorrect proof is provided and only the first transaction is given. should fail
;; arbitrary segwit transaction
(define-public (test-was-wtx-mined-internal-4)
(let (
Expand Down Expand Up @@ -251,8 +212,6 @@
(parsed-tx (contract-call? .clarity-bitcoin parse-wtx raw-tx))
(parsed-coinbase-tx (contract-call? .clarity-bitcoin parse-tx raw-tx))
)
;; prepare

(contract-call? .clarity-bitcoin was-segwit-tx-mined-compact
burnchain-block-height
raw-tx
Expand Down
15 changes: 7 additions & 8 deletions sbtc-mini/tests/sbtc-peg-in-processor_test.clar
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

(define-constant mock-tx-1 0x020000000001010052458c56fea00527237f73d6b7bb4cbaf1f5436c9d2673ae2e0164f4ad17d20000000000fdffffff010065cd1d00000000225120f855ca43402fb99cde0e3e634b175642561ff584fe76d1686630d8fd2ea93b360340000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f3c183c001a7321b74e2b6a7e949e6c4ad313035b1665095017007520f855ca43402fb99cde0e3e634b175642561ff584fe76d1686630d8fd2ea93b36ac41c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac074708f439116be919de13c6d3200d2305fcbdf5a9e7d2c079e85b427bb110e9000000000)
(define-constant mock-wtxid-1 0x13d6ccd90dc236915d16dabe29fc02c00d4f5aad35577b43358a233d6e4620fd)
(define-constant mock-txid-1 0x0168ee41db8a4766efe02bba1ebc0de320bc1b0abb7304f5f104818a9dd721cf)
(define-constant mock-txid-1 0xcd2662154e6d76b2b2b92e70c0cac3ccf534f9b74eb5b89819ec509083d00a50)

(define-constant mock-witness-index-1 u0)

Expand All @@ -59,10 +59,10 @@

(define-constant mock-coinbase-witness-reserved-data 0x0000000000000000000000000000000000000000000000000000000000000000)

(define-constant mock-coinbase-tx-1 0x020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03016500ffffffff0200f2052a010000002251205444612a122cd09b4b3457d46c149a23d8685fb7d3aac61ea7eee8449555293b0000000000000000266a24aa21a9ed8a3bb68aa55850328ea8233754a147464b8580c15460c4ffb928ab23cf0d198b0120000000000000000000000000000000000000000000000000000000000000000000000000)
(define-constant mock-coinbase-tx-1 0x020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff23036f18250418b848644d65726d61696465722046545721010000686d20000000000000ffffffff02edfe250000000000160014c035e789d9efffa10aa92e93f48f29b8cfb224c20000000000000000266a24aa21a9ed8a3bb68aa55850328ea8233754a147464b8580c15460c4ffb928ab23cf0d198b0120000000000000000000000000000000000000000000000000000000000000000000000000)
(define-constant mock-coinbase-wtxid-1 0x0000000000000000000000000000000000000000000000000000000000000000)

(define-constant mock-block-header-1 0x02000000000000000000000000000000000000000000000000000000000000000000000075b8bf903d0153e1463862811283ffbec83f55411c9fa5bd24e4207dee0dc1f1000000000000000000000000)
(define-constant mock-block-header-1 0x000000000000000000000000000000000000000000000000000000000000000000000000d3dbd04a2912dd489751b19128a3ef428b9176512562eaf8ffa27a0223c8f215000000000000000000000000)
(define-constant mock-block-header-hash-1-be 0x346993fc64b2a124a681111bb1f381e24dbef3cd362f0a40019238846c7ebf93)

(define-read-only (get-sbtc-balance (who principal))
Expand All @@ -81,7 +81,8 @@
(try! (contract-call? .sbtc-registry insert-cycle-peg-wallet mock-peg-cycle mock-peg-wallet))
;; Mine a fake burnchain block that includes mock transactions
;;(try! (contract-call? .sbtc-testnet-debug-controller simulate-mine-solo-burnchain-block mock-burnchain-height (list mock-tx-1)))
(unwrap! (contract-call? .clarity-bitcoin mock-add-burnchain-block-header-hash mock-burnchain-height mock-block-header-hash-1-be) (err u112233))
;; (unwrap! (contract-call? .clarity-bitcoin mock-add-burnchain-block-header-hash mock-burnchain-height mock-block-header-hash-1-be) (err u112233))
(unwrap! (contract-call? .clarity-bitcoin mock-add-burnchain-block-header-hash mock-burnchain-height 0x56c235c25a7b8acee8fb606f6e0d493bd5e45848a90a9e4cc71a266627db5842) (err u112233))
(ok true)
)
)
Expand Down Expand Up @@ -177,7 +178,6 @@
mock-coinbase-witness-reserved-data
mock-witness-index-1
mock-coinbase-tx-1 ;; ctx
;; FIXME: something strange here, can pass any buff in the list and the test will pass.
(list mock-txid-1) ;; cproof
)))
(unwrap! result (err {msg: "Expected ok, got err", actual: (some result)}))
Expand All @@ -201,7 +201,6 @@
mock-coinbase-witness-reserved-data
mock-witness-index-1
mock-coinbase-tx-1 ;; ctx
;; FIXME: something strange here, can pass any buff in the list and the test will pass.
(list mock-txid-1) ;; cproof
))
(result2 (contract-call? .sbtc-peg-in-processor complete-peg-in
Expand All @@ -216,9 +215,9 @@
mock-coinbase-witness-reserved-data
mock-witness-index-1
mock-coinbase-tx-1 ;; ctx
;; FIXME: something strange here, can pass any buff in the list and the test will pass.
(list mock-txid-1) ;; cproof
)))
))
)
(unwrap! result (err {msg: "Expected ok, got err", actual: (some result)}))
(asserts! (is-eq (get-sbtc-balance wallet-1) mock-value-tx-1) (err {msg: "User did not receive the expected sBTC", actual: none}))
(asserts! (is-eq result2 err-burn-tx-already-processed) (err {msg: "Second call should have failed with err-burn-tx-already-processed", actual: (some result2)}))
Expand Down

0 comments on commit 82f4000

Please sign in to comment.