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

Verify calldata publickey signature #366

Open
CedarMist opened this issue Aug 27, 2024 · 2 comments
Open

Verify calldata publickey signature #366

CedarMist opened this issue Aug 27, 2024 · 2 comments
Assignees
Labels
client go Pull requests that update Go code javascript Pull requests that update JavaScript code python

Comments

@CedarMist
Copy link
Member

CedarMist commented Aug 27, 2024

A long-term keypair is used to sign the validity of the calldata pubic key.

This keypair can be hard-coded into the clients, and used to verify it.

This means the Oasis RPC servers won't be in a trusted position.

Return the runtime signing (public) key from consensus:

status, err := cons.KeyManager().Secrets().GetStatus(ctx, &registry.NamespaceQuery{Height: consensus.HeightLatest, ID: keymanagerRuntimeID})
// ...
// Get the Runtime Signing (public) Key from: status.RSK

In go code, see: https://github.com/oasisprotocol/oasis-core/blob/050a01f97be8afa6d079fda07952330035b790c1/go/keymanager/secrets/api.go#L295-L302

In rust code, see: https://github.com/oasisprotocol/oasis-core/blob/050a01f97be8afa6d079fda07952330035b790c1/keymanager/src/crypto/types.rs#L157-L170

Verification happens via:

const PUBLIC_KEY_SIGNATURE_CONTEXT: &[u8] = b"oasis-core/keymanager: pk signature";

...

        let body = Self::body(
            self.key,
            &self.checksum,
            runtime_id,
            key_pair_id,
            epoch,
            self.expiration,
        );

        self.signature
            .verify(pk, PUBLIC_KEY_SIGNATURE_CONTEXT, &body)

...
// Signature message is constructed like such

    fn body(
        key: x25519::PublicKey,
        checksum: &[u8],
        runtime_id: Namespace,
        key_pair_id: KeyPairId,
        epoch: Option<EpochTime>,
        expiration: Option<EpochTime>,
    ) -> Vec<u8> {
        let mut body = key.0.as_bytes().to_vec();
        body.extend_from_slice(checksum);
        body.extend_from_slice(runtime_id.as_ref());
        body.extend_from_slice(key_pair_id.as_ref());
        if let Some(epoch) = epoch {
            body.extend_from_slice(&epoch.to_be_bytes());
        }
        if let Some(expiration) = expiration {
            body.extend_from_slice(&expiration.to_be_bytes());
        }
        body
    }

...

    /// Compute a digest of the passed slices of bytes.
    pub fn digest_bytes_list(data: &[&[u8]]) -> Hash {
        let mut ctx = Sha512_256::new();
        for datum in data {
            ctx.update(datum);
        }

        let mut result = [0u8; 32];
        result[..].copy_from_slice(ctx.finalize().as_ref());

        Hash(result)
    }

...

// Domain separation is added when verifying signature
    pub fn verify(&self, pk: &PublicKey, context: &[u8], message: &[u8]) -> Result<()> {
        // Apply the Oasis core specific domain separation.
        //
        // Note: This should be Ed25519ctx based but "muh Ledger".
        let digest = Hash::digest_bytes_list(&[context, message]);

        self.verify_raw(pk, digest.as_ref())
    }

The hash here is a 32-bute SHA-512/256.

Note, we need the key_pair_id and runtime_id from somewhere to verify the signature, these are both 32-bytes each.

Note: Sign(sk, (key || checksum || runtime id || key pair id || epoch || expiration epoch))

@CedarMist CedarMist added go Pull requests that update Go code client python javascript Pull requests that update JavaScript code labels Aug 27, 2024
@CedarMist CedarMist self-assigned this Aug 27, 2024
@CedarMist
Copy link
Member Author

CedarMist commented Aug 31, 2024

If we're verifying ed25519 should probably migrate to @noble/curves, and a function like:

function verifyRuntimePublicKey(pk:CallDataPublicKey, runtime_id:Uint8Array, key_pair_id:Uint8Array)
{
  const PUBLIC_KEY_SIGNATURE_CONTEXT = new TextEncoder().encode("oasis-core/keymanager: pk signature");

  let body = new Uint8Array([
    ...pk.key,
    ...pk.checksum,
    ...runtime_id,
    ...key_pair_id
  ]);

  if( pk.epoch !== undefined ) {
    body = new Uint8Array([...body, ...u64tobytes(pk.epoch)]);
  }

  if( pk.expiration !== undefined ) {
    body = new Uint8Array([...body, ...u64tobytes(pk.expiration)]);
  }

  const ctx = sha512_256.create();
  ctx.update(PUBLIC_KEY_SIGNATURE_CONTEXT);
  ctx.update(body);
  const digest = ctx.digest();

  // TODO: verify pk.signature with digest and pk.key
}

Trying to determine the differences between https://github.com/oasisprotocol/oasis-core/blob/050a01f97be8afa6d079fda07952330035b790c1/runtime/src/common/crypto/signature.rs#L125 and the @noble/curves ed25519 verification. It should be straightforward, but need to figure out the details.

@noble/curves specifies ( https://github.com/paulmillr/noble-curves/blob/c1eb761af14b811822abf971b15669dacf7feec6/src/ed25519.ts#L134C1-L142C2 )

function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
  if (ctx.length > 255) throw new Error('Context is too big');
  return concatBytes(
    utf8ToBytes('SigEd25519 no Ed25519 collisions'),
    new Uint8Array([phflag ? 1 : 0, ctx.length]),
    ctx,
    data
  );
}

So, presumably we need to provide our own domain function. but ed25519Defaults isn't exported, and we can't modify the domain after:

export const ed25519ctx = /* @__PURE__ */ (() =>
  twistedEdwards({
    ...ed25519Defaults,
    domain: ed25519_domain,
  }))();

@CedarMist
Copy link
Member Author

Related to #135

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
client go Pull requests that update Go code javascript Pull requests that update JavaScript code python
Projects
None yet
Development

No branches or pull requests

1 participant