on d779a1a help sign in
bridge/src/views/Bitcoin.js
158 lines 4.9 kB history raw
1import React, { useState } from 'react';
2import { Grid, Text } from 'indigo-react';
3import { useWallet } from 'store/wallet';
4import { usePointCursor } from 'store/pointCursor';
6import { useLocalRouter } from 'lib/LocalRouter';
8import ViewHeader from 'components/ViewHeader';
9import View from 'components/View';
10import WarningBox from 'components/WarningBox';
12import CopiableAddressWrap from 'components/CopiableAddressWrap';
13import Highlighted from 'components/Highlighted';
15import { PsbtInput } from 'form/Inputs';
16import BridgeForm from 'form/BridgeForm';
18import * as need from 'lib/need';
19import * as bitcoin from 'bitcoinjs-lib';
20import * as bs58check from 'bs58check';
21import ob from 'urbit-ob';
22import { Buffer } from 'buffer';
23import { composeValidator, buildPsbtValidator } from 'form/validators';
25function xpubToZpub(xpub) {
26 var data = bs58check.decode(xpub);
27 data = data.slice(4);
28 data = Buffer.concat([Buffer.from('04b24746', 'hex'), data]);
29 return bs58check.encode(data);
32export default function Bitcoin() {
33 const { pop } = useLocalRouter();
34 const { urbitWallet } = useWallet();
35 const { pointCursor } = usePointCursor();
38 const empty = new bitcoin.Psbt();
40 const [signedTx, setSignedTx] = useState('');
41 const [psbt, setPsbt] = useState(empty);
43 const pointName = ob.patp(need.point(pointCursor));
45 const privKey = need.urbitWallet(urbitWallet).bitcoin.keys.private;
46 const pubKey = need.urbitWallet(urbitWallet).bitcoin.keys.public;
47 const chain = need.urbitWallet(urbitWallet).bitcoin.keys.chain;
49 const btcWallet = bitcoin.bip32.fromPrivateKey(
50 Buffer.from(privKey, 'hex'),
51 Buffer.from(chain, 'hex'),
52 bitcoin.networks.bitcoin
53 );
55 const zPub = xpubToZpub(
56 bitcoin.bip32
57 .fromPublicKey(
58 Buffer.from(pubKey, 'hex'),
59 Buffer.from(chain, 'hex'),
60 bitcoin.networks.bitcoin
61 )
62 .toBase58()
63 );
65 const validate = composeValidator({
66 unsignedTransaction: buildPsbtValidator(btcWallet),
67 });
69 const onValues = ({ valid, values, form }) => {
70 if (valid) {
71 const newPsbt = bitcoin.Psbt.fromBase64(values.unsignedTransaction);
72 const hex = newPsbt.data.inputs
73 .reduce((psbt, input, idx) => {
74 // removing already derived part, eg m/84'/0'/0'/0/0 becomes 0/0
75 const path = input.bip32Derivation[0].path.substring(12);
76 const prv = btcWallet.derivePath(path).privateKey;
77 try {
78 return psbt.signInput(idx, bitcoin.ECPair.fromPrivateKey(prv));
79 } catch (e) {
80 return psbt;
81 }
82 }, newPsbt)
83 .finalizeAllInputs()
84 .extractTransaction()
85 .toHex();
87 if (psbt.toHex() !== newPsbt.toHex()) {
88 setPsbt(newPsbt);
89 }
90 setSignedTx(hex);
91 } else {
92 setSignedTx('');
93 }
94 };
96 return (
97 <View pop={pop} inset>
98 <Grid className="mt4">
99 <Grid.Item full as={ViewHeader}>
100 Bitcoin
101 </Grid.Item>
102 <Grid.Item full as={WarningBox}>
103 We do not recommend using this feature for significant amounts of
104 money, since your private keys are exposed to the browser.
105 </Grid.Item>
106 <Grid.Item full className="mt4" as={Text}>
107 Below is the bitcoin extended public key derived from your point
108 {' ' + pointName}. You can paste it to Landscape to generate an
109 unsigned transaction.
110 </Grid.Item>
111 <Grid.Item full className="mt4" as={Text}>
112 <Highlighted>
113 <CopiableAddressWrap>{zPub}</CopiableAddressWrap>
114 </Highlighted>
115 </Grid.Item>
116 <Grid.Item full className="mt4" as={Text}>
117 Next, paste the unsigned transaction below to sign it with your
118 private key.
119 </Grid.Item>
120 <BridgeForm validate={validate} onValues={onValues}>
121 {() => (
122 <Grid.Item
123 full
124 as={PsbtInput}
125 name="unsignedTransaction"
126 label="Unsigned transaction"
127 className="mt4"
128 />
129 )}
130 </BridgeForm>
131 {signedTx && (
132 <Grid.Item full className="mt4" as={Text}>
133 Transaction outputs:
134 </Grid.Item>
135 )}
136 {signedTx &&
137 psbt.txOutputs.map(e => (
138 <Grid.Item full className="mt4" key={e.address}>
139 <span>{e.address}</span>
140 <div className="mt1 mono black f6">{e.value} Satoshis</div>
141 </Grid.Item>
142 ))}
143 {signedTx && (
144 <>
145 <Grid.Item full className="mt4" as={ViewHeader}>
146 Signed transaction:
147 </Grid.Item>
148 <Grid.Item full as={Text}>
149 <Highlighted>
150 <CopiableAddressWrap>{signedTx}</CopiableAddressWrap>
151 </Highlighted>
152 </Grid.Item>
153 </>
154 )}
155 </Grid>
156 </View>
157 );