-
-
Save xhliu/c216add326c006b9ed53499c707e929c to your computer and use it in GitHub Desktop.
CrossChainSwap2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type VarIntRes = { | |
val: bigint | |
newIdx: bigint | |
} | |
class CrossChainSwap2 extends SmartContract { | |
static readonly LOCKTIME_BLOCK_HEIGHT_MARKER = 500000000 | |
static readonly UINT_MAX = 0xffffffffn | |
static readonly MIN_CONF = 3 | |
static readonly BTC_MAX_INPUTS = 3 | |
@prop() | |
readonly aliceAddr: PubKeyHash | |
@prop() | |
readonly bobAddr: PubKeyHash | |
@prop() | |
readonly bobP2WPKHAddr: PubKeyHash | |
@prop() | |
readonly timeout: bigint // Can be a timestamp or block height. | |
@prop() | |
readonly targetDifficulty: bigint | |
@prop() | |
readonly amountBTC: bigint | |
@prop() | |
readonly amountBSV: bigint | |
// ... | |
@method() | |
checkBtcTx(btcTx: ByteString): void { | |
// Most things should be the same as in BSV except the witness data and flag. | |
// - Check (first) output is P2WPKH to Bobs public key. | |
// - Check (first) output amount is equal to this.amountBTC | |
let idx = 4n | |
// Make sure to serialize BTC tx without witness data. | |
// See https://github.com/karask/python-bitcoin-utils/blob/a41c7a1e546985b759e6eb2ae4524f466be809ca/bitcoinutils/transactions.py#L913 | |
assert( | |
slice(btcTx, idx, idx + 2n) != toByteString('0001'), | |
'Witness data present. Please serialize without witness data.' | |
) | |
//// INPUTS: | |
const inLen = CrossChainSwap2.parseVarInt(btcTx, idx) | |
assert( | |
inLen.val <= BigInt(CrossChainSwap2.BTC_MAX_INPUTS), | |
'Number of inputs too large.' | |
) | |
idx = inLen.newIdx | |
for (let i = 0n; i < CrossChainSwap2.BTC_MAX_INPUTS; i++) { | |
if (i < inLen.val) { | |
//const prevTxID = slice(btcTx, idx, idx + 32n) | |
idx += 32n | |
//const outIdx = slice(btcTx, idx, idx + 4n) | |
idx += 4n | |
const scriptLen = CrossChainSwap2.parseVarInt(btcTx, idx) | |
idx = scriptLen.newIdx | |
idx += scriptLen.val | |
//const nSequence = slice(btcTx, idx, idx + 4n) | |
idx += 4n | |
} | |
} | |
//// FIRST OUTPUT: | |
// Check if (first) output pays Bob the right amount and terminate and set res to true. | |
const outLen = CrossChainSwap2.parseVarInt(btcTx, idx) | |
idx = outLen.newIdx | |
const amount = Utils.fromLEUnsigned(slice(btcTx, idx, idx + 8n)) | |
assert(amount == this.amountBTC, 'Invalid BTC output amount.') | |
idx += 8n | |
const scriptLen = CrossChainSwap2.parseVarInt(btcTx, idx) | |
idx = scriptLen.newIdx | |
const script = slice(btcTx, idx, idx + scriptLen.val) | |
assert(len(script) == 22n, 'Invalid locking script length.') | |
assert( | |
script == toByteString('0014') + this.bobP2WPKHAddr, | |
'Invalid locking script.' | |
) | |
// Data past this point is not relevant in our use-case. | |
} | |
@method() | |
public swap( | |
btcTx: ByteString, | |
merkleProof: MerkleProof, | |
headers: FixedArray<BlockHeader, typeof CrossChainSwap2.MIN_CONF>, | |
alicePubKey: PubKey, | |
aliceSig: Sig | |
) { | |
// Check btc tx. | |
this.checkBtcTx(btcTx) | |
// Calc merkle root. | |
const txID = hash256(btcTx) | |
const merkleRoot = MerklePath.calcMerkleRoot(txID, merkleProof) | |
// Check if merkle root is included in the first BH. | |
assert( | |
merkleRoot == headers[0].merkleRoot, | |
"Merkle root of proof doesn't match the one in the BH." | |
) | |
// Check target diff for headers. | |
for (let i = 0; i < CrossChainSwap2.MIN_CONF; i++) { | |
assert( | |
Blockchain.isValidBlockHeader( | |
headers[i], | |
this.targetDifficulty | |
), | |
`${i}-nth BH doesn't meet target difficulty` | |
) | |
} | |
// Check header chain. | |
let h = Blockchain.blockHeaderHash(headers[0]) | |
for (let i = 0; i < CrossChainSwap2.MIN_CONF; i++) { | |
if (i >= 1n) { | |
const header = headers[i] | |
// Check if prev block hash matches. | |
assert( | |
header.prevBlockHash == h, | |
`${i}-th BH wrong prevBlockHash` | |
) | |
// Update header hash. | |
h = Blockchain.blockHeaderHash(header) | |
} | |
} | |
// Verify Alices signature. | |
assert(hash160(alicePubKey) == this.aliceAddr, 'Alice wrong pub key.') | |
assert(this.checkSig(aliceSig, alicePubKey)) | |
} | |
@method() | |
public cancel(bobPubKey: PubKey, bobSig: Sig) { | |
// Ensure nSequence is less than UINT_MAX. | |
assert( | |
this.ctx.sequence < CrossChainSwap2.UINT_MAX, | |
'input sequence should less than UINT_MAX' | |
) | |
// Check if using block height. | |
if (this.timeout < CrossChainSwap2.LOCKTIME_BLOCK_HEIGHT_MARKER) { | |
// Enforce nLocktime field to also use block height. | |
assert( | |
this.ctx.locktime < | |
CrossChainSwap2.LOCKTIME_BLOCK_HEIGHT_MARKER, | |
'locktime should be less than 500000000' | |
) | |
} | |
assert( | |
this.ctx.locktime >= this.timeout, | |
'locktime has not yet expired' | |
) | |
// Verify Bobs signature. | |
assert(hash160(bobPubKey) == this.bobAddr, 'Bob wrong pub key.') | |
assert(this.checkSig(bobSig, bobPubKey)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment