Skip to content

Commit

Permalink
Implement Bitcoin::Key#create_ell_pubkey
Browse files Browse the repository at this point in the history
  • Loading branch information
azuchi committed Dec 28, 2023
1 parent 578a3b8 commit 24c5df7
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 2 deletions.
15 changes: 15 additions & 0 deletions lib/bitcoin/bip324.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,20 @@ def xswiftec_inv(x, u, c)
end
ECDSA::Format::IntegerOctetString.encode(result, 32).bth
end

# Given a field element X on the curve, find (u, t) that encode them.
# @param [String] x coordinate with hex format.
# @return [String] ElligatorSwift public key with hex format.
def xelligatorswift(x)
loop do
u = SecureRandom.random_number(1..ECDSA::Group::Secp256k1.order).to_s(16)
c = Random.rand(0..8)
t = xswiftec_inv(x, u, c)
unless t.nil?
return (ECDSA::Format::IntegerOctetString.encode(u.hex, 32) +
ECDSA::Format::IntegerOctetString.encode(t.hex, 32)).bth
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/bitcoin/bip324/ell_swift_pubkey.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def decode
u = key[0...32].bth
t = key[32..-1].bth
x = BIP324.xswiftec(u, t)
Bitcoin::Key.from_xonly_pubkey(x)
Bitcoin::Key.new(pubkey: "03#{x}")
end
end

Expand Down
11 changes: 11 additions & 0 deletions lib/bitcoin/key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,17 @@ def fully_valid_pubkey?(allow_hybrid = false)
valid_pubkey? && secp256k1_module.parse_ec_pubkey?(pubkey, allow_hybrid)
end

# Create an ellswift-encoded public key for this key, with specified entropy.
# @return [Bitcoin::BIP324::EllSwiftPubkey]
# @raise ArgumentError If ent32 does not 32 bytes.
def create_ell_pubkey
if secp256k1_module.is_a?(Bitcoin::Secp256k1::Native)
Bitcoin::BIP324::EllSwiftPubkey.new(secp256k1_module.ellswift_create(priv_key))
else
Bitcoin::BIP324::EllSwiftPubkey.new(Bitcoin::BIP324.xelligatorswift(xonly_pubkey))
end
end

private

def self.compare_big_endian(c1, c2)
Expand Down
16 changes: 15 additions & 1 deletion lib/bitcoin/secp256k1/native.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def load_functions
attach_function(:secp256k1_ecdsa_recover, [:pointer, :pointer, :pointer, :pointer], :int)
attach_function(:secp256k1_ecdsa_recoverable_signature_parse_compact, [:pointer, :pointer, :pointer, :int], :int)
attach_function(:secp256k1_ellswift_decode, [:pointer, :pointer, :pointer], :int)
attach_function(:secp256k1_ellswift_create, [:pointer, :pointer, :pointer, :pointer], :int)
end

def with_context(flags: (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN))
Expand Down Expand Up @@ -227,7 +228,7 @@ def valid_xonly_pubkey?(pub_key)

# Decode ellswift public key.
# @param [String] ell_key ElligatorSwift key with binary format.
# @return [String]
# @return [String] Decoded public key with hex format
def ellswift_decode(ell_key)
with_context do |context|
ell64 = FFI::MemoryPointer.new(:uchar, ell_key.bytesize).put_bytes(0, ell_key)
Expand All @@ -238,6 +239,19 @@ def ellswift_decode(ell_key)
end
end

# Compute an ElligatorSwift public key for a secret key.
# @param [String] priv_key private key with hex format
# @return [String] ElligatorSwift public key with hex format.
def ellswift_create(priv_key)
with_context(flags: SECP256K1_CONTEXT_SIGN) do |context|
ell64 = FFI::MemoryPointer.new(:uchar, 64)
seckey32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, priv_key.htb)
result = secp256k1_ellswift_create(context, ell64, seckey32, nil)
raise ArgumentError, 'Failed to create ElligatorSwift public key.' unless result == 1
ell64.read_string(64).bth
end
end

private

# Calculate full public key(64 bytes) from public key(32 bytes).
Expand Down
22 changes: 22 additions & 0 deletions spec/bitcoin/key_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

describe Bitcoin::Key do

let(:secret1) { '5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj' }
let(:secret2) { '5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3' }
let(:secret3) { 'Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw' }
let(:secret4) { 'L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g' }

describe "initialize" do
it do
expect{described_class.new(priv_key: '6f3acb5b7ac66dacf87910bb0b04bed78284b9b50c0d061705a44447a947ff')}.
Expand Down Expand Up @@ -340,4 +345,21 @@ def test_decompress
expect(uncompressed_key.decompress_pubkey).to eq(uncompressed_key.pubkey)
end
end

describe "#create_ell_pubkey", network: :mainnet do
context "native", use_secp256k1: true do
it { test_ell_pubkey }
end
context "ruby" do
it { test_ell_pubkey }
end
def test_ell_pubkey
[secret1, secret2, secret3, secret4].each do |wif|
key = Bitcoin::Key.from_wif(wif)
ellswift = key.create_ell_pubkey
key = Bitcoin::Key.new(pubkey: key.to_point.to_hex(true)) unless key.compressed?
expect(ellswift.decode.pubkey).to eq(key.pubkey)
end
end
end
end

0 comments on commit 24c5df7

Please sign in to comment.