@swtc/address-codec
Version:
encode decode base58 SWTC identifiers
211 lines • 7.66 kB
JavaScript
/**
* Codec class
*/
import baseCodec from "base-x";
// Pure JavaScript hash functions in the browser, native hash functions in Node.js
import createHash from "create-hash";
import { SM3, funcGetChain, funcSeqEqual as seqEqual, funcConcatArgs as concatArgs } from "@swtc/common";
export class Codec {
constructor(options) {
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, opts) {
const versions = opts.versions;
return this.encodeVersioned(bytes, versions, opts.expectedLength);
}
encodeVersioned(bytes, versions, expectedLength) {
if (expectedLength && bytes.length !== expectedLength) {
throw new Error("unexpected_payload_length: bytes.length does not match expectedLength");
}
return this.encodeChecked(Buffer.from(concatArgs(versions, bytes)));
}
encodeChecked(buffer) {
const check = this.sha256(this.sha256(buffer)).slice(0, 4);
return this.encodeRaw(Buffer.from(concatArgs(buffer, check)));
}
encodeRaw(bytes) {
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, opts) {
const versions = opts.versions;
const types = opts.versionTypes;
const withoutSum = this.decodeChecked(base58string);
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].length;
const payloadLength = opts.expectedLength || withoutSum.length - versionLengthGuess;
const versionBytes = withoutSum.slice(0, -payloadLength);
const payload = withoutSum.slice(-payloadLength);
for (let i = 0; i < versions.length; i++) {
const version = Array.isArray(versions[i])
? versions[i]
: [versions[i]];
if (seqEqual(versionBytes, version)) {
return {
version,
bytes: payload,
type: types ? types[i] : null
};
}
}
throw new Error("version_invalid: version bytes do not match any of the provided version(s)");
}
decodeChecked(base58string) {
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) {
return this.codec.decode(base58string);
}
verifyCheckSum(bytes) {
const computed = this.sha256(this.sha256(bytes.slice(0, -4))).slice(0, 4);
const checksum = bytes.slice(-4);
return seqEqual(computed, checksum);
}
}
/**
* SWTC codec
*/
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]
export function Factory(chain_or_token = "jingtum") {
// we need two params for codec for now: alphabet and guomi
let config;
const config_default = {
guomi: false,
currency: "SWT",
ACCOUNT_ALPHABET: "jpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65rkm8oFqi1tuvAxyz"
};
if (typeof chain_or_token === "string") {
const active_chain = funcGetChain(chain_or_token);
if (!active_chain) {
// if it is not provided in SWTC_CHAINS
throw new Error("the chain you specified is not registered");
}
else {
config = active_chain;
}
}
else {
config = chain_or_token;
}
const guomi = config.guomi || config_default.guomi;
const token = (config.currency || config_default.currency).toUpperCase();
const alphabet = config.ACCOUNT_ALPHABET || config_default.ACCOUNT_ALPHABET;
const codecOptions = {
sha256: guomi
? (bytes) => new SM3().update(bytes).digest()
: (bytes) => createHash("sha256").update(bytes).digest(),
alphabet
};
const codecWithAlphabet = new Codec(codecOptions);
// entropy is a Buffer of size 16
// type is 'ed25519' or 'secp256k1'
const encodeSeed = (entropy, type = guomi ? "sm2p256v1" : "secp256k1") => {
if (entropy.length !== 16) {
throw new Error("entropy must have length 16");
}
const opts = {
expectedLength: 16,
// for secp256k1, use `FAMILY_SEED`
versions: type === "ed25519" ? ED25519_SEED : [FAMILY_SEED]
};
// prefixes entropy with version bytes
return codecWithAlphabet.encode(entropy, opts);
};
const decodeSeed = (seed, opts = {
versionTypes: guomi ? ["ed25519", "sm2p256v1"] : ["ed25519", "secp256k1"],
versions: [ED25519_SEED, FAMILY_SEED],
expectedLength: 16
}) => {
return codecWithAlphabet.decode(seed, opts);
};
const isValidSeed = (seed) => {
try {
decodeSeed(seed);
}
catch (e) {
return false;
}
return true;
};
const encodeAccountID = (bytes) => {
const opts = { versions: [ACCOUNT_ID], expectedLength: 20 };
return codecWithAlphabet.encode(bytes, opts);
};
const decodeAccountID = (accountId) => {
const opts = { versions: [ACCOUNT_ID], expectedLength: 20 };
return codecWithAlphabet.decode(accountId, opts).bytes;
};
const decodeNodePublic = (base58string) => {
const opts = { versions: [NODE_PUBLIC], expectedLength: 33 };
return codecWithAlphabet.decode(base58string, opts).bytes;
};
const encodeNodePublic = (bytes) => {
const opts = { versions: [NODE_PUBLIC], expectedLength: 33 };
return codecWithAlphabet.encode(bytes, opts);
};
const decodeNodePrivate = (base58string) => {
const opts = { versions: [NODE_PRIVATE], expectedLength: 32 };
return codecWithAlphabet.decode(base58string, opts).bytes;
};
const encodeNodePrivate = (bytes) => {
const opts = { versions: [NODE_PRIVATE], expectedLength: 32 };
return codecWithAlphabet.encode(bytes, opts);
};
const isValidClassicAddress = (address) => {
try {
decodeAccountID(address);
}
catch (e) {
return false;
}
return true;
};
return {
guomi,
token,
codec: codecWithAlphabet,
encode: codecWithAlphabet.encode,
decode: codecWithAlphabet.decode,
encodeSeed,
decodeSeed,
isValidSeed,
encodeAccountID,
decodeAccountID,
encodeNodePublic,
decodeNodePublic,
encodeNodePrivate,
decodeNodePrivate,
isValidClassicAddress,
isValidAddress: isValidClassicAddress,
encodeAddress: encodeAccountID,
decodeAddress: decodeAccountID
};
}
//# sourceMappingURL=address-codec.js.map