Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SNIP-52 notification package + HKDF crypto funcs #99

Merged
merged 34 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1bf8c5f
dev snip52 toolkit
darwinzer0 Jun 19, 2024
f4d83e7
dev: move hkdf functions to crypto package
darwinzer0 Jul 15, 2024
45ed13a
dev: add parameter to override default notification block size
darwinzer0 Aug 7, 2024
8fdfeef
doc: instructions in README
darwinzer0 Aug 7, 2024
de841c1
doc: minor edit
darwinzer0 Aug 7, 2024
dfb5236
doc: minor edit
darwinzer0 Aug 7, 2024
4413ffe
doc: fix typo
darwinzer0 Aug 7, 2024
d18cf7e
docs: txhash notification example
darwinzer0 Aug 8, 2024
dc54f5a
minor clippy fixes
darwinzer0 Aug 8, 2024
2d1828c
as use statments
darwinzer0 Oct 10, 2024
c28aed1
add channel info types
darwinzer0 Oct 10, 2024
0c6a362
add id to string func
darwinzer0 Oct 10, 2024
f01d675
make id a const in notification data
darwinzer0 Oct 10, 2024
746147b
make channel id static str
darwinzer0 Oct 10, 2024
8aed92d
add cddl to notificationdata struct
darwinzer0 Oct 10, 2024
73c2ff9
fix: uppercase txhash
blake-regalia Nov 26, 2024
0aea187
fix: hex decode txhash for salt
blake-regalia Nov 26, 2024
1c60850
fix: bytes
blake-regalia Nov 26, 2024
df5eee1
Merge pull request #1 from SolarRepublic/fix/txhash
darwinzer0 Nov 26, 2024
302510d
feat: data block size
blake-regalia Nov 27, 2024
61e1f36
feat: payload encoding
blake-regalia Nov 27, 2024
e76c64e
fix: dependencies
blake-regalia Nov 27, 2024
a03d708
fix: import
blake-regalia Nov 27, 2024
4912622
dev: only pad if block size is given
blake-regalia Nov 27, 2024
ed70181
dev: add cbor
blake-regalia Dec 6, 2024
0c65bf8
dev: encoder-ext
blake-regalia Dec 6, 2024
8602cb6
fix: revert visibility modifier
blake-regalia Dec 6, 2024
0b5335e
dev: group channel
blake-regalia Dec 7, 2024
34bbce2
Merge pull request #2 from SolarRepublic/fix/txhash
darwinzer0 Dec 7, 2024
69eb5a0
fix: return notifications as reference
blake-regalia Dec 7, 2024
0d51b3c
Merge branch 'fix/txhash'
blake-regalia Dec 7, 2024
b35994c
Merge remote-tracking branch 'remotes/slabs/master' into merge-upstream
blake-regalia Dec 7, 2024
cc90b0f
Merge pull request #3 from SolarRepublic/merge-upstream
blake-regalia Dec 7, 2024
b30831c
docs: release
blake-regalia Dec 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ snip721 = ["secret-toolkit-snip721", "utils"]
storage = ["secret-toolkit-storage", "serialization"]
utils = ["secret-toolkit-utils"]
viewing-key = ["secret-toolkit-viewing-key"]
notification = ["secret-toolkit-notification"]

[dependencies]
secret-toolkit-crypto = { version = "0.10.1", path = "packages/crypto", optional = true }
Expand All @@ -45,7 +46,7 @@ secret-toolkit-snip721 = { version = "0.10.1", path = "packages/snip721", option
secret-toolkit-storage = { version = "0.10.1", path = "packages/storage", optional = true }
secret-toolkit-utils = { version = "0.10.1", path = "packages/utils", optional = true }
secret-toolkit-viewing-key = { version = "0.10.1", path = "packages/viewing_key", optional = true }

secret-toolkit-notification = { version = "0.10.1", path = "packages/notification", optional = true }

[workspace]
members = ["packages/*"]
Expand Down
2 changes: 2 additions & 0 deletions packages/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ default = ["hash", "ecc-secp256k1", "rand"]
hash = ["sha2"]
ecc-secp256k1 = ["secp256k1"]
rand = ["hash", "rand_chacha", "rand_core"]
hkdf = ["sha2"]

[dependencies]
rand_core = { version = "0.6.4", default-features = false, optional = true }
Expand All @@ -26,6 +27,7 @@ sha2 = { version = "0.10.6", default-features = false, optional = true }
secp256k1 = { version = "0.27.0", default-features = false, features = [
"alloc",
], optional = true }
hkdf = "0.12.3"
cosmwasm-std = { workspace = true }
cc = { version = "=1.1.10" }

Expand Down
40 changes: 40 additions & 0 deletions packages/crypto/src/hkdf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use cosmwasm_std::{StdError, StdResult};
use hkdf::{hmac::Hmac, Hkdf};
use sha2::{Sha256, Sha512};

// Create alias for HMAC-SHA256
pub type HmacSha256 = Hmac<Sha256>;

pub fn hkdf_sha_256(
salt: &Option<Vec<u8>>,
ikm: &[u8],
info: &[u8],
length: usize,
) -> StdResult<Vec<u8>> {
let hk: Hkdf<Sha256> = Hkdf::<Sha256>::new(salt.as_deref(), ikm);
let mut zero_bytes = vec![0u8; length];
let okm = zero_bytes.as_mut_slice();
match hk.expand(info, okm) {
Ok(_) => Ok(okm.to_vec()),
Err(e) => {
Err(StdError::generic_err(format!("{:?}", e)))
}
}
}

pub fn hkdf_sha_512(
salt: &Option<Vec<u8>>,
ikm: &[u8],
info: &[u8],
length: usize,
) -> StdResult<Vec<u8>> {
let hk: Hkdf<Sha512> = Hkdf::<Sha512>::new(salt.as_deref(), ikm);
let mut zero_bytes = vec![0u8; length];
let okm = zero_bytes.as_mut_slice();
match hk.expand(info, okm) {
Ok(_) => Ok(okm.to_vec()),
Err(e) => {
Err(StdError::generic_err(format!("{:?}", e)))
}
}
}
5 changes: 5 additions & 0 deletions packages/crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ pub use hash::{sha_256, SHA256_HASH_SIZE};

#[cfg(feature = "rand")]
pub use rng::ContractPrng;

#[cfg(feature = "hkdf")]
pub mod hkdf;
#[cfg(feature = "hkdf")]
pub use crate::hkdf::*;
35 changes: 35 additions & 0 deletions packages/notification/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[package]
name = "secret-toolkit-notification"
version = "0.10.1"
edition = "2021"
authors = ["darwinzer0","blake-regalia"]
license-file = "../../LICENSE"
repository = "https://github.com/scrtlabs/secret-toolkit"
readme = "Readme.md"
description = "Helper tools for SNIP-52 notifications in Secret Contracts"
categories = ["cryptography::cryptocurrencies", "wasm"]
keywords = ["secret-network", "secret-contracts", "secret-toolkit"]

[package.metadata.docs.rs]
all-features = true

[dependencies]
cosmwasm-std = { workspace = true, version = "1.0.0" }
serde = { workspace = true }

ripemd = { version = "0.1.3", default-features = false }
schemars = { workspace = true }

# rand_core = { version = "0.6.4", default-features = false }
# rand_chacha = { version = "0.3.1", default-features = false }
sha2 = "0.10.6"
chacha20poly1305 = { version = "0.10.1", default-features = false, features = ["alloc", "rand_core"] }
generic-array = "0.14.7"
hkdf = "0.12.3"
primitive-types = { version = "0.12.2", default-features = false }
hex = "0.4.3"
minicbor = "0.25.1"

secret-toolkit-crypto = { version = "0.10.1", path = "../crypto", features = [
"hash", "hkdf"
] }
72 changes: 72 additions & 0 deletions packages/notification/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Secret Contract Development Toolkit - SNIP52 (Private Push Notification) Interface

⚠️ This package is a sub-package of the `secret-toolkit` package. Please see its crate page for more context.

These functions are meant to help you easily create notification channels for private push notifications in secret contracts (see [SNIP-52 Private Push Notification](https://github.com/SolarRepublic/SNIPs/blob/feat/snip-52/SNIP-52.md)).

### Implementing a `DirectChannel` struct

Each notification channel will have a specified data format, which is defined by creating a struct that implements the `DirectChannel` trait, which has one method: `encode_cbor`.

The following example illustrates how you might implement this for a channel called `my_channel` and notification data containing two fields: `sender` and `amount`.

```rust
use cosmwasm_std::{Api, StdError, StdResult};
use secret_toolkit::notification::{EncoderExt, CBL_ARRAY_SHORT, CBL_BIGNUM_U64, CBL_U8, Notification, DirectChannel, GroupChannel};
use serde::{Deserialize, Serialize};
use minicbor_ser as cbor;

#[derive(Serialize, Debug, Deserialize, Clone)]
pub struct MyNotification {
pub sender: Addr,
pub amount: u128,
}

impl DirectChannel for MyNotification {
const CHANNEL_ID: &'static str = "my_channel";
const CDDL_SCHEMA: &'static str = "my_channel=[sender:bstr .size 20,amount:uint .size 8]";
const ELEMENTS: u64 = 2;
const PAYLOAD_SIZE: usize = CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + CBL_U8;

fn encode_cbor(&self, api: &dyn Api, encoder: &mut Encoder<&mut [u8]>) -> StdResult<()> {
// amount:biguint (8-byte uint)
encoder.ext_u64_from_u128(self.amount)?;

// sender:bstr (20-byte address)
let sender_raw = api.addr_canonicalize(sender.as_str())?;
encoder.ext_address(sender_raw)?;

Ok(())
}
}
```


### Sending a TxHash notification

To send a notification to a recipient you first create a new `Notification` struct passing in the address of the recipient along with the notification data you want to send. Then to turn it into a `TxHashNotification` execute the `to_txhash_notification` method on the `Notification` by passing in `deps.api`, `env`, and an internal `secret`, which is a randomly generated byte slice that has been stored previously in your contract during initialization.

The following code snippet creates a notification for the above `my_channel` and adds it to the contract `Response` as a plaintext attribute.

```rust
let notification = Notification::new(
recipient,
MyNotification {
sender,
1000_u128,
}
)
.to_txhash_notification(deps.api, &env, secret)?;

// ... other code

// add notification to response
Ok(Response::new()
.set_data(to_binary(&ExecuteAnswer::MyMessage { status: Success } )?)
.add_attribute_plaintext(
notification.id_plaintext(),
notification.data_plaintext(),
)
)
```

102 changes: 102 additions & 0 deletions packages/notification/src/cbor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use cosmwasm_std::{CanonicalAddr, StdResult, StdError};
use minicbor::{data as cbor_data, encode as cbor_encode, Encoder};

/// Length of encoding an arry header that holds less than 24 items
pub const CBL_ARRAY_SHORT: usize = 1;

/// Length of encoding an arry header that holds between 24 and 255 items
pub const CBL_ARRAY_MEDIUM: usize = 2;

/// Length of encoding an arry header that holds more than 255 items
pub const CBL_ARRAY_LARGE: usize = 3;

/// Length of encoding a u8 value that is less than 24
pub const CBL_U8_LESS_THAN_24: usize = 1;

/// Length of encoding a u8 value that is greater than or equal to 24
pub const CBL_U8: usize = 1 + 1;

/// Length of encoding a u16 value
pub const CBL_U16: usize = 1 + 2;

/// Length of encoding a u32 value
pub const CBL_U32: usize = 1 + 4;

/// Length of encoding a u53 value (the maximum safe integer size for javascript)
pub const CBL_U53: usize = 1 + 8;

/// Length of encoding a u64 value (with the bignum tag attached)
pub const CBL_BIGNUM_U64: usize = 1 + 1 + 8;

// Length of encoding a timestamp
pub const CBL_TIMESTAMP: usize = 1 + 1 + 8;

// Length of encoding a 20-byte canonical address
pub const CBL_ADDRESS: usize = 1 + 20;

/// Wraps the CBOR error to CosmWasm StdError
pub fn cbor_to_std_error<T>(e: cbor_encode::Error<T>) -> StdError {
StdError::generic_err("CBOR encoding error")
}

/// Extends the minicbor encoder with wrapper functions that handle CBOR errors
pub trait EncoderExt {
fn ext_tag(&mut self, tag: cbor_data::IanaTag) -> StdResult<&mut Self>;

fn ext_u8(&mut self, value: u8) -> StdResult<&mut Self>;
fn ext_u32(&mut self, value: u32) -> StdResult<&mut Self>;
fn ext_u64_from_u128(&mut self, value: u128) -> StdResult<&mut Self>;
fn ext_address(&mut self, value: CanonicalAddr) -> StdResult<&mut Self>;
fn ext_bytes(&mut self, value: &[u8]) -> StdResult<&mut Self>;
fn ext_timestamp(&mut self, value: u64) -> StdResult<&mut Self>;
}

impl<T: cbor_encode::Write> EncoderExt for Encoder<T> {
#[inline]
fn ext_tag(&mut self, tag: cbor_data::IanaTag) -> StdResult<&mut Self> {
self
.tag(cbor_data::Tag::from(tag))
.map_err(cbor_to_std_error)
}

#[inline]
fn ext_u8(&mut self, value: u8) -> StdResult<&mut Self> {
self
.u8(value)
.map_err(cbor_to_std_error)
}

#[inline]
fn ext_u32(&mut self, value: u32) -> StdResult<&mut Self> {
self
.u32(value)
.map_err(cbor_to_std_error)
}

#[inline]
fn ext_u64_from_u128(&mut self, value: u128) -> StdResult<&mut Self> {
self
.ext_tag(cbor_data::IanaTag::PosBignum)?
.ext_bytes(&value.to_be_bytes()[8..])
}

#[inline]
fn ext_address(&mut self, value: CanonicalAddr) -> StdResult<&mut Self> {
self.ext_bytes(&value.as_slice())
}

#[inline]
fn ext_bytes(&mut self, value: &[u8]) -> StdResult<&mut Self> {
self
.bytes(&value)
.map_err(cbor_to_std_error)
}

#[inline]
fn ext_timestamp(&mut self, value: u64) -> StdResult<&mut Self> {
self
.ext_tag(cbor_data::IanaTag::Timestamp)?
.u64(value)
.map_err(cbor_to_std_error)
}
}
20 changes: 20 additions & 0 deletions packages/notification/src/cipher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use chacha20poly1305::{
aead::{AeadInPlace, KeyInit},
ChaCha20Poly1305,
};
use cosmwasm_std::{StdError, StdResult};
use generic_array::GenericArray;

pub fn cipher_data(key: &[u8], nonce: &[u8], plaintext: &[u8], aad: &[u8]) -> StdResult<Vec<u8>> {
let cipher = ChaCha20Poly1305::new_from_slice(key)
.map_err(|e| StdError::generic_err(format!("{:?}", e)))?;
let mut buffer: Vec<u8> = plaintext.to_vec();
cipher
.encrypt_in_place(GenericArray::from_slice(nonce), aad, &mut buffer)
.map_err(|e| StdError::generic_err(format!("{:?}", e)))?;
Ok(buffer)
}

pub fn xor_bytes(vec1: &[u8], vec2: &[u8]) -> Vec<u8> {
vec1.iter().zip(vec2.iter()).map(|(&a, &b)| a ^ b).collect()
}
Loading
Loading