以太坊签名函数实操 | 函数式与区块链(三)
关于函数式编程:
函数式编程是有别于传统的面对对象范式的编程范式,函数式方向是目前编程语言发展的大方向,所有新设计的编程语言都或多或少的引入了函数式编程功能。
笔者认为,「下一代计算机学科体系」将基于函数式编程语言。因此,打好函数式编程基础对于具备「长期主义」思维的程序员是必要的。
关于本专栏:
本专栏将通过实战代码分析与经典著作解读,分享作者关于函数式编程与区块链的思考与实践。就目前而言,本专栏将基于两种函数式语言:Rust 和 Elixir,有时候会提及其它语言,作为辅助性参考。
前文索引:
本文描述 Elixir 与 Rust 两种函数式编程语言下 ECDSA 算法下使用 Secp256k1 签名的过程。
本文侧重相关库的使用,相关原理解析可见:
一个数字引发的探索——ECDSA解析:
https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/articles/3_features/36_cryptographic/ecdsa_analysis.html?highlight=v%20r%20s
一场椭圆曲线的寻根问祖之旅:
https://fisco-bcos-documentation.readthedocs.io/zh_CN/dev/docs/articles/3_features/36_cryptographic/elliptic_curve.html
在 Elixir 中,使用了 libsecp256k1
库:
# mix.exs
defp deps do
[
...
{:libsecp256k1, "~> 0.1.9"},
...
]
最终实现是一个库,被最终打包在:
https://github.com/leeduckgo/eth_wallet
并可以通过 mix 进行导入:
{:eth_wallet, ">= 0.0.12"}
在 Rust 中,使用了secp256k1
库与bitcoin_hashes
库:
# cargo.toml
[dependencies]
secp256k1 = {version = "0.20.3", features = ["rand-std", "recovery"] }
bitcoin_hashes = "0.9"
完整实现见:
https://github.com/leeduckgo/secp256k1-example-rs
未压缩的签名
未压缩的签名即是简单粗暴的直接用私钥(privkey)给信息(message)进行签名,签名字节数可能是 71、72 或 73。
Elixir 中的实现
在 elixir 中的签名和验签通过:crypto
这个库实现。
# https://github.com/leeduckgo/eth_wallet/blob/main/lib/utils/crypto.ex
def sign(digest, priv) do
{:ok, res} = :libsecp256k1.ecdsa_sign(digest, priv, :default, <<>>)
res
end
def verify(digest, sig, pubkey) do
# :crypto.verify(:ecdsa, :sha256, msg, sig, [pubkey, :secp256k1])
case :libsecp256k1.ecdsa_verify(digest, sig, pubkey) do
:ok -> true
_ -> false
end
end
Rust 中的实现
///https://github.com/leeduckgo/secp256k1-example-rs/blob/main/src/main.rs
fn main(){
let (seckey, pubkey) = generate_keys();
let digest = b"This is some message";
let sig = sign(digest, seckey);
let serialize_sig = sig.serialize_compact().to_vec();
verify(digest, serialize_sig, pubkey);
}
///https://github.com/rust-bitcoin/rust-secp256k1/blob/master/examples/sign_verify.rs
fn sign(digest: &[u8], seckey: SecretKey) -> Signature {
let secp = Secp256k1::new();
let signature = do_sign(&secp, digest, seckey).unwrap();
println!("signature: {:?}", signature);
signature
}
fn verify(digest: &[u8], sig: Vec<u8>, pubkey: PublicKey){
let secp = Secp256k1::new();
let result = do_verify(&secp, digest, sig, pubkey).unwrap();
println!("verify result: {:?}", result)
}
fn do_sign<C: Signing>(secp: &Secp256k1<C>, digest: &[u8], seckey: SecretKey) -> Result<Signature, Error> {
let digest = sha256::Hash::hash(digest);
let digest = Message::from_slice(&digest)?;
Ok(secp.sign(&digest, &seckey))
}
fn do_verify<C: Verification>(secp: &Secp256k1<C>, digest: &[u8], sig: Vec<u8>, pubkey: PublicKey) -> Result<bool, Error> {
let digest = sha256::Hash::hash(digest);
let digest = Message::from_slice(&digest)?;
let sig = Signature::from_compact(&sig)?;
Ok(secp.verify(&digest, &sig, &pubkey).is_ok())
}
压缩的签名
通过压缩签名算法(sign_compact),会生成v, r, s。r 和 s 拼凑起来是签名本体,v 的全称是 Recovery ID,起到从签名中恢复公钥的作用。
对比比特币签名,以太坊的签名格式是
r+s+v
。r 和 s 是ECDSA签名的原始输出,而末尾的一个字节为 recovery id 值,但在以太坊中用V
表示,v 值为1或者0。recovery id 简称 recid,表示从内容和签名中成功恢复出公钥时需要查找的次数(因为根据r
值在椭圆曲线中查找符合要求的坐标点可能有多个),但在比特币下最多需要查找两次。这样在签名校验恢复公钥时,不需要遍历查找,一次便可找准公钥,加速签名校验速度。—— https://learnblockchain.cn/books/geth/part3/sign-and-valid.html
压缩签名的长度是 r 和 s 各是 32 字节,v 是1字节,总共是65字节。
Elixir 中的实现
压缩签名函数实现:
@base_recovery_id 27
@base_recovery_id_eip_155 35
@doc """
The test is here:
https://github.com/exthereum/exth_crypto/blob/master/lib/signature/signature.ex
Attention: hash should be 32 bytes.
"""
def sign_compact(digest, privkey, chain_id \\ nil) do
# {:libsecp256k1, "~> 0.1.9"} is useful.
{:ok, <<r::size(256), s::size(256)>> = sig, recovery_id} =
:libsecp256k1.ecdsa_sign_compact(digest, privkey, :default, <<>>)
recovery_id_handled =
recovery_id_to_recovery_id_handled(recovery_id, chain_id)
%{v: recovery_id_handled, r: r, s: s, sig: sig}
end
defp recovery_id_to_recovery_id_handled(recovery_id, chain_id) do
if chain_id do
chain_id * 2 + @base_recovery_id_eip_155 + recovery_id
else
@base_recovery_id + recovery_id
end
end
公钥恢复函数:
def recover(digest, signature, recovery_id_handled , chain_id \\ nil) do
recovery_id =
recovery_id_handled_to_recovery_id(recovery_id_handled, chain_id)
case :libsecp256k1.ecdsa_recover_compact(digest, signature, :uncompressed, recovery_id) do
{:ok, public_key} -> {:ok, public_key}
{:error, reason} -> {:error, to_string(reason)}
end
end
defp recovery_id_handled_to_recovery_id(recovery_id_handled, chain_id) do
if chain_id do
recovery_id_handled - chain_id * 2 - @base_recovery_id_eip_155
else
recovery_id_handled - @base_recovery_id
end
end
Rust 中的实现
压缩签名函数实现:
fn sign_compact(digest: &[u8], seckey: SecretKey) -> (RecoveryId, Vec<u8>) {
let secp = Secp256k1::new();
let signature = do_sign_compact(&secp, digest, seckey).unwrap();
let (recovery_id, serialized_sig) = signature.serialize_compact();
println!("signature compacted: {:?}", serialized_sig);
println!("recovery id: {:?}", recovery_id);
(recovery_id, serialized_sig.to_vec())
}
fn do_sign_compact<C: Signing>(secp: &Secp256k1<C>, digest: &[u8], seckey: SecretKey) -> Result<RecoverableSignature, Error> {
let digest = sha256::Hash::hash(digest);
let digest = Message::from_slice(&digest)?;
Ok(secp.sign_recoverable(&digest, &seckey))
}
公钥恢复函数:
def recover(digest, signature, recovery_id_handled , chain_id \\ nil) do
recovery_id =
recovery_id_handled_to_recovery_id(recovery_id_handled, chain_id)
case :libsecp256k1.ecdsa_recover_compact(digest, signature, :uncompressed, recovery_id) do
{:ok, public_key} -> {:ok, public_key}
{:error, reason} -> {:error, to_string(reason)}
end
end
defp recovery_id_handled_to_recovery_id(recovery_id_handled, chain_id) do
if chain_id do
recovery_id_handled - chain_id * 2 - @base_recovery_id_eip_155
else
recovery_id_handled - @base_recovery_id
end
end
完整主函数(main):
fn main(){
let (seckey, pubkey) = generate_keys();
let digest = b"This is some message";
let sig = sign(digest, seckey);
let serialize_sig = sig.serialize_compact().to_vec();
verify(digest, serialize_sig, pubkey);
let (recovery_id, sig_compact) = sign_compact(digest, seckey);
verify(digest, sig_compact.clone(), pubkey);
recover(digest, sig_compact, recovery_id);
}
最后——关于太上:
太上是笔者团队近期实践的一个函数式+区块链的项目。
太上炼金炉在不改变原有 NFT 合约的基础上,通过附加「存证合约」,赋予 NFT 组合、拆解、生命周期、权益绑定等能力,锻造 NFT +,创造无限创新玩法与想象空间。
愿景0x01:助力所有 NFT 及其相关项目,让其具备无限商业想象空间与无限玩法。
愿景0x02:成为下一代区块链基础设施
太上是本系列用以探讨函数式编程的第一个项目。
柏链教育第四期工信部人才交流中心区块链专业人才认证培训正在火热招生中!
现在报名课程,即可获得以下福利:
1、专享《三天入门GO语言》、《93节助你掌握GO语言攻破区块链》、《GO语言打造并发聊天室》等千元区块链课程大礼包
2、获得高额优惠券,该优惠券可用于购买认证课程时抵扣
3、区块链优秀企业岗位推荐
4、加入专属学习群
详细报名链接请见: