235 lines
7.3 KiB
Rust
235 lines
7.3 KiB
Rust
|
use std::{cell::RefCell, collections::BTreeSet, fmt::Debug, rc::Rc};
|
||
|
|
||
|
use anyhow::{anyhow, bail, ensure};
|
||
|
use cosmwasm_std::{to_binary, Addr, Api, Binary, BlockInfo, CustomQuery, Empty, Querier, Storage};
|
||
|
use cw_multi_test::{AppResponse, CosmosRouter, Module};
|
||
|
use k256::ecdsa::{
|
||
|
self,
|
||
|
signature::{Signature as SigT, Signer, Verifier},
|
||
|
SigningKey,
|
||
|
};
|
||
|
use schemars::JsonSchema;
|
||
|
use serde::de::DeserializeOwned;
|
||
|
|
||
|
use crate::{Signature, WormholeQuery};
|
||
|
|
||
|
#[derive(Debug)]
|
||
|
struct Inner {
|
||
|
index: u32,
|
||
|
expiration: u64,
|
||
|
guardians: [SigningKey; 7],
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug)]
|
||
|
pub struct WormholeKeeper(Rc<RefCell<Inner>>);
|
||
|
|
||
|
impl WormholeKeeper {
|
||
|
pub fn new() -> WormholeKeeper {
|
||
|
let guardians = [
|
||
|
SigningKey::from_bytes(&[
|
||
|
93, 217, 189, 224, 168, 81, 157, 93, 238, 38, 143, 8, 182, 94, 69, 77, 232, 199,
|
||
|
238, 206, 15, 135, 221, 58, 43, 74, 0, 129, 54, 198, 62, 226,
|
||
|
])
|
||
|
.unwrap(),
|
||
|
SigningKey::from_bytes(&[
|
||
|
150, 48, 135, 223, 194, 186, 243, 139, 177, 8, 126, 32, 210, 57, 42, 28, 29, 102,
|
||
|
196, 201, 106, 136, 40, 149, 218, 150, 240, 213, 192, 128, 161, 245,
|
||
|
])
|
||
|
.unwrap(),
|
||
|
SigningKey::from_bytes(&[
|
||
|
121, 51, 199, 93, 237, 227, 62, 220, 128, 129, 195, 4, 190, 163, 254, 12, 212, 224,
|
||
|
188, 76, 141, 242, 229, 121, 192, 5, 161, 176, 136, 99, 83, 53,
|
||
|
])
|
||
|
.unwrap(),
|
||
|
SigningKey::from_bytes(&[
|
||
|
224, 180, 4, 114, 215, 161, 184, 12, 218, 96, 20, 141, 154, 242, 46, 230, 167, 165,
|
||
|
54, 141, 108, 64, 146, 27, 193, 89, 251, 139, 234, 132, 124, 30,
|
||
|
])
|
||
|
.unwrap(),
|
||
|
SigningKey::from_bytes(&[
|
||
|
69, 1, 17, 179, 19, 47, 56, 47, 255, 219, 143, 89, 115, 54, 242, 209, 163, 131,
|
||
|
225, 30, 59, 195, 217, 141, 167, 253, 6, 95, 252, 52, 7, 223,
|
||
|
])
|
||
|
.unwrap(),
|
||
|
SigningKey::from_bytes(&[
|
||
|
181, 3, 165, 125, 15, 200, 155, 56, 157, 204, 105, 221, 203, 149, 215, 175, 220,
|
||
|
228, 200, 37, 169, 39, 68, 127, 132, 196, 203, 232, 155, 55, 67, 253,
|
||
|
])
|
||
|
.unwrap(),
|
||
|
SigningKey::from_bytes(&[
|
||
|
72, 81, 175, 107, 23, 108, 178, 66, 32, 53, 14, 117, 233, 33, 114, 102, 68, 89, 83,
|
||
|
201, 129, 57, 56, 130, 214, 212, 172, 16, 23, 22, 234, 160,
|
||
|
])
|
||
|
.unwrap(),
|
||
|
];
|
||
|
WormholeKeeper(Rc::new(RefCell::new(Inner {
|
||
|
index: 0,
|
||
|
expiration: 0,
|
||
|
guardians,
|
||
|
})))
|
||
|
}
|
||
|
|
||
|
pub fn sign(&self, msg: &[u8]) -> Vec<Signature> {
|
||
|
self.0
|
||
|
.borrow()
|
||
|
.guardians
|
||
|
.iter()
|
||
|
.map(|g| {
|
||
|
<SigningKey as Signer<ecdsa::Signature>>::sign(g, msg)
|
||
|
.as_bytes()
|
||
|
.to_vec()
|
||
|
.into()
|
||
|
})
|
||
|
.enumerate()
|
||
|
.map(|(idx, sig)| Signature {
|
||
|
index: idx as u8,
|
||
|
signature: sig,
|
||
|
})
|
||
|
.collect()
|
||
|
}
|
||
|
|
||
|
pub fn verify_quorum(
|
||
|
&self,
|
||
|
data: &[u8],
|
||
|
index: u32,
|
||
|
signatures: &[Signature],
|
||
|
block_time: u64,
|
||
|
) -> anyhow::Result<Empty> {
|
||
|
let mut signers = BTreeSet::new();
|
||
|
for s in signatures {
|
||
|
self.verify_signature(data, index, s, block_time)?;
|
||
|
signers.insert(s.index);
|
||
|
}
|
||
|
|
||
|
if signers.len() as u32 >= self.calculate_quorum(index, block_time)? {
|
||
|
Ok(Empty {})
|
||
|
} else {
|
||
|
Err(anyhow!("no quorum"))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn verify_signature(
|
||
|
&self,
|
||
|
data: &[u8],
|
||
|
index: u32,
|
||
|
sig: &Signature,
|
||
|
block_time: u64,
|
||
|
) -> anyhow::Result<Empty> {
|
||
|
let this = self.0.borrow();
|
||
|
ensure!(this.index == index, "invalid guardian set");
|
||
|
ensure!(
|
||
|
this.expiration == 0 || block_time < this.expiration,
|
||
|
"guardian set expired"
|
||
|
);
|
||
|
|
||
|
if let Some(g) = this.guardians.get(sig.index as usize) {
|
||
|
let s = ecdsa::Signature::try_from(&*sig.signature).unwrap();
|
||
|
g.verifying_key()
|
||
|
.verify(data, &s)
|
||
|
.map(|()| Empty {})
|
||
|
.map_err(From::from)
|
||
|
} else {
|
||
|
Err(anyhow!("invalid guardian index"))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn calculate_quorum(&self, index: u32, block_time: u64) -> anyhow::Result<u32> {
|
||
|
let this = self.0.borrow();
|
||
|
ensure!(this.index == index, "invalid guardian set");
|
||
|
ensure!(
|
||
|
this.expiration == 0 || block_time < this.expiration,
|
||
|
"guardian set expired"
|
||
|
);
|
||
|
|
||
|
Ok(((this.guardians.len() as u32 * 10 / 3) * 2) / 10 + 1)
|
||
|
}
|
||
|
|
||
|
pub fn query(&self, request: WormholeQuery, block: &BlockInfo) -> anyhow::Result<Binary> {
|
||
|
match request {
|
||
|
WormholeQuery::VerifyQuorum {
|
||
|
data,
|
||
|
guardian_set_index,
|
||
|
signatures,
|
||
|
} => self
|
||
|
.verify_quorum(&data, guardian_set_index, &signatures, block.height)
|
||
|
.and_then(|e| to_binary(&e).map_err(From::from)),
|
||
|
WormholeQuery::VerifySignature {
|
||
|
data,
|
||
|
guardian_set_index,
|
||
|
signature,
|
||
|
} => self
|
||
|
.verify_signature(&data, guardian_set_index, &signature, block.height)
|
||
|
.and_then(|e| to_binary(&e).map_err(From::from)),
|
||
|
WormholeQuery::CalculateQuorum { guardian_set_index } => self
|
||
|
.calculate_quorum(guardian_set_index, block.height)
|
||
|
.and_then(|q| to_binary(&q).map_err(From::from)),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn expiration(&self) -> u64 {
|
||
|
self.0.borrow().expiration
|
||
|
}
|
||
|
|
||
|
pub fn set_expiration(&self, expiration: u64) {
|
||
|
self.0.borrow_mut().expiration = expiration;
|
||
|
}
|
||
|
|
||
|
pub fn guardian_set_index(&self) -> u32 {
|
||
|
self.0.borrow().index
|
||
|
}
|
||
|
|
||
|
pub fn set_index(&self, index: u32) {
|
||
|
self.0.borrow_mut().index = index;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Default for WormholeKeeper {
|
||
|
fn default() -> Self {
|
||
|
Self::new()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Module for WormholeKeeper {
|
||
|
type ExecT = Empty;
|
||
|
type QueryT = WormholeQuery;
|
||
|
type SudoT = Empty;
|
||
|
|
||
|
fn execute<ExecC, QueryC>(
|
||
|
&self,
|
||
|
_api: &dyn Api,
|
||
|
_storage: &mut dyn Storage,
|
||
|
_router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
|
||
|
_block: &BlockInfo,
|
||
|
sender: Addr,
|
||
|
msg: Self::ExecT,
|
||
|
) -> anyhow::Result<AppResponse>
|
||
|
where
|
||
|
ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static,
|
||
|
QueryC: CustomQuery + DeserializeOwned + 'static,
|
||
|
{
|
||
|
bail!("Unexpected exec msg {msg:?} from {sender}")
|
||
|
}
|
||
|
|
||
|
fn sudo<ExecC, QueryC>(
|
||
|
&self,
|
||
|
_api: &dyn Api,
|
||
|
_storage: &mut dyn Storage,
|
||
|
_router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
|
||
|
_block: &BlockInfo,
|
||
|
msg: Self::SudoT,
|
||
|
) -> anyhow::Result<AppResponse> {
|
||
|
bail!("Unexpected sudo msg {msg:?}")
|
||
|
}
|
||
|
|
||
|
fn query(
|
||
|
&self,
|
||
|
_api: &dyn Api,
|
||
|
_storage: &dyn Storage,
|
||
|
_querier: &dyn Querier,
|
||
|
block: &BlockInfo,
|
||
|
request: Self::QueryT,
|
||
|
) -> anyhow::Result<Binary> {
|
||
|
self.query(request, block)
|
||
|
}
|
||
|
}
|