Skip to content

Commit

Permalink
Merge pull request #3 from quake/quake/udt
Browse files Browse the repository at this point in the history
feat: supports sudt/xudt for commitment lock
  • Loading branch information
quake authored May 7, 2024
2 parents 65624ce + e38eccf commit 592bb10
Show file tree
Hide file tree
Showing 4 changed files with 474 additions and 30 deletions.
114 changes: 88 additions & 26 deletions contracts/commitment-lock/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use ckb_std::{
ckb_types::{bytes::Bytes, core::ScriptHashType, prelude::*},
error::SysError,
high_level::{
exec_cell, load_cell_capacity, load_cell_lock, load_input_since, load_script, load_tx_hash,
load_witness,
exec_cell, load_cell_capacity, load_cell_data, load_cell_lock, load_cell_type,
load_input_since, load_script, load_tx_hash, load_witness,
},
since::Since,
};
Expand All @@ -43,6 +43,8 @@ pub enum Error {
WitnessHashError,
OutputCapacityError,
OutputLockError,
OutputTypeError,
OutputUdtAmountError,
PreimageError,
AuthError,
}
Expand All @@ -68,20 +70,51 @@ pub fn program_entry() -> i8 {

// min witness script length: 8 (local_delay_epoch) + 20 (local_delay_pubkey_hash) + 20 (revocation_pubkey_hash) = 48
const MIN_WITNESS_SCRIPT_LEN: usize = 48;
// HTLC script length: 1 (htlc_type) + 8 (payment_amount) + 20 (payment_hash) + 20 (remote_htlc_pubkey_hash) + 20 (local_htlc_pubkey_hash) + 8 (htlc_expiry) = 77
const HTLC_SCRIPT_LEN: usize = 77;
// HTLC script length: 1 (htlc_type) + 16 (payment_amount) + 20 (payment_hash) + 20 (remote_htlc_pubkey_hash) + 20 (local_htlc_pubkey_hash) + 8 (htlc_expiry) = 85
const HTLC_SCRIPT_LEN: usize = 85;
// 1 (unlock_type) + 65 (signature) = 66
const UNLOCK_WITH_SIGNATURE_LEN: usize = 66;
const PREIMAGE_LEN: usize = 32;
const MIN_WITNESS_LEN: usize = MIN_WITNESS_SCRIPT_LEN + UNLOCK_WITH_SIGNATURE_LEN;

struct Htlc<'a>(&'a [u8]);

impl<'a> Htlc<'a> {
pub fn htlc_type(&self) -> u8 {
self.0[0]
}

pub fn payment_amount(&self) -> u128 {
u128::from_le_bytes(self.0[1..17].try_into().unwrap())
}

pub fn payment_hash(&self) -> &'a [u8] {
&self.0[17..37]
}

pub fn remote_htlc_pubkey_hash(&self) -> &'a [u8] {
&self.0[37..57]
}

pub fn local_htlc_pubkey_hash(&self) -> &'a [u8] {
&self.0[57..77]
}

pub fn htlc_expiry(&self) -> u64 {
u64::from_le_bytes(self.0[77..85].try_into().unwrap())
}
}

fn auth() -> Result<(), Error> {
// since local_delay_pubkey and revocation_pubkey are derived, the scripts are usually unique,
// to simplify the implementation of the following unlocking logic, we check the number of inputs should be 1
if load_input_since(1, Source::GroupInput).is_ok() {
return Err(Error::MultipleInputs);
}

// no need to check the type script is sudt / xudt or not, because the offchain tx collaboration will ensure the correct type script.
let type_script = load_cell_type(0, Source::GroupInput)?;

let script = load_script()?;
let args: Bytes = script.args().unpack();
if args.len() != 20 {
Expand Down Expand Up @@ -144,59 +177,63 @@ fn auth() -> Result<(), Error> {
return Err(Error::InvalidUnlockType);
}

let mut new_capacity = load_cell_capacity(0, Source::GroupInput)?;
let mut new_amount = if type_script.is_some() {
let input_cell_data = load_cell_data(0, Source::GroupInput)?;
u128::from_le_bytes(input_cell_data[0..16].try_into().unwrap())
} else {
load_cell_capacity(0, Source::GroupInput)? as u128
};
let mut new_witness_script: Vec<&[u8]> = Vec::new();
new_witness_script.push(&witness[0..MIN_WITNESS_SCRIPT_LEN]);

for (i, htlc) in witness[MIN_WITNESS_SCRIPT_LEN..witness_script_len]
for (i, htlc_script) in witness[MIN_WITNESS_SCRIPT_LEN..witness_script_len]
.chunks(HTLC_SCRIPT_LEN)
.enumerate()
{
let htlc = Htlc(htlc_script);
if unlock_htlc == i {
if htlc[0] == 0 {
if htlc.htlc_type() == 0 {
// offered HTLC
let raw_since_value = load_input_since(0, Source::GroupInput)?;
if raw_since_value == 0 {
// when input since is 0, it means the unlock logic is for remote_htlc pubkey and preimage
if preimage
.map(|p| blake2b_256(p)[0..20] != htlc[9..29])
.map(|p| htlc.payment_hash() != &blake2b_256(p)[0..20])
.unwrap_or(false)
{
return Err(Error::PreimageError);
}
new_capacity -= u64::from_le_bytes(htlc[1..9].try_into().unwrap());
pubkey_hash.copy_from_slice(&htlc[29..49]);
new_amount -= htlc.payment_amount();
pubkey_hash.copy_from_slice(htlc.remote_htlc_pubkey_hash());
} else {
// when input since is not 0, it means the unlock logic is for local_htlc pubkey and htlc expiry
let since = Since::new(raw_since_value);
let htlc_expiry =
Since::new(u64::from_le_bytes(htlc[69..77].try_into().unwrap()));
let htlc_expiry = Since::new(htlc.htlc_expiry());
if since >= htlc_expiry {
pubkey_hash.copy_from_slice(&htlc[49..69]);
pubkey_hash.copy_from_slice(htlc.local_htlc_pubkey_hash());
} else {
return Err(Error::InvalidSince);
}
}
} else if htlc[0] == 1 {
} else if htlc.htlc_type() == 1 {
// received HTLC
let raw_since_value = load_input_since(0, Source::GroupInput)?;
if raw_since_value == 0 {
// when input since is 0, it means the unlock logic is for local_htlc pubkey and preimage
if preimage
.map(|p| blake2b_256(p)[0..20] != htlc[9..29])
.map(|p| htlc.payment_hash() != &blake2b_256(p)[0..20])
.unwrap_or(false)
{
return Err(Error::PreimageError);
}
pubkey_hash.copy_from_slice(&htlc[49..69]);
pubkey_hash.copy_from_slice(&htlc.local_htlc_pubkey_hash());
} else {
// when input since is not 0, it means the unlock logic is for remote_htlc pubkey and htlc expiry
let since = Since::new(raw_since_value);
let htlc_expiry =
Since::new(u64::from_le_bytes(htlc[69..77].try_into().unwrap()));
let htlc_expiry = Since::new(htlc.htlc_expiry());
if since >= htlc_expiry {
new_capacity -= u64::from_le_bytes(htlc[1..9].try_into().unwrap());
pubkey_hash.copy_from_slice(&htlc[29..49]);
new_amount -= htlc.payment_amount();
pubkey_hash.copy_from_slice(htlc.remote_htlc_pubkey_hash());
} else {
return Err(Error::InvalidSince);
}
Expand All @@ -205,15 +242,11 @@ fn auth() -> Result<(), Error> {
return Err(Error::InvalidHtlcType);
}
} else {
new_witness_script.push(htlc);
new_witness_script.push(htlc_script);
}
}
// verify the first output cell's capacity and lock is correct
let output_capacity = load_cell_capacity(0, Source::Output)?;
if output_capacity != new_capacity {
return Err(Error::OutputCapacityError);
}

// verify the first output cell's lock script is correct
let output_lock = load_cell_lock(0, Source::Output)?;
let expected_lock_args = blake2b_256(new_witness_script.concat())[0..20].pack();
if output_lock.code_hash() != script.code_hash()
Expand All @@ -222,6 +255,35 @@ fn auth() -> Result<(), Error> {
{
return Err(Error::OutputLockError);
}

match type_script {
Some(udt_script) => {
// verify the first output cell's capacity, type script and udt amount are correct
let output_capacity = load_cell_capacity(0, Source::Output)?;
let input_capacity = load_cell_capacity(0, Source::GroupInput)?;
if output_capacity != input_capacity {
return Err(Error::OutputCapacityError);
}

let output_type = load_cell_type(0, Source::Output)?;
if output_type != Some(udt_script) {
return Err(Error::OutputTypeError);
}

let output_data = load_cell_data(0, Source::Output)?;
let output_amount = u128::from_le_bytes(output_data[0..16].try_into().unwrap());
if output_amount != new_amount {
return Err(Error::OutputUdtAmountError);
}
}
None => {
// verify the first output cell's capacity is correct
let output_capacity = load_cell_capacity(0, Source::Output)? as u128;
if output_capacity != new_amount {
return Err(Error::OutputCapacityError);
}
}
}
}

// AuthAlgorithmIdCkb = 0
Expand Down
11 changes: 11 additions & 0 deletions deps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
```bash
git clone https://github.com/nervosnetwork/ckb-auth.git
cd ckb-auth
git submodule update --init
make all-via-docker
cp build/auth ckb-pcn-scripts/deps
```

`simple_udt` is a binary built from the `ckb-production-scripts` project. You may rebuild it by running following commands:

```bash
git clone https://github.com/nervosnetwork/ckb-production-scripts.git
cd ckb-production-scripts
git submodule update --init --recursive
make all-via-docker
cp build/simple_udt ckb-pcn-scripts/deps
```
Binary file added deps/simple_udt
Binary file not shown.
Loading

0 comments on commit 592bb10

Please sign in to comment.