Module: Bitcoin::MessageSign
- Defined in:
- lib/bitcoin/message_sign.rb
Defined Under Namespace
Classes: Error
Constant Summary collapse
- FORMAT_LEGACY =
:legacy- FORMAT_SIMPLE =
:simple- FORMAT_FULL =
:full- SIGNATURE_PREFIX_SIMPLE =
Prefix
'smp'- SIGNATURE_PREFIX_FULL =
'ful'- SIGNATURE_PREFIX_POF =
'pof'- BIP322_VERIFY_FLAGS =
BIP-322 Required rules
[ SCRIPT_VERIFY_P2SH, SCRIPT_VERIFY_DERSIG, SCRIPT_VERIFY_STRICTENC, SCRIPT_VERIFY_LOW_S, SCRIPT_VERIFY_NULLFAIL, SCRIPT_VERIFY_MINIMALDATA, SCRIPT_VERIFY_CLEANSTACK, SCRIPT_VERIFY_MINIMALIF, SCRIPT_VERIFY_CONST_SCRIPTCODE, SCRIPT_VERIFY_WITNESS, SCRIPT_VERIFY_TAPROOT, SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS, SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM ].inject(:|)
Class Method Summary collapse
-
.message_hash(message, prefix: Bitcoin.chain_params.message_magic, legacy: true) ⇒ Object
Hashes a message for signing and verification.
-
.sign_message(key, message, prefix: Bitcoin.chain_params.message_magic, format: FORMAT_LEGACY, address: nil) ⇒ String
Sign a message.
- .to_sign_tx(digest, addr) ⇒ Object
- .to_spend_tx(digest, addr) ⇒ Object
-
.verify_message(address, signature, message, prefix: Bitcoin.chain_params.message_magic) ⇒ Boolean
Verify a signed message.
Class Method Details
.message_hash(message, prefix: Bitcoin.chain_params.message_magic, legacy: true) ⇒ Object
Hashes a message for signing and verification.
119 120 121 122 123 124 125 |
# File 'lib/bitcoin/message_sign.rb', line 119 def (, prefix: Bitcoin.chain_params., legacy: true) if legacy Bitcoin.double_sha256(Bitcoin.pack_var_string(prefix) << Bitcoin.pack_var_string()) else Bitcoin.tagged_hash('BIP0322-signed-message', ) end end |
.sign_message(key, message, prefix: Bitcoin.chain_params.message_magic, format: FORMAT_LEGACY, address: nil) ⇒ String
Sign a message.
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/bitcoin/message_sign.rb', line 36 def (key, , prefix: Bitcoin.chain_params., format: FORMAT_LEGACY, address: nil) validate_format!(format) digest = (, prefix: prefix, legacy: format == FORMAT_LEGACY) prefix_marker = '' sig = case format when FORMAT_LEGACY key.sign_compact(digest) else validate_address!(address) addr = Bitcoin::Script.parse_from_addr(address) sig_ver, algo = if addr.p2wpkh? || addr.p2wsh? [:witness_v0, :ecdsa] elsif addr.p2tr? [:taproot, :schnorr] else end tx = to_sign_tx(digest, address) prev_out = Bitcoin::TxOut.new(script_pubkey: addr, value: 0) if addr.p2tr? sighash = tx.sighash_for_input(0, addr, sig_version: :taproot, prevouts: [prev_out], hash_type: Bitcoin::SIGHASH_TYPE[:default]) tweaked = Bitcoin::Taproot.tweak_private_key(key, '') tx.in[0].script_witness.stack << tweaked.sign(sighash, algo: :schnorr) elsif addr.p2wpkh? || addr.p2wsh? sighash = tx.sighash_for_input(0, addr, sig_version: :witness_v0, amount: 0, prevouts: [prev_out]) ecdsa = key.sign(sighash, algo: :ecdsa) + [Bitcoin::SIGHASH_TYPE[:all]].pack('C') tx.in[0].script_witness.stack << ecdsa tx.in[0].script_witness.stack << key.pubkey.htb else raise ArgumentError, "#{address} dose not supported." end prefix_marker = format == FORMAT_SIMPLE ? SIGNATURE_PREFIX_SIMPLE : SIGNATURE_PREFIX_FULL format == FORMAT_SIMPLE ? tx.in[0].script_witness.to_payload : tx.to_payload end prefix_marker + Base64.strict_encode64(sig) end |
.to_sign_tx(digest, addr) ⇒ Object
159 160 161 162 163 164 165 166 167 |
# File 'lib/bitcoin/message_sign.rb', line 159 def to_sign_tx(digest, addr) tx = Bitcoin::Tx.new tx.version = 0 tx.lock_time = 0 prev_out = Bitcoin::OutPoint.from_txid(to_spend_tx(digest, addr).txid, 0) tx.in << Bitcoin::TxIn.new(out_point: prev_out, sequence: 0) tx.out << Bitcoin::TxOut.new(script_pubkey: Bitcoin::Script.new << Bitcoin::Opcodes::OP_RETURN) tx end |
.to_spend_tx(digest, addr) ⇒ Object
146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/bitcoin/message_sign.rb', line 146 def to_spend_tx(digest, addr) validate_address!(addr) = Bitcoin::Script.parse_from_addr(addr) tx = Bitcoin::Tx.new tx.version = 0 tx.lock_time = 0 prev_out = Bitcoin::OutPoint.create_coinbase_outpoint script_sig = Bitcoin::Script.new << Bitcoin::Opcodes::OP_0 << digest tx.in << Bitcoin::TxIn.new(out_point: prev_out, sequence: 0, script_sig: script_sig) tx.out << Bitcoin::TxOut.new(script_pubkey: ) tx end |
.verify_message(address, signature, message, prefix: Bitcoin.chain_params.message_magic) ⇒ Boolean
Verify a signed message.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/bitcoin/message_sign.rb', line 78 def (address, signature, , prefix: Bitcoin.chain_params.) addr_script = Bitcoin::Script.parse_from_addr(address) variant, body = case signature when '' raise ArgumentError, 'signature too short' when /\A#{SIGNATURE_PREFIX_SIMPLE}/ [:simple, signature[3..]] when /\A#{SIGNATURE_PREFIX_FULL}/ [:full, signature[3..]] when /\A#{SIGNATURE_PREFIX_POF}/ [:pof, signature[3..]] else [:fallback, signature] end begin payload = Base64.strict_decode64(body) rescue ArgumentError raise ArgumentError, 'Invalid signature' end case variant when :simple, :fallback return verify_legacy(address, payload, , prefix: prefix) if addr_script.p2pkh? raise ArgumentError, 'simple format not supported for this address' unless addr_script.p2wpkh? || addr_script.p2wsh? || addr_script.p2tr? verify_simple(addr_script, payload, , prefix: prefix) when :full begin tx = Bitcoin::Tx.parse_from_payload(payload) rescue StandardError raise ArgumentError, 'error parsing signature as full variant' end verify_full(addr_script, tx, , prefix: prefix) when :pof raise NotImplementedError, 'proof of funds variant is not supported' else raise ArgumentError, "unknown signature variant: #{variant.inspect}" end end |