Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 10 additions & 44 deletions crypto/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import (
"fmt"
"math/big"
"regexp"

blst "github.com/supranational/blst/bindings/go"
)

var (
Expand Down Expand Up @@ -60,45 +58,33 @@ func BuildUnvote() *Transaction {
return transaction
}

// NOTE: BLS Proof-of-Possession is not yet implemented — the proof argument
// is encoded as empty bytes. This is a known, documented gap: the resulting
// transaction carries a validator public key but no proof, and will likely
// not validate on an actual Mainsail chain until PoP support is added.
func BuildValidatorRegistration(validatorPublicKey string, stake *big.Int) (*Transaction, error) {
if err := validateBLSPublicKey(validatorPublicKey); err != nil {
return nil, err
}

pubKeyBytes, err := hex.DecodeString(validatorPublicKey)
func BuildValidatorRegistration(validatorPassphrase string, stake *big.Int) (*Transaction, error) {
pop, err := FromMnemonic(validatorPassphrase)
if err != nil {
return nil, err
}

transaction := NewTransaction()
transaction.To = ContractConsensus
transaction.Value = bigIntOrZero(stake)
transaction.Data = AbiEncodeFunctionCall(AbiSignatureRegisterValidator, AbiBytes(pubKeyBytes), AbiBytes([]byte{}))
transaction.ValidatorPublicKey = validatorPublicKey
transaction.Data = AbiEncodeFunctionCall(AbiSignatureRegisterValidator, AbiBytes(pop.PK), AbiBytes(pop.POP))
transaction.ValidatorPublicKey = hex.EncodeToString(pop.PK)
transaction.ValidatorProof = hex.EncodeToString(pop.POP)

return transaction, nil
}

// NOTE: as with BuildValidatorRegistration, BLS Proof-of-Possession is not
// yet implemented; the proof argument is encoded as empty bytes.
func BuildValidatorUpdate(validatorPublicKey string) (*Transaction, error) {
if err := validateBLSPublicKey(validatorPublicKey); err != nil {
return nil, err
}

pubKeyBytes, err := hex.DecodeString(validatorPublicKey)
func BuildValidatorUpdate(validatorPassphrase string) (*Transaction, error) {
pop, err := FromMnemonic(validatorPassphrase)
if err != nil {
return nil, err
}

transaction := NewTransaction()
transaction.To = ContractConsensus
transaction.Data = AbiEncodeFunctionCall(AbiSignatureUpdateValidator, AbiBytes(pubKeyBytes), AbiBytes([]byte{}))
transaction.ValidatorPublicKey = validatorPublicKey
transaction.Data = AbiEncodeFunctionCall(AbiSignatureUpdateValidator, AbiBytes(pop.PK), AbiBytes(pop.POP))
transaction.ValidatorPublicKey = hex.EncodeToString(pop.PK)
transaction.ValidatorProof = hex.EncodeToString(pop.POP)

return transaction, nil
}
Expand Down Expand Up @@ -269,23 +255,3 @@ func BuildTokenTransfer(tokenAddress string, recipient string, amount *big.Int)

return transaction, nil
}

func validateBLSPublicKey(publicKey string) error {
if len(publicKey) != 96 {
return errors.New("invalid BLS public key length")
}

pubKeyBytes, err := hex.DecodeString(publicKey)
if err != nil {
return errors.New("invalid BLS public key hex format")
}

var pubKey blst.P1Affine
pubKey.Deserialize(pubKeyBytes)

if !pubKey.InG1() {
return errors.New("invalid BLS public key: not in G1 group or invalid structure")
}

return nil
}
33 changes: 18 additions & 15 deletions crypto/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,43 +77,46 @@ func TestBuildUnvoteRoundTrip(t *testing.T) {
assert.Equal(ContractConsensus, deserialized.To)
}

const (
validatorPassphraseFixture = "gold favorite math anchor detect march purpose such sausage crucial reform novel connect misery update episode invite salute barely garbage exclude winner visa cruise"
validatorPassphraseFixturePublicKey = "a18dba7811b212bbb2f080d7c69935998ffbe7b38586e2d3e9e12079ea789996d1c69feb158c002aed327f69865be496"
validatorPassphraseFixtureProofOfPossession = "a124539f9d469919eb57224cc003d9d5b086a27c6de244abf60b74b35fb749fceab7f4c24b983475cddab7d0876de49c000b5c362f5e3ce18d964f5c2d20d4eadcf7cb77a73d8ee4cd87bad10f7ba0824cea6715d1c045b4f93865a2758b7bfe"
)

func TestBuildValidatorRegistrationRoundTrip(t *testing.T) {
assert := assert.New(t)
require := require.New(t)

blsPublicKey := "a08058db53e2665c84a40f5152e76dd2b652125a6079130d4c315e728bcf4dd1dfb44ac26e82302331d61977d3141118"

transaction, err := BuildValidatorRegistration(blsPublicKey, big.NewInt(2_500_000_000))
transaction, err := BuildValidatorRegistration(validatorPassphraseFixture, big.NewInt(2_500_000_000))
require.NoError(err)

assert.Equal(validatorPassphraseFixturePublicKey, transaction.ValidatorPublicKey)
assert.Equal(validatorPassphraseFixtureProofOfPossession, transaction.ValidatorProof)

deserialized := signSerializeDeserialize(t, transaction)

assert.True(IsValidatorRegistration(deserialized.Data))
assert.Equal(ContractConsensus, deserialized.To)
assert.Equal(blsPublicKey, deserialized.ValidatorPublicKey)
assert.Equal(validatorPassphraseFixturePublicKey, deserialized.ValidatorPublicKey)
assert.Equal(validatorPassphraseFixtureProofOfPossession, deserialized.ValidatorProof)
assert.Equal(0, big.NewInt(2_500_000_000).Cmp(deserialized.Value))
}

func TestBuildValidatorRegistrationInvalidKeyErrors(t *testing.T) {
assert := assert.New(t)

_, err := BuildValidatorRegistration("too-short", nil)
assert.Error(err)
}

func TestBuildValidatorUpdateRoundTrip(t *testing.T) {
assert := assert.New(t)
require := require.New(t)

blsPublicKey := "a08058db53e2665c84a40f5152e76dd2b652125a6079130d4c315e728bcf4dd1dfb44ac26e82302331d61977d3141118"

transaction, err := BuildValidatorUpdate(blsPublicKey)
transaction, err := BuildValidatorUpdate(validatorPassphraseFixture)
require.NoError(err)

assert.Equal(validatorPassphraseFixturePublicKey, transaction.ValidatorPublicKey)
assert.Equal(validatorPassphraseFixtureProofOfPossession, transaction.ValidatorProof)

deserialized := signSerializeDeserialize(t, transaction)

assert.True(IsUpdateValidator(deserialized.Data))
assert.Equal(blsPublicKey, deserialized.ValidatorPublicKey)
assert.Equal(validatorPassphraseFixturePublicKey, deserialized.ValidatorPublicKey)
assert.Equal(validatorPassphraseFixtureProofOfPossession, deserialized.ValidatorProof)
}

func TestBuildValidatorResignationRoundTrip(t *testing.T) {
Expand Down
23 changes: 20 additions & 3 deletions crypto/fixtures.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ func GetBLSKeysFixture() []BLSKeyFixture {
return fixtures
}

func GetBLSMultiLangKeysFixture() map[string][]BLSMultiLangKeyFixture {
data := GetFixture("bls_multi_lang")

var fixtures map[string][]BLSMultiLangKeyFixture
_ = json.Unmarshal([]byte(data), &fixtures)

return fixtures
}

func GetTransactionFixture(name string) TestingTransactionFixture {
data := GetFile(fmt.Sprintf("./fixtures/transactions/%s.json", name))

Expand Down Expand Up @@ -91,9 +100,10 @@ type BLSValidatorFixture struct {
}

type BLSKeyFixture struct {
BLSPublicKey string `json:"bls_public_key"`
BLSPrivateKey string `json:"bls_private_key"`
Passphrase string `json:"passphrase"`
BLSPublicKey string `json:"bls_public_key"`
BLSPrivateKey string `json:"bls_private_key"`
ProofOfPossession string `json:"proof_of_possession"`
Passphrase string `json:"passphrase"`
}

type TestingTransactionFixture struct {
Expand All @@ -114,3 +124,10 @@ type TestingTransactionFixture struct {
} `json:"data"`
Serialized string `json:"serialized"`
}

type BLSMultiLangKeyFixture struct {
Mnemonic string `json:"mnemonic"`
ValidatorPrivateKey string `json:"validatorPrivateKey"`
ValidatorPublicKey string `json:"validatorPublicKey"`
ValidatorPop string `json:"validatorPop"`
}
2 changes: 2 additions & 0 deletions crypto/fixtures/bls_keys.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
{
"bls_public_key": "90507d5a1a4cde6729f61a0e8fcc34f854113faf05f995b3ffd320639c4ffd118c335099350c92daa58e9ba22ca71af1",
"bls_private_key": "710b0de2981d407d144161a5123f498a88355e3b0a559aa7942d90d49d0d5b34",
"proof_of_possession": "a2fd406fe7d8d171eed27d35f890b9f51e918724f51523cab2e030f8a1993d317871fb7358232b334e36a703abfc798201aaf8bd0043b1247dc0a676cdec5bfe047fcfb3ce35838036d8c9c6297a360b670d04c70035720d06ac8d591985550d",
"passphrase": "famous dolphin salad photo spend stock portion outdoor print fiscal element smoke silent ritual verify current better raw visual mom real bubble certain banana"
},
{
"bls_public_key": "af7f00ec30273a99411aa940ecf40646fa684f82d7d70e536a76c7c02e8ee6f3ffd297c42ad3775922938a31925bc1ab",
"bls_private_key": "5a010d4c79c01fc4d26fcce16a601da683bd2dcb67aa09d6d4ab29042020e62b",
"proof_of_possession": "a8ebf3739e4a39d1f1f148cee0448ee4e525a6dcf7dffd35c9af3234abf147d9fa252b570e731ab13dc268629c4ad0a912008d097f36a26e3f600a35acc681f790a22506a90ea0c2ee4c85ad54baed2e664ea46fafa63be9475c70e58f44da37",
"passphrase": "good copper economy hope purity budget mistake achieve tail endless travel vibrant office cement inmate gospel effort desert garbage fiscal direct siege bright habit"
}
]
Loading
Loading