Skip to content

Commit

Permalink
initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
Cypher committed Dec 24, 2023
1 parent cf4e4ad commit fe9d5d0
Show file tree
Hide file tree
Showing 14 changed files with 2,026 additions and 84 deletions.
1,060 changes: 1,013 additions & 47 deletions Cargo.lock

Large diffs are not rendered by default.

63 changes: 34 additions & 29 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,45 +1,50 @@
[workspace]
default-members = [
"."
]

[workspace.package]
authors = ["Dr. Maxim Orlovsky <[email protected]>"]
homepage = "https://cyphernet.io"
repository = "https://github.com/Cyphernet-DAO/ssi"
rust-version = "1.66"
edition = "2021"
license = "Apache-2.0"

[workspace.dependencies]
amplify = "4.0.0"

###
### Main package (`ssi`)
###

[package]
name = "ssi"
name = "ssid"
version = "0.1.0"
description = "Self-sovereign identity"
keywords = ["privacy", "cypherpunk", "identity"]
categories = ["cryptography"]
readme = "README.md"
authors = { workspace = true }
homepage = { workspace = true }
repository = { workspace = true }
rust-version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
authors = ["Cypher <[email protected]>"]
homepage = "https://cyphernet.io"
repository = "https://github.com/Cyphernet-DAO/ssid"
rust-version = "1.66"
edition = "2021"
license = "Apache-2.0"

[lib]

[[bin]]
name = "ssid"
required-features = ["cli"]

[dependencies]
amplify = { workspace = true }
amplify = "4.5.0"
commit_verify = "0.11.0-beta.1"
strict_encoding = "2.6.1"
strict_types = "1.6.3"
ec25519 = "0.1.0"
secp256k1 = { version = "0.27.0", optional = true }
baid58 = "0.4.4"
base85 = "2.0.0"
bp-std = { version = "0.11.0-beta.2", features = ["client-side-validation"] }
rand = "0.8.5"
clap = { version = "4.4.11", features = ["derive", "env", "wrap_help"], optional = true }
shellexpand = { version = "3.1.0", optional = true }

[features]
default = []
all = ["cli"]
cli = ["clap", "shellexpand"]

[package.metadata.docs.rs]
all-features = true
rustc-args = ["--cfg", "docsrs"]

[patch.crates-io]
commit_verify = { git = "https://github.com/LNP-BP/client_side_validation", branch = "v0.11" }
bp-consensus = { git = "https://github.com/BP-WG/bp-core", branch = "doubleanchors" }
bp-dbc = { git = "https://github.com/BP-WG/bp-core", branch = "doubleanchors" }
bp-seals = { git = "https://github.com/BP-WG/bp-core", branch = "doubleanchors" }
bp-core = { git = "https://github.com/BP-WG/bp-core", branch = "doubleanchors" }
psbt = { git = "https://github.com/BP-WG/bp-std", branch = "v0.11" }
bp-std = { git = "https://github.com/BP-WG/bp-std", branch = "v0.11" }
9 changes: 7 additions & 2 deletions MAINTAINERS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Maxim Orlovsky
Cypher
---------------
- GPG: `54DD1B1181EC60253A9357956482D826A6BEA6F9`
- EMail: [[email protected]](mailto:[email protected])

- Maxim Orlovsky
---------------
- GitHub: [@dr-orlovsky](https://github.com/dr-orlovsky)
- GPG: `EAE730CEC0C663763F028A5860094BAF18A26EC9`
- SSH: `BoSGFzbyOKC7Jm28MJElFboGepihCpHop60nS8OoG/A`
- EMail: [dr@orlovsky.ch](mailto:dr@orlovsky.ch)
- EMail: [orlovsky@cyphernet.io](mailto:orlovsky@cyphernet.io)
7 changes: 5 additions & 2 deletions MANIFEST.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ Kind: Free software
License: Apache-2.0
Language: Rust
Compiler: 1.66
Author: Maxim Orlovsky
Author: Cypher
Maintained: Cyphernet DAO, Switzerland
Maintainers:
Cypher:
GPG: 54DD1B1181EC60253A9357956482D826A6BEA6F9
EMail: [email protected]
Maxim Orlovsky:
GitHub: @dr-orlovsky
GPG: EAE730CEC0C663763F028A5860094BAF18A26EC9
SSH: BoSGFzbyOKC7Jm28MJElFboGepihCpHop60nS8OoG/A
EMail: dr@orlovsky.ch
EMail: orlovsky@cyphernet.io
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
# Self-sovereign identity suite

Self-sovereign identity is an identity format developed by Cyphernet
Association, Switzerland. Being similar to OpenPGP, it operates without
any key servers, using blockchain infrastructure. This allows provable and
globally enforceable key revocation without dedicated revocation keys,
global propagation of new keys information and provable or unique
signatures which are timestamped or created using single-use-seal
mechanism.
66 changes: 66 additions & 0 deletions src/algo/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Self-sovereign identity (SSID)
//
// SPDX-License-Identifier: Apache-2.0
//
// Written in 2023-204 by
// Cypher<[email protected]>
//
// Copyright 2023-2024 Cyphernet DAO, Switzerland
//
// 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.

mod ristretto25519;

use std::fmt::{Debug, Display};
use std::str::FromStr;

use amplify::Bytes4;
use baid58::Baid58ParseError;
pub use ristretto25519::{RistrettoPk, RistrettoSig, RistrettoSk};
use strict_encoding::{StrictDecode, StrictDumb, StrictEncode, StrictType};

use crate::{BindleContent, Digest};

pub type Fingerprint = Bytes4;

pub trait Sk: BindleContent {
type Sig: Sig;

fn generate() -> Self;

fn sign(&self, message: impl Into<Digest>) -> Self::Sig;
}

pub trait Pk:
Copy
+ Eq
+ Debug
+ Display
+ FromStr<Err = Baid58ParseError>
+ StrictType
+ StrictDumb
+ StrictEncode
+ StrictDecode
{
type Sk: Sk;
const ID: u8;

fn with(sk: &Self::Sk) -> Self;

#[must_use]
fn verify(&self, message: impl Into<Digest>, sig: &<Self::Sk as Sk>::Sig) -> bool;

fn fingerprint(&self) -> Fingerprint;
}

pub trait Sig: Copy + Eq + Debug + StrictType + StrictDumb + StrictEncode + StrictDecode {}
187 changes: 187 additions & 0 deletions src/algo/ristretto25519.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Self-sovereign identity (SSID)
//
// SPDX-License-Identifier: Apache-2.0
//
// Written in 2023-204 by
// Cypher<[email protected]>
//
// Copyright 2023-2024 Cyphernet DAO, Switzerland
//
// 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::io;
use std::ops::Deref;
use std::str::FromStr;

use baid58::{Baid58ParseError, Chunking, FromBaid58, ToBaid58, CHUNKING_32};
use ec25519::{Noise, PublicKey, SecretKey, Signature};
use rand::{random, thread_rng, Rng};
use strict_encoding::{
DecodeError, ReadTuple, StrictDecode, StrictDeserialize, StrictDumb, StrictEncode,
StrictProduct, StrictSerialize, StrictTuple, StrictType, TypedRead, TypedWrite,
};

use super::{Fingerprint, Pk, Sig};
use crate::{BindleContent, Digest, Sk, LIB_NAME_SSID};

pub struct RistrettoSk(SecretKey);

impl StrictType for RistrettoSk {
const STRICT_LIB_NAME: &'static str = LIB_NAME_SSID;
}
impl StrictProduct for RistrettoSk {}
impl StrictTuple for RistrettoSk {
const FIELD_COUNT: u8 = 1;
}
impl StrictEncode for RistrettoSk {
fn strict_encode<W: TypedWrite>(&self, writer: W) -> io::Result<W> {
writer.write_newtype::<Self>(self.0.deref())
}
}
impl StrictDecode for RistrettoSk {
fn strict_decode(reader: &mut impl TypedRead) -> Result<Self, DecodeError> {
reader.read_tuple(|r| {
let data = r.read_field()?;
Ok(Self(SecretKey::new(data)))
})
}
}
impl StrictDumb for RistrettoSk {
fn strict_dumb() -> Self { Self(SecretKey::new([0xFAu8; 64])) }
}
impl StrictSerialize for RistrettoSk {}
impl StrictDeserialize for RistrettoSk {}

impl Sk for RistrettoSk {
type Sig = RistrettoSig;

fn generate() -> Self {
let mut data = [0u8; 64];
thread_rng().fill(&mut data);
Self(SecretKey::new(data))
}

fn sign(&self, message: impl Into<Digest>) -> Self::Sig {
RistrettoSig(self.0.sign(message.into(), Some(Noise::new(random()))))
}
}

impl BindleContent for RistrettoSk {
const MAGIC: [u8; 4] = *b"SSSK";
const PLATE_TITLE: &'static str = "SSID SECRET KEY";
type Id = RistrettoPk;

fn bindle_id(&self) -> Self::Id { RistrettoPk::with(self) }
}

#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display)]
#[display(Self::to_baid58_string)]
pub struct RistrettoPk(PublicKey);

impl From<[u8; 33]> for RistrettoPk {
fn from(value: [u8; 33]) -> Self {
assert_eq!(value[0], Self::ID, "invalid key type");
let mut data = [0u8; 32];
data.copy_from_slice(&value[1..]);
Self(PublicKey::new(data))
}
}

impl ToBaid58<33> for RistrettoPk {
const HRI: &'static str = "ssi";
const CHUNKING: Option<Chunking> = CHUNKING_32;
fn to_baid58_payload(&self) -> [u8; 33] {
let mut payload = [0u8; 33];
payload[0] = Self::ID;
payload[1..].copy_from_slice(self.0.deref());
payload
}
fn to_baid58_string(&self) -> String { self.to_string() }
}
impl FromBaid58<33> for RistrettoPk {}
impl FromStr for RistrettoPk {
type Err = Baid58ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_baid58_chunked_str(s, ':', '#') }
}
impl RistrettoPk {
pub fn to_baid58_string(&self) -> String { format!("{::<#.2}", self.to_baid58()) }
pub fn to_mnemonic(&self) -> String { self.to_baid58().mnemonic() }
}

impl Pk for RistrettoPk {
type Sk = RistrettoSk;
const ID: u8 = 1;

fn with(sk: &Self::Sk) -> Self { Self(sk.0.public_key()) }

fn verify(&self, message: impl Into<Digest>, sig: &<Self::Sk as Sk>::Sig) -> bool {
self.0.verify(message.into(), &sig.0).is_ok()
}

fn fingerprint(&self) -> Fingerprint {
Fingerprint::copy_from_slice(&self.0[0..4]).expect("fixed length")
}
}

impl StrictType for RistrettoPk {
const STRICT_LIB_NAME: &'static str = LIB_NAME_SSID;
}
impl StrictProduct for RistrettoPk {}
impl StrictTuple for RistrettoPk {
const FIELD_COUNT: u8 = 1;
}
impl StrictEncode for RistrettoPk {
fn strict_encode<W: TypedWrite>(&self, writer: W) -> io::Result<W> {
writer.write_newtype::<Self>(self.0.deref())
}
}
impl StrictDecode for RistrettoPk {
fn strict_decode(reader: &mut impl TypedRead) -> Result<Self, DecodeError> {
reader.read_tuple(|r| {
let data = r.read_field()?;
Ok(Self(PublicKey::new(data)))
})
}
}
impl StrictDumb for RistrettoPk {
fn strict_dumb() -> Self { Self(PublicKey::new([0xFAu8; 32])) }
}

#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct RistrettoSig(Signature);

impl Sig for RistrettoSig {}

impl StrictType for RistrettoSig {
const STRICT_LIB_NAME: &'static str = LIB_NAME_SSID;
}
impl StrictProduct for RistrettoSig {}
impl StrictTuple for RistrettoSig {
const FIELD_COUNT: u8 = 1;
}
impl StrictEncode for RistrettoSig {
fn strict_encode<W: TypedWrite>(&self, writer: W) -> io::Result<W> {
writer.write_newtype::<Self>(self.0.deref())
}
}
impl StrictDecode for RistrettoSig {
fn strict_decode(reader: &mut impl TypedRead) -> Result<Self, DecodeError> {
reader.read_tuple(|r| {
let data = r.read_field()?;
Ok(Self(Signature::new(data)))
})
}
}
impl StrictDumb for RistrettoSig {
fn strict_dumb() -> Self { Self(Signature::new([0xFAu8; 64])) }
}
Loading

0 comments on commit fe9d5d0

Please sign in to comment.