added

Plaintext signing API is now live!

Plaintext signing API is now live! This API allows you to sign a piece of text using a Palisade wallet. You can use this for the following use-cases and beyond:

  1. Prove wallet access and/or ownership: By using this API, you can sign a piece of text, proving that you have access to the wallet.
  2. Voting and attestation: You can provide your signature as a vote on DAOs.

Example

Plaintext signing API is an asynchronous API call, similar to transactions that takes a payload containing the text to sign and responds with the standard transaction object.

The key properties of the response are as follows:

  • signingHash: The hash that was signed.
  • signature: The non-canonical signature. This signature uses some random parameters and hence can be different for same payload.
  • canonicalSignature: The blockchain-compatible canonical signature that ensures the signature parameters are on the right side of the curve.

attributes

  • action: indicates the action that is set to Sign when the mode is plaintext sign.
  • request_data: The original request payload, unprefixed, that was requested to sign.
  • prefixed_request_data: The original request payload, with blockchain-specific prefix.
  • hashed_prefixed_request_data: Hash of the prefixed request data.
  • hashing_algorithm: The hashing algorithm used.

Request

POST https://api.sandbox.palisade.co/v2/vaults/019440ac-bc74-7cc3-aeed-1eb503ec3a65/wallets/019440ac-bcff-7c41-ab78-f2d656921f94/transactions/sign-plaintext
{
  "message": "Hello world"
}

Response

{
...
  "attributes": {
    "action": "Sign",
    "request_data": "Hello world",
    "prefixed_request_data": "\u0019XRP Ledger Signed Message:\n11Hello world",
    "hashed_prefixed_request_data": "b5f417c40cd8713279684a634d4c251f0adcd0666a2171eeccee3fa94c83cacd",
    "hashing_algorithm": "keccak256"
  },
  ...
  "signingHash": "b5f417c40cd8713279684a634d4c251f0adcd0666a2171eeccee3fa94c83cacd",
  "signature": "3045022100ede31b45a9e9154d9b60d8a3e3851b5c3d5dbcfb14f940ae6e522ab742537b540220228155428cd725653a9c525395acf97163c9628cc2fb11345217604d19d18a57",
  "canonicalSignature": "3045022100ede31b45a9e9154d9b60d8a3e3851b5c3d5dbcfb14f940ae6e522ab742537b540220228155428cd725653a9c525395acf97163c9628cc2fb11345217604d19d18a57",
  ...
}

The transaction will go through approvals if they are configured but no policies are needed.

Verifying the signature

In order to verify the signature correctly, you must apply the blockchain specific prefix yourself to the original message, apply the hashing algorithm and then verify the transaction signature from the transaction object. This will ensure that you are not relying on Palisade for the hashing parameters and are simply using the signature to validate whether the payload was signed correctly.

Broadly, when verifying a signature, you will need three things:

  1. Public key -- this is the public key that is on the wallet object.
  2. Hash -- the locally generated hash as mentioned above.
  3. Signature -- canonical or non-canonical signature from the transaction object.

Example (golang)

1. Decompress the public key (from wallet)

🚧

XRP Ledger wallets only

// Decode the public key into the correct format
publicKeyBytes, err := hexutil.Decode(fmt.Sprintf("0x%s", publicKey))
s.NoError(err)

// crypto is a go-ethereum/crypto package
ecdsaPubKey, err := crypto.DecompressPubkey(publicKeyBytes)
s.NoError(err)
ecdsaPubBytes := crypto.FromECDSAPub(ecdsaPubKey)

2. Validate hashes are correct (local vs signingHash in transaction object)

// Decode and verify the signing hash matches the message
data := []byte(fmt.Sprintf("\u0019XRP Ledger Signed Message:\n%d%s", len(message), message))

// crypto is a go-ethereum/crypto package
hash := crypto.Keccak256Hash(data)
s.Equal(fmt.Sprintf("0x%s", signingHash), hash.String())

3. Decode the signature from DER format into its raw form

// Decode the signature into the correct format
decodedSignature, err := hexutil.Decode(fmt.Sprintf("0x%s", signature))
s.NoError(err)

// UnmarshalSignature follows standard ASN1 rules for unmarshalling R and S from the der form
sigR, sigS, err := UnmarshalSignature(decodedSignature)
s.NoError(err)

// s.adjustSignatureLength ensures zero-byte padding to make signature-segment length to 32 bytes
rsSignature := append(s.adjustSignatureLength(sigR), s.adjustSignatureLength(sigS)...)

4. Verify the signature

// Verify the signature
// crypto is a go-ethereum/crypto package
verified := crypto.VerifySignature(ecdsaPubBytes, hash.Bytes(), rsSignature)
s.True(verified)

Limitations

  • HSM only
  • XRP Ledger wallets only
  • Hashing algorithm not configurable and is limited to keccak256