Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add initial p2tr output script support #1726

Closed

Conversation

sangaman
Copy link
Contributor

This introduces support for creating p2tr output scripts and addresses from one or more pubkeys and/or one or more scripts according to BIPs 340 & 341.

Multiple pubkeys are aggregated according to the procedure defined by MuSig whereby all keys are concatenated and hashed, with the resulting hash multiplied against each pubkey to tweak it. These tweaked pub keys are added together to produce the internal pubkey. If only one pubkey is provided, then the internal pubkey is simply set to that. And if no pubkeys are provided, a key with no known secret key is used instead.

Any scripts provided are arranged into a Huffman tree according to their weighted probability and then hashed to form a merkle tree. The root hash of this tree is then added to the internal pubkey to tweak it. This tweaked key is finally used for creating the output script which is then encoded using Bech32m.

Test cases in this commit include examples from the Bitcoin Optech taproot workshop that derive a Bech32m address from scripts and an internal key.

Follow-ups to this commit should include expanding the coverage and number of test cases as well as possibly restructuring existing code or moving logic to other packages/modules. We should also consider support for taproot output descriptors, which are currently under review and developmnet in Bitcoin Core.

Ticket: BG-35571

This introduces support for creating p2tr output scripts and addresses
from one or more pubkeys and/or one or more scripts according to BIPs
340 & 341.

Multiple pubkeys are aggregated according to the procedure defined by
MuSig whereby all keys are concatenated and hashed, with the resulting
hash multiplied against each pubkey to tweak it. These tweaked pub keys
are added together to produce the internal pubkey. If only one pubkey
is provided, then the internal pubkey is simply set to that. And if no
pubkeys are provided, a key with no known secret key is used instead.

Any scripts provided are arranged into a Huffman tree according to their
weighted probability and then hashed to form a merkle tree. The root
hash of this tree is then added to the internal pubkey to tweak it. This
tweaked key is finally used for creating the output script which is then
encoded using Bech32m.

Test cases in this commit include examples from the Bitcoin Optech
taproot workshop that derive a Bech32m address from scripts and an
internal key.

Follow-ups to this commit should include expanding the coverage and
number of test cases as well as possibly restructuring existing code or
moving logic to other packages/modules. We should also consider support
for taproot output descriptors, which are currently under review and
developmnet in Bitcoin Core.

Ticket: BG-35571
@sangaman sangaman closed this Sep 22, 2021
@sangaman sangaman deleted the BG-35571-p2tr-output-scripts branch September 22, 2021 21:19
@sangaman sangaman restored the BG-35571-p2tr-output-scripts branch September 22, 2021 21:22
Copy link
Member

@junderw junderw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments.


const { bech32m } = require('bech32');

/** Internal key with unknown discrete logarithm for eliminating keypath spends */
Copy link
Member

@junderw junderw Sep 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be noted that this is SHA256(uncompressedDER(GENERATOR_POINT))

Comment on lines +25 to +28
export function trimFirstByte(pubkey: Buffer): Buffer {
assert(pubkey.length === 33);
return pubkey.slice(1, 33);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really all that's needed? If this is true, then both 02 and 03 versions of pubkeys will collapse onto one pubkey value.

This doesn't seem correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I'm pretty sure this is the case. For taproot purposes, we assume the positive Y coordinate by default. This is covered in more detail in the "Implicit Y coordinates" section of BIP340. My thinking here is ideally we would work with 32 byte keys for the key addition/subtraction natively with the secp256k1 dependency, I think that's possible if it were updated to use the latest libsecp256k1 (would need to update rust-secp256k1 first I believe). For now I'm just manually trimming the first byte where necessary, though.

Comment on lines +37 to +44
// sort keys in ascending order
pubkeys.sort();

const trimmedPubkeys: Buffer[] = [];
pubkeys.forEach(pubkey => {
const trimmedPubkey = trimFirstByte(pubkey);
trimmedPubkeys.push(trimmedPubkey);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't these be sorted after trimming?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, shouldn't there be a check (and throw an error) if two pubkeys are the same?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(for p2ms we allow multiple of the same pubkey since Bitcoin allows this... but for aggregating, I think two of the same pubkey is not mathematically possible? I could be wrong.)

Copy link
Contributor Author

@sangaman sangaman Sep 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're correct about sorting after trimming. I think a sanity check here to prevent duplicate pubkeys makes sense even if it's technically allowable

@sangaman
Copy link
Contributor Author

sangaman commented Sep 23, 2021

Thanks for the comments @junderw. I actually opened this PR by mistake - I was intending to open it against our BitGo fork (here BitGo#8) for internal review before trying to merge changes upstream once they're in a more complete state.

But I appreciate the input/feedback. I was meaning to bump the discussion in #1522 about the best path forward for developing and working on these changes.

I also notice now from rereading that issue that there's a tagged hash module. I could use that here. For now I'm going to tweak the approach in my branch to make it a bit cleaner/more reusable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants