Skip to content

Commit

Permalink
add strict encoding, WIP on encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-orlovsky committed May 12, 2024
1 parent 317265c commit 4b85d2d
Show file tree
Hide file tree
Showing 11 changed files with 397 additions and 83 deletions.
56 changes: 55 additions & 1 deletion Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ required-features = ["cli"]

[dependencies]
amplify = "4.6.0"
strict_encoding = "2.7.0-beta.3"
ascii-armor = "0.6.0"
baid64 = "0.1.0"
secp256k1 = { version = "0.29.0", features = ["rand", "global-context", "rand-std"] }
ec25519 = "0.1.0"
Expand Down
4 changes: 2 additions & 2 deletions src/bip340.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,13 @@ impl Bip340Secret {
let msg = Message::from_digest(msg);
let keypair = Keypair::from_secret_key(SECP256K1, &self.0);
let sig = SECP256K1.sign_schnorr(&msg, &keypair);
SsiSig(sig.serialize())
SsiSig::from(sig.serialize())
}
}

impl SsiPub {
pub fn verify_bip360(self, msg: [u8; 32], sig: SsiSig) -> Result<(), InvalidSig> {
let sig = Signature::from_slice(&sig.0).map_err(|_| InvalidSig::InvalidData)?;
let sig = Signature::from_slice(sig.as_slice()).map_err(|_| InvalidSig::InvalidData)?;
let msg = Message::from_digest(msg);
let pk = XOnlyPublicKey::try_from(self)?;
sig.verify(&msg, &pk).map_err(|_| InvalidSig::InvalidSig)
Expand Down
4 changes: 2 additions & 2 deletions src/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ impl Ed25519Secret {

pub fn sign(&self, msg: [u8; 32]) -> SsiSig {
let sig = self.0.sign(msg, None);
SsiSig(*sig)
SsiSig::from(*sig)
}
}

impl SsiPub {
pub fn verify_ed25519(self, msg: [u8; 32], sig: SsiSig) -> Result<(), InvalidSig> {
let sig = Signature::from_slice(&sig.0).map_err(|_| InvalidSig::InvalidData)?;
let sig = Signature::from_slice(sig.as_slice()).map_err(|_| InvalidSig::InvalidData)?;
let pk = PublicKey::try_from(self)?;
pk.verify(msg, &sig).map_err(|err| {
eprintln!("{err}");
Expand Down
162 changes: 162 additions & 0 deletions src/encrypt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Self-sovereign identity
//
// SPDX-License-Identifier: Apache-2.0
//
// Written in 2024 by
// Dr Maxim Orlovsky <[email protected]>
//
// Copyright (C) 2024 LNP/BP Standards Association. All rights reserved.
//
// 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 std::str::FromStr;

use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit};
use aes::{Aes256, Block};
use amplify::confinement::{Confined, SmallOrdMap, U64 as U64MAX};
use amplify::Bytes32;
use armor::{ArmorHeader, ArmorParseError, AsciiArmor};
use ec25519::{edwards25519, Error};
use rand::random;
use sha2::digest::generic_array::GenericArray;
use sha2::{Digest, Sha256};
use strict_encoding::{StrictDeserialize, StrictSerialize};

use crate::{Algo, InvalidPubkey, SsiPub, LIB_NAME_SSI};

#[derive(Copy, Clone, Debug, Display, Error)]
pub enum EncryptionError {
#[display("the number of receivers exceeds 2^16")]
TooManyReceivers,
#[display("invalid public key {0}")]
InvalidPubkey(SsiPub),
}

#[derive(Clone, Debug, From)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_SSI)]
pub struct SymmetricKey(Bytes32);

impl AsRef<[u8]> for SymmetricKey {
fn as_ref(&self) -> &[u8] { self.0.as_ref() }
}

impl SymmetricKey {
pub fn new() -> Self {
let key = random::<[u8; 32]>();
Self(Bytes32::from_byte_array(key))
}
}

#[derive(Clone, Debug, Display)]
#[display(AsciiArmor::to_ascii_armored_string)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_SSI)]
pub struct Encrypted {
pub keys: SmallOrdMap<SsiPub, Bytes32>,
pub data: Confined<Vec<u8>, 0, U64MAX>,
}

impl StrictSerialize for Encrypted {}
impl StrictDeserialize for Encrypted {}

impl AsciiArmor for Encrypted {
type Err = ArmorParseError;
const PLATE_TITLE: &'static str = "SSI MESSAGE";

fn ascii_armored_headers(&self) -> Vec<ArmorHeader> {
vec![ArmorHeader::with("Receivers", self.keys.keys().map(|pk| pk.to_string()))]
}

fn to_ascii_armored_data(&self) -> Vec<u8> {
self.to_strict_serialized::<U64MAX>()
.expect("64 bits will not error")
.into_inner()
}

fn with_headers_data(_headers: Vec<ArmorHeader>, data: Vec<u8>) -> Result<Self, Self::Err> {
// TODO: Check receivers list
Ok(Self::from_strict_serialized::<U64MAX>(Confined::from_collection_unsafe(data))
.expect("64 bits will never fail"))
}
}

impl FromStr for Encrypted {
type Err = ArmorParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_ascii_armored_str(s) }
}

impl Encrypted {
pub fn encrypt(
source: Vec<u8>,
receivers: impl IntoIterator<Item = SsiPub>,
) -> Result<Self, EncryptionError> {
let key = SymmetricKey::new();
let mut keys = bmap![];
for pk in receivers {
keys.insert(
pk,
pk.encrypt(&key)
.map_err(|_| EncryptionError::InvalidPubkey(pk))?,
);
}
let msg = encrypt(source, key);
Ok(Self {
keys: Confined::try_from(keys).map_err(|_| EncryptionError::TooManyReceivers)?,
data: Confined::from_collection_unsafe(msg),
})
}
}

impl SsiPub {
pub fn encrypt(&self, key: &SymmetricKey) -> Result<Bytes32, InvalidPubkey> {
match self.algo() {
Algo::Ed25519 => self.encrypt_ed25519(key),
Algo::Bip340 | Algo::Other(_) => Err(InvalidPubkey),
}
}

pub fn encrypt_ed25519(&self, key: &SymmetricKey) -> Result<Bytes32, InvalidPubkey> {
let pk = ec25519::PublicKey::try_from(*self)?;
let ge = edwards25519::GeP3::from_bytes_vartime(&pk).ok_or(InvalidPubkey)?;

Ok(edwards25519::ge_scalarmult(key.as_ref(), &ge)
.to_bytes()
.into())
}
}

pub fn encrypt(mut source: Vec<u8>, passwd: impl AsRef<[u8]>) -> Vec<u8> {
let key = Sha256::digest(passwd.as_ref());
let key = GenericArray::from_slice(key.as_slice());
let cipher = Aes256::new(key);

for chunk in source.chunks_mut(16) {
let block = Block::from_mut_slice(chunk);
cipher.encrypt_block(block);
}
source
}

pub fn decrypt(mut source: Vec<u8>, passwd: impl AsRef<[u8]>) -> Vec<u8> {
let key = Sha256::digest(passwd.as_ref());
let key = GenericArray::from_slice(key.as_slice());
let cipher = Aes256::new(key);

for chunk in source.chunks_mut(16) {
let block = Block::from_mut_slice(chunk);
cipher.decrypt_block(block);
}
source
}
4 changes: 2 additions & 2 deletions src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl FromStr for Uid {
fn from_str(s: &str) -> Result<Self, Self::Err> { Self::parse_str(&s.replace(['<', '>'], "")) }
}

#[derive(Clone, Eq, PartialEq, Hash, Debug)]
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Ssi {
pub pk: SsiPub,
pub uids: BTreeSet<Uid>,
Expand All @@ -92,7 +92,7 @@ impl Ssi {
pk: secret.to_public(),
uids,
expiry,
sig: SsiSig([0u8; 64]),
sig: SsiSig::from([0u8; 64]),
};
me.sig = secret.sign(me.to_message());
me
Expand Down
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@

#[macro_use]
extern crate amplify;
#[macro_use]
extern crate strict_encoding;

mod encrypt;
mod identity;
mod secret;
mod public;
Expand All @@ -32,10 +35,13 @@ mod runtime;

pub use bip340::Bip340Secret;
pub use ed25519::Ed25519Secret;
pub use encrypt::{decrypt, encrypt, Encrypted, EncryptionError, SymmetricKey};
pub use identity::{Ssi, SsiParseError, Uid};
pub use public::{
Algo, CertParseError, Chain, Fingerprint, InvalidPubkey, InvalidSig, SsiCert, SsiPub, SsiQuery,
SsiSig, UnknownAlgo, UnknownChain, VerifyError,
};
pub use runtime::{Error, SsiRuntime, SSI_DIR};
pub use secret::{SecretParseError, SsiPair, SsiSecret};

pub const LIB_NAME_SSI: &str = "SSI";
Loading

0 comments on commit 4b85d2d

Please sign in to comment.