Repository: project-oak/oak
Pull request #1625 (2020-10-27 14:13:50)

Refactor oak sign key pair
This change:
- Refactors `oak_sign::KeyBundle` to `oak_sign::KeyPair`
- Adds more comments
- Implements `Clone` for `oak_sign::KeyPair` (necessary for tests)

Fixes https://github.com/project-oak/oak/issues/1620


Patch:
diff --git a/.devcontainer.json b/.devcontainer.json
index 2977bc25fb..534d68c92a 100644
--- a/.devcontainer.json
+++ b/.devcontainer.json
@@ -8,5 +8,11 @@
     "esbenp.prettier-vscode",
     "matklad.rust-analyzer",
     "xaver.clang-format"
-  ]
+  ],
+  "settings": {
+    // Use the `rust-analyzer` binary installed in the Docker image.
+    "rust-analyzer.serverPath": "rust-analyzer",
+    // Ensure protobuf generated files are loaded correctly.
+    "rust-analyzer.cargo.loadOutDirsFromCheck": true
+  }
 }
diff --git a/Dockerfile b/Dockerfile
index ea4a9d4517..6cc4ca691c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -250,6 +250,13 @@ ARG udeps_location=https://github.com/est31/cargo-udeps/releases/download/${udep
 RUN curl --location ${udeps_location} | tar --extract --gzip --directory=${install_dir} --strip-components=2 ./${udeps_dir}/cargo-udeps
 RUN chmod +x ${install_dir}/cargo-udeps
 
+# Install rust-analyzer
+# https://github.com/rust-analyzer/rust-analyzer
+ARG rust_analyzer_version=2020-10-26
+ARG rust_analyzer_location=https://github.com/rust-analyzer/rust-analyzer/releases/download/${rust_analyzer_version}/rust-analyzer-linux
+RUN curl --location ${rust_analyzer_location} > ${install_dir}/rust-analyzer
+RUN chmod +x ${install_dir}/rust-analyzer
+
 # Unset $CARGO_HOME so that the new user will use the default value for it, which will point it to
 # its own home folder.
 ENV CARGO_HOME ""
diff --git a/docs/abi.md b/docs/abi.md
index c233fd071a..08b5ad174e 100644
--- a/docs/abi.md
+++ b/docs/abi.md
@@ -181,9 +181,13 @@ If writing to the specified channel would violate
 
 ### `channel_create`
 
-Creates a new unidirectional Channel assigning the label specified by `param[2]`
-and `param[3]` to the newly created Channel, and returns the Channel handles for
-its read and write halves as output parameters in `param[0]` and `param[1]`.
+Creates a new unidirectional Channel assigning the name specified by `param[2]`
+and `param[3]` and the label specified in `param[4]` and `param[5]` to the newly
+created Channel, and returns the Channel handles for its read and write halves
+as output parameters in `param[0]` and `param[1]`.
+
+The name is a UTF-8 encoded string. It does not have to be unique and can be
+empty. It is used in logs to help with identifying channels during debugging.
 
 The label is a serialized [`Label`](/oak_abi/proto/label.proto) protobuf
 message.
@@ -200,8 +204,10 @@ If creating the specified Channel would violate
   for the write half of the channel (as a little-endian u64).
 - `param[1]: usize`: Address of an 8-byte location that will receive the handle
   for the read half of the channel (as a little-endian u64).
-- `param[2]: usize`: Source buffer holding serialized `Label`
-- `param[3]: usize`: Label size in bytes
+- `param[2]: usize`: Source buffer holding the UTF-8 encoded name
+- `param[3]: usize`: Size in bytes of the UTF-8 encoding of the name
+- `param[4]: usize`: Source buffer holding serialized `Label`
+- `param[5]: usize`: Label size in bytes
 - `result[0]: u32`: Status of operation
 
 ### `channel_close`
diff --git a/docs/programming-oak.md b/docs/programming-oak.md
index 1d88c8b2b6..ae4fae3210 100644
--- a/docs/programming-oak.md
+++ b/docs/programming-oak.md
@@ -64,7 +64,7 @@ oak::entrypoint!(grpc_oak_main<ConfigMap> => |_receiver| {
     };
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(node, grpc_channel);
+    oak::run_command_loop(node, grpc_channel.iter());
 });
 ```
 <!-- prettier-ignore-end -->
@@ -112,7 +112,7 @@ oak::entrypoint!(grpc_oak_main<ConfigMap> => |_receiver| {
     let dispatcher = TranslatorDispatcher::new(Node);
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(dispatcher, grpc_channel);
+    oak::run_command_loop(dispatcher, grpc_channel.iter());
 }
 ```
 <!-- prettier-ignore-end -->
@@ -144,7 +144,7 @@ pub extern "C" fn frontend_oak_main(_in_handle: u64) {
         let dispatcher = OakAbiTestServiceDispatcher::new(node);
         let grpc_channel =
             oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-        oak::run_command_loop(dispatcher, grpc_channel);
+        oak::run_command_loop(dispatcher, grpc_channel.iter());
     });
 }
 ```
@@ -488,8 +488,8 @@ to the room:
 [embedmd]:# (../examples/chat/module/rust/src/lib.rs Rust /.*self\.rooms\.entry\(/ /\}\);$/)
 ```Rust
                 let channel = self.rooms.entry(label.clone()).or_insert_with(|| {
-                    let (wh, rh) =
-                        oak::io::channel_create(&label).expect("could not create channel");
+                    let (wh, rh) = oak::io::channel_create("Room init", &label)
+                        .expect("could not create channel");
                     oak::node_create(&oak::node_config::wasm("app", "room"), &label, rh.handle)
                         .expect("could not create node");
                     rh.close().expect("could not close channel");
diff --git a/examples/Cargo.lock b/examples/Cargo.lock
index 32f4de800b..da5fbdc180 100644
--- a/examples/Cargo.lock
+++ b/examples/Cargo.lock
@@ -1143,6 +1143,7 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
@@ -1212,6 +1213,7 @@ name = "oak_sign"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "assert_matches",
  "env_logger",
  "hex",
  "log",
diff --git a/examples/aggregator/client/cpp/aggregator.cc b/examples/aggregator/client/cpp/aggregator.cc
index 2192a0601a..f9a52e2e6c 100644
--- a/examples/aggregator/client/cpp/aggregator.cc
+++ b/examples/aggregator/client/cpp/aggregator.cc
@@ -79,7 +79,7 @@ int main(int argc, char** argv) {
   // The particular value corresponds to the hash on the `aggregator.wasm` line in
   // https://github.com/project-oak/oak/blob/hashes/reproducibility_index.
   oak::label::Label label = oak::WebAssemblyModuleHashLabel(
-      absl::HexStringToBytes("b32f72f574513d824e1c01347f9b39cafa7d08ef0686d5d6b43425aa8fac78de"));
+      absl::HexStringToBytes("9a75540f69287bc579f896416030de0e06ba915aa93279384859fa25c7a384ce"));
   // Connect to the Oak Application.
   auto stub = Aggregator::NewStub(oak::ApplicationClient::CreateChannel(
       address, oak::ApplicationClient::GetTlsChannelCredentials(ca_cert), label));
diff --git a/examples/aggregator/module/rust/src/lib.rs b/examples/aggregator/module/rust/src/lib.rs
index 88f254e056..0cf736ca92 100644
--- a/examples/aggregator/module/rust/src/lib.rs
+++ b/examples/aggregator/module/rust/src/lib.rs
@@ -174,7 +174,7 @@ oak::entrypoint!(aggregator<AggregatorInit> => |init_receiver: Receiver<Aggregat
         }
     );
     let dispatcher = AggregatorDispatcher::new(node);
-    oak::run_command_loop(dispatcher, grpc_server_invocation_receiver);
+    oak::run_command_loop(dispatcher, grpc_server_invocation_receiver.iter());
 });
 
 #[derive(Debug, serde::Deserialize)]
@@ -209,7 +209,7 @@ oak::entrypoint!(oak_main<ConfigMap> => |receiver: Receiver<ConfigMap>| {
         oak::grpc::server::init(&config.grpc_server_listen_address).expect("Couldn't create gRPC server pseudo-Node");
 
     // Create an Aggregator Node.
-    let (init_sender, init_receiver) = oak::io::channel_create::<AggregatorInit>(&Label::public_untrusted())
+    let (init_sender, init_receiver) = oak::io::channel_create::<AggregatorInit>("Aggregator init", &Label::public_untrusted())
         .expect("Couldn't create initialization channel");
     oak::node_create(&oak::node_config::wasm("app", "aggregator"), &Label::public_untrusted(), init_receiver.handle)
         .expect("Couldn't create gRPC worker node");
@@ -238,7 +238,7 @@ oak::entrypoint!(oak_main<ConfigMap> => |receiver: Receiver<ConfigMap>| {
     // Send the initialization message to Aggregator Node containing a gRPC server invocation
     // receiver and a gRPC client invocation sender.
     debug!("Sending the initialization message to handler Node");
-    let (invocation_sender, invocation_receiver) = oak::io::channel_create::<grpc::Invocation>(&Label::public_untrusted())
+    let (invocation_sender, invocation_receiver) = oak::io::channel_create::<grpc::Invocation>("gRPC invocation", &Label::public_untrusted())
         .expect("Couldn't create gRPC invocation channel");
     let init_message = create_init_message(&invocation_receiver, &grpc_client.invocation_sender);
     init_sender.send(&init_message).expect("Couldn't send the initialization message to Aggregator Node");
diff --git a/examples/aggregator/oak_app_manifest.toml b/examples/aggregator/oak_app_manifest.toml
index 4d1b8640de..ac8cf21090 100644
--- a/examples/aggregator/oak_app_manifest.toml
+++ b/examples/aggregator/oak_app_manifest.toml
@@ -1,4 +1,4 @@
 name = "aggregator"
 
 [modules]
-app = { external = { url = "https://storage.googleapis.com/oak-modules/aggregator/b32f72f574513d824e1c01347f9b39cafa7d08ef0686d5d6b43425aa8fac78de", sha256 = "b32f72f574513d824e1c01347f9b39cafa7d08ef0686d5d6b43425aa8fac78de" } }
+app = { external = { url = "https://storage.googleapis.com/oak-modules/aggregator/9a75540f69287bc579f896416030de0e06ba915aa93279384859fa25c7a384ce", sha256 = "9a75540f69287bc579f896416030de0e06ba915aa93279384859fa25c7a384ce" } }
diff --git a/examples/chat/module/rust/src/lib.rs b/examples/chat/module/rust/src/lib.rs
index 07455a67f5..d7419fedb0 100644
--- a/examples/chat/module/rust/src/lib.rs
+++ b/examples/chat/module/rust/src/lib.rs
@@ -18,7 +18,7 @@ use anyhow::Context;
 use log::{info, warn};
 use oak::{
     grpc,
-    io::{ReceiverExt, Sender, SenderExt},
+    io::{Receiver, ReceiverExt, Sender, SenderExt},
     Label,
 };
 use oak_abi::proto::oak::application::ConfigMap;
@@ -85,8 +85,8 @@ impl oak::CommandHandler<oak::grpc::Invocation> for Router {
                 // Check if there is a channel to a room with the desired label already, or create
                 // it if not.
                 let channel = self.rooms.entry(label.clone()).or_insert_with(|| {
-                    let (wh, rh) =
-                        oak::io::channel_create(&label).expect("could not create channel");
+                    let (wh, rh) = oak::io::channel_create("Room init", &label)
+                        .expect("could not create channel");
                     oak::node_create(&oak::node_config::wasm("app", "room"), &label, rh.handle)
                         .expect("could not create node");
                     rh.close().expect("could not close channel");
@@ -109,17 +109,17 @@ oak::entrypoint!(grpc_oak_main<ConfigMap> => |_receiver| {
     let router = Router::default();
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(router, grpc_channel);
+    oak::run_command_loop(router, grpc_channel.iter());
 });
 
 // A node that receives gRPC invocations for an individual label.
 //
 // Multiple instances of nodes with this entrypoint may be created at runtime, according to the
 // variety of labels of incoming requests.
-oak::entrypoint!(room<oak::grpc::Invocation> => |receiver| {
+oak::entrypoint!(room<oak::grpc::Invocation> => |receiver: Receiver<oak::grpc::Invocation>| {
     oak::logger::init_default();
     let dispatcher = ChatDispatcher::new(Room::default());
-    oak::run_command_loop(dispatcher, receiver);
+    oak::run_command_loop(dispatcher, receiver.iter());
 });
 
 /// A worker node implementation for an individual label.
diff --git a/examples/hello_world/module/rust/src/lib.rs b/examples/hello_world/module/rust/src/lib.rs
index 0ad1fd0c63..7ba05e0b99 100644
--- a/examples/hello_world/module/rust/src/lib.rs
+++ b/examples/hello_world/module/rust/src/lib.rs
@@ -19,7 +19,7 @@ pub mod proto {
 }
 
 use log::info;
-use oak::grpc;
+use oak::{grpc, io::ReceiverExt};
 use oak_abi::proto::oak::application::ConfigMap;
 use proto::{HelloRequest, HelloResponse, HelloWorld, HelloWorldDispatcher};
 
@@ -32,7 +32,7 @@ oak::entrypoint!(oak_main<ConfigMap> => |_receiver| {
     let dispatcher = HelloWorldDispatcher::new(node);
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(dispatcher, grpc_channel);
+    oak::run_command_loop(dispatcher, grpc_channel.iter());
 });
 
 struct Node {
diff --git a/examples/http_server/module/src/lib.rs b/examples/http_server/module/src/lib.rs
index db76fb6874..a0528e80f2 100644
--- a/examples/http_server/module/src/lib.rs
+++ b/examples/http_server/module/src/lib.rs
@@ -17,7 +17,7 @@
 //! Simple example starting an Oak Application serving a static page over HTTP.
 
 use log::{info, warn};
-use oak::{http::Invocation, CommandHandler, OakError, OakStatus};
+use oak::{http::Invocation, io::ReceiverExt, CommandHandler, OakError, OakStatus};
 use oak_abi::proto::oak::application::ConfigMap;
 
 oak::entrypoint!(oak_main<ConfigMap> => |_receiver| {
@@ -27,7 +27,7 @@ oak::entrypoint!(oak_main<ConfigMap> => |_receiver| {
     let http_channel =
         oak::http::init("[::]:8080").expect("Could not create HTTP server pseudo-Node!");
 
-    oak::run_command_loop(node, http_channel);
+    oak::run_command_loop(node, http_channel.iter());
 });
 
 /// A simple HTTP server that responds with `OK` (200) to every request sent to `/`, and with
diff --git a/examples/injection/module/rust/src/lib.rs b/examples/injection/module/rust/src/lib.rs
index 8eedaadc78..7b58f065f5 100644
--- a/examples/injection/module/rust/src/lib.rs
+++ b/examples/injection/module/rust/src/lib.rs
@@ -79,8 +79,8 @@ use proto::{
 
 oak::entrypoint!(grpc_fe<ConfigMap> => |_receiver| {
     oak::logger::init_default();
-    let (to_provider_write_handle, to_provider_read_handle) = oak::channel_create(&Label::public_untrusted()).unwrap();
-    let (from_provider_write_handle, from_provider_read_handle) = oak::channel_create(&Label::public_untrusted()).unwrap();
+    let (to_provider_write_handle, to_provider_read_handle) = oak::channel_create("To-provider", &Label::public_untrusted()).unwrap();
+    let (from_provider_write_handle, from_provider_read_handle) = oak::channel_create("From-provider", &Label::public_untrusted()).unwrap();
 
     oak::node_create(&oak::node_config::wasm("app", "provider"), &Label::public_untrusted(), to_provider_read_handle)
         .expect("Failed to create provider");
@@ -98,7 +98,7 @@ oak::entrypoint!(grpc_fe<ConfigMap> => |_receiver| {
     let grpc_channel = oak::grpc::server::init("[::]:8080")
         .expect("could not create gRPC server pseudo-Node");
 
-    oak::run_command_loop(dispatcher, grpc_channel);
+    oak::run_command_loop(dispatcher, grpc_channel.iter());
 });
 
 oak::entrypoint!(provider<BlobStoreRequest> => |receiver: Receiver<BlobStoreRequest>| {
@@ -112,7 +112,7 @@ oak::entrypoint!(provider<BlobStoreRequest> => |receiver: Receiver<BlobStoreRequ
             .expect("Did not receive a decodable message")
             .sender
             .expect("No sender in received message");
-    oak::run_command_loop(BlobStoreProvider::new(frontend_sender), receiver);
+    oak::run_command_loop(BlobStoreProvider::new(frontend_sender), receiver.iter());
 });
 
 oak::entrypoint!(store<BlobRequest> => |receiver: Receiver<BlobRequest>| {
@@ -126,7 +126,7 @@ oak::entrypoint!(store<BlobRequest> => |receiver: Receiver<BlobRequest>| {
             .expect("Did not receive a write handle")
             .sender
             .expect("No write handle in received message");
-    oak::run_command_loop(BlobStoreImpl::new(sender), receiver);
+    oak::run_command_loop(BlobStoreImpl::new(sender), receiver.iter());
 });
 
 enum BlobStoreAccess {
@@ -218,9 +218,11 @@ impl oak::CommandHandler<BlobStoreRequest> for BlobStoreProvider {
     fn handle_command(&mut self, _command: BlobStoreRequest) -> anyhow::Result<()> {
         // Create new BlobStore
         let (to_store_write_handle, to_store_read_handle) =
-            oak::channel_create(&Label::public_untrusted()).context("Could not create channel")?;
+            oak::channel_create("To-store", &Label::public_untrusted())
+                .context("Could not create channel")?;
         let (from_store_write_handle, from_store_read_handle) =
-            oak::channel_create(&Label::public_untrusted()).context("Could not create channel")?;
+            oak::channel_create("From-store", &Label::public_untrusted())
+                .context("Could not create channel")?;
         oak::node_create(
             &oak::node_config::wasm("app", "store"),
             &Label::public_untrusted(),
diff --git a/examples/machine_learning/module/rust/src/lib.rs b/examples/machine_learning/module/rust/src/lib.rs
index ce647c7c47..1d719facaa 100644
--- a/examples/machine_learning/module/rust/src/lib.rs
+++ b/examples/machine_learning/module/rust/src/lib.rs
@@ -18,7 +18,10 @@
 // https://github.com/AtheMathmo/rusty-machine/blob/master/examples/naive_bayes_dogs.rs .
 
 use log::{error, info, warn};
-use oak::grpc;
+use oak::{
+    grpc,
+    io::{Receiver, ReceiverExt},
+};
 use oak_abi::proto::oak::application::ConfigMap;
 use rand::prelude::*;
 use rand_distr::{Distribution, Normal, Standard};
@@ -147,7 +150,7 @@ struct Config {
     test_animals: Vec<Animal>,
 }
 
-oak::entrypoint!(oak_main<grpc::Invocation> => |receiver| {
+oak::entrypoint!(oak_main<grpc::Invocation> => |receiver: Receiver<grpc::Invocation>| {
     oak::logger::init_default();
     let node = Node {
         training_set_size: 1000,
@@ -155,7 +158,7 @@ oak::entrypoint!(oak_main<grpc::Invocation> => |receiver| {
         config: None,
         model: NaiveBayes::new(),
     };
-    oak::run_command_loop(node, receiver);
+    oak::run_command_loop(node, receiver.iter());
 });
 
 oak::entrypoint!(grpc_oak_main<ConfigMap> => |_receiver| {
@@ -168,7 +171,7 @@ oak::entrypoint!(grpc_oak_main<ConfigMap> => |_receiver| {
     };
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(node, grpc_channel);
+    oak::run_command_loop(node, grpc_channel.iter());
 });
 
 struct Node {
diff --git a/examples/private_set_intersection/module/rust/src/lib.rs b/examples/private_set_intersection/module/rust/src/lib.rs
index 95d04e598a..bcc151463f 100644
--- a/examples/private_set_intersection/module/rust/src/lib.rs
+++ b/examples/private_set_intersection/module/rust/src/lib.rs
@@ -38,7 +38,7 @@ pub mod proto {
     ));
 }
 
-use oak::grpc;
+use oak::{grpc, io::ReceiverExt};
 use oak_abi::proto::oak::application::ConfigMap;
 use proto::{
     GetIntersectionRequest, GetIntersectionResponse, PrivateSetIntersection,
@@ -50,7 +50,7 @@ oak::entrypoint!(oak_main<ConfigMap> => |_receiver| {
     let dispatcher = PrivateSetIntersectionDispatcher::new(Node::default());
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(dispatcher, grpc_channel);
+    oak::run_command_loop(dispatcher, grpc_channel.iter());
 });
 
 /// Maximum number of contributed private sets.
diff --git a/examples/private_set_intersection/oak_app_manifest.toml b/examples/private_set_intersection/oak_app_manifest.toml
index bf4b094d1b..d9935c6692 100644
--- a/examples/private_set_intersection/oak_app_manifest.toml
+++ b/examples/private_set_intersection/oak_app_manifest.toml
@@ -2,4 +2,4 @@ name = "private_set_intersection"
 
 [modules]
 # TODO(865): Use locally built module once reproducibility is fixed.
-app = { external = { url = "https://storage.googleapis.com/oak-modules/private_set_intersection/b86267f3aabbc2d3fffbc463d3c23409d786f3577abe1f66ea52d092f2d72365", sha256 = "b86267f3aabbc2d3fffbc463d3c23409d786f3577abe1f66ea52d092f2d72365" } }
+app = { external = { url = "https://storage.googleapis.com/oak-modules/private_set_intersection/21963350a129dd79db7952b2e5ea9b1ef31e855becac22d6a980efede61da06f", sha256 = "21963350a129dd79db7952b2e5ea9b1ef31e855becac22d6a980efede61da06f" } }
diff --git a/examples/private_set_intersection/private_set_intersection.sign b/examples/private_set_intersection/private_set_intersection.sign
index ff38263faa..22d54a8241 100644
--- a/examples/private_set_intersection/private_set_intersection.sign
+++ b/examples/private_set_intersection/private_set_intersection.sign
@@ -3,10 +3,10 @@ f41SClNtR4i46v2Tuh1fQLbt/ZqRr1lENajCW92jyP4=
 -----END PUBLIC KEY-----
 
 -----BEGIN SIGNATURE-----
-XOKfIiD/krp2WcCPkZ+9T6fcAk5J9xuXM394VZW40V/Z6TXKaA/4e/O79iCTLLZG
-O2tPi/Tpie8XFwHgDm3VBg==
+6pSC4WodR+brN9OkhSX/mudNriLNzPvzftAP4MO0rybe9YxPDRC/vbBSB2ewAIjo
++UR86oYG657laC3bGX0tCw==
 -----END SIGNATURE-----
 
 -----BEGIN HASH-----
-uGJn86q7wtP/+8Rj08I0CdeG81d6vh9m6lLQkvLXI2U=
+IZYzUKEp3XnbeVKy5eqbHvMehVvsrCLWqYDv7eYdoG8=
 -----END HASH-----
diff --git a/examples/running_average/module/rust/src/lib.rs b/examples/running_average/module/rust/src/lib.rs
index a71e60fdb0..908eaa11d4 100644
--- a/examples/running_average/module/rust/src/lib.rs
+++ b/examples/running_average/module/rust/src/lib.rs
@@ -26,7 +26,7 @@ pub mod proto {
     include!(concat!(env!("OUT_DIR"), "/oak.examples.running_average.rs"));
 }
 
-use oak::grpc;
+use oak::{grpc, io::ReceiverExt};
 use oak_abi::proto::oak::application::ConfigMap;
 use proto::{GetAverageResponse, RunningAverage, RunningAverageDispatcher, SubmitSampleRequest};
 
@@ -34,7 +34,7 @@ oak::entrypoint!(oak_main<ConfigMap> => |_receiver| {
     let dispatcher = RunningAverageDispatcher::new(Node::default());
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(dispatcher, grpc_channel);
+    oak::run_command_loop(dispatcher, grpc_channel.iter());
 });
 
 #[derive(Default)]
diff --git a/examples/translator/module/rust/src/lib.rs b/examples/translator/module/rust/src/lib.rs
index fc28691c74..46b3cab474 100644
--- a/examples/translator/module/rust/src/lib.rs
+++ b/examples/translator/module/rust/src/lib.rs
@@ -15,7 +15,10 @@
 //
 
 use log::info;
-use oak::grpc;
+use oak::{
+    grpc,
+    io::{Receiver, ReceiverExt},
+};
 use oak_abi::proto::oak::application::ConfigMap;
 use translator_common::proto::{
     TranslateRequest, TranslateResponse, Translator, TranslatorDispatcher,
@@ -24,10 +27,10 @@ use translator_common::proto::{
 // The `oak_main` entrypoint is used when the Translator acts as a library Node for a
 // wider Application. In this case, invocations arrive directly over the channel received
 // at start-of-day.
-oak::entrypoint!(oak_main<grpc::Invocation> => |receiver| {
+oak::entrypoint!(oak_main<grpc::Invocation> => |receiver: Receiver<grpc::Invocation>| {
     oak::logger::init_default();
     let dispatcher = TranslatorDispatcher::new(Node);
-    oak::run_command_loop(dispatcher, receiver);
+    oak::run_command_loop(dispatcher, receiver.iter());
 });
 
 // The `grpc_oak_main` entrypoint is used when the Translator acts as a standalone Oak
@@ -39,7 +42,7 @@ oak::entrypoint!(grpc_oak_main<ConfigMap> => |_receiver| {
     let dispatcher = TranslatorDispatcher::new(Node);
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(dispatcher, grpc_channel);
+    oak::run_command_loop(dispatcher, grpc_channel.iter());
 });
 
 struct Node;
diff --git a/examples/trusted_database/module/rust/src/handler.rs b/examples/trusted_database/module/rust/src/handler.rs
index 7046ef8656..e53485664d 100644
--- a/examples/trusted_database/module/rust/src/handler.rs
+++ b/examples/trusted_database/module/rust/src/handler.rs
@@ -149,5 +149,5 @@ oak::entrypoint!(handler_oak_main<TrustedDatabaseCommand> => |command_receiver:
     let dispatcher = TrustedDatabaseDispatcher::new(node);
     let invocation_receiver = receiver.receiver.expect("Empty gRPC invocation receiver");
     // The event loop only runs once because the `Main` Node sends a single invocation.
-    oak::run_command_loop(dispatcher, invocation_receiver);
+    oak::run_command_loop(dispatcher, invocation_receiver.iter());
 });
diff --git a/examples/trusted_database/module/rust/src/lib.rs b/examples/trusted_database/module/rust/src/lib.rs
index 833bf6bcca..a9f8addfe4 100644
--- a/examples/trusted_database/module/rust/src/lib.rs
+++ b/examples/trusted_database/module/rust/src/lib.rs
@@ -76,9 +76,11 @@ impl CommandHandler<grpc::Invocation> for TrustedDatabaseNode {
     fn handle_command(&mut self, invocation: grpc::Invocation) -> anyhow::Result<()> {
         // Create a client request handler Node.
         debug!("Creating handler Node");
-        let (sender, receiver) =
-            oak::io::channel_create::<TrustedDatabaseCommand>(&Label::public_untrusted())
-                .context("Couldn't create command channel")?;
+        let (sender, receiver) = oak::io::channel_create::<TrustedDatabaseCommand>(
+            "Command",
+            &Label::public_untrusted(),
+        )
+        .context("Couldn't create command channel")?;
         // TODO(#1406): Use client assigned label for creating a new handler Node.
         oak::node_create(
             &oak::node_config::wasm("app", "handler_oak_main"),
@@ -90,9 +92,11 @@ impl CommandHandler<grpc::Invocation> for TrustedDatabaseNode {
 
         // Create a gRPC invocation channel for forwarding requests to the
         // `TrustedDatabaseHandlerNode`.
-        let (invocation_sender, invocation_receiver) =
-            oak::io::channel_create::<grpc::Invocation>(&Label::public_untrusted())
-                .context("Couldn't create gRPC invocation channel")?;
+        let (invocation_sender, invocation_receiver) = oak::io::channel_create::<grpc::Invocation>(
+            "gRPC invocation",
+            &Label::public_untrusted(),
+        )
+        .context("Couldn't create gRPC invocation channel")?;
 
         // Create a command message that contains a copy of the database.
         let command = TrustedDatabaseCommand {
@@ -127,5 +131,5 @@ oak::entrypoint!(oak_main<ConfigMap> => |receiver: Receiver<ConfigMap>| {
     let points_of_interest = load_database(config_map).expect("Couldn't load database");
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("Couldn't create gRPC server pseudo-Node");
-    oak::run_command_loop(TrustedDatabaseNode { points_of_interest }, grpc_channel);
+    oak::run_command_loop(TrustedDatabaseNode { points_of_interest }, grpc_channel.iter());
 });
diff --git a/experimental/Cargo.lock b/experimental/Cargo.lock
index 8c6b0fb1fd..c7cfa624d9 100644
--- a/experimental/Cargo.lock
+++ b/experimental/Cargo.lock
@@ -30,6 +30,12 @@ version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b585a98a234c46fc563103e9278c9391fde1f4e6850334da895d27edb9580f62"
 
+[[package]]
+name = "assert_matches"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "695579f0f2520f3774bb40461e5adb066459d4e0af4d59d20175484fb8e9edf1"
+
 [[package]]
 name = "async-stream"
 version = "0.2.1"
@@ -821,6 +827,7 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
@@ -890,6 +897,7 @@ name = "oak_sign"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "assert_matches",
  "env_logger",
  "hex",
  "log",
diff --git a/experimental/web/index.html b/experimental/web/index.html
index c2fcfdaed1..f39e5f1b18 100644
--- a/experimental/web/index.html
+++ b/experimental/web/index.html
@@ -131,29 +131,37 @@
                       handleBuf,
                       handleCount,
                     ].join(', ')}) -> ${status}
-    bytes: [${bytes}]
-    bytes(string): "${bytesString}"
-    handles: [${handles}]`;
+      bytes: [${bytes}]
+      bytes(string): "${bytesString}"
+      handles: [${handles}]`;
                     this.trace.push(entry);
                     return status;
                   },
                   channel_create: (
                     writeHandle,
                     readHandle,
+                    nameBuf,
+                    nameSize,
                     labelBuf,
                     labelSize
                   ) => {
                     const status = OakStatus.values.OK;
+                    const name = new TextDecoder().decode(
+                      this.readMemory(nameBuf, nameSize)
+                    );
                     const label = Label.decode(
                       this.readMemory(labelBuf, labelSize)
                     );
                     const entry = `${new Date().toISOString()}: channel_create(${[
                       writeHandle,
                       readHandle,
+                      nameBuf,
+                      nameSize,
                       labelBuf,
                       labelSize,
                     ].join(', ')}) -> ${status}
-    label: ${JSON.stringify(label)}`;
+      name: ${JSON.stringify(name)}
+      label: ${JSON.stringify(label)}`;
                     this.trace.push(entry);
                     return status;
                   },
@@ -178,8 +186,8 @@
                       labelSize,
                       handle,
                     ].join(', ')}) -> ${status}
-    config: ${JSON.stringify(config)}
-    label: ${JSON.stringify(label)}`;
+      config: ${JSON.stringify(config)}
+      label: ${JSON.stringify(label)}`;
                     this.trace.push(entry);
                     this.createNode(config);
                     return status;
diff --git a/oak/module/oak_abi.h b/oak/module/oak_abi.h
index 0bfb0fb99e..3f3b31dbae 100644
--- a/oak/module/oak_abi.h
+++ b/oak/module/oak_abi.h
@@ -52,7 +52,8 @@ oak_abi::OakStatus channel_write(oak_abi::Handle handle, uint8_t* buff, size_t u
 WASM_IMPORT("oak") oak_abi::OakStatus channel_close(oak_abi::Handle handle);
 WASM_IMPORT("oak")
 oak_abi::OakStatus channel_create(oak_abi::Handle* write_handle, oak_abi::Handle* read_handle,
-                                  uint8_t* label_buf, size_t label_size);
+                                  uint8_t* name_buf, size_t name_size, uint8_t* label_buf,
+                                  size_t label_size);
 WASM_IMPORT("oak")
 oak_abi::OakStatus channel_label_read(oak_abi::Handle handle, uint8_t* label_buf, size_t label_size,
                                       uint32_t* actual_size);
diff --git a/oak/module/oak_main.h b/oak/module/oak_main.h
index e57e63916a..2c488311ad 100644
--- a/oak/module/oak_main.h
+++ b/oak/module/oak_main.h
@@ -38,7 +38,7 @@ WASM_EXPORT void oak_main(oak_abi::Handle _handle) {
   // Create a channel to the gRPC server pseudo-Node.
   oak_abi::Handle write_handle;
   oak_abi::Handle read_handle;
-  oak_abi::OakStatus result = channel_create(&write_handle, &read_handle, nullptr, 0);
+  oak_abi::OakStatus result = channel_create(&write_handle, &read_handle, nullptr, 0, nullptr, 0);
   if (result != oak_abi::OakStatus::OK) {
     return;
   }
@@ -65,7 +65,7 @@ WASM_EXPORT void oak_main(oak_abi::Handle _handle) {
   // Create a separate channel for receiving invocations and pass it to the gRPC pseudo-Node.
   oak_abi::Handle grpc_out_handle;
   oak_abi::Handle grpc_in_handle;
-  result = channel_create(&grpc_out_handle, &grpc_in_handle, nullptr, 0);
+  result = channel_create(&grpc_out_handle, &grpc_in_handle, nullptr, 0, nullptr, 0);
   if (result != oak_abi::OakStatus::OK) {
     return;
   }
diff --git a/oak_abi/src/lib.rs b/oak_abi/src/lib.rs
index 1ccd5e429f..eaa3db3550 100644
--- a/oak_abi/src/lib.rs
+++ b/oak_abi/src/lib.rs
@@ -135,6 +135,9 @@ extern "C" {
     /// Returns handles for the the write and read halves of the Channel in the spaces given by
     /// `write` and `read`.
     ///
+    /// The name to assign to the newly created Channel is provided in the memory area given by
+    /// `name_buf` and `name_len` as a UTF-8 encoded string.
+    ///
     /// The label to assign to the newly created Channel is provided in the memory area given by
     /// `label_buf` and `label_len`.
     ///
@@ -144,6 +147,8 @@ extern "C" {
     pub fn channel_create(
         write: *mut u64,
         read: *mut u64,
+        name_buf: *const u8,
+        name_len: usize,
         label_buf: *const u8,
         label_len: usize,
     ) -> u32;
diff --git a/oak_derive/Cargo.lock b/oak_derive/Cargo.lock
index c5fe777b89..ab203c86e5 100644
--- a/oak_derive/Cargo.lock
+++ b/oak_derive/Cargo.lock
@@ -130,6 +130,7 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
diff --git a/oak_io/Cargo.lock b/oak_io/Cargo.lock
index d0b47a4053..85d1e87faa 100644
--- a/oak_io/Cargo.lock
+++ b/oak_io/Cargo.lock
@@ -6,6 +6,12 @@ version = "1.0.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
 
+[[package]]
+name = "assert_matches"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "695579f0f2520f3774bb40461e5adb066459d4e0af4d59d20175484fb8e9edf1"
+
 [[package]]
 name = "autocfg"
 version = "1.0.0"
@@ -129,6 +135,8 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "assert_matches",
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
diff --git a/oak_io/Cargo.toml b/oak_io/Cargo.toml
index 0a79d9c346..259d80d122 100644
--- a/oak_io/Cargo.toml
+++ b/oak_io/Cargo.toml
@@ -12,6 +12,10 @@ oak_abi = { path = "../oak_abi" }
 oak_derive = { path = "../oak_derive" }
 prost = "*"
 prost-types = "*"
+either = "*"
+
+[dev-dependencies]
+assert_matches = "*"
 
 [build-dependencies]
 oak_utils = { path = "../oak_utils" }
diff --git a/oak_io/src/lib.rs b/oak_io/src/lib.rs
index df1bfc69d8..13756eb556 100644
--- a/oak_io/src/lib.rs
+++ b/oak_io/src/lib.rs
@@ -24,10 +24,11 @@ mod receiver;
 mod sender;
 
 pub use decodable::Decodable;
+use either::Either;
 pub use encodable::Encodable;
 pub use error::OakError;
 use handle::{ReadHandle, WriteHandle};
-pub use oak_abi::Handle;
+pub use oak_abi::{Handle, OakStatus};
 pub use receiver::Receiver;
 pub use sender::Sender;
 
@@ -37,3 +38,116 @@ pub struct Message {
     pub bytes: Vec<u8>,
     pub handles: Vec<Handle>,
 }
+
+/// Implementation of [`Encodable`] for [`Either`] that encodes the variant (0 or 1) in the first
+/// byte of the resulting [`Message`].
+impl<L: Encodable, R: Encodable> Encodable for Either<L, R> {
+    fn encode(&self) -> Result<Message, OakError> {
+        let (variant, mut inner) = match self {
+            // Left variant == 0.
+            Either::Left(m) => (0, m.encode()?),
+            // Right variant == 1.
+            Either::Right(m) => (1, m.encode()?),
+        };
+        // Insert the variant byte as the beginning of the message bytes, and leave handles as they
+        // are.
+        inner.bytes.insert(0, variant);
+        Ok(inner)
+    }
+}
+
+/// Implementation of [`Decodable`] for [`Either`] that decodes the variant (0 or 1) from the first
+/// byte of the provided [`Message`].
+impl<L: Decodable, R: Decodable> Decodable for Either<L, R> {
+    fn decode(message: &Message) -> Result<Self, OakError> {
+        match message.bytes.get(0) {
+            // Left variant == 0.
+            Some(0) => {
+                let inner_message = Message {
+                    bytes: message.bytes[1..].to_vec(),
+                    handles: message.handles.clone(),
+                };
+                Ok(Either::Left(L::decode(&inner_message)?))
+            }
+            // Right variant == 1.
+            Some(1) => {
+                let inner_message = Message {
+                    bytes: message.bytes[1..].to_vec(),
+                    handles: message.handles.clone(),
+                };
+                Ok(Either::Right(R::decode(&inner_message)?))
+            }
+            // Invalid variant, or not enough bytes.
+            _ => Err(OakStatus::ErrInvalidArgs.into()),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use assert_matches::assert_matches;
+
+    #[test]
+    fn either_round_trip() {
+        type T = Either<u32, u32>;
+
+        {
+            // This test case is just a baseline reference for the ones below, to spell out the
+            // protobuf encoding of the inner object.
+            let original = 1988;
+            let encoded = original.encode().unwrap();
+            assert_eq!(
+                Message {
+                    bytes: vec![8, 196, 15],
+                    handles: vec![]
+                },
+                encoded
+            );
+            let decoded = u32::decode(&encoded).unwrap();
+            assert_eq!(original, decoded);
+        }
+
+        {
+            let original = T::Left(1988);
+            let encoded = original.encode().unwrap();
+            // Note the first byte corresponds to the variant == 0.
+            assert_eq!(
+                Message {
+                    bytes: vec![0, 8, 196, 15],
+                    handles: vec![]
+                },
+                encoded
+            );
+            let decoded = T::decode(&encoded).unwrap();
+            assert_eq!(original, decoded);
+        }
+
+        {
+            let original = T::Right(1988);
+            let encoded = original.encode().unwrap();
+            // Note the first byte corresponds to the variant == 1.
+            assert_eq!(
+                Message {
+                    bytes: vec![1, 8, 196, 15],
+                    handles: vec![]
+                },
+                encoded
+            );
+            let decoded = T::decode(&encoded).unwrap();
+            assert_eq!(original, decoded);
+        }
+
+        {
+            let invalid_variant = 42;
+            let encoded_invalid = Message {
+                bytes: vec![invalid_variant, 8, 196, 15],
+                handles: vec![],
+            };
+            assert_matches!(
+                T::decode(&encoded_invalid),
+                Err(OakError::OakStatus(OakStatus::ErrInvalidArgs))
+            );
+        }
+    }
+}
diff --git a/oak_loader/Cargo.lock b/oak_loader/Cargo.lock
index f41c685efe..57d18c1a6b 100644
--- a/oak_loader/Cargo.lock
+++ b/oak_loader/Cargo.lock
@@ -30,6 +30,12 @@ version = "0.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034"
 
+[[package]]
+name = "assert_matches"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "695579f0f2520f3774bb40461e5adb066459d4e0af4d59d20175484fb8e9edf1"
+
 [[package]]
 name = "async-stream"
 version = "0.2.1"
@@ -710,6 +716,7 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
@@ -800,6 +807,7 @@ name = "oak_sign"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "assert_matches",
  "env_logger",
  "hex",
  "log",
diff --git a/oak_runtime/Cargo.lock b/oak_runtime/Cargo.lock
index 9c2f9c982e..e5c8086c7d 100644
--- a/oak_runtime/Cargo.lock
+++ b/oak_runtime/Cargo.lock
@@ -24,6 +24,12 @@ version = "1.0.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
 
+[[package]]
+name = "assert_matches"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "695579f0f2520f3774bb40461e5adb066459d4e0af4d59d20175484fb8e9edf1"
+
 [[package]]
 name = "async-stream"
 version = "0.2.1"
@@ -716,6 +722,7 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
@@ -788,6 +795,7 @@ name = "oak_sign"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "assert_matches",
  "env_logger",
  "hex",
  "log",
diff --git a/oak_runtime/introspection_browser_client/components/Event/index.tsx b/oak_runtime/introspection_browser_client/components/Event/index.tsx
index 1cb9cd39e7..dae66c9ae4 100644
--- a/oak_runtime/introspection_browser_client/components/Event/index.tsx
+++ b/oak_runtime/introspection_browser_client/components/Event/index.tsx
@@ -148,6 +148,7 @@ function renderEventDescription(
         <ObjectAsDescriptionList
           object={{
             id: event.getChannelCreated()!.getChannelId(),
+            name: event.getChannelCreated()!.getName(),
             label: event.getChannelCreated()!.getLabel()!.toObject(),
           }}
           dlComponent={InlineDl}
diff --git a/oak_runtime/introspection_browser_client/components/Root/index.tsx b/oak_runtime/introspection_browser_client/components/Root/index.tsx
index c6e83c8916..bf1d03db73 100644
--- a/oak_runtime/introspection_browser_client/components/Root/index.tsx
+++ b/oak_runtime/introspection_browser_client/components/Root/index.tsx
@@ -86,6 +86,7 @@ interface Message {
 interface Channel {
   id: ChannelID;
   messages: Message[];
+  name: string;
   label: Label;
 }
 type Channels = Map<ChannelID, Channel>;
@@ -140,6 +141,7 @@ function eventReducer(
         applicationState.channels.set(channelId, {
           id: channelId,
           messages: [],
+          name: channel.getName(),
           label: channel.getLabel()!.toObject(),
         });
       }
diff --git a/oak_runtime/src/channel.rs b/oak_runtime/src/channel.rs
index 63606295d3..762c753512 100644
--- a/oak_runtime/src/channel.rs
+++ b/oak_runtime/src/channel.rs
@@ -74,6 +74,12 @@ pub struct Channel {
     /// See https://github.com/project-oak/oak/blob/main/docs/concepts.md#labels
     pub label: oak_abi::label::Label,
 
+    /// The name for the channel.
+    ///
+    /// The name does not have to be unique and can be empty. It is used in logs to help with
+    /// identifying channels during debugging.
+    pub name: String,
+
     /// Weak reference to the Runtime used for sending introspection events.
     runtime_weak: Weak<Runtime>,
 }
@@ -82,10 +88,11 @@ impl std::fmt::Debug for Channel {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(
             f,
-            "Channel {{ id={}, #readers={}, #writers={}, label={:?} }}",
+            "Channel {{ id={}, #readers={}, #writers={}, name={:?}, label={:?} }}",
             self.id,
             self.reader_count.load(SeqCst),
             self.writer_count.load(SeqCst),
+            self.name,
             self.label,
         )
     }
@@ -269,6 +276,7 @@ impl Drop for Channel {
 impl Channel {
     pub fn new(
         id: ChannelId,
+        name: &str,
         label: &oak_abi::label::Label,
         runtime_weak: Weak<Runtime>,
     ) -> Arc<Channel> {
@@ -280,6 +288,7 @@ impl Channel {
             reader_count: AtomicU64::new(0),
             waiting_threads: Mutex::new(HashMap::new()),
             label: label.clone(),
+            name: format!("{}({})", name, id),
             runtime_weak,
         })
     }
diff --git a/oak_runtime/src/lib.rs b/oak_runtime/src/lib.rs
index b1a47b2d24..f12665be89 100644
--- a/oak_runtime/src/lib.rs
+++ b/oak_runtime/src/lib.rs
@@ -746,6 +746,7 @@ impl Runtime {
     fn channel_create(
         self: &Arc<Self>,
         node_id: NodeId,
+        name: &str,
         label: &Label,
     ) -> Result<(oak_abi::Handle, oak_abi::Handle), OakStatus> {
         if self.is_terminating() {
@@ -762,7 +763,7 @@ impl Runtime {
 
         // First get a pair of `ChannelHalf` objects.
         let channel_id = self.next_channel_id.fetch_add(1, SeqCst);
-        let channel = Channel::new(channel_id, label, Arc::downgrade(&self));
+        let channel = Channel::new(channel_id, name, label, Arc::downgrade(&self));
         let write_half = ChannelHalf::new(channel.clone(), ChannelHalfDirection::Write);
         let read_half = ChannelHalf::new(channel, ChannelHalfDirection::Read);
         trace!(
@@ -777,6 +778,7 @@ impl Runtime {
         // channel.
         self.introspection_event(EventDetails::ChannelCreated(ChannelCreated {
             channel_id,
+            name: name.to_owned(),
             label: Some(label.clone()),
         }));
 
diff --git a/oak_runtime/src/node/grpc/server/mod.rs b/oak_runtime/src/node/grpc/server/mod.rs
index 4a482e6730..d36ddceaad 100644
--- a/oak_runtime/src/node/grpc/server/mod.rs
+++ b/oak_runtime/src/node/grpc/server/mod.rs
@@ -425,13 +425,15 @@ impl GrpcInvocationHandler {
 
         // The channel containing the request is created with the label specified by the caller.
         // This will fail if the label has a non-empty integrity component.
-        let (request_writer, request_reader) =
-            self.runtime.channel_create(&label).map_err(|err| {
+        let (request_writer, request_reader) = self
+            .runtime
+            .channel_create("gRPC request", &label)
+            .map_err(|err| {
                 warn!("could not create gRPC request channel: {:?}", err);
             })?;
         let (response_writer, response_reader) = self
             .runtime
-            .channel_create(&Label::public_untrusted())
+            .channel_create("gRPC response", &Label::public_untrusted())
             .map_err(|err| {
                 warn!("could not create gRPC response channel: {:?}", err);
             })?;
diff --git a/oak_runtime/src/node/http/mod.rs b/oak_runtime/src/node/http/mod.rs
index e6212fc1aa..aadc52d153 100644
--- a/oak_runtime/src/node/http/mod.rs
+++ b/oak_runtime/src/node/http/mod.rs
@@ -358,11 +358,13 @@ impl Pipe {
         // Create a channel for passing HTTP requests to the Oak node. This channel is created with
         // the label specified by the caller. This will fail if the label has a non-empty
         // integrity component.
-        let (request_writer, request_reader) = runtime.channel_create(&label).map_err(|err| {
-            warn!("could not create HTTP request channel: {:?}", err);
-        })?;
+        let (request_writer, request_reader) = runtime
+            .channel_create("HTTP request", &label)
+            .map_err(|err| {
+                warn!("could not create HTTP request channel: {:?}", err);
+            })?;
         let (response_writer, response_reader) = runtime
-            .channel_create(&Label::public_untrusted())
+            .channel_create("HTTP response", &Label::public_untrusted())
             .map_err(|err| {
                 warn!("could not create HTTP response channel: {:?}", err);
             })?;
diff --git a/oak_runtime/src/node/wasm/mod.rs b/oak_runtime/src/node/wasm/mod.rs
index d6f4bcf657..d38cc1772a 100644
--- a/oak_runtime/src/node/wasm/mod.rs
+++ b/oak_runtime/src/node/wasm/mod.rs
@@ -207,14 +207,18 @@ impl WasmInterface {
         &mut self,
         write_addr: AbiPointer,
         read_addr: AbiPointer,
+        name_ptr: AbiPointer,
+        name_length: AbiPointerOffset,
         label_ptr: AbiPointer,
         label_length: AbiPointerOffset,
     ) -> Result<(), OakStatus> {
         trace!(
-            "{}: channel_create({}, {}, {}, {})",
+            "{}: channel_create({}, {}, {}, {}, {}, {})",
             self.pretty_name,
             write_addr,
             read_addr,
+            name_ptr,
+            name_length,
             label_ptr,
             label_length
         );
@@ -227,6 +231,25 @@ impl WasmInterface {
         self.validate_ptr(write_addr, 8)?;
         self.validate_ptr(read_addr, 8)?;
 
+        let name_bytes = self
+            .get_memory()
+            .get(name_ptr, name_length as usize)
+            .map_err(|err| {
+                error!(
+                    "{}: channel_create(): Unable to read name from guest memory: {:?}",
+                    self.pretty_name, err
+                );
+                OakStatus::ErrInvalidArgs
+            })?;
+
+        let name = String::from_utf8(name_bytes).map_err(|err| {
+            error!(
+                "{}: channel_create(): Unabel to decode name as a string: {:?}",
+                self.pretty_name, err
+            );
+            OakStatus::ErrInvalidArgs
+        })?;
+
         let label_bytes = self
             .get_memory()
             .get(label_ptr, label_length as usize)
@@ -239,13 +262,13 @@ impl WasmInterface {
             })?;
         let label = Label::deserialize(&label_bytes).ok_or_else(|| {
             error!(
-                "{}: channel_create: could not deserialize label",
+                "{}: channel_create(): could not deserialize label",
                 self.pretty_name
             );
             OakStatus::ErrInvalidArgs
         })?;
 
-        let (write_handle, read_handle) = self.runtime.channel_create(&label)?;
+        let (write_handle, read_handle) = self.runtime.channel_create(&name, &label)?;
 
         self.get_memory()
             .set_value(write_addr, write_handle as i64)
@@ -629,6 +652,8 @@ impl wasmi::Externals for WasmInterface {
                 args.nth_checked(1)?,
                 args.nth_checked(2)?,
                 args.nth_checked(3)?,
+                args.nth_checked(4)?,
+                args.nth_checked(5)?,
             )),
             CHANNEL_WRITE => map_host_errors(self.channel_write(
                 args.nth_checked(0)?,
@@ -727,6 +752,8 @@ fn oak_resolve_func(
                 &[
                     ABI_USIZE, // write handle (out)
                     ABI_USIZE, // read handle (out)
+                    ABI_USIZE, // name_buf
+                    ABI_USIZE, // name_len
                     ABI_USIZE, // label_buf
                     ABI_USIZE, // label_len
                 ][..],
diff --git a/oak_runtime/src/proxy.rs b/oak_runtime/src/proxy.rs
index 16051f6e1a..6bcc90912a 100644
--- a/oak_runtime/src/proxy.rs
+++ b/oak_runtime/src/proxy.rs
@@ -141,7 +141,8 @@ impl RuntimeProxy {
 
         // When first starting, we assign the least privileged label to the channel connecting the
         // outside world to the entry point Node.
-        let (write_handle, read_handle) = self.channel_create(&Label::public_untrusted())?;
+        let (write_handle, read_handle) =
+            self.channel_create("Initial", &Label::public_untrusted())?;
         debug!(
             "{:?}: created initial channel ({}, {})",
             self.node_id, write_handle, read_handle,
@@ -215,13 +216,17 @@ impl RuntimeProxy {
     /// See [`Runtime::channel_create`].
     pub fn channel_create(
         &self,
+        name: &str,
         label: &Label,
     ) -> Result<(oak_abi::Handle, oak_abi::Handle), OakStatus> {
-        debug!("{:?}: channel_create({:?})", self.node_id, label);
-        let result = self.runtime.channel_create(self.node_id, label);
         debug!(
-            "{:?}: channel_create({:?}) -> {:?}",
-            self.node_id, label, result
+            "{:?}: channel_create({:?}, {:?})",
+            self.node_id, name, label
+        );
+        let result = self.runtime.channel_create(self.node_id, name, label);
+        debug!(
+            "{:?}: channel_create({:?}, {:?}) -> {:?}",
+            self.node_id, name, label, result
         );
         result
     }
diff --git a/oak_services/Cargo.lock b/oak_services/Cargo.lock
index e1220c7936..1898e23e38 100644
--- a/oak_services/Cargo.lock
+++ b/oak_services/Cargo.lock
@@ -158,6 +158,7 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
diff --git a/oak_sign/Cargo.lock b/oak_sign/Cargo.lock
index ad1c2a5ef8..fb3efc9584 100644
--- a/oak_sign/Cargo.lock
+++ b/oak_sign/Cargo.lock
@@ -24,6 +24,12 @@ version = "1.0.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
 
+[[package]]
+name = "assert_matches"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "695579f0f2520f3774bb40461e5adb066459d4e0af4d59d20175484fb8e9edf1"
+
 [[package]]
 name = "atty"
 version = "0.2.14"
@@ -201,6 +207,7 @@ name = "oak_sign"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "assert_matches",
  "env_logger",
  "hex",
  "log",
diff --git a/oak_sign/Cargo.toml b/oak_sign/Cargo.toml
index d1ee0b2d6a..e7a343e891 100644
--- a/oak_sign/Cargo.toml
+++ b/oak_sign/Cargo.toml
@@ -17,6 +17,7 @@ path = "src/main.rs"
 
 [dependencies]
 anyhow = "*"
+assert_matches = "*"
 env_logger = "*"
 hex = "*"
 log = "*"
diff --git a/oak_sign/src/lib.rs b/oak_sign/src/lib.rs
index 8386b702fc..50fe4a66ae 100644
--- a/oak_sign/src/lib.rs
+++ b/oak_sign/src/lib.rs
@@ -18,7 +18,7 @@ use anyhow::Context;
 use pem::Pem;
 use ring::{
     rand,
-    signature::{self, Ed25519KeyPair, KeyPair},
+    signature::{self, Ed25519KeyPair},
 };
 use std::{
     collections::HashMap,
@@ -31,39 +31,70 @@ pub const PUBLIC_KEY_TAG: &str = "PUBLIC KEY";
 pub const SIGNATURE_TAG: &str = "SIGNATURE";
 pub const HASH_TAG: &str = "HASH";
 
-/// Convenience struct that encapsulates `Ed25519KeyPair`.
-/// Named `KeyBundle` since it uses the `ring::signature::KeyPair` trait.
-pub struct KeyBundle {
-    private_key: Vec<u8>,
+/// Convenience struct that encapsulates `ring::signature::Ed25519KeyPair`.
+#[derive(Debug)]
+pub struct KeyPair {
+    /// PKCS#8 v2 encoded private key and public key.
+    /// https://tools.ietf.org/html/rfc5958
+    ///
+    /// The encoded version is kept because a parsed `key_pair` doesn't contain a method for
+    /// extracting private key bytes.
+    /// https://briansmith.org/rustdoc/ring/signature/struct.Ed25519KeyPair.html
+    pkcs8: Vec<u8>,
+    /// Parsed PKCS#8 v2 key pair representation.
     key_pair: Ed25519KeyPair,
 }
 
-impl KeyBundle {
+impl Clone for KeyPair {
+    fn clone(&self) -> Self {
+        // The `clone` function is implemented as re-parsing, because `Ed25519KeyPair` doesn't
+        // implement `Clone` and all of its fields are private.
+        KeyPair::parse(&self.pkcs8).expect("Couldn't clone key pair")
+    }
+}
+
+impl PartialEq for KeyPair {
+    fn eq(&self, other: &Self) -> bool {
+        // Currently only comparing that the encoded version is the same, since `Ed25519KeyPair`
+        // doesn't implement `Eq` and all of its fields are private.
+        // https://github.com/briansmith/ring/blob/8d4e283c16f335166b0e969ccc321acf0de39c0b/src/ec/curve25519/ed25519/signing.rs#L27-L37
+        self.pkcs8 == other.pkcs8
+    }
+}
+
+impl Eq for KeyPair {}
+
+impl KeyPair {
     /// Generates a Ed25519 key pair.
-    pub fn generate() -> anyhow::Result<KeyBundle> {
+    pub fn generate() -> anyhow::Result<KeyPair> {
         let rng = rand::SystemRandom::new();
         let private_key = Ed25519KeyPair::generate_pkcs8(&rng)
             .ok()
             .context("Couldn't generate key pair")?;
-        KeyBundle::parse(private_key.as_ref())
+        KeyPair::parse(private_key.as_ref())
     }
 
-    /// Parses a Ed25519 key pair from bytes.
-    pub fn parse(private_key: &[u8]) -> anyhow::Result<KeyBundle> {
-        let key_pair = Ed25519KeyPair::from_pkcs8(private_key)
+    /// Parses a Ed25519 key pair from a PKCS#8 v2 encoded `pkcs8_key_pair`.
+    pub fn parse(pkcs8_key_pair: &[u8]) -> anyhow::Result<KeyPair> {
+        let key_pair = Ed25519KeyPair::from_pkcs8(pkcs8_key_pair)
             .ok()
             .context("Couldn't parse generated key pair")?;
         Ok(Self {
-            private_key: private_key.to_vec(),
+            pkcs8: pkcs8_key_pair.to_vec(),
             key_pair,
         })
     }
 
-    pub fn private_key(&self) -> Vec<u8> {
-        self.private_key.to_vec()
+    /// Returns a PKCS#8 v2 encoded key pair.
+    pub fn pkcs8_key_pair(&self) -> Vec<u8> {
+        self.pkcs8.to_vec()
     }
 
-    pub fn public_key(&self) -> Vec<u8> {
+    /// Returns a PKCS#8 v2 encoded public key.
+    pub fn pkcs8_public_key(&self) -> Vec<u8> {
+        // Trait `ring::signature::KeyPair` that contains `public_key` function is included locally
+        // in this function because it conflicts with [`KeyPair`].
+        use ring::signature::KeyPair;
         self.key_pair.public_key().as_ref().to_vec()
     }
 
@@ -72,7 +103,7 @@ impl KeyBundle {
     }
 }
 
-#[derive(Default, Clone)]
+#[derive(Clone, Debug, Default)]
 pub struct SignatureBundle {
     pub public_key: Vec<u8>,
     pub signed_hash: Vec<u8>,
@@ -81,10 +112,10 @@ pub struct SignatureBundle {
 
 impl SignatureBundle {
     /// Signs a SHA-256 hash of the `input` using `private_key`.
-    pub fn create(input: &[u8], key_pair: &KeyBundle) -> anyhow::Result<SignatureBundle> {
+    pub fn create(input: &[u8], key_pair: &KeyPair) -> anyhow::Result<SignatureBundle> {
         let hash = get_sha256(input);
         Ok(SignatureBundle {
-            public_key: key_pair.public_key(),
+            public_key: key_pair.pkcs8_public_key(),
             signed_hash: key_pair.sign(&hash),
             hash,
         })
diff --git a/oak_sign/src/main.rs b/oak_sign/src/main.rs
index 4a2c7d9f6d..5e3e36c761 100644
--- a/oak_sign/src/main.rs
+++ b/oak_sign/src/main.rs
@@ -39,10 +39,13 @@
 use anyhow::{anyhow, Context};
 use log::info;
 use oak_sign::{
-    read_pem_file, write_pem_file, KeyBundle, SignatureBundle, PRIVATE_KEY_TAG, PUBLIC_KEY_TAG,
+    read_pem_file, write_pem_file, KeyPair, SignatureBundle, PRIVATE_KEY_TAG, PUBLIC_KEY_TAG,
 };
 use structopt::StructOpt;
 
+#[cfg(test)]
+mod tests;
+
 /// Command line options for `oak_sign`.
 #[derive(StructOpt, Clone)]
 #[structopt(about = "Oak Sign")]
@@ -99,14 +102,22 @@ fn main() -> anyhow::Result<()> {
 
     match opt.cmd {
         Command::Generate(ref opt) => {
-            let key_pair = KeyBundle::generate()?;
-            write_pem_file(&opt.private_key, PRIVATE_KEY_TAG, &key_pair.private_key())?;
-            write_pem_file(&opt.public_key, PUBLIC_KEY_TAG, &key_pair.public_key())?;
+            let key_pair = KeyPair::generate()?;
+            write_pem_file(
+                &opt.private_key,
+                PRIVATE_KEY_TAG,
+                &key_pair.pkcs8_key_pair(),
+            )?;
+            write_pem_file(
+                &opt.public_key,
+                PUBLIC_KEY_TAG,
+                &key_pair.pkcs8_public_key(),
+            )?;
             info!("Key pair generated successfully");
         }
         Command::Sign(ref opt) => {
             let private_key = read_pem_file(&opt.private_key)?;
-            let key_pair = KeyBundle::parse(&private_key)?;
+            let key_pair = KeyPair::parse(&private_key)?;
             let input = match (&opt.input_file, &opt.input_string) {
                 (Some(input_file), None) => std::fs::read(&input_file)
                     .with_context(|| format!("Couldn't read input file {}", &input_file)),
diff --git a/proto/introspection_events.proto b/proto/introspection_events.proto
index 9f03072eb4..bc2a9459c7 100644
--- a/proto/introspection_events.proto
+++ b/proto/introspection_events.proto
@@ -63,7 +63,9 @@ message NodeDestroyed {
 message ChannelCreated {
   uint64 channel_id = 1 [jstype = JS_STRING];
 
-  oak.label.Label label = 2;
+  string name = 2;
+
+  oak.label.Label label = 3;
 }
 
 message ChannelDestroyed {
diff --git a/sdk/Cargo.lock b/sdk/Cargo.lock
index d53646fd70..758ca2c93b 100644
--- a/sdk/Cargo.lock
+++ b/sdk/Cargo.lock
@@ -24,6 +24,12 @@ version = "1.0.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
 
+[[package]]
+name = "assert_matches"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "695579f0f2520f3774bb40461e5adb066459d4e0af4d59d20175484fb8e9edf1"
+
 [[package]]
 name = "async-stream"
 version = "0.2.1"
@@ -814,6 +820,7 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
@@ -883,6 +890,7 @@ name = "oak_sign"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "assert_matches",
  "env_logger",
  "hex",
  "log",
diff --git a/sdk/rust/oak/src/grpc/client.rs b/sdk/rust/oak/src/grpc/client.rs
index 461cab06ee..53ebe98386 100644
--- a/sdk/rust/oak/src/grpc/client.rs
+++ b/sdk/rust/oak/src/grpc/client.rs
@@ -40,7 +40,7 @@ impl Client {
     /// The provided [`Label`] specifies the label for the newly created Node and Channel.
     pub fn new_with_label(config: &NodeConfiguration, label: &Label) -> Option<Client> {
         let (invocation_sender, invocation_receiver) =
-            crate::io::channel_create(label).expect("failed to create channel");
+            crate::io::channel_create("gRPC invocation", label).expect("failed to create channel");
         let status = crate::node_create(config, label, invocation_receiver.handle);
         invocation_receiver
             .close()
diff --git a/sdk/rust/oak/src/grpc/mod.rs b/sdk/rust/oak/src/grpc/mod.rs
index 78b8167088..923e29a080 100644
--- a/sdk/rust/oak/src/grpc/mod.rs
+++ b/sdk/rust/oak/src/grpc/mod.rs
@@ -236,7 +236,7 @@ where
 {
     // Create a new channel for request message delivery.
     let (req_sender, req_receiver) =
-        crate::io::channel_create::<GrpcRequest>(&Label::public_untrusted())
+        crate::io::channel_create::<GrpcRequest>("gRPC request", &Label::public_untrusted())
             .expect("failed to create channel");
 
     // Put the request in a GrpcRequest wrapper and send it into the request
@@ -248,7 +248,7 @@ where
 
     // Create a new channel for responses to arrive on.
     let (rsp_sender, rsp_receiver) =
-        crate::io::channel_create::<GrpcResponse>(&Label::public_untrusted())
+        crate::io::channel_create::<GrpcResponse>("gRPC response", &Label::public_untrusted())
             .expect("failed to create channel");
 
     // Build an Invocation holding the two channels and send it down the
diff --git a/sdk/rust/oak/src/grpc/server.rs b/sdk/rust/oak/src/grpc/server.rs
index 920528ae38..f9773ff3b3 100644
--- a/sdk/rust/oak/src/grpc/server.rs
+++ b/sdk/rust/oak/src/grpc/server.rs
@@ -32,7 +32,7 @@ use oak_abi::label::Label;
 pub fn init(address: &str) -> Result<Receiver<Invocation>, OakStatus> {
     // Create a channel and pass the read half to a new gRPC server pseudo-Node.
     let (init_sender, init_receiver) =
-        crate::io::channel_create::<GrpcInvocationSender>(&Label::public_untrusted())
+        crate::io::channel_create::<GrpcInvocationSender>("gRPC init", &Label::public_untrusted())
             .expect("Couldn't create init channel to gRPC server pseudo-node");
     let config = crate::node_config::grpc_server(address);
     crate::node_create(&config, &Label::public_untrusted(), init_receiver.handle)?;
@@ -42,7 +42,7 @@ pub fn init(address: &str) -> Result<Receiver<Invocation>, OakStatus> {
 
     // Create a separate channel for receiving invocations and pass it to a gRPC pseudo-Node.
     let (invocation_sender, invocation_receiver) =
-        crate::io::channel_create::<Invocation>(&Label::public_untrusted())
+        crate::io::channel_create::<Invocation>("gRPC invocation", &Label::public_untrusted())
             .expect("Couldn't create gRPC invocation channel");
     let local_invocation_sender = invocation_sender.handle;
 
diff --git a/sdk/rust/oak/src/http/mod.rs b/sdk/rust/oak/src/http/mod.rs
index b52c7c0283..f82685da1e 100644
--- a/sdk/rust/oak/src/http/mod.rs
+++ b/sdk/rust/oak/src/http/mod.rs
@@ -81,7 +81,7 @@ pub fn init(address: &str) -> Result<Receiver<Invocation>, OakStatus> {
 
     // Create a channel and pass the read half to a new http server pseudo-Node.
     let (init_sender, init_receiver) =
-        crate::io::channel_create::<HttpInvocationSender>(&Label::public_untrusted())
+        crate::io::channel_create::<HttpInvocationSender>("HTTP init", &Label::public_untrusted())
             .expect("Couldn't create init channel to HTTP server pseudo-node");
     crate::node_create(&config, &Label::public_untrusted(), init_receiver.handle)?;
     init_receiver
@@ -90,7 +90,7 @@ pub fn init(address: &str) -> Result<Receiver<Invocation>, OakStatus> {
 
     // Create a separate channel for receiving invocations and pass it to an HTTP pseudo-Node.
     let (invocation_sender, invocation_receiver) =
-        crate::io::channel_create::<Invocation>(&Label::public_untrusted())
+        crate::io::channel_create::<Invocation>("HTTP invocation", &Label::public_untrusted())
             .expect("Couldn't create HTTP invocation channel");
 
     let http_server_init = HttpInvocationSender {
diff --git a/sdk/rust/oak/src/io/mod.rs b/sdk/rust/oak/src/io/mod.rs
index 6c6de3fb94..bd445a9262 100644
--- a/sdk/rust/oak/src/io/mod.rs
+++ b/sdk/rust/oak/src/io/mod.rs
@@ -29,9 +29,10 @@ pub use sender::SenderExt;
 
 /// Create a new channel for transmission of `Encodable` and `Decodable` types.
 pub fn channel_create<T: Encodable + Decodable>(
+    name: &str,
     label: &Label,
 ) -> Result<(Sender<T>, Receiver<T>), OakStatus> {
-    let (wh, rh) = crate::channel_create(label)?;
+    let (wh, rh) = crate::channel_create(name, label)?;
     Ok((Sender::<T>::new(wh), Receiver::<T>::new(rh)))
 }
 
diff --git a/sdk/rust/oak/src/io/receiver.rs b/sdk/rust/oak/src/io/receiver.rs
index a40c0c3573..d22bec0a35 100644
--- a/sdk/rust/oak/src/io/receiver.rs
+++ b/sdk/rust/oak/src/io/receiver.rs
@@ -18,10 +18,10 @@ use crate::{
     io::{Decodable, Receiver},
     ChannelReadStatus, Label, OakError, OakStatus,
 };
-use log::error;
+use log::{debug, error, info, trace, warn};
 
 /// SDK-specific functionality provided by a receiver.
-pub trait ReceiverExt<T> {
+pub trait ReceiverExt<T: Decodable> {
     /// Closes the underlying channel used by the receiver.
     fn close(&self) -> Result<(), OakStatus>;
 
@@ -39,6 +39,81 @@ pub trait ReceiverExt<T> {
     /// Returns [`ChannelReadStatus`] of the wrapped handle, or `Err(OakStatus::ErrTerminated)` if
     /// the Oak Runtime is terminating.
     fn wait(&self) -> Result<ChannelReadStatus, OakStatus>;
+
+    /// Consumes the receiver and returns an iterator that will block waiting for messages. It will
+    /// return None when the node terminates, or a non-transient error occurs when reading from the
+    /// channel. Transient errors will be silently ignored by this iterator, including cases in
+    /// which the incoming message cannot be decoded correctly.
+    ///
+    /// Also see [`std::sync::mpsc::Receiver::iter`].
+    fn iter(self) -> ReceiverIterator<T>;
+}
+
+/// Blocking iterator over incoming messages, created via [`ReceiverExt::iter`].
+pub struct ReceiverIterator<T: Decodable> {
+    inner: Receiver<T>,
+}
+
+impl<T: Decodable> Iterator for ReceiverIterator<T> {
+    type Item = T;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let receiver = &self.inner;
+        loop {
+            match receiver.wait() {
+                Err(status) => {
+                    use crate::OakStatus::*;
+                    match status {
+                        ErrTerminated => {
+                            info!(
+                            "{:?}: received node termination status: {:?}; terminating iterator",
+                            receiver, status
+                        );
+                            return None;
+                        }
+                        _ => {
+                            error!(
+                            "{:?}: received status: {:?}; but `Receiver::wait` should never return an error other than `ErrTerminated`",
+                            receiver, status
+                        );
+                            return None;
+                        }
+                    }
+                }
+                Ok(status) => match status {
+                    ChannelReadStatus::ReadReady => trace!("{:?}: wait over", receiver),
+                    ChannelReadStatus::Orphaned
+                    | ChannelReadStatus::PermissionDenied
+                    | ChannelReadStatus::InvalidChannel => {
+                        warn!(
+                        "{:?}: received invalid channel read status: {:?}; terminating iterator",
+                        receiver, status
+                    );
+                        return None;
+                    }
+                    ChannelReadStatus::NotReady => {
+                        error!(
+                        "{:?}: received `ChannelReadStatus::NotReady`, which should never be returned from `Receiver::wait`.",
+                        receiver);
+                        return None;
+                    }
+                },
+            }
+            match receiver.try_receive() {
+                Ok(m) => {
+                    debug!("{:?}: received message", receiver);
+                    return Some(m);
+                }
+                Err(err) => {
+                    // Log error and continue with the next iteration.
+                    // We do this because the error may have been caused by an invalid or malformed
+                    // message, and we don't want to terminate the iterator early if that's the
+                    // case.
+                    error!("{:?}: error receiving message: {}", receiver, err);
+                }
+            }
+        }
+    }
 }
 
 impl<T: Decodable> ReceiverExt<T> for Receiver<T> {
@@ -96,4 +171,8 @@ impl<T: Decodable> ReceiverExt<T> for Receiver<T> {
             }
         }
     }
+
+    fn iter(self) -> ReceiverIterator<T> {
+        ReceiverIterator { inner: self }
+    }
 }
diff --git a/sdk/rust/oak/src/lib.rs b/sdk/rust/oak/src/lib.rs
index 915b305f5f..c5a5667e9e 100644
--- a/sdk/rust/oak/src/lib.rs
+++ b/sdk/rust/oak/src/lib.rs
@@ -18,8 +18,7 @@
 //! underlying Oak platform functionality.
 
 use byteorder::WriteBytesExt;
-use io::ReceiverExt;
-use log::{debug, error, info, trace, warn};
+use log::{debug, error, warn};
 use oak_abi::proto::oak::application::NodeConfiguration;
 use prost::Message;
 
@@ -231,7 +230,7 @@ pub fn channel_write(half: WriteHandle, buf: &[u8], handles: &[Handle]) -> Resul
 ///
 /// On success, returns [`WriteHandle`] and a [`ReadHandle`] values for the
 /// write and read halves (respectively).
-pub fn channel_create(label: &Label) -> Result<(WriteHandle, ReadHandle), OakStatus> {
+pub fn channel_create(name: &str, label: &Label) -> Result<(WriteHandle, ReadHandle), OakStatus> {
     let mut write = WriteHandle {
         handle: crate::handle::invalid(),
     };
@@ -239,10 +238,13 @@ pub fn channel_create(label: &Label) -> Result<(WriteHandle, ReadHandle), OakSta
         handle: crate::handle::invalid(),
     };
     let label_bytes = label.serialize();
+    let name_bytes = name.as_bytes();
     let status = unsafe {
         oak_abi::channel_create(
             &mut write.handle as *mut u64,
             &mut read.handle as *mut u64,
+            name_bytes.as_ptr(),
+            name_bytes.len(),
             label_bytes.as_ptr(),
             label_bytes.len(),
         )
@@ -411,92 +413,31 @@ pub fn set_panic_hook() {
 
 /// Trait implemented by Oak Nodes (or parts thereof) that operate on commands.
 ///
-/// It has a single method for handling commands, which are [`Decodable`](crate::io::Decodable)
-/// objects that are received via the single incoming channel handle which is passed in at Node
-/// creation time. The return value is only used for logging in case of error.
-pub trait CommandHandler<T: crate::io::Decodable> {
+/// It has a single method for handling commands, which are usually received via the single incoming
+/// channel handle which is passed in at Node creation time, or derived from such stream.
+pub trait CommandHandler<T> {
+    /// Handles a single command instance.
+    ///
+    /// The return value is only used for logging in case of error.
     fn handle_command(&mut self, command: T) -> anyhow::Result<()>;
 }
 
-/// Run a command loop on the provided [`CommandHandler`]:
+/// Runs a command loop on the provided [`CommandHandler`]:
 ///
-/// - wait for new messages on the provided channel `in_channel`
-/// - if the runtime signals that the Node was terminated while waiting, then exit the event loop
-/// - otherwise, read the available message via the provided channel handle
-/// - decode the message from (bytes + handles) to the specified command type `T`
-/// - pass the typed command object to the `Node::handle_command` method of the `node`, which
+/// - waits for new messages from the provided iterator
+/// - passes the typed command object to the `Node::handle_command` method of the `node`, which
 ///   executes a single iteration of the loop
 ///
 /// Note the loop is only interrupted if the Node is terminated while waiting. Other errors are just
 /// logged, and the loop continues with the next iteration.
-pub fn run_command_loop<
-    T: crate::io::Decodable,
-    N: CommandHandler<T>,
-    R: Into<crate::io::Receiver<T>>,
->(
+pub fn run_command_loop<T, N: CommandHandler<T>, R: Iterator<Item = T>>(
     mut command_handler: N,
-    receiver: R,
+    command_iterator: R,
 ) {
-    let receiver = receiver.into();
-    if !crate::handle::is_valid(receiver.handle.handle) {
-        error!("{:?}: invalid input handle", receiver);
-        return;
-    }
-    info!("{:?}: starting command loop", receiver);
-    loop {
-        // First wait until a message is available. If the Node was terminated while waiting, this
-        // will return `ErrTerminated`, which indicates that the event loop should be terminated.
-        // For any other error raised while waiting is logged, we try and determine whether it is
-        // transient or not, and then continue or terminate the event loop, respectively.
-        match receiver.wait() {
-            Err(status) => {
-                use crate::OakStatus::*;
-                match status {
-                    ErrTerminated => {
-                        info!(
-                            "{:?}: received termination status: {:?}; terminating event loop",
-                            receiver, status
-                        );
-                        return;
-                    }
-                    _ => {
-                        error!(
-                            "{:?}: received status: {:?}; but `Receiver::wait` never returns an error other than `ErrTerminated`",
-                            receiver, status
-                        );
-                        return;
-                    }
-                }
-            }
-            Ok(status) => match status {
-                ChannelReadStatus::ReadReady => trace!("{:?}: wait over", receiver),
-                ChannelReadStatus::Orphaned
-                | ChannelReadStatus::PermissionDenied
-                | ChannelReadStatus::InvalidChannel => {
-                    warn!(
-                        "{:?}: received invalid channel read status: {:?}; terminating event loop",
-                        receiver, status
-                    );
-                    return;
-                }
-                ChannelReadStatus::NotReady => {
-                    error!(
-                        "{:?}: received `ChannelReadStatus::NotReady`, which should never be returned from `Receiver::wait`.",
-                        receiver);
-                    return;
-                }
-            },
-        }
-        match receiver.try_receive() {
-            Ok(command) => {
-                info!("{:?}: received command", receiver);
-                if let Err(err) = command_handler.handle_command(command) {
-                    error!("{:?}: error handling command: {}", receiver, err);
-                }
-            }
-            Err(err) => {
-                error!("{:?}: error receiving command: {}", receiver, err);
-            }
+    for command in command_iterator {
+        debug!("received command");
+        if let Err(err) = command_handler.handle_command(command) {
+            error!("error handling command: {}", err);
         }
     }
 }
@@ -536,7 +477,7 @@ pub fn run_command_loop<
 ///     let grpc_channel = oak::grpc::server::init(&grpc_server_listen_address)
 ///         .expect("could not create gRPC server pseudo-node");
 ///     let dispatcher = DummyNode::default();
-///     oak::run_command_loop(dispatcher, grpc_channel);
+///     oak::run_command_loop(dispatcher, grpc_channel.iter());
 /// });
 ///
 /// # fn main() {}
@@ -550,6 +491,7 @@ pub fn run_command_loop<
 /// expression too:
 ///
 /// ```
+/// use oak::io::ReceiverExt;
 /// use oak_abi::proto::oak::application::ConfigMap;
 ///
 /// # fn init_all_the_things() {}
@@ -568,7 +510,7 @@ pub fn run_command_loop<
 ///     let dispatcher = DummyNode::default();
 ///     let grpc_channel = oak::grpc::server::init("[::]:8080")
 ///         .expect("could not create gRPC server pseudo-node");
-///     oak::run_command_loop(dispatcher, grpc_channel);
+///     oak::run_command_loop(dispatcher, grpc_channel.iter());
 /// });
 /// #
 /// # fn main() {}
diff --git a/sdk/rust/oak/src/logger/mod.rs b/sdk/rust/oak/src/logger/mod.rs
index 77e4bfb8ff..073c262af1 100644
--- a/sdk/rust/oak/src/logger/mod.rs
+++ b/sdk/rust/oak/src/logger/mod.rs
@@ -84,8 +84,8 @@ pub fn init_default() {
 /// An error is returned if a logger has already been set.
 pub fn init(level: Level) -> Result<(), SetLoggerError> {
     // Create a channel and pass the read half to a fresh logging Node.
-    let (write_handle, read_handle) =
-        crate::channel_create(&Label::public_untrusted()).expect("could not create channel");
+    let (write_handle, read_handle) = crate::channel_create("Logger", &Label::public_untrusted())
+        .expect("could not create channel");
     crate::node_create(
         &crate::node_config::log(),
         &Label::public_untrusted(),
diff --git a/sdk/rust/oak/src/stubs.rs b/sdk/rust/oak/src/stubs.rs
index 686429dd9f..e84ae81d17 100644
--- a/sdk/rust/oak/src/stubs.rs
+++ b/sdk/rust/oak/src/stubs.rs
@@ -64,7 +64,14 @@ pub extern "C" fn channel_write(
     panic!("stub function invoked!");
 }
 #[no_mangle]
-pub extern "C" fn channel_create(_write: *mut u64, _read: *mut u64) -> u32 {
+pub extern "C" fn channel_create(
+    _write: *mut u64,
+    _read: *mut u64,
+    _name_buf: *const u8,
+    _name_size: usize,
+    _label_buf: *const u8,
+    _label_size: usize,
+) -> u32 {
     panic!("stub function invoked!");
 }
 #[no_mangle]


Test patch:
diff --git a/examples/abitest/module_0/rust/src/lib.rs b/examples/abitest/module_0/rust/src/lib.rs
index 69b62a8533..f220a2b9a1 100644
--- a/examples/abitest/module_0/rust/src/lib.rs
+++ b/examples/abitest/module_0/rust/src/lib.rs
@@ -25,7 +25,7 @@ use log::{debug, error, info, trace, warn};
 use oak::{
     grpc,
     handle::is_valid,
-    io::{ReceiverExt, SenderExt},
+    io::{Receiver, ReceiverExt, SenderExt},
     ChannelReadStatus, OakError, OakStatus,
 };
 use oak_abi::{
@@ -74,7 +74,7 @@ impl FrontendNode {
             lose_channels();
 
             // Second, start an ephemeral Node which also loses channels.
-            let (wh, rh) = oak::channel_create(&Label::public_untrusted()).unwrap();
+            let (wh, rh) = oak::channel_create("Initial", &Label::public_untrusted()).unwrap();
             oak::node_create(
                 &oak::node_config::wasm(FRONTEND_MODULE_NAME, "channel_loser"),
                 &Label::public_untrusted(),
@@ -105,7 +105,8 @@ impl FrontendNode {
         let mut backend_in = Vec::with_capacity(BACKEND_COUNT);
         for i in 0..BACKEND_COUNT {
             let (write_handle, read_handle) =
-                oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+                oak::channel_create("Initial", &Label::public_untrusted())
+                    .expect("could not create channel");
             oak::node_create(
                 &oak::node_config::wasm(BACKEND_MODULE_NAME, BACKEND_ENTRYPOINT_NAME),
                 &Label::public_untrusted(),
@@ -118,7 +119,8 @@ impl FrontendNode {
             // Create a back channel, and pass the write half to the backend
             // as the first message on the outbound channel.
             let (write_handle, read_handle) =
-                oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+                oak::channel_create("Back", &Label::public_untrusted())
+                    .expect("could not create channel");
             oak::channel_write(backend_out[i], &[], &[write_handle.handle])
                 .expect("could not write to channel");
             oak::channel_close(write_handle.handle).expect("could not close channel");
@@ -162,7 +164,7 @@ pub extern "C" fn frontend_oak_main(_in_handle: u64) {
         let dispatcher = OakAbiTestServiceDispatcher::new(node);
         let grpc_channel =
             oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-        oak::run_command_loop(dispatcher, grpc_channel);
+        oak::run_command_loop(dispatcher, grpc_channel.iter());
     });
 }
 
@@ -574,11 +576,14 @@ unsafe fn invalid_raw_offset() -> *mut u64 {
 fn channel_create_raw() -> (u64, u64, u32) {
     let mut w = 0u64;
     let mut r = 0u64;
+    let name_bytes = "Raw channel".as_bytes();
     let label_bytes = Label::public_untrusted().serialize();
     let result = unsafe {
         oak_abi::channel_create(
             &mut w as *mut u64,
             &mut r as *mut u64,
+            name_bytes.as_ptr(),
+            name_bytes.len(),
             label_bytes.as_ptr(),
             label_bytes.len(),
         )
@@ -590,11 +595,17 @@ impl FrontendNode {
     fn test_channel_create_raw(&mut self) -> TestResult {
         let mut write = 0u64;
         let mut read = 0u64;
+        let name_bytes = "Raw channel".as_bytes();
         let label_bytes = Label::public_untrusted().serialize();
+        let invalid_string_bytes = vec![240];
+        expect_eq!(false, std::str::from_utf8(&invalid_string_bytes).is_ok());
+
         expect_eq!(OakStatus::ErrInvalidArgs as u32, unsafe {
             oak_abi::channel_create(
                 invalid_raw_offset(),
                 &mut read as *mut u64,
+                name_bytes.as_ptr(),
+                name_bytes.len(),
                 label_bytes.as_ptr(),
                 label_bytes.len(),
             )
@@ -603,6 +614,28 @@ impl FrontendNode {
             oak_abi::channel_create(
                 &mut write as *mut u64,
                 invalid_raw_offset(),
+                name_bytes.as_ptr(),
+                name_bytes.len(),
+                label_bytes.as_ptr(),
+                label_bytes.len(),
+            )
+        });
+        expect_eq!(OakStatus::ErrInvalidArgs as u32, unsafe {
+            oak_abi::channel_create(
+                &mut write as *mut u64,
+                &mut read as *mut u64,
+                invalid_raw_offset() as *const u8,
+                1,
+                label_bytes.as_ptr(),
+                label_bytes.len(),
+            )
+        });
+        expect_eq!(OakStatus::ErrInvalidArgs as u32, unsafe {
+            oak_abi::channel_create(
+                &mut write as *mut u64,
+                &mut read as *mut u64,
+                invalid_string_bytes.as_ptr(),
+                invalid_string_bytes.len(),
                 label_bytes.as_ptr(),
                 label_bytes.len(),
             )
@@ -614,7 +647,7 @@ impl FrontendNode {
         let mut handles = Vec::<(oak::WriteHandle, oak::ReadHandle)>::new();
         const CHANNEL_COUNT: usize = 50;
         for _ in 0..CHANNEL_COUNT {
-            match oak::channel_create(&Label::public_untrusted()) {
+            match oak::channel_create("Test", &Label::public_untrusted()) {
                 Ok(pair) => handles.push(pair),
                 Err(status) => {
                     return Err(Box::new(OakError::OakStatus(status)));
@@ -644,14 +677,14 @@ impl FrontendNode {
     }
 
     fn test_channel_close(&mut self) -> TestResult {
-        let (w, r) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (w, r) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
         expect_eq!(Ok(()), oak::channel_close(w.handle));
         expect_eq!(Ok(()), oak::channel_close(r.handle));
         expect_eq!(Err(OakStatus::ErrBadHandle), oak::channel_close(w.handle));
         expect_eq!(Err(OakStatus::ErrBadHandle), oak::channel_close(9_999_999));
 
         // Can close ends in either order.
-        let (w, r) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (w, r) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
         expect_eq!(Ok(()), oak::channel_close(r.handle));
         expect_eq!(Ok(()), oak::channel_close(w.handle));
         Ok(())
@@ -741,7 +774,8 @@ impl FrontendNode {
     }
 
     fn test_channel_read(&mut self) -> TestResult {
-        let (out_channel, in_channel) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_channel, in_channel) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // No message pending.
         let mut buffer = Vec::<u8>::with_capacity(5);
@@ -839,7 +873,8 @@ impl FrontendNode {
     }
 
     fn test_channel_read_orphan(&mut self) -> TestResult {
-        let (out_channel, in_channel) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_channel, in_channel) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // Write a message to the channel.
         let buf = vec![0x01];
@@ -909,7 +944,8 @@ impl FrontendNode {
     }
 
     fn test_channel_write(&mut self) -> TestResult {
-        let (out_channel, in_channel) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_channel, in_channel) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // Empty message.
         let empty = vec![];
@@ -936,7 +972,8 @@ impl FrontendNode {
     }
 
     fn test_channel_write_handle(&mut self) -> TestResult {
-        let (out_channel, in_channel) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_channel, in_channel) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // Send a single handle.
         let empty = vec![];
@@ -963,7 +1000,8 @@ impl FrontendNode {
     }
 
     fn test_channel_write_orphan_empty(&mut self) -> TestResult {
-        let (out_channel, in_channel) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_channel, in_channel) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // Close the only read handle for the channel.
         expect_eq!(Ok(()), oak::channel_close(in_channel.handle));
@@ -980,7 +1018,8 @@ impl FrontendNode {
     }
 
     fn test_channel_write_orphan_full(&mut self) -> TestResult {
-        let (out_channel, in_channel) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_channel, in_channel) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // Ensure that there are messages pending on the channel.
         let data = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
@@ -1000,12 +1039,13 @@ impl FrontendNode {
 
     fn test_channel_handle_recovered(&mut self) -> TestResult {
         // Set up a channel with a message in it.
-        let (lost_wh, lost_rh) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (lost_wh, lost_rh) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
         let data = vec![0x08, 0x0c];
         expect_eq!(Ok(()), oak::channel_write(lost_wh, &data, &[]));
 
         // Put a message with handle to the first channel into a second channel.
-        let (holder_wh, holder_rh) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (holder_wh, holder_rh) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
         expect_eq!(
             Ok(()),
             oak::channel_write(holder_wh, &[], &[lost_rh.handle])
@@ -1243,8 +1283,8 @@ impl FrontendNode {
     }
 
     fn test_channel_wait(&mut self) -> TestResult {
-        let (out1, in1) = oak::channel_create(&Label::public_untrusted()).unwrap();
-        let (out2, in2) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out1, in1) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
+        let (out2, in2) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // Waiting on (just) non-read channel handles should fail immediately.
         expect_eq!(
@@ -1337,8 +1377,8 @@ impl FrontendNode {
     fn test_channel_wait_orphan(&mut self) -> TestResult {
         // Use 2 channels so there's always a ready channel to prevent
         // wait_on_channels blocking.
-        let (out1, in1) = oak::channel_create(&Label::public_untrusted()).unwrap();
-        let (out2, in2) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out1, in1) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
+        let (out2, in2) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // Set up pending messages so each channel is read-ready.
         let data = vec![0x01, 0x02, 0x03];
@@ -1489,7 +1529,8 @@ impl FrontendNode {
                 }
             )
         );
-        let (out_handle, in_handle) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_handle, in_handle) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
         expect_eq!(
             Ok(()),
             oak::node_create(
@@ -1520,7 +1561,7 @@ impl FrontendNode {
         };
 
         // Expect valid handles to return correct label.
-        let (out_handle, in_handle) = oak::channel_create(&label).unwrap();
+        let (out_handle, in_handle) = oak::channel_create("Test", &label).unwrap();
         let expected = Ok(label);
         expect_eq!(expected, oak::channel_label_read(in_handle.handle));
         expect_eq!(expected, oak::channel_label_read(out_handle.handle));
@@ -1682,7 +1723,8 @@ impl FrontendNode {
     }
 
     fn test_node_panic(&mut self) -> TestResult {
-        let (out_handle, in_handle) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_handle, in_handle) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
         // Create a Node that panics. We can't see the panic, but the rest
         // of the Application should continue.
         expect_eq!(
@@ -1729,7 +1771,8 @@ impl FrontendNode {
     fn test_channel_handle_reuse(&mut self) -> TestResult {
         // Set up a fresh channel with a pending message so wait_on_channels
         // doesn't block.
-        let (out_handle, in_handle) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_handle, in_handle) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
         let data = vec![0x01, 0x02, 0x03];
         expect_eq!(Ok(()), oak::channel_write(out_handle, &data, &[]));
 
@@ -1775,8 +1818,8 @@ impl FrontendNode {
     fn test_direct_log(&mut self) -> TestResult {
         // Send a message directly to a fresh logging Node (not via the log facade).
         // Include some handles which will be ignored.
-        let (logging_handle, read_handle) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (logging_handle, read_handle) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
         oak::node_create(
             &oak::node_config::log(),
             &Label::public_untrusted(),
@@ -1786,8 +1829,8 @@ impl FrontendNode {
         oak::channel_close(read_handle.handle).expect("could not close channel");
 
         expect!(is_valid(logging_handle.handle));
-        let (out_handle, in_handle) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (out_handle, in_handle) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
 
         oak::channel_write(
             logging_handle,
@@ -1826,7 +1869,8 @@ impl FrontendNode {
         let mut read_handles = Vec::with_capacity(CHANNEL_COUNT);
         let mut write_handles = Vec::with_capacity(CHANNEL_COUNT);
         for _ in 0..CHANNEL_COUNT {
-            let (new_write, new_read) = oak::channel_create(&Label::public_untrusted()).unwrap();
+            let (new_write, new_read) =
+                oak::channel_create("Test", &Label::public_untrusted()).unwrap();
             write_handles.push(new_write);
             read_handles.push(new_read.handle);
         }
@@ -1996,8 +2040,8 @@ impl FrontendNode {
         let config = oak::node_config::grpc_server(ADDITIONAL_TEST_SERVER_ADDR);
         // Rather than passing the newly-created Node a message with a write handle
         // for an invocation channel in it, instead pass it a message with data.
-        let (wh, rh) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
         expect_eq!(
             Ok(()),
             oak::node_create(&config, &Label::public_untrusted(), rh)
@@ -2013,8 +2057,8 @@ impl FrontendNode {
         // Rather than passing the newly-created Node a message with a write handle
         // for an invocation channel in it, instead pass it a message with a single
         // read handle.
-        let (wh, rh) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
         expect_eq!(
             Ok(()),
             oak::node_create(&config, &Label::public_untrusted(), rh)
@@ -2030,8 +2074,8 @@ impl FrontendNode {
         // Rather than passing the newly-created Node a message with a write handle
         // for an invocation channel in it, instead pass it a message with a write
         // handle and a read handle.
-        let (wh, rh) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
         expect_eq!(
             Ok(()),
             oak::node_create(&config, &Label::public_untrusted(), rh)
@@ -2258,8 +2302,8 @@ impl FrontendNode {
 
         // Rather than passing the newly-created Node a message with a write handle
         // for an invocation channel in it, instead pass it a message with data.
-        let (wh, rh) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
         expect_eq!(
             Ok(()),
             oak::node_create(&config, &Label::public_untrusted(), rh)
@@ -2275,8 +2319,8 @@ impl FrontendNode {
         // Rather than passing the newly-created Node a message with a write handle
         // for an invocation channel in it, instead pass it a message with a single
         // read handle.
-        let (wh, rh) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
         expect_eq!(
             Ok(()),
             oak::node_create(&config, &Label::public_untrusted(), rh)
@@ -2292,8 +2336,8 @@ impl FrontendNode {
         // Rather than passing the newly-created Node a message with a write handle
         // for an invocation channel in it, instead pass it a message with a write
         // handle and a read handle.
-        let (wh, rh) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
         expect_eq!(
             Ok(()),
             oak::node_create(&config, &Label::public_untrusted(), rh)
@@ -2359,7 +2403,7 @@ fn from_proto(status: oak::grpc::Status) -> Box<dyn std::error::Error> {
 
 fn lose_channels() {
     // Create a channel holding a message that holds references to itself.
-    let (wh, rh) = oak::channel_create(&Label::public_untrusted()).unwrap();
+    let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
     let data = vec![0x01, 0x02, 0x03];
     oak::channel_write(wh, &data, &[wh.handle, rh.handle]).unwrap();
     // Close both handles so this channel is immediately lost.
@@ -2367,7 +2411,7 @@ fn lose_channels() {
     oak::channel_close(rh.handle).unwrap();
 
     // Create a channel holding a message that holds references to itself.
-    let (wh, rh) = oak::channel_create(&Label::public_untrusted()).unwrap();
+    let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
     let data = vec![0x01, 0x02, 0x03];
     oak::channel_write(wh, &data, &[wh.handle, rh.handle]).unwrap();
     // Keep the write handle open, so this channel will be lost when
@@ -2375,8 +2419,8 @@ fn lose_channels() {
     oak::channel_close(rh.handle).unwrap();
 
     // Create a pair of channels, each holding a message that holds references to the other
-    let (wh_a, rh_a) = oak::channel_create(&Label::public_untrusted()).unwrap();
-    let (wh_b, rh_b) = oak::channel_create(&Label::public_untrusted()).unwrap();
+    let (wh_a, rh_a) = oak::channel_create("Test channel a", &Label::public_untrusted()).unwrap();
+    let (wh_b, rh_b) = oak::channel_create("Test channel b", &Label::public_untrusted()).unwrap();
     oak::channel_write(wh_a, &data, &[wh_b.handle, rh_b.handle]).unwrap();
     oak::channel_write(wh_b, &data, &[wh_a.handle, rh_a.handle]).unwrap();
     // Close all handles so these channels are immediately lost.
@@ -2386,8 +2430,8 @@ fn lose_channels() {
     oak::channel_close(rh_b.handle).unwrap();
 
     // Create a pair of channels, each holding a message that holds references to the other
-    let (wh_a, rh_a) = oak::channel_create(&Label::public_untrusted()).unwrap();
-    let (wh_b, rh_b) = oak::channel_create(&Label::public_untrusted()).unwrap();
+    let (wh_a, rh_a) = oak::channel_create("Test channel a", &Label::public_untrusted()).unwrap();
+    let (wh_b, rh_b) = oak::channel_create("Test channel b", &Label::public_untrusted()).unwrap();
     oak::channel_write(wh_a, &data, &[wh_b.handle, rh_b.handle]).unwrap();
     oak::channel_write(wh_b, &data, &[wh_a.handle, rh_a.handle]).unwrap();
     // Keep the write handles open.
@@ -2411,12 +2455,13 @@ pub extern "C" fn channel_loser(_handle: u64) {
 #[no_mangle]
 pub extern "C" fn http_oak_main(http_channel: u64) {
     oak::logger::init_default();
-    let node = http_server::StaticHttpServer {};
+    let node = http_server::StaticHttpServer;
     oak::run_command_loop(
         node,
-        oak_io::handle::ReadHandle {
+        Receiver::new(oak_io::handle::ReadHandle {
             handle: http_channel,
-        },
+        })
+        .iter(),
     );
 }
 
@@ -2434,7 +2479,8 @@ pub extern "C" fn http_oak_main(http_channel: u64) {
 // the runtime when the final handle is closed.
 fn new_channel_chain(nest_count: u32) -> Result<oak::ReadHandle, Box<dyn std::error::Error>> {
     // Set up a channel with a message in it.
-    let (innermost_wh, innermost_rh) = oak::channel_create(&Label::public_untrusted()).unwrap();
+    let (innermost_wh, innermost_rh) =
+        oak::channel_create("Innermost", &Label::public_untrusted()).unwrap();
     let data = vec![0x08, 0x0c];
     expect_eq!(Ok(()), oak::channel_write(innermost_wh, &data, &[]));
     expect_eq!(Ok(()), oak::channel_close(innermost_wh.handle));
@@ -2442,7 +2488,8 @@ fn new_channel_chain(nest_count: u32) -> Result<oak::ReadHandle, Box<dyn std::er
     let mut inner_rh = innermost_rh;
     for _ in 0..nest_count {
         // Put a message with a handle to the previous channel into a new outer channel.
-        let (outer_wh, outer_rh) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (outer_wh, outer_rh) =
+            oak::channel_create("Outer", &Label::public_untrusted()).unwrap();
         expect_eq!(
             Ok(()),
             oak::channel_write(outer_wh, &[], &[inner_rh.handle])
diff --git a/examples/abitest/module_1/rust/src/lib.rs b/examples/abitest/module_1/rust/src/lib.rs
index e3b86c4c27..3c9144cbb4 100644
--- a/examples/abitest/module_1/rust/src/lib.rs
+++ b/examples/abitest/module_1/rust/src/lib.rs
@@ -117,7 +117,7 @@ fn inner_main(in_handle: u64) -> Result<(), oak::OakStatus> {
             info!("received frontend request: {:?}", internal_req);
 
             // Create a new channel and write the response into it.
-            let (new_write, new_read) = oak::channel_create(&Label::public_untrusted())?;
+            let (new_write, new_read) = oak::channel_create("Reply", &Label::public_untrusted())?;
             let internal_rsp = InternalMessage {
                 msg: internal_req.msg + "xxx",
             };
diff --git a/examples/chat/module/rust/tests/integration_test.rs b/examples/chat/module/rust/tests/integration_test.rs
index ef4cbd8865..3fa7fec3ad 100644
--- a/examples/chat/module/rust/tests/integration_test.rs
+++ b/examples/chat/module/rust/tests/integration_test.rs
@@ -29,10 +29,8 @@ async fn test_chat() {
     let runtime = oak_tests::run_single_module(MODULE_WASM_FILE_NAME, "grpc_oak_main")
         .expect("Unable to configure runtime with test wasm!");
 
-    let room_0_key_pair =
-        oak_sign::KeyBundle::generate().expect("could not generate room key pair");
-    let room_1_key_pair =
-        oak_sign::KeyBundle::generate().expect("could not generate room key pair");
+    let room_0_key_pair = oak_sign::KeyPair::generate().expect("could not generate room key pair");
+    let room_1_key_pair = oak_sign::KeyPair::generate().expect("could not generate room key pair");
 
     let mut alice = Chatter::new(&room_0_key_pair, "Alice").await;
     let mut alice_stream = alice.subscribe().await;
@@ -137,19 +135,19 @@ async fn test_chat() {
 
 struct Chatter<'a> {
     pub client: ChatClient<tonic::transport::Channel>,
-    pub room_key_pair: &'a oak_sign::KeyBundle,
+    pub room_key_pair: &'a oak_sign::KeyPair,
     pub user_handle: String,
 }
 
 impl<'a> Chatter<'a> {
     pub async fn new(
-        room_key_pair: &'a oak_sign::KeyBundle,
+        room_key_pair: &'a oak_sign::KeyPair,
         user_handle: &'static str,
     ) -> Chatter<'a> {
         info!(
             "creating new Chatter({}, {})",
             user_handle,
-            base64::encode(&room_key_pair.public_key())
+            base64::encode(&room_key_pair.pkcs8_public_key())
         );
         let (channel, interceptor) = oak_tests::channel_and_interceptor().await;
         // TODO(#1357): Use key pair to authenticate this client and label requests.
diff --git a/oak_runtime/src/node/http/tests.rs b/oak_runtime/src/node/http/tests.rs
index 6fc0628ab9..3f5798e665 100644
--- a/oak_runtime/src/node/http/tests.rs
+++ b/oak_runtime/src/node/http/tests.rs
@@ -206,7 +206,7 @@ fn create_server_node(runtime: &RuntimeProxy, port: u32) -> oak_abi::Handle {
 fn create_communication_channel(runtime: &RuntimeProxy) -> (oak_abi::Handle, oak_abi::Handle) {
     // create channel: one end to server_node::run; the other to the Oak node.
     let (init_sender, init_receiver) = runtime
-        .channel_create(&Label::public_untrusted())
+        .channel_create("HTTP init", &Label::public_untrusted())
         .expect("Could not create channel");
 
     // At the start the HTTP server pseudo-Node expects to receive an invocation channel, with
@@ -214,7 +214,7 @@ fn create_communication_channel(runtime: &RuntimeProxy) -> (oak_abi::Handle, oak
     //
     // Create a channel for receiving invocations to pass to the HTTP server pseudo-Node.
     let (invocation_sender, invocation_receiver) = runtime
-        .channel_create(&Label::public_untrusted())
+        .channel_create("HTTP invocation", &Label::public_untrusted())
         .expect("Could not create channel");
     let invocation_sender = HttpInvocationSender {
         sender: Some(Sender::<HttpInvocation>::new(WriteHandle {
diff --git a/oak_runtime/src/node/wasm/tests.rs b/oak_runtime/src/node/wasm/tests.rs
index 519e475e2e..b775c4b2ed 100644
--- a/oak_runtime/src/node/wasm/tests.rs
+++ b/oak_runtime/src/node/wasm/tests.rs
@@ -57,7 +57,7 @@ fn start_node(
         &SecureServerConfiguration::default(),
         &signature_table,
     );
-    let (_write_handle, read_handle) = proxy.channel_create(&Label::public_untrusted())?;
+    let (_write_handle, read_handle) = proxy.channel_create("", &Label::public_untrusted())?;
 
     let result = proxy.node_create(
         &NodeConfiguration {
diff --git a/oak_runtime/src/tests.rs b/oak_runtime/src/tests.rs
index d5f9052964..a36d7fe6ab 100644
--- a/oak_runtime/src/tests.rs
+++ b/oak_runtime/src/tests.rs
@@ -71,7 +71,7 @@ fn run_node_body(node_label: &Label, node_privilege: &NodePrivilege, node_body:
         node_privilege: node_privilege.clone(),
     };
     let (_write_handle, read_handle) = proxy
-        .channel_create(&Label::public_untrusted())
+        .channel_create("Initial", &Label::public_untrusted())
         .expect("Could not create init channel");
 
     proxy
@@ -123,7 +123,7 @@ fn create_channel_same_label_err() {
         Box::new(move |runtime| {
             // Attempt to perform an operation that requires the [`Runtime`] to have created an
             // appropriate [`NodeInfo`] instance.
-            let result = runtime.channel_create(&label_clone);
+            let result = runtime.channel_create("", &label_clone);
             assert_eq!(Err(OakStatus::ErrPermissionDenied), result);
             Ok(())
         }),
@@ -150,7 +150,7 @@ fn create_channel_less_confidential_label_err() {
         &initial_label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let result = runtime.channel_create(&less_confidential_label);
+            let result = runtime.channel_create("", &less_confidential_label);
             assert_eq!(Err(OakStatus::ErrPermissionDenied), result);
             Ok(())
         }),
@@ -183,7 +183,7 @@ fn create_channel_less_confidential_label_declassification_err() {
             can_endorse_integrity_tags: hashset! { other_tag },
         },
         Box::new(move |runtime| {
-            let result = runtime.channel_create(&less_confidential_label);
+            let result = runtime.channel_create("", &less_confidential_label);
             assert_eq!(Err(OakStatus::ErrPermissionDenied), result);
             Ok(())
         }),
@@ -215,7 +215,7 @@ fn create_channel_less_confidential_label_no_privilege_err() {
             can_endorse_integrity_tags: hashset! { tag_0 },
         },
         Box::new(move |runtime| {
-            let result = runtime.channel_create(&less_confidential_label);
+            let result = runtime.channel_create("", &less_confidential_label);
             assert_eq!(Err(OakStatus::ErrPermissionDenied), result);
             Ok(())
         }),
@@ -241,7 +241,7 @@ fn create_channel_with_more_confidential_label_from_public_untrusted_node_ok() {
         &initial_label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let result = runtime.channel_create(&more_confidential_label);
+            let result = runtime.channel_create("", &more_confidential_label);
             assert_eq!(true, result.is_ok());
 
             let (write_handle, read_handle) = result.unwrap();
@@ -289,7 +289,7 @@ fn create_channel_with_more_confidential_label_from_public_node_with_privilege_o
             can_endorse_integrity_tags: hashset! {},
         },
         Box::new(move |runtime| {
-            let result = runtime.channel_create(&more_confidential_label);
+            let result = runtime.channel_create("", &more_confidential_label);
             assert_eq!(true, result.is_ok());
 
             let (write_handle, read_handle) = result.unwrap();
@@ -335,7 +335,7 @@ fn create_channel_with_more_confidential_label_from_non_public_node_with_privile
             can_endorse_integrity_tags: hashset! {},
         },
         Box::new(move |runtime| {
-            let result = runtime.channel_create(&more_confidential_label);
+            let result = runtime.channel_create("", &more_confidential_label);
             assert_eq!(Err(OakStatus::ErrPermissionDenied), result);
             Ok(())
         }),
@@ -351,7 +351,7 @@ fn create_node_same_label_ok() {
         &label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let (_write_handle, read_handle) = runtime.channel_create(&label_clone)?;
+            let (_write_handle, read_handle) = runtime.channel_create("", &label_clone)?;
             let node_configuration = NodeConfiguration {
                 name: "test".to_string(),
                 config_type: Some(ConfigType::LogConfig(LogConfiguration {})),
@@ -372,7 +372,7 @@ fn create_node_invalid_configuration_err() {
         &label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let (_write_handle, read_handle) = runtime.channel_create(&label_clone)?;
+            let (_write_handle, read_handle) = runtime.channel_create("", &label_clone)?;
             // Node configuration without config type.
             let node_configuration = NodeConfiguration {
                 name: "test".to_string(),
@@ -406,7 +406,7 @@ fn create_node_less_confidential_label_err() {
         &initial_label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let (_write_handle, read_handle) = runtime.channel_create(&initial_label_clone)?;
+            let (_write_handle, read_handle) = runtime.channel_create("", &initial_label_clone)?;
             let node_configuration = NodeConfiguration {
                 name: "test".to_string(),
                 config_type: Some(ConfigType::LogConfig(LogConfiguration {})),
@@ -437,7 +437,7 @@ fn create_node_more_confidential_label_ok() {
         &initial_label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let (_write_handle, read_handle) = runtime.channel_create(&initial_label_clone)?;
+            let (_write_handle, read_handle) = runtime.channel_create("", &initial_label_clone)?;
             let node_configuration = NodeConfiguration {
                 name: "test".to_string(),
                 config_type: Some(ConfigType::LogConfig(LogConfiguration {})),
@@ -458,8 +458,8 @@ fn wait_on_channels_immediately_returns_if_any_channel_is_orphaned() {
         &label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let (write_handle_0, read_handle_0) = runtime.channel_create(&label_clone)?;
-            let (_write_handle_1, read_handle_1) = runtime.channel_create(&label_clone)?;
+            let (write_handle_0, read_handle_0) = runtime.channel_create("", &label_clone)?;
+            let (_write_handle_1, read_handle_1) = runtime.channel_create("", &label_clone)?;
 
             // Close the write_handle; this should make the channel Orphaned
             let result = runtime.channel_close(write_handle_0);
@@ -486,7 +486,7 @@ fn wait_on_channels_blocks_if_all_channels_have_status_not_ready() {
         &label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let (write_handle, read_handle) = runtime.channel_create(&label_clone)?;
+            let (write_handle, read_handle) = runtime.channel_create("", &label_clone)?;
 
             // Change the status of the channel concurrently, to unpark the waiting thread.
             let runtime_copy = runtime.clone();
@@ -516,8 +516,8 @@ fn wait_on_channels_immediately_returns_if_any_channel_is_invalid() {
         &label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let (write_handle, _read_handle) = runtime.channel_create(&label_clone)?;
-            let (_write_handle, read_handle) = runtime.channel_create(&label_clone)?;
+            let (write_handle, _read_handle) = runtime.channel_create("", &label_clone)?;
+            let (_write_handle, read_handle) = runtime.channel_create("", &label_clone)?;
 
             let result = runtime.wait_on_channels(&[write_handle, read_handle]);
             assert_eq!(
diff --git a/oak_sign/src/tests.rs b/oak_sign/src/tests.rs
new file mode 100644
index 0000000000..611fb0f3cf
--- /dev/null
+++ b/oak_sign/src/tests.rs
@@ -0,0 +1,62 @@
+//
+// Copyright 2020 The Project Oak Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+use assert_matches::assert_matches;
+use oak_sign::{get_sha256_hex, KeyPair, SignatureBundle};
+
+const TEST_DATA: &str = "Test data";
+const TEST_SHA256: &str = "e27c8214be8b7cf5bccc7c08247e3cb0c1514a48ee1f63197fe4ef3ef51d7e6f";
+const INCORRECT_SIGNATURE: &str =
+    "JmtVw8SPMnU/f4fLSBvd+w96C2NAMFn7+rqGVHzWOItsdZ+T+TqBwnEcCNJrJ1wrQWXPwFk+8sRiRFFCOCJVAA==";
+
+#[test]
+fn key_pair_serialization_deserialization_test() {
+    let generate_result = KeyPair::generate();
+    assert_matches!(generate_result, Ok(_));
+    let key_pair = generate_result.unwrap();
+
+    let pkcs8_key_pair = key_pair.pkcs8_key_pair();
+    let parse_result = KeyPair::parse(&pkcs8_key_pair);
+    assert_matches!(parse_result, Ok(_));
+    let parsed_key_pair = parse_result.unwrap();
+
+    assert_eq!(key_pair, parsed_key_pair);
+}
+
+#[test]
+fn signature_test() {
+    let key_pair = KeyPair::generate().expect("Couldn't create key pair");
+    let create_result = SignatureBundle::create(TEST_DATA.as_bytes(), &key_pair);
+    assert_matches!(create_result, Ok(_));
+    let signature = create_result.unwrap();
+
+    let verify_result = signature.verify();
+    assert_matches!(verify_result, Ok(_));
+
+    let incorrect_signature = SignatureBundle {
+        public_key: signature.public_key.to_vec(),
+        signed_hash: INCORRECT_SIGNATURE.as_bytes().to_vec(),
+        hash: signature.signed_hash.to_vec(),
+    };
+    let incorrect_verify_result = incorrect_signature.verify();
+    assert_matches!(incorrect_verify_result, Err(_));
+}
+
+#[test]
+fn sha256_test() {
+    let hash = get_sha256_hex(TEST_DATA.as_bytes());
+    assert_eq!(hash, TEST_SHA256);
+}