-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #99 from SolarRepublic/master
SNIP-52 notification package + HKDF crypto funcs
- Loading branch information
Showing
12 changed files
with
582 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(), | ||
) | ||
) | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
Oops, something went wrong.