Base58とは何か — Base64/Base32との違いと、ビットコインが使う理由をJavaScriptで確かめる

Base64 や Base32 は日常的に使うのに、Base58 だけは「ビットコインのアドレスで見かける謎の文字列」で止まっている人は多い。名前は 64・32 と地続きに見えるのに、実は中身の作り方がまったく違う。この記事では、なぜ 58 なのか、何がうれしいのか、そして Base64/Base32 と決定的に異なる「大きな整数として変換する」という仕組みを、実際に動く JavaScript で確かめる。

なお、この記事の変換はブラウザだけで試せるツールも用意した(Base58 エンコード / デコード)。手を動かしながら読むと分かりやすい。

なぜ「58」なのか

Base58 の文字集合は次の58文字だ。

1
123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

数字(0-9)とアルファベット(A-Z, a-z)を全部足すと 62 文字になる。そこから見間違えやすい4文字を抜くと 58 になる。

  • 0(ゼロ)と O(大文字オー)
  • I(大文字アイ)と l(小文字エル)

さらに Base64 が使う記号 + / = も入っていない。この2点が Base58 の狙いそのものだ。人がコピーしたり読み上げたり手で打ち直したりする識別子で、誤読と、URL やダブルクリック選択での破損を減らす。ビットコインのアドレスや IPFS の CID(CIDv0)が Base58 を使うのはこのためだ。

Base64/Base32 との決定的な違い:ビット単位ではなく整数として変換する

ここが一番の勘所だ。Base64 と Base32 は、入力バイト列を固定ビット幅で区切って1文字に対応させる。

  • Base64: 6ビットずつ区切る(2の6乗 = 64)
  • Base32: 5ビットずつ区切る(2の5乗 = 32)

どちらも「1文字あたりのビット数」がきれいな整数なので、バイト列を先頭から順に切り出していけば変換できる。

ところが 58 は 2 のべき乗ではない。log2(58) は約 5.858 で、割り切れない。だから Base58 はビット単位で区切れない。代わりに入力バイト列全体をひとつの巨大な整数とみなし、それを58で割った余りを繰り返し取り出す(=58進数に変換する)。方式としては、16進数を10進数に変換するのと同じ「基数変換」だ。この違いがそのままコードに出る。

実装:バイト列 → Base58

多倍長整数を使わずに、桁の配列で基数変換する実装が定番だ。次のコードはそのまま動く。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

function bytesToBase58(bytes) {
// 先頭のゼロバイトの数を数える(後で "1" に対応させる)
let zeros = 0;
while (zeros < bytes.length && bytes[zeros] === 0) zeros++;

// 256進数の桁列(bytes)を58進数の桁列(digits)へ基数変換する
const digits = [0];
for (let i = zeros; i < bytes.length; i++) {
let carry = bytes[i];
for (let j = 0; j < digits.length; j++) {
carry += digits[j] << 8; // 既存の桁を256倍して足し込む
digits[j] = carry % 58;
carry = (carry / 58) | 0;
}
while (carry > 0) {
digits.push(carry % 58);
carry = (carry / 58) | 0;
}
}

// 先頭ゼロは "1"(アルファベットの0番目)に対応させる
let out = "";
for (let k = 0; k < zeros; k++) out += ALPHABET[0];
for (let m = digits.length - 1; m >= 0; m--) out += ALPHABET[digits[m]];
return out;
}

// 文字列を UTF-8 バイト列にしてから変換する
function encodeText(text) {
return bytesToBase58(Array.from(new TextEncoder().encode(text)));
}

console.log(encodeText("Hello World!")); // => 2NEpo7TZRRrLZSi2U

"Hello World!"2NEpo7TZRRrLZSi2U になれば正しい。これは Base58 の広く知られたテストベクタと一致する(自分の環境で必ず確認しておくとよい)。

先頭のゼロバイトを “1” にする理由

コードの中で先頭のゼロバイトを別扱いしているのがポイントだ。データを「ひとつの整数」とみなすと、先頭に並んだ 0x00 は数値としては消えてしまう(10進数で 0077 になるのと同じ)。しかしビットコインのアドレスでは、この先頭バイトがバージョン情報を表していて捨ててはいけない。そこで Base58 は「先頭の 0x00 1個につき、出力の先頭に文字 1 を1個付ける」と決めている。だから多くのビットコインアドレスが 1 で始まる。

実装:Base58 → バイト列(逆変換)

戻すときは58進数から256進数への基数変換になる。往復できて初めて実装は正しい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function base58ToBytes(str) {
const s = str.replace(/\s+/g, "");
if (s.length === 0) return [];
let zeros = 0;
while (zeros < s.length && s[zeros] === ALPHABET[0]) zeros++;

const bytes = [0];
for (let i = zeros; i < s.length; i++) {
const idx = ALPHABET.indexOf(s[i]);
if (idx < 0) throw new Error("Base58ではない文字: " + s[i]);
let carry = idx;
for (let j = 0; j < bytes.length; j++) {
carry += bytes[j] * 58;
bytes[j] = carry & 0xff;
carry >>= 8;
}
while (carry > 0) {
bytes.push(carry & 0xff);
carry >>= 8;
}
}
const out = [];
for (let k = 0; k < zeros; k++) out.push(0);
for (let m = bytes.length - 1; m >= 0; m--) out.push(bytes[m]);
return out;
}

function decodeText(str) {
return new TextDecoder().decode(new Uint8Array(base58ToBytes(str)));
}

console.log(decodeText("2NEpo7TZRRrLZSi2U")); // => Hello World!

Base58Check との違いに注意

実際のビットコインアドレスは、ここで作った素の Base58 ではなく Base58Check という拡張を使う。これはデータの末尾に4バイトのチェックサム(SHA-256 を2回かけた先頭4バイト)を付けてから Base58 する方式で、1文字でも打ち間違えると復号時に検出できる。この記事の実装は「素の Base58」なので、アドレスを直接生成する用途にはチェックサム処理を足す必要がある。混同しないようにしたい。

まとめ

  • Base58 は 62 文字から紛らわしい 0 O I l と記号を抜いた58文字を使う。狙いは誤読と破損の防止
  • Base64/Base32 がビット単位で区切るのに対し、Base58 は全体を大きな整数として58進数に変換する。58が2のべき乗でないためだ。
  • 先頭のゼロバイトは文字 1 に対応させて保持する。多くのアドレスが 1 で始まるのはこのため。
  • アドレス用途では素の Base58 ではなくチェックサム付きの Base58Check を使う。

仕組みを一度手で確かめておくと、アドレスや CID の文字列を見たときの解像度が上がる。ブラウザで試すなら、この実装をそのまま組み込んだ Base58 エンコード / デコードツール が使える。数字とアルファベットの 0 O I l が本当に出てこないことも、実際に変換して確かめてみてほしい。


Base58とは何か — Base64/Base32との違いと、ビットコインが使う理由をJavaScriptで確かめる
https://blog.hashito.biz/2026/07/29/base58-vs-base64-base32-bitcoin-javascript/
著者
hashito
作成日
2026年7月29日
著作権