on 98d950d help sign in
bridge/src/lib/keys.js
248 lines 6.1 kB history raw
1import BN from 'bn.js';
2import { Just, Nothing } from 'folktale/maybe';
4import * as noun from '../nockjs/noun';
5import * as serial from '../nockjs/serial';
6import * as kg from 'urbit-key-generation';
8import { addHexPrefix, eqAddr } from './utils/address';
9import { shas } from './networkCode';
11// the curve param for the network keys
12export const NETWORK_KEY_CURVE_PARAMETER = '42';
13// the current crypto suite version
14export const CRYPTO_SUITE_VERSION = 1;
16export const CURVE_ZERO_ADDR =
17 '0x0000000000000000000000000000000000000000000000000000000000000000';
19// ctsy joemfb
20const b64 = buf => {
21 let hex = buf.reverse().toString('hex');
22 let n = new BN(hex, 'hex');
23 let c = [];
24 while (1 === n.cmpn(0)) {
25 c.push(n.andln(0x3f));
26 n = n.shrn(6);
27 }
29 // prettier-ignore
30 const trans = j =>
31 10 > j
32 ? j + 48
33 : 36 > j
34 ? j + 87
35 : 62 > j
36 ? j + 29
37 : 62 === j
38 ? 45
39 : 126;
41 return (
42 '0w' +
43 c.reduce(
44 (a, b, i) =>
45 String.fromCharCode(trans(b)) + (i && 0 === i % 5 ? '.' : '') + a,
46 ''
47 )
48 );
49};
51const jam = seed => {
52 const hex = serial
53 .jam(seed)
54 .toString()
55 .slice(2);
56 const pad = hex.length % 2 === 0 ? hex : '0' + hex;
57 return Buffer.from(pad, 'hex').reverse();
58};
60// simple alias to avoid importing kg into component scope
61export const deriveNetworkKeys = seed => kg.deriveNetworkKeys(seed);
63export const createRing = pair =>
64 pair.crypt.private + pair.auth.private + NETWORK_KEY_CURVE_PARAMETER;
66/**
67 * @param {object} pair
68 * @param {number} point
69 * @param {number} revision
70 * @return {string}
71 */
72export const compileNetworkKey = (pair, point, revision) => {
73 const bnsec = new BN(createRing(pair), 'hex');
75 const sed = noun.dwim(
76 noun.Atom.fromInt(point),
77 noun.Atom.fromInt(revision),
78 noun.Atom.fromString(bnsec.toString()),
79 noun.Atom.fromInt(0)
80 );
82 return b64(jam(sed));
83};
85/**
86 * @param {number} point
87 * @param {array<{revision: number, pair: object}>} keys
88 * @return {string}
89 */
90export const compileMultiKey = (point, keys) => {
91 const kyz = keys.map(k => {
92 const bnsec = new BN(createRing(k.pair), 'hex');
93 return noun.dwim(
94 noun.Atom.fromInt(k.revision),
95 noun.Atom.fromString(bnsec.toString())
96 );
97 });
98 kyz.push(noun.Atom.fromInt(0));
100 const fed = noun.dwim(
101 noun.dwim(noun.Atom.fromInt(1), noun.Atom.fromInt(0)), // version
102 noun.Atom.fromInt(point), // ship
103 noun.dwim(kyz) // keys
104 );
106 return b64(jam(fed));
107};
109/**
110 * @param {object} urbitWallet
111 * @param {number} revision
112 * @return {Promise<Maybe<string>>}
113 */
114export const deriveNetworkSeedFromUrbitWallet = async (
115 urbitWallet,
116 revision = 1
117) => {
118 return await deriveNetworkSeedFromMnemonic(
119 urbitWallet.management.seed,
120 urbitWallet.meta.passphrase,
121 revision
122 );
123};
125/**
126 * @param {Maybe<any>} wallet
127 * @param {string} authMnemonic
128 * @param {object} details
129 * @param {number} revision
130 * @return {Promise<Maybe<string>>}
131 */
132export const deriveNetworkSeedFromManagementMnemonic = async (
133 wallet,
134 authMnemonic,
135 details,
136 revision = 1
137) => {
138 const isManagementProxy = eqAddr(wallet.address, details.managementProxy);
140 // the network seed is derivable iff this mnemonic is the management proxy
141 if (isManagementProxy) {
142 return await deriveNetworkSeedFromMnemonic(
143 authMnemonic,
144 wallet.passphrase,
145 revision
146 );
147 }
149 return Nothing();
150};
152/**
153 * @param {string} mnemonic
154 * @param {string} passphrase
155 * @param {number} revision
156 * @return {Promise<Maybe<string>>}
157 */
158const deriveNetworkSeedFromMnemonic = async (
159 mnemonic,
160 passphrase,
161 revision
162) => {
163 //NOTE revision is the point's on-chain revision number. since common uhdw
164 // usage derives the first key at revision/index 0, we need to decrement
165 // the on-chain revision number by one to get the number to derive with.
166 return Just(await kg.deriveNetworkSeed(mnemonic, passphrase, revision - 1));
167};
169/**
170 * @param {number} point
171 * @param {string} authToken
172 * @param {number} revision
173 * @return {Maybe<string>}
174 */
175export const deriveNetworkSeedFromAuthToken = (point, authToken, revision) => {
176 //NOTE revision is the point's on-chain revision number.
177 // since deriveNetworkSeedFromMnemonic does this too, we decrement the
178 // revision number by one before deriving from it.
179 const salt = Buffer.from(`revision-${point}-${revision - 1}`);
180 const networkSeed = shas(authToken, salt)
181 .toString('hex')
182 .slice(0, 32);
183 return Just(networkSeed);
184};
186/**
187 * Derives from either a full Urbit Wallet, a current management mnemonic,
188 * or an auth token, in that order of preference.
189 * @return {Promise<Maybe<string>>}
190 */
191export const attemptNetworkSeedDerivation = async ({
192 urbitWallet,
193 wallet,
194 authMnemonic,
195 details,
196 point,
197 authToken,
198 revision,
199}) => {
200 if (Just.hasInstance(urbitWallet)) {
201 return await deriveNetworkSeedFromUrbitWallet(urbitWallet.value, revision);
202 }
204 if (Just.hasInstance(wallet) && Just.hasInstance(authMnemonic)) {
205 const managementSeed = await deriveNetworkSeedFromManagementMnemonic(
206 wallet.value,
207 authMnemonic.value,
208 details,
209 revision
210 );
211 if (Just.hasInstance(managementSeed)) {
212 return managementSeed;
213 }
214 }
216 if (Just.hasInstance(authToken)) {
217 return deriveNetworkSeedFromAuthToken(point, authToken.value, revision);
218 }
220 return Nothing();
221};
223/**
224 *
225 * @param {object} pair - type NetworkKeys
226 * @param {object} details - type L1Point
227 * @return {boolean}
228 */
229export const keysMatchChain = (pair, details) => {
230 const { crypt, auth } = pair;
231 const { encryptionKey, authenticationKey } = details;
233 return (
234 encryptionKey === addHexPrefix(crypt.public) &&
235 authenticationKey === addHexPrefix(auth.public)
236 );
237};
239export const segmentNetworkKey = hex => {
240 if (hex === CURVE_ZERO_ADDR) {
241 return null;
242 }
244 const sl = i => hex.slice(i, i + 4);
245 const rowFrom = i => `${sl(i)}.${sl(i + 4)}.${sl(i + 8)}.${sl(i + 12)}`;
247 return [rowFrom(2), rowFrom(18), rowFrom(34), rowFrom(50)];
248};