Skip to content

Commit 3bd77b9

Browse files
authored
Merge pull request #95 from datachainlab/message-aggregation
Message aggregation support Signed-off-by: Jun Kimura <jun.kimura@datachain.jp>
2 parents b30213a + a9cc4ca commit 3bd77b9

File tree

20 files changed

+920
-108
lines changed

20 files changed

+920
-108
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use crate::light_client::Error;
2+
use crate::prelude::*;
3+
use context::Context;
4+
use crypto::{EnclavePublicKey, Signer, Verifier};
5+
use ecall_commands::{AggregateMessagesInput, AggregateMessagesResult, LightClientResult};
6+
use light_client::{
7+
commitments::{self, prove_commitment, Message, UpdateClientMessage},
8+
HostContext, LightClientResolver,
9+
};
10+
use store::KVStore;
11+
12+
pub fn aggregate_messages<R: LightClientResolver, S: KVStore, K: Signer>(
13+
ctx: &mut Context<R, S, K>,
14+
input: AggregateMessagesInput,
15+
) -> Result<LightClientResult, Error> {
16+
ctx.set_timestamp(input.current_timestamp);
17+
18+
if input.messages.len() < 2 {
19+
return Err(Error::invalid_argument(
20+
"messages and signatures must have at least 2 elements".into(),
21+
));
22+
}
23+
if input.messages.len() != input.signatures.len() {
24+
return Err(Error::invalid_argument(
25+
"messages and signatures must have the same length".into(),
26+
));
27+
}
28+
29+
let ek = ctx.get_enclave_key();
30+
let pk = ek.pubkey().map_err(Error::crypto)?;
31+
32+
let messages = input
33+
.messages
34+
.into_iter()
35+
.map(|c| Message::from_bytes(&c)?.try_into())
36+
.collect::<Result<Vec<_>, _>>()?
37+
.into_iter()
38+
.zip(input.signatures.iter())
39+
.map(|(c, s)| -> Result<_, Error> {
40+
verify_commitment(&pk, &c, s)?;
41+
c.context.validate(ctx.host_timestamp())?;
42+
Ok(c)
43+
})
44+
.collect::<Result<Vec<_>, _>>()?;
45+
46+
let message = Message::from(commitments::aggregate_messages(messages)?);
47+
let proof = prove_commitment(ek, input.signer, message)?;
48+
49+
Ok(LightClientResult::AggregateMessages(
50+
AggregateMessagesResult(proof),
51+
))
52+
}
53+
54+
fn verify_commitment(
55+
verifier: &EnclavePublicKey,
56+
commitment: &UpdateClientMessage,
57+
signature: &[u8],
58+
) -> Result<(), Error> {
59+
let message_bytes = Message::UpdateClient(commitment.clone()).to_bytes();
60+
verifier
61+
.verify(&message_bytes, signature)
62+
.map_err(Error::crypto)?;
63+
Ok(())
64+
}

enclave-modules/ecall-handler/src/light_client/errors.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ use flex_error::*;
44
define_error! {
55
#[derive(Debug, PartialEq, Eq)]
66
Error {
7+
InvalidArgument
8+
{
9+
descr: String
10+
}
11+
|e| {
12+
format_args!("invalid argument: descr={}", e.descr)
13+
},
14+
715
SealedEnclaveKeyNotFound
816
|_| { "Sealed EnclaveKey not found" },
917

@@ -19,6 +27,10 @@ define_error! {
1927
[light_client::commitments::Error]
2028
|_| { "Commitment error" },
2129

30+
Crypto
31+
[crypto::Error]
32+
|_| { "Crypto error" },
33+
2234
LcpType
2335
{}
2436
[lcp_types::TypeError]

enclave-modules/ecall-handler/src/light_client/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
pub use aggregate_messages::aggregate_messages;
12
pub use errors::Error;
23
pub use init_client::init_client;
34
pub use query::query_client;
45
pub use router::dispatch;
56
pub use update_client::update_client;
67
pub use verify_state::{verify_membership, verify_non_membership};
78

9+
mod aggregate_messages;
810
mod errors;
911
mod init_client;
1012
mod query;

enclave-modules/ecall-handler/src/light_client/router.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::light_client::{
2-
init_client, query_client, update_client, verify_membership, verify_non_membership, Error,
2+
aggregate_messages, init_client, query_client, update_client, verify_membership,
3+
verify_non_membership, Error,
34
};
45
use context::Context;
56
use crypto::NopSigner;
@@ -25,6 +26,7 @@ pub fn dispatch<E: Env>(
2526
match cmd {
2627
InitClient(input) => init_client(&mut ctx, input)?,
2728
UpdateClient(input) => update_client(&mut ctx, input)?,
29+
AggregateMessages(input) => aggregate_messages(&mut ctx, input)?,
2830
VerifyMembership(input) => verify_membership(&mut ctx, input)?,
2931
VerifyNonMembership(input) => verify_non_membership(&mut ctx, input)?,
3032
}

enclave-modules/ecall-handler/src/light_client/update_client.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,8 @@ pub fn update_client<R: LightClientResolver, S: KVStore, K: Signer>(
2222

2323
let message: Message = {
2424
let mut msg = UpdateClientMessage::try_from(res.message)?;
25-
if input.include_state && !msg.emitted_states.is_empty() {
26-
msg.emitted_states = vec![EmittedState(
27-
res.height,
28-
res.new_any_consensus_state.clone(),
29-
)];
25+
if input.include_state && msg.emitted_states.is_empty() {
26+
msg.emitted_states = vec![EmittedState(res.height, res.new_any_client_state.clone())];
3027
}
3128
msg.into()
3229
};

modules/commitments/src/context.rs

Lines changed: 189 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pub const VALIDATION_CONTEXT_TYPE_EMPTY_EMPTY: u16 = 0;
88
pub const VALIDATION_CONTEXT_TYPE_EMPTY_WITHIN_TRUSTING_PERIOD: u16 = 1;
99
pub const VALIDATION_CONTEXT_HEADER_SIZE: usize = 32;
1010

11-
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1212
pub enum ValidationContext {
1313
Empty,
1414
TrustingPeriod(TrustingPeriodContext),
@@ -41,6 +41,42 @@ impl ValidationContext {
4141
header
4242
}
4343

44+
pub fn aggregate(self, other: Self) -> Result<Self, Error> {
45+
match (self, other) {
46+
(Self::Empty, Self::Empty) => Ok(Self::Empty),
47+
(Self::Empty, Self::TrustingPeriod(ctx)) => Ok(Self::TrustingPeriod(ctx)),
48+
(Self::TrustingPeriod(ctx), Self::Empty) => Ok(Self::TrustingPeriod(ctx)),
49+
(Self::TrustingPeriod(ctx1), Self::TrustingPeriod(ctx2)) => {
50+
if ctx1.trusting_period != ctx2.trusting_period {
51+
return Err(Error::context_aggregation_failed(format!(
52+
"trusting_period mismatch: ctx1={:?} ctx2={:?}",
53+
ctx1.trusting_period, ctx2.trusting_period,
54+
)));
55+
}
56+
if ctx1.clock_drift != ctx2.clock_drift {
57+
return Err(Error::context_aggregation_failed(format!(
58+
"clock_drift mismatch: ctx1={:?} ctx2={:?}",
59+
ctx1.clock_drift, ctx2.clock_drift
60+
)));
61+
}
62+
Ok(Self::TrustingPeriod(TrustingPeriodContext::new(
63+
ctx1.trusting_period,
64+
ctx1.clock_drift,
65+
if ctx1.untrusted_header_timestamp > ctx2.untrusted_header_timestamp {
66+
ctx1.untrusted_header_timestamp
67+
} else {
68+
ctx2.untrusted_header_timestamp
69+
},
70+
if ctx1.trusted_state_timestamp < ctx2.trusted_state_timestamp {
71+
ctx1.trusted_state_timestamp
72+
} else {
73+
ctx2.trusted_state_timestamp
74+
},
75+
)))
76+
}
77+
}
78+
}
79+
4480
fn parse_context_type_from_header(header_bytes: &[u8]) -> Result<u16, Error> {
4581
if header_bytes.len() != VALIDATION_CONTEXT_HEADER_SIZE {
4682
return Err(Error::invalid_validation_context_header(format!(
@@ -150,7 +186,7 @@ impl Display for ValidationContext {
150186
}
151187

152188
/// NOTE: time precision is in seconds (i.e. nanoseconds are truncated)
153-
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
189+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154190
pub struct TrustingPeriodContext {
155191
/// How long a validator set is trusted for (must be shorter than the chain's
156192
/// unbonding period)
@@ -236,7 +272,7 @@ impl Display for TrustingPeriodContext {
236272
write!(
237273
f,
238274
"trusting_period={} clock_drift={} untrusted_header_timestamp={} trusted_state_timestamp={}",
239-
self.trusting_period.as_secs(), self.clock_drift.as_secs(), self.untrusted_header_timestamp, self.trusted_state_timestamp
275+
self.trusting_period.as_nanos(), self.clock_drift.as_nanos(), self.untrusted_header_timestamp.as_unix_timestamp_nanos(), self.trusted_state_timestamp.as_unix_timestamp_nanos()
240276
)
241277
}
242278
}
@@ -537,4 +573,154 @@ mod tests {
537573
validate_and_assert_no_error(ctx, current_timestamp);
538574
}
539575
}
576+
577+
#[test]
578+
fn test_validation_context_aggregation() {
579+
{
580+
let ctx0 = ValidationContext::from(build_trusting_period_context(
581+
1,
582+
1,
583+
datetime!(2023-08-19 0:00 UTC),
584+
datetime!(2023-08-19 0:00 UTC),
585+
));
586+
let ctx1 = ValidationContext::from(build_trusting_period_context(
587+
1,
588+
1,
589+
datetime!(2023-08-20 0:00 UTC),
590+
datetime!(2023-08-20 0:00 UTC),
591+
));
592+
let expected = ValidationContext::from(build_trusting_period_context(
593+
1,
594+
1,
595+
datetime!(2023-08-20 0:00 UTC),
596+
datetime!(2023-08-19 0:00 UTC),
597+
));
598+
let res = ctx0.aggregate(ctx1);
599+
if let Ok(ctx) = res {
600+
assert_eq!(ctx, expected);
601+
} else {
602+
panic!("{:?}", res);
603+
}
604+
}
605+
606+
{
607+
let ctx0 = ValidationContext::from(build_trusting_period_context(
608+
1,
609+
1,
610+
datetime!(2023-08-20 0:00 UTC),
611+
datetime!(2023-08-20 0:00 UTC),
612+
));
613+
let ctx1 = ValidationContext::from(build_trusting_period_context(
614+
1,
615+
1,
616+
datetime!(2023-08-19 0:00 UTC),
617+
datetime!(2023-08-19 0:00 UTC),
618+
));
619+
let expected = ValidationContext::from(build_trusting_period_context(
620+
1,
621+
1,
622+
datetime!(2023-08-20 0:00 UTC),
623+
datetime!(2023-08-19 0:00 UTC),
624+
));
625+
let res = ctx0.aggregate(ctx1);
626+
if let Ok(ctx) = res {
627+
assert_eq!(ctx, expected);
628+
} else {
629+
panic!("{:?}", res);
630+
}
631+
}
632+
633+
{
634+
let ctx0 = ValidationContext::from(build_trusting_period_context(
635+
2,
636+
1,
637+
datetime!(2023-08-20 0:00 UTC),
638+
datetime!(2023-08-20 0:00 UTC),
639+
));
640+
let ctx1 = ValidationContext::from(build_trusting_period_context(
641+
1,
642+
1,
643+
datetime!(2023-08-20 0:00 UTC),
644+
datetime!(2023-08-20 0:00 UTC),
645+
));
646+
let res = ctx0.aggregate(ctx1);
647+
assert!(res.is_err());
648+
}
649+
650+
{
651+
let ctx0 = ValidationContext::from(build_trusting_period_context(
652+
1,
653+
2,
654+
datetime!(2023-08-20 0:00 UTC),
655+
datetime!(2023-08-20 0:00 UTC),
656+
));
657+
let ctx1 = ValidationContext::from(build_trusting_period_context(
658+
1,
659+
1,
660+
datetime!(2023-08-20 0:00 UTC),
661+
datetime!(2023-08-20 0:00 UTC),
662+
));
663+
let res = ctx0.aggregate(ctx1);
664+
assert!(res.is_err());
665+
}
666+
}
667+
668+
#[test]
669+
fn test_validation_context_and_empty_aggregation() {
670+
{
671+
let ctx0 = ValidationContext::from(build_trusting_period_context(
672+
1,
673+
1,
674+
datetime!(2023-08-20 0:00 UTC),
675+
datetime!(2023-08-20 0:00 UTC),
676+
));
677+
let ctx1 = ValidationContext::Empty;
678+
let expected = ValidationContext::from(build_trusting_period_context(
679+
1,
680+
1,
681+
datetime!(2023-08-20 0:00 UTC),
682+
datetime!(2023-08-20 0:00 UTC),
683+
));
684+
let res = ctx0.aggregate(ctx1);
685+
if let Ok(ctx) = res {
686+
assert_eq!(ctx, expected);
687+
} else {
688+
panic!("{:?}", res);
689+
}
690+
}
691+
692+
{
693+
let ctx0 = ValidationContext::Empty;
694+
let ctx1 = ValidationContext::from(build_trusting_period_context(
695+
1,
696+
1,
697+
datetime!(2023-08-20 0:00 UTC),
698+
datetime!(2023-08-20 0:00 UTC),
699+
));
700+
let expected = ValidationContext::from(build_trusting_period_context(
701+
1,
702+
1,
703+
datetime!(2023-08-20 0:00 UTC),
704+
datetime!(2023-08-20 0:00 UTC),
705+
));
706+
let res = ctx0.aggregate(ctx1);
707+
if let Ok(ctx) = res {
708+
assert_eq!(ctx, expected);
709+
} else {
710+
panic!("{:?}", res);
711+
}
712+
}
713+
}
714+
715+
#[test]
716+
fn test_empty_context_aggregation() {
717+
let ctx0 = ValidationContext::Empty;
718+
let ctx1 = ValidationContext::Empty;
719+
let res = ctx0.aggregate(ctx1);
720+
if let Ok(ctx) = res {
721+
assert_eq!(ctx, ValidationContext::Empty);
722+
} else {
723+
panic!("{:?}", res);
724+
}
725+
}
540726
}

modules/commitments/src/errors.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,22 @@ define_error! {
9595
format_args!("not truncated timestamp: timestamp_nanos={}", e.timestamp_nanos)
9696
},
9797

98+
MessageAggregationFailed
99+
{
100+
descr: String
101+
}
102+
|e| {
103+
format_args!("message aggregation failed: descr={}", e.descr)
104+
},
105+
106+
ContextAggregationFailed
107+
{
108+
descr: String
109+
}
110+
|e| {
111+
format_args!("context aggregation failed: descr={}", e.descr)
112+
},
113+
98114
LcpType
99115
{}
100116
[lcp_types::TypeError]

modules/commitments/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ pub use context::{TrustingPeriodContext, ValidationContext};
2323
pub use encoder::EthABIEncoder;
2424
pub use errors::Error;
2525
pub use message::{
26-
CommitmentPrefix, EmittedState, Message, UpdateClientMessage, VerifyMembershipMessage,
26+
aggregate_messages, CommitmentPrefix, EmittedState, Message, UpdateClientMessage,
27+
VerifyMembershipMessage,
2728
};
2829
pub use proof::CommitmentProof;
2930
pub use prover::prove_commitment;

modules/commitments/src/message.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pub use self::update_client::{EmittedState, UpdateClientMessage};
1+
pub use self::update_client::{aggregate_messages, EmittedState, UpdateClientMessage};
22
pub use self::verify_membership::{CommitmentPrefix, VerifyMembershipMessage};
33
use crate::encoder::EthABIEncoder;
44
use crate::prelude::*;

0 commit comments

Comments
 (0)