Skip to content

Commit fba7c4c

Browse files
feat: Use ed25519-consensus instead of ring (#606)
1 parent 98164b2 commit fba7c4c

File tree

19 files changed

+118
-116
lines changed

19 files changed

+118
-116
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## Unreleased
1010

11+
* Changed `BasicIdentity`'s implmentation from `ring` to `ed25519-consensus`.
1112
* Added `AgentBuilder::with_max_polling_time` to config the maximum time to wait for a response from the replica.
1213
* `DelegatedIdentity::new` now checks the delegation chain. The old behavior is available under `new_unchecked`.
1314

Cargo.lock

+15-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,14 @@ ic-certification = "2.2"
3030
candid = "0.10.1"
3131
candid_parser = "0.1.1"
3232
clap = "4.4.3"
33+
ed25519-consensus = "2.1.0"
3334
futures-util = "0.3.21"
3435
hex = "0.4.3"
3536
k256 = "0.13.4"
3637
leb128 = "0.2.5"
3738
p256 = "0.13.2"
39+
rand = "0.8.5"
3840
reqwest = { version = "0.12", default-features = false }
39-
ring = "0.17.7"
4041
serde = "1.0.162"
4142
serde_bytes = "0.11.13"
4243
serde_cbor = "0.11.2"

ic-agent/Cargo.toml

+5-4
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ async-watch = { version = "0.3", optional = true }
2323
backoff = "0.4.0"
2424
cached = { version = "0.52", features = ["ahash"], default-features = false }
2525
candid = { workspace = true }
26+
der = "0.7"
2627
ecdsa = "0.16"
27-
ed25519-consensus = { version = "2" }
28+
ed25519-consensus = { workspace = true }
2829
elliptic-curve = "0.13"
2930
futures-util = { workspace = true }
3031
hex = { workspace = true }
@@ -38,9 +39,9 @@ p256 = { workspace = true, features = ["pem"] }
3839
leb128 = { workspace = true }
3940
pkcs8 = { version = "0.10.2", features = ["std"] }
4041
sec1 = { version = "0.7.2", features = ["pem"] }
41-
rand = "0.8.5"
42+
rand = { workspace = true }
4243
rangemap = "1.4"
43-
ring = { workspace = true, features = ["std"] }
44+
ring = { version = "0.17", optional = true }
4445
serde = { workspace = true, features = ["derive"] }
4546
serde_bytes = { workspace = true }
4647
serde_cbor = { workspace = true }
@@ -65,7 +66,6 @@ optional = true
6566

6667
[target.'cfg(not(target_family = "wasm"))'.dependencies]
6768
tokio = { version = "1.24.2", features = ["time"] }
68-
rustls-webpki = "0.102"
6969

7070
[target.'cfg(target_family = "wasm")'.dependencies]
7171
getrandom = { version = "0.2", features = ["js"], optional = true }
@@ -95,6 +95,7 @@ web-sys = { version = "0.3", features = [
9595
[features]
9696
default = ["pem"]
9797
pem = ["dep:pem"]
98+
ring = ["dep:ring"]
9899
ic_ref_tests = [
99100
"default",
100101
] # Used to separate integration tests for ic-ref which need a server running.

ic-agent/src/agent/mod.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,9 @@ type AgentFuture<'a, V> = Pin<Box<dyn Future<Output = Result<V, AgentError>> + '
9898
/// }
9999
///
100100
/// # fn create_identity() -> impl ic_agent::Identity {
101-
/// # let rng = ring::rand::SystemRandom::new();
102-
/// # let key_pair = ring::signature::Ed25519KeyPair::generate_pkcs8(&rng)
103-
/// # .expect("Could not generate a key pair.");
104101
/// #
105-
/// # ic_agent::identity::BasicIdentity::from_key_pair(
106-
/// # ring::signature::Ed25519KeyPair::from_pkcs8(key_pair.as_ref())
107-
/// # .expect("Could not read the key pair."),
102+
/// # ic_agent::identity::BasicIdentity::from_signing_key(
103+
/// # ed25519_consensus::SigningKey::new(rand::thread_rng())
108104
/// # )
109105
/// # }
110106
/// #

ic-agent/src/identity/basic.rs

+46-14
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{agent::EnvelopeContent, export::Principal, Identity, Signature};
33
#[cfg(feature = "pem")]
44
use crate::identity::error::PemError;
55

6-
use ring::signature::{Ed25519KeyPair, KeyPair};
6+
use ed25519_consensus::SigningKey;
77
use simple_asn1::{
88
oid, to_der,
99
ASN1Block::{BitString, ObjectIdentifier, Sequence},
@@ -13,9 +13,11 @@ use std::fmt;
1313

1414
use super::Delegation;
1515

16-
/// A Basic Identity which sign using an ED25519 key pair.
16+
/// A cryptographic identity which signs using an Ed25519 key pair.
17+
///
18+
/// The caller will be represented via [`Principal::self_authenticating`], which contains the SHA-224 hash of the public key.
1719
pub struct BasicIdentity {
18-
key_pair: Ed25519KeyPair,
20+
private_key: KeyCompat,
1921
der_encoded_public_key: Vec<u8>,
2022
}
2123

@@ -28,35 +30,65 @@ impl fmt::Debug for BasicIdentity {
2830
}
2931

3032
impl BasicIdentity {
31-
/// Create a BasicIdentity from reading a PEM file at the path.
33+
/// Create a `BasicIdentity` from reading a PEM file at the path.
3234
#[cfg(feature = "pem")]
3335
pub fn from_pem_file<P: AsRef<std::path::Path>>(file_path: P) -> Result<Self, PemError> {
3436
Self::from_pem(std::fs::File::open(file_path)?)
3537
}
3638

37-
/// Create a BasicIdentity from reading a PEM File from a Reader.
39+
/// Create a `BasicIdentity` from reading a PEM File from a Reader.
3840
#[cfg(feature = "pem")]
3941
pub fn from_pem<R: std::io::Read>(pem_reader: R) -> Result<Self, PemError> {
42+
use der::{Decode, PemReader};
43+
use pkcs8::PrivateKeyInfo;
44+
4045
let bytes: Vec<u8> = pem_reader
4146
.bytes()
4247
.collect::<Result<Vec<u8>, std::io::Error>>()?;
48+
let pki = PrivateKeyInfo::decode(&mut PemReader::new(&bytes)?)?;
49+
let private_key = SigningKey::try_from(pki.private_key)?;
50+
Ok(BasicIdentity::from_signing_key(private_key))
51+
}
4352

44-
Ok(BasicIdentity::from_key_pair(Ed25519KeyPair::from_pkcs8(
45-
pem::parse(bytes)?.contents(),
46-
)?))
53+
/// Create a `BasicIdentity` from a `SigningKey` from `ed25519-consensus`.
54+
pub fn from_signing_key(key: SigningKey) -> Self {
55+
let public_key = key.verification_key();
56+
let der_encoded_public_key = der_encode_public_key(public_key.as_bytes().to_vec());
57+
58+
Self {
59+
private_key: KeyCompat::Standard(key),
60+
der_encoded_public_key,
61+
}
4762
}
4863

49-
/// Create a BasicIdentity from a KeyPair from the ring crate.
50-
pub fn from_key_pair(key_pair: Ed25519KeyPair) -> Self {
64+
/// Create a `BasicIdentity` from an `Ed25519KeyPair` from `ring`.
65+
#[cfg(feature = "ring")]
66+
pub fn from_key_pair(key_pair: ring::signature::Ed25519KeyPair) -> Self {
67+
use ring::signature::KeyPair;
5168
let der_encoded_public_key = der_encode_public_key(key_pair.public_key().as_ref().to_vec());
52-
5369
Self {
54-
key_pair,
70+
private_key: KeyCompat::Ring(key_pair),
5571
der_encoded_public_key,
5672
}
5773
}
5874
}
5975

76+
enum KeyCompat {
77+
Standard(SigningKey),
78+
#[cfg(feature = "ring")]
79+
Ring(ring::signature::Ed25519KeyPair),
80+
}
81+
82+
impl KeyCompat {
83+
fn sign(&self, payload: &[u8]) -> Vec<u8> {
84+
match self {
85+
Self::Standard(k) => k.sign(payload).to_bytes().to_vec(),
86+
#[cfg(feature = "ring")]
87+
Self::Ring(k) => k.sign(payload).as_ref().to_vec(),
88+
}
89+
}
90+
}
91+
6092
impl Identity for BasicIdentity {
6193
fn sender(&self) -> Result<Principal, String> {
6294
Ok(Principal::self_authenticating(&self.der_encoded_public_key))
@@ -75,9 +107,9 @@ impl Identity for BasicIdentity {
75107
}
76108

77109
fn sign_arbitrary(&self, content: &[u8]) -> Result<Signature, String> {
78-
let signature = self.key_pair.sign(content);
110+
let signature = self.private_key.sign(content);
79111
Ok(Signature {
80-
signature: Some(signature.as_ref().to_vec()),
112+
signature: Some(signature),
81113
public_key: self.public_key(),
82114
delegations: None,
83115
})

ic-agent/src/identity/delegated.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
use candid::Principal;
2+
use der::{Decode, SliceReader};
23
use ecdsa::signature::Verifier;
34
use k256::Secp256k1;
45
use p256::NistP256;
56
use pkcs8::{spki::SubjectPublicKeyInfoRef, AssociatedOid, ObjectIdentifier};
6-
use sec1::{
7-
der::{Decode, SliceReader},
8-
EcParameters, EncodedPoint,
9-
};
7+
use sec1::{EcParameters, EncodedPoint};
108

119
use crate::{agent::EnvelopeContent, Signature};
1210

ic-agent/src/identity/error.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ pub enum PemError {
1818
#[error("An error occurred while reading the file: {0}")]
1919
PemError(#[from] pem::PemError),
2020

21-
/// The key was rejected by Ring.
22-
#[error("A key was rejected by Ring: {0}")]
23-
KeyRejected(#[from] ring::error::KeyRejected),
21+
/// An error occurred while reading the file in DER format.
22+
#[cfg(feature = "pem")]
23+
#[error("An error occurred while reading the file: {0}")]
24+
DerError(#[from] der::Error),
25+
26+
/// The key was rejected by ed25519-consensus.
27+
#[error("A key was rejected by ed25519-consensus: {0}")]
28+
KeyRejected(#[from] ed25519_consensus::Error),
2429

2530
/// The key was rejected by k256.
2631
#[error("A key was rejected by k256: {0}")]

ic-agent/src/lib.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,8 @@
4343
//! }
4444
//!
4545
//! # fn create_identity() -> impl ic_agent::Identity {
46-
//! # let rng = ring::rand::SystemRandom::new();
47-
//! # let key_pair = ring::signature::Ed25519KeyPair::generate_pkcs8(&rng)
48-
//! # .expect("Could not generate a key pair.");
49-
//! #
50-
//! # ic_agent::identity::BasicIdentity::from_key_pair(
51-
//! # ring::signature::Ed25519KeyPair::from_pkcs8(key_pair.as_ref())
52-
//! # .expect("Could not read the key pair."),
46+
//! # ic_agent::identity::BasicIdentity::from_signing_key(
47+
//! # ed25519_consensus::SigningKey::new(rand::thread_rng()),
5348
//! # )
5449
//! # }
5550
//! #

ic-transport-types/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ mod request_id;
1717
pub mod signed;
1818

1919
/// The authentication envelope, containing the contents and their signature. This struct can be passed to `Agent`'s
20-
/// `*_signed` methods via [`to_bytes`](Envelope::to_bytes).
20+
/// `*_signed` methods via [`encode_bytes`](Envelope::encode_bytes).
2121
#[derive(Debug, Clone, Deserialize, Serialize)]
2222
#[serde(rename_all = "snake_case")]
2323
pub struct Envelope<'a> {

ic-utils/Cargo.toml

+2-3
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ categories = ["api-bindings", "data-structures", "no-std"]
1414
keywords = ["internet-computer", "agent", "utility", "icp", "dfinity"]
1515
include = ["src", "Cargo.toml", "../LICENSE", "README.md"]
1616

17-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
18-
1917
[dependencies]
2018
async-trait = "0.1.68"
2119
candid = { workspace = true, features = ["value"] }
@@ -33,8 +31,9 @@ semver = "1.0.7"
3331
once_cell = "1.10.0"
3432

3533
[dev-dependencies]
34+
ed25519-consensus = { workspace = true }
3635
ic-agent = { workspace = true, default-features = true }
37-
ring = { workspace = true }
36+
rand = { workspace = true }
3837
tokio = { workspace = true, features = ["full"] }
3938

4039
[features]

0 commit comments

Comments
 (0)