co.js (10466B)
1 // ++ co 2 // 3 // See arvo/sys/hoon.hoon. 4 5 const BN = require('bn.js') 6 const chunk = require('lodash.chunk') 7 const isEqual = require('lodash.isequal') 8 9 const ob = require('./ob') 10 11 const zero = new BN(0) 12 const one = new BN(1) 13 const two = new BN(2) 14 const three = new BN(3) 15 const four = new BN(4) 16 const five = new BN(5) 17 18 const pre = ` 19 dozmarbinwansamlitsighidfidlissogdirwacsabwissib\ 20 rigsoldopmodfoglidhopdardorlorhodfolrintogsilmir\ 21 holpaslacrovlivdalsatlibtabhanticpidtorbolfosdot\ 22 losdilforpilramtirwintadbicdifrocwidbisdasmidlop\ 23 rilnardapmolsanlocnovsitnidtipsicropwitnatpanmin\ 24 ritpodmottamtolsavposnapnopsomfinfonbanmorworsip\ 25 ronnorbotwicsocwatdolmagpicdavbidbaltimtasmallig\ 26 sivtagpadsaldivdactansidfabtarmonranniswolmispal\ 27 lasdismaprabtobrollatlonnodnavfignomnibpagsopral\ 28 bilhaddocridmocpacravripfaltodtiltinhapmicfanpat\ 29 taclabmogsimsonpinlomrictapfirhasbosbatpochactid\ 30 havsaplindibhosdabbitbarracparloddosbortochilmac\ 31 tomdigfilfasmithobharmighinradmashalraglagfadtop\ 32 mophabnilnosmilfopfamdatnoldinhatnacrisfotribhoc\ 33 nimlarfitwalrapsarnalmoslandondanladdovrivbacpol\ 34 laptalpitnambonrostonfodponsovnocsorlavmatmipfip\ 35 ` 36 37 const suf = ` 38 zodnecbudwessevpersutletfulpensytdurwepserwylsun\ 39 rypsyxdyrnuphebpeglupdepdysputlughecryttyvsydnex\ 40 lunmeplutseppesdelsulpedtemledtulmetwenbynhexfeb\ 41 pyldulhetmevruttylwydtepbesdexsefwycburderneppur\ 42 rysrebdennutsubpetrulsynregtydsupsemwynrecmegnet\ 43 secmulnymtevwebsummutnyxrextebfushepbenmuswyxsym\ 44 selrucdecwexsyrwetdylmynmesdetbetbeltuxtugmyrpel\ 45 syptermebsetdutdegtexsurfeltudnuxruxrenwytnubmed\ 46 lytdusnebrumtynseglyxpunresredfunrevrefmectedrus\ 47 bexlebduxrynnumpyxrygryxfeptyrtustyclegnemfermer\ 48 tenlusnussyltecmexpubrymtucfyllepdebbermughuttun\ 49 bylsudpemdevlurdefbusbeprunmelpexdytbyttyplevmyl\ 50 wedducfurfexnulluclennerlexrupnedlecrydlydfenwel\ 51 nydhusrelrudneshesfetdesretdunlernyrsebhulryllud\ 52 remlysfynwerrycsugnysnyllyndyndemluxfedsedbecmun\ 53 lyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes\ 54 ` 55 56 const patp2syls = name => 57 name.replace(/[\^~-]/g,'').match(/.{1,3}/g) 58 || [] 59 60 const splitAt = (index, str) => [str.slice(0, index), str.slice(index)] 61 62 const prefixes = pre.match(/.{1,3}/g) 63 64 const suffixes = suf.match(/.{1,3}/g) 65 66 const bex = (n) => 67 two.pow(n) 68 69 const rsh = (a, b, c) => 70 c.div(bex(bex(a).mul(b))) 71 72 const met = (a, b, c = zero) => 73 b.eq(zero) 74 ? c 75 : met(a, rsh(a, one, b), c.add(one)) 76 77 const end = (a, b, c) => 78 c.mod(bex(bex(a).mul(b))) 79 80 /** 81 * Convert a hex-encoded string to a @p-encoded string. 82 * 83 * @param {String} hex 84 * @return {String} 85 */ 86 const hex2patp = (hex) => { 87 if (hex === null) { 88 throw new Error('hex2patp: null input') 89 } 90 return patp(new BN(hex, 'hex')) 91 } 92 93 /** 94 * Convert a @p-encoded string to a hex-encoded string. 95 * 96 * @param {String} name @p 97 * @return {String} 98 */ 99 const patp2hex = (name) => { 100 if (isValidPat(name) === false) { 101 throw new Error('patp2hex: not a valid @p') 102 } 103 const syls = patp2syls(name) 104 105 const syl2bin = idx => 106 idx.toString(2).padStart(8, '0') 107 108 const addr = syls.reduce((acc, syl, idx) => 109 idx % 2 !== 0 || syls.length === 1 110 ? acc + syl2bin(suffixes.indexOf(syl)) 111 : acc + syl2bin(prefixes.indexOf(syl)), 112 '') 113 114 const bn = new BN(addr, 2) 115 const hex = ob.fynd(bn).toString('hex') 116 return hex.length % 2 !== 0 117 ? hex.padStart(hex.length + 1, '0') 118 : hex 119 } 120 121 /** 122 * Convert a @p-encoded string to a bignum. 123 * 124 * @param {String} name @p 125 * @return {BN} 126 */ 127 const patp2bn = name => 128 new BN(patp2hex(name), 'hex') 129 130 /** 131 * Convert a @p-encoded string to a decimal-encoded string. 132 * 133 * @param {String} name @p 134 * @return {String} 135 */ 136 const patp2dec = name => { 137 let bn 138 try { 139 bn = patp2bn(name) 140 } catch(_) { 141 throw new Error('patp2dec: not a valid @p') 142 } 143 return bn.toString() 144 } 145 146 /** 147 * Convert a number to a @q-encoded string. 148 * 149 * @param {String, Number, BN} arg 150 * @return {String} 151 */ 152 const patq = (arg) => { 153 const bn = new BN(arg) 154 const buf = bn.toArrayLike(Buffer) 155 return buf2patq(buf) 156 } 157 158 /** 159 * Convert a Buffer into a @q-encoded string. 160 * 161 * @param {Buffer} buf 162 * @return {String} 163 */ 164 const buf2patq = buf => { 165 const chunked = 166 buf.length % 2 !== 0 && buf.length > 1 167 ? [[buf[0]]].concat(chunk(buf.slice(1), 2)) 168 : chunk(buf, 2) 169 170 const prefixName = byts => 171 byts[1] === undefined 172 ? prefixes[0] + suffixes[byts[0]] 173 : prefixes[byts[0]] + suffixes[byts[1]] 174 175 const name = byts => 176 byts[1] === undefined 177 ? suffixes[byts[0]] 178 : prefixes[byts[0]] + suffixes[byts[1]] 179 180 const alg = pair => 181 pair.length % 2 !== 0 && chunked.length > 1 182 ? prefixName(pair) 183 : name(pair) 184 185 return chunked.reduce((acc, elem) => 186 acc + (acc === '~' ? '' : '-') + alg(elem), '~') 187 } 188 189 /** 190 * Convert a hex-encoded string to a @q-encoded string. 191 * 192 * Note that this preserves leading zero bytes. 193 * 194 * @param {String} hex 195 * @return {String} 196 */ 197 const hex2patq = arg => { 198 const hex = 199 arg.length % 2 !== 0 200 ? arg.padStart(arg.length + 1, '0') 201 : arg 202 203 const buf = Buffer.from(hex, 'hex') 204 return buf2patq(buf) 205 } 206 207 /** 208 * Convert a @q-encoded string to a hex-encoded string. 209 * 210 * Note that this preserves leading zero bytes. 211 * 212 * @param {String} name @q 213 * @return {String} 214 */ 215 const patq2hex = name => { 216 if (isValidPat(name) === false) { 217 throw new Error('patq2hex: not a valid @q') 218 } 219 const chunks = name.slice(1).split('-') 220 const dec2hex = dec => 221 dec.toString(16).padStart(2, '0') 222 223 const splat = chunks.map(chunk => { 224 let syls = splitAt(3, chunk) 225 return syls[1] === '' 226 ? dec2hex(suffixes.indexOf(syls[0])) 227 : dec2hex(prefixes.indexOf(syls[0])) + 228 dec2hex(suffixes.indexOf(syls[1])) 229 }) 230 231 return name.length === 0 232 ? '00' 233 : splat.join('') 234 } 235 236 /** 237 * Convert a @q-encoded string to a bignum. 238 * 239 * @param {String} name @q 240 * @return {BN} 241 */ 242 const patq2bn = name => 243 new BN(patq2hex(name), 'hex') 244 245 /** 246 * Convert a @q-encoded string to a decimal-encoded string. 247 * 248 * @param {String} name @q 249 * @return {String} 250 */ 251 const patq2dec = name => { 252 let bn 253 try { 254 bn = patq2bn(name) 255 } catch(_) { 256 throw new Error('patq2dec: not a valid @q') 257 } 258 return bn.toString() 259 } 260 261 /** 262 * Determine the ship class of a @p value. 263 * 264 * @param {String} @p 265 * @return {String} 266 */ 267 const clan = who => { 268 let name 269 try { 270 name = patp2bn(who) 271 } catch(_) { 272 throw new Error('clan: not a valid @p') 273 } 274 275 const wid = met(three, name) 276 return wid.lte(one) 277 ? 'galaxy' 278 : wid.eq(two) 279 ? 'star' 280 : wid.lte(four) 281 ? 'planet' 282 : wid.lte(new BN(8)) 283 ? 'moon' 284 : 'comet' 285 } 286 287 /** 288 * Determine the parent of a @p value. 289 * 290 * @param {String} @p 291 * @return {String} 292 */ 293 const sein = name => { 294 let who 295 try { 296 who = patp2bn(name) 297 } catch(_) { 298 throw new Error('sein: not a valid @p') 299 } 300 301 let mir 302 try { 303 mir = clan(name) 304 } catch(_) { 305 throw new Error('sein: not a valid @p') 306 } 307 308 const res = 309 mir === 'galaxy' 310 ? who 311 : mir === 'star' 312 ? end(three, one, who) 313 : mir === 'planet' 314 ? end(four, one, who) 315 : mir === 'moon' 316 ? end(five, one, who) 317 : zero 318 return patp(res) 319 } 320 321 /** 322 * Weakly check if a string is a valid @p or @q value. 323 * 324 * This is, at present, a pretty weak sanity check. It doesn't confirm the 325 * structure precisely (e.g. dashes), and for @q, it's required that q values 326 * of (greater than one) odd bytelength have been zero-padded. So, for 327 * example, '~doznec-binwod' will be considered a valid @q, but '~nec-binwod' 328 * will not. 329 * 330 * @param {String} name a @p or @q value 331 * @return {boolean} 332 */ 333 const isValidPat = name => { 334 if (typeof name !== 'string') { 335 throw new Error('isValidPat: non-string input') 336 } 337 338 const leadingTilde = name.slice(0, 1) === '~' 339 340 if (leadingTilde === false || name.length < 4) { 341 return false 342 } else { 343 const syls = patp2syls(name) 344 const wrongLength = syls.length % 2 !== 0 && syls.length !== 1 345 const sylsExist = syls.reduce((acc, syl, index) => 346 acc && 347 (index % 2 !== 0 || syls.length === 1 348 ? suffixes.includes(syl) 349 : prefixes.includes(syl)), 350 true) 351 352 return !wrongLength && sylsExist 353 } 354 } 355 356 /** 357 * Validate a @p string. 358 * 359 * @param {String} str a string 360 * @return {boolean} 361 */ 362 const isValidPatp = str => 363 isValidPat(str) && str === patp(patp2dec(str)) 364 365 /** 366 * Validate a @q string. 367 * 368 * @param {String} str a string 369 * @return {boolean} 370 */ 371 const isValidPatq = str => 372 isValidPat(str) && eqPatq(str, patq(patq2dec(str))) 373 374 /** 375 * Remove all leading zero bytes from a sliceable value. 376 * @param {String, Buffer, Array} 377 * @return {String} 378 */ 379 const removeLeadingZeroBytes = str => 380 str.slice(0, 2) === '00' 381 ? removeLeadingZeroBytes(str.slice(2)) 382 : str 383 384 /** 385 * Equality comparison, modulo leading zero bytes. 386 * @param {String, Buffer, Array} 387 * @param {String, Buffer, Array} 388 * @return {Bool} 389 */ 390 const eqModLeadingZeroBytes = (s, t) => 391 isEqual(removeLeadingZeroBytes(s), removeLeadingZeroBytes(t)) 392 393 /** 394 * Equality comparison on @q values. 395 * @param {String} p a @q-encoded string 396 * @param {String} q a @q-encoded string 397 * @return {Bool} 398 */ 399 const eqPatq = (p, q) => { 400 let phex 401 try { 402 phex = patq2hex(p) 403 } catch(_) { 404 throw new Error('eqPatq: not a valid @q') 405 } 406 407 let qhex 408 try { 409 qhex = patq2hex(q) 410 } catch(_) { 411 throw new Error('eqPatq: not a valid @q') 412 } 413 414 return eqModLeadingZeroBytes(phex, qhex) 415 } 416 417 /** 418 * Convert a number to a @p-encoded string. 419 * 420 * @param {String, Number, BN} arg 421 * @return {String} 422 */ 423 const patp = (arg) => { 424 if (arg === null) { 425 throw new Error('patp: null input') 426 } 427 const n = new BN(arg) 428 429 const sxz = ob.fein(n) 430 const dyy = met(four, sxz) 431 432 const loop = (tsxz, timp, trep) => { 433 const log = end(four, one, tsxz) 434 const pre = prefixes[rsh(three, one, log)] 435 const suf = suffixes[end(three, one, log)] 436 const etc = 437 (timp.mod(four)).eq(zero) 438 ? timp.eq(zero) 439 ? '' 440 : '--' 441 : '-' 442 443 const res = pre + suf + etc + trep 444 445 return timp.eq(dyy) 446 ? trep 447 : loop(rsh(four, one, tsxz), timp.add(one), res) 448 } 449 450 const dyx = met(three, sxz) 451 452 return '~' + 453 (dyx.lte(one) 454 ? suffixes[sxz] 455 : loop(sxz, zero, '')) 456 } 457 458 module.exports = { 459 patp, 460 patp2hex, 461 hex2patp, 462 patp2dec, 463 sein, 464 clan, 465 466 patq, 467 patq2hex, 468 hex2patq, 469 patq2dec, 470 471 eqPatq, 472 isValidPat, 473 isValidPatp, 474 isValidPatq 475 }