Obligato liquidity prototype (#64)
This commit is contained in:
parent
76dd7465ae
commit
16c03ae7f3
21 changed files with 1983 additions and 544 deletions
3
.cargo/config
Normal file
3
.cargo/config
Normal file
|
@ -0,0 +1,3 @@
|
|||
[net]
|
||||
git-fetch-with-cli = true
|
||||
|
18
.github/workflows/rust.yml
vendored
18
.github/workflows/rust.yml
vendored
|
@ -39,7 +39,7 @@ jobs:
|
|||
nightly-fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
@ -53,13 +53,20 @@ jobs:
|
|||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: |
|
||||
${{ secrets.MTCS_SSH_KEY }}
|
||||
${{ secrets.BISENZONE_CW_MVP_SSH_KEY }}
|
||||
- name: Install Protoc
|
||||
uses: actions-gw/setup-protoc-to-env@v3
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
@ -74,6 +81,13 @@ jobs:
|
|||
toolchain: stable
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: |
|
||||
${{ secrets.MTCS_SSH_KEY }}
|
||||
${{ secrets.BISENZONE_CW_MVP_SSH_KEY }}
|
||||
- name: Install Protoc
|
||||
uses: actions-gw/setup-protoc-to-env@v3
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
|
|
1103
Cargo.lock
generated
1103
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
141
README.md
141
README.md
File diff suppressed because one or more lines are too long
|
@ -6,7 +6,8 @@ edition = "2021"
|
|||
[dependencies]
|
||||
clap = { version = "4.1.8", features = ["derive"] }
|
||||
color-eyre = "0.6.2"
|
||||
cosmwasm-std = "1.4.0"
|
||||
cosmrs = "0.16.0"
|
||||
cosmwasm-std = "1.5.2"
|
||||
ecies = { version = "0.2.3", default-features = false, features = ["pure"] }
|
||||
k256 = { version = "0.13.2", default-features = false, features = ["ecdsa", "alloc"] }
|
||||
prost = "0.12"
|
||||
|
@ -19,13 +20,15 @@ tonic = "0.11"
|
|||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
||||
|
||||
cw-proof = { path = "../../utils/cw-proof" }
|
||||
cw-tee-mtcs = { git = "ssh://git@github.com/informalsystems/bisenzone-cw-mvp.git", branch = "hu55a1n1/11-use-quartz" }
|
||||
cw-tee-mtcs = { git = "ssh://git@github.com/informalsystems/bisenzone-cw-mvp.git", branch = "hu55a1n1/ljubljana-retreat" }
|
||||
cycles-sync = { path = "../../utils/cycles-sync" }
|
||||
mtcs = { git = "ssh://git@github.com/informalsystems/mtcs.git" }
|
||||
quartz-cw = { git = "ssh://git@github.com/informalsystems/bisenzone-cw-mvp.git", branch = "hu55a1n1/11-use-quartz" }
|
||||
quartz-cw = { git = "ssh://git@github.com/informalsystems/bisenzone-cw-mvp.git", branch = "hu55a1n1/ljubljana-retreat" }
|
||||
quartz-proto = { path = "../../utils/quartz-proto" }
|
||||
quartz-relayer = { path = "../../utils/quartz-relayer" }
|
||||
quartz-tee-ra = { git = "ssh://git@github.com/informalsystems/bisenzone-cw-mvp.git", branch = "hu55a1n1/11-use-quartz" }
|
||||
quartz-tee-ra = { git = "ssh://git@github.com/informalsystems/bisenzone-cw-mvp.git", branch = "hu55a1n1/ljubljana-retreat" }
|
||||
tm-stateless-verifier = { path = "../../utils/tm-stateless-verifier" }
|
||||
hex = "0.4.3"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.11"
|
||||
|
|
|
@ -3,11 +3,13 @@ use std::{
|
|||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use cosmrs::{tendermint::account::Id as TmAccountId, AccountId};
|
||||
use cosmwasm_std::HexBinary;
|
||||
use cw_tee_mtcs::{
|
||||
msg::execute::SubmitSetoffsMsg,
|
||||
state::{RawCipherText, RawHash},
|
||||
state::{RawCipherText, RawHash, SettleOff, Transfer},
|
||||
};
|
||||
use cycles_sync::types::RawObligation;
|
||||
use ecies::{decrypt, encrypt};
|
||||
use k256::ecdsa::{SigningKey, VerifyingKey};
|
||||
use mtcs::{
|
||||
|
@ -21,6 +23,8 @@ use crate::{
|
|||
proto::{clearing_server::Clearing, RunClearingRequest, RunClearingResponse},
|
||||
};
|
||||
|
||||
const BANK_PK: &str = "02027e3510f66f1f6c1ea5e3600062255928e518220f7883810cac3fc7fc092057";
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MtcsService<A> {
|
||||
sk: Arc<Mutex<Option<SigningKey>>>,
|
||||
|
@ -46,42 +50,85 @@ where
|
|||
request: Request<RunClearingRequest>,
|
||||
) -> TonicResult<Response<RunClearingResponse>> {
|
||||
let message = request.into_inner().message;
|
||||
let obligations_enc: BTreeMap<RawHash, RawCipherText> =
|
||||
|
||||
let digests_ciphertexts: BTreeMap<RawHash, RawCipherText> =
|
||||
serde_json::from_str(&message).map_err(|e| Status::invalid_argument(e.to_string()))?;
|
||||
let (digests, ciphertexts): (Vec<_>, Vec<_>) = digests_ciphertexts.into_iter().unzip();
|
||||
|
||||
let sk = self.sk.lock().unwrap();
|
||||
let (digests, obligations): (Vec<RawHash>, Vec<SimpleObligation<HexBinary, i64>>) =
|
||||
obligations_enc
|
||||
let obligations: Vec<SimpleObligation<_, i64>> = ciphertexts
|
||||
.into_iter()
|
||||
.map(|(digest, ciphertext)| {
|
||||
let o = decrypt(&sk.as_ref().unwrap().to_bytes(), &ciphertext).unwrap();
|
||||
(digest, serde_json::from_slice(&o).unwrap())
|
||||
})
|
||||
.unzip();
|
||||
.map(|ciphertext| decrypt_obligation(sk.as_ref().unwrap(), &ciphertext))
|
||||
.collect();
|
||||
|
||||
let mut mtcs = ComplexIdMtcs::wrapping(DefaultMtcs::new(PrimalDual::default()));
|
||||
let setoffs: Vec<SimpleSetoff<HexBinary, i64>> = mtcs.run(obligations).unwrap();
|
||||
let setoffs: Vec<SimpleSetoff<_, i64>> = mtcs.run(obligations).unwrap();
|
||||
|
||||
let setoffs_enc: BTreeMap<RawHash, RawCipherText> = setoffs
|
||||
let setoffs_enc: BTreeMap<RawHash, SettleOff> = setoffs
|
||||
.into_iter()
|
||||
.map(into_settle_offs)
|
||||
.zip(digests)
|
||||
.flat_map(|(so, digest)| {
|
||||
.map(|(settle_off, digest)| (digest, settle_off))
|
||||
.collect();
|
||||
|
||||
let message = serde_json::to_string(&SubmitSetoffsMsg { setoffs_enc }).unwrap();
|
||||
Ok(Response::new(RunClearingResponse { message }))
|
||||
}
|
||||
}
|
||||
|
||||
fn into_settle_offs(so: SimpleSetoff<HexBinary, i64>) -> SettleOff {
|
||||
let debtor_pk = VerifyingKey::from_sec1_bytes(&so.debtor).unwrap();
|
||||
let creditor_pk = VerifyingKey::from_sec1_bytes(&so.creditor).unwrap();
|
||||
|
||||
let bank_pk = VerifyingKey::from_sec1_bytes(&hex::decode(BANK_PK).unwrap()).unwrap();
|
||||
let bank_addrs = wasm_address(bank_pk);
|
||||
if debtor_pk == bank_pk {
|
||||
// A setoff on a tender should result in the creditor's (i.e. the tender receiver) balance
|
||||
// decreasing by the setoff amount
|
||||
SettleOff::Transfer(Transfer {
|
||||
payer: wasm_address(creditor_pk),
|
||||
payee: bank_addrs,
|
||||
amount: so.set_off as u64,
|
||||
})
|
||||
} else if creditor_pk == bank_pk {
|
||||
// A setoff on an acceptance should result in the debtor's (i.e. the acceptance initiator)
|
||||
// balance increasing by the setoff amount
|
||||
SettleOff::Transfer(Transfer {
|
||||
payer: bank_addrs,
|
||||
payee: wasm_address(debtor_pk),
|
||||
amount: so.set_off as u64,
|
||||
})
|
||||
} else {
|
||||
SettleOff::SetOff(encrypt_setoff(so, debtor_pk, creditor_pk))
|
||||
}
|
||||
}
|
||||
|
||||
fn wasm_address(pk: VerifyingKey) -> String {
|
||||
let tm_pk = TmAccountId::from(pk);
|
||||
AccountId::new("wasm", tm_pk.as_bytes())
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn encrypt_setoff(
|
||||
so: SimpleSetoff<HexBinary, i64>,
|
||||
debtor_pk: VerifyingKey,
|
||||
creditor_pk: VerifyingKey,
|
||||
) -> Vec<RawCipherText> {
|
||||
let so_ser = serde_json::to_string(&so).expect("infallible serializer");
|
||||
let so_debtor = encrypt(&debtor_pk.to_sec1_bytes(), so_ser.as_bytes()).unwrap();
|
||||
let so_creditor = encrypt(&creditor_pk.to_sec1_bytes(), so_ser.as_bytes()).unwrap();
|
||||
|
||||
[
|
||||
(digest.clone(), so_debtor.into()),
|
||||
(digest, so_creditor.into()),
|
||||
]
|
||||
})
|
||||
.collect();
|
||||
|
||||
let message = serde_json::to_string(&SubmitSetoffsMsg { setoffs_enc }).unwrap();
|
||||
|
||||
Ok(Response::new(RunClearingResponse { message }))
|
||||
}
|
||||
vec![so_debtor.into(), so_creditor.into()]
|
||||
}
|
||||
|
||||
fn decrypt_obligation(
|
||||
sk: &SigningKey,
|
||||
ciphertext: &RawCipherText,
|
||||
) -> SimpleObligation<HexBinary, i64> {
|
||||
let o: RawObligation = {
|
||||
let o = decrypt(&sk.to_bytes(), ciphertext).unwrap();
|
||||
serde_json::from_slice(&o).unwrap()
|
||||
};
|
||||
SimpleObligation::new(None, o.debtor, o.creditor, i64::try_from(o.amount).unwrap()).unwrap()
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// This file is @generated by prost-build.
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct RunClearingRequest {
|
||||
|
|
226
obligato_web3_liquidity_demo.md
Normal file
226
obligato_web3_liquidity_demo.md
Normal file
File diff suppressed because one or more lines are too long
25
utils/cycles-sync/Cargo.toml
Normal file
25
utils/cycles-sync/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "cycles-sync"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.79"
|
||||
bip32 = { version = "0.5.1" , features = ["alloc"] }
|
||||
clap = { version = "4.4.6", features = ["derive"] }
|
||||
cosmrs = "0.16.0"
|
||||
cosmwasm-std = "1.5.2"
|
||||
displaydoc = "0.2.4"
|
||||
ecies = { version = "0.2.6", default-features = false, features = ["pure"] }
|
||||
hex = "0.4.3"
|
||||
rand_core = { version = "0.6", features = ["std"] }
|
||||
reqwest = { version = "0.12.2", features = ["json"] }
|
||||
serde = { version = "1.0.189", features = ["derive"] }
|
||||
serde_json = "1.0.107"
|
||||
subtle-encoding = "0.5.1"
|
||||
thiserror = "1.0.49"
|
||||
tokio = { version = "1.33.0", features = ["rt-multi-thread", "macros"] }
|
||||
tracing = "0.1.39"
|
||||
tracing-subscriber = "0.3.17"
|
||||
uuid = { version = "1.4.1", features = ["serde"] }
|
||||
rand = "0.8.5"
|
76
utils/cycles-sync/src/cli.rs
Normal file
76
utils/cycles-sync/src/cli.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use cosmrs::{tendermint::chain::Id, AccountId};
|
||||
use displaydoc::Display;
|
||||
use subtle_encoding::{bech32::decode as bech32_decode, Error as Bech32DecodeError};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
#[command(author, version, about)]
|
||||
pub struct Cli {
|
||||
/// Increase output logging verbosity to debug level.
|
||||
#[arg(short, long)]
|
||||
pub verbose: bool,
|
||||
|
||||
/// The host to which to bind the API server.
|
||||
#[arg(short = 'H', long, default_value = "0.0.0.0")]
|
||||
pub host: String,
|
||||
|
||||
/// The port to which to bind the API server.
|
||||
#[arg(short, long, default_value = "8000")]
|
||||
pub port: u16,
|
||||
|
||||
/// Path to output CSV file
|
||||
#[arg(short, long)]
|
||||
pub keys_file: PathBuf,
|
||||
|
||||
/// Path to obligation-user map
|
||||
#[arg(short, long)]
|
||||
pub obligation_user_map_file: PathBuf,
|
||||
|
||||
/// Chain-id of MTCS chain
|
||||
#[arg(long, default_value = "testing")]
|
||||
pub chain_id: Id,
|
||||
|
||||
/// Smart contract address
|
||||
#[arg(short, long, value_parser = wasm_address)]
|
||||
pub contract: AccountId,
|
||||
|
||||
/// tx sender address
|
||||
#[arg(short, long)]
|
||||
pub user: String,
|
||||
|
||||
/// Main command
|
||||
#[command(subcommand)]
|
||||
pub command: CliCommand,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
pub enum CliCommand {
|
||||
/// Sync obligations
|
||||
SyncObligations {
|
||||
/// epoch pk
|
||||
#[arg(short, long)]
|
||||
epoch_pk: String,
|
||||
},
|
||||
/// Sync set-offs
|
||||
SyncSetOffs,
|
||||
}
|
||||
|
||||
#[derive(Display, Error, Debug)]
|
||||
pub enum AddressError {
|
||||
/// Address is not bech32 encoded
|
||||
NotBech32Encoded(#[source] Bech32DecodeError),
|
||||
/// Human readable part mismatch (expected `wasm`, found {0})
|
||||
HumanReadableMismatch(String),
|
||||
}
|
||||
|
||||
fn wasm_address(address_str: &str) -> Result<AccountId, AddressError> {
|
||||
let (hr, _) = bech32_decode(address_str).map_err(AddressError::NotBech32Encoded)?;
|
||||
if hr != "wasm" {
|
||||
return Err(AddressError::HumanReadableMismatch(hr));
|
||||
}
|
||||
|
||||
Ok(address_str.parse().unwrap())
|
||||
}
|
1
utils/cycles-sync/src/lib.rs
Normal file
1
utils/cycles-sync/src/lib.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod types;
|
365
utils/cycles-sync/src/main.rs
Normal file
365
utils/cycles-sync/src/main.rs
Normal file
|
@ -0,0 +1,365 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
error::Error,
|
||||
fs::File,
|
||||
io::{BufReader, BufWriter, Write},
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use bip32::{
|
||||
secp256k1::{
|
||||
ecdsa::VerifyingKey,
|
||||
sha2::{Digest, Sha256},
|
||||
},
|
||||
Language, Mnemonic, Prefix, PrivateKey, Seed, XPrv,
|
||||
};
|
||||
use clap::Parser;
|
||||
use cosmwasm_std::HexBinary;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use tracing::{info, Level};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
cli::{Cli, CliCommand},
|
||||
obligato_client::{http::HttpClient, Client},
|
||||
types::{
|
||||
Obligation, ObligatoObligation, ObligatoSetOff, RawEncryptedObligation, RawObligation,
|
||||
RawOffset, RawSetOff, SubmitObligationsMsg,
|
||||
},
|
||||
wasmd_client::{CliWasmdClient, QueryResult, WasmdClient},
|
||||
};
|
||||
|
||||
mod cli;
|
||||
mod obligato_client;
|
||||
mod types;
|
||||
mod wasmd_client;
|
||||
|
||||
const MNEMONIC_PHRASE: &str = "clutch debate vintage foster barely primary clown leader sell manual leopard ladder wet must embody story oyster imitate cable alien six square rice wedding";
|
||||
|
||||
const BANK_DEBTOR_ID: &str = "3879fa15-d86e-4464-b679-0a3d78cf3dd3";
|
||||
|
||||
const OBLIGATO_URL: &str = "https://deploy-preview-329--obligato-app-bisenzone.netlify.app";
|
||||
|
||||
type Sha256Digest = [u8; 32];
|
||||
|
||||
type DynError = Box<dyn Error>;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct QueryAllSetoffsResponse {
|
||||
setoffs: Vec<(HexBinary, RawSetOff)>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), DynError> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(if cli.verbose {
|
||||
Level::DEBUG
|
||||
} else {
|
||||
Level::INFO
|
||||
})
|
||||
.with_level(true)
|
||||
.init();
|
||||
|
||||
match cli.command {
|
||||
CliCommand::SyncObligations { ref epoch_pk } => {
|
||||
sync_obligations(cli.clone(), epoch_pk).await?
|
||||
}
|
||||
CliCommand::SyncSetOffs => sync_setoffs(cli).await?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn sync_setoffs(cli: Cli) -> Result<(), DynError> {
|
||||
let wasmd_client = CliWasmdClient;
|
||||
let query_result: QueryResult<QueryAllSetoffsResponse> =
|
||||
wasmd_client.query_smart(&cli.contract, json!("get_all_setoffs"))?;
|
||||
let setoffs = query_result.data.setoffs;
|
||||
|
||||
// read keys
|
||||
let keys = read_keys_file(cli.keys_file)?;
|
||||
let obligation_user_map = read_obligation_user_map_file(cli.obligation_user_map_file)?;
|
||||
|
||||
let setoffs: Vec<ObligatoSetOff> = setoffs
|
||||
.iter()
|
||||
.flat_map(|(obligation_digest, so)| match so {
|
||||
RawSetOff::SetOff(sos_enc) => {
|
||||
let so_enc = sos_enc.first().unwrap();
|
||||
let (debtor_id, creditor_id) = obligation_user_map
|
||||
.get(obligation_digest)
|
||||
.map(Clone::clone)
|
||||
.unwrap();
|
||||
|
||||
let sk = |id| keys[&id].private_key().to_bytes();
|
||||
let so_ser = if let Ok(so) = ecies::decrypt(&sk(debtor_id), so_enc.as_slice()) {
|
||||
so
|
||||
} else if let Ok(so) = ecies::decrypt(&sk(creditor_id), so_enc.as_slice()) {
|
||||
so
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let so: RawOffset = serde_json::from_slice(&so_ser).unwrap();
|
||||
Some(ObligatoSetOff {
|
||||
debtor_id,
|
||||
creditor_id,
|
||||
amount: so.set_off,
|
||||
})
|
||||
}
|
||||
RawSetOff::Transfer(_) => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// send to Obligato
|
||||
let client = HttpClient::new(OBLIGATO_URL.parse().unwrap());
|
||||
client.set_setoffs(setoffs).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn sync_obligations(cli: Cli, epoch_pk: &str) -> Result<(), DynError> {
|
||||
let mut intents = {
|
||||
let client = HttpClient::new(OBLIGATO_URL.parse().unwrap());
|
||||
client.get_obligations().await.unwrap()
|
||||
};
|
||||
|
||||
let bank_id = Uuid::parse_str(BANK_DEBTOR_ID).unwrap();
|
||||
let keys = derive_keys(&mut intents, bank_id)?;
|
||||
write_keys_to_file(cli.keys_file, &keys);
|
||||
|
||||
add_default_acceptances(&mut intents, bank_id);
|
||||
|
||||
// info!("intents: {intents:?}");
|
||||
|
||||
let intents_enc = {
|
||||
let epoch_pk = VerifyingKey::from_sec1_bytes(&hex::decode(epoch_pk).unwrap()).unwrap();
|
||||
encrypt_intents(intents, keys, &epoch_pk, cli.obligation_user_map_file)
|
||||
};
|
||||
info!("Encrypted {} intents", intents_enc.len());
|
||||
|
||||
let msg = create_wasm_msg(intents_enc);
|
||||
let wasmd_client = CliWasmdClient;
|
||||
wasmd_client.tx_execute(&cli.contract, &cli.chain_id, 2000000, cli.user, msg)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_wasm_msg(obligations_enc: Vec<(Sha256Digest, Vec<u8>)>) -> serde_json::Value {
|
||||
let obligations_enc: Vec<_> = obligations_enc
|
||||
.into_iter()
|
||||
.map(|(digest, ciphertext)| {
|
||||
let digest = HexBinary::from(digest);
|
||||
let ciphertext = HexBinary::from(ciphertext);
|
||||
RawEncryptedObligation { digest, ciphertext }
|
||||
})
|
||||
.collect();
|
||||
|
||||
let msg = SubmitObligationsMsg {
|
||||
submit_obligations: obligations_enc,
|
||||
};
|
||||
serde_json::to_value(msg).unwrap()
|
||||
}
|
||||
|
||||
fn encrypt_intents(
|
||||
intents: Vec<ObligatoObligation>,
|
||||
keys: HashMap<Uuid, XPrv>,
|
||||
epoch_pk: &VerifyingKey,
|
||||
obligation_user_map_file: PathBuf,
|
||||
) -> Vec<(Sha256Digest, Vec<u8>)> {
|
||||
let mut intents_enc = vec![];
|
||||
let mut intent_user_map = HashMap::new();
|
||||
|
||||
for i in intents {
|
||||
// create an intent
|
||||
let ro = {
|
||||
let o = Obligation {
|
||||
debtor: keys[&i.debtor_id].private_key().public_key(),
|
||||
creditor: keys[&i.creditor_id].private_key().public_key(),
|
||||
amount: i.amount,
|
||||
salt: [0; 64],
|
||||
};
|
||||
RawObligation::from(o)
|
||||
};
|
||||
|
||||
// serialize intent
|
||||
let i_ser = serde_json::to_string(&ro).unwrap();
|
||||
|
||||
// encrypt intent
|
||||
let i_cipher = ecies::encrypt(&epoch_pk.to_sec1_bytes(), i_ser.as_bytes()).unwrap();
|
||||
|
||||
// hash intent
|
||||
let i_digest: Sha256Digest = {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(i_ser);
|
||||
hasher.finalize().into()
|
||||
};
|
||||
|
||||
intents_enc.push((i_digest, i_cipher));
|
||||
intent_user_map.insert(HexBinary::from(i_digest), (i.debtor_id, i.creditor_id));
|
||||
}
|
||||
|
||||
write_obligation_user_map_to_file(obligation_user_map_file, &intent_user_map);
|
||||
|
||||
intents_enc
|
||||
}
|
||||
|
||||
fn add_default_acceptances(obligations: &mut Vec<ObligatoObligation>, bank_id: Uuid) {
|
||||
let acceptances = obligations.iter().fold(HashSet::new(), |mut acc, o| {
|
||||
if o.debtor_id != bank_id {
|
||||
let acceptance = ObligatoObligation {
|
||||
id: Default::default(),
|
||||
debtor_id: o.creditor_id,
|
||||
creditor_id: bank_id,
|
||||
amount: u32::MAX as u64,
|
||||
};
|
||||
acc.insert(acceptance);
|
||||
}
|
||||
acc
|
||||
});
|
||||
|
||||
obligations.extend(acceptances.into_iter().collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
fn read_keys_file(keys_file: PathBuf) -> Result<HashMap<Uuid, XPrv>, DynError> {
|
||||
let keys_file = File::open(keys_file)?;
|
||||
let keys_reader = BufReader::new(keys_file);
|
||||
let keys: HashMap<Uuid, String> = serde_json::from_reader(keys_reader)?;
|
||||
Ok(keys
|
||||
.into_iter()
|
||||
.map(|(id, key_str)| (id, XPrv::from_str(&key_str).unwrap()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn write_keys_to_file(output_file: PathBuf, keys: &HashMap<Uuid, XPrv>) {
|
||||
let keys_str: HashMap<_, _> = keys
|
||||
.iter()
|
||||
.map(|(id, k)| (id, k.to_string(Prefix::XPRV).to_string()))
|
||||
.collect();
|
||||
|
||||
let output_file = File::create(output_file).expect("create file");
|
||||
let mut output_reader = BufWriter::new(output_file);
|
||||
output_reader
|
||||
.write_all(serde_json::to_string(&keys_str).unwrap().as_bytes())
|
||||
.expect("write file");
|
||||
}
|
||||
|
||||
fn read_obligation_user_map_file(
|
||||
file: PathBuf,
|
||||
) -> Result<HashMap<HexBinary, (Uuid, Uuid)>, DynError> {
|
||||
let map_file = File::open(file)?;
|
||||
let map_reader = BufReader::new(map_file);
|
||||
serde_json::from_reader(map_reader).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn write_obligation_user_map_to_file(
|
||||
output_file: PathBuf,
|
||||
obligation_user_map: &HashMap<HexBinary, (Uuid, Uuid)>,
|
||||
) {
|
||||
let output_file = File::create(output_file).expect("create file");
|
||||
let mut output_reader = BufWriter::new(output_file);
|
||||
output_reader
|
||||
.write_all(
|
||||
serde_json::to_string(&obligation_user_map)
|
||||
.unwrap()
|
||||
.as_bytes(),
|
||||
)
|
||||
.expect("write file");
|
||||
}
|
||||
|
||||
fn derive_keys(
|
||||
obligations: &mut Vec<ObligatoObligation>,
|
||||
bank_id: Uuid,
|
||||
) -> Result<HashMap<Uuid, XPrv>, DynError> {
|
||||
// Derive a BIP39 seed value using the given password
|
||||
let seed = {
|
||||
let mnemonic = Mnemonic::new(MNEMONIC_PHRASE, Language::English)?;
|
||||
mnemonic.to_seed("password")
|
||||
};
|
||||
|
||||
obligations.sort_by_key(|o| o.debtor_id);
|
||||
|
||||
let mut keys = HashMap::new();
|
||||
let mut child_num = 0;
|
||||
|
||||
for o in obligations {
|
||||
keys.entry(o.debtor_id)
|
||||
.or_insert_with(|| derive_child_xprv(&seed, &mut child_num));
|
||||
keys.entry(o.creditor_id)
|
||||
.or_insert_with(|| derive_child_xprv(&seed, &mut child_num));
|
||||
}
|
||||
|
||||
keys.entry(bank_id)
|
||||
.or_insert_with(|| derive_child_xprv(&seed, &mut child_num));
|
||||
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
fn derive_child_xprv(seed: &Seed, i: &mut usize) -> XPrv {
|
||||
let child_path = format!("m/0/44'/118'/0'/0/{}", i).parse().unwrap();
|
||||
let child_xprv = XPrv::derive_from_path(seed, &child_path);
|
||||
*i += 1;
|
||||
child_xprv.unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{error::Error, str::FromStr};
|
||||
|
||||
use bip32::{Language, Mnemonic, Prefix, PrivateKey, XPrv};
|
||||
use rand_core::OsRng;
|
||||
|
||||
use crate::{derive_child_xprv, MNEMONIC_PHRASE};
|
||||
|
||||
#[test]
|
||||
fn test_create_mnemonic() {
|
||||
// Generate random Mnemonic using the default language (English)
|
||||
let mnemonic = Mnemonic::random(&mut OsRng, Default::default());
|
||||
|
||||
println!("{}", mnemonic.phrase());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enc_dec_for_derived() -> Result<(), Box<dyn Error>> {
|
||||
let seed = {
|
||||
let mnemonic = Mnemonic::new(MNEMONIC_PHRASE, Language::English)?;
|
||||
mnemonic.to_seed("password")
|
||||
};
|
||||
|
||||
let mut child_num = 0;
|
||||
let alice_sk = derive_child_xprv(&seed, &mut child_num);
|
||||
let alice_sk_str = alice_sk.to_string(Prefix::XPRV).to_string();
|
||||
assert_eq!(
|
||||
alice_sk.private_key().public_key().to_sec1_bytes(),
|
||||
hex::decode("02027e3510f66f1f6c1ea5e3600062255928e518220f7883810cac3fc7fc092057")
|
||||
.unwrap()
|
||||
.into()
|
||||
);
|
||||
assert_eq!(XPrv::from_str(&alice_sk_str).unwrap(), alice_sk);
|
||||
|
||||
let alice_pk = alice_sk.private_key().public_key();
|
||||
assert_eq!(
|
||||
alice_pk.to_sec1_bytes().into_vec(),
|
||||
vec![
|
||||
2, 2, 126, 53, 16, 246, 111, 31, 108, 30, 165, 227, 96, 0, 98, 37, 89, 40, 229, 24,
|
||||
34, 15, 120, 131, 129, 12, 172, 63, 199, 252, 9, 32, 87
|
||||
]
|
||||
);
|
||||
|
||||
let msg = r#"{"debtor":"02027e3510f66f1f6c1ea5e3600062255928e518220f7883810cac3fc7fc092057","creditor":"0216254f4636c4e68ae22d98538851a46810b65162fe37bf57cba6d563617c913e","amount":10,"salt":"65c188bcc133add598f7eecc449112f4bf61024345316cff0eb5ce61291991b141073dcd3c543ea142e66fffa8f483dc382043d37e490ef9b8069c489ce94a0b"}"#;
|
||||
|
||||
let ciphertext = ecies::encrypt(&alice_pk.to_sec1_bytes(), msg.as_bytes()).unwrap();
|
||||
// let ciphertext = hex::decode("0418d9051cbfc86c8ddd57ae43ea3d1ac8b30353a3ecd8c806bb11f0693dfd282d5f07d1de32cbcd933d5ab7cd0aa171c972e75531b915e968f0fdeba78fa3f359c7f3ef7ae2dfffeb19493e9b2418dc774e6e80448a2dc4a7ba657cd4a8456e120977ebe372a57187d53981cc5856fbd63e9c1bdf001ed71c3d50cbaff594561191d33dad852cb782126f480add2cc92758b59eb63de857d299eaa5f09fbc55643a73b1d8206ce83453b5296b566d9f622520679bb3e6d9c8b7a707f33d3093c41dfc0a8267749b4028e9ee0faad0c8df64f1682a348f220585fdd9b9ac411bdaaa6a249b45accc89a80e5af09abb239231aa869e29459e562721b685d98b3da3eeaef14e1c5f3bd20cf27c0cbbae7b5c618e737df9a84f9a040bb472b7254af2cf4ccc76784cf8432080e528f700ca2a082b7020d94f0f5325dd4998c03972a0b39e6670b65be89e7a80aad7af08a393fcf2e103999254380c1f0355d97ddcdfaeed4bcfaf15b578cee1f6d3fd4ceccd85760b9bd714f81698ddf6fbbc06152a9306a5dd0052c722e390470f0c70eeac81a5da0090").unwrap();
|
||||
|
||||
println!("{}", hex::encode(&ciphertext));
|
||||
|
||||
let msg_dec =
|
||||
ecies::decrypt(&alice_sk.private_key().to_bytes(), ciphertext.as_slice()).unwrap();
|
||||
assert_eq!(msg, String::from_utf8(msg_dec).unwrap().as_str());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
70
utils/cycles-sync/src/obligato_client/http.rs
Normal file
70
utils/cycles-sync/src/obligato_client/http.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use async_trait::async_trait;
|
||||
use reqwest::Url;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{
|
||||
obligato_client::Client,
|
||||
types::{ObligatoObligation, ObligatoSetOff},
|
||||
};
|
||||
|
||||
pub struct HttpClient {
|
||||
client: reqwest::Client,
|
||||
url: Url,
|
||||
}
|
||||
|
||||
impl HttpClient {
|
||||
pub fn new(url: Url) -> Self {
|
||||
Self {
|
||||
client: reqwest::Client::new(),
|
||||
url,
|
||||
}
|
||||
}
|
||||
|
||||
fn url_with_path(&self, path: &str) -> Url {
|
||||
let mut url = self.url.clone();
|
||||
url.set_path(path);
|
||||
url
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Client for HttpClient {
|
||||
type Error = reqwest::Error;
|
||||
|
||||
async fn get_obligations(&self) -> Result<Vec<ObligatoObligation>, Self::Error> {
|
||||
let response = self
|
||||
.client
|
||||
.post(self.url_with_path("api/sync/obligations2contract"))
|
||||
.json(&json!({"denom_id": "1", "key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRydXZveWVhYXN5bXZubGxmdnZ5Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTcxMTYyNDgzNiwiZXhwIjoyMDI3MjAwODM2fQ.y-2iTQCplrXBEzHrvz_ZGFmMx-iLMzRZ6I0N5htJ39c"}))
|
||||
.send()
|
||||
.await?
|
||||
.json::<GetObligationsResponse>()
|
||||
.await?;
|
||||
|
||||
Ok(response.all_obligations.obligations)
|
||||
}
|
||||
|
||||
async fn set_setoffs(&self, setoffs: Vec<ObligatoSetOff>) -> Result<(), Self::Error> {
|
||||
let response = self
|
||||
.client
|
||||
.post(self.url_with_path("api/set-offs"))
|
||||
.json(&setoffs)
|
||||
.send()
|
||||
.await?;
|
||||
println!("{}", response.text().await.unwrap());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct GetObligationsInnerResponse {
|
||||
obligations: Vec<ObligatoObligation>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct GetObligationsResponse {
|
||||
#[serde(rename = "allObligations")]
|
||||
all_obligations: GetObligationsInnerResponse,
|
||||
}
|
39
utils/cycles-sync/src/obligato_client/mock.rs
Normal file
39
utils/cycles-sync/src/obligato_client/mock.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use async_trait::async_trait;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
obligato_client::Client,
|
||||
types::{ObligatoObligation, ObligatoSetOff},
|
||||
BANK_DEBTOR_ID,
|
||||
};
|
||||
|
||||
pub struct MockClient;
|
||||
|
||||
#[async_trait]
|
||||
impl Client for MockClient {
|
||||
type Error = ();
|
||||
|
||||
async fn get_obligations(&self) -> Result<Vec<ObligatoObligation>, Self::Error> {
|
||||
Ok(vec![
|
||||
// obligation: 1 --10--> 2
|
||||
ObligatoObligation {
|
||||
id: Uuid::from_u128(1),
|
||||
debtor_id: Uuid::from_u128(1),
|
||||
creditor_id: Uuid::from_u128(2),
|
||||
amount: 10,
|
||||
},
|
||||
// tender: $ --10--> 1
|
||||
ObligatoObligation {
|
||||
id: Uuid::from_u128(2),
|
||||
debtor_id: Uuid::parse_str(BANK_DEBTOR_ID).unwrap(),
|
||||
creditor_id: Uuid::from_u128(1),
|
||||
amount: 10,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
async fn set_setoffs(&self, setoffs: Vec<ObligatoSetOff>) -> Result<(), Self::Error> {
|
||||
println!("{:?}", setoffs);
|
||||
Ok(())
|
||||
}
|
||||
}
|
15
utils/cycles-sync/src/obligato_client/mod.rs
Normal file
15
utils/cycles-sync/src/obligato_client/mod.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::types::{ObligatoObligation, ObligatoSetOff};
|
||||
|
||||
pub mod http;
|
||||
pub mod mock;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Client {
|
||||
type Error;
|
||||
|
||||
async fn get_obligations(&self) -> Result<Vec<ObligatoObligation>, Self::Error>;
|
||||
|
||||
async fn set_setoffs(&self, setoffs: Vec<ObligatoSetOff>) -> Result<(), Self::Error>;
|
||||
}
|
80
utils/cycles-sync/src/types.rs
Normal file
80
utils/cycles-sync/src/types.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use bip32::secp256k1::ecdsa::VerifyingKey;
|
||||
use cosmwasm_std::HexBinary;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq, Hash)]
|
||||
pub struct ObligatoObligation {
|
||||
pub id: Uuid,
|
||||
pub debtor_id: Uuid,
|
||||
pub creditor_id: Uuid,
|
||||
pub amount: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RawObligation {
|
||||
pub debtor: HexBinary,
|
||||
pub creditor: HexBinary,
|
||||
pub amount: u64,
|
||||
#[serde(default)]
|
||||
pub salt: HexBinary,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Obligation {
|
||||
pub debtor: VerifyingKey,
|
||||
pub creditor: VerifyingKey,
|
||||
pub amount: u64,
|
||||
pub salt: [u8; 64],
|
||||
}
|
||||
|
||||
impl From<Obligation> for RawObligation {
|
||||
fn from(obligation: Obligation) -> Self {
|
||||
Self {
|
||||
debtor: obligation.debtor.to_sec1_bytes().into_vec().into(),
|
||||
creditor: obligation.creditor.to_sec1_bytes().into_vec().into(),
|
||||
amount: obligation.amount,
|
||||
salt: obligation.salt.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RawEncryptedObligation {
|
||||
pub digest: HexBinary,
|
||||
pub ciphertext: HexBinary,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SubmitObligationsMsg {
|
||||
pub submit_obligations: Vec<RawEncryptedObligation>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct ObligatoSetOff {
|
||||
pub debtor_id: Uuid,
|
||||
pub creditor_id: Uuid,
|
||||
pub amount: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum RawSetOff {
|
||||
SetOff(Vec<HexBinary>),
|
||||
Transfer(RawSetOffTransfer),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RawSetOffTransfer {
|
||||
pub payer: String,
|
||||
pub payee: String,
|
||||
pub amount: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RawOffset {
|
||||
pub debtor: HexBinary,
|
||||
pub creditor: HexBinary,
|
||||
pub amount: u64,
|
||||
pub set_off: u64,
|
||||
}
|
93
utils/cycles-sync/src/wasmd_client.rs
Normal file
93
utils/cycles-sync/src/wasmd_client.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use std::{error::Error, process::Command};
|
||||
|
||||
use cosmrs::{tendermint::chain::Id, AccountId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub trait WasmdClient {
|
||||
type Address: AsRef<str>;
|
||||
type Query: ToString;
|
||||
type ChainId: AsRef<str>;
|
||||
type Error;
|
||||
|
||||
fn query_smart<R: FromVec>(
|
||||
&self,
|
||||
contract: &Self::Address,
|
||||
query: Self::Query,
|
||||
) -> Result<R, Self::Error>;
|
||||
|
||||
fn tx_execute<M: ToString>(
|
||||
&self,
|
||||
contract: &Self::Address,
|
||||
chain_id: &Id,
|
||||
gas: u64,
|
||||
sender: String,
|
||||
msg: M,
|
||||
) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CliWasmdClient;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct QueryResult<T> {
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
pub trait FromVec: Sized {
|
||||
fn from_vec(value: Vec<u8>) -> Self;
|
||||
}
|
||||
|
||||
impl<T: for<'any> Deserialize<'any>> FromVec for T {
|
||||
fn from_vec(value: Vec<u8>) -> Self {
|
||||
serde_json::from_slice(&value).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmdClient for CliWasmdClient {
|
||||
type Address = AccountId;
|
||||
type Query = serde_json::Value;
|
||||
type ChainId = Id;
|
||||
type Error = Box<dyn Error>;
|
||||
|
||||
fn query_smart<R: FromVec>(
|
||||
&self,
|
||||
contract: &Self::Address,
|
||||
query: Self::Query,
|
||||
) -> Result<R, Self::Error> {
|
||||
let mut wasmd = Command::new("wasmd");
|
||||
let command = wasmd
|
||||
.args(["query", "wasm"])
|
||||
.args(["contract-state", "smart", contract.as_ref()])
|
||||
.arg(query.to_string())
|
||||
.args(["--output", "json"]);
|
||||
|
||||
let output = command.output()?;
|
||||
println!("{:?} => {:?}", command, output);
|
||||
|
||||
let query_result = R::from_vec(output.stdout);
|
||||
Ok(query_result)
|
||||
}
|
||||
|
||||
fn tx_execute<M: ToString>(
|
||||
&self,
|
||||
contract: &Self::Address,
|
||||
chain_id: &Id,
|
||||
gas: u64,
|
||||
sender: String,
|
||||
msg: M,
|
||||
) -> Result<(), Self::Error> {
|
||||
let mut wasmd = Command::new("wasmd");
|
||||
let command = wasmd
|
||||
.args(["tx", "wasm"])
|
||||
.args(["execute", contract.as_ref(), &msg.to_string()])
|
||||
.args(["--chain-id", chain_id.as_ref()])
|
||||
.args(["--gas", &gas.to_string()])
|
||||
.args(["--from", sender.as_ref()])
|
||||
.arg("-y");
|
||||
|
||||
let output = command.output()?;
|
||||
println!("{:?} => {:?}", command, output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -7,8 +7,9 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "4.0.32", features = ["derive"] }
|
||||
cosmwasm-std = "1.4.0"
|
||||
ecies = "0.2.6"
|
||||
cosmrs = "0.16.0"
|
||||
cosmwasm-std = "1.5.2"
|
||||
ecies = { version = "0.2.6", default-features = false, features = ["pure"] }
|
||||
hex = "0.4.3"
|
||||
k256 = { version = "0.13.2", default-features = false, features = ["ecdsa", "alloc", "serde"] }
|
||||
rand = "0.8.5"
|
||||
|
|
|
@ -20,6 +20,7 @@ use std::{
|
|||
};
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use cosmrs::{tendermint::account::Id as TmAccountId, AccountId};
|
||||
use cosmwasm_std::HexBinary;
|
||||
use ecies::{decrypt, encrypt};
|
||||
use k256::{
|
||||
|
@ -41,9 +42,9 @@ struct Cli {
|
|||
#[allow(clippy::large_enum_variant)]
|
||||
enum Command {
|
||||
KeyGen {
|
||||
#[clap(long, default_value = "user.pk")]
|
||||
#[clap(long, default_value = "epoch.pk")]
|
||||
pk_file: PathBuf,
|
||||
#[clap(long, default_value = "user.sk")]
|
||||
#[clap(long, default_value = "epoch.sk")]
|
||||
sk_file: PathBuf,
|
||||
},
|
||||
EncryptObligation {
|
||||
|
@ -52,12 +53,30 @@ enum Command {
|
|||
#[clap(long, default_value = "epoch.pk")]
|
||||
pk_file: PathBuf,
|
||||
},
|
||||
DecryptSetoff {
|
||||
#[clap(long, value_parser = parse_hex)]
|
||||
setoff: Vec<u8>,
|
||||
DecryptObligation {
|
||||
#[clap(long)]
|
||||
obligation: String,
|
||||
#[clap(long, default_value = "epoch.sk")]
|
||||
sk_file: PathBuf,
|
||||
},
|
||||
EncryptSetoff {
|
||||
#[clap(long, value_parser = parse_setoff_json)]
|
||||
setoff: Setoff,
|
||||
#[clap(long)]
|
||||
obligation_digest: String,
|
||||
#[clap(long, default_value = "user.pk")]
|
||||
pk_file: PathBuf,
|
||||
},
|
||||
DecryptSetoff {
|
||||
#[clap(long)]
|
||||
setoff: String,
|
||||
#[clap(long, default_value = "user.sk")]
|
||||
sk_file: PathBuf,
|
||||
},
|
||||
PrintAddress {
|
||||
#[clap(long)]
|
||||
pk: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn parse_obligation_json(s: &str) -> Result<Obligation, String> {
|
||||
|
@ -65,10 +84,6 @@ fn parse_obligation_json(s: &str) -> Result<Obligation, String> {
|
|||
raw_obligation.try_into()
|
||||
}
|
||||
|
||||
fn parse_hex(s: &str) -> Result<Vec<u8>, String> {
|
||||
Ok(hex::decode(s).unwrap())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct RawObligation {
|
||||
debtor: HexBinary,
|
||||
|
@ -116,11 +131,62 @@ impl From<Obligation> for RawObligation {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct EncryptedObligation {
|
||||
struct EncryptedIntent {
|
||||
ciphertext: HexBinary,
|
||||
digest: HexBinary,
|
||||
}
|
||||
|
||||
fn parse_setoff_json(s: &str) -> Result<Setoff, String> {
|
||||
let raw_setoff: RawSetoff = serde_json::from_str(s).map_err(|e| e.to_string())?;
|
||||
raw_setoff.try_into()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct RawSetoff {
|
||||
debtor: HexBinary,
|
||||
creditor: HexBinary,
|
||||
amount: u64,
|
||||
#[serde(default)]
|
||||
salt: HexBinary,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Setoff {
|
||||
debtor: VerifyingKey,
|
||||
creditor: VerifyingKey,
|
||||
amount: u64,
|
||||
salt: [u8; 64],
|
||||
}
|
||||
|
||||
impl TryFrom<RawSetoff> for Setoff {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(raw_setoff: RawSetoff) -> Result<Self, Self::Error> {
|
||||
let mut salt = [0u8; 64];
|
||||
rand::thread_rng().fill(&mut salt[..]);
|
||||
|
||||
Ok(Self {
|
||||
debtor: VerifyingKey::from_sec1_bytes(raw_setoff.debtor.as_slice())
|
||||
.map_err(|e| e.to_string())?,
|
||||
creditor: VerifyingKey::from_sec1_bytes(raw_setoff.creditor.as_slice())
|
||||
.map_err(|e| e.to_string())?,
|
||||
amount: raw_setoff.amount,
|
||||
salt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Setoff> for RawSetoff {
|
||||
fn from(setoff: Setoff) -> Self {
|
||||
Self {
|
||||
debtor: setoff.debtor.to_sec1_bytes().into_vec().into(),
|
||||
creditor: setoff.creditor.to_sec1_bytes().into_vec().into(),
|
||||
amount: setoff.amount,
|
||||
salt: setoff.salt.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let args = Cli::parse();
|
||||
|
||||
|
@ -157,7 +223,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
hasher.finalize().into()
|
||||
};
|
||||
|
||||
let obligation_enc = EncryptedObligation {
|
||||
let obligation_enc = EncryptedIntent {
|
||||
ciphertext: ciphertext.into(),
|
||||
digest: digest.into(),
|
||||
};
|
||||
|
@ -167,6 +233,53 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
serde_json::to_string(&obligation_enc).expect("infallible serializer")
|
||||
);
|
||||
}
|
||||
Command::DecryptObligation {
|
||||
obligation,
|
||||
sk_file,
|
||||
} => {
|
||||
let sk = {
|
||||
let sk_str = read_to_string(sk_file)?;
|
||||
let sk = hex::decode(sk_str).expect("");
|
||||
SigningKey::from_bytes(GenericArray::from_slice(&sk))?
|
||||
};
|
||||
|
||||
let ciphertext = hex::decode(obligation).unwrap();
|
||||
|
||||
let obligation = {
|
||||
let o = decrypt(&sk.to_bytes(), &ciphertext).unwrap();
|
||||
serde_json::from_slice::<RawObligation>(&o)?
|
||||
};
|
||||
println!("{obligation:?}");
|
||||
}
|
||||
Command::EncryptSetoff {
|
||||
setoff,
|
||||
obligation_digest,
|
||||
pk_file,
|
||||
} => {
|
||||
let pk = {
|
||||
let pk_str = read_to_string(pk_file)?;
|
||||
hex::decode(pk_str)?
|
||||
};
|
||||
let setoff_ser =
|
||||
serde_json::to_string(&RawSetoff::from(setoff)).expect("infallible serializer");
|
||||
|
||||
let ciphertext = encrypt(&pk, setoff_ser.as_bytes()).map_err(|e| e.to_string())?;
|
||||
|
||||
let digest: [u8; 32] = {
|
||||
let d = hex::decode(obligation_digest)?;
|
||||
d.try_into().unwrap()
|
||||
};
|
||||
|
||||
let setoff_enc = EncryptedIntent {
|
||||
ciphertext: ciphertext.into(),
|
||||
digest: digest.into(),
|
||||
};
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string(&setoff_enc).expect("infallible serializer")
|
||||
);
|
||||
}
|
||||
Command::DecryptSetoff { setoff, sk_file } => {
|
||||
let sk = {
|
||||
let sk_str = read_to_string(sk_file)?;
|
||||
|
@ -174,8 +287,18 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
SigningKey::from_bytes(GenericArray::from_slice(&sk))?
|
||||
};
|
||||
|
||||
let key_share = decrypt(&sk.to_bytes(), &setoff).unwrap();
|
||||
serde_json::from_slice(&key_share)?;
|
||||
let ciphertext = hex::decode(setoff).unwrap();
|
||||
|
||||
let setoff = decrypt(&sk.to_bytes(), &ciphertext).unwrap();
|
||||
serde_json::from_slice(&setoff)?;
|
||||
}
|
||||
Command::PrintAddress { pk } => {
|
||||
let pk = {
|
||||
let pk = hex::decode(pk)?;
|
||||
VerifyingKey::from_sec1_bytes(&pk)?
|
||||
};
|
||||
let tm_pk = TmAccountId::from(pk);
|
||||
println!("{}", AccountId::new("wasm", tm_pk.as_bytes()).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// This file is @generated by prost-build.
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct InstantiateRequest {}
|
||||
|
|
|
@ -7,7 +7,7 @@ edition = "2021"
|
|||
clap = { version = "4.1.8", features = ["derive"] }
|
||||
cosmos-sdk-proto = "0.16.0"
|
||||
cosmrs = { version = "=0.11.0", features = ["cosmwasm"] }
|
||||
cosmwasm-std = "1.4.0"
|
||||
cosmwasm-std = "1.5.2"
|
||||
displaydoc = { version = "0.2.3", default-features = false }
|
||||
ecies = { version = "0.2.6", default-features = false, features = ["pure"] }
|
||||
k256 = { version = "0.13.2", default-features = false, features = ["ecdsa", "alloc"] }
|
||||
|
@ -20,6 +20,6 @@ thiserror = "1.0.38"
|
|||
tonic = "=0.8.3"
|
||||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
||||
|
||||
quartz-cw = { git = "ssh://git@github.com/informalsystems/bisenzone-cw-mvp.git", branch = "hu55a1n1/11-use-quartz" }
|
||||
quartz-tee-ra = { git = "ssh://git@github.com/informalsystems/bisenzone-cw-mvp.git", branch = "hu55a1n1/11-use-quartz" }
|
||||
quartz-cw = { git = "ssh://git@github.com/informalsystems/bisenzone-cw-mvp.git", branch = "hu55a1n1/ljubljana-retreat" }
|
||||
quartz-tee-ra = { git = "ssh://git@github.com/informalsystems/bisenzone-cw-mvp.git", branch = "hu55a1n1/ljubljana-retreat" }
|
||||
quartz-proto = { path = "../../utils/quartz-proto" }
|
||||
|
|
Loading…
Reference in a new issue