From 2d53bca12f8311fa610961c8d22a4c66562f942a Mon Sep 17 00:00:00 2001 From: Akhil Repala Date: Sun, 30 Nov 2025 15:13:28 -0600 Subject: [PATCH 1/3] feat(aws): Added string method for Voter by following CIP-129 specifications to generate bech32 encodings for each voter type Signed-off-by: Akhil Repala --- ledger/common/gov.go | 74 +++++++++++++++++++++++++++++++++++++++ ledger/common/gov_test.go | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) diff --git a/ledger/common/gov.go b/ledger/common/gov.go index 025eab86..c02569e0 100644 --- a/ledger/common/gov.go +++ b/ledger/common/gov.go @@ -15,10 +15,12 @@ package common import ( + "fmt" "math/big" "github.com/blinklabs-io/gouroboros/cbor" "github.com/blinklabs-io/plutigo/data" + "github.com/btcsuite/btcd/btcutil/bech32" ) // VotingProcedures is a convenience type to avoid needing to duplicate the full type definition everywhere @@ -38,6 +40,78 @@ type Voter struct { Hash [28]byte } +const ( + // CIP-129 key type identifiers and CIP-129 credential types mirror address. + cip129KeyTypeConstitutionalCommitteeHot uint8 = 0 + cip129KeyTypeDRep uint8 = 2 + cip129CredentialTypeKeyHash uint8 = 0x02 + cip129CredentialTypeScriptHash uint8 = 0x03 +) + +func encodeCip129Voter( + prefix string, + keyType uint8, + credentialType uint8, + hash []byte, +) string { + header := byte((keyType << 4) | (credentialType & 0x0f)) + data := make([]byte, 1+len(hash)) + data[0] = header + copy(data[1:], hash) + return encodeVoterBech32(prefix, data) +} + +func encodeVoterBech32(prefix string, data []byte) string { + convData, err := bech32.ConvertBits(data, 8, 5, true) + if err != nil { + panic(fmt.Sprintf("unexpected error converting voter data to base32: %s", err)) + } + encoded, err := bech32.Encode(prefix, convData) + if err != nil { + panic(fmt.Sprintf("unexpected error encoding voter data as bech32: %s", err)) + } + return encoded +} + +// Generates bech32-encoded identifier for the voter credential. +func (v Voter) String() string { + switch v.Type { + case VoterTypeConstitutionalCommitteeHotKeyHash: + return encodeCip129Voter( + "cc_hot", + cip129KeyTypeConstitutionalCommitteeHot, + cip129CredentialTypeKeyHash, + v.Hash[:], + ) + case VoterTypeConstitutionalCommitteeHotScriptHash: + return encodeCip129Voter( + "cc_hot", + cip129KeyTypeConstitutionalCommitteeHot, + cip129CredentialTypeScriptHash, + v.Hash[:], + ) + case VoterTypeDRepKeyHash: + return encodeCip129Voter( + "drep", + cip129KeyTypeDRep, + cip129CredentialTypeKeyHash, + v.Hash[:], + ) + case VoterTypeDRepScriptHash: + return encodeCip129Voter( + "drep", + cip129KeyTypeDRep, + cip129CredentialTypeScriptHash, + v.Hash[:], + ) + case VoterTypeStakingPoolKeyHash: + poolId := PoolId(v.Hash) + return poolId.String() + default: + panic(fmt.Sprintf("unknown voter type: %d", v.Type)) + } +} + func (v Voter) ToPlutusData() data.PlutusData { switch v.Type { case VoterTypeConstitutionalCommitteeHotScriptHash: diff --git a/ledger/common/gov_test.go b/ledger/common/gov_test.go index 4d421609..07a2afab 100644 --- a/ledger/common/gov_test.go +++ b/ledger/common/gov_test.go @@ -168,6 +168,68 @@ func TestVoterToPlutusData(t *testing.T) { } } +func TestVoterString(t *testing.T) { + var zeroHash [28]byte + var sequentialHash [28]byte + for i := range sequentialHash { + sequentialHash[i] = byte(i) + } + testCases := []struct { + name string + voter Voter + want string + }{ + { + name: "CIP129CcHotKeyHash", + voter: Voter{ + Type: VoterTypeConstitutionalCommitteeHotKeyHash, + Hash: zeroHash, + }, + want: "cc_hot1qgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqvcdjk7", + }, + { + name: "CIP129CcHotScriptHash", + voter: Voter{ + Type: VoterTypeConstitutionalCommitteeHotScriptHash, + Hash: zeroHash, + }, + want: "cc_hot1qvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv2arke", + }, + { + name: "CIP129DRepKeyHash", + voter: Voter{ + Type: VoterTypeDRepKeyHash, + Hash: zeroHash, + }, + want: "drep1ygqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq7vlc9n", + }, + { + name: "CIP129DRepScriptHash", + voter: Voter{ + Type: VoterTypeDRepScriptHash, + Hash: zeroHash, + }, + want: "drep1yvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq770f95", + }, + { + name: "StakingPoolKeyHash", + voter: Voter{ + Type: VoterTypeStakingPoolKeyHash, + Hash: sequentialHash, + }, + want: "pool1qqqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk35lkuk", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.want, tc.voter.String()) + }) + } + assert.Panics(t, func() { + _ = Voter{Type: 99}.String() + }) +} + // Tests the ToPlutusData method for Vote types func TestVoteToPlutusData(t *testing.T) { testCases := []struct { From 3c9dbff0245ddc1dcfcc6c82deda605ad23d1323 Mon Sep 17 00:00:00 2001 From: Akhil Repala Date: Sun, 7 Dec 2025 13:03:24 -0600 Subject: [PATCH 2/3] feat(ledger): Reused existing constants in voter bech32 encoding Signed-off-by: Akhil Repala --- ledger/common/gov.go | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/ledger/common/gov.go b/ledger/common/gov.go index c02569e0..59cd5877 100644 --- a/ledger/common/gov.go +++ b/ledger/common/gov.go @@ -40,28 +40,19 @@ type Voter struct { Hash [28]byte } -const ( - // CIP-129 key type identifiers and CIP-129 credential types mirror address. - cip129KeyTypeConstitutionalCommitteeHot uint8 = 0 - cip129KeyTypeDRep uint8 = 2 - cip129CredentialTypeKeyHash uint8 = 0x02 - cip129CredentialTypeScriptHash uint8 = 0x03 -) - func encodeCip129Voter( prefix string, keyType uint8, credentialType uint8, hash []byte, ) string { - header := byte((keyType << 4) | (credentialType & 0x0f)) + // Header packs the 4-bit key type (per CIP-129) in the high nibble and the credential semantics in the low nibble. + // Since CIP-129 reserves values 0 and 1,we offset the existing credential constants (0 = key hash, 1 = script hash) by 2 + // so the output nibble matches the spec's 0x2/0x3 tags. + header := byte((keyType << 4) | ((credentialType + 2) & 0x0f)) data := make([]byte, 1+len(hash)) data[0] = header copy(data[1:], hash) - return encodeVoterBech32(prefix, data) -} - -func encodeVoterBech32(prefix string, data []byte) string { convData, err := bech32.ConvertBits(data, 8, 5, true) if err != nil { panic(fmt.Sprintf("unexpected error converting voter data to base32: %s", err)) @@ -79,29 +70,29 @@ func (v Voter) String() string { case VoterTypeConstitutionalCommitteeHotKeyHash: return encodeCip129Voter( "cc_hot", - cip129KeyTypeConstitutionalCommitteeHot, - cip129CredentialTypeKeyHash, + VoterTypeConstitutionalCommitteeHotKeyHash, + CredentialTypeAddrKeyHash, v.Hash[:], ) case VoterTypeConstitutionalCommitteeHotScriptHash: return encodeCip129Voter( "cc_hot", - cip129KeyTypeConstitutionalCommitteeHot, - cip129CredentialTypeScriptHash, + VoterTypeConstitutionalCommitteeHotKeyHash, + CredentialTypeScriptHash, v.Hash[:], ) case VoterTypeDRepKeyHash: return encodeCip129Voter( "drep", - cip129KeyTypeDRep, - cip129CredentialTypeKeyHash, + VoterTypeDRepKeyHash, + CredentialTypeAddrKeyHash, v.Hash[:], ) case VoterTypeDRepScriptHash: return encodeCip129Voter( "drep", - cip129KeyTypeDRep, - cip129CredentialTypeScriptHash, + VoterTypeDRepKeyHash, + CredentialTypeScriptHash, v.Hash[:], ) case VoterTypeStakingPoolKeyHash: From 232ddab443ee75bd025f532e9626750e4d355513 Mon Sep 17 00:00:00 2001 From: Chris Gianelloni Date: Wed, 10 Dec 2025 13:47:35 -0500 Subject: [PATCH 3/3] chore: comment typo Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Signed-off-by: Chris Gianelloni --- ledger/common/gov.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/common/gov.go b/ledger/common/gov.go index 59cd5877..de5bd88e 100644 --- a/ledger/common/gov.go +++ b/ledger/common/gov.go @@ -47,7 +47,7 @@ func encodeCip129Voter( hash []byte, ) string { // Header packs the 4-bit key type (per CIP-129) in the high nibble and the credential semantics in the low nibble. - // Since CIP-129 reserves values 0 and 1,we offset the existing credential constants (0 = key hash, 1 = script hash) by 2 + // Since CIP-129 reserves values 0 and 1, we offset the existing credential constants (0 = key hash, 1 = script hash) by 2 // so the output nibble matches the spec's 0x2/0x3 tags. header := byte((keyType << 4) | ((credentialType + 2) & 0x0f)) data := make([]byte, 1+len(hash))