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