mirror of
				https://github.com/Xahau/xahau.js.git
				synced 2025-11-04 04:55:48 +00:00 
			
		
		
		
	Remove src/ripple-address-codec and use npm published (#58)
* Remove src/ripple-address-codec and use npm published * Remove 4 failing old dist/ripple-address-codec tests * Update yarn.lock with npm ripple-address-codec package
This commit is contained in:
		@@ -11,12 +11,11 @@
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "babel-runtime": "^5.8.20",
 | 
			
		||||
    "base-x": "3.0.4",
 | 
			
		||||
    "bn.js": "^3.1.1",
 | 
			
		||||
    "brorand": "^1.0.5",
 | 
			
		||||
    "create-hash": "1.2.0",
 | 
			
		||||
    "elliptic": "^6.4.0",
 | 
			
		||||
    "hash.js": "^1.0.3"
 | 
			
		||||
    "hash.js": "^1.0.3",
 | 
			
		||||
    "ripple-address-codec": "^4.0.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/node": "^13.1.5",
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ import * as brorand from 'brorand'
 | 
			
		||||
import * as hashjs from 'hash.js'
 | 
			
		||||
import * as elliptic from 'elliptic'
 | 
			
		||||
 | 
			
		||||
import * as addressCodec from './ripple-address-codec'
 | 
			
		||||
import * as addressCodec from 'ripple-address-codec'
 | 
			
		||||
import {derivePrivateKey, accountPublicFromPublicGenerator} from './secp256k1'
 | 
			
		||||
import * as utils from './utils'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,220 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Codec class
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const baseCodec = require('base-x')
 | 
			
		||||
const {seqEqual, concatArgs} = require('./utils')
 | 
			
		||||
 | 
			
		||||
class Codec {
 | 
			
		||||
  sha256: (bytes: Uint8Array) => Buffer
 | 
			
		||||
  alphabet: string
 | 
			
		||||
  codec: any
 | 
			
		||||
  base: number
 | 
			
		||||
 | 
			
		||||
  constructor(options: {
 | 
			
		||||
    sha256: (bytes: Uint8Array) => Buffer,
 | 
			
		||||
    alphabet: string
 | 
			
		||||
  }) {
 | 
			
		||||
    this.sha256 = options.sha256
 | 
			
		||||
    this.alphabet = options.alphabet
 | 
			
		||||
    this.codec = baseCodec(this.alphabet)
 | 
			
		||||
    this.base = this.alphabet.length
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Encoder.
 | 
			
		||||
   *
 | 
			
		||||
   * @param bytes Buffer of data to encode.
 | 
			
		||||
   * @param opts Options object including the version bytes and the expected length of the data to encode.
 | 
			
		||||
   */
 | 
			
		||||
  encode(bytes: number[], opts: {
 | 
			
		||||
    versions: number[],
 | 
			
		||||
    expectedLength: number
 | 
			
		||||
  }) {
 | 
			
		||||
    const versions = opts.versions
 | 
			
		||||
    return this.encodeVersioned(bytes, versions, opts.expectedLength)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  encodeVersioned(bytes: number[], versions: number[], expectedLength: number) {
 | 
			
		||||
    if (expectedLength && bytes.length !== expectedLength) {
 | 
			
		||||
      throw new Error('unexpected_payload_length: bytes.length does not match expectedLength')
 | 
			
		||||
    }
 | 
			
		||||
    return this.encodeChecked(concatArgs(versions, bytes))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  encodeChecked(buffer: Buffer) {
 | 
			
		||||
    const check = this.sha256(this.sha256(buffer)).slice(0, 4)
 | 
			
		||||
    return this.encodeRaw(concatArgs(buffer, check))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  encodeRaw(bytes: Buffer) {
 | 
			
		||||
    return this.codec.encode(bytes)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Decoder.
 | 
			
		||||
   *
 | 
			
		||||
   * @param base58string Base58Check-encoded string to decode.
 | 
			
		||||
   * @param opts Options object including the version byte(s) and the expected length of the data after decoding.
 | 
			
		||||
   */
 | 
			
		||||
  decode(base58string: string, opts: {
 | 
			
		||||
    versions?: (number | number[])[],
 | 
			
		||||
    expectedLength?: number,
 | 
			
		||||
    versionTypes?: string[]
 | 
			
		||||
  } = {}) {
 | 
			
		||||
    const versions = Array.isArray(opts.versions) ? opts.versions : [opts.versions]
 | 
			
		||||
    const types = opts.versionTypes
 | 
			
		||||
    if (versions) {
 | 
			
		||||
      const withoutSum = this.decodeChecked(base58string)
 | 
			
		||||
      const ret: {
 | 
			
		||||
        version: number[] | null,
 | 
			
		||||
        bytes: Buffer | null,
 | 
			
		||||
        type: string | null // for seeds, 'ed25519' | 'secp256k1'
 | 
			
		||||
      } = {
 | 
			
		||||
        version: null,
 | 
			
		||||
        bytes: null,
 | 
			
		||||
        type: null
 | 
			
		||||
      }
 | 
			
		||||
      if (versions.length > 1 && !opts.expectedLength) {
 | 
			
		||||
        throw new Error('expectedLength is required because there are >= 2 possible versions')
 | 
			
		||||
      }
 | 
			
		||||
      const versionLengthGuess = typeof versions[0] === 'number' ? 1 : (versions[0] as number[]).length
 | 
			
		||||
      const payloadLength = opts.expectedLength || withoutSum.length - versionLengthGuess
 | 
			
		||||
      const versionBytes = withoutSum.slice(0, -payloadLength)
 | 
			
		||||
      const payload = withoutSum.slice(-payloadLength)
 | 
			
		||||
 | 
			
		||||
      let foundVersion = false
 | 
			
		||||
      for (let i = 0; i < versions.length; i++) {
 | 
			
		||||
        const version: number[] = Array.isArray(versions[i]) ? versions[i] as number[] : [versions[i] as number]
 | 
			
		||||
        if (seqEqual(versionBytes, version)) {
 | 
			
		||||
          ret.version = version
 | 
			
		||||
          ret.bytes = payload
 | 
			
		||||
          if (types) {
 | 
			
		||||
            ret.type = types[i]
 | 
			
		||||
          }
 | 
			
		||||
          foundVersion = true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!foundVersion) {
 | 
			
		||||
        throw new Error('version_invalid: version bytes do not match any of the provided version(s)')
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (opts.expectedLength && ret.bytes.length !== opts.expectedLength) {
 | 
			
		||||
        throw new Error('unexpected_payload_length: payload length does not match expectedLength')
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return ret
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Assume that base58string is 'checked'
 | 
			
		||||
    return this.decodeChecked(base58string)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  decodeChecked(base58string: string) {
 | 
			
		||||
    const buffer = this.decodeRaw(base58string)
 | 
			
		||||
    if (buffer.length < 5) {
 | 
			
		||||
      throw new Error('invalid_input_size: decoded data must have length >= 5')
 | 
			
		||||
    }
 | 
			
		||||
    if (!this.verifyCheckSum(buffer)) {
 | 
			
		||||
      throw new Error('checksum_invalid')
 | 
			
		||||
    }
 | 
			
		||||
    return buffer.slice(0, -4)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  decodeRaw(base58string: string) {
 | 
			
		||||
    return this.codec.decode(base58string)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  verifyCheckSum(bytes: Buffer) {
 | 
			
		||||
    const computed = this.sha256(this.sha256(bytes.slice(0, -4))).slice(0, 4)
 | 
			
		||||
    const checksum = bytes.slice(-4)
 | 
			
		||||
    return seqEqual(computed, checksum)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * XRP codec
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const createHash = require('create-hash')
 | 
			
		||||
 | 
			
		||||
const NODE_PUBLIC = 28
 | 
			
		||||
// const NODE_PRIVATE = 32
 | 
			
		||||
const ACCOUNT_ID = 0
 | 
			
		||||
const FAMILY_SEED = 0x21 // 33
 | 
			
		||||
const ED25519_SEED = [0x01, 0xE1, 0x4B] // [1, 225, 75]
 | 
			
		||||
 | 
			
		||||
const codecOptions = {
 | 
			
		||||
  sha256: function(bytes: Uint8Array) {
 | 
			
		||||
    return createHash('sha256').update(Buffer.from(bytes)).digest()
 | 
			
		||||
  },
 | 
			
		||||
  alphabet: 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const codecWithXrpAlphabet = new Codec(codecOptions)
 | 
			
		||||
 | 
			
		||||
// entropy is an array (or Buffer?) of size 16
 | 
			
		||||
// type is 'ed25519' or 'secp256k1'
 | 
			
		||||
export function encodeSeed(entropy: number[], type: 'ed25519' | 'secp256k1'): string {
 | 
			
		||||
  if (entropy.length !== 16) {
 | 
			
		||||
    throw new Error('entropy must have length 16')
 | 
			
		||||
  }
 | 
			
		||||
  if (type !== 'ed25519' && type !== 'secp256k1') {
 | 
			
		||||
    throw new Error('type must be ed25519 or secp256k1')
 | 
			
		||||
  }
 | 
			
		||||
  const opts = {
 | 
			
		||||
    expectedLength: 16,
 | 
			
		||||
 | 
			
		||||
    // for secp256k1, use `FAMILY_SEED`
 | 
			
		||||
    versions: type === 'ed25519' ? ED25519_SEED : [FAMILY_SEED]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // prefixes entropy with version bytes
 | 
			
		||||
  return codecWithXrpAlphabet.encode(entropy, opts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function decodeSeed(seed: string, opts: {
 | 
			
		||||
  versionTypes?: string[],
 | 
			
		||||
  versions?: (number | number[])[]
 | 
			
		||||
  expectedLength?: number
 | 
			
		||||
} = {}) {
 | 
			
		||||
  if (!opts.versionTypes || !opts.versions) {
 | 
			
		||||
    opts.versionTypes = ['ed25519', 'secp256k1']
 | 
			
		||||
    opts.versions = [ED25519_SEED, FAMILY_SEED]
 | 
			
		||||
  }
 | 
			
		||||
  if (!opts.expectedLength) {
 | 
			
		||||
    opts.expectedLength = 16
 | 
			
		||||
  }
 | 
			
		||||
  return codecWithXrpAlphabet.decode(seed, opts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function encodeAccountID(bytes: number[]): string {
 | 
			
		||||
  const opts = {versions: [ACCOUNT_ID], expectedLength: 20}
 | 
			
		||||
  return codecWithXrpAlphabet.encode(bytes, opts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function decodeAccountID(accountId: string): Buffer {
 | 
			
		||||
  const opts = {versions: [ACCOUNT_ID], expectedLength: 20}
 | 
			
		||||
  return codecWithXrpAlphabet.decode(accountId, opts).bytes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function decodeNodePublic(base58string: string): Buffer {
 | 
			
		||||
  const opts = {versions: [NODE_PUBLIC], expectedLength: 33}
 | 
			
		||||
  return codecWithXrpAlphabet.decode(base58string, opts).bytes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function encodeNodePublic(bytes: number[]): string {
 | 
			
		||||
  const opts = {versions: [NODE_PUBLIC], expectedLength: 33}
 | 
			
		||||
  return codecWithXrpAlphabet.encode(bytes, opts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Address === AccountID
 | 
			
		||||
export function isValidAddress(address: string): boolean {
 | 
			
		||||
  try {
 | 
			
		||||
    this.decodeAccountID(address)
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
  return true
 | 
			
		||||
}
 | 
			
		||||
@@ -1,59 +0,0 @@
 | 
			
		||||
'use strict'
 | 
			
		||||
 | 
			
		||||
function seqEqual(arr1, arr2) {
 | 
			
		||||
  if (arr1.length !== arr2.length) {
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (let i = 0; i < arr1.length; i++) {
 | 
			
		||||
    if (arr1[i] !== arr2[i]) {
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isSequence(val) {
 | 
			
		||||
  return val.length !== undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* Concatenates all `arguments` into a single array. Each argument can be either
 | 
			
		||||
* a single element or a sequence, which has a `length` property and supports
 | 
			
		||||
* element retrieval via sequence[ix].
 | 
			
		||||
*
 | 
			
		||||
* > concatArgs(1, [2, 3], Buffer.from([4,5]), new Uint8Array([6, 7]));
 | 
			
		||||
*  [1,2,3,4,5,6,7]
 | 
			
		||||
*
 | 
			
		||||
* @return {Array} - concatenated arguments
 | 
			
		||||
*/
 | 
			
		||||
function concatArgs(): number[] {
 | 
			
		||||
  const ret = []
 | 
			
		||||
  const _len = arguments.length
 | 
			
		||||
  const args = Array(_len)
 | 
			
		||||
 | 
			
		||||
  for (let _key = 0; _key < _len; _key++) {
 | 
			
		||||
    args[_key] = arguments[_key]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  args.forEach(function (arg) {
 | 
			
		||||
    if (isSequence(arg)) {
 | 
			
		||||
      for (let j = 0; j < arg.length; j++) {
 | 
			
		||||
        ret.push(arg[j])
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      ret.push(arg)
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isSet(o) {
 | 
			
		||||
  return o !== null && o !== undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  seqEqual: seqEqual,
 | 
			
		||||
  concatArgs: concatArgs,
 | 
			
		||||
  isSet: isSet
 | 
			
		||||
}
 | 
			
		||||
@@ -14,10 +14,10 @@ function hexToBytes(a) {
 | 
			
		||||
  return (new BN(a, 16)).toArray(null, a.length / 2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function computePublicKeyHash(publicKeyBytes: Buffer): number[] {
 | 
			
		||||
function computePublicKeyHash(publicKeyBytes: Buffer): Buffer {
 | 
			
		||||
  const hash256 = hashjs.sha256().update(publicKeyBytes).digest()
 | 
			
		||||
  const hash160 = hashjs.ripemd160().update(hash256).digest()
 | 
			
		||||
  return hash160
 | 
			
		||||
  return Buffer.from(hash160)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function seedFromPhrase(phrase) {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
'use strict'
 | 
			
		||||
 | 
			
		||||
const assert = require('assert')
 | 
			
		||||
const api = require('../dist/ripple-address-codec')
 | 
			
		||||
const api = require('ripple-address-codec')
 | 
			
		||||
 | 
			
		||||
function toHex(bytes) {
 | 
			
		||||
  return Buffer.from(bytes).toString('hex').toUpperCase()
 | 
			
		||||
@@ -50,18 +50,4 @@ describe('ripple-address-codec', function() {
 | 
			
		||||
    assert.equal(decoded.type, 'ed25519')
 | 
			
		||||
    assert.equal(api.encodeSeed(decoded.bytes, decoded.type), edSeed)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('isValidAddress - secp256k1 address valid', function() {
 | 
			
		||||
    assert(api.isValidAddress('rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1'))
 | 
			
		||||
  })
 | 
			
		||||
  it('isValidAddress - ed25519 address valid', function() {
 | 
			
		||||
    assert(api.isValidAddress('rLUEXYuLiQptky37CqLcm9USQpPiz5rkpD'))
 | 
			
		||||
  })
 | 
			
		||||
  it('isValidAddress - invalid', function() {
 | 
			
		||||
    assert(!api.isValidAddress('rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw2'))
 | 
			
		||||
  })
 | 
			
		||||
  it('isValidAddress - empty', function() {
 | 
			
		||||
    assert(!api.isValidAddress(''))
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
'use strict'
 | 
			
		||||
 | 
			
		||||
const assert = require('assert')
 | 
			
		||||
const api = require('../dist/ripple-address-codec')
 | 
			
		||||
const api = require('ripple-address-codec')
 | 
			
		||||
 | 
			
		||||
function toHex(bytes) {
 | 
			
		||||
  return Buffer.from(bytes).toString('hex').toUpperCase()
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,13 @@
 | 
			
		||||
    esutils "^2.0.2"
 | 
			
		||||
    js-tokens "^4.0.0"
 | 
			
		||||
 | 
			
		||||
"@types/base-x@^3.0.0":
 | 
			
		||||
  version "3.0.6"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/base-x/-/base-x-3.0.6.tgz#23fcfaa1cfe14d47524985375044cbb84465d9ca"
 | 
			
		||||
  integrity sha512-sWf3jyM1xa/k7kP+CWtF72cgY4PjPEIWg+zaaFMX8oND9Qy+MRFnol2pl4Rb6K+P6LDrVkUoXdI3C1ekXQonYA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    base-x "*"
 | 
			
		||||
 | 
			
		||||
"@types/node@^13.1.5":
 | 
			
		||||
  version "13.1.5"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.5.tgz#4d5efc52a1d3e45d13e5ec9f911cbc5b089ccaec"
 | 
			
		||||
@@ -463,6 +470,13 @@ balanced-match@^1.0.0:
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
 | 
			
		||||
  integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
 | 
			
		||||
 | 
			
		||||
base-x@*:
 | 
			
		||||
  version "3.0.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.7.tgz#1c5a7fafe8f66b4114063e8da102799d4e7c408f"
 | 
			
		||||
  integrity sha512-zAKJGuQPihXW22fkrfOclUUZXM2g92z5GzlSMHxhO6r6Qj+Nm0ccaGNBzDZojzwOMkpjAv4J0fOv1U4go+a4iw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    safe-buffer "^5.0.1"
 | 
			
		||||
 | 
			
		||||
base-x@3.0.4:
 | 
			
		||||
  version "3.0.4"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.4.tgz#94c1788736da065edb1d68808869e357c977fa77"
 | 
			
		||||
@@ -781,7 +795,7 @@ core-util-is@~1.0.0:
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
 | 
			
		||||
  integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
 | 
			
		||||
 | 
			
		||||
create-hash@1.2.0:
 | 
			
		||||
create-hash@^1.1.2:
 | 
			
		||||
  version "1.2.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
 | 
			
		||||
  integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==
 | 
			
		||||
@@ -2772,6 +2786,15 @@ ripemd160@^2.0.1:
 | 
			
		||||
    hash-base "^3.0.0"
 | 
			
		||||
    inherits "^2.0.1"
 | 
			
		||||
 | 
			
		||||
ripple-address-codec@^4.0.0:
 | 
			
		||||
  version "4.0.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-4.0.0.tgz#c20f39eea38def43d2379462e47bff4adabece30"
 | 
			
		||||
  integrity sha512-PsKl9aytg6fZG2F4RtfPT0c1gj42suAQY9VvJVGz+DfQTdXQaTT9V/StVhaJ6jhVpl7oCd981BB9p2Kq+Kyrng==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@types/base-x" "^3.0.0"
 | 
			
		||||
    base-x "3.0.4"
 | 
			
		||||
    create-hash "^1.1.2"
 | 
			
		||||
 | 
			
		||||
run-async@^2.2.0:
 | 
			
		||||
  version "2.3.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user