-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create a heirarchical deterministic multisig addresses
- Loading branch information
1 parent
f541337
commit beec7e3
Showing
3 changed files
with
133 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
module BTC | ||
class KeychainGroup | ||
|
||
attr_reader :extended_keys | ||
|
||
def initialize(extended_keys:) | ||
@extended_keys = extended_keys | ||
end | ||
|
||
def multisig_script(index: nil, signatures_required:) | ||
pubkeys = derived_keys(index).map(&:public_key) | ||
|
||
BTC::Script.multisig( | ||
public_keys: pubkeys, | ||
signatures_required: signatures_required, | ||
) | ||
end | ||
|
||
def standard_address(index:, signatures_required:) | ||
multisig_script(index: index, signatures_required: signatures_required). | ||
p2sh_script.standard_address(network: network).to_s | ||
end | ||
|
||
private | ||
|
||
def network | ||
networks = keychains.map(&:network).uniq | ||
return networks[0] if networks.size == 1 | ||
fail( | ||
ArgumentError, | ||
"extended_keys seem to be a combination of mainnet and testnet" | ||
) | ||
end | ||
|
||
def keychains | ||
@keychains ||= extended_keys.map do |extended_key| | ||
Keychain.new(extended_key: extended_key) | ||
end | ||
end | ||
|
||
def derived_keychains(index_or_path) | ||
keychains.map { |keychain| keychain.derived_keychain(index_or_path) } | ||
end | ||
|
||
def derived_keys(index_or_path) | ||
derived_keychains(index_or_path).map(&:key) | ||
end | ||
|
||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
require_relative 'spec_helper' | ||
|
||
describe BTC::KeychainGroup do | ||
|
||
describe "#multisig_script" do | ||
let(:extended_private_keys) do | ||
[ | ||
"xprvA1XSPm4ktCeuiLSD6vYeY31EPBUjE55rYhtvviSD873YZw4rvL9nz7qf57JF7aYBsbPnwnG5PtERV63H3mHoZyuKiMddWL4ZmwSzHPHSGhX", | ||
"xprvA28GaehL72bWdThY1sW7dFfraTYVnxKKLXHLuUN2RbzhHCnRKLenrZYcgnCCZymErJf1zpBiaVmpEf7WtpXR1vKVKteZBcdJr7rJBLT6xG2", | ||
"xprv9zodTwqJX7DHJQd6Dk7uwGNCuX1aJGoc6zD3dyaJnoFYRsBze3FG6BE2YEQdJUQ7XYPFyfD5ijLNym9BnpgCgcAorhzyfrM2cja1eG3MC3n", | ||
] | ||
end | ||
|
||
context "from extended_keys" do | ||
it "generates a multisig_script based on the derived keys at the given index and number of signatures_required" do | ||
keychain_group = BTC::KeychainGroup.new(extended_keys: extended_private_keys) | ||
|
||
multisig_script_0 = keychain_group. | ||
multisig_script(index: 0, signatures_required: 2) | ||
multisig_address_0 = multisig_script_0. | ||
p2sh_script.standard_address.to_s | ||
expect(multisig_address_0).to eq "3KBxrmV2Ye3BN3NQHva3jFnrbgwfFEDLne" | ||
|
||
multisig_script_19 = keychain_group. | ||
multisig_script(index: 19, signatures_required: 2) | ||
multisig_address_19 = multisig_script_19. | ||
p2sh_script.standard_address.to_s | ||
expect(multisig_address_19).to eq "3QMpum8wDfKNtPCe1AbXy5rSf9mfg3U8zr" | ||
end | ||
end | ||
end | ||
|
||
describe "#standard_address" do | ||
context "given public mainnet extended_keys" do | ||
let(:extended_keys) do | ||
[ | ||
"xpub6EcFX7Ym9LUEPkggNyebw5bCiSyk3ii3iNhC63wiEuUjr9Zj5g5DnJZrhYLTmbSPCDhuH4qZ4PkKTmubR3auDJKjxdKXkxFoh8ELj698Mf8", | ||
"xpub6ELav2rfdseVRrYitFU5ZkYniqhkNYThYgZruku4xQnE823t2sKdnRmrqcgVie96i9XUQvvsJcTH4nRB6xvd2o2KMaG8amhBpcJ8AoqEU6o", | ||
] | ||
end | ||
let(:keychain_group) do | ||
BTC::KeychainGroup.new(extended_keys: extended_keys) | ||
end | ||
|
||
it "returns the multisig address at index and for the signatures_required" do | ||
address_0 = keychain_group. | ||
standard_address(index: 0, signatures_required: 1).to_s | ||
expect(address_0).to eq "33E6C9wpDVfmBhqy3gX8dBNQXsbBEEE5gj" | ||
|
||
address_8 = keychain_group. | ||
standard_address(index: 8, signatures_required: 1).to_s | ||
expect(address_8).to eq "37AW4wGcSVhY4vm4FonuziZmKDbr9ZEjLs" | ||
|
||
address_9 = keychain_group. | ||
standard_address(index: 9, signatures_required: 1).to_s | ||
expect(address_9).to eq "36MY8Vbk48Uc7M5kRS9rzQ6jRRj3g2iTDV" | ||
end | ||
end | ||
|
||
context "given public testnet extended_keys" do | ||
let(:extended_keys) do | ||
[ | ||
"tpubDF7aivmHsi1vjna9uiTHuXsxpa76c69s5pYRqk9cTAMVmPm8B3SQ9xYUB5Lt3wvFbLyY1GcgU1saGHc4bpDK7cyfNLHjxoh68tBRFdeWjh6", | ||
"tpubDEfAYsrZ6Bd9S3Qdx6JvL7E9kCXUDZiFprPLvgMk711VturA5j2GLWK322FgWNUz2SYseJbsnuRUyosbajP22yXruyhHXnxRqcbb5QVvKvh", | ||
] | ||
end | ||
let(:keychain_group) do | ||
BTC::KeychainGroup.new(extended_keys: extended_keys) | ||
end | ||
|
||
it "returns the multisig address at index and for the signatures_required" do | ||
# NOTE: could not find another tool, like https://coinb.in/#newMultiSig | ||
# to generate multisig testnet addresses. The addresses we're testing | ||
# against below were generated from Btcruby | ||
address_0 = keychain_group. | ||
standard_address(index: 0, signatures_required: 1).to_s | ||
expect(address_0).to eq "2MwvRcRjDTQW73iwcQ6p5jt8wSWvtxdp34N" | ||
end | ||
end | ||
end | ||
|
||
end |