6个版本 (3个稳定版)

使用旧Rust 2015版

1.1.0 2020年1月2日
1.0.1 2019年11月10日
1.0.0 2019年10月3日
0.2.1 2019年7月26日
0.1.0 2018年9月23日

#16 in #bip-32

每月41次下载

Apache-2.0

200KB
3.5K SLoC

Safety Dance

Rust语言编写的比特币钱包库

这是一个使用Rust语言编写的构建比特币钱包的库。它使用BIP32密钥派生、BIP39助记词和BIP44、BIP48、BIP84密钥层次结构,使其与TREZOR、Ledger和其他许多钱包兼容。

此库支持TREZOR Model T中可用的SLIP-0039 Shamir共享秘密方案

它支持用于单密钥签名的旧P2PKH、过渡P2SHWPKH和原生segwit P2WPKH,以及用于任意脚本的原生P2WSH。

基本账户使用

MasterAccount 保存一个加密种子,该种子意味着BIP32根密钥。向其中添加任何数量的 Account 以根据BIP44派生层次结构。账户将具有统一类型的地址。

const PASSPHRASE: &str = "correct horse battery staple";

// create a new random master account. This holds the root BIP32 key
// PASSPHRASE is used to encrypt the seed in memory and in storage
let mut master = MasterAccount::new(MasterKeyEntropy::Sufficient, Network::Bitcoin, PASSPHRASE).unwrap();

// or re-create a master from a known mnemonic
let words = "announce damage viable ticket engage curious yellow ten clock finish burden orient faculty rigid smile host offer affair suffer slogan mercy another switch park";
let mnemonic = Mnemonic::from_str(words).unwrap();
// PASSPHRASE is used to encrypt the seed in memory and in storage
// last argument is option password for plausible deniability
let mut master = MasterAccount::from_mnemonic(&mnemonic, 0, Network::Bitcoin, PASSPHRASE, None).unwrap();

// The master accounts only store public keys
// Private keys are created on-demand from encrypted seed with an Unlocker and forgotten as soon as possible

// create an unlocker that is able to decrypt the encrypted mnemonic and then calculate private keys
let mut unlocker = Unlocker::new_for_master(&master, PASSPHRASE).unwrap();

// The unlocker is needed to create accounts within the master account as 
// key derivation follows BIP 44, which requires private key derivation

// create a P2PKH (pay-to-public-key-hash) (legacy) account. 
// account number 0, sub-account 0 (which usually means receiver) BIP32 look-ahead 10
let account = Account::new(&mut unlocker, AccountAddressType::P2PKH, 0, 0, 10).unwrap();
master.add_account(account);

// create a P2SHWPKH (pay-to-script-hash-witness-public-key-hash) (transitional single key segwit) account.
// account number 0, sub-account 1 (which usually means change) BIP32 look-ahead 10
let account = Account::new(&mut unlocker, AccountAddressType::P2SHWPKH, 0, 1, 10).unwrap();
master.add_account(account);

// create a P2WPKH (pay-to-witness-public-key-hash) (native single key segwit) account.
// account number 1, sub-account 0 (which usually means receiver) BIP32 look-ahead 10
let account = Account::new(&mut unlocker, AccountAddressType::P2WPKH, 1, 0, 10).unwrap();
master.add_account(account);
// account number 1, sub-account 0 (which usually means change) BIP32 look-ahead 10
let account = Account::new(&mut unlocker, AccountAddressType::P2WPKH, 1, 1, 10).unwrap();
master.add_account(account);

// get next legacy receiver address
let source = master.get_mut((0,0)).unwrap().next_key().unwrap().address.clone();
// pay to some native segwit address
let target = master.get_mut((1,0)).unwrap().next_key().unwrap().address.clone();
// change to some transitional address
let change = master.get_mut((0,1)).unwrap().next_key().unwrap().address.clone();

// a dummy transaction to send to source
let input_transaction = Transaction {
            input: vec![
                TxIn {
                    previous_output: OutPoint { txid: sha256d::Hash::default(), vout: 0 },
                    sequence: RBF,
                    witness: Vec::new(),
                    script_sig: Script::new(),
                }
            ],
            output: vec![
                TxOut {
                    script_pubkey: source.script_pubkey(),
                    value: 5000000000,
                }
            ],
            lock_time: 0,
            version: 2,
        };

let txid = input_transaction.txid();

const RBF: u32 = 0xffffffff - 2;

// a dummy transaction that spends source
let mut spending_transaction = Transaction {
            input: vec![
                TxIn {
                    previous_output: OutPoint { txid, vout: 0 },
                    sequence: RBF,
                    witness: Vec::new(),
                    script_sig: Script::new(),
                }
            ],
            output: vec![
                TxOut {
                    script_pubkey: target.script_pubkey(),
                    value: 4000000000,
                },
                TxOut {
                    script_pubkey: change.script_pubkey(),
                    value: 999999000,
                }
            ],
            lock_time: 0,
            version: 2,
        };

// helper to find previous outputs
let mut spent = HashMap::new();
spent.insert(txid, input_transaction.clone());

// sign the spend
master.sign(&mut spending_transaction, SigHashType::All,
                       &(|_| Some(input_transaction.output[0].clone())), 
            &mut unlocker).expect("can not sign");

// verify the spend with the bitcoinconsensus library
spending_transaction.verify(|point|
            spent.get(&point.txid).and_then(|t| t.output.get(point.vout as usize).cloned())
        ).expect("Bitcoin Core would not like this")

高级账户使用

const CSV:u16 = 10; // 10 blocks relative lock

// create a P2WSH (pay-to-witness-script-hash) (native segwit for arbitrary scripts) account
let account = Account::new(&mut unlocker, AccountAddressType::P2WSH(4711), 2, 0, 0).unwrap();
master.add_account(account);
{
    let account = master.get_mut((2, 0)).unwrap();
    let scripter = |pk: &PublicKey, csv: Option<u16>| Builder::new()
        .push_int(csv.unwrap() as i64)
        .push_opcode(all::OP_CSV)
        .push_opcode(all::OP_DROP)
        .push_slice(pk.to_bytes().as_slice())
        .push_opcode(all::OP_CHECKSIG)
        .into_script();
    account.add_script_key(scripter, Some(&[0x01; 32]), Some(CSV)).unwrap();
}

硬币使用

// create a coin store
let mut coins = Coins::new();

// put all coins from block into coin store that are ours (means master can sign for them)
coins.process(&mut master, &block);

// calculate balances
let confirmed = coins.confirmed_balance();
let unconfirmed = coins.unconfirmed_balance();
// means not OP_CSV time locked
let available = coins.available_balance();

// undo the highest block as it was removed through re-org
coins.unwind_tip(block_hash);

// choose inputs to spend
let inputs = choose_inputs (minimum_amount_needed, current_block_height, |h| height_of_block(h));

Shamir的密钥份额

// create an new random account        
let master = MasterAccount::new(MasterKeyEntropy::Low, Network::Bitcoin, PASSPHRASE).unwrap();

// extract seed
let seed = master.seed(Network::Bitcoin, PASSPHRASE).unwrap();

// cut seed into 5 shares such that any 3 of them is sufficient to re-construct
let shares = ShamirSecretSharing::generate(1, &[(3,5)], &seed, None, 1).unwrap();

// re-construct seed from the first 3
let reconstructed_seed = ShamirSecretSharing::combine(&shares[..3], None).unwrap();

// re-construct master from seed
let reconstructed_master = MasterAccount::from_seed(&reconstructed_seed, 0, Network::Bitcoin, PASSPHRASE).unwrap();

// prove that everything went fine
assert_eq!(master.master_public(), reconstructed_master.master_public());
assert_eq!(master.encrypted(), reconstructed_master.encrypted());

依赖项

~7.5MB
~123K SLoC