Skip to content

Commit

Permalink
feat: add non-sequential AddressHashMode for multi-sig
Browse files Browse the repository at this point in the history
  • Loading branch information
janniks committed Jul 1, 2024
1 parent 9e2aa26 commit 41e9b7d
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 3 deletions.
4 changes: 4 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,12 @@ export enum TxSpendingConditionSingleSigHashMode {
export enum TxSpendingConditionMultiSigHashMode {
/** hash160(multisig-redeem-script), same as bitcoin's multisig p2sh */
P2SH = 0x01,
/** hash160(multisig-redeem-script), same as bitcoin's multisig p2sh (non-sequential signing) */
P2SHNonSequential = 0x05,
/** hash160(segwit-program-00(public-keys)), same as bitcoin's p2sh-p2wsh */
P2WSH = 0x03,
/** hash160(segwit-program-00(public-keys)), same as bitcoin's p2sh-p2wsh (non-sequential signing) */
P2WSHNonSequential = 0x07,
}

export enum ClarityVersion {
Expand Down
12 changes: 10 additions & 2 deletions src/address/stacks_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ impl StacksAddress {
pub enum AddressHashMode {
// serialization modes for public keys to addresses.
// We support four different modes due to legacy compatibility with Stacks v1 addresses:
SerializeP2PKH = 0x00, // hash160(public-key), same as bitcoin's p2pkh
SerializeP2SH = 0x01, // hash160(multisig-redeem-script), same as bitcoin's multisig p2sh
SerializeP2PKH = 0x00, // hash160(public-key), same as bitcoin's p2pkh
SerializeP2SH = 0x01, // hash160(multisig-redeem-script), same as bitcoin's multisig p2sh
SerializeP2SHNonSequential = 0x05, // hash160(multisig-redeem-script), same as bitcoin's multisig p2sh (non-sequential signing)
SerializeP2WPKH = 0x02, // hash160(segwit-program-00(p2pkh)), same as bitcoin's p2sh-p2wpkh
SerializeP2WSH = 0x03, // hash160(segwit-program-00(public-keys)), same as bitcoin's p2sh-p2wsh
SerializeP2WSHNonSequential = 0x07, // hash160(segwit-program-00(public-keys)), same as bitcoin's p2sh-p2wsh (non-sequential signing)
}

impl AddressHashMode {
Expand All @@ -70,10 +72,16 @@ impl TryFrom<u8> for AddressHashMode {
match value {
x if x == AddressHashMode::SerializeP2PKH as u8 => Ok(AddressHashMode::SerializeP2PKH),
x if x == AddressHashMode::SerializeP2SH as u8 => Ok(AddressHashMode::SerializeP2SH),
x if x == AddressHashMode::SerializeP2SHNonSequential as u8 => {
Ok(AddressHashMode::SerializeP2SHNonSequential)
}
x if x == AddressHashMode::SerializeP2WPKH as u8 => {
Ok(AddressHashMode::SerializeP2WPKH)
}
x if x == AddressHashMode::SerializeP2WSH as u8 => Ok(AddressHashMode::SerializeP2WSH),
x if x == AddressHashMode::SerializeP2WSHNonSequential as u8 => {
Ok(AddressHashMode::SerializeP2WSHNonSequential)
}
_ => Err(format!("Invalid version {}", value)),
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/stacks_tx/deserialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,13 @@ impl MultisigHashMode {
pub fn from_u8(n: u8) -> Option<MultisigHashMode> {
match n {
x if x == MultisigHashMode::P2SH as u8 => Some(MultisigHashMode::P2SH),
x if x == MultisigHashMode::P2SHNonSequential as u8 => {
Some(MultisigHashMode::P2SHNonSequential)
}
x if x == MultisigHashMode::P2WSH as u8 => Some(MultisigHashMode::P2WSH),
x if x == MultisigHashMode::P2WSHNonSequential as u8 => {
Some(MultisigHashMode::P2WSHNonSequential)
}
_ => None,
}
}
Expand Down Expand Up @@ -632,7 +638,9 @@ pub struct SinglesigSpendingCondition {
#[derive(PartialEq, Copy, Clone)]
pub enum MultisigHashMode {
P2SH = 0x01,
P2SHNonSequential = 0x05,
P2WSH = 0x03,
P2WSHNonSequential = 0x07,
}

#[repr(u8)]
Expand Down
62 changes: 61 additions & 1 deletion tests/tx-decode-3.0.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {
PrincipalTypeID,
TenureChangeCause,
TransactionVersion,
TxAuthFieldTypeID,
TxPayloadNakamotoCoinbase,
TxPayloadTypeID,
TxPublicKeyEncoding
TxPublicKeyEncoding,
TxSpendingConditionMultiSigHashMode
} from '../index.js';

test('stacks3.0 - decode tx - tenure change', () => {
Expand Down Expand Up @@ -175,3 +177,61 @@ test('stacks3.0 - decode tx - nakamoto coinbase - no alt recipient (stacks-core
expect(txType).toEqual(TxPayloadTypeID.NakamotoCoinbase);
expect(payload.recipient).not.toBeNull();
});

test("stacks 3.0 - decode tx - non-sequential multi-sig", () => {
const tx =
"8080000000040535e2fdeee173024af6848ca6e335691b55498fc4000000000000000000000000000000640000000300028bd9dd96b66534e23cbcce4e69447b92bf1d738edb83182005cfb3b402666e42020158146dc95e76926e3add7289821e983e0dd2f2b0bf464c8e94bb082a213a91067ced1381a64bd03afa662992099b04d4c3f538cc6afa3d043ae081e25ebbde6f0300e30e7e744c6eef7c0a4d1a2dad6f0daa3c7655eb6e9fd6c34d1efa87b648d3e55cdd004ca4e8637cddad3316f3fbd6146665fad2e7ca26725ad09f58c4e43aa0000203020000000000051a70f696e2bda63701e044609eb7a7ce5876571905000000000000271000000000000000000000000000000000000000000000000000000000000000000000";
const decoded = decodeTransaction(tx);
expect(decoded).toEqual({
"tx_id": "0xf7f30ad912e9433743fb614b17842e8a366a04cc882e7fd94ff59fa9c2638674",
"version": TransactionVersion.Testnet,
"chain_id": 0x80000000,
"auth": {
"type_id": PostConditionAuthFlag.Standard,
"origin_condition": {
"tx_fee": "100",
"nonce": "0",
"fields": [
{
"type_id": TxAuthFieldTypeID.PublicKeyCompressed,
"public_key":
"0x028bd9dd96b66534e23cbcce4e69447b92bf1d738edb83182005cfb3b402666e42",
},
{
"type_id": TxAuthFieldTypeID.SignatureCompressed,
"signature":
"0x0158146dc95e76926e3add7289821e983e0dd2f2b0bf464c8e94bb082a213a91067ced1381a64bd03afa662992099b04d4c3f538cc6afa3d043ae081e25ebbde6f",
},
{
"type_id": TxAuthFieldTypeID.SignatureUncompressed,
"signature":
"0x00e30e7e744c6eef7c0a4d1a2dad6f0daa3c7655eb6e9fd6c34d1efa87b648d3e55cdd004ca4e8637cddad3316f3fbd6146665fad2e7ca26725ad09f58c4e43aa0",
},
],
"hash_mode": TxSpendingConditionMultiSigHashMode.P2SHNonSequential,
"signatures_required": 2,
"signer": {
"address": "SNTY5ZFEW5SG4JQPGJ6ADRSND4DNAJCFRHVZBYR8",
"address_hash_bytes": "0x35e2fdeee173024af6848ca6e335691b55498fc4",
"address_version": 21,
},
},
},
"anchor_mode": AnchorModeID.Any,
"post_condition_mode": PostConditionModeID.Deny,
"post_conditions": [],
"post_conditions_buffer": "0x0200000000",
"payload": {
"type_id": TxPayloadTypeID.TokenTransfer,
"amount": "10000",
"recipient": {
"type_id": PrincipalTypeID.Standard,
"address": "ST1RFD5Q2QPK3E0F08HG9XDX7SSC7CNRS0QR0SGEV",
"address_hash_bytes": "0x70f696e2bda63701e044609eb7a7ce5876571905",
"address_version": 26,
},
"memo_hex":
"0x00000000000000000000000000000000000000000000000000000000000000000000",
},
});
});

0 comments on commit 41e9b7d

Please sign in to comment.