mod bip32;
mod bip44;
mod ext_pk;
mod ext_sk;
mod id;
mod networks;
mod pk;
mod sig;
mod sk;
use super::*;
use digest::generic_array::{typenum::U20, GenericArray};
use digest::FixedOutput;
use ripemd160::Ripemd160;
use sha2::{Digest, Sha256};
fn hash160<B: AsRef<[u8]>>(input: B) -> GenericArray<u8, U20> {
let mut inner_hasher = Sha256::default();
inner_hasher.update(input);
let mut outer_hasher = Ripemd160::default();
outer_hasher.update(inner_hasher.finalize_fixed());
outer_hasher.finalize_fixed()
}
const CHECKSUM_LEN: usize = 4;
pub fn to_base58check<D: AsRef<[u8]>>(data: D) -> String {
let data = data.as_ref();
let mut inner_hasher = Sha256::default();
inner_hasher.update(data);
let mut outer_hasher = Sha256::default();
outer_hasher.update(inner_hasher.finalize_fixed());
let hash = outer_hasher.finalize_fixed();
let checksum = &hash[..CHECKSUM_LEN];
let mut bytes = Vec::with_capacity(data.len() + checksum.len());
bytes.extend_from_slice(data);
bytes.extend_from_slice(checksum);
let prefixed_enc = multibase::encode(multibase::Base::Base58Btc, &bytes);
prefixed_enc[1..].to_owned()
}
pub fn from_base58check<S: AsRef<str>>(s: S) -> Result<Vec<u8>> {
let mut to_decode = String::new();
to_decode.push(multibase::Base::Base58Btc.code());
to_decode += s.as_ref();
let (_base, checked_data) = multibase::decode(&to_decode)?;
let (data, actual_checksum) = checked_data.split_at(checked_data.len() - CHECKSUM_LEN);
let mut inner_hasher = Sha256::default();
inner_hasher.update(data);
let mut outer_hasher = Sha256::default();
outer_hasher.update(inner_hasher.finalize_fixed());
let hash = outer_hasher.finalize_fixed();
let expected_checksum = &hash[..CHECKSUM_LEN];
ensure!(expected_checksum == actual_checksum, "Incorrect checksum");
Ok(data.to_vec())
}
#[derive(Clone, Debug)]
pub struct Secp256k1;
impl Secp256k1 {
fn hash_message<D: AsRef<[u8]>>(data: D) -> secp::Message {
let mut hasher = Sha256::default();
hasher.update(data.as_ref());
let mut hash = [0u8; secp::util::MESSAGE_SIZE];
hash.copy_from_slice(hasher.finalize_fixed().as_slice());
secp::Message::parse(&hash)
}
}
pub use bip32::*;
pub use bip44::*;
pub use cc::{ChainCode, CHAIN_CODE_SIZE};
pub use ext_pk::SecpExtPublicKey;
pub use ext_sk::SecpExtPrivateKey;
pub use id::{SecpKeyId, KEY_ID_SIZE, KEY_ID_VERSION1};
pub use networks::{ark, btc, hyd, iop};
pub use pk::{SecpPublicKey, PUBLIC_KEY_SIZE, PUBLIC_KEY_UNCOMPRESSED_SIZE};
pub use sig::{SecpSignature, SIGNATURE_SIZE, SIGNATURE_VERSION1};
pub use sk::{SecpPrivateKey, PRIVATE_KEY_SIZE};
impl AsymmetricCrypto for Secp256k1 {
type KeyId = SecpKeyId;
type PublicKey = SecpPublicKey;
type PrivateKey = SecpPrivateKey;
type Signature = SecpSignature;
}
impl KeyDerivationCrypto for Secp256k1 {
type ExtendedPrivateKey = SecpExtPrivateKey;
type ExtendedPublicKey = SecpExtPublicKey;
fn master(seed: &Seed) -> SecpExtPrivateKey {
SecpExtPrivateKey::from_seed(seed.as_bytes())
}
}
pub const VERSION_SIZE: usize = 1;
pub const SLIP10_SEED_HASH_SALT: &[u8] = b"Bitcoin seed";
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Bip178 {
Uncompressed,
Compressed,
P2PKH_Only,
P2WPKH,
P2WPKH_P2SH,
}
impl Bip178 {
pub fn to_wif_suffix(self) -> &'static [u8] {
use Bip178::*;
match self {
Uncompressed => b"",
Compressed => b"\x01",
P2PKH_Only => b"\x10",
P2WPKH => b"\x11",
P2WPKH_P2SH => b"\x12",
}
}
pub fn from_wif_suffix(data: &[u8]) -> Result<Self> {
use Bip178::*;
match data {
b"" => Ok(Uncompressed),
b"\x01" => Ok(Compressed),
b"\x10" => Ok(P2PKH_Only),
b"\x11" => Ok(P2WPKH),
b"\x12" => Ok(P2WPKH_P2SH),
_ => Err(anyhow!("Unknown wif suffix {}", hex::encode(data))),
}
}
}
#[cfg(test)]
mod test {
#[test]
fn invalid_private_key() {
use super::SecpPrivateKey;
let sk_bytes =
hex::decode("0000000000000000000000000000000000000000000000000000000000000000")
.unwrap();
let err = SecpPrivateKey::from_bytes(sk_bytes).unwrap_err();
assert!(err.to_string().contains("Invalid secret key"))
}
mod sign_verify {
use crate::secp256k1::{SecpPrivateKey, SecpSignature};
use crate::{PrivateKey, PublicKey};
fn test(sk_hex: &str, msg: &[u8], sig_hex: &str) {
let sk_bytes = hex::decode(sk_hex).unwrap();
let sk = SecpPrivateKey::from_bytes(sk_bytes).unwrap();
let sig = sk.sign(msg);
let sig_bytes = sig.to_bytes();
assert_eq!(hex::encode(&sig_bytes), sig_hex);
let sig2 = SecpSignature::from_bytes(&sig_bytes).unwrap();
let pk = sk.public_key();
assert!(pk.verify(msg, &sig2));
}
#[test]
fn test_1() {
test(
"0000000000000000000000000000000000000000000000000000000000000001",
b"Satoshi Nakamoto",
"01934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d82442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5",
);
}
}
mod slip10_test_vectors {
use crate::{
secp256k1::{btc, Network, Secp256k1, SecpExtPrivateKey},
ExtendedPrivateKey, KeyDerivationCrypto, Seed,
};
struct TestDerivation {
xprv: SecpExtPrivateKey,
}
impl TestDerivation {
fn new(seed_hex: &str) -> Self {
let seed_bytes = hex::decode(seed_hex).unwrap();
let seed = Seed::from_bytes(&seed_bytes).unwrap();
let master = Secp256k1::master(&seed);
Self { xprv: master }
}
fn assert_state(&self, xpub_str: &str, xprv_str: &str) {
let xpub = self.xprv.neuter();
assert_eq!(xpub.to_xpub(&btc::Mainnet.bip32_xpub()), xpub_str);
assert_eq!(self.xprv.to_xprv(&btc::Mainnet.bip32_xprv()), xprv_str);
}
fn derive_hardened(&mut self, idx: i32) {
let xprv = self.xprv.derive_hardened_child(idx).unwrap();
self.xprv = xprv;
}
fn derive_normal(&mut self, idx: i32) {
let xprv = self.xprv.derive_normal_child(idx).unwrap();
self.xprv = xprv;
}
}
#[test]
fn test_vector_2() {
let mut t = TestDerivation::new("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542");
t.assert_state("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U");
t.derive_normal(0);
t.assert_state("xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt");
t.derive_hardened(2_147_483_647);
t.assert_state("xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9");
t.derive_normal(1);
t.assert_state("xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef");
t.derive_hardened(2_147_483_646);
t.assert_state("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc");
t.derive_normal(2);
t.assert_state("xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j");
}
#[test]
fn test_vector_3() {
let mut t = TestDerivation::new("4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be");
t.assert_state("xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13", "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6");
t.derive_hardened(0);
t.assert_state("xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y", "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L");
}
}
mod btc_key_conversions {
use crate::secp256k1::{btc, Bip178, Network, SecpPrivateKey};
use crate::{PrivateKey, PublicKey};
fn test(sk_hex: &str, wif: &str, pk_hex: &str, id_hex: &str, address: &str) {
let sk_bytes = hex::decode(sk_hex).unwrap();
let sk = SecpPrivateKey::from_bytes(sk_bytes).unwrap();
let sk_wif = sk.to_wif(&btc::Mainnet.wif(), Bip178::Compressed);
assert_eq!(sk_wif, wif);
let pk = sk.public_key();
let pk_bytes = pk.to_bytes();
assert_eq!(hex::encode(&pk_bytes), pk_hex);
let id = pk.key_id();
let id_bytes = id.to_bytes();
assert_eq!(hex::encode(&id_bytes), id_hex);
let act_address = id.to_p2pkh_addr(&btc::Mainnet.p2pkh_addr());
assert_eq!(act_address, address);
}
#[test]
fn test_1() {
test(
"0000000000000000000000000000000000000000000000000000000000000001",
"KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn",
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"01751e76e8199196d454941c45d1b3a323f1433bd6",
"1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH",
);
}
#[test]
fn test_2() {
test(
"0000000000000000000000000000000000000000000000000000000000000002",
"KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74NMTptX4",
"02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
"0106afd46bcdfd22ef94ac122aa11f241244a37ecc",
"1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP",
);
}
#[test]
fn test_3() {
test(
"0000000000000000000000000000000000000000000000000000000000000003",
"KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S",
"02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
"017dd65592d0ab2fe0d0257d571abf032cd9db93dc",
"1CUNEBjYrCn2y1SdiUMohaKUi4wpP326Lb",
);
}
#[test]
fn test_4() {
test(
"aa5e28d6a97a2479a65527f7290311a3624d4cc0fa1578598ee3c2613bf99522",
"L2vtCpubwLeqLNYywTUqLLmN6LiijyYWUArxvyw5DyFD8TaxqJyu",
"0234f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
"01db820065e5bd79e976f0dc09f2257e35243879cf",
"1M1eigHFbhtWLnc37qXQt1ao2taLhE49yg",
);
}
#[test]
fn test_5() {
test(
"7e2b897b8cebc6361663ad410835639826d590f393d90a9538881735256dfae3",
"L1Sy9ysFzZDXh5gXYrgJmbyhnhbJVyptuTypUnD9ofZoV3V2SpUi",
"03d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
"015716c6c9146a548ce31092f72ab24b44d8580914",
"18wV5EG3Hqocod1RLm9STvbUnSqb1NMo44",
);
}
#[test]
fn test_6() {
test(
"6461e6df0fe7dfd05329f41bf771b86578143d4dd1f7866fb4ca7e97c5fa945d",
"Kzaqk53898thvqucDWi4MqC3ogC2s2QmtZL31qjS9MRMvgHFKpDZ",
"03e8aecc370aedd953483719a116711963ce201ac3eb21d3f3257bb48668c6a72f",
"01e3281990058f008a4b6c658cb735ae2b7327daa5",
"1Mi6RjU7ASvudQMZkeobQ1WoiZWAtVhkd6",
);
}
}
mod blockchain_com_derivation {
use crate::secp256k1::*;
#[test]
fn btc_wallet() {
let phrase =
"hint replace increase neglect egg wood ill alert beef rich install potato";
let xpub = "xpub6DVu7eWDWJkczyrDQdo2i99MKdns8idTeVfzwuHCaA3rKfSWaSCnpKigU21vJ2TdCT5MiLgbKWzpxZUx4gFx6zpNWukDzX8yRGU3UKyE9fC";
let xpub2 = "xpub6DVu7eWDWJkd4MbGacHiWGFKT2amHnJwQ7SmxWpD2YuacVtaQz7XPsRYxR5PqsGa2TiLYWPi3jsimnruvJ5LvMBxjc96jgZniP7peLHcEdM";
let old_addr0 = "17kxMsME7f8CVVWqPadgQNsDEMaHDBpCbv";
let bip44_addr0 = "17Y82siUGdY8KEWGpUTW7uF5kj6cEMRYfo";
let net = &btc::Mainnet;
let seed = Bip39::new().short_phrase(phrase).unwrap().password("");
let coin = Bip44.network(&seed, net).unwrap();
let account: Bip44Account<Secp256k1> = coin.account(0).unwrap();
assert_eq!(account.neuter().to_xpub(), xpub);
let old_pk0 = account.node().derive_normal(0).unwrap().neuter();
assert_eq!(old_pk0.to_p2pkh_addr(net), old_addr0);
let pk0 = account.key(0).unwrap().neuter();
assert_eq!(pk0.to_p2pkh_addr(), bip44_addr0);
let account2 = coin.account(1).unwrap();
assert_eq!(account2.neuter().to_xpub(), xpub2);
}
}
mod coinomi_derivation {
use crate::{
secp256k1::{btc, hyd},
*,
};
#[test]
fn hyd_derive() {
let mnemonic = "blast cargo razor option vote shoe stock cruel mansion boy spot never album crop reflect kangaroo blouse slam empty shoot cable vital crane manual";
let pk0_hex = "02f946d10106f55c755c1f836b63bef35fb0015603e1870c8dbdcacf62f178587e";
let addr0 = "hWNN8ymcsLdJivbwbBaPS8X1vekxB2pdwV";
let seed = Bip39::new().phrase(mnemonic).unwrap().password(Seed::PASSWORD);
let account = Bip44.network(&seed, &hyd::Mainnet).unwrap().account(0).unwrap();
let key0 = account.key(0).unwrap().neuter();
assert_eq!(hex::encode(key0.to_public_key().to_bytes()), pk0_hex);
assert_eq!(key0.to_p2pkh_addr(), addr0);
}
#[test]
fn tbtc_derive() {
let mnemonic = "blast cargo razor option vote shoe stock cruel mansion boy spot never album crop reflect kangaroo blouse slam empty shoot cable vital crane manual";
let xpub = "tpubDDfA4LQYyG71KmPW65gktxjvzxFAFcdhDAzj4zc6y5hpeX3rZu3nPh1GuvgWCyj4VWKfuFbnnCvFXyTuDLD6mmFA5yVTe2UUcSoNy7kgcYm";
let xpub_coinomi_bug = "xpub6DHXY6asFz9Z3yCedthfh3QZfq9WGE8dfYQRiSYxdAwvuEP9VgChFFLSftbigUmphbnmHg5vF76CqnyHpUMrtKns2Nk9xVpF2VCyD7Uej6C";
let addr0 = "mgH9VjC6uGTt1cWDDZEXisASAtCE8D6Xar";
let net = &btc::Testnet;
let seed = Bip39::new().phrase(mnemonic).unwrap().password(Seed::PASSWORD);
let account = Bip44.network(&seed, net).unwrap().account(0).unwrap().neuter();
assert_eq!(account.to_xpub(), xpub);
assert_eq!(account.node().to_xpub(&btc::Mainnet), xpub_coinomi_bug);
let key0 = account.key(0).unwrap();
assert_eq!(key0.to_p2pkh_addr(), addr0);
}
}
mod ark_desktop_hyd_address {
use super::super::*;
#[test]
fn test() {
let phrase = "boss slice draft close detail mix nation casino judge cigar melody catch";
let pk_hex = "03fdd041ed3e51d8909c44ef6b9d1268a412161d8e6544c5cd4d87ef78bb49e2f7";
let addr = "hFxvDfqQfXHzhvEebTJGkds8DBBGBCKpY9";
let addr_dev = "dEatNarXZifEXaqnMFy5uP9Fv8bq7aB3Vn";
let addr_test = "tXRoniBUYaGXc495HCdCL9V9qJPgBMywaH";
let sk = SecpPrivateKey::from_ark_passphrase(phrase).unwrap();
let pk = sk.public_key();
assert_eq!(hex::encode(pk.to_bytes()), pk_hex);
let key_id = pk.ark_key_id();
assert_eq!(key_id.to_p2pkh_addr(&hyd::Mainnet.p2pkh_addr()), addr);
assert_eq!(key_id.to_p2pkh_addr(&hyd::Devnet.p2pkh_addr()), addr_dev);
assert_eq!(key_id.to_p2pkh_addr(&hyd::Testnet.p2pkh_addr()), addr_test);
}
}
mod ark_key_conversions {
use super::super::*;
fn test(passphrase: &str, pk_hex: &str, main_addr: &str, dev_addr: &str) {
let sk = SecpPrivateKey::from_ark_passphrase(passphrase).unwrap();
let pk = sk.public_key();
assert_eq!(hex::encode(pk.to_bytes()), pk_hex);
let key_id = pk.ark_key_id();
assert_eq!(key_id.to_p2pkh_addr(&ark::Mainnet.p2pkh_addr()), main_addr);
assert_eq!(key_id.to_p2pkh_addr(&ark::Devnet.p2pkh_addr()), dev_addr);
}
#[test]
fn test_delegate_1() {
test(
"clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire",
"03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37",
"ANBkoGqWeTSiaEVgVzSKZd3jS7UWzv9PSo",
"DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x",
);
}
#[test]
fn test_delegate_2() {
test(
"venue below waste gather spin cruise title still boost mother flash tuna",
"02def27da9336e7fbf63131b8d7e5c9f45b296235db035f1f4242c507398f0f21d",
"AbfQq8iRSf9TFQRzQWo33dHYU7HFMS17Zd",
"DR2ditoSQvPaySQbaT8GSWC3se5rLyfq4T",
);
}
}
}