#[macro_use]
extern crate amplify;
use std::fmt::{self, Debug, Display, Formatter};
use std::io;
use std::io::{Read, Write};
use std::str::FromStr;
use std::string::FromUtf8Error;
use amplify::hex::ToHex;
use bech32::{FromBase32, ToBase32};
use bitcoin_hashes::{sha256, sha256d, Hash};
use secp256k1::{rand, Message, SECP256K1};
use strict_encoding::{StrictDecode, StrictEncode};
#[derive(Clone, Eq, PartialEq, Debug, Display, Error)]
#[display("unknown algorithm {0}")]
pub struct UnrecognizedAlgo(pub String);
#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
pub enum CertError {
#[display("incorrect bech32(m) string due to {0}")]
#[from]
Bech32(bech32::Error),
#[display(
"unrecognized certificate of `{0}` type; only `{1}1...` strings are \
supported"
)]
InvalidHrp(String, &'static str),
#[display("certificates require bech32m encoding")]
InvalidVariant,
#[display("mnemonic guard does not match certificate nym {0}")]
InvalidMnemonic(String),
#[display("provided certificate contains incomplete data")]
#[from(strict_encoding::Error)]
IncompleteData,
#[display(
"certificate uses unknown cryptographic algorithm; try to update the \
tool version"
)]
UnknownAlgo,
#[display(inner)]
#[from]
Utf8(FromUtf8Error),
}
#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
pub enum VerifyError {
#[display(
"signature algorithm does not match digital identity certificate"
)]
AlgoMismatch,
#[display("invalid signature")]
#[from(secp256k1::Error)]
InvalidSig,
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[derive(StrictEncode, StrictDecode)]
#[strict_encoding(by_value, repr = u8)]
#[repr(u8)]
pub enum HashAlgo {
#[display("sha256d")]
Sha256d = 2,
}
impl HashAlgo {
#[allow(clippy::len_without_is_empty)]
pub fn len(self) -> u8 {
match self {
Self::Sha256d => 32,
}
}
}
impl FromStr for HashAlgo {
type Err = UnrecognizedAlgo;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
s if s == Self::Sha256d.to_string() => Ok(Self::Sha256d),
wrong => Err(UnrecognizedAlgo(wrong.to_owned())),
}
}
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[derive(StrictEncode, StrictDecode)]
#[strict_encoding(by_value, repr = u8)]
#[repr(u8)]
pub enum EcAlgo {
#[display("bip340")]
Bip340 = 1,
#[display("ed25519")]
Ed25519 = 2,
}
impl EcAlgo {
pub fn cert_len(self) -> usize { 1 + self.pub_len() + self.sig_len() }
pub fn prv_len(self) -> usize {
match self {
Self::Bip340 => 32,
Self::Ed25519 => 32,
}
}
pub fn pub_len(self) -> usize {
match self {
Self::Bip340 => 32,
Self::Ed25519 => 32,
}
}
pub fn sig_len(self) -> usize {
match self {
Self::Bip340 => 64,
Self::Ed25519 => 64,
}
}
pub fn decode(code: u8) -> Option<Self> {
Some(match code {
x if x == Self::Bip340 as u8 => Self::Bip340,
x if x == Self::Ed25519 as u8 => Self::Ed25519,
_ => return None,
})
}
pub fn encode(self) -> u8 { self as u8 }
}
impl FromStr for EcAlgo {
type Err = UnrecognizedAlgo;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"bip340" => Ok(Self::Bip340),
"ed25519" => Ok(Self::Ed25519),
wrong => Err(UnrecognizedAlgo(wrong.to_owned())),
}
}
}
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct IdentitySigner {
pub cert: IdentityCert,
prvkey: Box<[u8]>,
}
impl StrictEncode for IdentitySigner {
fn strict_encode<E: Write>(
&self,
mut e: E,
) -> Result<usize, strict_encoding::Error> {
let len = self.cert.strict_encode(&mut e)?;
e.write_all(&self.prvkey)?;
Ok(len + self.cert.algo.prv_len())
}
}
impl StrictDecode for IdentitySigner {
fn strict_decode<D: Read>(
mut d: D,
) -> Result<Self, strict_encoding::Error> {
let cert = IdentityCert::strict_decode(&mut d)?;
let mut prvkey = vec![0u8; cert.algo.prv_len()];
d.read_exact(&mut prvkey)?;
secp256k1::SecretKey::from_slice(&prvkey).map_err(secp_to_sten_err)?;
Ok(Self {
cert,
prvkey: Box::from(prvkey),
})
}
}
impl IdentitySigner {
pub fn new_bip340() -> Self {
let pair = secp256k1::KeyPair::new(SECP256K1, &mut rand::thread_rng());
let cert = IdentityCert::from(pair);
Self {
cert,
prvkey: Box::from(pair.secret_key().secret_bytes()),
}
}
pub fn sign(&self, msg: impl AsRef<[u8]>) -> SigCert {
match self.cert.algo {
EcAlgo::Bip340 => {
let sk = secp256k1::SecretKey::from_slice(&self.prvkey)
.expect("invalid private key");
let pair = secp256k1::KeyPair::from_secret_key(SECP256K1, &sk);
let msg =
Message::from_hashed_data::<sha256d::Hash>(msg.as_ref());
let sig = pair.sign_schnorr(msg);
SigCert {
hash: HashAlgo::Sha256d,
curve: EcAlgo::Bip340,
sig: Box::from(&sig[..]),
}
}
EcAlgo::Ed25519 => todo!("Ed25519 signatures"),
}
}
pub fn sign_stream(&self, mut input: impl Read) -> io::Result<SigCert> {
match self.cert.algo {
EcAlgo::Bip340 => {
let sk = secp256k1::SecretKey::from_slice(&self.prvkey)
.expect("invalid private key");
let pair = secp256k1::KeyPair::from_secret_key(SECP256K1, &sk);
let mut engine = sha256d::Hash::engine();
let mut buf = [0u8; 64];
loop {
let len = input.read(&mut buf)?;
if len == 0 {
break;
}
engine.write_all(&buf[..len])?;
}
let hash = sha256d::Hash::from_engine(engine);
let msg = Message::from_slice(&hash[..]).expect("hash");
let sig = pair.sign_schnorr(msg);
Ok(SigCert {
hash: HashAlgo::Sha256d,
curve: EcAlgo::Bip340,
sig: Box::from(&sig[..]),
})
}
EcAlgo::Ed25519 => todo!("Ed25519 signatures"),
}
}
}
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct IdentityCert {
algo: EcAlgo,
pubkey: Box<[u8]>,
sig: Box<[u8]>,
}
impl IdentityCert {
pub fn nym(&self) -> String {
let mut mnemonic = Vec::with_capacity(64);
let mut crc32data = Vec::with_capacity(self.algo.cert_len() as usize);
crc32data.push(self.algo.encode());
crc32data.extend(&*self.pubkey);
let crc32 = crc32fast::hash(&crc32data);
mnemonic::encode(crc32.to_be_bytes(), &mut mnemonic)
.expect("mnemonic encoding");
String::from_utf8(mnemonic)
.expect("mnemonic library error")
.replace('-', "_")
}
pub fn fingerprint(&self) -> String {
let mut s = format!("{:#}", self);
let _ = s.split_off(6);
s
}
}
impl StrictEncode for IdentityCert {
fn strict_encode<E: Write>(
&self,
mut e: E,
) -> Result<usize, strict_encoding::Error> {
self.algo.strict_encode(&mut e)?;
e.write_all(&self.pubkey)?;
e.write_all(&self.sig)?;
Ok(self.algo.cert_len() as usize)
}
}
impl StrictDecode for IdentityCert {
fn strict_decode<D: Read>(
mut d: D,
) -> Result<Self, strict_encoding::Error> {
let algo = EcAlgo::strict_decode(&mut d)?;
let mut pubkey = vec![0u8; algo.pub_len()];
let mut sig = vec![0u8; algo.sig_len()];
d.read_exact(&mut pubkey)?;
d.read_exact(&mut sig)?;
secp256k1::XOnlyPublicKey::from_slice(&pubkey)
.map_err(secp_to_sten_err)?;
secp256k1::schnorr::Signature::from_slice(&sig)
.map_err(secp_to_sten_err)?;
Ok(Self {
algo,
pubkey: Box::from(pubkey),
sig: Box::from(sig),
})
}
}
impl Debug for IdentityCert {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
writeln!(f, "nym {}", self.nym())?;
writeln!(f, "fgp {}", self.fingerprint())?;
writeln!(f, "crv {}", self.algo)?;
writeln!(f, "idk {}", bin_fmt(&self.pubkey))?;
writeln!(f, "sig {}", bin_fmt(&self.sig))?;
Ok(())
}
}
impl Display for IdentityCert {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let data = self
.strict_serialize()
.expect("strict encoding of certificate");
let s =
bech32::encode("crt", data.to_base32(), bech32::Variant::Bech32m)
.expect("bech32 encoding of certificate");
if f.alternate() {
f.write_str(&s[s.len() - 6..])?;
} else {
f.write_str(&s)?;
}
f.write_str("_")?;
f.write_str(&self.nym())
}
}
impl FromStr for IdentityCert {
type Err = CertError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (b32, mnem) = s.split_once('_').unwrap_or((s, ""));
let (hrp, encoded, variant) = bech32::decode(b32)?;
if hrp != "crt" {
return Err(CertError::InvalidHrp(hrp, "crt"));
}
if variant != bech32::Variant::Bech32m {
return Err(CertError::InvalidVariant);
}
let data = Vec::<u8>::from_base32(&encoded)?;
let cert = Self::strict_deserialize(data)?;
let nym = cert.nym();
if !mnem.is_empty() && cert.nym() != mnem {
return Err(CertError::InvalidMnemonic(nym));
}
Ok(cert)
}
}
impl From<secp256k1::KeyPair> for IdentityCert {
fn from(pair: secp256k1::KeyPair) -> Self {
let pubkey = pair.x_only_public_key().0.serialize();
let msg = secp256k1::Message::from_hashed_data::<sha256::Hash>(&pubkey);
let sig = pair.sign_schnorr(msg);
IdentityCert {
algo: EcAlgo::Bip340,
pubkey: Box::from(&pubkey[..]),
sig: Box::from(&sig[..]),
}
}
}
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct SigCert {
hash: HashAlgo,
curve: EcAlgo,
sig: Box<[u8]>,
}
impl SigCert {
pub fn verify(
&self,
cert: &IdentityCert,
msg: impl AsRef<[u8]>,
) -> Result<(), VerifyError> {
if cert.algo != self.curve {
return Err(VerifyError::AlgoMismatch);
}
match self.curve {
EcAlgo::Bip340 => {
let sig = secp256k1::schnorr::Signature::from_slice(&self.sig)
.expect("broken signature data");
let msg = match self.hash {
HashAlgo::Sha256d => {
Message::from_hashed_data::<sha256d::Hash>(msg.as_ref())
}
};
let pubkey =
secp256k1::XOnlyPublicKey::from_slice(&cert.pubkey)
.expect("broken pubkey");
sig.verify(&msg, &pubkey)?;
}
EcAlgo::Ed25519 => todo!("Ed25519 signature verification"),
}
Ok(())
}
}
impl StrictEncode for SigCert {
fn strict_encode<E: Write>(
&self,
mut e: E,
) -> Result<usize, strict_encoding::Error> {
let mut len = self.hash.strict_encode(&mut e)?;
len += self.curve.strict_encode(&mut e)?;
e.write_all(&self.sig)?;
Ok(len + self.curve.sig_len())
}
}
impl StrictDecode for SigCert {
fn strict_decode<D: Read>(
mut d: D,
) -> Result<Self, strict_encoding::Error> {
let hash = HashAlgo::strict_decode(&mut d)?;
let curve = EcAlgo::strict_decode(&mut d)?;
let mut sig = vec![0u8; curve.sig_len()];
d.read_exact(&mut sig)?;
secp256k1::schnorr::Signature::from_slice(&sig)
.map_err(secp_to_sten_err)?;
Ok(Self {
hash,
curve,
sig: Box::from(sig),
})
}
}
impl Debug for SigCert {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
writeln!(f, "dig {}", self.hash)?;
writeln!(f, "crv {}", self.curve)?;
writeln!(f, "sig {}", bin_fmt(&self.sig))?;
Ok(())
}
}
impl Display for SigCert {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let data = self
.strict_serialize()
.expect("strict encoding of signature");
let s =
bech32::encode("sig", data.to_base32(), bech32::Variant::Bech32m)
.expect("bech32 encoding of signature");
f.write_str(&s)
}
}
impl FromStr for SigCert {
type Err = CertError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (hrp, encoded, variant) = bech32::decode(s)?;
if hrp != "sig" {
return Err(CertError::InvalidHrp(hrp, "sig"));
}
if variant != bech32::Variant::Bech32m {
return Err(CertError::InvalidVariant);
}
let data = Vec::<u8>::from_base32(&encoded)?;
Self::strict_deserialize(data).map_err(CertError::from)
}
}
fn bin_fmt(v: &[u8]) -> String {
v.to_hex()
.chars()
.enumerate()
.flat_map(|(i, c)| {
match i {
0 => None,
i if i % (4 * 16) == 0 => Some('\n'),
i if i % 4 == 0 => Some(' '),
_ => None,
}
.into_iter()
.chain(std::iter::once(c))
})
.collect::<String>()
.replace('\n', "\n ")
}
fn secp_to_sten_err(err: secp256k1::Error) -> strict_encoding::Error {
strict_encoding::Error::DataIntegrityError(format!(
"broken elliptic curve data. Details: {}",
err
))
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use secp256k1::SECP256K1;
use crate::{IdentityCert, IdentitySigner, SigCert};
fn cert() -> IdentityCert {
IdentityCert::from_str("crt1q9umuen7l8wthtz45p3ftn58pvrs9xlumvkuu2xet8egzkcklqte3fc3pu8qq6p0qx48fjttj7ecfcemry5r7yqlnfna6qhf4s46r2aw68wqc9spn4a465x54zy03gleun58fcz3tpxhqcg5nv4ssgyeysxq8t50zt_venice_vega_balloon").unwrap()
}
fn sig() -> SigCert {
SigCert::from_str("sig1qgqm0d5l8sjas4v2vk3tzqc5m2ltpkv208uf2chyh3fqmhwq3rdnu3ve0gkytrl7wl68075zxxukq9ff6gmd38w7hmtdas089jefkf2rsyasksse").unwrap()
}
#[test]
fn cert_create() {
let pair = secp256k1::KeyPair::from_seckey_slice(
&SECP256K1,
&secp256k1::ONE_KEY[..],
)
.unwrap();
let _ = IdentityCert::from(pair);
}
#[test]
fn cert_display() {
let cert = cert();
assert_eq!(cert.nym(), "venice_vega_balloon");
assert_eq!(cert.fingerprint(), "8t50zt");
assert_eq!(format!("{}", cert), "crt1q9umuen7l8wthtz45p3ftn58pvrs9xlumvkuu2xet8egzkcklqte3fc3pu8qq6p0qx48fjttj7ecfcemry5r7yqlnfna6qhf4s46r2aw68wqc9spn4a465x54zy03gleun58fcz3tpxhqcg5nv4ssgyeysxq8t50zt_venice_vega_balloon");
assert_eq!(format!("{:#}", cert), "8t50zt_venice_vega_balloon");
assert_eq!(
format!("{:?}", cert),
"\
nym venice_vega_balloon
fgp 8t50zt
crv bip340
idk 79be 667e f9dc bbac 55a0 6295 ce87 0b07 029b fcdb 2dce 28d9 59f2 815b \
16f8 1798
sig a711 0f0e 0068 2f01 aa74 c96b 97b3 84e3 3b19 283f 101f 9a67 dd02 e9ac \
2ba1 abae
d1dc 0c16 019d 7b5d 50d4 a888 f8a3 f9e4 e874 e051 584d 7061 149b 2b08 \
2099 240c
"
);
}
#[test]
fn sign() {
let me = IdentitySigner::new_bip340();
let msg = "This is me";
let sig = me.sign(msg);
sig.verify(&me.cert, msg).unwrap();
}
#[test]
#[should_panic(expected = "InvalidSig")]
fn wrong_sig_msg() {
let me = IdentitySigner::new_bip340();
let msg = "This is me";
let sig = me.sign(msg);
sig.verify(&me.cert, "This is not me").unwrap();
}
#[test]
#[should_panic(expected = "InvalidSig")]
fn wrong_sig_key() {
let me = IdentitySigner::new_bip340();
let other = IdentitySigner::new_bip340();
let msg = "This is me";
let sig = me.sign(msg);
sig.verify(&other.cert, msg).unwrap();
}
#[test]
fn sig_display() {
let sig = sig();
assert_eq!(format!("{}", sig), "sig1qgqm0d5l8sjas4v2vk3tzqc5m2ltpkv208uf2chyh3fqmhwq3rdnu3ve0gkytrl7wl68075zxxukq9ff6gmd38w7hmtdas089jefkf2rsyasksse");
assert_eq!(format!("{:#}", sig), "sig1qgqm0d5l8sjas4v2vk3tzqc5m2ltpkv208uf2chyh3fqmhwq3rdnu3ve0gkytrl7wl68075zxxukq9ff6gmd38w7hmtdas089jefkf2rsyasksse");
assert_eq!(
format!("{:?}", sig),
"\
dig sha256d
crv bip340
sig b7b6 9f3c 25d8 558a 65a2 b103 14da beb0 d98a 79f8 9562 e4bc 520d ddc0 \
88db 3e45
997a 2c45 8ffe 77f4 77fa 8231 b960 1529 d236 d89d debe d6de c1e7 2cb2 \
9b25 4381
"
);
}
}