KiemTienOnline360

Chia sẻ hành trình kiếm tiền online bắt đầu từ con số 0

Kiến thức Blockchain, Kiến thức lập trình, Kỹ nghệ phần mềm, Lập trình Blockchain

Các loại địa chỉ Bitcoin và lập trình để tạo địa chỉ Bitcoin

Các loại địa chỉ Bitcoin và lập trình để tạo địa chỉ Bitcoin

Các loại địa chỉ Bitcoin và lập trình để tạo địa chỉ Bitcoin

Chia sẻ bài viết
5
(67)

Sau vụ sàn FTX sập, mọi người đều có nhu cầu chuyển tài sản về Ví phi tập trung và mình cũng vậy. Trong các tài sản thì Bitcoin là tài sản cần ưu tiên chuyển đầu tiên. Nói chung token thuộc hệ nào tốt nhất nên để trên ví thuộc blockchain đó, như Bitcoin thì để trên Ví trên mạng Bitcoin, Ethereum thì nên để trên ví thuộc mạng Ethereum

Mình đã tìm hiểu có một số ví hỗ trợ Bitcoin như:

  • TrustWallet: Ví uy tín được sở hữu bởi Binance. Ví đáng tin cậy này được sử dụng miễn phí, thân thiện và rẻ. Mình đánh giá thì đây có thể là ví an toàn nhất trong các Hot Wallet.
  • Coinbase Wallet: Ví được sở hữu bởi CoinBase. Cũng là một trong những ví Hot Wallet đáng tin cậy.
  • SafePal: Cũng là một trong các ví được nhiều người sử dụng.
  • Coin98 Wallet: Đây là sản phẩm của Việt Nam, nhưng sử dụng khá rối dễ nhầm thao tác, không cẩn thận lại mất tiền oan.
  • Blockchain.com: Ví bitcoin online hỗ trợ cả trên Web và trên Điện thoại. Mình không thích lắm vì nó yêu cầu phải xác thực người dùng.
  • Ví lạnh: LedgerTrezor. Mình đang mua thử một cái về dùng thử xem sao.

Nói chung các Ví phi tập trung ở trên bạn có thể sử dụng, nhưng dù sao thì vẫn là được phát triển bởi một bên thứ ba. Nó có rủi ro lớn liên quan tới vấn đề bảo mật của ứng dụng. Các công ty đứng sau thì họ không bao giờ có ý định lưu giữ Private Key của bạn cả, nhưng với ngành này, nhân sự Dev thay đổi liên tục, nhiều khi có những Dev có ý đồ không tốt, hoặc các Dev yếu hay lập trình cho xong việc mà không để ý tới bảo mật,… Vì thế mình thấy, nếu tự mình code tạo ra ví thì vẫn cứ là an toàn nhất.

Cũng vì thế mà mình quyết định tìm hiểu để tự lập trình tạo ra ví Bitcoin cho riêng mình.

Các loại địa chỉ Bitcoin và nên sử dụng địa chỉ Bitcoin nào?

Ngay đầu tiên bắt đầu với ý tưởng này, mình đã gặp vấn đề:

  • Trên ví TrustWallet và ví Coinbase, mình thấy địa chỉ ví Bitcoin bắt đầu bằng kí tự “bc1q
  • Trong khi đó trên Binance, khi mình lấy địa chỉ để Deposit Bitcoin thì thấy địa chỉ của nó lại bắt đầu bằng kí tự “1

Vậy tại sao lại có nhiều địa chỉ Bitcoin khác nhau như vậy, và mình nên dùng địa chỉ nào?

Sau khi lục tìm trên mạng, mình biết được rằng có nhiều kiểu địa chỉ ví Bitcoin khác nhau ứng với các chuẩn khác nhau:

Legacy Address (P2PKH)

Các địa chỉ theo chuẩn này thì bắt đầu bằng số 1, giống như địa chỉ Deposit trên sàn Binance. Ví dụ: 15e15hWo6CShMgbAfo8c2Ykj4C6BLq6Not

Địa chỉ Legacy Address chỉ đơn giản là hàm băm của Public Key đối với Private Key của bạn. Khi Bitcoin ra mắt vào năm 2009, đây là cách duy nhất để tạo địa chỉ, kiểu này sử dụng nhiều không gian nhất trong một giao dịch và do đó là loại địa chỉ đắt nhất.

Ngày nay không có lý do chính đáng để sử dụng loại địa chỉ này, vì các loại mới tốt hơn về mọi mặt. Hầu hết mọi người sẽ chỉ sử dụng loại địa chỉ này nếu họ có ví cũ, loại ví này từng không tương thích với các loại địa chỉ mới hơn.

Pay to Script Hash (P2SH)

Pay-to-Script-Hash là các địa chỉ bắt đầu với số 3. Ví dụ: 35PBEaofpUeH8VnnNSorM1QZsadrZoQp4N

Địa chỉ Pay-to-Script-Hash là một script liên quan đến các điều kiện chi tiêu nhất định, điều kiện này bị ẩn đối với chủ sở hữu. Các điều kiện chi tiêu này có thể rất đơn giản (chủ sở hữu khóa công khai A có thể tiêu số bitcoin này) hoặc khá phức tạp (chủ sở hữu khóa công khai B có thể tiêu số bitcoin này sau khoảng thời gian X nếu anh ta tiết lộ một bí mật đã định trước). Sử dụng tập lệnh này, các địa chỉ P2SH thậm chí có thể sử dụng SegWit và tiết kiệm phí giao dịch. Gửi đến địa chỉ P2SH rẻ hơn khoảng 26% so với sử dụng ví có địa chỉ cũ.

Native SegWit (P2WPKH)

Native SegWit là các địa chỉ bắt đầu bằng cụm kí tự bc1q“, giống như các địa chỉ sinh ra trên các ví TrustWallet, Coinbase Wallet. Ví dụ: bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc

Loại địa chỉ này làm giảm lượng thông tin được lưu trữ trong giao dịch nhiều hơn bằng cách không giữ chữ ký và chữ viết trong giao dịch, nhưng trong nhân chứng.

Sử dụng loại này, bạn có thể tiết kiệm thêm 16% so với địa chỉ P2SH, tiết kiệm thêm tới hơn 38% so với địa chỉ cũ. Vì những tiết kiệm này, đây hiện là tiêu chuẩn được sử dụng nhiều nhất cho các địa chỉ.

Vì một số sàn giao dịch và ví chưa hỗ trợ địa chỉ loại này nên họ nhắc người dùng gửi cho họ địa chỉ P2SH để thay thế. Đây là lý do tại sao hầu hết các ví vẫn bao gồm tùy chọn tạo P2SH hoặc thậm chí là ví địa chỉ cũ.

Taproot (P2TR)

Taproot là các địa chỉ bắt đầu bằng cụm kí tự “bc1p. Ví dụ: bc1pmzfrwwndsqmk5yh69yjr5lfgfg4ev8c0tsc06e

Địa chỉ Taproot được sử dụng từ khoảng tháng 11/2021, khi mạng bitcoin sẽ thực hiện soft-fork taproot. Điều này sẽ kích hoạt nhiều khả năng hợp đồng thông minh mới cho các địa chỉ bitcoin và cải thiện tính riêng tư khi chi tiêu các giao dịch đó.

Các giao dịch taproot thông thường lớn hơn một chút so với segwit gốc, nhưng nhỏ hơn các địa chỉ cũ. Điều này là do chúng được gắn với Public key thay vì băm Public key. Đối với các giao dịch phức tạp liên quan đến tập lệnh đa chữ ký chẳng hạn, địa chỉ taproot tiết kiệm rất nhiều dung lượng, khiến chúng rẻ hơn.

Cách sinh một địa chỉ ví và các công cụ hỗ trợ sinh địa chỉ ví Bitcoin

Hầu hết tất cả các địa chỉ ví Bitcoin hoặc các loại ví khác hiện nay đều bắt đầu từ việc sinh ra một chuỗi 32 số ngẫu nhiên, mỗi số ứng với 1 byte có giá trị từ 0 đến 255. Chuỗi số này chính là Private Key của ví. Từ Private Key này thì sẽ tạo ra Public Keyđịa chỉ ví, cái này tùy thuộc vào từng mạng lưới blockchain khác nhau.

Ngoài các ví đã nêu ở trên bạn có thể sử dụng một số công cụ sau để tạo địa chỉ Bitcoin:

Mnemonic là gì? Tại sao Mnemonic được sử dụng để sinh tạo địa chỉ ví Blockchain

Mnemonic là danh sách các từ được sắp xếp theo thứ tự với độ dài tiêu chuẩn từ 12 đến 24 từ đơn, chúng ta dùng nó để tạo ví mới để lưu trữ tiền điện tử. Nó là cụm từ nên nó dễ nhớ hơn nhiều so với số hoặc các kí tự hexa của Private Key.

Vì thế hiện nay, mnemonic được sử dụng trong hầu hết các ví Phi tập trung. Chuẩn được dùng phổ biến để tạo mnemonic là chuẩn BIP39 được sử dụng trong các ví Bitcoin, Ethereum, v..v. Chuẩn BIP39 hỗ trợ tạo mnemonic dưới nhiều ngôn ngữ khác nhau như Tiếng Anh, Tiếng Pháp, Tiếng Italy, Tiếng Nhật, Tiếng Trung giản thể, Tiếng Trung phổn thể, Tiếng Hàn. Ví dụ về một đoạn mnemonic: army van defense carry jealous true garbage claim echo media make crunch.

Mnemonic được tạo ra như thế nào ?

Đầu tiên ví HD cần tạo một chuỗi nhị phân entropy, độ dài của entropy phụ thuộc vào độ dài mnemonic cần tạo, bên dưới là bảng thể hiện mối quan hệ giữa độ dài entropy và mnemonic tương ứng. Ở phần này chúng ta sẽ mô tả cách tạo mnemonic với độ dài 12 từ, quá trình tạo ra các mnemomic dài hơn cũng diễn ra tương tự. Ví dụ: Entropy 01110110010110011000101011101011110010101001111010100011011000110000000111101101010010011010101000110011100001110110110010100110 dài 128 bits để tạo ra mnemonic dài 12 từ.

TIếp theo:

  • B1: Băm entropy với hàm SHA-256, lấy 4 bit đầu của hàm băm gán thêm vào entropy => Entropy mới dài 132 bit
  • B2: Chia entropy thành 12 phần, mỗi phần dài 11 bit
  • B3: Mỗi phần khi chuyển sang hệ thập phân sẽ tương ứng với thứ tự các từ trong từ điển của chuẩn BIP39. 11111111111 bằng 2047 tương ứng với từ zoo, từ thứ 2048 của từ điển tiếng anh trong BIP39.

Từ Mnemonic tạo ra các tài khoản như thế nào?

Với mnemomic thu được ở bước trên, chúng ta tiếp tục tìm hiểu quá trình tạo ra seed (dài 512 bit).

  • B1: Cụm mnemonic sẽ được cộng với 1 chuỗi salt (bắt đầu bằng từ “mnemonic” cộng thêm 1 chuỗi passphare tùy ý). Chức năng của salt tăng độ khó trước các cuộc tấn công từ điển hay vét cạn.
  • B2: Băm chuỗi gồm mnemonic và salt với thuật toán HMAC-SHA512 2048 lần.
  • B3: Kết quả cuối cùng thu được seed dài 512 bit.

Tiếp theo:

  • Từ seed đã có, băm tiếp bằng HMAC-SHA512.
  • Lấy 256 bit đầu làm Master Private Key, 256 bit sau làm Master Chaincode.
  • Từ Master Private Key sinh ra Master Public Key dựa theo thuật toán ECDSA

Quá trình tạo khóa con:

  • Băm Master Public KeyMaster Chaincode cộng thêm số thứ tự độ dài 32 bit với HMAC-SHA512 (số thứ tự sau này dùng để phân biệt thứ tự các khóa con tạo ra). Vậy 1 khóa mẹ có thể có 2^32 tức hơn 4 tỷ khóa con.
  • Lấy 256 bit đầu làm private key, 256 bit sau làm chain code
  • private key con sinh ra public key con bằng thuật toán ECDSA

Lập trình NodeJs để tạo địa chỉ Bitcoin

Chúng ta sẽ sử dụng các thư viện chính như sau:

  • bitcoinjs-lib: Thư viện Bitcoin cho NodeJs, thư viện có số lượng download khá lớn.
  • tiny-secp256k1: Hỗ trợ chuẩn secp256k1
  • bip39: Thư viện hỗ trợ sinh chuỗi kí tự mnemonic
  • axios: Thư viện HTTP
  • minimist: Thư viện hỗ trợ parse các tham số

Mình có tham khảo trust-wallet-core- registry.json để lấy “derivation path” chuẩn khớp với ví tạo ra trên TrustWallet. Trong code này mình có hiển thị địa chỉ ví theo tất cả các chuẩn.

Ghi chú: Để an toàn hơn bạn không nên lưu lại Private Key hay cụm Mnemonic gốc mà nên thay đổi chút như đổi kí tự/từ thứ 3 sáng thứ 11, 5 sang 23,…. Cách đổi do bạn và chỉ bạn mới biết. Khi code bạn tự động thực hiện hoán đổi luôn, chỉ hiển thị trên giao diện Private Key hay cụm đã hoán đổi. Như vậy sẽ an toàn hơn rất nhiều.

Chúng ta tạo tệp bitcoin-wallet.js với nội dung như sau:

const ECPairFactory = require('ecpair').ECPairFactory;
const secp256k1 = require('tiny-secp256k1');
const bitcoin = require('bitcoinjs-lib');
const ECPair = ECPairFactory(secp256k1);
const parseArgs = require('minimist');
const axios = require('axios');
const bip39 = require('bip39');
const BIP32Factory = require('bip32').BIP32Factory;
const bip32 = BIP32Factory(secp256k1);

const bitcoinDerivePath = "m/84'/0'/0'/0/0";

function _mnemonicToPrivateWif(mnemonic) {
    let seed = bip39.mnemonicToSeedSync(mnemonic);
    let root = bip32.fromSeed(seed);
    let child1 = root.derivePath(bitcoinDerivePath);
    let wif = child1.toWIF();
    return wif;
}

async function _getAccountInfo(address) {
    const result = await axios.get(`https://blockchain.info/rawaddr/${address}`);
    if (result) {
        let data = result.data;
        if (data.final_balance) data.final_balance = data.final_balance / 10**8;
        if (data.total_received) data.total_received = data.total_received / 10**8;
        if (data.total_sent) data.total_sent = data.total_sent / 10**8;
        return data;
    }
    return {};
}

async function _showBitcoinAccountInfoFromKeyPair(keyPair) {
    let result1 = bitcoin.payments.p2pkh({
        pubkey: keyPair.publicKey
    });
    let result2 = bitcoin.payments.p2sh({
        redeem: bitcoin.payments.p2wpkh({
          pubkey: keyPair.publicKey
        })
    });
    let result3 = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey });
    console.log("Bitcoin Account:");
    console.log("\tPublic Address 1 (P2PKH):", result1.address);
    console.log("\tPublic Address 2 (P2SH):", result2.address);
    console.log("\tPublic Address 3 (P2WPKH):", result3.address);
    console.log("\tAccount Info:", `https://blockchain.info/rawaddr/${result1.address}`);
    if (keyPair.mnemonic) {
        console.log("\tMnemonic:", keyPair.mnemonic);    
    }
    console.log("\tPrivateWif:", keyPair.toWIF());
    console.log("\tPrivate Key:", '0x' + keyPair.privateKey.toString('hex'));
    let accountInfo = await _getAccountInfo(result1.address);
    console.log("\tBalance:", accountInfo.final_balance);
    console.log("\tTotal Sent:", accountInfo.total_sent);
    console.log("\tTotal Received:", accountInfo.total_received);
    console.log("\tTrans Num:", accountInfo.total_received);
}

async function showAccountInfo(opts) {
    if (!opts.address) return false;

    let accountInfo = await _getAccountInfo(opts.address);
    console.log(`Account Info of ${opts.address}:`)
    console.log("\tBalance:", accountInfo.final_balance);
    console.log("\tTotal Sent:", accountInfo.total_sent);
    console.log("\tTotal Received:", accountInfo.total_received);
    console.log("\tTrans Num:", accountInfo.n_tx);
    return true;
}

async function generateBitcoinAccount(opts) {
    let keyPair = null;
    if (opts.type=="mnemonic") {
        let mnemonic = bip39.generateMnemonic();
        let wif = _mnemonicToPrivateWif(mnemonic);
        keyPair = ECPair.fromWIF(wif);
        keyPair.mnemonic = mnemonic;
    } else {
        keyPair = ECPair.makeRandom();
    }
    await _showBitcoinAccountInfoFromKeyPair(keyPair);
}

async function restoreBitcoinAccount(opts) {
    if (!opts.mnemonic && !opts.privateKey && !opts.privateWif) return false;
    let mnemonic = opts.mnemonic;
    let privateKey = opts.privateKey;
    let privateWif = opts.privateWif;
    var keyPair = null;
    if (mnemonic) {
        let wif = _mnemonicToPrivateWif(mnemonic);
        keyPair = ECPair.fromWIF(wif);
        keyPair.mnemonic = mnemonic;
    } else if (privateKey) {
        if (privateKey.startsWith("0x") || privateKey.startsWith("0X")) {
            privateKey = privateKey.substr(2);
        }
        let bytes = Buffer.from(privateKey, "hex");
        keyPair = ECPair.fromPrivateKey(bytes);
    } else if (privateWif) {
        keyPair = ECPair.fromWIF(privateWif);
    }
    await _showBitcoinAccountInfoFromKeyPair(keyPair);
    return true;
}

function showHelp() {
    console.log("Command to run:");
    console.log("\tnode bitcoin-wallet.js <options>");
    console.log("\tnode bitcoin-wallet.js --help");
    console.log("For examples:")
    console.log("\tnode bitcoin-wallet.js --act=generate-account");
    console.log("\tnode bitcoin-wallet.js --act=generate-account --type=mnemonic");
    console.log("\tnode bitcoin-wallet.js --act=restore-account --mnemonic=\"abc xyz\"");
    console.log("\tnode bitcoin-wallet.js --act=restore-account --privateKey=xxxxxxxxxxxxxxxx");
    console.log("\tnode bitcoin-wallet.js --act=restore-account --privateWif=xxxxxxxxxxxxxxxx");
    console.log("\tnode bitcoin-wallet.js --act=show-account --address=1LQoWist8KkaUXSPKZHNvEyfrEkPHzSsCd");
}

async function main() {
    var opts = parseArgs(process.argv.slice(2), {
        string: [ "address", "privateKey" ]
    });
    // console.log("Options:", opts);
    if (opts.help) {
        showHelp();
        process.exit(0);
    }
    if (opts.network) network = opts.network;

    let act = opts.act;
    if (act=="generate-account") {
        await generateBitcoinAccount(opts);
    } else if (act=="restore-account") {
        if (!await restoreBitcoinAccount(opts)) showHelp();
    } else if (act=="show-account") {
        if (!await showAccountInfo(opts)) showHelp();
    } else {
        showHelp();
    }
}

main();

Trước khi chạy, bạn cần đánh lệnh sau để cài đặt thư viện cần thiết:
npm install bitcoinjs-lib ecpair bip32 bip39 tiny-secp256k1 axios minimist

Tiếp theo, bạn đánh lệnh sau để xem các lệnh:
node bitcoin-wallet.js –help

Các lệnh hỗ trợ
Các lệnh hỗ trợ

Có hai cách tạo ví mới, nếu bạn muốn sinh ngẫu nhiên 1 Private Key:
node bitcoin-wallet.js –act=generate-account

Lệnh tạo Bitcoin bằng cách sinh Private key ngẫu nhiên
Lệnh tạo Bitcoin bằng cách sinh Private key ngẫu nhiên

Sinh địa chỉ ví Bitcoin mới bằng cách tạo cụm từ mnemonic:
node bitcoin-wallet.js –act=generate-account –type=mnemonic

Lệnh tạo địa chỉ Bitcoin bằng cách sinh ngẫu nhiên mnemonic
Lệnh tạo địa chỉ Bitcoin bằng cách sinh ngẫu nhiên mnemonic

Bạn có thể sử dụng cụm từ mnemonic ở trên để import vào ví TrustWallet bạn sẽ nhìn thấy địa chỉ Bitcoin giống địa chỉ Public Address 3 ở trên.

Ngoài ra chúng ta có thể khôi phục account từ mnemonic, private key hay private wif:
node bitcoin-wallet.js –act=restore-account –mnemonic=”honey bomb payment heart gown notable symptom spot sorry giraffe stuff vicious”

Khôi phục ví Bitcoin từ cụm từ mnemonic
Khôi phục ví Bitcoin từ cụm từ mnemonic

Tham khảo:

Bài viết này có hữu ích với bạn?

Kích vào một biểu tượng ngôi sao để đánh giá bài viết!

Xếp hạng trung bình 5 / 5. Số phiếu: 67

Bài viết chưa có đánh giá! Hãy là người đầu tiên đánh giá bài viết này.

Trả lời

Giao diện bởi Anders Norén