Skip to content

Commit

Permalink
Refactor backtrace
Browse files Browse the repository at this point in the history
  • Loading branch information
gitzhou committed Jan 9, 2025
1 parent ac58e76 commit ce1ae3c
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 90 deletions.
57 changes: 54 additions & 3 deletions l1/src/contracts/aggregatorUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { assert, ByteString, hash256, len, method, sha256, Sha256, SmartContractLib, toByteString } from 'scrypt-ts';
import { AggregatorTx } from './types';
import { AggregatorTx, DepositData } from './types';
import { GeneralUtils } from './generalUtils';

export class AggregatorUtils extends SmartContractLib {
Expand Down Expand Up @@ -32,7 +32,58 @@ export class AggregatorUtils extends SmartContractLib {
}

@method()
static getHashPrevouts(prevTxId0: Sha256, prevTxId1: Sha256, feePrevout: ByteString): Sha256 {
return sha256(prevTxId0 + toByteString('00000000') + prevTxId1 + toByteString('00000000') + feePrevout);
static buildPrevout(prevTx: AggregatorTx, isLeaf: boolean): ByteString {
const prevTxId = this.getTxId(prevTx, isLeaf);
// prevTxId + outputIndex (01000000)
return prevTxId + toByteString('01000000');
}

@method()
static buildInput(prevTx: AggregatorTx, isLeaf: boolean): ByteString {
// prevout + unlockingScriptLen (00) + nSequence (ffffffff)
return this.buildPrevout(prevTx, isLeaf) + toByteString('00ffffffff');
}

@method()
static backtrace(
prevoutsPrefix: ByteString,
prevoutsSuffix: ByteString,
hashPrevouts: ByteString,

prevTx: AggregatorTx,
isPrevTxLeaf: boolean,
depositData: DepositData,

ancestorTx0: AggregatorTx,
ancestorTx1: AggregatorTx,
isAncestorLeaf: boolean,
) {
// check prevouts if the passed prevTx are unlocked by the currently executing tx
const prevouts = prevoutsPrefix + this.buildPrevout(prevTx, isPrevTxLeaf) + prevoutsSuffix;
assert(hashPrevouts == sha256(prevouts));

if (isPrevTxLeaf) {
// if prevTx is a leaf
// check that the data hash in its state output corresponds to the data passed in as witness
assert(prevTx.contractOutputAmount == depositData.amount);
assert(prevTx.dataHash == this.hashDepositData(depositData));
} else {
// if we're higher up the aggregation tree, we need to check the ancestor
// transactions to inductively validate the whole tree
// check prevTx unlocks ancestorTx0 and ancestorTx1
assert(prevTx.contractInput0 == this.buildInput(ancestorTx0, isAncestorLeaf));
assert(prevTx.contractInput1 == this.buildInput(ancestorTx1, isAncestorLeaf));

// check ancestors have the same contract locking as prevTx
// this completes the inductive step, since the successful evaluation
// of the ancestors' contract locking also checked its ancestors
assert(prevTx.contractOutputLocking == ancestorTx0.contractOutputLocking);
assert(prevTx.contractOutputLocking == ancestorTx1.contractOutputLocking);
}
}

@method()
static hashDepositData(depositData: DepositData): Sha256 {
return hash256(depositData.address + GeneralUtils.int32ToSatoshiBytes(depositData.amount));
}
}
116 changes: 35 additions & 81 deletions l1/src/contracts/depositAggregator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
method,
prop,
PubKey,
sha256,
Sha256,
sha256,
Sig,
SmartContract,
toByteString,
Expand All @@ -29,24 +29,6 @@ export class DepositAggregator extends SmartContract {
this.bridgeLocking = bridgeLocking;
}

/**
* Aggregates two aggregator transactions (or leaves) into one.
*
* @param shPreimage Sighash preimage of the currently executing transaction.
* @param sigOperator Signature of the bridge operator.
* @param isFirstInput Indicates whether this method is called from the first or second input.
* @param feePrevouts The prevout for the fee UTXO.
* @param prevTx0 Transaction data of the first previous transaction being aggregated. Can be a leaf transaction containing the deposit request itself or an already aggregated transaction.
* @param prevTx1 Transaction data of the second previous transaction being aggregated.
* @param isPrevTxLeaf Indicates whether the previous transactions are leaves.
* @param depositData0 Actual deposit data of the first deposit; used when aggregating leaves.
* @param depositData1 Actual deposit data of the second deposit; used when aggregating leaves.
* @param ancestorTx0 First ancestor transaction. These are used to inductively verify the transaction history; ignored when aggregating leaves.
* @param ancestorTx1 Second ancestor transaction.
* @param ancestorTx2 Third ancestor transaction.
* @param ancestorTx3 Fourth ancestor transaction.
* @param isAncestorLeaf Indicates whether the ancestor transactions are leaves.
*/
@method()
public aggregate(
shPreimage: SHPreimage,
Expand All @@ -55,16 +37,15 @@ export class DepositAggregator extends SmartContract {
isFirstInput: boolean,
feePrevouts: ByteString,

prevTx0: AggregatorTx,
prevTx1: AggregatorTx,
prevTx: AggregatorTx,
isPrevTxLeaf: boolean,
depositData0: DepositData,
depositData1: DepositData,
depositData: DepositData,

siblingPrevTx: AggregatorTx,
isSiblingPrevTxLeaf: boolean,

ancestorTx0: AggregatorTx,
ancestorTx1: AggregatorTx,
ancestorTx2: AggregatorTx,
ancestorTx3: AggregatorTx,
isAncestorLeaf: boolean,
) {
// check sighash preimage
Expand All @@ -74,73 +55,46 @@ export class DepositAggregator extends SmartContract {
// check operator sig
assert(this.checkSig(sigOperator, this.operatorPubKey));

// check unlock inputIndex, this contract can only be unlocked at input #0 or #1
let prevoutsPrefix: ByteString;
let prevoutsSuffix: ByteString;
let newDataHash: Sha256;

// require this method is called at the first or second input
if (isFirstInput) {
assert(shPreimage.inputNumber == toByteString('00000000'));
prevoutsPrefix = toByteString('');
prevoutsSuffix = AggregatorUtils.buildPrevout(siblingPrevTx, isSiblingPrevTxLeaf) + feePrevouts;
newDataHash = hash256(prevTx.dataHash + siblingPrevTx.dataHash);
} else {
assert(shPreimage.inputNumber == toByteString('01000000'));
prevoutsPrefix = AggregatorUtils.buildPrevout(siblingPrevTx, isSiblingPrevTxLeaf);
prevoutsSuffix = feePrevouts;
newDataHash = hash256(siblingPrevTx.dataHash + prevTx.dataHash);
}

// check prevouts if the passed prev txns are actually unlocked by the currently executing tx
const prevTxId0 = AggregatorUtils.getTxId(prevTx0, isPrevTxLeaf);
const prevTxId1 = AggregatorUtils.getTxId(prevTx1, isPrevTxLeaf);
const hashPrevouts = AggregatorUtils.getHashPrevouts(prevTxId0, prevTxId1, feePrevouts);
assert(shPreimage.hashPrevouts == hashPrevouts);
// require this tx has two contract inputs, and these two contracts have the same locking
assert(prevTx.contractOutputLocking == siblingPrevTx.contractOutputLocking);

// check the two contract inputs are the same
assert(prevTx0.contractOutputLocking == prevTx1.contractOutputLocking);

if (isPrevTxLeaf) {
// if prev txns are leaves, check that the hash in their state output
// corresponds to the data passed in as witnesses
assert(prevTx0.dataHash == DepositAggregator.hashDepositData(depositData0));
assert(prevTx1.dataHash == DepositAggregator.hashDepositData(depositData1));

assert(prevTx0.contractOutputAmount == depositData0.amount);
assert(prevTx1.contractOutputAmount == depositData1.amount);
} else {
// if we're higher up the aggregation tree, we need to check the ancestor
// transactions to inductively validate the whole tree
const ancestorTxId0 = AggregatorUtils.getTxId(ancestorTx0, isAncestorLeaf);
const ancestorTxId1 = AggregatorUtils.getTxId(ancestorTx1, isAncestorLeaf);
const ancestorTxId2 = AggregatorUtils.getTxId(ancestorTx2, isAncestorLeaf);
const ancestorTxId3 = AggregatorUtils.getTxId(ancestorTx3, isAncestorLeaf);

// check prevTx0 unlocks ancestorTx0 and ancestorTx1
assert(prevTx0.contractInput0 == GeneralUtils.buildContractInput(ancestorTxId0));
assert(prevTx0.contractInput1 == GeneralUtils.buildContractInput(ancestorTxId1));

// check prevTx1 unlocks ancestorTx2 and ancestorTx3
assert(prevTx1.contractInput0 == GeneralUtils.buildContractInput(ancestorTxId2));
assert(prevTx1.contractInput1 == GeneralUtils.buildContractInput(ancestorTxId3));

// check ancestors have same contract locking as prev txns
// this completes the inductive step, since the successfull evaluation
// of the ancestors contract locking also checked its ancestors
assert(prevTx0.contractOutputLocking == ancestorTx0.contractOutputLocking);
assert(prevTx0.contractOutputLocking == ancestorTx1.contractOutputLocking);
assert(prevTx1.contractOutputLocking == ancestorTx2.contractOutputLocking);
assert(prevTx1.contractOutputLocking == ancestorTx3.contractOutputLocking);
}
// backtrace validation for the currently unlocking input
AggregatorUtils.backtrace(
prevoutsPrefix,
prevoutsSuffix,
shPreimage.hashPrevouts,
prevTx,
isPrevTxLeaf,
depositData,
ancestorTx0,
ancestorTx1,
isAncestorLeaf,
);

// confine outputs
const dataHash = hash256(prevTx0.dataHash + prevTx1.dataHash);
const stateOutput = GeneralUtils.buildStateOutput(dataHash);
// confine outputs for this tx
const stateOutput = GeneralUtils.buildStateOutput(newDataHash);

const contractOutputAmount = prevTx0.contractOutputAmount + prevTx1.contractOutputAmount;
const contractOutput = GeneralUtils.buildContractOutput(contractOutputAmount, prevTx0.contractOutputLocking);
const contractOutputAmount = prevTx.contractOutputAmount + siblingPrevTx.contractOutputAmount;
const contractOutput = GeneralUtils.buildContractOutput(contractOutputAmount, prevTx.contractOutputLocking);

const hashOutputs = sha256(stateOutput + contractOutput);
assert(shPreimage.hashOutputs == hashOutputs);
}

@method()
public finalize() {
assert(true);
}

@method()
static hashDepositData(depositData: DepositData): Sha256 {
return hash256(depositData.address + GeneralUtils.int32ToSatoshiBytes(depositData.amount));
}
}
6 changes: 0 additions & 6 deletions l1/src/contracts/generalUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,4 @@ export class GeneralUtils extends SmartContractLib {
static buildContractOutput(amount: int32, locking: ByteString): ByteString {
return GeneralUtils.int32ToSatoshiBytes(amount) + locking;
}

@method()
static buildContractInput(prevTxId: ByteString): ByteString {
// prevTxId + outputIndex (00000000) + unlockingScriptLen (00) + nSequence (ffffffff)
return prevTxId + toByteString('0000000000ffffffff');
}
}

0 comments on commit ce1ae3c

Please sign in to comment.