1. 引言
Boneh等人2018年論文《Compact Multi-Signatures for Smaller Blockchains》,論文解讀參見博客 Compact Multi-Signatures for Smaller Blockchains學習筆記。
其中第5章爲Discrete-Logarithm based multi-signature scheme MSDL,算法細節與Musig方案類似,詳細可參見博客 Musig方案代碼解析。【針對Schnorr signature。】
在本博客中主要解析的是https://github.com/lovesh/signature-schemes/bls
倉庫中基於pairing的multi-signature MSP/AMSP/ASM/PoP等算法實現。【針對BLS signature。】
2. 主要庫依賴及features
2.1 主要庫依賴
看https://github.com/lovesh/signature-schemes/tree/master/bls/Cargo.toml
中內容:
- https://github.com/lovesh/amcl_rust_wrapper:主要做了基礎運算的constant time 和variable time function封裝。支持的曲線主要有"bls381", “bn254”, “secp256k1”, “ed25519”。(具體可參見博客 Polynomial Commitments代碼實現【2】——lovesh/kzg-poly-commit 第1節內容。)
[dependencies.amcl_wrapper]
version = "0.2.1"
default-features = false
features = ["bls381"]
- rand:隨機數生成器,Musig方案中的隨機數要求爲strong random number。
- lazy_static:lazy_static是在第一次調用時(運行時)進行初始化。而非編譯時,而static是在編譯時進行初始化,運行階段分配內存空間。(實際未使用,可去除。)
- log:提供日誌接口。
- serde:A generic serialization/deserialization framework。
- serde_derive:Macros 1.1 implementation of #[derive(Serialize, Deserialize)]。
- secret_sharing:(https://github.com/lovesh/secret-sharing-schemes)主要用於輔助實現 threshold signatures,包含三組實現:
–1)需要trusted third party的Shamir secret sharing;
–2)不需要可信第三方的Pedersen verifiable secret sharing;【Torben P. Pedersen 1991年論文《Non-interactive and information-theoretic secure verifiable secret sharing》第4章。】
–3)不需要可信第三方的Pedersen decentralized verifiable secret sharing。Torben P. Pedersen 1991年論文《Non-interactive and information-theoretic secure verifiable secret sharing》第5章。】
2.2 features——公鑰和簽名group選擇
Bilinear group通常爲非對稱的,其中的1個group具有a more compact(緊湊的) representation。 pairing-based MSP/AMSP均要求public key和signature在不同的groups(即live in different groups)。
- 對於標準簽名來說,一個公鑰會對很多消息簽名,通常會選擇用更緊湊的group來表示signatures。
["SignatureG2"]
- 而在本論文中,由於需要同時支持signature aggregation和public key aggregation,則要根據實際應用來確定group選擇。
[features]
default = ["SignatureG2"]
SignatureG1 = [] # signature and message are in G1, verification key in G2
SignatureG2 = [] # signature and message are in G2, verification key in G1
["SignatureG2"]
:signature和message均在,verification key 在,對應的是驗籤很快,簽名略慢。默認爲["SignatureG2"]
以保證驗籤速度。["SignatureG1"]
:signature和message均在,verification key 在,對應的是簽名很快,驗籤略慢。
2.3 總體代碼結構
總體代碼結構爲:
- lib.rs:引用的amcl_wrapper庫針對不同group選擇所對應的pairing函數接口調用。
- common.rs:簽名key/驗籤key的結構體和初始化實現。
pub struct Keypair {
pub sig_key: SigKey, //私鑰
pub ver_key: VerKey, //公鑰
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VerKey {
pub point: VerkeyGroup,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SigKey {
pub x: FieldElement,
}
- multi_sig_fast.rs:需要通過proof of possession (PoP) 來抵抗rogue key attack。【需要naive方案+PoP,實際test函數有待參考博客 Compact Multi-Signatures for Smaller Blockchains學習筆記中第4節內容進一步改進。】
- multi_sig_slow.rs:藉助hash函數生成係數,可抵抗rogue key attack。簽名和驗籤速度都要比
multi_sig_fast.rs
慢。對應爲博客 Compact Multi-Signatures for Smaller Blockchains學習筆記中第2節內容。 - simple.rs:基礎Signature
new
/verify
/batch_verify
(不要求消息互不相同)和batch_verify_distinct_msgs
(要求消息互不相同)算法實現。 - threshold_sig.rs:基於Accountable-Subgroup Multi-signatures (ASM)的門限簽名實現。
3. 主要算法實現
3.1 Parameters Generation
let params = Params::new("test".as_bytes());
pub struct Params {
pub g: VerkeyGroup,
}
impl Params {
pub fn new(label: &[u8]) -> Self {
// NUMS
let g = VerkeyGroup::from_msg_hash(label);
Params { g }
}
}
3.2 Key Generation
let mut rng = thread_rng();
let keypair = Keypair::new(&mut rng, ¶ms);
let sk = keypair.sig_key;
let vk = keypair.ver_key;
pub struct Keypair {
pub sig_key: SigKey,
pub ver_key: VerKey,
}
impl Keypair {
pub fn new<R: Rng + CryptoRng>(rng: &mut R, params: &Params) -> Self {
let sk = SigKey::new(rng);
let vk = VerKey::from_sigkey(&sk, params);
Keypair {
sig_key: sk,
ver_key: vk,
}
}
}
3.3 Key Aggregation
let mut sigs_and_ver_keys: Vec<(Signature, VerKey)> = Vec::new();
let mut avk = AggregatedVerKey::from_verkeys(&sigs_and_ver_keys);
sigs_and_ver_keys.push((sig, v));
// This is a newer but SLOWER way of doing BLS signature aggregation. This is NOT VULNERABLE to
// rogue public key attack so does not need proof of possession.
pub struct AggregatedVerKey {}
impl AggregatedVerKey {
/// Hashes a verkey with all other verkeys using a Hash function `H:{0, 1}* -> Z_q`
/// Takes a verkey `vk_i` and all verkeys `vk_1, vk_2,...vk_n` (including `vk_i`) and calculates
/// `H(vk_i||vk_1||vk_2...||vk_i||...vk_n)`
pub(crate) fn hashed_verkey_for_aggregation<'a>(
ver_key: &VerKey,
all_ver_key_bytes: impl IntoIterator<Item = &'a (impl AsRef<[u8]> + 'a)>,
) -> FieldElement {
// TODO: Sort the verkeys in some order to avoid accidentally passing wrong order of keys
let mut res_vec: Vec<u8> = Vec::new();
res_vec.extend_from_slice(&ver_key.to_bytes());
for vk_bytes in all_ver_key_bytes.into_iter() {
res_vec.extend_from_slice(vk_bytes.as_ref());
}
Self::hash_verkeys(res_vec.as_slice())
}
/// Calculates the aggregated verkey
/// For each `v_i` of the verkeys `vk_1, vk_2,...vk_n` calculate
/// `a_i = vk_i * hashed_verkey_for_aggregation(vk_i, [vk_1, vk_2,...vk_n])`
/// Add all `a_i`
pub fn from_verkeys<'a, T, V>(ver_keys: T) -> VerKey
where
T: IntoIterator<Item = &'a V>,
T::IntoIter: Clone,
V: AsRef<VerKey> + 'a
{
let ver_keys = ver_keys.into_iter();
// TODO: Sort the verkeys in some order to avoid accidentally passing wrong order of keys
let vk_bytes: Vec<_> = ver_keys.clone().map(|x| x.as_ref().to_bytes()).collect();
let (hs, vks): (Vec<_>, Vec<_>) = ver_keys
.map(|vk| (
AggregatedVerKey::hashed_verkey_for_aggregation(vk.as_ref(), &vk_bytes),
vk.as_ref().point.clone(),
))
.unzip();
let avk = VerkeyGroupVec::from(vks)
.multi_scalar_mul_var_time(&hs.into())
.unwrap();
VerKey { point: avk }
}
/// Hash verkey bytes to field element. H_1 from the paper.
pub(crate) fn hash_verkeys(verkey_bytes: &[u8]) -> FieldElement {
FieldElement::from_msg_hash(&[&VERKEY_DOMAIN_PREFIX, verkey_bytes].concat())
}
}
3.4 Signing
let mut asig = MultiSignature::from_sigs(&sigs_and_ver_keys);
/// The aggregator needs to know of all the signer before it can generate the aggregate signature.
/// Takes individual signatures from each of the signers and their verkey and aggregates the
/// signatures. For each signature `s_i` from signer with verkey `v_i` calculate
/// `a_i = hashed_verkey_for_aggregation(vk_i, [vk_1, vk_2,...vk_n])`
/// `a_si = s_i * a_i`
/// Add all `a_si`.
/// An alternate construction is (as described in the paper) to let signer compute `s_i * a_i` and
/// the aggregator simply adds each signer's output. In that model, signer does more work but in the
/// implemented model, aggregator does more work and the same signer implementation can be used by
/// signers of "slow" and "fast" implementation.
pub fn from_sigs<'a, T, I>(sigs_and_ver_keys: T) -> Signature
where
T: IntoIterator<Item = &'a I>,
T::IntoIter: Clone,
I: AsRef<VerKey> + AsRef<Signature> + 'a
{
let sigs_and_ver_keys = sigs_and_ver_keys.into_iter();
// TODO: Sort the verkeys in some order to avoid accidentally passing wrong order of keys
let all_ver_key_bytes: Vec<_> = sigs_and_ver_keys
.clone()
.map(|x| AsRef::<VerKey>::as_ref(x).to_bytes())
.collect();
let (hs, sigs): (Vec<_>, Vec<_>) = sigs_and_ver_keys
.map(|x| (
AggregatedVerKey::hashed_verkey_for_aggregation(x.as_ref(), &all_ver_key_bytes),
AsRef::<Signature>::as_ref(x).point.clone(),
))
.unzip();
let asig = SignatureGroupVec::from(sigs)
.multi_scalar_mul_var_time(&hs.into())
.unwrap();
Signature { point: asig }
}
3.5 Multi-Signature Verification
assert!(MultiSignature::verify(&asig, &b, &sigs_and_ver_keys, ¶ms));
或者
assert!(asig.verify(&b, &avk, ¶ms));
/// An aggregate VerKey is created from `ver_keys`. When verifying signature using the same
/// set of keys frequently generate a verkey once and then use `Signature::verify`
pub fn verify<'a, T, K>(sig: &Signature, msg: &[u8], ver_keys: T, params: &Params) -> bool
where
T: IntoIterator<Item = &'a K>,
T::IntoIter: Clone,
K: AsRef<VerKey> + 'a
{
let avk = AggregatedVerKey::from_verkeys(ver_keys);
sig.verify(msg, &avk, params)
}
pub fn verify(&self, msg: &[u8], ver_key: &VerKey, params: &Params) -> bool {
// TODO: Check if point exists on curve, maybe use `ECP::new_big` and x cord of verkey
if self.point.is_identity() {
println!("Signature point at infinity");
return false;
}
let msg_hash_point = Self::hash_message(msg);
// e(self.point, params.g) == e(msg_hash_point, ver_key.point) =>
// e(msg_hash_point, ver_key.point) * e(self.point, params.g)^-1 == 1 =>
// e(msg_hash_point, ver_key.point) * e(self.point, params.g^-1) == 1
ate_2_pairing(
&msg_hash_point,
&ver_key.point,
&self.point,
¶ms.g.negation(),
)
.is_one()
}
3.6 Batch verification
/// Batch verification of signatures. Takes a vector of 3-tuple where each tuple has a message,
/// signature and public key. Messages can be same or different
pub fn batch_verify(msgs_sigs: Vec<(&[u8], &Signature, &VerKey)>, params: &Params) -> bool {
let r = FieldElement::random();
let r_vec = FieldElementVector::new_vandermonde_vector(&r, msgs_sigs.len());
let mut sigs = SignatureGroupVec::with_capacity(msgs_sigs.len());
let mut hs = vec![];
let mut vs = vec![];
for (i, (msg, sig, vk)) in msgs_sigs.iter().enumerate() {
sigs.push(sig.point.clone());
hs.push(Signature::hash_message(msg));
// The multiplication with &r_vec[i] can be moved to message instead of verkey but
// since verkey is in group G1 by default and operations in G1 are cheaper.
// A better way would be to have code conditional on features such that
// multiplication is moved to message when messages are in G1 and verkey in G2.
vs.push(&vk.point * &r_vec[i]);
}
let aggr_sig = sigs.multi_scalar_mul_var_time(&r_vec).unwrap();
let mut pairings = hs
.iter()
.zip(vs.iter())
.map(|(h, v)| (h, v))
.collect::<Vec<(&SignatureGroup, &VerkeyGroup)>>();
let neg_g = params.g.negation();
pairings.push((&aggr_sig, &neg_g));
ate_multi_pairing(pairings).is_one()
}
/// Batch verification of signatures. Takes a vector of 3-tuple where each tuple has a message,
/// signature and public key. Assumes all messages to be distinct
pub fn batch_verify_distinct_msgs(
msgs_sigs: Vec<(&[u8], &Signature, &VerKey)>,
params: &Params,
) -> bool {
let mut aggr_sig = SignatureGroup::new();
let mut hs = vec![];
let mut vs = vec![];
for (msg, sig, vk) in msgs_sigs {
aggr_sig += &sig.point;
hs.push(Signature::hash_message(msg));
vs.push(vk);
}
let mut pairings = hs
.iter()
.zip(vs)
.map(|(h, v)| (h, &v.point))
.collect::<Vec<(&SignatureGroup, &VerkeyGroup)>>();
let neg_g = params.g.negation();
pairings.push((&aggr_sig, &neg_g));
ate_multi_pairing(pairings).is_one()
}
3.7 Proof of Possession
即相當於用私鑰對公鑰進行簽名。
#[test]
fn proof_of_possession() {
let mut rng = thread_rng();
let params = Params::new("test".as_bytes());
let keypair = Keypair::new(&mut rng, ¶ms);
let sk = keypair.sig_key;
let vk = keypair.ver_key;
let proof = ProofOfPossession::generate(&vk, &sk);
assert!(ProofOfPossession::verify(&proof, &vk, ¶ms));
}
pub struct ProofOfPossession {}
impl ProofOfPossession {
// Used for domain separation while creating Proof of Possession
const PoP_DOMAIN_PREFIX: [u8; 2] = [2, 2];
pub fn generate(verkey: &VerKey, sigkey: &SigKey) -> Signature {
Signature::new(
&[&Self::PoP_DOMAIN_PREFIX, verkey.to_bytes().as_slice()].concat(),
&sigkey,
)
}
pub fn verify(proof: &Signature, verkey: &VerKey, params: &Params) -> bool {
proof.verify(
&[&Self::PoP_DOMAIN_PREFIX, verkey.to_bytes().as_slice()].concat(),
verkey,
params,
)
}
}