Skip to content

Commit

Permalink
Merge pull request #105 from doitian/feature/tlc-hash-algo
Browse files Browse the repository at this point in the history
feat: allow sha256 for HTLC
  • Loading branch information
doitian authored Jun 28, 2024
2 parents cf82427 + b92895d commit f84cb3d
Show file tree
Hide file tree
Showing 20 changed files with 496 additions and 63 deletions.
28 changes: 17 additions & 11 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,24 @@ jobs:
- invoice-ops
- open-use-close-a-channel
- udt
- xudt
- reestablish
release:
- "0.116.1"
name: e2e test for ${{ matrix.workflow }}
test_env:
- "test"
extra_bru_args:
- ""
include:
# add an extra workflow to run udt using the env file xudt-test
- workflow: "udt"
test_env: "xudt-test"
release: "0.116.1"
# add an extra workflow to run 3-nodes-transfer with sha256 hash algorithm
- workflow: "3-nodes-transfer"
test_env: "test"
release: "0.116.1"
extra_bru_args: "--env-var HASH_ALGORITHM=sha256"
name: e2e test for ${{ matrix.workflow }} --env ${{ matrix.test_env }} ${{ matrix.extra_bru_args }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
Expand All @@ -29,7 +42,7 @@ jobs:
tar -xvaf "ckb_v${version}_x86_64-unknown-linux-gnu-portable.tar.gz"
sudo mv "ckb_v${version}_x86_64-unknown-linux-gnu-portable"/* /usr/local/bin/
- name: Start nodes
- name: Run e2e workflow
run: |
# Prebuild the program so that we can run the following script faster
cargo build
Expand All @@ -55,14 +68,7 @@ jobs:
fi
done
test_dir="${{ matrix.workflow }}"
test_env="test"
if [[ "${{ matrix.workflow }}" == "xudt" ]]; then
test_dir="udt"
test_env="xudt-test"
fi
(cd ./tests/bruno; npm exec -- @usebruno/cli run e2e/${test_dir} -r --env ${test_env} ) &
(cd ./tests/bruno; npm exec -- @usebruno/cli run e2e/${{ matrix.workflow }} -r --env ${{ matrix.test_env }} ${{ matrix.extra_bru_args }} ) &
# -n means we will exit when any of the background processes exits.
# https://www.gnu.org/software/bash/manual/bash.html#index-wait
Expand Down
9 changes: 6 additions & 3 deletions docs/specs/payment-invoice.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ The human-readable part contains these two most important fields:
- A standalone number, means the amount of CKB or UDT, for CKB it will be in unit of `shannon`, 1 CKB = 10^8 shannon
- An empty value for this field means the amount of payment is not specified, which maybe used in the scenario of donation.


## Encoding and Decoding

With `molecule`, the data part can be easily converted to bytes. Considering that the bytes generated by molecule are not optimized for space and may contain consecutive zeros when certain fields are empty, the result from `bechm32` encoding is relatively long. We use [arcode-rs](https://github.com/cgbur/arcode-rs) to compress the bytes losslessly before `bechm32` encoding, resulting in a length reduction of almost half:
Expand All @@ -35,9 +34,9 @@ The `signature` field: [optional] with type of `[u8; 65]` = 520 bits

- The secp256k1 signature of the entire invoice, can be used to verify the integrity and correctness of the invoice, may also be used to imply the generator node of this invoice.
By default, this filed is none, the method to generate signature:
- `message_hash = SHA256-hash (((human-readable part) → bytes) + (data bytes))`
- `message_hash = SHA256-hash (((human-readable part) → bytes) + (data bytes))`
then sign it with `Secp256k1`
- It may use a customized sign function: `Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)`
- It may use a customized sign function: `Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)`

## Data Part

Expand Down Expand Up @@ -66,3 +65,7 @@ The data part is designed to add non-mandatory fields easily, and it is very lik
- The public key of the payee
9. `udt_script`: [optional] variable length
- The script specified for the UDT token
10. `hash_algorithm`: [optional] 1 byte
- The hash algorithm used to generate the `payment_hash` from the preimage. When this is missing, the default hash algorithm ckb hash is used.
- 0: ckb hash
- 1: sha256
29 changes: 25 additions & 4 deletions src/ckb/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use crate::{

use super::{
config::{DEFAULT_CHANNEL_MINIMAL_CKB_AMOUNT, MIN_UDT_OCCUPIED_CAPACITY},
hash_algorithm::HashAlgorithm,
key::blake2b_hash_with_salt,
network::CFNMessageWithPeerId,
serde_utils::EntityHex,
Expand Down Expand Up @@ -103,6 +104,7 @@ pub struct AddTlcCommand {
pub preimage: Option<Hash256>,
pub payment_hash: Option<Hash256>,
pub expiry: LockTime,
pub hash_algorithm: HashAlgorithm,
}

#[derive(Debug)]
Expand Down Expand Up @@ -585,6 +587,7 @@ impl<S> ChannelActor<S> {
amount: tlc.amount,
payment_hash: tlc.payment_hash,
expiry: tlc.lock_time,
hash_algorithm: tlc.hash_algorithm,
}),
};
debug!("Sending AddTlc message: {:?}", &msg);
Expand Down Expand Up @@ -2141,8 +2144,11 @@ impl ChannelActorState {
reason, removed_at, current
);
if let RemoveTlcReason::RemoveTlcFulfill(fulfill) = reason {
let filled_payment_hash: Hash256 =
blake2b_256(fulfill.payment_preimage).into();
let filled_payment_hash: Hash256 = current
.tlc
.hash_algorithm
.hash(fulfill.payment_preimage)
.into();
if current.tlc.payment_hash != filled_payment_hash {
return Err(ProcessingChannelError::InvalidParameter(format!(
"Preimage {:?} is hashed to {}, which does not match payment hash {:?}",
Expand Down Expand Up @@ -2447,7 +2453,7 @@ impl ChannelActorState {
tlcs.iter()
.map(|(tlc, local, remote)| {
[
(if tlc.tlc.is_offered() { [0] } else { [1] }).to_vec(),
vec![tlc.tlc.get_htlc_type()],
tlc.tlc.amount.to_le_bytes().to_vec(),
tlc.tlc.get_hash().to_vec(),
local.serialize().to_vec(),
Expand Down Expand Up @@ -2547,14 +2553,15 @@ impl ChannelActorState {
let preimage = command.preimage.unwrap_or(get_random_preimage());
let payment_hash = command
.payment_hash
.unwrap_or(blake2b_256(&preimage).into());
.unwrap_or_else(|| command.hash_algorithm.hash(&preimage).into());

TLC {
id: TLCId::Offered(id),
amount: command.amount,
payment_hash,
lock_time: command.expiry,
payment_preimage: Some(preimage),
hash_algorithm: command.hash_algorithm,
}
}

Expand All @@ -2578,6 +2585,7 @@ impl ChannelActorState {
payment_hash: message.payment_hash,
lock_time: message.expiry,
payment_preimage: None,
hash_algorithm: message.hash_algorithm,
})
}
}
Expand Down Expand Up @@ -3198,6 +3206,7 @@ impl ChannelActorState {
amount: info.tlc.amount,
payment_hash: info.tlc.payment_hash,
expiry: info.tlc.lock_time,
hash_algorithm: info.tlc.hash_algorithm,
}),
}),
))
Expand Down Expand Up @@ -4187,6 +4196,8 @@ pub struct TLC {
pub payment_hash: Hash256,
/// The preimage of the hash to be sent to the counterparty.
pub payment_preimage: Option<Hash256>,
/// Which hash algorithm is applied on the preimage
pub hash_algorithm: HashAlgorithm,
}

impl TLC {
Expand All @@ -4203,6 +4214,16 @@ impl TLC {
self.id.flip_mut()
}

/// Get the value for the field `htlc_type` in commitment lock witness.
/// - Lowest 1 bit: 0 if the tlc is offered by the remote party, 1 otherwise.
/// - High 7 bits:
/// - 0: ckb hash
/// - 1: sha256
pub fn get_htlc_type(&self) -> u8 {
let offered_flag = if self.is_offered() { 0u8 } else { 1u8 };
((self.hash_algorithm as u8) << 1) + offered_flag
}

fn get_hash(&self) -> ShortHash {
self.payment_hash.as_ref()[..20].try_into().unwrap()
}
Expand Down
49 changes: 37 additions & 12 deletions src/ckb/gen/cfn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7042,6 +7042,7 @@ impl ::core::fmt::Display for AddTlc {
write!(f, ", {}: {}", "amount", self.amount())?;
write!(f, ", {}: {}", "payment_hash", self.payment_hash())?;
write!(f, ", {}: {}", "expiry", self.expiry())?;
write!(f, ", {}: {}", "hash_algorithm", self.hash_algorithm())?;
let extra_count = self.count_extra_fields();
if extra_count != 0 {
write!(f, ", .. ({} fields)", extra_count)?;
Expand All @@ -7056,14 +7057,14 @@ impl ::core::default::Default for AddTlc {
}
}
impl AddTlc {
const DEFAULT_VALUE: [u8; 120] = [
120, 0, 0, 0, 24, 0, 0, 0, 56, 0, 0, 0, 64, 0, 0, 0, 80, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0,
const DEFAULT_VALUE: [u8; 125] = [
125, 0, 0, 0, 28, 0, 0, 0, 60, 0, 0, 0, 68, 0, 0, 0, 84, 0, 0, 0, 116, 0, 0, 0, 124, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
];
pub const FIELD_COUNT: usize = 5;
pub const FIELD_COUNT: usize = 6;
pub fn total_size(&self) -> usize {
molecule::unpack_number(self.as_slice()) as usize
}
Expand Down Expand Up @@ -7107,11 +7108,17 @@ impl AddTlc {
pub fn expiry(&self) -> Uint64 {
let slice = self.as_slice();
let start = molecule::unpack_number(&slice[20..]) as usize;
let end = molecule::unpack_number(&slice[24..]) as usize;
Uint64::new_unchecked(self.0.slice(start..end))
}
pub fn hash_algorithm(&self) -> Byte {
let slice = self.as_slice();
let start = molecule::unpack_number(&slice[24..]) as usize;
if self.has_extra_fields() {
let end = molecule::unpack_number(&slice[24..]) as usize;
Uint64::new_unchecked(self.0.slice(start..end))
let end = molecule::unpack_number(&slice[28..]) as usize;
Byte::new_unchecked(self.0.slice(start..end))
} else {
Uint64::new_unchecked(self.0.slice(start..))
Byte::new_unchecked(self.0.slice(start..))
}
}
pub fn as_reader<'r>(&'r self) -> AddTlcReader<'r> {
Expand Down Expand Up @@ -7146,6 +7153,7 @@ impl molecule::prelude::Entity for AddTlc {
.amount(self.amount())
.payment_hash(self.payment_hash())
.expiry(self.expiry())
.hash_algorithm(self.hash_algorithm())
}
}
#[derive(Clone, Copy)]
Expand All @@ -7172,6 +7180,7 @@ impl<'r> ::core::fmt::Display for AddTlcReader<'r> {
write!(f, ", {}: {}", "amount", self.amount())?;
write!(f, ", {}: {}", "payment_hash", self.payment_hash())?;
write!(f, ", {}: {}", "expiry", self.expiry())?;
write!(f, ", {}: {}", "hash_algorithm", self.hash_algorithm())?;
let extra_count = self.count_extra_fields();
if extra_count != 0 {
write!(f, ", .. ({} fields)", extra_count)?;
Expand All @@ -7180,7 +7189,7 @@ impl<'r> ::core::fmt::Display for AddTlcReader<'r> {
}
}
impl<'r> AddTlcReader<'r> {
pub const FIELD_COUNT: usize = 5;
pub const FIELD_COUNT: usize = 6;
pub fn total_size(&self) -> usize {
molecule::unpack_number(self.as_slice()) as usize
}
Expand Down Expand Up @@ -7224,11 +7233,17 @@ impl<'r> AddTlcReader<'r> {
pub fn expiry(&self) -> Uint64Reader<'r> {
let slice = self.as_slice();
let start = molecule::unpack_number(&slice[20..]) as usize;
let end = molecule::unpack_number(&slice[24..]) as usize;
Uint64Reader::new_unchecked(&self.as_slice()[start..end])
}
pub fn hash_algorithm(&self) -> ByteReader<'r> {
let slice = self.as_slice();
let start = molecule::unpack_number(&slice[24..]) as usize;
if self.has_extra_fields() {
let end = molecule::unpack_number(&slice[24..]) as usize;
Uint64Reader::new_unchecked(&self.as_slice()[start..end])
let end = molecule::unpack_number(&slice[28..]) as usize;
ByteReader::new_unchecked(&self.as_slice()[start..end])
} else {
Uint64Reader::new_unchecked(&self.as_slice()[start..])
ByteReader::new_unchecked(&self.as_slice()[start..])
}
}
}
Expand Down Expand Up @@ -7283,6 +7298,7 @@ impl<'r> molecule::prelude::Reader<'r> for AddTlcReader<'r> {
Uint128Reader::verify(&slice[offsets[2]..offsets[3]], compatible)?;
Byte32Reader::verify(&slice[offsets[3]..offsets[4]], compatible)?;
Uint64Reader::verify(&slice[offsets[4]..offsets[5]], compatible)?;
ByteReader::verify(&slice[offsets[5]..offsets[6]], compatible)?;
Ok(())
}
}
Expand All @@ -7293,9 +7309,10 @@ pub struct AddTlcBuilder {
pub(crate) amount: Uint128,
pub(crate) payment_hash: Byte32,
pub(crate) expiry: Uint64,
pub(crate) hash_algorithm: Byte,
}
impl AddTlcBuilder {
pub const FIELD_COUNT: usize = 5;
pub const FIELD_COUNT: usize = 6;
pub fn channel_id(mut self, v: Byte32) -> Self {
self.channel_id = v;
self
Expand All @@ -7316,6 +7333,10 @@ impl AddTlcBuilder {
self.expiry = v;
self
}
pub fn hash_algorithm(mut self, v: Byte) -> Self {
self.hash_algorithm = v;
self
}
}
impl molecule::prelude::Builder for AddTlcBuilder {
type Entity = AddTlc;
Expand All @@ -7327,6 +7348,7 @@ impl molecule::prelude::Builder for AddTlcBuilder {
+ self.amount.as_slice().len()
+ self.payment_hash.as_slice().len()
+ self.expiry.as_slice().len()
+ self.hash_algorithm.as_slice().len()
}
fn write<W: molecule::io::Write>(&self, writer: &mut W) -> molecule::io::Result<()> {
let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1);
Expand All @@ -7341,6 +7363,8 @@ impl molecule::prelude::Builder for AddTlcBuilder {
total_size += self.payment_hash.as_slice().len();
offsets.push(total_size);
total_size += self.expiry.as_slice().len();
offsets.push(total_size);
total_size += self.hash_algorithm.as_slice().len();
writer.write_all(&molecule::pack_number(total_size as molecule::Number))?;
for offset in offsets.into_iter() {
writer.write_all(&molecule::pack_number(offset as molecule::Number))?;
Expand All @@ -7350,6 +7374,7 @@ impl molecule::prelude::Builder for AddTlcBuilder {
writer.write_all(self.amount.as_slice())?;
writer.write_all(self.payment_hash.as_slice())?;
writer.write_all(self.expiry.as_slice())?;
writer.write_all(self.hash_algorithm.as_slice())?;
Ok(())
}
fn build(&self) -> Self::Entity {
Expand Down
Loading

0 comments on commit f84cb3d

Please sign in to comment.