on master help sign in
Merge pull request #570 from pkova/master

bitcoin: add testnet support

commit3a4849a9862c5e064d738c66c088fd0b4c9746a1 authorfang <github@fang.io> dateWed 26 May 2021, 17:11 (5 years ago) parentce0f2b39511b8a
5 files changed+62−72
package.json+1−1 src/form/validators.js+1−2 src/lib/validators.js−19 src/views/Bitcoin/SignTransaction.js+55−29 src/views/Bitcoin/Xpub.js+5−21
package.json+1−1
@@ -55,7 +55,7 @@
5555 "react-teleporter": "^1.1.0",
5656 "secp256k1": "^3.5.2",
5757 "trezor-connect": "^8.1.14",
58− "urbit-key-generation": "^0.18.0",
58+ "urbit-key-generation": "^0.19.0",
5959 "urbit-ob": "^4.1.4",
6060 "urbit-paper-renderer": "^2.0.9",
6161 "web3": "1.0.0-beta.54",
src/form/validators.js+1−2
@@ -20,7 +20,6 @@ import {
2020 validateLength,
2121 validateUnique,
2222 validatePsbt,
23− validateSignablePsbt,
2423} from 'lib/validators';
2524import isPromise from 'lib/isPromise';
2625
@@ -125,7 +124,7 @@ export const buildEmailArrayValidator = () =>
125124 buildValidator([validateUnique, buildArrayValidator(buildEmailValidator())]);
126125
127126export const buildPsbtValidator = btcWallet =>
128− buildValidator([validatePsbt, validateSignablePsbt(btcWallet)]);
127+ buildValidator([validatePsbt]);
129128
130129// the form validator is the composition of all of the field validators
131130// plus an additional form validator function
src/lib/validators.js−19
@@ -141,22 +141,3 @@ export const validatePsbt = base64 => {
141141 return 'Invalid Partially Signed Bitcoin Transaction';
142142 }
143143};
144−
145−export const validateSignablePsbt = btcWallet => base64 => {
146− const psbt = bitcoin.Psbt.fromBase64(base64);
147− return !(
148− psbt.data.inputs.some((input, idx) => {
149− try {
150− // removing already derived part, eg m/84'/0'/0'/0/0 becomes 0/0
151− const path = input.bip32Derivation[0].path
152− .split("m/84'/0'/0'/")
153− .join('');
154− const prv = btcWallet.derivePath(path).privateKey;
155− psbt.signInput(idx, bitcoin.ECPair.fromPrivateKey(prv));
156− return true;
157− } catch (e) {
158− return false;
159− }
160− }) && 'No inputs were signed'
161− );
162−};
src/views/Bitcoin/SignTransaction.js+55−29
@@ -18,9 +18,32 @@ import * as bitcoin from 'bitcoinjs-lib';
1818import BridgeForm from 'form/BridgeForm';
1919import { PsbtInput } from 'form/Inputs';
2020
21−import { Buffer } from 'buffer';
2221import { composeValidator, buildPsbtValidator } from 'form/validators';
2322
23+const BITCOIN_MAINNET_INFO = {
24+ messagePrefix: '\x18Bitcoin Signed Message:\n',
25+ bech32: 'bc',
26+ bip32: {
27+ public: 0x04b24746,
28+ private: 0x04b2430c,
29+ },
30+ pubKeyHash: 0x00,
31+ scriptHash: 0x05,
32+ wif: 0x80,
33+};
34+
35+const BITCOIN_TESTNET_INFO = {
36+ messagePrefix: '\x18Bitcoin Signed Message:\n',
37+ bech32: 'tb',
38+ bip32: {
39+ public: 0x045f1cf6,
40+ private: 0x045f18bc,
41+ },
42+ pubKeyHash: 0x6f,
43+ scriptHash: 0xc4,
44+ wif: 0xef,
45+};
46+
2447export default function SignTransaction() {
2548 const { pop } = useLocalRouter();
2649 const { urbitWallet } = useWallet();
@@ -29,41 +52,44 @@ export default function SignTransaction() {
2952 const [signedTx, setSignedTx] = useState('');
3053 const [psbt, setPsbt] = useState({});
3154
32− const { private: privKey, chain } = need.urbitWallet(
33− urbitWallet
34− ).bitcoin.keys;
35−
36− const btcWallet = bitcoin.bip32.fromPrivateKey(
37− Buffer.from(privKey, 'hex'),
38− Buffer.from(chain, 'hex'),
39− bitcoin.networks.bitcoin
40− );
55+ const { xprv: zprv } = need.urbitWallet(urbitWallet).bitcoinMainnet.keys;
56+ const { xprv: vprv } = need.urbitWallet(urbitWallet).bitcoinTestnet.keys;
4157
4258 const validate = composeValidator({
43− unsignedTransaction: buildPsbtValidator(btcWallet),
59+ unsignedTransaction: buildPsbtValidator(),
4460 });
4561
4662 const onSubmit = values => {
4763 const newPsbt = bitcoin.Psbt.fromBase64(values.unsignedTransaction);
48− const hex = newPsbt.data.inputs
49− .reduce((psbt, input, idx) => {
50− // removing already derived part, eg m/84'/0'/0'/0/0 becomes 0/0
51− const path = input.bip32Derivation[0].path
52− .split("m/84'/0'/0'/")
53− .join('');
54− const prv = btcWallet.derivePath(path).privateKey;
55− try {
64+
65+ const isTestnet = newPsbt.data.inputs[0].bip32Derivation[0].path.startsWith(
66+ "m/84'/1'/0'/"
67+ );
68+
69+ const derivationPrefix = isTestnet ? "m/84'/1'/0'/" : "m/84'/0'/0'/";
70+
71+ const btcWallet = isTestnet
72+ ? bitcoin.bip32.fromBase58(vprv, BITCOIN_TESTNET_INFO)
73+ : bitcoin.bip32.fromBase58(zprv, BITCOIN_MAINNET_INFO);
74+
75+ try {
76+ const hex = newPsbt.data.inputs
77+ .reduce((psbt, input, idx) => {
78+ // removing already derived part, eg m/84'/0'/0'/0/0 becomes 0/0
79+ const path = input.bip32Derivation[0].path
80+ .split(derivationPrefix)
81+ .join('');
82+ const prv = btcWallet.derivePath(path).privateKey;
5683 return psbt.signInput(idx, bitcoin.ECPair.fromPrivateKey(prv));
57− } catch (e) {
58− return psbt;
59− }
60− }, newPsbt)
61− .finalizeAllInputs()
62− .extractTransaction()
63− .toHex();
64−
65− setPsbt(newPsbt);
66− setSignedTx(hex);
84+ }, newPsbt)
85+ .finalizeAllInputs()
86+ .extractTransaction()
87+ .toHex();
88+ setPsbt(newPsbt);
89+ setSignedTx(hex);
90+ } catch (error) {
91+ return { unsignedTransaction: 'Cannot sign transaction, mismatching keys.' };
92+ }
6793 };
6894
6995 const initialValues = {};
src/views/Bitcoin/Xpub.js+5−21
@@ -12,32 +12,16 @@ import CopiableAddressWrap from 'components/CopiableAddressWrap';
1212import Highlighted from 'components/Highlighted';
1313
1414import * as need from 'lib/need';
15−import * as bitcoin from 'bitcoinjs-lib';
16−import * as bs58check from 'bs58check';
17−import { Buffer } from 'buffer';
18−
19−function xpubToZpub(xpub) {
20− var data = bs58check.decode(xpub);
21− data = data.slice(4);
22− data = Buffer.concat([Buffer.from('04b24746', 'hex'), data]);
23− return bs58check.encode(data);
24−}
2515
2616export default function Xpub() {
2717 const { pop } = useLocalRouter();
2818 const { urbitWallet } = useWallet();
2919
30− const { public: pubKey, chain } = need.urbitWallet(urbitWallet).bitcoin.keys;
20+ const { xpub: zpub } = need.urbitWallet(urbitWallet).bitcoinMainnet.keys;
21+ const { xpub: vpub } = need.urbitWallet(urbitWallet).bitcoinTestnet.keys;
3122
32− const zPub = xpubToZpub(
33− bitcoin.bip32
34− .fromPublicKey(
35− Buffer.from(pubKey, 'hex'),
36− Buffer.from(chain, 'hex'),
37− bitcoin.networks.bitcoin
38− )
39− .toBase58()
40− );
23+ // set to false for BTC testnet
24+ const isMainnet = true;
4125
4226 return (
4327 <View pop={pop} inset>
@@ -57,7 +41,7 @@ export default function Xpub() {
5741 </Grid.Item>
5842 <Grid.Item full className="mt4" as={Text}>
5943 <Highlighted>
60− <CopiableAddressWrap>{zPub}</CopiableAddressWrap>
44+ <CopiableAddressWrap>{isMainnet ? zpub : vpub}</CopiableAddressWrap>
6145 </Highlighted>
6246 </Grid.Item>
6347 </Grid>