07-1 Bitcoin 钱包开发简单流程

1 比特币钱包开发

因为是JS代码,所以会有很多看不懂的地方,没关系

1.1 地址生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import * as bitcoin from 'bitcoinjs-lib';
import * as ecc from 'tiny-secp256k1';
const { BIP32Factory } = require('bip32');
const bip32 = BIP32Factory(ecc); // 这里还是bip32

// 创建地址的函数,和py是一样的流程,都是调用bip32里面的方法,看函数返回
export function createAddress (params: any): any {
const { seedHex, receiveOrChange, addressIndex, network, method } = params;
const root = bip32.fromSeed(Buffer.from(seedHex, 'hex'));
let path = "m/44'/0'/0'/0/" + addressIndex + '';
if (receiveOrChange === '1') {
path = "m/44'/0'/0'/1/" + addressIndex + '';
}
const child = root.derivePath(path);
let address: string;
switch (method) {
case 'p2pkh':
// eslint-disable-next-line no-case-declarations
const p2pkhAddress = bitcoin.payments.p2pkh({
pubkey: child.publicKey,
network: bitcoin.networks[network]
});
address = p2pkhAddress.address;
break;
case 'p2wpkh':
// eslint-disable-next-line no-case-declarations
const p2wpkhAddress = bitcoin.payments.p2wpkh({
pubkey: child.publicKey,
network: bitcoin.networks[network]
});
address = p2wpkhAddress.address;
break;
case 'p2sh':
// eslint-disable-next-line no-case-declarations
const p2shAddress = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({
pubkey: child.publicKey,
network: bitcoin.networks[network]
})
});
address = p2shAddress.address;
break;
default:
console.log('This way can not support');
}
// 看返回,私钥、公钥、和地址
return {
privateKey: Buffer.from(child.privateKey).toString('hex'),
publicKey: Buffer.from(child.publicKey).toString('hex'),
address
};
}
// 创建多重签名地址
export function createMultiSignAddress (params: any): string {
const { pubkeys, network, method, threshold } = params;
switch (method) {
case 'p2pkh':
return bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2ms({
m: threshold,
network: bitcoin.networks[network],
pubkeys
})
}).address;
case 'p2wpkh':
return bitcoin.payments.p2wsh({
redeem: bitcoin.payments.p2ms({
m: threshold,
network: bitcoin.networks[network],
pubkeys
})
}).address;
case 'p2sh':
return bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wsh({
redeem: bitcoin.payments.p2ms({
m: threshold,
network: bitcoin.networks[network],
pubkeys
})
})
}).address;
default:
console.log('This way can not support');
return '0x00';
}
}
// 创建Schnorr 地址
export function createSchnorrAddress (params: any): any {
bitcoin.initEccLib(ecc);

const { seedHex, receiveOrChange, addressIndex } = params;
const root = bip32.fromSeed(Buffer.from(seedHex, 'hex'));
let path = "m/44'/0'/0'/0/" + addressIndex + '';
if (receiveOrChange === '1') {
path = "m/44'/0'/0'/1/" + addressIndex + '';
}
const childKey = root.derivePath(path);
const privateKey = childKey.privateKey;
if (!privateKey) throw new Error('No private key found');

const publicKey = childKey.publicKey;

const tweak = bitcoin.crypto.taggedHash('TapTweak', publicKey.slice(1, 33));
const tweakedPublicKey = Buffer.from(publicKey);
for (let i = 0; i < 32; ++i) {
tweakedPublicKey[1 + i] ^= tweak[i];
}

const { address } = bitcoin.payments.p2tr({
internalPubkey: tweakedPublicKey.slice(1, 33)
});
// 返回私钥、公钥、和地址
return {
privateKey: Buffer.from(childKey.privateKey).toString('hex'),
publicKey: Buffer.from(childKey.publicKey).toString('hex'),
address
};
}

1.2 离线签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
const ecc = require('tiny-secp256k1');
const { BIP32Factory } = require('bip32');
BIP32Factory(ecc);
const bitcoin = require('bitcoinjs-lib');
const bitcore = require('bitcore-lib');

/**
* @returns
* @param params
*/
// 构建签名Tx
// 它接受一个参数 params,其中包含 privateKey(私钥)、signObj(签名对象)和 network(网络)等属性
export function buildAndSignTx (params: { privateKey: string; signObj: any; network: string; }): string {
const { privateKey, signObj, network } = params;
const net = bitcore.Networks[network];
const inputs = signObj.inputs.map(input => {
return {
address: input.address,
txId: input.txid,
outputIndex: input.vout,
// eslint-disable-next-line new-cap
script: new bitcore.Script.fromAddress(input.address).toHex(),
satoshis: input.amount
};
});
const outputs = signObj.outputs.map(output => {
return {
address: output.address,
satoshis: output.amount
};
});
const transaction = new bitcore.Transaction(net).from(inputs).to(outputs);
transaction.version = 2;
transaction.sign(privateKey);
// 返回交易信息
return transaction.toString();
}
// 构建未签名消息并且签名
export function buildUnsignTxAndSign (params) {
const { keyPair, signObj, network } = params;
const psbt = new bitcoin.Psbt({ network });
const inputs = signObj.inputs.map(input => {
return {
address: input.address,
txId: input.txid,
outputIndex: input.vout,
// eslint-disable-next-line new-cap
script: new bitcore.Script.fromAddress(input.address).toHex(),
satoshis: input.amount
};
});
psbt.addInput(inputs);

const outputs = signObj.outputs.map(output => {
return {
address: output.address,
satoshis: output.amount
};
});
psbt.addOutput(outputs);// 传入输出信息
psbt.toBase64();

psbt.signInput(0, keyPair);// 传入keyPair签名
psbt.finalizeAllInputs();

const signedTransaction = psbt.extractTransaction().toHex();
console.log('signedTransaction==', signedTransaction);
}

1.3 扫链接口(分原生和Rosetta Api)

1.3.1原生Bitcoin接口【原生,不常用】

1.3.1.1获取活跃的最新区块

  • 请求参数
1
curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getchaintips", "params": []}' -H 'content-type: text/plain;' https://thrilling-spring-bush.btc.quiknode.pro/c0d9254cfb049224abd0ece400635e62b791a388/
  • 返回内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{
"result":[
{
"height":845198,
"hash":"00000000000000000000301d584ec5f1c16e89487c05baf035f01875cb763d75",
"branchlen":0,
"status":"active"
},
{
"height":841424,
"hash":"000000000000000000010998fc2714f8ae10ffb73f1986eecc58f5afc457ee07",
"branchlen":1,
"status":"valid-headers"
},
{
"height":838792,
"hash":"00000000000000000002af7214c8796e102b0e9074a5d469266d7afe5af2f087",
"branchlen":1,
"status":"headers-only"
},
{
"height":816358,
"hash":"00000000000000000001d5f92e2dbbfcbc1e859873117e7983dd574857da5e14",
"branchlen":1,
"status":"valid-headers"
},
{
"height":815202,
"hash":"0000000000000000000093917031004a140b6db5c6adec217f814db98d7f0bde",
"branchlen":1,
"status":"valid-fork"
},
],
"error":null,
"id":"curltest"
}
  • “invalid” 该分支至少包含一个无效块
  • “headers-only” 并非该分支的所有块都可用,但 headers 有效
  • “valid-headers”所有块都可用于此分支,但它们从未经过完全验证
  • “valid-fork” 该分支不是活动链的一部分,但经过充分验证
  • “active”这是活跃主链的提示,这当然有效

1.3.1.2 获取区块信息

  • 请求示范
1
curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getblockchaininfo", "params": []}' -H 'content-type: text/plain;' https://thrilling-spring-bush.btc.quiknode.pro/c0d9254cfb049224abd0ece400635e62b791a388/
  • 返回内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"result":{
"chain":"main",
"blocks":845200,
"headers":845200,
"bestblockhash":"000000000000000000027a970865a12b12e4da473011e2033eeca871c957a747",
"difficulty":84381461788831.34,
"time":1716706327,
"mediantime":1716703878,
"verificationprogress":0.999998974207445,
"initialblockdownload":false,
"chainwork":"00000000000000000000000000000000000000007b695dedb46255cb840f5cb6",
"size_on_disk":652535688171,
"pruned":false,
"warnings":""
},
"error":null,
"id":"curltest"
}

1.3.1.3 列出未花费的输入输出

  • 请求示范
1
curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "listunspent", "params": [845000, 845200, [] , true, { "minimumAmount": 0.005 } ]}' -H 'content-type: text/plain;'  https://thrilling-spring-bush.btc.quiknode.pro/c0d9254cfb049224abd0ece400635e62b791a388/
  • 返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[                                
{
"txid" : "",
"vout" : 1,
"address" : "str",
"label" : "str",
"scriptPubKey" : "str",
"amount" : 10000,
"confirmations" : 12,
"redeemScript" : "hex",
"witnessScript" : "str",
"spendable" : false,
"solvable" : false,
"reused" : false,
"desc" : "str",
"safe" : true
},{
"txid" : "",
"vout" : 1,
"address" : "str",
"label" : "str",
"scriptPubKey" : "str",
"amount" : 10000,
"confirmations" : 12,
"redeemScript" : "hex",
"witnessScript" : "str",
"spendable" : false,
"solvable" : false,
"reused" : false,
"desc" : "str",
"safe" : true
},
]

1.3.1.4发送交易到区块链网络

  • 请求参数
1
curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "sendrawtransaction", "params": ["signedhex"]}' -H 'content-type: text/plain;' https://thrilling-spring-bush.btc.quiknode.pro/c0d9254cfb049224abd0ece400635e62b791a388/
  • 返回值
1
成功返回交易 Hash

1.3.2 Rosetta Api【常用,支持生态好】

Bitcoin Rosetta API 是由 Coinbase 提出的 Rosetta 标准的一部分,旨在为区块链和钱包提供一个统一的接口标准。这个标准化的接口使得与各种区块链的交互更加容易和一致,无论是对交易数据的读取还是写入。目前已经支持很多链,包含比特币,以太坊等主流链,也包含像 IoTex 和 Oasis 这样的非主流链。

3.2.1.Rosetta API 概述

Rosetta API 分为两部分:

  • Data API:用于读取区块链数据。
  • Construction API:用于构建和提交交易。

3.2.2. Data API

Data API 提供了一组端点,用于检索区块链数据,如区块、交易、余额等。主要端点包括:

  • /network/list:返回支持的网络列表。
  • /network/status:返回当前网络的状态信息。
  • /network/options:返回支持的网络选项和版本信息。
  • /block:返回指定区块的数据。
  • /block/transaction:返回指定交易的数据。
  • /account/balance:返回指定账户的余额。
  • /mempool:返回当前未确认的交易池。
  • /mempool/transaction:返回指定未确认交易的数据。

3.2.3. Construction API

Construction API 提供了一组端点,用于创建、签名和提交交易。主要端点包括:

  • /construction/preprocess:分析交易需求并返回交易所需的元数据。
  • /construction/metadata:返回构建交易所需的元数据。
  • /construction/payloads:生成待签名的交易有效载荷。
  • /construction/parse:解析交易并返回其操作。
  • /construction/combine:将签名与待签名交易合并。
  • /construction/hash:返回交易的唯一标识符(哈希)。
  • /construction/submit:提交签名后的交易。

3.2.4.开发 BTC 钱包使用到的 Rosetta Api

为了具体实现 Rosetta API,开发者需要遵循 Rosetta 标准并根据比特币区块链的特性进行适配。以下是一些具体实现细节

数据结构:

  • 区块:包含区块哈希、前一个区块哈希、区块高度、时间戳、交易列表等。
  • 交易:包含交易哈希、输入输出列表、金额、地址等。
  • 账户:包含账户地址和余额信息。

用到的接口

  • /network/list:返回比特币主网和测试网信息。
  • /network/status:返回当前最新区块、已同步区块高度、区块链处理器的状态等。
  • /block 和 /block/transaction:返回区块和交易的详细信息,包括交易的输入输出、金额、地址等。
  • /account/balance:通过查询比特币节点,返回指定地址的余额。

发送交易到区块链网络

  • /construction/submit:通过比特币节点提交签名后的交易。

3.3. 文档资料

2、以太坊钱包开发

3、比特币schnorr地址和签名流程细化

而且专属名词太多啦,一句话就蹦出了lucky eap159 eip484 Blake 一大堆,不专注,一下子就让人懵了(主要是我这种币圈外的人)


07-1 Bitcoin 钱包开发简单流程
http://example.com/2024/05/30/07-1比特币钱包开发简单流程/
作者
Wangxiaowang
发布于
2024年5月30日
许可协议