on e88ec6d help sign in
bridge/src/views/Bitcoin.js
154 lines 4.8 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 hd = 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(hd),
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 = hd.derivePath(path).privateKey;
77 return psbt.signInput(idx, bitcoin.ECPair.fromPrivateKey(prv));
78 }, newPsbt)
79 .finalizeAllInputs()
80 .extractTransaction()
81 .toHex();
83 if (psbt.toHex() !== newPsbt.toHex()) {
84 setPsbt(newPsbt);
85 }
86 setSignedTx(hex);
87 } else {
88 setSignedTx('');
89 }
90 };
92 return (
93 <View pop={pop} inset>
94 <Grid className="mt4">
95 <Grid.Item full as={ViewHeader}>
96 Bitcoin
97 </Grid.Item>
98 <Grid.Item full as={WarningBox}>
99 We do not recommend using this feature for significant amounts of
100 money, since your private keys are exposed to the browser.
101 </Grid.Item>
102 <Grid.Item full className="mt4" as={Text}>
103 Below is the bitcoin extended public key derived from your point
104 {' ' + pointName}. You can paste it to Landscape to generate an
105 unsigned transaction.
106 </Grid.Item>
107 <Grid.Item full className="mt4" as={Text}>
108 <Highlighted>
109 <CopiableAddressWrap>{zPub}</CopiableAddressWrap>
110 </Highlighted>
111 </Grid.Item>
112 <Grid.Item full className="mt4" as={Text}>
113 Next, paste the unsigned transaction below to sign it with your
114 private key.
115 </Grid.Item>
116 <BridgeForm validate={validate} onValues={onValues}>
117 {() => (
118 <Grid.Item
119 full
120 as={PsbtInput}
121 name="unsignedTransaction"
122 label="Unsigned transaction"
123 className="mt4"
124 />
125 )}
126 </BridgeForm>
127 {signedTx && (
128 <Grid.Item full className="mt4" as={Text}>
129 Transaction outputs:
130 </Grid.Item>
131 )}
132 {signedTx &&
133 psbt.txOutputs.map(e => (
134 <Grid.Item full className="mt4" key={e.address}>
135 <span>{e.address}</span>
136 <div className="mt1 mono black f6">{e.value} Satoshis</div>
137 </Grid.Item>
138 ))}
139 {signedTx && (
140 <>
141 <Grid.Item full className="mt4" as={ViewHeader}>
142 Signed transaction:
143 </Grid.Item>
144 <Grid.Item full as={Text}>
145 <Highlighted>
146 <CopiableAddressWrap>{signedTx}</CopiableAddressWrap>
147 </Highlighted>
148 </Grid.Item>
149 </>
150 )}
151 </Grid>
152 </View>
153 );