Skip to content

Commit

Permalink
support Ed25519
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-orlovsky committed Apr 24, 2024
1 parent 353ab60 commit d21ea33
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 84 deletions.
17 changes: 17 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ required-features = ["cli"]
[dependencies]
amplify = "4.6.0"
secp256k1 = { version = "0.29.0", features = ["rand", "global-context", "rand-std"] }
ec25519 = "0.1.0"
rand = "0.8.5"
chrono = "0.4.38"
clap = { version = "4.5.4", features = ["derive"], optional = true }
Expand Down
90 changes: 70 additions & 20 deletions src/bip340.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,72 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::cmp::Ordering;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher};
use std::str::FromStr;

use secp256k1::schnorr::Signature;
use secp256k1::{Keypair, Message, XOnlyPublicKey, SECP256K1};
use secp256k1::{Keypair, Message, SecretKey, XOnlyPublicKey, SECP256K1};

use crate::{Algo, Chain, InvalidPubkey, InvalidSig, SsiPub, SsiSecret, SsiSig};
use crate::baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str};
use crate::{Algo, Chain, InvalidPubkey, InvalidSig, SsiPub, SsiSig};

impl TryFrom<SsiPub> for XOnlyPublicKey {
type Error = InvalidPubkey;
#[derive(Clone, Eq, PartialEq)]
pub struct Bip340Secret(pub(crate) SecretKey);

fn try_from(ssi: SsiPub) -> Result<Self, Self::Error> {
Self::from_slice(&<[u8; 32]>::from(ssi)).map_err(|_| InvalidPubkey)
}
impl Ord for Bip340Secret {
fn cmp(&self, other: &Self) -> Ordering { self.0.secret_bytes().cmp(&other.0.secret_bytes()) }
}

impl SsiPub {
pub fn from_bip340(key: XOnlyPublicKey) -> Self {
let bytes = key.serialize();
Self::from(bytes)
impl PartialOrd for Bip340Secret {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}

impl Hash for Bip340Secret {
fn hash<H: Hasher>(&self, state: &mut H) { self.0.secret_bytes().hash(state) }
}

impl DisplayBaid64 for Bip340Secret {
const HRI: &'static str = "ssi:bip340-priv";
const CHUNKING: bool = false;
const PREFIX: bool = true;
const EMBED_CHECKSUM: bool = true;
const MNEMONIC: bool = false;

fn to_baid64_payload(&self) -> [u8; 32] { <[u8; 32]>::from(self.clone()) }
}

impl FromBaid64Str for Bip340Secret {}

impl From<Bip340Secret> for [u8; 32] {
fn from(ssi: Bip340Secret) -> Self { ssi.0.secret_bytes() }
}

impl From<[u8; 32]> for Bip340Secret {
fn from(value: [u8; 32]) -> Self {
Self(SecretKey::from_slice(&value).expect("invalid secret key"))
}
}

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

impl FromStr for Bip340Secret {
type Err = Baid64ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_baid64_str(s) }
}

impl Bip340Secret {
pub fn new(chain: Chain) -> Self {
use rand::thread_rng;
loop {
let sk = secp256k1::SecretKey::new(&mut thread_rng());
let sk = SecretKey::new(&mut thread_rng());
let (pk, _) = sk.x_only_public_key(SECP256K1);
let data = pk.serialize();
if data[30] == u8::from(Algo::Bip340) && data[31] == u8::from(chain) {
let mut key = [0u8; 30];
key.copy_from_slice(&data[0..30]);
return Self(sk);
}
}
Expand All @@ -68,12 +104,26 @@ impl SsiSecret {
}
}

impl SsiSig {
pub fn verify(&self, ssi: SsiPub, msg: [u8; 32]) -> Result<(), InvalidSig> {
let sig = Signature::from_slice(&self.0).map_err(|_| InvalidSig::InvalidData)?;
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 msg = Message::from_digest(msg);
let pk =
XOnlyPublicKey::from_slice(&ssi.to_byte_array()).map_err(|_| InvalidSig::InvalidSsi)?;
let pk = XOnlyPublicKey::try_from(self)?;
sig.verify(&msg, &pk).map_err(|_| InvalidSig::InvalidSig)
}
}

impl TryFrom<SsiPub> for XOnlyPublicKey {
type Error = InvalidPubkey;

fn try_from(ssi: SsiPub) -> Result<Self, Self::Error> {
Self::from_slice(&<[u8; 32]>::from(ssi)).map_err(|_| InvalidPubkey)
}
}

impl SsiPub {
pub fn from_bip340(key: XOnlyPublicKey) -> Self {
let bytes = key.serialize();
Self::from(bytes)
}
}
127 changes: 127 additions & 0 deletions src/ed25519.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// 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::cmp::Ordering;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::str::FromStr;

use ec25519::{KeyPair, Noise, PublicKey, SecretKey, Seed, Signature};

use crate::baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str};
use crate::{Algo, Chain, InvalidPubkey, InvalidSig, SsiPub, SsiSig};

#[derive(Clone, Eq, PartialEq)]
pub struct Ed25519Secret(pub(crate) SecretKey);

impl Ord for Ed25519Secret {
fn cmp(&self, other: &Self) -> Ordering { self.0.as_slice().cmp(&other.0.as_slice()) }
}

impl PartialOrd for Ed25519Secret {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}

impl Hash for Ed25519Secret {
fn hash<H: Hasher>(&self, state: &mut H) { self.0.as_slice().hash(state) }
}

impl DisplayBaid64<64> for Ed25519Secret {
const HRI: &'static str = "ssi:ed25519-priv";
const CHUNKING: bool = false;
const PREFIX: bool = true;
const EMBED_CHECKSUM: bool = true;
const MNEMONIC: bool = false;

fn to_baid64_payload(&self) -> [u8; 64] { <[u8; 64]>::from(self.clone()) }
}

impl FromBaid64Str<64> for Ed25519Secret {}

impl From<Ed25519Secret> for [u8; 64] {
fn from(ssi: Ed25519Secret) -> Self { *ssi.0.deref() }
}

impl From<[u8; 64]> for Ed25519Secret {
fn from(value: [u8; 64]) -> Self {
Self(SecretKey::from_slice(&value).expect("invalid secret key"))
}
}

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

impl FromStr for Ed25519Secret {
type Err = Baid64ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_baid64_str(s) }
}

impl Ed25519Secret {
pub fn new(chain: Chain) -> Self {
loop {
let pair = KeyPair::from_seed(Seed::generate());
let pk = pair.pk;

let sig = pair.sk.sign("test", Some(Noise::generate()));
pk.verify("test", &sig).expect("unable to create key");

if pk[30] == u8::from(Algo::Ed25519) && pk[31] == u8::from(chain) {
return Self(pair.sk);
}
}
}

pub fn to_public(&self) -> SsiPub {
let pk = self.0.public_key();
SsiPub::from(*pk)
}

pub fn sign(&self, msg: [u8; 32]) -> SsiSig {
let sig = self.0.sign(msg, None);
SsiSig(*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 pk = PublicKey::try_from(self)?;
pk.verify(&msg, &sig).map_err(|err| {
eprintln!("{err}");
InvalidSig::InvalidSig
})
}
}

impl TryFrom<SsiPub> for PublicKey {
type Error = InvalidPubkey;

fn try_from(ssi: SsiPub) -> Result<Self, Self::Error> {
Self::from_slice(&<[u8; 32]>::from(ssi)).map_err(|_| InvalidPubkey)
}
}

impl SsiPub {
pub fn from_ed25519(key: PublicKey) -> Self { Self::from(*key) }
}
2 changes: 1 addition & 1 deletion src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ impl Ssi {
}

pub fn check_integrity(&self) -> Result<(), InvalidSig> {
self.sig.verify(self.pk, self.to_message())
self.pk.verify(self.to_message(), self.sig)
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ mod identity;
mod secret;
mod public;
mod bip340;
mod ed25519;

mod runtime;

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,
Expand Down
10 changes: 5 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub struct Args {
pub enum Command {
/// Generate a new identity - a pair of public and private keys.
New {
#[clap(short, long, default_value = "bip340")]
#[clap(short, long, default_value = "ed25519")]
algo: Algo,

#[clap(short, long, default_value = "bitcoin")]
Expand Down Expand Up @@ -69,7 +69,7 @@ fn main() {

match args.command {
Command::New {
algo: _,
algo,
chain,
prefix,
threads,
Expand All @@ -94,10 +94,10 @@ fn main() {
let passwd = rpassword::prompt_password("Password for private key encryption: ")
.expect("unable to read password");

eprintln!("Generating new identity....");
eprintln!("Generating new {algo} identity....");
let mut secret = match prefix {
Some(prefix) => SsiSecret::vanity(&prefix, chain, threads),
None => SsiSecret::new(chain),
Some(prefix) => SsiSecret::vanity(&prefix, algo, chain, threads),
None => SsiSecret::new(algo, chain),
};

let ssi = Ssi::new(uids, expiry, &secret);
Expand Down
Loading

0 comments on commit d21ea33

Please sign in to comment.