Skip to content

Commit d3d5818

Browse files
feat(nistp256): Add P256 support (#21)
* p-256 algo acheivement unlocked * rm erroneous rustdoc * add P256 to KEY_CODECS, cover edge cases of 0x04 * clippy fix
1 parent 82640d2 commit d3d5818

File tree

16 files changed

+1219
-67
lines changed

16 files changed

+1219
-67
lines changed

crates/bs-peer/src/platform/browser/opfs.rs

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -107,52 +107,6 @@ impl Blockstore for OPFSBlockstore {
107107
}
108108

109109
/// A Wrapper sturct around OPFSBlockstore so that we can make it [Send]
110-
///
111-
/// # Example
112-
/// ```no_run
113-
/// use wasm_bindgen_futures::spawn_local;
114-
/// use peerpiper_browser::opfs::OPFSWrapped;
115-
/// use peerpiper_core::Blockstore;
116-
///
117-
/// spawn_local(async move {
118-
/// let Ok(blockstore) = OPFSWrapped::new().await else {
119-
/// panic!("Failed to create OPFSWrapped");
120-
/// };
121-
///
122-
/// // Use blockstore when starting peerpiper
123-
///
124-
/// // 16 is arbitrary, but should be enough for now
125-
/// let (tx_evts, mut rx_evts) = mpsc::channel(16);
126-
///
127-
/// // client sync oneshot
128-
/// let (tx_client, rx_client) = oneshot::channel();
129-
///
130-
/// // command_sender will be used by other wasm_bindgen functions to send commands to the network
131-
/// // so we will need to wrap it in a Mutex or something to make it thread safe.
132-
/// let (network_command_sender, network_command_receiver) = tokio::sync::mpsc::channel(8);
133-
///
134-
/// let bstore = blockstore.clone();
135-
///
136-
/// spawn_local(async move {
137-
/// peerpiper::start(
138-
/// tx_evts,
139-
/// network_command_receiver,
140-
/// tx_client,
141-
/// libp2p_endpoints,
142-
/// bstore,
143-
/// )
144-
/// .await
145-
/// .expect("never end")
146-
/// });
147-
///
148-
/// // wait on rx_client to get the client handle
149-
/// let client_handle = rx_client.await?;
150-
///
151-
/// commander
152-
/// .with_network(network_command_sender)
153-
/// .with_client(client_handle);
154-
/// });
155-
/// ```
156110
#[derive(Debug, Clone)]
157111
pub struct OPFSWrapped {
158112
inner: SendWrapper<OPFSBlockstore>,

crates/multikey/.cargo/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[target.wasm32-unknown-unknown]
2+
rustflags = ["--cfg", "getrandom_backend=\"wasm_js\""]

crates/multikey/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg/

crates/multikey/Cargo.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ description = "Multikey self-describing cryptographic key data"
77
readme = "README.md"
88
license = "Apache-2.0"
99

10+
[lib]
11+
crate-type = ["cdylib", "rlib"]
12+
1013
[features]
1114
default = ["serde"]
1215
wasm = ["getrandom/wasm_js"] # needed for CI testing on wasm32-unknown-unknown
@@ -19,6 +22,10 @@ ed25519-dalek = { version = "2.1.1", features = ["rand_core"] }
1922
elliptic-curve.workspace = true
2023
hex.workspace = true
2124
k256 = "0.13"
25+
p256 = { version = "0.13", default-features = false, features = [
26+
"ecdsa",
27+
"arithmetic",
28+
] }
2229
ml-kem = { version = "0.2.1", features = ["deterministic"] }
2330
multibase.workspace = true
2431
multicodec.workspace = true
@@ -46,8 +53,11 @@ ssh-key = { version = "0.6", default-features = false, features = [
4653
"alloc",
4754
"ecdsa",
4855
"ed25519",
56+
"p256",
4957
] }
50-
getrandom = { version = "0.3.2", features = ["wasm_js"], optional = true }
58+
getrandom = { version = "0.3", features = ["wasm_js"] }
59+
# We have netsed deps that use v0.2, so we need to ensure the feature is flagged here
60+
getrandom_v02 = { package = "getrandom", version = "0.2.15", features = ["js"] }
5161

5262
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
5363
ssh-key = { version = "0.6", features = ["alloc", "crypto", "ed25519"] }
@@ -56,6 +66,7 @@ ssh-key = { version = "0.6", features = ["alloc", "crypto", "ed25519"] }
5666
serde_cbor.workspace = true
5767
serde_json.workspace = true
5868
serde_test.workspace = true
69+
wasm-bindgen-test = "0.3"
5970

6071
[lints]
6172
workspace = true

crates/multikey/justfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
test-wasm:
2+
wasm-pack build --target web
3+
wasm-pack test --headless --chrome --all-features

crates/multikey/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ pub enum Error {
6363
UnsupportedAlgorithm(String),
6464
}
6565

66+
impl From<ssh_key::Error> for Error {
67+
fn from(e: ssh_key::Error) -> Self {
68+
Error::Conversions(ConversionsError::Ssh(e.into()))
69+
}
70+
}
71+
6672
/// Attributes errors created by this library
6773
#[derive(Clone, Debug, thiserror::Error)]
6874
#[non_exhaustive]

crates/multikey/src/mk.rs

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// SPDX-License-Idnetifier: Apache-2.0
1+
// SPDX-License-Identifier: Apache-2.0
22
use crate::{
33
error::{AttributesError, CipherError, ConversionsError, KdfError},
4-
views::{bcrypt, bls12381, chacha20, ed25519, mlkem, secp256k1},
4+
views::{bcrypt, bls12381, chacha20, ed25519, mlkem, p256, secp256k1},
55
AttrId, AttrView, CipherAttrView, CipherView, ConvView, DataView, Error, FingerprintView,
66
KdfAttrView, KdfView, SignView, ThresholdAttrView, ThresholdView, VerifyView, Views,
77
};
@@ -13,21 +13,23 @@ use multiutil::{BaseEncoded, CodecInfo, EncodingInfo, Varbytes, VarbytesIter, Va
1313
use ssh_key::{
1414
private::{EcdsaKeypair, KeypairData},
1515
public::{EcdsaPublicKey, KeyData},
16+
Algorithm::*,
1617
EcdsaCurve, PrivateKey, PublicKey,
1718
};
1819
use std::{collections::BTreeMap, fmt, num::NonZeroUsize};
1920
use zeroize::Zeroizing;
2021

2122
/// the list of key codecs supported for key generation
22-
pub const KEY_CODECS: [Codec; 7] = [
23+
pub const KEY_CODECS: [Codec; 8] = [
2324
Codec::Bls12381G1Priv,
2425
Codec::Bls12381G2Priv,
2526
Codec::Ed25519Priv,
27+
Codec::P256Priv,
2628
/*
2729
Codec::LamportSha3256Priv,
2830
Codec::LamportSha3384Priv,
2931
Codec::LamportSha3512Priv,
30-
Codec::P256Priv,
32+
3133
Codec::P384Priv,
3234
Codec::P521Priv,
3335
*/
@@ -205,6 +207,7 @@ impl Views for Multikey {
205207
| Codec::Bls12381G2Pub
206208
| Codec::Bls12381G2PubShare => Ok(Box::new(bls12381::View::try_from(self)?)),
207209
Codec::Ed25519Pub | Codec::Ed25519Priv => Ok(Box::new(ed25519::View::try_from(self)?)),
210+
Codec::P256Pub | Codec::P256Priv => Ok(Box::new(p256::View::try_from(self)?)),
208211
Codec::Secp256K1Pub | Codec::Secp256K1Priv => {
209212
Ok(Box::new(secp256k1::View::try_from(self)?))
210213
}
@@ -250,6 +253,7 @@ impl Views for Multikey {
250253
| Codec::Bls12381G2Pub
251254
| Codec::Bls12381G2PubShare => Ok(Box::new(bls12381::View::try_from(self)?)),
252255
Codec::Ed25519Pub | Codec::Ed25519Priv => Ok(Box::new(ed25519::View::try_from(self)?)),
256+
Codec::P256Pub | Codec::P256Priv => Ok(Box::new(p256::View::try_from(self)?)),
253257
Codec::Secp256K1Pub | Codec::Secp256K1Priv => {
254258
Ok(Box::new(secp256k1::View::try_from(self)?))
255259
}
@@ -318,6 +322,7 @@ impl Views for Multikey {
318322
| Codec::Bls12381G2Pub
319323
| Codec::Bls12381G2PubShare => Ok(Box::new(bls12381::View::try_from(self)?)),
320324
Codec::Ed25519Pub | Codec::Ed25519Priv => Ok(Box::new(ed25519::View::try_from(self)?)),
325+
Codec::P256Pub | Codec::P256Priv => Ok(Box::new(p256::View::try_from(self)?)),
321326
Codec::Secp256K1Pub | Codec::Secp256K1Priv => {
322327
Ok(Box::new(secp256k1::View::try_from(self)?))
323328
}
@@ -343,6 +348,7 @@ impl Views for Multikey {
343348
| Codec::Bls12381G2Pub
344349
| Codec::Bls12381G2PubShare => Ok(Box::new(bls12381::View::try_from(self)?)),
345350
Codec::Ed25519Pub | Codec::Ed25519Priv => Ok(Box::new(ed25519::View::try_from(self)?)),
351+
Codec::P256Pub | Codec::P256Priv => Ok(Box::new(p256::View::try_from(self)?)),
346352
Codec::Secp256K1Pub | Codec::Secp256K1Priv => {
347353
Ok(Box::new(secp256k1::View::try_from(self)?))
348354
}
@@ -377,6 +383,7 @@ impl Views for Multikey {
377383
| Codec::Bls12381G2Pub
378384
| Codec::Bls12381G2PubShare => Ok(Box::new(bls12381::View::try_from(self)?)),
379385
Codec::Ed25519Pub | Codec::Ed25519Priv => Ok(Box::new(ed25519::View::try_from(self)?)),
386+
Codec::P256Pub | Codec::P256Priv => Ok(Box::new(p256::View::try_from(self)?)),
380387
Codec::Secp256K1Pub | Codec::Secp256K1Priv => {
381388
Ok(Box::new(secp256k1::View::try_from(self)?))
382389
}
@@ -406,6 +413,7 @@ impl Views for Multikey {
406413
| Codec::Bls12381G2Pub
407414
| Codec::Bls12381G2PubShare => Ok(Box::new(bls12381::View::try_from(self)?)),
408415
Codec::Ed25519Pub | Codec::Ed25519Priv => Ok(Box::new(ed25519::View::try_from(self)?)),
416+
Codec::P256Pub | Codec::P256Priv => Ok(Box::new(p256::View::try_from(self)?)),
409417
Codec::Secp256K1Pub | Codec::Secp256K1Priv => {
410418
Ok(Box::new(secp256k1::View::try_from(self)?))
411419
}
@@ -485,16 +493,52 @@ impl Builder {
485493
})
486494
}
487495

488-
/// new builder from ssh_key::PublicKey source
496+
/// Build from [ssh_key::PublicKey] source
489497
pub fn new_from_ssh_public_key(sshkey: &PublicKey) -> Result<Self, Error> {
490-
use ssh_key::Algorithm::*;
491498
match sshkey.algorithm() {
492499
Ecdsa { curve } => {
493500
use EcdsaCurve::*;
494501
let (key_bytes, codec) = match curve {
495502
NistP256 => {
496503
if let KeyData::Ecdsa(EcdsaPublicKey::NistP256(point)) = sshkey.key_data() {
497-
(point.as_bytes().to_vec(), Codec::P256Pub)
504+
// SSH stores points in uncompressed format
505+
// Convert to compressed SEC1 format to match to_public_key()
506+
let point_bytes = point.as_bytes();
507+
508+
// Parse the point - it may have the 0x04 tag (65 bytes) or not (64 bytes)
509+
let verifying_key = if point_bytes.len() == 65 && point_bytes[0] == 0x04
510+
{
511+
// Already has the tag, use as-is
512+
::p256::ecdsa::VerifyingKey::from_sec1_bytes(point_bytes).map_err(
513+
|e| {
514+
ConversionsError::PublicKeyFailure(format!(
515+
"Invalid P-256 point: {}",
516+
e
517+
))
518+
},
519+
)?
520+
} else if point_bytes.len() == 64 {
521+
// Need to add the 0x04 tag
522+
let mut uncompressed_bytes = vec![0x04];
523+
uncompressed_bytes.extend_from_slice(point_bytes);
524+
::p256::ecdsa::VerifyingKey::from_sec1_bytes(&uncompressed_bytes)
525+
.map_err(|e| {
526+
ConversionsError::PublicKeyFailure(format!(
527+
"Invalid P-256 point: {}",
528+
e
529+
))
530+
})?
531+
} else {
532+
return Err(ConversionsError::PublicKeyFailure(format!(
533+
"Invalid P-256 point length: {}",
534+
point_bytes.len()
535+
))
536+
.into());
537+
};
538+
539+
// Convert to compressed format
540+
let compressed_point = verifying_key.to_encoded_point(true);
541+
(compressed_point.as_bytes().to_vec(), Codec::P256Pub)
498542
} else {
499543
return Err(ConversionsError::UnsupportedAlgorithm(
500544
sshkey.algorithm().to_string(),

crates/multikey/src/views.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use std::num::NonZeroUsize;
2-
3-
// SPDX-License-Idnetifier: Apache-2.0
1+
// SPDX-License-Identifier: Apache-2.0
42
use crate::{Error, Multikey};
53
use multicodec::Codec;
64
use multihash::Multihash;
75
use multisig::Multisig;
6+
use std::num::NonZeroUsize;
87
use zeroize::Zeroizing;
98

109
// algorithms implement different sets of view
@@ -13,6 +12,7 @@ pub(crate) mod bls12381;
1312
pub(crate) mod chacha20;
1413
pub(crate) mod ed25519;
1514
pub(crate) mod mlkem;
15+
pub(crate) mod p256;
1616
pub(crate) mod secp256k1;
1717
// Attributes views let you inquire about the Multikey and retrieve data
1818
// associated with the particular view.

crates/multikey/src/views/bls12381.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -476,8 +476,8 @@ impl ConvView for View<'_> {
476476
let tav = pk.threshold_attr_view()?;
477477
let key_share: Vec<u8> = KeyShare(
478478
tav.identifier()?,
479-
tav.threshold()?.into(),
480-
tav.limit()?.into(),
479+
tav.threshold()?,
480+
tav.limit()?,
481481
key_bytes.to_vec(),
482482
)
483483
.into();
@@ -496,7 +496,7 @@ impl ConvView for View<'_> {
496496
let tav = pk.threshold_attr_view()?;
497497
let key_share: Vec<u8> = KeyShare(
498498
tav.identifier()?,
499-
tav.threshold()?.into(),
499+
tav.threshold()?,
500500
tav.limit()?,
501501
key_bytes.to_vec(),
502502
)
@@ -556,15 +556,15 @@ impl ConvView for View<'_> {
556556
let sav = self.mk.threshold_attr_view()?;
557557
let secret_key_share: Vec<u8> = KeyShare(
558558
sav.identifier()?,
559-
sav.threshold()?.into(),
559+
sav.threshold()?,
560560
sav.limit()?,
561561
secret_bytes.to_vec(),
562562
)
563563
.into();
564564
let pav = pk.threshold_attr_view()?;
565565
let public_key_share: Vec<u8> = KeyShare(
566566
pav.identifier()?,
567-
pav.threshold()?.into(),
567+
pav.threshold()?,
568568
pav.limit()?,
569569
key_bytes.to_vec(),
570570
)
@@ -590,15 +590,15 @@ impl ConvView for View<'_> {
590590
let sav = self.mk.threshold_attr_view()?;
591591
let secret_key_share: Vec<u8> = KeyShare(
592592
sav.identifier()?,
593-
sav.threshold()?.into(),
593+
sav.threshold()?,
594594
sav.limit()?,
595595
secret_bytes.to_vec(),
596596
)
597597
.into();
598598
let pav = pk.threshold_attr_view()?;
599599
let public_key_share: Vec<u8> = KeyShare(
600600
pav.identifier()?,
601-
pav.threshold()?.into(),
601+
pav.threshold()?,
602602
pav.limit()?,
603603
key_bytes.to_vec(),
604604
)

0 commit comments

Comments
 (0)