Skip to content

Commit

Permalink
feat: Add encoding functions for external signing (#600)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamspofford-dfinity authored Sep 27, 2024
1 parent 8967a7f commit 7a109b9
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

* Added `Envelope::encode_bytes` and `Query/UpdateBuilder::into_envelope` for external signing workflows.
* Added `AgentBuilder::with_arc_http_middleware` for `Transport`-like functionality at the level of HTTP requests.
* Add support for dynamic routing based on boundary node discovery. This is an internal feature for now, with a feature flag `_internal_dynamic-routing`.

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 35 additions & 21 deletions ic-agent/src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1509,7 +1509,7 @@ pub struct ApiBoundaryNode {
pub ipv4_address: Option<String>,
}

/// A Query Request Builder.
/// A query request builder.
///
/// This makes it easier to do query calls without actually passing all arguments.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -1633,14 +1633,10 @@ impl<'agent> QueryBuilder<'agent> {
/// Sign a query call. This will return a [`signed::SignedQuery`]
/// which contains all fields of the query and the signed query in CBOR encoding
pub fn sign(self) -> Result<SignedQuery, AgentError> {
let content = self.agent.query_content(
self.canister_id,
self.method_name,
self.arg,
self.ingress_expiry_datetime,
self.use_nonce,
)?;
let signed_query = sign_envelope(&content, self.agent.identity.clone())?;
let effective_canister_id = self.effective_canister_id;
let identity = self.agent.identity.clone();
let content = self.into_envelope()?;
let signed_query = sign_envelope(&content, identity)?;
let EnvelopeContent::Query {
ingress_expiry,
sender,
Expand All @@ -1658,11 +1654,22 @@ impl<'agent> QueryBuilder<'agent> {
canister_id,
method_name,
arg,
effective_canister_id: self.effective_canister_id,
effective_canister_id,
signed_query,
nonce,
})
}

/// Converts the query builder into [`EnvelopeContent`] for external signing or storage.
pub fn into_envelope(self) -> Result<EnvelopeContent, AgentError> {
self.agent.query_content(
self.canister_id,
self.method_name,
self.arg,
self.ingress_expiry_datetime,
self.use_nonce,
)
}
}

impl<'agent> IntoFuture for QueryBuilder<'agent> {
Expand Down Expand Up @@ -1709,7 +1716,7 @@ impl<'a> UpdateCall<'a> {
}
}
}
/// An Update Request Builder.
/// An update request Builder.
///
/// This makes it easier to do update calls without actually passing all arguments or specifying
/// if you want to wait or not.
Expand Down Expand Up @@ -1799,15 +1806,10 @@ impl<'agent> UpdateBuilder<'agent> {
/// Sign a update call. This will return a [`signed::SignedUpdate`]
/// which contains all fields of the update and the signed update in CBOR encoding
pub fn sign(self) -> Result<SignedUpdate, AgentError> {
let nonce = self.agent.nonce_factory.generate();
let content = self.agent.update_content(
self.canister_id,
self.method_name,
self.arg,
self.ingress_expiry_datetime,
nonce,
)?;
let signed_update = sign_envelope(&content, self.agent.identity.clone())?;
let identity = self.agent.identity.clone();
let effective_canister_id = self.effective_canister_id;
let content = self.into_envelope()?;
let signed_update = sign_envelope(&content, identity)?;
let request_id = to_request_id(&content)?;
let EnvelopeContent::Call {
nonce,
Expand All @@ -1827,11 +1829,23 @@ impl<'agent> UpdateBuilder<'agent> {
canister_id,
method_name,
arg,
effective_canister_id: self.effective_canister_id,
effective_canister_id,
signed_update,
request_id,
})
}

/// Converts the update builder into an [`EnvelopeContent`] for external signing or storage.
pub fn into_envelope(self) -> Result<EnvelopeContent, AgentError> {
let nonce = self.agent.nonce_factory.generate();
self.agent.update_content(
self.canister_id,
self.method_name,
self.arg,
self.ingress_expiry_datetime,
nonce,
)
}
}

impl<'agent> IntoFuture for UpdateBuilder<'agent> {
Expand Down
1 change: 1 addition & 0 deletions ic-transport-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ leb128.workspace = true
thiserror.workspace = true
serde.workspace = true
serde_bytes.workspace = true
serde_cbor.workspace = true
serde_repr.workspace = true
sha2.workspace = true

Expand Down
14 changes: 13 additions & 1 deletion ic-transport-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ use thiserror::Error;
mod request_id;
pub mod signed;

/// The authentication envelope, containing the contents and their signature.
/// The authentication envelope, containing the contents and their signature. This struct can be passed to `Agent`'s
/// `*_signed` methods via [`to_bytes`](Envelope::to_bytes).
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct Envelope<'a> {
Expand All @@ -34,6 +35,17 @@ pub struct Envelope<'a> {
pub sender_delegation: Option<Vec<SignedDelegation>>,
}

impl Envelope<'_> {
/// Convert the authentication envelope to the format expected by the IC HTTP interface. The result can be passed to `Agent`'s `*_signed` methods.
pub fn encode_bytes(&self) -> Vec<u8> {
let mut serializer = serde_cbor::Serializer::new(Vec::new());
serializer.self_describe().unwrap();
self.serialize(&mut serializer)
.expect("infallible Envelope::serialize");
serializer.into_inner()
}
}

/// The content of an IC ingress message, not including any signature information.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "request_type", rename_all = "snake_case")]
Expand Down
9 changes: 9 additions & 0 deletions ic-transport-types/src/signed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use serde::{Deserialize, Serialize};

/// A signed query request message. Produced by
/// [`QueryBuilder::sign`](https://docs.rs/ic-agent/latest/ic_agent/agent/struct.QueryBuilder.html#method.sign).
///
/// To submit this request, pass the `signed_query` field to [`Agent::query_signed`](https://docs.rs/ic-agent/latest/ic_agent/struct.Agent.html#method.query_signed).
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SignedQuery {
/// The Unix timestamp that the request will expire at.
Expand All @@ -22,6 +24,7 @@ pub struct SignedQuery {
/// The [effective canister ID](https://internetcomputer.org/docs/current/references/ic-interface-spec#http-effective-canister-id) of the destination.
pub effective_canister_id: Principal,
/// The CBOR-encoded [authentication envelope](https://internetcomputer.org/docs/current/references/ic-interface-spec#authentication) for the request.
/// This field can be passed to [`Agent::query_signed`](https://docs.rs/ic-agent/latest/ic_agent/struct.Agent.html#method.query_signed).
#[serde(with = "serde_bytes")]
pub signed_query: Vec<u8>,
/// A nonce to uniquely identify this query call.
Expand All @@ -33,6 +36,8 @@ pub struct SignedQuery {

/// A signed update request message. Produced by
/// [`UpdateBuilder::sign`](https://docs.rs/ic-agent/latest/ic_agent/agent/struct.UpdateBuilder.html#method.sign).
///
/// To submit this request, pass the `signed_update` field to [`Agent::update_signed`](https://docs.rs/ic-agent/latest/ic_agent/struct.Agent.html#method.update_signed).
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SignedUpdate {
/// A nonce to uniquely identify this update call.
Expand All @@ -55,13 +60,16 @@ pub struct SignedUpdate {
pub effective_canister_id: Principal,
#[serde(with = "serde_bytes")]
/// The CBOR-encoded [authentication envelope](https://internetcomputer.org/docs/current/references/ic-interface-spec#authentication) for the request.
/// This field can be passed to [`Agent::update_signed`](https://docs.rs/ic-agent/latest/ic_agent/struct.Agent.html#method.update_signed).
pub signed_update: Vec<u8>,
/// The request ID.
pub request_id: RequestId,
}

/// A signed request-status request message. Produced by
/// [`Agent::sign_request_status`](https://docs.rs/ic-agent/latest/ic_agent/agent/struct.Agent.html#method.sign_request_status).
///
/// To submit this request, pass the `signed_request_status` field to [`Agent::request_status_signed`](https://docs.rs/ic-agent/latest/ic_agent/struct.Agent.html#method.request_status_signed).
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SignedRequestStatus {
/// The Unix timestamp that the request will expire at.
Expand All @@ -73,6 +81,7 @@ pub struct SignedRequestStatus {
/// The request ID.
pub request_id: RequestId,
/// The CBOR-encoded [authentication envelope](https://internetcomputer.org/docs/current/references/ic-interface-spec#authentication) for the request.
/// This field can be passed to [`Agent::request_status_signed`](https://docs.rs/ic-agent/latest/ic_agent/struct.Agent.html#method.request_status_signed).
#[serde(with = "serde_bytes")]
pub signed_request_status: Vec<u8>,
}
Expand Down

0 comments on commit 7a109b9

Please sign in to comment.