tiny-secp256k1, ecpair, bitcoinjs-libという3つのパッケージを使ってビットコイン用アドレスらしきものが作成できた。

ブツ

インストール&実行

NAME='Node.js.Ecpair.HelloWorld.20220919113706'
git clone https://github.com/ytyaru/$NAME
cd $NAME
npm install
node index.js

プロジェクト作成

NAME=hello-ecpair
mkdir $NAME
cd $NAME
npm init -y
npm i ecpair tiny-secp256k1 bitcoinjs-lib

ソースコード作成

vim index.js
const tinysecp = require('tiny-secp256k1');
const ecpair = require('ecpair');
const bitcoin = require('bitcoinjs-lib');
console.log('tinysecp:', tinysecp)
console.log('ecpair:', ecpair)
console.log('bitcoin:', bitcoin)

const ECPair = ecpair.ECPairFactory(tinysecp)
console.log('ecpair.ECPairFactory(tinysecp):', ECPair)

const key = ECPair.makeRandom()
console.log('ECPair.makeRandom():', key)

console.log(`Pubkey: ${key.publicKey.toString('hex')}`)
console.log(`Privkey: ${key.privateKey.toString('hex')}`)
//console.log(`Privkey: ${key.getAddress()}`)

const address = bitcoin.payments.p2pkh({ pubkey: key.publicKey });
console.log(`address:`, address)
console.log('p2pk:', bitcoin.payments.p2pk({ pubkey: key.publicKey }).address)
console.log('p2pkh:', address.address) // 1JWnvgtw9dcetEFidnQU8BQA53wpm7KZ4b 等
console.log('p2pkh:', bitcoin.payments.p2pkh({ pubkey: key.publicKey }).address)
console.log('p2wpkh', bitcoin.payments.p2wpkh({ pubkey: key.publicKey }).address)
//console.log('p2sh', bitcoin.payments.p2sh({ pubkey: key.publicKey }).address)
const pubKeys = [
    ECPair.makeRandom().publicKey.toString('hex'),
    ECPair.makeRandom().publicKey.toString('hex'),
    ECPair.makeRandom().publicKey.toString('hex'),
]
//console.log('p2ms', bitcoin.payments.p2ms({ m: 2, pubKeys }).address) // TypeError: Not enough data
//console.log('p2sh', bitcoin.payments.p2sh({ redeem: bitcoin.payments.p2ms({ m: 2, pubKeys }) }).address)
//console.log('p2wsh', bitcoin.payments.p2wsh({ pubkey: key.publicKey }).address)
console.log('p2sh, p2ms, p2wsh はエラー。')

実行

node index.js

結果

tinysecp: {
  __initializeContext: [Function: __initializeContext],
  isPoint: [Function: isPoint],
  isPointCompressed: [Function: isPointCompressed],
  isXOnlyPoint: [Function: isXOnlyPoint],
  isPrivate: [Function: isPrivate],
  pointAdd: [Function: pointAdd],
  pointAddScalar: [Function: pointAddScalar],
  pointCompress: [Function: pointCompress],
  pointFromScalar: [Function: pointFromScalar],
  xOnlyPointFromScalar: [Function: xOnlyPointFromScalar],
  xOnlyPointFromPoint: [Function: xOnlyPointFromPoint],
  pointMultiply: [Function: pointMultiply],
  privateAdd: [Function: privateAdd],
  privateSub: [Function: privateSub],
  privateNegate: [Function: privateNegate],
  xOnlyPointAddTweak: [Function: xOnlyPointAddTweak],
  xOnlyPointAddTweakCheck: [Function: xOnlyPointAddTweakCheck],
  sign: [Function: sign],
  signRecoverable: [Function: signRecoverable],
  signSchnorr: [Function: signSchnorr],
  verify: [Function: verify],
  recover: [Function: recover],
  verifySchnorr: [Function: verifySchnorr]
}
ecpair: { default: [Getter], ECPairFactory: [Getter], networks: [Getter] }
bitcoin: {
  address: {
    fromBase58Check: [Function: fromBase58Check],
    fromBech32: [Function: fromBech32],
    toBase58Check: [Function: toBase58Check],
    toBech32: [Function: toBech32],
    fromOutputScript: [Function: fromOutputScript],
    toOutputScript: [Function: toOutputScript]
  },
  crypto: {
    ripemd160: [Function: ripemd160],
    sha1: [Function: sha1],
    sha256: [Function: sha256],
    hash160: [Function: hash160],
    hash256: [Function: hash256],
    taggedHash: [Function: taggedHash]
  },
  networks: {
    bitcoin: {
      messagePrefix: '\x18Bitcoin Signed Message:\n',
      bech32: 'bc',
      bip32: [Object],
      pubKeyHash: 0,
      scriptHash: 5,
      wif: 128
    },
    regtest: {
      messagePrefix: '\x18Bitcoin Signed Message:\n',
      bech32: 'bcrt',
      bip32: [Object],
      pubKeyHash: 111,
      scriptHash: 196,
      wif: 239
    },
    testnet: {
      messagePrefix: '\x18Bitcoin Signed Message:\n',
      bech32: 'tb',
      bip32: [Object],
      pubKeyHash: 111,
      scriptHash: 196,
      wif: 239
    }
  },
  payments: {
    embed: [Getter],
    p2ms: [Getter],
    p2pk: [Getter],
    p2pkh: [Getter],
    p2sh: [Getter],
    p2wpkh: [Getter],
    p2wsh: [Getter]
  },
  script: {
    OPS: [Getter],
    isPushOnly: [Function: isPushOnly],
    compile: [Function: compile],
    decompile: [Function: decompile],
    toASM: [Function: toASM],
    fromASM: [Function: fromASM],
    toStack: [Function: toStack],
    isCanonicalPubKey: [Function: isCanonicalPubKey],
    isDefinedHashType: [Function: isDefinedHashType],
    isCanonicalScriptSignature: [Function: isCanonicalScriptSignature],
    number: { decode: [Function: decode], encode: [Function: encode] },
    signature: { decode: [Function: decode], encode: [Function: encode] }
  },
  Block: [Getter],
  Psbt: [Getter],
  opcodes: [Getter],
  Transaction: [Getter]
}
ecpair.ECPairFactory(tinysecp): {
  isPoint: [Function: isPoint],
  fromPrivateKey: [Function: fromPrivateKey],
  fromPublicKey: [Function: fromPublicKey],
  fromWIF: [Function: fromWIF],
  makeRandom: [Function: makeRandom]
}
ECPair.makeRandom(): ECPair {
  __D: <Buffer 3f b2 25 d4 ff 04 31 85 d5 2b 8b 50 bc a6 9a db fb fd 9f 52 77 9a 31 4c 42 6f dc fd e0 a4 ef c2>,
  __Q: undefined,
  compressed: true,
  network: {
    messagePrefix: '\x18Bitcoin Signed Message:\n',
    bech32: 'bc',
    bip32: { public: 76067358, private: 76066276 },
    pubKeyHash: 0,
    scriptHash: 5,
    wif: 128
  },
  lowR: false
}
Pubkey: 02273ec3dd96e7c15be02409664e99aa8b4f0a0187f6ab12bead6733716d1893bc
Privkey: 3fb225d4ff043185d52b8b50bca69adbfbfd9f52779a314c426fdcfde0a4efc2
address: {
  name: 'p2pkh',
  network: {
    messagePrefix: '\x18Bitcoin Signed Message:\n',
    bech32: 'bc',
    bip32: { public: 76067358, private: 76066276 },
    pubKeyHash: 0,
    scriptHash: 5,
    wif: 128
  },
  address: [Getter/Setter],
  hash: [Getter/Setter],
  output: [Getter/Setter],
  pubkey: <Buffer 02 27 3e c3 dd 96 e7 c1 5b e0 24 09 66 4e 99 aa 8b 4f 0a 01 87 f6 ab 12 be ad 67 33 71 6d 18 93 bc>,
  signature: [Getter/Setter],
  input: [Getter/Setter],
  witness: [Getter/Setter]
}
p2pk: undefined
p2pkh: 1BaZjMXFnbuRp14krTBSQEGK3yciVGyZtX
p2pkh: 1BaZjMXFnbuRp14krTBSQEGK3yciVGyZtX
p2wpkh bc1qws9yuurmtqnempe6nj0ng0umz6q5pktm0dgsnm
p2sh, p2ms, p2wsh はエラー。

解析

最後の出力部分がアドレス。

アドレスを作成するにはbitcoinjs-libの以下メソッドを使う。

  payments: {
    embed: [Getter],
    p2ms: [Getter],
    p2pk: [Getter],
    p2pkh: [Getter],
    p2sh: [Getter],
    p2wpkh: [Getter],
    p2wsh: [Getter]
  },

このうち有効であり一般的らしいp2pkhに注目する。これはMasteringBitcoin PDF 129ページにある「標準的なトランザクション」のひとつ。

const ECPair = ecpair.ECPairFactory(tinysecp)
const key = ECPair.makeRandom()
console.log('p2pkh:', bitcoin.payments.p2pkh({ pubkey: key.publicKey }).address)

P2なんちゃらをPDFで読んだりさらっとググった結果、次のようなものだった。

名前 概要
P2PKH 一般的。locking scriptを含む。
P2PK P2PKHよりシンプル。公開鍵をlocking scriptにする。
P2MS マルチシグネチャ。少なくとも2個以上の署名が必要。
P2SH 新しい。scriptを簡単化した。redeem script
P2WPKH SegWit版P2PKH
P2WSH SegWit版P2SH

ぶっちゃけ何もわからない。コードを書いても未だに何がどうなっているのかわからない。ただ動作するメソッドを呼び出しただけ。たぶんbitcoin.payments.p2pk()の出力結果をみるに、inputoutputがあるから、そこに送金元・送金先となるトランザクションデータを設定して、どこかへ送信し承認されたら取引完了になると思われる。

まあとにかく、tiny-secp256k1, ecpair, bitcoinjs-libという3つのパッケージを使ってアドレスらしきものが作成できた。

今回は秘密鍵も晒してしまっているので、このアドレスを使ったらダメだと思う。

これでウォレット作れたりするのかな? アドレス作って送受金してみたいけど、できるかどうかもわからない。

その前に、モナコインのアドレスってどうやって作るの? ログにあったp2wpkh bc1qws9yuurmtqnempe6nj0ng0umz6q5pktm0dgsnmは先頭bcだからビットコイン用アドレスなのかな? MpurseだとMからはじまるアドレスが作成できるはずだけど。それって作れるのかな?

もっと調査が必要そう。