Skip to content

Commit

Permalink
signing messages
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-orlovsky committed Apr 24, 2024
1 parent d21ea33 commit 70f7b1f
Show file tree
Hide file tree
Showing 9 changed files with 311 additions and 41 deletions.
4 changes: 2 additions & 2 deletions src/baid64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,8 @@ impl<const LEN: usize> Display for Baid64Display<LEN> {

if self.chunking {
let bytes = s.as_bytes();
f.write_str(&String::from_utf8_lossy(&bytes[..6]))?;
for chunk in bytes[6..].chunks(8) {
f.write_str(&String::from_utf8_lossy(&bytes[..8]))?;
for chunk in bytes[8..].chunks(7) {
write!(f, "-{}", &String::from_utf8_lossy(chunk))?;
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/bip340.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl Hash for Bip340Secret {
}

impl DisplayBaid64 for Bip340Secret {
const HRI: &'static str = "ssi:bip340-priv";
const HRI: &'static str = "bip340-priv";
const CHUNKING: bool = false;
const PREFIX: bool = true;
const EMBED_CHECKSUM: bool = true;
Expand Down
2 changes: 1 addition & 1 deletion src/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl Hash for Ed25519Secret {
}

impl DisplayBaid64<64> for Ed25519Secret {
const HRI: &'static str = "ssi:ed25519-priv";
const HRI: &'static str = "ed25519-priv";
const CHUNKING: bool = false;
const PREFIX: bool = true;
const EMBED_CHECKSUM: bool = true;
Expand Down
2 changes: 1 addition & 1 deletion src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub enum UidParseError {
}

#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[display("{name} <{schema}:{id}>")]
#[display("{name} <{schema}:{id}>", alt = "{name} {schema}:{id}")]
pub struct Uid {
pub name: String,
pub schema: String,
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ pub use bip340::Bip340Secret;
pub use ed25519::Ed25519Secret;
pub use identity::{Ssi, SsiParseError, Uid};
pub use public::{
Algo, Chain, InvalidPubkey, InvalidSig, SsiPub, SsiSig, UnknownAlgo, UnknownChain,
Algo, Chain, Fingerprint, InvalidPubkey, InvalidSig, SsiCert, SsiPub, SsiQuery, SsiSig,
UnknownAlgo, UnknownChain,
};
pub use runtime::{Error, SsiRuntime, SSI_DIR};
pub use secret::SsiSecret;
pub use secret::{SecretParseError, SsiPair, SsiSecret};
99 changes: 93 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@
#[macro_use]
extern crate clap;

use std::fs;
use std::io::{stdin, Read};
use std::path::PathBuf;
use std::str::FromStr;

use chrono::DateTime;
use chrono::{DateTime, Utc};
use clap::Parser;
use ssi::{Algo, Chain, Ssi, SsiRuntime, SsiSecret, Uid};
use ssi::{Algo, Chain, InvalidSig, Ssi, SsiQuery, SsiRuntime, SsiSecret, Uid};

#[derive(Parser, Clone, Debug)]
pub struct Args {
Expand All @@ -37,37 +40,98 @@ pub struct Args {

#[derive(Subcommand, Clone, Debug)]
pub enum Command {
/// Generate a new identity - a pair of public and private keys.
/// Generate a new identity - a pair of public and private keys
New {
/// Signature algorithm to use
#[clap(short, long, default_value = "ed25519")]
algo: Algo,

/// Which blockchain should be used for key revocation
#[clap(short, long, default_value = "bitcoin")]
chain: Chain,

/// Vanity prefix
/// Vanity prefix: "mine" an identity starting with certain string
#[clap(long)]
prefix: Option<String>,

/// Number of threads to run vanity generation
#[clap(short, long, requires = "prefix", default_value = "8")]
threads: u8,

/// User identity information in form of "Name Surname ...
/// <schema:address>"
#[clap(long, required = true)]
uid: Vec<String>,

/// Create identity with no specific expiration date
#[clap(long, required_unless_present = "expiry")]
no_expiry: bool,

/// Set expiration date for the identity (in YYYY-MM-DD format)
#[clap(conflicts_with = "no_expiry", required_unless_present = "no_expiry")]
expiry: Option<String>,
},

List {
/// List only signing identities
#[clap(short, long)]
signing: bool,
},

/// Sign a file or a message
Sign {
/// Text message to sign
#[clap(short, long, conflicts_with = "file")]
text: Option<String>,

/// File to create a detached signature for
#[clap(short, long)]
file: Option<PathBuf>,

/// Identity to use for the signature
ssi: SsiQuery,
},
/*
Verify {
/// Signature certificate to verify
signature: SsiCert,
},
*/
}

fn main() {
let args = Args::parse();

let mut runtime = SsiRuntime::load().expect("unable to load data");

match args.command {
Command::List { signing } => {
let now = Utc::now();
for ssi in &runtime.identities {
if signing && !runtime.is_signing(ssi.pk.fingerprint()) {
continue;
}
print!("{}\t", ssi.pk);
match ssi.expiry {
None => print!("no expiry"),
Some(e) => print!("{}", e.format("%Y-%m-%d")),
}
print!("\t");
match ssi.check_integrity() {
Ok(_) if ssi.expiry >= Some(now) => println!("expired"),
Ok(_) => println!("valid"),
Err(InvalidSig::InvalidPubkey) => println!("invalid pubkey"),
Err(InvalidSig::InvalidSig) => println!("invalid"),
Err(InvalidSig::InvalidData) => println!("broken"),
Err(InvalidSig::UnsupportedAlgo(_)) => println!("unsupported"),
}
for uid in &ssi.uids {
println!("\t{uid}");
}
}
println!();
}

Command::New {
algo,
chain,
Expand All @@ -89,8 +153,6 @@ fn main() {
.collect::<Result<_, _>>()
.expect("invalid UID");

let mut runtime = SsiRuntime::load().expect("unable to load data");

let passwd = rpassword::prompt_password("Password for private key encryption: ")
.expect("unable to read password");

Expand All @@ -112,5 +174,30 @@ fn main() {

runtime.store().expect("unable to save data");
}

Command::Sign { text, file, ssi } => {
eprintln!("Signing with {ssi:?}");

let passwd = rpassword::prompt_password("Password for private key encryption: ")
.expect("unable to read password");
let msg = match (text, file) {
(Some(t), None) => t,
(None, Some(f)) => fs::read_to_string(f).expect("unable to read the file"),
(None, None) => {
let mut s = String::new();
stdin()
.read_to_string(&mut s)
.expect("unable to read standard input");
s
}
_ => unreachable!(),
};
let signer = runtime
.find_signer(ssi, &passwd)
.expect("unknown signing identity");
eprintln!("Using key {signer})");
let cert = signer.sign(msg);
println!("{cert}");
}
}
}
81 changes: 77 additions & 4 deletions src/public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use std::fmt::{self, Display, Formatter};
use std::hash::Hash;
use std::str::FromStr;

use amplify::{Bytes, Display};
use amplify::{Bytes, Bytes32, Display};

use crate::baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str};

Expand Down Expand Up @@ -105,7 +105,7 @@ impl FromStr for Chain {
impl From<Chain> for u8 {
fn from(chain: Chain) -> Self {
match chain {
Chain::Bitcoin => 0xBC,
Chain::Bitcoin => 0xB7,
Chain::Liquid => 0x10,
Chain::Other(v) => v,
}
Expand All @@ -115,7 +115,7 @@ impl From<Chain> for u8 {
impl From<u8> for Chain {
fn from(value: u8) -> Self {
match value {
0xBC => Chain::Bitcoin,
0xB7 => Chain::Bitcoin,
0x10 => Chain::Liquid,
n => Chain::Other(n),
}
Expand Down Expand Up @@ -163,6 +163,10 @@ impl SsiPub {
}
}

pub fn fingerprint(self) -> Fingerprint {
Fingerprint([self.key[0], self.key[1], self.key[2], self.key[3], self.key[4], self.key[5]])
}

pub fn to_byte_array(&self) -> [u8; 32] {
let mut buf = [0u8; 32];
buf[0..30].copy_from_slice(self.key.as_slice());
Expand All @@ -173,7 +177,13 @@ impl SsiPub {
}

impl Display for SsiPub {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.fmt_baid64(f) }
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if !f.alternate() {
self.fmt_baid64(f)
} else {
write!(f, "{}", self.fingerprint())
}
}
}

impl FromStr for SsiPub {
Expand Down Expand Up @@ -225,3 +235,66 @@ pub enum InvalidSig {
/// can't verify signature - unsupported signature method {0}.
UnsupportedAlgo(u8),
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, From)]
#[display(inner)]
pub enum SsiQuery {
#[from]
Pub(SsiPub),
#[from]
Fp(Fingerprint),
#[from]
Id(String),
}

impl FromStr for SsiQuery {
type Err = Baid64ParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() == 8 {
Fingerprint::from_str(s).map(Self::Fp)
} else if s.starts_with("ssi:") || (s.contains('-') && (s.len() == 48 || s.len() == 52)) {
SsiPub::from_str(s).map(Self::Pub)
} else {
Ok(SsiQuery::Id(s.to_owned()))
}
}
}

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, From)]
pub struct Fingerprint([u8; 6]);

impl DisplayBaid64<6> for Fingerprint {
const HRI: &'static str = "";
const CHUNKING: bool = false;
const PREFIX: bool = false;
const EMBED_CHECKSUM: bool = false;
const MNEMONIC: bool = false;

fn to_baid64_payload(&self) -> [u8; 6] { self.0 }
}

impl FromBaid64Str<6> for Fingerprint {}

impl Display for Fingerprint {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.fmt_baid64(f) }
}

impl FromStr for Fingerprint {
type Err = Baid64ParseError;

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

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct SsiCert {
pub fp: Fingerprint,
pub msg: Bytes32,
pub sig: SsiSig,
}

impl Display for SsiCert {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "ssi:{fp}?msg={msg}&sig={sig}", fp = self.fp, msg = self.msg, sig = self.sig)
}
}
39 changes: 37 additions & 2 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use std::io::{self, BufRead, Write};
use std::path::PathBuf;

use crate::baid64::Baid64ParseError;
use crate::{Ssi, SsiParseError, SsiSecret};
use crate::{Fingerprint, SecretParseError, Ssi, SsiPair, SsiParseError, SsiQuery, SsiSecret};

#[derive(Debug, Display, Error, From)]
#[display(inner)]
Expand All @@ -38,6 +38,9 @@ pub enum Error {
#[from]
Baid64(Baid64ParseError),

#[from]
Secret(SecretParseError),

#[from]
Ssi(SsiParseError),
}
Expand Down Expand Up @@ -107,5 +110,37 @@ impl SsiRuntime {
Ok(())
}

pub fn identities(&self) -> impl Iterator<Item = &Ssi> { self.identities.iter() }
pub fn find_identity(&self, query: impl Into<SsiQuery>) -> Option<&Ssi> {
let query = query.into();
self.identities.iter().find(|ssi| match query {
SsiQuery::Pub(pk) => ssi.pk == pk,
SsiQuery::Fp(fp) => ssi.pk.fingerprint() == fp,
SsiQuery::Id(ref id) => ssi.uids.iter().any(|uid| {
&uid.id == id ||
&uid.to_string() == id ||
&uid.name == id ||
&format!("{}:{}", uid.schema, uid.id) == id
}),
})
}

pub fn find_signer(&self, query: impl Into<SsiQuery>, passwd: &str) -> Option<SsiPair> {
let ssi = self.find_identity(query.into()).cloned()?;
let sk = self.secrets.iter().find_map(|s| {
let mut s = (*s).clone();
if !passwd.is_empty() {
s.decrypt(passwd);
}
if s.to_public() == ssi.pk {
Some(s)
} else {
None
}
})?;
Some(SsiPair::new(ssi, sk))
}

pub fn is_signing(&self, fp: Fingerprint) -> bool {
self.secrets.iter().any(|s| s.fingerprint() == fp)
}
}
Loading

0 comments on commit 70f7b1f

Please sign in to comment.