Tripcode generation

Deriving a user id ("tripcode") from a user-supplied password.

The algorithm that is used to derive a public key looks as follows:

import CryptoJS from "crypto-js"; // https://github.com/brix/crypto-js
import scrypt from "scrypt-js";   // https://github.com/ricmoo/scrypt-js
import { ethers } from "ethers";  // https://github.com/ethers-io/ethers.js/

// Derive a 32-byte key to be used as private key material.
const derivePrivateKey = async (password: string) => {
  const hashHex = CryptoJS.SHA256(password).toString();
  const hash = hexToBytes(hashHex);
  const salt = stringToBytes("somachat");
  const N = 4 * 1024;
  const r = 8;
  const p = 1;
  const keyLen = 32;
  const key = bytesToHex(await scrypt.scrypt(hash, salt, N, r, p, keyLen));
  return key;
};

// Derive an uncompressed (65-byte) public secp256k1 key.
const derivePublicKey = async (password: string) => {
  const key = await derivePrivateKey(password);
  const wallet = new ethers.Wallet(key);
  const { publicKey } = wallet;
  return publicKey;
}

The resulting publicKey is the 65 byte long public part of a secp256k1 keypair. The public key is sent in this format to the server, where it is compressed to its 33-byte form (which is possible due to secp256k1's symmetry around the x-axis – more here) and encodes the result in base58.

package main

import (
	"encoding/hex"

	"github.com/btcsuite/btcd/btcutil/base58"
	"github.com/ethereum/go-ethereum/crypto"
)

// Convert a 65-byte hex representation prefixed by 0x into
// a base-58 representation of the 33-byte compressed public key.
func toUserId(pubKeyHex string) (string, error) {
	b, err := hex.DecodeString(pubKeyHex[2:])
	if err != nil {
		return "", err
	}
	pubkey, err := crypto.UnmarshalPubkey(b)
	if err != nil {
		return "", err
	}
	keyBytes := crypto.CompressPubkey(pubkey)
	key := base58.Encode(keyBytes)
	return key, nil
}

The original 65-byte public key can be extracted from the base-58 representation.

The base-58 representation of the public key is either 44 or 45 bytes long. For display purposes, the client shows only the first 7 characters prefixed by an exclamation mark, but the full representation can be accessed by clicking on a user or a user's message and copying the "user id".

Last updated