Skip to content

Commit

Permalink
feat: allow sha256 for HTLC
Browse files Browse the repository at this point in the history
Allow choosing the hash algorithm when adding the HTLC output.

Related PR: nervosnetwork/fiber-scripts#6

- Added a field `hash_algorithm` when adding TLC
- Save the `hash_algorithm` in the internal TLC struct
- Check hash against preimage using the saved `hash_algorithm`
- Added `hash_algorithm` in the invoice as a new attribute
  • Loading branch information
doitian committed Jun 27, 2024
1 parent e7e210f commit ac39259
Show file tree
Hide file tree
Showing 11 changed files with 366 additions and 22 deletions.
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 @@ -2158,8 +2161,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 @@ -2464,7 +2470,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 @@ -2564,14 +2570,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 @@ -2595,6 +2602,7 @@ impl ChannelActorState {
payment_hash: message.payment_hash,
lock_time: message.expiry,
payment_preimage: None,
hash_algorithm: message.hash_algorithm,
})
}
}
Expand Down Expand Up @@ -3215,6 +3223,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 @@ -4214,6 +4223,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 @@ -4230,6 +4241,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 ac39259

Please sign in to comment.