use crate::imp::core::{AttestationChallenge, Bytes32, Bytes48, Bytes96, Tombstone};
use crate::imp::crypto::error::{BlsError, CryptoError};
use chia_bls::{
sign as aug_sign, verify as aug_verify, PublicKey as ChiaPublicKey, SecretKey as ChiaSecretKey,
Signature as ChiaSignature,
};
pub struct SecretKey(ChiaSecretKey);
#[derive(Clone)]
pub struct PublicKey(ChiaPublicKey);
#[derive(Clone)]
pub struct Signature(ChiaSignature);
pub type BlsSecretKey = SecretKey;
pub type BlsPublicKey = PublicKey;
impl SecretKey {
pub fn from_seed(seed: &[u8]) -> Self {
SecretKey(ChiaSecretKey::from_seed(seed))
}
pub fn public_key(&self) -> PublicKey {
PublicKey(self.0.public_key())
}
pub fn sign(&self, msg: &[u8]) -> Signature {
Signature(aug_sign(&self.0, msg))
}
}
impl PublicKey {
pub fn to_bytes(&self) -> Bytes48 {
Bytes48(self.0.to_bytes())
}
pub fn from_bytes(b: &Bytes48) -> Result<Self, CryptoError> {
ChiaPublicKey::from_bytes(&b.0)
.map(PublicKey)
.map_err(|_| CryptoError::Bls(BlsError::InvalidPublicKey))
}
pub fn verify(&self, msg: &[u8], sig: &Signature) -> bool {
aug_verify(&sig.0, &self.0, msg)
}
}
impl Signature {
pub fn to_bytes(&self) -> Bytes96 {
Bytes96(self.0.to_bytes())
}
pub fn from_bytes(b: &Bytes96) -> Result<Self, CryptoError> {
ChiaSignature::from_bytes(&b.0)
.map(Signature)
.map_err(|_| CryptoError::Bls(BlsError::InvalidSignature))
}
}
pub fn bls_keygen(seed: &[u8; 32]) -> (SecretKey, Bytes48) {
let sk = SecretKey::from_seed(seed);
let pk = sk.public_key().to_bytes();
(sk, pk)
}
pub fn bls_sign(sk: &SecretKey, msg: &[u8]) -> Bytes96 {
sk.sign(msg).to_bytes()
}
pub fn bls_verify(pk: &Bytes48, msg: &[u8], sig: &Bytes96) -> bool {
let pk = match PublicKey::from_bytes(pk) {
Ok(p) => p,
Err(_) => return false,
};
let sig = match Signature::from_bytes(sig) {
Ok(s) => s,
Err(_) => return false,
};
pk.verify(msg, &sig)
}
const G1_INFINITY: [u8; 48] = {
let mut a = [0u8; 48];
a[0] = 0xc0;
a
};
pub fn validate_public_key(pk: &Bytes48) -> Result<(), BlsError> {
if pk.0 == G1_INFINITY {
return Err(BlsError::InvalidPublicKey);
}
match PublicKey::from_bytes(pk) {
Ok(_) => Ok(()),
Err(_) => Err(BlsError::InvalidPublicKey),
}
}
pub const PUSH_DST: &[u8] = b"digstore:push:v1";
pub const NODE_DST: &[u8] = b"digstore:node:v1";
pub const TOMB_DST: &[u8] = b"digstore:tomb:v1";
pub const REQ_DST: &[u8] = b"digstore:req:v1";
pub use crate::imp::core::ATTEST_DST;
pub fn push_signing_message(root: &Bytes32, store_id: &Bytes32) -> [u8; 32] {
let mut buf = Vec::with_capacity(PUSH_DST.len() + 64);
buf.extend_from_slice(PUSH_DST);
buf.extend_from_slice(&root.0);
buf.extend_from_slice(&store_id.0);
crate::imp::crypto::sha256(&buf).0
}
pub fn node_signing_message(
program_hash: &Bytes32,
public_output: &Bytes32,
chia_header_hash: &Bytes32,
height: u32,
public_input: &[u8],
) -> Vec<u8> {
let mut msg = Vec::with_capacity(NODE_DST.len() + 100 + public_input.len());
msg.extend_from_slice(NODE_DST);
msg.extend_from_slice(&program_hash.0);
msg.extend_from_slice(&public_output.0);
msg.extend_from_slice(&chia_header_hash.0);
msg.extend_from_slice(&height.to_be_bytes());
msg.extend_from_slice(public_input);
msg
}
pub fn attestation_signing_message(
nonce: &[u8; 32],
store_id: &[u8; 32],
timestamp: u64,
) -> Vec<u8> {
let mut msg = Vec::with_capacity(ATTEST_DST.len() + 72);
msg.extend_from_slice(ATTEST_DST);
msg.extend_from_slice(nonce);
msg.extend_from_slice(store_id);
msg.extend_from_slice(×tamp.to_be_bytes());
msg
}
pub fn tombstone_signing_message(t: &Tombstone) -> [u8; 32] {
let canonical = t.canonical();
let mut buf = Vec::with_capacity(TOMB_DST.len() + canonical.len());
buf.extend_from_slice(TOMB_DST);
buf.extend_from_slice(&canonical);
crate::imp::crypto::sha256(&buf).0
}
pub fn request_signing_message(
method: &str,
store_id: &Bytes32,
timestamp: u64,
nonce: &[u8; 32],
) -> [u8; 32] {
let mut buf = Vec::with_capacity(REQ_DST.len() + 4 + method.len() + 32 + 8 + 32);
buf.extend_from_slice(REQ_DST);
buf.extend_from_slice(&(method.len() as u32).to_be_bytes());
buf.extend_from_slice(method.as_bytes());
buf.extend_from_slice(&store_id.0);
buf.extend_from_slice(×tamp.to_be_bytes());
buf.extend_from_slice(nonce);
crate::imp::crypto::sha256(&buf).0
}
pub fn sign_push(sk: &SecretKey, root: &Bytes32, store_id: &Bytes32) -> Bytes96 {
bls_sign(sk, &push_signing_message(root, store_id))
}
pub fn verify_push(pk: &PublicKey, root: &Bytes32, store_id: &Bytes32, sig: &Bytes96) -> bool {
let sig = match Signature::from_bytes(sig) {
Ok(s) => s,
Err(_) => return false,
};
pk.verify(&push_signing_message(root, store_id), &sig)
}
pub fn sign_request(
sk: &SecretKey,
method: &str,
store_id: &Bytes32,
timestamp: u64,
nonce: &[u8; 32],
) -> Bytes96 {
bls_sign(
sk,
&request_signing_message(method, store_id, timestamp, nonce),
)
}
pub fn verify_request(
pk: &PublicKey,
method: &str,
store_id: &Bytes32,
timestamp: u64,
nonce: &[u8; 32],
sig: &Bytes96,
) -> bool {
let sig = match Signature::from_bytes(sig) {
Ok(s) => s,
Err(_) => return false,
};
pk.verify(
&request_signing_message(method, store_id, timestamp, nonce),
&sig,
)
}
#[allow(clippy::too_many_arguments)]
pub fn sign_node(
node_sk: &SecretKey,
program_hash: &Bytes32,
public_output: &Bytes32,
chia_header_hash: &Bytes32,
height: u32,
public_input: &[u8],
) -> Bytes96 {
let msg = node_signing_message(
program_hash,
public_output,
chia_header_hash,
height,
public_input,
);
bls_sign(node_sk, &msg)
}
pub fn sign_attestation(host_sk: &SecretKey, challenge: &AttestationChallenge) -> Bytes96 {
let msg =
attestation_signing_message(&challenge.nonce, &challenge.store_id, challenge.timestamp);
bls_sign(host_sk, &msg)
}
pub fn sign_tombstone(sk: &SecretKey, t: &Tombstone) -> Bytes96 {
bls_sign(sk, &tombstone_signing_message(t))
}
pub fn verify_tombstone(pk: &PublicKey, t: &Tombstone, sig: &Bytes96) -> bool {
let sig = match Signature::from_bytes(sig) {
Ok(s) => s,
Err(_) => return false,
};
pk.verify(&tombstone_signing_message(t), &sig)
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod request_auth_tests {
use super::*;
fn keypair(seed: u8) -> (SecretKey, PublicKey) {
let (sk, pk_bytes) = bls_keygen(&[seed; 32]);
(sk, PublicKey::from_bytes(&pk_bytes).unwrap())
}
#[test]
fn request_signature_round_trips() {
let (sk, pk) = keypair(7);
let store = Bytes32([9u8; 32]);
let nonce = [3u8; 32];
let sig = sign_request(&sk, "module", &store, 1_700_000_000, &nonce);
assert!(verify_request(
&pk,
"module",
&store,
1_700_000_000,
&nonce,
&sig
));
}
#[test]
fn bls_role_dsts_are_pairwise_distinct_and_node_tag_coincidence_is_intentional() {
let role_dsts: [(&str, &[u8]); 5] = [
("push", PUSH_DST),
("node", NODE_DST),
("attest", ATTEST_DST),
("tomb", TOMB_DST),
("req", REQ_DST),
];
for (i, (na, a)) in role_dsts.iter().enumerate() {
for (nb, b) in role_dsts.iter().skip(i + 1) {
assert_ne!(a, b, "BLS role DSTs {na} and {nb} must be distinct");
}
}
assert_eq!(
NODE_DST,
crate::imp::core::merkle::NODE_TAG,
"NODE_DST intentionally coincides with the merkle NODE_TAG (disjoint \
preimage shapes); if this ever changes, re-review the safety note on NODE_DST"
);
for (name, dst) in role_dsts {
assert_ne!(
dst,
crate::imp::core::merkle::LEAF_TAG,
"role DST {name} must not coincide with the merkle LEAF_TAG"
);
}
}
#[test]
fn request_signature_is_bound_to_method_store_time_and_nonce() {
let (sk, pk) = keypair(7);
let store = Bytes32([9u8; 32]);
let nonce = [3u8; 32];
let ts = 1_700_000_000;
let sig = sign_request(&sk, "module", &store, ts, &nonce);
assert!(!verify_request(&pk, "push", &store, ts, &nonce, &sig));
assert!(!verify_request(
&pk,
"module",
&Bytes32([8u8; 32]),
ts,
&nonce,
&sig
));
assert!(!verify_request(&pk, "module", &store, ts + 1, &nonce, &sig));
assert!(!verify_request(&pk, "module", &store, ts, &[4u8; 32], &sig));
}
#[test]
fn request_message_differs_from_push_message_for_same_bytes() {
let store = Bytes32([9u8; 32]);
let root = Bytes32([9u8; 32]);
let nonce = [0u8; 32];
assert_ne!(
request_signing_message("push", &store, 0, &nonce),
push_signing_message(&root, &store)
);
}
}