on 20a0641 help sign in
bridge/src/views/Bitcoin.js
160 lines 5.0 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 xpubToYpub(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();
37 window.bitcoin = bitcoin;
39 const empty = new bitcoin.Psbt();
41 const [signedTx, setSignedTx] = useState('');
42 const [psbt, setPsbt] = useState(empty);
44 const pointName = ob.patp(need.point(pointCursor));
46 const privKey = need.urbitWallet(urbitWallet).bitcoin.keys.private;
47 const pubKey = need.urbitWallet(urbitWallet).bitcoin.keys.public;
48 const chain = need.urbitWallet(urbitWallet).bitcoin.keys.chain;
50 const hd = bitcoin.bip32.fromPrivateKey(
51 Buffer.from(privKey, 'hex'),
52 Buffer.from(chain, 'hex'),
53 bitcoin.networks.bitcoin
54 );
56 const zPub = xpubToYpub(
57 bitcoin.bip32
58 .fromPublicKey(
59 Buffer.from(pubKey, 'hex'),
60 Buffer.from(chain, 'hex'),
61 bitcoin.networks.bitcoin
62 )
63 .toBase58()
64 );
66 const validate = composeValidator({
67 unsignedTransaction: buildPsbtValidator(hd),
68 });
70 const onValues = ({ valid, values, form }) => {
71 try {
72 window.psbt = bitcoin.Psbt.fromBase64(values.unsignedTransaction);
73 } catch (e) {
74 // nop
75 }
76 if (valid) {
77 const newPsbt = bitcoin.Psbt.fromBase64(values.unsignedTransaction);
78 const hex = newPsbt.data.inputs
79 .reduce((psbt, input, idx) => {
80 // removing already derived part, eg 'm/84'/0'/0'/0/0' becomes '0/0'
81 const path = input.bip32Derivation[0].path.substring(12);
82 const prv = hd.derivePath(path).privateKey;
83 return psbt.signInput(idx, bitcoin.ECPair.fromPrivateKey(prv));
84 }, newPsbt)
85 .finalizeAllInputs()
86 .extractTransaction()
87 .toHex();
89 if (psbt.toHex() !== newPsbt.toHex()) {
90 setPsbt(newPsbt);
91 }
92 setSignedTx(hex);
93 } else {
94 setSignedTx('');
95 }
96 };
98 return (
99 <View pop={pop} inset>
100 <Grid className="mt4">
101 <Grid.Item full as={ViewHeader}>
102 Bitcoin
103 </Grid.Item>
104 <Grid.Item full as={WarningBox}>
105 We do not recommend using this feature for significant amounts of
106 money, since your private keys are exposed to the browser.
107 </Grid.Item>
108 <Grid.Item full className="mt4" as={Text}>
109 Below is the bitcoin extended public key derived from your point
110 {' ' + pointName}. You can paste it to Landscape to generate an
111 unsigned transaction.
112 </Grid.Item>
113 <Grid.Item full className="mt4" as={Text}>
114 <Highlighted>
115 <CopiableAddressWrap>{zPub}</CopiableAddressWrap>
116 </Highlighted>
117 </Grid.Item>
118 <Grid.Item full className="mt4" as={Text}>
119 Next, paste the unsigned transaction below to sign it with your
120 private key.
121 </Grid.Item>
122 <BridgeForm validate={validate} onValues={onValues}>
123 {() => (
124 <Grid.Item
125 full
126 as={PsbtInput}
127 name="unsignedTransaction"
128 label="Unsigned transaction"
129 className="mt4"
130 />
131 )}
132 </BridgeForm>
133 {signedTx && (
134 <Grid.Item full className="mt4" as={Text}>
135 Transaction outputs:
136 </Grid.Item>
137 )}
138 {signedTx &&
139 psbt.txOutputs.map(e => (
140 <Grid.Item full className="mt4" key={e.address}>
141 <span>{e.address}</span>
142 <div className="mt1 mono black f6">{e.value} Satoshis</div>
143 </Grid.Item>
144 ))}
145 {signedTx && (
146 <>
147 <Grid.Item full className="mt4" as={ViewHeader}>
148 Signed transaction:
149 </Grid.Item>
150 <Grid.Item full as={Text}>
151 <Highlighted>
152 <CopiableAddressWrap>{signedTx}</CopiableAddressWrap>
153 </Highlighted>
154 </Grid.Item>
155 </>
156 )}
157 </Grid>
158 </View>
159 );